/* 拉丁字母统一走 JBMono Var（unicode-range: U+0000–00FF），中文仍落回 --mono 栈。
   想改成自托管：把 woff2 放到 fonts/ 下，第一条 src 就会命中。 */
@font-face {
  font-family: "JBMono Var";
  src: url("fonts/JetBrainsMono-wght.woff2") format("woff2"),
       url("https://cdn.jsdelivr.net/npm/@fontsource-variable/jetbrains-mono@5/files/jetbrains-mono-latin-wght-normal.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF;
}

/* 首屏欢迎语 — Noto Serif SC（OFL-1.1），只作用于 .hero__title。
   按欢迎语用字分片加载；自托管见 fonts/ 下同名 woff2。 */
@font-face {
  font-family: "Noto Serif SC";
  src: url("fonts/noto-serif-sc-113-wght-normal.woff2") format("woff2-variations"),
       url("https://cdn.jsdelivr.net/npm/@fontsource-variable/noto-serif-sc@5.3.0/files/noto-serif-sc-113-wght-normal.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+91ce;
}

@font-face {
  font-family: "Noto Serif SC";
  src: url("fonts/noto-serif-sc-116-wght-normal.woff2") format("woff2-variations"),
       url("https://cdn.jsdelivr.net/npm/@fontsource-variable/noto-serif-sc@5.3.0/files/noto-serif-sc-116-wght-normal.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+8fce, U+9879;
}

@font-face {
  font-family: "Noto Serif SC";
  src: url("fonts/noto-serif-sc-117-wght-normal.woff2") format("woff2-variations"),
       url("https://cdn.jsdelivr.net/npm/@fontsource-variable/noto-serif-sc@5.3.0/files/noto-serif-sc-117-wght-normal.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+6b22, U+539f, U+76ee, U+96c6;
}

@font-face {
  font-family: "Noto Serif SC";
  src: url("fonts/noto-serif-sc-118-wght-normal.woff2") format("woff2-variations"),
       url("https://cdn.jsdelivr.net/npm/@fontsource-variable/noto-serif-sc@5.3.0/files/noto-serif-sc-118-wght-normal.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+5230, U+6765;
}

@font-face {
  font-family: "Noto Serif SC";
  src: url("fonts/noto-serif-sc-119-wght-normal.woff2") format("woff2-variations"),
       url("https://cdn.jsdelivr.net/npm/@fontsource-variable/noto-serif-sc@5.3.0/files/noto-serif-sc-119-wght-normal.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+7684;
}

/* 注册成 <number> 才能被过渡插值，直接过渡 font-variation-settings 不够稳 */
@property --wght {
  syntax: "<number>";
  initial-value: 600;
  inherits: false;
}

:root {
  --mono: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas,
          "Liberation Mono", "Courier New", "Sarasa Mono SC",
          "Noto Sans Mono CJK SC", monospace;

  --hero-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", serif;

  --bg: #17110b;
  --fg: #d4c4a4;
  --fg-bright: #e8dcc0;
  --fg-dim: #8a7d68;
  --line: #3b2e1d;
  --tint: #211a11;
  --stroke: 3px;

  --enter: 30px;
  --dur: .45s;
  --text-scale: 1.3;
  --detail-safe: calc((1rem + env(safe-area-inset-bottom, 0px)) * 2);
  --cli-font: inherit;
  --cli-lh: 1.8;
  --cli-ls: .01em;
  --otokit-icon-bg: var(--tint);
  --otokit-ring-hi: var(--fg-bright);
  --otokit-ring-lo: var(--fg-dim);
  --otokit-icon-mark: var(--fg-bright);
  --thewarp-bg-lo: #202020;
  --thewarp-bg-mid: #414141;
  --thewarp-bg-hi: #696969;
  --thewarp-icon-mark: #fff;
}

:root[data-theme="dark"] { color-scheme: dark; }

:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f1ecda;
  --fg: #4b3720;
  --fg-bright: #2b1d0d;
  --fg-dim: #8b7551;
  --line: #b8a480;
  --tint: #e4dcc4;
  --stroke: 3px;
  --otokit-icon-bg: var(--fg-bright);
  --otokit-ring-hi: var(--line);
  --otokit-ring-lo: var(--fg-dim);
  --otokit-icon-mark: var(--bg);
  --thewarp-bg-lo: var(--fg-bright);
  --thewarp-bg-mid: color-mix(in srgb, var(--fg) 35%, var(--fg-dim) 65%);
  --thewarp-bg-hi: var(--fg-dim);
  --thewarp-icon-mark: var(--bg);
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: "JBMono Var", var(--mono);
  font-size: clamp(19.5px, .58rem + .5vw, 22px);
  font-weight: 450;
  line-height: 1.8;
  letter-spacing: .02em;
  transition: background .25s ease, color .25s ease;
}

/* 英文界面字走 JBMono 400；须显式 font-family，部分手机对继承 + wght 会误落系统字 */
.prompt,
.topbar,
.pager,
.btn,
.boot,
.foot,
.proj__host,
.proj__idx,
.links__key {
  font-family: "JBMono Var", var(--mono);
  font-weight: 400;
  font-variation-settings: "wght" 400;
}

::selection { background: var(--fg); color: var(--bg); }

:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

/* ---------- 顶栏 ---------- */

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem clamp(1.25rem, 5vw, 3.5rem);
  background: var(--bg);
  font-size: calc(.84rem * var(--text-scale));
  color: var(--fg-dim);
}

.topbar__theme {
  font: inherit;
  color: var(--fg-dim);
  background: none;
  border: 0;
  padding: .05rem .35rem;
  cursor: pointer;
}

.topbar__theme:hover,
.topbar__theme:focus-visible { background: var(--fg); color: var(--bg); }

/* ---------- 右侧分屏指示 ---------- */

.pager {
  position: fixed;
  top: 50%;
  right: clamp(1rem, 3vw, 2.4rem);
  z-index: 20;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .6rem;
  font-size: calc(.72rem * var(--text-scale));
  color: var(--fg-dim);
}

.pager__dot {
  width: 10px;
  height: 10px;
  padding: 0;
  border: var(--stroke) solid var(--fg-dim);
  background: none;
  cursor: pointer;
}

.pager__dot:hover { border-color: var(--fg); }
.pager__dot.is-on { background: var(--fg); border-color: var(--fg); }

.pager__count {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity .22s ease, transform .22s ease, visibility 0s;
}

/* 900–1213px 展开详情时 [02/03] 会压住 SVG 框，圆点不受影响故保留 */
@media (min-width: 900px) and (max-width: 1213px) {
  body.is-deck:has(.projects-inner.is-detail) .pager__count {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-0.35em);
    pointer-events: none;
    transition: opacity .22s ease, transform .22s ease, visibility 0s linear .22s;
  }
}

/* ---------- 分屏容器 ---------- */

.panel { padding: 7rem clamp(1.25rem, 6vw, 4rem) 5rem; }
.panel__inner { width: min(100%, 60rem); margin: 0 auto; }

/* ---------- 首屏 ---------- */

.boot {
  margin: 0;
  color: var(--fg-dim);
  font-size: calc(.82rem * var(--text-scale));
  letter-spacing: .03em;
  white-space: nowrap;
}
.ok { color: var(--fg-bright); }

.prompt { margin: .35rem 0 0; }
.prompt__sign { color: var(--fg-dim); }

.projects__cmd,
.panel[data-title="home"] > .panel__inner > .prompt,
.hero__cmdline,
.hero__echo,
.hero__out {
  font-size: var(--cli-font);
  letter-spacing: var(--cli-ls);
  line-height: var(--cli-lh);
}

.hero__title {
  margin: 1.4rem 0 .5rem;
  min-height: 1.35em;
  font-family: var(--hero-serif);
  font-size: clamp(calc(1.55rem * var(--text-scale)), 4.4vw, calc(3rem * var(--text-scale)));
  font-weight: 800;
  font-variation-settings: "wght" 800;
  line-height: 1.35;
  letter-spacing: .02em;
  color: var(--fg-bright);
}

.hero__sub {
  margin: 0;
  min-height: 1.8em;
  font-size: clamp(calc(.86rem * var(--text-scale)), 1.5vw, calc(1.08rem * var(--text-scale)));
  font-variation-settings: normal;
  letter-spacing: .06em;
  color: var(--fg-dim);
}

.hero__term {
  margin: 2.2rem 0 0;
  min-height: 1.8em;
}
.hero__cmdline {
  margin: 0;
  min-height: 1.8em;
}
.hero__cmdline .prompt__sign::after,
.hero__echo .prompt__sign::after { content: " "; }
.hero__cmdline.is-submitted .caret,
.hero__cmdline.is-submitted .hero__suggest { display: none; }
.hero__cmdline.is-merged .prompt__sign,
.hero__cmdline.is-merged #heroInput,
.hero__cmdline.is-submitted.is-merged .hero__suggest,
.hero__cmdline.is-merged .caret { display: none; }
.hero__full[hidden] { display: none; }
.hero__cmdline.is-merged .hero__full { display: inline; }
.hero__cmdline.is-ominous .hero__full { color: var(--fg-bright); font-weight: 500; }
.hero__suggest {
  color: var(--fg-dim);
  opacity: .42;
}
.hero__out {
  margin: .45rem 0 0;
  padding: 0;
  font-family: inherit;
  font-weight: inherit;
  font-variation-settings: inherit;
  color: var(--fg-dim);
  white-space: pre-wrap;
  word-break: break-word;
}
.hero__echo { margin: .45rem 0 0; }
.hero__hint {
  margin: .6rem 0 0;
  min-height: 1.8em;
}

.hero__term.is-standby,
.hero__hint.is-standby {
  visibility: hidden;
  pointer-events: none;
}

body.is-deck .panel[data-title="home"] .panel__inner,
body.is-flow .panel[data-title="home"] .panel__inner {
  transform: translateY(-2.5rem);
}

.hint {
  font: inherit;
  font-size: calc(.82rem * var(--text-scale));
  letter-spacing: .12em;
  color: var(--fg-dim);
  background: none;
  border: 0;
  padding: .05rem .35rem;
  cursor: pointer;
}

.hint:hover, .hint:focus-visible { background: var(--fg); color: var(--bg); }

.caret {
  display: inline-block;
  width: .6em;
  height: 1.05em;
  background: currentColor;
  vertical-align: -.18em;
  animation: blink 1.05s steps(1, end) infinite;
}

.is-typing::after {
  content: "";
  display: inline-block;
  width: .6em;
  height: 1.05em;
  background: currentColor;
  vertical-align: -.18em;
  margin-left: .1em;
  animation: blink 1.05s steps(1, end) infinite;
}

@keyframes blink { 50% { opacity: 0; } }

/* ---------- 项目 ---------- */

.btn {
  font: inherit;
  font-size: calc(.82rem * var(--text-scale));
  letter-spacing: .1em;
  color: var(--fg-dim);
  background: none;
  border: 0;
  padding: .15rem .45rem;
  text-decoration: none;
  cursor: pointer;
  transition: background .16s ease, color .16s ease;
}

.btn:hover, .btn:focus-visible { background: var(--fg); color: var(--bg); outline: none; }

.projects-inner { position: relative; }

.projects {
  list-style: none;
  margin: 1.8rem 0 0;
  padding: 0;
  display: grid;
  gap: 1rem;
}

.proj {
  padding: 1rem 1.2rem;
  border-left: var(--stroke) solid var(--line);
  transition: background .18s ease, border-color .18s ease;
}

/* 悬停只改底色和左边框。不能改标题颜色：点「详情」时鼠标正悬在卡片上，
   而详情层里那份标题没有被悬停，交接那一帧会看到文字暗一档。 */
.proj-item:hover .proj { background: var(--tint); border-color: var(--fg); }

.proj__lead {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .25em .4em;
  font-size: calc(1.05rem * var(--text-scale));
}

.proj__idx, .proj__host, .proj__feat { color: var(--fg-dim); }
.proj__idx { flex-shrink: 0; line-height: 1; }
.proj__name {
  --wght: 600;
  margin: 0;
  font-family: "JBMono Var", var(--mono);
  font-size: 1.18em;
  font-weight: 600;
  font-variation-settings: "wght" var(--wght);
  letter-spacing: .06em;
  line-height: 1;
}
.proj__host { font-size: .8em; letter-spacing: .06em; }

.proj__body { margin-top: .2rem; }
.proj__desc {
  margin: 0;
  font-size: calc(.9rem * var(--text-scale));
  font-weight: 500;
  font-variation-settings: normal;
}
.proj__feat {
  margin: 0;
  font-size: calc(.76rem * var(--text-scale));
  font-variation-settings: normal;
}

.proj__actions { display: flex; flex-wrap: wrap; gap: .2rem .6rem; margin: .7rem 0 0 -.45rem; }

/* ---------- 项目详情 ----------
   收起态的规则同时定义了「收起」这一程的时序，
   展开态的规则定义「展开」这一程，两程各约 1.1s。 */

.detail {
  position: absolute;
  inset: 0;
  padding-top: var(--cmd-h, 3.2rem);
  display: flex;
  flex-direction: column;
  visibility: hidden;
  pointer-events: none;
  opacity: 1;
  transition: visibility 0s linear 1100ms, opacity var(--dur) ease;
}

.detail.is-open {
  visibility: visible;
  pointer-events: auto;
  transition: visibility 0s linear 0s, opacity var(--dur) ease;
}

/* 未展开的详情：跟着本屏一起藏，不需要位移 */
body.is-deck .panel:not(.is-active) .detail:not(.is-open) {
  visibility: hidden;
  pointer-events: none;
  opacity: 0;
  transition: visibility 0s linear var(--dur), opacity var(--dur) ease;
}

/* 展开中的详情：翻屏时跟 .anim 用同一套位移 + 淡入淡出。
   visibility 的延迟必须在每条规则里单独写全，不能用 transition-delay 一把盖：
   盖了会让进场先隐藏 130ms（看起来像整块闪掉一下），
   离场立刻隐藏（看起来像直接消失，来不及淡出）。 */
body.is-deck .panel.is-active .detail.is-open {
  visibility: visible;
  pointer-events: auto;
  opacity: 1;
  transform: none;
  transition: visibility 0s linear 0s,
              opacity var(--dur) ease 130ms,
              transform var(--dur) ease 130ms;
}

body.is-deck .panel:not(.is-active) .detail.is-open {
  visibility: hidden;
  pointer-events: none;
  opacity: 0;
  transition: visibility 0s linear var(--dur),
              opacity var(--dur) ease,
              transform var(--dur) ease;
}

body.is-deck .panel.is-below .detail.is-open { transform: translateY(var(--enter)); }
body.is-deck .panel.is-above .detail.is-open { transform: translateY(calc(var(--enter) * -1)); }

.projects-inner.no-anim,
.projects-inner.no-anim * { transition: none !important; }
.projects-inner.no-anim .anim { transform: none !important; opacity: 1 !important; }

.detail .detail__head {
  font-size: var(--head-fs, 1rem);
  align-items: center;
  gap: .2em .35em;
  transition: font-size 480ms ease 590ms;
}

.detail.is-open .detail__head {
  font-size: clamp(calc(1.55rem * var(--text-scale)), 3.3vw, calc(2.15rem * var(--text-scale)));
  transition-delay: 185ms;
}

/* 标题行不再作为刚体整体平移，而是每个元素各自被钉到卡片上的对应元素。
   解绑之后行内部的度量（徽标宽度、gap）不需要和 .proj__lead 保持一致，
   图标左对齐和收紧后的间距可以随意调，都不会在交接那一帧跳位。 */

.detail__badge {
  transform: translate(var(--bx, 0px), var(--by, 0px));
  transition: transform 480ms ease 590ms;
}

.detail.is-open .detail__badge { transform: none; transition-delay: 185ms; }

.detail__name {
  transform: translate(var(--nx, 0px), var(--ny, 0px));
  transition: transform 480ms ease 590ms, --wght 480ms ease 590ms;
}

.detail.is-open .detail__name {
  --wght: 800;
  transform: none;
  transition-delay: 185ms;
}

.detail__host {
  transform: translate(var(--hx, 0px), var(--hy, 0px));
  transition: transform 480ms ease 590ms;
}

.detail.is-open .detail__host { transform: none; transition-delay: 185ms; }

.detail__body {
  width: var(--body-w-from, auto);
  transform: translate(var(--bdx, 0px), var(--bdy, 0px));
  transition: transform 480ms ease 590ms, margin-top 480ms ease 590ms, width 480ms ease 590ms;
}

.detail.is-open .detail__body {
  width: 100%;
  transform: none;
  margin-top: .85rem;
  transition-delay: 185ms;
}

.detail__badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  flex-shrink: 0;
  width: 1.45em;
  height: 1.45em;
  line-height: 1;
}
.detail__num {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  transition: opacity 370ms ease 590ms;
}
.detail.is-open .detail__num { opacity: 0; transition-delay: 295ms; }

.detail__icon {
  position: absolute;
  top: 50%;
  left: 0;
  width: 1.45em;
  height: 1.45em;
  border-radius: .3em;
  background: var(--fg);
  opacity: 0;
  transform: translateY(-50%) scale(.55);
  transition: opacity 370ms ease 590ms, transform 370ms ease 590ms;
}

.detail.is-open .detail__icon {
  opacity: 1;
  transform: translateY(-50%) scale(1);
  transition-delay: 295ms;
}

.detail__icon--otokit {
  background: var(--otokit-icon-bg);
  box-shadow: inset 0 0 0 1px var(--line);
  transition: opacity 370ms ease 590ms, transform 370ms ease 590ms,
              background .25s ease, box-shadow .25s ease;
}

.detail__icon--thewarp {
  background: none;
  transition: opacity 370ms ease 590ms, transform 370ms ease 590ms;
}

.detail__icon--thewarp .detail__icon-bg {
  transition: fill .25s ease;
}

.detail__icon--thewarp .detail__icon-mark {
  fill: var(--thewarp-icon-mark);
  transition: fill .25s ease;
}

.detail__icon-svg {
  display: block;
  width: 100%;
  height: 100%;
}

.detail__icon-ring { fill: url(#otokit-ring); }

.detail__icon-mark {
  fill: var(--otokit-icon-mark);
  transition: fill .25s ease;
}

.detail__main { position: relative; flex: 1; min-height: 0; margin-top: 1.4rem; }

.detail__box {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 0;
  overflow: hidden;
  border: var(--stroke) solid var(--line);
  border-radius: 0;
  opacity: 0;
  transform: scaleX(0);
  transition: height 370ms ease 0ms, border-radius 370ms ease 0ms,
              transform 240ms ease 370ms, opacity 130ms ease 610ms;
}

.detail.is-open .detail__box {
  height: 100%;
  border-radius: 15px;
  opacity: 1;
  transform: none;
  transition: opacity 150ms ease 515ms, transform 240ms ease 515ms,
              height 370ms ease 735ms, border-radius 370ms ease 735ms;
}

.detail__stage { height: 100%; }

/* ---------- OTOKiT 访达动画 ---------- */

.finder-stage {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--tint);
}

.finder-stage__layer {
  position: absolute;
  left: 0;
  top: 0;
  width: 640px;
  height: 419px;
  transform-origin: 0 0;
  will-change: transform;
  opacity: 0;
  transition: opacity 320ms ease;
}

.finder-stage.is-ready .finder-stage__layer {
  opacity: 1;
}

.fx-win {
  width: 640px;
  height: 419px;
  background: #fff;
  border: 0.5px solid #d8d8d8;
  border-radius: 10px;
  overflow: hidden;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  position: relative;
  user-select: none;
  color: #333;
}

.fx-win__titlebar {
  height: 32px;
  background: #ebebeb;
  display: flex;
  align-items: center;
  padding: 0 12px;
  gap: 8px;
  border-bottom: 0.5px solid #d8d8d8;
}

.fx-win__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex-shrink: 0;
}

.fx-win__dot--close { background: #ff5f57; }
.fx-win__dot--min { background: #ffbd2e; }
.fx-win__dot--max { background: #28c840; }

.fx-win__title {
  flex: 1;
  text-align: center;
  font-size: 12px;
  color: #6e6e6e;
  margin-right: 56px;
}

.fx-win__toolbar {
  height: 38px;
  background: #f7f7f7;
  display: flex;
  align-items: center;
  padding: 0 12px;
  gap: 12px;
  border-bottom: 0.5px solid #e2e2e2;
  color: #9a9a9a;
}

.fx-win__toolbar-spacer { flex: 1; }

.fx-win__search {
  width: 104px;
  height: 21px;
  border-radius: 6px;
  background: #eaeaea;
  display: flex;
  align-items: center;
  padding: 0 8px;
  font-size: 11px;
  color: #adadad;
  gap: 4px;
}

.fx-win__path {
  height: 29px;
  background: #f1f1f1;
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-size: 12px;
  color: #5a5a5a;
  gap: 5px;
  border-bottom: 0.5px solid #e2e2e2;
}

.fx-win__pathchev {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  opacity: 1;
  transition: opacity .3s ease;
}

.fx-win__body {
  display: flex;
  height: 320px;
}

.fx-win__sidebar {
  width: 150px;
  background: #f8f8f8;
  border-right: 0.5px solid #e2e2e2;
  padding: 12px 8px;
  font-size: 12px;
  color: #6e6e6e;
  flex-shrink: 0;
}

.fx-win__sidebar-label {
  font-size: 11px;
  color: #a5a5a5;
  margin: 2px 6px 4px;
}

.fx-win__sidebar-label + .fx-win__sidebar-label { margin-top: 10px; }

.fx-win__sidebar-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px;
  border-radius: 5px;
}

.fx-win__sidebar-item.is-active {
  background: #d8e7fb;
  color: #1a5fc4;
}

.fx-win__content {
  flex: 1;
  position: relative;
  background: #fff;
  overflow: hidden;
}

.fx-selbox {
  position: absolute;
  left: 22px;
  top: 22px;
  width: 72px;
  height: 72px;
  border-radius: 6px;
  background: #cfe2ff;
  opacity: 0;
  transition: opacity .12s ease;
}

.fx-folder {
  position: absolute;
  left: 22px;
  top: 26px;
  width: 72px;
  text-align: center;
  opacity: 0;
  transform: scale(.5);
  transition: opacity .25s ease, transform .25s ease;
}

.fx-folder svg {
  margin: 0 auto;
  display: block;
}

.fx-label {
  margin-top: 4px;
  display: inline-block;
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 12px;
  color: #333;
  background: transparent;
  border: 1px solid transparent;
}

.fx-caret {
  display: none;
  border-left: 1px solid #333;
  margin-left: 1px;
}

.fx-menu {
  position: absolute;
  left: 58px;
  top: 60px;
  width: 168px;
  background: #fbfbfb;
  border: 0.5px solid #d4d4d4;
  border-radius: 8px;
  padding: 4px;
  font-size: 12.5px;
  color: #333;
  opacity: 0;
  transform: scale(.95);
  transform-origin: top left;
  transition: opacity .18s ease, transform .18s ease;
  z-index: 40;
}

.fx-mi {
  padding: 5px 9px;
  border-radius: 5px;
  transition: background .15s ease, color .15s ease;
}

.fx-menu__sep {
  height: 1px;
  background: #e4e4e4;
  margin: 4px 6px;
}

.fx-cursor {
  position: absolute;
  left: 58px;
  top: 60px;
  z-index: 50;
}

.fx-cursor svg {
  transform-origin: 0 0;
  transition: transform .12s ease;
  display: block;
}

.detail__close {
  position: absolute;
  top: var(--cmd-h, 3.2rem);
  right: 0;
  opacity: 0;
  transition: opacity 90ms ease 0ms, background .16s ease, color .16s ease;
}

.detail.is-open .detail__close { opacity: 1; transition-delay: 920ms; }

/* 展开时列表与命令行的联动 */

.projects { transition: opacity 295ms ease 770ms; }
.is-detail .projects { opacity: 0; pointer-events: none; transition: opacity 200ms ease 0ms; }

.projects__shift { display: inline-block; transition: transform 440ms ease 625ms; }
.is-detail .projects__shift { transform: translateY(var(--cmd-dy, 0px)); transition-delay: 110ms; }

/* 源卡片的标题与介绍在展开瞬间让位给详情层里那份，收起时等它归位再回来。
   这里必须用 opacity 而不是 visibility：visibility 会被分屏容器继承，
   一加过渡就会让整块内容在每次翻回本屏时慢半拍才出现。 */

.proj-item .proj__lead,
.proj-item .proj__body { transition: opacity 0s linear 1100ms; }

.is-detail .proj-item.is-source .proj__lead,
.is-detail .proj-item.is-source .proj__body { opacity: 0; transition-delay: 0s; }

/* ---------- 联系 ---------- */

.links { list-style: none; margin: 1.8rem 0 0; padding: 0; display: grid; gap: .4rem; }
.links li { display: flex; gap: 1.4rem; }
.links__key { flex: none; width: 9ch; color: var(--fg-dim); }
.links__val.is-pending { color: var(--fg-dim); opacity: .65; }

.links__val a {
  color: var(--fg-dim);
  text-decoration: none;
  border-bottom: var(--stroke) solid var(--line);
  padding: .05rem .15rem;
  margin: 0 -.15rem;
  transition: background .16s ease, color .16s ease, border-color .16s ease;
}

.links__val a:hover,
.links__val a:focus-visible {
  background: var(--fg);
  color: var(--bg);
  border-color: transparent;
  outline: none;
}

.foot { margin: 3rem 0 0; font-size: calc(.8rem * var(--text-scale)); letter-spacing: .1em; }

.foot--row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1rem 2.5rem;
  max-width: 36rem;
  margin-left: auto;
  margin-right: auto;
}

.foot__item { flex: 0 1 auto; min-width: 0; text-align: center; }

.foot__link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.foot__link:hover,
.foot__link:focus-visible {
  background: var(--fg);
  color: var(--bg);
  outline: none;
}

/* ---------- 滚动劫持模式（PC） ---------- */

body.is-deck { height: 100vh; overflow: hidden; }

body.is-deck .deck { position: fixed; inset: 0; }

body.is-deck .panel {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  transition: visibility 0s linear 0s;
}

body.is-deck .panel:not(.is-active) {
  visibility: hidden;
  pointer-events: none;
  transition-delay: var(--dur);
}

/* 项目屏需要撑满安全区，详情层才有确定的高度可以展开 */
body.is-deck .panel--projects { align-items: stretch; }

body.is-deck .projects-inner {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

body.is-deck .anim {
  opacity: 0;
  transition: opacity var(--dur) ease, transform var(--dur) ease;
}

body.is-deck .is-below .anim { transform: translateY(var(--enter)); }
body.is-deck .is-above .anim { transform: translateY(calc(var(--enter) * -1)); }

body.is-deck .is-active .anim {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--i, 0) * 55ms + 130ms);
}

/* ---------- 常规滚动模式（窄屏 / 触屏） ---------- */

body.is-flow .pager { display: none; }
body.is-flow .hero__hint { display: none; }
body.is-flow .panel:first-child { min-height: 100vh; display: flex; align-items: center; }

/* 末屏也占满一整屏，滚到底时联系方式正好落在视口正中。
   上下内边距取齐，居中才不会偏 */
body.is-flow .panel:last-child {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding-bottom: 7rem;
}

body.is-flow .anim {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity .55s ease, transform .55s ease;
}

body.is-flow .anim.is-in { opacity: 1; transform: none; }

/* 窄屏虽是常规滚动，详情仍绝对定位盖在列表上：落点固定在命令行下沿，
   标题与简介才有确定的终点，可以直接复用上面那套从卡片飞过去的时序。
   窄屏没有固定屏高，改由 --inner-min 撑开容器，
   展开先长高、收起最后缩，中途不会压到下一屏。 */

body.is-flow .projects-inner {
  min-height: var(--inner-min, 0px);
  overflow-anchor: none;
  transition: min-height 370ms ease 735ms;
}

body.is-flow .projects-inner.is-detail {
  transition: none;
}

/* 只关滚动，不能再设 height：一旦压缩文档高度，滚动位置会被重置回顶部 */
html.is-flow-locked,
body.is-flow.is-flow-locked {
  overflow: hidden;
}

/* 锁定期间首屏改用锁定瞬间量到的像素高度，
   否则拖视口边框时 100vh 会把下方内容整体顶上去 */
body.is-flow.is-flow-locked .panel:first-child {
  min-height: var(--hero-h, 100vh);
}

body.is-flow .projects__cmd { scroll-margin-top: 5rem; }

body.is-flow .detail {
  inset: var(--cmd-h, 3.2rem) 0 auto 0;
  padding-top: 0;
}

/* 命令行本来就在容器顶端，不需要 PC 那段上移 */
body.is-flow .projects__shift { transform: none; }

body.is-flow .detail.is-open .detail__head {
  font-size: clamp(calc(1.45rem * var(--text-scale)), 6.2vw, calc(2rem * var(--text-scale)));
}

/* 高度由 JS 按视口剩余空间写入 --stage-h；clamp 仅作兜底 */
body.is-flow .detail .detail__main {
  flex: none;
  height: var(--stage-h, clamp(16rem, 62vw, 22rem));
  margin-top: 1.4rem;
}

body.is-flow .detail .detail__close {
  position: static;
  align-self: flex-start;
  margin: .85rem 0 var(--detail-safe) -.45rem;
  flex-shrink: 0;
}

@media (max-width: 560px) {
  .links li { flex-direction: column; gap: 0; }
  .links__key { width: auto; }

  /* JBMono 比系统等宽字更宽，窄屏单独收 CLI 字号与字距，尽量保持单行 */
  body.is-flow {
    font-size: clamp(17px, .5rem + .4vw, 20px);
    --cli-font: clamp(13.5px, 3.4vw, 16px);
    --cli-lh: 1.55;
    --cli-ls: 0;
  }

  body.is-flow .boot {
    font-size: calc(.74rem * var(--text-scale));
    letter-spacing: .02em;
  }

  body.is-flow .hero__sub {
    font-size: clamp(calc(.8rem * var(--text-scale)), 1.35vw, calc(1rem * var(--text-scale)));
    letter-spacing: .04em;
  }

  body.is-flow .topbar {
    font-size: calc(.78rem * var(--text-scale));
  }

  body.is-flow .btn {
    font-size: calc(.76rem * var(--text-scale));
    letter-spacing: .06em;
  }

  body.is-flow .detail.is-open .detail__head {
    flex-wrap: nowrap;
    gap: .2em .28em;
    font-size: clamp(calc(1.35rem * var(--text-scale)), 5.4vw, calc(1.85rem * var(--text-scale)));
  }

  body.is-flow .detail.is-open .detail__name {
    letter-spacing: .04em;
    flex-shrink: 1;
    min-width: 0;
  }

  body.is-flow .detail.is-open .detail__host {
    flex-shrink: 0;
    white-space: nowrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
  }
}
