/* =====================================================================
   艾灸智能诊断系统 · 设计系统
   移动优先，温暖中医调性
   ===================================================================== */
:root {
  /* 版面宽度：手机端 480px 一列；折叠屏展开/平板按断点放宽（见文件末尾「宽屏适配」）。
     🔴 所有 fixed 层（底栏、抽屉、弹层）与 .app **必须共用这个变量** ——
        它们各自写死 480px 时，一旦 .app 放宽就会错位（fixed 不继承父容器的居中）。 */
  --app-w: 480px;
  /* 内容区两侧的留白（宽屏时 > 0）。
     🔴 所有 `position: fixed` 且写 right/left 贴边的元素都必须叠加这个值 ——
        fixed 是相对**视口**定位的，不认 .app 的居中：折叠屏展开时
        浮钮、麦克风、字幕条会直接飘到页面外面。 */
  --app-gutter: max(0px, calc((100vw - var(--app-w)) / 2));
  --bg: #F5F2ED;
  --bg-alt: #EFEAE2;
  --card: #FFFFFF;
  --primary: #6B8E7F;
  --primary-deep: #4F6E60;
  --primary-soft: #EDF3F0;
  --accent: #D4915C;
  --accent-deep: #B5763F;
  --accent-soft: #FBF1E7;
  --danger: #C46A5A;
  --danger-soft: #F9EFED;
  --warn-soft: #FDF6EC;
  --warn-text: #B8843A;
  --text: #2E2B26;
  --text-sub: #7D766D;
  --text-faint: #A39C92;
  --line: #E8E3DA;
  --line-soft: #F0EBE3;
  --zhou: #C4714A;
  --cheng: #5B7FA6;
  --ma: #5F8A78;
  --r-lg: 18px;
  --r-md: 14px;
  --r-sm: 10px;
  --shadow: 0 4px 16px rgba(46, 43, 38, .06);
  --shadow-lg: 0 10px 30px -12px rgba(46, 43, 38, .18);
  /* 右下角浮动按钮的基准线（2026-10-31）：让开底部导航/操作条（约 84px）+ 安全区。
     「帮助」直接站在这条线上；「回到顶部」站在它上方一个按钮位。
     两个都从这里算，以后调底栏高度只改这一处，不会出现一上一下错位。 */
  /* --bot-h 由 app.js 按 #bottombar 实测高度写入（各页底栏 60~110px 不等）；
     写死 84px 时浮钮会骑在底栏上，或把上面一行内容盖住。缺省值仍是 84px。 */
  --fab-bottom: calc(var(--bot-h, 84px) + env(safe-area-inset-bottom));
  --fab-h: 44px;
  --fab-gap: 10px;
  --sans: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", sans-serif;
  --serif: "Noto Serif SC", "Songti SC", "STSong", "SimSun", serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }

/* 🔴 2026-11-02 多设备适配：禁掉浏览器的「自动文字放大」(font boosting)。
   华为/小米/OPPO 等国产浏览器在 text-size-adjust:auto 下会按块长度偷偷放大字号
   （同一份页面，华为折叠屏内屏可能放大 1.1~1.3 倍，iPhone 完全不放大）——
   这就是「有的会员手机字会折行、有的不会」的根因。
   关掉它不影响无障碍：我们自己的 A⁺ 大字体（html.bigfont）才是唯一的放大入口，
   两套放大叠加必然撑破布局。 */
html {
  -webkit-text-size-adjust: 100%;
  -moz-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  font-family: var(--sans);
  background: var(--bg-alt);
  color: var(--text);
  /* 2026-10-31 工单 T4：正文 15 → 16px。实测 35~55 岁自助用户在大字体模式之外仍嫌小。 */
  font-size: 16px; line-height: 1.7;
  display: flex; justify-content: center;
  -webkit-font-smoothing: antialiased;
}
.app {
  width: 100%; max-width: var(--app-w); min-height: 100vh; min-height: 100dvh;
  background: var(--bg); display: flex; flex-direction: column;
  position: relative; box-shadow: 0 0 40px rgba(0,0,0,.05);
}
button { font-family: inherit; cursor: pointer; border: none; background: none; }
a { color: inherit; text-decoration: none; }
.hidden { display: none !important; }

/* ---------------- 顶栏 ---------------- */
.topbar { flex: none; background: var(--bg); position: sticky; top: 0; z-index: 20; }
/* unslop（2026-11-05）：顶栏装饰条从绿→橙跨色渐变收成单色墨绿 —— 跨色相渐变是典型脏项，
   装饰条不需要两个色相；v2 里另有 3px 覆盖，两处同改。 */
.accentbar { height: 4px; background: var(--primary); }
.nav {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 16px; border-bottom: 1px solid transparent;
  transition: border-color .2s;
}
/* 家庭账号 · 代操作提示条（2026-10-19）
   插在 #nav（topbar）内部末尾，所以跟着 topbar 的 sticky 一起粘顶；
   只有户主切进家人档案时才由 app.js 注入，其余情况不存在这个元素。 */
.acting-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 14px; background: var(--accent-soft);
  border-top: 1px solid #F0DCC6; font-size: 16px; line-height: 1.55;
  color: var(--text-sub);
}
.acting-bar b { color: var(--text); }
.acting-bar button {
  margin-left: auto; flex: none; padding: 4px 10px; border-radius: 6px;
  background: var(--primary); color: #fff; font-size: 16px; font-weight: 600;
}
.nav.scrolled { border-bottom-color: var(--line); background: rgba(245,242,237,.92); backdrop-filter: blur(10px); }
.nav .back {
  width: 34px; height: 34px; flex: none; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  background: var(--card); border: 1px solid var(--line); color: var(--text-sub);
}
.nav .back:active { background: var(--bg-alt); }
/* ⚠️ 2026-11-02 顶栏一屏修复：brand 是 flex:1，min-width:0 会让它被右侧控件压成 **0 宽**
   （390px 首页实测：logo「灸」宽度 0、只剩一条竖线，后面控件还得往外挤 9px、整页能横拉）。
   所以给它一个「logo 宽度」的硬底线 —— 宁可让标题文字省略，也不许品牌标消失。 */
.brand { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 700;
         flex: 1 1 auto; min-width: 28px; }
/* 顶栏标题要包一层 .bn 才能「省略号收尾」：裸文本是 flex 匿名项，text-overflow 对它不生效，
   空间不够时它会折成两行（后台页实测「管理 / 后台」「数据 / 运维」「退 / 出」竖排）。
   ⚠️ 这条必须在 app.css（基础层）—— 后台页按约定不引 app-v2.css。 */
.brand .bn { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 首页显式传 title:'' （只留品牌标，页内已有大字标题）—— 空 span 不能白吃 brand 的 8px gap */
.brand .bn:empty { display: none; }
.brand .logo {
  width: 28px; height: 28px; border-radius: 9px; flex: none;
  background: var(--primary-soft); color: var(--primary);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-size: 16px; font-weight: 700;
}
/* 名字胶囊：390px 首页实测「⚙ + 5 字名」要 99px，原来的 100px 上限刚好卡在临界，
   多一个字就被截成「⚙ 系统管理…」。顶栏右侧瘦身后有富余，放宽到 132px。 */
.nav .who {
  font-size: 13px; color: var(--text-sub); flex: none;
  background: var(--card); border: 1px solid var(--line);
  padding: 4px 10px; border-radius: 6px; max-width: 132px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------------- 内容区 ---------------- */
.content { flex: 1; padding: 16px 16px 130px; overflow-x: hidden; }
.screen { animation: fadein .3s ease; }
@keyframes fadein { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ---------------- 首页 Hero ---------------- */
.hero { text-align: center; padding: 26px 0 4px; }
.hero .mark {
  width: 84px; height: 84px; border-radius: 26px; margin: 0 auto 18px;
  background: linear-gradient(140deg, var(--primary-soft), #fff);
  border: 1px solid #DFE9E4;
  display: flex; align-items: center; justify-content: center;
}
.hero h1 { font-family: var(--serif); font-size: 26px; font-weight: 600; letter-spacing: 1px; margin-bottom: 8px; color: var(--text); }
.hero .sub { font-size: 16px; color: var(--text-sub); line-height: 1.8; }

/* ---------------- 卡片 ---------------- */
.card { background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow); padding: 16px; margin-bottom: 12px; }
.card-title { font-size: 16px; font-weight: 700; margin-bottom: 10px; display: flex; align-items: center; gap: 7px; }
/* unslop（2026-11-05）：标题前的 ❖ 菱形换成艾叶剪影（与 logo 同一图形语言，
   mask 抠出中脉）；颜色吃 currentColor —— 危险卡标题红叶、普通标题随文字色，
   不再自挂固定橙色。全站唯一装饰符，别再往卡片标题前加别的图形。 */
.card-title::before {
  content: ""; flex: none; width: 13px; height: 13px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.9 2.1C8.6 2.3 4.2 4.6 3 10.2c-.2 1 0 2.2.1 3.4 1.2.1 2.4.3 3.4.1C12.1 12.4 14.1 7.4 13.9 2.1Z'/%3E%3Cpath d='M3.6 13C6 9.2 9 6.2 12.8 3.4' stroke='%23fff' stroke-width='1.3' fill='none'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.9 2.1C8.6 2.3 4.2 4.6 3 10.2c-.2 1 0 2.2.1 3.4 1.2.1 2.4.3 3.4.1C12.1 12.4 14.1 7.4 13.9 2.1Z'/%3E%3Cpath d='M3.6 13C6 9.2 9 6.2 12.8 3.4' stroke='%23fff' stroke-width='1.3' fill='none'/%3E%3C/svg%3E") center / contain no-repeat;
}
.card ul { list-style: none; font-size: 16px; color: var(--text-sub); line-height: 1.95; }
.card ul li { padding-left: 15px; position: relative; }
.card ul li::before { content: "·"; position: absolute; left: 3px; color: var(--accent); font-weight: 700; }

/* ---------------- 大师卡 ---------------- */
.master-card {
  display: flex; gap: 13px; align-items: flex-start;
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow);
  padding: 15px 16px; margin-bottom: 10px;
  border-left: 3px solid transparent;
}
.master-card.m-zhou { border-left-color: var(--zhou); }
.master-card.m-cheng { border-left-color: var(--cheng); }
.master-card.m-ma { border-left-color: var(--ma); }
.avatar {
  width: 46px; height: 46px; border-radius: 14px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-size: 20px; font-weight: 700; color: #fff;
}
.a-zhou { background: var(--zhou); } .a-cheng { background: var(--cheng); } .a-ma { background: var(--ma); }
.master-card .m-name { font-size: 16px; font-weight: 700; }
.master-card .m-title { font-size: 16px; color: var(--text-sub); margin-top: 2px; }
.master-card .m-book { font-size: 16px; color: var(--primary); margin-top: 4px; }
.master-card .m-desc { font-size: 16px; color: var(--text-sub); line-height: 1.8; margin-top: 7px; }
.chip-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.chip {
  font-size: 13px; padding: 3px 9px; border-radius: 6px;
  background: var(--bg-alt); color: var(--text-sub);
}
/* 2026-11-02 适老：可点击的 chip（穴位快捷、特征多选）原来只有 23px 高，
   约等于拇指接触面积的 1/4，老人必然点错相邻项。文字标签本身是不点的，
   所以只抬 button.chip，不动纯展示的 span.chip。 */
button.chip { min-height: 40px; padding: 9px 13px; }
.chip-row { gap: 8px; }
.chip.green { background: var(--primary-soft); color: var(--primary-deep); }
.chip.orange { background: var(--accent-soft); color: var(--accent-deep); }
.chip.warn { background: var(--warn-soft); color: var(--warn-text); }
.chip.danger { background: var(--danger-soft); color: var(--danger); }
/* 可点击的特征 chip（舌面象手填 / 反馈反应多选） */
.chip.pick {
  cursor: pointer; padding: 6px 11px; font-size: 13px;
  border: 1px solid var(--line); transition: background .16s, color .16s, border-color .16s;
  -webkit-tap-highlight-color: transparent;
}
.chip.pick.on {
  background: var(--primary); color: #fff;
  border-color: var(--primary); font-weight: 600;
}

/* ---------------- 按钮 ---------------- */
.btn {
  width: 100%; border: none; border-radius: 13px; padding: 15px;
  font-size: 16px; font-weight: 700; font-family: inherit; letter-spacing: .5px;
  transition: background-color .18s, color .18s, box-shadow .18s, transform .18s; display: flex; align-items: center; justify-content: center; gap: 8px;
}
.btn { line-height: 1.45; }
.btn:active { transform: scale(.985); }
.btn-primary { background: var(--primary); color: #fff; box-shadow: 0 6px 16px -8px rgba(46,43,38,.35); }
.btn-primary:disabled { background: #C9D4CF; color: #EDF3F0; box-shadow: none; }
.btn-accent { background: var(--accent); color: #fff; box-shadow: 0 6px 16px -8px rgba(46,43,38,.35); }
.btn-ghost { background: transparent; color: var(--text-sub); border: 1.5px solid var(--line); }
.btn-sm { padding: 12px; font-size: 16px; }
.btn-row { display: flex; gap: 10px; }
/* 并排按钮：.btn 自己是 width:100%，两个挤在一起时按 flex-shrink 分，
   短文案那颗会被压到只剩 69px，于是「先不设」折成「先不 / 设」。
   改成按内容撑开 + 等分剩余空间。 */
.btn-row .btn { flex: 1 1 0; width: auto; min-width: max-content; white-space: nowrap; }
/* 副操作（ghost）按内容宽、主操作吃掉剩余 —— 否则「先不设 / 保存」被等分，
   主按钮反而变窄，跟视觉权重相反 */
.btn-row .btn-ghost { flex: 0 0 auto; }

.bottombar {
  position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: var(--app-w); z-index: 30;
  background: rgba(245,242,237,.94); backdrop-filter: blur(12px);
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  display: flex; gap: 10px;
  transition: transform .25s ease, opacity .25s;
}
.bottombar.hide { transform: translateX(-50%) translateY(110%); opacity: 0; pointer-events: none; }
/* .bottombar 是 flex，里面的 .btn-row 默认 flex:0 1 auto —— 宽度只等于内容宽，
   于是只有一颗按钮的底栏（反馈的「下一步」、回访的「保存回访」）永远只占六成宽，
   右边留一条空白。字号 16 之后按钮变宽，这条空白更显眼。 */
.bottombar > .btn-row { flex: 1 1 100%; width: 100%; }
.bottombar > .btn { flex: 1 1 auto; width: auto; }

/* ---------------- 进度 ---------------- */
.step { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.step .label { font-size: 16px; font-weight: 700; flex: none; }
.step .label em { font-style: normal; color: var(--text-sub); font-weight: 400; font-size: 16px; }
.progress { flex: 1; height: 5px; background: var(--line); border-radius: 3px; overflow: hidden; }
.progress i { display: block; height: 100%; background: linear-gradient(90deg, var(--primary), #8FAF9F); border-radius: 3px; transition: width .4s ease; }

/* 段标题行（2026-11-02 修）：原来是不换行的 flex，右侧 .tip 一旦是长句
   （问卷第 1 步那段 40 字的橙色提醒），就会把左侧标题压到只剩一个字宽 ——
   实测「禁忌筛查」竖排成四行一字一行，是全站最刺眼的一处。
   现在：标题不收缩、tip 允许折到下一行占满，两边都不再互相挤。 */
.section { display: flex; align-items: center; gap: 8px; margin: 18px 0 12px; flex-wrap: wrap; }
.section .bar { width: 4px; height: 16px; border-radius: 2px; background: var(--primary); flex: none; }
.section h2 { font-size: 16px; font-weight: 700; flex: none; white-space: nowrap; }
.section .tip { margin-left: auto; font-size: 16px; color: var(--warn-text); background: var(--warn-soft); padding: 3px 8px; border-radius: 6px; flex: 0 1 auto; max-width: 100%; }

/* ---------------- 题目卡 ---------------- */
.qcard { background: var(--card); border-radius: var(--r-md); padding: 15px; box-shadow: var(--shadow); margin-bottom: 11px; }
.qcard.invalid { border: 1.5px solid var(--danger); background: var(--danger-soft); }
.qcard .q { font-size: 16px; font-weight: 600; line-height: 1.65; margin-bottom: 11px; }
.qcard .q .num { color: var(--accent); margin-right: 6px; font-size: 13px; font-weight: 700; }
.qcard .q-hint { font-size: 16px; color: var(--text-faint); margin: -6px 0 9px; line-height: 1.7; }
/* 出处引用（2026-11-02）：如「依据 《马氏温灸法》第一部分 四、（五）其他注意事项」
   23 字，16px 要 369px、卡内只有 327px —— 差 42px，必然折出 3 字末行。
   这是文献名，属**次要信息**（不参与阅读主链），按次要档 13px 处理即可整行放下。
   ⚠️ 不用 text-wrap:balance 兜底：中文没有词边界，强行等长会断在词中间
      （实测把「判定分」切成「判 / 定分」），比末行短几个字更难看。 */
.qcard .q-src { font-size: 13px; color: var(--text-faint); margin-top: 9px; padding-top: 8px; border-top: 1px dashed var(--line); line-height: 1.7; }
.opts { display: flex; flex-direction: column; gap: 8px; }
.opts.two { flex-direction: row; }
.opts.two .opt { flex: 1; justify-content: center; }
.opt {
  border: 1.5px solid var(--line); border-radius: 11px; padding: 12px 14px;
  font-size: 16px; color: var(--text); transition: background-color .16s, color .16s, border-color .16s; background: #fff;
  display: flex; align-items: center; gap: 9px; text-align: left;
}
.opt .radio { width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--line); flex: none; position: relative; transition: background-color .16s, border-color .16s; }
.opt.selected { border-color: var(--primary); background: var(--primary-soft); font-weight: 600; }
.opt.selected .radio { border-color: var(--primary); }
.opt.selected .radio::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--primary); }
.opt.yes-selected { border-color: var(--danger); background: var(--danger-soft); font-weight: 600; }
.opt.yes-selected .radio { border-color: var(--danger); }
.opt.yes-selected .radio::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--danger); }

.alert { border-radius: 11px; padding: 12px 14px; font-size: 16px; line-height: 1.75; margin-bottom: 12px; }
.alert.danger { background: var(--danger-soft); color: var(--danger); border: 1px solid #EAD5D0; }
.alert.warn { background: var(--warn-soft); color: var(--warn-text); }
.alert.info { background: var(--primary-soft); color: var(--primary-deep); }
.alert strong { display: block; margin-bottom: 4px; font-size: 16px; }

/* ---------------- 拍照 ---------------- */
.shot-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 14px; }
.shot {
  position: relative; aspect-ratio: 3/4; border-radius: var(--r-md);
  border: 1.5px dashed #D6CFC3; background: var(--card);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  overflow: hidden; color: var(--text-sub); font-size: 16px; transition: border-color .18s, background-color .18s, color .18s;
}
.shot:active { border-color: var(--primary); background: var(--primary-soft); }
.shot.filled { border-style: solid; border-color: var(--primary); }
.shot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.shot .shot-tag {
  position: absolute; top: 8px; left: 8px; z-index: 2;
  font-size: 13px; font-weight: 700; color: #fff;
  background: rgba(46,43,38,.62); padding: 3px 9px; border-radius: 6px;
  backdrop-filter: blur(4px);
}
.shot .shot-retake {
  position: absolute; bottom: 8px; right: 8px; z-index: 2;
  font-size: 16px; color: #fff; background: rgba(46,43,38,.62);
  padding: 5px 11px; border-radius: 6px; backdrop-filter: blur(4px);
}
.shot .shot-icon { width: 34px; height: 34px; opacity: .45; }
.shot .shot-sub { font-size: 13px; color: var(--text-faint); }

.tips-list { font-size: 16px; color: var(--text-sub); line-height: 1.9; list-style: none; }
.tips-list li { padding-left: 16px; position: relative; }
.tips-list li::before { content: "·"; position: absolute; left: 4px; color: var(--accent); font-weight: 700; }

/* ---------------- 结果页 ---------------- */
.verdict {
  background: linear-gradient(135deg, #6B8E7F, #55705F);
  border-radius: var(--r-md); padding: 22px 18px; text-align: center; color: #fff;
  margin-bottom: 14px; box-shadow: 0 1px 2px rgba(26,42,34,.08);
}
.verdict .lab { font-size: 16px; letter-spacing: 1px; opacity: .82; margin-bottom: 8px; }
.verdict .who { font-family: var(--serif); font-size: 23px; font-weight: 600; letter-spacing: 1px; }
.verdict .pct { font-size: 16px; opacity: .93; margin-top: 8px; }
.verdict .pct b { font-size: 16px; }
.verdict.danger { background: linear-gradient(135deg, #C46A5A, #A9543F); }

.badge {
  display: flex; align-items: center; gap: 10px; background: var(--card);
  border-radius: var(--r-md); box-shadow: var(--shadow); padding: 14px 16px; margin-bottom: 12px;
}
.badge .bd-icon { width: 38px; height: 38px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 17px; flex: none; font-weight: 700; }
.badge .bd-txt { flex: 1; min-width: 0; }
.badge .bd-txt .bd-t { font-size: 16px; font-weight: 700; }
.badge .bd-txt .bd-s { font-size: 16px; color: var(--text-sub); margin-top: 3px; line-height: 1.6; }
.badge .tag { font-size: 13px; font-weight: 700; padding: 4px 10px; border-radius: 6px; flex: none; }
.tag-high { color: var(--primary-deep); background: var(--primary-soft); }
.tag-mid { color: var(--warn-text); background: var(--warn-soft); }
.tag-low { color: var(--text-sub); background: var(--bg-alt); }
.tag-danger { color: var(--danger); background: var(--danger-soft); }

.sec-t { font-size: 16px; font-weight: 700; color: var(--text-sub); letter-spacing: 1.5px; margin: 18px 0 10px 2px; }
.panel { background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow); padding: 16px; }
.panel p.para { font-size: 16px; color: var(--text-sub); line-height: 1.95; margin: 0 0 10px; }
.panel p.para:last-child { margin-bottom: 0; }
.panel p.para b { color: var(--text); }

.rank-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.rank-row:last-child { margin-bottom: 0; }
.rank-row .nm { font-size: 16px; width: 74px; flex: none; color: var(--text-sub); }
.rank-row .track { flex: 1; height: 7px; background: var(--line); border-radius: 4px; overflow: hidden; }
.rank-row .track i { display: block; height: 100%; border-radius: 4px; transition: width .5s ease; }
.rank-row .pc { font-size: 16px; font-weight: 700; width: 40px; text-align: right; flex: none; }
/* 匹配度行的角色标签 + 判定理由（2026-10-17：分值是判定分，不是问卷分数） */
.rank-row .rl-tag { font-size: 16px; padding: 2px 7px; border-radius: 6px; flex: none;
  background: var(--line); color: var(--text-sub); font-weight: 600; }
.rank-row .rl-tag.on { background: var(--accent-soft); color: var(--accent-deep); font-weight: 700; }
/* 匹配度行的判定理由：原来 margin-left:84px 对齐「马少群」姓名列，
   16px 下可用宽只剩 242px，「本证型声明的体质只有该家专长覆盖」（需 256px）
   就被折出 2 字末行。理由是一整句，占满行宽更顺，也不再被挤。 */
.rl-why { font-size: 16px; color: var(--text-faint); line-height: 1.6; margin: -6px 0 10px; }

/* 方案页「具体怎么做」折叠块（2026-10-17）：默认收起，让页面先给方案本身 */
details.how-to {
  margin: 4px 0 14px; border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--card); overflow: hidden;
}
details.how-to > summary {
  padding: 13px 15px; font-size: 16px; font-weight: 600; cursor: pointer;
  list-style: none; display: flex; align-items: center; gap: 8px; color: var(--text-sub);
}
details.how-to > summary::-webkit-details-marker { display: none; }
details.how-to > summary::before {
  content: '▸'; font-size: 16px; color: var(--text-faint); transition: transform .2s ease;
}
details.how-to[open] > summary::before { transform: rotate(90deg); }
details.how-to[open] > summary { border-bottom: 1px dashed var(--line); color: var(--text); }
details.how-to .tbl-wrap { margin: 0; border: none; border-radius: 0; }

.info-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; padding: 12px 0; border-bottom: 1px dashed var(--line); font-size: 16px; }
.info-row:last-child { border-bottom: none; padding-bottom: 0; }
.info-row:first-child { padding-top: 0; }
.info-row .k { color: var(--text-sub); flex: none; }
.info-row .v { font-weight: 600; text-align: right; }

/* 穴位条目 */
.point-item { border-bottom: 1px dashed var(--line); padding: 13px 0; }
.point-item:last-child { border-bottom: none; padding-bottom: 0; }
.point-item:first-child { padding-top: 0; }
.point-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.point-head .pn { font-family: var(--serif); font-size: 16px; font-weight: 700; }
.point-head .pcode { font-size: 16px; color: var(--text-faint); }
.point-head .pmin { margin-left: auto; font-size: 16px; font-weight: 700; color: var(--accent-deep); flex: none; }
.point-body { font-size: 16px; color: var(--text-sub); line-height: 1.8; }
.point-body .lbl { color: var(--text-faint); }
.point-why { font-size: 16px; color: var(--text-faint); line-height: 1.75; margin-top: 6px; padding-left: 2px; }
.point-why summary { cursor: pointer; color: var(--primary); font-size: 16px; list-style: none; }
.point-why summary::-webkit-details-marker { display: none; }
.point-why summary::before { content: "▸ "; }
.point-why[open] summary::before { content: "▾ "; }

/* ---- 背 / 正面子方案（一次施灸拆成两段，先背面再正面） ---- */
.plan-sec { margin-bottom: 10px; border-left: 3px solid var(--accent); }
.plan-sec:last-of-type { margin-bottom: 0; }
.plan-sec-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 8px; border-bottom: 1px dashed var(--line); }
.plan-sec-step { flex: none; font-size: 16px; font-weight: 700; color: var(--accent-deep); line-height: 1; }
.plan-sec-title { font-family: var(--serif); font-size: 16px; font-weight: 600; }
.plan-sec-meta { margin-left: auto; font-size: 13px; color: var(--text-faint); white-space: nowrap; }
.plan-sec-hint { font-size: 13px; color: var(--text-sub); line-height: 1.7; padding: 8px 0 2px; }
.plan-sec-foot { padding-top: 10px; margin-top: 6px; border-top: 1px dashed var(--line); }
.point-jump { margin-top: 8px; }
/* nowrap：顶栏和卡片头里的 .btn-xs 放不下就折行，把「退出」挤成竖排「退 / 出」
   （2026-11-02 后台页实测）。小按钮本来就只有 2~4 个字，宁可挤走同级元素也不折字。 */
.btn-xs { font-size: 16px; padding: 6px 11px; border-radius: 8px; width: auto; white-space: nowrap; }

/* 阶段 */
.phase { padding: 12px 0; border-bottom: 1px dashed var(--line); }
.phase:last-child { border-bottom: none; padding-bottom: 0; }
.phase:first-child { padding-top: 0; }
.phase-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.phase-badge { font-size: 13px; font-weight: 700; color: #fff; background: var(--accent); padding: 3px 9px; border-radius: 6px; flex: none; }
.phase-badge.s2 { background: #7A9C8B; }
.phase-badge.s3 { background: var(--primary-deep); }
.phase-badge.dim { background: var(--line); color: var(--text-faint); }
.phase-name { font-family: var(--serif); font-size: 16px; font-weight: 600; }
.phase-goal { font-size: 16px; color: var(--text-faint); }
.phase-plan { font-size: 16px; line-height: 1.85; color: var(--text-sub); margin-top: 6px; }

/* 折叠 */
.acc { background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow); overflow: hidden; margin-bottom: 10px; }
.acc summary { padding: 14px 16px; font-size: 16px; font-weight: 600; cursor: pointer; list-style: none; display: flex; align-items: center; gap: 9px; }
.acc summary::-webkit-details-marker { display: none; }
.acc summary::after { content: "展开"; margin-left: auto; color: var(--text-faint); font-size: 16px; font-weight: 400; }
.acc[open] summary::after { content: "收起"; }
.acc .body { padding: 0 16px 15px; font-size: 16px; color: var(--text-sub); line-height: 1.9; }
.acc .body p { margin: 0 0 8px; }
.acc .body p:last-child { margin-bottom: 0; }
.acc .body b { color: var(--text); }

/* ---------------------------------------------------------------------
   折叠标题收一档（2026-11-02 用户口径：「本来一行就能做下的，现在变成两行」）
   ---------------------------------------------------------------------
   实测：`🔺 安全与灸后护理（其余禁忌 · 灸后 · 常见反应）` 单行需 364px，
   卡内可用只有 326px。差的 38px 里，41px 被右侧「展开」两字（16px）和
   ▸ + gap 吃掉 —— 所以先把导航辅助收一档，标题就回到一行，
   比整体缩字号划算（用户要的是「一行」，不是「小字」）。
   触控高度不变：15px 字 + 12px 上下内边距 ≈ 46px，仍高于 44px 的点击标准。

   ⚠️ 同时把 flex 换成 grid（2026-11-02）：flex 的文本子项是「flex item」，
      `text-wrap: pretty` 管不到它，20 字以上的长标题会被切成「18 字 + 孤零零 2 字」
      （「三位大家方案匹配度（判定分 · 为什么是这 / 一位）」）。
      grid 让标题成为独立的 1fr 文本块，pretty 才能均衡末行。
   --------------------------------------------------------------------- */
.acc summary {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  column-gap: 8px; padding: 12px 13px; font-size: 15px;
}
.acc summary::after { font-size: 13px; margin-left: 0; justify-self: end; }
details.how-to > summary {
  display: grid; grid-template-columns: auto 1fr; align-items: center;
  column-gap: 6px; padding: 12px 13px; font-size: 15px;
}
details.how-to > summary::before { font-size: 13px; }

.disclaimer {
  font-size: 13px; color: var(--text-faint); line-height: 1.85;
  background: var(--bg-alt); border-radius: 12px; padding: 13px 15px; margin: 14px 0 0;
}
.disclaimer b { color: var(--text-sub); }
/* 空的免责块会留一个灰底圆角框（有背景、有内边距，却没字），
   在预约页尾部看起来像一张空白卡片。没内容就不占位。 */
.disclaimer:empty { display: none; }

/* ---------------- 时间线 ---------------- */
.tl { position: relative; padding-left: 26px; }
.tl::before { content: ""; position: absolute; left: 7px; top: 6px; bottom: 6px; width: 1.5px; background: var(--line); }
.tl-item { position: relative; margin-bottom: 14px; }
.tl-item::before {
  content: ""; position: absolute; left: -23px; top: 7px;
  width: 11px; height: 11px; border-radius: 50%; background: var(--card);
  border: 2.5px solid var(--line); box-sizing: border-box;
}
.tl-item.t-screening::before { border-color: var(--primary); }
.tl-item.t-plan::before { border-color: var(--accent); }
.tl-item.t-feedback::before { border-color: #8FAF9F; }
.tl-item.t-photo::before { border-color: #B9AFA0; }
.tl-item.t-appointment::before { border-color: #C9A227; }
.tl-card { background: var(--card); border-radius: var(--r-sm); padding: 12px 14px; box-shadow: var(--shadow); }
.tl-card .tl-t { font-size: 16px; font-weight: 700; }
.tl-card .tl-s { font-size: 16px; color: var(--text-sub); margin-top: 4px; line-height: 1.7; }
.tl-card .tl-at { font-size: 16px; color: var(--text-faint); margin-top: 6px; }
.tl-card img { width: 100%; max-height: 150px; object-fit: cover; border-radius: 8px; margin-top: 8px; }

.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 14px; }
.stat { background: var(--card); border-radius: var(--r-sm); padding: 11px 6px; text-align: center; box-shadow: var(--shadow); }
.stat .v { font-family: var(--serif); font-size: 20px; font-weight: 700; color: var(--primary-deep); }
.stat .k { font-size: 16px; color: var(--text-faint); margin-top: 2px; }

/* ---------------- 反馈打分 ---------------- */
.scale { display: flex; gap: 6px; }
.scale .dot {
  flex: 1; aspect-ratio: 1; border-radius: 10px; border: 1.5px solid var(--line);
  background: #fff; display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 700; color: var(--text-faint); transition: background-color .16s, color .16s, border-color .16s;
}
.scale .dot.on { border-color: var(--primary); background: var(--primary-soft); color: var(--primary-deep); }
.scale.heat .dot.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); }

/* ---------------- 5 档文字量表（2026-10-31） ----------------
   替代 1–5 数字圆点：会员不用再记「哪个方向才是好的」。
   档位文案在 assets/app.js 的 SCALES 里（每天/每周期/症状程度三套），这里只管长相。 */
.sstep-row { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; margin-top: 8px; }
.sstep {
  min-width: 0; padding: 9px 2px; border-radius: 10px;
  border: 1.5px solid var(--line); background: #fff; color: var(--text-sub);
  font-family: inherit; font-size: 16px; font-weight: 600; line-height: 1.3;
  white-space: nowrap; cursor: pointer; transition: background-color .16s, color .16s, border-color .16s;
}
.sstep.on { border-color: var(--primary); background: var(--primary-soft); color: var(--primary-deep); }
.sstep.warm.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-deep); }

/* ---------------- 首次提示条「右上角能放大字」（2026-10-31） ----------------
   只在首页出现一次；点「知道了」或点过 A⁺ 就写入标记不再出现。 */
.big-tip {
  display: flex; align-items: center; gap: 10px;
  margin: 8px 14px 0; padding: 10px 13px; border-radius: 12px;
  background: var(--primary-soft); color: var(--primary-deep);
  font-size: 16px; line-height: 1.6;
}
.big-tip b { font-size: 16px; }
.big-tip .bt-ok {
  margin-left: auto; flex: none; border: 0; background: var(--primary); color: #fff;
  font-family: inherit; font-size: 16px; font-weight: 700;
  padding: 7px 13px; border-radius: 8px; cursor: pointer;
}

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 16px; font-weight: 600; margin-bottom: 7px; }
.field .hint { font-size: 16px; color: var(--text-faint); margin-bottom: 7px; }
.field input, .field textarea, .field select {
  width: 100%; border: 1.5px solid var(--line); border-radius: 11px;
  padding: 12px 13px; font-size: 16px; font-family: inherit; color: var(--text);
  background: #fff; transition: border-color .16s;
}
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--primary); }
.field textarea { min-height: 82px; resize: vertical; line-height: 1.7; }

/* 密码可见切换（2026-10-18 用户要求）：.pwd-wrap 包住 input + 眼睛按钮。
   会员输密码时能先看一眼再提交 —— 减少「明明输对了却说密码错」的挫败。 */
.pwd-wrap { position: relative; }
.pwd-wrap > input { padding-right: 46px; }
.pwd-eye {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 38px; height: 38px; padding: 0; border: 0; border-radius: 10px;
  background: transparent; color: var(--text-faint);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.pwd-eye:active { background: var(--line-soft); }
.pwd-eye.on { color: var(--primary); }
.pwd-eye svg { width: 20px; height: 20px; display: block; }
/* 默认「划线眼」= 密码正被遮住；点开变干净的眼睛 = 已明文可见 */
.pwd-eye .eye-off { display: inline; }
.pwd-eye.on .eye-off { display: none; }

/* ---------------- Toast / Loading ---------------- */
.toast {
  position: fixed; left: 50%; bottom: 108px; transform: translateX(-50%) translateY(10px);
  background: rgba(46,43,38,.9); color: #fff; font-size: 16px;
  padding: 11px 20px; border-radius: 12px; z-index: 200;
  opacity: 0; pointer-events: none; transition: opacity .25s, transform .25s;
  max-width: 86vw; text-align: center; line-height: 1.6;
  backdrop-filter: blur(8px);
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

.loading {
  position: fixed; inset: 0; z-index: 300; display: none;
  align-items: center; justify-content: center; flex-direction: column; gap: 16px;
  background: rgba(245,242,237,.86); backdrop-filter: blur(6px);
}
.loading.show { display: flex; }
.loading .spinner {
  width: 40px; height: 40px; border-radius: 50%;
  border: 3px solid var(--line); border-top-color: var(--primary);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading .txt { font-size: 16px; color: var(--text-sub); }

/* 进度条（视觉模型分析这类「耗时不可预知」的等待）
   有 pct 时隐藏转圈、换成「百分比 + 渐近填充 + 流光」：
   条子始终在动、文案分阶段推进，长等待不会被误判成卡死。 */
.loading .prog { display: none; width: 220px; }
.loading.with-prog .spinner { display: none; }
.loading.with-prog .prog { display: block; }
.loading .prog-pct {
  font-size: 26px; font-weight: 600; line-height: 1; color: var(--primary-deep);
  text-align: center; margin-bottom: 13px; font-variant-numeric: tabular-nums;
}
.loading .prog-track {
  position: relative; height: 7px; border-radius: 99px;
  background: var(--line); overflow: hidden;
}
.loading .prog-track > i {
  display: block; height: 100%; width: 0%; border-radius: 99px;
  background: linear-gradient(90deg, var(--primary), var(--accent));
  transition: width .32s cubic-bezier(.22,.61,.36,1);
}
.loading .prog-track::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: -45%; width: 45%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.65), transparent);
  animation: progShine 1.5s linear infinite;
}
@keyframes progShine { from { left: -45%; } to { left: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .loading .prog-track::after { animation: none; opacity: 0; }
  .loading .prog-track > i { transition: none; }
}

.empty { text-align: center; padding: 40px 20px; color: var(--text-faint); font-size: 16px; line-height: 1.9; }
.empty .big { font-size: 34px; margin-bottom: 10px; opacity: .35; }

/* ---------------- 弹层（预约前的今日打卡门槛） ----------------
   只服务「点预约入口 → 今天还没打卡」这一种场景，所以样式做窄。
   z-index 取 260：压在 toast（200）之上、loading（300）之下。

   ⚠️ 这套类名必须与下方「底部抽屉」的 .modal 系列**分开命名**（2026-09-22 事故）：
   抽屉那边有一条 `.modal { position: fixed; inset: 0; z-index: 150; display: none; }`，
   而抽屉靠 `.modal.show` 才显示。本弹层的卡片是没有 .show 的，
   如果它也叫 .modal，同一优先级下**后出现的 display:none 会赢**，
   卡片被整块隐藏 —— 用户看到的是「点预约 → 屏幕暗一下 → 什么都没发生」，
   表现成「无法预约门店」，且永远点不到「去记录今日打卡」，等于彻底走不通。
   为什么原来的测试拦不住它？**不是** jsdom 算不了层叠 —— 实测它算得出来
   （加载 app.css 后 getComputedStyle 能给出 .gate-card→block、.modal→none）。
   真原因是当时门槛的 11 项断言只查了「DOM 在场」与文案，**压根没查 computed 样式**。
   → 回归锁见 tests/test-flow-v2.js：① 真实 getComputedStyle 断言；
     ② §CSS 层叠检查（静态扫 app.css，比对 .gate-card 与 .modal 的最终 display）。
   注意：jsdom 用 resources:'usable' 加载 CSS 是异步的，**必须等 styleSheets[0]
   规则数 > 50 再断言**，否则 getComputedStyle 返回空串，`display !== 'none'` 会假通过。 */
.gate-mask {
  position: fixed; inset: 0; z-index: 260; display: flex;
  align-items: center; justify-content: center; padding: 22px;
  background: rgba(46,43,38,.42); backdrop-filter: blur(3px);
}
.gate-card {
  width: 100%; max-width: 360px; background: var(--card);
  border-radius: 18px; box-shadow: var(--shadow-lg); padding: 20px 18px 16px;
  animation: gateIn .18s ease-out;
}
@keyframes gateIn { from { opacity: 0; transform: translateY(8px) scale(.985); } }
.gate-title { font-size: 16.5px; font-weight: 700; margin-bottom: 9px; }
.gate-body { font-size: 16px; color: var(--text-sub); line-height: 1.85; }
.gate-actions { display: flex; gap: 10px; margin-top: 16px; }
.gate-actions .btn { flex: 1; text-decoration: none; }
.gate-actions .btn-ghost { flex: 0 0 34%; }

@media (max-width: 360px) {
  .hero h1 { font-size: 23px; }
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
}

/* =====================================================================
   会员登录 / 预约 / 管理后台
   ===================================================================== */

/* ---------------- 分段切换（登录·注册 / 后台标签页） ---------------- */
.seg {
  display: flex; gap: 4px; background: var(--bg-alt);
  border-radius: 12px; padding: 4px; margin-bottom: 14px;
}
.seg button {
  flex: 1; padding: 10px 6px; border-radius: 9px;
  font-size: 16px; font-weight: 600; color: var(--text-sub);
  transition: background-color .16s, color .16s, box-shadow .16s;
}
.seg button.on { background: var(--card); color: var(--primary-deep); box-shadow: var(--shadow); }

/* 2026-11-02：7 个 tab 挤在 390px 里，每格被压到 40px → 全部折成竖排
   （「概 / 览」「预 / 约 / 管 / 理」）。改成**横向滚动 + 不折行**：
   手机上一行能划，比竖排好读；桌面内容装得下时不会出现滚动条。 */
.tabs {
  display: flex; gap: 18px; border-bottom: 1px solid var(--line); margin-bottom: 14px;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  flex: none; white-space: nowrap; min-width: 40px;
  padding: 10px 2px; font-size: 16px; font-weight: 600; color: var(--text-sub);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs button.on { color: var(--primary-deep); border-bottom-color: var(--primary); }

/* ---------------- 服务项目 ---------------- */
.srv {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow);
  padding: 13px 14px; margin-bottom: 9px; border: 1.5px solid transparent;
  transition: border-color .16s, background .16s;
}
.srv.on { border-color: var(--primary); background: var(--primary-soft); }
.srv .srv-t { font-size: 16px; font-weight: 700; }
.srv .srv-d { font-size: 16px; color: var(--text-sub); line-height: 1.7; margin-top: 3px; }
.srv .srv-meta { display: flex; gap: 10px; align-items: center; margin-top: 6px; font-size: 16px; color: var(--text-faint); }
.srv .srv-price { margin-left: auto; flex: none; font-size: 16px; font-weight: 700; color: var(--accent-deep); }

/* ---------------- 日期条 ---------------- */
.date-strip { display: flex; gap: 8px; overflow-x: auto; padding: 2px 2px 8px; scrollbar-width: none; }
.date-strip::-webkit-scrollbar { display: none; }
.dchip {
  flex: none; width: 56px; padding: 9px 0; border-radius: 12px; text-align: center;
  background: var(--card); border: 1.5px solid var(--line); transition: background-color .16s, border-color .16s;
}
.dchip .dw { font-size: 13px; color: var(--text-faint); }
.dchip .dd { font-size: 16px; font-weight: 700; margin-top: 1px; }
.dchip .dm { font-size: 16px; color: var(--text-faint); }
.dchip.on { background: var(--primary); border-color: var(--primary); }
.dchip.on .dw, .dchip.on .dm { color: rgba(255,255,255,.85); }
.dchip.on .dd { color: #fff; }

/* ---------------- 时段 ---------------- */
.slot-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.slot {
  padding: 12px 4px; border-radius: 11px; text-align: center;
  border: 1.5px solid var(--line); background: var(--card);
  font-size: 16px; font-weight: 600; transition: background-color .16s, color .16s, border-color .16s;
}
.slot small { display: block; font-size: 16px; font-weight: 400; color: var(--text-faint); margin-top: 2px; }
.slot:disabled { background: var(--bg-alt); color: var(--text-faint); border-color: var(--line-soft); cursor: not-allowed; }
.slot:disabled small { color: var(--text-faint); }
.slot.on { background: var(--primary); border-color: var(--primary); color: #fff; }
.slot.on small { color: rgba(255,255,255,.85); }

/* ---------------- 预约单 ---------------- */
.appt {
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow);
  padding: 14px; margin-bottom: 10px; border-left: 3px solid var(--line);
}
.appt.s-booked { border-left-color: var(--accent); }
.appt.s-confirmed { border-left-color: var(--primary); }
.appt.s-done { border-left-color: #8FAF9F; }
.appt.s-canceled { border-left-color: var(--line); opacity: .72; }
.appt.s-noshow { border-left-color: var(--danger); }
.appt .ap-head { display: flex; align-items: center; gap: 8px; }
.appt .ap-t { font-size: 16px; font-weight: 700; flex: 1; min-width: 0; }
.appt .ap-when { font-family: var(--serif); font-size: 16px; margin-top: 5px; }
.appt .ap-meta { font-size: 16px; color: var(--text-sub); line-height: 1.75; margin-top: 5px; }
.appt .ap-act { display: flex; gap: 8px; margin-top: 10px; }

.st-badge { font-size: 13px; font-weight: 700; padding: 4px 10px; border-radius: 6px; flex: none; }
.st-booked { color: var(--accent-deep); background: var(--accent-soft); }
.st-confirmed { color: var(--primary-deep); background: var(--primary-soft); }
.st-done { color: var(--text-sub); background: var(--bg-alt); }
.st-canceled { color: var(--text-faint); background: var(--bg-alt); }
.st-noshow { color: var(--danger); background: var(--danger-soft); }
/* 待门店缴费激活（2026-09-23 两段式养护卡）：刻意用「待办」的暖色，
   与「已激活」的绿色（st-confirmed）拉开距离 —— 这两者必须一眼可辨，
   因为「现在能不能扫码施灸」完全取决于它是哪一个。 */
.st-pending { color: var(--warn-text); background: var(--warn-soft); }

/* 机器人状态徽章（2026-09-25 机器人预约管理）。
   类名与 server/lib/robot-availability.js 的 DEVICE_STATUS[*].cls 一一对应 ——
   **服务端下发 cls，前端不自己按状态拼类名**，否则加一种状态就会两边对不上。
   四色刻意拉开：空闲=绿（能约）、施灸中=橙（等一会儿）、离线=黄（要找店员）、
   停用=浅灰（门店主动停的，不是故障，别用红色吓人）。 */
.st-idle { color: var(--primary-deep); background: var(--primary-soft); }
.st-running { color: var(--accent-deep); background: var(--accent-soft); }
.st-offline { color: var(--warn-text); background: var(--warn-soft); }
.st-disabled { color: var(--text-faint); background: var(--bg-alt); }

/* ---------------- 管理后台 ---------------- */
.adm-stat { display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; margin-bottom: 14px; }
.adm-stat .s {
  background: var(--card); border-radius: var(--r-sm); box-shadow: var(--shadow);
  padding: 12px 13px;
}
.adm-stat .s .v { font-family: var(--serif); font-size: 22px; font-weight: 700; color: var(--primary-deep); }
.adm-stat .s .k { font-size: 16px; color: var(--text-faint); margin-top: 2px; }
.adm-stat .s.warn .v { color: var(--accent-deep); }

.adm-row {
  background: var(--card); border-radius: var(--r-sm); box-shadow: var(--shadow);
  padding: 12px 13px; margin-bottom: 8px;
}
.adm-row .r1 { display: flex; align-items: center; gap: 8px; }
.adm-row .nm { font-size: 16px; font-weight: 700; flex: 1; min-width: 0; }
.adm-row .ph { font-size: 16px; color: var(--text-faint); margin-top: 3px; }
.adm-row .r2 { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.adm-row .ap-meta { font-size: 16px; color: var(--text-sub); line-height: 1.7; margin-top: 4px; }
.adm-row .ap-act { display: flex; gap: 7px; margin-top: 9px; flex-wrap: wrap; }

.btn-xs {
  padding: 7px 12px; font-size: 16px; font-weight: 600; border-radius: 9px;
  border: 1.5px solid var(--line); color: var(--text-sub); background: var(--card);
  min-height: 40px;
}
.btn-xs.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-xs.accent { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-xs.danger { background: var(--danger-soft); border-color: #EAD5D0; color: var(--danger); }
.btn-xs:disabled { opacity: .45; }

.filter-bar { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 8px; scrollbar-width: none; }
.filter-bar::-webkit-scrollbar { display: none; }
.fchip {
  flex: none; padding: 7px 13px; border-radius: 8px; font-size: 13px;
  background: var(--card); border: 1.5px solid var(--line); color: var(--text-sub);
}
.fchip.on { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; }

.sub-t { font-size: 13px; font-weight: 700; color: var(--text-sub); letter-spacing: 1px; margin: 14px 0 8px 2px; }

/* 底部导航（会员侧）：由 #bottombar 承载，四个 tab 等分 */
.tabbar {
  /* ⚠️ 列数**跟着 TAB_ITEMS 走**，别写死 4 列：2026-09-23 底栏从 4 项变 5 项
     （新增的那项在正中间），写死 4 列会让第 5 项掉到第二行。
     auto-fit + minmax(0,1fr) 的实际列数 = 子项数，以后加减项都不用回来改这里。 */
  flex: 1; display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 4px;
}
.tabbar button {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 6px 2px; font-size: 13px; color: var(--text-faint); border-radius: 10px;
}
.tabbar button.on { color: var(--primary-deep); font-weight: 700; background: var(--primary-soft); }
.tabbar button svg { width: 19px; height: 19px; }
/* 长标签（目前只有「机器人艾灸服务」7 字）：5 栏在 375px 屏上每栏仅约 68px，
   10.5px × 7 字 ≈ 74px 必然折断成「6 字 + 1 字」。降一档字号（9px × 7 ≈ 63px）
   就能在常见机型上收成一行；320px 这类极窄屏上 text-wrap:balance 会把它
   平分成「机器人艾 / 灸服务」两行，仍比 6+1 好看。 */
.tabbar button.t-long { font-size: 13px; }
.tabbar button.t-long span { line-height: 1.15; }
/* 底栏承载导航时收紧内边距，避免底栏过高挤压内容 */
.bottombar.as-tabs { padding: 6px 10px calc(6px + env(safe-area-inset-bottom)); }

/* ---------------- 三步流程条（机器人艾灸服务的 ①扫码 ②核销凭证 ③方案与准备） ----------------
   会员端「机器人艾灸服务」这条动线要跨两个页面（扫码页 = ①②，方案页 = ③），
   流程条让用户随时知道自己在第几步。样式放这里而不是各页 <style>，
   因为两个页面都要用同一份 —— 各写一份迟早两边长得不一样。
   ⚠️ 「② 核销凭证」只是流程命名：真正扣占用在第③步点「确认施灸」时（见 app.js 注释）。 */
.steps { display: flex; align-items: center; gap: 6px; margin: 2px 0 12px; }
.step-item {
  display: flex; align-items: center; gap: 5px; flex: none;
  font-size: 16px; color: var(--text-faint); white-space: nowrap;
}
.step-item i {
  width: 18px; height: 18px; flex: none; border-radius: 50%; font-style: normal;
  background: var(--bg-alt); color: var(--text-sub); font-size: 13px;
  display: flex; align-items: center; justify-content: center;
}
.step-item.on { color: var(--primary-deep); font-weight: 600; }
.step-item.on i { background: var(--primary); color: #fff; }
.step-line { flex: 1; min-width: 8px; height: 1px; background: var(--line); }

/* 四格版（2026-09-25 机器人预约管理上线后由三步改四步）。
   为什么必须降一档：四格 + 三个连接线，在 375px 屏上内容区只有约 343px。
   按原尺寸（12px 字 / 18px 圆点）四格标签「选机器人 扫码 核销凭证 方案与准备」
   量到约 356px 会溢出，最后那格会被挤出屏幕。
   降一档后约 273px，留出了余量；标签再变长就得重新量一次。
   ⚠️ 不要靠 `overflow:hidden` 兜底 —— 那会把「方案与准备」直接裁掉，
      比折行更难发现。 */
.steps.s4 { gap: 4px; }
/* 四格流程条：本来就是「字号降一档」的导航指示，R1 统一抬到 16px 后
   四格 + 三条连线在 390 屏上放不下，第四格「方案与准备」被右边裁掉。
   导航指示不属正文，回到次要档 13px（与 feedback 页三步条同一档）。 */
.steps.s4 .step-item { font-size: 13px; gap: 3px; white-space: nowrap; }
.steps.s4 .step-item i { width: 15px; height: 15px; font-size: 13px; }
.steps.s4 .step-line { min-width: 4px; }

/* 首页功能入口 */
.entry-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.entry {
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow);
  padding: 15px 14px; text-align: left;
}
.entry .e-ico { font-size: 20px; }
.entry .e-t { font-size: 18px; font-weight: 700; margin-top: 7px; }
.entry .e-s { font-size: 16px; color: var(--text-sub); line-height: 1.65; margin-top: 3px; }
.entry.primary { background: linear-gradient(140deg, var(--primary), var(--primary-deep)); }
.entry.primary .e-t, .entry.primary .e-s { color: #fff; }
.entry.primary .e-s { opacity: .88; }

/* ---------------- 底部抽屉 ----------------
   ⚠️ .modal 是**全局类名**，且这里是「默认 display:none，靠 .show 打开」的口径。
   任何不带 .show 的浮层都**不要**复用 .modal，否则会被这里的 display:none 整块隐藏
   （2026-09-22 预约门槛弹层就踩过这个坑，已改用 .gate-* 系列）。
   抽屉目前只有 admin.html 的 #modal 在用。 */
.modal { position: fixed; inset: 0; z-index: 150; display: none; }
.modal.show { display: block; }
.modal .mask { position: absolute; inset: 0; background: rgba(46,43,38,.42); }
.modal .sheet {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 0;
  width: 100%; max-width: var(--app-w); background: var(--bg);
  border-radius: 18px 18px 0 0; max-height: 90vh; overflow-y: auto;
  padding: 0 16px calc(22px + env(safe-area-inset-bottom));
  animation: sheetup .26s ease;
  box-shadow: 0 -8px 30px rgba(46,43,38,.18);
}
@keyframes sheetup { from { transform: translateX(-50%) translateY(24px); opacity: .5; } to { transform: translateX(-50%); opacity: 1; } }
.modal .sheet-head {
  position: sticky; top: 0; background: var(--bg); z-index: 2;
  display: flex; align-items: center; gap: 10px; padding: 16px 0 12px;
  border-bottom: 1px solid var(--line); margin-bottom: 14px;
}
.modal .sheet-head h3 { font-size: 16px; font-weight: 700; flex: 1; min-width: 0; }
.modal .close-x {
  width: 32px; height: 32px; border-radius: 10px; background: var(--card);
  border: 1px solid var(--line); color: var(--text-sub); flex: none;
  display: flex; align-items: center; justify-content: center; font-size: 16px;
}

/* ---------------- 首页顶部倡导横幅（2026-09-22） ---------------- */
/* 通栏浅色条，紧贴导航栏之下。可手动关闭；关闭后由底部小链接区再打开。 */
.notice-bar {
  flex: none;
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 16px;
  background: var(--accent-soft);
  border-bottom: 1px solid #F0E1D2;
}
.notice-bar .nb-text {
  flex: 1; min-width: 0;
  font-family: var(--serif); font-size: 16px; line-height: 1.7;
  color: var(--accent-deep); letter-spacing: .2px;
}
.notice-bar .nb-close {
  flex: none; width: 22px; height: 22px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; color: var(--accent-deep); background: rgba(255,255,255,.6);
}
.notice-bar .nb-close:active { background: #fff; }

/* ---------------- 今日艾灸任务提醒卡 ---------------- */
.remind-card {
  background: var(--card); border-radius: var(--r-md);
  box-shadow: var(--shadow); padding: 14px 15px; margin-bottom: 12px;
  border-left: 3px solid var(--accent);
}
.remind-card .rc-head { display: flex; align-items: center; gap: 7px; }
.remind-card .rc-bell { font-size: 16px; }
.remind-card .rc-title { font-size: 16px; font-weight: 700; flex: 1; min-width: 0; }
.remind-card .rc-time {
  flex: none; font-size: 16px; padding: 3px 8px; border-radius: 6px;
  background: var(--accent-soft); color: var(--accent-deep); font-weight: 600;
}
.remind-card .rc-line { font-size: 16px; font-weight: 600; margin-top: 9px; }
.remind-card .rc-sub { font-size: 16px; color: var(--text-sub); margin-top: 4px; }
.remind-card .rc-actions { display: flex; gap: 10px; margin-top: 13px; }
.remind-card .rc-actions .btn { padding: 11px; font-size: 16px; }

/* ---------------- 首页底部小链接区 + 提醒设置面板 ---------------- */
.mini-links {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 14px 0 4px; font-size: 13px;
}
.mini-links button {
  color: var(--text-sub); font-size: 13px;
  text-decoration: underline; text-underline-offset: 3px;
}
.mini-links .sep { color: var(--text-faint); }

.remind-panel .rp-note {
  font-size: 13px; color: var(--text-sub); line-height: 1.85;
  background: var(--bg-alt); border-radius: var(--r-sm); padding: 10px 12px;
}
.remind-panel .rp-switch {
  display: flex; align-items: center; gap: 9px;
  font-size: 16px; font-weight: 600; margin: 13px 0 4px; cursor: pointer;
}
.remind-panel .rp-switch input { width: 16px; height: 16px; accent-color: var(--primary); }
.remind-panel .rp-label { font-size: 13px; color: var(--text-sub); margin-top: 10px; }
.remind-panel #rpTime {
  width: 100%; margin-top: 8px; padding: 11px 12px;
  border: 1.5px solid var(--line); border-radius: var(--r-sm);
  background: var(--card); font-family: inherit; font-size: 16px; color: var(--text);
}
.remind-panel .rp-state { font-size: 16px; color: var(--text-faint); margin-top: 10px; text-align: center; }

/* =====================================================================
   施灸执行表（2026-09-22）
   ---------------------------------------------------------------------
   方案页把「灸哪里 / 怎么灸 / 灸多久 / 哪天灸哪天不灸」做成表格先行，
   依据与判断排到表格之后。只定义表格本身，配色沿用页面既有变量；
   窄屏靠 .tbl-wrap 横向滚动兜底，不压缩到读不了。
   ===================================================================== */
.sec-t.sub-sec { font-size: 16px; letter-spacing: 1px; margin: 16px 0 8px 2px; }
.sec-t.sub-sec .sec-note {
  font-weight: 500; letter-spacing: 0; font-size: 16px;
  color: var(--text-faint); margin-left: 6px;
}
.tbl-wrap {
  background: var(--card); border-radius: var(--r-md); box-shadow: var(--shadow);
  overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 0 12px;
}
table.tbl { width: 100%; border-collapse: collapse; font-size: 16px; }
table.tbl th {
  text-align: left; font-weight: 600; font-size: 13px; color: var(--text-sub);
  background: var(--bg-alt); padding: 9px 10px; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
table.tbl td {
  padding: 10px; border-bottom: 1px solid var(--line-soft);
  vertical-align: top; line-height: 1.7; color: var(--text);
}
table.tbl tr:last-child td { border-bottom: none; }
table.tbl td.nowrap { white-space: nowrap; }
table.tbl td.how { font-size: 16px; color: var(--text-sub); }
table.tbl td .sub { font-size: 13px; color: var(--text-faint); line-height: 1.6; margin-top: 3px; }

/* ---------------- 进度卡 + 四列排班表（2026-09-25 第二轮，2026-09-26 第五轮改列结构） ----------------
   目标：手机一屏横向放下 4 列，**不出现左右滑动**。
   手段：缩短表头（间隔 → 建议时间 / 预约状态 → 进度状态）、方案简写、固定列宽 + 压缩内边距；
   刻意**不用**「把字号调到很小」那一招 —— 字号只从 12.5 降到 12，宽度主要靠字段压缩。
   第五轮变更：卡片主信息换成「方案共 N 次」（最左、加重），周次降为次要小字；
   去掉与表格下方说明重复的 .wk-sub。 */
.wk-card { padding: 13px 14px; }
.wk-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.wk-head .wk-cnt { font-size: 16px; font-weight: 800; color: var(--primary-deep); }
.wk-head .wk-sep { color: var(--line); }
.wk-head .wk-no { font-size: 16px; font-weight: 700; color: var(--text-sub); }
.wk-head .wk-done { margin-left: auto; font-size: 16px; font-weight: 700; color: var(--primary-deep); }
.wk-bar { margin: 9px 0 0; height: 6px; }
.wk-note { margin-top: 8px; font-size: 16px; color: var(--text-faint); line-height: 1.75; }

/* 固定列宽 → 表格宽度恒等于容器宽度，不会触发 .tbl-wrap 的横向滚动 */
.tbl-wrap.course-wrap { overflow-x: visible; }
table.tbl.course-tbl { table-layout: fixed; font-size: 16px; }
table.tbl.course-tbl th { padding: 8px 5px; font-size: 13px; }
table.tbl.course-tbl td { padding: 8px 5px; vertical-align: middle; line-height: 1.55; }
table.tbl.course-tbl col.c-seq { width: 17%; }
table.tbl.course-tbl col.c-time { width: 22%; }
table.tbl.course-tbl col.c-plan { width: 33%; }
table.tbl.course-tbl col.c-book { width: 28%; }
table.tbl.course-tbl td.c-plan { color: var(--text-sub); word-break: break-word; }
table.tbl.course-tbl td .sub { margin-top: 2px; font-size: 16px; }
/* 进度状态徽标在窄列里再收一点内边距，保证 4 列不挤爆 */
table.tbl.course-tbl td .st-badge { padding: 3px 8px; }
.c-on { color: var(--primary-deep); font-weight: 700; }
.c-off { color: var(--text-faint); }

/* =====================================================================
   拍照链路（2026-09-26 第六轮）：取景引导框 / 来源抽屉 / 预览大图 / 页内相机
   ===================================================================== */

/* ---------------- 空态取景引导框 ----------------
   叠在 .shot 卡片里的一层虚线轮廓，让人一眼知道「舌头/脸要放进哪个范围」。
   ⚠️ 只有空态显示：paintShot() 重拍时只删 img 与 span，**不会删这个 svg**，
      所以「拍完就消失」必须由 CSS 负责（.shot.filled 那条）。
   这里是绝对定位元素，会盖在普通流里的图标/文字之上 —— 所以下面给它们补 z-index。 */
.shot .shot-guide { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
.shot .shot-guide ellipse { fill: none; stroke: #C9C1B4; stroke-width: 1.1; stroke-dasharray: 4 3; }
.shot .shot-guide path { fill: none; stroke: #DAD3C8; stroke-width: 1.1; stroke-linecap: round; }
.shot.filled .shot-guide { display: none; }
.shot .shot-icon, .shot > span { position: relative; z-index: 1; }

/* ---------------- 来源抽屉（capture.html 专属的 .cap-* 系列） ----------------
   ⚠️ **刻意不复用 .modal**：那是全站保留给 admin.html 抽屉的类名（默认 display:none，
   靠 .show 打开），test-flow-v2.js 有一条全站反向锁钉着「只有 admin.html 能用它」——
   2026-09-22 预约门槛弹层就是复用 .modal 忘了带 .show，整块隐形。
   所以拍照来源抽屉自带一套 .cap-*，默认态与打开方式都写在自己名下。 */
.cap-choose { position: fixed; inset: 0; z-index: 150; display: none; }
.cap-choose.show { display: block; }
.cap-choose .cap-mask { position: absolute; inset: 0; background: rgba(46,43,38,.42); }
.cap-choose .cap-sheet {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 0;
  width: 100%; max-width: var(--app-w); background: var(--bg);
  border-radius: 18px 18px 0 0; max-height: 90vh; overflow-y: auto;
  padding: 0 16px calc(22px + env(safe-area-inset-bottom));
  animation: sheetup .26s ease;
  box-shadow: 0 -8px 30px rgba(46,43,38,.18);
}
.cap-choose .cap-head {
  position: sticky; top: 0; background: var(--bg); z-index: 2;
  display: flex; align-items: center; gap: 10px; padding: 16px 0 12px;
  border-bottom: 1px solid var(--line); margin-bottom: 14px;
}
.cap-choose .cap-head h3 { font-size: 16px; font-weight: 700; flex: 1; min-width: 0; }
.cap-choose .cap-x {
  width: 32px; height: 32px; border-radius: 10px; background: var(--card);
  border: 1px solid var(--line); color: var(--text-sub); flex: none;
  display: flex; align-items: center; justify-content: center; font-size: 16px;
}
.cap-choose .cap-hint {
  font-size: 13px; color: var(--text-sub); line-height: 1.8;
  background: var(--bg-alt); border-radius: var(--r-sm);
  padding: 10px 12px; margin-bottom: 12px;
}
.cap-choose .cap-item {
  display: block; width: 100%; text-align: center;
  font-size: 16px; font-weight: 700; letter-spacing: .5px; color: var(--text);
  background: var(--card); border: 1.5px solid var(--line);
  border-radius: var(--r-md); padding: 15px; margin-bottom: 10px;
}
.cap-choose .cap-item:active { background: var(--primary-soft); border-color: var(--primary); }
/* 「取消」不跟两个主选项抢注意力：去掉底框，只留文字 */
.cap-choose .cap-item.ghost {
  background: transparent; border-color: transparent;
  color: var(--text-sub); font-weight: 500; margin-bottom: 0;
}
.cap-choose .cap-item.ghost:active { background: var(--bg-alt); }

/* ---------------- 拍完的预览大图 ----------------
   刻意**不复用 .modal**：那个类默认 display:none 且靠 .show 打开，
   但这层要的是「深色全屏 + flex 纵向三段（标题 / 图 / 底部操作）」，语义不同。
   深色底上的文字色在这里就地写死，别引用浅色主题的 --text-*，否则会看不见。 */
.pv-layer {
  position: fixed; inset: 0; z-index: 160;
  display: none; flex-direction: column;
  background: rgba(28,26,23,.95);
  padding: max(12px, env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom));
}
.pv-layer.show { display: flex; }
.pv-head {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 4px 2px 12px; color: rgba(255,255,255,.94);
  font-size: 16px; font-weight: 700; letter-spacing: .5px;
}
.pv-head #pvTitle { flex: 1; min-width: 0; }
.pv-x {
  flex: none; width: 32px; height: 32px; border-radius: 10px;
  background: rgba(255,255,255,.12); color: #fff; font-size: 16px;
  display: flex; align-items: center; justify-content: center;
}
.pv-x:active { background: rgba(255,255,255,.22); }
.pv-body { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
.pv-body img {
  max-width: 100%; max-height: 100%; object-fit: contain;
  border-radius: var(--r-md); background: #000;
}
.pv-foot { flex: none; padding-top: 12px; }
.pv-meta { font-size: 13px; line-height: 1.8; color: rgba(255,255,255,.7); margin-bottom: 12px; }
.pv-meta .pv-warn { margin-top: 5px; color: #F0C08A; }
.pv-meta .pv-tip { margin-top: 5px; color: rgba(255,255,255,.5); }
.pv-actions { display: flex; gap: 10px; }
.pv-actions .btn { flex: 1; }
/* 深色层里 ghost 按钮要换配色，不然浅色边框+灰字几乎看不见 */
.pv-actions .btn-ghost { color: rgba(255,255,255,.9); border-color: rgba(255,255,255,.32); }

/* ---------------- 页内相机（带取景框） ----------------
   能用 getUserMedia 就用它：实时看得到引导框，拍不准立刻调整。
   用不了（非安全上下文 / 无权限 / 无设备）时由 JS 回退系统相机，这层不参与。 */
.cam-layer { position: fixed; inset: 0; z-index: 170; display: none; background: #000; overflow: hidden; }
.cam-layer.show { display: block; }
/* 前置摄像头预览镜像（scaleX(-1)）：自拍时像照镜子，手往哪边动画面就往哪边动。
   ⚠️ 只影响**显示**——快门走 canvas.drawImage()，取的是视频原始帧，不受 CSS transform 影响，
   所以存下来/上传给视觉模型的那张图仍是**真实朝向**（与原生相机一致：预览镜像、成片不镜像）。 */
.cam-layer video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
.cam-guide { position: absolute; inset: 0; pointer-events: none; }
.cam-guide svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.cam-guide ellipse { fill: none; stroke: rgba(255,255,255,.92); stroke-width: .9; stroke-dasharray: 3 2.4; }
.cam-tip {
  position: absolute; left: 18px; right: 18px; bottom: 138px; text-align: center;
  font-size: 13px; line-height: 1.75; color: #fff;
}
.cam-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 16px 20px calc(24px + env(safe-area-inset-bottom));
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  background: linear-gradient(to top, rgba(0,0,0,.6), transparent);
}
/* .btn 默认 width:100%，在 flex 行里会把快门挤没 —— 两侧按钮必须回到内容宽度 */
.cam-bar .btn-ghost {
  flex: none; width: auto; padding: 9px 15px; font-size: 16px; font-weight: 600;
  color: #fff; background: rgba(255,255,255,.16); border-color: transparent;
}
.cam-bar .btn-ghost:active { background: rgba(255,255,255,.3); }
.cam-shutter {
  flex: none; width: 68px; height: 68px; border-radius: 50%;
  background: #fff; border: 4px solid rgba(255,255,255,.45);
  box-shadow: 0 0 0 2px rgba(0,0,0,.2); transition: transform .12s;
}
.cam-shutter:active { transform: scale(.93); }

/* =====================================================================
   出错兜底与「需要帮助」（2026-10-31 工单 T2）
   ---------------------------------------------------------------------
   兜底里原来只有一句「或打电话问门店」——没有号码、没有链接，等于没给退路。
   这里给：错误条里的可拨号码（.err-call）+ 右下角常驻入口（.help-fab）+ 浮层（.hs-*）。
   FAB 的 bottom 让开底部导航/操作栏（约 60px）+ 安全区；44px 高满足最小触摸目标。
   ===================================================================== */
.err-call {
  color: var(--danger); font-weight: 700;
  text-decoration: underline; text-underline-offset: 3px;
}

.help-fab {
  position: fixed; z-index: 60;
  /* 相对**内容区**右边界 14px，而不是视口右边界。
     折叠屏展开时 .app 只有 700px 居中，写死 right:14px 会让浮钮飘到页面外面
     （实测 Mate X6 内屏 741dp 下浮钮落在 665~730，而 .app 到 611 就结束了）。 */
  right: max(14px, calc((100vw - var(--app-w)) / 2 + 14px));
  bottom: var(--fab-bottom);
  height: var(--fab-h); padding: 0 16px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--card); color: var(--primary-deep);
  font-size: 16px; font-weight: 700; font-family: inherit;
  box-shadow: var(--shadow-lg); cursor: pointer;
}
.help-fab:active { transform: scale(.96); }

/* ---------------- 全站语音助手按钮（2026-11-03，取代「帮助」） ----------------
   方案口径：把帮助按钮直接切换为语音助手，每个页面都能用。
   为什么是圆形而不是原来的文字胶囊：
     · 它现在只有一个动作（说话），文字「帮助」会让人以为还能点开攻略；
     · 圆形话筒是这类按钮的通用形状，不需要读字；
     · 56px 比原 44px 高一点 —— 它是这一屏唯一的语音入口，值得占更大面积
       （WCAG 2.5.5 底线是 40×40，56 是拇指舒适区）。
   ⚠️ right 必须用 --app-w 算（折叠屏展开时 .app 居中，写死 14px 会让按钮飘到页面外）。 */
:root { --voice-fab: 56px; }
.voicemic {
  /* 🔴 2026-11-04：从"右下角圆钮"改成**右侧吸附竖条**（用户口径）。
     贴屏幕右缘、垂直居中，圆角只做左边两个 —— 看起来像从侧边拉出来的抽屉把手。
     点它 → 白色面板从右侧推出（见 voice-panel.js 的 .vp-panel 动画）。 */
  position: fixed; z-index: 62;
  /* 🔴 unslop（2026-11-05 用户推翻 11-04「抽屉把手」口径）：从右侧居中吸边
     挪到**右下角 FAB 列基线槽位**（--fab-bottom）—— 不再悬浮在内容卡上。
     架构自洽：回顶按钮本来就按「话筒占底部槽」让位（fab-bottom + voice-fab + gap，
     见 .back-top）；旧 help-fab 已无页面渲染，槽位是空的。
     ⚠️ right 与 .help-fab 同一算法（相对 .app 右边界 14px），折叠屏不飘出页面。 */
  right: max(14px, calc((100vw - var(--app-w)) / 2 + 14px));
  bottom: var(--fab-bottom);
  width: 52px; height: 52px;
  border-radius: 16px;
  border: 1px solid var(--line);
  /* 2026-11-04 用户口径：不要绿色方块，只留吸附侧边的**白色小图标** */
  background: #fff; color: var(--primary);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 16px -8px rgba(46,43,38,.4);
  cursor: pointer;
  transition: transform .12s, background .2s, width .2s, height .2s, right .2s, bottom .2s, border-radius .2s;
  /* 移动端：去掉双击缩放等待与灰色点击高亮 —— 按下就要立刻有反应 */
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  /* 按住说话：长按不能选中文字、不能弹系统菜单（否则按住 500ms 会跳菜单） */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
/* 🔴 模式2 陪伴入口使用独立 class：不共用模式1 的 .voicemic，
   因此 robot-session.html 那条 display:none !important 不会命中它。
   两套模式的样式可以分别迭代，互不干扰。 */
.vcomp-fab {
  position: fixed; z-index: 63;
  right: var(--app-gutter, 0px); top: 50%; transform: translateY(-50%);
  width: 40px; min-width: 40px; height: 52px; min-height: 44px;
  border-radius: 14px 0 0 14px;
  border: 1px solid var(--line); border-right: none;
  background: #fff; color: var(--primary);
  display: flex; align-items: center; justify-content: center;
  box-shadow: -2px 2px 12px -6px rgba(46,43,38,.28);
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.vcomp-fab svg { width: 26px; height: 26px; }
.vcomp-fab:active { transform: translateY(-50%) scale(.94); }
/* 施灸页自带 84px 话筒在右下角：上移错开，仍守住 40×40 点击底线 */
.vcomp-fab.companion-self {
  top: 32%; height: 44px; min-height: 44px; border-radius: 12px 0 0 12px;
}
@media (prefers-color-scheme: dark) {
  .vcomp-fab { background: #232A26; color: #EDE9E2; border-color: #46524B; }
}
.voicemic svg { width: 26px; height: 26px; }
.voicemic:active { transform: scale(.94); }
/* 四态：loading 灰（正在加载语音库）／listening 黄（在听）／thinking 灰蓝（在想）／speaking 蓝（在说） */
.voicemic.loading { background: #8A9A93; opacity: .8; }
.voicemic.listening { background: var(--accent); animation: vmicPulse 1.6s ease-in-out infinite; }
.voicemic.thinking { background: #8A9A93; }
.voicemic.speaking { background: #4C7DA6; }
@keyframes vmicPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(214,158,46,.42); }
  50% { box-shadow: 0 0 0 10px rgba(214,158,46,0); }
}
@media (prefers-reduced-motion: reduce) {
  .voicemic.listening { animation: none; box-shadow: 0 0 0 4px rgba(214,158,46,.45); }
}

/* ---------------- 语音按钮：收进侧边栏（2026-11-03 三改，用户口径） ----------------
   用户口径：「这种不好看，改成那种可以收藏进侧边栏的」。
   所以收起后**不再是一个小圆点**，而是贴着右边缘的一条竖胶囊（32×56，
   右侧直角贴边、左侧圆角），像抽屉/侧栏的把手 —— 点它滑回来。

   为什么不是彻底消失：藏完没有回来的路，中老年用户不会去设置里翻开关，
   这一屏唯一的语音入口一没，等于功能没了。
   · 32×56 ≈ 1792px²，等效点击面积 ≥ 40×40（WCAG 2.5.5）—— 竖条比小圆点好戳；
   · right 用 max(0, (100vw-–app-w)/2)：贴的是 **.app 的右边界**，折叠屏展开时
     不会贴在视口最右边、跑到内容外面去；
   · 右侧不留边框（border-right:none）—— 贴着边还画一条线，像没对齐。 */


/* 「收进侧边」的小箭头：.voicemic 的兄弟元素（<button> 里不能嵌 <button>）。
   热区 40×40，视觉只有 18px 的白圆 + 8px 的 ›，比叉轻、也不像「删除/关闭」。
   箭头朝右 = 往右边收，方向和动作一致。 */




/* ---------------- 按住说话浮层（2026-11-03，参照微信语音消息） ----------------
   用户反馈「只是变色，感受不明显」。微信的做法是按住就弹一块浮层：
   音量条 + 「松手发送　上滑取消」，上滑变红 → 松手取消。
   这套用户已经被教过，直接搬；只做反馈，不接点击（pointer-events:none）。

   🔴 2026-11-03 晚（用户口径「放大并突出浮层显示，让用户清晰可见」）：
   · 宽 168 → 232px，主句 14 → 17px，音量条 20 → 30px 高 / 3 → 5px 粗，圆角 16 → 20px；
   · 文案拆两行：主句（该干嘛）大、副句（备选）小 —— 一行挤着再放大就是一坨字；
   · 深底 + 白色描边 + 双层投影（深投影拉层次、浅外圈勾边），浅色页面上一眼跳出来；
   · 出现时从 0.9 倍弹到 1（140ms，轻微过冲）—— 静态淡入在余光里根本看不见；
   · 上滑到取消：底色转红 + 红色外圈呼吸，这是"松手就没了"的唯一提醒。 */
.voicepanel {
  position: fixed; z-index: 63; pointer-events: none;
  left: 50%;
  bottom: calc(var(--fab-bottom) + var(--voice-fab) + 18px);
  display: flex; align-items: center; gap: 14px;
  min-width: 232px; max-width: min(calc(100vw - 24px), var(--app-w));
  padding: 14px 20px;
  border-radius: 20px;
  background: rgba(24, 32, 28, .96);
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 22px 46px -14px rgba(0, 0, 0, .78), 0 0 0 3px rgba(255, 255, 255, .07);
  color: #fff;
  font-size: 17px; line-height: 1.15;
  opacity: 0;
  transform: translateX(-50%) translateY(8px) scale(.9);
  transition: opacity .14s ease, transform .16s cubic-bezier(.2, 1.25, .4, 1), background .14s ease;
}
.voicepanel.show { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
.voicepanel.cancel {
  background: rgba(172, 42, 42, .97);
  border-color: rgba(255, 255, 255, .26);
  animation: vpCancelGlow 1.1s ease-in-out infinite;
}
@keyframes vpCancelGlow {
  0%, 100% { box-shadow: 0 22px 46px -14px rgba(0,0,0,.78), 0 0 0 4px rgba(224, 96, 96, .34); }
  50% { box-shadow: 0 22px 46px -14px rgba(0,0,0,.78), 0 0 0 13px rgba(224, 96, 96, 0); }
}
.voicepanel[hidden] { display: none; }

.voicepanel .vp-bars {
  display: flex; align-items: flex-end; gap: 4px;
  height: 30px; flex: none;
}
.voicepanel .vp-bars i {
  display: block; width: 5px; height: 16%;
  background: currentColor; border-radius: 3px;
  opacity: .95;
  transition: height .09s linear;
}
.voicepanel .vp-tip {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 3px;
}
.voicepanel .vp-tip b {
  font-size: 17px; font-weight: 700; letter-spacing: .4px;
  white-space: nowrap;
}
.voicepanel .vp-tip span {
  font-size: 13px; letter-spacing: .2px; opacity: .78;
  white-space: nowrap;
}
/* 窄屏（360dp 是国产机销量最大档，232px 的浮层在那里也要放得下） */
@media (max-width: 370px) {
  .voicepanel { min-width: 0; padding: 13px 16px; gap: 11px; }
  .voicepanel .vp-tip b { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .voicepanel, .voicepanel .vp-bars i { transition: none; }
  .voicepanel.cancel {
    animation: none;
    box-shadow: 0 22px 46px -14px rgba(0,0,0,.78), 0 0 0 6px rgba(224, 96, 96, .38);
  }
}

/* ---------------- 首次登录 · 新手指引（2026-11-03 第 2 版：聚光灯分步） ----------------
   形态：屏幕压暗 → 只留被讲的那个控件是亮的（一个"洞"）→ 旁边一个气泡 → 下一步。
   气泡里的按钮是唯一可点区域，其余点击被 .vg-block 吃掉（防止照着引导点到别处）。
   "洞"用 ring 的 box-shadow 大范围铺出来，不用四块遮罩拼接 —— 拼接在圆角/转屏时必出缝。 */
.vg-tour { position: fixed; inset: 0; z-index: 90; }
.vg-tour.out { opacity: 0; transition: opacity .2s ease; }
.vg-block { position: fixed; inset: 0; background: transparent; }

.vg-ring {
  position: fixed; pointer-events: none;
  border-radius: 14px;
  box-shadow: 0 0 0 9999px rgba(12, 16, 14, .62), 0 0 0 2px rgba(255, 255, 255, .85) inset;
  transition: left .22s cubic-bezier(.4,0,.2,1), top .22s cubic-bezier(.4,0,.2,1),
              width .22s cubic-bezier(.4,0,.2,1), height .22s cubic-bezier(.4,0,.2,1);
}
.vg-ring[hidden] { display: none; }

.vg-tip {
  position: fixed; z-index: 91;
  width: min(300px, calc(100vw - 32px));
  padding: 14px 16px 12px;
  border-radius: 14px;
  background: var(--card); color: var(--text);
  box-shadow: 0 18px 40px -18px rgba(0,0,0,.55);
  text-align: left;
  transition: left .18s ease, top .18s ease;
}
.vg-tip .vg-title {
  display: block; font-size: 17px; font-weight: 800; line-height: 1.4;
  color: var(--primary-deep);
}
.vg-tip .vg-text {
  margin: 6px 0 12px; font-size: 14.5px; line-height: 1.62; color: var(--muted);
}
.vg-tip .vg-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.vg-dots { display: flex; align-items: center; gap: 5px; }
.vg-dots i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--line); display: block;
}
.vg-dots i.on { background: var(--primary); }
.vg-next {
  min-width: 88px; height: 38px; padding: 0 16px;
  border: none; border-radius: 8px;
  background: var(--primary); color: #fff;
  font-size: 15px; font-weight: 800; font-family: inherit;
  cursor: pointer; touch-action: manipulation;
}
.vg-next:active { transform: scale(.97); }
.vg-next:focus { outline: none; }
.vg-next:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.vg-skip {
  position: fixed; z-index: 91;
  top: max(10px, env(safe-area-inset-top));
  right: max(12px, calc((100vw - var(--app-w)) / 2 + 12px));
  height: 34px; padding: 0 14px;
  border: 1px solid rgba(255,255,255,.35); border-radius: 8px;
  background: rgba(0,0,0,.28); color: #fff;
  font-size: 13.5px; font-family: inherit; cursor: pointer;
  backdrop-filter: blur(2px);
}
.vg-skip:active { transform: scale(.97); }

/* 关掉引导后让话筒亮一下：看完的第一反应是"试试看"，得让他找得到 */
.vg-nudge { animation: vgNudge 1.1s ease-in-out 2; }
@keyframes vgNudge {
  0%, 100% { box-shadow: 0 1px 2px rgba(26,42,34,.08); }
  50% { box-shadow: 0 0 0 10px rgba(26,42,34,.16); }
}
@media (prefers-reduced-motion: reduce) {
  .vg-ring, .vg-tip, .vg-tour { transition: none; }
  .vg-nudge { animation: none; box-shadow: 0 0 0 8px rgba(26,42,34,.18); }
}

/* ---------------- 回到顶部（2026-10-31 交互评估 P0） ----------------
   冲突：右下角已经被「帮助」占了（会员实测找不到帮助是更大的问题，不能为了回顶把它挪走）。
   解法：不抢位置、也不并排挤 —— **竖着排在帮助上方一个按钮位**，只在滚过一屏半之后升起。
     · 帮助键位置一点不动（它常驻，空间记忆是它的价值）
     · 回顶是临时性的，滚回顶部自己降下去，静止时右下角还是只有一个按钮
     · 宽度与帮助键同档（同为胶囊 + 同一套边框/阴影），竖排看起来是一组，不像两个野按钮
   ⚠️ 不要改成和 help-fab 同一个 bottom —— 会直接重叠。 */
.back-top {
  position: fixed; z-index: 61;
  right: max(14px, calc((100vw - var(--app-w)) / 2 + 14px));
  /* 🔴 基线跟着语音按钮的**实际高度**走（--voice-fab 56px，不是 --fab-h 44px）。
     原来按 --fab-h 算，语音按钮一变大就直接压住回顶按钮。 */
  bottom: calc(var(--fab-bottom) + var(--voice-fab) + var(--fab-gap));
  height: var(--fab-h); padding: 0 16px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--card); color: var(--primary-deep);
  font-size: 16px; font-weight: 700; font-family: inherit;
  box-shadow: var(--shadow-lg); cursor: pointer;
  opacity: 0; transform: translateY(10px) scale(.94); pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
}
.back-top.show { opacity: 1; transform: none; pointer-events: auto; }
.back-top:active { transform: scale(.96); }

.hs-mask {
  position: fixed; inset: 0; z-index: 90; background: rgba(0, 0, 0, .42);
  display: flex; align-items: flex-end;
}
.hs-sheet {
  width: 100%; background: var(--card); border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
  max-height: 82vh; overflow-y: auto;
}
.hs-title { font-size: 17px; font-weight: 700; margin-bottom: 4px; }
.hs-call { margin: 12px 0 4px; }
.hs-row {
  display: flex; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--line); font-size: 16px; line-height: 1.7;
}
.hs-row .hs-k { flex: 0 0 68px; color: var(--text-faint); }
.hs-row .hs-v { flex: 1; min-width: 0; }
.hs-tip { font-size: 13px; color: var(--text-sub); line-height: 1.8; margin: 12px 0; }
.hs-nophone { font-size: 16px; color: var(--text-sub); line-height: 1.8; margin: 12px 0; }
.hs-close { margin-top: 6px; }

/* =====================================================================
   中文排版：避开「末行只剩一两个字」（2026-11-01）
   ---------------------------------------------------------------------
   字号抬到 16 后，一段 20 字的话常被切成「19 字 + 1 字」—— 孤字行非常刺眼
   （首页安全提示、方案页舌象提示都中过）。
   text-wrap: pretty 让浏览器在断行时优先把末行填满；不支持的浏览器
   （旧 Firefox）保持默认断行，不会错版。
   ===================================================================== */
body { text-wrap: pretty; }

/* =====================================================================
   宽屏适配：折叠屏展开 / 平板（2026-11-02）
   ---------------------------------------------------------------------
   实测（真浏览器 17 档设备）：华为 Mate X6 内屏 741dp、Mate XT 744dp、
   三星 Z Fold6 690dp、荣耀 Magic V3 782dp、小米 MIX Fold4 829dp。
   改用 `--app-w` 之前 .app 死守 480px → 内屏只占 58%~65%，
   两侧各留 100~180px 灰底，用户感受是「首页显示不完整」。

   放宽上限取 700px：16px 字号下一行约 40 个汉字，再宽超出自助用户的
   舒适阅读行长。同时给卡片网格加列 —— 只把单列拉长会让页面显得更空。
   ⚠️ 底栏/抽屉/弹层也走 --app-w，否则 fixed 层不跟 .app 居中就错位。
   ===================================================================== */
@media (min-width: 560px) {
  :root { --app-w: 560px; }
  .entry-grid { grid-template-columns: repeat(auto-fit, minmax(212px, 1fr)); }
  .shot-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .adm-stat { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
}
@media (min-width: 660px) { :root { --app-w: 640px; } }
@media (min-width: 800px) { :root { --app-w: 700px; } }

/* 极窄屏（320 / 折叠屏外屏最窄档 344）：四宫格改单列。
   双列时每格可用宽只剩 110~122px，13px 释义小字必然折成两行、末行只剩一个字
   ——「三分钟 帮你判断怎么灸」实测 110 vs 需 124。
   ⚠️ 阈值必须是 350 而不是 380：360dp 是国产机销量最大的一档，双列每格 130px
      本来放得下（124 < 130），设 380 会把整整一档主流机型误判成单列、
      首页凭空多出两屏高度。 */
@media (max-width: 350px) {
  .entry-grid { grid-template-columns: 1fr; }
}

/* 适老点击目标硬底线 40×40（WCAG 2.5.5 AAA 建议 44，国内适老化取 40 下限）。
   用 min-* 而不是逐页改 width/height —— CSS 里 min 约束天然胜过 width，
   页面内联 <style> 写的 34×34 会被自动抬起来，不必改 20 多个 HTML。
   实测原状：返回 34×34、清空/关闭 34×34、「知道了」74×35、A⁺ 38×36。 */
.x, .nav .back, .nav .nav-big, .nav .nav-family, .nav .nav-logout, .bt-ok { min-width: 40px; min-height: 40px; }

/* 窄屏（344 折叠外屏 / 320 老机）顶栏要塞 4 件 + 品牌标时：
   名字胶囊收到 96px，把宽度让给页面标题（132px 是给大字体档预留的，
   窄屏根本用不满）。纯手机端不会触发 —— 390 以上仍用 132。 */
@media (max-width: 400px) {
  .nav .who { max-width: 96px; }
}

/* 大字体（html.bigfont → body{zoom:1.25}）时，body 的布局宽只剩 视口/1.25，
   而 --app-w 的 480~700 已经大于它 → .app 实际占满，两侧没有留白。
   此时 --app-gutter 若还按 100vw 算就会得到 50px 的假留白（100vw 是视觉像素、
   px 是 zoom 后的布局像素，两者不同坐标系），浮钮会莫名内缩。
   实测覆盖面：390→布局 312、741→593、829→663，三者都 < 700，故取 0 正确。 */
html.bigfont { --app-gutter: 0px; }

/* 大字体 + 窄屏（≤400px，布局宽只剩 256）：顶栏 4 件 + 名字胶囊必然溢出 21px。
   名字胶囊是身份显示 + 去个人页的第二入口，不是唯一入口 ——
   大字体用户首先要能看清内容，先让位。 */
@media (max-width: 400px) {
  html.bigfont .nav .who { display: none; }
}

/* =====================================================================
   语音通话按钮（2026-11-05）—— 结构性规则写基础层 app.css
   为什么不放 app-v2.css：后台页按约定不引 v2，而 v2 里的 media query
   会用同特异性规则覆盖后面的定义（.ov-id 的坑踩过一次）。这里只新增
   --vc-* 变量与 .vc-* 选择器，不改动任何全站主题色，不破坏原有布局。
   ===================================================================== */
:root {
  --vc-idle-bg: #E8F1FB;        /* 空闲：柔和浅蓝 */
  --vc-idle-fg: #3A6EA5;
  --vc-active-bg: #C0392B;      /* 通话中：红 */
  --vc-connect-bg: #EFEAE2;     /* 连接中：置灰 */
  --vc-connect-fg: #A39C92;
  --vc-bar-bg: rgba(255, 255, 255, .96);
  --vc-bar-fg: #2E2B26;
  --vc-bar-line: #E8E3DA;
  --vc-mask: rgba(46, 43, 38, .42);
}
/* 深色模式自动适配：只翻组件自己的变量，全站 :root 主题一个字不动 */
@media (prefers-color-scheme: dark) {
  :root {
    --vc-idle-bg: #1E3346;
    --vc-idle-fg: #8FC0F0;
    --vc-active-bg: #A93226;
    --vc-connect-bg: #3A3833;
    --vc-connect-fg: #9B948A;
    --vc-bar-bg: rgba(32, 30, 27, .96);
    --vc-bar-fg: #F2EEE8;
    --vc-bar-line: #4A453E;
    --vc-mask: rgba(0, 0, 0, .6);
  }
}

/* order 4：排在 .who(0) / .nav-big(1) / .nav-logout(3) 之后，稳在顶栏最右。
   40×40 是 WCAG 2.5.5 的点击底线，用 min-* 而不是改各页 width。 */
.nav .vc-btn {
  order: 4; flex: none;
  width: 40px; height: 40px; min-width: 40px; min-height: 40px;
  border-radius: 8px; border: 1px solid var(--vc-bar-line);
  background: var(--vc-idle-bg); color: var(--vc-idle-fg);
  display: flex; align-items: center; justify-content: center;
  padding: 0; transition: transform .16s, background .16s, box-shadow .16s;
}
.nav .vc-btn:hover { transform: scale(1.06); box-shadow: 0 2px 10px rgba(58, 110, 165, .18); }
.nav .vc-btn:active { transform: scale(.96); }
.nav .vc-btn.is-connecting {
  background: var(--vc-connect-bg); color: var(--vc-connect-fg);
  border-color: transparent; cursor: default; box-shadow: none;
}
.nav .vc-btn.is-connecting:hover { transform: none; }
.nav .vc-btn.is-active {
  background: var(--vc-active-bg); color: #fff; border-color: transparent;
  box-shadow: 0 2px 12px rgba(192, 57, 43, .28);
}
.nav .vc-btn:disabled { pointer-events: none; }
.vc-spin {
  display: block; width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid rgba(163, 156, 146, .35); border-top-color: var(--vc-connect-fg);
  animation: vc-rot .8s linear infinite;
}
@keyframes vc-rot { to { transform: rotate(360deg); } }

/* 左下角极小状态条：--app-gutter 保证折叠屏展开不飘出页面，
   --fab-bottom 让开底栏，pointer-events:none 保证不遮挡任何原有操作按钮。 */
.vc-bar {
  position: fixed; z-index: 88;
  left: calc(var(--app-gutter) + 12px);
  bottom: calc(var(--fab-bottom) + 8px);
  max-width: calc(var(--app-w) - 24px); min-width: 132px;
  padding: 6px 10px; border-radius: 12px;
  background: var(--vc-bar-bg); color: var(--vc-bar-fg);
  border: 1px solid var(--vc-bar-line); box-shadow: var(--shadow);
  /* 16px 是全站字号下限（check-font-floor 门禁）；「极小」靠 padding 与单行省略，
     不靠缩字号 —— 项目铁律：字号只许改容器，不许往回缩。 */
  font-size: 16px; line-height: 1.5; pointer-events: none;
}
.vc-bar[hidden] { display: none; }
.vc-bar-t { font-weight: 700; color: var(--vc-idle-fg); display: flex; align-items: center; gap: 5px; }
.vc-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--vc-active-bg); flex: none; }
.vc-line { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .82; }
.vc-line[hidden] { display: none; }

/* 麦克风权限弹窗（拒绝时才出现，不是 toast） */
.vc-mask {
  position: fixed; inset: 0; z-index: 95; background: var(--vc-mask);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.vc-mask[hidden] { display: none; }
.vc-modal {
  width: 100%; max-width: 320px; padding: 20px 18px 16px;
  border-radius: 18px; background: var(--vc-bar-bg); color: var(--vc-bar-fg);
  box-shadow: var(--shadow-lg); text-align: center;
}
.vc-modal p { font-size: 16px; line-height: 1.7; margin-bottom: 16px; }
.vc-modal .vc-ok {
  min-height: 40px; min-width: 120px; padding: 8px 18px; border-radius: 8px;
  background: var(--vc-idle-bg); color: var(--vc-idle-fg);
  font-family: inherit; font-size: 16px; font-weight: 700;
}
.vc-modal .vc-ok:active { filter: brightness(.94); }

/* 顶栏多出第 5 件时的窄屏让位：只压名字胶囊上限与 gap，
   品牌标仍由 app.css 的 .brand{min-width:28px} 保底，不会被压成 0 宽。 */
@media (max-width: 360px) {
  .nav { gap: 6px; }
  .nav .who { max-width: 84px; }
}
@media (prefers-reduced-motion: reduce) {
  .nav .vc-btn { transition: none; }
  .vc-spin { animation-duration: 2s; }
}
