/* ============================================================
   layout.css —— 布局壳
   1. 全局视口锁定（body 不滚动）
   2. 桌面 ≥768px 居中 480px 手机框 + 两侧留白文案；移动端全屏
   3. 双页轨道 .pages/.pages-track/.page（含触摸滚动约定）
   4. 顶部 Tab .top-tab（v23 起页码指示器 .page-indicator 已下线）
   5. 浮层容器：.overlay-root / .share-sheet / 转发指引 / .ed-panel / .mp-detail
      / .gallery（多媒体图集灯箱 R4-08）/ .file-protocol-tip
   5f. v38：多设备编辑冲突（.conflict-panel / .conflict-bar）+ 平台下架态（.mod-notice / .mod-unavailable）
   6. z-index 层级表（全项目唯一定义处，架构 §8.3）
   ============================================================ */

/* ---------- 1. 全局视口锁定 ---------- */
html, body { height: 100%; }

/* ---------- 2. 舞台与手机框 ---------- */
.stage { height: 100%; }

.phone-frame {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--c-bg);
  /* v135（需求 1 第四板）：顶栏高度**单源定义**在此。41px = 现网页签段实测高
     （QA 基线 _v135-baseline-rest.json 的 topTab.h = 41）。
     `.topbar` 自身高度读它（`.lh-back` 返回条的吸附阈值也曾读它；v140 需求① 返回条
   已整体下线，现仅剩 .topbar 一个消费者），改一处即可全局对齐。 */
  --topbar-h: 41px;
}

/* 桌面端：居中手机容器，两侧留白展示说明 */
@media (min-width: 768px) {
  .stage {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 48px;
    padding: 24px;
    background:
      radial-gradient(1200px 600px at 20% 0%, rgba(30, 58, 138, .10), transparent 60%),
      radial-gradient(1000px 700px at 85% 100%, rgba(201, 169, 97, .12), transparent 55%),
      #EEF1F6;
  }
  .phone-frame {
    flex: none;
    width: 480px;
    max-width: calc(100vw - 120px);
    height: min(880px, calc(100vh - 48px));
    border-radius: 36px;
    border: 1px solid rgba(15, 43, 91, .10);
    box-shadow: 0 32px 80px rgba(15, 43, 91, .24), 0 6px 20px rgba(15, 43, 91, .12);
    outline: 10px solid #10254D;
    outline-offset: -2px;
  }
  .stage-side {
    flex: none;
    width: 220px;
    color: var(--c-text-2);
    user-select: none;
  }
  .stage-side-l { text-align: right; }
  .stage-side-title {
    font-size: 22px;
    font-weight: 800;
    color: var(--c-primary);
    line-height: 1.4;
    letter-spacing: 1px;
  }
  .stage-side-gold {
    display: inline-block;
    margin-top: 10px;
    padding: 3px 14px;
    border: 1px solid var(--c-gold);
    border-radius: var(--radius-btn);
    color: var(--c-gold);
    font-size: 12px;
    letter-spacing: 4px;
  }
  .stage-side-text { margin-top: 16px; font-size: 13px; line-height: 2; }
}
@media (max-width: 767px) {
  .stage-side { display: none; }
}

/* ---------- 3. 双页轨道 ---------- */
/* v43：.pages 从 absolute inset:0 改为 flex 剩余空间——
   固定头部（名片横卡 + CTA + 一线页签）在文档流里占位，
   内容区只占其下方，页签不再可能遮挡名片 */
.pages {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  /* v132（需求 1）：.pages 改 flex column —— 顶部的常驻固定头部 .app-head（名片卡 + 页签）
     占自然高度，横滑轨道 .pages-track 吃满剩余高度。纯文档流，零 JS 位移。 */
  display: flex;
  flex-direction: column;
}
.pages-track {
  display: flex;
  width: 300%;
  /* v132（需求 1）：高度改由 flex 分配（轨道是 .pages column flex 的第二项，flex:1），
     不再写死 height:100% —— 写死会把头部高度顶出容器。 */
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
  transform: translateX(0);
  transition: transform 260ms cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}
.pages-track.is-dragging { transition: none; }

.page {
  flex: 0 0 33.3333%;
  width: 33.3333%;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;         /* 纵向滚动交给浏览器，横滑交给 Swiper */
  /* v44：滚动到顶继续下拉交给页内「下拉刷新」，禁掉浏览器原生下拉刷新（防双重刷新） */
  overscroll-behavior-y: contain;
  position: relative;
  background: var(--c-bg);
  /* v47（用户报告「快捷条脱离原有位置」）：**让位内边距从滚动容器移到 .page-inner**。
     为什么：Chrome 中滚动容器自身的 padding-top 会额外叠加到内部 sticky 元素的吸附位
     （tmp-v47-sticky-probe 最小复现：容器 padding-top:100 时 sticky top:0 实际吸附在 100px），
     于是 .card-quickbar 的 top(v46 = --head-h - --head-collapse) 又被加了整整一个 --head-h
     → 快捷条整体下移一个头部高度（实测 top = 2×head-h，正好脱离原位）。
     改到 .page-inner 后滚动容器无内边距，sticky 吸附位恢复语义正确。 */

  /* v137（需求 ① 用户报告「上滑时头像与电话区一直抖动」）：关掉**滚动锚定**。
     ── 为什么（自维持振荡环的因果链，取证已量化，见 _forensics_v137/）──
       `overflow-anchor` 的 CSS 默认值是 `auto`：滚动容器的内容尺寸变化时，浏览器会
       **自动补偿 scrollTop**，把视觉上同一个锚点钉在原处。本项目恰好把它接进了一个环：
         ① 用户上滑，.page.scrollTop 越过阈值 40 ⇒ syncTopbarTuck 给 .cardlayer 加 .is-tucked；
         ② 卡片层 max-height 折叠 ⇒ .pages-track 变高 ⇒ .page.clientHeight 每帧从 535 涨到 844
            （v137 方案A 后为 491 → 844）；
         ③ 容器变高 ⇒ 浏览器把 scrollTop **clamp 回 29**（折叠态可滚上限 = scrollHeight−844）；
         ④ 29 < 阈值 40 ⇒ 摘掉 .is-tucked ⇒ 卡片层展开 ⇒ 容器尺寸再次变化
            ⇒ **overflow-anchor 又把 scrollTop 补偿回 41**（>40）⇒ 再加 .is-tucked ⇒ 回到 ①。
       无限循环，实测约 **23.8 次/秒**（每帧一次）。振荡每帧打断 .topbar 的
       `opacity 280ms` 过渡 ⇒ 头像/电话区持续明灭 —— 这就是用户看到的「抖动」。
       ⚠ 关键证据：#appHead 的 rect.top 取值集合**恒为 [0]**，几何从未移动，
         所以抖动**不是位置抖而是明灭**；且全库无任何 JS 写 scrollTop，回升纯由锚定产生。
     ── 为什么这一行就能根治（单变量对照）──
       锚定是「回升」的唯一来源。关掉它，第 ④ 步不再发生，环在第 ③ 步自然收敛：
       实测切换次数 **96 → 0**。
     ── 代价（明写，便于日后评估）──
       关掉锚定意味着「内容在视口上方变高时，浏览器不再自动保持视线位置」。
       本项目**不需要**该行为：卡片层折叠发生在 .pages-track **之外**（轨道外结构保证），
       页内唯一会让上方内容变高的路径就是这张卡片，且它的折叠时机由 .is-tucked 单一时钟决定；
       三项回归（可滚距离 / sticky 吸顶 / 懒加载触发）实测均无变化，见实施说明。
     ── 收窄写法无效（已实测，勿改回窄版）──
       `.page.is-tucked { overflow-anchor: none }` 只能压到 4 次、**不归零** ——
       环的另一半发生在摘掉 .is-tucked 之后（展开态锚定仍在生效），窄版漏掉了它。
     ── 其它两条已被否决的方案（勿再尝试）──
       · 只加迟滞（hysteresis）：往复幅度是 41↔29，迟滞带要开 ≥29px，反而更糟；
       · `transition: none`：实测注入后抖动**更频繁**（254 次 vs 83 次），
         因为去掉过渡让 clamp 与补偿在同帧内完成，环转得更快。 */
  overflow-anchor: none;
}
/* v132（需求 1）：让位内边距取消 —— 名片卡/页签已常驻固定在 .app-head（.pages 顶部），
   内容从页首自然排布；--head-h 与 collapsing-head.js 一并下线。 */
.page-inner {
  padding-bottom: calc(72px + var(--safe-bottom));
}

/* 两页模式（R4-01）：公众栏页关闭后轨道 200%、页宽 50%；隐藏页 display:none 不占 flex 位
 * （display:none 后剩余页前移，swiper._pageWidth() 实测 pages[0] 自动得新页宽） */
.phone-frame.is-two .pages-track { width: 200%; }
.phone-frame.is-two .page { flex-basis: 50%; width: 50%; }
/* 单页模式（R6-5）：公众栏与微小店均关闭后轨道/页宽 100%，仅剩名片夹页 */
.phone-frame.is-one .pages-track { width: 100%; }
.phone-frame.is-one .page { flex-basis: 100%; width: 100%; }
.page.is-hidden { display: none; }

/* ---------- 3b. v135 顶栏 .topbar（41px · 轨道外 · 初态隐藏 · 滚动后淡入） ----------
   v132 的 .app-head（纯文档流、含名片卡槽 + 页签）**本轮退役**：老板第四板要求
   「首屏不显示窄条，往下滚动后顶部吸顶窄条才出现」，而"占文档流的常驻头部"与
   "初态隐藏"在几何上互斥（要初态隐藏就不能占流）。

   为什么是 absolute 而不是 fixed：桌面端 .phone-frame 固定 480px 且自身
   position:relative，fixed 会以**视口**为基准铺满 ⇒ 桌面端顶栏会横跨整个屏幕。
   absolute 以 .pages（position:relative）为基准，天然被手机框约束。

   为什么不需要给 .page-inner 加 padding-top 让位：absolute 不占文档流，
   首屏名片卡顶部就是 y===0（老板「首屏保持现网样式（名片卡在上）」的硬要求）；
   滚过阈值顶栏淡入时名片卡早已滚走，被遮的始终是用户已看过的内容。
   ⚠ 切勿"顺手"加 padding-top:41px —— 会把卡片顶下 41px，直接违背上述要求。 */
.topbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  /* 初态隐藏：不占位、不拦截点击（pointer-events:none 是关键 —— 只靠 opacity:0
     的话，顶栏整条 41px 仍是透明的可点层，会吞掉名片卡顶部的手指点击）。 */
  opacity: 0;
  pointer-events: none;
  transition: opacity .28s ease;
  /* 不透明底 + 底部 1px 线：断言 #10 要求顶栏能遮住滚上来的卡片文字，
     半透明会让两层文字叠影。 */
  background: var(--c-bg);
  border-bottom: 1px solid var(--c-line);
}
/* 滚过阈值（main.js 按 scrollTop > 40 加类）⇒ 淡入并恢复可点 */
.topbar.is-tucked { opacity: 1; pointer-events: auto; }

/* 前缀（头像 + 拨号）—— ★ 必须是 .top-tab 的**兄弟节点**，见 page-chrome.js 文件头。
   flex:none 固定自身宽度，剩余空间全归 .top-tab，指示条随 .top-tab 整体右移。 */
.topbar-prefix {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding-left: 16px;
}

/* 顶栏头像 28×28 圆形：与名片卡同一条 lazy-media 解析路径（零额外请求） */
.topbar-avatar {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--c-bg-2, #F2F5FF);
}
.topbar-avatar img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;   /* 任意比例的头像都不变形 */
}
/* 无头像时的「金底首字」圆片 —— 与名片卡 .hero-card-avatar.is-fallback 同款语义，
   顶栏留空会出现"卡片有首字、顶栏是黑洞"的割裂感（v135 明确要求同款处理）。 */
.topbar-avatar.is-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(135deg, var(--c-gold, #C6A15B), var(--c-primary, #4F7BFF));
}

/* 顶栏一键拨号：36×36 热区（移动端可点性优先）+ 电话听筒图标 */
.topbar-dial {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  background: var(--c-gold-soft, rgba(198, 206, 215, .18));
  color: var(--c-primary, #4F7BFF);
  cursor: pointer;
}
.topbar-dial:active { opacity: .7; }
.topbar-dial:disabled { opacity: .45; cursor: not-allowed; }
.topbar-dial-ico { display: flex; }
.topbar-dial-ico svg { width: 18px; height: 18px; display: block; }

/* ★ 收窄落点（通则 0.9）：`.topbar > .top-tab` 只补**一条 flex 参与分配**，
   让 .top-tab 吃掉前缀之外的剩余宽度。刻意**不写进 .top-tab 本身** ——
   .top-tab 的 padding:0 16px / height / sticky 全部保持原样，指示条几何
   （left:16px; right:16px; width:calc(100%/var(--tab-count)); translateX(--tab-pos*100%)）
   一个字都没动 ⇒ 指示条左沿与活动页签左沿天然对齐（断言 #11，相对 ±1px）。
   用 `.topbar > .top-tab` 这个**作用域更窄**的选择器而不是全局改 .top-tab，
   是为了让"顶栏之外若还有 .top-tab"的场景零影响。 */
/* v136 已被需求 7 取代：页签不再与前缀同处 .topbar，故这条规则**整体删除**。
   保留说明（勿照此恢复）：它原本只让 .top-tab 吃掉前缀之外的剩余宽度，
   是「前缀 + 页签共处一个 flex 行」的必要配件。v136 页签迁到卡片层后，
   .topbar 里只剩 .topbar-prefix（flex:none），不再需要任何人分配剩余空间。
   ⚠ 若回滚 v136 把 .top-tab 加回顶栏，这条必须一并恢复，否则页签宽度塌成内容宽。 */

/* 历史 .app-head（v132）已无任何元素命中 —— 样式保留供回滚，不再有消费者。
   ⚠ 若将来有人把 .app-head 加回 DOM，记得它会占文档流、与 v135 顶栏冲突。 */
.app-head {
  flex: none;
  background: var(--c-bg);
}

/* ---------- 4. 常驻固定头部与一线页签（v132 重构，z-index 见文末层级表） ----------
   v43~v128（历史）：固定头部 = 名片横卡（#heroSlot）+ 一线页签，绝对定位覆盖层 +
   collapsing-head.js 逐帧写 transform 折叠 —— 与原生滚动两套时钟，是三段各滑各的根因。
   v131（C 方案）：覆盖层下线，名片卡/页签改每页各渲染一份进 .page-inner 首部。
   v132（需求 1，老板拍板「名片卡常驻固定」）：两者从各页抽出，收敛为 .pages 顶部的
     **唯一头部区块 .app-head**（纯文档流 flex column 的一项）—— 零 JS 位移、零
     transform、零 fixed/覆盖层；横滑切页只动 .pages-track，头部纹丝不动；
     各页 .page 仍是独立滚动容器，scrollTop 记忆 / 快捷条原生 sticky 保留
  （v132 时还含「返回条」；v140 需求① 返回条下线，注释随之收窄）。 */
/* 一线页签：三个提示词 + 通栏细线 + 指示短线（active 段随索引滑动）。
   v132（需求 1）：页签随名片卡常驻固定在 .app-head（唯一一份）；sticky 保留
   （.app-head 自身不滚动，top:0 无副作用，回滚 v132 无需改回）。
   指示条几何由 --tab-count / --tab-index / --tab-pos 变量驱动（main.js 维护，写在
   .phone-frame 上，页签经 CSS 变量继承取值），两页/单页模式下隐藏页签 display:none
   后剩余页签均分宽度，指示条自动对齐。 */
.top-tab {
  position: sticky;
  top: var(--sticky-tab-top, 0px);
  z-index: 30;
  /* v131：高度**显式钉死**为 --tab-h（原 41px = 11 上内边距 + 20 行高 + 10 下内边距）。
     v141（需求 ② 方案 B）：用户报告「页签文字上下留白太多」⇒ 收窄为 33px
     （= 7 上内边距 + 20 行高 + 7 下内边距，字号 14px 行盒不变）。
     v132：页签在 .app-head 里，下方 .card-quickbar 的吸顶位不再由它推导
     （页签在滚动容器之外），但 .page-inner 内容首行对位仍依赖头部整体高度稳定
     —— 钉死高度保持不变（页签不随字段/字体浮动）。
     ⚠ 消费方核对（v141 实测 grep）：--tab-h 仅本处与 .cardlayer-inner 注释算式引用，
       JS 不写该变量；改默认值后头部整体高度 -8px，页内几何断言基线需同步重钉。 */
  height: var(--tab-h, 33px);
  display: flex;
  padding: 0 16px;
  /* v137（需求 ② 方案 A）：`border-bottom: 1px solid var(--c-line)` **已删除**。
     原灰线的作用被「页签下方的独立指示轨道」接管（见下方 .top-tab-indicator），
     两条线同时存在会显得重复 —— 这正是用户要替换掉的那条灰线。
     ⚠ 本块其余属性（position / top / z-index / height / display / padding / background）
       一行未改：指示轨道仍以本元素的 padding box（左右 16px）为横向基准，
       改这里会重演历史记录过的 +50.67px 横向错位。 */
  background: var(--c-bg);
}
.top-tab button {
  flex: 1 1 0;
  /* v141（需求 ② 方案 B）：上下内边距 11/10 → 7/7（配合 .top-tab 高 41→33）。
     用户报告：文字上下各空出一大截、三个页签像被撑开。行盒 20px 不变。 */
  padding: 7px 4px 7px;
  background: none;
  font-size: 14px;
  font-weight: 600;
  color: var(--c-text-2);
  letter-spacing: 1px;
  transition: color .2s ease;
  white-space: nowrap;
}
.top-tab button.is-active {
  color: var(--c-primary);
}
/* v137（需求 ② 方案 A）：指示条由「压在页签底沿的短线」改为「页签下方的独立轨道」。
   ── 为什么仍留在 nav 内部（而不是挪成 nav 的兄弟节点）──
     ① 门禁 A-2.3 数的是「页签内恰好 1 个 .top-tab-indicator」；留在内部可让这条既有断言继续成立。
     ② 它是绝对定位（脱离文档流），因此**不会**成为 .top-tab 的 flex item ——
        .top-tab 的 display:flex 一行未动，三个页签的均分逻辑完全不受影响。
   ── 定位基准：为什么是 .cardlayer 而不是 .top-tab ──
     #cardTabs.top-tab 被强制为 `position: static`（见本文件 #cardTabs.top-tab 规则），
     而 .top-tab 的 position 属于**不许改**的冻结几何（改它会波及 sticky 语义与历史基准）。
     静态元素不是绝对定位子元素的包含块 ⇒ 本元素的包含块回退到最近的定位祖先 .cardlayer。
     ⚠ .cardlayer 是 `overflow: hidden`：轨道**不能**用 `bottom: -3px` 往下溢出，会被裁掉。
       故改为「由 .cardlayer-inner 的 padding-bottom 预留 3px 空位，轨道 bottom:0 填进这份空位」，
       视觉上就是紧贴页签下沿下方的一条独立轨道，且零裁剪、零溢出。
   ── 横向基准 ── 左右 16px，与 .top-tab 的 padding box 对齐（**属性值一行未改**），
     故活动段左沿 === 活动页签**格**左沿（实测差值 0.00px，三页与两页模式均如此）。
   ── 纵向基准 ── `bottom: 0` 是相对 .cardlayer 的 padding box 下沿；.cardlayer 无 padding，
     该下沿即 .cardlayer-inner 的下沿 = 页签下沿 + 预留的 3px 空位。
   ── 已知耦合（改动本块前必读）── 本轨道落在页签**正下方**，前提是
     「nav 是 .cardlayer-inner 的最后一个在流子元素」。若日后有人在 nav 之后
     再插入可见元素，轨道会与它重叠 —— 那时必须同步调整 .cardlayer-inner 的 padding-bottom。 */
.top-tab-indicator {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 0;
  height: 3px;
  /* v137：轨道本体 = 整条浅灰底线（var(--c-line)），活动段由 ::before 叠在其上。
     这条灰线同时**接管**了原先 .top-tab 的 border-bottom 的视觉职责。 */
  background: var(--c-line);
  border-radius: 3px;
  pointer-events: none;
}
/* v137：活动段。与 v136 的差别只有两点 —— ① 不再 `bottom:-1px` 骑在 border 上，
   而是完整填满 3px 高的轨道（`top:0` + 去掉了负偏移）；② 圆角四周一致（原为上圆角）。
   ⚠ 宽度/位移/过渡三者的算法**一行未改**（--tab-count 均分 + --tab-pos 连续量驱动），
     这是拖拽跟手与两页模式自适应的根据，动它就会失同步。 */
.top-tab-indicator::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: calc(100% / var(--tab-count, 3));
  height: 3px;
  border-radius: 3px;
  background: var(--c-primary);
  /* v62（需求 4）：位移改由 **连续量 --tab-pos** 驱动（main.js 在拖拽期写入小数），
     未写入时回落整数 --tab-index —— 静止时两者等价，向后兼容。
     过渡时长/缓动与内容轨道（swiper SNAP_DURATION/SNAP_EASING）保持同源同值。 */
  transform: translateX(calc(var(--tab-pos, var(--tab-index, 0)) * 100%));
  transition: transform 260ms cubic-bezier(.22, .61, .36, 1);
}
/* v62（需求 4）：手指拖拽期间关掉过渡 —— 指示条与内容轨道一样"跟手走"，
   松手落定后本类摘掉，过渡恢复，指示条与轨道同步滑到目标页签（同缓动同时长）。
   v131：类挂在 .phone-frame 上（三份页签共享一份拖拽状态）。 */
.phone-frame.is-tab-drag .top-tab-indicator::before { transition: none; }

/* v23：底部页码指示器（.page-indicator 三圆点）已下线（用户指令：影响美观）；
 * 页面切换仅由顶部页签驱动。原样式块与 z-index 层级表条目（60）同步移除。 */
/* 两页模式（R4-01）：隐藏页对应的页签整体不占位 */
.top-tab button[hidden] { display: none; }

/* ---------- 4a. v136（需求 7）共享卡片层 .cardlayer（轨道外 · 可纵向折叠） ----------
   需求 7 三件事：① 名片卡横滑不动；② 页签下沉到卡下；③ 顶栏只留头像拨号。

   ⛔ 为什么卡片层必须在 .pages-track **之外**（结构保证，不是 JS 干预）：
     Swiper 横滑时唯一被写的是 `this._track.style.transform = translateX(...)`。
     轨道外的元素不吃这个 transform ⇒ 天生纹丝不动。这条比"每帧同步卡片位置"
     强得多：后者是第二时钟，且一旦 transform 写法变化就静默失效。
   ⛔ 为什么收起用**一个布尔 + CSS 自算目标高度**，而不是逐帧写 height/top：
     折叠量由 `.is-tucked` 这**一个布尔**决定（main.js 的 syncTopbarTuck 与顶栏
     共用同一个 want、同一次判定），CSS 自己算出目标高度 ⇒ 单一时钟。
     若改成 JS 逐帧写 max-height，等于把折叠动画搬回 JS，与 v131~v135 削掉的
     collapsing-head.js 是同一类回退。
   ⛔ v138：折叠的**动效载体**由 `max-height` 换成 `grid-template-rows`（仍是一个布尔
     驱动 CSS、仍是单一时钟）—— 换的是「CSS 用哪个属性把高度算到 0」，不是换数据流。
     理由与实测数字见下方 .cardlayer 的注释块。

   收起后的残留高度 = 0（v138 起；grid 轨道 0fr 精确归零，v136/v137 的 max-height:0
   也归零，但那一版的过渡有 35% 时长是「高度不动」的无效区）；translateY(-26px) 让内容
   先"走"一点再被裁，视觉上是"抽上去"而不是"被压扁"。pointer-events:none 保证收起后
   不拦截点击。 */
.cardlayer {
  /* 占文档流高度（flex:none），.pages-track 仍靠 flex:1 吃满剩余。
     ⚠ 不能写 flex:1 1 auto —— 那会让卡片层与轨道争高度，卡片高度随窗口浮动，
       而 QA 基线要求卡片高度稳定。 */
  flex: none;
  position: relative;
  /* ⚠ 必须保留 overflow:hidden，且**两个职责缺一不可**：
     ① 折叠时的裁剪容器（grid 轨道收缩到 0fr 后，溢出的卡片内容靠它裁掉）；
     ② 指示轨道 .top-tab-indicator（绝对定位）的包含块来源 —— 它与 position:relative
        一起把轨道的 `bottom:0` 钉在卡片层底沿（v137 需求② 的三条几何基线靠这条成立）。 */
  overflow: hidden;

  /* ---------- v138（需求① 用户报告「名片夹页面上滑时名片卡瞬间向上滑动，
     没有中间过渡过程」）：折叠载体 max-height → grid-template-rows ----------

     ── 病根（v136/v137 的 max-height 写法，取证已量化，见 _forensics_v138/）──
       `max-height: 1000px` 只是「大于任何真实卡片高度」的上界，而真实的视觉高度由
       内容决定（实测三档：空模板 273.19px / 常规 333.28px / 长字段 366.17px）。
       过渡从 1000px 走到 0px，**只要 max-height 仍 > 真实高度，视觉高度就完全不动** ——
       前 35.4% 的时长（实测静止期 99.2ms / 名义 280ms）画面是冻住的，之后剩余高度才在
       167.8ms 内被 ease 的收尾段一次塌完 ⇒ 观感＝「先顿一下，然后瞬移」。
       反向对照（把 timing 改成 linear）：静止期实测 183.5ms ≈ 理论 (1000−333.28)/1000×280
       = 186.7ms ⇒ 静止期**就是**「上界与真实高度之比」的直接产物，不是测量噪声。
       阳性对照（transition:none）：静止期 0ms、收缩 0ms ⇒ 探针有毫秒级分辨力。

     ── 为什么 grid-template-rows:1fr→0fr 能根治 ──
       `1fr` 的轨道尺寸**就是内容尺寸**，不存在「上界 vs 真实高度」这对参数 ⇒ 过渡一开始
       轨道就在收缩，没有无效区。实测静止期 **0ms**（对照基线 99.2ms），收缩耗时
       266~283ms（≈名义 280ms，抖动来自帧对齐），展开/折叠的终态高度仍由内容算，
       **在结构与参数上都不可能裁到任何内容**。

     ── 为什么**不**用「把 max-height 上界降到接近典型值」（如 420px）──
       ① 治不净：上界 420px 时静止期仍有 33.6ms（12%，因为 420 仍大于真实 333.28px），
          做不到归零；
       ② 有静默裁切风险：实测长字段卡真实高 366.17px，420 只剩 53.8px 余量；把上界降到
          340px 时同一张卡被 `overflow:hidden` **静默裁掉 26px 且零报错**（不抛异常、
          不报 pageerror、功能断言全绿）。字段多、字号/系统字体放大、公司名与地址折行
          都会继续推高真实高度 ⇒ 「一个常数上界」这条路的天花板是**不可控的裁切风险**，
          而 grid 方案把高度语义交回内容，从根上没有这个风险。

     ── 兼容性（诚实边界）──
       grid 轨道插值在 Chromium 107+ 落地；本项目**实测环境为 Chromium 155**（本机 Edge），
       **iOS WKWebView 未验证**。不支持插值的内核上退化形态 = 轨道瞬时跳到 0fr
       （等价 `transition:none`）：**不破布局、不产生错位或裁切**，只是失去过渡动画 ——
       即不比「现状观感」更差。上线前须在 iOS / Android 微信真机各过一遍折叠动画。
     ⚠ 若日后有人想改回 max-height：请先读 `_forensics_v138/e1_curve.json`（逐帧静止期）
       与 `e5_solutions.json`（四方案对照），再决定。

     ── v154（需求②）：动效载体再次更换 grid-template-rows → height ──
       实测真机反馈：iOS WKWebView 与部分安卓 WebView **不支持 grid 轨道插值**，
       退化形态就是「轨道瞬时到 0fr」＝等价 `transition:none` —— 正好是用户报的
       「上滑时名片卡瞬间折叠、下方内容瞬跳」。桌面 Chromium（本机 Edge 155）支持插值，
       所以 v138 的逐帧取证与历轮 QA 全绿都测不出这个问题，**只有真机才暴露**。
       换法：`height` 是各内核都支持插值的属性；难点是 `auto` 不可插值，故由
       `main.js` 的 `setCardLayerTucked()` 在**状态翻转那一帧**量一次真实高度写进 inline，
       transitionend 后清掉（展开态回到 auto）。数据流仍是一个布尔 + 一个时钟，
       阈值 / 门控 / 锁存 / 终态残留 0 / 收起不可点全部未动。
       ⚠ 不要在这里加回 `grid-template-rows`：那会让 iOS 重新退化成无动画，
          而且桌面上看不出来（这正是这个问题藏了 16 个版本的原因）。 */
  display: block;
  /* 展开态：auto（内容多高就多高）。折叠动画开始时 JS 会临时写 px。 */
  height: auto;
  opacity: 1;
  transition: height .28s ease, opacity .28s ease, transform .28s ease;
}
/* 滚过 40px（main.js 的 .is-tucked）⇒ 纵向收起。
   ⚠ v154：`height: 0` 取代 `grid-template-rows: 0fr`。0 是精确归零，
     终态残留仍是 0px；配合 JS 写入的起始 px，过渡在任何内核上都是平滑的。 */
.cardlayer.is-tucked {
  height: 0;
  opacity: 0;
  transform: translateY(-26px);
  /* 收起后整层不可点：否则它虽然不可见，却仍是一层透明的点击拦截层，
     会吞掉页面顶部的首次点击（与 .topbar 初态 pointer-events:none 同理）。 */
  pointer-events: none;
}

/* 页签槽：卡片层内，卡片之下。
   ⚠ 为什么只覆盖 position / top 两项：.top-tab 自身的 padding:0 16px /
     height:41px / 指示条 left/right/width/transform **一行未改** ——
     指示条以 .top-tab 的 padding box 为基准，改 padding 会造成历史记录过的
     +50.67px 错位。v136 只把它从「粘在滚动容器顶部」改成「卡片层内的静态流元素」。
   ⚠ 为什么必须覆盖 top:.top-tab 默认 `top: var(--sticky-tab-top, 0px)`：
     v140 需求① 前线索返回条在场时该变量被写成 44px，若不覆盖，页签会凭空下移 44px。
     v140 起该变量再无人写，但覆盖保留：①防未来有人恢复该变量；②卡片层内本就
     不需要任何 sticky 语义（它整体随 is-tucked 折叠），故置为 auto。 */
#cardTabs.top-tab {
  position: static;
  top: auto;
}

/* v137（需求 ② 方案 A）：为「页签下方的独立指示轨道」预留 3px 空位。
   ⚠ 为什么必须预留：.cardlayer 是 `overflow: hidden`，而指示轨道是绝对定位、
     包含块回退到 .cardlayer —— 轨道不可能靠 `bottom: -3px` 向下溢出（会被裁掉）。
     故由本元素在页签之后留出 3px，轨道 `bottom: 0` 正好填进这份空位。

   ⚠ 首屏净高度的真实构成（−36.25px，实测 309.44 → 273.19，两版独立测得一致）：
       删：引导文字块 ................................ −39.25
       删：.top-tab 的 border-bottom .................. −0.00   ← 不是 −1，理由见下
       加：本元素的 padding-bottom（轨道落点）......... + 3.00
       ────────────────────────────────────────────────────
       合计 ............................................ −36.25
     **⚠⚠ 灰线为什么贡献 0px（不是 1px）**：
       全局 `box-sizing: border-box`（base.css:58）。`.top-tab` 的高度被显式钉死为
       `height: var(--tab-h, 41px)`，在 border-box 下这 41px 是**含边框的总高** ——
       那条 1px 的 border-bottom 是从 content box 里**挤进去**的，不会把盒子撑高。
       实测（探针，同一元素 border-bottom 取 0 / 1 两态）：
           border-box  : 41px → 41px   增量 0px
           content-box : 41px → 42px   增量 1px   ← 反向自检，证明探针真能测出增量
       ⇒ **删掉 border-bottom 不改变 nav 高度，一分净高度都省不出来。**
       v136 基线 nav 高 41px、v137 nav 高 41px，正是这条的直接体现。
       v141（需求 ②）起 --tab-h 默认值改 33px —— 本段是 v137 的**历史账目**，仍按 41px
       记载；查 v141 后的几何请以 33px 重算，不要拿本段数字直接对照新探针。
     ⚠ 这里曾把灰线记成 −1px、并据此推出「净减 37.25px」，那是**错的**：
       ① 灰线实际贡献 0；② 37.25 与实测的 36.25 也自相矛盾。
       两处都错在「以为 border 会撑高盒子」。后人若照错误账目反推，会算出第三个值。
   ⚠ 耦合（**静默失效，零报错，只有几何实测能发现**）：
     轨道落在页签正下方的前提是「nav 是 .cardlayer-inner 的**最后一个在流可见子元素**」。
     一旦有人往 nav 之后插入任何元素，该元素会与轨道**重叠**——
     不抛错、不报 pageerror、所有功能测试照样全绿，只有量几何才会看见。
     详见 index.html 中 #cardTabs 处的强注释。 */
.cardlayer-inner {
  padding-bottom: 3px;

  /* ---------- v138：grid 折叠的收缩前提 —— 「至少留一」，且本层**故意只留 min-height:0** ----------
     `grid-template-rows: 0fr` 只把**轨道**定到 0；网格子项默认 `min-height:auto`
     （即不小于内容高度），不解除这一条，子项会把轨道顶回去 ⇒ 折叠动画完全不动。
     ⚠ 这里**不是**「缺一不可」（v138 初版注释写错了，QA 单变量实验已推翻）：
       `overflow ≠ visible` 的盒子 automatic minimum size 为 0 ⇒ `overflow:hidden` 本身
       就已把 `min-height:0` 的职责覆盖了。两者**互为冗余**，留一即可：
         · 单删 min-height:0（保留 overflow:hidden） ⇒ 照样归零；
         · 单删 overflow:hidden（保留 min-height:0） ⇒ 照样归零；
         · 两个都删 ⇒ 失效（实测终态 330.28px，完全不动）。
     ⚠ 本层**故意只保留 min-height:0、不保留 overflow:hidden**（v138 R4 复核，实测依据
       `_forensics_v138/e10_band.json` / `e11_paused.json` / `r4-A/B-*.png`）：
       Chrome 对 auto 高 grid 容器做 fr 插值时，**容器盒子高度滞后于轨道尺寸**
       （把过渡暂停在 80ms 实测：轨道 87.7px、容器盒子 170.2px，差 82.5px，暂停态仍在 ⇒
       不是采样误差）。若本层自己裁剪（overflow:hidden），卡片会提前在 87.7px 处被切掉，
       盒子下方 82px 变成「谁都不画」的空带，观感＝卡片中段被齐刷刷削掉一块
       （r4-A-fixoff-80ms.png）；去掉本层裁剪后，内容一直画到**容器**的裁剪边
       （.cardlayer 的 overflow:hidden 是唯一裁剪边界），空带被卡片内容填满
       （r4-B-fixon-80ms.png），折叠终态照旧精确归零且无内容漏画（r4-fix-collapsed-rest.png，
       终态实测容器高 0px）。展开态不受影响：inner 高度＝内容高度，无溢出可裁。
     ⚠ 展开态尺寸一字未变（实测展开高度 273.19 / 333.28 / 366.17px 三档与 v137 一致）。
     ⚠ v154 起折叠载体换成 height（见上方 .cardlayer），本层的 min-height:0 已不再承担
       「解除 grid 子项最小尺寸」的职责；**保留**它是因为它对 block 布局无害，
       且删掉属于与本需求无关的改动（范围外）。 */
  min-height: 0;
}
/* v138：折叠态去掉那 3px 的「指示轨道落点」预留。
   ⚠ 为什么必须单独加这一条：`.cardlayer-inner` 的 `padding-bottom:3px` 是 v137 为
     指示轨道预留的落点（.cardlayer 是 overflow:hidden，轨道不可能靠 `bottom:-3px`
     向下溢出）。但内边距**不参与** grid 轨道收缩 —— 轨道到 0fr 后这 3px 仍占位，
     实测折叠终态残留 3px（补本规则后实测 cardLayerH 3px → 0px）。
   ⚠ 这 3px 是**瞬间**撤掉的（padding 不参与过渡），但此时整层正在
     opacity→0 + translateY(-26px) + 轨道收缩，3px 的瞬时差不可见；
     若要更"干净"可给 padding-bottom 也加过渡，但那会多一个动画属性（本轮不引入）。 */
.cardlayer.is-tucked .cardlayer-inner {
  padding-bottom: 0;
}

/* 卡片槽：v135 名片卡是「三页各一份插进 .page-inner」，v136 改为共享单份放这里。
   宽度不加约束 —— 由 .cardlayer-slot 自然撑满，hero-card 自带 margin。 */
.cardlayer-slot {
  display: block;
  /* v136：卡片槽与下方页签之间留 10px 呼吸位。
     ⚠ 为什么必须显式留：.hero-card-wrap 的 padding 是 `16px 16px 0`
       （**下内边距为 0**，见 card.css:699），所以卡片底边与页签顶边会**紧贴**，
       视觉上像"页签长在卡片上"而不是"页签在卡下"。
     ⚠ 为什么加在槽上而不是改 .hero-card-wrap：wrap 是**不许动的公共类**
       （顶部边距 16px 是首屏卡片贴顶的硬要求，改它会把卡片整体下移）。
       加在 v136 新增的槽上是纯增量，零外溢。 */
  padding-bottom: 10px;
}
.cardlayer-slot:empty {
  /* 空槽不占任何高度：data 尚未到达（首帧到货前）时卡片层只含页签，
     避免出现一段空白把页签顶到视口外。 */
  display: none;
}

/* ---------- 4b. v44 下拉刷新指示器（owner 编辑态；fixed-head 上方浮出） ---------- */
.pull-refresh {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 80;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(15, 43, 91, .85);
  color: #fff;
  font-size: 12px;
  letter-spacing: 1px;
  opacity: 0;
  pointer-events: none;
  transform: translateX(-50%) translateY(-56px);
  transition: opacity .18s ease, transform .12s linear;
  box-shadow: var(--shadow-card);
}
.pull-refresh.is-visible { opacity: 1; }
.pull-refresh.is-ready { background: var(--c-primary); }
.pull-refresh-arrow { font-size: 14px; line-height: 1; }

/* ---------- 5. 浮层容器 ---------- */

/* 浮层根：不建立层叠上下文，让子浮层按层级表直接生效 */
.overlay-root {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.overlay-root > * { pointer-events: auto; }
/* v87（需求 2）：Toast 必须点击穿透。
 * 缘由：base.css 的 .toast{pointer-events:none} 与上面这条同特异性，而 layout.css 后加载，
 * 通用规则把它盖掉 —— Toast（绝对定位在屏幕正中、宽至 76%）因而变成可命中元素；
 * 更麻烦的是该节点在首次提示后常驻 overlayRoot（opacity:0 仍参与命中测试），
 * 于是屏幕正中一条横带会永久吞掉落在其上的手势（横滑起手不翻页、点按无反应）。
 * 这条用更高特异性（.overlay-root > .toast）恢复原意：提示只是提示，不挡操作。纯样式，零布局影响。 */
.overlay-root > .toast { pointer-events: none; }

/* 5.1 分享面板 + 转发指引浮层 */
.share-sheet {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background: rgba(10, 14, 22, .5);
  animation: ui-fade-in .2s ease;
}
.share-panel {
  background: #fff;
  /* v60（需求 4）：圆角 20px → **16px**，与全局弹层统一（需求原文"圆角 16px"） */
  border-radius: 16px 16px 0 0;
  padding: 20px 20px calc(20px + var(--safe-bottom));
  animation: ui-slide-up .28s cubic-bezier(.22, .61, .36, 1);
}
.share-panel-title {
  text-align: center;
  font-size: 15px;
  font-weight: 700;
  color: var(--c-text);
  margin-bottom: 6px;
}

/* ---------- v62（需求 3）：分享兜底面板 .share-fallback ----------
   只在「原生分享没起来」时出现（桥不可用 / 桥报错 / 启发式判定失败 /
   系统分享不可用）。三件事：微信引导语 + 复制链接 + 保存为图片 + 取消。
   遮罩与贴底布局复用 .share-sheet 同款写法，层级同登记 700（见本文件底部层级表）。 */
.share-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background: rgba(10, 14, 22, .5);
  animation: ui-fade-in .2s ease;
}
.share-fallback-panel {
  background: #fff;
  border-radius: 16px 16px 0 0;
  padding: 18px 18px calc(18px + var(--safe-bottom));
  animation: ui-slide-up .28s cubic-bezier(.22, .61, .36, 1);
}
.share-fallback-guide {
  background: var(--c-gold-soft);
  border-radius: 12px;
  padding: 10px 12px;
  margin-bottom: 12px;
}
.share-fallback-guide-t {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--c-text);
}
.share-fallback-guide-s {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.7;
  color: var(--c-text-2);
}
.share-fallback-row {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--c-line);
  background: var(--c-bg);
  border-radius: 12px;
  padding: 11px 13px;
  margin-bottom: 8px;
  cursor: pointer;
}
.share-fallback-row:active { background: var(--c-line-soft); }
.share-fallback-row-t {
  display: block;
  font-size: 14px;
  font-weight: 700;
  color: var(--c-text);
}
.share-fallback-row-s {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--c-text-2);
}
.share-fallback-cancel {
  display: block;
  width: 100%;
  margin: 8px auto 0;
  padding: 13px;
  border-radius: 12px;
  background: var(--c-bg);
  color: var(--c-text-2);
  border: 0;
  font-size: 14px;
  cursor: pointer;
}
/* v60（需求 4）：底部「取消」—— 整宽按钮（微信同款的手感），点它或点遮罩关闭 */
.share-panel-cancel {
  display: block;
  width: 100%;
  margin: 16px auto 0;
  padding: 13px;
  border-radius: 12px;
  background: var(--c-bg);
  color: var(--c-text-2);
  font-size: 14px;
  font-weight: 600;
}

/* v40（P0-4）：统一分享面板的应用图标排；v60（需求 4）改为**微信同款 4 列网格**
   （圆形图标 + 文字，5 个目标：转发给朋友 / 分享到朋友圈 / 收藏 / 复制链接 / 保存为图片）。
   纪律：图标一律内联 SVG、零请求；**禁用 backdrop-filter**（动画容器真机滚动卡顿主嫌）；
   层级沿用 .share-sheet 700 内部，不在 §6 层级表新增档位。 */
.share-panel-apps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px 8px;
  padding: 14px 0 2px;
  justify-items: center;
}
.share-app {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 0;
  background: none;
  border: 0;
}
.share-app-ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
  color: var(--c-text-2);
  transition: transform .16s ease;
}
.share-app-ico svg { width: 26px; height: 26px; }
.share-app:active .share-app-ico { transform: scale(.93); }
.share-app-label { font-size: 11.5px; color: var(--c-text-2); white-space: nowrap; }
/* 转发给朋友：微信绿底 + 白色微信 SVG */
.share-app-wechat .share-app-ico {
  background: #07C160;
  border-color: #07C160;
  color: #fff;
}
/* 分享到朋友圈 —— 相机图形 + 琥珀底，与「转发给朋友」一眼可分（图形 + 底色双重区分） */
.share-app-moments .share-app-ico {
  background: #F59E0B;
  border-color: #F59E0B;
  color: #fff;
}
/* 收藏（v60 新增）：金色底 + 五角星 */
.share-app-star .share-app-ico {
  background: #C9A961;
  border-color: #C9A961;
  color: #fff;
}
/* 保存为图片（v60 新增）：品牌深蓝底 + 下载箭头 */
.share-app-save .share-app-ico {
  background: var(--c-primary-deep);
  border-color: var(--c-primary-deep);
  color: #fff;
}

/* v40（P0-8）：分享链接接收端的内容定位高亮（文章 / 商品共用同一份 keyframes）
   放在 layout.css 而非 mp/mall 各自文件：两个页面共用，且不污染 base.css 基础层。
   高亮 1.6s 后自动褪去，不锁定用户操作。 */
.is-target-flash {
  animation: target-flash 1.6s ease;
}
@keyframes target-flash {
  0% { box-shadow: 0 0 0 0 rgba(201, 169, 97, .55); }
  35% { box-shadow: 0 0 0 4px rgba(201, 169, 97, .35); }
  100% { box-shadow: 0 0 0 0 rgba(201, 169, 97, 0); }
}

/* 转发指引：全屏遮罩 + 右上角箭头气泡 */
.share-guide {
  position: absolute;
  inset: 0;
  background: rgba(10, 14, 22, .82);
  animation: ui-fade-in .25s ease;
  color: #fff;
}
.share-guide-bubble {
  position: absolute;
  top: 64px;
  right: 22px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.share-guide-arrow {
  width: 54px;
  height: 54px;
  flex: none;
}
.share-guide-text {
  max-width: 210px;
  font-size: 15px;
  line-height: 1.9;
  text-align: left;
}
.share-guide-text strong { color: var(--c-gold-light); }
.share-guide-tip {
  position: absolute;
  left: 50%;
  top: 62%;
  transform: translateX(-50%);
  width: 78%;
  text-align: center;
  font-size: 13px;
  color: rgba(255, 255, 255, .78);
  line-height: 1.9;
}
.share-guide-tip .share-guide-tap {
  display: inline-block;
  margin-top: 18px;
  padding: 10px 34px;
  border-radius: var(--radius-btn);
  border: 1px solid rgba(255, 255, 255, .4);
  font-size: 14px;
  color: #fff;
  animation: guide-pulse 1.6s ease infinite;
}
@keyframes guide-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .55; }
}

/* 5.2 公众栏文章详情子页（第五轮 T5-7：挂载点迁至 #overlayRoot，满屏覆盖
   phone-frame；独立纵向滚动。原先挂 page-b 内受 .pages-track transform
   层叠上下文压制，z-index 200 实际低于页签 50，返回栏被页签遮挡） */
.mp-detail {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  background: #fff;
  transform: translateY(100%);
  transition: transform 300ms cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}
.mp-detail.is-open { transform: translateY(0); }
/* v145（需求①）：深链直达 —— 首帧即终态，不做 300ms 上滑（好友点开分享卡片
   第一眼就该是正文，而不是"半个白页滑上来"）。由 openArticle(id,{instant:true})
   在加 is-open 之前打上，下一帧移除，之后的关闭动画仍按原过渡工作。 */
.mp-detail.is-instant { transition: none; }

/* 5.3 file:// 协议提示（ES Modules 无法在 file:// 下加载） */
.file-protocol-tip {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 32px;
  text-align: center;
  background: var(--c-primary);
  color: #fff;
}
.file-protocol-tip[hidden] { display: none; }
.file-protocol-tip h2 { font-size: 18px; letter-spacing: 1px; }
.file-protocol-tip p { font-size: 13px; color: rgba(255, 255, 255, .82); line-height: 2; }
.file-protocol-tip code {
  padding: 2px 8px;
  border-radius: 6px;
  background: rgba(255, 255, 255, .14);
  font-family: Consolas, Menlo, monospace;
}

/* 5.4 管理模式面板（全屏浮层，内部独立滚动；类名与 editor.js 一一对应） */
/* ============================================================
 * v83（需求）：手机端「管理模式」（.ed-panel）固定白色系 —— 与名片主题解耦
 * 背景：v82 已把桌面后台（admin.html / ops.html）固定成白色；但手机端管理模式
 *   （index.html 内的 .ed-panel 全屏浮层，DOM 与桌面后台同源）当时只做了桌面
 *   覆写，其配色仍由 --c-* 决定 → 随名片主题漂移（实测头部在 lilac/crimson-gold/
 *   jade-green/cool-platinum 下依次呈紫 / 红 / 绿 / 蓝灰）。
 * 做法：与 v82 完全同手法 —— 在浮层根容器 .ed-panel 作用域内重定义 --c-* 为固定
 *   白色系（取值与 admin.css / ops.css 的同类块同一套），layout.css 内所有
 *   复用 var(--c-*) 的 .ed-* 组件（分区卡/输入框/按钮/主题卡等）一处生效；
 *   金色点缀家族 --c-gold* 一并并入强调色，使管理模式也只保留「一个中性强调色」。
 *   再单独改掉 .ed-* 里「深色底假设」的硬编码（面板头 / 页签 / 关闭键 / 角标）。
 * v112（需求 4）：这套固定色的**具体取值整体换代** —— 由「白底 + 蓝 #2563EB」换成
 *   企业名片页那一套（base.css :root / themes.css cool-platinum 的实数），
 *   使手机端管理模式、电脑端后台、平台后台三处与名片页同一画风。
 *   换装同时收掉两处**写死的蓝**（.ed-tabs 激活态淡底、ops.css 的 .ops-link 虚线）。
 * 边界：只作用于 .ed-panel 自身 —— 名片夹 / 公众栏 / 微小店 / 线索页、以及全局
 *   浮层（#overlayRoot 不是 .ed-panel 的后代）配色不受影响，仍跟随主题切换。
 * 注：本块只重定义「值」，不新增任何布局属性，故不改变任何页面几何。
 * ============================================================ */
.ed-panel {
  /* v112（需求 4）：配色由「白底 + 蓝 #2563EB」换成**企业名片页那一套**
     （= base.css :root / themes.css cool-platinum 的实数，逐个 token 抄过来，
     不是"看着像"的近似值）。结构、布局、字号一律不动，只换颜色。
     老板原话：「将名片管理里的页面颜色画风设计成和企业名片页面的颜色画风一样，
     一样的颜色看起来协调一点」。 */
  --c-primary: #5C6773;
  --c-primary-2: #8A96A3;
  --c-primary-deep: #3E4753;
  --c-gold: #A9B3BF;
  --c-gold-light: #C6CED7;
  --c-gold-soft: rgba(169, 179, 191, .18);
  --c-gold-bar-1: #C6CED7;
  --c-gold-bar-2: #A9B3BF;
  /* 底由纯白改月白：名片页是「月白底 + 白卡」两层，纯白底 + 白卡等于白叠白、
     没有层次；改月白后才和管理模式/名片页一致（--c-card 仍是白，卡片本身不变）。 */
  --c-bg: #F6F7F9;
  --c-card: #FFFFFF;
  --c-text: #1F242B;
  --c-text-2: #5A626C;
  --c-text-3: #98A0AA;
  --c-title: #1F242B;
  --c-eyebrow: #AEB6C0;
  --c-line: #E4E7EB;
  --c-line-soft: #EEF0F3;
  /* 文字三档灰、价格暖棕、危险红**不动** —— 它们本来就与名片页同值；
     危险色必须继续显眼（删除账户等动作不能跟着"统一"成灰）。 */
  --c-price: #8A6A3A;
  --c-danger: #C9433F;
  --shadow-card: 0 4px 16px rgba(92, 103, 115, .08);
  --shadow-float: 0 12px 40px rgba(62, 71, 83, .20);
  --shadow-soft: 0 2px 10px rgba(92, 103, 115, .05);
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--c-bg);
  animation: ui-slide-up .3s cubic-bezier(.22, .61, .36, 1);
}
/* v112（需求 4）：base.css 的 .btn-primary 投影写死为深蓝 rgba(15,43,91,.28)，
   银灰主色按钮下会泛出一圈蓝光 —— 换装最容易漏的就是这种"颜色不在变量块里"的地方。
   只在管理模式作用域内覆写为中性投影；**不动 base.css 的全局值**，
   名片页自己的主按钮投影保持原样。admin.css / ops.css 里有同一条。 */
.ed-panel .btn-primary { box-shadow: 0 4px 12px rgba(92, 103, 115, .28); }
.ed-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 3px 12px;
  /* v128（需求 2）：顶栏「管理模式 / 退出」上下留白收窄（A 方案：只收留白，仍占满整宽）——
     上下 padding 由 14px 收到 8px、左右由 16px 收到 12px，顶栏总高随之由约 60px 收到约 44px。
     结构与配色一概不动（不做胶囊条），仍是一行铺满整宽。 */
  /* v129（需求 4，老板**拍板方案 B**）：顶栏上下留白再收一档 ——
     上下 padding 由 8px 收到 3px、左右 12px 不变，顶栏总高随之由约 44px 收到约 34px，
     顶到「退出」胶囊（28px 高）的贴边极限；结构与配色一概不动。 */
  /* v83（需求）：面板头由「var(--c-primary) 深底 + 白字」改「白底 + 深字」，
     与下方页签条用 1px 浅灰底描边分隔（描边不改变盒模型：全局 box-sizing:border-box）。 */
  background: var(--c-card);
  color: var(--c-text);
  border-bottom: 1px solid var(--c-line);
}
.ed-head-main { flex: 1; min-width: 0; }
.ed-head-title { font-size: 14px; font-weight: 700; letter-spacing: 2px; }
/* v126（需求 1）：关闭键由「×」圆形图标改**文字胶囊**（文案「退出」）——
   它同时承接了原「完成并关闭」底栏的职责，做成一眼能认的按钮比一个叉更清楚。
   宽度改自适应（原 32px 定宽放不下两个字），高度与圆角与原图标键对齐。 */
.ed-close {
  flex: none;
  height: 28px;
  padding: 0 12px;
  border-radius: 999px;
  /* v83（需求）：关闭键随头部一起改「白底深字」—— 原为白字 + 白半透明底（深底假设）。 */
  color: var(--c-text-2);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1px;
  background: var(--c-line-soft);
  transition: background .2s ease;
}
.ed-close:active { background: var(--c-line); }

/* 管理壳页签（R4-07：名片/公众栏/微小店/通用设置 4 管理页，DOM 常驻切换） */
/* v44：管理页签一行最多 5 个（名片夹管理/公众栏管理/微小店管理/线索列表/通用设置），
 * 窄屏放不下改为**横向滑动**（此前 flex:1 均分 + 「未启用」文字角标把行挤爆，
 * 最右「通用设置」点不到）。页签 flex:none 恒宽，容器横向滚动，滚动条隐藏。 */
.ed-tabs {
  flex: none;
  /* v123：横线指示器（.ed-tab-indicator）的包含块 —— 绝对定位、随本容器滚动一起走。 */
  position: relative;
  display: flex;
  gap: 4px;
  padding: 8px 12px;
  /* v83（需求）：页签条由「var(--c-primary-deep) 深底」改「白底 + 底描边」。 */
  background: var(--c-card);
  border-bottom: 1px solid var(--c-line);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.ed-tabs::-webkit-scrollbar { display: none; }
.ed-tabs button {
  flex: 0 0 auto;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 12px;
  font-weight: 600;
  /* v83（需求）：页签文字由「白字（深底假设）」改深灰。 */
  color: var(--c-text-2);
  letter-spacing: 1px;
  white-space: nowrap;
  transition: background .2s ease, color .2s ease;
}
.ed-tabs button.is-active {
  /* v83（需求）：激活态由「白半透明底 + 金」改「强调色淡底 + 强调色字」。
     v112（需求 4）：淡底**不再写死蓝色** rgba(37,99,235,.10)，改走 --c-gold-soft
     （已是名片页的银灰淡底 rgba(169,179,191,.18)）—— 写死的那一行是"换装换不干净"
     的典型：变量块换了，这一行仍旧是蓝的。 */
  background: var(--c-gold-soft);
  color: var(--c-primary);
}
/* v123（老板口径）：页签下的**横线指示器** —— 与主页 .top-tab-indicator::before 同一
   节奏（时长 260ms / 缓动 cubic-bezier(.22,.61,.36,1)，与 Swiper 的 SNAP_DURATION/
   SNAP_EASING 同源同值）。与主页唯一的差别是**驱动量不同**：主页 3 个页签等宽 ⇒
   用 --tab-count 均分 + --tab-pos 位移即可；本处 4 个页签文字长短不一（公众栏/微小店
   还带「未启用」角标）⇒ 左边界与宽度都由 JS 按页签**实测几何**写成 inline style
   （见 editor.js paintIndicator），故这里只给过渡与外观，不写任何几何。
   拖拽期 JS 给 .ed-tabs 加 is-tab-drag 关掉过渡 —— 指哪跟哪；落定后摘掉，
   过渡恢复，与内容轨道同步滑到目标页签。 */
.ed-tab-indicator {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 2.5px;
  border-radius: 2px;
  background: var(--c-primary);
  pointer-events: none;            /* 绝不吞页签点击 */
  transform: translateX(0);
  transition: transform 260ms cubic-bezier(.22, .61, .36, 1),
    width 260ms cubic-bezier(.22, .61, .36, 1);
}
.ed-tabs.is-tab-drag .ed-tab-indicator { transition: none; }
/* v123 首帧落位：面板刚入 DOM 时指示器的 CSS 初值是 width:0 / translateX(0)，带过渡写入
   目标几何会被当成「从最左滑到当前页签」的动画（打开面板能看到横线从左边缘溜过去）。
   editor.js 首次落位时临时挂上 is-tab-init 关掉过渡、写入后立即摘除 —— 首帧即正位。 */
.ed-tabs.is-tab-init .ed-tab-indicator { transition: none; }
/* 公众栏/微小店管理页签"未启用"角标（R4-11/R6-5：开关关闭时常驻显示）
 * T6-2 根因修复：本类的显式 display 会覆盖 [hidden] 的 UA 默认 display:none，
 * 导致启用后角标仍显示——必须显式声明 [hidden] 规则
 * v44 改过红点（.is-dot）；v61（需求 2，用户指令）**恢复「未启用」文字角标**
 * （v58 线索列表页签下线后只剩 4 页签，文字不再挤爆行）。.is-dot 规则随之移除；
 * 后台侧边栏 admin.js 的红点是独立类 .admin-nav-badge，不受影响。 */
.ed-tab-badge {
  display: inline-block;
  margin-left: 3px;
  padding: 0 5px;
  border-radius: 8px;
  /* v83（需求）：角标由「白半透明底 + 白字」改「浅灰底 + 深灰字」。 */
  background: var(--c-line-soft);
  font-size: 9px;
  letter-spacing: 0;
  vertical-align: 1px;
  color: var(--c-text-2);
}
.ed-tab-badge[hidden] { display: none; }

/* 微小店商品卡保存状态徽标（R6-M1：已保存 ✓ / 未保存） */
.mall-save-badge {
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  white-space: nowrap;
}
.mall-save-badge.is-saved {
  background: rgba(14, 110, 84, .12);
  color: #0E6E54;
}
.mall-save-badge.is-dirty {
  background: var(--c-gold-soft);
  color: #8A6D2F;
}

/* ============================================================
 * v124（需求 2）：微小店商品**折叠列表**（方案 A —— 折叠行只读 + 操作全在展开区）
 *   折叠行 .mi-head（整行可点）= 封面 48px（复用 .ed-thumb-sm）+ 商品名(1 行省略)
 *     + 价格 + 上下架状态点 + 保存徽标（沿用 .mall-save-badge）+ 展开箭头；
 *   展开区 .mi-body = 原有表单 + 上下架开关 + 操作行（保存本商品 · ↑ 上移 · ↓ 下移 · 删除）。
 *   ⚠ 关键约束：折叠**只切 .is-open 一个 class，不重建 DOM**（见 editor-pages.js 的
 *     applyOpenState）—— 所以「展开 A 再展开 B，A 里没保存的草稿仍在」，与 v112/v123
 *     「切走再切回草稿不丢」同口径。故此处的收起一律用 display 控制，绝不改结构。
 *   配色：绿/灰沿用 .mall-save-badge 同款字面量（:root 无 success 语义 token，
 *     与既有权衡保持一致，不新造变量）。
 * ============================================================ */
.mi-item {
  border: 1px solid var(--c-line);
  border-radius: 12px;
  background: var(--c-card);
  margin-bottom: 9px;
  overflow: hidden;
}
.mi-item.is-open { border-color: var(--c-gold); }

.mi-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 11px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.mi-main { flex: 1; min-width: 0; }
.mi-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--c-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mi-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
  font-size: 11.5px;
  color: var(--c-text-3);
  flex-wrap: nowrap;
  white-space: nowrap;
  overflow: hidden;
}
.mi-meta .price { flex: none; color: var(--c-price); font-weight: 800; font-size: 12.5px; }
.mi-arrow {
  flex: none;
  width: 22px;
  text-align: center;
  font-size: 11px;
  color: var(--c-text-3);
  transition: transform .2s;
}
.mi-item.is-open .mi-arrow { transform: rotate(180deg); }

/* 上下架状态点（折叠行；v124 需求 2 新增字段的只读呈现） */
.mi-meta .st {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: none;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .4px;
}
.mi-meta .st i {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  font-style: normal;
}
.mi-meta .st.on { color: #0E6E54; }
.mi-meta .st.on i { background: #0E6E54; }
.mi-meta .st.off { color: #8B95A1; }
.mi-meta .st.off i { background: #8B95A1; }

/* 展开区：收起时用 display 隐藏（DOM 常驻 ⇒ 草稿不丢；display 也不影响上传按钮的程序化触发） */
.mi-body { display: none; }
.mi-item.is-open .mi-body {
  display: block;
  padding: 11px;
  border-top: 1px dashed var(--c-line-soft);
}
/* 操作行（v125 需求 3.4）：保存本商品 + 保存状态徽标 */
.mi-ops {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 11px;
  flex-wrap: wrap;
}
/* v126（需求 3.1）：这两颗并到同一行后，字号 / 内边距不同会高低不齐 ——
   「保存本商品」是 .btn.btn-sm（9px 16px / 13px），「删除该商品」是 .ed-btn（7px 14px / 12px）。
   把这一行里的 .ed-btn 统一按 .btn-sm 的尺度对齐（只作用在 .mi-ops 内，不动通用 .ed-btn）。 */
.mi-ops .ed-btn { padding: 9px 16px; font-size: 13px; }

/* ============================================================
 * v125（老板 10-13 三项需求）界面样式
 *   ① 名片夹管理 7 栏折叠卡（.ed-fold*）
 *   ② 公众栏文章：预览/编辑双态 + 底部固定三按钮 + 列表卡上下架开关
 *   ③ 微小店商品：图视频混排素材带（.media-grid/.mcell/.mbox/.addm）+ 排列位置输入
 * 配色一律取既有 :root token；两处绿/灰沿用 .mall-save-badge 同款字面量
 *   （:root 无 success 语义 token，与该处既有权衡保持一致，不新造变量）。
 * ============================================================ */

/* ---------- ① 折叠卡（名片夹管理页 7 栏；与微小店折叠同一套开合语义） ---------- */
/* .ed-fold 就是那张卡本身，所以要把 .ed-section 自带的 padding 收掉，改由 head/sum/body 各自承担，
   否则标题与摘要会各多出 14px，折叠态两张卡的间距也会被撑开。
   ⚠ 选择器必须写成 `.ed-section.ed-fold`（0,2,0）而不是孤零零的 `.ed-fold`（0,1,0）：
     本段样式在文件里位于**基础规则之前**，而 `.ed-section{padding:14px 14px 16px;margin-bottom:14px}`
     在同权重下**后者胜出** —— 只写 `.ed-fold` 会被它整块盖掉（v125 自检实测：
     padding 没被收掉，折叠卡卡头与摘要都被多缩进 14px，与设计稿不符）。 */
.ed-section.ed-fold {
  padding: 0;
  border: 1px solid var(--c-line);
  border-radius: 12px;
  background: var(--c-card);
  margin-bottom: 9px;
  overflow: hidden;
  box-shadow: var(--shadow-soft);
}
.ed-fold .ed-sec-head {
  margin-bottom: 0;
  padding: 11px 12px 8px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ed-fold .ed-sec-head:focus-visible { outline: 2px solid var(--c-primary); outline-offset: -2px; }
.ed-fold.is-open { border-color: var(--c-gold); }
.ed-fold-sum {
  padding: 0 12px 11px;
  font-size: 11.5px;
  color: var(--c-text-3);
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ed-fold-arrow {
  flex: none;
  width: 20px;
  text-align: center;
  font-size: 11px;
  color: var(--c-text-3);
  transition: transform .2s;
}
.ed-fold.is-open .ed-fold-arrow { transform: rotate(180deg); }
/* 收起 = display:none（DOM 常驻 ⇒ 展开区里没保存的草稿切走再切回仍在，与 v124 微小店同口径） */
.ed-fold-body { display: none; border-top: 1px solid var(--c-line-soft); padding: 12px; }
.ed-fold.is-open .ed-fold-body { display: block; }
/* 「保存本区」按钮所在行（从卡头搬来，见 editor-kit.js foldSection 注记） */
.ed-fold-ops { display: flex; justify-content: flex-end; margin-top: 4px; }
.ed-fold-ops:empty { display: none; }

/* ---------- ② 文章编辑页：预览态排版（只读渲染，不是输入框） ---------- */
.art-preview-pane { padding: 2px 0 4px; }
.art-title { font-size: 17px; font-weight: 800; color: var(--c-title); line-height: 1.45; margin-bottom: 6px; }
.art-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11px; color: var(--c-text-3); margin-bottom: 10px;
}
.art-chip {
  padding: 1px 6px; border-radius: 999px;
  background: var(--c-primary-2); color: #fff;
  font-size: 10px; font-weight: 700;
}
.art-cover {
  display: block; width: 100%; height: 104px; object-fit: cover;
  border-radius: 10px; margin-bottom: 12px; background: var(--c-bg);
}
.art-summary {
  padding: 8px 10px; margin-bottom: 12px;
  border-left: 3px solid var(--c-gold);
  background: var(--c-bg); border-radius: 0 8px 8px 0;
  font-size: 12px; color: var(--c-text-3); line-height: 1.7;
}
.art-body { font-size: 12.5px; color: var(--c-text-2); line-height: 1.85; }
.art-body h4 { margin: 12px 0 6px; font-size: 13px; color: var(--c-title); }
.art-body p { margin: 0 0 8px; }
.art-body blockquote {
  margin: 8px 0; padding: 6px 10px;
  border-left: 3px solid var(--c-gold);
  background: var(--c-bg); color: var(--c-text-2); font-size: 12px;
}
.art-body-img {
  display: block; width: 100%; border-radius: 10px;
  margin: 8px 0; background: var(--c-bg);
}
.art-body-link { margin: 6px 0; color: var(--c-primary); font-size: 12px; }
.art-body-empty { color: var(--c-text-3); }

/* 文章编辑页底部三按钮条（退出 / 保存文章 / 编辑文章·暂缓编辑）——
   v126（需求 4）：曾去掉吸底定位、回到普通文档流（按钮接在正文末尾随文章一起滚）。
   v128（需求 1）：老板要求把这三颗按键**固定在手机屏幕底部**（页面滚动、按键钉在屏幕底部），
   本轮是同一处的**口径反转** —— 恢复 position: sticky 吸底常驻（见下方规则里的定位三行）。
   外观零变化（只改行为、不改长相）：铺满 .ed-item 内边距区的负外边距 + 贴卡片下圆角 + 上描边
   一律保留；底部 -12px 让这条尾栏延展到 .ed-item 的 12px 内边距区、与卡片下沿贴合。 */
.ed-footbar {
  display: flex; align-items: center; gap: 8px;
  position: sticky; bottom: 0; z-index: 5;   /* v128（需求 1）：按键钉在手机屏幕底部（吸底常驻） */
  margin: 12px -12px -12px;
  padding: 10px 12px calc(10px + var(--safe-bottom));
  background: var(--c-card);
  border-top: 1px solid var(--c-line);
  border-radius: 0 0 11px 11px;
}
.ed-footbar .btn { flex: 1; justify-content: center; padding: 10px 8px; font-size: 13px; }
.btn-out { background: #fff; border: 1px solid var(--c-primary); color: var(--c-primary-deep); }
/* 保存中转圈（设计稿：12px 白圈 + 顶边提亮） */
.spinner {
  display: inline-block; width: 12px; height: 12px;
  border: 2px solid rgba(255, 255, 255, .45); border-top-color: #fff;
  border-radius: 50%;
  animation: ed-spin .7s linear infinite;
}
@keyframes ed-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 1.6s; } }

/* 文章列表卡：上下架状态行 + 开关（点一下即切，不进编辑页） */
.art-list-status { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.art-list-hint { font-size: 10.5px; color: var(--c-text-3); }
.art-switch {
  flex: none;
  position: relative;
  width: 40px; height: 22px;
  margin-left: auto;                 /* 与状态/提示拉开，贴到卡片右侧 */
  padding: 0;
  border: 0; border-radius: 999px;
  background: #C6CCD4;
  cursor: pointer;
  transition: background .2s;
}
.art-switch::after {
  content: '';
  position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .2);
  transition: left .2s;
}
.art-switch.is-on { background: #0E6E54; }   /* 与 .mall-save-badge.is-saved 同款绿 */
.art-switch.is-on::after { left: 20px; }
.art-switch:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }

/* 上下架状态点：全局口径（文章预览头 / 文章列表卡 / 商品折叠行共用）
   ⚠ .mi-meta .st 的既有规则依旧更具体，不受影响。 */
.st { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 700; letter-spacing: .4px; }
.st i { display: inline-block; width: 6px; height: 6px; border-radius: 50%; font-style: normal; background: currentColor; }
.st.on { color: #0E6E54; }
.st.off { color: #8B95A1; }

/* ---------- ③ 商品素材带（图片 / 视频按添加顺序混排） ---------- */
.media-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.mcell { width: 74px; }
.mbox {
  position: relative;
  width: 74px; height: 74px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--c-line); border-radius: 10px;
  background: var(--c-bg);
  overflow: hidden;
}
.mbox.is-vid { background: linear-gradient(140deg, #C9D2DC, #9FAAB8); color: #31404F; }
.mbox-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.mbox-ph { font-size: 11px; font-weight: 600; color: #31404F; }
/* 首个素材 = 商品封面（图或视频皆可；视频用它的缩略图） */
.mcell-cover {
  position: absolute; left: 4px; top: 4px; z-index: 1;
  padding: 1px 5px; border-radius: 999px;
  background: var(--c-gold-soft); color: var(--c-primary-deep);
  font-size: 9px; font-weight: 700;
}
.mcell-play {
  position: absolute; right: 4px; top: 4px; z-index: 1;
  width: 16px; height: 16px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(31, 36, 43, .62); color: #fff; font-size: 9px;
}
.mcell-ops { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }
.mcell-btn {
  width: 100%;
  padding: 3px 0;
  border: 1px solid var(--c-line); border-radius: 7px;
  background: #fff; color: var(--c-text-2);
  font-family: inherit; font-size: 10px; font-weight: 600;
}
.mcell-btn.is-del { color: var(--c-danger); border-color: rgba(201, 67, 63, .35); }
.addm {
  width: 74px; height: 74px;
  border: 1px dashed var(--c-gold); border-radius: 10px;
  background: var(--c-gold-soft); color: var(--c-primary-deep);
  font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1.35;
}
.addm:disabled { opacity: .45; }

/* ---------- v126（需求 3.2）：素材长按拖动换位 ---------- */
/* ⚠ 起始元素必须关掉浏览器的触摸手势（touch-action: none）：否则手指一动浏览器就接管
   纵向滚动、并派发 pointercancel，拖动当场断掉（.ed-page 上是 pan-y，纵向必被接管）。
   代价是被拖那一格的图上**滑不动页面** —— 由 JS 在「长按尚未激活」时把纵向位移手动
   转成 .ed-page 的 scrollTop 补回来；格子间隙与卡片其它区域照常滚动。
   只加在 .mbox（图片/视频本体）上，不含 .mcell-ops（「删」按钮区照常可滚可点）。 */
.mcell .mbox {
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
.mbox-img { -webkit-user-drag: none; pointer-events: none; }
.mcell.is-dragging { position: relative; z-index: 5; }
.mcell.is-dragging .mbox {
  border-color: var(--c-gold);
  box-shadow: 0 8px 20px rgba(31, 36, 43, .22);
  transform: scale(1.06);
}
/* 拖动中手指所指的目标位：虚线框提示「松手就落在这里」 */
.mcell.is-drop .mbox { border-color: var(--c-gold); border-style: dashed; }

/* 排列位置输入（原 ↑↓ 两键，v125 需求 3.5） */
.mi-pos-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mi-pos-wrap .ed-input { width: 64px; text-align: center; font-weight: 700; }
.mi-pos-cap { flex: 1; min-width: 150px; font-size: 11px; color: var(--c-text-3); line-height: 1.5; }
/* v126（需求 3.1）：.mi-ops-foot（「删除该商品」独占一行）随该按钮并入 .mi-ops 一并下线 ——
   展开一张商品卡时不再多出一整行，卡片高度与底部留白同时收掉。 */

/* v60（需求 4）：原「分享面板粘贴引导行」.share-panel-note（R6-3）随新面板（微信同款）下线 ——
   面板内不再有链接文本行，引导语改由全屏浮层 .share-guide 承担。规则整体删除。 */

/* ---------- R9-1 分享落地页（share-landing.js；微信小程序内容分享卡片风） ---------- */
/* 全屏覆盖手机框；染色背景与名片夹 Hero 同源（主题变量，跟随主题切换） */
.share-landing-root {
  position: absolute;
  inset: 0;
  z-index: 70;                    /* 高于页签(50)/圆点(60)，低于弹层(200+) */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background:
    radial-gradient(420px 220px at 85% -10%, var(--c-hero-glow, rgba(201, 169, 97, .28)), transparent 62%),
    linear-gradient(160deg, var(--c-primary-2) 0%, var(--c-primary) 46%, var(--c-primary-deep) 100%);
}
.share-landing {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(var(--safe-top) + 16px) 22px calc(24px + var(--safe-bottom));
  box-sizing: border-box;
}
/* 上方留白区 + 提示词（用户主诉求：留白区写"点击查看全部内容"） */
.sl-space {
  flex: none;
  min-height: 28vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 16px 0 8px;
  color: #fff;
  text-align: center;
}
.sl-hint {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 2.5px;
  text-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.sl-hint-sub {
  font-size: 11px;
  color: rgba(255, 255, 255, .72);
  letter-spacing: 1.5px;
}
.sl-hint-arrow {
  width: 28px;
  height: 28px;
  margin-top: 4px;
  color: var(--c-gold-light, #E3C98F);
  opacity: .95;
  animation: sl-bob 2s ease-in-out infinite;
}
.sl-hint-arrow svg { width: 100%; height: 100%; }
@keyframes sl-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(8px); }
}
/* 名片精简卡（微信小程序内容分享卡片比例：~88% 宽、大圆角、重投影） */
.sl-card {
  width: 100%;
  max-width: 360px;
  background: var(--c-card, #fff);
  border-radius: 22px;
  box-shadow: 0 22px 56px rgba(6, 14, 30, .38);
  overflow: hidden;
  cursor: pointer;
  transition: transform .18s ease;
  text-align: left;
}
.sl-card:active { transform: scale(.985); }
/* 卡片头部：头像 + 姓名/职位/公司 */
.sl-card-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 22px 20px 18px;
  background: linear-gradient(180deg, rgba(201, 169, 97, .08) 0%, transparent 100%);
}
.sl-avatar {
  width: 72px;
  height: 72px;
  flex: none;
  border-radius: 50%;
  border: 3px solid var(--c-gold, #C9A961);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .14);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--c-bg);
}
.sl-avatar img { width: 100%; height: 100%; object-fit: cover; }
.sl-avatar.is-fallback {
  font-size: 28px;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(135deg, var(--c-gold) 0%, #9C7E3F 100%);
}
.sl-card-titles { min-width: 0; }
.sl-name {
  font-size: 20px;
  font-weight: 800;
  color: var(--c-text);
  letter-spacing: 1.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sl-title {
  margin-top: 5px;
  font-size: 12px;
  color: var(--c-text-2);
  letter-spacing: .5px;
  line-height: 1.5;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
/* 卡片主体：联系信息 + 个人介绍 */
.sl-card-body { padding: 0 20px 16px; }
.sl-contacts {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--c-line);
}
.sl-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--c-text-2);
  min-height: 18px;
}
.sl-row-ico {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--c-gold);
}
.sl-row-ico svg { width: 100%; height: 100%; }
.sl-row-label {
  flex: none;
  width: 32px;
  font-size: 11px;
  color: var(--c-text-3);
  letter-spacing: .5px;
}
.sl-row-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sl-row.is-wrap .sl-row-text { white-space: normal; line-height: 1.6; }
/* 个人介绍块（后台未填写不渲染） */
.sl-profile {
  padding-top: 14px;
}
.sl-profile-p {
  font-size: 12.5px;
  color: var(--c-text-2);
  line-height: 1.8;
  margin-bottom: 6px;
}
.sl-profile-p:last-child { margin-bottom: 0; }
/* 卡片底部：查看全部内容按钮 */
.sl-card-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 13px 20px;
  background: var(--c-gold-soft);
  color: var(--c-gold);
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: 2px;
}
.sl-more-ico {
  font-size: 16px;
  line-height: 1;
  margin-top: -1px;
}
.sl-brand {
  margin-top: 20px;
  font-size: 11px;
  color: rgba(255, 255, 255, .55);
  letter-spacing: 2px;
}
/* ---------- v134：分享落地页「取数失败」整页提示（share-landing.js renderShareLandingNotice）
 * 背景：带 ?view=intro&c=<slug> 打开但名片取不到时，旧代码继续用 defaultCardData()
 *   渲染 —— 访客看到的是一张写着「您的姓名 / 职位 / 公司名称」的空白模板，
 *   会以为对方敷衍地填了资料（v56 已在完整名片页修掉，落地页当时漏了）。
 * 现在改为如实告知，并按错误类别分流：云故障 → 「服务暂时不可用」+ 刷新页面；
 *   不存在/未公开/已下架 → v38/v51 原「该内容当前不可访问」口径。
 * 为什么单起一套类名而不复用 .mod-unavailable：那套是给手机框内（.phone-frame）
 *   绝对定位覆盖用的，落地页根 .share-landing-root 自带主题渐变 + 独立滚动，
 *   硬套会得到"深底上的白卡片"，与落地页视觉不搭。
 * ★ 配色全部走既有主题变量 + 落地页既有的半透明白（与 .sl-hint-sub 同族），
 *   未新增任何主题变量、未改 .sl-dual（240×46 居中）任何几何。 */
.sl-fail {
  /* .share-landing 已是 min-height:100% 的 flex 列；这里只补居中，
     让提示落在视觉重心而不是被 .sl-brand 的 margin 顶到下方 */
  justify-content: center;
}
.sl-fail-text {
  max-width: 280px;
  margin-top: 14px;
  font-size: 13px;
  line-height: 1.9;
  color: rgba(255, 255, 255, .86);
  text-align: center;
}
.sl-fail-acts {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}
.sl-retry-btn {
  min-width: 104px;
  padding: 10px 18px;
  font-size: 13px;
  font-weight: 700;
  color: var(--c-primary, #3B6BF5);
  background: var(--c-card, #fff);
  border: none;
  border-radius: 999px;
  box-shadow: 0 10px 26px rgba(6, 14, 30, .22);
  text-align: center;
  box-sizing: border-box;
  touch-action: manipulation;
}
.sl-retry-btn.is-sec { color: rgba(255, 255, 255, .92); }
.sl-retry-btn:active { transform: translateY(1px); }
/* v133（需求 2）：落地页双按键 .sl-dual —— 款 1「卡脚延伸」再窄化（老板 2026-10-16 拍板）。
 * 相对 v132 的变化：**总宽 360 → 240px（= 名片卡宽的 2/3，缩短 1/3）**、高 48 → 46px；
 * 位置贴合自然：240px 窄条**居中**于卡的正下方（与卡同一条中轴线，margin: 6px auto 0），
 *   上缘平直、下缘与卡同款 22px 圆角 —— 视觉上是卡「正下方伸出的一个小底座」，
 *   不再是通栏底栏；与卡之间仍只隔 6px 悬浮缝。
 * 键本身无边框无底色（主键主题色粗体 +「＋」，副键灰字），中间竖细线分隔，
 *   按下浮现淡金底（= 卡 foot 条的 gold-soft）。
 * 保留全部硬约束：flex:1 等宽 / touch-action:manipulation / 整枚键全热区 /
 *   零确认弹窗 / :active 即时反馈 / 副文案双态（.is-signed-in）。
 * ★ 全部颜色走主题 CSS 变量（--c-primary/--c-card/--c-line/--c-text-2/--c-gold-soft），
 *   禁止任何 # 硬编码色值；替代 v30 单按钮 .sl-cta（样式块已随 v132 移除）。 */
.sl-dual {
  display: flex;
  width: 240px;
  margin: 6px auto 0;
  background: var(--c-card, #fff);
  border-radius: 0 0 22px 22px;
  box-shadow: 0 14px 32px rgba(6, 14, 30, .20);
  overflow: hidden;
}
.sl-dual-btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 46px;
  border: none;
  background: none;
  font-family: inherit;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 3px 6px;
  cursor: pointer;
  transition: background .15s ease;
  touch-action: manipulation;
}
.sl-dual-btn:active { background: var(--c-gold-soft); }
.sl-dual-btn.is-pri { color: var(--c-primary); }
.sl-dual-btn.is-sec { color: var(--c-text-2); border-left: 1px solid var(--c-line); }
.sl-dual-btn b {
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .6px;
  display: flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.sl-dual-btn .dual-plus { font-size: 13px; font-weight: 800; }
.sl-dual-btn i {
  font-style: normal;
  font-size: 9.5px;
  letter-spacing: .2px;
  opacity: .62;
  white-space: nowrap;
}
/* 副文案双份：默认未登录版；.is-signed-in 切已登录版（main.js 落地页分支传入，
   与名片页 .hero-dual 的同一套机制） */
.sl-dual-btn .v-user { display: none; }
.sl-dual.is-signed-in .v-guest { display: none; }
.sl-dual.is-signed-in .v-user { display: inline; }
/* v24：分享落地页原「制作我自己的名片」注册 CTA 随账号体系下线；
 * v30（第 5 步）：获客入口回归为单按钮 .sl-cta（其样式块随 v132 需求 2 一并移除，
 * DOM 由 js/pages/share-landing.js 改产出 .sl-dual 双按键）。 */

/* v18 起顶部用户状态条（.user-bar / .user-name / .user-actions / .user-link /
 * .phone-frame.has-userbar 让位规则）整体下线——前端不再渲染任何顶部管理入口。
 * v24 清理残留死样式（原 R10 平台化段落，规则已无任何 DOM 消费者）。
 */

/* v24：原「我也要制作名片」访客浮球（.guest-create-btn，指向 auth.html
 * 注册漏斗）随账号体系整体下线，样式规则移除。 */

/* 管理页容器（v123）：DOM 常驻 + **横向轨道位移**切页（与 .page 逐字同构）。
   ⚠ 不再 display 切换 —— 滑动手势要求相邻页**同时**留在渲染树里，否则滑过去是空白，
   也就谈不上「所有子页内容提前渲染好、滑动中零请求」。is-active 保留为**语义标记**
   （探针/样式仍按它认"当前页"），不再控制显隐。 */
.ed-page {
  flex: 0 0 25%;                   /* ADMIN_PAGES 恒 4 页 */
  width: 25%;
  height: 100%;
  overflow-y: auto;                /* 纵向滚动从 .ed-body 下移到每一页自己 */
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;             /* 纵向滚动交给浏览器，横滑交给 Swiper */
  overscroll-behavior-y: contain;
  padding: 14px 14px calc(12px + var(--safe-bottom));
  /* v126（需求 1）：页底内边距由 20px 收到 12px —— 底栏「完成并关闭」下线后，
     页面底部只剩这一处留白，收掉老板说的那段"删键后留下的空"。
     （.ed-body 是 flex:1 + overflow:hidden，少一条 flex:none 的底栏后内容区
       自动吃掉那段高度，无需在此另外补偿。） */
}
/* v128（需求 3）：文章编辑态 —— 编辑区向上覆盖「公众栏/微小店展示开关」分区，
   编辑从页顶铺开、不再被上方分区挤成一小段；退出/暂存后类被摘掉，原样恢复。
   只隐藏开关分区本身，文章卡与其编辑区零结构改动。 */
.ed-page.is-art-editing .is-page-toggle { display: none; }
/* 管理页顶部"对应展示页"色带（U9：名片金 / 公众栏蓝 / 微小店主题渐变 / 通用灰） */
.ed-page-band { height: 4px; border-radius: 2px; margin-bottom: 12px; }
.ed-page-band.band-card { background: linear-gradient(90deg, var(--c-gold-light), var(--c-gold)); }
.ed-page-band.band-mp { background: linear-gradient(90deg, var(--c-primary-2), var(--c-primary)); }
.ed-page-band.band-mall { background: linear-gradient(135deg, var(--c-gold), var(--c-primary-2)); }
.ed-page-band.band-common { background: var(--c-line); }
/* ============================================================
 * v34 第7步：商家后台「线索列表」样式（layout.css 唯一定义处——
 *   桌面 admin.html 与手机端 index.html 管理模式都必须加载到，故不放 admin.css）
 * ============================================================ */
.leads-head-btns { display: flex; gap: 8px; flex: none; }
.leads-stat { margin: 2px 0 10px; font-size: 12.5px; color: var(--c-text-3); }
.leads-list { display: flex; flex-direction: column; gap: 10px; }
.leads-loading,
.leads-empty {
  padding: 22px 14px;
  border-radius: 12px;
  background: var(--c-bg);
  border: 1px dashed var(--c-line);
  text-align: center;
  font-size: 13px;
  color: var(--c-text-2);
}
.leads-empty-sub { margin-top: 6px; font-size: 11.5px; color: var(--c-text-3); line-height: 1.7; }
.lead-row {
  padding: 12px 13px 10px;
  border-radius: 12px;
  background: var(--c-card);
  border: 1px solid var(--c-line);
}
/* 未读：金色左条 + 商品名加粗 + 红色「未读」胶囊（三处同时提示，色弱也可辨） */
.lead-row.is-unread {
  border-color: var(--c-gold);
  box-shadow: inset 3px 0 0 var(--c-gold);
}
.lead-row.is-unread .lead-product { font-weight: 800; }
.lead-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lead-product { font-size: 14px; font-weight: 600; color: var(--c-text); }
.lead-dot {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--c-danger);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .5px;
}
.lead-time { margin-left: auto; flex: none; font-size: 11.5px; color: var(--c-text-3); }
.lead-phone { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 7px; }
.lead-phone-val { font-size: 15px; font-weight: 700; color: var(--c-text); letter-spacing: .5px; }
.lead-act {
  display: inline-block;
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid var(--c-line);
  background: var(--c-bg);
  color: var(--c-text-2);
  font-size: 11.5px;
  line-height: 1.6;
  text-decoration: none;
}
.lead-act:active { border-color: var(--c-gold); }
.lead-msg {
  margin-top: 7px;
  padding: 8px 10px;
  border-radius: 9px;
  background: var(--c-bg);
  font-size: 12.5px;
  color: var(--c-text-2);
  line-height: 1.75;
  white-space: pre-wrap;
  word-break: break-word;
}
.lead-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 9px; }

/* ============================================================
 * v37：后台「存储与媒体」+「危险操作」样式（layout.css 唯一定义处——
 *   桌面 admin.html 与手机端 index.html 管理模式都要加载，故不放 admin.css）
 * ============================================================ */
.media-head-btns { display: flex; gap: 8px; flex: none; }
.media-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
  overflow: hidden;
}
.media-bar-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--c-primary-2), var(--c-primary));
  transition: width .25s ease;
}
/* 80% 起转金、100% 转红：颜色先于数字被注意到 */
.media-bar-fill.is-warn { background: linear-gradient(90deg, var(--c-gold-light), var(--c-gold)); }
.media-bar-fill.is-full { background: linear-gradient(90deg, var(--c-gold), var(--c-danger)); }
.media-stat { margin: 8px 0 10px; font-size: 12.5px; color: var(--c-text-2); }
.media-list { display: flex; flex-direction: column; gap: 10px; }
.media-loading,
.media-empty {
  padding: 22px 14px;
  border-radius: 12px;
  background: var(--c-bg);
  border: 1px dashed var(--c-line);
  text-align: center;
  font-size: 13px;
  color: var(--c-text-2);
}
.media-empty-sub { margin-top: 6px; font-size: 11.5px; color: var(--c-text-3); line-height: 1.7; }
.media-row {
  padding: 11px 12px 9px;
  border-radius: 12px;
  background: var(--c-card);
  border: 1px solid var(--c-line);
}
/* 未引用：灰色虚线边 + 名称降权，一眼看出「这些可以清」 */
.media-row.is-unused { border-style: dashed; background: var(--c-bg); }
.media-row.is-unused .media-name { color: var(--c-text-2); }
.media-top { display: flex; align-items: center; gap: 8px; }
.media-name {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.media-tag {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .5px;
}
/* v112（需求 4）：素材库「在用」小标签的底色原本写死深蓝 rgba(15,43,91,.1)，
   是旧「藏蓝金」主题的遗留色 —— 它出现在管理模式的素材页里，属于管理界面，
   故一并换成主色淡底（与 --c-primary 同族），保持「在用 / 未引用」两态可区分。 */
.media-tag.is-in { background: rgba(92, 103, 115, .10); color: var(--c-primary); }
.media-tag.is-out { background: var(--c-gold-soft); color: var(--c-text-2); }
.media-meta {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--c-text-3);
}
.media-foot { display: flex; justify-content: flex-end; margin-top: 8px; }

.danger-note {
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
  font-size: 12px;
  line-height: 1.8;
  color: var(--c-text-2);
}
.danger-agree {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 10px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--c-text);
}
.danger-agree input { flex: none; margin-top: 2px; }
.danger-actions { display: flex; justify-content: flex-end; margin-top: 10px; }
/* v123（老板口径）：`.ed-body` 由「纵向滚动容器」改为**横向视口**（与 .pages 同构）——
   纵向滚动下移到每个 .ed-page 自己身上，横滑交给 Swiper 的轨道 transform。
   原来的 padding 14px 也一并下移到 .ed-page（滚动容器的 padding 会额外叠加到内部
   sticky 元素的吸附位，见上方 v47 教训；滚动容器归零、留白交给页更安全）。 */
.ed-body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  touch-action: pan-y;
}
/* 管理页轨道（v123）：与 .pages-track 同款同缓动同位移模型（Swiper 直接写 transform）。 */
.ed-track {
  display: flex;
  width: 400%;
  height: 100%;
  transform: translateX(0);
  transition: transform 260ms cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}
.ed-track.is-dragging { transition: none; }
/* v126（需求 1）：.ed-foot（管理壳底部「完成并关闭」条）随该按钮一并下线 ——
   外壳节点已不存在（见 js/pages/editor.js panelEl）。桌面端从来不吃这条
   （admin.css 自带一条 .admin-main .admin-section .ed-foot{display:none} 的防御规则），
   所以删除对桌面端零影响。 */

.ed-section {
  background: #fff;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 14px 14px 16px;
  margin-bottom: 14px;
}
.ed-sec-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.ed-sec-title {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--c-primary);
  letter-spacing: 1px;
}
.ed-sec-title::before {
  content: '';
  width: 4px;
  height: 14px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--c-gold-light), var(--c-gold));
}
.ed-sec-tag { font-size: 10px; color: var(--c-text-3); letter-spacing: 2px; }

.ed-field { display: block; margin-bottom: 12px; }
.ed-field:last-child { margin-bottom: 0; }
.ed-label {
  display: block;
  font-size: 12px;
  color: var(--c-text-2);
  margin-bottom: 5px;
  letter-spacing: 1px;
}
.ed-input, .ed-textarea {
  width: 100%;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--c-line);
  background: var(--c-bg);
  font-size: 13.5px;
  color: var(--c-text);
  outline: none;
  transition: border-color .2s ease;
}
.ed-input:focus, .ed-textarea:focus { border-color: var(--c-gold); }
.ed-textarea { min-height: 72px; resize: vertical; line-height: 1.7; }
.ed-hint { margin: 6px 0 10px; font-size: 11px; color: var(--c-text-3); line-height: 1.7; }
.ed-media-note { font-size: 11px; color: var(--c-text-3); line-height: 1.7; }

.ed-btn {
  padding: 7px 14px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--c-line);
  background: #fff;
  font-size: 12px;
  font-weight: 600;
  color: var(--c-primary);
  white-space: nowrap;
  transition: border-color .2s ease, opacity .2s ease;
}
.ed-btn:disabled { opacity: .5; }
.ed-btn:active { border-color: var(--c-gold); }
.ed-btn-danger { color: var(--c-danger); border-color: rgba(220, 38, 38, .35); }
.ed-btn-add {
  display: block;
  width: 100%;
  padding: 10px;
  margin-top: 4px;
  border-radius: 12px;
  border: 1px dashed var(--c-gold);
  color: var(--c-gold);
  font-size: 13px;
  font-weight: 600;
  background: var(--c-gold-soft);
}
.ed-btn-add:disabled { opacity: .45; }

.ed-row { display: flex; gap: 10px; align-items: flex-start; }
.ed-row-main { flex: 1; min-width: 0; }
.ed-row-ops { flex: none; display: flex; flex-direction: column; gap: 6px; }

.ed-item {
  border: 1px solid var(--c-line);
  border-radius: 12px;
  padding: 12px;
  margin-bottom: 10px;
  background: #fff;
}
.ed-item:last-child { margin-bottom: 0; }

.ed-thumb {
  width: 64px;
  height: 64px;
  flex: none;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--c-line);
  background: var(--c-bg);
}
.ed-thumb-sm { width: 48px; height: 48px; border-radius: 8px; }

.ed-avatar {
  width: 60px;
  height: 60px;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--c-gold);
  background: var(--c-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 800;
  color: var(--c-gold);
  overflow: hidden;
}
.ed-avatar.is-fallback {
  background: linear-gradient(135deg, var(--c-gold-soft), rgba(201, 169, 97, .06));
}

/* v50：宿主承载层 —— div.ed-avatar 内的预览图铺满（原先 <img> 直接当宿主，
   空元素无法承载覆盖 input）；v96：原缩略图外层锚点死样式已随清理移除 */
.ed-avatar > img {
  display: block;
  flex: none;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

.ed-check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--c-text-2);
}

/* ============================================================
 * v39 需求②：媒体预览可点更换 + 上传无响应兜底通道
 * 背景（真机取证）：头像 60×60、cursor:auto、无 role/title —— 用户直接点头像
 * 必然毫无反应；另外被沙箱 iframe 包裹的预览环境会静默拦掉程序化 input.click()，
 * 因此补一条"用户真实点击"的兜底选择通道。
 * ============================================================ */
.ed-clickable-media {
  cursor: pointer;
  transition: box-shadow .15s ease, transform .15s ease;
}
.ed-clickable-media:hover {
  box-shadow: 0 0 0 3px rgba(201, 169, 97, .30);
}
.ed-clickable-media:focus-visible {
  outline: 2px solid var(--c-primary);
  outline-offset: 2px;
}
.ed-clickable-media.is-uploading {
  opacity: .55;
  pointer-events: none;
}

/* v50（用户需求 2）：上传控件的**铺满式透明真 file input**（命中区域 = 整个宿主）。
   取代 v39 的「隐藏 1px input + 程序化 click + 4s 兜底行」三件套——用户明确不要兜底，
   任何路径都不再出现「选择器没弹出来？请点这里选择文件」提示；且手指直接落在真
   input 上，由浏览器**原生**唤起系统选择器（部分安卓 WebView / 微信 X5 会静默拦截
   对视觉隐藏 input 的程序化 click()，这是"点几次才灵"的根因）。 */
.ed-file-cover {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-size: 0;
  touch-action: manipulation;
}
/* 宿主禁用（上传中）时，覆盖 input 不得继续抢事件 */
.ed-btn[disabled] .ed-file-cover,
.ed-clickable-media.is-uploading .ed-file-cover,
.ed-btn-add[disabled] .ed-file-cover {
  pointer-events: none;
}
/* 按压反馈：触点落在覆盖 input 上，宿主自身收不到 :active，用 is-picking 补一个态 */
.ed-btn.is-picking,
.ed-btn-add.is-picking,
.ed-clickable-media.is-picking {
  opacity: .85;
}
.ed-check-row input[type="checkbox"] {
  width: 15px;
  height: 15px;
  accent-color: var(--c-gold);
}

.ed-stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 12px;
}
.ed-stat-cell {
  background: var(--c-bg);
  border-radius: 12px;
  padding: 12px 10px;
  text-align: center;
}
.ed-stat-cell .ed-input { text-align: center; padding: 8px 6px; margin-bottom: 4px; }
.ed-stat-num { font-size: 22px; font-weight: 800; color: var(--c-primary); }
.ed-stat-label { font-size: 11px; color: var(--c-text-2); letter-spacing: 1px; }

/* v24：密码保护门（.lock-gate 全族）随后台密码功能整体下线，样式区块与
 * z-index 层级表条目一并移除（进入管理模式不再需要任何密码）。 */

/* 5.6 编辑器：主题外观选择卡（editor.js buildThemeSection） */
.theme-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.theme-card {
  position: relative;
  padding: 12px;
  border-radius: 12px;
  border: 2px solid var(--c-line);
  background: #fff;
  text-align: center;
  transition: border-color .2s ease, background .2s ease;
}
.theme-card.is-active { border-color: var(--c-gold); background: var(--c-gold-soft); }
.theme-dots { display: flex; justify-content: center; gap: 6px; }
.theme-dot { width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(0, 0, 0, .08); }
.theme-name { margin-top: 8px; font-size: 12px; font-weight: 700; color: var(--c-text); letter-spacing: 1px; }
.theme-check { position: absolute; top: 6px; right: 8px; font-size: 12px; color: var(--c-gold); font-weight: 800; }

/* 5.7 多媒体图集灯箱（gallery.js，R4-08；z-index 800 见文末层级表；
 * 手势策略 D-8：仅按钮+键盘切换；stage 绝对定位避开顶部关闭钮与底部 foot） */
.gallery {
  position: absolute;
  inset: 0;
  background: rgba(8, 10, 16, .93);
  animation: ui-fade-in .2s ease;
}
.gallery-close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(255, 255, 255, .12);
  color: #fff;
  font-size: 18px;
  line-height: 1;
  padding: 0 0 2px;
}
.gallery-close:active { background: rgba(255, 255, 255, .26); }
.gallery-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .2);
  background: rgba(255, 255, 255, .12);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  padding: 0 0 3px;
}
.gallery-nav:active { background: rgba(255, 255, 255, .26); }
.gallery-nav[hidden] { display: none; }
.gallery-prev { left: 12px; }
.gallery-next { right: 12px; }
.gallery-stage {
  position: absolute;
  top: 64px;
  left: 52px;
  right: 52px;
  bottom: 104px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gallery-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 4px;
  box-shadow: 0 12px 48px rgba(0, 0, 0, .5);
}
.gallery-video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
  display: block;
}
/* v72（需求 2）：播放框按**视频自身宽高比**定形（宽高由 JS 显式算成等比矩形），
   视频内容用 object-fit:contain 填满该框 —— 竖拍就看竖的、横拍就看横的，
   不再出现"先被拉长、播放后再跳回原比例"的两段观感。
   历史（v71 及以前）：这里是一张 width/height 均为 100% 的占位卡，直接铺满舞台
   （390×844 屏上舞台约 286×676），竖拍视频首帧观感即被"拉长"。 */
.gallery-video-box {
  position: relative;
  border-radius: 4px;
  background: #000;
  overflow: hidden;
  box-shadow: 0 12px 48px rgba(0, 0, 0, .5);
}
.gallery-video-poster {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* 比例未探明时的中性加载态：只有文字，没有任何矩形（避免出现与视频无关的"拉长"播放屏） */
.gallery-video-loading {
  color: rgba(255, 255, 255, .72);
  font-size: 13px;
  letter-spacing: .5px;
}
/* 播放占位按钮（叠在按比例定形的播放框里） */
.gallery-video-play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 68px;
  height: 68px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .85);
  background: rgba(15, 43, 91, .6);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform .2s ease, background .2s ease;
}
.gallery-video-play svg { width: 28px; height: 28px; margin-left: 3px; }
/* v72：按下反馈必须带上定位用的 translate(-50%,-50%)，否则按下瞬间按钮会跳位 */
.gallery-video-play:active { transform: translate(-50%, -50%) scale(.92); background: rgba(201, 169, 97, .75); }
/* 底部：序号 + 说明（pointer-events none 不挡遮罩点击关闭） */
.gallery-foot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(18px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0 60px;
  text-align: center;
  pointer-events: none;
}
.gallery-counter { font-size: 12px; letter-spacing: 2px; color: rgba(255, 255, 255, .7); }
.gallery-caption {
  max-width: 100%;
  font-size: 13px;
  line-height: 1.6;
  color: rgba(255, 255, 255, .88);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gallery-caption[hidden] { display: none; }

/* ---------- 5b. v17：交换名片抽屉（F1，复用询价抽屉交互模式，层级表 600 档） ---------- */
.lead-sheet {
  position: fixed;
  inset: 0;
  background: rgba(8, 15, 30, .55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  opacity: 0;
  transition: opacity .25s ease;
}
.lead-sheet.is-open { opacity: 1; }
.lead-panel {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: 82vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--c-card);
  border-radius: 18px 18px 0 0;
  padding: 8px 20px calc(20px + var(--safe-bottom));
  transform: translateY(100%);
  transition: transform .28s cubic-bezier(.22, .8, .32, 1);
}
.lead-panel.is-open { transform: translateY(0); }
.lead-title { margin-top: 10px; font-size: 17px; font-weight: 800; color: var(--c-text); letter-spacing: 1px; }

/* ---------- 5c. v17-M1：编辑器页签内锚点目录 chips ---------- */
.ed-anchor-bar {
  display: flex;
  gap: 8px;
  padding: 2px 2px 12px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.ed-anchor-bar::-webkit-scrollbar { display: none; }
.ed-anchor-chip {
  flex: none;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--c-line);
  background: var(--c-card);
  color: var(--c-text-2);
  font-size: 12px;
  white-space: nowrap;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.ed-anchor-chip.is-active {
  background: var(--c-gold-soft);
  border-color: var(--c-gold);
  color: var(--c-text);
  font-weight: 700;
}
.ed-anchor-chip:active { transform: scale(.96); }

/* ---------- 5d. v17-M3：分区卡头保存态徽标（未改/未保存/已保存 ✓） ---------- */
.sec-save-badge {
  flex: none;
  align-self: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  white-space: nowrap;
  background: var(--c-bg);
  color: var(--c-text-3);
  border: 1px solid var(--c-line);
}
.sec-save-badge.is-saved {
  background: rgba(14, 110, 84, .12);
  color: #0E6E54;
  border-color: transparent;
}
.sec-save-badge.is-dirty {
  background: var(--c-gold-soft);
  color: #8A6D2F;
  border-color: transparent;
}

/* ---------- 5d-2. v127：展示开关卡头的**上架状态图标提示语**（● 已上架 / ○ 未上架） ----------
   老板需求 2：「按键左边的图标改为以上架和未上架的图标提示语」。
   它是 .sec-save-badge 的**替身**（同一位置、同一段位），但不共用类名 ——
   保存态徽标的文案逻辑由 secCard 的 paintBadge 统一驱动（未改/未保存/已保存 ✓），
   上架状态是另一套语义、由 buildPageToggle 的 paint() 驱动，混用会让两边互相打架。
   形状与 .sec-save-badge 对齐：flex:none + 胶囊 + 11px/700；差别只在
   「有圆点」和「两态都用状态色」。 */
.sec-up-badge {
  flex: none;
  align-self: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  white-space: nowrap;
  border: 1px solid transparent;
}
.sec-up-badge.is-on { background: rgba(14, 110, 84, .12); color: #0E6E54; }
.sec-up-badge.is-off { background: var(--c-bg); color: var(--c-text-3); border-color: var(--c-line); }

/* ---------- 5e. v31 第6步：我的名片夹抽屉（复用 .lead-sheet 底座，层级表 600 档） ---------- */
/* 抽屉容器/面板/标题样式全部复用 .lead-sheet/.lead-panel/.lead-title（5b 节），
   此处仅定义名片夹自身组件；禁 backdrop-filter（v18 性能铁律）。 */
.holder-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.holder-count {
  margin-top: 4px;
  font-size: 12px;
  color: var(--c-text-3);
}
.holder-close {
  flex: none;
  width: 32px;
  height: 32px;
  border: 1px solid var(--c-line);
  border-radius: 50%;
  background: var(--c-bg);
  color: var(--c-text-2);
  font-size: 20px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.holder-close:active { transform: scale(.94); }
.holder-search {
  margin-top: 12px;
  position: relative;
}
.holder-search-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 14px;
  border: 1px solid var(--c-line);
  border-radius: 12px;
  background: var(--c-bg);
  color: var(--c-text);
  font-size: 14px;
  outline: none;
}
.holder-search-input:focus { border-color: var(--c-gold); }
.holder-search-input::placeholder { color: var(--c-text-3); }
.holder-list {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
}
.holder-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  box-sizing: border-box;
  padding: 12px 4px;
  border: none;
  border-bottom: 1px solid var(--c-line);
  background: transparent;
  text-align: left;
  cursor: pointer;
}
.holder-row:active { background: var(--c-bg); }
.holder-row[hidden] { display: none; }
.holder-avatar {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--c-line);
}
.holder-avatar img { width: 100%; height: 100%; object-fit: cover; }
.holder-avatar.is-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(135deg, var(--c-primary), var(--c-primary-deep));
}
.holder-row-main { flex: 1; min-width: 0; }
.holder-row-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.holder-row-sub {
  margin-top: 2px;
  font-size: 12px;
  color: var(--c-text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.holder-row-slug {
  margin-top: 2px;
  font-size: 11px;
  color: var(--c-text-3);
}
/* v59（需求 4）：名片号 +「删除」同一行（不给右侧竖排加第 4 件，避免行高被撑开） */
.holder-row-meta {
  margin-top: 3px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.holder-row-meta .holder-row-slug { margin-top: 0; }
.holder-del {
  flex: none;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid #F0D3D3;
  background: #FDF3F3;
  color: #C05050;
  font-size: 11px;
  line-height: 1.6;
  cursor: pointer;
}
.holder-del:active { background: #F8E4E4; }
.holder-row-side {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 3px;
}
.holder-badge {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  background: rgba(14, 110, 84, .12);
  color: #0E6E54;
}
.holder-row-date { font-size: 10px; color: var(--c-text-3); }
.holder-row-go { font-size: 16px; color: var(--c-text-3); line-height: 1; }
.holder-empty { padding: 34px 0 26px; text-align: center; }
.holder-empty.is-hidden { display: none; }
.holder-empty-ico { font-size: 34px; }
.holder-empty-title { margin-top: 10px; font-size: 14px; font-weight: 700; color: var(--c-text-2); }
.holder-empty-sub { margin-top: 6px; font-size: 12px; color: var(--c-text-3); line-height: 1.6; padding: 0 24px; }

/* ---------- 5f. v38：多设备编辑冲突（conflict.js）+ 平台下架态（moderation.js） ----------
   层级：650 —— 高于 .ed-panel(500) 与抽屉(600)，低于 .confirm-dialog(700) 与 Toast(900)。
   这样冲突面板能盖住编辑器，而它自己弹的确认框/提示仍在最上面。
   挂载点：#overlayRoot（.phone-frame 是 position:relative 且无 transform，
   故面板用 fixed 即相对视口铺满，不会被手机框裁掉）。 */
.conflict-panel {
  position: fixed;
  inset: 0;
  z-index: 650;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(15, 24, 40, .55);
}
.conflict-box {
  width: min(440px, 100%);
  max-height: 86vh;
  overflow: auto;
  padding: 24px 22px 18px;
  background: var(--c-card);
  border-radius: 16px;
  box-shadow: 0 18px 48px rgba(15, 24, 40, .28);
  text-align: left;
}
.conflict-ico { font-size: 30px; line-height: 1; }
.conflict-title {
  margin-top: 10px;
  font-size: 16.5px;
  font-weight: 700;
  color: var(--c-text);
}
.conflict-desc {
  margin: 9px 0 0;
  font-size: 13px;
  line-height: 1.75;
  color: var(--c-text-2);
}
.conflict-desc strong { color: #B42318; }
.conflict-actions {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.conflict-actions .btn { width: 100%; justify-content: center; }
.conflict-sub {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding-left: 6px;
  border-left: 2px solid var(--c-border, #e5e7eb);
}
.conflict-foot {
  margin-top: 14px;
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--c-text-3);
}

/* 提前提示条（远端有更新但尚未冲突）：非阻断，可收起 */
.conflict-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 650;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: #FFF6E5;
  border-bottom: 1px solid #F0D9A8;
  box-shadow: 0 4px 14px rgba(15, 24, 40, .08);
}
.conflict-bar-text { font-size: 12.5px; color: #8A5A00; line-height: 1.6; }
.conflict-bar-acts { display: flex; gap: 8px; flex: none; }
.conflict-bar-btn { padding: 6px 12px; font-size: 12.5px; }

/* 商家侧「被平台下架」只读提示条（不给恢复入口） */
.mod-notice {
  margin: 0 0 16px;
  padding: 14px 16px;
  background: #FDF1F1;
  border: 1px solid #F3C9C9;
  border-left: 4px solid #C0392B;
  border-radius: 10px;
}
.mod-notice-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14.5px;
  font-weight: 700;
  color: #9B1C1C;
}
.mod-notice-ico { font-size: 16px; line-height: 1; }
.mod-notice-body { margin-top: 8px; }
.mod-notice-row {
  font-size: 13px;
  line-height: 1.8;
  color: #7A2B2B;
}
.mod-notice-row b { color: #9B1C1C; font-weight: 700; }
.mod-notice-tip {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.75;
  color: var(--c-text-3);
}

/* 访客侧「该名片当前不可访问」整页提示（替代原先的静默跳首页） */
.mod-unavailable {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 36px 26px;
  box-sizing: border-box;
  text-align: center;
  background: var(--c-bg);
}
.mod-unavailable-ico { font-size: 42px; line-height: 1; }
.mod-unavailable-title { font-size: 17px; font-weight: 700; color: var(--c-text); }
.mod-unavailable-text {
  max-width: 260px;
  font-size: 13px;
  line-height: 1.85;
  color: var(--c-text-2);
}
/* v134：云故障期兜底（buildServiceDownNotice）的双按键行 —— 「刷新页面」+「返回首页」。
   为什么单列一行而不是让两个 .btn 直接并排挂在 .mod-unavailable 里：那个容器是
   flex-direction: column 且 align-items: center，直接并列会被拉伸成两行整宽按钮；
   显式收成一行更符合「主操作 + 次操作」的视觉层级。gap/字号沿用既有 .btn 口径，
   未新增任何主题变量（配色仍全部走 --c-*）。 */
.mod-unavailable-acts { display: flex; gap: 10px; margin-top: 2px; }
.mod-unavailable-acts .btn { min-width: 104px; }

/* ---------- v134：旧/失效地址提示条（index.html #staleHostBar） ----------
 * 为什么复用 .conflict-bar 的视觉语汇而不是新造一套：那一套已经过 QA 实测
 * （顶部固定条 + 金色系 + 主/次双键 + 阴影），与本应用既有浮层一致；
 * 这里只复用「形态」，另起 .stale-host-* 类名以免和冲突提醒条的
 * DOM 契约（.conflict-bar-text / -acts / -btn）互相牵连。
 * 高度与层级：z-index 900（与 Toast 同档，低于弹层 700 以上者不受影响）——
 * 它必须在分享面板/灯箱等浮层**之上**，否则用户点旧链接进来又被浮层盖住看不见。 */
.stale-host-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: #FFF6E5;
  border-bottom: 1px solid #F0D9A8;
  box-shadow: 0 4px 14px rgba(15, 24, 40, .08);
}
.stale-host-bar[hidden] { display: none; }
.stale-host-text { font-size: 12.5px; color: #8A5A00; line-height: 1.6; }
.stale-host-acts { display: flex; gap: 8px; flex: none; }
.stale-host-acts .btn { padding: 6px 12px; font-size: 12.5px; }

/* ---------- 6. z-index 层级表（架构 §8.3，全项目唯一定义处） ---------- */
/* 5    页内快捷操作条（v135：已取消 sticky，退回普通流内元素，随卡滚走） */
.card-quickbar { z-index: 5; }
/* 6    v136 共享卡片层 .cardlayer（轨道外 flex 流内元素 + 自己的滚动上下文）
   ⚠ 档位推导（唯一定义处，改这里前先看这条）：
     卡片层在**文档流里**（flex:none 占高度），而页面内容在 .pages-track 内的
     .page 里 —— 二者是「上/下」排列而非重叠，故卡片层**不需要**高过任何浮层：
     给低档（6）即可，滚动上来的页内内容自然从它下方经过。
     ⚠ 但必须**高于 .card-quickbar 的 5**：卡片层收起时若低于 5，
       快捷条会从它上方穿过（视觉上是"内容穿过卡片"）。
     ⚠ 不给 30/32 那些档：那些是覆盖层（脱离文档流），卡片层不是；
       把流内元素提到覆盖层档位会掩盖真实的层叠问题。 */
.cardlayer { z-index: 6; }
/* 5    文章详情页内吸顶返回栏（sticky，属于 mp-detail 内部，见 mp.css） */
.art-topbar { z-index: 5; }
/* 32   v135 顶栏 .topbar（轨道外 absolute 覆盖层）
   ⚠ 档位来历：v140 需求① 前必须高于线索返回条 .lh-back 的 31（leads-hub.css，
   返回条吸顶后落在 41px 处，若顶栏层级更低，返回条会盖在顶栏之上—— 实测正是
   "elementFromPoint(顶部) 命中的是返回条而不是顶栏头像"，故 31 → 32 只高 1 档）。
   v140 返回条下线后 32 档保留不动（覆盖层身份不变，仍远低于
   .ed-panel 500 / 抽屉 600 / 弹层 700，编辑与浮层不受影响）。 */
.topbar { z-index: 32; }
/* 50   顶部 Tab */
/* v132：页签为常驻固定头部 .app-head 内的一线页签（覆盖层与每页各一份均已成历史） */
.top-tab { z-index: 30; }
/* v23：原「60 页码指示器 .page-indicator」层级已随该组件下线移除 */
/* 200  文章详情子页、公司发展历程弹层、微小店介绍弹层（v15-R3，均相对 page 容器；修复：登录态 user-bar(z5)/页签(z50) 曾压制弹层致返回键不可点） */
.mp-detail { z-index: 200; }
/* 500  管理模式面板 */
.ed-panel { z-index: 500; }
/* 600  微小店询价抽屉（半屏，盖过管理面板） */
.inquiry-sheet { z-index: 600; }
/* 600  交换名片留资抽屉（v17-F1，半屏，与询价抽屉同档同模式） */
.lead-sheet { z-index: 600; }
/* 600  我的名片夹抽屉（v31 第6步，复用 .lead-sheet 底座——同选择器即本档登记） */
/* v24：原「650 密码保护门 .lock-gate」层级随该组件下线移除 */
/* 650  v38：多设备编辑冲突面板/提醒条（需盖过 .ed-panel 500 与抽屉 600，且低于确认弹窗 700，
   否则冲突面板自己弹出的确认框会被压在下面点不到） */
.conflict-panel { z-index: 650; }
.conflict-bar { z-index: 650; }
/* 700  分享/转发指引浮层；确认弹窗（需盖过管理面板） */
.share-sheet { z-index: 700; }
/* 700  v62：分享兜底面板（原生分享拉不起时的保底路径，与 .share-sheet 同档） */
.share-fallback { z-index: 700; }
.confirm-dialog { z-index: 700; }
/* 700  编辑器关闭二次确认（v17-M3：复用 .confirm-dialog 组件挂载 #overlayRoot，本档即登记处） */
/* 800  图片灯箱；多媒体图集灯箱（R4-08，多媒体专用新通道） */
.lightbox { z-index: 800; }
.gallery { z-index: 800; }
/* 750  注册/登录页「服务协议」全文弹层（v28 A1，auth.html；需盖过 .auth-card 但低于 Toast） */
.auth-agreement { z-index: 750; }
/* 900  Toast；file:// 协议提示 */
.toast { z-index: 900; }
.file-protocol-tip { z-index: 900; }
/* v134  旧/失效地址提示条（index.html #staleHostBar）—— 与 Toast 同档：
   用户点旧链接进来第一眼就要看到"地址已迁移"，不能被任何浮层盖住。 */
.stale-host-bar { z-index: 900; }

/* ---------- v55：编辑器「所属行业」= 预设下拉 + 自定义 ---------- */
.ed-industry { display: flex; flex-direction: column; gap: 8px; }
.ed-industry-custom[hidden] { display: none; }

/* ============================================================
   v59（需求 6 + 7）→ v60（需求 5 重排）→ v73（需求 3 + 4）：个性化浮层
   ------------------------------------------------------------------
   页面结构（v73 定稿，从上到下）：
     ① **主题外观**（六套配色）—— 最上面的主切换区块；
     ② **按键主题**（六套，与名片配色一一配套）—— v73 需求 3 新增，紧贴其下；
     ③ **名片风格**（五类 × 2 坑位）—— 最下方。
   三个区块都**卡片化 + 金线标题 + 选中高亮明显**；页面内没有其它内容。
   坑位（需求 5 原话）：风格坑位 = 虚线空框 + 风格名 + 「待设计」角标；
   当前生效项高亮并打「使用中」角标。
   v73 需求 4：面板高度收到 52%（原 78%）并收紧内距，主人侧「仅在保存后生效」
   提示条删除 —— 让浮层外的名片卡与按键排留在视野内，切换即时可见。

   底座复用 `.share-sheet`（遮罩 + 贴底 flex 列 + z-index 700，见 §6 层级表），
   故本段落**不写任何 z-index**，仅补面板本体与内容区样式。
   配色纪律：面板底走 var(--c-card)、文字走 --c-text/--c-text-2，
   **不写死 #fff / 深色字面量** —— 否则任何深色主题下都会变成
   「白底 + 近白字」不可读（--c-text 在深色主题是浅色）。
   显式 display 的元素必须自带 [hidden] 规则（T6-2 / v39 教训）。
   ============================================================ */
.ps-panel {
  display: flex;
  flex-direction: column;
  /* v73（需求 4）：面板高度 78% → 52%，内距一并收紧 —— 让浮层外的
     「名片卡 + 按键排」同时留在视野内，用户切换主题 / 按键主题时能**直接看到实时变化**，
     尽量少遮挡页面（需求原话）。改动只动这几处尺寸，不改任何配色。 */
  max-height: 52%;
  background: var(--c-card);
  border-radius: 20px 20px 0 0;
  padding: 12px 14px calc(12px + var(--safe-bottom));
  animation: ui-slide-up .28s cubic-bezier(.22, .61, .36, 1);
}
.ps-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 8px;
}
.ps-title { font-size: 15px; font-weight: 700; color: var(--c-text); }
.ps-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--c-bg);
  color: var(--c-text-2);
  font-size: 13px;
  line-height: 1;
}
.ps-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* 角色提示条：**v73 起只有查看者一档**（主人侧的「仅在保存后生效」提示条已删除 ——
   需求 4 明确要求；它与底部「保存 / 确定更换」按钮语义重复且占高度）。
   查看者档必须保留：那是「本机预览、关闭即还原」的唯一告知。 */
.ps-note {
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
}
.ps-note.is-viewer { background: var(--c-gold-soft); border-color: var(--c-gold); }
.ps-note-t { font-size: 13px; font-weight: 700; color: var(--c-text); margin-bottom: 3px; }
.ps-note-s { font-size: 12px; line-height: 1.7; color: var(--c-text-2); }

/* ---------- 区块外壳（卡片化 + 金线标题）—— v73 起共三块：主题外观 / 按键主题 / 名片风格 ---------- */
.ps-block {
  margin-top: 10px;
  padding: 10px 10px 12px;
  border-radius: 14px;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
}
.ps-block-h {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 8px;
}
.ps-block-h::before {
  content: '';
  flex: none;
  width: 3px;
  height: 12px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--c-gold-bar-1), var(--c-gold-bar-2));
}
.ps-block-t { font-size: 13.5px; font-weight: 700; color: var(--c-title); letter-spacing: .3px; }
.ps-block-s { font-size: 11px; color: var(--c-text-3); }

.ps-groups { display: block; }
.ps-group + .ps-group { margin-top: 10px; }
.ps-group-h { font-size: 12px; font-weight: 600; color: var(--c-text-2); margin-bottom: 6px; letter-spacing: .4px; }
.ps-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 7px; }
.ps-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 8px 8px;
  border-radius: 12px;
  background: var(--c-card);
  border: 1px solid var(--c-line);
  transition: transform .16s ease, border-color .16s ease, background .16s ease;
}
.ps-card:active { transform: scale(.97); }
/* 选中高亮（需求 5："选中高亮明显"）：金色描边 + 金色底 + 内描边双重强调 */
.ps-card.is-active {
  border-color: var(--c-gold);
  background: var(--c-gold-soft);
  box-shadow: inset 0 0 0 1px var(--c-gold);
}
.ps-dots { display: flex; gap: 6px; }
.ps-dot { width: 16px; height: 16px; border-radius: 50%; border: 1px solid rgba(0, 0, 0, .08); }
.ps-name { font-size: 12px; color: var(--c-text); }
.ps-row { display: inline-flex; align-items: center; gap: 4px; }
.ps-mark { font-size: 11px; font-weight: 800; color: var(--c-gold); }
.ps-mark[hidden] { display: none; }
/* 风格坑位：虚线空框（该风格的设计稿尚未产出）+ 风格名 + 「待设计」角标 */
.ps-slot {
  display: block;
  width: 100%;
  height: 32px;
  border-radius: 8px;
  border: 1px dashed var(--c-line);
  background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(120, 120, 120, .07) 6px 12px);
}
.ps-card.is-active .ps-slot { border-color: var(--c-gold); }
.ps-slot-tag {
  position: absolute;
  top: 5px;
  right: 6px;
  padding: 1px 5px;
  border-radius: 5px;
  background: var(--c-gold-soft);
  color: var(--c-gold);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .3px;
}
/* 「使用中」角标：当前生效的主题 / 按键主题 / 风格 */
.ps-badge {
  position: absolute;
  top: 5px;
  left: 6px;
  padding: 1px 5px;
  border-radius: 5px;
  background: var(--c-gold);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .3px;
}
.ps-badge[hidden] { display: none; }
.ps-reset {
  display: block;
  width: 100%;
  margin: 8px 0 0;
  padding: 8px;
  border-radius: 10px;
  background: var(--c-bg);
  border: 1px dashed var(--c-line);
  color: var(--c-text-2);
  font-size: 12px;
}
.ps-reset[hidden] { display: none; }
.ps-foot { display: flex; gap: 10px; padding-top: 10px; }
.ps-foot .btn { flex: 1; }
.ps-foot .btn[hidden] { display: none; }
