/* ============================================================
   tokens.css — 设计系统机制层
   ------------------------------------------------------------
   三层结构（与原站一致）:
     global  原始刻度, 无语义, 不直接用
     alias   语义命名, 组件只用这层
     theme   色彩, 见 themes.css
   ------------------------------------------------------------
   核心机制: 全站尺寸 = 基础 px × --ratio。
   改一个 --ratio, 整站等比缩放, 不需要给每个组件写断点。
   ============================================================ */

/* ------------------------------------------------------------
   1. 缩放引擎
   原站实测: 1024 / 1920 / 2560 三个断点上取 1 / 1.125 / 1.25 / 1.375。
   4K 屏上整站放大 37.5%, 所以大屏不会显得字小而空。
   ------------------------------------------------------------ */
:root { --ratio: 1; }
@media screen and (min-width: 1024px) and (max-width: 1920px) { :root { --ratio: 1.125; } }
@media screen and (min-width: 1920px) and (max-width: 2560px) { :root { --ratio: 1.25; } }
@media screen and (min-width: 2560px)                          { :root { --ratio: 1.375; } }

:root {
  /* ----------------------------------------------------------
     2. 间距刻度 (global)
     8 的倍数为主, 低段补 1/2/3/4/6 供精细对齐。
     ---------------------------------------------------------- */
  --sp-25:   calc(2px   * var(--ratio));
  --sp-50:   calc(4px   * var(--ratio));
  --sp-75:   calc(6px   * var(--ratio));
  --sp-100:  calc(8px   * var(--ratio));
  --sp-150:  calc(12px  * var(--ratio));
  --sp-200:  calc(16px  * var(--ratio));
  --sp-250:  calc(20px  * var(--ratio));
  --sp-300:  calc(24px  * var(--ratio));
  --sp-400:  calc(32px  * var(--ratio));
  --sp-500:  calc(40px  * var(--ratio));
  --sp-600:  calc(48px  * var(--ratio));
  --sp-800:  calc(64px  * var(--ratio));
  --sp-1000: calc(80px  * var(--ratio));
  --sp-1200: calc(96px  * var(--ratio));
  --sp-1600: calc(128px * var(--ratio));
  --sp-2000: calc(160px * var(--ratio));
  --sp-2500: calc(200px * var(--ratio));

  /* ----------------------------------------------------------
     3. 字号刻度 (global) + 语义别名 (alias)
     ---------------------------------------------------------- */
  --fs-125:  calc(10px * var(--ratio));
  --fs-150:  calc(12px * var(--ratio));
  --fs-175:  calc(14px * var(--ratio));
  --fs-200:  calc(16px * var(--ratio));
  --fs-225:  calc(18px * var(--ratio));
  --fs-250:  calc(20px * var(--ratio));
  --fs-300:  calc(24px * var(--ratio));
  --fs-400:  calc(32px * var(--ratio));
  --fs-500:  calc(40px * var(--ratio));
  --fs-600:  calc(48px * var(--ratio));
  --fs-700:  calc(56px * var(--ratio));
  --fs-800:  calc(64px * var(--ratio));
  --fs-900:  calc(72px * var(--ratio));
  --fs-1100: calc(88px * var(--ratio));

  --text-display: var(--fs-800);
  --text-h1:      var(--fs-700);
  --text-h2:      var(--fs-600);
  --text-h3:      var(--fs-500);
  --text-h4:      var(--fs-400);
  --text-h5:      var(--fs-300);
  --text-h6:      var(--fs-250);
  --text-lg:      var(--fs-225);
  --text-md:      var(--fs-200);
  --text-sm:      var(--fs-175);
  --text-xs:      var(--fs-150);
  --text-xxs:     var(--fs-125);

  /* 超大标题: 视口驱动而非刻度驱动, 保证 hero 字总是撑满 */
  --text-hero: clamp(2.5rem, 11vw, 15rem);

  /* ----------------------------------------------------------
     4. 排版细节 —— 视觉签名所在
     标题与正文都用负字距 (-0.025em), 标签用正字距 (+0.02em):
     紧凑的标题 + 疏朗的小标签, 是这套设计辨识度的来源之一。
     ---------------------------------------------------------- */
  --tracking-heading: -0.025em;
  --tracking-body:    -0.025em;
  --tracking-label:    0.02em;
  --tracking-display:  0.06em;   /* 展示字体全大写时需要放开 */

  --leading-tight:  100%;
  --leading-snug:   110%;
  --leading-heading:130%;
  --leading-body:   150%;

  --weight-regular: 400;
  --weight-medium:  500;
  --weight-bold:    700;
  --weight-black:   900;

  /* ----------------------------------------------------------
     5. 字体栈
     原站用 GTA 定制 ArtDeco 做展示字, Helvetica Now 做正文。
     两者都是商业授权字体, 不随模板分发 —— 这里给出等价的系统字体栈。
     换成自有品牌字体: 只改这三行, 其余全部自动跟随。
     ---------------------------------------------------------- */
  --font-display: "Futura", "Avenir Next Condensed", "Oswald",
                  "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
  --font-heading: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Inter",
                  "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
  --font-body:    -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Inter",
                  "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
  --font-mono:    "SF Mono", "Consolas", "Roboto Mono", monospace;

  /* ----------------------------------------------------------
     6. 圆角 / 描边
     ---------------------------------------------------------- */
  --radius-0:   0px;
  --radius-25:  2px;
  --radius-50:  4px;
  --radius-100: 8px;
  --radius-150: 12px;
  --radius-200: 16px;
  --radius-300: 24px;
  --radius-400: 32px;
  --radius-pill: 1000px;
  --radius-circle: 9999px;

  --stroke-thin:   1px;
  --stroke-medium: 2px;
  --stroke-thick:  4px;

  /* ----------------------------------------------------------
     7. 动效
     下列时长与曲线全部取自原站的动效 token 组 (--_16b6yl7*) 与
     实际 animation 声明, 未做改写。
     实测分布: 0.2s / 0.25s / 0.3s 占绝对多数（微交互）,
     1s 与 2.15s 用于入场级大动效。
     ---------------------------------------------------------- */
  --dur-instant: 0.15s;
  --dur-fast:    0.2s;    /* 原站 --_16b6yl72 / 73 */
  --dur-base:    0.25s;
  --dur-medium:  0.3s;    /* 原站 --_16b6yl77 / 79 */
  --dur-slow:    0.4s;    /* 原站 --_16b6yl75 */
  --dur-slower:  1s;
  --dur-cinematic: 2.15s; /* 原站加载动画周期 */

  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);            /* 原站 --_16b6yl76 / 78 */
  --ease-in-out:   cubic-bezier(0.455, 0.03, 0.515, 0.955); /* 原站扫光底色曲线, easeInOutQuad */
  --ease-emphasis: cubic-bezier(0.645, 0.045, 0.355, 1);    /* 原站 --_16b6yl74 */
  --ease-sweep:    cubic-bezier(0.77, 0, 0.175, 1);         /* 原站扫光轨迹曲线 */
  --ease-entrance: cubic-bezier(0.32, 0.72, 0, 1);          /* 原站 :root --_16b6yl70 默认值 */
  --ease-smooth:   cubic-bezier(0.42, 0, 0.58, 1);          /* 原站 :root --_16b6yl71 默认值 */
  --ease-exit:     cubic-bezier(0.55, 0.085, 0.68, 0.53);   /* easeInQuad, 用在 1s 长淡出上 */
  /* 只在 ≥768px 覆盖时出现: 终点 y 是 0 而非 1 —— 缓入后回落,
     "起来一下又沉回去"的收尾, 不是笔误。 */
  --ease-settle:   cubic-bezier(0.42, 0, 0.58, 0);

  /* 滚动运镜专用: 原站 GSAP 侧统一 ease:"none"。
     滚动位置 1:1 绑定进度, 来回滚严格跟手可逆 —— 一旦加了缓动,
     观感立刻从"运镜"退回"入场动画"。 */
  --ease-scrub:    linear;

  /* ----------------------------------------------------------
     8. 层级 / 模糊
     z 值取自原站的层级 token 组 (--_19z1c6d3..e), 原样保留。
     刻意留出 90 (贴顶内容) < 100/110/115 (导航) < 120~122 (弹层)
     < 300/301 (加载遮罩) < 401 (提示) < 9999 (强制置顶) 的间隔,
     每档之间都留了插入空间。
     ---------------------------------------------------------- */
  --z-base:    0;
  --z-raised:  10;    /* 原站 --_19z1c6d3 */
  --z-stuck:   90;    /* 原站 --_19z1c6d7: 贴顶但要让位给导航的元素 */
  --z-sticky:  100;   /* 原站 --_19z1c6d4 */
  --z-nav:     110;   /* 原站 --_19z1c6d5 */
  --z-nav-top: 115;   /* 原站 --_19z1c6d6: 导航内部还要再压一层的元素 */
  --z-overlay: 120;   /* 原站 --_19z1c6d8 */
  --z-modal:   121;   /* 原站 --_19z1c6d9 */
  --z-popover: 122;   /* 原站 --_19z1c6da */
  --z-loading-bg: 300;/* 原站 --_19z1c6db */
  --z-loading: 301;   /* 原站 --_19z1c6dc */
  --z-toast:   401;   /* 原站 --_19z1c6dd */
  --z-top:     9999;  /* 原站 --_19z1c6de */

  /* 视口刻度: 原站 --_19z1c6d1 = max(5vw, 10vh)。
     取宽高里较大的那个, 所以横屏看宽度、竖屏看高度, 两种姿态下
     标志尺寸与辉光半径都不会失衡。

     说明边界: 原站只有**一个组件**消费它(标志的 height / width /
     drop-shadow 三处), 不是全局刻度。本模板把它提到 token 层供复用,
     属于扩大用途, 不是原样照搬。 */
  --viewport-scale: max(5vw, 10vh);
  --logo-size:      min(calc(var(--viewport-scale) * 0.45), 2.1rem);

  --blur-subtle: 6.25px;
  --blur-glass:  13px;
  --blur-heavy:  25px;
  --blur-bloom:  150px;

  /* 原站有两档玻璃态: 常规 blur(13px) saturate(1.4),
     以及覆盖全屏的强档 blur(150px) brightness(1.2) saturate(2.5)。
     强档用于对话框/全屏遮罩, 会把底下的画面熬成一片色雾。 */
  --glass-filter:       blur(var(--blur-glass)) saturate(1.4);
  --glass-filter-heavy: blur(var(--blur-bloom)) brightness(1.2) saturate(2.5);

  /* 分区斜切角度, 单位 vw。原站实测: 桌面 4, 移动 6 —— 窄屏反而更陡。
     因为 vw 跟着宽度缩, 4vw 在 390px 上只剩 15.6px, 斜边形同虚设。 */
  --angle-cut: 4;
  --angle-cut-mobile: 6;

  /* 内容最大宽度与页边距。
     注意: 这三个值不是从原站实测的, 是本模板自行选定的通用默认值。 */
  --page-gutter: clamp(1.25rem, 5vw, 6rem);
  --content-max: 90rem;
  --measure: 46ch;          /* 正文单行最佳字数 */

  /* ----------------------------------------------------------
     9. 噪点纹理
     原站在渐变面上叠了一层 SVG fractalNoise, 参数与不透明度如下,
     全部为实测值。这层几乎看不见, 但没有它, 大面积渐变会出现色带
     (banding) —— 高级感有相当一部分来自这里。
     ---------------------------------------------------------- */
  --noise-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  --noise-opacity: 0.03;

  /* ----------------------------------------------------------
     10. 辉光
     原站的辉光是成体系的: 文字用 text-shadow, 图形用 drop-shadow,
     且总是"小半径高透明 + 大半径低透明"两层叠加。
     下列半径与色值均为实测。
     注意辉光的"近光"一层用的是固定白色而非主题色 —— 原站在这里刻意脱离色阶,
     让高光始终是中性白, 只有"远光"一层才染主题色。
     ---------------------------------------------------------- */
  --glow-text:        0 0 28px var(--tone-21-a25);
  --glow-text-bright: 0 0 28px var(--tone-24-a25);
  --glow-text-strong: 0 0 20px #ffffff4d, 0 0 100px #ffffff99;

  --glow-drop:        drop-shadow(0 0 10px #ffffff33) drop-shadow(0 20px 70px var(--tone-4));
  --glow-drop-strong: drop-shadow(0 0 30px #ffffff4d) drop-shadow(0 50px 100px var(--tone-5));
  --glow-drop-light:  drop-shadow(0 0 100px #ffffff99) drop-shadow(0 0 20px #ffffff4d);

  /* 半径随视口缩放的辉光。原站给标志用的就是这一档:
     模糊半径 = 视口刻度 × 0.025 / × 0.1, 所以小屏上不会糊成一坨。
     近光是粉色 #ffb0c480 而不是白色 —— 这是它跟前面几档的区别。 */
  --glow-drop-fluid: drop-shadow(0 0 calc(var(--viewport-scale) * 0.025) #ffb0c480)
                     drop-shadow(0 0 calc(var(--viewport-scale) * 0.1) #000);
  --glow-text-gold:  0 0 100px #ffdb6566;   /* 原站的金色远光, 独立于色阶 */

  /* ----------------------------------------------------------
     11. 渐变
     --glow 在 themes.css 里按主题定义 (顶部光晕)。原站把同一个
     渐变**同时**当背景光晕和标题文字的填充用, 所以文字看起来像是
     被同一个光源照亮的 —— 这是整页色彩统一的关键一招。

     --gradient-sunset 是原站 hero 标题的专用渐变, 刻度用 vh:
     圆心在视口上方 30vh, 到 150vh 处淡出。因为刻度是视口而不是
     元素自身, 同一行字在页面里的位置不同, 取到的颜色段就不同。
     ---------------------------------------------------------- */
  --gradient-sunset: radial-gradient(circle at 50% -30vh,
                       #ffd687 0,
                       #fc5243 50vh,
                       #9d2f6a 90vh,
                       #201f4200 150vh);

  /* 卡片底部压暗 (scrim), 三段式 —— 图上叠文字时保证可读 */
  --gradient-scrim: linear-gradient(0deg, #00000047 0%, #0000007a 52%, #000 87%);
}

/* ------------------------------------------------------------
   9. 语义色层
   注意选择器是 [data-theme] 而非 :root —— 这样任何一个带
   data-theme 的元素都会就地重算语义色, 分区换肤才能生效。
   ------------------------------------------------------------ */
[data-theme] {
  --bg-base:      var(--tone-1);
  --bg-raised:    var(--tone-3);
  --bg-inset:     var(--tone-0);

  --text-hi:      var(--tone-22);
  --text-mid:     var(--tone-18);
  --text-low:     var(--tone-14);
  --text-invert:  var(--tone-1);

  --line-subtle:  var(--tone-5-a50);
  --line-base:    var(--tone-8-a50);
  --line-strong:  var(--tone-11);

  /* 遮罩与半透明层。
     组件层一律用这些语义名, 不许直接写 --tone-<n>:
     tone 索引是有方向的, 一旦反转明暗就会变成深底深字。 */
  --scrim-solid:  var(--tone-0);
  --scrim:        var(--tone-0-a80);
  --scrim-soft:   var(--tone-0-a50);
  --veil:         var(--tone-2-a50);
  --glass:        var(--tone-1-a80);
  --glass-soft:   var(--tone-8-a25);
  --glass-hover:  var(--tone-10-a50);
  --sunken:       var(--tone-0-a50);

  --accent:       var(--accent-warm-1);
  --accent-hover: var(--accent-warm-2);
  --accent-press: var(--accent-warm-3);
  --accent-alt:   var(--accent-cool-1);

  color-scheme: dark;
}

/* 亮色反转: 同一条色阶, 从另一端取值。
   单色相隧道天生对称, 所以不需要第二套色板。

   两个选择器缺一不可:
     第一条   命中自身同时带 data-theme 与 data-mode 的元素 (通常是 <html>);
     第二条   命中亮色容器内部那些只带 data-theme 的分区 —— 没有它,
              分区级换肤会卡在暗色不跟随整页反转。
   :where() 把祖先部分的优先级抹平, :not() 保证局部写死 data-mode="dark"
   的分区能落回暗色, 两者合起来让三种组合都成立。 */
[data-theme][data-mode="light"],
:where([data-mode="light"]) [data-theme]:not([data-mode="dark"]) {
  --bg-base:      var(--tone-23);
  --bg-raised:    var(--tone-24);
  --bg-inset:     var(--tone-21);

  --text-hi:      var(--tone-2);
  --text-mid:     var(--tone-6);
  --text-low:     var(--tone-10);
  --text-invert:  var(--tone-23);

  --line-subtle:  var(--tone-19-a50);
  --line-base:    var(--tone-16-a50);
  --line-strong:  var(--tone-13);

  --scrim-solid:  var(--tone-24);
  --scrim:        var(--tone-24-a80);
  --scrim-soft:   var(--tone-24-a50);
  --veil:         var(--tone-22-a50);
  --glass:        var(--tone-23-a80);
  --glass-soft:   var(--tone-16-a25);
  --glass-hover:  var(--tone-14-a50);
  --sunken:       var(--tone-22-a50);

  /* 强调色在亮色模式下必须压暗。
     accent-warm/cool 三档都是为暗底设计的高亮色 —— 实测把它们直接放到浅底上,
     六个主题的对比度全部落在 1.2~2.3, 远低于 3.0 的下限, 主按钮会变成浅底浅字。
     这里混入同主题的暗色档来压低明度, 色相保持不变。
     每条都先给一个不依赖 color-mix 的回退值, 老浏览器仍可用。 */
  --accent:       var(--tone-9);
  --accent:       color-mix(in oklab, var(--accent-warm-3) 30%, var(--tone-3));
  --accent-hover: var(--tone-7);
  --accent-hover: color-mix(in oklab, var(--accent-warm-3) 22%, var(--tone-2));
  --accent-press: var(--tone-5);
  --accent-press: color-mix(in oklab, var(--accent-warm-3) 15%, var(--tone-1));
  --accent-alt:   var(--tone-9);
  --accent-alt:   color-mix(in oklab, var(--accent-cool-3) 30%, var(--tone-3));

  color-scheme: light;
}

/* ------------------------------------------------------------
   10. 基础 reset

   视口高度单位的分工 —— 移动端最容易被忽略、后果最严重的一处。
   手机浏览器滚动时地址栏会收起, 可见高度在两个值之间来回变:
     lvh  large  —— 地址栏收起后的大高度, 固定值, 永不变化
     svh  small  —— 地址栏展开时的小高度, 固定值, 永不变化
     dvh  dynamic —— 当前实际高度, 滚动中持续变化
   dvh 的名字听起来最"聪明", 却是滚动叙事的灾难: 用它做钉住舞台的高度,
   地址栏一收, 舞台就长高, 整条滚动行程随之变化, 洗刷进度和视差全部跳一下。

   原站的分工(实测)已经把这件事想透了:
     lvh  滚动叙事的固定媒体层与行程高度 —— 要绝对稳定, 且收起地址栏后正好铺满
     svh  必须完整看到的一屏内容、长滚动区高度 —— 要绝对稳定, 且展开时也不被裁
     dvh  overlay / 抽屉 / 全屏菜单 —— 这些要贴合"此刻"的可见区, 才不会被挡

   唯一的例外原站也写了: 应用内嵌浏览器(Instagram、Twitter 等 WebView)的
   工具条不收放且长期占位, lvh 会让底部被裁掉, 所以那里才反过来强制 dvh。
   本模板照抄这条规则, 见文件末尾的 [data-in-app="true"]。
   ------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  /* svh 而非 dvh: 首屏至少填满最小视口, 且不随地址栏收放改变文档高度 */
  min-height: 100svh;
  background: var(--bg-base);
  color: var(--text-mid);
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: var(--leading-body);
  letter-spacing: var(--tracking-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  line-height: var(--leading-heading);
  letter-spacing: var(--tracking-heading);
  color: var(--text-hi);
  text-wrap: balance;
}

h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }
h3 { font-size: var(--text-h3); }
h4 { font-size: var(--text-h4); }
h5 { font-size: var(--text-h5); }
h6 { font-size: var(--text-h6); }

p { margin: 0 0 var(--sp-400); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

img, video, canvas, svg, picture { display: block; max-width: 100%; }
a { color: inherit; }
button { font: inherit; color: inherit; }

::selection { background: var(--accent); color: var(--text-invert); }

/* 焦点环。
   原站的做法: 轮廓**常驻**, 平时把颜色设成透明, 聚焦时才染色。
   好处是 outline 的空间一直占着, 聚焦瞬间不会有任何位移或重排,
   而且颜色可以 transition —— 直接 `outline: none → 2px solid` 是做不到的。
   outline-offset 取原站的 4px。 */
:where(a, button, summary, [tabindex], input, select, textarea) {
  outline-width: var(--stroke-medium);
  outline-style: solid;
  outline-color: transparent;
  outline-offset: 4px;
  transition: outline-color var(--dur-medium) var(--ease-standard);
}

:where(a, button, summary, [tabindex], input, select, textarea):focus-visible {
  outline-color: var(--accent);
  border-radius: var(--radius-50);
}

/* 移动端点击时系统给的那层灰色高亮, 会盖掉自己的按压反馈 */
:where(a, button, summary, [role="button"], [tabindex]) {
  -webkit-tap-highlight-color: transparent;
}

/* 滚动锁定: 弹层打开时给 <html> 挂 data-scroll-locked。
   overscroll-behavior: contain 是关键 —— 只加 overflow:hidden 的话,
   在触摸设备上滚到边界仍会把底层页面带着一起动 (滚动穿透)。 */
html[data-scroll-locked],
html[data-scroll-locked] body {
  overflow: hidden !important;
  overscroll-behavior: contain;
}

/* 滚动条。两套写法并存: 标准属性给 Firefox 与新版内核,
   -webkit- 伪元素给 Safari 与旧 Chromium。
   scrollbar-gutter: stable 预留出滚动条的位置, 避免内容在
   "出现滚动条"的瞬间横向跳一下。 */
.vx-scrollarea {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
}
.vx-scrollarea::-webkit-scrollbar { width: 16px; height: 16px; }
.vx-scrollarea::-webkit-scrollbar-track { background: transparent; }
.vx-scrollarea::-webkit-scrollbar-thumb {
  background-color: var(--line-strong);
  background-clip: padding-box;   /* 配合透明边框, 做出"两侧留白的细条" */
  border: 4px solid transparent;
  border-radius: 8px;
}

/* 屏幕阅读器专用文本: 视觉上不可见, 但仍在无障碍树里 */
.vx-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* 跳转到主内容: 键盘用户按第一下 Tab 就能拿到, 鼠标用户永远看不见 */
.vx-skip-link {
  position: absolute;
  top: var(--sp-200);
  left: var(--sp-200);
  z-index: var(--z-top);
  padding: var(--sp-150) var(--sp-300);
  background: var(--accent);
  color: var(--text-invert);
  border-radius: var(--radius-100);
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform var(--dur-medium) var(--ease-standard);
}
.vx-skip-link:focus-visible { transform: translateY(0); }

/* 高对比度模式 (Windows 强制配色): 自定义的半透明色会被系统全部替换,
   焦点环若只靠 outline-color 会消失, 这里改回系统色。 */
@media (forced-colors: active) {
  :where(a, button, summary, [tabindex]):focus-visible {
    outline-color: Highlight;
  }
  .vx-noise, .vx-noise-page { display: none; }
}

/* ------------------------------------------------------------
   12. 字体度量覆盖 —— 防加载时布局跳动 (CLS)
   ------------------------------------------------------------
   问题: 自定义字体下载完成前, 浏览器先用系统字体排版。两种字体的
   字高与字宽不一样, 换上去的一瞬间整页文字会重排 —— 就是那种
   "刷新后内容抖一下"。font-display:swap 让文字尽早可读, 但也正是
   它把这次抖动暴露了出来。

   解法: 为 fallback 单独声明一个 @font-face, 用 src:local() 指向
   系统字体, 再用四个 override 描述符把它的度量**改写成**自定义字体
   的度量。这样两者占位完全一致, 切换时零位移。
   原站 68 个 @font-face 里有 34 个就是干这个的。

   下面的数值是原站 Helvetica Now Text → Arial 的实测值, 可直接用。
   换成自己的字体时, 四个数按这个公式算:
     size-adjust       = 自定义字体 avgCharWidth ÷ 回退字体 avgCharWidth
     ascent-override   = 自定义字体 ascender  ÷ unitsPerEm ÷ size-adjust
     descent-override  = 自定义字体 descender ÷ unitsPerEm ÷ size-adjust
     line-gap-override = 自定义字体 lineGap   ÷ unitsPerEm ÷ size-adjust
   这些数在字体文件的 OS/2 与 hhea 表里, 用 fontkit / opentype.js 读得到;
   Next.js 的 next/font 与 fontaine 插件也能自动生成。
   ------------------------------------------------------------ */
@font-face {
  font-family: "Body Fallback";
  src: local("Arial");
  size-adjust: 106.55%;
  ascent-override: 104.177%;
  descent-override: 33.787%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Body Fallback";
  src: local("Arial Bold"), local("Arial");
  font-weight: 700;
  size-adjust: 107.223%;
  ascent-override: 103.523%;
  descent-override: 33.575%;
  line-gap-override: 0%;
}

/* ------------------------------------------------------------
   13. 中日韩补偿
   -0.025em 的负字距是为拉丁字母调的, 直接套到中日韩文字上会挤成一团。
   原站的做法是给 CJK 单独换字重档 (Latin CondensedHeavy 900 ↔ Noto Black 900);
   这里额外把字距归零、行高放宽, 因为方块字本身已经足够密。
   ------------------------------------------------------------ */
:where(:lang(zh), :lang(ja), :lang(ko)) {
  --tracking-heading: 0em;
  --tracking-body: 0em;
  --tracking-display: 0.12em;   /* 中文展示字反而要更疏, 否则笔画糊在一起 */
  --leading-heading: 140%;
  --leading-body: 175%;
}

/* ------------------------------------------------------------
   14. 体验分级 (hifi / lofi)
   ------------------------------------------------------------
   原站在弱设备上不是"少放几个动效", 而是把整组动效 token **归零**:
   时长全部 0s、曲线全部 linear。于是所有过渡瞬间完成, 页面依然
   完整可用, 只是不再有动画。比逐个组件写 if 干净得多。

   给根元素加 data-experience="lofi" 即可。什么时候加由你决定 ——
   原站读 deviceMemory / hardwareConcurrency / connection.effectiveType
   / UA 自动判定, 并允许用户手动切换。

   注意: 原站的 lofi 只影响这组共享过渡 token, 滚动运镜不读它们,
   所以弱机上运镜照跑。本模板的 lofi 连运镜一起停(见下面的 scrub/dolly),
   这是有意的偏离。
   ------------------------------------------------------------ */
[data-experience="lofi"] {
  --dur-instant: 0s;
  --dur-fast:    0s;
  --dur-base:    0s;
  --dur-medium:  0s;
  --dur-slow:    0s;
  --dur-slower:  0s;

  --ease-standard: linear;
  --ease-in-out:   linear;
  --ease-emphasis: linear;
  --ease-entrance: linear;
  --ease-smooth:   linear;
  --ease-exit:     linear;
  --ease-settle:   linear;
}

/* lofi 下停掉循环动画与滚动运镜 —— 弱机上这两项最费电 */
[data-experience="lofi"] .vx-shimmer,
[data-experience="lofi"] .vx-shimmer::before,
[data-experience="lofi"] .vx-shimmer::after,
[data-experience="lofi"] .vx-skeleton::after,
[data-experience="lofi"] .vx-marquee__track { animation: none; }
[data-experience="lofi"] .vx-dolly { --dolly-progress: 0 !important; }
/* 横向画廊: 停掉淡入淡出与位移, 但**保留** --h-progress 驱动的裁切平移 ——
   那一条是纯 object-position, 不产生合成层, 而且它承担构图而不只是效果。 */
[data-experience="lofi"] .vx-hfade { --h-visible: 1 !important; }
[data-experience="lofi"] .vx-hpan  { --hp: 0 !important; }

/* ------------------------------------------------------------
   15. 无障碍: 尊重系统的减弱动效偏好
   ------------------------------------------------------------
   这一节是**本模板的补充, 原站没有**。实测原站 CSS 里
   `@media (prefers-reduced-motion)` 出现次数为 0, JS 侧虽然有
   usePrefersReducedMotion, 但只接了两个组件(Tooltip 与一个序列帧
   播放器), 滚动运镜完全不读它 —— 开了系统"减弱动态效果"之后,
   原站的运镜照常播放。那是个可复制的无障碍缺陷, 没有照抄。
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------
   16. 分区之间的连续染色

   切 [data-theme] 是离散的 —— 交界处颜色会"啪"地跳一下。原站不是这么做的:
   它让 GSAP 在 scrub 里对 CSS 变量做颜色插值, 每帧把 rgba(...) 写进内联
   style(实测 3 个变量各有 24~29 个不同取值), 也就是一路化过去的。

   零依赖复刻不需要在 JS 里解析颜色: motion.js 只写"从哪个色 / 到哪个色 /
   走到几成", 混色交给 color-mix()。--sync-accent 就是插好的那个值,
   任何想跟着分区连续变色的元素引用它即可; 没跑 JS 时自动退回 --accent。
   ------------------------------------------------------------ */
:root {
  /* 三个默认值让这套在没跑 JS 时也成立: 自己混自己 = 自己 */
  --sync-from: var(--accent);
  --sync-to:   var(--accent);
  --sync-p:    0;
  /* 用 oklch 而不是 oklab —— 这一处的差别很大。
     oklab 是直线插值: 从黄绿混到紫色, 直线要穿过色彩空间的中心,
     中间那一段会掉成灰白, 看着像"褪色"而不是"换色"。
     oklch 沿色相环走, 亮度和饱和度一路保持, 中间是黄绿→青→蓝→紫,
     这才是整站单色相隧道该有的过渡。 */
  --sync-accent: color-mix(in oklch,
                   var(--sync-to) calc(var(--sync-p) * 100%),
                   var(--sync-from));
}

/* ------------------------------------------------------------
   17. 应用内嵌浏览器(WebView)覆写 —— lvh 的唯一例外

   从 Instagram / Twitter / 微信 里点开链接时, 页面跑在宿主 App 的
   WebView 里。那种环境的顶栏和底栏不会随滚动收起, 是长期占位的,
   于是 lvh(等于地址栏收起后的大高度)比实际可见区高出一截,
   满屏元素的底部会被工具条永久盖住 —— 恰恰是滚动提示、CTA 按钮
   最爱待的位置。这时反过来用 dvh 才对: 它就等于当前真实可见高度,
   而 WebView 里这个值本来就不变, 不存在跳动问题。

   原站实测有同一条规则(`[data-in-app=true] .xxx { height:100dvh!important }`),
   由 JS 嗅探 UA 后在 <html> 上打标记。本模板照抄机制, 打标记这一步
   留给使用方 —— 各家 App 的 UA 特征在变, 硬编码进 CSS 包会过期。

   用法: 检测到 WebView 时 document.documentElement.dataset.inApp = "true"
   自己的满屏元素挂 .vx-viewport-fill, 就一起纳入这条覆写。
   ------------------------------------------------------------ */

/* 满屏元素的通用钩子: 平时 lvh 保持稳定, 进 WebView 自动换 dvh */
.vx-viewport-fill { min-height: 100lvh; }

[data-in-app="true"] .vx-hero,
[data-in-app="true"] .vx-viewport-fill {
  min-height: 100dvh;
}

[data-in-app="true"] .vx-scrub__stage {
  height: 100dvh;
}
