/* ============================================================
   家园小程序 · 设计系统
   依据《家园小程序产品说明书 V2.0》3.4 视觉风格
   品牌青绿 #07b3a4（logo 文字色）+ 行动橙 #f56e20（logo 花瓣色）
   白底 + 浅灰卡片 / 卡片化 / 圆角 8-12px
   ============================================================ */

:root {
  /* ============================================================
     品牌配色 —— 取自「菊厂家园」官方 logo（2026-09-21 换肤）
     ------------------------------------------------------------
     logo 里「菊厂家园」四个字是青绿 #07b3a4，这才是品牌识别色；
     橙色在 logo 里只是六片花瓣中的一片（#f56e20）。
     因此拆成两套角色，而不是一个色打天下：
       --brand    青绿 → 品牌识别：导航选中、标签、进度、链接、进度条
       --action   花瓣橙 → 行动色：主 CTA 按钮（橙色更「热」，转化率更高）
     这样既忠于 logo，又不动摇按钮的点击力。
     ============================================================ */
  --brand: #07b3a4;          /* logo 文字色 · 青绿 */
  --brand-dark: #059286;     /* 按下 / 强调 */
  --brand-light: #e6f7f5;    /* 极浅底（标签、图标底） */
  --brand-soft: #c9eeea;     /* 描边 / 分隔 */

  --action: #f56e20;         /* logo 花瓣橙 · 主按钮 */
  --action-dark: #dd5a12;
  --action-light: #fff2ea;
  --action-soft: #ffdcc6;

  /* ---- logo 六色花瓣（仅用于数据可视化与分类标签）---- */
  --petal-orange: #f56e20;
  --petal-yellow: #fccb0e;
  --petal-green: #a3cd2d;
  --petal-teal: #07b3a4;
  --petal-rose: #ef3f5d;
  --petal-purple: #98458d;
  --petal-core: #fcd24b;

  /* ---- 功能色 ---- */
  --success: #16a34a;
  --warning: #f59e0b;
  --danger: #dc2626;
  --info: #2563eb;

  /* ---- 中性色 ---- */
  --bg: #f5f6f7;
  --card: #ffffff;
  --text: #1f2328;
  --text-2: #5b6470;
  --text-3: #98a1ad;
  --line: #eceef1;
  --line-2: #e3e6ea;

  /* ---- 字号层级 ---- */
  --fs-xs: 11px;
  --fs-sm: 12px;
  --fs-md: 13px;
  --fs-base: 14px;
  --fs-lg: 15px;
  --fs-xl: 17px;
  --fs-2xl: 20px;
  --fs-3xl: 24px;
  --fs-4xl: 30px;

  /* ---- 间距 ---- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;

  /* ---- 圆角（8-12px） ---- */
  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 12px;
  --r-xl: 16px;
  --r-pill: 999px;

  /* ---- 阴影 ---- */
  --sh-1: 0 1px 3px rgba(17, 24, 39, .06);
  --sh-2: 0 2px 10px rgba(17, 24, 39, .08);
  --sh-3: 0 8px 28px rgba(17, 24, 39, .14);

  /* ---- 布局 ---- */
  --tabbar-h: 54px;
  --navbar-h: 48px;
  --page-max: 480px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--text);
  background: #e9ebee;
  -webkit-font-smoothing: antialiased;
}

/* ============================================================
   手机外壳（桌面浏览时居中显示）
   ============================================================ */
#app {
  position: relative;
  max-width: var(--page-max);
  margin: 0 auto;
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  box-shadow: 0 0 40px rgba(0, 0, 0, .08);
}

/* 页面滚动区 */
.page-scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  /* 底部留白 = TabBar 高度 + 悬浮浮条(约 62px) + 呼吸位，保证最后一行内容可完整滚出遮挡区 */
  padding-bottom: calc(var(--tabbar-h) + 74px + env(safe-area-inset-bottom, 0px));
  -webkit-overflow-scrolling: touch;
}

/* ============================================================
   导航栏
   ============================================================ */
.navbar {
  position: sticky;
  top: 0;
  z-index: 60;
  height: var(--navbar-h);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-3);
  background: var(--card);
  border-bottom: 1px solid var(--line);
}

.navbar-title {
  font-size: var(--fs-xl);
  font-weight: 700;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.navbar-back {
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; cursor: pointer;
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: 20px;
  padding: 0;
  flex: none;
}
.navbar-back:active { background: var(--line); }

/* ============================================================
   底部 TabBar（五 Tab · 均分宽度，无凸起按钮）
   ============================================================ */
.tabbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  width: 100%;
  max-width: var(--page-max);
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  display: flex;
  background: rgba(255, 255, 255, .97);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  z-index: 100;
}

.tabbar-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-3);
  font-size: var(--fs-xs);
  position: relative;
  padding: 0;
  transition: color .15s;
}

.tabbar-item.active { color: var(--brand); font-weight: 600; }

/* ★ 文字标签（2026-09-30 用户口径：「左下角 tab 模板有问题，只有图标显示
   蓝色，字体不变，且字体大小好像跟设计样板也不一致」）
   ------------------------------------------------------------
   原型里图标是内联 `<svg stroke="currentColor">` —— 它和文字吃的是**同一条**
   `.tabbar-item.active { color }`，所以 H5 上「图标蓝了」必然等于「文字也蓝」，
   这两件事在原型里根本拆不开。
   小程序的图标换成了**两张位图**（未选中 #98a1ad / 选中 #07b3a4，
   见 custom-tab-bar/icons.js），颜色不走 CSS —— 于是这条链上就只剩文字，
   而文字**能不能从父级拿到 color / font-size 是不保证的**：小程序 `<text>`
   的继承不像 H5 `<span>` 那样可信（同源的坑已经让 `.share-qr-cap text` /
   `.kb-row text` 当初都必须显式命中标签）。
   用户报的两条症状正好一一对上：
     · **图标蓝、字还是灰的**   → color 没继承到；
     · **字号不像设计样板**     → font-size 没继承到，回落到 page 的 14px
                                  （设计样板是 --fs-xs = 11px）。
   ⇒ 给标签自己一条规则，并且**数值写成带字面量回落的 var()** ——
      连「变量本身在这一层取不到」也一并兜住。`var(--x, 字面量)` 是本项目
      已有的写法（见 `.action-bar.is-over-tab` 的 `--safe-b`），不是新花样。
   ⚠️ 必须用**类名**而不是只靠 `.tabbar-item.active` 的继承：小程序侧
      `.tabbar-label` 原样带过去（port-styles.py 只映射裸 HTML 标签名，
      类名不做转换），两端同一份规则。 */
.tabbar-item > .tabbar-label {
  color: inherit;
  font-size: var(--fs-xs, 11px);
}
.tabbar-item.active > .tabbar-label { color: var(--brand, #07b3a4); font-weight: 600; }

.tabbar-icon { width: 22px; height: 22px; display: block; }
.tabbar-icon svg { width: 100%; height: 100%; display: block; }

/* 角标 */
.tab-badge {
  position: absolute;
  top: 4px;
  left: 50%;
  margin-left: 6px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--danger);
  color: #fff;
  font-size: 10px;
  line-height: 16px;
  text-align: center;
  font-weight: 600;
}
.tab-dot {
  position: absolute;
  top: 8px;
  left: 50%;
  margin-left: 8px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--danger);
}

/* ============================================================
   通用排版
   ============================================================ */
h1, h2, h3, h4 { margin: 0; font-weight: 700; }

.sec-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-5) var(--sp-4) var(--sp-3);
}
.sec-title h2 {
  font-size: var(--fs-xl);
  letter-spacing: -.2px;
}
.sec-title .more {
  font-size: var(--fs-sm);
  color: var(--text-3);
  border: none; background: none; cursor: pointer;
  display: flex; align-items: center; gap: 2px;
}
/* 静态计数（没有对应 action 的右侧文案，如产品库的「共 12 款」）——
   去掉手型与按下反馈，避免看起来像个能点的入口 */
.sec-title .more.is-static { cursor: default; }

.muted { color: var(--text-3); }
.muted-2 { color: var(--text-2); }
.small { font-size: var(--fs-sm); }
.xs { font-size: var(--fs-xs); }
.bold { font-weight: 700; }
.brand { color: var(--brand); }
.nowrap { white-space: nowrap; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clamp2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.clamp3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ============================================================
   卡片
   ============================================================ */
.card {
  background: var(--card);
  border-radius: var(--r-lg);
  margin: 0 var(--sp-4) var(--sp-3);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.card-pad { padding: var(--sp-4); }
.card-flat { box-shadow: none; border: 1px solid var(--line); }

.card-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-4) 0;
}
.card-head h3 { font-size: var(--fs-lg); flex: 1; }

/* ============================================================
   按钮
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: none;
  border-radius: var(--r-sm);
  font-size: var(--fs-base);
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  padding: 10px 16px;
  transition: opacity .15s, transform .1s;
  line-height: 1.4;
}
.btn:active { transform: scale(.98); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn-primary { background: var(--action); color: #fff; }
.btn-primary:active { background: var(--action-dark); }
.btn-ghost { background: var(--brand-light); color: var(--brand); }
.btn-outline { background: #fff; color: var(--text); border: 1px solid var(--line-2); }
.btn-block { display: flex; width: 100%; }
.btn-lg { padding: 13px 20px; font-size: var(--fs-lg); border-radius: var(--r-md); }
.btn-sm { padding: 6px 12px; font-size: var(--fs-sm); border-radius: var(--r-sm); }
.btn-text { background: none; color: var(--brand); padding: 4px 0; font-weight: 500; }

/* ============================================================
   标签 / 徽章
   ============================================================ */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 7px;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  line-height: 1.5;
  background: var(--line);
  color: var(--text-2);
  white-space: nowrap;
  font-weight: 500;
}
.tag-brand { background: var(--brand-light); color: var(--brand); }
.tag-success { background: #e7f7ee; color: var(--success); }
.tag-warn { background: #fef3e2; color: #b45309; }
.tag-danger { background: #fdeaea; color: var(--danger); }
.tag-info { background: #e8f0fe; color: var(--info); }
.tag-solid { background: var(--brand); color: #fff; }

/* ---- 小区详情链接（第八十五轮） ----
   房源详情「小区」那一行的值。用户口径只说了「小区名称需要加链接」，
   没说加提示文案；项目又有一条「说明性内容不显示」的硬口径（CX.inlineNote），
   所以**可点击性只能靠视觉传达**：品牌色 + 右箭头 + 按压反馈，三者缺一不可。
   ⚠️ 这里是 <button> 而不是 <a href>（H5 是单页应用，没有真实路由，
      `<a href>` 会让浏览器真的跳走、回不来）⇒ 必须把 button 的默认外观
      （背景 / 边框 / 字体 / 内边距）全部清掉，否则会出现一个灰底小方块。
   ⚠️ 右箭头用 <i> 加 `.cm-link-arrow`，不改按钮的点击热区 ——
      箭头与文字在同一个 button 内，点哪儿都能进详情。 */
.cm-link {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 0; margin: 0; border: 0; background: none;
  font-family: inherit; font-size: inherit; line-height: inherit;
  color: var(--brand); font-weight: 600;
  cursor: pointer; text-align: right;
  -webkit-tap-highlight-color: transparent;
}
.cm-link:active { opacity: .6; }
.cm-link-arrow { font-style: normal; font-size: 1.15em; line-height: 1; opacity: .75; }

/* ---- 申请流程（竖排步骤条）+ 申请材料（清单）（第八十五轮） ----
   信贷详情浮层里的两块。字段在后台是 `apply_flow` / `materials`（type: lines），
   运营一行写一步 / 一行写一份，这里按行渲染。

   ⚠️ 流程**必须画出「先后」**：序号圆点 + 一条竖线连到下一个圆点。
      只排一列「1. … 2. …」看着像清单，看不出「第 2 步要在第 1 步之后」。
      竖线用 `.cm-step::before`，并且**最后一条不画**（`.is-last`）——
      否则浮层底部会多出一截悬空的线，看着像内容被截断了。
   ⚠️ 竖线的 left 值与圆点半径对齐（22/2 = 11px），改了圆点尺寸记得一起改。 */
.cm-steps { padding-left: 2px; }
.cm-step { position: relative; display: flex; gap: var(--sp-3); padding-bottom: var(--sp-3); }
/* 竖线：**默认画**，最后一条显式 `display: none` 收掉。
   ⚠️ 刻意**不用** `.cm-step:not(.is-last)::before` —— 本文件上面 `.filter-chip`
      那一段的注释已经写死这条规矩：小程序 WXSS 对 `:not()` 的支持要看基础库，
      显式覆盖更稳，也更容易被走查断言钉住。app.css 是两端共用的唯一真相源，
      所以 H5 也照同一条口径写，不为 H5 单独用一个更花哨的选择器。 */
.cm-step::before {
  content: ''; position: absolute; left: 11px; top: 26px; bottom: 2px;
  width: 1px; background: var(--line);
}
.cm-step.is-last { padding-bottom: 0; }
.cm-step.is-last::before { display: none; }
.cm-step-no {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-light); color: var(--brand);
  font-size: var(--fs-xs); font-weight: 700; line-height: 1;
}
.cm-step-text { font-size: var(--fs-sm); color: var(--text-2); line-height: 1.5; padding-top: 3px; }

.cm-mats { display: flex; flex-direction: column; gap: var(--sp-2); }
.cm-mat { display: flex; gap: var(--sp-2); align-items: flex-start; }
.cm-mat-dot { flex: none; color: var(--brand); display: inline-flex; padding-top: 1px; }
.cm-mat-text { font-size: var(--fs-sm); color: var(--text-2); line-height: 1.5; }

/* ============================================================
   列表行
   ============================================================ */
.list-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  background: var(--card);
}
.list-row:last-child { border-bottom: none; }
.list-row:active { background: #fafbfc; }
.list-row .lr-label { flex: 1; min-width: 0; font-size: var(--fs-base); }
.list-row .lr-value {
  font-size: var(--fs-md);
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.chev { color: var(--text-3); font-size: 15px; flex: none; }

/* ============================================================
   表单
   ============================================================ */
.field { padding: 0 var(--sp-4) var(--sp-4); }
.field-label {
  display: block;
  font-size: var(--fs-md);
  font-weight: 600;
  margin-bottom: var(--sp-2);
}
.field-label .req { color: var(--danger); margin-left: 2px; }

.input, .textarea, .select {
  width: 100%;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: 11px 12px;
  font-size: var(--fs-base);
  font-family: inherit;
  color: var(--text);
  background: #fff;
  outline: none;
  transition: border-color .15s;
  /* ★★ 2026-10-01（第八十一轮）真机截图为证：**第七十九轮那条是无效的**。
     原因在 `box-sizing` —— 全局是 `border-box`（文件顶 `*` 与 app.wxss 的标签枚举），
     `min-height` 约束的是**边框盒**，而上下留白 22 + 边框 2 = **24**，
     本身就已超过当时写的 22 ⇒ 那条 `min-height: 22px` 是个 no-op，一个字都没生效。
     ⚠️ 下面所有数字都是「设计稿 px」，**刻意不带 px 后缀** —— `port-styles.py`
       连注释里的「数字+px」也一起 ×2 换成 rpx，带上后缀这段说明就会自相矛盾
       （实测：生成后「22 + 2 = 24」会变成「44rpx + 4rpx = 48rpx」而前半句仍是 22）。

     真机上为什么照样裁字：浏览器 `<input>` 有**固有内容高度**（≈ 行盒 = 1.4 × 14 = 19.6），
     所以 H5 的边框盒 = 19.6 + 24 = 43.6，看着完全正常；
     微信原生 `<input>` 是原生组件、**没有固有内容高度** ⇒ 内容区塌成 0
     ⇒ 边框盒只剩 24（= 48rpx）。
     用户真机截图实测：框高 62 物理px ÷ 1.344（1rpx 的物理px）= 46rpx ≈ 48rpx，
     与「内容区为 0」严丝合缝；占位文字被从下方裁掉约 40% 字身。

     ⇒ 修法：**给出真实的内容区高度**，不是「给个下限」：
       `min-height` 抬到 46（= 92rpx）⇒ 内容区 = 46 − 24 = 22 > 行盒 19.6 ✓；
       另由下面 `.input` 再钉一条显式 `height` —— 原生 `<input>` 对 `min-height`
       的支持不保证一致，`height` 才是稳的。
     H5 侧 43.6 → 46（+2.4，肉眼无感），两端重新对齐。 */
  min-height: 46px;
  line-height: 1.4;
}

/* 原生 `<input>` 单独把高度钉死（上面那条同时覆盖着 .textarea / .select）。
   ⚠️ **只钉 `.input`**：`.textarea` 要能多行增长、靠自己的 `min-height` 撑，
      给它钉死高度会毁掉输入体验。 */
.input { height: 46px; }
.input:focus, .textarea:focus, .select:focus { border-color: var(--brand); }
.textarea { resize: none; min-height: 76px; }
.input::placeholder, .textarea::placeholder { color: var(--text-3); }

/* 选项组 */
.options { display: flex; flex-direction: column; gap: var(--sp-2); }
.option {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 13px var(--sp-4);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: #fff;
  cursor: pointer;
  transition: all .15s;
  font-size: var(--fs-base);
}
.option:active { background: #fafbfc; }
.option.selected {
  border-color: var(--brand);
  background: var(--brand-light);
  color: var(--brand);
  font-weight: 600;
}
.option-radio {
  width: 18px; height: 18px;
  border: 1.5px solid var(--line-2);
  border-radius: 50%;
  flex: none;
  position: relative;
}
.option.selected .option-radio { border-color: var(--brand); }
.option.selected .option-radio::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--brand);
}
.option-check {
  width: 18px; height: 18px;
  border: 1.5px solid var(--line-2);
  border-radius: 5px;
  flex: none;
  position: relative;
}
.option.selected .option-check { border-color: var(--brand); background: var(--brand); }
.option.selected .option-check::after {
  content: '';
  position: absolute;
  left: 5px; top: 2px;
  width: 5px; height: 9px;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: rotate(45deg);
}

/* 宫格选择 */
.chip-grid { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.chip {
  padding: 8px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: #fff;
  font-size: var(--fs-md);
  cursor: pointer;
  transition: all .15s;
}
.chip.selected {
  border-color: var(--brand);
  background: var(--brand-light);
  color: var(--brand);
  font-weight: 600;
}

/* ============================================================
   进度条
   ============================================================ */
.progress {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--line);
  overflow: hidden;
}
.progress-bar {
  height: 100%;
  background: linear-gradient(90deg, #6bd8cd, var(--brand));
  border-radius: var(--r-pill);
  transition: width .4s ease;
}

/* 分步表单进度 */
.steps { display: flex; align-items: center; gap: 6px; }
.step-dot {
  flex: 1;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--line);
  transition: background .25s;
}
.step-dot.done { background: var(--brand); }
.step-dot.current { background: var(--brand-soft); }

/* ============================================================
   底部固定操作条
   ============================================================ */
.action-bar {
  position: sticky;
  bottom: 0;
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
  background: rgba(255, 255, 255, .97);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  display: flex;
  gap: var(--sp-3);
  z-index: 50;
}
/* 悬浮留资条（详情页底部固定） */
.float-cta {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--tabbar-h) + 8px + env(safe-area-inset-bottom, 0px));
  width: calc(100% - 24px);
  max-width: calc(var(--page-max) - 24px);
  background: linear-gradient(135deg, #fff4ee, #ffffff);
  border: 1px solid var(--brand-soft);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  padding: 10px 12px;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  z-index: 90;
}

/* ============================================================
   弹层 / 抽屉
   ============================================================ */
.mask {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .45);
  z-index: 200;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: fadeIn .2s;
  /* 抽屉内的输入框聚焦时不要被软键盘顶飞；
     底部额外留白，避免抽屉底部按钮贴在页面级固定元素（悬浮球 / 浮条 / 标签栏）后面。
     ★ 2026-09-30（第七十九轮）把这个**意图真的实现**（原值一直是 0，注释与实现不符）：
       真机上**自定义 TabBar 是「页面之上」的一层，页面里的 z-index 盖不住它**
       —— 用户真机口径：「资讯文章浮层最下面『分享给好友』被页面下部的 tab 遮挡」、
       「点预约团办 / 产品详情页，下面都被底部 tab 遮挡」。
       修法：Tab 页由 `safeStyle` 注入 `--sheet-pad`（= TabBar 高 + 安全区），
       浮层据此**让出**一条给它；非 Tab 页没有 TabBar ⇒ 变量不存在 ⇒ 回落 0、行为不变。
       H5 侧也**永不定义**它（原型的 TabBar 是普通层、被 z-index 200 的 mask 盖住），
       所以两端共用这一行源码而各自正确。
     ★★ 2026-10-01（第八十轮）注意**这个变量注入在哪**：`.mask` 是 `.page-scroll`
       的**兄弟**（浮层刻意写在滚动容器之外，见各页 WXML），而 CSS 自定义属性
       只向下继承 ⇒ 只把 `--sheet-pad` 写在页面根节点上，这里永远取到 0，
       真机上一点效果都没有。所以**每一处 `.mask` 自己也要挂一份 `safeStyle`**。 */
  padding-bottom: var(--sheet-pad, 0);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.sheet {
  width: 100%;
  max-width: var(--page-max);
  background: #fff;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  max-height: 86vh;
  overflow: hidden;          /* 由 .sheet-body 单独滚动，避免 footer 被滚出视口 */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  animation: slideUp .26s cubic-bezier(.22, 1, .36, 1);
  display: flex;
  flex-direction: column;
}
@keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }

.sheet-head {
  position: sticky;
  top: 0;
  background: #fff;
  padding: var(--sp-4) var(--sp-4) var(--sp-3);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  z-index: 2;
}
.sheet-head h3 { font-size: var(--fs-xl); flex: 1; }
.sheet-close {
  border: none; background: none; cursor: pointer;
  font-size: 22px; color: var(--text-3); padding: 0 4px; line-height: 1;
}
/* 抽屉头部右上角的**分享小图标**（第八十轮）。
   用户真机口径：「浮层底部那个分享给好友被底部 tab 挡住，建议删除，
   再浮层右上角做个分享小图标即可，跟其他页面风格也统一」。
   ⚠️ 版式与 .sheet-close 对齐（同排、同色、同 padding），**不给单边 margin** ——
      两枚图标之间的间距由 .sheet-head 的 `gap: var(--sp-2)` 统一控制，
      单独给一边加 margin 会让它们一宽一窄。
   ⚠️ 图标尺寸由 Share.ICON 自带的 inline `width:17px;height:17px` 决定，
      这里**不重复声明**（两处尺寸就是两个真相源，改一处漏一处）；
      本规则只管点击区与颜色。
   ⚠️ 小程序侧的微信 `<button>` 复位（`margin:auto` / `::after` 灰边）在
      port-styles 的 **TAIL** 里，不写第二份 —— 同一个类两处定义 = 改一处漏一处。
   ⚠️ 第八十二轮起这枚图标**不再只有首页的文章浮层**用：团办详情与信贷产品
      详情也各有一枚（见 .sheet-fav 上面那条）。 */
.sheet-share {
  border: none; background: none; cursor: pointer;
  color: var(--text-3); padding: 0 4px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.sheet-share:active { opacity: .6; }
/* 抽屉头部右上角的**收藏心形**（第八十二轮）。
   用户真机口径：「团办详情底栏有报名和分享，这样也可以，但是右上角的收藏没了，
   信贷产品详情浮层里面也是一样」——追问后确认收藏要进「我的收藏」、四类并列。
   版式与 .sheet-share **逐条相同**（同排、同色、同 padding、同样不加单边 margin ——
   间距由 .sheet-head 的 `gap: var(--sp-2)` 统一控制），唯一区别是它**有选中态**：
   `on` 时换品牌色（实心/空心由 JS 换 svg，见 app.js 的 `case 'fav'`）。
   ⚠️ `.on` 这个类名与 app.js 那两行原地换图是**一对**：抽屉挂在 <body> 上、
      `App.render()` 重写的是 `#app` ⇒ 重渲不到它，只能靠 JS 手动同步。
      改一处必须改另一处，否则表现是「点了收藏、心形没变」。
   ⚠️ 尺寸由 svg 自带的内联 style 决定（Icons.heart / Icons.heartFill），
      这里不重复声明。 */
.sheet-fav {
  border: none; background: none; cursor: pointer;
  color: var(--text-3); padding: 0 4px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.sheet-fav.on { color: var(--brand); }
.sheet-fav:active { opacity: .6; }
.sheet-body {
  padding: var(--sp-4);
  flex: 1 1 auto;
  min-height: 0;             /* 允许 flex 子项收缩，否则无法滚动 */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.sheet-foot {
  flex: 0 0 auto;            /* 固定在抽屉底部，不随内容滚动 */
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line);
  background: #fff;
  position: relative;
  z-index: 3;                /* 高于页面级 fixed 元素，避免被浮条/悬浮球遮挡 */
}

/* 居中弹窗 */
.dialog {
  margin: auto;
  width: calc(100% - 56px);
  max-width: 320px;
  background: #fff;
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  animation: popIn .22s cubic-bezier(.22, 1, .36, 1);
}
@keyframes popIn { from { transform: scale(.92); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.dialog h3 { font-size: var(--fs-xl); margin-bottom: var(--sp-3); }
.dialog p { margin: 0 0 var(--sp-4); font-size: var(--fs-md); color: var(--text-2); }
.mask-center { align-items: center; }

/* ============================================================
   Toast
   ============================================================ */
.toast {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: rgba(28, 32, 38, .92);
  color: #fff;
  padding: 12px 20px;
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  z-index: 400;
  max-width: 76%;
  text-align: center;
  animation: popIn .2s;
  line-height: 1.5;
  /* 提示是「只能看、不能点」的浮层：必须让点击穿透，
     否则在它显示的 1~2 秒内会吞掉其覆盖区域的所有点击
     （位于屏幕中央，恰好压住列表中部的主操作按钮）。 */
  pointer-events: none;
}

/* ============================================================
   AI 悬浮助手
   ============================================================ */
.ai-fab {
  position: fixed;
  right: max(12px, calc((100vw - var(--page-max)) / 2 + 12px));
  bottom: calc(var(--tabbar-h) + 18px + env(safe-area-inset-bottom, 0px));
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--action), #ff9a5c);
  color: #fff;
  border: none;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(7, 179, 164, .38);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 120;
  transition: transform .18s;
}
.ai-fab:active { transform: scale(.92); }
.ai-fab.hidden { display: none; }
/* ★ 有悬浮留资条（`.float-cta`）时把球收起来（第六十一轮，真机排版修复）。
   两者都 `fixed` 贴右下：`.float-cta` 高 59、底边在 `--tabbar-h + 8px`，
   而球的底边在 `--tabbar-h + 18px` ⇒ **球整个落在浮动条右端那颗按钮上**
   （实测矩形交集 52×49px，占球面积 94%），看着就是「框框叠在一起」。
   小程序侧本来就只在首页挂球（服务台 / 详情页都没有），
   这里让原型对齐同一口径 —— 有浮动条就不摆球，少一个遮挡也少一份歧义。 */
body:has(.float-cta) .ai-fab { display: none; }

/* AI 对话面板 */
.ai-panel {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  width: 100%;
  max-width: var(--page-max);
  height: 78vh;
  background: var(--bg);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  animation: slideUp .26s cubic-bezier(.22, 1, .36, 1);
  box-shadow: var(--sh-3);
}
.ai-head {
  padding: var(--sp-3) var(--sp-4);
  background: #fff;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex: none;
}
.ai-avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--action), #ff9a5c);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px;
  flex: none;
}
.ai-msgs {
  flex: 1;
  overflow-y: auto;
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.ai-msg {
  max-width: 82%;
  padding: 10px 13px;
  border-radius: var(--r-md);
  font-size: var(--fs-md);
  line-height: 1.6;
  word-break: break-word;
}
.ai-msg.bot {
  align-self: flex-start;
  background: #fff;
  border-bottom-left-radius: 4px;
  box-shadow: var(--sh-1);
}
.ai-msg.user {
  align-self: flex-end;
  background: var(--brand);
  color: #fff;
  border-bottom-right-radius: 4px;
}
.ai-quick {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-3);
}
.ai-input-bar {
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
  background: #fff;
  border-top: 1px solid var(--line);
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  flex: none;
}

/* ============================================================
   房源 / 产品卡片
   ============================================================ */
.house-card { display: flex; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line); cursor: pointer; background: #fff; }
.house-card:last-child { border-bottom: none; }
.house-card:active { background: #fafbfc; }

.house-thumb {
  width: 108px;
  height: 82px;
  border-radius: var(--r-sm);
  flex: none;
  background: linear-gradient(135deg, #f0f2f5, #e6e9ed);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #b9c0c9;
  font-size: 22px;
  overflow: hidden;
  position: relative;
}
.house-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* ★ 房源照片（**滚动图**，最多 10 张）—— 2026-10-10 补齐。
   在此之前 H5 原型**两侧都没有图位**（app.css 零 `url(`、house.js 零 `<img`），
   而它自称「设计真相源」⇒ 小程序与真机版反而跑在它前面。
   高度与小程序 `.hd-gallery` 同值（440rpx ⇔ 220px），两端是同一块版式。
   ⚠️ 高度写在**轨道**上、图片用 height:100% —— 与小程序页面已有的那两条
      （`.hd-gallery{height:440rpx}` / `.hd-gallery-img{height:100%}`）逐条对齐，
      这样 port-styles 生成 app.wxss 时不会和页面里已有的规则打架。 */
.hd-gallery { position: relative; background: var(--bg); }
.hd-gallery-track {
  display: flex;
  height: 220px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.hd-gallery-track::-webkit-scrollbar { display: none; }
.hd-gallery-img {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  scroll-snap-align: start;
}
/* 圆点：只在**多图**时渲染（单图配一排点会让人以为还有别的图）。
   加一层极浅的外描边 —— 浅色照片上纯白点会看不见。 */
.hd-gallery-dots {
  position: absolute; left: 0; right: 0; bottom: 10px;
  display: flex; gap: 5px; justify-content: center;
  pointer-events: none;
}
.hd-gallery-dots i {
  width: 6px; height: 6px; border-radius: 50%;
  background: rgba(255, 255, 255, .5);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .18);
}
.hd-gallery-dots i.on { background: #fff; }
/* ★★ 2026-10-12（第九十三轮）无图时的**占位块**。
   用户口径：「新房和二手房，都需要像法拍房那样留出图片区域」——
   所以没有照片也**不能整块消失**，要给一个同尺寸的位置。
   改前 `.hd-gallery` 由 JS 在无图时整块不渲染 ⇒ 二手房 / 新房详情页
   连「这里有图位」都看不出来（用户就是这么报的）。
   ⚠️ 高度必须与 `.hd-gallery-track` **同值**（220px ⇔ 小程序 440rpx）——
      留位而尺寸不同，等于没留；两端同一条口径。
   ⚠️ 只用既有 CSS 变量（--bg / --line / --text-3），明暗主题自动跟随。 */
.hd-gallery-ph {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--line);
  box-sizing: border-box;
}
.hd-gallery-ph-inner { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.hd-gallery-ph-icon { font-size: 26px; line-height: 1; opacity: .55; }
.hd-gallery-ph-text { font-size: var(--fs-xs); color: var(--text-3); }
.house-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.house-title {
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.45;
  margin-bottom: 5px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.house-meta { font-size: var(--fs-xs); color: var(--text-3); margin-bottom: 5px; }
.house-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.house-price-row { margin-top: auto; display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
.house-price { color: var(--brand); font-weight: 700; font-size: var(--fs-xl); line-height: 1.2; }
.house-price .unit { font-size: var(--fs-xs); font-weight: 500; }

/* 房源卡的触达操作条（2026-09-28：预约看房 / 了解详情）。
   ⚠️ 只在「找房列表页」开（HouseCard 的 opts.actions）—— 本卡还在首页房源轨 /
      搜索浮层 / 小区档案 / 测算结果里复用，那些位置各有自己的 CTA，
      再塞一行会喧宾夺主（搜索浮层里更是会挤掉结果本身）。
   ⚠️ 两个按钮都带 `data-action`：全局委托里 `[data-action]` 优先于 `[data-house]`，
      点按钮**不会**连带把详情页打开 —— 这是刻意的，两个动作分开。 */
.hc-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }
.hc-actions .btn { min-height: 32px; }

/* ============================================================
   房源详情的「三种联系方式」卡（预约 / 电话 / 企微）
   ------------------------------------------------------------
   用户口径（2026-09-28）：「多维度引导客户联系销售」。
   三条并列而不是一条主 CTA —— 看房的人卡在三种不同心理位置，
   一条通道只能接住其中一种。 */
.ch-rows { display: flex; flex-direction: column; gap: var(--sp-2); }
.ch-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  text-align: left;
  padding: 11px var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: #fff;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.ch-row:active { background: #fafbfc; }
.ch-ico { font-size: 20px; flex: none; line-height: 1; }
/* ⚠️ 用显式类而不是 `b` / `i` 选择器：这份 CSS 会被 `migration/port-styles.py`
   整份转成 app.wxss，而小程序里 `<b>` / `<i>` 不是能用标签 —— 裸标签选择器
   过不去（`check-classes.py` 也要模板里的每个 class 都有规则）。 */
.ch-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ch-txt .ch-t { font-size: var(--fs-base); font-weight: 600; }
.ch-txt .ch-d { font-size: var(--fs-xs); color: var(--text-3); line-height: 1.5; }
.ch-go { flex: none; color: var(--text-3); display: flex; align-items: center; }
/* 「已收藏」提示条：把「收藏」这个动作接到「让顾问帮你盯」上 ——
   收藏本身不是目的，用户要的是「别错过节点」，那件事只有顾问能落地。 */
.ch-hint {
  display: flex;
  gap: var(--sp-2);
  align-items: flex-start;
  background: var(--brand-light);
  border: 1px solid var(--brand-soft);
  border-radius: var(--r-sm);
  padding: 9px var(--sp-3);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--text-2);
  line-height: 1.6;
}

/* ============================================================
   数据网格
   ============================================================ */
.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-1); }
.stat-grid-2 { grid-template-columns: repeat(2, 1fr); }
.stat-item { text-align: center; padding: var(--sp-3) var(--sp-2); }
.stat-num { font-size: var(--fs-2xl); font-weight: 700; color: var(--brand); line-height: 1.25; }
.stat-label { font-size: var(--fs-xs); color: var(--text-3); margin-top: 2px; }

.kv-list { display: flex; flex-direction: column; }
.kv-row {
  display: flex;
  padding: 9px 0;
  border-bottom: 1px dashed var(--line);
  font-size: var(--fs-md);
  gap: var(--sp-3);
}
.kv-row:last-child { border-bottom: none; }
.kv-k { color: var(--text-3); width: 84px; flex: none; }
.kv-v { flex: 1; min-width: 0; }
/* ★★ 第一百零八轮（2026-10-14 用户反馈）：「法拍房字段较多，建议详情页分组展示，
   如：基本信息 / 价格信息 / 拍卖信息 / 楼栋信息」。
   分组标题行（数据里是 `['§', 组名]`，见 pages/house.js 的 rowHtml）——
   只用在**房源详情**的参数明细里。
   ⚠️ 故意**不沿用** `.kv-row` 那条虚线：组头是「章节名」而不是一条数据，
      加虚线会让人以为它也是一行可读的参数。
   ⚠️ 两端共用这一份（小程序那份由 migration/port-styles.py 从这里派生）。 */
.kv-group {
  padding: 10px 0 4px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--brand);
  letter-spacing: .02em;
}
.kv-group:first-child { padding-top: 0; }

/* ============================================================
   时间轴
   ============================================================ */
.timeline { position: relative; padding-left: 22px; }
.timeline::before {
  content: '';
  position: absolute;
  left: 5px; top: 6px; bottom: 6px;
  width: 2px;
  background: var(--line);
}
.tl-item { position: relative; padding-bottom: var(--sp-4); }
.tl-item:last-child { padding-bottom: 0; }
.tl-dot {
  position: absolute;
  left: -22px;
  top: 4px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--line-2);
}
.tl-item.done .tl-dot { background: var(--brand); border-color: var(--brand); }
.tl-item.current .tl-dot {
  background: #fff;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-light);
}
.tl-title { font-size: var(--fs-md); font-weight: 600; }
.tl-time { font-size: var(--fs-xs); color: var(--text-3); margin-top: 1px; }

/* ============================================================
   通用工具类
   ============================================================ */
.row { display: flex; align-items: center; }
.row-between { display: flex; align-items: center; justify-content: space-between; }
/* ⚠️ 兼容写法：loan.js 里大量用的是 `class="row space-between"`，
   但 `.space-between` 之前**没有定义**，所以那些「标题左 / 数值右」的卡片头
   一直悄悄退化成左对齐（2026-09-21 查成交价结果页发现）。
   这里补上这个工具类，既修新页面也顺带修了查房价结果页的同类问题。
   不要删 —— 删了 loan.js 的 13 处会集体回退。 */
.space-between { justify-content: space-between; }
.col { display: flex; flex-direction: column; }
.gap-1 { gap: var(--sp-1); }
.gap-2 { gap: var(--sp-2); }
.gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); }
.flex-1 { flex: 1; min-width: 0; }
.wrap { flex-wrap: wrap; }
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
/* ★ 2026-10-14（第一百零九轮）：补上 `.mb-1`。
   `mt-1..4` 一直是全的，`mb` 却从 `2` 开始 —— 于是 `.mb-1` 在页面上**一直是个空类**
   （最少一处：biz.js 的 `row-between mb-1`，此前压根没生效，只是没人看得出来）。
   小程序的类名覆盖检查（migration/check-classes.py）把这种「写了但没定义」抓了出来。 */
.mb-1 { margin-bottom: var(--sp-1); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.px-4 { padding-left: var(--sp-4); padding-right: var(--sp-4); }
.py-3 { padding-top: var(--sp-3); padding-bottom: var(--sp-3); }
.hidden { display: none !important; }
.text-center { text-align: center; }
.divider { height: 1px; background: var(--line); margin: var(--sp-3) 0; }
.divider-lg { height: 8px; background: var(--bg); }

/* 空状态 */
.empty {
  padding: 48px var(--sp-4);
  text-align: center;
  color: var(--text-3);
}
.empty-icon { font-size: 40px; margin-bottom: var(--sp-3); opacity: .45; }
.empty-text { font-size: var(--fs-md); }

/* 合规提示条 */
.compliance {
  display: flex;
  gap: 6px;
  padding: 10px var(--sp-3);
  background: #fbfbfc;
  border: 1px dashed var(--line-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.55;
}
.compliance .ci { flex: none; }

/* 关键提示块 */
.callout {
  padding: var(--sp-3) var(--sp-4);
  background: var(--brand-light);
  border-left: 3px solid var(--brand);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--fs-md);
  color: #8a3a12;
  line-height: 1.6;
}
.callout-info { background: #eef4ff; border-left-color: var(--info); color: #1e40af; }
.callout-success { background: #ecfdf3; border-left-color: var(--success); color: #15803d; }

/* 骨架屏 */
.skeleton {
  background: linear-gradient(90deg, #f0f1f3 25%, #e8eaed 37%, #f0f1f3 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease infinite;
  border-radius: var(--r-sm);
}
@keyframes shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* 翻转/淡入 */
.fade-enter { animation: pageIn .22s ease; }
@keyframes pageIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* 滚动横滑 */
.hscroll {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  padding: 0 var(--sp-4) var(--sp-3);
  scrollbar-width: none;
}
.hscroll::-webkit-scrollbar { display: none; }
.hscroll > * { flex: none; }

/* ============================================================
   首页专用
   ============================================================ */
.home-header {
  /* ============================================================
     ★ 2026-09-30 用户口径：「首页顶部背景模板改为跟底部一致的白色风格」
     ------------------------------------------------------------
     改前 = `linear-gradient(160deg, 青绿…)` + 白字（第六十一轮·续那版）；
     改后 = **白底 + 下边框**，与其余四 Tab 的导航栏 `.navbar`、
            底部 TabBar `.tabbar` 收成同一套：
              background      rgba(255, 255, 255, .97)
              backdrop-filter blur(12px)
              border-bottom   1px solid var(--line)

     ⚠️ 换底色**必然**连带翻下面三个「为渐变底写的浅色控件」，
        否则是「白字 / 白底落在白头上」—— **不报错，只是整块看不见**：
          · `.city-picker`  半透明白底 + 白字        → 浅灰底 + 深字
          · `.home-search`  白胶囊（白头上没有边界） → 浅灰底 + 细描边
          · `.home-share`   白底 + 描边              → 保持原样（纯白头上只留一圈细边，可接受）
     ★★ 2026-10-13（第一百零三轮，小程序侧 v2.2.50）**反转**上面那条
        「这块不是 sticky」：用户口径「首页头部还是有问题，可以参考图示的
        信贷页面头部处理，把最上面一行做**固定形态**，这样就不会出问题了」
        ⇒ 本行改成**吸顶**（`position: sticky; top: 0`），与其余四 Tab 的
        `.navbar` 收成同一种形态。
     ⚠️ 原注释的顾虑（「吸顶会把 banner / 快讯挤出首屏」）**不成立**：
        还有一版等高的占位在流内（H5 就是这块自己的高度），
        首屏内容的位置一点不变 —— 吸顶只是让它滚动时不跟着走。
     ⚠️ 因此上面那条 `backdrop-filter` 从「写了不生效」变成**真生效**
        （滚动时从半透明白条下穿过的内容会被模糊），与 `.tabbar` 同一套观感。
     ⚠️ 两端**同义不同形**：小程序 `navigationStyle: "custom"`，头部还要让开
        状态栏与右上角胶囊 ⇒ 那边是「.nav-status 占位 + .nav-hold 占位 +
        .nav-fixed 本体」三块（见 migration/port-styles.py 的 TAIL）；
        H5 没有状态栏/胶囊，一块 sticky 就够。判据是**行为同义**：
        两端都是「滚动时头部不动」，不是「类名一样」。
     ⚠️ 历史教训（别丢）：这块原是**彩色头部**，`.main-cta` / `.archive-card`
        曾用负边距「骑」在它上面，用户手机上一眼判为缺陷（两侧露出青色
        「肩膀」）。⇒ 无论头部是彩是白，**不许任何卡片压头部**；
        `qa/overlap-probe.js` 的 `KNOWN` 免报名单已清空，再相交就是回归。
     ============================================================ */
  background: rgba(255, 255, 255, .97);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
  padding: var(--sp-3) var(--sp-4);
  /* ★ 第一百零三轮：吸顶（与 .navbar 同形）。z-index 取 60 —— 与 .navbar
     一致，低于 .mask(200)。 */
  position: sticky;
  top: 0;
  z-index: 60;
}
/* ★★ 2026-10-13（第一百零一轮）用户口径：「城市选择 放最上面左上角，
   最上面中间居中显示小程序名称 菊厂家园」⇒ `.hh-top` 从「flex 一行依次排」
   改成**三列网格** `1fr auto 1fr`：
     左列 = 城市（`start`）／中列 = 站名（自动宽、居中）／右列 = 留白（分享按钮）。
   ⚠️ 为什么不用 `text-align:center`：左列有内容时，「居中」只发生在**剩余空间**里
      ⇒ 标题会被推得偏右。`1fr auto 1fr` 让左右两列**等宽**，中列才真的落在屏幕中线。
   ⚠️ 分享按钮默认是关的（`ShareBtn` 返回空串）⇒ 右列是空网格单元；
      空单元同样占 `1fr`，所以**关掉分享不会把标题带偏**（这点必须成立，
      否则「开关一关、版式就歪」又是一类只有逐屏比对才发现的漂移）。 */
.home-header .hh-top {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--sp-3);
}
.home-header .hh-top .city-picker { justify-self: start; }
.home-header .hh-top .home-share { justify-self: end; }
/* 站名（居中那一行）。⚠️ 字号只到 `--fs-xl`(17px)、**不用**品牌行那档 `--fs-3xl`(24px)：
   它是**导航栏标题**的量级，不是页面 hero —— 用大字号会把头部撑高、
   把首屏的 banner / 快讯顶下去（那两块才是用户点名要「置顶到页头正下方」的内容）。
   颜色用 `--brand`(青绿 #07b3a4) —— 与 logo 里「菊厂家园」四个字的品牌识别色一致
   （2026-10-13 用户点名「品牌蓝色」；本仓品牌色即 logo 文字色，非功能色 `--info`）。 */
.hh-title {
  font-size: var(--fs-xl);
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--brand);
  text-align: center;
  white-space: nowrap;
}

.city-picker {
  display: flex;
  align-items: center;
  gap: 3px;
  /* ★ 2026-09-30：原为「半透明白底 rgba(255,255,255,.2) + 白字」，那是给
     青绿渐变头部写的；头部改白之后变成白字白底，整块会消失。
     改成浅灰底 + 深字（与 .navbar 上的控件同一套语言）。 */
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: 600;
  padding: 5px 10px;
  border-radius: var(--r-pill);
  cursor: pointer;
  font-family: inherit;
}
.home-search {
  flex: 1;
  height: 34px;
  /* ★ 2026-09-30：原为 rgba(255,255,255,.95) —— 在青绿头上是一块白胶囊，
     头部也变白之后就「没有边界」了 ⇒ 改浅灰底 + 一层极浅描边。
     ★★ 同日对比度复测（WCAG AA）：文字原是 `--text-3` (#98a1ad)，
        压在**新底** `--bg` (#f5f6f7) 上只有 **2.41:1** —— 比改底色前的
        2.55:1（白底）还低，是**这次换底色自己引入的退化**。
        而这行字是搜索框的**主提示文案**（不是次要说明），必须可读
        ⇒ 提到 `--text-2` (#5b6470) = **5.54:1 ✓**（图标是 currentColor，一起变清楚）。
        ⚠️ 全站其余 `--text-3` 次要文字仍是 2.6:1（既有口径，未动）——
           要整体提对比得先改 `--text-3` 本身，那会波及全站观感，属产品决策。 */
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 0 var(--sp-4);
  font-size: var(--fs-md);
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}
/* ★ 2026-10-13（第一百零一轮）：搜索框从头部搬下来，独占「银行上门团办」上面一行。
   ⚠️ 只有**外层**是新的；`.home-search` 本体一条没改（浅灰底 + 细描边 + `flex:1`），
      所以它在这个 flex 行里自动占满整行宽 —— 别再给它加 `width:100%`，
      两处都写就会变成「同一个宽度有两个来源」，改一处不生效另一处。
   ⚠️ 左右留白与下间距沿用 `.news-bar` 那一套（`0 var(--sp-4) var(--sp-3)`），
      让这一行与快讯条 / 信息流落在同一条左边线上。 */
.home-search-row {
  display: flex;
  margin: 0 var(--sp-4) var(--sp-3);
}
/* 品牌区：置于头部最上面一行，左对齐
   2026-09-21 用户要求（最终版）：
     · 只保留「logo + 菊厂家园」——副标题「华为人美好生活服务商」已全站删除
     · 这一行整体**放在头部最上面**（城市选择 / 搜索框挪到它下面）
     · 整行贴左：logo 在标题左侧，logo 左缘距品牌区左缘 ≤2px
   历史：曾把标题放最左、logo 夹在标题与副标题之间；
         副标题删掉后 logo 顺势回到标题左侧（品牌标准「标 + 字」组合）。
-------------------------------------------------------- */
.home-brand {
  padding: 0 0 var(--sp-3);
  text-align: left;
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 8px;                    /* logo 与标题之间统一 8px */
}
.home-brand .hb-title { flex: 0 0 auto; }
.home-brand .logo-mark { flex: 0 0 auto; }
/* logo 标记：轻微投影让它「浮」起来。
   ⚠️ 这句原话是「在青绿渐变底上浮起来」—— 首页头部 2026-09-30 改成白底后，
      那处引用没了；投影本身在白底上（关于页 `.logo-lockup`）依然成立，保留。 */
.logo-mark {
  display: block;
  flex: 0 0 auto;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .16));
}
.home-brand .hb-title {
  font-size: var(--fs-3xl);
  font-weight: 700;
  letter-spacing: 1px;
  line-height: 1.15;
}
/* 通用「标 + 字」组合（页脚等处也用） */
.logo-lockup { display: inline-flex; align-items: baseline; gap: 9px; }
.logo-lockup .logo-mark { align-self: center; }
.ll-text { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: 1px; }

/* ★ 2026-09-29：`.main-cta`（主 CTA 卡）的规则整段删除 —— 那块 UI 已按用户口径
   从首页拿掉（「30 秒算清你能贷多少 这个板块删除吧…不如多展示几个 银行上门
   团办预约报名的信息」），位置换成 `.group-list`（复用信贷页那份团办紧凑行）。
   留着规则不报错，只会让下一次读这份「唯一真相源」的人以为页面上还有一张卡。
   （历史：它曾用 `-32px` 负边距骑在头部上，第六十一轮·续改成 0 ——
     那段教训在 `.home-header` 的注释里，与这里无关，不要一起删掉。
     头部本体在 2026-09-30 由青绿渐变改成了白底，但「不许卡片压头部」这条不变。） */

/* 快讯条 */
.news-bar {
  margin: 0 var(--sp-4) var(--sp-3);
  background: #fff;
  border-radius: var(--r-md);
  padding: 9px var(--sp-3);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.news-bar .nb-label {
  font-size: var(--fs-xs);
  color: var(--brand);
  font-weight: 700;
  flex: none;
  display: flex;
  align-items: center;
  gap: 3px;
}
.news-track {
  flex: 1;
  min-width: 0;
  position: relative;
  height: 20px;
  overflow: hidden;
}
.news-item {
  font-size: var(--fs-sm);
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 20px;
}

/* 功能宫格 */
.grid-nav {
  display: grid;
  /* 列数由 home.js 内联覆盖（条目数决定）—— 这里只是兜底：
     写死列数时删掉一项会留一个空列，把图标挤到左边。 */
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-2) 0;
  margin: 0 var(--sp-4) var(--sp-3);
  background: #fff;
  border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-2);
  box-shadow: var(--sh-1);
}
.grid-nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border: none;
  background: none;
  cursor: pointer;
  font-size: var(--fs-xs);
  color: var(--text-2);
  font-family: inherit;
  padding: 0;
}
.grid-nav-icon {
  width: 40px; height: 40px;
  border-radius: var(--r-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 19px;
  background: var(--brand-light);
}

/* banner 轮播 */
/* banner 轮播
   2026-09-21：高度由 132px 提到 168px，并加上下方渐隐遮罩，
   让幻灯片底部自然过渡到页面背景（配合首页「banner 下移」的排版调整）。 */
.banner {
  margin: 0 var(--sp-4) var(--sp-3);
  border-radius: var(--r-lg);
  overflow: hidden;
  position: relative;
  height: 168px;
  /* 底部渐隐：图片下缘不做硬切，柔化到卡片圆角内 */
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, rgba(0, 0, 0, .86) 100%);
  mask-image: linear-gradient(to bottom, #000 78%, rgba(0, 0, 0, .86) 100%);
}
/* 底部柔化层：在幻灯片内容之下压一层同色渐变，降低下缘对比度 */
.banner::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 46px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, .16));
  z-index: 2;
}
.banner-track { display: flex; height: 100%; transition: transform .35s ease; }
.banner-slide {
  min-width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 var(--sp-5) 14px;
  color: #fff;
}
.banner-slide .bs-tag {
  font-size: var(--fs-xs);
  background: rgba(255, 255, 255, .25);
  padding: 2px 8px;
  border-radius: var(--r-pill);
  align-self: flex-start;
  margin-bottom: 6px;
}
.banner-slide .bs-title { font-size: var(--fs-xl); font-weight: 700; line-height: 1.35; }
.banner-slide .bs-sub { font-size: var(--fs-sm); opacity: .92; margin-top: 3px; }
.banner-dots {
  position: absolute;
  bottom: 12px; left: 50%;
  transform: translateX(-50%);
  display: flex; gap: 4px;
  z-index: 3;
}
.banner-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .5);
  transition: all .25s;
}
.banner-dot.active { width: 14px; border-radius: var(--r-pill); background: #fff; }

/* 双轨混排流 */
.feed-tabs {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 10;
}
.feed-tab {
  font-size: var(--fs-lg);
  color: var(--text-3);
  border: none;
  background: none;
  cursor: pointer;
  font-family: inherit;
  padding: 0 0 6px;
  position: relative;
  font-weight: 500;
}
.feed-tab.active { color: var(--text); font-weight: 700; font-size: var(--fs-xl); }
.feed-tab.active::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;
  width: 18px; height: 3px;
  border-radius: var(--r-pill);
  background: var(--brand);
}

/* 文章卡片 */
.article-card {
  background: #fff;
  padding: var(--sp-4);
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  display: flex;
  gap: var(--sp-3);
}
.article-card:active { background: #fafbfc; }
.article-main { flex: 1; min-width: 0; }
.article-title {
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1.5;
  margin-bottom: 6px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.article-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--text-3);
  flex-wrap: wrap;
}
.article-thumb {
  width: 88px; height: 66px;
  border-radius: var(--r-sm);
  flex: none;
  background: linear-gradient(135deg, #f0f2f5, #e6e9ed);
  display: flex; align-items: center; justify-content: center;
  color: #b9c0c9; font-size: 20px;
  /* ★ 第七十八轮：配图与「原文」角标。
     overflow 裁掉图片的直角（否则会把圆角盖成方角）；
     position: relative 是给角标做定位锚点 —— 两条都不能少。 */
  overflow: hidden;
  position: relative;
}
/* 后台按文章链接抓来的封面（第七十八轮）。
   与 .house-thumb img 同一句，别写两套。 */
.article-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* 「这篇有公众号原文链接」角标。做成**状态可视化**而不是装饰：
   运营在兜底样例（没有 url）上点半天，看见这个标记就知道该去后台补链接。 */
.article-src-badge {
  position: absolute; right: 0; bottom: 0;
  padding: 1px 5px;
  font-size: 10px; line-height: 1.5;
  color: #fff;
  background: rgba(0, 0, 0, .55);
  border-top-left-radius: var(--r-sm);
}

/* ============================================================
   信贷模块专用
   ============================================================ */
.filter-bar {
  display: flex;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: #fff;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
  position: sticky;
  top: 0;
  z-index: 10;
}
.filter-bar::-webkit-scrollbar { display: none; }
.filter-chip {
  padding: 6px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  background: #fff;
  font-size: var(--fs-md);
  cursor: pointer;
  white-space: nowrap;
  font-family: inherit;
  color: var(--text-2);
  transition: all .15s;
  flex: none;
}
.filter-chip.active {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  font-weight: 600;
}

/* 产品卡 */
.product-card {
  background: #fff;
  border-radius: var(--r-lg);
  margin: 0 var(--sp-4) var(--sp-3);
  padding: var(--sp-4);
  box-shadow: var(--sh-1);
  cursor: pointer;
}
.product-card:active { background: #fafbfc; }
.product-top { display: flex; align-items: flex-start; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.bank-logo {
  width: 42px; height: 42px;
  border-radius: var(--r-sm);
  flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-md);
  font-weight: 700;
  color: #fff;
}
/* ★ 银行商标（2026-10-10 用户口径：「信贷只需要上传一张银行的商标照片就行，
   以跟不同银行的产品做区分」）。摆在产品卡**右侧**，用来看图识行。
   为什么是白底 + 细边框 + 内边距：
     · 银行 logo 有透明底的、也有深色底的，直接贴在白卡上会糊成一块；
       `.bank-logo` 那套色块是**我们自己画的字标**，两者不能共用一套皮肤。
   为什么是 contain 不是 cover：
     · logo 有方形徽标（如工行）也有长条字标（如「XX 银行」横排），
       cover 会把长条裁得只剩中间两个字 —— 那就彻底失去「看图识行」的意义。
   ⚠️ 尺寸刻意比 .bank-logo（42px）扁一点：它是**辅助识别**，不是主视觉。 */
.product-logo {
  width: 48px;
  height: 32px;
  flex: none;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: #fff;
  padding: 3px;
  object-fit: contain;
}
.product-name { font-size: var(--fs-lg); font-weight: 700; line-height: 1.4; }
.product-rate-row {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-top: 1px dashed var(--line);
}
.product-rate-item { flex: 1; }
.product-rate-num {
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--brand);
  line-height: 1.3;
}
.product-rate-num .u { font-size: var(--fs-xs); font-weight: 500; }
.product-rate-label { font-size: var(--fs-xs); color: var(--text-3); }

/* 工具宫格（2026-09-21 紧凑版：小图标 + 标题同行，整体高度砍掉一半）
   改版前：38px 图标**独占一行** + 15px 标题 + 16px 内边距 → 每卡约 130px 高，
   找房 5 张卡占 3 行 ≈ 430px，把下面的内容全推出首屏。
   改版后：图标缩到 22px 且**与标题同一行**，内边距 11px，标题 14px → 每卡约 66px。
   卡片结构： [小图标] 标题 (新增) ／ 副标题（最多 2 行）
   ⚠️ 图标与标题必须在 .tool-head 这个 flex 行里 —— 这行就是「缩小」的关键。
      若把 .tool-icon 改成 display:block，它会重新独占一行、高度立刻反弹。 */
.tool-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-2);
  padding: 0 var(--sp-4) var(--sp-3);
}
.tool-card {
  background: #fff;
  border-radius: var(--r-md);
  padding: 10px 11px;
  box-shadow: var(--sh-1);
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.tool-card:active { transform: scale(.985); }
/* 奇数个工具时最后一张跨两列 —— 2 列网格里留一张半宽的孤立卡很难看。
   找房「房产工具」是 5 个，正好走这条。 */
.tool-grid.is-odd > .tool-card:last-child { grid-column: 1 / -1; }
.tool-head { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
.tool-icon {
  width: 22px; height: 22px; flex: none;
  border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; line-height: 1;
}
.tool-name {
  font-size: var(--fs-base); font-weight: 700;
  line-height: 1.25;
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tool-desc {
  font-size: var(--fs-xs); color: var(--text-3); line-height: 1.4;
  /* 最多 2 行：「查积分」那句里的「多源核验」是卖点，截成 1 行会把它切掉 */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* 计算结果 */
.result-hero {
  text-align: center;
  padding: var(--sp-5) var(--sp-4);
  background: linear-gradient(160deg, #fff6f1, #fff);
}
.result-num {
  font-size: 42px;
  font-weight: 700;
  color: var(--brand);
  line-height: 1.1;
  letter-spacing: -1px;
}
.result-num .u { font-size: var(--fs-lg); font-weight: 600; }
.result-label { font-size: var(--fs-md); color: var(--text-2); margin-top: 6px; }

/* 团办场次卡 */
.group-card {
  background: #fff;
  border-radius: var(--r-lg);
  /* 底部留白与其它卡片一致，但卡片内部的按钮行需要额外呼吸位：
     否则在页面最底部时，卡片的主按钮会被 .float-cta / .tabbar 压住 */
  margin: 0 var(--sp-4) var(--sp-3);
  box-shadow: var(--sh-1);
  overflow: hidden;
  border: 1px solid transparent;
  transition: border-color .2s;
}
.group-card-body { padding: var(--sp-4); padding-bottom: calc(var(--sp-4) + 6px); }
.group-card.urgent {
  border-color: var(--brand);
  box-shadow: 0 4px 18px rgba(245, 110, 32, .20);
}
.group-card-head {
  padding: var(--sp-4);
  background: linear-gradient(135deg, #fff8f4, #fff);
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}
.group-card-body { padding: 0 var(--sp-4) var(--sp-4); }
/* 「我已预约本场团办」标记条 —— 产品卡预约与官方场次合并去重后，
   必须仍能看出用户自己已经预约过（2026-09-21 走查补） */
.my-intent-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: var(--sp-3);
  padding: 8px 12px;
  border-radius: var(--r-md);
  background: var(--brand-light);
  color: var(--brand-dark);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.my-intent-bar svg { flex: none; color: var(--brand); }

/* ============================================================
   团办紧凑行（信贷页「银行上门团办」列表用）
   2026-09-21 用户口径：「每一家银行占用的地方有点大，需要精简一下，能展示更多银行」。
   完整决策卡 .group-card 留给「服务 → 全部场次」；这里是一家银行一行：
   logo + 银行名 + 状态 + 截止 + 产品名 + 进度 + 报名，整行约 62px，
   一屏 6~7 家。整行可点（打开详情抽屉），右侧按钮只是视觉提示。
============================================================ */
.group-list { margin: 0 0 var(--sp-2); }
.group-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 var(--sp-4) 8px;
  padding: 9px var(--sp-3);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  cursor: pointer;
  transition: .15s;
}
.group-row:active { background: #fafbfb; }
/* 紧急场次只做「边框偏暖」的弱提示 —— 紧凑行本来就很挤，再加整块底色会吵 */
.group-row.is-urgent { border-color: #f6c9a8; }
.gr-logo { width: 30px; height: 30px; font-size: 12px; flex: 0 0 auto; }
.gr-main { flex: 1; min-width: 0; }
.gr-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.gr-bank {
  font-weight: 700; font-size: var(--fs-sm); color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gr-line-2 { display: flex; align-items: baseline; gap: 8px; margin-top: 3px; }
.gr-prod {
  font-size: var(--fs-xs); color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gr-remain { font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; margin-left: auto; }
.gr-remain b { font-weight: 700; }
.gr-days {
  flex: 0 0 auto; font-size: 10px; line-height: 1.6;
  color: #c47a10; background: #fdf6e6; border-radius: var(--r-pill);
  padding: 0 6px; white-space: nowrap;
}
.gr-days.is-urgent { color: #c0273a; background: #fdecee; }
.gr-bar { margin-top: 5px; height: 4px; }
.gr-btn { flex: 0 0 auto; pointer-events: none; }
.countdown {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--action);
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: 700;
  padding: 3px 8px;
  border-radius: var(--r-sm);
  font-variant-numeric: tabular-nums;
}
.seat-bar {
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--line);
  overflow: hidden;
  margin-top: 6px;
}
.seat-bar > i {
  display: block;
  height: 100%;
  background: var(--brand);
  border-radius: var(--r-pill);
}

/* ============================================================
   服务 Tab 专用
   ============================================================ */
.advisor-card {
  background: linear-gradient(150deg, #0bc0b0 0%, var(--brand) 60%, #048c81 100%);
  border-radius: var(--r-xl);
  margin: 0 var(--sp-4) var(--sp-3);
  padding: var(--sp-4);
  color: #fff;
  box-shadow: 0 8px 24px rgba(7, 179, 164, .28);
  position: relative;
  overflow: hidden;
}
.advisor-card::after {
  content: '';
  position: absolute;
  right: -40px; top: -40px;
  width: 140px; height: 140px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .1);
}
.advisor-top { display: flex; gap: var(--sp-3); align-items: center; position: relative; z-index: 1; }
.advisor-avatar {
  width: 58px; height: 58px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .25);
  border: 2px solid rgba(255, 255, 255, .7);
  display: flex; align-items: center; justify-content: center;
  font-size: 22px;
  font-weight: 700;
  flex: none;
}
.advisor-name { font-size: var(--fs-xl); font-weight: 700; display: flex; align-items: center; gap: 6px; }
.advisor-sub { font-size: var(--fs-sm); opacity: .92; margin-top: 2px; }
.advisor-stats {
  display: flex;
  gap: 0;
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid rgba(255, 255, 255, .25);
  position: relative;
  z-index: 1;
}
.advisor-stat { flex: 1; text-align: center; }
.advisor-stat .n { font-size: var(--fs-lg); font-weight: 700; }
.advisor-stat .l { font-size: 10px; opacity: .85; margin-top: 1px; }
.advisor-actions {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
  position: relative;
  z-index: 1;
}
.advisor-actions .btn { flex: 1; }
.advisor-actions .btn-w { background: #fff; color: var(--action); font-weight: 700; }
.advisor-actions .btn-o { background: rgba(255, 255, 255, .18); color: #fff; border: 1px solid rgba(255, 255, 255, .45); }

/* ------------------------------------------------------------
   顾问「配对块」（第一百零九轮 · 用户反馈 P025）
   ------------------------------------------------------------
   用户原话：「小程序顾问部分前台显示不合理，顾问集中放一起，联系方式集中放一起，
   无法精准锁定顾问」。
   形态：一位顾问 = 一块 —— 上半名片（白底），下半**紧挨着**他的联系方式
   （浅底 + 上虚线），末尾一颗属于他的「设为 / 换成本城专属顾问」。
   ⚠️ 浅底 + 虚线，而不是给联系方式再套一张白卡：白卡套白卡在真机上会读成
      「两块不相干的内容」，正好回到用户抱怨的那个观感。
   ⚠️ `.advisor-avatar-sm` 是给浅底名片用的 44px 头像 —— 深色 `.advisor-avatar`
      那套（58px + 半透明白边）是为青绿渐变的名片设计的，放到白底上会糊成一片。
   ------------------------------------------------------------ */
.advisor-pick {
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: #fff;
  overflow: hidden;
}
.advisor-pick + .advisor-pick { margin-top: var(--sp-3); }
.advisor-pick-hd { padding: var(--sp-3); }
.advisor-pick-ct {
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  background: var(--bg);
  border-top: 1px dashed var(--line-2);
}
.advisor-avatar-sm {
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--brand-light);
  color: var(--brand);
  border: 1px solid var(--brand-soft);
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 700;
  flex: none;
}
/* 浅底里的联系方式条：去掉自身的白卡描边，只留行间细线（见上面第一条 ⚠️） */
.contact-list.is-tight .contact-row {
  border: none;
  border-radius: 0;
  background: transparent;
  padding: 8px 0;
}
.contact-list.is-tight .contact-row + .contact-row { border-top: 1px solid var(--line-2); }

/* 华友卡 */
.member-card {
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  margin: 0 var(--sp-4) var(--sp-3);
  color: #fff;
  position: relative;
  overflow: hidden;
}
/* 两态配色（2026-09-29 取消等级：原 lv0 / lv1 / lv2 / lv3 四档渐变整体撤掉）。
   ⚠️ 类名跟着 DB.MEMBER_CARD.states 的 key 走（guest / member）——
      再出现 lv0~lv3 这四个类就是残留，不是设计。
   未注册用中性灰蓝（「还没开通」），已注册用品牌青绿（「已开通」，与全站主色一致）。 */
.member-card.guest  { background: linear-gradient(135deg, #8d99ae, #6b7687); }
.member-card.member { background: linear-gradient(135deg, var(--brand), #0a8f85); }
.member-card::after {
  content: '';
  position: absolute;
  right: -30px; bottom: -50px;
  width: 150px; height: 150px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .1);
}
.member-lv { font-size: var(--fs-xl); font-weight: 700; display: flex; align-items: center; gap: 7px; }
.member-desc { font-size: var(--fs-sm); opacity: .92; margin-top: 3px; }
.member-progress-wrap { margin-top: var(--sp-4); position: relative; z-index: 1; }
.member-progress-bar {
  height: 6px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .28);
  overflow: hidden;
}
.member-progress-bar > i {
  display: block; height: 100%;
  background: #fff;
  border-radius: var(--r-pill);
}
.member-next { font-size: var(--fs-sm); margin-top: 7px; opacity: .95; }

/* 服务项 */
.service-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.service-item:last-child { border-bottom: none; }
.service-item:active { background: #fafbfc; }
.service-icon {
  width: 42px; height: 42px;
  border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 19px;
  flex: none;
}

/* 资质/来源标注 */
.src-note {
  font-size: var(--fs-xs);
  color: var(--text-3);
  padding: var(--sp-3) var(--sp-4);
  line-height: 1.6;
}

/* ============================================================
   置业测算器（找房模块强化 · 问题 #4）
   ============================================================ */
.calc-entry {
  margin: var(--sp-4) var(--sp-4) var(--sp-3);
  padding: var(--sp-4);
  border-radius: var(--r-lg);
  background: linear-gradient(140deg, #0bc0b0 0%, var(--brand) 62%, #048c81 100%);
  color: #fff;
  box-shadow: 0 8px 22px rgba(7, 179, 164, .26);
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.calc-entry::after {
  content: '';
  position: absolute;
  right: -36px; top: -46px;
  width: 140px; height: 140px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
}
.calc-entry:active { transform: scale(.99); }
.ce-top { display: flex; align-items: center; gap: var(--sp-3); position: relative; z-index: 1; }
.ce-icon {
  width: 40px; height: 40px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .22);
  display: flex; align-items: center; justify-content: center;
  font-size: 20px;
  flex: none;
}
.ce-title { font-size: var(--fs-xl); font-weight: 700; }
.ce-sub { font-size: var(--fs-xs); opacity: .92; margin-top: 3px; line-height: 1.5; }
.ce-points {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: var(--sp-3);
  font-size: var(--fs-xs);
  opacity: .95;
  position: relative;
  z-index: 1;
}
.ce-btn {
  margin-top: var(--sp-3);
  background: #fff;
  color: var(--action);
  font-weight: 700;
  font-size: var(--fs-md);
  text-align: center;
  padding: 9px;
  border-radius: var(--r-sm);
  position: relative;
  z-index: 1;
}

/* 表单底部实时预览条 */
.calc-live {
  position: sticky;
  bottom: 0;
  z-index: 40;
  margin: 0 var(--sp-4) var(--sp-4);
  padding: 11px var(--sp-4);
  border-radius: var(--r-md);
  background: var(--brand-light);
  border: 1px solid var(--brand-soft);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
}
.calc-live .cl-label { font-size: var(--fs-xs); color: var(--text-3); }
.calc-live .cl-num { font-size: var(--fs-lg); font-weight: 700; color: var(--brand); }
.calc-live .cl-sub { font-size: var(--fs-xs); color: var(--text-2); }

/* 推荐板块 */
.reco-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px dashed var(--line);
}
.reco-item:last-of-type { border-bottom: none; }
.reco-rank {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  flex: none;
  margin-top: 1px;
}

/* ============================================================
   联系方式条（问题 #5：直接给联系方式，不必先留资）
   ============================================================ */
.contact-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.contact-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 10px var(--sp-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: #fff;
}
.contact-icon {
  width: 32px; height: 32px;
  border-radius: var(--r-sm);
  background: var(--brand-light);
  color: var(--brand);
  display: flex; align-items: center; justify-content: center;
  font-size: 15px;
  flex: none;
}
.contact-value {
  font-size: var(--fs-base);
  font-weight: 700;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: .3px;
  word-break: break-all;
}

/* 我的团办预约行 */
.intent-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px dashed var(--line);
}
.intent-row:last-child { border-bottom: none; }

/* ============================================================
   我的
   ============================================================ */
.profile-head {
  background: linear-gradient(160deg, var(--brand) 0%, #2ec4b6 100%);
  /* 上下同为 `--sp-5`（20px）。第六十一轮·续：这里**不再**为负边距留额外底部留白，
     因为 `.archive-card` 已经不压边了（见其下方注释），20px 就是正常的段落间距。 */
  padding: var(--sp-5) var(--sp-4) var(--sp-5);
  color: #fff;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}
.profile-avatar {
  width: 60px; height: 60px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .25);
  border: 2px solid rgba(255, 255, 255, .6);
  display: flex; align-items: center; justify-content: center;
  font-size: 24px;
  font-weight: 700;
  flex: none;
}
.profile-name { font-size: var(--fs-2xl); font-weight: 700; }
.profile-sub { font-size: var(--fs-sm); opacity: .9; margin-top: 2px; }

.archive-card {
  /* ★ `-24px` → `0`（第六十一轮·续）。与首页 `.main-cta` 同一件事：
     用户点名「我的融资档案」这张卡与上面的头部「框框有重叠」。
     去掉负边距后卡片落在头部下沿，两边不再互相压。
     （这块头部仍是青绿渐变 `.profile-head`；2026-09-30 改白底的是**首页**头部。） */
  margin: 0 var(--sp-4) var(--sp-3);
  background: #fff;
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  box-shadow: var(--sh-2);
  position: relative;
  z-index: 2;
}
.archive-ring {
  width: 52px; height: 52px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex: none;
  position: relative;
}

/* ============================================================
   运营/演示辅助面板
   ============================================================ */
.demo-bar {
  position: fixed;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  margin-left: calc(var(--page-max) / 2 + 8px);
  z-index: 500;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}
@media (max-width: 1000px) {
  /* ★ 窄屏（含手机）把演示面板挪到**左边缘、垂直居中**（第六十一轮）。
     原来贴右上角 `top: 8px; right: 8px`，正好压在导航栏那一行上 ——
     实测「📊 转化漏斗」按钮与 `.navbar` 矩形 100% 重合，
     在手机上看着就是「黑框框盖住了页面标题」，属于最容易被当成 bug 的一类重叠。
     挪到左边缘中部后：避开导航栏（顶部）、TabBar（底部）、
     `.float-cta`（底部悬浮条）与 `.ai-fab`（右下球），只压内容中部的左边缘 —— 
     对一块内部评审用的调试面板来说这是可接受的代价。 */
  .demo-bar {
    left: 0;
    right: auto;
    top: 50%;
    bottom: auto;
    transform: translateY(-50%);
    margin-left: 0;
    align-items: flex-start;
  }
  /* ★ 再收一层：**只留图标，收成 ~34px 的小圆片**（同样是第六十一轮）。
     上一版把「📊 转化漏斗」整颗胶囊贴到左边缘 —— 位置躲开了导航栏，
     但胶囊宽 ~110px，在 320~430px 的屏上横跨正文左半边，
     实测压住「设置」标题、房源卡的价格行、顾问卡的「8 年经验」行。
     收成图标后宽度 ≈34px，只擦到卡片外边距，不再盖住任何文字。
     功能不变：点它照样展开埋点看板 / 重置数据。 */
  .demo-bar > #demo-toggle {
    width: 34px; height: 34px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 0;                       /* 藏文字，只留下面 ::before 的图标 */
    border-radius: 0 var(--r-pill) var(--r-pill) 0;
  }
  .demo-bar > #demo-toggle::before {
    content: '📊';
    font-size: 15px;
    line-height: 1;
  }
}
.demo-btn {
  background: rgba(28, 32, 38, .86);
  color: #fff;
  border: none;
  border-radius: var(--r-pill);
  padding: 7px 12px;
  font-size: var(--fs-xs);
  cursor: pointer;
  font-family: inherit;
  backdrop-filter: blur(8px);
  white-space: nowrap;
}
.demo-btn:active { opacity: .8; }
.demo-panel {
  background: #fff;
  border-radius: var(--r-md);
  box-shadow: var(--sh-3);
  padding: var(--sp-3);
  width: 216px;
  font-size: var(--fs-xs);
}
.demo-panel h4 { font-size: var(--fs-sm); margin-bottom: var(--sp-2); }
.demo-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding: 3px 0; }
.demo-row .v { font-weight: 700; color: var(--brand); font-variant-numeric: tabular-nums; }


/* ============================================================
   .sf-* 正文块版式（版本信息 / 免责声明 / 风险提示 / 联系我们 / 版权行）
   2026-09-21 页脚整条撤掉（用户：「页尾最下面那行菊厂家园整个删除」）后，
   这些块**只**服务于「我的 → 关于家园小程序」页（.about-legal）。
   所以这里的样式已全局化：不再依赖 .site-footer 作用域，页面级边距见 .about-legal。
   · 基调比正文弱（小字 + 弱色），避免抢占内容注意力
   · 风险提示用暖色左侧竖条标记，与普通免责声明区分开
   · .sf-brand / .sf-slogan / .site-footer 已删除，别再按老写法去挂它们
============================================================ */
/* .sf-slogan 已废弃：slogan「华为人美好生活服务商」按用户要求全站删除（2026-09-21） */
.sf-block { margin-top: var(--sp-4); }
.sf-h {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text-2);
  margin-bottom: var(--sp-2);
  padding-left: 8px;
  border-left: 3px solid var(--brand);
  line-height: 1.2;
}
.sf-h-warn {
  color: #b25000;
  border-left-color: #ff9500;
}
.sf-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 3px 0;
}
.sf-row span { flex: 0 0 auto; color: var(--text-3); }
.sf-row b {
  flex: 1 1 auto;
  text-align: right;
  font-weight: 600;
  color: var(--text-2);
  word-break: break-all;
}
.sf-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.sf-list li {
  position: relative;
  padding-left: 14px;
  margin-bottom: 6px;
}
.sf-list li::before {
  content: '·';
  position: absolute;
  left: 4px;
  color: var(--text-3);
  font-weight: 700;
}
.sf-list li b { color: var(--text-2); font-weight: 600; }
.sf-list-warn li::before { content: '!'; color: #ff9500; font-size: 10px; }
/* 精简后的免责 / 风险：一段话，不用列表（2026-09-21 用户要求压缩篇幅） */
.sf-note {
  margin: 0;
  font-size: 11px;
  line-height: 1.7;
  color: var(--text-3);
}
.sf-note b { color: var(--text-2); font-weight: 600; }
.sf-note-warn {
  padding-left: 9px;
  border-left: 2px solid rgba(255, 149, 0, .55);
}
.sf-bottom {
  margin-top: var(--sp-5);
  padding-top: var(--sp-3);
  border-top: 1px dashed var(--line);
  text-align: center;
  color: var(--text-3);
  font-size: 10px;
  line-height: 1.7;
}


/* ============================================================
   查房价工具（price）
   ------------------------------------------------------------
   结果页信息密度高：主值 → 置信度 → 挂盘 → 成交 → 走势 → 因子 → 源
   所以统一用「卡片 + 分节」节奏，避免堆成一大坨数字。
   涨跌配色沿用中式观感：涨=红、跌=绿（--price-up / --price-down）。
   ============================================================ */

/* 工具卡上的「新增」角标 */
/* 「新增」徽标：跟在工具卡标题**后面**的行内小标签（属于 .tool-head 那一行）。
   ⚠️ 原来是 absolute 定位在卡片右上角 —— 紧凑版卡片变矮后它会压到标题上，
      所以改成行内项。改回 absolute 前先看 .tool-head 那节的注释。 */
.tool-tag {
  flex: none;
  font-size: 9px; font-weight: 700;
  padding: 1px 5px; border-radius: 999px;
  background: var(--brand); color: #fff;
  letter-spacing: .5px;
  line-height: 1.4;
}

/* ---- 输入表单：小区/面积 快捷填充 ---- */
.suggest-row {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 8px;
}
.suggest-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-xs); color: var(--text-2);
  padding: 4px 9px; border-radius: 999px;
  background: #f4f6f8; border: 1px solid transparent;
  cursor: pointer; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.suggest-chip:active { background: var(--brand-light); }
.suggest-chip em {
  font-style: normal; color: var(--text-3); font-size: 10px;
}

/* 数据源 pill（表单页说明区 + 结果页交叉比对） */
.src-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-xs); color: var(--text-2);
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--line-2); background: #fff;
}
.src-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
}
.src-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px 0; border-bottom: 1px dashed var(--line);
}
.src-row:last-of-type { border-bottom: none; }
.src-dot-lg {
  width: 30px; height: 30px; border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: var(--fs-md); font-weight: 700;
  flex: 0 0 auto;
}

/* ---- 结果页：参考价下方的单价区间带 ---- */
.price-band {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 10px; padding: 5px 14px;
  border-radius: 999px; background: rgba(7, 179, 164, .10);
  font-size: var(--fs-md); font-weight: 600; color: var(--brand-dark);
  letter-spacing: .2px;
}
.price-band i { font-style: normal; opacity: .45; font-weight: 400; }

/* ---- 置信度 ---- */
.conf-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-4) var(--sp-2);
}
.conf-badge {
  flex: 0 0 auto;
  font-size: var(--fs-xs); font-weight: 700;
  padding: 4px 10px; border-radius: 999px;
  background: #e7f7ee; color: #0f7a45;
  border: 1px solid rgba(15, 122, 69, .16);
}
.conf-badge[data-lv="中等"] { background: #fff7e6; color: #a66300; border-color: rgba(166, 99, 0, .18); }
.conf-badge[data-lv="参考"] { background: #fdeceb; color: #b3261e; border-color: rgba(179, 38, 30, .18); }

/* ---- 挂盘价区间条 ---- */
.range-bar { padding: 4px 0 0; }
.range-track {
  position: relative; height: 8px; border-radius: 999px;
  background: linear-gradient(90deg, #eef3f8, #e3ecf5);
}
.range-fill {
  position: absolute; top: 0; bottom: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, #9fe6dd, var(--brand));
  opacity: .85;
}
.range-mark {
  position: absolute; top: -6px; bottom: -6px; width: 2px;
  background: var(--brand-dark); border-radius: 2px;
}
.range-mark span {
  position: absolute; top: -17px; left: 50%; transform: translateX(-50%);
  font-size: 10px; color: var(--brand-dark); font-weight: 600; white-space: nowrap;
}
.range-labels {
  display: flex; justify-content: space-between;
  margin-top: 10px; font-size: var(--fs-sm); color: var(--text-2);
}

/* ---- 走势折线图 ---- */
.trend-svg {
  width: 100%; height: 96px; display: block;
  overflow: visible;
}
.tag-up { background: #fdeceb; color: #c0392b; }
.tag-down { background: #e7f7ee; color: #0f7a45; }
.kv-v.up { color: #c0392b; font-weight: 600; }
.kv-v.down { color: #0f7a45; font-weight: 600; }

/* ---- 已排除数据源 ---- */
.blocked-box {
  margin-top: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: #fbfbfc;
  border: 1px dashed var(--line-2);
}
.blocked-row {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: 3px 0;
}
.blocked-name {
  flex: 0 0 auto; font-size: var(--fs-xs); font-weight: 600;
  color: var(--text-2);
  text-decoration: line-through;
  text-decoration-color: rgba(0, 0, 0, .28);
}

/* ---- 花瓣色分类标签（取自 logo 六瓣花，用于「类型」而非「状态」）---- */
.tag-petal-orange { background: #ffeadf; color: #b8460a; }
.tag-petal-yellow { background: #fdf6da; color: #8a6a00; }
.tag-petal-green  { background: #edf7dc; color: #4d6b09; }
.tag-petal-teal   { background: #dff5f2; color: #056d64; }
.tag-petal-rose   { background: #fde8ec; color: #ab1731; }
.tag-petal-purple { background: #f5e6f3; color: #6d3163; }

/* ============================================================
   本地知识库：学区（查学区）/ 成交价（查成交价）
   ------------------------------------------------------------
   2026-09-21 新增。两个工具的结果页共用一套「知识库元信息」呈现，
   所以类名统一用 kb- 前缀 —— 数据源 / 更新频率 / 最近更新 / 下次更新
   必须与结果同屏可见，否则用户无从判断一条学区或成交价是不是过期的。
   ============================================================ */

/* ---- 知识库标识 ---- */
.kb-badge {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: var(--r-pill);
  font-size: 10px; font-weight: 700;
  background: #fff; color: var(--brand);
  border: 1px solid rgba(7, 179, 164, .34);
}

/* ---- 知识库元信息行 ---- */
.kb-meta { display: flex; flex-direction: column; }
.kb-row {
  display: flex; align-items: baseline; gap: var(--sp-3);
  padding: 5px 0;
  border-bottom: 1px dashed rgba(7, 179, 164, .22);
  font-size: var(--fs-xs);
}
.kb-row:last-child { border-bottom: none; }
.kb-row span { flex: 0 0 54px; color: var(--text-3); }
.kb-row b { flex: 1; min-width: 0; font-weight: 600; color: var(--text); line-height: 1.55; }

/* ---- 对口学校行 ---- */
.school-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px 0;
  border-bottom: 1px dashed var(--line);
}
.school-row:last-of-type { border-bottom: none; }
.school-badge {
  flex: 0 0 auto;
  width: 34px; height: 34px; border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; color: #fff;
  background: var(--brand);
}
.school-badge[data-type="初中"] { background: #6d3163; }

/* ---- 学位锁定 ---- */
.lock-box {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
}
.lock-box.is-locked { background: #fff7e6; border: 1px solid rgba(180, 83, 9, .18); color: #8a4b06; }
.lock-box.is-free   { background: #ecfdf3; border: 1px solid rgba(21, 128, 61, .16); color: #14532d; }
.lock-icon { flex: 0 0 auto; font-size: 16px; line-height: 1.25; }

/* ---- 招生范围 · 对口小区（点一下直接看成交价）---- */
.comm-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.comm-chip {
  display: inline-flex; flex-direction: column; gap: 1px;
  padding: 5px 9px; border-radius: var(--r-md);
  background: #f7f8fa; border: 1px solid var(--line);
  font-size: var(--fs-xs); color: var(--text); line-height: 1.35;
}
.comm-chip:active { background: var(--brand-light); border-color: rgba(7, 179, 164, .42); }
.comm-chip em { font-style: normal; font-size: 9px; color: var(--text-3); }

/* ---- 同城各板块录取线排行 ---- */
.rank-row { display: flex; align-items: center; gap: var(--sp-3); padding: 6px 0; }
.rank-no {
  flex: 0 0 16px; text-align: center;
  font-size: 10px; font-weight: 700; color: var(--text-3);
}
.rank-row.is-cur .rank-no { color: var(--brand); }
.rank-row.is-cur .small { color: var(--brand-dark); }
.rank-track {
  margin-top: 4px; height: 4px; border-radius: var(--r-pill);
  background: #eef0f3; overflow: hidden;
}
.rank-track i { display: block; height: 100%; border-radius: var(--r-pill); background: #c9d3dd; }
.rank-row.is-cur .rank-track i { background: linear-gradient(90deg, #9fe6dd, var(--brand)); }

/* ---- 成交明细表 ----
   6 列在 390px 屏上会挤成一团，所以把「楼层」并进「签约」列做第二行，
   压到 5 列；单价/总价的单位放在表下方注里，避免表头被撑破。 */
.deal-table { border-radius: var(--r-md); overflow: hidden; }
.deal-head, .deal-row {
  display: grid;
  grid-template-columns: 52px 1fr 50px 44px 34px;
  gap: 4px;
  align-items: center;
}
.deal-head {
  padding: 7px 0;
  background: #f7f8fa;
  font-size: 9.5px; font-weight: 600; color: var(--text-3);
}
.deal-head span:nth-child(n+3) { text-align: right; }
.deal-row {
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-xs);
}
.deal-row:last-child { border-bottom: none; }
.dr-date, .dr-area { display: flex; flex-direction: column; line-height: 1.35; min-width: 0; }
.dr-date { color: var(--text-2); font-variant-numeric: tabular-nums; }
.dr-area { color: var(--text); font-variant-numeric: tabular-nums; }
.dr-date em, .dr-area em {
  font-style: normal; font-size: 9px; color: var(--text-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dr-unit, .dr-total { font-variant-numeric: tabular-nums; text-align: right; }
.dr-bargain { text-align: right; font-weight: 600; color: var(--action); }

/* ============================================================
   查积分（score）—— 积分知识库
   积分不是裸分数，是「类别 + 分数 + 来源 + 核验结论」四件套，
   所以需要一套能同时承载「数值 / 结论徽章 / 来源明细」的小组件。
   ============================================================ */

/* ---- 逐年「录取线 + 核验结论 + 来源明细」台账 ----
   三列网格 + 来源明细整行跨列：320px 窄屏下把来源塞进同一行会全部折断。 */
.score-table { display: flex; flex-direction: column; }
.score-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: 8px;
  padding: 9px 0;
  border-bottom: 1px dashed var(--line);
}
.score-row:last-child { border-bottom: none; }
.sc-year {
  font-size: var(--fs-sm); color: var(--text-2);
  display: inline-flex; align-items: baseline; gap: 5px;
  font-variant-numeric: tabular-nums;
}
.sc-year em {
  font-style: normal; font-size: 10px; font-weight: 600;
  color: var(--brand); background: var(--brand-light);
  border-radius: 4px; padding: 0 4px; line-height: 16px; white-space: nowrap;
}
.sc-line { font-size: var(--fs-base); font-weight: 700; font-variant-numeric: tabular-nums; }
.sc-src {
  grid-column: 1 / -1;
  font-size: 10px; color: var(--text-3); line-height: 1.6;
  margin-top: -2px;
}
/* 核验结论五档：颜色即语义（绿=已独立核验，蓝=小幅差异，橙=有分歧，
   灰=同源不算核验，黄=单源待补） */
.sc-vd, .vd-chip {
  font-size: 10px; font-weight: 600; line-height: 16px;
  padding: 0 6px; border-radius: 999px; white-space: nowrap;
}
.vd-chip { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; font-size: var(--fs-xs); }
.vd-chip b { font-weight: 700; font-variant-numeric: tabular-nums; }
.sc-vd[data-lv="exact"],  .vd-chip[data-lv="exact"]  { color: #0f7a3d; background: #e6f7ed; }
.sc-vd[data-lv="near"],   .vd-chip[data-lv="near"]   { color: #1d6fd4; background: #e8f1fd; }
.sc-vd[data-lv="doubt"],  .vd-chip[data-lv="doubt"]  { color: #b34a00; background: #fff3e6; }
.sc-vd[data-lv="same"],   .vd-chip[data-lv="same"]   { color: var(--text-2); background: #f1f3f5; }
.sc-vd[data-lv="single"], .vd-chip[data-lv="single"] { color: #8a6d00; background: #fdf6e0; }
.vd-row { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---- 该区积分规则：类别基础分 ----
   单位「分」由伪元素补，避免和数据值一起被当成可复制内容。 */
.rule-bases { display: flex; flex-direction: column; }
.rule-base {
  display: grid;
  grid-template-columns: 46px 30px 1fr;
  align-items: baseline; gap: 8px;
  padding: 7px 0;
  border-bottom: 1px dashed var(--line);
}
.rule-base:last-child { border-bottom: none; }
.rb-cls { font-size: var(--fs-sm); font-weight: 700; color: var(--brand); }
.rb-score { font-size: var(--fs-sm); font-weight: 700; font-variant-numeric: tabular-nums; }
.rb-score::after { content: '分'; font-size: 9px; font-weight: 400; color: var(--text-3); margin-left: 1px; }
.rb-desc { font-size: var(--fs-xs); color: var(--text-2); line-height: 1.55; }

/* ---- 分数反查三档 ---- */
.bk-list { display: flex; flex-direction: column; }
.bk-row {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 0;
  border-bottom: 1px dashed var(--line);
}
.bk-row:last-child { border-bottom: none; }
/* ⚠️ 这里的绿/红表示「够 / 不够」，不是涨跌，别和行情色混用 */
.bk-good { color: var(--success); font-weight: 600; font-variant-numeric: tabular-nums; }
.bk-bad  { color: var(--danger);  font-weight: 600; font-variant-numeric: tabular-nums; }

/* ---- 换区 chips：积分口径各区独立，必须能一键切区重算 ---- */
.dist-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.dist-chip {
  display: inline-flex; align-items: baseline; gap: 4px;
  font-size: var(--fs-sm); color: var(--text-2);
  background: var(--bg); border: 1px solid var(--line-2);
  border-radius: 999px; padding: 4px 10px;
  cursor: pointer; transition: .15s;
}
.dist-chip em { font-style: normal; font-size: 10px; color: var(--text-3); }
.dist-chip.is-on {
  color: var(--brand-dark); background: var(--brand-light);
  border-color: var(--brand-soft); font-weight: 600;
}
.dist-chip.is-on em { color: var(--brand); }

/* ============================================================
   「我的 → 关于家园小程序」页（2026-09-21 新增）
   页脚原来承载的 版本信息 / 免责声明 / 风险提示 / 联系我们 / 版权行
   全部搬到这一页。正文块仍用 .sf-* 版式（已全局化，不依赖 .site-footer 作用域），
   所以这里只需要补页面级的边距与那条虚线分隔。
------------------------------------------------------------ */
.about-legal { padding: 0 var(--sp-4) var(--sp-4); }
/* 页面里第一块上面不需要额外留白（上面已经有品牌卡） */
.about-legal > .sf-block:first-child { margin-top: 0; }

/* ============================================================
   置业测算器 · 三步向导 + 政策化结果页（2026-09-21 重做）
   ------------------------------------------------------------
   向导头 .calc-steps / 政策依据 .policy-item / 税费清单 .tax-list
   三组是本轮新增；.callout.ok / .callout.warn 是给「资格通过 / 不通过」用的两态。
------------------------------------------------------------ */

/* ---- 步骤指示条 ---- */
.calc-steps {
  display: flex;
  gap: 6px;
  margin: 0 var(--sp-4) var(--sp-3);
}
.cs-item {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border-radius: var(--r-md);
  background: #fff;
  box-shadow: var(--sh-1);
  border: 1px solid transparent;
}
.cs-item.on { border-color: var(--brand); background: var(--brand-light); }
.cs-item.done { background: #f6fbfa; }
.cs-num {
  flex: none;
  width: 18px; height: 18px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700;
  background: var(--line-2); color: var(--text-2);
}
.cs-item.on .cs-num { background: var(--brand); color: #fff; }
.cs-item.done .cs-num { background: var(--brand-soft); color: var(--brand-dark); }
.cs-name { font-size: var(--fs-xs); color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cs-item.on .cs-name { color: var(--brand-dark); font-weight: 700; }

/* ---- callout 两态（默认是品牌青绿，.warn 转橙） ---- */
.callout.ok { background: var(--brand-light); border-left-color: var(--brand); color: var(--brand-dark); }
.callout.warn { background: #fff5ef; border-left-color: var(--action); color: #9a4410; }

/* ---- 政策依据列表 ---- */
.policy-item {
  padding: var(--sp-3) 0;
  border-bottom: 1px dashed var(--line);
}
.policy-item:last-child { border-bottom: none; padding-bottom: 0; }

/* ---- 税费清单：名称 / 税率 / 金额 三列 ---- */
.tax-list { border-top: 1px dashed var(--line); }
.tax-head {
  display: flex;
  justify-content: space-between;
  padding: 8px 0 4px;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.tax-head > span:nth-child(2) { flex: 0 0 52px; text-align: right; }
.tax-head > span:nth-child(3) { flex: 0 0 74px; text-align: right; }
.tax-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: 7px 0 2px;
  font-size: var(--fs-md);
  border-top: 1px dashed var(--line);
}
.tax-row .tr-name { flex: 1; min-width: 0; }
.tax-row .tr-rate { flex: 0 0 52px; text-align: right; color: var(--text-2); font-size: var(--fs-sm); }
.tax-row .tr-amt { flex: 0 0 74px; text-align: right; font-weight: 700; }
.tax-row.is-opt .tr-name, .tax-row.is-opt .tr-amt { color: var(--text-2); font-weight: 600; }
.tr-note { padding: 0 0 6px; line-height: 1.5; }
/* ============================================================
   找房改版（2026-09-21）：法拍 + 新房双品类 / 片区筛选
   ------------------------------------------------------------
   · 「二级筛选条（片区 chips）」已在**第111轮随改下拉整体退场** ——
     那一族的全部规则（`.filter-bar.is-sub` / `.filter-chip.is-sm` /
     `.filter-chip.is-sm.active`）已删除，留档见下方原处注释。
     ⚠️ `.filter-bar` / `.filter-chip` **基础类保留**（品类条与信贷页场景条仍在用）。
   · 原来的 `.ckb-*`（小区知识库入口 / 知识库小区行 / 详情页档案条）在
     第六十六轮随整个前台一并删除，**不要复活**（已无任何渲染点引用）。
     历史坑：那一族当初统一用 `ckb-` 前缀，是因为 app.css 里 `.kb-*` 已被
     「学区知识库」占用（`.kb-row span { flex: 0 0 54px }`），复用会把 chip
     压成 54px 宽。
   ============================================================ */

/* ★ 第111轮（2026-10-14）删除整块「二级筛选条（片区）」样式：
     `.filter-bar.is-sub` / `.filter-chip.is-sm` / `.filter-chip.is-sm.active`。
   原因：这一族**唯一的渲染点**就是找房页那排 区域/价格/面积 chips，
   而它已在第111轮改成**原生下拉**（见下方 `.filter-row-v` / `.filter-select`）。
   ⚠️ 别按「看着还在用」把它们加回来。
   留档：`.filter-chip.is-sm.active { color:#fff }` 当年是修「二级条选中后字色仍是灰」
   （`.filter-chip.active` 与 `.filter-chip.is-sm` 都是 0,2,0 特异性、后定义者胜出）
   而写的一行显式覆盖，随这排 chips 一起退场。 */

/* ★★ 第一百零九轮（2026-10-14）：找房三维筛选（区域 / 价格 / 面积）。
   ★★ 第111轮（2026-10-14 用户反馈第 3 条）：三个维度由「一行 chips」改为**原生下拉** ——
      用户口径：「找房页面，区域、价格、面积的选项，都应该做成下拉框，不应该是平铺展示」。
   · 三层结构不变：`.filter-row`（背景 + 分隔线）/ `.filter-row-k`（小标题）/
     `.filter-row-v`（本行控件容器），所以外层一条都没动。
   · 控件是**原生 `<select>`**（`.filter-select`）：手机上是原生选择器、桌面上是原生下拉，
     「下拉框」这条一次满足，也不用再养一套浮层。
     **刻意不做 `appearance: none`** —— 关掉原生外观就得自己画箭头，而 `<select>`
     不支持伪元素，只能塞背景图 data URI；那个 URI 里的 px 会被 `port-styles.py` 的
     px→rpx 规则一并改写、把图形改坏，而小程序端**本来就不读这份样式**（它用 `<picker>`）。
     保留原生外观 = 箭头由平台给，两端都不会「少一个箭头」。
   ⚠️ 别把 `.filter-chip` 套到 `<select>` 上：那族带 pill 圆角与选中底色，与原生控件打架。
   ⚠️ 第111轮一并**删除**的规则：`.filter-bar.is-sub` / `.filter-chip.is-sm` /
     `.filter-chip.is-sm.active` / `.filter-chip.is-none(.active)` ——
     它们唯一的渲染点就是被换掉的那排 chips。
     ⚠️ 别因为「看着还在用」把它们加回来；但 **`.filter-chip` 基础类与 `.filter-bar`
        本体必须保留** —— 品类条（本页 / 信贷页场景条）仍在用（`.filter-bar` 的
        `position: sticky` 也只应在品类条上生效）。
     留档：`.filter-chip.is-sm.active { color:#fff }` 当年是为修「二级条选中后字色仍是灰」
     （特异性平手 + 定义顺序）而写的，随本排 chips 一起退场。 */
.filter-row {
  background: #fff;
  border-bottom: 1px solid var(--line);
}
.filter-row-k {
  padding: var(--sp-2) var(--sp-4) 0;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-3);
}
.filter-row-v {
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}
.filter-select {
  width: 100%;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: #fff;
  padding: 7px 10px;
  font-size: var(--fs-sm);
  font-family: inherit;
  color: var(--text);
  cursor: pointer;
}
.filter-select:focus { border-color: var(--brand); outline: none; }

/* ---------------- 三维筛选：一行三格（第一百一十二轮） ----------------
   用户口径（2026-10-15）：「找房页：区域、价格、面积 这 3 个重新排版在**同一行**，
   不要分多行，太占版面」⇒ 原来「纵向三排、每排一个小标题 + 一个全宽下拉」
   改成「一行三格，每格小标题 + 下拉」，整块高度 190px → **75px**（实测）。

   · 容器 `.filter-grid`：3 等分 + 白底 + 一条下分隔线（与上面品类条同一视觉语言）；
   · 每格仍是**原来的** `.filter-row` 三层结构（皮肤复用）——只把它自带的
     底色与下分隔线关掉，所以 `.filter-row` / `.filter-row-k` / `.filter-row-v`
     三条**一条都没删**（`test-house` 21b 那条「样式在 app.wxss」的断言依旧成立）。
   ⚠️ `minmax(0, 1fr)` 里的 `0` **不能省**：不写就是 `min-width: auto`，
      会被 `<select>` 的固有宽度顶开 ⇒ 说好三格等分却把容器撑破
      （grid 里「设了 1fr 还溢出」的经典成因）。
   ⚠️ 控件字号取 `--fs-xs`(11px)、内边距 6px/8px，是**量出来的**不是拍脑袋：
      三格里最长的档位文案是价格的「500-1000 万（N）」，12px 字体下 101px，
      而 390px 机型单格内宽只有 96px ⇒ 必被切。降到 11px 后 92.6px，
      390px 机型内宽 96px **放得下**、360px 机型 86px 会切掉尾巴上的
      「（N）」约 7px（`text-overflow: ellipsis` 收尾，档位数字仍在）。
      ⇒ 判据别写成「三格都能完整显示」，那是**做不到**的；写「容器不横向溢出」。
      （测量脚本：`webapp/qa/fsel-probe.js`，三档视口 × 三格逐项给读数。） */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-2);
  background: #fff;
  border-bottom: 1px solid var(--line);
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
}
.filter-grid .filter-row { border-bottom: 0; }
.filter-grid .filter-row-k { padding: 0 0 4px; }
.filter-grid .filter-row-v { padding: 0; }
.filter-grid .filter-select {
  padding: 6px 8px;
  font-size: var(--fs-xs);
  text-overflow: ellipsis;
}

/* ---------------- 新房促销条 / 法拍来源行 ---------------- */
.promo-bar {
  margin-top: var(--sp-3);
  padding: 6px 10px;
  border-radius: var(--r-pill);
  background: #fff7f2;
  border: 1px solid var(--brand-soft);
  color: var(--action);
  font-size: var(--fs-xs);
  font-weight: 600;
  display: inline-block;
}
.hc-promo {
  font-size: 10px;
  line-height: 1.5;
  color: var(--action);
  background: #fff7f2;
  border-radius: var(--r-sm);
  padding: 3px 7px;
  margin-bottom: 6px;
  align-self: flex-start;
}
.hc-src { display: flex; align-items: center; gap: 6px; margin-top: 5px; }
.hc-src-k { font-size: 10px; color: var(--text-3); }
.hc-src-v { font-size: 10px; font-weight: 600; }
.hc-src-v.is-ok { color: #0f9d58; }

/* ---------------- 数据来源与更新节奏（关于页 / 概览卡 / 知识库详情） ---------------- */
.sync-row {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px dashed var(--line);
}
.sync-row:last-child { border-bottom: none; }
.sync-row.is-off { opacity: .62; }
.sync-row .sy-name {
  flex: 0 0 74px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
}
.sync-row .sy-meta {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.6;
}
.sync-row .sy-badge {
  display: inline-block;
  margin-left: 5px;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--line-2);
  color: var(--text-3);
  font-size: 9px;
  font-weight: 500;
}

/* ============================================================
   后台配置页（我的 → 设置 → 模块与展示项配置）
   开关是自绘的（原型里没有引入任何 UI 库），所以 .cfg-sw 必须自带
   role="switch" / aria-checked 的语义，样式与状态一一对应。
   ============================================================ */
.cfg-toolbar { display: flex; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-2); }
.cfg-toolbar .btn { flex: 1; }
.cfg-count { padding: 0 var(--sp-4) var(--sp-3); font-size: var(--fs-xs); color: var(--text-3); }
.cfg-group { margin-bottom: var(--sp-2); }
.cfg-group-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 0 var(--sp-4) var(--sp-1);
}
.cfg-group-name { font-size: var(--fs-sm); font-weight: 600; color: var(--text-2); }
.cfg-group-count { font-size: var(--fs-xs); color: var(--text-3); }
.cfg-group-count.is-full { color: var(--brand); font-weight: 600; }
.cfg-group-note {
  padding: 0 var(--sp-4) var(--sp-2);
  font-size: var(--fs-xs); color: var(--text-3); line-height: 1.6;
}
.cfg-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.cfg-row:last-child { border-bottom: none; }
.cfg-row-main { flex: 1; min-width: 0; }
.cfg-row-label {
  font-size: var(--fs-base); font-weight: 500;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.cfg-row.is-off .cfg-row-label { color: var(--text-3); font-weight: 400; }
.cfg-row-desc { font-size: var(--fs-xs); color: var(--text-3); margin-top: 2px; line-height: 1.55; }
.cfg-badge {
  font-size: 9px; font-weight: 500; padding: 1px 5px;
  border-radius: var(--r-pill); background: var(--line-2); color: var(--text-3);
}
.cfg-badge.is-changed { background: var(--action-light); color: var(--action-dark); }
.cfg-sw {
  flex: none; width: 44px; height: 26px; padding: 0;
  border: none; border-radius: var(--r-pill); background: var(--line-2);
  position: relative; cursor: pointer; transition: background .18s ease;
}
.cfg-sw .cfg-knob {
  position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .2);
  transition: transform .18s ease;
}
.cfg-sw.is-on { background: var(--brand); }
.cfg-sw.is-on .cfg-knob { transform: translateX(18px); }

/* ---- 数值项（第七十一轮）：首页三轨条数这类「可调数字」的步进行 ----
   与开关行同高：左边文案不动，右边 − [n] +。
   ⚠️ 输入框用 text-align:center + tabular-nums —— 1 位数与 2 位数切换时
      宽度不一致会让整行左右抖一下，看起来像页面在闪。
   ⚠️ 这类样式**只能写在这里**（H5 app.css 是唯一真相源），
      改完必须重跑 `migration/port-styles.py` 同步到小程序 app.wxss。 */
.cfg-num { flex: none; display: flex; align-items: center; gap: var(--sp-2); }
.cfg-num-step {
  width: 30px; height: 30px; padding: 0; border: 1px solid var(--line-2);
  border-radius: var(--r-sm); background: var(--card); color: var(--text);
  font-size: var(--fs-lg); line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.cfg-num-step:active { background: var(--bg); }
/* ⚠️ `:disabled` 对**小程序的 `<view>` 不生效**（它不是表单控件，没有 disabled 属性），
   所以小程序侧由 index.js 算好 `canMinus / canPlus`、WXML 打 `.is-disabled`。
   两条选择器都要留着：H5 用前者、小程序用后者。 */
.cfg-num-step:disabled, .cfg-num-step.is-disabled { opacity: .35; cursor: default; }
/* 数值行的文案区右侧留一点空隙，别贴着步进器（WXML 里这个类必须存在，
   否则 check-classes.py 会把「未定义的类」判红） */
.cfg-row-num .cfg-row-main { padding-right: var(--sp-2); }
.cfg-num-input {
  width: 52px; height: 30px; padding: 0; text-align: center;
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--card); color: var(--text); font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
}
.cfg-num-input:focus { border-color: var(--brand); outline: none; }
/* 数字输入框自带的上下箭头没用（我们用 −/+ 两个按钮），去掉免得挤占宽度 */
.cfg-num-input::-webkit-outer-spin-button,
.cfg-num-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.cfg-num-input { -moz-appearance: textfield; appearance: textfield; }

/* IN.demoBar 关掉时由 SiteCfg.applyChrome() 给 body 加这个类。
   用类而不是直接删 DOM：面板里有 #demo-reset 等节点，
   删了以后走查/调试再想临时打开就没法恢复了。 */
.cfg-hide-demo .demo-bar { display: none !important; }

/* ============================================================
   分享（分享卡 + 分享按钮）
   分享卡刻意不用品牌色打底：它出现在抽屉里、周围都是白底卡片，
   一块高饱和色块会把「复制链接」这个真正的动作压下去。
   ============================================================ */
.navbar-share {
  flex: none; width: 32px; height: 32px; padding: 0;
  border: none; background: transparent; border-radius: 50%;
  color: var(--text-2); display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.navbar-share:active { background: var(--bg); }
.home-share {
  flex: none; width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--line); background: #fff; border-radius: 50%;
  color: var(--text-2); display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.share-btn-label { margin-left: 5px; }

.share-card {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--card); overflow: hidden;
}
.share-card-brand {
  display: flex; align-items: center; gap: 6px;
  padding: var(--sp-3) var(--sp-4) 0;
  font-size: var(--fs-xs); color: var(--text-3);
}
.share-card-title {
  padding: 6px var(--sp-4) 0;
  font-size: var(--fs-xl); font-weight: 600; line-height: 1.45;
}
.share-card-sub { padding: 6px var(--sp-4) 0; font-size: var(--fs-sm); color: var(--text-2); }
.share-card-points { display: flex; flex-wrap: wrap; gap: 6px; padding: var(--sp-3) var(--sp-4) 0; }
.share-chip {
  font-size: var(--fs-xs); padding: 2px 8px;
  border-radius: var(--r-pill); background: var(--brand-light); color: var(--brand-dark);
}
.share-card-foot { display: flex; gap: var(--sp-3); align-items: center; padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.share-qr-wrap { flex: none; text-align: center; }
.share-qr { width: 64px; height: 64px; border-radius: 6px; border: 1px solid var(--line); display: block; }
.share-qr-cap { font-size: 9px; color: var(--text-3); line-height: 1.35; margin-top: 2px; }
.share-qr-cap span { color: var(--action-dark); }
.share-link-label { font-size: var(--fs-xs); color: var(--text-3); }
.share-link {
  margin-top: 3px; padding: 5px 7px;
  font-size: 10px; line-height: 1.5; color: var(--text-2);
  word-break: break-all; background: var(--bg); border-radius: 6px;
}
.share-hint { margin-top: 5px; font-size: var(--fs-xs); color: var(--brand-dark); line-height: 1.55; }
