/* =========================================================
 *  我的空间 · 设计系统
 *  ---------------------------------------------------------
 *  两种页面色调：
 *    · 暗色（默认）：首页 / 相册 / 视频 / Blog —— 深蓝底 + 玫红点缀
 *    · 纸质（个人页）：浅粉底 + 深棕字
 *  想换主色调：改下面 --rose 系列即可
 * ======================================================= */

:root {
  /* 暗色页面 */
  --bg:        #12142a;
  --bg-2:      #191c34;
  --bg-3:      #22264180;
  --panel:     #1b1f38;
  --ink:       #ecedf6;
  --ink-2:     #9298b8;
  --ink-3:     #7b82a4;
  --line:      rgba(236, 237, 246, .13);
  --line-2:    rgba(236, 237, 246, .07);

  /* 强调色 */
  --rose:      #e0466f;
  --rose-2:    #f2749a;
  --rose-deep: #a82f52;
  --rose-dim:  rgba(224, 70, 111, .13);

  /* 纸质页面 */
  --paper-ink:   #241a20;
  --paper-ink-2: #8b7681;
  --paper-line:  rgba(36, 26, 32, .14);
  --paper-rose:  #c8395f;

  /* 字体 */
  --font-serif: 'Noto Serif SC', 'Source Han Serif SC', 'Songti SC', 'STSong', 'SimSun', Georgia, serif;
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
    'Microsoft YaHei', 'Noto Sans SC', sans-serif;
  --font-mono: 'JetBrains Mono', Consolas, 'SF Mono', Menlo, monospace;

  --nav-h: 62px;

  /* ---------- 圆角（想整体调圆/调方，改这三个数就行） ---------- */
  --radius:      14px;   /* 卡片、面板、弹窗 */
  --radius-sm:   8px;    /* 标签、输入框、小按钮 */
  --radius-pill: 999px;  /* 胶囊按钮 */
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  transition: background .4s ease;
}
/* 个人页：整页换成浅粉纸感 */
body[data-view='me'] {
  background: linear-gradient(158deg, #fdf9fa 0%, #f8ebf0 48%, #f1dee7 100%);
  background-attachment: fixed;
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
button, input { font: inherit; color: inherit; }
::selection { background: var(--rose); color: #fff; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: rgba(146, 152, 184, .35); border-radius: 9px; }
::-webkit-scrollbar-thumb:hover { background: var(--rose); }
::-webkit-scrollbar-track { background: transparent; }

/* =========================================================
 *  通用小部件
 * ======================================================= */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 12.5px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.eyebrow-num { color: var(--rose); font-weight: 700; letter-spacing: .1em; font-family: var(--font-mono); }
.eyebrow-line { width: 50px; height: 1px; background: currentColor; opacity: .45; flex: none; }
.eyebrow-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.page-head { margin-bottom: 26px; }
.page-title-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; margin-top: 14px; flex-wrap: wrap;
}
.page-title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: clamp(30px, 4vw, 46px);
  font-weight: 700;
  letter-spacing: .1em;
}
.page-sub { margin: 0 0 6px; color: var(--ink-2); font-size: 12.5px; letter-spacing: .12em; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.chip {
  font-size: 11px; letter-spacing: .06em;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
}
/* ⚠ 文章页的标签现在是真链接（以前是点不动的 <span>）。
   要给足"这是能点的"暗示：手型光标 + 悬停变色描边。
   用 :where() 保持低优先级，免得盖掉别处对 .chip 的定制。 */
.chip--link {
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease, background .2s ease;
}
@media (hover: hover) {
  .chip--link:hover {
    color: #fff;
    border-color: var(--rose);
    background: var(--rose-dim);
  }
}
.chip--link:focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 2px;
}
.empty { color: var(--ink-3); text-align: center; padding: 70px 0; letter-spacing: .1em; font-size: 13.5px; }
.empty code { font-family: var(--font-mono); color: var(--rose); }
/* 筛到什么都没有时的那一行：别只留一片空白（那看起来就像"坏了"） */
.empty-reset {
  display: inline-block;
  margin-left: 10px;
  padding: 3px 12px;
  border: 1px solid var(--rose-dim);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--rose-2);
  font: inherit;
  font-size: 12px;
  letter-spacing: .08em;
  cursor: pointer;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
@media (hover: hover) {
  .empty-reset:hover { background: var(--rose-dim); border-color: var(--rose); color: #fff; }
}
/* 计数归零时染成玫红，让"筛掉了"这件事在数字上也能看见 */
.blog-count[data-zero='1'] { color: var(--rose); }

.icon-btn {
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  transition: .2s;
  border-radius: 50%;
  flex: none;
}
/* ⚠ 用 @media (hover: hover) 包住：触屏上 :hover 会被"卡住"——
   手指点过 ☰ 之后，手机浏览器会一直保留 hover 状态，
   表现就是按钮外面挂着一圈玫瑰色圆环（截图里那个圈）。桌面端不变。 */
@media (hover: hover) {
  .icon-btn:hover { color: var(--rose); border-color: var(--rose); }
}

/* =========================================================
 *  顶栏
 * ======================================================= */
.topbar {
  position: sticky; top: 0; z-index: 60;
  background: #14162c;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 1px 0 rgba(0, 0, 0, .3);
}
.topbar-inner {
  max-width: 1320px; margin: 0 auto;
  height: var(--nav-h);
  padding: 0 26px;
  display: flex; align-items: center; gap: 30px;
}

.brand { display: flex; align-items: center; gap: 11px; flex: none; }
.brand-mark {
  color: var(--rose);
  font-size: 17px;
  transform: rotate(0deg);
  filter: drop-shadow(0 0 8px rgba(224, 70, 111, .5));
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text b {
  font-size: 15px; font-weight: 800; letter-spacing: .1em;
  font-family: var(--font-serif);
}
.brand-text b em { font-style: italic; color: var(--rose); }
/* 品牌名下面的小字：当前板块 / 序号 */
.brand-sub {
  font-style: normal;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-3); margin-top: 1px;
}

.tabs { display: flex; gap: 30px; margin: 0 auto; }
.tab {
  position: relative;
  display: flex; align-items: center; gap: 7px;
  padding: 6px 2px;
  font-size: 12.5px; font-weight: 600; letter-spacing: .16em;
  color: var(--ink-2);
  transition: color .2s;
}
/* 导航前面的小序号，学 Kisara 那种目录感 */
.tab-no {
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .06em; color: var(--ink-3);
  transition: color .2s;
}
.tab:hover .tab-no, .tab.active .tab-no { color: var(--rose); }
.tab svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* ⚠ 这里以前有一条 .tab::after 的下划线（.tab.active::after { scaleX(1) }）。
   它和顶栏那条"跟着鼠标滑动的指示条"（.nav-ind）压在**同一个位置**：
   指示条带 12px 玫红辉光、底下的下划线是纯色实线，两条一叠就是
   一条边缘发虚的粗线 —— 看起来像渲染重影，也是"顶栏那条线怪"的来源。
   现在只留指示条一条，它本身就负责"当前在哪 / 鼠标在哪"。
   悬停反馈交给图标轻抬 + 编号变亮（下面那几条）。 */

.tab:hover { color: var(--ink); }
.tab.active { color: #fff; }

.topbar-actions { display: flex; align-items: center; gap: 12px; flex: none; }
.avatar-btn {
  position: relative;
  width: 38px; height: 38px; border-radius: 50%;
  padding: 2px; overflow: hidden;
  border: 1px solid rgba(224, 70, 111, .55);
  transition: .22s;
}
.avatar-btn img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
/* 头像文件缺失时不要留一个破图标，显示一个玫红菱形占位 */
.avatar-btn img.is-broken { display: none; }
.avatar-btn::after {
  content: '◆';
  position: absolute; inset: 0;
  display: none; place-items: center;
  color: var(--rose); font-size: 15px;
}
.avatar-btn:has(img.is-broken)::after { display: grid; }
/* 同上：触屏点过头像后不要一直留着玫红光环 */
@media (hover: hover) {
  .avatar-btn:hover { border-color: var(--rose); box-shadow: 0 0 0 3px var(--rose-dim); transform: scale(1.05); }
}
.menu-btn { display: none; }

/* =========================================================
 *  布局
 * ======================================================= */
main { max-width: 1320px; margin: 0 auto; padding: 34px 26px 70px; }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

.footer {
  max-width: 1320px; margin: 0 auto;
  padding: 22px 26px 40px;
  color: var(--ink-3); font-size: 12.5px; letter-spacing: .1em;
  border-top: 1px solid var(--line-2);
}
body[data-view='me'] .footer { color: var(--paper-ink-2); border-top-color: var(--paper-line); }

/* =========================================================
 *  首页
 * ======================================================= */
/* ---------- 漫画分格 ---------- */
.panels {
  display: grid;
  grid-template-columns: 1.05fr 1fr 1fr;
  grid-template-rows: repeat(2, minmax(190px, 1fr));
  gap: 12px;
  margin-bottom: 26px;
}
.panel {
  position: relative;
  display: block;
  overflow: hidden;
  border: 1.5px solid rgba(10, 12, 24, .9);
  background: linear-gradient(140deg, #232847, #171a30);
  border-radius: var(--radius);
  min-height: 190px;
  isolation: isolate;
}
.panel-home { grid-row: span 2; min-height: 400px; }
.panel-art {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* 图片没加载出来时，兜底是这块渐变，而不是一片死黑 */
  background: linear-gradient(140deg, #2a2f52, #171a30);
  display: block;
  /* 插画底色偏白，稍微压一点，白底不会刺眼 */
  opacity: .92;
  filter: grayscale(1) contrast(1.06);
  transform: scale(1.01);
  transition: opacity .4s ease, filter .5s ease, transform .8s cubic-bezier(.2, .8, .2, 1);
}
/* 文件缺失 → 收掉图片层，只留渐变 */
.panel-art.is-broken {
  opacity: 0;
  visibility: hidden;
  filter: none;
}
.panel:hover .panel-art { opacity: 1; filter: none; transform: scale(1.05); }
.panel:hover .panel-art.is-broken { opacity: 0; transform: none; }
/* 网点纸质感 */
.panel::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background-image: radial-gradient(rgba(6, 8, 18, .5) 1px, transparent 1px);
  background-size: 4px 4px;
  opacity: .3;
  mix-blend-mode: multiply;
  pointer-events: none;
}
.panel-plate {
  position: absolute; left: 18px; bottom: 18px; z-index: 2;
  background: #fff; color: #14162a;
  border: 2px solid #14162a;
  border-radius: var(--radius-sm);
  padding: 10px 18px 9px;
  min-width: 168px;
  box-shadow: 5px 5px 0 rgba(6, 8, 18, .45);
  transition: transform .3s ease, box-shadow .3s ease;
}
.panel:hover .panel-plate { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 rgba(6, 8, 18, .55); }
.panel-home .panel-plate { left: 26px; bottom: 26px; padding: 14px 24px 13px; min-width: 220px; }
.p-num {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px; color: #9aa0b8; letter-spacing: .1em;
}
.panel-home .p-num { font-size: 12px; }
.p-name {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-serif);
  font-size: 21px; font-weight: 700; font-style: italic;
  letter-spacing: .04em; line-height: 1.2;
}
.panel-home .p-name { font-size: 30px; }
.p-name i { font-style: normal; font-size: .7em; color: var(--rose); }
.p-sub { display: block; font-size: 11px; color: #7b8199; letter-spacing: .16em; margin-top: 2px; }

/* ---------- 章节条 ---------- */
.chapters {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 16px 0;
  margin-bottom: 22px;
}
.chapters-label { color: var(--rose); font-weight: 700; font-size: 13.5px; letter-spacing: .08em; }
.chapters-count { color: var(--ink-3); font-family: var(--font-mono); font-size: 12px; margin: 0 22px 0 16px; }
.chapter {
  display: flex; align-items: center; gap: 9px;
  font-size: 12.5px; letter-spacing: .1em; color: var(--ink-2);
  transition: color .2s;
}
.chapter b { font-family: var(--font-mono); font-weight: 400; color: var(--rose); }
.chapter::before { content: '→'; color: var(--ink-3); margin: 0 22px 0 6px; font-size: 12px; }
.chapter:first-of-type::before { content: none; }
.chapter:hover { color: var(--ink); }

/* =========================================================
 *  筛选按钮
 * ======================================================= */
.filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 24px; }
.filter {
  padding: 7px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-2);
  font-size: 12.5px; letter-spacing: .12em;
  cursor: pointer; transition: .2s;
}
.filter i { font-style: normal; font-family: var(--font-mono); font-size: 10.5px; opacity: .6; margin-left: 6px; }
.filter:hover { color: var(--ink); border-color: var(--ink-2); }
.filter.active { color: #fff; background: var(--rose); border-color: var(--rose); }

/* =========================================================
 *  相册
 * ======================================================= */
.photo-grid { column-count: 3; column-gap: 20px; }
.photo-card {
  position: relative;
  break-inside: avoid;
  margin: 0 0 20px;
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: var(--radius);
  cursor: zoom-in;
  overflow: hidden;
  transition: border-color .25s, transform .25s;
}
.photo-card:hover { border-color: var(--rose); transform: translateY(-3px); }
.photo-card:focus-visible { outline: 2px solid var(--rose); outline-offset: 3px; }
/* 相册图：一律按图片自身的原始比例显示 —— 只固定宽度（撑满所在栏），
   高度交给图片自己的比例。这里**不写** aspect-ratio / 固定 height / object-fit: cover，
   所以横图竖图都不会被裁掉或拉变形。
   <img> 上的 width/height 属性（脚本从成品图读出来的）负责在图片下载完之前就占好位。 */
.photo-grid .photo-card img,
.photo-card img {
  width: 100%;
  height: auto;
  max-height: none;
  aspect-ratio: auto;
  /* 兜底：万一将来外层强加了固定高度，也只在上下留白（contain），绝不裁画面 */
  object-fit: contain;
  display: block;
  transition: transform .5s ease, filter .4s;
}
.photo-card:hover img { transform: scale(1.03); }
.photo-card figcaption {
  position: absolute; inset: auto 0 0 0;
  padding: 34px 14px 12px;
  background: linear-gradient(transparent, rgba(8, 10, 22, .92));
  opacity: 0; transform: translateY(8px);
  transition: .3s ease;
}
.photo-card:hover figcaption, .photo-card:focus-visible figcaption { opacity: 1; transform: none; }
.photo-card figcaption strong { display: block; font-family: var(--font-serif); font-size: 15px; }
.photo-card figcaption > span { display: block; font-size: 11.5px; color: var(--ink-2); margin-top: 3px; }
.photo-card .chip { border-color: rgba(255, 255, 255, .22); color: rgba(255, 255, 255, .8); }
.photo-card .card-no {
  position: absolute; left: 0; top: 0; z-index: 2;
  background: rgba(10, 12, 24, .78);
  color: #fff; font-family: var(--font-mono); font-size: 10.5px;
  padding: 3px 9px; letter-spacing: .1em;
}
.photo-card .card-corner {
  position: absolute; right: 0; bottom: 0; z-index: 2;
  color: rgba(255, 255, 255, .82);
  font-size: 9.5px; letter-spacing: .18em;
  padding: 4px 10px;
  background: rgba(10, 12, 24, .6);
  opacity: 0; transition: .3s;
}
.photo-card:hover .card-corner { opacity: 1; }
/* 图片文件缺失时不留破图标：显示一张「缺图」占位卡 */
.photo-card.is-broken { min-height: 190px; }
.photo-card.is-broken img { display: none; }
.photo-card.is-broken::before {
  content: '图片缺失';
  display: grid; place-items: center;
  min-height: 190px;
  color: var(--ink-3); font-size: 12px; letter-spacing: .2em;
  background: linear-gradient(140deg, #2a2f52, #171a30);
}

/* =========================================================
 *  视频
 * ======================================================= */
.video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 24px; }
.video-card {
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: var(--radius);
  cursor: pointer;
  transition: .25s;
  overflow: hidden;
}
.video-card:hover { border-color: var(--rose); transform: translateY(-3px); }
.video-card .thumb { position: relative; aspect-ratio: 16 / 9; background: #10132a; overflow: hidden; border-radius: var(--radius) var(--radius) 0 0; }
.video-card .thumb img { width: 100%; height: 100%; object-fit: cover; opacity: .82; transition: .45s; }
.video-card:hover .thumb img { opacity: 1; transform: scale(1.04); }
.thumb-fallback { width: 100%; height: 100%; background: linear-gradient(140deg, #2a2f52, #171a30); position: relative; }

/* 没有封面时生成的「设计底图」：色调由标题推导（--h），比一块灰色好看得多 */
.thumb-gen {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 18px 22px; text-align: center;
  background:
    radial-gradient(120% 90% at 20% 12%, rgba(255, 255, 255, .14), transparent 62%),
    linear-gradient(140deg,
      hsl(var(--h) 40% 22%),
      hsl(var(--h) 52% 33%) 58%,
      hsl(calc(var(--h) + 38) 45% 24%));
}
.thumb-gen::after {
  content: ''; position: absolute; inset: 0;
  background-image: repeating-linear-gradient(135deg, rgba(255, 255, 255, .05) 0 1px, transparent 1px 9px);
  pointer-events: none;
}
.thumb-gen b {
  position: relative; z-index: 1;
  font-family: var(--font-serif); font-size: 22px; font-weight: 700;
  font-style: italic; line-height: 1.4; letter-spacing: .02em;
  color: rgba(255, 255, 255, .92);
  max-width: 100%; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .35);
}
.thumb-gen .tg-hint {
  position: relative; z-index: 1;
  font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: .22em; text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
}
/* 用生成底图时，中间那个大播放按钮会和标题打架，收掉它
   （提示文字里已经有「▶ 点开播放」，整张卡片也是可点的） */
.thumb--gen .play { display: none; }

/* live: true —— 卡片里直接嵌播放器，显示视频真实画面 */
.thumb-live { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.thumb--live { background: #000; }
.thumb--live .play { display: none; }
/* 嵌入播放器时右下的「时长」小标签要在播放器之上 */
.thumb--live .dur { z-index: 3; }
/* 封面文件坏了（被脚本摘掉 img）时，给这块缩略图补上同一套斜纹底 */
.thumb-broken::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(140deg, #2a2f52, #171a30);
}
/* 没有封面时给一点斜纹，别是一块死板的纯色 */
.thumb-fallback::after {
  content: ''; position: absolute; inset: 0;
  background-image: repeating-linear-gradient(135deg, rgba(255, 255, 255, .05) 0 1px, transparent 1px 9px);
}
.play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(10, 12, 24, .55);
  border: 1px solid rgba(255, 255, 255, .5);
  color: #fff; font-size: 16px; padding-left: 3px;
  backdrop-filter: blur(3px);
  transition: .25s;
}
.video-card:hover .play { background: var(--rose); border-color: var(--rose); transform: translate(-50%, -50%) scale(1.08); }
.dur {
  position: absolute; right: 9px; bottom: 9px;
  background: rgba(8, 10, 22, .82); color: #fff;
  font-family: var(--font-mono); font-size: 10.5px;
  padding: 2px 7px;
}
/* 来源标签：本地 / 抖音 / B站 / YouTube（左上角，和右下角时长对称） */
.src-tag {
  position: absolute; left: 9px; top: 9px;
  z-index: 2;
  background: rgba(8, 10, 22, .72);
  border-left: 2px solid var(--rose);
  color: #fff;
  font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: .14em; text-transform: uppercase;
  padding: 3px 8px;
}
.vc-body { padding: 16px 18px 18px; }
.vc-body h3 { margin: 0 0 7px; font-family: var(--font-serif); font-size: 17px; line-height: 1.5; }
.vc-body p { margin: 0 0 10px; color: var(--ink-2); font-size: 13px; }
.vc-meta { font-size: 11px; letter-spacing: .12em; color: var(--ink-3); font-family: var(--font-mono); }

/* =========================================================
 *  Blog（笔记列表）
 * ======================================================= */
.blog-bar {
  display: flex; align-items: center; gap: 26px;
  padding-bottom: 16px; margin-bottom: 28px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.blog-search {
  flex: 1 1 320px;
  display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 11px 18px;
  transition: border-color .25s;
}
.blog-search:focus-within { border-color: var(--rose); }
.blog-search svg { width: 15px; height: 15px; fill: none; stroke: var(--ink-3); stroke-width: 2; flex: none; }
.blog-search input {
  flex: 1; min-width: 0;
  background: none; border: none; outline: none;
  letter-spacing: .12em; font-size: 12.5px; color: var(--ink);
}
.blog-search input::placeholder { color: var(--ink-3); letter-spacing: .14em; }
.blog-filters { display: flex; gap: 22px; flex-wrap: wrap; }
.blog-filters button {
  background: none; border: none; cursor: pointer;
  padding: 4px 0; position: relative;
  font-size: 12.5px; letter-spacing: .12em; color: var(--ink-3);
  transition: color .2s;
}
.blog-filters button::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -2px;
  height: 1px; background: var(--rose);
  transform: scaleX(0); transform-origin: left; transition: transform .25s;
}
.blog-filters button:hover { color: var(--ink-2); }
.blog-filters button.active { color: #fff; }
.blog-filters button.active::after { transform: scaleX(1); }
.blog-count { font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em; color: var(--ink-3); }

.blog-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 34px 26px; }
.post { display: block; }
.post-cover {
  position: relative;
  /* ⚠ 没有封面图时用 4/3 占位（生成底图本来就是这个比例）；
     有封面图时由下面的 img 撑开 → 按图片自身比例显示，不裁不拉 */
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #10132a;
}
.post-cover img { width: 100%; height: 100%; object-fit: cover; transition: transform .55s ease, filter .4s; }

/* 有真实封面图时：容器跟着图片比例走（不再裁成 4/3）。
   放在上面两条规则之后，靠 :has() 只在「真有 img」时生效 —— 
   生成底图（.post-cover--gen）不受影响，仍是 4/3。 */
/* ---------- 漫画封面式：统一竖版 2:3 ----------
   上一版按图片原比例显示，结果是同一行三张高度各不相同、下面的
   标题行参差不齐（"没对齐"）。漫画封面本来就是**统一开本**，
   所以这里改回固定比例：图片裁切填满画框，一行三张一样高 → 自然对齐。 */
.post-cover,
.post-cover:has(img) {
  position: relative;
  aspect-ratio: 2 / 3;          /* 竖版开本，接近漫画单行本 */
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #10132a;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .34);
  transition: transform .38s cubic-bezier(.22, .61, .36, 1), box-shadow .38s ease, border-color .3s ease;
}

/* 图片一律填满画框（这就是"漫画封面"的做法：裁切，不留白） */
.post-cover img,
.post-cover:has(img) img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: none;                 /* 清掉上一版的降饱和 */
  -webkit-mask-image: none;     /* 清掉上一版的底部渐隐 */
  mask-image: none;
  transition: transform .5s cubic-bezier(.22, .61, .36, 1), filter .4s ease;
}

/* 漫画感：默认轻微压暗压色（像印出来的），悬停"掀起来"并恢复通透 */
.post-cover img,
.post-cover:has(img) img { filter: saturate(.94) contrast(1.02) brightness(.97); }
.post:hover .post-cover,
.post:focus-visible .post-cover {
  transform: translateY(-5px) rotate(-.35deg);       /* 微微掀起，像拿起一本 */
  box-shadow: 0 20px 44px rgba(0, 0, 0, .52);
  border-color: var(--rose-dim);
}
.post:hover .post-cover img,
.post:hover .post-cover:has(img) img,
.post:focus-visible .post-cover img {
  filter: saturate(1.08) contrast(1.04) brightness(1.02);
  transform: scale(1.06);
}

/* 画框内圈高光：漫画封面那种"有厚度"的边。
   ⚠ 用 ::before 而不是 ::after —— 原本的 .post-cover::after 是网点纹
   （halftone 圆点，本身就是漫画质感的一部分），不能被我这条覆盖掉。 */
.post-cover::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07),
              inset 0 -34px 46px -30px rgba(6, 8, 18, .8);
  pointer-events: none;
  z-index: 2;
}

/* 行内卡片各自贴合内容高度（统一开本后其实已经齐了，这条是保险） */
.blog-grid { align-items: start; }
.post:hover .post-cover img { transform: scale(1.05); }
.post-cover::after {
  content: ''; position: absolute; inset: 0;
  background-image: radial-gradient(rgba(6, 8, 18, .55) 1px, transparent 1px);
  background-size: 4px 4px; opacity: .28; mix-blend-mode: multiply;
}
.post-cover--gen {
  display: grid; place-items: center;
  background:
    radial-gradient(120% 90% at 20% 15%, rgba(255, 255, 255, .12), transparent 60%),
    linear-gradient(140deg, hsl(var(--h) 42% 20%), hsl(var(--h) 55% 31%) 60%, hsl(calc(var(--h) + 35) 48% 24%));
}
.post-cover--gen b {
  position: relative; z-index: 1;
  font-family: var(--font-serif); font-size: 54px; font-style: italic;
  color: rgba(255, 255, 255, .28); letter-spacing: .04em;
}
.post-cover .cover-tag {
  position: absolute; right: 10px; bottom: 8px; z-index: 2;
  font-size: 9.5px; letter-spacing: .18em;
  color: rgba(255, 255, 255, .85);
}
.post-meta {
  display: flex; align-items: center; gap: 14px;
  margin-top: 14px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em;
  color: var(--ink-3);
}
.post-meta .pm-cat { color: var(--rose); }
.post-meta time { margin-left: auto; }
.post-title {
  margin: 10px 0 0;
  font-family: var(--font-serif);
  font-size: 20px; font-weight: 700; line-height: 1.55;
  letter-spacing: .02em;
  transition: color .2s;
}
.post:hover .post-title { color: var(--rose); }
.post-sum { margin: 8px 0 0; color: var(--ink-2); font-size: 12.5px; line-height: 1.8;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* 新结构：卡片正文包了一层 .post-body，里面多了大号序号和箭头。
   KISARA 保持原样 —— 这层用 display:contents 等于不存在，
   meta / 标题 / 摘要还是 .post 的直接子元素。序号和箭头只在
   SYNAPSE 下出现（见 theme.css ⑩）。 */
.post-body { display: contents; }
.post-no, .post-arrow { display: none; }

/* =========================================================
 *  Blog 阅读页
 * ======================================================= */
.post-bar {
  display: flex; align-items: center; gap: 18px;
  padding-bottom: 14px; margin-bottom: 34px;
  border-bottom: 1px solid var(--line);
}
.post-back {
  display: flex; align-items: center; gap: 9px;
  font-size: 12.5px; letter-spacing: .14em; color: var(--ink-2);
  transition: color .2s;
}
.post-back:hover { color: var(--rose); }
.post-back span { color: var(--rose); }
.post-crumb {
  flex: 1;
  font-size: 11px; letter-spacing: .16em; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* =========================================================
 *  Markdown 正文
 * ======================================================= */
.markdown { font-size: 15px; }
.markdown > *:first-child { margin-top: 0; }
.markdown h1, .markdown h2, .markdown h3, .markdown h4 {
  font-family: var(--font-serif);
  line-height: 1.45; margin: 40px 0 16px; scroll-margin-top: 90px;
  letter-spacing: .02em;
}
.markdown h1 { font-size: 32px; margin-top: 8px; }
.markdown h2 { font-size: 23px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.markdown h3 { font-size: 18.5px; }
.markdown h4 { font-size: 16px; color: var(--ink-2); }
.markdown p { margin: 15px 0; }
.markdown ul, .markdown ol { margin: 15px 0; padding-left: 24px; }
.markdown li { margin: 7px 0; }
.markdown li::marker { color: var(--rose); }
.markdown .md-task { color: var(--rose); font-weight: 700; }
.markdown .md-task.done { opacity: .65; }
.markdown .md-li-task { list-style: none; margin-left: -20px; }
.markdown a { color: var(--rose); border-bottom: 1px solid var(--rose-dim); transition: .2s; }
.markdown a:hover { border-bottom-color: var(--rose); }
.markdown strong { font-weight: 700; }
.markdown mark { background: rgba(224, 70, 111, .22); color: inherit; padding: 0 4px; }
.markdown hr { border: none; border-top: 1px solid var(--line); margin: 34px 0; }
.markdown img { margin: 20px 0; border: 1px solid var(--line); border-radius: var(--radius); }

.markdown code {
  font-family: var(--font-mono); font-size: .88em;
  background: rgba(224, 70, 111, .12);
  border: 1px solid var(--rose-dim);
  border-radius: var(--radius-sm);
  padding: 1px 6px; color: var(--rose-2);
  white-space: nowrap;
}
.md-code {
  position: relative;
  background: #0d1024;
  border: 1px solid var(--line);
  border-left: 2px solid var(--rose);
  border-radius: var(--radius);
  padding: 18px 20px;
  overflow: auto; margin: 22px 0; line-height: 1.65;
}
.md-code code {
  background: none; border: none; padding: 0;
  color: #d7dbf0; font-size: 13px; white-space: pre;
}
.md-code[data-lang]::after {
  content: attr(data-lang);
  position: absolute; right: 12px; top: 9px;
  font-family: var(--font-mono); font-size: 10px;
  color: var(--ink-3); letter-spacing: .1em; text-transform: uppercase;
}
.markdown blockquote {
  margin: 22px 0; padding: 14px 20px;
  border-left: 2px solid var(--rose);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--rose-dim);
  color: var(--ink);
}
.markdown blockquote p { margin: 6px 0; }

/* =========================================================
 *  滚动浮现动效（由 Base.astro 里的 IntersectionObserver 驱动）
 *  ---------------------------------------------------------
 *  默认**全部可见** —— JS 挂了、被禁用、或系统开了「减少动态效果」时
 *  页面照常完整显示，绝不会白屏。
 *  只有脚本成功运行时才会给 <html> 加 .reveal，这时才先隐藏、滚到再浮现。
 *  ⚠ 这份选择器清单要和 Base.astro 里 startReveal() 的 targets 保持一致。
 *    .gallery-item 目前没有页面在用（相册走的是 .photo-card），
 *    但脚本的浮现目标 + 鼠标热区里都留着它，所以这里也跟着留。
 *  ⚠ .gate-inner > *（首屏舞台里的字）**故意不在这里**：
 *    它们的入场由下面 .gate-kicker / .gate-title / .gate-sub / .gate-scroll
 *    自己的一段 CSS 动画负责（纯 CSS，不依赖任何 JS）。
 * ======================================================= */

/* =========================================================
 *  滚动进度（替代原来的 IntersectionObserver 浮现）
 *  ---------------------------------------------------------
 *  用户要求"主页也像 ME 页那样滑动渐显"。所以这里换成 ME 页同一套：
 *  元素进入视口后播一段**定时**动画（0.76s），错开 80ms 一拍 ——
 *  而不是原来那种"滚动进度直接映射"的时间线动画。
 *  两者观感差别很大：
 *    · 时间线驱动（原来的 scrollSettle）＝ 内容随滚动位置被"擦"出来，
 *      滚多快就多快，停住就停在半路
 *    · 定时驱动（ME 页那套）＝ 内容自己浮上来，滚快滚慢都好看
 *  用户要的是后者，而 ME 页正是后者，所以直接沿用同一组数值。
 *
 *  ⚠ 开关是 <html class="u-reveal">，由脚本在"确认能跑"之后才加：
 *    · 没加 → 元素完全按自己的样式显示（默认可见）
 *    · 加了 → 元素先用 backwards 填充停在"未浮现"，滚到再加 .is-in
 *    这样脚本挂了、被禁用、或系统开了减少动效，页面都是一张完整的静态页，
 *    绝不会出现"内容藏着不出来"。
 *  ⚠ fill-mode 只用 backwards，绝不用 forwards/both ——
 *    动画结束后元素回到自己声明的样式，悬停的 transform 才不会被永久压制。
 * ======================================================= */
@keyframes riseIn {
  from { opacity: 0; transform: translateY(22px) scale(.985); filter: blur(3px); }
  to   { opacity: 1; transform: none; filter: none; }
}

@media (prefers-reduced-motion: no-preference) {
  html.u-reveal .panel,
  html.u-reveal .chapters .chapter,
  html.u-reveal .friend-entry,
  html.u-reveal .finale {
    /* ⚠ 这里**不用** animation-play-state: paused 来"开场期间先冻结"。
       原因：paused 会把动画停在第一帧，而动画**已经开始了**；
       等开场结束把类摘掉，动画不会重新开始，元素就永远停在"半透明"。
       改用 .is-in 类驱动：脚本滚到（或开场结束时）才加上去。 */
    opacity: 0;
    animation: riseIn .76s cubic-bezier(.22, .61, .36, 1) backwards;
  }
  html.u-reveal .panel.is-in,
  html.u-reveal .chapters .chapter.is-in,
  html.u-reveal .friend-entry.is-in,
  html.u-reveal .finale.is-in {
    opacity: 1;
    /* --rise-delay 由脚本按顺序写进去，错开 80ms 一拍 */
    animation-delay: var(--rise-delay, 0s);
  }
}

/* =========================================================
 *  自定义鼠标：**已整体删除**
 *  ---------------------------------------------------------
 *  参考站 yuimi-chaya.github.io（Kisara 主题）在四个首页 CSS 里
 *  `cursor: none` 出现 **0 次** —— 它一直用系统光标。
 *
 *  删掉的理由（也是用户的原话"不跟手、看着廉价"）：
 *    · 系统光标是零延迟的，任何自定义光标都赢不了它 —— 手感上永远差一点
 *    · 圈会在链接/卡片上放大变色、显示 OPEN，等于满屏都在喊"这里可以点"，
 *      把内容本身的注意力抢走了
 *    · backdrop-filter + 磁吸偏移 + 每帧写 CSS 变量，白烧一批合成
 *
 *  原来这里的规则是：html.has-cursor{ cursor:none }（全局隐藏系统光标）、
 *  .cur-dot / .cur-ring / .cur-label 三件套、以及触屏与 reduced-motion
 *  的降级。现在**一条都不留** —— 连带旧缓存里可能残留的 DOM 也不会
 *  再隐藏光标（那是最坏的情况：一个光标都没有）。
 * ======================================================= */

/* 系统开了「减少动态效果」→ 关掉所有入场/循环动画，位移归零 */
@media (prefers-reduced-motion: reduce) {
  .markdown > *, .post-bar, .post, .gallery-item, .video-card, .panel {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    translate: none !important;
    scale: none !important;
    filter: none !important;
  }
}

/* 悬停反馈：图片轻微放大 + 阴影，链接变色 */
.markdown img {
  transition: transform .38s cubic-bezier(.22, .61, .36, 1), box-shadow .38s ease;
}
.markdown img:hover {
  transform: scale(1.015);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .38);
}
.markdown a { transition: color .18s ease, border-color .18s ease; }
.markdown h2, .markdown h3 {
  scroll-margin-top: 90px;   /* 点目录/锚点跳转时别被顶部导航挡住 */
}
.markdown blockquote, .markdown .md-code, .markdown .md-table-wrap,
.markdown .mermaid, .markdown .md-math-block, .markdown .md-dataview {
  transition: border-color .3s ease, background .3s ease;
}

/* 列表页卡片：悬停上浮（入场浮现交给 html.reveal 那套） */
.post {
  transition: transform .3s cubic-bezier(.22, .61, .36, 1), box-shadow .3s ease, border-color .3s ease;
}
.post:hover {
  transform: translateY(-3px);
  border-color: var(--rose-dim);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .34);
}

/* ---------- 笔记列表页（/notes/）的细节动效 ---------- */
/* 悬停：封面缓慢放大（图片和「生成封面」都一样对待） */
.post .post-cover { overflow: hidden; }
.post .post-cover img {
  transition: transform .55s cubic-bezier(.22, .61, .36, 1), filter .55s ease;
}
.post:hover .post-cover img { transform: scale(1.055); }
.post-cover--gen {
  transition: transform .55s cubic-bezier(.22, .61, .36, 1), filter .55s ease;
}
.post:hover .post-cover--gen { transform: scale(1.045); filter: saturate(1.18); }

/* 悬停：标题下划线从左扫出 */
.post-title { position: relative; display: inline-block; }
.post-title::after {
  content: '';
  position: absolute; left: 0; bottom: -4px;
  width: 100%; height: 1px;
  background: var(--rose);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .42s cubic-bezier(.22, .61, .36, 1);
}
.post:hover .post-title::after { transform: scaleX(1); }

/* 悬停：编号（01/02/…）轻微右移 + 变亮 */
.post .cover-tag { transition: transform .4s ease, color .4s ease; }
.post:hover .cover-tag { transform: translateX(3px); color: var(--rose); }

/* 分类筛选按钮：切换时颜色/底色平滑过渡 */
.tab, .filter, .chip {
  transition: background .25s ease, color .25s ease, border-color .25s ease, transform .18s ease;
}
.tab:active, .filter:active, .chip:active { transform: scale(.96); }

/* 减少动效时一并关闭 */
@media (prefers-reduced-motion: reduce) {
  .post .post-cover img, .post-cover--gen, .post-title::after, .post .cover-tag,
  .tab, .filter, .chip { transition: none !important; transform: none !important; }
}

/* =========================================================
 *  页面切换过渡（Astro ClientRouter / View Transitions）
 *  参考 Kisara 主题那种「外壳不动、内容平滑换掉」的感觉
 * ======================================================= */
::view-transition-old(root) {
  animation: pageOut .34s cubic-bezier(.4, 0, .2, 1) both;
}
::view-transition-new(root) {
  animation: pageIn .5s cubic-bezier(.22, .61, .36, 1) both;
}
@keyframes pageOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-8px) scale(.995); }
}
@keyframes pageIn {
  from { opacity: 0; transform: translateY(14px) scale(.995); filter: blur(2px); }
  to   { opacity: 1; transform: none; filter: none; }
}

/* 顶栏在换页时保持不动（体感上像「只有内容在换」） */
.topbar { view-transition-name: site-topbar; }
.footer { view-transition-name: site-footer; }

/* 换页时那条「正在加载」的进度感（保留占位，不依赖 :has 支持） */
html.js-motion::after {
  content: '';
  position: fixed; left: 0; top: 0;
  height: 2px; width: 100%;
  z-index: 10000;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--rose), transparent);
  transform: translateX(-100%);
  opacity: 0;
}

/* 减少动效 → 换页直接切换，不做过渡 */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }
  html.js-motion::after { display: none; }
}

/* =========================================================
 *  导航栏动效
 *  · 一条指示条跟着鼠标在导航项之间滑动
 *  · 悬停时编号（01~05）和图标有轻微反馈
 * ======================================================= */
/* ⚠ 桌面端才需要给导航加相对定位（那是给「跟着鼠标滑动的指示条」当锚点的）。
   千万不能写成全局的 #navTabs { position: relative } ——
   id 选择器优先级高于手机的 .tabs 规则，会把下拉菜单的 position: absolute 顶掉，
   结果 top/left/right 变成"相对自身位移"，菜单被推下去、第一项被顶栏压住。 */
@media (min-width: 761px) {
  #navTabs { position: relative; }
}

/* 移动端：导航是下拉菜单，不需要指示条（在菜单里会变成一根游离的小横条） */
@media (max-width: 760px) {
  .nav-ind { display: none !important; }
}

/* 指示条：现在它是顶栏**唯一**的一条线（下面那条 .tab::after 已经删掉）。
   所以把张扬的 12px 玫红辉光收掉了 —— 一条实心的小短条压在 62px 高的
   顶栏下沿，本来就够醒目，再糊一层 12px 的光只会让边缘发虚。 */
.nav-ind {
  position: absolute;
  left: 0; bottom: -3px;
  height: 2px;
  width: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--rose), var(--rose-2, #ff7ba0));
  box-shadow: 0 0 6px rgba(224, 70, 111, .35);
  opacity: 0;
  pointer-events: none;
  transition:
    transform .42s cubic-bezier(.22, .61, .36, 1),
    width .42s cubic-bezier(.22, .61, .36, 1),
    opacity .3s ease;
}
.nav-ind.is-on { opacity: 1; }
.nav-ind.is-instant { transition: none; }

/* 当前板块：加粗 + 纯白。
   （指示条会跟着鼠标走，所以"我在哪"还得靠字重和颜色说清楚） */
.tab.active { font-weight: 700; }

/* 导航项自身：悬停时图标轻抬、编号变亮 */
.tab svg { transition: transform .32s cubic-bezier(.22, .61, .36, 1); }
.tab:hover svg { transform: translateY(-2px) scale(1.06); }
.tab .tab-no { transition: color .28s ease, opacity .28s ease; }
.tab:hover .tab-no { color: var(--rose); }
.tab { transition: color .25s ease, background .25s ease; }

/* 顶栏整体：进站时从上方滑下 */
html.js-motion .topbar { animation: barDrop .55s cubic-bezier(.22, .61, .36, 1) both; }
@keyframes barDrop {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-ind { transition: none !important; }
  .tab svg, .tab .tab-no, .tab { transition: none !important; transform: none !important; }
  html.js-motion .topbar { animation: none !important; }
}

/* =========================================================
 *  开场：纸做的电影片头（.opening）
 *  =========================================================
 *  一页纸像杂志那样翻进来盖住屏幕 → 纸上打一张电影片头式的字卡
 *  → 纸翻走，露出已经在后面就位的首屏。
 *
 *  为什么用"纸"：这个站是深色底，深色物体在上面"动了等于没动"
 *  （前几轮的深色幕布就是这个下场）。一张暖白的纸是强反差，
 *  **不依赖底图好不好看** —— 这是这个方案能成立的全部理由。
 *
 *  ⚠ 安全设计（硬要求，改这里之前先读一遍）：
 *    · 纸的基础样式是 opacity:0 + visibility:hidden。
 *      只有 <html class="u-opening">（首页硬加载 + 允许动效）时才显示。
 *      没有那个类 → 纸就是个不可见的空 div，页面照常。
 *    · 动画只做 translate / opacity —— 没有 clip-path、没有 mask、
 *      没有 backdrop-filter，这三样在低端机上是翻车常客。
 *    · 组件脚本有三条强制拆除路径（animationend / setTimeout /
 *      滚动·点击·按键），任何一条命中都会把纸从 DOM 里删掉。
 *    · 纸是纯装饰：它永远不会是"内容还在等它"的状态。
 * ======================================================= */
.opening {
  position: fixed;
  inset: 0;
  z-index: 9990;              /* 顶栏是 60、进度轨 9000 —— 纸要在它们之上 */
  display: grid;
  place-items: center;
  overflow: hidden;
  background: #f7f3ee;        /* 暖白：比 ME 页的粉纸更"印刷" */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  will-change: transform, opacity;
}
/* 只有这一个开关能让纸出现。
   ⚠ **不再有"滑入"动画**：之前是从右往左滑（translate 101% → 0），
     中途会露出一帧"纸只盖住左边一条、字卡半透明、进入按钮还没到"的
     状态（用户截图截到的正是这一帧），很难看。
     现在纸直接铺满，进场的动势交给粒子层（从中心向外重组）。 */
html.u-opening .opening {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;       /* 开场期间吃掉点击，避免误触底下的链接 */
  animation: openingPaperIn .42s ease-out both;
}
@keyframes openingPaperIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* ⚠⚠ 离场：纸**保持完全不透明**，只把底下的字卡散掉。
   ---------------------------------------------------------
   这里曾经让纸自己淡出（1.45s），结果和脚本的行动顺序撞车：
   脚本一点击就摘掉了 .u-opening（纸的显示开关），纸当场隐藏，
   粒子跟着一起没了 —— 用户看到的就是"直接进主页、没有消散"。

   现在分工明确：
     · .opening[.is-leaving]  → 纸保持可见（只把按钮/副标题等散掉），
                                当粒子爆炸的**舞台**
     · 画布粒子              → 唯一负责"文字炸开"的表演
     · 脚本的 finishEnter()  → 动画放完才撤纸 + 首屏入场 + 开滚动
   opacity:1 写成内联优先级之外的最高一档（带 !important），
   保证它在任何类组合下都不会因为基础样式的 visibility:hidden 而消失。 */
.opening.is-leaving {
  opacity: 1 !important;
  visibility: visible !important;
  animation: none !important;
  pointer-events: none;
}
/* 字卡（eyebrow / 标题占位 / 横线 / 副标题 / 页脚 / 按钮 / 提示）：
   点击后先散掉，把画面让给粒子。标题本来就是 opacity:0，不受影响。 */
.opening.is-leaving .opening-inner {
  animation: openingCardAway .5s cubic-bezier(.3, 0, .4, 1) both;
}
@keyframes openingCardAway {
  from { opacity: 1; transform: translate3d(0, 0, 0); filter: blur(0); }
  to   { opacity: 0; transform: translate3d(0, -18px, 0); filter: blur(6px); }
}
/* 折痕：纸两边的暗边，让"翻动"有厚度感。
   只用纯色渐变，不加 blur —— 便宜且没有额外合成层。 */
.opening-fold {
  position: absolute;
  top: 0; bottom: 0;
  width: 46px;
  pointer-events: none;
}
.opening-fold--left {
  left: 0;
  background: linear-gradient(90deg, rgba(60, 40, 30, .10), rgba(60, 40, 30, 0));
}
.opening-fold--right {
  right: 0;
  background: linear-gradient(270deg, rgba(60, 40, 30, .08), rgba(60, 40, 30, 0));
}

/* ---------- 字卡：电影片头的排版 ---------- */
.opening-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 0 24px;
  text-align: center;
  max-width: 900px;
}
/* 字卡元素：由虚到实浮现，配合粒子"聚拢"的过程 */
.opening-inner > * { opacity: 0; animation: openingIn .8s cubic-bezier(.2, .7, .2, 1) both; }
@keyframes openingIn {
  from { opacity: 0; transform: translate3d(0, 14px, 0); filter: blur(5px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
/* 粒子层：铺满整张纸，在字卡之上（粒子飞过字面才好看），但不吃点击 */
.opening-canvas {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.opening-inner { z-index: 3; }
.opening-fold { z-index: 1; }

/* ⚠ 时间轴：粒子 0.9s 聚拢完成，字卡跟着一拍一拍落位。
   按钮和提示的 delay 写在它们各自的规则里（见下），
   因为那两个元素的规则在后面、会覆盖这里的简写。
   总时长约 1.6s —— 再长就变成"等动画"而不是"进门"了。 */
html.u-opening .opening-eyebrow { animation-delay: .26s; }
html.u-opening .opening-title   { animation-delay: .38s; }
html.u-opening .opening-rule    { animation-delay: .54s; }
html.u-opening .opening-sub     { animation-delay: .64s; }
html.u-opening .opening-foot    { animation-delay: .74s; }

.opening-eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .34em;
  color: var(--rose-deep);
  text-transform: uppercase;
}
.opening-title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: clamp(46px, 10vw, 104px);
  line-height: 1.04;
  letter-spacing: .14em;
  /* 衬线大字往右补半格字距，让"大字距排版"看起来是居中的 */
  text-indent: .14em;
  color: #241a20;             /* 纸质页那套深棕，和 ME 页同一语言 */
}
/* 细横线：从中间向两侧展开 —— 电影片头最常见的那一笔 */
.opening-rule {
  display: block;
  width: min(320px, 52vw);
  height: 1px;
  background: currentColor;
  color: rgba(36, 26, 32, .38);
  transform-origin: center;
  animation-name: openingRule;
}
@keyframes openingRule {
  from { opacity: 0; transform: scaleX(0); }
  to   { opacity: 1; transform: scaleX(1); }
}
.opening-sub {
  margin: 0;
  font-size: 13.5px;
  letter-spacing: .18em;
  color: rgba(36, 26, 32, .62);
}
.opening-foot {
  margin: 6px 0 0;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .26em;
  color: rgba(36, 26, 32, .42);
  text-transform: uppercase;
}

/* ---------- 「进入主页」按钮 ----------
   整张纸都可点，但**必须有一个看得见的落点**：
   触屏用户、键盘用户、以及"不知道该干嘛"的人都靠它。
   Enter / 空格 / Esc 也都能触发（见组件脚本）。 */
.opening-enter {
  margin-top: 22px;
  padding: 11px 30px;
  border: 1px solid rgba(36, 26, 32, .28);
  border-radius: var(--radius-pill);
  background: transparent;
  color: #241a20;
  font-family: var(--font-sans);
  font-size: 13px;
  letter-spacing: .22em;
  cursor: pointer;
  /* 和字卡其它元素一样"落位后出现"，但再晚一拍 —— 它是这场开场的终点 */
  animation-delay: .84s;
  transition: background .28s ease, border-color .28s ease, color .28s ease,
              transform .28s cubic-bezier(.22, .61, .36, 1);
}
@media (hover: hover) {
  .opening-enter:hover {
    background: #241a20;
    border-color: #241a20;
    color: #f7f3ee;
    transform: translateY(-2px);
  }
}
.opening-enter:focus-visible {
  outline: 2px solid var(--rose-deep);
  outline-offset: 4px;
}
/* 按钮下面那行小字：告诉用户"不用非得点按钮" */
.opening-hint {
  margin: 14px 0 0;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(36, 26, 32, .38);
  animation-delay: .98s;
}
/* 整张纸都是可点的：给个手型光标，别让人以为只有按钮能点 */
html.u-opening .opening { cursor: pointer; }
html.u-opening .opening-enter { cursor: pointer; }

/* 减少动态效果 / 窄屏：纸完全不出现 */
@media (prefers-reduced-motion: reduce) {
  html.u-opening .opening { display: none !important; }
}
@media (max-width: 600px) {
  .opening-title { font-size: clamp(40px, 15vw, 64px); letter-spacing: .1em; text-indent: .1em; }
  .opening-inner { gap: 10px; }
  .opening-enter { margin-top: 16px; padding: 10px 24px; }
}

/* =========================================================
 *  首页首屏舞台（Gate）
 *  ---------------------------------------------------------
 *  由 index.astro / Gate.astro 的脚本写入的 CSS 变量只有两组：
 *    · --gate-p   0~1 的滚动进度（淡出用）
 *    · --gate-ty  这个进度换算出来的竖向位移（px）
 *    · --mx/--my  指针位置（-0.5~0.5，视差用）
 *  位移一律走 transform / translate3d，脚本挂了就是一张静态主视觉。
 *
 *  ⚠ 三条以前互相打架、现在被拆开的分工：
 *    1. 入场（一次性）→ 只动外层包裹（.gate-particles 的漂移层）
 *       和文字元素（.gate-kicker / .gate-title / .gate-sub / .gate-scroll）
 *    2. 滚动（连续）  → 只动 .gate-hero 的 opacity 和 .gate-bg 的 --gate-ty
 *    3. 指针（连续）  → 只动各个元素的 translate3d(var(--mx/--my))
 *    一个元素上不再同时挂"入场动画"和"滚动动画" —— 那正是旧版
 *    「一进去就啪地全出现」的原因：同一条 animation 简写里，
 *    @supports 里的滚动视差把前面的入场动画整条覆盖掉了。
 * ======================================================= */
.gate {
  position: relative;
  /* ⚠ 必须写两行：不支持 svh 的浏览器（旧版）会把 100svh 整条丢掉，
     首屏高度就塌成内容高度（看起来只有半屏）。100vh 兜底。 */
  min-height: 100vh;
  min-height: 100svh;
  margin: 0 0 30px;
  border-radius: var(--radius);
  overflow: clip;
  background: var(--bg-2);
  isolation: isolate;
}
.gate-bg { position: absolute; inset: 0; z-index: 0; }

/* =========================================================
 *  开场（Opening）—— 由两件事组成
 *  ---------------------------------------------------------
 *    ① 背景极慢地推近：scale 1.22 → 1（2.2s）　← 就是下面这条
 *    ② 主视觉从虚焦拉到实焦：blur 16px → 0　　← 见下面 gateFocusIn
 *
 *  ⚠ .gate-bg 上**不再有**遮罩类伪元素（历史上那块"幕布"已经删掉）。
 *     原因写在下面 gateFocusIn 那一段里：深色幕布压深色页面看不出开场，
 *     而且"遮住内容"的东西一旦出错就是整屏空白。
 *  ⚠ .gate-bg 上不放 transform —— 指针视差挪到 .gate-hero 和 .gate-dust。
 * ======================================================= */
/* 背景推近也由"点进入"触发（写在下面 gateBgIn 的 keyframes 处）。 */
@keyframes gateBgIn {
  /* 1.12 → 1：纸滑走时画面轻微推近收尾。
     之前是 1.22，那是"深色幕布"时代为了让画面自己有点动势；
     现在动势由纸负责，背景只要一点点跟随就够了，幅度大了反而晕。 */
  from { scale: 1.12; }
  to   { scale: 1; }
}

/* =========================================================
 *  开场：**纸做的电影片头**（主开场，见 .opening 那一段）
 *  ---------------------------------------------------------
 *  这里只保留"首屏自己的入场"，它现在是**配角**：
 *  纸滑出的同时首屏才开始动，所以它的时间轴要跟着纸走 ——
 *  下面的负延迟只有在 <html class="u-opening">（首页 + 允许动效）时生效。

 *  为什么不再用深色遮罩 / 大范围虚化：
 *    实测首屏平均亮度只有 26/255（页面底色 21），
 *    深色物体在深色底上"动了等于没动"；而遮住内容的东西
 *    一旦出错就是整屏空白（这套代码已经因此出过两次故障）。
 *
 *  ⚠ 主视觉的 opacity 全程由**一条规则**负责，绝不参与开场 ——
 *    历史故障就是把它交给动画，被中断后永久淡成 0。
 * ======================================================= */
.gate-hero {
  background-image: var(--hero);
  opacity: max(.15, calc(1 - var(--gate-p, 0) * .9));
  will-change: opacity;
}
/* ⚠ 首屏的入场要等用户**点完进入**才开始（html.u-entered）。
   为什么不能像以前那样用固定延迟：现在是"手动进入"，
   用户可能盯着字卡看很久 —— 固定延迟的话，等他们点进来，
   首屏动画早播完了，什么都看不到。所以改成由点击触发。 */
html.u-entered .gate-hero {
  animation: gateFocusIn .95s cubic-bezier(.2, .62, .25, 1) both;
}
@keyframes gateFocusIn {
  from { filter: blur(11px) brightness(1.10) saturate(1.05); }
  60%  { filter: blur(2px) brightness(1.02) saturate(1.02); }
  to   { filter: blur(0) brightness(1) saturate(1); }
}

/* 主视觉层：cover 铺满
   ⚠ 滚动淡出由 --gate-p 负责（见上面 .gate-hero 那条唯一规则） */
.gate-layer {
  position: absolute;
  inset: -3%;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
/* ⚠ 主视觉的 opacity：**只有一条规则说了算**（就在上面 .gate-hero 里）。
   ---------------------------------------------------------
   这里曾经是第二次踩同一个坑的地方，记录下来避免以后再犯：

     ① 基础规则写 `opacity: calc(1 - var(--gate-p) * .9)`
     ② 另外又给同一个元素加了一条 `animation: gateHeroIn … both`，
        而它的 from 是 `calc(1 - var(--gate-p)*.9 - .72)` ——
        在页面顶部 p=0 时这个算式等于 -0.72，被钳成 0
     ③ `both` + 延迟 ⇒ 动画开始前就先用 from 的值（0）
     ④ 用户在动画播完前滚一下 → 脚本打上 data-opened →
        `animation: none !important` 撤掉动画 → opacity 落回基础值
     ⑤ 基础值在顶部就是 0 ⇒ **主视觉永久透明**，整屏只剩氛围层的深蓝

   两条规矩：
     · opacity 只能有一个来源（现在只服务于滚动淡出，并设了下限 .15）
     · 开场**不许动 opacity** —— 用 blur / 位移 / 压暗层来做，
       这样任何一层出错，最坏也只是"偏亮偏虚"，内容永远在 */

/* 压暗层：只压够文字可读的程度。之前压得太重（上 .62 / 下 .88），
   深色主视觉会被压成一团糊 —— 现在上 .32 / 下 .68，画面能看清楚了

   ⚠ 这一层现在是**静态终态**，不带动画。
   曾经让它从 .18 淡到 1（想做出"开场亮、之后沉下来"），实测虽然生效，
   但它带来两个新问题：
     · 开场那 0.5 秒里画面很亮、文字对比度不足（标题是白字压亮插画）
     · 一旦被 data-opened 中断，规则失效会让它瞬间跳变 —— 画面突然变暗
   所以"由亮到暗"这件事改由主视觉自己的 blur / brightness 动画承担
   （见 .gate-hero 的 gateFocusIn），这一层老实当静态压暗。 */
.gate-veil {
  position: absolute; inset: 0;
  /* ⚠ 数值砍了一半（上 .32→.16、中 .06→.03、下 .68→.42）。
     原因是实测出来的一个硬数字：首屏平均亮度只有 **26/255**，
     而页面底色是 21 —— 主视觉和背景几乎是同一个颜色，等于一块黑。
     压暗叠了四层（veil + atmosphere + 图本身 + 滚动淡出），
     每一层单看都"不过分"，加在一起就把插画吃掉了。
     现在目标是平均亮度 55~70，让画面第一次真正露出来。 */
  background:
    linear-gradient(180deg, rgba(9, 11, 22, .16) 0%, rgba(9, 11, 22, .03) 42%, rgba(9, 11, 22, .42) 100%),
    radial-gradient(120% 80% at 50% 0%, transparent 46%, rgba(9, 11, 22, .16) 100%);
}

.gate-inner {
  position: relative; z-index: 2;
  min-height: 100vh;    /* 同上：给不支持 svh 的浏览器兜底 */
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 10px;
  padding: 0 clamp(18px, 4vw, 44px) clamp(30px, 6vw, 66px);
}
/* ---------- 首屏文字的入场（一段有节奏的开场，纯 CSS） ----------
   以前这四个元素靠 Base.astro 的 IntersectionObserver 加 .is-in 淡入：
   observers 注册完、下一帧回调才执行，首屏早就画出来了，
   于是先看到完整内容、再"啪"地淡入一次 —— 像闪一下，不像开场。
   现在改成元素自己的入场动画：一帧都不会等，也不会和滚动进度抢属性。
   缓动统一 cubic-bezier(.2, .7, .2, 1)：起手快、收尾稳。

   ⚠ 时间轴分两种：
     · 手动进入（html.u-entered，由点击触发）：纸滑走的**同时**首屏开始入场
         kicker 0.10s → 大标题 0.22s → 副标题 0.52s → 滚动指示 0.64s
     · 没有开场（内页 / 减少动效 / 脚本没跑）：没有延迟，立刻正常显示
   这样"有开场"和"没开场"是两套独立的时间轴，互不干扰。 */
@keyframes gateKickerIn {
  from { opacity: 0; transform: translate3d(0, 8px, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes gateTextIn {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  to   { opacity: 1; transform: none; }
}

.gate-kicker { animation: gateKickerIn .62s cubic-bezier(.2, .7, .2, 1) both; }
.gate-sub    { animation: gateTextIn .72s cubic-bezier(.2, .7, .2, 1) both; }
.gate-scroll { animation: gateTextIn .72s cubic-bezier(.2, .7, .2, 1) both; }
html.u-entered .gate-kicker { animation-delay: .10s; }
html.u-entered .gate-sub    { animation-delay: .52s; }
html.u-entered .gate-scroll { animation-delay: .64s; }
/* ⚠ 文字层级要压在氛围层（z-index:3）之上。
   参考站里 .kisara-gate-atmosphere 是盖在标题上的（它靠更重的压暗来
   统一画面），但这里的主视觉是插画、文字是白色大标题 ——
   氛围层压上去会把标题也蒙一层，白字发灰。
   所以层次是：主视觉 → 尘埃/氛围（作用于画面）→ 文字（最上层）。 */
.gate-inner { z-index: 4; }
/* 标题：淡入上浮 + clip-path 圆形揭晓合成**一条**动画。
   ⚠ 以前是两个来源：opacity/位移来自 Base.astro 的 IntersectionObserver
   （要等回调，晚一帧才加 .is-in），clip-path 来自 .is-in 触发的 gateEnchant。
   两者起跑时间不一致，开场就"先看见字、再重新揭晓一次"。
   现在合成一条、延迟写死在动画里 —— 不需要任何 JS，时间也不会错位。
   ⚠ 注意区别：这里动的是**标题文字**，不是主视觉画面。
     主视觉那边严禁用 opacity 做开场（见 .gate-hero 上方的长注释）。 */
.gate-title  { animation: gateTitleIn .92s cubic-bezier(.22, .61, .36, 1) both; }
html.u-entered .gate-title { animation-delay: .22s; }

/* =========================================================
 *  标题：**只留位置，不画文字**（粒子版本，u-particle-title）
 *  ---------------------------------------------------------
 *  用户的要求是"把粒子变成文字" —— 所以文字本身必须让位：
 *  如果还画着深色的衬线字，那看到的永远是"粒子飞向文字"，
 *  而不是"粒子就是文字"。
 *
 *  做法：opacity 归零，但元素**仍在布局里** ——
 *    · 标题的位置/宽度/字体度量还能量，粒子靠它采样字形
 *    · 屏幕阅读器读到的仍是真标题（不是一堆点）
 *    · 换行、居中、响应式全都照旧
 *  粒子侧则提高不透明度与密度，让笔画实到能读出来（见组件里
 *  的 alpha 曲线与 MAX_PARTS）。
 * ======================================================= */
html.u-particle-title .opening-title { opacity: 0 !important; }

/* ⚠ 顺序要紧：标题排在 kicker 和 sub 之间（0.44s 起、1.5s 落） */
.gate-kicker {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: .34em;
  /* ⚠ 这里原来就是 --rose(#e0466f)：11px 的小字压在深色插画上，
     那个浪漫的玫红在暗处几乎读不出来。换成亮一档的 --rose-2，
     再补一点同色微光（比投影便宜，也不会在插画上糊出灰边）。 */
  color: var(--rose-2, #f2749a);
  text-shadow: 0 0 14px rgba(224, 70, 111, .55);
}
.gate-title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: clamp(38px, 8vw, 92px);
  line-height: 1.02;
  letter-spacing: .02em;
  color: #fff;
  text-shadow: 0 6px 40px rgba(0, 0, 0, .55);
}
.gate-sub {
  margin: 0;
  font-size: 13.5px; letter-spacing: .16em;
  color: rgba(255, 255, 255, .68);
}
/* 底部的「向下滚动」小指示 */
.gate-scroll {
  margin-top: 14px;
  width: 20px; height: 34px;
  border: 1px solid rgba(255, 255, 255, .34);
  border-radius: var(--radius-pill);
  display: flex; justify-content: center;
  padding-top: 6px;
}
.gate-scroll i {
  width: 2px; height: 7px;
  border-radius: 2px;
  background: var(--rose);
  animation: gateDot 1.9s cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes gateDot {
  0%   { opacity: 0; transform: translateY(0); }
  28%  { opacity: 1; }
  70%  { opacity: 0; transform: translateY(14px); }
  100% { opacity: 0; transform: translateY(14px); }
}

/* 顶部滚动进度轨 */
.gate-rail {
  position: fixed; left: 0; top: 0;
  z-index: 9000;
  width: 100%; height: 2px;
  pointer-events: none;
  background: transparent;
}
.gate-rail > i {
  display: block;
  height: 100%;
  width: calc(var(--rail, 0) * 100%);
  background: linear-gradient(90deg, var(--rose), var(--rose-2, #ff7ba0));
  box-shadow: 0 0 10px rgba(224, 70, 111, .6);
}

/* 移动端：整屏高度用 svh，别被手机地址栏顶掉一截 */
@media (max-width: 760px) {
  .gate, .gate-inner { min-height: 88svh; }
  .gate-title { font-size: clamp(32px, 11vw, 54px); }
}

/* 减少动态效果：静态显示主视觉（不做开场 / 淡出 / 漂移） */
@media (prefers-reduced-motion: reduce) {
  .gate-hero { opacity: 1 !important; animation: none !important; }
  .gate-bg { animation: none !important; scale: none !important; }
  .gate-bg::after { display: none !important; }          /* 幕布直接不要 */
  .gate-kicker, .gate-sub, .gate-scroll, .gate-dust { animation: none !important; }
  .gate-title { animation: none !important; clip-path: none !important; }
  .gate-scroll i { animation: none; }
  .gate-rail { display: none; }
}

/* ⚠ 一滚动就结束开场。
   ---------------------------------------------------------
   index.astro / Gate.astro 的脚本在第一次真实滚动时给 .gate 打上
   data-opened（**一次性**，不再摘掉；滚动期间另外打 data-scrolling）。

   为什么必须是一次性的：这些进场动画都是 `both` 填充的一次性动画，
   从第 0 帧就占着属性不放；写成"滚动时禁用、停下再恢复"的话，
   规则一失效浏览器会把动画当新动画重套一遍，从 0% 再来一次。

   ⚠ 名单里**故意没有** .gate-hero / .gate-veil / .gate-atmosphere：
     它们是"画面本身"，中断只会让画面突然变清晰或突然变亮 ——
     那是从"开场"直接跳到"结束"，比让它自己播完难看。
     让它们安静播完即可（都不影响可读性）。
   这里只掐"文字类"和"装饰类"，它们中途结束的观感就是"直接到位"。 */
.gate[data-opened] .gate-title,
.gate[data-opened] .gate-kicker,
.gate[data-opened] .gate-sub,
.gate[data-opened] .gate-scroll,
.gate[data-opened] .gate-dust { animation: none !important; }
/* =========================================================
 *  ③ 鼠标视差 + 滚动位移（同一层上只剩这两件事）
 *  ---------------------------------------------------------
 *  每个元素最多只用 transform / translate 的**一个**通道：
 *    · .gate-bg       → 只有 scale（开场）+ 幕布伪元素。**不放 transform**，
 *                        因为幕布 ::after 会跟着父元素的 transform 一起动，
 *                        会把"向上提起"的位移方向搞乱
 *    · .gate-hero     → transform 走指针视差，opacity 走滚动淡出
 *    · .gate-inner    → 只走指针视差（文字不再跟着滚动上下飘，
 *                        滚动时文字和背景反向错位，就是"发飘"的观感来源）
 *    · .gate-particles→ transform 走指针视差；.gate-dust → translate 走滚动
 *  ⚠ 这里**不许**再写 transition: transform ——
 *    指针位置是每帧写进变量的，叠一个 0.5s 过渡等于每帧重开一次过渡，
 *    手感会明显"慢半拍"。要阻尼在 JS 里做插值。
 * ======================================================= */
.gate-bg {
  translate: 0 var(--gate-ty, 0px);
}
/* ⚠ 背景推近必须写在**这条基础规则之后**。
   基础规则也用了 `.gate-bg` 这个选择器、而且在文件里出现得更早，
   如果入场动画写回上面那段，就会被这条覆盖掉（历史上就这么丢过一次入场）。 */
html.u-entered .gate-bg {
  animation: gateBgIn 1.6s cubic-bezier(.16, .84, .3, 1) both;
}
.gate-hero {
  transform: translate3d(calc(var(--mx, 0) * 9px), calc(var(--my, 0) * 9px), 0);
}
.gate-inner { transform: translate3d(calc(var(--mx, 0) * -5px), calc(var(--my, 0) * -5px), 0); }
/* 漂浮粒子单独包一层来承接滚动位移：
   .gate-particles 承接指针视差（transform），
   .gate-dust 承接滚动位移（translate）+ 开场漂移（animation）——
   一个元素只能有一条 animation，挤在一起就会互相顶掉。 */
.gate-particles { transform: translate3d(calc(var(--mx, 0) * 20px), calc(var(--my, 0) * 20px), 0); }
.gate-dust {
  position: absolute; inset: 0;
  translate: 0 var(--gate-ty-dust, 0px);
  /* 比文字再晚一点（0.45s）：让"幕布拉开 → 字出来 → 尘埃浮起"有先有后 */
  animation: gateDriftIn 1.35s cubic-bezier(.2, .7, .2, 1) .45s both;
}
@keyframes gateDriftIn {
  from { opacity: 0; translate: 0 calc(var(--gate-ty-dust, 0px) + 34px); }
  to   { opacity: 1; translate: 0 var(--gate-ty-dust, 0px); }
}

/* 触屏 / 窄屏：不做指针视差，位移压到最低（手机上少动一点更稳） */
@media (max-width: 760px) {
  .gate-hero, .gate-inner, .gate-particles, .gate-dust { transform: none; }
}

/* =========================================================
 *  ① 环境粒子 + ② 氛围层
 *  ---------------------------------------------------------
 *  这一段是照参考站（yuimi-chaya.github.io / Kisara 主题）的两条规则
 *  做的，也是它"画面是活的、但不吵"的全部秘密：
 *
 *    .kisara-ambient-particles i{
 *      width:var(--kisara-particle-size); height:var(--kisara-particle-size);
 *      opacity:0; background:hsl(var(--kisara-particle-hue) 35% 88%);
 *      animation:kisara-ambient-particle-drift var(--kisara-particle-duration) linear infinite;
 *      border-radius:1px;
 *    }
 *    .kisara-gate-atmosphere{
 *      background:
 *        linear-gradient(112deg,#14193738,#1419370a 42%,#ffbed21a),
 *        radial-gradient(at 50% 46%,#0000 38%,#080a1f1a 68%,#080a1f52 100%),
 *        linear-gradient(#070a1f47,#070a1f2b 44%,#070a1f6b);
 *    }
 *
 *  要点：
 *    · 粒子是 1~3px 的**小方块**（border-radius:1px，不是圆点），
 *      低饱和、极慢、永远不跟鼠标互动 —— 只是空气里的尘埃
 *    · 氛围层完全不动画，只叠三种渐变：斜向冷光 + 中心暗角 + 上下压暗。
 *      它把"一张图"变成"一个场景"，而且零成本
 *  ⚠ 这里替掉了原来那层可交互的 Canvas 引力星野（132 个粒子两两连线、
 *    带 shadowBlur 辉光、每帧 8600 次距离计算）—— 那是首屏最贵的一块，
 *    而且"鼠标一过就连线发亮"属于会玩，不属于好看。
 * ======================================================= */
.gate-ambient {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  overflow: hidden;
  /* 幕布提起来之前就开始显影，别让"拉开之后才出现"看起来像加载 */
  opacity: 0;
  animation: gateAmbientIn 1.5s ease .5s forwards;
}
@keyframes gateAmbientIn { to { opacity: 1; } }

.gate-ambient i {
  position: absolute;
  left: var(--x, 50%);
  top: var(--y, 50%);
  width: var(--s, 2px);
  height: var(--s, 2px);
  border-radius: 1px;                     /* 小方块，不是圆点 */
  background: hsl(var(--hue, 226) 35% 88%);
  opacity: 0;
  /* linear + 负延迟：每个方块一进场就已经在漂，不会"同时起步" */
  animation: gateAmbientDrift var(--dur, 40s) linear var(--delay, 0s) infinite;
  will-change: transform, opacity;
}
@keyframes gateAmbientDrift {
  0%   { opacity: 0;    transform: translate3d(0, 12px, 0); }
  18%  { opacity: .55; }
  72%  { opacity: .32; }
  100% { opacity: 0;    transform: translate3d(14px, -78px, 0); }
}

.gate-atmosphere {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none;
  /* ⚠ 和 .gate-veil 一样：静态终态，不带动画；
     并且数值一起减半（原来 .10/.07/.12/.18/.22，现在全部腰斩），
     理由见 .gate-veil 上面那段 —— 四层压暗叠起来把插画吃成一块黑。 */
  background:
    linear-gradient(112deg, rgba(20, 25, 55, .05), rgba(20, 25, 55, 0) 46%, rgba(255, 190, 210, .03)),
    radial-gradient(at 50% 46%, transparent 52%, rgba(8, 10, 31, .04) 74%, rgba(8, 10, 31, .09) 100%),
    linear-gradient(rgba(7, 10, 31, .06), rgba(7, 10, 31, .02) 46%, rgba(7, 10, 31, .11));
}

/* 减少动态效果 / 窄屏：尘埃不要（省电也省视觉噪音），氛围层留着 */
@media (prefers-reduced-motion: reduce), (max-width: 760px) {
  .gate-ambient { display: none; }
}

/* =========================================================
 *  可玩性动效：**已整体删除**
 *  ---------------------------------------------------------
 *  这一段原来放着四套"会玩"的效果：卡片 3D 倾斜、卡面光晕跟随、
 *  点击涟漪、粒子爆发。参考站 yuimi-chaya.github.io（Kisara 主题）
 *  在四个首页 CSS 里一次都没用：
 *    · `perspective(` / `rotateX(` → 0 次
 *    · 点击涟漪 / 粒子爆发 → 完全没有
 *  它卡片唯一的悬停反馈是 `transition: border-color .16s ease-out`。
 *
 *  删掉的理由：
 *    · 3D 倾斜 + 光晕让每张卡片都在自己动，一屏五张卡就是五份注意力；
 *      而且倾斜写的是 transform，和滚动浮现/悬停位移挤在同一个属性上
 *    · 涟漪在每次点导航/链接时都炸一圈玫红，和页面切换过渡抢戏
 *    · 自定义光标（.cur-*）也一并删了，见上面那一段
 *  卡片的悬停反馈现在只剩"轻微上浮 + 描边变色"，见下面「卡片悬停」。
 * ======================================================= */

/* ---------- 卡片悬停：轻微上浮 + 描边变色（对齐参考站的克制写法） ----------
   参考站是 `transition: border-color .16s ease-out`；这里保留一点点位移，
   因为纯变色在深色底上不够明确。位移用 translate（不是 transform），
   这样它和滚动驱动动画的 translate 是同一个通道、写法一致，
   而 transform 这个属性彻底空出来给别的东西用。 */
.post,
.gallery-item,
.video-card,
.panel,
.link-card,
.photo-card {
  position: relative;
  transition: translate .28s cubic-bezier(.22, .61, .36, 1),
              border-color .22s ease-out,
              box-shadow .28s ease;
}
@media (hover: hover) {
  .post:hover,
  .gallery-item:hover,
  .video-card:hover,
  .panel:hover,
  .link-card:hover,
  .photo-card:hover {
    translate: 0 -3px;
    border-color: var(--rose-dim);
    box-shadow: 0 14px 34px rgba(0, 0, 0, .32);
  }
}
@media (prefers-reduced-motion: reduce) {
  .post, .gallery-item, .video-card, .panel, .link-card, .photo-card {
    transition: none !important;
    translate: none !important;
  }
}

/* ---------- 滚动驱动动画：只剩接缝条(seam) ----------
   首屏那三层（背景/文字/粒子）以前也挂在这里，问题是：
   `animation: gateParallaxBg linear both` 是一句**简写**，
   它会把上面 .gate-bg 那条入场动画（gateBgIn）整条覆盖掉 ——
   所以在支持 animation-timeline 的浏览器（Chrome / Edge / 新版 Safari）
   里，首屏根本没有入场动画，一进去就是"啪"地全出现。
   现在首屏的滚动位移改由 JS 写 --gate-ty / --gate-ty-dust，
   入场动画独占了 animation 这个属性，两边不再抢。
   面板区的 scrollLift 也一并去掉：它和 html.reveal 的浮现（opacity +
   transform）同时作用在同一批卡片上，等于一次入场叠了两套位移。 */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* 首屏的三层视差（原来在这里）已经移交 JS：见上面 .gate-bg / .gate-inner
       / .gate-particles 的 --gate-ty 说明。面板区的 scrollLift 同样已删除。
       这个 @supports 块留给以后想加的原生滚动驱动效果。 */
  }
}

/* ---------- ⑤ 相册筛选：编排式入场 ----------
   ⚠ 类名是 .photo-card（相册页用的），不是 .gallery-item（那是旧类名）。
   切换分类时：旧图缩小模糊退场，新图错峰放大入场（--stagger 由 JS 写）。
   默认（没脚本）直接显示，不会白屏。 */

.photo-card.is-entering {
  animation: galleryIn .56s cubic-bezier(.2, .8, .25, 1) both;
  animation-delay: var(--stagger, 0s);
}
@keyframes galleryIn {
  0%   { opacity: 0; transform: scale(.9) translateY(14px); filter: blur(6px); }
  100% { opacity: 1; transform: scale(1) translateY(0); filter: blur(0); }
}

.photo-card.is-leaving {
  animation: galleryOut .26s ease forwards;
  pointer-events: none;
}
@keyframes galleryOut {
  to { opacity: 0; transform: scale(.94); filter: blur(4px); }
}

@media (prefers-reduced-motion: reduce) {
  .photo-card.is-entering,
  .photo-card.is-leaving { animation: none; }
}
.gate-particles {
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  overflow: hidden;
}
.gate-particles i {
  position: absolute;
  bottom: -12px;
  width: 2px; height: 2px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .9);
  box-shadow: 0 0 9px rgba(255, 178, 205, .95), 0 0 18px rgba(224, 70, 111, .5);
  opacity: 0;
  animation: gateFloat var(--dur, 24s) linear var(--delay, 0s) infinite;
}
@keyframes gateFloat {
  0%   { opacity: 0; transform: translate3d(0, 0, 0) scale(.55); }
  14%  { opacity: .9; }
  70%  { opacity: .55; }
  100% { opacity: 0; transform: translate3d(var(--dx, 14px), calc(-1 * var(--dy, 60vh)), 0) scale(1.15); }
}

/* ④ 黑场节拍（.gate-flash）：整个效果已经删掉了 ——
   它原来靠 JS 写 --flash 来压黑一下，但脚本里那两行本来就被设成 0
   （"实际看着像闪一下，像故障而不像节拍"），元素一直是个永远透明的空壳。
   现在把元素和规则一起删掉；下面这条只防旧缓存里还留着 DOM 的情况。 */
.gate-flash { display: none; }

/* ⑤ 光带扫过：已删除（见 index.astro 注释）。保留这条只为防旧缓存里
   还留着那个元素时露出白条 —— 直接隐藏。 */
.gate-sweep { display: none; }

/* ② 标题注魔：渐变字 + clip-path 圆形揭晓（源站这个效果本来就是纯 CSS）
   ⚠ 渐变收手了：原来从 #ffffff 一路冲到 --rose(#e0466f) 再拉回来，
     在 92px 的大字上，"玫红"占了将近半个字宽 —— 压在那张深色插画上
     就是一条突兀的粉柱子，也是"不好看"最直观的一处。
     现在改成「白 → 极淡的玫瑰白 → 白」：只在字的下缘留一点暖意，
     整体仍然是白字，跟主视觉的冷紫灰是协调的。 */
.gate-title {
  background-image: linear-gradient(
    102deg,
    #ffffff 0%, #fffafc 38%, #ffe9f1 54%, #fff6fa 72%, #ffffff 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  /* ⚠ 这里原来还有 filter: drop-shadow(0 6px 26px rgba(224,70,111,.38))。
     文字被设成 transparent（渐变裁进字形），drop-shadow 只跟"绘制出来的
     不透明像素"走，所以这层玫红投影基本看不到，留下的是每帧对大字号做
     一次滤镜的开销。删掉。 */
}
/* ⚠ 标题的入场不再依赖 .is-in（IntersectionObserver）——
   回调时机比首屏绘制晚，会让标题先完整露一下、再重新揭晓一次。
   现在动画直接挂在 .gate-title 上，上面那条就是全部。 */
@keyframes gateTitleIn {
  from {
    opacity: 0;
    clip-path: circle(0% at 8% 55%);
    transform: translate3d(0, 18px, 0);
  }
  to {
    opacity: 1;
    clip-path: circle(150% at 8% 55%);
    transform: none;
  }
}
/* 减少动效：标题直接显示完整（不做圆形揭晓） */
@media (prefers-reduced-motion: reduce) {
  .gate-title { animation: none !important; clip-path: none !important; }
  .gate-particles, .gate-flash, .gate-sweep { display: none !important; }
  .gate-bg, .gate-inner, .gate-particles, .gate-dust { transform: none !important; }
}

/* =========================================================
 *  接缝条（Seam）—— 章节之间那条「漫画分格 / 撕纸」的斜切过渡
 *  ---------------------------------------------------------
 *  做法参考自 Kisara 主题的 home-transitions.css：
 *    一条带竖向渐变的横条 + 两个不同角度的伪元素色带（clip-path 斜切）
 *  · 上下各 -34px 负 margin 与相邻区块重叠 → 不出现硬边
 *  · pointer-events:none 绝不挡点击/滚动
 *  · isolation:isolate + z-index 固定 → 不干扰首屏 Gate 的层叠
 *  · clip-path 只用来切形状，**不做 transition**（只动 transform/opacity）
 * ======================================================= */
.seam {
  /* ⚠ 配色照参考站（Kisara 主题 / home-transitions.css）抄的实色：
       .kisara-home-transition:before{ background:#ad3c60 }  玫红
       .kisara-home-transition:after { background:#7bb6b2 }  青
     原来这两条是 52% / 38% 的半透明色，叠在深色底上会"发灰发脏"，
     斜切带看着像污渍。实色更干净，也才有那本画集的印刷感。
     （.seam--footer 换成"最新→页脚"那一组的配色：青在前、玫红在后） */
  --band-a: #ad3c60;
  --band-b: #7bb6b2;
  position: relative;
  height: clamp(78px, 11vw, 116px);
  margin-block: -34px;
  pointer-events: none;
  overflow: hidden;
  isolation: isolate;
  z-index: 1;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(224, 70, 111, .06) 44%,
    rgba(123, 182, 178, .06) 56%,
    transparent 100%
  );
}
.seam::before,
.seam::after {
  content: '';
  position: absolute;
  inset: 26px -2px;
}
.seam::before {
  background: var(--band-a);
  clip-path: polygon(0 10%, 100% 42%, 100% 74%, 0 44%);
}
.seam::after {
  background: var(--band-b);
  clip-path: polygon(0 58%, 100% 26%, 100% 46%, 0 76%);
}

/* 第二条换角度（斜向反过来），两条放在一起才不会像复制粘贴 */
.seam--footer::before { clip-path: polygon(0 62%, 100% 22%, 100% 50%, 0 84%); }
.seam--footer::after  { clip-path: polygon(0 14%, 100% 48%, 100% 70%, 0 38%); }
.seam--footer { --band-b: #7bb6b2; }

/* 支持的浏览器上让它随滚动轻微漂移（Chromium / 新版 Safari 才有；
   不支持的浏览器就是静态斜切条，一样好看） */
@supports (animation-timeline: view()) {
  .seam::before,
  .seam::after {
    animation: seamDrift linear both;
    animation-timeline: view(block);
    animation-range: entry 0% exit 100%;
  }
  @keyframes seamDrift {
    from { transform: translateY(6px); }
    to   { transform: translateY(-6px); }
  }
}

@media (max-width: 760px) {
  .seam { height: clamp(64px, 16vw, 96px); margin-block: -26px; }
  .seam::before, .seam::after { inset: 20px -2px; }
}

/* 减少动态效果：保持静态斜切条（不漂移） */
@media (prefers-reduced-motion: reduce) {
  .seam::before, .seam::after { animation: none !important; transform: none !important; }
}

/* 终幕画面：独立一段，保证这张图一定被看到（不再只靠滚动揭晓） */
.finale {
  position: relative;
  margin: 34px 0 0;
  border-radius: var(--radius);
  overflow: clip;
  min-height: clamp(240px, 46vh, 520px);
  background: var(--bg-2);
  isolation: isolate;
}
/* ⚠ 原来这里有一条 html.reveal .finale（靠 JS 加 .reveal 才隐藏、
   滚到再加 .is-in 浮出来）。那套 IntersectionObserver 浮现已经整体换成
   滚动驱动动画（见上面 scrollSettle），所以这两条删掉。
   终幕现在是：进视口时由 scrollSettle 轻轻上浮 + 放大，滚过去复位。 */
.finale-art {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 42%;
  transform: scale(1.001);
  transition: transform 1.1s cubic-bezier(.22, .61, .36, 1), filter .6s ease;
}
.finale:hover .finale-art { transform: scale(1.025); }
.finale-art.is-broken { opacity: 0; }
.finale-veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(9, 11, 22, .18), transparent 34%, rgba(9, 11, 22, .72) 100%);
}
.finale-cap {
  position: absolute;
  left: clamp(18px, 4vw, 44px);
  bottom: clamp(16px, 3.4vw, 34px);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .3em;
  color: #fff;
  text-shadow: 0 2px 20px rgba(0, 0, 0, .65);
}

/* =========================================================
 *  友链卡片（.link-*）
 *  ---------------------------------------------------------
 *  ⚠ 这一段是**预留**的，当前没有任何页面在用它 ——
 *    /links/ 走的是下面那套 .route / .orb 的「keep the route open」场景。
 *    这段留给 links.js 里的 avatar / tags / since / featured 字段
 *    （README「友链是怎么工作的」有说明）。想用就得自己在页面里写标签。
 *    不要当成死样式删掉。
 * ======================================================= */
.link-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
  margin: 26px 0 0;
}

.link-card {
  position: relative;
  display: flex; align-items: center; gap: 14px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .02);
  overflow: hidden;
  transition: transform .32s cubic-bezier(.22, .61, .36, 1),
              border-color .32s ease, box-shadow .32s ease, background .32s ease;
}
.link-card:hover {
  transform: translateY(-3px);
  border-color: var(--rose-dim);
  background: rgba(224, 70, 111, .05);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .34);
}
/* 鼠标划过时左侧扫出一条玫红竖条 */
.link-card::before {
  content: '';
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 2px;
  background: linear-gradient(180deg, var(--rose), transparent);
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform .38s cubic-bezier(.22, .61, .36, 1);
}
.link-card:hover::before { transform: scaleY(1); }
.link-card.is-featured { border-color: rgba(224, 70, 111, .42); }

/* 头像位：没有图就按站名生成一张配色底图（首字母 + 渐变色相） */
.link-art {
  flex: none;
  width: 56px; height: 56px;
  border-radius: 50%;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(140deg,
    hsl(var(--h, 340) 62% 34%), hsl(calc(var(--h, 340) + 40) 55% 18%));
  border: 1px solid var(--line-2);
}
.link-art img { width: 100%; height: 100%; object-fit: cover; }
.link-art img.is-broken { display: none; }
.link-art b {
  font-family: var(--font-serif);
  font-size: 24px; color: #fff; opacity: .92;
}

.link-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.link-name {
  font-family: var(--font-serif);
  font-size: 17px; font-weight: 700; color: var(--ink);
  display: flex; align-items: center; gap: 6px;
}
.link-star { font-style: normal; font-size: 11px; color: var(--rose); }
.link-desc {
  font-size: 12.5px; line-height: 1.6; color: var(--ink-2);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.link-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 3px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
  color: var(--ink-3);
}
.link-meta em { font-style: normal; color: var(--rose-2); }
.link-since { margin-left: auto; opacity: .8; }

/* 底部「交换友链」区 */
.link-open {
  margin: 34px 0 0;
  padding: 22px clamp(18px, 3.4vw, 30px);
  border: 1px solid var(--rose-dim);
  border-radius: var(--radius);
  background: linear-gradient(150deg, rgba(224, 70, 111, .07), rgba(123, 182, 178, .05));
}
.link-open-desc { margin: 12px 0 16px; font-size: 13.5px; line-height: 1.8; color: var(--ink-2); }
.link-mine {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.link-mine-row {
  display: flex; gap: 14px;
  padding: 10px 14px;
  font-size: 12.5px;
  border-bottom: 1px solid var(--line-2);
}
.link-mine-row:last-child { border-bottom: 0; }
.link-mine-row > span {
  flex: none; width: 46px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
  color: var(--ink-3);
}
.link-mine-row > b {
  font-weight: 500; color: var(--ink-2);
  word-break: break-all;
}
.link-open-hint { margin: 16px 0 0; font-size: 12.5px; line-height: 1.8; color: var(--ink-3); }
.link-open-hint a { color: var(--rose); border-bottom: 1px solid var(--rose-dim); }

@media (max-width: 560px) {
  .link-grid { grid-template-columns: 1fr; gap: 12px; }
  .link-mine-row { flex-direction: column; gap: 3px; }
  .link-mine-row > span { width: auto; }
}

/* =========================================================
 *  首页「进入友链」入口条
 *  （参考用户给的那张图：左边心形方块 + 中间 eyebrow/大字 + 右边 ↗）
 * ======================================================= */
/* ---------------------------------------------------------
 *  整条：深色玻璃横幅卡片
 *  · 底色比页面略亮（gradient 叠在 --bg-2 上），四周玫红细描边 + 圆角
 *  · box-shadow 第一层是 inset 高光 → 玻璃质感的内圈亮线
 *  · isolation 让 ::before/::after 的斜切带留在卡片层叠里
 *  · 悬停只动 transform / border / box-shadow，绝不动 padding|margin，
 *    所以不会把下面的章节推来推去
 * ------------------------------------------------------- */
.friend-entry {
  /* 显式抬到上层 + 允许点击：首页里有几个 position:relative / fixed 的元素
     （gate、seam、进度轨、finale），万一有重叠就会把点击吃掉 */
  position: relative;
  z-index: 3;
  pointer-events: auto;
  isolation: isolate;
  display: flex;
  align-items: center;
  gap: clamp(15px, 3vw, 28px);
  margin: 36px 0 0;
  padding: clamp(18px, 2.4vw, 24px) clamp(18px, 3vw, 30px);
  overflow: hidden;                      /* 剪住斜切带与圆角 */
  border: 1px solid rgba(224, 70, 111, .28);
  border-radius: var(--radius);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, .055) 0%, rgba(255, 255, 255, .014) 42%, rgba(224, 70, 111, .05) 100%),
    var(--bg-2);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .09),      /* 内侧一圈高光 */
    inset 0 0 0 1px rgba(255, 255, 255, .02),
    0 20px 48px rgba(0, 0, 0, .32);
  transition:
    transform .45s cubic-bezier(.22, .61, .36, 1),
    border-color .35s ease,
    background .35s ease,
    box-shadow .45s ease;
}
.friend-entry:hover {
  transform: translateY(-4px);
  border-color: rgba(224, 70, 111, .55);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, .08) 0%, rgba(255, 255, 255, .02) 42%, rgba(224, 70, 111, .1) 100%),
    var(--bg-2);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .14),
    inset 0 0 0 1px rgba(224, 70, 111, .12),
    0 30px 64px rgba(0, 0, 0, .46),
    0 0 34px rgba(224, 70, 111, .16);
}

/* 右侧极淡的斜切色带（借 .seam 的 clip-path 做法，呼应首页两条接缝条）
   · 不透明度压到 ~1/4，只作为装饰，不抢内容
   · inset 取负值 → 色带斜着切出卡片边缘，像被裁掉一刀
   · 悬停时用 transform 轻微扫过（不动 clip-path，避免重绘抖动） */
.friend-entry::before,
.friend-entry::after {
  content: '';
  position: absolute;
  inset: -6% -10% -6% 40%;
  z-index: -1;                           /* 沉到卡片背景之下，只被 overflow 剪出来 */
  pointer-events: none;
  transition: transform .8s cubic-bezier(.22, .61, .36, 1);
}
.friend-entry::before {                     /* 玫红带 */
  background: rgba(224, 70, 111, .16);
  clip-path: polygon(100% 2%, 100% 34%, 0 72%, 0 46%);
}
.friend-entry::after {                      /* 青灰带（和 .seam 的 --band-b 同源） */
  background: rgba(123, 182, 178, .11);
  clip-path: polygon(100% 44%, 100% 62%, 0 96%, 0 76%);
}
.friend-entry:hover::before { transform: translate3d(-22px, -6px, 0); }
.friend-entry:hover::after  { transform: translate3d(-38px, -10px, 0); }

/* 内容抬到色带之上 */
.friend-entry > .friend-icon,
.friend-entry > .friend-text,
.friend-entry > .friend-arrow { position: relative; z-index: 1; }

/* ---------------------------------------------------------
 *  左侧图标位：圆角方块 + 玫红描边 → 悬停填充玫红 + 发光
 * ------------------------------------------------------- */
.friend-icon {
  flex: none;
  width: 64px; height: 64px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(224, 70, 111, .45);
  border-radius: var(--radius);          /* 比 --radius-sm 更圆润，和卡片同族 */
  background: linear-gradient(150deg, rgba(224, 70, 111, .16), rgba(224, 70, 111, .05));
  color: var(--rose-2);
  font-size: 26px;
  line-height: 1;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .1),
    0 0 0 0 rgba(224, 70, 111, 0);
  transition:
    transform .45s cubic-bezier(.22, .61, .36, 1),
    border-color .35s ease,
    background .35s ease,
    color .3s ease,
    box-shadow .45s ease;
}
.friend-entry:hover .friend-icon {
  border-color: var(--rose);
  background: linear-gradient(150deg, var(--rose), #b8325a);
  color: #fff;
  /* 悬停反馈用 transform 过渡（.friend-icon 上已经声明了 transition）——
     可逆、平滑、不会闪。 */
  transform: scale(1.05);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 8px 20px rgba(224, 70, 111, .34),
    0 0 14px rgba(224, 70, 111, .26);
}
/* ⚠ 原来的 @keyframes friendHeart 已删除。
   它没写 animation-fill-mode: forwards —— 动画播完会立刻回到初始值，
   表现就是"爱心跳一下又弹回原位"，看着像抽动（用户实际看到的就是这个）。
   悬停反馈一律走 transition，不要用只播一次的关键帧。 */

/* 中间文字块 */
.friend-text {
  display: flex; flex-direction: column; gap: 5px;
  min-width: 0;
  position: relative;
  padding-left: 15px;
  transition: transform .45s cubic-bezier(.22, .61, .36, 1);
}
/* 左侧一道玫红竖线 —— 呼应卡片切角，也给这块文字一个「被框住」的起点 */
.friend-text::before {
  content: '';
  position: absolute;
  left: 0;
  top: 2px; bottom: 2px;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--rose), rgba(224, 70, 111, .06));
  transition: background .35s ease, box-shadow .35s ease;
}
.friend-entry:hover .friend-text::before {
  background: linear-gradient(180deg, var(--rose-2), rgba(224, 70, 111, .14));
  box-shadow: 0 0 12px rgba(224, 70, 111, .5);
}
.friend-eyebrow {
  font-family: var(--font-mono);
  font-size: 10.5px; letter-spacing: .3em;
  color: rgba(242, 116, 154, .82);       /* 原来 --ink-3 太淡，提到可读 */
}
.friend-title {
  font-family: var(--font-serif);
  font-size: clamp(22px, 3.6vw, 34px);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: .01em;
  white-space: nowrap;                   /* clamp 上限 34px 无需换行 */
  background-image: linear-gradient(
    98deg,
    #ffffff 0%, #ffe6ef 26%, var(--rose-2) 58%, var(--rose) 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 4px 18px rgba(224, 70, 111, .2));
  transition: filter .4s ease;
}
.friend-entry:hover .friend-title {
  filter: drop-shadow(0 5px 24px rgba(224, 70, 111, .42));
}

/* ---------------------------------------------------------
 *  右侧 ↗：圆形圆环按钮；悬停整条右移 + 箭头右上位移 + 描边变亮
 * ------------------------------------------------------- */
.friend-arrow {
  margin-left: auto; flex: none;
  width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(236, 237, 246, .18);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .03);
  font-family: var(--font-mono);         /* 等宽字里的 ↗ 更工整 */
  font-size: 18px;
  line-height: 1;
  color: var(--ink-2);                   /* 比 --ink-3 亮一档 */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
  transition:
    transform .45s cubic-bezier(.22, .61, .36, 1),
    border-color .35s ease,
    color .3s ease,
    background .35s ease,
    box-shadow .45s ease;
}
.friend-entry:hover .friend-arrow {
  transform: translateX(4px);
  border-color: var(--rose);
  color: var(--rose);
  background: rgba(224, 70, 111, .1);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .12),
    0 0 22px rgba(224, 70, 111, .3);
}

@media (max-width: 560px) {
  .friend-entry { gap: 13px; padding: 15px 16px; margin-top: 28px; }
  .friend-entry::before,
  .friend-entry::after { left: 30%; }    /* 窄屏色带别盖到文字上 */
  .friend-icon { width: 42px; height: 42px; font-size: 18px; border-radius: var(--radius-sm); }
  .friend-text { padding-left: 12px; }
  .friend-eyebrow { letter-spacing: .18em; font-size: 9.5px; }
  .friend-title { font-size: clamp(19px, 5.4vw, 24px); }
  .friend-arrow { width: 38px; height: 38px; font-size: 16px; }
}

/* 减少动态效果：只保留颜色变化，不做位移/旋转/跳动 */
@media (prefers-reduced-motion: reduce) {
  .friend-entry, .friend-icon, .friend-text, .friend-text::before,
  .friend-title, .friend-arrow, .friend-entry::before, .friend-entry::after {
    transition: none !important;
  }
  .friend-entry:hover { transform: none !important; }
  .friend-entry:hover .friend-icon { transform: none !important; animation: none !important; }
  .friend-entry:hover .friend-arrow { transform: none !important; }
  .friend-entry:hover .friend-text { transform: none !important; }
  .friend-entry:hover::before,
  .friend-entry:hover::after { transform: none !important; }
}

/* =========================================================
 *  友链页「keep the route open」（/links/）
 *  参考 Kisara 主题：全屏场景 + 左侧大字 + 右侧玻璃面板
 * ======================================================= */
.route {
  position: relative;
  margin: 0 0 26px;
  border-radius: var(--radius);
  overflow: clip;
  isolation: isolate;
  min-height: clamp(560px, 84vh, 920px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 41%);
  align-content: space-between;
  gap: clamp(20px, 4vw, 54px);
  padding: clamp(26px, 4.6vw, 62px);
  background: var(--bg-2);
}

/* 背景：一层图片 + 一层紫调渐变压暗（都在 z-index 负层，不挡点击） */
.route::before {
  content: '';
  position: absolute; inset: 0; z-index: -2;
  background-image: var(--route-bg);
  background-size: cover;
  background-position: center 42%;
  filter: saturate(.9) brightness(.66);
  transform: scale(1.02);
}
.route::after {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(120% 90% at 18% 26%, rgba(122, 74, 168, .5), transparent 56%),
    radial-gradient(90% 70% at 92% 80%, rgba(60, 96, 140, .35), transparent 60%),
    linear-gradient(180deg, rgba(9, 11, 22, .58) 0%, rgba(9, 11, 22, .8) 60%, rgba(9, 11, 22, .92) 100%);
}

/* 细弧线（轨道感） */
.route-lines { position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }
.route-lines i { position: absolute; border: 1px solid rgba(255, 255, 255, .09); border-radius: 50%; }
.route-lines i:nth-child(1) { width: 120%; height: 150%; left: -38%; top: -32%; }
.route-lines i:nth-child(2) { width: 86%; height: 118%; right: -24%; bottom: -42%; }
.route-lines i:nth-child(3) { width: 1px; height: 150%; left: 51%; top: -22%; border: 0;
  background: linear-gradient(180deg, transparent, rgba(255, 255, 255, .2), transparent); }
.route-lines i:nth-child(4) { width: 150%; height: 1px; left: -22%; top: 62%; border: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .14), transparent); }

/* 左栏 */
.route-left { position: relative; z-index: 1; align-self: start; max-width: 640px; }
.route .eyebrow { margin-bottom: 18px; }
.route-title {
  margin: 0 0 20px;
  font-family: var(--font-serif);
  font-size: clamp(44px, 8.4vw, 104px);
  line-height: .94;
  letter-spacing: -.01em;
  color: #fff;
  text-shadow: 0 10px 50px rgba(0, 0, 0, .5);
}
.route-title em {
  font-style: italic;
  color: var(--rose);
  text-shadow: 0 8px 34px rgba(224, 70, 111, .45);
}
.route-p {
  margin: 0 0 12px;
  max-width: 34em;
  font-size: 13.5px;
  line-height: 1.95;
  color: rgba(255, 255, 255, .78);
}
.route-p--dim { color: rgba(255, 255, 255, .5); font-size: 12.5px; }

/* 右侧玻璃面板 */
.route-panel {
  position: relative;
  z-index: 1;
  align-self: start;
  padding: clamp(18px, 2.3vw, 26px);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--radius);
  background: linear-gradient(160deg, rgba(255, 255, 255, .1), rgba(255, 255, 255, .03));
  backdrop-filter: blur(9px);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .42);
}
/* 四个角的细括号（只用两个伪元素，各画两个角） */
.route-panel::before,
.route-panel::after {
  content: '';
  position: absolute;
  width: 16px; height: 16px;
  border-color: rgba(255, 255, 255, .45);
  border-style: solid;
  border-width: 0;
}
.route-panel::before { left: 7px; top: 7px; border-left-width: 1px; border-top-width: 1px; }
.route-panel::after { right: 7px; bottom: 7px; border-right-width: 1px; border-bottom-width: 1px; }

.route-panel-head {
  margin: 0 0 4px;
  font-family: var(--font-mono);
  font-size: 9.5px; letter-spacing: .22em;
  color: rgba(255, 255, 255, .55);
}
.route-panel-head i { font-style: normal; color: var(--rose); }

/* 轨道图形 */
.route-orb {
  position: relative;
  height: clamp(96px, 12vw, 138px);
  display: flex; align-items: center; justify-content: center;
  margin: 4px 0 10px;
}
.orb-core {
  width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 13px;
  font-size: 19px; color: #fff;
  background: linear-gradient(135deg, #ff7ba0, #7b6cff);
  box-shadow: 0 0 34px rgba(255, 123, 160, .6);
  animation: orbFloat 6.5s ease-in-out infinite;
}
@keyframes orbFloat {
  0%, 100% { transform: translateY(-2px) rotate(6deg); }
  50%      { transform: translateY(3px) rotate(-4deg); }
}
.orb-ring {
  position: absolute;
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 50%;
}
.orb-ring--a {
  width: min(74%, 186px); height: min(54%, 104px);
  animation: orbSpinA 19s linear infinite;
}
.orb-ring--b {
  width: min(60%, 148px); height: min(44%, 84px);
  animation: orbSpinB 25s linear infinite;
}
@keyframes orbSpinA {
  from { transform: rotate(-16deg); }
  to   { transform: rotate(344deg); }
}
@keyframes orbSpinB {
  from { transform: rotate(26deg); }
  to   { transform: rotate(-334deg); }
}
.orb-dot {
  position: absolute;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 9px rgba(255, 255, 255, .95);
}
.orb-dot--a { left: 14%; top: 30%; animation: orbTwinkle 3.6s ease-in-out infinite; }
.orb-dot--b { right: 16%; bottom: 26%; animation: orbTwinkle 4.8s ease-in-out infinite .8s; }
@keyframes orbTwinkle {
  0%, 100% { opacity: .35; transform: scale(.8); }
  50%      { opacity: 1; transform: scale(1.25); }
}

/* 列表 */
.route-list { list-style: none; margin: 0; padding: 0; }
.route-list li > a,
.route-list li.is-empty {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 11px 8px;
  border-top: 1px solid rgba(255, 255, 255, .1);
  transition: background .3s ease, padding-left .38s cubic-bezier(.22, .61, .36, 1);
}
.route-list li:last-child > a,
.route-list li.is-empty:last-child { border-bottom: 1px solid rgba(255, 255, 255, .1); }
.route-list li > a:hover { background: rgba(255, 255, 255, .07); padding-left: 16px; }

.route-no {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .1em;
  color: rgba(255, 255, 255, .42);
}
.route-kicker {
  display: block;
  font-family: var(--font-mono);
  font-size: 9px; letter-spacing: .24em;
  color: rgba(255, 255, 255, .4);
}
.route-name {
  display: block;
  margin-top: 3px;
  font-family: var(--font-serif);
  font-size: 15.5px; font-weight: 700;
  color: #fff;
}
.route-desc {
  display: block;
  margin-top: 2px;
  font-size: 11.5px; line-height: 1.6;
  color: rgba(255, 255, 255, .58);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.route-go {
  font-size: 15px;
  color: rgba(255, 255, 255, .45);
  transition: transform .38s cubic-bezier(.22, .61, .36, 1), color .28s ease;
}
.route-list li > a:hover .route-go { transform: translate(3px, -3px); color: var(--rose); }
.route-list li.is-empty { opacity: .38; }

.route-panel-foot {
  display: flex; justify-content: space-between; gap: 14px;
  margin: 12px 0 0;
  font-family: var(--font-mono);
  font-size: 9px; letter-spacing: .2em;
  color: rgba(255, 255, 255, .38);
}

/* 底部一行 */
.route-bottom {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap;
  align-items: flex-end; justify-content: space-between;
  gap: 16px;
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .18em;
  color: rgba(255, 255, 255, .55);
}
.route-back { display: inline-flex; align-items: center; gap: 8px; color: rgba(255, 255, 255, .7); }
.route-back span { transition: transform .35s cubic-bezier(.22, .61, .36, 1); }
.route-back:hover span { transform: translateX(-4px); }
.route-back:hover { color: #fff; }
.route-slots { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 4px; text-align: right; }
.route-slots b { color: var(--rose); font-weight: 500; letter-spacing: .2em; }

/* 移动端：单列，标题收小，面板贴满 */
@media (max-width: 900px) {
  .route {
    grid-template-columns: 1fr;
    min-height: 0;
    gap: 26px;
    padding: 22px 18px 26px;
  }
  .route-title { font-size: clamp(38px, 13vw, 60px); }
  .route-panel { backdrop-filter: blur(6px); }
  .route-slots { align-items: flex-start; text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  .orb-core, .orb-ring--a, .orb-ring--b, .orb-dot { animation: none !important; }
  .route-list li > a, .route-go, .route-back span { transition: none !important; }
  .route-list li > a:hover { padding-left: 8px; }
}

/* =========================================================
 *  友链页进场编排（纯 CSS 动画：不依赖脚本，一定会播完并停在可见）
 *  顺序：标题 → 正文 → 面板 → 轨道图形 → 列表逐行 → 底部
 * ======================================================= */
@keyframes routeIn {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes routePanelIn {
  from { opacity: 0; transform: translateY(26px) scale(.986); filter: blur(7px); }
  to   { opacity: 1; transform: none; filter: none; }
}
@keyframes routeRowIn {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: none; }
}
@keyframes routeOrbIn {
  from { opacity: 0; transform: scale(.8); }
  to   { opacity: 1; transform: none; }
}

/* 左栏：eyebrow → 大标题 → 两段正文，依次落下 */
.route-left > * { animation: routeIn .85s cubic-bezier(.22, .61, .36, 1) both; }
.route-left > *:nth-child(1) { animation-delay: .04s; }
.route-left > *:nth-child(2) { animation-delay: .12s; }
.route-left > *:nth-child(3) { animation-delay: .28s; }
.route-left > *:nth-child(4) { animation-delay: .4s; }

/* 大标题：稍微放大着落下来，比正文更"重" */
.route-title { animation: routeIn 1.05s cubic-bezier(.22, .61, .36, 1) .12s both; }

/* 玻璃面板：从下方升起 + 从模糊到清晰 */
.route-panel { animation: routePanelIn 1.05s cubic-bezier(.22, .61, .36, 1) .3s both; }

/* 轨道图形：单独放大入场（它自己有浮动/旋转的循环动画，互不干扰） */
.route-orb { animation: routeOrbIn .95s cubic-bezier(.22, .61, .36, 1) .42s both; }

/* 列表逐行从右侧滑入（含空位那几行） */
.route-list li { animation: routeRowIn .62s cubic-bezier(.22, .61, .36, 1) both; }
.route-list li:nth-child(1) { animation-delay: .5s; }
.route-list li:nth-child(2) { animation-delay: .58s; }
.route-list li:nth-child(3) { animation-delay: .66s; }
.route-list li:nth-child(4) { animation-delay: .74s; }
.route-list li:nth-child(5) { animation-delay: .82s; }
.route-list li:nth-child(n+6) { animation-delay: .88s; }

/* 底部那一行最后出现 */
.route-bottom { animation: routeIn .7s cubic-bezier(.22, .61, .36, 1) .9s both; }

/* 细弧线也慢慢淡入，别一开始就杵在那里 */
.route-lines { animation: routeIn 1.6s ease .1s both; }

@media (prefers-reduced-motion: reduce) {
  .route-left > *, .route-title, .route-panel, .route-orb,
  .route-list li, .route-bottom, .route-lines { animation: none !important; }
}

/* =========================================================
 *  友链页背景动效
 *  · 背景图极慢地推近/横移（Ken Burns，46 秒一个来回）
 *  · 紫调渐变层轻轻"呼吸"
 *  · 两条大弧线以 140s / 180s 反向旋转（几乎察觉不到在动，但画面不死）
 * ======================================================= */
@keyframes routeKenBurns {
  0%   { transform: scale(1.02) translate3d(0, 0, 0); }
  50%  { transform: scale(1.09) translate3d(-1.2%, -1.4%, 0); }
  100% { transform: scale(1.02) translate3d(0, 0, 0); }
}
.route::before { animation: routeKenBurns 46s ease-in-out infinite; }

@keyframes routeGlow {
  0%, 100% { opacity: .9; }
  50%      { opacity: 1; }
}
.route::after { animation: routeGlow 13s ease-in-out infinite; }

@keyframes routeArcSpin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.route-lines i:nth-child(1) { animation: routeArcSpin 140s linear infinite; }
.route-lines i:nth-child(2) { animation: routeArcSpin 180s linear infinite reverse; }

@media (prefers-reduced-motion: reduce) {
  .route::before, .route::after,
  .route-lines i:nth-child(1), .route-lines i:nth-child(2) { animation: none !important; }
}

/* =========================================================
 *  友链页：背景 + 各层跟随鼠标做视差
 *  --mx / --my 由 links.astro 的脚本写入（-0.5 ~ 0.5）
 *  ---------------------------------------------------------
 *  ⚠ 背景图**必须**用 background-position，不能用 transform：
 *    ::before 上已经有 Ken Burns 的 transform 动画，
 *    再声明 transform 会被动画完全覆盖（等于没写）。
 * ======================================================= */
.route::before {
  background-position:
    calc(50% + var(--mx, 0) * 34px)
    calc(42% + var(--my, 0) * 26px);
  transition: background-position .5s cubic-bezier(.22, .61, .36, 1);
}
/* 渐变层反向轻移，和底图错开 → 纵深 */
.route::after {
  background-position:
    calc(0% + var(--mx, 0) * -22px)
    calc(0% + var(--my, 0) * -16px);
  transition: background-position .6s cubic-bezier(.22, .61, .36, 1);
}
/* 左栏跟着走（顺向），列表反向走 —— 一前一后就有深度了 */
.route-left {
  transform: translate3d(calc(var(--mx, 0) * 13px), calc(var(--my, 0) * 11px), 0);
  transition: transform .55s cubic-bezier(.22, .61, .36, 1);
}
.route-list {
  transform: translate3d(calc(var(--mx, 0) * -9px), calc(var(--my, 0) * -7px), 0);
  transition: transform .55s cubic-bezier(.22, .61, .36, 1);
}
/* 细弧线移动幅度最大 —— 最近的一层 */
.route-lines {
  translate: calc(var(--mx, 0) * 26px) calc(var(--my, 0) * 20px);
  transition: translate .7s cubic-bezier(.22, .61, .36, 1);
}

@media (prefers-reduced-motion: reduce) {
  .route::before, .route::after, .route-left, .route-list, .route-lines {
    transition: none !important;
    transform: none !important;
    translate: none !important;
  }
  .route::before { background-position: center 42%; }
}

/* =========================================================
 *  移动端布局（≤767px）—— 只写在这一层里，桌面端完全不受影响
 *  第一条：首页首屏
 * ======================================================= */
@media (max-width: 767px) {
  /* 首屏从 88svh 降到 78svh：
     桌面那套是"整屏首屏"，手机上会变成"一大块首屏 + 下面一片空黑"，
     露出一点下一屏才能提示"可以往下滑"。 */
  .gate, .gate-inner { min-height: 78svh; }

  /* 标题收一点、行距紧一点：手机上 11vw 会让 "IRinsho" 顶到两侧 */
  .gate-title {
    font-size: clamp(30px, 10vw, 44px);
    line-height: 1.06;
    letter-spacing: 0;
  }
  .gate-kicker { font-size: 10px; letter-spacing: .26em; }
  .gate-sub { font-size: 13px; letter-spacing: .1em; }

  /* 内边距跟随屏幕宽度；底部让出 iPhone 的小白条 */
  .gate-inner {
    gap: 8px;
    padding: 0 clamp(16px, 5vw, 24px) calc(22px + env(safe-area-inset-bottom));
  }

  /* 粒子在窄屏上少几颗，避免糊在脸上（后 4 颗隐藏） */
  .gate-particles i:nth-child(n + 7) { display: none; }

  /* 滚动提示别贴着底边 */
  .gate-scroll { margin-top: 10px; }
}

.md-table-wrap { overflow-x: auto; margin: 22px 0; border: 1px solid var(--line); border-radius: var(--radius); }

/* =========================================================
 *  笔记正文扩展：数学公式 / Mermaid / 双链 / Dataview
 * ======================================================= */
/* 数学公式（KaTeX 渲染前先把原样 TeX 藏起来，避免闪烁） */
.md-math, .md-math-block { font-family: 'Latin Modern Math', Cambria, 'Times New Roman', serif; }
.md-math-block {
  margin: 22px 0; padding: 16px 12px; overflow-x: auto;
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  text-align: center; color: var(--ink-2); font-size: 14px;
}
.md-math-block .katex-display { margin: 0; }
.katex { font-size: 1.05em; }
.katex-display { overflow-x: auto; overflow-y: hidden; padding: 4px 0; }

/* Mermaid 图表 */
.mermaid {
  margin: 22px 0; padding: 14px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  text-align: center; overflow-x: auto;
  font-family: var(--font-mono); font-size: 12px; color: var(--ink-3);
}
.mermaid svg { max-width: 100%; height: auto; }

/* 双链 [[笔记名]] */
.md-wikilink {
  color: var(--rose-2);
  border-bottom: 1px dashed var(--rose-dim);
  padding-bottom: 1px;
}
.md-wikilink:hover { color: var(--rose); border-bottom-color: var(--rose); }
.md-wikilink-missing {
  color: var(--ink-3);
  border-bottom: 1px dotted var(--line);
  cursor: help;
}

/* Dataview 查询结果 */
.md-dataview {
  margin: 22px 0; padding: 14px 16px;
  background: rgba(224, 70, 111, .05);
  border: 1px solid var(--rose-dim);
  border-left: 2px solid var(--rose);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: 14px;
}
.md-dataview ul { margin: 0; padding-left: 20px; }
.md-dataview li { margin: 5px 0; }
.md-dataview a { color: var(--rose-2); }
.md-dataview a:hover { color: var(--rose); }
.md-dataview .md-dv-date {
  margin-left: 8px; font-family: var(--font-mono);
  font-size: 11px; color: var(--ink-3);
}
.md-dataview .md-table-wrap { margin: 0; border: 0; }
.md-dataview-empty { color: var(--ink-3); font-size: 13px; letter-spacing: .06em; }
.markdown table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.markdown th, .markdown td { padding: 10px 15px; border-bottom: 1px solid var(--line-2); text-align: left; }
.markdown thead th {
  background: rgba(255, 255, 255, .04);
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .1em;
  font-weight: 600; white-space: nowrap;
}
.markdown tbody tr:last-child td { border-bottom: none; }
.markdown tbody tr:hover { background: rgba(224, 70, 111, .05); }

/* =========================================================
 *  个人档案（纸质页）
 * ======================================================= */
.me-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr) 96px;
  gap: 46px;
  align-items: center;
  min-height: calc(100vh - var(--nav-h) - 190px);
}

/* 左边大图 */
.me-photo {
  position: relative;
  margin: 0;
  padding: 11px;
  background: #fff;
  border: 1px solid var(--paper-line);
  box-shadow: 0 22px 50px rgba(140, 80, 110, .16);
}
.me-photo img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.me-decor {
  position: absolute;
  left: -6px; bottom: 58px;
  writing-mode: vertical-rl;
  font-family: var(--font-serif);
  font-size: 56px; font-weight: 700; letter-spacing: .1em;
  color: var(--paper-ink);
  text-shadow: 0 0 12px rgba(255, 255, 255, .9), 0 0 4px rgba(255, 255, 255, .95);
  pointer-events: none;
}
.me-photo-cap {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding-top: 10px;
  font-size: 10.5px; letter-spacing: .18em; color: var(--paper-ink-2);
}
.me-photo-cap b { font-family: var(--font-mono); font-weight: 400; }

/* 中间正文 */
.me-body { min-width: 0; }
.me-quote {
  margin: 20px 0 22px;
  font-family: var(--font-serif);
  font-size: clamp(26px, 3.1vw, 44px);
  font-weight: 700; line-height: 1.5; letter-spacing: .01em;
  white-space: pre-line;
  background: linear-gradient(96deg, #7d2a3f 0%, #bf3a5d 52%, #e0466f 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.me-bio {
  margin: 0 0 34px;
  max-width: 46ch;
  color: #4a3a43;
  font-size: 14px; line-height: 2;
}
/* --- 三张轻薄小卡片：浅色描边 + 极淡填充 + 圆角（旧版是「细横线表格行」） ---
   ⚠ 修对比度：这两行的所有文字一律用 --paper-ink(#241a20，约 14:1) 或
   --paper-rose-deep(#a52b52，约 6.2:1)，不再用 --paper-ink-2 —— 它在浅粉底上
   只有约 3.5:1，对 9.5px 的小字是明确的可访问性问题。 */
/* --paper-rose-deep 是本页局部令牌（不改 :root，避免影响别的页面）：
   #a52b52 在浅粉底（#f8ebf0 上下）上约 6.2:1，达到正文级对比度。 */
.me-links { --paper-rose-deep: #a52b52; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
.me-links a {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  background: rgba(255, 255, 255, .62);
  border: 1px solid rgba(200, 57, 95, .17);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 2px rgba(140, 80, 110, .05);
  font-size: 12px; letter-spacing: .16em;
  color: var(--paper-ink);
  transition: .22s;
}
/* 方形图标位：线框 + 圆角 + 图标居中（svg 本身是 <a> 的直接子元素，
   所以只能用 .me-links a svg 选中它，me.astro 里没有包一层的元素） */
.me-links a svg {
  width: 36px; height: 36px; flex: none;
  padding: 10px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  border: 1px solid rgba(200, 57, 95, .3);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .5);
  transition: .22s;
}
.me-links a .ml-arrow {
  margin-left: auto; color: var(--paper-rose-deep);
  transition: transform .25s;
}
.me-links a:hover {
  background: rgba(255, 255, 255, .92);
  border-color: var(--paper-rose);
  box-shadow: 0 10px 22px rgba(140, 80, 110, .16);
}
.me-links a:hover svg {
  background: var(--rose-dim);
  border-color: var(--paper-rose);
  color: var(--paper-rose);
}
.me-links a:hover .ml-arrow { transform: translate(3px, -3px); color: var(--paper-rose); }

.me-friend {
  display: flex; align-items: center; gap: 16px;
  padding: 11px 14px;
  background: rgba(255, 255, 255, .62);
  border: 1px solid rgba(200, 57, 95, .17);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 2px rgba(140, 80, 110, .05);
  transition: .22s;
}
.me-friend .mf-icon {
  width: 36px; height: 36px; flex: none;
  display: grid; place-items: center;
  border: 1px solid rgba(200, 57, 95, .3);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .5);
  color: var(--paper-rose-deep); font-size: 15px;
  transition: .22s;
}
.me-friend .mf-text { display: flex; flex-direction: column; line-height: 1.5; }
.me-friend .mf-text i { font-style: normal; font-size: 9.5px; letter-spacing: .2em; color: var(--paper-ink); }
/* 「进入友链」：正文级对比度（深玫红约 6.2:1），不再是淡淡的水印色 */
.me-friend .mf-text b { font-family: var(--font-serif); font-size: 17px; font-weight: 700; color: var(--paper-rose-deep); }
.me-friend .mf-arrow {
  margin-left: auto; color: var(--paper-rose-deep);
  transition: transform .25s;
}
.me-friend:hover .mf-text b { color: var(--paper-rose); }
.me-friend:hover .mf-arrow { transform: translate(3px, -3px); color: var(--paper-rose); }
.me-friend:hover .mf-icon {
  background: var(--rose-dim);
  border-color: var(--paper-rose);
  color: var(--paper-rose);
}

/* 右侧竖排导航（真实链接） */
.me-sidenav { display: flex; flex-direction: column; gap: 26px; align-items: flex-end; }
.me-sidenav a {
  display: flex; align-items: center; gap: 8px;
  font-size: 10.5px; letter-spacing: .2em;
  color: rgba(36, 26, 32, .42);
  transition: color .2s, transform .2s;
}
.me-sidenav a::after { content: '◇'; font-size: 9px; }
.me-sidenav a.active { color: var(--paper-rose); }
.me-sidenav a.active::after { content: '◆'; }
.me-sidenav a:hover { color: var(--paper-ink); transform: translateX(-3px); }

.me-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  margin-top: 40px; padding-top: 16px;
  border-top: 1px solid var(--paper-line);
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .18em;
  color: var(--paper-ink-2);
}

/* =========================================================
 *  灯箱 / 弹窗 / 提示
 * ======================================================= */
.lightbox {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(6, 8, 18, .95);
  backdrop-filter: blur(10px);
  display: grid; grid-template-columns: 74px minmax(0, 1fr) 74px;
  align-items: center;
  animation: rise .22s ease;
}
.lb-stage { grid-column: 2; margin: 0; display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 22px 0; min-width: 0; }
.lb-prev { grid-column: 1; }
.lb-next { grid-column: 3; }
.lb-stage img { max-width: 100%; max-height: 74vh; object-fit: contain; border: 1px solid rgba(255, 255, 255, .18); border-radius: var(--radius); }
.lb-caption { text-align: center; max-width: 70ch; }
.lb-caption strong { display: block; font-family: var(--font-serif); font-size: 17px; }
.lb-caption span { display: block; color: var(--ink-2); font-size: 12px; margin-top: 4px; letter-spacing: .1em; }
.lb-counter { margin-top: 8px; font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; color: var(--ink-3) !important; }
.lb-btn {
  justify-self: center;
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, .24);
  background: rgba(255, 255, 255, .06);
  color: #fff; font-size: 20px; cursor: pointer; transition: .2s;
}
.lb-btn:hover { background: var(--rose); border-color: var(--rose); }
.lb-close { position: absolute; right: 24px; top: 22px; font-size: 16px; }

.modal { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 24px; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(6, 8, 18, .84); backdrop-filter: blur(8px); }
.modal-panel {
  position: relative;
  width: min(1040px, 100%); max-height: 92vh; overflow: auto;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px rgba(0, 0, 0, .6);
  animation: pop .26s ease;
  overflow: hidden auto;
}
@keyframes pop { from { opacity: 0; transform: scale(.97) translateY(10px); } to { opacity: 1; transform: none; } }
.modal-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding: 16px 20px; border-bottom: 1px solid var(--line);
}
.modal-head h3 { margin: 0; font-family: var(--font-serif); font-size: 18px; }
.modal-head p { margin: 4px 0 0; color: var(--ink-3); font-family: var(--font-mono); font-size: 11px; letter-spacing: .12em; }
.modal-body { background: #000; }
.modal-body video, .modal-body iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
/* ---------- 画面比例自适应 ----------
   弹窗里那块播放区不再写死 16:9 / 9:16，而是跟着视频真实比例走：
     · videos.js 里写了 ratio: '3:4'      → 用它
     · B站接口 / 抓取时拿到的尺寸          → 用它
     · 本地视频                            → 浏览器读完元数据自动算，最准
     · 抖音这种跨域 iframe（读不到尺寸）    → 由 JS 按平台给一个 9 / 16 猜值
   --ratio 由 JS 设成 "宽 / 高" 这样的值。 */
.modal-body.has-ratio {
  /* --ratio 是 CSS 用的比例（"1080 / 1920"），--rn 是数值（0.5625）。
     宽度取「容器宽」和「按高度预算算出的宽」里小的那个，
     这样画面既不会被裁掉，也不会溢出弹窗，还不会留黑边。 */
  aspect-ratio: var(--ratio, 16 / 9);
  width: min(100%, calc(68vh * var(--rn, 1.7778)));
  max-width: 100%;
  height: auto;
  max-height: none;
  margin: 0 auto;
}
/* 比例已知时把弹窗收窄，避免 1040px 里只放一小块画面 */
.modal-panel.narrow { width: min(560px, 100%); }
.modal-body.has-ratio .embed-stage,
.modal-body.has-ratio .embed-stage.is-playing { width: 100%; height: 100%; aspect-ratio: auto; }

/* ---------- 外站视频：先显示封面 + 播放按钮，点了才加载播放器 ----------
   为什么要这样：抖音播放器一加载就先来「一大块黑 + 抖音 logo」，
   B 站 / YouTube 也要缓冲，一打开弹窗就怼脸上很难看。
   点一下再加载，第一眼看到的就是我们自己设计的画面。 */
.embed-stage {
  position: relative;
  display: grid; place-items: center;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #0b0e1c;
  overflow: hidden;
}
.embed-stage > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: .5;
}
/* 没封面时，弹窗里也用生成底图（和卡片同一套配色逻辑） */
.embed-gen {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  padding: 24px;
  background:
    radial-gradient(120% 90% at 20% 12%, rgba(255, 255, 255, .14), transparent 62%),
    linear-gradient(140deg,
      hsl(var(--h) 40% 22%),
      hsl(var(--h) 52% 33%) 58%,
      hsl(calc(var(--h) + 38) 45% 24%));
}
.embed-gen b {
  font-family: var(--font-serif); font-size: 26px; font-weight: 700; font-style: italic;
  color: rgba(255, 255, 255, .9); text-align: center; line-height: 1.45;
  text-shadow: 0 2px 14px rgba(0, 0, 0, .4);
}
.embed-play {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 13px 26px;
  border: 1px solid var(--rose);
  border-radius: var(--radius-pill);
  background: rgba(224, 70, 111, .92);
  color: #fff;
  font-size: 13.5px; letter-spacing: .12em;
  font-family: inherit;
  text-decoration: none;   /* 它是 <a>（去原站看）时也长得像按钮 */
  cursor: pointer;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
  transition: transform .2s, background .2s;
}
.embed-play span { font-size: 11px; }
.embed-play:hover { transform: scale(1.05); background: var(--rose); }
/* 点了播放之后：播放器铺满这个台子（放在上面 .modal-body 规则之后才能盖住它） */
.embed-stage.is-playing { aspect-ratio: auto; width: 100%; height: 100%; background: #000; }
.embed-stage iframe { aspect-ratio: auto; width: 100%; height: 100%; border: 0; display: block; max-height: none; margin: 0; }

/* ---------- 我们自己的播放器外壳 ----------
   官方播放器只占中间「画面」那一格；标题条、脚注、边框、出口都是我们的，
   看起来就是站内自带的播放器，而不是把抖音的页面整个搬进来。 */
.player {
  display: flex; flex-direction: column;
  width: 100%; height: 100%;
  background: #0b0e1c;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.player-bar {
  flex: none;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  background: linear-gradient(180deg, #1c2038, #171a30);
  border-bottom: 1px solid var(--line-2);
  font-size: 12.5px;
}
.player-dot {
  font-style: normal; font-size: 9px;
  color: var(--rose);
}
.player-bar b {
  font-family: var(--font-serif); font-weight: 700; font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.player-tag {
  margin-left: auto; flex: none;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
  color: var(--ink-3);
}
.player-screen { position: relative; flex: 1 1 auto; min-height: 0; background: #000; overflow: hidden; }
.player-screen iframe { width: 100%; height: 100%; border: 0; display: block; }

/* 抖音播放器页最上面有一条「抖音 logo」横条，跨域改不掉 ——
   把 iframe 放大一点、整体上移，把那条裁到可视区外。
   想调裁多少：改这两个 64px（数值要和下面的 height 增量一致）。 */
.player-screen.is-douyin iframe {
  position: absolute;
  left: 0; right: 0;
  top: -64px;
  width: 100%;
  height: calc(100% + 64px);
}
.player-foot {
  flex: none;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 14px;
  background: var(--bg-2);
  border-top: 1px solid var(--line-2);
  font-size: 11.5px; letter-spacing: .06em; color: var(--ink-3);
}
.player-foot a { color: var(--rose); }
.player-foot a:hover { text-decoration: underline; }
/* 视频打不开时的提示（代替一块纯黑屏） */
.play-error {
  margin: 0; padding: 28px 24px 10px;
  color: var(--ink-2); font-size: 13.5px; letter-spacing: .04em;
  word-break: break-all;
}
.play-download {
  display: inline-block; margin: 0 24px 26px;
  padding: 9px 18px;
  border: 1px solid var(--rose); color: var(--rose);
  border-radius: var(--radius-pill);
  font-size: 12.5px; letter-spacing: .1em; transition: .2s;
}
.play-download:hover { background: var(--rose); color: #fff; }
/* 外站播放器（抖音 / B站 / YouTube）下方的一行提示 */
.embed-bar {
  margin: 0; padding: 12px 20px 16px;
  color: var(--ink-3); font-size: 12.5px; letter-spacing: .08em;
}
.embed-bar .play-download { margin: 0 0 0 10px; padding: 5px 12px; font-size: 11.5px; }
.embed-hint { display: block; margin-top: 8px; color: var(--ink-3); font-size: 11.5px; letter-spacing: .04em; }
.modal-desc { margin: 0; padding: 14px 20px 20px; color: var(--ink-2); font-size: 13.5px; }

.toast {
  position: fixed; left: 50%; bottom: 34px; transform: translateX(-50%);
  z-index: 200;
  background: var(--bg-2); color: var(--ink);
  border: 1px solid var(--rose);
  border-radius: var(--radius-pill);
  padding: 10px 22px; font-size: 13px; letter-spacing: .08em;
  box-shadow: 0 16px 40px rgba(0, 0, 0, .5);
  animation: rise .22s ease;
  max-width: 82vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* =========================================================
 *  响应式
 * ======================================================= */
@media (max-width: 1180px) {
  .photo-grid { column-count: 2; }
  .me-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 34px; }
  .me-sidenav { display: none; }
  .blog-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .panels { grid-template-columns: 1fr 1fr; grid-template-rows: auto; }
  .panel-home { grid-column: span 2; grid-row: auto; min-height: 260px; }
  .panel { min-height: 170px; }
  .tabs { gap: 18px; }
  .tab { font-size: 11.5px; letter-spacing: .1em; }
}

@media (max-width: 760px) {
  :root { --nav-h: 58px; }
  /* iOS 横屏 / 放大字体时不要打乱布局 */
  html { -webkit-text-size-adjust: 100%; }
  .topbar-inner { padding: 0 14px; gap: 14px; }
  /* ⚠ 导航收进下拉菜单后，顶栏中间就没有「撑开」的东西了：
     桌面端靠 .tabs 的 margin: 0 auto 把右侧顶开，而这里 .tabs 是 display:none，
     所以得自己把头像 + ☰ 推到右边，否则它们会紧贴着品牌名、右半条顶栏空着。 */
  .topbar-actions { margin-left: auto; }
  .menu-btn { display: grid; }
  .tabs {
    /* top: 100% = 顶栏下沿，再留 6px 间距。
       ⚠ 别写死 top: 64px —— 那是拿 64 去凑 --nav-h(58)，一旦改了 --nav-h，
         菜单就会嵌进顶栏里、把第一项（01 HOME）压到看不见。 */
    position: absolute; left: 12px; right: 12px; top: 100%;
    flex-direction: column; gap: 2px; margin: 0; margin-top: 6px; padding: 10px;
    background: #14162c; border: 1px solid rgba(255, 255, 255, .12);
    border-radius: var(--radius);
    box-shadow: 0 20px 40px rgba(0, 0, 0, .5);
    display: none;
  }
  .tabs.open { display: flex; }
  /* 下拉菜单里的条目做大一点，手指好点 */
  .tab { padding: 13px 12px; font-size: 13px; }
  .tab-no { font-size: 10px; }
  .tab::after { display: none; }
  .tab.active { color: var(--rose); }

  /* 底部留出 iPhone 小横条的空间 */
  main { padding: 22px 15px calc(56px + env(safe-area-inset-bottom)); }
  .footer { padding: 18px 15px calc(28px + env(safe-area-inset-bottom)); }

  .panels { grid-template-columns: 1fr; }
  .panel-home { grid-column: auto; }
  .panel { min-height: 190px; }
  .chapters-count { margin: 0 12px; }
  .chapter::before { margin: 0 12px 0 2px; }
  .photo-grid { column-count: 1; }
  .video-grid { grid-template-columns: 1fr; }
  .blog-grid { grid-template-columns: 1fr; }
  .blog-bar { gap: 14px; }
  /* 手机上搜索框独占一行，旁边挤着分类会很难点 */
  .blog-search { flex: 1 1 100%; }
  .blog-filters { gap: 14px; flex-wrap: wrap; }
  .blog-count { margin-left: auto; }
  .me-grid { grid-template-columns: 1fr; gap: 30px; min-height: 0; }
  .me-decor { font-size: 42px; bottom: 46px; }
  .me-links { grid-template-columns: 1fr; gap: 16px; }
  .markdown h1 { font-size: 25px; }
  .markdown h2 { font-size: 20px; }
  .markdown img { margin: 16px -4px; max-width: calc(100% + 8px); }
  .lightbox { grid-template-columns: 46px minmax(0, 1fr) 46px; }
  .lb-btn { width: 38px; height: 38px; font-size: 17px; }
  .lb-close { right: 14px; top: 12px; }

  /* ---------- 触屏常见尺寸优化 ---------- */
  /* 筛选按钮手指好点 */
  .filter { padding: 9px 16px; font-size: 12px; }
  .chips { gap: 8px; }
  .chip { padding: 4px 10px; font-size: 11.5px; }
  /* 卡片正文别太挤 */
  .vc-body { padding: 14px 15px 16px; }
  .vc-body h3 { font-size: 16.5px; }
  .photo-card figcaption { opacity: 1; transform: none; }
  /* 触屏没有 hover，说明文字直接显示 */
  .photo-card .card-corner { opacity: 1; }

  /* ---------- 视频弹窗：手机上几乎全屏 ---------- */
  .modal { padding: 0; align-items: stretch; }
  .modal-panel {
    width: 100%;
    /* 100vh 在手机上会被地址栏骗到（内容超出被切），dvh 才是真实可视高度 */
    height: 100vh;
    height: 100dvh;
    max-height: 100vh;
    max-height: 100dvh;
    border-radius: 0; border: 0;
    display: flex; flex-direction: column;
    /* ⚠ 不能用 hidden：手机上内容一旦超出（标题+播放器+提示+描述），
       超出部分会被直接裁掉，看起来就是「界面展示不全」。允许纵向滚动。 */
    overflow-x: hidden;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .modal-head { flex: none; padding: 12px 14px; align-items: center; }
  .modal-head h3 { font-size: 16px; }
  .modal-head p { font-size: 10.5px; margin-top: 2px; }
  /* 关闭按钮做大，手指好点 */
  .modal-head .icon-btn { width: 42px; height: 42px; font-size: 15px; }
  /* 播放区吃掉剩下的高度；内容太长时它自己滚 */
  .modal-body {
    flex: 1 1 auto; min-height: 0;
    display: grid; place-items: center;
    overflow: auto;
  }
  .modal-body video, .modal-body iframe { width: 100%; max-height: 100%; }
  /* 比例已知 / 竖屏：手机上弹窗本来就满屏，按屏幕高度预算算贴合宽度 */
  .modal-panel.narrow { width: 100%; }
  .modal-body.has-ratio {
    width: min(100%, calc(70dvh * var(--rn, 1.7778)));
    max-height: none;
    flex: 0 1 auto;
    margin: 0 auto;
  }
  .modal-body.has-ratio .embed-stage { width: 100%; height: 100%; aspect-ratio: auto; }
  /* 不嵌播放器时（只显示封面 + 去原站按钮）：手机上收成一张居中卡片，
     不占满整屏留一大片空黑 */
  .modal-panel.compact {
    height: auto;
    max-height: 92vh;
    width: calc(100% - 28px);
    align-self: center;
    margin: 0 auto;
    border-radius: var(--radius);
  }
  .modal-panel.compact .modal-body { flex: 0 0 auto; }

  .embed-bar { flex: none; padding: 12px 14px 14px; font-size: 12px; }
  .embed-bar .play-download { padding: 7px 14px; margin-left: 8px; }
  /* 手机上竖向空间紧张，那行补充说明先收起来，只留「去原站看」按钮 */
  .embed-hint { display: none; }
  .play-error { padding: 20px 16px 8px; font-size: 13px; }
  .play-download { margin: 0 16px 18px; padding: 10px 18px; }
  .modal-desc { flex: none; padding: 12px 14px calc(18px + env(safe-area-inset-bottom)); font-size: 13px; }
  /* 弹窗打开时别让背景跟着滑 */
  .modal { overscroll-behavior: contain; }
}

/* =========================================================
 *  移动端布局（≤767px）—— 只写在这一层里，桌面端完全不受影响
 *  第二条：视频弹窗（44px 返回条 + 画面按 --rn 居中 + 脚注贴底）
 * ======================================================= */
@media (max-width: 767px) {
  /* 先把「竖向预算」算清楚，下面都用它：
       --m-bar   顶部返回条
       --m-foot  底部脚注区（描述 / 提示）预留
       --m-play  真正留给画面的高度
     画面宽 = --m-play × --rn（--rn 是 JS 写进去的宽高比数值），
     于是竖屏视频刚好顶到脚注上方，横屏视频也不会被压扁。 */
  .modal-panel {
    --m-bar: 44px;
    --m-foot: 104px;
    --m-play: calc(100dvh - var(--m-bar) - var(--m-foot) - env(safe-area-inset-bottom));
  }
  /* 不嵌播放器、只显示封面 + 「去原站看」的紧凑弹窗里，
     脚注区还多一行按钮，画面预算再收一点，免得整屏都要滚动 */
  .modal-panel.compact { --m-foot: 168px; }

  /* ---------- 顶部返回条：固定 44px ---------- */
  /* sticky：画面往下滚时这条不跟着跑，随时都能退出去 */
  .modal-head {
    position: sticky; top: 0; z-index: 3;
    flex: none;
    height: var(--m-bar);
    min-height: var(--m-bar);
    padding: 0 6px 0 14px;
    align-items: center;
    background: var(--bg-2);
    border-bottom: 1px solid var(--line);
  }
  /* 标题和副信息挤在同一行，两边都允许省略号 —— 别把 ✕ 顶出屏幕 */
  .modal-head > div { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
  .modal-head h3 {
    min-width: 0; font-size: 15px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .modal-head p {
    flex: 0 1 auto; min-width: 0; margin: 0;
    font-size: 10px; letter-spacing: .1em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* 没填副信息时整格收掉，否则 flex 的 gap 会白留 8px */
  .modal-head p:empty { display: none; }
  /* 44px 的条里放 40px 的按钮 + 4px 余量，右上角整块都点得到 */
  .modal-head .icon-btn { flex: none; width: 40px; height: 40px; font-size: 15px; }

  /* ---------- 播放区 ---------- */
  /* 760px 那层给 .modal-body 开了自己的滚动条，那会把
     「放不出来？去抖音看」这一条裁在画面框里（框是固定的比例，内容比它长）。
     这里交回给整个弹窗来滚，和桌面端行为一致。 */
  .modal-body { overflow: visible; }

  .modal-body.has-ratio {
    flex: 0 0 auto;
    /* 上下左右全是 auto：flex 列里 auto 外边距会吃掉剩余空间，
       于是画面正好居中在「返回条」和「脚注」之间，而脚注被顶到最底部。 */
    margin: auto;
    width: min(100%, calc(var(--m-play) * var(--rn, 1.7778)));
    max-width: 100%;
    max-height: none;
  }
  /* 画面铺满 body 那一格：比例已经由 --rn 算过一次，这里别再算 16:9 */
  .modal-body.has-ratio > video,
  .modal-body.has-ratio > iframe {
    width: 100%; height: 100%;
    max-height: none;
    aspect-ratio: auto;
    object-fit: contain;
  }

  /* ---------- 脚注贴底 ---------- */
  /* .modal-desc 是弹窗最后一格：上面 body 的 auto 外边距已经把它顶到最底下，
     这里只负责让它长得像一条脚注，并让出 iPhone 的小横条。 */
  .modal-desc {
    flex: none;
    margin: 0;
    padding: 12px 16px calc(16px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--line);
    background: var(--bg-2);
    font-size: 13px;
  }
  /* 嵌播放器时的提示条在 body 里、贴着画面下沿，同样让出安全区 */
  .embed-bar { padding: 12px 16px calc(14px + env(safe-area-inset-bottom)); }
}

/* 很窄的手机（iPhone SE 之类）：再收一点 */
@media (max-width: 380px) {
  .page-title { font-size: 26px; }
  .p-name { font-size: 19px; }
  .panel-home .p-name { font-size: 25px; }
  .thumb-gen b { font-size: 19px; }
  .blog-search { flex: 1 1 100%; }
}

@media print {
  .topbar, .footer, .blog-bar, .post-bar, .lightbox, .modal, .toast, .filters { display: none !important; }
  body { background: #fff; color: #000; }
}

/* =========================================================
 *  ME 页（/me/ 浅粉纸页）动效增强
 *  ---------------------------------------------------------
 *  ⚠ 整块都是**追加**的：上面任何已有规则都没有被改动。
 *  作用域一律带 body[data-view='me'] 前缀 —— data-view 由 Base.astro
 *  按路径算出来，只有 /me/ 会得到 'me'，所以不可能漏到别的页面。
 *  选择器全部逐字核对过 me.astro 里真实存在的类名：
 *    .me-grid > .me-photo / .me-body > (.eyebrow|.me-quote|.me-bio|
 *    .me-links|.me-friend) / .me-sidenav a / .me-foot
 *    .me-links a > .ml-arrow / .me-friend > .mf-arrow / .mf-text
 *
 *  【和全局「滚动浮现」系统的关系：不冲突，也没有叠两层】
 *    Base.astro 的 startReveal() 只观察
 *      .markdown > * / .post-bar / .post / .gallery-item /
 *      .video-card / .panel / .finale / .gate-inner > *
 *    style.css 里 html.reveal 那一组同名选择器也是这几个。
 *    ME 页的元素（.me-*）**一个都不在其中** —— 它们从来没被浮现系统
 *    隐藏过（所以 ME 页此前是"没有任何进场动效"的），这里补上第一层，
 *    不是第二层；两边不会争同一个 opacity/transform。
 *
 *  【fill-mode 只用 backwards，绝不用 forwards/both】
 *    动画结束后元素回到自己声明的样式（transform: none / filter: none），
 *    悬停的 transform 才不会被动画终值永久压制。
 *    （对比：.route-* 那套用 both，所以友链页悬停只能改 padding —— 这里
 *     是故意反着来的，为的是"进场"和"悬停浮起"能共存。）
 *
 *  循环动画：整块只有 .me-friend::after 一个极慢的呼吸光（7.6s），
 *  没有任何大幅无限循环。缓动统一 cubic-bezier(.22, .61, .36, 1)。
 * ========================================================= */

/* ---------- ① 进场编排：照片 → eyebrow → 引文 → 简介 → 社交行 → 友链行
                → 侧栏逐行 → 页脚，错开 80ms 依次淡入上浮 ---------- */
@keyframes meRiseIn {
  from { opacity: 0; transform: translateY(22px) scale(.985); filter: blur(3px); }
  to   { opacity: 1; transform: none; filter: none; }
}

@media (prefers-reduced-motion: no-preference) {
  body[data-view='me'] .me-grid > .me-photo,
  body[data-view='me'] .me-body > *,
  body[data-view='me'] .me-sidenav a,
  body[data-view='me'] .me-foot {
    animation: meRiseIn .76s cubic-bezier(.22, .61, .36, 1) backwards;
  }

  body[data-view='me'] .me-body > .eyebrow   { animation-delay: .09s; }
  body[data-view='me'] .me-body > .me-quote  { animation-delay: .17s; }
  body[data-view='me'] .me-body > .me-bio    { animation-delay: .25s; }
  body[data-view='me'] .me-body > .me-links  { animation-delay: .33s; }
  body[data-view='me'] .me-body > .me-friend { animation-delay: .41s; }
  body[data-view='me'] .me-foot              { animation-delay: .49s; }

  /* 右侧竖排导航：一行一行落下来 */
  body[data-view='me'] .me-sidenav a:nth-child(1)   { animation-delay: .45s; }
  body[data-view='me'] .me-sidenav a:nth-child(2)   { animation-delay: .53s; }
  body[data-view='me'] .me-sidenav a:nth-child(3)   { animation-delay: .61s; }
  body[data-view='me'] .me-sidenav a:nth-child(4)   { animation-delay: .69s; }
  body[data-view='me'] .me-sidenav a:nth-child(n+5) { animation-delay: .77s; }
}

/* ---------- ② 照片：悬停轻轻浮起（-4px + 1.02）＋ 阴影加深 ＋ 一圈极淡描边光 ---------- */
body[data-view='me'] .me-photo {
  transition:
    transform .62s cubic-bezier(.22, .61, .36, 1),
    box-shadow .62s cubic-bezier(.22, .61, .36, 1);
}
body[data-view='me'] .me-photo:hover {
  transform: translateY(-4px) scale(1.02);
  box-shadow:
    0 36px 68px rgba(140, 80, 110, .26),
    0 0 0 1px rgba(200, 57, 95, .16);
}
/* 贴在照片内沿的一圈玫红细光（绝对定位 + pointer-events:none，不参与布局） */
body[data-view='me'] .me-photo::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  border: 1px solid rgba(200, 57, 95, 0);
  transition:
    border-color .62s cubic-bezier(.22, .61, .36, 1),
    box-shadow .62s cubic-bezier(.22, .61, .36, 1);
}
body[data-view='me'] .me-photo:hover::after {
  border-color: rgba(200, 57, 95, .22);
  box-shadow: inset 0 0 36px rgba(224, 70, 111, .07);
}

/* ---------- ③ 三张卡片：悬停微微上浮 ＋ 阴影加深，不再整行右移 ----------
   transform / box-shadow / background / color / border-color 都是**不参与布局**的
   属性，所以浮起来不会挤到旁边那张卡，也不会引发布局抖动。
   位移只有 -3px（旧版是 translateX(10px)"扫"出去），配合新卡片观感更收得住。 */
body[data-view='me'] .me-links a,
body[data-view='me'] .me-friend {
  position: relative;
  transition:
    transform .5s cubic-bezier(.22, .61, .36, 1),
    box-shadow .5s cubic-bezier(.22, .61, .36, 1),
    background-color .5s cubic-bezier(.22, .61, .36, 1),
    color .5s cubic-bezier(.22, .61, .36, 1),
    border-color .5s cubic-bezier(.22, .61, .36, 1);
}
body[data-view='me'] .me-links a:hover,
body[data-view='me'] .me-friend:hover {
  transform: translateY(-3px);
  border-color: var(--paper-rose);
  box-shadow: 0 14px 28px rgba(140, 80, 110, .18);
}

/* 方形图标位也跟着卡片一起亮起来（背景填充 + 描边转玫红） */
body[data-view='me'] .me-links a svg,
body[data-view='me'] .me-friend .mf-icon {
  transition:
    background-color .5s cubic-bezier(.22, .61, .36, 1),
    border-color .5s cubic-bezier(.22, .61, .36, 1),
    color .5s cubic-bezier(.22, .61, .36, 1);
}
body[data-view='me'] .me-links a:hover svg,
body[data-view='me'] .me-friend:hover .mf-icon {
  background: var(--rose-dim);
  border-color: var(--paper-rose);
  color: var(--paper-rose);
}

/* （旧版这里是一条从左侧"扫"出来的玫红细线 ::before。现在三行改成了卡片，
   它们本身已经有描边 + 填充把彼此分开，再多一条侧线视觉上就脏了，所以撤掉；
   图标位的填充变化放在 ③→④ 之间统一升级。） */

/* ---------- ④ 社交 / 友链的 ↗ 箭头：右上位移 4px 并变玫红 ---------- */
body[data-view='me'] .me-links a .ml-arrow,
body[data-view='me'] .me-friend .mf-arrow {
  transition:
    transform .5s cubic-bezier(.22, .61, .36, 1),
    color .5s cubic-bezier(.22, .61, .36, 1);
}
body[data-view='me'] .me-links a:hover .ml-arrow,
body[data-view='me'] .me-friend:hover .mf-arrow {
  transform: translate(4px, -4px);
  color: var(--paper-rose);
}

/* ---------- ⑤ 友链入口：一层极慢的柔和呼吸光（全块唯一的循环动画）----------
   放在 ::after 上而不是 .me-friend 自己身上：
   .me-friend 已经有 ①的进场动画，同一个元素再声明一次 animation 会**覆盖**掉
   进场（animation 是单值属性，不是叠加的），所以呼吸光必须换一个元素承载。
   isolation:isolate 把 z-index:-1 锁在这一格内部 → 光永远在文字之下、又在
   浅粉底之上，不依赖 transform 是否生效。 */
@keyframes meFriendBreathe {
  0%, 100% { opacity: .3; }
  50%      { opacity: 1; }
}
body[data-view='me'] .me-friend { isolation: isolate; }
body[data-view='me'] .me-friend::after {
  content: '';
  position: absolute; inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(120% 170% at 50% 50%,
    rgba(224, 70, 111, .12), rgba(224, 70, 111, 0) 72%);
  opacity: .3;
  animation: meFriendBreathe 7.6s ease-in-out infinite;
}

/* ---------- ⑥ 浅粉底之上再叠一层极淡的径向光晕 ----------
   fixed + z-index:-1：画在 body 那层渐变之上、所有内容之下，
   页面滚动时内容从光晕上滑过（本身就是一层很轻的视差）。
   这里**故意不加**滚动驱动的 keyframes：ME 页只有一屏左右，
   滚动位移几乎看不出来，不值得再引入第二个循环动画。 */
body[data-view='me'] #main::before {
  content: '';
  position: fixed; inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(60% 46% at 16% 10%, rgba(224, 70, 111, .085), rgba(224, 70, 111, 0) 70%),
    radial-gradient(52% 42% at 86% 84%, rgba(200, 57, 95, .07), rgba(200, 57, 95, 0) 72%);
}

/* ---------- 降级：系统开了「减少动态效果」→ 动画/过渡全关、位移归零 ---------- */
@media (prefers-reduced-motion: reduce) {
  body[data-view='me'] .me-grid > .me-photo,
  body[data-view='me'] .me-body > *,
  body[data-view='me'] .me-sidenav a,
  body[data-view='me'] .me-foot,
  body[data-view='me'] .me-friend::after,
  body[data-view='me'] #main::before { animation: none !important; }

  body[data-view='me'] .me-photo,
  body[data-view='me'] .me-photo::after,
  body[data-view='me'] .me-links a,
  body[data-view='me'] .me-links a svg,
  body[data-view='me'] .me-friend,
  body[data-view='me'] .me-friend .mf-icon,
  body[data-view='me'] .me-links a .ml-arrow,
  body[data-view='me'] .me-friend .mf-arrow { transition: none !important; }

  /* 位移归零：卡片不上浮，箭头不右上移（颜色/描边变化仍保留，作为悬停反馈） */
  body[data-view='me'] .me-photo:hover,
  body[data-view='me'] .me-links a:hover,
  body[data-view='me'] .me-friend:hover,
  body[data-view='me'] .me-links a:hover .ml-arrow,
  body[data-view='me'] .me-friend:hover .mf-arrow { transform: none !important; }
}
