/* ============================================================================
 * 版块层：首屏 / 产品矩阵 / 关于我们 / 联系我们 / 页脚
 * ==========================================================================*/

/* ==========================================================================
 * 首屏
 * --------------------------------------------------------------------------
 * 全站唯一的图形元素在这里：Canvas 点阵标识的环境动画。
 * 版式是「图在上、字在下」的中轴对齐，四周留极大空白 —— 首屏的力度
 * 来自留白与字号的悬殊对比，而不是任何装饰。
 * ========================================================================*/

.hero {
  position: relative;
  min-height: 100svh;          /* svh 而非 vh：移动端地址栏收起时不跳版 */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* 黄金分割落位：让整块（标识 + 公司名）的光学中心落在视口高的 38.2% 处，
     而不是正中 —— 主体偏上、下方留大片空白，与底部的滚动提示呼应。
     推导：flex 居中时内容中心 = (H + padTop - padBot) / 2，
     令其等于 0.382H 解得 padBot = padTop + 0.236H。
     23.6vh 的来源：上下内边距之差必须等于视口高的 (1 - 2×38.2%) = 23.6%。
     故下内边距写成「上内边距 + 23.6vh」。
     这个写法不依赖字号与标识尺寸，日后调大小无需重算；
     内容即使高过视口，差值仍在，比例只是平滑降级而非跳变。 */
  padding-block: calc(var(--header-h) + var(--sp-6))
                 calc(var(--header-h) + var(--sp-6) + 23.6vh);
  text-align: center;
}

/* 兜底：浏览器不支持 svh 时退回 vh */
@supports not (height: 100svh) {
  .hero { min-height: 100vh; }
}

.hero-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
}

/* ---------------------------------------------------------- 标识画布 */

.hero-canvas-wrap {
  /* 比例是「格数 + 四周溢出余量」之比，而不是格数比 20:23 本身。
     格数 20 × 23 由提取脚本按标识轮廓的宽高比选出（见
     tools/extract-dots.py 的 choose_cells）；四周各留 0.7 个格距的余量，
     因为最外圈圆点会溢出格阵盒 —— 推导与数值见 js/logo-halftone.js 的 PAD。
     于是 (20 + 1.4) : (23 + 1.4) = 21.4 : 24.4。

     余量必须留在画布内：画布外的像素不存在，外圈圆点会被裁成平口。
     写死比例可避免画布在字体加载前后跳动。 */
  aspect-ratio: 21.4 / 24.4;
  /* 这里约束的是「格阵部分」的宽度，再整体放大 214/200 = 1.07 倍喂给
     画布 —— 换句话说，标识本身仍按原来的 clamp(160px, 30vh, 200px) 走，
     一点没变小，变大的只有它周围那圈透明余量。
     不能反过来写成 clamp(160px, 30vh, 214px)：那样在 30vh 小于 214px 的
     视口上（高度低于 713px），标识会跟着缩掉 6.5%，与本次改动无关。 */
  /* v1.5：格阵 200 → 260px。首屏的文字层级全部收小之后，体量由标识承担，
     因此标识放大、文字变小，关系变成「大图 + 小字」（像一枚印章配一行注脚）。
     上限取 260 而非 280 是几何约束：放大标识会抬高首屏内容块，而
     黄金分割落位要求 H ≥ (200 + 内容块高) / 0.764。取 260 时 1080p 仍落在
     38.2%，取 280 即越界，1080p 上落点开始上漂（见白皮书 §12.1）。
     动画在 JS 里、尺寸在 CSS 里 —— 放大标识不触碰 logo-halftone.js 一行。 */
  width: calc(clamp(160px, 30vh, 260px) * 1.07);
  margin-bottom: clamp(1.5rem, 5vh, 3rem);
}

.hero-canvas {
  /* canvas 默认是 inline，基线下会多出一条缝，把高度撑过 100%；
     block 化后它严格等于外层容器，尺寸完全由这里的 CSS 决定
     （JS 只负责后端缓冲的分辨率，不碰布局尺寸）。 */
  display: block;
  width: 100%;
  height: 100%;
}

/* 心跳：标识整体轻微缩放。
   曲线是心电的样子 —— 快起快落的一下大的，紧跟一下小的，然后是一段
   相对长的间歇，而不是等幅正弦（那是呼吸，不是心跳；点阵本身已经在
   呼吸了）。幅度只给 2.5% / 1.5%：标识高 244px，一次心跳边缘最多挪
   6px，再多就从「心跳」变成跟点阵抢戏了。

   缩放的是整个画布元素、含它四周那圈透明余量（PAD 见 logo-halftone.js），
   所以标识与画布边的相对关系不变，不会把外圈圆点送出去被裁。
   画布缓冲是 214×244 的 2 倍，1.025 的缩放仍在降采样区间内
   （1.025 < 2），边缘不会因此发虚 —— 这里没有重新栅格化。

   v1.5-r4：--dur-beat 由 1.3s 改为 2.0s（需求方：跳动频率有点快）。
   由「一次呼吸跳两下」改为「心跳按静息心率走」，与点阵呼吸不再整倍同步 ——
   取舍与代价见 tokens.css 的 --dur-beat 注释。关键帧形状一字未改。

   两处要跟着知道的连带影响：
   · js/logo-halftone.js 的 measure() 因此改用 clientWidth/Height ——
     缓冲尺寸必须来自布局盒，不能来自被 transform 放大过的视觉盒；
   · js/main.js 换算指针坐标时读的是视觉盒，于是「指针相对标识中心」的
     位移被一并放大 2.5%。中心处误差为 0（transform 原点就是盒中心），
     越往边缘越大，最多 5px，落在感应半径（两倍标识尺寸）的平滑衰减里，
     读不出差别，故不额外补偿。 */
@media (prefers-reduced-motion: no-preference) {
  .hero-canvas-wrap {
    animation: logo-beat var(--dur-beat) var(--ease-out) infinite;
  }
}

@keyframes logo-beat {
  0%   { transform: scale(1); }
  6%   { transform: scale(1.025); }   /* lub */
  15%  { transform: scale(1); }
  24%  { transform: scale(1.015); }   /* dub */
  33%  { transform: scale(1); }
  100% { transform: scale(1); }
}

/* JS 关闭时 Canvas 不会有任何绘制 —— 用纯 CSS 排出一个极简字标顶上。
   内容永远不依赖 JS 才可见。 */
.hero-canvas-fallback { display: none; }

.no-js .hero-canvas-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
}
/* 没有 JS 就没有任何绘制，画布是块死白，必须撤掉 ——
   否则它会和兜底字标并排挤在一起。 */
.no-js .hero-canvas { display: none; }
.no-js .hero-canvas-fallback {
  display: block;
  font-size: clamp(4rem, 12vw, 7rem);
  font-weight: var(--fw-thin);
  letter-spacing: .06em;
  line-height: 1;
}

/* ---------------------------------------------------------------- 文字 */

.hero-title {
  font-size: var(--fs-display);
  letter-spacing: var(--ls-display);
  /* v1.5-r3：公司名取朱砂，成为首屏唯一的亮色点。
     需求方原话「大面积的黑白点缀亮色」—— 首屏的构成因此是：
     黑（点阵标识）+ 朱（公司名）+ 黑（主张句），一处亮色，其余全素。
     白底上 5.9:1，正文级对比度成立。
     一个要目视确认的点：Thin 100 在彩色上的笔画观感会比纯黑略「薄」，
     若看着发虚，解法是保留颜色、把字重提到 Light（不动字号）。 */
  color: var(--c-accent);
  /* 公司名必须单行 —— 折行会把这个中轴对称的首屏彻底破坏。
     字号已按视口宽度算好（见 --fs-display），此处只需禁止换行。 */
  white-space: nowrap;
}

/* 中英双名锁排：英文作中文名的注脚，而不是并列的第二行大字。
   小字号 + 大字距 + 降不透明度，是双语字标的标准处理方式。
   负外边距把它拉近中文名 —— 二者是一个整体。 */
.hero-en {
  font-size: var(--fs-xs);
  letter-spacing: .16em;
  color: var(--c-faint);
  margin-top: calc(var(--sp-3) * -1);
  /* 英文行较长，窄屏允许折行；中文名才是必须单行的那个 */
  text-wrap: balance;
}

.hero-tagline {
  font-size: var(--fs-lead);
  color: var(--c-ink);
  letter-spacing: .08em;
  margin-top: var(--sp-1);
}

/* 中英文分行。英文沿用与 .hero-en 完全相同的「注脚」处理
   （小字号 + 大字距 + 降不透明度）—— 首屏因此有两组中英锁排，
   用同一套规则，节奏才是统一的，而不是两处各说各话。 */
.hero-tagline-cn,
.hero-tagline-en { display: block; }

.hero-tagline-en {
  margin-top: var(--sp-2);
  font-size: var(--fs-xs);
  letter-spacing: .16em;
  color: var(--c-faint);
}

.hero-sub {
  font-size: var(--fs-sm);
  color: var(--c-muted);
  letter-spacing: .1em;
}

/* ------------------------------------------------------------ 滚动提示 */

.hero-scroll {
  position: absolute;
  left: 50%;
  /* 锚定「首屏折线」而非版块底边。黄金分割要求下方留白多于上方，于是在
     视口高不足约 900px 的笔记本上，.hero 会比视口高出一截 —— 此时若仍用
     bottom 定位，这个提示会被推到折线之下，首屏完全看不见。
     改用 top 定位到 100svh 再上移自身高度：版块恰好一屏时，与原来的
     bottom 定位结果逐像素一致，只有高过一屏时才体现出差别。
     页头是 fixed（不占流），.hero 从 y=0 开始，故 100svh 即折线。 */
  top: calc(100vh - var(--sp-6));
  transform: translate(-50%, -100%);

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);

  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-faint);
}

.hero-scroll-line {
  width: 1px;
  height: 44px;
  background: var(--c-rule-strong);
  /* 一条向下生长的细线，暗示「还有内容」 —— 比箭头更克制 */
  transform-origin: top;
  animation: scroll-cue 2.4s var(--ease-in-out) infinite;
}

@keyframes scroll-cue {
  0%        { transform: scaleY(0); transform-origin: top; }
  45%       { transform: scaleY(1); transform-origin: top; }
  55%       { transform: scaleY(1); transform-origin: bottom; }
  100%      { transform: scaleY(0); transform-origin: bottom; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-scroll-line { animation: none; transform: scaleY(1); }
}

/* 支持 svh 的浏览器用 svh —— 移动端地址栏收起时折线不跳 */
@supports (height: 100svh) {
  .hero-scroll { top: calc(100svh - var(--sp-6)); }
}

/* 首屏内容较高时滚动提示可能与标签重叠，窄屏直接隐藏 */
@media (max-height: 620px) {
  .hero-scroll { display: none; }
}

/* ==========================================================================
 * 版块头部
 * ========================================================================*/

/* ==========================================================================
 * 版块头：轴号系统
 * --------------------------------------------------------------------------
 * 版块序号从 12px 的注脚级放大到 96px，成为全站最大的元素 —— 这是
 * 「安静的文字」得以成立的前提：体量从文字转移到数字上，而两位数的
 * 放大不受容器宽度限制，也不违反「文字克制」。字号跨度因此由 3 倍变 8 倍。
 *
 * 序号与正文之间是一条 1px 轴线，样式取自建筑图纸的轴号系统。
 * 这是建筑行业专属的视觉隐喻，天然黑白，成本极低，
 * 而且它让「秩序」第一次被看见 —— 访客会觉得这是一张图纸，不是一个模板。
 *
 * 序号是全站四处允许出现朱砂的位置之一（其余三处：证据数字、
 * 点阵成熟度满格标记、可点击链接）。它落在白底上，对比度 5.9:1。
 *
 * 首版说明：轴线目前只贯穿版块头（序号 → 标题），没有跨整个版块拉通，
 * 因为它需要重排全部版块容器的栅格。全高轴线列为 P2 细化项。
 * ==========================================================================*/

.section-head {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 0;
  align-items: start;
}

.section-index {
  display: block;
  font-family: var(--f-mono);
  font-size: var(--fs-index);
  line-height: .82;
  letter-spacing: -.02em;
  color: var(--c-accent);
  /* 序号与轴线之间留出气口。轴线的横坐标因此由序号自身宽度决定，
     不写死数值，三个版块（01/02/03）的轴线必然对齐。 */
  padding-right: clamp(1rem, 3vw, 2.25rem);
}

/* 承载标题与说明，左侧那道 1px 就是轴线 */
.section-head-body {
  border-left: var(--axis-w) solid var(--c-rule-strong);
  padding-left: clamp(1rem, 3vw, 2.25rem);
}

.section-title {
  font-size: var(--fs-title);
  margin-bottom: var(--sp-3);
}

.section-desc {
  font-size: var(--fs-body);
  color: var(--c-muted);
  max-width: 46ch;      /* 控制中文行宽，约 40–46 字最舒适 */
}

/* 紧跟主说明的第二句（给身份的那句与给人话的那句分列两段） */
.section-desc + .section-desc {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--c-faint);
}

/* 窄屏：序号退回常规尺寸，标题不再被轴线挤窄 —— 但轴线保留 */
@media (max-width: 560px) {
  .section-head {
    grid-template-columns: 1fr;
    row-gap: var(--sp-4);
  }
  .section-index {
    font-size: clamp(2.75rem, 14vw, 3.5rem);
    line-height: 1;
    padding-right: 0;
  }
  .section-head-body {
    padding-left: var(--sp-4);
  }
}

/* ==========================================================================
 * 点阵显影：版块标题；首屏标语中文行做的是逐字渐显（见下）
 * --------------------------------------------------------------------------
 * 为什么要显影，以及为什么不用 Canvas
 * ------------------------------------
 * 首屏的标识是「点阵长成图形」，这里补上另一半：「点阵长成文字」。
 * 两者是同一套语言的两个方向，合起来点阵才不只是个装饰花样。
 * 这一套只用在版块标题上 —— 标题 32px 起、笔画够粗，点阵才铺得开；
 * 首屏标语最大才 22px（1440px 视口下 21.6px），显影会退化成噪点，
 * 它走的是逐字渐显。
 *
 * 实现上没有走 Canvas 那条路。Canvas 显影要先离屏把字画一遍、采样成点、
 * 再把画布盖在真字上，于是必须让画布里的字与 DOM 里的字逐像素对齐 ——
 * 字号、字距、字重、换行、中点标点的挤压，任一项对不上就会看到一次错位。
 * 这里改用遮罩（mask）：文字仍是浏览器自己排的那份，原样不动，
 * 只在它上面盖一张点阵网，动画把这张网由粗收到细、由薄收到满。
 * 排版因此不可能对不上 —— 因为压根没有被排第二遍。
 *
 * 三条降级路径
 * ------------
 *   · 「减少动态效果」 -> 整段包在 no-preference 里，从不进入，
 *                          标题就是普通标题、标语就是普通文字，
 *                          既没有遮罩残留，也没有停在 opacity 0 上
 *   · JS 未执行        -> .reveal 拿不到 .is-visible，标题动画不触发；
 *                          标语那条**照常逐字渐显** —— 它是纯 CSS，
 *                          不读 JS 也不需要 .is-visible，这是刻意的：
 *                          降级只该拿走锦上添花的那部分
 *   · 不支持遮罩插值的浏览器 -> 退化为「前一半点阵、后一半实心」，
 *                          仍是一次成立的显影，而不是坏掉
 * ========================================================================*/

@media (prefers-reduced-motion: no-preference) {

  /* 版块标题：父级 .reveal 加了 .is-visible 之后才开始。
     reveal.js 显示后即 unobserve，所以每屏只播一次，来回滚动不会重放。 */
  .reveal.is-visible .section-title {
    animation: dot-resolve var(--dur-resolve) linear backwards;
    animation-delay: var(--delay-title-resolve);
  }

  /* 首屏标语：逐字渐显，一句陈述被读成一句有停顿的话。
     英文行不参与 —— 它是 13px 的注脚，撑不起任何显影效果；而且它是「注」，
     本就不该跟着正句一起晃（见上面的分行说明）。
     中文行曾与版块标题共用 dot-resolve 点阵显影，但 22px 的中文一行不到
     5 格高、笔画只有 1.8px，点阵要贴着笔画走才认得出是字，起手那半程读
     下来是一把沙。小字号该用的是直接渐入：由虚转实，而不是「聚成字」。 */
  .hero-tagline-cn > span {
    animation: tagline-fade var(--dur-char) var(--ease-out) backwards;
    animation-delay: calc(
      var(--delay-hero-resolve) + var(--i) * var(--stagger-char)
    );
  }

  /* 逗号之后停一拍 —— 整句的戏剧感全在这一拍上：逗号前说完，静一下，
     后半句才接上。
     v1.5：标语由「我不完美，但是真实。」（10 字，逗号在第 5）换成
     「AI，把建筑做对。」（9 字，逗号在第 3），因此这一拍的位置
     由 n + 6 改为 n + 4。**改的是逗号的位置，不是动画。**
     三档节奏令牌（--dur-char / --stagger-char / --beat-clause）
     与关键帧 tagline-fade 仍然一行未动。 */
  .hero-tagline-cn > span:nth-child(n + 4) {
    animation-delay: calc(
      var(--delay-hero-resolve) + var(--i) * var(--stagger-char) + var(--beat-clause)
    );
  }

  /* 黑底版块的格言复用同一套逐字渐显（v1.5）。
     这是「在新元素上引用既有动画」，不是修改既有动画 ——
     关键帧 tagline-fade 与三个节奏令牌（--dur-char / --stagger-char /
     --beat-clause）一行未动，只是换了播出的时机与位置：
     首屏标语随载入播，格言随滚动进入视口播（挂在 .reveal.is-visible 上），
     且不含首屏那条 1.1s 的等待（--delay-hero-resolve 是首屏专有的先后关系）。
     第 6 个子元素仍是「但」，逗号后的那一拍仍然对得上。 */
  .reveal.is-visible .quote-cn > span {
    animation: tagline-fade var(--dur-char) var(--ease-out) backwards;
    animation-delay: calc(var(--i) * var(--stagger-char));
  }

  .reveal.is-visible .quote-cn > span:nth-child(n + 6) {
    animation-delay: calc(
      var(--i) * var(--stagger-char) + var(--beat-clause)
    );
  }
}

/* 首屏标语的渐入：只动不透明度。小字号上任何位移或缩放都会先被读成抖动，
   而不是「浮现」，所以这里连 1px 的位移都不给。
   逐字渐显与整行渐显共用这一对关键帧 —— 变的只是「谁来播、什么时候播」：
   每个字各自 backwards，于是在轮到它之前一直停在 from 上（不可见），
   不是先整行亮一下再重来。
   字是 `display: inline` 的普通 span：只动不透明度就不需要 inline-block，
   因此这十个 span 对排版几乎没有影响 —— 实测整行 237.61 → 237.66px、
   左边缘 836.19 → 836.17px，差值都在 0.05px 以内，是内联盒的取整，
   不是字距或字号变了（每个字仍各自带着父级的 letter-spacing）。 */
@keyframes tagline-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* 起手与收尾都是「点阵」，只是疏密不同，因此两个关键帧是同一套参数的
   两端，中间由浏览器插值 —— 这里不需要任何逐帧脚本。
   墨量走的是 stop 的平方（点面积 ∝ 半径²），点距同时收到 2.5px，
   两条一起走，前半程才看得出「点」而不只是「灰」。
   起手那一端允许被使用方覆盖（--resolve-pitch-from / --resolve-ink-from），
   留给将来更小的标题字号；目前没有使用方覆盖它，版块标题用的是默认值。
   收尾那一端不开放覆盖 —— 它必须正好是「整格盖满」，遮罩才敢在动画
   结束后直接撤掉；改早一点就会在收尾处留下一次看得见的跳变。 */
@keyframes dot-resolve {
  from {
    -webkit-mask-image: radial-gradient(circle, #000 var(--resolve-ink-from, 26%),
                                        transparent calc(var(--resolve-ink-from, 26%) + 2%));
            mask-image: radial-gradient(circle, #000 var(--resolve-ink-from, 26%),
                                        transparent calc(var(--resolve-ink-from, 26%) + 2%));
    -webkit-mask-size: var(--resolve-pitch-from, var(--dot-resolve-from))
                       var(--resolve-pitch-from, var(--dot-resolve-from));
            mask-size: var(--resolve-pitch-from, var(--dot-resolve-from))
                       var(--resolve-pitch-from, var(--dot-resolve-from));
  }
  to {
    /* stop 100% 时圆的半径已是格子的半对角线 —— 整格被盖满，
       遮罩全不透明，与「没有遮罩」逐像素等价。动画因此可以直接撤掉，
       末尾不会有一下跳变。 */
    -webkit-mask-image: radial-gradient(circle, #000 100%, transparent 100%);
            mask-image: radial-gradient(circle, #000 100%, transparent 100%);
    -webkit-mask-size: var(--dot-resolve-to) var(--dot-resolve-to);
            mask-size: var(--dot-resolve-to) var(--dot-resolve-to);
  }
}

/* ==========================================================================
 * 产品矩阵
 * ========================================================================*/

.product-grid {
  display: grid;
  /* 6 张卡片，3 / 2 / 1 列都能整除，因此任何断点下排布都是对称的。
     用 1fr 而非固定宽度，各行各列严格等宽等高。 */
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: clamp(1rem, 2vw, 1.5rem);
}

.product-card {
  /* 点击反馈的点阵层是 .product-card::after 的绝对定位子元素 */
  position: relative;
  /* 每张卡片各自独立：描边 + 直角，互不相连、不共用边框。
     v1.5 取消 14px 圆角 —— 圆角是通用 Web 卡片的语言，既不属于
     「点阵（交互与生命）」，也不属于「实线（结构与静态）」，
     它是页面上的第三种语言，也正是「看起来像模板」的来源。
     直角 + 与 10px 点阵栅格对齐的内边距，才是这套语言该有的卡片。 */
  border: 1px solid var(--c-rule);
  border-radius: var(--radius-card);
  background: var(--c-paper);
  transition: border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}

/* 悬停只改描边深浅与底色，不做位移 —— 卡片上带着 .reveal 的
   transform 过渡，再叠加位移会与渐显动画抢同一个属性。 */
.product-card:hover {
  border-color: var(--c-rule-strong);
  background: var(--c-wash);
}

/* 按下：描边压到纯黑、底色再深一档，表达「压下去」。
   同样刻意不用 transform —— 理由与上面悬停态一字不差。 */
.product-card:active {
  border-color: var(--c-ink);
  background: var(--c-press);
}

.product-link {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  height: 100%;
  padding: clamp(1.5rem, 3vw, 2rem);
  /* 继承卡片圆角，保证内边距区域的点击范围与视觉边界一致 */
  border-radius: inherit;
}

.product-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

.product-no {
  font-family: var(--f-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-faint);
}

.product-arrow {
  font-size: var(--fs-sm);
  color: var(--c-faint);
  line-height: 1;
  transition: transform var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}

.product-link:hover .product-arrow {
  color: var(--c-ink);
  transform: translate(3px, -3px);
}

/* 按下时箭头再进 1px，与描边、底色一起构成按下反馈 */
.product-link:active .product-arrow {
  transform: translate(4px, -4px);
}

.product-name {
  display: block;
  font-size: var(--fs-h3);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-title);
}

/* 名称与描述之间拉开距离：描述贴底，卡片高度一致时形成整齐的行 */
.product-desc {
  display: block;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--c-muted);
  margin-top: auto;
}

/* 成熟度标记所在的一行：点阵 + 文字标签，紧贴产品名下方。
   点阵 aria-hidden，含义由文字标签承载；3×3 墨量编码去掉颜色仍可区分。 */
.product-meta {
  display: flex;
  align-items: center;
}

.product-note {
  margin-top: var(--sp-6);
  font-size: var(--fs-sm);
  color: var(--c-faint);
}

/* 触屏设备没有 hover，改为按下时给出反馈 */
@media (hover: none) {
  .product-card:active { background: var(--c-wash); }
}

/* ------------------------------------------------------ 卡片点击反馈 */
/* 上面那些是「按下去」的即时反馈（:active，按下即生效、松手即消失）。
   这一段是「按完了」的确认态：由 js/cards.js 挂上 .is-pressed
   （有时再加 .is-pending），停留 --dur-ack 后摘掉。
   两者是两件事 —— 前者说「你的手正按着」，后者说「这一步收到了」。
   三组反馈回答同一个问题的三层：「是这张」→「按下去了」→「为什么不跳转」。 */

/* 「是这张」：卡片自己的轮廓内荡开一层点阵。
   全局涟漪（js/ripple.js）说的是「全站收到了这次点击」，这里是「是这一张」，
   所以它必须被裁在卡片自己的圆角边界内，漫出去就成了同一件事说两遍。 */
.product-card::after {
  content: "";
  position: absolute;
  inset: 0;
  /* 继承圆角，于是点阵被裁在卡片轮廓里。
     刻意不用 overflow:hidden —— 那会把键盘焦点圈（outline-offset:3px）
     一并裁掉，而焦点圈是键盘用户唯一的定位依据，不能用视觉整洁去换。 */
  border-radius: inherit;
  pointer-events: none;

  /* 点阵网格：点距与峰值直径都取自全站点阵那一套（--dot-pitch / 涟漪的
     2.4px 直径）。墨色取三级墨 --c-faint 而不是次级文字的 --c-muted ——
     这一层是盖在卡片自己的文字上的，点击坐标多半就落在字上，
     52% 的墨压上去会把这 0.6 秒变成「看不清」。确认的重任由序号、
     箭头和那句答复承担，这一层只管「是这张」的氛围，淡才对。 */
  background-image: radial-gradient(circle, var(--c-faint) 1.2px, transparent 1.5px);
  background-size: var(--dot-pitch) var(--dot-pitch);
  /* 默认铺法会把第一个点落在格心（距左上 4.5px）。挪半格，
     让点阵从卡片自己的左上角起算，与卡片边界对齐。 */
  background-position: calc(var(--dot-pitch) / -2) calc(var(--dot-pitch) / -2);

  /* 圆心是点击坐标，由 js/cards.js 写成百分比变量。
     写百分比而非 px：卡片宽高随栅格从 3 列降到 2 列再到 1 列，
     写死像素得给每个断点各配一个值。变量缺失时退回正中，不会失效。 */
  clip-path: circle(0% at var(--card-x, 50%) var(--card-y, 50%));
  opacity: 0;

  /* 再压一层软衰减。只有 clip-path 的话，漫开的就是一个边缘齐刷刷的
     圆盘 —— 读起来像有人贴了张圆片上来，而不是一圈荡开的东西。
     加上这层之后，点阵从落点向外由浓转淡，走到卡片边上已经没有了，
     于是它有了方向：从点出去，越远越弱。 */
  -webkit-mask-image: radial-gradient(circle at var(--card-x, 50%) var(--card-y, 50%),
                                      #000 0%, rgba(0, 0, 0, .5) 58%, transparent 94%);
          mask-image: radial-gradient(circle at var(--card-x, 50%) var(--card-y, 50%),
                                      #000 0%, rgba(0, 0, 0, .5) 58%, transparent 94%);
}

/* 整卡面积上的扩散，正是「减少动态效果」要拦下的那一类 —— 所以这条和
   下面的箭头回弹都用 no-preference 把播放本身掐掉，而不是把时长压到
   0.01ms。压时长的话动画仍会走一遍，只是快到看不见；掐掉播放则是根本
   不动，且 ::after 停在 opacity:0、clip-path: circle(0%) 的基础态上，
   连一帧点阵都不会画出来。
   信息并未因此丢失 —— 序号翻黑与那句「即将开放」走的是 transition，
   时长令牌已压到 0.01ms，于是它们「啪」地出现、停够 --dur-ack 再消失。
   动效没了，答复还在。 */
@media (prefers-reduced-motion: no-preference) {
  .product-card.is-pressed::after {
    animation: card-dot-spread 620ms var(--ease-out);
  }
}

@keyframes card-dot-spread {
  0%   { clip-path: circle(0% at var(--card-x, 50%) var(--card-y, 50%)); opacity: 1; }
  /* 前半程保持满不透明，点阵是「漫开」而不是「一路淡出」；
     淡出只发生在末段，避免整圈还没铺满就开始消失。 */
  55%  { opacity: 1; }
  100% { clip-path: circle(130% at var(--card-x, 50%) var(--card-y, 50%)); opacity: 0; }
}

/* 「按下去了」之二：箭头再进一格后回弹。
   原本悬停是 translate(3px,-3px)、按下是 (4px,-4px)，都是硬切；
   点击确认把它变成一次有来回的动作，才读得出系统接住了这一下。
   限定在有真指针的设备上：触屏没有悬停态，箭头的静止位是原点，
   而下面这套关键帧起止都停在悬停位（3px,-3px），在触屏上会先跳一下。
   触屏另有上面那条 :active 的底色反馈，不缺这一下。
   再叠一层 no-preference：这是一次真正的位移，理应让位给减少动态效果。
   掐掉之后箭头停在静止位，不跳也不错位。 */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .product-card.is-pressed .product-arrow {
    animation: card-arrow-nudge 460ms var(--ease-out);
  }
}

@keyframes card-arrow-nudge {
  0%   { transform: translate(3px, -3px); }
  35%  { transform: translate(6px, -6px); }
  60%  { transform: translate(2px, -2px); }
  100% { transform: translate(3px, -3px); }
}

/* 「按下去了」之三：序号翻成黑底白字，随后退回灰字。
   内边距与负外边距逐边相抵，方块的加入不改变这一行的排版 ——
   序号是六张卡片里唯一横向对齐的基准，它一动整行就跟着晃。
   左右不等（6 / 4）：等宽字距会在末字后留一格空白，补掉它方块才是正的。 */
.product-no {
  padding: 1px 4px 1px 6px;
  margin: -1px -4px -1px -6px;
  border-radius: 3px;
  transition: background-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}

.product-card.is-pending .product-no {
  background: var(--c-ink);
  color: var(--c-paper);
}

/* 「为什么不跳转」：卡片内浮出答复。
   只给占位链接 —— 产品一旦接入真实地址，products.js 会把 href 换掉，
   js/cards.js 里那条分支自然不再命中，反馈不需要跟着产品一起改。 */
.product-flash {
  /* 紧跟序号，并把箭头推向右侧（父级是 space-between 的三栏） */
  margin-inline: var(--sp-3) auto;
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-ink);
  white-space: nowrap;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

.product-card.is-pending .product-flash {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
 * 关于我们
 * ========================================================================*/

.about-body {
  max-width: 62ch;
  margin-bottom: clamp(3rem, 8vh, 5rem);
}

.about-lead {
  font-size: var(--fs-lead);
  line-height: 1.65;
  letter-spacing: .01em;
  margin-bottom: var(--sp-5);
}

.about-text {
  color: var(--c-muted);
  max-width: 52ch;
}

/* ------------------------------------------------------------ 技术底座 */

.foundation-title {
  font-size: var(--fs-micro);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-label);
  color: var(--c-faint);
  text-transform: uppercase;
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--c-rule);
  margin-bottom: var(--sp-6);
}

.foundation-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-6) clamp(1.5rem, 4vw, 3.5rem);
}

.foundation-item dt {
  font-size: var(--fs-body);
  letter-spacing: var(--ls-title);
  margin-bottom: var(--sp-3);
  padding-top: var(--sp-4);
  /* 每条顶部一道短横线，长度固定，比整条分隔线轻 */
  position: relative;
}

.foundation-item dt::before {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 24px;
  height: 1px;
  background: var(--c-rule-strong);
}

.foundation-item dd {
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--c-muted);
}

/* ==========================================================================
 * 联系我们
 * ========================================================================*/

.contact-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-6) clamp(1.5rem, 4vw, 3rem);
}

.contact-item {
  border-top: 1px solid var(--c-rule);
  padding-top: var(--sp-4);
}

.contact-item dt {
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-faint);
  margin-bottom: var(--sp-3);
}

.contact-item dd {
  font-size: var(--fs-body);
  line-height: var(--lh-snug);
}

.contact-item dd a {
  border-bottom: 1px solid var(--c-rule);
  padding-bottom: 2px;
}
.contact-item dd a:hover { border-bottom-color: var(--c-ink); }

/* ==========================================================================
 * 页脚
 * ========================================================================*/

.footer-brand {
  font-size: var(--fs-sm);
  letter-spacing: .06em;
}

/* 页脚同样中英并列，与首屏的锁排呼应 */
.footer-en {
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  color: var(--c-faint);
  margin-top: var(--sp-1);
}

.footer-icp a { border-bottom: 1px solid transparent; }
.footer-icp a:hover { border-bottom-color: var(--c-muted); }

.footer-legal a { border-bottom: 1px solid transparent; }
.footer-legal a:hover { border-bottom-color: var(--c-muted); }

.footer-sep { padding-inline: var(--sp-1); color: var(--c-faint); }

/* ==========================================================================
 * 法务页（privacy.html / terms.html）
 * --------------------------------------------------------------------------
 * 中长文本内容页，无视觉诉求，因此：
 *   · 使用系统字体栈，不纳入字体子集（避免为它把子集撑大数倍）
 *   · 版式只求稳定可读：单栏、限宽、段间距宽松
 * ========================================================================*/

.legal {
  font-family: system-ui, -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
  padding-top: calc(var(--header-h) + var(--sp-8));
  padding-bottom: var(--sp-10);
}

.legal .wrap { max-width: 760px; }

.legal-title {
  font-size: var(--fs-title);
  font-weight: 300;
  margin-bottom: var(--sp-3);
}

.legal-updated {
  font-size: var(--fs-xs);
  color: var(--c-muted);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid var(--c-rule);
  margin-bottom: var(--sp-7);
}

.legal h2 {
  font-size: var(--fs-h3);
  font-weight: 400;
  margin-top: var(--sp-8);
  margin-bottom: var(--sp-4);
}

.legal p,
.legal li {
  font-size: var(--fs-sm);
  line-height: 1.95;
  color: rgba(0, 0, 0, .78);
  margin-bottom: var(--sp-4);
  text-wrap: pretty;
}

.legal ul {
  list-style: none;
  padding-left: 0;
}

.legal li {
  position: relative;
  padding-left: var(--sp-5);
}

.legal li::before {
  content: "";
  position: absolute;
  left: 0; top: .82em;
  width: 6px;
  height: 1px;
  background: var(--c-faint);
}

.legal-back {
  display: inline-block;
  margin-top: var(--sp-8);
  font-size: var(--fs-sm);
  border-bottom: 1px solid var(--c-rule-strong);
  padding-bottom: 2px;
}
.legal-back:hover { border-bottom-color: var(--c-ink); }

/* ==========================================================================
 * v1.5 新增组件
 * --------------------------------------------------------------------------
 * 对应《官网调性与版式重构方案 v1.5.0》四、4.4 节。全部零图片、零依赖。
 * ==========================================================================*/

/* ------------------------------------------------- 点阵成熟度标记
   六款产品的成熟度编码。它**不只是视觉**：依《AKO 对外服务合规规范 v1.0.3》
   第九条 9.1／9.3，未实装功能不得写成完成态、规划中功能须明确标注 ——
   这个标记就是该义务的履行载体。
   三级用「密度 + 墨色」双重编码而非只靠颜色，去掉颜色仍可区分（色盲安全）。 */
.product-status {
  display: inline-block;
  width: 13px;
  height: 13px;
  background-repeat: repeat;
  background-size: 5px 5px;
  background-image: radial-gradient(circle, var(--c-accent) 2.2px, transparent 2.3px);
}
.product-status[data-level="beta"] {
  background-image: radial-gradient(circle, var(--c-ink) 1.7px, transparent 1.8px);
}
.product-status[data-level="dev"] {
  background-size: 6.5px 6.5px;
  background-image: radial-gradient(circle, var(--c-faint) 1.3px, transparent 1.4px);
}
/* 未标状态：最小最疏 —— 开发期占位，上线前必须逐条填实 */
.product-status[data-level="unknown"] {
  background-size: 6.5px 6.5px;
  background-image: radial-gradient(circle, var(--c-faint) .9px, transparent 1px);
}

.product-status-note {
  margin-left: var(--sp-2);
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-faint);
}

/* ------------------------------------------------------- 黑底反白版块
   全站唯一的「面」级对比。黑白系统里最强的资产是黑，一点不用是浪费。
   朱砂不上这一层 —— 黑底上只有 3.6:1，且黑块本身已是最大视觉事件。 */
.section-inverse {
  position: relative;
  background: var(--c-inv-bg);
  color: var(--c-inv-fg);
  overflow: hidden;
}

/* 点阵网纹：CSS 生成。10px 节距与首屏标识同源，是同一个母题。 */
.section-inverse::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(255, 255, 255, .14) 1px, transparent 1.1px);
  background-size: var(--dot-pitch) var(--dot-pitch);
  pointer-events: none;
}

.section-inverse > .wrap {
  position: relative;
  z-index: 1;
}

/* 黑底版块顶部那道点阵线要反过来：它落在黑底上，深色会看不见。
   注意只改「黑底版块自己」的这一条 —— 黑底版块**之后**那一节的白底
   仍然用深色点阵线（那是 .section + .section::before 的默认值）。 */
.section + .section-inverse::before {
  background-image: repeating-linear-gradient(
    to right,
    var(--c-inv-rule) 0 var(--dot-rule-size),
    transparent var(--dot-rule-size) var(--dot-rule-pitch)
  );
}

.section-inverse .section-index { color: var(--c-inv-faint); }  /* 朱砂只上白底 */
.section-inverse .section-head-body { border-left-color: var(--c-inv-rule); }
.section-inverse .section-desc { color: var(--c-inv-muted); }

/* 黑底版块内所有原本用黑色灰阶的文字必须整体翻白 —— 否则就是黑字压黑底。
   这里逐条覆盖，不用通配符：通配符会连带把不该改的（如块内链接）一起翻掉。 */
.section-inverse .about-lead  { color: var(--c-inv-fg); }
.section-inverse .about-text  { color: var(--c-inv-muted); }
.section-inverse .foundation-title {
  color: var(--c-inv-faint);
  border-bottom-color: var(--c-inv-rule);
}
.section-inverse .foundation-item dt::before { background: var(--c-inv-rule); }
.section-inverse .foundation-item dd { color: var(--c-inv-muted); }

/* ------------------------------------------------------------ 格言
   全站唯一一句「人话」。它从首屏下沉到这里之后，性质变了：
   在首屏是免责声明，在黑底上是立场宣言。 */
.quote {
  max-width: 36ch;
}

.quote-cn {
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  font-weight: var(--fw-thin);
  line-height: 1.35;
  letter-spacing: .04em;
}

.quote-en {
  display: block;
  margin-top: var(--sp-3);
  font-size: var(--fs-xs);
  letter-spacing: .16em;
  color: var(--c-inv-faint);
}

.quote-note {
  margin-top: clamp(1.5rem, 4vh, 2.5rem);
  max-width: 54ch;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--c-inv-muted);
}

/* 能力边界：与格言同处黑底，是同一件事的两种说法 */
.boundary {
  margin-top: clamp(2rem, 5vh, 3.5rem);
  padding-top: var(--sp-5);
  border-top: var(--axis-w) solid var(--c-inv-rule);
  font-size: var(--fs-lead);
  line-height: 1.55;
  letter-spacing: .04em;
}

.boundary-line { display: block; }
.boundary-line + .boundary-line { color: var(--c-inv-muted); }

/* ---------------------------------------------------------- 证据层
   三个数字必须是三种不同的证明：懂行 / 底座 / 实战。
   数字是全站四处允许出现朱砂的位置之一（且在白底上）。 */
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.metric {
  position: relative;
  padding-right: var(--sp-6);
}

.metric + .metric {
  padding-left: var(--sp-6);
}

/* 点阵竖线分隔 —— 与版块分隔线说同一种语言 */
.metric + .metric::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--dot-rule-pitch);
  background-image: repeating-linear-gradient(
    to bottom,
    var(--c-rule-strong) 0 var(--dot-rule-size),
    transparent var(--dot-rule-size) var(--dot-rule-pitch)
  );
}

.metric-value {
  display: block;
  font-size: var(--fs-metric);
  font-weight: var(--fw-thin);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--c-accent);
}

.metric-unit {
  font-size: var(--fs-h3);
  margin-left: .12em;
  letter-spacing: 0;
}

.metric-note {
  margin-top: var(--sp-4);
  max-width: 24ch;
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--c-muted);
}

@media (max-width: 560px) {
  .metric { padding-right: 0; padding-bottom: var(--sp-6); }
  .metric + .metric { padding-left: 0; padding-top: var(--sp-6); }
  .metric + .metric::before {
    top: 0; bottom: auto;
    width: 100%; height: var(--dot-rule-pitch);
    background-image: repeating-linear-gradient(
      to right,
      var(--c-rule-strong) 0 var(--dot-rule-size),
      transparent var(--dot-rule-size) var(--dot-rule-pitch)
    );
  }
}

/* -------------------------------------------------------------- FAQ
   折叠呈现：视觉上每题只占一行（完全符合克制），展开才有内容；
   而折叠内容在 HTML 里，AI 爬虫照样读得到。
   这是极简与 GEO 唯一可行的和解方式（见方案 6.5）。 */
.faq {
  border-top: var(--axis-w) solid var(--c-rule-strong);
}

.faq-item {
  border-bottom: var(--axis-w) solid var(--c-rule);
}

.faq-item > summary {
  display: flex;
  align-items: baseline;
  gap: clamp(.75rem, 2vw, 1.5rem);
  padding-block: var(--sp-5);
  cursor: pointer;
  list-style: none;
}

.faq-item > summary::-webkit-details-marker { display: none; }

.faq-no {
  font-family: var(--f-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-accent);
  flex-shrink: 0;
}

.faq-q {
  font-size: var(--fs-body);
  letter-spacing: var(--ls-title);
}

.faq-sign {
  margin-left: auto;
  color: var(--c-faint);
  font-size: var(--fs-sm);
}

.faq-item[open] .faq-sign { color: var(--c-ink); }

.faq-a {
  padding-bottom: var(--sp-6);
  max-width: 62ch;
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
  color: var(--c-muted);
}

.faq-a p + p { margin-top: var(--sp-3); }

.faq-a strong { font-weight: var(--fw-light); color: var(--c-ink); }

/* --------------------------------------------------- 合作入口（留资）
   依《AKO 对外服务合规规范 v1.0.3》第五条 5.1：对外获客模式为
   「先留资、后自助」。现官网原先零留资入口，属与既定策略不符。 */
.cta-band {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: var(--sp-6);
  margin-top: clamp(2.5rem, 6vh, 4rem);
  padding-top: var(--sp-6);
  border-top: var(--axis-w) solid var(--c-rule-strong);
}

.cta-lead {
  font-size: var(--fs-lead);
  line-height: 1.55;
  letter-spacing: .03em;
}

.cta-note {
  margin-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--c-muted);
}

.cta-action {
  font-size: var(--fs-sm);
  letter-spacing: .06em;
  border-bottom: var(--axis-w) solid var(--c-accent);
  padding-bottom: 3px;
  color: var(--c-accent);
  white-space: nowrap;
}

.cta-action:hover { border-bottom-color: var(--c-ink); color: var(--c-ink); }

@media (max-width: 640px) {
  .cta-band { grid-template-columns: 1fr; align-items: start; }
}

/* -------------------------------------------------- 页脚法定公示项
   依《AKO 对外服务合规规范 v1.0.3》第六条，官网须在显著位置公示
   经营主体名称、统一社会信用代码、法定代表人、备案号、联系方式、
   服务条款与隐私政策入口。第四条 4.1 规定主体信息「禁止改写」。
   小字号灰阶，不占视觉重心。 */
.footer-legal-id {
  margin-top: var(--sp-2);
  font-size: var(--fs-micro);
  line-height: 1.9;
  color: var(--c-faint);
}
