/* ==========================================================================
   懒人之家 LAZYhome · 页面级样式（2026-08-30 从 components.css 拆分）
   ------------------------------------------------------------
   按板块/页面拆出去的 section：论坛正文 / 长文阅读 / COC 角色卡 /
   横条播报 / 快捷入口折叠块 / 积分商城 / 门户 hero 与双栏 / 找服 /
   资源中心 / 社区规则 / 个人主页 / 事件卡 / 标签选择器 / me 网格 /
   统一后台 / 空白卡库。
   引入顺序：tokens.css → components.css → 本文件（保持拆分前的层叠顺序，
   见 base.html）。拆分原则：全站组件留 components.css，只认页面的进这里。
   规则不变：只用语义 token 与 --accent，禁止写死色值。
   ========================================================================== */
/* ==========================================================================
   帖子正文（Markdown 渲染结果）
   ========================================================================== */
.post-content { font-size: var(--text-md); line-height: var(--leading-loose); word-wrap: break-word; }
.post-content > *:first-child { margin-top: 0; }
.post-content > *:last-child { margin-bottom: 0; }
.post-content p { margin: 0 0 var(--sp-3); }
.post-content h1, .post-content h2, .post-content h3 { margin: var(--sp-5) 0 var(--sp-2); }
.post-content h1 { font-size: var(--text-xl); }
.post-content h2 { font-size: var(--text-lg); }
.post-content h3 { font-size: var(--text-md); }
.post-content ul, .post-content ol { margin: 0 0 var(--sp-3); padding-left: var(--sp-6); list-style: revert; }
.post-content img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }
.post-content code {
  padding: 2px 5px; border-radius: var(--radius-xs);
  background: var(--bg-subtle); font-family: var(--font-mono); font-size: 0.9em;
}
.post-content pre {
  margin: 0 0 var(--sp-3); padding: var(--sp-4); overflow-x: auto;
  background: var(--bg-subtle); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.post-content pre code { padding: 0; background: none; }
.post-content blockquote {
  margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-4);
  border-left: 3px solid var(--border-strong);
  color: var(--fg-secondary);
}
.post-content table { width: 100%; border-collapse: collapse; margin-bottom: var(--sp-3); font-size: var(--text-sm); }
.post-content th, .post-content td { padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border-subtle); text-align: left; }
.post-content th { background: var(--bg-subtle); }
/* B 站视频嵌入（services/content.py 生成的 iframe）。
   aspect-ratio 撑高度而不写死 px：小屏不会上下留黑边，也省掉外层 padding-hack 包裹层。 */
.post-content .video-embed {
  display: block; width: 100%; aspect-ratio: 16 / 9;
  margin: 0 0 var(--sp-3); border: 0; border-radius: var(--radius-md);
  background: #000;
}
/* 正文图片可点开大图（JS 委托绑定，见 app.js 的 initLightbox） */
.post-content img { cursor: zoom-in; }

/* ==========================================================================
   长文体验：TOC 目录 / 代码高亮配色 / 图片 Lightbox
   ========================================================================== */

/* --- 右侧悬浮目录 ---
   1280px 以下直接隐藏：内容区本身就窄，再挤一栏会压正文。
   目录由 JS 追加到 body 末尾，所以用 fixed 定位而不是跟着内容流。 */
.post-toc { display: none; }
@media (min-width: 1280px) {
  .post-toc {
    display: block; position: fixed; top: 96px; right: var(--sp-4);
    width: 200px; max-height: calc(100vh - 160px); overflow-y: auto;
    padding: var(--sp-3); z-index: 20;
    background: var(--bg-surface); border: 1px solid var(--border);
    border-radius: var(--radius-md); font-size: var(--text-xs);
  }
}
.post-toc__title {
  margin: 0 0 var(--sp-2); padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-subtle);
  font-weight: var(--weight-bold); color: var(--fg-secondary);
}
.post-toc__list { margin: 0; padding: 0; list-style: none; }
.post-toc__item { margin-bottom: 2px; }
.post-toc__item--h2 { padding-left: var(--sp-2); }
.post-toc__item--h3 { padding-left: var(--sp-4); }
.post-toc__list a {
  display: block; padding: 3px var(--sp-2); border-radius: var(--radius-xs);
  color: var(--fg-secondary); text-decoration: none; line-height: 1.5;
  /* 长标题截断，不让目录横向撑开 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.post-toc__list a:hover { background: var(--bg-subtle); color: var(--fg-primary); }
.post-toc__list a.is-active {
  background: var(--accent-weak); color: var(--accent); font-weight: var(--weight-bold);
}

/* --- 代码高亮配色 ---
   刻意**不引入 highlight.js 自带主题**：它们会硬编码 background / padding /
   border-radius，把上面 .post-content pre 的设计令牌盖掉，还要为深浅色各准备
   一套并手动切换。这里只给 token 上色，代码块外壳仍走 --bg-subtle，
   自动跟随主题。 */
.hljs-comment, .hljs-quote { color: var(--fg-tertiary); font-style: italic; }
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-doctag { color: var(--accent); }
.hljs-string, .hljs-regexp, .hljs-addition { color: var(--syntax-string); }
.hljs-number, .hljs-symbol, .hljs-bullet { color: var(--syntax-number); }
.hljs-title, .hljs-section, .hljs-name, .hljs-title\.function\_ { color: var(--syntax-title); }
.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable { color: var(--syntax-attr); }
.hljs-type, .hljs-class .hljs-title, .hljs-built_in { color: var(--syntax-type); }
.hljs-deletion { color: var(--state-error); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: var(--weight-bold); }
/* 语法配色走 --syntax-* token（定义见 tokens.css）。
   之前这里是 `[data-theme='dark'] .hljs-xxx` 一组硬编码覆盖 —— 每加一套深色
   主题都得记得再抄一遍，abyss 就是这么漏掉的。改成 token 后新主题只需在
   tokens.css 里声明自己的 5 个语法色，components.css 一行都不用动。 */

/* --- 图片 Lightbox --- */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4); background: rgba(0, 0, 0, 0.85);
  cursor: zoom-out; animation: lightbox-in 0.15s ease-out;
}
.lightbox__img {
  max-width: 100%; max-height: 100%;
  border-radius: var(--radius-sm); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
}
.lightbox__close {
  position: absolute; top: var(--sp-4); right: var(--sp-4);
  width: 40px; height: 40px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 28px; line-height: 1; cursor: pointer;
  color: #fff; background: rgba(255, 255, 255, 0.15);
  border: 0; border-radius: 50%;
}
.lightbox__close:hover { background: rgba(255, 255, 255, 0.3); }
@keyframes lightbox-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* --- 自动等级徽章（services/level.py 算出来的 Lv.N）---
   与 .title-badge 刻意分开：那个是管理员手动挂的荣誉牌，这个是按行为自动算的。
   两者可以并排出现在同一个名字旁，所以做得比 title-badge 更轻（无背景色块，
   只描边），避免两个实心牌子挤在一起显脏。 */
.level-badge {
  display: inline-block; padding: 0 5px; margin-left: 4px;
  font-size: 10px; line-height: 16px; font-weight: var(--weight-bold);
  font-family: var(--font-mono); vertical-align: middle;
  border: 1px solid currentColor; border-radius: var(--radius-xs);
  color: var(--fg-tertiary); cursor: help; white-space: nowrap;
}
/* 档位配色照抄 MC 材质本身的颜色，玩家一眼就能认出来。
   低档走土/木/石的灰暗调不抢眼，钻石往上才上色 + 给底色，让"升上去"有视觉回报。 */
.level-badge--l0 { color: #8b6914; }              /* 泥土 */
.level-badge--l1 { color: #b5854b; }              /* 橡木 */
.level-badge--l2 { color: #7b7b7b; }              /* 原石 */
.level-badge--l3 { color: #c06a45; }              /* 铜 */
.level-badge--l4 { color: #9ea7ad; }              /* 铁 */
.level-badge--l5 { color: #d4a017; }              /* 金 */
.level-badge--l6 {                                 /* 钻石 */
  color: #2ec4b6;
  border-color: #2ec4b6;
  background: rgba(46, 196, 182, 0.1);
}
.level-badge--l7 {                                 /* 下界合金 */
  color: #6b5876;
  border-color: #6b5876;
  background: rgba(107, 88, 118, 0.12);
}

/* 荣誉衔（黑曜石 / 哭泣的黑曜石）：站长手动颁发，爬经验永远升不进来。
   所以视觉上必须压过满级的下界合金 —— 用深色实心底 + 白字，而不是描边。
   这里不写 Lv.N，直接写材质名，强调"这不是等级而是荣誉"。 */
.level-badge--honor {
  font-family: var(--font-sans);
  letter-spacing: 0.3px;
  border: none;
  color: #fff;
}
.level-badge--obsidian {
  background: linear-gradient(135deg, #1d1030, #3b2058);
}
.level-badge--crying_obsidian {
  background: linear-gradient(135deg, #2b0f4a, #6d28a8);
  /* 哭泣的黑曜石会发微光，用外发光呼应 */
  box-shadow: 0 0 6px rgba(147, 51, 234, 0.55);
}

/* --------------------------------------------------------------------------
   COC 调查员等级徽章：和 .level-badge（MC 材质线）视觉上刻意错开
   同一个用户在个人主页上会同时挂两个牌子，用不同字体+略深底色区分：
   MC 走 mono 字体+描边（"数据感"），COC 走 sans 字体+浅底（"身份感"）。
   -------------------------------------------------------------------------- */
.coc-level-badge {
  display: inline-block; padding: 1px 6px; margin-left: 4px;
  font-size: 10px; line-height: 14px; font-weight: var(--weight-bold);
  font-family: var(--font-sans); vertical-align: middle;
  border-radius: var(--radius-xs);
  letter-spacing: 0.2px; cursor: help; white-space: nowrap;
}
/* 五个语义档，颜色从冷到暖：见习偏中性，传说走橙金，一眼分得出高低。 */
.coc-level-badge--novice   { color: #5a6b7a; background: rgba(90, 107, 122, 0.14); }
.coc-level-badge--regular  { color: #2a7bbd; background: rgba(42, 123, 189, 0.13); }
.coc-level-badge--senior   { color: #1f8a70; background: rgba(31, 138, 112, 0.14); }
.coc-level-badge--elite    { color: #9b4dca; background: rgba(155, 77, 202, 0.14); }
.coc-level-badge--legend   {
  color: #fff;
  background: linear-gradient(135deg, #b8860b, #d4a017);
  box-shadow: 0 0 5px rgba(212, 160, 23, 0.5);
}


/* ==========================================================================
   COC 角色卡（车卡工具）
   依赖 Token：--bg-subtle, --border-subtle, --accent, --fg-*
   响应式：640px 以下属性/技能改单列
   ========================================================================== */

/** 通用表单网格：自适应列数，最小 160px 一列 */
.char-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--sp-4);
}

/** 属性输入格 */
.attr-box { min-width: 0; }

/** 属性只读展示态（详情页用） */
.attr-box--view {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-3);
  background: var(--bg-subtle);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.attr-box__label { font-size: var(--text-xs); color: var(--fg-tertiary); }
.attr-box__value {
  font-size: var(--text-2xl); font-weight: var(--weight-semibold);
  font-family: var(--font-mono); color: var(--accent);
  line-height: 1.1;
}

/** 衍生属性网格：比属性格更紧凑 */
.derived-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: var(--sp-3);
}
.derived-box {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-3) var(--sp-2);
  background: var(--accent-weak);
  border-radius: var(--radius-md);
}
.derived-box span { font-size: var(--text-2xs); color: var(--fg-secondary); }
.derived-box b {
  font-size: var(--text-lg); font-family: var(--font-mono);
  color: var(--accent); line-height: 1.2;
}

/** 技能分组折叠面板 */
.skill-group {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  margin-bottom: var(--sp-3);
  overflow: hidden;
}
.skill-group > summary {
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-subtle);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  cursor: pointer;
  user-select: none;
}
.skill-group > summary:hover { background: var(--bg-hover); }
.skill-group[open] > summary { border-bottom: 1px solid var(--border-subtle); }

/** 技能输入网格 */
.skill-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-3);
  padding: var(--sp-4);
}
.skill-row { display: flex; align-items: center; gap: var(--sp-2); }
.skill-row label {
  flex: 1; min-width: 0;
  font-size: var(--text-sm); color: var(--fg-secondary);
}
.skill-row__base { font-size: var(--text-2xs); color: var(--fg-tertiary); }
.skill-row .field { width: 68px; flex: none; text-align: center; }

/** 技能只读展示（详情页） */
.skill-view-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--sp-2);
}
.skill-view {
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-subtle);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
}
.skill-view b { font-family: var(--font-mono); color: var(--accent); }

/* 手机档（≤560）：车卡表单的格子收窄。基数是 auto-fill + minmax（自适应），
   这档只是强制更保守的列数（2026-08-30 断点统一：640 并入 560）。 */
@media (max-width: 560px) {
  .char-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
  .derived-grid { grid-template-columns: repeat(3, 1fr); }
  .skill-grid { grid-template-columns: 1fr; padding: var(--sp-3); }
  .skill-view-grid { grid-template-columns: repeat(2, 1fr); }
  .attr-box__value { font-size: var(--text-xl); }
}

/** ============ 板块资讯位轮播（Promo carousel） ============ */
.promo-carousel {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-subtle);
}
.promo-carousel__viewport {
  position: relative;
  /* ⚠️ width:100% 不能省 —— 这是 2026-08-14 实报"右边露出一条奶白"的根因。
     只写 aspect-ratio + max-height 而不给 width 时，Chrome 会**反向传递约束**：
     高度被 max-height 压住后，它把宽度也按 16:9 缩回去（480px × 16/9 ≈ 853px，
     和实测的橙色区宽度吻合），于是 viewport 比外框窄一截，右侧露出
     .promo-carousel 自己的 --bg-subtle 底色，而下面的标签条仍是满宽 —— 看着
     就像图没铺满。显式给 100% 后宽度只由栅格列决定，高度才是被裁的那一维。

     保持 16:9 的宽高比，但用 max-height 给个"绝对高度天花板"，防止宽屏上
     16:9 把整个首屏吃掉（主列 ~1300px 时 16:9 高达 ~730px，"MC 论坛"标题
     被顶到折叠线以下 —— 2026-08-14 实测）。
     天花板用 clamp(280px, 48vh, 540px)：
       - 48vh 让 banner 大约占视口一半，"有存在感但不吃掉首屏"的折中；这个数
         是 2026-08-14 三次调整后定的 —— 无上限（约 730px，吃掉整屏）→ 34vh
         偏小 → 42vh → 修完上面的宽度 bug 后再放到 48vh；要再调只动这一个数。
       - 540px 封顶，超大屏也不至于过高；280px 兜底，超矮窗口仍有存在感。
     min-height 180 是更硬的下限（窄屏 aspect-ratio 已经很矮时保证不塌）。
     触到 max-height 后实际渲染比会比 16:9 略扁，图用 background-size:cover +
     center 居中裁切，视觉可接受；要"完整不裁"得换成 3:1/4:1 的横幅素材，另说。 */
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: clamp(280px, 48vh, 540px);
  min-height: 180px;
}
.promo-slide {
  position: absolute; inset: 0;
  display: flex; align-items: flex-end;
  padding: var(--sp-5) var(--sp-6) var(--sp-6);
  text-decoration: none;
  background-color: var(--accent-weak);
  background-size: cover;
  background-position: center;
  color: #fff;
  /* 纯 CSS 淡入淡出：默认全部隐藏，只有 .is-active 显示。
     不再依赖 Alpine 的 x-show —— CDN 挂了也不会出现全部叠着的重影。
     用 visibility 兜住淡出后仍拦截点击的问题。 */
  opacity: 0; visibility: hidden;
  transition: opacity .55s cubic-bezier(.22,.61,.36,1),
              visibility 0s linear .55s;
}
.promo-slide.is-active {
  opacity: 1; visibility: visible;
  transition: opacity .55s cubic-bezier(.22,.61,.36,1), visibility 0s;
}
/* 没有配图时的兜底。⚠️ 原来只有一句 linear-gradient(120deg, var(--accent), var(--accent-weak))：
   右端落到 --accent-weak（几乎是奶白/浅色），于是右半张发白，标题文字和左右箭头
   浮在浅底上、白字压不住 —— 2026-08-14 用户实报"更丑了"就是这个。
   改法（方案 A）：
     1) 底色用两个都够深的档位（--accent-strong → --accent）斜向渐变，整张保持
        饱和暖/冷色调，不再出现发白的半张；
     2) 叠一层由下往上的黑色蒙版，和有图幻灯片（内联 style 里那层 to-top 黑蒙版）
        观感一致，保证左下角标题区白字始终可读。
   这是"没图时也不难看"的保险；正片效果仍以逐条配图为准（见 _promo_carousel.html）。 */
.promo-slide:not([style*="background-image"]) {
  background:
    linear-gradient(to top, rgba(0,0,0,.48), rgba(0,0,0,.12) 55%, rgba(0,0,0,0) 72%),
    linear-gradient(120deg, var(--accent-strong), var(--accent));
}
.promo-slide__body { position: relative; z-index: 1; max-width: 72%; }
.promo-slide__tag { margin-bottom: var(--sp-2); }
.promo-slide__title {
  font-size: clamp(var(--text-lg), 2.2vw, var(--text-2xl));
  font-weight: var(--weight-bold);
  line-height: 1.25; margin: 0;
  text-shadow: 0 2px 6px rgba(0,0,0,.55);
}
.promo-slide__sub {
  font-size: var(--text-sm); margin: var(--sp-2) 0 0;
  opacity: .95; text-shadow: 0 1px 4px rgba(0,0,0,.5);
}

/* 左下角圆点。绝对定位在 viewport 上，避免被 slide 的淡入淡出带走 */
.promo-carousel__dots {
  position: absolute; left: var(--sp-6); bottom: var(--sp-4);
  display: flex; gap: 7px; z-index: 3;
}
.promo-carousel__dot {
  width: 8px; height: 8px; padding: 0; border: none; border-radius: 999px;
  background: rgba(255,255,255,.45); cursor: pointer;
  /* 用弹性缓动，让激活时的拉伸更有呼吸感 */
  transition: background .35s ease, width .45s cubic-bezier(.34,1.56,.64,1), transform .35s ease;
}
.promo-carousel__dot:hover { background: rgba(255,255,255,.75); }
.promo-carousel__dot.is-active { background: #fff; width: 22px; }

/* 右下角翻页按钮组 */
.promo-carousel__nav {
  position: absolute; right: var(--sp-5); bottom: var(--sp-4);
  display: flex; gap: var(--sp-2); z-index: 3;
}
.promo-carousel__nav button {
  width: 30px; height: 30px; border: none; border-radius: 50%;
  background: rgba(0,0,0,.32); color: #fff; font-size: 18px; line-height: 1;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: background .25s ease, transform .25s cubic-bezier(.34,1.56,.64,1);
}
.promo-carousel__nav button:hover { background: rgba(0,0,0,.6); transform: scale(1.08); }
.promo-carousel__nav button:active { transform: scale(.94); }

/* 底部标题 tab 条 —— B 站风格：圆点和标题合并在图内底部一行，
   移除原来的独立 .promo-tabs 条，改成图片内叠加显示当前标题 + 两侧左右按钮。
   原 promo-tabs 仍保留样式但在小屏隐藏。 */
.promo-tabs {
  display: flex; overflow-x: auto;
  background: var(--bg-surface);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  border: 1px solid var(--border-subtle); border-top: none;
  scrollbar-width: none;
}
.promo-tabs::-webkit-scrollbar { display: none; }
.promo-tabs__item {
  position: relative;
  flex: 1 0 auto; min-width: 0;
  padding: var(--sp-2) var(--sp-3);
  border: none; background: transparent;
  font-size: var(--text-xs); color: var(--fg-secondary);
  white-space: nowrap; cursor: pointer;
  border-right: 1px solid var(--border-subtle);
  transition: background .3s ease, color .3s ease;
}
/* 底部滑动指示条（跟随激活项，用 transform:scaleX 做展开动效） */
.promo-tabs__item::after {
  content: ""; position: absolute; left: 12%; right: 12%; bottom: 0; height: 2px;
  background: var(--accent); border-radius: 2px;
  transform: scaleX(0); transform-origin: center;
  transition: transform .35s cubic-bezier(.22,.61,.36,1);
}
.promo-tabs__item:last-child { border-right: none; }
.promo-tabs__item:hover { background: var(--bg-hover); color: var(--fg-primary); }
.promo-tabs__item.is-active {
  background: var(--accent); color: #fff; font-weight: var(--weight-medium);
}
.promo-tabs__item.is-active::after { transform: scaleX(1); background: #fff; }

/* ---- 激活 slide 的内容分层上浮（标题/副标题/tag 依次进场）----
   之前挂在 Alpine 的 .slide-enter 上；现在改由 .promo-slide.is-active 触发，
   纯 CSS，切到哪张哪张的文字重新上浮一次。 */
@keyframes promoRise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.promo-slide.is-active .promo-slide__tag   { animation: promoRise .55s .02s both cubic-bezier(.22,.61,.36,1); }
.promo-slide.is-active .promo-slide__title { animation: promoRise .6s  .08s both cubic-bezier(.22,.61,.36,1); }
.promo-slide.is-active .promo-slide__sub   { animation: promoRise .6s  .16s both cubic-bezier(.22,.61,.36,1); }

/* 减少动效偏好：全部退回瞬时切换，不折腾前庭敏感的用户 */
@media (prefers-reduced-motion: reduce) {
  .promo-slide,
  .promo-carousel__dot, .promo-carousel__nav button,
  .promo-tabs__item, .promo-tabs__item::after { transition: none !important; }
  .promo-slide.is-active .promo-slide__tag,
  .promo-slide.is-active .promo-slide__title,
  .promo-slide.is-active .promo-slide__sub { animation: none !important; }
}

/* 手机档（≤560，2026-08-30 断点统一：640 并入）：B 站移动端风格 —— 去掉底部
   标题 tab 条，改成"图内叠标题 + 底部居中圆点 + 右下角箭头"。
   561~640 的中间宽度保留 tab 条（可横向滚动，scrollbar 已隐藏），
   这是 bilibili 网页端在平板宽度下的同款行为。 */
@media (max-width: 560px) {
  .promo-tabs { display: none; }

  /* 图内文字底部留白，避免和圆点/箭头叠在一起 */
  .promo-slide { padding: var(--sp-4); padding-bottom: calc(var(--sp-4) + 26px); }
  .promo-slide__body { max-width: 72%; }
  .promo-slide__title { font-size: var(--text-md); }
  .promo-slide__sub { display: none; }  /* 小屏只留主标题，副标题省掉防止挤压 */

  /* 圆点挪到底部居中，B 站那种一排小点 */
  .promo-carousel__dots {
    left: 50%; transform: translateX(-50%);
    bottom: var(--sp-3); gap: 6px;
  }
  .promo-carousel__dot { width: 6px; height: 6px; }
  .promo-carousel__dot.is-active { width: 16px; }

  /* 箭头保留（图2 那样在右下角），比桌面略小 */
  .promo-carousel__nav { right: var(--sp-3); bottom: var(--sp-2); gap: var(--sp-1); }
  .promo-carousel__nav button { width: 26px; height: 26px; font-size: 15px; }
}

/* ==========================================================================
   顶部横条：16:9 大播报（左）+ 公告栏（右侧栏）
   ========================================================================== */
/* ⚠️ 轨道定义必须和 .layout-forum 逐字一致（1fr + --sidebar-w + 同一个 gap）。
   原来这里写的是 `4fr 1fr` + `gap: var(--sp-3)`，是**另一套**列系统：右列宽度
   随容器变、间距也更小，于是公告栏比它正下方的「官方 QQ 群」卡片宽出一截，
   两个板块的右边界对不齐（2026-08-14 实报）。
   以后改侧栏宽度只需动 --sidebar-w，两处会一起跟着走；千万别只改一处。 */
.promo-row {
  display: grid;
  grid-template-columns: 1fr var(--sidebar-w);
  gap: clamp(var(--sp-4), 2.4vw, var(--sp-6));
  align-items: stretch;
  margin-bottom: var(--sp-5);
}
/* 轮播原本自带 margin-bottom，放进 grid 后由 .promo-row 统一管留白 */
.promo-row__main .promo-carousel { margin-bottom: 0; }

/* 右列整列：上「快讯 + 热搜」，下「官方 QQ 群」。
   .promo-row 是 align-items:stretch，所以这一列的高度 = 左边播报图的高度，
   加群卡排在最后且 flex:none，自然贴底，下沿和播报图齐平。 */
.promo-row__side {
  display: flex;
  flex-direction: column;
  gap: clamp(var(--sp-3), 1.2vw, var(--sp-4));
  min-width: 0;
}
/* 快讯/热搜卡吃掉加群卡以外的**全部**剩余高度 —— 站长口径"剩下的区域是热搜"。
   min-height:0 是 flex 子项能真正收缩的前提，否则内部 overflow-y:auto 不生效。 */
.promo-row__side > .notice-board { flex: 1 1 auto; min-height: 0; }
.promo-row__qq {
  flex: none;
  /* MC 侧没有快讯/热搜时 QQ 卡是右列唯一子项，margin-top:auto 把它推到底部，
     下沿与左侧播报图对齐（TRPG 侧有 notice-board 占位天然贴底；2026-09-06 补上）。 */
  margin-top: auto;
  /* 紧凑化：减少内边距，使其与左侧播报图下沿对齐。 */
  padding: var(--sp-2) var(--sp-3);
}
.promo-row__qq .card__header { padding: var(--sp-1) 0; }
.promo-row__qq .card__header .card__title { font-size: var(--text-sm); }
.promo-row__qq .card__body { padding: var(--sp-1) 0; gap: var(--sp-2) !important; }
.promo-row__qq .card__body .btn { padding: 2px 10px; font-size: var(--text-xs); }
.promo-row__qq .card__body .text-xs { font-size: var(--text-2xs); }

.notice-board {

  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--bg-surface); overflow: hidden;
}
.notice-board__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
  border-bottom: 1px solid var(--border-subtle);
}
.notice-board__title { font-size: var(--text-sm); font-weight: var(--weight-bold); margin: 0; }
.notice-board__more { font-size: var(--text-xs); color: var(--fg-tertiary); white-space: nowrap; }
.notice-board__list { list-style: none; margin: 0; padding: var(--sp-1) 0; overflow-y: auto; }
.notice-board__item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); text-decoration: none; color: inherit;
  transition: background .15s;
}
.notice-board__item:hover { background: var(--bg-hover); }
.notice-board__dot {
  width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--accent);
}
.notice-board__text {
  flex: 1; min-width: 0; font-size: var(--text-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.notice-board__date { font-size: var(--text-2xs); color: var(--fg-tertiary); flex: none; }

/* 注：原来这里还有 .notice-board__empty（"暂无公告"占位）。改成"两段都空则整块
   aside 不渲染"之后就没有消费者了，按项目惯例删掉，不留死 CSS。 */

/* 热搜段（同一张卡片的下半段，模板里是 .notice-board__hot）。
   flex:1 —— 卡片被 .promo-row__side 拉到"播报图高度减去加群卡"，多出来的高度
   全部归热搜段，标签往下长就是了（站长口径：热搜是放标签的）。
   :not(:first-child) 只用来判"上面确实有快讯"从而补一条分隔线；没有快讯时它
   就是首个子元素，直接从顶部开始 —— 免得模板里再传一个标志位。

   ⚠️ 这里原来是 margin-top:auto（把热搜顶到卡片底部）+ .notice-board 上的
   align-self:start（没有快讯时让卡片缩回内容高度）。两条都随本次改版删掉：
   加群卡已经把右列底部填满，"中间留一条空带"和"卡片缩一半"都不再是好观感；
   而且 align-self 在 flex 纵向列里管的是**横向**对齐，留着会让卡片宽度塌成
   内容宽，是个隐蔽的错。 */
.notice-board__hot {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0;
}
.notice-board__hot:not(:first-child) {
  border-top: 1px solid var(--border-subtle);
}
.hot-terms {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  /* align-content:flex-start 让换行后的标签行**贴顶**堆叠，
     不要被 flex:1 撑开的高度均分成一行行分散开。 */
  align-content: flex-start;
  flex: 1; min-height: 0; overflow-y: auto;
  padding: var(--sp-3);
}

/* 词条复用全站 .tag 体系，这里只压一下字号：侧栏宽度只有 --sidebar-w(240px)，
   默认字号下 4~5 个字的词就要换行，10 个词能堆到六七行。 */
.hot-terms .tag { font-size: var(--text-xs); }

/* 窄屏（≤900px，与 .layout-forum 同断点）：只隐藏**快讯段**，保留热搜段。
   取舍理由：小屏纵向空间金贵，公告是 news 系统的 category=notice，
   用户仍可从 /mc/news?cat=notice 看全部，藏掉不丢信息；而热搜在手机上
   恰恰最有用 —— 省打字，点一下就搜。
   选择器分工：快讯的 head 是 .notice-board 的**直接子元素**，热搜的 head 包在
   .notice-board__hot 里，所以用 `>` 就能只打中快讯那一个，不会误伤热搜标题。
   :has() 那条兜的是"只有快讯、没有热搜"的场合 —— 不加的话小屏上会剩一个
   有边框有底色的空卡片。
   ⚠️ 隐藏而不是"搬到页面最底部"：这块在 .promo-row 里，页面底部在
   .layout-forum 之后，属于不同父级，grid/flex 的 order 管不到跨父级重排。 */
@media (max-width: 900px) {
  .promo-row { grid-template-columns: 1fr; }
  .notice-board > .notice-board__head,
  .notice-board__list { display: none; }
  .notice-board:not(:has(.notice-board__hot)) { display: none; }
  /* 段落已经是唯一内容，不需要再补分隔线 */
  .notice-board__hot:not(:first-child) { border-top: 0; }
  /* 窄屏是纵向堆叠、没有"被播报图撑高"这回事：撤掉 flex:1 与滚动条，
     让热搜按内容自然长高，否则标签多时会在小屏上出现一个内嵌滚动区。 */
  .notice-board__hot { flex: none; }
  .hot-terms { flex: none; overflow-y: visible; }
}



/** ============ 世界通报滚动条（World broadcast bar） ============ */
.worldbar {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--accent-weak);
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--text-sm);
  overflow: hidden;
}
.worldbar__label {
  flex: none; font-weight: var(--weight-medium); color: var(--accent);
  white-space: nowrap;
}
.worldbar__track { flex: 1; min-width: 0; overflow: hidden; }
.worldbar__scroll {
  display: inline-flex; gap: var(--sp-6);
  white-space: nowrap;
  /* 内容重复了两遍，跑一半刚好首尾相接 */
  animation: worldbar-scroll 30s linear infinite;
}
.worldbar:hover .worldbar__scroll { animation-play-state: paused; }
.worldbar__item { color: var(--fg-secondary); }
.worldbar__item b { color: var(--fg-primary); }

@keyframes worldbar-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* 尊重"减少动效"偏好：关掉滚动，改成静态排列 */
@media (prefers-reduced-motion: reduce) {
  .worldbar__scroll { animation: none; }
}

/** ============ 常驻侧栏（COC 书架 / MC 服务器榜） ============ */
/* 参考 mc 百科侧栏：标题竖色条 + tab 切换 + 横排列表（左缩略图+右文字）。
   200px 宽而非和论坛侧栏共用 240px —— 侧栏列表更细碎，窄一格信息密度反而更好。
   ⚠️ 类名原来是 .coc-shelf，2026-08-14 给 MC 也加了侧栏后改成中性的 .shelf ——
   同一套盒子样式两个板块共用，只有内容不同（COC=书架+热门模组，MC=在线服务器+资讯）。
   改名时全仓只有 CSS 两处 + coc/_shelf_sidebar.html 一处引用，已一起改。 */
.layout-shelf {
  display: grid; grid-template-columns: 1fr 200px;
  gap: clamp(var(--sp-4), 2.4vw, var(--sp-6)); align-items: start;
  transition: grid-template-columns var(--dur-slow) var(--ease-in-out),
              gap var(--dur-slow) var(--ease-in-out);
}
.shelf {
  /* 吸顶要让开 sticky 导航栏的高度，否则滚动时侧栏文字会钻到
     半透明 + backdrop-blur 的导航栏底下，两层字叠在一起 */
  position: sticky; top: calc(var(--navbar-h) + var(--sp-3));
  display: flex; flex-direction: column; gap: var(--sp-4);
  /* 内容比视口高时自己滚，不要顶出屏幕外拿不到 */
  max-height: calc(100vh - var(--navbar-h) - var(--sp-6));
  overflow-y: auto;
}
/* 每块区域（我的书架 / 热门模组） */
.shelf-box {
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--sp-3);
}
.shelf-box__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--sp-2);
}
.shelf-box__title {
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  /* mc 百科标题左侧的竖色条：3px 宽 accent 色 */
  padding-left: var(--sp-2);
  border-left: 3px solid var(--accent);
}
.shelf-box__head a { font-size: var(--text-2xs); color: var(--fg-tertiary); text-decoration: none; }
.shelf-box__head a:hover { color: var(--accent); }

/* 快捷入口块：按钮竖排铺满侧栏宽度（侧栏只有 200px，横排放不下三个中文按钮） */
.shelf-nav { display: flex; flex-direction: column; gap: var(--sp-1); }
.shelf-nav .btn { justify-content: flex-start; width: 100%; }

/* ==========================================================================
   快捷入口折叠块（2026-08-15）—— <details>/<summary> 原生开合，无 JS。
   站长定：默认收起（模板不写 open）、挂在「官方 QQ 群」卡片下面。
   ========================================================================== */
.quick-fold {
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  overflow: hidden;               /* 让内部圆角随外框裁切，展开时列表不溢出圆角 */
}
.quick-fold__head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3);
  cursor: pointer; user-select: none;
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  list-style: none;               /* 去掉部分浏览器默认的三角，改用下面的 ::after */
}
/* Safari/旧 WebKit 的三角要单独关 */
.quick-fold__head::-webkit-details-marker { display: none; }
.quick-fold__title {
  padding-left: var(--sp-2);
  border-left: 3px solid var(--accent);   /* 和 .shelf-box__title 一致的竖色条 */
}
.quick-fold__count {
  font-size: var(--text-2xs); color: var(--fg-tertiary);
  background: var(--bg-elev); border-radius: var(--radius-full);
  padding: 0 var(--sp-2); min-width: 18px; text-align: center;
}
/* 右侧折叠箭头：收起朝下、展开朝上，纯 CSS 靠 [open] 翻转 */
.quick-fold__head::after {
  content: "▾"; margin-left: auto;
  color: var(--fg-tertiary);
  transition: transform var(--dur-fast) var(--ease-out);
}
.quick-fold[open] .quick-fold__head::after { transform: rotate(180deg); }
.quick-fold__body {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: 0 var(--sp-3) var(--sp-3);
}
.quick-fold__item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2);
  border-radius: var(--radius-sm);
  color: var(--fg-primary); text-decoration: none;
  font-size: var(--text-sm);
}
.quick-fold__item:hover { background: var(--accent-weak); color: var(--accent); text-decoration: none; }
.quick-fold__icon { font-size: 1.1em; line-height: 1; flex: none; }
.quick-fold__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.quick-fold__edit {
  margin-top: 2px; text-align: right;
  font-size: var(--text-2xs); color: var(--fg-tertiary); text-decoration: none;
}
.quick-fold__edit:hover { color: var(--accent); }


/* 设置页里的入口勾选网格：自适应列数，窄屏自动堆成一列 */
.shelf-pick {
  display: grid; gap: var(--sp-2);
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.shelf-pick__item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-subtle); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm); cursor: pointer;
}
.shelf-pick__item:hover { border-color: var(--accent); }

/* 自动完成下拉：绝对定位盖在输入框下方，z-index 要高过表单卡片 */
.autocomplete-list {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 20;
  max-height: 240px; overflow-y: auto;
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  margin-top: 2px;
}
.autocomplete-item {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; padding: var(--sp-2) var(--sp-3);
  background: none; border: 0; text-align: left; cursor: pointer;
  font-size: var(--text-sm); color: var(--fg-primary);
}
.autocomplete-item:hover { background: var(--bg-raised); }

/* tab 按钮行 */
.shelf-tabs {
  display: flex; gap: 0; margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-subtle);
}
.shelf-tabs button {
  flex: 1; padding: var(--sp-1) var(--sp-2);
  font-size: var(--text-2xs); font-weight: var(--weight-medium);
  background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--fg-tertiary); cursor: pointer; transition: all .15s;
}
.shelf-tabs button.is-active,
.shelf-tabs button:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* 列表区 */
.shelf-list { display: flex; flex-direction: column; gap: var(--sp-2); }
/* 排行榜模式：行里换成 18px 序号（无 36×48 封面），行高矮一半，间距跟着收紧一档 */
.shelf-list--ranked { gap: var(--sp-1); }
.shelf-empty { font-size: var(--text-2xs); color: var(--fg-tertiary); margin: var(--sp-2) 0; }

/* 横排行：左缩略图 + 右文字（mc 百科同）。信息密度高于纯封面网格 */
.shelf-row {
  display: flex; align-items: center; gap: var(--sp-2);
  text-decoration: none; color: inherit;
  padding: 2px 0;
  transition: background .1s;
}
.shelf-row:hover { background: var(--bg-subtle); border-radius: var(--radius-xs); }
.shelf-row__cover {
  width: 36px; height: 48px; flex-shrink: 0;
  border-radius: var(--radius-xs);
  overflow: hidden; display: flex; align-items: center; justify-content: center;
}
.shelf-row__cover img { width: 100%; height: 100%; object-fit: cover; }
.shelf-row__cover b { color: rgba(255,255,255,.85); font-size: var(--text-sm); user-select: none; }
.shelf-row__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.shelf-row__title {
  font-size: var(--text-2xs); line-height: 1.3; font-weight: var(--weight-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shelf-row__meta {
  display: flex; align-items: center; gap: var(--sp-1);
  font-size: 10px; color: var(--fg-tertiary);
}
/* 资讯分类小标（MC 侧栏）。为什么不复用 .tag：那个是 20px 高、--text-2xs 的
   正文级标签，塞进 10px 的 meta 行会把行高顶起来近一倍，五条资讯就多占半屏。
   这里只要"一小块带底色的分类字"，高度跟着 meta 行走。
   也不复用 mc/news.html 里那串写死的 inline style —— 那份是列表页的大号版，
   同一个视觉概念抄成两份 inline style 以后改不动。 */
.shelf-cat {
  flex: none; padding: 0 4px; border-radius: var(--radius-xs);
  background: var(--accent-weak); color: var(--accent);
  font-weight: var(--weight-medium); white-space: nowrap;
}

/* 热门排行序号 */
.shelf-rank {
  width: 18px; height: 18px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: var(--weight-bold);
  border-radius: var(--radius-xs);
  background: var(--bg-subtle); color: var(--fg-tertiary);
}
.shelf-rank.is-top { background: var(--accent); color: #fff; }

/** ============ 运营后台 /console ============ */
.console-page {
  max-width: 1080px; margin: var(--sp-6) auto 0;
  display: flex; flex-direction: column; gap: var(--sp-6);
}
.console-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap;
}
.console-title { font-size: var(--text-xl); font-weight: var(--weight-bold); }
.console-hint { margin-top: var(--sp-1); font-size: var(--text-sm); color: var(--fg-secondary); }
.console-roles { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* 顶部指标卡：数字大字号，进后台第一眼就知道有多少活 */
.stat-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--sp-3);
}
.stat-card {
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
/* 有待处理时用 accent 边框吸引视线，不用 error 红 —— 留着给真正的错误提示 */
.stat-card.is-alert { border-color: var(--accent); }
.stat-card__label { font-size: var(--text-xs); color: var(--fg-tertiary); }
.stat-card__value {
  margin: 2px 0; font-size: 28px; font-weight: var(--weight-bold); line-height: 1.1;
}
.stat-card__hint {
  display: block; font-size: var(--text-xs); color: var(--fg-tertiary);
  text-decoration: none;
}
a.stat-card__hint:hover { color: var(--accent); }

/* 分区标题：竖色条 + 计数徽章，和侧栏 .shelf-box__title 保持同一套视觉语言 */
.console-section { display: flex; flex-direction: column; gap: var(--sp-3); }
.console-section--slim { gap: var(--sp-2); }
.console-section__title {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--text-md); font-weight: var(--weight-semibold);
  padding-left: var(--sp-2); border-left: 3px solid var(--accent);
}
.console-section__count {
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  padding: 1px 8px; border-radius: var(--radius-full);
  background: var(--accent); color: #fff;
}

/* 功能卡片网格：宽屏自动铺满，窄屏自然收成单列 */
.console-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-3);
}
.console-tile {
  display: block; padding: var(--sp-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  color: inherit; text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.console-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
/* 有待处理项：用 inset 阴影画左侧色条而不是加粗 border ——
   改 border 宽度会让卡片内容横向位移 2px，一排卡片文字对不齐 */
.console-tile.is-alert { box-shadow: inset 3px 0 0 var(--accent); }
.console-tile__top {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.console-tile__icon { font-size: 22px; line-height: 1; }
.console-tile__name { flex: 1; font-size: var(--text-md); font-weight: var(--weight-bold); }
.console-tile__desc { font-size: var(--text-xs); color: var(--fg-tertiary); line-height: 1.5; }
.console-badge {
  padding: 1px 8px; border-radius: var(--radius-full);
  background: var(--state-error); color: #fff;
  font-size: var(--text-xs); font-weight: var(--weight-bold);
}

/* 待处理清零时的正向反馈 */
.console-empty {
  padding: var(--sp-6); text-align: center;
  background: var(--bg-subtle); border-radius: var(--radius-md);
  color: var(--fg-secondary); font-size: var(--text-sm);
}
.console-empty__icon { display: block; font-size: 32px; margin-bottom: var(--sp-2); }

/** ============ 调查员星图 /trpg/atlas ============ */
.atlas-page { margin-top: var(--sp-6); }
.atlas-toolbar {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}
/* 视图切换（星图/时间轴/排行），复用侧栏 tab 的视觉语言 */
.atlas-tabs { display: flex; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); overflow: hidden; }
.atlas-tabs button {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--text-sm); background: none; border: none;
  color: var(--fg-secondary); cursor: pointer;
  border-right: 1px solid var(--border-subtle);
}
.atlas-tabs button:last-child { border-right: none; }
.atlas-tabs button.is-active { background: var(--accent-weak); color: var(--accent); font-weight: var(--weight-medium); }

/* 图例：布局定死之后语义全靠这里说明 */
.atlas-legend {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3);
  background: var(--bg-subtle); border-radius: var(--radius-sm);
  font-size: var(--text-xs); color: var(--fg-secondary);
}
.atlas-legend span { display: inline-flex; align-items: center; gap: var(--sp-1); }
.atlas-legend__line { display: inline-block; width: 22px; height: 0; border-top: 2px solid var(--fg-tertiary); }
.atlas-legend__line--dash { border-top-style: dashed; }
.atlas-legend__dot {
  display: inline-block; width: 11px; height: 11px; border-radius: 50%;
  border: 2px solid var(--accent); background: var(--bg-surface);
}

/* 画布 + 详情侧栏。侧栏浮在画布右上角，不挤压画布宽度 */
.atlas-stage { position: relative; }
.atlas-canvas {
  width: 100%; height: 560px;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.atlas-detail {
  position: absolute; top: var(--sp-3); right: var(--sp-3);
  width: 300px; max-height: calc(100% - var(--sp-6));
  overflow-y: auto;
  padding: var(--sp-4);
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.atlas-detail__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-2); margin-bottom: var(--sp-3);
}
.atlas-detail__bio {
  font-size: var(--text-xs); color: var(--fg-secondary);
  margin: 0 0 var(--sp-3); line-height: 1.6;
}
.atlas-detail__label {
  font-size: var(--text-xs); color: var(--fg-tertiary);
  margin: var(--sp-3) 0 var(--sp-1);
  padding-left: var(--sp-2); border-left: 2px solid var(--accent);
}
/** ============ 个人图谱 /u/{name}/atlas ============ */
/* 画布点阵底纹：空白画布给"这里可以摆东西"的暗示。
   用 radial-gradient 而不是贴图 —— 无请求、深浅色主题下自动跟随 --border-subtle */
.atlas-canvas--grid {
  background-image: radial-gradient(var(--border-subtle) 1px, transparent 1px);
  background-size: 22px 22px;
  background-position: -1px -1px;
}

/* 图例胶囊：形状必须和 Cytoscape 里实际渲染的一致，
   否则用户看图例找不到对应节点（圆=用户 方=模组 菱=NPC） */
.atlas-chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px 10px 2px 6px; border-radius: var(--radius-full);
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  font-size: var(--text-2xs); color: var(--fg-secondary);
}
.atlas-chip__mark {
  display: inline-block; width: 10px; height: 10px;
  border: 2px solid currentColor; background: var(--bg-surface);
}
.atlas-chip__mark--circle { border-radius: 50%; }
.atlas-chip__mark--square { border-radius: 2px; }
.atlas-chip__mark--diamond { transform: rotate(45deg); border-radius: 1px; }

/* 编辑抽屉：浮在画布左上，不再挂到画布下方 ——
   编辑时要能同时看到图，挂下面得滚动，等于盲操作 */
.atlas-editor {
  position: absolute; top: var(--sp-3); left: var(--sp-3);
  width: 320px; max-height: calc(100% - var(--sp-6));
  overflow-y: auto; padding: var(--sp-4);
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  animation: atlas-pop var(--dur-fast) var(--ease-out);
}
.atlas-editor__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--sp-3);
  font-size: var(--text-sm); font-weight: var(--weight-bold);
}
.atlas-detail { animation: atlas-pop var(--dur-fast) var(--ease-out); }
@keyframes atlas-pop {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* 空状态：盖在画布中央而不是画布下方一行灰字 */
.atlas-blank {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-2); pointer-events: none;
  color: var(--fg-tertiary); font-size: var(--text-sm); text-align: center;
}
.atlas-blank__icon { font-size: 40px; opacity: 0.5; }

/* 详情面板里的评分：星星比 "4 ★" 直观 */
.atlas-score { color: #e0a63c; letter-spacing: 1px; }
.atlas-score__dim { color: var(--border-subtle); }

/* 移动档（≤900，2026-08-30 断点统一：720 并入）：抽屉和详情面板改成铺满宽度的
   常规块，绝对定位在触屏上会盖死画布。900 正是全站切 tabbar/触控目标的档，
   图谱作为触屏画布页跟着一起走，口径一致。 */
@media (max-width: 900px) {
  .atlas-editor, .atlas-detail {
    position: static; width: auto; max-height: none;
    margin-top: var(--sp-3); box-shadow: none;
  }
}

/** ============ 门户板块入口卡 ============ */
/* 原来 hover 位移写在模板的 onmouseenter/onmouseleave 内联 JS 里 ——
   CSS 能做的事不该用 JS，且内联事件在 CSP 收紧后会被拦。 */
.portal-tile {
  display: block; text-decoration: none; color: inherit;
  transition: transform var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.portal-tile:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
  text-decoration: none;
}
.portal-tile__body { display: flex; align-items: center; gap: var(--sp-4); }
.portal-tile__icon {
  font-size: 2.8rem; line-height: 1; flex-shrink: 0;
  width: 56px; height: 56px; display: flex; align-items: center; justify-content: center;
  background: var(--accent-weak); border-radius: var(--radius-md);
}
.portal-tile__main { flex: 1; min-width: 0; text-align: left; }
.portal-tile__name { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-semibold); }
.portal-tile__count {
  color: var(--fg-tertiary); font-size: var(--text-xs); flex-shrink: 0;
  background: var(--bg-subtle); padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-full);
}

@media (max-width: 900px) {
  /* 手机上详情侧栏改成画布下方的普通块，浮层会盖掉大半张图 */
  .atlas-canvas { height: 380px; }
  .atlas-detail {
    position: static; width: auto; max-height: none;
    margin-top: var(--sp-3); box-shadow: none;
  }
}

/* 窄屏：侧栏折到正文上方横排（和 layout-forum 同思路） */
@media (max-width: 1100px) {
  .layout-shelf { grid-template-columns: 1fr; }
  .shelf { position: static; flex-direction: row; flex-wrap: wrap; gap: var(--sp-3); }
  .shelf-box { flex: 1; min-width: 260px; }
}


/* ==========================================================================
   V5 · 积分商城档位选择器
   radio 藏起来、整块 label 可点：手机上点击区域大得多，也免得对齐原生 radio。
   ========================================================================== */
.shop-tiers {
  display: grid; gap: var(--sp-2);
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.shop-tier {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-3);
  background: var(--bg-surface);
  border: 1.5px solid var(--border-subtle); border-radius: var(--radius-md);
  cursor: pointer; text-align: center;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.shop-tier:hover { border-color: var(--accent); transform: translateY(-1px); }
/* 原生 radio 挪出视觉但保留可聚焦 —— 不能用 display:none，那样键盘就选不到了 */
.shop-tier input[type="radio"] {
  position: absolute; opacity: 0; width: 0; height: 0;
}
.shop-tier:has(input:checked),
.shop-tier.is-active {
  border-color: var(--accent); background: var(--accent-weak);
}
/* 键盘聚焦时给整块加环，因为真正的 radio 已经不可见了 */
.shop-tier:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.shop-tier__time { font-size: var(--text-md); font-weight: var(--weight-semibold); }
.shop-tier__cost { font-size: var(--text-xs); color: var(--fg-secondary); }

/* ==========================================================================
   积分商城商品列表（2026-08-15，商城拆成"列表→详情"两级后新增）
   整卡是一个 <a>：商品条目只有"点进详情"一个动作，包成链接比在卡里再放按钮
   更好点（手机上尤其），也天然带键盘可达与右键新开。
   auto-fill + minmax 让商品从 2 件涨到 N 件都不用改样式。
   ========================================================================== */
.shop-grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.shop-card {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--bg-surface);
  border: 1.5px solid var(--border-subtle); border-radius: var(--radius-lg);
  color: var(--fg-primary); text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.shop-card:hover {
  border-color: var(--accent); transform: translateY(-2px);
  box-shadow: 0 4px 12px var(--accent-weak); text-decoration: none;
}
.shop-card__icon { font-size: 2rem; line-height: 1; flex: none; }
/* min-width:0 是必须的：否则长 tagline 会把卡片撑破而不是换行 */
.shop-card__body { flex: 1; min-width: 0; display: grid; gap: 2px; }
.shop-card__name { font-size: var(--text-md); }
.shop-card__tagline { font-size: var(--text-sm); color: var(--fg-secondary); line-height: 1.6; }
.shop-card__go { flex: none; color: var(--fg-tertiary); font-size: var(--text-lg); }
.shop-card:hover .shop-card__go { color: var(--accent); }


/* ==========================================================================
   V4 · 快捷入口按钮（门户页 card__header 里的邮箱/设置）
   设计意图：比普通 btn--ghost 更醒目，但不抢签到按钮风头。
   有未读时（--alert）升级成实心填充 + 红点角标，提示"这里需要你来点"。
   ========================================================================== */
.quick-entry {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1.5px solid var(--border-strong); border-radius: var(--radius-full);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--fg-primary); text-decoration: none;
  background: var(--bg-surface);
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
  position: relative;
}
.quick-entry:hover {
  border-color: var(--accent); background: var(--accent-weak);
  transform: translateY(-1px); box-shadow: 0 2px 8px var(--accent-weak);
  text-decoration: none;
}
.quick-entry__icon {
  font-size: 1.1em; line-height: 1;
}
/* 未读角标：右上角红色圆点数字 */
.quick-entry__badge {
  position: absolute; top: -6px; right: -6px;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center;
  background: var(--state-error); color: #fff;
  font-size: 11px; font-weight: var(--weight-semibold);
  border-radius: var(--radius-full);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  line-height: 1;
}

   规则：只加、不覆盖已有语义 —— 把这一节整块删掉页面仍应正常显示
   为什么放最后而不是散在各组件里：一次打磨的诉求以后可能推倒重来，
   聚合成一段方便整体升降级，也方便后来人一眼看到"这里是最新一版审美偏好"。
   ========================================================================== */

/* 页面淡入：加载完之后 200ms 完成，避免"闪出来"的生硬感 */
@keyframes lh-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
main { animation: lh-fade-in var(--dur-base) var(--ease-out); }

/* 尊重用户"减少动效"偏好，无障碍底线 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 滚动条：Webkit（Chrome/Edge/Safari）+ Firefox 都要，两条声明缺一不可 */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: var(--radius-full);
  border: 2px solid var(--bg-canvas);
}
*::-webkit-scrollbar-thumb:hover { background: var(--fg-tertiary); }

/* ==========================================================================
   门户 hero（参考「服联」落地页）
   徽条 → 特大斜体标志 → 说明 → 两颗胶囊 CTA，整体居中、留白给足。
   标志的流光/入场动画都写在 .brand-lockup--live 上，MC 概览页的字组不受影响。
   ========================================================================== */
.portal-hero {
  text-align: center;
  margin: 0 0 var(--sp-6);
  padding: clamp(var(--sp-8), 7vw, var(--sp-12)) 0 clamp(var(--sp-7), 5vw, var(--sp-10));
}

/* 标题：斜体 + 极粗 + 紧行距，字号跟着视口走 */
.portal-hero__title {
  display: flex; flex-wrap: wrap;
  align-items: baseline; justify-content: center;
  gap: var(--sp-1) var(--sp-3);
  margin: var(--sp-4) 0 var(--sp-3);
  font-size: clamp(3.8rem, 11vw, 7.5rem); line-height: 1.02;
  font-style: italic; font-weight: 900; letter-spacing: -0.02em;
}
.portal-hero__suffix {
  font-size: clamp(var(--text-2xl), 3.6vw, var(--text-3xl));
  font-style: normal; font-weight: var(--weight-semibold);
  letter-spacing: 0; color: var(--fg-secondary);
}
.portal-hero__sub {
  max-width: 720px; margin: 0 auto;
  color: var(--fg-secondary); font-size: var(--text-lg);
}
/* 小技巧轮播：定高容器 + 单条淡入上浮切换。定高避免文字长短不一导致下方
   按钮跳动；min-height 用两行高度兜底，窄屏一条 tip 换行也不抖。 */
.portal-hero__ticker {
  position: relative;
  min-height: calc(var(--text-lg) * var(--leading-normal) * 2);
  display: flex; align-items: center; justify-content: center;
}
.portal-hero__tip {
  display: block; width: 100%;
  opacity: 0; transform: translateY(6px);
  transition: opacity .5s ease, transform .5s ease;
}
.portal-hero__tip.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .portal-hero__tip { transition: none; transform: none; }
}

.portal-hero__actions {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  justify-content: center; margin-top: var(--sp-6);
}
/* CTA 做成胶囊、比常规按钮高一档 —— 落地页第一屏的点击目标要够大 */
.portal-hero__actions .btn {
  height: 48px; padding-inline: var(--sp-8);
  border-radius: var(--radius-full);
  font-size: var(--text-lg); font-weight: var(--weight-semibold); white-space: nowrap;
}

/* 标志动效：渐变横向流动（background-position）+ 入场上浮。
   background-clip:text 的背景框本来只有一个字宽，先把 background-size 撑到 220%
   才有位移空间，否则动画看不出来。 */
.brand-lockup--live .brand-lockup__main {
  background-size: 220% 100%;
  animation: brandFlow 6s linear infinite;
}
.brand-lockup--live {
  animation: brandRise var(--dur-slow, 320ms) var(--ease-out, ease-out) both;
}
.brand-lockup--live .brand-lockup__tail {
  animation: brandRise var(--dur-slow, 320ms) var(--ease-out, ease-out) 120ms both;
}
/* 悬停时流光加速，给一点"活的"反馈 */
.brand-lockup--live:hover .brand-lockup__main { animation-duration: 1.6s; }
@keyframes brandFlow {
  from { background-position: 0% 50%; }
  to   { background-position: 200% 50%; }
}
@keyframes brandRise {
  from { opacity: 0; transform: translateY(0.18em) skewX(-4deg); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .brand-lockup--live,
  .brand-lockup--live .brand-lockup__main,
  .brand-lockup--live .brand-lockup__tail { animation: none; }
}

@media (max-width: 560px) {
  .portal-hero__title { gap: var(--sp-1); }
  .portal-hero__actions .btn { width: 100%; }
}



/* ==========================================================================
   V11 门户双栏（.portal-layout）
   左主区 = 站点内容（板块入口 / 近期活动 / 规则），
   右竖栏 = 我的状态（三身份 / 积分 / 签到 / 快捷入口）。
   信息层级更清楚，也不用再让个人卡横铺一整行浪费宽度。
   ========================================================================== */
.portal-layout {
  display: grid;
  /* 右栏 300px → 220px：主体是左边的站点内容，个人卡只是状态条，
     不该占掉近三分之一宽度。配合下面 me-card 全部收成单竖列，
     220px 已经够放"积分 1/100"这类最长的键值行。 */
  grid-template-columns: minmax(0, 1fr) 220px;
  gap: clamp(var(--sp-4), 2.4vw, var(--sp-6));
  align-items: start;
  padding: var(--sp-6) 0 0;
  transition: grid-template-columns var(--dur-slow) var(--ease-in-out),
              gap var(--dur-slow) var(--ease-in-out);
}

/* grid 子项归零 min-width，防止内容顶开轨道 */
.portal-layout > * { min-width: 0; }
/* 游客没有右竖栏（me-card 只对登录用户渲染）—— 收成单列，
   否则那条 300px 轨道会空着，主区被无意义地挤窄。 */
.portal-layout--solo { grid-template-columns: minmax(0, 1fr); }
.portal-layout__side { position: sticky; top: calc(var(--navbar-h) + var(--sp-4)); }
/* 主内容区：flex/grid 场景下 min-width:0 防止子内容顶开轨道 */
.portal-layout__main { flex: 1; min-width: 0; }

/* MC / COC 板块入口同一行 */
.portal-boards {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
  transition: gap var(--dur-slow) var(--ease-in-out);
}

/* --- P1 门户工具宫格：工具是产品，第一屏先给工具再给板块 --- */
.portal-tools {
  /* 2026-09-06：门户工具瓦片 4 个（找团/找服玩/全站新闻/车卡），
     列数改 4 列，刚好一行。 */
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.portal-tool {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--sp-1); text-align: center;
  padding: var(--sp-4) var(--sp-2);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  transition: transform var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.portal-tool:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--border-strong);
}
.portal-tool__icon { color: var(--accent); font-size: 22px; line-height: 1; }
.portal-tool__name { font-weight: var(--weight-bold); font-size: var(--text-md); }
.portal-tool__desc {
  font-size: var(--text-2xs); color: var(--fg-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}

/* --- P1 门户聚合区：最新讨论 / 服务器在线 / 近期开团 三列 --- */
.portal-digest {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
}
.portal-digest__list { list-style: none; margin: 0; padding: 0; }
.portal-digest__row {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--text-sm);
  color: var(--fg-primary);
}
.portal-digest__row:last-child { border-bottom: none; }
.portal-digest__row:hover { background: var(--bg-hover); }
.portal-digest__text {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.portal-digest__meta { color: var(--fg-tertiary); font-size: var(--text-xs); flex-shrink: 0; }
.portal-digest__dot {
  width: 8px; height: 8px; border-radius: var(--radius-full);
  background: var(--fg-tertiary); flex-shrink: 0;
}
.portal-digest__dot--on { background: var(--state-success); }

/* 中屏：工具宫格收成两行三列，聚合区叠一列 */
@media (max-width: 900px) {
  .portal-tools { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-2); }
  .portal-digest { grid-template-columns: 1fr; }
}

/* --- 右栏"我的"卡片（仿小说站紧凑风格） --- */
.me-card {
  background: var(--bg-surface); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg); overflow: hidden;
  font-size: var(--text-sm);
}
/* 顶部：头像 + 昵称，可点击去个人主页 */
.me-card__head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  text-decoration: none; color: inherit;
  transition: background var(--dur-fast) var(--ease-out);
}

.me-card__head:hover { background: var(--bg-hover); }
.me-card__head-text { min-width: 0; }
.me-card__name { font-weight: var(--weight-semibold); font-size: var(--text-md); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.me-card__title { font-size: var(--text-xs); color: var(--fg-tertiary); }

/* MC/COC 身份 chip 行 —— 改成竖排。
   横排两个 chip 在 220px 下每个只剩 ~90px，"MC" 标签 + 昵称必然被挤到
   省略号甚至和头像叠在一起（站长截图里就是这个症状）。竖排后每条独占整宽。 */
.me-card__idents {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
}
.me-card__ident {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: var(--bg-subtle); border-radius: var(--radius-sm);
  font-size: var(--text-xs); min-width: 0;
}
.me-card__ident-label { color: var(--fg-tertiary); flex: none; }
.me-card__ident-name {
  font-weight: var(--weight-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 积分键值表：单竖列，一行一项（原来是 2×2 网格，那是 300px 时代的排法）。
   dt/dd 用 space-between 顶两端，读起来就是"项目 —— 数值"的账本行。 */
.me-card__stats {
  display: grid; grid-template-columns: 1fr;
  padding: 0; margin: 0;
}
.me-card__stats > div {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
}
.me-card__stats dt { color: var(--fg-tertiary); font-size: var(--text-xs); font-weight: normal; }

.me-card__stats dd { margin: 0; font-weight: var(--weight-semibold); font-size: var(--text-sm); }
.me-card__stats dd small { font-weight: normal; color: var(--fg-tertiary); }

/* 签到块 */
.me-card__checkin { padding: var(--sp-3); border-bottom: 1px solid var(--border-subtle); text-align: center; }

.me-card__checked { font-size: var(--text-xs); color: var(--fg-tertiary); }
.me-card__hint { display: block; font-size: var(--text-xs); color: var(--fg-tertiary); margin-top: var(--sp-1); }
.me-card__hint--alert { color: var(--accent); text-decoration: none; }
.me-card__hint--alert:hover { text-decoration: underline; }

/* 底部快捷工具栏：三等分 icon+label */
.me-card__tools {
  display: grid; grid-template-columns: repeat(3, 1fr);
}
.me-card__tool {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-3) var(--sp-2);
  font-size: var(--text-2xs); color: var(--fg-secondary);
  text-decoration: none; border-right: 1px solid var(--border-subtle);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.me-card__tool:last-child { border-right: none; }
.me-card__tool:hover { background: var(--bg-hover); color: var(--accent); }
.me-card__tool svg { width: 18px; height: 18px; }
.me-card__tool--alert { color: var(--accent); font-weight: var(--weight-medium); }
.me-card__tool-dot {
  position: absolute; top: var(--sp-1); right: calc(50% - 16px);
  min-width: 14px; height: 14px; line-height: 14px; padding: 0 3px;
  background: var(--state-error); color: #fff; border-radius: 999px;
  font-size: 10px; text-align: center; font-weight: var(--weight-bold);
}

/* 平板及以下：右栏落到主区下面，取消 sticky */
@media (max-width: 900px) {
  .portal-layout { grid-template-columns: 1fr; }
  .portal-layout__side { position: static; }
  /* 落下去后 me-card 占满整宽，竖列就太空了 —— 身份 chip 回到横排、
     积分行拼成四栏，纵向少占几屏。 */
  .me-card__idents { flex-direction: row; }
  .me-card__ident { flex: 1; }
  .me-card__stats { grid-template-columns: repeat(4, 1fr); }
  .me-card__stats > div:nth-child(odd) { border-right: 1px solid var(--border-subtle); }
  .me-card__stats > div:nth-child(even) { border-right: 1px solid var(--border-subtle); }

  .me-card__stats > div:nth-child(4n) { border-right: none; }
}
/* 手机：板块入口叠成一列 */
@media (max-width: 560px) {
  .portal-boards { grid-template-columns: 1fr; gap: var(--sp-3); }
  .portal-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
  .portal-digest { grid-template-columns: 1fr; gap: var(--sp-3); }
  .me-card__stats { grid-template-columns: repeat(2, 1fr); }
  /* 轮播文案窄屏容易占三行，把字号降一档、容器留三行高，防止切换时下方按钮跳动 */
  .portal-hero__ticker {
    font-size: var(--text-base);
    min-height: calc(var(--text-base) * var(--leading-normal) * 3);
    padding-inline: var(--sp-2);
  }
}

/* 按钮抛光：hover 时更细腻的位移 + 阴影，取代原来直接改亮度 */
.btn--primary:hover {
  filter: brightness(1.06);
  box-shadow: 0 4px 12px var(--accent-weak);
  transform: translateY(-1px);
}
.btn--secondary:hover {
  border-color: var(--accent);
  background: var(--bg-hover);
}

/* Tag 微光：hover 时轻微高亮，提示可点击的 tag */
a.tag:hover, button.tag:hover { transform: translateY(-1px); }
.tag { transition: transform var(--dur-fast) var(--ease-out); }

/* 输入框聚焦：把浏览器默认蓝框换成品牌色（focus-visible 已经给了 outline，
   这里补一下 field 内部的柔和高亮，让整体感更连贯） */
.field:focus, .field:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
  outline: none;
}

/* 表格斑马纹：偶数行淡背景，提高长表格可读性
   （table 组件在 §Table 已定义骨架，这里只加视觉增强） */
.table tbody tr:nth-child(even) { background: rgba(0, 0, 0, 0.015); }
/* 深色系主题下反过来用极淡的白：黑上黑的 0.015 alpha 完全看不出条纹。
   涵盖手动选中的 dark / abyss，以及未标 data-theme 时的系统深色偏好（依靠
   :root:not([data-theme]) 无法直接选中偶数行，改用 @media 兜底）。 */
[data-theme="forest"] .table tbody tr:nth-child(even),
[data-theme="abyss"] .table tbody tr:nth-child(even),
[data-theme="ember"] .table tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.02); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .table tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.02); }
}


/* ==========================================================================
   V7 找服着陆页 Hero（.mc-hero）
   MC 找服是核心入口，给它一个大标题 + 主行动按钮的着陆头，
   风格参考站点的"协议牌 + 特大标题 + 黑色主按钮"，居中排布。
   ========================================================================== */
.mc-hero {
  position: relative; isolation: isolate;
  text-align: center;
  padding: var(--sp-6) var(--sp-4) var(--sp-4);
  margin-bottom: var(--sp-6);
}
/* Hero 双层氛围背景（都是 scope 感知的，MC 和 COC 自动换气质）：
     ::before = 柔光层（MC 草绿高亮 / COC 墨青磷光），负责"发光"
     ::after  = 纹理层（MC 像素点阵 / COC 星图噪点），负责"质感"
   纹理上下用 mask 淡出，避免出现生硬的裁切边。两层都 z-index:-1 垫在文字下。 */
.mc-hero::before,
.mc-hero::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
}
.mc-hero::before {
  background: var(--hero-glow);
}
.mc-hero::after {
  background-image: var(--hero-texture);
  background-size: var(--hero-texture-size);
  opacity: .9;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%, #000 62%, transparent);
  mask-image: linear-gradient(180deg, transparent, #000 22%, #000 62%, transparent);
}
/* 标题 + 按钮组同行：左边"标题 + 一句话说明"成一整块，右边按钮组。
   两侧都是两行高的块，垂直居中对齐 —— 视觉重量对称，不会一头沉。
   之前是"eyebrow 徽标 → 大标题 → 副标题 → 全宽按钮组"的四段纵向布局，
   信息密度太低——首屏被 hero 占满，服务器列表要滚一屏才看得到。 */
.mc-hero__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4) var(--sp-6); flex-wrap: wrap;
  margin-bottom: 0;
}
/* 左块可压缩但保底 280px，免得窄屏时标题被按钮挤成竖排单字 */
.mc-hero__lead {
  flex: 1 1 280px; min-width: 0;
  display: flex; flex-direction: column; gap: var(--sp-2);
  align-items: flex-start;
}
.mc-hero__title {
  font-size: clamp(1.9rem, 5.5vw, 3.2rem);
  font-weight: var(--weight-bold);
  font-style: italic;
  line-height: 1.02; letter-spacing: -0.02em;
  margin: 0;
  text-align: left;
}
.mc-hero__title em { color: var(--accent); font-style: inherit; }

/* 品牌字组 LAZYhome：LAZY 主号、home 小一号，用 baseline 对齐拼一块。
   宽屏时是一行内的 "LAZYhome 服务器概览"。 */
.brand-lockup {
  display: inline-flex; align-items: baseline; gap: 1px;
}
/* LAZY 主号用品牌渐变文字（background-clip:text）。
   加 -webkit- 前缀是因为 Safari/旧 Chrome 仍只认带前缀的 background-clip:text。
   fallback：不支持时 color 生效，退化成纯 accent 色，不会变成透明看不见。
   ⚠ 斜体的 Y 尾巴伸出字符 advance width 之外，而 background-clip:text 的
   背景框只按 advance width 走 —— 结果 Y 的右上角落在背景外、被 transparent
   涂掉，看起来像被切了。用 padding 撑大背景框，再用等量负 margin 抵掉，
   这样只有渐变背景变宽、"home" 的排版位置一点不动。 */
.brand-lockup__main {
  color: var(--accent);
  background: var(--accent-gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  padding-inline-end: 0.14em;
  margin-inline-end: -0.14em;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .brand-lockup__main { background: none; -webkit-text-fill-color: currentColor; }
}
.brand-lockup__tail {
  font-size: 0.42em; font-weight: var(--weight-bold);
  color: var(--fg-primary); letter-spacing: 0.01em;
}
/* "服务器概览" 是副标题性质的说明词，压到品牌字组的 0.62em，
   让 LAZY 在视觉上稳稳压住这行字，不再被中文抢主角。 */
.mc-hero__suffix {
  font-size: 0.62em; letter-spacing: 0.01em;
  margin-left: 0.12em;
}
/* 窄屏：home 收到 LAZY 的右下角 —— 字组改竖排、右对齐，
   home negative margin 上提贴住 LAZY 底边，"服务器概览" 自然换到下一行。 */
@media (max-width: 560px) {
  .brand-lockup {
    display: inline-flex; flex-direction: column; align-items: flex-end;
    line-height: 0.92; gap: 0; vertical-align: bottom;
  }
  .brand-lockup__tail { margin-top: -0.12em; }
}
.mc-hero__subtitle {
  max-width: 620px; margin: 0;
  color: var(--fg-secondary); font-size: var(--text-sm);
  line-height: var(--leading-normal);
  text-align: left;
}
/* 右块：两颗胶囊按钮 + "我的提交"独占第二行右对齐。
   这样右边也是两行高，和左边"标题 + 说明"配平，不会左重右轻。 */
.mc-hero__actions {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: flex-end; gap: var(--sp-2);
  flex: 0 1 auto;
}
.mc-hero__actions .btn { border-radius: var(--radius-full); white-space: nowrap; }
.mc-hero__mine {
  flex-basis: 100%; text-align: right;
  font-size: var(--text-sm); color: var(--fg-tertiary);
}

/* 移动档（≤900，2026-08-30 断点统一：720 并入，与 tabbar/触控目标同一档）：
   标题块 + 按钮组竖排，间距收紧，避免中间空出一大片 */
@media (max-width: 900px) {
  .mc-hero { padding: var(--sp-5) var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4); }
  .mc-hero__row { flex-direction: column; align-items: stretch; gap: var(--sp-4); }
  /* ⚠ 必须重置 flex-basis：竖排后主轴变成纵向，桌面端那个 280px 的 basis
     会被当成"高度 280px"，把标题块撑成一大片空白、按钮被顶到下面去 */
  .mc-hero__lead { flex: 0 0 auto; align-items: center; gap: var(--sp-1); }
  .mc-hero__title { text-align: center; }
  .mc-hero__subtitle { text-align: center; }
  /* 按钮铺满整行、纵向堆叠，方便拇指点；"我的提交" 居中收在底部 */
  .mc-hero__actions { flex-direction: column; align-items: stretch; justify-content: center; gap: var(--sp-2); width: 100%; }
  .mc-hero__actions .btn { width: 100%; justify-content: center; }
  .mc-hero__mine { text-align: center; flex-basis: auto; }
}


/* ==========================================================================
   V7 找服筛选栏 + 服务器卡片网格
   原来是行内 flex-wrap + 固定 width:120px 的下拉，窄屏会挤成"一行半"，
   下拉和按钮各自换行位置不可控 → 换成 grid，断点明确。
   ========================================================================== */
.server-filter { margin-bottom: var(--sp-4); }
/* grid 子项：归零 min-width，别让 select 的 intrinsic 宽度顶开 130px 轨道 */
.server-filter__pick { min-width: 0; }
.server-filter__row {
  display: grid;
  grid-template-columns: minmax(200px, 1fr) 130px 130px auto auto;
  gap: var(--sp-3);
  align-items: end;
  transition: grid-template-columns var(--dur-slow) var(--ease-in-out),
              gap var(--dur-slow) var(--ease-in-out);
}
.server-filter__row select.field { width: 100%; }
.server-filter__check {
  display: flex; align-items: center; gap: var(--sp-2);
  /* 和下拉底边对齐（下拉 28px 高 + label 行高） */
  height: 28px; white-space: nowrap;
}
.server-filter__submit { align-self: end; }

/* 移动档（≤900，2026-08-30 断点统一：860 并入）：搜索独占一行，其余四项挤第二行 */
@media (max-width: 900px) {
  .server-filter__row { grid-template-columns: 1fr 1fr auto auto; }
  .server-filter__search { grid-column: 1 / -1; }
}
/* 手机：全部堆叠，筛选按钮拉满宽方便拇指点 */
@media (max-width: 560px) {
  .server-filter__row { grid-template-columns: 1fr 1fr; }
  .server-filter__check { grid-column: 1 / -1; height: auto; }
  .server-filter__submit { grid-column: 1 / -1; width: 100%; }
}

/* ==========================================================================
   标签多选筛选（.tag-filter / .tag-check）
   ==========================================================================
   找团页 / 找服页共用。**零 JS**：就是一堆 checkbox 放在筛选 form 里，
   折叠靠原生 <details>，点「筛选」提交。Alpine 挂了也照样能筛。

   为什么不用 <a href="?tag=N"> 那种 chip：链接必须把当前所有筛选参数都拼进
   href 才不丢状态，多选时还要算"再加一个/去掉一个"的 URL，模板里会变成一坨；
   而且找服页原来就是这么写的，结果 href 里只有 cat= 一个参数，点标签把
   版本/在线勾选全丢了。checkbox 在 form 内天然不存在这个问题。 */
.tag-filter {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border-subtle);
}
.tag-filter__summary {
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--fg-secondary);
  /* list-style 交给默认三角，别自己画 —— 原生的在各浏览器上都对 */
}
.tag-filter__summary b { color: var(--accent); font-weight: var(--weight-semibold); }
.tag-filter__hint {
  margin-left: var(--sp-2);
  font-size: var(--text-2xs);
  font-weight: var(--weight-regular);
  color: var(--fg-tertiary);
}
/* 分组行：组名固定宽度对齐，标签自己换行 */
.tag-filter__group {
  display: grid;
  grid-template-columns: 68px 1fr;
  align-items: start;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.tag-filter__group-name {
  font-size: var(--text-xs);
  color: var(--fg-tertiary);
  /* 和第一行标签的文字基线大致对齐 */
  padding-top: 3px;
}
.tag-filter__items { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* checkbox 伪装成 chip：input 视觉隐藏但仍在无障碍树里、仍可 Tab 聚焦，
   外观全画在紧邻的 span 上。不用 display:none —— 那会让它无法聚焦。 */
.tag-check { position: relative; display: inline-flex; cursor: pointer; }
.tag-check input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.tag-check__box {
  padding: 2px var(--sp-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  color: var(--fg-secondary);
  background: var(--bg-surface);
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.tag-check:hover .tag-check__box { border-color: var(--border-strong); color: var(--fg-primary); }
.tag-check input:checked + .tag-check__box {
  border-color: var(--accent);
  background: var(--accent-weak);
  color: var(--accent);
  font-weight: var(--weight-medium);
}
/* 键盘可见焦点：input 本身是透明的，焦点环得画到 box 上 */
.tag-check input:focus-visible + .tag-check__box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* 窄屏：组名换到标签上面一行，68px 那列在手机上太占宽 */
@media (max-width: 560px) {
  .tag-filter__group { grid-template-columns: 1fr; gap: var(--sp-1); }
  .tag-filter__group-name { padding-top: 0; }
}


.server-grid {
  display: grid;
  /* 显式分档（2026-08-30 断点统一后）：≥1101px 4 列、561~1100px 3 列、≤560px 2 列。
     用固定列数（而不是 auto-fill + minmax）确保最小屏也保证同行两个卡片，
     并把最大列数硬性锁在 4，避免超宽屏铺出七八列扁条。 */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-3);
  transition: gap var(--dur-slow) var(--ease-in-out);
}
@media (max-width: 1100px) {
  .server-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* 卡片瘦身：所有多列档位共用一套紧凑样式，只有列数在变。
   移动档（≤900）整段生效，因为 3 列时也放不下大封面。 */
@media (max-width: 900px) {
  .server-grid > .card > div:first-child { height: 84px; }
  .server-grid > .card > div:first-child > span { font-size: var(--text-sm); }
  .server-grid .card__body { padding: var(--sp-3); gap: var(--sp-1) !important; }
  .server-grid .card__body > p { font-size: var(--text-xs); -webkit-line-clamp: 2; }
  .server-grid .card__body .tag:nth-of-type(n+3) { display: none; }
}
/* 手机档（≤560，2026-08-30 断点统一：720 并入）：两列 + 更紧的卡片。
   561~900 维持 3 列 —— 该区间的设备（小平板/折叠屏/横屏手机）给 3 列
   每卡 ~220px，比原来的 2 列（~340px 大卡）利用率更高。 */
@media (max-width: 560px) {
  .server-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
  .server-grid > .card > div:first-child { height: 72px; }
  .server-grid .card__body { padding: var(--sp-2) !important; }
}


/* ==========================================================================
   V7 响应式补强
   补 tabbar / 导航 / 卡片网格在极窄屏下的溢出问题。
   ========================================================================== */
@media (max-width: 900px) {
  /* tabbar 项目最多 7 个（COC 板块），375px 屏上每格只有 ~53px：
     flex 基础尺寸必须归零，否则文字撑开后整条横向溢出 */
  .tabbar__item { flex: 1 1 0; min-width: 0; }
  .tabbar__item > span:last-child {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* 顶栏在手机上只剩 logo + 门户 + 主题 + 账号；不允许换行挤成两层 */
  .navbar { flex-wrap: nowrap; }
  .navbar__logo { white-space: nowrap; }

  /* section-head 的标题过长时先压标题，别把右侧按钮挤掉 */
  .section-head > h1, .section-head > h2 { min-width: 0; overflow-wrap: anywhere; }
}

@media (max-width: 560px) {
  /* 极窄屏：底栏字号再收一档，7 项也能完整显示 */
  .tabbar__item { font-size: 10px; }
  .tabbar__icon { font-size: 18px; }

  /* 通栏网格统一降到两列保住信息密度（不再降到单列） */
  .server-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* 长 URL / 服务器地址在窄屏会撑破卡片 */
  .card__body { overflow-wrap: anywhere; }
}

/* ==========================================================================
   V8 资源中心（.link-tabs / .link-grid / .link-card）
   资源中心几乎全是"跳去第三方站"的纯链接，所以刻意做轻：
     - 不用 .card（外壳 + header + body 三层 padding 太重）
     - 一张卡就是 图标 + 名字 + 一行说明，高度固定在 ~72px
     - 分类走 Tab 而不是长页堆叠，省掉滚动
   ========================================================================== */
.link-tabs {
  display: flex; gap: var(--sp-2);
  overflow-x: auto; /* 分类多了横向滑，不换行 */
  scrollbar-width: none;
  padding-bottom: var(--sp-1);
  border-bottom: 1px solid var(--border-subtle);
}
.link-tabs::-webkit-scrollbar { display: none; }
.link-tabs__item {
  flex: none;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid transparent; border-radius: var(--radius-full);
  background: none; cursor: pointer;
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--fg-tertiary); white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.link-tabs__item:hover { background: var(--bg-hover); color: var(--fg-primary); }
.link-tabs__item.is-active {
  background: var(--accent); color: var(--fg-on-accent, #fff);
}

.link-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  gap: var(--sp-2);
  transition: gap var(--dur-slow) var(--ease-in-out);
}
.link-card {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  background: var(--bg-surface);
  text-decoration: none; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.link-card:hover {
  border-color: var(--accent); transform: translateY(-1px); text-decoration: none;
}
.link-card__icon {
  flex: none;
  display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  background: var(--bg-subtle); font-size: 18px; line-height: 1;
}
/* min-width:0 是必须的：否则长说明文字会把 flex 项撑破而不是省略 */
.link-card__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.link-card__name {
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.link-card__ext { font-size: var(--text-2xs); color: var(--fg-tertiary); }
.link-card__desc {
  font-size: var(--text-xs); color: var(--fg-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 分类小标题：一条细横线 + 类名 + 条数，够扫读、不喧宾夺主 */
.link-cat {
  display: flex; align-items: baseline; gap: var(--sp-2);
  margin: 0 0 var(--sp-3); padding-bottom: var(--sp-1);
  font-size: var(--text-md); font-weight: var(--weight-medium);
  border-bottom: 1px solid var(--border-subtle);
}
.link-cat__count {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--fg-tertiary); font-weight: normal;
}

@media (max-width: 560px) {
  /* 链接卡窄屏保持两列，密度更高；说明文字省略号已处理不怕挤。
     （2026-08-30 断点统一：删掉了原 ≤380px 的 1 列兜底 —— 2 列卡在 360px
     屏上每卡 ~165px，icon+标题+省略号说明仍然成立，且符合高密度偏好。） */
  .link-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
  .link-card { padding: var(--sp-2) var(--sp-3); }
  .link-card__icon { width: 30px; height: 30px; font-size: 16px; }
  .link-tabs__item { padding: var(--sp-2) var(--sp-3); }
}

/* ==========================================================================
   V9 社区规则页（.rules-head / .rules-grid / .rule-card）
   规则是"读一遍就走"的静态文案，所以走双栏卡片而不是长条列表：
   每张卡是一个主题，标题下面直接列条款，扫读比逐行滚更快。
   ========================================================================== */
.rules-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-6);
}
.rules-head__eyebrow {
  display: block;
  font-family: var(--font-mono); font-size: var(--text-xs);
  font-style: italic; font-weight: var(--weight-bold);
  letter-spacing: .16em; color: var(--fg-primary);
  margin-bottom: var(--sp-2);
}
.rules-head__title {
  font-size: clamp(1.75rem, 5vw, 2.5rem); font-weight: var(--weight-bold);
  font-style: italic; letter-spacing: -0.02em; line-height: 1.1;
  margin: 0 0 var(--sp-2);
}
.rules-head__sub { margin: 0; color: var(--fg-tertiary); font-size: var(--text-sm); }
.rules-head__badge {
  flex: none;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-full);
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: .2em; color: var(--fg-tertiary); white-space: nowrap;
}

.rules-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr));
  gap: var(--sp-4);
  align-items: start;   /* 条数不同的卡不互相拉高 */
}
.rule-card {
  padding: var(--sp-5);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
}
.rule-card__head { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.rule-card__icon {
  flex: none;
  display: grid; place-items: center;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--accent); color: #fff;
}
.rule-card__tag {
  display: block;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: .14em; color: var(--fg-tertiary);
}
.rule-card__title {
  font-size: var(--text-lg); font-weight: var(--weight-bold); font-style: italic;
  margin: 2px 0;
}
.rule-card__count { font-size: var(--text-xs); color: var(--fg-tertiary); }

/* ==========================================================================
   V11 个人主页（.profile-layout / .profile-hero / .profile-stats）
   原来是"三张身份卡横铺一整屏 + 操作散落在顶部和中间"，问题有两个：
     1. 未自定义时 MC/COC 卡只是重复总信息，占 1/3 屏却没信息量
     2. 关系图谱/编辑资料在顶部，送玫瑰/丢鸡蛋在页面中段，操作找不到
   改成：左边看这个人（主身份 banner + 统计 + 发帖），右边全是操作和副身份。
   ========================================================================== */
.profile-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: clamp(var(--sp-4), 2.4vw, var(--sp-6));
  align-items: start;
  padding: var(--sp-6) 0 var(--sp-12);
  transition: grid-template-columns var(--dur-slow) var(--ease-in-out),
              gap var(--dur-slow) var(--ease-in-out);
}
.profile-layout > * { min-width: 0; }
.profile-layout__main { display: flex; flex-direction: column; gap: var(--sp-4); }
.profile-layout__side {
  display: flex; flex-direction: column; gap: var(--sp-3);
  position: sticky; top: calc(var(--navbar-h) + var(--sp-4));
}
/* 右栏的 MC / COC 副身份卡：栏宽只有 280px，.card 默认 sp-5 内边距会把头像+名字挤成两行，
   所以在这张卡上把 header / body 的内边距各收一档，其余外观完全沿用 .card */
.profile-ident-card .card__header { padding: var(--sp-3) var(--sp-4); }
.profile-ident-card .card__body { padding: var(--sp-4); }

/* 主身份 banner：一条主题色渐变盖顶，头像压在渐变下沿 */
.profile-hero {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  overflow: hidden;
}
/* 封面：MC 绿 → COC 墨青的对角渐变，代表"这个人横跨两个板块"。
   叠一层斜向细纹（repeating-linear-gradient）避免大色块显得廉价。 */
.profile-hero__cover {
  position: relative;
  height: 88px;
  background: linear-gradient(120deg, var(--brand-mc), var(--brand-coc));
}
.profile-hero__cover::after {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, .07) 0 2px,
    transparent 2px 8px
  );
}
.profile-hero__info {
  display: flex; align-items: flex-start; gap: var(--sp-4);
  /* 整行落在 banner 下方（padding-top 把内容压到封面之下），只让头像用负 margin
     上提去压 banner —— 之前是整行 margin-top:-38px 上移，徽章多时（站长+两条等级线）
     文字块变高，flex-end 把名字顶端顶进绿色封面里，就是站长报的"名字被盖住"。 */
  padding: var(--sp-3) var(--sp-5) var(--sp-4);
}
.profile-hero__info .avatar--xl {
  flex: none; border: 3px solid var(--bg-surface);
  background: var(--bg-surface);
  /* 2026-08-17 站长实报"绿条幅遮挡头像"：不再用负 margin 把头像压上封面，
     头像完整落在封面下方的白底区，一眼看全。 */
}
.profile-hero__text { min-width: 0; }

.profile-hero__name {
  margin: 0 0 var(--sp-1); font-size: var(--text-2xl);
  font-weight: var(--weight-semibold); line-height: 1.2;
}
.profile-hero__bio {
  margin: var(--sp-2) 0 0; font-size: var(--text-sm);
  color: var(--fg-secondary); line-height: var(--leading-normal);
}

/* 统计条：等分四格，中间用细竖线隔开，比原来四个居中 div 整齐 */
.profile-stats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  overflow: hidden;
}
.profile-stats__item {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--sp-3) var(--sp-2);
  border-right: 1px solid var(--border-subtle);
}
.profile-stats__item:last-child { border-right: none; }
.profile-stats__val { font-size: var(--text-xl); font-weight: var(--weight-semibold); }
.profile-stats__lbl { font-size: var(--text-xs); color: var(--fg-tertiary); }

.profile-actions .btn { justify-content: center; }

@media (max-width: 900px) {
  .profile-layout { grid-template-columns: 1fr; }
  .profile-layout__side { position: static; }
}
@media (max-width: 560px) {
  .profile-hero__cover { height: 68px; }
  .profile-hero__info { padding: var(--sp-3) var(--sp-4) var(--sp-4); gap: var(--sp-3); }
  .profile-hero__info .avatar--xl { width: 60px; height: 60px; }
  .profile-hero__name { font-size: var(--text-xl); }
  /* 四格统计换 2×2，不然每格只剩 80px 数字和标签会挤 */
  .profile-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .profile-stats__item:nth-child(2) { border-right: none; }
  .profile-stats__item:nth-child(-n+2) { border-bottom: 1px solid var(--border-subtle); }
}

.rule-list { list-style: none; margin: 0; padding: 0; }
.rule-list__item {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-2) 0;
  font-size: var(--text-sm); line-height: var(--leading-normal);
  color: var(--fg-secondary);
}
/* 图标是 1em 高、行高 1.6，往下推半行才对得上第一行文字的视觉中线 */
.rule-list__item > .icon { flex: none; margin-top: 0.3em; }

@media (max-width: 560px) {
  .rules-head { flex-direction: column; gap: var(--sp-3); }
  .rule-card { padding: var(--sp-4); }
}

/* ==========================================================================
   V10 门户事件卡 + MC 服务器侧栏
   ========================================================================== */
/* 门户事件列表：竖向堆叠、左边日期右边标题，对齐时间线感 */
.portal-events {
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.portal-event {
  display: flex; gap: var(--sp-4); align-items: flex-start;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  background: var(--bg-surface);
  text-decoration: none; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.portal-event[href]:hover { border-color: var(--accent); transform: translateX(2px); }
.portal-event--today { border-left: 3px solid var(--accent); background: var(--accent-weak); }
.portal-event__date {
  flex: none; width: 52px; text-align: center;
  font-family: var(--font-mono); font-size: var(--text-sm); line-height: 1.2;
  color: var(--fg-tertiary);
}
.portal-event__date b { display: block; font-size: var(--text-md); color: var(--fg-primary); }
.portal-event__body { flex: 1; min-width: 0; }
.portal-event__title {
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  line-height: var(--leading-normal);
}
.portal-event__meta { margin-top: var(--sp-1); display: flex; gap: var(--sp-2); }

/* MC 论坛侧栏服务器列表（仿小说网站目录条：紧凑、可滚动） */
.server-sidebar { max-height: 360px; overflow-y: auto; scrollbar-width: thin; }
.server-sidebar__item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--text-sm);
  border-bottom: 1px solid var(--border-subtle);
  color: inherit; text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out);
}
.server-sidebar__item:last-child { border-bottom: none; }
.server-sidebar__item:hover { background: var(--bg-hover); }
.server-sidebar__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--weight-medium); }
.server-sidebar__ping { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--fg-tertiary); }


/* ==========================================================================
   Toast / flash 一次性提示
   ==========================================================================
   全站写操作走 POST→303，跳转后必须有个东西告诉用户"刚才那步成了"。
   由 base.html 渲染 services/flash.py 入队的消息。

   为什么固定在右上而不是顶部通栏：
     通栏横幅会把页面内容整体往下推，用户视线刚落到的位置又跳一次；
     右上浮层不占文档流，页面不抖。桌面端右上是"通知"的既成语义
     （GitHub / Vercel / Linear 都在这），学习成本为零。
   手机上改成贴底通栏：右上角在单手握持时是最难看清的死角，
   贴底反而在拇指自然视野内。

   为什么不自动消失：
     运维一次操作可能连着弹 2-3 条（批准 + 发信 + 记日志），
     自动消失会让人来不及读完。给个 × 手动关，配 6s 后淡出到半透明
     作为"这条已经旧了"的暗示，但不移除 —— 信息不该被计时器吃掉。 */
.toast-stack {
  position: fixed;
  top: calc(var(--navbar-h) + var(--sp-3));
  right: var(--sp-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  max-width: min(380px, calc(100vw - var(--sp-6)));
  pointer-events: none; /* 容器本身不挡点击，只有 toast 自己接事件 */
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  border-left-width: 3px;
  background: var(--bg-raised);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  color: var(--fg-primary);
  animation: toast-in var(--dur-slow) var(--ease-out) both,
             toast-fade 1s var(--ease-in-out) 6s forwards;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-8px) scale(.97); }
  to   { opacity: 1; transform: none; }
}
/* 只降到 .55 不到 0：淡掉是"这条旧了"的提示，不是删除。 */
@keyframes toast-fade { to { opacity: .55; } }
.toast:hover { opacity: 1 !important; animation-play-state: paused; }

.toast__icon {
  flex-shrink: 0;
  width: 18px; height: 18px;
  border-radius: var(--radius-full);
  display: grid; place-items: center;
  font-size: 11px; font-weight: var(--weight-bold);
  color: var(--fg-inverse);
}
.toast__text { flex: 1; min-width: 0; overflow-wrap: anywhere; line-height: var(--leading-normal); }
.toast__close {
  flex-shrink: 0;
  background: none; border: 0; cursor: pointer;
  color: var(--fg-tertiary);
  font-size: var(--text-lg); line-height: 1;
  padding: 0 2px; border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease-out);
}
.toast__close:hover { color: var(--fg-primary); }
.toast__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.toast--success { border-left-color: var(--state-success); }
.toast--success .toast__icon { background: var(--state-success); }
.toast--error   { border-left-color: var(--state-error); }
.toast--error .toast__icon   { background: var(--state-error); }
.toast--warning { border-left-color: var(--state-warn); }
.toast--warning .toast__icon { background: var(--state-warn); }
.toast--info    { border-left-color: var(--fg-link); }
.toast--info .toast__icon    { background: var(--fg-link); }

/* 手机档（≤560，2026-08-30 断点统一：720 并入）：贴底通栏。
   右上角在单手握持下是视觉死角；561~900（小平板/横屏）保持右上角 ——
   那个宽度大概率双手持机或桌面触屏，不缺可达性。 */
@media (max-width: 560px) {
  .toast-stack {
    top: auto; bottom: var(--sp-3);
    left: var(--sp-3); right: var(--sp-3);
    max-width: none;
  }
  @keyframes toast-in {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
  }
}
@media (prefers-reduced-motion: reduce) {
  /* 位移动画对前庭敏感用户不友好；保留淡入，去掉滑动。
     注意也要去掉那条 6s 后的 fade —— 它是装饰，不承载信息。 */
  .toast { animation: none; }
}

/* ==================== 个人中心 /me 门户网格 ====================
   auto-fill + minmax 让列数随容器宽度自适应：手机 2 列、平板 3 列、桌面 4 列，
   不用写三档 media query。整张卡是 <a>，点击区域最大化（Fitts' law）。 */
.me-hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--sp-3);
}
.me-hub-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-subtle);
  color: inherit;
  text-decoration: none;
  transition: border-color .15s, transform .15s, background .15s;
}
.me-hub-card:hover {
  border-color: var(--accent);
  background: var(--bg-hover);
  transform: translateY(-2px);
}
.me-hub-card__icon { font-size: 26px; line-height: 1; }
.me-hub-card__title {
  font-weight: var(--weight-semibold);
  font-size: var(--text-md);
  display: flex; align-items: center; gap: var(--sp-2);
}
.me-hub-card__badge {
  font-size: var(--text-xs);
  padding: 0 6px; border-radius: 999px;
  background: var(--accent); color: #fff;
}
.me-hub-card__desc { font-size: var(--text-xs); color: var(--fg-tertiary); }


/* ==================== 统一后台布局（.cadmin） ====================
   左侧固定导航 + 右侧内容区。所有管理页共用，从
   admin/_console_layout.html 继承。

   为什么用 grid 而不是 position:fixed 侧栏：
   fixed 侧栏要给 main 补 padding-left，一旦侧栏宽度变了两处得同步改；
   grid 只有一个 grid-template-columns 是真源。 */
.cadmin {
  display: grid;
  grid-template-columns: 216px 1fr;
  gap: var(--sp-5);
  align-items: start;
  margin-top: var(--sp-5);
}
.cadmin > * { min-width: 0; }  /* 防止长表格把 grid 撑破 */

.cadmin__side {
  position: sticky;
  top: var(--sp-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-subtle);
  overflow: hidden;
}
.cadmin__brand {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border-subtle);
  font-weight: var(--weight-bold);
  font-size: var(--text-sm);
}
.cadmin__nav { padding: var(--sp-2); display: flex; flex-direction: column; gap: var(--sp-3); }
.cadmin__group { display: flex; flex-direction: column; gap: 2px; }
.cadmin__group-label {
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--text-2xs);
  color: var(--fg-tertiary);
  text-transform: uppercase;
  letter-spacing: .06em;
}
.cadmin__link {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2);
  border-radius: var(--radius-md);
  color: var(--fg-secondary);
  text-decoration: none;
  font-size: var(--text-sm);
}
.cadmin__link:hover { background: var(--bg-hover); color: var(--fg-primary); }
/* 当前页用左侧色条 + 加粗，不只靠背景色 —— 深色主题下背景差异容易看不出 */
.cadmin__link.is-active {
  background: var(--bg-hover);
  color: var(--accent);
  font-weight: var(--weight-semibold);
  box-shadow: inset 2px 0 0 var(--accent);
}
.cadmin__link-icon { flex: none; width: 18px; text-align: center; }
.cadmin__link-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cadmin__link-badge {
  flex: none;
  min-width: 18px; padding: 0 5px;
  border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: var(--text-2xs); font-weight: var(--weight-bold);
  text-align: center;
}

/* 窄屏：侧栏收到顶部变成横向滚动条，别在手机上吃掉半个屏幕 */
@media (max-width: 900px) {
  .cadmin { grid-template-columns: 1fr; gap: var(--sp-4); }
  .cadmin__side { position: static; }
  .cadmin__nav { flex-direction: row; overflow-x: auto; gap: var(--sp-2); }
  .cadmin__group { flex-direction: row; gap: 2px; }
  .cadmin__group-label { display: none; }  /* 横排下分组标题只会添乱 */
  .cadmin__link { white-space: nowrap; }
  .cadmin__link-text { overflow: visible; }
}


/* ==========================================================================
   空白卡库（/trpg/sheet）—— 传统纸质角色卡观感
   ==========================================================================
   站长要求这一页"和网站车新卡页面不一样，做成传统车卡观感"，所以这里
   刻意不复用 .card / .panel 那套现代卡片样式，改用衬线字体 + 双线边框 +
   点线引导线 + 左侧编号栏来模拟印刷表单。

   两个约束决定了实现方式：
   1. **不写死纸黄底色**。站内有 5 套主题（含 2 套深色），硬编码 #f5e8d3
      在深色主题下会炸。"纸感"全部靠排版语言表达：字体、边框、引导线、
      编号戳，底色仍走 --bg-raised / --bg-subtle，主题切换自动跟随。
   2. **不新增字体依赖**。tokens.css 里只有 --font-mono，没有 --font-serif，
      而站长要求不加前端依赖（不能引 Google Fonts）。所以这里就地写一个
      系统衬线栈 —— 中文优先宋体族，英文交给 Georgia，两边都是系统自带。 */
.sheet-lib {
  /* 局部变量而不是全局 token：只有这一页要衬线，没必要污染 tokens.css */
  --sheet-serif: Georgia, "Times New Roman", "Songti SC", "SimSun", "Noto Serif CJK SC", serif;
  max-width: 860px;
  margin: 0 auto;
  padding: var(--sp-6) 0 var(--sp-10);
  font-family: var(--sheet-serif);
}

/* 表头：双线（下边框 3px 里嵌一条细线）是老式表单的标志性分隔 */
.sheet-lib__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding-bottom: var(--sp-3);
  border-bottom: 3px double var(--border-strong);
}
.sheet-lib__titles { display: grid; gap: 2px; }
.sheet-lib__title-zh {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  /* 中文标题拉开字距，模仿铅字排版 */
  letter-spacing: .14em;
  color: var(--fg-primary);
}
.sheet-lib__title-en {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--fg-tertiary);
}
.sheet-lib__lead {
  margin: var(--sp-4) 0 var(--sp-6);
  font-size: var(--text-sm);
  line-height: 1.85;
  color: var(--fg-secondary);
}
.sheet-lib__lead b { color: var(--fg-primary); font-weight: var(--weight-semibold); }

/* 列表：条目之间不留缝，靠共享边框连成一张"表"，而不是一堆浮起的卡片 */
.sheet-lib__group { margin-bottom: var(--sp-6); }
.sheet-lib__group-title {
  font-size: var(--text-md); letter-spacing: 2px; color: var(--fg-secondary);
  border-bottom: 1px solid var(--border); padding-bottom: var(--sp-1); margin: 0 0 var(--sp-3);
}
.sheet-lib__list {
  display: grid;
  gap: var(--sp-5);
}

/* 单张卡：左编号栏 + 右正文，双线外框。
   grid-template-columns 用固定 64px 而不是 auto —— 所有条目的编号栏要对齐，
   否则 "No.01" 和 "No.100" 会把正文推到不同起点。 */
.sheet-item {
  display: grid;
  grid-template-columns: 64px 1fr;
  border: 1px solid var(--border-strong);
  /* 内层再补一圈细线做"双框"，比 double 更像印刷品的套框 */
  box-shadow: inset 0 0 0 1px var(--bg-raised), inset 0 0 0 2px var(--border-subtle);
  background: var(--bg-raised);
}

/* 编号栏：竖排编号 + 格式戳，用等宽字体强调"表单字段"感 */
.sheet-item__gutter {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-2);
  border-right: 1px solid var(--border-subtle);
  background: var(--bg-subtle);
  font-family: var(--font-mono);
}
.sheet-item__no {
  font-size: var(--text-2xs);
  letter-spacing: .04em;
  color: var(--fg-tertiary);
  white-space: nowrap;
}
/* 格式戳：模仿公文上的方形印章。transform 用 rotate 让它有点手盖的歪斜 */
.sheet-item__stamp {
  display: grid;
  place-items: center;
  min-width: 42px;
  padding: 3px var(--sp-1);
  border: 1px solid currentColor;
  border-radius: var(--radius-xs);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: .06em;
  color: var(--fg-secondary);
  opacity: .75;
  transform: rotate(-4deg);
}

.sheet-item__body { padding: var(--sp-4) var(--sp-5); min-width: 0; }

.sheet-item__titlebar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border-subtle);
}
.sheet-item__name {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  letter-spacing: .04em;
  color: var(--fg-primary);
}
/* 版本用方括号包起来，是表单上标注"（副本）""（存档）"的那种写法 */
.sheet-item__variant {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--fg-tertiary);
}
.sheet-item__variant::before { content: "["; }
.sheet-item__variant::after { content: "]"; }
.sheet-item__rec {
  margin-left: auto;
  padding: 2px var(--sp-2);
  border: 1px solid var(--accent);
  border-radius: var(--radius-full);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  color: var(--accent);
  background: var(--accent-weak);
  white-space: nowrap;
}

/* 元数据：两列点线引导线。dl 里包了一层 div.sheet-row，
   所以直接用 grid 排 dt/dd，不用管 dl 自身的默认排版。 */
.sheet-item__meta {
  margin: var(--sp-3) 0 0;
  display: grid;
  gap: 4px;
}
.sheet-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--text-xs);
}
.sheet-row dt { color: var(--fg-tertiary); white-space: nowrap; }
.sheet-row dd { margin: 0; color: var(--fg-primary); text-align: right; }
/* 中间那一列用底部点线撑成引导线：传统表单"项目 …… 内容"的排版。
   放在伪元素上而不是 border-bottom on dt，是为了让点线宽度随 1fr 自动伸缩。 */
.sheet-row::before {
  content: "";
  border-bottom: 1px dotted var(--border-strong);
  transform: translateY(-3px);
  opacity: .6;
}

.sheet-item__desc {
  margin: var(--sp-3) 0 0;
  font-size: var(--text-sm);
  line-height: 1.75;
  color: var(--fg-secondary);
}

/* 特性标签：方框小票，等宽字体，模仿表单勾选项 */
.sheet-item__features {
  list-style: none;
  margin: var(--sp-3) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.sheet-item__features li {
  padding: 2px var(--sp-2);
  border: 1px dashed var(--border-subtle);
  border-radius: var(--radius-xs);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--fg-secondary);
}
.sheet-item__features li::before { content: "✓ "; color: var(--accent); }

.sheet-item__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border-subtle);
}
.sheet-item__hint {
  font-size: var(--text-2xs);
  color: var(--fg-tertiary);
}
/* 缺档提示：整条置灰 + 警示色左边框，和可用条目一眼区分 */
.sheet-item__unavailable {
  font-size: var(--text-xs);
  color: var(--fg-tertiary);
  padding-left: var(--sp-3);
  border-left: 3px solid var(--state-warn);
  line-height: 1.6;
}
.sheet-item--missing { opacity: .72; }

.sheet-lib__foot {
  margin-top: var(--sp-8);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--text-2xs);
  color: var(--fg-tertiary);
  text-align: center;
}

/* 手机档（≤560，2026-08-30 断点统一：640 并入）：编号栏横过来贴顶，
   正文占满整宽 —— 64px 侧栏在手机上太挤。 */
@media (max-width: 560px) {
  .sheet-item { grid-template-columns: 1fr; }
  .sheet-item__gutter {
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--sp-3);
    border-right: none;
    border-bottom: 1px solid var(--border-subtle);
    padding: var(--sp-2) var(--sp-4);
  }
  .sheet-item__stamp { transform: none; }
  /* 点线引导线在窄屏容易和右列挤成一团，直接收掉中间列 */
  .sheet-row { grid-template-columns: auto 1fr; }
  .sheet-row::before { display: none; }
  .sheet-row dd { text-align: left; }
}

/* ============================================================
   UTILITIES · 工具类层（2026-08-30 从 components.css 迁来）
   ------------------------------------------------------------
   ⚠️ 刻意放在整个样式栈的**最末尾**：工具类要能压制所有单类
   语义类（.btn/.tag/.field-hint/.row…），与被替换前的内联样式
   行为等价（内联本来就是全覆盖）。这是 Tailwind 等 CSS 架构的
   utilities-last 分层惯例。
   仍压不过的只有复合选择器（.server-grid .card__body 这类
   0,2,0 特异性），模板转换脚本对 .btn/.tag/.card__body 上的
   font-size/color/gap 内联保留不转，见内联收敛脚本注释。
   ============================================================ */
/* 间距映射：--sp-N 的类名直写。高频收敛（margin-bottom:var(--sp-3/4) 等），
   低频的（sp-12 页底留白这种一处一用的）不进这表 —— 档位越少越好记。 */
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--sp-1); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.mb-5 { margin-bottom: var(--sp-5); }
.mb-6 { margin-bottom: var(--sp-6); }
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mt-6 { margin-top: var(--sp-6); }
.m-0 { margin: 0; }

/* display / flex / 图标渲染 / flex 布局修正（第二批收敛）。 */
.d-inline { display: inline; }
.d-block { display: block; }
.d-flex { display: flex; }
.flex-1 { flex: 1; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.min-w-0 { min-width: 0; }
.pixelated { image-rendering: pixelated; }
.justify-end { justify-content: flex-end; }
.justify-between { justify-content: space-between; }
/* 保住原文换行（地址 / 日志等用户文本），浏览器默认会折行。 */
.pre-wrap { white-space: pre-wrap; }

/* gap / padding 工具类（第三批收敛）：gap 覆盖 .row(sp-3)/.stack(sp-4)/
   .form-row(sp-2) 等语义类的场景靠 utilities-last 的加载顺序自然压制。 */
.gap-1 { gap: var(--sp-1); }
.gap-2 { gap: var(--sp-2); }
.gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); }
.gap-5 { gap: var(--sp-5); }
.gap-6 { gap: var(--sp-6); }
.p-2 { padding: var(--sp-2); }
.p-3 { padding: var(--sp-3); }
.p-4 { padding: var(--sp-4); }
.pb-2 { padding-bottom: var(--sp-2); }
.pb-3 { padding-bottom: var(--sp-3); }
.pb-4 { padding-bottom: var(--sp-4); }
.pb-5 { padding-bottom: var(--sp-5); }
/* 页底留白：原 30 处 style="padding-bottom:var(--sp-12)" 同值收敛。 */
.pb-12 { padding-bottom: var(--sp-12); }

/* 自动边距 / 文本对齐（第三批收敛）。 */
.ml-auto { margin-left: auto; }
.mr-auto { margin-right: auto; }
.mx-auto { margin-inline: auto; }
.text-center { text-align: center; }

/* 字号 / 颜色工具类：本区块在样式栈最末，能压制所有单类语义类
   （与被替换的内联样式行为等价）。仍压不过复合选择器 ——
   .btn/.tag/.card__body 上的 font-size/color/gap 内联保留不转。 */
.text-2xs { font-size: var(--text-2xs); }
.text-xs { font-size: var(--text-xs); }
.text-sm { font-size: var(--text-sm); }
.text-md { font-size: var(--text-md); }
.text-lg { font-size: var(--text-lg); }
.text-xl { font-size: var(--text-xl); }
.text-2xl { font-size: var(--text-2xl); }
.text-primary { color: var(--fg-primary); }
.text-tertiary { color: var(--fg-tertiary); }
.text-secondary { color: var(--fg-secondary); }
