/* ==========================================================================
   门店预约日历 · 前端模块：基础 + 洗美样式
   本文件由 门店排班日历.html 拆出（v0609-248），逻辑与原文件逐行一致，未做改写。
   加载方式：经典 <script>（非 module），顺序 shared → grooming → foster → mobile → boot，
            五个文件共享同一个脚本作用域。
   ⚠️ 正因为共享作用域：**本页所有顶层 const/let 都是全局可见的**（原来是 IIFE 私有）。
      在此新增顶层声明前，请先在所有 js/*.js 里 grep 一遍名字，避免重复声明。
   ========================================================================== */
  :root {
    --bg: #f4f6fb;
    --panel: #ffffff;
    --border: #e5e8f0;
    --text: #1f2430;
    --muted: #7a8496;
    --accent: #4f6ef2;
    --accent-dark: #3a55d6;
    --danger: #e5484d;
    --shadow: 0 6px 24px rgba(31, 36, 48, .08);
    --radius: 12px;
    /* v0609-30：日视图美容师列最小宽 = 列体(.sd-col) 与 表头列(.sd-colhead) 共用同一个
       变量。此前表头列由 JS 内联写死 min-width:150px，而窄屏媒体查询只把 .sd-col 降到
       96px → 手机上第 2 列起表头与列体逐列错位（每列差 54px，横滚后越拉越偏） */
    --sd-colmin: 150px;
    /* v0609-310：iOS 26/27 系统 scrollEdgeEffect 无 CSS 开关，只能把内容让出雾带；
       设 0 即撤回。只在手机宽度（≤720px）给顶部让位，见「移动端适配」的 header
       与 login.html 内联样式 */
    --glass-gap-top: 0px;   /* 用户 2026-10-28：40px 让位后顶栏太低，不接受 → 默认归 0（机制保留，可改 20/32 试）*/
  }
  * { box-sizing: border-box; }
  /* ===== v0609-161（用户）：字体改「阿里巴巴普惠体 3.0」 =====
     · 先用 local()：装了该字体的设备（本机 Mac 就是）**直接命中、零下载**；
     · 没装的（同事手机）才去取 woff2 子集（GB2312 全字符 + ASCII + 常用标点，
       55-Regular 0.89MB / 85-Bold 0.91MB；服务器一年强缓存 → 只首次加载一次）；
     · font-display: swap → 字体没到也先正常显示系统字，不白屏。 */
  @font-face {
    font-family: "PuHuiTi3";
    src: local("阿里巴巴普惠体 3.0"), local("Alibaba PuHuiTi 3.0"),
         local("AlibabaPuHuiTi_3_55_Regular"),
         url("/fonts/puhuiti3-55-Regular.woff2") format("woff2");
    font-weight: 400; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: "PuHuiTi3";
    src: local("阿里巴巴普惠体 3.0"), local("Alibaba PuHuiTi 3.0"),
         local("AlibabaPuHuiTi_3_85_Bold"),
         url("/fonts/puhuiti3-85-Bold.woff2") format("woff2");
    font-weight: 700; font-style: normal; font-display: swap;
  }
  html, body { margin: 0; height: 100%; }
  /* v0609-195（用户「键盘离文本框有点远，拉近一些」）：底部安全区（Home 指示条那 34px）
     在**键盘弹起时是被键盘盖住的**，此时再叠加它就会多出一段空白 → 抽成变量，键盘弹起归零。
     --kb-inset 由文件尾部脚本按 visualViewport 写入；html.kb-up 同源切换。 */
  :root { --kb-safe: env(safe-area-inset-bottom, 0px); }
  html.kb-up { --kb-safe: 0px; }
  /* v0609-190a/b/c（用户「页面上滚之后文本框和键盘间隔稍微有点大，缩小一些」）：
     键盘弹起时那段 34px 底部安全区（Home 指示条）是被键盘盖住的，留着就是多出来的空白。
     原来只在"键盘高度量到了"（kb-up）时才归零；而在**页面上滚那种状态**下，iOS 会把 innerHeight
     一起缩、又给出 offsetTop，导致算出来的键盘高是 0 → kb-up 不成立 → 那 34px 被留着 →
     输入栏离键盘 = 4 + 34 = 38px（就是用户看到的偏大）。
     ⚠️ v0609-190b 修正：**只对输入栏生效，绝不改 `--kb-safe` 本身**。
        之前那条 `html.kb-focus{--kb-safe:0}` 是"全局归零"，而 `--kb-safe` 还被 #main 的
        让位内边距（68+34）、弹窗、时间滚轮共用 → 一聚焦就让**日历内容区凭空长高 34px**，
        看起来就是"页面又沉了一下" ✗。现在只覆盖 .bk-bar 自己的底距，其它一处不动。 */
  /* v0609-185：浏览器模式下用动态视口高度（地址栏收起/展开时 100% 会偏高，
     底部固定元素会被地址栏挡住）；PWA 独立模式 dvh 等于屏幕高，等价无害 */
  @supports (height: 100dvh) { html, body { height: 100dvh; } }
  body {
    font-family: "PuHuiTi3", "阿里巴巴普惠体 3.0", "Alibaba PuHuiTi 3.0",
                 "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, sans-serif;
    background: var(--bg);
    color: var(--text);
    display: flex;
    flex-direction: column;
    /* 禁用双击缩放，避免与双击交互冲突、以及放大后无法缩回 */
    touch-action: manipulation;
    /* v0609-28：页面永不滚动——滚动一律发生在各视图内部容器（日视图 .sd-body /
       FC .fc-scrollgrid 等），否则表格会跟着页面级滚动整体上下乱移（手机端「表格
       不固定」根因）；overscroll 链已被内部容器 overscroll-behavior:contain 阻断 */
    overflow: hidden;
  }
  header {
    /* v0609-22：页面内标题行删除（与浏览器标签重复），header 只剩一行工具条
       （绿点 + 选择门店/美容师 + 新建…），padding 相应压缩
       v0609-185：顶部同样跟安全区（加了 viewport-fit=cover 后，独立模式内容会铺到
       刘海/状态栏区域 → 不补内边距顶栏会被压住；Safari 里该值为 0，无副作用） */
    display: flex; align-items: center;
    padding: calc(10px + env(safe-area-inset-top, 0px)) 20px 10px;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
  }
  .status-badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px;
                  color: var(--muted); font-weight: 600; flex: 0 0 auto; }
  .status-badge .lamp { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
  .status-badge.online .lamp { background: #30a46c; box-shadow: 0 0 0 3px rgba(48,164,108,.18); }
  .status-badge.offline .lamp { background: #f59e0b; }
  /* v0609-235：手机端「门店胶囊」内的状态灯（真实 flex 子项，参与布局）。
     宽屏不用它（用 badge 自带的 .lamp），所以默认隐藏，只在窄屏媒体查询里放开。 */
  .lamp-dot { display: none; }
  .toolbar-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: 1; }
  .field-group { display: flex; align-items: center; gap: 6px; }
  .field-group label { font-size: 12px; color: var(--muted); }
  select, input[type="text"], input[type="tel"], input[type="time"],
  input[type="datetime-local"], input[type="date"], input[type="number"] {
    font: inherit; padding: 6px 10px; border: 1px solid var(--border);
    border-radius: 8px; background: #fff; color: var(--text); outline: none;
  }
  select:focus, input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79,110,242,.15); }
  .btn {
    font: inherit; font-weight: 600; cursor: pointer; border: none;
    border-radius: 8px; padding: 7px 14px; transition: background .15s, transform .05s;
  }
  .btn:active { transform: translateY(1px); }
  .btn-primary { background: var(--accent); color: #fff; }
  .btn-primary:hover { background: var(--accent-dark); }
  .btn-ghost { background: #fff; color: var(--text); border: 1px solid var(--border); }
  .btn-ghost:hover { background: #f0f2f8; }
  .btn-danger { background: #fff; color: var(--danger); border: 1px solid #f3c1c3; }
  .btn-danger:hover { background: #fdecec; }
  #main { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }
  #main > .view-pane { flex: 1; display: none; min-width: 0; min-height: 0; }
  #main > .view-pane.active { display: block; }
  /* v0609-15：月/周/日共用同一条共享工具栏（位于两个视图 pane 之外，切换视图不换条）
     v0609-30：改三列 grid（1fr | auto | 1fr）——「日」钮隐藏后左右两列等宽，日期标题
     严格视觉居中（用户 9/8：把日期居中）。子元素用显式 grid-column 定位，故 .sd-views
     被 display:none 抽掉后日期/翻页钮仍各就各位（不会自动往左挤） */
  .view-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
              gap: 8px; flex: 0 0 auto; padding: 14px 20px 2px; }
  #calendar, #staffDayWrap { height: 100%; }
  #calendar { padding: 6px 20px 16px; }
  #staffDayWrap { display: flex; flex-direction: column; padding: 6px 20px 14px; }
  /* v0609-30：月/周视图已去掉、只剩日视图 → 「日」按钮无意义，整组隐藏（用户 9/8：
     日视图的日先隐藏）。按钮元素与 setVbTab 逻辑保留，日后恢复视图只需删掉 display:none */
  .sd-views { display: none; gap: 4px; grid-column: 1; justify-self: start; }
  .sd-views .btn { padding: 6px 12px; }
  .sd-views .btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
  /* v0609-21：中间日期标题可点选弹原生日历（日期输入透明铺满整块接收点击），
     右侧独立日期输入已移除（与中间日期重复，用户 9/7 反馈） */
  .view-bar .sd-date { grid-column: 2; justify-self: center; text-align: center;
                       min-width: 130px; position: relative; cursor: pointer; }
  .view-bar .sd-date #sdDate { display: block; position: relative; z-index: 1;
                               font-size: 16px; font-weight: 700; white-space: nowrap;
                               overflow: hidden; text-overflow: ellipsis; padding: 0 2px;
                               pointer-events: none; }
  .view-bar .sd-date #sdPick { position: absolute; inset: 0; width: 100%; height: 100%;
                               opacity: 0; border: 0; padding: 0; background: transparent;
                               cursor: pointer; z-index: 3; min-width: 0; }
  .view-bar .sd-date #sdPick:focus, .view-bar .sd-date #sdPick:focus-visible {
                               outline: none; box-shadow: none; }
  .view-bar .sd-week { font-size: 12px; color: var(--muted); margin-left: 6px; }
  /* v0609-30：‹/› 箭头加大（用户 9/8：现在太小了不太好点）——桌面 ≥46×38px 点击区、
     箭头字号 22px；「今天」保持文字钮尺寸 */
  .sd-vnav { display: flex; align-items: center; gap: 6px; grid-column: 3; justify-self: end; }
  .sd-vnav .btn { padding: 6px 12px; min-height: 38px; }
  .sd-vnav #vbPrev, .sd-vnav #vbNext { min-width: 46px; padding: 4px 10px;
                                       font-size: 22px; font-weight: 700; line-height: 1; }
  .sd-hintbar { margin: 2px 2px 8px; font-size: 11px; }
  /* v0609-28 起日视图布局演进（表头行 = 滚动容器外的独立固定层，横向由 JS 同步列体
     scrollLeft，纵向永不动）。
     v0609-34（终版架构）：表体 = 唯一的双向滚动容器 #sdBody（overflow:auto），时间列是
     表体滚动内容里的 position:sticky 左列——纵向与卡片列同层原生一体滚动（零帧差、
     滚轮/手指落在时间列上同样能滚 = 无手势死区），横向被 sticky 钉住不跟手、左右仍可滑。
     架构取舍实证（2026-09-08 CDP）：v0609-33 嵌套容器（.sd-yscroll 外纵 + #sdBody 内横）
     在 Chromium 输入管线里，纵向手势一旦落在「内层横向滚动容器」上即被吞掉不再向祖先
     纵滚容器上抛——列体上滚轮/触屏滑动全部失效（display:none 后穿透证明拦截者就是
     sdBody），只有非滚动容器（时间列）能滚 = 依旧割裂；v0609-29~32 的容器外固定层 +
     JS translateY 同步则有两帧差且时间列是手势死区。sticky 左列同时消除这两个问题。 */
  .sd-body { flex: 0 1 auto; min-height: 0; overflow: auto; overscroll-behavior: none;
             touch-action: pan-x pan-y;   /* v0609-101：显式声明双向可滚（避免继承歧义） */
             background: #fff; border: 1px solid var(--border);
             border-radius: 10px; }   /* v0609-38：表头行已移入内容（sticky top），顶框线/上圆角归 #sdBody */
  /* v0609-31 几何：flex:0 1 auto —— 高视口下表体盒高=内容 576px（09:00-21:00），下框线
     正好落在 21:00，无死白带；视口比内容矮时收缩、由表体内部滚动（滚动条在表体右侧）。
     overscroll-behavior:none = 双向掐橡皮筋（v0609-30 只掐了 x） */
  .sd-body.sd-headless { border-top: 1px solid var(--border); border-radius: 10px;
                         min-height: 220px; }   /* v0609-31：空态（无美容师列）不塌成 0 */
  .sd-body.sd-emptybody { display: flex; align-items: center; justify-content: center; }
  /* v0609-34：时间列 = 表体滚动内容里的 sticky 左列（.sd-cols 第一个 flex 子项）——
     纵向随整表原生滚动（与卡片逐像素一体），横向滚时被 left:0 钉住。z-index 高于列体
     内容（事件卡/现在线横贯时被时间列盖住左端）。高度由 24 格内容撑起=576
     v0609-39：z-index 3→50 —— 第 9 报「卡片覆盖掉时间」（真机像素实证：选中卡
     z-index:3 与时间列 z-index:3 平级、DOM 靠后 → 横向滚到 scrollLeft≈187 时整张卡
     画在时间列标签区上方，14:00 标签被黄条/卡灰盖住）。50 > 选中卡 3/拖动卡 8/空白
     选中 2 的一切卡片态，时间列恒盖卡片；现红线 .sd-nowline 抬到 55 仍横贯时间列 */
  .sd-timecol { position: sticky; left: 0; z-index: 50; flex: 0 0 86px;
                background: #fff; border-right: 1px solid #e5e8f0;
                user-select: none; -webkit-user-select: none; }
  /* v0609-32：时间列格子补上与列体同相位的 30min 交替色带（:nth-child(odd)=第 1/3/5…
     格 = 整点格，着色 #eef1f8，与各列 .sd-colbody 顶部 repeating-linear-gradient
     的 0-24px 首段色完全一致）。此前时间列整条纯白、右边列体有浅色带 → 左右纹理在
     列边界处断裂（像素采样实证：整点行 时间列(255,255,255) vs 列体(238,241,248)），
     用户看就是「时间列和卡片列分割成两部分」；补齐后色带横贯整行，左右读作一张表 */
  .sd-timecell { font-size: 12px; color: #1f2430; text-align: right; padding: 1px 8px 0 0;
                 border-top: 1px solid transparent; }   /* v0609-39：时间字 11px 灰 #7a8496 → 12px 黑 #1f2430（用户「稍大一号黑色字体」） */
  /* v0609-161（用户「空白行的灰色部分再浅一点」）：空白行灰 #eef1f8(238,241,248) → #f7f9fd(247,249,253)。
     真正的空白行色带来自列体的 repeating-linear-gradient（渲染 JS 里那一行），两处色值保持一致。
     ⚠️ 下面这条 :nth-child(odd) 目前**被后面 `.sd-timecell{background:transparent !important}`
     （v0609-93 手机端视觉重构：时间列改整条平色）覆盖、并不生效**——保留同色只为将来若去掉
     那条 !important 时不会出现「时间列与列体色带不同色」的断裂（v0609-32 踩过）。 */
  .sd-timecell:nth-child(odd) { background: #f7f9fd; }
  .sd-grid { display: grid; min-width: 0; position: relative; }
  .sd-grid .sd-left { position: sticky; left: 0; z-index: 5; background: #f7f9fd; }
  /* ===== v0609-38（滚动架构终版）：表头行移入 #sdBody 滚动内容顶层做 sticky top，与列体
       同层原生横滚；syncHeadScroll/alignHeadPad/#sdHeadScroll 整个 JS 同步层删除。
       依据 = v0609-34 sticky 时间列同款实证：任何「滚动容器外固定层 + JS 同步 scrollLeft」
       都有一帧级同步差（iOS 触摸滚动期间 rAF 暂停则整段滞后），表头与卡片列物理上不在
       同一层，「横滑错位」只能缓解、无法根除；移入同层后错位在构造上不可能发生。
       纵向：sticky top:0 把表头钉在滚动视口顶（短视口内滚时不跟走）；橡皮筋露白带也
       结构性消灭——表头就是内容的一部分，拉到顶/底时与列体一起动，中间不存在夹层 ===== */
  .sd-vstack { display: flex; flex-direction: column; width: max-content;
               min-width: 100%; }   /* 表头行 + 列体共用同一几何源 */
  .sd-headrow { position: sticky; top: 0; z-index: 60; display: flex;   /* v0609-39：z 5→60，恒盖一切卡（拖动卡 z8）与时间列 z50 */
                align-items: stretch; flex: none; width: 100%;
                background: #f7f9fd; user-select: none; -webkit-user-select: none;
                box-shadow: 0 1px 0 var(--border); }   /* 表头下缘发丝线 = 与列体的分隔 */
  .sd-headrow .sd-colhead { border-bottom: none; }
  /* v0609-30：角格「时间」必须与 .sd-timecol 严格同宽（86px）。它复用 .sd-colhead 取样式，
     而 .sd-colhead 带 min-width: var(--sd-colmin)（窄屏 96px）会把 inline flex-basis 86px
     顶大 → 角格变 96px、整条表头列轨道右移 10px，与列体逐列错位（实测左缘差恒 10px）。
     故这里显式钉死宽度并解除 min/max-width 约束；
     v0609-38：另加 sticky left 钉在视口左缘，横向滚动时盖住滑过的列头（z=1 > 普通列头） */
  .sd-headrow .sd-corner { flex: 0 0 86px; min-width: 0; max-width: 86px;
                           position: sticky; left: 0; z-index: 1; }
  .sd-colhead { display: flex; align-items: center; justify-content: center; gap: 4px;
                font-weight: 700; font-size: 13px; border-bottom: 1px solid var(--border);
                border-right: 1px solid var(--border); background: #f7f9fd;
                padding: 6px 4px; flex: 1 1 0; min-width: var(--sd-colmin); }
  .sd-colhead .sd-colstore { font-size: 10px; font-weight: 600; color: var(--muted);
                             white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                             max-width: 100%; }
  /* ===== v0609-121 长按列头拖动 = 调整美容师列序 =====
     .can-order 才是可排的列头（选了门店、没做美容师筛选、且不止一列）：
     长按期间给「按下去了」的反馈，拖动时把原列头压暗、并浮一个跟手的幽灵标签。
     注意**不设 touch-action**：列头上照样要能用手指横向滑动表格，锁定交给
     sdTouchLock（武装后 touchmove 被 preventDefault）。 */
  .sd-colhead.can-order { cursor: grab; -webkit-touch-callout: none; }
  .sd-colhead.can-order:active { background: #eef3ff; }
  .sd-colhead.ord-hold { background: #e4ecff !important; box-shadow: inset 0 0 0 2px #4f6ef2;
                         border-radius: 6px; }
  .sd-colhead.ord-src { opacity: .35; }
  .sd-colhead.ord-armed { cursor: grabbing; }
  .sd-colghost { position: fixed; z-index: 9999; pointer-events: none;
                 background: #4f6ef2; color: #fff; font-weight: 700; font-size: 13px;
                 padding: 6px 10px; border-radius: 8px; box-shadow: 0 6px 18px rgba(31,36,48,.28);
                 transform: translate(-50%, -50%); white-space: nowrap; }
  .sd-colmark { position: fixed; z-index: 9998; pointer-events: none; width: 3px;
                background: #4f6ef2; border-radius: 2px; box-shadow: 0 0 0 1px rgba(79,110,242,.25); }
  .sd-ordhint { position: fixed; z-index: 9999; pointer-events: none; left: 50%; bottom: 86px;
                transform: translateX(-50%); background: rgba(31,36,48,.92); color: #fff;
                font-size: 12.5px; font-weight: 600; padding: 7px 12px; border-radius: 999px; }
  /* v0609-91（req 2）：非上班时段斜线底纹——参照宠老板「按天」视图：45° 细斜线、浅灰、
     不抢卡片、不挡任何点按（pointer-events:none，空白格手势照旧）。休息=整列斜线；
     有班次=班次区间之外的部分斜线；排班未知则不画（宁可不画也不谎报休息）。 */
  .sd-hatch { position: absolute; left: 0; right: 0; pointer-events: none; z-index: 0;
    background-image: repeating-linear-gradient(45deg,
      rgba(150,163,190,.22) 0, rgba(150,163,190,.22) 1px, transparent 1px, transparent 7px); }
  /* v0609-89：当天休息的美容师列——列头标「休」（偏红），名字变灰；列仍保留（卡片不丢） */
  .sd-colhead .sd-colstore .sd-off { color: #d05a5a; }
  .sd-colhead.sd-offcol > span:first-child { color: var(--muted); }
  .sd-rowlabel { font-size: 10px; color: var(--muted); text-align: right;
                 padding-right: 8px; border-bottom: 1px solid var(--border);
                 border-right: 1px solid var(--border); position: relative;
                 background: #f7f9fd; user-select: none; }
  .sd-rowlabel b { position: absolute; right: 8px; top: -6px; font-weight: 600;
                   background: #f7f9fd; padding: 0 2px; }
  .sd-cell { border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
             position: relative; }
  .sd-cell:nth-child(even) { background: rgba(79,110,242,.02); }
  .sd-cell.sd-now { background: rgba(229,72,77,.07); }
  .sd-event { position: absolute; left: 3px; right: 3px; border-radius: 2px;
              overflow: hidden; cursor: grab; font-size: 10px; line-height: 1.25;
              box-shadow: 0 2px 6px rgba(31,36,48,.12);
              /* v0609-97（用户问题 1/2）：**卡片不再禁掉滚动**。
                 旧值 touch-action:none 让浏览器完全不处理落在卡片上的手势 → 手指压在
                 卡片上左右滑时表格纹丝不动（用户：「只有点空白处才能左右滑，点在有卡片
                 的地方没办法左右滑」「切换日期后有时候左右滑动页面就卡在那一直不动，
                 要多次上下左右尝试才会动」）。
                 现在改为 **滑动优先**：默认允许浏览器滚动（auto）；拖动/拉边在真正开始时
                 由 JS 调 sdLockScroll(true) 用 window 级非 passive touchmove
                 preventDefault 精确锁住滚动（旧式可靠做法），松手立刻解锁。
                 交互语义 = 用户要的：平时滑＝滚表格 → 点卡片＝选中 → 选中后按住＝拖/拉。 */
              touch-action: auto;
              -webkit-user-select: none; user-select: none; }
              /* v0609-40：整卡圆角 6→2px——黄条贴卡顶且 .sd-event overflow:hidden，
                 外层圆角直接裁出黄条顶角弧（tag 自身已 0px 仍见弧即此因）；
                 4→2→0 全改在 tag 上未触及真源，用户两连报「圆弧还是有点大」 */
  .sd-event.sd-dragging { opacity: .88; cursor: grabbing; z-index: 8;
                          box-shadow: 0 8px 22px rgba(31,36,48,.35); }
  /* v0609-56：触屏换美容师落点确认（z 96：压过时间滚轮 95/弹窗 90，但不挡 statusBadge） */
  .sd-confirm { position: fixed; inset: 0; z-index: 96; background: rgba(20,24,40,.45);
                display: flex; align-items: center; justify-content: center; padding: 24px;
                touch-action: none; }
  .sd-confirm-box { width: 100%; max-width: 340px; background: #fff; border-radius: 14px;
                    padding: 16px 16px 12px; box-shadow: 0 10px 34px rgba(31,36,48,.28);
                    font-size: 14px; }
  .sd-confirm-title { font-weight: 700; font-size: 16px; margin-bottom: 8px; }
  .sd-confirm-pet { font-weight: 700; }
  .sd-confirm-t { font-variant-numeric: tabular-nums; margin-top: 2px; color: #333; }
  .sd-confirm-g { margin-top: 2px; color: #555; }
  .sd-confirm-g b { color: var(--danger, #e5484d); }
  .sd-confirm-acts { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }
  .sd-confirm-acts .sc-btn { border: none; border-radius: 8px; padding: 8px 18px;
                             font-size: 15px; font-weight: 600; cursor: pointer; }
  .sc-no { background: #eef1f7; color: #4a5568; }
  .sc-yes { background: var(--primary, #4f6ef2); color: #fff; }
  .sd-event.sd-resizing { cursor: ns-resize; z-index: 8; }
  /* v0609-19：手柄视觉彻底对齐 FC 周视图——hover/拖动均零视觉（无任何线），
     仅保留 14px 透明热区 + ns-resize 光标示意可拉（v0609-19 曾 hover 现 2px
     细线，用户「鼠标放在上面不要有上下两条线，跟周视图一样」→ 全删） */
  .sd-event .sd-rsz { position: absolute; left: 0; right: 0; bottom: 0; height: 14px;
                      cursor: ns-resize; background: none; border: 0;
                      border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; }
  .sd-event .sd-rsz-top { position: absolute; left: 0; right: 0; top: 0; height: 14px;
                      cursor: ns-resize; background: none; border: 0;
                      border-top-left-radius: 2px; border-top-right-radius: 2px; }
  .sd-event.sd-selected { outline: 2px solid #1f2430; outline-offset: 0; z-index: 3; }
  /* v0609-42：跨美容师拖拽不再整列描边高亮（v0609-41 的整列蓝框太长扎眼，用户
     明确不要）——落点反馈仅靠卡片本身实时跨列预览 + 列头归属，足够清晰 */
  /* v0609-30：日视图卡片四行（服务标签 / 宠物 / 起止时间 / 主人＋电话），字号压紧、
     行距收拢，尽量在 60min（48px）色块内完整显示；电话仍放不下时自动换到下一行
     v0609-39：gap 0→2px（用户「宠物名离黄色标签下沿隔开一些」「宠物名/时间/手机号
     换行间隔统一」——flex column gap 统一 2px，三行等距、黄条与宠物名也隔开） */
  .sd-event .evt-card { font-size: 10px; gap: 2px; }
  /* v0609-40：三行文字左缘与黄标签文字对齐——黄字起点=卡内 x5（.evt-tag padding 0 5px），
     三行原本从 x0 起笔左突 5px（CDP 实测 tagTextL=101 vs petL/whenL/whoL=96）；
     补同值水平 padding 后白字起点==黄字起点，右缘同步收 5px 不顶卡边（flex-wrap 换行
     行/ellipsis 均在 padding 盒内自动生效） */
  .sd-event .evt-pet, .sd-event .evt-when, .sd-event .evt-who,
  .sd-event .evt-svcs, .sd-event .evt-note { padding: 0 5px; }
  .sd-event .evt-phone::before { content: ""; margin-right: 0; }   /* 日视图省掉 📞 图标占位 */
  .sd-event .evt-tag, .sd-event .evt-when, .sd-event .evt-time,
  .sd-event .evt-svcs { font-size: 9px; }
  .sd-event .evt-pet, .sd-event .evt-when, .sd-event .evt-svcs { line-height: 1.2; }
  .sd-empty { padding: 30px; text-align: center; color: var(--muted); font-size: 13px; }
  .sd-nowline { position: absolute; left: 0; right: 0; border-top: 2px solid var(--danger);
                z-index: 55; pointer-events: none; }   /* v0609-39：z 4→55，保持横贯时间列(>50)但低于表头行(60) */
  /* ===== v0609-26 日视图空白格手势（单击选时段/长按拖选）＋ 周视图重叠折叠 + 触摸滚动稳定；
         v0609-27 单击/拖选松开 → 保留高亮并浮「＋ 新建预约」钮，点钮才弹新建窗 ===== */
  /* 需求1：日视图拖动时表格不跟着乱移——滚动容器禁回弹外传；折叠/选中期间由 JS 锁滚
     v0609-30：横向橡皮筋一并禁掉——用户 9/8 反馈「左右拉可以一直拉，会出现空白部分」；
     内容未超宽时 JS 再加 .sd-nox 彻底禁横滚。
     v0609-37：⚠️ 本规则曾写成 `overscroll-behavior: none contain`（=x:none y:contain），
     后写覆盖了 120 行的双向 none → 纵向实际 = contain（允许容器自身橡皮筋）——移动端
     拉到顶/底时内容被拉离固定表头行露白带，CSS 侧从未真正禁掉（v0609-35 的 touchmove
     拦截只覆盖手指在屏的拖拽段，快速下滑的惯性段没有 touchmove 事件可拦）。
     双向 none：纵向一并钉死，拖拽段由 sdRubberEdge 拦、惯性段由本 CSS 兜底。 */
  .sd-body { overscroll-behavior: none; }
  .sd-body.sd-nox { overflow-x: hidden; }
  .sd-col { flex: 1 1 0; min-width: var(--sd-colmin); position: relative; }
  .sd-blanksel { position: absolute; left: 2px; right: 2px; z-index: 2;
                 background: rgba(79,110,242,.14); border: 1px dashed var(--accent);
                 border-radius: 6px; pointer-events: none; }
  /* 周视图折叠主卡上的「共 n 条」角标 */
  .fc-fold-chip { position: absolute; top: 2px; right: 2px; z-index: 6;
                  background: rgba(31,36,48,.78); color: #fff; font-size: 9px; font-weight: 700;
                  padding: 1px 6px; border-radius: 8px; line-height: 14px;
                  pointer-events: none;   /* 点角标=点到下方主卡 → 弹明细列表 */
                  white-space: nowrap; }
  /* 空白格：长按/拖选不弹系统菜单与文本选择（滚动/拖选手势由 JS 分派） */
  .sd-colbody { user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
  .fc-folded { box-shadow: 0 3px 12px rgba(31,36,48,.28); }
  /* 折叠明细弹层 */
  .fold-mask { position: fixed; inset: 0; background: rgba(20,24,40,.16); z-index: 46;
               display: flex; align-items: center; justify-content: center; padding: 24px; }
  .fold-pop { background: var(--panel); border-radius: 14px; box-shadow: var(--shadow);
              width: 430px; max-width: 100%; max-height: 78vh; overflow: auto; padding: 16px 18px; }
  .fold-pop h3 { margin: 0; font-size: 15px; display: flex; align-items: center; gap: 8px; }
  .fold-pop h3 .fp-close { margin-left: auto; cursor: pointer; color: var(--muted); font-size: 16px;
                           font-weight: 400; padding: 0 4px; }
  .fold-pop .fp-sub { font-size: 11px; color: var(--muted); margin: 4px 0 10px; }
  .fold-row { display: flex; align-items: center; gap: 8px; padding: 7px 8px; margin: 0 -8px;
              border-radius: 8px; cursor: pointer; font-size: 12px; }
  .fold-row:hover { background: #f0f2f8; }
  .fold-row.active { background: #e8edff; }
  .fold-row .fr-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
  .fold-row .fr-time { font-weight: 700; color: var(--accent-dark); flex: 0 0 auto;
                       font-size: 11px; font-variant-numeric: tabular-nums; }
  .fold-row .fr-main { flex: 1; min-width: 0; }
  .fold-row .fr-main b { display: block; overflow: hidden; text-overflow: ellipsis;
                         white-space: nowrap; font-size: 12px; font-weight: 600; }
  .fold-row .fr-main span { display: block; font-size: 10px; color: var(--muted);
                            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .fold-row .fr-tag { flex: 0 0 auto; background: #f7b500; color: #3a2e00; font-weight: 700;
                      font-size: 9px; padding: 1px 5px; border-radius: 4px; max-width: 96px;
                      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .fold-row .fr-star { flex: 0 0 auto; font-size: 10px; color: var(--accent-dark); font-weight: 700; }
  /* v0609-36：手机窄屏「一屏 2 名美容师」——列最小宽由 JS 按视口算成 (列区宽-时间列)/2，
     3 名起自然横向溢出靠横滑（不再 3 列挤窄）；行内变量覆盖此 96px 兜底 */
  @media (max-width: 760px) { :root { --sd-colmin: 96px; } }
  @media (max-width: 720px) {
    .view-bar { padding: 10px 8px 0; }
    #calendar { padding: 4px 6px 10px; }
    #staffDayWrap { padding: 4px 6px 10px; }
  }
  #calendar { position: relative; }
  .fc-newbtn {
    position: absolute; z-index: 30; background: #4f6ef2; color: #fff;
    border: none; border-radius: 7px; padding: 7px 12px; font-size: 13px;
    font-weight: 600; cursor: pointer; box-shadow: 0 3px 10px rgba(0,0,0,.3);
    transform: translate(-50%, -50%); white-space: nowrap; line-height: 1;
    /* v0609-27：待确认钮——按压不选中文字/不触发系统长按菜单；
       v0609-36：钮高压到 < 单个时间格（桌面 27px、移动 ~21px < 36px 格），
       不再「大过时间格子」 */
    user-select: none; -webkit-user-select: none; touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
  }
  .fc-newbtn:active { background: #3a55d6; }
  @media (max-width: 760px) { .fc-newbtn { padding: 5px 10px; font-size: 12px; } }
  .fc { --fc-border-color: var(--border); --fc-page-bg-color: var(--bg);
        --fc-today-bg-color: rgba(79,110,242,.06); }
  .fc .fc-toolbar-title { font-size: 16px; font-weight: 700; }
  .fc .fc-col-header-cell-cushion, .fc .fc-daygrid-day-number,
  .fc .fc-timegrid-axis-cushion { color: var(--muted); font-weight: 600; }
  .fc .fc-event { border-radius: 6px; cursor: pointer; border: none; font-size: 12px; }
  .fc .fc-timegrid-event { box-shadow: var(--shadow); }
  .fc .fc-timegrid-event .fc-event-main { padding: 3px 6px; }
  .fc .fc-timegrid-now-indicator-line { border-color: var(--danger); }
  .fc .fc-timegrid-now-indicator-arrow { border-color: var(--danger); }
  .fc .fc-highlight { background: rgba(79,110,242,.12) !important; }
  /* v0609-19：FC 周/月视图点击选中 = 同款黑框（与日视图 .sd-selected 统一）；
     旧样式是蓝 accent 3px+蓝 shadow，从未有代码触发，本次接入 eventClick */
  .fc .fc-event.fc-selected {
    outline: 2px solid #1f2430; outline-offset: 0;
    z-index: 2;
  }
  .evt-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .evt-sub { font-size: 10px; opacity: .85; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* 宠老板卡片式事件块（v0609-09）：黄服务标签+宠物/主人分行，同宠老板卡片观感
     v0609-30（用户 9/8）：① 时间不再挤在卡片右上角，改到宠物名下面独立一行，且
     「开始–结束」都显示；② 主人姓名与电话拆成两段 flex-wrap —— 同一行放不下时
     电话整体换到下一行（不再被 ellipsis 截断看不到号码） */
  .evt-card { display: flex; flex-direction: column; gap: 1px; min-width: 0;
              line-height: 1.25; font-size: 11px; }
  .evt-top { display: flex; align-items: center; gap: 4px; min-width: 0; }
  .evt-tag { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
             background: #f7b500; color: #3a2e00; font-weight: 700; font-size: 10px;
             padding: 0 5px; border-radius: 0; line-height: 15px; }   /* v0609-39 圆弧 4→2px；v0609-40 2→0px 直角（用户两连报「圆弧还是有点大」） */
  .evt-pet { font-weight: 700; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .evt-when { color: #fff; font-weight: 600; font-size: 10px; white-space: nowrap;
              overflow: hidden; text-overflow: ellipsis; }
  /* v0609-63：时间下面展示全部服务项目；v0609-69：格式与宠物名/时间/主人三行完全一致
     （去掉原先的半透明白底小标签，纯白文字 + 、分隔，超长自动换行） */
  .evt-svcs { color: #fff; font-size: 10px; line-height: 1.2; min-width: 0; padding: 0 5px;
              overflow-wrap: anywhere; }
  .evt-time { flex-shrink: 0; color: #fff; font-weight: 600; font-size: 10px; }  /* 旧右上角时间样式（v0609-30 起卡片改用 .evt-when，保留兼容折叠主卡/恢复） */
  .evt-who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 4px;
             color: #fff; font-size: 10px; line-height: 1.2; }   /* v0609-39：rgba(.9) → 纯白（用户「家长手机号也用白色」） */
  .evt-name { flex: 0 1 auto; min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .evt-phone { white-space: nowrap; font-weight: 700; }   /* v0609-39：手机号加粗（用户「家长手机号…白色加粗」，与宠物名同级） */
  .evt-phone::before { content: "📞"; margin-right: 2px; font-size: 9px; }

  .modal-mask {
    /* v0609-43：z 50→90——分列表头行 .sd-headrow z=60（v0609-39 抬的）原比弹窗遮罩高，
       弹窗打开时「时间/美容师名」整行从弹窗顶部穿透可见（用户 9/8 反馈「美容师名字穿透」）*/
    position: fixed; inset: 0; background: rgba(20,24,40,.45);
    display: none; align-items: center; justify-content: center; z-index: 90; padding: 20px;
    touch-action: none;   /* v0609-45：遮罩禁一切默认手势——iOS 手指落暗处横拉不再带起背景 */
  }
  .modal-mask.show { display: flex; }
  .modal-mask .modal { touch-action: pan-y; }   /* v0609-45：弹窗本体仍可纵向滚动（内容超高时） */
  /* v0609-45：弹窗打开期间冻结页面与表格（iPhone 实机 9/8：弹窗开着背景日历还能被左右拉动）。
     html/body overflow hidden 掐页面级滚动链与橡皮筋；#sdBody 直接禁滚（滚动位置保留），
     类由 freezeBoard() 在 openModal/closeModal 切换，关窗即恢复 */
  html.modal-open, html.modal-open body { overflow: hidden !important; }
  html.modal-open #sdBody { overflow: hidden !important; }
  .modal {
    background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow);
    width: 460px; max-width: 100%; max-height: 92vh; overflow: auto;
    padding: 22px 24px;
  }
  .modal-head { display: flex; align-items: center; justify-content: space-between;
                flex-wrap: wrap; gap: 8px 12px; margin-bottom: 14px; }
  .modal h2 { margin: 0; font-size: 18px; }
  .modal-head .dhead { display: flex; align-items: center; gap: 6px; flex: 0 0 auto;
                       font-size: 12px; font-weight: 600; color: var(--muted); }
  .modal-head .dhead input[type="date"] { min-height: 30px; padding: 3px 8px; }
  /* v0609-44：表单改行式——每行「标签在左、填写框在右侧同一行」；开始/结束时间同行左右并排；
     日期已上移到 .modal-head 标题处（用户 9/8 五连反馈） */
  .form-grid { display: flex; flex-direction: column; gap: 10px; }
  .form-grid .req { color: var(--danger, #e5484d); }
  .mrow { display: flex; align-items: center; gap: 10px; }
  /* v0609-132 表单清爽化：备注行标签顶部对齐；多行备注自适应高度 */
  .mrow.note { align-items: flex-start; }
  /* v0609-135：失败原因说明（待确认卡）——整行铺满、可换行、淡红底，手机端一眼看清 */
  .mrow.why { align-items: flex-start; }
  .mrow.why > label { padding-top: 6px; color: #b4453c; }
  .why-box { flex: 1 1 auto; min-width: 0; background: #fdf2f1; border: 1px solid #f3d7d4;
             color: #a5382f; border-radius: 10px; padding: 8px 10px; font-size: 13px;
             line-height: 1.45; overflow-wrap: anywhere; }
  .mrow.note > label { padding-top: 8px; }
  .mrow textarea { flex: 1 1 auto; min-width: 0; width: 100%; min-height: 46px; max-height: 132px;
                   resize: none; overflow-y: auto; line-height: 1.45; padding: 8px 10px;
                   font-family: inherit; font-size: inherit; }
  .mrow.pair .half > label { font-size: 13.5px; }
  .mrow > label { flex: 0 0 100px; font-size: 14px; font-weight: 600; color: var(--muted);
                  line-height: 1.3; }
  .mrow > input, .mrow > select { flex: 1 1 auto; min-width: 0; }
  .mrow input[type="date"], .mrow input[type="time"], .mrow input[type="tel"],
  .mrow input[type="text"], .mrow select { min-height: 36px; }
  /* v0609-45→49 演化见 backups/CHANGELOG。v0609-50 终修根因（用户 9/9 第 6 报 iPhone
     真机截图 798×1545 像素取证——本次确证 v49 已部署：品种/肩高行 flex+tgap 渲染
     完全正确=两框 299/299@dsf1.448≈206CSS、缝 36px≈16CSS ✓；唯独时间行异常=两框
     333/333≈230CSS、缝 3px≈2CSS、右框溢出弹窗右缘 34px）：
     ⚠️该 WKWebView 引擎对 flex 项「不会收缩到 input[type=time] 固有宽度以下」——
     min-width:0/width:100%/max-width:100% 对时间控件全部失效（text/tel/select 均正常
     收缩=品种行/手机号行旁证），缝再多重保险(tgap)也被撑爆的框挤掉。
     解法=时间行弃 flex 改 table-layout:fixed——列宽由表格结构决定、与内容固有宽
     彻底无关（IE8 级老引擎原语，比 flex/grid 更古老更稳），单元格 overflow:hidden
     兜底裁剪；输入框 display:block+width:100% 铺满列。
     品种/肩高、美容师/服务项目并排行（用户 9/9 第 1/4 条）=text/select 可收缩类型，
     flex+tgap 沿用，标签改与输入框同行（label 左 input 右） */
  .mrow.pair { display: flex; align-items: center; gap: 0; }  /* gap:0 显式清零 .mrow 继承的 gap:10px */
  .mrow .half { min-width: 0; display: flex; flex-direction: column; }
  .mrow .half label { font-size: 14px; font-weight: 600; color: var(--muted);
                      margin-bottom: 4px; }
  .mrow.time .half label { margin-bottom: 0; }   /* table-cell 里 label 是匿名表格外层
                      的 inline，margin-bottom 不生效→显式清零，间距由 input margin-top 出 */
  .mrow.time .half input { margin-top: 4px; }
  .mrow.pair .half { flex: 1 1 0; padding: 0; }
  .mrow.pair .half { flex-direction: row; align-items: center; }
  .mrow.pair .half > label { flex: 0 0 auto; margin: 0 8px 0 0; }
  .mrow.pair .half > input, .mrow.pair .half > select { flex: 1 1 auto; min-width: 0;
                      max-width: 100%; }
  .mrow .tgap { flex: 0 0 16px; width: 16px; }   /* 物理间隔：任何引擎都得占位 */
  /* 时间行 table 化（.half/.tgap 类名保留，CSS 按 .mrow.time 上下文覆盖） */
  .mrow.time { display: table; table-layout: fixed; width: 100%; border-spacing: 0;
               gap: 0; }
  .mrow.time .half { display: table-cell; vertical-align: top; overflow: visible; }
  /* v0609-51：改 visible——此前 overflow:hidden 会把铺满列的 input 右侧 1px 边框裁掉
     （用户报「方框右边断开」）；table-layout:fixed 已把列宽钉死与内容无关，无溢出风险 */
  /* v0609-52：框体按用户建议收窄一档、中缝加宽——列宽显式钉成 44%+12%+44%=100%，
     两框之间永远有 ≥12% 的物理空档（任何引擎都让不出这一列），也杜绝右框贴/溢出弹窗
     v0609-54：缝收窄到 6%、两框加宽到 47%——div.time-box 无控件固有宽度、table-fixed
     下重叠构造上不可能，用户嫌 38px 中缝太宽框太窄（9/9 第 10 轮）→ 缝 ≈21-25px
     （手机/桌面）仍清晰分开，框体 +3% 更好操作 */
  .mrow.time .half { width: 47%; }
  .mrow.time .tgap { display: table-cell; width: 6%; }
  /* v0609-53：时间框弃原生 <input type=time>（iOS 无视 step=900、渲染两端不一致且空框
     残缺，v45–v52 八轮修补全是其固有怪癖）→ 展示框 .time-box + 自绘滚轮弹层 .ts-*。
     div 宽度完全受 table-cell 列宽控制，任何引擎不再有「控件固有最小宽度」可撑破 */
  .mrow.time .time-box { display: flex; align-items: center; justify-content: space-between;
    gap: 4px; width: 100%; box-sizing: border-box; min-height: 40px; margin-top: 6px;
    padding: 0 10px; border: 1px solid var(--border); border-radius: 8px; background: #fff;
    font-size: 16px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .mrow.time .time-box .tb { min-width: 0; white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis; color: var(--text); font-variant-numeric: tabular-nums; }
  .mrow.time .time-box.empty .tb { color: #b3b9c6; font-size: 13px; }
  .time-box .tb-arrow { flex: 0 0 auto; color: #b3b9c6; font-size: 12px; }
  /* ---- v0609-53 时间滚轮弹层（自绘，替代系统时间控件；微信同款底部 sheet）---- */
  .ts-mask { position: fixed; inset: 0; z-index: 95; display: none;
    align-items: flex-end; justify-content: center; background: rgba(20,24,40,.45);
    touch-action: none; }
  .ts-mask.show { display: flex; }
  .ts-sheet { width: 100%; max-width: 420px; background: #fff;
    border-radius: 18px 18px 0 0; box-shadow: 0 -8px 28px rgba(20,24,40,.2);
    padding: 10px 16px calc(12px + var(--kb-safe) + var(--kb-inset, 0px));
    touch-action: pan-y; }
  .ts-head { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; }
  .ts-clear { border: none; background: none; color: var(--danger, #e5484d);
    font: inherit; font-size: 14px; font-weight: 600; padding: 4px 2px; cursor: pointer; }
  .ts-title { font-size: 15px; font-weight: 700; color: var(--text); }
  .ts-head .btn { padding: 5px 16px; font-size: 14px; }
  .ts-body { position: relative; height: 200px; display: flex; align-items: center;
    justify-content: center; }
  .ts-col { width: 96px; height: 100%; overflow-y: auto; overflow-x: hidden;
    -webkit-overflow-scrolling: touch; scroll-snap-type: y mandatory;
    scrollbar-width: none; overscroll-behavior: contain; }
  .ts-col::-webkit-scrollbar { display: none; }
  .ts-col .ts-fill { height: 80px; flex: none; }
  .ts-col .ts-it { height: 40px; line-height: 40px; text-align: center;
    font-size: 17px; color: #9aa0ae; font-variant-numeric: tabular-nums;
    scroll-snap-align: center; transition: color .12s, font-size .12s, font-weight .12s; }
  .ts-col .ts-it.on { color: #171b26; font-size: 19px; font-weight: 700; }
  .ts-colsep { flex: 0 0 auto; width: 20px; text-align: center; font-size: 19px;
    font-weight: 700; color: #171b26; }
  .ts-line { position: absolute; left: 0; right: 0; top: 80px; height: 40px;
    border-top: 1px solid #e4e7f0; border-bottom: 1px solid #e4e7f0;
    pointer-events: none; }
  .ts-shade { position: absolute; left: 0; right: 0; height: 80px; z-index: 2;
    pointer-events: none; }
  .ts-shade.top { top: 0; background: linear-gradient(rgba(255,255,255,1), rgba(255,255,255,0)); }
  .ts-shade.bot { bottom: 0; background: linear-gradient(rgba(255,255,255,0), rgba(255,255,255,1)); }
  /* v0609-54：桌面（鼠标）滚轮弹层不再贴屏幕底——openTimePick 里 fine pointer 检测后把
     sheet 锚定在点按的时间框正下方（fixed+top/left，放不下自动翻到框上方）；此媒体只改
     观感：贴框的小卡样式（圆角+投影），与手机端底部 sheet 区分 */
  @media (hover: hover) and (pointer: fine) {
    .ts-sheet { border-radius: 14px; box-shadow: 0 12px 40px rgba(20,24,40,.25); }
  }
  /* v0609-54：提示行左缩进 110px 取消（那是行式表单「标签列 100px」的对齐位，时间行无
     标签列、两框从网格左缘起）——左缘与时间框左缘对齐、单行展示不折行（用户 9/9 第 10
     轮：灰字折两行且不对齐） */
  .frm-hint { font-size: 11px; color: var(--muted); line-height: 1.35; margin: 0;
              text-align: left; white-space: nowrap; overflow: hidden;
              text-overflow: ellipsis; }
  .modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 22px; }
  .hint { font-size: 11px; color: var(--muted); margin-top: 6px; }
  .sd-hintbar .hint-touch { display: none; }   /* v0609-20：触屏提示仅移动端显示 */
  .toast {
    position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
    background: #1f2430; color: #fff; padding: 10px 18px; border-radius: 10px;
    /* v0609-154：限宽 + 居中 + 行高——「预约成功：宠物 时间 服务」比旧文案长，
       手机上不限宽会横向撑出屏幕（用户最在意手机端） */
    /* width:max-content 必需——固定定位 + left:50% 的可用宽度只有半屏(50%)，
       「预约成功：宠物 时间 服务」会被卡在 ~195px 折成两行；改内容自适应宽度后
       是一行，超长才由 max-width 兜住换行 */
    width: max-content; max-width: min(92vw, 520px); text-align: center; line-height: 1.45;
    font-size: 13px; box-shadow: var(--shadow); opacity: 0; transition: opacity .25s;
    z-index: 100;   /* v0609-53：60→100——原低于 modal(90)/ts 弹层(95)，弹层内校验提示会被盖住 */
    pointer-events: none;
  }
  .toast.show { opacity: 1; }

  /* v0609-62：「正在预约」占位卡——保存自动下单期间，目标槽位先出现白底虚线占位卡，
     卡内「正在预约」+ 三点波浪跳动表示处理中；闭环成功后原位变回正式预约卡片。
     （取代 v0609-61 屏幕中央浮层：用户视线本就在卡片位置，就地反馈更直观） */
  .sd-event.sd-pending {
    background: #fff !important; border: 1.5px dashed #b9c2d8;
    box-shadow: 0 1px 6px rgba(60,72,100,.12); cursor: default;
  }
  .sd-pending .bkpending {
    height: 100%; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 5px; padding: 2px;
  }
  .bkpending-txt { font-size: 12px; color: #5a6478; font-weight: 600;
                   letter-spacing: 1px; white-space: nowrap; }
  .bkloading-dots { display: flex; justify-content: center; gap: 7px; }
  .bkloading-dots i {
    width: 6px; height: 6px; border-radius: 50%; background: #4f6ef2;
    animation: bkdot 1.1s ease-in-out infinite;
  }
  .bkloading-dots i:nth-child(2) { animation-delay: .16s; }
  .bkloading-dots i:nth-child(3) { animation-delay: .32s; }
  @keyframes bkdot {
    0%, 60%, 100% { transform: translateY(0); opacity: .25; }
    30% { transform: translateY(-5px); opacity: 1; }
  }
  /* FC 周/月视图占位事件同款观感 */
  .fc-event.bk-pending-fc {
    background: #fff !important; border: 1.5px dashed #b9c2d8 !important;
    color: #5a6478 !important; font-weight: 600;
  }

  /* ===== 移动端适配 ===== */
  @media (max-width: 720px) {
    /* v0609-20：顶部重排——标题条压缩成一行、门店/美容师+新建合成一行、
       viewBar(月周日·日期·‹今天›)强制单行，合计「筛选行 + 日期行」两行主体，
       把空间让给排班表（旧版 header 三行 + viewBar 折两行共 ~207px → 现在 ~110px）
       v0609-21：主标题只留「门店排班」（版本号/副题不再显示，用户 9/7 嫌重复）；
       v0609-22：页面内「门店排班」标题行整体删除（用户嫌与浏览器标签重复），
       在线绿点随 statusBadge 进筛选行最左 = 「选择门店」左边 */
    header {
      padding: 3px 8px;
      /* v0609-310：iOS 26/27 顶栏液态玻璃（系统 scrollEdgeEffect）无 CSS 开关，
         把顶栏整体让出状态栏下方约 40px 雾带；--glass-gap-top 设 0px 即撤回。
         原来的 env(safe-area-inset-top) 保留（不能丢）：
           · 默认 40px → 计算值 = env + 10 + 40（与设计口径一致）
           · 设 0px     → 计算值 = 原来的 env + 3（clamp 只为让 0 这一档精确归位；
             40 及其它 >0 的档位仍是 3 + var + 7 = 10 + var，可直接按值微调） */
      padding-top: calc(3px + env(safe-area-inset-top, 0px) + var(--glass-gap-top, 40px)
                       + clamp(0px, var(--glass-gap-top, 40px), 1px) * 7);
    }
    .status-badge { font-size: 9px; padding: 0 2px; gap: 3px; }
    .status-badge #statusText { display: none; }   /* 状态只留灯色，省空间 */
    /* 筛选行（1）：绿点/门店/美容师 + 新建预约；同步/导出收起（有自动同步，导出台式机用） */
    .toolbar-controls { width: 100%; gap: 5px; flex-wrap: nowrap; }
    .field-group { flex: 1 1 0; min-width: 0; }
    .toolbar-controls select { min-height: 30px; font-size: 12px; width: 100%;
                               padding: 3px 6px; }
    .toolbar-controls .btn { min-height: 30px; font-size: 12px; }
    #btnNew { flex: 0 0 auto; padding: 3px 8px; }
    #btnSync, #btnExport { display: none; }
    /* 日期行（2）：日期(可点弹日历) + ‹/今天/› 强制单行不折
       v0609-30：「日」钮已隐藏（.sd-views display:none），三列 grid 左右等宽 → 日期居中；
       ‹/› 箭头加大到 44px 级触摸目标（用户 9/8：现在太小了不太好点） */
    .view-bar { padding: 4px 6px 0; gap: 3px; min-width: 0; }
    .view-bar .sd-views { gap: 2px; }
    .view-bar .sd-views .btn { padding: 3px 0; font-size: 12px; min-height: 26px;
                               width: 32px; text-align: center; }
    .view-bar .sd-date { font-size: 12px; min-width: 0; padding: 0; }
    .view-bar .sd-date #sdDate { font-size: 13px; }
    .view-bar .sd-date .sd-week { display: none; }  /* 「今天」标记省掉（右侧已有今天钮） */
    .view-bar .sd-vnav { gap: 4px; }
    .view-bar .sd-vnav .btn { padding: 3px 8px; font-size: 12px; min-height: 34px; }
    .view-bar .sd-vnav #vbPrev, .view-bar .sd-vnav #vbNext { min-width: 44px; min-height: 36px;
                                                             font-size: 20px; padding: 2px 8px; }
    /* 提示行：触屏版短文案（点卡片＝选中后再拖/拉；空白处滚动） */
    .sd-hintbar { font-size: 9px; margin: 1px 2px 4px; line-height: 1.3; }
    .sd-hintbar .hint-full { display: none; }
    .sd-hintbar .hint-touch { display: inline; }
    .sd-colhead { font-size: 11px; padding: 2px 2px; }
    /* v0609-90：窄屏列头只留班次名（96px 放不下精确时间）；桌面显示「中班 10:00-20:00」 */
    .sd-colhead .sd-colstore .sd-shifttime { display: none; }
    #calendar { padding: 4px 4px; }
    .fc .fc-toolbar { margin-bottom: .4em; flex-wrap: wrap; row-gap: 4px; }
    .fc .fc-toolbar-title { font-size: 13px; }
    .fc .fc-button { padding: 3px 6px; font-size: 11px; min-height: 26px; }
    .fc .fc-col-header-cell-cushion { font-size: 11px; }
    .fc .fc-timegrid-now-indicator-line { border-width: 2px; }
    .fc .fc-timegrid-event .fc-event-main { padding: 2px 4px; }
    .fc .fc-event { font-size: 11px; }
    .evt-title, .evt-sub, .evt-card { max-width: 100%; }
    /* v0609-164：旧的全站卡片字号覆盖删除（同因：只有 .evt-phone 被直接命中而变小） */
    .modal { width: 100%; padding: 16px; max-height: 94vh; }
    /* v0609-45：iOS 聚焦 <16px 的输入框会自动放大页面（跳版感），弹窗表单字号提到 16px */
    .modal input[type="time"], .modal input[type="tel"], .modal input[type="text"],
    .modal input[type="date"], .modal select { font-size: 16px; }
    /* v0609-44：表单已改行式 flex（窄屏自动单列生效），旧网格覆写不再需要 */
    .modal-actions { gap: 8px; }
    .modal-actions .btn { flex: 1; }
    .hint { font-size: 10px; }
    .frm-hint { font-size: 10px; }   /* v0609-54：时间行下提示单行文案的窄屏字号 */
    /* v0609-47：手机端隐藏表格横向「拖动条」（用户 9/9：最下面显示一个拖动条要删）——
       左右滑动仍是手势/滚轮驱动，条只占行碍眼；桌面端保留滚动条（仅限移动媒体内隐藏）
       v0609-50：扩到全站所有滚动容器（用户 9/9 第 2 条复报「最下面还是会显示拉条」——
       此前只藏了 .sd-body，弹窗 .modal(overflow:auto) 等其它容器的滚动条没管住） */
    ::-webkit-scrollbar { width: 0 !important; height: 0 !important; }
    * { scrollbar-width: none !important; }
    /* ---- v0609-67：手机端 UI 适配 ---- */
    /* 新建/详情弹窗改底部 sheet（微信同款：贴底、顶部圆角、上滑入场），拇指可达、
       不再居中遮挡；桌面维持居中卡片不变 */
    .modal-mask { align-items: flex-end; padding: 0; }
    .modal { width: 100%; border-radius: 18px 18px 0 0; max-height: 88vh;
      padding: 14px 16px calc(14px + var(--kb-safe) + var(--kb-inset, 0px));
      animation: bkSheetUp .22s ease-out; box-shadow: 0 -8px 28px rgba(20,24,40,.22); }
    .modal-head { margin-bottom: 10px; }
    .modal h2 { font-size: 17px; }
    /* 底部输入栏常驻 → 日历底部让出一个栏位高度，防盖住最后一行时刻
       （v0609-185：这条会被后面的顶级 #main 规则覆盖，已删——避免留下"看着生效其实没有"
        的死规则；让位高度统一由顶级那条 + 安全区决定） */
    /* toast 上移避开底部输入栏（T-103：栏顶上移 15px，70 → 85 保持原来的 6px 间距） */
    .toast { bottom: calc(85px + env(safe-area-inset-bottom, 0px)); }
    /* 时间滚轮弹层同样抬高（原本贴底会与输入栏视觉打架） */
    .ts-sheet { box-shadow: 0 -8px 34px rgba(20,24,40,.3); }
  }
  @keyframes bkSheetUp { from { transform: translateY(40px); opacity: .6; }
                         to { transform: translateY(0); opacity: 1; } }
  /* ---- v0609-67：底部快捷输入栏（自然语言 → 解析 → 预填表单确认）---- */
  /* v0609-185（用户「做成 PWA 后底部文本框超出屏幕」）：底部栏与"内容让位"都改成
     跟着 **安全区** 走 —— iPhone 的 Home 指示条占 34px，独立模式（装到桌面）没有
     浏览器工具栏，栏位若只按固定 px 抬就会压在指示条上；`env()` 在独立模式下要
     靠 <meta viewport-fit=cover> 才有值（已加）。PWA 独立模式再兜底多抬 8px。 */
  /* v0609-186（用户「抬得有点过高了」）：安全区**本身**已经包含 Home 指示条 + 一点余量，
     所以底距 = **4px + 安全区**即可（iPhone 上 = 38px）。之前是 10px + 安全区 + 独立模式再加
     8px = 52px，实测照片量出来栏下留白 54pt，明显偏高。独立模式的额外加成已删除。 */
  /* v0609-194（用户「点文本框弹键盘，页面不上浮，看不到输入内容」）：
     iOS 弹系统键盘时，`position:fixed` 的底部栏**不会自动上浮**（fixed 相对"布局视口"，
     键盘只压缩"可视视口"）→ 输入框被压在键盘下面。修法：用 visualViewport 算出键盘高度，
     写进 CSS 变量 --kb-inset，底部栏据此抬高（见文件尾部的脚本）。 */
  .bk-bar { position: fixed; left: 50%; transform: translateX(-50%);
    /* T-103（ICP 备案）：为页面最底部那行备案号（.bk-icp）让出 15px——4 → 19。
       字框占 4..16（12px = 10px × 1.2），栏底边在 19 → 两者间距 3px。
       ⚠️ 与 #main 的让位成对：下面那条 68 → 82，否则日历最后一行会被栏压住。 */
    bottom: calc(19px + var(--kb-safe) + var(--kb-inset, 0px));
    width: min(720px, calc(100% - 20px)); display: flex; gap: 8px; align-items: center;
    background: #fff; border: 1px solid #dfe4ee; border-radius: 16px; padding: 7px 8px 7px 14px;
    box-shadow: 0 6px 24px rgba(31,36,48,.16); z-index: 60; }
  /* T-103（ICP 备案）：首页底部备案号——《互联网信息服务管理办法》要求标明备案号并链接到
     工信部备案系统。只有一行 10px 极淡灰字、居中、点开新标签（不新增图标/边框/底色/分割线）。
     它占据输入栏**原来的** bottom 位置（4px + 安全区 → 在 Home 指示条**之上**，不在安全区里），
     输入栏整体上移 15px 让位（见上一条），两者间距 3px（字框 12px = 10px × 1.2）。
     z-index 59 < 输入栏 60：任何情况下字都不会盖住输入栏。
     left:50% + translateX(-50%)：点击区只有文字那么宽，不会在屏幕底部留下整宽死区。 */
  .bk-icp { position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(4px + var(--kb-safe) + var(--kb-inset, 0px));
    height: 12px; line-height: 1.2; white-space: nowrap; text-align: center;
    font-size: 10px; color: #9aa0ab; text-decoration: none; z-index: 59; }
  /* v0609-190c：输入框聚焦 ⇒ 键盘必然弹起 ⇒ **输入栏与日历让位都不再为 Home 指示条留那 34px**。
     ⚠️ 两条必须成对：只改输入栏的话，输入栏下移了 34px、日历容器却还留着 34px 空白，
        于是"表格与输入栏之间的空白"反而从 14px 变成 48px（用户：「文本框又离上面的表格空白有点多」）。
     与 `kb-up` 的做法保持一致（kb-up 是靠全局把 --kb-safe 归零达成同一效果），
     这里只覆盖这两个消费方自己的属性，**不动 --kb-safe 本身**，以免波及其它使用方。 */
  /* T-103（ICP 备案）：键盘态同样成对——输入栏与备案字的底距都只留 --kb-inset，
     让位 82 同步（三条一起 +15/+14，键盘弹起时三者相对关系与不弹键盘时完全一致）。 */
  html.kb-focus .bk-bar { bottom: calc(19px + var(--kb-inset, 0px)); }
  html.kb-focus #main { padding-bottom: calc(82px + var(--kb-inset, 0px)); }
  .bk-bar input, .bk-bar textarea { flex: 1 1 auto; min-width: 0; border: none; outline: none;
    background: none; font: inherit; font-size: 13px; line-height: 19px; color: var(--text);
    resize: none; overflow: hidden; }
  /* v0609-86：文本域定高两行、文字在框内上下居中（用户「文本框没提示/上下居中」）——
     2 行 × 19px + 上下 padding 3px = 44px，与右侧按钮等高，两行提示恰好居中 */
  .bk-bar textarea { height: 44px; padding: 3px 0; box-sizing: border-box; }
  /* v0609-84：底部输入框改两行文本域（用户「手机端提示显示不完整，用两行完整显示」）——
     单行 input 无法换行，占位文案被截断；textarea rows=2 可完整显示两行提示。 */
  .bk-bar textarea::placeholder { color: #aab1c0; font-size: 12px; white-space: pre-line; }
  .bk-bar .btn { flex: 0 0 auto; min-height: 38px; padding: 6px 18px; }
  /* 输入栏常驻（桌面+手机）：日历底部让位防盖内容——让位高度同样跟着安全区走
     （v0609-185：独立模式先让出 18px + 指示条 34px，下面内容才不会被栏位压住）
     T-103：68 → 82（+14）——栏底 4 → 19（+15），栏顶与日历之间的空隙从 4px 收到 3px（−1），
           故取整 82；与上面 html.kb-focus 那条成对，实测栏顶与日历底仍留 3px 空隙。 */
  #main { padding-bottom: calc(82px + var(--kb-safe) + var(--kb-inset, 0px)); }
  /* T-103：toast 跟着上移 15px（原来是「栏顶 64 + 16」，栏顶变 79 → 95，手机端 70 → 85），
     否则会压到备案号/输入栏。 */
  .toast { bottom: calc(95px + env(safe-area-inset-bottom, 0px)); }
  /* v0609-67：详情态只读字段灰显（手机号打码/宠物档案由宠老板主管） */
  .mrow input.frm-ro, .mrow select.frm-ro { background: #f3f5f9; color: #8a93a6;
    -webkit-text-fill-color: #8a93a6; }
  select.frm-ro:disabled { opacity: 1; }   /* 只读≠半透明，保持可读 */
  /* T-103：冻结日详情只读——时间框与备注沿用上面同一组只读灰（**不新增色值**）；
     时间框右侧 ▾ 隐藏（框已不可点，箭头是"能点开"的承诺） */
  .mrow .time-box.frm-ro { background: #f3f5f9; cursor: default; }
  .mrow .time-box.frm-ro .tb { color: #8a93a6; }
  .mrow .time-box.frm-ro .tb-arrow { display: none; }
  .mrow textarea.frm-ro { background: #f3f5f9; color: #8a93a6; }
  /* v0609-71：提交时问题字段高亮（缺字段/冲突，不关窗） */
  .mrow input.frm-err, .mrow select.frm-err, .mrow .time-box.frm-err {
    border-color: var(--danger, #e5484d); background: #fdf0f0;
    box-shadow: 0 0 0 2px rgba(229,72,77,.15); }

  /* ================= v0609-92：手机端 UI 精修（简洁 / 美观 / 高级 / 易用） =================
     原则：**只覆盖视觉层**（颜色·字重·圆角·间距·阴影·安全区），一行都不碰滚动与 sticky
     几何——那部分是 v0609-28~38 十几轮像素级踩坑换来的（表头/时间列固定层、双轴滚动、
     sticky 左列），改一处就可能复发错位。所有规则写在样式表末尾，天然覆盖前面的同名声明。 */
  :root {
    --bg: #f6f8fc;                 /* 更柔的底，白卡片更立体 */
    --panel: #ffffff;
    --border: #eaeef6;
    --border-2: #e2e7f0;
    --text: #1c2230;
    --muted: #8b94a7;
    --muted-2: #a7aebd;
    --accent: #4f6ef2;
    --accent-dark: #3a55d6;
    --accent-soft: #eef2fe;
    --radius: 14px;
    --shadow-1: 0 1px 2px rgba(28,34,48,.05), 0 1px 1px rgba(28,34,48,.03);
    --shadow-2: 0 6px 20px rgba(28,34,48,.10);
  }
  /* --- 顶部：控件统一成「轻胶囊」 --- */
  select, input[type="text"], input[type="tel"], input[type="date"], input[type="time"] {
    border-radius: 11px; border: 1px solid var(--border-2); background: #fff;
    box-shadow: var(--shadow-1); transition: border-color .15s, box-shadow .15s;
  }
  select:focus, input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79,110,242,.14); }
  .btn { border-radius: 11px; transition: background .15s, transform .06s, box-shadow .15s; }
  .btn-primary { box-shadow: 0 2px 8px rgba(79,110,242,.28); }
  .btn-ghost { box-shadow: var(--shadow-1); }
  /* --- 看板面板：更圆更浮 --- */
  .sd-body { border-radius: var(--radius); border-color: var(--border-2);
             box-shadow: var(--shadow-1); }
  .sd-body.sd-headless { border-radius: var(--radius); }
  .sd-headrow .sd-colhead { background: #fbfcfe; color: #2c3444; font-weight: 650;
                            letter-spacing: .2px; }
  .sd-colhead .sd-colstore { color: var(--muted-2); font-weight: 600; }
  .sd-timecell { color: #5b6475; font-variant-numeric: tabular-nums; }
  /* v0609-156：卡片视觉全部移到下方「v0609-155r2」统一块（直角卡 + 顶线 + 卡头图标 +
     内框到底）。此处**只保留与卡片无关的几何**，避免旧规则把新样式盖掉（实测：
     这里的 border-radius:14px / left:4px 压过了新块，卡片仍是圆角）。 */
  .sd-event.sd-selected { outline: 2px solid #1c2230; outline-offset: 1px; border-radius: 0; }
  /* --- 底部输入条：悬浮胶囊 + iOS 安全区 --- */
  .bk-bar { border-radius: 16px; box-shadow: var(--shadow-2); border: 1px solid var(--border-2); }
  .bk-bar .btn { border-radius: 12px; }
  /* --- 弹窗（新建/详情）：底部抽屉观感 --- */
  .modal { border-radius: 18px 18px 0 0; box-shadow: 0 -8px 32px rgba(28,34,48,.16); }
  .modal-actions .btn { border-radius: 12px; min-height: 44px; font-size: 15px; }
  .modal-head h3 { font-size: 16px; font-weight: 700; letter-spacing: .2px; }
  /* --- 轻提示：深色胶囊 + 毛玻璃 --- */
  .toast { border-radius: 999px; background: rgba(28,34,48,.92);
           -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
           box-shadow: var(--shadow-2); padding: 9px 16px; font-size: 13px; }
  /* --- 手机端：把垂直空间还给看板 --- */
  @media (max-width: 720px) {
    .view-bar { padding: 10px 10px 0; gap: 6px; }
    .view-bar .sd-date { min-width: 108px; }
    .view-bar .sd-date #sdDate { font-size: 15px; letter-spacing: .3px; }
    .view-bar .sd-week { font-size: 11px; margin-left: 4px; }
    .sd-vnav { gap: 4px; }
    .sd-vnav .btn { min-height: 34px; padding: 4px 10px; border-radius: 10px; font-size: 13px;
                    white-space: nowrap; }
    .sd-vnav #vbPrev, .sd-vnav #vbNext { min-width: 38px; font-size: 18px; }
    #staffDayWrap { padding: 2px 8px 8px; }
    /* 时间列收窄：86→64px（时间格与角格必须同宽——几何铁律，两处一起改） */
    .sd-timecol { flex: 0 0 64px; }
    .sd-headrow .sd-corner { flex: 0 0 64px; min-width: 0; max-width: 64px; }
    .sd-timecell { font-size: 11px; padding-right: 6px; }
    .sd-headrow .sd-colhead { font-size: 12px; }
    /* v0609-164：手机端也不再单独调卡片字号（旧规则把 .evt-phone 压成 9.5px）——
       卡内所有文字统一走 --evt-fs。 */
    .toast { padding: 8px 14px; font-size: 12.5px; }
  }

  /* =====================================================================================
     v0609-93 手机端视觉重构：把看板从「深灰实心块」换成「白卡 + 彩色标识」的信息卡风格。
     为什么这样改：深灰实心块 + 白色小字在手机上既沉闷又难读（对比度、层级都平），
     也最容易显得「普通」。新语言 = 安静的中性底 + 高质量的白卡 + 精确的彩色标识
     （左侧 3px 色条 = 归属美容师 / 小圆点 = 状态 / 服务标签用该色淡染），文字回到深色。
     仍然**只覆盖视觉层**：滚动容器、sticky 时间列、表头固定层的几何一律不动。
     卡片颜色经由 `--evt-c` 变量下发（v0609-93 起渲染不再写内联 background），
     所以这里可以用 var()/color-mix() 精确派生——颜色仍按美容师稳定取色，归属不会丢。
     ===================================================================================== */
  :root {
    --bg: #f7f8fb;
    --panel: #ffffff;
    --border: #e8ecf4;
    --border-2: #dfe5f0;
    --text: #1b2130;
    --muted: #8b93a5;
    --muted-2: #aab1c0;
    --accent: #4f6ef2;
    --accent-dark: #3a55d6;
    --accent-soft: #eef2fe;
    --radius: 16px;
    --shadow-1: 0 1px 2px rgba(24,30,45,.05), 0 2px 6px rgba(24,30,45,.04);
    --shadow-2: 0 10px 30px rgba(24,30,45,.12);
  }
  body { background: var(--bg); -webkit-font-smoothing: antialiased; }
  /* ---------- 顶部：控件统一成轻胶囊，去掉厚重描边 ---------- */
  select, input[type="text"], input[type="tel"], input[type="date"], input[type="time"] {
    border-radius: 12px; border: 1px solid var(--border-2); background: #fff;
    box-shadow: var(--shadow-1); transition: border-color .15s, box-shadow .15s;
  }
  select:focus, input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79,110,242,.14); }
  .btn { border-radius: 12px; font-weight: 600; letter-spacing: .1px; }
  #btnSync .sync-icon { display: none; }   /* 桌面显示「同步宠老板」全称，手机换图标（v0609-93） */
  /* v0609-298：退出登录入口（工具条最右、导出之后）。桌面用「退出登录」全称 + 中性 .btn-ghost
     ——常驻的红色按钮会持续抢注意力，且退出是可逆的低频动作；红色只出现在确认弹窗的确认钮上 */
  #btnLogout .logout-short { display: none; }
  /* 退出确认弹窗的正文（复用 .modal 外壳，不引入新的颜色/圆角体系） */
  .logout-body { font-size: 13px; color: var(--text); line-height: 1.6; }
  .btn-primary { box-shadow: 0 3px 10px rgba(79,110,242,.26); }
  /* ---------- 看板外壳：更大圆角 + 更克制的分隔线 ---------- */
  .sd-body { border-radius: var(--radius); border: 1px solid var(--border);
             box-shadow: var(--shadow-1); background: var(--panel); }
  .sd-body.sd-headless { border-radius: var(--radius); }
  .sd-headrow .sd-colhead { background: #fff; color: #2a3143; font-weight: 640;
                            letter-spacing: .3px; border-right-color: var(--border);
                            border-bottom: 1px solid var(--border); }
  .sd-colhead .sd-colstore { color: var(--muted-2); font-weight: 600; }
  .sd-timecol { background: #fcfdff; }
  .sd-timecell { color: #6b7385; font-variant-numeric: tabular-nums;
                 background: transparent !important; border-bottom-color: #f1f4fa !important; }
  .sd-colhead.sd-corner { color: var(--muted); font-weight: 600; background: #fcfdff; }
  /* ---------- 非上班斜线：更淡，退到背景里 ---------- */
  .sd-hatch { background-image: repeating-linear-gradient(45deg,
      rgba(146,158,184,.16) 0, rgba(146,158,184,.16) 1px, transparent 1px, transparent 8px); }
  /* ---------- 事件卡：白底 + 左侧色条 + 迷你圆点，文字回到深色 ---------- */
  /* v0609-155r2（用户三条纠正 + 逐像素取色）：**卡片直角（radius 0）**、顶线 1.5px、
     卡头「时钟图标 + 主色加粗服务名」、**内框几乎到底**（四周仅 ~3px 内缩、圆角 ~11px、
     底色是卡底的更浅一档而非纯白）。以下色值全部由参考图逐像素测得（原图 3x，已折算 CSS）：
       蓝 顶#32A6DD/底#E3EDF6/框#F1F6FB/字#0082CD   紫 顶#8880BB/底#EAE5F1/框#F4F2F9/字#4A479B
       橙 顶#E2AF67/底#F6EFD8/框#FAF7EC/字#D48820   绿 顶#83C08D/底#DCECDE/框#EDF5EE/字#34A45D
       红 顶#E6968F/底#FAEBEB/框#FCF5F5/字#DC5F5C   时间行统一 #DF302C   备注行 #747474
     结构比例（卡高 231 基准，3x 实测折算）：卡头行高 ~15、内缩 3/上留白 ~8、时间行占 1/3 高、内框占 ~84%。 */
  .sd-event {
    left: 3px; right: 3px; border-radius: 0;      /* 参考图：直角 */
    background: #e3edf6;
    background: var(--evt-bg, #eef1f7);
    border: 0;
    border-top: 1.5px solid var(--evt-c, #94a3b8);
    box-shadow: 0 1px 3px rgba(24,30,45,.10);
  }
  .sd-event.sd-dragging { box-shadow: 0 8px 22px rgba(24,30,45,.24); opacity: .97; }
  .sd-event.sd-selected { outline: 2px solid var(--text); outline-offset: 1px;
    /* v0609-97：**已选中**才彻底接管手势（touch-action:none）——拖动/拉边不再依赖
       preventDefault 的时序，最可靠；未选中卡片保持 auto（滑动优先）。 */
    touch-action: none; }
  .sd-event .evt-card { display: flex; flex-direction: column; gap: 0;
                        height: 100%; min-width: 0; line-height: 1.25; }
  /* ---- 卡头：时钟图标 + 主色加粗服务名（参考图实测：行高 15px、左缘 9px） ---- */
  .sd-event .evt-top { position: relative; display: flex; align-items: center;
                       gap: 4px; min-width: 0; flex: 0 0 auto;
                       padding: 4px 7px 3px 10px; }
  .sd-event .evt-ico { flex: 0 0 auto; width: 12px; height: 12px;
                       color: var(--evt-tx, #94a3b8); display: inline-flex; }
  .sd-event .evt-ico svg { width: 100%; height: 100%; display: block; }
  /* ===== v0609-162（用户）：「卡片内容的字体大小都一样大，行间距比现在稍微多一些」 =====
     卡内**所有**文字（卡头服务名 / 宠物名 / 时间 / 服务项目 / 会员名+手机号 / 备注）
     统一走 --evt-fs 与 --evt-lh，再也不用逐个元素对字号；行间距由 --evt-gap 控制。
     正常档 11px/1.35/2px，紧凑档 10px/1.3/2px（evt-sm 覆盖）。
     ⚠️ 字号与行距都变大 → 能放下的行数会比以前少，这是预期；不会裁切（fitCardLines 兜底）。 */
  .sd-event { --evt-fs: 11px; --evt-lh: 1.35; --evt-gap: 2px; }
  /* v0609-162 定稿：**全站卡片统一一个字号**（用户「内容字体大小都一样大」）——
     紧凑档不再缩字号，只保留更小的内边距（多留一行空间），避免同一屏里
     大卡 11px、小卡 10px 看着不齐。行数不够时由 fitCardLines 自适应裁剪。 */
  .sd-event.evt-sm { --evt-fs: 11px; --evt-lh: 1.35; --evt-gap: 2px; }
  .sd-event .evt-tag {
    flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    background: none; border: 0; border-radius: 0;
    color: var(--evt-tx, #334155);
    font-weight: 700; font-size: var(--evt-fs, 11px); line-height: var(--evt-lh, 1.35);
    padding: 0; letter-spacing: .1px;
  }
  /* 宠老板只读行与手工行同一外观（参考图不区分；只读仍由无拖拽手柄表达） */
  .sd-event.sd-clbrow .evt-tag { background: none; border: 0; }
  /* ---- 内框（参考图核心）：几乎占满卡（左右/下各 3px）、大圆角 11px、近白同色底 ---- */
  .sd-event .evt-body {
    flex: 1 1 auto; min-height: 0; min-width: 0;
    margin: 0 3px 3px; border-radius: 12px;
    background: #f1f6fb; background: var(--evt-pnl, #f1f6fb);
    padding: 8px 10px 7px;
    display: flex; flex-direction: column; gap: var(--evt-gap, 2px);
    overflow: hidden;
  }
  /* v0609-160 关键修复：面板内的行**必须禁止被压缩**（flex: 0 0 auto）。
     否则 .evt-pet/.evt-when 自带 overflow:hidden → 作为 flex 子项的「自动最小尺寸」
     变成 0 → 被压扁到 1px：内容看不见了，但 scrollHeight==clientHeight 让「溢出检测」
     也失效（实测 45 分钟长名卡：宠物名/时间被压成 1px，我却检测不到溢出）。
     禁止压缩后，装不下就是真的 scrollHeight > clientHeight，fitCardLines 才能准确裁剪。 */
  .sd-event .evt-body > div { flex: 0 0 auto; }
  .sd-event .evt-pet { color: #1b1b1b; font-weight: 700;
                       font-size: var(--evt-fs, 11px); line-height: var(--evt-lh, 1.35);
                       letter-spacing: .1px; padding: 0; }
  /* v0609-124（用户）：时间行红、加粗（参考图实测 #DF302C） */
  .sd-event .evt-when { color: #df302c; font-weight: 700;
                        font-size: var(--evt-fs, 11px); line-height: var(--evt-lh, 1.35);
                        font-variant-numeric: tabular-nums; padding: 0; }
  .sd-event .evt-who { color: #1b1b1b; font-size: var(--evt-fs, 11px);
                       line-height: var(--evt-lh, 1.35); padding: 0; }
  /* v0609-164（用户实测「卡片里手机号字体比其他内容小」）：这里必须**显式**给字号——
     它先前只设了颜色，字号靠继承；而手机媒体查询里有两条旧规则直接命中 .evt-phone
     （9px / 9.5px），**直接命中永远赢过继承** → 只有手机号变小，同行的姓名却是 11px。
     教训：卡内每个文字元素都要显式用 --evt-fs，不要依赖继承。 */
  /* v0609-164：**加粗恢复**——v0609-39 你要求过「手机号加粗」，我在卡片改版时误改成了 400。
     现在加粗 + 完整号码 + 可点击拨打，号码是这张卡上最该被一眼看到的操作项。 */
  .sd-event .evt-phone { color: #434041; font-weight: 700; padding: 0;
                         font-size: var(--evt-fs, 11px);
                         line-height: var(--evt-lh, 1.35); }
  .sd-event .evt-svcs { color: #434041; font-size: var(--evt-fs, 11px);
                        line-height: var(--evt-lh, 1.35); padding: 0; }
  /* v0609-124（用户）：备注行——最下行、最浅（参考图实测 #747474） */
  /* v0609-134：待确认卡的「失败原因」行——灰红字，和备注同一位置但一眼能看出是原因 */
  .sd-event .evt-note.evt-why { color: #b4453c; }
  .sd-event .evt-note { color: #747474; font-size: var(--evt-fs, 11px);
                        line-height: var(--evt-lh, 1.35); padding: 0;
                        overflow-wrap: anywhere; }
  .sd-event .evt-phone::before { content: ""; }
  /* 占位卡（正在预约）保持虚线白底（直角同卡片） */
  .sd-event.sd-pending { background: #fff; border: 1px dashed #c3cade; border-top: 1px dashed #c3cade; }
  /* 卡太矮放不下备注正文时的极小角标；浮在卡头右端 */
  /* v0609-162：角标字号也并入统一值（11px）；为保证不遮住标题，给标签留的右侧内边距
     同步从 34px 加大到 40px（见渲染处的 padding-right）。 */
  .evt-notemark { position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
                  font-size: var(--evt-fs, 11px); line-height: 1.35; padding: 0 4px; border-radius: 4px;
                  background: rgba(255,255,255,.92); color: var(--evt-tx, #2a3242); font-weight: 700;
                  border: 1px solid var(--evt-bd, #e0e6f0);
                  letter-spacing: .2px; white-space: nowrap; }
  /* ---- 紧凑/极短卡降级（比例照参考图收缩） ---- */
  .sd-event.evt-sm .evt-top { padding: 3px 5px 1px 7px; gap: 3px; }
  .sd-event.evt-sm .evt-ico { width: 9px; height: 9px; }
  /* v0609-162：紧凑档字号统一由 .evt-sm 的 --evt-fs 控制，不再逐个覆盖 */
  .sd-event.evt-sm .evt-body { margin: 0 3px 3px; border-radius: 10px; padding: 6px 8px 5px; }
  /* 极短卡（≈31 分钟以内，物理装不下）：内框只留宠物名，when 挪到卡头右端小字；
     服务项目由行预算自然省略（用户 v0609-158：「极端卡就不用非得进去了」）——
     它的服务名在卡头已经显示，信息不丢。 */
  .sd-event.evt-xs .evt-who, .sd-event.evt-xs .evt-when,
  .sd-event.evt-xs .evt-note, .sd-event.evt-xs .evt-svcs { display: none; }
  .sd-event.evt-xs .evt-body { margin: 0 3px 3px; padding: 2px 6px; border-radius: 7px; }
  /* v0609-155：极短卡面板里放不下时间行 → 开始时间挪到卡头右端；
     有备注角标时不渲染（角标优先，避免重叠）。
     v0609-162：字号并入卡片统一值（11px），不再单独缩小。 */
  .evt-xs-when { margin-left: auto; padding-left: 4px; flex: 0 0 auto;
                 font-size: var(--evt-fs, 11px); font-weight: 700; color: #df302c;
                 font-variant-numeric: tabular-nums; white-space: nowrap; }
  /* ---------- v0609-163：拨打按钮（详情内）+ 拨号确认层的号码 ---------- */
  .btn-call { flex: 0 0 auto; margin-left: 8px; padding: 0 12px; min-height: 34px;
              border: 1px solid color-mix(in srgb, var(--accent) 35%, #fff);
              background: var(--accent-soft, #eef2fe); color: var(--accent-dark, #3a55d6);
              border-radius: 10px; font-size: 14px; font-weight: 700; cursor: pointer; }
  .btn-call:active { background: color-mix(in srgb, var(--accent) 16%, #fff); }
  /* ---------- v0609-280：手机号「框内圆框✕」清空 + 老客服务建议标签行 ---------- */
  /* 手机号：输入框包一层 .phone-wrap（.mrow > input 是直接子代选择器，包层后要补回 flex） */
  .phone-wrap { flex: 1 1 auto; min-width: 0; position: relative; display: flex; }
  .phone-wrap > input { flex: 1 1 auto; min-width: 0; width: 100%; padding-right: 34px; }
  /* 框内最右侧的圆框✕（灰底白叉，移动端标准清空样式）；空值时自动隐藏 */
  #fPhone:placeholder-shown ~ #fPhoneClear { display: none !important; }
  #fServiceInput:placeholder-shown ~ #fSvcClear { display: none !important; }
  /* v0609-283（用户）：圆框缩小、叉放大（18px 圆 + 13px 字，视觉更精致） */
  .btn-xclear { position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
                width: 18px; height: 18px; padding: 0; border: none; border-radius: 50%;
                background: #c6c6d2; color: #fff; font-size: 13px; line-height: 18px;
                text-align: center; cursor: pointer; -webkit-appearance: none; }
  .btn-xclear:active { background: #9a9aa8; }
  /* 服务建议：表单内直接可见的标签行（点一下即填入服务项目） */
  .svc-suggest { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 6px;
                 padding: 1px 0 3px; }
  .svc-suggest .hd { flex: 0 0 100%; font-size: 11px; color: #9a9aab; }
  .svc-chip { padding: 6px 12px; border: 1px solid color-mix(in srgb, var(--accent) 30%, #fff);
              background: var(--accent-soft, #eef2fe); color: var(--accent-dark, #3a55d6);
              border-radius: 999px; font-size: 13px; font-weight: 600; cursor: pointer;
              -webkit-user-select: none; user-select: none; }
  .svc-chip:active { background: color-mix(in srgb, var(--accent) 18%, #fff); }
  /* ---------- v0609-330（T-114）：宠老板真卡详情的「服务项目」列表（可改/可加/可删） ----------
     与 .svc-chip（新建表单的建议标签）刻意分开命名：那是"点一下填进输入框"的建议，
     这是"卡片当前的服务项目"（带 × 可删、提交时整组发后端），语义不同别复用。 */
  .svc-list-wrap { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap;
                   gap: 6px; align-items: flex-start; }
  /* 与 #svcSuggestRow 同一条理由（v0609-289 实测）：`.mrow` 的 100px 标签列会把可用宽度
     压到 ~245px，一个「基础清洁(肩高≤28cm)」就占满一行 → 一排一个、拉得很长。
     这里让标签单独占一行（仍保留「服务项目 *」标题），列表整行铺满。 */
  #fSvcListRow { display: block; }
  #fSvcListRow > label { display: block; padding: 2px 0 6px; }
  .svc-tags { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 100%; }
  .svc-tag { display: inline-flex; align-items: center; gap: 4px; padding: 5px 5px 5px 10px;
             border: 1px solid color-mix(in srgb, var(--accent) 30%, #fff);
             background: var(--accent-soft, #eef2fe); color: var(--accent-dark, #3a55d6);
             border-radius: 999px; font-size: 13px; font-weight: 600; max-width: 100%; }
  .svc-tag > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .svc-tag .x { flex: 0 0 auto; width: 18px; height: 18px; line-height: 18px; text-align: center;
                border-radius: 50%; background: #c6c6d2; color: #fff; font-size: 13px;
                cursor: pointer; -webkit-appearance: none; border: none; padding: 0; }
  .svc-tag .x:active { background: #9a9aa8; }
  .svc-tags-empty { font-size: 12px; color: #c0392b; }
  .btn-svc-add { flex: 0 0 auto; padding: 0 12px; min-height: 30px;
                 border: 1px dashed color-mix(in srgb, var(--accent) 45%, #fff);
                 background: #fff; color: var(--accent-dark, #3a55d6); border-radius: 999px;
                 font-size: 13px; font-weight: 600; cursor: pointer; }
  .btn-svc-add:active { background: var(--accent-soft, #eef2fe); }
  .svc-pick { flex: 1 1 100%; margin-top: 2px; padding: 8px; background: #fafafd;
              border: 1px solid #e6e6ee; border-radius: 10px; }
  .svc-pick-hd { font-size: 11px; color: #9a9aab; margin-bottom: 6px; }
  #fSvcPickKw { width: 100%; padding: 6px 8px; font-size: 13px; border: 1px solid #dcdce6;
                border-radius: 8px; }
  .svc-pick-list { display: flex; flex-wrap: wrap; gap: 6px; max-height: 132px;
                   overflow-y: auto; margin-top: 6px; }
  .svc-pick-empty { font-size: 12px; color: #9a9aab; }
  /* ---------- v0609-289（用户 2026-10-16）：服务建议标签行**整行铺满** ----------
     原来 #svcSuggestRow 是个 .mrow（带 100px 空标签列），标签只能从「输入框」的位置起，
     可用的宽度只剩 ~245px，一条「基础清洁(肩高≤28cm)」就占满一行 → 一排一个、拉得很长。
     改成不占标签列、从**「服务项目」标签的左边缘**开始铺到右边，标签本身 flex-wrap 排多个
     （390 宽的手机上一行能放下两条）。⚠️ 行本身的显隐仍是内联 display（JS 置 ''/none），
     这里只覆盖"显示时"的布局，不影响隐藏逻辑。 */
  #svcSuggestRow { display: block; }
  #svcSuggestRow > label { display: none; }     /* 空标签列不再占位 */
  #svcSuggestRow .svc-suggest { width: 100%; gap: 5px 6px; }
  /* ---------- v0609-282：宠物名「选档案」返回按钮（选了「手填新宠物」后切回档案下拉） ---------- */
  .btn-pick { flex: 0 0 auto; margin-left: 6px; padding: 0 11px; min-height: 34px;
              border: 1px solid #d9d9e3; background: #f6f6fa; color: #6b6b7b; border-radius: 10px;
              font-size: 13px; font-weight: 600; cursor: pointer; }
  .btn-pick:active { background: #ececf4; color: #3a55d6; }
  .sd-call-num { font-size: 20px; font-weight: 700; letter-spacing: .5px;
                 font-variant-numeric: tabular-nums; color: #1b2130; margin: 2px 0 4px; }
  /* 卡片上的手机号：可点（浅色下划线提示「可拨」），但视觉上不抢眼 */
  .sd-event .evt-phone[data-tel] { text-decoration: underline; text-decoration-style: dotted;
                                   text-underline-offset: 2px; cursor: pointer; }
  /* ---------- 底部输入条：悬浮胶囊 ---------- */
  .bk-bar { border-radius: 18px; box-shadow: var(--shadow-2); border: 1px solid var(--border); }
  .bk-bar .btn { border-radius: 13px; }
  /* v0609-165：语音输入按钮——圆形、与 38px 高的提交钮等高；录音时变红 + 呼吸圈 */
  .bk-mic { flex: 0 0 auto; width: 38px; height: 38px; padding: 0; border-radius: 50%;
            border: 1px solid var(--border-2); background: #fff; color: var(--muted);
            display: inline-flex; align-items: center; justify-content: center;
            cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .bk-mic svg { width: 20px; height: 20px; display: block; }
  .bk-mic:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, #fff); }
  /* v0609-166：当前环境不支持语音时，按钮仍显示但置灰（点它会给指引），
     不做静默隐藏——否则同事看不到按钮会以为坏了 */
  .bk-mic.unsupported { opacity: .45; }
  .bk-mic.on { background: #e5484d; border-color: #e5484d; color: #fff;
               animation: bkMicPulse 1.15s ease-in-out infinite; }
  @keyframes bkMicPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(229,72,77,.55); }
    50%      { box-shadow: 0 0 0 7px rgba(229,72,77,0); }
  }
  /* ---------- 弹窗：抽屉式 ---------- */
  .modal { border-radius: 20px 20px 0 0; box-shadow: 0 -10px 36px rgba(24,30,45,.18); }
  .modal-head h3 { font-size: 16px; font-weight: 700; letter-spacing: .2px; }
  .modal-actions .btn { border-radius: 13px; min-height: 46px; font-size: 15px; }
  /* ---------- 轻提示 ---------- */
  .toast { border-radius: 999px; background: rgba(24,30,45,.93); color: #fff;
           -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
           box-shadow: var(--shadow-2); padding: 10px 16px; font-size: 13px; }
  /* ---------- v0609-93 追加：把顶部工具栏收成「一整块」 ---------- */
  /* 门店/美容师两个下拉在手机上并排、等宽、贴合成一条（视觉上像一颗分段胶囊），
     孤立在左上角的连接状态绿点改为跟随门店下拉（不再单独占位） */
  .toolbar-controls { gap: 8px; }
  @media (max-width: 720px) {
    /* v0609-93 手机端顶栏重排（原来「绿点 + 门店 + 美容师」三个胶囊各自被文字撑宽、
       左侧那个孤立绿点还占位，看起来零碎）——现在收成一条：
         门店胶囊（内含连接状态小灯） | 美容师胶囊 | 同步小胶囊
       门店与美容师等宽平分，整条视觉上是一颗分段胶囊，绿点不再单独占位。 */
    /* v0609-97（用户问题 5）：两个下拉**等长**（原来美容师那份是 1.12 宽，看着不齐）
       + 中间留 8px 间距（原来 gap:0 是贴合的）。 */
    .toolbar-controls { gap: 8px; padding: 9px 10px 3px; align-items: stretch; flex-wrap: nowrap; }
    .toolbar-controls .field-group { flex: 1 1 0; min-width: 0; }
    .toolbar-controls .field-group select { width: 100%; }
    /* badge 收成 1px 视觉：仍保留语义与版本号 title，JS 仍按它切 online/offline 类 */
    .toolbar-controls .status-badge { position: absolute; width: 1px; height: 1px;
      padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
    /* v0609-235【用户要求「改自适应，不同手机都能适配」】—— 结构式修法，不再靠调数值：
       旧版把灯画成浮在胶囊上的绝对定位伪元素，再另写一处「文字左缩进」的固定 px，
       两者靠人工对齐；字号、字重、DPR、渲染引擎任一不同就可能压到文字上（用户「有的手机
       绿点重合」，我按 320/390 实拍复现到光晕贴字）。
       现在：① 门店组与美容师组都渲染成**胶囊**（边框/圆角/内边距挂在 group 上，内部 select
       去边框透明铺满）→ 两组结构完全一致，天然等长等高，对齐不可能错开；
       ② 灯是**真实 flex 子项**（.lamp-dot），它与文字之间的间距由浏览器按当前字号/设备自己算
       → 任何机型、任何字号都不会重叠，且没有任何写死的偏移量。
       灯色/光圈同样走 CSS 变量（setStatus 写入），旧 iOS 没有 :has() 也能正确上色。 */
    .toolbar-controls > .field-group { display: flex; align-items: center; gap: 6px;
      border: 1px solid var(--border); border-radius: 8px; background: #fff;
      padding: 0 8px; min-height: 30px; box-sizing: border-box; }
    .toolbar-controls > .field-group > select { flex: 1 1 auto; width: 100%; min-width: 0;
      border: 0; border-radius: 0; background: transparent; padding: 3px 0; min-height: 0; }
    .toolbar-controls > .field-group > select:focus { box-shadow: none; }
    .toolbar-controls .lamp-dot { display: inline-block; flex: 0 0 auto;
      width: 6px; height: 6px; border-radius: 50%;
      background: var(--lamp-color, #cbd2e0);
      box-shadow: 0 0 0 3px var(--lamp-ring-c, transparent); }
    /* v0609-97：两个下拉严格等长（用户要求） */
    .toolbar-controls .field-group:first-of-type { flex: 1 1 0; }
    .toolbar-controls .field-group:nth-of-type(2) { flex: 1 1 0; }
    /* v0609-93：手机端保留「同步」但收成图标钮（原 v0609-20 整条隐藏，用户若想手动刷新
       就没入口；看板每 30 秒自动同步，这个只是「我想立刻刷一次」用） */
    #btnSync { display: inline-flex; flex: 0 0 auto; padding: 0;
               width: 34px; min-height: 30px; align-items: center; justify-content: center;
               font-size: 15px; line-height: 1; border-radius: 11px; }
    #btnExport { display: none; }     /* 导出在手机上用不到，桌面仍有 */
    #btnSync .sync-full { display: none; }
    #btnSync .sync-icon { display: inline; }
    #btnSync.syncing .sync-icon { animation: sdspin .9s linear infinite; }
    @keyframes sdspin { to { transform: rotate(360deg); } }
    /* v0609-298：退出入口在手机上收成两字「退出」（宽度预算：390 下两个门店/美容师胶囊
       各 ≈126px 仍不挤压，整条不折行、无横向滚动）；title/aria-label 保留完整语义。
       高度/圆角与 #btnSync 图标钮一致（30px / 11px）。 */
    #btnLogout { display: inline-flex; flex: 0 0 auto; align-items: center; padding: 0 10px;
                 min-height: 30px; font-size: 12px; border-radius: 11px; }
    #btnLogout .logout-full { display: none; }
    #btnLogout .logout-short { display: inline; }
  }
  /* ---------- 手机端：信息密度与触感 ---------- */
  @media (max-width: 720px) {
    .view-bar { padding: 10px 10px 2px; gap: 6px; }
    /* v0609-97：日期改为按内容自适应（原来 min-width:106px 会把「今天」标记挤掉），
       字号略收一点，给右侧导航留出空间 */
    /* v0609-97：日期列给一个「内容刚好放得下」的最小宽（112px）——完全不设会被
       grid 压到 86px，「今天」标记被挤出去（实测）。112 + 导航 119 + gap 12 = 243，
       390px 内容区 370px 下两边 1fr 各 ~63px，日期与导航之间还留 7px 间隔。 */
    .view-bar .sd-date { min-width: 112px; }
    .view-bar .sd-date #sdDate { font-size: 14px; letter-spacing: .2px; }
    .view-bar .sd-week { font-size: 11px; margin-left: 4px; }
    .sd-vnav { gap: 4px; }
    .sd-vnav .btn { min-height: 34px; padding: 4px 10px; border-radius: 11px; font-size: 13px;
                    white-space: nowrap; }
    .sd-vnav #vbPrev, .sd-vnav #vbNext { min-width: 38px; font-size: 18px; }
    #staffDayWrap { padding: 2px 8px 10px; }
    /* v0609-97（用户问题 3）：时间列再收 1/3（62 → 42px），省下的宽度全给卡片列
       （.sd-col 是 flex:1 自动吃掉）；`.sd-timecol` 与 `.sd-corner` 必须同宽（几何铁律），
       字号同步降到 10px 以免「10:00」被挤断。 */
    .sd-timecol { flex: 0 0 42px; }
    .sd-headrow .sd-corner { flex: 0 0 42px; min-width: 0; max-width: 42px; }
    .sd-timecell { font-size: 10px; padding-right: 4px; letter-spacing: -.2px; }
    .sd-headrow .sd-colhead { font-size: 12.5px; padding: 8px 4px; }
    .sd-colhead .sd-colstore { font-size: 9.5px; }
    /* 卡片字级：宠物名 12px 主打，其余 10px 辅助——三层字阶一眼分得清 */
    /* v0609-162：手机端不再单独调卡片字号——统一走 --evt-fs（用户要求卡内等大） */
    /* v0609-128：顶部不再留 1px —— 服务标签即卡顶，留 1px 会在窄屏上把标签顶下去、
       露出白色小缝（桌面无此 padding 所以只有手机看得到）。底部 2px 保留（末行呼吸感）。 */
    .sd-event { left: 3px; right: 3px; border-radius: 0; padding: 0; }   /* v0609-156：直角 + 3px 内缩（同桌面） */
    /* 长单（>2 小时）给主人行留出一点下方呼吸，短单不受影响 */
    .sd-event:not(.evt-sm) .evt-who { padding-bottom: 2px; }
    /* v0609-162：紧凑档字号统一走 --evt-fs（原逐元素覆盖已删） */
  }

  /* v0609-97（用户问题 6）：‹/今天/› 原来三个各自带边框的圆角按钮并排，手机上显得零碎。
     改成**一枚分段控件**：整条一个胶囊、按钮之间一条细分隔线、按下浅灰底——
     视觉上是一个整体，也是 iOS/微信里用户最熟悉的形态。 */
  /* ⚠️ 选择器必须带 .view-bar 前缀：媒体查询里已有一条 `.view-bar .sd-vnav #vbPrev`
     （特异性 1-2-0）会压过 `.sd-vnav #vbPrev`（1-1-0）——第一次改完实测按钮仍是 44px、
     导航总宽 145px 溢出第三列（135.9px）盖住日期，就是这个原因。 */
  .view-bar .sd-vnav { display: inline-flex; align-items: stretch; gap: 0; background: #fff;
             border: 1px solid var(--border-2); border-radius: 12px; overflow: hidden;
             box-shadow: 0 1px 2px rgba(24,30,45,.06); }
  .view-bar .sd-vnav .btn { border: none; background: transparent; border-radius: 0; box-shadow: none;
                  display: inline-flex; align-items: center; justify-content: center; }
  .view-bar .sd-vnav .btn + .btn { border-left: 1px solid var(--border); }
  .view-bar .sd-vnav .btn:hover { background: #f5f7fc; }
  .view-bar .sd-vnav .btn:active { background: #eef1f8; transform: none; }
  /* 尺寸要能塞进「1fr auto 1fr」的第三列（390px 屏上该列约 136px）：
     36 + 44 + 36 + 2(分隔线) = 118px，留出余量，日期列也能拿到更宽的内容宽度。 */
  .view-bar .sd-vnav #vbPrev, .view-bar .sd-vnav #vbNext {
    min-width: 36px; min-height: 34px; padding: 0 4px; font-size: 19px; font-weight: 500; line-height: 1; }
  .view-bar .sd-vnav #vbToday { min-height: 34px; padding: 0 9px; font-size: 13px; font-weight: 650; }
