/* ============================================================
   jam board shell — FigJam / Miro inspired
   ============================================================ */

.svg-defs {
  position: absolute; width: 0; height: 0; overflow: hidden;
}

/* ============================================================
   悬浮 nav —— 靠左，liquid glass
   玻璃感靠三件事叠出来，缺一件就变成"半透明白块"：
     1. backdrop-filter 里的 saturate —— 底下的东西透上来要更艳，不然只是雾
     2. 顶沿一道内高光 + 底沿一道内暗边 —— 玻璃的厚度是这么读出来的
     3. 一层从上到下的高光渐变，模拟打在弧面上的光
   ============================================================ */
.chrome {
  position: fixed;
  top: 28px; left: 28px;
  z-index: 40;
  display: flex; align-items: center; gap: 12px;
  width: max-content;
  max-width: calc(100vw - 56px);
  padding: 10px 26px 10px 20px;
  border-radius: var(--r-pill);
  color: var(--ink);
  /* 参考图 pill nav：白底 + 细描边，贴冷灰底，不染色 */
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  border: 1px solid var(--line);
  backdrop-filter: blur(18px) saturate(1.15);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
  box-shadow: var(--shadow);
}
/* backdrop-filter 不支持时退回不透明底，不然文字会压在画布点阵上 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .chrome { background: var(--paper); box-shadow: var(--shadow-lift); }
}

.chrome__name {
  font-family: var(--display); font-weight: 800; font-size: 1.15rem;
  letter-spacing: -0.02em; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 42vw;
  padding-left: 8px;
}
.chrome__back + .chrome__name { padding-left: 0; }

.chrome__badge {
  font-family: var(--mono); font-size: 14px; letter-spacing: .08em;
  padding: 6px 11px; border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  flex-shrink: 0;
}

/* 返回：没有容器也没有文字，就一个箭头 */
.chrome__back {
  flex-shrink: 0;
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  color: var(--ink-2);
  transition: color var(--d) var(--e), background-color var(--d) var(--e), translate var(--d) var(--e);
}
/* display 会盖掉 hidden 属性 —— 不写这条，落地页也会露出返回箭头 */
.chrome__back[hidden] { display: none; }
.chrome__back svg { width: 22px; height: 22px; }
.chrome__back:hover {
  color: var(--ink);
  background: rgba(16, 16, 16, .07);
  translate: -2px 0;
}
.chrome__back:active { translate: -1px 0; }

.stage {
  position: fixed; inset: 0;
  overflow: hidden;
  touch-action: none;
  cursor: grab;
}
.stage.is-panning { cursor: grabbing; }
.stage.is-over-card { cursor: default; }

/* —— 自定义光标（细指针设备才开；见 app.js） —— */
html.has-cur, html.has-cur * { cursor: none !important; }
.cur {
  position: fixed; z-index: 100; top: 0; left: 0;
  pointer-events: none;
  /* 锚在箭头尖：缩放/位移都以尖为原点，点击时不会「跳开」 */
  transform-origin: 0 0;
  will-change: transform;
}
.cur[hidden] { display: none; }
.cur__arrow {
  display: block;
  filter: drop-shadow(0 1px 2px rgba(10, 10, 10, .18));
}
.cur__tag {
  position: absolute; left: 16px; top: 18px;
  padding: 3px 8px 4px;
  background: var(--cursor);
  color: #0A0A0A;
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .02em; line-height: 1.2;
  white-space: nowrap;
  border: 1px solid #0A0A0A;
  box-shadow: 1px 1px 0 rgba(10, 10, 10, .2);
}
.cur__ibeam {
  display: none;
  width: 2px; height: 22px;
  margin-top: -2px;
  background: var(--cursor);
  box-shadow: 0 0 0 1px rgba(10, 10, 10, .35);
  border-radius: 1px;
}
.cur.is-text .cur__arrow,
.cur.is-text .cur__tag { display: none; }
.cur.is-text .cur__ibeam { display: block; }

/* —— 进出场：landing 便签错落入场 / 项目 section 上浮 —— */
@keyframes jam-enter {
  from { opacity: 0; translate: 0 22px; }
  to   { opacity: 1; translate: 0 0; }
}
@keyframes rsec-enter {
  from { opacity: 0; translate: 0 32px; filter: blur(5px); }
  to   { opacity: 1; translate: 0 0; filter: blur(0); }
}
@keyframes chrome-swap {
  from { opacity: .35; translate: 0 -4px; }
  to   { opacity: 1; translate: 0 0; }
}
body.is-landing-enter .jam {
  animation: jam-enter .52s cubic-bezier(.2, .8, .2, 1) both;
  animation-delay: calc(var(--enter-i, 0) * 45ms);
}
body.is-read-enter .rsec {
  animation: rsec-enter .58s cubic-bezier(.2, .8, .2, 1) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}
body.is-landing-enter .chrome__name,
body.is-read-enter .chrome__name {
  animation: chrome-swap .4s cubic-bezier(.2, .8, .2, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  body.is-landing-enter .jam,
  body.is-read-enter .rsec,
  body.is-landing-enter .chrome__name,
  body.is-read-enter .chrome__name { animation: none; }
}

.board {
  position: absolute; inset: 0;
  background-color: var(--canvas);
  background-image: radial-gradient(circle, var(--dot) 1.15px, transparent 1.25px);
  background-size: var(--dot-gap) var(--dot-gap);
  background-position: center;
}

.board__world {
  position: absolute;
  left: 0; top: 0;
  width: 4800px; height: 3200px;
  transform-origin: 0 0;
  will-change: transform;
}

/* —— Landing: sticky notes + project folders —— */
.jam {
  position: absolute;
  text-align: left;
  border: 2px solid transparent;
  user-select: none;
  touch-action: none;
  transition: scale var(--d) var(--e), filter var(--d) var(--e);
}
.jam:hover,
.jam.is-hot {
  scale: 1.02;
  z-index: 5;
}
.jam.is-dragging {
  scale: 1.04;
  z-index: 10;
  opacity: .96;
}
.jam__kicker {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); font-size: 14px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 10px;
}
.jam__dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--tip, var(--focus));
}
.jam__title {
  display: block;
  font-family: var(--display); font-weight: 800;
  font-size: 1.15rem; letter-spacing: -0.02em;
  line-height: 1.2; color: var(--ink);
  margin-bottom: 8px;
}
.jam__sub {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  font-size: 14px; line-height: 1.45; color: var(--ink-2);
}
.jam__sub--full {
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
  font-size: 14px;
  line-height: 1.55;
}
.jam__meta {
  display: block; margin-top: 12px;
  font-family: var(--mono); font-size: 14px; color: var(--ink-3);
}

.sticky {
  width: 220px; min-height: 180px;
  padding: 20px 18px 16px;
  border-radius: 0;              /* 纸是方的：圆角是卡片的语言，不是纸的 */
  background: var(--sticky, var(--pastel-yellow));
  box-shadow: var(--shadow);
}
.sticky--hero {
  width: 340px; min-height: 280px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 0;
  box-shadow: var(--shadow);
}
.sticky--hero .jam__kicker { font-size: 13px; }
.sticky--hero .jam__title { font-size: 1.75rem; margin-bottom: 8px; }
.jam__role {
  display: block;
  font-size: .95rem; font-weight: 600; color: var(--ink-2);
  margin-bottom: 14px;
}
.sticky--hero .jam__role { font-size: 1rem; }
.sticky--hero .jam__sub--full {
  font-size: 15.5px;
  line-height: 1.65;
  display: grid; gap: 12px;
}
.jam__p { display: block; }
.sticky--info {
  width: 260px; min-height: 220px;
}
.sticky:hover,
.sticky.is-dragging { outline: 2px solid var(--focus); outline-offset: 2px; }
.sticky__contacts {
  display: grid; gap: 8px; margin-top: 4px;
  font-size: 14px; line-height: 1.4; color: var(--ink-2);
}
.sticky__link {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--ink-3);   /* 静止态也要看得出能点 —— HR 是来复制邮箱的 */
  text-decoration-thickness: 1px;
  transition: text-decoration-color var(--d) var(--e), text-decoration-thickness var(--d) var(--e);
}
/* 强调色就是墨色，所以 hover 不能靠"变色"——靠下划线变实变粗 */
.sticky__link:hover {
  text-decoration-color: var(--ink);
  text-decoration-thickness: 2px;
}

/* ============================================================
   Project —— 钉在板上的便签
   一张品牌色便签，压在两张白纸上，顶上一枚大头针把它们别在一起。
   白纸上是手写笔迹（内联 SVG 波浪线当背景图，不额外加 DOM）。
   ============================================================ */
.project {
  /* 背后的白纸是绝对定位 + 旋转的，不参与撑高，所以按钮得自己把它们的
     溢出量让出来 —— 下面四个变量就是实测出来的溢出（相对便签四边）。
     hover outline 画在这个内边距的外缘，于是"整个被钉住的东西"都被框进去。
     改了白纸的 translate / rotate，就回来重新量这四个值。 */
  --paper-out-t: 9px;
  --paper-out-r: 34px;
  --paper-out-b: 44px;
  --paper-out-l: 31px;
  --halo: 12px;                 /* 线与内容之间的呼吸 */

  width: auto;
  padding:
    calc(var(--paper-out-t) + var(--halo))
    calc(var(--paper-out-r) + var(--halo))
    calc(var(--paper-out-b) + var(--halo))
    calc(var(--paper-out-l) + var(--halo));
  background: transparent;
  box-shadow: none;
  border: 0;
  color: var(--ink);
}
.pinned {
  position: relative;
  display: block;
  width: 244px;
}
.project--soon .pinned { isolation: isolate; }

/* —— 底下的白纸：各自歪一点，露出边角 —— */
/* 白纸比便签略大一圈、下缘多探出一截 —— 手写的那几行就落在这条露出来的带子上，
   不然纸整个被便签盖住，"钉在一沓纸上"这件事就读不出来。 */
.pinned__paper {
  position: absolute;
  inset: -5px -9px -22px -9px;
  background-color: var(--paper);
  background-repeat: no-repeat;
  box-shadow: 0 1px 4px rgba(10, 10, 10, .10);
}
.pinned__paper--b {
  rotate: -4.5deg;
  translate: -13px 6px;
  background-image: var(--scribble), var(--scribble), var(--scribble);
  background-size: 58% 7px, 44% 7px, 62% 7px;
  background-position: 14px calc(100% - 42px), 14px calc(100% - 26px), 14px calc(100% - 10px);
}
.pinned__paper--c {
  rotate: 5deg;
  translate: 15px 11px;
  background-image: var(--scribble), var(--scribble);
  background-size: 50% 7px, 66% 7px;
  background-position: 18px calc(100% - 30px), 18px calc(100% - 12px);
}

/* —— 便签本体 —— */
.pinned__note {
  position: relative;
  display: flex; flex-direction: column;
  height: 212px;
  padding: 30px 20px 16px;      /* 顶部给大头针让出位置 */
  background: color-mix(in srgb, var(--tip, var(--pastel-mint)) 48%, white);
  /* 便签要"翘起来"：一层贴地接触影定住底边，一层扩得很开的软影拉开与板面的距离 */
  box-shadow:
    0 2px 4px rgba(10, 10, 10, .10),
    0 14px 34px rgba(10, 10, 10, .16);
  overflow: hidden;
  text-align: left;
  /* 撕口感：右下角切一小块，让它更像"撕下来的一张"而不是一个方块 */
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%);
}
/* hover outline：贴着便签外扩一圈的 CSS 边框，再套粗糙笔触滤镜。
   inset 是负值，天生跟着盒子尺寸走 —— 不会再出现线和本体错位。
   画在 .pinned 上而不是 .pinned__note 上：便签自己是 overflow:hidden，
   会把这圈画在盒子外面的伪元素直接剪掉。.pinned 的盒子和便签等大
   （白纸都是绝对定位不参与撑高），所以位置完全一致。 */
/* hover：不画线，改成把背后压着的两张纸各自往外拨开一点。
   轮廓线永远要跟一个歪着的纸堆较劲；而"纸松了一下"是这个隐喻里最自然的反馈。
   只动 rotate / translate，不碰布局，所以全程走合成层不触发重排。 */
.pinned__paper {
  transition: rotate var(--d) var(--e), translate var(--d) var(--e), box-shadow var(--d) var(--e);
}
.project:hover .pinned__paper--b,
.project.is-dragging .pinned__paper--b {
  rotate: -9deg;
  translate: -26px 10px;
  box-shadow: 0 3px 10px rgba(10, 10, 10, .14);
}
.project:hover .pinned__paper--c,
.project.is-dragging .pinned__paper--c {
  rotate: 10deg;
  translate: 30px 16px;
  box-shadow: 0 3px 10px rgba(10, 10, 10, .14);
}
/* 便签本体跟着轻轻抬一下，让"被拨开"的层次关系读得出来 */
.pinned__note { transition: box-shadow var(--d) var(--e); }
.project:hover .pinned__note,
.project.is-dragging .pinned__note {
  box-shadow:
    0 3px 6px rgba(10, 10, 10, .12),
    0 20px 44px rgba(10, 10, 10, .20);
}

/* —— 大头针：一颗有高光的圆珠 + 一根细针 —— */
.pinned__pin {
  position: absolute;
  left: 50%; top: -9px;
  translate: -50% 0;
  z-index: 3;
  width: 22px; height: 22px;
  border-radius: 50%;
  /* clay 的做法：不用高光小圆点做镜面反射，用顶边一道内高光 + 底部一圈内阴影
     捏出体积；表面保持哑光，所以底色是平的而不是径向渐变。 */
  background: color-mix(in srgb, var(--ink) 82%, white);
  box-shadow:
    inset 0 2px 1px rgba(255, 255, 255, .34),
    inset 0 -3px 4px rgba(0, 0, 0, .30),
    0 3px 7px rgba(10, 10, 10, .22);
}
.pinned__pin::after {                 /* 针尖：从珠子底下探出来一点 */
  content: "";
  position: absolute; left: 50%; top: 15px;
  translate: -50% 0;
  width: 2px; height: 9px;
  background: linear-gradient(var(--ink-3), color-mix(in srgb, var(--ink-3) 20%, transparent));
}

.project .jam__kicker { margin-bottom: 8px; }
.project .jam__title { font-size: 1.05rem; }
.project .jam__sub { -webkit-line-clamp: 2; font-size: 14px; flex: 1; }
.project .jam__meta { margin-top: auto; }

/* 敬请期待：灰便签，可拖不可进；hover 出提示 */
.project--soon { cursor: grab; color: var(--ink-3); }
.project--soon .pinned__note {
  background: color-mix(in srgb, #B0B0B5 28%, white);
}
.project--soon .jam__title { color: var(--ink-3); }
.project--soon .jam__sub,
.project--soon .jam__meta { color: color-mix(in srgb, var(--ink-3) 80%, white); }

.project--soon .project__soon {
  position: absolute;
  left: 50%; top: 50%;
  translate: -50% -50%;
  z-index: 4;
  padding: 8px 14px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  font-size: 14px; font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-lift);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--d) var(--e);
}
.project--soon:hover .project__soon,
.project--soon:focus-visible .project__soon { opacity: 1; }
.project--soon:hover .pinned__note { filter: grayscale(1) brightness(.97); }

.next-proj {
  margin-top: 8px;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 16px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-size: .9rem; font-weight: 600;
  box-shadow: var(--shadow);
  transition: filter var(--d) var(--e), transform var(--d) var(--e);
}
.next-proj:hover { filter: brightness(1.08); transform: translateX(2px); }
.next-proj__sub {
  display: block; margin-top: 10px;
  font-family: var(--mono); font-size: 14px; color: var(--ink-3);
}

.board-sub {
  position: absolute;
  font-size: 1rem; color: var(--ink-3);
  pointer-events: none;
}

/* —— Project board sections —— */
.sec {
  position: absolute;
  width: min(720px, 84vw);
  min-height: 300px;
  /* --sec-pad 由 .read 传下来；.sec 若出现在阅读态之外，退回固定值 */
  padding: var(--sec-pad, 40px) var(--sec-pad, 38px) calc(var(--sec-pad, 40px) * .9);
  border-radius: 24px;
  background: var(--paper);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  cursor: default;
  transition: border-color var(--d) var(--e), box-shadow var(--d) var(--e);
}
/* 阅读态 section 不可点：去掉 pointer / hover 抬起暗示 */
body.is-read .sec,
body.is-read .rsec {
  cursor: inherit;
}
body.is-read .sec:hover,
body.is-read .sec:focus-visible {
  border-color: var(--line);
  box-shadow: var(--shadow);
  outline: none;
}
.sec--hint {
  border-color: var(--focus);
  box-shadow: var(--shadow-lift);
  animation: sec-blink 1s var(--e) 3;
}
.sec-tip {
  position: absolute;
  left: 50%; top: -18px;
  transform: translate(-50%, -100%);
  padding: 7px 12px;
  border-radius: 10px;
  background: var(--ink);
  color: var(--paper);
  font-size: 14px; font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-lift);
  pointer-events: none;
  z-index: 3;
  animation: tip-float 2.2s var(--e) infinite;
}
.sec-tip::after {
  content: "";
  position: absolute; left: 50%; bottom: -5px;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: var(--ink);
  border-bottom: 0;
}
@keyframes sec-blink {
  0%, 100% { border-color: var(--focus); box-shadow: var(--shadow-lift); }
  50% { border-color: color-mix(in srgb, var(--focus) 35%, var(--line)); box-shadow: var(--shadow); }
}
.sec--cover {
  background: color-mix(in srgb, var(--tip, var(--focus)) 42%, white);
  border-color: color-mix(in srgb, var(--tip, var(--focus)) 35%, var(--line));
}
.sec-arrow {
  position: absolute;
  width: 52px; height: 28px;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 800; font-size: 1.35rem;
  color: var(--ink-3);
  pointer-events: none;
}
.sec__pill {
  position: absolute; left: 16px; top: -12px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--paper);
  border: 1px solid var(--line);
  font-family: var(--mono); font-size: 14px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3);
  box-shadow: var(--shadow);
}
.sec__pill i {
  width: 8px; height: 8px; border-radius: 2px;
  background: var(--tip, var(--focus));
}
.sec__title {
  font-family: var(--display); font-weight: 800;
  font-size: 1.3rem; line-height: 1.35; letter-spacing: -0.02em;
  color: var(--ink); margin: 4px 0 14px;
  /* 不人为限宽折行；只在碰到 section 容器边时才换行 */
  max-width: none;
  text-wrap: unset;
  overflow-wrap: break-word;
}

/* ============================================================
   notes —— 对话气泡（见 app.js renderViz "notes"）
   头像在气泡外，气泡左侧一个指向头像的小尖角。
   整组罩一层很浅的底 —— 让这段"别人说的话"和上下正文自然断开，
   读者一眼知道视角换人了。
   ============================================================ */
.viz--notes {
  display: grid; gap: 18px;
  padding: 24px 22px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3.5%, white);
}
.note {
  display: flex; align-items: flex-start; gap: 12px;
}
.note__pin {
  flex: none;
  display: grid; place-items: center;
  width: 40px; height: 40px; border-radius: 50%;
  overflow: hidden;
  box-shadow:
    0 1px 2px rgba(10, 10, 10, .12),
    inset 0 1px 0 rgba(255, 255, 255, .28);
}
.note__pin svg { width: 100%; height: 100%; display: block; }
.note__bubble {
  position: relative;
  max-width: 46ch;
  padding: 13px 18px 14px;
  border-radius: 4px 18px 18px 18px;   /* 靠尖角那一侧收角，其余圆 */
  background: var(--paper);
  box-shadow:
    0 1px 2px rgba(10, 10, 10, .05),
    0 8px 22px rgba(10, 10, 10, .09);
}
/* 尖角：一个旋转 45° 的小方块，颜色跟气泡一样，藏在左上角外侧 */
.note__bubble::before {
  content: "";
  position: absolute; left: -4px; top: 13px;
  width: 12px; height: 12px;
  background: inherit;
  transform: rotate(45deg);
  border-radius: 2px;
}
.note__who {
  font-size: 14px; font-weight: 700; color: var(--ink); margin-bottom: 4px;
}
.note__body { display: grid; gap: 6px; }
.note__body p { font-size: 14px; line-height: 1.65; color: var(--ink-2); }

/* 头像本身带肤色/发色，不再靠底色区分人 */
.note--a .note__pin,
.note--b .note__pin,
.note--c .note__pin,
.note--reply .note__pin { background: transparent; }

/* 我的回应：靠右、气泡换个方向，读起来就是"我在回话" */
.note--reply { flex-direction: row-reverse; margin-left: 40px; }
.note--reply .note__bubble {
  border-radius: 18px 4px 18px 18px;
  background: color-mix(in srgb, var(--tip, var(--focus)) 10%, white);
}
.note--reply .note__bubble::before { left: auto; right: -4px; }
.note--reply .note__who { color: var(--ink-2); }

/* 正文重点 —— rich() 里的 **加粗** */
.sec__lines b.em, .note__body b.em { font-weight: 700; color: var(--ink); }
.sec__lines { display: grid; gap: 10px; }
.sec__lines p {
  font-size: .92rem; line-height: 1.7; color: var(--ink-2);
  text-wrap: pretty;
}
.sec__facts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px; margin-top: 14px;
}
.sec__fact {
  padding: 10px 12px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--tip, var(--focus)) 12%, white);
}
.sec__fact b {
  display: block; font-family: var(--display); font-weight: 800;
  font-size: 1.05rem; color: var(--ink); letter-spacing: -0.02em;
}
.sec__fact span { font-size: 14px; color: var(--ink-3); }

/* Cover：基础信息左、成果数据右（右侧 2 列） */
.cover-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
  gap: 28px 36px;
  align-items: start;
}
.cover-split .sec__title { margin-top: 4px; }
.cover-split .sec__facts {
  margin-top: 4px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
/* 窄屏：数据打成一行，落到项目信息下面 */
@media (max-width: 900px) {
  .cover-split {
    grid-template-columns: 1fr;
    gap: 18px;
  }
  .cover-split .sec__facts {
    display: flex;
    flex-wrap: nowrap;
    gap: 8px;
    overflow-x: auto;
  }
  .cover-split .sec__fact {
    flex: 1 1 0;
    min-width: 112px;
  }
}

.board-title {
  position: absolute;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2rem, 4vw, 3.2rem);
  letter-spacing: -0.035em; line-height: 1;
  color: var(--ink);
  pointer-events: none;
}

/* viz — compact jam style */
.viz { margin-top: 28px; }
.viz + .viz { margin-top: 24px; }
.viz--compare, .viz--dual {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
/* ── clay —— section 内所有卡片的统一质感 ────────────────────────
   不描边、靠阴影托起来。描边和阴影同时上会显脏：边把形状钉死，
   阴影又想让它浮起来，两个信号打架。留阴影这一个。
   底色比 --paper 稍暖一点点（3% 墨），顶边的内高光才有东西可衬。 */
/* 这一条是 section 内卡片表面的唯一出处。
   注意：各组件自己的规则排在这条后面，同权重时后来者赢 ——
   所以那些组件里**不能**再出现 border / box-shadow，否则这里就白写了。
   （之前 .aud/.ba-col/.ba-cell/.chain__node/.prac-pde/.note 就是这么漏回旧样式的。） */
/* .note 不在这条里：评论有自己那层"浮出来"的扩散阴影，
   写进来的话会被这条 clay 盖掉（它排在 .note 后面）。 */
.viz-card, .cov, .setup, .layer, .flow-step, .path, .aud,
.chain__node, .pstep, .prac-pde, .ba-fold, .shot img,
.hist__item, .gear__item, .feat {
  border: 0;
  box-shadow: var(--clay);
}
/* .path 原来是裸排的一行行；进了多列网格就得自己成一张卡，否则三列之间没有边界 */
.path {
  padding: 16px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
}
.path--best { background: color-mix(in srgb, var(--tip, var(--focus)) 8%, white); }
.viz-card {
  padding: 16px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
}
.viz-card--hi {
  background: color-mix(in srgb, var(--tip, var(--focus)) 16%, white);
}
.viz-tag {
  font-family: var(--mono); font-size: 14px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 6px;
}
.viz-big {
  font-family: var(--display); font-weight: 800; font-size: 1.12rem;
  letter-spacing: -0.02em; line-height: 1.35; color: var(--ink); margin-bottom: 2px;
}
.viz-sub, .viz-body, .viz-note, .viz-flow, .viz-h {
  font-size: 14px; color: var(--ink-2); line-height: 1.5;
}
.viz-h { font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.viz-card ul { margin: 6px 0 0; padding-left: 1.1em; font-size: 14px; }
/* dual 问题卡：条目与标题左对齐，不缩进 */
.viz--dual .viz-card ul {
  padding-left: 0;
  list-style: none;
  display: grid;
  gap: 6px;
}
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.chip {
  padding: 4px 10px; border-radius: 999px; font-size: 14px;
  background: color-mix(in srgb, var(--ink) 6%, white); color: var(--ink-3);
}
.chip--on {
  background: color-mix(in srgb, var(--tip, var(--focus)) 22%, white);
  color: var(--ink);
}
.chip--light { background: transparent; border: 1px dashed var(--line); }
.bar {
  height: 5px; margin-top: 8px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 8%, white);
}
.bar i { display: block; height: 100%; background: var(--tip, var(--focus)); }

/* 问题卡：正文就是那句问题本身，得比说明文字重一档 —— 放大、加粗、吃满墨色。
   卡片两侧都上品牌色底：这两句是并列的一对问题，没有主次之分。 */
.viz--ask .viz-body {
  font-size: 1.12rem;
  line-height: 1.55;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.viz--ask .viz-tag { margin-bottom: 10px; }
.viz--ask .viz-card { padding: 20px 22px; }
@media (max-width: 760px) {
  .viz--ask .viz-body { font-size: 1.02rem; }
}

.viz--table { overflow-x: auto; }
.viz--table table { width: 100%; border-collapse: collapse; font-size: 14px; }
.viz--table th {
  text-align: left; padding: 0 10px 10px 0;
  white-space: nowrap;          /* 「满分」这种两字表头被拆成上下两行很难看 */
  font-family: var(--mono); font-size: 14px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
  border-bottom: 1px solid var(--line);
}
.viz--table td {
  padding: 12px 10px 12px 0; border-bottom: 1px solid var(--line);
  vertical-align: top; color: var(--ink-2); line-height: 1.45;
}
.viz--table td b { color: var(--ink); font-weight: 600; }
.viz--table tr.is-win td {
  background: color-mix(in srgb, var(--tip, var(--focus)) 10%, transparent);
}
/* 整列高亮：四条路径横着比，赢的是一整栏。
   首行首尾补圆角，让这一列读起来像一根竖着的高亮条而不是几个方块。 */
.viz--table th.is-wincol, .viz--table td.is-wincol {
  background: color-mix(in srgb, var(--tip, var(--focus)) 12%, transparent);
  color: var(--ink); padding-left: 10px; padding-right: 10px;
}
.viz--table th.is-wincol {
  color: var(--ink); border-radius: var(--r-sm) var(--r-sm) 0 0;
  padding-top: 6px;
}
.viz--table tr:last-child td.is-wincol { border-radius: 0 0 var(--r-sm) var(--r-sm); }

/* 评分分档嵌在维度表最后一列 */
.tbl-rubric {
  display: grid; gap: 6px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}
.tbl-rubric__tier {
  display: grid; grid-template-columns: 56px 40px 1fr; gap: 8px; align-items: start;
  font-size: 14px; line-height: 1.45;
}
.tbl-rubric__band {
  font-family: var(--mono); font-size: 14px; color: var(--ink);
}
.tbl-rubric__tag { font-weight: 600; color: var(--ink-2); }
.tbl-rubric__t { color: var(--ink-3); }
@media (max-width: 700px) {
  .tbl-rubric__tier { grid-template-columns: 52px 1fr; }
  .tbl-rubric__tag { grid-column: 2; }
  .tbl-rubric__t { grid-column: 1 / -1; }
}

/* 条件卡 / 步骤卡一律单列：多列会把顺序和对照关系拆散 */
.viz--coverage, .viz--paths {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(288px, 1fr));
}
/* layers：大屏按条数一排铺满 → 中屏 2 列 → 窄屏 1 列 */
.viz--layers {
  display: grid; gap: 12px;
  grid-template-columns: repeat(var(--layers-n, 4), minmax(0, 1fr));
}
@media (max-width: 1000px) {
  .viz--layers { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .viz--layers { grid-template-columns: 1fr; }
}
.viz--flow {
  display: grid; gap: 12px;
  grid-template-columns: 1fr;
}
/* 卡片等分填满整栏，列数跟着「有几张卡」走，不写死。
   写死 repeat(5,…) 的话，只有三张卡的块也会被切成五格 ——
   三张长文案卡各自只剩 176px，中文一行十来个字就断。
   auto-fit 的关键性质：放得下几列就摆几列，多出来的空轨道会被折叠掉，
   所以列数天然不会超过卡片数，每张卡再由 1fr 撑满剩余宽度。
     · 5 张短标签（评测条件）→ 5 列 ×174px，跟原来一样
     · 3 张长文案（实施三件事）→ 3 列 ×302px，不再被挤 */
.viz--setup {
  width: 100%;
  display: grid; gap: 12px;
  grid-template-columns: repeat(var(--setup-n, 5), minmax(0, 1fr));
}
.viz--setup .viz__hd { grid-column: 1 / -1; }
.viz--setup .setup { min-width: 0; width: 100%; }
/* 窄屏一行一张：170px 的下限在小屏上还能塞下两三列，但中文读起来已经太挤 */
@media (max-width: 1000px) {
  .viz--setup { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .viz--setup { grid-template-columns: 1fr; }
}
.viz__hd {
  font-size: 14px; font-weight: 700; color: var(--ink);
  margin: 0 0 2px;
}
.viz--flow .viz__hd,
.viz--setup .viz__hd,
.viz--table .viz__hd,
.viz--rubric .viz__hd { margin-bottom: 4px; }
.viz--arrow {
  display: grid; place-items: center;
  margin: 4px 0 8px;
  color: var(--ink-3);
  font-size: 1.35rem; line-height: 1;
}

/* 功能点卡片：固定 3 列 × 2 行，窄屏再收 */
.viz--features {
  display: grid; gap: 12px;
  grid-template-columns: repeat(3, 1fr);
}
.feat {
  display: grid; grid-template-columns: 40px 1fr; gap: 12px; align-items: start;
  padding: 16px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
}
.feat__ico {
  width: 40px; height: 40px; border-radius: 10px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 6%, white);
  color: var(--ink);
}
.feat__ico svg { width: 20px; height: 20px; }
.feat__h { font-size: 14px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.feat__t { font-size: 14px; line-height: 1.5; color: var(--ink-2); }

/* 评分 Rubric：每个维度一张卡，下面分档 */
.viz--rubric { display: grid; gap: 14px; }
.rubric {
  padding: 16px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
  box-shadow: var(--clay);
}
.rubric__h {
  font-size: 14px; font-weight: 700; color: var(--ink); margin-bottom: 2px;
}
.rubric__of {
  font-size: 14px; color: var(--ink-3); margin-bottom: 10px;
}
.rubric__tiers { display: grid; gap: 8px; }
.rubric__tier {
  display: grid; grid-template-columns: 72px 52px 1fr; gap: 10px; align-items: start;
  font-size: 14px; line-height: 1.5;
}
.rubric__band {
  font-family: var(--mono); font-size: 14px; color: var(--ink);
  padding-top: 1px;
}
.rubric__tag {
  font-weight: 600; color: var(--ink-2); padding-top: 1px;
}
.rubric__t { color: var(--ink-2); }

@media (max-width: 700px) {
  .rubric__tier { grid-template-columns: 64px 1fr; }
  .rubric__tag { grid-column: 2; }
  .rubric__t { grid-column: 1 / -1; }
}
/* 多列之后每张卡只有 ~300px，再切 110px 的键列就只剩 190px 放内容 ——
   改成键在上、值在下。.flow-step 例外：它的键是个 36px 的序号徽章，不占宽。 */
.cov, .setup, .layer, .flow-step {
  display: grid; grid-template-columns: 1fr;
  gap: 8px; padding: 16px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
}
.flow-step { grid-template-columns: 36px 1fr; gap: 10px; align-items: start; }
.cov__k, .setup__k, .layer__n, .flow-step__n {
  font-size: 14px; font-weight: 600; color: var(--ink);
}
.cov__k em, .layer__n em {
  display: block; font-style: normal; font-size: 14px;
  font-weight: 400; color: var(--ink-3); margin-top: 2px;
}
.cov__v { display: flex; flex-wrap: wrap; gap: 4px; }
.setup__v, .layer__d, .flow-step__p { font-size: 14px; color: var(--ink-2); line-height: 1.5; }
.flow-step__n {
  font-family: var(--mono); place-self: start center;
  width: 30px; height: 30px; display: grid; place-items: center;
  border-radius: 999px; background: color-mix(in srgb, var(--tip, var(--focus)) 18%, white);
  color: var(--ink);
}
/* 高亮的那一步：整张卡吃品牌色底，序号也跟着实心 */
.flow-step--hi {
  background: color-mix(in srgb, var(--tip, var(--focus)) 8%, white);
}
.flow-step--hi .flow-step__n {
  background: color-mix(in srgb, var(--tip, var(--focus)) 82%, white);
  color: #fff;
}
.flow-step__h { font-size: 14px; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
/* 每一步自己的配图：跟着这一步的文字走，而不是整节共用一张 —— 
   读者看到"这步做了什么"的同时就能看到"做出来长什么样"。 */
.flow-step__img { display: block; margin-top: 12px; }

.path__hd { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.path__nm { font-size: 14px; color: var(--ink); }
.path__nm em { font-style: normal; color: var(--ink-3); margin-left: 6px; font-size: 14px; }
.path__sc { font-family: var(--display); font-weight: 800; color: var(--ink-3); }
.path__bar {
  height: 8px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 8%, white);
}
.path__bar i { display: block; height: 100%; background: color-mix(in srgb, var(--ink) 25%, white); }
.path--best .path__sc, .path--best .path__nm { color: var(--ink); }
.path--best .path__bar i { background: var(--tip, var(--focus)); }
.path__dt { margin-top: 4px; font-size: 14px; color: var(--ink-3); }

/* ── audit —— 逐路径的结构差异清单 ─────────────────────────────
   借 .cov 那套「左键右值」的骨架，值换成句子而不是 chip。
   问题条是中性的：站点没有红色 token，也不需要 —— 数量本身就是信号。 */
.viz--audit { display: grid; gap: 10px; }
.aud {
  display: grid; grid-template-columns: 132px 1fr;
  gap: 16px; padding: 16px 18px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
}
.aud--best {
  background: color-mix(in srgb, var(--tip, var(--focus)) 8%, white);
}
.aud__k { font-size: 14px; font-weight: 600; color: var(--ink); }
.aud__k em {
  display: block; font-style: normal; font-size: 14px;
  font-weight: 400; color: var(--ink-3); margin-top: 2px;
}
.aud__v { display: grid; gap: 9px; }
.aud__i {
  display: grid; grid-template-columns: 14px 1fr; gap: 7px;
  font-size: 14px; line-height: 1.5; color: var(--ink-2);
}
.aud__i i {
  font-style: normal; font-size: 14px; line-height: 1.62;
  color: var(--ink-3); text-align: center;
}
/* 问题条没有字符，用一小段横线占住那一格，让所有条目对齐同一条轴 */
.aud__i:not(.aud__i--ok) i::before { content: "—"; }
.aud__i--ok i { color: var(--tip, var(--focus)); font-weight: 700; }
.aud__i--ok { color: var(--ink); }

/* ── ba —— before / after 对照 ───────────────────────────────── */
.viz--ba { display: grid; gap: 30px; }
.ba-grp { display: grid; gap: 12px; }
.ba-grp__hd { font-size: 14px; color: var(--ink); }
.ba-grp__hd em {
  font-style: normal; color: var(--ink-3);
  font-size: 14px; margin-left: 8px;
}
/* 组标题下的整页对照图（列表页 / 详情页），跟 ba-item__img 同一套 missing 占位 */
.ba-grp__img { display: block; margin: 2px 0 4px; }
.ba-cols, .ba-item__pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.ba-col {
  padding: 12px 14px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 2.5%, white);
  box-shadow: none;   /* 落在白 clay 卡里时不再自己浮一层 */
}
.ba-col--hi {
  background: color-mix(in srgb, var(--tip, var(--focus)) 8%, white);
}
.ba-col b { display: block; font-size: 14px; font-weight: 600; color: var(--ink); }
.ba-col em {
  display: block; font-style: normal; font-size: 14px;
  color: var(--ink-3); margin-top: 1px;
}
.ba-item { margin-top: 20px; }
.ba-item__h {
  display: flex; align-items: center; gap: 7px;
  font-size: 14px; font-weight: 600; color: var(--ink); margin-bottom: 6px;
}
.ba-item__h i {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--mono); font-style: normal; font-size: 14px; line-height: 1;
  background: color-mix(in srgb, var(--tip, var(--focus)) 20%, white);
  color: var(--ink);
}
.ba-item__d { font-size: 14px; color: var(--ink-3); line-height: 1.5; margin-bottom: 8px; }
/* 区域布局示意图：夹在区域标题和前后对照之间 —— 先看长什么样，再读差在哪 */
.ba-item__img { display: block; margin: 8px 0 10px; }
.ba-cell {
  padding: 13px 15px; border-radius: var(--r-sm);
  font-size: 14px; line-height: 1.55; color: var(--ink-2);
  background: color-mix(in srgb, var(--ink) 2%, white);
  box-shadow: none;
}
.ba-cell--hi {
  background: color-mix(in srgb, var(--tip, var(--focus)) 6%, white);
  color: var(--ink-2);
}

/* 比站点的 760 断点早一档就要叠起来：栏宽是 70vw，到 1000px 时每一侧只剩
   ~240px，中文一行不到二十个字，读起来全是断句。上下叠反而保住了"前 → 后"的顺序。 */
@media (max-width: 1000px) {
  .ba-cols, .ba-item__pair { grid-template-columns: 1fr; gap: 6px; }
}

/* —— 折叠：轻 clay 卡 + 项目 tip 绿点缀 ——
   标题条吃一点品牌绿，箭头同色；展开后内容区仍是白底。 */
.ba-fold {
  border-radius: var(--r-sm);
  background: var(--paper);
  padding: 0;
  overflow: hidden;
  transition: box-shadow var(--d) var(--e);
}
.ba-fold:hover { box-shadow: var(--clay-lift); }
.ba-fold__t {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 18px;
  background: color-mix(in srgb, var(--tip, var(--focus)) 12%, white);
  font-size: 14px; font-weight: 600;
  color: color-mix(in srgb, var(--tip, var(--focus)) 55%, var(--ink));
  cursor: pointer; list-style: none;
}
.ba-fold__t::-webkit-details-marker { display: none; }   /* Safari 的三角 */
.ba-fold__t i {
  font-style: normal; font-size: .9rem; line-height: 1;
  color: var(--tip, var(--focus));
  transition: transform var(--d) var(--e);
}
.ba-fold[open] > .ba-fold__t {
  border-bottom: 1px solid color-mix(in srgb, var(--tip, var(--focus)) 22%, var(--line));
}
.ba-fold[open] > .ba-fold__t i { transform: rotate(180deg); }
.ba-fold__b { display: grid; gap: 26px; padding: 18px; }

.viz--chains .chain { margin-bottom: 32px; }
.viz--chains .chain:last-child { margin-bottom: 0; }
.chain__title { font-size: 14px; color: var(--ink-3); margin-bottom: 6px; }
.chain__row { display: flex; flex-wrap: wrap; gap: 8px; align-items: stretch; }
.chain__node {
  flex: 1; min-width: 72px; padding: 12px;
  border-radius: var(--r-sm); background: color-mix(in srgb, var(--ink) 3%, white); font-size: 14px; color: var(--ink-2);
}
.chain__node b { display: block; font-size: 14px; color: var(--ink); margin-bottom: 2px; }
.chain__arrow { color: var(--ink-3); align-self: center; }
.chain__note { margin-top: 6px; font-size: 14px; color: var(--ink-3); }
/* accent 行只靠浅底区分，标题字跟普通行一样用墨色，避免品牌黄叠在浅黄底上撞色 */
.chain--hi .chain__node {
  background: color-mix(in srgb, var(--ink) 3%, white);
}

/* 痛点→机会→功能对照图：横流程，列对齐下挂 */
.viz--opmap { overflow-x: auto; }
.viz--opmap .viz__hd { margin-bottom: 16px; }
.opmap {
  display: grid; gap: 10px;
  min-width: 720px;
}
.opmap__row {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  gap: 10px;
  align-items: stretch;
}
.opmap__rk {
  font-size: 14px; font-weight: 600; color: var(--ink-3);
  display: grid; align-content: center; justify-items: center;
  text-align: center;
  line-height: 1.3;
}
.opmap__cells {
  display: flex;
  align-items: stretch;
  gap: 0;
}
.opmap__cell {
  flex: 1 1 0;
  min-width: 0;
  padding: 12px 10px;
  /* 整张流程图的字统一居中：每格宽度不等，左对齐会让上下几行的字参差不齐 */
  text-align: center;
  display: grid; place-content: center;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
  box-shadow: var(--clay);
  font-size: 14px; line-height: 1.45; color: var(--ink-2);
}
.opmap__cell--path {
  font-size: 14px; font-weight: 700; color: var(--ink);
  text-align: center;
  display: grid; place-content: center;
  /* 调研路径：淡黄 */
  background: color-mix(in srgb, var(--tip, var(--pastel-yellow)) 32%, white);
}
.opmap__cell--out {
  font-size: 14px; color: var(--ink-2);
  /* 现状产物：这一行讲的是"改造前手工做出来的东西"，用中性灰，
     不跟黄色的路径 / 功能点抢 —— 黄色留给"这条链路要变成什么"。 */
  background: color-mix(in srgb, var(--ink) 6%, white);
}
.opmap__cell--feat {
  font-weight: 600; color: var(--ink);
  /* 功能点：实黄 */
  background: var(--tip, var(--pastel-yellow));
}
.opmap__gap {
  flex: 0 0 18px;
  display: grid; place-items: center;
  color: transparent; font-size: 14px;
  user-select: none;
}
.opmap__gap--arrow { color: var(--ink-3); }
.opmap__note {
  margin-top: 10px; font-size: 14px; color: var(--ink-3);
}

.layer { grid-template-columns: 1fr; }
.layer__s .bar { margin-top: 6px; }
.layer__s small { display: block; margin-top: 4px; font-size: 14px; color: var(--ink-3); }

.nb { white-space: nowrap; }
.shot { margin: 0; }
.shot--viz { margin-top: 16px; }
.shot__box { display: block; border-radius: var(--r-sm); overflow: hidden; }
.shot img, .shot video { width: 100%; border-radius: var(--r-sm); display: block; box-shadow: var(--clay); }
.shot figcaption {
  margin-top: 8px; font-size: 14px; line-height: 1.6; color: var(--ink-3);
}
/* 定高展示：高度锁死、宽度自适应 —— 比例不变，靠 grid 居中。
   不用 width:100% + object-fit:contain，因为那样元素盒子仍是满栏宽，
   阴影会绕着"留白后的空框"画一圈，而不是贴着图本身。 */
.shot__box--fit {
  display: grid;
  place-items: center;
  height: var(--shot-h, 320px);
}
/* 真正的定高：元素铺满整格，由 object-fit:contain 在格子里按原始比例缩放并居中。
   这样比 360 矮的图会被放大到 360、比 360 高的会缩到 360，一排图高度完全一致。
   代价是元素盒子始终是满栏宽，所以这里要关掉 clay 阴影 ——
   否则窄图两侧的留白也会被框进阴影里，看起来像给空气打了个框。
   类名重复一次是为了压过 .shot img 的 width:100%（同权重靠源码顺序决胜太脆弱）。 */
.shot__box--fit.shot__box--fit img,
.shot__box--fit.shot__box--fit video {
  width: 100%;
  /* 直接给确定值，不写 height:100% —— 盒子是 grid，隐式行高是 auto，
     百分比高度找不到确定的解析对象就会回退成 auto：图按原始比例长到几百上千像素，
     再被盒子的 overflow:hidden 切掉。这就是"定了高却被遮挡"的原因。 */
  height: var(--shot-h, 320px);
  object-fit: contain;
  object-position: center;
  box-shadow: none;
  border-radius: 0;
}
/* 图还没来时占同样的高度，放进去之后版面不跳 */
.shot__box--fit.is-missing { height: var(--shot-h, 320px); min-height: 0; }

/* 图还没放进来时：虚线框 + 直接写出该存到哪个路径。
   比一张碎图好，也比什么都不显示好 —— 它自己就是待办清单。 */
.shot__box.is-missing {
  display: grid; place-items: center; gap: 4px;
  min-height: 132px; padding: 18px; text-align: center;
  border: 1.5px dashed color-mix(in srgb, var(--tip, var(--focus)) 45%, var(--line));
  background: repeating-linear-gradient(
    -45deg, transparent 0 9px,
    color-mix(in srgb, var(--tip, var(--focus)) 5%, transparent) 9px 18px);
}
.shot__box.is-missing::before {
  content: "图片待放入"; font-size: 14px; font-weight: 600; color: var(--ink-2);
}
.shot__box.is-missing::after {
  content: var(--miss, "assets/shots/…");
  font-family: var(--mono); font-size: 14px; color: var(--ink-3);
  word-break: break-all;
}

/* 素材位：图 / Demo 还没进来时占一个明确的框（见 app.js renderSlot） */
.shot--slot {
  margin-top: 14px;
  aspect-ratio: var(--ratio, 16/9);
  border: 1.5px dashed color-mix(in srgb, var(--tip, var(--focus)) 45%, var(--line));
  border-radius: var(--r-sm);
  background:
    repeating-linear-gradient(
      -45deg,
      transparent 0 9px,
      color-mix(in srgb, var(--tip, var(--focus)) 5%, transparent) 9px 18px
    );
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; padding: 14px; text-align: center;
}
.shot__kind {
  font-family: var(--mono); font-size: 14px; letter-spacing: .12em;
  text-transform: uppercase; padding: 4px 10px; border-radius: 999px;
  background: color-mix(in srgb, var(--tip, var(--focus)) 16%, white);
  color: color-mix(in srgb, var(--tip, var(--focus)) 75%, black);
}
.shot__label { font-size: 14px; line-height: 1.4; color: var(--ink-2); max-width: 30ch; }
.shot__note { font-family: var(--mono); font-size: 14px; color: var(--ink-3); max-width: 34ch; }

/* ── roleswim —— 泳道图 ─────────────────────────────────────────
   一个节点一行，落在自己那条泳道列上；行与行之间是折线连接器。
   竖着的虚线是泳道分隔（借 repeating-linear-gradient 画，不占 DOM）。 */
.viz--roleswim { font-size: 14px; }
.rswim {
  display: grid;
  grid-template-columns: 92px repeat(var(--n, 3), minmax(0, 1fr));
  align-items: center;
  column-gap: 0; row-gap: 0;
  position: relative;
  padding-bottom: 8px;
}
/* 泳道分隔线：从角色表头下方一直拉到底 */
.rswim::before {
  content: ""; position: absolute; inset: 34px 0 0 92px;
  pointer-events: none; z-index: 0;
  background: repeating-linear-gradient(
    to right,
    transparent 0 calc(100% / var(--n) - 1px),
    color-mix(in srgb, var(--ink) 9%, transparent) calc(100% / var(--n) - 1px) calc(100% / var(--n))
  );
}
.rswim > * { position: relative; z-index: 1; }
.rswim-role {
  font-family: var(--mono); font-size: 14px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
  padding: 6px 4px 10px; text-align: center;
}
.rswim-corner { padding: 6px 4px 10px; }
/* 阶段名：竖着占满这一段的所有行 */
.rswim-ph {
  grid-column: 1; align-self: stretch;
  display: flex; align-items: center; justify-content: center;
  padding: 6px 8px 6px 0;
}
.rswim-ph span {
  padding: 8px 10px; border-radius: 8px; text-align: center;
  background: color-mix(in srgb, var(--tip, var(--focus)) 12%, white);
  color: var(--ink); font-weight: 600; font-size: 14px; line-height: 1.35;
}
.rswim-slot { display: flex; justify-content: center; padding: 0 8px; }
.rstep {
  padding: 9px 12px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--ink) 4%, white);
  color: var(--ink-2); text-align: center; line-height: 1.35;
  box-shadow: var(--clay);
}
.rstep--me {
  background: color-mix(in srgb, var(--tip, var(--focus)) 18%, white);
  color: var(--ink); font-weight: 600;
}
.rstep--end {
  background: var(--ink); color: var(--paper); font-weight: 600;
}
/* 连接器：跨满所有泳道列，内部按百分比定位到泳道中线 */
.rswim-link {
  grid-column: 2 / -1; position: relative; height: 30px;
}
.rl-h, .rl-up, .rl-dn { position: absolute; background: var(--ink-3); }
.rl-h { top: 15px; height: 1.5px; }               /* 横向换道段 */
.rl-up { top: 0; height: 16px; width: 1.5px; }    /* 从上一节点出来 */
.rl-dn { top: 14px; height: 16px; width: 1.5px; } /* 落到下一节点 */
/* 箭头：用两条边转 45° 拼一个 chevron，不引进图片也不写 SVG */
.rl-dn::after {
  content: ""; position: absolute; left: 50%; bottom: -1px;
  width: 7px; height: 7px; transform: translateX(-50%) rotate(45deg);
  border-right: 1.5px solid var(--ink-3); border-bottom: 1.5px solid var(--ink-3);
}

@media (max-width: 760px) {
  /* 窄屏摆不下三条泳道 + 连接器，退回竖排：角色写进节点里 */
  .rswim { grid-template-columns: 1fr; }
  .rswim::before, .rswim-role, .rswim-corner, .rswim-link { display: none; }
  .rswim-ph { grid-column: 1 !important; grid-row: auto !important; justify-content: flex-start; padding: 10px 0 4px; }
  .rswim-slot { grid-column: 1 !important; justify-content: flex-start; padding: 3px 0; }
}

/* practice —— 三行共用列的链路图：需求环节 / AI 做什么 / PDE 做什么 上下对齐 */
.viz--practice { display: grid; gap: 6px; }
.prac-scroll {
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 6px;
  scrollbar-width: thin;
}
.prac-grid {
  display: grid;
  grid-template-columns: 88px repeat(var(--n, 9), minmax(132px, 1fr));
  gap: 8px 10px;
  align-items: stretch;
  /* 字号从 ~11px 提到 14px，每格得跟着放宽，否则每个词都断成两行 */
  min-width: 1290px;
}
.prac-k {
  grid-column: 1;
  display: flex; align-items: center;
  font-family: var(--mono); font-size: 14px; letter-spacing: .03em;
  color: var(--ink-3); white-space: nowrap;
}
/* 顶部阶段条：深色，跨列 */
.prac-phase {
  display: flex; align-items: center; justify-content: center;
  padding: 5px 8px; border-radius: 6px;
  background: var(--ink); color: var(--paper);
  font-size: 14px; font-weight: 600; letter-spacing: .02em;
  text-align: center;
}
/* PDE 行：浅色，跨列 */
.prac-pde {
  display: flex; align-items: center; justify-content: center;
  padding: 6px 8px; border-radius: 6px;
  background: color-mix(in srgb, var(--tip, var(--focus)) 10%, white);
  font-size: 14px; line-height: 1.35; color: var(--ink-2); text-align: center;
}
/* AI 行：每列一个（分支列两个）
   下缘留空带给产物标签 —— 标签高度约 28px，空带必须 ≥ 它，
   否则会压住本列步骤框、或伸进下一行 PDE 块。 */
.prac-col { position: relative; display: flex; flex-direction: column; gap: 4px; padding-bottom: 36px; }
.prac-stack { display: flex; flex-direction: column; gap: 4px; flex: 1; justify-content: center; }
/* 支线步骤：虚线是有意义的（这条不是必经），所以卡片去边之后
   用一圈 outline 把这个语义留住 —— 它标的是"性质"，不是"这是张卡片"。 */
.prac-stack--branch .pstep {
  outline: 1px dashed color-mix(in srgb, var(--tip, var(--focus)) 40%, var(--line));
  outline-offset: -1px;
  background: color-mix(in srgb, var(--tip, var(--focus)) 6%, white);
}
.pstep {
  display: flex; align-items: center; justify-content: center;
  padding: 6px 8px; border-radius: 6px; font-size: 14px; line-height: 1.35;
  background: color-mix(in srgb, var(--ink) 4%, white);
  color: var(--ink); text-align: center;
  /* flex: 1 会连 flex-shrink:1 一起给出去。分支列里叠着两个步骤、
     行高又由别的单步骤列定，这一列就被压到比文字还矮 —— 字直接叠到一起。
     0 是不许压：列撑高，整行跟着高。字号提到 14px 之后尤其明显。 */
  flex: 1 0 auto;
}
.pstep--end {
  background: color-mix(in srgb, var(--tip, var(--focus)) 82%, white);
  border-color: transparent; color: white; font-weight: 600;
}
/* 产物标签：坐在两列之间的连接处，贴在空带底部 */
.prac-out {
  position: absolute; right: 0; bottom: 4px;
  transform: translateX(52%);
  display: inline-flex; align-items: center; gap: 3px;
  padding: 3px 9px; border-radius: 999px;
  background: var(--paper); box-shadow: var(--clay);
  font-family: var(--mono); font-size: 14px; letter-spacing: .02em;
  color: var(--ink-3); white-space: nowrap; z-index: 2;
  pointer-events: none;
}
.prac-out i { font-style: normal; color: color-mix(in srgb, var(--tip, var(--focus)) 70%, black); }
.prac-hint { font-family: var(--mono); font-size: 14px; color: var(--ink-3); }
.pstep--pde {
  background: color-mix(in srgb, var(--tip, var(--focus)) 16%, white);
}
.prac-arrow {
  width: 14px; height: 1px; background: var(--line); display: inline-block;
}

.hist, .gear, .lines { display: grid; gap: 12px; }
.hist__item, .gear__item {
  padding: 14px 16px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
}
.hist__when, .gear__name { font-family: var(--mono); font-size: 14px; color: var(--ink-3); }
.hist__role, .lines__v { font-weight: 600; color: var(--ink); }
.hist__org, .hist__note, .gear__items { font-size: 14px; color: var(--ink-2); margin-top: 2px; }
.lines__row { display: flex; gap: 10px; align-items: baseline; }
.lines__k { width: 3.5em; font-family: var(--mono); font-size: 14px; color: var(--ink-3); }

/* —— About-me agent (corner dialog) —— */
.agent-dock {
  position: fixed; left: 20px; bottom: 20px; z-index: 45;
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
}
.agent-tip {
  position: relative;
  margin-left: 4px;
  padding: 8px 12px;
  border-radius: 12px;
  background: var(--ink);
  color: var(--paper);
  font-size: 14px; font-weight: 600;
  box-shadow: var(--shadow-lift);
  animation: tip-float 2.4s var(--e) infinite;
  max-width: 200px;
}
.agent-tip::after {
  content: "";
  position: absolute; left: 22px; bottom: -6px;
  border: 6px solid transparent;
  border-top-color: var(--ink);
  border-bottom: 0;
}
.agent-dock.is-open .agent-tip { display: none; }
@keyframes tip-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-4px); }
}
.agent-fab {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: 999px;
  background: var(--focus);
  color: #fff;
  border: 3px solid var(--paper);
  box-shadow: var(--shadow-lift), 0 0 0 4px color-mix(in srgb, var(--focus) 24%, transparent);
  transition: transform var(--d) var(--e), filter var(--d) var(--e);
}
.agent-fab__ico { width: 26px; height: 26px; }
.agent-fab:hover,
.agent-fab.is-open {
  transform: scale(1.06);
  filter: brightness(1.05);
}

.agent {
  position: fixed; left: 18px; bottom: 92px; z-index: 46;
  width: min(360px, calc(100vw - 36px));
  max-height: min(520px, calc(100svh - 120px));
  display: flex; flex-direction: column;
  background: color-mix(in srgb, var(--paper) 52%, transparent);
  border: 1px solid color-mix(in srgb, white 65%, var(--line));
  border-radius: 24px;
  box-shadow: var(--shadow-lift);
  backdrop-filter: blur(20px) saturate(1.1);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
  overflow: hidden;
}
.agent[hidden] { display: none !important; }
.agent__hd {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 14px 14px 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.agent__title {
  font-family: var(--display); font-weight: 800; font-size: 1.1rem;
  letter-spacing: -0.02em; color: var(--ink);
}
.agent__close {
  width: 32px; height: 32px; border-radius: 999px;
  font-size: 1.2rem; color: var(--ink-3);
  background: color-mix(in srgb, var(--paper) 40%, transparent);
}
.agent__close:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink); }
.agent__thread {
  flex: 1; overflow-y: auto; padding: 14px;
  display: flex; flex-direction: column; gap: 10px;
  min-height: 180px;
}
.agent__msg {
  max-width: 92%;
  padding: 10px 12px; border-radius: 14px;
  font-size: 14px; line-height: 1.55;
}
.agent__msg--bot {
  align-self: flex-start;
  background: color-mix(in srgb, var(--paper) 70%, transparent);
  border: 1px solid color-mix(in srgb, white 50%, var(--line));
  color: var(--ink-2);
  border-bottom-left-radius: 4px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.agent__msg--user {
  align-self: flex-end;
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  color: var(--paper);
  border-bottom-right-radius: 4px;
}
.agent__prompts {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 10px 14px 14px;
  border-top: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  background: color-mix(in srgb, var(--paper) 28%, transparent);
}
.agent__chip {
  padding: 6px 10px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, white 40%, var(--line));
  background: color-mix(in srgb, var(--paper) 55%, transparent);
  font-size: 14px; color: var(--ink-2);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: border-color var(--d) var(--e), color var(--d) var(--e);
}
.agent__chip:hover:not(:disabled) {
  border-color: var(--focus); color: var(--focus);
}
.agent__chip:disabled {
  opacity: .45; cursor: default;
}

.zoombar {
  position: fixed; right: 18px; bottom: 18px; z-index: 40;
  display: flex; align-items: center; gap: 2px;
  padding: 4px; border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 78%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 70%, white);
  box-shadow: var(--shadow);
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
}
.zoombar__btn, .zoombar__pct {
  min-width: 36px; height: 32px; padding: 0 10px;
  border-radius: 999px; font-size: 14px; color: var(--ink-2);
}
.zoombar__pct {
  font-family: var(--mono); font-size: 14px; min-width: 52px;
}
.zoombar__btn:hover, .zoombar__pct:hover {
  background: color-mix(in srgb, var(--focus) 10%, white); color: var(--focus);
}

@media (max-width: 760px) {
  .chrome__hint { display: none; }
  .sticky { width: 180px; }
  .sticky--hero { width: 240px; }
  .sticky--info { width: 220px; }
  .project__shape { width: 190px; }
  .project__body { height: 150px; }
  .sticky--hero { width: 280px; }
  .sec { width: min(340px, 86vw); }
  .viz--compare, .viz--dual { grid-template-columns: 1fr; }
  .viz--features { grid-template-columns: 1fr; }
  .aud { grid-template-columns: 1fr; gap: 7px; }
  .sec__facts { grid-template-columns: 1fr; }
  .agent { bottom: 88px; max-height: min(460px, calc(100svh - 100px)); }
  .agent-tip { font-size: 14px; max-width: 160px; }
}

/* ============================================================
   artifact —— 内联进文档的产物（不是 iframe）
   CSS 已由 tools/build-artifacts.py 锁进 .art-<id>，两边互不污染。
   产物按自己的设计宽度排版，再整体等比缩到卡片宽度：整幅外露，无内滚动。
   ============================================================ */
.artifact { margin: 0; display: grid; gap: 6px; }
.art__step { display: grid; gap: 7px; }
.art__step-t {
  justify-self: start;
  padding: 5px 12px; border-radius: var(--r-pill, 999px);
  background: color-mix(in srgb, var(--tip, var(--focus)) 16%, white);
  border: 1px solid color-mix(in srgb, var(--tip, var(--focus)) 32%, var(--line));
  font-family: var(--mono); font-size: 14px; letter-spacing: .04em; color: var(--ink-2);
}
.art__step-cap, .art__cap { font-size: 14px; line-height: 1.6; color: var(--ink-3); }
.art__link {
  justify-self: center;
  font-family: var(--display); font-weight: 800; font-size: 1.1rem;
  color: var(--ink-3); line-height: 1; padding: 2px 0;
}
.art__viewport {
  position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: #fff;
}
.art {
  width: calc(var(--w, 1200) * 1px);
  transform: scale(var(--s, 1));
  transform-origin: 0 0;
}
/* 产物内部一律不吃站点的盒模型/字体，交给它自己那份作用域 CSS */
.art, .art * { box-sizing: border-box; }

/* —— 支线卡片：挂在主线下方（Demo / 原型这类） —— */
.sec--branch { --tip: var(--focus); }
.sec-down {
  position: absolute;
  display: grid; justify-items: center; gap: 4px;
  pointer-events: none; z-index: 1;
}
.sec-down__line {
  width: 2px; height: 26px;
  background: repeating-linear-gradient(
    to bottom, var(--ink-3) 0 5px, transparent 5px 10px);
}
.sec-down__cue {
  padding: 5px 12px; border-radius: var(--r-pill, 999px);
  background: var(--ink); color: var(--paper);
  font-size: 14px; font-weight: 600; white-space: nowrap;
  box-shadow: var(--shadow);
}
.sec-down__tip {
  font-family: var(--display); font-weight: 800; font-size: 1.2rem;
  color: var(--ink-3); line-height: 1;
}

/* —— MacBook 13" 画布：1280×800 真实渲染，等比缩放显示 —— */
.embed--mac .embed__stack {
  height: auto; aspect-ratio: 1280 / 800;
  background: #fff;
}
.embed--mac .embed__pane { position: absolute; inset: 0; overflow: hidden; }
.embed--mac iframe {
  width: 1280px; height: 800px;
  transform: scale(var(--s, .52));
  transform-origin: 0 0;
}

/* ============================================================
   阅读态 —— 项目页不再是画布，是一篇长文
   落地页保留画布（挑项目时"逛"是对的），进了 case 就该安静地读。

   滚动是怎么成立的：.stage 固定满屏并 overflow-y:auto，
   .board 必须回到静态流里（不能 fixed），否则内容撑不高它，
   .stage 就没有可滚的东西 —— 这是最容易踩的一脚。
   ============================================================ */
body.is-read .stage {
  overflow-y: auto; overflow-x: hidden;
  cursor: default; touch-action: auto;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}
html.has-cur body.is-read .stage { cursor: none; }
body.is-read .board {
  position: relative; inset: auto;
  min-height: 100%;
}
body.is-read .board__world {
  position: static; width: auto; height: auto;
  transform: none !important; will-change: auto;
}
body.is-read .zoombar { display: none; }

.read {
  /* 页边距和卡片内边距都用 clamp 连续伸缩，而不是在断点上"跳"一下。
     取值逻辑：宽屏保持原来的呼吸感（28 / 40），窄屏一路收到能用的下限（10 / 16）。
     中间跟着视口线性走，所以 900、700、500 这些尺寸下都不会突然显得挤或突然空一片。
     卡片内边距单独抽成 --sec-pad，让 .sec 和这里共用同一个来源，不会各调各的。 */
  --pad-x: clamp(10px, 2.2vw, 28px);
  --sec-pad: clamp(16px, 2.8vw, 40px);

  /* 栏宽不是固定的 70%，而是随视口线性变化 —— 屏幕越窄，占比越高、两侧留白收得越快。
     写死 70vw 的话比例恒定：1000px 的屏上卡片只有 700，两边各空 128，看着就是"没排满"。
     42vw + 402px 这条线经过：1440→1008(70%)、1200→907(76%)、1000→823(82%)、
     860→764(89%)。1330 封顶，再宽正文一行会长到读不动。 */
  --col: min(calc(42vw + 402px), 1330px);
  max-width: calc(var(--col) + var(--pad-x) * 2);
  margin: 0 auto;
  padding: clamp(84px, 9vw, 116px) var(--pad-x) clamp(120px, 16vw, 220px);
  /* minmax(0,1fr) 不能写成 1fr：grid item 默认 min-width:auto，
     下面「破栏」的产物有 1240px 的固定宽，会把这一列整个撑成 1240 ——
     正文栏跟着被顶偏、右边被 overflow-x:hidden 切掉。
     只有有产物的项目页会犯，看起来就像"这页整体放大了"。 */
  grid-template-columns: minmax(0, 1fr);
  gap: 76px;
  display: grid;
}

/* .rsec 只负责"从画布定位改成文档流"，卡片外观继续吃 .sec */
.rsec {
  position: relative;
  left: auto; top: auto;
  width: 100%;
  min-width: 0;          /* 同上：别让破栏的产物撑宽正文栏 */
  min-height: 0;
  cursor: inherit;
}
.rsec .sec__title { font-size: 1.7rem; line-height: 1.34; max-width: none; }
.rsec .sec__lines { gap: 20px; }
.rsec .sec__lines p { font-size: .97rem; line-height: 1.85; }
.rsec .sec__facts { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
.rsec .cover-split .sec__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rsec .viz, .rsec .artifact, .rsec .shot { margin-top: 34px; }

.rsec--cover .sec__title { font-size: 2.4rem; line-height: 1.22; max-width: none; }
.rsec--cover .sec__lines p:first-child { font-size: 1.14rem; color: var(--ink); }
.rsec--cover .cover-split { margin-top: 4px; }
@media (max-width: 900px) {
  .rsec .cover-split .sec__facts {
    display: flex;
    flex-wrap: nowrap;
    grid-template-columns: none;
  }
}

/* 支线引子：一段虚线 + 一颗胶囊，把 Demo 这类支线接在正文下面 */
.read__cue { display: grid; justify-items: center; gap: 6px; margin: -34px 0 -34px; }
.read__cue-line {
  width: 2px; height: 30px;
  background: repeating-linear-gradient(to bottom, var(--ink-3) 0 5px, transparent 5px 10px);
}
.read__cue-t {
  padding: 6px 14px; border-radius: var(--r-pill, 999px);
  background: var(--ink); color: var(--paper);
  font-size: 14px; font-weight: 600;
  box-shadow: var(--shadow);
}
.rsec--branch { border-style: dashed; }

@media (max-width: 760px) {
  /* 手机上 70% 是灾难：375 的屏只剩 262 的卡片。窄屏一直是"能用的全用上"。
     左右边距已经由 --pad-x 收到 10px，这里只需要放开宽度和纵向间距。 */
  .read { max-width: none; gap: 56px; }
  .rsec .sec__title { font-size: 1.35rem; }
  .rsec--cover .sec__title { font-size: 1.9rem; }
}

/* ============================================================
   产物（内嵌原型）在阅读态里的宽度
   曾经它是「破栏」的：正文只有 824px 时，图得冲出卡片才够看。
   栏宽提到 70% 之后这个补偿就不需要了 —— 现在反过来，
   产物收在卡片内、比正文再窄一档，说明文字仍与正文左对齐。
   ============================================================ */
.rsec { overflow: visible; }
.rsec .artifact {
  width: 100%;
  margin-inline: auto;
}
.rsec .artifact .art__step-cap,
.rsec .artifact .art__cap { max-width: 100%; }
@media (max-width: 760px) {
  .rsec .artifact { width: 100%; }
}


/* ============================================================
   电子简历 —— 画布上的一份文件 + 点开的面板
   ============================================================ */
.filecard {
  width: auto;
  /* 顶边与同排 sticky / 项目齐平，不额外垫高 */
  padding: 0 18px 18px;
  background: transparent;
  border: 0;
  box-shadow: none;
  color: var(--ink);
  text-align: left;
}
/* 一沓纸 —— 和项目便签底下那叠白纸是同一种物件，只是这次纸本身是主角。
   三张叠着，后两张各歪一点露出边角；最上面那张写字。 */
.filecard__stack {
  position: relative;
  display: block;
  width: 210px;
  height: 252px;
}
.filecard__sheet {
  position: absolute; inset: 0;
  background: var(--paper);
  box-shadow:
    0 1px 3px rgba(10, 10, 10, .10),
    0 8px 22px rgba(10, 10, 10, .10);
  transition: rotate var(--d) var(--e), translate var(--d) var(--e), box-shadow var(--d) var(--e);
}
.filecard__sheet--c { rotate: 4.5deg;  translate: 10px 8px; }
.filecard__sheet--b { rotate: -3.5deg; translate: -9px 4px; }
.filecard__sheet--top {
  display: grid;
  align-content: start;
  gap: 8px;
  padding: 26px 22px 20px;
  box-shadow:
    0 2px 4px rgba(10, 10, 10, .10),
    0 14px 32px rgba(10, 10, 10, .15);
}
.filecard__k {
  font-family: var(--mono); font-size: 14px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3);
}
.filecard__t {
  font-family: var(--display); font-weight: 800; font-size: 1.3rem;
  letter-spacing: -0.02em; color: var(--ink);
}
/* 纸上"写过字"的痕迹，跟便签背后那两张白纸用同一支笔 */
.filecard__lines { display: grid; gap: 9px; margin-top: 6px; }
.filecard__lines i {
  display: block; height: 7px;
  background-image: var(--scribble);
  background-repeat: no-repeat;
  background-size: 100% 7px;
}
.filecard__lines i:nth-child(1) { width: 88%; }
.filecard__lines i:nth-child(2) { width: 96%; }
.filecard__lines i:nth-child(3) { width: 72%; }
.filecard__lines i:nth-child(4) { width: 84%; }
.filecard__m {
  font-size: 14px; color: var(--ink-3);
  margin-top: 10px;
}
/* hover：整沓散开一点，和项目便签「拨开纸堆」是同一套反馈语言 */
.filecard:hover .filecard__sheet--c,
.filecard.is-dragging .filecard__sheet--c { rotate: 9deg;  translate: 22px 12px; }
.filecard:hover .filecard__sheet--b,
.filecard.is-dragging .filecard__sheet--b { rotate: -8deg; translate: -20px 8px; }
.filecard:hover .filecard__sheet--top,
.filecard.is-dragging .filecard__sheet--top {
  translate: 0 -4px;
  box-shadow:
    0 4px 8px rgba(10, 10, 10, .12),
    0 22px 48px rgba(10, 10, 10, .22);
}

/* —— 面板 —— */
.rez-veil {
  position: fixed; inset: 0; z-index: 48;
  background: rgba(10, 10, 10, .28);
  backdrop-filter: blur(2px);
}
.rez-veil[hidden], .rez[hidden] { display: none !important; }
.rez {
  position: fixed; z-index: 49;
  left: 50%; top: 50%; translate: -50% -50%;
  width: min(560px, calc(100vw - 32px));
  max-height: min(760px, calc(100svh - 64px));
  display: flex; flex-direction: column;
  background: var(--paper);
  border-radius: 20px;
  box-shadow: var(--shadow-lift);
  overflow: hidden;
}
.rez[hidden] { display: none; }
.rez__hd {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 20px 22px 14px;
  border-bottom: 1px solid var(--line);
}
/* 无标题：只留关闭钮浮在内容右上 */
.rez__hd--bare {
  position: absolute; top: 0; right: 0; z-index: 2;
  justify-content: flex-end;
  padding: 12px 12px 0 0;
  border-bottom: 0;
  pointer-events: none;
}
.rez__hd--bare .rez__close { pointer-events: auto; }
.rez__title {
  font-family: var(--display); font-weight: 800; font-size: 1.3rem;
  letter-spacing: -0.02em; color: var(--ink);
}
.rez__close {
  width: 34px; height: 34px; border-radius: 999px;
  font-size: 1.3rem; line-height: 1; color: var(--ink-3);
  display: grid; place-items: center;
  transition: background-color var(--d) var(--e), color var(--d) var(--e);
}
.rez__close:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink); }
.rez__body { overflow-y: auto; padding: 22px 22px 8px; display: grid; gap: 22px; }
.rez:has(.rez__hd--bare) .rez__body { padding-top: 18px; padding-right: 48px; }
.rez__lead { font-size: 1.05rem; font-weight: 700; color: var(--ink); }
.rez__bio { display: grid; gap: 10px; margin-top: -12px; }
.rez__bio p { font-size: 14px; line-height: 1.75; color: var(--ink-2); }
.rez__sec { display: grid; gap: 10px; }
.rez__k {
  font-family: var(--mono); font-size: 14px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
}
.rez__row {
  display: grid; gap: 2px;
  padding: 12px 14px; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ink) 3%, white);
  box-shadow: var(--clay);
}
.rez__row b { font-size: 14px; font-weight: 700; color: var(--ink); }
.rez__row span { font-size: 14px; color: var(--ink-2); }
.rez__row em { font-style: normal; font-size: 14px; color: var(--ink-3); }
/* 时间线：一条竖轴 + 节点。轴用伪元素画在圆点的中线上，
   最后一项不再往下延伸，所以线到最后一个点为止。 */
.tl { display: grid; gap: 2px; }
.tl__i {
  position: relative;
  padding: 0 0 20px 24px;
}
.tl__i::before {                     /* 竖轴 */
  content: "";
  position: absolute; left: 4.5px; top: 6px; bottom: -2px;
  width: 1.5px;
  background: var(--line);
}
.tl__i:last-child { padding-bottom: 0; }
.tl__i:last-child::before { display: none; }
.tl__dot {
  position: absolute; left: 0; top: 4px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--paper);
  box-shadow: 0 0 0 2px var(--ink-3);
}
.tl__i--now .tl__dot { background: var(--ink); box-shadow: 0 0 0 2px var(--ink); }
.tl__when {
  font-family: var(--mono); font-size: 14px; color: var(--ink-3);
  letter-spacing: .02em;
}
.tl__what { font-size: 14px; font-weight: 700; color: var(--ink); margin-top: 2px; }
.tl__who { font-size: 14px; color: var(--ink-2); }
.tl__note { font-size: 14px; color: var(--ink-3); margin-top: 2px; }

.rez__gear { display: grid; gap: 6px; }
.rez__gear-k { font-size: 14px; font-weight: 600; color: var(--ink-2); }
.rez__links { display: grid; gap: 8px; }
.rez__link {
  font-size: 14px; color: var(--ink);
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--ink-3); text-decoration-thickness: 1px;
}
.rez__link:hover { text-decoration-color: var(--ink); text-decoration-thickness: 2px; }
.rez__link--flat { text-decoration: none; color: var(--ink-2); }
.rez__ft { padding: 14px 22px 20px; border-top: 1px solid var(--line); }
.rez__dl {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px; border-radius: var(--r-sm);
  background: var(--ink); color: var(--paper);
  font-size: 14px; font-weight: 700;
  transition: opacity var(--d) var(--e);
}
.rez__dl svg { width: 18px; height: 18px; }
.rez__dl:hover { opacity: .86; }
.rez__note { font-size: 14px; color: var(--ink-3); text-align: center; }

/* ============================================================
   贴纸栏 —— 落地页底部，拖一张贴到板上
   ============================================================ */
.tray {
  position: fixed; z-index: 44;
  left: 50%; bottom: 18px; translate: -50% 0;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 10px 14px 12px;
  border-radius: 22px;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lift);
  backdrop-filter: blur(18px) saturate(1.15);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
  transition: opacity var(--d) var(--e), translate var(--d) var(--e);
}
/* 进了项目页就收起来 */
body.no-tray .tray {
  opacity: 0; translate: -50% 24px; pointer-events: none;
}
.tray__hint {
  font-family: var(--mono); font-size: 14px; letter-spacing: .04em;
  color: var(--ink-3);
}
.tray__row { display: flex; align-items: flex-end; gap: 6px; }
.tray__item {
  width: 62px; height: 62px;
  display: grid; place-items: center;
  border-radius: 14px;
  background: transparent;
  cursor: grab;
  touch-action: none;
  /* 依次浮现，像一排贴纸被摊开 */
  animation: tray-in .4s var(--e) both;
  animation-delay: calc(var(--i) * 40ms);
  transition: translate var(--d) var(--e), scale var(--d) var(--e);
}
@keyframes tray-in {
  from { opacity: 0; translate: 0 14px; scale: .8; }
  to   { opacity: 1; translate: 0 0;    scale: 1; }
}
.tray__item img {
  width: 100%; height: 100%; object-fit: contain;
  /* 贴纸多半是去背 PNG，阴影要跟着轮廓走 —— box-shadow 会画成方框 */
  filter: drop-shadow(0 2px 3px rgba(10, 10, 10, .22));
  transition: filter var(--d) var(--e);
  pointer-events: none;
}
.tray__item:hover, .tray__item:focus-visible {
  translate: 0 -8px; scale: 1.14;
}
.tray__item:hover img {
  filter: drop-shadow(0 6px 10px rgba(10, 10, 10, .28));
}
.tray__item:active { cursor: grabbing; }
/* 正被拖出去的那一格：留个凹陷提示它暂时被拿走了 */
.tray__item.is-taken {
  opacity: .3; scale: .88; translate: 0 0;
}

/* 跟手的幽灵 */
.sticker-ghost {
  position: fixed; z-index: 60;
  width: 132px; height: 132px;
  translate: -50% -50%;
  pointer-events: none;
  rotate: -6deg;
  animation: ghost-grab .18s var(--e) both;
}
.sticker-ghost img {
  width: 100%; height: 100%; object-fit: contain;
  filter: drop-shadow(0 10px 18px rgba(10, 10, 10, .3));
}
@keyframes ghost-grab {
  from { scale: .7; opacity: .4; }
  to   { scale: 1;  opacity: 1; }
}
/* 拖回栏上＝放弃，缩一下给个"不会贴"的信号 */
.sticker-ghost.is-over-tray { scale: .72; opacity: .5; rotate: 0deg; }

/* 贴到板上的那张 */
.sticker {
  position: absolute; z-index: 6;
  width: 132px; height: 132px;
  rotate: var(--tilt, 0deg);
  cursor: grab;
  touch-action: none;
  transition: rotate .32s var(--e), scale .32s var(--e), opacity .2s linear;
}
.sticker img {
  width: 100%; height: 100%; object-fit: contain;
  filter: drop-shadow(0 4px 8px rgba(10, 10, 10, .24));
  pointer-events: none;
  transition: filter var(--d) var(--e);
}
/* 落地瞬间：大一点、正一点，再弹回自己的角度 */
.sticker.is-landing { rotate: 0deg; scale: 1.28; }
.sticker.is-held { cursor: grabbing; scale: 1.08; z-index: 12; }
.sticker.is-held img { filter: drop-shadow(0 10px 18px rgba(10, 10, 10, .3)); }
.sticker.is-gone { scale: .5; opacity: 0; }

@media (max-width: 760px) {
  .tray { bottom: 10px; padding: 8px 10px 10px; }
  .tray__item { width: 50px; height: 50px; }
  .tray__hint { display: none; }
}


/* ============================================================
   灯箱 —— 正文里的图/视频点开放大
   ============================================================ */
/* 没有自定义光标时（触屏 / 降低动效）退回原生 zoom-in；
   开了自定义光标的话，第 84 行那条 * { cursor:none !important } 会压掉它，
   "能放大"改由光标标签 TAG.zoom 表达。 */
.shot__box img, .shot__box video,
.flow-step__img img, .ba-item__img img {
  cursor: zoom-in;
}
body.lb-open { overflow: hidden; }
.lb[hidden] { display: none !important; }
.lb {
  position: fixed; inset: 0; z-index: 70;
  display: grid; place-items: center;
  grid-template-rows: 1fr auto;
  gap: 14px; padding: 40px 28px 28px;
  background: rgba(10, 10, 10, .82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: lb-in .16s var(--e) both;
}
@keyframes lb-in { from { opacity: 0 } to { opacity: 1 } }
.lb__stage {
  display: grid; place-items: center;
  max-width: 100%; max-height: 100%;
  animation: lb-pop .2s var(--e) both;
}
@keyframes lb-pop { from { scale: .94; opacity: 0 } to { scale: 1; opacity: 1 } }
.lb__stage { touch-action: none; }
.lb__stage.is-zoomed { cursor: grab; }
.lb__stage.is-zoomed:active { cursor: grabbing; }
.lb__stage img, .lb__stage video {
  transform-origin: center;
  will-change: transform;
  max-width: min(1400px, 92vw);
  max-height: calc(100svh - 140px);
  width: auto; height: auto;
  border-radius: 6px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, .5);
  cursor: default;
}
.lb__cap {
  font-size: 14px; line-height: 1.6; color: rgba(255, 255, 255, .82);
  text-align: center; max-width: 74ch;
}
.lb__cap[hidden] { display: none; }
.lb__close {
  position: absolute; right: 18px; top: 18px; z-index: 2;
  width: 40px; height: 40px; border-radius: 999px;
  display: grid; place-items: center;
  font-size: 1.5rem; line-height: 1;
  background: rgba(255, 255, 255, .14);
  color: #fff;
  transition: background-color var(--d) var(--e);
}
.lb__close:hover { background: rgba(255, 255, 255, .26); }
