/* ==================================================================
   派单 · 控制台设计系统
   ------------------------------------------------------------------
   零 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; }
/*
 * 侧栏品牌区必须能收窄并截断：这两行都是 nowrap，
 * 一旦文字变长（曾经把版本号拼进副标题）就会溢出侧栏，
 * 压在右侧页面标题上，看着像整页布局崩了。
 */
.sb-brand-text { min-width: 0; overflow: hidden; }
.sb-brand-name {
  font-size: var(--fs-small); font-weight: 650; color: var(--ink);
  white-space: nowrap; letter-spacing: .01em;
  overflow: hidden; text-overflow: ellipsis;
}
/* 字母间距拉开的小写标签，是工具类产品 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;
  overflow: hidden; text-overflow: ellipsis;
}

.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，写在同一层里会互相覆盖 ——
 * 上一版三张卡叠在一起，一半原因就在这里。
 */
/* ---------------- 三张 3D 任务卡 ----------------
 *
 * 上一版是三张静态的白色半透明卡片：能看，但完全没有「东西」的感觉 ——
 * 平躺在渐变上，鼠标划过毫无反应。
 *
 * 这一版改成真的 3D：
 *   · 场景有透视，三张卡在 Z 轴上分层（近的更大、离得远的更小更淡）
 *   · 鼠标在左栏移动时整组跟着转，三层错峰位移 → 有立体感而不是一张贴图
 *   · 每张卡自己还在缓慢浮动，卡与卡之间不同步
 *   · 鼠标移到某张卡上，它朝前抬起、描边和光晕同时亮，别的卡微微压暗
 *
 * 全是 CSS 3D + 几个 CSS 变量，没有引入任何库，也不下载图片。
 */

.auth-visual {
  flex: 1;
  position: relative;
  /* 卡片的 3D 舞台：perspective 放在共用父级，三张卡才共享同一个消失点 */
  perspective: 760px;
  perspective-origin: 50% 48%;
  min-height: 420px;
}

/* 舞台下方的一团圆光，给卡片一个「落在地上」的参照 */
.auth-visual::after {
  content: '';
  position: absolute;
  left: 50%; top: 58%;
  width: 300px; height: 300px;
  margin: -150px 0 0 -150px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 66%);
  filter: blur(6px);
  pointer-events: none;
}

.auth-scene {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  /*
   * --px / --py 由 JS 按鼠标位置写入，取值 -1 ~ 1，静止时是 0。
   * 场景整体只转很小的角度（±7°/±9°）：再大就晕，而且卡片会被压扁。
   */
  transform:
    rotateX(calc(var(--py, 0) * -7deg))
    rotateY(calc(var(--px, 0) * 9deg));
  transition: transform .22s ease-out;
  will-change: transform;
}

.auth-card {
  position: absolute;
  left: 50%; top: 50%;
  width: 208px;
  margin: -74px 0 0 -104px;
  transform-style: preserve-3d;
  transition: transform .35s cubic-bezier(.2, .7, .3, 1);
  will-change: transform;
}

/*
 * 三张卡的位置与深度。
 *
 * Z 轴故意拉开：c1 最近（72px）、c3 最远（-56px），
 * 于是三张卡大小不同、浮起来的时间差也更明显 —— 平铺在一起就是「三张图」，
 * 拉开 Z 才像「三个东西」。
 */
.auth-card.c1 { transform: translate3d(calc(-58px + var(--px, 0) * -26px), calc(-168px + var(--py, 0) * -16px), 132px) rotateY(12deg) rotateZ(-7deg); }
.auth-card.c2 { transform: translate3d(calc(36px + var(--px, 0) * -14px), calc(6px + var(--py, 0) * -10px), -14px) rotateY(-9deg) rotateZ(3deg); }
.auth-card.c3 { transform: translate3d(calc(-52px + var(--px, 0) * 22px), calc(172px + var(--py, 0) * 15px), -128px) rotateY(10deg) rotateZ(-5deg); }

/* 浮动层：和位置层分开，否则两个 transform 会互相覆盖 */
.auth-card-float {
  transform-style: preserve-3d;
  animation: authFloat 7.5s ease-in-out infinite, authFade .7s ease-out both;
}
.auth-card.c2 .auth-card-float { animation-delay: -2.6s, .1s; }
.auth-card.c3 .auth-card-float { animation-delay: -5.1s, .2s; }

.auth-card.c3 .auth-card-face { opacity: .86; }
.auth-card.c2 .auth-card-face { opacity: .95; }

.auth-card-face {
  position: relative;
  display: flex; flex-direction: column; gap: 9px;
  padding: 18px 16px 16px;
  border-radius: 14px;
  background: linear-gradient(158deg, rgba(255, 255, 255, .30) 0%, rgba(255, 255, 255, .11) 100%);
  border: 1px solid rgba(255, 255, 255, .40);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  box-shadow:
    0 20px 42px -20px rgba(4, 24, 60, .55),
    inset 0 1px 0 rgba(255, 255, 255, .55);
  transition: transform .3s cubic-bezier(.2, .7, .3, 1), box-shadow .3s ease, border-color .3s ease;
  cursor: pointer;
}

/* 鼠标压上去：卡片朝前抬起、边缘发光 —— 这是「可交互」最直接的反馈 */
.auth-card:hover .auth-card-face {
  transform: translateY(-6px) scale(1.035);
  border-color: rgba(255, 255, 255, .72);
  box-shadow:
    0 30px 60px -22px rgba(6, 40, 100, .62),
    0 0 0 1px rgba(255, 255, 255, .28),
    inset 0 1px 0 rgba(255, 255, 255, .7);
}

/* 一张被抬起时，另外两张稍微压暗后退，注意力集中在一张上 */
.auth-scene:hover .auth-card:not(:hover) .auth-card-face { opacity: .74; transform: scale(.985); }
.auth-scene:hover .auth-card:not(:hover) .auth-card-face { transition: opacity .25s ease, transform .3s ease; }

.auth-card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.auth-card-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #9dffd3;
  box-shadow: 0 0 0 3px rgba(157, 255, 211, .22);
  animation: authDot 2.6s ease-in-out infinite;
}
.auth-card.c2 .auth-card-top { justify-content: flex-end; }
.auth-card.c3 .auth-card-top { justify-content: flex-start; }

.auth-card-chip {
  height: 7px; width: 46px; border-radius: 4px;
  background: rgba(255, 255, 255, .42);
}

/* 卡面上的「内容行」：宽度不同，看起来才像真的一行行字 */
.vl { display: block; height: 8px; border-radius: 4px; background: rgba(255, 255, 255, .46); }
.vl.v1 { width: 86%; }
.vl.v2 { width: 100%; }
.vl.v3 { width: 54%; }

@keyframes authFloat {
  0%, 100% { transform: translateY(-9px); }
  50%      { transform: translateY(9px); }
}
@keyframes authDot {
  0%, 100% { opacity: .55; box-shadow: 0 0 0 3px rgba(157, 255, 211, .16); }
  50%      { opacity: 1; box-shadow: 0 0 0 5px rgba(157, 255, 211, .26); }
}

/*
 * 气泡海洋。
 *
 * 原来写死七个气泡、七条 CSS 规则，改一个位置要动样式表。
 * 现在尺寸、横向位置、速度、上升距离、模糊量全部走 CSS 变量，
 * 由 JS 用固定种子生成一批 —— 每次刷新位置都一样，但数量能随手调。
 *
 * 远近靠模糊 + 透明度区分：清晰的大泡在前，糊一点的小泡在后，
 * 一层平面里叠出深度，才像「一片」而不是「几个圆点」。
 */
.auth-bubble {
  position: absolute;
  bottom: -8%;
  left: var(--l, 50%);
  width: var(--s, 16px);
  height: var(--s, 16px);
  border-radius: 50%;
  opacity: var(--o, .5);
  filter: blur(var(--b, 0));
  background: radial-gradient(circle at 34% 28%,
    rgba(255, 255, 255, .62), rgba(255, 255, 255, .16) 58%, rgba(255, 255, 255, .04) 100%);
  box-shadow: inset 0 0 8px rgba(255, 255, 255, .28);
  animation: authBubble var(--d, 12s) linear infinite;
  animation-delay: var(--delay, 0s);
  pointer-events: none;
  will-change: transform, opacity;
}

@keyframes authBubble {
  0%   { transform: translate3d(0, 0, 0) scale(.86); opacity: 0; }
  10%  { opacity: var(--o, .5); }
  70%  { opacity: calc(var(--o, .5) * .8); }
  100% { transform: translate3d(var(--drift, 0px), calc(var(--rise, 420px) * -1), 0) scale(1.08); opacity: 0; }
}
@keyframes authFade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* 系统里关掉了动效就别动 —— 这类漂浮动画对前庭敏感的人是真难受 */
@media (prefers-reduced-motion: reduce) {
  .auth-card-float,
  .auth-card-dot,
  .auth-bubble { animation: none !important; }
  .auth-bubble { opacity: .4; }
  /* 视差也停掉：它是纯装饰，晕的人不该被它晃 */
  .auth-scene { transform: none !important; }
}

/* 口号压到底部：上方留给卡片，视线最后落在这句话上 */
.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;
}

/* ==================================================================
   运营端登录页（/admin）
   ------------------------------------------------------------------
   和用户端登录页刻意不是一套视觉：
     · 左边是「暗色值守台」——深底、极光、漂移网格、状态条，
       传达的是「这里在跑系统」，不是营销页；
     · 右边是一张白卡，只干登录这一件事。
   动效全部是 CSS，没有依赖，也没有额外的图片资源。
   ================================================================== */

.adm-auth {
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(420px, 520px);
  background: #070c16;
}

/* ---------------- 左侧：暗色值守台 ---------------- */

.adm-side {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 26px;
  padding: 44px 56px 30px;
  color: #e6eefc;
  isolation: isolate;
}

/* 背景层单独一层：极光、网格都不参与布局，也不会被内容挤变形 */
.adm-bg { position: absolute; inset: 0; z-index: -1; overflow: hidden; }

.adm-aurora {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(70px);
  opacity: .55;
  will-change: transform;
}
.adm-aurora.a1 {
  width: 620px; height: 620px;
  top: -220px; left: -160px;
  background: radial-gradient(circle at 40% 40%, #2f7bff 0%, rgba(47, 123, 255, 0) 68%);
  animation: admDriftA 22s ease-in-out infinite alternate;
}
.adm-aurora.a2 {
  width: 520px; height: 520px;
  bottom: -220px; right: -120px;
  background: radial-gradient(circle at 50% 50%, #14d0c4 0%, rgba(20, 208, 196, 0) 70%);
  opacity: .4;
  animation: admDriftB 26s ease-in-out infinite alternate;
}
.adm-aurora.a3 {
  width: 380px; height: 380px;
  top: 42%; left: 46%;
  background: radial-gradient(circle at 50% 50%, #6a5bff 0%, rgba(106, 91, 255, 0) 70%);
  opacity: .38;
  animation: admDriftC 19s ease-in-out infinite alternate;
}

/* 网格慢慢往上走一格：静止的网格像截图，走起来才有「系统在跑」的感觉 */
.adm-grid {
  position: absolute; inset: -40px;
  background-image:
    linear-gradient(to right, rgba(120, 170, 255, .12) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(120, 170, 255, .12) 1px, transparent 1px);
  background-size: 44px 44px;
  -webkit-mask-image: radial-gradient(ellipse 78% 62% at 42% 42%, #000 32%, transparent 82%);
  mask-image: radial-gradient(ellipse 78% 62% at 42% 42%, #000 32%, transparent 82%);
  animation: admGridFlow 26s linear infinite;
}

@keyframes admDriftA {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(90px, 60px, 0) scale(1.14); }
}
@keyframes admDriftB {
  from { transform: translate3d(0, 0, 0) scale(1.05); }
  to   { transform: translate3d(-80px, -50px, 0) scale(.92); }
}
@keyframes admDriftC {
  from { transform: translate3d(-30px, 20px, 0) scale(.96); }
  to   { transform: translate3d(40px, -30px, 0) scale(1.1); }
}
@keyframes admGridFlow {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -44px, 0); }
}

/* 品牌行 */
.adm-brand {
  display: flex; align-items: center; gap: 12px;
  animation: admRise .7s var(--ease) both;
}
.adm-brand b { display: block; font-size: 15px; letter-spacing: .04em; color: #f2f7ff; }
.adm-brand em {
  display: block; margin-top: 3px;
  font-style: normal; font-size: 10px; letter-spacing: .18em;
  color: rgba(173, 198, 235, .75);
}

/* 主标语：删掉说明文字后，用 auto 上边距把它和状态条一起压到视觉中部，
   否则左上角一堆内容、下方一大片空白，看着像没加载完 */
.adm-hero { margin-top: auto; animation: admRise .8s var(--ease) .06s both; }
.adm-hero h1 {
  margin: 0;
  font-size: clamp(26px, 2.6vw, 38px);
  line-height: 1.28;
  letter-spacing: -.01em;
  font-weight: 700;
  color: #fff;
}
/* 「一处办完」用渐变字：同一句话里分出主次，又不用加粗到失真 */
.adm-hero h1 span {
  display: inline-block;
  background: linear-gradient(96deg, #6ea8ff 0%, #35d6c6 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}

/* 状态条：三个「在跑」的信号 */
.adm-tiles { display: grid; gap: 10px; max-width: 400px; animation: admRise .8s var(--ease) .12s both; }
.adm-tile {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, .045);
  border: 1px solid rgba(140, 180, 255, .14);
  backdrop-filter: blur(6px);
  animation: admRise .7s var(--ease) both;
  animation-delay: calc(.16s + var(--i, 0) * .08s);
}
.adm-tile-t b { display: block; font-size: 13px; font-weight: 600; color: #eaf2ff; }
.adm-tile-t em {
  display: block; margin-top: 2px;
  font-style: normal; font-size: 11px; letter-spacing: .04em;
  color: rgba(166, 190, 224, .72);
  font-variant-numeric: tabular-nums;
}
/*
 * 呼吸点：光晕往外扩散，像心跳而不是闪烁的灯泡。
 *
 * 颜色由 --dot 控制，四个状态共用同一套动画：
 *   绿 = 正常 / 黄 = 需要注意 / 红 = 异常 / 灰 = 还没测出来
 */
.adm-dot {
  --dot: 53, 224, 161;
  position: relative; flex: 0 0 auto;
  width: 8px; height: 8px; border-radius: 50%;
  background: rgb(var(--dot));
  animation: admPulse 2.4s ease-out infinite;
  transition: background .25s var(--ease);
}
.adm-dot.is-checking { --dot: 146, 172, 208; animation-duration: 1.5s; }
.adm-dot.is-warn { --dot: 245, 181, 68; }
.adm-dot.is-bad { --dot: 255, 107, 107; }

/* 异常时整张卡的描边和说明文字跟着变色，让人不必去盯那个小圆点 */
.adm-tile.is-bad { border-color: rgba(255, 120, 120, .34); background: rgba(255, 96, 96, .07); }
.adm-tile.is-bad em { color: #ffb3b3; }
.adm-tile.is-warn { border-color: rgba(245, 181, 68, .3); }
.adm-tile.is-warn em { color: #ffd79a; }

/* 底部柱状图：纯装饰，给暗面一点「有数据在动」的质感 */
.adm-bars {
  /* 定高 + 不参与纵向拉伸：让它像一条数据带，而不是把左栏下半截全吃掉 */
  flex: 0 0 auto;
  height: 92px;
  margin-top: 26px;
  display: flex; align-items: flex-end; gap: 6px;
  max-width: 400px;
  animation: admRise .9s var(--ease) .2s both;
}
.adm-bar {
  flex: 1 1 0;
  height: var(--h, 50%);
  border-radius: 3px 3px 0 0;
  background: linear-gradient(180deg, rgba(126, 178, 255, .78), rgba(60, 118, 220, .06));
  transform-origin: bottom;
  animation: admBar 3.6s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -.18s);
}

/* ---------------- 右侧：登录卡 ---------------- */

.adm-main {
  background: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px;
  padding: 48px 40px;
}

.adm-card {
  width: 100%; max-width: 352px;
  animation: admRise .72s var(--ease) .1s both;
}
.adm-card-head { text-align: center; margin-bottom: 26px; }
.adm-card-head h2 { margin: 12px 0 4px; font-size: 21px; font-weight: 700; color: var(--ink); }
.adm-card-head p { margin: 0; font-size: 12.5px; color: var(--slate-500); }

/* 表单项依次浮起：同一时间全部出现会显得生硬 */
.adm-card form > * { animation: admRise .5s var(--ease) both; }
.adm-card form > *:nth-child(1) { animation-delay: .18s; }
.adm-card form > *:nth-child(2) { animation-delay: .24s; }
.adm-card form > *:nth-child(3) { animation-delay: .3s; }
.adm-card form > *:nth-child(4) { animation-delay: .36s; }

.adm-card label.field > span { font-size: 12px; color: var(--slate-500); letter-spacing: .01em; }
.adm-card input[type="text"],
.adm-card input[type="password"] {
  height: 42px; padding: 0 13px; border-radius: 10px;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease), background .16s var(--ease);
}
.adm-card input:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 4px rgba(11, 108, 240, .12);
  background: #fbfdff;
}

/* 主按钮：渐变底 + 一道扫光，按下去轻微回弹 */
.adm-card .btn.primary.block.lg {
  position: relative;
  height: 44px;
  border-radius: 10px;
  border: 0;
  margin-top: 6px;
  background: linear-gradient(135deg, #3ba5ff 0%, #0b6cf0 52%, #0a54d2 100%);
  box-shadow: 0 10px 22px -12px rgba(11, 108, 240, .85);
  overflow: hidden;
  transition: transform .12s var(--ease), box-shadow .2s var(--ease), filter .2s var(--ease);
}
.adm-card .btn.primary.block.lg::after {
  content: '';
  position: absolute; top: 0; bottom: 0; left: -60%;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .42), transparent);
  transform: skewX(-18deg);
  animation: admSheen 3.4s ease-in-out infinite;
}
.adm-card .btn.primary.block.lg:hover:not(:disabled) {
  filter: brightness(1.04);
  box-shadow: 0 14px 26px -12px rgba(11, 108, 240, .95);
}
.adm-card .btn.primary.block.lg:active:not(:disabled) { transform: scale(.988); }

/* 正在登录：文字先退场，按钮中央转圈 */
.adm-card .btn.is-busy { color: transparent !important; }
.adm-card .btn.is-busy::before {
  content: '';
  position: absolute; top: 50%; left: 50%;
  width: 17px; height: 17px; margin: -8.5px 0 0 -8.5px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .42);
  border-top-color: #fff;
  animation: admSpin .7s linear infinite;
}

/* 出错时卡片抖一下：比单纯多一行红字更容易被注意到 */
.adm-card .alert { animation: admShake .42s var(--ease); }

.adm-hint {
  margin-top: 18px;
  text-align: center;
  font-size: 11.5px;
  color: var(--slate-400);
  line-height: 1.7;
}
.adm-foot { margin: 0; font-size: 11px; letter-spacing: .08em; color: var(--slate-300); }

@keyframes admRise {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes admPulse {
  0%   { box-shadow: 0 0 0 0 rgba(var(--dot), .5); }
  70%  { box-shadow: 0 0 0 9px rgba(var(--dot), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--dot), 0); }
}
@keyframes admBar {
  0%, 100% { transform: scaleY(1); opacity: .75; }
  50%      { transform: scaleY(.55); opacity: 1; }
}
@keyframes admSheen {
  0%, 62% { left: -60%; }
  88%, 100% { left: 120%; }
}
@keyframes admSpin { to { transform: rotate(360deg); } }
@keyframes admShake {
  0%, 100% { transform: translateX(0); }
  22% { transform: translateX(-5px); }
  46% { transform: translateX(5px); }
  70% { transform: translateX(-3px); }
}

/* 窄屏：暗面收成一条头图，不占半屏 */
@media (max-width: 900px) {
  .adm-auth { grid-template-columns: minmax(0, 1fr); }
  .adm-side {
    gap: 14px;
    padding: 26px 22px 22px;
    min-height: 0;
  }
  .adm-side .adm-bars,
  .adm-side .adm-tiles { display: none; }
  .adm-hero h1 { font-size: 22px; }
  .adm-main { padding: 30px 22px 40px; }
}

/* 关掉动效：动画是装饰，不该让需要安静界面的用户难受 */
@media (prefers-reduced-motion: reduce) {
  .adm-aurora,
  .adm-grid,
  .adm-bar,
  .adm-dot,
  .adm-card .btn.primary.block.lg::after { animation: none !important; }
  .adm-brand, .adm-hero, .adm-tiles, .adm-tile, .adm-bars,
  .adm-card, .adm-card form > *, .adm-card .alert { animation: none !important; }
}
