/* ============================================================
   HEPRGRYPH 动效层
   全部动效集中在此文件；prefers-reduced-motion 时整层失效。
   依赖 global.css 里的 --c-* 变量。
   ============================================================ */

/* 动效开关：motion.js 会写 html[data-motion] */
html[data-motion="off"] *,
html[data-motion="off"] *::before,
html[data-motion="off"] *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
}

/* 关掉动效时必须保证没有任何元素卡在出场动画的起始态（opacity:0 / 压扁）。
   上面的规则把时长压到 0.001ms 后 fill:both 会落到 100%，但若 JS 是在动画
   播到一半时才切开关，元素可能停在中间帧 —— 这里直接钉死终态。 */
html[data-motion="off"] .logo-hero {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
}

:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-back: cubic-bezier(0.34, 1.4, 0.64, 1);
  --grid-shift: 0px;
  --scroll-y: 0px;
}

/* ---------------- 1. 顶部加载进度条 ---------------- */

#ns-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 100%;
  /* 必须高于 sticky 顶栏(z-index:60)与扫描线层(9998)，否则被盖住 */
  z-index: 100000;
  pointer-events: none;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--c-green), var(--c-cyan), var(--c-magenta));
  box-shadow: 0 0 12px color-mix(in srgb, var(--c-green) 80%, transparent),
    0 0 3px color-mix(in srgb, var(--c-cyan) 90%, transparent);
  opacity: 0;
}
#ns-progress.run {
  animation: progressRun 0.95s var(--ease-out-expo) forwards;
}
@keyframes progressRun {
  0% { transform: scaleX(0); opacity: 1; }
  55% { transform: scaleX(0.72); opacity: 1; }
  85% { transform: scaleX(0.94); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

/* 离开页面时，进度条保持可见并向右推进 */
html.leaving #ns-progress {
  opacity: 1;
  transform: scaleX(0.9);
  animation: none;
  transition: transform 0.26s ease-out, opacity 0.2s 0.1s;
}

/* ---------------- 2. 页面进场 / 退场 ---------------- */

/* 退场：整体淡出并轻微上移 + 轻微失焦 */
html.leaving body {
  opacity: 0;
  transform: translateY(-8px) scale(0.995);
  filter: blur(1.5px);
  transition: opacity 0.26s ease-in, transform 0.26s ease-in, filter 0.26s ease-in;
  pointer-events: none;
}

/* 进场：各段依次浮入 */
.enter {
  animation: enterUp 0.62s var(--ease-out-expo) both;
}
@keyframes enterUp {
  from {
    opacity: 0;
    transform: translateY(14px);
    filter: blur(3px);
  }
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

/* 顶栏从上方落下 */
.topbar.enter {
  animation-name: enterDown;
}
@keyframes enterDown {
  from { opacity: 0; transform: translateY(-100%); }
  to { opacity: 1; transform: none; }
}

/* 终端窗口：轻微放大 + 扫描打开 */
.term.enter {
  animation: termIn 0.7s var(--ease-out-expo) both;
}
@keyframes termIn {
  0% {
    opacity: 0;
    transform: translateY(18px) scale(0.985);
    clip-path: inset(0 0 100% 0);
  }
  60% { opacity: 1; }
  100% {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 0 0);
  }
}

/* ---------------- 3. 输出行 / 卡片逐条入场 ---------------- */

.line {
  animation: lineIn 0.28s var(--ease-out-expo) both;
}
@keyframes lineIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* 终端输出块：从左侧滑入，带极淡的扫描感 */
.out-block:not(.line) {
  animation: outIn 0.34s var(--ease-out-expo) both;
}
@keyframes outIn {
  from { opacity: 0; transform: translateX(-6px); }
  to { opacity: 1; transform: none; }
}

/* 卡片：级联浮入 */
.card {
  animation: cardIn 0.5s var(--ease-out-expo) both;
}
@keyframes cardIn {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.99);
  }
  to { opacity: 1; transform: none; }
}

/* 滚动揭示：未进入视口前隐藏 */
.card[data-revealed="pending"],
.out-block[data-revealed="pending"],
.cmdlist[data-revealed="pending"],
.table-wrap[data-revealed="pending"],
blockquote[data-revealed="pending"],
.chips[data-revealed="pending"] {
  opacity: 0;
  transform: translateY(16px);
}
.card[data-revealed="pending"] { transform: translateY(16px) scale(0.99); }

.card.revealed,
.out-block.revealed,
.cmdlist.revealed,
.table-wrap.revealed,
blockquote.revealed,
.chips.revealed {
  animation: revealUp 0.6s var(--ease-out-expo) both;
}
@keyframes revealUp {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}

/* 卡片被点击跳转时的“弹射”反馈 */
.card-launch {
  animation: cardLaunch 0.26s ease-in both !important;
}
@keyframes cardLaunch {
  from { transform: translateX(0) scale(1); }
  to {
    transform: translateX(14px) scale(0.985);
    opacity: 0.35;
    filter: blur(1px);
  }
}

/* ---------------- 4. 卡片 hover 强化 ---------------- */

.card {
  position: relative;
  overflow: hidden;
}

/* 左侧强调条。
   之前的问题：这条 ::before 和卡片的 border-left 用同一个颜色叠在同一位置，
   静止时 scaleY(0.25) 会在实线中间露出一截更亮的短柱、且末端硬切，很难看。
   现在改为：
   - 底色只有 28% 不透明度（在 global.css 里），静止时是一条很淡的引导线；
   - ::before 从上往下生长（transform-origin 在顶部），hover 时铺满全高并点亮；
   - 比 border 略宽 1px，让"长出来"这件事看得见。 */
.card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: linear-gradient(
    to bottom,
    var(--accent, var(--c-green)),
    color-mix(in srgb, var(--accent, var(--c-green)) 65%, transparent)
  );
  transform: scaleY(0);
  transform-origin: 50% 0; /* 从顶部往下长 */
  opacity: 0.55;
  transition: transform 0.32s var(--ease-out-expo), opacity 0.25s ease-out,
    box-shadow 0.3s ease-out;
  pointer-events: none;
}
a.card:hover::before {
  transform: scaleY(1);
  opacity: 1;
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent, var(--c-green)) 75%, transparent);
}

/* 一道高光斜着扫过（仅在 hover 时跑一次） */
.card::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -60%;
  width: 45%;
  background: linear-gradient(
    100deg,
    transparent,
    color-mix(in srgb, var(--accent, var(--c-green)) 14%, transparent),
    transparent
  );
  transform: skewX(-18deg);
  opacity: 0;
  pointer-events: none;
}
a.card:hover::after {
  opacity: 1;
  animation: sweep 0.75s var(--ease-out-expo);
}
@keyframes sweep {
  from { left: -60%; }
  to { left: 130%; }
}

/* 标题在 hover 时轻微发光 */
a.card:hover .card-title {
  text-shadow: 0 0 12px var(--accent, var(--c-green));
}
.card-title {
  transition: text-shadow 0.2s;
}

/* 标签 hover 时上浮 */
.tag {
  transition: transform 0.18s var(--ease-out-back), border-color 0.18s, color 0.18s;
}
a.card:hover .tag {
  transform: translateY(-1px);
}

/* ---------------- 5. 命令按钮 ---------------- */

.chip {
  position: relative;
  overflow: hidden;
  transition: border-color 0.18s, background 0.18s, color 0.18s,
    transform 0.18s var(--ease-out-back), box-shadow 0.18s;
}
.chip:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 14px -6px var(--c-cyan);
}
.chip:active {
  transform: translateY(0) scale(0.97);
}
/* 点击涟漪 */
.chip.ripple::after {
  content: "";
  position: absolute;
  left: var(--rx, 50%);
  top: var(--ry, 50%);
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.5;
  animation: ripple 0.5s ease-out forwards;
  pointer-events: none;
}
@keyframes ripple {
  to { transform: scale(18); opacity: 0; }
}

/* ---------------- 6. 导航链接 ---------------- */

.topbar nav a {
  position: relative;
  transition: color 0.2s, text-shadow 0.2s;
}
.topbar nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.28s var(--ease-out-expo);
}
.topbar nav a:hover::after,
.topbar nav a[aria-current="page"]::after {
  transform: scaleX(1);
}

/* ---------------- 7. 光标 ---------------- */

.caret {
  display: inline-block;
  width: 9px;
  height: 1.05em;
  background: var(--c-green);
  box-shadow: 0 0 10px var(--c-green);
  animation: blink 1.05s steps(1) infinite;
  pointer-events: none;
}
@keyframes blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}
/* motion.js 接管后改用 transform 精确定位。
   关键：光标必须落在输入文本【之后】。
   left 对齐到 input 的左内边距起点，translateX 由脚本按实际文字宽度给出，
   这样不会压在第一个字上，也不会和末尾字形重叠。 */
.cmdline.has-caret .caret {
  position: absolute;
  left: 0;
  /* 与输入文本的视觉行高对齐：让光标竖直居中于 input 的行盒 */
  top: 0.28em;
  margin: 0;
  transition: transform 0.06s linear;
}
/* 输入框为空时不显示光标，避免压在 placeholder 文字上 */
.cmdline.has-caret.placeholder-shown .caret {
  opacity: 0;
}
.cmdline.typing .caret {
  animation: none;
  opacity: 1;
}

/* 提示符呼吸 */
.ps1 {
  animation: ps1Pulse 3.4s ease-in-out infinite;
}
@keyframes ps1Pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.82; }
}

/* ---------------- 8. 背景 ---------------- */

/* 网格随滚动轻微位移（视差） */
body::before {
  transform: translateY(var(--grid-shift));
  transition: transform 0.1s linear;
}

/* 顶部辉光缓慢呼吸 */
.term {
  position: relative;
}
.term::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 9px;
  pointer-events: none;
  background: linear-gradient(
    120deg,
    transparent 20%,
    color-mix(in srgb, var(--c-green) 9%, transparent) 50%,
    transparent 80%
  );
  background-size: 220% 220%;
  animation: termGlow 7s ease-in-out infinite;
  opacity: 0.75;
}
@keyframes termGlow {
  0%, 100% { background-position: 0% 0%; }
  50% { background-position: 100% 100%; }
}

/* 扫描线：亮度闪烁 + 缓慢下移，模拟 CRT 刷新 */
@keyframes flicker {
  0%, 96%, 100% { opacity: 1; }
  97% { opacity: 0.86; }
  98% { opacity: 0.95; }
}
body::after {
  animation: flicker 6s steps(60) infinite, scanDrift 9s linear infinite;
}
@keyframes scanDrift {
  from { background-position-y: 0; }
  to { background-position-y: 120px; }
}

/* ---------------- 9. 3D 字符 LOGO 标题 ----------------
 * 首页的 FIGlet <pre class="ascii"> 已换成真正 3D 的 <pre class="logo3d">。
 * 关键帧名保留 asciiGlow（测试和历史都按这个名字找），但选择器换成 .logo3d，
 * 否则这条呼吸辉光就永远挂在一个不存在的元素上了。
 *
 * 注意：LOGO 现在住在终端卡片**外面**的 .logo-hero 里（build.mjs 的 terminalShell）。
 * 出场动画挂在外层 .logo-hero 上（它独占 opacity/transform），
 * 呼吸辉光留在 .logo3d 上（它独占 text-shadow）—— 拆成两层是因为
 * asciiGlow 自己也在动 opacity，两条动画同抢一个属性会互相覆盖。
 */
.logo3d {
  animation: asciiGlow 4.5s ease-in-out infinite;
}
@keyframes asciiGlow {
  0%, 100% {
    text-shadow: 0 0 16px color-mix(in srgb, var(--c-green) 45%, transparent);
  }
  50% {
    text-shadow: 0 0 26px color-mix(in srgb, var(--c-green) 70%, transparent),
      0 0 42px color-mix(in srgb, var(--c-green) 30%, transparent);
  }
}

/* ---- 9b. LOGO 出场动画 ----
 * 卡片从侧面转正的错觉：先极度压扁（rotateY 接近 90° 的视觉等效 = scaleX 很小），
 * 再展开、放大、落定，同时从模糊里聚焦出来。
 * 用 scaleX 而不是 rotateY 是因为这是一个 <pre> 字符块，rotateY 会触发
 * 子像素重采样把等宽字符糊掉；scaleX 只做水平缩放，字符边缘清楚。
 *
 * **由类名触发，不是自动播放**：LOGO 在静态 HTML 里，页面一加载就在，
 * 但那时 BIOS 开机动画还在跑；若让它自动播，到交接时早演完了，用户根本看不见。
 * 所以默认处于"待机"（不可见、不播），由 handoffToHome() 加 .logo-enter 才开演。
 */

/* 高度过渡：LOGO 从"不占位"展开到自然高度，终端卡片随之被推下去。
   时长取的比 logoEnter(1.5s) 短一点、且用同一根 ease-out-expo —— 这条曲线
   前段极快，展开到 93% 只需 0.42s，而 logoEnter 恰好在 0.42s（28% 关键帧）
   才达到 opacity:1，也就是说"画面变清楚时，位置早就让出来了"，
   展开过程不会被 overflow 裁掉太多东西。
   自然高度由 terminal.js 量出来写成内联 px（过渡完再交还给 auto），
   所以这里不需要知道 LOGO 到底多高。 */
.logo-slot {
  transition: height 1.05s var(--ease-out-expo);
}

.logo-hero {
  opacity: 0;
  transform: scaleX(0.04) scale(0.82);
  filter: blur(7px);
}

.logo-hero.logo-enter {
  animation: logoEnter 1.5s var(--ease-out-expo) both;
}

/* 兜底：脚本没跑起来（禁用 JS / 报错 / 脚本加载失败）时，
   绝不能让 LOGO 永远停在 opacity:0 —— 那等于首页没有标题。
   motion.js 一执行就会给 <html> 加 .js-on，所以这条只在"JS 没成功启动"
   时命中，把终态直接钉死。 */
html:not(.js-on) .logo-hero {
  opacity: 1;
  transform: none;
  filter: none;
}

@keyframes logoEnter {
  0% {
    opacity: 0;
    transform: scaleX(0.04) scale(0.82);
    filter: blur(7px);
  }
  28% {
    opacity: 1;
    transform: scaleX(0.10) scale(0.88);
    filter: blur(4px);
  }
  62% {
    opacity: 1;
    transform: scaleX(0.86) scale(1.045);
    filter: blur(0.6px);
  }
  100% {
    opacity: 1;
    transform: scaleX(1) scale(1);
    filter: blur(0);
  }
}

/* ---------------- 10. 标题装饰 ---------------- */

h2::before,
h3::before {
  animation: hashIn 0.4s var(--ease-out-expo) both;
}
@keyframes hashIn {
  from { opacity: 0; transform: translateX(-8px); }
  to { opacity: 1; transform: none; }
}

/* 强调查看时的高亮扫过 */
strong {
  background-image: linear-gradient(
    120deg,
    transparent 0%,
    color-mix(in srgb, var(--c-green) 18%, transparent) 50%,
    transparent 100%
  );
  background-size: 220% 100%;
  background-repeat: no-repeat;
  animation: strongSweep 5s ease-in-out infinite;
}
@keyframes strongSweep {
  0%, 100% { background-position: 120% 0; }
  60% { background-position: -20% 0; }
}

/* ---------------- 11. 状态标签脉冲 ---------------- */

.status {
  animation: statusPulse 3s ease-in-out infinite;
}
@keyframes statusPulse {
  0%, 100% { opacity: 0.75; }
  50% { opacity: 1; }
}

/* ---------------- 12. 进度条 / 标签分布（stats 命令） ---------------- */

.bar {
  display: inline-block;
  overflow: hidden;
  vertical-align: baseline;
}
.bar-fill {
  display: inline-block;
  animation: barGrow 0.85s var(--ease-out-expo) both;
  transform-origin: 0 50%;
}
@keyframes barGrow {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}

/* 标签分布：每行依次滑入，条形图按百分比生长 */
.tagbars {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 6px 0 14px;
}
.tagbar {
  display: grid;
  grid-template-columns: minmax(72px, 130px) 1fr minmax(28px, auto);
  align-items: center;
  gap: 10px;
  animation: tagbarIn 0.45s var(--ease-out-expo) both;
}
@keyframes tagbarIn {
  from { opacity: 0; transform: translateX(-10px); }
  to { opacity: 1; transform: none; }
}
.tagbar .tb-name {
  color: var(--c-text);
  opacity: 0.85;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tagbar .tb-track {
  height: 10px;
  background: color-mix(in srgb, var(--c-line) 60%, transparent);
  border-radius: 2px;
  overflow: hidden;
}
.tagbar .tb-fill {
  display: block;
  height: 100%;
  width: var(--pct, 0%);
  background: linear-gradient(90deg, var(--c-green-dim), var(--c-green));
  border-radius: 2px;
  transform-origin: 0 50%;
  animation: tbGrow 0.95s var(--ease-out-expo) both;
  animation-delay: inherit;
  box-shadow: 0 0 8px color-mix(in srgb, var(--c-green) 45%, transparent);
}
@keyframes tbGrow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
.tagbar .tb-num {
  color: var(--c-cyan);
  text-align: right;
  font-size: 0.85em;
}

/* 标签索引：可点击的标签胶囊，依次浮入 */
.tagindex {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 6px 0 16px;
}
.tagchip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--c-line);
  border-radius: 999px;
  padding: 1px 10px;
  font-size: 0.82rem;
  color: var(--c-muted);
  border-bottom-width: 1px;
  animation: tagchipIn 0.4s var(--ease-out-back) both;
  transition: color 0.18s, border-color 0.18s, transform 0.18s var(--ease-out-back),
    background 0.18s, box-shadow 0.18s;
}
@keyframes tagchipIn {
  from { opacity: 0; transform: translateY(6px) scale(0.9); }
  to { opacity: 1; transform: none; }
}
.tagchip .tc-name { color: inherit; }
.tagchip .tc-n {
  color: var(--c-green-dim);
  font-size: 0.75em;
  border-left: 1px solid var(--c-line);
  padding-left: 6px;
}
.tagchip:hover {
  color: var(--c-bg);
  background: var(--c-cyan);
  border-color: var(--c-cyan);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px -5px var(--c-cyan);
  text-decoration: none;
}
.tagchip:hover .tc-n {
  color: var(--c-bg);
  border-left-color: color-mix(in srgb, var(--c-bg) 40%, transparent);
}

/* ---------------- 12b. 启动动画细节 ---------------- */

/* 终端通电：短暂高亮 + 轻微抖动，模拟 CRT 启动 */
.term.power-on {
  animation: powerOn 0.42s ease-out;
}
@keyframes powerOn {
  0% {
    filter: brightness(2.6) saturate(0.4);
    transform: scaleY(0.004) scaleX(1.02);
  }
  35% {
    filter: brightness(1.5);
    transform: scaleY(0.06) scaleX(1.01);
  }
  70% { filter: brightness(1.12); transform: scaleY(1.01); }
  100% { filter: none; transform: none; }
}

/* 每行 OK 点亮 */
.ok.boot-ok {
  animation: okBlip 0.4s ease-out both;
  animation-delay: inherit;
}
@keyframes okBlip {
  0% { opacity: 0; text-shadow: 0 0 18px var(--c-green); }
  45% { opacity: 1; text-shadow: 0 0 22px var(--c-green); }
  100% { opacity: 1; text-shadow: 0 0 10px color-mix(in srgb, var(--c-green) 50%, transparent); }
}

/* 终端输出块入场（命令结果） */
.out-enter {
  animation: outEnter 0.36s var(--ease-out-expo) both;
}
@keyframes outEnter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* 命令回显行：像真的被敲进去 */
.echo {
  animation: echoIn 0.22s ease-out both;
}
@keyframes echoIn {
  from { opacity: 0; transform: translateX(-4px); }
  to { opacity: 1; transform: none; }
}

/* ---------- 开屏 → 首页 的交接 ----------
   之前是直接 clearTerm() 再整块塞入首页：元素数 8→4、高度一帧内跳
   493px，还触发自动滚动，观感就是"抽一下"。
   现在分三步：BIOS 逐行淡出 → 高度平滑过渡 → 首页分层浮入。 */

/* 第 1 步：BIOS 行自下而上淡出并轻微上移，像被卷走 */
.term-out.handoff-out > .out-block {
  animation: handoffOut 0.32s ease-in both;
}
@keyframes handoffOut {
  from { opacity: 1; transform: none; filter: blur(0); }
  to { opacity: 0; transform: translateY(-6px); filter: blur(1.5px); }
}
/* 最后一行先走，形成自下而上的收束感 */
.term-out.handoff-out > .out-block:nth-last-child(1) { animation-delay: 0ms; }
.term-out.handoff-out > .out-block:nth-last-child(2) { animation-delay: 28ms; }
.term-out.handoff-out > .out-block:nth-last-child(3) { animation-delay: 56ms; }
.term-out.handoff-out > .out-block:nth-last-child(n + 4) { animation-delay: 84ms; }

/* 第 2 步：首页内容从低亮浮现，而不是直接弹出 */
.term-out.handoff-in > .out-block {
  animation: handoffIn 0.5s var(--ease-out-expo) both;
}
@keyframes handoffIn {
  0% {
    opacity: 0;
    transform: translateY(10px);
    filter: brightness(0.35) blur(1px);
  }
  55% { filter: brightness(1.18) blur(0); }
  100% { opacity: 1; transform: none; filter: none; }
}

/* 第 3 步：容器高度平滑变化，避免一帧内暴增导致的滚动跳变。
   注意必须写 height（脚本锁的是 height，不是 min-height），
   属性名对不上就完全不会过渡。 */
.term-body.handoff-height {
  transition: height 0.4s var(--ease-out-expo);
}

/* 交接期间先隐去提示行，减少视觉噪声 */
.prompt-line.handoff-hidden {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease-out;
}

/* ---------------- 13. 404 / 空状态 ---------------- */

.flicker-text {
  animation: textFlicker 2.6s steps(1) infinite;
}
@keyframes textFlicker {
  0%, 92%, 100% { opacity: 1; }
  93% { opacity: 0.35; }
  94% { opacity: 1; }
  96% { opacity: 0.5; }
  97% { opacity: 1; }
}

/* ---------------- 14. 页脚 ---------------- */

.footer {
  transition: opacity 0.3s;
}

/* ---------------- 15. 终端窗口打字指示 ---------------- */

.term-bar .title::after {
  content: "▍";
  color: var(--c-green);
  animation: blink 1.05s steps(1) infinite;
  margin-left: 2px;
  opacity: 0.8;
}

/* ---------------- 16. 主题切换过渡 ---------------- */

body,
.term,
.card,
.tag,
.chip {
  transition: background-color 0.32s ease, border-color 0.32s ease, color 0.32s ease;
}

/* ---------------- 无障碍 ---------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
  .card[data-revealed="pending"],
  .out-block[data-revealed="pending"],
  .cmdlist[data-revealed="pending"],
  .table-wrap[data-revealed="pending"],
  blockquote[data-revealed="pending"],
  .chips[data-revealed="pending"] {
    opacity: 1;
    transform: none;
  }
  #ns-progress { display: none; }
  body::after { display: none; }
}

/* ---------------- 打印 ---------------- */

@media print {
  #ns-progress,
  .term::after { display: none !important; }
  .card[data-revealed="pending"] { opacity: 1 !important; transform: none !important; }
}
