/* ===========================================================================
   产品销售官网 · 样式（2026 版视觉系统）
   ---------------------------------------------------------------------------
   ★ 改配色只需要动下面 :root 里的变量，不用翻下面的规则。
   ★ 命名是「板块名-元素名」（如 .pricing-card / .faq summary），
     想改哪一块，按板块名搜一下就能定位。
   ★ 不依赖任何外部字体 / 图标库 / CDN：图标全是内联 SVG，字体用系统字体。
     这样离线、内网、私有化部署环境下样式完全一致，也不会因为某个 CDN 挂了就崩。

   ── 这一版改了什么（相对 1160px 的旧版）─────────────────────────────────
   ① 版心 1160 → 1320（≥1700 视口放到 1440）。旧版在 1920 屏上两侧各空
      380px、留白 39.6%，读起来像"没铺满的 A4 纸"。
   ② 字号整体上移一档：正文 16 → 16.5、H3 17 → 20、H2 34 → clamp 到 52、
      H1 47 → clamp 到 82。旧版的标题在中文字体下"字重够、个头不够"。
   ③ 首屏改成满屏（min-height:100svh）深色沉浸式。旧版首屏只有 683px，
      1080 的屏上白白空掉 31%，下一个板块提前露头，像"半截页面"。
   ④ 首屏深色 + 正文浅色：深色负责第一眼的专业感与记忆点，
      浅色负责长页面（定价、FAQ、表单）的可读性 —— 整站全深色读 9 屏会累。
   ⑤ 功能/痛点区改成 Bento 拼贴（4 栅格，卡片宽度 2/1/1 相间），
      不再是三列等分重复两遍 —— 那正是"平庸"的来源。
   ⑥ 移动端专项：输入框字号补到 16px（iOS 聚焦不再放大整页）、
      触控目标补到 ≥44px、首屏标题解除强制换行 + text-wrap:balance 消孤字。
   ⑦ 新增滚动渐入（assets/js/reveal.js）。★ 是渐进增强：
      没 JS / prefers-reduced-motion 时内容照常完整显示，不做任何隐藏。
   =========================================================================== */

/* ===========================================================================
   字体 · MiSans（小米）
   ---------------------------------------------------------------------------
   ★ 这一版之前正文只写系统字体栈，注释里写着"字体用系统字体"。
     改掉的理由：Mac 上是苹方、Windows 上是微软雅黑，两套字形的字宽与标点
     差得很明显 —— 同一屏文案，在客户机器上和在我机器上"排出来不一样"。
     官网是要给客户看的门面，这一条不能靠"看的人自己适配"。
   ★ 不用苹方：它是 Apple 的授权字体，禁止 @font-face 嵌入分发到非 Apple 设备。
     MiSans 免费商用、许可明确允许嵌入，条件是不得改字形、须注明出处。
     许可原文见 assets/fonts/LICENSE-MiSans.txt。
   ★ 按需下载：用到哪档才拉哪档，正文 Light + 标题 Bold 两档，约 1.8MB。
     不是一次性 3.5MB。font-display: swap 保证首屏不空等字体。
   ========================================================================= */

@font-face {
  font-family: 'MiSans';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('../fonts/misans-light-sc.woff2') format('woff2');
}

@font-face {
  font-family: 'MiSans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/misans-regular-sc.woff2') format('woff2');
}

@font-face {
  font-family: 'MiSans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/misans-medium-sc.woff2') format('woff2');
}

@font-face {
  font-family: 'MiSans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/misans-medium-sc.woff2') format('woff2');
}

@font-face {
  font-family: 'MiSans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/misans-bold-sc.woff2') format('woff2');
}

:root {
  /* ---- 字体 ----
     MiSans 在前，后面是兜底链（字体未下载完 / 生僻字不在子集里时用到）。 */
  --font: 'MiSans', -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
          "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  /* ★ 正文默认字重：300 = MiSans Light。想换回常规粗细改成 400，只改这一行。 */
  --font-weight-base: 300;

  /* ---- 品牌色：靛蓝。换品牌时优先改这几个 ----
     ★★ 2026-10-08 主色对齐后台（本次改版的根因级修复）：
        官网这里长期停在 #4958DD / #3C48B6，而 web-admin 在 2026-09-27
        就已按「方案 D」把界面主色迁到 #4F46E5 / #4338CA
        （理由见 web-admin/assets/styles/base.css 顶部那段注释：
          标识的蓝 #2563EB(h221) 与旧的界面蓝 #4958DD(h234) 色差 ΔE 仅 7.35，
          既不够"同"也不够"异"；改成 #4F46E5(h243) 后 ΔE = 21.04，
          被读作「刻意的两个色」，且对白字对比度从 5.64:1 升到 6.29:1）。
        后台改了、官网漏了 —— 于是同一家公司两个界面长期不同色。
        官网是对外那一面，这个不一致最伤品牌识别度，所以本次一并对齐。
     ⚠️ 改这里要连 rgba() 形式的旧值一起换（全文件约 20 处 rgba(73,88,221,…)），
        否则光晕与描边会继续用旧色，出现"按钮新色、背景旧色"的错位。 */
  --brand: #4f46e5;         /* 主色（按钮实底 / 链接 / 图标）—— 与后台 --color-primary 同值 */
  --brand-dark: #4338ca;    /* 品牌渐变暗端（按钮描底 / hover）—— 与后台 --color-primary-hover 同值 */
  --brand-500: #6366f1;     /* 渐变亮端（Indigo-500）—— 与后台同值，用于按钮/光晕的渐变色 */
  --brand-light: #eef0fe;   /* 品牌极浅底（图标衬底 / 选中态） */
  --cyan: #22d3ee;          /* 渐变强调色的另一端 */
  --violet: #8b5cf6;        /* 与后台 --color-ai 同值（"AI"在图表里的分类色） */
  --violet-deep: #a855f7;   /* 与后台 --color-ai-deep 同值 */

  /* ---- 中性色 ---- */
  --ink: #070d1b;           /* 标题 */
  --ink-2: #33405a;         /* 正文 */
  --ink-3: #6b7a90;         /* 次要说明 */
  --line: #e5eaf2;          /* 分隔线 / 边框 */
  --line-2: #eef2f8;        /* 更浅的线（卡片内分隔） */
  --bg: #ffffff;            /* 页面底色 */
  --bg-alt: #f5f8fd;        /* 交替区块底色 */

  /* ---- 深色区（首屏 / 下单区 / 页脚）---- */
  --dark: #05070f;
  --dark-2: #0b1120;
  --dark-line: rgba(255, 255, 255, .10);
  --dark-ink: #edf1fb;
  --dark-ink-2: #a6b4cf;
  --dark-ink-3: #7b8aa6;

  /* ---- 状态色 ---- */
  --ok: #10b981;
  --warn: #f59e0b;
  --warn-bg: #fffbeb;
  --info-bg: #f0f9ff;
  --info-line: #bae6fd;

  /* ---- 表单控件（下单区深色表单专用）----
     ★ 下拉箭头是自绘的内联 SVG，不是浏览器原生那个。
       原生箭头固定在离边框约 6px 处，而输入框圆角是 12px，
       箭头会落进圆角弧线里，看起来"顶到边上"。
       自绘之后位置由 CSS 说了算（见 .field select 的 background-position）。
       内联 SVG 不依赖任何外部文件 / 图标库，私有化部署也一致。 */
  --chevron: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6.4 8 10.4l4-4' stroke='%2394a3c4' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --chevron-on: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6.4 8 10.4l4-4' stroke='%23e2e8f0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --field-bg: #141c2e;        /* 输入框底色（实底，从卡片上"浮"起来） */
  --field-bg-hover: #18213a;
  --field-bg-focus: #161f36;

  /* ---- 形状 ---- */
  --r-sm: 10px;
  --r: 14px;
  --r-lg: 20px;
  --r-xl: 28px;
  --shadow-sm: 0 1px 2px rgba(7, 13, 27, .05);
  --shadow: 0 12px 30px -14px rgba(7, 13, 27, .20);
  --shadow-lg: 0 34px 70px -34px rgba(7, 13, 27, .42);

  /* ---- 版式 ---- */
  --wrap: 1320px;           /* ★ 旧版 1160 */
  --header-h: 72px;
  --sec: clamp(64px, 6.6vw, 118px);   /* 区块上下留白，随屏宽走 */
}

/* 超宽屏再放一档，别让 1920 以上两侧空得太多 */
@media (min-width: 1700px) {
  :root { --wrap: 1440px; }
}

/* ============================ 基础重置 ============================ */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 18px);
  -webkit-text-size-adjust: 100%;   /* 横屏时 iOS 不要自作主张放大字号 */
}

/* ===================== 站内跨页过渡（View Transitions） ===================== */
/*
   【为什么加】（2026-10-09，用户反馈"点左侧标题后右侧内容刷新有点僵硬"）
     官网是多页静态站，点链接就是**整页硬刷新**：白一下、再整页画出来。
     顶栏/页脚明明逐字节相同，也要跟着重画一遍 —— 观感就是"卡了一下"。

     @view-transition 是浏览器原生的跨文档过渡（Chromium 126+ / Safari 18+）：
     旧页截图 → 新页截图 → 交叉淡入。全部由浏览器实现 ——
     不需要改造成 SPA、不需要改任何一个链接、
     **不支持的内核就是普通整页跳转**（一行 JS 都没有，零风险降级）。

   【为什么给顶栏 / 页脚单独起名字】
     这两块每一页都逐字节相同（唯一来源 tools/_lib/site-chrome.js）。
     不命名时它们跟整页一起淡出淡入，看起来就是"头部闪了一下"；
     命名后就地交叉淡入 —— 同样的画面叠同样的画面，等于静止。
     ⚠️ 同一页里一个 view-transition-name 只能出现一次，
        所以只给这两个"全站唯一"的元素命名（挂件是 JS 注入的、时机不确定，不命名）。

   【与「减少动态效果」的关系】
     视图过渡**不受** prefers-reduced-motion 自动影响，必须显式关掉。
*/
@view-transition { navigation: auto; }

.site-header { view-transition-name: site-header; }
.site-footer { view-transition-name: site-footer; }

/* 默认 250ms 在长页面上偏慢，收到 180ms：看得见是"过渡"，又不拖节奏 */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .18s; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink-2);
  font-family: var(--font);
  font-weight: var(--font-weight-base);
  font-size: 16px;
  line-height: 1.78;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;      /* 深色首屏的装饰层不许撑出横向滚动条 */
}

/*
 * ★ 默认字重是 300（Light）后，UA 的 <b> / <strong>（font-weight: bolder）
 *   只会把 300 抬到 400，强调几乎失效；显式钉到 500（MiSans Medium）。
 *   h1~h6 不受影响 —— UA 给的是绝对值 bold(700)。
 */
b, strong { font-weight: 500; }

h1, h2, h3, h4 {
  color: var(--ink);
  margin: 0;
  line-height: 1.24;
  font-weight: 700;
  letter-spacing: -.01em;   /* 中文标题略微收紧，比 0 更"齐" */
}
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img, svg { max-width: 100%; }
em { font-style: normal; }
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .92em;
  background: var(--bg-alt);
  padding: 1px 6px;
  border-radius: 6px;
}

/* 段落与标题的断行优化：Chrome 支持，不支持的浏览器自动忽略，无副作用 */
h1, h2, .section-sub, .hero-sub { text-wrap: balance; }
p, li { text-wrap: pretty; }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }
.wrap-narrow { max-width: 900px; }

/* 只给读屏用的文字：视觉上完全不可见，但仍留在无障碍树里被念出来。
   ★ 不能用 display:none / visibility:hidden / width:0 —— 那三个会连
     无障碍树一起摘掉，读屏就再也念不到了。只能用"裁到 1px"这个手法。 */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ============================ 按钮 ============================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px;                  /* 触控目标下限，移动端不再"点不准" */
  padding: 11px 22px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 16px; font-weight: 600; line-height: 1.2;
  cursor: pointer; white-space: nowrap;
  transition: transform .18s cubic-bezier(.16, 1, .3, 1), box-shadow .18s ease,
              background .18s ease, color .18s ease, border-color .18s ease;
  font-family: inherit;
}
.btn:active { transform: translateY(1px); }

.btn-primary {
  background: linear-gradient(135deg, var(--brand), var(--brand-dark));
  color: #fff;
  box-shadow: 0 6px 20px -8px rgba(79, 70, 229, .55);
}
.btn-primary:hover {
  box-shadow: 0 12px 28px -10px rgba(79, 70, 229, .62);
  transform: translateY(-1px);
}

.btn-outline { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-outline:hover { border-color: var(--brand); color: var(--brand); }

.btn-ghost { background: transparent; color: var(--ink-2); }
.btn-ghost:hover { color: var(--brand); background: var(--brand-light); }

.btn-lg { min-height: 52px; padding: 14px 30px; font-size: 16px; }
.btn-block { width: 100%; }
.btn-mini { min-height: 32px; padding: 5px 14px; font-size: 12px; font-weight: 400; }

/* ============================ 顶部导航 ============================
   页头有两套配色，靠 CSS 变量在两者间切换（品牌 / 导航 / 幽灵按钮 / 汉堡
   四个元素只写一遍规则，见下方「为什么用变量」）：

     · 浅色页头（默认）—— 白色毛玻璃 + 深字
     · 深色页头 —— 透明 + 浅字，压在深色首屏上融为一体

   ★ 2026-10-20 把默认值反转了（旧版默认是"透明 + 浅字，滚动后切白底"）
     ------------------------------------------------------------------
     旧写法隐含一个前提："全站首屏都是深色的"。这个前提从未成立过 ——
     实测 43 个页面里**只有 index.html 首屏是深色**（.hero），
     其余 42 个（37 个 .page-hero 内页 + chat / register / legal 等）
     首屏底色都是 #f8f9fe ~ #fdfdff 的浅色。

     于是内页顶部导航变成 #edf1fb 的浅字压在 #f8f9fe 的浅底上：
     对比度约 1.05:1，肉眼几乎看不见，且**只有往下滚 8px 才恢复正常**。
     用户的第一印象是"这个站的菜单是坏的"。

     反转后给 1 个页面写例外，而不是给 42 个页面写例外。

   ★ 为什么判据是 body:has(.hero)，而不是给 <body> 加 class
     .hero 是首屏 section，它常驻 DOM，不随滚动出现或消失 ——
     所以这个判据回答的是"这页是不是深色首屏"，不是"现在滚到哪了"。
     判据稳定，不需要 JS 参与，也就没有脚本慢一拍导致的闪色。
     站内 body:has(.launch-banner) 已经是同一个模式，不引入新依赖。

   ★ 为什么用 CSS 变量、而不是把每条规则写两遍：
     页头有两套配色 x 四个元素（品牌 / 导航 / 幽灵按钮 / 汉堡），逐条写就是
     8 条重复规则，以后改一处配色要翻半天。这里把配色收敛成几个变量，
     切状态只改变量，元素规则只写一遍。

   ★ 第二个触发条件：顶部那条「禁止对外发布」横幅。它是浅底红字，
     页头若还是"透明 + 浅字"压在上面，导航会直接看不见。
     :has() 现代浏览器都支持；万一不支持，最坏也只是"配置没填齐"这个中间
     状态下页头偏淡 —— 配置填齐后横幅消失，这个状态就不存在了。 */
.site-header {
  /* 页头配色令牌，默认 = 浅底深字（内页、404、legal 等所有非深色首屏页） */
  --hd-fg: var(--ink);
  --hd-fg-2: var(--ink-2);
  --hd-hover-fg: var(--brand);
  --hd-hover-bg: var(--brand-light);
  --hd-bg: rgba(255, 255, 255, .84);
  --hd-border: var(--line);
  --hd-shadow: 0 1px 16px rgba(7, 13, 27, .06);
  --hd-toggle-bg: #ffffff;
  --hd-toggle-border: var(--line);
  --hd-toggle-bar: #33405a;

  position: sticky; top: 0; z-index: 60;
  height: var(--header-h);
  background: var(--hd-bg);
  border-bottom: 1px solid var(--hd-border);
  box-shadow: var(--hd-shadow);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  transition: background .25s ease, border-color .25s ease, box-shadow .25s ease;
}

/* 首页深色首屏、且还没往下滚 → 透明 + 浅字，与首屏连成一片。
   一旦 site.js 加上 .is-scrolled，这条不再命中，自动回落到上面的浅色页头。

   ★ 背景 / 边框 / 阴影为什么直接写属性，而不像文字颜色那样走 --hd-* 变量
     ----------------------------------------------------------------
     文字颜色继续用变量（--hd-fg / --hd-fg-2 / --hd-hover-*），省掉四条重复规则；
     背景这三项直接声明。原因是它们在三个状态间来回切，直接写只多三行，
     却不依赖"var() 在 :has() 状态翻转时立即重算"这个前提 ——
     本轮排查中出现过「变量已读到 transparent、background 仍是白色」的读数，
     后来发现是测量时序造成的假象（动态摘掉横幅后同步读值），
     并未隔离证实是引擎差异。既然直接声明同样能过、且不受这类时序影响，
     就不把可靠性押在未证实的结论上。 */
body:has(.hero) .site-header:not(.is-scrolled) {
  --hd-fg: #edf1fb;
  --hd-fg-2: #a6b4cf;
  --hd-hover-fg: #ffffff;
  --hd-hover-bg: rgba(255, 255, 255, .10);
  --hd-toggle-bg: rgba(255, 255, 255, .06);
  --hd-toggle-border: rgba(255, 255, 255, .22);
  --hd-toggle-bar: #edf1fb;
  background: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
  /* 透明态没有"毛玻璃"可糊，留着反而会在部分安卓浏览器上出现一条灰带 */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* 横幅出现时（本质只影响首页 —— 内页默认已是白底深字）：
   必须压过上面那条，否则浅字压在浅底红条上，导航整排消失。
   优先级 (0,4,1) > (0,3,1)，选择器里写死，不靠源码顺序去赌。 */
body:has(.hero):has(.launch-banner:not([hidden])) .site-header {
  --hd-fg: var(--ink);
  --hd-fg-2: var(--ink-2);
  --hd-hover-fg: var(--brand);
  --hd-hover-bg: var(--brand-light);
  --hd-toggle-bg: #ffffff;
  --hd-toggle-border: var(--line);
  --hd-toggle-bar: #33405a;
  background: rgba(255, 255, 255, .84);
  border-bottom-color: var(--line);
  box-shadow: 0 1px 16px rgba(7, 13, 27, .06);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
}

.header-inner { height: var(--header-h); display: flex; align-items: center; gap: 26px; }

.brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: 700; font-size: 18px;
  color: var(--hd-fg);
  transition: color .25s ease;
}

/*
 * 顶栏窄屏：品类词隐藏，只留品牌名「搭聆」。
 *
 * ★ 为什么要这一条（2026-09-27，实测驱动，不是凭感觉加的）
 *   品牌名由「智能在线客服」（6 字）换成「对搭 · 智能客服系统」（11 字）后，
 *   自然宽度从 102px 涨到 150px。而 .brand-text 没有独立规则，它只是
 *   .brand(font-size:17px) 的子项，默认 flex-shrink:1 + white-space:normal ——
 *   可用宽度不足就直接换行，且**不会报错、不会溢出**，只在视觉上折行。
 *
 *   实测 tools/_probe-mobile-brandwidth.cjs（10 档宽度，同一容器换文本对照）：
 *     折行档  旧名「智能在线客服」 → 320 / 360 / 768
 *             「对搭 · 智能客服系统」→ 320 / 360 / 375 / 390 / 414 / 768
 *                                     ← 多了三个 iPhone 主流宽度
 *     320px 下后者折 4 行、前者折 3 行
 *     品牌名「对搭」→ 10 档全部单行（自然宽仅 34px）
 *
 * ★ 2026-09-27 更名「搭聆」后这条规则还留着吗 —— 留，但性质变了
 *   新品牌全称「搭聆智服」只有 4 字，比 11 字短得多，实测 10 档宽度全部单行，
 *   **折行风险本身已经消失**。但这条规则不删：
 *     · 它是「品牌全称一旦变长就自动降级」的保险。将来加英文副标 / 地区词，
 *       窄屏会立刻重新折行，而那时没人会记得当初为什么加它；
 *     · 它还顺手解决了另一件事 —— 窄屏下 4 字的「搭聆智服」会比 2 字的
 *       「搭聆」多占约 34px，顶栏右侧的按钮组在 320px 下会变挤。
 *   留着它是零成本的。**删之前请先读上面那组实测数字。**
 *
 * ★ 断点为什么取 1023 而不是 767
 *   折行**不是随宽度单调消失**的：768px 平板下导航栏出现，反而比 430px 更挤，
 *   品牌名可用宽只剩 99px，同样折 2 行。所以断点必须盖住 768 这一档。
 *
 * ★ 选择器为什么带 .header-inner
 *   页脚的 .brand-text 用的是同一个类，但页脚是纵向排布、宽度充足，
 *   显示完整品牌名更自然，所以限定作用域只压顶栏。
 */
@media (max-width: 1023px) {
  .header-inner .brand-cat { display: none; }
}

/*
 * 品牌 LOGO（v2，2026-09-25）：圆角方块 + 双色平涂 + 透明缝。
 *
 * ★★ 本类的 background 必须是 transparent，这是 v2 最容易做错的一条。
 *    v1 是「蓝底容器 + 一条白缝 stroke」，容器负责颜色、SVG 只画缝。
 *    v2 反过来：SVG 自己铺满两个色块（#2563EB / #14B8A6），
 *    缝是**透明缝** —— 靠两个颜色的天然分界来读，而不是靠白线对比。
 *    容器若还留着蓝底，缝里透出来的就是容器蓝，缝等于消失了。
 *
 *    历史沿革（保留，便于回溯为什么不再用渐变）：
 *      · 2026-09-23 曾把「品牌蓝 → 青」撞色渐变收敛为同色族「亮端 → 暗端」
 *        （#5466ff → #3c48b6），目的是让官网 / 后台侧边栏 / 登录页三处 LOGO
 *        看起来是同一个东西。v2 把配色统一这件事做得更彻底：
 *        三处共用同一份内联几何（brand/mark-glyph.svg）+ 同一组双色。
 *      · 渐变本身在 v2 被去掉了 —— 平涂是消除"AI 生成感"的主要手段，
 *        也让单色印刷不再出现色带。
 *
 * ★ overflow:hidden 不是可选项（v1 沿用至今）：
 *    容器负责圆角（11px = 36 × 30.6%，与 brand 规范的 rx=11/36 同比例），
 *    SVG 里那两条 path 是**直角方块**，全靠这里裁成圆角。
 *    顺带也把缝的两端裁成与边缘平齐的切口。
 *
 * ★ 没有 box-shadow（v1 有，v2 刻意删掉）：
 *    v1 靠 box-shadow 做「被点亮的方块」光晕。但 box-shadow 画在元素**背后**，
 *    背景透明后它会从那条 2.52px 的透明缝里透出来 —— 缝会变成蓝色而不是"空"。
 *    改成 filter:drop-shadow 也不行（模糊会糊进缝里）。所以 v2 不用投影。
 */
.brand-mark {
  width: 36px; height: 36px; border-radius: 11px; flex: none;
  display: grid; place-items: center;
  background: transparent;
  overflow: hidden;
}

.nav { display: flex; align-items: center; gap: 2px; margin-left: 10px; flex: 1; }
.nav a {
  padding: 9px 14px; border-radius: 9px;
  font-size: 16px;
  color: var(--hd-fg-2);
  transition: color .18s ease, background .18s ease;
}
.nav a:hover { color: var(--hd-hover-fg); background: var(--hd-hover-bg); }

.header-actions { display: flex; align-items: center; gap: 10px; }
.header-actions .btn-ghost { color: var(--hd-fg-2); }
.header-actions .btn-ghost:hover { color: var(--hd-hover-fg); background: var(--hd-hover-bg); }

.nav-toggle {
  display: none; width: 44px; height: 44px;
  border: 1px solid var(--hd-toggle-border); border-radius: 12px;
  background: var(--hd-toggle-bg);
  flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  cursor: pointer; padding: 0;
  /* 同 .to-top：<button> 默认字号是 13.3333px，显式声明避免落到刻度外 */
  font-size: 14px;
  transition: background .2s ease, border-color .2s ease;
}
.nav-toggle span { width: 18px; height: 2px; background: var(--hd-toggle-bar); border-radius: 2px; transition: .2s; }

/* ============================ 首屏（深色沉浸） ============================ */
.hero {
  position: relative;
  display: flex; flex-direction: column;
  margin-top: calc(var(--header-h) * -1);   /* 顶到页头底下，页头浮在上面 */
  padding-top: var(--header-h);
  min-height: 100vh;                        /* 老浏览器兜底 */
  min-height: 100svh;                       /* 手机上不被地址栏算进去，正好一屏 */
  overflow: hidden;
  background: var(--dark);
  color: var(--dark-ink-2);
  isolation: isolate;
}

/* 顶部有「禁止对外发布」横幅时，页头不再压在首屏上（被横幅推到下面去了），
   这时首屏既不需要负外边距去贴页头，也不需要给页头让位的上内边距。 */
.launch-banner:not([hidden]) + main .hero {
  margin-top: 0;
  padding-top: 0;
}

/* 极光背景 + 网格，纯 CSS，不引任何图片 */
.hero-bg {
  position: absolute; inset: 0; z-index: 0;
  background:
    radial-gradient(900px 560px at 10% -12%, rgba(79, 70, 229, .46), transparent 62%),
    radial-gradient(760px 480px at 90% 2%, rgba(34, 211, 238, .22), transparent 60%),
    radial-gradient(820px 420px at 52% 112%, rgba(124, 92, 255, .22), transparent 64%);
}
.hero-bg::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, .045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .045) 1px, transparent 1px);
  background-size: 68px 68px;
  -webkit-mask-image: radial-gradient(72% 62% at 50% 26%, #000 0%, transparent 100%);
  mask-image: radial-gradient(72% 62% at 50% 26%, #000 0%, transparent 100%);
}

.hero-inner {
  position: relative; z-index: 1; flex: 1;
  display: flex; flex-direction: column; justify-content: center;
  padding-top: clamp(20px, 3.4vh, 48px);
  padding-bottom: clamp(20px, 3.4vh, 48px);
}

/* 标题独占整幅版心宽度（见 index.html 里的说明） */
.hero-head { max-width: 1180px; }

/* 标题下方才分两栏：左说明 / 右对话演示 */
.hero-body {
  display: grid; grid-template-columns: .92fr 1.08fr;
  gap: 60px; align-items: center;
  margin-top: clamp(22px, 3.4vh, 46px);
}

.eyebrow {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 16px; margin-bottom: clamp(16px, 2.2vh, 26px);
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 999px;
  font-size: 14px; color: var(--dark-ink-2);
  backdrop-filter: blur(6px);
}
.eyebrow .dot {
  width: 7px; height: 7px; border-radius: 50%; background: #34d399;
  box-shadow: 0 0 0 4px rgba(52, 211, 153, .18);
}

.hero h1 {
  /* ★ 2026-10-08：原来是 clamp(36px, min(5.4vw, 7.6vh), 82px) —— 流体字号
       在 1440×900 上算出 68.4px、1024×768 上 55.3px，**任何宽度都落不到主流刻度上**，
       正是"字号随手填"的典型。改成固定 64px，并另配两档断点把它降下来：
         ≤1080px        → 48px（平板）
         ≥1081 且 高度≤760 → 48px（窗口压扁时，保住"首屏不溢出"的老意图）
         手机 ≤760px     → 28px（另有 (屏宽-40)/12 的两行推导，见 760 断点注释）
         手机 ≤400px     → 24px
       视口高度自适应那层意图没有丢，只是从"连续流体"换成了"离散三档"。 */
  font-size: 64px;
  line-height: 1.1;
  letter-spacing: -.022em;
  color: #fff;
  font-weight: 800;
}
.hero h1 em {
  background: linear-gradient(96deg, #7fb0ff, var(--cyan));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero-sub {
  margin-top: clamp(16px, 2.2vh, 26px);
  font-size: 18px;
  line-height: 1.85;
  color: var(--dark-ink-2);
  max-width: 610px;
}

.hero-cta { display: flex; gap: 14px; margin-top: clamp(22px, 3vh, 38px); flex-wrap: wrap; }
.hero .btn-outline {
  background: rgba(255, 255, 255, .06);
  border-color: rgba(255, 255, 255, .20);
  color: #fff;
}
.hero .btn-outline:hover { border-color: rgba(255, 255, 255, .45); background: rgba(255, 255, 255, .12); color: #fff; }

.hero-points {
  list-style: none; padding: 0; margin: clamp(20px, 2.8vh, 34px) 0 0;
  display: flex; flex-wrap: wrap; gap: 12px 14px;
}
.hero-points li {
  position: relative; padding: 8px 16px 8px 34px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 999px;
  font-size: 14px; color: var(--dark-ink-2);
}
.hero-points li::before {
  content: ""; position: absolute; left: 14px; top: 50%; margin-top: -4px;
  width: 11px; height: 6px;
  border-left: 2px solid #34d399; border-bottom: 2px solid #34d399;
  transform: rotate(-45deg);
}
.hero-points b { color: #fff; font-weight: 600; }

/* ---- 右侧对话示意（深色玻璃卡）---- */
/* ★ 这里同时是「3D 跟随」的**场景容器**：
     perspective 定义了观察者在屏幕前方多远。数值越小视角越"广角"，
     倾斜时的透视收缩越夸张；1180px 是实测出来刚好"有立体感但不畸变"的值
     （试过 800 太夸张，气泡边缘会像被拉扯；1600 又几乎看不出纵深）。 */
.hero-visual {
  position: relative;
  perspective: 1180px;
  perspective-origin: 50% 50%;
}
.hero-visual::before {           /* 卡片背后的光晕 */
  content: ""; position: absolute; inset: -14% -10%;
  background: radial-gradient(60% 60% at 60% 40%, rgba(79, 70, 229, .34), transparent 70%);
  filter: blur(26px); z-index: -1;
  /* 光晕反向平移（由 hero-tilt.js 写 --halo-x/y）——倾斜时像光源被甩到另一侧 */
  transform: translate3d(var(--halo-x, 0px), var(--halo-y, 0px), 0);
}
.mock {
  background: rgba(255, 255, 255, .055);
  border: 1px solid rgba(255, 255, 255, .13);
  border-radius: var(--r-xl);
  /* 阴影方向随倾斜反推（--sh-x/y 由 JS 写）：卡往右倒，影子就往左偏 */
  box-shadow:
    var(--sh-x, 0px) var(--sh-y, 40px) 90px -40px rgba(0, 0, 0, .9),
    inset 0 1px 0 rgba(255, 255, 255, .10);
  /* ★★ 不许写 overflow: hidden ——
     overflow 取任何非 visible 值都会把 transform-style 强制压回 flat，
     于是下面所有 translateZ 景深层层全部失效（这是最容易踩、
     而且完全不报错的坑：卡片照样斜，就是"扁"的，看不出哪错了）。
     原先靠父级裁切的圆角改由 .mock-foot 自理，见下。 */
  transform-style: preserve-3d;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.mock-head {
  display: flex; align-items: center; gap: 12px;
  padding: 15px 18px;
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}
.mock-avatar {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 16px;
  background: linear-gradient(135deg, var(--brand), var(--cyan));
  box-shadow: 0 6px 16px -6px rgba(79, 70, 229, .8);
}
.mock-name { font-size: 16px; font-weight: 700; color: #fff; }
.mock-status { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--dark-ink-3); font-weight: 400; }
.mock-status i { width: 6px; height: 6px; border-radius: 50%; background: #34d399; box-shadow: 0 0 0 3px rgba(52, 211, 153, .18); }

.mock-body { padding: 20px 18px; display: flex; flex-direction: column; gap: 11px;
  min-height: min(320px, 26vh); }
.bubble {
  max-width: 86%; padding: 12px 16px; border-radius: 16px;
  font-size: 14px; line-height: 1.7;
}
.bubble.them {
  background: rgba(255, 255, 255, .08); color: #dbe4f5;
  border: 1px solid rgba(255, 255, 255, .08);
  border-bottom-left-radius: 5px; align-self: flex-start;
}
.bubble.me {
  background: linear-gradient(135deg, var(--brand), var(--brand-dark)); color: #fff;
  border-bottom-right-radius: 5px; align-self: flex-end;
  box-shadow: 0 10px 26px -12px rgba(79, 70, 229, .8);
}
.bubble.me.human { background: linear-gradient(135deg, #0f766e, #115e59); box-shadow: none; }
.bubble .tag {
  display: inline-block; margin-right: 7px; padding: 2px 8px;
  background: rgba(255, 255, 255, .22); border-radius: 999px; font-size: 12px;
  font-weight: 400;
}
.mock-tag { align-self: flex-end; font-size: 12px; color: var(--dark-ink-3); font-weight: 400; }
.mock-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid rgba(255, 255, 255, .10);
  background: rgba(0, 0, 0, .18);
  /* 这一层原来靠 .mock 的 overflow:hidden 裁成圆角；父级现在为了 3D
     景深必须放开 overflow，所以圆角在这里自己交代清楚。 */
  border-bottom-left-radius: var(--r-xl);
  border-bottom-right-radius: var(--r-xl);
}
.mock-input { flex: 1; font-size: 14px; color: var(--dark-ink-3); }
.mock-send { font-size: 14px; font-weight: 600; color: #7fb0ff; }

/* ===========================================================================
   右侧对话演示 · 3D 跟随动效（景深分配 + 光泽）
   ---------------------------------------------------------------------------
   ★ 这套样式的全部效果都建立在 .mock 的 transform-style: preserve-3d 上，
     且**只在 hero-tilt.js 给 .hero-visual 挂上 .is-tilt-ready 后才生效**。
     JS 不加载 / 触屏 / 用户开了「减少动态效果」→ 这一整段等同于不存在，
     卡片保持原来的静态样子，页面一字不少、一寸不移。

   ★ 为什么用 translateZ 分层而不是"整体转一下就完事"：
     整体旋转只是把一张平面照片转个角度，看久了是"贴纸在晃"。
     把头部、气泡、输入区放到不同的 Z 平面上，倾斜时它们相对卡片
     产生不同幅度的位移 —— 这才是"透视法"让人觉得东西是**立体的**的原因。
     幅度刻意压得很小（12~30px）：景深是让人"感觉到"的，
     一旦大到看得出层次错位，就从高级变成了事故。

   ★ z 值不是随手排的，按"在真实聊天窗口里谁离玻璃更近"来定：
       底部输入区  最近的交互面  → 26 / 18px
       自己的气泡  用户自己发的  → 24px
       AI 用时提示 浮在气泡上方  → 30px（最贴玻璃，像贴在表面的一行小字）
       对方的气泡  最"贴屏"的层  → 12px
       头像        压在标题行上  → 26px
   ========================================================================= */
.hero-visual.is-tilt-ready .mock-avatar { transform: translateZ(26px); }
.hero-visual.is-tilt-ready .bubble.them { transform: translateZ(12px); }
.hero-visual.is-tilt-ready .bubble.me { transform: translateZ(24px); }
.hero-visual.is-tilt-ready .mock-tag { transform: translateZ(30px); }
.hero-visual.is-tilt-ready .mock-input { transform: translateZ(18px); }
.hero-visual.is-tilt-ready .mock-send { transform: translateZ(26px); }

/* 景深层的"浮起 / 落下"用 transition 平滑，0.46s 比倾斜到位（约 0.2s）慢一拍，
   于是看起来是「卡片先转过去，气泡再跟着浮起来」—— 这个时间差就是层次感的来源。
   ★★ 这段只管**子元素**。.mock 自己的 transform 由 hero-tilt.js 逐帧写，
      那里绝不能挂 transition，否则快速划动会变成橡皮筋拖尾。
   ★ 之所以选 transition 而不是也交给 JS 插值：类只切换一次、transition 就播一次，
     比每帧再算六个元素的 z 省得多，而且和逐帧写父级 transform 互不干扰。 */
.hero-visual .mock-avatar,
.hero-visual .mock .bubble,
.hero-visual .mock-tag,
.hero-visual .mock-input,
.hero-visual .mock-send {
  transition: transform .46s cubic-bezier(.22, .8, .3, 1);
}

/* 跟随鼠标的光泽：一层贴着卡片表面的高光，位置由 JS 写进 --mx/--my。
   用 radial-gradient 而不是 linear：点光源比"扫过一条光带"更自然，
   也不会有来回移动时方向突变的跳感。 */
.mock::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity .38s ease;
  background: radial-gradient(
    380px circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, .17),
    rgba(255, 255, 255, .055) 34%,
    transparent 62%);
  transform: translateZ(1px);   /* 贴在卡片平面上方 1px，避免与边框打架 */
}
.hero-visual.is-tilting .mock::after { opacity: 1; }

/* 光晕带 26px 模糊、且悬停期间每帧都在平移。提前提为合成层，
   免得每帧去重做一遍高斯模糊的合成决策。只在真正会动的那段时间挂着，
   指针一走就释放（照常由浏览器自己管，不常驻）。 */
.hero-visual.is-tilting::before { will-change: transform; }

/* 触摸设备 / 减少动效：把上面整段的效果彻底关掉。
   即使 JS 因为某种原因还是挂上了类，这里也兜得住。 */
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .hero-visual .mock-avatar,
  .hero-visual .mock .bubble,
  .hero-visual .mock-tag,
  .hero-visual .mock-input,
  .hero-visual .mock-send {
    transform: none !important;
    transition: none;
  }
  .hero-visual.is-tilting::before { will-change: auto; }
  .mock::after { display: none; }
}

/* ---- 首屏下的四点 ----
   ★ 2026-10-11 加图标（用户："banner 四个有点配个 icon 会更好"）——
     为什么**不**复用 .feature-icon / .pain-icon 那种"浅底渐变圆角方块"：
     那两个都在**白底**板块上（方块亮、图标深），而信任条压在**深色首屏**上，
     照搬会变成四个刺眼的亮块，把视线从主标题抢走。
     所以这里做"深色玻璃"：几乎看不见的白底 + 1px 亮边，图标本身用亮靛蓝，
     与首屏那层极光同色系 —— 安静，但有存在感。
   ★ 高度账（首屏总高是硬约束，不容被撑开）：图标 46px，而右侧文字
     (b 24px×1.4 + 14px 描述) ≈ 54px > 46px ⇒ 行高仍由文字决定，
     .trust-item 的高度与加图标前**完全一致**，首屏不被推高。
     手机档同理（文字 25+20=45px > 图标 36px），四个断点都按这个账配尺寸。
*/
.trust-bar {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px;
  padding-top: clamp(18px, 2.4vh, 30px); padding-bottom: clamp(20px, 3vh, 38px);
  border-top: 1px solid rgba(255, 255, 255, .10);
  --ico-ink: #a5b4fc;   /* indigo-300：深底上清晰，又与 --brand 同源 */
}
.trust-item { display: flex; align-items: center; gap: 13px; }
.trust-ico {
  flex: none;
  width: 46px; height: 46px; border-radius: 13px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .055);
  border: 1px solid rgba(255, 255, 255, .105);
  color: var(--ico-ink);
}
/* 图标本身固定 23px（只缩容器，不缩笔画——笔画跟着缩会变糊） */
.trust-ico svg { width: 23px; height: 23px; }
/* ★ min-width:0 不能省：flex 子项默认 min-width:auto，遇到"7×24"这种
   不含空格的长串会撑破列；手机上信任条排成 2×2，列宽只有 (屏宽-40)/2。 */
.trust-txt { display: block; min-width: 0; }
.trust-item b { display: block; font-size: 24px; color: #fff; line-height: 1.4; font-weight: 700; }
.trust-item .trust-desc { font-size: 14px; color: var(--dark-ink-3); }

/* ============================ 通用板块 ============================ */
.section { padding: var(--sec) 0; }

/* ---- 交替区块的底色：纯色 → 品牌微渐变（2026-10-08）----
   ★ 诉求来源：用户提出"单色的背景可以结合后台的渐变色系，把每个页面背景合理分配"。
     为什么**不能**直接搬后台那套鲜艳渐变：后台是工作界面，长时间盯着表格，
     大面积高饱和渐变会累眼；官网是销售界面，渐变要"感觉得到、但说不出哪里变了"。
     所以这里只借后台渐变的**色相**（靛蓝 → 紫，与 --brand / --violet 同源），
     把明度压在接近白的区间（#f7f8fe → #fdfdff），承担三件事：
       · 与纯白正文区拉开层次，页面才有"呼吸"，不然整页一片白会糊；
       · 让品牌色在**底色**上就有存在感 —— 原来只有按钮和图标是蓝的；
       · 两块角落各带一抹光晕（左上靛蓝、右下紫），是后台渐变的极淡呼应。
   ★ 光晕位置由 --tint-* 控制，下面用 nth-of-type 让相邻区块**错开方向**，
     免得连着两块长得一模一样。
   ⚠️ nth-of-type 数的是 main 里**全部** <section>（不只是 .section-alt），
      以后增删区块会让交替关系挪位 —— 不影响正确性，只是节奏换个方向。
      真出现"两块同向"也不难看，因为渐变极淡，肉眼几乎分不出。 */
.section-alt {
  --tint-1x: 8%;  --tint-1y: -8%;
  --tint-2x: 96%; --tint-2y: 106%;
  /* ★ 2026-10-12：把「极淡点阵」换成 **SVG 噪点**（140px 拼贴、不透明度 .05）。
     换的理由：点阵"眼花"的根因**不是太深，是太规律** —— 规律图案遇到 Windows 常见的
     125% / 150% 缩放，22px 网格落在设备像素的 1.25 / 1.5 倍上，1px 圆点被抗锯齿摊成
     **大小不匀**的灰斑，滚动时产生摩尔纹；调低透明度只能缓解、不能根治。
     噪点完全无规律 ⇒ 原理上不可能产生摩尔纹，同时保住「大块浅底不是一张平涂」的纸张质感。
     色彩由下面两层品牌光晕提供 —— 本次按「加一点点色彩光晕」的口径把两层略作提升
     （靛蓝 .07→.10、紫 .06→.085）。
     ⚠️ data URI 里的 `#` 必须写成 %23（否则被当 fragment 分隔符、整条 URL 静默断掉）；
        属性引号用单引号 + 外层 url() 用双引号，两者不冲突。
     ⚠️ 纹理层必须写在 background 的**第一层**（最靠上）：写成最后一层的话，
        会被下面两层 radial-gradient 的实心区盖掉大半。 */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E") 0 0 / 140px 140px,
    radial-gradient(760px 390px at var(--tint-1x) var(--tint-1y), rgba(79, 70, 229, .10), transparent 60%),
    radial-gradient(620px 340px at var(--tint-2x) var(--tint-2y), rgba(139, 92, 246, .085), transparent 62%),
    linear-gradient(180deg, #f7f8fe 0%, #fdfdff 100%);
}
main > .section-alt:nth-of-type(even) {
  --tint-1x: 94%; --tint-1y: -8%;
  --tint-2x: 4%;  --tint-2y: 106%;
}

.section-dark {
  background: var(--dark);
  color: var(--dark-ink-2);
  position: relative;
  overflow: hidden;
}
/* 深色区的顶部光晕：由单色 → 靛蓝 + 紫双光源（与后台渐变色系一致）。
   ★ 双光源而不是加大单光源：单色光晕在深底上会读成"一块亮斑"，
     两个相邻色相的光叠在一起才有"渐变"的观感，也更接近后台的 135° 双色渐变。 */
.section-dark::before {
  content: ""; position: absolute; inset: -30% -10% auto -10%; height: 70%;
  background:
    radial-gradient(640px 390px at 18% 0%, rgba(79, 70, 229, .32), transparent 66%),
    radial-gradient(560px 340px at 84% 6%, rgba(139, 92, 246, .22), transparent 64%);
  pointer-events: none;
}
.section-dark > * { position: relative; z-index: 1; }
.section-dark h2, .section-dark h3 { color: #fff; }

/* ===========================================================================
   首页改版新增组件（2026-10-08）
   ---------------------------------------------------------------------------
   本节全部是本次改版新加的东西，集中放一处便于日后整体回退。
   命名前缀：cap-（三层能力）/ ai-（AI 专章）/ ds-（产品界面演示）/ mock-typing。
   =========================================================================== */

/* ---------- 一、三层能力总览卡 ---------- */
.cap-card {
  position: relative;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 34px 30px 30px;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: transform .28s cubic-bezier(.16, 1, .3, 1), box-shadow .28s ease, border-color .28s ease;
}
/* 顶部一条品牌渐变细线：把三张互相独立的卡"串成一组"，
   比给每张卡加边框更像一个整体。 */
.cap-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--brand-500), var(--violet));
}
.cap-card:hover { --lift: -5px; box-shadow: var(--shadow); border-color: #cfdaea; }
.cap-no {
  display: block; margin-bottom: 14px;
  font-size: 12px; font-weight: 600; letter-spacing: .16em;
  color: var(--brand);
}
.cap-card h3 { font-size: 24px; margin-bottom: 10px; }
.cap-lead {
  font-size: 16px; line-height: 1.75; color: var(--ink-2);
  font-weight: 500; margin-bottom: 14px;
}
/* 中间那张「答得准」是这一版最想让访客记住的：给一层极淡品牌底把它托起来。
   只做底色差，不加边框加重 —— 三张卡是一个组，中间那张跳出来会破坏整体。 */
.cap-card.is-key { background: linear-gradient(165deg, #fafbff 0%, #fff 58%); }

/* ---------- 二、功能矩阵里「升级故事」的三张卡 ---------- */
/* 为什么只给这三张加重：9 张卡里挑 3 张，是"这里有三件事跟别处不一样"的暗示；
   若更多张加重，页面会碎、反而等于没重点。 */
.feature-card.is-key {
  background: linear-gradient(158deg, #f9fafe 0%, #fff 62%);
  border-color: #dfe3f7;
}
.feature-card.is-key::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(180deg, var(--brand-500), var(--violet));
}
.feature-card.is-key .feature-icon {
  background: linear-gradient(135deg, #e9ecfd, #f3e9fe);
}

/* ---------- 三、首屏对话卡的「正在输入」三点 ---------- */
/* 默认 opacity:0 —— site.js 在首屏可见时点亮一小会儿，然后让位给下面那条回复。
   JS 不执行就永远是隐形的，静态版式一字不移。 */
.mock-typing {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 13px 15px;
  border-radius: 16px 16px 16px 5px;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .08);
  opacity: 0; transition: opacity .3s ease;
}
.mock-typing.is-on { opacity: 1; }
.mock-typing span {
  width: 6px; height: 6px; border-radius: 50%; background: #9fb4d8;
  animation: mock-dots 1.05s ease-in-out infinite;
}
.mock-typing span:nth-child(2) { animation-delay: .16s; }
.mock-typing span:nth-child(3) { animation-delay: .32s; }
@keyframes mock-dots {
  0%, 60%, 100% { opacity: .34; transform: translateY(0); }
  30%           { opacity: 1;   transform: translateY(-3px); }
}

/* 首屏气泡的入场 —— **只做 opacity，绝不动 transform**。
   原因：.bubble 的 transform 已经被 hero-tilt.js 的 translateZ 景深占用，
   两个动画抢同一个属性会互相打架（表现为倾斜时气泡忽然跳位）。
   opacity 不参与布局，也不影响套件读取 #mockReplyText 的完整文本。
   ★ 门控在 .has-reveal 上（reveal.js 在 <head> 同步打的类）：
     JS 被禁用时没有这个类，气泡照常显示，不会定格在全透明的状态。
   ★ .mock-typing 排除在外 —— 它有自己的 opacity 状态机。 */
.has-reveal .mock-body > .bubble,
.has-reveal .mock-body > .mock-tag {
  animation: mock-in .52s cubic-bezier(.16, 1, .3, 1) both;
}
.has-reveal .mock-body > :nth-child(1) { animation-delay: .10s; }
.has-reveal .mock-body > :nth-child(2) { animation-delay: .22s; }
.has-reveal .mock-body > :nth-child(3) { animation-delay: .34s; }
.has-reveal .mock-body > :nth-child(4) { animation-delay: .46s; }
.has-reveal .mock-body > :nth-child(5) { animation-delay: .58s; }
.has-reveal .mock-body > :nth-child(6) { animation-delay: .70s; }
@keyframes mock-in { from { opacity: 0; } to { opacity: 1; } }

/* ---------- 四、AI 能力专章 ---------- */
.ai-layout {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 56px; align-items: start;
}
.ai-points { display: flex; flex-direction: column; gap: 32px; }
.ai-point { position: relative; padding-left: 58px; }
.ai-no {
  position: absolute; left: 0; top: 1px;
  width: 40px; height: 40px; border-radius: 13px;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 600; letter-spacing: .04em;
  color: #d4daff;
  background: linear-gradient(135deg, rgba(99, 102, 241, .34), rgba(139, 92, 246, .26));
  border: 1px solid rgba(255, 255, 255, .14);
}
.ai-point h3 { font-size: 20px; margin-bottom: 9px; }
.ai-point p { font-size: 16px; line-height: 1.85; color: var(--dark-ink-2); margin: 0; }

.ai-demo {
  border-radius: var(--r-xl);
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .12);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  overflow: hidden;
  box-shadow: 0 44px 90px -56px rgba(0, 0, 0, .95);
}
.ai-demo-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}
.ai-demo-title { font-size: 14px; font-weight: 500; color: #fff; }
.ai-demo-live {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--dark-ink-3);
}
.ai-demo-live i {
  width: 6px; height: 6px; border-radius: 50%; background: #34d399;
  box-shadow: 0 0 0 3px rgba(52, 211, 153, .18);
  animation: ai-live 1.7s ease-in-out infinite;
}
@keyframes ai-live { 0%, 100% { opacity: 1; } 50% { opacity: .34; } }

.ai-demo-body {
  padding: 20px 18px 22px;
  display: flex; flex-direction: column; gap: 13px;
  min-height: 306px;
}
.ai-row { display: flex; flex-direction: column; gap: 6px; }
.ai-row.is-q { align-items: flex-start; }
.ai-row.is-a { align-items: flex-end; }
.ai-who { font-size: 12px; color: var(--dark-ink-3); padding: 0 6px; }
.ai-text {
  max-width: 90%; margin: 0; padding: 12px 16px;
  border-radius: 16px; font-size: 14px; line-height: 1.75;
  /* 逐字打字时文本会不断变长，行数跟着跳会让卡片高度抖动。
     min-height 撑住两行的高度，打字过程中卡片不上下弹。 */
  min-height: 46px;
}
.ai-row.is-q .ai-text {
  background: rgba(255, 255, 255, .08); color: #dbe4f5;
  border: 1px solid rgba(255, 255, 255, .08);
  border-bottom-left-radius: 5px;
}
.ai-row.is-a .ai-text {
  background: linear-gradient(135deg, var(--brand), var(--brand-dark));
  color: #fff; border-bottom-right-radius: 5px;
}
/* 「答不了」的那条刻意换一副面孔：不是失败的红，而是一种更沉的靛紫 ——
   要传达的是"它在守边界"，不是"它出错了"。 */
.ai-row.is-a.is-unsure .ai-text {
  background: linear-gradient(135deg, #413d68, #2d2a4d);
  color: #e6e4f5;
}
.ai-src { align-self: flex-end; font-size: 12px; color: var(--dark-ink-3); }
.ai-handoff {
  align-self: center; margin-top: 5px;
  padding: 8px 18px; border-radius: 999px;
  font-size: 12px; color: #ccd6f6;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .14);
}
/* 打字演示的"武装态"：JS 挂上 is-armed 才把各步藏起来逐个揭示。
   JS 不执行 → 没有 is-armed → 下面这句静态文本原样可见，不留空白。 */
.ai-demo.is-armed .ai-row,
.ai-demo.is-armed .ai-src,
.ai-demo.is-armed .ai-handoff { opacity: 0; }
.ai-demo.is-armed .is-on { opacity: 1; transition: opacity .38s ease; }

/* ---------- 五、产品界面演示（取代原「待补充真实素材」空槽位） ---------- */
/* ★ 为什么用 CSS 复刻界面、而不放真实截图：
     R7 守卫禁止全站引入任何外部资源；截图是二进制新增、且会随后台改版失效。
     矢量复刻能跟着主题色走、任何分辨率都清晰、不会过期。
     三张卡展示的是**我们自己的产品**，不涉及编造客户，所以可以放心展示
     —— 这正是原断言「不许编造客户与数据」想守住的那条线。 */
.demo-shot {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--r);
  border: 1px solid var(--line);
  overflow: hidden;
  margin-bottom: 22px;
}
.demo-cap {
  display: inline-block; margin-bottom: 10px;
  padding: 4px 11px; border-radius: 999px;
  font-size: 12px; font-weight: 500;
  color: var(--brand); background: var(--brand-light);
}

/* (1) 访客端挂件 —— 深色底，右下角一个小球 + 两条气泡 */
.demo-widget {
  background: linear-gradient(155deg, #0b1120, #16203a 60%, #1c2a4d);
}
.demo-widget .dw-ball {
  position: absolute; right: 16px; bottom: 14px;
  width: 34px; height: 34px; border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-500), var(--brand-dark));
  box-shadow: 0 8px 18px -8px rgba(0, 0, 0, .9);
}
.demo-widget .dw-ball::after {
  content: ""; position: absolute; inset: 11px 9px;
  border-radius: 3px;
  background: rgba(255, 255, 255, .9);
  clip-path: polygon(0 0, 100% 0, 100% 62%, 62% 62%, 62% 100%, 0 100%);
}
.demo-widget .dw-bubble {
  position: absolute;
  max-width: 68%;
  padding: 8px 13px; border-radius: 13px;
  font-size: 12px; line-height: 1.6;
}
.demo-widget .dw-bubble.them {
  left: 16px; top: 26px;
  background: rgba(255, 255, 255, .10); color: #dbe4f5;
  border-bottom-left-radius: 4px;
}
.demo-widget .dw-bubble.me {
  right: 16px; top: 78px;
  background: linear-gradient(135deg, var(--brand), var(--brand-dark)); color: #fff;
  border-bottom-right-radius: 4px;
  box-shadow: 0 10px 22px -12px rgba(79, 70, 229, .9);
}
.demo-widget .dw-shot {
  position: absolute; left: 16px; bottom: 50px;
  width: 42%; height: 26%;
  border-radius: 8px;
  background: rgba(255, 255, 255, .07);
  border: 1px dashed rgba(255, 255, 255, .34);
}
/* 常驻输入条：给悬浮球让出右侧 34px + 边距，不重叠 */
.demo-widget .dw-input {
  position: absolute; left: 16px; right: 60px; bottom: 14px;
  padding: 7px 13px; border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .14);
  font-size: 12px; line-height: 1.5;
  color: rgba(219, 228, 245, .58);
}
/* 「AI 正在输入」三点指示器：只出现在第 1 幕（访客问完、回答还没出来）。
   ★ 它一半是装饰、一半是必需：第 1 幕只有一条访客气泡，
     实测那一幕里卡片下半部分是一大片空 —— 这三颗点在语义上正好补位
     （"AI 在想），视觉上也把空白填掉了。默认 display:none，
     所以没有 JS 时它根本不出现，me 气泡照常静态显示。 */
.demo-widget .dw-typing {
  position: absolute; right: 16px; top: 78px;
  display: none; align-items: center; gap: 4px;
  padding: 10px 13px; border-radius: 13px;
  border-bottom-right-radius: 4px;
  background: rgba(255, 255, 255, .12);
}
.demo-widget .dw-typing i {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(219, 228, 245, .85);
}
html.is-motion .demo-widget.is-live.is-scene-1 .dw-typing { display: flex; }
html.is-motion .demo-widget.is-live.is-scene-1 .dw-typing i {
  animation: dot-hop 1.1s ease-in-out infinite;
}
html.is-motion .demo-widget.is-live.is-scene-1 .dw-typing i:nth-child(2) { animation-delay: .16s; }
html.is-motion .demo-widget.is-live.is-scene-1 .dw-typing i:nth-child(3) { animation-delay: .32s; }
@keyframes dot-hop {
  0%, 100% { transform: translateY(0); opacity: .55; }
  40%      { transform: translateY(-3px); opacity: 1; }
}
.demo-widget .dw-shot::after {
  content: ""; position: absolute; inset: 26% 30%;
  border-radius: 3px;
  background: rgba(52, 211, 153, .5);
}

/* (2) 坐席工作台 —— 浅底三栏：会话列表 / 对话 / 上下文 */
.demo-desk { background: #fff; display: grid; grid-template-columns: 30% 42% 28%; }
.demo-desk > div { padding: 12px 10px; }
.demo-desk .dd-col { border-right: 1px solid var(--line-2); }
.demo-desk .dd-col:last-child { border-right: 0; background: #fafbff; }
.demo-desk .dd-item {
  height: 15px; border-radius: 4px; background: #eef1f8; margin-bottom: 8px;
}
.demo-desk .dd-item.is-on { background: var(--brand-light); box-shadow: inset 3px 0 0 var(--brand); }
.demo-desk .dd-item.w60 { width: 60%; }
.demo-desk .dd-item.w80 { width: 80%; }
.demo-desk .dd-bub {
  height: 19px; border-radius: 9px; margin-bottom: 9px;
}
.demo-desk .dd-bub.them { width: 76%; background: #eef1f8; }
.demo-desk .dd-bub.me {
  width: 66%; margin-left: auto;
  background: linear-gradient(135deg, var(--brand-500), var(--brand-dark));
  opacity: .9;
}
.demo-desk .dd-tag {
  display: inline-block; margin-bottom: 9px;
  padding: 2px 8px; border-radius: 999px;
  font-size: 12px; color: var(--brand); background: var(--brand-light);
}
.demo-desk .dd-line { height: 9px; border-radius: 4px; background: #eceffa; margin-bottom: 7px; }
.demo-desk .dd-line.w50 { width: 50%; }
.demo-desk .dd-line.w70 { width: 70%; }

/* (3) 数据看板 —— 迷你趋势线 + 三列指标（2026-10-19 改版）
   ★ 原来是"三张数字卡 + 一组竖柱"，读起来像报表格。换成
     「一行标题数字 + 一条趋势线 + 三列底部指标」，与控制台数据看板同一套语言：
     数字当主角、线看走势、指标垫底。
   ★ 为什么用趋势线而不是柱：这张卡只有 430px 宽，7 根柱子在这么窄的
     容器里既读不出量级也看不出走势；曲线在同样的宽度里能表达"在涨"。
   ★ 颜色一律 var(--brand)：官网主色换季时这条线要跟着变，写死就脱节了。 */
.demo-stat { background: linear-gradient(170deg, #fbfcff, #f4f6fd); padding: 17px 18px 13px; }

.demo-stat .ds-top {
  display: flex; align-items: baseline; gap: 7px;
  font-size: 12px; color: var(--ink-3);
}
.demo-stat .ds-top > b {
  font-size: 15px; font-weight: 700; color: var(--ink);
  letter-spacing: -.01em; font-variant-numeric: tabular-nums;
}
.demo-stat .ds-toggle {
  margin-left: auto; display: inline-flex; padding: 2px;
  border-radius: 7px; background: #eceff4; font-size: 10.5px;
}
.demo-stat .ds-toggle i {
  font-style: normal; padding: 2px 7px; border-radius: 5px; color: #667085;
}
.demo-stat .ds-toggle i.on {
  background: #fff; color: var(--ink);
  box-shadow: 0 1px 2px rgba(16, 24, 40, .10);
}

.demo-stat .ds-spark { position: relative; height: 74px; margin-top: 8px; overflow: hidden; }
.demo-stat .ds-spark svg { display: block; width: 100%; height: 100%; }

.demo-stat .ds-ft {
  display: flex; gap: 22px; margin-top: 10px; padding-top: 10px;
  border-top: 1px solid #eceff3;
}
.demo-stat .ds-ft div { display: flex; flex-direction: column; gap: 2px; }
.demo-stat .ds-ft span { font-size: 10.5px; color: var(--ink-3); }
.demo-stat .ds-ft b {
  font-size: 13px; font-weight: 700; color: var(--ink);
  letter-spacing: -.01em; font-variant-numeric: tabular-nums;
}


.section-head { max-width: 780px; margin-bottom: 52px; }
.section-head h2 {
  font-size: 48px;
  line-height: 1.18;
  letter-spacing: -.02em;
}
.section-kicker {
  display: inline-flex; align-items: center; gap: 8px;
  margin-bottom: 16px;
  font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--brand);
}
.section-kicker::before {
  content: ""; width: 22px; height: 2px; background: currentColor; border-radius: 2px;
}
.section-dark .section-kicker { color: #7fb0ff; }
.section-sub { margin-top: 18px; color: var(--ink-3); font-size: 16px; line-height: 1.8; }
.section-dark .section-sub { color: var(--dark-ink-3); }

.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }

/* 功能区的 Bento 栅格：4 列，宽窄相间（宽-窄-窄 / 窄-窄-宽）。
   ★ 三列等分重复两遍，正是这一版要治的"平庸"；宽窄相间才有版式节奏。 */
.grid-bento { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }

/* ============================ 痛点卡 ============================ */
.pain-card {
  position: relative;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 32px 28px;
  box-shadow: var(--shadow-sm);
  transition: transform .28s cubic-bezier(.16, 1, .3, 1), box-shadow .28s ease, border-color .28s ease;
}
/* ★ 2026-10-08：抬升量改由 --lift 变量承担（见文件后段「统一交互层」）。
   原来直接写 transform: translateY(-4px) 会**盖掉**那一层里的 3D 微倾斜
   （同选择器下 :hover 优先级更高），卡片就只会平移、不会跟着指针转。 */
.pain-card:hover { --lift: -5px; box-shadow: var(--shadow); border-color: #cfdaea; }
.pain-card h3 { font-size: 20px; margin-bottom: 12px; }
.pain-card p { font-size: 16px; color: var(--ink-3); line-height: 1.8; }

/* 痛点图标：★ 旧版用的是 🕐🔁🧾 表情符号 —— 各系统渲染成不同的彩色卡通样，
   在一张商务页面里是本页"最不专业"的一处。改成单色内联 SVG，线条风格统一。 */
.pain-icon {
  width: 48px; height: 48px; border-radius: 14px;
  display: grid; place-items: center;
  margin-bottom: 22px;
  background: linear-gradient(135deg, #fff1f0, #ffe4e6);
  color: #e11d48;
}
.pain-icon svg { width: 24px; height: 24px; }

/* ============================ 功能卡（Bento 拼贴） ============================
   ★ 12 栅格，卡宽 4 / 2 / 3 交替，而不是三列等分重复两遍。
     宽卡（4 格）里的图标挪到左侧、文字走右侧，宽度才不浪费。
   ========================================================================== */
.feature-card {
  position: relative;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 32px 30px;
  box-shadow: var(--shadow-sm);
  transition: transform .28s cubic-bezier(.16, 1, .3, 1), box-shadow .28s ease, border-color .28s ease;
  overflow: hidden;
}
.feature-card::after {          /* hover 时从**指针位置**透出一层品牌光（坐标由 site-motion.js 写 --mx/--my） */
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(420px 260px at var(--mx, 0%) var(--my, 0%), rgba(79, 70, 229, .10), transparent 70%);
  opacity: 0; transition: opacity .28s ease; pointer-events: none;
}
.feature-card:hover { --lift: -5px; box-shadow: var(--shadow); border-color: #cfdaea; }
.feature-card:hover::after { opacity: 1; }
.feature-card > * { position: relative; z-index: 1; }

.feature-icon {
  width: 52px; height: 52px; border-radius: 15px; margin-bottom: 20px;
  display: grid; place-items: center;
  background: linear-gradient(135deg, var(--brand-light), #e6f6ff);
  color: var(--brand);
  border: 1px solid rgba(79, 70, 229, .12);
}
.feature-icon svg { width: 25px; height: 25px; }
.feature-card h3 { font-size: 20px; margin-bottom: 11px; }
.feature-card p { font-size: 16px; color: var(--ink-3); line-height: 1.8; }

/* 宽卡（占 2 格）：图标挪到左侧、标题与正文走右侧。
   不加这个，宽卡里就只剩图标下面一大片空白，比等宽还难看。
   ★ align-content: center 而不是 start —— 同一行的卡片高度会被最高的那张
     （正文最长的窄卡）撑齐，宽卡正文天生只有 2 行，靠上放会留出一大片空。 */
.grid-bento > .feature-card.is-wide {
  grid-column: span 2;
  display: grid; grid-template-columns: auto 1fr;
  gap: 4px 24px; align-content: center; align-items: center;
  background: linear-gradient(135deg, #fbfdff, #fff 55%);
}
.grid-bento > .feature-card.is-wide .feature-icon { grid-row: 1 / span 2; margin-bottom: 0; }
.grid-bento > .feature-card.is-wide h3 { grid-column: 2; }
.grid-bento > .feature-card.is-wide p { grid-column: 2; }

/* ============================ 提示条 ============================ */
.notice {
  margin-top: 30px; padding: 18px 22px;
  border-radius: var(--r);
  font-size: 16px; line-height: 1.8;
  border: 1px solid var(--line); background: #fff;
}
.notice-soft { background: var(--bg-alt); color: var(--ink-3); }
/*
 * 「待补充」标记
 * 用于联系方式、企业主体这类**业务事实**尚未提供的位置。
 * 刻意做得显眼：既提醒访客「这里还没写」，也提醒站长「上线前必须填」。
 * 绝不在这类位置编一个假的电话/邮箱 —— 客户打过去是别人家，比空着严重。
 */
.cfg-missing {
  display: inline-block; padding: 2px 9px; border-radius: 7px;
  background: var(--warn-bg); border: 1px dashed #fbbf24; color: #92400e;
  font-size: 14px;
  /* ★ 原为 white-space: nowrap。它有一条很长的文案
     （页脚那条：「待补充：在 site-config.js 的 SITE_CONFIG.contact 里填写…」），
     nowrap 让它的 min-content 固定成 378px，于是把所在栅格撑破：
       390px 手机横向溢出  8px
       360px 手机横向溢出 38px
      1280px 桌面横向溢出 77px（页脚 4 列时）
     390~420px 之间整页能左右拖动，是"不够 H5"的典型症状之一。
     改为允许断行，并让 SITE_CONFIG.contact 这类无空格长串可在任意位置断开。
     max-width:100% 保证它永远不会超出所在列。 */
  white-space: normal;
  overflow-wrap: anywhere;
  max-width: 100%;
  font-weight: 400;
}
.notice-info { background: var(--info-bg); border-color: var(--info-line); color: #075985; }
.notice-warn { background: var(--warn-bg); border-color: #fde68a; color: #92400e; }
.section-dark .notice { background: rgba(255, 255, 255, .06); border-color: var(--dark-line); color: var(--dark-ink-2); }

/* ============================ 定价 ============================ */
.pricing-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; align-items: start; }
.pricing-skeleton {
  grid-column: 1 / -1; padding: 52px; text-align: center;
  color: var(--ink-3); background: #fff; border: 1px dashed var(--line); border-radius: var(--r-lg);
  font-size: 16px;
}
/* 套餐目录拉取失败时的兜底样式：明确是错误，而不是「加载中」 */
.pricing-skeleton.is-error {
  border-color: #fecaca; background: #fef2f2; color: #991b1b;
  line-height: 1.9; font-size: 16px;
}
.pricing-skeleton.is-error strong { color: #b91c1c; }

.pricing-card {
  position: relative;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-xl);
  padding: 34px 30px;
  box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column;
  transition: transform .28s cubic-bezier(.16, 1, .3, 1), box-shadow .28s ease, border-color .28s ease;
}
.pricing-card:hover { --lift: -5px; box-shadow: var(--shadow); }
.pricing-card.is-recommended {
  border-color: rgba(79, 70, 229, .38);
  box-shadow: 0 24px 60px -30px rgba(79, 70, 229, .55), 0 0 0 1px rgba(79, 70, 229, .18);
  --lift: -8px;
  background: linear-gradient(180deg, #fbfdff, #fff 40%);
}
.pricing-card.is-recommended:hover { --lift: -12px; }
.pricing-badge {
  position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
  padding: 5px 16px; border-radius: 999px;
  background: linear-gradient(94deg, var(--brand), var(--cyan)); color: #fff;
  font-size: 12px; font-weight: 700; white-space: nowrap;
  box-shadow: 0 6px 18px -6px rgba(79, 70, 229, .7);
}

/* ==========================================================================
   活动折扣（2026-09-26）
   --------------------------------------------------------------------------
   两件事：促销角标（.pricing-badge-promo）与划线原价（.price-was）。
   数字本身由后端套餐目录 + 活动规则算出，这里只管排版。

   ★ 角标为什么要单独一个定位：.card 上可能同时存在两个角标 ——
     「最常被选」（居中）与「限时 8 折」（靠左）。
     共用居中定位会让它们**直接叠在一起**，两个都看不清。
   ========================================================================== */
.pricing-badge-promo {
  left: 14px; transform: none;
  background: linear-gradient(94deg, #f0503a, #ff8a3d);
  box-shadow: 0 6px 18px -6px rgba(240, 80, 58, .7);
}

/* 划线原价：促销时显示在折后价左侧（月付价与年付价两处共用这个类） */
.price-was {
  color: var(--ink-3); text-decoration: line-through; opacity: .7;
  font-weight: 500; margin-right: 6px;
}
/* 在 42px 的主价格里要压小，否则会跟着一起变成大字、抢走折后价的视觉重心 */
.pricing-price .num .price-was { font-size: 18px; }
/* 年付那行本来就是 14px，再小一档即可 */
.pricing-yearly .price-was { font-size: 14px; font-weight: 400; }

.pricing-name { font-size: 20px; font-weight: 700; color: var(--ink); }.pricing-tagline { margin-top: 8px; font-size: 14px; color: var(--ink-3); min-height: 46px; line-height: 1.7; }
.pricing-price { margin: 22px 0 6px; display: flex; align-items: baseline; gap: 8px; }
.pricing-price .num { font-size: 40px; font-weight: 800; color: var(--ink); letter-spacing: -.03em; line-height: 1.1; }
.pricing-price .unit { font-size: 16px; color: var(--ink-3); }
.pricing-yearly { font-size: 14px; color: var(--ink-3); min-height: 24px; }

/* 「规格」行：套餐的 6 个字段里可量化的部分（坐席 / AI 额度 / 知识库 / 第三方嵌入 / 年度价格）
   值全部来自后端套餐目录，这里只管排版。 */
.pricing-specs { list-style: none; margin: 20px 0 0; padding: 18px 0 0; border-top: 1px solid var(--line-2); }
.pricing-specs li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 5px 0; font-size: 14px; color: var(--ink-3);
}
.pricing-specs li b { font-weight: 600; color: var(--ink); text-align: right; }
/* 有规格行时，下面的能力清单不再自己画一条分隔线，免得两条线挨在一起 */
.pricing-specs + .pricing-features { border-top: 0; margin-top: 12px; padding-top: 2px; }

.pricing-features { list-style: none; padding: 22px 0 0; margin: 22px 0 0; border-top: 1px solid var(--line-2); flex: 1; }
.pricing-features li { position: relative; padding: 8px 0 8px 30px; font-size: 16px; color: var(--ink-2); font-weight: 400; }
/* 图标 = 「圆底 + 白勾」两层：::before 画 18px 实心圆，::after 画勾压在圆上。
   ★★ 为什么不用"裸勾"（2026-10-15 用户提议改的）
     裸勾只能靠**颜色**区分（绿勾 / 灰勾），11 行扫下来几乎分不出哪几行是灰的；
     改成圆底之后变成「绿实心圆 + 白勾」对「淡灰圆底 + 灰勾」——
     形状与明度一起变，扫视成本大幅下降。这也是主流定价表的通行做法。
   ★ 用 top:50% + margin-top 居中，而不是写死 top:16px ——
     清单字号在移动端档会降到 14px，写死 top 会让圆点偏离行中线。 */
.pricing-features li::before {
  content: ""; position: absolute; left: 0; top: 50%; margin-top: -9px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--ok);
}
.pricing-features li::after {
  content: ""; position: absolute; left: 4px; top: 50%; margin-top: -3px;
  width: 8px; height: 4px; border-left: 2px solid #fff; border-bottom: 2px solid #fff;
  transform: rotate(-45deg);
}
/* ---------------------------------------------------------------------------
   未包含项：三档列的是**同一份完整清单**，本档没有的那几项灰显。

   ★★ 2026-10-15 用户第一次反馈"区分不明显、一眼看不太清" ⇒ 先把颜色拉开；
      第二次提议"淡灰色圆点加勾图标" ⇒ 定型为现在这版：**三处一起灰**，
      少任何一处都会出现"半灰"的怪状态：
        ① 文字：已包含 #33405a / 400（对白 10.4:1）／未包含 #97a3b5 / 300（2.6:1）
        ② 圆底：已包含 --ok 亮绿 ／ 未包含 #e8edf5 淡灰
        ③ 勾  ：已包含 #fff 白 ／ 未包含 #aab5c5 灰

   ⚠️ 勾改由 ::after 画之后，判据也要跟着查 ::after ——
      见 tools/verify-plan-entitlements-e2e.js 的 I 节与 _mutate-pricing-card.py。
   --------------------------------------------------------------------------- */
.pricing-features li.is-missing { color: #97a3b5; font-weight: 300; }
.pricing-features li.is-missing::before { background: #e8edf5; }
.pricing-features li.is-missing::after { border-color: #aab5c5; }

/* ---------------------------------------------------------------------------
   清单排版：三列并排时改「两列 + 紧凑行距」
   ---------------------------------------------------------------------------
   【为什么改】2026-10-15 用户反馈"卡片拉很长、一屏看不全"。
     实测（tools/_probe-pricing-height.cjs）：卡高 1004.9px，
     1440×900 下卡片底部超出视口 488.9px —— 差了一整块 CTA。
     大头是清单 452px（11 行 × 40.9px）。
   【改完多少】同视口 692.9px，滚到卡片顶部即可整卡看全，还余 119px。
   【为什么下限是 1081px】两列要求**半宽**装得下最长的
     「开放 API 对接自有系统」：
       · 视口 ≥1340（卡片 405px）→ 零折行
       · 视口 1320（卡片 400px）→ 折 6 行 ⇒ 卡高 713.9，**仍然放得下**
       · 视口 ≤1080 → 定价卡自身退成 2 列/1 列，卡片反而更宽，不需要这套
     所以 1081~1339 这一段是"用少量折行换 290px 缩短"，取舍上划算。
   ⚠️ 改这里的任一条都要重跑 tools/_probe-pricing-height.cjs 对账卡高。
   --------------------------------------------------------------------------- */
@media (min-width: 1081px) {
  .pricing-card { padding: 26px 24px; }
  .pricing-specs { padding-top: 12px; margin-top: 14px; }
  .pricing-specs li { padding: 3px 0; }
  .pricing-features { display: grid; grid-template-columns: 1fr 1fr; column-gap: 20px; }
  .pricing-features li { font-size: 14px; line-height: 1.5; padding: 5px 0 5px 24px; }
}
.pricing-cta { margin-top: 26px; }

/* ============================ 流程 ============================ */
.steps {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px;
  counter-reset: step;
}
.steps li {
  position: relative;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 30px 26px;
  box-shadow: var(--shadow-sm);
}
/* 步骤之间的连接线：桌面端才画，移动端单列时是一条竖线 */
.steps li::after {
  content: ""; position: absolute; top: 44px; left: 100%; width: 22px; height: 1px;
  background: linear-gradient(90deg, var(--line), transparent);
}
.steps li:last-child::after { display: none; }
.step-no {
  display: grid; place-items: center; width: 38px; height: 38px; margin-bottom: 18px;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--brand), var(--cyan));
  color: #fff; font-weight: 700; font-size: 16px;
  box-shadow: 0 8px 20px -10px rgba(79, 70, 229, .8);
}
.steps h3 { font-size: 18px; margin-bottom: 10px; }
.steps p { font-size: 16px; color: var(--ink-3); line-height: 1.8; }

/* ============================ 案例 ============================ */
.case-card {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 28px;
  box-shadow: var(--shadow-sm);
}
.case-card.is-placeholder { border-style: dashed; background: rgba(255, 255, 255, .55); }
.case-logo {
  display: inline-block; margin-bottom: 18px; padding: 6px 14px;
  background: var(--bg-alt); border: 1px dashed var(--line); border-radius: var(--r-sm);
  font-size: 12px; color: var(--ink-3);
  font-weight: 400;
}
.case-card h3 { font-size: 18px; margin-bottom: 10px; }
.case-card p { font-size: 16px; color: var(--ink-3); line-height: 1.8; }
.case-metric { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line-2); }
.case-metric b { display: block; font-size: 24px; color: var(--ink); letter-spacing: -.02em; }
.case-metric span { font-size: 14px; color: var(--ink-3); }

/* ============================ FAQ ============================ */
.faq-list { display: flex; flex-direction: column; gap: 14px; }
.faq {
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  overflow: hidden;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.faq:hover { border-color: #cfdaea; box-shadow: var(--shadow-sm); }
.faq[open] { border-color: rgba(79, 70, 229, .3); box-shadow: 0 12px 30px -20px rgba(79, 70, 229, .45); }
.faq summary {
  cursor: pointer; list-style: none;
  padding: 22px 62px 22px 26px; position: relative;
  font-size: 18px; font-weight: 600; color: var(--ink);
  transition: background .18s ease, color .18s ease;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { background: var(--bg-alt); }
.faq summary::after {
  content: ""; position: absolute; right: 26px; top: 50%; width: 10px; height: 10px;
  border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3);
  transform: translateY(-70%) rotate(45deg); transition: transform .22s ease;
}
.faq[open] summary::after { transform: translateY(-30%) rotate(-135deg); }
.faq[open] summary { color: var(--brand); }
.faq-body { padding: 0 26px 24px; font-size: 16px; color: var(--ink-3); line-height: 1.85; }
.faq-body p + p { margin-top: 12px; }
.faq-body b { color: var(--ink-2); }

/* ============================ 下单区（深色） ============================ */
.order-layout { display: grid; grid-template-columns: 1.15fr .85fr; gap: 28px; align-items: start; }

.card-dark {
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--dark-line);
  border-radius: var(--r-xl);
  padding: 30px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* 整个下单表单走深色 color-scheme：文本框右下角的缩放把手、文本框内的滚动条
   都会跟着变深，不再是浅色系统控件贴在深色卡片上（那个把手原来很扎眼）。 */
.order-form { color-scheme: dark; }
.order-form .field { margin-bottom: 18px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

.field label { display: block; margin-bottom: 9px; font-size: 14px; font-weight: 600; color: #e2e8f0; }
.field label i { color: #fca5a5; font-style: normal; }

/* ★ 输入框 16px：低于 16px 时 iOS Safari 一聚焦就会把整页放大，
   用户得手动缩回去 —— 这是"不够 H5"最典型的体感问题。

   ── 2026-09-25 改版（原来是 background: rgba(255,255,255,.06) + border .16 + r14）──
   ① 底色改「实底」--field-bg = #141c2e。原来那层 6% 白叠在卡片上，实测渲染成
      中性灰 (31,33,40) —— 一整块没有色相的大灰块，尤其备注框，看着像"没上过
      样式的原生控件"。换成同色系的深蓝灰之后，靠的是"色相差别"而不是"亮度差别"
      把框和卡片分开（★ 亮度其实略降：卡片 17,19,27 → 改前框 31,33,40 / 改后框 20,28,46），
      再配一道 1px 投影勾出上下边界。
      ★ 这里必须用 background-color，不能用 background 简写 ——
        简写会把下面 select 的 background-image 一起重置成 none，箭头就没了。
   ② 圆角 14 → 12px：52px 高的控件配 14px 已经接近胶囊，收紧一点更像"输入框"。
   ③ 下拉箭头改为自绘：见 --chevron 的注释。
   ④ color-scheme: dark —— 让下拉弹层也按深色渲染，不再弹出白底列表。
   ⑤ 占位符 #64748b → #7e8ea6 并显式 opacity:1。
      这是肉眼最明显的一处：占位符对比度 3.4:1 → 5.1:1（正文 14.7:1 → 15.5:1）。
      另外 Firefox 默认会给占位符再叠一层透明度，不写 1 的话三家浏览器不一致。 */
.field input, .field select, .field textarea {
  width: 100%; padding: 13px 15px;
  background-color: var(--field-bg);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 12px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .35);
  /* font-weight: inherit 不是可有可无的：UA 给表单控件写的是
     `font: 400 13.33px Arial` 这条简写，会把字重就地钉在 400，
     body 上的 Light 传不进来 —— 下单表单的字会比周围正文粗一档。
     （font-family / font-size 那两行同理，只是这两项早就写了。） */
  color: #f1f5f9; font-size: 16px; font-family: inherit; font-weight: inherit; line-height: 1.5;
  min-height: 48px;
  transition: border-color .18s ease, background-color .18s ease, box-shadow .18s ease;
}
.field input:hover, .field select:hover, .field textarea:hover {
  background-color: var(--field-bg-hover);
  border-color: rgba(255, 255, 255, .22);
}
.field textarea { min-height: 84px; }
.field input::placeholder, .field textarea::placeholder { color: #7e8ea6; opacity: 1; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--brand); background-color: var(--field-bg-focus);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, .24);
}

/* ---- 下拉：去掉原生控件外观，改用自绘箭头 ----
   background-position 是相对 padding-box 的，所以 right 16px 就是"离边框 16px"；
   箭头本身宽 16px，于是箭头中心落在 16 + 8 + 1(边框) = 25px 处，
   稳稳让开 12px 的圆角（改版前原生箭头只离 9.1px，落进圆角里）。
   右侧 46px 内距是为了文字不会钻到箭头底下，配 text-overflow 兜底截断。 */
.field select {
  padding-right: 46px;
  appearance: none; -webkit-appearance: none;
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 16px 16px;
  text-overflow: ellipsis;
  color-scheme: dark;
}
.field select:hover, .field select:focus { background-image: var(--chevron-on); }
.field select::-ms-expand { display: none; }        /* 老 Edge / IE：去掉自带箭头 */
.field select option { background-color: var(--field-bg); color: #f1f5f9; }
.field textarea { resize: vertical; }

.field-error { margin-top: 8px; font-size: 14px; color: #fca5a5; min-height: 0; }
.field-hint { margin-top: 9px; font-size: 14px; color: var(--dark-ink-3); }

.amount-preview {
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
  margin: 6px 0 20px; padding: 18px 22px;
  background: linear-gradient(135deg, rgba(79, 70, 229, .20), rgba(34, 211, 238, .10));
  border: 1px solid rgba(79, 70, 229, .34);
  border-radius: var(--r);
}
.amount-label { font-size: 14px; color: #a5c8ff; }
.amount-value { font-size: 32px; font-weight: 800; color: #fff; letter-spacing: -.02em; line-height: 1.2; }
.amount-note { font-size: 14px; color: var(--dark-ink-3); }

.form-foot { margin-top: 14px; font-size: 14px; color: var(--dark-ink-3); line-height: 1.75; }

/* ---- 右侧 ---- */
.order-side { display: flex; flex-direction: column; gap: 20px; position: sticky; top: calc(var(--header-h) + 20px); }
.side-card h3 { font-size: 18px; margin-bottom: 14px; }
.check-list { list-style: none; padding: 0; margin: 0; }
.check-list li { position: relative; padding: 8px 0 8px 24px; font-size: 16px; color: var(--dark-ink-2); line-height: 1.75; }
.check-list li::before {
  content: ""; position: absolute; left: 2px; top: 17px;
  width: 11px; height: 6px; border-left: 2px solid #34d399; border-bottom: 2px solid #34d399;
  transform: rotate(-45deg);
}
.side-divider { height: 1px; background: var(--dark-line); margin: 20px 0; }
.side-note { font-size: 14px; color: var(--dark-ink-3); line-height: 1.85; }
.side-note a { color: #7fb0ff; }

.result-badge {
  display: inline-block; margin-bottom: 16px; padding: 6px 15px;
  background: rgba(16, 185, 129, .16); border: 1px solid rgba(16, 185, 129, .38);
  border-radius: 999px; font-size: 14px; font-weight: 700; color: #6ee7b7;
}
.order-no-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }
.order-no {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 16px; color: #fff; word-break: break-all;
}

.result-list { margin: 0 0 18px; display: grid; grid-template-columns: auto 1fr; gap: 10px 18px; font-size: 16px; }
.result-list dt { color: var(--dark-ink-3); }
.result-list dd { margin: 0; color: #e2e8f0; text-align: right; }

.pay-instruction {
  padding: 16px 18px; margin-bottom: 18px;
  background: rgba(255, 255, 255, .06); border: 1px solid var(--dark-line);
  border-radius: var(--r); font-size: 14px; color: var(--dark-ink-2); line-height: 1.8;
}

/* ================= 订单结果区 · 动作 / 客服码 / 凭证 =================
   客户拿到订单号之后真正要做的三件事：
     ① 复制订单信息发出去  ② 存一份凭证图片  ③ 有疑问找到客服
   这三块归成一组，放在金额与付款指引下方 —— 那是客户读完信息后视线停留的位置。
   ==================================================================== */

.result-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
.result-actions .btn { min-height: 46px; padding: 10px 12px; font-size: 14px; white-space: nowrap; }
/* 生成图片期间按钮要"看起来被按住了"，否则客户会连点、弹两层浮层 */
.result-actions .btn:disabled { opacity: .6; cursor: default; transform: none; box-shadow: none; }
.actions-hint { margin: 0 0 20px; font-size: 14px; line-height: 1.75; color: var(--dark-ink-3); font-weight: 400; }

.contact-qr {
  margin-bottom: 20px; padding: 18px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--dark-line); border-radius: var(--r);
}
.contact-qr-head { margin-bottom: 14px; font-size: 14px; font-weight: 700; color: #cbd5e1; }
.contact-qr-body { display: flex; gap: 18px; align-items: center; }
/* 二维码一律放在白色衬底里：深色卡片上直接贴黑白码，边缘没有静默区，
   部分扫码器会识别不出（静默区是 QR 规范的硬要求，不是美观问题） */
.contact-qr-code {
  flex: none; display: flex; align-items: center; justify-content: center;
  min-width: 166px; min-height: 166px; padding: 10px;
  background: #fff; border-radius: 12px;
}
.contact-qr-body.is-text-only .contact-qr-code { display: none; }
.contact-qr-img { display: block; width: 166px; height: 166px; object-fit: contain; border-radius: 8px; }
.contact-qr-broken { font-size: 12px; line-height: 1.7; color: #b45309; text-align: center; font-weight: 400; }
.contact-qr-broken span { font-size: 12px; color: #94a3b8; font-weight: 400; }
.contact-qr-info { flex: 1; min-width: 0; }
.contact-qr-tip { margin: 0 0 10px; font-size: 14px; line-height: 1.75; color: var(--dark-ink-2); }
.contact-qr-list { list-style: none; margin: 0; padding: 0; }
.contact-qr-list[hidden] { display: none; }
.contact-qr-list li { padding: 3px 0; font-size: 14px; line-height: 1.7; color: var(--dark-ink-2); }
.contact-qr-list li b { font-weight: 500; color: var(--dark-ink-3); }

/* ================= 右下角客服浮标的安全留白 =================
   ★ 问题（实测现象，不是推测）
     挂件的悬浮球是 position: fixed（60×60，桌面上右边 24px、底边 84px），
     钉在视口上，会扫过页面右下角的一切内容。
     而下单区的订单结果卡就在**右列**，客户拿到订单号后要看的客服二维码、
     说明文字与联系方式正好落在它的扫掠带里 —— 实测在 1080px 与 800px
     两个宽度上（下单区已变单列）浮标压住了二维码区右下角 25px。

   ★ 怎么修：给卡片左侧内容让出一条与浮标等宽的安全带 ——
     统一手段是**加卡片右侧内边距**，内容整体左移，浮标扫过的区域留空。
     桌面 84px、手机（浮标更贴边）60+16+2px，见下面两段媒体查询。
     ⚠️ 为什么不是"把二维码挪到右边"：浮标就在右边 ——
        那样等于把它直接送进浮标的扫描带，方向正好反了。

   ★ 为什么挂在 html.has-cs-widget 下面，而不是无条件生效
     挂件可以在 site-config.js 里关掉（widget.enabled = false）。
     关掉之后还留着 80 多像素空当，等于凭空少一块版面 —— 那也是一种错。
     chat-widget.js 在确认要挂出浮标时才给 <html> 加这个类。

   ★ 数值怎么来的：浮标 60px + 距右边 24px = 84px。
     .card-dark 原有内边距是 30px，所以写成 calc(30px + 84px)。
     ⚠️ 这里的 30px 必须跟着 .card-dark 的 padding 一起改 ——
         写成固定值 114px 的话，哪天卡片内边距调了，安全区就静默失效了。

   ★★ 断点为什么是 1500px（不是 1439px）—— 推导，不是拍的
     .wrap 上限 1320px 且水平居中。视口为 V 时，右侧自然余量
       = (V − 1320) / 2 + 28（wrap 内边距）
     要靠这个余量自己保住 92px（浮标 84 + 呼吸 8），需要
       (V − 1320) / 2 ≥ 64  →  V ≥ 1448
     所以 1448px 以下必须由安全带兜住。第一版把断点写成 1439px，
     等于 1440~1447 这 8px 处在"临界裸奔"状态 —— 加上浏览器滚动条
     宽度不一（15 / 17px），很容易静默漏掉。取 1500px 留 52px 缓冲。

   ★★ 为什么"单列之后改为只留底部高度"是错的（第一版的错误，已被实测打回）
     第一版在 ≤1080px 处把卡片 padding-right 改回 30px，理由是
     "下单区已变单列、卡片通栏，底部留高度比右侧留宽度更合适"。
     实测（tools/_verify-site-qr-ui.cjs 第二轮，1080px 与 800px 两个宽度）
     这个判断恰好反了：
       · 单列后 .contact-qr 变成**通栏**，右边缘比双列时更靠外，
         横向反而更容易进浮标带；
       · padding-bottom 只把**内容**往上推，推不动它自己的矩形 ——
         而"有没有被压住"是按矩形相交判定的，所以那条规则等于没生效；
         更糟的是 padding-bottom 把块本身撑得**更高**，反而扩大了
         与浮标带纵向相交的机会。
     结论：单列同样要用横向安全带（加卡片右内边距），只是手机屏窄，
     数值要按浮标更贴边（右边距 16px）相应减小，别把版面挤没。
   ============================================================ */

@media (max-width: 1500px) {
  html.has-cs-widget .order-side .side-card {
    padding-right: calc(30px + 84px);
  }
  /* 卡片底部也留一格：滚到最底时，最后一个按钮不至于正好停在浮标下方 */
  html.has-cs-widget .order-side {
    padding-bottom: 96px;
  }
}

/* 手机：浮标右边距从 24px 缩到 16px（见 widget/src/styles.ts 的 ≤480px 断点），
   安全带按 60 + 16 算，再加 2px 呼吸 = 78px —— 手机版面窄，不照搬桌面的 114px。
   ⚠️ 这两个数字分别写在三个文件里（挂件 styles.ts、本文件、以及验证脚本的几何断言），
      改一边必须改另一边。 */
@media (max-width: 440px) {
  html.has-cs-widget .order-side .side-card {
    padding-right: 78px;
  }
  html.has-cs-widget .contact-qr {
    padding-bottom: calc(18px + 92px);
  }
}

/* ==================== 订单凭证浮层（保存/长按保存） ====================
   桌面给「下载」，移动端提示「长按保存」—— iOS Safari 不支持 <a download>，
   只做下载按钮的话，iPhone 用户点了没有任何反应。
   ==================================================================== */
@keyframes oc-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes oc-pop { from { opacity: 0; transform: translateY(12px) scale(.985); } to { opacity: 1; transform: none; } }

.oc-overlay {
  position: fixed; inset: 0; z-index: 9000;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(5, 7, 15, .8);
  animation: oc-fade .18s ease both;
}
body.oc-lock { overflow: hidden; }
.oc-dialog {
  display: flex; flex-direction: column; width: min(560px, 100%);
  max-height: calc(100vh - 40px);
  background: #fff; border-radius: 16px; overflow: hidden;
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .55);
  animation: oc-pop .22s cubic-bezier(.2, .7, .3, 1) both;
}
.oc-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.oc-head strong { font-size: 16px; color: var(--ink); }
.oc-close {
  padding: 0 6px; border: 0; background: none; cursor: pointer;
  font-size: 24px; line-height: 1; color: var(--ink-3);
}
.oc-close:hover { color: var(--ink); }
.oc-body { padding: 16px; overflow: auto; background: var(--bg-alt); }
.oc-img { display: block; width: 100%; height: auto; border-radius: 10px; box-shadow: 0 6px 20px -8px rgba(7, 13, 27, .22); }
.oc-tip { margin: 12px 0 0; font-size: 14px; line-height: 1.7; color: var(--ink-3); text-align: center; }
.oc-foot { display: flex; gap: 10px; padding: 14px 18px; border-top: 1px solid var(--line); }
.oc-foot .btn { flex: 1; min-height: 46px; }
/* 对话框是白底，默认的 .btn-outline 也是白底 —— 在这里会看不出边界，单独给个浅灰 */
.oc-foot .oc-cancel { background: #f1f4f9; border-color: var(--line); color: var(--ink-2); }
.oc-foot .oc-cancel:hover { background: #e9eef7; color: var(--ink); }

/* 窄屏：两个动作按钮并排会把「保存凭证图片」压到换行，改成竖排更好按 */
@media (max-width: 440px) {
  .result-actions { grid-template-columns: 1fr; }
  .contact-qr-body { flex-direction: column; align-items: stretch; gap: 14px; }
  .contact-qr-code { align-self: center; }
}

/* 窄屏浮层：从底部弹出，像系统原生的分享面板 —— 拇指够得着，
   而且图片上半部分露在外面，方便直接长按保存 */
@media (max-width: 560px) {
  .oc-overlay { align-items: flex-end; padding: 0; }
  .oc-dialog { width: 100%; max-height: 92vh; border-radius: 16px 16px 0 0; }
}

/* ============================ 订单查询 ============================ */
.query-form { display: flex; gap: 14px; }
.query-input {
  flex: 1; padding: 15px 18px; min-height: 52px;
  border: 1px solid var(--line); border-radius: var(--r);
  font-size: 16px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--ink); background: #fff;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.query-input::placeholder { font-family: inherit; font-size: 16px; color: #9aa8bd; }
.query-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 4px rgba(79, 70, 229, .13); }

.query-result {
  margin-top: 26px; padding: 26px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
}
.query-result.is-error { border-color: #fecaca; background: #fef2f2; }
.query-status { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; flex-wrap: wrap; }
.status-pill { padding: 5px 14px; border-radius: 999px; font-size: 14px; font-weight: 700; }
.status-pill.pending { background: var(--warn-bg); color: #92400e; }
.status-pill.paid { background: #ecfdf5; color: #065f46; }
.status-pill.cancelled, .status-pill.expired { background: var(--bg-alt); color: var(--ink-3); }
.query-msg { font-size: 16px; color: var(--ink-2); }
.query-list { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 10px 20px; font-size: 16px; }
.query-list dt { color: var(--ink-3); }
.query-list dd { margin: 0; color: var(--ink); text-align: right; }

/* ============================ 意见反馈 ============================
   ★ 为什么整块做成"白卡片 + 浅底区块"：
     它落在 #query（白底）与 .final-cta（浅底渐变）之间，
     自身用 section-alt 浅底，内部再放一张白卡片 ——
     这样表单边界清晰，且与上方订单查询区不糊在一起。
   ================================================================= */

.fb-form {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: 32px;
  display: grid;
  gap: 22px;
}

/* ★ 必须显式写这一条：.fb-form 自己声明了 display:grid，
   而 [hidden] 的 display:none 来自 UA 样式表，特异度低于类选择器会被覆盖 ——
   结果就是 fbShowDone 里 setting form.hidden = true 完全不起作用。 */
.fb-form[hidden] { display: none; }

/* ---------- 类型分段 ----------
   ★★ 2026-09-25 重做。原来的问题不是"配色不好看"，而是**根本没写样式**：
      这两个按钮只声明了尺寸、圆角、字号、颜色，**没有重置 border / background /
      appearance / font-family**，于是浏览器 UA 样式表那四条直接生效 ——
        · border      → 2px outset 的深色边框，和 .fb-seg 容器自己那道 1px 边
                        叠成"双圈"，看着像两个没上过样式的原生控件；
        · background  → ButtonFace 灰（Windows 上是 #f0f0f0 一类的纯灰，
                        和本站任何一处灰都不同色）；
        · font-family → 按钮不继承页面字体（UA 默认给的是系统 UI 字体），
                        所以那两个标签的字形和页面其它文字**不是一套**。
      ⚠️ 这类缺陷的隐蔽处在于：CSS 里"什么都没写"，review 时最容易滑过去。
         排查办法是数这个控件到底有几条可见的边、底色来自哪条声明。

   为什么选中态改成「实心品牌色」而不是原来的「白底 + 品牌色文字」：
      原来选中态是白药丸，压在浅灰轨道上，两者明度只差一点点 ——
      在亮屏 / 投屏 / 低对比度显示器上，选中和未选中几乎分不出来。
      实心品牌色与轨道是明确的明度反差，而且与下方「提交反馈」主按钮同色，
      整块表单读起来是一套东西，而不是两个互不相干的控件。 */
.fb-seg {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
  background: var(--bg-alt);
  border: 1px solid var(--line);
  justify-self: start;
}

.fb-seg-btn {
  /* ★ 四条 reset 缺一不可 —— UA 会同时给 <button> 上 border / background /
     appearance / font-family，只写其中一条，剩下三条照样露出来。 */
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  font-family: inherit;

  /* 44px 与全站 .btn 的触控下限一致；原来 38px 是"看着还行但点着偏小" */
  min-height: 44px;
  padding: 0 22px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease, box-shadow .16s ease;
}
.fb-seg-btn:hover { color: var(--ink); background: rgba(255, 255, 255, .78); }

.fb-seg-btn[data-active='1'] {
  background: var(--brand);
  color: #fff;
  box-shadow: 0 4px 12px -5px rgba(79, 70, 229, .65);
}
/* 选中态 hover 不能再被上面那条 hover 的白底盖掉 —— 顺序写对还不够，
   [data-active] 那条本身特异度就更高，这里显式补一条是给"以后调顺序"留保险。 */
.fb-seg-btn[data-active='1']:hover { background: var(--brand-dark); color: #fff; }

/* 键盘用户必须看得见焦点：reset 掉 UA 描边后就剩这一条了 */
.fb-seg-btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* ---------- 字段 ---------- */
.fb-field label {
  display: block;
  margin-bottom: 9px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
}
.fb-field label i { color: #ef4444; font-style: normal; }
.fb-optional { font-weight: 400; font-size: 14px; color: var(--ink-3); }

/* ★ 排除 type=file：它是 hidden 的，被套上样式不会出问题，
   但会让下一个人以为"文件框是需要美化的可见控件"。 */
.fb-field input:not([type='file']),
.fb-field textarea {
  width: 100%;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  font-size: 16px;
  font-family: inherit;
  line-height: 1.6;
  color: var(--ink);
  background: #fff;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.fb-field input:not([type='file']):focus,
.fb-field textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px rgba(79, 70, 229, .13);
}
.fb-field input::placeholder,
.fb-field textarea::placeholder { color: #9aa8bd; }

/* 高度可拖：桌面端写长文时能拉大看全，比内部滚动舒服 */
.fb-field textarea { min-height: 120px; resize: vertical; }

/* ---------- 字段级校验失败（2026-10-07 新增）----------
   ★ 用在「联系方式」格：格式不对时给该格单独加红框 + 红标签，
     让访客一眼看出是**哪一格**要改，而不是只有页面级错误条。

   ⚠️ 必须写在 :focus 之后（当前就在之后），否则聚焦时品牌色边框
      会盖掉红框 —— 而那恰好是"点提交 → 焦点跳到该格"的那一刻，
      也就是最需要看到红色的时候。 */
.fb-field.is-invalid input:not([type='file']),
.fb-field.is-invalid textarea {
  border-color: #ef4444;
  box-shadow: 0 0 0 4px rgba(239, 68, 68, .12);
}
.fb-field.is-invalid label { color: #b91c1c; }

.fb-counter {
  margin-top: 8px;
  text-align: right;
  font-size: 12px;
  color: var(--ink-3);
  /* 等宽数字：否则 9/200 → 10/200 时宽度跳动，数字会抖一下 */
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}
.fb-counter[data-full='1'] { color: #ef4444; }

/* ---------- 截图 ---------- */
.fb-images { display: flex; flex-wrap: wrap; gap: 12px; }

.fb-add {
  width: 84px;
  height: 84px;
  border: 1px dashed var(--line);
  border-radius: var(--r);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-3);
  background: #fff;
  cursor: pointer;
  font-size: 24px;
  line-height: 1;
  transition: border-color .16s ease, color .16s ease;
}
.fb-add:hover { border-color: var(--brand); color: var(--brand); }
/* 同上：flex 会盖掉 [hidden] 的 display:none，必须显式收回来 */
.fb-add[hidden] { display: none; }

.fb-thumb {
  position: relative;
  width: 84px;
  height: 84px;
  border-radius: var(--r);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--bg-alt);
}
.fb-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.fb-thumb[data-state='uploading'] img { opacity: .4; }
.fb-thumb[data-state='failed'] { border-color: #fecaca; }
.fb-thumb[data-state='failed'] img { opacity: .35; }

/* 上传中：底部一条来回扫的进度条（不确定进度，不是真实百分比，见 site.js 说明） */
.fb-thumb[data-state='uploading']::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: var(--brand);
  transform-origin: left center;
  animation: fb-progress 1.1s ease-in-out infinite;
}

@keyframes fb-progress {
  0%   { transform: scaleX(.08); opacity: .6; }
  50%  { transform: scaleX(1);   opacity: 1; }
  100% { transform: scaleX(.08); opacity: .6; }
}

@media (prefers-reduced-motion: reduce) {
  .fb-thumb[data-state='uploading']::after { animation: none; transform: scaleX(1); }
}

.fb-thumb-del {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(7, 13, 27, .62);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: background-color .16s ease;
}
.fb-thumb-del:hover { background: #ef4444; }

/* ---------- 错误提示 ---------- */
.fb-error {
  padding: 12px 16px;
  border-radius: var(--r-sm);
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #b91c1c;
  font-size: 14px;
  line-height: 1.6;
}
.fb-error[hidden] { display: none; }
.fb-error:empty { display: none; }

/* ---------- 提交 ---------- */
.fb-submit { width: 100%; }

.fb-note {
  margin: 0;
  text-align: center;
  font-size: 14px;
  line-height: 1.7;
  color: var(--ink-3);
  font-weight: 400;
}

/* ---------- 提交成功 ---------- */
.fb-done {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: 48px 32px;
  text-align: center;
}
.fb-done[hidden] { display: none; }

/* 成功用固定绿（--ok）而不是主色：主色是品牌色、会随站点改版变，
   而"成功"是语义色，不该跟着换。 */
.fb-done-icon { color: var(--ok); margin-bottom: 14px; }
.fb-done-icon svg { width: 56px; height: 56px; }

.fb-done-title { font-size: 24px; font-weight: 700; color: var(--ink); margin-bottom: 8px; }
.fb-done-text { margin: 0 0 24px; color: var(--ink-3); font-size: 16px; line-height: 1.75; }

/* 回执行：编号 + 提交时间。做成浅底小药丸 ——
   要"看得见、能记住"，但不能抢过上面那句谢谢和下面那个按钮。
   等宽数字是必要的：编号和时间都是数字，不等宽的话换行时宽度会跳。

   ★ 用 display:flex + width:fit-content + margin:auto 居中，**不要用 inline-flex**：
     .btn 也是 inline-flex，两个行内级盒子会并排落在同一行 ——
     第一版就是"编号 #71 · 09-25 21:00  [再提一条]"挤在一行。
     块级化之后，编号独占一行压在按钮上方，层级才对。 */
.fb-done-meta {
  display: flex;
  width: fit-content;
  margin: 0 auto 26px;
  align-items: center;
  gap: 9px;
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--bg-alt);
  border: 1px solid var(--line-2);
  font-size: 14px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
/* ★ 必须显式写：上面那句 display:flex 的特异度高于 UA 的 [hidden]{display:none}，
   少这一条 hidden 就完全不起作用（本项目在 .fb-form / .fb-add 上各踩过一次） */
.fb-done-meta[hidden] { display: none; }
.fb-done-meta b { color: var(--ink); font-weight: 700; }
.fb-done-sep { color: #cbd5e1; }

/* ---------- 自动返回填写的倒计时 ----------
   位置在「再提一条」**下面**：那个按钮是主操作（想再写一条就点它），
   倒计时是背景行为，不能跟主操作抢注意力，所以排在后、做得最轻。

   ★ 颜色用 currentColor 一路传下去：整块的色相只由这一处的 color 决定，
     切换状态时改一个变量就够了，不用逐条覆盖（下面 [data-state="off"] 就是）。
   ★ 进度条宽度由 JS 逐帧写，**不设 CSS transition** ——
     一设 transition，暂停/恢复时它会自己再滑一段，看上去像"卡了一下"。
   ★ 因此也没有 prefers-reduced-motion 需要处理：这里唯一的动效就是
     那根条在变宽，而它是"信息"（还剩多久），不是装饰。 */
.fb-count {
  margin: 26px auto 0;
  max-width: 250px;
  color: var(--brand);           /* 跑着的时候：品牌色 */
}
.fb-count[data-state="off"] { color: var(--ink-3); }   /* 停住之后：退回中性灰 */

.fb-count-bar {
  height: 3px;
  border-radius: 999px;
  background: var(--line-2);
  overflow: hidden;
}
.fb-count-bar > span {
  display: block;
  width: 0;                      /* 初始 0；由 site.js 每帧写真实比例 */
  height: 100%;
  border-radius: inherit;
  background: currentColor;
}
/* 停住时把残条再压淡一档：让"冻结在中间"看起来是有意为之，而不是画错了 */
.fb-count[data-state="off"] .fb-count-bar > span { opacity: .5; }

.fb-count-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 9px;
  font-size: 12px;
  line-height: 1.5;
  font-weight: 400;
}
/* 秒数每秒都在变，等宽数字才不会有"6→10"这种宽度跳动 */
.fb-count-text { font-variant-numeric: tabular-nums; }

/* 「取消 / 继续」：做成低存在感的文字按钮。
   ★ 上下留 6px 内距 + 负外边距收回视觉高度 —— 触控目标从 ~19px 抬到 ~31px，
     而文字基线位置一点没动。 */
.fb-count-toggle {
  flex: none;
  padding: 6px 3px;
  margin: -6px 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-3);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(107, 122, 144, .45);
  transition: color .16s, text-decoration-color .16s;
}
.fb-count-toggle:hover { color: var(--ink); text-decoration-color: currentColor; }
.fb-count-toggle:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
  color: var(--ink);
}

/* ============================ 结束区 ============================ */
.final-cta {
  padding: clamp(72px, 8vw, 130px) 0; text-align: center;
  background: linear-gradient(180deg, var(--bg-alt), #fff);
}
.final-cta h2 { font-size: 48px; letter-spacing: -.02em; line-height: 1.18; }
.final-cta p { margin: 18px 0 32px; color: var(--ink-3); font-size: 18px; }

/* ============================ 页脚 ============================ */
/* ★ 2026-10-12：页脚不再是「一整块纯黑」。叠一层 ::before 氛围（G 组合）——
   顶部品牌柔光 + 噪点颗粒 + 微渐变消死黑，三层只用已有品牌色 #4f46e5。
   原来「上方 .final-cta 是浅色区、浅→深一次硬切」的问题靠顶部柔光过渡掉。
   ⚠️⚠️ 绝不直接改 --dark 的色值：它同时被第 468 / 824 行两处深色区共用，
      改了会波及别处。底座保留 var(--dark)，效果只做加法（这层只叠不减）。
   ⚠️ 层序：background 里写在**前面**的画在**上面**。噪点必须排在渐变之前，
      否则会被不透明的渐变整层盖掉（纸感就没了）。
   ⚠️ .footer-inner / .footer-bottom 必须显式 z-index:1 —— position:absolute 的
      伪元素在绘制顺序上高于 static 内容，不压层会整条盖住版权行。
   （原理与 8 套候选对比见 memory/REF-官网视觉与动效层.md §12） */
.site-footer {
  position: relative;
  background: var(--dark); color: var(--dark-ink-3); padding: 72px 0 30px; font-size: 16px;
}
.site-footer::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(1100px 430px at 50% -14%, rgba(79, 70, 229, .20), transparent 68%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E") 0 0 / 140px 140px,
    linear-gradient(180deg, #101a33 0%, #080c1a 50%, #05070f 100%);
}
.footer-inner { display: grid; grid-template-columns: 1.7fr 1fr 1fr 1.2fr; gap: 36px; }
.site-footer > .footer-inner,
.site-footer > .footer-bottom { position: relative; z-index: 1; }
/* ★ min-width:0 是栅格溢出的标准修法，这里必须加：
   页脚联系方式在「未配置」状态会渲染一条很长的「待补充：…」提示，
   而 1fr 轨道的自动最小尺寸默认等于内容的 min-content，
   于是整个页脚栅格会被这条提示撑宽、把页面顶出横向滚动条。
   给栅格项加 min-width:0 后，轨道可以收缩到内容最小宽以下。
   （配合 .cfg-missing 取消 nowrap —— 两者必须一起改：
     只加 min-width:0 会让桌面端 1280 反而溢出 77px。） */
.footer-inner > * { min-width: 0; }
.footer-brand .brand { color: #fff; margin-bottom: 16px; }
.footer-brand p { color: var(--dark-ink-3); font-size: 16px; }
.footer-col h4 { color: #fff; font-size: 16px; margin-bottom: 16px; letter-spacing: .01em; }
.footer-col a { display: block; padding: 6px 0; color: var(--dark-ink-3); transition: color .16s ease, padding-left .16s ease; }
.footer-col a:hover { color: #fff; padding-left: 4px; }
.footer-contact div { padding: 5px 0; }
.footer-contact b { color: var(--dark-ink-2); font-weight: 600; }

.footer-bottom {
  margin-top: 52px; padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, .08);
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 14px; color: #64748b;
}

/* 页脚 ICP 备案号（法定展示项，由 site.js 从 SITE_CONFIG.icp 渲染）
   ★ margin-right:auto 是必需的：.footer-bottom 用的是 space-between，
     多出这一个 flex item 之后，三行元素会摊成"左 / 中 / 右"三段。
     给它一个 auto 右边距，把空白全吸到右侧，备案号就会紧贴版权行，
     #apiTip 仍留在最右 —— 未配置时本元素被 display:none，也不影响原有布局。 */
#icp { margin-right: auto; }
#icp a { color: inherit; text-decoration: none; }
#icp a:hover { color: #94a3b8; text-decoration: underline; }

/* ==================== 上线前置校验横幅 ==================== */
/* 套餐定价 / 销售联系方式 / 官网域名 三项没配齐时显示，配齐后由 JS 隐藏。
   用醒目的红：它不是一条"友情提示"，而是"这个页面现在不能对外发"。 */
.launch-banner {
  background: #fef2f2;
  border-bottom: 1px solid #fecaca;
  color: #7f1d1d;
  font-size: 14px;
  line-height: 1.75;
  padding: 15px 0;
}
.launch-banner b { color: #991b1b; }
.launch-banner ul { margin: 9px 0 0; padding-left: 20px; }
.launch-banner li { margin: 4px 0; }
.launch-banner li b { font-weight: 700; }
.launch-banner-note { display: block; margin-top: 9px; font-size: 14px; color: #b91c1c; font-weight: 400; }

/* ============================ 轻提示 ============================ */
.toast {
  position: fixed; left: 50%; bottom: 36px;
  transform: translateX(-50%) translateY(14px);
  z-index: 90; padding: 13px 24px; border-radius: 999px;
  background: #0f172a; color: #fff; font-size: 16px;
  box-shadow: var(--shadow-lg); opacity: 0;
  transition: opacity .22s ease, transform .22s ease;
  max-width: calc(100vw - 40px);
}
.toast.is-show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ===========================================================================
   统一交互层（2026-10-08）· H5 / CSS3 动效总集
   ---------------------------------------------------------------------------
   ★ 为什么要有这一层：上一版全站只有「滚动渐入 + hover 换阴影」两类动效 ——
     卡片按上去一动不动、区块之间没有过渡、SVG 图标是死图、FAQ 是硬开合，
     整体读起来"很硬"。这里把缺的补齐，全部原生 CSS3 + 原生 JS，
     **不引任何外部库**（R7 守卫禁止外部依赖：离线 / 内网 / 私有化部署下必须一致）。

   ★ 三条安全线（与 reveal.js 同一口径）：
     ① 凡"先把元素藏起来再揭示"的动画，都门控在 JS 加的类上
        （.is-armed / .is-live）—— JS 不跑，内容默认就是**完成态**，一字不少；
     ② 尊重系统「减少动态效果」：文件末尾统一摘掉动画（不是 duration:0）；
     ③ JS 只写 CSS 变量与类名，不写死任何尺寸 —— 版式仍由 CSS 说了算。

   ★ 动效层是**可整体摘掉**的增强件：删掉本段 + site-motion.js，
     页面回到纯静态形态（见 index.html 里那段注释）。
   =========================================================================== */

/* ---------- 1-1. 卡片统一动作：抬升 + 指针微倾斜 ---------- */
/* 用 CSS 变量而不是直接写 transform：抬升（--lift）、倾斜（--tx/--ty）、
   聚光位置（--mx/--my）三件事要能各自变化、互不打架。
   ⚠️ 必须把 .sol-card 一起列进来 —— 它的基础样式在 pages.css（后加载），
      不在这里声明的话，pages.css 里那条 :hover 的 translateY 会盖掉倾斜。 */
.pain-card, .cap-card, .feature-card, .sol-card, .case-card, .pricing-card {
  position: relative;
  transform:
    perspective(1200px)
    rotateX(var(--tx, 0deg))
    rotateY(var(--ty, 0deg))
    translateY(var(--lift, 0px));
  transition:
    transform .42s cubic-bezier(.16, 1, .3, 1),
    box-shadow .42s ease,
    border-color .42s ease;
}

/* ---------- 1-2. 指针聚光：光斑跟着鼠标走 ---------- */
/* border-radius: inherit 让它跟着卡片圆角走 ——
   所以不需要给卡片加 overflow: hidden（那会把定价卡的角标裁掉）。 */
.pain-card::after,
.cap-card::after,
.sol-card::after,
.case-card::after,
.pricing-card::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(340px 240px at var(--mx, 50%) var(--my, 0%),
    rgba(79, 70, 229, .11), transparent 64%);
  opacity: 0; transition: opacity .34s ease; pointer-events: none;
}
.pain-card:hover::after,
.cap-card:hover::after,
.sol-card:hover::after,
.case-card:hover::after,
.pricing-card:hover::after { opacity: 1; }

/* 卡内内容压在光斑之上。
   ★ .sol-card / .pricing-card 是 flex 容器，它们的子项本来就是 flex item，
     z-index 直接生效 —— 不需要（也**不能**）给它们加 position: relative：
     定价卡的角标 .pricing-badge 就是直接子项，加了会把它的 absolute 顶掉。 */
.pain-card > *, .cap-card > *, .feature-card > *, .case-card > * { position: relative; z-index: 1; }
.sol-card > *, .pricing-card > * { z-index: 1; }

/* 三层能力卡的顶部渐变细线：hover 时缓慢横移，静态细线有"在流动"的感觉 */
.cap-card::before { background-size: 200% 100%; transition: background-position .7s ease; }
.cap-card:hover::before { background-position: 100% 0; }

/* ---------- 2. 顶部滚动进度条（由 site-motion.js 创建节点） ---------- */
.scroll-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px;
  z-index: 200;
  background: linear-gradient(90deg, var(--brand), var(--cyan));
  transform: scaleX(var(--sp, 0)); transform-origin: 0 50%;
  pointer-events: none;
}

/* ---------- 3. 功能区分类 Tab ---------- */
/* ★ 默认 display:none —— site-motion.js 打上 .is-live 才出现。
   JS 不跑时不留一条空按钮排，9 张卡照旧全部平铺。 */
.feat-tabs { display: none; flex-wrap: wrap; gap: 10px; margin: -24px 0 32px; }
.feat-tabs.is-live { display: flex; }
.feat-tab {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center;
  cursor: pointer;
  padding: 9px 18px; border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff; color: var(--ink-2);
  font: inherit; font-size: 14px; font-weight: 500;
  transition: color .22s ease, background .22s ease, border-color .22s ease,
              transform .22s ease, box-shadow .22s ease;
}
.feat-tab:hover { border-color: #c9d3ea; transform: translateY(-1px); }
.feat-tab:active { transform: translateY(0) scale(.97); }
/* ★★ 2026-10-08 修正（这是个真 bug，不是风格问题）：
   按钮上写的是 aria-pressed（见 site-motion.js 的 apply()），
   而这里原来写的是 [aria-selected="true"] —— 两个属性对不上，
   于是**被选中的 Tab 和没选中的长得一模一样**，
   "点了 Tab 有反应"这件事整个看不出来。
   改回 aria-pressed 之后，选中态才真的会亮成品牌色。 */
.feat-tab[aria-pressed="true"] {
  color: #fff; border-color: transparent;
  background: linear-gradient(135deg, var(--brand-500), var(--brand-dark));
  box-shadow: 0 10px 24px -12px rgba(79, 70, 229, .85);
}
.feat-tab-n {
  margin-left: 7px; padding: 1px 7px; border-radius: 999px;
  font-size: 12px; font-weight: 600;
  background: var(--bg-alt); color: var(--ink-3);
  transition: background .22s ease, color .22s ease;
}
.feat-tab[aria-pressed="true"] .feat-tab-n { background: rgba(255, 255, 255, .22); color: #fff; }

/* ---------- 4. 过滤：栅格自动重排 + 卡片逐张弹入 ---------- */
/* 过滤态放弃 Bento 的"宽窄相间"，改成自适应等宽 ——
   3 张一组时按 4 列排会在右边留一个空格，很难看。 */
.grid-bento.is-filtered { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid-bento.is-filtered > .feature-card.is-wide {
  grid-column: auto;
  display: block;
}
.grid-bento.is-filtered > .feature-card.is-wide .feature-icon { margin-bottom: 20px; }
.feature-card.is-cap-hidden { display: none; }
.feature-card.is-cap-in {
  animation: feat-pop .46s cubic-bezier(.16, 1, .3, 1) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}
@keyframes feat-pop {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* ---------- 5. SVG 图标描边绘制 ---------- */
/* site-motion.js 量出每条可见描边的长度，写成内联的 dasharray / dashoffset
   （= 先"藏起来"），进视口后把 offset 归零 —— 线条是自己画出来的。
   ⚠️ 只对有 stroke 的图元生效；纯 fill 的（那颗星、实心圆点）跳过；
      本来就有 stroke-dasharray 的（截图卡里那个虚线框）也要跳过，否则会被覆盖。
   JS 不跑 → 没有内联样式 → 图标就是普通静态图标。 */
[data-draw] { transition: stroke-dashoffset .9s cubic-bezier(.33, 1, .68, 1); transition-delay: var(--dd, 0ms); }

/* 虚线选框"爬行"：是"这是可以拖的选框"的暗示，不是装饰性闪烁，所以很慢 */
svg .ants { animation: ants-crawl 3.4s linear infinite; }
@keyframes ants-crawl { to { stroke-dashoffset: -8.8; } }

/* ---------- 6. 流程条：连接线随滚动长出来 ---------- */
html.is-motion .steps li::after { transform: scaleX(0); transform-origin: 0 50%; }
html.is-motion .steps li.is-on::after { transform: scaleX(1); transition: transform .7s cubic-bezier(.16, 1, .3, 1) .12s; }
html.is-motion .step-no { transform: scale(.86); opacity: .55; }
html.is-motion .steps li.is-on .step-no {
  transform: none; opacity: 1;
  transition: transform .5s cubic-bezier(.34, 1.56, .64, 1), opacity .4s ease;
}

/* ---------- 7. 产品实景：进入视口后界面"自己动起来" ---------- */
/* ★ 默认（没有 .is-armed）就是完成态：柱子满高、气泡可见。
   JS 跑起来才先归零、再由 .is-live 逐条揭示。
   ★ 柱子用 scaleY 而不是动画 height —— 高度是 HTML 里写死的百分比，
     用 transform 就用不着把它读出来再写回去。 */
/* 迷你趋势线：进入视口后"描"出来（2026-10-19 卡片改版后取代柱子的 scaleY）
   ★ pathLength="1" 把路径长度归一化 —— dasharray/dashoffset 只要用 1，
     换数据改 path 时不用重算真实长度（这一步最容易被忘掉，忘了线就不画）。 */
.demo-shot.is-armed .ds-line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.demo-shot.is-armed.is-live .ds-line {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.15s cubic-bezier(.16, 1, .3, 1) .15s;
}
.demo-shot.is-armed .ds-area { opacity: 0; }
.demo-shot.is-armed.is-live .ds-area {
  opacity: 1;
  transition: opacity .7s ease .55s;
}

.demo-shot.is-armed .dw-bubble,
.demo-shot.is-armed .dw-shot,
.demo-shot.is-armed .dd-item,
.demo-shot.is-armed .dd-bub,
.demo-shot.is-armed .dd-line,
.demo-shot.is-armed .ds-ft div {
  opacity: 0; transform: translateY(7px);
}
.demo-shot.is-armed.is-live .dw-bubble,
.demo-shot.is-armed.is-live .dw-shot,
.demo-shot.is-armed.is-live .dd-item,
.demo-shot.is-armed.is-live .dd-bub,
.demo-shot.is-armed.is-live .dd-line,
.demo-shot.is-armed.is-live .ds-ft div {
  opacity: 1; transform: none;
  transition: opacity .5s ease, transform .5s cubic-bezier(.16, 1, .3, 1);
  transition-delay: calc(var(--bi, 0) * 85ms);
}
.demo-widget .dw-bubble.them { --bi: 0; }
.demo-widget .dw-bubble.me   { --bi: 2; }
.demo-widget .dw-shot        { --bi: 4; }
.demo-desk .dd-item:nth-child(2) { --bi: 0; }
.demo-desk .dd-item:nth-child(3) { --bi: 1; }
.demo-desk .dd-item:nth-child(4) { --bi: 2; }
.demo-desk .dd-item:nth-child(5) { --bi: 3; }
.demo-desk .dd-bub:nth-child(1) { --bi: 0; }
.demo-desk .dd-bub:nth-child(2) { --bi: 1; }
.demo-desk .dd-bub:nth-child(3) { --bi: 2; }
.demo-desk .dd-bub:nth-child(4) { --bi: 3; }
.demo-desk .dd-line:nth-child(2) { --bi: 0; }
.demo-desk .dd-line:nth-child(3) { --bi: 1; }
.demo-desk .dd-line:nth-child(4) { --bi: 2; }
.demo-desk .dd-line:nth-child(5) { --bi: 3; }
.demo-stat .ds-ft div:nth-child(1) { --bi: 0; }
.demo-stat .ds-ft div:nth-child(2) { --bi: 1; }
.demo-stat .ds-ft div:nth-child(3) { --bi: 2; }

/* 悬浮球呼吸光环：只在这张卡"活了"之后转，静态图里不闪 */
.demo-shot.is-live .dw-ball { animation: dw-halo 3s ease-in-out infinite; }
@keyframes dw-halo {
  0%, 100% { box-shadow: 0 8px 18px -8px rgba(0, 0, 0, .9), 0 0 0 0 rgba(99, 102, 241, .55); }
  60%      { box-shadow: 0 8px 18px -8px rgba(0, 0, 0, .9), 0 0 0 13px rgba(99, 102, 241, 0); }
}

/* ---------- 8. AI 专章三条边界的指针反馈 ---------- */
.ai-point { transition: transform .36s cubic-bezier(.16, 1, .3, 1); }
.ai-point:hover { transform: translateX(4px); }
.ai-no { transition: transform .36s cubic-bezier(.34, 1.56, .64, 1), box-shadow .36s ease; }
.ai-point:hover .ai-no {
  transform: translateY(-2px) scale(1.08);
  box-shadow: 0 10px 26px -12px rgba(99, 102, 241, .9);
}

/* ---------- 9. 首屏极光缓慢漂移 ---------- */
/* 从 identity 起步，所以动画启动的那一刻不会跳一下。
   ★ 只动 .hero-bg（纯装饰层）：不动 .hero-visual ——
     那里是 hero-tilt.js 的场景容器，动它会干扰它的景深断言。 */
.hero-bg { animation: hero-drift 34s ease-in-out infinite; }
/* ---------- 首屏极光画布（2026-10-11，由 site-motion.js 的 initHeroAurora 插入） ----------
   ★ 只有「WebGL 可用」且「两段着色器都编译链接成功」时才会出现这个节点。
     它铺在 .hero-bg 内部、那三层 CSS 光晕之上，但自身背景完全透明 ——
     所以它不是"替换"原背景，只是在上面叠一层会流动的光；
     原来的 CSS 极光依旧是底座，也是它整个挂掉时的兜底。
   ★ 尺寸跟满 .hero-bg：父级 hero-drift 的缩放与位移会连它一起带着走，
     所以永远不会在边缘露出底色（父级是 scale(1.07) 放大，位移量远小于放大余量）。
   ★ pointer-events: none —— 它是装饰，绝不能挡住首屏那两个按钮的点击。 */
.hero-aurora {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
}

/* ---------- 深色区丝绸光带画布（2026-10-12，由 site-motion.js 的 initSilkBand 插入） ----------
   ★ 对应 §⑫。目标区块是 #ai-detail（"它不是「更聪明的搜索框」"那节）。
     只有「WebGL 可用」且着色器编译链接都成功时，这个节点才会出现。
   ★ 与 .hero-aurora 同思路：**叠加而非替换**。
     canvas 背景完全透明（clearColor 全 0），只画会流动的绸缎高光；
     .section-dark 自己的深底、::before 的顶部双光源光晕照常在下面透出来。
     WebGL 不可用 / 编译失败 / 用户开了「减少动态效果」→ 三种情况都退回
     原来的纯深底，一个像素都不缺。
   ★ 为什么单独一个类名而不复用 .hero-aurora：
     两处的缩放/漂移参数不同（首屏有 hero-drift 带着缩放，这一节没有），
     且层级要求相反 —— 这里要贴到 section 的**最底层**（正文之下），
     而 .hero-aurora 是在 .hero-bg 内部、那三层光晕**之上**。
     共用一个类名会把首屏那套参数误带到这一节来。
   ★ pointer-events: none —— 它是装饰，绝不能挡住这一节里的演示区交互。
   ★ ⚠ z-index 这条有个坑：.section-dark > *（第 844 行）会给**所有**直接子元素
     加 position:relative; z-index:1 —— 包括这个 canvas。
     那条选择器权重是 (0,1,1)，本类名只有 (0,1,0)，单靠 z-index:0 压不过它，
     画布会被抬到和 .wrap 同层，导致它盖在正文上（内容看着发白、点击被抢）。
     所以这里必须显式把 position 也写回 absolute，并把选择器提到 (0,2,1)：
     .section-dark > .silk-band 同时拿到 id 所在层级的确定性和足够权重。 */
.section-dark > .silk-band {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
  z-index: 0;
}
/* 开「减少动态效果」时 site-motion.js 根本不会启动（boot 里 RM 直接 return），
   所以这里不需要额外的降级样式：没有 canvas 节点就是原来的深底。 */
@keyframes hero-drift {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(1.4%, 1.6%, 0) scale(1.07); }
  100% { transform: translate3d(-1.2%, .8%, 0) scale(1.03); }
}

/* ---------- 10. 「查看更多」箭头的位移反馈 ---------- */
.page-jump a span { display: inline-block; transition: transform .26s cubic-bezier(.16, 1, .3, 1); }
.page-jump a:hover span { transform: translateX(4px); }

/* ===========================================================================
   常驻动效层（2026-10-08 第二轮）
   ---------------------------------------------------------------------------
   ★ 为什么还要再来一层：第一轮加的动效**几乎全是交互触发型**
     （悬停倾斜、指针聚光、滚动渐显、点 Tab 过滤）——
     它们只有"手放上去"才看得见，页面静止时依旧是一张死图，
     于是得到反馈「看着很呆板很硬」。这一层补的是**不用碰就会动**的部分：
       图标缓浮 + 光环脉冲 / 首屏漂浮光点 / 按钮光泽扫过 /
       区块标题品牌色扫光 / 流程线流动。
   ★ 全部门控在 html.is-motion 上（由 site-motion.js 打）：
       · JS 不跑 → 没有这个类 → 页面一字不动，回到纯静态；
       · 用户在系统里开了「减少动态效果」→ 也不会有这个类 → 同上。
     这和第一轮的 .is-armed / .is-live 是同一套渐进增强纪律。
   =========================================================================== */

/* ---------- 11. 图标「活起来」：缓浮 + 柔光呼吸 ---------- */
/* 图标原本是**死图**：进视口画完描边之后，就永久静止了。
   这里让它持续缓浮（动的是 svg 本身，不是外框），外框再罩一层柔光呼吸。
   ★ 只挑 .pain-icon / .feature-icon：
     它们内部没有任何别的 transform，动它们不会和谁抢属性。
     （.ai-no 不行 —— 它的 :hover 有位移；.step-no 也不行 —— 它的
       opacity 被"逐节点亮"占着。抢同一个属性 = 动画把功能盖掉。） */
html.is-motion .pain-icon svg,
html.is-motion .feature-icon svg { animation: icon-float 6.4s ease-in-out infinite var(--d, 0s); }
html.is-motion .pain-icon::after,
html.is-motion .feature-icon::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  animation: icon-glow 6.4s ease-in-out infinite var(--d, 0s);
}
.pain-icon { --glow: rgba(225, 29, 72, .50); }
.feature-icon { --glow: rgba(79, 70, 229, .55); }
/* ★ 错峰（2026-10-12 修）：同一排卡片不能一起浮、一起亮 —— 那不像"图标在呼吸"，
   而像**整块布局在抖**。
   ⚠️ 原来只写了 nth-child(2)/(3)（负延迟 ≈ 周期 ÷ 3），是照"一排 3 张"设计的；
     但 #featGrid 实际有 **9 张** .feature-card（2026-10-08 从 6 张扩到 9 张时没跟着扩），
     第 4~9 张全部 delay=0s、与第 1 张同相 ⇒ 9 张里 7 张同时又浮又亮，
     用户反馈「眼睛都花了」（探针 _probe-icon-fx.cjs 实测硬证据）。
   现在按卡序号把 6.4s 均分成「张数」份，用 --d 一处定义、svg 与 ::after 共用 ——
   两者周期已统一到 6.4s，同一个 --d 才能保证"同一张卡里的浮与亮同相"。
   ⚠️ .pain-card 真的只有 3 张，(2)/(3) 对它**本来就是对的**（6.4 ÷ 3 ≈ 2.1s）；
     所以 4~9 只写 .feature-card，别顺手也塞给 pain-card。 */
html.is-motion .pain-card:nth-child(2) { --d: -2.1s; }
html.is-motion .pain-card:nth-child(3) { --d: -4.2s; }
html.is-motion .feature-card:nth-child(2) { --d: -0.71s; }
html.is-motion .feature-card:nth-child(3) { --d: -1.42s; }
html.is-motion .feature-card:nth-child(4) { --d: -2.13s; }
html.is-motion .feature-card:nth-child(5) { --d: -2.84s; }
html.is-motion .feature-card:nth-child(6) { --d: -3.56s; }
html.is-motion .feature-card:nth-child(7) { --d: -4.27s; }
html.is-motion .feature-card:nth-child(8) { --d: -4.98s; }
html.is-motion .feature-card:nth-child(9) { --d: -5.69s; }
@keyframes icon-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}
/* A · 柔光呼吸（2026-10-12 替换原 ring-pulse）：一圈柔光缓慢明暗，
   **不外扩、无位移** ⇒ 即使多张同时也不吵。
   对比原「扩散涟漪」ring-pulse：外扩是"从卡里往外冒"的动作，9 张同相时像整屏在闪；
   呼吸只是框沿的亮度变化，信息量小得多。 */
@keyframes icon-glow {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50%      { box-shadow: 0 0 26px -3px var(--glow, rgba(79, 70, 229, .55)); }
}

/* ---------- 12. 图标悬停：四层组合响应（2026-10-08 第三轮） ---------- */
/* ★ 为什么改成"组合"而不是"幅度大一点"：
     只把 -3px 改成 -6px，观感还是"一个方块弹了一下"，看不出设计意图。
     hover 是一个可以**同时表达四件事**的时刻，这里让四层各说一句话：
       ① 外框：抬起 + 放大 + 投影加深（"它被拿起来了"）
       ② 底色：渐变从左上滑到右下（"表面转了一下"）
       ③ 玻璃高光：一道斜光从框外滑进来（"有人照它"）
       ④ 图标本体：放大 + 小角度回摆（"它自己醒了一下"）
       ⑤ 柔光呼吸：周期从 6.4s 收紧到 2.5s（"节奏也变了"）
   ★ 属性归属必须理清，否则互相覆盖：
       · 外框 transform/box-shadow —— 本层独占（第 11 节只动 svg 与 ::after）
       · svg transform —— 被 icon-float 动画占着，故 hover 时先 animation:none 再接管
       · ::after —— 已被 icon-glow 占着，只用 animation-duration 提速，不重写
       · ::before —— 空闲，玻璃高光放这里
   ★ 无需 html.is-motion 门控：hover 是访客主动触发的一次性反馈，
     不加载 JS 时也应当有；四个属性都是 transition 而非持续 animation，
     系统开「减少动态效果」时由文件末尾的降级块统一收口。 */
.pain-icon, .feature-icon {
  position: relative;
  background-size: 175% 175%;
  background-position: 0% 0%;
  transition: transform .34s cubic-bezier(.16, 1, .3, 1),
              box-shadow .34s ease,
              border-color .34s ease,
              background-position .6s cubic-bezier(.16, 1, .3, 1);
}
/* ③ 玻璃高光：一道斜光，平时在框外（不可见），hover 时滑进来 */
.pain-icon::before, .feature-icon::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(135deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, 0) 46%);
  opacity: 0;
  transform: translate(-34%, -34%);
  transition: opacity .3s ease, transform .55s cubic-bezier(.16, 1, .3, 1);
  pointer-events: none;
}
.pain-card:hover .pain-icon,
.feature-card:hover .feature-icon {
  transform: translateY(-4px) scale(1.1);
  background-position: 100% 100%;
  box-shadow: 0 18px 34px -16px rgba(79, 70, 229, .62);
}
.pain-card:hover .pain-icon::before,
.feature-card:hover .feature-icon::before {
  opacity: 1; transform: translate(0, 0);
}
/* ④ 图标本体：先卸掉缓浮动画，再接管 transform —— 否则动画优先级更高，写了也不生效 */
html.is-motion .pain-card:hover .pain-icon svg,
html.is-motion .feature-card:hover .feature-icon svg {
  animation: none;
  transform: scale(1.16) rotate(-7deg);
  transition: transform .42s cubic-bezier(.34, 1.56, .64, 1);
}
/* ⑤ 柔光呼吸提速：节奏变化本身就是一种"响应"（6.4s → 2.5s，与旧 3.8→1.5 同倍率） */
.pain-card:hover .pain-icon::after,
.feature-card:hover .feature-icon::after { animation-duration: 2.5s; }

/* ---------- 13. 主按钮：一道缓慢扫过的光泽 ---------- */
/* 用 ::after 做一条斜向高光在按钮里扫过。overflow: hidden 把高光裁进胶囊形。
   ★ ::after 是自由的（.btn-primary 原本没有伪元素），不要动 .btn 本身。 */
.btn-primary { position: relative; overflow: hidden; }
html.is-motion .btn-primary::after {
  content: ""; position: absolute; top: -60%; bottom: -60%; left: -70%; width: 42%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .55), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
  animation: btn-sheen 6.4s cubic-bezier(.55, 0, .2, 1) infinite;
}
html.is-motion .btn-primary:hover::after { animation-duration: 2.6s; }
@keyframes btn-sheen {
  0%, 64%   { left: -70%; }
  86%, 100% { left: 140%; }
}

/* ---------- 13-b. 首屏主 CTA：一圈缓慢巡游的边框流光（2026-10-11） ---------- */
/* ★ 为什么只给首屏：.btn-primary 全站有几十个（定价卡每张一个、下单按钮、页脚……），
   个个都转边框会变成"满屏在闪"，主 CTA 也就不再是主 CTA。只在 .hero-cta 这一处给，
   它才叫强调。
   ★ 做法是双层背景 + background-clip：
       内层 linear-gradient 用 padding-box 铺满内容区（按钮还是那个靛蓝渐变）；
       外层 conic-gradient 用 border-box，只落在 1px 的边框环上。
     .btn 基础样式本来就写了 border: 1px solid transparent，这一环是白捡的 ——
     不用改尺寸，也不会被 .btn-primary 的 overflow: hidden 裁到（边框在盒内）。
   ★ 流转靠 @property 注册的 <angle>：不支持 @property 的内核只会看到一圈
     **静止**的高光边，既不消失也不报错，属于可接受的降级。
   ★ 保留 ::after 上的 btn-sheen 斜扫光：两者节奏不同（5.2s / 6.4s），
     叠起来是"边框在巡游、内部偶尔掠光"，比单独任何一条都有层次。 */
@property --cta-ang { syntax: "<angle>"; inherits: false; initial-value: 0deg; }

.hero-cta .btn-primary {
  background:
    linear-gradient(135deg, var(--brand), var(--brand-dark)) padding-box,
    conic-gradient(from var(--cta-ang),
      rgba(255, 255, 255, .18) 0turn,
      rgba(255, 255, 255, .90) .10turn,
      rgba(255, 255, 255, .18) .26turn,
      rgba(255, 255, 255, .18) 1turn) border-box;
}
html.is-motion .hero-cta .btn-primary { animation: cta-border-spin 5.2s linear infinite; }
@keyframes cta-border-spin { from { --cta-ang: 0deg; } to { --cta-ang: 360deg; } }

/* ---------- 13-c. 数字：一次"落定"的入场（2026-10-11） ---------- */
/* ★ 为什么是"落定"而不是"逐帧改文本"：
     数字要逐帧变化就得改 textContent，而 verify-site-browser 的
     B4（月价 == 接口月价）/ B6 / B8c（六项字段逐卡相等）/ F5b / F9
     读的都是**精确文本** —— 动画中途被取值必然拿到中间态，断言全红。
     ★ 2026-10-11 第二轮：真正的"数字滚动"已经落地了，见下面的 13-d ——
       它把滚动值画在**伪元素**里（伪元素不进 textContent），所以同样安全。
       本节保留，负责"整块浮上来"这一层；两层叠在一起效果更好。
   ★ 用 animation 而不是 transition：定价卡是异步生成的，
     新节点插入 DOM 时 CSS 动画会自动播一遍，transition 不会。
   ★ 不用 forwards / both：动画跑完就回到 CSS 声明态（正常显示）。
     万一动画因故没跑，数字照样在 —— 不会出现"加了动效反而看不见数字"。 */
html.is-motion .trust-item b,
html.is-motion .pricing-price .num {
  animation: num-settle .68s cubic-bezier(.16, 1, .3, 1);
}
@keyframes num-settle {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- 13-d. 数字滚动（2026-10-11，内页定价卡） ---------- */
/* 由 assets/js/reveal.js 的 §⑬ 驱动：它把"最后一个含数字的文本节点"
   包进 .num-live，然后逐帧写 --roll-txt，滚动值画在 ::after 里。
   ★★ 整个方案的要害：**伪元素内容不进 textContent**。
      B4 / B6 / B8c / F5b / F9 读的都是 textContent（精确取值），
      把滚动值画在伪元素里 ⇒ DOM 文本全程是终值，断言任何时候读都对；
      选中 / 复制 / 屏幕阅读器读到的也都是终值。
   ★ 用 --roll-ink 承接"原色"：滚动时宿主文本要透明（否则和滚动值叠字），
      而伪元素的 color 是从宿主继承的 —— 不给它另留一份原色，
      滚动值会跟着一起透明，就变成"数字不见了"。JS 在加 .is-rolling
      **之前**读 computedStyle.color 写进 --roll-ink。 */
.num-live {
  position: relative;
  display: inline-block;
}
.num-live::after {
  content: var(--roll-txt, '');       /* 不滚动时是空串 ⇒ 伪元素不占任何东西 */
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;                /* 滚动中的位数变化不许把字号/换行抖开 */
  color: var(--roll-ink, currentColor);
}
.num-live.is-rolling { color: transparent; }   /* 只藏宿主文本，伪元素照常显示 */
@media (prefers-reduced-motion: reduce) {
  /* 双保险：JS 已经在 pre-check 里拦掉了「减少动效」，这里再兜一层 ——
     万一滚动中途用户改了系统设置 / 页面被挂起，也绝不会留下"数字不可见"。 */
  .num-live.is-rolling { color: var(--roll-ink, inherit); }
  .num-live::after { content: ''; }
}

/* ---------- 14. 区块标题：一道极慢的品牌色扫光 ---------- */
/* 用 @supports 兜住：不支持 background-clip:text 的内核保持原来的纯色标题，
   绝不会变成"透明的字看不见"。
   ★ --title-ink 是每个语境下的"底色"：浅底用深墨、深底用白色 ——
     不这么做的话，深色区的标题会被刷成深色，等于把标题弄丢了。 */
.section-head h2, .final-cta h2 { --title-ink: var(--ink); }
.section-dark .section-head h2 { --title-ink: #fff; }
/* @keyframes 放在最外层：塞在 @supports 里虽然多数内核也认，
   但一旦条件不匹配，动画名就"不存在"了，排查起来很绕。 */
@keyframes title-sheen {
  0%, 100% { background-position: 128% 0; }
  50%      { background-position: -28% 0; }
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
  html.is-motion .section-head h2,
  html.is-motion .final-cta h2 {
    background-image: linear-gradient(100deg,
      var(--title-ink) 0%, var(--title-ink) 36%,
      var(--brand) 50%,
      var(--title-ink) 64%, var(--title-ink) 100%);
    background-size: 260% 100%;
    background-position: 128% 0;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: title-sheen 11s ease-in-out infinite;
  }
  /* 深色区用亮一点的扫光色（否则品牌蓝压在深蓝底上几乎看不见） */
  html.is-motion .section-dark .section-head h2 {
    background-image: linear-gradient(100deg,
      var(--title-ink) 0%, var(--title-ink) 36%,
      #8fb4ff 50%,
      var(--title-ink) 64%, var(--title-ink) 100%);
  }
}
/* 编号徽章跟着标题一起"呼吸"，但只做极轻的透明度变化 ——
   重了就像页面在闪，反而不专业。 */
html.is-motion .cap-no { animation: badge-breath 6s ease-in-out infinite; }
@keyframes badge-breath {
  0%, 100% { opacity: 1; }
  50%      { opacity: .58; }
}

/* 首屏标题里那两个字（接得住 / 答得准）原本就是渐变字，
   让它**缓慢流动**起来 —— 那是访客第一眼看的东西，它一动，整页就"活"了。 */
html.is-motion .hero h1 em {
  background-size: 240% 100%;
  animation: em-flow 7.5s ease-in-out infinite;
}
@keyframes em-flow {
  0%, 100% { background-position: 0% 0; }
  50%      { background-position: 100% 0; }
}

/* ---------- 14-b. 深色区大标题：Blur Text（逐字模糊落定，2026-10-12） ---------- */
/* 需求原话：「把搭聆官网的深色背景，白色大标题的字 运用 Blur Text 的文字特效。」
   ＋第二轮（同日）：「底部也可以做成这个文字动态」。
   落点 = 全站深色大标题 3 处 ＋ 页尾 CTA 标题（每页都有）：
     index.html   .hero h1                     64px（h1 自身无渐变，只有内部 <em> 是渐变字）
     index.html   #ai-detail .section-head h2  48px（★ 带上面第 14 节的扫光）
     pricing.html #order     .section-head h2  48px（pricing 无 is-motion ⇒ 无扫光）
     所有页       .final-cta h2                48px（★ 带扫光；浅底 ⇒ 渐变两端是深墨 --title-ink）
   门控 = reveal.js 在 <head> **同步**打上的 html.has-blurtext。不打则页面一字不变
   （无 JS / 老内核无 IntersectionObserver / 系统开了"减少动效" / 拆字过程抛错 —— 都不打）。

   ★★ 唯一的硬前提：**渐变必须"下沉到字"**（实测得出，改回去必翻车）
     第 14 节的扫光 = `background-clip:text` + `-webkit-text-fill-color:transparent`，
     而 `-webkit-text-fill-color` **会被子元素继承**。只要给子 span 加 opacity / transform /
     filter **其中任何一个**，span 就被提升为独立绘制层 ⇒ 父级的 background-clip 遮罩
     **不再覆盖它** ⇒ 这个字「填充色是继承来的 transparent、背景又不落在它头上」= **整块消失**。
     实测 tools/_probe-blurtext.cjs：子 span 分别只加 opacity:.35 / 只加 transform /
     只加 filter:blur(6px)，三档**全部整行不见**；而把同一支渐变搬到 span 自己身上
     （span 自带 background-clip、父级不再提供渐变）后，filter 与 transform 都恢复正常。
     ⇒ 所以：**字 span 必须自己扛渐变**（.bt-sheen），且**标题自身的扫光必须让位**
       （.bt-sheen-root 那条），否则两层渐变 + 两条 11s 动画会错相叠色。
     ⇒ .hero h1 自身没有渐变、只有 <em> 有 ⇒ **<em> 整块当一个"字"用，不拆它内部**
       （拆了它的渐变同样会没）。

   ★★ 第二坑（同日第二轮，用户截图抓到）：扫光标题的字 span **必须用 background-repeat: repeat**。
     渐变图只有 2.6W 宽，而扫光行程是 -2.048W → +0.448W ⇒ 两端各有约 0.5W 的"越界区"；
     写 no-repeat 时落在那里的字就**没有背景 + 填充是继承来的 transparent = 隐形**
     （首页 #ai-detail 的 12 个字里 11 个越界 ⇒ 只剩右边 4 个字）。详见下面 .bt-sheen 那条注释。 */

/* 扫光相位（单位 px）。只有注册成 <length> 才能被动画**平滑插值** ——
   不注册的话 0%→50% 之间是直接跳变，扫光会变成"闪一下"。 */
@property --bt-p { syntax: '<length>'; inherits: true; initial-value: 0px; }

html.has-blurtext .bt-w,
html.has-blurtext .bt-a { display: inline-block; }

/* 拆分完成之前先把标题藏着 —— 门控类由 reveal.js 在 <head> 同步打上，
   所以**首次绘制之前**就已经藏好了，不会出现"先看到完整标题、再被拆开重播"的闪。 */
html.has-blurtext .hero h1:not(.bt-ready),
html.has-blurtext .section-dark .section-head h2:not(.bt-ready) { visibility: hidden; }

html.has-blurtext .bt-w,
html.has-blurtext .bt-a {
  opacity: 0;
  filter: blur(14px);
  transform: translateY(-.3em);
  will-change: filter, opacity, transform;
}

/* 出场：`both` 让动画在 delay 期间就停在 from 态（否则会先闪一下终态） */
html.has-blurtext .bt-play {
  animation: bt-in .82s cubic-bezier(.2, .66, .32, 1) both;
  animation-delay: var(--bt-d, 0ms);
}
@keyframes bt-in {
  from { opacity: 0; filter: blur(14px); transform: translateY(-.3em); }
  to   { opacity: 1; filter: blur(0);    transform: translateY(0); }
}
/* 首屏 h1 里那两个字（接得住 / 答得准）本来是渐变字 + em-flow 流动，
   `.bt-play` 的 animation 简写会把它顶掉 ⇒ 这里把两条并列保留。
   （em-flow 不存在时引用不到关键帧，浏览器只是跳过它，不会报错。） */
html.has-blurtext .hero h1 em.bt-play {
  animation: bt-in .82s cubic-bezier(.2, .66, .32, 1) both,
             em-flow 7.5s ease-in-out infinite;
  animation-delay: var(--bt-d, 0ms), 0s;
}

@supports (-webkit-background-clip: text) or (background-clip: text) {
  /* 扫光下沉到字：同一支渐变，但 background-size 用 **px**（2.6 × 标题宽）、
     background-position 扣掉该字自身在标题里的左偏移
     ⇒ 每个字看到的是"整行同一条渐变"的对应切片，视觉上仍是**一道光扫过整行**，
       而不是每个字各扫各的（那样会变成东一块西一块的蓝）。 */
  html.has-blurtext .bt-sheen {
    /* ★ 这支渐变是**兜底**（深色区那一支）。正常路径下 reveal.js 会把标题自己的
       computed backgroundImage 原样内联到每个字上（自动跟随 .section-dark 的亮色变体），
       内联优先。留这一条是为了"JS 那一步万一没写成"时字不会变成隐形。 */
    background-image: linear-gradient(100deg, #fff 0%, #fff 36%, #8fb4ff 50%, #fff 64%, #fff 100%);
    /* ★★ 必须是 repeat（原版第 14 节没设 background-repeat ⇒ 就是默认的 repeat）：
       扫光 --bt-p 从 -2.048W 走到 +0.448W，渐变图（2.6W 宽）只覆盖整行的
       [--bt-p, --bt-p + 2.6W] ⇒ 行程两端各有 0.45W / 0.55W 的"越界区"。
       一旦写 no-repeat，落在越界区里的字**没有背景**、而填充又是从标题继承来的
       transparent ⇒ **整字数消失**（2026-10-12 用户截图抓到：首页 #ai-detail 的
       「它不是「更聪明的搜索框」」只剩右边 4 个字；探针实测 12 字里 11 字越界）。
       渐变两端同色（--title-ink）⇒ repeat 的拼接处无缝，视觉与原版逐像素一致。 */
    background-repeat: repeat;
    background-size: var(--bt-bw, 260%) 100%;
    background-position: calc(var(--bt-p) - var(--bt-ox, 0px)) 0;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
  }
  /* 标题自身让位：把第 14 节那条 title-sheen 换成只驱动 --bt-p 的 bt-sweep
     （简写里 animation-name 换掉了，原动画自然停） */
  html.has-blurtext .section-head h2.bt-ready {
    background-image: none;
    -webkit-text-fill-color: currentColor;
    animation: none;
  }
  html.has-blurtext .section-head h2.bt-ready.bt-sheen-root {
    animation: bt-sweep 11s ease-in-out infinite;
  }
}
/* @keyframes 放最外层（同第 14 节的理由：塞进 @supports 里一旦条件不匹配，
   动画名就"不存在"了，排查起来很绕） */
@keyframes bt-sweep {
  0%, 100% { --bt-p: var(--bt-p0, 0px); }
  50%      { --bt-p: var(--bt-p1, 0px); }
}

@media (prefers-reduced-motion: reduce) {
  /* 双保险：reveal.js 已经在 pre-check 里拦掉了"减少动效"，这里再兜一层 ——
     万一用户中途改系统设置 / 页面被挂起，也绝不留下"标题看不见"。 */
  html.has-blurtext .hero h1:not(.bt-ready),
  html.has-blurtext .section-dark .section-head h2:not(.bt-ready) { visibility: visible; }
  html.has-blurtext .bt-w,
  html.has-blurtext .bt-a { opacity: 1; filter: none; transform: none; }
  html.has-blurtext .bt-play { animation: none; }
  html.has-blurtext .bt-sheen { background-position: 0 0; }
}

/* ---------- 15. 首屏漂浮光点（节点由 site-motion.js 生成） ---------- */
/* 一层极淡的柔光点，在首屏里各自缓慢漂移。整层再跟指针做 ±10px 视差，
   于是光点之间有了"前后深度"，首屏不再是平的。 */
.hero-orbs {
  position: absolute; inset: 0; overflow: hidden;
  pointer-events: none; z-index: 0;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
}
.hz-orb {
  position: absolute; border-radius: 50%;
  background: radial-gradient(circle at 50% 50%,
    rgba(255, 255, 255, .92), rgba(165, 190, 255, .28) 46%, transparent 72%);
  opacity: var(--o, .5);
  will-change: transform;
}
/* 隔三个来一颗偏冷的，白点里有一点蓝，才不像"下雪" */
.hz-orb.is-cool {
  background: radial-gradient(circle at 50% 50%,
    rgba(196, 216, 255, .95), rgba(110, 150, 255, .26) 48%, transparent 74%);
}
html.is-motion .hz-orb {
  animation: orb-drift var(--dur, 16s) ease-in-out var(--del, 0s) infinite;
}
@keyframes orb-drift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(var(--ox, 10px), var(--oy, -30px), 0); }
}

/* ---------- 16. 流程线：连接线上有一道光在流 ---------- */
html.is-motion .steps li::after {
  background-image: linear-gradient(90deg, var(--brand), var(--cyan), var(--brand));
  background-size: 200% 100%;
}
html.is-motion .steps li.is-on::after { animation: line-flow 2.8s linear infinite; }
@keyframes line-flow { to { background-position: -200% 0; } }

/* ---------- 17. FAQ：折叠头悬停与箭头的过渡更利落 ---------- */
.faq summary { transition: color .22s ease, background .22s ease; }
.faq summary:hover { color: var(--brand); }
.faq summary::after { transition: transform .3s cubic-bezier(.16, 1, .3, 1), border-color .22s ease; }
.faq summary:hover::after { border-color: var(--brand); }

/* ===========================================================================
   18. 界面演示播放器（2026-10-08 第三轮）
   ---------------------------------------------------------------------------
   ★ 要解决的问题：三张「产品实景」卡原来是**一张静止的矢量画** ——
     访客看得见"界面长什么样"，看不见"它怎么用"。而这两个界面的价值
     恰恰在动作里：访客打字提问 → 发出去 → AI 接住；坐席的队列在流动。
   ★ 做法：每张卡切成 3 幕，每幕 5 秒，15 秒一轮循环。
     切幕由 site-motion.js 的 initInterfaceDemo() 写 is-scene-N 类驱动；
     每一幕"演什么"全在本节 —— JS 只决定"何时演、演到第几幕"。
   ★ 为什么用 JS 切幕而不是一条 15 秒的 CSS 长动画：
       ① 要"进视口才演"（离屏的卡不该在后台空转）；
       ② 要能悬停暂停（访客想看清某一刻）；
       ③ 要能整体关闭（减少动态效果）；
       ④ 纯 CSS 长动画在切走标签页再回来时会"追赶进度"，画面会跳。
   ★ 三层门控缺一不可：
       html.is-motion（JS 在跑 且 用户没关动效）
     × .demo-shot.is-live（这张卡已经进过视口）
     × .is-scene-N（当前在第几幕）
     少了任何一条，卡就停在"内容全在"的完成态 —— 也就是原来那张静态图。
   ★ 打字机为什么用 clip-path 而不是 width：
       width 动画得先知道文字精确宽度，中文字体下算不准（字宽随字号/字重变）；
       clip-path: inset() 是"从右侧把遮罩收回去"，与字体度量无关，
       配 steps() 就是一格一字，任何字号下都对得齐。
   ========================================================================= */

/* —— 打字机：外层裁切 + 尾部光标 ——
   ★ 用 inline-block 而不是 block：光标 .dw-caret 必须紧跟在文字**同一行**，
     设成 block 的话光标会被挤到下一行去。 */
.demo-widget .dw-type { display: inline-block; white-space: nowrap; overflow: hidden; }
.demo-widget .dw-caret {
  display: inline-block; width: 2px; height: .92em; margin-left: 2px;
  background: currentColor; vertical-align: -1px; opacity: 0;
}
html.is-motion .demo-widget.is-live .dw-type { clip-path: inset(0 100% 0 0); }
@keyframes type-reveal {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 -2% 0 0); }
}
@keyframes caret-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* —— (1) 访客端挂件：访客提问 → AI 接住 → 圈选截图 —— */

/* 第 1 幕「访客提问」：球在跳，同时那句话被逐字打出来。
   ★ 这里刻意**不把 them 气泡藏起来** —— 第一版让第 1 幕只剩一个跳动的球，
     实测截图里整张卡在那一幕看着像"坏了"。教训：循环动画的每一帧
     都必须是完整画面，不能有空幕。
   ★ me 只在第 1 幕隐藏：第 2 幕它就该出现并开始回答。 */
html.is-motion .demo-widget.is-live.is-scene-1 .dw-bubble.me { opacity: 0; }
html.is-motion .demo-widget.is-live.is-scene-1 .dw-ball {
  animation: dw-halo 3s ease-in-out infinite, dw-hop 1.15s cubic-bezier(.34, 1.56, .64, 1) infinite;
}
@keyframes dw-hop {
  0%, 100% { transform: translateY(0); }
  42%      { transform: translateY(-8px); }
  72%      { transform: translateY(0); }
}

/* 第 2 幕「AI 接住」：回答也逐字打出来。
   ★ 三幕都声明**同一条** them 动画（名字 / 时长 / 延迟逐项相同）——
     切幕时浏览器的计算值没变，动画就不会重启；
     若让 scene-2 另写一条，访客那句话会每 5 秒从头再打一遍。 */
html.is-motion .demo-widget.is-live.is-scene-1 .dw-bubble.them .dw-type,
html.is-motion .demo-widget.is-live.is-scene-2 .dw-bubble.them .dw-type,
html.is-motion .demo-widget.is-live.is-scene-3 .dw-bubble.them .dw-type {
  animation: type-reveal 1.5s steps(14) .3s both;
}
/* me 的动画只在第 2、3 幕声明（第 1 幕它还是 opacity:0）——
   否则"看不见的时候先把句子打完，等显出来时已经全在"，
   访客只会看到它"啪"地出现，白做了打字机。 */
html.is-motion .demo-widget.is-live.is-scene-2 .dw-bubble.me .dw-type,
html.is-motion .demo-widget.is-live.is-scene-3 .dw-bubble.me .dw-type {
  animation: type-reveal 1.35s steps(11) .2s both;
}

/* 光标：各自只在「自己那句话说出口」的那一幕里闪，
   打完的那条不再闪 —— 光标一直闪会显得它还在输入。 */
html.is-motion .demo-widget.is-live.is-scene-1 .dw-bubble.them .dw-caret {
  animation: caret-blink .68s steps(1) infinite;
}
html.is-motion .demo-widget.is-live.is-scene-2 .dw-bubble.me .dw-caret {
  animation: caret-blink .68s steps(1) infinite;
}

/* 第 3 幕「圈选」：两条气泡都留在画面上，访客把出错的地方圈给客服看 */
html.is-motion .demo-widget.is-live .dw-shot { clip-path: inset(0 100% 100% 0); }
html.is-motion .demo-widget.is-live.is-scene-3 .dw-shot {
  animation: shot-draw .72s cubic-bezier(.16, 1, .3, 1) 1.6s both;
}
@keyframes shot-draw {
  from { clip-path: inset(0 100% 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}
html.is-motion .demo-widget.is-live.is-scene-3 .dw-shot::after {
  animation: shot-ping 1.15s ease-out 2.4s infinite;
}
@keyframes shot-ping {
  0%   { transform: scale(.55); opacity: 0; }
  28%  { opacity: 1; }
  100% { transform: scale(1.3); opacity: 0; }
}

/* —— (2) 坐席工作台：排队在流动 → 对话推进 → 上下文逐条补齐 —— */

/* 第 1 幕：高亮条在等待队列里往下走（= 又进来一位访客） */
html.is-motion .demo-desk.is-live.is-scene-1 .dd-item:nth-child(3),
html.is-motion .demo-desk.is-live.is-scene-1 .dd-item:nth-child(4) {
  animation: queue-focus 2.4s ease-in-out infinite;
}
html.is-motion .demo-desk.is-live.is-scene-1 .dd-item:nth-child(4) { animation-delay: .55s; }
@keyframes queue-focus {
  0%, 100% { background: #eef1f8; box-shadow: none; }
  32%, 58% { background: var(--brand-light); box-shadow: inset 3px 0 0 var(--brand); }
}

/* 第 2 幕：对话一条条"发出来"（从左/右各自的方向展开） */
html.is-motion .demo-desk.is-live .dd-bub { transform-origin: 0 50%; }
html.is-motion .demo-desk.is-live .dd-bub.me { transform-origin: 100% 50%; }
html.is-motion .demo-desk.is-live.is-scene-2 .dd-bub { animation: bub-pop .46s cubic-bezier(.16, 1, .3, 1) both; }
html.is-motion .demo-desk.is-live.is-scene-2 .dd-bub:nth-child(2) { animation-delay: .12s; }
html.is-motion .demo-desk.is-live.is-scene-2 .dd-bub:nth-child(3) { animation-delay: .92s; }
html.is-motion .demo-desk.is-live.is-scene-2 .dd-bub:nth-child(4) { animation-delay: 1.7s; }
@keyframes bub-pop {
  from { transform: scaleX(.24); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}

/* 第 3 幕：右侧「访客上下文」四行逐条填上，标题脉冲提示"这就是接手时看到的" */
html.is-motion .demo-desk.is-live.is-scene-3 .dd-line { animation: line-fill .46s ease both; }
html.is-motion .demo-desk.is-live.is-scene-3 .dd-line:nth-child(2) { animation-delay: .1s; }
html.is-motion .demo-desk.is-live.is-scene-3 .dd-line:nth-child(3) { animation-delay: .52s; }
html.is-motion .demo-desk.is-live.is-scene-3 .dd-line:nth-child(4) { animation-delay: .94s; }
html.is-motion .demo-desk.is-live.is-scene-3 .dd-line:nth-child(5) { animation-delay: 1.36s; }
@keyframes line-fill {
  from { transform: scaleX(0); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}
html.is-motion .demo-desk.is-live.is-scene-3 .dd-col:last-child .dd-tag {
  animation: ctx-pulse 1.7s ease-in-out .2s infinite;
}
@keyframes ctx-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(79, 70, 229, .3); }
  45%      { box-shadow: 0 0 0 6px rgba(79, 70, 229, 0); }
}

/* —— (3) 管理端：柱子长出来 → 一道扫描线扫过 → 三个指标依次亮一下 —— */

/* 第 2 幕：一道光从左侧扫过柱状图（= 数据在刷新） */
/* 扫描线的宿主从"柱组"换成"趋势线画布"：
   卡片改版后没有柱子了，但"一道光扫过 = 数据正在刷新"这一幕要保留落点。 */
html.is-motion .demo-stat.is-live.is-scene-2 .ds-spark::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, transparent, var(--cyan), transparent);
  box-shadow: 0 0 10px 1px rgba(34, 211, 238, .75);
  animation: scan-x 2.3s cubic-bezier(.45, 0, .55, 1) .25s both;
}
@keyframes scan-x {
  from { left: 0; opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  to   { left: 100%; opacity: 0; }
}

/* 第 3 幕：三个 KPI 依次被"点亮"一遍（= 每天要看的就这三件事） */
html.is-motion .demo-stat.is-live.is-scene-3 .ds-ft div { animation: kpi-hi 1.6s ease-in-out both; }
html.is-motion .demo-stat.is-live.is-scene-3 .ds-ft div:nth-child(2) { animation-delay: .45s; }
html.is-motion .demo-stat.is-live.is-scene-3 .ds-ft div:nth-child(3) { animation-delay: .9s; }
@keyframes kpi-hi {
  0%, 100% { transform: none; box-shadow: none; border-color: var(--line); }
  38%      { transform: translateY(-3px); box-shadow: 0 12px 24px -15px rgba(79, 70, 229, .8); border-color: rgba(79, 70, 229, .45); }
}
html.is-motion .demo-stat.is-live.is-scene-3 .ds-line { animation: bar-breathe 1.5s cubic-bezier(.45, 0, .55, 1) both; }
@keyframes bar-breathe {
  0%, 100% { filter: none; }
  50%      { filter: brightness(1.22) saturate(1.12); }
}

/* —— 悬停暂停：访客想看清某一刻时，把这一轮定住 —— */
.demo-shot:hover .dw-ball,
.demo-shot:hover .dw-type,
.demo-shot:hover .dw-caret,
.demo-shot:hover .dw-shot::after,
.demo-shot:hover .dd-item,
.demo-shot:hover .dd-bub,
.demo-shot:hover .dd-line,
.demo-shot:hover .dd-tag,
.demo-shot:hover .ds-spark::after,
.demo-shot:hover .ds-ft div,
.demo-shot:hover .ds-line { animation-play-state: paused; }

/* ===========================================================================
   19. 首屏 3D 徽标：跟着指针转的科技图腾（2026-10-08 第三轮）
   ---------------------------------------------------------------------------
   ★ 参考的是 GitHub 首页那只吉祥物的做法：图形本身并不变形，而是**整个平面
     跟着指针倾斜**，再叠一层跟着指针跑的高光 —— 平面的东西因此有了体积。
     真正起作用的是后面那半句：**倾斜 + 光斑位移必须一起做**。
     只倾斜不挪光，看着像一张被推歪的贴纸；两者同步，才像"一个会反光的实体"。
   ★ 四层由后往前，各给一点不同的 translateZ —— 一旦倾斜，层间就产生视差，
     体积感是从这个视差里长出来的，不是靠画得立体：
       halo   光晕（最远，缓慢呼吸）
       ring   一道锥形亮环（绕 Z 自转，像仪表刻度盘）
       core   内核（贴一枚对话图标）
       gloss  高光（跟着指针走的柔光，mix-blend: screen）
   ★ 角度由 site-motion.js 写两个变量：--rx（上下倾）/ --ry（左右转）。
     本节只定义"拿到角度之后长什么样"，不关心角度怎么来。
   ★ 与首屏其它动效层的分工（**严禁抢同一棵子树**，抢了 hero-tilt 的景深断言会红）：
       .hero-bg → 极光漂移 ｜ .hero-visual → 景深倾斜 ｜ .hero-orbs → 光点 ｜
       .orb3d → 本节。四者互不嵌套，各动各的。
   ★ 没有 JS 时不藏不空：徽标本身是一枚完整的静态图形，只是不转 ——
     门控只压 animation 与过渡，不给它"先隐藏"的初始态。
   ========================================================================= */
.orb3d {
  --rx: 0deg; --ry: 0deg;
  position: relative; flex: none;
  width: 78px; height: 78px;
  perspective: 460px;
}
/* 最外一道虚轨，反向慢转 —— 给徽标一个"在运行"的边 */
.orb3d::after {
  content: ""; position: absolute; inset: -7%; border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, .26);
}
.orb3d-plane {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx)) rotateY(var(--ry));
  transition: transform .35s cubic-bezier(.16, 1, .3, 1);
}
.orb3d-halo, .orb3d-ring, .orb3d-core, .orb3d-gloss {
  position: absolute; border-radius: 50%;
}
.orb3d-halo {
  inset: -16%;
  transform: translateZ(-20px);
  background: radial-gradient(circle at 50% 50%,
    rgba(129, 180, 255, .78), rgba(79, 70, 229, .32) 48%, transparent 72%);
  filter: blur(3px);
  opacity: .8;
}
.orb3d-ring {
  inset: 0;
  transform: translateZ(5px);
  background: conic-gradient(from 0deg,
    transparent 0 66%, rgba(186, 220, 255, 1) 84%, transparent 94% 100%);
  -webkit-mask: radial-gradient(circle, transparent 0 61%, #000 63% 100%);
          mask: radial-gradient(circle, transparent 0 61%, #000 63% 100%);
  opacity: .95;
}
.orb3d-ring::after {
  content: ""; position: absolute; inset: 21%;
  border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, .38);
}
.orb3d-core {
  inset: 20%;
  transform: translateZ(22px);
  display: grid; place-items: center;
  color: #f2f6ff;
  background: radial-gradient(circle at 34% 26%, #8b93ff, #4f46e5 62%, #3730a3 100%);
  box-shadow: 0 0 24px -4px rgba(99, 102, 241, .85),
              0 12px 24px -12px rgba(0, 0, 0, .95),
              inset 0 1px 0 rgba(255, 255, 255, .5);
}
.orb3d-core svg { width: 52%; height: 52%; }
.orb3d-gloss {
  inset: 3%;
  transform: translateZ(28px);
  background: radial-gradient(circle 18px at var(--gx, 34%) var(--gy, 26%),
    rgba(255, 255, 255, .78), rgba(255, 255, 255, .1) 54%, transparent 74%);
  mix-blend-mode: screen;
  pointer-events: none;
}
html.is-motion .orb3d::after { animation: orb-orbit 17s linear infinite; }
@keyframes orb-orbit { to { transform: rotate(-360deg); } }
html.is-motion .orb3d-halo { animation: orb-breath 4.2s ease-in-out infinite; }
@keyframes orb-breath {
  0%, 100% { opacity: .6; transform: translateZ(-20px) scale(.94); }
  50%      { opacity: 1;  transform: translateZ(-20px) scale(1.08); }
}
html.is-motion .orb3d-ring { animation: orb-spin 9s linear infinite; }
@keyframes orb-spin {
  from { transform: translateZ(5px) rotate(0deg); }
  to   { transform: translateZ(5px) rotate(360deg); }
}

/* 徽标与首屏标语同排（不额外占用纵向高度 —— 首屏本来就紧） */
.hero-top {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 14px 16px;
  margin-bottom: clamp(16px, 2.2vh, 26px);
}
.hero-top .eyebrow { margin-bottom: 0; }

/* ===========================================================================
   字号归一（2026-10-08）
   ---------------------------------------------------------------------------
   ★ 问题：全站字号长期是"随手填"的 —— 15.5 / 16.5 / 13.5 / 12.5 / 11.5 /
     18.5 / 19.5 / 21 / 22 / 42 混在一起。相邻层级只差 0.5px，
     视觉上分不出层级，小字还容易在不同缩放比下糊掉。
   ★ 做法：收敛到一套主流刻度 —— 12 / 14 / 16 / 18 / 20 / 24 / 32 / 40 / 48。
       正文 16 ｜ 次要说明 14 ｜ 极小标记 12
       卡片标题 18~20 ｜ 区块标题 24~48 ｜ 大数字 24~40
     ⚠️ 这是一层**覆盖**，不是把原声明删掉：原值留在原处，便于日后对照；
        这里按同样选择器重声明，靠"同文件更靠后"取胜。
     ⚠️ .sol-card / .ticks / .cta-band 不在本节 —— 它们在 pages.css 里，
        那份是后加载的，必须在那边改才压得住。

   ---------------------------------------------------------------------------
   ★★ 2026-10-08 第二遍：从"覆盖 45 个选择器"改成"按实测结果全站收口"
   ---------------------------------------------------------------------------
   第一遍只覆盖了 45 个选择器，看着够了，实际漏得很厉害 —— 用无头浏览器把
   **真实渲染出来的每一个可见元素**的计算字号抓出来比对刻度（.tmp/_probe-fonts-full.js），
   1440×900 上还有 99 个实例、1024×768 上 84 个、375×812 上 105 个不在刻度上。
   漏在两处：
     · 本文件 2370 行**之后**的断点块又把字号写回去了（浏览器默认按钮字号
       13.3333px 也在这类里）；
     · 流体 clamp —— 流体字号在**任何**宽度都算不出整数，天生落不到刻度上。
   所以第二遍做了三件事：
     ① 硬编码的非刻度值 → 就地吸附到最近刻度（17→18 / 15→16 / 13.5→14 /
        13→12 / 12.5→12 / 11.5→12 / 11→12 / 19→18 / 21→20 / 36→32 …）；
     ② 全部流体 clamp 换成**离散档位**，液体的部分丢给断点：
           .hero h1            64 / 48(≤1080) / 48(矮窗) / 28(≤760) / 24(≤400)
           .section-head h2    48 / 32(≤1080) / 24(≤760)
           .final-cta h2       同上
           .hero-sub           18 / 16(≤760)
        → 于是**任何断点下页面出现的字号都只有这十个**：
             12 14 16 18 20 24 28 32 40 48
     ③ <button> 显式声明字号，避免落到浏览器默认的 13.3333px。

   ★ 刻度最终定为 12 / 14 / 16 / 18 / 20 / 24 / 28 / 32 / 40 / 48
     （比第一遍多一个 28：它是手机上板块标题的档位，也是 Bootstrap h3 的默认值，
       属于主流刻度。原来 12/14/16/18/20/24/32/40/48 里 24→32 的空档太大。）
       正文 16 ｜ 次要说明 14 ｜ 极小标记 12 ｜ 卡片标题 18~20
       板块标题 24~48（三档）｜ 大数字 24~40
   =========================================================================== */
body { font-size: 16px; }
/* ★ 上面这些是"桌面档"的基线；断点档位在各自 @media 里，见 ≤1080 / ≤760 / ≤400。 */
.section-head h2 { font-size: 48px; }
.section-sub { font-size: 16px; }
.section-kicker { font-size: 12px; }
/* ★ 2026-10-08：原来是 clamp(16px, 1.15vw, 18px) —— 1440 宽下算出 16.56px，
     不在刻度上。正文类文字一律**定值**（流体只留给大标题），故直接写 18px；
     手机档在 760 断点里降到 16px。 */
.hero-sub { font-size: 18px; }
.eyebrow { font-size: 14px; }
.trust-item b { font-size: 24px; }
.trust-item .trust-desc { font-size: 14px; }
.mock-avatar { font-size: 14px; }
.mock-name { font-size: 14px; }
.mock-status { font-size: 12px; }
.mock-tag { font-size: 12px; }
.btn-mini { font-size: 12px; }

.pain-card h3, .feature-card h3 { font-size: 20px; }
.pain-card p, .feature-card p, .cap-lead { font-size: 16px; }
.ai-point h3 { font-size: 18px; }
.ai-point p { font-size: 16px; }
.ai-demo-title { font-size: 14px; }
.ai-demo-live, .ai-who, .ai-src, .ai-handoff { font-size: 12px; }

.notice { font-size: 14px; }
.cfg-missing { font-size: 12px; }
.steps h3 { font-size: 18px; }
.steps p { font-size: 14px; }
.case-card h3 { font-size: 18px; }
.case-card p { font-size: 14px; }
.case-metric b { font-size: 24px; }
.case-metric span { font-size: 14px; }
.faq summary { font-size: 16px; }
.faq-body { font-size: 16px; }
.demo-cap { font-size: 12px; }
.demo-desk .dd-tag { font-size: 12px; }
.demo-stat .ds-ft span { font-size: 12px; }

.pricing-name { font-size: 20px; }
.pricing-tagline { font-size: 14px; }
.pricing-price .num { font-size: 40px; }
.pricing-price .unit { font-size: 14px; }
.pricing-price .num .price-was { font-size: 16px; }
.pricing-yearly .price-was { font-size: 12px; }
.pricing-badge { font-size: 12px; }
.pricing-specs li { font-size: 14px; }
.pricing-features li { font-size: 14px; }

.final-cta h2 { font-size: 48px; }
.final-cta p { font-size: 16px; }
.site-footer { font-size: 14px; }
.footer-brand p, .footer-col h4 { font-size: 14px; }

/* ============================ 滚动渐入 ============================
   ★ 渐进增强：默认状态内容就是可见的。
     只有 reveal.js 跑起来、给 <html> 加上 .has-reveal 之后，
     才把 .reveal 元素先藏起来等待进入视口。
     这样 JS 被禁用 / 报错 / 用户开了"减少动态效果"时，
     页面内容一律完整可见 —— 不会出现"白屏一片什么都没有"。
   ================================================================= */
.reveal { opacity: 1; }
/* ★★ 2026-10-08 关键改动：入场位移从 transform 换成 translate。
   原因（这一条不看清楚，后面所有卡片动效都会"写了没反应"）：
     .reveal 是加在**卡片本身**上的（.pain-card / .cap-card / .feature-card …），
     而卡片还要用 transform 做"指针微倾斜"。两者写在同一个 transform 属性上，
     而 html.has-reveal .reveal.is-in { transform: none } 的优先级（0,3,1）
     远高于卡片那条（0,1,0）——于是入场一结束，卡片的倾斜就被这句
     transform: none 永久盖死，调 hover 怎么试都没反应。
     translate 是**独立的 CSS 属性**，与 transform 各走各的、互不覆盖，
     两件事于是能同时成立。
   ★ transition 里那几项 transform / box-shadow / border-color 是为卡片顺带写的：
     transition 是整条声明生效（不合并），这条优先级又高于卡片自己那条，
     不在这里列全，卡片的 hover 过渡就会被这条"吞掉"变成瞬间跳变。 */
html.has-reveal .reveal {
  opacity: 0;
  translate: 0 26px;
  scale: .975;
  transition:
    opacity .72s cubic-bezier(.16, 1, .3, 1),
    translate .72s cubic-bezier(.16, 1, .3, 1),
    scale .72s cubic-bezier(.16, 1, .3, 1),
    transform .42s cubic-bezier(.16, 1, .3, 1),
    box-shadow .42s ease,
    border-color .42s ease;
}
/* scale 也是**独立属性**（和 translate 一样），不与卡片的 transform 争抢 ——
   见上面那段注释。加它是因为只做"淡入 + 上移 20px"在视觉上太平，
   加一点缩放的"托起感"，卡片入场才像真的"浮上来"。 */
html.has-reveal .reveal.is-in { opacity: 1; translate: none; scale: none; }
/* 同组卡片依次入场，形成节奏（最多排到第 6 个） */
html.has-reveal .grid-3 > .reveal:nth-child(2),
html.has-reveal .grid-bento > .reveal:nth-child(2),
html.has-reveal .pricing-grid > .reveal:nth-child(2),
html.has-reveal .steps > .reveal:nth-child(2) { transition-delay: .07s; }
html.has-reveal .grid-3 > .reveal:nth-child(3),
html.has-reveal .grid-bento > .reveal:nth-child(3),
html.has-reveal .pricing-grid > .reveal:nth-child(3),
html.has-reveal .steps > .reveal:nth-child(3) { transition-delay: .14s; }
html.has-reveal .grid-3 > .reveal:nth-child(4),
html.has-reveal .grid-bento > .reveal:nth-child(4) { transition-delay: .21s; }
html.has-reveal .grid-3 > .reveal:nth-child(5),
html.has-reveal .grid-bento > .reveal:nth-child(5) { transition-delay: .28s; }
html.has-reveal .grid-3 > .reveal:nth-child(6),
html.has-reveal .grid-bento > .reveal:nth-child(6) { transition-delay: .35s; }

/* —— 矮屏专用档（笔记本 768~920 高：1366x768 / 1440x900 都落进来）——
   首屏内容里最占高的是右侧那张对话演示（实测固定 456px，由气泡内容撑出来）。
   窄屏可以靠折行腾地方，矮屏不能，所以这里把演示稿的密度整体收一档。
   1080 及以上的屏不受影响。 */
@media (min-width: 1081px) and (max-height: 920px) {
  .mock-head { padding: 12px 16px; gap: 10px; }
  .mock-avatar { width: 34px; height: 34px; font-size: 14px; }
  .mock-name { font-size: 14px; }
  .mock-status { font-size: 12px; font-weight: 400; }
  .mock-body { padding: 16px; gap: 8px; min-height: 0; }
  .bubble { padding: 9px 13px; font-size: 14px; border-radius: 13px; font-weight: 400; }
  .mock-tag { font-size: 12px; font-weight: 400; }
  .mock-foot { padding: 10px 14px; }
  .mock-input, .mock-send { font-size: 14px; font-weight: 400; }
  .hero-points li { padding: 6px 14px 6px 30px; font-size: 14px; }
  .hero-points { gap: 9px 12px; }
  .trust-item b { font-size: 20px;
  font-weight: 400; }
  /* 矮屏档（大屏但高度 ≤920）文字降到 20px，图标要跟着收一档，
     否则 46px 的方块与缩小的文字失衡；40px 仍小于文字两行高(48px) ⇒ 不撑高 */
  .trust-ico { width: 40px; height: 40px; border-radius: 11px; }
  .trust-ico svg { width: 20px; height: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.has-reveal .reveal { opacity: 1; translate: none; transition: none; }
  .btn, .pain-card, .feature-card, .pricing-card { transition: none; }
}

/* ============================ 响应式 ============================ */

/* —— 桌面但窗口矮（笔记本外接、分屏、浏览器下沿被任务栏吃掉）——
   ★ 保留原 fluid 版本用 min(5.4vw, 7.6vh) 达成的意图：视口矮 → 标题跟着小，
     首屏才不会被标题顶出去。这里把它离散成一档 48px。 */
@media (min-width: 1081px) and (max-height: 760px) {
  .hero h1 { font-size: 48px; }
}

/* —— 平板 / 小笔记本：三列降两列 —— */
@media (max-width: 1080px) {
  /* ★ 不再取消 .hero 的 100svh —— "首屏满屏"在平板/手机同样成立。
     内容本来就远超一屏，这条只是保证内容少的机型也不会露出下一板块的边。 */
  .hero-head { max-width: none; }
  .hero-body { grid-template-columns: 1fr; gap: 42px; }
  .hero-visual { max-width: 620px; }
  .grid-3, .pricing-grid { grid-template-columns: repeat(2, 1fr); }
  .steps { grid-template-columns: repeat(2, 1fr); }
  .steps li:nth-child(2n)::after { display: none; }
  .order-layout { grid-template-columns: 1fr; }
  .order-side { position: static; }
  .footer-inner { grid-template-columns: 1fr 1fr; }
  /* Bento 降到 2 列：宽卡正好占满一整行 */
  .grid-bento { grid-template-columns: repeat(2, 1fr); }
  .grid-bento > .feature-card.is-wide { grid-column: span 2; }
  /* ★ 标题降到平板档（48 → 32）。这两条是 2026-10-08 把流体 clamp
     换成"三档固定值"时补的，否则 ≤1080 会一直沿用桌面的 48px。 */
  .hero h1 { font-size: 48px; }
  .section-head h2, .final-cta h2 { font-size: 32px; }
}

/* —— 手机 —— */
@media (max-width: 760px) {
  :root {
    --header-h: 62px;
    --sec: 62px;
    --r-xl: 22px;
  }
  body { font-size: 16px; }
  .wrap { padding: 0 20px; }

  /* 手机端：菜单展开时页头切白底深字。
     菜单面板本身是白的，页头若还是"透明 + 浅字"（首页首屏的状态），
     展开瞬间会断成"上半截深、下半截白"两截。

     ★ 2026-10-20：选择器前显式加 body，把优先级从 (0,2,0) 抬到 (0,3,1) ——
       刚好与首页那条 body:has(.hero) .site-header:not(.is-scrolled) 持平，
       而本规则在源码里更靠后，所以赢。
       内页默认已是白底深字，这条对内页只是把同样的值再设一遍，无副作用。 */
  body .site-header:has(.nav.is-open) {
    --hd-fg: var(--ink);
    --hd-fg-2: var(--ink-2);
    --hd-hover-fg: var(--brand);
    --hd-hover-bg: var(--brand-light);
    --hd-toggle-bg: #ffffff;
    --hd-toggle-border: var(--line);
    --hd-toggle-bar: #33405a;
    /* 背景同样直接声明，理由见桌面端那条规则的注释（var 不重算） */
    background: #ffffff;
    border-bottom-color: var(--line);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .header-actions .btn-ghost { display: none; }

  .nav { display: none; }
  .nav.is-open {
    display: flex; flex-direction: column; align-items: stretch;
    position: absolute; left: 0; right: 0; top: var(--header-h);
    background: #fff; border-bottom: 1px solid var(--line);
    padding: 12px 20px 20px; gap: 2px; box-shadow: var(--shadow);
  }
  .nav.is-open a { padding: 13px 14px; color: var(--ink-2); min-height: 46px; }
  .nav-toggle { display: flex; }

  /* —— 首屏：★ 这是"不够 H5"最集中的地方 —— */
  /* ★ 保留 100svh：手机首屏仍应"至少一屏"，只是内容远超一屏、不会看到效果 */
  .hero { padding-top: calc(var(--header-h) + 8px); margin-top: calc(var(--header-h) * -1); }
  .hero-inner { padding-top: 20px; padding-bottom: 26px; }
  .hero-body { grid-template-columns: 1fr; gap: 24px; margin-top: 18px; }
  /* ★ 标题字号是按"一行装得下 12 个汉字"倒推的：
       手机内容宽 = 屏宽 - 40；12 字 × 字号 ≤ 该宽度 → 字号 ≤ (屏宽-40)/12。
       390 屏 → 上限约 29px，取 7.2vw（=28px）。
       这样 24 字的标题正好两行："客户在网站上问的每一句话" / "都不该等到第二天才有人回"，
       断句落在语义停顿处。旧版用 30px 会折成三行，且"等 / 到"被切开。 */
  .hero h1 {
    /* ★ 2026-10-08：由 clamp(24px, 7.2vw, 32px) 收敛为固定 28px。
       7.2vw 在 390 屏上本来就是 28.08px —— 取整即 28，落回刻度；
       360~375 这两个更窄的档位由下面 ≤400 的 24px 兜住，
       12 字 × 字号 ≤ (屏宽-40) 的两行推导仍然成立。 */
    font-size: 28px;
    line-height: 1.26;
    letter-spacing: -.01em;
  }
  /* ★ 解除源码里写死的 <br>：手机上强制换行会让"每一句话"折成孤字行，
     交给 text-wrap:balance 自己算最好看的两行 */
  .hero h1 br { display: none; }
  /* ★ 手机上首屏总高本来就是 1.5 屏以上，节奏收紧一档（每处省 6~8px） */
  .eyebrow { margin-bottom: 14px; font-size: 14px; font-weight: 400; }
  .hero-sub { margin-top: 14px; font-size: 16px; line-height: 1.75; max-width: none; }
  .hero-cta { margin-top: 20px; gap: 12px; }
  .hero-cta .btn { width: 100%; }
  .hero-points { margin-top: 16px; gap: 8px; }
  .hero-points li { font-size: 14px; padding: 7px 14px 7px 32px; width: 100%; }
  .trust-item b { font-size: 18px; }
  .trust-item .trust-desc { font-size: 14px; font-weight: 400; }
  .mock-body { padding: 14px 16px; min-height: 0; gap: 9px; }
  .bubble { font-size: 14px; max-width: 92%; }

  .trust-bar { grid-template-columns: 1fr 1fr; gap: 18px 16px; padding-top: 26px; padding-bottom: 30px; }
  .trust-item b { font-size: 18px; }
  .trust-item .trust-desc { font-size: 14px; font-weight: 400; }
  /* 手机上排成 2×2，列宽只有 (屏宽-40)/2 ⇒ 图标再收一档、间距收紧；
     38px < 文字两行高(25+20=45px) ⇒ 首屏总高仍不受影响 */
  .trust-ico { width: 38px; height: 38px; border-radius: 11px; margin-top: -5px; }
  .trust-ico svg { width: 20px; height: 20px; }
  /* ★ 窄档必须改成顶对齐：文字在窄列里会折成 2~3 行，图标若还垂直居中就会
     "飘到文字中段"、与首行标题明显错位（375 宽下实测偏 24~37px —— 用户看到的是
     "数字在上面、图标却在下面"）。上面那个 -5px 是把图标的视觉中心挪回首行标题
     （b 18px × 1.4 = 25.2 行高）的中心，顶对齐后正好差 5px 左右。
     桌面档保持居中：那里文字只有两行，居中恰好落在两行之间，是好看的。 */
  .trust-item { gap: 11px; align-items: flex-start; }

  .section-head { margin-bottom: 36px; }
  /* ★ 标题降到手机档（32 → 24）。为什么不跟 h1 一样用 28：
     手机上首屏标题(h1, 28) 与板块标题(h2) 会轮番出现，同号就分不出主次，
     所以 h2 再降一档到 24，形成 48 / 32 / 24 的三档阶梯。 */
  .section-head h2, .final-cta h2 { font-size: 24px; }
  /* ★ trust-bar 不在此列 —— 它是 4 个短数据（1 行 b + 1 行 span），
     竖排成 4 行会白吃掉 135px 高度。上面已给 1fr 1fr，手机上排成 2×2 最省地方。 */
  .grid-3, .pricing-grid, .steps, .field-row { grid-template-columns: 1fr; }
  /* 单列时宽卡不再横排，图标回到标题上方，否则一行只有图标+两行字会很怪 */
  .grid-bento { grid-template-columns: 1fr; }
  .grid-bento > .feature-card.is-wide {
    grid-column: span 1;
    grid-template-columns: 1fr;
    gap: 0;
  }
  .grid-bento > .feature-card.is-wide .feature-icon { grid-row: auto; margin-bottom: 20px; }
  .grid-bento > .feature-card.is-wide h3,
  .grid-bento > .feature-card.is-wide p { grid-column: 1; }
  .steps li::after { display: none; }
  .pricing-card.is-recommended { transform: none; }
  .pricing-card.is-recommended:hover { transform: translateY(-4px); }
  .pricing-price .num { font-size: 32px; }

  .query-form { flex-direction: column; }
  .query-form .btn { width: 100%; }

  /* 反馈表单：卡片内边距收一档，分段选择改成整行两等分（手机上更好点） */
  .fb-form { padding: 22px; gap: 18px; }
  .fb-done { padding: 36px 22px; }
  .fb-seg { width: 100%; justify-self: stretch; }
  .fb-seg-btn { flex: 1; padding: 0 10px; }
  /* 窄屏药丸会换行，字号和内距收一点才放得下「编号 #65 · 09-25 12:15」 */
  .fb-done-meta { font-size: 12px; padding: 7px 12px; gap: 7px; font-weight: 400; }

  .footer-inner { grid-template-columns: 1fr; gap: 30px; }
  /* 页脚链接在手机上给足触控高度 */
  .footer-col a { min-height: 44px; display: flex; align-items: center; padding: 0; }
  .footer-bottom { margin-top: 36px;
  font-weight: 400; }
}

/* —— 小手机 ——
   ★ 这里不再单独给 .hero h1 定字号：760 那一档已经是按
     「(屏宽-40)/12」倒推出来的，360~430 全区间都稳在两行。
     早先这里多写了一条 clamp(28px, 8.6vw, 34px)，在 390 屏上把字号
     顶回 33.5px、标题重新折成三行 —— 一条多余的覆盖，删掉。 */
@media (max-width: 400px) {
  .pricing-card { padding: 28px 22px; }
  .trust-bar { gap: 14px 12px; }
  /* 360~400 最窄档：图标再收一档，否则两列里文字只剩 ~90px 会折成三行 */
  .trust-ico { width: 34px; height: 34px; border-radius: 10px; }
  .trust-ico svg { width: 18px; height: 18px; }
  .trust-item { gap: 9px; }
  /* ★ 360~400 这一段再降一档 24px：28px 在 360 屏上 12 字要 336px，
     而可用宽只有 320px，会被迫折成三行。 */
  .hero h1 { font-size: 24px; }
}

/* ===========================================================================
   返回顶部按钮
   ---------------------------------------------------------------------------
   ★ 位置为什么是 right:32px / bottom:24px —— 它是按**右侧客服悬浮按钮**倒推的，
     两个数字都不要随手改：

       ★ 本轮排布改过一次（原来是"TOP 在上、客服在下"），原因很实在：
         聊天面板的定位是从客服按钮正上方往上长的（挂件里是 bottom:156px），
         而旧版 TOP 按钮恰好也在 bottom:92px —— 与面板底边完全重合。
         于是**面板一展开就必然把 TOP 压住**，不是概率问题，是几何必然。
         访客正在长对话时想回到页面顶部，按钮却正好在聊天窗口底下，点不到。

       现在改成竖排、TOP 在最下：
         返回顶部（本按钮）  width/height 44px，right 32px，bottom 24px
         客服悬浮按钮        width/height 60px，right 24px，bottom 84px
         聊天面板                           right 24px，bottom 156px

         → 客服按钮水平中心线距右 24 + 60/2 = 54px
           本按钮要落在同一条中心线上：right = 54 - 44/2 = 32px ✔
         → 本按钮占 24~68px，客服按钮从 84px 起，中间留 16px ✔
         → 面板从 156px 起，三个元素依次向上排开，**任何一个都不会压住另一个** ✔
         → 而且最底部这一格给了体量最小、最不抢眼的返回顶部，
           客服按钮抬高后离视线中心更近，被发现的概率反而更高。

   ★ z-index 必须**低于**客服挂件的 2147483000（这里取 900：高于吸顶页头）。
     现在两者位置不再重叠，但这条仍然要保留 —— 拖动聊天面板时面板可以
     被拖到屏幕任何一个位置（挂件支持拖拽），会经过本按钮所在区域；
     保持低 z-index 才能让面板始终盖在上面，而不是被一个方形小按钮穿出来。

   ★ 为什么用 opacity + visibility 两条（只写 opacity 是常见 bug）：
     只降低透明度的话，按钮其实**还在原地可点** —— 页面刚加载时
     用户在那个位置点一下，会莫名其妙跳回顶部。visibility:hidden
     把它从命中测试里摘出去，配合 transition 又能做出淡出动画。

   ★ 不用 display:none 切换：display 参与不了过渡，只能硬闪。
   =========================================================================== */
.to-top {
  position: fixed;
  right: 32px;
  bottom: 24px;
  z-index: 900;

  width: 44px;              /* 触控目标 ≥44px（移动端最小可点尺寸） */
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  /* ★ 必须显式声明字号：<button> 不给 font-size 时会落到浏览器默认的
     13.3333px（Chrome: font 400 13.3333px Arial），既不在刻度上，
     也会顺着继承把里面的 svg / path 一起带歪。 */
  font-size: 14px;

  border-radius: 14px;      /* 方圆角 —— 与客服的圆形按钮区分开，避免看错 */
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .94);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--ink-2);
  box-shadow: 0 10px 24px -12px rgba(7, 13, 27, .30);

  cursor: pointer;
  /* 初始隐藏 */
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity .22s ease, transform .22s ease, visibility .22s ease,
              color .18s ease, border-color .18s ease;
}

.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.to-top:hover {
  color: var(--brand);
  border-color: #c7d7ff;
}

.to-top:active { transform: translateY(1px); }

/* 键盘 Tab 过来时要看得见焦点圈（鼠标点击不显示，避免留下难看的描边） */
.to-top:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.to-top svg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------------------
   ★ 为什么不做「客服挂件没启用时按钮落到底部」这种动态处理：
      看似更贴心，实际要引入一个跨脚本耦合 —— 谁给 body 加标记类、
      widget 初始化晚到或失败时类没加上，按钮就会和客服按钮**重叠**，
      点哪个都点不准。
      本页一定会挂客服挂件（挂件的悬浮按钮只要脚本加载成功就必然渲染），
      固定 bottom:24px 就够了。少一条依赖运行时状态的规则，少一类 bug。

   ★ 本轮之后这条更站得住了：TOP 现在是最底下那一格，
      就算挂件**完全没加载**（脚本 404、被拦截、客户站点屏蔽第三方脚本），
      本按钮也只是孤零零待在右下角，不会和任何东西重叠 ——
      这正是"少一条运行时依赖"换来的性质。
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   ★ 深色区（首屏 / 下单区 / 页脚）上按钮的观感 ——
      这几个区段是深底，白底按钮本身对比度没问题，
      但边框与阴影在白底上更"贴"，这里给深底单独调一版。
      实现方式：不监听滚动位置（那样要一直算，滚动时掉帧），
      而是**让按钮始终是白色实底**，在深浅背景上都成立。
      所以这里不改，仅留说明 —— 少一条依赖滚动状态的逻辑就少一类 bug。
   --------------------------------------------------------------------------- */

/* 尊重系统「减少动态效果」设置：去掉位移与过渡，直接显现 */
@media (prefers-reduced-motion: reduce) {
  .to-top {
    transform: none;
    transition: opacity .01ms linear, visibility .01ms linear;
  }
  .to-top.is-visible { transform: none; }
  .to-top:active { transform: none; }
}

/* ---------------------------------------------------------------------------
   ★ 手机上要重新对一次中心线 —— 断点必须跟客服挂件一致，都是 480px

   挂件在 ≤480px 时会自己换一套定位（widget/src/styles.ts 末尾的媒体查询）：
       .wc-launcher { bottom: 72px; right: 16px !important }
   于是客服按钮的水平中心线从「距右 54px」变成 16 + 60/2 = 46px，
   占的高度区间也变成 72~132px。

   本按钮跟着改：right = 46 - 44/2 = 24px；bottom 16px（占 16~60px），
   与客服按钮之间留 12px 间隙。

   ⚠️ 断点写 480px，不要写 640px：
      挂件是在 480px 换的定位，本按钮如果到 640px 才跟着调，
      那么 481~640 这一段（大量小尺寸平板竖屏正好落在这里）
      两个按钮的中心线会差 8px，肉眼能看出歪。
   --------------------------------------------------------------------------- */
@media (max-width: 480px) {
  .to-top {
    right: 24px;
    bottom: 16px;
  }
}

/* ============================================================================
   搭聆猫头鹰 LOGO · 眼珠动画（2026-10-02，v3 品牌图形）
   - 只有眼珠动：环视 9.6s + 眨眼（眼珠纵向压扁，右眼延迟 0.08s）
   - 眼白 / 身体 / 红嘴全部静止；t=0 姿态 = 静态版（降级安全）
   - 几何真源 brand/mark-glyph.svg；时间轴与 brand/owl/owl-animated.svg 一致
   ========================================================================== */
.dailing-owl .owl-move { transform-box: view-box; animation: dailing-owl-look 9.6s infinite; }
.dailing-owl .owl-blink { transform-box: view-box; animation: dailing-owl-blink 9.6s infinite; }
.dailing-owl #owl-pw-l { transform-origin: 79.984px 115.750px; }
.dailing-owl #owl-pw-r { transform-origin: 134.762px 99.126px; animation-delay: 0.08s; }
@keyframes dailing-owl-look {
  0% { transform: translate(0px, 0px); }
  14.583% { transform: translate(0px, 0px); animation-timing-function: ease-in-out; }
  31.25% { transform: translate(-9px, 1.5px); }
  43.75% { transform: translate(-9px, 1.5px); animation-timing-function: ease-in-out; }
  60.417% { transform: translate(9px, -2px); }
  72.917% { transform: translate(9px, -2px); animation-timing-function: ease-in-out; }
  87.5% { transform: translate(0px, 0px); }
  100% { transform: translate(0px, 0px); }
}
@keyframes dailing-owl-blink {
  0%, 89.583%, 97.083%, 100% { transform: scaleY(1); animation-timing-function: ease-in-out; }
  93.333% { transform: scaleY(0.15); }
}
@media (prefers-reduced-motion: reduce) {
  .dailing-owl .owl-move, .dailing-owl .owl-blink { animation: none; }
}

/* ===========================================================================
   首页改版新增组件的响应式与降级（2026-10-08）
   ---------------------------------------------------------------------------
   ★ 这一段必须留在文件**最末**：窄屏覆盖要晚于上面的基础规则，才压得住它们。
   =========================================================================== */
@media (max-width: 1080px) {
  /* AI 专章在窄屏上从"左说明 / 右演示"改成上下堆叠 ——
     两栏各占一半宽度时，演示卡里的对话气泡会被挤到只剩两三个字一行。 */
  .ai-layout { grid-template-columns: 1fr; gap: 40px; }
  .ai-demo-body { min-height: 0; }
}
@media (max-width: 760px) {
  .cap-card { padding: 28px 22px 24px; }
  .cap-card h3 { font-size: 20px; }
  /* 编号方块从"左侧浮标"改成"顶部一行"，否则正文被挤到只剩很窄一条 */
  .ai-point { padding-left: 0; padding-top: 52px; }
  .ai-no { top: 0; }
  .ai-text { max-width: 100%; }
  .demo-stat .ds-ft b { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  /* 用户在系统里设了"减少动态效果"：入场动画与打字演示一律停用。
     注意这里是**把动画整个摘掉**，而不是设成 duration:0 ——
     animation:both 的填充状态会把元素锁在 opacity 0，摘掉才会回到自然态。 */
  .has-reveal .mock-body > .bubble,
  .has-reveal .mock-body > .mock-tag { animation: none; }
  .mock-typing span,
  .ai-demo-live i { animation: none; }
  .ai-demo.is-armed .is-on { transition: none; }
}

/* ===========================================================================
   统一交互层的响应式与降级（2026-10-08）
   ---------------------------------------------------------------------------
   ★ 同上一段，必须留在文件**最末**才压得住上面的基础规则。
   =========================================================================== */
@media (max-width: 1080px) {
  /* 平板及以下不支持精细指针：倾斜/聚光/位移类反馈全部关掉 ——
     触摸设备上"hover"会在点完之后粘住，比不做还难受。 */
  .pain-card, .cap-card, .feature-card, .sol-card, .case-card, .pricing-card {
    transform: translateY(var(--lift, 0px));
  }
  .pain-card:hover, .cap-card:hover, .feature-card:hover,
  .sol-card:hover, .case-card:hover, .pricing-card:hover { --lift: -4px; }
  .pain-card::after, .cap-card::after, .sol-card::after,
  .case-card::after, .pricing-card::after { display: none; }
  .ai-point:hover { transform: none; }
}
@media (max-width: 760px) {
  .feat-tabs { margin: -18px 0 26px; gap: 8px; }
  .feat-tab { padding: 8px 14px; font-size: 12px; }
  /* 窄屏一列：TAB 横向滚动而不是折成两行，省掉半屏高度 */
  .feat-tabs.is-live { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .feat-tabs.is-live::-webkit-scrollbar { display: none; }
  .feat-tab { flex: 0 0 auto; }
}

/* 「减少动态效果」下的最终降级：本层所有持续动画与过渡一律摘掉。
   ★ 注意这里**不能**只写 animation: none 就完事 ——
     柱状图/气泡的初始态是 opacity:0 + scaleY(0)，只摘动画会把它们
     永远留在"没画出来"的状态。所以先把 .is-armed 那套初始态一并还原。 */
@media (prefers-reduced-motion: reduce) {
  .hero-bg { animation: none; }
  .scroll-progress { display: none; }
  .demo-shot.is-live .dw-ball { animation: none; }
  svg .ants { animation: none; }
  .demo-shot.is-armed .ds-line,
  .demo-shot.is-armed .dw-bubble,
  .demo-shot.is-armed .dw-shot,
  .demo-shot.is-armed .dd-item,
  .demo-shot.is-armed .dd-bub,
  .demo-shot.is-armed .dd-line,
  .demo-shot.is-armed .ds-ft div { transform: none; opacity: 1; }
  /* 趋势线的初始态不是 opacity/transform，而是"没描到头"——
     必须单独把 dashoffset 归零，否则减少动效的用户看到的是一条空线。 */
  .demo-shot.is-armed .ds-line { stroke-dashoffset: 0; }
  .demo-shot.is-armed .ds-area { opacity: 1; }
  html.is-motion .steps li::after { transform: none; }
  html.is-motion .step-no { transform: none; opacity: 1; }
  .feature-card.is-cap-in { animation: none; }
  .feat-tab, .feat-tab:hover, .feat-tab:active { transform: none; }
  .ai-point, .ai-point:hover { transform: none; }
  /* —— 常驻动效层（2026-10-08 第二轮）的关闭 ——
     这一层本来全门控在 html.is-motion 上（RM 下压根不会加这个类），
     这里再显式关一次，是为了防止以后有人把某条漏出 is-motion 之外。 */
  html.is-motion .pain-icon svg, html.is-motion .feature-icon svg { animation: none; }
  html.is-motion .pain-icon::after, html.is-motion .feature-icon::after { display: none; }
  html.is-motion .cap-no { animation: none; }
  html.is-motion .hero h1 em { animation: none; }
  /* —— 2026-10-11 新增两层的关闭 ——
     ★ 13-b 的边框流光必须**连背景一起退回单层**：
       --cta-ang 在 RM 下停在 0deg，conic 那层会在 12 点钟方向留一道静止的亮边，
       看着像"按钮上沾了个白点"。只关 animation 是不够的。 */
  html.is-motion .hero-cta .btn-primary { animation: none; }
  .hero-cta .btn-primary {
    background: linear-gradient(135deg, var(--brand), var(--brand-dark));
  }
  /* ★ 13-c 的数字落定：这里必须把 from 的 opacity:0 还回来 ——
     和上面 .is-armed 那批同理，只停 animation 会让数字永久停在"没显示"。 */
  html.is-motion .trust-item b,
  html.is-motion .pricing-price .num { animation: none; opacity: 1; transform: none; }
  .btn-primary::after { display: none; }
  .hero-orbs { display: none; }
  html.is-motion .steps li.is-on::after { animation: none; }
  html.has-reveal .reveal { translate: none; scale: none; }

  /* —— 18. 界面演示播放器 / 19. 3D 徽标 的关闭 ——
     这两节和上面同理：主体已门控在 html.is-motion 上，这里再显式关一遍。
     ★ 关掉"隐藏类"的初始态尤其重要：打字机与圈选框平时是 clip-path 藏着的，
       只停 animation 不还原 clip，内容会**永久缺一半** ——
       那正是"加了动效页面反而更差"的典型，必须在这里救回来。 */
  html.is-motion .demo-widget.is-live .dw-type,
  html.is-motion .demo-widget.is-live .dw-shot { clip-path: none; }
  html.is-motion .demo-widget.is-live.is-scene-1 .dw-bubble.me { opacity: 1; }
  .demo-widget .dw-caret { display: none; }
  html.is-motion .demo-widget.is-live.is-scene-1 .dw-typing { display: none; }
  .demo-shot:hover .dw-ball,
  .demo-shot:hover .dw-type,
  .demo-shot:hover .dw-caret,
  .demo-shot:hover .dw-shot::after,
  .demo-shot:hover .dd-item,
  .demo-shot:hover .dd-bub,
  .demo-shot:hover .dd-line,
  .demo-shot:hover .dd-tag,
  .demo-shot:hover .ds-spark::after,
  .demo-shot:hover .ds-ft div,
  .demo-shot:hover .ds-line { animation-play-state: running; }
  html.is-motion .orb3d::after,
  html.is-motion .orb3d-halo,
  html.is-motion .orb3d-ring { animation: none; }
  .orb3d-plane { transform: none; transition: none; }
}
