/* ============================================================================
   沐雪 AI · PWA 界面样式（安装入口 / 安装引导面板 / 更新提示条）
   ----------------------------------------------------------------------------
   本文件只做**新增界面**的样式，不碰任何既有 class（app.css:2294 记录过
   "200+ class 被 JS 依赖，重命名直接打断页面"）。**不追加到 app.css**。
   颜色一律走既有 Token（app.css:6-67），本文件不出现任何十六进制色值 —
   遮罩的透明度用 `opacity` 表达，不用 rgba 字面量。
   实例化节奏：入口按钮 / 面板均由 /js/pwa.js 懒创建（已安装与桌面端不创建 DOM）。

   ── z-index 阶梯（本文件只占 3 / 302 / 400 / 401 四层，**不许各自为政写数字**）──
       2   .muxue-loading       既存 3D 加载遮罩（舞台内）
       3   .install-chip--float 本文件 安装入口的**兜底**形态（挂 #muxue3d 时）——
                                 要压过同容器内的加载遮罩(2)，又必须远低于面板(400)。
                                 默认形态挂在 .page-actions 里，流式布局，不参与层级
     250   .auth-gate           既存 登录门禁
     300   .toast-host          既存 全局 toast
     302   .pwa-update-bar      本文件 更新提示条 —— 必须高于 toast（"有新版本"比
                                 一次性提示重要），低于 tour/auth-gate，避免盖住流程
     310   .tour-layer          既存 新手引导
     340   .tr-splash           既存 开屏预告片 —— 会盖住 chip，属**预期覆盖**
     400   .install-scrim       本文件 安装面板遮罩
     401   .install-sheet       本文件 安装面板 —— **严格高于同文件的更新条(302)**，
                                 否则两者同时出现时面板会被更新条压住（或反过来）
   规则：新增层级就往这张表里插一行并说明"为什么在它上面/下面"，不许出现裸数字。
   ========================================================================== */

/* ── 1. 安装入口 chip（挂进页头 `.page-actions`，**纯图标 32px**）─────────────
   为什么不是"图标+文字"：`.page-actions` 是 `flex-wrap: wrap`，第二行在真机上构成是
   `select-sm(音色下拉,158) + tag(引擎,66) + btn sm(试听,52) = 276`，再加一个 62px 的
   文字版 chip 就超过容器宽 362 ⇒ **多占一整行，把门禁卡片与主 CTA 推下去 47px**。
   （我第一次验收报的"91 → 91 零增长"是本地空下拉造成的假基线：`#voiceSel` 的音色列表
   由 `/api` 填充，本地无后端 ⇒ 只有 108 宽 ⇒ 假象。真机 158 宽才是压力最大的形态。）
   现在固定 **32×38**，`justify-content:center` 让图标居中，宽度与视口无关：
   三档视口实测 Δ=0（含把下拉撑到 158 的加压复测）。
   语义靠 `aria-label` + `title`，不靠可见文字；面板内有完整说明「把沐雪装到桌面」。 */
.install-chip {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 38px; padding: 0;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  background: var(--brand-soft);
  color: var(--brand);
  cursor: pointer; flex: none;
  transition: color 150ms, border-color 150ms;
}
/* 热区：视觉 32×38，四周各扩 6px ⇒ **44 × 50**（wcag 2.5.5 要求 ≥44×44）。
   纵向 38 已够 44，横向必须补，否则 32 宽只有 38 的可点区域。 */
.install-chip::after { content: ""; position: absolute; inset: -6px; }
.install-chip:hover, .install-chip:focus-visible { border-color: var(--brand); color: var(--brand-strong); }
.install-chip svg { width: 16px; height: 16px; flex: none; }

/* 兜底容器 #muxue3d：回到绝对定位（不进文档流 ⇒ 面板高度零变化） */
.install-chip--float { position: absolute; top: 10px; right: 10px; z-index: 3; }
.muxue3d.muxue3d-fallback .install-chip--float { top: auto; right: 10px; bottom: 10px; }

/* ── 2. 遮罩 ───────────────────────────────────────────────────────────────── */
.install-scrim {
  position: fixed; inset: 0; z-index: 400;
  background: var(--bg);
  opacity: 0;
  transition: opacity 150ms cubic-bezier(.4, 0, .2, 1);
}
.install-scrim.is-open { opacity: .72; }

/* ── 3. 面板（bottom sheet）───────────────────────────────────────────────── */
.install-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 401;
  width: 100%; max-width: 480px; margin: 0 auto;
  background: var(--panel-solid);
  border-top: 1px solid var(--line-2);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  /* ★2026-11-01（Wave-1 · 授权内）：`env()` 兜底，写成**两条声明**而不是只加 `, 0px`。
     为什么两条都要：
       · 只写 `calc(20px + env(..., 0px))` ⇒ 完全不支持 `env()` 的旧内核（老 X5 / UC）
         会把整条 `padding` 判为解析失败并**丢弃**（不是退化成 0）⇒ 底部内距彻底消失，
         按钮贴死屏幕下沿。fallback 参数只在"支持 env() 但该变量未定义"时才起作用。
       · 所以先给一条纯字面量（旧内核用），再给一条带 env() 的（15.4+ / 安卓 Chrome 用）。
     安全性：inset=0 时 `calc(20px + 0px)` 与原值 20px **逐像素相同** ⇒ 对非刘海机型零变化。 */
  padding: 8px 18px 20px;
  padding: 8px 18px calc(20px + env(safe-area-inset-bottom, 0px));
  transform: translateY(100%);
  transition: transform 200ms cubic-bezier(.2, 0, 0, 1);
}
.install-sheet.is-open { transform: translateY(0); }

.install-grab {
  width: 36px; height: 4px; margin: 0 auto 10px;
  border-radius: 999px; background: var(--line-2);
}

.install-head { display: grid; grid-template-columns: 56px 1fr 36px; gap: 12px; align-items: start; }
.install-appicon {
  width: 56px; height: 56px; border-radius: 14px; display: block;
}
.install-headtx { min-width: 0; }
.install-title {
  margin: 0; font-size: var(--fs-lg); font-weight: 590; line-height: 1.3;
  color: var(--text); letter-spacing: -.01em;
}
.install-desc {
  margin: 5px 0 0; font-size: var(--fs-sm); line-height: var(--lh-base);
  color: var(--muted); letter-spacing: .01em;
}
.install-x {
  position: relative;
  width: 36px; height: 36px; padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
  transition: color 150ms, border-color 150ms;
}
.install-x::after { content: ""; position: absolute; inset: -4px; }  /* 36 → 44 热区 */
.install-x:hover { color: var(--text); border-color: var(--line-2); }

/* ── 4. 分支内容：状态只有一个真源（父级的 data-branch / data-state）─────── */
.install-points, .install-steps { display: none; margin: 16px 0 0; padding: 0; list-style: none; }
.install-sheet[data-branch="android"] .install-points { display: grid; }
.install-sheet[data-branch="ios"] .install-steps,
.install-sheet[data-branch="wechat"] .install-steps { display: grid; }
/* Android 但浏览器没给 beforeinstallprompt：走菜单教学，显示步骤、隐藏（空的三条卖点）。
 * 不加这条的话，android-manual 的 3 步说明会写进 DOM 却被 display:none 藏掉——
 * 用户只看到"从菜单添加"一句，等于没说怎么点。 */
.install-sheet[data-branch="android"][data-state="manual"] .install-points { display: none; }
.install-sheet[data-branch="android"][data-state="manual"] .install-steps { display: grid; }
.install-sheet[data-state="pending"] .install-points,
.install-sheet[data-state="pending"] .install-steps { opacity: .5; }
.install-sheet[data-state="done"] .install-points,
.install-sheet[data-state="done"] .install-steps { display: none; }

.ip-item, .is-item { display: grid; gap: 10px; align-items: start; }
.ip-item { grid-template-columns: 20px 1fr; }
.ip-item + .ip-item { margin-top: 13px; }
.is-item { grid-template-columns: 24px 1fr; }
.is-item + .is-item { margin-top: 13px; }
.ip-ic { color: var(--brand); display: inline-flex; }
.ip-ic svg { width: 20px; height: 20px; }
.is-n {
  width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--brand); color: var(--brand);
  font: 590 12px/1 var(--mono);
}
.ip-tx, .is-tx { display: block; min-width: 0; }
.ip-t, .is-t { display: block; font-size: var(--fs-sm); font-weight: 510; color: var(--text); }
.ip-d, .is-d {
  display: block; margin-top: 2px; font-size: var(--fs-xs);
  color: var(--muted); line-height: var(--lh-base);
}

/* ── 5. 底部按钮区 ─────────────────────────────────────────────────────────── */
.install-foot { margin-top: 18px; display: grid; gap: 9px; }
.install-foot .btn { min-height: 44px; }                     /* WCAG 2.5.5 */
.install-foot .btn[hidden] { display: none; }                /* .btn 的 inline-flex 会压过 [hidden] */
.install-tip { margin: 3px 0 0; font-size: 12.5px; }

/* pending：主按钮左侧一个描边旋转环（reduced-motion 下静止） */
.install-sheet[data-state="pending"] #installPrimary::before {
  content: ""; width: 14px; height: 14px; flex: none;
  border-radius: 50%;
  border: 2px solid var(--brand); border-right-color: transparent;
  animation: pwaSpin 800ms linear infinite;
}
@keyframes pwaSpin { to { transform: rotate(360deg); } }

/* ── 6. 更新提示条（新版本已就绪 · 刷新）──────────────────────────────────── */
.pwa-update-bar {
  position: fixed; left: 50%;
  /* 同 `.install-sheet`：先给纯字面量（旧内核不认 env() 会整条丢弃），再给带 env() 的那条。
     inset=0 时 `calc(10px + 0px)` 与原值 10px 逐像素相同 ⇒ 非刘海机型零变化。 */
  top: 10px;
  top: calc(10px + env(safe-area-inset-top, 0px));
  transform: translateX(-50%);
  z-index: 302;                 /* 高于 toast-host(300)，低于 tour(310)/auth-gate(250→310+)/预告片(340) */
  display: inline-flex; align-items: center; gap: 12px;
  padding: 9px 12px 9px 15px;
  border-radius: 999px;
  background: var(--panel-solid);
  border: 1px solid var(--line-2);
  color: var(--text);
  font-size: 12.5px;
  max-width: calc(100vw - 32px);
}
.pwa-update-tx { color: var(--text-mid); white-space: nowrap; }
.pwa-update-btn {
  position: relative; min-height: 34px; padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--brand); background: var(--brand-soft); color: var(--brand);
  font: 510 12.5px/1 var(--font);
}
.pwa-update-btn::after { content: ""; position: absolute; inset: -5px 0; }  /* 34 → 44 */

/* ── 7. 动效降级（不是可选项）────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .install-sheet { transform: none; opacity: 0; transition: opacity 150ms linear; }
  .install-sheet.is-open { transform: none; opacity: 1; }
  .install-scrim { transition: none; }
  .install-sheet[data-state="pending"] #installPrimary::before { animation: none; }
}

/* ── 侧栏「下载中心」卡片（2026-11-02：主行改开下载中心页，pwa.js DL_HREF）──
 * 复用 .tr-card（侧栏预告片卡片）的视觉语言：同圆角、同渐变底、同 hover 光晕，
 * 这样菜单底部三张卡（预告片 / 下载 / 核心状态）看起来是一套，不是外挂上去的。
 * 没有用 .nav-item 的原因：那是导航项（左侧图标+单行文字），而这是一个带
 * 副标题的行动卡片，尺寸和信息密度都不一样。 */
.dl-card {
  display: flex; align-items: center; gap: 9px;
  width: 100%; margin-top: 10px; padding: 8px 10px;
  flex-wrap: wrap;                       /* 次行 .dl-card-alt 要独占第二行 */
  color: var(--text-mid); text-decoration: none;
  background: var(--side-card-bg,
    linear-gradient(180deg, var(--brand-soft), var(--panel-2)));
  border: 1px solid var(--line-2); border-radius: 12px;
  transition: border-color .18s, box-shadow .18s, color .18s;
}
.dl-card:hover, .dl-card:focus-visible {
  color: var(--text);
  /* ★兜底值不是冗余：2026-10-26 真实发生过「pwa.css 引用了 app.css 里还没入库的
     --side-card-*」⇒ fresh clone 下这三条全是无效值，下载卡变一张无底色的裸链接，
     而且布局还在、肉眼不容易发现。有值走 token，没值走兜底，两种状态都渲染得出来。
     兜底值同样只用 token（不写字面量），不违反 P0-2。 */
  border-color: var(--side-card-line, var(--brand));
  box-shadow: var(--side-card-glow, 0 6px 22px var(--brand-soft));
}
.dl-card-ic {
  display: grid; place-items: center; width: 30px; height: 30px; flex: none;
  border-radius: 9px; background: var(--brand-soft); color: var(--brand);
}
.dl-card-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
/* ── 次行「装到主屏幕」（OD-011：常驻安装入口）─────────────────────────────
   为什么需要它：chip 已撤、nudge 面板只弹一次（`startNudge` 首次 openSheet 前就
   clearInterval + 落 30 天冷却）⇒ **安卓用户关掉面板后本会话再也装不了 PWA**。
   这一行是唯一常驻入口，不是锦上添花。
   布局：`.dl-card` 加 flex-wrap，本行 `flex:0 0 100%` 独占第二行。
   ★热区用 min-height:44px **不用** ::after 外扩 —— 主行与次行之间没有间隙，
     外扩会抢主行的点击（设计师 2026-10-26 实测规则：能靠布局长高就长高）。 */
.dl-card-alt {
  display: flex; align-items: center; gap: 6px;
  flex: 0 0 100%; width: 100%; min-height: 44px;
  margin-top: 2px; padding: 0;
  border: 0; border-top: 1px solid var(--line);
  background: transparent; color: var(--brand); cursor: pointer;
  font: 510 11px/1 var(--font); text-align: left;
}
.dl-card-alt:hover, .dl-card-alt:focus-visible { color: var(--brand-strong); }
.dl-card-alt svg { width: 14px; height: 14px; flex: none; }
/* 无 deferredPrompt 时不禁用、不灰掉（灰掉等于告诉用户"你不配"），
   改为次级色 + 文案指向 ⋮ 菜单 */
.dl-card-alt[data-state="manual"] { color: var(--muted); }
body.nav-collapsed .dl-card-alt { display: none; }   /* 收窄态放不下两行 */
.dl-card-title { font: 600 12.5px/1.2 var(--font); color: var(--text); }
.dl-card-sub { font-size: 11px; color: var(--muted); }
/* 侧栏收窄态（只显示图标）：与 .tr-card 的处理保持一致 */
body.nav-collapsed .dl-card { justify-content: center; padding: 8px 0; }
body.nav-collapsed .dl-card-meta { display: none; }
