/* ==========================================================================
   ZIMARK 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;
  /* ⚠ 别名（第四十二轮补）：`--success/warning/danger/info/purple/teal` 都成对配了 `-bg`，
     唯独 `--brand` 没有 —— 于是任何写 `var(--brand-bg)` 的声明都会**整条静默失效**
     （无定义 + 无兜底 → 计算值阶段直接作废，不报错）。
     本轮市场情报的「跟进中」小签正要用它，按梯度补成最浅的一档蓝。 */
  --brand-bg:  var(--brand-50);

  --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; }

/* ==========================================================================
   登录页
   ========================================================================== */
.login-page { display: flex; min-height: 100vh; }

.login-brand {
  flex: 1.15; position: relative; overflow: hidden; color: #fff;
  background: linear-gradient(150deg, #0b1224 0%, #16244a 45%, #1e3a8a 100%);
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 56px 60px;
}
.login-brand::before {
  content: ""; position: absolute; width: 620px; height: 620px; border-radius: 50%;
  background: radial-gradient(circle, rgba(59,130,246,.42), transparent 66%);
  top: -220px; right: -180px;
}
.login-brand::after {
  content: ""; position: absolute; width: 460px; height: 460px; border-radius: 50%;
  background: radial-gradient(circle, rgba(124,58,237,.34), transparent 68%);
  bottom: -200px; left: -120px;
}
.login-brand > * { position: relative; z-index: 1; }
.login-logo { display: flex; align-items: center; gap: 12px; font-size: 19px; font-weight: 700; letter-spacing: .3px; }
.login-logo .mark {
  width: 40px; height: 40px; border-radius: 11px; background: linear-gradient(135deg,#3b82f6,#7c3aed);
  display: grid; place-items: center; font-weight: 800; font-size: 18px;
  box-shadow: 0 8px 20px rgba(59,130,246,.42);
}
.login-hero h1 { font-size: 38px; line-height: 1.28; font-weight: 700; letter-spacing: -.6px; margin-bottom: 18px; }
.login-hero h1 em { font-style: normal; background: linear-gradient(90deg,#7dd3fc,#c4b5fd); -webkit-background-clip: text; background-clip: text; color: transparent; }
.login-hero p { font-size: 15px; color: #b9c6de; max-width: 460px; line-height: 1.85; }
.login-feats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-top: 40px; max-width: 520px; }
.login-feat {
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
  border-radius: var(--r); padding: 15px 16px; backdrop-filter: blur(8px);
}
.login-feat .t { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; display: flex; align-items: center; gap: 7px; }
.login-feat .d { font-size: 12px; color: #93a4c0; line-height: 1.65; }
.login-foot { font-size: 12px; color: #6b7c99; display: flex; gap: 18px; }

.login-panel {
  width: 468px; flex: 0 0 468px; background: var(--surface);
  display: flex; flex-direction: column; justify-content: center; padding: 48px 52px;
}
.login-panel h2 { font-size: 24px; font-weight: 700; letter-spacing: -.3px; }
.login-panel .sub { color: var(--text-3); font-size: 13.5px; margin: 6px 0 30px; }
.login-form .field { margin-bottom: 18px; }
.login-form label { display: block; font-size: 13px; font-weight: 600; color: var(--text-2); margin-bottom: 7px; }
.demo-box { margin-top: 28px; border-top: 1px dashed var(--border); padding-top: 20px; }
.demo-box .hint { font-size: 12px; color: var(--text-3); margin-bottom: 11px; display: flex; align-items: center; gap: 6px; }
.demo-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.demo-chip {
  display: flex; align-items: center; gap: 9px; padding: 8px 10px; border: 1px solid var(--border);
  border-radius: var(--r-sm); background: var(--surface-2); text-align: left; transition: .15s;
}
.demo-chip:hover { border-color: var(--brand-200); background: var(--brand-50); transform: translateY(-1px); }
.demo-chip .av { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 600; flex: 0 0 26px; }
.demo-chip .n { font-size: 12.5px; font-weight: 600; line-height: 1.3; }
.demo-chip .r { font-size: 11px; color: var(--text-3); line-height: 1.3; }

/* ==========================================================================
   应用骨架
   ========================================================================== */
.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(135deg,#3b82f6,#7c3aed);
  display: grid; place-items: center; font-weight: 800; font-size: 15px; 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); }
/* 个人中心是**唯一**一个不进侧边栏的页面（`NAV` 里 `nav: false`）——
   它被打开时，侧边栏不会有任何一项高亮，整屏只剩面包屑在说「你在哪」。
   这个 active 态补上那一半提示（由 `renderNav()` 统一维护，见 app.js）。
   ⚠ 用 `--brand-50`（自带浅蓝）而不是 `--brand-soft`：后者**从来没有定义过**，
     写 `var(--brand-soft)` 会让整条声明在计算值阶段静默失效（第二十九轮那族坑）。 */
.header-btn.active { background: var(--brand-50); 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)); }
/* 五列（第四十五轮）：项目成本从三轨变四轨，tile 也随之从 4 个变 5 个。
   与 .c4 同形（复合选择器，`.c5` 不是全局裸类 —— audit-style 的 D 类守着这条）。 */
.grid.c5 { grid-template-columns: repeat(5, 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); }

.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); }

/* ⭐ 无头弹窗（第三十七轮 · 人物名片）：`openModal({ bare: true })`，见 ui.js 的注释。
   去掉「员工名片 ｜ ✕」那条白横条，弹窗里只剩名片本身。
   ⚠ `overflow: hidden` 是必须的：无头之后**内容自带底色并顶到弹窗四角**
     （照片、专属色淡底），圆角得由弹窗来裁 —— 否则四个角各露出一小块方角，
     比原来那条横条更扎眼。
   ⚠ `padding: 0` 也是必须的：`.modal-body` 的 20px 是给表单弹窗用的，
     留着它，名片四周仍有一圈白边 —— 那正是「看起来不协调」的另一半。 */
.modal.bare { overflow: hidden; }
.modal.bare .modal-body { padding: 0; }

.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; } }
.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; }

/* ==========================================================================
   图表
   ========================================================================== */
.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: 1240px) { .login-brand { padding: 44px; } .login-brand h1 { font-size: 32px; } }
@media (max-width: 1024px) {
  .login-brand { display: none; }
  .login-panel { width: 100%; flex: 1; padding: 40px 28px; }
}
@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-page` 里面（它在右边那一栏），却要沿用同一套红 / 灰 / 细线 ——
   只挂两个选择器的话，`.tw-opt` 的边框与选中态会因为 `var()` 为空而**整条失效**
   （症状与第三十六轮那次一模一样：元素在、类名对、就是不出样式）。
   与其给每个新块各补一份变量，不如立一个显式的「作用域标记」类：谁要用就挂上它。 */
.tw-scope {
  --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);
}
/* ⚠ 上面这块**只放变量**；装饰另起一段，且**不含 `.tw-scope`** ——
   `.tw-scope` 是「作用域标记」，挂它的地方本来就在别的卡片里面，
   给它再套一层白底 / 圆角 / 阴影，就成了卡片里再套一张卡片。 */
.tw-page,
.tw-profile {
  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;
}

/* 统计三项 + 搜索框共处一行（窄屏自动折行）。
   `align-items: flex-end` 让搜索框与数字的**基线**对齐 —— 数字是 26px 的大字，
   居中对齐会显得搜索框悬在半空。 */
.tw-tools { margin-top: 22px; display: flex; align-items: flex-end; flex-wrap: wrap; gap: 16px 26px; }
.tw-tools .tw-meta { margin-top: 0; }
/* 搜索框：推到自己这一行的右端。`margin-left: auto` 在折行后会自动失效
   （它被挤到第二行时左边距仍是 auto，于是靠右）—— 这是我们想要的：窄屏下它单独占一行、右对齐。 */
.tw-find {
  margin-left: auto; display: inline-flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 13px; border: 1px solid var(--tw-line); border-radius: 8px;
  background: #fff; color: var(--tw-ink-soft);
  transition: border-color .2s var(--tw-ease), box-shadow .2s var(--tw-ease);
}
.tw-find:focus-within { border-color: var(--tw-red); box-shadow: 0 0 0 3px rgba(214,32,42,.09); }
.tw-find-in {
  border: 0; outline: none; background: transparent; font: inherit; font-size: 13px;
  color: var(--tw-ink); width: 176px;
}
/* `type=search` 的浏览器自带清除按钮：留着它。清空后要能回到整面墙，
   而「把文字删掉」在触屏上比点一下难受得多。 */
.tw-find-n { font-size: 12px; color: var(--tw-red); white-space: nowrap; opacity: 0; transition: opacity .2s; }
.tw-find-n.on { opacity: 1; }

/* ---------- 墙 ---------- */
/* ⚠ 第三十八轮起 `.tw-scene` 里放的是**多个分区**（管理团队 + 各部门），
   不再是单独一面墙 —— 每个分区自带标题与一面 `.tw-wall`。 */
.tw-scene { padding: 6px 34px 38px; }

/* 分区（方案 B 的「目录」）
   ── 300 人的墙必须能按组织找到人。分区头给三样东西：部门名 / 人数 / 有工照的人数。
   ⚠ 第三样（工照数）不是装饰：它把「这个部门没人传照片」这件事**摊在明面上**，
     否则整墙的灰度文字卡看起来只是「设计成这样」。 */
.tw-sec { margin-top: 30px; }
.tw-sec:first-child { margin-top: 0; }

/* 小部门并排（2026-10-17）
   ── 1~3 人的部门各自独占一整段时，一行只放得下一张卡，右边全是空白；
      四个一人部门就是四行空白。这里把它们横向并列成一行。
   ⚠ 只在**连续**的小分区之间成行（分组逻辑在 web/pages/team-wall.js）——
     把夹在大部门中间的小部门抽出来，会让「部门目录」的顺序变得不可预测。
   ⚠ 部门名仍保留在各自的区头里：分区是目录，藏了名字就找不到人。 */
.tw-minor {
  display: grid; gap: 22px 24px; margin-top: 30px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.tw-minor.tw-off { display: none; }
.tw-minor .tw-sec { margin-top: 0; }
.tw-minor .tw-sechd { padding-bottom: 8px; margin-bottom: 10px; gap: 9px; }
.tw-minor .tw-sechd h3 { font-size: 14px; }
/* 一人部门不再写「暂无工照」：那句话是给「整个部门都没传照片」用的，
   放在 1 人部门上只是噪音，且把区头挤成两行。 */
.tw-minor .tw-secph { display: none; }
.tw-sechd {
  display: flex; align-items: baseline; gap: 12px;
  padding-bottom: 11px; margin-bottom: 14px; border-bottom: 1px solid var(--tw-line);
}
/* 分区名左侧的红杠 —— 全墙第二处（也是最后一处）带色的元素，另一处是悬停描边 */
.tw-sechd::before {
  content: ""; width: 18px; height: 3px; background: var(--tw-red);
  align-self: center; border-radius: 2px; flex: 0 0 auto;
}
.tw-sechd h3 { margin: 0; font-size: 15px; font-weight: 600; color: var(--tw-ink); }
.tw-secn { font-size: 12px; color: var(--tw-ink-soft); }
.tw-secph { font-size: 11.5px; color: #a8a5a0; margin-left: auto; }

/* 搜索过滤态：命中的留下，其余收起的**同时收掉分区标题**（标题还在就是「这个部门没人」的假象）。
   ⚠ 用 `.tw-off` 而不是 `hidden` 属性：项目里已有多处 `display` 覆写，
     统一走一个类名比逐处确认「哪个 display 生效」要可靠。 */
.tw-sec.tw-off, .tw-tile.tw-off { display: none; }
.tw-noresult {
  margin-top: 26px; padding: 26px 0; text-align: center;
  font-size: 13px; color: var(--tw-ink-soft);
}

/* 列数由 `auto-fill` 随宽度自己算（1470 视口下 10 列 · 格子 121px 见方）。
   ⚠ 第二十四轮起**所有砖等大**，不再有跨 2 列的格子 —— 原先「必须让 4 个跨格 + 2 个普通格
     凑满 10 列、否则右边挂出孤列」的约束已经不存在了（那是 21–23 轮的事）。
     这里仍保持 110px：全员上墙后格子数以百计，110px 是「一格能认出人、一屏能看下几十个」的交点。 */
.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; }

/* ---------- 名片弹窗（左右分栏） ----------
   ⭐ 第三十七轮：弹窗改为**无头**（`openModal({ bare: true })`），所以这里
   不再需要「负 margin 抵掉 body 内边距」那套技巧 —— `.modal.bare .modal-body`
   的内边距已经是 0。四角统一用 `var(--r-lg)`（与弹窗同值），
   `.modal.bare` 的 `overflow: hidden` 再做一层收口。

   ⭐ 第三十六轮改版：**姓名压在照片底部的深色渐变上**。
   旧版左边是「中性灰底 + 灰度照 + 右下角红斜渐晕」、右边一张纯白卡 ——
   两块底色不同、照片与文字区完全割裂，这就是用户说的「两张皮」。
   现在照片本身成为版面的一部分，右侧不再把姓名 / 职位写第二遍。 */
.tw-profile {
  display: grid; grid-template-columns: 300px minmax(0, 1fr);
  border-radius: 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-band`），不直接写 `background`：
   这样它对 CSS 仍是「可覆盖的一层」，窄屏那条媒体查询才关得掉它
   （内联的 `background` 只有 `!important` 压得住，那是另一种味道）。
   没有 `--tw-band` 时 `background-image: none` → 只剩 `--tw-wall` 的底。 */
.tw-portrait-in {
  position: absolute; inset: 0;
  background: var(--tw-wall);
  background-image: var(--tw-band, none);
}
/* ⭐ 名片里**不回灰度**（第三十六轮）。
   墙面要灰度，是因为一面墙上 11 张彩照 + 31 张文字卡会互相打架（第二十二轮定的口径）；
   名片里只放大一个人，没有「互相打架」这个前提 —— 而用户要求的正是「点击之后认识本人」，
   这一点上彩色照片比灰度更称职。
   ⚠ 因此这里**没有**「悬停回彩」那条规则：默认就是彩的，再叠一层悬停没有意义。 */
.tw-portrait-in img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* ⭐ 正片取景「让人像在画框里居中」（第三十六轮续）。
   12 张工照实测：**头顶距上边 0.0%–6.4%，均值 4%**（一张是完全顶到边），
   而人像区是 300×340 —— `cover` 时纵向**一点都不裁**，于是头顶必然顶着上沿、
   人脸落在上四分之一、下面一大片西装。用户的原话就是「照片都没有居中」。

   ⭐⭐ 这里有一条绕不过去的硬约束：**`cover` 给不了头顶留白**。
     cover 保证照片铺满画框、纵向绝不裁切 ⇒ 头顶留白**恒等于原图那 4%**；
     放大只会让人更大、不会让人更靠下 —— 实测放到「留白够看」时，头会宽过整个画框。
   → 唯一出路是**不让照片铺满**：`contain` 把正片原样缩小、**贴底**放。
     人脸落到纵向 ~40%、头顶留出 ~15% 呼吸位，与参考稿的构图一致。
     贴底而不是居中：多出来的空档全部留在**上边**（下边的空档正好被姓名条盖住，看不见）。
   ⚠ 上方那截空档的底色由 JS 以 `--tw-band` 注入（`photoTopGradient` 取照片**首行**的
     **横向色带**，不是一个平均色），**不要**改成 `--tw-wall` 或「同图模糊垫底」——
     两者都会在照片上沿留一道横向色阶（照片首行本身左右就有 ~25 阶渐变，填平色必然对不上），
     原因见 photos.js 里 `photoTopGradient` 的注释。 */
.tw-pshot { object-fit: contain; object-position: center bottom; }

/* 无照片（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; }
/* ⭐ 头部一行两列：左「部门 + 简介」，右关闭按钮。
   第三十七轮弹窗去掉了标题栏，关闭入口必须补在这里 ——
   位置与用户参考稿一致：**与正文同排，不浮在内容之上**。
   ⚠ 别改成 `position: absolute` 钉在角上：它会压在「— 人事部」或标签上，
     而右栏首行的高度本来就随 `bio` 有无而变（有的名片没有 bio）。 */
.tw-phead { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.tw-phead-main { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.tw-pclose {
  flex: 0 0 auto; width: 30px; height: 30px; margin: -4px -4px 0 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; color: var(--tw-ink-soft); transition: .15s;
}
.tw-pclose:hover { background: var(--tw-tile); color: var(--tw-ink); }

/* 名片右上角的动作区（第三十八轮）——关掉标题栏之后，这里是唯一的按钮群。
   `gap: 6px` + `flex: 0 0 auto`：动作再多也不能把左边的部门名挤变形。 */
.tw-pacts { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* 行政部的兜底重置入口：**故意做得低调**（描边按钮、非红底）。
   它是一把备用的钥匙，不是这页面的主操作 —— 做成红色主按钮，
   就会有人顺手点（而它清的是别人的名片内容）。 */
.tw-preset {
  display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 11px;
  font: inherit; font-size: 12px; white-space: nowrap; cursor: pointer;
  border: 1px solid var(--tw-line); border-radius: 8px; background: #fff;
  color: var(--tw-ink-soft); transition: .15s;
}
.tw-preset:hover { border-color: var(--tw-ink-soft); color: var(--tw-ink); }

/* ---------- 我的名片（个人中心页签 · 第三十八轮）----------
   预览卡借 `.tw-page` 拿那一套 `--tw-*` 变量（也顺带拿到白底 / 圆角 / 细阴影），
   内部标签与引言**直接用名片上的类**（`.tw-ptags` / `.tw-ptag` / `.tw-pquote`）——
   于是「编辑时看到的」与「墙上显示的」不是两套样式，改一边另一边自然跟着变。 */
.tw-me { padding: 0; box-shadow: none; border: 1px solid var(--tw-line); }
.tw-me-in { padding: 20px; display: flex; flex-direction: column; gap: 15px; }
.tw-me-top { display: flex; align-items: center; gap: 13px; }
.tw-me-name { font-size: 19px; font-weight: 900; letter-spacing: -.01em; color: var(--tw-ink); }
.tw-me-pos { margin-top: 3px; font-size: 12.5px; color: var(--tw-ink-soft); }

/* 标签选择器：候选**由接口下发**，这里只管画。
   ⚠ 选中态必须够明显（红边 + 红字 + 浅红底）—— 它同时承担「已选了几个」的读秒功能，
     而「最多 4 个」的限制是**点了才知道**的，选中态看不清就会一直点、一直收到同一条提示。 */
.tw-pick { display: flex; flex-wrap: wrap; gap: 7px; }
.tw-opt {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 7px 11px; font: inherit; font-size: 12.5px; cursor: pointer;
  border: 1px solid var(--tw-line); border-radius: 8px; background: #fff;
  color: var(--tw-ink-soft); transition: .15s;
}
.tw-opt b { font-weight: 500; color: var(--tw-ink); }
.tw-opt i { font-style: normal; font-size: 9.5px; opacity: .55; letter-spacing: .04em; }
.tw-opt:hover { border-color: var(--tw-ink-soft); }
.tw-opt.on { border-color: var(--tw-red); background: #fff5f5; color: var(--tw-red); }
.tw-opt.on b { color: var(--tw-red); font-weight: 700; }
.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; }
  /* 窄屏是「上照片、下档案」的单栏：画框变成**宽而矮**的一条，方照片按 contain
     只占中间一块 —— 横向色带会被拉满整条，在照片左右两侧留下两道对不上的竖边。
     窄屏干脆不要色带（`.tw-pshot` 的取景不变），退回画框的中性底。
     ⚠ 这条只能在**真机窄视口**下验，probe 的【丙】组就是为此把视口切到 800 的。 */
  .tw-portrait-in { background-image: none; }
  .tw-pinfo { padding: 20px 22px 24px; }
}
@media (max-width: 720px) {
  .tw-hero { padding: 26px 20px 22px; }
  .tw-scene { padding: 4px 20px 28px; }
  /* 窄屏下搜索框单独占一整行 —— `margin-left: auto` 挤出来的那点宽度在 360px 屏上只剩 100px 出头，
     输入两个字就看不清自己打了什么。 */
  .tw-find { margin-left: 0; width: 100%; }
  .tw-find-in { width: auto; flex: 1; }
  .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` / `.delta.up` / `.delta.down` / `.delta.neutral`）：
   它唯一的消费者是门户绩效榜按 `portal_scores.delta_pct` 排的「进步榜」，
   而那个榜连同那份数据源一起下架了（榜单改成按已审批产值排）。
   删掉的理由与「部门积分榜」同一条：**判据是「还有谁在用」**，而不是「留着也不占地方」——
   留着一段没人产出的类名，下次改榜的人会以为这里还有一条口径要维护。
   要恢复，从 git 历史取回这一段。 */

/* ---- 通用：首字母圆形头像 ---- */
.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); }

/* ---- 个人产值榜：当期前三名的领奖台（第二十九轮「只保留前三」，第三十九轮换口径，
         第四十轮撤金额） ----
 * ⚠ 第三十轮起门户上**只剩这一个榜**（部门积分榜已下架），
 *   第二十九轮那条「必须与部门积分榜不同形」的约束随之作废。
 * ⚠ 第三十九轮：排序依据从「人事手填评分」换成「已审批产值」，形态一个字没改 ——
 *   大数字从 `92.5`（分）变成 `¥9,870.00`（元），`/ 100` 的分母位与环比徽章一起撤掉，
 *   原位补上**产值构成**一行（`.pp-parts` / `.pm-parts`）。
 * ⚠⭐ 第四十轮：**卡面正中那个大数字（金额）撤掉了**，`.pp-score` 一整套规则随之删除 ——
 *   用户口径「排名时以产值来排名，但是产值金额可以不显示，可以显示产量」。
 *   排序口径没动（仍是产值降序），撤的是显示；`.pp-parts` 从注释升为主视觉。
 *   要恢复金额：CSS 补回 `.perf-pod .pp-score` 两条、JS 补回 `perfPod()` 里那一行。
 * ⚠ 视觉顺序用 `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 不占布局，会在卡片下方留一段空洞。
   ⚠ 第四十轮：卡面正中那个 34px 的大数字没了，冠军卡与银/铜卡的高度差一下少 6px，
      所以内边距再补一点点（22/18 → 26/20）把这个落差找回来，否则「领奖台」会塌成排排站。 */
.perf-pod.r1 {
  order: 2; max-width: 300px; padding: 26px 18px 20px;
  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); }
/* 产量构成（「齐天项目 168 小时 · 等 2 项」）——
 * ⭐⭐ 第四十轮起它是**卡面上唯一的信息**（金额已撤，见 portal.js 的 perfCard 顶部注释），
 *   所以从「11px 的注释」升成「13/14px 的主视觉」：原来它只是金额的解释，
 *   现在它自己承担「为什么他排这个名次」。
 *   字号不许再往下压（撤掉金额后，这一行糊掉就等于整个榜是空的）；
 *   颜色也不许用最淡那一档（`--text-4` 在银 / 铜底上会糊）。
 * ⚠ 高度**固定两行**（`min-height` = `max-height` = 3.1em）：三张卡的构成区一样高，
 *   高度差就只剩「内边距 / 头像 / 名字字号」这几项可控的量 ——
 *   否则「项目名长的银牌卡比冠军卡还高」，`align-items: flex-end` 之下的领奖台形态就塌了。
 *   ⚠ 为什么不写 `white-space: nowrap` + 省略号（上一版的写法）：项目名动辄十几个汉字
 *     （「数据采集员驻场派遣服务（苏州基地）」），单行必然被截成「数据采集员驻场派…」——
 *     撤掉金额之后，这一行是**卡面上唯一的信息**，截掉就等于这个榜什么都不说。
 * ⚠ 冠军卡底色是深蓝，`--text-*` 那套在它上面全部失效 → 单独给白色系。 */
.perf-pod .pp-parts {
  margin-top: 11px; max-width: 100%;
  font-size: 13px; line-height: 1.55; font-weight: 600; color: var(--text-2);
  min-height: 3.1em; max-height: 3.1em; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  word-break: break-word;
}
.perf-pod.r1 .pp-parts { font-size: 14px; color: rgba(255,255,255,.95); }

/* 回落说明（「今日暂无产值登记，显示本月累计」）—— 它答的是「你看到的为什么不是今天的」，
   因此**必须在榜上方、带底色**：压在榜下面、或者写成一行灰字，都会被当成页脚注。 */
.perf-note {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 12px; padding: 7px 11px; border-radius: var(--r);
  background: var(--brand-50); color: var(--brand-700);
  font-size: 12px; line-height: 1.5;
}

/* 「我的排名」细条 —— 榜单砍到只剩三个人之后，**榜外的人**才是这条的主要读者。
   ⛔ 不做吸底（参考稿是单页样例，可以吸底）：门户是长滚动页，
      固定条会永久盖住照片墙与公司简介。 */
.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; }
/* 我这条也带构成（取第 1 项）—— 与榜上三张卡同一个问题、同一句话法。
   `min-width:0` + 省略号：窄屏时先牺牲构成，不许把「第几名」和相邻名次挤掉。 */
.perf-me .pm-parts {
  min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.perf-me .pm-gap { margin-left: auto; }
/* ⚠ 第四十轮删掉了 `.perf-me .pm-score`（末尾那个 17px 的金额）。
   它必须和榜上三张卡**同时**撤 —— 原来这条里还写着「落后第 2 名 ¥161.52」，
   自己的数已知、一减就是对方的数：只撤领奖台等于没撤（见 portal.js 的 perfMe 注释）。 */

/* ---- 部门积分榜 ---- */
/* ⚠ 第三十轮已下架（用户口径「部门积分榜 这个板块不用了」）：`.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; }

/* ---------- 简历 ---------- */
.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; }


/* ==========================================================================
   市场情报（第四十二轮）
   --------------------------------------------------------------------------
   ⚠ 命名两条纪律，两条都是本仓踩过的坑：
     ① 顶层类名一律带 `intel-` 前缀；卡片**内部**的子元素沿用既有约定
        （`.intel-card .hd` / `.ft` / `.facts`）—— 与本仓 `.proj-card` /
        `.pool-card` / `.role-card` 是同一套写法。这些短名在全局**不是裸类**
        （只以后代选择器出现过），所以不会把布局属性漏到别的元素上。
     ② 修饰段一律加前缀（`.intel-tag.b-bid`、`.intel-state.s-new`）——
        修饰段**不能**用与全局裸类同名的词（`.main` / `.side` / `.empty`）：
        `.A.B` 会额外吃下全局那条规则，症状是**整块错位且不报任何错**
        （第三十二轮 `.md-btn.main` 被套上侧边栏左外边距的事故，
        `tests/audit-style.mjs` 的 D 类守着这条）。
   ========================================================================== */

/* ---- 顶部同步条：全页唯一一处「这个池子还新不新」的信息 ---- */
.intel-sync {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 14px; margin-bottom: 16px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: var(--r); font-size: 12.5px; color: var(--text-2);
}
.intel-sync svg { color: var(--text-3); flex: 0 0 auto; }
.intel-sync .sep { color: var(--text-4); }
/* 已过截止的未处理情报 —— 全页唯一「现在就要动手」的信号，给它最强的颜色 */
.intel-sync .hot {
  margin-left: auto; display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: 999px; font-weight: 700;
  background: var(--danger-bg); color: var(--danger);
}

/* ---- 情报卡片 ---- */
.intel-card {
  display: flex; flex-direction: column;
  padding: 14px 16px; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.intel-card:hover { border-color: var(--brand-200); box-shadow: var(--sh-sm); transform: translateY(-1px); }
.intel-card .hd { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
.intel-card h4 {
  margin: 0 0 7px; font-size: 14.5px; font-weight: 700; line-height: 1.45; color: var(--text-1);
  /* 标题最多两行 —— 长短不一的招标标题否则会把同一排卡片撑成三种高度 */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.intel-card .owner { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-3); }
.intel-card .owner svg { flex: 0 0 auto; }
.intel-card .owner .rg { margin-left: 6px; padding: 1px 7px; border-radius: 999px; background: var(--surface-2); }
.intel-card .facts { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 9px; font-size: 12px; color: var(--text-3); }
.intel-card .facts b { color: var(--text-1); font-weight: 700; font-variant-numeric: tabular-nums; }
/* 金额未知（NULL）显示「待核实」—— 与「¥0」是两件事，颜色上也不该一样 */
.intel-card .facts b.trend-down { color: var(--danger); }
.intel-card .tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.intel-card .tg { padding: 1px 8px; border-radius: 999px; background: var(--surface-2); color: var(--text-3); font-size: 11px; }
/* 判断与行动建议：这条情报**为什么值得看**，是「情报」与「公告」的分界，单独给一块底 */
.intel-card .act {
  margin: 10px 0 0; padding: 8px 10px; border-radius: 8px;
  background: var(--surface-2); color: var(--text-2); font-size: 12px; line-height: 1.6;
}
.intel-card .ft { display: flex; align-items: center; gap: 8px; margin-top: auto; padding-top: 11px; }
.intel-card .ft .fs-11 { color: var(--success); font-weight: 600; }

/* ---- 分档 / 状态 / 侧别 三枚小签（默认中性，具体档位在下面覆盖） ---- */
.intel-tag, .intel-state, .intel-side {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 600;
  background: var(--surface-2); color: var(--text-3);
}
.intel-tag svg { flex: 0 0 auto; }
.intel-side { font-weight: 500; }
.intel-tag.b-bid    { background: var(--danger-bg);  color: var(--danger); }
.intel-tag.b-follow { background: var(--brand-bg);   color: var(--brand); }
.intel-tag.b-intel  { background: var(--info-bg);    color: var(--info); }
.intel-tag.b-eco    { background: var(--success-bg); color: var(--success); }
.intel-state.s-new       { background: var(--warning-bg); color: var(--warning); }
.intel-state.s-following { background: var(--brand-bg);   color: var(--brand); }
.intel-state.s-converted { background: var(--success-bg); color: var(--success); }
.intel-state.s-dropped   { background: var(--surface-2);  color: var(--text-4); }

/* ---- 详情抽屉：字段表 ---- */
.intel-facts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px;
  border: 1px solid var(--border-2); border-radius: 10px; overflow: hidden;
  background: var(--border-2);
}
.intel-facts .fr { display: flex; gap: 10px; padding: 9px 12px; background: var(--surface); font-size: 12.5px; }
/* 字段数是奇数时最后一项跨两列 —— 否则右下角会露出一小块网格底色 */
.intel-facts .fr:last-child:nth-child(odd) { grid-column: 1 / -1; }
.intel-facts .fr .k { flex: 0 0 68px; color: var(--text-4); }
.intel-facts .fr .v { flex: 1; min-width: 0; color: var(--text-1); word-break: break-word; }

/* ---- 详情抽屉：判断与行动建议 ---- */
.intel-action { margin-top: 14px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.intel-action .ah {
  display: flex; align-items: center; gap: 6px; padding: 8px 12px;
  background: var(--surface-2); color: var(--text-2); font-size: 12.5px; font-weight: 700;
}
.intel-action .ah svg { color: var(--brand); flex: 0 0 auto; }
.intel-action .ab { padding: 11px 13px; color: var(--text-1); font-size: 13px; line-height: 1.75; white-space: pre-wrap; }

.intel-note {
  display: flex; align-items: flex-start; gap: 5px; margin-top: 12px; padding: 8px 11px;
  border-radius: 8px; background: var(--surface-2); color: var(--text-3); font-size: 12px;
}
/* 转化弹窗顶部的说明 —— 用 warning 底是因为它讲的是「做了就回不去」的一件事 */
.intel-conv-hint {
  display: block; margin-bottom: 13px; padding: 8px 11px; border-radius: 8px;
  background: var(--warning-bg); color: var(--text-2); line-height: 1.6;
}


/* ==========================================================================
   法务中心：合同台账与审核留痕（第四十四轮）
   --------------------------------------------------------------------------
   命名两条纪律与上面的情报段完全一致，理由也一样：
     ① 顶层类名一律带 `legal-` 前缀；
     ② **状态与动作修饰一律用独立类名**（`.legal-st-pending`、`.legal-tl-reject`），
        不写复合选择器（`.legal-st.s-pending`）—— 本仓有三百多个带布局属性的全局裸类，
        复合写法只要撞上其中一个，就会额外吃下那条全局规则，
        症状是**整块错位而类型检查 / DOM 断言 / 四类审计全绿**
        （第三十二轮 `.md-btn.main` 被套上侧边栏左外边距的事故，`audit-style.mjs` 的 D 类守着这条）。
   ========================================================================== */

/* ---- 种类小标与审核状态签 ---- */
.legal-kind { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-3); }
.legal-st {
  display: inline-flex; align-items: center; white-space: nowrap;
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
}
.legal-st-draft    { background: var(--surface-2); color: var(--text-3); }
.legal-st-pending  { background: var(--warning-bg); color: var(--warning); }
.legal-st-approved { background: var(--success-bg); color: var(--success); }
.legal-st-rejected { background: var(--danger-bg); color: var(--danger); }
/* 存量豁免刻意做成「虚线框 + 灰字」而不是彩签：它的意思是「这份不进待办」，
   不是一种需要被注意的状态 —— 给彩签会让人以为它是待处理的 */
.legal-st-legacy { background: var(--surface-2); color: var(--text-3); border: 1px dashed var(--border-2); }

/* ---- 详情里的字段表（与 .intel-facts 同一套写法） ---- */
.legal-facts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px;
  border: 1px solid var(--border-2); border-radius: 10px; overflow: hidden;
  background: var(--border-2);
}
.legal-facts .fr { display: flex; gap: 10px; padding: 9px 12px; background: var(--surface); font-size: 12.5px; }
.legal-facts .fr:last-child:nth-child(odd) { grid-column: 1 / -1; }
.legal-facts .fr .k { flex: 0 0 82px; color: var(--text-4); }
.legal-facts .fr .v { flex: 1; min-width: 0; color: var(--text-1); word-break: break-word; }

/* ---- 审核意见（通过 / 驳回两色） ---- */
.legal-note {
  display: flex; align-items: flex-start; gap: 7px; margin-top: 14px;
  padding: 10px 12px; border-radius: 9px; font-size: 12.5px; line-height: 1.7;
}
.legal-note-bad  { background: var(--danger-bg); }
.legal-note-pass { background: var(--success-bg); }
.legal-note-h { font-weight: 600; margin-bottom: 2px; color: var(--text-1); }
.legal-note-b { color: var(--text-2); }

/* ---- 区块小标题 ---- */
.legal-sec-h { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); margin-bottom: 9px; }

/* ---- 审核留痕时间线：过程要看得见「第几轮、谁、什么意见」 ---- */
.legal-tl { position: relative; padding-left: 17px; }
.legal-tl::before {
  content: ''; position: absolute; left: 4px; top: 7px; bottom: 7px;
  width: 1px; background: var(--border-2);
}
.legal-tl-item { position: relative; padding-bottom: 12px; }
.legal-tl-item:last-child { padding-bottom: 0; }
.legal-tl-dot {
  position: absolute; left: -17px; top: 5px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--text-4); box-shadow: 0 0 0 3px var(--surface);
}
.legal-tl-submit  .legal-tl-dot { background: var(--brand); }
.legal-tl-approve .legal-tl-dot { background: var(--success); }
.legal-tl-reject  .legal-tl-dot { background: var(--danger); }
.legal-tl-hd { font-size: 12.5px; color: var(--text-1); }
.legal-tl-nt { margin-top: 3px; font-size: 12px; color: var(--text-2); line-height: 1.65; }

/* ---- 项目详情页的「合同」页签 ---- */
.ct-list { display: flex; flex-direction: column; gap: 10px; }
.ct-item { border: 1px solid var(--border-2); border-radius: 10px; padding: 12px 14px; background: var(--surface); }
.ct-item .ct-hd { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ct-item .ct-no { font-size: 11px; color: var(--text-3); font-family: var(--font-mono, monospace); }
.ct-item .ct-title { font-size: 13.5px; font-weight: 600; color: var(--text-1); margin: 5px 0 3px; }
.ct-item .ct-meta { font-size: 12px; color: var(--text-3); display: flex; gap: 14px; flex-wrap: wrap; }
.ct-item .ct-acts { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.ct-empty {
  padding: 12px 14px; border-radius: 9px; font-size: 12.5px; line-height: 1.7;
  background: var(--warning-bg); color: var(--text-2);
}


/* ==================== 数据导入向导（第四十五轮） ====================
   ⚠️ 语义色（`.t-ok` / `.t-bad` …）**只以复合选择器出现**（`.imp-chip.t-ok`），
      绝不写成裸的 `.t-ok { … }`。理由是仓库里踩过一次的坑：
      修饰类名撞上**全局裸类**会造成不报错的整块错位 ——
      `.md-btn.main` 撞 `.main{flex:1;margin-left:var(--sidebar-w)}`，
      按钮被挤出面板，而类型检查 / `node --check` / DOM 断言**全过**，
      只有真机量像素才看得见。`audit-style.mjs` 的 D 类守着这条。
   另外所有类名统一带 `imp-` 前缀，与既有 1300 行样式零重叠（加之前搜过一遍裸类）。 */

/* ---- 上传区 ---- */
.imp-file { display: none; }
.imp-drop {
  border: 1.5px dashed var(--border); border-radius: 10px;
  padding: 26px 20px; text-align: center; cursor: pointer;
  background: var(--surface-2, #f8fafc);
  transition: border-color .15s, background .15s;
}
.imp-drop:hover, .imp-drop.is-over { border-color: var(--brand); background: var(--brand-50, #eff6ff); }
.imp-drop-ic { color: var(--text-3); margin-bottom: 7px; }
.imp-drop-t { font-size: 14px; font-weight: 600; color: var(--text-1); }
.imp-drop-h { font-size: 12px; color: var(--text-3); margin-top: 6px; line-height: 1.7; }

/* ---- 选项 ---- */
.imp-opts { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 18px; }
.imp-opt .lbl { font-size: 12px; color: var(--text-3); margin-bottom: 7px; }
.imp-opt .val { font-size: 13px; font-weight: 600; }
.imp-opt .val.is-ok { color: var(--success, #059669); }
.imp-opt .val.is-warn { color: var(--warning, #d97706); }
.imp-opt .hint { font-size: 11.5px; color: var(--text-3); margin-top: 6px; line-height: 1.7; }
@media (max-width: 900px) { .imp-opts { grid-template-columns: 1fr; } }

.imp-busy { display: flex; align-items: center; gap: 8px; padding: 12px 0; font-size: 13px; color: var(--text-2); }
.imp-busy .spin { animation: imp-spin .9s linear infinite; }
@keyframes imp-spin { to { transform: rotate(360deg); } }

/* ---- 五类计数 ---- */
.imp-chips { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
.imp-chip {
  border: 1px solid var(--border); border-radius: 9px;
  padding: 11px 13px; background: var(--surface-2, #f8fafc);
}
.imp-chip .n { font-size: 21px; font-weight: 700; line-height: 1.15; letter-spacing: -.5px; }
.imp-chip .l { font-size: 12px; color: var(--text-3); margin-top: 3px; }
.imp-chip.t-ok .n { color: var(--success, #059669); }
.imp-chip.t-info .n { color: var(--brand); }
.imp-chip.t-mute .n { color: var(--text-3); }
.imp-chip.t-warn .n { color: var(--warning, #d97706); }
.imp-chip.t-bad .n { color: var(--danger, #dc2626); }
@media (max-width: 900px) { .imp-chips { grid-template-columns: repeat(2, 1fr); } }

.imp-meta {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 15px; font-size: 12px; color: var(--text-3);
}
.imp-file-tag { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; color: var(--text-2); }

/* ---- 提示条 ---- */
.imp-alert {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 11px 13px; margin-top: 14px; border-radius: 9px;
  font-size: 13px; line-height: 1.7;
}
.imp-alert > svg { flex: none; margin-top: 2px; }
.imp-alert.is-ok { background: var(--success-bg, #ecfdf5); color: #047857; }
.imp-alert.is-bad { background: var(--danger-bg, #fef2f2); color: #b91c1c; }
.imp-alert.is-warn { background: var(--warning-bg, #fffbeb); color: #b45309; }
.imp-alert.is-info { background: var(--brand-50, #eff6ff); color: #1d4ed8; }

/* ---- 分段与表格 ---- */
.imp-sec { margin-top: 20px; }
.imp-sec-h {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; color: var(--text-1); margin-bottom: 9px;
}
.imp-sec-h .sub { font-weight: 400; font-size: 12px; color: var(--text-3); }

.imp-table { border: 1px solid var(--border); border-radius: 9px; overflow: hidden; }
.imp-tr {
  display: grid; grid-template-columns: 92px 74px minmax(120px, 1fr) minmax(220px, 2fr);
  gap: 10px; padding: 9px 12px; font-size: 12.5px; align-items: start;
  border-top: 1px solid var(--border);
}
.imp-tr:first-child { border-top: 0; }
.imp-tr.imp-th {
  background: var(--surface-2, #f8fafc); font-weight: 600; font-size: 12px;
  color: var(--text-3);
}
.imp-tr .imp-c-no { color: var(--text-3); font-variant-numeric: tabular-nums; }
.imp-tr .imp-c-who { color: var(--text-1); font-weight: 500; }
.imp-tr .imp-c-msg { color: var(--text-2); line-height: 1.65; }
.imp-tr .imp-c-file { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.imp-tr .imp-c-k.t-ok { color: var(--success, #059669); font-weight: 600; }
.imp-tr .imp-c-k.t-info { color: var(--brand); font-weight: 600; }
.imp-tr .imp-c-k.t-warn { color: var(--warning, #d97706); font-weight: 600; }
.imp-tr .imp-c-k.t-bad { color: var(--danger, #dc2626); font-weight: 600; }

/* 批次台账的表头不一样（6 列），单独一份栅格 */
.imp-batches .imp-tr { grid-template-columns: 220px 68px minmax(120px, 1fr) minmax(180px, 1.2fr) minmax(130px, 1fr) 96px; }
.imp-batches .imp-tr .tag { margin-left: 6px; }

/* ---- 正常样本 ---- */
.imp-samples { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 10px; }
.imp-sample { border: 1px solid var(--border); border-radius: 9px; padding: 10px 12px; background: var(--surface-2, #f8fafc); }
.imp-sample .hd { display: flex; align-items: center; gap: 7px; font-size: 13px; }
.imp-sample .bd { margin-top: 7px; display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12px; color: var(--text-2); }
.imp-sample .imp-kv i { font-style: normal; color: var(--text-3); margin-right: 3px; }

.imp-badge {
  display: inline-block; padding: 1px 7px; border-radius: 5px;
  font-size: 11px; font-weight: 600;
  background: var(--surface-2, #f1f5f9); color: var(--text-2);
}
.imp-badge.t-ok { background: var(--success-bg, #ecfdf5); color: #047857; }
.imp-badge.t-info { background: var(--brand-50, #eff6ff); color: #1d4ed8; }
.imp-badge.t-mute { background: var(--surface-2, #f1f5f9); color: var(--text-3); }
.imp-badge.t-warn { background: var(--warning-bg, #fffbeb); color: #b45309; }
.imp-badge.t-bad { background: var(--danger-bg, #fef2f2); color: #b91c1c; }

/* ---- 底部动作 ---- */
.imp-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--border);
}
.imp-blocked { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-3); }

.imp-pager {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  margin-top: 14px;
}

/* 打印时不留悬浮物 */
@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; }
}
