/* ══════════════════════════════════════════════════════════════════════
   KernelHub 官网 —— FX 层（动效 + SVG 影片）

   ── 为什么单独一份，而不是塞进 kernelhub.css ────────────────────
   这一层里的每一条都是可以整层关掉的。关掉之后页面必须还是一个
   完整的、读得下去的站 —— 信息一个字不少，只是不动。
   把它和版式混在一起写，就再也分不清「哪些是内容、哪些是效果」，
   而 prefers-reduced-motion 的降级会变成一条一条去追。

   ── 三条硬约束 ──────────────────────────────────────────────────
   ① 不引第三方库、不加载视频、不用图片序列帧。全部是 CSS + 内联 SVG。
      首屏不因为动效多一次网络往返。
   ② 影片是一条时间线：同一个 14s 周期，每个元素用自己的
      @keyframes 在自己的时间窗里出场。所有元素共用同一个 duration，
      所以它们天生同步 —— 用一串 setTimeout 去编排的话，
      标签页切到后台再回来就全乱了。
   ③ prefers-reduced-motion 时全部定格在信息最全的那一帧，
      不是「动得慢一点」。见文件末尾那一节。

   色值全部来自 kernelhub.css 的变量（它们又来自 18091 Demo），
   这里一个新颜色都没有挑。
   ══════════════════════════════════════════════════════════════════════ */

/* ══ 1. 页面级氛围 ═══════════════════════════════════════════════════ */

/* 网格底：Demo 首屏那种"控制台"感。两层 —— 细网格 + 缓慢横移的高光带。
   用 background-position 动画（合成层）而不是 transform 一整块，
   免得把 body 提升成一个巨大的合成层。 */
body::after {
  content: "";
  position: fixed; inset: -50% -50% auto -50%; height: 200%;
  pointer-events: none; z-index: 0; opacity: .5;
  background-image:
    linear-gradient(to right, rgba(120, 200, 255, .045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(120, 200, 255, .045) 1px, transparent 1px);
  background-size: 62px 62px, 62px 62px;
  mask-image: radial-gradient(70% 45% at 50% 22%, #000 0%, transparent 72%);
  -webkit-mask-image: radial-gradient(70% 45% at 50% 22%, #000 0%, transparent 72%);
  animation: gridDrift 34s linear infinite;
}
@keyframes gridDrift { to { background-position: 62px 62px, 62px 62px; } }

/* 指针跟随的那团光。坐标由 fx.js 写进 --mx/--my；
   JS 不在时变量取默认值，光停在中上方 —— 页面不会因此变难看。 */
.spot {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background: radial-gradient(420px 420px at var(--mx, 50%) var(--my, 12%),
              rgba(56, 214, 240, .075), transparent 70%);
  transition: background-position .2s linear;
}

/* ══ 2. 滚动进场 ═════════════════════════════════════════════════════
   IntersectionObserver 给 [data-in]。默认是可见的 ——
   .rv 只在 JS 确认自己能观测时才由 fx.js 加上，
   否则 JS 一挂，整站内容就全是 opacity:0（这个坑这个仓库踩过：
   入场动画决定了内容在不在）。 */
.js-rv .rv { opacity: 0; transform: translateY(18px); }
.js-rv .rv[data-in] {
  opacity: 1; transform: none;
  transition: opacity .7s cubic-bezier(.2, .9, .3, 1), transform .7s cubic-bezier(.2, .9, .3, 1);
}
.js-rv .rv-d1[data-in] { transition-delay: .08s; }
.js-rv .rv-d2[data-in] { transition-delay: .16s; }
.js-rv .rv-d3[data-in] { transition-delay: .24s; }
.js-rv .rv-d4[data-in] { transition-delay: .32s; }
.js-rv .rv-d5[data-in] { transition-delay: .40s; }

/* ══ 3. 卡片 —— 描边流光 + 悬停辉光 ═════════════════════════════════ */

.card { position: relative; overflow: hidden; }
/* 一道极淡的斜向高光，从卡片左上扫到右下。只在 hover 时跑一次。 */
.card::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 30%, rgba(142, 247, 216, .07) 45%, transparent 60%);
  transform: translateX(-120%);
}
.card:hover::after { animation: sheen .9s ease-out; }
@keyframes sheen { to { transform: translateX(120%); } }
.card { transition: border-color .35s ease, box-shadow .35s ease, transform .35s ease; }
.card:hover {
  border-color: rgba(142, 247, 216, .26);
  box-shadow: 0 0 40px rgba(142, 247, 216, .07);
}

/* 统计数字：数字本身由 fx.js 滚上去，这里只管那层辉光。 */
.stat b { text-shadow: 0 0 26px rgba(142, 247, 216, .35); }

/* 章节小标题前面那个跳动的方块 —— Demo 里的 caret。 */
.eyebrow::before {
  content: "▌"; margin-right: 7px; color: var(--cyan);
  animation: caret 1.15s step-end infinite;
}
@keyframes caret { 50% { opacity: 0; } }

/* ══ 4. 影片通用 ═════════════════════════════════════════════════════ */

.film {
  position: relative; width: 100%;
  border: 1px solid var(--line); border-radius: var(--r-xl);
  background:
    radial-gradient(90% 120% at 18% 0%, rgba(56, 214, 240, .07), transparent 62%),
    radial-gradient(80% 110% at 92% 8%, rgba(142, 247, 216, .06), transparent 60%),
    rgba(3, 7, 12, .58);
  overflow: hidden;
}
.film svg { display: block; width: 100%; height: auto; }

/* 左上角那个 LIVE 角标 + 右上角时间码。做成录像机的样子 ——
   这一段是演示，不是产品截图，角标让人一眼看出来。 */
.film-tag {
  position: absolute; top: 12px; left: 14px; z-index: 2;
  display: inline-flex; align-items: center; gap: 7px;
  font: 700 10px/1 var(--mono); letter-spacing: 1.4px;
  color: var(--mint); background: rgba(4, 33, 26, .78);
  border: 1px solid rgba(142, 247, 216, .3); border-radius: 999px; padding: 6px 11px;
}
.film-tag i {
  width: 6px; height: 6px; border-radius: 50%; background: var(--mint);
  box-shadow: 0 0 10px var(--mint); animation: pulseDot 1.6s ease-in-out infinite;
}
@keyframes pulseDot { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }

.film-cap { margin-top: 12px; font-size: 13px; color: var(--ink-3); }

/* 扫描线：一条极淡的横带，14s 一趟。视频感有一半来自这一条。 */
.film-scan {
  position: absolute; left: 0; right: 0; height: 130px; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(142, 247, 216, .055), transparent);
  animation: scanSweep 7s ease-in-out infinite;
}
@keyframes scanSweep {
  0%   { transform: translateY(-140px); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translateY(560px); opacity: 0; }
}

/* ══ 5. 主影片（14s 一个周期）════════════════════════════════════════
   下面每一条 @keyframes 都写着它在这 14 秒里的哪一段出场。
   顺序：手机打字 → 发给服务端 → 派到本机 → 读/改/测 →
         证据一条条冒出来 → 改动回到手机 → 审批卡 → 点头 → 完成。
   这就是产品真实那条链（flow.n1..n10），不是随手编的一段动效。

   换算：14s 一圈，1s = 7.143%。所有元素共用这个 duration，
   于是它们天生同步 —— 谁也不用等谁。 */

.kf * { transform-box: fill-box; }

/* ── 5.1 常驻的呼吸：让画面在任何一帧都不是静止的 ── */
.kf-glow    { animation: breathe 4.5s ease-in-out infinite; transform-origin: center; }
.kf-glow-b  { animation: breathe 4.5s ease-in-out infinite .9s; transform-origin: center; }
@keyframes breathe { 0%, 100% { opacity: .45; } 50% { opacity: .95; } }

.kf-caret { animation: caret 1s step-end infinite; }
/* 输入框那根光标只在"正在打字"那一段闪，打完就收 */
.kf-caret-t { animation: caretType 14s step-end infinite; }
@keyframes caretType { 0%, 25% { opacity: 1; } 26%, 100% { opacity: 0; } }

/* 服务端周围那两个反向转的扫描环 */
.kf-radar   { transform-origin: center; animation: spin 22s linear infinite; }
.kf-radar-b { transform-origin: center; animation: spin 34s linear infinite reverse; }
@keyframes spin { to { transform: rotate(360deg); } }

/* 时间码：四段各占循环的四分之一，像录像机在走秒 */
.kf-tc { opacity: 0; }
.kf-tc-1 { animation: tc 14s step-end infinite 0s; }
.kf-tc-2 { animation: tc 14s step-end infinite 3.5s; }
.kf-tc-3 { animation: tc 14s step-end infinite 7s; }
.kf-tc-4 { animation: tc 14s step-end infinite 10.5s; }
@keyframes tc { 0% { opacity: 1; } 25% { opacity: 0; } 100% { opacity: 0; } }

/* 连线本体：一直在流的虚线 */
.kf-wire { stroke-dasharray: 5 9; animation: wireFlow 1.4s linear infinite; }
@keyframes wireFlow { to { stroke-dashoffset: -14; } }

/* 数据包：沿路径跑。offset-path 让它真的贴着贝塞尔曲线走，
   而不是走一条看起来"差不多"的直线 —— 差别就在拐弯那一下。
   1/2 是任务下去（青），3/4 是证据上来（薄荷），
   5/6 是你点头之后那次放行（琥珀，和审批卡同色）。 */
.kf-pkt { offset-rotate: 0deg; opacity: 0; }
.kf-pkt-1 { offset-path: path("M 258 250 C 340 250, 372 214, 452 214"); animation: pktRun 14s linear infinite 2.2s; }
.kf-pkt-2 { offset-path: path("M 588 214 C 648 214, 660 196, 712 196"); animation: pktRun 14s linear infinite 3.0s; }
.kf-pkt-3 { offset-path: path("M 712 300 C 660 300, 648 268, 588 268"); animation: pktRun 14s linear infinite 7.4s; }
.kf-pkt-4 { offset-path: path("M 452 268 C 372 268, 340 300, 258 300"); animation: pktRun 14s linear infinite 8.0s; }
.kf-pkt-5 { offset-path: path("M 258 250 C 340 250, 372 214, 452 214"); animation: pktRun 14s linear infinite 10.9s; }
.kf-pkt-6 { offset-path: path("M 588 214 C 648 214, 660 196, 712 196"); animation: pktRun 14s linear infinite 11.4s; }
@keyframes pktRun {
  0%   { offset-distance: 0%;   opacity: 0; }
  0.8% { opacity: 1; }
  5.4% { offset-distance: 100%; opacity: 1; }
  6.4% { offset-distance: 100%; opacity: 0; }
  100% { offset-distance: 100%; opacity: 0; }
}

/* ── 5.2 手机：打字 → 发出 → 证据流 → 收到卡 → 点头 → 完成 ── */

/* 输入框里的字：一个 clip 矩形从 0 拉到满，做出逐字打出来的样子。
   真正逐字要么切成很多 <tspan>，要么上 JS —— 那两条都比这个脆。 */
.kf-type { animation: typeIn 14s steps(24, end) infinite; }
@keyframes typeIn {
  0%   { width: 0; }
  14%  { width: 118px; }
  25%  { width: 118px; }
  26%  { width: 0; }
  100% { width: 0; }
}
.kf-send { opacity: 0; animation: flash 14s linear infinite; }
@keyframes flash {
  0%, 24% { opacity: 0; }
  26%     { opacity: 1; }
  30%     { opacity: 0; }
  100%    { opacity: 0; }
}

/* 手机上那条运行中的行 */
.kf-run { opacity: 0; animation: runRow 14s linear infinite; }
@keyframes runRow {
  0%, 26%  { opacity: 0; transform: translateY(6px); }
  30%      { opacity: 1; transform: none; }
  78%      { opacity: 1; transform: none; }
  82%      { opacity: .25; }
  100%     { opacity: .25; }
}
.kf-prog { transform: scaleX(0); transform-origin: left center; animation: progFill 14s linear infinite; }
@keyframes progFill {
  0%, 28%  { transform: scaleX(0); }
  38%      { transform: scaleX(.3); }
  50%      { transform: scaleX(.56); }
  60%      { transform: scaleX(.78); }
  86%      { transform: scaleX(.78); }
  92%      { transform: scaleX(1); }
  100%     { transform: scaleX(1); }
}

/* 证据流：一条条冒出来，这才是"实时工作区"真正在做的事 —— 
   不是转圈，是把它读了什么、跑了什么一条条报上来。 */
.kf-ev { opacity: 0; }
@keyframes evIn {
  0%   { opacity: 0; transform: translateY(7px); }
  1.6% { opacity: 1; transform: none; }
  56%  { opacity: 1; transform: none; }
  60%  { opacity: 0; }
  100% { opacity: 0; }
}
.kf-ev-1 { animation: evIn 14s ease-out infinite 4.2s; }
.kf-ev-2 { animation: evIn 14s ease-out infinite 5.3s; }
.kf-ev-3 { animation: evIn 14s ease-out infinite 6.4s; }
.kf-ev-4 { animation: evIn 14s ease-out infinite 7.4s; }

/* 审批卡：从手机底部滑上来（Demo 里 dnxsheet 那一下的曲线） */
.kf-sheet { opacity: 0; animation: sheetUp 14s cubic-bezier(.2, .9, .3, 1) infinite; }
@keyframes sheetUp {
  0%, 58%  { opacity: 0; transform: translateY(100px); }
  63%      { opacity: 1; transform: translateY(0); }
  80%      { opacity: 1; transform: translateY(0); }
  84%      { opacity: 0; transform: translateY(30px); }
  100%     { opacity: 0; transform: translateY(100px); }
}
.kf-tap { opacity: 0; transform-origin: center; animation: tapRipple 14s ease-out infinite; }
@keyframes tapRipple {
  0%, 72%  { opacity: 0; transform: scale(.2); }
  74%      { opacity: .9; transform: scale(.4); }
  79%      { opacity: 0; transform: scale(1.9); }
  100%     { opacity: 0; transform: scale(.2); }
}
.kf-btn { animation: btnPress 14s ease-out infinite; transform-origin: center; }
@keyframes btnPress {
  0%, 73%  { transform: none; }
  75%      { transform: scale(.955); }
  77%      { transform: none; }
  100%     { transform: none; }
}
.kf-done { opacity: 0; animation: doneIn 14s ease-out infinite; }
@keyframes doneIn {
  0%, 89%  { opacity: 0; transform: translateY(-10px); }
  92%      { opacity: 1; transform: none; }
  99%      { opacity: 1; transform: none; }
  100%     { opacity: 0; }
}

/* ── 5.3 服务端：收到任务时打出同心圆，队列首格跟着亮 ── */
.kf-ring { opacity: 0; transform-origin: center; }
.kf-ring-1 { animation: ringOut 14s ease-out infinite 2.9s; }
.kf-ring-2 { animation: ringOut 14s ease-out infinite 3.2s; }
.kf-ring-3 { animation: ringOut 14s ease-out infinite 7.9s; }
.kf-ring-4 { animation: ringOut 14s ease-out infinite 11.2s; }
@keyframes ringOut {
  0%   { opacity: .8; transform: scale(.35); }
  4.5% { opacity: 0;  transform: scale(1.75); }
  100% { opacity: 0;  transform: scale(1.75); }
}
.kf-q { transform-origin: left center; }
.kf-q-1 { animation: qLit 14s ease-out infinite 2.9s; }
.kf-q-2 { animation: qLit 14s ease-out infinite 3.15s; }
.kf-q-3 { animation: qLit 14s ease-out infinite 3.4s; }
@keyframes qLit {
  0%   { transform: scaleX(1); }
  1.4% { transform: scaleX(1.06); }
  4%   { transform: scaleX(1); }
  100% { transform: scaleX(1); }
}
.kf-hub { animation: hubLit 14s linear infinite; }
@keyframes hubLit {
  0%, 19%  { stroke: rgba(56,214,240,.34); }
  22%      { stroke: rgba(56,214,240,.9); }
  30%      { stroke: rgba(56,214,240,.34); }
  55%      { stroke: rgba(56,214,240,.34); }
  58%      { stroke: rgba(142,247,216,.85); }
  64%      { stroke: rgba(56,214,240,.34); }
  100%     { stroke: rgba(56,214,240,.34); }
}

/* ── 5.4 这台机器：窗口醒来 → 代码一行行写出来 → 状态切换 → 对勾 ── */

.kf-win { animation: winWake 14s ease-out infinite; }
@keyframes winWake {
  0%, 27%  { stroke: rgba(255,255,255,.08); }
  31%      { stroke: rgba(142,247,216,.6); }
  42%      { stroke: rgba(142,247,216,.22); }
  100%     { stroke: rgba(142,247,216,.22); }
}
/* 面板里那条从上往下扫一遍的光带 */
.kf-sweep { opacity: 0; animation: winSweep 14s ease-in-out infinite 3.9s; }
@keyframes winSweep {
  0%   { opacity: 0;  transform: translateY(-70px); }
  2%   { opacity: .55; }
  26%  { opacity: .55; transform: translateY(280px); }
  30%  { opacity: 0;  transform: translateY(280px); }
  100% { opacity: 0;  transform: translateY(280px); }
}

/* 代码行：每一行自己一个延时，用 scaleX 从左边长出来 —— 
   这是"正在被写出来"最省的一种画法。 */
.kf-line { transform-origin: left center; transform: scaleX(0); opacity: 0; }
@keyframes lineIn {
  0%   { transform: scaleX(0); opacity: 0; }
  1.6% { transform: scaleX(1); opacity: 1; }
  78%  { transform: scaleX(1); opacity: 1; }
  84%  { transform: scaleX(1); opacity: .34; }
  100% { transform: scaleX(1); opacity: .34; }
}
.kf-l1 { animation: lineIn 14s ease-out infinite 4.2s; }
.kf-l2 { animation: lineIn 14s ease-out infinite 4.5s; }
.kf-l3 { animation: lineIn 14s ease-out infinite 4.9s; }
.kf-l4 { animation: lineIn 14s ease-out infinite 5.3s; }
.kf-l5 { animation: lineIn 14s ease-out infinite 5.9s; }
.kf-l6 { animation: lineIn 14s ease-out infinite 6.4s; }
.kf-l7 { animation: lineIn 14s ease-out infinite 6.9s; }
.kf-l8 { animation: lineIn 14s ease-out infinite 7.4s; }

/* 右上角 +8 −3 的计数条 */
.kf-cnt { transform-origin: left center; transform: scaleX(.05); animation: cntFill 14s ease-out infinite 4.2s; }
@keyframes cntFill {
  0%   { transform: scaleX(.05); }
  24%  { transform: scaleX(1); }
  84%  { transform: scaleX(1); }
  100% { transform: scaleX(1); }
}

/* 三个状态：Reading / Modifying / Testing 依次点亮 */
.kf-st { opacity: .28; }
.kf-st-r { animation: stOn 14s linear infinite 3.9s; }
.kf-st-m { animation: stOn 14s linear infinite 5.9s; }
.kf-st-t { animation: stOn 14s linear infinite 11.0s; }
@keyframes stOn {
  0%   { opacity: .28; }
  1.4% { opacity: 1; }
  14%  { opacity: 1; }
  17%  { opacity: .5; }
  100% { opacity: .5; }
}

/* 机器上的执行光标：跟着写到的那一行往下走 */
.kf-cur { animation: curMove 14s steps(1, end) infinite; }
@keyframes curMove {
  0%, 29%  { opacity: 0; transform: translateY(0); }
  31%      { opacity: 1; transform: translateY(0); }
  34%      { opacity: 1; transform: translateY(17px); }
  37%      { opacity: 1; transform: translateY(34px); }
  40%      { opacity: 1; transform: translateY(51px); }
  44%      { opacity: 1; transform: translateY(68px); }
  48%      { opacity: 1; transform: translateY(85px); }
  53%      { opacity: 1; transform: translateY(102px); }
  56%      { opacity: 1; transform: translateY(119px); }
  80%      { opacity: 1; transform: translateY(119px); }
  84%      { opacity: 0; transform: translateY(119px); }
  100%     { opacity: 0; }
}

/* 构建成功那一行：跑完了才出现 */
.kf-build { opacity: 0; animation: buildOk 14s ease-out infinite; }
@keyframes buildOk {
  0%, 82%  { opacity: 0; }
  85%      { opacity: 1; }
  99%      { opacity: 1; }
  100%     { opacity: 0; }
}

/* 收尾的对勾：描边一笔画出来 */
.kf-check { stroke-dasharray: 30; stroke-dashoffset: 30; animation: draw 14s ease-out infinite; }
@keyframes draw {
  0%, 85%  { stroke-dashoffset: 30; opacity: 0; }
  86%      { opacity: 1; }
  90%      { stroke-dashoffset: 0; opacity: 1; }
  99%      { opacity: 1; }
  100%     { opacity: 0; }
}

/* ══ 6. Live Diff 影片（9s 周期）══════════════════════════════════
   逐行"打"出来的做法：每一行有自己的裁切矩形，宽度从 0 拉到整行。
   不用 scaleX —— 那会把字横向拉扁，一眼假。
   每行的目标宽度不一样（代码本来就长短不一），所以只能一行一组关键帧。
   写死八组是丑了点，但比在 @keyframes 里绕自定义属性稳。 */

.kdc { width: 0; }
.kdc-1 { animation: kdType1 9s steps(26, end) infinite 0.00s; }
.kdc-2 { animation: kdType2 9s steps(21, end) infinite 0.45s; }
.kdc-3 { animation: kdType3 9s steps(30, end) infinite 0.95s; }
.kdc-4 { animation: kdType4 9s steps(16, end) infinite 1.50s; }
.kdc-5 { animation: kdType5 9s steps(32, end) infinite 2.10s; }
.kdc-6 { animation: kdType6 9s steps(34, end) infinite 2.75s; }
.kdc-7 { animation: kdType7 9s steps(24, end) infinite 3.40s; }
.kdc-8 { animation: kdType8 9s steps(18, end) infinite 4.05s; }
@keyframes kdType1 { 0% { width: 0; } 7% { width: 214px; } 92% { width: 214px; } 100% { width: 0; } }
@keyframes kdType2 { 0% { width: 0; } 7% { width: 168px; } 92% { width: 168px; } 100% { width: 0; } }
@keyframes kdType3 { 0% { width: 0; } 7% { width: 246px; } 92% { width: 246px; } 100% { width: 0; } }
@keyframes kdType4 { 0% { width: 0; } 7% { width: 132px; } 92% { width: 132px; } 100% { width: 0; } }
@keyframes kdType5 { 0% { width: 0; } 7% { width: 258px; } 92% { width: 258px; } 100% { width: 0; } }
@keyframes kdType6 { 0% { width: 0; } 7% { width: 272px; } 92% { width: 272px; } 100% { width: 0; } }
@keyframes kdType7 { 0% { width: 0; } 7% { width: 196px; } 92% { width: 196px; } 100% { width: 0; } }
@keyframes kdType8 { 0% { width: 0; } 7% { width: 150px; } 92% { width: 150px; } 100% { width: 0; } }

/* 光标：跟着写到的那一行往下走 */
.kd-cur { animation: kdCur 9s steps(1, end) infinite; }
@keyframes kdCur {
  0%   { transform: translateY(0);    opacity: 1; }
  6%   { transform: translateY(19px); }
  12%  { transform: translateY(38px); }
  19%  { transform: translateY(57px); }
  26%  { transform: translateY(76px); }
  33%  { transform: translateY(95px); }
  41%  { transform: translateY(114px); }
  49%  { transform: translateY(133px); }
  92%  { transform: translateY(133px); opacity: 1; }
  100% { transform: translateY(133px); opacity: 0; }
}
/* 右边 +8 / −3 那两条计数条，随着行数长出来 */
.kd-bar { transform-origin: left center; }
.kd-bar-a { animation: kdBar 9s ease-out infinite; }
.kd-bar-b { animation: kdBar 9s ease-out infinite .8s; }
@keyframes kdBar { 0% { transform: scaleX(.04); } 55% { transform: scaleX(1); } 92% { transform: scaleX(1); } 100% { transform: scaleX(.04); } }
/* 文件行的"正在写入"呼吸 */
.kd-file { animation: breathe 2.6s ease-in-out infinite; }

/* ══ 7. 六档权限影片（6s 周期）════════════════════════════════════ */

.kt-tier { opacity: .3; }
@keyframes ktOn { 0% { opacity: .3; } 6% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 1; } }
.kt-1 { animation: ktOn 6s ease-out infinite .1s; }
.kt-2 { animation: ktOn 6s ease-out infinite .5s; }
.kt-3 { animation: ktOn 6s ease-out infinite .9s; }
.kt-4 { animation: ktOn 6s ease-out infinite 1.3s; }
.kt-5 { animation: ktOn 6s ease-out infinite 1.7s; }
/* 第六档（delete/migrate）不亮 —— 它默认就不给。这一格是内容，不是动效。 */
.kt-shield { transform-origin: center; animation: shieldPulse 6s ease-in-out infinite; }
@keyframes shieldPulse { 0%, 100% { opacity: .5; } 45% { opacity: 1; } }
.kt-deny { animation: denyBlink 6s steps(1, end) infinite; }
@keyframes denyBlink { 0%, 60% { opacity: .35; } 66% { opacity: 1; } 78% { opacity: .35; } 100% { opacity: .35; } }

/* ══ 10. 员工流程影片（12s 周期）══════════════════════════════════
   雇用 → 指派 → 干活 → 审批 → 产物 → 结果。1s = 8.333%。
   六个节点各占两秒依次点亮并**保持**亮着（流程走过的地方不该再暗回去）；
   每个节点下面那一幕在自己的两秒里出场。 */

.kh * { transform-box: fill-box; }
.kh-n { opacity: .32; }
@keyframes khOn { 0% { opacity: .32; } 4% { opacity: 1; } 100% { opacity: 1; } }
.kh-n1 { animation: khOn 12s ease-out infinite 0s; }
.kh-n2 { animation: khOn 12s ease-out infinite 2s; }
.kh-n3 { animation: khOn 12s ease-out infinite 4s; }
.kh-n4 { animation: khOn 12s ease-out infinite 6s; }
.kh-n5 { animation: khOn 12s ease-out infinite 8s; }
.kh-n6 { animation: khOn 12s ease-out infinite 10s; }

/* 轨道上的数据包：分六段停在每个节点正下方 */
@keyframes khPkt {
  0%   { transform: translateX(110px); opacity: 0; }
  3%   { opacity: 1; }
  15%  { transform: translateX(110px); }
  17%  { transform: translateX(290px); }
  32%  { transform: translateX(290px); }
  34%  { transform: translateX(470px); }
  49%  { transform: translateX(470px); }
  51%  { transform: translateX(650px); }
  66%  { transform: translateX(650px); }
  68%  { transform: translateX(830px); }
  82%  { transform: translateX(830px); }
  84%  { transform: translateX(1010px); }
  96%  { transform: translateX(1010px); opacity: 1; }
  100% { transform: translateX(1010px); opacity: 0; }
}
.kh-pkt { animation: khPkt 12s ease-in-out infinite; }
.kh-rail { animation: wireFlow 1.2s linear infinite; }

/* 每一幕：在自己那两秒里淡入 + 上浮，之后保持 */
.kh-s { opacity: 0; }
@keyframes khIn { 0% { opacity: 0; transform: translateY(6px); } 5% { opacity: 1; transform: translateY(0); } 100% { opacity: 1; transform: translateY(0); } }
.kh-s1  { animation: khIn 12s ease-out infinite .4s; }
.kh-s2  { animation: khIn 12s ease-out infinite 2.4s; }
.kh-s3a { animation: khIn 12s ease-out infinite 4.3s; }
.kh-s3b { animation: khIn 12s ease-out infinite 4.9s; }
.kh-s3c { animation: khIn 12s ease-out infinite 5.5s; }
.kh-s5  { animation: khIn 12s ease-out infinite 8.4s; }
.kh-s6  { animation: khIn 12s ease-out infinite 10.3s; }
.kh-link { stroke-dasharray: 3 3; animation: wireFlow 1s linear infinite; }

/* 审批卡：从手机底部滑上来，你点了之后按钮闪一下 */
@keyframes khSheet {
  0%, 52% { opacity: 0; transform: translateY(40px); }
  56%     { opacity: 1; transform: translateY(0); }
  100%    { opacity: 1; transform: translateY(0); }
}
.kh-s4 { opacity: 0; animation: khSheet 12s cubic-bezier(.2,.9,.2,1) infinite; }
@keyframes khTap {
  0%, 60%  { opacity: 0; transform: scale(.4); }
  62%      { opacity: 1; transform: scale(1); }
  68%      { opacity: 0; transform: scale(2.4); }
  100%     { opacity: 0; transform: scale(2.4); }
}
.kh-tap { transform-origin: center; animation: khTap 12s ease-out infinite; }
@keyframes khPress { 0%, 60% { opacity: 1; } 62% { opacity: .55; } 66%, 100% { opacity: 1; } }
.kh-btn { animation: khPress 12s step-end infinite; }

/* 收尾的对勾：描边画出来 */
.kh-check { stroke-dasharray: 40; stroke-dashoffset: 40; animation: khDraw 12s ease-out infinite; }
@keyframes khDraw { 0%, 86% { stroke-dashoffset: 40; } 92%, 100% { stroke-dashoffset: 0; } }

/* ══ 11. Agent 治理影片（12s 周期）══════════════════════════════════
   0–3s   READ 请求从 Agent 走到执行体，闸不拦，本机标 read ✓
   3–5s   WRITE 请求出发，走到闸前停下；闸变琥珀
   5–7s   手机从下面滑上来，卡上是 Allow WRITE
   7–8s   点头 → 闸变薄荷（开）
   8–10s  WRITE 过闸到执行体，本机写入那一行亮起
   10–12s 保持 —— 然后重来 */

.kg * { transform-box: fill-box; }
.kg-req { opacity: 0; }
@keyframes kgRead {
  0%   { opacity: 0; transform: translateX(242px); }
  3%   { opacity: 1; }
  10%  { transform: translateX(445px); }
  14%  { transform: translateX(643px); }
  18%  { transform: translateX(728px); }
  22%  { transform: translateX(824px); }
  25%  { transform: translateX(1004px); opacity: 1; }
  28%  { opacity: 0; }
  100% { opacity: 0; transform: translateX(1004px); }
}
.kg-read { animation: kgRead 12s ease-in-out infinite; }
@keyframes kgWrite {
  0%, 25% { opacity: 0; transform: translateX(242px); }
  28%  { opacity: 1; transform: translateX(242px); }
  33%  { transform: translateX(445px); }
  38%  { transform: translateX(643px); }
  41%  { transform: translateX(700px); }
  66%  { transform: translateX(700px); }
  70%  { transform: translateX(824px); }
  76%  { transform: translateX(1004px); opacity: 1; }
  96%  { transform: translateX(1004px); opacity: 1; }
  100% { opacity: 0; transform: translateX(1004px); }
}
.kg-write { animation: kgWrite 12s ease-in-out infinite; }
/* 本机上的 read ✓ 在 READ 到达那一刻亮 */
.kg-read-mark { opacity: 0; animation: kgReadMark 12s step-end infinite; }
@keyframes kgReadMark { 0%, 24% { opacity: 0; } 25%, 100% { opacity: 1; } }
/* 闸：默认红（关）；WRITE 到达时琥珀闪；点头之后绿（开）覆在上面 */
@keyframes kgGateHot { 0%, 40% { opacity: .55; } 42% { opacity: 1; } 66% { opacity: 1; } 100% { opacity: .55; } }
.kg-gate { animation: kgGateHot 12s ease-out infinite; }
.kg-gate-open { opacity: 0; animation: kgGateOpen 12s step-end infinite; }
@keyframes kgGateOpen { 0%, 66% { opacity: 0; } 67%, 96% { opacity: 1; } 100% { opacity: 0; } }
/* 手机：闸拦住之后滑上来，点头之后收回去 */
.kg-phone { opacity: 0; animation: kgPhone 12s cubic-bezier(.2,.9,.2,1) infinite; }
@keyframes kgPhone {
  0%, 42% { opacity: 0; transform: translateY(40px); }
  46%     { opacity: 1; transform: translateY(0); }
  74%     { opacity: 1; transform: translateY(0); }
  78%     { opacity: 0; transform: translateY(40px); }
  100%    { opacity: 0; transform: translateY(40px); }
}
.kg-phone-wire { opacity: 0; animation: kgPhoneWire 12s step-end infinite; }
@keyframes kgPhoneWire { 0%, 42% { opacity: 0; } 43%, 74% { opacity: 1; } 75%, 100% { opacity: 0; } }
.kg-tap { transform-origin: center; animation: kgTap 12s ease-out infinite; }
@keyframes kgTap {
  0%, 60%  { opacity: 0; transform: scale(.4); }
  62%      { opacity: 1; transform: scale(1); }
  67%      { opacity: 0; transform: scale(2.4); }
  100%     { opacity: 0; transform: scale(2.4); }
}
.kg-btn { animation: khPress 12s step-end infinite; }
/* 本机：写入那一行 + 计数只在 WRITE 过闸之后出现 */
.kg-done { opacity: 0; animation: kgDone 12s step-end infinite; }
@keyframes kgDone { 0%, 76% { opacity: 0; } 77%, 97% { opacity: 1; } 100% { opacity: 0; } }
.kg-cur { animation: kgDone 12s step-end infinite; }
.kg-agent { animation: breathe 4s ease-in-out infinite; }
.kg-wire { animation: wireFlow 1.2s linear infinite; stroke-dasharray: 5 4; }

/* ══ 8. 窄屏 ═══════════════════════════════════════════════════════ */
/* ⚠️ 这一条必须在下面那个 @media 之前。写在后面的话同优先级后来居上，
   手机上的提示就永远显示不出来 —— 而判据只查"字符串在不在 CSS 里"，
   查不出顺序错。这是看手机截图才发现的。 */
.film-hint { display: none; font-size: 12px; color: var(--ink-3); margin-top: 8px; }

@media (max-width: 760px) {
  .film-tag { top: 8px; left: 9px; padding: 5px 9px; font-size: 9px; }
  /* 主影片在窄屏上横向可滚 —— 缩到 360px 宽的话手机那块只有 60px，
     字全糊成一团。宁可让它横着滚。 */
  .film-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .film-scroll > svg { width: 860px; max-width: none; }
  .film-hint { display: block; }
}

/* ══ 9. ★ 减少动态：整层定格 ═══════════════════════════════════════
   要点是定格在信息最全的那一帧，不是把元素藏起来：
   代码行全部写完、状态三个都点亮、审批卡在屏幕上、对勾画完。
   这一段之后，页面上一个像素都不会动。 */
@media (prefers-reduced-motion: reduce) {
  body::after, .spot { animation: none !important; }
  .js-rv .rv, .js-rv .rv[data-in] { opacity: 1 !important; transform: none !important; transition: none !important; }
  .card::after, .card:hover::after { animation: none !important; }
  .eyebrow::before { animation: none !important; opacity: 1; }
  .film-scan { display: none !important; }
  .film-tag i { animation: none !important; }

  .kf *, .kh *, .kg *, .kdc, .kd-cur, .kd-bar, .kd-file, .kt-tier, .kt-shield, .kt-deny {
    animation: none !important;
    transition: none !important;
  }
  /* 员工流程 / Agent 治理两段：定格在「全部走完」那一帧 */
  .kh-n, .kh-s, .kh-s4 { opacity: 1 !important; transform: none !important; }
  .kh-pkt { transform: translateX(1010px) !important; opacity: 1 !important; }
  .kh-check { stroke-dashoffset: 0 !important; }
  .kh-tap, .kg-tap { opacity: 0 !important; }
  .kg-req { opacity: 1 !important; }
  .kg-read { transform: translateX(1004px) !important; opacity: 0 !important; }
  .kg-write { transform: translateX(700px) !important; }
  .kg-gate { opacity: 1 !important; }
  .kg-gate-open { opacity: 0 !important; }
  .kg-phone, .kg-phone-wire, .kg-done, .kg-cur, .kg-read-mark { opacity: 1 !important; transform: none !important; }
  /* 定格帧 */
  .kf-line { transform: scaleX(1) !important; opacity: 1 !important; }
  /* 每一行都定格在写完的宽度上 —— 用 max-content 免得再写八条 */
  .kdc { width: 320px !important; }
  .kf-pkt { opacity: 1 !important; offset-distance: 62% !important; }
  .kf-run, .kf-sheet, .kf-send { opacity: 1 !important; transform: none !important; }
  .kf-prog { transform: scaleX(1) !important; }
  .kf-st, .kt-tier { opacity: 1 !important; }
  .kf-check { stroke-dashoffset: 0 !important; opacity: 1 !important; }
  .kf-glow, .kf-glow-b, .kf-hub, .kf-win { opacity: 1 !important; }
  .kf-ring { opacity: .28 !important; transform: scale(1.35) !important; }
  .kf-tap { opacity: 0 !important; }        /* 点击涟漪没有静态语义，收掉 */
  .kf-ev, .kf-build { opacity: 1 !important; transform: none !important; }
  /* 完成提示条和运行卡画在同一个位置（动起来时它们本来就不同时出现）。
     静态帧里两个都显示就会叠字 —— 留信息多的那个。 */
  .kf-done { opacity: 0 !important; }
  .kf-cnt { transform: scaleX(1) !important; }
  .kf-sweep { opacity: 0 !important; }      /* 扫描带同理：静态下它只是一块糊 */
  .kf-tc-1 { opacity: 1 !important; }       /* 时间码留第一格，四格叠着会糊成一团 */
  .kf-ph { opacity: 1 !important; }
  .kf-tc-2, .kf-tc-3, .kf-tc-4 { opacity: 0 !important; }
  .kf-caret, .kd-cur { opacity: 1 !important; }
  .kd-bar { transform: scaleX(1) !important; }
}

/* 输入框的灰色占位：只在"没在打字"的那段露出来 —— 
   打字那一秒它必须让开，否则两行字叠在一起。 */
.kf-ph { animation: phHide 14s step-end infinite; }
@keyframes phHide { 0%, 25% { opacity: 0; } 26%, 100% { opacity: 1; } }
