/* ==========================================================================
   XINGQI OA —— 企业级设计系统
   设计基调：深色导航 + 浅色工作区 + 蓝色主色，信息密度接近国内主流 OA
   ========================================================================== */

/* ---------- 设计令牌 ---------- */
:root {
  --brand-50:  #eff6ff;
  --brand-100: #dbeafe;
  --brand-200: #bfdbfe;
  --brand-500: #3b82f6;
  --brand:     #2563eb;
  --brand-700: #1d4ed8;
  --brand-900: #1e3a8a;

  --nav-bg:    #0f1729;
  --nav-bg-2:  #16203a;
  --nav-active:#2563eb;
  --nav-text:  #94a3b8;
  --nav-text-active: #ffffff;

  --bg:        #f4f6fa;
  --surface:   #ffffff;
  --surface-2: #f9fafb;
  --border:    #e5e9f0;
  --border-2:  #eef1f6;

  /* ⚠ 别名（第二十九轮补）：历史代码里有 8 处写了 `var(--line)`、2 处写了 `var(--text-1)`，
     而这两个变量**从来没有定义过**。`var()` 既无定义、又没写兜底值时，
     整条声明会在计算值阶段失效 —— 所以那些边框一直是**不渲染**的：
     `.notice-foot` 的虚线、`.headline-item` 的分隔线，以及当时还在的 `.team-card`
     金/银/铜描边（该板块已于第三十轮下架），全都没出来。
     这里补上别名，一次性修好，而不是去逐个改写旧代码 ——
     与「改展示、不改契约」同一口径：声明还是原来那句话，只是终于生效了。 */
  --line:      var(--border);
  --text-1:    var(--text);

  --text:      #0f1729;
  --text-2:    #475467;
  --text-3:    #98a2b3;
  --text-4:    #c4ccd8;

  --success:   #12b76a;
  --success-bg:#ecfdf3;
  --warning:   #f79009;
  --warning-bg:#fffaeb;
  --danger:    #f04438;
  --danger-bg: #fef3f2;
  --info:      #2e90fa;
  --info-bg:   #eff8ff;
  --purple:    #7c3aed;
  --purple-bg: #f5f3ff;
  --teal:      #0d9488;
  --teal-bg:   #f0fdfa;

  --r-xs: 4px;  --r-sm: 6px;  --r: 10px;  --r-lg: 14px;  --r-xl: 20px;
  /* ⚠ `--r-md` 被用过 2 处（`.rev-item` / `.conflict-box`）却从未定义 ——
     按命名梯度补成 10px（与 `--r` 同值），和 `--line` / `--text-1` 是同一轮补的同类漏网。 */
  --r-md: var(--r);
  --sh-xs: 0 1px 2px rgba(16,24,40,.05);
  --sh-sm: 0 1px 3px rgba(16,24,40,.08), 0 1px 2px rgba(16,24,40,.04);
  --sh-md: 0 4px 12px rgba(16,24,40,.08);
  --sh-lg: 0 12px 32px rgba(16,24,40,.12);
  --sh-xl: 0 24px 60px rgba(16,24,40,.18);

  /* 页面内容区的内边距 —— **唯一来源**。
     `.content` 用它，门户首屏也用它的负值把自己「顶」到页面边缘（见 .portal-hero）。
     两个数字分开写就会漂移：改了一处，首屏就与下面的卡片对不齐。 */
  --content-pad-x: 24px;
  --content-pad-t: 22px;

  --sidebar-w: 236px;
  --header-h: 60px;

  --font: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
          "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  --mono: "SF Mono", ui-monospace, Menlo, Consolas, monospace;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; font-family: var(--font); font-size: 14px; line-height: 1.6;
  color: var(--text); background: var(--bg);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
h1,h2,h3,h4,h5,p,figure { margin: 0; }
ul,ol { margin: 0; padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: none; background: none; }
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
table { border-collapse: collapse; width: 100%; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: #d0d5dd; border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: #b4bcc9; }
::-webkit-scrollbar-track { background: transparent; }

/* ==========================================================================
   登录页 —— 公司 OA 的经典两栏：左栏讲「这是哪个系统」，右栏只做登录。
   —— 为什么不是一张居中小卡片：
      登录页要同时完成两件事 —— ① 让员工确认「这是我公司的系统」，② 3 秒内登进去。
      做成 404px 的居中小卡，第 ① 件事就没地方放：内容要么被砍光，要么挤进卡片
      把表单埋在下面。两栏各司其职才是合理的分工。
   —— 配色不另造一套：左栏直接复用侧边栏的 --nav-bg 深蓝，右栏白底，
      于是「登录页」与「登录后的界面」是同一套视觉语言。
   —— 「简约」= 去掉装饰，不是去掉内容：
      去掉渐变光斑 / 网格纹理 / 玻璃拟态 / 金色点缀；
      保留品牌标、金句、简介、四条能力、版权行 —— 这些是内容。
   ========================================================================== */
.login-page { display: flex; min-height: 100vh; background: var(--bg); }

/* ---- 左：品牌区 ---- */
.login-brand {
  flex: 1 1 auto; min-width: 0; position: relative; overflow: hidden; color: #fff;
  background: linear-gradient(165deg, var(--nav-bg) 0%, #132039 58%, #17356b 100%);
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 52px 56px 40px;
}
/* 一层品牌蓝淡光，给深底一点纵深；不再堆第二个彩色光斑 */
.login-brand::before {
  content: ""; position: absolute; width: 580px; height: 580px; border-radius: 50%;
  background: radial-gradient(circle, rgba(37,99,235,.28), transparent 68%);
  top: -260px; right: -200px;
}
.login-brand > * { position: relative; z-index: 1; }

/* 品牌标 */
.login-logo { display: flex; align-items: center; gap: 11px; }
.login-logo .mark {
  width: 38px; height: 38px; border-radius: 11px; color: #fff; flex: 0 0 38px;
  background: linear-gradient(140deg, #3b82f6, #2563eb);
  display: grid; place-items: center;
  box-shadow: 0 6px 16px -6px rgba(37,99,235,.85);
}
.login-logo .name { font-size: 18px; font-weight: 700; letter-spacing: .5px; line-height: 1.2; }
.login-logo .en { font-size: 10px; color: #7d8faf; letter-spacing: 1.2px; margin-top: 3px; }

/* 金句 + 简介 */
.login-hero { max-width: 580px; margin: 46px 0 0; }
.login-hero h1 { font-size: 34px; line-height: 1.36; font-weight: 700; letter-spacing: -.5px; margin-bottom: 16px; }
.login-hero h1 em { font-style: normal; color: #93c5fd; }
.login-hero p { font-size: 14px; color: #9fb0cc; line-height: 1.85; }

/* 四条能力：图标 + 两行字，不加卡片背景（那层玻璃卡就是「不简约」的来源之一） */
.login-feats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 24px; margin-top: 34px; }
.login-feat { display: flex; align-items: flex-start; gap: 11px; }
.login-feat .ico {
  width: 30px; height: 30px; border-radius: 8px; flex: 0 0 30px; color: #93c5fd;
  background: rgba(147,197,253,.10); border: 1px solid rgba(147,197,253,.16);
  display: grid; place-items: center;
}
.login-feat .t { font-size: 13px; font-weight: 600; color: #e6edf8; margin-bottom: 3px; line-height: 1.3; }
.login-feat .d { font-size: 11.5px; color: #8195b5; line-height: 1.6; }

.login-foot { font-size: 11.5px; color: #5b6b88; display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 40px; }

/* ---- 右：登录区 ---- */
.login-panel {
  flex: 0 0 460px; min-width: 0; background: #fff;
  display: flex; flex-direction: column; justify-content: center;
  padding: 48px 46px;
}
.login-panel h2 { font-size: 22px; font-weight: 700; letter-spacing: -.3px; }
.login-panel .sub { color: var(--text-3); font-size: 12.5px; margin: 6px 0 26px; }
.login-form .field { margin-bottom: 16px; }
.login-form label { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-2); margin-bottom: 6px; }
.login-tip { margin-top: 14px; text-align: center; font-size: 11px; color: var(--text-3); }

/* 演示账号（只应存在于演示环境，正式上线整块删除） */
.login-demo { margin-top: 24px; padding-top: 18px; border-top: 1px dashed var(--border); }
.login-demo-h { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-3); margin-bottom: 10px; }
/* 11 个账号限高滚动：既不把右栏撑过一屏，也不让它们消失 */
.demo-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
  max-height: 130px; overflow-y: auto; padding-right: 2px;
}
.demo-grid::-webkit-scrollbar { width: 5px; }
.demo-grid::-webkit-scrollbar-thumb { background: #d4dbe6; border-radius: 3px; }
.demo-chip {
  display: flex; align-items: center; gap: 8px; padding: 6px 8px;
  border: 1px solid var(--border); border-radius: 8px; background: #fff;
  text-align: left; transition: .15s;
}
.demo-chip:hover { border-color: var(--brand-200); background: var(--brand-50); }
.demo-chip .txt { min-width: 0; }
.demo-chip .n { display: block; font-size: 11.5px; font-weight: 600; line-height: 1.3; color: var(--text-1); }
.demo-chip .r { display: block; font-size: 10px; color: var(--text-3); line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }



/* ==========================================================================
   应用骨架
   ========================================================================== */
.app { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w); flex: 0 0 var(--sidebar-w); background: var(--nav-bg);
  display: flex; flex-direction: column; position: fixed; inset: 0 auto 0 0; z-index: 50;
  overflow: hidden;
}
.brand-toggle {
  margin-left: auto; color: #5b6b88; padding: 4px; border-radius: 6px; transition: .15s;
}
.brand-toggle:hover { color: #d7dee9; background: rgba(255,255,255,.08); }
.sidebar-brand {
  height: var(--header-h); display: flex; align-items: center; gap: 10px; padding: 0 18px;
  border-bottom: 1px solid rgba(255,255,255,.07); flex: 0 0 var(--header-h);
}
.sidebar-brand .mark {
  width: 32px; height: 32px; border-radius: 9px;
  background: linear-gradient(140deg, #2563eb, #1d4ed8);
  display: grid; place-items: center; color: #fff; flex: 0 0 32px;
}
.sidebar-brand .txt { line-height: 1.2; }
.sidebar-brand .txt b { color: #fff; font-size: 14.5px; letter-spacing: .3px; display: block; }
.sidebar-brand .txt span { color: #5b6b88; font-size: 10.5px; letter-spacing: .6px; }

.nav { flex: 1; overflow-y: auto; padding: 12px 10px 20px; }
.nav::-webkit-scrollbar-thumb { background: #2a3752; }
.nav-group { margin-bottom: 4px; }
.nav-group-title {
  font-size: 10.5px; color: #4d5c78; font-weight: 700; letter-spacing: 1px;
  padding: 14px 10px 6px; text-transform: uppercase;
}
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: 8px;
  color: var(--nav-text); font-size: 13.5px; transition: .15s; width: 100%; text-align: left;
  position: relative; margin-bottom: 1px;
}
.nav-item:hover { background: rgba(255,255,255,.06); color: #d7dee9; }
.nav-item.active { background: var(--nav-active); color: #fff; box-shadow: 0 4px 14px rgba(37,99,235,.4); }
.nav-item.active .ico { opacity: 1; }
.nav-item .ico { width: 17px; height: 17px; flex: 0 0 17px; opacity: .82; }
.nav-item .badge-count {
  margin-left: auto; background: var(--danger); color: #fff; font-size: 10.5px; font-weight: 700;
  min-width: 18px; height: 18px; border-radius: 9px; display: grid; place-items: center; padding: 0 5px;
}
.nav-item.active .badge-count { background: rgba(255,255,255,.28); }

.sidebar-user {
  border-top: 1px solid rgba(255,255,255,.07); padding: 12px 14px;
  display: flex; align-items: center; gap: 10px;
}
.sidebar-user .av { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-weight: 600; font-size: 13px; flex: 0 0 34px; }
.sidebar-user .info { flex: 1; min-width: 0; }
.sidebar-user .info b { color: #e4e8ef; font-size: 13px; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-user .info span { color: #5b6b88; font-size: 11px; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-user .logout { color: #5b6b88; padding: 6px; border-radius: 6px; transition: .15s; }
.sidebar-user .logout:hover { color: var(--danger); background: rgba(240,68,56,.12); }

.main { flex: 1; margin-left: var(--sidebar-w); display: flex; flex-direction: column; min-width: 0; }

/* ---- 侧边栏收起态：深色细图标条（56px，与展开态同一底色），鼠标移入直接展开 ---- */
:root { --sidebar-collapsed-w: 56px; }

/* 收起（默认） */
.app.nav-mini .sidebar {
  width: var(--sidebar-collapsed-w);
  border-right: 1px solid rgba(255,255,255,.06); box-shadow: none;
}
.app.nav-mini .main { margin-left: var(--sidebar-collapsed-w); }
.app.nav-mini .sidebar-brand { padding: 0 12px; justify-content: center; }
.app.nav-mini .sidebar-brand .txt { display: none; }
.app.nav-mini .brand-toggle { display: none; }
.app.nav-mini .nav { padding: 12px 8px 20px; }
.app.nav-mini .nav::-webkit-scrollbar { width: 0; }        /* 细条内不显滚动条（深色thumb会露出黑边） */
.app.nav-mini .nav-group-title { padding: 14px 0 6px; font-size: 0; text-align: center; }
.app.nav-mini .nav-group-title::before { content: '···'; font-size: 11px; letter-spacing: 1px; color: #3d4c68; }
.app.nav-mini .nav-item { padding: 9px 0; justify-content: center; border-radius: 10px; color: #8fa0bb; }
.app.nav-mini .nav-item:hover { background: rgba(255,255,255,.07); color: #d7dee9; }
.app.nav-mini .nav-item.active { color: #fff; box-shadow: 0 4px 14px rgba(37,99,235,.4); }
.app.nav-mini .nav-item .ico { opacity: 1; }
.app.nav-mini .nav-item .lbl { display: none; }
.app.nav-mini .nav-item .badge-count {
  position: absolute; top: 3px; right: 6px; min-width: 15px; height: 15px;
  font-size: 9.5px; padding: 0 4px;
}
.app.nav-mini .sidebar-user { padding: 12px 10px; justify-content: center; border-top-color: rgba(255,255,255,.07); }
.app.nav-mini .sidebar-user .info,
.app.nav-mini .sidebar-user .logout { display: none; }

/* 悬停展开：与常规侧栏完全一致（文字立即出现，无「半展开」中间态） */
.app.nav-mini .sidebar:hover {
  width: var(--sidebar-w); box-shadow: var(--sh-xl);
}
.app.nav-mini .sidebar:hover .sidebar-brand { padding: 0 18px; justify-content: flex-start; }
.app.nav-mini .sidebar:hover .sidebar-brand .txt { display: block; }
.app.nav-mini .sidebar:hover .brand-toggle { display: block; margin-left: auto; }
.app.nav-mini .sidebar:hover .nav { padding: 12px 10px 20px; }
.app.nav-mini .sidebar:hover .nav::-webkit-scrollbar { width: 8px; }
.app.nav-mini .sidebar:hover .nav-group-title { padding: 14px 10px 6px; font-size: 10.5px; text-align: left; }
.app.nav-mini .sidebar:hover .nav-group-title::before { content: none; }
.app.nav-mini .sidebar:hover .nav-item {
  padding: 9px 11px; justify-content: flex-start; border-radius: 8px; color: var(--nav-text);
}
.app.nav-mini .sidebar:hover .nav-item:hover { background: rgba(255,255,255,.06); color: #d7dee9; }
.app.nav-mini .sidebar:hover .nav-item.active { color: #fff; }
.app.nav-mini .sidebar:hover .nav-item .lbl { display: inline; }
.app.nav-mini .sidebar:hover .nav-item .badge-count {
  position: static; min-width: 18px; height: 18px; font-size: 10.5px; padding: 0 5px;
}
.app.nav-mini .sidebar:hover .sidebar-user { padding: 12px 14px; justify-content: flex-start; }
.app.nav-mini .sidebar:hover .sidebar-user .info { display: flex; }
.app.nav-mini .sidebar:hover .sidebar-user .logout { display: block; }

.header {
  height: var(--header-h); background: rgba(255,255,255,.88); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 16px;
  padding: 0 24px; position: sticky; top: 0; z-index: 40;
}
.header .crumb { font-size: 13px; color: var(--text-3); display: flex; align-items: center; gap: 7px; }
.header .crumb b { color: var(--text); font-size: 16px; font-weight: 700; letter-spacing: -.2px; }
.header .spacer { flex: 1; }
.header-btn {
  width: 34px; height: 34px; border-radius: 8px; display: grid; place-items: center;
  color: var(--text-2); position: relative; transition: .15s;
}
.header-btn:hover { background: var(--surface-2); color: var(--brand); }
.header-btn .dot { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); border: 2px solid #fff; }
.header-search {
  display: flex; align-items: center; gap: 8px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 7px 12px; width: 240px; color: var(--text-3); font-size: 13px;
}
.header-search input { border: none; background: none; outline: none; flex: 1; font-size: 13px; }
.header-search input::placeholder { color: var(--text-4); }

.content { padding: var(--content-pad-t) var(--content-pad-x) 60px; flex: 1; }
.page-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 18px; flex-wrap: wrap; }
.page-head .t h2 { font-size: 21px; font-weight: 700; letter-spacing: -.3px; }
.page-head .t p { color: var(--text-3); font-size: 13px; margin-top: 3px; }
.page-head .actions { margin-left: auto; display: flex; gap: 9px; flex-wrap: wrap; }

/* ==========================================================================
   卡片 / 面板
   ========================================================================== */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--sh-xs); }
.card-head {
  padding: 15px 18px; border-bottom: 1px solid var(--border-2);
  display: flex; align-items: center; gap: 12px;
}
.card-head h3 { font-size: 14.5px; font-weight: 700; }
.card-head .sub { font-size: 12px; color: var(--text-3); }
.card-head .actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }

/* ---- 卡片头当「筛选行」用时的控件宽度 ----
   `.input` / `.select` 默认 `width: 100%`，那是为**表单字段撑满一栏**写的。
   可它们也被放进 `.card-head` 这条 flex 行当筛选用 —— 此时 `width:100%`
   被解析成「吃掉整行」：一行里几个控件互相抢宽，最后统统收缩到 min-content。
   观察到的症状：
     · 员工档案：三个下拉各占 381px，把搜索框挤到 46px，「共 300 名员工」中文逐字竖排（23px 宽 96px 高）
     · 费用报销：两个下拉各被撑到 1320px，各自独占一整行
     · 任务中心 / 办公用品：单个下拉被撑到 915px / 883px，把 `.grow` 弹性占位挤成 0
   修法是把宽度交还给调用方：下拉给固定宽、允许换行、按钮与计数不参与收缩。
   ⚠ 只作用于 `.card-head` 的直接子元素，**不影响表单里的 `.field > .select`**（那里仍撑满一栏）。 */
.card-head { flex-wrap: wrap; }
.card-head > .select { width: 180px; flex: 0 0 180px; }
.card-head .input-icon-wrap { flex: 0 0 auto; }
.card-head > .btn, .card-head .actions { flex: 0 0 auto; white-space: nowrap; }

.card-body { padding: 18px; }
.card-body.tight { padding: 0; }

.grid { display: grid; gap: 16px; }
.grid.c2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.grid.c3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.grid.c4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
.grid.c23 { grid-template-columns: minmax(0,1.6fr) minmax(0,1fr); }
.grid.c32 { grid-template-columns: minmax(0,1fr) minmax(0,1.6fr); }
@media (max-width: 1240px) {
  .grid.c4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .grid.c3, .grid.c23, .grid.c32 { grid-template-columns: 1fr; }
}
@media (max-width: 900px) { .grid.c2 { grid-template-columns: 1fr; } }

/* 指标卡 */
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 17px 18px; position: relative; overflow: hidden; }
.stat .lbl { font-size: 12.5px; color: var(--text-3); display: flex; align-items: center; gap: 7px; }
.stat .val { font-size: 27px; font-weight: 700; letter-spacing: -.8px; margin: 9px 0 3px; font-variant-numeric: tabular-nums; }
.stat .val small { font-size: 13px; font-weight: 600; color: var(--text-3); margin-left: 3px; letter-spacing: 0; }
.stat .foot { font-size: 11.5px; color: var(--text-3); display: flex; align-items: center; gap: 5px; }
.stat .ico-bg {
  position: absolute; right: 14px; top: 14px; width: 38px; height: 38px; border-radius: 10px;
  display: grid; place-items: center;
}
.stat.accent { background: linear-gradient(135deg,#1d4ed8,#3b82f6); border-color: transparent; color: #fff; }
.stat.accent .lbl, .stat.accent .foot { color: rgba(255,255,255,.8); }
.stat.accent .val small { color: rgba(255,255,255,.75); }
.stat.accent .ico-bg { background: rgba(255,255,255,.18); }

/* 可切换卡片里的时间范围切换器（第四十轮：项目看板「工时」卡）
   放在卡内而不是页头 —— 切换器跟着它控制的那张卡走，不容易看错行。 */
.stat-switch { display: flex; flex-direction: column; }
.stat-seg { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-top: 10px; }
.stat-seg button {
  padding: 3px 9px; font-size: 11px; line-height: 1.6; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); color: var(--text-3); border: 1px solid var(--border);
  transition: .14s;
}
.stat-seg button:hover { border-color: var(--brand); color: var(--brand); }
.stat-seg button.active { background: var(--brand); border-color: var(--brand); color: #fff; }
@media (max-width: 720px) {
  .stat-seg { margin-top: 8px; }
  .stat-seg button { padding: 3px 8px; font-size: 10.5px; }
}

.trend-up { color: var(--success); } .trend-down { color: var(--danger); }

/* ==========================================================================
   按钮
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 15px; border-radius: 8px; font-size: 13px; font-weight: 600;
  border: 1px solid transparent; transition: .15s; white-space: nowrap; line-height: 1.4;
}
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary { background: var(--brand); color: #fff; box-shadow: 0 1px 2px rgba(37,99,235,.25); }
.btn-primary:hover:not(:disabled) { background: var(--brand-700); box-shadow: 0 4px 12px rgba(37,99,235,.32); }
.btn-default { background: var(--surface); color: var(--text-2); border-color: var(--border); box-shadow: var(--sh-xs); }
.btn-default:hover:not(:disabled) { border-color: var(--brand-200); color: var(--brand); background: var(--brand-50); }
.btn-ghost { color: var(--text-2); }
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--brand); }
.btn-success { background: var(--success); color: #fff; }
.btn-success:hover:not(:disabled) { background: #0e9f5c; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover:not(:disabled) { background: #d92d20; }
.btn-danger-outline { border-color: #fda29b; color: var(--danger); background: var(--surface); }
.btn-danger-outline:hover:not(:disabled) { background: var(--danger-bg); }
.btn-sm { padding: 5px 11px; font-size: 12.5px; border-radius: 7px; }
.btn-xs { padding: 3px 9px; font-size: 12px; border-radius: 6px; }
.btn-lg { padding: 11px 20px; font-size: 14.5px; border-radius: 9px; }
.btn-block { width: 100%; }
.btn .spin { width: 13px; height: 13px; border: 2px solid rgba(255,255,255,.4); border-top-color: #fff; border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   表单
   ========================================================================== */
.field { margin-bottom: 15px; }
.field > label { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-2); margin-bottom: 6px; }
.field > label .req { color: var(--danger); margin-left: 2px; }
.input, .select, .textarea {
  width: 100%; padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); outline: none; transition: .15s; font-size: 13.5px;
}
.input:hover, .select:hover, .textarea:hover { border-color: #cfd6e2; }
.input:focus, .select:focus, .textarea:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(37,99,235,.12); }
.input::placeholder, .textarea::placeholder { color: var(--text-4); }
.textarea { resize: vertical; min-height: 84px; line-height: 1.7; }
.select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5 6 7.5 9 4.5' stroke='%2398a2b3' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 11px center; padding-right: 30px; cursor: pointer; }
.input.with-icon { padding-left: 34px; }
.input-icon-wrap { position: relative; }
.input-icon-wrap .ic { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--text-4); pointer-events: none; }
.field .help { font-size: 11.5px; color: var(--text-3); margin-top: 5px; }
.field .err { font-size: 11.5px; color: var(--danger); margin-top: 5px; }
.form-row { display: grid; gap: 14px; }
.form-row.c2 { grid-template-columns: 1fr 1fr; }
.form-row.c3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 760px) { .form-row.c2, .form-row.c3 { grid-template-columns: 1fr; } }

.checkbox, .radio { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 13px; user-select: none; }
.checkbox input, .radio input { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; margin: 0; }
.switch { position: relative; width: 38px; height: 21px; flex: 0 0 38px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .track { position: absolute; inset: 0; background: #d0d5dd; border-radius: 11px; transition: .2s; }
.switch .track::before { content: ""; position: absolute; width: 17px; height: 17px; border-radius: 50%; background: #fff; top: 2px; left: 2px; transition: .2s; box-shadow: var(--sh-sm); }
.switch input:checked + .track { background: var(--brand); }
.switch input:checked + .track::before { transform: translateX(17px); }

/* ==========================================================================
   表格
   ========================================================================== */
.table-wrap { overflow-x: auto; }
table.tbl { font-size: 13px; }
table.tbl th {
  text-align: left; padding: 11px 14px; background: var(--surface-2); color: var(--text-2);
  font-weight: 600; font-size: 12.5px; white-space: nowrap; border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 2;
}
table.tbl td { padding: 12px 14px; border-bottom: 1px solid var(--border-2); vertical-align: middle; }
table.tbl tbody tr { transition: .12s; }
table.tbl tbody tr:hover { background: #fafbfd; }
table.tbl tbody tr:last-child td { border-bottom: none; }
table.tbl td.num, table.tbl th.num { text-align: right; font-variant-numeric: tabular-nums; }
table.tbl td.mono { font-family: var(--mono); font-size: 12px; color: var(--text-2); }
table.tbl .row-actions { display: flex; gap: 4px; justify-content: flex-end; }
table.tbl.compact td, table.tbl.compact th { padding: 8px 12px; }
.amount { font-variant-numeric: tabular-nums; font-weight: 600; }
.amount.big { font-size: 15px; }

/* 头像 & 人员单元 */
.avatar { border-radius: 50%; display: inline-grid; place-items: center; color: #fff; font-weight: 600; flex: 0 0 auto; }
.avatar.sm { width: 25px; height: 25px; font-size: 10.5px; }
.avatar.md { width: 32px; height: 32px; font-size: 12.5px; }
.avatar.lg { width: 44px; height: 44px; font-size: 16px; }
.avatar.xl { width: 64px; height: 64px; font-size: 23px; }
.person { display: flex; align-items: center; gap: 9px; min-width: 0; }
.person .nm { min-width: 0; }
.person .nm b { display: block; font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.person .nm span { display: block; font-size: 11.5px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==========================================================================
   徽章 / 标签
   ========================================================================== */
.badge {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: 20px;
  font-size: 11.5px; font-weight: 600; line-height: 1.7; white-space: nowrap;
}
.badge .dt { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.badge.gray    { background: #f2f4f7; color: #475467; }
.badge.blue    { background: var(--brand-50); color: var(--brand-700); }
.badge.green   { background: var(--success-bg); color: #027a48; }
.badge.orange  { background: var(--warning-bg); color: #b54708; }
.badge.red     { background: var(--danger-bg); color: #b42318; }
.badge.purple  { background: var(--purple-bg); color: #5b21b6; }
.badge.teal    { background: var(--teal-bg); color: #115e59; }
.badge.solid   { background: var(--brand); color: #fff; }

/* ---------- 公告标签 chip：类型 / 状态两个体系（第二十八轮） ----------
   见 `web/ui.js` 的 `noticeKindTag()` / `noticeTopTag()` —— 标签的**判断**只在那一处。
   设计口径：**先让形态分家，再让颜色分工**。
     · 类型 `.cat`  = soft（浅底 + 细边 + 图标），全站**同一个颜色** —— 区分靠图标，不靠颜色；
       读者只学一条规则「带图标的浅蓝牌 = 类型」，不必记「紫=制度 / 青=活动」四组映射。
     · 状态 `.mark` = solid（实底 + 白字），暖色只留给「要你注意」的：置顶琥珀、紧急红。
   动颜色前先问一句：**这个颜色有没有被读者用来反推含义？**
   反例（改前的状态）：四类型各一色，置顶又借了其中「红」—— 红色同时意味「紧急」和「置顶」。
   ⚠ 实底两色都用深色（#b54708 / #b42318）而不是亮色，是为了白字达标 ——
     `--danger`(#f04438) 配白字只有 3.3:1，小字不合格。 */
.chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px; border-radius: var(--r-xs);
  font-size: 11.5px; font-weight: 600; line-height: 1.7; white-space: nowrap;
  flex: 0 0 auto;
}
.chip > svg { flex: 0 0 auto; }
.chip.cat { background: var(--brand-50); color: var(--brand-700); border: 1px solid rgba(37,99,235,.18); }
/* 实底牌补 1px 透明边框，与类型牌同高 —— 否则两种牌并排时基线差 1px */
.chip.mark { border: 1px solid transparent; }
.chip.mark.top { background: #b54708; color: #fff; }
.chip.mark.urgent { background: #b42318; color: #fff; }

.tag { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: var(--r-xs); background: var(--surface-2); border: 1px solid var(--border); font-size: 11.5px; color: var(--text-2); }

/* 优先级 */
.prio { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; }
.prio::before { content: ""; width: 3px; height: 12px; border-radius: 2px; background: currentColor; }
.prio.urgent { color: var(--danger); }
.prio.high { color: var(--warning); }
.prio.normal { color: var(--text-3); }
.prio.low { color: var(--text-4); }

/* ==========================================================================
   进度条
   ========================================================================== */
.progress { height: 6px; background: #eef1f6; border-radius: 4px; overflow: hidden; }
.progress > i { display: block; height: 100%; border-radius: 4px; background: linear-gradient(90deg,#3b82f6,#2563eb); transition: width .4s; }
.progress.green > i { background: linear-gradient(90deg,#34d399,#12b76a); }
.progress.orange > i { background: linear-gradient(90deg,#fbbf24,#f79009); }
.progress.red > i { background: linear-gradient(90deg,#f97066,#f04438); }
.progress-row { display: flex; align-items: center; gap: 10px; }
.progress-row .pct { font-size: 12px; font-weight: 600; color: var(--text-2); font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; }

/* ==========================================================================
   弹窗 / 抽屉
   ========================================================================== */
.overlay {
  position: fixed; inset: 0; background: rgba(16,24,40,.45); backdrop-filter: blur(2px);
  z-index: 200; display: flex; animation: fadeIn .16s ease;
}
@keyframes fadeIn { from { opacity: 0; } }
.overlay.center { align-items: center; justify-content: center; padding: 24px; }
.overlay.right { justify-content: flex-end; }

.modal {
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-xl);
  width: 100%; max-width: 620px; max-height: 88vh; display: flex; flex-direction: column;
  animation: popIn .2s cubic-bezier(.2,.9,.3,1.2);
}
.modal.wide { max-width: 880px; }
.modal.narrow { max-width: 460px; }
@keyframes popIn { from { opacity: 0; transform: scale(.96) translateY(8px); } }
.modal-head { padding: 17px 20px; border-bottom: 1px solid var(--border-2); display: flex; align-items: center; gap: 12px; }
.modal-head h3 { font-size: 16px; font-weight: 700; flex: 1; }
.modal-head .x { color: var(--text-3); padding: 5px; border-radius: 6px; transition: .15s; }
.modal-head .x:hover { background: var(--surface-2); color: var(--text); }
.modal-body { padding: 20px; overflow-y: auto; flex: 1; }
/* ⚠ 类名撞车修复（第二十一轮发现）：`.overlay.center` 里的 `center` 本意是「居中**定位**」，
   却同时命中了通用工具类 `.center { text-align: center }`（见下方排版工具区）——
   于是**每一个弹窗的文本都被悄悄居中**。文字型弹窗（如人物墙名片）一眼就看出来不对，
   表单弹窗因为 label 有各自的规则、或干脆看不出差别，藏了十几轮。
   这里把继承来的居中拨回去：弹窗内容默认左对齐，需要居中的地方自己显式声明。 */
.overlay.center .modal-body { text-align: left; }
.modal-foot { padding: 14px 20px; border-top: 1px solid var(--border-2); display: flex; gap: 9px; justify-content: flex-end; background: var(--surface-2); border-radius: 0 0 var(--r-lg) var(--r-lg); }

.drawer {
  background: var(--surface); box-shadow: var(--sh-xl); width: 100%; max-width: 640px;
  height: 100%; display: flex; flex-direction: column; animation: slideIn .22s cubic-bezier(.2,.9,.3,1);
}
.drawer.wide { max-width: 860px; }
@keyframes slideIn { from { transform: translateX(40px); opacity: .4; } }
.drawer-head { padding: 17px 22px; border-bottom: 1px solid var(--border-2); display: flex; align-items: center; gap: 12px; flex: 0 0 auto; }
.drawer-head h3 { font-size: 16.5px; font-weight: 700; flex: 1; }
.drawer-body { padding: 22px; overflow-y: auto; flex: 1; }
.drawer-foot { padding: 14px 22px; border-top: 1px solid var(--border-2); display: flex; gap: 9px; justify-content: flex-end; flex: 0 0 auto; background: var(--surface-2); }

/* ==========================================================================
   Toast
   ========================================================================== */
.toast-root { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 400; display: flex; flex-direction: column; gap: 9px; align-items: center; pointer-events: none; }
.toast {
  background: #1d2939; color: #fff; padding: 11px 17px; border-radius: 9px; font-size: 13.5px;
  box-shadow: var(--sh-lg); display: flex; align-items: center; gap: 9px; animation: toastIn .22s cubic-bezier(.2,.9,.3,1.2);
  max-width: 520px; pointer-events: auto;
}
@keyframes toastIn { from { opacity: 0; transform: translateY(-14px); } }
.toast.out { animation: toastOut .2s forwards; }
@keyframes toastOut { to { opacity: 0; transform: translateY(-14px); } }
.toast.success { background: #05321f; }
.toast.error { background: #4d1211; }
.toast.warn { background: #4a2b06; }
.toast .ic { flex: 0 0 16px; }

/* ==========================================================================
   Tabs / 分段控件
   ========================================================================== */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 18px; overflow-x: auto; }
.tab {
  padding: 10px 15px; font-size: 13.5px; font-weight: 600; color: var(--text-3);
  border-bottom: 2px solid transparent; margin-bottom: -1px; transition: .15s; white-space: nowrap;
  display: flex; align-items: center; gap: 6px;
}
.tab:hover { color: var(--brand); }
.tab.active { color: var(--brand); border-bottom-color: var(--brand); }
.tab .cnt { background: var(--surface-2); color: var(--text-3); font-size: 11px; padding: 0 6px; border-radius: 9px; height: 17px; display: inline-grid; place-items: center; font-weight: 700; }
.tab.active .cnt { background: var(--brand-50); color: var(--brand-700); }

.segment { display: inline-flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 2px; gap: 2px; }
.segment button { padding: 5px 13px; font-size: 12.5px; font-weight: 600; color: var(--text-2); border-radius: 6px; transition: .15s; }
.segment button.active { background: var(--surface); color: var(--brand); box-shadow: var(--sh-xs); }

/* ==========================================================================
   分页
   ========================================================================== */
.pager { display: flex; align-items: center; gap: 8px; padding: 14px 16px; border-top: 1px solid var(--border-2); flex-wrap: wrap; }
.pager .info { font-size: 12.5px; color: var(--text-3); margin-right: auto; }
.pager .page-btn {
  min-width: 30px; height: 30px; padding: 0 8px; border-radius: 7px; border: 1px solid var(--border);
  background: var(--surface); font-size: 12.5px; font-weight: 600; color: var(--text-2); transition: .15s;
  display: inline-grid; place-items: center;
}
.pager .page-btn:hover:not(:disabled) { border-color: var(--brand-200); color: var(--brand); }
.pager .page-btn.active { background: var(--brand); border-color: var(--brand); color: #fff; }
.pager .page-btn:disabled { opacity: .45; cursor: not-allowed; }

/* ==========================================================================
   空状态 / 加载
   ========================================================================== */
.empty { padding: 46px 20px; text-align: center; color: var(--text-3); }
.empty .ic { margin: 0 auto 12px; width: 46px; height: 46px; border-radius: 13px; background: var(--surface-2); display: grid; place-items: center; color: var(--text-4); }
.empty p { font-size: 13px; }
.empty .hint { font-size: 12px; color: var(--text-4); margin-top: 5px; }

.skeleton { background: linear-gradient(90deg,#eef1f6 25%,#f7f9fc 37%,#eef1f6 63%); background-size: 400% 100%; animation: shimmer 1.3s ease infinite; border-radius: 6px; }
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
.loading-box { padding: 44px; display: grid; place-items: center; color: var(--text-3); font-size: 13px; gap: 12px; }
.loader { width: 26px; height: 26px; border: 2.5px solid var(--brand-100); border-top-color: var(--brand); border-radius: 50%; animation: spin .8s linear infinite; }

/* ==========================================================================
   时间线（审批流转）
   ========================================================================== */
.timeline { position: relative; padding-left: 4px; }
.tl-item { display: flex; gap: 13px; padding-bottom: 20px; position: relative; }
.tl-item:last-child { padding-bottom: 0; }
.tl-item::before { content: ""; position: absolute; left: 14px; top: 30px; bottom: 0; width: 1.5px; background: var(--border); }
.tl-item:last-child::before { display: none; }
.tl-dot { width: 29px; height: 29px; border-radius: 50%; display: grid; place-items: center; flex: 0 0 29px; z-index: 1; background: var(--surface-2); border: 1.5px solid var(--border); color: var(--text-3); }
.tl-dot.done { background: var(--success-bg); border-color: #a6f4c5; color: #027a48; }
.tl-dot.reject { background: var(--danger-bg); border-color: #fecdca; color: #b42318; }
.tl-dot.active { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: 0 0 0 4px rgba(37,99,235,.14); }
.tl-body { flex: 1; min-width: 0; padding-top: 2px; }
.tl-body .hd { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.tl-body .hd b { font-size: 13.5px; font-weight: 600; }
.tl-body .hd time { font-size: 11.5px; color: var(--text-3); margin-left: auto; }
.tl-body .cmt { margin-top: 7px; background: var(--surface-2); border: 1px solid var(--border-2); border-radius: 8px; padding: 9px 12px; font-size: 12.5px; color: var(--text-2); line-height: 1.65; }

/* ==========================================================================
   项目卡片
   ========================================================================== */
.proj-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 17px 18px; transition: .18s; cursor: pointer; position: relative; overflow: hidden; }
.proj-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-md); transform: translateY(-2px); }
.proj-card .hd { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.proj-card .code { font-size: 11px; font-family: var(--mono); color: var(--text-3); }
.proj-card h4 { font-size: 14.5px; font-weight: 700; line-height: 1.45; margin-bottom: 3px; }
.proj-card .cust { font-size: 12px; color: var(--text-3); display: flex; align-items: center; gap: 5px; }
.proj-card .meta { display: flex; gap: 16px; margin: 13px 0 12px; font-size: 12px; color: var(--text-3); flex-wrap: wrap; }
.proj-card .meta b { color: var(--text); font-weight: 600; }
.proj-card .ft { display: flex; align-items: center; gap: 10px; padding-top: 12px; border-top: 1px solid var(--border-2); }

/* 看板列 */
.kanban { display: grid; grid-template-columns: repeat(4, minmax(230px, 1fr)); gap: 14px; align-items: start; }
@media (max-width: 1100px) { .kanban { grid-template-columns: repeat(2, minmax(230px,1fr)); } }
@media (max-width: 680px) { .kanban { grid-template-columns: 1fr; } }
.kb-col { background: #eef1f6; border-radius: var(--r); padding: 12px; min-height: 140px; }
.kb-col-hd { display: flex; align-items: center; gap: 8px; padding: 2px 4px 11px; font-size: 13px; font-weight: 700; }
.kb-col-hd .cnt { background: #fff; color: var(--text-3); font-size: 11px; padding: 0 7px; border-radius: 9px; height: 18px; display: inline-grid; place-items: center; }
.kb-col-hd .dt { width: 7px; height: 7px; border-radius: 50%; }
.kb-card { background: #fff; border: 1px solid var(--border); border-radius: 9px; padding: 12px 13px; margin-bottom: 9px; cursor: pointer; transition: .15s; }
.kb-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-sm); }
.kb-card .t { font-size: 13px; font-weight: 600; line-height: 1.5; margin-bottom: 8px; }
.kb-card .meta { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--text-3); flex-wrap: wrap; }
.kb-card .meta .sep { color: var(--text-4); }

/* ==========================================================================
   组织架构树
   ========================================================================== */
.org-layout { display: grid; grid-template-columns: 268px minmax(0,1fr); gap: 16px; align-items: start; }
@media (max-width: 960px) { .org-layout { grid-template-columns: 1fr; } }
/* 组织架构窄屏自适应：窗口变窄时左侧部门树改为「上方可折叠区块」，不再压到人员表上 */
.org-tree-toggle { display: none; }
@media (max-width: 1280px) {
  .org-layout { grid-template-columns: 1fr !important; }
  .org-layout > section.card {
    position: static !important;
    top: auto !important;
    margin-bottom: 12px;
  }
  .org-layout .dept-tree { max-height: 46vh; overflow-y: auto; }
  .org-tree-toggle { display: inline-flex; margin-bottom: 8px; }
}
@media (max-width: 720px) {
  .org-layout .dept-tree { max-height: 38vh; }
}
.dept-tree { padding: 12px; }
.tree-node {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px;
  cursor: pointer; transition: .14s; font-size: 13px; width: 100%; text-align: left;
}
.tree-node:hover { background: var(--surface-2); }
.tree-node.active { background: var(--brand-50); color: var(--brand-700); font-weight: 600; }
.tree-node .cnt { margin-left: auto; font-size: 11px; color: var(--text-3); background: var(--surface-2); padding: 0 6px; border-radius: 8px; }
.tree-node.active .cnt { background: var(--brand-100); color: var(--brand-700); }
.tree-children { padding-left: 18px; }
.tree-toggle { width: 16px; height: 16px; display: grid; place-items: center; color: var(--text-3); flex: 0 0 16px; transition: .15s; }

.dept-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(218px,1fr)); gap: 13px; }
.dept-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 15px 16px; cursor: pointer; transition: .16s; }
.dept-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-sm); }
.dept-card .nm { font-size: 14px; font-weight: 700; margin-bottom: 3px; }
.dept-card .cd { font-size: 11px; font-family: var(--mono); color: var(--text-3); margin-bottom: 12px; }
.dept-card .ft { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-3); }
.dept-card .hc { font-size: 21px; font-weight: 700; color: var(--brand); letter-spacing: -.5px; }

/* ==========================================================================
   权限矩阵
   ========================================================================== */
.perm-group { margin-bottom: 18px; }
.perm-group > .gh { font-size: 13px; font-weight: 700; padding: 8px 0; border-bottom: 1px solid var(--border-2); margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
.perm-group > .gh .n { font-size: 11px; color: var(--text-3); font-weight: 600; }
.perm-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 9px; }
.perm-item { display: flex; align-items: center; gap: 9px; padding: 9px 12px; border: 1px solid var(--border); border-radius: 8px; transition: .14s; }
.perm-item:hover { border-color: var(--brand-200); background: var(--brand-50); }
.perm-item.on { border-color: var(--brand-200); background: var(--brand-50); }
.perm-item .txt { min-width: 0; flex: 1; }
.perm-item .txt b { font-size: 12.5px; font-weight: 600; display: block; }
.perm-item .txt code { font-size: 10.5px; color: var(--text-3); font-family: var(--mono); }

.role-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 16px; cursor: pointer; transition: .16s; }
.role-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-sm); }
.role-card.active { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(37,99,235,.1); }
.role-card .hd { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
.role-card .hd b { font-size: 14px; font-weight: 700; }
.role-card .desc { font-size: 12px; color: var(--text-3); line-height: 1.65; min-height: 40px; }
.role-card .ft { display: flex; gap: 14px; margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--border-2); font-size: 11.5px; color: var(--text-3); }
.role-card .ft b { color: var(--brand); font-size: 14px; }

/* ---------- 权限矩阵（第三十九轮）----------
   一张表看完「谁有什么」。11 个角色 × 70 个权限点 = 770 格，
   所以三件必需品：表头吸顶、首列吸左、容器内滚动（不把整页撑宽）。 */
.mx-wrap { max-height: 620px; overflow: auto; border: 1px solid var(--border-2); border-radius: 10px; }
.perm-matrix { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 12px; }
.perm-matrix th, .perm-matrix td { padding: 0; }
.perm-matrix thead th {
  position: sticky; top: 0; z-index: 3; background: var(--surface-2);
  padding: 9px 10px; text-align: left; vertical-align: bottom;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
.perm-matrix thead th.mx-corner { left: 0; z-index: 4; min-width: 210px; }
.perm-matrix thead th.mx-role { min-width: 84px; }
.perm-matrix thead th.mx-role div { font-size: 12px; font-weight: 700; color: var(--text); }
.perm-matrix thead th.mx-role code { font-size: 9.5px; color: var(--text-3); font-family: var(--mono); }
/* 模块分隔行：整行铺满，读矩阵时靠它分段 */
.perm-matrix tbody tr.mx-mod td {
  position: sticky; left: 0; background: var(--brand-50);
  padding: 7px 12px; font-size: 11.5px; color: var(--brand);
  border-top: 1px solid var(--border-2); border-bottom: 1px solid var(--border-2);
}
.perm-matrix tbody tr.mx-mod b { font-weight: 700; }
.perm-matrix tbody tr.mx-mod span { color: var(--brand); opacity: .7; }
/* 权限点列：吸左，长名字不换行撑高 */
.perm-matrix th.mx-perm {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  padding: 7px 12px; text-align: left; min-width: 210px; max-width: 260px;
  border-bottom: 1px solid var(--border-2); font-weight: 400;
}
.perm-matrix th.mx-perm b { display: block; font-size: 12px; font-weight: 600; color: var(--text); }
.perm-matrix th.mx-perm code { font-size: 10px; color: var(--text-3); font-family: var(--mono); }
/* 单元格 */
.perm-matrix td.mx-cell {
  width: 84px; text-align: center; cursor: pointer; color: var(--text-3);
  border-bottom: 1px solid var(--border-2); border-left: 1px solid var(--border-2);
  transition: background .12s;
}
.perm-matrix td.mx-cell.on { color: var(--brand); font-weight: 700; background: var(--brand-50); }
.perm-matrix td.mx-cell:hover { background: var(--brand-50); }
/* 超管恒全量：格子是锁定的，点了不会改任何东西，所以光标也别骗人 */
.perm-matrix td.mx-cell.lock { cursor: not-allowed; opacity: .75; }
.perm-matrix td.mx-cell.lock:hover { background: transparent; }
.perm-matrix tbody tr:hover th.mx-perm { background: var(--brand-50); }
@media (max-width: 720px) {
  .mx-wrap { max-height: 460px; }
  .perm-matrix thead th.mx-corner, .perm-matrix th.mx-perm { min-width: 150px; max-width: 170px; }
  .perm-matrix thead th.mx-role, .perm-matrix td.mx-cell { min-width: 68px; width: 68px; }
}

/* ==========================================================================
   图表
   ========================================================================== */
.chart-box { padding: 4px 0; }
.chart-svg { width: 100%; display: block; overflow: visible; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 14px; justify-content: center; }
.chart-legend .li { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); }
.chart-legend .li .sw { width: 9px; height: 9px; border-radius: 2.5px; }
.chart-legend .li .vl { color: var(--text-3); font-variant-numeric: tabular-nums; }

/* 条形排行 */
.bar-list { display: flex; flex-direction: column; gap: 13px; }
.bar-item .hd { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; margin-bottom: 6px; }
.bar-item .hd .nm { flex: 1; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-item .hd .vl { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   列表项（待办 / 通知）
   ========================================================================== */
.list { display: flex; flex-direction: column; }
.list-item { display: flex; align-items: center; gap: 12px; padding: 13px 18px; border-bottom: 1px solid var(--border-2); transition: .13s; cursor: pointer; }
.list-item:last-child { border-bottom: none; }
.list-item:hover { background: #fafbfd; }
.list-item.unread { background: #f8fbff; }
.list-item .body { flex: 1; min-width: 0; }
.list-item .body .t { font-size: 13px; font-weight: 600; margin-bottom: 2px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.list-item .body .d { font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-item .body .m { font-size: 11.5px; color: var(--text-4); margin-top: 3px; }
.list-item .tail { text-align: right; flex: 0 0 auto; }

/* 紧凑列表（第二十八轮）：门户里的公告流是「扫一眼」的辅助信息，比公告列表页更密 ——
   `.list-item` 的 13px 行高是按「列表页的正文条目」定的，搬到门户当侧栏用就偏松了。
   做成开关类而不是改全局，是为了**不牵连其它页的列表**（公告页、报销页仍要松一些）。 */
/* ⚠ 这一块的高度**不由左边那篇文章决定** —— 实测（1470px）左列自然高 315、右列 363，
   grid 行高取二者 max，所以「把头条图压小」对整块高度毫无作用，力气全在右边这 6 条上。
   动这一块之前，先把两列的自然高量出来（见 `docs/VERIFICATION.md` 第二十八轮）。 */
.list.compact .list-item { padding: 7px 14px; }
/* 单行条目（第二十八轮）：`.body` 从「标题一行 + 作者日期一行」改成一行的两端对齐 ——
   标题占满剩余宽度、作者与日期靠右。**两行并一行是这块明显变小的主力**
   （每条 66 → 42px，6 条就省下 140px），比把字号压小有效得多，可读性还更好。 */
.list.compact .list-item .body { display: flex; align-items: center; gap: 10px; }
/* ⚠ `flex-wrap: nowrap` 必须显式写：全局 `.list-item .body .t` 是 `wrap`（列表页允许标题与状态牌
   分两行），不覆盖的话长标题会把「置顶」牌挤到第二行 —— 那条就从 35px 变成 59px，
   一列里出现一根高出来的条目，整块的「整齐」就没了。改为 nowrap 后由标题自己省略。 */
.list.compact .list-item .body .t {
  display: flex; align-items: center; gap: 6px; flex-wrap: nowrap;
  flex: 1 1 auto; min-width: 0; margin-bottom: 0; font-size: 12.5px;
}
.list.compact .list-item .body .t .tt {
  flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.list.compact .list-item .body .d { flex: 0 0 auto; font-size: 11px; }
/* 可点击的暗示：静态列表最大的问题是「看不出来能点」 */
.list.compact .list-item:hover { background: var(--brand-50); }
.list.compact .list-item:hover .body .t .tt { color: var(--brand); }

/* 公告区底部汇总条：替掉标题旁那个「共 N 条」——「有几条没看」才是有行动价值的信息 */
.notice-foot {
  display: flex; align-items: center; gap: 16px;
  margin: 4px 0 0; padding: 8px 14px 0;
  border-top: 1px dashed var(--line);
  font-size: 11.5px; color: var(--text-3);
}
.notice-foot b { color: var(--danger); font-weight: 600; }

/* 公告列表左侧的图标方块（第二十八轮统一配色）：
   原先按类型上四色，与右侧类型牌重复表达同一维度 —— 现在统一品牌蓝，色彩只用来表达状态。 */
.notice-ic {
  width: 38px; height: 38px; border-radius: 10px; flex: 0 0 38px;
  display: grid; place-items: center; margin-top: 2px;
  background: var(--brand-50); color: var(--brand);
}
/* 标签在紧凑列表里也要跟着瘦：`.chip` 的基础 line-height 1.7 是按「一行里有呼吸」定的，
   搬到每条只有 ~48px 的列表里，标签本身就吃掉近半行高（实测 `.t` 高 28px，其中文字只占 20px）。 */
.list.compact .chip { font-size: 11px; line-height: 1.45; padding: 0 6px; gap: 3px; }

/* 通知面板 */
.notif-panel { width: 372px; max-height: 76vh; display: flex; flex-direction: column; background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-xl); animation: popIn .2s cubic-bezier(.2,.9,.3,1.2); overflow: hidden; }
.notif-head { padding: 15px 18px; border-bottom: 1px solid var(--border-2); display: flex; align-items: center; gap: 10px; }
.notif-head h3 { font-size: 15px; font-weight: 700; flex: 1; }
.notif-list { overflow-y: auto; }

/* ==========================================================================
   审批详情布局
   ========================================================================== */
.detail-grid { display: grid; grid-template-columns: minmax(0,1.55fr) minmax(0,1fr); gap: 18px; align-items: start; }
@media (max-width: 1040px) { .detail-grid { grid-template-columns: 1fr; } }
.kv { display: grid; grid-template-columns: 96px minmax(0,1fr); gap: 8px 14px; font-size: 13px; }
.kv dt { color: var(--text-3); font-size: 12.5px; }
.kv dd { margin: 0; font-weight: 500; word-break: break-all; }
.kv.rows { display: block; }
.kv.rows .row { display: flex; padding: 9px 0; border-bottom: 1px dashed var(--border-2); }
.kv.rows .row:last-child { border-bottom: none; }
.kv.rows .row .k { width: 100px; color: var(--text-3); font-size: 12.5px; flex: 0 0 100px; }
.kv.rows .row .v { flex: 1; word-break: break-all; }

.amount-hero { text-align: center; padding: 22px 0 18px; }
.amount-hero .lb { font-size: 12.5px; color: var(--text-3); }
.amount-hero .am { font-size: 36px; font-weight: 700; letter-spacing: -1.2px; margin-top: 6px; font-variant-numeric: tabular-nums; }

.notice-content { font-size: 14px; line-height: 2; color: var(--text-2); white-space: pre-wrap; }

/* ==========================================================================
   工具类
   ========================================================================== */
.row { display: flex; align-items: center; gap: 10px; }
.row.wrap { flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.col { display: flex; flex-direction: column; gap: 10px; }
.grow { flex: 1; min-width: 0; }
.ml-auto { margin-left: auto; }
.mt-0 { margin-top: 0; } .mt-4 { margin-top: 4px; } .mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; } .mt-16 { margin-top: 16px; } .mt-20 { margin-top: 20px; }
.mb-0 { margin-bottom: 0; } .mb-8 { margin-bottom: 8px; } .mb-12 { margin-bottom: 12px; } .mb-16 { margin-bottom: 16px; }
.gap-4 { gap: 4px; } .gap-6 { gap: 6px; } .gap-8 { gap: 8px; } .gap-12 { gap: 12px; } .gap-16 { gap: 16px; }
.t-2 { color: var(--text-2); } .t-3 { color: var(--text-3); } .t-4 { color: var(--text-4); }
.fs-11 { font-size: 11px; } .fs-12 { font-size: 12px; } .fs-13 { font-size: 13px; } .fs-14 { font-size: 14px; }
.fw-6 { font-weight: 600; } .fw-7 { font-weight: 700; }
.mono { font-family: var(--mono); }
.num { font-variant-numeric: tabular-nums; }
.center { text-align: center; } .right { text-align: right; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.divider { height: 1px; background: var(--border-2); margin: 16px 0; }
.hidden { display: none !important; }
.nowrap { white-space: nowrap; }

/* 登录页响应式：宽屏两栏 → 收窄左栏 → 隐去品牌栏、表单占满整屏 */
@media (max-width: 1180px) {
  .login-brand { padding: 40px 40px 32px; }
  .login-hero { margin-top: 34px; }
  .login-hero h1 { font-size: 28px; }
  .login-panel { flex: 0 0 420px; padding: 40px 34px; }
}
@media (max-width: 900px) {
  .login-brand { display: none; }
  .login-panel { flex: 1 1 auto; padding: 40px 28px; }
}
@media (max-width: 480px) {
  .login-panel { padding: 28px 20px; }
  .demo-grid { max-height: none; }
}
@media (max-width: 860px) {
  .sidebar { transform: translateX(-100%); transition: transform .22s; }
  .sidebar.open { transform: none; box-shadow: var(--sh-xl); }
  .main { margin-left: 0; }
  /* 移动端抽屉不受「收起态」影响：整体还原为常规深色侧栏 */
  .app.nav-mini .sidebar { width: var(--sidebar-w); background: var(--nav-bg); border-right: none; }
  .app.nav-mini .main { margin-left: 0; }
  .app.nav-mini .sidebar-brand { padding: 0 18px; justify-content: flex-start; }
  .app.nav-mini .sidebar-brand .txt { display: block; }
  .app.nav-mini .brand-toggle { display: block; margin-left: auto; }
  .app.nav-mini .nav { padding: 12px 10px 20px; }
  .app.nav-mini .nav-group-title { font-size: 10.5px; letter-spacing: 1px; padding: 14px 10px 6px; text-align: left; }
  .app.nav-mini .nav-group-title::before { content: none; }
  .app.nav-mini .nav-item { padding: 9px 11px; justify-content: flex-start; border-radius: 8px; color: var(--nav-text); }
  .app.nav-mini .nav-item:hover { background: rgba(255,255,255,.06); color: #d7dee9; }
  .app.nav-mini .nav-item.active { box-shadow: 0 4px 14px rgba(37,99,235,.4); }
  .app.nav-mini .nav-item .lbl { display: inline; }
  .app.nav-mini .nav-item .badge-count { position: static; min-width: 18px; height: 18px; font-size: 10.5px; padding: 0 5px; }
  .app.nav-mini .sidebar-user { padding: 12px 14px; justify-content: flex-start; }
  .app.nav-mini .sidebar-user .info { display: flex; }
  .app.nav-mini .sidebar-user .logout { display: block; }
  .content { padding: var(--content-pad-t) var(--content-pad-x) 50px; }
  .header { padding: 0 14px; }
  .header-search { display: none; }
  .menu-toggle { display: grid !important; }
}
.menu-toggle { display: none; }

/* ==========================================================================
   了不起的他们 · 人物墙（第十七轮建 → 第二十一轮参考稿改版 → 第二十二轮白底谐调）
   --------------------------------------------------------------------------
   视觉口径：**一面墙只讲一种语言 —— 灰度照片 + 中性文字卡。**
   全墙带色的只有两处：口号砖（红/黑/白）与悬停时的品牌红描边；
   人物的专属色一律退到名片里，一次只放大一个人。
   ⚠ 第二十一轮曾给每人一块「专属色底 + 姓名末字大字」，结果 31 个高饱和彩块
     与 11 张灰度工照并排，两套视觉语言打架 —— **这就是「不协调」的根源**。
   ⚠ 颜色变量一律声明在 `.tw-page` 上，**不要提到 `:root`** ——
     红色一旦进全局，会顺着 `var()` 渗进按钮、链接、告警条，全站配色就毁了。
   ⚠⚠ **但 `.tw-page` 一个选择器不够 —— 名片弹窗挂在 `#overlay-root` 下，不在 `.tw-page` 里。**
     只挂 `.tw-page` 时，弹窗内**所有** `var(--tw-*)` 都是空值，于是整条声明
     在**计算值阶段静默失效**（元素在、文本在、类名对，静态检查与 DOM 断言全都看不见）。
     第三十六轮实测到的三处症状，全都是这一个原因：
       · 部门标签 `.tw-tag`：`background` 失效 → 透明底 + `color:#fff` = **白字白底，完全看不见**；
       · 引言 `.tw-pquote`：`border-left` 失效 → **左侧红竖条宽度算成 0**，从来没渲染过；
       · 标签 `.tw-ptag` / 数字格 `.tw-fact`：`background: var(--tw-bg)` 失效 → 透明 →
         与白卡同色 → **整片退化成裸文字**（看起来像「被禁用的浅色标签」）。
     ⇒ 所以变量块必须**同时挂 `.tw-page` 与 `.tw-profile`**（名片根类）。
       新增作用域时照此办理：**声明处必须覆盖每一处使用处的祖先链**。
       ⚠ 守它的是 `tests/probe-teamcard.mjs`（真机量计算值）——
         这类缺陷静态检查一个字也说不出，jsdom 也没有布局与级联计算。
   ⚠ 不引外部字体（参考稿用的 Archivo Black / Noto Sans SC 在断网内网会掉回默认字体、
     版式全变）；改用系统字体栈 + 900 字重 + 紧字距近似「超粗黑体」的观感。
   ========================================================================== */
.tw-page,
.tw-profile {
  --tw-ink: #141414;
  --tw-ink-soft: #5f5e5a;
  --tw-red: #d6202a;
  --tw-red-deep: #a8141d;
  --tw-bg: #ffffff;          /* 白底（第二十二轮，用户指定） */
  --tw-wall: #ecebe7;        /* 仅名片人像位的底色 */
  --tw-tile: #f0eee9;        /* ⭐ 全墙统一的瓷砖底色 —— 不再每人一色 */
  --tw-line: #e8e7e3;
  --tw-sh1: 0 1px 2px rgba(20,20,20,.05);
  --tw-sh2: 0 10px 26px rgba(20,20,20,.14), 0 2px 6px rgba(20,20,20,.07);
  --tw-ease: cubic-bezier(.22,.9,.28,1);
  background: var(--tw-bg); border-radius: var(--r-lg);
  box-shadow: 0 1px 2px rgba(16,24,40,.04);
  color: var(--tw-ink); overflow: hidden;
}

/* ---------- 标题区 ---------- */
.tw-hero { padding: 38px 34px 28px; }
.tw-kicker {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 12px; font-weight: 700; letter-spacing: .35em; color: var(--tw-red);
}
.tw-kicker::before { content: ""; width: 26px; height: 3px; background: var(--tw-red); }
.tw-h1 {
  margin-top: 15px; font-size: clamp(30px, 4.6vw, 58px); font-weight: 900;
  line-height: 1.06; letter-spacing: -.02em; color: var(--tw-ink);
}
.tw-h1 .tw-red { color: var(--tw-red); }
.tw-sub { margin-top: 14px; max-width: 640px; font-size: 13.5px; line-height: 1.85; color: var(--tw-ink-soft); }
/* `flex-wrap: wrap` 不是为了窄屏 —— 也为了让版面探针不把这一行当「flex 行控件被压扁」来查 */
.tw-meta { margin-top: 22px; display: flex; flex-wrap: wrap; gap: 22px 36px; font-size: 12px; color: var(--tw-ink-soft); }
.tw-meta b {
  display: block; font-size: 26px; font-weight: 900; letter-spacing: -.02em;
  color: var(--tw-ink); font-variant-numeric: tabular-nums;
}

/* ---------- 墙 ---------- */
.tw-scene { padding: 6px 34px 38px; }

/* 工具条：搜索 + 分组筛选（第三十八轮，墙上从 42 人变成全体在职员工后才有必要） */
.tw-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
  padding: 0 34px 16px;
}
.tw-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.tw-chip {
  padding: 5px 13px; font-size: 12.5px; border-radius: 999px; cursor: pointer;
  background: #fff; color: var(--tw-ink-soft);
  border: 1px solid var(--tw-line); transition: all .18s var(--tw-ease);
}
.tw-chip:hover { border-color: var(--tw-red); color: var(--tw-red); }
.tw-chip.on { background: var(--tw-red); border-color: var(--tw-red); color: #fff; }
.tw-empty { padding: 34px 0 10px; text-align: center; font-size: 13px; color: var(--tw-ink-soft); }

/* 部门标题行：**整行砖**（`grid-column: 1/-1`），让一面墙读懂组织架构。
   它不是 `.tw-tile` —— 没有 aspect-ratio，不参与「每块砖一样大」那条约束。 */
.tw-sect {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 10px; padding: 14px 2px 6px;
  border-bottom: 1px solid var(--tw-line);
}
.tw-sect:first-child { margin-top: 0; }
.tw-sect i {
  font-style: normal; font-size: 10px; font-weight: 700;
  letter-spacing: .22em; text-transform: uppercase; color: var(--tw-red);
}
.tw-sect b { font-size: 15.5px; font-weight: 900; letter-spacing: -.01em; color: var(--tw-ink); }
.tw-sect span { margin-left: auto; font-size: 11.5px; color: var(--tw-ink-soft); }

/* 列数由 `auto-fill` 随宽度自己算（1470 视口下 10 列 · 格子 121px 见方）。
   ⚠ 第二十四轮起**所有砖等大**，不再有跨 2 列的格子 —— 原先「必须让 4 个跨格 + 2 个普通格
     凑满 10 列、否则右边挂出孤列」的约束已经不存在了（那是 21–23 轮的事）。
     这里仍保持 110px：网格在这个宽度下最周正，格子也还认得出人。
   ⚠ 第三十八轮起墙上装的是**全体在职员工**（300 人量级）+ 部门标题行，
     所以砖多、行多；性能靠 `.tw-tile` 的懒加载照片与筛选时只改 display 来兜。 */
.tw-wall {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 10px; transform-style: preserve-3d; transition: transform .35s var(--tw-ease);
}
.tw-tile {
  position: relative; aspect-ratio: 1; background: var(--tw-tile);
  border-radius: 6px; box-shadow: var(--tw-sh1); overflow: hidden;
  opacity: 0; transform: translateY(22px) scale(.95);
  transition: opacity .5s var(--tw-ease), transform .4s var(--tw-ease), box-shadow .3s var(--tw-ease);
}
.tw-tile.tw-in { opacity: 1; transform: none; }
.tw-person { cursor: pointer; }
/* 悬停：品牌红描边 + 抬起。**全墙统一用红**，不用本人的专属色 ——
   42 种色相一旦同时铺在墙上就乱；「红描边」本身就是这套视觉的签名。 */
.tw-person:hover { box-shadow: 0 0 0 2px var(--tw-red), var(--tw-sh2); z-index: 5; transform: translateY(-3px) scale(1.04); }
/* ⚠ 照片**绝对定位填满**，不能只写 `width/height: 100%`。
   `height: 100%` 在 auto 高度的容器里会退回 `auto` → 照片按自身比例撑高，
   而栅格行高取「本行最高的一项」，于是**一张照片就把整行拉到 250px**：
   第二十三轮把核心格从 2×2 收成 2×1 时，四个横幅死活不降高就是这个原因 ——
   横向拉伸的格子一旦被内容顶高，`aspect-ratio: auto` 也救不回来。
   绝对定位后照片**完全不参与布局**，格子高度只由栅格说了算。 */
.tw-photo {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
  filter: grayscale(1) contrast(1.05);
  transition: filter .35s var(--tw-ease), transform .5s var(--tw-ease);
}
.tw-person:hover .tw-photo { filter: grayscale(0) contrast(1); transform: scale(1.04); }

/* 文字卡：细红线 + 姓名 + 岗位。
   **与照片格同档次**，不是占位符 —— 42 人里只有 11 人有工照，缺照片是常态。
   它给出的信息（姓名 / 岗位）与照片格底部的姓名条完全一致，只是呈现方式不同。
   ⚠ 字号不能再小：这本是一面「认人」的墙，姓名必须一眼读得出（15px 时第二行几乎糊掉）。 */
.tw-face {
  width: 100%; height: 100%; padding: 10px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; text-align: center;
}
.tw-face-bar { width: 24px; height: 3px; background: var(--tw-red); flex: 0 0 auto; }
.tw-face-name {
  font-size: 18px; font-weight: 900; line-height: 1.25; letter-spacing: .01em;
  color: var(--tw-ink); max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tw-face-pos {
  font-size: 10px; line-height: 1.4; letter-spacing: .05em;
  color: var(--tw-ink-soft); max-width: 100%;
}

/* 姓名条：**只有照片格才有**（照片上没写名字，需要它）。
   文字卡格自带姓名与岗位，底部就不该再压一条 —— 那是同一份信息重复两遍。
   ⚠ 判据是 `.tw-has-photo` 而不是「有没有 img」：照片到达那一刻是**先打标记、再换节点**。
   ⚠ 隐藏用 `opacity` + `transform` 两个一起，**不能只靠 translateY** ——
     只位移的话元素自身仍是「可见」的，版面探针会把它当成溢出到容器外的文本查。 */
.tw-plate {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: linear-gradient(180deg, rgba(16,16,16,0), rgba(16,16,16,.86) 46%);
  color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .03em; line-height: 1.3;
  text-align: center; padding: 16px 6px 7px;
  opacity: 0; transform: translateY(100%);
  transition: transform .32s var(--tw-ease), opacity .28s var(--tw-ease);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tw-has-photo .tw-plate { opacity: 1; transform: none; }
.tw-plate i { font-style: normal; opacity: .78; font-weight: 500; }
/* ⭐ 第二十四轮**删掉了「核心格」整段**（原 `.tw-big` / `.tw-role` / `.tw-big .tw-*`）。
   用户定稿「所有格子一样大」，于是：跨列没了、深色底没了、部门角标没了。
   ⚠ **不要因为「核心成员应该更显眼」再把它们加回来** —— 三轮迭代的轨迹是
     2×2（4 倍面积）→ 2×1（2 倍面积）→ 取消：每一次收敛都是因为大格吃掉了整面墙的视觉重心。
     这面墙的立意是「每一块砖都算数」，尺寸分层与它本来就相冲。
   要表达层级时优先考虑**口号砖的节奏**，而不是把某些人格子放大。 */

/* 口号砖：红 / 黑 / 白三变体，兼作视觉节奏与「缺照片」的缓冲 */
.tw-say { display: grid; place-items: center; text-align: center; padding: 10px; }
.tw-say-in { display: block; }
/* 眉标必须 `nowrap` —— 「SINCE 2021」这类长眉标在 112px 的砖里会折成两三行，
   既难看、又会被版面探针当成「文本硬折行」缺陷报出来（探针只认字形宽度与行数）。 */
.tw-tick {
  display: block; font-style: normal; font-size: 9px; font-weight: 500;
  letter-spacing: .22em; opacity: .72; margin-bottom: 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tw-say b, .tw-say-in > span {
  display: block; font-size: 16px; font-weight: 900;
  letter-spacing: .06em; line-height: 1.35;
}
.tw-say.red { background: linear-gradient(150deg, var(--tw-red), var(--tw-red-deep)); color: #fff; }
.tw-say.dark { background: #1c1c1c; color: #fff; }
.tw-say.white { background: #fff; color: var(--tw-ink); box-shadow: inset 0 0 0 1px var(--tw-line); }
.tw-say.white b { color: var(--tw-red); }

/* ---------- 页脚 ---------- */
.tw-foot {
  padding: 0 34px 32px; text-align: center;
  font-size: 11.5px; letter-spacing: .18em; color: var(--tw-ink-soft);
}
.tw-bar { display: block; width: 42px; height: 3px; background: var(--tw-red); margin: 0 auto 16px; }

/* ---------- 名片弹窗（左右分栏） ----------
   负 margin 抵掉 `.modal-body` 的 20px 内边距，让照片**贴到弹窗边缘**；
   右下/左下用同一套圆角，否则照片的直角会盖住弹窗的 14px 圆角。

   ⭐ 第三十六轮改版：**姓名压在照片底部的深色渐变上**。
   旧版左边是「中性灰底 + 灰度照 + 右下角红斜渐晕」、右边一张纯白卡 ——
   两块底色不同、照片与文字区完全割裂，这就是用户说的「两张皮」。
   现在照片本身成为版面的一部分，右侧不再把姓名 / 职位写第二遍。 */
.tw-profile {
  display: grid; grid-template-columns: 300px minmax(0, 1fr);
  margin: -20px; border-radius: 0 0 var(--r-lg) var(--r-lg); overflow: hidden;
}
.tw-portrait { position: relative; background: var(--tw-wall); min-height: 340px; overflow: hidden; }
/* ⭐ 人像层的**中性底**（第三十六轮补规则）—— 照片未到达时的占位底就靠它。
   为什么补：这一层原先**只有 `position/inset`、没有任何背景**，而「有照片」这条路
   看起来完全正常 —— 因为父层 `.tw-portrait` 也有一份 `background: var(--tw-wall)`，
   占位底色是**碰巧**被父层兜住的。于是「占位层」这个说法只是注释里的一句承诺：
   谁哪天把父层底色改掉，占位层会跟着一起塌，而静态检查、`node --check`、
   DOM 断言**一句都不会说**。
   ⚠ 也别把这条当成「跟父层重复」删掉 —— 父层那次兜底是**巧合**，不是契约。
   ⚠⚠ 更别把它做成一个只在加载窗口内存在的修饰类（如 `tw-pblank`）：照片到达时
      `.tw-portrait-in` 会被 `replaceWith()` **整个换掉**（team-wall.js），
      换出来的新节点不带那个类 —— 于是「有没有底色」这件事**只在某一瞬间成立**，
      任何事后断言都测不到它。底色属于**这一层**，就该写在**这一层**的基类上。 */
.tw-portrait-in { position: absolute; inset: 0; background: var(--tw-wall); }
/* ⭐ 名片里**不回灰度**（第三十六轮）。
   墙面要灰度，是因为一面墙上 11 张彩照 + 31 张文字卡会互相打架（第二十二轮定的口径）；
   名片里只放大一个人，没有「互相打架」这个前提 —— 而用户要求的正是「点击之后认识本人」，
   这一点上彩色照片比灰度更称职。
   ⚠ 因此这里**没有**「悬停回彩」那条规则：默认就是彩的，再叠一层悬停没有意义。 */
.tw-portrait-in img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 无照片（42 人里 31 人，**这才是主场景**）：`墙上文字卡的放大版` ——
   专属色淡底（JS 注入）+ 红细线 + 姓名 + 岗位。不是一块没有内容的空色块。 */
.tw-portrait-in.tw-portrait-face { display: flex; align-items: flex-end; padding: 26px 22px; }
.tw-pface { min-width: 0; }
.tw-pbar { display: block; width: 26px; height: 3px; background: var(--tw-red); margin-bottom: 12px; }

/* 有照片：底部深色渐变承住姓名。渐变从透明起跳，照片上沿保持干净。 */
.tw-pcover {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 52px 22px 20px;
  background: linear-gradient(to top,
    rgba(16, 18, 24, .78), rgba(16, 18, 24, .30) 48%, rgba(16, 18, 24, 0));
}
.tw-pname {
  display: block; min-width: 0; font-size: 26px; font-weight: 900;
  letter-spacing: .02em; line-height: 1.2; color: var(--tw-ink);
}
.tw-ptitle { display: block; font-size: 12.5px; letter-spacing: .1em; color: var(--tw-ink-soft); }
.tw-pcover .tw-pname { color: #fff; }
.tw-pcover .tw-ptitle { margin-top: 4px; color: rgba(255, 255, 255, .84); }

/* 右侧：一条信息只出现一次（见 team-wall.js 的 showProfile 注释） */
.tw-pinfo { padding: 26px 28px; min-width: 0; display: flex; flex-direction: column; gap: 18px; }
.tw-phead { display: flex; flex-direction: column; gap: 8px; }
.tw-pdept {
  display: inline-flex; align-items: center; gap: 8px; align-self: flex-start;
  font-size: 11.5px; font-weight: 700; letter-spacing: .2em; color: var(--tw-ink-soft);
}
.tw-pdept::before { content: ""; width: 16px; height: 3px; background: var(--tw-red); flex: 0 0 auto; }
.tw-pbio { font-size: 13.5px; line-height: 1.8; color: #3c3b38; }
/* ⚠ 芯片底色**必须是灰的**：旧版写的 `var(--tw-bg)` 就是 `#fff`，
   白底白卡 → 标签只剩文字、看着像「被禁用的浅色标签」（用户圈到了这一点）。 */
.tw-ptags { display: flex; flex-wrap: wrap; gap: 7px; }
.tw-ptag {
  font-size: 11.5px; border-radius: 999px; padding: 4px 11px; white-space: nowrap;
  background: #f5f4f1; border: 1px solid var(--tw-line); color: #3c3b38;
}
.tw-ptag i { font-style: normal; opacity: .5; margin-left: 5px; font-size: 9.5px; letter-spacing: .04em; }
/* 引言：左侧红条 + 浅底圆角，让它成为记忆点而不是一句飘着的文字 */
.tw-pquote {
  border-left: 3px solid var(--tw-red); background: #faf9f7;
  padding: 12px 16px; border-radius: 0 10px 10px 0;
  font-size: 14px; font-weight: 500; line-height: 1.75; color: var(--tw-ink);
}
/* 底部数字：`margin-top: auto` 把它压到卡片底边 —— 内容多少都稳在同一处。
   ⚠ 间距走 `.tw-pinfo` 的 `gap`，不靠子元素 margin-top：后者在 `auto` 吃掉空间后会归零。 */
.tw-pfacts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: auto; border: 1px solid var(--tw-line); border-radius: 10px; overflow: hidden;
}
.tw-fact { padding: 13px 14px; background: #fcfcfb; }
.tw-fact + .tw-fact { border-left: 1px solid var(--tw-line); }
.tw-fact b { display: block; font-size: 19px; font-weight: 900; color: var(--tw-ink); font-variant-numeric: tabular-nums; }
.tw-fact span { font-size: 11.5px; color: var(--tw-ink-soft); }
/* 两个数字都取不到时的替代说明（旧版这里是并排两个破折号，像数据没取到） */
.tw-pnote {
  margin-top: auto; padding-top: 16px; border-top: 1px solid var(--tw-line);
  font-size: 12px; color: var(--tw-ink-soft);
}

@media (max-width: 860px) {
  .tw-profile { grid-template-columns: 1fr; }
  .tw-portrait { min-height: 240px; }
  .tw-pinfo { padding: 20px 22px 24px; }
}
@media (max-width: 720px) {
  .tw-hero { padding: 26px 20px 22px; }
  .tw-toolbar { padding: 0 20px 12px; gap: 9px; }
  .tw-chip { padding: 4px 11px; font-size: 12px; }
  .tw-sect { margin-top: 6px; padding: 11px 2px 5px; }
  .tw-sect b { font-size: 14px; }
  .tw-scene { padding: 4px 20px 28px; }
  .tw-foot { padding: 0 20px 26px; }
  .tw-wall { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
  .tw-face { padding: 8px; gap: 4px; }
  .tw-face-name { font-size: 13px; }
  .tw-face-pos { font-size: 9px; }
  .tw-plate { font-size: 10px; padding: 12px 5px 6px; }
  .tw-say b, .tw-say-in > span { font-size: 13px; }
}
/* 关掉动效时瓷砖必须**直接可见** —— 它们初始 `opacity:0`，
   若只禁用 transition 而不改 opacity，整面墙会空白 */
@media (prefers-reduced-motion: reduce) {
  .tw-tile { opacity: 1; transform: none; transition: none; }
  .tw-wall { transition: none; }
}

/* ==========================================================================
   星栖星球 —— 员工自己的内容（第三十四轮）
   --------------------------------------------------------------------------
   视觉口径：**与「了不起的他们」同一套语言**（这一栏就挂在它下面，两者是同一段文化的两页）——
   白底、品牌红点睛、900 字重标题、系统字体栈。
   ⚠ 颜色变量同样声明在 `.tk-page` 上，**不要提到 `:root`** ——
     红色一旦进全局，会顺着 var() 渗进按钮、链接、告警条，全站配色就毁了（与 .tw-page 同一条理由）。
   ⚠ `.tk-page` 与 `.tw-page` 是**两份独立声明**，不共用一个类：
     共用会让「改 A 页的留白顺手改了 B 页」，而两页的版式节奏并不相同
     （信息流是窄栏长页，人物墙是宽幅网格）。
   ========================================================================== */
.tk-page {
  --tk-ink: #141414;
  --tk-ink-soft: #5f5e5a;
  --tk-red: #d6202a;
  --tk-red-deep: #a8141d;
  --tk-bg: #ffffff;
  --tk-tile: #f0eee9;
  --tk-line: #e8e7e3;
  --tk-sh1: 0 1px 2px rgba(20,20,20,.05);
  --tk-sh2: 0 10px 26px rgba(20,20,20,.12), 0 2px 6px rgba(20,20,20,.07);
  background: var(--tk-bg); border-radius: var(--r-lg);
  box-shadow: 0 1px 2px rgba(16,24,40,.04);
  color: var(--tk-ink); overflow: hidden;
}
/* 评论行的小头像 —— `.avatar` 原有 sm/md/lg/xl 四档，信息流里比 sm 再小一档更合手 */
.avatar.xs { width: 22px; height: 22px; font-size: 9.5px; }

/* ---------- 标题区 ---------- */
.tk-hero { padding: 38px 34px 22px; }
.tk-kicker {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 12px; font-weight: 700; letter-spacing: .35em; color: var(--tk-red);
}
.tk-kicker::before { content: ""; width: 26px; height: 3px; background: var(--tk-red); }
.tk-h1 {
  margin-top: 15px; font-size: clamp(30px, 4.6vw, 58px); font-weight: 900;
  line-height: 1.06; letter-spacing: -.02em; color: var(--tk-ink);
}
.tk-h1 .tk-red { color: var(--tk-red); }
.tk-sub { margin-top: 14px; max-width: 640px; font-size: 13.5px; line-height: 1.85; color: var(--tk-ink-soft); }
/* `flex-wrap: wrap` 与 .tw-meta 同理：也让版面探针不把这一行当「flex 行控件被压扁」来查 */
.tk-stats { margin-top: 22px; display: flex; flex-wrap: wrap; gap: 30px 44px; }
.tk-stat b {
  font-size: 26px; font-weight: 900; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--tk-ink);
}
.tk-stat span { font-size: 12px; color: var(--tk-ink-soft); margin-left: 3px; }
.tk-stat i { display: block; font-style: normal; font-size: 11px; letter-spacing: .12em; color: #9b9a95; }

/* ---------- 工具栏 ---------- */
.tk-bar { display: flex; align-items: center; gap: 14px; padding: 0 34px 16px; flex-wrap: wrap; }
.tk-segs { display: flex; }
.tk-page .segment { background: var(--tk-tile); border-color: var(--tk-line); }
.tk-page .segment button.active { color: var(--tk-red); }

/* ---------- 信息流 ---------- */
.tk-stream { padding: 0 34px; display: flex; flex-direction: column; gap: 14px; }
.tk-post {
  display: flex; gap: 14px; padding: 18px 20px;
  background: #fff; border: 1px solid var(--tk-line); border-radius: 12px;
  transition: box-shadow .18s, border-color .18s;
}
.tk-post:hover { box-shadow: var(--tk-sh1); border-color: #dcdad4; }
.tk-ava { flex: 0 0 auto; }
.tk-body { flex: 1; min-width: 0; }
.tk-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.tk-who { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.tk-name { font-size: 14px; font-weight: 800; color: var(--tk-ink); }
.tk-dept { font-size: 12px; color: var(--tk-ink-soft); }
.tk-time { margin-left: auto; font-size: 11.5px; color: #a3a19b; white-space: nowrap; }

/* 三类内容的标签：说说（中性）/ 活动（品牌红）/ 分享（暖褐）——
   一眼能分，但都不抢正文 */
.tk-kind { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 5px; white-space: nowrap; }
.tk-kind.k-say { background: var(--tk-tile); color: var(--tk-ink-soft); }
.tk-kind.k-activity { background: rgba(214,32,42,.09); color: var(--tk-red); }
.tk-kind.k-share { background: #fbf1e2; color: #96631c; }

.tk-title { margin-top: 10px; font-size: 16px; font-weight: 800; letter-spacing: -.01em; color: var(--tk-ink); }
.tk-text { margin-top: 8px; font-size: 13.5px; line-height: 1.9; color: #2f2e2b; white-space: pre-wrap; word-break: break-word; }
.tk-act-info {
  margin-top: 12px; display: inline-flex; flex-wrap: wrap; gap: 8px 18px;
  padding: 9px 13px; border-radius: 9px; background: rgba(214,32,42,.05);
  font-size: 12.5px; color: var(--tk-red-deep);
}
.tk-act-info span { display: inline-flex; align-items: center; gap: 6px; }

/* ---------- 九宫格 ----------
   ⚠ 图片一律 `position: relative` 的格子 + 绝对铺满的 img ——
     内层 `height: 100%` 的媒体是行高炸弹（本项目第三十一轮踩过），
     用 `object-fit: cover` 的绝对定位才是安全的写法。 */
.tk-grid { margin-top: 12px; display: grid; gap: 6px; max-width: 520px; }
.tk-grid.n1 { grid-template-columns: minmax(0, 250px); }
.tk-grid.n2, .tk-grid.n4 { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 340px; }
.tk-grid.n3, .tk-grid.n5, .tk-grid.n6, .tk-grid.n7, .tk-grid.n8, .tk-grid.n9 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tk-cell {
  position: relative; aspect-ratio: 1 / 1; overflow: hidden; cursor: zoom-in;
  border-radius: 8px; background: var(--tk-tile);
}
.tk-grid.n1 .tk-cell { aspect-ratio: 4 / 3; }
.tk-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.tk-img.failed { display: none; }
.tk-broken { display: grid; place-items: center; width: 100%; height: 100%; color: #b9b7b1; }

/* ---------- 操作条 ---------- */
.tk-acts { margin-top: 12px; display: flex; align-items: center; gap: 8px; }
.tk-act {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 8px; font-size: 12.5px; font-weight: 600;
  color: var(--tk-ink-soft); background: transparent; transition: .15s;
}
.tk-act:hover { background: var(--tk-tile); color: var(--tk-ink); }
.tk-act .n { font-variant-numeric: tabular-nums; }
/* 点赞选中态：icon() 默认 fill:none，这里把填充打开 ——
   所以心形必须是**闭合单路径**，否则填出来是个残缺的形状 */
.tk-like.on { color: var(--tk-red); background: rgba(214,32,42,.08); }
.tk-like.on svg { fill: currentColor; }
.tk-del:hover { background: rgba(214,32,42,.08); color: var(--tk-red); }

/* ---------- 评论 ---------- */
.tk-cmts { margin-top: 12px; background: #faf9f7; border-radius: 10px; padding: 6px 12px; }
.tk-cmts:empty { display: none; }
.tk-cmt { display: flex; gap: 9px; padding: 8px 0; }
.tk-cmt + .tk-cmt { border-top: 1px solid #f0eeea; }
.tk-cmt-b { flex: 1; min-width: 0; }
.tk-cmt-t { font-size: 13px; line-height: 1.7; word-break: break-word; }
.tk-cmt-n { font-weight: 700; color: #33322f; margin-right: 7px; }
.tk-cmt-x { color: #4a4945; white-space: pre-wrap; }
.tk-cmt-a { margin-top: 3px; display: flex; align-items: center; gap: 12px; font-size: 11.5px; color: #a3a19b; }
.tk-link { font-size: 11.5px; font-weight: 600; color: #6d6b66; padding: 0; display: inline-flex; align-items: center; gap: 3px; }
.tk-link:hover { color: var(--tk-red); }
.tk-link.del:hover { color: var(--tk-red); }
.tk-replies { margin-top: 8px; padding-left: 10px; border-left: 2px solid #eceae5; }
.tk-replies .tk-cmt { padding: 6px 0; }
.tk-replies .tk-cmt + .tk-cmt { border-top: 0; }

/* ---------- 评论输入 ---------- */
.tk-form { margin-top: 10px; display: flex; gap: 8px; align-items: center; }
.tk-input { flex: 1; min-width: 0; font-size: 13px; }
.tk-send { flex: 0 0 auto; }
.tk-replybar {
  margin-top: 8px; display: flex; align-items: center; gap: 10px;
  font-size: 12px; color: var(--tk-red-deep);
}

/* ---------- 发帖弹窗 ---------- */
.tk-form-body .field + .field { margin-top: 12px; }
.tk-extra { margin-top: 12px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.tk-pick-area { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.tk-pick-info { font-size: 12px; color: var(--tk-ink-soft); }
.tk-picks { display: flex; flex-wrap: wrap; gap: 8px; width: 100%; }
.tk-pick { position: relative; width: 64px; height: 64px; border-radius: 8px; overflow: hidden; background: var(--tk-tile); }
.tk-pick img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.tk-pick-x {
  position: absolute; top: 2px; right: 2px; width: 18px; height: 18px;
  display: grid; place-items: center; border-radius: 50%;
  background: rgba(0,0,0,.55); color: #fff;
}
.tk-pick-x:hover { background: rgba(214,32,42,.9); }

/* ---------- 灯箱 ---------- */
.tk-viewer { display: flex; flex-direction: column; gap: 12px; }
.tk-viewer-meta { font-size: 12.5px; color: var(--tk-ink-soft); }
.tk-viewer-stage {
  display: grid; place-items: center; min-height: 320px; max-height: 62vh;
  border-radius: 10px; background: #f4f3f0; overflow: hidden;
}
.tk-viewer-big { max-width: 100%; max-height: 62vh; object-fit: contain; display: block; }
.tk-viewer-thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.tk-thumb {
  position: relative; width: 56px; height: 56px; border-radius: 7px; overflow: hidden;
  background: var(--tk-tile); outline: 2px solid transparent; outline-offset: -2px;
}
.tk-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.tk-thumb.active { outline-color: var(--tk-red); }

.tk-foot { padding: 8px 34px 26px; }
.tk-foot .pager { border-top: 0; }

@media (max-width: 720px) {
  .tk-hero { padding: 26px 20px 18px; }
  .tk-bar, .tk-stream, .tk-foot { padding-left: 20px; padding-right: 20px; }
  .tk-post { padding: 14px 14px; gap: 10px; }
  .tk-stats { gap: 20px 28px; }
  .tk-extra { grid-template-columns: 1fr; }
  .tk-time { margin-left: 0; }
}

/* ==========================================================================
   商务立项 —— 商务部视角
   ========================================================================== */

/* 项目类型胶囊 */
.cat-tag {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid transparent; border-radius: 7px;
  padding: 3px 9px; font-size: 12px; font-weight: 600; line-height: 1.5;
  white-space: nowrap;
}
.cat-tag.sm { padding: 2px 7px; font-size: 11px; border-radius: 6px; }
.cat-tag svg { flex: 0 0 auto; }

/* 业务类型分布 */
.biz-dist { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.biz-dist-item {
  border: 1px solid var(--border); border-radius: 10px; padding: 14px;
  cursor: pointer; transition: .16s; background: var(--surface);
}
.biz-dist-item:hover { border-color: var(--brand-200); box-shadow: var(--sh-sm); }
.biz-dist-item.idle { opacity: .6; }
.biz-dist-item .hh { display: flex; align-items: center; gap: 8px; }
.biz-dist-item .ci { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; flex: 0 0 26px; }
.biz-dist-item .nm { font-size: 12.5px; font-weight: 600; }
.biz-dist-item .n { font-size: 22px; font-weight: 700; margin: 10px 0 2px; letter-spacing: -.5px; font-variant-numeric: tabular-nums; }
.biz-dist-item .n small { font-size: 11px; font-weight: 600; color: var(--text-3); margin-left: 4px; letter-spacing: 0; }
.biz-dist-item .amt { font-size: 11.5px; color: var(--text-3); }
.biz-dist-item .bar { height: 4px; border-radius: 3px; background: var(--surface-2); margin-top: 10px; overflow: hidden; }
.biz-dist-item .bar i { display: block; height: 100%; border-radius: 3px; transition: .3s; }

/* 待派单提醒条 */
.biz-alert {
  display: flex; align-items: center; gap: 10px;
  background: #fffaeb; border: 1px solid #fedf89; color: #b54708;
  border-radius: 10px; padding: 12px 15px; font-size: 13px;
}
.biz-alert .btn { margin-left: auto; color: #b54708; }

/* 交付链路 */
.biz-flow {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  padding: 15px 18px;
}
.biz-step { display: flex; align-items: flex-start; gap: 10px; flex: 1 1 180px; min-width: 0; }
.biz-step .n {
  width: 30px; height: 30px; border-radius: 9px; flex: 0 0 30px;
  display: grid; place-items: center; background: var(--brand-50); color: var(--brand);
}
.biz-step .st { font-size: 13px; font-weight: 700; line-height: 1.4; }
.biz-step .sd { font-size: 11.5px; color: var(--text-3); line-height: 1.5; margin-top: 2px; }
.biz-flow .arw { color: var(--text-4); flex: 0 0 auto; }

/* 待派单文字标记 */
.biz-warn { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; color: #b54708; }

/* 商务立项表单：业务类型卡片选择 */
.cat-pick { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
.cat-opt {
  border: 1.5px solid var(--border); border-radius: 10px; padding: 12px 11px;
  cursor: pointer; transition: .15s; background: var(--surface);
}
.cat-opt:hover { border-color: var(--brand-200); }
.cat-opt .ci { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; margin-bottom: 9px; }
.cat-opt .cn { font-size: 13px; font-weight: 700; }
.cat-opt .cd { font-size: 10.5px; color: var(--text-3); line-height: 1.55; margin-top: 4px; }

/* ==========================================================================
   线索订单（项目池 / 我的订单 / 线索协作页）
   ========================================================================== */

/* 池卡片与订单卡片 */
.pool-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  padding: 17px 18px; transition: .18s; cursor: pointer; display: flex; flex-direction: column;
}
.pool-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-md); transform: translateY(-2px); }
.pool-card .hd { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.pool-card h4 { font-size: 14.5px; font-weight: 700; line-height: 1.45; margin: 0 0 6px; }
.pool-card .cust { font-size: 12px; color: var(--text-3); display: flex; align-items: center; gap: 5px; }
.pool-card .ft { display: flex; align-items: center; gap: 10px; padding-top: 12px; margin-top: auto; border-top: 1px solid var(--border-2); }
.proj-card .ft .grow, .pool-card .ft .grow { flex: 1; }
.proj-card .desc, .pool-card .desc {
  font-size: 12.5px; color: var(--text-2); line-height: 1.65; margin: 10px 0 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* 评估摘要行 */
.eval-line {
  display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2);
  margin: 12px 0; padding: 8px 10px; background: var(--surface-2); border-radius: 8px;
}
.eval-line b { color: var(--brand-700); font-weight: 700; }
.biz-ok {
  display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 600;
  color: #027a48; background: var(--success-bg); padding: 3px 8px; border-radius: 6px;
}

/* 线索协作页布局 */
.lead-grid { display: grid; grid-template-columns: minmax(0, 1fr) 344px; gap: 16px; align-items: start; }
.lead-desc { font-size: 13.5px; line-height: 1.85; color: var(--text-2); margin: 0 0 16px; white-space: pre-wrap; }
.lead-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(138px, 1fr)); gap: 12px; }
.lead-meta .item { background: var(--surface-2); border: 1px solid var(--border-2); border-radius: 10px; padding: 11px 13px; }
.lead-meta .lb { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--text-3); margin-bottom: 5px; }
.lead-meta .vl { font-size: 13px; font-weight: 600; color: var(--text); }

/* 评估卡片 */
.eval-card { border: 1px solid var(--border); border-radius: 12px; padding: 15px 16px; background: var(--surface); }
.eval-card.mine { border-color: var(--brand-200); background: var(--brand-50); }
.eval-card .hd { display: flex; align-items: center; gap: 8px; margin-bottom: 13px; }
.eval-card .nums { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.nb { background: var(--surface-2); border-radius: 8px; padding: 9px 10px; }
.nb .bl { font-size: 11px; color: var(--text-3); margin-bottom: 4px; }
.nb .bv { font-size: 14px; font-weight: 700; color: var(--text); letter-spacing: -.2px; }
.nb.blue .bv { color: var(--brand-700); }
.nb.red .bv { color: var(--danger); }
.eval-note {
  font-size: 12.5px; line-height: 1.75; color: var(--text-2); margin: 12px 0 0;
  padding: 10px 12px; background: var(--surface-2); border-left: 3px solid var(--border); border-radius: 0 8px 8px 0;
}

/* 成本报价对比 */
.eval-bars { display: flex; flex-direction: column; gap: 11px; }
.eval-bar { display: grid; grid-template-columns: 76px minmax(0, 1fr) 86px; align-items: center; gap: 12px; font-size: 12px; }
.eval-bar .nm { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.eval-bar .track { height: 9px; background: var(--surface-2); border-radius: 5px; overflow: hidden; }
.eval-bar .track i { display: block; height: 100%; border-radius: 5px; background: var(--brand-500); transition: .3s; }
.eval-bar .v { text-align: right; font-weight: 700; color: var(--brand-700); font-variant-numeric: tabular-nums; }

@media (max-width: 1180px) {
  .lead-grid { grid-template-columns: minmax(0, 1fr); }
  .eval-card .nums { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.pt-0 { padding-top: 0 !important; }

@media (max-width: 1100px) {
  .biz-dist { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .cat-pick { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
  .biz-dist { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cat-pick { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   项目报告（日报 / 周报 / 月报）
   ========================================================================== */

.report-card {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  padding: 16px 18px 18px;
  box-shadow: var(--sh-xs);
  transition: border-color .2s, box-shadow .2s;
}
.report-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-sm); }
.report-card .hd {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}

/* 自动汇总数据条 */
.snap-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
.snap-tile {
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: var(--r);
  padding: 10px 13px;
}
.snap-tile .lbl { font-size: 11.5px; color: var(--text-3); margin-bottom: 5px; }
.snap-tile .val {
  font-size: 16px; font-weight: 700; color: var(--text);
  letter-spacing: -.3px; font-variant-numeric: tabular-nums;
}
.snap-tile .foot { font-size: 11px; color: var(--text-3); margin-top: 4px; }

/* 报告正文 */
.rp-body { display: flex; flex-direction: column; gap: 12px; }
.rp-sec .lbl {
  font-size: 11.5px; font-weight: 600; color: var(--brand-700);
  margin-bottom: 5px; letter-spacing: .2px;
}
.rp-sec p {
  margin: 0; font-size: 13px; line-height: 1.85; color: var(--text-2);
  white-space: pre-wrap; word-break: break-word;
}
.rp-people {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--border);
}

/* 撰写弹窗内的「系统自动带数」提示块 */
.rp-preview:empty { display: none; }
.rp-auto {
  background: var(--brand-50);
  border: 1px solid var(--brand-100);
  border-radius: var(--r);
  padding: 11px 13px;
  font-size: 12.5px;
  color: var(--brand-900);
}
.rp-auto .ttl {
  display: flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 600; color: var(--brand-700);
}

@media (max-width: 900px) {
  .snap-row { grid-template-columns: 1fr; }
}

/* ---------- 日报日历：左日历 + 右当日详情 ---------- */
.day-wrap { display: grid; grid-template-columns: 292px minmax(0, 1fr); gap: 18px; align-items: start; }
.day-side { position: sticky; top: 12px; }
.day-main { min-width: 0; }

.mini-cal {
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  padding: 12px 12px 10px;
  box-shadow: var(--sh-xs);
}
.mini-cal .cal-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-bottom: 9px; }
.mini-cal .cal-title { font-size: 13px; font-weight: 700; color: var(--text); }
.mini-cal .cal-nav {
  appearance: none; border: 1px solid var(--border); background: var(--surface);
  color: var(--text-3); width: 26px; height: 26px; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; transition: border-color .15s, color .15s, background .15s;
}
.mini-cal .cal-nav:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-50); }
.mini-cal .cal-week { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 4px; }
.mini-cal .cal-week span { text-align: center; font-size: 11px; color: var(--text-3); padding: 3px 0; }
.mini-cal .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.mini-cal .cal-cell {
  position: relative; appearance: none; border: 0; background: transparent;
  height: 34px; border-radius: 8px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums;
  font-family: inherit; transition: background .14s, color .14s;
}
.mini-cal .cal-cell.blank { visibility: hidden; }
.mini-cal .cal-cell:hover { background: var(--brand-50); color: var(--brand-700); }
.mini-cal .cal-cell.has { font-weight: 700; color: var(--text); }
.mini-cal .cal-cell.today { box-shadow: inset 0 0 0 1px var(--brand-200); }
.mini-cal .cal-cell.on { background: var(--brand); color: #fff; box-shadow: none; }
.mini-cal .cal-dot {
  position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%; background: var(--brand);
}
.mini-cal .cal-cell.on .cal-dot { background: #fff; }
.mini-cal .cal-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 9px; padding-top: 8px; border-top: 1px dashed var(--border);
  font-size: 11px; color: var(--text-3);
}

.day-rec {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); padding: 16px 18px 18px; box-shadow: var(--sh-xs);
}
.day-rec .hd {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}

.day-empty {
  border: 1px dashed var(--border-2); border-radius: var(--r-lg);
  background: var(--surface-2); padding: 42px 20px; text-align: center;
}
.day-empty .ic { color: var(--text-3); display: flex; justify-content: center; margin-bottom: 10px; }
.day-empty .t { font-size: 14px; font-weight: 700; color: var(--text); }
.day-empty .d { font-size: 12.5px; color: var(--text-3); margin-top: 6px; line-height: 1.7; }

/* ---------- 报告协作：追加补充 / 变更记录 / 冲突提示（第十轮） ---------- */
.day-append {
  margin-top: 14px; padding-top: 13px; border-top: 1px dashed var(--border);
}
.day-append .lbl {
  display: flex; align-items: center; gap: 4px;
  font-size: 12px; font-weight: 700; color: var(--brand-700); margin-bottom: 8px;
}

/* 「变更记录 N」按钮：有过他人补充时点亮，提示这份日报不止一个人写过 */
.btn.rev-has { color: var(--brand-700); font-weight: 700; }

.rev-list { display: flex; flex-direction: column; }
.rev-item {
  padding: 11px 13px; border: 1px solid var(--border);
  border-radius: var(--r-md); background: var(--surface);
}
.rev-item + .rev-item { margin-top: 10px; }
.rev-hd {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-bottom: 9px; margin-bottom: 9px; border-bottom: 1px dashed var(--border);
}
.rev-body .rp-sec { margin-bottom: 0; }
.rev-body .rp-sec p { white-space: pre-wrap; margin: 3px 0 0; }

/* 409 冲突提示：不关弹窗，就地提示 + 一键载入最新内容 */
.conflict-box {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 14px; padding: 11px 13px;
  border: 1px solid var(--warning); border-left-width: 3px;
  border-radius: var(--r-md); background: var(--warning-bg);
}
.conflict-box .txt {
  flex: 1; min-width: 220px;
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; line-height: 1.65; color: #b54708;
}

@media (max-width: 900px) {
  .day-wrap { grid-template-columns: 1fr; }
  .day-side { position: static; }
}

/* ---------- 凭证附件（第七轮） ---------- */
.att-picker { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.att-picker-compact { gap: 4px; }

.att-chip {
  display: flex; align-items: center; gap: 7px;
  max-width: 250px; min-width: 0;
  padding: 5px 7px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.att-thumb {
  flex: 0 0 24px; width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-xs);
}
.att-name {
  font-size: 12px; font-weight: 600; color: var(--text);
  cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.att-name:hover { color: var(--brand); text-decoration: underline; }
.att-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.att-x {
  flex: 0 0 20px;
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0;
  border: none; background: transparent; border-radius: var(--r-xs);
  color: var(--text-3); cursor: pointer;
}
.att-x:hover { background: var(--danger-bg); color: var(--danger); }

.att-add {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 10px;
  font-size: 12px; font-weight: 500;
  color: var(--text-2);
  background: transparent;
  border: 1px dashed var(--text-4);
  border-radius: var(--r-sm);
  cursor: pointer;
  white-space: nowrap;
}
.att-add:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-50); }
.att-add-sm { padding: 3px 7px; font-size: 11px; }

.att-picker-compact .att-chip { max-width: 178px; padding: 3px 6px; }
.att-picker-compact .att-thumb { flex-basis: 20px; width: 20px; height: 20px; }
.att-picker-compact .att-sub { display: none; }

/* ==========================================================================
   公司门户（第十一轮）
   门户是登录后的落地页，视觉上比内部列表页「松弛」一档：
   头部用深色渐变立品牌识别，往下各区块回到标准卡片，信息密度不抢眼。
   ========================================================================== */

/* ---------- 门户头部（第二十六轮：白底品牌首屏 · 去卡片化） ----------
   取向对齐 RTN 路演 PPT 封面：大字号金句 + 楼体远景。深色渐变与「字母 Z」方块已撤，
   品牌识别改由**官方 logo 标 + 金句 + 远景图**三件套承担。

   ⭐ 去卡片化（用户口径：「我不想让他模块化，而是直接在背景上」）：
   首屏**不再是一张卡片** —— 没有 surface 底色、没有边框、没有圆角、没有阴影，
   并向上、向左右抵消 `.content` 的内边距（用 --content-pad-* 的负值，不写死 24px），
   于是这一块与页面底色连成一片：文案、logo、楼体都**直接落在页面背景上**，
   楼体也顺势贴到页面右边缘（不再被卡片的圆角框住）。

   ⚠ 配套：楼体图必须用**透明底**（`building.webp`）。白底 JPG 铺在 #f4f6fa 上
   会露出一块白方块 —— 那就又变回「一个模块」了，正好与这次改动相冲。 */
.portal-hero {
  position: relative; overflow: hidden;
  background: transparent;
  margin: calc(-1 * var(--content-pad-t)) calc(-1 * var(--content-pad-x)) 16px;
  /* 底部问候行的高度：远景图要正好停在它上面，两者共用一个变量才不会错位 */
  --hero-foot-h: 36px;
}
/* 右侧远景：站在页面背景上，占满内容区右侧整高（让出底部问候行），楼体裁切交给右边缘 */
.portal-hero .hero-vista {
  position: absolute; top: 0; right: 0; bottom: var(--hero-foot-h);
  width: 44%;
  display: flex; align-items: flex-end; justify-content: flex-end;
  pointer-events: none;
}
.portal-hero .hero-photo {
  width: 100%; height: 100%;
  object-fit: contain; object-position: right bottom;
  display: block;
}

.portal-hero .hero-body {
  position: relative;
  padding: 26px var(--content-pad-x) 18px;
  max-width: 56%;
}

/* 品牌锁定组合：官方标记 + 中文名 + 英文名 */
.portal-hero .hero-lockup { display: flex; align-items: center; gap: 9px; }
.portal-hero .hero-mark {
  width: 33px; height: 33px; border-radius: 9px; display: block;
  box-shadow: 0 2px 6px rgba(40,86,163,.18);
}
.portal-hero .hero-brand { font-size: 16px; font-weight: 800; color: var(--brand-900); letter-spacing: .4px; line-height: 1.15; }
.portal-hero .hero-brand-en { font-size: 8.5px; font-weight: 600; color: var(--text-3); letter-spacing: 1.8px; margin-top: 1px; }

/* 金句：整页最大的字 */
.portal-hero .hero-quote {
  margin: 12px 0 0;
  font-size: 22px; font-weight: 800; line-height: 1.26;
  color: var(--text); letter-spacing: -.2px;
}
.portal-hero .hero-quote .accent { color: var(--brand); }
.portal-hero .hero-sub {
  margin-top: 6px;
  font-size: 12px; color: var(--text-2); line-height: 1.55;
}

/* 业务四支柱（与路演 PPT 封面同款） */
.portal-hero .hero-pillars { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.portal-hero .hero-pillar {
  padding: 3px 9px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600;
  color: var(--brand-700); background: var(--brand-50);
  border: 1px solid var(--brand-100);
}

/* 四项公司速览：一行四项（细线分隔），放在文案列内。
   排 2×2 会把卡面顶得过高、并且横穿到楼体上 —— 楼体是配图，文字不该骑在它身上。 */
.portal-hero .hero-stats {
  display: flex; gap: 0;
  margin-top: 13px; padding-top: 11px;
  border-top: 1px solid var(--border-2);
}
.portal-hero .hero-stat {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 0 13px;
  border-left: 1px solid var(--border-2);
}
.portal-hero .hero-stat:first-child { border-left: 0; padding-left: 0; }
.portal-hero .hero-stat .ic {
  flex: 0 0 24px; width: 24px; height: 24px; border-radius: 7px;
  display: grid; place-items: center;
  color: var(--brand); background: var(--brand-50);
}
/* 数值与单位不许折行：「¥4882.00 万」被断成两行比小一号字难看得多 */
.portal-hero .hero-stat .v { font-size: 16px; font-weight: 800; line-height: 1.22; color: var(--text); letter-spacing: -.2px; white-space: nowrap; }
.portal-hero .hero-stat .l { font-size: 11px; color: var(--text-2); }
.portal-hero .hero-stat .f { font-size: 9.5px; color: var(--text-3); margin-top: 1px; }

/* 底部问候行：只有一条细线，没有底色 —— 它也是「背景上的一行字」，不是一个色块栏 */
.portal-hero .hero-foot {
  position: relative;
  display: flex; align-items: center; gap: 9px;
  min-height: var(--hero-foot-h);
  padding: 6px var(--content-pad-x);
  font-size: 11.5px; color: var(--text-2);
  border-top: 1px solid var(--border-2);
}
.portal-hero .hero-foot .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--text-4); }
/* 素材说明推到右端，与问候语分开 —— 它是对图的注释，不该混进「你好」这句话里 */
.portal-hero .hero-note { margin-left: auto; font-size: 10px; color: var(--text-3); letter-spacing: .2px; }

/* ---------- 首屏问候行里的天气（第二十七轮：透明、不做卡、不压楼） ----------
   用户口径：「这个不要遮挡后面的大楼」「这个天气不能和背景融为一体吗」
   「不要是卡片的形式，让他透明」。

   楼体把首屏右列（`hero-vista`）占满了，而 `hero-vista` 的下边界就是问候行的上边界 ——
   所以**任何浮在右列的东西都必然压住楼**。结论不是「把浮卡挪个位置」，而是**换一个层级**：
   天气从「浮在楼上的卡」降级为**问候行里的一段文字**（挂在 `.hero-foot` 内）。

   · **与楼体零重叠**：问候行在 `hero-vista` **下方**（`vista.bottom = foot.top`），纵向不相交；
   · **全透明**：无底色 / 边框 / 圆角 / 阴影 / 毛玻璃 —— 它和「下午好，×」是同一类东西，
     即「背景上的一行字」，而不是又一个模块（与首屏「去卡片化」是同一条口径）；
   · **零高度成本**：它是问候行内的一个行内段，首屏高度仍是 290px，也不再需要任何响应式位移；
   · **没有第二个标题**：原来卡头的「今日关怀 / 公开气象服务」随卡片一起去掉 ——
     一行文字再顶一个标题就重复了。

   ⚠ 内容仍是同一套 `.wx-*` 标记（JS 里没有第二套天气渲染），只是按行内尺度取舍：
     保留「几度、什么天、一句关怀」，收起城市与更新时间（`.wx-city`）、
     体感三项（`.wx-meta`）与四天预报格（`.wx-days`）。
     它们只是**视觉收起**（DOM 保留），想放回来删掉对应那行 `display:none` 即可。 */
.portal-hero .hero-foot .hero-wx { display: flex; align-items: center; flex: 0 1 auto; min-width: 0; }
/* 与前一格（日期）之间的分隔点。⚠ 必须挂在 `.hero-wx` 自己身上、**不能**写成它前面的
   一个独立 `.dot` 元素 —— 否则窄屏换行时那个点会留在上一行末尾，变成一个孤零零的「·」 */
.portal-hero .hero-foot .hero-wx::before {
  content: '·'; margin-right: 9px; color: var(--text-4);
}
.portal-hero .hero-foot .hero-wx .wx-body {
  align-items: center; gap: 9px; flex-wrap: nowrap; min-width: 0;
}
.portal-hero .hero-foot .hero-wx .wx-now { flex: 0 0 auto; gap: 6px; min-width: 0; }
/* 图标去掉色块底：色块是「卡片里的图标」，一行文字要的是一枚纯色小图标。
   ⚠ 这里**不要**写 color —— 让 `.wx-ic.tone-*` 那组服务端下发的 tone 色继续生效
   （晴=暖橙 / 雨=品牌蓝 / 雪=青 / 雷=紫），否则六个天气图标会全变成同一个颜色。 */
.portal-hero .hero-foot .hero-wx .wx-ic {
  width: 17px; height: 17px; border-radius: 0; flex: 0 0 17px;
  background: none;
}
.portal-hero .hero-foot .hero-wx .wx-main { display: flex; align-items: baseline; gap: 6px; }
.portal-hero .hero-foot .hero-wx .wx-city { display: none; }
.portal-hero .hero-foot .hero-wx .wx-meta { display: none; }
.portal-hero .hero-foot .hero-wx .wx-days { display: none; }
.portal-hero .hero-foot .hero-wx .wx-temp { margin: 0; gap: 5px; }
.portal-hero .hero-foot .hero-wx .wx-temp b { font-size: 15px; letter-spacing: 0; }
.portal-hero .hero-foot .hero-wx .wx-text { font-size: 11.5px; font-weight: 600; }
/* 关怀语同样不许带底色与左侧色条 —— 那正是「卡片感」的来源。
   它是这一行里**唯一可以被压缩**的部分（`flex: 0 1 auto` + `min-width: 0`）：
   位宽不够时先截它，保住「几度、什么天」这两个硬信息不折行、不断字。 */
.portal-hero .hero-foot .hero-wx .wx-tip {
  display: flex; align-items: center;
  flex: 0 1 auto; min-width: 0;
  padding: 0; margin: 0; border: 0; border-radius: 0; background: none;
}
/* 「晴」与关怀语之间补一个分隔点：不然「晴 今天天气不错」会读成一句话 */
.portal-hero .hero-foot .hero-wx .wx-tip::before {
  content: '·'; margin-right: 8px; color: var(--text-4);
}
.portal-hero .hero-foot .hero-wx .wx-tip .ti { display: none; }
.portal-hero .hero-foot .hero-wx .wx-tip .tt {
  margin: 0; font-size: 11.5px; line-height: 1.4; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 骨架也压到行高尺度：它换掉的那一刻，问候行的行盒高度不变，所以不会跳 */
.portal-hero .hero-foot .hero-wx .wx-skeleton { min-height: 0; gap: 8px; }
.portal-hero .hero-foot .hero-wx .wx-skeleton .sk { height: 12px; border-radius: 4px; }
.portal-hero .hero-foot .hero-wx .wx-skeleton .sk.w54 { width: 17px; }
.portal-hero .hero-foot .hero-wx .wx-skeleton .sk.w200 { width: 96px; flex: 0 0 96px; }

/* ---------- 公司简介 ---------- */
.about-row { display: flex; flex-direction: column; }
.about-item { display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 12.5px; }
.about-item .ic { color: var(--text-3); display: flex; }
.about-item .lb { color: var(--text-3); min-width: 62px; }
.about-item .vl { font-weight: 600; color: var(--text); }
.divider { height: 1px; background: var(--border-2); margin: 10px 0 14px; }

/* ---------- 今日关怀（天气条，第二十五轮） ----------
   结构：左（实况：城市 / 大温度 / 体感·湿度·风）/ 中（今天 + 未来三天）/ 右（一句关怀语）
   配色纪律：这一条里只有两类着色 —— 天气图标的圆底（按天气语义取系统色板）
   与关怀语的品牌蓝竖条，其余全部走中性灰阶。颜色多了就变成一块花布。 */
.wx-body { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }

.wx-now { display: flex; align-items: center; gap: 14px; flex: 1 1 300px; min-width: 260px; }
.wx-ic {
  width: 54px; height: 54px; border-radius: 16px; flex: 0 0 54px;
  display: grid; place-items: center;
}
.wx-ic.sm { width: 34px; height: 34px; border-radius: 11px; flex: 0 0 34px; margin: 0 auto; }
.wx-ic.tone-sun     { background: var(--warning-bg); color: var(--warning); }
.wx-ic.tone-cloud   { background: var(--surface-2);  color: var(--text-3); }
.wx-ic.tone-rain    { background: var(--brand-50);   color: var(--brand); }
.wx-ic.tone-snow    { background: var(--teal-bg);    color: var(--teal); }
.wx-ic.tone-thunder { background: var(--purple-bg);  color: var(--purple); }

.wx-main { min-width: 0; }
.wx-city { display: flex; align-items: baseline; gap: 8px; font-size: 12px; color: var(--text-3); }
.wx-upd { font-size: 10.5px; color: var(--text-4); }
.wx-temp { display: flex; align-items: baseline; gap: 8px; margin-top: 1px; }
.wx-temp b { font-size: 33px; font-weight: 800; line-height: 1.05; letter-spacing: -1px; color: var(--text); }
.wx-temp b.wx-na { color: var(--text-4); }
.wx-temp .wx-text { font-size: 13px; font-weight: 600; color: var(--text-2); }
.wx-meta { font-size: 11.5px; color: var(--text-3); margin-top: 4px; }

.wx-days { display: flex; gap: 8px; }
.wx-day {
  width: 82px; padding: 8px 4px 6px; text-align: center;
  border: 1px solid transparent; border-radius: 12px; background: var(--surface-2);
}
/* 「今天」用白底 + 描边，与「未来三天」的浅灰底区分开 —— 不靠颜色靠明度 */
.wx-day.today { background: var(--surface); border-color: var(--border); }
.wx-day .dl { font-size: 11px; color: var(--text-3); }
.wx-day .dt { margin-top: 3px; font-size: 13px; font-weight: 700; color: var(--text); }
.wx-day .dt i { font-style: normal; font-size: 11.5px; font-weight: 500; color: var(--text-3); }
.wx-day .dp { min-height: 13px; font-size: 10px; color: var(--brand); }

.wx-tip {
  flex: 1 1 240px; min-width: 220px;
  padding: 10px 14px;
  border-left: 3px solid var(--brand); border-radius: 0 10px 10px 0;
  background: linear-gradient(90deg, rgba(37,99,235,.06), rgba(37,99,235,0));
}
.wx-tip .ti { font-size: 10.5px; font-weight: 700; letter-spacing: .6px; color: var(--brand); }
.wx-tip .tt { margin-top: 3px; font-size: 12.5px; line-height: 1.6; color: var(--text-2); }

/* 骨架：天气要出外网，占位先立住，避免卡片高度在填充时跳动 */
.wx-skeleton { display: flex; align-items: center; gap: 14px; min-height: 76px; }
.wx-skeleton .sk {
  height: 42px; border-radius: 12px;
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--border-2) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: wx-sk 1.3s ease infinite;
}
.wx-skeleton .sk.w54 { width: 54px; }
.wx-skeleton .sk.grow { flex: 1; }
.wx-skeleton .sk.w200 { width: 200px; }
@keyframes wx-sk { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* ---------- 照片墙 ---------- */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 12px;
}
.photo-card {
  position: relative; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--sh-xs);
}
.photo-card .ph { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: var(--surface-2); }
.photo-card .ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-card .meta { padding: 9px 11px 11px; }
.photo-card .meta .tt {
  font-size: 12.5px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.photo-card .meta .sub {
  margin-top: 3px; font-size: 10.5px; color: var(--text-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.photo-card .hover-bar {
  position: absolute; top: 8px; right: 8px;
  display: flex; gap: 5px;
  opacity: 0; transition: opacity .16s;
}
.photo-card:hover .hover-bar { opacity: 1; }
.ph-btn {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 9px;
  font-size: 11.5px; font-weight: 600;
  color: var(--text);
  background: rgba(255,255,255,.94);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer; box-shadow: var(--sh-sm);
}
.ph-btn:hover { color: var(--brand); border-color: var(--brand-200); }
.ph-btn.danger { padding: 5px 7px; }
.ph-btn.danger:hover { color: var(--danger); border-color: var(--danger-bg); background: var(--danger-bg); }
.photo-fail {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; height: 100%;
  font-size: 11.5px; color: var(--text-3); background: var(--surface-2);
}

/* ---------- 大图查看 ---------- */
.lightbox { text-align: center; }
.lightbox .lb-img { max-width: 100%; max-height: 66vh; border-radius: var(--r); box-shadow: var(--sh-md); }
.lightbox .lb-meta {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: 12px; font-size: 12px; color: var(--text-3);
}

/* ---------- 上传 ---------- */
.up-pick {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 26px 16px; margin-bottom: 14px;
  border: 1px dashed var(--text-4); border-radius: var(--r);
  background: var(--surface-2); cursor: pointer;
  transition: border-color .14s, background .14s;
}
.up-pick:hover { border-color: var(--brand); background: var(--brand-50); }
.up-pick .ic { color: var(--text-3); }
.up-pick .tx { font-size: 13px; font-weight: 600; color: var(--text-2); }
.up-pick .hint { font-size: 11px; color: var(--text-3); }
.up-preview { text-align: center; }
.up-preview img { max-width: 100%; max-height: 210px; border-radius: var(--r); box-shadow: var(--sh-sm); margin-bottom: 12px; }

/* ---------- 页脚说明 ---------- */
.portal-foot {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 2px 10px;
  font-size: 11.5px; color: var(--text-3); line-height: 1.7;
}

/* ---------- 窄屏 ---------- */
/* 内容区在 ≤1024px 收窄内边距（与上面的 .content 规则成对）——
   门户首屏靠这套变量把自己顶到页面边缘，所以这里改一次、两边同时跟上。 */
@media (max-width: 1024px) {
  :root { --content-pad-x: 14px; --content-pad-t: 16px; }
}
@media (max-width: 1180px) {
  /* 楼体是配图不是主体：宽度不够时先让步，把版面还给金句 */
  .portal-hero .hero-vista { width: 34%; opacity: .9; }
  .portal-hero .hero-body { max-width: 62%; }
  /* 四项速览在 1180 以下开始挤：与其让「¥4882.00 万」折成两行，不如压字号与间距保住单行
     （四格是等宽的，只有最宽的那格会先折 —— 折了那一格，整行高度就跟着涨） */
  .portal-hero .hero-stat { padding: 0 10px; gap: 7px; }
  .portal-hero .hero-stat .ic { flex: 0 0 22px; width: 22px; height: 22px; }
  .portal-hero .hero-stat .v { font-size: 14.5px; }
}
@media (max-width: 900px) {
  .portal-hero .hero-vista { display: none; }
  .portal-hero .hero-body { max-width: 100%; }
}
@media (max-width: 720px) {
  .portal-hero .hero-body { padding: 18px var(--content-pad-x) 14px; }
  .portal-hero .hero-quote { font-size: 19px; }
  /* 速览改竖排：四列挤在手机宽度上每格只剩几十像素，数字会被压成两行 */
  .portal-hero .hero-stats { flex-direction: column; gap: 9px; }
  .portal-hero .hero-stat { flex: 0 0 auto; padding: 0; border-left: 0; }
  .portal-hero .hero-foot { padding: 10px var(--content-pad-x); flex-wrap: wrap; }
  .portal-hero .hero-note { margin-left: 0; flex-basis: 100%; }
  /* 手机上一行装不下「你好 + 日期 + 天气」：让天气整段换行占满，
     比在位宽里挤成一截省略号好看（它们本来就可以各占一行）。
     它已经独占一行，前置的分隔点就不需要了，收掉免得行首挂一个「·」 */
  .portal-hero .hero-foot .hero-wx { flex-basis: 100%; }
  .portal-hero .hero-foot .hero-wx::before { display: none; }
  .photo-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}


/* ==========================================================================
   公司门户 · 第十二轮扩展：头条要闻 / 个人绩效
   （原第三节「部门积分榜」已于第三十轮下架，见下方留痕）
   ========================================================================== */

/* ---- 头条要闻 ----
   第二十八轮三处一起收（用户口径：「这个板块太大了」+「头条新闻大图和整体浅色 UI 冲突强烈
   （高饱和实拍图压在干净的浅色界面上很跳）」+「图上的白字标题对比度不够」）：
     ① **高度** —— 264 → 184px：首屏下面紧挨着的第一块不该再霸掉大半个屏；
     ② **调性** —— 遮罩从「纯黑压暗」改成**品牌蓝渐变压暗**，并给底图降一点饱和：
        大图与浅色页面的冲突，本质是「高饱和实拍照」与「低饱和品牌色」两套色彩体系被并置；
        把照片先拉进品牌色所在的区间，比「原图 + 更重的黑遮罩」干净（后者会把画面压成一块糊灰）；
     ③ **文字** —— 标题加投影、meta 提亮（`.72 → .86`），把白字从「勉强看得见」拉到一眼读完。
   实测对比度（标题字 vs 其正下方像素）见 `docs/VERIFICATION.md` 第二十八轮。 */
.headline-hero {
  position: relative; border-radius: 12px; overflow: hidden;
  min-height: 184px; display: flex; align-items: flex-end;
  background: linear-gradient(135deg, #1e3a8a 0%, #1d4ed8 60%, #2563eb 100%);
}
/* ⚠ 修饰类用 `is-` 前缀：`.headline-hero.empty` 里的 `empty` 会**顺带命中**
   styles.css:610 那条全局的 `.empty { padding: 46px 20px; text-align: center; … }`
   （空状态块的样式）。这里靠复合选择器优先级更高才没出事 ——
   但那是「侥幸」不是「设计」，换个属性就翻车。见 tests/audit-style.mjs【D】。 */
.headline-hero.is-empty {
  align-items: center; justify-content: center; gap: 10px;
  color: rgba(255,255,255,.85); padding: 24px; text-align: center;
}
.headline-hero .headline-cover {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity .3s;
  filter: saturate(.82) contrast(1.04);
}
.headline-hero .headline-cover.loaded { opacity: 1; }
.headline-hero .headline-grad {
  position: absolute; inset: 0;
  /* 两段叠加：纵向自浅入深（上 40% 只轻压、下 28% 快速压深，把文字区压到能承载白字）；
     横向再补一层左重右轻的品牌蓝 —— 标题靠左，左边需要更实。 */
  background:
    linear-gradient(180deg,
      rgba(23,42,96,.10) 0%,
      rgba(19,36,84,.36) 42%,
      rgba(13,24,58,.76) 74%,
      rgba(9,16,42,.90) 100%),
    linear-gradient(90deg, rgba(23,42,96,.30) 0%, rgba(23,42,96,0) 58%);
}
.headline-hero .headline-body { position: relative; padding: 15px 18px; width: 100%; }
.headline-tag {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  background: var(--brand); color: #fff; font-size: 11px; font-weight: 600;
  box-shadow: 0 1px 6px rgba(6,12,32,.28);
}
.headline-title {
  margin: 8px 0 6px; color: #fff; font-size: 16.5px; line-height: 1.42; font-weight: 700;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  text-shadow: 0 1px 3px rgba(6,12,32,.55), 0 0 16px rgba(6,12,32,.32);
}
.headline-meta {
  color: rgba(255,255,255,.86); font-size: 11.5px;
  text-shadow: 0 1px 2px rgba(6,12,32,.5);
}
.headline-more { border-top: 1px solid var(--line); margin-top: 4px; }
.headline-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 2px;
  border-bottom: 1px dashed var(--line);
}
.headline-item:last-child { border-bottom: none; }
.headline-item .t {
  flex: 1; min-width: 0; font-size: 13px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.headline-item .d { font-size: 11.5px; color: var(--text-3); white-space: nowrap; }
/* 次级条目的分类牌与 `.chip.cat` 对齐（同色、同圆角、同尺寸）——
   它和大图上那枚 `.headline-tag` 是同一语义的两处表现，别让它们长得不像一家人。
   ⚠ 头条的分类（公司要闻 / 业务动态 / 荣誉表彰）与公告的分类是两套字典，不能共用组件。 */
.headline-item-tag {
  flex-shrink: 0; font-size: 11px; font-weight: 600; color: var(--brand-700);
  background: var(--brand-50); border: 1px solid rgba(37,99,235,.18);
  padding: 1px 7px; border-radius: var(--r-xs); line-height: 1.7;
}
.headline-detail-cover { border-radius: 10px; overflow: hidden; margin-bottom: 14px; }
.headline-detail-cover img { width: 100%; max-height: 320px; object-fit: cover; display: block; }
.headline-detail-title { margin: 0 0 8px; font-size: 19px; line-height: 1.45; }
.headline-detail-meta { font-size: 12px; color: var(--text-3); margin-bottom: 14px; }
.headline-detail-lead {
  font-size: 13.5px; font-weight: 600; color: var(--text-2);
  background: var(--bg-2, #f6f8fb); border-radius: 8px; padding: 10px 12px;
}
.headline-detail-p { font-size: 13.5px; line-height: 1.9; color: var(--text-1); margin: 10px 0; }

/* ---- 通用：环比徽章（涨红跌绿） ---- */
.delta { font-size: 12px; font-weight: 600; white-space: nowrap; }
.delta.up { color: var(--danger); }
.delta.down { color: var(--success); }
.delta.neutral { color: var(--text-4); }

/* ---- 通用：首字母圆形头像 ---- */
.score-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;
  color: #fff; font-weight: 700; font-size: 15px; opacity: .92;
}
.score-avatar.sm { width: 30px; height: 30px; font-size: 12.5px; }
.score-avatar.lg { width: 58px; height: 58px; font-size: 22px; border: 2px solid rgba(255,255,255,.55); }

/* ---- 个人绩效榜：当月前三名的领奖台（第二十九轮「只保留前三」） ----
 * ⚠ 第三十轮起门户上**只剩这一个榜**（部门积分榜已下架），
 *   第二十九轮那条「必须与部门积分榜不同形」的约束随之作废。
 * ⚠ 视觉顺序用 `order` 排，DOM 顺序仍保持 1·2·3（见 web/pages/portal.js 的 perfCard）——
 *   读屏与键盘 Tab 要按名次走，不该跟着视觉的 2-1-3 走。 */
.perf-podium {
  display: flex; align-items: flex-end; justify-content: center;
  gap: 14px; padding-top: 12px;        /* 给冠军卡头顶的皇冠留位置 */
}
.perf-pod {
  order: 9;                            /* 兜底：万一将来出现 r4+，也别插到中间去 */
  flex: 1 1 0; min-width: 0; max-width: 250px;
  position: relative; text-align: center; border-radius: var(--r-lg);
  border: 1px solid var(--border); background: var(--surface);
  padding: 16px 14px 14px;
  display: flex; flex-direction: column; align-items: center;
}
.perf-pod.r2 { order: 1; border-color: #cbd5e1; background: linear-gradient(180deg, #f8fafc 0%, var(--surface) 62%); }
.perf-pod.r3 { order: 3; border-color: #e8cbb0; background: linear-gradient(180deg, #fdf6f0 0%, var(--surface) 62%); }
/* 冠军卡「高一截」是靠**自身更高**（更多内边距 + 更大头像字号）＋容器 `align-items:flex-end`
   顶出来的，不是 `transform` —— transform 不占布局，会在卡片下方留一段空洞。 */
.perf-pod.r1 {
  order: 2; max-width: 300px; padding: 22px 18px 18px;
  border-color: transparent; color: #fff;
  background: linear-gradient(120deg, var(--brand-900) 0%, var(--brand) 100%);
  box-shadow: 0 10px 26px rgba(37,99,235,.24);
}
.perf-pod .pp-crown {
  position: absolute; top: -12px; left: 50%; transform: translateX(-50%);
  color: #f59e0b; line-height: 0;
}
.perf-pod .pp-medal {
  align-self: flex-start; width: 24px; height: 24px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 800;
  color: var(--text-3); background: var(--surface-2);
}
.perf-pod.r1 .pp-medal { background: rgba(255,255,255,.2); color: #fde68a; }
.perf-pod.r2 .pp-medal { color: #475569; background: #eef2f7; }
.perf-pod.r3 .pp-medal { color: #c2410c; background: #ffedd5; }
.perf-pod .score-avatar { margin: 8px 0 10px; }
.perf-pod.r1 .score-avatar { margin: 10px 0 12px; box-shadow: 0 0 0 4px rgba(255,255,255,.22); }
.perf-pod .pp-name { font-size: 15.5px; font-weight: 800; }
.perf-pod.r1 .pp-name { font-size: 19px; }
.perf-pod .pp-role {
  font-size: 11.5px; color: var(--text-3); margin-top: 3px; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.perf-pod.r1 .pp-role { color: rgba(255,255,255,.74); }
.perf-pod .pp-score { margin-top: 12px; display: flex; align-items: baseline; gap: 4px; }
.perf-pod .pp-score .num {
  font-size: 28px; font-weight: 800; letter-spacing: -.5px;
  color: var(--brand); font-variant-numeric: tabular-nums;
}
.perf-pod.r1 .pp-score .num { font-size: 38px; color: #fff; }
.perf-pod .pp-score .den { font-size: 11.5px; color: var(--text-4); }
.perf-pod.r1 .pp-score .den { color: rgba(255,255,255,.62); }
.perf-pod .pp-delta { margin-top: 6px; }
.perf-pod.r1 .pp-delta .delta { color: rgba(255,255,255,.86); }

/* 「我的排名」细条 —— 榜单砍到只剩三个人之后，**榜外的人**才是这条的主要读者。
   ⛔ 不做吸底（参考稿是单页样例，可以吸底）：门户是长滚动页，
      固定条会永久盖住照片墙与公司简介。 */
.perf-me {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding: 9px 14px; border-radius: var(--r);
  background: var(--surface-2); border: 1px solid var(--border-2);
  font-size: 12px; color: var(--text-3);
}
.perf-me:empty { display: none; }      /* 没有我的积分记录时整条收起（mount 会把子节点清空） */
.perf-me .pm-tag {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  font-weight: 600; color: var(--brand-700); background: var(--brand-50);
  padding: 2px 8px; border-radius: var(--r-xs);
}
.perf-me .pm-name { font-weight: 700; color: var(--text); }
.perf-me .pm-rank b { font-size: 15px; font-weight: 800; color: var(--brand); margin: 0 1px; }
.perf-me .pm-gap { margin-left: auto; }
.perf-me .pm-score {
  font-size: 17px; font-weight: 800; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.perf-me .pm-delta { flex: 0 0 auto; }

/* ---- 部门积分榜 ---- */
/* ⚠ 第三十轮已下架（用户口径「部门积分榜 这个板块不用了」）：`.team-podium` / `.team-card*` /
   `.team-rows` / `.team-row*` 全部删除，连同窄屏那条 `.team-podium { grid-template-columns: 1fr }`。
   门户上现在只剩「个人绩效榜」一个榜。要恢复布局，从 git 历史取回本段。 */

@media (max-width: 900px) {
  /* 领奖台在窄屏竖排，且**冠军回到最前** —— 竖排之后「抬高」这件事不再成立，
     再让第 2 名排在第一位就纯粹是反直觉了。 */
  .perf-podium { flex-direction: column; align-items: stretch; }
  .perf-pod, .perf-pod.r1 { max-width: none; }
  .perf-pod.r1 { order: 1; padding: 20px 16px 16px; }
  .perf-pod.r2 { order: 2; }
  .perf-pod.r3 { order: 3; }
  .perf-me .pm-gap { margin-left: 0; }
  .headline-hero { min-height: 168px; }
}

/* ==========================================================================
   第十三轮 · 员工档案（照片 / 简历）与人事工作台
   ========================================================================== */

/* ---------- 头像照片 ---------- */
/* 照片是「可选的增强」：没照片时是姓名首字色块，有照片时内部追加 <img>。
   容器 overflow:hidden + 圆形，让两种形态在任何尺寸下都长得一样整。 */
.avatar { overflow: hidden; position: relative; }
.avatar.xxl { width: 84px; height: 84px; font-size: 29px; }
.avatar.has-photo { background: var(--surface-2) !important; }
.avatar .avatar-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* 档案页头部的照片 + 可点击换图 */
.staff-head { display: flex; gap: 16px; align-items: flex-start; }
.staff-photo-wrap { position: relative; flex: 0 0 auto; }
.staff-photo-wrap .cam {
  position: absolute; right: -2px; bottom: -2px; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; background: var(--brand); color: #fff; cursor: pointer;
  border: 2px solid var(--surface); box-shadow: var(--sh-sm);
}
.staff-photo-wrap .cam:hover { background: var(--brand-700); }
.staff-photo-wrap.busy .cam { opacity: .6; pointer-events: none; }

/* 新增/编辑员工弹窗里的照片选择：预览头像 + 选择/移除 + 已选状态。
   复用 .avatar（预览即最终头像的样子）与 .att-add（虚线小按钮），不另起一套控件外观。 */
.photo-pick { display: flex; align-items: center; gap: 12px; }
.photo-pick-avatar { flex: 0 0 auto; display: flex; }
.photo-pick-body { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.photo-pick-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.photo-pick-status { color: var(--text-2); }

/* ---------- 简历 ---------- */
.resume-sec { margin-top: 14px; }
.resume-sec > .hd {
  display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700;
  color: var(--text-2); margin-bottom: 8px;
}
.resume-sec > .hd .ico { color: var(--text-3); }
.resume-item {
  display: flex; gap: 12px; padding: 9px 0; border-bottom: 1px dashed var(--border-2);
}
.resume-item:last-child { border-bottom: none; }
.resume-item .when {
  flex: 0 0 96px; font-size: 11.5px; color: var(--text-3); padding-top: 2px;
  font-variant-numeric: tabular-nums;
}
.resume-item .what { flex: 1; min-width: 0; }
.resume-item .what .t1 { font-size: 13px; font-weight: 600; color: var(--text); }
.resume-item .what .t2 { font-size: 12px; color: var(--text-3); margin-top: 1px; }
.resume-item .what .t3 { font-size: 12.5px; color: var(--text-2); margin-top: 5px; line-height: 1.65; }
.chip-list { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px;
  padding: 3px 9px; border-radius: 999px; background: var(--brand-50); color: var(--brand-700);
  border: 1px solid var(--brand-100);
}
.chip.gray { background: var(--surface-2); color: var(--text-2); border-color: var(--border-2); }

/* 简历编辑器的经历行 */
.res-row { border: 1px solid var(--border-2); border-radius: var(--r); padding: 10px 12px; margin-bottom: 9px; background: var(--surface-2); }
.res-row .res-row-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.res-row .res-row-head .idx { font-size: 11.5px; font-weight: 700; color: var(--text-3); }
.res-row .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.res-row .grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }

/* ---------- 工作台 · 打卡卡 ---------- */
.clock-card {
  background: linear-gradient(120deg, #0f1729 0%, #1e3a8a 58%, #2563eb 100%);
  border: none; color: #fff; overflow: hidden; position: relative;
}
.clock-card .card-body { padding: 20px 22px; }
.clock-card .lead { font-size: 12.5px; color: rgba(255,255,255,.72); }
.clock-card .big-time {
  font-size: 30px; font-weight: 800; letter-spacing: -.6px; font-variant-numeric: tabular-nums;
  line-height: 1.1; margin: 4px 0 2px;
}
.clock-card .punch-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.clock-card .btn-punch {
  background: #fff; color: var(--brand-900); border: none; font-weight: 700;
  padding: 9px 20px; border-radius: 999px; cursor: pointer; font-size: 13.5px;
  display: inline-flex; align-items: center; gap: 7px;
}
.clock-card .btn-punch:hover { background: #eef4ff; }
.clock-card .btn-punch.ghost { background: rgba(255,255,255,.14); color: #fff; }
.clock-card .btn-punch.ghost:hover { background: rgba(255,255,255,.24); }
.clock-card .btn-punch:disabled { opacity: .5; cursor: default; }
.clock-card .punch-meta { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,.16); }
.clock-card .punch-meta .mi .k { font-size: 11px; color: rgba(255,255,255,.62); }
.clock-card .punch-meta .mi .v { font-size: 16px; font-weight: 700; margin-top: 1px; font-variant-numeric: tabular-nums; }
.clock-card select.punch-proj {
  background: rgba(255,255,255,.12); color: #fff; border: 1px solid rgba(255,255,255,.24);
  border-radius: 8px; padding: 7px 10px; font-size: 12.5px; max-width: 260px;
}
.clock-card select.punch-proj option { color: #0f1729; }

/* ---------- 工作台 · 人事面板 ---------- */
.hr-note { font-size: 11.5px; color: var(--text-3); }
.wb-kpi { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.wb-kpi .k {
  border: 1px solid var(--border-2); border-radius: var(--r); padding: 11px 13px; background: var(--surface-2);
}
.wb-kpi .k .lbl { font-size: 11.5px; color: var(--text-3); }
.wb-kpi .k .val { font-size: 21px; font-weight: 800; letter-spacing: -.4px; margin-top: 2px; }
.hr-alert-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px dashed var(--border-2);
}
.hr-alert-row:last-child { border-bottom: none; }
.hr-alert-row .nm { font-weight: 600; font-size: 13px; }
.hr-alert-row .meta { font-size: 11.5px; color: var(--text-3); }
.hr-alert-row .days { margin-left: auto; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hr-alert-row .days.over { color: var(--danger); }
.hr-alert-row .days.soon { color: var(--warning); }
.hr-att-dept { display: flex; align-items: center; gap: 10px; padding: 7px 0; }
.hr-att-dept .nm { flex: 0 0 96px; font-size: 12.5px; }
.hr-att-dept .bar { flex: 1; height: 7px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.hr-att-dept .bar i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, #60a5fa, #2563eb); }
.hr-att-dept .pct { flex: 0 0 48px; text-align: right; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- 通用 · 说明条（第十四轮补：行政办公各页用） ----------
   之前设计系统里只有 toast 风格的提示，没有「常驻在表单/列表里的一段说明」。
   `.alert` 就是那一层：它不消失，因为「发放时按实际单价结算」这种事
   恰恰需要用户在看表单的每一秒都能重新读到。 */
.alert {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 13px; border-radius: var(--r-sm); font-size: 12.5px; line-height: 1.6;
  border: 1px solid var(--border-2); background: var(--surface-2); color: var(--text-2);
}
.alert svg { flex: 0 0 auto; margin-top: 1px; }
.alert.info  { background: var(--info-bg);    border-color: #b2ddff; color: #175cd3; }
.alert.warn  { background: var(--warning-bg); border-color: #fedf89; color: #b54708; }
.alert.danger{ background: var(--danger-bg);  border-color: #fecdca; color: #b42318; }
.alert.ok    { background: var(--success-bg); border-color: #a6f4c5; color: #027a48; }

/* ---------- 通用 · 键值清单 ----------
   .kv 本身是按 dt/dd 设计的；这里补上 div+span 形态（详情抽屉里更省 DOM），
   两种写法共用同一套栅格，不会出现「有的抽屉对齐、有的不对齐」。 */
.kv-list { display: grid; gap: 0; }
.kv-list .kv { padding: 7px 0; border-bottom: 1px dashed var(--border-2); }
.kv-list .kv:last-child { border-bottom: none; }
.kv .k { color: var(--text-3); font-size: 12.5px; }
.kv .v { font-weight: 500; word-break: break-all; }

/* ---------- 工作台 · 行政面板 ---------- */
.adm-mini {
  border: 1px solid var(--border-2); border-radius: var(--r); padding: 10px 12px; background: var(--surface-2);
}
.adm-mini .l { font-size: 11.5px; color: var(--text-3); }
.adm-mini .v { font-size: 19px; font-weight: 800; letter-spacing: -.3px; margin-top: 1px; }
.adm-mini .f { font-size: 11px; color: var(--text-4); margin-top: 2px; }
.adm-office { border: 1px solid var(--border-2); border-radius: var(--r); overflow: hidden; }
.adm-office .hd {
  display: flex; align-items: flex-start; gap: 10px; padding: 11px 13px;
  background: var(--surface-2); border-bottom: 1px solid var(--border-2);
}
.adm-office .hd .ic {
  width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; flex: 0 0 30px;
}
.adm-office .hd .ic.todo { background: var(--warning-bg); color: var(--warning); }
.adm-office .hd .ic.ok   { background: var(--success-bg); color: var(--success); }
.adm-office .list { padding: 4px 13px; }
.adm-office .list-item { padding: 8px 0; }

/* ---------- 背景音乐播放器（第三十一轮立 · 第三十二轮面板改制 · 第三十三轮悬停展开） ----------
   右下角悬浮小岛：收起是一枚圆钮，**鼠标移上去**展开成一张播放器面板，移开自动缩回。
   ⚠ 三条不能动的约束（细则见 web/music.js 文件头）：
     · 定位只能用 fixed；z-index 夹在「页面内容」与「弹窗」之间 ——
       它是常驻装饰，绝不能盖住弹窗，但也不该被页面卡片压住。
       ⚠ 这里曾误写为 300（比弹窗的 200 还高），结果播放器盖在「曲库管理」弹窗的右下角上，
       把列表右侧那列「编辑 / 下架 / 删除」整片点了没反应 —— 点击被它吃掉，
       而且**不报任何错**，排查时只能靠 `document.elementFromPoint()` 才看得出遮挡链。
       现降到 120；
     · 面板默认用 `visibility: hidden` 而不是 `display: none`：既保留过渡动画，
       又把里面的按钮**移出 Tab 顺序**（看不见却能被 Tab 到是典型的无障碍坑）；
       ⚠ 但 `visibility: hidden` **仍然占布局** —— 所以面板必须绝对定位，
       否则收起时那 400px 仍留在容器命中盒里，鼠标划过右下角一块空地就会弹面板（第三十三轮）；
     · 颜色全部走既有令牌，不新造变量（新造没用过的变量正是第二十九轮踩过的坑）。
   本轮的版面调整（对齐优化稿）：模式三态按钮、带圆点的可拖拽进度条、居中的对称三键、
   音量百分比、列表里的音波动画与时长列、`role="switch"` 的自定义开关。 */
.music-dock {
  position: fixed; right: 22px; bottom: 22px; z-index: 120;
  /* ⚠ 第三十三轮：面板改**绝对定位**浮出来，容器里只有圆钮参与布局。
     为什么非改不可：面板收起时用的是 `visibility: hidden`，而它**仍然占着 400px 宽**——
     容器的命中盒于是变成「圆钮 + 一大片看不见的空白」，
     鼠标从右下角那片空地上划过就会弹面板，而用户根本没碰到圆钮。
     改成绝对定位后，容器的命中盒**就是圆钮本身**；展开后是「圆钮 ∪ 面板」
     （两者都是本元素的后代，从圆钮挪到面板的整段路都不会触发 mouseleave）。
     ⚠ Tab 顺序不受定位影响：面板仍写在 DOM 里圆钮**之后**，且它是 `position: absolute`
     而非 `display: none` —— 展开后键盘照样 Tab 得进去。 */
}
.md-fab {
  /* 容器已不是 flex（第三十三轮），宽度靠自身 width/height 定，不再写 flex 缩写 */
  position: relative; width: 48px; height: 48px;
  border: 0; border-radius: 50%; cursor: pointer;
  background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
  color: #fff; display: grid; place-items: center;
  box-shadow: var(--sh-lg); transition: transform .16s, box-shadow .16s;
}
.md-fab:hover { transform: translateY(-2px); box-shadow: var(--sh-xl); }
.md-fab .md-ic { display: grid; place-items: center; transition: transform .18s; }
.md-bars { position: absolute; left: 0; right: 0; bottom: 8px; display: none; justify-content: center; gap: 2px; }
.md-fab.playing .md-bars { display: flex; }
.md-fab.playing .md-ic { transform: translateY(-5px); }
.md-bars i { width: 2px; height: 6px; background: rgba(255, 255, 255, .9); border-radius: 1px; animation: mdBar .9s ease-in-out infinite; }
.md-bars i:nth-child(2) { animation-delay: .15s; }
.md-bars i:nth-child(3) { animation-delay: .3s; }
@keyframes mdBar { 0%, 100% { height: 5px; } 50% { height: 11px; } }
/* 自动播被拦下的提示环 —— 让「需要你点一下」这件事在余光里就能看见 */
.md-fab.needs-tap::after {
  content: ''; position: absolute; inset: -4px; border-radius: 50%;
  border: 2px solid var(--brand-500); animation: mdPulse 1.6s ease-out infinite;
}
@keyframes mdPulse { 0% { transform: scale(.92); opacity: .9; } 100% { transform: scale(1.4); opacity: 0; } }

.md-panel {
  /* 浮在圆钮**左侧**（宽屏）：`right: 100% + 10px` 里的 100% 是**容器的宽**（=圆钮 48px），
     所以右边缘落在圆钮左边 10px 处。窄屏改浮在圆钮上方，见下方媒体查询。 */
  position: absolute; right: calc(100% + 10px); bottom: 0;
  /* min() 收口：窄屏自动缩到视口内，不需要再写一条媒体查询
     （写了也行，但两处各管一半，以后改宽度必然漏掉一处）。
     400px 是量出来的下限，不是随手挑的：再窄一点，底部的「工作日 9:00–19:00」
     就会被 `text-overflow: ellipsis` 截成「工作日 9:00…」——
     而**被截掉的那半句恰恰是这条策略的全部信息**。 */
  width: min(400px, calc(100vw - 44px));
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--sh-xl); overflow: hidden;
  opacity: 0; visibility: hidden; transform: translateY(8px) scale(.98);
  transform-origin: bottom right; transition: opacity .18s, transform .18s, visibility .18s;
}
.music-dock.open .md-panel { opacity: 1; visibility: visible; transform: none; }

/* ---- 头部 ---- */
.md-head { display: flex; align-items: flex-start; gap: 8px; padding: 12px 12px 10px; border-bottom: 1px solid var(--border-2); }
.md-hd-txt { flex: 1; min-width: 0; }
.md-title { font-size: 13.5px; font-weight: 700; }
.md-sub { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-3); margin-top: 3px; }
.md-studio {
  display: inline-flex; align-items: center; gap: 3px; max-width: 160px;
  background: var(--brand-50); color: var(--brand-700);
  padding: 1px 7px; border-radius: 999px; font-size: 11px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-x { border: 0; background: transparent; color: var(--text-3); cursor: pointer; padding: 3px; border-radius: var(--r-xs); display: grid; place-items: center; }
.md-x:hover { background: var(--surface-2); color: var(--text); }
.md-body { padding: 12px; }

/* ---- 需要用户知道的两种「没响」：被浏览器拦下 / 还没到时段 ---- */
.md-need {
  display: flex; align-items: center; gap: 7px; padding: 8px 10px; margin-bottom: 10px;
  background: var(--warning-bg); color: #b54708; border-radius: var(--r-sm); font-size: 12px; line-height: 1.45;
}
.md-need.info { background: var(--brand-50); color: var(--brand-700); }

/* ---- 正在播放 ---- */
.md-now-top { display: flex; align-items: center; gap: 10px; }
.md-now-main { flex: 1; min-width: 0; }
.md-now-t { font-size: 14px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-now-d {
  font-size: 11.5px; color: var(--text-3); margin-top: 2px; min-height: 15px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-mode {
  position: relative; flex: 0 0 auto; width: 34px; height: 34px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface);
  color: var(--text-2); cursor: pointer; display: grid; place-items: center;
}
.md-mode:hover { border-color: var(--brand-200); color: var(--brand); }
.md-mode.on { background: var(--brand-50); border-color: transparent; color: var(--brand); }
.md-mode-badge {
  position: absolute; top: -6px; right: -6px; min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 8px; background: var(--brand); color: #fff;
  font-size: 9px; line-height: 15px; text-align: center; letter-spacing: .2px;
}

/* ---- 进度条（可点击定位，也可按住拖动）----
   ⚠ 这里**不能**加 overflow: hidden —— 圆点会被裁掉一半，而「有个能拖的圆点」
     正是「这条进度条可以拖」的唯一视觉线索。 */
.md-track { position: relative; height: 6px; border-radius: 3px; background: var(--surface-2); margin: 12px 0 5px; cursor: pointer; }
.md-track:hover { background: var(--border-2); }
.md-fill { height: 100%; width: 0; border-radius: 3px; background: linear-gradient(90deg, var(--brand-500), var(--brand)); }
.md-knob {
  position: absolute; top: 50%; left: 0; width: 13px; height: 13px; border-radius: 50%;
  background: #fff; border: 3px solid var(--brand); box-shadow: 0 1px 4px rgba(37, 99, 235, .4);
  transform: translate(-50%, -50%); transition: transform .12s;
  pointer-events: none;   /* 事件必须落在 .md-track 上，否则按住圆点反而拖不动 */
}
.md-track:hover .md-knob { transform: translate(-50%, -50%) scale(1.15); }
.md-time { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ---- 控制区：对称三键居中（左右两枚衬着中间那枚大的）---- */
.md-ctrl { display: flex; align-items: center; justify-content: center; gap: 16px; margin: 12px 0 8px; }
.md-btn {
  width: 34px; height: 34px; border: 1px solid var(--border); background: var(--surface); border-radius: 50%;
  display: grid; place-items: center; color: var(--text-2); cursor: pointer; flex: 0 0 auto;
  transition: background .15s, color .15s, border-color .15s, transform .15s;
}
.md-btn:hover { border-color: var(--brand-200); color: var(--brand); }
/* ⚠⚠ 修饰类名绝不能与**全局裸类**同名 —— 这里踩过一次，代价是整块控制区错位：
   `.md-btn.main` 会同时命中 styles.css 顶部那条 `.main { flex: 1; margin-left: var(--sidebar-w); … }`
   （App 外壳的主内容区），于是播放键被套上侧边栏宽的左外边距 + flex:1，
   整排按钮挤出面板外 —— 而且**不报任何错**，只看截图会以为是「间距没调好」。
   故改用 `.play` / `.step`（这两个词不是任何全局类）。
   `tests/audit-style.mjs` 的【D】类会扫描这条规则，新增修饰类前先跑一次。 */
.md-btn.step { width: 40px; height: 40px; border: 0; background: transparent; }
.md-btn.step:hover { background: var(--surface-2); color: var(--text); }
.md-btn.play { width: 52px; height: 52px; border: 0; background: var(--brand); color: #fff; box-shadow: 0 6px 16px rgba(37, 99, 235, .32); }
.md-btn.play:hover { background: var(--brand-700); color: #fff; transform: scale(1.04); }
.md-btn.sm { width: 26px; height: 26px; border: 0; background: transparent; }
.md-btn.sm:hover { background: var(--surface-2); }

/* ---- 音量：图标 + 滑杆 + 百分比（数字让「55%」不再靠猜）---- */
.md-vol { display: flex; align-items: center; gap: 8px; margin: 2px 0 2px; color: var(--text-3); }
.md-range { flex: 1; min-width: 0; height: 4px; accent-color: var(--brand); cursor: pointer; }
.md-pct { flex: 0 0 36px; text-align: right; font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ---- 播放列表 ---- */
.md-list { max-height: 178px; overflow: auto; margin-top: 10px; border-top: 1px solid var(--border-2); padding-top: 6px; }
.md-list-head { display: flex; justify-content: space-between; align-items: center; padding: 2px 7px 6px; font-size: 11.5px; color: var(--text-3); }
.md-item { display: flex; align-items: center; gap: 9px; padding: 7px; border-radius: var(--r-sm); cursor: pointer; }
.md-item:hover { background: var(--surface-2); }
.md-item.on { background: var(--brand-50); }
.md-item-ic {
  flex: 0 0 26px; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; background: var(--surface-2); color: var(--text-3);
}
.md-item:hover .md-item-ic { color: var(--brand); }
.md-item.on .md-item-ic { background: var(--brand); color: #fff; }
.md-item-tx { flex: 1; min-width: 0; }
.md-item-t { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-item-d { font-size: 11px; color: var(--text-3); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-item-dur { flex: 0 0 auto; font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* 正在播放的音波：三根柱子的高低差就是「它在响」这一件事本身。
   ⚠ 用 currentColor 而不是写死颜色 —— 选中行的图标底色是品牌蓝，柱子必须跟着变白。 */
.md-eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; }
.md-eq i { width: 2.5px; border-radius: 1px; background: currentColor; animation: mdEq 1s ease-in-out infinite; }
.md-eq i:nth-child(1) { animation-delay: 0s; }
.md-eq i:nth-child(2) { animation-delay: .22s; }
.md-eq i:nth-child(3) { animation-delay: .44s; }
.md-item.paused .md-eq i { animation-play-state: paused; height: 4px; }
@keyframes mdEq { 0%, 100% { height: 4px; } 50% { height: 12px; } }

/* ---- 底部：自动播放开关 + 曲库入口 ---- */
.md-foot { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--surface-2); border-top: 1px solid var(--border-2); }
.md-auto {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--text-2); font-size: 11.5px; text-align: left;
}
.md-auto-tx { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-hint { color: var(--text-4); }
.md-switch {
  position: relative; flex: 0 0 34px; width: 34px; height: 19px; border-radius: 10px;
  background: var(--text-4); transition: background .2s;
}
.md-switch::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .2); transition: left .2s;
}
.md-switch.on { background: var(--brand); }
.md-switch.on::after { left: 17px; }
.md-by { font-size: 11.5px; color: var(--text-4); margin-left: auto; }
.md-foot .btn { margin-left: auto; flex: 0 0 auto; }

/* 曲目管理弹窗里的在架 / 已下架标记 */
.md-tag { flex: 0 0 auto; font-size: 11px; padding: 2px 7px; border-radius: 999px; background: var(--success-bg); color: var(--success); }
.md-tag.off { background: var(--surface-2); color: var(--text-3); }
.md-picked { font-size: 12px; color: var(--text-2); margin-top: 8px; }
.md-picked span { display: inline-flex; align-items: center; gap: 4px; }


/* 打印时不留悬浮物 */
@media print { .music-dock { display: none; } }

@media (max-width: 900px) {
  .wb-kpi { grid-template-columns: repeat(2, 1fr); }
  .res-row .grid3 { grid-template-columns: 1fr; }
  .adm-office .list { padding: 4px 10px; }
  /* 窄屏：面板改为**竖排到圆钮上方**（横排会让 320px 的卡片从左侧溢出屏幕） */
  .music-dock { right: 14px; bottom: 14px; }
  .md-panel { right: 0; bottom: calc(100% + 10px); width: min(320px, calc(100vw - 28px)); transform-origin: bottom right; }
}
/* 表头排序（第五十二轮）：可点的表头给出明确的视觉反馈 */
.tbl th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.tbl th.sortable:hover { color: var(--brand); }
.tbl th.sorted { color: var(--brand); }
.tbl th .sort-ar { margin-left: 4px; font-size: 10px; opacity: .95; }
.tbl th .sort-ar.muted { opacity: .35; }

/* ==================== 出勤管理（第五十四轮） ====================
   一个 31 天 × 全员的人员出勤矩阵。设计约束：
   ① 用 <table> 而不是 CSS grid —— 300 行 × 33 列要列宽天然对齐，
      grid 得靠 subgrid（浏览器支持参差），table 天生就对齐；
   ② 横向滚动 + 表头吸顶 + 左侧「姓名/部门」吸附，否则滚到 20 号就不知道
      自己在看谁的那一行（这是这张表唯一真正会让人出错的点）；
   ③ 三态（上班 / 休 / 请假）+ 节假日底色，全部复用既有色板，不引入新色。
   吸附列用固定 left 偏移：勾选 34 + 姓名 96 + 部门 88，改宽度要同步改 left。 */
.att-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.att-toolbar .grow { flex: 1; min-width: 0; }
.att-matrix-wrap { overflow: auto; max-height: 620px; border: 1px solid var(--border); border-radius: var(--r-sm); }
.att-matrix { border-collapse: separate; border-spacing: 0; font-size: 12px; width: max-content; min-width: 100%; }
.att-matrix th, .att-matrix td { border-bottom: 1px solid var(--border-2); border-right: 1px solid var(--border-2); padding: 0; text-align: center; }
.att-matrix thead th { position: sticky; top: 0; z-index: 3; background: var(--surface-2); color: var(--text-3); font-weight: 600; height: 34px; white-space: nowrap; }
.att-matrix thead th .dw { display: block; font-size: 10px; font-weight: 400; opacity: .75; }
.att-matrix thead th.off { color: var(--danger); background: var(--danger-bg); }
.att-matrix thead th.mk { color: var(--brand); }
.att-matrix tbody td { height: 34px; white-space: nowrap; }
.att-matrix .c-ck { width: 34px; min-width: 34px; position: sticky; left: 0; z-index: 2; background: var(--surface); }
.att-matrix .c-name { width: 96px; min-width: 96px; position: sticky; left: 34px; z-index: 2; background: var(--surface); text-align: left; padding: 0 8px; }
.att-matrix .c-dept { width: 88px; min-width: 88px; position: sticky; left: 130px; z-index: 2; background: var(--surface); text-align: left; padding: 0 8px; color: var(--text-3); }
.att-matrix thead .c-name, .att-matrix thead .c-dept, .att-matrix thead .c-ck { z-index: 4; background: var(--surface-2); }
.att-matrix tr.sel .c-name, .att-matrix tr.sel .c-dept, .att-matrix tr.sel .c-ck { background: var(--brand-50); }
.att-cell { width: 30px; min-width: 30px; cursor: pointer; user-select: none; color: var(--text-4); }
.att-cell span { display: block; margin: 2px; border-radius: 4px; line-height: 26px; font-size: 11px; }
.att-cell.st-work span { color: var(--text-3); background: #fff; border: 1px solid var(--border-2); }
.att-cell.st-rest span { color: var(--text-3); background: var(--surface-2); }
.att-cell.st-leave span { color: #b54708; background: var(--warning-bg); font-weight: 700; }
.att-cell.st-holiday span { color: var(--danger); background: var(--danger-bg); }
.att-cell.ov span { box-shadow: inset 0 0 0 1px var(--brand-500); }
.att-cell.today span { box-shadow: inset 0 0 0 2px var(--brand); }
.att-matrix.readonly .att-cell { cursor: default; }
.att-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--text-3); }
.att-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }

/* ==================== 项目详情 · 产量排行（第五十四轮） ====================
   「工时与人力」页签里、成员工时表**上方**的 Top5 展示块。
   它必须是一个独立的展示块，不能做成「表格的另一种排序」——
   表头排序是同一张表的视图状态，排行榜是结果展示（只取前 5、带名次、要显眼）；
   混在一起的话，点表头排序会让排行榜跟着一起乱序。
   5 列在 1100px 以下收成 3 列（详情页右侧还有边栏，实际可用宽度比整页窄）。 */
.pd-rank { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
@media (max-width: 1180px) { .pd-rank { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.pd-rank .pd-rk { border: 1px solid var(--border); border-radius: var(--r-sm); padding: 10px 12px; background: var(--surface); min-width: 0; }
.pd-rank .pd-rk.first { border-color: #f59e0b; background: linear-gradient(180deg, #fffbeb, #fff); }
.pd-rank .pd-rk .medal { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 700; border-radius: 999px; padding: 1px 8px; }
.pd-rank .pd-rk .qty { font-size: 19px; font-weight: 800; line-height: 1.2; }
.pd-rank .pd-rk .unit { font-size: 11px; font-weight: 500; color: var(--text-3); margin-left: 3px; }
.pd-rank .pd-rk .nm { font-size: 12.5px; font-weight: 600; margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pd-rank .pd-rk .dp { font-size: 11px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 文件选择控件（第五十九轮）：浏览器原生的「选择文件」按钮又小又丑、和整套 UI 不搭。
   用 ::file-selector-button 把它改成与 .btn 同款的外观，**不改任何 markup**，
   所以电脑端打卡凭证、员工照片、手机端上传全都一起生效。 */
input[type="file"] { font-size: 13px; color: var(--text-3, #64748b); max-width: 100%; }
input[type="file"]::file-selector-button {
  margin-right: 10px; padding: 8px 14px; border-radius: 8px; border: 1px solid var(--border, #e2e8f0);
  background: #fff; color: var(--text, #0f172a); font-weight: 600; font-size: 13px; cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
}
input[type="file"]::file-selector-button:hover { border-color: var(--brand, #2563eb); color: var(--brand, #2563eb); background: var(--brand-50, #eff6ff); }
input[type="file"]::-webkit-file-upload-button {
  margin-right: 10px; padding: 8px 14px; border-radius: 8px; border: 1px solid var(--border, #e2e8f0);
  background: #fff; color: var(--text, #0f172a); font-weight: 600; font-size: 13px; cursor: pointer;
}


/* 员工表单分区标题（第九十二轮） */
.emp-sec{
  font-size:12px;font-weight:700;color:#667085;letter-spacing:.3px;
  margin:18px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--border);
}
.emp-sec:first-child{margin-top:2px}


/* ==================== 资产管理 · 宿舍卡片（第一百零一轮） ==================== */
.dorm-card{
  background:#fff;border-radius:16px;overflow:hidden;cursor:pointer;
  border:1px solid #eef1f6;box-shadow:0 2px 10px rgba(16,24,40,.05);
  transition:transform .16s ease,box-shadow .16s ease,border-color .16s;
}
.dorm-card:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(37,99,235,.14);border-color:#cfdcff}
.dorm-card.empty{background:linear-gradient(180deg,#fbfcff,#f6f8fc)}
.dorm-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:13px 16px;background:linear-gradient(135deg,#2f6bff,#1e4fd8);color:#fff;
}
.dorm-card.empty .dorm-head{background:linear-gradient(135deg,#94a3b8,#64748b)}
.dorm-code{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:800;letter-spacing:.4px}
.dorm-pill{background:rgba(255,255,255,.22);border-radius:999px;padding:3px 10px;font-size:11.5px;font-weight:700}
.dorm-pill.off{background:rgba(255,255,255,.16);opacity:.85}
.dorm-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:#eef1f6;border-bottom:1px solid #eef1f6}
.dorm-stat{background:#fff;padding:10px 12px}
.dorm-stat .k{font-size:11px;color:#8a94a6;margin-bottom:3px}
.dorm-stat .v{font-size:15px;font-weight:800;color:#1f2937;font-variant-numeric:tabular-nums}
.dorm-stat.accent .v{color:#b45309}
.dorm-stat.accent{background:#fffdf5}
.dorm-members{display:flex;flex-wrap:wrap;gap:7px;padding:12px 14px;min-height:52px;align-items:flex-start}
.dorm-empty{font-size:12px;color:#a6aebd;line-height:28px}
.dorm-chip{
  display:inline-flex;align-items:center;gap:6px;background:#f5f7fb;border:1px solid #e9edf4;
  border-radius:999px;padding:3px 10px 3px 4px;font-size:12px;
}
.dorm-chip i{
  width:20px;height:20px;border-radius:50%;color:#fff;font-style:normal;
  display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;
}
.dorm-chip b{font-weight:700;color:#374151}
.dorm-chip em{font-style:normal;color:#8a94a6;font-size:11px}
.dorm-foot{
  padding:9px 14px;border-top:1px dashed #eef1f6;font-size:11px;color:#98a2b3;background:#fcfdff;
}


/* ==================== 宿舍卡片 · 入住人员行（第一百零三轮） ==================== */
.dorm-members{display:flex;flex-direction:column;gap:6px;padding:10px 12px;min-height:52px}
.dorm-person{
  display:flex;align-items:center;gap:10px;padding:7px 9px;border-radius:12px;
  background:linear-gradient(180deg,#fafbff,#f5f7fc);border:1px solid #eceff6;
}
.dorm-person:hover{border-color:#cfdcff;background:#f4f8ff}
.dp-av{
  width:32px;height:32px;border-radius:50%;flex:none;color:#fff;font-weight:800;font-size:13.5px;
  display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,24,40,.16);
}
.dp-main{flex:1;min-width:0}
.dp-name{font-size:13.5px;font-weight:700;color:#1f2937;display:flex;align-items:center;gap:6px}
.dp-dept{font-size:10.5px;font-weight:500;color:#94a3b8;background:#eef2f8;border-radius:6px;padding:1px 6px}
.dp-phone{font-size:11.5px;color:#667085;margin-top:2px;font-variant-numeric:tabular-nums}
.dp-fee{text-align:right;flex:none}
.dp-fee b{display:block;font-size:13px;color:#b45309;font-weight:800}
.dp-fee em{font-style:normal;font-size:10.5px;color:#98a2b3}

/* dp v2：头像 + 姓名 + 电话（第一百零三轮修正） */
.dorm-person{padding:9px 10px;gap:11px;border-radius:14px}
.dp-av{width:36px;height:36px;font-size:15px}
.dp-name{font-size:14px;font-weight:700;color:#1f2937;line-height:1.25}
.dp-phone{font-size:12px;color:#667085;margin-top:3px;font-variant-numeric:tabular-nums;letter-spacing:.2px}
.dp-dept,.dp-fee{display:none}
/* dp v2 marker */
/* 表头/单元格居中（组织架构 操作列） */
th.c-center, td.c-center { text-align: center !important; }

/* 组织架构成员表：操作列（最后一列）表头与单元格居中 */
.org-layout table th:last-child,
.org-layout table td:last-child { text-align: center !important; }
.org-layout table th:last-child > *,
.org-layout table td:last-child > * { margin-left: auto !important; margin-right: auto !important; }
/* scoped-org-actions */
