﻿/* ═══════════════════════════════════════════════════════════════════════════
   OAIRoute API 首页样式
   ───────────────────────────────────────────────────────────────────────────
   设计规则（和根域名站同一家族，布局不同）：
     · 浅底 + 灰阶 + 唯一强调蓝。主按钮近黑，悬停转蓝。
     · 根域名站是居中的"展厅"；这里是左对齐的"入口"，首屏右侧直接给端点。
     · 区块用 1px 细线分隔，不用背景色块。
     · 想换强调色只改 --blue 一个值。
   ═══════════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* 主蓝：比原来的 #2B5CE6 提亮一档，饱和度不拉满，
     避免变成那种很"数码"的荧光蓝。整套蓝只改这几个值。 */
  --blue:      #4A7DFF;
  --blue-lt:   #7BA0FF;
  --blue-mid:  #3A6BF0;
  --blue-wash: rgba(74,125,255,.085);
  --blue-glow: rgba(74,125,255,.35);

  --ink:    #0B1020;
  --ink-2:  #1A2135;
  --gray-1: #5A6478;
  --gray-2: #8B93A7;
  --gray-3: #C3C9D6;
  --line:   #E6E9F0;
  --line-2: #EFF1F6;
  --paper:  #F7F8FA;
  --white:  #FFFFFF;

  /* 深色底上该用的文字色（主按钮、选中筛选、toast）
     单独拆出来，是因为夜间模式下 --ink 变浅，直接写 #fff 会白字白底。 */
  --on-ink:     #FFFFFF;
  --bar:        rgba(255,255,255,.78);
  --bar-solid:  rgba(255,255,255,.94);
  --row-hover:  #FBFCFE;
  --code-bg:    #0E1424;

  --green: #0E9F6E;

  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --maxw: 1160px;
  --maxw-n: 1240px;

  /* 厂商横滚条的总高度：上下 padding 26×2 + 内容 21 + 两条 1px 边框 = 75，
     取 76 留 1px 余量。首屏从"一屏"里减掉它，名条才能和首屏一起看全。 */
  --marquee-h: 76px;

  /* 首屏动效的统一节拍。三个动画都引用它，改一个值就整体变速 */
  --cycle: 2.6s;

  /* ══ 汇聚链时间轴：只定义这四个数，其余全部推导 ══
     这条链上有四段动画（图标环 → 流光 → 下划线 → OAI 字母），
     它们必须共用同一根时间轴，否则改一段就会和别段错开。
     所以下面只允许改这四个量，各段的位置全部用 calc() 算：

       --beat    相邻两对图标之间的间隔
       --lit     一对图标亮着的时间
       --flight  流光从起跳到撞线的时长
       --feed-cycle  整个环的长度

     推导关系：
       第 d 对亮起          = d * --beat
       最内一对熄灭         = 3 * --beat + --lit          → 流光起飞点
       流光撞线             = 3 * --beat + --lit + --flight = --feed-cycle（必须）
       下划线亮起           = 撞线的那一刻，即周期 0%
       OAI 字母起跳         = 撞线后约 0.1s，即周期 3%

     改 --beat 时，--feed-cycle 要跟着改成
       3 * --beat + --lit + --flight，
     否则撞线会跑到周期之外，下划线和字母就会脱节。 */
  --beat: .72s;
  --lit: .37s;                 /* 一对图标从亮起到边框落回灰色 */
  --flight: .87s;              /* 流光起飞 → 撞线 */
  --feed-cycle: 3.4s;          /* = 3 × .72 + .37 + .87 */

  --sh-1: 0 1px 2px rgba(11,16,32,.04);
  --sh-2: 0 4px 16px -6px rgba(11,16,32,.10);
  --sh-3: 0 12px 34px -12px rgba(11,16,32,.16);
  --sh-blue: 0 10px 30px -10px rgba(74,125,255,.42);

  color-scheme: light;
}

/* ══════════════════════════════ 夜间配色 ══════════════════════════════
   只覆盖颜色变量，布局一条都不用改 —— 因为整页的颜色都从上表取值。
   data-theme 由 index.html 头部的内联脚本在首屏前写好，不会闪白。 */
:root[data-theme="dark"] {
  --blue:      #6E93FF;
  --blue-lt:   #8FAEFF;
  --blue-wash: rgba(110,147,255,.11);

  --ink:    #EDF1F8;
  --ink-2:  #D5DCEA;
  --gray-1: #A3AEC4;
  --gray-2: #7C879E;
  --gray-3: #4A5468;
  --line:   #232A38;
  --line-2: #1A2029;
  --paper:  #11151E;
  --white:  #0B0F17;

  --on-ink:    #0B0F17;
  --bar:       rgba(11,15,23,.78);
  --bar-solid: rgba(11,15,23,.94);
  --row-hover: #121826;
  --code-bg:   #070A11;

  --green: #35C894;

  --sh-1: 0 1px 2px rgba(0,0,0,.4);
  --sh-2: 0 4px 16px -6px rgba(0,0,0,.5);
  --sh-3: 0 12px 34px -12px rgba(0,0,0,.6);
  --sh-blue: 0 10px 30px -10px rgba(110,147,255,.3);

  color-scheme: dark;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 32px;
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--white);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15.5px;
  line-height: 1.65;
  letter-spacing: -.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* ══════════════════════════════ 阅读区鼠标交互层 ══════════════════════════════
   一张固定定位的 canvas，铺满视口，只从厂商横滚条以下开始画涟漪。
   不自带任何静态纹理 —— 背景保持纯白，移动鼠标才有内容。
   z-index 3：在内容之上，小窗口里也不会被卡片或表格挡住。
   pointer-events:none：不拦截任何点击。 */
.side-fx {
  position: fixed; inset: 0; z-index: 3;
  width: 100%; height: 100%;
  pointer-events: none;
}
/* 内容层：.sec / .final 不设背景，靠 body 的白底透出 */
main > :not(.side-fx), footer { position: relative; z-index: 1; }

/* ══════════════════════════════ 底部超大品牌字标 ══════════════════════════════
   一页只出现这一次。透明度很低（4.5%），读成"纸上的压印"而不是标题。
   下沿在盒子内部裁掉一点（clip-path），不靠页脚遮挡 ——
   页脚是不透明白底，用负边距压过去会被整块盖住。 */
.wm-giant {
  display: flex; justify-content: center; align-items: flex-start;
  padding: 0 28px;
  margin-top: 24px;
  /* 盒子高度约为字号的 0.62：底部裁掉一截，读成"被版心切掉的落款"。
     高度不能小于这个数，否则字标会被上方的 overflow 裁成一条线。 */
  height: clamp(52px, 9.6vw, 148px);
  overflow: hidden;
  pointer-events: none;
  user-select: none;
  font-size: clamp(84px, 15.5vw, 236px);
  line-height: .78;
  font-weight: 700;
  letter-spacing: -.045em;
  white-space: nowrap;
}
.wm-giant-oai { color: rgba(11,16,32,.045); }
.wm-giant-acc { color: rgba(74,125,255,.075); }
:root[data-theme="dark"] .wm-giant-oai { color: rgba(237,241,248,.05); }
:root[data-theme="dark"] .wm-giant-acc { color: rgba(123,160,255,.09); }

/* 铺满整页后不再需要窄屏隐藏；点阵在小屏上同样成立，
   只是内容列占满宽度，点只出现在卡片之间的缝隙里。 */

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
code, pre { font-family: var(--mono); }

:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; border-radius: 4px; }

.skip-link {
  position: fixed; left: 16px; top: -60px; z-index: 200;
  padding: 10px 16px; background: var(--ink); color: var(--on-ink);
  border-radius: 8px; font-size: 14px; transition: top .2s;
}
.skip-link:focus { top: 16px; }

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

/* ══════════════════════════════ 品牌标识 ══════════════════════════════
   顶部不再有固定栏。这里只剩页脚那一个品牌标识用的样式。
   浅色/深色两版 wordmark 仍然按主题显示其中一张。 */
.brand { flex: none; display: inline-flex; align-items: center; gap: 9px; }
.brand img { display: block; height: 23px; width: auto; }
/* 深色主题下把深色文字反相成浅色。
   用滤镜而不是换文件，单文件预览版也能正常显示（不依赖 /brand/ 目录）。 */
:root[data-theme="dark"] .brand img {
  filter: brightness(0) invert(1) opacity(.92);
}
[data-theme="dark"] .wm-light { display: none; }
[data-theme="light"] .wm-dark { display: none; }

/* ── 中文排版 ──
   英文的负字距是为了让大标题更紧凑，中文字符用同样的负值会挤在一起。
   切到中文时只放宽字距和行高，其他规则不动。 */
:root[data-lang="zh"] body { letter-spacing: 0; }
:root[data-lang="zh"] .hero-wordmark { letter-spacing: -.02em; }
:root[data-lang="zh"] .hero-tagline { letter-spacing: .01em; }
:root[data-lang="zh"] .sec-head h2,
:root[data-lang="zh"] .qs-copy h2,
:root[data-lang="zh"] .final h2 { letter-spacing: -.01em; }
:root[data-lang="zh"] .hero-spec { line-height: 1.75; }
:root[data-lang="zh"] .hero-kicker,
:root[data-lang="zh"] .kicker { letter-spacing: .14em; }

/* ══════════════════════════════ 按钮 ══════════════════════════════ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 12px 22px; border-radius: 10px;
  font-size: 14.6px; font-weight: 600; white-space: nowrap;
  transition: transform .18s cubic-bezier(.2,.8,.2,1), box-shadow .22s, background .18s, border-color .18s;
}
.btn svg { width: 15px; height: 15px; flex: none; }
.btn-p { background: var(--ink); color: var(--on-ink); box-shadow: 0 2px 14px -4px rgba(11,16,32,.34); }
.btn-p:hover { background: var(--blue); transform: translateY(-2px); box-shadow: var(--sh-blue); }
.btn-g { background: var(--white); color: var(--ink); border: 1px solid var(--line); }
.btn-g:hover { border-color: var(--gray-3); transform: translateY(-2px); box-shadow: var(--sh-2); }
.btn-lg { padding: 14px 26px; font-size: 15.2px; border-radius: 11px; }

/* ══════════════════════════════ 首屏 ══════════════════════════════ */
/* 顶部没有固定栏，首屏直接从页面上方开始：
   上方留白只用来给 wordmark 一个呼吸空间，不再是"让开顶栏"的量。

   高度跟着屏幕走：
     · min-height 取"一屏减去厂商名条"，名条正好落在折叠线上方，
       首屏和它一起构成完整的第一眼画面；
     · 内容用 flex 垂直居中 —— 窗口高的时候首屏不会被下一页露出来，
       也不会把内容顶在上半截；
     · 价格条是 .hero 的最后一个子元素，flex 下自然贴住首屏底边；
     · 100svh 是"浏览器工具栏收起来时"的稳定高度，比 100vh 少跳一次；
       老浏览器不认 svh 时回落到上一行的 100vh。
   注意 min-height 只是下限：内容本身比屏幕高时（窄屏）会照常撑开，
   不会为了塞进一屏而压缩字距和图标。 */
.hero {
  padding: 96px 28px 0; position: relative; overflow: hidden;
  min-height: calc(100vh - var(--marquee-h));
  min-height: calc(100svh - var(--marquee-h));
  display: flex; flex-direction: column;
}
/* 背景光晕缓慢漂移：周期长、幅度小，作用是让静态画面"活着"，
   不是让人注意到它在动。 */
.hero::before {
  content: ''; position: absolute; inset: -12% -6%; pointer-events: none;
  background: radial-gradient(46% 54% at 42% 32%, var(--blue-wash), transparent 70%),
              radial-gradient(38% 46% at 62% 44%, var(--blue-wash), transparent 72%);
  animation: wash-drift 26s ease-in-out infinite alternate;
}
@keyframes wash-drift {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1); }
  100% { transform: translate3d(3%, 2%, 0) scale(1.06); }
}

/* 背景点阵层：铺满首屏，在所有内容之下。
   它有反应但必须退后 —— 点很淡，鼠标经过才亮起来。 */
.hero-canvas {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.hero-in { z-index: 1; }
.hero-in {
  max-width: var(--maxw); margin: 0 auto;
  display: flex; flex-direction: column; align-items: center;
  text-align: center; position: relative;
  padding-bottom: 96px;
  /* 撑满 .hero 剩下的高度，让内容在"屏幕高度 − 价格条"这段里垂直居中。
     0 0 auto 保证它不会为了居中而被压扁；min-height:0 让它在矮屏上
     可以正常比可用空间高、由 .hero 自己撑开。 */
  flex: 1 0 auto; justify-content: center; min-height: 0;
}

.hero-kicker {
  font-family: var(--mono); font-size: 11.5px; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--gray-2); margin-bottom: 34px;
}

/* ══════════════════════════════ 名字 ══════════════════════════════
   OAI 和 Route 是两种不同的动法，所以拆成两个 span：
     · OAI   —— 每个字母单独跳，错开出发
     · Route —— 等宽字形 + 横向扫描线，做出点阵/像素的质感
   两者节奏错开：OAI 在跳的时候 Route 完全静止。 */
.hero-wordmark {
  font-size: clamp(54px, 10.6vw, 132px);
  line-height: .95; font-weight: 700;
  letter-spacing: -.052em;
  white-space: nowrap;
  color: var(--ink);
  display: inline-flex; align-items: baseline;
  margin-top: 4px;
}

.wm-oai { display: inline-flex; }
/* OAI 的跳动排在光束之后：
   流光撞上名字下方的下划线（上一个周期的末尾），下划线亮起，
   字母随即弹一下 —— 读起来是"被光点亮"的结果，不是名字自己在动。
   整轮的顺序：图标环由外向内 → 最内一对熄灭 → 流光上冲 →
   击中下划线 → 下划线亮 → 字母回应 → 下一轮。 */
.wm-oai .ch {
  display: inline-block;
  animation: letter-hop var(--feed-cycle) cubic-bezier(.34,1.4,.5,1) infinite;
  animation-delay: calc(var(--i) * .075s);
}
@keyframes letter-hop {
  0%, 3%, 100%   { transform: translateY(0); }
  6%             { transform: translateY(-.07em); }
  11%            { transform: translateY(0); }
  16%            { transform: translateY(-.025em); }
  21%            { transform: translateY(0); }
}
.hero-wordmark:hover .ch { animation-duration: 1.6s; }

/* ── 像素字体：Route 专用 ──
   Jersey 15，SIL OFL 1.1。笔画厚、像素格大、字母骨架标准，
   小写 a/g/o 都是单层的，远看也不会认错。
   字体文件放在本地 assets/fonts，不依赖 Google Fonts。 */
@font-face {
  font-family: 'Jersey 15';
  src: url('fonts/jersey15.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

.wm-route {
  color: var(--blue);
  font-family: 'Jersey 15', var(--mono);
  font-weight: 400;
  /* 字高对齐：Jersey 15 的大写字高只有字号的 56%，而 Inter 700 有 74%。
     两边同样 127px 时，OAI 的 O 高 94px、Route 的 R 只有 71px，
     看起来就是"一个高一个扁"。
     这里把 Route 的字号提到 1.324 倍（R 高 → 94px），两者才真正等大。
     line-height 同比收窄，避免放大后的行盒把下划线和链往下顶。 */
  font-size: 1.324em;
  line-height: .756;
  letter-spacing: .005em;
  position: relative;
  display: inline-block;
}

/* 逐字母外壳：只用来错开滚动相位，本身不加纹理。
   像素字体自带格子，再叠网格会把笔画切碎。 */
.wm-route .px {
  display: inline-block;
  position: relative;
}

/* ── 代码滚动层 ──
   真实的代码符号在字形内部向上流：{} => </> 0x1F :: && …
   做法：
     1. 把字符排成一张 248×90 的 SVG 贴图（6 行 × 15px，正好一个周期）
     2. background-clip: text 裁进笔画 —— 只有笔画里能看见字符
     3. 纵向平移一整张图的高度，首尾图案相同，循环无接缝、无空白帧

   每个字母看到的字符不一样：
   贴图横向铺了 4 列不同符号，每个字母用 --ox 取其中一列，
   所以 R/o/u/t/e 各自跑着不同的字符流，而不是同一张图重复五遍。 */
.wm-route .px::after {
  content: attr(data-ch);
  position: absolute;
  left: 0; top: 0;
  pointer-events: none;
  color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22248%22%20height%3D%2290%22%3E%3Cg%20fill%3D%22white%22%20font-family%3D%22monospace%22%20font-size%3D%2212%22%20font-weight%3D%22700%22%3E%3Ctext%20x%3D%220%22%20y%3D%2213%22%3E%7B%20%7D%3C%2Ftext%3E%3Ctext%20x%3D%2262%22%20y%3D%2213%22%3E%3D%26gt%3B%3C%2Ftext%3E%3Ctext%20x%3D%22124%22%20y%3D%2213%22%3E%26lt%3B%2F%26gt%3B%3C%2Ftext%3E%3Ctext%20x%3D%22186%22%20y%3D%2213%22%3E0x1F%3C%2Ftext%3E%3Ctext%20x%3D%220%22%20y%3D%2228%22%3E%3A%3A%3C%2Ftext%3E%3Ctext%20x%3D%2262%22%20y%3D%2228%22%3E%26amp%3B%26amp%3B%3C%2Ftext%3E%3Ctext%20x%3D%22124%22%20y%3D%2228%22%3E%7C%7C%3C%2Ftext%3E%3Ctext%20x%3D%22186%22%20y%3D%2228%22%3E!%3D%3D%3C%2Ftext%3E%3Ctext%20x%3D%220%22%20y%3D%2243%22%3E01%3C%2Ftext%3E%3Ctext%20x%3D%2262%22%20y%3D%2243%22%3E10%3C%2Ftext%3E%3Ctext%20x%3D%22124%22%20y%3D%2243%22%3E%5B%20%5D%3C%2Ftext%3E%3Ctext%20x%3D%22186%22%20y%3D%2243%22%3E(%20)%3C%2Ftext%3E%3Ctext%20x%3D%220%22%20y%3D%2258%22%3E..%2F%3C%2Ftext%3E%3Ctext%20x%3D%2262%22%20y%3D%2258%22%3E%23id%3C%2Ftext%3E%3Ctext%20x%3D%22124%22%20y%3D%2258%22%3E%24_%3C%2Ftext%3E%3Ctext%20x%3D%22186%22%20y%3D%2258%22%3E*%26amp%3B%3C%2Ftext%3E%3Ctext%20x%3D%220%22%20y%3D%2273%22%3E%26lt%3B%3F%26gt%3B%3C%2Ftext%3E%3Ctext%20x%3D%2262%22%20y%3D%2273%22%3E1.0%3C%2Ftext%3E%3Ctext%20x%3D%22124%22%20y%3D%2273%22%3E%2B%20-%3C%2Ftext%3E%3Ctext%20x%3D%22186%22%20y%3D%2273%22%3E%2F%2F%3C%2Ftext%3E%3Ctext%20x%3D%220%22%20y%3D%2288%22%3E%5E_%3C%2Ftext%3E%3Ctext%20x%3D%2262%22%20y%3D%2288%22%3E%25~%3C%2Ftext%3E%3Ctext%20x%3D%22124%22%20y%3D%2288%22%3E%7C%26gt%3B%3C%2Ftext%3E%3Ctext%20x%3D%22186%22%20y%3D%2288%22%3E.%3D%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  background-repeat: repeat;
  background-size: 248px 90px;
  /* 横向取列：--ox 由每个字母单独给，纵向交给动画 */
  background-position: var(--ox, 0px) 0;
  -webkit-background-clip: text;
  background-clip: text;
  opacity: .8;
  animation: code-scroll 3.4s linear infinite;
  animation-delay: calc(var(--i) * -.31s);
}

/* 只动 Y，X 保持 --ox 指定的那一列 */
@keyframes code-scroll {
  0%   { background-position-y: 0; }
  100% { background-position-y: 90px; }
}

/* 五个字母各取一列。62px 是贴图里两列之间的间距，
   错开它就能让相邻字母看到完全不同的字符。 */
.wm-route .px:nth-child(1) { --ox: 0px; }
.wm-route .px:nth-child(2) { --ox: -62px; }
.wm-route .px:nth-child(3) { --ox: -124px; }
.wm-route .px:nth-child(4) { --ox: -186px; }
.wm-route .px:nth-child(5) { --ox: -93px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ══════════════════════════════ 名字下划线 ══════════════════════════════
   名字正下方一条浅灰细线。它不是装饰，是整条汇聚链的落点：
   八格依次亮起 → 两道流光在中间相撞 → 一束光上行打到这条线上 →
   线亮一下 → OAI 三个字母接着弹一下。
   宽度跟着名字走，但收窄一点，免得和名字等宽显得死板。 */
.name-rule {
  display: block;
  width: min(88%, 468px);
  height: 1px;
  margin: 14px auto 0;
  background: linear-gradient(90deg,
    transparent 0%,
    var(--gray-3) 14%,
    var(--gray-3) 86%,
    transparent 100%);
  position: relative;
  z-index: 2;
}
/* 被光打中的那一刻：整条线亮成蓝色，并向两侧推开一点点。
   流光在周期末尾 3.42s 撞上来，紧接着就以 0–16% 这一段回应。 */
.name-rule::after {
  content: '';
  position: absolute; inset: -1px 0;
  background: linear-gradient(90deg,
    transparent 0%,
    var(--blue-lt) 18%,
    var(--blue) 50%,
    var(--blue-lt) 82%,
    transparent 100%);
  opacity: 0;
  transform: scaleX(.72);
  animation: rule-lit var(--feed-cycle) ease-out infinite;
}
@keyframes rule-lit {
  0%       { opacity: 0;   transform: scaleX(.72); box-shadow: none; }
  2%       { opacity: 1;   transform: scaleX(1);   box-shadow: 0 0 12px rgba(74,125,255,.55); }
  9%       { opacity: .5;  transform: scaleX(1);   box-shadow: 0 0 8px rgba(74,125,255,.3); }
  16%, 100%{ opacity: 0;   transform: scaleX(1);   box-shadow: none; }
}

/* ══════════════════════════════ 汇聚结构 ══════════════════════════════
   只剩一条横向串联链 —— 竖线和中心的虚线都去掉了，
   光靠一条线把 8 个方块串成一组，落点交给上面的下划线。

   ── 节拍 ──
   一个周期 --feed-cycle = 3.4s。图标环只用前 2.5s，
   剩下的 0.9s 是留给"最内一对熄灭 → 最外一对再亮"的停顿。

     0.05s  左一 + 右四 亮起
     0.31s  熄灭              间隔 0.46s
     0.77s  第二对 亮起
     1.03s  熄灭              间隔 0.46s
     1.49s  第三对 亮起
     1.75s  熄灭              间隔 0.46s
     2.21s  左四 + 右一 亮起
     2.47s  熄灭  ← 跑到这里为止
     3.40s  下一轮开始        停顿 0.93s

   每对之间统一留 0.46s 的空白，收尾多留 0.46s。
   所以读起来是"由外向内点一圈 → 停一拍 → 再点一圈"，
   而不是无缝首尾相接。

   另一条循环（流光 / 下划线 / OAI 跳动）仍挂在周期起点：
     0.07–0.95s  两道流光从内侧出发、汇聚、升到名字下划线
     1.05s       下划线亮起
     1.19s       OAI 三个字母依次弹一下
   它和图标环不是同一条时间线 —— 图标环管"两侧"，
   光束管"中心"，两者各走各的，只在周期起点对齐。

   间距：四格一组贴紧，中间留 68px 缺口给流光跑完。
   光点用绝对定位挂在链的坐标上，不依赖方块宽度。 */
.feed {
  position: relative;
  margin-top: 20px;
}

/* ── 中间的两条引线 ──
   左四向右引出 15px，右一向左引出 15px，各自收在一个端点上。
   **两个端点之间是空的** —— 那段空白就是流星汇聚的跑道。 */
.feed-chain {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  gap: 0;                   /* 间距全交给方块自己的 margin，线才对得齐 */
  padding: 0 4px;
}
.feed-chain::before {
  content: '';
  position: absolute; top: 50%;
  left: calc(50% - 34px); width: 15px;
  height: 1px; transform: translateY(-50%);
  background: rgba(139,147,167,.55);
}
.feed-chain::after {
  content: '';
  position: absolute; top: 50%;
  right: calc(50% - 34px); width: 15px;
  height: 1px; transform: translateY(-50%);
  background: rgba(139,147,167,.55);
}

/* ── 引线末端的点 ──
   贴在 15px 引线的内端，告诉人"这段线到这里为止"。
   两个点之间没有线，只有空气。 */
.feed-cap {
  position: absolute; top: 50%; z-index: 2;
  width: 5px; height: 5px; margin-top: -2.5px;
  border-radius: 50%;
  background: var(--gray-2);
}
.feed-cap.l { left: calc(50% - 21.5px); }
.feed-cap.r { right: calc(50% - 21.5px); }

/* ── 方块 ──
   每个方块带 --d（0–3，代表第几对），同一对的两个同时亮、同时灭。
   margin 很小：四格一组要贴紧，成组的关系才清楚。 */
.feed-node {
  --icon-off: grayscale(1);
  --icon-on:  grayscale(0);
  position: relative; z-index: 1;
  width: 32px; height: 32px; margin: 0 8px;
  display: grid; place-items: center;
  border: 1px solid var(--gray-3);
  border-radius: 7px;
  background: var(--white);
  animation: node-light var(--feed-cycle) ease-out infinite;
  animation-delay: calc(var(--d) * var(--beat));
}
/* 亮 → 保持 → 熄灭，中间留空档，下一对才开始。
   下面的百分比由 app.js 的 syncFeedTimeline() 按 --beat / --lit / --flight
   重新算一遍并覆盖，所以这里写的是一组"没跑 JS 时的兜底值"。
   改时间轴只需要改 :root 里那四个变量，不用动这些数字。 */
@keyframes node-light {
  0%    { border-color: var(--gray-3); box-shadow: none; transform: translateY(0); }
  1.5%  { border-color: var(--blue);   box-shadow: 0 0 0 3px var(--blue-wash), 0 4px 14px -4px rgba(74,125,255,.55); transform: translateY(-2px); }
  9%    { border-color: var(--blue);   box-shadow: 0 0 0 3px var(--blue-wash), 0 4px 14px -4px rgba(74,125,255,.55); transform: translateY(-2px); }
  11%   { border-color: var(--gray-3); box-shadow: none; transform: translateY(0); }
  100%  { border-color: var(--gray-3); box-shadow: none; transform: translateY(0); }
}

/* 最内一对不再单开一套关键帧。
   旧写法让它在 2%–88% 全程保持点亮（约 2.9s / 3.4s），
   加上 --d:3 的延迟后跨越周期边界首尾相接，看起来就是"一直亮着"。
   现在四对用同一套节拍：各自亮 7.5%，熄灭后留白，下一对再起。
   最内一对在 3.05s 亮、3.31s 灭，距周期终点还有 0.09s，
   不会拖进下一轮去撞最外一对。 */

.feed-node img {
  width: 17px; height: 17px;
  filter: grayscale(1);
  opacity: .6;
  animation: node-icon var(--feed-cycle) ease-out infinite;
  animation-delay: calc(var(--d) * var(--beat));
}
@keyframes node-icon {
  0%, 12%  { filter: var(--icon-off); opacity: .6; }
  1.5%, 9% { filter: var(--icon-on);  opacity: 1; }
  100%     { filter: var(--icon-off); opacity: .6; }
}
/* 最内一对的图标跟着方块走，用的是同一套关键帧 */
/* 夜间：图标原色多为深色，必须反相才看得见 */
:root[data-theme="dark"] .feed-node {
  --icon-off: grayscale(1) invert(1);
  --icon-on:  grayscale(0) invert(0);
}
:root[data-theme="dark"] .feed-node img { opacity: .5; }

.feed-node .fb {
  font-size: 10px; font-weight: 700; color: var(--gray-2);
}

/* ── 悬停 ──
   ⚠️ 方块和图标都挂着常驻 animation，而 animation 的优先级高于普通声明，
   所以 :hover 里写的 border-color / transform / filter 全都会被动画压掉 ——
   这正是"有的图标碰了会亮、有的不会"的根因：
   只有 --icon-on 那组变量还能生效，而它取决于图标本身有没有原色可恢复。
   用 !important 把悬停态提到动画之上，才能稳定生效。 */
.feed-node:hover {
  border-color: var(--blue-lt) !important;
  box-shadow: 0 4px 12px -4px rgba(74,125,255,.45) !important;
  transform: translateY(-3px) !important;
}
.feed-node:hover img {
  filter: grayscale(0) !important;
  opacity: 1 !important;
}
:root[data-theme="dark"] .feed-node:hover img {
  filter: grayscale(0) invert(0) !important;
}

/* ── 中心缺口 ──
   只负责撑开宽度，本身不可见 —— 虚线和中心圆点都已删除。
   宽度 68px 和两道流光的行程对应。 */
.feed-hub {
  flex: 0 0 68px; width: 68px; height: 0;
  position: relative; z-index: 2;
}

/* ── 流星 ──
   只和图标有一处关联：左四 / 右一熄灭的那一刻发射。
   发射之后它自己走完，不等图标，也不暂停图标。

   路线是一个反 T：
     ① 从两个端点出发，**水平**向中间靠拢
     ② 在正中合成一个点
     ③ **垂直**上升，撞上下划线后消失（不穿过）

   行程：横向 19px（端点 → 中心），纵向 46px（中心 → 下划线）。 */
.feed-chain .meteor {
  position: absolute; top: 50%; z-index: 3;
  width: 7px; height: 7px; margin-top: -3.5px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 0 2px rgba(74,125,255,.18), 0 0 12px rgba(74,125,255,.85);
  opacity: 0;
}
.feed-chain .meteor.l {
  left: calc(50% - 22.5px);
  animation: meteor-l var(--feed-cycle) cubic-bezier(.45,0,.55,1) infinite;
  /* 起飞时刻 = 最内一对熄灭 = 3 × beat + lit，不手填数字 */
  animation-delay: calc(3 * var(--beat) + var(--lit));
}
.feed-chain .meteor.r {
  left: calc(50% + 15.5px);
  animation: meteor-r var(--feed-cycle) cubic-bezier(.45,0,.55,1) infinite;
  animation-delay: calc(3 * var(--beat) + var(--lit));
}

/* 关键帧里的百分比 = 实际时刻 / --feed-cycle。
   当前 flight .87s / cycle 3.4s ≈ 25.6%，所以用 26% 收尾：
     0–2%    出现
     2–13%   水平向中心汇聚 19px
     13–26%  垂直上升 37px，正好落在线条上
     26–100% 不可见，等下一轮
   纵向行程 37px 是从"链中心 → 下划线"实测出来的，不是估的：
   改布局（间距、下划线 margin）之后这个数要重新量，否则光点会
   飞过线或者够不到线 —— 这正是之前"打得不同步"的根源之一。 */
@keyframes meteor-l {
  0%   { opacity: 0;    transform: translate(0, 0)        scale(.5); }
  2%   { opacity: 1;    transform: translate(0, 0)        scale(1); }
  13%  { opacity: 1;    transform: translate(19px, 0)     scale(1); }
  26%  { opacity: 0;    transform: translate(19px, -37px) scale(.4); }
  100% { opacity: 0;    transform: translate(19px, -37px) scale(.4); }
}
@keyframes meteor-r {
  0%   { opacity: 0;    transform: translate(0, 0)         scale(.5); }
  2%   { opacity: 1;    transform: translate(0, 0)         scale(1); }
  13%  { opacity: 1;    transform: translate(-19px, 0)     scale(1); }
  26%  { opacity: 0;    transform: translate(-19px, -37px) scale(.4); }
  100% { opacity: 0;    transform: translate(-19px, -37px) scale(.4); }
}

/* 上行光已经并入 .meteor：流星斜飞到下划线就是"上行"这个动作，
   不再单独由一条竖直光承担，所以这里已经没有 .spark-up。 */

.hero-spec {
  margin-top: 22px; max-width: 64ch;
  font-size: 15px; line-height: 1.6; color: var(--gray-1);
}
.hero-spec b { color: var(--ink); font-weight: 600; }

/* 矮屏（笔记本 720/768 高、横屏平板）：首屏内容本身约 720px，
   不压缩一点就会把价格条挤出屏幕。这里只收紧首屏内部的垂直间距，
   字号、图标尺寸、链条结构都不动。 */
@media (max-height: 820px) and (min-width: 641px) {
  .hero { padding-top: 64px; }
  .hero-kicker { margin-bottom: 24px; }
  .hero-wordmark { margin-top: 0; }
  .hero-spec { margin-top: 18px; }
  .hero .btns { margin-top: 22px; }
  .endpoint-pill { margin-top: 18px; }
  .hero-in { padding-bottom: 64px; }
}

.hero .btns { margin-top: 30px; }

.endpoint-pill {
  margin-top: 26px;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 8px 12px 8px 14px; border-radius: 999px;
  background: var(--white); border: 1px solid var(--line);
  box-shadow: var(--sh-1);
  transition: border-color .18s, box-shadow .2s, transform .18s;
}
.endpoint-pill:hover {
  border-color: var(--gray-3); box-shadow: var(--sh-2); transform: translateY(-1px);
}
.endpoint-pill .lbl {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gray-2);
}
.endpoint-pill code {
  font-size: 13px; color: var(--ink); letter-spacing: -.01em;
}
.endpoint-pill svg { width: 14px; height: 14px; color: var(--gray-2); flex: none; transition: color .18s; }
.endpoint-pill:hover svg, .endpoint-pill.done svg { color: var(--blue); }

/* 入场：一条序列，不散落 */
.route-line.l, .route-line.r { animation: line-in .9s cubic-bezier(.22,.8,.22,1) both; transform-origin: center; }
.hero-tagline, .hero-spec, .hero .btns, .endpoint-pill { animation: rise-in .7s cubic-bezier(.22,.8,.22,1) both; }
.hero-tagline { animation-delay: .22s; }
.hero-spec { animation-delay: .28s; }
.hero .btns { animation-delay: .34s; }
.endpoint-pill { animation-delay: .4s; }
@keyframes line-in { from { opacity: 0; transform: scaleX(.4); } to { opacity: 1; transform: scaleX(1); } }
@keyframes rise-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/* 关闭动效时：流星不跑（保留导轨和节点）、边缘光不亮、
   背景不漂、绿点不脉冲 */
@media (prefers-reduced-motion: reduce) {
  .route-line::before { animation: none; opacity: 0; }
  .route-line.l::after, .route-line.r::after { animation: none; }
  .wm-edge { animation: none; opacity: 0; }
  .hero::before { animation: none; }
  .pr-tag::before { animation: none; }
  /* 汇聚链：停在点亮后的样子，不再循环 */
  .feed-node, .feed-node img, .feed-chain .meteor,
  .name-rule::after, .wm-oai .ch { animation: none; }
  .feed-node { border-color: var(--gray-3); }
  .feed-node img { filter: var(--icon-on); opacity: .9; }
  .feed-chain .meteor { opacity: 0; }
  .name-rule::after { opacity: 0; }
}

/* ── 端点卡片：全页唯一的深色元素，视线落点 ── */
.endpoint-card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 22px;
  box-shadow: var(--sh-3);
  position: relative;
}
.endpoint-card::before {
  content: ''; position: absolute; inset: -1px; border-radius: 19px;
  pointer-events: none;
  background: linear-gradient(160deg, rgba(74,125,255,.16), transparent 42%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  padding: 1px;
}
.ep-head {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 10px;
}
.ep-lbl {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase; color: var(--gray-2);
}
.ep-live {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 500; color: var(--green);
}
.ep-live i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--green);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

.ep-url {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 13px 15px; border-radius: 10px;
  background: var(--paper); border: 1px solid var(--line);
  transition: border-color .18s, background .18s;
}
.ep-url:hover { border-color: var(--gray-3); background: var(--white); }
.ep-url code { font-size: 13.5px; letter-spacing: -.01em; color: var(--ink); }
.ep-url em { font-style: normal; color: var(--blue); font-weight: 500; }
.ep-url svg { width: 15px; height: 15px; color: var(--gray-2); flex: none; transition: color .18s; }
.ep-url:hover svg { color: var(--blue); }
.ep-url.done svg { color: var(--green); }

.ep-req {
  /* 同 .code：这是"深色终端"样式，不能跟随 --ink 翻转 */
  margin-top: 12px; background: var(--code-bg);
  border-radius: 10px; overflow: hidden;
}
.ep-req-head {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 14px; border-bottom: 1px solid rgba(255,255,255,.08);
  font-family: var(--mono); font-size: 11.5px;
}
.ep-req-head .verb { color: #7EA2FF; font-weight: 600; letter-spacing: .04em; }
.ep-req-head .path { color: rgba(255,255,255,.62); }
.ep-req pre {
  padding: 14px 15px 16px; font-size: 12px; line-height: 1.75;
  color: rgba(255,255,255,.86); overflow-x: auto;
}
.ep-req .t-k { color: #9DB7FF; }
.ep-req .t-s { color: #8FE3BC; }
.ep-req .t-n { color: #F2C77E; }
.ep-req .t-p { color: rgba(255,255,255,.42); }
.ep-foot { margin-top: 12px; font-size: 12.5px; color: var(--gray-2); }

/* ── 首屏下方的价格条 ──
   口径必须一眼可见：这是"最低渠道价"，不是单一报价 */
.price-ribbon {
  margin: 0 -28px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--paper);
}
.price-ribbon-in {
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
  padding-top: 15px; padding-bottom: 15px;
}
.pr-tag {
  flex: none;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--ink);
}
.pr-tag::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--green);
  animation: live-dot 2.6s ease-in-out infinite;
}
@keyframes live-dot {
  0%, 100% { box-shadow: 0 0 0 0 rgba(14,159,110,.28); }
  55%      { box-shadow: 0 0 0 5px rgba(14,159,110,0); }
}
.pr-items { display: flex; align-items: baseline; gap: 26px; flex-wrap: wrap; flex: 1; }
.pr-item { display: inline-flex; align-items: baseline; gap: 7px; }
.pr-item .k { font-size: 12.5px; color: var(--gray-1); }
.pr-item .v {
  font-family: var(--mono); font-size: 14px; font-weight: 600;
  color: var(--ink); letter-spacing: -.01em;
}
.pr-item .u { font-size: 11.5px; color: var(--gray-2); }

/* 厂商倍率条：厂商名加粗当主角，倍率用等宽字跟在后面，不挂后缀 */
.pr-ratio .k {
  font-size: 13px; font-weight: 600; color: var(--ink);
  letter-spacing: -.01em;
}
.pr-ratio .v { font-size: 13px; font-weight: 500; color: var(--blue); }
.pr-link {
  font-size: 12.5px; font-weight: 550; color: var(--blue);
  flex: none; transition: opacity .16s;
}
.pr-link:hover { opacity: .75; }

/* ══════════════════════════════ 厂商横滚 ══════════════════════════════
   两组相同内容首尾相接，整体平移 50% 就能无缝循环。
   遮罩用渐变让两端淡出，避免出现硬切边。 */
.marquee {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 26px 0;
  overflow: hidden;
}
.marquee-mask {
  overflow: hidden;
  /* 两端用较宽的柔化带：内容淡入淡出，而不是在边缘被一刀切掉 */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.marquee-track {
  display: flex; width: max-content;
  animation: marquee 46s linear infinite;
  will-change: transform;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.mq-group {
  display: flex; align-items: center; gap: 44px; padding-right: 44px;
  flex: none;
}
.mq-item {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 14px; font-weight: 500; color: var(--gray-2);
  white-space: nowrap;
  transform-origin: center;
  transition: color .22s;
}
.mq-item img {
  width: 21px; height: 21px;
  /* 默认压暗成灰色：整条是背景信息，不和首屏抢注意力 */
  filter: grayscale(1) brightness(.92);
  opacity: .42;
  transition: filter .3s ease, opacity .3s ease, transform .3s cubic-bezier(.3,.8,.3,1);
}
.mq-item .fb {
  width: 21px; height: 21px; display: grid; place-items: center;
  font-size: 11px; font-weight: 700; color: var(--gray-2);
  border: 1px solid var(--line); border-radius: 5px;
  opacity: .55; transition: opacity .3s;
}

/* ① 鼠标进入整条区域：所有厂商一起亮起来 */
.marquee:hover .mq-item { color: var(--gray-1); }
.marquee:hover .mq-item img { filter: grayscale(0) brightness(1); opacity: 1; }
.marquee:hover .mq-item .fb { opacity: 1; }

/* ② 鼠标压到某个厂商上：它再额外亮一档并放大 */
.mq-item:hover { color: var(--ink); }
.mq-item:hover img { transform: scale(1.14); }
/* ③ 以及那一下抖动 */
.mq-item:hover { animation: mq-wiggle .46s cubic-bezier(.3,.7,.3,1); }
@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@keyframes mq-wiggle {
  0%   { transform: translate(0, 0) rotate(0); }
  22%  { transform: translate(-2px, -3px) rotate(-2.2deg); }
  46%  { transform: translate(2px, 2px) rotate(1.8deg); }
  68%  { transform: translate(-1px, -1px) rotate(-.8deg); }
  100% { transform: translate(0, 0) rotate(0); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .mq-item:hover { animation: none; }
}

/* ══════════════════════════════ 区块骨架 ══════════════════════════════ */
.sec { padding: 92px 0; }
.sec + .sec { border-top: 1px solid var(--line); }
.sec-head { max-width: 660px; margin-bottom: 40px; }
.kicker {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--blue); margin-bottom: 14px;
}
.kicker::before {
  content: ''; width: 18px; height: 1.5px; flex: none;
  background: linear-gradient(90deg, var(--blue), transparent);
}
.sec-head h2, .qs-copy h2, .final h2 {
  font-size: clamp(26px, 3.3vw, 36px);
  line-height: 1.14; font-weight: 700; letter-spacing: -.032em;
  margin-bottom: 13px;
}
.sec-head p, .qs-copy p { font-size: 16px; color: var(--gray-1); line-height: 1.66; max-width: 60ch; }

/* ══════════════════════════════ 模型表 ══════════════════════════════ */
.filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.filter {
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--white);
  font-size: 13px; font-weight: 500; color: var(--gray-1);
  transition: border-color .16s, color .16s, background .16s;
}
.filter:hover { border-color: var(--gray-3); color: var(--ink); }
.filter.on { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }

.mtable { border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.mtable-head, .mrow {
  display: grid;
  grid-template-columns: minmax(0, 2.2fr) 84px 96px 96px minmax(0, 1.5fr);
  align-items: center; gap: 16px;
  padding: 0 20px;
}
.mtable-head {
  height: 40px; background: var(--paper);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gray-2);
}
.mrow {
  min-height: 66px; padding-top: 13px; padding-bottom: 13px;
  /* 行必须是实底：页面背景铺了整页点阵，不填底色的话点会透过表格行 */
  background: var(--white);
  border-bottom: 1px solid var(--line-2);
  transition: background .16s;
}
.mrow:last-child { border-bottom: 0; }
.mrow:hover { background: var(--row-hover); }

.m-name { display: flex; align-items: center; gap: 12px; min-width: 0; }
.m-logo {
  width: 32px; height: 32px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 9px; background: var(--white);
}
.m-logo img { width: 17px; height: 17px; }
.m-logo .fb { font-size: 12px; font-weight: 700; color: var(--gray-2); }
.m-txt { min-width: 0; }
.m-title { display: flex; align-items: center; gap: 8px; }
.m-title b {
  font-size: 14.6px; font-weight: 600; letter-spacing: -.012em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.badge {
  font-family: var(--mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .04em; padding: 2px 6px; border-radius: 4px;
  background: var(--blue-wash); color: var(--blue); flex: none;
}
.badge.g { background: rgba(14,159,110,.09); color: var(--green); }
.m-prov { display: block; font-size: 12.5px; color: var(--gray-2); margin-top: 1px; }

.m-num { font-family: var(--mono); font-size: 13px; color: var(--ink-2); white-space: nowrap; }
.m-num.dim { color: var(--gray-2); }

.m-id {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  padding: 5px 9px; border-radius: 6px;
  background: var(--paper); border: 1px solid var(--line);
  font-family: var(--mono); font-size: 11.5px; color: var(--gray-1);
  transition: border-color .16s, color .16s, background .16s;
}
.m-id span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m-id svg { width: 11px; height: 11px; flex: none; opacity: .55; }
.m-id:hover { border-color: var(--gray-3); color: var(--ink); background: var(--white); }
.m-id.done { border-color: rgba(14,159,110,.35); color: var(--green); }

.mtable-note { margin-top: 16px; font-size: 12.5px; color: var(--gray-2); }
.mtable-warn {
  margin-top: 10px; font-size: 12.5px; color: var(--gray-1);
  padding: 10px 14px; border-radius: 8px;
  background: var(--blue-wash); border: 1px solid rgba(74,125,255,.14);
  display: flex; gap: 9px; align-items: flex-start;
}
.mtable-warn::before {
  content: 'i'; flex: none;
  width: 15px; height: 15px; margin-top: 1px;
  display: grid; place-items: center;
  border-radius: 50%; background: var(--blue); color: #fff;
  font-size: 10px; font-weight: 700; font-style: italic;
}
.mtable-empty { padding: 36px 20px; text-align: center; color: var(--gray-2); font-size: 14px; }

/* 最低价列：价格后的 FROM 标记 */
.m-num .from {
  font-family: var(--sans); font-size: 10.5px; font-weight: 500;
  color: var(--gray-2); margin-right: 4px; letter-spacing: .01em;
}

/* ══════════════════════════════ 前沿模型最低价 ══════════════════════════════ */
.fr-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
.fr-card {
  border: 1px solid var(--line); border-radius: 14px;
  padding: 24px 24px 22px; background: var(--white);
  display: flex; flex-direction: column;
  transition: border-color .18s, box-shadow .22s, transform .18s;
}
.fr-card:hover { border-color: var(--gray-3); box-shadow: var(--sh-2); transform: translateY(-2px); }
.fr-top { display: flex; align-items: center; gap: 11px; margin-bottom: 18px; }
.fr-logo {
  width: 34px; height: 34px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 9px;
}
.fr-logo img { width: 18px; height: 18px; }
.fr-logo .fb { font-size: 12px; font-weight: 700; color: var(--gray-2); }
.fr-meta { min-width: 0; }
.fr-name {
  display: block; font-size: 15px; font-weight: 600; letter-spacing: -.015em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fr-prov { display: block; font-size: 12.3px; color: var(--gray-2); }
.fr-price {
  display: flex; align-items: baseline; gap: 6px;
  padding: 16px 0; border-top: 1px solid var(--line-2); border-bottom: 1px solid var(--line-2);
  margin-bottom: 14px;
}
.fr-price .from { font-size: 12px; color: var(--gray-2); }
.fr-price b {
  font-family: var(--mono); font-size: 26px; font-weight: 600;
  letter-spacing: -.03em; line-height: 1;
}
.fr-price .per { font-size: 12px; color: var(--gray-2); }
.fr-list { font-size: 13px; color: var(--gray-1); line-height: 1.55; }
.fr-list .row { display: flex; justify-content: space-between; gap: 12px; padding: 3px 0; }
.fr-list .row span:last-child { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.fr-save {
  margin-top: 14px; font-size: 12px; font-weight: 550; color: var(--green);
  display: inline-flex; align-items: center; gap: 6px;
}
.fr-note {
  margin-top: 22px; font-size: 13px; color: var(--gray-1);
  padding-left: 14px; border-left: 2px solid var(--line);
}

/* ══════════════════════════════ 优势 ══════════════════════════════ */
.adv-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line); border-left: 1px solid var(--line);
}
.adv {
  padding: 32px 30px 34px;
  border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.adv-ico {
  width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: 9px; background: var(--blue-wash); color: var(--blue);
  margin-bottom: 16px;
}
.adv-ico svg { width: 17px; height: 17px; }
.adv h3 { font-size: 16.5px; font-weight: 650; letter-spacing: -.018em; margin-bottom: 7px; }
.adv p { font-size: 14.4px; line-height: 1.62; color: var(--gray-1); max-width: 42ch; }

/* ══════════════════════════════ 快速开始 ══════════════════════════════ */
.qs {
  display: grid; grid-template-columns: .85fr 1.15fr;
  gap: 56px; align-items: start;
}
.qs-copy p { margin-bottom: 26px; }

.tabs { display: flex; gap: 4px; margin-bottom: 10px; }
.tab {
  padding: 7px 13px; border-radius: 7px;
  font-size: 12.8px; font-weight: 500; color: var(--gray-1);
  transition: background .16s, color .16s;
}
.tab:hover { color: var(--ink); }
.tab.on { background: var(--line-2); color: var(--ink); }

.code {
  /* 不能写 var(--ink)：夜间模式下 --ink 是浅色，整个代码框会被翻白 */
  background: var(--code-bg); border-radius: 12px; overflow: hidden;
  box-shadow: var(--sh-2);
}
.code-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,.08);
}
.dots { display: flex; gap: 5px; }
.dots i { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,.14); }
.code-file { font-family: var(--mono); font-size: 11px; color: rgba(255,255,255,.5); flex: 1; }
.cp {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: rgba(255,255,255,.5);
  padding: 4px 8px; border-radius: 5px;
  transition: color .16s, background .16s;
}
.cp:hover { color: #fff; background: rgba(255,255,255,.08); }
.cp svg { width: 12px; height: 12px; }
.cp.done { color: #8FE3BC; }
.code-body { padding: 16px 16px 20px; overflow-x: auto; }
.code-body pre { font-size: 12.4px; line-height: 1.8; color: rgba(255,255,255,.87); }
.code .t-k { color: #9DB7FF; }
.code .t-s { color: #8FE3BC; }
.code .t-n { color: #F2C77E; }
.code .t-p { color: rgba(255,255,255,.42); }
.code .t-c { color: rgba(255,255,255,.38); font-style: italic; }
.code .t-f { color: #C7B4FF; }
.code .t-v { color: #F2C77E; }
.tab-panel { display: none; }
.tab-panel.on { display: block; }

/* ══════════════════════════════ 底部 CTA ══════════════════════════════ */
.final { border-top: 1px solid var(--line); padding: 88px 0; }
.final-in { max-width: 640px; }
.final h2 { margin-bottom: 12px; }
.final p { font-size: 16px; color: var(--gray-1); margin-bottom: 26px; }

/* 传播用：两条可复制的地址 */
.share-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 30px; }
.share-chip {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 8px 13px; border-radius: 8px;
  background: var(--white); border: 1px solid var(--line);
  transition: border-color .16s, box-shadow .18s;
}
.share-chip:hover { border-color: var(--gray-3); box-shadow: var(--sh-1); }
.share-chip .k {
  font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gray-2);
}
.share-chip code { font-size: 12.5px; color: var(--ink-2); }
.share-chip.done { border-color: rgba(14,159,110,.35); }

/* ── 联系方式：一块独立的面板，带图标徽章和两个大按钮 ── */
.contact {
  margin-top: 40px;
  padding: 26px 28px 28px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--paper);
}
.ct-head { margin-bottom: 20px; }
.contact h3 {
  display: flex; align-items: center; gap: 9px;
  font-size: 18px; font-weight: 650; color: var(--ink); margin-bottom: 6px;
}
.contact h3::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--blue); box-shadow: 0 0 0 4px rgba(74,125,255,.14);
}
.contact p { font-size: 14.5px; color: var(--gray-1); line-height: 1.55; max-width: 52ch; margin: 0; }

.contact-pills { display: flex; gap: 12px; flex-wrap: wrap; }
.contact-pill {
  display: inline-flex; align-items: center; gap: 11px;
  padding: 14px 20px 14px 16px; border-radius: 12px;
  background: var(--white); border: 1px solid var(--line);
  color: var(--ink); font-size: 14.5px; min-width: 240px;
  transition: border-color .16s, box-shadow .18s, transform .16s;
}
.contact-pill:hover {
  border-color: rgba(74,125,255,.42);
  box-shadow: 0 6px 22px -12px rgba(74,125,255,.5);
  transform: translateY(-1px);
}
/* 左侧图标：实心色块，比裸图标更容易被眼睛抓到 */
.contact-pill .cp-ico {
  width: 34px; height: 34px; flex: none; border-radius: 9px;
  display: grid; place-items: center;
  background: rgba(74,125,255,.1); color: var(--blue);
}
.contact-pill .cp-ico svg { width: 17px; height: 17px; }
/* 名称在上、地址在下：地址用等宽字体，看起来像"可以复制的原文" */
.contact-pill .cp-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.contact-pill .cp-lbl { font-size: 14px; font-weight: 550; color: var(--ink); letter-spacing: -.005em; }
.contact-pill .cp-val {
  font-family: var(--mono); font-size: 12px; line-height: 1.3;
  color: var(--gray-2); letter-spacing: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.contact-pill .cp-go { margin-left: auto; color: var(--gray-3); transition: color .16s, transform .16s; }
.contact-pill .cp-go svg { width: 15px; height: 15px; }
.contact-pill:hover .cp-go { color: var(--blue); transform: translateX(2px); }

@media (max-width: 640px) {
  .contact { padding: 22px 18px; }
  .contact-pill { min-width: 0; width: 100%; }
}

/* ══════════════════════════════ 页脚 ══════════════════════════════ */
footer { border-top: 1px solid var(--line); background: var(--paper); }
.foot-in {
  display: flex; justify-content: space-between; gap: 48px;
  padding-top: 44px; padding-bottom: 34px; flex-wrap: wrap;
}
.foot-brand p { font-size: 13px; color: var(--gray-2); margin-top: 12px; max-width: 34ch; }
.foot-cols { display: flex; gap: 64px; flex-wrap: wrap; }
.foot-col h4 {
  font-size: 11.5px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--gray-2); margin-bottom: 12px;
}
.foot-col a {
  display: block; font-size: 13.8px; color: var(--gray-1);
  margin-bottom: 8px; transition: color .16s;
}
.foot-col a:hover { color: var(--ink); }
.foot-bot {
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  padding-top: 18px; padding-bottom: 26px;
  border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--gray-2);
}

/* ══════════════════════════════ Toast ══════════════════════════════ */
.toast {
  position: fixed; bottom: 26px; left: 50%; z-index: 300;
  transform: translate(-50%, 20px);
  display: flex; align-items: center; gap: 9px;
  padding: 11px 18px; border-radius: 10px;
  background: var(--ink); color: var(--on-ink);
  font-size: 13.5px; box-shadow: var(--sh-3);
  opacity: 0; pointer-events: none;
  transition: opacity .22s, transform .22s;
}
.toast svg { width: 15px; height: 15px; color: #8FE3BC; }
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ══════════════════════════════ 响应式 ══════════════════════════════ */
@media (max-width: 960px) {
  .hero { padding: 64px 24px 72px; }
  .hero-in { grid-template-columns: 1fr; gap: 44px; }
  .hero h1 { max-width: 18ch; }
  .endpoint-card { max-width: 540px; }
  /* 单列布局下，grid 子项默认按内容撑宽 —— 代码块里最长那行
     （cURL 的 -d '...'）会把整列顶到 510px，手机屏横向溢出。
     min-width:0 让子项可以窄于内容宽度，超出的部分交给 .code-body 内部横向滚动。 */
  .qs { grid-template-columns: 1fr; gap: 36px; }
  .qs > * { min-width: 0; }
  .adv-grid { grid-template-columns: 1fr; }
  .fr-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .fr-card { padding: 20px 18px 18px; }
  .fr-name { font-size: 14px; }
  .mtable-head, .mrow { grid-template-columns: minmax(0, 2fr) 70px 86px minmax(0, 1.4fr); }
  .mtable-head span:nth-child(4), .mrow > :nth-child(4) { display: none; }
  .stats { max-width: none; }
  .price-ribbon { margin-top: 48px; }
}

@media (max-width: 640px) {
  /* 首屏在手机上也走"至少一屏"：高度不够时先收垂直间距，
     而不是把价格条挤到屏幕外。390×844 下首屏正好收进一屏；
     更矮的手机（横屏、小屏）仍会滚动，不压缩字号和图标。 */
  .hero { padding: 40px 18px 0; }
  .hero-kicker { margin-bottom: 18px; }
  .hero-in { padding-bottom: 28px; }
  .hero-wordmark { font-size: clamp(46px, 15.5vw, 68px); letter-spacing: -.045em; }
  .hero-spec { font-size: 14.5px; max-width: 34ch; margin-top: 16px; }
  .hero .btns { margin-top: 24px; }

  /* 汇聚链在窄屏收窄：方块 26px、间距 7px，8 个正好铺满 390 宽。 */
  .feed { margin-top: 12px; }
  .name-rule { width: 92%; margin-top: 6px; }
  .feed-node { width: 26px; height: 26px; margin: 0 5px; border-radius: 6px; }
  .feed-node img { width: 14px; height: 14px; }
  .feed-hub { flex: 0 0 44px; width: 44px; }
  .feed-chain::before { left: calc(50% - 18px); right: calc(50% + 18px); }
  .feed-cap.l { left: calc(50% - 20px); }
  .feed-cap.r { right: calc(50% - 20px); }
  .feed-chain { overflow: hidden; max-width: 100%; }

  .hero .btns { margin-top: 24px; width: 100%; flex-direction: column; }
  .hero .btns .btn { width: 100%; }
  .endpoint-pill { margin-top: 20px; max-width: 100%; padding: 8px 10px 8px 12px; }
  .endpoint-pill code { font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* 价格条在窄屏改为两行：标签一行，价格一行 */
  .price-ribbon-in { gap: 8px 18px; padding-top: 12px; padding-bottom: 12px; }
  .pr-tag { width: 100%; }
  .pr-items { gap: 16px; }
  .pr-item .u { display: none; }
  .pr-link { margin-left: auto; }

  .wrap { padding: 0 18px; }
  .price-ribbon { margin: 0 -18px; }
  /* 手机上四张前沿模型卡改为单列。
     两列时每张只剩约 150px 内宽，模型名和价格会挤成两三行；
     单列读起来更像卡片，也更符合纵向滚动的节奏。 */
  .fr-grid { grid-template-columns: 1fr; gap: 10px; }
  .sec { padding: 64px 0; }
  .strip-in { gap: 14px; }
  .strip-list { gap: 16px; }
  .mq-group { gap: 30px; padding-right: 30px; }
  .mq-item { font-size: 13px; }
  /* 手机上的厂商名条改为"薄条"：上下 26px 的留白压到 13px，
     整条从 75px 降到 48px。这是首屏高度预算里最贵的一块，
     手机屏幕本来就矮，留白可以省，字号和图标尺寸不动。
     --marquee-h 同步调小，首屏才会按新的条高扣减。 */
  .marquee { padding: 13px 0; }
  :root { --marquee-h: 49px; }
  .mtable-head { display: none; }
  .mrow {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name id"
      "name price";
    gap: 6px 14px; padding: 14px 16px;
  }
  .mrow > :nth-child(1) { grid-area: name; }
  .mrow > :nth-child(3) { grid-area: price; text-align: right; font-size: 12px; }
  .mrow > :nth-child(5) { grid-area: id; justify-self: end; }
  .mrow > :nth-child(2), .mrow > :nth-child(4) { display: none; }
  .m-prov { font-size: 12px; }
  .adv { padding: 26px 20px 28px; }
  .foot-cols { gap: 40px; }
  .foot-in { padding-top: 34px; }
  .final { padding: 64px 0; }
}

/* 小屏手机（375×812、360×800 这类）：再收一档垂直间距，
   首屏才能连价格条一起装进一屏。只动间距，不动字号和图标。
   必须写在上面的 640 断点之后，否则同优先级的 margin 会被它覆盖。 */
@media (max-width: 640px) and (max-height: 860px) {
  .hero { padding-top: 20px; }
  .hero-in { padding-bottom: 10px; }
  .hero-kicker { margin-bottom: 10px; }
  .hero-wordmark { margin-top: 0; }
  .feed { margin-top: 8px; }
  .hero-spec { margin-top: 8px; }
  .hero .btns { margin-top: 12px; }
  .endpoint-pill { margin-top: 8px; }
}

/* ══════════════════════════ 宽屏：首屏整体等比放大 ══════════════════════════
   2K / 4K 屏上 1160px 的容器只占窗口 45%，首屏缩在中间一条；
   同时 132px 的名字上限让 clamp 提前封顶，画面更显空。

   这里不单独放大名字 —— 那会拆散首屏的比例关系
   （名字变大、图标链和说明没变，读起来像两套尺寸拼在一起）。
   改用 zoom 对整块内容等比缩放：字号、图标、间距、动画行程
   全部按同一系数走，整块观感和设计原稿一致。

   zoom 只作用在内容层；画布背景 .hero-canvas 是它的兄弟节点，
   不受影响，点阵密度保持原样。

   ⚠️ 纵向预算：首屏（含价格条）+ 厂商名条必须在一屏内看完。
   2K 屏的浏览器去掉标签栏和地址栏后，可用高度约 1050–1100px，
   所以 zoom 的系数不能只按"字够不够大"来定 —— 每加 0.08 就多出
   约 60px 高度，厂商名条就会被顶出首屏。
   当前 2549px 宽下：内容 840 × 1.38 + 顶部留白 104 + 价格条 75
   ≈ 首屏 1020px，名条落在 1020–1090，正好在折叠线以上。

   .hero 自身的上下 padding 不在缩放范围内，同一档里手动设定。 */
@media (min-width: 1600px) {
  .hero { padding-top: 92px; }
  .hero-in { zoom: 1.12; padding-bottom: 56px; }
  .price-ribbon-in { zoom: 1.12; }
}
@media (min-width: 1850px) {
  .hero { padding-top: 98px; }
  .hero-in { zoom: 1.22; padding-bottom: 56px; }
  .price-ribbon-in { zoom: 1.22; }
}
@media (min-width: 2150px) {
  .hero { padding-top: 102px; }
  .hero-in { zoom: 1.32; padding-bottom: 56px; }
  .price-ribbon-in { zoom: 1.32; }
}
@media (min-width: 2450px) {
  .hero { padding-top: 104px; }
  .hero-in { zoom: 1.38; padding-bottom: 56px; }
  .price-ribbon-in { zoom: 1.38; }
}
@media (min-width: 3000px) {
  .hero { padding-top: 112px; }
  .hero-in { zoom: 1.48; padding-bottom: 56px; }
  .price-ribbon-in { zoom: 1.48; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
