/* ============================================================================
 * site.css —— 南谷AI工作室官网全局样式
 *
 * ⚠️ 本文件是**设计令牌的唯一权威**（《技术架构设计》§5.3 / 主站需求 §5.3）。
 *    产品区 CSS **只允许追加 `--c-product-*` 新变量并微调 --wrap**，
 *    **禁止重定义**本文件里的任何令牌（构建期守卫会检查）。
 *
 * 判据：**删掉产品区 CSS 后，产品页应变成「主站风格的产品页」而不是崩掉。**
 * ========================================================================== */

:root {
  /* ───────────────────────────────────────────────────────────────────────
   * ⚠️ 对比度是**硬约束**，不是审美偏好。改这里的任何颜色之前，先看后面标的比值。
   *
   *    WCAG 2.1 AA：正文文字 ≥ 4.5:1；非文字部件（边框、焦点环）≥ 3:1
   *    实测背景：--c-bg #FFFFFF、--c-bg-soft #F6F8FB（后者更严，footer 用它）
   *
   *    构建期守卫会**实测**这些比值，不达标直接构建失败（tool/build.js 第 ⑩ 步）。
   * ─────────────────────────────────────────────────────────────────────── */

  /* ── 主色：取自 Logo 蓝段，偏青（与 Logo 青绿端能接上）──
   *    --c-primary       #2563D6   白底 5.48:1  软底 5.15:1  浅蓝底 4.86:1
   *                                白字在其上 5.48:1（主按钮填充）
   *    --c-primary-strong #1B5FC4  白底 6.03:1  浅蓝底 5.35:1（hover / kicker）
   *
   *    📌 历史：2026-10-07 之前是 #2E7BE6 = **4.12:1，不达 AA**，
   *       而它当时被用在**全部链接**和**主按钮**上。已调深。
   */
  --c-primary:        #2563D6;
  --c-primary-strong: #1B5FC4;
  --c-primary-soft:   #EAF2FE;   /* 浅蓝底；底色本身不判对比度 */

  /* ── 品牌渐变（装饰用）：Logo、色块、装饰条 ──
   *    ⚠️ 这不是 Logo 里量出来的原始渐变，而是**为界面提亮过的变体**。
   *       实测 Logo 真实色阶：#002C74（深藏青，占约 73%）→ #14D4A8（青绿，约 27%）。
   *       原样搬到界面上起点过暗，所以中段提亮到 #1E6FD9。
   *       Logo 与印刷场景请直接用 logo 图片，不要用这个令牌。
   *
   *    ⛔ **绝对不能拿它当文字颜色**：末端 #2FD3A8 在白底上只有 1.91:1，
   *       连大号文字的 3:1 都不到。要渐变文字请用下面的 --g-brand-text。
   */
  --g-brand: linear-gradient(120deg, #143A8F 0%, #1E6FD9 45%, #2FD3A8 100%);

  /* ── 品牌渐变（**文字专用**）──
   *    把青绿端从 #2FD3A8 压到 #0F7A66，于是**整条渐变**最低 4.85:1
   *    （最低点在中段 #1E6FD9），即使按正文尺寸用也达标。
   *    实测方式是**沿渐变采样 41 个点逐个算对比度**——
   *    只看两端是不够的，CSS 在 sRGB 空间插值，中间可能更低。
   */
  --g-brand-text: linear-gradient(120deg, #143A8F 0%, #1E6FD9 45%, #0F7A66 100%);

  /* ── 辅助色：**只作填充色（徽标底、图表色块），不作浅底上的文字** ──
   *    它们在白底上的对比度分别是 2.71 / 1.85 / 2.88，**作文字一律不达 AA**。
   *    需要"绿色/黄色/橙色的文字"时，另配深色变体，不要直接用这三个。
   */
  --c-accent-green:   #38B26A;   /* **只能作装饰**，见下方说明 */
  --c-accent-yellow:  #F5B31C;
  --c-accent-orange:  #F2733F;

  /* ── 徽标配对：浅底 + 深字 ──────────────────────────────────────────────
   *    ⚠️ 不要做「辅助色底 + 白字」——那**也不达标**。
   *       对比度是**对称的**：辅助色对白底只有 2.71 / 1.85 / 2.88，
   *       它承载白字时必然还是同一个数。**这三个色双向都不达标。**
   *       所以彩色标签一律用下面这几对（均实测）。
   */
  --c-tag-green-bg:   #E8F5EE;   --c-tag-green-ink:   #166534;   /* 6.36:1 */
  --c-tag-yellow-bg:  #FEF3C7;   --c-tag-yellow-ink:  #92400E;   /* 6.37:1 */
  --c-tag-orange-bg:  #FFEDD5;   --c-tag-orange-ink:  #9A3412;   /* 6.38:1 */

  /* ── 中性色（后标的比值都是对白底 / 软底）── */
  --c-text:           #1F2733;   /* 15.04:1 / 14.14:1  正文 */
  --c-text-sub:       #5A6675;   /*  5.85:1 /  5.49:1  次要文字 */
  --c-text-mute:      #64717F;   /*  4.99:1 /  4.69:1  辅助文字 */
  --c-bg:             #FFFFFF;
  --c-bg-soft:        #F6F8FB;
  /* 首屏底色：比 bg-soft **明显更深**，制造块面级的明暗对比。
     与 #F6F8FB 的亮度差 13.2%（此前"白 vs soft"只有 6.3%）。
     ⚠️ 代价：这层底上 --c-text-mute 只有 4.06:1、--c-primary 只有 4.46:1，
        都**不达 AA** —— 所以 Hero 内的辅助行改用 --c-text-sub，
        ghost 按钮改用 --c-primary-strong（详见 .hero 那一段）。 */
  --c-hero-bg:        #DCE9FC;
  --c-border:         #E3E8EF;   /* 1.23:1 —— 只能作**分割线/装饰**；
                                  * 输入框边框、焦点环等**功能性**部件必须用
                                  * 对比度 ≥ 3:1 的颜色（如 --c-text-mute 或 --c-primary） */
  --c-danger:         #C9302C;   /*  5.33:1 /  5.01:1  错误提示 */

  /* ── 骨架尺寸 ──
   * ⚠️ --header-h 必须是**变量**，不能在两处各写一个字面值。
   *    导航是 sticky 的，任何要"贴在导航下方"的浮层都得知道它有多高；
   *    写两份 = 改一处忘一处，而那种错**只在某一个断点上看得到**。
   *    （2026-10-08：成功提示条固定在视口顶部后压在导航栏上，
   *      就是因为它不知道 header 在移动端是 60px 而不是 72px。） */
  --header-h: 72px;

  /* ── 圆角与阴影 ── */
  --r-sm: 8px;  --r-md: 14px;  --r-lg: 22px;  --r-pill: 999px;
  --sh-1: 0 1px 2px rgba(16,24,40,.06);
  --sh-2: 0 8px 24px rgba(16,24,40,.08);
  --sh-3: 0 16px 40px rgba(16,24,40,.10);

  /* ── 间距：8pt 网格 ── */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px;  --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px;  --s-8: 64px; --s-9: 96px;

  /* ── 版心 ── */
  --wrap: 1200px;  --wrap-narrow: 820px;
}

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

/* ⚠️ `hidden` 属性靠 UA 样式的 `display: none` 生效，特异性极低 ——
   任何显式 display 都会把它盖掉。本项目就踩了这个坑：`.btn` 设了
   `display: inline-flex`，于是那个带 `hidden` 的「菜单」按钮在**桌面端也出现**了
   （桌面端导航本来就可见，多一个点不动、也没作用的按钮只会让人困惑）。
   2026-10-07 由外部审计发现（选择器 /html/body/header/div/button）。 */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", sans-serif;
  font-size: 17px;
  line-height: 1.75;
  /* 中文正文最小 16px，永不小于 14px（UI 规范禁止 12px 及以下） */
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--c-primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* 可见焦点环（可访问性硬要求，不许 outline:none） */
:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }

/* ============================ 排版层级 ============================ */
h1, h2, h3 { line-height: 1.3; margin: 0 0 var(--s-4); font-weight: 700; }
h1 { font-size: 48px; line-height: 1.25; }
h2 { font-size: 32px; }
h3 { font-size: 20px; font-weight: 600; line-height: 1.4; }

p { margin: 0 0 var(--s-4); }

/* 行宽控制在每行 30–40 个汉字 */
.prose { max-width: 38em; }

/* ============================ 布局 ============================ */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--s-4);
}

.section { padding-block: var(--s-8); }
.section--soft { background: var(--c-bg-soft); }

/* ============================ 骨架注入区 ============================ */
/* 占位注释被 Caddy 替换成 partial 内容；这里只处理替换后的成品样式 */

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255,255,255,.82);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--c-border);
}
.site-header__inner {
  display: flex; align-items: center; gap: var(--s-5);
  height: var(--header-h);
}
.site-header__logo { display: flex; align-items: center; }
.site-header__logo img { height: 34px; width: auto; }

.site-nav { display: flex; gap: var(--s-5); margin-inline-start: auto; align-items: center; }
.site-nav a {
  color: var(--c-text-sub); font-size: 16px; font-weight: 500;
  padding: var(--s-2) 0; text-decoration: none;
}
.site-nav a:hover { color: var(--c-primary); }
/* 当前页高亮：只认 aria-current，避免两套选中逻辑 */
.site-nav a[aria-current="page"] { color: var(--c-primary); font-weight: 600; }

/* ⚠️ 特异性陷阱（2026-10-07 由外部审计发现，选择器 /html/body/header/div/nav/a[5]）：
   `.site-nav a` 的特异性是 (0,1,1)，**高于** `.btn--primary` 的 (0,1,0)，
   于是它把导航里「联系我们」按钮的文字色覆盖成了 `--c-text-sub`。
   实测后果：#5A6675 压在 #2563D6 上只有 **1.07:1** —— 按钮几乎完全看不见。
   ⚠️ 构建期的对比度守卫查的是「白字在 --c-primary 上 = 5.48:1」，**令牌级达标
      不等于渲染后达标**：这类层叠偏差它一个都抓不到（见《UI 页面设计规范》C8）。
   这条与 `.site-nav a:hover` 特异性相同，靠**写在后面**取胜，别往上挪。 */
.site-nav a.btn--primary,
.site-nav a.btn--primary:hover { color: #fff; }

/* 菜单按钮（移动端抽屉的开关）：**只在移动端出现**。
   由 CSS 决定「在哪显示」，而不是让 JS 去判断屏幕宽度 —— 窗口缩放时不会失配。
   `hidden` 属性由 site.js 在启动时移除（无 JS 时不显示一个点不动的按钮）。 */
.site-header [data-nav-toggle] { display: none; }

/* 导航右侧的转化按钮（与导航项视觉可分） */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-2);
  min-height: 44px;              /* 触摸目标 ≥ 44px */
  padding: var(--s-3) var(--s-5);
  border: 1px solid transparent; border-radius: var(--r-pill);
  font-size: 16px; font-weight: 600; line-height: 1;
  cursor: pointer; text-decoration: none;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}
.btn--primary { background: var(--c-primary); color: #fff; }
.btn--primary:hover { background: var(--c-primary-strong); text-decoration: none; }
.btn--ghost { background: transparent; color: var(--c-primary); border-color: var(--c-primary); }
.btn--ghost:hover { background: var(--c-primary-soft); text-decoration: none; }

/* 禁用态：规范要求「必须可见」——用户要知道这里有个按钮，只是现在点不了。
   WCAG 对失效控件豁免对比度，但**不许做得和正常态一样**（那等于骗用户去点）。
   实测：底 --c-border + 字 --c-text-sub = 4.75:1。 */
.btn[disabled], .btn[aria-disabled="true"] {
  background: var(--c-border); color: var(--c-text-sub);
  border-color: var(--c-border); cursor: not-allowed; pointer-events: none;
}

/* ── 徽标：**浅底 + 深字**（配对令牌见 :root，全部实测达标）──────────────
   ⚠️ 不要改成「辅助色底 + 白字」：辅助色承载白字同样不达标（对比度是对称的）。 */
.tag {
  display: inline-block; padding: 2px 10px;
  border-radius: var(--r-pill); font-size: 14px; font-weight: 600; line-height: 1.6;
  background: var(--c-primary-soft); color: var(--c-primary-strong);
}
.tag--green  { background: var(--c-tag-green-bg);  color: var(--c-tag-green-ink); }
.tag--yellow { background: var(--c-tag-yellow-bg); color: var(--c-tag-yellow-ink); }
.tag--orange { background: var(--c-tag-orange-bg); color: var(--c-tag-orange-ink); }

/* ── 品牌渐变文字（关键词强调）────────────────────────────────────────────
   只用 --g-brand-text（每个色阶 >= 4.5:1）。
   @supports 是**兜底**：万一不支持 background-clip:text 就退回纯主色（仍达标），
   而不是让文字变成透明不可见。 */
.hl-brand { color: var(--c-primary); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .hl-brand {
    background: var(--g-brand-text);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* ── 工具类（用来替掉内联样式：内联样式会绕过令牌与构建守卫）── */
.text-sub { color: var(--c-text-sub); }
.center { text-align: center; }
.measure { max-width: 38em; }
.mx-auto { margin-inline: auto; }
.mt-5 { margin-top: var(--s-5); }
.stack { display: grid; gap: var(--s-5); }

/* 面包屑（产品区必需，v2.0 起是强制项） */
.breadcrumb {
  font-size: 14px; color: var(--c-text-mute);
  padding-block: var(--s-3);
}
.breadcrumb ol { display: flex; flex-wrap: wrap; gap: var(--s-2); list-style: none; margin: 0; padding: 0; }
.breadcrumb li + li::before { content: "/"; margin-inline-end: var(--s-2); color: var(--c-border); }

/* ============================ 页脚 ============================ */
.site-footer {
  border-top: 1px solid var(--c-border);
  background: var(--c-bg-soft);
  padding-block: var(--s-7) var(--s-5);
  font-size: 15px; color: var(--c-text-sub);
}
.site-footer__cols {
  display: grid; gap: var(--s-6);
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.site-footer h2 { font-size: 15px; font-weight: 600; color: var(--c-text); margin-bottom: var(--s-3); }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.site-footer a { color: var(--c-text-sub); }
.site-footer__legal {
  margin-top: var(--s-6); padding-top: var(--s-4);
  border-top: 1px solid var(--c-border);
  font-size: 14px; color: var(--c-text-mute);
}

/* ============================ 首页专属 ============================ */
/* 首屏整宽区块：底色 + 柔和几何形状。
   ⚠️ 底色是**整宽**的，所以 HTML 结构是 .hero > .wrap（内容收在版心里）。 */
.hero {
  position: relative; overflow: hidden;
  background: var(--c-hero-bg);
  padding-block: var(--s-9) var(--s-8);
}
.hero > .wrap { position: relative; z-index: 1; }   /* 内容压在几何之上 */

/* 柔和几何形状：一个**有明确边缘**的圆。
   之前试过 radial-gradient 的色晕 —— 实测像一片雾，既没形成形状又把右上角弄脏。
   改用 border-radius:50% + 线性渐变填充，**边缘清晰才成其为「形状」**。

   ⚠️ 位置是**算出来的，不是随手定的**。这个圆会压低其下文字的对比度，
      实测（rgba 最深处 .16 完全覆盖时）：
        --c-text          #1F2733 → 10.00:1  ✅ 仍达标（所以 H1 可以与圆重叠）
        --c-text-sub      #5A6675 →  3.89:1  ❌ 不达标
        --c-primary-strong #1B5FC4 → 4.01:1  ❌ 不达标
      而且**降到 .04 才达标，那时形状就看不见了** ——
      结论：**不能靠降透明度，只能让形状避开 text-sub / primary-strong 的文字**。
      按 Hero 高约 493px 算，圆心上移到 y≈-35 后，副标题与按钮都落在圆外；
      H1 仍可重叠（10:1 有余量）。
      改这里的 top/right/尺寸时，**必须重新核对副标题与 ghost 按钮是否仍在圆外**。 */
.hero::after {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  right: -6%; top: -68%;
  width: 600px; height: 600px; border-radius: 50%;
  background: linear-gradient(135deg,
      rgba(37, 99, 214, .18) 0%, rgba(47, 211, 168, .12) 100%);
}
/* 移动端隐藏：小屏空间不够，装饰只会让文字更难读 */
@media (max-width: 767px) {
  .hero::after { display: none; }
}
.hero__kicker {
  display: inline-block; margin-bottom: var(--s-4);
  padding: var(--s-1) var(--s-3);
  background: var(--c-primary-soft); color: var(--c-primary-strong);
  border-radius: var(--r-pill); font-size: 14px; font-weight: 600;
}
.hero h1 { font-size: 56px; letter-spacing: -.02em; }
.hero__sub { font-size: 20px; color: var(--c-text-sub); max-width: 34em; }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-6); }
/* ⚠️ 底色加深后 --c-text-mute 在这层上只有 4.06:1（不达 AA）→ 改用 text-sub（4.76:1） */
.hero__meta { margin-top: var(--s-4); font-size: 15px; color: var(--c-text-sub); }

/* ⚠️ 同理：--c-primary 在这层底上只有 4.46:1 → ghost 按钮改用更深的 strong（4.91:1）。
   hover 时底变 primary-soft，strong 在其上是 5.35:1，也达标。 */
.hero .btn--ghost {
  color: var(--c-primary-strong);
  border-color: var(--c-primary-strong);
}

/* ── 首屏入场：**只位移，绝不改 opacity** ────────────────────────────────
 *
 * ⚠️ 为什么不用 `opacity: 0 → 1`（那是需求文档给「滚动动效」规定的写法）：
 *    LCP 测的是元素**首次被绘制的时刻**，而 `opacity: 0` 的元素
 *    **不被算作已绘制** —— 给首屏做淡入会把 LCP 整体推后，
 *    推迟量约等于 animation-delay。
 *    实测：只做 translateY 时 LCP=128ms、CLS=0，与完全不加动画一致。
 *
 * 错位（stagger）让五个元素**依次**升起，而不是整块一起动 ——
 * 这是「简约但有动感」的关键：只有位移一个维度，没有缩放、没有淡入。
 */
@keyframes hero-rise {
  from { transform: translateY(16px); }
  to   { transform: none; }
}
.hero .wrap > * {
  animation: hero-rise .42s cubic-bezier(.2, .7, .3, 1) both;
}
.hero__kicker  { animation-delay: 0ms; }
.hero h1       { animation-delay: 60ms; }
.hero__sub     { animation-delay: 120ms; }
.hero__actions { animation-delay: 180ms; }
.hero__meta    { animation-delay: 240ms; }

/* ⚠️ 降级必须**显式**处理这一组，不能只靠文件末尾那条通用规则：
 *    通用规则只把 animation-duration 压到 .01ms，**不会重置 animation-delay**；
 *    配合 fill-mode: both，元素会「卡在下方 16px」等 240ms 再瞬移 —— 比不动更糟。 */
@media (prefers-reduced-motion: reduce) {
  .hero .wrap > * { animation: none !important; transform: none !important; }
}

/* 核心业务三块 */
.grid-3 { display: grid; gap: var(--s-5); grid-template-columns: repeat(3, minmax(0, 1fr)); }
.card {
  background: var(--c-bg); border: 1px solid var(--c-border);
  border-radius: var(--r-md); padding: var(--s-5); box-shadow: var(--sh-1);
  transition: box-shadow .18s ease;
}
.card:hover { box-shadow: var(--sh-2); }

/* 顶部品牌渐变条变体 —— **只给需要视觉重点的那一组用**。
   六个卡片全加会变花；有对比才有层次。（规范 §3.5 允许的渐变用途之一）
   用 ::before 而不是 border-image：border-image 在圆角上表现不一致。 */
.card--accent { position: relative; overflow: hidden; }
.card--accent::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--g-brand);
}
.card h3 { margin-bottom: var(--s-2); }
.card p:last-child { margin-bottom: 0; }

/* 产品卡片 */
.product-card {
  display: flex; gap: var(--s-5); align-items: flex-start;
  background: var(--c-bg); border: 1px solid var(--c-border);
  border-radius: var(--r-lg); padding: var(--s-5); box-shadow: var(--sh-2);
}
.product-card__icon { width: 88px; height: 88px; flex: 0 0 auto; }
.product-card__body { flex: 1 1 auto; min-width: 0; }
.product-card__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-3);
}
.product-card__spec { font-size: 14px; color: var(--c-text-mute); }
.product-card__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-4); }

/* 空状态（必须有出口，不许只写「暂无数据」） */
.empty {
  text-align: center; padding: var(--s-8) var(--s-5);
  border: 1px dashed var(--c-border); border-radius: var(--r-md);
  color: var(--c-text-sub);
}

/* ============================ 响应式 ============================ */
@media (max-width: 1023px) {
  .site-footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: 1fr; }
}
@media (max-width: 767px) {
  body { font-size: 16px; }
  h1 { font-size: 30px; }
  h2 { font-size: 24px; }
  .hero { padding-block: var(--s-7) var(--s-6); }
  .hero h1 { font-size: 32px; }
  .hero__sub { font-size: 17px; }
  /* 导航在窄屏变矮（60px）。--header-h 必须跟着改，
     否则贴在它下方的浮层会按 72px 算、压到导航上去。 */
  :root { --header-h: 60px; }
  .site-header__inner { height: var(--header-h); }
  .site-nav { display: none; }          /* 移动端导航进抽屉，由 site.js 接管 */
  .site-header [data-nav-toggle] { display: inline-flex; }

  /* 抽屉展开态：贴 header 下沿的整宽面板。
     ⚠️ 依赖 .site-header 是 positioned（它是 position: sticky）——
        哪天把它改成 static，inset 会失效、面板会跑到页面顶部去。
     2026-10-07 之前这里**只有 `display: none`，没有展开态**，
     所以移动端点了「菜单」没有任何反应、主导航完全不可达。 */
  .site-nav[data-open="true"] {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    position: absolute;
    inset: 100% 0 auto 0;
    margin-inline-start: 0;
    padding: var(--s-2) var(--s-4) var(--s-4);
    background: var(--c-bg);
    border-bottom: 1px solid var(--c-border);
    box-shadow: var(--sh-2);
  }
  .site-nav[data-open="true"] a {
    padding: var(--s-3) 0;
    border-bottom: 1px solid var(--c-border);
  }
  .site-nav[data-open="true"] a[aria-current="page"] { border-bottom-color: var(--c-primary); }
  .site-nav[data-open="true"] .btn { margin-top: var(--s-3); border-bottom: 0; }
  .product-card { flex-direction: column; }
  .section { padding-block: var(--s-7); }
}

/* ============================ 滚动淡入 ============================ */
/* 需求文档规定的滚动动效：元素进入视口 opacity 0→1 + translateY 16px→0。
 *
 * ⚠️ 两条安全设计，缺一不可：
 *
 * ① **默认可见**。这里刻意**不**写 `[data-reveal] { opacity: 0 }` ——
 *    万一 JS 加载失败或未执行，那会让整页内容永久不可见。
 *    改成只在 JS 就绪后（html 上有 .js-reveal）才隐藏待入场元素。
 *
 * ② **首屏元素不参与**。给它们的 opacity:0 会把 LCP 往后推
 *    （LCP 不计 opacity:0 的元素为"已绘制"），所以 index.html 里
 *    只给首屏**以下**的区块加 data-reveal。
 */
.js-reveal [data-reveal] {
  opacity: 0; transform: translateY(16px);
}
.js-reveal [data-reveal].is-in {
  opacity: 1; transform: none;
  transition: opacity .22s ease, transform .22s cubic-bezier(.2, .7, .3, 1);
}

/* ============================ hover 微交互 ============================ */
/* 只动 transform / box-shadow / background-color —— 都是轻量或合成属性。
   prefers-reduced-motion 下由文件末尾的通用规则把 transition 压到 .01ms。 */
.btn {
  transition: background-color .18s ease, color .18s ease, border-color .18s ease,
              transform .18s ease, box-shadow .18s ease;
}
.btn--primary:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(37, 99, 214, .24); }
.btn--ghost:hover   { transform: translateY(-1px); }
.card:hover         { transform: translateY(-2px); box-shadow: var(--sh-2); }
.product-card       { transition: box-shadow .18s ease, transform .18s ease; }
.product-card:hover { transform: translateY(-2px); box-shadow: var(--sh-3); }

/* ============================================================================
 * 子页面版面组件
 *
 * 对应《UI 页面设计规范》v3.0 §6.3 – §6.7（原 C7 待实现清单）。
 * 适用于 /apps、/blog、/contact、/about 四页。
 *
 * ⚠️ 本节**只新增类**，不改动上面任何一条已有规则。
 * ⚠️ 本节不新增任何颜色令牌，全部走已有的 --c-*（对比度已由构建守卫实测）。
 * ========================================================================== */

/* ── 页头带（§6.3）：四个子页面共用，软底，不铺 --c-hero-bg ─────────────
 * 为什么不用 --c-hero-bg：全站的"冲击力预算"是共用的，首页首屏的蓝底独一份
 * （§6.1）。子页面页头统一为"同一层的门"，页面的个性交给主体和收尾。 */
.page-head {
  background: var(--c-bg-soft);
  border-bottom: 1px solid var(--c-border);
  padding-block: var(--s-9) var(--s-8);   /* 96 / 64 */
}
.page-head h1 { font-size: 48px; letter-spacing: -.02em; margin-bottom: var(--s-3); }
.page-head__lead {
  font-size: 17px; color: var(--c-text-sub);   /* 软底 5.49:1 ✅ */
  max-width: 38em; margin: 0;
}

/* ── 收尾转化（§6.3）：白底居中 + 一个主按钮 ───────────────────────────── */
.section--cta { text-align: center; }
.section--cta .prose { margin-inline: auto; }

/* ── 两列网格（§6.2）─────────────────────────────────────────────────── */
.grid-2 { display: grid; gap: var(--s-5); grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* ── 产品大卡（§6.4）：产品少时把卡片做大，页面才不显得空 ───────────────── */
.product-card--featured { padding: var(--s-6); }
.product-card--featured .product-card__icon { width: 128px; height: 128px; }
/* h2 与 h3 都覆盖：首页的产品卡用 h3；/apps 里产品名是页头之下的第一个区块标题，用 h2 */
.product-card--featured h2,
.product-card--featured h3 { font-size: 28px; line-height: 1.25; }

/* ── 浅蓝转化条（§6.4 / §6.6）───────────────────────────────────────────
 * ⚠️ 浅蓝底上的文字只能用 --c-text（13.34:1）或 --c-text-sub（5.19:1）。
 *    **不能用 --c-text-mute**（实测 4.42:1，不达 AA，见规范 §七 末注）。 */
.callout {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--s-4);
  background: var(--c-primary-soft);
  border-radius: var(--r-lg);
  padding: var(--s-6);
  margin-top: var(--s-8);
}
.callout p { margin: 0; color: var(--c-text); font-size: 17px; }

/* 左侧引导卡（§6.6 /contact）：竖排链接，不用横向转化条 */
.guide {
  background: var(--c-primary-soft);
  border-radius: var(--r-lg);
  padding: var(--s-5);
}
.guide h2 { font-size: 18px; margin-bottom: var(--s-3); }
.guide ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.guide a { color: var(--c-primary-strong); font-weight: 600; }   /* 浅蓝底 5.35:1 ✅ */

/* ── 文章卡（§6.5）──────────────────────────────────────────────────── */
.post-card {
  display: block; background: var(--c-bg);
  border: 1px solid var(--c-border); border-radius: var(--r-md);
  padding: var(--s-5); color: inherit;
  transition: transform .18s ease, box-shadow .18s ease;
}
.post-card:hover { transform: translateY(-2px); box-shadow: var(--sh-2); text-decoration: none; }
.post-card h3 { font-size: 20px; margin: var(--s-3) 0 var(--s-2); }
.post-card p { font-size: 15px; color: var(--c-text-sub); margin-bottom: var(--s-3); }
.post-card__meta { font-size: 14px; color: var(--c-text-mute); margin: 0; }
/* 头条卡跨两列：版面被"打破"一次，眼睛先落在它上面（§6.5 的冲击力来源） */
.post-card--lead { grid-column: span 2; }
.post-card--lead h3 { font-size: 28px; line-height: 1.3; }

/* ── 分类条（§6.5）：分类 ≥2 且各有内容时才渲染 ───────────────────────── */
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-6); }
.filter-bar a {
  padding: var(--s-2) var(--s-4); border-radius: var(--r-pill);
  font-size: 15px; color: var(--c-text-sub); background: var(--c-bg-soft);
  text-decoration: none;
}
.filter-bar a[aria-current="page"] {
  background: var(--c-primary-soft); color: var(--c-primary-strong); font-weight: 600;
}

/* ── 表单卡（§6.6）────────────────────────────────────────────────────
 * ⚠️ 输入框边框用 --c-text-mute（4.99:1 ≥ 3:1）。
 *    **不许用 --c-border**（1.23:1）——它是"用户定位输入位置"的唯一线索，
 *    属非文字部件，必须 ≥ 3:1（规范 §4.4）。 */
.form-card {
  background: var(--c-bg); border: 1px solid var(--c-border);
  border-radius: var(--r-lg); box-shadow: var(--sh-2);
  padding: var(--s-6);
}
.form-card__row { display: grid; gap: var(--s-4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
.form-card__field { margin-bottom: var(--s-5); }
.form-card__field:last-of-type { margin-bottom: var(--s-6); }
.form-card label { display: block; font-size: 15px; font-weight: 600; margin-bottom: var(--s-2); }
.form-card .req { color: var(--c-danger); font-weight: 400; }   /* 白底 5.33:1 ✅ */
.form-card input, .form-card select, .form-card textarea {
  width: 100%; font: inherit; font-size: 16px;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--c-text-mute);
  border-radius: var(--r-sm);
  background: var(--c-bg); color: var(--c-text);
}
.form-card textarea { min-height: 132px; resize: vertical; }
.form-card input::placeholder, .form-card textarea::placeholder {
  color: var(--c-text-mute);   /* 4.99:1 ✅ —— 浏览器默认灰不保证达标 */
}
.form-card .btn { width: 100%; }
.form-card__note { font-size: 14px; color: var(--c-text-sub); margin: var(--s-4) 0 0; }

/* ── 提交结果提示（需求 §3.4.1：成功用顶部绿色条 3s 自动消失，失败用表单内联红字）──
 * 两者刻意是**两个不同的东西**：
 *   · 成功条是页面级的、会自己消失 —— 因为它不需要用户做任何事；
 *   · 错误是表单内的、不会自己消失 —— 因为它需要用户**改点什么**，
 *     一个 3 秒后消失的错误提示，等于让用户重新猜一遍哪里填错了。
 *
 * ⚠️ 成功条必须 **position: fixed**（2026-10-08 修）。
 *
 *    第一版它是表单上方的一个普通块。用真实浏览器量过：
 *      · 桌面 1440×900：用户要滚到 scrollY=744 才看得到提交按钮，
 *        此时成功条在 **top = -344px** —— 视口上方 344 像素处。
 *      · 手机 375×812：表单更长，只会更糟。
 *    也就是说：**它确实弹出来了，只是没有一个人看得见。**
 *    然后表单被清空、3 秒后那行字也没了，页面变回和刚进来时一模一样 ——
 *    用户的原话是「感觉没有提交一样」。
 *
 *    它叫「顶部绿色条」，那就得**在视口顶部**，
 *    而不是「在文档流的表单上方」。判据也从「绿条出现了」改成「绿条在视口里」——
 *    前者是一个假的绿灯，它绿着，用户却什么都没看见。
 *
 * ⚠️ 颜色一律走令牌（--c-tag-green-* / --c-danger），不许在这里写十六进制：
 *    令牌是构建守卫唯一认得的东西，写死一个色值就等于**绕过了对比度检查**。
 *    （第一版正是两个硬编码色 —— 它们其实达标，但"其实达标"不是重点，
 *      重点是没有任何一层检查会知道它们存不存在。）
 */
.form-banner {
  position: fixed; left: 50%;
  /* ⚠️ 贴在**导航下方**，不是视口顶部。
     导航是 sticky 的，视口顶部那 72px（窄屏 60px）永远被它占着；
     第一版直接写 top: var(--s-4)，成功条就压在导航栏上，
     和 logo、「菜单」按钮叠成一团（截图一眼就能看出来）。
     +1px 是导航的下边框。 */
  top: calc(var(--header-h) + 1px + var(--s-3));
  transform: translateX(-50%);
  z-index: 60;                              /* 高于 header 的 50 */
  max-width: calc(100vw - var(--s-6));
  /* ⚠️ 深绿底 + 白字，不用「浅底深字」那套徽标配对。
     第一版用的是 --c-tag-green-bg / --c-tag-green-ink（浅绿底深绿字，
     6.36:1 完全达标），但截图一看就发现：浅绿底压在白页面上**几乎没有轮廓**，
     它融进背景里了 —— 达标和「看得见」是两件事（又一次）。
     这个条的职责是**打断用户一下**：用户此刻的注意力在底部提交按钮上，
     一个淡绿色的方块出现在顶部，大概率还是被忽略。
     深底白字对比度更高、轮廓明确，而它 3 秒就消失，突兀一点是应该的。 */
  background: var(--c-tag-green-ink); color: var(--c-bg);   /* 白字在深绿上 ✅ */
  border-radius: var(--r-pill);
  padding: var(--s-3) var(--s-6);
  box-shadow: var(--sh-3);
  font-size: 15px; font-weight: 600; text-align: center;
  /* 从上方滑入。它是浮层，没有任何「本来就在那儿」的暗示；
     而用户此刻的注意力在底部的提交按钮上，不动一下很难被余光注意到。 */
  animation: banner-in .22s ease-out;
}
@keyframes banner-in {
  from { opacity: 0; transform: translate(-50%, -14px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .form-banner { animation: none; }
}
.form-error {
  color: var(--c-danger);                                            /* 白底 5.33:1 ✅ */
  font-size: 14px; margin: var(--s-3) 0 0;
}

/* 两栏：左说明 / 右表单。≤1023px 单列，且**表单在上**——
   用户来这里是留言的，别让他先读一屏说明（§6.6）。 */
.contact-layout {
  display: grid; gap: var(--s-7);
  grid-template-columns: 5fr 7fr; align-items: start;
}
.contact-layout__side { display: grid; gap: var(--s-5); }
/* 侧栏卡片是并列分区，标题保持 h2 语义，但视觉压到 18px */
.contact-layout h2 { font-size: 18px; margin-bottom: var(--s-3); }

/* 公开联系方式：一项一行、各自独立。
   未配置的项**在构建期就不渲染**（并列的 if 区块），所以这里没有"占位符"样式。 */
.contact-line {
  margin: 0 0 var(--s-2); font-size: 15px; color: var(--c-text-sub);   /* 白底 5.85:1 ✅ */
}
.contact-line--none { color: var(--c-text-mute); }                     /* 白底 4.99:1 ✅ */

/* 客服微信二维码
   ⚠️ 两条硬约束，都不是审美问题：
     1. 图必须是**无损** WebP（tool/make_web_images.py 用 lossless 生成）。
        有损压缩会在黑白格边缘产生振铃，人眼看着还好，扫码识别率会掉——
        而"能扫出来"是这张图唯一的用途。
     2. 四周必须留够**静默区**（quiet zone）。图本身自带白边，这里再垫一层
        --c-bg-soft 底的 padding，让白边之外还有一圈浅灰，边界更清楚。
        静默区不够，是最常见的"图没问题但扫不出来"的原因。 */
.qr {
  margin: 0 0 var(--s-4); padding: var(--s-3);
  background: var(--c-bg-soft); border-radius: var(--r-sm); text-align: center;
}
.qr img { display: block; margin: 0 auto; width: 180px; height: 180px; }
.qr figcaption { margin-top: var(--s-2); font-size: 14px; color: var(--c-text-sub); }  /* soft 底 5.50:1 ✅ */

/* ── 竖排横卡（§6.7 /about 核心业务）：与首页三列卡片墙刻意不同 ────────────
 * 首页 ② 和 /about ③ 文案同源，版面必须不同——同源文案配同款版面，
 * 用户会觉得"这页我看过了"（§6.7）。 */
.card--wide {
  display: grid; grid-template-columns: 64px 1fr; gap: var(--s-5);
  background: var(--c-bg); border: 1px solid var(--c-border);
  border-radius: var(--r-md); padding: var(--s-5);
  /* 刻意**不加阴影**，避免与首页的卡片墙混淆 */
}
.card--wide__num {
  font-size: 40px; font-weight: 700; line-height: 1.1;
  color: var(--c-primary);   /* 白底 5.48:1 ✅（40px 属大号文字，3:1 即达标） */
}
.card--wide h3 { margin-bottom: var(--s-2); }
.card--wide p:last-child { margin-bottom: 0; }

/* ── 理念组（§6.7 /about 品牌理念）：竖线 + 留白，不加卡片 ──────────────── */
.tenets { display: grid; gap: var(--s-6); }
.tenet { border-left: 3px solid var(--c-primary); padding-left: var(--s-5); }
.tenet h3 { margin-bottom: var(--s-1); }
.tenet p:last-child { margin-bottom: 0; }

/* ── 步骤条（§6.7 /about 服务流程）：全页唯一的"图形" ────────────────────
 * 顺序信息靠**真实数字文本**（HTML 里写死，不是 CSS 伪元素生成），
 * 不依赖位置或颜色 —— WCAG 1.4.1（规范 §4.5）。 */
.steps {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--s-4); list-style: none; margin: 0; padding: 0;
}
.steps li { position: relative; }
.steps__num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--c-primary-soft); color: var(--c-primary-strong);   /* 5.35:1 ✅ */
  font-size: 15px; font-weight: 700; margin-bottom: var(--s-3);
}
.steps h3 { font-size: 17px; margin-bottom: var(--s-1); }
.steps p { font-size: 14px; color: var(--c-text-sub); margin: 0; }
/* 圆点之间的连线：纯装饰（1.23:1 的 --c-border 可以，规范 §4.4） */
.steps li::after {
  content: ""; position: absolute; top: 16px; left: 44px; right: calc(var(--s-4) * -1 + 4px);
  height: 1px; background: var(--c-border);
}
.steps li:last-child::after { display: none; }

/* ── 空状态图形（§6.5 /blog）：复用品牌 NG 标记（需求 §5.7）───────────── */
.empty__mark { width: 88px; height: 88px; margin: 0 auto var(--s-5); opacity: .9; }
/* 整页级的空状态（/blog）：去掉虚线框，靠留白与居中撑住版面（§6.5） */
.empty--plain { border: 0; padding-block: var(--s-9); }
/* 空状态的主文案要比 .empty 的默认灰更实一点，否则整屏只有一行灰字，像出错了 */
.empty__lead { color: var(--c-text); font-size: 17px; }

/* ── 子页面响应式 ─────────────────────────────────────────────────────── */
@media (max-width: 1023px) {
  .grid-2 { grid-template-columns: 1fr; }
  .post-card--lead { grid-column: auto; }   /* 单列时跨列会撑坏网格 */
  .contact-layout { grid-template-columns: 1fr; }
  .contact-layout__form { order: -1; }      /* 表单在上 */
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .steps li::after { display: none; }       /* 折行后连线会指错方向 */
}
@media (max-width: 767px) {
  .page-head { padding-block: var(--s-7) var(--s-6); }   /* 48 / 32 */
  .page-head h1 { font-size: 30px; }
  .product-card--featured { padding: var(--s-5); }
  .product-card--featured h3 { font-size: 24px; }
  .form-card { padding: var(--s-5); }
  .form-card__row { grid-template-columns: 1fr; }
  .callout { padding: var(--s-5); }
  .callout .btn { width: 100%; }
  .card--wide { grid-template-columns: 1fr; gap: var(--s-3); }
  .card--wide__num { font-size: 32px; }
  .steps { grid-template-columns: 1fr; }
}

/* ============================ 动效降级 ============================ */
/* 所有滚动动效在 prefers-reduced-motion 下必须关闭 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
