/* 很简单进销存 —— 大白话界面：字大、按钮大、手机也能点 */
:root {
  --bg: #f5f6f8; --card: #fff; --line: #e3e6ea;
  --ink: #222; --muted: #777; --brand: #1a66d6; --danger: #d64545; --ok: #2b9e5b;
}
* { box-sizing: border-box; }
/* hidden 属性必须真能藏起来。
   `.login { display: flex }` 这类作者样式的优先级高于浏览器默认的
   `[hidden] { display: none }`，于是设了 hidden 也照样显示——登录之后
   登录卡片还端端正正摆在第一屏，人以为没登进去，又登一次。
   （2026-10-30 截图时发现的，肉眼可见，但没有一条测试会报错。） */
[hidden] { display: none !important; }
body { margin: 0; font-family: system-ui, "Microsoft YaHei", sans-serif; background: var(--bg); color: var(--ink); }

/* ---- 半透明外壳（2026-11-03）----

   老板要的是「像微信那样能看见自己的桌面」。真透明这件事，Edge/Chrome 的
   --app 窗口做不到——客户区整块由 Chromium 自己合成，Win32 的窗口透明度
   对它无效。所以走的是**跟微信同一个观感的另一条路**：把桌面壁纸铺成页面
   底色，上面盖一层半透白的膜，顶栏和左栏再加真·毛玻璃。

   看起来一样，边界不一样，这句要跟人讲清楚：透出来的是**壁纸**，不是
   挡在后面的别的窗口。桌面上一张 Excel 挡在后面，我们这边看不见它。

   三条设计上的取舍：
     1. **只覆盖 --card 这一个变量**，所有卡片、顶栏、左栏跟着一起透。
        逐个容器改颜色，改到第 30 个必然漏一个，漏的那个就是一块突兀的白板。
     2. **只有顶栏和左栏用 backdrop-filter**。卡片有几十个，每个都挂毛玻璃
        是拿显卡换观感，低端办公机上滚表格会掉帧。
     3. **表格的冻结列不跟着透**（见下面 .sticky 那条）——那一列是用来挡住
        后面滚过去的字的，半透就白挡了。

   图从 /api/desktop/backdrop 来，地址由 shell.js 设进 --desk；取不到就
   什么都不做，保持原来那块干净的浅灰。 */
body.glass {
  --card: rgba(255, 255, 255, .88);
  --line: rgba(203, 212, 224, .85);
  /* 压在壁纸上的那层白有多浓。**这个数不是调的，是判出来的**：
     屏幕上打底的是深色壁纸时，界面这堆深灰字直接糊在里头。所以 shell.js
     会把壁纸缩成 32×18 算平均亮度，深底就把膜加到 .78，浅底只给 .44。
     不这么干的话，同一个界面在办公室那台浅色壁纸的机器上好看，在车间
     那台深色壁纸的机器上是"看不见字"。 */
  --veil: .26;
  background: var(--bg);
}
body.glass[data-desk="dark"] { --veil: .60; }
/* 壁纸自己要先压淡，而且必须做在**单独一层伪元素**上。

   第一版直接把壁纸写进 body 的 background，结果一屏红岩底色跟表格里的
   红字打成一团——客户第一眼不是"好看"，是"看不清"。压淡分两件事：一层
   白膜降对比（下面这条 gradient），一层轻模糊去掉细节（filter）。

   模糊只给 4px，**不能更多**：糊到 30px 时背景成了一块渐变色的板子，
   跟桌面已经没关系了，客户的原话会是"我桌面呢"。要的是"认出这是我的桌面，
   但它退到后面去了"，不是"换成一张好看的渐变"。

   这层的膜**故意做得很薄**（--veil .26）。它只负责把照片降成"色块"，
   真正挡字的那层白在内容区（见下面 .pagewrap），不在这儿。一开始把厚膜
   铺满整屏，结果顶栏和左栏叠了两层白，透出来跟纯白没区别——微信那种
   "边上透、中间实"正好是反过来的做法。

   inset -40px 是为了吃掉模糊边缘：blur 会把四周往外摊成半透明，贴边铺
   会露出一圈发白的描边。 */
body.glass::before {
  content: ""; position: fixed; inset: -40px; z-index: 0;
  background:
    linear-gradient(rgba(250, 251, 253, var(--veil)),
                    rgba(250, 251, 253, calc(var(--veil) + .10))),
    var(--desk) center / cover no-repeat;
  filter: blur(4px) saturate(1.1);
  pointer-events: none;
}
/* 内容区再压一层：表格里全是小数字，跟照片直接叠在一起是看不清的。
   只压中间这一块，边上那圈留给壁纸透——这就是"微信"那个分寸。 */
body.glass .pagewrap {
  background: linear-gradient(rgba(250, 251, 253, .52), rgba(250, 251, 253, .58));
}
body.glass[data-desk="dark"] .pagewrap {
  background: linear-gradient(rgba(250, 251, 253, .62), rgba(250, 251, 253, .66));
}
/* 上面那层糊好的底要压在真内容后面。

   **`#loginView` 不能漏**：登录页和主界面是 body 的两个平级孩子，只给
   `#mainView` 提层的话，登录页整张卡片会被伪元素盖住——2026-11-03 第一版
   就是这样，460 那个小窗打开只剩一个密码框孤零零浮在照片上，标题、
   手机号输入、登录按钮全不见。这种错在截图上非常明显，但没人会去看
   登录页——上线后客户第一眼就是它。 */
body.glass #loginView,
body.glass #mainView { position: relative; z-index: 1; }
/* ---- 真玻璃（2026-11-03 晚）----

   只有跑在**自己开的窗口**里才有意义（URL 带 `?rg=1`，见 shell.js）。
   那种窗口底下才是 DWM 真磨砂：压在该窗口后面的别的软件、桌面图标，
   都会糊着透上来——这才是老板要的"半透看到后面那层东西"。

   所以上面那套假底（壁纸 + 白膜）必须**整层关掉**。不关就是两层：
   壁纸把磨砂盖住，观感退回到"贴了一张图"，而且后面压着的窗口永远看不见。

   留着的只有 `--card` 那一个变量：卡片、顶栏、左栏一起半透。这条是原设计
   里唯一可复用的东西——逐个容器改颜色，改到第 30 个必然漏一个。 */
body.glass.real { background: transparent; }
body.glass.real::before { display: none; }
body.glass.real .pagewrap { background: transparent; }
/* ---- 失焦补雾（2026-11-03 深夜）----

   亚克力在窗口失焦后会被 DWM 整块降级成**不透明浅灰**。实测数据（玻璃窗
   后面压一块纯蓝 32,64,160）：激活时背景量到 (137,158,221)，蓝色透上来了；
   失焦后量到 (211,211,211)，一点蓝成分都没有——不是雾淡了，是材质被关掉了。
   Win11 的开始菜单、通知中心失焦也一样变实，这条没有开关。

   既然拦不住，就顺着它：铺一层很淡的雾，把这块灰提成"我们的浅色底"。
   211 经 α=.72 的白雾 → 约 238，接近 Win11 原生 Mica 失焦的观感，切换读起来
   是"暗下来了"而不是"掉色了"。

   雾色带一点冷调（248,250,253）而不是纯白：纯白会让这块灰变成一张白纸，
   跟周围留白的卡片糊在一起，分不清哪是窗口哪是内容。

   状态由壳来打（glass.py 的 WM_ACTIVATE → html.win-blur），不靠浏览器自己的
   focus/blur 事件——那个在 WebView2 里可靠不可靠，得另外验，没必要。
   ::after 一直存在只改 opacity，所以切换有过渡；一直 display:none 再显示
   的话是硬切。 */
body.glass.real::after {
  content: ""; position: fixed; inset: 0; z-index: 0;
  background: rgba(248, 250, 253, .72);
  opacity: 0; pointer-events: none;
  transition: opacity .16s linear;
}
html.win-blur body.glass.real::after { opacity: 1; }

button { font: inherit; padding: 8px 14px; border: 1px solid var(--line); border-radius: 8px; background: #fff; cursor: pointer; }
button.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
button.danger { color: var(--danger); border-color: var(--danger); }
button.ghost { border: none; background: none; color: var(--muted); }
button.big { width: 100%; padding: 12px; font-size: 17px; }

/* 按钮的"手上有感觉"三件套：滑上去变色、按下去下沉、松手回弹。
   2026-11-02 老板原话："颜色定死，鼠标滑倒客户点击是直接弹窗。没有过渡"。
   原来全场按钮既没有 hover 也没有 active——鼠标滑上去纹丝不动，点一下弹窗
   哐当就盖上来，手感像点在一张图上，不像点在能按的东西上。
   0.12s 是"能感觉到"和"不拖沓"之间的位置；再慢就变成卡顿。
   `:not(.linkbtn)` 是必须的：表格里那种文字按钮（作废/明细）自带边框色，
   被这里刷成灰框会看着像"整行都能点"。 */
button { transition: background-color .12s ease, border-color .12s ease, color .12s ease, transform .06s ease; }
button:hover:not(:disabled):not(.linkbtn) { background: #f7f9fc; border-color: #c9d2dd; }
button:active:not(:disabled) { transform: translateY(1px); }
button.primary:hover:not(:disabled) { background: #1557b8; border-color: #1557b8; }
button.danger:hover:not(:disabled) { background: #fdf1f1; }
button.ghost:hover:not(:disabled) { background: #f2f4f7; color: var(--ink); }

/* 描边按钮：两个同等重要的动作并排时用（加供应商 / 加客户）。
   为什么不用 primary：两个蓝块并排会互相抢，而这一页最主要的动作是看名单，
   不是加人。为什么也不用默认白底：老板圈的就是这一处——一个蓝一个白，
   在新账套（客户 0 家）里看着像"只有供应商能加"。描边用品牌色，
   两个一样重、都看得见、又不抢上面那份名单。 */
button.tint { border-color: var(--brand); color: var(--brand); background: #fff; }
button.tint:hover:not(:disabled) { background: #eef3ff; border-color: var(--brand); }
input, select, textarea { font: inherit; padding: 8px; border: 1px solid var(--line); border-radius: 8px; width: 100%; }
/* 锁住的下拉要看得出来锁住了。不然它跟能点的一模一样，人点两下没反应，
   只会以为软件坏了，看不到底下那句"为什么锁"的说明。 */
input:disabled, select:disabled { background: #f2f2f2; color: #8a8a8a; cursor: not-allowed; }
.muted { color: var(--muted); }

/* 二维码（qrlib.js 生成的 svg 没有 width/height 属性）。
   不撑满的话它会按 SVG 的默认尺寸 300x150 显示，把卡片顶变形。
   扫码页和打印标签页用的是同一个生成器，所以这条两边都管用。 */
.qrbox svg { width: 100%; height: 100%; display: block; }
.err { color: var(--danger); }
.ok { color: var(--ok); }

/* 登录 */
.login { display: flex; min-height: 100vh; align-items: center; justify-content: center; }
.login-card { background: var(--card); padding: 32px; border-radius: 14px; width: 372px; display: grid; gap: 12px; box-shadow: 0 4px 20px rgba(0,0,0,.06); }
.login-card h1 { margin: 0; text-align: center; }

/* 顶栏 */
.banner { padding: 8px 14px; background: #fff7e0; color: #8a6d00; font-size: 14px; }
.banner.err { background: #fdeaea; color: #a12525; }
/* 横幅里的动作：只给一句「有事」不给出路，人只能干着急 */
.banner .linkbtn { margin-left: 8px; padding: 2px 10px; font-size: 13px; }
.linkbtn { background: none; border: 1px solid currentColor; color: inherit; border-radius: 8px; cursor: pointer; }
.linkbtn[disabled] { opacity: .55; cursor: default; }
/* 同步清单：一条一块，手机上不挤 */
.syncrow { border: 1px solid var(--line); border-radius: 10px; padding: 10px; display: grid; gap: 6px; }
.syncrow .h { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.syncrow .why { color: var(--danger); font-size: 13px; }
.syncrow .pick { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.syncrow .pick select { flex: 1; min-width: 160px; }

/* 打样：一条一块，不用表格。
   7 列的表格在 390 宽的手机上会挤成「一个字一行」——客户名竖着排成四行，
   按钮摞成两行，看着像坏了。（2026-10-30 截图确认过，不是猜的。） */
.itemlist { display: grid; gap: 8px; margin: 8px 0; }
.itemrow { border: 1px solid var(--line); border-radius: 10px; padding: 10px; display: grid; gap: 5px; }
.itemrow .h { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.itemrow .acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
.itemrow .acts button { flex: 0 0 auto; }
/* 作废的单还在列表里（要留痕），但整块淡下去，别跟正常单混在一起看 */
.itemrow.voidrow { opacity: .55; }
/* 表单：标签在小字上、输入框占满一行，手机上不用瞄着猜哪格填什么 */
.formgrid { display: grid; gap: 10px; margin: 8px 0; }
.formgrid .row { margin: 0; }
.fld { display: grid; gap: 4px; flex: 1; min-width: 140px; }
.fld > label { font-size: 12.5px; }
.tabs { display: flex; gap: 6px; padding: 8px 10px; background: var(--card); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 5; flex-wrap: wrap; }
.tabs button.on { background: var(--brand); color: #fff; border-color: var(--brand); }
/* 顶栏这排页签的 hover（2026-11-02 老板："菜单键颜色固定，鼠标移动没有变色提示"）。
   根因不是"没写 hover"，是通用那条只给到 #f7f9fc —— 几乎就是白色，贴在白色顶栏上
   看不出来，等于没有。这里给足对比：淡品牌底 + 品牌字。
   当前页（.on，蓝底白字）滑上去只许更深一档，**不许被通用规则刷成浅灰**
   （通用规则是 3 段选择器，会压过 .tabs button.on 的 2 段，滑一下当前页就变白）。
   所以这两条都写成 4 段，专门用来压过它。 */
.tabs button:not(.on):hover:not(:disabled) {
  background: #eef3ff; border-color: var(--brand); color: var(--brand); }
.tabs button.on:hover:not(:disabled) {
  background: #1557b8; border-color: #1557b8; color: #fff; }
.tabs .spacer { flex: 1; }
/* 顶栏那个版本号（2026-11-02）。刻意做小、做灰：它是给报错时用的，不是给人看的招牌。
   数字走 tabular-nums，免得 2026.11.07 和 2026.11.10 宽度不一样、把旁边的按钮挤动。 */
.vertag { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums;
  padding: 0 6px; align-self: center; cursor: pointer; user-select: none;
  border-bottom: 1px dashed transparent; }
.vertag:hover { color: var(--ink); border-bottom-color: var(--line); }
/* 「别填金额和客户名」这类提醒。不用红字：红字在这套界面里是「出错了」，
   而这句话是「填之前先看一眼」，是提醒不是报错——标红会让人以为已经出问题。 */
.hint-warn { background: #fff8e6; border: 1px solid #f0dfb0; color: #7a5c00;
  font-size: 12.5px; line-height: 1.55; border-radius: 8px; padding: 7px 10px; margin: 0 0 10px; }
/* 「全部功能」：藏起来的页签从这儿进去，不能让人找不着（铁律第二条） */
.tabs button.more { color: var(--muted); border-style: dashed; }
.morelist { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.moreitem { display: flex; justify-content: space-between; align-items: center; width: 100%; text-align: left; }

/* 860 是当初按「登录框一样窄的小窗」定的。2026-11-03 窗口改成按工作区开
   （见 app.js 的 mainWindowSize），再卡 860 就变成两边空出两指宽的白边，
   窗口大了、内容没大，看着更空。放到 960，配大屏那一档（shell.css 1500 断点）。 */
main { max-width: 960px; margin: 0 auto; padding: 12px; display: grid; gap: 12px; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; }
.card h3 { margin: 0 0 8px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.row > * { flex: 1; min-width: 120px; }
/* 「选个月 → 关账」这种一行只有一个小输入 + 一个按钮的，别让它们平分整张卡。
   平分的结果是 40px 高的输入框拉成 265px、按钮拉成 277px 的大蓝块——
   2026-11-02 老板截图里「关账」「保 存」那两块蓝就是这么来的。
   只在这一类行上收，不动 .row 的全局规则（那 90 多处靠它排版）。 */
.row.tightrow > input { flex: 0 0 auto; width: 172px; min-width: 0; }
.row.tightrow > button,
.row.tightrow > span { flex: 0 0 auto; min-width: 0; }
@media (max-width: 640px) { .row.tightrow > input { width: 150px; } }
/* 单据页的「从…到…」。跟 .row 不一样，这里不许换行：换行会把「到」和
   第二个日期拆到下一行，看着像两组日子。所以标签不给最小宽度、
   两个日期框平分剩下的地方（390 宽下各约 130px，原生日期框刚好够显示）。 */
.daterow { display: flex; gap: 6px; align-items: center; flex-wrap: nowrap; margin-top: 8px; }
.daterow[hidden] { display: none; }   /* 本身写了 display:flex，hidden 会被压掉 */
.daterow > label { flex: 0 0 auto; min-width: 0; color: var(--muted); font-size: 13px; }
.daterow > input { flex: 1 1 0; min-width: 0; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-weight: normal; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.tag { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 12px; background: #eef; }
/* 首页「② 今天的单据」那三块：原来是 .row 里的 span，被 `.row > * { flex:1 }`
   拉成三条等宽长条——「入库 3 张」四个字左边一杵，右边一大片空底色，
   看着像断了的三根色条而不是三个数（2026-11-02 老板截图圈的就是这行）。
   现在它们是**按钮**：浅色底不变，按下去颜色变深，点一下直接跳到那一类单据。 */
.pillrow { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
button.tag { border: 0; cursor: pointer; font: inherit; font-size: 13px; padding: 4px 12px;
  transition: background .12s, color .12s, transform .06s; }
button.tag b { font-size: 15px; font-variant-numeric: tabular-nums; }
button.tag:hover:not(:disabled) { filter: brightness(.96); }
button.tag:active:not(:disabled) { transform: translateY(1px); filter: brightness(.9); }
button.tag.mat:hover:not(:disabled) { background: #d7e4fb; }
button.tag.fg:hover:not(:disabled) { background: #d3f0de; }
button.tag.tool:hover:not(:disabled) { background: #fbe0cf; }
/* 卡片标题右边的数量徽章：一眼看出"今天有几件要动手"，浅蓝底不抢内容 */
.badge { display: inline-block; margin-left: 8px; padding: 1px 9px; border-radius: 10px;
  background: #e8f0fe; color: #1a56c6; font-size: 12px; font-weight: 600; vertical-align: 2px; }
/* ---- 定高列表 + 搜索胶囊（2026-11-03 老板要求）----

   原话：「这种不能无限向下加」「不能让下面的功能溢出找不到」。

   现在每一页都是把整张表铺开：首页那张「该补料的」一长，下面「今天的单据」
   「把数据带走」就被顶到几屏以外——客户找不到，而不是看不到。

   一条规则，所有列表都套，别一页一页去调：
     .cardhead   卡片头：标题 + 条数徽章 + 搜索胶囊，永远一行
     .lst        卡片体：高度封顶，自己滚
     .more       卡片底：「查看全部 N 条」的出口

   封顶用 vh 不是写死 10 行的像素：行高不统一（首页约 40px、物料列表 62px），
   写死像素等于每页单独调一次，迟早有一页漏。42vh 的分寸是"下面那张卡的
   标题还露得出来"——看不见下一张卡，客户会以为这页就这些。 */
.lst {
  --lst-h: 42vh;
  max-height: var(--lst-h, 42vh);
  overflow-y: auto;
  /* 滚到底不要带着整页一起滚：那种"我明明只在翻这张表，怎么整页跑了"
     是最容易让人以为自己点错东西的。 */
  overscroll-behavior: contain;
}
/* 表头跟着滚上去之后，第二屏开始就不知道每列是什么数字了。
   sticky 必须给底色，不然下面的行从字缝里透上来。
   表格大多是 `<tr><th>` 直接写、没有 thead，所以两个都选上。 */
.lst thead th,
.lst tr:first-child th {
  position: sticky; top: 0; z-index: 1;
  background: var(--card);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.cardhead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 8px; }
.cardhead > h3 { margin: 0; flex: 1 1 auto; min-width: 0; }

/* 搜索框外面那一圈色环（老板：「像胶囊又不是胶囊」）。
   圆角只给 12px，不给 999——全圆是手机搜索框，这个要的是"输入框外面
   还套了一层东西"。色环用 box-shadow 而不是 border：border 会挤掉
   输入区宽度，聚焦变色时整行会抖一下。 */
.sbox {
  display: flex; align-items: center; gap: 6px;
  flex: 0 1 240px; min-width: 150px;
  padding: 0 10px 0 12px;
  /* 底不是纯白：卡片本来就是白的，纯白 + 细边在浅色界面上等于没框。
     给一点冷白，框自己才浮得出来。 */
  background: #fbfcff;
  border: 1px solid #d5dcea;
  border-radius: 14px;
  box-shadow: 0 0 0 3px rgba(26, 102, 214, .12);
  transition: box-shadow .16s ease, border-color .16s ease, background .16s ease;
}
.sbox:focus-within {
  background: #fff;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px rgba(26, 102, 214, .20);
}
.sbox > input { flex: 1 1 auto; min-width: 0; border: 0; background: none; padding: 7px 0; font-size: 13.5px; }
.sbox > input:focus { outline: 0; }
.sbox > input::placeholder { color: #9aa4b2; }
.sbox > .sico { color: var(--muted); display: grid; place-items: center; flex: 0 0 auto; }
/* 放进 .row 里的时候要压过 `.row > * { flex: 1 }`：
   不压的话输入区被拉满整行，右边那个「近 30 张」被顶到下一行去。 */
.row > .sbox { flex: 1 1 240px; min-width: 150px; }
@media (max-width: 640px) { .sbox { flex: 1 1 100%; } }

.more { margin: 10px 0 0; }

/* ---- 上下滚动的提示条（2026-11-03 老板：「很空，加些安全提示、备份、操作等，
   多做一些提示上下滚动的方式做提示」）----

   一条一条往上滚，鼠标移上去停住——客户正在读的那条不该被抢走。
   高度写死 40px，滚的是 transform：改高度会让整张卡跟着抖。 */
.tips { height: 40px; overflow: hidden; margin: 0 0 10px; }
.tips-run { transition: transform .5s cubic-bezier(.4, 0, .2, 1); }
.tips-line {
  height: 40px; display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tips-line b { color: var(--ink); font-weight: 500; }
.tips-line::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand); opacity: .5; flex: 0 0 auto;
}

/* 自动备份那个开关。用 checkbox + 自己画的滑轨，不用 <input type=checkbox>
   原生那个小方框——这一页就这一个开关，原生方框在中文界面里像"打勾问卷"。 */
.sw { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font-size: 14px; flex: 0 0 auto; }
.sw > input { position: absolute; width: 0; height: 0; opacity: 0; }
.sw .track {
  width: 42px; height: 23px; border-radius: 12px; background: #cfd6df;
  position: relative; transition: background .18s; flex: 0 0 auto;
}
.sw .track::after {
  content: ""; position: absolute; left: 2px; top: 2px; width: 19px; height: 19px;
  border-radius: 50%; background: #fff; transition: transform .18s;
  box-shadow: 0 1px 3px rgba(16, 26, 40, .22);
}
.sw > input:checked + .track { background: var(--brand); }
.sw > input:checked + .track::after { transform: translateX(19px); }
.sw > input:focus-visible + .track { box-shadow: 0 0 0 3px rgba(26, 102, 214, .22); }

.tag.mat { background: #e8f0fe; color: #1a56c6; }
.tag.fg { background: #e6f6ec; color: #1e7a45; }
.tag.tool { background: #fdeee6; color: #b45309; }
.tag.void { background: #f2f2f2; color: #999; text-decoration: line-through; }
.tag.supplier { background: #f3e9fd; color: #6b21a8; }
.tag.customer { background: #e6f6ec; color: #1e7a45; }
/* 「类型标签 + 名字」的标题。标签一律排最前，且跟着文字流走——
   原来是「名字 + 标签」，名字一长（「温州皮革（龙湾分厂）」这类）在 390 宽里
   标签就被推到第二行末尾甚至屏幕外，老板的原话：「谁也看不到是供应商还是客户」。
   不用 flex：flex 在窄屏会把标签挤成单独一行，看着像飘在上面。
   走文字流最省事——标签占第一行开头，名字接着排，换行后从行首开始。 */
.ttl { display: block; }
.ttl > .tag { vertical-align: middle; margin-right: 6px; }
.ttl > .nm { overflow-wrap: anywhere; }
.low { color: var(--danger); font-weight: bold; }
.todo-big { font-size: 26px; font-weight: bold; }

/* 手机上表格不许硬挤。
   `table { width: 100% }` 在 390 宽、7 列的时候，每列只剩五十来像素——
   「老王皮业」被挤成一个字一行、按钮摞成两层，看着像坏了（2026-10-30 逐页
   截图确认过，工单/工具两页都是）。所以手机上给表格一个最小宽度，超出的部分
   在卡片里横向滑。滑一下总比一个字一行好读。
   kv 表（单据详情那种两列竖排的）不算，它本来就该窄。 */
@media (max-width: 640px) {
  .card, dialog #dlgBody { overflow-x: auto; }
  /* 不给表格写死最小宽度：写死会让本来就窄的表也被撑出去，白滑一屏。
     `white-space: nowrap` 让表格按内容自然变宽，装不下才滑。 */
  .card th, .card td, #dlgBody th, #dlgBody td { white-space: nowrap; }
  .card th, .card td { padding: 6px 6px; }
  /* 最后一列基本都是按钮（结单/作废/继续盘/收款）。表格能横滑以后它会滑出屏幕——
     按钮看不见等于没有。把它钉在卡片右边缘，横向滑的时候它不动。
     两处细节：①要给 z-index，否则别的单元格会盖在它上面
              ②卡片有 14px 右内边距，内容会从那儿漏出来，用一块同色影子盖住 */
  .card table:not(.kv) th:last-child,
  .card table:not(.kv) td:last-child {
    position: sticky; right: 0; z-index: 2; background: var(--card);
    box-shadow: -6px 0 6px -6px rgba(0,0,0,.18), 16px 0 0 var(--card);
  }
  /* 手机上先别给最不重要的那几列留地方：备注、开单人这类滑过去才看得到，
     不如干脆收起来，让关键几列不用滑就看全。 */
  .hide-sm { display: none !important; }
  /* 表单里的长标签别再跟输入框挤一行：390 宽一行装不下十几个字，
     硬挤的结果是标签被拦腰截成两行（「业务日期（补录以前／的单才填」），
     看着像软件坏了。让标签独占一行、输入框在下面占满——
     两行都是整整齐齐的字，不是一行断成两截。桌面（>640px）不受影响。 */
  .row > label { flex: 1 1 100%; }
  /* 附件那排控件：「拍 照」独占一行（手指要按得中，也是手机上最常用的那个），
     下面「相册/文件」和「上 传」并排。不这么摆，几个挤在 354px 里会换行成
     "拍照 / 相册" 一行、"上传" 单独一行——上传跟选文件分了家，看着别扭。
     原生的「选择文件 未选择文件」在手机上是灰小字、还会被截成「未…件」，
     所以手机上换成按钮（见 app.js 里的 #filePickBtn）。 */
  #fileCamBtn { flex: 1 1 100%; }
}

/* 平板：触摸屏 + 比手机断点宽。
   断点只有 640，平板全走桌面规则，于是出现"鼠标版界面 + 手指"：
   实测列表按钮 62×38px、表内小按钮 42×34px，都低于触摸目标下限。
   点在「作废」这种不可逆的地方，点偏的代价不是重试一次。
   44px 是 Apple / Google 都给的触摸目标建议值。

   **为什么不管手机（≤640）**：实测手机上的按钮也是 38px，一样偏小。
   但手机是双手捧着、离眼近、屏幕就那么大，把它撑到 44 会挤掉本来就不多的可视行数；
   平板常常单手拎着、离得远、还在走动，才是真的需要大目标。
   电脑（`pointer: fine`）不受影响；接鼠标的折叠屏自动回到鼠标规则。 */
@media (pointer: coarse) and (min-width: 641px) {
  button { min-height: 44px; padding: 10px 16px; }
  /* ghost 原本是 `padding: 8px 14px`——照抄成 10px 12px 会把「作废」这类
     无边框按钮压窄 4px，反而更难点了。水平给回 14px。 */
  button.ghost { padding: 10px 14px; }
  input, select, textarea { min-height: 44px; }
  /* 行高跟着加一点：手指落在两行中间时，别一下点到隔壁那张单上 */
  th, td { padding: 9px 8px; }
  /* 盘点「实数」格：桌面是 88px（内联写的），平板上给到 96px
     ——仓库里是一列竖着一个个填，戴手套时格子太窄容易填进上一行 */
  .ctReal { min-width: 96px; }
}

dialog { border: none; border-radius: 12px; padding: 0; width: min(720px, 94vw); }
/* 弹窗自己不画焦点黑框。showModal 后焦点落在 dialog 上，Chrome 给它描一圈
   outline——原本点一下遮罩就关掉了看不见，现在弹窗留着，这圈黑边就一直挂着，
   看着像"弹窗坏了/漏了个框"。真正的输入框和按钮仍有各自的焦点样式。 */
dialog:focus, dialog:focus-visible { outline: none; }
dialog::backdrop { background: rgba(0,0,0,.35); }
/* 弹窗别"啪"地砸出来。0.16s 的轻微下沉 + 淡入，够让人看清"是从这一下点出来的"，
   又不至于要等——老板说的"没有过渡"就是这个。时长跟按钮 hover 同一档
   （0.12~0.16s），一整套动作才像一个东西，不会有的快有的慢。
   遮罩慢一点点（0.2s）压住底色，正文先清楚，眼睛落在字上而不是落在暗下去那一层。 */
@keyframes dlgIn { from { opacity: 0; transform: translateY(-8px) scale(.985); } }
@keyframes veilIn { from { opacity: 0; } }
dialog[open] { animation: dlgIn .16s ease-out; }
dialog[open]::backdrop { animation: veilIn .2s ease-out; }
/* 老浏览器没有 showModal()（Safari 15.4 以下、旧 Android WebView）。
   openDlg 会给它加上 .noModal 并手工写 open 属性——这里把它摆成普通浮层：
   居中的白框 + 自己画一层遮罩（用 box-shadow 的大面积投影，不额外插元素）。
   做不到"点了遮罩外才关"那种模态行为（那要重写焦点管理），只保证**看得见、点得着**。 */
dialog.noModal {
  position: fixed; inset: 0; margin: auto; z-index: 50;
  height: fit-content; max-height: 88vh; overflow: auto;
  box-shadow: 0 0 0 100vmax rgba(0,0,0,.35), 0 12px 32px rgba(0,0,0,.2);
}
#dlgBody { padding: 18px; display: grid; gap: 10px; }
/* 弹窗里标题自带上下 margin，叠上 grid 的 gap 能撑出二十来像素的空白。
   手机上这半屏空白很显眼——看着像内容没加载出来。收成紧贴下面那行。 */
#dlgBody h3 { margin: 2px 0 0; font-size: 16px; }

/* 单上的话与图纸 */
h4 { margin: 8px 0 0; font-size: 15px; }
h4 .muted { font-weight: normal; font-size: 12px; }
/* 左列标签**不许折行**：原来写死 92px，「＋ 这个月买进」正好差几个像素，
   被挤成「＋ 这个月买 / 进」两行——一张算式表从中间断开，读起来像两件事
   （2026-11-02 老板截图圈的就是这处）。改成按内容收：width:1% 让浏览器
   取最小内容宽，nowrap 保证一行。手机上标签总共也就 91px，仍放得下。 */
table.kv th { width: 1%; white-space: nowrap; color: var(--muted); font-weight: normal; }
/* 数字等宽：不上等宽时 1 和 8 占的宽度不同，同一列上下对不齐。 */
/* `white-space` 必须显式放回 normal：上面 `.card td` 是 nowrap（给数据表用的），
   月结这张两列说明表的右列常常是一句话——多单位时是「148 个 · 3 公斤 · 1,000 条」
   这种长串，跟着 nowrap 会被裁到卡片外（2026-11-02 实测 390 宽：右半边整段不见）。 */
table.kv td { font-variant-numeric: tabular-nums; white-space: normal; }
/* 右列一个数就是一个跳转，下划线穿过「1,000 条 · 22 片」这种长串只会更脏；
   鼠标移上去再给下划线，仍然看得出能点。 */
table.kv td a { text-decoration: none; }
table.kv td a:hover { text-decoration: underline; }
table.kv tr:last-child th, table.kv tr:last-child td { border-bottom: none; }
.notes { display: grid; gap: 8px; max-height: 240px; overflow: auto; }
.note { background: #f7f8fa; border-radius: 8px; padding: 8px 10px; }
.note .who { font-size: 12px; color: var(--muted); margin-bottom: 2px; }
.note .what { white-space: pre-wrap; word-break: break-word; }
textarea { resize: vertical; }
.preview { max-width: 100%; border-radius: 8px; display: block; margin: 0 auto; }
.previewFrame { width: 100%; height: 62vh; border: 1px solid var(--line); border-radius: 8px; }
.fileTable td { font-size: 13px; }
.talkMark { white-space: nowrap; font-size: 13px; }
input[type=file] { padding: 6px; }

/* 选料框：输入即搜（2026-11-02）。原生 <select> 客户 200 个料就是 200 项、
   不能输入，一天一百单每单都要滚。 */
.mpick { position: relative; flex: 1; min-width: 120px; }
.mp-list { position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 2px);
  background: #fff; border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,.10); max-height: 260px; overflow: auto; }
.mp-i { padding: 8px 10px; cursor: pointer; display: flex; gap: 6px;
  justify-content: space-between; align-items: baseline; }
.mp-i.on, .mp-i:hover { background: #eef3ff; }
.mp-i b { font-weight: 600; }
.mp-i .muted { font-size: 12px; white-space: nowrap; }

/* 纸型兜底（T-048，2026-11-02）。
   以前全仓没有 @page，浏览器一律按默认 A4 排；小厂打送货单十有八九是针式打印机 +
   241×140 三联单，A4 的版式套上去字会跑到纸外。这一条**只是兜底**：真正换纸型是在
   打印那一刻由 `printOpen()` 塞 <style>——静态 CSS 改不了已经打开那页的 @page。
   12mm 边距：正文按 178mm 排，A4 可印区 186mm，两边还各余 4mm。 */
@page { size: A4; margin: 12mm }
/* 打印领用单 */
@media print {
  body * { visibility: hidden; }
  #printArea, #printArea * { visibility: visible; }
  #printArea { position: absolute; left: 0; top: 0; width: 100%; }
}
/* 打印件统一版式（2026-11-01）
   四张纸：入库单 / 出库单 / 盘点表 / 工具领用单。各不相同的地方只有表头和列，
   边框、字号、抬头、页脚全一样——一张纸上有、一张没有，客户会觉得漏印了。
   尺寸按 A4 来：正文 178mm 宽，10mm 页边距下正好放得下，不会被切掉右列。 */
#printArea .ticket { width: 84mm; margin: 0 auto; font-size: 13px; color: #000; }
/* A4 那两张（入库/出库/盘点）撑满一页高，页脚靠 margin-top:auto 压到纸底：
   版权行跟着正文飘在半页中间，看着像没打完；压到底才像一张正经单据，
   而且上面后面再加行也不会把它顶到第二页去。 */
#printArea .ticket.a4, #printArea .ticket.wide {
  width: 178mm; min-height: 252mm; display: flex; flex-direction: column;
  font-size: 12px; }
/* 241×140 三联单（T-048）。纸一边窄一边矮：A4 那套 178mm 宽 / 252mm 高套上去，
   横向空掉三分一，纵向直接翻成两页——三张单打出来是三张废纸。
   宽 220mm（可印区 229mm，两边各余 4.5mm）、不限高（有几分行就打几行）、字号降 1px。
   这几条必须盖在 `.ticket.a4` 后面：选择器权重比它高，谁先谁后都不怕，但排在后面更好读。 */
#printArea .ticket.a4.n241 { width: 220mm; min-height: auto; font-size: 11px; }
#printArea .ticket.a4.n241 .ph-title { font-size: 16px; letter-spacing: 3px; }
#printArea .ticket.a4.n241 .sign { margin-top: 6mm; margin-bottom: 3mm; }
#printArea .ticket.a4.n241 .print-foot { padding-top: 2mm; }
/* 一等分（241×280）：整张纸，比 A4 宽 6 厘米、矮 1.7 厘米。把宽度用起来，
   明细列宽一点，货名长的时候不用看它折行。 */
#printArea .ticket.a4.n241-1 { width: 225mm; }
/* 三等分（241×93）：只有巴掌高。页头、明细、签字、版权全展开一定翻第二页，
   第二页往往是空纸——客户按联数买纸，多出来的那一张就是白花钱。
   这一档整体收一号字、行距压紧，版权那两行留最小的字号：它是给查验用的，
   不是为了好看。 */
#printArea .ticket.a4.n241-3 { font-size: 11px; }
#printArea .ticket.a4.n241-3 .ph { padding-bottom: 2px; margin-bottom: 3px; }
#printArea .ticket.a4.n241-3 .ph-co { font-size: 12.5px; letter-spacing: 0; }
#printArea .ticket.a4.n241-3 .ph-title { font-size: 17px; letter-spacing: 4px; margin-top: 1px; }
#printArea .ticket.a4.n241-3 .meta { margin-bottom: 3px; }
#printArea .ticket.a4.n241-3 .meta th,
#printArea .ticket.a4.n241-3 .meta td { padding: 1px 4px; }
#printArea .ticket.a4.n241-3 .grid th,
#printArea .ticket.a4.n241-3 .grid td { padding: 2px 4px; }
#printArea .ticket.a4.n241-3 .sign { margin: 3mm 0 1mm; }
#printArea .ticket.a4.n241-3 .print-foot { border-top: none; padding-top: 0; margin-top: 2mm; }
#printArea .ticket.a4.n241-3 .print-sign { font-size: 7px; line-height: 1.35; }

/* 抬头（2026-11-02 老板：标题要居中、字太小）。
   原来厂名靠左、单据名靠右，客户拿到手的单子像一张「内部表格截图」；
   正式单据都是居中的——厂名一行、单据名一行，底下一条粗线。
   字号也跟着提了一档：A4 上 19px 的单据名，放在一叠纸里要凑近才认得出来。 */
#printArea .ph { text-align: center; border-bottom: 2px solid #000;
  padding-bottom: 6px; margin-bottom: 8px; }
#printArea .ph-co { font-size: 18px; font-weight: 700; letter-spacing: 1px; }
#printArea .ph-r { text-align: center; }
#printArea .ph-title { font-size: 26px; font-weight: 700; letter-spacing: 10px;
  margin-top: 4px; }
/* 84mm 那张（工具领用单）载不下横排抬头：厂名 16 个字横着排到 84mm 外，
   标题被挤成两行还压在右上角，看着像没排版。窄纸整体收小一档。 */
#printArea .ticket:not(.a4):not(.wide) .ph-co { font-size: 11.5px; font-weight: 400; }
#printArea .ticket:not(.a4):not(.wide) .ph-title { font-size: 16px; letter-spacing: 3px; }
#printArea .ticket:not(.a4):not(.wide) .meta td { width: auto; }
#printArea .ph-no { font-size: 11px; color: #333; margin-top: 2px; }
#printArea .sub { margin: 6px 0; font-size: 11.5px; }

/* 单据信息：单号/日期/对方/经手人，两列或多列，细框 */
#printArea .meta { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
#printArea .meta th, #printArea .meta td { border: 1px solid #999; padding: 3px 5px; }
#printArea .meta th { width: 18mm; background: #f3f3f3; color: #333; font-weight: 400;
  text-align: left; white-space: nowrap; }
#printArea .meta td { width: 62mm; }

/* 明细表：实线框，方便对着纸质单核对；数字一律右对齐、等宽 */
#printArea .grid { width: 100%; border-collapse: collapse; }
#printArea .grid th, #printArea .grid td { border: 1px solid #333; padding: 4px 5px; }
#printArea .grid th { background: #eee; font-weight: 700; text-align: center; }
#printArea .grid td.c { text-align: center; }
#printArea .grid td.n { text-align: right; font-variant-numeric: tabular-nums; }
#printArea .grid tr.sum td { font-weight: 700; background: #f7f7f7; }
/* 盘点表要车间拿笔填：留够高度，别让人把字挤在缝里 */
#printArea .grid td.fill { height: 10mm; }
/* 作废的单照样能打（存档、对账都要），但一眼看得出它不作数 */
#printArea .grid tr.void td { color: #888; text-decoration: line-through; }

/* 签字栏：三个位置分开站，别挤成一串 */
#printArea .sign { display: flex; justify-content: space-between;
  margin-top: 10mm; font-size: 12px; }
#printArea .ticket:not(.a4):not(.wide) .sign { margin-top: 8mm; gap: 6mm; justify-content: flex-start; }

/* 页脚两行：版权归属 + 这软件是谁给的。
   8.5px 是下限，再大就会把送货单的签字栏挤到第二页——那边要留地方签骑缝。 */
#printArea .print-foot { margin-top: auto; border-top: 1px solid #bbb; padding-top: 3mm; }
#printArea .sign { margin-bottom: 6mm; }
#printArea .print-sign { text-align: center; font-size: 8.5px; color: #888;
  line-height: 1.5; margin: 2px 0 0; }
#printArea .print-sign:first-of-type { margin-top: 0; }

/* 密码框：右边留出「显示」的位置；那行提示只在打中文/全角时冒出来。
   网页换不了系统的输入法，但至少让人一眼看见打进去的是中文还是英文。 */
.pwd-wrap { position: relative; }
.pwd-wrap input { padding-right: 56px; }
.pwd-eye { position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  border: 0; background: none; color: var(--muted); font-size: 12.5px; padding: 4px 8px; }
.pwd-eye:hover { color: var(--brand); }
.pwd-tip { display: none; margin: -6px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--danger); }
.pwd-tip.on { display: block; }

/* 一张单录好几样的暂存区（2026-11-01）。
   攒着的那几行要能一眼数清、一眼删掉，所以给一张小表，不给一串文字。
   行高比正表矮一点：它是"还没入账的草稿"，别抢了下面已入账列表的注意力。 */
table.cart { margin-top: 6px; font-size: 13.5px; }
table.cart th { font-size: 12.5px; border-bottom: 1px solid var(--line); }
/* 列宽定死，别让「数量」「单价」跟着表宽乱飘：表是 100% 宽，
   不定列宽的话四列平均分，物料名被挤到中间、删字跑到最右边。 */
table.cart th:nth-child(1) { width: 30px; }
table.cart th:nth-child(3) { width: 96px; }
table.cart th:nth-child(4) { width: 96px; }
table.cart th:nth-child(5) { width: 48px; }
table.cart td { border-bottom: 1px dashed var(--line); padding: 4px 8px; }
table.cart button.link { border: none; background: none; color: var(--danger);
  padding: 0 4px; font-size: 13px; cursor: pointer; }
/* 提交结果那一行（✓ 已入库，单号 ERP-PIN-000001）单独占一行。
   原来它是按钮行里的一个 <span>，单号一长就被挤到右边折成两截，
   看着像报错区，客户反而不敢确认自己到底成没成。
   `:empty` 时不留空档——没提交就别凭空多一条缝。 */
.msgline { margin-top: 10px; font-size: 13.5px; line-height: 1.5; }
.msgline:empty { margin-top: 0; }
.msgline .ok { font-weight: 600; }

/* 月结一页纸（2026-11-01）。
   三张数字卡是这页的主角：老板月底只想看三个数——进多少、出多少、还剩多少。
   所以数字给 22px，标题给 13px 灰字，一眼扫过去先看见数字。 */
.monthnav { align-items: center; gap: 10px; }
.monthnav b { flex: 1 1 auto; text-align: center; font-size: 16px; }
.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 6px; }
.stat { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; display: grid; gap: 2px; }
.stat > span:first-child { color: var(--muted); font-size: 13px; }
.stat b { font-size: 22px; font-variant-numeric: tabular-nums; }
.stat > span:last-child { color: var(--muted); font-size: 12.5px; }
/* 出库前 10 名：一条一条横着比长短，不做图表库（一页纸不需要那些开关） */
.bars { display: grid; gap: 6px; margin-top: 8px; }
.bar { display: grid; grid-template-columns: 96px 1fr 64px; align-items: center; gap: 8px; font-size: 13.5px; }
.bar .bn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar .bt { background: #eef1f5; border-radius: 6px; height: 12px; overflow: hidden; }
.bar .bt i { display: block; height: 100%; background: #7aa7e8; border-radius: 6px; }
.bar .bv { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
@media (max-width: 640px) {
  .stat-row { grid-template-columns: 1fr; }
  .bar { grid-template-columns: 76px 1fr 52px; }
}

/* 有人把系统的"减少动态效果"打开了（前庭功能敏感，动一下会晕），动画一律关掉。
   关掉之后功能一点不少，只是不动，不是降级。
   **这段必须放在文件最后**：@media 不提升优先级，只是"条件成立时才生效"，
   同优先级的规则仍然是后写的赢。放在前面的话，后面那条
   `dialog[open] { animation: dlgIn }` 会把它盖掉——开关看着写了，
   实际对弹窗一点用没有（2026-11-02 自己踩的，写完先核了一遍先后顺序）。 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ---------- 「我的消息」（2026-11-02） ---------- */
/* 顶栏那个未读数。做成一枚小红标贴在字后面，不绝对定位：
   右上角压着容易被窗口按钮挤掉，而且数字从 9 变 10 时宽度一变，
   旁边几个按钮会跟着左右跳。跟着字排，宽度变化只影响它自己。 */
.msgdot { display: inline-block; margin-left: 5px; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 8px; background: var(--danger); color: #fff;
  font-size: 11px; line-height: 16px; text-align: center;
  font-variant-numeric: tabular-nums; }

/* 一张单一条。用 <details> 而不是自己做展开：点标题就展开，键盘也能用，
   不用再写一套"记住哪条是开的"。 */
.tk { border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; }
.tk + .tk { margin-top: 8px; }
.tk > summary { cursor: pointer; list-style: none; display: block; }
.tk > summary::-webkit-details-marker { display: none; }
.tk > summary::before { content: "▸"; color: var(--muted); margin-right: 6px; }
.tk[open] > summary::before { content: "▾"; }
.tk > summary .muted { font-size: 12.5px; margin-left: 6px; }
/* 「新回复」：客户唯一需要立刻看见的三个字。用品牌色不用红的——
   红在这套界面里是"出错了"，一条回复不是错。 */
.tknew { margin-left: 6px; padding: 0 6px; border-radius: 4px; font-size: 11.5px;
  background: #eef3ff; color: var(--brand); }
.tkbody { margin-top: 8px; display: grid; gap: 6px; }
/* 对话气泡：他说的靠左白底，我们说的靠左但换色——不靠右对齐，
   因为一屏里左右跳会让长句子断得很碎。 */
.msg { border-radius: 8px; padding: 6px 9px; font-size: 13.5px; max-width: 92%; }
.msg .who { font-size: 11.5px; color: var(--muted); margin-bottom: 2px; }
.msg .body { white-space: pre-wrap; word-break: break-word; }
.msg.me { background: #f4f6f9; }
.msg.them { background: #eef3ff; }

/* 打印流水（2026-11-02）。一行 = 一张打出去的纸。
   左边是「哪张单」，右边是「什么时候、什么纸、谁打的、补打」——
   客户来查的顺序就是这个：先认单，再确认是不是自己那张，最后按一下补。 */
.plog { display: flex; flex-direction: column; gap: 8px; }
.plogrow { display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 10px; }
.plogmain { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.plogside { display: flex; align-items: center; gap: 10px; flex-shrink: 0; font-size: 13px; }
/* 窄屏（车间的笔记本、平板竖着放）折成两行：时间和按钮挤一行会把单号挤没。 */
@media (max-width: 640px) {
  .plogrow { flex-direction: column; align-items: stretch; }
  .plogside { justify-content: space-between; }
}

/* 泡泡提示（hintpop）已撤：改成右侧「虚拟人物 + 卷轴帮助」，样式在 help.css。 */

/* 从机标记：这台电脑不记账，窗口连的是厂里另一台（2026-11-04）。
   用琥珀色而不是品牌蓝：蓝色在顶栏里到处都是，看不出来；
   客户要的是"这台跟平时不一样"的那一眼。 */
.clienttag {
  display: inline-flex; align-items: center; gap: 5px;
  height: 24px; padding: 0 10px; border-radius: 999px;
  background: #fff4e0; border: 1px solid #f0c98a;
  color: #8a5a00; font-size: 12px; line-height: 1;
  font-variant-numeric: tabular-nums; max-width: 260px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.clienttag::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: #e08b00; flex: none;
}

/* 登录页上的从机提示（2026-11-04）。
   比顶栏那个更靠前——客户还没登录就该知道这本账不在这台电脑上。 */
.login-client {
  display: none; margin: 0 0 14px; padding: 9px 12px;
  border-radius: 9px; background: #fff7e8; border: 1px solid #f0d9a8;
  color: #7a5100; font-size: 12.5px; line-height: 1.75; text-align: left;
}
.login-client b { color: #5d3d00; }
/* 主机那台：中性灰，不是警告色。
   它要说的是"这台就是记账的机器 + 它叫什么"，不是"出事了"。 */
.login-client.local {
  background: #f4f6f9; border-color: #e2e8f0; color: #55657a;
}
.login-client.local b { color: #2c3a4b; }

/* ============ 老板看（驾驶舱，2026-11-04）============
 *
 * 这一页跟软件其它部分**长得不一样**，是有意的：它是挂在墙上/摆在老板桌上
 * 看的一屏，不是给人操作的。所以深色、大数字、没有按钮。
 *
 * 隔离靠 `#content.boss-on` 一条 class：离开这一页就摘掉，
 * 别的页面不会沾到任何一条这里的样式。
 */
#content.boss-on { max-width: none; padding: 0; display: block; background: #070b14;
  overflow: hidden; }
/* 大屏上不摆那条黄色提示（演示站的是"随时恢复原样"，客户机器上的是到期提醒）——
   它是**操作提示**，这一页不是给人操作的。切走就恢复，别的地方照样看得到。 */
body.boss-mode #banner, body.boss-mode #claimBar { display: none; }

.boss {
  --bk: #e8eefc; --bdim: #8494ad; --bfaint: #6f7f9b;
  --bcard: linear-gradient(160deg, rgba(23,33,54,.94), rgba(13,20,35,.94));
  --bline: rgba(96,124,175,.22);
  /* 高度由 bossFit() 量出来写进 style：客户的窗口从 1366×768 到 2560 宽都有，
     写死 vh 减一个数，在带顶栏/导航的窗口里一定被压住底部那条滚动条。
     这里留 100% 只是兜底（脚本没跑起来时不至于塌成一条）。 */
  position: relative; height: 100%; min-height: 420px;
  padding: 18px 22px 16px; color: var(--bk);
  background:
    radial-gradient(1100px 560px at 50% -8%, rgba(56,189,248,.15), transparent 70%),
    radial-gradient(820px 460px at 100% 108%, rgba(124,92,255,.11), transparent 70%),
    #070b14;
  display: flex; flex-direction: column; gap: 14px;
  font-variant-numeric: tabular-nums;
}
.boss-wait { color: #8494ad; padding: 40px 4px; font-size: 14px; }

.boss-top { display: flex; align-items: flex-end; gap: 14px; }
.boss-top .co { flex: 1; min-width: 0; }
.boss-top .co b { font-size: clamp(17px, 1.5vw, 25px); font-weight: 600; letter-spacing: .6px;
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.boss-top .co span { font-size: 12px; color: var(--bdim); letter-spacing: 1.6px; }
.boss-today { display: flex; align-items: center; gap: 16px; font-size: 14px; color: #b9c6dc; }
.boss-today b { color: #fff; font-weight: 600; }
.boss-today i { width: 1px; height: 16px; background: rgba(126,150,190,.28); font-style: normal; }
.boss-today em { font-style: normal; color: var(--bdim); font-size: 12px; margin-right: 2px; }
.boss-clock { text-align: right; flex: none; }
.boss-clock b {
  /* **必须定宽**（2026-11-04 老板：「跳秒后今日开单那行会动位置」）：
     时钟每秒换一次文字，雅黑的数字不是等宽的——「12:00:11」比「12:00:08」
     窄一点点，于是右边这一块宽度一跳，整条顶栏就跟着抖，中间「今日开单」
     那组被推来推去。`tabular-nums` 在雅黑上不生效（它没有这组特性），
     所以直接按 8 个字符定宽，再右对齐。 */
  display: inline-block;
  min-width: 8ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: clamp(20px, 1.7vw, 30px); font-weight: 300; letter-spacing: 1px;
}
.boss-clock span { display: block; font-size: 12px; color: var(--bdim); margin-top: 2px; }

/* min-height:0 不能省：flex 子项默认按内容撑高，格子一撑，下面那条滚动条
   就被挤出可视区（1366×768 上实测：整条"最近业务"不见了）。 */
.boss-grid { flex: 1; min-height: 0; display: grid;
  grid-template-columns: 1fr 1fr; gap: 14px; }
.boss-card {
  position: relative; border-radius: 16px; padding: 18px 20px;
  background: var(--bcard); border: 1px solid var(--bline);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 10px 28px rgba(0,0,0,.34);
  display: flex; flex-direction: column; min-width: 0; overflow: hidden;
}
.boss-card::before {
  content: ""; position: absolute; left: 20px; right: 20px; top: 0; height: 1px;
  background: linear-gradient(90deg, rgba(56,189,248,.55), transparent 60%);
}
.boss-h { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.boss-h b { font-size: 15.5px; font-weight: 600; letter-spacing: 1.2px; }
.boss-h em { font-style: normal; font-size: 11.5px; color: var(--bfaint); }
.boss-tag { font-size: 11.5px; padding: 2px 8px; border-radius: 999px;
  background: rgba(251,191,36,.1); color: #fbbf24; border: 1px solid rgba(251,191,36,.3); }

.boss-big { margin-top: 10px; display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.boss-big b { font-size: clamp(28px, 3vw, 54px); font-weight: 300; letter-spacing: -1px; line-height: 1.05; }
.boss-big span { font-size: 14px; color: var(--bdim); }
.boss-big .up { color: #f87171; }      /* 涨＝红 */
.boss-big .dn { color: #34d399; }
.boss-sub { margin-top: 5px; font-size: 13px; color: var(--bdim); }

.boss-kpi { display: flex; gap: 26px; margin-top: 12px; flex-wrap: wrap; }
.boss-kpi div span { display: block; font-size: 11.5px; color: var(--bdim); margin-bottom: 3px; }
.boss-kpi div b { font-size: clamp(17px, 1.5vw, 28px); font-weight: 300; }
.boss-kpi div b.red { color: #f87171; } .boss-kpi div b.amber { color: #fbbf24; }
.boss-kpi div b.green { color: #34d399; }

.boss-spark { margin-top: auto; display: flex; align-items: flex-end; gap: 5px; height: 58px; }
.boss-spark i { flex: 1; border-radius: 3px 3px 0 0; min-height: 3px;
  background: linear-gradient(180deg, rgba(56,189,248,.85), rgba(56,189,248,.16)); }
.boss-spark i.hot { background: linear-gradient(180deg, rgba(251,191,36,.9), rgba(251,191,36,.14)); }

.boss-aging { display: flex; gap: 8px; margin-top: 12px; }
.boss-aging div { flex: 1; background: rgba(126,150,190,.07); border: 1px solid rgba(96,124,175,.16);
  border-radius: 9px; padding: 7px 10px; position: relative; overflow: hidden; min-width: 0; }
.boss-aging span { display: block; font-size: 11px; color: var(--bdim); }
.boss-aging b { display: block; margin-top: 2px; font-size: clamp(15px, 1.2vw, 21px); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.boss-aging i { position: absolute; left: 0; bottom: 0; height: 3px; width: 100%; }
.boss-aging i.ok { background: #38bdf8; } .boss-aging i.mid { background: #fbbf24; }
.boss-aging i.bad { background: #f87171; }

/* 名单：每屏固定几条，8 秒换一屏。换的时候整块淡入，不做位移——
   位移在远处看像"字在跑"，淡入才像"翻页"。 */
.boss-rows { margin-top: 10px; display: flex; flex-direction: column; gap: 7px; }
.boss-rows.flip { animation: bossFlip .45s ease-out; }
@keyframes bossFlip { from { opacity: .15; } to { opacity: 1; } }
.boss-row { display: flex; align-items: center; gap: 10px; font-size: 13.5px; }
.boss-row .k { width: 96px; flex: none; color: #b9c6dc; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.boss-row .bar { flex: 1; height: 6px; border-radius: 4px; background: rgba(126,150,190,.14);
  overflow: hidden; min-width: 30px; }
.boss-row .bar i { display: block; height: 100%; border-radius: 4px;
  background: linear-gradient(90deg, #38bdf8, #6366f1); }
.boss-row .v { width: 150px; text-align: right; color: #cdd9ec; flex: none;
  font-size: 13px; white-space: nowrap; }
.boss-foot { margin-top: auto; padding-top: 9px; border-top: 1px solid rgba(96,124,175,.14);
  font-size: 11.5px; color: var(--bfaint); }

.boss-ticker { height: 54px; border-radius: 12px; overflow: hidden; position: relative;
  background: rgba(17,25,42,.95); border: 1px solid rgba(96,124,175,.2);
  display: flex; align-items: center; flex: none; }
.boss-ticker .cap { flex: none; padding: 0 16px 0 20px; height: 100%; display: flex;
  align-items: center; font-size: 12px; letter-spacing: 2px; color: #7dd3fc;
  border-right: 1px solid rgba(96,124,175,.2); background: #111a2b;
  position: relative; z-index: 3; box-shadow: 14px 0 18px rgba(17,26,43,.95); }
.boss-ticker .vp { flex: 1; height: 100%; overflow: hidden; display: flex; align-items: center; }
.boss-ticker .track { display: flex; gap: 48px; padding-left: 32px; white-space: nowrap;
  animation: bossRoll 46s linear infinite; }
@keyframes bossRoll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.boss-ticker .it { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: #b9c6dc; }
.boss-ticker .it b { color: #e8eefc; font-weight: 500; }
.boss-ticker .it .dot { width: 6px; height: 6px; border-radius: 50%; background: #38bdf8; flex: none; }
.boss-ticker .it .dot.red { background: #f87171; }
.boss-ticker .it .dot.green { background: #34d399; }
.boss-ticker .it .t { color: var(--bfaint); font-size: 12px; }

/* 窗口窄（客户用 1366 笔记本，或者把窗口拉小）时：四块改上下两行一列，
   字整体收一档。不收的话两张卡会互相压字。 */
@media (max-width: 1180px) {
  .boss { padding: 12px 14px; gap: 10px; }
  .boss-grid { grid-template-columns: 1fr; }
  .boss-row .k { width: 84px; }
  .boss-row .v { width: 132px; }
  .boss-today { display: none; }   /* 窄屏先让出顶栏，数字比"今日"要紧 */
}

/* 矮屏（1366×768 的笔记本、把窗口拉成一半的台式机）。
   四块还是两列，但**每一块都得装得下**：这套收法在 768 高的屏上实测过，
   收之前第二排卡片的下沿正好被切掉，页脚那行字看不见。 */
@media (max-height: 820px) {
  .boss { padding: 12px 16px 10px; gap: 10px; }
  .boss-big { margin-top: 6px; }
  .boss-big b { font-size: clamp(24px, 2.4vw, 40px); }
  .boss-sub { margin-top: 3px; font-size: 12px; }
  .boss-kpi { margin-top: 8px; gap: 20px; }
  .boss-kpi div b { font-size: clamp(15px, 1.2vw, 22px); }
  .boss-aging { margin-top: 8px; }
  .boss-aging div { padding: 5px 8px; }
  .boss-rows { margin-top: 7px; gap: 5px; }
  .boss-row { font-size: 12.5px; }
  .boss-spark { height: 34px; }
  .boss-card { padding: 14px 16px; }
  .boss-h b { font-size: 14.5px; }
  .boss-foot { font-size: 11px; padding-top: 6px; }
  .boss-ticker { height: 44px; }
}

/* 对账单顶上那四行：期初 → 本月发生 → 本月已付 → 期末。
   为什么不是一张小表：这四行是**一条算式**，横着摆人在心里就能验
   （72,000 + 38,400 − 20,000 = 90,400），竖着摆就得自己再誊一遍。
   最后一格描边加重：那是这张纸的结论，客户只看那一个数的时候也找得到。 */
.ps-sum {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px; margin-top: 12px;
}
.ps-sum > div {
  background: #fafbfc; border: 1px solid var(--line);
  border-radius: 10px; padding: 9px 12px; min-width: 0;
}
.ps-sum > div.end { background: #f3f7ff; border-color: #b9cdf0; }
.ps-sum span {
  display: block; font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ps-sum b {
  display: block; margin-top: 3px; font-size: 18px; font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ps-sum i {
  display: block; margin-top: 2px; font-style: normal;
  font-size: 11px; color: var(--muted); white-space: nowrap;
}
.ps-sum > div.end b { color: var(--brand); }
@media (max-width: 680px) {
  .ps-sum { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
