/* ==================================================================
   派单 · 控制台设计系统
   ------------------------------------------------------------------
   零 CDN、零字体文件，完全离线可用。
   设计要点（对应上一版的问题）：
     · 导航从「挤在顶栏的一排文字按钮」改为左侧边栏，主内容区完整
     · 建立真正的字号／字重层级，不再全挤在 13–14px
     · 用分层阴影 + 极淡描边造深度，不再是清一色 1px 灰框
     · 数字统一等宽（tabular-nums），金额可竖向对齐比对
   ================================================================== */

:root {
  /*
   * 声明这套 UI 只按浅色方案渲染。
   *
   * 不写的话，浏览器原生控件（select 的弹出菜单、日期选择器、滚动条）
   * 会跟随**系统**的深色模式渲染 —— 页面是浅的、弹层是黑的，半透明底上
   * 灰字叠灰字，就是截图里那个看不清的下拉菜单。声明 light 后原生层也走浅色。
   */
  color-scheme: light;

  /* —— 中性色 —— */
  --ink:        #111827;
  --navy:       #1f2937;
  --slate-900:  #1f2937;
  --slate-700:  #374151;
  --slate-500:  #6b7280;
  --slate-400:  #9ca3af;
  --slate-300:  #d1d5db;
  --line:       #e8ebef;
  --line-soft:  #f1f3f6;
  --canvas:     #f7f8fa;
  --subtle:     #fafbfc;
  --white:      #ffffff;

  /*
   * —— 品牌色：取自新 Logo 的亮蓝 ——
   *
   * 旧的一套是偏青的 #0b6e9e，跟新图标（亮蓝渐变）放一起会显脏 ——
   * 图标亮、文字和按钮发青，像是两个产品拼的。
   * 这一版直接按设计稿的色阶来，主色 #0b6cf0 跟图标同一个蓝。
   */
  --brand-50:   #eef5ff;
  --brand-100:  #d9e8ff;
  --brand-200:  #c7dcff;
  --brand-400:  #3ba5ff;
  --brand:      #0b6cf0;
  --brand-700:  #0959cc;
  --brand-900:  #0a4fb4;
  /** 品牌渐变，用在登录页品牌区这种需要「亮一下」的地方 */
  --brand-grad: linear-gradient(135deg, #3ba5ff 0%, #0b6cf0 55%, #0a54d2 100%);

  /* —— 语义色：主要染文字，不铺重底 —— */
  --ok:         #067a53;
  --ok-bg:      #f1faf6;
  --ok-line:    #cbe9dc;
  --warn:       #a45a08;
  --warn-bg:    #fdf8f0;
  --warn-line:  #f0dcc0;
  --bad:        #b4243c;
  --bad-bg:     #fdf3f4;
  --bad-line:   #f0ccd2;

  /*
   * 圆角只有三档，全站不混用（Vercel Geist 的做法）。
   *
   * 原来是 5 档，相邻只差 2px —— 肉眼分辨不出，等于每次都要重新决定用哪个，
   * 最后各处随机取值，页面看起来就没有统一的性格。
   * 收成三档后每档职责明确：控件 / 浮层 / 大块面。
   * xs、xl 保留为别名，免得遗漏的引用失效。
   */
  --r-ctl:  8px;    /* 按钮、输入框、标签（设计稿定的 8，比原来的 6 圆一点） */
  --r-flt:  12px;   /* 卡片、菜单、弹层 */
  --r-blk:  16px;   /* 模态框、整块面 */
  --r-xs: var(--r-ctl);
  --r-sm: var(--r-ctl);
  --r:    var(--r-flt);
  --r-lg: var(--r-flt);
  --r-xl: var(--r-blk);

  /* ==================================================================
     字体阶梯
     ------------------------------------------------------------------
     之前有 14 个字号：8.5 / 9 / 10.5 / 11 / 11.5 / 12 / 12.5 / 13 /
     13.5 / 14 / 15 / 17 / 18 / 25。

     问题不是「多」，而是**相邻档只差 0.5px** —— 11.5 和 12 在正常
     视距下根本无法分辨，等于这层区分白做了，还让每次改字号都要
     重新决定用哪个。层级失效就是这么来的。

     收成六档，相邻至少差 1px 且多数差 2px 以上，每档都有明确职责：

       display  数字与标题的主视觉（指标值、大数字）
       title    页面 / 区块标题
       subtitle 卡片标题、强调正文
       body     正文（用得最多）
       small    辅助说明、次要元数据
       micro    标签、注脚、徽标

     遵循文档要求的三类角色：Display / UI-Body / Mono-Data ——
     等宽那一类走 --font-mono，字号共用本阶梯。
     ================================================================== */
  /*
   * 字号阶梯按「可读性红线」重排过：
   *   主文本不低于 14px（后台高密虽可例外，但这类平台到处是金额、
   *   时限、账号，13px 在长时间核对下确实费眼）；
   *   任何一级都不低于 12px —— 原来 micro 是 11px，用在徽标和注脚上，
   *   而那些位置恰恰常写状态，属于「关键信息靠小字传达」的典型反模式。
   *
   * 各档整体上调 1px，保留原有层级关系，不至于让布局失控。
   */
  /*
   * 字号整体上调：正文 20px，小字 18px。
   *
   * 原来正文 14、小字 12~13，在运营台上偏挤 —— 一屏要扫几十条任务、
   * 核验记录、金额，字小了容易看串行。现在最小是 18，
   * 正常内容 20~24，标题 30。
   */
  /*
   * 倒计时专用，比 display 还大一档。
   *
   * 它是全屏唯一在变、且变了就无法挽回的东西（名额超时就被释放），
   * 余光要能扫到，所以字号单独开一档，不复用 display ——
   * 后者是标题，跟它抢同一档会让"谁更重要"变模糊。
   */
  /*
   * 阶梯要有层次：正文 20 保持不动，但下面必须真有"小字"。
   *
   * 之前 small 和 micro 都是 18 —— 等于没有小字档，结果注释、标签、
   * 时间戳、表格里的辅助信息全跟正文一样大，一眼看过去全是重点，
   * 也就没有重点了。现在往下开出 16 / 14 / 13 三档：
   *   small(16) 次要信息，micro(14) 注释与时间戳，nano(13) 角标与单位。
   * 13px 只给"看一眼就过"的东西（计量单位、角标），正文不碰。
   */
  /*
   * 字号按"是不是重点"分，不是全都放大。
   *
   * 大字只给两处：**标题**（层级要看得出）和**视觉强调**
   * （倒计时 40、指标数字）—— 这些是余光就该扫到的。
   * 正文与辅助信息一律收回到常规区间：全都放大之后，
   * 一屏里全是重点，反而看不出哪个是重点。
   */
  --fs-count:    40px;   /* 视觉强调：交付倒计时 */
  --fs-display:  28px;   /* 页面大标题 */
  --fs-title:    24px;   /* 卡片 / 区块标题 */
  --fs-subtitle: 18px;   /* 副标题 */
  --fs-body:     18px;   /* 正文 */
  --fs-small:    16px;   /* 次要信息 */
  --fs-micro:    14px;   /* 注释、标签、时间戳、表格 */
  --fs-nano:     13px;   /* 纯辅助计数 */

  --lh-tight: 1.25;   /* 标题、大数字 */
  --lh-snug:  1.45;   /* 卡片内短文本 */
  --lh-body:  1.6;    /* 正文、说明 */

  /*
   * 标题用负字距（Geist：字号越大，字间距越紧）。
   * 中文标题按默认字距排会显得松散、发飘；收紧一点立刻有重量感。
   * 只给标题和大数字，正文不变 —— 正文收紧会伤可读性。
   */
  --ls-title: -0.015em;

  --font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
             'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ==================================================================
     间距阶梯（4px 基准）
     ------------------------------------------------------------------
     原来是 3/4/5/7/8/9/10/11/12/14/15/40 —— 没有规律，改动时只能靠感觉。
     四的倍数能让任意两块对齐，也让「这里该留多少」有唯一答案。
     ================================================================== */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  /* 触控目标最小边长（WCAG 2.5.8） */
  --tap: 44px;

  /*
   * 阴影只给「浮起来的东西」用（弹窗、浮层、吐司）。
   * 常驻卡片一律 1px 描边 —— 满屏投影会让界面显得脏。
   */
  --sh-xs: none;
  --sh-sm: none;
  --sh:    none;
  --sh-lg: 0 4px 12px rgba(17, 24, 39, .08), 0 12px 32px -8px rgba(17, 24, 39, .12);
  --sh-xl: 0 8px 24px rgba(17, 24, 39, .1), 0 24px 56px -16px rgba(17, 24, 39, .18);

  --sidebar-w: 216px;
  --header-h: 54px;

  --ease: cubic-bezier(.4, 0, .2, 1);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body {
  background: var(--canvas);
  color: var(--slate-900);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* 数字一律等宽，金额才能上下对齐着看 */
.mono, .num, table.tbl td.right, .metric .value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.mono {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

h1, h2, h3, h4 { margin: 0; color: var(--ink); line-height: 1.3; }
/*
 * 标题字号走阶梯，不再硬编码（原来 24/17/15 都不在六档里）。
 * 负字距保留：中文标题按默认字距排会发飘，字号越大收得越紧（Geist）。
 */
h1 { font-size: var(--fs-display); font-weight: 650; letter-spacing: -.02em; }
h2 { font-size: var(--fs-title); font-weight: 600; letter-spacing: -.012em; }
h3 { font-size: var(--fs-subtitle); font-weight: 600; letter-spacing: var(--ls-title); }
p  { margin: 0; }

a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--brand-700); text-decoration: underline; text-underline-offset: 2px; }

::selection { background: var(--brand-100); }

/* 统一滚动条，避免默认样式打断整体质感 */
* { scrollbar-width: thin; scrollbar-color: var(--slate-300) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--slate-300); border-radius: 99px;
  border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--slate-400); background-clip: content-box; }

.icon { flex-shrink: 0; display: block; }

/* ==================================================================
   应用骨架：侧边栏 + 主区
   ================================================================== */

.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  min-height: 100vh;
}

/* 侧边栏用浅色：整页只有一种明度基调，比深色块更安静 */
.sidebar {
  position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column;
  background: var(--subtle);
  border-right: 1px solid var(--line);
  z-index: 30;
  /*
   * 字号整体调大后，菜单文字需要的宽度也跟着涨。
   * 侧栏是内容撑宽的，不设 flex-shrink 的话会被右侧主区挤扁，
   * 菜单项上的 nowrap + 省略号就会真的开始截字。
   */
  flex-shrink: 0;
}

.sb-brand {
  display: flex; align-items: center; gap: 8px;
  height: var(--header-h); padding: 0 14px;
  border-bottom: 1px solid var(--line);
}
.brand-mark { display: block; flex-shrink: 0; line-height: 0; }
.sb-brand-text { min-width: 0; }
.sb-brand-name {
  font-size: var(--fs-small); font-weight: 650; color: var(--ink);
  white-space: nowrap; letter-spacing: .01em;
}
/* 字母间距拉开的小写标签，是工具类产品 logo 旁最常见的处理 */
.sb-brand-sub {
  font-size: var(--fs-micro); font-weight: 600; color: var(--slate-400);
  letter-spacing: .16em; margin-top: 1px; white-space: nowrap;
}

.sb-section {
  padding: 16px 12px 4px;
  font-size: var(--fs-micro); font-weight: 500; color: var(--slate-400);
}

.sb-nav { flex: 1; padding: 4px 8px; overflow-y: auto; }
.sb-nav::-webkit-scrollbar { width: 0; }

.sb-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 12px; margin-bottom: 1px;
  border: 0; border-radius: var(--r-sm);
  background: transparent; color: var(--slate-700);
  font: inherit; font-size: var(--fs-small); text-align: left;
  cursor: pointer; transition: background .13s var(--ease), color .13s var(--ease);
}
.sb-item:hover { background: rgba(17, 24, 39, .045); color: var(--ink); }
/* 选中态：白底 + 描边，像一张「浮到前面」的标签，不用彩色块 */
.sb-item.active {
  background: var(--white); color: var(--brand); font-weight: 550;
  box-shadow: inset 0 0 0 1px var(--line);
}
.sb-item .icon { color: var(--slate-400); }
.sb-item:hover .icon { color: var(--slate-500); }
.sb-item.active .icon { color: var(--brand); }
.sb-item-badge {
  /*
   * 原来是 18px 高的小圆点配 12px 字。字号整体上调后它也变成 18px，
   * 字高和圆高一样就没了留白，数字会顶到边上——所以尺寸跟着放到 22px。
   */
  margin-left: auto; min-width: 24px; height: 24px; padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 99px; font-size: var(--fs-micro); font-weight: 600;
  background: var(--bad); color: #fff;
  font-variant-numeric: tabular-nums;
}

.sb-foot {
  padding: 8px; border-top: 1px solid var(--line);
}
.sb-user {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px; border: 0; border-radius: var(--r-sm);
  background: transparent;
  font: inherit; text-align: left; color: inherit;
  transition: background .13s var(--ease);
}
.sb-user.switchable { cursor: pointer; }
.sb-user.switchable:hover { background: rgba(17, 24, 39, .045); }
.sb-avatar {
  width: 26px; height: 26px; border-radius: 6px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-micro); font-weight: 600;
  background: var(--brand-50); color: var(--brand);
}
/* 三个角色不再各用一种颜色 —— 身份靠文字说明就够了 */
.sb-avatar.c1, .sb-avatar.c2, .sb-avatar.c3 {
  background: var(--brand-50); color: var(--brand);
}
.sb-user-info { min-width: 0; flex: 1; display: block; }
.sb-user-name {
  display: block; font-size: var(--fs-micro); font-weight: 550; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-user-sub {
  display: block; font-size: var(--fs-micro); color: var(--slate-500);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-user-caret { color: var(--slate-400); flex-shrink: 0; }

/* ==================================================================
   浮层菜单（身份切换器）
   ================================================================== */

.pop-layer { position: fixed; inset: 0; z-index: 150; }
.pop {
  position: fixed;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--sh-xl);
  padding: 8px;
  animation: popUp .16s var(--ease);
}
@keyframes popUp {
  from { opacity: 0; transform: translateY(6px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

.pop-head { padding: 8px 12px 12px; }
.pop-head-t {
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--slate-400);
}
.pop-head-v {
  font-size: var(--fs-small); font-weight: 600; color: var(--ink); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pop-head-v .muted { font-weight: 400; font-size: var(--fs-micro); }

.pop-label {
  padding: 8px 12px 4px;
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--slate-400);
  border-top: 1px solid var(--line-soft);
}
.pop-list { display: flex; flex-direction: column; gap: 1px; }

.pop-item {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 8px 12px; border: 0; border-radius: var(--r-sm);
  background: transparent; font: inherit; text-align: left; color: var(--slate-900);
  cursor: pointer; transition: background .13s var(--ease);
}
.pop-item:hover { background: var(--canvas); }
.pop-item.active { background: var(--brand-50); }
.pop-item.danger { color: var(--bad); }
.pop-item.danger:hover { background: var(--bad-bg); }
.pop-ico {
  width: 28px; height: 28px; flex-shrink: 0; border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-50); color: var(--brand);
}
.pop-ico.c1, .pop-ico.c2, .pop-ico.c3 {
  background: var(--brand-50); color: var(--brand);
}
.pop-ico.plain { background: var(--line-soft); color: var(--bad); }
.pop-main { flex: 1; min-width: 0; }
.pop-name { display: block; font-size: var(--fs-small); font-weight: 570; }
.pop-sub {
  display: block; font-size: var(--fs-micro); color: var(--slate-500); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pop-check { color: var(--brand); flex-shrink: 0; }
.pop-sep { height: 1px; background: var(--line-soft); margin: 5px 0; }

/* —— 主区 —— */
.main-col { display: flex; flex-direction: column; min-width: 0; }

.topbar {
  position: sticky; top: 0; z-index: 20;
  height: var(--header-h);
  display: flex; align-items: center; gap: 12px;
  padding: 0 22px;
  background: var(--white);
  border-bottom: 1px solid var(--line);
}
/*
 * 标题不许换行。
 * 不限制的话，窄屏上「内容与挂单」会被挤成竖排的四个字 —— 顶栏只有
 * 54px 高，竖排标题会撑破布局，看起来像页面坏了。
 */
.topbar-title {
  font-size: var(--fs-subtitle); font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.topbar-right {
  margin-left: auto; display: flex; align-items: center;
  gap: var(--sp-1); flex-shrink: 0;
}

/* 资金摘要：无边框无阴影，靠淡底分组 */
.money-chip {
  display: flex; align-items: baseline; gap: 8px;
  padding: 4px 12px; border-radius: var(--r-sm);
  background: var(--canvas);
}
.money-chip .icon { color: var(--slate-400); align-self: center; }
.money-chip-k { font-size: var(--fs-micro); color: var(--slate-500); }
/*
 * 数值原来是 12px —— 和它的标签一样大。
 *
 * 「可兑换 14,500 分」是接单人打开平台第一眼要找的东西，
 * 却和「可兑换」三个字同样大小，等于没给它任何视觉权重。
 * 提到 subtitle(16) 并加粗，标签保持 12px 辅助位，主次才成立。
 */
.money-chip-v {
  font-size: var(--fs-subtitle); font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.money-chip.ok  .money-chip-v { color: var(--ok); }
.money-chip.warn .money-chip-v { color: var(--warn); }

.content { flex: 1; padding: 24px; max-width: 1520px; width: 100%; }

/* ==================================================================
   响应式
   ------------------------------------------------------------------
   断点统一到四档，每一档对应一个**布局形态变化**，而不是「字号小一点」：

     1280  两栏塌成一栏
     1024  侧边栏收成图标条
      768  卡片网格全部单列（平板）
      640  手机：顶栏精简、触控目标放大

   之前是 560 / 620 / 860 / 980 / 1080 / 1180 / 1280 七套互不相关的
   数字，改一处要同时确认另外六处会不会打架。四档各管一件事，
   每档的职责写在注释里，加规则时知道该放哪。
   ================================================================== */

/* 1024：侧边栏收成图标条，正文让出宽度 */
@media (max-width: 1024px) {
  :root { --sidebar-w: 56px; }
  .sb-brand-text, .sb-item span:not(.sb-item-badge), .sb-section,
  .sb-user-info, .sb-brand-sub, .sb-user-caret { display: none; }
  .sb-brand, .sb-item, .sb-user { justify-content: center; }
  .sb-item { padding: var(--sp-2); }
  .content, .topbar { padding-left: var(--sp-3); padding-right: var(--sp-3); }
}

/*
 * 640：手机。
 *
 * 顶栏在 390px 上原本会溢出：「可用 ¥11202.50」被挤成竖排的「可/用」，
 * 数字也折行。根因是顶栏右侧几个 chip 谁都不肯让，最后一起被压扁。
 *
 * 处理办法是**按重要性取舍**，而不是把字号调小：
 *   - 保留「可用余额」—— 客户最关心它，且它决定能不能挂单；
 *   - 收起「冻结」—— 它是可用余额的派生信息，预算总览页有完整拆解；
 *   - 充值按钮只留加号 —— 图标已经足够表意，文字在这里是奢侈的。
 *
 * 这是「重新设计而不是缩小」：同样的信息，手机上只留当下最需要的那条。
 */
@media (max-width: 640px) {
  .topbar { gap: var(--sp-2); }
  /* 冻结金额让位：可用余额是决策依据，冻结只是它的注脚 */
  .topbar-right .money-chip.warn { display: none; }
  .money-chip { padding: 4pxvar(--sp-2); }
  .money-chip-k { white-space: nowrap; }
  /* 充值收成图标按钮，文字在手机上挤不下 */
  .topbar-right .btn-label { display: none; }
  .topbar-right .btn { min-width: var(--tap); justify-content: center; }

  /* 触控目标放大到可点：手机上 24px 高的按钮点不准 */
  .btn.xs, .btn.sm { height: 32px; padding: 0 var(--sp-2); }
  .sb-item { min-height: var(--tap); }

  /* 卡片内边距收紧，把宽度还给内容 */
  .card-pad { padding: var(--sp-3); }
  .card-head { padding: var(--sp-3); }
  /* 弹窗不能超出屏幕 */
  .modal { width: calc(100vw - var(--sp-5)); max-width: 100%; }
}

/* ==================================================================
   页头
   ================================================================== */

/* ==================================================================
   卡片：只有 1px 描边，不投影
   ================================================================== */

.card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.card-pad { padding: 16px 24px; }

.card-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 24px;
  border-bottom: 1px solid var(--line-soft);
}
.card-head h3 { font-size: var(--fs-small); font-weight: 600; }
.card-head .spacer { margin-left: auto; }
.card-head > .icon { color: var(--slate-400); }
.card-note { font-size: var(--fs-micro); color: var(--slate-500); }

/* ==================================================================
   栅格
   ================================================================== */

.grid { display: grid; gap: 16px; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g-side { grid-template-columns: minmax(0, 1fr) 372px; }
.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.row-tight { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.spacer { margin-left: auto; }

/* 1280：两栏塌成一栏。顺序要重排 —— 见下面 .g-side 的 order 说明 */
@media (max-width: 1280px) {
  .g-side { grid-template-columns: minmax(0, 1fr); }
  /*
   * 窄屏下把「输入」提到「动作」前面。
   *
   * 宽屏是左动作右输入（平台列表在左、内容选择器在右），因为一屏能同时看到。
   * 塌成一栏后如果还按 DOM 顺序，用户要先滚过七个平台行才够得着选内容 ——
   * 而内容是挂单的前提，没选内容时下面的平台列表根本没有意义。
   * 这是「重新设计」而不是「缩小」：同样的元素，窄屏下正确的顺序是反的。
   */
  .g-side > .order-first-narrow { order: -1; }
}
/* 1024：四列指标收成两列 */
@media (max-width: 1024px) { .g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/*
 * 768：平板。
 *
 * 文档特别提醒「不要简单认为 Tablet = 大号 Mobile」。
 * 原来这一档把 g2/g3/g4 全部拍成单列，就是那个毛病 ——
 * 768 减掉侧边栏和内边距还有约 690px，放两列指标绰绰有余，
 * 拍成单列只是白白浪费一半屏幕，还让页面变得又长又稀。
 *
 * 列数的选择要保证**每一行都填满**：
 *   g4（四个指标）→ 2 列，正好两行
 *   g3（三个指标）→ 3 列，正好一行（每格约 230px，放一个数字足够）
 *
 * g3 不能收成 2 列：三个格子排两列会空出右下角，而数据带的分隔线
 * 是靠「缝隙透底色」实现的 —— 空格子会露出一块底色，看着像页面坏了。
 */
@media (max-width: 768px) {
  .g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .g2 { grid-template-columns: minmax(0, 1fr); }
}

/* 640：手机才真正拍成单列 */
@media (max-width: 640px) {
  .g2, .g3, .g4 { grid-template-columns: minmax(0, 1fr); }
}

/* ==================================================================
   指标卡：数字是唯一的主角
   ================================================================== */

.metric {
  background: linear-gradient(180deg, var(--white) 0%, #fcfdfe 100%);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 16px;
}

/*
 * 数据带：把一组指标收成**一个**块，用竖分隔线切分。
 *
 * 起因是运营总览有七张一模一样的指标卡。文档里那条判据在这里成立：
 * 「大量尺寸完全一样的 Card → 说明层级已经失效」。七张卡就是七个
 * 视觉中心，等于没有中心；读者不知道该先看哪个，只能从头扫到尾。
 *
 * 收成一条之后：
 *   - 它在页面上是一个块，与「交付漏斗」「验收通过率」形成两级层次；
 *   - 去掉六条边框和六次圆角，噪声少了一半；
 *   - 分隔线比留白更紧凑，同样的信息占更少高度。
 *
 * 这不是「把卡片连起来」，而是重新判断：这些数字是**同一类事物的
 * 并列读数**，本来就该被读成一组，而不是七个独立对象。
 * 按文档的标准 —— 去掉 border/background/radius 后信息关系依然成立，
 * 说明它们本来就不需要各自的卡片。
 */
.strip {
  display: grid;
  /*
   * 用「缝隙透底色」做分隔线，而不是给子项加 border-left。
   *
   * border-left 只在单行时成立：一旦折成两行，第二行第一个会多出一条
   * 左线、行与行之间又没有横线，看着像没对齐。而缝隙技法是让 1px 的
   * grid gap 透出容器底色 —— 无论排成几行几列，分隔线都自动出现在
   * 该出现的位置，不需要为每种列数写一遍例外。
   */
  gap: 1px;
  background: var(--line-soft);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
/* 带内指标不再自成一卡，只负责填满自己的格子 */
.strip > .metric {
  background: var(--white);
  border: 0;
  border-radius: 0;
}
.metric-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
}
.metric .label {
  font-size: var(--fs-micro); color: var(--slate-500);
  padding-top: 4px;
}
/*
 * 图标独立成块放右上角，淡底 + 同色描边图标。
 * 默认中性灰，有语义时才染色 —— 一屏四张卡不该有四种颜色在抢注意力。
 */
.metric-ico {
  width: 28px; height: 28px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm);
  background: var(--line-soft); color: var(--slate-400);
}
.metric.accent .metric-ico { background: var(--brand-50);  color: var(--brand); }
.metric.good   .metric-ico { background: var(--ok-bg);     color: var(--ok); }
.metric.warn   .metric-ico { background: var(--warn-bg);   color: var(--warn); }
.metric.bad    .metric-ico { background: var(--bad-bg);    color: var(--bad); }

.metric .value {
  font-size: var(--fs-title); font-weight: 620; color: var(--ink);
  letter-spacing: -.025em; line-height: 1.2; margin-top: 10px;
}
.metric .value.sm { font-size: var(--fs-body); letter-spacing: -.015em; }
.metric .hint { font-size: var(--fs-micro); color: var(--slate-400); margin-top: 6px; line-height: 1.5; }

/* 语义只染数值 —— 不加顶部彩条，也不做 hover 位移 */
.metric.good .value { color: var(--ok); }
.metric.warn .value { color: var(--warn); }
.metric.bad  .value { color: var(--bad); }

/* ==================================================================
   徽标：胶囊形淡底，语义色带状态点
   ================================================================== */

.badge {
  display: inline-flex; align-items: center; gap: 4px;
  height: 26px; padding: 0 9px;
  border-radius: 99px;
  font-size: var(--fs-micro); font-weight: 500; white-space: nowrap;
  background: var(--line-soft); color: var(--slate-700);
}
/* 平台徽章里的官方 logo：小而圆角，加载失败会被 JS 摘掉，不留破图 */
.badge .pf-logo {
  width: 14px; height: 14px; border-radius: 3px;
  object-fit: cover; flex-shrink: 0;
}
/*
 * 语义徽标前置一个同色小圆点。
 * 好处是即使并排放三四个不同状态，也能靠圆点先分组、再读文字，
 * 比纯色块更快识别 —— 表格里一列状态时差别尤其明显。
 */
.badge.blue::before, .badge.green::before,
.badge.amber::before, .badge.rose::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.badge.blue  { background: var(--brand-50); color: var(--brand); }
.badge.green { background: var(--ok-bg);   color: var(--ok); }
.badge.amber { background: var(--warn-bg); color: var(--warn); }
.badge.rose  { background: var(--bad-bg);  color: var(--bad); }
.badge.gray  { background: var(--line-soft); color: var(--slate-500); }
.badge.solid { background: var(--brand); color: #fff; }

/*
 * 转圈：表示「机器正在跑，等结果」。
 *
 * 只用在**真的在等**的状态上（比如预检）。静态状态配转圈会让人
 * 以为卡住了 —— 那种情况宁可写「待审核」三个字。
 */
.spinner {
  display: inline-block;
  width: 12px; height: 12px;
  border: 2px solid rgba(11, 108, 240, .25);
  border-top-color: var(--brand);
  border-radius: 50%;
  animation: spin .8s linear infinite;
}
.badge .spinner { margin-right: 5px; }
@keyframes spin { to { transform: rotate(360deg); } }

/*
 * 平台徽标配色。
 *
 * 全部取自各平台自己的品牌主色 —— 用用户已经认识的颜色，
 * 他不需要「学习」这套配色就能认出来。
 *
 * 对比度：都用白字，主色本身是深色的直接用，偏浅的（B站粉）
 * 加深到能压住白字（4.5:1 的门槛不能靠肉眼猜）。
 * 没匹配到平台时不加色，退回 .badge.solid 的品牌蓝。
 */
/*
 * 平台品牌色：徽标和内容页的平台链节点**共用同一份**。
 *
 * 之前在平台链上用的是通用的蓝色，而任务列表里的抖音徽标是品牌黑 ——
 * 同一个平台在两处是两个颜色，扫一眼会以为是两种东西。
 * 现在色值只写一遍，两处引用同一组选择器。
 */
.badge.pf-douyin, .chain-node.pf-douyin             { background: #161823; color: #fff; }
.badge.pf-xiaohongshu, .chain-node.pf-xiaohongshu   { background: #ff2442; color: #fff; }
.badge.pf-zhihu, .chain-node.pf-zhihu               { background: #0084ff; color: #fff; }
.badge.pf-bilibili, .chain-node.pf-bilibili         { background: #d84b7a; color: #fff; }
.badge.pf-weibo, .chain-node.pf-weibo               { background: #e6162d; color: #fff; }
.badge.pf-quora, .chain-node.pf-quora               { background: #b92b27; color: #fff; }
.badge.pf-reddit, .chain-node.pf-reddit             { background: #d93a00; color: #fff; }
.badge.pf-smzdm, .chain-node.pf-smzdm               { background: #c81c1c; color: #fff; }
.badge.pf-wechat, .chain-node.pf-wechat             { background: #07a854; color: #fff; }
.badge.pf-toutiao, .chain-node.pf-toutiao           { background: #d9363a; color: #fff; }

/* 状态点：去掉光晕，纯色小圆点足够 */
.dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.dot.blue  { background: var(--brand); }
.dot.green { background: var(--ok); }
.dot.amber { background: #c07d16; }
.dot.rose  { background: var(--bad); }
.dot.gray  { background: var(--slate-400); }

/* ==================================================================
   按钮：纯色，无渐变、无投影
   ================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  height: 32px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--white); color: var(--slate-900);
  font: inherit; font-size: var(--fs-small); font-weight: 500;
  white-space: nowrap; cursor: pointer;
  transition: background .13s var(--ease), border-color .13s var(--ease);
}
.btn:hover:not(:disabled) { background: var(--subtle); border-color: var(--slate-300); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

.btn.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn.primary:hover:not(:disabled) { background: var(--brand-700); border-color: var(--brand-700); }
.btn.success { background: var(--ok); border-color: var(--ok); color: #fff; }
.btn.success:hover:not(:disabled) { background: #05633f; border-color: #05633f; }
.btn.danger { background: var(--white); border-color: var(--line); color: var(--bad); }
.btn.danger:hover:not(:disabled) { background: var(--bad-bg); border-color: var(--bad-line); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--brand); }
.btn.ghost:hover:not(:disabled) { background: var(--brand-50); border-color: transparent; }

.btn.sm { height: 28px; padding: 0 10px; font-size: var(--fs-micro); }
.btn.xs { height: 24px; padding: 0 8px; font-size: var(--fs-micro); gap: 4px; }
.btn.lg { height: 38px; padding: 0 18px; font-size: var(--fs-small); }
.btn.block { width: 100%; }

/* ==================================================================
   表单
   ================================================================== */

label.field { display: block; margin-bottom: 14px; }
label.field > span {
  display: block; margin-bottom: 5px;
  font-size: var(--fs-micro); font-weight: 500; color: var(--slate-700);
}
label.field > span .req { color: var(--bad); margin-left: 1px; }

input[type="text"], input[type="number"], input[type="email"],
input[type="password"], input[type="datetime-local"], input[type="tel"], select, textarea {
  width: 100%; height: 34px; padding: 0 11px;
  font: inherit; font-size: var(--fs-small); color: var(--ink);
  background: var(--white);
  border: 1px solid var(--slate-300); border-radius: var(--r-sm);
  transition: border-color .13s var(--ease), box-shadow .13s var(--ease);
}
textarea { height: auto; min-height: 86px; padding: 8px 12px; line-height: 1.65; resize: vertical; }
select { appearance: none; padding-right: 32px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center; background-size: 14px;
}
input::placeholder, textarea::placeholder { color: var(--slate-400); }
input:hover, select:hover, textarea:hover { border-color: var(--slate-400); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(11, 110, 158, .1);
}
.field-hint { font-size: var(--fs-micro); color: var(--slate-500); margin-top: 4px; line-height: 1.5; }

/*
 * 文件选择框（「参考视频」这类）跟文本框用同一套外观。
 *
 * 原生 input[type=file] 不在上面那条统一样式里，长的是浏览器默认样子 ——
 * 高度、边框、内边距全对不上，跟标题框并排摆着像两个不同世界的控件。
 * 这里补齐：同高同宽同边框，里面的「选择文件」按钮也跟着框走。
 */
input[type="file"] {
  width: 100%; height: 34px; padding: 0 11px;
  font: inherit; font-size: var(--fs-small); color: var(--slate-700);
  background: var(--white);
  border: 1px solid var(--slate-300); border-radius: var(--r-sm);
  cursor: pointer;
}
input[type="file"]::file-selector-button {
  height: 24px; margin: 0 10px 0 0; padding: 0 10px;
  font: inherit; font-size: var(--fs-micro); color: var(--ink);
  background: var(--subtle);
  border: 1px solid var(--slate-300); border-radius: var(--r-xs);
  cursor: pointer;
}
input[type="file"]:hover { border-color: var(--slate-400); }
input[type="file"]:hover::file-selector-button { background: var(--line-soft); }
input[type="file"]:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(11, 110, 158, .1); }

/* ==================================================================
   表格：只留横线，表头不加底色
   ================================================================== */

.table-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-small); }
table.tbl th {
  position: sticky; top: 0; z-index: 1;
  padding: 8px 16px; text-align: left; white-space: nowrap;
  font-size: var(--fs-micro); font-weight: 500; color: var(--slate-500);
  background: var(--white);
  border-bottom: 1px solid var(--line);
}
table.tbl td {
  padding: 12px 16px; vertical-align: middle;
  color: var(--slate-700);
  border-bottom: 1px solid var(--line-soft);
}
table.tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl tbody tr { transition: background .1s var(--ease); }
table.tbl tbody tr:hover { background: var(--subtle); }
table.tbl td.strong { color: var(--ink); font-weight: 550; }
table.tbl td.right, table.tbl th.right { text-align: right; }
table.tbl td.mono { font-size: var(--fs-micro); }

/* ==================================================================
   平台矩阵行
   ================================================================== */

/*
 * 每个平台一行：左边平台名，右边一个「挂单发布」。
 *
 * 用分隔线而不是独立卡片 —— 九张卡片叠起来太吵。
 * 行高刻意压得比较小：这里只是让人挑一个平台，不是看数据的地方。
 */
.pf-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
  transition: background .12s var(--ease);
}
.pf-row:last-child { border-bottom: 0; }
.pf-row:hover { background: var(--subtle); }
/* 已挂过的行整体压淡，一眼能看出它不参与选择 */
.pf-row.is-blocked { background: var(--subtle); }
.pf-row.is-blocked .pf-name { color: var(--slate-500); font-weight: 500; }
.pf-name { font-size: var(--fs-body); font-weight: 600; color: var(--ink); }
/* 右侧成组：指标 + 状态/按钮，整体右对齐 */
.pf-row-meta { display: flex; align-items: center; gap: var(--sp-4); }
.pf-warn { font-size: var(--fs-small); color: var(--warn); }

/* 交付成功率：次要信息，不能跟「挂单发布」抢注意力 */
.pf-rate {
  font-size: var(--fs-small); color: var(--slate-500);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ==================================================================
   附加要求（折叠）
   ------------------------------------------------------------------
   用原生 <details> —— 展开/收起、键盘操作、读屏朗读都是浏览器给的，
   不用自己补。这里只负责外观。
   ================================================================== */
.req-fold {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--white);
}
.req-fold > summary {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 12pxvar(--sp-3);
  cursor: pointer;
  font-size: var(--fs-body); font-weight: 550; color: var(--ink);
  /* 去掉浏览器默认的三角，自己画一个能跟文字对齐的 */
  list-style: none;
}
.req-fold > summary::-webkit-details-marker { display: none; }
.req-fold > summary::before {
  content: '';
  width: 0; height: 0; flex-shrink: 0;
  border-left: 5px solid var(--slate-400);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .16s var(--ease);
}
.req-fold[open] > summary::before { transform: rotate(90deg); }
.req-fold > summary:hover { background: var(--subtle); }
.req-fold > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
/* 勾了几项要能一眼看到，否则收起后就忘了自己设过要求 */
.req-count {
  margin-left: auto;
  font-size: var(--fs-micro); font-weight: 600;
  color: var(--brand);
}
.req-count:empty { display: none; }

.req-fold-body {
  padding: 0 var(--sp-3) var(--sp-3);
  border-top: 1px solid var(--line-soft);
  padding-top: var(--sp-2);
}
.req-opt {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: 8pxvar(--sp-2);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.req-opt:hover { background: var(--subtle); }
.req-opt input { margin-top: 2px; flex-shrink: 0; }
.req-opt-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.req-opt-label { font-size: var(--fs-body); color: var(--ink); }
.req-opt-hint { font-size: var(--fs-micro); color: var(--slate-500); line-height: 1.5; }
/* 低于 40% 值得警示 —— 但用文字色而不是红底，避免压过主按钮 */
.pf-rate.low { color: var(--warn); }
.pf-rate-empty { font-size: var(--fs-small); color: var(--slate-400); }

/* 迷你比例尺。颜色要能在浅底上看清 —— 之前用浅灰，条几乎隐形 */
.mini-bar { height: 3px; border-radius: 99px; background: var(--line-soft); overflow: hidden; }
.mini-bar > i { display: block; height: 100%; border-radius: 99px; }
.mini-bar > i.blue  { background: var(--brand-400); }
.mini-bar > i.amber { background: #c99a5b; }
.mini-bar > i.green { background: var(--ok); }
.mini-bar > i.rose  { background: var(--bad); }
.mini-bar > i.gray  { background: var(--slate-300); }

/* 状态分布行：一行文字 + 一条占比尺 */
.dist-row { margin-bottom: 13px; }
.dist-row:last-child { margin-bottom: 0; }
.dist-top {
  display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
}
.dist-top .num { font-variant-numeric: tabular-nums; }

/* ==================================================================
   步骤条
   ================================================================== */

/*
 * 发布步骤：竖排线性，一列从上往下。
 *
 * 原来四张卡并排——四个动作没有先后感，扫一眼像四件可选的事；
 * 实际是严格按顺序做的。竖排才表达「一步步来」，第 4 步
 * 直接由下面的链接表单接棒，不再单列一张「去填写」的卡。
 */
/*
 * 步骤之间用细线分隔，不再一层层套卡片。
 *
 * 原来是「白卡片里再放几个带边框的灰底卡片」，加上步骤里的按钮边框，
 * 同一块区域叠了三层框线：看着挤，也分不清哪层是分组、哪层是内容。
 * 去掉内层卡片的外壳，只留分隔线，层次反而更清楚。
 */
.steps { display: grid; grid-template-columns: minmax(0, 1fr); }
.step {
  display: flex; flex-direction: column;
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--line-soft);
}
.step:first-child { padding-top: 0; }
.step:last-child { padding-bottom: 0; border-bottom: 0; }
/* 序号与标题同一行：序号圆形固定 20px，只能放一个数字 */
.step-top { display: flex; align-items: center; gap: 8px; }
/*
 * 这一块整体比正文小一档是刻意的（它是个操作清单，不是正文），
 * 但原来的档位压得太狠：标题 16、说明 14，手机上看「照看它拍或剪」
 * 这种一句话说明要眯眼。整体提一档，序号也跟着放大到能对齐标题。
 */
.step .n {
  flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-small); font-weight: 600; line-height: 1;
  background: var(--brand-50); color: var(--brand);
}
.step .t {
  font-size: var(--fs-body); font-weight: 600; color: var(--ink);
  min-width: 0; overflow-wrap: anywhere;
}
.step .d { font-size: var(--fs-small); color: var(--slate-500); margin-top: 3px; line-height: 1.5; }
.step .act { margin-top: 8px; }

/* ==================================================================
   任务大厅卡片
   ================================================================== */

/*
 * 大厅列表：一行一个 / 一行两个。
 * 手机端（≤768px）强制单列 —— 屏幕就那么宽，分两列只会把卡片挤成一团。
 */
/* 发布要求：默认收起，点标题展开 —— 约束不该抢在操作步骤前面堆一屏 */
.req-fold {
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--subtle); overflow: hidden;
}
.req-fold > summary {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 12px; cursor: pointer; list-style: none;
  font-size: var(--fs-small); color: var(--slate-700);
}
.req-fold > summary::-webkit-details-marker { display: none; }
.req-fold > summary::after {
  content: ''; margin-left: auto;
  border: 4px solid transparent; border-top-color: var(--slate-400);
  transform: translateY(2px); transition: transform .13s var(--ease);
}
.req-fold[open] > summary::after { transform: rotate(180deg) translateY(-2px); }
.req-fold .req-list { padding: 0 12px 10px; }

.hall-list { display: grid; gap: 12px; }
.hall-list.hall-view-row { grid-template-columns: minmax(0, 1fr); }
.hall-list.hall-view-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 768px) {
  .hall-list.hall-view-grid { grid-template-columns: minmax(0, 1fr); }
}

/* 视图切换：一组小图标按钮，选中的那个用品牌浅底标出来 */
.view-switch {
  display: inline-flex; border: 1px solid var(--line);
  border-radius: 6px; overflow: hidden; flex-shrink: 0;
}
.vs-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 28px; padding: 0; border: 0;
  background: var(--white); color: var(--slate-400); cursor: pointer;
  transition: background .13s var(--ease), color .13s var(--ease);
}
.vs-btn + .vs-btn { border-left: 1px solid var(--line); }
.vs-btn:hover { background: var(--brand-50); }
.vs-btn.on { background: var(--brand-50); color: var(--brand); }

/*
 * 大厅筛选条：下拉和视图切换是"辅助操作"，字号比正文退一档。
 *
 * 之前跟正文同大（16px），一排控件占的视觉重量比它筛选出来的
 * 结果还大。作用域限定在 .hall-panel 里 —— 全局的 select / 按钮
 * （表单、模态、各处操作按钮）不动，那里 16px 是合适的。
 */
.hall-panel select,
.hall-panel .vs-btn { font-size: var(--fs-micro); }

/* 内容形式：图形 + 文字，底色只做很淡的区分，不抢状态徽标的语义色 */
.badge.kind { padding: 0 8px 0 6px; }
.badge.kind-article { background: var(--brand-50); color: var(--brand); }
.badge.kind-video { background: #f5edff; color: #7c3aed; }

/* 份数进度（3/10）：等宽数字，扫一列卡片时数字宽度一致 */
.badge.copies { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/*
 * 名额余量：亮条 + `还剩/总共`，和「接单」**同一行**排在它左边。
 *
 * 「还剩几个坑」和「点不点按钮」是同一条决定里的两句话，隔一行就断了。
 * 只在**还有余量**时渲染（被接满时前端返回 null，这一行只剩按钮）。
 *
 * 条宽固定 40px、徽标用等宽字体（`1/10` 和 `10/10` 宽度一致），
 * 免得几张卡的按钮左右参差。
 */
.slot-action {
  display: flex; align-items: center; gap: var(--sp-2);
}
.slot-row {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  /*
   * 名额文字不换行。「50/50 共 50 份」比「1/1」宽得多，折行的话
   * 「共 50 份」会掉到按钮下面，卡片被撑高 —— 两列卡片高矮不齐。
   */
  white-space: nowrap;
}
.slot-bar {
  width: 40px; height: 4px; border-radius: 99px;
  background: var(--line-soft); overflow: hidden;
}
/* 条是满的：它只表达「还有余量」，不画比例（份数少时比例没有中间态） */
/* 宽度由内联样式按已接比例给 —— 这里写死 100% 的话，没人接的单条也是满的 */
.slot-bar > i { display: block; height: 100%; background: var(--brand); }

/*
 * 任务单折叠组：组行是「一次决策」，展开的子行是组里的逐张单。
 * 子行整体垫一层淡底色、缩进 —— 不缩进的话和组行视觉权重一样，
 * 扫一眼分不清哪行属于哪组。
 */
tr.sub-row > td { background: var(--subtle); }

/* ==================================================================
   自定义下拉（替代原生 select，见 core.js 的 menuSelect）
   ================================================================== */

/*
 * 触发器长得像输入框，但**不是** select —— 原生 select 的下拉列表
 * 无法样式化，选项一多还会跑出视口。这里点了弹自定义菜单，
 * 定位交给 popover（越界翻转 + 夹视口内）。
 */
.select-trigger {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 0 var(--sp-2); height: 32px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--white); color: var(--ink);
  font-size: var(--fs-micro); font-weight: 500;
  cursor: pointer; white-space: nowrap;
}
.select-trigger:hover { border-color: var(--slate-400); }
.select-trigger.on { border-color: var(--brand); color: var(--brand); }
.select-trigger .st-caret { display: inline-flex; color: var(--slate-400); }

.menu-list { display: flex; flex-direction: column; padding: var(--sp-1); }
.menu-item {
  display: block; width: 100%; text-align: left;
  padding: var(--sp-1) var(--sp-2);
  border: none; border-radius: var(--r-sm); background: transparent;
  color: var(--ink); font-size: var(--fs-micro); font-family: inherit;
  cursor: pointer;
}
.menu-item:hover { background: var(--brand-50); color: var(--brand); }
/* 当前选中项：加粗 + 左侧留一个实心竖条，不靠底色（底色要留给 hover） */
.menu-item.on {
  font-weight: 650; color: var(--brand);
  box-shadow: inset 3px 0 0 var(--brand);
}

/* ==================================================================
   配图上传：拖拽区 + 累积清单 + 悬停预览
   ================================================================== */

/*
 * 拖拽区用**虚线框**：在表单里一眼能认出"这是可以往里扔东西的地方"，
 * 实线框会和普通输入框混淆。拖入时描边转品牌色、底色变浅 ——
 * 反馈必须立刻可见，否则用户会怀疑到底有没有接住。
 */
.drop-zone {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: var(--sp-4); margin-bottom: var(--sp-2);
  border: 1px dashed var(--line); border-radius: var(--r-sm);
  background: var(--white); color: var(--slate-500);
  font-size: var(--fs-small);
}
.drop-zone.over { border-color: var(--brand); background: var(--brand-50); color: var(--brand); }

.pick-list { display: flex; flex-direction: column; gap: var(--sp-1); margin-bottom: var(--sp-2); }
.pick-row {
  position: relative;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--subtle); font-size: var(--fs-small);
}
.pick-name {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pick-size { flex-shrink: 0; }
.pick-del {
  flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border: none; border-radius: 50%;
  background: transparent; color: var(--slate-400); cursor: pointer;
}
.pick-del:hover { background: var(--bad-bg); color: var(--bad); }

/*
 * 悬停预览：缩略图绝对定位浮在行的上方。
 * 用绝对定位而不是撑开行高 —— 预览图一撑，整张表单会跟着抖。
 * pointer-events 关掉，否则鼠标刚移上去就被它挡住、触发 mouseleave 闪个不停。
 */
.pick-preview {
  display: none; position: absolute; left: 0; bottom: calc(100% + 6px);
  max-width: 200px; max-height: 200px; z-index: 40;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  box-shadow: var(--sh-xl); background: var(--white);
  pointer-events: none;
}
.pick-row:hover .pick-preview { display: block; }

/*
 * 交付倒计时。
 *
 * 这一屏里唯一在变的、且变了就没法挽回的东西，所以给它最大的字号
 * 和最大的对比：数字 40px 等宽（tabular-nums，秒位跳动时不会左右晃），
 * 标签压成小字放上面 —— 数字是主角，说明退到背景。
 * 最后 5 分钟整块转红并轻微脉动：这时候人通常在别处忙着，
 * 靠余光察觉比靠"仔细看"现实。
 */
.count-box .alert-body { display: flex; align-items: baseline; gap: 10px; }
.count-box .count-label {
  font-size: var(--fs-small); font-weight: 600;
  color: var(--slate-500); white-space: nowrap;
}
.count-box .count {
  font-size: var(--fs-count); font-weight: 700; line-height: 1.05;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
}
.count-box.urgent .count { color: var(--bad); animation: countPulse 1s ease-in-out infinite; }
@keyframes countPulse { 50% { opacity: .5; } }

.hall-card {
  padding: 16px 16px; margin-bottom: 12px;
  border: 1px solid var(--line); border-radius: var(--r);
  /*
   * 柔和投影 + hover 抬升。
   * 纯 1px 边框卡片在灰底上贴成一片，卡片之间分不出主次，
   * 也没有「这块可以按」的手感 —— 分层阴影是补这两点的最低成本手段。
   */
  box-shadow: 0 1px 2px rgba(17, 24, 39, .04), 0 1px 3px rgba(17, 24, 39, .03);
  transition: border-color .14s var(--ease), box-shadow .14s var(--ease),
              transform .14s var(--ease);
}
.hall-card:last-child { margin-bottom: 0; }
/*
 * 两列网格下去掉卡片的下边距：网格的 gap 已经给了 12px 间距，
 * 再叠 margin 会让非末行的卡片比末行高出一截 —— 两列卡片高矮不齐，
 * 接单按钮也不在一条水平线上（单列视图没有网格，margin 照旧生效）。
 */
.hall-list.hall-view-grid .hall-card { margin-bottom: 0; }
.hall-card:hover {
  border-color: var(--slate-300);
  box-shadow: 0 4px 12px rgba(17, 24, 39, .08), 0 2px 4px rgba(17, 24, 39, .04);
  transform: translateY(-1px);
}
/* 不可接的单压低对比但仍可读 —— 用 opacity 会把里面的文字也一起糊掉 */
.hall-card.disabled { background: var(--subtle); }
.hall-card.disabled:hover { border-color: var(--line); transform: none; box-shadow: none; }
/*
 * 积分是接单人扫列表时唯一真正在找的数字：
 * 用品牌色 + 放大 + 等宽，让它从一排灰字里第一个被看到。
 */
.hall-price {
  font-size: var(--fs-title); font-weight: 700; color: var(--brand-700);
  font-variant-numeric: tabular-nums;
}
/*
 * 大厅容器拆掉卡片外框。
 *
 * 反模式里有一条很准：「所有页面都堆成卡片，缺少层级和主次」。
 * 之前这里是一张大卡片包着标题、筛选、以及里面一排小卡片 ——
 * 操作区和内容区都是卡片，视觉上分不出主次，整屏像一个灰白方块阵列。
 *
 * 拆掉外框后：标题+筛选变成一条轻工具条，列表里的每张卡重新成为主角。
 * 只动样式不动结构，改动面最小。
 */
.hall-panel {
  background: transparent; border: none; box-shadow: none; padding: 0;
}
.hall-panel > .card-head {
  padding: 0 0 12px; margin-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line); background: transparent;
}
.hall-panel > .card-pad { padding: 0; background: transparent; }

/* 卡片两栏：左信息右操作。min-width:0 防止长标题把右栏挤出去 */
/*
 * 左右两栏**顶部对齐、各自拉到满高**，不是垂直居中。
 *
 * 原来写的是 align-items: center：左栏描述折成两行时整体变高，
 * 右栏（价格 + 底部操作）就被居中推下去，于是同一行上的两张卡
 * 价格不在一条水平线上、底部操作也对不齐 —— 看起来像两张不同的卡片。
 * 改成 stretch 之后价格各自顶在开头，两卡永远齐平。
 */
.hall-row { display: flex; gap: var(--sp-4); align-items: stretch; }
.hall-main { flex: 1; min-width: 0; }
/* 标题单行省略，别折行撑高卡片（完整标题接单后可见） */
.hall-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hall-side {
  flex: none; display: flex; flex-direction: column;
  align-items: flex-end; gap: var(--sp-2);
}
/*
 * 底部那行（名额余量 + 接单按钮）贴到卡片底部。
 * 配合 stretch：左栏内容多高都行，右栏的按钮始终落在同一水平线。
 */
.hall-side > :last-child { margin-top: auto; }
.hall-side .btn { min-width: 88px; }
.hall-side .hall-price { line-height: 1.2; }

/*
 * 任务卡上的客户附加要求。
 * 用盾牌图标 + 淡底，和「价格」「截止」区分开 ——
 * 它不是这单的固有属性，而是客户额外加的门槛。
 */
.hall-reqs { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-bottom: var(--sp-3); }
.hall-req {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 8px; border-radius: 99px;
  font-size: var(--fs-micro);
  background: var(--brand-50); color: var(--brand-700);
}

/* 通用列表项：任务单、申诉等「一行一条」的卡片 */
.list-item {
  padding: 16px 16px; margin-bottom: 10px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
.list-item:last-child { margin-bottom: 0; }

/* 内容选择器行 */
.pick-item {
  padding: 12px 16px; cursor: pointer;
  border-bottom: 1px solid var(--line-soft);
  transition: background .12s var(--ease);
}
.pick-item:last-child { border-bottom: 0; }
.pick-item:hover { background: var(--subtle); }
.pick-item.active { background: var(--brand-50); }

/* ==================================================================
   平台任务链 —— Signature Element
   ------------------------------------------------------------------
   一排小徽标，每个代表「这篇内容在这个平台上的一张单子」，
   点色表示那张单子走到了哪一步。

   这是产品机制的直视图：一篇内容 × N 个平台 × 各自独立的状态。
   遮掉 Logo 之后，这一排仍然只在「多平台众包分发」的语境里成立。
   ================================================================== */
.chain {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-1);
  margin-top: var(--sp-2);
}
.chain-empty {
  font-size: var(--fs-micro); color: var(--slate-400);
}
.chain-node {
  display: inline-flex; align-items: center; gap: 4px;
  height: 26px; padding: 0 8px;
  border-radius: 99px;
  font-size: var(--fs-micro);
  background: var(--line-soft); color: var(--slate-700);
  white-space: nowrap;
}
/*
 * 状态点用 currentColor，颜色跟着色调走。
 * 圆点比文字先被眼睛读到，一列徽标扫过去能先按颜色分组，
 * 再决定看哪一个 —— 这比读完整行文字快得多。
 *
 * ⚠️ 节点底色改成**平台品牌色**之后（见文件上方的 .pf-* 规则），
 * 文字是白的，圆点再用 currentColor 就变成白点、看不出状态了。
 * 所以圆点改由 tone-* 给独立颜色 —— 底色说"哪个平台"，
 * 圆点说"什么状态"，两个通道互不遮挡。
 */
.chain-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex-shrink: 0;
}
.chain-dot.tone-blue  { background: #60a5fa; }
.chain-dot.tone-green { background: #4ade80; }
.chain-dot.tone-amber { background: #fbbf24; }
.chain-dot.tone-rose  { background: #fb7185; }
.chain-dot.tone-gray  { background: rgba(255, 255, 255, .5); }
/* 兜底：旧的非 pf 节点（没有平台色底）仍按原来的淡色块渲染 */
.chain-node.blue  { background: var(--brand-50); color: var(--brand); }
.chain-node.amber { background: var(--warn-bg);  color: var(--warn); }
/* 平台合并后的份数：跟在名字后面，比正文重一档，数得清又不抢戏 */
.chain-node .chain-n { font-weight: 650; opacity: .85; }
.chain-node.green { background: var(--ok-bg);    color: var(--ok); }
.chain-node.rose  { background: var(--bad-bg);   color: var(--bad); }
.chain-node.gray  { background: var(--line-soft); color: var(--slate-500); }
.chain-count {
  /* 纯辅助计数（"共 50 份"），比注释再小一档 */
  font-size: var(--fs-nano); color: var(--slate-400);
  margin-left: var(--sp-1);
}

/* 数值涨跌 */
.delta-up   { color: var(--ok); }
.delta-down { color: var(--bad); }
.delta-flat { color: var(--slate-400); }

/* 要求清单：逐条成行，带项目符号 */
.req-list {
  margin: 8px 0 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 4px;
}
.req-list li {
  position: relative; padding-left: 12px;
  font-size: var(--fs-micro); line-height: 1.6;
}
.req-list li::before {
  content: ''; position: absolute; left: 3px; top: 8px;
  width: 3px; height: 3px; border-radius: 50%;
  background: currentColor; opacity: .5;
}

/* ==================================================================
   时间线
   ================================================================== */

.timeline { position: relative; padding-left: 24px; }
.timeline::before {
  content: ''; position: absolute; left: 4px; top: 6px; bottom: 6px; width: 1px;
  background: var(--line);
}
.tl-item { position: relative; padding-bottom: 16px; }
.tl-item:last-child { padding-bottom: 0; }
/* 节点是实心小圆点，不套光晕 */
.tl-item::before {
  content: ''; position: absolute; left: -22px; top: 6px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--brand);
}
.tl-item.sys::before  { background: var(--slate-300); }
.tl-item.good::before { background: var(--ok); }
.tl-item.bad::before  { background: var(--bad); }
.tl-head { font-size: var(--fs-small); font-weight: 550; color: var(--ink); }
.tl-meta { font-size: var(--fs-micro); color: var(--slate-400); margin-top: 1px; }
.tl-body { font-size: var(--fs-micro); color: var(--slate-700); margin-top: 3px; }

/* ==================================================================
   关卡列表
   ================================================================== */

.gate {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px0; border-bottom: 1px solid var(--line-soft);
}
.gate:first-child { padding-top: 0; }
.gate:last-child { border-bottom: 0; padding-bottom: 0; }
.gate-tag {
  flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  min-width: 58px; height: 26px; padding: 0 7px;
  border-radius: var(--r-xs);
  font-size: var(--fs-micro); font-weight: 500;
  background: var(--line-soft); color: var(--slate-500);
}
.gate-tag.pass { background: var(--ok-bg);  color: var(--ok);  }
.gate-tag.fail { background: var(--bad-bg); color: var(--bad); }
/* 机器没判定的关卡：不能混在「通过」里 —— 通过不等于核对过 */
.gate-tag.manual { background: var(--warn-bg); color: var(--warn); }
.gate-body { flex: 1; min-width: 0; }
.gate-name {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: var(--fs-small); font-weight: 600; color: var(--ink);
}
.gate-reason { font-size: var(--fs-micro); color: var(--slate-700); margin-top: 3px; word-break: break-word; line-height: 1.5; }

/* ==================================================================
   弹窗
   ================================================================== */

.mask {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 6vh 16px 40px; overflow-y: auto;
  background: rgba(17, 24, 39, .42);
  animation: fadeIn .14s var(--ease);
}
.modal {
  width: 100%; max-width: 760px;
  background: var(--white);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-xl);
  overflow: hidden;
  animation: popIn .17s var(--ease);
}
.modal.wide   { max-width: 1020px; }
.modal.narrow { max-width: 450px; }

/*
 * 右侧抽屉：交付面板这类「要照着做的长内容」用。
 *
 * 从右缘滑入、全高、贴边直角 —— 和居中模态的「对话框」感刻意区分开。
 * 背后的页面仍可见（遮罩还在，但左边菜单不被盖住），
 * 用户可以边看抽屉里的步骤、边对照右边的列表。
 * 内容长了在 body 里滚，头尾固定。
 */
.mask.drawer {
  justify-content: flex-end;
  padding: 0;
  /*
   * 让抽屉撑满遮罩的高度，而不是自己写死 100vh。
   *
   * 遮罩是 fixed + inset:0，量出来就是**真实可视高度**；100vh 不是 ——
   * 手机上地址栏、桌面上的窗口缩放都会让 vh 比实际能看见的区域大，
   * 抽屉底部于是留出一条，把下面的页面（连客服按钮）露出来。
   */
  align-items: stretch;
}
.modal.drawer {
  width: min(960px, 92vw);
  max-width: none;
  /* 高度交给遮罩的 stretch 决定，别再写 100vh（见上） */
  height: auto;
  max-height: none;
  border-radius: 0;
  display: flex; flex-direction: column;
  animation: drawerIn .22s var(--ease);
}
.modal.drawer .modal-body {
  flex: 1; overflow-y: auto; min-height: 0;
  /*
   * 必须清掉通用那条 max-height。
   *
   * 通用规则写的是 calc(100vh - 250px)（给居中模态防超出屏幕用的），
   * 抽屉沿用它就糟了：720 高的屏，body 被卡在 470，抽屉底部于是
   * 空出 134px —— 看着就是「底栏下面还有一块白的」，而那块白是
   * 抽屉自己的空白，不是别的元素盖上来。
   */
  max-height: none;
}

/*
 * 抽屉打开时把手机端的底部导航藏掉。
 *
 * 它是 fixed bottom 的一条（半透明白 + 模糊），在矮视口里正好压在
 * 抽屉下方的按钮上 —— 从用户角度看就是「按钮被一条白块挡住」，
 * 而且它盖住的是「提交交付」这种关键操作。
 * 模态打开时本来就切不走导航，直接隐藏最省事。
 */
body:has(.mask.drawer) .tabbar { display: none; }
.modal.drawer .modal-foot { flex-shrink: 0; }
@keyframes drawerIn {
  from { transform: translateX(56px); opacity: .35; }
}
.modal-head {
  display: flex; align-items: center; gap: 8px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--line-soft);
}
.modal-head h3 { font-size: var(--fs-small); font-weight: 600; }
.modal-head .x {
  margin-left: auto; display: flex; padding: 4px;
  border: 0; border-radius: var(--r-xs); background: transparent;
  color: var(--slate-400); cursor: pointer; transition: background .13s, color .13s;
}
.modal-head .x:hover { background: var(--line-soft); color: var(--ink); }
.modal-body { padding: 24px; max-height: calc(100vh - 250px); overflow-y: auto; }
.modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 16px 24px;
  background: var(--subtle);
  border-top: 1px solid var(--line-soft);
}
/*
 * 只有标题、没有正文的确认框（「确定放弃吗」这类）。
 * 头和脚直接相邻，各自的边框会贴成一条 2px 的粗线，
 * 比别处分隔线显眼 —— 去掉下面那条，留头部的即可。
 */
.modal-head + .modal-foot { border-top: 0; }

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes popIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* ==================================================================
   提示条：淡底、无描边，靠图标区分语义
   ================================================================== */

.alert {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 12px 12px; border-radius: var(--r-sm);
  font-size: var(--fs-small); line-height: 1.65;
  background: var(--canvas); color: var(--slate-700);
}
.alert > .icon { margin-top: 2px; flex-shrink: 0; color: var(--slate-400); }
.alert-body { min-width: 0; }
.alert.blue  { background: var(--brand-50); color: var(--slate-700); }
.alert.blue  > .icon { color: var(--brand); }
.alert.green { background: var(--ok-bg); color: var(--slate-700); }
.alert.green > .icon { color: var(--ok); }
.alert.amber { background: var(--warn-bg); color: var(--slate-700); }
.alert.amber > .icon { color: var(--warn); }
.alert.rose  { background: var(--bad-bg); color: var(--slate-700); }
.alert.rose  > .icon { color: var(--bad); }
.alert b { font-weight: 600; color: var(--ink); }

/* ==================================================================
   吐司
   ================================================================== */

.toasts {
  position: fixed; top: 16px; right: 16px; z-index: 200;
  display: flex; flex-direction: column; gap: 8px; max-width: 400px;
}
.toast {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 12px 16px; border-radius: var(--r-sm);
  font-size: var(--fs-small); line-height: 1.5;
  background: var(--white); color: var(--slate-900);
  border: 1px solid var(--line);
  box-shadow: var(--sh-lg);
  animation: slideIn .18s var(--ease);
}
.toast > .icon { flex-shrink: 0; margin-top: 1px; color: var(--slate-400); }
.toast.ok   > .icon { color: var(--ok); }
.toast.err  > .icon { color: var(--bad); }
.toast.warn > .icon { color: var(--warn); }
@keyframes slideIn {
  from { opacity: 0; transform: translateX(20px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* ==================================================================
   登录页
   ------------------------------------------------------------------
   单列居中。控制台的登录页不该是营销落地页 ——
   双栏 hero、网格纹理、渐变标题都是噪音，去掉后只剩「选身份进入」这一件事。
   ================================================================== */

/* ==================================================================
   登录页：左边品牌面，右边表单
   ================================================================== */

/*
 * 左半边铺品牌渐变，右半边留白给表单。
 *
 * 这是全站唯一一处大面积用渐变的地方 —— 登录页是「人还没进系统」
 * 的那一屏，也是唯一有空间说清「我们是干什么的」的地方。
 * 进去之后每一屏都是干活用的，不该再抢戏。
 */
.auth {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  background: var(--white);
}

.auth-aside {
  background: var(--brand-grad);
  color: var(--white);
  padding: 56px 56px 40px;
  display: flex; flex-direction: column;
}
.auth-logo { display: flex; align-items: center; gap: var(--sp-3); }
.auth-logo-text b { display: block; font-size: var(--fs-title); letter-spacing: .04em; }
.auth-logo-text em {
  font-style: normal; display: block; margin-top: var(--sp-1);
  font-size: var(--fs-micro); letter-spacing: .08em; opacity: .8;
}

/*
 * 中间的任务卡：纯装饰，不承载任何信息。
 *
 * 原来这里是三条产品要点，删掉后左栏空了一大块 —— 半屏的位置空着，
 * 整页看着像没加载完。用几张错落的半透明卡片把空间撑起来：
 * 卡片呼应「派单」这件事本身，但一个字都不放，不抢标语的视线。
 *
 * 全用 rgba 白而不是实色：底色是品牌渐变，实色卡片会像贴上去的补丁，
 * 半透明才像「浮在渐变上」。
 *
 * 分内外两层是必须的：外层摆位置（位移 + 旋转），内层慢慢浮动。
 * 两个效果各占一个 transform，写在同一层里会互相覆盖 ——
 * 上一版三张卡叠在一起，一半原因就在这里。
 */
.auth-visual {
  flex: 1;
  position: relative;
  /*
   * 三张卡拉开后纵向跨约 354px（最上张上移 185 + 最下张下移 95 + 卡高 74），
   * 再加浮动动画的 ±14px，给到 400px 才不会被裁。
   */
  min-height: 400px;
}

.auth-vcard {
  position: absolute;
  top: 50%; left: 50%;
  width: 190px;
  will-change: transform;
}
/*
 * 三张卡摆成一个明显的三角：左上、右中、左下，各自旋转角度错开。
 * 位移的百分比是相对卡片自身（190 宽 × 74 高）。
 *
 * y 取 -250% / -60% / 128%：相邻两张中心差约 140px，卡片本身才 74px 高 ——
 * 空隙留到 60 多像素，是「各自飘着」而不是挨在一起。再大的话最上那张
 * 会顶出容器被裁掉，所以配套把容器撑到 400px（见 .auth-visual）。
 * x 顺势错开，让阶梯感跟着变松。
 */
.auth-vcard.v1 { transform: translate(-108%, -250%) rotate(-9deg); }
.auth-vcard.v2 { transform: translate(-14%, -60%) rotate(4deg); }
.auth-vcard.v3 { transform: translate(-62%, 128%) rotate(-4deg); }

.auth-vcard-in {
  padding: 16px;
  display: flex; flex-direction: column; gap: 8px;
  border-radius: var(--r-ctl);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .2);
  /*
   * 两个动画叠着跑，各管一个属性：浮动动的是 transform，进场淡入动的是 opacity。
   * 延迟错开，三张不同步 —— 一起上下等于整块背景在晃，很机械；错开才像各自飘着。
   */
  animation: authFloat 7s ease-in-out infinite, authFade .6s ease-out both;
}
.auth-vcard.v2 .auth-vcard-in { animation-delay: -2.4s, 0s; }
.auth-vcard.v3 .auth-vcard-in { animation-delay: -4.8s, 0s; }

@keyframes authFloat {
  0%, 100% { transform: translateY(-14px); }
  50% { transform: translateY(14px); }
}

/*
 * 上浮的小气泡：七个，大小/位置/节奏各不相同。
 * 动画是「从下往上飘 + 渐隐」，跑完从头再来 —— 看起来像一直在冒。
 */
.auth-bubble {
  position: absolute;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,
    rgba(255, 255, 255, .34), rgba(255, 255, 255, .08) 70%);
  box-shadow: inset 0 0 6px rgba(255, 255, 255, .18);
  animation: authBubble linear infinite;
  pointer-events: none;
}
.auth-bubble.b1 { width: 14px;  height: 14px;  left: 12%;  top: 72%; animation-duration: 9s;   animation-delay: 0s; }
.auth-bubble.b2 { width: 26px;  height: 26px;  left: 30%;  top: 84%; animation-duration: 12s;  animation-delay: -3s; }
.auth-bubble.b3 { width: 10px;  height: 10px;  left: 52%;  top: 88%; animation-duration: 8s;   animation-delay: -6s; }
.auth-bubble.b4 { width: 34px;  height: 34px;  left: 68%;  top: 78%; animation-duration: 14s;  animation-delay: -2s; }
.auth-bubble.b5 { width: 18px;  height: 18px;  left: 84%;  top: 90%; animation-duration: 10s;  animation-delay: -5s; }
.auth-bubble.b6 { width: 12px;  height: 12px;  left: 44%;  top: 12%; animation-duration: 11s;  animation-delay: -4s; }
.auth-bubble.b7 { width: 20px;  height: 20px;  left: 8%;   top: 20%; animation-duration: 13s;  animation-delay: -8s; }

@keyframes authBubble {
  0%   { transform: translateY(0) scale(.9); opacity: 0; }
  12%  { opacity: .8; }
  85%  { opacity: .5; }
  100% { transform: translateY(-46vh) scale(1.06); opacity: 0; }
}
@keyframes authFade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 系统里关掉了动效就别动 —— 这类漂浮动画对前庭敏感的人是真难受 */
@media (prefers-reduced-motion: reduce) {
  .auth-vcard-in { animation: none; }
  .auth-bubble { animation: none; opacity: .4; }
}

.vl { height: 8px; border-radius: 4px; background: rgba(255, 255, 255, .42); }
.vl.v1 { width: 62%; }
.vl.v2 { width: 88%; }
.vl.v3 { width: 44%; }

/* 口号压到底部：上方留给卡片，视线最后落在这句话上 */
.auth-slogan { margin-top: auto; }
.auth-slogan h2 {
  font-size: var(--fs-display); line-height: 1.35;
  font-weight: 650; letter-spacing: -.01em;
}

.auth-main { display: grid; place-items: center; padding: 40px var(--sp-5); }
.auth-box { width: 100%; max-width: 380px; }

/*
 * 窄屏：品牌面从「半屏」收成「一条头」。
 *
 * 平板以下还分两栏的话，表单会被压到不够 320px（输入框都要变形），
 * 所以改成上下排；手机上再把三条要点收掉，只留 Logo 和口号 ——
 * 那三条是给第一次来的人看的，小屏上滚动成本太高。
 */
@media (max-width: 1024px) {
  .auth { grid-template-columns: minmax(0, 1fr); }
  .auth-aside { padding: var(--sp-6) var(--sp-5); }
  /* 左栏收成一条头，装饰卡片就不留了：它撑的是竖向空间，横过来反而挡路 */
  .auth-visual { display: none; }
  .auth-slogan { margin-top: var(--sp-5); }
  .auth-slogan h2 { font-size: var(--fs-title); }
}
@media (max-width: 640px) {
  .auth-aside { padding: var(--sp-5) var(--sp-4); }
  .auth-main { padding: var(--sp-5) var(--sp-4); }
}

/* ---- 登录 / 注册 切换 ---- */
.auth-tabs {
  display: flex; gap: 4px; margin-bottom: 20px;
  background: var(--line-soft); border-radius: var(--r-sm); padding: 4px;
}
.auth-tab {
  flex: 1; height: 30px; border: 0; border-radius: calc(var(--r-sm) - 2px);
  background: transparent; color: var(--slate-500);
  font-size: var(--fs-small); font-weight: 500; cursor: pointer;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.auth-tab.on { background: var(--white); color: var(--ink); box-shadow: 0 1px 2px rgba(17,24,39,.06); }

/* ---- 注册表单 ---- */
/* type=tel 不继承通用输入框的 width:100%，会缩成默认宽度 */
/* tel 已并入上方全局输入框样式，这里的补丁式宽度覆盖不再需要 */
.reg-role-list { display: flex; gap: 8px; margin-bottom: 18px; }
.reg-role {
  flex: 1; display: flex; flex-direction: column; gap: 4px;
  padding: 8px 12px; text-align: left; cursor: pointer;
  background: var(--white); border: 1px solid var(--line); border-radius: var(--r-sm);
  transition: border-color .14s var(--ease), background .14s var(--ease);
}
.reg-role:hover { border-color: var(--brand-200); }
.reg-role.on { border-color: var(--brand); background: var(--brand-50); }
.reg-role-name { font-size: var(--fs-small); font-weight: 550; color: var(--ink); }
.reg-role-hint { font-size: var(--fs-micro); color: var(--slate-500); }
.reg-note { font-size: var(--fs-micro); color: var(--slate-400); margin-top: 10px; line-height: 1.6; }

.login-foot {
  margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line-soft);
  font-size: var(--fs-micro); color: var(--slate-400); line-height: 1.6;
}

/*
 * 加群引导：页脚最下面一行。
 *
 * 刻意保持克制 —— 引导条是辅助信息，用品牌色标出群号就够，
 * 不加图标不加底色，不跟主任务（登录/选身份）抢注意力。
 * 群号没配置时整行不渲染（见 app.js），不会出现点了没反应的空入口。
 */
.qq-guide {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  font-size: var(--fs-micro); color: var(--slate-500); text-align: center;
}
.qq-guide .qq-no,
.qq-guide a,
.qq-guide .link-btn {
  color: var(--brand); font-weight: 600; margin-left: 4px;
  font-variant-numeric: tabular-nums; text-decoration: none;
}

/*
 * 文字按钮：长得像链接，实际是按钮（点了弹面板，不跳页）。
 *
 * 用 button 而不是 <a> 是为了语义对得上：屏幕阅读器不会把「展开客服面板」
 * 念成「链接」，键盘 Tab 也能正常聚焦、回车触发。
 * 之所以要单独定义，是因为浏览器给 button 的默认样式（灰底、内边距、
 * 居中）跟这里要的「一行小字」差得远，不清掉会顶出一个方块。
 */
.link-btn {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--brand); font-weight: 600;
}
/*
 * 登录页的「忘记密码」：次要入口，压到辅助档。
 *
 * 它跟正文一样大的话会跟下面那个「登录」主按钮抢注意力 ——
 * 而它只是「想不起密码才会点」的兜底路径，不该占视觉重量。
 */
.login-forgot { font-size: var(--fs-micro); }
.link-btn:hover { text-decoration: underline; }
.qq-guide a:hover { text-decoration: underline; }

/*
 * 注册页「我已阅读并同意《须知》」那一行。
 * 勾选框与文字对齐同一行；条款名必须一眼看出可点 —— 否则没人去看全文，
 * 勾选就变成走过场，这份须知也就白写了。
 */
.agree-row {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.agree-row input[type='checkbox'] { width: 16px; height: 16px; flex: none; }
a.link { color: var(--brand); text-decoration: underline; }

/*
 * 须知全文：保留原文的换行与缩进（内容是分条写的，压成一段没法读），
 * 限高滚动 —— 全文不短，但不能把弹窗撑到满屏。
 */
.agreement {
  white-space: pre-wrap; line-height: 1.8;
  max-height: 58vh; overflow-y: auto;
}

/* ==================================================================
   礼品卡兑换
   ================================================================== */

.sku-list { display: flex; flex-direction: column; gap: 8px; }
.sku-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 12px;
  background: var(--white);
  border: 1px solid var(--line); border-radius: var(--r);
}
/*
 * 买不起的品类调淡但**不隐藏**。
 * 藏起来的话，用户不知道再攒一点能换什么 —— 看得见的目标才有攒的动力。
 */
.sku-row.dim { background: var(--subtle); }
.sku-row.dim .sku-name { color: var(--slate-500); }
.sku-ico {
  width: 30px; height: 30px; flex-shrink: 0; border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-50); color: var(--brand);
}
.sku-row.dim .sku-ico { background: var(--line-soft); color: var(--slate-400); }
.sku-main { flex: 1; min-width: 0; }
.sku-name { font-size: var(--fs-small); font-weight: 550; color: var(--ink); }
.sku-sub { margin-top: 1px; }

/*
 * 卡密展示框。
 * 等宽字体 + 加大字号 + 可选中：卡密是要被抄下来的，
 * 用正文字体显示 0/O、1/l 会让人抄错。
 */
.card-secret {
  padding: 16px 16px;
  background: var(--ink); color: #fff;
  border-radius: var(--r);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-small); letter-spacing: .04em; line-height: 1.7;
  word-break: break-all; user-select: all;
}

/* ==================================================================
   杂项
   ================================================================== */

.empty {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 40px 24px; text-align: center;
  font-size: var(--fs-small); color: var(--slate-500);
}
.empty .icon { color: var(--slate-300); }

.loading {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 52px 24px; font-size: var(--fs-small); color: var(--slate-500);
}
.spinner {
  width: 16px; height: 16px; flex-shrink: 0;
  border: 2px solid var(--line); border-top-color: var(--brand);
  border-radius: 50%; animation: spin .65s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.divider { height: 1px; background: var(--line-soft); margin: 14px 0; }

.kv { display: flex; justify-content: space-between; gap: 16px; padding: 4px0; font-size: var(--fs-small); }
.kv .k { color: var(--slate-500); }
.kv .v { color: var(--ink); font-weight: 550; text-align: right; font-variant-numeric: tabular-nums; }
.kv.total { margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--line); }
.kv.total .k { color: var(--slate-700); }
.kv.total .v { font-size: var(--fs-body); font-weight: 600; letter-spacing: -.015em; }

/* 多段进度条：纯色分段，不做渐变 */
.bar { height: 6px; border-radius: 99px; background: var(--line-soft); overflow: hidden; display: flex; }
.bar > i { display: block; height: 100%; transition: width .35s var(--ease); }
.bar > i.green { background: var(--ok); }
.bar > i.amber { background: #c07d16; }
.bar > i.rose  { background: var(--bad); }
.bar > i.blue  { background: var(--brand); }

.body-preview {
  padding: 12px 12px; max-height: 200px; overflow-y: auto;
  font-size: var(--fs-micro); line-height: 1.75; color: var(--slate-700);
  white-space: pre-wrap; word-break: break-word;
  background: var(--subtle); border: 1px solid var(--line); border-radius: var(--r-sm);
}

.thumbs { display: flex; gap: 8px; flex-wrap: wrap; }
.thumb {
  width: 56px; height: 56px; border-radius: var(--r-xs);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; padding: 0;
  font-size: var(--fs-micro); line-height: 1.2; text-align: center; color: var(--slate-400);
  background: var(--line-soft);
  border: 1px solid var(--line);
}
/* 配图是真图：铺满小方块，点一下能单独下载 */
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ==================================================================
   在线客服挂件
   ------------------------------------------------------------------
   常驻右下角、可最小化。刻意不做成整页：用户是干活时顺手问一句，
   切走页面去找客服，回来就忘了要问什么。
   面板只占 340px，不盖住主操作区；窄屏时铺满宽度但只占六成高，
   底下的内容仍然看得见。
   ================================================================== */
.support-root {
  position: fixed; right: var(--sp-5); bottom: var(--sp-5); z-index: 60;
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-3);
}

.support-fab {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  height: var(--tap); padding: 0 var(--sp-4);
  border: 1px solid var(--brand-200); border-radius: 999px;
  background: var(--white); color: var(--brand);
  font-family: inherit; font-size: var(--fs-body); font-weight: 600;
  box-shadow: var(--sh-lg); cursor: pointer;
}
.support-fab.active { background: var(--brand); color: var(--white); border-color: var(--brand); }

.support-panel {
  width: 340px; max-height: min(560px, calc(100vh - 160px));
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--sh-lg);
}
.support-panel.hidden { display: none; }

.support-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line);
}
.support-title {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-subtitle); font-weight: 600; color: var(--navy);
}
.support-icon-btn {
  margin-left: auto; padding: var(--sp-1) var(--sp-2);
  border: 0; background: none; color: var(--slate-400);
  font-size: var(--fs-title); line-height: 1; cursor: pointer;
}

.support-body {
  flex: 1; overflow-y: auto; padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.support-msg {
  max-width: 88%; padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r); font-size: var(--fs-body); line-height: var(--lh-body);
  white-space: pre-wrap; word-break: break-word;
}
.support-msg.bot { align-self: flex-start; background: var(--subtle); border: 1px solid var(--line); color: var(--slate-700); }
.support-msg.user { align-self: flex-end; background: var(--brand); color: var(--white); }
.support-msg.system { align-self: center; background: none; color: var(--slate-500); font-size: var(--fs-small); }
.support-handoff { margin-top: var(--sp-2); }

.support-foot { display: flex; gap: var(--sp-2); padding: var(--sp-3); border-top: 1px solid var(--line); }
.support-foot input {
  flex: 1; height: 36px; padding: 0 var(--sp-3);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-family: inherit; font-size: var(--fs-body);
}
.support-tips {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 0 var(--sp-3) var(--sp-3);
  font-size: var(--fs-micro); color: var(--slate-400);
}

@media (max-width: 640px) {
  /*
   * 手机端客服挂件要抬到底部导航之上。
   *
   * 手机端有固定底栏（.tabbar，fixed bottom:0，高度约 60px + 安全区），
   * 挂件原来的 bottom:12px 正好叠在底栏上，会压住「我的账号」。
   * 抬高量 = 底栏高度 + 一点间隙；safe-area 跟底栏保持同一基准，
   * 全面屏上才不会一个抬了一个没抬。
   */
  .support-root {
    left: var(--sp-3); right: var(--sp-3);
    bottom: calc(68px + env(safe-area-inset-bottom, 0px));
    align-items: stretch;
  }
  .support-panel { width: auto; max-height: 55vh; }
  .support-fab { align-self: flex-end; }
}

/* 漏斗行 */
.funnel-row { margin-bottom: 12px; }
.funnel-row:last-child { margin-bottom: 0; }
.funnel-top {
  display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px;
}
.funnel-label { font-size: var(--fs-small); color: var(--slate-700); }
.funnel-v {
  margin-left: auto; font-size: var(--fs-small); font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.funnel-pct { font-size: var(--fs-micro); color: var(--slate-400); font-variant-numeric: tabular-nums; }

.muted  { color: var(--slate-500); }
/* small 与 xsmall 必须差一档 —— 归并时两者撞成同一个值，等于白设 */
.small  { font-size: var(--fs-small); }
.xsmall { font-size: var(--fs-micro); }
.strong { color: var(--ink); font-weight: 600; }
.nowrap { white-space: nowrap; }
.break  { word-break: break-all; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.mt8 { margin-top: 8px; }  .mt12 { margin-top: 12px; } .mt16 { margin-top: 16px; }
.mb8 { margin-bottom: 8px; } .mb12 { margin-bottom: 12px; } .mb16 { margin-bottom: 16px; }

.scroll-y    { max-height: 440px; overflow-y: auto; }
.scroll-y-lg { max-height: 640px; overflow-y: auto; }

/* ==================================================================
   键盘可达性
   ------------------------------------------------------------------
   之前只有 .btn 有 focus-visible，其余可交互元素（侧边栏项、页签、
   角色卡、输入框、表格里的按钮）全部没有可见焦点 —— 键盘用户按 Tab
   会完全失去位置感，等于走不通。

   这里给所有可聚焦元素兜一个默认焦点环。用 :focus-visible 而不是
   :focus，是为了只在键盘操作时出现，鼠标点击不会留下残留光圈。

   全局规则放最后：它优先级低，各组件仍可用更贴合的样式覆盖
   （比如深色背景上的按钮需要反色焦点环）。
   ================================================================== */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* 输入类控件本身有边框，焦点环贴着边更利落 */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 0;
}

/* 深底元素上品牌蓝看不清，改用白色焦点环 */
.sb-item:focus-visible,
.badge.solid:focus-visible {
  outline: 2px solid var(--brand-200);
  outline-offset: -2px;
}

/*
 * 动效降级。
 *
 * 不只缩短时长 —— 位移与循环动画要真正去掉，否则前庭功能敏感的用户
 * 依然会被晃动影响。这里只保留透明度变化，它不产生空间位移。
 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-property: opacity, background-color, border-color, color !important;
    transform: none !important;
  }
}

/* 高对比度模式下确保描边不丢 —— 强制颜色会覆盖背景，靠边框撑结构 */
@media (forced-colors: active) {
  .card, .btn, .field input, .field select, .field textarea, .tbl th, .tbl td {
    border: 1px solid CanvasText;
  }
  .badge { border: 1px solid CanvasText; }
}
.billing-box { display:flex; justify-content:space-between; gap:var(--sp-3); padding:12px var(--sp-3); margin-bottom:var(--sp-3); background:var(--brand-50); border-radius:var(--r-sm); }
.billing-k { font-size:var(--fs-small); color:var(--slate-500); }
.billing-v { font-size:var(--fs-body); font-weight:600; color:var(--brand-700); }

/* ==================================================================
   移动端（≤768）：让屏幕小的设备真能用
   ================================================================== */

/*
 * 弹窗改近全屏。
 *
 * 手机本来就窄，再套一个「上下留 6vh、左右留 16px」的浮层，
 * 正文只剩一小条 —— 填交付链接、看验收记录这种活儿根本没法干。
 * 铺满之后，按钮还留在拇指够得着的位置（底部 sticky）。
 */
/*
 * 顶栏品牌与底部导航：桌面默认不出现，手机上才接管。
 *
 * 这条必须写在媒体查询**前面** —— 同特异性下靠源顺序决定胜负，
 * 写在后面会把媒体查询里的 display:flex 一起盖掉，
 * 结果是手机上也看不见导航。
 */
.topbar-brand,
.tabbar { display: none; }

@media (max-width: 768px) {
  /*
   * 手机换底部导航：侧边栏整个让位。
   *
   * 原来手机上侧边栏收成 56px 图标条，看着"省地方"，实际仍然占着一条竖边 ——
   * 屏就这么宽，正文被挤得只能左右滑。让位之后正文占满，导航挪到拇指
   * 自然够得到的底部（单手也能点）。
   */
  .sidebar { display: none; }
  .shell { grid-template-columns: minmax(0, 1fr); }

  .tabbar {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    background: rgba(255, 255, 255, .96);
    backdrop-filter: blur(10px);
    border-top: 1px solid var(--line);
    /* iPhone 有底部横条，不留安全区导航会被它压住 */
    padding-bottom: env(safe-area-inset-bottom);
  }
  .tab-item {
    position: relative;                 /* 给角标定位 */
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--sp-1); min-height: 56px; padding: var(--sp-1);
    border: 0; background: none; cursor: pointer;
    color: var(--slate-400); font-size: var(--fs-micro);
    font-family: inherit;
  }
  .tab-item.active { color: var(--brand); font-weight: 600; }
  .tab-item span { white-space: nowrap; }

  /* 正文底部留出导航的高度，否则最后一行会被压到导航底下 */
  .content { padding-bottom: 84px; }

  /* 顶栏品牌：侧边栏让位后，品牌挪上来（桌面端由侧边栏承担，这里隐藏） */
  .topbar-brand {
    display: flex; align-items: center; gap: var(--sp-2);
    font-size: var(--fs-body); font-weight: 700; color: var(--ink);
    white-space: nowrap;
  }
  /* 品牌和页面标题并排，标题收一档免得挤 */
  .topbar-title { font-size: var(--fs-small); }

  /*
   * 顶栏多了一个品牌之后更紧了，把精简提前到 768。
   * （原来只在 640 以下处理，而 640～768 这段正是侧边栏让位、
   *   顶栏反而变挤的区间。）
   */
  .topbar { padding: 0 var(--sp-3); gap: var(--sp-2); }
  /* 冻结只是可用余额的注脚，手机上先让它 */
  .topbar-right .money-chip.warn { display: none; }
  /* 充值收成图标，文字挤不下 */
  .topbar-right .btn-label { display: none; }
  .topbar-right .btn { min-width: var(--tap); justify-content: center; }

  .mask { padding: 0; align-items: stretch; }

  .modal, .modal.wide, .modal.narrow {
    display: flex; flex-direction: column;
    max-width: none; width: 100%; min-height: 100%;
    border-radius: 0;
  }
  .modal-head { padding: var(--sp-3) var(--sp-4); }
  .modal-body { padding: var(--sp-4); max-height: none; flex: 1 1 auto; }
  .modal-foot {
    position: sticky; bottom: 0;
    /* iPhone 有底部横条，不留安全区按钮会被它压住 */
    padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom));
  }
  .modal-foot .btn { flex: 1 1 auto; }

  /* 表格照旧横滑（.table-wrap 已有），但字号收一档，一屏能多看一列 */
  .tbl { font-size: var(--fs-micro); }
}

/* 导航角标：未处理数量 */
.tab-badge {
  position: absolute; top: 4px; left: 50%; transform: translateX(10px);
  min-width: 18px; height: 18px; padding: 0 4px;
  border-radius: 999px; background: var(--bad); color: var(--white);
  font-size: var(--fs-micro); line-height: 18px; text-align: center;
  font-weight: 600;
}
