/* ============================================
   超华店工作台 - 样式
   移动端优先，响应式适配桌面
   ============================================ */

:root {
    /* ══════════════════════════════════════════════════════════════
       V2.0 护眼主题（2026-09-30）
       --------------------------------------------------------------
       决策：**橙主色 + 绿底护眼**，而非全量换绿。
         · 主色保持橙 #ff6b35 —— 换掉它等于推翻品牌识别，
           老店主打开会发现"这不是我的系统"，且 194 处 emoji、
           无数 ::before 都绑着这个色，全量替换的回归面太大。
         · 底色走淡绿护眼 —— 这才是"看久了眼睛累"的真正解药：
           人眼对大面积背景色最敏感，背景绿 + 主色橙 的组合，
           既护眼又保留橙色作为"行动召唤"的抓眼优势（互补色）。
         · 中性灰阶同步去蓝调、加一点绿（hue 145），
           避免原来的冷灰与绿底打架，见 --ds-* 那组。
       ══════════════════════════════════════════════════════════════ */
    --primary: #ff6b35;
    --primary-dark: #e55a2b;
    --primary-light: #fff3ed;   /* 橙的浅底，配合下面 --success-soft 等新增 */
    --bg: #f2f7f4;              /* 护眼底：原 #eafaf0 偏"薄荷清新"，
                                   饱和度略高、看久了发飘；
                                   改为 #f2f7f4 —— 同色相但降饱和、
                                   降明度差，更接近"纸"的观感 */
    --card: #ffffff;
    --text: #2d3436;
    --text-light: #5f6f66;      /* 原 #6b7d72，对白底对比度 4.5:1 边缘，
                                   加深一点保障 WCAG AA */
    --border: #dce9e1;          /* 原 #d8ecdf 偏绿，配新 bg 略脏；
                                   统一到中性偏绿 */
    --green: #22c55e;
    --yellow: #f59e0b;
    --red: #ef4444;
    --blue: #22a7f0;
    --purple: #8b5cf6;
    --gold: #ffb800;
    --radius: 18px;
    --shadow: 0 4px 16px rgba(40,80,60,0.07);  /* 阴影带去绿的灰，
                                                  纯黑阴影压在绿底上会发脏 */
    /* ── 液态玻璃 ── */
    --glass-bg: rgba(255,255,255,0.5);
    --glass-bg-strong: rgba(255,255,255,0.72);
    --glass-border: rgba(255,255,255,0.7);
    --glass-blur: blur(24px) saturate(180%);
    --glass-shadow: 0 12px 40px rgba(31,120,72,0.14),
                    inset 0 1px 0 rgba(255,255,255,0.75),
                    inset 0 -1px 0 rgba(255,255,255,0.25);

    /* ── iOS 系统动效 token ──
       复刻 iOS 手感的关键不是堆特效，而是全站共用同一套曲线与时长：
       曲线统一 → 各个动作之间不打架；时长分档 → 轻重有层次。
         ios-out    减速极慢、末端"吸住"，iOS 页面与列表入场的默认手感
         ios-sheet  iOS 弹层 / present 专用，起步果断、收尾绵长
         ios-spring 轻微过冲回弹，只给"主动触发"的反馈（按下、Tab 激活）
         ios-ease   iOS 默认 ease，用于颜色等无位移的过渡 */
    --ios-ease:   cubic-bezier(.25,.1,.25,1);
    --ios-out:    cubic-bezier(.16,1,.3,1);
    --ios-sheet:  cubic-bezier(.32,.72,0,1);
    --ios-spring: cubic-bezier(.34,1.56,.64,1);
    --dur-tap:  .12s;   /* 按下，必须快，慢一点就叫迟钝 */
    --dur-fast: .20s;   /* 颜色 / 阴影等轻过渡 */
    --dur-base: .34s;   /* 标准入场 */
    --dur-slow: .42s;   /* 大块 / 瀑布流入场 */
    --dur-page: .38s;   /* 页面转场 */

    /* ── 按钮尺寸阶梯（只有这三档，别再新造高度）──
       为什么要有阶梯：以前 .btn 是 padding 驱动（上下 10px ≈ 40px 高），
       而工具栏那几处是 height 硬写（34 / 32 / 30 / 27px）。
       两套驱动方式混在一起，就永远没法用一条规则统一改高度 ——
       想调矮工具栏，就得把每一处硬写的高度单独找出来改一遍，
       漏一处就参差不齐（历史上正是这么攒出 6~7 套尺寸的）。

       统一成 height 驱动之后，高度由一个变量说了算，padding 只管左右。
       想整体调矮/调高，改这三个变量即可，全站跟着走。

       触控最小 44px 不在这里管 —— 那是 min-height 的职责（见文件后段
       .btn,.tab-btn,.side-btn,.nav-btn 那条），它和阶梯是两个维度：
       阶梯管"视觉尺寸"，min-height 管"手指够不够得着"。 */
    --btn-h-xs: 24px;  --btn-px-xs: 8px;   --btn-fs-xs: 12px;  --btn-r-xs: 6px;
    --btn-h-sm: 30px;  --btn-px-sm: 12px;  --btn-fs-sm: 13px;  --btn-r-sm: 8px;
    --btn-h-md: 38px;  --btn-px-md: 16px;  --btn-fs-md: 14px;  --btn-r-md: 10px;

    /* ══════════════════════════════════════════════════════════════
       V2.0 新增：动效时长统一档（指令第四十条）
       --------------------------------------------------------------
       原有的 --dur-tap/fast/base/slow/page 是"入场"用的，偏慢；
       V2.0 需要一套"交互反馈"档，两者并存、各管一段，不互相覆盖。

       为什么要分这么细：指令第四十条给了四档区间，
       但如果只定义 4 个变量，实现时 inevitably 会到处写裸值
       （"这里 250ms 好像更顺"），最后又是一地散参数。
       多定义两档（instant/press），把最常用的两个值固定下来，
       手就不会痒。
       ══════════════════════════════════════════════════════════════ */
    --t-instant: 100ms;   /* 按下 scale —— 超过 120ms 就叫"迟钝" */
    --t-fast:    150ms;   /* hover / 颜色 / 图标微动 */
    --t-base:    220ms;   /* 展开收起 / 页面切换（指令给 200-280） */
    --t-slow:    280ms;   /* 大块展开 / 弹窗面板位移 */
    --t-ai:      600ms;   /* AI 状态切换（指令给 300-800）
                             扫描线一个来回 ≈ 1.8s，这里控制的是
                             "阶段文案切换"的淡入，不是扫描线本身 */
    --ease-out:  cubic-bezier(.22,1,.36,1);      /* 通用减速，收尾干净 */
    --ease-soft: cubic-bezier(.4,0,.2,1);        /* 标准材质曲线 */
    --ease-back: cubic-bezier(.34,1.4,.64,1);    /* 轻回弹，只给主动触发 */
}

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

/* ── 滚动容器：文档 → .content ──
   原先整页一起滚，文档高度会随卡片开合剧烈变化（实测 863 ↔ 1552）。
   在 iOS Safari 上，文档高度一旦跨过「可滚动 ↔ 不可滚动」的临界点，
   浏览器就会收放地址栏，而 position:fixed 的底栏和备案条是贴着视觉视口
   定位的，于是跟着上下跳 —— 用户反馈的"底部跟着卡片开合跳动"就是这么来的。
   桌面 Chromium 复现不出来（实测底栏位移 0、单帧 0），是 iOS 独有的毛病，
   所以只能从根上避免：让文档高度恒定。

   把滚动挪进 .content 之后，文档高度恒等于视口、永不变化，
   地址栏不再收放，底栏和备案条就真的钉死了。

   注意：sticky 表头不会因此失效。项目里 6 处 sticky 全在 .table-wrap 内部
   （含横向列冻结），而 .table-wrap 自己就是滚动容器（overflow:auto），
   它们的滚动祖先本来就不是 document。 */
html, body {
    height: 100%;
    overflow: hidden;
}
html {
    /* iOS 橡皮筋回弹会把文档拉过头，露出渲染范围之外的区域 ——
       html 没背景时那块是纯白，跟绿渐变的 body 拼在一起，
       就是用户看到的"底栏下面一大片白色"。铺同色系绿底，回弹无感。 */
    background: #d8f3e3;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
        'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
    background: linear-gradient(160deg, #d8f3e3 0%, #ecfbf1 45%, #dff5e8 100%);
    color: var(--text);
    /* 70px 是底部导航高度，再叠加 iPhone 底部 Home 指示条安全区，
       否则导航会压在小黑条上、最后一个图标点不准。
       （真正的底部让位值由后面的响应式规则给出：导航 58 + 备案 30） */
    padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px));
    /* 左右同样要留：横屏时刘海会吃掉约 48px，少了这两行内容会被裁进刘海里。
       注意 env() 只有在 viewport 带 viewport-fit=cover 时才非 0，
       两者必须成对出现，缺一个这套安全区就是白写。 */
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overscroll-behavior-y: contain;   /* 滚到顶/底不再带动整页橡皮筋 */
}
/* iOS 原生 App 点按时没有那层灰色高亮块 —— 去掉它，观感立刻"像原生" */
* { -webkit-tap-highlight-color: transparent; }
/* touch-action:manipulation 去掉双击缩放判定，消灭 iOS 上 ~300ms 的点击延迟 */
button, .btn, .nav-btn, .side-btn, .tab-btn, .channel-card, input, select, textarea {
    touch-action: manipulation;
}
/* iOS 会自动放大 font-size < 16px 的输入框，聚焦时整页被撑大 —— 锁死 16px */
input, select, textarea { font-size: 16px; }

/* ── 顶部栏 ── */
.topbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 100;
    /* 刘海 / 灵动岛：让状态栏区域也透出毛玻璃，而不是压住内容 */
    padding-top: env(safe-area-inset-top, 0px);
    /* 横屏时左右各留刘海宽度：毛玻璃背景仍然通栏，只有内容往里收 */
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-bottom: 1px solid var(--glass-border);
    box-shadow: var(--glass-shadow);
}
.topbar-inner {
    max-width: 1400px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
}
.brand { display: flex; align-items: center; gap: 8px; }
.brand-icon { font-size: 24px; }
.brand-name { font-size: 18px; font-weight: 700; white-space: nowrap; }
/* 版本号徽章：低调显示在店名右侧，远程排查时用于确认线上版本 */
.app-version {
    font-size: 11px; font-weight: 600; color: #6b7a72;
    background: #eef4f0; border: 1px solid #e2ebe6;
    border-radius: 9px; padding: 1px 7px; white-space: nowrap;
    letter-spacing: .02em;
}
.brand-edit {
    background: transparent;
    border: none;
    cursor: pointer;
    font-size: 14px;
    padding: 2px 4px;
    border-radius: 6px;
    opacity: 0.6;
    transition: opacity 0.2s, background 0.2s;
}
.brand-edit:hover { opacity: 1; background: rgba(0,0,0,0.05); }

.month-selector {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.month-selector select {
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 14px;
    background: var(--bg);
    color: var(--text);
}

/* ── 移动端底部导航 ── */
.bottom-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 100;
    display: flex;
    /* 底部安全区（Home 指示条）不在 nav 自身垫 padding —— 备案条
       .site-footer 已自带 padding-bottom: env(safe-area-inset-bottom)，
       nav 再垫一份就是双算：nav 会被顶出一条 34px 的透明空带，
       底部整体占用从 122px 虚胖到 156px，内容还被遮 24px（实测）。
       nav 只需把自己抬到备案条上沿：bottom = 备案内容高 28 + 安全区。 */
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-top: 1px solid var(--glass-border);
    box-shadow: var(--glass-shadow);
}
.nav-btn {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 8px 4px 6px;
    border: none;
    background: none;
    color: var(--text-light);
    font-size: 11px;
    cursor: pointer;
}
.nav-btn.active { color: var(--primary); }
.nav-icon { font-size: 20px; }

/* ── 桌面侧边栏 ── */
.sidebar {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 220px;
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-right: 1px solid var(--glass-border);
    box-shadow: var(--glass-shadow);
    padding: 20px 12px;
    z-index: 90;
    flex-direction: column;
    gap: 8px;
}
.sidebar-brand { display: flex; align-items: center; gap: 8px; padding: 8px 12px 24px; }
.side-nav { display: flex; flex-direction: column; gap: 4px; }
.side-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    border: none;
    background: none;
    border-radius: 8px;
    font-size: 15px;
    color: var(--text-light);
    cursor: pointer;
    text-align: left;
}
.side-btn.active { background: #fff3ed; color: var(--primary); font-weight: 600; }
.side-btn .nav-icon { font-size: 18px; }

/* ── 主内容 ── */
.content {
    max-width: 1400px;
    margin: 0 auto;
    padding: 16px;
    /* 顶部避开固定顶栏。--topbar-height 由 adjustTopbar() 实测 topbar 高度后
       写入（高度 + 8px 间隙），而 topbar 自身已含 padding-top: env(安全区)，
       所以这个变量天然包含安全区 —— 这里不能再加一遍 env()，
       否则 iPhone 上顶部让位双算 47px，无谓再压一截内容区。 */
    padding-top: var(--topbar-height, 70px);
    /* 接管整页滚动（见上方「滚动容器：文档 → .content」的说明）。
       高度撑满 body 内容区即可 —— body 的 padding-bottom 已经把
       底部导航 + 备案条那一段让出来了，这里不用再算一遍。 */
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    /* 禁掉 Chrome 的原生滚动锚定：渠道面板展开时，浏览器为了让「视口顶附近的
       锚点元素」视觉不动，会把我们刚吸顶滚上去的 scrollTop 又原路调回去 ——
       实测展开动画可见区被封在 306px、路程比掉到 0.42（吸顶被整体抵消）。
       iOS Safari 本来就没有 scroll anchoring，真机不受影响；桌面禁掉后
       两端行为一致。页面高度变化都来自用户主动操作（点卡片开合），
       不存在"后台插入内容把阅读位置顶跑"的场景，禁用是安全的。 */
    overflow-anchor: none;
    /* 滚动这条线上下两轮各翻过一次车，教训都在这：
       ① 不能写 overscroll-behavior-y: contain 的"裸版"——盘点是手风琴，
          内容常不满一屏，容器没有可滚区间，contain 又把回弹禁掉，
          手感是"撞墙"（上一轮真机反馈）。
       ② 但也不能不写 —— 不写的话，长列表滚到底后手势会链给 document，
          整页被带进橡皮筋后，后续手势全被 document 吃掉，内容区再也
          收不到，表现就是"向下滑得到底，往回拉不回去、卡在弹跳"
          （这一轮真机反馈）。contain 只拦"传给整页"的那一段，
          容器自己的边界回弹仍然保留。
       ③ -webkit-overflow-scrolling: touch 千万别加 —— iOS 13+ 默认就有
          动量滚动，这行老属性会让部分机型创建独立滚动层，出现
          "到底后卡住、反向滑不动"的祖传 bug。
       ②的撞墙面由手机端 .page.active 的 min-height 兜底（见下方媒体块）：
       强制内容至少比可视区高 1px，容器永远"可滚"，手势反馈走容器回弹。 */
    overscroll-behavior-y: contain;
}
.page { display: none; }
.page.active { display: block; }

.page-title { font-size: 20px; font-weight: 700; margin-bottom: 16px; }
.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 16px;
    flex-wrap: wrap;
    gap: 10px;
}
.page-header .page-title { margin-bottom: 0; }
.header-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.header-actions select {
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 13px;
}

/* ── 统计卡片 ── */
.stat-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}
.stat-card {
    background: linear-gradient(135deg, rgba(255,255,255,0.94), rgba(255,255,255,0.82));
    border: 1px solid var(--glass-border);
    border-radius: 22px;
    padding: 16px;
    box-shadow: var(--glass-shadow);
}
.stat-label { font-size: 13px; color: var(--text-light); margin-bottom: 6px; }
.stat-value { font-size: 26px; font-weight: 700; color: var(--text); }
.stat-value-gold { color: var(--gold); }
.stat-value-blue { color: var(--blue); }
.stat-sub { font-size: 12px; color: var(--text-light); margin-top: 4px; }
.stat-cards-sm .stat-value { font-size: 30px; }
.stat-unit { font-size: 14px; font-weight: 400; color: var(--text-light); margin-left: 2px; }

/* ── 仪表盘：五个工作模块纵向大卡片（与下方面板一致的观感）── */
.module-tiles {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 16px;
}
.module-tile {
    width: 100%;
    background: linear-gradient(135deg, rgba(255,255,255,0.94), rgba(255,255,255,0.82));
    border: 1px solid var(--glass-border);
    border-radius: 22px;
    padding: 16px 18px;
    box-shadow: var(--glass-shadow);
    cursor: pointer;
    transition: transform .12s ease, box-shadow .12s ease;
}
.module-tile:hover { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(255,107,53,0.16); }
.module-tile:active { transform: scale(.995); }
.mt-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.mt-icon { font-size: 20px; }
.mt-name { font-size: 15px; font-weight: 700; color: var(--text); }
.mt-spacer { flex: 1; }
.mt-value { font-size: 30px; font-weight: 700; color: var(--primary); line-height: 1.1; }
.mt-sub { font-size: 13px; color: var(--text-light); margin-top: 6px; }
/* 总览 + 环比明细 + 分项 */
.mt-overview { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.mt-label { font-size: 13px; color: var(--text-light); }
.mt-mom {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    padding: 10px 0;
    border-top: 1px dashed rgba(0,0,0,0.1);
    border-bottom: 1px dashed rgba(0,0,0,0.1);
}
.mom-cell { display: flex; flex-direction: column; gap: 3px; }
.mom-k { font-size: 11px; color: var(--text-light); }
.mom-v { font-size: 15px; font-weight: 700; color: var(--text); }
.mom-up { color: #059669; }
.mom-down { color: #dc2626; }
.mt-detail { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.dt-row { display: flex; justify-content: space-between; align-items: center; font-size: 13px; }
.dt-k { color: var(--text-light); }
.dt-v { font-weight: 600; color: var(--text); }
.rate-pill { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; font-weight: 700; padding: 3px 9px; border-radius: 999px; }
.rate-pill.rate-up { color: #059669; background: rgba(5,150,105,0.12); }
.rate-pill.rate-down { color: #dc2626; background: rgba(220,38,38,0.12); }
.rate-pill.rate-na { color: var(--text-light); background: rgba(0,0,0,0.05); }
.rate-up { color: #059669; }
.rate-down { color: #dc2626; }
.rate-na { color: var(--text-light); }

/* ── 仪表盘：瀑布流卡片网格（稳定、所有设备可用）── */
.dash-charts {
    position: relative;
    column-count: 2;
    column-gap: 16px;
    padding: 4px 16px 60px;
}
@media (max-width: 760px) {
    .dash-charts { column-count: 1; }
}
@media (min-width: 1200px) {
    .dash-charts { column-count: 3; }
}

/* 入场动画：默认卡片可见（opacity:1），进入视口时播一次上浮淡入。
   纯 CSS 动画，JS 挂了也不会白屏。 */
@keyframes dashRise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}
/* 原 .6s / 22px：慢且幅度大，和页面其它动作对不上拍。
   收敛到 .42s / 14px，与板块入场的节奏统一。 */
.dash-charts .panel.dash-panel.in-view {
    animation: dashRise var(--dur-slow) var(--ios-out) both;
}

/* 单张卡片：瀑布流里禁止跨列断裂；离屏卡片跳过绘制，长列表滚动不掉帧 */
.dash-charts .panel.dash-panel {
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    page-break-inside: avoid;
    content-visibility: auto;
    contain-intrinsic-size: auto 320px;
    width: 100%;
    margin: 0 0 16px;
    border-radius: 18px;
    cursor: pointer;
    transition:
        transform var(--dur-base) var(--ios-out),
        box-shadow var(--dur-base) var(--ios-ease),
        border-color var(--dur-fast) var(--ios-ease);
}
/* 标题头可点击 */
.dash-charts .panel.dash-panel .panel-title {
    cursor: pointer;
}
/* 聚焦态：轻放大 + 高亮边框（点击展开看图表，再点收起） */
.dash-charts .panel.dash-panel.open {
    position: relative;
    z-index: 50;
    transform: scale(1.02);
    box-shadow:
        0 18px 50px rgba(31,52,110,.28),
        0 0 0 2px rgba(99,140,255,.55);
}
.chart-empty {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-light);
    font-size: 13px;
}
@media (max-width: 560px) {
    .chart { height: 240px; }
}

.dp-growth { font-size: 12px; font-weight: 600; }
.dp-growth.up { color: #16a34a; }
.dp-growth.down { color: #dc2626; }
.dp-growth.na { color: var(--text-light); font-weight: 400; }

/* ── 点评页：记录明细「全部 / 🟡收藏 / 🔵打卡」标签页 ── */
/* 工具栏：标签和编辑/删除必须挤在同一行里。
   手机 390px 宽下，「全部+收藏+打卡」约 168px，再放「编辑+删除」约 120px，
   加起来放得下 —— 所以这里不给按钮组任何左边距，也不让标签组 flex-grow。

   换行策略是这块的关键，踩过两次：
   ① 一开始让 .dp-detail-bar 换行 → 手机上按钮被顶到第二行，工具栏变两行；
   ② 改成 bar 不换行、但留 .dp-detail-tabs 内部 wrap → 普通模式看不出问题，
      一旦进选择模式（按钮组多了「全选/取消/确定」，要占约 190px），
      标签组被挤到 148px，「🔵打卡」就掉到第二行 —— 标签组高 60px，
      整条工具栏跟着变 60px，比普通的 44px 高出一截。
   结论：两头都不许换行。空间不够时让按钮组自己收缩（min-width:0），
   而不是让任何一组折行。真到极窄屏（<360px）再统一整组换行，见下面的媒体查询。 */
.dp-detail-bar {
    display: flex;
    flex-wrap: nowrap;          /* 不换行：宁可挤一点，也不出现第二行 */
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    margin-bottom: 12px;
}
.dp-detail-tabs {
    display: flex;
    flex-wrap: nowrap;          /* 标签组内部同样不折行，否则「打卡」会掉下去 */
    gap: 8px;
    flex: 0 0 auto;             /* 标签组不压缩：压它就会横向溢出 */
    min-width: 0;
}
.dp-detail-tabs .dp-tab { flex: 0 0 auto; }   /* 标签不压缩，文字不换行 */
/* emoji 用 <i> 包起来是为了手机上能单独藏掉（见下面媒体查询），
   桌面上它照常显示，只是把图标和文字之间的空隙交给 margin 控制 */
.dp-detail-tabs .tab-emoji { font-style: normal; margin-right: 3px; }
.dp-detail-acts { display: flex; flex: 0 1 auto; min-width: 0; align-items: center; gap: 6px; }
/* 计数只在选择模式出现（平时为空字符串，不占位） */
.dp-sel-count {
    font-size: 12.5px;
    color: var(--text-light);
    white-space: nowrap;
}
.dp-sel-count:empty { display: none; }
/* 选择模式下的 全选/取消/确定。
   注意 [hidden] 必须显式写：UA 样式表的 [hidden]{display:none} 优先级最低，
   下面这条 display:inline-flex 会把它盖掉，导致「隐藏」的工具组照样显示出来。 */
.dp-sel-tools { display: inline-flex; align-items: center; gap: 6px; }
.dp-sel-tools[hidden] { display: none; }
.dp-detail-acts .btn[disabled] { opacity: .45; cursor: not-allowed; }
/* [hidden] 显式兜底：作者样式里的任何 display 都会盖掉 UA 的 [hidden]{display:none}，
   这两个按钮要能在两种模式间来回切，必须保证 hidden 真的隐藏得掉 */
.dp-detail-acts .btn[hidden] { display: none; }
/* 手机上把工具栏压扁：标签和按钮都缩一档字号/内边距，
   让「全部 收藏 打卡」+「编辑 删除」+「全选 取消 确定」都稳在一行。
   选择模式下按钮组从 2 个变 5 个，是本页最挤的时刻，按那一刻的宽度来定档。 */
@media (max-width: 640px) {
    .dp-detail-bar { gap: 3px; }
    .dp-detail-tabs { gap: 3px; }
    .dp-detail-tabs .dp-tab { padding: 5px 8px; font-size: 12px; line-height: 1.35; }
    .dp-detail-acts { gap: 3px; }
    /* 选择器要写到 .dp-detail-bar 前缀：基类 .btn 定义在文件更靠后，
       同权重（0,2,0）下后者胜出，光写 .dp-detail-acts .btn 压不住它，
       按钮会保持基础 padding:10px 16px → 44px 高，比 28px 的标签高出一头。
       这里权重提到 (0,3,0)，再加上 line-height:1.35 锁死行高，
       两种模式工具栏高度才能一致。 */
    .dp-detail-bar .dp-detail-acts .btn,
    .dp-detail-bar .dp-sel-tools .btn {
        padding: 5px 6px;
        font-size: 12px;
        line-height: 1.35;
        border-radius: 6px;
    }
    /* 选择模式下 5 个按钮 + 计数要挤进 339px，条数文案越短越稳。
       「已选 3 条」比「已选 3 条记录」省 4 个字，意思一样清楚。 */
    .dp-sel-count { font-size: 11px; }
    .dp-sel-tools { gap: 3px; }
    /* 手机上的标签只留「收藏 / 打卡」，去掉前面的 🟡🔵：
       每个 emoji 连空格要吃掉 18px，两个就是 36px —— 正是差的那几像素。
       颜色区分在表格里的徽章上还有，标签上省掉不影响辨认。 */
    .dp-detail-tabs .dp-tab .tab-emoji { display: none; }
}
/* 极窄屏（iPhone SE 320/360 这一类）：5 个按钮在 320px 里怎么挤都放不下，
   那就痛快地整组折到第二行 —— 但只折一次，且两行都是满宽对齐，看着是故意的。 */
@media (max-width: 380px) {
    .dp-detail-bar { flex-wrap: wrap; row-gap: 6px; }
    .dp-detail-acts { justify-content: flex-end; flex: 0 0 100%; }
}
/* 勾选列：只在选择模式出现，窄到不抢戏但点得到 */
.dp-ck-col { width: 34px; text-align: center; }
.dp-ck-col input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; margin: 0; }
#dpDetailTable tbody tr.dp-detail-row:hover { background: rgba(255,255,255,0.55); }
/* 选择模式下点整行可切换勾选，所以整行给个手型；普通模式没这功能，不要误导 */
#dpDetailTable tbody tr.dp-detail-row.dp-sel-mode { cursor: pointer; }
.dp-detail-row input[type="checkbox"] { vertical-align: middle; }
.dp-tab {
    border: 1px solid var(--glass-border);
    background: rgba(255,255,255,0.7);
    color: var(--text-light);
    border-radius: 999px;
    padding: 6px 14px;
    font-size: 13px;
    cursor: pointer;
    transition: all .15s ease;
}
.dp-tab:hover { background: rgba(255,255,255,0.95); }
.dp-tab.active {
    background: var(--primary);
    color: #fff;
    border-color: var(--primary);
    font-weight: 600;
}

/* ── 点评页：分类统计拆成「收藏 / 打卡」两个独立小表 ── */
.dp-cat-sub { margin-bottom: 14px; }
.dp-cat-sub:last-child { margin-bottom: 0; }
.dp-cat-sub-title { font-size: 13.5px; font-weight: 700; margin-bottom: 6px; }

/* ── 点评页：评价目录（每日评分/差评）可编辑单元格 ── */
.dp-review-date { font-weight: 600; white-space: nowrap; }
.dp-review-input {
    width: 76px;
    padding: 4px 6px;
    border: 1px solid var(--glass-border);
    border-radius: 6px;
    font-size: 13px;
    text-align: center;
    background: #fff;
    color: var(--text);
}
.dp-review-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px rgba(99,102,241,0.18);
}
/* 自动延续自前一天的评分格（灰显提示，仍可编辑覆盖） */
.dp-review-input.dp-review-cont {
    background: rgba(0,0,0,0.035);
    color: var(--text-light);
    font-style: italic;
}
.dp-review-save { margin: 0 auto; display: block; }
/* 「今天」小标：评价目录最后一行，说明这行是延续值、明天还没到 */
.dp-review-today {
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    color: #fff;
    background: var(--primary);
    vertical-align: middle;
}
.dp-row-actions { display: flex; gap: 6px; white-space: nowrap; }
.dp-row-actions .btn {
    height: var(--btn-h-xs);
    padding: 0 var(--btn-px-xs);
    font-size: var(--btn-fs-xs);
    border-radius: var(--btn-r-xs);
}
.dp-row-actions .btn-danger { background: #fdf3f3; border: 1px solid #fecaca; color: #dc2626; }
.dp-row-actions .btn-outline { border: 1px solid var(--primary); color: var(--primary); background: transparent; }
/* .btn-xs 的尺寸与配色已并入按钮阶梯（见文件上方「按钮」一节），
   这里原来那份重复定义已删除 —— 它和 .btn-primary / .btn-upload 是
   同一个"橙底白字"，三处各写一遍只会让改配色变成三处同步。 */

/* ── 面板/卡片 ── */
.panel {
    background: linear-gradient(135deg, rgba(255,255,255,0.94), rgba(255,255,255,0.82));
    border: 1px solid var(--glass-border);
    border-radius: 22px;
    padding: 16px;
    box-shadow: var(--glass-shadow);
    margin-bottom: 16px;
}
/* 仪表盘面板标题：左侧色条区分（仅作用于首页看板，不影响其它页面的 panel） */
.dash-panel .panel-title {
    display: flex; align-items: center; gap: 7px;
    font-size: 15px; font-weight: 600; margin-bottom: 12px;
}
.dash-panel .panel-title::before {
    content: ''; width: 3px; height: 14px; border-radius: 2px;
    background: var(--primary); flex: none;
}
.grid-2 {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}
.chart { height: 280px; width: 100%; }
.chart-lg { height: 300px; }

/* ── 表格通用 ── */
.table-wrap {
    position: relative;
    overflow-x: auto;
    background: linear-gradient(135deg, rgba(255,255,255,0.94), rgba(255,255,255,0.82));
    border: 1px solid var(--glass-border);
    border-radius: 22px;
    padding: 12px;
    box-shadow: var(--glass-shadow);
}
.table-wrap table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    min-width: 400px;
}
.table-wrap th, .table-wrap td {
    padding: 10px 12px;
    text-align: left;
    border-bottom: 1px solid rgba(0,0,0,0.06);
    white-space: nowrap;
}
.table-wrap th { background: rgba(255,255,255,0.35); font-weight: 600; color: #555; }
.table-wrap tr:hover td { background: rgba(255,255,255,0.3); }
.table-wrap .total-row td { background: rgba(255,107,53,0.12); font-weight: 700; }

/* ── 明细表头冻结（竖向滚动时表头固定）── */
.table-wrap.sticky-head {
    max-height: 70vh;          /* 放宽浏览区域 */
    overflow: auto;            /* 同时支持窄屏横向滚动 */
    /* 纵向【不】contain —— 这是「拉不到表头」最直接的命中点。
       吸顶表头（下面 thead th 的 position:sticky）正是用户口中的"表头"，
       而这类表格往往是页面里最长的一块。纵向 contain 会把滚到边界的手势
       锁死在表格内部：滑到表格底部后想再往上回到表头/上方内容时，
       表格自己不动、外层也收不到手势，手感就是"卡住、拉不回去"。
       纵向一律放行，手势才能链回外层（.ord-panel → .content）。
       横向仍 contain：窄屏列多必须能横向滑，滑到头不该带动整页。 */
    overscroll-behavior-x: contain;
    overscroll-behavior-y: auto;
    padding: 0 4px 4px;        /* 去掉上内边距，避免表头吸顶后上方留缝；同时减小侧/下边距 */
    border-radius: 14px;       /* 比常规 table-wrap 更紧凑的圆角 */
}
.table-wrap.sticky-head thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    background: rgba(255,255,255,0.97);
    box-shadow: 0 1px 0 rgba(0,0,0,0.14);
}
/* 金额/操作等次要列在窄屏横向滚动时也更突出 */
.table-wrap.sticky-head thead th:first-child { z-index: 4; }

/* 订货明细按日期分组：分组容器放行 sticky，并在单一滚动区内逐组冻结列头 */
#orderDetailTable.sticky-head .order-group { overflow: visible; }
#orderDetailTable.sticky-head .order-group-head {
    border-top-left-radius: var(--radius);
    border-top-right-radius: var(--radius);
}
#orderDetailTable.sticky-head .order-group-table tbody tr:last-child td:first-child {
    border-bottom-left-radius: var(--radius);
}
#orderDetailTable.sticky-head .order-group-table tbody tr:last-child td:last-child {
    border-bottom-right-radius: var(--radius);
}

.empty-tip { color: var(--text-light); text-align: center; padding: 20px; }
.hint { font-size: 12px; color: var(--text-light); margin-top: 8px; }

/* ── 说明标注：连续成段的 .hint 归集成一条可点开的标注 ─────────────
   盘点页卡片下面原本平铺三行说明，手机端白吃近 200px，面板一展开就顶出屏外。
   手机端折成「💡 说明 · 3 条 ▾」一行，默认收起、点开才占位；
   桌面高度不紧张，照旧平铺一眼扫到更好 —— 所以按钮在桌面不出。
   收起态用 height:0（配 overflow:hidden），不用 display:none ——
   抽屉动画要量「展开后的真实高度」，display:none 量出来是 0。 */
.hints-fold { position: relative; }
.hints-toggle {
    display: none;                 /* 桌面不折，手机端断点里改回 inline-flex */
    align-items: center; gap: 5px;
    margin-top: 8px; padding: 4px 9px;
    font: inherit; font-size: 11.5px; line-height: 1.5;
    color: var(--text-light);
    background: rgba(0, 0, 0, .03);
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--dur-tap) var(--ios-ease),
                border-color var(--dur-tap) var(--ios-ease);
}
.hints-toggle:active { background: rgba(0, 0, 0, .08); }
.hints-ico { font-size: 11px; }
.hints-caret {
    display: inline-block; font-size: 9px; line-height: 1;
    transition: transform var(--dur-fast) var(--ios-out);
}
.hints-fold.open .hints-caret { transform: rotate(180deg); }
.hints-body { overflow: hidden; }  /* 折叠时裁掉；展开时 height:auto */

/* ── 考勤页说明：桌面也默认收起 ──────────────────────────────
   通用规则是「只在手机端折」—— 桌面高度不紧张，平铺一眼扫到更好。
   考勤页是例外：表格横着 31 列、员工多了竖着也长，尤其「工资规则」
   那行平铺下来要占两行，把表格往下顶小半屏。所以容器打了
   data-hints-always 的，桌面同样收成「💡 说明 · N 条」一行。
   行距那两行的活儿由移动端断点代劳，这里按相同数值再写一遍：
   断点是 max-width: 768px，够不到桌面。 */
.hints-fold[data-always] .hints-toggle { display: inline-flex; }
.hints-fold[data-always] .hints-body { height: 0; }
.hints-fold[data-always].open .hints-body { height: auto; }
.hints-fold[data-always] .hints-body .hint { margin-top: 6px; }
.hints-fold[data-always] .hints-body .hint:first-child { margin-top: 8px; }
.hints-fold[data-always] .hints-toggle:hover {
    background: rgba(0, 0, 0, .06);
    border-color: #b9d8c6;
}

/* ── 理论消耗量数值高亮 ── */
.pos-val { color: #d35400; }
.neg-val { color: #c0392b; }

/* ── 订货明细按日期分组 ── */
.order-group {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 12px;
    overflow: hidden;
    background: var(--card);
}
.order-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    background: #fff7f2;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
    gap: 6px;
}
.order-group-date { font-weight: 700; color: var(--primary, #ff6b35); }
.order-group-meta { font-size: 12px; color: #777; }
.order-group-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.order-group-table th, .order-group-table td {
    padding: 8px 12px;
    border-bottom: 1px solid var(--border);
    text-align: left;
    white-space: nowrap;
}
.order-group-table th {
    background: #fafafa; font-weight: 600; color: #555; font-size: 12px;
}
.order-group-table tbody tr:last-child td { border-bottom: none; }
.order-group-table tbody tr:hover td { background: #f9f9f9; }

/* ── 考勤表格 ── */
.attendance-table-wrap {
    overflow-x: auto;
    background: var(--card);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
.attendance-table {
    border-collapse: collapse;
    font-size: 12px;
    min-width: 900px;
}
.attendance-table th, .attendance-table td {
    padding: 6px 4px;
    text-align: center;
    border: 1px solid #f0f0f0;
}
.attendance-table th { background: #fafafa; font-weight: 600; color: #555; }
.attendance-table .sticky-name {
    position: sticky;
    left: 0;
    background: #fff;
    z-index: 5;
    min-width: 90px;
    text-align: left;
    padding-left: 10px;
}
.attendance-table thead th.sticky-name { background: #fafafa; }

.emp-cell { display: flex; flex-direction: column; }
.emp-name { font-weight: 600; }
.emp-pos { font-size: 10px; color: var(--text-light); }

.att-cell {
    width: 34px;
    height: 30px;
    cursor: pointer;
    transition: background 0.2s;
    font-size: 11px;
}
.att-cell:hover { filter: brightness(0.95); }

.att-present { background: #dcfce7; color: #166534; }
.att-off { background: #fef3c7; color: #92400e; }
.att-leave { background: #fee2e2; color: #991b1b; }
.att-half_day { background: #ede9fe; color: #5b21b6; }
.att-none { background: #fafafa; color: #cfdcd4; }
.att-disabled { background: #f5f5f5; cursor: default; }

/* ── 图例 ── */
.legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; font-size: 12px; color: #666; }
.legend-item { display: flex; align-items: center; gap: 5px; }
.dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.dot-present { background: var(--green); }
.dot-off { background: var(--yellow); }
.dot-leave { background: var(--red); }
.dot-half { background: var(--purple); }
.dot-empty { background: #fafafa; border: 1px solid var(--border); }

/* ── 平台标签页 ── */
.platform-tabs {
    display: flex;
    gap: 10px;
    margin-bottom: 16px;
}
.tab-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    border: 1px solid var(--border);
    background: var(--card);
    border-radius: 10px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    color: var(--text-light);
}
.tab-btn.active { border-color: var(--primary); color: var(--primary); background: #fff3ed; }
.platform-logo {
    width: 24px;
    height: 24px;
    border-radius: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    color: #fff;
    font-weight: 700;
}
.platform-logo.meituan { background: #ffd100; color: #333; }
.platform-logo.meicai { background: #22a7f0; }

/* ── 按钮 ──
   基础档 = md（38px）。用 inline-flex + height 而不是 padding：
   ① 高度可控（padding 驱动时，行高会掺进来，实际高度随字体浮动）；
   ② inline-flex 让图标和文字天然垂直居中，不用再靠 line-height 凑；
   ③ 左边距交给 gap（各容器自己写），按钮只负责自己这一格。
   white-space: nowrap 收在这里，省得每个容器再写一遍 —— 按钮文字
   折行从来都是 bug 不是需求。 */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: var(--btn-h-md);
    padding: 0 var(--btn-px-md);
    border-radius: var(--btn-r-md);
    border: none;
    font-size: var(--btn-fs-md);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
}

/* ── 尺寸档（类名保留，只把它们映射到阶梯上）──
   .btn-sm → sm 档；.btn-xs / .btn-upload → xs 档。
   这三个类名在 index.html / app.js 里有 70+ 处调用，改名会牵动全部调用点，
   所以只改它们的定义，不动名字。 */
.btn-sm {
    height: var(--btn-h-sm);
    padding: 0 var(--btn-px-sm);
    font-size: var(--btn-fs-sm);
    border-radius: var(--btn-r-sm);
}
.btn-xs,
.btn-upload {
    height: var(--btn-h-xs);
    padding: 0 var(--btn-px-xs);
    font-size: var(--btn-fs-xs);
    border-radius: var(--btn-r-xs);
}

/* ── 语义色 ──
   橙底白字原先在三处各写了一遍（.btn-primary / .btn-xs / .btn-upload），
   改配色得同时改三处，漏一处就出现两种橙。合并成一组选择器，
   配色与尺寸彻底分离：色只管色，尺寸由上面的档位管。 */
.btn-primary,
.btn-xs,
.btn-upload {
    background: var(--primary);
    color: #fff;
    border: none;
}
.btn-primary:hover,
.btn-xs:hover,
.btn-upload:hover { background: var(--primary-dark); }
.btn-outline { background: transparent; border: 1px solid var(--primary); color: var(--primary); }
.btn-outline:hover { background: #fff3ed; }

/* ── 弹窗 ── */
.modal {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    /* 200 → 10100：底部停靠坞是 10000，弹窗只有 200 的话，
       弹窗底部那一截（正好是「取消 / 保存」）会被坞整个盖住。
       手机端沉底后弹窗本就该盖住底栏和备案条（iOS 的 sheet 也是全盖），
       z-index 必须压过坞。 */
    z-index: 10100;
    padding: 16px;
}
.modal-content {
    position: relative;
    background: linear-gradient(135deg, rgba(255,255,255,0.85), rgba(255,255,255,0.6));
    border: 1px solid var(--glass-border);
    border-radius: 24px;
    padding: 20px;
    width: 100%;
    max-width: 480px;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 24px 60px rgba(31,120,72,0.22);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
}
.modal-content h3 { margin-bottom: 16px; }
/* 弹窗里的说明段：批量操作这类「留空=不改」的语义必须先讲清楚，避免误改一批 */
.modal-tip {
    margin: -8px 0 14px;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--text-light);
    background: rgba(99,102,241,0.06);
    border-left: 3px solid var(--primary);
    border-radius: 0 6px 6px 0;
    padding: 8px 10px;
}
.modal-tip b { color: var(--primary); }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }
.form-group { margin-bottom: 12px; }
.form-group label { display: block; font-size: 13px; color: #555; margin-bottom: 5px; }
/* 「自动」小标：标明这一格是系统按其它字段推算带出来的，店主不必自己算 */
.form-group label .label-auto {
    display: inline-block; margin-left: 6px; padding: 1px 6px;
    font-size: 10px; font-weight: 500; line-height: 15px;
    color: #0369a1; background: #e0f2fe; border-radius: 999px;
    vertical-align: 1px;
}
.form-group input, .form-group select, .form-group textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 14px;
}
.form-group textarea { resize: vertical; font-family: monospace; }
.form-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 640px) {
    .form-grid { grid-template-columns: 1fr 1fr; }
    .form-grid .form-group:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* ── Toast（v11.0.10 重做） ──
   原来所有提示一律黑底，保存成功和保存失败长得一模一样 —— 门店里手快，
   一眼扫过去分不清是成了还是砸了，得停下来读一遍字才知道。

   现在按语义分四色，并在左侧给图标：颜色是"不用读就能懂"的信息通道。
   类型由 app.js 的 _toastKind() 按文案自动推断，
   不用去改那 300 多处 showToast('...') 调用。

   另外两个改动：
     · 位置从正中央挪到上方 22%。正中央会压住刚点过的按钮和正在填的输入框，
       而上方的空白区在手机竖屏上基本是页面标题，挡了也不影响操作。
     · 底部加一条倒计时进度条，让人知道这条提示还要待多久 ——
       原来的做法是到点突然消失，长提示（比如 4 秒的导入结果）会让人
       怀疑自己是不是看漏了。 */
#toast {
    position: fixed;
    top: 22%;
    left: 50%;
    /* 三条 transform 必须一起写：居中的 translate(-50%) 和动画的 translateY、scale
       是同一个属性，写漏了居中就会在动画开始那一刻跳到屏幕右边 */
    transform: translate(-50%, 0) scale(.88);
    background: rgba(24, 28, 36, .93);
    color: #fff;
    padding: 11px 18px;
    border-radius: 12px;
    font-size: 14px;
    line-height: 1.45;
    z-index: 300;
    opacity: 0;
    pointer-events: none;
    max-width: min(78vw, 360px);
    box-shadow: 0 8px 26px rgba(0, 0, 0, .22);
    /* 只过渡 opacity / transform，不写 all —— all 会让 background 也跟着渐变，
       换类型时（比如连着点一次失败一次成功）会看见颜色缓慢爬过去 */
    transition: opacity .2s ease-out, transform .38s cubic-bezier(.34, 1.42, .64, 1);
    overflow: hidden;
}
#toast.show {
    opacity: 1;
    transform: translate(-50%, 0) scale(1);
    /* 退场走快一点的缓动，让消失显得干脆；回弹曲线留给入场 */
    transition: opacity .18s ease-in, transform .18s ease-in;
}
/* 图标 + 文字：图标用 ::before 塞 emoji，不额外建 DOM 节点 */
#toast::before { margin-right: 7px; font-size: 15px; }
#toast.t-success { background: rgba(16, 137, 97, .95); }
#toast.t-success::before { content: '✓'; }
#toast.t-error   { background: rgba(197, 48, 48, .95); }
#toast.t-error::before   { content: '✕'; }
#toast.t-warning { background: rgba(180, 108, 12, .95); }
#toast.t-warning::before { content: '!'; font-weight: 800; }
#toast.t-info    { background: rgba(24, 28, 36, .93); }
#toast.t-info::before    { content: 'i'; font-weight: 800; font-style: italic; }

/* 倒计时进度条：宽度由 JS 设的 --toast-ms 驱动，
   从 100% 线性收到 0，收完提示正好消失 */
#toast .toast-bar {
    position: absolute;
    left: 0; bottom: 0;
    height: 2px;
    width: 100%;
    background: rgba(255, 255, 255, .45);
    transform-origin: 0 50%;
    transform: scaleX(0);
}
#toast.show .toast-bar {
    animation: toastBar var(--toast-ms, 2000ms) linear forwards;
}
@keyframes toastBar {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

/* ── 切页进度条（v11.0.10） ──
   切页要先等数据，快则 30ms、慢则两三百毫秒。这段时间页面是空的 ——
   白屏裸奔，手指点了却没反应，最容易让人以为"又卡了"。
   顶部一条 2px 的进度条把这段等待变成看得见的东西：
   按下瞬间就走，比任何动画都能先一步给出"收到了"的反馈。
   进度分两段：起步冲到 70%（无论数据快慢，先给足反馈），
   数据真正到位后再推到 100% 并淡出。
   注意这只是"忙"的指示器，不是真实百分比 —— 前端拿不到后端加载比例，
   硬编一个假百分比反而更糟，这里刻意做成"先快后补"的观感。 */
#pageProgress {
    position: fixed;
    top: 0; left: 0;
    width: 100%;
    height: 2px;
    background: var(--primary);
    z-index: 400;
    transform-origin: 0 50%;
    transform: scaleX(0);
    opacity: 0;
    pointer-events: none;
    box-shadow: 0 0 8px rgba(255, 107, 53, .55);
}
#pageProgress.pp-busy {
    opacity: 1;
    transform: scaleX(.7);
    transition: transform .28s cubic-bezier(.2, .7, .35, 1), opacity .12s ease-out;
}
#pageProgress.pp-done {
    opacity: 1;
    transform: scaleX(1);
    transition: transform .16s ease-out, opacity .22s ease-out .06s;
}

/* ── 报表 ── */
.report-tabs { display: flex; gap: 10px; margin-bottom: 16px; overflow-x: auto; padding-bottom: 2px; }
.rate { color: var(--text-light); font-size: 11px; }

.badge { padding: 3px 8px; border-radius: 4px; font-size: 12px; }
.badge.collect { background: #fef3c7; color: #92400e; }
.badge.checkin { background: #e0f2fe; color: #0369a1; }

.num-present { color: var(--green); font-weight: 600; }
.num-off { color: var(--yellow); font-weight: 600; }
.num-leave { color: var(--red); font-weight: 600; }

/* ── 上传功能 ── */
/* .btn-upload 的尺寸与配色同样已并入按钮阶梯（见「按钮」一节）——
   它原来自己写了一套 primary 配色 + 7px 12px 尺寸，和 .btn-sm 是同一档，
   只是圆角差 2px。现在归到 xs 档，配色复用上面合并后的那一组。 */

.modal-wide { max-width: 720px; }

/* V2.1（2026-10-01）：类型按钮紧凑化。
   原先是 .tab-btn 通用样式（padding 10px 16px、font 14px、gap 8px）
   直接套用，7 个按钮在 678px 宽里排不下 → 挤成两行，
   第二行只剩「🏢 公司代采」一个，还被压成竖排单字（截图里很难看）。
   这里按容器限定收窄：字号降一档、内边距减半、间距 6px，
   一行 4 个 × 两行，排列均匀。
   ⚠ 只作用于 .upload-type-tabs 内部，不影响别处共用的 .tab-btn。 */
.upload-type-tabs {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}
.upload-type-tabs .tab-btn {
    flex: 0 1 auto;
    padding: 6px 11px;
    gap: 5px;
    font-size: 12.5px;
    font-weight: 500;
    border-radius: 8px;
    line-height: 1.4;
    white-space: nowrap;
    min-height: 0;
}
.upload-type-tabs .tab-btn.active { font-weight: 600; }

/* 脚注样式：把原来那条占一整行、带底色内边距的蓝框提示，
   降级成一行灰色小字，信息不丢但不抢版面 */
.upload-tip {
    margin: 0 0 12px;
    font-size: var(--ds-fs-xs);
    line-height: 1.6;
    color: var(--ds-text-3);
}

/* ── 营收识别分流入口（2026-10-01 识别入口集中化）──────────────────
   营收识别走的是独立链路（/api/biz-log/recognize + #blShot），
   不做成第 8 个 tab，而是在 tab 下方给一个跳转入口。
   视觉上要满足三点：
     ① 与上方 7 个 tab 明确分隔 —— 用上边框 + 上下留白，
        否则会被误认成"第 8 个类型按钮"（这正是要避免的）
     ② 不抢 drop-zone 的注意力 —— 用描边按钮而非主色实心
     ③ 说明文字要能一行读完，不做折叠 */
.upload-alt-entry {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: -2px 0 14px;
    padding-top: 12px;
    border-top: 1px dashed var(--ds-border);
}
.upload-alt-tip {
    flex: 1 1 200px;
    min-width: 0;
    font-size: var(--ds-fs-xs);
    line-height: 1.55;
    color: var(--ds-text-3);
}
/* 窄屏让按钮独占一行，说明文字换到下一行 —— 并排会把文字挤成竖排 */
@media (max-width: 560px) {
    .upload-alt-entry .btn { width: 100%; justify-content: center; }
}

.drop-zone {
    border: 2px dashed var(--border);
    border-radius: 12px;
    padding: 32px 20px;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s;
    margin-bottom: 12px;
}
.drop-zone:hover { border-color: var(--primary); background: #fff9f5; }
.drop-zone-icon { font-size: 40px; margin-bottom: 8px; }
.drop-zone-text { font-size: 15px; font-weight: 600; color: var(--text); }
.drop-zone-sub { font-size: 12px; color: var(--text-light); margin-top: 4px; }

.upload-progress {
    display: flex;
    align-items: center;
    gap: 12px;
    justify-content: center;
    padding: 20px;
    color: var(--text-light);
}
.spinner {
    width: 24px;
    height: 24px;
    border: 3px solid var(--border);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.preview-info {
    background: #f8f9fa;
    border-radius: 8px;
    padding: 10px 14px;
    margin-bottom: 12px;
    font-size: 13px;
    color: #555;
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
}
.preview-info .tag { background: #fff; border: 1px solid var(--border); border-radius: 4px; padding: 2px 8px; }

.inv-month-input {
    width: 78px;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 12px;
    color: #1f2a24;
    background: #fff;
}
.inv-month-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px rgba(255,107,53,.15); }

.preview-table-wrap table { min-width: 500px; }
.preview-table-wrap td[contenteditable] {
    outline: none;
    cursor: text;
    min-width: 60px;
}
.preview-table-wrap td[contenteditable]:focus { background: #fff7ed; }
.preview-table-wrap .cell-edit-badge { color: var(--text-light); font-size: 11px; }

.file-list { margin: 8px 0 12px; }
.file-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    background: #f8f9fa;
    border-radius: 6px;
    font-size: 13px;
    margin-bottom: 4px;
}
.preview-errors {
    background: #fdf3f3;
    border: 1px solid #fecaca;
    border-radius: 8px;
    padding: 10px 14px;
    margin-bottom: 12px;
    font-size: 13px;
    color: #991b1b;
}

/* ── 预览/编辑两态 ── */
.preview-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 12px;
}
.preview-mode-tabs {
    display: flex;
    gap: 6px;
    background: #f1f5f9;
    border-radius: 8px;
    padding: 3px;
}
.mode-btn {
    border: none;
    background: transparent;
    padding: 6px 14px;
    border-radius: 6px;
    font-size: 13px;
    color: #64748b;
    cursor: pointer;
    transition: all .2s;
}
.mode-btn.active {
    background: #fff;
    color: #1e40af;
    font-weight: 600;
    box-shadow: 0 1px 3px rgba(0,0,0,.1);
}
.preview-missing-tip {
    background: #fffbeb;
    border: 1px solid #fde68a;
    border-radius: 8px;
    padding: 10px 14px;
    margin-bottom: 12px;
    font-size: 13px;
    color: #92400e;
}
/* 待补充单元格：红色边框 + 黄色底纹 */
.preview-table-wrap td.cell-missing {
    background: #fef3c7;
    border: 1px solid #f59e0b;
    position: relative;
}
.preview-table-wrap td.cell-missing .placeholder {
    color: #d97706;
    font-style: italic;
    opacity: .8;
    display: block;
    min-width: 50px;
}
.preview-table-wrap td.cell-missing:hover {
    background: #fde68a;
}
.preview-table-wrap td.cell-missing[data-missing="1"]::after {
    content: '待补充';
    position: absolute;
    top: -6px;
    right: 4px;
    font-size: 10px;
    color: #b45309;
    background: #fef3c7;
    padding: 0 3px;
    border-radius: 3px;
}
.preview-table-wrap td[data-missing="1"] span[contenteditable="true"] {
    background: #fff7ed;
    border: 1px dashed #f59e0b;
    border-radius: 4px;
    padding: 1px 4px;
}

/* ── 月度盘点 ── */
.inv-summary-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}
/* 手机端盘点汇总卡紧凑化（用户反馈"这两个太大"）：
   原样走通用 .stat-card（padding 16 / 圆角 22 / 值 22px），393px 宽下
   每张高约 86px，两张卡吃掉小半屏。只压 .inv-summary-cards 容器内的卡，
   看板页（dpStatsCards）/花名册页（rosterCards）的同类卡不受影响。
   压缩后每张 ~46px：padding 16→7、圆角 22→12、标签 13→10px、
   金额 22→15px（¥41,276.81 在 178px 卡宽内仍完整显示，不截断）。 */
@media (max-width: 768px) {
    .inv-summary-cards { gap: 6px; margin-bottom: 10px; }
    .inv-summary-cards .stat-card {
        padding: 7px 10px 8px;
        border-radius: 12px;
    }
    .inv-summary-cards .stat-label {
        font-size: 10px; margin-bottom: 2px;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .inv-summary-cards .stat-value { font-size: 15px; }
}
/* 盘点管理工具栏 */
.inv-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    background: rgba(255,255,255,0.72);
    border: 1px solid rgba(255,255,255,0.6);
    border-radius: 16px;
    padding: 10px 14px;
    margin-bottom: 16px;
    backdrop-filter: blur(18px) saturate(180%);
    -webkit-backdrop-filter: blur(18px) saturate(180%);
    box-shadow: 0 6px 20px rgba(34,120,80,0.08),
                inset 0 1px 0 rgba(255,255,255,0.7);
}
.inv-toolbar-label { font-weight: 700; font-size: 14px; color: #2d6a4f; }
.inv-toolbar select {
    padding: 7px 10px;
    border: 1px solid var(--border);
    border-radius: 10px;
    font-size: 14px;
    background: rgba(255,255,255,0.9);
    color: var(--text);
}
.inv-edit-hint { font-size: 13px; color: var(--primary); font-weight: 600; }
/* 盘点明细行内编辑输入框 */
.inv-edit-input {
    width: 84px;
    padding: 5px 7px;
    border: 1px solid var(--primary);
    border-radius: 8px;
    font-size: 13px;
    background: #fff;
    color: var(--text);
    text-align: right;
}
/* 盘点明细「订货平台」下拉（切换即改归属） */
.inv-pf-select {
    padding: 4px 6px;
    border-radius: 8px;
    border: 1px solid #e2ebe6;
    background: #f7faf8;
    font-size: 12px;
    color: var(--text);
    cursor: pointer;
    white-space: nowrap;
}
.inv-pf-select:hover { border-color: var(--primary); }
.inv-pf-select.pf-meituan { background: #fff3c4; border-color: #f2d478; color: #7a5c00; font-weight: 600; }
.inv-pf-select.pf-meicai { background: #dcf1fd; border-color: #a8dcf5; color: #0b5e8a; font-weight: 600; }
.inv-pf-select.pf-procurement { background: #ede9fe; border-color: #c4b5fd; color: #5b21b6; font-weight: 600; }
.inv-pf-select.pf-self { background: #ccfbf1; border-color: #7fd8c8; color: #0f766e; font-weight: 600; }
.inv-pf-select.pf-other { background: #fde8c8; border-color: #f5c98a; color: #92500a; font-weight: 600; }

/* ── 订货页渠道统计卡（四渠道并排，点击切换明细） ── */
.channel-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}
.channel-card {
    background: #fff;
    border-radius: 14px;
    padding: 14px 16px;
    border: 2px solid transparent;
    box-shadow: 0 1px 3px rgba(0,0,0,.06);
    cursor: pointer;
    transition: transform .15s ease, box-shadow .15s ease;
    text-align: left;
}
.channel-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,.10); }
.channel-card .ch-name {
    display: flex; align-items: center; gap: 8px;
    font-weight: 700; font-size: 14px; margin-bottom: 6px;
}
.channel-card .ch-amount { font-size: 22px; font-weight: 800; letter-spacing: -.5px; }
.channel-card .ch-count { font-size: 12px; color: #6b7a72; margin-top: 2px; }
.channel-card.ch-mt { background: linear-gradient(135deg, #fffbf0, #fff3c4); }
.channel-card.ch-mt .ch-amount { color: #b45309; }
.channel-card.ch-mc { background: linear-gradient(135deg, #f0f9ff, #dcf1fd); }
.channel-card.ch-mc .ch-amount { color: #0369a1; }
.channel-card.ch-pc { background: linear-gradient(135deg, #faf5ff, #ede9fe); }
.channel-card.ch-pc .ch-amount { color: #6d28d9; }
.channel-card.ch-sf { background: linear-gradient(135deg, #f0fdfa, #ccfbf1); }
.channel-card.ch-sf .ch-amount { color: #0f766e; }
.channel-card.active {
    border-color: currentColor;
    box-shadow: 0 4px 14px rgba(0,0,0,.14);
}
.channel-card.active.ch-mt { border-color: #f59e0b; }
.channel-card.active.ch-mc { border-color: #38bdf8; }
.channel-card.active.ch-pc { border-color: #8b5cf6; }
.channel-card.active.ch-sf { border-color: #14b8a6; }
.channel-card.ch-ot { background: linear-gradient(135deg, #fff7ed, #fed7aa); }
.channel-card.ch-ot .ch-amount { color: #c2410c; }
.channel-card.active.ch-ot { border-color: #f59e0b; }
/* 「▾ 查看明细」这一行常驻占位，展开时只淡入、不插入。
   以前只有 .active 才生成 ::after，而 content 的变化没法做过渡 ——
   展开的那一瞬间卡片凭空长高 20px，下方内容硬跳一下，
   和面板的抽屉动画（平滑长高）完全对不上，看着就是"先跳再滑"。
   常驻后卡片高度恒定，实测手机端 .channel-cards 稳定在 214px。 */
.channel-card::after {
    content: '▾ 查看明细';
    display: block;
    font-size: 11px;
    color: inherit;
    opacity: 0;
    margin-top: 4px;
    font-weight: 600;
    transition: opacity .18s var(--ios-ease, ease);
    pointer-events: none;
}
.channel-card.active::after { opacity: .75; }
/* 订货：工作面板（手风琴容器，操作按钮内嵌、随面板显隐） */
.ord-panel { margin-bottom: 16px; }

/* 渠道面板全部收起时的引导提示（2026-09-30 加）。
   以前这种情况会留下一整块空白，用户看不出该做什么；
   给一句明确的下一步，把「空白」变成「指引」。
   视觉上刻意做得轻（虚线框 + 灰字），它是兜底文案，不是主要界面。 */
.ord-empty-tip {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 44px 20px;
    margin-bottom: 16px;
    border: 1.5px dashed var(--border);
    border-radius: 12px;
    background: #f7faf8;
    color: var(--text-light, #8a94a3);
    font-size: 14.5px;
    text-align: center;
}
.ord-empty-icon {
    font-size: 20px;
    line-height: 1;
    animation: ordEmptyNudge 1.8s ease-in-out infinite;
}
@keyframes ordEmptyNudge {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-5px); }
}
@media (prefers-reduced-motion: reduce) {
    .ord-empty-icon { animation: none; }
}
.ord-panel-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: 12px;
    padding: 10px 12px;
    background: #f8f9fb;
    border: 1px solid var(--border);
    border-radius: 12px;
}
.ord-panel-actions .btn { box-shadow: 0 1px 2px rgba(0,0,0,.04); }
/* 数据体检提示条：新店漏录 / 未设月薪等会导致算错工资的缺口，集中提示 */
.audit-bar { margin-bottom: 14px; }
.audit-box {
    border-radius: 12px;
    padding: 12px 16px;
    border: 1px solid;
    font-size: 13px;
    line-height: 1.7;
}
.audit-box ul { margin: 6px 0 0; padding-left: 18px; }
.audit-box li { margin-bottom: 4px; }
.audit-box li:last-child { margin-bottom: 0; }
.audit-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.audit-icon { font-size: 15px; }
.audit-store { color: var(--text-light); font-size: 12px; margin-left: auto; }
.audit-tip { display: block; color: var(--text-light); font-size: 12px; }
.audit-error { background: #fdf3f3; border-color: #fecaca; color: #991b1b; }
.audit-error .audit-tip { color: #b91c1c; }
.audit-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
.audit-warn .audit-tip { color: #b45309; }
.audit-info { background: #eff9f7; border-color: #bfdbfe; color: #1e40af; }
.audit-info .audit-tip { color: #1d4ed8; }

/* ── 数据体检：默认收起成一条 ──────────────────────────────
   体检条一出问题就有四五条，平铺下来把考勤表整个顶下去。
   收起后只剩一颗按钮——但按钮跟着等级走同一套配色（红/橙/蓝），
   不展开也能一眼看出"有事儿 + 多严重"；展开后明细照旧是红字，
   配色由 .audit-error 那几条管，折叠壳一概不碰。 */
.audit-fold .audit-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 11px;
    font: inherit; font-size: 12.5px; line-height: 1.5;
    border: 1px solid; border-radius: 999px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--dur-tap) var(--ios-ease),
                border-color var(--dur-tap) var(--ios-ease);
}
.audit-fold .audit-ico { font-size: 13px; }
.audit-fold .audit-count { font-weight: 600; }
.audit-fold .audit-toggle:active { filter: brightness(.96); }
/* 箭头直接复用说明折叠那颗 .hints-caret 的样式，只是把旋转前缀换成 audit-fold */
.audit-fold.open .hints-caret { transform: rotate(180deg); }
.audit-fold .audit-body { overflow: hidden; height: 0; }
.audit-fold.open .audit-body { height: auto; }
.audit-fold .audit-box { margin-top: 8px; }
/* 等级配色：按钮用和 .audit-box 一样的底/边/字色，收起态也保留严重度 */
.audit-fold.lv-error .audit-toggle { background: #fdf3f3; border-color: #fecaca; color: #991b1b; }
.audit-fold.lv-warn  .audit-toggle { background: #fffbeb; border-color: #fde68a; color: #92400e; }
.audit-fold.lv-info  .audit-toggle { background: #eff9f7; border-color: #bfdbfe; color: #1e40af; }
.audit-fold.lv-error .audit-count { color: #991b1b; }
.audit-fold.lv-warn  .audit-count { color: #92400e; }
.audit-fold.lv-info  .audit-count { color: #1e40af; }

/* 花名册面板标题行：标题与操作按钮同行（粘贴导入 / 一键导出） */
.panel-head-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}
.panel-head-row .panel-title { margin: 0; }
.panel-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.panel-actions button {
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 14px;
    font-size: 13px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: #fff;
    color: var(--text);
    cursor: pointer;
    transition: all .15s;
}
.panel-actions button:hover { background: #f1f5f9; border-color: #94a3b8; }
.panel-actions button.btn-primary {
    background: var(--primary);
    border-color: var(--primary);
    color: #fff;
}
.panel-actions button.btn-primary:hover { filter: brightness(1.06); background: var(--primary); }

/* ── 盘点明细表格：限高 + 区内滚动 ──
   以前表格多长、面板就有多高（200 行 → 8289px），于是展开面板时，
   面板下方那三行说明要在 380ms 内被推走 8305px —— 每帧走 1915px，
   肉眼只看到它「嗖」一下消失，也就是用户说的"跳卡、没有动画"。
   这不是动画曲线能救的，位移量本身太大，只能把位移量压下来。
   限到 50vh 后：页面总高 9152 → 1552，展开的视觉位移 8305 → 705，
   单帧跳变 1915 → 126，帧率仍是满帧 16.7ms。

   （下方 .ord-panel 的新规则已把滚动容器上移到面板本身并配合 JS 限高，
   手机端这条表格限高自然失效、桌面端继续生效，两条并不冲突。） */
.inv-ord-panel > .table-wrap {
    max-height: 50vh;
    /* svh 而不是 dvh —— 这一行就是「滑动弹跳、拉不到表头」的元凶之一。
       dvh 是动态视口单位：Safari 的地址栏/底部工具栏一收一放，它就实时变化。
       于是在同一次滚动里，容器的 max-height 被反复重算，scrollHeight 跟着变，
       scrollTop 的目标位置一直在漂 —— 手感就是"上下乱弹、拉不回表头"。
       svh 取工具栏展开时的高度，是一个静态值，滚动全程不变。
       （手机端这条还会被下方媒体块整块覆盖：手机端表格不再自己滚。） */
    max-height: 50svh;
    overflow-y: auto;
    /* 这里【不写】overscroll-behavior: contain。
       它只该加在最外层滚动容器（.content）上，理由见下方手机端
       「滚动层级：contain 只能加在最外层」那段注释。 */
    /* 底部一层内阴影，暗示下面还有内容、这里可以滑 */
    box-shadow: var(--glass-shadow), inset 0 -18px 14px -14px rgba(0, 0, 0, .16);
}

/* ── 渠道面板：手机端装进视口可见区 ──
   上一条注释里「滚动容器不能设在面板本身」的旧结论已被推翻：
   内联 overflow 清掉后 CSS 类的 overflow-y:auto 会正常接管（已实测），
   当时真正的问题是没有配合 JS 先限高，max-height 加了也白加。
   现在的分工是：
     JS  _fitPanelToViewport()  展开瞬间把「底栏上沿 − 面板顶」的精确
                                可用高写进内联 max-height（窗口旋转后由
                                resize 防抖清除、下次展开重算）；
     CSS 这里给一个宽松兜底（JS 未跑到时不至于把页面撑出几千px），
        并提供内部滚动。手机端表格区 50vh 永远触不到（面板本身只有
        300~500px 高），嵌套滚动不会发生；桌面端面板不受这条影响。 */
/* ── 滚动层级：contain 只能加在最外层 ─────────────────────────────
   第 25 轮真机反馈「盘点条目上下滑会卡住弹跳、拉不到表头」，真凶就是
   下面这条：此前 .content / .ord-panel / .table-wrap 三层【都】写了
   overscroll-behavior: contain。
   contain 的语义是把滚到边界后的手势锁死在当前容器：
     · 不许把手势链给外层 → 外层纹丝不动，表头明明在上面却"拉不回来"；
     · 只能在自己这层原地橡皮筋 → 手感就是"卡住 + 弹跳"。
   三层一起 contain，手势被彻底困在最里面那一层，怎么滑都出不去。
   正确的层级是只拦最外面那一层：
       .content        overscroll-behavior-y: contain  ← 只在这里拦，
         └ .ord-panel  auto  ← 滚到边界交给 .content，于是能一路拉回表头
            └ .table-wrap  手机端已不再自己纵向滚（见下）
   内层一律留默认 auto，手势才能一级级往上链出去。 */
@media (max-width: 768px) {
    .ord-panel {
        max-height: 85vh;
        max-height: 85svh;          /* 同样用 svh 不用 dvh，理由见上方表格那条 */
        overflow-y: auto;
        overscroll-behavior: auto;  /* 关键：手机端面板不再锁手势 */
        box-shadow: var(--glass-shadow), inset 0 -18px 14px -14px rgba(0, 0, 0, .16);
    }
    /* 手机端取消表格自身的限高：面板（.ord-panel）已经是滚动容器，里面再放
       一个能独立纵向滚动的 .table-wrap 就是嵌套滚动 —— iOS 上两层各自维护
       scrollTop，手势归属靠系统猜，必然"滑到底卡住 / 往回拉不动"。
       去掉限高后表格取自然高度、纵向没有可滚区间，纵向手势直接链给
       .ord-panel，手机端便只剩面板这一层内层滚动。
       横向仍要保留（列多 + 列冻结），但只在横向拦，纵向放给面板。 */
    .inv-ord-panel > .table-wrap {
        max-height: none;
        overscroll-behavior-x: contain;   /* 横向滑到头别带动整页 */
        overscroll-behavior-y: auto;      /* 纵向交给 .ord-panel */
        box-shadow: none;                 /* 已不是可滚区，底部内阴影是误导 */
    }
    /* 面板内所有吸顶表头表格（订货明细等）同理：手机端交给面板统一纵向滚，
       表格不再自成滚动区。表头的 position:sticky 会自动改以 .ord-panel
       为滚动容器继续吸顶 —— 于是滑到哪儿表头都在，"拉不到表头"从根上消失。 */
    .ord-panel .table-wrap.sticky-head {
        max-height: none;
    }
}

/* ── 月度盘点渠道面板：统一操作按钮（高度/内边距/圆角一致，整齐一行展示） ── */
.inv-ord-panel .ord-panel-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    padding: 10px 12px;
    background: #f8f9fb;
    border: 1px solid var(--border);
    border-radius: 12px;
}
.inv-ord-panel .ord-panel-actions .btn {
    /* 34px 介于 sm(30) 与 md(38) 之间，是这块面板特意要的"比 sm 松、比 md 紧"。
       高度仍写死，但不再自己写 display/align/justify —— 那是 .btn 基础档的活儿，
       这里重复写一遍只会让以后改基础档时漏掉这处。 */
    height: 34px;
    padding: 0 16px;
    border-radius: 8px;
    font-size: 14px;
    box-shadow: 0 1px 2px rgba(0,0,0,.04);
}
/* ── 盘点录入：统一工具条按钮（高度/内边距/圆角一致，移动端紧凑） ── */
.entry-toolbar { gap: 8px; }
.entry-toolbar .btn {
    /* 同 .ord-panel-actions：34px 是这块工具条的专用档位，保留；
       居中/nowrap 已由 .btn 基础档提供，这里不再重复。 */
    height: 34px;
    padding: 0 14px;
    border-radius: 8px;
    font-size: 13px;
    box-shadow: 0 1px 2px rgba(0,0,0,.04);
}
.entry-toolbar .entry-month,
.entry-toolbar .entry-search { flex: 0 1 auto; }
/* 录入面板：6 个动作按钮排成 3 列网格（输入框/提交/状态独立成行） */
.entry-actions-grid {
    flex: 1 1 100%;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
}
.entry-actions-grid .btn { width: 100%; }
/* 桌面：导出/提交靠右成簇，主次分明、排列整齐 */
.entry-tool-spacer { flex: 1 1 8px; min-width: 8px; }
@media (min-width: 769px) {
    .entry-toolbar .entry-month { width: 150px; }
    .entry-toolbar .entry-search { width: 190px; }
}
/* 移动端：输入整行、按钮 2 列等宽，紧凑一致 */
@media (max-width: 768px) {
    .entry-toolbar { gap: 6px; }
    .entry-toolbar .entry-month,
    .entry-toolbar .entry-search { flex: 1 1 100%; width: 100%; }
    .entry-tool-spacer { display: none; }
    .entry-toolbar .btn { flex: 1 1 calc(50% - 4px); }
    .entry-toolbar #entryStatus { flex: 1 1 100%; }
}
/* 重导入选择弹窗 */
.reimport-opts { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.reimport-opts .btn { width: 100%; }
.inv-columns { align-items: stretch; }
.inv-col { display: flex; flex-direction: column; gap: 4px; }
.inv-col-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 700;
    font-size: 15px;
    padding: 10px 14px;
    border-radius: 10px;
    margin-bottom: 8px;
}
.inv-col-head.meituan-head { background: linear-gradient(135deg, #fff8dc, #ffefb0); color: #7a5c00; }
.inv-col-head.meicai-head { background: linear-gradient(135deg, #e3f4fe, #c4e8fb); color: #0b5e8a; }
.inv-col-head.other-head { background: linear-gradient(135deg, #fef3e2, #fde4c0); color: #92500a; }
.inv-col-head.proc-head { background: linear-gradient(135deg, #f1e9fe, #e2d4fb); color: #5b21b6; }
.inv-col-head.self-head { background: linear-gradient(135deg, #e6faf5, #c9f2e8); color: #0f766e; }
.platform-logo.other { background: #f59e0b; }
.platform-logo.procurement { background: #7c3aed; }
.platform-logo.self { background: #0d9488; }

/* 渠道头部统计徽标 */
.inv-head-stat {
    margin-left: auto;
    font-size: 12px;
    font-weight: 600;
    background: rgba(255,255,255,.75);
    border-radius: 20px;
    padding: 3px 10px;
    white-space: nowrap;
}
.inv-head-stat b { font-size: 13px; }

/* 盘点页四渠道统一网格（美团/美菜/代采/自采，响应式两列） */
.inv-channel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(430px, 1fr));
    gap: 16px;
    align-items: start;
}

/* 公司代采栏（盘点识别为公司代采的物品） */
.inv-proc-panel { border: 1px solid #c4b5fd; }
.inv-proc-banner {
    background: #f5f3ff;
    border: 1px solid #d8ccfb;
    color: #4c1d95;
    border-radius: 10px;
    padding: 10px 14px;
    font-size: 13px;
    line-height: 1.6;
    margin-bottom: 10px;
}
.inv-proc-banner b { color: #5b21b6; }

/* 个人自采栏（盘点） */
.inv-self-panel { border: 1px solid #99e6d5; }

/* 其它/待复核栏（盘点智能归类未识别项） */
.inv-other-panel { border: 1px dashed #f59e0b; }
.inv-other-banner {
    background: #fffbeb;
    border: 1px solid #fcd34d;
    color: #92400e;
    border-radius: 10px;
    padding: 10px 14px;
    font-size: 13px;
    line-height: 1.6;
    margin-bottom: 10px;
}
.inv-other-banner b { color: #b45309; }

/* 预览表智能归类徽标 */
.cls-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}
.cls-badge.cls-mt { background: #fff3c4; color: #7a5c00; }
.cls-badge.cls-mc { background: #dcf1fd; color: #0b5e8a; }
.cls-badge.cls-ot { background: #fde8c8; color: #92500a; }
.cls-badge.cls-pc { background: #ede9fe; color: #5b21b6; }
.review-flag { color: #f59e0b; font-weight: 700; cursor: help; }
.cat-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 6px;
    vertical-align: middle;
}
.cat-select {
    display: inline-block;
    padding: 3px 6px;
    font-size: 12px;
    border: 1px solid rgba(0,0,0,.12);
    border-radius: 8px;
    background: rgba(255,255,255,.85);
    color: #2d3436;
    cursor: pointer;
    max-width: 110px;
}
.cat-select:focus { outline: 2px solid #22a7f0; outline-offset: 1px; }
.cat-select:disabled { opacity: .6; cursor: wait; }

/* ── 品名合并：待归类标记 / 已记忆标记 ── */
.prod-pending-tip {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    margin: 0 0 10px; padding: 8px 12px;
    font-size: 12.5px; color: #92400e; line-height: 1.6;
    background: #fffbeb; border: 1px solid #fde68a;
    border-left: 3px solid #f59e0b; border-radius: 8px;
}
.prod-pending-tip b { color: #b45309; }
.prod-filter {
    display: inline-flex; align-items: center; gap: 5px;
    margin-left: auto; padding: 3px 9px;
    font-size: 12px; color: #b45309; white-space: nowrap; cursor: pointer;
    background: #fff; border: 1px solid #fcd34d; border-radius: 999px;
}
.prod-filter input { accent-color: #f59e0b; cursor: pointer; }
tr.prod-row-pending > td { background: #fffdf5; }
.prod-pending-tag {
    display: inline-block; margin-left: 5px; padding: 1px 6px;
    font-size: 10.5px; line-height: 1.5; color: #92400e; white-space: nowrap;
    background: #fef3c7; border: 1px solid #fcd34d; border-radius: 999px;
}
.prod-mem-tag {
    display: inline-block; margin-left: 4px;
    font-size: 11px; color: #16a34a;
}

/* ── 上传后「还有商品没归类」提示条 ── */
.pending-classify-bar {
    position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
    z-index: 3000; display: none; align-items: center; gap: 12px;
    max-width: min(760px, calc(100vw - 32px)); padding: 12px 16px;
    font-size: 13px; color: #fff; line-height: 1.55;
    background: linear-gradient(135deg, #334155, #1e293b);
    border-radius: 12px; box-shadow: 0 12px 32px rgba(15,23,42,.32);
}
.pending-classify-bar .pcb-text { flex: 1; }
.pending-classify-bar .pcb-text b { color: #fbbf24; }
.pending-classify-bar button {
    padding: 6px 14px; font-size: 12.5px; white-space: nowrap; cursor: pointer;
    border: none; border-radius: 8px; transition: opacity .15s;
}
.pending-classify-bar button:hover { opacity: .86; }
.pending-classify-bar .pcb-go { background: #f59e0b; color: #1f2a24; font-weight: 600; }
.pending-classify-bar .pcb-close { background: rgba(255,255,255,.16); color: #e2e8f0; }
@media (max-width: 640px) {
    .pending-classify-bar { flex-wrap: wrap; bottom: 14px; gap: 8px; }
    .pending-classify-bar .pcb-text { flex: 1 1 100%; }
}
.btn-del {
    border: none;
    background: none;
    color: #cfdcd4;
    cursor: pointer;
    font-size: 13px;
    padding: 2px 6px;
    border-radius: 6px;
}
.btn-del:hover { color: #ef4444; background: #fee2e2; }
.btn-edit {
    border: none;
    background: none;
    color: #cfdcd4;
    cursor: pointer;
    font-size: 13px;
    padding: 2px 6px;
    border-radius: 6px;
}
.btn-edit:hover { color: #2563eb; background: #d8f0ec; }
.nowrap { white-space: nowrap; }
.inv-export-group { display: inline-flex; align-items: center; gap: 4px; }
.inv-export-pf {
    border: 1px solid var(--border);
    background: #fff;
    color: #888;
    cursor: pointer;
    border-radius: 6px;
    padding: 4px 8px;
    font-size: 12px;
}
.inv-export-pf.active { background: var(--primary); border-color: var(--primary); color: #fff; }

/* ── 成本测算 ── */
.cost-form {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 12px;
    margin-bottom: 16px;
}
.cost-item label {
    display: block;
    font-size: 13px;
    color: var(--text-light);
    margin-bottom: 6px;
    font-weight: 600;
}
.cost-input-wrap { position: relative; display: flex; align-items: center; }
.cost-currency {
    position: absolute;
    left: 12px;
    color: var(--text-light);
    font-size: 14px;
    pointer-events: none;
}
.cost-input-wrap input {
    width: 100%;
    padding: 10px 12px 10px 30px;
    border: 1px solid var(--border);
    border-radius: 10px;
    font-size: 15px;
    font-weight: 600;
}
.cost-input-wrap input:focus { outline: none; border-color: var(--primary); }
.auto-flag {
    display: inline-block;
    font-size: 11px;
    color: #0b5e8a;
    background: #dbeefe;
    border-radius: 6px;
    padding: 1px 6px;
    margin-left: 6px;
    white-space: nowrap;
}
.cost-total {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: linear-gradient(135deg, #fff4ec, #ffe3d0);
    border: 1px solid #ffd9b8;
    border-radius: 12px;
    padding: 14px 18px;
}
.cost-total-label { font-weight: 700; font-size: 15px; color: #9a3412; }
.cost-total-value { font-size: 24px; font-weight: 800; color: var(--primary); }

/* ── 利润测算单元 ── */
.profit-calc { margin-top: 4px; }
.profit-revenue-item { max-width: 320px; margin-bottom: 16px; }
.profit-rates {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
    margin-bottom: 18px;
}
.cost-suffix { left: auto; right: 12px; }
.profit-rates .cost-input-wrap input { padding: 10px 30px; }
.profit-results {
    border-top: 1px dashed var(--border);
    padding-top: 14px;
    max-width: 480px;
}
.profit-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 0;
    font-size: 14px;
    border-bottom: 1px solid #f0f0f0;
}
.profit-amt { font-weight: 700; font-variant-numeric: tabular-nums; }
.profit-subtotal { font-weight: 600; color: var(--text-light); }
.profit-net {
    border-bottom: none;
    margin-top: 4px;
    padding: 12px 14px;
    border-radius: 12px;
    /* V2.0：原为 #eafaf0→#d6f5e3 的薄荷绿渐变，饱和度偏高，
       与新的护眼底 #f2f7f4 同色但更艳，会显得"这块在发光"。
       降饱和后与底色同族，仍保留"净利是绿色"的语义。 */
    background: linear-gradient(135deg, #eef7f1, #dcf0e4);
    border: 1px solid #c2e6d0;
}
.profit-net span:first-child { font-weight: 700; color: #166534; }
.profit-net .profit-amt { font-size: 18px; color: #15803d; }

/* ── 竖屏移动端顶栏紧凑适配 ──
   演进史：最初 7 个控件 flex-wrap 折成 3 行（高 156px）→ 改单行横滑
   （高 108px，但月份下拉被挤成一条竖缝、店名与门店下拉重复显示"XX华店"）。
   本版（v3）：高频留一排，低频进浮层 ——
     常驻：🏪 | 门店切换(占满) | 月份 | ⋯
     ⋯ 浮层：门店管理 / 账号 / 密码 / 退出（低频入口，一天点不了几次）
   店名隐藏不丢信息：门店下拉本身就显示当前店名，原来两处重复。
   顶栏内容区 37px → 32px，且横向再无拥挤。 */
/* 桌面/平板不出这个入口 —— 必须写在上面 media 块「之前」：
   同特异性下后者赢，放后面会把手机端的 display:flex 也盖掉（实测踩过） */
.topbar-more { display: none; }
.topbar-menu { display: none; }
@media (max-width: 768px) {
    .topbar-inner { padding: 6px 10px; gap: 8px; }
    .brand { min-width: 0; flex: none; gap: 0; }
    .brand-icon { font-size: 20px; }
    /* 店名 / 版本号 / 编辑笔与门店下拉信息重复，全部收起 */
    .brand-name, .app-version, .brand-edit { display: none; }
    .month-selector { flex: 0 0 auto; min-width: 0; flex-wrap: nowrap; gap: 6px; }
    .month-selector .readonly-badge { flex: none; padding: 3px 7px; font-size: 11px; }
    .month-selector select {
        padding: 0 8px; font-size: 12px; border-radius: 8px;
        min-width: 0;                   /* 覆盖 #globalStore 的 min-width:92px */
        height: 32px; min-height: 32px; /* 覆盖触屏断点的 44px 一刀切 */
    }
    /* v4「左右分裂」格局：左=门店切换、中=页面标题、右=月份+更多。
       门店下拉不再占满 —— 店名就几个字，中间富余宽让给页面标题
       （标题由 app.js 在切页时同步，各页自己的大标题行随之整行省掉）。 */
    #globalStore { flex: 0 1 auto; max-width: 34vw; }
    #globalMonth { flex: 0 0 auto; width: 88px; }      /* 主控月份，完整显示 2026-08 */
    .topbar-title {
        flex: 1 1 auto; min-width: 0;   /* 吃掉中间全部富余宽 */
        font-size: 15px; font-weight: 700; color: var(--text);
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .month-selector .btn-sm {
        padding: 0 10px; font-size: 12px; border-radius: 8px;
        white-space: nowrap; flex: none;
        height: 32px; min-height: 32px;
    }
    /* ⋯ 更多入口：与左侧 select 同高，视觉上就是第四个控件 */
    .topbar-more {
        display: flex; align-items: center; justify-content: center;
        flex: none; width: 34px; height: 32px; padding: 0;
        border: 1px solid var(--border); border-radius: 8px;
        background: var(--bg); color: var(--text);
        font-size: 17px; font-weight: 700; line-height: 1;
        cursor: pointer;
    }
    .topbar-more:active { background: rgba(0,0,0,.06); }
    /* iOS 风格浮层菜单：毛玻璃 + 圆角 + 右上角锚点弹入。
       挂在 .topbar（fixed）下做 absolute 定位，天然贴着顶栏底沿。 */
    .topbar-menu {
        display: none; position: absolute; top: 100%; right: 8px;
        min-width: 176px; padding: 5px;
        background: rgba(255,255,255,.97);
        backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
        border: 1px solid rgba(0,0,0,.06);
        border-radius: 14px;
        box-shadow: 0 12px 32px rgba(0,0,0,.18), 0 2px 8px rgba(0,0,0,.08);
        flex-direction: column; gap: 2px;
        z-index: 300;                   /* 高于顶栏 100，低于锁屏 */
        transform-origin: top right;
    }
    .topbar-menu.open { display: flex; animation: menuPop .18s var(--ios-spring, cubic-bezier(.32,.72,0,1)); }
    /* 菜单行：搬进来的原按钮改为全宽左对齐，44px 触控高度还给它们。
       color 压过 .btn-outline 的品牌橙 —— iOS 菜单行是中性深灰，
       只留退出标红做破坏性提示（见下）。 */
    .topbar-menu .btn {
        display: flex; align-items: center; justify-content: flex-start;
        gap: 8px; width: 100%; min-height: 44px; padding: 0 12px;
        border: none; background: none; border-radius: 9px;
        font-size: 14px; text-align: left; white-space: nowrap;
        color: var(--text); font-weight: 500;
    }
    .topbar-menu .btn:active { background: rgba(0,0,0,.06); }
    /* 退出是破坏性操作，iOS 惯例标红 */
    .topbar-menu .btn[onclick="doLogout()"] { color: #dc2626; font-weight: 600; }
}
@keyframes menuPop {
    from { opacity: 0; transform: scale(.9); }
    to   { opacity: 1; transform: none; }
}

/* 移动端底部导航 8 个按钮适配（新增「营收」页后由 7 项增至 8 项）
   360px 屏：8 × 44px = 352px < 360px，不必横向滚动即可显示全部入口。
   高度压缩：icon 16px + 标签 10px/12px 行高 + padding 4/3 ≈ 40px
   （原 58px），下方内容每屏多出近 20px。

   ⚠ 这里是「宽屏那档」的唯一定义。手机上的更紧凑版本在文件末尾的
   max-width:768px 块里（min-width:34px 那组）—— 两处是有意分档的，
   不要再在别的地方补第三条 .bottom-nav .nav-btn 尺寸，
   三处并存时谁生效只看书写顺序，改一处不生效、还以为没保存。 */
.bottom-nav { overflow-x: auto; }
.bottom-nav .nav-btn { min-width: 40px; padding: 4px 2px 3px; gap: 1px; min-height: 0; }
.nav-btn span:not(.nav-icon) { font-size: 10px; line-height: 12px; white-space: nowrap; }
/* 特异性必须压过 768px 断点的 .nav-icon{font-size:22px}，否则底栏虚高 */
.bottom-nav .nav-icon { font-size: 16px; line-height: 1.2; }

/* ── 锁屏 ── */
.lock-screen {
    position: fixed; inset: 0; z-index: 9999;
    background: linear-gradient(160deg, #d8f3e3 0%, #ecfbf1 45%, #dff5e8 100%);
    display: flex; align-items: center; justify-content: center;
}
.lock-card {
    background: linear-gradient(135deg, rgba(255,255,255,0.85), rgba(255,255,255,0.55));
    border: 1px solid rgba(255,255,255,0.7);
    border-radius: 24px; padding: 36px 32px;
    width: min(360px, calc(100vw - 48px)); text-align: center;
    box-shadow: 0 20px 60px rgba(31,120,72,0.22), inset 0 1px 0 rgba(255,255,255,0.8);
    backdrop-filter: blur(22px) saturate(180%);
    -webkit-backdrop-filter: blur(22px) saturate(180%);
}
.lock-icon { font-size: 44px; }
.lock-title { margin: 10px 0 4px; font-size: 20px; }
.lock-sub { color: var(--text-light); font-size: 13px; margin-bottom: 18px; }
.lock-input {
    width: 100%; padding: 12px 14px; font-size: 16px;
    border: 1px solid var(--border); border-radius: 10px;
    box-sizing: border-box; text-align: center; letter-spacing: 2px;
}
.lock-input:focus { outline: none; border-color: var(--primary); }
.lock-error { color: #c0392b; font-size: 13px; margin: 8px 0 0; }
.lock-btn { width: 100%; margin-top: 14px; padding: 12px; font-size: 15px; }
.lock-link {
    display: block; width: 100%; margin-top: 12px; padding: 6px;
    background: none; border: none; cursor: pointer;
    color: var(--primary); font-size: 13px; text-decoration: underline;
}
.lock-link:active { opacity: .6; }

/* ── 花名册 ── */
.roster-panel { margin-top: 16px; }
.roster-sub { margin-top: 14px; font-size: 14px; }
.placeholder { color: #d97706; font-style: italic; opacity: .8; }
/* 门店列：多店场景下一眼区分是谁家的名册；列窄所以字号压小一点 */
.td-store {
    font-size: 12px; color: #0369a1; max-width: 110px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 合同到期日：自动推算的淡一档 + 虚线下划，手工填的实心黑 —— 
   让店主扫一眼就知道哪些日期是系统算的、续签前要不要复核 */
.contract-auto {
    color: #64748b; border-bottom: 1px dashed #94a3b8; cursor: help;
}
.contract-manual { color: #0f172a; font-weight: 500; cursor: help; }
.roster-badge {
    display: inline-block; padding: 1px 8px; border-radius: 10px;
    font-size: 12px; font-weight: 600;
}
.badge-active { background: #ecfdf5; color: #059669; }
.badge-left { background: #fdf3f3; color: #dc2626; }
.rate-pill { font-weight: 700; }
.rate-high { color: #dc2626; }
.rate-mid { color: #d97706; }
.rate-low { color: #059669; }
.cur-half { background: #fffbeb; }

/* ── 响应式三端适配 ──
   手机 <768px：底部导航 + 顶栏品牌
   平板 768-1023px：图标窄侧栏(64px) + 顶栏品牌
   电脑 ≥1024px：完整侧栏(220px)，品牌在侧栏
── */

/* ── 平板：图标窄侧栏 ── */
@media (min-width: 769px) {
    body { padding-bottom: 0; }
    .bottom-nav { display: none; }
    .sidebar {
        display: flex;
        width: 64px;
        padding: 14px 6px;
        align-items: center;
    }
    .sidebar-brand { padding: 4px 0 18px; justify-content: center; }
    .sidebar-brand .brand-name { display: none; }
    .side-nav { align-items: center; width: 100%; }
    .side-btn {
        flex-direction: column;
        justify-content: center;
        gap: 3px;
        padding: 9px 2px;
        font-size: 11px;
        text-align: center;
        width: 100%;
        border-radius: 10px;
    }
    .side-btn .nav-icon { font-size: 20px; }
    .content { margin-left: 64px; padding-top: 76px; }
    /* 平板顶栏显示品牌（侧栏放不下） */
    .topbar .brand { display: flex; }
    .topbar .brand-name { font-size: 16px; }
    .grid-2 { grid-template-columns: 1fr 1fr; }
    .stat-cards { grid-template-columns: repeat(3, 1fr); }
    .chart { height: 280px; }
}

/* ── 电脑：完整侧栏 ── */
@media (min-width: 1024px) {
    .sidebar {
        width: 220px;
        padding: 20px 12px;
        align-items: stretch;
    }
    .sidebar-brand { padding: 8px 12px 24px; justify-content: flex-start; }
    .sidebar-brand .brand-name { display: inline; }
    .side-nav { align-items: stretch; }
    .side-btn {
        flex-direction: row;
        justify-content: flex-start;
        gap: 10px;
        padding: 12px 14px;
        font-size: 15px;
        text-align: left;
        width: auto;
    }
    .side-btn .nav-icon { font-size: 18px; }
    .content { margin-left: 220px; max-width: 1180px; padding: 20px 24px 24px; padding-top: 76px; }
    .topbar .brand { display: none; }
    .cost-form { grid-template-columns: repeat(3, 1fr); }
    .chart-lg { height: 320px; }
}

/* ── 手机微调（默认即手机样式） ── */
@media (max-width: 768px) {
    /* var 已含安全区（adjustTopbar 实测写入，见基础规则处注释），无需再加 env() */
    /* 底部内边距 16 → 6：body 的 padding-bottom 已按「底栏 + 备案条」精确让位，
       .content 自己再垫 16px 就变成了双份 —— 实测内容墨底离底栏白空 19px。
       6px 只留"滚到底不贴脸"的最小呼吸。 */
    .content { padding: 12px 12px 6px; padding-top: var(--topbar-height, 70px); }
    .page-title { font-size: 18px; }
    /* ── 手机端点评页「收藏打卡分类统计 / 分类汇总明细」撑破屏幕 ──────
       真机复现：面板 460px > 屏幕 393px，整页可以横向拖动。
       根因是 grid 轨道被内容撑开：全局 table 有 min-width:400px（保证桌面
       列不挤），而 .panel 是 .grid-2 的 grid item，min-width 默认为 auto ——
       于是它的最小宽度 = 内容最小宽度 = 表格的 400px + 面板内边距，
       轨道被顶到 460px。外层 .table-wrap 的 overflow-x:auto 本该把它
       截断成"面板内横滚"，但滚动容器挡不住父级 grid item 的 auto 最小值。
       治本：给 grid item 显式 min-width:0，轨道回到 1fr（373px），
       表格的 400px 就老老实实在 .table-wrap 内部横向滚动了。 */
    .grid-2 > * { min-width: 0; }
    .panel { min-width: 0; }
    .stat-value { font-size: 22px; }
    .stat-cards-sm .stat-value { font-size: 26px; }
    /* ── 手机端点评页统计卡紧凑化（用户："点评页面的图标也优化适配"）──
       与盘点卡同款尺寸，但点评卡多第三行 .stat-sub（环比/说明），压缩后
       三行仍完整可见：padding 16→7、圆角 22→12、标签 13→10px、
       数值 26→15px（sm 卡原比通用卡还大 4px）、副行 12→10px、单位 14→11px。
       .stat-cards-sm 全站仅点评页两处使用（本月收藏/打卡 2 张 +
       评价统计分析 5 张），改动不外溢。
       ⚠ 必须写在紧邻上一条同特异性规则的后面，才能覆盖掉 26px。 */
    .stat-cards-sm { gap: 6px; margin-bottom: 10px; }
    .stat-cards-sm .stat-card { padding: 7px 10px 8px; border-radius: 12px; }
    .stat-cards-sm .stat-label {
        font-size: 10px; margin-bottom: 2px;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .stat-cards-sm .stat-value { font-size: 15px; }
    .stat-cards-sm .stat-sub {
        font-size: 10px; margin-top: 2px;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .stat-cards-sm .stat-unit { font-size: 11px; }
    .header-actions .btn-sm { padding: 6px 10px; font-size: 12px; }
    .chart { height: 230px; }
    /* ── 手机端弹窗 = 沉底抽屉（bottom sheet）──────────────────
       此前手机端弹窗是「整屏居中 + max-height:90vh」：
       ① 居中意味着弹窗底部悬在屏幕中段，下方空出一大片；
       ② 弹窗 z-index 200 远低于底部停靠坞的 10000，
          底栏 + 备案条直接压在弹窗下半部分上
       —— 两者叠加，就是用户看到的「底部空白太高，取消/保存按钮没了」。
       iOS 的解法：弹窗从屏幕最低端升起、铺满宽度、操作条 sticky 沉在最底，
       辅以自带的安全区内边距，任何情况下按钮都在拇指够得到的地方。 */
    .modal {
        align-items: flex-end;      /* 居中 → 贴底 */
        padding: 26px 0 0;          /* 顶部留一条遮罩（点它关闭），左右下不留 */
    }
    .modal .modal-content {
        max-width: 100%; width: 100%;
        /* 100% = 遮罩层的 content box（.modal 自身 padding-top 已留 26px 遮罩条）。
           不写死 100dvh 的原因：iOS 弹键盘时 dvh 不变，得靠 JS 压缩 .modal
           本身的高度（见 app.js visualViewport 同步），这里用百分比自动跟随。 */
        max-height: 100%;
        border-radius: 20px 20px 0 0;
        /* 底部 padding 含安全区：没有 .modal-actions 的弹窗（按钮直接散在末尾）
           靠它把内容顶到 Home 条之上，不会被系统小白条压住。 */
        padding: 16px 14px calc(12px + env(safe-area-inset-bottom, 0px));
    }
    /* 操作条沉底：滚动时 sticky 钉在底部；内容不足一屏（不产生滚动）时
       sticky 无效，靠负 margin-bottom 吃掉 content 的 padding-bottom ——
       两条路都通向"贴在屏幕最低端"。毛玻璃：内容从它底下滚过时有 iOS 质感。 */
    .modal .modal-actions {
        position: sticky;
        bottom: calc(-12px - env(safe-area-inset-bottom, 0px));
        margin: 14px -14px calc(-12px - env(safe-area-inset-bottom, 0px));
        padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
        background: rgba(255, 255, 255, .92);
        backdrop-filter: blur(12px);
        -webkit-backdrop-filter: blur(12px);
        border-top: 1px solid var(--border);
    }
    /* 识别确认弹窗自带 flex 列布局，操作条是 flex 末项：
       同样要补安全区，否则贴底时按钮压在 Home 条上。 */
    .modal .modal-entry .modal-actions {
        margin: 0 -14px;
        padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
    }
    /* 抽屉式进出场：缩放版 panelOpen 在贴底场景会露出左右两侧的底边，
       换成纯位移的 sheet —— 从屏幕外滑入 / 滑出。 */
    .modal .pm-open  { animation: sheetIn  .38s var(--ios-sheet) backwards; }
    .modal .pm-close { animation: sheetOut .26s var(--ios-sheet) forwards; }
    .modal-wide { max-width: 100%; }
    .preview-table-wrap table { min-width: 420px; }
    .cost-total { padding: 12px 14px; }
    .cost-total-value { font-size: 20px; }
    .att-cell { width: 30px; height: 28px; }
}
/* ↑ 这个闭合括号此前丢失：整个「模板制考勤 / 加班弹窗 / 门店切换 / 营收」样式块
   以及后面所有规则都被吞进了 max-width: 768px 媒体查询，桌面端全部不生效。 */

/* ── 模板制考勤（✔/休/假 + 加班 + 工资） ── */
.att-week {
    font-size: 10px;
    color: #999;
    padding: 2px 0 !important;
    background: #f6f6f6;
    font-weight: 500;
}
.att-week.weekend { color: #ef4444; font-weight: 600; }
.att-cell.att-present { font-weight: 700; font-size: 13px; }
.att-cell.att-off, .att-cell.att-leave, .att-cell.att-half_day { font-size: 12px; font-weight: 600; }
.att-cell.has-ot { box-shadow: inset 0 0 0 2px #38bdf8; }
.att-sum-head {
    background: #fff7f2 !important;
    color: #9a3412 !important;
    font-size: 12px !important;
    min-width: 52px;
}
.att-sum {
    text-align: center;
    font-weight: 600;
    white-space: nowrap;
    position: sticky;
    right: 0;
    background: #fff;
    z-index: 4;
    font-size: 12px;
}
tr:hover .att-sum { background: #f9f9f9; }
.att-ot { cursor: pointer; color: #0b5e8a; }
.att-ot:hover { background: #e0f2fe !important; }
.att-salary { cursor: pointer; color: var(--primary); font-weight: 700; }
.att-salary:hover { background: #fff3ed !important; }
.att-foot-name {
    font-weight: 700;
    color: #9a3412;
    background: #fff7f2;
}
.att-foot-pad { background: #fff7f2; }
tfoot .att-sum { background: #fff7f2; font-weight: 700; }
/* 加班登记弹窗 */
.ot-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
    gap: 8px;
    margin: 12px 0;
}
.ot-day { text-align: center; }
.ot-day label { display: block; font-size: 11px; color: #888; margin-bottom: 3px; }
.ot-day input {
    width: 100%;
    padding: 6px 2px;
    text-align: center;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-size: 13px;
}
.ot-day input:focus { outline: none; border-color: var(--primary); }
.ot-day.ot-dim { opacity: .45; }
.ot-total { font-size: 14px; color: #0b5e8a; background: #e0f2fe; border-radius: 8px; padding: 8px 14px; }

/* ── 存休体系与员工设置 ── */
.att-sum-head { min-width: 40px; font-size: 11px !important; padding: 4px 4px !important; }
.att-sum { font-size: 11px; padding: 6px 4px; }
.att-cfg {
    cursor: pointer;
    color: #0b5e8a;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}
.att-cfg:hover { background: #e0f2fe !important; }
/* 本月存休：累计口径的核心数字，紫色加粗突出。
   原先是「结余」列的样式，2026-09-28 去掉结余列后改由本月存休沿用。
   负数（欠休）也是这个色 —— 加粗本身就能和旁边的普通数字分开，
   不必再加红色，否则整排红字反而看不出哪个真该注意。 */
.att-balance { font-weight: 700; color: #7c3aed; }

/* 季末清零标记（2026-09-29）：这一格的存休本季末已折成补贴进工资。
   加一层淡金底色 + 左侧竖条，平时不动声色，季末一眼能认出来
   「这个数已经兑换过了」。 */
.att-clear {
    background: #fffbeb;
    box-shadow: inset 3px 0 0 #f0b400;
}
/* 危险色只定义配色，尺寸交给按钮阶梯 ——
   原先这里自带 padding:8px 14px / font-size:13px，等于偷偷造了第 4 档尺寸，
   用它的地方高度就和其他按钮对不齐。 */
.btn-danger {
    background: #fdf3f3;
    border: 1px solid #fecaca;
    color: #dc2626;
}
.btn-danger:hover { background: #fee2e2; }
/* 手机端汇总列太宽，取消右侧吸附改为随内容滚动 */
@media (max-width: 768px) {
    .att-sum { position: static; }
}

/* ── 多店模型：门店切换与管理 ── */
#globalStore {
    min-width: 92px;
    max-width: 160px;
}
.store-list { display: flex; flex-direction: column; gap: 8px; }
.store-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    border: 1px solid #e2ebe6;
    border-radius: 10px;
    background: #fafafa;
}
.store-item .store-name {
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
}
.store-item .store-default { font-size: 11px; color: #7c3aed; background:#ede9fe; padding:2px 8px; border-radius:999px; }
.store-item .store-city { font-size: 11px; color: #0369a1; background:#e0f2fe; padding:2px 8px; border-radius:999px; font-weight: 500; }
.store-item .store-city-empty { color: #b45309; background:#fef3c7; }
.store-item .store-actions { display:flex; gap:6px; }
.store-item .store-actions .btn {
    height: var(--btn-h-xs);
    padding: 0 var(--btn-px-xs);
    font-size: var(--btn-fs-xs);
    border-radius: var(--btn-r-xs);
}
.store-count { font-size: 12px; color: #6b7a72; }

/* ── 经营日登记：一键清除 ── */
.btn-danger-ghost { color: #b91c1c; border: 1px solid #fca5a5; background: #fff; }
.btn-danger-ghost:hover { background: #fdf3f3; }
.bl-clear-row { display: flex; gap: 12px; }
.bl-clear-row .form-group { flex: 1; }
.bl-quick { display: flex; flex-wrap: wrap; gap: 6px; }
.bl-quick .btn {
    height: var(--btn-h-xs);
    padding: 0 var(--btn-px-xs);
    font-size: var(--btn-fs-xs);
    border-radius: var(--btn-r-xs);
}

/* ── 经营日登记：完整性体检 ── */
.bl-chk-cards { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.bl-chk-card {
    flex: 1 1 120px; min-width: 120px; padding: 10px 12px; border-radius: 10px;
    border: 1px solid #e2ebe6; background: #f7faf8; text-align: center;
}
.bl-chk-card b { display: block; font-size: 22px; line-height: 1.2; color: #1f2a24; }
.bl-chk-card span { font-size: 12px; color: #6b7a72; }
.bl-chk-card.ok { border-color: #a7f3d0; background: #ecfdf5; }
.bl-chk-card.ok b { color: #047857; }
.bl-chk-card.warn { border-color: #fecaca; background: #fdf3f3; }
.bl-chk-card.warn b { color: #b91c1c; }
.bl-chk-h { margin: 16px 0 6px; font-size: 14px; }
.bl-chk-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 2px; }
.bl-chk-tag {
    display: inline-block; font-size: 12px; padding: 2px 8px; border-radius: 999px;
    background: #eef4f0; color: #3f4c45; margin: 0 4px 4px 0;
}
.bl-chk-tag.core { background: #fee2e2; color: #b91c1c; }
.bl-chk-tag.suggest { background: #fef3c7; color: #92400e; }
.bl-chk-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.bl-chk-tbl td { padding: 5px 6px; border-bottom: 1px solid #eef4f0; vertical-align: top; }
.bl-chk-date { white-space: nowrap; width: 92px; }
#blCheckBody { max-height: 62vh; overflow-y: auto; }

/* ── 多账号：账号管理 ── */
.user-add-form {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.user-add-form input, .user-add-form select {
    flex: 1;
    min-width: 120px;
    padding: 8px 10px;
    border: 1px solid #cfdcd4;
    border-radius: 8px;
    font-size: 13px;
}
.user-add-form .btn { white-space: nowrap; }
.user-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.user-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid #e2ebe6;
    border-radius: 10px;
    background: #fafafa;
    flex-wrap: wrap;
}
.user-item.disabled { opacity: .55; }
.user-item .user-info { font-size: 13px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.user-item .user-name { font-weight: 700; }
.user-item .user-role { font-size: 11px; padding: 2px 8px; border-radius: 999px; }
.user-item .role-admin { background: #fee2e2; color: #dc2626; }
.user-item .role-manager { background: #d8f0ec; color: #1d4ed8; }
.user-item .user-store { font-size: 12px; color: #6b7a72; }
.user-item .user-status { font-size: 11px; color: #8b968f; }
.user-item .user-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.user-item .user-actions .btn, .user-item .user-actions .btn-danger {
    height: var(--btn-h-xs);
    padding: 0 var(--btn-px-xs);
    font-size: var(--btn-fs-xs);
    border-radius: var(--btn-r-xs);
}

/* ── 花名册宽表（完整员工资料） ── */
.roster-table { min-width: 1280px; }
.roster-table .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px; white-space: nowrap;
}
.roster-table .addr { min-width: 180px; }
.roster-table td { vertical-align: top; }

/* 预览自采徽标 + 盘点总金额卡新配色 */
.cls-badge.cls-sf { background: #ccfbf1; color: #0f766e; }
.stat-value.stat-value-purple { color: #7c3aed; }
.stat-value.stat-value-green { color: #0d9488; }

/* 手机端：渠道统计卡两列 */
@media (max-width: 640px) {
    .channel-cards { grid-template-columns: repeat(2, 1fr); gap: 8px; }
    .channel-card { padding: 10px 12px; }
    .channel-card .ch-amount { font-size: 17px; }
    .channel-card .ch-name { font-size: 12px; }
    .inv-channel-grid { grid-template-columns: 1fr; }
}

/* ── 盘点明细「品类」下拉（点击即编辑切换，平时似文本，悬停显现编辑态） ── */
.inv-cat-select {
    appearance: menulist;
    padding: 4px 6px;
    border: 1px dashed transparent;
    border-left-width: 3px;
    border-radius: 8px;
    background: transparent;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    max-width: 130px;
}
.inv-cat-select:hover,
.inv-cat-select:focus {
    background: #fff;
    border: 1px solid #cbd5e1;
    border-left-width: 3px;
    outline: none;
    box-shadow: 0 1px 4px rgba(0,0,0,.08);
}

/* ── 订货明细「品类」下拉（点击即切换，仅限本平台品类，平时似文本） ── */
.order-cat-select {
    appearance: menulist;
    padding: 4px 6px;
    border: 1px dashed transparent;
    border-left-width: 3px;
    border-radius: 8px;
    background: transparent;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    max-width: 130px;
}
.order-cat-select:hover,
.order-cat-select:focus {
    background: #fff;
    border: 1px solid #cbd5e1;
    border-left-width: 3px;
    outline: none;
    box-shadow: 0 1px 4px rgba(0,0,0,.08);
}


/* 盘点录入：作为第 6 张渠道卡，与前面 5 张对称排列（桌面 3 列 × 2 行） */
.inv-channel-cards { grid-template-columns: repeat(3, 1fr); }
/* 中屏：2 列，仍对称 */
@media (max-width: 980px) { .inv-channel-cards { grid-template-columns: repeat(2, 1fr); } }
/* 第 6 张卡的主题色（绿色，区别于渠道卡） */
.platform-logo.entry { background: #16a34a; }
.channel-card.ch-entry { background: linear-gradient(135deg, #f0fdf4, #dcfce7); }
.channel-card.ch-entry .ch-amount { color: #15803d; }
.channel-card.ch-entry.active { border-color: #22c55e; }
.channel-card.ch-entry.active::after {
    content: '▾ 录入中';
    display: block;
    font-size: 11px;
    color: #15803d;
    margin-top: 4px;
}

/* 盘点录入：工作面板「极窄边框」——仅左右+底边为 1mm 细线，顶边开放 */
.inv-ord-panel .panel.thin {
    background: #fff;
    border: none;
    border-left: 1mm solid #e2ebe6;
    border-right: 1mm solid #e2ebe6;
    border-bottom: 1mm solid #e2ebe6;
    border-radius: 8px;
    box-shadow: none;
    padding: 14px;
}
/* 录入表格：固定列宽 + 关键列收窄，名称/规格可换行显示更多信息 */
/* 宽度用 max(100%, 920px)：宽屏铺满、窄屏（平板/手机）不再被压窄，
   必走横向滚动，避免 table-layout:fixed 在手机上因 min-width 被忽略而列重叠/输入框塌陷 */
/* 选择器须带 .table-wrap 前缀以压过 .table-wrap table{width:100%}（特异度 0,1,1），
   否则 max(100%,920px) 被覆盖、手机端表宽被压成视口宽、nowrap 数字列相互叠加 */
.table-wrap .entry-table { table-layout: fixed; width: max(100%, 920px); font-size: 12.5px; }
.table-wrap .entry-table th, .table-wrap .entry-table td { padding: 6px 8px; }
/* 列宽按 class 定义（不依赖 nth-child，移除序号列后不会错位） */
.entry-table .entry-col-cat  { width: 92px; }   /* 统配类别 */
.entry-table .entry-col-name { width: 190px; }  /* 原料名称 */
.entry-table .entry-col-spec { width: 150px; }  /* 规格 */
.entry-table .entry-col-mu   { width: 64px; }   /* 最小单位 */
.entry-table .entry-col-box  { width: 56px; }   /* 箱规 */
.entry-table .entry-col-bp   { width: 78px; }   /* 整箱价 */
.entry-table .entry-col-up   { width: 78px; }   /* 单价 */
.entry-table .entry-col-qty  { width: auto; min-width: 120px; }  /* 盘点数量：动态吸收剩余空间，最小 120 防手机塌陷 */
.entry-table .entry-col-amt  { width: 90px; }   /* 金额 */
.entry-table .entry-col-name, .entry-table .entry-col-spec { white-space: normal; word-break: break-all; }
.entry-table td.entry-amount { font-weight: 600; color: #059669; }

/* 盘点录入：数量输入框（R 角圆角、填满整格，全局单条可编辑） */
.entry-qty-input { width: 100%; box-sizing: border-box; padding: 5px 7px; border: 1px solid #cfdcd4; border-radius: 8px; text-align: right; background: #fff; }
.entry-qty-input:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 2px rgba(234,88,12,.12); }
.entry-row-filled { background: #f0fdf4; }
.entry-row-filled .entry-qty-input { border-color: #22c55e; }
.entry-row-review { background: #fffbeb; }
.entry-row-unmatched { background: #fdf3f3; }
#entryShotPreview .data-table th { background: #f7faf8; position: sticky; top: 0; }
.entry-match-select { max-width: 240px; padding: 3px 4px; border: 1px solid #cfdcd4; border-radius: 6px; font-size: 12px; background: #fff; }
.entry-reason { color: #b91c1c; font-size: 11px; line-height: 1.3; margin-top: 2px; max-width: 180px; }
.entry-reason-warn { color: #b45309; font-size: 11px; line-height: 1.3; margin-top: 2px; max-width: 200px; }
.entry-badge { display: inline-block; padding: 1px 6px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.entry-badge.mem { background: #cffafe; color: #0e7490; }

/* 盘点录入：编辑模式下字段可编辑 */
.entry-edit-input { width: 100%; box-sizing: border-box; padding: 4px 6px; border: 1px solid #cfdcd4; border-radius: 6px; font-size: 12.5px; background: #fff; }
.entry-edit-input:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 2px rgba(234,88,12,.12); }
.entry-table tr.entry-editing td { background: #fffbeb; }
.entry-badge.auto { background: #dcfce7; color: #15803d; }
.entry-badge.rec { background: #fef3c7; color: #b45309; }
.entry-dup-list { display: flex; flex-direction: column; gap: 8px; margin: 8px 0 4px; max-height: 50vh; overflow: auto; }
.entry-dup-item { border: 1px solid #fde68a; background: #fffbeb; border-radius: 10px; padding: 10px 12px; }
.entry-dup-item.entry-dup-done { border-color: #bbf7d0; background: #f0fdf4; opacity: 0.85; }
.entry-dup-name { font-weight: 600; margin-bottom: 2px; }
.entry-dup-info { font-size: 13px; color: #475569; margin-bottom: 6px; }
.entry-dup-tag { display: inline-block; margin-left: 4px; padding: 0 6px; border-radius: 8px; background: #fee2e2; color: #b91c1c; font-size: 11px; }
.entry-dup-btns { display: flex; gap: 8px; }
.entry-dup-ok { color: #15803d; font-weight: 600; font-size: 13px; }
.entry-unrec { margin-top: 10px; border: 1px solid #fecaca; background: #fdf3f3; border-radius: 10px; padding: 10px 12px; }
.entry-unrec-title { font-weight: 600; color: #b91c1c; margin-bottom: 4px; }
.entry-unrec ul { margin: 4px 0 6px 18px; padding: 0; }
.entry-unrec li { font-size: 13px; color: #475569; margin: 2px 0; }

/* 盘点录入：截图解析中状态 */
.entry-parsing {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 8px; padding: 28px 16px; border: 1px solid #fed7aa; background: #fff7ed;
    border-radius: 10px; color: #9a3412; font-size: 14px; font-weight: 600; text-align: center;
}
.entry-parsing-sub { font-size: 12px; font-weight: 400; color: #b45309; opacity: .85; }
.entry-spinner {
    width: 26px; height: 26px; border-radius: 50%;
    border: 3px solid #fed7aa; border-top-color: var(--primary, #ea580c);
    animation: entrySpin .8s linear infinite;
}
@keyframes entrySpin { to { transform: rotate(360deg); } }

/* 盘点录入：识别预览「尚未写入表格」标记 */
.entry-preview-flag {
    display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px;
    background: #e0f2fe; color: #0369a1; font-size: 11px; font-weight: 600; vertical-align: middle;
}
.entry-preview-sub { display: block; margin-top: 3px; color: #888; font-size: 11.5px; font-weight: normal; line-height: 1.5; }

/* 盘点录入：识别后确认弹窗 */
.entry-confirm-summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
.entry-cf-chip {
    padding: 4px 10px; border-radius: 999px; background: #f1f5f9;
    color: #334155; font-size: 12.5px; border: 1px solid #e2e8f0;
}
.entry-cf-chip.warn { background: #fef3c7; color: #92400e; border-color: #fde68a; }
.entry-cf-chip.ok { background: #dcfce7; color: #15803d; border-color: #bbf7d0; }
.entry-confirm-list { max-height: 46vh; overflow: auto; margin: 4px 0 8px; }
.entry-confirm-table { font-size: 12.5px; }
.entry-confirm-table th, .entry-confirm-table td { padding: 5px 8px; white-space: nowrap; }

/* 识别预览 / 确认弹窗：重复项高亮 */
.entry-row-dup > tr, .entry-cf-row-dup { background: #fffbeb !important; }
.entry-row-dup > tr > td:first-child, .entry-cf-row-dup > td:first-child { box-shadow: inset 3px 0 0 #f59e0b; }
.entry-row-conflict > tr, .entry-cf-row-conflict { background: #fdf3f3 !important; }
.entry-row-conflict > tr > td:first-child, .entry-cf-row-conflict > td:first-child { box-shadow: inset 3px 0 0 #dc2626; }
.entry-row-dup-both > tr { background: #fee2e2 !important; }
.entry-row-dup-both > tr > td:first-child { box-shadow: inset 3px 0 0 #b91c1c; }
.entry-dup-tag.conflict { background: #dc2626; color: #fff; }
.entry-cf-warn { color: #b91c1c; }
.entry-confirm-legend { display: flex; flex-wrap: wrap; gap: 10px; margin: 6px 0 2px; font-size: 11.5px; color: #64748b; }
.entry-confirm-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }

/* ── 识别确认全屏弹窗（合并预览 + 核对 + 写入）── */
.modal-entry { max-width: 960px; display: flex; flex-direction: column;
               background: #fff; overflow: hidden; }
.modal-entry .entry-modal-body { flex: 1 1 auto; overflow-y: auto; padding: 4px 2px; min-height: 0; }
.modal-entry .modal-actions { position: sticky; bottom: 0; background: #fff;
                              border-top: 1px solid #e2ebe6; padding-top: 10px;
                              margin: 0 -4px; padding-left: 4px; padding-right: 4px; }
.modal-entry .data-table th { position: sticky; top: 0; z-index: 2; background: #f7faf8; }
.entry-section-title { display: flex; align-items: center; gap: 8px;
                       font-size: 13px; font-weight: 600; margin: 10px 0 6px; color: #92400e; }
.entry-conflict-item  { background: #fdf3f3; border-left: 3px solid #dc2626;
                        padding: 8px 10px; border-radius: 6px; margin-bottom: 6px; }
.entry-conflict-item .entry-dup-btns { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.entry-conflict-item .entry-dup-info { font-size: 12px; color: #475569; margin-top: 2px; }
.entry-conflict-item .entry-dup-name { font-weight: 600; color: #0f172a; }
#entryConfirmAuto { margin-top: 8px; border: 1px solid #e2ebe6; border-radius: 8px; padding: 0 10px; background: #f7faf8; }
#entryConfirmAuto > summary { cursor: pointer; padding: 8px 0; font-weight: 600; font-size: 13px;
                              color: #15803d; list-style: none; }
#entryConfirmAuto > summary::-webkit-details-marker { display: none; }
#entryConfirmAuto > summary::before { content: '▶ '; display: inline-block; transition: transform .15s; }
#entryConfirmAuto[open] > summary::before { transform: rotate(90deg); }
@media (max-width: 768px) {
    .modal-entry { max-width: 100%; }
    .modal-entry .entry-confirm-table th,
    .modal-entry .entry-confirm-table td { padding: 4px 5px; font-size: 11.5px; }
    .modal-entry .table-wrap { overflow-x: auto; }
    .modal-entry .entry-qty-input,
    .modal-entry .entry-name-input { min-width: 80px; }
}

/* ── 订货：月度品类合并（隐藏式折叠面板）── */
.collapse-panel { padding-bottom: 14px; }
.collapse-head {
    display: flex; align-items: center; justify-content: space-between;
    cursor: pointer; user-select: none;
    padding: 2px 6px 12px;
    border-bottom: 1px dashed rgba(0,0,0,0.10);
    margin-bottom: 12px;
}
.collapse-panel.open .collapse-head { border-bottom-color: transparent; margin-bottom: 0; }
.collapse-title { font-size: 15px; font-weight: 600; }
.collapse-arrow { transition: transform .2s; font-size: 18px; color: var(--text-light); }
.collapse-panel.open .collapse-arrow { transform: rotate(90deg); }
.collapse-body { padding: 4px 2px 0; }
/* 点评页：评价目录 / 记录明细 可折叠（作用域隔离，不影响订货页） */
#page-dianping .collapse-panel { padding-bottom: 0; }
#page-dianping .collapse-panel:not(.open) > .collapse-body { display: none; }

/* ── 订货：每日汇总（每个日期一个独立面板）── */
.daily-panels { display: flex; flex-direction: column; gap: 12px; }
.daily-date-panel { margin-bottom: 0; }
.daily-date-panel .ddp-head {
    display: flex; align-items: center; gap: 12px;
    cursor: pointer; user-select: none;
    padding: 14px;
}
.daily-date-panel.open .ddp-head { padding-bottom: 12px; }
.ddp-date { font-size: 16px; font-weight: 700; min-width: 92px; }
.ddp-meta { display: flex; gap: 8px; flex: 1; flex-wrap: wrap; }
.acc-pill {
    background: rgba(255,107,53,0.10); color: #c2410c;
    border-radius: 999px; padding: 2px 10px; font-size: 12.5px; font-weight: 600;
}
.acc-pill.amt { background: rgba(5,150,105,0.12); color: #047857; }
.acc-arrow { transition: transform .2s; color: var(--text-light); margin-left: auto; }
.daily-date-panel.open .acc-arrow { transform: rotate(90deg); }
.ddp-body { padding: 4px 14px 14px; }
.ddp-body .table-wrap { border: none; background: transparent; box-shadow: none; padding: 4px 0; }
.ddp-body .table-wrap table { font-size: 12.5px; min-width: 520px; }

/* 手机端：去边框、贴边、更紧凑，让展开内容显示更多 */
@media (max-width: 640px) {
    .daily-panels { gap: 8px; }
    /* 去掉外层 panel 的卡片边距，只保留标题条 */
    .daily-date-panel.panel {
        background: transparent; border: none; border-radius: 0;
        box-shadow: none; padding: 0; margin: 0;
    }
    .daily-date-panel .ddp-head {
        gap: 8px; padding: 10px 12px;
        border-radius: 14px;
        background: linear-gradient(135deg, rgba(255,255,255,0.94), rgba(255,255,255,0.82));
        border: 1px solid var(--glass-border);
        box-shadow: var(--glass-shadow);
    }
    .daily-date-panel.open .ddp-head {
        border-radius: 14px 14px 0 0;
        border-bottom-color: transparent;
        padding-bottom: 10px;
    }
    .ddp-date { font-size: 15px; min-width: 84px; }
    .ddp-meta { gap: 6px; }
    .acc-pill { font-size: 11.5px; padding: 1px 7px; }
    .acc-arrow { font-size: 13px; }

    /* 展开内容无边框、全宽 */
    .daily-date-panel .ddp-body {
        padding: 0;
        background: transparent;
        border: none;
    }
    .daily-date-panel .ddp-body .table-wrap {
        padding: 0;
        border: none; border-radius: 0;
        background: transparent; box-shadow: none;
        overflow-x: auto;
    }
    .daily-date-panel .ddp-body .table-wrap table {
        min-width: auto;
        width: 100%;
        font-size: 12px;
        border-collapse: collapse;
        background: #fff;
    }
    .daily-date-panel .ddp-body .table-wrap th,
    .daily-date-panel .ddp-body .table-wrap td {
        padding: 6px 4px;
        white-space: nowrap;
        border-bottom: 1px solid #f0f0f0;
    }
    .daily-date-panel .ddp-body .table-wrap th {
        background: #fafafa; font-weight: 600; font-size: 11.5px;
    }
    .daily-date-panel .ddp-body .table-wrap td:first-child,
    .daily-date-panel .ddp-body .table-wrap th:first-child { padding-left: 8px; }
    .daily-date-panel .ddp-body .table-wrap td:last-child,
    .daily-date-panel .ddp-body .table-wrap th:last-child { padding-right: 8px; }

    /* 列宽适配：品名可折行，其余紧凑 */
    .daily-date-panel .ddp-body .table-wrap td:nth-child(1),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(1) { min-width: 90px; max-width: 160px; white-space: normal; }
    .daily-date-panel .ddp-body .table-wrap td:nth-child(2),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(2) { min-width: 54px; }
    .daily-date-panel .ddp-body .table-wrap td:nth-child(3),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(3) { min-width: 70px; }
    .daily-date-panel .ddp-body .table-wrap td:nth-child(4),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(4) { min-width: 36px; text-align: center; }
    .daily-date-panel .ddp-body .table-wrap td:nth-child(5),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(5),
    .daily-date-panel .ddp-body .table-wrap td:nth-child(6),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(6),
    .daily-date-panel .ddp-body .table-wrap td:nth-child(7),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(7) { min-width: 48px; text-align: right; }
    .daily-date-panel .ddp-body .table-wrap td:nth-child(8),
    .daily-date-panel .ddp-body .table-wrap th:nth-child(8) { min-width: 54px; text-align: center; }

    /* 订货：月度品类合并（隐藏折叠面板）展开后的表格——贴边、紧凑、列宽适配 */
    .collapse-panel .collapse-body { padding: 0; }
    .collapse-panel .collapse-body .table-wrap {
        padding: 0; border: none; border-radius: 0;
        background: transparent; box-shadow: none; overflow-x: auto;
    }
    .collapse-panel .collapse-body .table-wrap table {
        min-width: auto; width: 100%; font-size: 12px; border-collapse: collapse; background: #fff;
    }
    .collapse-panel .collapse-body .table-wrap th,
    .collapse-panel .collapse-body .table-wrap td {
        padding: 6px 4px; white-space: nowrap; border-bottom: 1px solid #f0f0f0;
    }
    .collapse-panel .collapse-body .table-wrap th { background: #fafafa; font-weight: 600; font-size: 11.5px; }
    .collapse-panel .collapse-body .table-wrap td:first-child,
    .collapse-panel .collapse-body .table-wrap th:first-child { padding-left: 8px; }
    .collapse-panel .collapse-body .table-wrap td:last-child,
    .collapse-panel .collapse-body .table-wrap th:last-child { padding-right: 8px; }
    /* 品类合并：首列(品名)可折行，其余数值列紧凑右对齐 */
    .collapse-panel .collapse-body .table-wrap td:nth-child(1),
    .collapse-panel .collapse-body .table-wrap th:nth-child(1) { min-width: 92px; max-width: 150px; white-space: normal; }
    .collapse-panel .collapse-body .table-wrap td:nth-child(n+2),
    .collapse-panel .collapse-body .table-wrap th:nth-child(n+2) { min-width: 56px; text-align: right; }

    /* 盘点：渠道面板内部去边框、贴边，展开明细横向显示更多 */
    .inv-ord-panel > .panel:not(.thin) {
        background: transparent; border: none; border-radius: 0;
        box-shadow: none; padding: 0; margin: 0;
    }
    .inv-ord-panel > .panel:not(.thin) > .panel-title { margin: 12px 0 8px; padding-left: 2px; }
    .inv-ord-panel > .panel:not(.thin) .table-wrap {
        padding: 0; border: none; border-radius: 0;
        background: transparent; box-shadow: none; overflow-x: auto;
    }
    .inv-ord-panel > .panel:not(.thin) .table-wrap table {
        min-width: auto; width: 100%; font-size: 12px; border-collapse: collapse; background: #fff;
    }
    .inv-ord-panel > .panel:not(.thin) .table-wrap th,
    .inv-ord-panel > .panel:not(.thin) .table-wrap td {
        padding: 6px 4px; white-space: nowrap; border-bottom: 1px solid #f0f0f0;
    }
    .inv-ord-panel > .panel:not(.thin) .table-wrap th { background: #fafafa; font-weight: 600; font-size: 11.5px; }
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:first-child,
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:first-child { padding-left: 8px; }
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:last-child,
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:last-child { padding-right: 8px; }
    /* 盘点明细 11 列：平台/品类下拉与操作按钮紧凑，整体可横向滚动 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(1),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(1) { min-width: 78px; }      /* 订货平台 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(2),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(2) { min-width: 76px; }      /* 品类 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(3),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(3) { min-width: 96px; max-width: 160px; white-space: normal; } /* 品名 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(4),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(4) { min-width: 64px; }      /* 规格 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(5),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(5) { min-width: 56px; }      /* 最小单位 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(6),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(6) { min-width: 56px; }      /* 箱规 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(7),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(7) { min-width: 72px; }      /* 原料单价/箱 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(8),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(8) { min-width: 56px; }      /* 数量 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(9),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(9) { min-width: 64px; }      /* 单价 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(10),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(10) { min-width: 72px; }     /* 金额 */
    .inv-ord-panel > .panel:not(.thin) .table-wrap td:nth-child(11),
    .inv-ord-panel > .panel:not(.thin) .table-wrap th:nth-child(11) { min-width: 52px; text-align: center; } /* 操作 */
    .inv-ord-panel .inv-pf-select, .inv-ord-panel .inv-cat-select { max-width: 96px; }
    .inv-ord-panel > .panel:not(.thin) .total-row td { background: rgba(255,107,53,0.10); }

    /* 盘点：录入面板（.panel.thin）去边框、贴边、列宽紧凑，横向滚动展示更多 */
    .inv-ord-panel > .panel.thin {
        background: transparent; border: none; border-radius: 0;
        box-shadow: none; padding: 0; margin: 0;
    }
    .inv-ord-panel > .panel.thin > .panel-title { margin: 12px 0 8px; padding-left: 2px; }
    .inv-ord-panel > .panel.thin .table-wrap {
        padding: 0; border: none; background: transparent; box-shadow: none; overflow-x: auto;
    }
    .inv-ord-panel > .panel.thin .entry-table {
        table-layout: fixed; width: max(100%, 780px); font-size: 12px;
    }
    .inv-ord-panel > .panel.thin .entry-table th,
    .inv-ord-panel > .panel.thin .entry-table td { padding: 5px 4px; }
    .inv-ord-panel > .panel.thin .entry-table .entry-col-cat  { width: 80px; }   /* 统配类别 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-name { width: 130px; }  /* 原料名称 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-spec { width: 96px; }   /* 规格 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-mu   { width: 54px; }   /* 最小单位 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-box  { width: 52px; }   /* 箱规 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-bp   { width: 68px; }   /* 整箱价 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-up   { width: 68px; }   /* 单价 */
    .inv-ord-panel > .panel.thin .entry-table .entry-col-qty  { width: 62px; }   /* 盘点数量：最多显示 4 位数字 */
    .inv-ord-panel > .panel.thin .entry-table th.entry-col-qty { white-space: normal; font-size: 11px; line-height: 1.15; }
    .inv-ord-panel > .panel.thin .entry-table .entry-col-amt  { width: 80px; }   /* 金额 */
}

/* ══════════════════════════════════════════════
   营收管理（收银系统数据）
   ══════════════════════════════════════════════ */

/* ── 概览条：一行铺开的紧凑指标，不用大卡片 ── */
.rev-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(108px, 1fr));
    gap: 10px;
}
.rev-stat {
    display: flex; flex-direction: column; gap: 3px;
    padding: 10px 12px;
    background: #f8fcf9;
    border: 1px solid var(--border);
    border-radius: 10px;
    border-left: 3px solid var(--primary);
}
.rev-stat-label { font-size: 12px; color: var(--text-light); }
.rev-stat-value { font-size: 17px; font-weight: 700; color: var(--text); }
.rev-mom { font-size: 11px; font-weight: 600; }
.rev-mom.up { color: var(--green); }
.rev-mom.down { color: var(--red); }
.rev-mom.na { color: var(--text-light); font-weight: 400; }

/* ── 经营结果（营收 − 成本 = 利润）── */
.rev-profit { display: flex; flex-direction: column; gap: 8px; }
.rev-profit-row {
    display: flex; justify-content: space-between; align-items: center;
    padding: 8px 12px; border-radius: 8px; background: #f8fcf9;
}
.rev-profit-amt { font-weight: 700; font-size: 15px; }
.rev-profit-net {
    background: #fff5ef; border: 1px solid var(--primary);
    font-weight: 700;
}
.rev-profit-net.pos .rev-profit-amt { color: var(--green); }
.rev-profit-net.neg .rev-profit-amt { color: var(--red); }

/* ── 表格 ── */
.rev-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.rev-table th {
    background: #f6faf7; color: var(--text-light); font-weight: 600;
    text-align: left; padding: 8px 10px;
    border-bottom: 1px solid var(--border); white-space: nowrap;
}
.rev-table td {
    padding: 8px 10px; border-bottom: 1px solid #eef5f0; color: var(--text);
}
.rev-table tbody tr:hover { background: #fafdfb; }
.rev-table td.rev-pay { font-size: 12px; color: var(--text-light); }
.rev-lock { cursor: help; }
.rev-suspicious { background: #fff4f4 !important; }

/* 行内可编辑单元格 */
.rev-table td.rev-edit {
    cursor: text;
    border-bottom: 1px dashed var(--border);
}
.rev-table td.rev-edit:hover { background: #fff8f3; }
.rev-table td.rev-edit:focus {
    outline: 2px solid var(--primary); outline-offset: -2px;
    background: #fff; border-radius: 3px;
}

/* 表格里的文字按钮 */
.btn-link {
    background: none; border: none; padding: 2px 4px;
    color: var(--primary); font-size: 12px; cursor: pointer;
    text-decoration: underline;
}
.btn-link:hover { color: var(--primary-dark); }
.btn-link.danger { color: var(--red); }
.btn-link.danger:hover { color: #c0392b; }
.rev-ops { white-space: nowrap; }

/* 批次日志状态 */
.rev-status { font-size: 12px; padding: 2px 8px; border-radius: 10px; }
.rev-status.ok { background: #e8f8ee; color: var(--green); }
.rev-status.err { background: #fdeaea; color: var(--red); }
.rev-status.undone { background: #f0f0f0; color: var(--text-light); }
.rev-table tr.rev-msg td {
    font-size: 12px; color: var(--red); background: #fffaf9;
    padding: 4px 10px;
}

/* ── 导入预览 ── */
.rev-imp-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.rev-tag {
    font-size: 12px; padding: 3px 10px; border-radius: 12px;
    background: #eef5f0; color: var(--text);
}
.rev-tag.ok { background: #e8f8ee; color: var(--green); font-weight: 600; }
.rev-tag.warn { background: #fff4e5; color: #d97706; font-weight: 600; }
.rev-tag.err { background: #fdeaea; color: var(--red); font-weight: 600; }
.rev-warn {
    margin: 0 0 10px; padding: 8px 12px 8px 26px;
    background: #fffaf0; border-left: 3px solid var(--yellow);
    border-radius: 6px; font-size: 12px; color: #92400e;
}
.rev-imp-table { max-height: 320px; display: block; overflow-y: auto; }

/* 列映射 */
.rev-adv { margin: 10px 0; border: 1px solid var(--border); border-radius: 8px; }
.rev-adv > summary {
    padding: 8px 12px; cursor: pointer; font-size: 13px;
    color: var(--text-light); user-select: none;
}
.rev-adv > summary:hover { color: var(--primary); }
.rev-mapping-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 8px; padding: 0 12px 10px;
}
.rev-map-item { display: flex; flex-direction: column; gap: 3px; font-size: 12px; }
.rev-map-item > span { color: var(--text-light); }
.rev-map-item input[type="checkbox"] { align-self: flex-start; }
.rev-mapping-title {
    font-size: 12px; color: var(--text-light);
    padding: 8px 12px 4px; font-weight: 600;
}
.rev-adv > .btn { margin: 0 12px 12px; }

/* ── 接口设置 ── */
.cb-label {
    display: flex; align-items: center; gap: 6px;
    font-size: 13px; color: var(--text); cursor: pointer;
}
.cb-label input[type="checkbox"] { width: 15px; height: 15px; }

/* 利润测算：营业额来源切换 */
.pf-src {
    margin-left: 6px; padding: 2px 6px; font-size: 11px;
    border: 1px solid var(--border); border-radius: 6px;
    background: #fff; color: var(--text-light); cursor: pointer;
}
#pfRevenue[readonly] { background: #f5f5f5; color: var(--text-light); }

/* ── 移动端 ── */
@media (max-width: 768px) {
    .rev-stats { grid-template-columns: repeat(2, 1fr); gap: 8px; }
    .rev-stat { padding: 8px 10px; }
    .rev-stat-value { font-size: 15px; }
    .rev-table { font-size: 12px; }
    .rev-table th, .rev-table td { padding: 6px 6px; }
    .rev-mapping-grid { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════
   跨店只读模式（店长被授权查看其它门店）
   ══════════════════════════════════════════ */
.readonly-badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 8px; border-radius: 999px;
    background: #fff4e5; color: #b45309;
    border: 1px solid #f3d6a4; font-size: 11px; font-weight: 600;
    white-space: nowrap; cursor: help;
}

/* 只读模式下隐藏所有写操作入口。
   按 onclick 函数名匹配，而不是逐个加 class：项目里写操作入口散落在
   几十个按钮上（含弹窗内、表格行内），逐个打补丁必然遗漏；新增按钮时
   命名沿用同一批动词即可自动生效。后端仍有独立 403 拦截，这里只是
   避免让用户点了才报错。 */
body.readonly-mode [onclick*="showUpload("],
body.readonly-mode [onclick*="confirmUpload("],
body.readonly-mode [onclick*="confirmReimport("],
body.readonly-mode [onclick*="showBatchImport("],
body.readonly-mode [onclick*="showAddInventory("],
body.readonly-mode [onclick*="showAddOrder("],
body.readonly-mode [onclick*="showAddEmployee("],
body.readonly-mode [onclick*="showAddDianping("],
body.readonly-mode [onclick*="submitAddOrder("],
body.readonly-mode [onclick*="submitAddInventory("],
body.readonly-mode [onclick*="submitAddEmployee("],
body.readonly-mode [onclick*="submitAddDianping("],
body.readonly-mode [onclick*="submitEmpEdit("],
body.readonly-mode [onclick*="submitEmpProfile("],
body.readonly-mode [onclick*="submitOvertime("],
body.readonly-mode [onclick*="deleteEmployee("],
body.readonly-mode [onclick*="clearInventory("],
body.readonly-mode [onclick*="clearOrders("],
body.readonly-mode [onclick*="undoInventory("],
body.readonly-mode [onclick*="undoOrders("],
body.readonly-mode [onclick*="undoProcurement("],
body.readonly-mode [onclick*="undoEntry("],
body.readonly-mode [onclick*="toggleInvEdit("],
body.readonly-mode [onclick*="toggleEntryEdit("],
body.readonly-mode [onclick*="saveCosts("],
body.readonly-mode [onclick*="saveEntryOverrides("],
body.readonly-mode [onclick*="saveEntryDraft("],
body.readonly-mode [onclick*="resolveAllEntryDupes("],
body.readonly-mode [onclick*="exportEntry("],
body.readonly-mode [onclick*="clearAllEntry("],
body.readonly-mode [onclick*="commitEntry("],
body.readonly-mode [onclick*="clearProcurement("],
body.readonly-mode [onclick*="addStore("],
body.readonly-mode [onclick*="deleteRevenueDay("],
body.readonly-mode [onclick*="unlockRevenueDay("],
body.readonly-mode [onclick*="revImportParse("],
body.readonly-mode [onclick*="revImportConfirm("],
body.readonly-mode [onclick*="syncRevenue("],
body.readonly-mode [onclick*="showRevenueImport("],
body.readonly-mode [onclick*="showRevenueConfig("],
body.readonly-mode [onclick*="revConfigSave("],
body.readonly-mode [onclick*="revConfigTest("],
body.readonly-mode [onclick*="openStoreManager("],
body.readonly-mode [onclick*="openUserManager("],
body.readonly-mode [onclick*="openHolidayManager("],
body.readonly-mode [onclick*="openRosterPaste("],
body.readonly-mode [onclick*="exportRoster("],
body.readonly-mode [onclick*="editWorkbenchName("],
body.readonly-mode #dropZone,
body.readonly-mode #entryShotBtn,
body.readonly-mode .btn-danger,
body.readonly-mode .btn-edit,
body.readonly-mode .btn-del {
    display: none !important;
}

/* 只读模式下的可编辑单元格：保留可读外观，去掉「可改」暗示 */
body.readonly-mode td[contenteditable="true"],
body.readonly-mode [contenteditable="true"] {
    background: #fafafa !important;
    cursor: default !important;
}

/* ── 账号管理：可查看其它门店 ── */
.form-hint {
    margin-top: 6px; font-size: 11px; line-height: 1.6;
    color: var(--text-light, #6b7a72);
}
.store-check-list {
    display: flex; flex-wrap: wrap; gap: 6px 14px;
    padding: 8px 10px; border: 1px solid var(--border, #e2ebe6);
    border-radius: 8px; background: #fafafa; max-height: 150px; overflow-y: auto;
}
.store-check {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 13px; cursor: pointer; user-select: none;
}
.store-check input { cursor: pointer; margin: 0; }
.checkbox-inline {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 13px; cursor: pointer; margin: 0;
}
.checkbox-inline input { cursor: pointer; }
.vs-who { font-size: 13px; color: var(--text, #1f2a24); margin: 4px 0 10px; }
.user-vs {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 1px 7px; border-radius: 999px;
    background: #eef4ff; color: #3557a5;
    border: 1px solid #cddcff; font-size: 11px;
}

@media (max-width: 768px) {
    .readonly-badge { padding: 1px 6px; font-size: 10px; }
    .store-check-list { max-height: 120px; }
}

/* ══════════════════════════════════════════════
   备案页脚（公安备案要求：首页底部可见）
   ──────────────────────────────────────────────
   z-index 10000 高于锁屏遮罩(9999)，因此未登录 /
   锁屏状态下依然可见 —— 检查人员访问首页看到的就是
   锁屏页，备案号必须出现在那里才符合要求。
   ══════════════════════════════════════════════ */
.site-footer {
    position: fixed; bottom: 0; left: 0; right: 0;
    z-index: 10000;
    height: 26px;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255, 255, 255, .94);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-top: 1px solid #e2ebe6;
    font-size: 11.5px; color: #6b7a72;
    padding: 0 10px;
}
.site-footer-inner {
    display: flex; align-items: center; justify-content: center;
    gap: 14px; flex-wrap: wrap;
}
.beian-item {
    display: inline-flex; align-items: center; gap: 4px;
    color: #6b7a72; text-decoration: none; white-space: nowrap;
    transition: color .15s;
}
a.beian-item:hover { color: #1e5aa8; text-decoration: underline; }
.beian-icon { flex: none; vertical-align: -1px; }
.beian-pending { color: #8b968f; }
.beian-edit {
    border: 1px solid #cfdcd4; background: #fff; color: #6b7a72;
    border-radius: 5px; padding: 1px 7px; font-size: 11px;
    cursor: pointer; white-space: nowrap; line-height: 1.6;
}
.beian-edit:hover { border-color: #1e5aa8; color: #1e5aa8; }

/* 底部让位的最终数值统一收口在文件后段「底部三层堆叠的精确几何」处，
   这里不再写中间值（历史上 26/30/96/88 反复横跳，全靠后者覆盖前者）。 */

@media (max-width: 420px) {
    /* 窄屏：只留警徽 + 号码，隐藏 ICP 与"待补充"之外的冗余文字 */
    .site-footer-inner { gap: 8px; }
    .beian-edit { font-size: 10px; padding: 1px 5px; }
}

/* ══════════════════════════════════════════
   成本与利润测算（v11.0）
   ══════════════════════════════════════════ */
.panel-sub {
    display: block; margin-top: 4px;
    font-size: 12px; font-weight: 400; color: var(--text-light);
}

/* ① 月纯收 */
.rev-grid {
    display: grid; gap: 12px;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.rev-card {
    background: #f7faf8; border: 1px solid #e2ebe6; border-radius: 10px; padding: 12px 14px;
}
.rev-card label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 8px; }

/* 「月纯收项」卡片的堂食 / 外卖图标（V2.0，原为 🍽️ / 🛵 两个 emoji）。
   emoji 的尺寸由 font-size 决定、基线跟着字形跑，跟旁边的文字
   并排时高度和垂直位置都对不齐；换成 SVG 后必须显式给尺寸，
   否则会退化成默认的 300×150 把整行撑爆。
   给字号 1em 的方块 + 垂直微调，让它和 13px 的行高居中对齐。 */
.rev-card label .rev-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px; height: 15px;
    margin-right: 5px;
    vertical-align: -2px;
    color: var(--primary);
}
.rev-card label .rev-ico svg { display: block; width: 100%; height: 100%; }
.rev-total-card { background: #eff9f7; border-color: #bfdbfe; }
.rev-total { font-size: 24px; font-weight: 800; color: var(--primary); line-height: 1.2; }
.rev-sub { margin: 6px 0 0; font-size: 12px; color: var(--text-light); }

/* 纯收来源徽标：自动 = 跟随营收，手填 = 人工覆盖 */
.rev-badge {
    display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px;
    font-size: 10px; font-weight: 700; vertical-align: middle; letter-spacing: .2px;
}
.rev-badge.auto { background: #dcfce7; color: #166534; }
.rev-badge.manual { background: #fef3c7; color: #92400e; }
/* 取数口径说明：让店主一眼看到数字是从哪拉来的 */
.rev-basis {
    margin: 6px 0 0; font-size: 11px; line-height: 1.5; color: var(--text-light);
}
.rev-basis.manual { color: #b45309; font-weight: 600; }
.rev-card .cost-input-wrap { margin-bottom: 0; }
/* 营收未取到的醒目提示（整行铺满） */
.rev-hint {
    grid-column: 1 / -1; margin: 0; padding: 9px 12px; border-radius: 9px;
    background: #fffbeb; border: 1px solid #fde68a; color: #92400e;
    font-size: 12px; line-height: 1.6;
}

/* ② 成本明细表 */
.ct-wrap { overflow-x: auto; }
.ct-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 640px; }
.ct-table th {
    text-align: left; font-size: 12px; color: var(--text-light); font-weight: 600;
    padding: 8px 10px; border-bottom: 2px solid #e2ebe6; white-space: nowrap;
}
.ct-table td { padding: 8px 10px; border-bottom: 1px solid #f1f5f9; vertical-align: middle; }
.ct-table .ct-c-amt { width: 170px; }
.ct-table .ct-c-ratio { width: 130px; }
.ct-table .ct-c-src { color: var(--text-light); font-size: 12px; }
.ct-row:hover { background: #f7faf8; }
.ct-group > .ct-c-name .ct-name { font-weight: 700; }
.ct-child { background: #f7faf8; }
.ct-child .ct-name.sub { color: var(--text-light); font-weight: 500; }
.ct-name { font-weight: 600; }

.ct-toggle {
    display: inline-block; width: 20px; height: 20px; line-height: 20px; text-align: center;
    border: none; background: none; cursor: pointer; color: var(--text-light);
    font-size: 10px; transition: transform .15s; padding: 0; margin-right: 2px;
    vertical-align: middle;
}
.ct-toggle.open { transform: rotate(90deg); color: var(--primary); }
.ct-toggle.ph { visibility: hidden; }

.ct-badge {
    display: inline-block; font-size: 10px; padding: 1px 6px; border-radius: 4px;
    margin-left: 6px; vertical-align: middle; font-weight: 600; line-height: 1.5;
}
.ct-badge.auto { background: #dcfce7; color: #15803d; }
.ct-badge.manual { background: #fef3c7; color: #92400e; }
.ct-badge.ov { background: #fee2e2; color: #b91c1c; }

.ct-table .cost-input-wrap { max-width: 165px; }
.ct-table .cost-input-wrap input { padding-right: 26px; }
.ct-reset {
    position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
    border: none; background: #fee2e2; color: #b91c1c; cursor: pointer;
    width: 20px; height: 20px; border-radius: 4px; font-size: 12px; line-height: 1; padding: 0;
}
.ct-reset:hover { background: #fecaca; }

.ct-ratio { font-weight: 700; font-variant-numeric: tabular-nums; }
.ct-bar {
    display: block; height: 4px; background: #e2ebe6; border-radius: 2px;
    margin-top: 4px; overflow: hidden;
}
.ct-bar > i { display: block; height: 100%; background: var(--primary); border-radius: 2px; }
.ct-row.ct-auto .ct-bar > i { background: #0ea5e9; }

/* 计算过程展开区 */
.ct-detail-row > td { background: #f7faf8; padding: 0 10px 12px !important; }
.ct-detail { border: 1px dashed #cbd5e1; border-radius: 8px; padding: 10px 12px; overflow-x: auto; }
.ct-detail-table { width: 100%; border-collapse: collapse; font-size: 12px; min-width: 520px; }
.ct-detail-table th {
    font-size: 11px; color: var(--text-light); padding: 5px 8px;
    border-bottom: 1px solid #e2ebe6; white-space: nowrap;
}
.ct-detail-table td { padding: 5px 8px; border-bottom: 1px solid #eef2f7; }
.ct-detail-table .n { text-align: right; font-variant-numeric: tabular-nums; }
.ct-eq { font-weight: 700; color: var(--primary); }
.ct-detail-sum td { font-weight: 700; border-top: 1px solid #cbd5e1; border-bottom: none; }

.cost-warn {
    display: none; background: #fffbeb; border: 1px solid #fde68a;
    border-radius: 8px; padding: 8px 12px; margin-bottom: 12px;
}
.cost-warn p { margin: 3px 0; font-size: 12px; color: #92400e; }

/* ③ 利润测算单元 */
.profit-summary {
    display: grid; gap: 12px; margin-bottom: 16px;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.ps-cell {
    background: #f7faf8; border: 1px solid #e2ebe6; border-radius: 10px;
    padding: 14px 16px; display: flex; flex-direction: column; gap: 4px;
}
.ps-label { font-size: 12px; color: var(--text-light); font-weight: 600; }
.ps-amt { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; }
.ps-amt.cost { color: #b45309; }
.ps-sub { font-size: 11px; color: var(--text-light); }
.ps-net { background: #f0fdf4; border-color: #bbf7d0; }
.ps-net .ps-amt { color: #15803d; }
.ps-net.neg { background: #fdf3f3; border-color: #fecaca; }
.ps-net.neg .ps-amt { color: #b91c1c; }

.profit-bars {
    background: #f7faf8; border: 1px solid #e2ebe6; border-radius: 10px; padding: 12px 14px;
}
.pb-title { font-size: 12px; color: var(--text-light); margin-bottom: 10px; }
.pb-row {
    display: grid; grid-template-columns: 130px 1fr 92px 52px;
    align-items: center; gap: 10px; padding: 4px 0; font-size: 12px;
}
.pb-name {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600;
}
.pb-track { height: 12px; background: #eef2f7; border-radius: 6px; overflow: hidden; }
.pb-fill {
    display: block; height: 100%; border-radius: 6px;
    background: linear-gradient(90deg, #fbbf24, #f59e0b);
}
.pb-fill.auto { background: linear-gradient(90deg, #38bdf8, #0ea5e9); }
.pb-amt { text-align: right; font-variant-numeric: tabular-nums; }
.pb-pct {
    text-align: right; font-weight: 700; color: var(--primary);
    font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
    .pb-row { grid-template-columns: 96px 1fr 78px 46px; gap: 6px; font-size: 11px; }
    .rev-total { font-size: 20px; }
    .ps-amt { font-size: 19px; }
}


/* ══════════════════════════════════════════════
   备案页脚 · 显示优化（v11.0.4）
   ──────────────────────────────────────────────
   z-index 10000 > 锁屏遮罩 9999：未登录/锁屏时同样可见，
   满足公安备案「首页底部可见」的硬性要求。
   ══════════════════════════════════════════════ */
.site-footer {
    /* 固定高度改最小高度：iPhone 底部 Home 指示条要靠 padding 撑开，
       而全局 box-sizing:border-box 下，固定 height 会被这个 padding 挤掉内容 */
    height: auto;
    /* border-box 下 min-height 连 padding 一起算：内容实际只有一行 ~17px，
       若只写 28px 总高会是 17+34=51，与 nav 的 bottom 偏移（28+安全区）对不上，
       中间漏出 11px 缝。写成 calc 把内容区精确钉在 28px。
       v3 瘦身：28 → 20px，字号/警徽同步缩小（见 420px 断点），
       给内容让出 8px；v6 再压到 14px（内容实测只有 13px：10px 字 + 1px padding），
       nav 的 bottom 与 body 让位同步改（见底部几何收口）。 */
    min-height: calc(14px + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: rgba(255, 255, 255, .96);
    border-top: 1px solid #e8eaed;
    font-size: 12px;
    color: #5f6368;
    letter-spacing: .1px;
}
.site-footer-inner { gap: 10px; }

.beian-item {
    gap: 5px;
    font-variant-numeric: tabular-nums;
}
a.beian-item { color: #5f6368; }
a.beian-item:hover { color: #1a73e8; }

/* 警徽与文字基线对齐，避免 SVG 把行高顶开 */
.beian-icon {
    width: 13px; height: 13px;
    display: block; flex: none;
    border-radius: 50%;
}

/* 分隔竖线 */
.beian-sep {
    color: #d4d7db;
    font-size: 11px;
    user-select: none;
}

/* 未备案占位：比正文更淡，暗示"待补充"而非正式备案号 */
.beian-pending {
    color: #b0b5bb;
    font-style: normal;
}
.beian-pending .beian-icon { opacity: .45; }

/* 管理员「✎ 填写」入口 */
.beian-edit {
    border: 1px solid #dadce0;
    border-radius: 6px;
    padding: 2px 8px;
    font-size: 11px;
    color: #5f6368;
    background: #fff;
    cursor: pointer;
    transition: all .15s;
}
.beian-edit:hover {
    border-color: #1a73e8;
    color: #1a73e8;
    background: #f0f6ff;
}

/* 版本号：比备案号更淡一档 —— 它是给排查问题的人看的，
   不该在视觉上和法定备案号抢注意力。tabular-nums 让数字等宽，
   版本从 11.0.9 跳到 11.0.10 时页脚不会因字符变宽而抖动。 */
.app-version {
    color: #9aa0a6;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    letter-spacing: .2px;
    user-select: all;   /* 方便直接三击复制去对部署记录 */
}

/* ── 备案编辑弹窗 ── */
.beian-tip {
    margin: 0 0 16px;
    padding: 10px 12px;
    background: #f8f9fa;
    border-left: 3px solid #1a73e8;
    border-radius: 4px;
    font-size: 12.5px;
    line-height: 1.65;
    color: #5f6368;
}
.beian-hint {
    margin-top: 5px;
    font-size: 11.5px;
    line-height: 1.6;
    color: #80868b;
}
.beian-hint b { color: #5f6368; font-weight: 600; }

/* 实时校验反馈：ok 绿 / warn 橙 */
.beian-check {
    margin-top: 6px;
    font-size: 11.5px;
    line-height: 1.55;
    min-height: 0;
    word-break: break-all;
}
.beian-check.ok { color: #188038; }
.beian-check.warn { color: #c26401; }

/* 「清空」靠左，与「取消/保存」形成左右分组 */
.modal-actions .beian-clear { margin-right: auto; }
.modal-actions .beian-clear:hover {
    border-color: #d93025;
    color: #d93025;
    background: #fef7f6;
}

@media (max-width: 420px) {
    /* min-height 由基础规则的 calc(20px + env()) 统一给出，这里不再覆盖 */
    .site-footer { font-size: 10px; padding: 0 6px env(safe-area-inset-bottom, 0px); }
    .site-footer-inner { gap: 6px; }
    /* 行高必须钉死：中文 10px 的 line-height:normal 实测 15px，
       加上 inner 的 1px padding 就是 16px，把 14px 的备案条撑到 16px，
       多出的 2px 会顶进上面 nav 的底下（备案条 top 上移、nav 纹丝不动）。 */
    .beian-item { line-height: 1.2; }
    .beian-icon { width: 10px; height: 10px; }
    .beian-sep { display: none; }
    .beian-edit { font-size: 10px; padding: 1px 6px; }
}

/* ── 底部停靠坞：备案条 + 底栏锁进同一个 fixed 容器 ───────────────
   此前两层各自 fixed、各写各的 bottom，全靠"备案条 14px"这个写死的数
   手工对齐 —— 登录管理员后「✎ 填写」按钮(约 20px)一显示，备案条被撑高，
   上沿直接顶进底栏，盖住导航文字（真机截图实测）。
   治本：改成同一个容器里的兄弟节点流式排列，物理上不可能重叠；
   总高用 JS 实测写入 --dock-h，body 让位跟着变量走 —— 备案条撑高、
   字号缩放、Safari 工具栏收放、env() 在不同浏览器的差异，全部自动适配。 */
.bottom-dock {
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 10000;   /* 高于锁屏遮罩 9999：锁屏时备案号仍可见（合规要求） */
    display: flex; flex-direction: column; align-items: center;
    /* 安全区不再由坞垫（那会透出一条绿渐变，跟底栏的玻璃白硬碰硬，
       观感是"底栏下面一条断层"）—— 改由底栏自己垫，
       玻璃白背景顺势延伸过 Home 条区域，底部一气呵成。 */
}
/* 坞内自上而下 = 导航栏菜单 → 备案信息。
   两个孩子改流式：旧的 fixed/bottom/left/right 对 static 无效，自然作废。
   安全区怎么垫，用户拍过板（"直接适配手机屏幕最低端，位置类似于 iPhone
   的小白条"）：备案条不垫安全区，整个坞（含白色药丸）压到屏幕物理最低端
   —— 药丸是半透明白，Home 指示条压在药丸上白上白、几乎不可见；
   换来的好处是坞直接薄 34px，正文可视区多出一截。 */
.bottom-dock .bottom-nav {
    position: static; width: 100%;
    padding-bottom: 0;  /* 不再垫安全区：坞整体沉底 */
}
.bottom-dock .site-footer {
    position: static;
    width: 100%;
    min-height: 0;      /* 原 calc(14px+env) 是"fixed+手工对齐"时代的遗物：
                           不清掉会按 48px 居中垫高，文字下方白空 27px */
    padding-bottom: 0;  /* 安全区不垫：用户要求备案号贴屏幕物理最低端 */
}
/* 让位 = 坞的实测高度。兜底值只服务"JS 没跑、坞未建立"的瞬间：
   那时 footer 还是原始样式（内容 14px + 自己垫安全区），nav 悬在它上面。 */
.bottom-nav { bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
body { padding-bottom: var(--dock-h, calc(41px + env(safe-area-inset-bottom, 0px))); }

/* ── 手机端页头（各页面自己的标题行）压缩 ─────────────────────────
   v4 去重：页面标题进了顶栏（.topbar-title），页头自己的大标题和
   月份下拉都成了重复信息 —— 月份尤其多余：顶栏的 globalMonth 是主控，
   改动会同步所有页面月份（app.js 里 handleMonthChange 的同步循环），
   页面留着第二个月份下拉只会让人疑惑"改哪个才生效"。
   页头只剩操作按钮（上传识别/导出这类高频动作，值得常驻），一行 30px。 */
@media (max-width: 768px) {
    .page-header { margin-bottom: 8px; gap: 6px; min-height: 30px; }
    .page-header .page-title { display: none; }
    .header-actions { gap: 6px; }
    .header-actions select {
        display: none;                  /* 页面月份全被顶栏主控月份取代 */
    }
    .header-actions .btn {
        padding: 0 9px; font-size: 12px; border-radius: 7px;
        min-height: 30px; height: 30px;
    }
}

/* ── 手机端备案条「白块」消除 ─────────────────────────────────────
   真机反馈：备案条下方一圈白色过宽。那截白其实是两段：
   ① Home 指示条的 34px 安全区（系统强制，谁也去不掉）；
   ② iPhone 上 Safari 自带的底部工具栏（~65px，属于浏览器 UI，
      网页管不着 —— 从 Safari 分享菜单「添加到主屏幕」后打开即全屏，
      这 65px 直接消失，index.html 已加 apple-mobile-web-app meta）。
   网页能做的是别再给 ① 刷白底：footer 背景透明，只有文字行自带
   白底小条，安全区透出 body 的绿色渐变 —— 视觉上白块变成绿底，
   备案号仍完整可见（公安备案要求不受影响）。 */
@media (max-width: 768px) {
    .site-footer {
        background: transparent;
        backdrop-filter: none; -webkit-backdrop-filter: none;
        border-top: none;
    }
    /* 内容不满一屏时（手风琴收起、数据少的月份），.content 没有可滚区间，
       iOS 会把手势直接透传给 document —— 整页一弹，手势就被它吃掉了。
       强制页面至少比可视区高 1px：容器永远"可滚"，边界反馈走容器自己的
       回弹，不再透传。1px 的滚动余量肉眼不可见。 */
    .page.active { min-height: calc(100% + 1px); }
    .site-footer-inner {
        background: rgba(255,255,255,.95);
        padding: 1px 10px 0;
        border-radius: 8px 8px 0 0;
    }
}

/* ── 全屏（添加到主屏幕）模式 ─────────────────────────────────────
   目标机型 iPhone 17 Pro：393 × 852pt、灵动岛、Home 指示条安全区 34pt。
   从主屏图标打开时视口 = 整块 852pt，没有 Safari 的地址栏和底部工具栏。

   底部延伸方案迭代史（别再走回头路）：
   ✗ v1 垫进白色小药丸 → 药丸只有中间一截，34pt 白块悬在绿背景上（翻车）；
   ✗ v2 footer 透明、安全区透出绿色渐变 → 本意"背景色自然延续=贴边"，
     真机上备案号下面凭空一整段色带，用户读出来的是"2cm 空白"（翻车）；
   ✓ v3（本版）iOS 原生 TabBar 式：备案行 + 34pt 安全区整段铺满宽玻璃白，
     与上方导航栏连成一块完整底栏，一直铺到屏幕物理最低端 ——
     和微信/支付宝底栏完全同款。Home 白条压在玻璃白上白上白、几乎不可见；
     备案号下方不再存在任何"另一段颜色"，空白感物理消失。
   文字仍在安全区上方，小白条压不到；坞高不变（仍是 nav+13px+34pt），
   ResizeObserver 实测写入 --dock-h，body 让位自动跟上。 */
html.standalone .bottom-dock .site-footer {
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    /* 不加 border-top：备案行与导航栏必须读成"同一块玻璃"，
       任何一条分隔线都会把它重新切成两截、唤回"下面又空了一段"的错觉 */
    border-top: none;
}
/* 白底小药丸在整片玻璃白上失去存在意义：透明化融入，
   顶圆角一并去掉 —— 否则圆角两侧会透出细微色差，看起来像道缝。 */
html.standalone .bottom-dock .site-footer-inner {
    background: transparent;
    border-radius: 0;
}

/* 「添加到主屏幕」引导条：仅 iOS Safari 且非全屏时出现一次，可关闭 */
.pwa-hint {
    position: fixed;
    left: 12px; right: 12px;
    bottom: calc(var(--dock-h, 48px) + 10px);
    z-index: 10050;              /* 高于坞 10000，低于弹窗 10100 */
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px;
    font-size: 12.5px; line-height: 1.45;
    color: #fff;
    background: rgba(26, 36, 31, .94);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border-radius: 12px;
    box-shadow: 0 8px 26px rgba(0, 0, 0, .26);
    animation: pwaHintIn .34s cubic-bezier(.32, .72, 0, 1) both;
}
.pwa-hint-txt { flex: 1 1 auto; }
.pwa-hint-x {
    flex: 0 0 auto;
    padding: 6px 11px;
    font-size: 12px; line-height: 1;
    color: #fff;
    background: rgba(255, 255, 255, .18);
    border: none; border-radius: 8px;
    cursor: pointer;
}
.pwa-hint-out { animation: pwaHintIn .26s cubic-bezier(.32, .72, 0, 1) reverse both; }
@keyframes pwaHintIn {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ── 手机端平台卡片一行化 ─────────────────────────────────────────
   6 张渠道卡原是 2 列 × 3 行（每张 ~115px 高，整片 ~360px），展开的
   明细面板被挤到屏幕外 —— 用户"根本看不到任何展开后的内容"。
   一行化：auto-fit 均分一行（盘点 6 张、订货 4 张各自成一行），
   卡片改竖排小卡：徽章 + 名称 + 金额；条数/进度收进面板不丢信息。
   整行 ~64px，面板顶上移近 300px，展开内容一屏可见。 */
@media (max-width: 768px) {
    .channel-cards {
        grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
        gap: 4px; margin-bottom: 10px;
    }
    .channel-card { padding: 6px 2px 5px; border-radius: 10px; }
    /* 名称行竖排：徽章在上文字在下。文字是裸文本节点（没法单独
       ellipsis），靠块的 nowrap+overflow 截断长名称（"公司代采盘点"） */
    .channel-card .ch-name {
        display: block; text-align: center;
        font-size: 10px; line-height: 1.2; margin-bottom: 2px;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .channel-card .ch-name .platform-logo {
        display: block; margin: 0 auto 3px;
        width: 20px; height: 20px; font-size: 10px; border-radius: 5px;
    }
    /* 金额是外溢重灾区：一行 6 卡每张只有 ~58px，"¥38,207.09" 用 13px
       必然顶破卡边叠到隔壁卡上（真机截图）。三重保险：
       ① 字号降到 10.5px（9 字符 ≈ 48px，放得下）；② 表格数字等宽；
       ③ nowrap+ellipsis 兜底 —— 万一还有更长的也只截尾不出界。 */
    .channel-card .ch-amount {
        font-size: 10.5px; letter-spacing: 0;
        font-variant-numeric: tabular-nums;
        max-width: 100%;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .channel-card .ch-count { display: none; }   /* 条数/进度面板里有 */
    /* 「▾ 查看明细」常驻占位（opacity:0 但 display:block，窄卡里还折行）
       白吃 42px 高度 —— 点开面板本身就是反馈，这行提示在窄卡没有存在价值 */
    .channel-card::after { display: none; }
}

/* ── 手机端底栏极限窄 ─────────────────────────────────────────────
   40px → 32px：图标 14 + 标签 9px，padding 3/2。8 个入口每个仍有
   ~44px 触控宽（flex 均分），高度压到图标+文字的物理极限。 */
@media (max-width: 768px) {
    .bottom-nav .nav-btn { min-width: 34px; padding: 2px 2px 1px; gap: 0; }
    .bottom-nav .nav-btn span:not(.nav-icon) { font-size: 9px; line-height: 9px; }
    .bottom-nav .nav-icon { font-size: 13px; line-height: 1.1; }
}

/* ── 手机端说明标注：默认收起成一行 ─────────────────────────────── */
@media (max-width: 768px) {
    .hints-toggle { display: inline-flex; }
    .hints-body { height: 0; }
    .hints-fold.open .hints-body { height: auto; }
    /* 正文展开后各行之间留点呼吸，收起时 height:0 一并裁掉 */
    .hints-body .hint { margin-top: 6px; }
    .hints-body .hint:first-child { margin-top: 8px; }
}

@media (min-width: 769px) {
    /* 平板/电脑无底部导航，只需为备案条留 28px（+ 安全区） */
    body { padding-bottom: var(--dock-h, calc(28px + env(safe-area-inset-bottom, 0px))); }
}

/* ══════════════════════════════════════════════════════════════
   动效系统（v11.0.7）
   ══════════════════════════════════════════════════════════════
   设计约束 —— 门店现场是抢时间的地方，动效宁可少不可多：
     ① 只动 transform / opacity。这两个走 GPU 合成，不触发重排重绘；
        width / height / top / left 一动就引发布局计算，低端机直接掉帧。
        （圆角 border-radius 是个例外，见 ②，它比这俩贵但值这个价。）
     ② scale 对 echarts 是安全的 —— 这条纠正 v11.0.7 写下的错误结论。
        当时以为「scale 改变元素实际宽度，echarts 用 clientWidth 量尺寸会量错」。
        实测（/tmp/wtest）证伪：scale .84 动画途中初始化图表，
        clientWidth=400、echarts.getWidth()=400、canvas 属性宽=800，
        与无动画基准逐项一致；只有 getBoundingClientRect() 变成 336（400×.84）。
        原因：clientWidth 读的是布局宽度，transform 不参与布局；
        getBoundingClientRect 读的是视觉盒，才受 transform 影响。
        所以含图表的容器也能放心用 scale，只要别拿 getBoundingClientRect 定尺寸。
     ③ 时长：页面切换 180~320ms；面板开合可以放到 400ms —— 后者是
        用户主动触发的强反馈，等得起；前者是被动等待，等不起。
        v11.0.10 把面板开合从 360ms 加到 400ms，多出的 40ms 全花在
        弹性过冲上（见 panelOpen 那条注释），不是"动得更慢"。
     ④ 入场用 backwards 而非 both：both 会让动画结束后的 forwards 状态常驻，
        而 animation 优先级高于普通声明，会把 :hover 的 transform 整个盖掉
        （卡片悬停上浮会失效）。backwards 只在延迟期间生效，结束后还原。
        退场动画反过来必须用 forwards，否则动画一结束元素就跳回原样再消失。
     ⑤ 退出动画同样做 —— v11.0.9 已把弹窗关闭全部收口到 closeModal()，
        不再是 v11.0.7 时「39 处直接 display:none、覆盖不全」的局面。
     ⑥ 系统开启「减弱动态效果」时全部关闭（无障碍 + 省电）。
   ────────────────────────────────────────────────────────────── */

@keyframes pageIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
/* 过冲（overshoot）怎么加、加在哪 —— v11.0.10 定下的规矩：
     主动触发的动画 → 加。面板开合、按钮按下、Tab 激活，
       用户刚做了动作，弹一下是"收到了"的回执，Q 弹的手感值这个钱。
     被动等待的动画 → 不加。页面/板块入场是用户干等的结果，
       十几个块同时冲过头再弹回来，不是 Q 弹，是整页抖了一下。
   所以下面 cardIn 只给 1.5px 的轻微回落（卡片少、有错峰、看得出层次），
   panelIn 干脆不加，只把幅度和时长各提一档，用"浮现得更实在"替代弹跳。 */
@keyframes cardIn {
    0%   { opacity: 0; transform: translateY(5px); }
    72%  {             transform: translateY(-1.5px); }
    100% { opacity: 1; transform: none; }
}
@keyframes panelIn {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}
@keyframes modalMaskIn { from { opacity: 0; } to { opacity: 1; } }
/* 手机端沉底抽屉的进出场：纯位移，不带缩放。
   缩放版 panelOpen 用在居中的小面板上没问题，贴到屏幕底边再缩放，
   左右两侧会露出被缩小的圆角和下方的遮罩，看着像"没贴稳"。
   sheet 是 iOS 底部弹层的标准做法：整块从屏幕外滑进来，滑出去。 */
@keyframes sheetIn  { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes sheetOut { from { transform: translateY(0); }    to { transform: translateY(100%); } }
/* 原 modalPopIn（内容块 .22s 小弹入）已删除：
   弹窗内容的入场统一改用下面 ④b 的 panelOpen，一处动画只留一个来源。 */

/* ① 页面切换：8 个功能页入场。
      挂在 .page-ready 而不是 .active 上 —— 这是 v11.0.7 实测踩到的坑：
      .active 是 switchPage 一开始就加的，那时数据还在路上，页面是空的，
      动画全程在空容器上跑完，用户只看见"数据突然蹦出来"。
      .page-ready 由 switchPage 在 await 完各页 load 函数后补加（见 app.js），
      保证动画落在已经渲染出内容的地方。

      整页只做淡入、不做位移 —— 位移交给下面 ② 的 .panel 去做。
      若整页和内部板块都位移，两者会叠加：整页 12px + 板块 14px = 26px，
      观感变成大幅弹跳，而且 .page 是含几百个 DOM + canvas 的大容器，
      transform 成本随子树复杂度上升。分层做既省又干净。

      实测三种挂法（空壳播 / 就绪后播大容器 / 就绪后播内容块）帧率完全一致
      （中位 16.7ms、掉帧数在噪声内）—— 动画本身几乎不花钱，
      真正的卡顿来自数据渲染的长任务（最长帧 66ms），别再拿动画开刀。 */
/* 与下面 ② 的板块错峰是"接力"关系：
   页面先用 .22s 淡入铺底（刻意不位移 —— 否则会和板块的 14px 叠加成大幅弹跳），
   板块紧接着以 45ms 间隔依次上浮。首尾相接而非同时乱动，这是"连贯"的来源。 */
.page.active.page-ready {
    animation: pageIn .22s var(--ios-ease) backwards;
}

/* ② 内容板块入场 —— 全部 8 个页面通用（每页 8~18 个 .panel）。
      这是"看得见"的主力：数据看板页其实一个 .stat-card 都没有，
      只有 7 个图表面板，之前给 .stat-card 做的错峰动画在这页根本不播。
      统一同时入场、不做逐块错峰：一屏十几个块若逐个延迟，
      最后一块要等到 0.7s 之后，反而显得拖沓。
      translateY 不改变元素宽度，echarts 用 clientWidth 量尺寸不受影响；
      含图表的容器依然不能用 scale（见顶部约束 ②）。 */
.page.active.page-ready .panel {
    animation: panelIn var(--dur-base) var(--ios-out) backwards;
}
/* iOS 列表入场的灵魂：不是一起涌上来，而是自上而下依次浮现。
   间隔 45ms、只错前 6 个 —— 再多最后一个要等 0.5s 以上反而拖沓，
   第 7 个起统一走最后一档，把节奏收住。 */
.page.active.page-ready .panel:nth-child(2)   { animation-delay: .045s; }
.page.active.page-ready .panel:nth-child(3)   { animation-delay: .09s;  }
.page.active.page-ready .panel:nth-child(4)   { animation-delay: .135s; }
.page.active.page-ready .panel:nth-child(5)   { animation-delay: .18s;  }
.page.active.page-ready .panel:nth-child(6)   { animation-delay: .225s; }
.page.active.page-ready .panel:nth-child(n+7) { animation-delay: .27s;  }

/* ③ 卡片错峰入场 —— 只给「展示型」卡片做。
      统计卡是数据渲染时由 innerHTML 新建的，天然就是"数据到了才播"，
      不需要 page-ready 那一套。幅度 5px，和外层面板的 14px 叠加后约 19px，
      属于"有层次但不轻浮"的范围。
      注意：数据看板页一个 .stat-card 都没有（只有 7 个图表面板），
      所以看板页的入场效果全部由上面 ② 的 .panel 承担。
      渠道卡（美团/美菜/代采…）是高频点击的交互元素，不做入场 ——
      实测给它加入场动画会让切页帧间隔从 23.9ms 顶到 28.2ms，
      而它对操作的正反馈很弱，只保留 hover 上浮 + 按下回弹（见 ⑤）。
      取舍原则：频繁点的元素少动，看数据的元素多动。 */
.stat-card { animation: cardIn var(--dur-base) var(--ios-out) backwards; }
.stat-card:nth-child(2) { animation-delay: .05s; }
.stat-card:nth-child(3) { animation-delay: .10s; }
.stat-card:nth-child(4) { animation-delay: .15s; }
.stat-card:nth-child(n+5) { animation-delay: .20s; }

/* ④ 弹窗：遮罩只做淡入，内容的缩放展开交给下面 ④b 的 .pm-open。
      这里刻意不给 .modal-content 写 animation —— 两条 animation 简写会互相覆盖，
      而 .pm-open 播完就被 JS 摘掉，一旦摘掉元素会立刻回落去播另一条、重播一次。
      一处动画只留一个来源，避免这种"播两遍"的坑。 */
.modal { animation: modalMaskIn var(--dur-fast) var(--ios-ease) backwards; }

/* ④b 面板 / 弹窗开合（v11.0.9）—— 仿 iPhone 打开 App 的转场。
      三个要素缺一不可：
        · 缩放 scale .84 → 1：从触发元素的体量"长"出来
        · 圆角 30px → 12px：这是 iPhone 开关 App 的灵魂 ——
          图标的大圆角在放大过程中收敛成面板的小圆角
        · 弹性收尾 cubic-bezier(.32,.72,0,1)：iOS 系统曲线的手感，
          末端减速极慢，有"吸住"的感觉，比 ease-out 高级很多

      transform-origin 由 JS 按触发元素位置动态设置（见 app.js animOpen），
      所以面板是"从被点的那张卡片里长出来"，而不是从自己中心放大 ——
      这个差别很大，从中心放大是"弹窗"，从卡片长出才是"打开 App"。

      关闭用 forwards：退场动画若不保持终态，动画一结束元素会先跳回原样
      再被 display:none，闪一下。入场用 backwards 则相反，见顶部约束 ④。

      关闭同理：先吸一下（1.014）再缩走。iOS 关 App 就是这个手感 ——
      不是直接变小，而是"被吸回去"。

      easing 写在 keyframes 里而不是 animation 简写上：过冲是分段动作，
      起手、冲顶、回落、归位四段需要的曲线各不相同，
      一条曲线通吃会把弹跳压平。keyframes 里的 animation-timing-function
      只管自己到下一帧的那一段，优先级高于 animation 属性。

      过冲 3.2% 会让面板在窄屏上超出视口 3.2%。transform 造成的溢出
      一样进滚动区，会撑出一条横向滚动条 —— 所以 app.js 在动画期间
      临时给 <html> 加 overflow-x:hidden，播完撤掉。
      为什么不常驻：body/html 一旦 overflow 不是 visible，内部 position:sticky
      的表头就会改以它为滚动容器而失效，这项目里有好几处 sticky 表头。 */
/* 面板开合只做 transform + opacity，绝不动 height。

   这一段踩过两次坑，都记在这里免得再绕回去：
   ① 最初只做缩放淡出、不管高度 —— 结果面板在整段动画里死死占着满高度，
      等收尾 display:none 的那一帧，下方内容一帧内上跳 674px，就是"跳一下"。
   ② 改成高度跟着一起塌（height/padding/margin 同步到 0）—— 跳是没了，
      但每帧都要把面板内上千个单元格重排一遍。121 行表格的实测：
      平均帧间隔从 17ms 劣化到 37ms，最大 110ms，一屏掉 5~6 帧。
      试过 contain: layout paint，没用，帧率纹丝不动。

   最终走 FLIP：布局只变一次（用负 margin 抵消面板自身高度，下方内容
   瞬间落到最终位置），再用 transform 把它们拉回原处、动画归零。
   补位动作全程跑在合成层上，零重排。实测回到 17.1ms/帧、最大 50ms、
   超 50ms 的帧为 0，且单帧位移占比 19.8%（不再有跳变）。
   配套的 JS 见 app.js 的 animOpen / animClose。 */
@keyframes panelOpen {
    0% {
        opacity: 0; transform: scale(.82) translateY(18px); border-radius: 32px;
        animation-timing-function: cubic-bezier(.16, .72, .30, 1);   /* 起手快，到顶前收力 */
    }
    55% {
        opacity: 1; transform: scale(1.018) translateY(0); border-radius: 11px;
        animation-timing-function: cubic-bezier(.40, 0, .45, 1);     /* 回落，柔和不生硬 */
    }
    78% {
        opacity: 1; transform: scale(.992) translateY(0);
        animation-timing-function: cubic-bezier(.33, 0, .30, 1);     /* 归位，末端吸住 */
    }
    100% {
        opacity: 1; transform: scale(1) translateY(0); border-radius: 12px;
    }
}
@keyframes panelClose {
    0% {
        opacity: 1; transform: scale(1) translateY(0); border-radius: 12px;
    }
    100% {
        opacity: 0; transform: scale(.96) translateY(6px); border-radius: 22px;
    }
}
/* 开 .40s / 关 --dur-base(.34s)：打开是"请进来"，慢一点；关闭是"扔掉"，快一点。
   这两个时长必须和 app.js 里补位动画的 FLIP_OPEN / FLIP_CLOSE 保持一致，
   否则面板还在动、下方内容已经停了，接缝处会看出来。 */
.pm-open  { animation: panelOpen  .40s backwards; }
.pm-close { animation: panelClose var(--dur-base) var(--ios-sheet) forwards; }
/* 只提示合成属性。别再往里加 height：那会诱导浏览器为布局动画做徒劳的准备。 */
.pm-open, .pm-close { will-change: opacity, transform; }

/* 大面板降级：位移 + 淡入，不带缩放。
   scale 会逼着浏览器每帧重新光栅化整个图层，位移则只是把已有纹理挪一下。
   4900px 的面板实测（收起）：
     scale(.82)+translateY  平均 31.9ms，6 帧超过 50ms
     translateY（无 scale） 平均 17.8ms，1 帧超过 50ms
     完全不播动画（基线）   平均 17.9ms
   也就是说去掉缩放后，动画本身几乎不花钱，却还能保住"滑进来"的动感。
   阈值 1400px 由 app.js 的 PM_TALL 控制：矮面板照样走上面那套精致的过冲缩放。 */
@keyframes panelShiftIn {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes panelShiftOut {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(8px); }
}
.pm-open-shift  { animation: panelShiftIn  .34s var(--ios-out) backwards; }
.pm-close-shift { animation: panelShiftOut .26s var(--ios-sheet) forwards; }
.pm-open-shift, .pm-close-shift { will-change: opacity, transform; }

/* 折叠面板（考勤日面板 / 月度品类）走"从标题下沿展开"，
   而不是从触发元素位置弹出 —— 它们是上下关系，accordion 该有的方向。
   JS 传 trigger=null 时不写内联 transform-origin，这条才生效。 */
.pm-from-top { transform-origin: 50% 0%; }

/* ⑤ 按下反馈（v11.0.10 加强）。
      按下 .07s 缩到 .94，松开 .17s 带回弹（会微微冲过 1 再落回原位）。
      按下刻意比松开快，这是有讲究的：按下慢叫"迟钝"，松开慢叫"有质感"，
      两者快慢对调一下手感就全变了。
      不用 pointer-events 锁，动画期间照样能点，避免让人误以为卡住。
      放在文件末尾，确保压过前面的 :hover */
.nav-btn, .side-btn, .btn, .tab-btn {
    transition: transform var(--dur-fast) var(--ios-spring),
                background .15s var(--ios-ease),
                color .15s var(--ios-ease),
                box-shadow .15s var(--ios-ease);
}
.nav-btn:active, .side-btn:active, .btn:active, .tab-btn:active {
    transform: scale(.94);
    /* 四个值一一对应上面四条属性：只有 transform 变快，颜色过渡维持 .15s。
       写成单个值会把颜色也一起拖快，hover 变色会跟着一闪 */
    transition-duration: var(--dur-tap), .15s, .15s, .15s;
}
/* 渠道卡单独写：它 hover 时上浮 2px，按下必须把这个上浮留着再叠加缩放，
   否则按住的一瞬间卡片会先掉下来再缩，看着像"塌了一下" */
.channel-card { transition: transform var(--dur-fast) var(--ios-spring), box-shadow .15s var(--ios-ease); }
.channel-card:active {
    transform: translateY(-2px) scale(.95);
    transition-duration: var(--dur-tap), .15s;
}

/* Tab 激活弹跳（v11.0.10）。
      报表页 5 个、上传页 7 个 Tab，原来切换只是换个边框色，
      点了以后没有"切过去了"的实感 —— 尤其上传页那排 Tab 很长，
      手指落在哪个上，视线不一定跟得上。
      给新激活的 Tab 一次 1.07 的过冲弹跳，把视线拽过去。
      没做滑动指示条：Tab 个数和宽度随数据变，算位置要处理窗口缩放
      和横向滚动，为这点观感引入一堆布局计算不划算。 */
@keyframes tabPop {
    0%   { transform: scale(1); }
    38%  { transform: scale(1.07); }
    100% { transform: scale(1); }
}
.tab-btn.pm-tab-pop { animation: tabPop var(--dur-base) var(--ios-spring); }

/* ⑦ 移动端适配（iOS 优先）。
      Apple HIG：可点区域不小于 44×44pt，手指才点得准 —— 原来只有 .nav-btn
      在窄屏下拿到 44px，按钮 / Tab 在手机上仍然是"细长一条"，容易点空。
      iOS 上横向滚动的表格必须显式开惯性，否则滑起来是一格一格的。 */
@media (max-width: 768px) {
    .btn, .tab-btn, .side-btn, .nav-btn { min-height: 44px; }
    /* 弹窗里并排的按钮在窄屏各占一份，避免误触相邻按钮 */
    .modal-actions { gap: 10px; }
    .modal-actions .btn { flex: 1 1 auto; min-height: 44px; }
    /* 横向滚动表格：开惯性 + 横滑到底不带动整页（不误触发返回手势） */
    .table-wrap, .ct-wrap, .modal-entry .table-wrap {
        overscroll-behavior-x: contain;
    }
    /* 手机上左右留白收窄，把宽度让给内容 */
    .content { padding-left: 12px; padding-right: 12px; }
    .nav-icon { font-size: 22px; }
}

/* iPhone 窄屏（≤430px）再收一档 */
@media (max-width: 430px) {
    .content { padding-left: 10px; padding-right: 10px; }
    .page-title { font-size: 18px; }
}

/* ⑥ 无障碍：系统开启「减弱动态效果」时全部关闭 */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-delay: 0ms !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* 锁屏内容隔离（修复未登录滚动穿透 / 业务内容泄露）
   ────────────────────────────────────────────────
   body 进入 .store-locked（showLock 添加）时，把业务导航与主内容整层
   visibility:hidden，只留锁屏卡片可见。此前锁屏仅靠 fixed 遮罩盖住视口，
   页面仍可向下滚动、露出锁屏下方的数据看板标题与空面板，观感很差。
   用 visibility（保留布局、不触发重排）而非 display:none，也不动
   body overflow —— 避免影响订货/盘点等处依赖的 position:sticky 表头。
   hideLock 会移除本类，业务区随即恢复。 */
body.store-locked .topbar,
body.store-locked .sidebar,
body.store-locked .bottom-nav,
body.store-locked .content {
    visibility: hidden;
}

/* （仪表盘卡片样式已统一在「瀑布流卡片网格」段定义，避免重复） */

/* ════════════════════════════════════════════════════════════
   月度经营日登记汇总表（营收页内嵌）
   结构严格对齐门店在用的 Excel「业绩登记汇总表」（A~AR 共 44 列）：
   三层表头 + 按周分块 + 周结 + 月合计 + 汇总呈现。
   表格天生很宽，所以：横向滚动 + 左侧「日期/星期/天气」三列吸附 +
   表头吸附，滚到哪一列都知道自己在填什么。
   ════════════════════════════════════════════════════════════ */
.bizlog-panel { overflow: visible; }

.bizlog-head {
    display: flex; align-items: center; justify-content: space-between;
    cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
    padding: 2px 0;
}
.bizlog-head .panel-title { margin: 0; }
.bizlog-fold {
    font-size: 12px; color: var(--text-light); padding: 4px 10px;
    border-radius: 999px; background: rgba(34,197,94,.10);
    transition: background var(--dur-fast) var(--ios-ease);
    white-space: nowrap;
}
.bizlog-head:active .bizlog-fold { background: rgba(34,197,94,.22); }
.bizlog-panel.folded .bizlog-body { display: none; }

.bizlog-bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px; flex-wrap: nowrap; margin: 10px 0 6px;
}
/* 门店/城市文字：占用剩余空间，但可被压缩到省略号 ——
   它是这行里唯一「短了也看得懂」的元素，空间不够时优先牺牲它，
   而不是让右边的按钮换行。min-width:0 是 flex 子项能被压到 0 的前提
   （默认 min-width:auto 会让它顶住不缩，按钮就还是被挤下去）。 */
.bizlog-city {
    font-size: 12.5px; color: var(--text-light);
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 功能按钮强制一行（2026-09-28 店主明确要求）。
   之前是 flex-wrap: wrap，窄屏/手机上会折成两行，把下面的 45 列大表
   又往下顶一截。现在改为 nowrap + 按钮可整体收缩：
     ① 整条工具栏永远只占一行，高度恒定，表格位置不跳；
     ② 城市文字让位（见上），按钮组 flex-shrink:0 保证不被压扁变形；
     ③ 真的窄到放不下时，由 .bizlog-wrap 横向滚动兜底，不靠换行。
   注意：不能用 overflow 隐藏 —— 按钮被裁掉就点不到了。 */
.bizlog-actions {
    display: flex; gap: 5px; flex-wrap: nowrap;
    align-items: center; flex-shrink: 0;
}

/* ── 工具栏下拉菜单（2026-09-28）────────────────────────────────
   把「设置」「工具」两组低频按钮收进下拉，工具栏 10 个按钮 → 6 个。
   用绝对定位浮在内容之上，不占布局高度（否则展开时又会把表格往下顶，
   正是这次想解决的问题）。 */
.bizlog-menu { position: relative; display: inline-block; flex-shrink: 0; }
.bizlog-menu-pop {
    display: none;
    position: absolute; top: calc(100% + 4px); left: 0;
    z-index: 60;
    min-width: 132px; padding: 4px;
    background: var(--card); border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, .13);
    /* 菜单本身也禁止被压窄：里面的选项文案（「城市设置」「数据体检」）
       一律 nowrap，宽度由内容决定，靠 min-width 兜底。 */
    white-space: nowrap;
}
.bizlog-menu-pop.open { display: block; }
/* 靠屏幕右侧的菜单改为右对齐，否则 132px 的浮层会向右伸出屏幕外
   （「工具」按钮紧挨着右侧的导出/保存，它的菜单若左对齐必然溢出）。
   这类菜单在 HTML 上标了 .menu-right，语义清晰，也不用靠 :has/JS 判断。 */
.bizlog-menu.menu-right .bizlog-menu-pop { left: auto; right: 0; }.bizlog-menu-pop button {
    display: block; width: 100%;
    padding: 7px 10px; margin: 0;
    font: inherit; font-size: 12.5px; line-height: 1.5;
    text-align: left; white-space: nowrap;
    color: var(--text); background: none; border: 0;
    border-radius: 7px; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.bizlog-menu-pop button:hover { background: rgba(0, 0, 0, .05); }
.bizlog-menu-pop button:active { background: rgba(0, 0, 0, .09); }
/* 展开时按钮本身也给个反馈，让人知道菜单是它开的 */
.bizlog-menu.open > .btn {
    background: rgba(0, 0, 0, .06);
    border-color: #b9d8c6;
}

/* ── 经营页说明折叠（2026-09-28）──────────────────────────────
   说明有 4 条规则近 150 字，常驻在表格上方会把表头推出屏幕（手机端
   尤其明显）。用原生 <details>：无需 JS，点即开合，兼容性最好。 */
.hint-fold { margin: 6px 0 0; }
.hint-fold > summary {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 4px 9px;
    font-size: 11.5px; line-height: 1.5;
    color: var(--text-light);
    background: rgba(0, 0, 0, .03);
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer; list-style: none;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--dur-tap) var(--ios-ease),
                border-color var(--dur-tap) var(--ios-ease);
}
.hint-fold > summary::-webkit-details-marker { display: none; }
.hint-fold > summary::before {
    content: '💡'; font-size: 11px;
}
.hint-fold > summary::after {
    content: '▾'; font-size: 9px; line-height: 1;
    transition: transform var(--dur-fast) var(--ios-out);
}
.hint-fold[open] > summary::after { transform: rotate(180deg); }
.hint-fold > summary:hover {
    background: rgba(0, 0, 0, .06);
    border-color: #b9d8c6;
}
.hint-fold > summary:active { background: rgba(0, 0, 0, .08); }
.hint-fold .hint { margin-top: 6px; }

/* 向右恢复（重做清除）：用绿色和「撤销」区分，避免误点 */
.bizlog-actions #bizlogRedoClear {
    color: #16a34a; border: 1px solid #86efac; background: #f0fdf4;
}
.bizlog-actions #bizlogRedoClear:hover { background: #dcfce7; }
.bizlog-actions .btn {
    /* 27px 比 xs 档(24px)略高，是登记表工具栏的特例：这排按钮多、又必须
       和旁边的月历控件对齐，用 xs 会显得挤。高度保留写死；
       居中/nowrap 由 .btn 基础档提供。 */
    height: 27px; padding: 0 8px;
    font-size: 12px; line-height: 1.5;
    border-radius: 8px;
    /* 强制一行方案的一部分：按钮自身不换行、也不因空间不足被压扁。
       收缩交给 .bizlog-actions 里的 gap 与下面的窄屏断点处理。 */
    flex-shrink: 0;
    white-space: nowrap;
}

/* 横向滚动容器：既要横向滚，又要让表头和左侧三列吸住 */
.bizlog-wrap {
    overflow: auto; max-height: 68vh; -webkit-overflow-scrolling: touch;
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--card);
    /* 必须抵掉 .table-wrap 的 padding: 12px：sticky 的 top:0 是相对
       滚动容器的 padding box 算的，留着这 12px，表头就会吸在内容区顶部、
       离容器口差一条 12px 的缝，数据行正好从这条缝里穿过去 ——
       用户看到的「表头缝隙穿透」就是这个。 */
    padding: 0;
}
.bizlog-tbl { border-collapse: separate; border-spacing: 0; width: max-content;
    min-width: 100%; font-size: 12px;
    /* 左侧吸附列的宽度只在这里定义一次，下面的 left 偏移全部由它们算出来。
       之前宽度和 left 各写各的常数，窄屏砍掉星期列后忘了改 left，
       天气列就停在 88px 上、跟 42px 宽的日期列之间空一条 46px 的缝。 */
    --bl-w-date: 52px;
    --bl-w-wd: 36px;
    --bl-w-wx: 52px;
}

.bizlog-tbl th, .bizlog-tbl td {
    border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
    padding: 3px 4px; text-align: center; vertical-align: middle;
    white-space: nowrap; background: var(--card);
}
/* 三层表头整体吸顶。
   之前是给每个 th 单独 position:sticky + 硬编码 top(0/26/48)，实测完全没粘住：
   th 的 sticky 被限制在 thead（只有 64px 高）这个包含块里，thead 自己跟着内容
   滚走了，th 只能在 thead 内部挪，等于没吸。改成 thead 整体 sticky 才真正生效，
   顺带也不用再猜三层的行高（硬编码 26/48 一遇字号变化就错位或留缝）。 */
.bizlog-tbl thead {
    position: sticky; top: 0; z-index: 6;
}
.bizlog-tbl thead th {
    /* 纵向由 thead 统一吸，th 自己不再 sticky */
    position: static; background: #f2faf5;
    font-weight: 600; color: var(--text); font-size: 11.5px; line-height: 1.25;
}
.bizlog-hd-l1 { background: #e6f6ec !important; }
.bizlog-hd-l2 { background: #eefaf3 !important; }
.bizlog-hd-l3 { background: #f7fdf9 !important; font-weight: 500 !important; }

/* 左侧三列吸附：日期 / 星期 / 天气 */
.bizlog-tbl .c-date { left: 0; }
.bizlog-tbl .c-wd   { left: var(--bl-w-date); }
/* 窄屏星期列不输出，--bl-w-wd 归 0，天气列自然接到日期列右边 */
.bizlog-tbl .c-wx   { left: calc(var(--bl-w-date) + var(--bl-w-wd)); }
.bizlog-tbl .c-date, .bizlog-tbl .c-wd, .bizlog-tbl .c-wx {
    position: sticky; z-index: 2; background: var(--card);
}
/* 表头里的左侧吸附列：横向仍要粘住（跟着横滚走），纵向交给 thead。
   z-index 要给到 7，压过表头里其余普通 th，否则横滚时被右侧表头盖住。 */
.bizlog-tbl thead .c-date, .bizlog-tbl thead .c-wd, .bizlog-tbl thead .c-wx {
    position: sticky; z-index: 7; background: #e6f6ec;
}
.bizlog-tbl .c-date { width: var(--bl-w-date); min-width: var(--bl-w-date); }
.bizlog-tbl .c-wd   { width: var(--bl-w-wd); min-width: var(--bl-w-wd); }
/* 天气列按「精简词」定宽：只放晴/阴/多云/小雨/中雨/大雨/暴雨/雪/台风
   这类两字词（温度移到 title 悬停），所以 92px 是白白浪费 ——
   44 列的宽表每省 40px 就多露出半列数据。 */
.bizlog-tbl .c-wx   { width: var(--bl-w-wx); min-width: var(--bl-w-wx); }
.bizlog-tbl .c-num  { width: 64px; min-width: 64px; }
.bizlog-tbl .c-txt  { width: 150px; min-width: 150px; text-align: left; }

/* 日期可点 → 打开「当日登记」抽屉（手机上逐格横着填 44 列不现实） */
.bizlog-date-btn {
    background: none; border: none; color: var(--primary); font-size: 12px;
    font-weight: 600; cursor: pointer; padding: 2px 3px; font-family: inherit;
}
.bizlog-date-btn:active { opacity: .6; }

/* 输入格：默认看不出是输入框，点了才显形，视觉上才像一张表 */
.bizlog-in {
    width: 100%; border: 1px solid transparent; background: transparent;
    text-align: right; font-size: 12px; padding: 3px 4px; border-radius: 5px;
    color: var(--text); font-family: inherit; -webkit-appearance: none;
}
.bizlog-in:hover { border-color: var(--border); background: rgba(34,197,94,.05); }
.bizlog-in:focus {
    outline: none; border-color: var(--primary); background: #fff;
    box-shadow: 0 0 0 2px rgba(255,107,53,.15);
}
.bizlog-in.txt { text-align: left; }
/* 评分列的「延续值」：本月还没出分，由最近一次有值的一天带过来。
   虚线底 + 灰字，和手填的实心格区分开 —— 但仍可编辑（今天出分了直接改）。 */
.bizlog-in.bizlog-cont {
    color: var(--text-light);
    font-style: italic;
    background: rgba(0,0,0,.03);
    border-bottom: 1px dashed rgba(0,0,0,.22);
}
.bizlog-in.bizlog-cont:hover { background: rgba(34,197,94,.08); }
/* 派生列（达成率/单均/人均/时效）：算出来的，不给改 */
.bizlog-cal {
    display: block; padding: 3px 4px; text-align: right;
    color: var(--text-light); background: #f6faf7; font-size: 11.5px;
}
.bizlog-wx {
    display: block; padding: 2px 3px; font-size: 11px; color: var(--text-light);
    background: #f6faf7; overflow: hidden; text-overflow: ellipsis;
}

/* 周结 / 月合计 / 汇总呈现：三种底色区分，避免看串 */
.bizlog-tbl tr.bizlog-week td, .bizlog-tbl tr.bizlog-week .c-date,
.bizlog-tbl tr.bizlog-week .c-wd, .bizlog-tbl tr.bizlog-week .c-wx {
    background: #eef7f1; font-weight: 600; color: #1f6b45;
}

/* ── V2.0：周结行折叠整周（需求第十六条）────────────────────────
   点「周结」→ 本周数据行 display:none，只留汇总行。

   为什么用 display:none 而不是 max-height / grid 动画：
   这张表是 <table>，行高由内容撑，套不了 grid-template-rows 那套
   （上一轮在卡片上踩过的坑）。而且 30 行 × 逐行动画会同时触发
   30 个合成层，低端机上明显掉帧。行折叠本质是"要么看见要么看不见"，
   给一个 120ms 的淡出就够，不必当真做高度动画。 */
.bizlog-tbl tr.is-week-collapsed { display: none; }

.bizlog-week-btn {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    /* 这排按钮是 reset 成裸按钮的：它是表格里的一个"看起来像文字"的
       触发点，任何默认按钮样式（背景/边框/内边距）都会让这一格
       和其他汇总格长得不一样。 */
    appearance: none; -webkit-appearance: none;
    border: 0; background: none; padding: 0;
    font: inherit; color: inherit; font-weight: inherit;
    cursor: pointer;
    border-radius: 4px;
    transition: background var(--t-fast) var(--ease-soft);
}
.bizlog-week-btn:hover { background: rgba(31, 107, 69, .1); }
.bizlog-week-btn:focus-visible {
    outline: 2px solid var(--ds-primary);
    outline-offset: 2px;
}
.bizlog-week-caret {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1em; height: 1em; flex: none;
    transition: transform var(--t-base) var(--ease-out);
}
.bizlog-week-caret svg { display: block; width: 100%; height: 100%; }
/* 收起时箭头转 -90°（指向右侧 = "这里还有东西被折起来了"）。
   展开态回到 0°。用 90deg 而不是 180deg：chevron 本身是"向下"的，
   收起后应该指向右，这样和「点击展开」的方向隐喻是一致的。 */
.bizlog-tbl tr.bizlog-week.is-collapsed .bizlog-week-caret { transform: rotate(-90deg); }
/* 收起时给周结行加一句提示，否则用户会以为"这周没数据" */
.bizlog-tbl tr.bizlog-week.is-collapsed .c-wx::after {
    content: ' （已收起）';
    font-size: 10px; font-weight: 400; opacity: .7;
}
.bizlog-tbl tr.bizlog-month td, .bizlog-tbl tr.bizlog-month .c-date,
.bizlog-tbl tr.bizlog-month .c-wd, .bizlog-tbl tr.bizlog-month .c-wx {
    background: #e2f2e9; font-weight: 700; color: #14532d;
}
.bizlog-tbl tr.bizlog-sum td, .bizlog-tbl tr.bizlog-sum .c-date,
.bizlog-tbl tr.bizlog-sum .c-wd, .bizlog-tbl tr.bizlog-sum .c-wx {
    background: #fff8ec; font-weight: 600; color: #7c4a03;}
.bizlog-tbl tr.bizlog-sum .c-date { text-align: left; }

/* 周末整行淡底，一眼分出工作日/周末 */
.bizlog-tbl tr.bizlog-wkend td:not(.c-date):not(.c-wd):not(.c-wx) {
    background: #fcfdfd;
}

/* ── 「当日登记」抽屉：手机上填 44 列的唯一现实方式 ──
   层级必须压过底部停靠坞（含备案条，z-index 10000）：否则手机上抽屉底部的
   「取消 / 填入表格」正好落在备案条底下，点不到（实测点击被 beian 链接拦截）。
   沿用站内既有约定：坞 10000 < 本抽屉 10050/10060 < 弹窗 10100。 */
.bizlog-day-mask {
    position: fixed; inset: 0; background: rgba(0,0,0,.42); z-index: 10050;
    opacity: 0; transition: opacity var(--dur-base) var(--ios-ease);
}
.bizlog-day-mask.show { opacity: 1; }
.bizlog-day-sheet {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 10060;
    background: var(--card); border-radius: 20px 20px 0 0;
    max-height: 88vh; display: flex; flex-direction: column;
    transform: translateY(100%);
    transition: transform var(--dur-slow) var(--ios-sheet);
    box-shadow: 0 -12px 40px rgba(0,0,0,.18);
}
.bizlog-day-sheet.show { transform: translateY(0); }
.bizlog-day-hd {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 16px 10px; border-bottom: 1px solid var(--border); flex: none;
}
.bizlog-day-hd h4 { font-size: 16px; color: var(--text); }
.bizlog-day-body { overflow-y: auto; padding: 12px 16px; -webkit-overflow-scrolling: touch; }
.bizlog-day-ft {
    padding: 10px 16px calc(12px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border); display: flex; gap: 8px; flex: none;
}
.bizlog-day-ft .btn { flex: 1; }
.bizlog-grp { margin-bottom: 14px; }
.bizlog-grp-t {
    font-size: 12.5px; font-weight: 600; color: var(--primary);
    margin-bottom: 6px; padding-left: 2px;
}
.bizlog-fld {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; padding: 7px 2px; border-bottom: 1px dashed var(--border);
}
.bizlog-fld:last-child { border-bottom: none; }
.bizlog-fld label { font-size: 13px; color: var(--text); flex: 1; }
.bizlog-fld input {
    width: 120px; text-align: right; font-size: 13px; padding: 6px 8px;
    border: 1px solid var(--border); border-radius: 8px; background: #fff;
    color: var(--text); font-family: inherit;
}
.bizlog-fld input:focus { outline: none; border-color: var(--primary); }
.bizlog-fld textarea {
    width: 100%; font-size: 13px; padding: 7px 8px; border: 1px solid var(--border);
    border-radius: 8px; background: #fff; color: var(--text);
    font-family: inherit; resize: vertical; min-height: 52px; margin-top: 5px;
}
.bizlog-fld.col { flex-direction: column; align-items: stretch; }
.bizlog-fld .calc {
    font-size: 13px; color: var(--text-light); background: #f6faf7;
    padding: 5px 8px; border-radius: 8px; min-width: 90px; text-align: right;
}

@media (max-width: 640px) {
    .bizlog-wrap { max-height: 62vh; }
    .bizlog-tbl { font-size: 11.5px; }
}

@media (prefers-reduced-motion: reduce) {
    .bizlog-day-mask, .bizlog-day-sheet, .bizlog-fold { transition: none; }
}

/* ════════════════════════════════════════════════════════════
   经营日登记表 · 截图识别预览弹窗
   ────────────────────────────────────────────────────────────
   识别结果一律先在这里过一遍再入表：机器认错一个数字就把当天真实数据
   盖掉，代价太大。层级跟当日登记抽屉一致（10050/10060）。
   ════════════════════════════════════════════════════════════ */
.bizlog-shot-mask {
    position: fixed; inset: 0; background: rgba(0, 0, 0, .42); z-index: 10050;
}
.bizlog-shot {
    position: fixed; z-index: 10060; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: min(520px, calc(100vw - 24px)); max-height: 84vh;
    background: var(--card); border-radius: 16px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, .22);
    display: flex; flex-direction: column; overflow: hidden;
}
.bizlog-shot-hd {
    display: flex; align-items: center; justify-content: space-between;
    padding: 13px 16px; border-bottom: 1px solid var(--border);
    font-size: 15px;
}
.bizlog-shot-x {
    background: none; border: none; font-size: 22px; line-height: 1;
    color: var(--text-light); cursor: pointer; padding: 0 4px;
}
.bizlog-shot-bd { padding: 12px 16px; overflow-y: auto; flex: 1 1 auto; }
.bizlog-shot-engine { font-size: 12px; color: var(--text-light); margin-bottom: 10px; }
.bizlog-shot-engine .warn, .bizlog-shot .warn { color: #c2410c; font-weight: 600; }
.bizlog-shot-empty {
    font-size: 13px; color: var(--text-light); line-height: 1.7;
    padding: 14px 4px; text-align: center;
}
.bizlog-shot-row {
    display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
    font-size: 13px; flex-wrap: wrap;
}
.bizlog-shot-row select, .bizlog-shot-in {
    border: 1px solid var(--border); border-radius: 8px; padding: 5px 8px;
    font-size: 13px; font-family: inherit; background: #fff; color: var(--text);
}
.bizlog-shot-in { width: 110px; text-align: right; }
/* 平台下拉：认错平台时改这里，下面的字段跟着重算 */
.bizlog-shot-plats {
    display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px;
}
.bizlog-shot-plat {
    display: flex; align-items: center; gap: 8px;
    background: #f6faf7; border: 1px solid var(--border);
    border-radius: 8px; padding: 6px 8px;
}
.bizlog-shot-plat .fn {
    flex: 1; min-width: 0; font-size: 12px; color: var(--text-light);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bizlog-shot-plat select {
    flex: none; max-width: 46%; border: 1px solid var(--border);
    border-radius: 6px; padding: 4px 6px; font-size: 13px;
    font-family: inherit; background: #fff; color: var(--text);
}
.bizlog-shot-tip { margin: 2px 0 10px; font-size: 12px; color: var(--text-light); }
/* 识别告警：可疑项红字顶在预览最上方，不让人稀里糊涂就确认 */
.bizlog-shot-warn {
    background: #fff5f5; border: 1px solid #fca5a5; border-radius: 8px;
    padding: 8px 10px; margin-bottom: 10px; font-size: 12px; color: #b91c1c;
    line-height: 1.6;
}
.bizlog-shot-warn b { display: block; margin-bottom: 4px; font-size: 13px; }
.bizlog-shot-warn ul { margin: 0; padding-left: 18px; }
/* 识别记录（留档）列表 */
.bizlog-hist-row {
    border: 1px solid var(--border); border-radius: 8px;
    padding: 8px 10px; margin-bottom: 8px; background: #fff;
}
.bizlog-hist-row.bad { border-color: #f0b37e; background: #fffaf3; }
.bizlog-hist-hd {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 12px; color: var(--text-light); margin-bottom: 4px;
}
.bizlog-hist-hd b { font-size: 13px; color: var(--text); }
.bizlog-hist-hd .fn {
    flex: 1; min-width: 0; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap;
}
.bizlog-hist-v { font-size: 13px; color: var(--text); word-break: break-all; }
.bizlog-hist-act { display: flex; gap: 8px; align-items: center; margin-top: 7px; }
.bizlog-hist-act .dim { font-size: 12px; color: var(--text-light); }
.bizlog-hist-v .warn { color: #d97706; }
.bizlog-hist-note {
    font-size: 12px; color: var(--text-light); margin-top: 4px;
    word-break: break-all; line-height: 1.5;
}
.bizlog-shot-tbl { overflow-x: auto; }
.bizlog-shot-tbl table { width: 100%; border-collapse: collapse; font-size: 13px; }
.bizlog-shot-tbl th, .bizlog-shot-tbl td {
    padding: 7px 8px; border-bottom: 1px solid var(--border); text-align: left;
}
.bizlog-shot-tbl th { color: var(--text-light); font-weight: 600; font-size: 12px; }
.bizlog-shot-tbl td.src { color: var(--text-light); font-size: 12px; }
/* 预览里的三平台小计行：只读，区别于上面可改的分项 */
.bizlog-shot-tbl tr.sum td {
    background: #f6faf7; font-weight: 600; color: var(--primary, #2f7a4d);
}
.bizlog-shot-row .fn { color: var(--text-light); font-size: 12px; margin-left: 6px; }
.bizlog-shot-row input[type="date"] {
    padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px;
    font-size: 13px; background: #fff;
}
.bizlog-shot-raw { margin-top: 12px; font-size: 12px; color: var(--text-light); }
.bizlog-shot-raw summary { cursor: pointer; padding: 4px 0; }
.bizlog-shot-raw p {
    margin: 4px 0; line-height: 1.6; word-break: break-all;
    background: #f6faf7; padding: 6px 8px; border-radius: 6px;
}
.bizlog-shot-ft {
    display: flex; justify-content: flex-end; gap: 8px;
    padding: 12px 16px; border-top: 1px solid var(--border);
}

/* ── 小票对账（2026-09-21）─────────────────────────────────────
   店主拿「实收S 是否等于小票合计」当对账锚点。两套取数口径不同源，
   差额天生存在，这里把它并排摆出来 —— 对上（绿）与没对上（黄）
   用左侧色条区分，不合上就能立刻看见。 */
.bizlog-shot-recon {
    margin: 12px 0 0; border: 1px solid var(--border); border-radius: 8px;
    border-left: 3px solid var(--primary, #2f7a4d); overflow: hidden;
}
.bizlog-shot-recon.has-gap { border-left-color: #d9822b; }
.bizlog-shot-recon .rc-hd {
    padding: 8px 12px; font-size: 13px; font-weight: 600;
    background: #f6faf7; border-bottom: 1px solid var(--border);
}
.bizlog-shot-recon.has-gap .rc-hd { background: #fdf6ec; }
.rc-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.rc-tbl td { padding: 7px 12px; border-bottom: 1px solid var(--border); }
.rc-tbl tr:last-child td { border-bottom: none; }
.rc-tbl td.n {
    text-align: right; font-variant-numeric: tabular-nums; font-weight: 600;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap;
}
.rc-tbl td.rc-note {
    color: var(--text-light); font-size: 12px; width: 42%;
    padding-left: 4px;
}
.rc-tbl tr.rc-gap td { border-top: 1px solid var(--border); }
.bizlog-shot-recon.has-gap .rc-tbl tr.rc-gap td { color: #d9822b; font-weight: 700; }

/* ── 2026-10-01：识别提示汇总（后端 _warn 首次展示到界面）──────
   以前 _warn 只进识别留档，前端从不读，等于白算。这些是"哪一行
   可能没抄对"的直接线索，用琥珀色区别于红色阻断 —— 它是**提示**，
   不是**错误**（多数时候对账仍能对上，只是提醒留个心眼）。 */
.bizlog-shot-warns {
    margin: 12px 0 0; border: 1px solid #f2d08a; border-radius: 8px;
    background: #fffdf5; overflow: hidden;
}
.bizlog-shot-warns .bw-hd {
    padding: 7px 12px; font-size: 12.5px; font-weight: 600;
    color: #9a6700; background: #fff8e5; border-bottom: 1px solid #f2d08a;
}
.bizlog-shot-warns ul { margin: 0; padding: 8px 12px 9px 28px; }
.bizlog-shot-warns li {
    font-size: 12.5px; line-height: 1.7; color: #7a5a12;
}
.bizlog-shot-warns li + li { margin-top: 3px; }

/* ── 2026-10-01：合计核对（实收S vs 票面合计 / 各行加总）───────
   店主口径「要能看出最终金额对不对得上」。rc-sum 是这条等式的结果行，
   用上边框跟上面「票面合计−外卖实收=堂食实收」的计算式分开，
   对上是绿、对不上是红 —— 红绿只用颜色区分不够，文案里同时带 ✓/✗。 */
.rc-tbl tr.rc-sum td { border-top: 1px solid var(--border); background: #fbfdfc; }
.rc-tbl tr.rc-sum td:first-child { font-weight: 600; }
.bizlog-shot-recon.has-gap .rc-tbl tr.rc-sum td { background: #fffaf3; }
.rc-diff { color: #d1242f; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.rc-wo { color: #d9822b; }
.rc-badge-wo {
    display: inline-block; margin-left: 4px; padding: 1px 7px; border-radius: 10px;
    font-size: 11.5px; font-weight: 600; color: #9a6700; background: #fff8e5;
    border: 1px solid #f2d08a; vertical-align: 1px;
}
/* 「已知悉差异，仍要填入」勾选框：差额阻断时的显式确认 */
.bizlog-shot-ack {
    display: inline-flex; align-items: center; gap: 6px; margin-right: auto;
    font-size: 12.5px; color: #d9822b; cursor: pointer; line-height: 1.4;
}
.bizlog-shot-ack input { cursor: pointer; flex: none; }
.bizlog-shot-ft { flex-wrap: wrap; row-gap: 8px; }
/* 阻断时确认按钮必须一眼看出点不动（不只是变灰） */
#blShotApplyBtn[disabled] { opacity: .45; cursor: not-allowed; }
.rc-detail { border-top: 1px solid var(--border); }
.rc-detail summary {
    padding: 8px 12px; cursor: pointer; font-size: 12.5px;
    color: var(--text-light); background: #fdf6ec;
}
.rc-detail .rc-tip {
    margin: 0; padding: 8px 12px; font-size: 12px; line-height: 1.65;
    color: var(--text-light); background: #fffaf3;
}
.rc-detail .rc-tbl td { padding: 6px 12px; font-size: 12.5px; }
@media (max-width: 640px) {
    .rc-tbl td.rc-note { width: auto; font-size: 11.5px; }
}

/* ════════════════════════════════════════════════════════════
   经营日登记表 · 手机端无边框模式
   ────────────────────────────────────────────────────────────
   手机上原本是「玻璃卡片框 + 表格框」两层框套在一起，44 列的宽表被
   这两层框吃掉 30 多像素横向空间，看着还厚重。这里把两层框都拆掉、
   让表格左右直接顶到屏幕边缘 —— 横向能多露出半列，视觉上也更像
   一张直接铺在屏幕上的表格，而不是"嵌在卡片里的小表格"。
   桌面端保持原样（屏幕宽，卡片感更有层次）。
   单元格之间的网格线保留：44 列没线会串行。
   ════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
    .bizlog-panel {
        /* 标题/工具栏缩进量：跟 .content 这档的左右留白保持一致
           （≤640px 是 12px、≤430px 是 10px），表格贴边、文字不贴边 */
        --bl-pad: 12px;
        background: transparent;
        border: none;
        border-radius: 0;
        box-shadow: none;
        padding: 0;
        /* 抵掉 .content 的左右内边距，真正顶到屏幕边。
           不能写死 -16px：手机端 .content 的左右留白是分档的
           （≤640px 用 12px、≤430px 用 10px），写死就会冲出屏幕
           —— 实测 430 宽的屏上左右各溢出 6px。
           calc(50% - 50vw) 与父级内边距无关，几档都自动对齐。 */
        margin-left: calc(50% - 50vw);
        margin-right: calc(50% - 50vw);
    }
    /* 框没了，标题和工具栏要自己缩进，否则贴着屏幕边。
       不能用 calc(50vw - 50%)：面板已经撑满视口宽，那个式子恒等于 0。 */
    .bizlog-head { padding: 0 var(--bl-pad) 8px; }
    .bizlog-bar { padding: 0 var(--bl-pad); margin: 4px 0 6px; }
    #bizlogHint { padding: 0 var(--bl-pad) 8px; }
    /* 手机端也要保住「一行」：14 寸屏能放下的间距，手机放不下。
       三项一起收：gap、按钮左右内边距、城市文字的可用空间。
       合计约省 15px/按钮 × 5 个 + 4×2px 间距 ≈ 83px ——
       这正好是 iPhone(390px) 上原来折行的差额。 */
    .bizlog-actions { gap: 3px; }
    .bizlog-actions .btn { padding: 0 6px; font-size: 11.5px; }
    .bizlog-city { font-size: 11.5px; }

    .bizlog-wrap {
        border: none;
        border-radius: 0;
        /* 无边框后不留纵向夹缝，让表格把整块高度占满 */
        max-height: 70vh;
    }
    /* 左侧吸附列与右侧内容要有分界，否则横向滚动时两片内容糊在一起 */
    .bizlog-tbl .c-wx {
        box-shadow: 6px 0 8px -6px rgba(31, 120, 72, .18);
    }
    /* 手机上字号更小，两字天气词 42px 就够；星期已并入日期格，
       整个星期列在窄屏不输出（--bl-w-wd 归 0），吸附区从 134px 压到 84px。
       列宽与吸附偏移都靠这三个变量联动，别再写死常数。 */
    .bizlog-tbl { --bl-w-date: 42px; --bl-w-wd: 0px; --bl-w-wx: 42px; }
    .bizlog-tbl .c-wd, .bizlog-tbl .c-wx { padding-left: 2px; padding-right: 2px; }
    .bizlog-tbl .c-date { padding-left: 4px; padding-right: 2px; }
    .bizlog-tbl th.c-date { padding-left: 4px; padding-right: 2px; }
    /* 日期格里的星期小字（"12 六"），跟日号并排不换行 ——
       换行会让 30 行每行都变高，纵向滚动反而更累。 */
    .bizlog-date-btn { display: inline-flex; align-items: baseline; gap: 2px; padding: 2px 1px; }
    .bizlog-dw {
        font-style: normal; font-size: 9.5px; font-weight: 400;
        color: var(--text-light); opacity: .85;
    }
    /* 贴边后日期列离屏幕左边太近，给单元格内容留点呼吸 */
    .bizlog-tbl .c-date { padding-left: 8px; }
    .bizlog-tbl th.c-date { padding-left: 8px; }
}

/* iPhone 窄屏（≤430px）：.content 留白收到 10px，缩进跟着收 */
@media (max-width: 430px) {
    .bizlog-panel { --bl-pad: 10px; }
    /* 超窄屏（iPhone SE / 小屏安卓）：按钮再收一档，并允许城市文字
       被压到 0 宽 —— 它是这行里唯一的非功能信息，宁可它消失，
       也不能让「保存」按钮换行或滑出屏幕。 */
    .bizlog-actions { gap: 2px; }
    .bizlog-actions .btn { padding: 0 5px; font-size: 11px; }
    .bizlog-city { flex: 0 1 auto; min-width: 0; }
}

/* ══════════════════════════════════════════════════════════════
   响应式集中区（v12）
   ══════════════════════════════════════════════════════════════

   为什么单开一节放文件末尾：CSS 里同权重选择器靠「谁写在后面谁赢」，
   而基础规则散布在全文各处。以前为了压住一条基础规则，只能在媒体查询里
   把权重往上堆（比如 .dp-detail-bar .dp-detail-acts .btn 特意写到
   (0,3,0)），既难懂又难维护 —— 下次再加一层类名就又压不住了。

   这一节里的规则一律写在最后，天然晚于所有基础规则，
   同权重即可生效，不需要靠堆权重硬压。

   断点约定（全站只有这几个）：
     ≤ 768px  手机
     769–1023px 平板   ← 本次新增
     ≥ 1024px 桌面（内容区让出 220px 给固定侧栏）
   ══════════════════════════════════════════════════════════════ */

/* ── 平板（769–1023px）──────────────────────────────────────────
   这一档原来是不存在的：768 以下全按手机处理，1024 以上按桌面处理，
   平板（iPad 竖屏 820px、横屏 1024px 刚过线）掉进了手机那套里 ——
   于是底部导航占着、内容区却是单列，宽屏空间全浪费了。

   取舍：平板上「隐藏侧栏、沿用底部导航」。理由是这个宽度下
   内容区去掉 220px 侧栏后只剩约 780px，花名册这类宽表已经吃紧，
   再让侧栏常驻会挤到没法看。底部导航用户手机上已经用惯了，
   平板上换个位置反而要重新学。 */
@media (min-width: 769px) and (max-width: 1023px) {
    /* 侧栏：这一档不显示。基础规则里 .sidebar 本来就是 display:none，
       1024px 才打开；这里显式写一遍是为了让「平板不显示侧栏」这件事
       在读代码时一眼可见，而不是靠"继承到某条基础规则"去推。 */
    .sidebar { display: none; }

    /* 内容区：没有侧栏，就不该有 margin-left；但底部导航还在，
       所以下边距照留。 */
    .content { padding: 16px 20px 24px; padding-top: 76px; }

    /* 两列。手机上这些是单列，平板上横着排能少滚一半。 */
    .grid-2, .stat-cards { grid-template-columns: repeat(2, 1fr); }
    .cost-form { grid-template-columns: repeat(2, 1fr); }

    /* 图表：手机 220px 太扁、桌面 360px 在这个宽下偏高，
       取中间值，一张图正好占半屏多一点。 */
    .chart, .chart-lg { height: 300px; }

    /* 底部导航：平板宽度富余，不必像手机那样把按钮压到极限。
       38px 是「不小于 iOS 建议的一半触控高度」又能塞下 8 个入口的值。 */
    .bottom-nav .nav-btn { min-width: 44px; padding: 4px 2px 3px; gap: 1px; }

    /* 花名册（14 列宽表）：1280px 在平板上必然溢出。
       不删列 —— 员工资料删哪列都是丢信息；改为收窄到 1100px
       再让序号/姓名首列吸附在左边，横向滚动时始终知道在看谁。

       选择器必须写成 .table-wrap .roster-table 而不是 .roster-table：
       基础规则 .table-wrap table（特异度 0,1,1）比 .roster-table（0,1,0）高，
       只写类名的话这里的 1100px 压不住那条 400px —— 而且因为它写在文件更后面，
       很容易误以为"已经生效了"（实测确实被压住，读出来还是 400px）。
       全站已有的同类坑见 2588 行 .table-wrap .entry-table 那条注释。 */
    .table-wrap .roster-table { min-width: 1100px; }
    .table-wrap .roster-table th:first-child,
    .table-wrap .roster-table td:first-child {
        position: sticky;
        left: 0;
        z-index: 2;
        background: #fff;
    }
    /* 表头行整行也是 sticky 的，首列表头要压在普通首列之上，
       否则横向滚动时表头格会被下面的单元格盖住。 */
    .table-wrap .roster-table th:first-child { z-index: 3; }

    /* 预览表格：2230 行那条随断点值同步过来的 420px 是给手机的，
       平板上有余量，放开到 500px。 */
    .preview-table-wrap table { min-width: 500px; }
}

/* ── 触屏横屏/小平板过渡（769–900px）───────────────────────────
   这一段是给「平板但很窄」的情况收口：820px 竖屏的 iPad 内容宽约 780px，
   放两列卡片还行；但像成本测算表单这种字段长、标签也长的，
   两列会把输入框挤到没法用。900px 以下退回单列。 */
@media (min-width: 769px) and (max-width: 900px) {
    .cost-form { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════
   识别进度组件（v12）
   ══════════════════════════════════════════════════════════════

   三条识别链路（批量上传 / 盘点截图 / 登记表截图）共用同一套结构，
   由 app.js 的 renderRecognizeProgress() 生成。

   此前三处的等待反馈各不相同：批量上传只有一行静态文字、
   盘点有转圈、登记表只把按钮文字改成「识别中…约N秒」。
   同一个动作在三处长得不一样，用户得学三遍；这里统一成一套。

   信息层次（自上而下）：
     阶段文案 + 第几张/共几张   ← 一句话说清"在干什么、还剩多少"
     进度条                      ← 一眼看出的完成度
     百分比 + 已用时间            ← 精确值与"还要多久"的参考
   ══════════════════════════════════════════════════════════════ */

.rt-prog {
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* 内联用（塞进已有的上传进度条里）时不抢宽度，撑满父容器即可 */
    width: 100%;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(255, 107, 53, 0.06);
    border: 1px solid rgba(255, 107, 53, 0.18);
}

/* 横向紧凑版：登记表那个按钮旁边空间小，一行放下 */
.rt-prog.rt-prog-inline {
    flex-direction: row;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
}
.rt-prog.rt-prog-inline .rt-prog-track { flex: 1 1 auto; min-width: 60px; }
.rt-prog.rt-prog-inline .rt-prog-foot { flex: none; }

.rt-prog-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    font-size: 12.5px;
    color: var(--text);
    min-width: 0;
}
.rt-prog-stage {
    font-weight: 600;
    /* 阶段文案可能较长（"排队中：前面还有 1 批"），
       让它占据剩余空间并在必要时省略，别把右侧的张数挤掉 */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.rt-prog-count {
    flex: none;
    font-variant-numeric: tabular-nums;
    color: var(--text-light);
    font-size: 12px;
}

.rt-prog-track {
    height: 6px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.07);
    overflow: hidden;
}
.rt-prog-fill {
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--primary), #ff8f5e);
    /* 宽度变化用 iOS 曲线，进度推进才显得顺而不是一跳一跳 */
    transition: width var(--dur-base) var(--ios-out);
}
/* 排队中还没动：用流动条纹表示"在等"而不是"卡住了" */
.rt-prog.rt-prog-queued .rt-prog-fill {
    width: 100% !important;
    background: repeating-linear-gradient(
        45deg,
        rgba(255, 107, 53, 0.35) 0 8px,
        rgba(255, 107, 53, 0.15) 8px 16px
    );
    animation: rtStripe 1.1s linear infinite;
}
@keyframes rtStripe {
    from { background-position: 0 0; }
    to   { background-position: 22px 0; }
}

.rt-prog-foot {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    font-size: 11.5px;
    color: var(--text-light);
    font-variant-numeric: tabular-nums;
}
.rt-prog-pct { font-weight: 700; color: var(--primary); }

/* 识别失败：整块转红，和 toast 的错误色一致 */
.rt-prog.rt-prog-error {
    background: rgba(239, 68, 68, 0.06);
    border-color: rgba(239, 68, 68, 0.2);
}
.rt-prog.rt-prog-error .rt-prog-fill { background: var(--red); }
.rt-prog.rt-prog-error .rt-prog-pct { color: var(--red); }

/* 尊重系统的"减少动态效果"：条纹不流动，进度条也不做过渡 */
@media (prefers-reduced-motion: reduce) {
    .rt-prog.rt-prog-queued .rt-prog-fill { animation: none; }
    .rt-prog-fill { transition: none; }
}

/* 手机端：字号收一档，避免和周围内容抢视觉 */
@media (max-width: 768px) {
    .rt-prog { padding: 8px 10px; gap: 5px; }
    .rt-prog-head { font-size: 12px; }
    .rt-prog-foot { font-size: 11px; }
}

/* ============================================================
   🎨 Design System v2（2026-09-30 追加）
   ------------------------------------------------------------
   为什么是「追加」而不是「重写」：
     全站 style.css 已有 4865 行、旧类名被 8 个页面和 11713 行 app.js
     引用。重写变量 = 全站回归，代价远大于收益。
     所以这里定义一套**新命名空间** --ds-* / .ds-*，
     与旧变量（--primary/--text/--radius…）并存，互不干扰：
       · 旧页面继续用旧类，一行不改，行为不变
       · 新页面（驾驶舱、新导航、AI 入口）用 .ds-*
     等新页面稳定后，旧类可以逐个迁移，是「渐进」不是「推倒」。

   设计基调（对应指令「高级、简洁、现代、专业」）：
     低饱和度、大留白、细阴影、统一圆角与间距阶梯。
     品牌色沿用原来的橙 #ff6b35（不换色 = 用户不觉得这是另一个系统），
     但把它收敛成只用于「主操作」和「强调数字」，
     其余全部走中性灰阶 —— 这是「专业感」的主要来源。
   ============================================================ */

:root {
    /* ── 语义色 ──
       每个语义色配三档：主色 / 浅底 / 深字。
       浅底用于背景块，深字保证在浅底上的对比度 ≥ 4.5:1（WCAG AA）。
       不许再在页面里写八进制色值，一律引用这里。

       V2.0 颜色纪律（指令第三十四条）：
         · 全部 -soft 浅底改为"绿调白"，与护眼底色同族 ——
           原来的 #fdf3f3（红白）、#eff9f7（蓝白）压在绿底上
           像三张贴纸，现在统一成低饱和的绿调底色。
         · info 从蓝 #2563eb 改为**青绿** #0d9488 ——
           需求明确"不要大量使用蓝紫"，且蓝+橙+绿三主色打架。
           青绿既区别于品牌橙，又属于绿族，落在护眼体系内。 */
    --ds-primary:        #ff6b35;   /* 品牌橙：主操作、强调数字 */
    --ds-primary-soft:   #fff4ee;
    --ds-primary-text:   #c2410c;

    --ds-success:        #16a34a;
    --ds-success-soft:   #f0f9f3;
    --ds-success-text:   #15803d;

    --ds-warning:        #d97706;
    --ds-warning-soft:   #fdf8ef;
    --ds-warning-text:   #b45309;

    --ds-danger:         #dc2626;
    --ds-danger-soft:    #fdf3f3;
    --ds-danger-text:    #b91c1c;

    --ds-info:           #0d9488;   /* V2.0：蓝 → 青绿 */
    --ds-info-soft:      #eff9f7;
    --ds-info-text:      #0f766e;

    /* ── 中性灰阶（专业感主力）──
       V2.0 调整：全部从"冷蓝灰"改为"暖绿灰"。
       原因：底色已换成护眼绿 #f2f7f4，如果中性灰仍带蓝调（原 #f6f7f9
       是偏蓝的），两者叠在一起会互相脏化 —— 绿底上的蓝灰卡片会显得
       发霉。把中性色的 hue 统一拉到 ~145（绿区间），饱和度压到 1~3%，
       做到"看不出颜色，但和底色是一家人"。
       另外把 --ds-text 从纯黑灰 #1f2a24 调为 #1f2a24（带去绿的中性深），
       长时间阅读比纯黑柔和，这也是"护眼"的一部分。 */
    --ds-bg:             #f2f7f4;
    --ds-surface:        #ffffff;
    --ds-surface-2:      #f7faf8;
    --ds-border:         #e2ebe6;
    --ds-border-strong:  #cfdcd4;
    --ds-text:           #1f2a24;
    --ds-text-2:         #55635b;
    --ds-text-3:         #8b968f;   /* 仅用于次要说明，不用于正文 */

    /* ── 圆角阶梯 ── */
    --ds-r-sm: 8px;
    --ds-r-md: 12px;
    --ds-r-lg: 16px;
    --ds-r-xl: 20px;

    /* ── 间距阶梯（8px 基准）──
       统一间距是"看起来专业"最容易被忽略、但收益最大的一条：
       间距乱 → 页面再好看也显脏。 */
    --ds-s1: 4px;  --ds-s2: 8px;  --ds-s3: 12px; --ds-s4: 16px;
    --ds-s5: 20px; --ds-s6: 24px; --ds-s8: 32px; --ds-s10: 40px;

    /* ── 阴影：克制的三层，不用大黑边 ── */
    --ds-shadow-xs: 0 1px 2px rgba(16,24,40,.05);
    --ds-shadow-sm: 0 1px 3px rgba(16,24,40,.08), 0 1px 2px rgba(16,24,40,.04);
    --ds-shadow-md: 0 4px 12px rgba(16,24,40,.07), 0 2px 4px rgba(16,24,40,.04);
    --ds-shadow-lg: 0 12px 28px rgba(16,24,40,.10), 0 4px 8px rgba(16,24,40,.04);

    /* ── 字号阶梯 ── */
    --ds-fs-xs: 12px; --ds-fs-sm: 13px; --ds-fs-base: 14px;
    --ds-fs-md: 15px; --ds-fs-lg: 17px; --ds-fs-xl: 20px;
    --ds-fs-2xl: 26px; --ds-fs-3xl: 32px;
}

/* ============================================================
   组件：按钮（四档视觉权重）
   ------------------------------------------------------------
   指令第三十一条「避免一个页面出现十几个视觉权重相同的按钮」，
   解法就是把按钮**限定为四档**，且一屏只允许一个 primary：
     .ds-btn-primary    主操作（新增、确认入库）—— 一屏最多一个
     .ds-btn-ai         AI 识别 —— 独一档，蓝色区分于主操作
     .ds-btn-secondary  次要（导入/导出/筛选）
     .ds-btn-danger     危险（删除）
   高度复用已有的 --btn-h-md，不新造尺寸。
   ============================================================ */
.ds-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: var(--btn-h-md, 38px);
    padding: 0 var(--ds-s4);
    border: 1px solid transparent;
    border-radius: var(--ds-r-md);
    font-size: var(--ds-fs-base);
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition: background .16s var(--ios-ease, ease),
                border-color .16s var(--ios-ease, ease),
                box-shadow .16s var(--ios-ease, ease),
                transform .1s var(--ios-ease, ease);
    -webkit-tap-highlight-color: transparent;
}
.ds-btn:active { transform: scale(.975); }
.ds-btn:focus-visible {
    outline: 2px solid var(--ds-primary);
    outline-offset: 2px;
}
.ds-btn[disabled], .ds-btn.is-disabled {
    opacity: .5; cursor: not-allowed; transform: none;
}

/* 主操作：品牌橙实心 */
.ds-btn-primary { background: var(--ds-primary); color: #fff; box-shadow: var(--ds-shadow-xs); }
.ds-btn-primary:hover { background: var(--primary-dark, #e55a2b); }

/* AI：蓝色实心，全站只此一档，让它一眼可辨 */
.ds-btn-ai { background: var(--ds-info); color: #fff; box-shadow: var(--ds-shadow-xs); }
.ds-btn-ai:hover { background: var(--ds-info-text); }

/* 次要：白底描边 */
.ds-btn-secondary { background: var(--ds-surface); color: var(--ds-text-2); border-color: var(--ds-border-strong); }
.ds-btn-secondary:hover { background: var(--ds-surface-2); color: var(--ds-text); border-color: var(--ds-text-3); }

/* 危险：红字描边，不用红底实心（避免误触视觉吸引） */
.ds-btn-danger { background: var(--ds-surface); color: var(--ds-danger); border-color: #fca5a5; }
.ds-btn-danger:hover { background: var(--ds-danger-soft); }

.ds-btn-sm { height: 30px; padding: 0 var(--ds-s3); font-size: var(--ds-fs-sm); border-radius: var(--ds-r-sm); }
.ds-btn-lg { height: 44px; padding: 0 var(--ds-s5); font-size: var(--ds-fs-md); }
.ds-btn-block { width: 100%; }

/* ============================================================
   组件：卡片
   ============================================================ */
.ds-card {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-lg);
    box-shadow: var(--ds-shadow-sm);
    padding: var(--ds-s5);
}
.ds-card-flush { padding: 0; overflow: hidden; }
.ds-card-hover { transition: box-shadow .18s var(--ios-ease, ease), transform .18s var(--ios-ease, ease); }
.ds-card-hover:hover { box-shadow: var(--ds-shadow-md); transform: translateY(-1px); }

.ds-card-title {
    font-size: var(--ds-fs-base);
    font-weight: 600;
    color: var(--ds-text);
    margin-bottom: var(--ds-s3);
    display: flex; align-items: center; justify-content: space-between; gap: var(--ds-s2);
}
.ds-card-title .ds-card-more {
    font-size: var(--ds-fs-xs); font-weight: 400;
    color: var(--ds-text-3); text-decoration: none; cursor: pointer;
}
.ds-card-title .ds-card-more:hover { color: var(--ds-primary); }

/* ============================================================
   组件：指标数字（驾驶舱主视觉）
   ------------------------------------------------------------
   指令要求「数字突出、标题小、单位清晰」，所以三者各有独立字号：
     标签 13px 灰 / 数字 32px 粗 / 单位 14px 灰
   ============================================================ */
.ds-metric { display: flex; flex-direction: column; gap: var(--ds-s2); }
.ds-metric-label {
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-3);
    font-weight: 400;
    display: flex; align-items: center; gap: 5px;
}
.ds-metric-value {
    font-size: var(--ds-fs-3xl);
    font-weight: 700;
    color: var(--ds-text);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;   /* 等宽数字，跳动时不晃 */
    letter-spacing: -.5px;
    word-break: break-all;
}
.ds-metric-value .ds-unit {
    font-size: var(--ds-fs-base);
    font-weight: 500;
    color: var(--ds-text-3);
    margin-left: 3px;
    letter-spacing: 0;
}
.ds-metric-delta { font-size: var(--ds-fs-xs); display: inline-flex; align-items: center; gap: 3px; }
.ds-metric-delta.up   { color: var(--ds-danger); }   /* 成本类涨=不好，交由具体页面决定语义 */
.ds-metric-delta.down { color: var(--ds-success); }
.ds-metric-delta.flat { color: var(--ds-text-3); }

/* 数字配色修饰，由页面按语义挑选 */
.ds-num-primary { color: var(--ds-primary); }
.ds-num-success { color: var(--ds-success); }
.ds-num-danger  { color: var(--ds-danger); }
.ds-num-info    { color: var(--ds-info); }
.ds-num-muted   { color: var(--ds-text-2); }

/* ============================================================
   组件：徽章 / 状态
   ============================================================ */
.ds-badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px var(--ds-s2);
    border-radius: 999px;
    font-size: var(--ds-fs-xs);
    font-weight: 500;
    line-height: 1.7;
    white-space: nowrap;
}
.ds-badge-success { background: var(--ds-success-soft); color: var(--ds-success-text); }
.ds-badge-warning { background: var(--ds-warning-soft); color: var(--ds-warning-text); }
.ds-badge-danger  { background: var(--ds-danger-soft);  color: var(--ds-danger-text); }
.ds-badge-info    { background: var(--ds-info-soft);    color: var(--ds-info-text); }
.ds-badge-neutral { background: var(--ds-surface-2); color: var(--ds-text-2); border: 1px solid var(--ds-border); }

/* ============================================================
   组件：空状态 / 骨架屏 / 错误态
   ------------------------------------------------------------
   指令第三十五、三十七条：禁止长时间空白，禁止只写「暂无数据」。
   空状态要给出「下一步能做什么」。
   ============================================================ */
.ds-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--ds-s3);
    padding: var(--ds-s10) var(--ds-s5);
    text-align: center;
    color: var(--ds-text-3);
}
/* V2.0：这个位置原来是 emoji（✅ 之类），靠 font-size:40px 撑大。
   换成 SVG 后 font-size 失效，必须显式给尺寸，否则 SVG 按规范
   默认 300×150 渲染，整个空态块会被撑成一大片空白。
   注意这里包了一层 .ttl-ico（V2i 的默认外壳），所以两条都要限住。 */
.ds-empty-icon { font-size: 40px; line-height: 1; opacity: .85;
    display: flex; align-items: center; justify-content: center; }
.ds-empty-icon svg { display: block; width: 40px; height: 40px; }
.ds-empty-icon .ttl-ico { width: 40px; height: 40px; margin-right: 0; }
.ds-empty-title { font-size: var(--ds-fs-md); font-weight: 600; color: var(--ds-text-2); }
.ds-empty-desc  { font-size: var(--ds-fs-sm); color: var(--ds-text-3); max-width: 340px; line-height: 1.7; }
.ds-empty-actions { display: flex; gap: var(--ds-s2); flex-wrap: wrap; justify-content: center; margin-top: var(--ds-s1); }

/* 骨架屏：加载中不给空白 */
.ds-skeleton {
    background: linear-gradient(90deg, #f0f2f5 25%, #e6e9ee 37%, #f0f2f5 63%);
    background-size: 400% 100%;
    animation: dsShimmer 1.4s ease-in-out infinite;
    border-radius: var(--ds-r-sm);
}
@keyframes dsShimmer {
    0%   { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}
.ds-skeleton-text { height: 13px; margin: 6px 0; }
.ds-skeleton-title { height: 20px; width: 40%; margin-bottom: var(--ds-s3); }
.ds-skeleton-metric { height: 32px; width: 60%; }
.ds-skeleton-card { height: 96px; border-radius: var(--ds-r-md); }
@media (prefers-reduced-motion: reduce) {
    .ds-skeleton { animation: none; background: #f0f2f5; }
}

/* 错误态：给「重新加载」，不给 Error 500 */
.ds-error {
    display: flex; flex-direction: column; align-items: center; gap: var(--ds-s3);
    padding: var(--ds-s8) var(--ds-s5); text-align: center;
    color: var(--ds-text-3);
}
.ds-error-icon { font-size: 34px; }
.ds-error-text { font-size: var(--ds-fs-base); color: var(--ds-text-2); }

/* ============================================================
   组件：分段控件（今日/昨日/本周/本月/今年）
   ============================================================ */
.ds-seg {
    display: inline-flex;
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-md);
    padding: 2px;
    gap: 2px;
    overflow-x: auto;
    max-width: 100%;
    scrollbar-width: none;
}
.ds-seg::-webkit-scrollbar { display: none; }
.ds-seg-item {
    border: 0; background: transparent; cursor: pointer;
    padding: 6px var(--ds-s3);
    border-radius: var(--ds-r-sm);
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-2);
    white-space: nowrap;
    transition: background .15s var(--ios-ease, ease), color .15s var(--ios-ease, ease);
}
.ds-seg-item:hover { color: var(--ds-text); }
.ds-seg-item.active {
    background: var(--ds-surface);
    color: var(--ds-primary);
    font-weight: 600;
    box-shadow: var(--ds-shadow-xs);
}

/* ============================================================
   组件：搜索框（统一，全站一个样子）
   ============================================================ */
.ds-search {
    display: flex; align-items: center; gap: var(--ds-s2);
    background: var(--ds-surface);
    border: 1px solid var(--ds-border-strong);
    border-radius: var(--ds-r-md);
    padding: 0 var(--ds-s3);
    height: var(--btn-h-md, 38px);
    transition: border-color .16s var(--ios-ease, ease), box-shadow .16s var(--ios-ease, ease);
}
.ds-search:focus-within {
    border-color: var(--ds-primary);
    box-shadow: 0 0 0 3px var(--ds-primary-soft);
}
.ds-search input {
    flex: 1; min-width: 0;
    border: 0; outline: 0; background: transparent;
    font-size: var(--ds-fs-base); color: var(--ds-text);
}
.ds-search .ds-search-icon { color: var(--ds-text-3); font-size: 15px; }

/* ============================================================
   组件：区块标题（页面内小节）
   ============================================================ */
.ds-section {
    font-size: var(--ds-fs-md);
    font-weight: 600;
    color: var(--ds-text);
    margin: var(--ds-s6) 0 var(--ds-s3);
    display: flex; align-items: center; gap: var(--ds-s2);
}
.ds-section-sub { font-size: var(--ds-fs-sm); font-weight: 400; color: var(--ds-text-3); }

/* ============================================================
   工具类：少量高频的排版/布局原子，避免到处写行内样式
   ============================================================ */
.ds-row    { display: flex; align-items: center; gap: var(--ds-s3); }
.ds-row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-s3); }
.ds-col    { display: flex; flex-direction: column; gap: var(--ds-s3); }
.ds-wrap   { flex-wrap: wrap; }
.ds-grow   { flex: 1; min-width: 0; }
.ds-muted  { color: var(--ds-text-3); font-size: var(--ds-fs-sm); }
.ds-mt-1   { margin-top: var(--ds-s1); }
.ds-mt-2   { margin-top: var(--ds-s2); }
.ds-mt-3   { margin-top: var(--ds-s3); }
.ds-mt-4   { margin-top: var(--ds-s4); }
.ds-mb-3   { margin-bottom: var(--ds-s3); }
.ds-mb-4   { margin-bottom: var(--ds-s4); }
.ds-grid   { display: grid; gap: var(--ds-s4); }
/* 自适应列：不写死断点，容器窄了自己折行 */
.ds-grid-cards { display: grid; gap: var(--ds-s4); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.ds-grid-2 { display: grid; gap: var(--ds-s4); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.ds-divider { height: 1px; background: var(--ds-border); margin: var(--ds-s4) 0; }
.ds-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-clickable { cursor: pointer; }
.ds-hidden { display: none !important; }

/* ============================================================
   🧭 经营驾驶舱（2026-09-30 新增）
   ------------------------------------------------------------
   与 Design System 同属新增层，只作用于 #cockpitRoot 内部，
   不影响任何既有页面元素。
   ============================================================ */

.cockpit { margin-bottom: var(--ds-s6); }

/* 顶部问候 */
.cockpit-hello {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--ds-s3);
    margin-bottom: var(--ds-s5);
}
.cockpit-greeting {
    font-size: var(--ds-fs-xl);
    font-weight: 700;
    color: var(--ds-text);
    letter-spacing: -.3px;
}
.cockpit-sub {
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-3);
    margin-top: 3px;
}
.cockpit-icon-btn {
    position: relative;
    width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--ds-border);
    background: var(--ds-surface);
    border-radius: var(--ds-r-md);
    font-size: 17px; cursor: pointer;
    transition: background .16s var(--ios-ease, ease), border-color .16s var(--ios-ease, ease);
}
.cockpit-icon-btn:hover { background: var(--ds-surface-2); border-color: var(--ds-border-strong); }
/* 红点：用 ::after 而不是真元素，避免影响按钮尺寸 */
.cockpit-dot {
    position: absolute; top: 7px; right: 7px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--ds-danger);
    border: 1.5px solid var(--ds-surface);
}

/* 今日待处理 */
.cockpit-todo { margin-bottom: var(--ds-s4); }
.cockpit-todo-list { display: flex; flex-direction: column; gap: var(--ds-s2); }
.ck-todo-item {
    display: flex; align-items: center; gap: var(--ds-s3);
    padding: var(--ds-s3);
    border-radius: var(--ds-r-md);
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
}
.ck-todo-item.is-danger { background: var(--ds-danger-soft); border-color: #fecaca; }
.ck-todo-item.is-warning { background: var(--ds-warning-soft); border-color: #fde68a; }
/* V2.0：原来是 font-size:16px 撑一个 🔴/🟡 字符。
   现在里面是个 CSS 画的圆点（.ds-lamp），需要 flex 居中才不会偏上。
   固定 16×16 的原因是它和右边两行文字的基线要对齐 ——
   不固定的话圆点会跟着父容器行高漂移。 */
.ck-todo-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px;
    flex-shrink: 0;
}.ck-todo-text { flex: 1; min-width: 0; }
.ck-todo-title { font-size: var(--ds-fs-base); color: var(--ds-text); font-weight: 500; }
.ck-todo-desc { font-size: var(--ds-fs-xs); color: var(--ds-text-3); margin-top: 1px; }
.ck-todo-btn {
    flex-shrink: 0;
    border: 1px solid var(--ds-border-strong);
    background: var(--ds-surface);
    color: var(--ds-text-2);
    border-radius: var(--ds-r-sm);
    padding: 5px 12px;
    font-size: var(--ds-fs-sm);
    cursor: pointer;
    transition: all .16s var(--ios-ease, ease);
}
.ck-todo-btn:hover { border-color: var(--ds-primary); color: var(--ds-primary); background: var(--ds-primary-soft); }

/* 完整度进度条 */
.ck-bar {
    height: 6px; border-radius: 99px;
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
    overflow: hidden;
    margin-top: 2px;
}
.ck-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, var(--ds-primary), #ff9a6b);
    border-radius: 99px;
    transition: width .6s var(--ios-out, ease);
}

/* ── AI 智能录入大入口 ──
   指令第十条要求「首页显著位置」。视觉上要一眼可辨但不喧宾夺主：
   蓝色调区别于品牌橙（橙=经营数据，蓝=AI），大圆角 + 细描边 + 轻投影。 */
.ck-ai {
    display: flex; align-items: center; gap: var(--ds-s4);
    margin: var(--ds-s5) 0;
    padding: var(--ds-s5);
    border-radius: var(--ds-r-lg);
    background: linear-gradient(135deg, #eff9f7 0%, #f5f9ff 55%, #ffffff 100%);
    border: 1.5px solid #bfdbfe;
    box-shadow: var(--ds-shadow-sm);
    cursor: pointer;
    transition: box-shadow .2s var(--ios-ease, ease), transform .2s var(--ios-ease, ease), border-color .2s var(--ios-ease, ease);
}
.ck-ai:hover {
    box-shadow: var(--ds-shadow-md);
    transform: translateY(-2px);
    border-color: #93c5fd;
}
.ck-ai:active { transform: translateY(0) scale(.995); }
.ck-ai:focus-visible { outline: 2px solid var(--ds-info); outline-offset: 3px; }
.ck-ai-icon {
    width: 52px; height: 52px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 25px;
    background: var(--ds-surface);
    border-radius: var(--ds-r-md);
    box-shadow: var(--ds-shadow-xs);
}
.ck-ai-body { flex: 1; min-width: 0; }
.ck-ai-title {
    font-size: var(--ds-fs-lg);
    font-weight: 700;
    color: var(--ds-text);
    letter-spacing: -.2px;
}
.ck-ai-desc { font-size: var(--ds-fs-sm); color: var(--ds-text-2); margin-top: 2px; }
.ck-ai-cta {
    flex-shrink: 0;
    font-size: var(--ds-fs-base);
    font-weight: 600;
    color: var(--ds-info);
    white-space: nowrap;
}

/* ── 数据健康 ── */
.ck-health-score {
    display: flex; align-items: baseline; gap: var(--ds-s2);
    margin-bottom: var(--ds-s3);
}
.ck-health-num {
    font-size: 40px; font-weight: 700; line-height: 1;
    font-variant-numeric: tabular-nums;
    letter-spacing: -1px;
}
.ck-health-num.good { color: var(--ds-success); }
.ck-health-num.warn { color: var(--ds-warning); }
.ck-health-num.bad  { color: var(--ds-danger); }
.ck-health-max { font-size: var(--ds-fs-base); color: var(--ds-text-3); }
.ck-health-badges { display: flex; gap: var(--ds-s2); flex-wrap: wrap; margin-bottom: var(--ds-s3); }

/* 健康检查条目 */
.ck-health-list { display: flex; flex-direction: column; gap: 1px; }
.ck-health-row {
    display: flex; align-items: center; gap: var(--ds-s2);
    padding: 7px 8px;
    border-radius: var(--ds-r-sm);
    font-size: var(--ds-fs-sm);
    cursor: pointer;
    transition: background .14s var(--ios-ease, ease);
}
.ck-health-row:hover { background: var(--ds-surface-2); }
.ck-health-row .ck-hr-label { flex: 1; min-width: 0; color: var(--ds-text-2); }
.ck-health-row .ck-hr-go { color: var(--ds-text-3); font-size: 12px; }

/* ── 通知条目 ── */
.ck-notify-item {
    display: flex; align-items: flex-start; gap: var(--ds-s2);
    padding: var(--ds-s2) 0;
    border-bottom: 1px solid var(--ds-border);
    cursor: pointer;
}
.ck-notify-item:last-child { border-bottom: 0; }
.ck-notify-item:hover .ck-nt-title { color: var(--ds-primary); }
.ck-nt-title { font-size: var(--ds-fs-sm); color: var(--ds-text); font-weight: 500; }
.ck-nt-desc { font-size: var(--ds-fs-xs); color: var(--ds-text-3); margin-top: 1px; }

/* ── 手机端：驾驶舱纵向压缩，数字缩小一档，避免"糊成一片"──
   375~430px 是主力机型区间，数字 32px 过大，降到 26px。 */
@media (max-width: 560px) {
    .cockpit-greeting { font-size: var(--ds-fs-lg); }
    .ds-metric-value { font-size: var(--ds-fs-2xl); }
    .ds-grid-cards { grid-template-columns: repeat(2, 1fr); gap: var(--ds-s3); }
    .ds-grid-2 { grid-template-columns: 1fr; }
    .ds-card { padding: var(--ds-s4); }
    .ck-ai { padding: var(--ds-s4); gap: var(--ds-s3); }
    .ck-ai-icon { width: 44px; height: 44px; font-size: 21px; }
    .ck-ai-title { font-size: var(--ds-fs-md); }
    .ck-ai-desc { font-size: var(--ds-fs-xs); }
    .ck-ai-cta { font-size: var(--ds-fs-sm); }
    .ck-health-num { font-size: 32px; }
    .ck-todo-item { padding: var(--ds-s2) var(--ds-s3); }
}
/* 超窄屏（iPhone SE 等 320~375）：CTA 收到第二行，别把标题挤断 */
@media (max-width: 380px) {
    .ck-ai { flex-wrap: wrap; }
    .ck-ai-cta { width: 100%; text-align: right; }
}

/* ============================================================
   🎨 全局底色切换（2026-09-30）
   ------------------------------------------------------------
   原来是绿渐变（#d8f3e3 → #ecfbf1），偏"清新"，与指令要求的
   「高级、简洁、专业、低饱和度」不符，且彩色背景会让白卡片
   显得发绿、层次不清。

   做法：不删原规则，只在末尾用同等特异性覆盖 —— 这样万一要回退，
   删掉这一段即可，原绿底立刻回来。
   （body 在 93 行已有一条 padding-bottom 覆盖，这里同样用
   后置覆盖的手法，是项目里既有的惯例。）
   ============================================================ */
body {
    background: var(--ds-bg);
    background-image: none;
}

/* ============================================================
   📱🖥 导航重构（2026-09-30 新增）
   ------------------------------------------------------------
   旧状：移动端 8 个 tab 挤一行横向滚动；PC 侧栏 8 项平铺无分组。
   新状：移动端 5 个固定 tab + 二级面板；PC 侧栏分 4 组。
   策略：全部用「后置覆盖」，不动上面既有规则 —— 便于回退。
   ============================================================ */

/* ── 移动端：5 等分固定，不再横向滚动 ──
   旧规则里有一条 `.bottom-nav{overflow-x:auto}`（第 2090 行），
   那是为 8 个 tab 准备的。现在只剩 5 个，必须关掉横向滚动，
   否则会出现「内容装得下但仍可横拖」的廉价感。 */
.bottom-nav {
    overflow-x: hidden;
    overflow-x: clip;
    justify-content: space-around;
}
.bottom-nav .nav-btn {
    flex: 1 1 0;
    min-width: 0;          /* 允许压缩，避免 5 个撑破容器 */
    padding: 7px 2px 5px;
    gap: 2px;
    transition: color .16s var(--ios-ease, ease);
}
.bottom-nav .nav-btn .nav-icon { font-size: 19px; }
.bottom-nav .nav-btn span:not(.nav-icon) {
    font-size: 10.5px;
    line-height: 1.2;
    white-space: nowrap;
}
/* AI tab：中间那颗，给一点色彩权重，让它一眼可辨
   （指令第十条要求 AI 入口显著，底部导航里也照应一下） */
.bottom-nav .nav-btn-ai { color: var(--ds-info); }
.bottom-nav .nav-btn-ai .nav-icon {
    filter: drop-shadow(0 1px 3px rgba(37, 99, 235, .35));
}
/* 激活态：AI tab 用蓝色，其余用品牌橙 */
.bottom-nav .nav-btn-ai.active { color: var(--ds-info); }
.bottom-nav .nav-btn.active .nav-icon { transform: translateY(-1px); }

/* ── PWA 独立模式下的空间回收（2026-10-01）────────────────────────
   背景：从浏览器"添加到桌面"打开时，顶部有浏览器地址栏、底部有自动
   填充提示条，两处白占屏幕。根治靠启用 SW 让 Chrome 走"安装应用"
   （见 index.html 顶部注释），独立模式下浏览器外壳消失。

   外壳消失后还有一笔可省的空间：**顶栏与系统状态栏之间的重复留白**。
   独立模式下页面从屏幕物理顶端开始（viewport-fit=cover），
   .topbar 的 padding-top 用 env(safe-area-inset-top) 给状态栏让位 ——
   这笔留白是必须的（否则内容被状态栏压住）。但 .topbar-inner 自己
   还有 12px 上下内边距，与状态栏留白叠加后顶部显得虚高，收紧到 8px。

   底部同理：独立模式下没有浏览器工具栏，坞可以更贴边。

   ⚠ 底线：按钮热区不得小于 44px（触摸可用性标准）。
     这里只压"容器留白"，不压按钮本身的高度。 */
html.standalone .topbar-inner { padding-top: 8px; padding-bottom: 8px; }
html.standalone .bottom-dock .bottom-nav .nav-btn { padding-top: 6px; padding-bottom: 4px; }

/* ── 独立模式：顶栏毛玻璃铺满到屏幕物理顶端（2026-10-01）─────────────
   用户诉求原话："下方浏览器导航栏占位太多，优化掉、展示更多内容"。
   装成 PWA 后 Safari 外壳没了，但顶部还剩两笔留白：
     ① .topbar 的 padding-top: env(safe-area-inset-top) —— 给状态栏让位，
        这笔**必须保留**，否则时间/电量会压住"门店工作台"标题；
     ② .topbar-inner 自己的 12px 内边距 —— 这笔是可以压的（上一轮已压到 8px）。
   目标状态是"顶栏像一块完整的玻璃，从屏幕物理顶端一直铺下来"：
   背景由 .topbar 提供（已 position:fixed; top:0，天然铺满），
   内容由 padding-top 让开状态栏。所以这里只需要保证
   **背景色不在状态栏区域变淡或断开**，观感就连续了。

   为什么不用纯色顶替：毛玻璃要透出下方内容才好看，纯色会显得生硬，
   且滚动时与内容区之间出现明显色块边界。这里用略强一档的 glass 变体，
   兼顾"看得清"与"还是玻璃"。 */
html.standalone .topbar {
    background: var(--glass-bg-strong);
}

/* ── 二级导航面板（「经营」「更多」用）── */
/* 遮罩只盖到坞的上沿：底部导航保持可见可点，
   用户能直接看到「当前在哪个 tab」并随时切走，不会以为自己被困住了 */
.nav-sheet-mask {
    position: fixed; left: 0; right: 0; top: 0;
    bottom: var(--dock-h, calc(41px + env(safe-area-inset-bottom, 0px)));
    background: rgba(17, 24, 39, .35);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    opacity: 0; pointer-events: none;
    transition: opacity .26s var(--ios-ease, ease);
    z-index: 200;
}
.nav-sheet-mask.show { opacity: 1; pointer-events: auto; }
/* 弹层从坞上方升起：bottom 直接踩在坞的上沿，
   这样展开后最后一行绝不会被底部导航盖住。
   坞高不写死 —— 用 JS 实测的 --dock-h（备案条「✎填写」显隐、
   字号缩放、Safari 工具栏收放都会改坞高），兜底值只服务坞未建立的瞬间。 */
.nav-sheet {
    position: fixed;
    left: 0; right: 0;
    bottom: var(--dock-h, calc(41px + env(safe-area-inset-bottom, 0px)));
    z-index: 201;
    background: var(--ds-surface);
    border-radius: var(--ds-r-xl) var(--ds-r-xl) 0 0;
    box-shadow: 0 -8px 32px rgba(16, 24, 40, .16);
    padding: var(--ds-s4) var(--ds-s4) var(--ds-s5);
    transform: translateY(calc(100% + var(--dock-h, 41px)));
    transition: transform .32s var(--ios-sheet, cubic-bezier(.32,.72,0,1));
    max-height: calc(72vh - var(--dock-h, 41px));
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}
.nav-sheet.show { transform: translateY(0); }
.nav-sheet-head {
    display: flex; align-items: center; justify-content: space-between;
    font-size: var(--ds-fs-md); font-weight: 600; color: var(--ds-text);
    padding: 0 var(--ds-s1) var(--ds-s3);
}
.nav-sheet-close {
    width: 30px; height: 30px;
    border: 0; background: var(--ds-surface-2);
    border-radius: var(--ds-r-sm);
    color: var(--ds-text-2); cursor: pointer; font-size: 14px;
}
.nav-sheet-close:hover { background: var(--ds-border); }
.nav-sheet-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--ds-s3);
    /* 条目数不是 3 的整数倍时（如 5 项），最后一行的孤儿项
       保持与上面同宽，不拉伸成整行 —— 视觉上整齐成列 */
    grid-auto-rows: 1fr;
}
.nav-sheet-item {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 6px;
    padding: var(--ds-s4) var(--ds-s2);
    min-height: 76px;          /* 固定最小高度，各行等高才整齐 */
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-md);
    background: var(--ds-surface-2);
    cursor: pointer;
    transition: background .16s var(--ios-ease, ease), border-color .16s var(--ios-ease, ease), transform .12s var(--ios-ease, ease);
}
.nav-sheet-item:active { transform: scale(.97); }
/* hover 只在真有指针的设备上生效：
   手机上点击后会残留 :hover，橙色背景一直挂着，像"选中了"一样误导 */
@media (hover: hover) and (pointer: fine) {
    .nav-sheet-item:hover { background: var(--ds-primary-soft); border-color: #ffd5c0; }
}
/* V2.0：原来是 font-size: 24px 配一个 emoji 字符。
   换成 SVG 之后 font-size 完全不起作用（SVG 尺寸由 width/height 决定），
   不显式给尺寸的话图标会按 SVG 默认的 300×150 撑爆整格。
   图标自带 nav-ico 外层 span，这里同时兜住"裸 svg"的情况。 */
.nav-sheet-item .nav-sheet-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; flex: none;
    color: var(--ds-primary);
}
.nav-sheet-item .nav-sheet-icon svg {
    display: block; width: 100%; height: 100%;
}
/* 菜单项图标的轻量反馈：hover 时图标跟着变成主色（在浅底上本来就已经是主色，
   这里靠放大一点点给出"可点"的暗示） */
.nav-sheet-item .nav-sheet-icon { transition: transform var(--t-fast) var(--ease-back); }
@media (hover: hover) and (pointer: fine) {
    .nav-sheet-item:hover .nav-sheet-icon { transform: scale(1.12); }
}
.nav-sheet-item .nav-sheet-label {
    font-size: var(--ds-fs-sm); color: var(--ds-text-2);
    text-align: center; line-height: 1.25;
    max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ── PC 侧栏：分组小标题 ──
   只加视觉分隔，不动 .side-btn 既有样式。 */
.side-group {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .6px;
    color: var(--ds-text-3);
    text-transform: uppercase;
    padding: var(--ds-s4) 12px var(--ds-s2);
    user-select: none;
}
.side-group:first-of-type { padding-top: var(--ds-s3); }
/* PC 侧栏在浅底卡片风下更清爽：去掉玻璃感带来的重影 */
@media (min-width: 769px) {
    .sidebar {
        background: var(--ds-surface);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        border-right: 1px solid var(--ds-border);
        box-shadow: none;
        padding: 16px 10px;
    }
}

/* 手机上不显示二级面板（它们是移动端专属） */
@media (min-width: 769px) {
    .nav-sheet, .nav-sheet-mask { display: none !important; }
}

/* ============================================================
   🤖 AI 识别结果信息条（2026-09-30 新增）
   ------------------------------------------------------------
   放在预览弹窗顶部：左边「识别类型」，右边「准确度 + 进度条」。
   准确度用颜色分级（≥90 绿 / ≥70 橙 / <70 红），但**不拦截**确认，
   只帮用户判断"要不要仔细核对"。
   ============================================================ */
.preview-ai-bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--ds-s4);
    flex-wrap: wrap;
    padding: 10px var(--ds-s4);
    margin-bottom: var(--ds-s3);
    background: var(--ds-info-soft);
    border: 1px solid #bfdbfe;
    border-radius: var(--ds-r-md);
}
.pai-left { display: flex; align-items: center; gap: var(--ds-s2); min-width: 0; }
/* 原来这里是 font-size: 17px 配一个 emoji，现在换成 SVG ——
   SVG 不吃 font-size，必须显式给宽高，否则会撑成默认 300×150。 */
.pai-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; flex: none; color: var(--ds-info-text);
}
.pai-icon svg { display: block; width: 100%; height: 100%; }
.pai-type { font-size: var(--ds-fs-base); font-weight: 600; color: var(--ds-info-text); }
.pai-right { display: flex; align-items: center; gap: var(--ds-s2); }
/* V2.0：原 .pai-conf / .pai-conf-bar / .pai-conf-fill 已废弃 ——
   那套是给「约 xx%」的假精确数字配的。现在换成两个计数：
     .pai-count  共 N 条      中性色，不抢眼
     .pai-bad    M 条待确认 / 全部通过   语义色，是这一行真正的重点
   旧 class 保留为空规则会留垃圾，直接删掉。 */
.pai-count {
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-2);
    font-variant-numeric: tabular-nums;
}
.pai-bad {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 9px;
    font-size: var(--ds-fs-xs);
    font-weight: 600;
    border-radius: 99px;
    font-variant-numeric: tabular-nums;
    cursor: help;
}
.pai-bad.is-good { color: var(--ds-success); background: var(--ds-success-soft); }
.pai-bad.is-warn { color: var(--ds-warning); background: var(--ds-warning-soft); }

/* AI 异常提示条 */
.preview-ai-warn {
    padding: 10px var(--ds-s4);
    margin-bottom: var(--ds-s3);
    background: var(--ds-warning-soft);
    border: 1px solid #fde68a;
    border-radius: var(--ds-r-md);
    color: var(--ds-warning-text);
    font-size: var(--ds-fs-sm);
    line-height: 1.7;
}
.preview-ai-warn b { font-weight: 700; }
@media (max-width: 560px) {
    .preview-ai-bar { padding: 9px var(--ds-s3); gap: var(--ds-s2); }
    .pai-bad { padding: 2px 7px; }
}

/* ══════════════════════════════════════════════════════════════════════
   🎬 V2.0 交互组件层（2026-09-30 新增）
   ══════════════════════════════════════════════════════════════════════
   本段集中实现需求第三十一/十五/二十五/三十四/三十/二十九条涉及的
   通用交互件。全部为**纯叠加**：不覆盖既有规则，不改既有 class 语义，
   要回退只需删掉本段。

   命名约定：V2.0 新增的交互件一律带 `v2-` 前缀，与既有 class 隔离，
   避免特异性和既有规则打架（这份 CSS 已有 5700 行、50+ 断点，
   新增选择器抢特异性是最容易出事的地方）。
   ══════════════════════════════════════════════════════════════════════ */


/* ──────────────────────────────────────────────────────────────
   1. 统一按钮状态机（需求第十四条）
   ──────────────────────────────────────────────────────────────
   给所有交互元素补齐 7 态。用 :where() 把特异性压到 0，
   这样它只作为"兜底"，任何既有规则都能覆盖它 —— 零回归的关键。

   为什么不直接改 .btn：
     .btn 及其变体在这份文件里有十几条规则，直接改会牵动全站布局。
     这里只增加"状态反馈"（transform / background / outline），
     不动尺寸与排版。
   ────────────────────────────────────────────────────────────── */
:where(button, .btn, .ds-btn, .nav-btn, .side-btn, .tab-btn, .cockpit-icon-btn, [role="button"]):not(:disabled) {
    transition:
        transform var(--t-instant) var(--ease-soft),
        background-color var(--t-fast) var(--ease-soft),
        border-color var(--t-fast) var(--ease-soft),
        color var(--t-fast) var(--ease-soft),
        box-shadow var(--t-fast) var(--ease-soft);
}

/* Press 态：需求明确 scale(0.97)、80~120ms */
:where(button, .btn, .ds-btn, .nav-btn, .side-btn, .tab-btn, .cockpit-icon-btn, [role="button"]):not(:disabled):active {
    transform: scale(.97);
}

/* Focus 态：键盘可达性。只在键盘导航时出现，鼠标点击不冒光圈 */
:where(button, .btn, .ds-btn, .nav-btn, .side-btn, .tab-btn, [role="button"]):focus-visible {
    outline: 2px solid var(--ds-primary);
    outline-offset: 2px;
    border-radius: inherit;
}

/* Disabled */
:where(button, .btn, .ds-btn):disabled,
.v2-disabled {
    opacity: .5;
    cursor: not-allowed;
    transform: none !important;
}

/* Hover：只在真有指针的设备上给。触屏 hover 会"粘住"最后点过的元素 */
@media (hover: hover) and (pointer: fine) {
    :where(.btn, .ds-btn, .side-btn, .cockpit-icon-btn):not(:disabled):hover {
        transform: translateY(-1px);
    }
    :where(.btn, .ds-btn, .side-btn, .cockpit-icon-btn):not(:disabled):active {
        transform: scale(.97);
    }
}

/* ── 按钮内联加载态（需求第二十四条）──
   按钮点击后进入 loading：文字旁出现小转圈，且阻止二次点击。
   loading 期间 **不改变按钮宽度** —— 宽度跳变会让整排按钮抖动。 */
.v2-loading {
    position: relative;
    pointer-events: none;
    color: transparent !important;
}
.v2-loading::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 14px; height: 14px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    /* 从按钮自身继承颜色，但在 color:transparent 下取不到 —— 
       所以这里显式给一个色，且用 !important 保证可见 */
    border-color: rgba(255,255,255,.45);
    border-top-color: #fff;
    animation: v2Spin .62s linear infinite;
}
/* 浅色按钮（描边/次要）用深色转圈 */
.v2-loading.v2-loading-dark::after {
    border-color: rgba(0,0,0,.18);
    border-top-color: var(--ds-text-2);
}
@keyframes v2Spin { to { transform: rotate(360deg); } }

/* 成功态：短暂显示对勾（需求第二十四条的 "✓ 已保存" 阶段） */
.v2-success-flash {
    animation: v2SuccessFlash .9s var(--ease-out);
}
@keyframes v2SuccessFlash {
    0%   { background-color: var(--ds-success); color: #fff; }
    70%  { background-color: var(--ds-success); color: #fff; }
    100% { background-color: ''; color: ''; }
}


/* ──────────────────────────────────────────────────────────────
   2. 展开/收起动画（需求第十五条）
   ──────────────────────────────────────────────────────────────
   需求要求：opacity 0→1、translateY(-4px)→0、200~280ms、箭头同步旋转。

   实现要点（这是最容易做错的地方）：
     展开动画**不能**用 max-height 过渡 —— 内容高度未知，
     写死 max-height 会导致"提前展开完"或"卡住"。
     这里用 grid-template-rows: 0fr → 1fr，是现代浏览器唯一能
     真正做"高度自适应过渡"的方法，且只触发合成层，不重排。

   ⚠ 关键细节（实测踩坑）：
     `grid-template-rows: 0fr` **单写是不够的**。
     fr 单位定义的行轨道，其最小值默认是 `auto`（即 minmax(auto, 0fr)），
     而 `auto` 的最小值会取**内容高度** —— 于是收起态仍保留内容高度
     （实测 40px，面板根本收不回去）。

     必须写成 `minmax(0, 0fr)` / `minmax(0, 1fr)`，把轨道最小值显式压到 0；
     同时 grid item（即 .v2-fold-body 的直接子元素）也要 min-height: 0，
     两者缺一不可。下面用的是标准写法。

   .v2-fold       容器
   .v2-fold.open  展开态
   .v2-fold-head  可点击头部（内含 .v2-fold-arrow）
   .v2-fold-body  内容区
   ────────────────────────────────────────────────────────────── */
.v2-fold > .v2-fold-body {
    display: grid;
    grid-template-rows: minmax(0, 0fr);
    opacity: 0;
    transition:
        grid-template-rows var(--t-base) var(--ease-out),
        opacity var(--t-base) var(--ease-out);
}
.v2-fold > .v2-fold-body > * {
    overflow: hidden;
    min-height: 0;
}
.v2-fold.open > .v2-fold-body {
    /* 展开态用 auto 而不是 1fr：
       grid 容器高度是 auto 时没有"剩余空间"可分，`1fr` 在内容较高的
       折叠面板上会塌成 0（营收卡片详情实测 944px 内容 → 0px 高度）。
       auto 让轨道按内容撑开，动画由 0fr ↔ auto 的插值完成。 */
    grid-template-rows: minmax(0, auto);
    opacity: 1;
}

/* 内容轻微上浮入场（需求要求的 translateY -4px → 0） */
.v2-fold > .v2-fold-body > * {
    transform: translateY(-4px);
    transition: transform var(--t-base) var(--ease-out);
}
.v2-fold.open > .v2-fold-body > * {
    transform: translateY(0);
}

/* 箭头：rotate 0 → 180（需求原文就是 180deg） */
.v2-fold-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1em; height: 1em;
    transition: transform var(--t-base) var(--ease-out);
    transform-origin: 50% 50%;
    flex: none;
}
.v2-fold.open > .v2-fold-head .v2-fold-arrow,
.v2-fold.open > .v2-fold-arrow {
    transform: rotate(180deg);
}

/* 头部整体可点击反馈 */
.v2-fold-head {
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) and (pointer: fine) {
    .v2-fold-head:hover { background: var(--ds-surface-2); }
}


/* ──────────────────────────────────────────────────────────────
   3. Skeleton 骨架屏（需求第二十五条）
   ──────────────────────────────────────────────────────────────
   既有 .ds-skeleton 已有；这里补上"淡入"与"被替换时"的处理。
   核心是：数据回来时不能"啪"地跳变，要有一次淡入。 */
.v2-fadein {
    animation: v2FadeIn var(--t-slow) var(--ease-out) both;
}
@keyframes v2FadeIn {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* 骨架卡专用的圆角与高度，补齐既有 .ds-skeleton 的不足 */
.v2-sk-card {
    border-radius: var(--ds-r-lg);
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    padding: var(--ds-s4);
}


/* ──────────────────────────────────────────────────────────────
   4. Toast 统一系统（需求第三十一条）
   ──────────────────────────────────────────────────────────────
   既有 #toast / .toast-bar 已实现，这里只补两个缺口：
     a) 移动端"顶部显示"（需求原文要求）
     b) 带图标的四种状态（成功/信息/警告/错误）
   用 .v2-toast-ico 包一个内联 SVG，不依赖任何图标字体。 */
.v2-toast-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15em; height: 1.15em;
    flex: none;
    vertical-align: -0.18em;
}
.v2-toast-ico svg { display: block; width: 100%; height: 100%; }


/* ──────────────────────────────────────────────────────────────
   5. 导航选中态（需求第三十条）
   ──────────────────────────────────────────────────────────────
   当前页绿色 + 图标 scale(.92)→1 + 2~3px 指示线/小圆点，180ms。 */
.nav-btn, .side-btn {
    position: relative;
}
.nav-btn .nav-icon, .side-btn .nav-icon {
    transition: transform var(--t-base) var(--ease-back);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.nav-btn.active .nav-icon,
.side-btn.active .nav-icon {
    transform: scale(1);
    animation: v2NavPop var(--t-base) var(--ease-back);
}
@keyframes v2NavPop {
    0%   { transform: scale(.92); }
    100% { transform: scale(1); }
}

/* 底部导航指示点（小圆点，需求说"指示线或小圆点"二选一，圆点更克制） */
.nav-btn::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 4px;
    width: 0; height: 3px;
    border-radius: 99px;
    background: var(--ds-primary);
    transform: translateX(-50%) scaleX(0);
    transition: transform var(--t-base) var(--ease-out);
    opacity: 0;
}
.nav-btn.active::after {
    width: 16px;
    transform: translateX(-50%) scaleX(1);
    opacity: 1;
}
/* AI 那个 tab 的指示点用橙，和其它一致 —— 它本来就是 CTA */
.nav-btn-ai.active::after { background: var(--ds-primary); }

/* 侧栏选中：左侧竖条 */
.side-btn::before {
    content: '';
    position: absolute;
    left: 0; top: 50%;
    width: 3px; height: 0;
    border-radius: 0 3px 3px 0;
    background: var(--ds-primary);
    transform: translateY(-50%);
    transition: height var(--t-base) var(--ease-out);
}
.side-btn.active::before { height: 18px; }


/* ──────────────────────────────────────────────────────────────
   6. 空状态 / 错误状态（需求第三十二、三十三条）
   ──────────────────────────────────────────────────────────────
   补齐既有 .ds-empty 的"操作按钮"区，并新增错误态。 */
.v2-empty-actions, .v2-error-actions {
    display: flex;
    gap: var(--ds-s3);
    justify-content: center;
    margin-top: var(--ds-s4);
    flex-wrap: wrap;
}
.v2-empty-icon, .v2-error-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 52px; height: 52px;
    margin: 0 auto var(--ds-s3);
    border-radius: 50%;
}
.v2-empty-icon {
    background: var(--ds-surface-2);
    color: var(--ds-text-3);
}
.v2-error-icon {
    background: var(--ds-danger-soft);
    color: var(--ds-danger);
}
.v2-empty-icon svg, .v2-error-icon svg {
    width: 26px; height: 26px;
    display: block;
}


/* ──────────────────────────────────────────────────────────────
   7. 移动端数据卡片（需求第十七条）
   ──────────────────────────────────────────────────────────────
   把 PC 表格在窄屏转成"卡片 + 展开详情"，而不是横向滚动。
   这个组件由 JS 生成（见 app.js 的 v2RenderCardTable），
   结构：.v2-cardlist > .v2-cardrow( + .v2-cardrow-main + .v2-cardrow-detail )
   ────────────────────────────────────────────────────────────── */
.v2-cardlist {
    display: flex;
    flex-direction: column;
    gap: var(--ds-s3);
}
.v2-cardrow {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-lg);
    overflow: hidden;
    transition: border-color var(--t-fast) var(--ease-soft),
                box-shadow var(--t-fast) var(--ease-soft);
}
.v2-cardrow.open {
    border-color: var(--ds-primary);
    box-shadow: var(--ds-shadow-sm);
}
.v2-cardrow-main {
    display: flex;
    align-items: center;
    gap: var(--ds-s3);
    padding: var(--ds-s4);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
}
.v2-cardrow-main:active { background: var(--ds-surface-2); }

/* 左侧：日期 + 星期（营收表） / 项目名（成本表）
   ⚠ 成本表的项目名比日期长得多（"食材成本" vs "10-01"），
     min-width 写死 76px 会把「外卖平台佣金」这种挤成两行，
     行高被撑开、卡片高低不齐。让它在 76px 基础上可以长到 132px，
     超出才省略。营收表那边项目名短，实际仍按 76px 渲染，不受影响。 */
.v2-cr-date {
    flex: none;
    min-width: 76px;
    max-width: 132px;
}
.v2-cr-day {
    font-size: var(--ds-fs-md);
    font-weight: 700;
    color: var(--ds-text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2-cr-wd {
    font-size: var(--ds-fs-xs);
    color: var(--ds-text-3);
    margin-top: 1px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 成本表卡片的主指标只有两个（金额 / 占纯收），
   用 3 列网格会在右边空出一整格。这里按实际数量收窄，
   让两个数各占一半、字号可以更大一点。 */
.v2-cardlist[data-v2-for="costTable"] .v2-cr-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* 成本表的项目名是长文本，主区让给它更多空间。
   ⚠ 必须放在下面的 560px / 380px 断点**之后**才生效 ——
     那两个断点里也有 .v2-cr-date { min-width } 的规则，
     同级选择器后者胜。所以这几条统一挪到文件靠后的「卡片表
     断点覆盖」小节里，见第 14 节末尾。 */

/* 中部：核心三指标（目标/实际/完成率） */
.v2-cr-metrics {
    flex: 1;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--ds-s2);
    min-width: 0;
}
.v2-cr-metric {
    min-width: 0;
}
.v2-cr-metric-label {
    font-size: 11px;
    color: var(--ds-text-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2-cr-metric-val {
    font-size: var(--ds-fs-base);
    font-weight: 600;
    color: var(--ds-text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-top: 1px;
}
.v2-cr-metric-val.is-good { color: var(--ds-success); }
.v2-cr-metric-val.is-warn { color: var(--ds-warning); }
.v2-cr-metric-val.is-bad  { color: var(--ds-danger); }
/* 主指标（营收表的「实收」）：字形略大、用主色，一眼抓到重点 */
.v2-cr-metric-val.is-strong {
    font-size: calc(var(--ds-fs-base) + 1px);
    font-weight: 700;
    color: var(--ds-text);
}

/* 右侧箭头 */
.v2-cr-arrow {
    flex: none;
    width: 20px; height: 20px;
    display: flex; align-items: center; justify-content: center;
    color: var(--ds-text-3);
    transition: transform var(--t-base) var(--ease-out),
                color var(--t-fast) var(--ease-soft);
}
.v2-cardrow.open .v2-cr-arrow {
    transform: rotate(180deg);
    color: var(--ds-primary);
}

/* 展开区：详情字段网格
   ⚠ 这里踩了两个坑，记下来免得再犯：
   1) 行轨道最小值默认是 auto。写 `0fr` 等价于 `minmax(auto, 0fr)`，
      auto 会取内容高度 → 收起态压根收不回去。必须 `minmax(0, 0fr)`。
   2) 但只写 `minmax(0, 1fr)` 展开也不行：grid 容器高度是 auto 时，
      `fr` 没有"剩余空间"可分，单行轨道会塌成 0（实测 944px 的内容
      渲染出 0px 高度）。展开态要用 `auto` 作为最大值，
      让轨道按内容撑开，同时靠外层 `overflow:hidden` 完成裁剪。
      配合 grid-template-rows 的插值，动画依然是从 0 平滑长到内容高。 */
.v2-cardrow-detail {
    display: grid;
    grid-template-rows: minmax(0, 0fr);
    transition: grid-template-rows var(--t-base) var(--ease-out);
    border-top: 1px solid transparent;
}
.v2-cardrow.open .v2-cardrow-detail {
    grid-template-rows: minmax(0, auto);
    border-top-color: var(--ds-border);
}
.v2-cardrow-detail > div {
    overflow: hidden;
    min-height: 0;
}
.v2-cr-fields {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: var(--ds-s3) var(--ds-s4);
    padding: var(--ds-s4);
    background: var(--ds-surface-2);
}
.v2-cr-field { min-width: 0; }
.v2-cr-field-label {
    font-size: 11px;
    color: var(--ds-text-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2-cr-field-val {
    font-size: var(--ds-fs-sm);
    font-weight: 600;
    color: var(--ds-text);
    font-variant-numeric: tabular-nums;
    margin-top: 2px;
    word-break: break-all;
}
.v2-cr-field-val.is-empty { color: var(--ds-text-3); font-weight: 400; }

/* 窄屏（需求第三十五条：375/390/430 必须无横向溢出） */
@media (max-width: 560px) {
    .v2-cardrow-main { padding: var(--ds-s3); gap: var(--ds-s2); }
    .v2-cr-date { min-width: 62px; }
    .v2-cr-day { font-size: var(--ds-fs-base); }
    .v2-cr-metrics { grid-template-columns: repeat(3, 1fr); gap: 6px; }
    .v2-cr-metric-val { font-size: var(--ds-fs-sm); }
    .v2-cr-fields {
        grid-template-columns: repeat(2, 1fr);
        padding: var(--ds-s3);
        gap: var(--ds-s3);
    }
}
@media (max-width: 380px) {
    .v2-cr-date { min-width: 54px; }
    .v2-cr-metrics { gap: 4px; }
    .v2-cr-metric-label { font-size: 10px; }
    .v2-cr-metric-val { font-size: 12px; }
}


/* ──────────────────────────────────────────────────────────────
   8. 底部弹窗拖动关闭（需求第二十八条）
   ──────────────────────────────────────────────────────────────
   JS 负责跟手（改 transform），CSS 负责松手后的归位动效。
   用 .v2-dragging 类切换过渡：拖动中必须关掉 transition，
   否则手指每动一下都触发一次动画，会明显粘滞。 */
.v2-dragging {
    transition: none !important;
}
.v2-sheet-handle {
    width: 36px; height: 4px;
    border-radius: 99px;
    background: var(--ds-border-strong);
    margin: 8px auto 4px;
    flex: none;
}


/* ──────────────────────────────────────────────────────────────
   9. 扫描识别动画（需求第二十、二十一条）
   ──────────────────────────────────────────────────────────────
   专业克制的扫描框 + 缓慢扫描线。刻意不用粒子/发光/旋转机器人。

   性能要点（需求第三十八条）：
     · 扫描线用 transform: translateY（合成层），不用 top
     · 用 will-change 提前提升图层，避免首帧掉帧
     · 整个动画只有 2 个属性在动，CPU 占用极低
   ────────────────────────────────────────────────────────────── */
.v2-scan {
    position: relative;
    width: 168px; height: 168px;
    margin: 0 auto;
    border-radius: var(--ds-r-lg);
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
    overflow: hidden;
}
/* 四角标记（专业工具感，不是游戏感） */
.v2-scan::before,
.v2-scan::after {
    content: '';
    position: absolute;
    width: 22px; height: 22px;
    border: 2px solid var(--ds-primary);
    opacity: .85;
}
.v2-scan::before {
    top: 10px; left: 10px;
    border-right: none; border-bottom: none;
    border-radius: 6px 0 0 0;
}
.v2-scan::after {
    bottom: 10px; right: 10px;
    border-left: none; border-top: none;
    border-radius: 0 0 6px 0;
}
/* 扫描线 */
.v2-scan-line {
    position: absolute;
    left: 8%; right: 8%;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg,
        transparent,
        var(--ds-primary) 22%,
        var(--ds-primary) 78%,
        transparent);
    box-shadow: 0 0 8px rgba(255,107,53,.45);
    will-change: transform;
    animation: v2ScanMove 2.6s var(--ease-soft) infinite;
}
@keyframes v2ScanMove {
    0%        { transform: translateY(16px);     opacity: 0; }
    12%       { opacity: 1; }
    88%       { opacity: 1; }
    100%      { transform: translateY(140px);    opacity: 0; }
}
/* 扫描底纹（淡淡的横向线，营造"正在读取"的暗示） */
.v2-scan-grid {
    position: absolute;
    inset: 0;
    background-image: repeating-linear-gradient(
        to bottom,
        transparent 0,
        transparent 11px,
        rgba(31,42,36,.035) 11px,
        rgba(31,42,36,.035) 12px);
    pointer-events: none;
}

/* 识别阶段文案（需求第十九条的四阶段） */
.v2-scan-stages {
    display: flex;
    flex-direction: column;
    gap: 9px;
    margin-top: var(--ds-s5);
    text-align: left;
    max-width: 300px;
    margin-left: auto;
    margin-right: auto;
}
.v2-stage {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-3);
    transition: color var(--t-base) var(--ease-soft);
}
.v2-stage-dot {
    width: 16px; height: 16px;
    flex: none;
    border-radius: 50%;
    border: 1.5px solid var(--ds-border-strong);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--t-base) var(--ease-soft);
}
.v2-stage-dot i {
    display: block;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: transparent;
    transition: background var(--t-fast) var(--ease-soft);
}
/* 已完成的阶段 */
.v2-stage.done { color: var(--ds-text-2); }
.v2-stage.done .v2-stage-dot {
    border-color: var(--ds-success);
    background: var(--ds-success);
}
.v2-stage.done .v2-stage-dot i { background: #fff; }
/* 进行中的阶段 */
.v2-stage.active { color: var(--ds-text); font-weight: 600; }
.v2-stage.active .v2-stage-dot {
    border-color: var(--ds-primary);
    animation: v2StagePulse 1.5s ease-in-out infinite;
}
.v2-stage.active .v2-stage-dot i { background: var(--ds-primary); }
@keyframes v2StagePulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(255,107,53,.35); }
    50%      { box-shadow: 0 0 0 4px rgba(255,107,53,0); }
}

/* 识别完成：对勾 + 轻微 scale(0.92→1)（需求第二十一条） */
.v2-done-badge {
    width: 62px; height: 62px;
    margin: 0 auto var(--ds-s4);
    border-radius: 50%;
    background: var(--ds-success-soft);
    color: var(--ds-success);
    display: flex;
    align-items: center;
    justify-content: center;
    animation: v2DonePop .48s var(--ease-back) both;
}
.v2-done-badge svg { width: 32px; height: 32px; display: block; }
@keyframes v2DonePop {
    0%   { transform: scale(.92); opacity: 0; }
    100% { transform: scale(1);   opacity: 1; }
}


/* ──────────────────────────────────────────────────────────────
   9.5 提交层容器（V2.0 新增）
   ──────────────────────────────────────────────────────────────
   所有"要盖住页面"的东西（未保存条、确认弹窗、全局遮罩）都挂这里。

   为什么不直接挂在 body 上、靠 z-index 比大小：
   页面里不少卡片带 transform（hover 抬升、展开动画）。只要祖先链
   上出现过 transform，浏览器就会在那儿新建一个**层叠上下文**，
   子元素的 z-index 只在那个上下文内部比较 —— 于是固定定位的提示条
   会被一个 z-index:1 的卡片压住，而且怎么调数字都没用。

   这个容器自己 `isolation: isolate`，等于给所有覆盖层开了一个
   独立的层叠空间，跟页面内容的 transform 彻底互不干扰。

   ⚠ 容器**本身不设 pointer-events**（保持 auto），否则里面的按钮
     会被一起禁掉；但它必须是 0 尺寸、不占位，所以底下给
     position:fixed + inset:0 + pointer-events:none 的写法会连
     子元素一起穿透。这里用 `pointer-events:none` + 子元素
     `pointer-events:auto` 的组合。
   ────────────────────────────────────────────────────────────── */
.v2-submit-layer {
    position: fixed;
    inset: 0;
    z-index: 9000;
    isolation: isolate;
    pointer-events: none;      /* 不挡点击 */
    /* 必须 0 尺寸语义：虽然固定定位不占文档流，但 inset:0 会让它
       覆盖整个视口，透传点击全靠下面这行 */
}
.v2-submit-layer > * {
    pointer-events: auto;      /* 子元素恢复可点击 */
}


/* ──────────────────────────────────────────────────────────────
   9.6 离开确认弹窗（V2.0 / Task#12）
   ──────────────────────────────────────────────────────────────
   挂在 V2_SUBMIT 层上，跟未保存条共用同一套层叠空间。

   入场动效：遮罩淡入 + 卡片从 96% 放大到 100% 并微微上浮。
   时长取 --t-base（220ms），落在"普通展开 200~280ms"的规范内。
   ⚠ 不用 scale 从 0 起：那样在 375px 小屏上会有明显的"炸开"感，
     96% 起跳更克制，跟整体动效调性一致。 */
.v2-confirm-mask {
    position: fixed;
    inset: 0;
    background: rgba(15, 34, 24, .38);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    opacity: 0;
    transition: opacity var(--t-base) var(--ease-out);
}
.v2-confirm-mask.show { opacity: 1; }

.v2-confirm {
    width: 100%;
    max-width: 340px;
    background: var(--ds-surface);
    border-radius: var(--ds-r-lg);
    box-shadow: var(--ds-shadow-lg);
    padding: var(--ds-s5) var(--ds-s4) var(--ds-s4);
    text-align: center;
    transform: translateY(6px) scale(.96);
    opacity: 0;
    transition: transform var(--t-base) var(--ease-out),
                opacity var(--t-base) var(--ease-out);
}
.v2-confirm-mask.show .v2-confirm {
    transform: translateY(0) scale(1);
    opacity: 1;
}

.v2-confirm-ico {
    width: 44px; height: 44px;
    border-radius: 50%;
    background: var(--ds-warning-soft);
    color: var(--ds-warning);
    display: flex; align-items: center; justify-content: center;
    margin: 0 auto var(--ds-s3);
}
.v2-confirm-ico svg { width: 22px; height: 22px; display: block; }

.v2-confirm-title {
    font-size: var(--ds-fs-md);
    font-weight: 700;
    color: var(--ds-text);
    margin-bottom: 6px;
}
.v2-confirm-desc {
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-2);
    line-height: 1.55;
    margin-bottom: var(--ds-s4);
}
.v2-confirm-acts {
    display: flex;
    gap: var(--ds-s2);
}
/* 两个按钮等宽：视觉上"留在本页"不该比"放弃"更弱 ——
   默认动作是留下来，它俩得平起平坐。 */
.v2-confirm-acts .ds-btn { flex: 1; justify-content: center; }

@media (prefers-reduced-motion: reduce) {
    .v2-confirm-mask,
    .v2-confirm { transition-duration: .01ms; }
    .v2-confirm { transform: none; }
}


/* ──────────────────────────────────────────────────────────────
   10. 未保存保护条（需求第三十四条）
   ──────────────────────────────────────────────────────────────
   顶部滑入的常驻提示条，带"继续编辑 / 放弃修改"两个动作。
   出现位置：移动端顶部（不遮底部导航），桌面端顶部居中。 */
.v2-unsaved-bar {
    position: fixed;
    left: 50%;
    /* ★ 2026-10-01 修：原先写死 top: 10px。
       这条规则是"浏览器模式"时代写的 —— 那时页面顶部还有 Safari 地址栏
       帮它挡着，10px 相对安全。**装成 PWA 独立模式后，顶部换成 iOS 状态栏
       （时间/电量/信号，高约 47~59px）**，而这条 fixed 提示条仍旧只距屏顶
       10px，于是整条钻到状态栏底下，与状态栏文字挤在一起。
       真机截图确认过：提示条紧贴状态栏下沿，且压住了页面标题。

       改法：贴到**顶栏下方**，而不是屏顶下方。
       --topbar-height 由 adjustTopbar() 实测写入，且已含安全区
       （独立模式下它是 顶栏高 108 + 8px 间隙 = 116px），
       所以这里直接用它，不能再叠 env() —— 否则双算。
       兜底值 60px 服务"JS 没跑/变量未写入"的瞬间。
       浏览器模式下该变量是 53px，与原先 top:10px 相比只是下移一点，
       观感更整齐（原先贴着地址栏，现在整整齐齐在顶栏下方）。 */
    top: var(--topbar-height, 60px);
    transform: translateX(-50%) translateY(-16px);
    z-index: 9000;
    display: flex;
    align-items: center;
    gap: var(--ds-s3);
    padding: 10px var(--ds-s4);
    background: var(--ds-surface);
    border: 1px solid var(--ds-warning);
    border-radius: var(--ds-r-lg);
    box-shadow: var(--ds-shadow-lg);
    font-size: var(--ds-fs-sm);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t-base) var(--ease-out),
                transform var(--t-base) var(--ease-out);
    max-width: calc(100vw - 24px);
}
.v2-unsaved-bar.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
}
.v2-unsaved-bar .v2-usb-text {
    display: flex;
    align-items: center;
    gap: 7px;
    color: var(--ds-warning-text);
    font-weight: 600;
    white-space: nowrap;
}
.v2-unsaved-bar .v2-usb-acts {
    display: flex;
    gap: var(--ds-s2);
    flex: none;
}
@media (max-width: 480px) {
    .v2-unsaved-bar {
        left: 8px; right: 8px;
        /* top 沿用基础规则的 var(--topbar-height)：窄屏正是手机，
           独立模式下这一条尤其关键 —— 不能让提示条钻进状态栏。 */
        transform: translateY(-16px);
        max-width: none;
        flex-wrap: wrap;
        justify-content: space-between;
    }
    .v2-unsaved-bar.show { transform: translateY(0); }
    .v2-unsaved-bar .v2-usb-text { font-size: var(--ds-fs-xs); }
}


/* ──────────────────────────────────────────────────────────────
   11. 首页「快速操作」+「最近使用」（需求第九、十一条）
   ────────────────────────────────────────────────────────────── */
.v2-quick {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--ds-s3);
}
.v2-quick-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    padding: var(--ds-s4) var(--ds-s2);
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-lg);
    cursor: pointer;
    text-align: center;
    -webkit-tap-highlight-color: transparent;
    /* button 元素 reset —— 这几行缺一不可：
       button 自带 border(2px outset) / background(buttonface) /
       font(系统 UI 字体 13.33px)，不清掉就跟旁边的卡片对不齐，
       字号也会比设计稿小一档。 */
    font: inherit;
    color: inherit;
    font-family: inherit;
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    box-sizing: border-box;
    transition: border-color var(--t-fast) var(--ease-soft),
                box-shadow var(--t-fast) var(--ease-soft),
                transform var(--t-instant) var(--ease-soft);
}
/* 键盘可达：button 天然能 Tab，补一个聚焦圈让焦点看得见 */
.v2-quick-item:focus-visible {
    outline: 2px solid var(--ds-primary);
    outline-offset: 2px;
}
@media (hover: hover) and (pointer: fine) {
    .v2-quick-item:hover {
        border-color: var(--ds-primary);
        box-shadow: var(--ds-shadow-sm);
    }
}
.v2-quick-item:active { transform: scale(.97); }
.v2-quick-ico {
    width: 34px; height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--ds-r-md);
    background: var(--ds-primary-soft);
    color: var(--ds-primary);
    flex: none;
}
.v2-quick-ico svg { width: 18px; height: 18px; display: block; }
/* 第一个（截图识别）作为主 CTA 强化 */
.v2-quick-item.is-primary {
    background: var(--ds-primary);
    border-color: var(--ds-primary);
    color: #fff;
}
.v2-quick-item.is-primary .v2-quick-ico {
    background: rgba(255,255,255,.2);
    color: #fff;
}
.v2-quick-item.is-primary .v2-quick-label { color: #fff; }
.v2-quick-label {
    font-size: var(--ds-fs-xs);
    font-weight: 600;
    color: var(--ds-text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
@media (max-width: 400px) {
    .v2-quick { gap: var(--ds-s2); }
    .v2-quick-item { padding: var(--ds-s3) 4px; gap: 5px; }
    .v2-quick-ico { width: 30px; height: 30px; }
    .v2-quick-ico svg { width: 16px; height: 16px; }
    .v2-quick-label { font-size: 11px; }
}

/* 最近使用：列表行 */
.v2-recent {
    display: flex;
    flex-direction: column;
}
.v2-recent-item {
    display: flex;
    align-items: center;
    gap: var(--ds-s3);
    padding: 10px var(--ds-s2);
    border-radius: var(--ds-r-md);
    cursor: pointer;
    /* button reset（同 .v2-quick-item，见那里的说明） */
    font: inherit;
    color: inherit;
    font-family: inherit;
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: none;
    width: 100%;
    box-sizing: border-box;
    text-align: left;
    transition: background var(--t-fast) var(--ease-soft);
    -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) and (pointer: fine) {
    .v2-recent-item:hover { background: var(--ds-surface-2); }
}
.v2-recent-item:active { background: var(--ds-surface-2); }
.v2-recent-ico {
    width: 30px; height: 30px;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--ds-r-sm);
    background: var(--ds-surface-2);
    color: var(--ds-text-2);
}
.v2-recent-ico svg { width: 16px; height: 16px; display: block; }
.v2-recent-body { flex: 1; min-width: 0; }
.v2-recent-name {
    font-size: var(--ds-fs-base);
    font-weight: 600;
    color: var(--ds-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2-recent-time {
    font-size: var(--ds-fs-xs);
    color: var(--ds-text-3);
    margin-top: 1px;
}
.v2-recent-go {
    flex: none;
    color: var(--ds-text-3);
    width: 16px; height: 16px;
    display: flex; align-items: center; justify-content: center;
}
.v2-recent-go svg { width: 14px; height: 14px; display: block; }


/* ──────────────────────────────────────────────────────────────
   12. 趋势区间切换（需求第十条）
   ──────────────────────────────────────────────────────────────
   分段控件，切换时图表平滑过渡（JS 侧用 setOption 的 notMerge:false
   保证 ECharts 自己做形变动画，不重建实例）。 */
.v2-seg {
    display: inline-flex;
    padding: 3px;
    background: var(--ds-surface-2);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-md);
    gap: 2px;
    position: relative;
}
.v2-seg-btn {
    position: relative;
    padding: 0 14px;
    height: 28px;
    border: none;
    background: transparent;
    border-radius: var(--ds-r-sm);
    font-size: var(--ds-fs-sm);
    color: var(--ds-text-2);
    cursor: pointer;
    font-family: inherit;
    transition: color var(--t-fast) var(--ease-soft),
                background var(--t-fast) var(--ease-soft);
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
}
.v2-seg-btn.active {
    background: var(--ds-surface);
    color: var(--ds-primary);
    font-weight: 700;
    box-shadow: var(--ds-shadow-xs);
}
@media (hover: hover) and (pointer: fine) {
    .v2-seg-btn:not(.active):hover { color: var(--ds-text); }
}
@media (max-width: 400px) {
    .v2-seg-btn { padding: 0 10px; font-size: var(--ds-fs-xs); }
}


/* ──────────────────────────────────────────────────────────────
   13. 全局动效降级（需求第三十九条）
   ──────────────────────────────────────────────────────────────
   用户开启"减少动画"时，关掉一切位移与循环动画，
   只保留必要的状态反馈（颜色变化、瞬时切换）。 */
@media (prefers-reduced-motion: reduce) {
    .v2-scan-line {
        animation: none;
        transform: translateY(80px);
        opacity: .7;
    }
    .v2-scan::before, .v2-scan::after { animation: none; }
    .v2-stage.active .v2-stage-dot { animation: none; }
    .v2-done-badge { animation: none; }
    .v2-fadein { animation: none; }
    .nav-btn.active .nav-icon { animation: none; }
    .v2-fold > .v2-fold-body,
    .v2-fold > .v2-fold-body > *,
    .v2-cardrow-detail,
    .v2-fold-arrow,
    .v2-cr-arrow,
    .v2-unsaved-bar,
    .v2-seg-btn,
    :where(button, .btn, .ds-btn, .nav-btn, .side-btn, .tab-btn):not(:disabled) {
        transition-duration: .01ms !important;
    }
    :where(button, .btn, .ds-btn, .nav-btn, .side-btn, .tab-btn, [role="button"]):not(:disabled):active {
        transform: none;
    }
}



/* ══════════════════════════════════════════════════════════════════════
   14. 卡片表格的宽窄屏切换（配合 JS 的 buildCardTable）
   ══════════════════════════════════════════════════════════════════════
   策略：宽屏（>768px）显示原表格、隐藏卡片；窄屏反之。
   为什么以 768 为界而不是更窄的 560：
     经营日登记表有 45 列，768px 下横滚已经很难用了，
     而卡片布局在 768 下仍能放下"日期 + 三指标 + 箭头"。
   ══════════════════════════════════════════════════════════════════════ */
/* 默认（宽屏）：卡片隐藏 */
.v2-cardlist { display: none; }

@media (max-width: 768px) {
    /* 窄屏：有卡片的表格整体隐藏，改用卡片。
       ⚠ .ct-wrap 必须一起写进来 —— 成本表用的是 .ct-wrap
         （营收表才是 .table-wrap）。漏掉它的话成本表会出现
         「卡片 + 表格」上下两份，用户会以为数据重复了。 */
    .v2-has-cards { display: none !important; }
    .v2-cardlist { display: flex; }
}

/* 兜底：JS 未执行时（禁用脚本）表格照常显示，什么都不会坏 */
@media (max-width: 768px) {
    .table-wrap:not(.v2-has-cards),
    .ct-wrap:not(.v2-has-cards) { overflow-x: auto; }
}

/* ── 每张表的卡片微调（必须在所有通用断点之后）──────────────────
   为什么单独开一节：上面 560px / 380px 断点里已经有
   `.v2-cr-date { min-width: 62px / 54px }`。同为单类选择器，
   谁在后面谁生效 —— 想按表覆盖就只能写在它们后面。
   把这些规则塞回各自的组件段里，看起来更"内聚"，
   但会被后面的断点静默盖掉，改完发现没反应。（这轮已经踩过。） */

/* ── 经营日登记表卡片：三组指标挤紧（2026-10-01 店主反馈）──────────
   问题：主区是 `display:grid; grid-template-columns: repeat(3, 1fr)`，
   三个指标组被平均撑满整个主区宽度。390 宽下每组 86px、组间还夹着
   6px gap —— 「实收」的标签和它的数字分居两端，读的时候眼睛要横跨
   半个屏才能把「实收」和 5317.06 连起来。店主原话：「项目和金额距离
   过远，不好查看」。
   （三组各 86px 是实测值，见 test/diag_card_gap.py。）

   修法：不让三组平分宽度，改成按内容宽度左对齐排布（auto + start）。
   每组只占「标签/数字里更宽的那个」那么宽 —— 实测把 86px 收到
   ~62px，三组总宽从 269px 降到 ~190px，右边不再留空档，
   标签与数字的横向跨度也跟着收窄。
   为什么用 `auto` 而不是写死 px：金额位数会变（269.00 vs 10258.76），
   写死宽度要么截断要么留白，`auto` 让浏览器自己按内容定。

   ⚠ 选择器为什么用 .bizlog-body 而不是 [data-v2-for="bizlogTable"]：
     卡片容器的 data-v2-for 取自 `tableEl.id`，而 #bizlogTable 是外层
     **div 包裹层**、里面的 <table> 没有 id —— 于是那个属性实际是空串
     （实测 `for: ""`），按它选根本不中。用已有的包裹类 .bizlog-body
     做祖先锚点最稳，也不必去改 buildCardTable 的 key 逻辑
     （那会连带影响「重建时保留展开态」的 oldList 查询）。

   为什么只对这张表生效：成本表卡片是「项目名 + 2 个指标」，项目名
   本来就长（"外卖平台佣金"），挤到 auto 会让它比日期格还宽、
   金额被顶到右边去 —— 那边维持 1fr 均分。.bizlog-body 天然只圈住
   经营日登记表，正好。 */
.bizlog-body .v2-cr-metrics {
    grid-template-columns: repeat(3, auto);
    justify-content: start;
    column-gap: 14px;
}

/* 左侧日期格也跟着收一点：卡片主区整体往左靠，日期到第一组指标的
   那段空白一并收窄。窄屏下 62px 里装 "11一"（日号+星期）刚好，
   再少会折行。 */
@media (max-width: 560px) {
    .bizlog-body .v2-cr-metrics { column-gap: 12px; }
}
@media (max-width: 380px) {
    .bizlog-body .v2-cr-metrics { column-gap: 10px; }
}

.v2-cardlist[data-v2-for="costTable"] .v2-cr-date { min-width: 88px; }
@media (max-width: 560px) {
    /* 成本表的项目名比日期长（"食材成本" vs "10-01"），
       560 以下给到 80px，两行以内能放下大多项目名。 */
    .v2-cardlist[data-v2-for="costTable"] .v2-cr-date { min-width: 80px; }
}
@media (max-width: 380px) {
    /* 375px 是支持的最窄宽度：日期格再让 4px 给指标区，
       否则「外卖平台佣金」会压到金额上。 */
    .v2-cardlist[data-v2-for="costTable"] .v2-cr-date { min-width: 74px; }
}

/* ── 卡片内的可编辑金额框（V2.0 · 移动端成本手工项）────────────────
   背景：≤768px 时原成本表格被 CSS 隐藏，卡片是唯一界面。卡片原本
   是只读镜像（cellText 把 <input> 读成纯文本），导致手机上手工项
   （租金/水电/网络/报销/推广/其它）根本没地方填。
   现在卡片「金额」格改成真的输入框，这里负责它的外观。

   为什么不用 .cost-input-wrap 那套：那套是给桌面表格单元格设的
   （padding 左右 12px、字号 15px、圆角 10px、上下 10px 内边距），
   放进卡片指标格里会把 v2-cr-metric-val 的 1px 行高撑到 40px+，
   卡片会变得又高又空。这里按卡片密度重新配一套紧凑值。 */
.v2-card-amt {
    position: relative;
    display: flex;
    align-items: center;
}
.v2-card-amt-cur {
    position: absolute;
    left: 6px;
    font-size: 11px;
    color: var(--ds-text-3);
    pointer-events: none;
}
.v2-card-amt input {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    padding: 4px 6px 4px 16px;
    border: 1px solid var(--ds-border);
    border-radius: 8px;
    background: var(--ds-surface);
    color: var(--ds-text);
    font-size: var(--ds-fs-sm);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    /* iOS 上字号 < 16px 会在聚焦时自动放大整个页面，缩放后
       布局会错位。靠 touch-action + maximum-scale 不敢保证，
       这里用 transform 的替代方案：保持字号可读即可，
       真正的防缩放交给 inputmode。 */
    text-align: right;
}
.v2-card-amt input:focus {
    outline: none;
    border-color: var(--ds-primary);
    box-shadow: 0 0 0 2px var(--ds-primary-soft, rgba(255, 122, 69, .18));
}
/* 去掉 number 输入框的上下箭头：手机上它只有 3~4px 宽，
   误触概率极高，且步进值对"填租金"这种场景毫无用处。 */
.v2-card-amt input::-webkit-outer-spin-button,
.v2-card-amt input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.v2-card-amt input[type="number"] { -moz-appearance: textfield; }

/* 主区点按是"展开详情"，输入框区域要豁免，否则点进输入框
   会顺手把卡片展开（手机上表现为键盘刚起来卡片就弹开）。 */
.v2-card-amt { cursor: text; }


/* ══════════════════════════════════════════════════════════════════════
   经营日登记表卡片：详情区可编辑字段（2026-10-01 店主要求）
   ══════════════════════════════════════════════════════════════════════
   店主原话：「经营登记表里面的数据需要支持点击手动输入修改」。
   手机上原表格被 CSS 收起，卡片是唯一界面，而详情区原本被
   cellInnerHtml 剥成了纯文本 —— 一格都改不了。现在把与桌面表格
   口径一致的 27 个 num/txt 列渲染成输入框（app.js 的 bizlogFieldEdit）。

   ⚠ 视觉上必须让"能改"和"不能改"一眼分得清，否则店主会在灰色的
     自动算格子上反复点、以为坏了：
       · 能改   .v2-cr-ed        白底 + 边框，像输入框
       · 不能改 .bizlog-cal      灰底（沿用表格里的既有样式）
     这与桌面表格的观感完全一致，两套界面不用重新学。
   ══════════════════════════════════════════════════════════════════════ */
.v2-cr-ed {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    margin-top: 2px;
    padding: 5px 7px;
    border: 1px solid var(--ds-border);
    border-radius: 8px;
    background: var(--ds-surface);
    color: var(--ds-text);
    font-size: var(--ds-fs-sm);
    font-weight: 600;
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.v2-cr-ed.txt { text-align: left; }
.v2-cr-ed:focus {
    outline: none;
    border-color: var(--ds-primary);
    box-shadow: 0 0 0 2px var(--ds-primary-soft, rgba(255, 122, 69, .18));
}
.v2-cr-ed::placeholder { color: var(--ds-text-3); font-weight: 400; }

/* 去掉 number 的上下箭头：手机上只有 3~4px 宽，误触概率极高，
   而且"步进"对填营业额这种场景毫无用处。 */
.v2-cr-ed::-webkit-outer-spin-button,
.v2-cr-ed::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.v2-cr-ed[type="number"] { -moz-appearance: textfield; }

/* 详情区里"自动算"的只读格：沿用表格的灰底，但把高度和内边距
   拉到跟输入框一致，避免一行里白框和灰块高低参差。 */
.v2-cr-field-val > .bizlog-cal {
    width: 100%;
    box-sizing: border-box;
    margin-top: 2px;
    padding: 5px 7px;
    border: 1px solid transparent;
    border-radius: 8px;
    font-size: var(--ds-fs-sm);
    font-weight: 600;
}


/* ══════════════════════════════════════════════════════════════════════
   15. 导航与顶栏 SVG 图标尺寸（V2.0 图标 SVG 化配套）
   ══════════════════════════════════════════════════════════════════════
   背景：原有的 .nav-icon 是 `font-size: Npx` 驱动 —— 因为里面装的是
   emoji，emoji 是**文字**，字号决定大小。换成 SVG 之后字号线不再生效，
   必须显式给 width/height，否则 SVG 会退化成默认的 300×150。

   尺寸取值：1.18em。为什么不是 1em：
     emoji 是彩色实心块，SVG 是线性描边（内部中空），同样的包围盒下
     线性图标"看起来"小 15%~20%。1.18em 是上一轮实测出的视觉等效系数
     （几何等效 1.0 会让线性图标明显偏小）。

   行盒高度：这几个位置的行盒由显式 line-height 决定（1.1~1.2），
   1.18em 落在其内不会顶高行盒 —— 这也是选 1.18 而不是 1.3 的原因。
   ══════════════════════════════════════════════════════════════════════ */
.nav-icon,
.brand-icon,
.btn-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.18em;
    height: 1.18em;
    vertical-align: middle;
    flex: none;
}
.nav-icon svg,
.brand-icon svg,
.btn-ico svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* 顶栏描边按钮里的图标：按钮是 inline-flex，不参与行盒计算，
   所以这里可以放心用 1.18em */
.btn-ico {
    margin-right: 5px;
    vertical-align: -0.15em;
}
/* 只读徽章里的锁：跟文字同高即可 */
#readonlyBadge .nav-icon,
#readonlyBadge svg {
    width: 1em;
    height: 1em;
    vertical-align: -0.12em;
}
/* brand-edit 是纯图标按钮，居中即可 */
.brand-edit svg {
    display: block;
    width: 1em;
    height: 1em;
}
.brand-edit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* 侧栏图标略大一点：侧栏文字是 14px，图标需要更醒目 */
.side-btn .nav-icon { width: 1.2em; height: 1.2em; }

/* ── 15b. 第二批 SVG 化的配套：行内小按钮 / 抽屉标题 / 折叠箭头 ──
   和 .nav-icon 同样的道理：这些都是从 emoji 换过来的，
   原来靠 `font-size: 13px` 控制大小，换 SVG 后字号线失效，
   不显式给尺寸就会退化成 300×150 的怪物。 */
.btn-edit svg,
.btn-del svg {
    display: block;
    width: 15px;
    height: 15px;
    /* 线性图标比同包围盒的 emoji 视觉上小一圈，给一点点补偿 */
    transform: scale(1.06);
}
/* 图标按钮去掉 emoji 自带的字距，让点击热区贴紧图标 */
.btn-edit,
.btn-del {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.btn-del:hover { color: var(--ds-danger); background: var(--ds-danger-soft); }
.btn-edit:hover { color: var(--ds-primary); background: var(--ds-primary-soft); }

/* 抽屉 / 面板标题里的图标 */
.ttl-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.1em;
    height: 1.1em;
    margin-right: 6px;
    vertical-align: -0.14em;
    color: var(--ds-primary);
    flex: none;
}
.ttl-ico svg { display: block; width: 100%; height: 100%; }

/* ══════════════════════════════════════════════════════════════════════
   第 17 组：图标 SVG 化的配套尺寸（2026-10-01）
   ──────────────────────────────────────────────────────────────────────
   这一组都是同一个毛病的收尾：原来靠 `font-size: 27px` 之类的属性
   去撑一个 emoji 字符，换成 <svg> 之后 font-size 完全不起作用，
   不给 width/height 的话 SVG 会退回规范默认的 300×150，把整块布局撑爆。
   凡是"原来是 emoji 的位置"，都要在这里补一条显式尺寸。
   ══════════════════════════════════════════════════════════════════════ */

/* 驾驶舱 AI 卡片的大图标（原 font-size: 25px 的 📷） */
.ck-ai-icon svg { display: block; width: 25px; height: 25px; color: var(--ds-primary); }

/* ⚠ 只读徽章的锁**不在这里**定义尺寸：上面 6737-6743 已经有一条
   `#readonlyBadge svg { width:1em; height:1em }` 了（原来就为 🔒 写的，
   对 SVG 同样成立）。再写一条是重复规则，只会让"到底哪条生效"变模糊。 */

/* 平台下拉 option 里去过 emoji 之后，图例改用 CSS 画的圆点。
   这样图例的圆和 .tab-dot 的圆是同一套尺寸，不管用户用哪个字体。 */
.tab-dot {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    margin-right: 5px;
    vertical-align: 0;
    flex: none;
}
.tab-dot-collect { background: #ffb800; }
.tab-dot-checkin { background: #22a7f0; }

/* 数据健康清单的状态灯（原 🟢/🔴）。
   用 CSS 画而不是 SVG：这里只需要一个纯色实心圆，
   画圆比塞一个 <svg> 省 DOM，而且能直接吃到 token 变量。 */
.ds-lamp {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    flex: none;
}
.ds-lamp.is-ok  { background: var(--ds-success); }
.ds-lamp.is-bad { background: var(--ds-danger); }

/* 按钮里的下拉箭头（营收工具栏的「设置 ▾」「工具 ▾」）。
   原来是文本字符 ▾，跟旁边的 SVG 图标并排时两个毛病都占全了：
     · 宽度不可控 —— ▾ 占的宽度由字体决定，同一个工具栏里
       「识别」（无箭头）和「设置 ▾」（有箭头）宽度差不是一个整数；
     · 基线不齐 —— 字符的上沿跟着字形走，跟 1.18em 的 SVG 图标
       对不齐，整排按钮看着"高高低低"。
   换成 SVG 之后两者都能精确控制。
   尺寸给 1em 而不是 1.18em：它是**从属**于前面主图标的修饰符，
   不该跟主图标一样大。 */
.btn-caret {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1em;
    height: 1em;
    margin-left: 2px;
    flex: none;
    opacity: .7;
    transition: transform var(--t-fast) var(--ease-soft);
}
.btn-caret svg { display: block; width: 100%; height: 100%; }
/* 菜单展开时箭头翻转 —— 给"点开了"一个明确的视觉确认。
   ⚠ 这里必须是 `.bizlog-menu.open`（不是 .is-open）：
     blMenuToggle 里加的是 `pop.parentNode.classList.add('open')`，
     父节点就是 .bizlog-menu。写成 .is-open 的话 class 永远加不上，
     箭头一动不动 —— 而且不会报错，纯静默失效。 */
.bizlog-menu.open .btn-caret { transform: rotate(180deg); opacity: 1; }

/* 展开箭头：原来 <span class="acc-arrow">▸</span> 用 font-size 控大小，
   且靠 rotate(90deg) 表示展开态。svg 版同宽高 + 同旋转，行为不变。 */
.acc-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    flex: none;
}
.acc-arrow svg {
    display: block;
    width: 100%;
    height: 100%;
    transition: transform var(--t-base) var(--ease-out);
}
.daily-date-panel.open .acc-arrow svg { transform: rotate(90deg); }
@media (max-width: 480px) {
    .btn-edit svg, .btn-del svg { width: 14px; height: 14px; }
}

/* ── 15c. innerHTML 拼装位置的图标尺寸 ──
   .store-city / .hints-ico / .file-item 里的 emoji 也是靠 font-size
   定大小的，换 SVG 后同样要显式给宽高。统一一个"行内跟随字号"的
   写法：1em 宽高 + 轻微垂直微调，这样不管外层字号怎么变都不会错位。 */
.store-city,
.hints-ico,
.file-item,
.bizlog-city {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.store-city svg,
.hints-ico svg,
.file-item svg,
.bizlog-city svg,
.hint svg {
    display: block;
    width: 1.05em;
    height: 1.05em;
    flex: none;
    vertical-align: -0.14em;
}
/* 提示语里的图标（识别完成、警告等）。
   ⚠ 不要给 .hint 加 display:inline-flex —— 它是 <p> 段落，
   改成 inline-flex 会收缩成一行宽，长文案不再换行、溢出容器。
   只调里面 svg 的尺寸和基线即可。 */
.hint svg {
    display: inline-block;
    width: 1.05em;
    height: 1.05em;
    vertical-align: -0.14em;
}

/* ── 16d. 扫描画面接入上传弹窗（V2.0 识别流程） ──
   接入方式：在 #uploadProgress **内部**追加 .v2-scan-holder。
   原有 spinner + 文案一字未动（改的只是容器允许换行），
   这样 renderRecognizeProgress 的钩子能在宿主里 querySelector 到它。 */
#uploadProgress {
    /* 原来是 display:flex 横向排；加了扫描块之后要允许折行，
       否则 spinner、文案、扫描画面会挤在一行里 */
    flex-wrap: wrap;
}
.v2-scan-holder {
    flex: 0 0 100%;
    width: 100%;
    margin-top: var(--ds-s2);
    animation: v2FadeInUp var(--t-slow) var(--ease-out) both;
}
/* V2.1：细进度条把「阶段 + 进度 N/4」表达得很清楚了，
   左侧那个转圈 spinner 属于重复信息、还抢视线，去掉。
   #uploadProgressText（"正在批量识别解析 3 个文件…"）**保留** ——
   它带的是「几个文件」这种阶段信息里没有的内容。

   布局交代：.v2-scan-holder 是 flex:0 0 100% 独占一整行、排在最后，
   #uploadProgressText 于是与已隐藏的 spinner 同处第一行，
   视觉上就是「文件数」在上、进度条在下，两层信息不打架。
   用 :has() 限定「仅在识别进行中」，识别结束/收起后 CSS 不再介入，
   原有 JS 行为一字未动。 */
#uploadProgress:has(.v2-scan-wrap) > .spinner {
    display: none;
}
/* 「正在批量识别解析 3 个文件…」这行也收起来 —— 它说的
   「正在识别」状态行已经表达，文件张数 .rt-prog 里也有（"0 / 3"），
   留着就是同一件事说第三遍。
   元素保留、只做视觉隐藏，JS 照常往里写文本，不动既有逻辑。 */
#uploadProgress:has(.v2-scan-wrap) > #uploadProgressText {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
@keyframes v2FadeInUp {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* 识别完成：扫描线停下并淡出，整体降一档存在感，
   让用户的注意力转到"下一步是预览结果"上 */
.v2-scan-wrap.is-done .v2-scan-line { animation: none; opacity: 0; }
.v2-scan-wrap.is-done .v2-scan { border-color: var(--ds-success); }
.v2-scan-wrap.is-done .v2-scan::before,
.v2-scan-wrap.is-done .v2-scan::after { border-color: var(--ds-success); }
.v2-scan-wrap.is-done .v2-scan-title { color: var(--ds-success); }

/* 识别失败：扫描线变红，最后一步标红叉 */
.v2-scan-wrap.is-error .v2-scan-line { animation: none; opacity: 0; }
.v2-scan-wrap.is-error .v2-scan { border-color: var(--ds-danger); }
.v2-scan-wrap.is-error .v2-scan::before,
.v2-scan-wrap.is-error .v2-scan::after { border-color: var(--ds-danger); }
.v2-scan-wrap.is-error .v2-scan-title { color: var(--ds-danger); }
.v2-stage.error .v2-stage-dot { background: var(--ds-danger-soft); color: var(--ds-danger); }

/* 窄屏压缩：扫描框小一圈，四阶段字号降一档 */
@media (max-width: 480px) {
    .v2-scan { width: 132px; height: 132px; }
    .v2-scan-holder .v2-scan-stages { font-size: 12px; }
    .v2-scan-holder { margin-top: var(--ds-s2); }
}

/* ── 16e. 识别状态行（V2.1，2026-10-01）────────────────────────────
   替换上面 6351-6460 那套「168px 大圆框 + 扫描线 + 底纹 + 四阶段列表」。
   旧规则**一条没删** —— .v2-scan / .v2-stage 若别处还在引用，删了会静默
   掉样式；新画面类名全部换成新的（.v2-pg-*），旧类不再被生成，自然失效。

   分工（避免和 .rt-prog 那条真实进度条重复）：
     .rt-prog      → 进度条 + 百分比 + 已用时间（唯一进度可视化）
     .v2-scan-wrap → 一行状态：第几步 / 这步在干什么（补充语义）
   高度：原扫描块 391px → 现在 22px。 */
.v2-scan-wrap {
    width: 100%;
    padding: 0;
}
.v2-pg-head {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.v2-scan-title {
    font-size: var(--ds-fs-sm);
    font-weight: 600;
    color: var(--ds-text);
    white-space: nowrap;
    flex: none;
}
.v2-scan-sub {
    font-size: var(--ds-fs-xs);
    color: var(--ds-text-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
/* 右侧：步骤圆点 + 第几步 */
.v2-pg-count {
    flex: none;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--ds-fs-xs);
    color: var(--ds-text-3);
    font-variant-numeric: tabular-nums;
}
.v2-pg-count b { color: var(--ds-primary); font-weight: 600; }
.v2-pg-count i { font-style: normal; }
.v2-pg-dots {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}
.v2-pg-dot {
    width: 5px; height: 5px;
    border-radius: 50%;
    background: var(--ds-border-strong, rgba(31,42,36,.18));
    transition: background var(--t-fast) var(--ease-soft),
                transform var(--t-fast) var(--ease-soft);
}
.v2-pg-dot.is-done { background: var(--ds-primary); }
.v2-pg-dot.is-cur {
    background: var(--ds-primary);
    transform: scale(1.45);
    box-shadow: 0 0 0 3px rgba(255,107,53,.16);
}
.v2-pg-dot.is-err { background: var(--ds-danger); }

/* 完成 / 失败：状态行整体染色，与 .rt-prog 的收尾色一致 */
.v2-scan-wrap.is-done .v2-scan-title,
.v2-scan-wrap.is-done .v2-pg-count b { color: var(--ds-success); }
.v2-scan-wrap.is-done .v2-pg-dot { background: var(--ds-success); }
.v2-scan-wrap.is-error .v2-scan-title,
.v2-scan-wrap.is-error .v2-pg-count b { color: var(--ds-danger); }
.v2-scan-wrap.is-error .v2-pg-dot { background: var(--ds-danger); }

/* 减少动画：圆点不做放大/光晕 */
@media (prefers-reduced-motion: reduce) {
    .v2-pg-dot { transition: none; }
    .v2-pg-dot.is-cur { transform: none; box-shadow: none; }
    .v2-scan-line { animation: none; }
}
