/* ==========================================================================
   CloudRidge 云岭 (CDR) — 主样式表
   配色由后台「外观设置」通过 CSS 变量注入（见 Themes::css()）
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

:root {
  /* ---- v1.8.2 云岭蓝：与服务器 logo 同色（藏青 + 青 + 亮绿点缀） ---- */
  --cdr-primary: #1B2F5E;
  --cdr-primary-2: #0A1C46;
  --cdr-accent: #35C4F2;
  --cdr-bg: #F4F8FC;
  --cdr-bg-dark: #080F22;
  --cdr-surface: #FFFFFF;
  --cdr-surface-dark: #132444;
  --cdr-text: #12213F;
  --cdr-text-dark: #E6EEF9;
  --cdr-muted: #5C6E8F;
  --cdr-border: #DCE6F2;
  --cdr-success: #1B2F5E;
  --cdr-warning: #C2820F;
  --cdr-danger: #D0453C;
  --cdr-radius: 10px;
  --cdr-radius-sm: 6px;
  --cdr-radius-pill: 999px;

  /* 阴影只保留一层极淡的：原来那种大范围泛光最容易看出是套的模板 */
  --cdr-shadow: 0 1px 2px rgba(18, 33, 63, .06);
  --cdr-shadow-lg: 0 10px 30px -18px rgba(18, 33, 63, .30);
  --cdr-header-h: 66px;
  /* 标题用系统衬线栈（不引外部字体、不引 CDN） */
  --cdr-serif: "Songti SC", "STSong", "Source Han Serif SC", "Noto Serif SC", "SimSun", Georgia, serif;
}

/* 浅色 */
html[data-color-mode="light"], html:not([data-color-mode]) { color-scheme: light; }

/* 深色（显式或跟随系统） */
html[data-color-mode="dark"] {
  color-scheme: dark;
  --cdr-bg: var(--cdr-bg-dark, #080F22);
  --cdr-surface: var(--cdr-surface-dark, #132444);
  --cdr-text: var(--cdr-text-dark, #E6EEF9);
  --cdr-muted: #93A6C4;
  --cdr-border: rgba(232, 230, 225, .14);
  --cdr-shadow: 0 1px 2px rgba(0, 0, 0, .35);
  --cdr-shadow-lg: 0 14px 36px -22px rgba(0, 0, 0, .7);
}
@media (prefers-color-scheme: dark) {
  html[data-color-mode="auto"] {
    color-scheme: dark;
    --cdr-bg: var(--cdr-bg-dark, #080F22);
    --cdr-surface: var(--cdr-surface-dark, #132444);
    --cdr-text: var(--cdr-text-dark, #E6EEF9);
    --cdr-muted: #93A6C4;
    --cdr-border: rgba(232, 230, 225, .14);
    --cdr-shadow: 0 1px 2px rgba(0, 0, 0, .35);
    --cdr-shadow-lg: 0 14px 36px -22px rgba(0, 0, 0, .7);
  }
}
/* 后台手动覆盖深色（忽略系统） */
html[data-dark-forced="1"] {
  color-scheme: dark;
  --cdr-bg: var(--cdr-bg-dark, #080F22);
  --cdr-surface: var(--cdr-surface-dark, #132444);
  --cdr-text: var(--cdr-text-dark, #E6EEF9);
  --cdr-muted: #93A6C4;
  --cdr-border: rgba(232, 230, 225, .14);
}

html { scroll-behavior: smooth; }

/* 页内锚点：导航栏是吸顶的（实测 66px），不补偏移的话跳过去会正好被压在导航底下。
   只影响「滚到某个带 id 的元素」这一件事，不改变任何元素自己的位置。 */
[id] { scroll-margin-top: 84px; }

body.cdr-body {
  margin: 0;
  font-family: "MiSans", "HarmonyOS Sans SC", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB",
               system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  font-size: 15px;
  line-height: 1.7;
  color: var(--cdr-text);
  background: var(--cdr-bg);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background .25s ease, color .25s ease;
}

img { max-width: 100%; height: auto; display: block; }
a { color: var(--cdr-primary); text-decoration: none; transition: opacity .15s, color .15s; }
a:hover { opacity: .82; }
h1, h2, h3, h4, h5 { margin: 0 0 .6em; line-height: 1.3; font-weight: 700; letter-spacing: -.01em; }
h1 { font-size: clamp(1.9rem, 3.4vw, 2.7rem); }
h2 { font-size: clamp(1.4rem, 2.3vw, 1.9rem); }
h3 { font-size: 1.18rem; }
p { margin: 0 0 1em; }
code, pre, kbd { font-family: "JetBrains Mono", "Cascadia Code", Consolas, "SF Mono", monospace; }
hr { border: none; border-top: 1px solid var(--cdr-border); margin: 24px 0; }

.cdr-container { width: min(1240px, 100% - 40px); margin-inline: auto; }
@media (max-width: 640px) { .cdr-container { width: calc(100% - 28px); } }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--cdr-primary); color: #fff; padding: 10px 16px; border-radius: 0 0 10px 0;
}
.skip-link:focus { left: 0; }

/* ---------------------------------------------------------------- 头部 */
/* ⚠️ v1.9.44 —— 导航条**故意不带 backdrop-filter**（原来有
   `saturate(180%) blur(14px)`）。站长 m11209 第 6 件、前后共五次报「鼠标一动
   就有一条横线」，而他这次在截图里点的位置正好是**页头上边缘（导航条下沿）**。
   这一条是全站唯一一处「整幅宽 + 固定 + 毛玻璃」的表面，正是 Chromium 133+
   在 issue 404285923 里对大面积 backdrop-filter 回归得最厉害的那种形态：
   只要它背后有任何一次重绘，它就得整条重新采样，采样按 tile 切开，接缝就是那条线。
   代价几乎为零：它本来就压着 86% 的底色，14px 模糊贡献的可见信息微乎其微，
   v1.9.44 把底色提到 92% 之后肉眼看不出差别，但那一整条采样面没有了。 */
.cdr-header {
  position: sticky; top: 0; z-index: 60;
  /* ⚠️ 只有 92% 不透明、又没有 backdrop-filter 时，滚上去的内容会以 8% 透出来。
     正文小字还好，但 `.page-head` 那个 2rem 粗标题透出来就像"两块内容叠在一起"
     （站长在手机版服务器指南里看到的就是这个）。
     加一层磨砂（和站内液态玻璃同一套观感）把透出来的东西糊掉；
     不支持 backdrop-filter 的浏览器退化成完全不透明，宁可挡住也不能叠字。 */
  background: color-mix(in srgb, var(--cdr-surface) 92%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  border-bottom: 1px solid var(--cdr-border);
  transition: box-shadow .2s;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .cdr-header { background: var(--cdr-surface); }
}
/* 滚起来就换不透明底：磨砂只够糊小字，`.page-head` 那个 2rem 粗标题
   在 92% 底下还是看得出轮廓（站长原话「两块内容叠在一起」）。 */
/* ⚠️ v1.9.76 —— 滚动期**关掉**这条毛玻璃：`.is-scrolled` 的底色已经换成完全不透明的
   `--cdr-surface`（亮色 #FFFFFF / 暗色 #132444），背后根本没有东西可糊，但浏览器
   照样每帧把整条设备宽度重新采样一遍（采样按 tile 切开）。而 `.is-scrolled` 恰恰是
   「已经滚起来了」的状态（app.js:115 在 y > 8 时加上），也就是掉帧最明显的那一刻。
   观感零变化：不透明底把模糊结果整个盖住了。页面顶端的磨砂（未滚动时）原样保留。 */
.cdr-header.is-scrolled {
  box-shadow: var(--cdr-shadow); background: var(--cdr-surface);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.header-inner {
  height: var(--cdr-header-h);
  display: flex; align-items: center; gap: 18px;
  min-width: 0;
}
.brand { display: flex; align-items: center; gap: 10px; color: inherit; flex-shrink: 0; min-width: 0; }
.brand-logo {
  display: block;
  height: 42px;
  width: auto;
  max-width: min(240px, 42vw);
  object-fit: contain;
  transition: transform .25s cubic-bezier(.22,1,.36,1), filter .25s;
}
.brand:hover .brand-logo { transform: scale(1.04); }
@media (max-width: 640px) {
  .brand-logo { height: 34px; max-width: 46vw; }
}
/* 页脚 logo 稍小一点 */
.brand-footer .brand-logo-brand-footer {
  display: block;
  height: 34px;
  width: auto;
  max-width: 210px;
  object-fit: contain;
  opacity: .95;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text strong { font-size: 1.12rem; letter-spacing: .04em; }
.brand-text small { color: var(--cdr-muted); font-size: .72rem; }

/* QQ 群一键加群 */
.qq-group-link {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--cdr-primary); font-weight: 600;
}
.qq-group-link:hover { opacity: .8; }

.main-nav { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.main-nav a {
  color: var(--cdr-text); padding: 8px 13px; border-radius: 10px;
  font-size: .93rem; font-weight: 500; white-space: nowrap;
}
.main-nav a:hover { background: color-mix(in srgb, var(--cdr-primary) 10%, transparent); opacity: 1; }
/* 顶级导航项颜色完全统一，只有「当前所在页」高亮（蓝字 + 浅蓝底）。
   以前额外给「常用项」加了 is-hot 变色，结果三个变蓝、三个还是深灰，
   看起来像同时选中了三项 —— 已去掉，现在导航只有一套高亮规则。 */
.main-nav a.active { color: var(--cdr-primary); background: color-mix(in srgb, var(--cdr-primary) 13%, transparent); }

/* ---- 导航分组（服务器 / 社区 下拉）---- */
.nav-group { position: relative; }
.nav-group-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 8px 13px; border-radius: 10px; border: 0; background: none;
  color: var(--cdr-text); font-size: .93rem; font-weight: 500;
  font-family: inherit; cursor: pointer; white-space: nowrap; transition: .18s;
}
.nav-group-btn:hover { background: color-mix(in srgb, var(--cdr-primary) 10%, transparent); }
/* 分组里有「当前所在页」时，按钮和普通链接项用同一套高亮，保持整条导航一致 */
.nav-group.is-active .nav-group-btn {
  color: var(--cdr-primary);
  background: color-mix(in srgb, var(--cdr-primary) 13%, transparent);
}
.nav-group-btn .chev { opacity: .6; transition: transform .18s; }
.nav-group.is-open .nav-group-btn .chev { transform: rotate(180deg); }
.nav-group-menu {
  position: absolute; left: 0; top: calc(100% + 8px); min-width: 186px;
  display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  border-radius: 14px; box-shadow: var(--cdr-shadow-lg);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: .18s; z-index: 70;
}
.nav-group-menu a { padding: 9px 12px; border-radius: 9px; font-size: .90rem; }
@media (min-width: 981px) {
  /* 桌面端：鼠标悬停也能打开，点击同样有效（触屏/键盘友好） */
  .nav-group:hover .nav-group-menu,
  .nav-group.is-open .nav-group-menu { opacity: 1; visibility: visible; transform: none; }
  .nav-group:hover .nav-group-btn { background: color-mix(in srgb, var(--cdr-primary) 10%, transparent); }
}

.header-tools { display: flex; align-items: center; gap: 6px; }
.icon-btn {
  width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: 11px; border: 1px solid var(--cdr-border);
  background: var(--cdr-surface); color: var(--cdr-text); cursor: pointer;
  transition: .18s;
}
.icon-btn:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.user-chip {
  display: flex; align-items: center; gap: 8px; padding: 4px 12px 4px 4px;
  border-radius: 999px; border: 1px solid var(--cdr-border);
  background: var(--cdr-surface); cursor: pointer; color: var(--cdr-text);
  font-size: .88rem; max-width: 180px;
}
.user-chip:hover { border-color: var(--cdr-primary); }
.user-chip .avatar { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; background: var(--cdr-border); }
.user-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-toggle { display: none; }

/* 下拉 */
.dropdown { position: relative; }
.dropdown-menu {
  position: absolute; right: 0; top: calc(100% + 8px); min-width: 190px;
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  border-radius: 14px; box-shadow: var(--cdr-shadow-lg); padding: 6px;
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: .18s; z-index: 70;
}
.dropdown.is-open .dropdown-menu { opacity: 1; visibility: visible; transform: none; }
.dropdown-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 9px 12px; border-radius: 9px; border: 0; background: none;
  color: var(--cdr-text); font-size: .9rem; text-align: left; cursor: pointer;
  font-family: inherit;
}
.dropdown-item:hover { background: color-mix(in srgb, var(--cdr-primary) 12%, transparent); opacity: 1; }
.dropdown-item.is-active { color: var(--cdr-primary); font-weight: 600; }
.dropdown-item.is-danger { color: var(--cdr-danger); }
.dropdown-head { padding: 8px 12px 10px; border-bottom: 1px solid var(--cdr-border); margin-bottom: 6px; }
.dropdown-head strong { display: block; font-size: .92rem; }
.dropdown-head small { color: var(--cdr-muted); font-size: .78rem; }

@media (max-width: 980px) {
  .main-nav {
    position: fixed; inset: var(--cdr-header-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 2px;
    background: var(--cdr-surface); border-bottom: 1px solid var(--cdr-border);
    padding: 12px; margin: 0; transform: translateY(-130%);
    transition: transform .25s ease; max-height: calc(100vh - var(--cdr-header-h)); overflow-y: auto;
  }
  .main-nav.is-open { transform: none; }
  .main-nav a { padding: 12px 14px; }
  .nav-toggle { display: grid; }
  .header-tools { margin-left: auto; }

  /* ---- 移动端导航：分组变手风琴（默认收起，点标题展开）---- */
  .nav-group { position: static; }
  .nav-group-btn {
    width: 100%; justify-content: space-between; padding: 12px 14px;
    font-size: .95rem; border-radius: 10px;
  }
  .nav-group-menu {
    position: static; opacity: 1; visibility: visible; transform: none;
    box-shadow: none; border: 0; border-radius: 0; padding: 0 0 4px 10px;
    background: none; min-width: 0; display: none;
    border-left: 2px solid color-mix(in srgb, var(--cdr-primary) 25%, transparent);
    margin-left: 12px;
  }
  .nav-group.is-open .nav-group-menu { display: flex; }
  .nav-group.is-open .nav-group-btn { color: var(--cdr-primary); }
  .nav-group-menu a { padding: 11px 12px; min-height: 44px; display: flex; align-items: center; }

  /* ---- 头部防溢出：窄屏下按钮收成图标、logo 限宽、汉堡必定可见 ---- */
  .header-inner { gap: 10px; }
  .brand-logo { height: 34px; max-width: 34vw; }
  .header-tools { gap: 4px; flex-shrink: 0; }
  .header-login { padding: 9px 11px; }
  .header-login .btn-text { display: none; }   /* 只留图标，省下 ~80px */
  .nav-toggle { flex-shrink: 0; }
}

@media (max-width: 420px) {
  /* 超窄屏：配色按钮也收起来（深色/浅色在手机系统设置里更自然） */
  .header-tools .dropdown:nth-of-type(2) { display: none; }
  .brand-logo { max-width: 30vw; }
}

/* ---------------------------------------------------------------- 按钮 */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 20px; border-radius: calc(var(--cdr-radius) * .75);
  border: 1px solid transparent; font-size: .93rem; font-weight: 600;
  cursor: pointer; font-family: inherit; text-align: center;
  transition: transform .12s, box-shadow .18s, background .18s, color .18s, border-color .18s;
  white-space: nowrap; line-height: 1.4;
}
.btn:active { transform: translateY(1px); }
.btn-primary {
  /* 横向渐变（原来 135° 斜角渐变在按钮边缘会留下 1px 异色像素，看起来像"颜色错位"）；
     终点色偏主色而非纯紫，整颗按钮读起来是一个色系。 */
  background-image: linear-gradient(100deg, var(--cdr-primary) 0%,
                    color-mix(in srgb, var(--cdr-primary) 38%, var(--cdr-primary-2)) 100%);
  background-origin: border-box;
  background-clip: border-box;
  border-color: transparent;
  color: #fff;
  box-shadow: 0 8px 22px -12px var(--cdr-primary), inset 0 1px 0 rgba(255, 255, 255, .22);
}
.btn-primary:hover { box-shadow: 0 12px 28px -10px var(--cdr-primary); opacity: 1; }
.btn-ghost { background: var(--cdr-surface); border-color: var(--cdr-border); color: var(--cdr-text); }
.btn-ghost:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); opacity: 1; }
.btn-soft {
  background: color-mix(in srgb, var(--cdr-primary) 13%, transparent);
  color: var(--cdr-primary);
}
.btn-soft:hover { background: color-mix(in srgb, var(--cdr-primary) 20%, transparent); opacity: 1; }
.btn-danger { background: var(--cdr-danger); color: #fff; }
.btn-sm { padding: 7px 14px; font-size: .85rem; }
.btn-lg { padding: 13px 28px; font-size: 1rem; }
.btn-block { width: 100%; }
.btn[disabled], .btn.is-disabled { opacity: .55; pointer-events: none; }

/* ---------------------------------------------------------------- 卡片 */
.card {
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius); box-shadow: var(--cdr-shadow);
  padding: 22px;
}
.card-pad-0 { padding: 0; overflow: hidden; }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 16px;
}
.card-head h3, .card-head h2 { margin: 0; }
.card-title-sm { font-size: .82rem; text-transform: uppercase; letter-spacing: .1em; color: var(--cdr-muted); font-weight: 600; }

.grid { display: grid; gap: 18px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1024px) { .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

/* 「主内容 + 侧栏」布局：桌面两栏，手机自动堆叠 */
.grid-split { grid-template-columns: minmax(0, 1fr) 320px; }
.grid-split-wide { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
@media (max-width: 900px) {
  .grid-split, .grid-split-wide { grid-template-columns: 1fr; }
}
/* 侧栏在手机上放到主内容之后（先看正文，再看边栏） */
@media (max-width: 900px) {
  .grid-split > aside, .grid-split-wide > aside { order: 2; }
  .grid-split > *, .grid-split-wide > * { min-width: 0; }
}
.stats-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* ---------------------------------------------------------------- 徽章 */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 999px; font-size: .76rem; font-weight: 600;
  background: color-mix(in srgb, var(--cdr-primary) 14%, transparent); color: var(--cdr-primary);
}
.badge-soft { background: color-mix(in srgb, var(--cdr-muted) 14%, transparent); color: var(--cdr-muted); }
.badge-success { background: color-mix(in srgb, var(--cdr-success) 16%, transparent); color: var(--cdr-success); }
.badge-warn { background: color-mix(in srgb, var(--cdr-warning) 18%, transparent); color: var(--cdr-warning); }
.badge-danger { background: color-mix(in srgb, var(--cdr-danger) 15%, transparent); color: var(--cdr-danger); }
.badge-role { background: color-mix(in srgb, currentColor 14%, transparent); }

.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--cdr-success); display: inline-block; }
.dot-live { position: relative; box-shadow: 0 0 0 0 color-mix(in srgb, var(--cdr-success) 70%, transparent); animation: pulse 2s infinite; }
.dot-off { background: var(--cdr-muted); animation: none; }
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--cdr-success) 60%, transparent); }
  70% { box-shadow: 0 0 0 9px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ---------------------------------------------------------------- 提示条 */
.alert {
  border-radius: 12px; padding: 12px 16px; margin: 14px 0; font-size: .9rem;
  border: 1px solid transparent; display: flex; align-items: center; gap: 10px;
}
.alert-success { background: color-mix(in srgb, var(--cdr-success) 12%, transparent); color: var(--cdr-success); border-color: color-mix(in srgb, var(--cdr-success) 28%, transparent); }
.alert-warn { background: color-mix(in srgb, var(--cdr-warning) 14%, transparent); color: #b45309; border-color: color-mix(in srgb, var(--cdr-warning) 30%, transparent); }
.alert-danger { background: color-mix(in srgb, var(--cdr-danger) 12%, transparent); color: var(--cdr-danger); border-color: color-mix(in srgb, var(--cdr-danger) 28%, transparent); }
html[data-color-mode="dark"] .alert-warn { color: var(--cdr-warning); }

/* ---------------------------------------------------------------- Hero */
.hero {
  position: relative; overflow: hidden; padding: 74px 0 64px;
  background:
    radial-gradient(1100px 520px at 12% -10%, color-mix(in srgb, var(--cdr-primary) 26%, transparent), transparent 62%),
    radial-gradient(900px 460px at 88% 4%, color-mix(in srgb, var(--cdr-primary-2) 24%, transparent), transparent 60%);
}
.hero::after {
  content: ""; position: absolute; inset: 0;
  background-image: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--cdr-muted) 30%, transparent) 1px, transparent 0);
  background-size: 26px 26px; opacity: .35; pointer-events: none;
  mask-image: linear-gradient(to bottom, #000 0%, #000 86%, transparent 100%);
}
.hero-inner { position: relative; z-index: 1; display: grid; grid-template-columns: 1.15fr .85fr; gap: 44px; align-items: center; }
@media (max-width: 940px) { .hero-inner { grid-template-columns: 1fr; gap: 28px; } }
.hero-eyebrow {
  display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px;
  border-radius: 999px; background: color-mix(in srgb, var(--cdr-surface) 70%, transparent);
  border: 1px solid var(--cdr-border); font-size: .82rem; font-weight: 600; color: var(--cdr-muted);
  margin-bottom: 18px;
}
.hero h1 { margin-bottom: .35em; }
.hero-lead { font-size: 1.06rem; color: var(--cdr-muted); max-width: 46ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }

.ip-box {
  display: flex; align-items: center; gap: 12px; margin-top: 26px;
  background: var(--cdr-surface); border: 1px dashed color-mix(in srgb, var(--cdr-primary) 45%, var(--cdr-border));
  border-radius: 14px; padding: 12px 16px; max-width: 460px;
}
.ip-box .ip-label { font-size: .72rem; color: var(--cdr-muted); text-transform: uppercase; letter-spacing: .08em; }
.ip-box .ip-value { font-family: "JetBrains Mono", Consolas, monospace; font-size: 1.08rem; font-weight: 700; color: var(--cdr-primary); }
.ip-box .btn { margin-left: auto; }

.hero-card {
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  border-radius: calc(var(--cdr-radius) + 4px); box-shadow: var(--cdr-shadow-lg);
  padding: 24px; backdrop-filter: blur(6px);
}
.hero-stat-row { display: flex; align-items: baseline; gap: 10px; }
.hero-stat-num {
  font-size: 2.6rem; font-weight: 800; line-height: 1;
  background: linear-gradient(135deg, var(--cdr-primary), var(--cdr-primary-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- 统计块 */
.stat {
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius); padding: 18px; text-align: center;
  transition: transform .18s, box-shadow .18s;
}
.stat:hover { transform: translateY(-3px); box-shadow: var(--cdr-shadow); }
.stat-value { font-size: 1.85rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15; }
.stat-label { color: var(--cdr-muted); font-size: .82rem; margin-top: 4px; }
.stat-icon { font-size: 1.3rem; margin-bottom: 6px; }

/* ---------------------------------------------------------------- 进度条 */
.meter { margin-bottom: 16px; }
.meter-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 7px; }
.meter-name { font-size: .88rem; font-weight: 600; }
.meter-val { font-size: .86rem; font-variant-numeric: tabular-nums; color: var(--cdr-muted); }
.meter-val strong { color: var(--cdr-text); font-size: .95rem; }
.meter-track { height: 9px; background: color-mix(in srgb, var(--cdr-muted) 18%, transparent); border-radius: 999px; overflow: hidden; }
.meter-fill {
  height: 100%; border-radius: 999px; width: 0;
  background: linear-gradient(90deg, var(--cdr-primary), var(--cdr-accent));
  transition: width .6s cubic-bezier(.22, 1, .36, 1), background .3s;
}
.meter-fill.is-ok { background: linear-gradient(90deg, var(--cdr-success), var(--cdr-accent)); }
.meter-fill.is-warn { background: linear-gradient(90deg, var(--cdr-warning), #fbbf24); }
.meter-fill.is-danger { background: linear-gradient(90deg, var(--cdr-danger), #f97316); }
.meter-sub { font-size: .78rem; color: var(--cdr-muted); margin-top: 5px; }

/* ---------------------------------------------------------------- 状态卡 */
.server-card { position: relative; overflow: hidden; }
.server-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 16px; }
.server-name { font-size: 1.12rem; font-weight: 700; display: flex; align-items: center; gap: 9px; }
.server-meta { color: var(--cdr-muted); font-size: .82rem; margin-top: 3px; }
.online-big { text-align: right; }
.online-big .num {
  font-size: 2.1rem; font-weight: 800; line-height: 1;
  background: linear-gradient(135deg, var(--cdr-primary), var(--cdr-primary-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  font-variant-numeric: tabular-nums;
}
.online-big .lbl { font-size: .76rem; color: var(--cdr-muted); }
.player-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; }
.player-chip {
  display: inline-flex; align-items: center; gap: 7px; padding: 4px 11px 4px 4px;
  background: color-mix(in srgb, var(--cdr-muted) 12%, transparent); border-radius: 999px;
  font-size: .82rem; font-weight: 500;
}
.player-chip img { width: 22px; height: 22px; border-radius: 50%; image-rendering: pixelated; }
.player-chip.plain { padding: 4px 11px; }

.state-pill { display: inline-flex; align-items: center; gap: 6px; font-size: .78rem; font-weight: 600; color: var(--cdr-success); }
.state-pill.off { color: var(--cdr-muted); }
.state-pill.stale { color: var(--cdr-warning); }

.raw-json {
  background: color-mix(in srgb, var(--cdr-muted) 10%, transparent);
  border: 1px solid var(--cdr-border); border-radius: 12px; padding: 14px;
  font-size: .78rem; max-height: 320px; overflow: auto; white-space: pre-wrap; word-break: break-all;
}

/* ---------------------------------------------------------------- 表格 */
.table-wrap { overflow-x: auto; border-radius: var(--cdr-radius); border: 1px solid var(--cdr-border); background: var(--cdr-surface); }
table.cdr-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
table.cdr-table th {
  text-align: left; padding: 13px 16px; font-size: .78rem; font-weight: 600;
  color: var(--cdr-muted); text-transform: uppercase; letter-spacing: .06em;
  border-bottom: 1px solid var(--cdr-border); background: color-mix(in srgb, var(--cdr-muted) 7%, transparent);
  white-space: nowrap;
}
table.cdr-table td { padding: 13px 16px; border-bottom: 1px solid var(--cdr-border); vertical-align: middle; }
table.cdr-table tbody tr:last-child td { border-bottom: 0; }
table.cdr-table tbody tr:hover { background: color-mix(in srgb, var(--cdr-primary) 5%, transparent); }
table.cdr-table .num { font-variant-numeric: tabular-nums; text-align: right; }
.text-muted { color: var(--cdr-muted); }
.text-sm { font-size: .84rem; }
.text-xs { font-size: .76rem; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.mono { font-family: "JetBrains Mono", Consolas, monospace; }
.nowrap { white-space: nowrap; }
.mt-0 { margin-top: 0; } .mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 24px; } .mt-4 { margin-top: 34px; }
.mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: 8px; } .mb-2 { margin-bottom: 16px; } .mb-3 { margin-bottom: 24px; }
.flex { display: flex; } .flex-wrap { flex-wrap: wrap; } .items-center { align-items: center; }
.justify-between { justify-content: space-between; } .gap-1 { gap: 8px; } .gap-2 { gap: 16px; }
.w-full { width: 100%; } .hidden { display: none !important; }

/* ---------------------------------------------------------------- 区块标题 */
.section { padding: 56px 0; }
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 28px; flex-wrap: wrap; }
.section-head h2 { margin: 0; }
.section-head .sub { color: var(--cdr-muted); font-size: .92rem; margin-top: 6px; }
.section-alt { background: color-mix(in srgb, var(--cdr-muted) 6%, transparent); }

.page-head {
  padding: 42px 0 30px;
  background: radial-gradient(900px 320px at 8% -30%, color-mix(in srgb, var(--cdr-primary) 20%, transparent), transparent 65%);
  border-bottom: 1px solid var(--cdr-border);
}
.page-head h1 { margin-bottom: .25em; }
.page-head .sub { color: var(--cdr-muted); }
.breadcrumb { font-size: .82rem; color: var(--cdr-muted); margin-bottom: 12px; display: flex; gap: 7px; flex-wrap: wrap; }
.breadcrumb a { color: var(--cdr-muted); }
.breadcrumb a:hover { color: var(--cdr-primary); }

/* ---------------------------------------------------------------- 相册 */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(255px, 1fr)); gap: 18px; }
.gallery-item {
  position: relative; border-radius: var(--cdr-radius); overflow: hidden;
  border: 1px solid var(--cdr-border); background: var(--cdr-surface);
  box-shadow: var(--cdr-shadow); transition: transform .22s, box-shadow .22s;
}
.gallery-item:hover { transform: translateY(-4px); box-shadow: var(--cdr-shadow-lg); }
.gallery-thumb { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: color-mix(in srgb, var(--cdr-muted) 14%, transparent); }
.gallery-thumb img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s; }
.gallery-item:hover .gallery-thumb img { transform: scale(1.06); }
.gallery-body { padding: 14px 16px; }
.gallery-body h4 { margin: 0 0 4px; font-size: 1rem; }
.gallery-meta { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: var(--cdr-muted); font-size: .8rem; }
.lightbox {
  position: fixed; inset: 0; z-index: 200; background: rgba(2, 6, 23, .92);
  display: none; place-items: center; padding: 30px; backdrop-filter: blur(4px);
}
.lightbox.is-open { display: grid; }
.lightbox img { max-width: min(1200px, 94vw); max-height: 84vh; border-radius: 12px; object-fit: contain; }
.lightbox-close { position: absolute; top: 22px; right: 26px; color: #fff; font-size: 2rem; cursor: pointer; line-height: 1; background: none; border: 0; }

/* ---------------------------------------------------------------- 论坛 */
.forum-cat {
  display: flex; align-items: center; gap: 16px; padding: 18px 20px;
  border-bottom: 1px solid var(--cdr-border); color: inherit;
}
.forum-cat:last-child { border-bottom: 0; }
.forum-cat:hover { background: color-mix(in srgb, var(--cdr-primary) 6%, transparent); opacity: 1; }
.forum-cat-icon {
  width: 46px; height: 46px; border-radius: 13px; display: grid; place-items: center;
  background: linear-gradient(135deg, color-mix(in srgb, var(--cdr-primary) 22%, transparent), color-mix(in srgb, var(--cdr-primary-2) 22%, transparent));
  font-size: 1.2rem; flex-shrink: 0;
}
.forum-cat-main { flex: 1; min-width: 0; }
.forum-cat-main h4 { margin: 0 0 3px; font-size: 1.02rem; }
.forum-cat-main p { margin: 0; color: var(--cdr-muted); font-size: .84rem; }
.forum-cat-stat { text-align: center; color: var(--cdr-muted); font-size: .78rem; flex-shrink: 0; }
.forum-cat-stat strong { display: block; font-size: 1.05rem; color: var(--cdr-text); }

.thread-row { display: flex; align-items: center; gap: 14px; padding: 15px 20px; border-bottom: 1px solid var(--cdr-border); color: inherit; }
.thread-row:last-child { border-bottom: 0; }
.thread-row:hover { background: color-mix(in srgb, var(--cdr-primary) 5%, transparent); opacity: 1; }
.thread-avatar img { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; background: var(--cdr-border); }
.thread-main { flex: 1; min-width: 0; }
.thread-main h4 { margin: 0 0 3px; font-size: .98rem; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.thread-main .meta { color: var(--cdr-muted); font-size: .79rem; }
.thread-stat { color: var(--cdr-muted); font-size: .8rem; text-align: center; flex-shrink: 0; min-width: 62px; }
.thread-stat strong { display: block; font-size: 1rem; color: var(--cdr-text); font-variant-numeric: tabular-nums; }

.post {
  display: flex; gap: 16px; padding: 20px; border-bottom: 1px solid var(--cdr-border);
}
.post:last-child { border-bottom: 0; }
.post-side { width: 128px; flex-shrink: 0; text-align: center; }
.post-side img { width: 62px; height: 62px; border-radius: 14px; margin: 0 auto 8px; object-fit: cover; background: var(--cdr-border); }
.post-side .name { font-weight: 700; font-size: .9rem; word-break: break-all; }
.post-side .role { font-size: .74rem; margin-top: 5px; }
.post-main { flex: 1; min-width: 0; }
.post-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: var(--cdr-muted); font-size: .8rem; margin-bottom: 12px; }
.post-body { line-height: 1.8; word-wrap: break-word; overflow-wrap: anywhere; }
.post-foot { display: flex; gap: 14px; align-items: center; margin-top: 14px; color: var(--cdr-muted); font-size: .82rem; }
@media (max-width: 640px) {
  .post { flex-direction: column; gap: 10px; }
  .post-side { width: auto; display: flex; align-items: center; gap: 10px; text-align: left; }
  .post-side img { width: 38px; height: 38px; margin: 0; border-radius: 50%; }
}

.like-btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px;
  border-radius: 999px; border: 1px solid var(--cdr-border); background: none;
  color: var(--cdr-muted); cursor: pointer; font-size: .82rem; font-family: inherit;
}
.like-btn:hover { border-color: var(--cdr-danger); color: var(--cdr-danger); }
.like-btn.is-liked { background: color-mix(in srgb, var(--cdr-danger) 13%, transparent); color: var(--cdr-danger); border-color: transparent; }

/* ---------------------------------------------------------------- 表单 */
.form-row { margin-bottom: 18px; }
.form-label { display: block; font-size: .87rem; font-weight: 600; margin-bottom: 7px; }
.form-hint { font-size: .79rem; color: var(--cdr-muted); margin-top: 5px; }
.form-input, .form-select, .form-textarea {
  width: 100%; padding: 11px 14px; border-radius: 11px;
  border: 1px solid var(--cdr-border); background: var(--cdr-surface);
  color: var(--cdr-text); font-size: .93rem; font-family: inherit;
  transition: border-color .18s, box-shadow .18s;
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
  outline: none; border-color: var(--cdr-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cdr-primary) 18%, transparent);
}
.form-textarea { min-height: 130px; resize: vertical; line-height: 1.7; }
.form-input.mono { font-family: "JetBrains Mono", Consolas, monospace; letter-spacing: .05em; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
.checkbox { display: flex; align-items: center; gap: 9px; font-size: .9rem; cursor: pointer; }
.checkbox input { width: 17px; height: 17px; accent-color: var(--cdr-primary); }

/* ============ 个人中心：分页签 + 账号安全 ============ */
/* ============ 指南：搜索 + 服务器实况 ============ */
.guide-search { padding: 0 14px 10px; }
.guide-search .form-input { width: 100%; }
.guide-live {
  margin-top: 14px; padding: 12px 14px 4px;
  border-top: 1px dashed var(--cdr-border);
}
.kv-list.kv-tight li { padding: 5px 0; font-size: .84rem; }
.kv-list.kv-tight .k { color: var(--cdr-muted); }
@media (max-width: 900px) {
  /* 手机上侧栏在正文上方，实况块不要再占一屏 */
  .guide-live { margin-top: 10px; }
}

.me-tabs { display: flex; gap: 6px; margin-top: 16px; flex-wrap: wrap; }
.me-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px; border-radius: 10px; font-size: .9rem; font-weight: 500;
  color: var(--cdr-text); border: 1px solid var(--cdr-border); background: var(--cdr-surface);
}
.me-tab:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.me-tab.is-on {
  color: var(--cdr-primary); font-weight: 600;
  border-color: color-mix(in srgb, var(--cdr-primary) 40%, transparent);
  background: color-mix(in srgb, var(--cdr-primary) 12%, transparent);
}
.verify-banner { display: block; }
.sec-row { padding: 14px 0; border-top: 1px dashed var(--cdr-border); }
.sec-row:first-of-type { border-top: 0; }
.sec-row-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.sec-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.sec-form .form-input { flex: 1 1 200px; min-width: 0; }
.text-success { color: var(--cdr-success); }
@media (max-width: 640px) {
  .me-tabs { gap: 4px; }
  .me-tab { padding: 7px 11px; font-size: .84rem; }
  .sec-form .btn { width: 100%; }
}

/* ============ 赞助入口（导航 / 页脚 / 首页卡片）============ */
.main-nav a.nav-sponsor { color: #e11d48; font-weight: 600; }
.main-nav a.nav-sponsor:hover { background: color-mix(in srgb, #e11d48 12%, transparent); }
.footer-links a.footer-sponsor { color: #e11d48; font-weight: 600; }
.sponsor-card { padding: 22px 24px; }
.sponsor-inner { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.sponsor-emoji { font-size: 2rem; line-height: 1; }
.sponsor-text { flex: 1 1 320px; min-width: 0; }
.sponsor-title { font-size: 1.05rem; margin: 0 0 4px; }
.sponsor-btn { flex: 0 0 auto; }
@media (max-width: 640px) {
  .sponsor-inner { gap: 12px; }
  .sponsor-btn { width: 100%; }
}

/* ============ 登录 / 注册 / 找回密码：背景图 + 磨砂卡片 ============
   背景层固定铺满，内容层抬到它上面；没上传图时用 CSS 渐变当默认氛围背景。
   卡片做半透明磨砂，保证背景图再花也能看清表单。 */
.cdr-body.auth-page { background: var(--cdr-bg); }
.auth-bg {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-color: var(--cdr-bg);
  /* v6：原来这里叠了青 + 紫 + 蓝三团大范围泛光（#8b5cf6 / #22d3ee），
     那是套模板最典型的底纹。改成同一色系的低饱和山雾渐变。 */
  background-image:
    radial-gradient(1200px 620px at 12% -12%, color-mix(in srgb, var(--cdr-primary) 18%, transparent), transparent 62%),
    radial-gradient(900px 520px at 92% 6%, color-mix(in srgb, var(--cdr-accent) 14%, transparent), transparent 64%),
    radial-gradient(820px 700px at 50% 120%, color-mix(in srgb, var(--cdr-text) 8%, transparent), transparent 62%);
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
/* 只有真的传了照片才压暗罩 —— 默认的渐变背景本来就够柔和，压了反而发灰 */
.auth-bg.has-image::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6, 12, 24, .34), rgba(6, 12, 24, .58));
}
/* 背景层是 fixed + z-index:0，非定位的 main/footer 会被它盖住，所以这两个要抬上来。
   注意**不能**把 .cdr-header 也写进来：头部本来就是 sticky + z-index:60，已经高于背景层；
   一旦被覆盖成 position:relative，吸顶就失效 —— 手机端菜单是挂在头部里的 fixed 元素，
   头部一滚走菜单就飘起来压住登录卡片（v1.3.7 踩过：登录/注册页吸顶导航消失）。 */
.cdr-body.auth-page .cdr-main,
.cdr-body.auth-page .cdr-footer { position: relative; z-index: 1; }
.cdr-body.auth-page .card {
  background: color-mix(in srgb, var(--cdr-surface) 88%, transparent);
  backdrop-filter: blur(14px) saturate(1.15);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  border-color: color-mix(in srgb, var(--cdr-border) 70%, transparent);
}
/* 用户系统关了「半透明效果」偏好时不强上磨砂 */
@media (prefers-reduced-transparency: reduce) {
  .cdr-body.auth-page .card { background: var(--cdr-surface); backdrop-filter: none; }
}

/* ---------------------------------------------------------------- QQ 登录
   v1.9.83：「官方小图标 + 站点自己的按钮外观」。
   依据 QQ 互联「网站前端页面规范」——规范里**「小图标 + 文字「QQ登录」」是允许的一档**：
   图标必须是腾讯官方原图（这里用官方 24×24，原图原比例、不改色、不叠滤镜），
   「QQ登录」四个字必须出现，而按钮的边框、底色、圆角、字重可以自己做。
   所以它终于不像一张贴上去的蓝色贴纸了（站长原话），合规性一点没丢。
   唯一实现是 site/app/View/partials/qq-button.php，登录页 / 注册页 / 顶栏 / 个人中心
   全都 require 它 —— 别再各写一份。
   ⚠️ 也别再换成自己画的企鹅 SVG + 自造文案：v1.9.82 之前被驳回的理由就是这个。 */
.qq-login { text-align: center; }
.qq-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 44px; padding: 0 18px;
  border: 1px solid var(--cdr-border); border-radius: var(--cdr-radius);
  background: color-mix(in srgb, var(--cdr-surface) 94%, transparent);
  color: var(--cdr-text); font-size: .95rem; font-weight: 650; line-height: 1.2;
  text-decoration: none; cursor: pointer;
  transition: border-color .18s ease, background-color .18s ease,
              transform .18s ease, box-shadow .18s ease;
}
/* v1.9.84：换成腾讯官方 16×16 小图标（透明底黑企鹅、无彩底）。
   站长反馈原来的 24×24 蓝方块「太抢眼、太卡通」—— 规范只锁"图标必须是官方原图"，
   尺寸可以在规范列出的档位里选小的，所以这里用最小档。 */
.qq-btn-ic {
  display: block; width: 16px; height: 16px; flex: 0 0 auto;
  border-radius: 0; border: 0;
}
.qq-btn-tx { white-space: nowrap; }
.qq-btn:hover, .qq-btn:focus-visible {
  border-color: color-mix(in srgb, var(--cdr-accent) 55%, var(--cdr-border));
  background: color-mix(in srgb, var(--cdr-accent) 9%, var(--cdr-surface));
  transform: translateY(-1px);
  box-shadow: 0 10px 22px -16px rgba(18, 33, 63, .55);
}
.qq-btn:active { transform: translateY(0); }
/* 登录页 / 注册页：整行，和主按钮同宽 */
.qq-btn--block { display: flex; width: 100%; }
/* 个人中心里的弱化版 */
.qq-btn--quiet { background: transparent; font-weight: 600; }
/* v1.9.85：顶栏那颗 chip 形态已删除（站长要求头部不要两个登录入口）。
   需要时再按 .qq-btn 基础样式 + 一颗官方 16×16 图标重建，别改图标本身。 */
@media (prefers-reduced-motion: reduce) {
  .qq-btn, .qq-btn:hover, .qq-btn:focus-visible { transform: none; }
}
/* 登录页「QQ 登录」与「邮箱登录」之间的分隔线 */
.auth-divider {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 0 16px; color: var(--cdr-muted); font-size: .78rem;
}
.auth-divider::before, .auth-divider::after {
  content: ''; flex: 1; height: 1px; background: var(--cdr-border);
}
.auth-divider span { white-space: nowrap; }
.bind-code {
  font-family: "JetBrains Mono", Consolas, monospace; font-size: 2.4rem; font-weight: 800;
  letter-spacing: .28em; text-align: center; padding: 18px;
  background: color-mix(in srgb, var(--cdr-primary) 11%, transparent);
  border: 2px dashed color-mix(in srgb, var(--cdr-primary) 45%, transparent);
  border-radius: 14px; color: var(--cdr-primary); user-select: all;
}

/* ---------------------------------------------------------------- 分页 */
.pagination { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin-top: 26px; }
.pagination a, .pagination span {
  min-width: 38px; height: 38px; padding: 0 12px; display: grid; place-items: center;
  border-radius: 10px; border: 1px solid var(--cdr-border); background: var(--cdr-surface);
  color: var(--cdr-text); font-size: .88rem;
}
.pagination a:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); opacity: 1; }
.pagination .current { background: var(--cdr-primary); color: #fff; border-color: transparent; font-weight: 700; }
.pagination .disabled { opacity: .45; pointer-events: none; }

/* ---------------------------------------------------------------- 富文本 */
.prose { line-height: 1.85; word-wrap: break-word; overflow-wrap: anywhere; }
.prose h2 { margin-top: 1.8em; padding-bottom: .35em; border-bottom: 1px solid var(--cdr-border); }
.prose h3 { margin-top: 1.5em; }
.prose ul, .prose ol { padding-left: 1.5em; margin-bottom: 1em; }
.prose li { margin-bottom: .4em; }
.prose img { border-radius: 12px; margin: 1em 0; box-shadow: var(--cdr-shadow); }
.prose blockquote {
  margin: 1.2em 0; padding: 12px 18px; border-left: 4px solid var(--cdr-primary);
  background: color-mix(in srgb, var(--cdr-primary) 7%, transparent); border-radius: 0 12px 12px 0;
  color: var(--cdr-muted);
}
.prose code {
  background: color-mix(in srgb, var(--cdr-muted) 16%, transparent); padding: 2px 7px;
  border-radius: 6px; font-size: .88em; color: var(--cdr-primary);
}
.prose pre {
  background: var(--cdr-bg-dark, #080F22); color: var(--cdr-text-dark, #E6EEF9); padding: 16px 18px; border-radius: 12px;
  overflow-x: auto; font-size: .85rem; line-height: 1.65; margin: 1.2em 0;
}
.prose pre code { background: none; color: inherit; padding: 0; }
.prose table { width: 100%; border-collapse: collapse; margin: 1.2em 0; font-size: .9rem; }
.prose th, .prose td { border: 1px solid var(--cdr-border); padding: 9px 13px; text-align: left; }
.prose th { background: color-mix(in srgb, var(--cdr-muted) 10%, transparent); }
.prose a { text-decoration: underline; text-underline-offset: 3px; }

/* ---------------------------------------------------------------- 指南侧栏 */
.guide-layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 30px; align-items: start; }
.guide-nav { position: sticky; top: calc(var(--cdr-header-h) + 18px); }
/* 窄屏（≤900px）除了塌成单列，侧栏还必须取消 sticky。
   桌面上的侧栏是 240px 宽的窄条，钉在左上角不挡正文；塌成单列后它变成整宽、
   手机上实测高 614px 的一块 —— 仍 sticky 的话它会永久钉在视口顶部，正文全从它
   底下滚过去（侧栏本身没有底色、链接有底色）⇒ 看起来就是「文字互相压住、
   白方块乱飞、内容不显示」。这条必须写在基础规则之后才盖得住它。 */
@media (max-width: 900px) {
  .guide-layout { grid-template-columns: 1fr; }
  .guide-nav { position: static; top: auto; }
}
.guide-nav a {
  display: block; padding: 9px 14px; border-radius: 10px; color: var(--cdr-text);
  font-size: .89rem; border-left: 3px solid transparent;
}
.guide-nav a:hover { background: color-mix(in srgb, var(--cdr-primary) 8%, transparent); opacity: 1; }
.guide-nav a.active {
  background: color-mix(in srgb, var(--cdr-primary) 12%, transparent);
  color: var(--cdr-primary); border-left-color: var(--cdr-primary); font-weight: 600;
}

/* ---------------------------------------------------------------- 时间线 */
.timeline { position: relative; padding-left: 24px; }
.timeline::before { content: ""; position: absolute; left: 7px; top: 6px; bottom: 6px; width: 2px; background: var(--cdr-border); }
.timeline-item { position: relative; padding-bottom: 20px; }
/* ── v1.9.76：试过给更新日志的条目加 `content-visibility: auto`（屏幕外的条目
   跳过样式计算 / 布局 / 绘制），**实测没有收益，已经撤掉**。留这段记录，免得
   后人（包括我自己）再走一遍：
     · 桌面 headless Edge + 4× CPU 节流：改与不改都是 0 掉帧，量不出差别；
     · 20× CPU 节流（更接近中端手机）滚同一条页面：改过的版本滚动期 longtask
       22 个 / 1221ms、掉帧 62 帧；还原成 `visible` 的基线 1 个 / 655ms、掉帧
       11 帧。它做的事只是把「加载时一次性排完」挪成「滚动时逐条补」，而这个
       站长的体感问题恰恰是**滚动**，所以这个挪法对他不利。
     · 顺带一个坑：`contain-intrinsic-size` 的估计值必须实测，不能拍脑袋。
       一开始写 220px，页面总高从真实的 52519 缩成 29405 —— 滚动条一边滚一边
       变长。实测 112 条：min 167 / p25 317 / p50 410 / p75 525 / p90 641 /
       max 1227（均值 453.6）；而同样用 `.timeline-item` 的封禁记录页
       （`site/app/View/bans.php:267`）每条只有 72px —— 一个估计值不可能同时
       伺候两个页面。
   真要再捡起来，先想清楚**在真机上怎么量出收益**。
   测量工具：`.tools/_perf-probe.js`（滚动掉帧，variant `before` 是这一批改动的
   对照基线）、`.tools/_perf-measure.js`（长列表条目高度分布）。 */
.timeline-item::before {
  content: ""; position: absolute; left: -24px; top: 6px; width: 12px; height: 12px;
  border-radius: 50%; background: var(--cdr-primary); border: 3px solid var(--cdr-bg);
}
.timeline-item.is-off::before { background: var(--cdr-muted); }

/* ---------------------------------------------------------------- 排序榜 */
.rank-row { display: flex; align-items: center; gap: 14px; padding: 11px 4px; border-bottom: 1px solid var(--cdr-border); }
.rank-row:last-child { border-bottom: 0; }
.rank-num {
  width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center;
  font-size: .82rem; font-weight: 800; background: color-mix(in srgb, var(--cdr-muted) 15%, transparent);
  color: var(--cdr-muted); flex-shrink: 0;
}
.rank-num.top1 { background: linear-gradient(135deg, #fbbf24, #f59e0b); color: #fff; }
.rank-num.top2 { background: linear-gradient(135deg, #cbd5e1, #94a3b8); color: #fff; }
.rank-num.top3 { background: linear-gradient(135deg, #d97706, #b45309); color: #fff; }
.rank-avatar img { width: 34px; height: 34px; border-radius: 9px; image-rendering: pixelated; background: var(--cdr-border); }
.rank-name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rank-value { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--cdr-primary); font-size: .92rem; }

/* ---------------------------------------------------------------- 封禁页：QQ/角色头像 */
.ban-player {
  display: flex; align-items: center; gap: 10px;
  color: inherit; min-width: 0; width: 100%;
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; text-align: left;
}
.ban-player:hover { opacity: 1; }
.ban-player:hover .ban-player-main strong { color: var(--cdr-primary); }
.ban-caret {
  margin-left: auto; color: var(--cdr-muted); font-size: .8rem;
  transition: transform .2s;
}
.ban-row.is-open .ban-caret { transform: rotate(180deg); }
.ban-row { cursor: pointer; }
.ban-detail > td { padding: 0 !important; background: color-mix(in srgb, var(--cdr-muted) 6%, transparent); }
.ban-detail-inner { padding: 16px 18px; border-top: 1px dashed var(--cdr-border); }
.ban-detail-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ban-detail-avatar { border-radius: 12px; object-fit: cover; flex-shrink: 0; }
.ban-detail-avatar.is-mc { image-rendering: pixelated; }
.ban-detail-avatar.is-qq { box-shadow: 0 0 0 2px color-mix(in srgb, var(--cdr-primary) 45%, transparent); }
@media (max-width: 680px) {
  .ban-detail-inner { padding: 13px 14px; }
  .ban-detail-head .btn { margin-left: 0 !important; width: 100%; }
  .ban-caret { display: none; }   /* 手机上表格已横向滚动，箭头省掉 */
}
.ban-player { display: flex; align-items: center; gap: 10px; color: inherit; min-width: 0; }
.ban-player:hover { opacity: 1; }
.ban-avatar {
  position: relative; flex-shrink: 0;
  width: 34px; height: 34px; border-radius: 10px; overflow: hidden;
  background: color-mix(in srgb, var(--cdr-muted) 16%, transparent);
  display: grid; place-items: center;
}
.ban-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ban-avatar.is-mc img { image-rendering: pixelated; }
.ban-avatar.is-qq { box-shadow: 0 0 0 2px color-mix(in srgb, var(--cdr-primary) 45%, transparent); }
.ban-avatar-badge {
  position: absolute; right: -2px; bottom: -2px;
  font-size: .55rem; font-weight: 800; font-style: normal; line-height: 1;
  padding: 2px 3px; border-radius: 5px;
  background: linear-gradient(135deg, #12b7f5, #0a84ff); color: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
}
.ban-player-main { min-width: 0; display: flex; flex-direction: column; }
.ban-qq { color: var(--cdr-primary); font-size: .76rem; }
.ban-noqq { color: var(--cdr-muted); font-size: .74rem; }

/* ---------------------------------------------------------------- 玩家详情：统计块 */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 12px; }
.stat-mini {
  background: color-mix(in srgb, var(--cdr-muted) 8%, transparent);
  border: 1px solid var(--cdr-border); border-radius: 12px; padding: 12px 14px;
}
.stat-mini .v { font-size: 1.22rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }
.stat-mini .l { font-size: .75rem; color: var(--cdr-muted); margin-top: 3px; }

/* 每日时长柱状图 */
.day-bars { display: flex; align-items: flex-end; gap: 3px; height: 92px; margin-top: 8px; }
.day-bar { flex: 1; min-width: 3px; border-radius: 3px 3px 0 0; background: color-mix(in srgb, var(--cdr-primary) 30%, transparent); position: relative; transition: background .2s, transform .2s; }
.day-bar:hover { background: var(--cdr-primary); transform: scaleY(1.04); }
.day-bar[data-v="0"] { background: color-mix(in srgb, var(--cdr-muted) 18%, transparent); }
.day-axis { display: flex; justify-content: space-between; font-size: .68rem; color: var(--cdr-muted); margin-top: 4px; }

/* 活动热力图（30 天）
   ⚠️ 列数**只在这一条规则里定义**（全站唯一权威）。
     · 30 格 = 15 列 × 2 行（原始设计）。固定列数 → 不会出现"最后一行只剩 2 格"
       的残缺行，也不会因为 minmax 的最小值比实际轨道还宽而撑破容器
       （旧写法 repeat(auto-fill, minmax(26px,1fr)) 在 356px 容器里只算得出
       22.6px 轨道 → 横向溢出 3px）。
     · minmax(0, 1fr) 让轨道能收缩到 0，容器再窄也不可能横向溢出。
     · 格子正方形由 .heat-cell 的 aspect-ratio:1 保证；**不要**用 min-height
       去"撑大"格子 —— 那会反过来把宽度顶大（详见下方「触摸目标」那段的说明）。
   要改"一行几格"就改这里的 15，别在文件后面追加同名 .heatmap 覆盖。 */
.heatmap { display: grid; grid-template-columns: repeat(15, minmax(0, 1fr)); gap: 4px; margin-top: 8px; }
@media (max-width: 480px) { .heatmap { gap: 3px; } }   /* 窄屏只收紧间距，不改列数 */
.heat-cell {
  aspect-ratio: 1; border-radius: 4px;
  background: color-mix(in srgb, var(--cdr-muted) 14%, transparent);
  transition: transform .15s;
}
.heat-cell:hover { transform: scale(1.15); }
.heat-cell.l1 { background: color-mix(in srgb, var(--cdr-primary) 28%, transparent); }
.heat-cell.l2 { background: color-mix(in srgb, var(--cdr-primary) 48%, transparent); }
.heat-cell.l3 { background: color-mix(in srgb, var(--cdr-primary) 68%, transparent); }
.heat-cell.l4 { background: var(--cdr-primary); }
.heat-legend { display: flex; align-items: center; gap: 6px; font-size: .7rem; color: var(--cdr-muted); margin-top: 8px; }
.heat-legend i { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }

/* 百分位进度条 */
.percentile-bar { height: 8px; border-radius: 999px; background: color-mix(in srgb, var(--cdr-muted) 18%, transparent); overflow: hidden; margin-top: 6px; }
.percentile-fill { height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--cdr-primary), var(--cdr-accent)); transition: width .8s cubic-bezier(.22,1,.36,1); }

/* ---------------------------------------------------------------- 玩家搜索 */
.player-search { position: relative; }
/* 局部导航（assets/js/partial-nav.js）取数期间给内容区一个极轻的「正在换」提示 ——
   不能太明显，否则每次翻页都闪一下反而更晃眼。 */
.is-partial-loading { opacity: .55; transition: opacity .12s ease; }
/* ⚠️ 只给 .player-suggest 设 z-index 是**没用的** —— 站长 m17154「我搜索被遮挡住了」：
   页面上的 .reveal 带动画，带了 `will-change: opacity, transform`，**will-change 会创建
   层叠上下文**；搜索那张卡自己也是一个层叠上下文，于是下拉框的 z-index:40 只在
   「卡内」有效，压不过后面 DOM 顺序更靠后的统计卡（.stat.reveal 同样有 will-change）。
   修法：把**整张搜索卡**抬起来（position + z-index），让它的层叠上下文排在统计卡之前。 */
.card-search { position: relative; z-index: 3; }
.player-search-input { width: 100%; padding-right: 90px; }
.player-search-btn { position: absolute; right: 5px; top: 5px; }
.player-suggest {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 40;
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  border-radius: 14px; box-shadow: var(--cdr-shadow-lg); padding: 6px;
  max-height: 340px; overflow-y: auto; display: none;
}
.player-suggest.is-open { display: block; }
.suggest-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border-radius: 10px; color: inherit; cursor: pointer;
}
.suggest-item:hover, .suggest-item.is-active { background: color-mix(in srgb, var(--cdr-primary) 10%, transparent); opacity: 1; }
.suggest-item img { width: 30px; height: 30px; border-radius: 8px; image-rendering: pixelated; }
.suggest-item .n { font-weight: 600; }
.suggest-item .m { color: var(--cdr-muted); font-size: .76rem; }
.suggest-empty { padding: 14px; text-align: center; color: var(--cdr-muted); font-size: .86rem; }

/* ---------------------------------------------------------------- 图表（带 tooltip） */
.chart-wrap { position: relative; width: 100%; }
.cdr-chart-svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* 坐标轴用 HTML 渲染：SVG 是 preserveAspectRatio="none"（为保证折线填满宽度），
   若把文字放进 SVG，在手机上会被横向压扁到看不清 */
.chart-yaxis {
  position: absolute; left: 0; top: 16px; bottom: 0; width: 34px;
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: .68rem; color: var(--cdr-muted); text-align: right; padding-right: 5px;
  font-variant-numeric: tabular-nums; pointer-events: none;
}
.chart-xaxis {
  display: flex; justify-content: space-between;
  font-size: .68rem; color: var(--cdr-muted); padding: 4px 0 0 34px;
  font-variant-numeric: tabular-nums;
}
.chart-tip {
  position: absolute; display: none; z-index: 20; pointer-events: none;
  min-width: 148px; padding: 9px 11px;
  background: color-mix(in srgb, var(--cdr-surface) 96%, transparent);
  border: 1px solid var(--cdr-border); border-radius: 11px;
  box-shadow: var(--cdr-shadow-lg); backdrop-filter: blur(6px);
  font-size: .8rem; line-height: 1.5;
}
.chart-tip .ct-time { color: var(--cdr-muted); font-size: .72rem; margin-bottom: 5px; }
.chart-tip .ct-row { display: flex; align-items: center; gap: 7px; }
.chart-tip .ct-row i { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.chart-tip .ct-label { color: var(--cdr-muted); }
.chart-tip .ct-row strong { margin-left: auto; font-variant-numeric: tabular-nums; }
.chart-hit { touch-action: pan-y; }
/* 绘图区：左侧留出 Y 轴刻度宽度 */
.chart-plot { position: relative; margin-left: 34px; height: 100%; }
/* 双轴：右侧再放一列刻度（如 MSPT），绘图区右边留位置 */
.chart-wrap.has-right-axis .chart-plot { margin-right: 38px; }
.chart-wrap.has-right-axis .chart-xaxis { padding-right: 38px; }
.chart-yaxis-right {
  left: auto; right: 0; width: 34px;
  text-align: left; padding-right: 0; padding-left: 5px;
  color: color-mix(in srgb, var(--cdr-warning) 78%, var(--cdr-muted));
}
/* 手机上把图表压低一点，避免占满整屏 */
@media (max-width: 640px) {
  .chart-yaxis { font-size: .62rem; width: 30px; padding-right: 4px; }
  .chart-plot { margin-left: 30px; }
  .chart-xaxis { padding-left: 30px; font-size: .62rem; }
  .chart-tip { min-width: 132px; font-size: .76rem; padding: 8px 10px; }
  .chart-wrap.has-right-axis .chart-plot { margin-right: 30px; }
  .chart-wrap.has-right-axis .chart-xaxis { padding-right: 30px; }
  .chart-yaxis-right { width: 28px; padding-left: 3px; }
}

/* ---------------------------------------------------------------- 服务端模组列表 */
.mod-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 8px; max-height: 320px; overflow-y: auto;
  border: 1px solid var(--cdr-border); border-radius: 12px; padding: 10px;
}
.mod-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 9px;
  background: color-mix(in srgb, var(--cdr-muted) 6%, transparent);
  font-size: .84rem;
}
.mod-item .mod-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mod-item .mod-ver {
  flex-shrink: 0; font-size: .72rem; color: var(--cdr-muted);
  background: color-mix(in srgb, var(--cdr-muted) 12%, transparent);
  padding: 1px 7px; border-radius: 999px;
}
@media (max-width: 560px) {
  .mod-list { grid-template-columns: 1fr; max-height: 260px; }
}

/* ---------------------------------------------------------------- 网页地图 */
.map-shell {
  border: 1px solid var(--cdr-border); border-radius: var(--cdr-radius);
  overflow: hidden; background: var(--cdr-surface); box-shadow: var(--cdr-shadow);
}
.map-toolbar {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px; border-bottom: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-muted) 7%, transparent);
}
.map-frame-wrap { position: relative; width: 100%; height: min(74vh, 760px); background: var(--cdr-bg-dark, #080F22); }
.map-frame-wrap iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
@media (max-width: 640px) { .map-frame-wrap { height: 62vh; } }

/* ---------------------------------------------------------------- Bilibili 视频 */
.video-card { padding: 14px; overflow: hidden; }
.video-frame {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  border-radius: 12px; overflow: hidden; background: var(--cdr-bg-dark, #080F22);
}
.video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.video-frame-fallback { display: grid; place-items: center; background: color-mix(in srgb, var(--cdr-muted) 12%, transparent); }
.video-meta { padding: 12px 4px 2px; }
.video-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; }

/* ---------------------------------------------------------------- 后台：视频行编辑 */
.video-rows { display: flex; flex-direction: column; gap: 12px; }
.video-row {
  border: 1px solid var(--cdr-border); border-radius: 12px; padding: 12px;
  background: color-mix(in srgb, var(--cdr-muted) 5%, transparent);
  animation: fadeIn .3s ease both;
}
.video-row-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 9px; }
.video-row-del { color: var(--cdr-danger); }
.video-row-del:hover { background: color-mix(in srgb, var(--cdr-danger) 12%, transparent); color: var(--cdr-danger); }

/* ---------------------------------------------------------------- 滚动入场动画 */
/* 关键点：只有 JS 就绪（html.js-anim）时才把元素设为不可见，
   这样脚本失效或搜索引擎抓取时内容依然可见（不会闪、不会被判隐藏内容） */
html.js-anim .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .6s cubic-bezier(.22, 1, .36, 1), transform .6s cubic-bezier(.22, 1, .36, 1);
  transition-delay: var(--reveal-delay, 0ms);
  /* ⚠️ v1.9.76 —— 这里原来还有一行 `will-change: opacity, transform;`，删掉了。
     `.reveal` 这个 class 加上去之后**永远不摘除**（ui-motion.js:166-184 只补一个
     `.is-visible` 就收工，全仓没有一处 `remove('reveal')`），于是 /changelog 这种
     一屏上百个卡片/时间线的页面上，每个被标记的元素都**长期独占一个合成层**，
     手机 GPU 的图层预算直接爆掉，表现就是整页滚动发涩。
     opacity/transform 的过渡在它真正动起来的那一刻浏览器本来就会自动提升，
     这行提前声明是净负担。 */
}
html.js-anim .reveal.is-visible { opacity: 1; transform: none; }
html.js-anim .reveal-left { transform: translateX(-22px); }
html.js-anim .reveal-left.is-visible { transform: none; }
html.js-anim .reveal-scale { transform: scale(.96); }
html.js-anim .reveal-scale.is-visible { transform: none; }
@media (prefers-reduced-motion: reduce) {
  html.js-anim .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .card, .stat, .gallery-item { transition: none !important; }
}

/* 顶部进度条 —— v1.9.76：改走**合成属性**（scaleX），不再逐帧改 width。
   `width` 会触发布局 + 重绘（元素还是 position: fixed），原来那条
   `transition: width .1s linear` 更让过渡在整个滚动过程里一直处于活动状态。
   唯一可见差别：渐变现在铺满整条、由左往右揭开，而不是压缩在前 p% 里
   （2.5px 高的一条线，肉眼分不出来）。 */
.scroll-progress {
  position: fixed; top: 0; left: 0; height: 2.5px; width: 100%; z-index: 200;
  transform: scaleX(0); transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--cdr-primary), var(--cdr-primary-2), var(--cdr-accent));
  pointer-events: none;
}

/* 骨架屏 */
.skeleton-line {
  height: 12px; border-radius: 6px; margin-bottom: 9px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--cdr-muted) 12%, transparent) 25%,
      color-mix(in srgb, var(--cdr-muted) 22%, transparent) 37%,
      color-mix(in srgb, var(--cdr-muted) 12%, transparent) 63%);
  background-size: 400% 100%; animation: shimmer 1.4s ease infinite;
}

/* ---------------------------------------------------------------- 服务端设置标签 */
.game-info { margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--cdr-border); }
.game-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.game-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px; border-radius: 8px; font-size: .78rem;
  background: color-mix(in srgb, var(--cdr-muted) 11%, transparent);
  border: 1px solid var(--cdr-border); color: var(--cdr-text);
  transition: transform .15s, border-color .15s;
}
.game-chip:hover { transform: translateY(-1px); border-color: var(--cdr-primary); }
.game-chip strong { font-weight: 700; }
.game-chip.is-danger {
  background: color-mix(in srgb, var(--cdr-danger) 13%, transparent);
  border-color: color-mix(in srgb, var(--cdr-danger) 35%, transparent);
  color: var(--cdr-danger); font-weight: 600;
}

/* ---------------------------------------------------------------- ALTCHA 人机验证 */
.altcha-wrap { margin: 12px 0; }
.altcha-wrap altcha-widget {
  --altcha-max-width: 100%;
  --altcha-border-radius: 12px;
  display: block;
  font-family: inherit;
}
.altcha-loading {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 14px; border-radius: 12px;
  border: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-muted) 7%, transparent);
}
.altcha-spinner {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--cdr-primary) 30%, transparent);
  border-top-color: var(--cdr-primary);
  animation: altchaSpin .8s linear infinite;
  flex-shrink: 0;
}
@keyframes altchaSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .altcha-spinner { animation-duration: 2s; } }

/* 手机上下拉菜单：必须保持"浮层"定位，不能被 flex 布局拉成竖列 */
@media (max-width: 680px) {
  .dropdown { position: relative; }
  .dropdown-menu {
    position: absolute !important;
    top: calc(100% + 8px) !important;
    right: 0 !important;
    left: auto !important;
    min-width: 168px;
    max-width: calc(100vw - 28px);
    /* 防止被父容器裁掉 */
    z-index: 90;
  }
  /* 靠右边的下拉（语言/主题）在窄屏上不要超出屏幕 */
  .header-tools .dropdown:nth-last-child(-n+3) .dropdown-menu { right: 0; }
  .dropdown-head { padding: 8px 12px 9px; }
}

/* ---------------------------------------------------------------- 空状态 */
.empty {
  text-align: center; padding: 54px 20px; color: var(--cdr-muted);
}
.empty-icon { font-size: 2.6rem; margin-bottom: 12px; opacity: .6; }

/* ---------------------------------------------------------------- 页脚 */
.cdr-footer { margin-top: 60px; border-top: 1px solid var(--cdr-border); background: color-mix(in srgb, var(--cdr-muted) 6%, transparent); }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 30px; padding: 44px 0 30px; }
@media (max-width: 980px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; gap: 22px; } }
.footer-col h4 { font-size: .84rem; text-transform: uppercase; letter-spacing: .1em; color: var(--cdr-muted); margin-bottom: 14px; }
.footer-desc { color: var(--cdr-muted); font-size: .87rem; }
.footer-links { list-style: none; padding: 0; margin: 0; }
.footer-links li { margin-bottom: 9px; font-size: .87rem; color: var(--cdr-muted); }
.footer-links a { color: var(--cdr-text); }
.footer-links a:hover { color: var(--cdr-primary); }
.footer-badges { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.footer-bottom { border-top: 1px solid var(--cdr-border); padding: 18px 0; }
.footer-bottom-inner { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; align-items: center; }
.footer-legal { display: flex; gap: 16px; flex-wrap: wrap; font-size: .8rem; color: var(--cdr-muted); align-items: center; }
.footer-legal a { color: var(--cdr-muted); }
.police-link { display: inline-flex; align-items: center; gap: 5px; }
.footer-note { font-size: .76rem; color: var(--cdr-muted); max-width: 560px; }

/* ---------------------------------------------------------------- 浮动按钮 */
.to-top {
  position: fixed; right: 24px; bottom: 24px; z-index: 80;
  width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
  background: var(--cdr-surface); border: 1px solid var(--cdr-border);
  color: var(--cdr-text); cursor: pointer; box-shadow: var(--cdr-shadow);
  opacity: 0; visibility: hidden; transition: .22s;
}
.to-top.is-visible { opacity: 1; visibility: visible; }
.to-top:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }

.copy-toast {
  position: fixed; left: 50%; bottom: 34px; transform: translate(-50%, 20px);
  background: var(--cdr-text); color: var(--cdr-bg); padding: 10px 22px;
  border-radius: 999px; font-size: .88rem; font-weight: 600; z-index: 300;
  opacity: 0; pointer-events: none; transition: .25s;
}
.copy-toast.is-show { opacity: 1; transform: translate(-50%, 0); }

/* ---------------------------------------------------------------- 图表 */
.chart-box { position: relative; width: 100%; height: 220px; }
.chart-box svg { width: 100%; height: 100%; overflow: visible; }
.chart-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: .8rem; color: var(--cdr-muted); margin-top: 10px; }
.chart-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 6px; }

/* ---------------------------------------------------------------- 工具类 */
.skeleton {
  background: linear-gradient(90deg, color-mix(in srgb, var(--cdr-muted) 12%, transparent) 25%,
    color-mix(in srgb, var(--cdr-muted) 20%, transparent) 37%, color-mix(in srgb, var(--cdr-muted) 12%, transparent) 63%);
  background-size: 400% 100%; animation: shimmer 1.4s ease infinite; border-radius: 8px;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

.fade-in { animation: fadeIn .45s ease both; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

@media print { .cdr-header, .cdr-footer, .to-top { display: none; } }

/* ==========================================================================
   手机端优化（≤680px 为主要断点，兼顾 360px 小屏）
   ========================================================================== */

/* --- 全局：防止任何元素撑破视口 ---
   这里必须用 clip 而不是 hidden：overflow-x:hidden 会让 body 变成**滚动容器**，
   于是 .cdr-header 的 position:sticky 只相对 body 生效 —— 整站吸顶导航会失效。
   手机上头部一滚走，挂在头部里的 fixed 菜单就错位、压住登录/注册卡片。
   clip 的裁切效果一样，但不会创建滚动容器。
   （实测：改成 clip 后头部滚动 400px 仍然是 top=0。） */
html, body.cdr-body { max-width: 100%; }
body.cdr-body { overflow-x: clip; }

/* 注意：下面两条用 :where() 把优先级降到 0，
   否则会盖掉组件自己的 min-width（下拉菜单曾被压成 40px 宽、文字一字一行）。
   低优先级 = 只作为"没人管时的默认值"，需要时组件样式依然能覆盖。 */
:where(.cdr-body *) { min-width: 0; }          /* flex/grid 子项默认允许收缩 */
.cdr-body img, .cdr-body svg, .cdr-body video, .cdr-body iframe { max-width: 100%; }
:where(.cdr-body a, .cdr-body p, .cdr-body li, .cdr-body td, .cdr-body th,
       .cdr-body h1, .cdr-body h2, .cdr-body h3, .cdr-body h4) {
  overflow-wrap: anywhere;                     /* 长玩家名/长链接不再撑破布局 */
  word-break: break-word;
}
.mono, code, pre, .ip-value { overflow-wrap: anywhere; }

/* 下拉菜单项：绝不允许折行（否则窄屏会一字一行） */
.dropdown-menu { width: max-content; max-width: min(320px, calc(100vw - 24px)); }
.dropdown-item {
  white-space: nowrap; overflow-wrap: normal; word-break: keep-all;
  overflow: hidden; text-overflow: ellipsis;
}

/* --- 触摸目标：可点击元素不小于 44px（WCAG 2.2 AA） --- */
@media (max-width: 680px) {
  .btn { min-height: 42px; padding: 11px 18px; }
  .btn-sm { min-height: 38px; padding: 9px 14px; }
  .icon-btn { width: 42px; height: 42px; }
  .dropdown-item { padding: 12px 14px; min-height: 44px; }
  .main-nav a { padding: 14px; min-height: 48px; display: flex; align-items: center; }
  .like-btn { min-height: 38px; padding: 8px 14px; }
  .pagination a, .pagination span { min-width: 42px; height: 42px; }
  /* 原来这里是 .heat-cell { min-height: 26px } —— 已删。
     格子有 aspect-ratio:1，min-height 会反过来把**宽度**顶到 26px，而 auto-fill
     在窄屏只算得出 22.6px 轨道 → 热力图横向溢出 3px（390px 实测）。
     真要让格子够大就改 .heatmap 的列数，不要用 min-height。 */
  input, select, textarea { font-size: 16px !important; }  /* 防止 iOS 聚焦时自动放大 */
}

/* --- 表格：手机上横向滚动 + 可见提示 + 粘性首列 --- */
@media (max-width: 680px) {
  .table-wrap {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    position: relative;
    background-image:
      linear-gradient(to right, var(--cdr-surface) 30%, transparent),
      linear-gradient(to left, var(--cdr-surface) 30%, transparent),
      radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.10), transparent),
      radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.10), transparent);
    background-position: 0 0, 100% 0, 0 0, 100% 0;
    background-repeat: no-repeat;
    background-size: 34px 100%, 34px 100%, 12px 100%, 12px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  table.cdr-table { font-size: .84rem; }
  table.cdr-table th, table.cdr-table td { padding: 10px 12px; white-space: nowrap; }
  table.cdr-table th:first-child, table.cdr-table td:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--cdr-surface);
    box-shadow: 1px 0 0 var(--cdr-border);
  }
  table.cdr-table thead th:first-child { z-index: 3; }
  /* 上面那条 nowrap 只对"包在 .table-wrap 里"的表格有效果（wrap 负责横向滚动）。
     漏包 .table-wrap、直接放在卡片里的表格（玩家详情「详细信息」那张）会被 nowrap
     顶出卡片 —— 实测 390px 下表格宽 352px，比卡片内容区宽 24px。
     这里给它们同样的横向滚动兜底，照抄本文件 .prose table 的写法
     （display:block + overflow-x:auto）。只在 ≤680px 生效，桌面端不受影响；
     包了 .table-wrap 的表格不受影响（它们不是 .card 的直接子元素）。 */
  .card > table.cdr-table { display: block; max-width: 100%; overflow-x: auto; }
  /* 表格上方给个滑动提示 */
  .table-scroll-hint { display: flex !important; }
}
.table-scroll-hint {
  display: none; align-items: center; gap: 6px;
  font-size: .74rem; color: var(--cdr-muted);
  /* 站长 m16473：这行字原来是 `padding: 7px 2px 0`（下边距 0），
     紧紧贴在表格上边框上，看着像"和表格叠在一起"。
     下边留 12px，让它明确属于上面那排搜索/勾选控件，而不是表格的一部分；
     左边再缩进 4px，跟表格左边缘错开，不至于两行起点齐平像同一块。 */
  padding: 7px 2px 12px 6px;
}

/* --- 热力图：**这一段不再定义任何 .heatmap 规则**（原来是溢出源之一）---
   原先这里有 repeat(auto-fill, minmax(26px, 1fr))，同优先级又写在后面，
   把 889 行"15 列 × 2 行"的设计整条压掉：宽屏变 1 行 30 格、1280 变 2 行
   （20 + 10 的残缺行）、390 变 3 行（14 + 14 + 2）。列数现在只在 .heatmap
   （889 行附近）定义一次，窄屏间距也跟着那条规则走。 */

/* --- 每日柱状图：手机上降低高度 --- */
@media (max-width: 480px) {
  .day-bars { height: 70px; gap: 2px; }
  .day-bar { min-width: 2px; }
}

/* --- 卡片与间距：手机上更紧凑 --- */
@media (max-width: 680px) {
  .section { padding: 34px 0; }
  .hero { padding: 42px 0 38px; }
  .hero h1 { font-size: 1.6rem; }
  .hero-lead { font-size: .96rem; }
  /* v1.8.2：首屏按钮从 2 个变成 3 个后，手机上会被挤到视口外。
     body 上的 overflow-x: clip 会把它悄悄裁掉（不出现滚动条，所以更隐蔽），
     所以这里明确换行、并且不拉伸，宁可两行也不要被裁。 */
  .hero-actions { flex-wrap: wrap; }
  .hero-actions .btn { flex: 0 1 auto; }
  .card { padding: 16px; }
  .card-head { flex-wrap: wrap; gap: 8px; }
  .page-head { padding: 26px 0 20px; }
  .page-head h1 { font-size: 1.45rem; }
  .stat-value { font-size: 1.5rem; }
  .stat { padding: 14px; }
  .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 9px; }
  .stat-mini { padding: 10px 12px; }
  .stat-mini .v { font-size: 1.08rem; }
  .section-head { margin-bottom: 18px; }
  .footer-grid { padding: 30px 0 20px; }
  .footer-bottom-inner { flex-direction: column; align-items: flex-start; gap: 10px; }
  .map-frame-wrap { height: 56vh; }
  .video-frame { border-radius: 10px; }
  .bind-code { font-size: 1.7rem; letter-spacing: .18em; padding: 13px; }
  .thread-row, .forum-cat { padding: 13px 14px; gap: 11px; }
  .forum-cat-stat { display: none; }           /* 手机上只看版块名，省空间 */
  .thread-stat { min-width: 48px; }
  .post { padding: 14px; }
  .prose pre { font-size: .78rem; padding: 12px 13px; }
  .prose table { display: block; overflow-x: auto; }
  /* 首页视频/相册区块：手机上只显示第一个，避免过长 */
  .hero-card { padding: 18px; }
  .hero-stat-num { font-size: 2.1rem; }
}

/* --- 超小屏（≤360px） --- */
@media (max-width: 360px) {
  .cdr-container { width: calc(100% - 20px); }
  .brand-logo { height: 30px; }
  .stats-grid { grid-template-columns: 1fr; }
  .btn { font-size: .88rem; }
  /* 这里原来还有一条 .heatmap{repeat(auto-fill,minmax(20px,1fr))} 覆盖 —— 已删。
     列数只由 .heatmap 一处决定；15 列在 320px 宽的容器里也不会溢出。 */
}

/* --- 手机端导航：可横向滚动的分类栏（论坛/相册等）--- */
@media (max-width: 680px) {
  .scroll-x {
    display: flex; gap: 8px; overflow-x: auto; -webkit-overflow-scrolling: touch;
    padding-bottom: 6px; margin-inline: -14px; padding-inline: 14px;
    scrollbar-width: none;
  }
  .scroll-x::-webkit-scrollbar { display: none; }
  .scroll-x > * { flex: 0 0 auto; }
}

/* --- 玩家搜索建议：手机上占满宽度 --- */
@media (max-width: 680px) {
  .player-suggest { max-height: 60vh; }
  .player-search-input { padding-right: 74px; }
  .player-search-btn { right: 4px; top: 4px; }
}

/* --- 后台在手机上 --- */
@media (max-width: 980px) {
  .admin-content { padding: 14px; }
  .admin-topbar { padding: 10px 12px; flex-wrap: wrap; gap: 8px; }
  .admin-page-title { font-size: 1.05rem; }
  .admin-page-action { order: 3; width: 100%; }
  .admin-footer { flex-direction: column; gap: 5px; font-size: .74rem; padding: 12px; }
  .form-grid { grid-template-columns: 1fr; }
  .video-row .form-grid { grid-template-columns: 1fr; }
}
/* ---------------------------------------------------------------- 服务器信息事实表（指南/关于页） */
.facts-card, .mods-card, .cmd-card {
  border: 1px solid var(--cdr-border); border-radius: var(--cdr-radius);
  background: var(--cdr-surface); padding: 16px 18px; margin: 18px 0;
  box-shadow: var(--cdr-shadow);
}
.facts-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.facts-head strong { font-size: 1rem; }
/* ── 指令表顶上的「玩家常用指令」块（人写的，下面是模组导出的全表）────── */
.cmd-players {
  margin: 0 0 14px; padding: 12px 14px; border-radius: 12px;
  background: color-mix(in srgb, var(--cdr-muted) 7%, transparent);
  border: 1px solid var(--cdr-border);
}
.cmd-players > summary { cursor: pointer; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; list-style: none; }
.cmd-players > summary::-webkit-details-marker { display: none; }
.cmd-players > summary::after { content: '▾'; margin-left: auto; color: var(--cdr-muted); transition: transform .18s; }
.cmd-players[open] > summary { margin-bottom: 10px; }
.cmd-players[open] > summary::after { transform: rotate(180deg); }
.cmd-players h4 { margin: 14px 0 6px; font-size: .92rem; }
.cmd-players h4:first-of-type { margin-top: 2px; }
.cmd-players .cdr-table { margin-bottom: 4px; }
@media (prefers-reduced-motion: reduce) { .cmd-players > summary::after { transition: none; } }
.facts-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
.fact { padding: 10px 12px; border-radius: 11px; background: color-mix(in srgb, var(--cdr-muted) 6%, transparent); }
.fact-l { font-size: .74rem; color: var(--cdr-muted); }
.fact-v { font-weight: 700; font-size: .95rem; display: flex; align-items: center; gap: 6px; }
.fact-n { font-size: .72rem; color: var(--cdr-muted); margin-top: 2px; }
@media (max-width: 560px) {
  .facts-grid { grid-template-columns: 1fr 1fr; }
  .facts-card, .mods-card, .cmd-card { padding: 14px; }
}
/* ---------------------------------------------------------------- 个人中心「我的角色」 */
.chars-list { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.char-row {
  display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  border: 1px solid var(--cdr-border); border-radius: 12px; background: var(--cdr-surface);
}
.char-row.is-primary { border-color: color-mix(in srgb, var(--cdr-primary) 45%, var(--cdr-border)); background: color-mix(in srgb, var(--cdr-primary) 5%, var(--cdr-surface)); }
.char-main { flex: 1; min-width: 0; }
.char-name { font-weight: 700; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.char-meta { margin-top: 2px; }
.char-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.char-actions form { display: inline; margin: 0; }
@media (max-width: 640px) {
  .char-row { flex-wrap: wrap; }
  .char-actions { width: 100%; }
}
/* ---------------------------------------------------------------- 建筑相册：大图模式（无文字描述） */
.gallery-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
@media (min-width: 1000px) { .gallery-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; } }
@media (max-width: 640px) { .gallery-grid { grid-template-columns: 1fr; gap: 12px; } }
.gallery-item { margin: 0; }
.gallery-thumb {
  position: relative; overflow: hidden; border-radius: 16px;
  border: 1px solid var(--cdr-border); background: var(--cdr-surface);
  aspect-ratio: 16 / 10;
}
.gallery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .35s ease; }
.gallery-thumb:hover img { transform: scale(1.035); }
.gallery-cap {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 26px 14px 10px;
  color: #fff; font-weight: 700; font-size: .95rem; letter-spacing: .2px;
  background: linear-gradient(to top, rgba(0,0,0,.72), rgba(0,0,0,0));
  opacity: 0; transition: opacity .25s ease; pointer-events: none;
}
.gallery-thumb:hover .gallery-cap { opacity: 1; }
/* 相册页的卡片是"图上的浮层"（首页那张用的是 figcaption），作者只能跟在标题后面 */
.gallery-cap .gallery-cap-by { font-style: normal; font-weight: 500; font-size: .82rem; opacity: .78; margin-left: 8px; }
.gallery-tools { position: absolute; top: 10px; right: 10px; display: flex; gap: 6px; align-items: center;
  opacity: 0; transition: opacity .25s ease; }
.gallery-thumb:hover .gallery-tools { opacity: 1; }
.gallery-tools form { margin: 0; display: inline; }
.gallery-tools .like-btn, .gallery-tools .btn {
  background: rgba(0,0,0,.55); color: #fff; border: 0; border-radius: 9px; padding: 4px 9px; backdrop-filter: blur(4px);
}
@media (hover: none) {
  .gallery-cap, .gallery-tools { opacity: 1; }
}
/* ---------------------------------------------------------------- 服务器状态页（总览 / 服务 / 节点） */
.ov-card {
  border: 1px solid var(--cdr-border); border-radius: 18px; padding: 20px 22px;
  background:
    radial-gradient(1200px 220px at 8% -40%, color-mix(in srgb, var(--cdr-primary) 16%, transparent), transparent 70%),
    linear-gradient(180deg, color-mix(in srgb, var(--cdr-primary) 5%, var(--cdr-surface)), var(--cdr-surface));
  box-shadow: var(--cdr-shadow);
}
.ov-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.ov-title { display: flex; align-items: center; gap: 14px; }
.ov-logo {
  width: 46px; height: 46px; border-radius: 14px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--cdr-primary) 14%, transparent);
  color: var(--cdr-primary); font-size: 1.3rem;
}
.ov-name { font-size: 1.42rem; font-weight: 800; letter-spacing: .2px; }
.ov-sub { display: flex; align-items: center; gap: 6px; font-size: .86rem; margin-top: 3px; color: var(--cdr-muted); }
.ov-when { text-align: right; font-size: .8rem; color: var(--cdr-muted); }
.ov-when strong { display: block; font-size: 1rem; color: var(--cdr-text); }
.ov-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-top: 18px; }
.ov-tile {
  padding: 13px 15px; border-radius: 14px; border: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-surface) 82%, transparent);
}
.ov-tile.is-ok { border-color: color-mix(in srgb, var(--cdr-success) 40%, var(--cdr-border)); }
.ov-tile-l { font-size: .76rem; color: var(--cdr-muted); }
.ov-tile-v { font-size: 1.6rem; font-weight: 800; line-height: 1.2; margin-top: 2px; }
.ov-tile-v small { font-size: .78rem; font-weight: 600; color: var(--cdr-muted); margin-left: 4px; }
.ov-tile-n { font-size: .72rem; color: var(--cdr-muted); margin-top: 2px; }

.panel {
  border: 1px solid var(--cdr-border); border-radius: 18px; background: var(--cdr-surface);
  padding: 18px 20px; margin-top: 18px; box-shadow: var(--cdr-shadow);
}
.panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
.panel-head h3 { margin: 0; font-size: 1.12rem; }
.panel-head .sub { margin: .25em 0 0; color: var(--cdr-muted); font-size: .83rem; }

.avail-strip { display: flex; gap: 3px; align-items: stretch; height: 40px; }
.avail-cell { flex: 1 1 0; border-radius: 4px; min-width: 4px; background: var(--cdr-border); transition: transform .12s ease; }
.avail-cell:hover { transform: scaleY(1.08); }
.avail-cell.is-ok { background: var(--cdr-success); }
.avail-cell.is-warn { background: var(--cdr-warning); }
.avail-cell.is-down { background: var(--cdr-danger); }
.avail-cell.is-none { background: color-mix(in srgb, var(--cdr-muted) 26%, transparent); }
.avail-axis { display: flex; justify-content: space-between; font-size: .72rem; color: var(--cdr-muted); margin-top: 6px; }

.svc-list { margin-top: 14px; display: flex; flex-direction: column; }
.svc-row {
  display: grid; grid-template-columns: minmax(180px, 1.2fr) 120px minmax(180px, 2fr);
  gap: 12px; align-items: center; padding: 11px 4px; border-top: 1px solid var(--cdr-border);
}
.svc-row:first-child { border-top: 0; }
.svc-name { display: flex; align-items: center; gap: 10px; }

.node-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.node-card {
  display: block; color: inherit; text-decoration: none;
  border: 1px solid var(--cdr-border); border-radius: 16px; padding: 16px 18px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--cdr-muted) 4%, transparent), transparent);
  transition: border-color .18s ease, transform .18s ease;
}
.node-card:hover { border-color: color-mix(in srgb, var(--cdr-primary) 45%, var(--cdr-border)); transform: translateY(-2px); }
.node-head { display: flex; align-items: center; gap: 10px; }
.node-icon {
  width: 36px; height: 36px; border-radius: 11px; display: grid; place-items: center; font-size: 1.05rem;
  background: color-mix(in srgb, var(--cdr-muted) 12%, transparent);
}
.node-icon.lg { width: 52px; height: 52px; font-size: 1.5rem; border-radius: 15px; }
.node-icon.accent-blue { background: color-mix(in srgb, var(--cdr-primary) 14%, transparent); }
.node-icon.accent-green { background: color-mix(in srgb, var(--cdr-success) 16%, transparent); }
.node-title { display: flex; align-items: center; gap: 7px; flex: 1; }
.node-more { font-size: .78rem; color: var(--cdr-muted); }
.node-desc { margin: 8px 0 12px; }
.node-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 16px; }
.node-metrics.big { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px 18px; }
.nm-l { font-size: .74rem; color: var(--cdr-muted); }
.nm-v { font-weight: 700; font-size: 1rem; }
.nm-bar { height: 5px; border-radius: 999px; background: color-mix(in srgb, var(--cdr-muted) 18%, transparent); margin-top: 4px; overflow: hidden; }
.nm-bar span { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--cdr-primary), var(--cdr-primary-2)); }
.node-facts { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 12px; font-size: .78rem; }
.node-facts em { font-style: normal; color: var(--cdr-muted); margin-right: 4px; }

.node-hero { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 10px; }
.node-switch { margin-left: auto; }
.fact-table { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 22px; }
.fact-row { display: flex; justify-content: space-between; gap: 12px; padding: 9px 2px; border-bottom: 1px solid var(--cdr-border); }
.fact-k { color: var(--cdr-muted); font-size: .86rem; }
.fact-v2 { font-weight: 600; }

.raw-wrap { margin: 10px 0; }
.raw-wrap summary { cursor: pointer; font-size: .82rem; color: var(--cdr-muted); }
.dot-warn { background: var(--cdr-warning); }

@media (max-width: 900px) {
  .ov-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .node-grid, .node-metrics.big { grid-template-columns: 1fr; }
  .svc-row { grid-template-columns: 1fr; gap: 6px; }
  .fact-table { grid-template-columns: 1fr; }
}
/* ---------------------------------------------------------------- 小工具页 */
.tool-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.tool-chip {
  padding: 7px 14px; border-radius: 999px; border: 1px solid var(--cdr-border);
  background: var(--cdr-surface); color: inherit; text-decoration: none; font-size: .86rem;
}
.tool-chip:hover { border-color: color-mix(in srgb, var(--cdr-primary) 50%, var(--cdr-border)); color: var(--cdr-primary); }
.tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.tool-field { display: flex; flex-direction: column; gap: 5px; font-size: .84rem; color: var(--cdr-muted); }
.tool-inline { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.tool-inline .form-input { flex: 1 1 260px; }
.tool-out { margin-top: 12px; }
.tool-hint { font-size: .84rem; color: var(--cdr-muted); margin-top: 10px; }
.chip-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.tool-msg { padding: 10px 12px; border-radius: 12px; border: 1px solid var(--cdr-border); background: color-mix(in srgb, var(--cdr-muted) 6%, transparent); font-size: .9rem; }
.tool-msg.is-ok { border-color: color-mix(in srgb, var(--cdr-success) 45%, var(--cdr-border)); }
.tool-msg.is-warn { border-color: color-mix(in srgb, var(--cdr-warning) 45%, var(--cdr-border)); }
.tool-msg.is-bad { border-color: color-mix(in srgb, var(--cdr-danger) 45%, var(--cdr-border)); }
.tool-msg.is-loading { display: flex; align-items: center; gap: 8px; }

/* v6：状态图标改用 CSS 画（原来是 JS 字符串里拼 ❌/✅/⚠️，缺 emoji 字体的机器上会显示方框）。
   放在样式层的好处：JS 只负责文本，图标怎么画由 CSS 决定。 */
.tool-msg.is-bad, .tool-msg.is-ok, .tool-msg.is-warn { position: relative; padding-left: 34px; }
.tool-msg.is-bad::before, .tool-msg.is-ok::before, .tool-msg.is-warn::before {
  content: ''; position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px;
  background-repeat: no-repeat; background-position: center; background-size: 16px 16px;
}
.tool-msg.is-ok::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231B2F5E' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M8 12.2l2.6 2.6L16 9.4'/%3E%3C/svg%3E");
}
.tool-msg.is-warn::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C2820F' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3l9 17H3L12 3zM12 9v5M12 17h.01'/%3E%3C/svg%3E");
}
.tool-msg.is-bad::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23D0453C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M9 9l6 6M15 9l-6 6'/%3E%3C/svg%3E");
}
.spinner { width: 13px; height: 13px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--cdr-primary) 30%, transparent); border-top-color: var(--cdr-primary); animation: cdr-spin .8s linear infinite; }
@keyframes cdr-spin { to { transform: rotate(360deg); } }

.gc-preview {
  margin: 14px 0; padding: 16px; border-radius: 14px; text-align: center;
  font-size: 1.5rem; font-weight: 700; letter-spacing: 1px;
  background: #101318; border: 1px solid var(--cdr-border);
  font-family: var(--cdr-font-mono, ui-monospace, Consolas, monospace);
}
.gc-out { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; }
.gc-block { border: 1px solid var(--cdr-border); border-radius: 12px; padding: 10px 12px; }
.gc-block-h { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; font-size: .86rem; }
.code-block {
  margin: 0; padding: 10px; border-radius: 9px; background: color-mix(in srgb, var(--cdr-muted) 10%, transparent);
  font-size: .78rem; max-height: 130px; overflow: auto; white-space: pre-wrap; word-break: break-all;
  font-family: var(--cdr-font-mono, ui-monospace, Consolas, monospace);
}
.code-block:empty::before { content: '（暂无内容）'; color: var(--cdr-muted); }

.ping-card { border: 1px solid var(--cdr-border); border-radius: 14px; padding: 14px 16px; }
.ping-head { display: flex; align-items: center; gap: 12px; }
.ping-icon { width: 52px; height: 52px; border-radius: 10px; image-rendering: pixelated; }
.ping-icon.ph {
  display: block; font-size: 0;
  background-color: rgba(107, 123, 120, .12);
  background-color: color-mix(in srgb, var(--cdr-muted) 12%, transparent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235C6E8F' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20l8-8'/%3E%3Cpath d='M12 12c1.5-4 4-6 8-6-1 3-2 4.5-4 6z'/%3E%3Cpath d='M7 7c2.5-1 5-1 7 0'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: center; background-size: 24px 24px;
}
.ping-motd { font-weight: 700; font-size: 1.05rem; line-height: 1.4; }
.ping-lat { margin-left: auto; font-size: 1.5rem; font-weight: 800; }
.ping-lat small { font-size: .7rem; color: var(--cdr-muted); margin-left: 2px; }
.ping-stats { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 12px; font-size: .92rem; }
.ping-stats em { font-style: normal; color: var(--cdr-muted); margin-right: 6px; font-size: .8rem; }
.ping-sample { margin-top: 8px; }

.pl-list { display: flex; flex-direction: column; }
.pl-row {
  display: grid; grid-template-columns: 1.4fr 1fr auto; gap: 10px; align-items: center;
  padding: 9px 4px; border-bottom: 1px solid var(--cdr-border); color: inherit; text-decoration: none;
}
.pl-row:hover { background: color-mix(in srgb, var(--cdr-primary) 5%, transparent); }
.pl-name { font-weight: 600; }

.code-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.code-chip {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--cdr-border); background: #14171c; text-align: left;
}
.code-chip:hover { border-color: color-mix(in srgb, var(--cdr-primary) 50%, var(--cdr-border)); }
.code-chip-k { font-family: var(--cdr-font-mono, monospace); font-weight: 700; }
.code-chip-n { font-size: .8rem; color: #cfd6e4; }
.code-chip-v { margin-left: auto; font-size: .7rem; color: var(--cdr-muted); }
@media (max-width: 640px) {
  .gc-out { grid-template-columns: 1fr; }
  .pl-row { grid-template-columns: 1fr; gap: 2px; }
}
/* ---------------------------------------------------------------- 状态页：服务器设置与在线玩家 */
.chip-row { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
.game-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px;
  border-radius: 999px; border: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-muted) 6%, transparent);
  font-size: .8rem; white-space: nowrap;
}
.online-strip { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.online-pill {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px 4px 4px;
  border-radius: 999px; border: 1px solid var(--cdr-border);
  background: var(--cdr-surface); color: inherit; text-decoration: none; font-size: .84rem;
}
.online-pill:hover { border-color: color-mix(in srgb, var(--cdr-success) 45%, var(--cdr-border)); }
.online-pill img { border-radius: 6px; image-rendering: pixelated; }
/* ---------------------------------------------------------------- 注册 / 登录 / 白名单 */
.auth-section { display: flex; justify-content: center; padding: 44px 16px 60px; }
.auth-card { width: 100%; max-width: 460px; padding: 28px 26px; }
.auth-head { text-align: center; margin-bottom: 18px; }
.auth-head h1 { font-size: 1.5rem; margin: 0 0 6px; }
.auth-head .sub { color: var(--cdr-muted); font-size: .88rem; margin: 0; }
.auth-steps { display: flex; gap: 8px; justify-content: center; margin-bottom: 16px; }
.auth-step {
  font-size: .76rem; padding: 4px 12px; border-radius: 999px;
  border: 1px solid var(--cdr-border); color: var(--cdr-muted);
}
.auth-step.is-on { border-color: color-mix(in srgb, var(--cdr-primary) 50%, var(--cdr-border)); color: var(--cdr-primary); font-weight: 600; }
.auth-tabs { display: flex; gap: 8px; margin-bottom: 14px; }
.auth-tab {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px; border-radius: 11px; border: 1px solid var(--cdr-border); cursor: pointer; font-size: .88rem;
}
.auth-tab input { display: none; }
.auth-tab.is-on { border-color: var(--cdr-primary); color: var(--cdr-primary); background: color-mix(in srgb, var(--cdr-primary) 8%, transparent); }
.auth-tab.is-disabled { opacity: .5; cursor: not-allowed; }
.auth-inline { display: flex; gap: 8px; align-items: stretch; }
.auth-inline .form-input { flex: 1; }
.auth-foot { margin-top: 16px; text-align: center; font-size: .86rem; color: var(--cdr-muted); }
.app-row { padding: 9px 0; border-bottom: 1px solid var(--cdr-border); }
.app-row:last-child { border-bottom: 0; }
.app-main strong { font-size: .95rem; }
.wl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.wl-chip {
  padding: 3px 9px; border-radius: 8px; font-size: .78rem;
  background: color-mix(in srgb, var(--cdr-muted) 10%, transparent);
}
@media (max-width: 860px) {
  .grid[style*="grid-template-columns"] { grid-template-columns: 1fr !important; }
}
/* ---------------------------------------------------------------- 前台管理面板（半圆悬浮球 + 抽屉） */
.mgr-fab {
  position: fixed; right: 0; bottom: 84px; z-index: 60;
  width: 54px; height: 84px; padding: 0; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--cdr-primary) 35%, var(--cdr-border));
  border-right: 0; border-radius: 42px 0 0 42px;   /* 左半圆 */
  background: linear-gradient(160deg, var(--cdr-primary), color-mix(in srgb, var(--cdr-primary) 45%, var(--cdr-primary-2)));
  color: #fff; box-shadow: -6px 8px 26px -12px color-mix(in srgb, var(--cdr-primary) 70%, transparent);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  transition: transform .18s ease, width .18s ease;
}
.mgr-fab:hover { transform: translateX(-3px); }
.mgr-fab.is-open { width: 44px; }
.mgr-fab-icon { font-size: 1.25rem; line-height: 1; }
.mgr-fab-badge {
  position: absolute; top: 10px; right: 8px; min-width: 18px; height: 18px; padding: 0 4px;
  border-radius: 999px; background: var(--cdr-danger); color: #fff; font-size: .68rem; font-weight: 700;
  display: grid; place-items: center;
}
/* ⚠️ 关键：上面那句 `display: grid` 的优先级**高于**浏览器默认的 `[hidden] { display: none }`，
 * 所以光靠 JS 加 hidden 属性是藏不住的 —— 没有待审申请时，红点会照样渲染出来（显示 0）。
 * 站长就是因为这个以为"红点关不掉"。必须显式写一条 [hidden] 规则。
 * 注意：这里不能用 `display: none` 之外的写法（比如 visibility），那会留下占位。 */
.mgr-fab-badge[hidden], .mgr-dot[hidden] { display: none !important; }
.mgr-drawer[hidden], .mgr-pane[hidden] { display: none !important; }
.mgr-drawer {
  position: fixed; right: 0; top: 0; bottom: 0; width: min(460px, 94vw); z-index: 70;
  background: var(--cdr-surface); border-left: 1px solid var(--cdr-border);
  box-shadow: -18px 0 48px -24px rgba(15, 23, 42, .35);
  display: flex; flex-direction: column;
}
.mgr-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 16px 18px; border-bottom: 1px solid var(--cdr-border); }
.mgr-tabs { display: flex; gap: 6px; padding: 10px 14px 0; }
.mgr-tab {
  flex: 1; padding: 8px 10px; border: 1px solid var(--cdr-border); border-bottom: 0;
  border-radius: 10px 10px 0 0; background: color-mix(in srgb, var(--cdr-muted) 6%, transparent);
  cursor: pointer; font-size: .84rem; color: var(--cdr-muted);
}
.mgr-tab.is-on { background: var(--cdr-surface); color: var(--cdr-text); font-weight: 700; }
.mgr-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--cdr-danger); margin-left: 4px; }

/* 面板顶部「更多待审」：相册 / 皮肤 / 论坛不在这个抽屉里审，各有现成的后台页，
   但角标把它们算进去了 —— 所以必须给出可点的入口（站长 m15841 第 1 件）。 */
.mgr-jump {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: 8px 14px; border-bottom: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-muted) 5%, transparent);
}
.mgr-jump-label { font-size: .76rem; color: var(--cdr-muted); margin-right: 2px; }
.mgr-jump-link {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px; border-radius: 999px; border: 1px solid var(--cdr-border);
  font-size: .78rem; color: var(--cdr-text); text-decoration: none;
  background: var(--cdr-surface);
}
.mgr-jump-link:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.mgr-jump-num {
  min-width: 16px; padding: 0 5px; border-radius: 999px;
  background: var(--cdr-danger); color: #fff; font-size: .7rem;
  font-style: normal; font-weight: 700; text-align: center; line-height: 16px;
}
.mgr-body { flex: 1; overflow: auto; padding: 14px 16px 24px; }
.mgr-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.mgr-inline { display: flex; gap: 8px; margin-bottom: 10px; }
.mgr-inline .form-input { flex: 1; }
.mgr-list { display: flex; flex-direction: column; gap: 10px; }
.mgr-card { border: 1px solid var(--cdr-border); border-radius: 12px; padding: 11px 13px; }
.mgr-card-h { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.mgr-note { margin-top: 6px; font-size: .84rem; padding: 6px 9px; border-radius: 9px; background: color-mix(in srgb, var(--cdr-muted) 8%, transparent); }
.mgr-actions { display: flex; gap: 8px; margin-top: 9px; }
.mgr-empty { text-align: center; color: var(--cdr-muted); padding: 30px 0; font-size: .9rem; }
/* 审核缩略图（v1.20 头像 / 背景页签）：点开原图看细节 —— 抽屉只有 340px 宽，
   不点开根本判断不了这张图该不该过。object-fit: cover 让横幅裁成一条、
   头像裁成方块，两种比例在同一条列表里看着一样高。 */
.mgr-media { display: block; margin-top: 8px; border: 1px solid var(--cdr-border); border-radius: 9px; overflow: hidden; background: color-mix(in srgb, var(--cdr-muted) 6%, transparent); }
.mgr-media img { display: block; width: 100%; height: 110px; object-fit: cover; }
.mgr-wl { max-height: 320px; overflow: auto; }
.mgr-wl button { border: 0; background: transparent; color: var(--cdr-danger); cursor: pointer; font-size: .9rem; padding: 0 0 0 4px; }
.mgr-cfg { display: grid; grid-template-columns: 180px 1fr; gap: 12px; min-height: 320px; }
.mgr-cfg-tree { border: 1px solid var(--cdr-border); border-radius: 11px; padding: 8px; max-height: 460px; overflow: auto; }
.cfg-dir { font-size: .72rem; color: var(--cdr-muted); margin: 8px 0 4px; }
.cfg-file { display: block; width: 100%; text-align: left; border: 0; background: transparent; padding: 4px 6px; border-radius: 7px; cursor: pointer; font-size: .8rem; }
.cfg-file:hover { background: color-mix(in srgb, var(--cdr-primary) 10%, transparent); }
.mgr-editor {
  width: 100%; min-height: 380px; border: 1px solid var(--cdr-border); border-radius: 11px;
  padding: 10px; font-size: .8rem; line-height: 1.5; resize: vertical; background: #0f1620; color: #d8e2f0;
}
.mgr-cfg-edit .mgr-toolbar { margin-bottom: 8px; }
@media (max-width: 620px) {
  .mgr-cfg { grid-template-columns: 1fr; }
  .mgr-fab { bottom: 70px; }
}
/* ---------------------------------------------------------------- 玩家皮肤 3D */
/* 纯 CSS 3D 拼出来的方块人模型（见 assets/js/skin3d.js），零外部依赖 */
.skin3d {
  display: flex; align-items: center; justify-content: center;
  /* ⚠️ 这里是 `pan-y` 不是 `none`。
     以前写 `none`，整个 3D 皮肤框会把手指的滚动动作全部吃掉 ——
     站长 m16931「手机版个人主页无法滑动」就是它：模型框在手机上占掉不小一块，
     手指落在上面往下划，页面纹丝不动（实测：落点 y=700 命中 div.skin3d-spin 时滚动 0→0，
     同一个页面落点在别处都是 0→385）。
     `pan-y` 把**竖向**交给浏览器滚动，**横向**仍留给模型旋转 —— 两个都保住。 */
  touch-action: pan-y; user-select: none; -webkit-user-select: none;
  cursor: grab; overflow: hidden;
}
.skin3d.is-dragging { cursor: grabbing; }
.skin3d-stage {
  perspective: 900px; perspective-origin: 50% 45%;
  display: flex; align-items: center; justify-content: center; width: 100%;
}
.skin3d-spin { position: relative; transform-style: preserve-3d; transform-origin: center center; }
.skin3d-model { position: absolute; left: 50%; top: 50%; transform-style: preserve-3d; }
.skin3d-part  { position: absolute; transform-style: preserve-3d; }
.skin3d-face  {
  position: absolute; background-repeat: no-repeat;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  backface-visibility: hidden;
}
.skin3d-error { color: var(--cdr-muted); font-size: .85rem; padding: 34px 0; }

/* 玩家页皮肤卡 */
.skin-card { display: grid; gap: 14px; }
.skin-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.skin-tip { color: var(--cdr-muted); font-size: .8rem; }

/* ==========================================================================
   v6 精修层（v1.8.2 换成与 logo 同色的云岭蓝）
   放在文件末尾，靠级联顺序覆盖前面的旧规则；不改任何 HTML 结构。
   ========================================================================== */

/* ---- color-mix() 的兜底（老浏览器会整条丢弃 color-mix 声明，这里先给纯 rgba 值；
        支持 color-mix 的浏览器会被后面同选择器的声明覆盖，不影响效果） ---- */
.wl-done       { background: rgba(27, 47, 94, .06); border: 1px solid rgba(27, 47, 94, .20); }
.wl-step-n     { background: rgba(27, 47, 94, .10); }
.ach-ic        { background: rgba(27, 47, 94, .10); }
.ach.is-locked .ach-ic { background: rgba(18, 33, 63, .07); }
.radar-bar     { background: rgba(18, 33, 63, .10); }
.namecard-banner { background: linear-gradient(180deg, rgba(27, 47, 94, .14), transparent), var(--cdr-bg); }

/* ---- 标题走衬线（系统字体栈，不引外部字体） ---- */
.page-head h1, .auth-head h1, .hero h1, .hero-title, .serif {
  font-family: var(--cdr-serif);
  letter-spacing: .012em;
  font-weight: 600;
}

/* ---- v1.8.2：品牌标识 ----
   站内用**原版完整 logo**（自带「云岭」与 CloudRidge 字样），所以旁边不再重复渲染站点名。
   放在文件末尾，靠级联顺序压过前面那几条旧的 .brand-logo 规则。
   注意这是像素风图片，缩放必须 pixelated，否则边缘会糊。 */
.brand-logo {
  height: 54px; width: auto; max-width: 46vw; flex: none; display: block;
  image-rendering: pixelated;
  transition: transform .18s ease;
}
.brand:hover .brand-logo { transform: translateY(-1px) scale(1); }
.brand-logo-footer { height: 64px; max-width: 60vw; }
@media (max-width: 720px) {
  .brand-logo { height: 42px; max-width: 42vw; }
  .brand-logo-footer { height: 52px; }
}
@media (max-width: 520px) { .brand-logo { height: 32px; max-width: 50vw; } }
/* 像素风图片在任何位置都不要被平滑 */
img[src*="logo-mark"], img[src*="logo.svg"], img[src*="logo.png"], .pixel-logo { image-rendering: pixelated; }

/* ---- 行内 SVG 图标与文字对齐 ---- */
.cdr-i { display: inline-block; vertical-align: -.16em; flex: none; }
h3 > .cdr-i, h4 > .cdr-i, .card-head h3 > .cdr-i { vertical-align: -.18em; margin-right: 2px; }

/* ---- 卡片：更细的描边、更小的圆角、去掉泛光 ---- */
.card { border-radius: var(--cdr-radius); }
.btn { border-radius: var(--cdr-radius-sm); }
.btn-lg { border-radius: var(--cdr-radius); }
.form-input, .form-select, .form-textarea { border-radius: var(--cdr-radius-sm); }
.badge { border-radius: var(--cdr-radius-pill); }

/* 卡片顶部一条极淡的"山脊"分隔，替代原来的渐变标题条 */
.card-head { border-bottom: 1px solid var(--cdr-border); }
.section-label {
  font-family: var(--cdr-serif);
  letter-spacing: .04em;
  color: var(--cdr-text);
  opacity: .88;
}

/* ---- 首页云带：极慢平移，纯装饰；尊重 prefers-reduced-motion ---- */
.hero { position: relative; }
.cdr-cloudband {
  position: absolute; inset: auto 0 0 0; height: 46px; overflow: hidden;
  pointer-events: none; opacity: .5;
}
.cdr-cloudband i {
  position: absolute; bottom: 6px; left: -30%;
  width: 160%; height: 34px; display: block;
  background-repeat: repeat-x;
  background-size: 420px 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='34' viewBox='0 0 420 34'%3E%3Cg fill='%231B2F5E' fill-opacity='.22'%3E%3Crect x='0' y='12' width='420' height='10' rx='5'/%3E%3Ccircle cx='52' cy='17' r='12'/%3E%3Ccircle cx='300' cy='17' r='14'/%3E%3C/g%3E%3Cg fill='%2335C4F2' fill-opacity='.16'%3E%3Crect x='90' y='3' width='260' height='8' rx='4'/%3E%3Ccircle cx='150' cy='7' r='9'/%3E%3C/g%3E%3C/svg%3E");
  animation: cdrCloudDrift 46s linear infinite;
}
@keyframes cdrCloudDrift { from { transform: translateX(0); } to { transform: translateX(420px); } }

/* ---- 滚动入场：只做一次，8px 上移 + 淡入 ---- */
.cdr-reveal { opacity: 0; transform: translateY(8px); transition: opacity .5s ease, transform .5s ease; }
.cdr-reveal.is-in { opacity: 1; transform: none; }

/* ---- 数字滚动（首页统计） ---- */
.cdr-count { font-variant-numeric: tabular-nums; }

/* ---- 卡片 hover：上浮 1px + 描边加深，不发光 ---- */
.card.is-hoverable { transition: transform .18s ease, border-color .18s ease; }
.card.is-hoverable:hover { transform: translateY(-1px); border-color: var(--cdr-primary); }

/* ---- 免登录勾选框 ---- */
.auth-remember {
  display: flex; align-items: center; gap: 8px;
  margin: 2px 2px 6px; font-size: .9rem; cursor: pointer;
}
.auth-remember input { width: 16px; height: 16px; accent-color: var(--cdr-primary); flex: none; }
.auth-remember-hint { font-size: .76rem; color: var(--cdr-muted); margin: 0 2px 12px; line-height: 1.55; }

/* ---- 白名单页：已通过状态卡 + 进服三步 ---- */
.wl-grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: 20px; align-items: start; }
@media (max-width: 880px) { .wl-grid { grid-template-columns: 1fr; } }

.wl-done {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 16px; border-radius: var(--cdr-radius);
  background: color-mix(in srgb, var(--cdr-primary) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--cdr-primary) 22%, transparent);
}
.wl-done-mark {
  width: 46px; height: 46px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--cdr-primary); color: #fff;
}
.wl-done-names { display: flex; flex-wrap: wrap; gap: 6px; }

.wl-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.wl-steps > li { display: flex; gap: 12px; align-items: flex-start; }
.wl-step-n {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  display: grid; place-items: center; font-size: .8rem; font-weight: 700;
  background: color-mix(in srgb, var(--cdr-primary) 12%, transparent);
  color: var(--cdr-primary);
}
.wl-step-body { min-width: 0; }

.wl-ip { display: flex; align-items: center; gap: 8px; margin: 7px 0; flex-wrap: wrap; }
.wl-ip code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  padding: 6px 12px; border-radius: var(--cdr-radius-sm);
  background: color-mix(in srgb, var(--cdr-text) 5%, transparent);
  border: 1px dashed var(--cdr-border);
  font-size: .98rem; letter-spacing: .02em;
}

.wl-details { margin-top: 18px; border-top: 1px solid var(--cdr-border); padding-top: 14px; }
.wl-details > summary { cursor: pointer; font-weight: 600; font-size: .92rem; color: var(--cdr-primary); }
.wl-details[open] > summary { margin-bottom: 10px; }

/* ---- 成就徽章墙 ---- */
.ach-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; }
.ach {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 8px; text-align: center;
  border-radius: var(--cdr-radius); border: 1px solid var(--cdr-border);
  background: var(--cdr-surface);
}
.ach-ic {
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--cdr-primary) 12%, transparent);
  color: var(--cdr-primary);
}
.ach-name { font-size: .82rem; font-weight: 600; line-height: 1.3; }
.ach-desc { font-size: .72rem; color: var(--cdr-muted); line-height: 1.35; }
.ach.is-locked { opacity: .45; }
.ach.is-locked .ach-ic { background: color-mix(in srgb, var(--cdr-text) 6%, transparent); color: var(--cdr-muted); }
.ach.is-new { border-color: var(--cdr-primary); }

/* ---- 能力雷达图 ---- */
.radar-wrap { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; align-items: center; }
@media (max-width: 720px) { .radar-wrap { grid-template-columns: 1fr; } }
/* ⚠️ 选择器必须带 `.cdr-body` 前缀 —— 行 1213 那条
 *    `.cdr-body img, .cdr-body svg, .cdr-body video, .cdr-body iframe { max-width: 100% }`
 *    优先级是 0-1-1，**高于**只写 `.radar-svg`（0-1-0），会把这里的 max-width: 320px 压掉，
 *    于是雷达图撑满整个栅格列（线上实测 582×566，而本意是"居中、不超过 320px"）。
 *    加上 .cdr-body 变成 0-2-0 才压得住。改这条时别把前缀去掉。 */
.cdr-body .radar-svg { width: 100%; max-width: 320px; height: auto; margin: 0 auto; display: block; }
.radar-legend { display: flex; flex-direction: column; gap: 7px; }
.radar-row { display: grid; grid-template-columns: 4.6em 1fr 3.4em; gap: 8px; align-items: center; font-size: .84rem; }
.radar-bar { height: 7px; border-radius: var(--cdr-radius-pill); background: color-mix(in srgb, var(--cdr-text) 8%, transparent); overflow: hidden; }
.radar-bar > i { display: block; height: 100%; border-radius: inherit; background: var(--cdr-primary); }
.radar-bar.is-avg > i { background: var(--cdr-muted); opacity: .5; }

/* ---- 社区痕迹 ---- */
.trace-list { display: flex; flex-direction: column; gap: 2px; }
.trace-row { display: flex; gap: 10px; align-items: baseline; padding: 7px 0; border-bottom: 1px dashed var(--cdr-border); }
.trace-row:last-child { border-bottom: 0; }
.trace-row .t-time { flex: none; font-size: .74rem; color: var(--cdr-muted); font-variant-numeric: tabular-nums; }
.trace-row .t-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- 账号安全评分卡 ---- */
.score-wrap { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.score-ring { width: 96px; height: 96px; flex: none; position: relative; }
.score-ring svg { transform: rotate(-90deg); display: block; }
.score-ring .score-num {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums;
}
.score-todos { display: flex; flex-direction: column; gap: 7px; min-width: 0; flex: 1 1 240px; }
.score-todo { display: flex; gap: 8px; align-items: center; font-size: .86rem; }
.score-todo .s-pts { margin-left: auto; flex: none; font-size: .78rem; color: var(--cdr-danger); font-variant-numeric: tabular-nums; }
.score-ok { color: var(--cdr-success); font-size: .86rem; }

/* ---- 个性名片预览 ---- */
.namecard {
  border-radius: var(--cdr-radius); overflow: hidden;
  border: 1px solid var(--cdr-border); background: var(--cdr-surface);
}
.namecard-banner {
  height: 84px;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--cdr-primary) 16%, transparent), transparent),
    var(--cdr-bg);
  background-size: cover; background-position: center;
}
.namecard-body { display: flex; gap: 12px; align-items: flex-end; padding: 0 14px 14px; margin-top: -26px; }
.namecard-avatar {
  width: 62px; height: 62px; border-radius: 12px; flex: none;
  border: 2px solid var(--cdr-surface); background: var(--cdr-bg);
  image-rendering: pixelated;
}
.namecard-name { font-weight: 700; font-family: var(--cdr-serif); }
.namecard-sig { font-size: .82rem; color: var(--cdr-muted); margin-top: 2px; }

/* ---- 主题色 / 头像框选择 ---- */
.swatch-row { display: flex; gap: 8px; flex-wrap: wrap; }
.swatch {
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  border: 2px solid var(--cdr-border);
}
.swatch.is-on { border-color: var(--cdr-text); box-shadow: 0 0 0 2px var(--cdr-surface) inset; }

/* ---- 社区痕迹：图片条 ---- */
.shot-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.shot {
  position: relative; display: block; aspect-ratio: 4 / 3;
  border-radius: var(--cdr-radius-sm); overflow: hidden;
  border: 1px solid var(--cdr-border); background: var(--cdr-bg);
}
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shot-likes {
  position: absolute; right: 4px; bottom: 4px;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 6px; border-radius: var(--cdr-radius-pill);
  background: rgba(0, 0, 0, .55); color: #fff; font-size: .68rem;
}

/* ---- 图形验证码（ALTCHA 的降级方案）----
   默认可见（禁 JS 的浏览器靠它才能提交）；html.cdr-js 一加上就先藏起来，
   只有 ALTCHA 算不出来 / 加载失败 / 报错时由 JS 加 .is-on 再露出来。
   .is-mandatory 是"始终要图形码"模式，永远显示。 */
.cdr-captcha { margin: 12px 0; }
html.cdr-js .cdr-captcha:not(.is-on):not(.is-mandatory) { display: none; }
.cdr-captcha-hint { margin-bottom: 6px; }
.cdr-captcha-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cdr-captcha-img {
  border-radius: var(--cdr-radius-sm); border: 1px solid var(--cdr-border);
  background: var(--cdr-bg); display: block; flex: none;
}
.cdr-captcha-input { margin-top: 8px; max-width: 220px; letter-spacing: .28em; text-transform: uppercase; }

/* ---- 动效降级 ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /**
   * 装饰云带**不**完全停掉，只是放慢到几乎察觉不到。
   *
   * 原来这里写的是 `animation: none` —— 结果凡是系统里关了"显示动画效果"的人
   * （Windows 那个开关会让浏览器报 prefers-reduced-motion: reduce），
   * 首页和服务器地址下面那条带子就彻底静止，看起来像页面坏了（用户反馈"线条动画没有移动"）。
   *
   * 它是纯背景装饰、位移幅度极小，不会引起不适；真正有功能性影响的动效
   * （平滑滚动、卡片悬浮位移、入场动画）下面照旧全部关掉。
   */
  .cdr-cloudband i { animation-duration: 150s; }
  .cdr-reveal { opacity: 1; transform: none; transition: none; }
  .card.is-hoverable:hover { transform: none; }
}

/* @fragment:seedmap —— 种子地图（/seedmap）样式，追加在 style.css 末尾
   ==========================================================================
   v6：种子地图（/seedmap）—— 采样格地图的壳与浮层
   命名统一用 sm- 前缀，和站内其它样式不冲突；这段是追加在文件末尾的，
   不要把它挪到文件中间，否则和别人同时改 style.css 的改动会打架。
   ========================================================================== */
.sm-empty .empty { padding: 26px 16px; }

/* .sm-loading / .sm-legend / .sm-tip 都写了 display，会把 hidden 属性自带的
   display:none 顶掉 —— 少了这条，"加载中"的遮罩会一直压在地图上（半透明黑，
   看起来就像配色发暗）。 */
.sm-stage [hidden] { display: none !important; }

.sm-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 10px; min-width: 0;
}
.sm-tabs { display: inline-flex; gap: 4px; padding: 3px; border-radius: var(--cdr-radius-pill);
  background: color-mix(in srgb, var(--cdr-muted) 10%, transparent); }
.sm-tab {
  border: 0; cursor: pointer; font: inherit; font-size: .86rem;
  padding: 6px 14px; border-radius: var(--cdr-radius-pill);
  background: transparent; color: var(--cdr-muted);
}
.sm-tab.is-on { background: var(--cdr-surface); color: var(--cdr-text); box-shadow: var(--cdr-shadow); font-weight: 600; }
.sm-tab.is-off { opacity: .4; cursor: not-allowed; }

.sm-switches { display: inline-flex; gap: 12px; flex-wrap: wrap; }
.sm-switch {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-size: .84rem; color: var(--cdr-muted); white-space: nowrap;
}
.sm-switch input { width: 15px; height: 15px; accent-color: var(--cdr-primary); flex: none; }
.sm-switch input:disabled { cursor: not-allowed; }

.sm-zoom { display: inline-flex; gap: 5px; }
.sm-zoom .btn { padding: 3px 10px; }

.sm-locate { display: flex; gap: 6px; align-items: center; margin-left: auto; min-width: 0; }
.sm-locate .form-input { width: 190px; max-width: 46vw; padding: 6px 10px; font-size: .86rem; }

/* 舞台：canvas 由 JS 按容器实际尺寸设定像素尺寸，这里只负责"框" */
.sm-stage {
  position: relative; width: 100%; min-width: 0;
  height: clamp(320px, 56vh, 620px);
  border: 1px solid var(--cdr-border); border-radius: var(--cdr-radius);
  overflow: hidden; background: #0d1420;
}
.sm-stage canvas {
  display: block; width: 100%; height: 100%;
  touch-action: none; cursor: grab; image-rendering: pixelated;
}
.sm-stage canvas:active { cursor: grabbing; }

.sm-tip {
  position: absolute; z-index: 3; pointer-events: none;
  padding: 6px 9px; border-radius: var(--cdr-radius-sm);
  background: rgba(10, 16, 26, .9); color: #e8eef8;
  font-size: .78rem; line-height: 1.5; white-space: nowrap;
  border: 1px solid rgba(255, 255, 255, .14);
}
.sm-tip .sm-tip-c { color: #9fd6a8; }
.sm-tip .sm-tip-k { color: #8fa4c4; font-size: .72rem; }
.sm-tip .sm-tip-s { color: #7ce08c; font-weight: 600; }

.sm-legend {
  position: absolute; left: 8px; bottom: 8px; right: 8px; z-index: 2;
  display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center;
  padding: 6px 9px; border-radius: var(--cdr-radius-sm);
  background: rgba(10, 16, 26, .72); color: #dbe6f5; font-size: .72rem;
  max-height: 34%; overflow: hidden;
}
.sm-legend-h { font-weight: 600; opacity: .8; }
.sm-legend-i { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.sm-legend-i i { width: 10px; height: 10px; border-radius: 2px; display: inline-block; flex: none; }

.sm-loading {
  position: absolute; inset: 0; z-index: 4; display: flex; align-items: center; justify-content: center;
  gap: 8px; background: rgba(13, 20, 32, .8); color: #dbe6f5; font-size: .88rem;
}

.sm-status {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 10px; min-width: 0; font-size: .86rem;
}
.sm-status .sm-chunkbase { margin-left: auto; }
.sm-hint { margin-top: 4px; }

@media (max-width: 720px) {
  .sm-stage { height: clamp(260px, 46vh, 420px); }
  .sm-locate { margin-left: 0; width: 100%; }
  .sm-locate .form-input { width: auto; flex: 1 1 auto; max-width: none; }
  .sm-status .sm-chunkbase { margin-left: 0; }
  .sm-legend { max-height: 42%; font-size: .68rem; }
}
/* @fragment:tools-extra —— 工具页四个新工具的样式，追加在 style.css 末尾
   ==========================================================================
   v6：工具页新增面板（配置推荐 / 红石体检 / 数据卡片 / 坐标速查）
   前缀：rec- / rs- / card- / cx-，都是新命名，不会和站内旧样式撞车。
   ========================================================================== */

/* ---- A 配置推荐器 ---- */
.rec-card { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.rec-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.rec-fact {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
  padding: 10px 13px; border-radius: var(--cdr-radius);
  border: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-primary) 5%, transparent);
}
.rec-fact span { font-size: .78rem; color: var(--cdr-muted); }
.rec-fact b { font-size: 1.32rem; line-height: 1.2; }
.rec-fact small {
  font-size: .72rem; color: var(--cdr-muted);
  font-family: "JetBrains Mono", Consolas, monospace; overflow-wrap: anywhere;
}
.rec-mods-h { font-weight: 600; font-size: .88rem; margin-bottom: 7px; }
.rec-mods ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.rec-mods li {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--cdr-radius-pill);
  border: 1px solid var(--cdr-border); font-size: .82rem; min-width: 0;
}
.rec-tag {
  font-style: normal; font-size: .68rem; padding: 0 6px; border-radius: var(--cdr-radius-pill);
  color: var(--cdr-primary); background: color-mix(in srgb, var(--cdr-primary) 10%, transparent);
}

/* ---- B 红石 / 卡顿体检 ---- */
.rs-card { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.rs-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rs-badge {
  padding: 3px 12px; border-radius: var(--cdr-radius-pill);
  font-size: .82rem; font-weight: 600; border: 1px solid var(--cdr-border);
}
.rs-badge.is-ok { color: var(--cdr-success); border-color: color-mix(in srgb, var(--cdr-success) 45%, transparent); background: color-mix(in srgb, var(--cdr-success) 8%, transparent); }
.rs-badge.is-warn { color: var(--cdr-warning); border-color: color-mix(in srgb, var(--cdr-warning) 45%, transparent); background: color-mix(in srgb, var(--cdr-warning) 8%, transparent); }
.rs-badge.is-bad { color: var(--cdr-danger); border-color: color-mix(in srgb, var(--cdr-danger) 45%, transparent); background: color-mix(in srgb, var(--cdr-danger) 8%, transparent); }
.rs-bar {
  flex: 1 1 140px; min-width: 110px; height: 8px; overflow: hidden;
  border-radius: var(--cdr-radius-pill);
  background: color-mix(in srgb, var(--cdr-text) 10%, transparent);
}
.rs-bar > i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--cdr-success), var(--cdr-warning) 55%, var(--cdr-danger)); }
.rs-score { font-size: .84rem; color: var(--cdr-muted); font-variant-numeric: tabular-nums; }
.rs-live { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.rs-live-h { font-size: .82rem; color: var(--cdr-muted); }
.rs-chip {
  display: inline-flex; align-items: baseline; gap: 4px; padding: 3px 10px;
  border-radius: var(--cdr-radius-pill); font-size: .84rem; font-weight: 600;
  background: color-mix(in srgb, var(--cdr-muted) 9%, transparent);
}
.rs-chip em { font-style: normal; font-size: .68rem; color: var(--cdr-muted); font-weight: 400; }
.rs-chip small { font-size: .68rem; color: var(--cdr-muted); font-weight: 400; }
.rs-advice-h { font-weight: 600; font-size: .88rem; margin-bottom: 4px; }
.rs-advice ul { margin: 0; padding-left: 18px; }
.rs-advice li { font-size: .87rem; margin: 5px 0; line-height: 1.6; }

/* ---- C 数据卡片 ---- */
.card-stage { margin-top: 12px; min-width: 0; }
.card-stage canvas {
  width: 100%; max-width: 660px; height: auto; display: block;
  border-radius: var(--cdr-radius); border: 1px solid var(--cdr-border);
}

/* ---- E 坐标速查 ---- */
.cx-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.cx-cell {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  min-width: 0; padding: 10px 13px; border-radius: var(--cdr-radius);
  border: 1px solid var(--cdr-border);
}
.cx-cell span { font-size: .78rem; color: var(--cdr-muted); }
.cx-cell b { font-size: 1.02rem; overflow-wrap: anywhere; }
.cx-cell .btn { padding: 2px 8px; font-size: .74rem; }
.cx-slime { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 10px; }
.cx-slime-h { font-size: .82rem; color: var(--cdr-muted); }
.cx-slime-i {
  display: inline-flex; align-items: baseline; gap: 6px; cursor: pointer;
  padding: 4px 10px; border-radius: var(--cdr-radius-sm);
  border: 1px solid var(--cdr-border); background: var(--cdr-surface);
  color: var(--cdr-text); font-size: .83rem;
}
.cx-slime-i:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.cx-slime-i small { font-size: .7rem; color: var(--cdr-muted); }
/* ---------------------------------------------------------------- 更新日志页
   @fragment: changelog-page（更新日志） */
.cl-intro { margin-bottom: 22px; }
.cl-lead { margin: 0 0 12px; font-size: .94rem; line-height: 1.75; }
.cl-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.cl-timeline { margin-top: 2px; }
.cl-card { padding: 16px 18px; }
.cl-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.cl-ver { font-weight: 700; font-size: 1.02rem; font-variant-numeric: tabular-nums; }
.cl-date { color: var(--cdr-muted); font-size: .82rem; font-variant-numeric: tabular-nums; }
.cl-title { margin: 0 0 9px; font-size: 1rem; font-weight: 600; line-height: 1.5; }
.cl-list { margin: 0; padding-left: 1.15em; }
.cl-list li { margin: 4px 0; font-size: .88rem; line-height: 1.7; overflow-wrap: anywhere; }
.cl-list li::marker { color: var(--cdr-muted); }
.cl-note { margin-top: 4px; }
/* 模组版本用强调色圆点，和网站版本区分开 */
.timeline-item.is-mod::before { background: var(--cdr-accent); }
@media (max-width: 640px) {
  .cl-card { padding: 13px 14px; }
  .cl-lead { font-size: .9rem; }
  .cl-title { font-size: .95rem; }
  .cl-list li { font-size: .85rem; }
}

/* ==========================================================================
   @fragment:me-redesign —— 个人中心（/me）v8 重做：卡片样式 / 折叠分层 / 手机端
   --------------------------------------------------------------------------
   追加在 style.css 末尾（同优先级的规则按出现顺序生效，这里能覆盖上面的默认值）。

   组成：
     · .me-head-*  页头（头像 / 名字 / 徽章 / 分数入口）
     · .me-card    统一的卡片外壳（比分 .card 更紧凑、手机上更好读）
     · .me-fold    <details> 折叠块（常用展开、次要折叠）
     · .me-grid    自适应两列 → 窄屏一列（手机少滚几屏）
     · .me-score / .me-suggests  安全评分与"还差什么"的可点建议
     · .me-list / .me-skin / .me-summ  帖子列表、皮肤卡、成就摘要

   手机端重点：减少 padding、把两列收成一列、按钮不换行、字号不小于 .79rem。
   ========================================================================== */

/* ------------------------------------------------------------------ 页头 */
.me-head { padding-bottom: 22px; }
.me-head-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.me-avatar {
  width: 76px; height: 76px; border-radius: 22px; object-fit: cover; flex: none;
  background: var(--cdr-border); border: 1px solid var(--cdr-border);
}
.me-head-main { min-width: 0; flex: 1 1 240px; }
.me-name { margin: 0 0 6px; font-size: clamp(1.35rem, 3vw, 1.8rem); line-height: 1.25; }
.me-badges { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.me-email { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.me-head-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.me-head-actions form { margin: 0; }
.me-badge-score { text-decoration: none; }
.me-head .breadcrumb { margin-bottom: 10px; }

/* ------------------------------------------------------------ 页面主体 */
.me-section { padding: 20px 0 42px; }
.me-cards { display: block; }

/* ------------------------------------------------------------------ 卡片 */
/**
 * `[hidden]` 必须真的不显示。
 *
 * 浏览器默认的 `[hidden]{display:none}` 会被**任何**自定义 display 覆盖 ——
 * 个人中心里的「验证码 + 确认」表单是 `.sec-form{display:flex}`，
 * 于是 `hidden` 形同虚设：验证码输入框在点「发送验证码」**之前**就露出来了
 * （站长明确要求它发出去之后才出现，改密码那块也是这个模式）。
 * 实测踩到过，所以在这里兜一层。
 */
.me-cards [hidden] { display: none !important; }

.me-card {
  background: var(--cdr-surface);
  border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius);
  box-shadow: var(--cdr-shadow);
  padding: 16px 18px;
  margin: 0;
}
.me-card-wide { width: 100%; }
.me-card-head { margin-bottom: 10px; }
.me-card-title {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 4px; font-size: 1.02rem; font-weight: 700; line-height: 1.35;
}
.me-card-title svg { flex: none; }
.me-card .sub { font-size: .83rem; line-height: 1.6; }

/* 自适应网格：宽屏两列，窄屏一列（手机上少滚几屏） */
.me-grid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
.me-grid + .me-grid { margin-top: 14px; }
.me-card + .me-card,
.me-card + .me-grid,
.me-grid + .me-card { margin-top: 14px; }

/* ------------------------------------------------------------ 折叠块 */
.me-fold { border-top: 1px solid var(--cdr-border); }
.me-fold:first-of-type { border-top: 0; }
.me-fold.is-last { border-bottom: 0; }
.me-fold-head {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 2px; cursor: pointer; list-style: none;
  font-size: .94rem; font-weight: 600; border-radius: 10px;
  transition: background .15s, color .15s;
}
.me-fold-head::-webkit-details-marker { display: none; }
.me-fold-head:hover { color: var(--cdr-primary); }
.me-fold-head:focus-visible { outline: 2px solid var(--cdr-primary); outline-offset: 2px; }
.me-fold-ic { display: inline-flex; flex: none; color: var(--cdr-muted); }
.me-fold-head:hover .me-fold-ic { color: var(--cdr-primary); }
.me-fold-title { flex: 0 1 auto; min-width: 0; }
.me-fold-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.me-fold-chev { display: inline-flex; flex: none; color: var(--cdr-muted); transition: transform .2s; }
.me-fold[open] .me-fold-chev { transform: rotate(180deg); }
.me-fold-body { padding: 2px 2px 15px; }
/* 说明文字在手机上别太挤 */
.me-fold-body .text-sm { line-height: 1.72; }

/* 账号安全的行（换绑邮箱 / 手机号 / 安全邮箱）——比默认更紧凑一层 */
.me-fold-body .sec-row { padding: 11px 0; }
.me-fold-body .sec-row:first-of-type { padding-top: 4px; }

/* 卡片里的小节 */
.me-sub {
  display: flex; align-items: center; gap: 7px;
  margin: 14px 0 8px; font-size: .88rem; font-weight: 700;
}
.me-sub:first-child { margin-top: 2px; }
.me-sub svg { flex: none; color: var(--cdr-muted); }
.me-sub-card {
  border: 1px solid var(--cdr-border); border-radius: 12px; padding: 12px 14px;
  background: color-mix(in srgb, var(--cdr-primary) 5%, transparent);
}
.me-sub-title { font-size: .86rem; font-weight: 700; margin-bottom: 4px; }
.me-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.me-actions form { margin: 0; }

/* --------------------------------------------------- 安全评分 + 可点建议 */
.me-score .score-wrap { gap: 14px; }
.me-score .score-num { font-size: 1.7rem; }
.me-score-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.me-score-sub { font-size: .85rem; color: var(--cdr-muted); margin-bottom: 6px; }
.me-suggests { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.me-suggest {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 10px; font-size: .86rem;
  border: 1px solid var(--cdr-border); background: var(--cdr-surface); color: var(--cdr-text);
}
.me-suggest:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); opacity: 1; }
.me-suggest svg { flex: none; color: var(--cdr-warning); }
.me-suggest-t { flex: 1 1 auto; min-width: 0; }
.me-suggest-p {
  flex: none; font-weight: 700; font-size: .8rem; color: var(--cdr-primary);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------- 列表 / 摘要 */
.me-list { display: flex; flex-direction: column; }
.me-list-row {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 8px 2px; border-bottom: 1px solid var(--cdr-border); color: inherit;
}
.me-list-row:last-child { border-bottom: 0; }
.me-list-row:hover { color: var(--cdr-primary); opacity: 1; }
.me-list-t { flex: 1 1 150px; min-width: 0; font-size: .88rem; }
.me-list-m { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.me-ach-sum { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.me-ach-sum strong { font-size: 1.4rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.me-bar {
  flex: 1 1 120px; height: 8px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--cdr-muted) 18%, transparent);
}
.me-bar > i { display: block; height: 100%; background: var(--cdr-primary); border-radius: 999px; }
.me-skins { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.me-skin {
  border: 1px solid var(--cdr-border); border-radius: 12px; padding: 12px;
  background: var(--cdr-surface); min-width: 0;
}
.me-skin form { margin: 0; }
.me-skin .form-row { margin-bottom: 10px; }

/* 手机端：输入框和按钮各占一行，避免"发送验证码"被挤成竖排 */
@media (max-width: 640px) {
  .me-fold-body .sec-form .form-input { flex: 1 1 100%; }
  .me-fold-body .sec-form .btn { width: 100%; }
}

/* 行内代码：前台样式表里原本没有这个类（只有后台有），
   个人中心的小提示里用到它，不定义的话会跟着正文一起糊成一片 */
.code-inline {
  display: inline-block; padding: 1px 6px; border-radius: 6px;
  background: color-mix(in srgb, var(--cdr-muted) 15%, transparent);
  font-family: "JetBrains Mono", Consolas, monospace; font-size: .87em;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------ 提示气泡 */
.me-toast {
  position: fixed; left: 50%; bottom: 26px; z-index: 60;
  transform: translate(-50%, 14px);
  max-width: min(420px, calc(100vw - 28px));
  padding: 11px 16px; border-radius: 12px;
  background: #0f172a; color: #f8fafc; font-size: .88rem; line-height: 1.5;
  box-shadow: 0 18px 40px -16px rgba(0, 0, 0, .55);
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
.me-toast.is-show { opacity: 1; transform: translate(-50%, 0); }
.me-toast.is-bad { background: var(--cdr-danger); color: #fff; }

/* ------------------------------------------------------------ 手机端 */
@media (max-width: 980px) {
  .me-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 680px) {
  .me-section { padding: 14px 0 34px; }
  .me-head { padding-bottom: 16px; }
  .me-head-row { gap: 12px; }
  .me-avatar { width: 58px; height: 58px; border-radius: 17px; }
  .me-name { font-size: 1.24rem; }
  .me-head-actions { margin-left: 0; width: 100%; }
  .me-card { padding: 13px 14px; }
  .me-card-title { font-size: .97rem; }
  .me-fold-head { padding: 10px 2px; font-size: .9rem; }
  .me-fold-body { padding-bottom: 12px; }
  .me-grid { gap: 11px; }
  .me-card + .me-card,
  .me-card + .me-grid,
  .me-grid + .me-card { margin-top: 11px; }
  .me-list-row { padding: 7px 0; }
  .me-skins { grid-template-columns: minmax(0, 1fr); }
  .me-score .score-ring svg { width: 76px; height: 76px; }
  .me-score .score-num { font-size: 1.4rem; }
}

/* ==========================================================================
   @fragment:polish —— 站长第二轮反馈（v1.8.3）
   --------------------------------------------------------------------------
   规则全部**追加在末尾**、且只新增选择器或覆盖本段自己定义的东西，
   不动上面任何一段已有规则（多人同时改 style.css 时不会打架）。

   这一段解决：
     1. 个人中心「账号与安全 / 个性化」原来两栏，左栏被拉出一大片空白 → 单栏
     2. 登录邮箱 / 手机号 / 安全邮箱三块长得一模一样 → 各自图标 + 用途 + 后果提示
     3. 卡片内距、字号统一；折叠块装进卡片后不再像"浮在页面上的空条"
     4. 深色模式对比度：.btn-soft（加入 QQ 群、发送验证码…）、服务器地址框文字、
        导航当前项、首屏大数字 —— 浅色下都正常，深色下几乎看不见
     5. 首页首屏「服务器要点」条
     6. 成就墙内嵌进折叠块后的间距（展开后底部不留一大片空）
   ========================================================================== */

/* ---------------------------------------------------------- 配色令牌
   浅色下"用主色当字色"是对的（藏青写在白底上很清楚）；深色下主色本身就是近黑的
   藏青，写在 #080F22 上等于隐形。所以把"需要跟着模式翻转"的几个颜色收成令牌，
   由下面的深色选择器整组替换 —— 不用给每条规则写三遍。

   三种深色来源都要覆盖：html[data-color-mode="dark"]（手动切）、
   html[data-dark-forced="1"]（后台强制）、以及 html[data-color-mode="auto"]
   + 系统深色（媒体查询里）。 */
:root {
  --polish-soft-bg: color-mix(in srgb, var(--cdr-primary) 13%, transparent);
  --polish-soft-bg-hover: color-mix(in srgb, var(--cdr-primary) 22%, transparent);
  --polish-soft-fg: var(--cdr-primary);
  --polish-soft-bd: transparent;
  --polish-strong: var(--cdr-primary);
  --polish-ghost-bd: var(--cdr-border);
  --polish-nav-fg: var(--cdr-primary);
  --polish-link: var(--cdr-primary);
  --polish-kicker: color-mix(in srgb, var(--cdr-accent) 32%, var(--cdr-primary));
  --polish-nav-bg: color-mix(in srgb, var(--cdr-primary) 13%, transparent);
  --polish-num-a: var(--cdr-primary);
  --polish-num-b: var(--cdr-primary-2);
  --polish-ok: var(--cdr-success);
}
html[data-color-mode="dark"],
html[data-dark-forced="1"] {
  --polish-soft-bg: color-mix(in srgb, var(--cdr-accent) 20%, transparent);
  --polish-soft-bg-hover: color-mix(in srgb, var(--cdr-accent) 30%, transparent);
  --polish-soft-fg: var(--cdr-text);
  --polish-soft-bd: color-mix(in srgb, var(--cdr-accent) 42%, transparent);
  --polish-strong: var(--cdr-text);
  --polish-ghost-bd: color-mix(in srgb, var(--cdr-text) 22%, transparent);
  --polish-nav-fg: var(--cdr-text);
  --polish-nav-bg: color-mix(in srgb, var(--cdr-accent) 18%, transparent);
  --polish-num-a: var(--cdr-text);
  --polish-num-b: var(--cdr-accent);
  --polish-link: #8CC2FF;
  --polish-kicker: var(--cdr-accent);
  --polish-ok: #5FD07A;
}
@media (prefers-color-scheme: dark) {
  html[data-color-mode="auto"] {
    --polish-soft-bg: color-mix(in srgb, var(--cdr-accent) 20%, transparent);
    --polish-soft-bg-hover: color-mix(in srgb, var(--cdr-accent) 30%, transparent);
    --polish-soft-fg: var(--cdr-text);
    --polish-soft-bd: color-mix(in srgb, var(--cdr-accent) 42%, transparent);
    --polish-strong: var(--cdr-text);
    --polish-ghost-bd: color-mix(in srgb, var(--cdr-text) 22%, transparent);
    --polish-nav-fg: var(--cdr-text);
    --polish-nav-bg: color-mix(in srgb, var(--cdr-accent) 18%, transparent);
    --polish-num-a: var(--cdr-text);
    --polish-num-b: var(--cdr-accent);
    --polish-link: #8CC2FF;
    --polish-kicker: var(--cdr-accent);
    --polish-ok: #5FD07A;
  }
}

/* ------------------------------------------- 深色模式：看不清的控件逐个补齐 */

/* 「加入 QQ 群」「发送验证码」「复制」这类软按钮：浅色 = 主色底 + 主色字，
   深色下必须换成"浅底浅字"，否则藏青写在 #080F22 上对比度只有 1.2~1.5。 */
.btn-soft { background: var(--polish-soft-bg); color: var(--polish-soft-fg); border-color: var(--polish-soft-bd); }
.btn-soft:hover { background: var(--polish-soft-bg-hover); color: var(--polish-soft-fg); opacity: 1; }

/* 「服务器指南」这类幽灵按钮：底色本来就是 surface，深色下再补一圈描边，
   不然按钮和页面底色几乎同色，只剩文字浮在那里。 */
.btn-ghost { border-color: var(--polish-ghost-bd); }

/* 虚线框里的服务器地址：原来写死 color: var(--cdr-primary)。 */
.ip-box { border-color: color-mix(in srgb, var(--polish-strong) 42%, var(--cdr-border)); }
.ip-box .ip-value { color: var(--polish-strong); }

/* 页头导航的「当前所在页」：深色下浅蓝底 + 浅色字。 */
.main-nav a.active { color: var(--polish-nav-fg); background: var(--polish-nav-bg); }

/* 个人中心的页签同款问题：.me-tab.is-on 也是"主色字 + 主色底"，
   深色下当前页签整块消失（对比度 1.42）。 */
.me-tab:hover { border-color: var(--polish-nav-fg); color: var(--polish-nav-fg); }
.me-tab.is-on {
  color: var(--polish-nav-fg);
  border-color: color-mix(in srgb, var(--polish-nav-fg) 40%, transparent);
  background: var(--polish-nav-bg);
}

/* 同一族问题的其它三处（都是"用主色表示当前选中"，浅色下没问题、深色下隐形）：
     · 导航分组按钮：所在的整组里有当前页时 .nav-group.is-active
     · 页头下拉里的当前项：语言 / 配色（.dropdown-item.is-active）
     · 指南侧栏的当前篇：.guide-nav a.active
   站长说的"深色模式下很多按钮看不见"主要就是这一族。 */
.nav-group.is-active .nav-group-btn {
  color: var(--polish-nav-fg);
  background: var(--polish-nav-bg);
}
.dropdown-item.is-active { color: var(--polish-nav-fg); }
.guide-nav a.active {
  color: var(--polish-nav-fg);
  background: var(--polish-nav-bg);
  border-left-color: var(--polish-nav-fg);
}

/* 玩家榜右侧的时长：写死 var(--cdr-primary)，深色下 1.18 —— 首页玩家榜和
   /players 两处都会用到。 */
.rank-value { color: var(--polish-strong); }

/* 裸链接（正文里没有自己颜色的 <a>，例如登录页的"注册新账号"）走的是
   `a { color: var(--cdr-primary) }`，深色下同样只剩 1.2 的对比度。

   这里用 :where() 把选择器权重压到和 `a` 一样（0,0,1），只靠"出现在后面"生效 ——
   这样 .btn-primary / .badge / .me-tab / .thread-row 这些**自己带了颜色**的链接
   照样赢过我这条，不会被改成蓝色。 */
:where(html[data-color-mode="dark"]) a,
:where(html[data-dark-forced="1"]) a { color: var(--polish-link); }
@media (prefers-color-scheme: dark) {
  :where(html[data-color-mode="auto"]) a { color: var(--polish-link); }
}

/* 没带变体的默认 .badge（例：讨论区标题栏的"今日新帖 0"）也是主色字 + 主色底。
   只挑没有 badge-* 变体的那些，-soft/-success/-warn/-danger/-role 各有各的颜色，
   这里一个都不碰；带内联 style 的（如评分徽章）本来也压不过内联。 */
html[data-color-mode="dark"] .badge:not(.badge-soft):not(.badge-success):not(.badge-warn):not(.badge-danger):not(.badge-role),
html[data-dark-forced="1"] .badge:not(.badge-soft):not(.badge-success):not(.badge-warn):not(.badge-danger):not(.badge-role) {
  color: var(--polish-link);
  background: color-mix(in srgb, var(--cdr-accent) 16%, transparent);
}
@media (prefers-color-scheme: dark) {
  html[data-color-mode="auto"] .badge:not(.badge-soft):not(.badge-success):not(.badge-warn):not(.badge-danger):not(.badge-role) {
    color: var(--polish-link);
    background: color-mix(in srgb, var(--cdr-accent) 16%, transparent);
  }
}

/* 首屏在线人数：原来是"主色→深主色"的渐变文字 + color:transparent，
   深色下整块数字直接消失。换成浅色渐变。 */
.hero-stat-num { background-image: linear-gradient(135deg, var(--polish-num-a), var(--polish-num-b)); }

/* ------------------------------------------------- 首页首屏「服务器要点」条 */
/* v9：站长要求删掉首屏那 4 个框（进服地址 / 版本要求 / 当前在线 / 服务端状态），
   上面的 .ip-box 与右侧 .hero-card 已经把同样的信息说完了。
   .hero-facts / .hero-fact* 的规则随 DOM 一起删除，这里不再留任何选择器。 */

/* ---------------------------------------- 赞助条：是一张有内容的卡，不是空条 */
/*
 * 站长反馈：桌面端这条显示成"一条空条"，手机端正常。
 *
 * 根因是**背景用了 background 简写 + 渐变里带 color-mix()**：
 * 老浏览器（Chrome < 111 / Safari < 16.2 / 国产套壳内核）解析不了 color-mix()，
 * 整条 `background: linear-gradient(…)` 会被判为无效而**丢掉整条声明** ——
 * 简写一失效，底色也一起没了。DevTools 里看到的
 * `background-image: linear-gradient(135deg, color(srgb 0.92…))` 是浏览器把
 * color-mix 的**计算值**序列化出来的写法，源码里并没有 color() 函数。
 *
 * 所以这里：① 显式拆成 background-color（纯色，永远有效）+ background-image（渐变，
 * 失效也不影响底色）；② 给卡片和它的内层各加一条 min-height，即使内容塌陷也还是
 * 一张有厚度的卡，不会变成一条细线；③ 文字颜色全部走纯色变量，不依赖渐变。
 */
.sponsor-card {
  background-color: var(--cdr-surface, #ffffff);
  background-image: linear-gradient(135deg, color-mix(in srgb, var(--cdr-accent) 10%, var(--cdr-surface)), var(--cdr-surface) 62%);
  border-left: 3px solid var(--cdr-accent, #35c4f2);
  min-height: 96px;
}
.sponsor-card .sponsor-inner { min-height: 66px; }
.sponsor-kicker {
  font-size: .72rem; font-weight: 700; letter-spacing: .1em;
  /* ⚠️ 顺序有讲究：先一条**纯色**（老浏览器里 --polish-kicker 是 color-mix 算的，
     整条 color 会在"计算值"阶段失效 → 变成继承父级），再用令牌覆盖。
     纯色取主色而不是强调色：强调色 #35C4F2 写在白底上对比度只有 2.0，小字看不清。 */
  color: var(--cdr-primary, #1B2F5E);
  color: var(--polish-kicker);
  margin-bottom: 2px;
}
/* 按钮同理：.btn-primary 的 background-image 里也有 color-mix()。
   只写 background-image 时，老浏览器上按钮会变成"白字 + 透明底" —— 卡片上等于没有按钮。
   这里补一条 background-color 兜底（现代浏览器仍是渐变覆盖纯色）。 */
.sponsor-btn.btn-primary {
  background-color: var(--cdr-primary, #1B2F5E);
  color: #fff;
}

/* ---------------------------------------------------------- 个人中心：单栏 */
.me-stack { display: flex; flex-direction: column; gap: 14px; }
.me-stack > .me-card + .me-card,
.me-stack > .me-card + .me-grid,
.me-stack > .me-grid + .me-card,
.me-stack > .me-card + .me-stack { margin-top: 0; }
@media (max-width: 680px) { .me-stack { gap: 11px; } }

/* 折叠块直接当网格子项时会变成"没有底色的一条浮条"，所以外面套一层 .me-card。
   卡片自己有内距，折叠块的行内距相应收一点，别叠成两倍。 */
.me-card-fold { padding: 12px 18px; }
.me-card-fold > .me-fold:first-child { border-top: 0; }
.me-card-fold .me-fold-head { padding: 10px 2px; }
.me-card-fold .me-fold-body { padding-bottom: 10px; }
.me-card-fold .me-fold-body > *:last-child { margin-bottom: 0; }

/* ------------------------------------------------------ 卡片内距与字号统一 */
@media (min-width: 681px) {
  .me-card { padding: 18px 20px; }
  .me-card-fold { padding: 12px 20px; }
  .me-card .sub { font-size: .88rem; }
  .me-fold-head { font-size: .97rem; }
}
/* 站长反馈"字号偏小"：只抬个人中心里的小字，不动全站 */
.me-cards .text-xs { font-size: .81rem; line-height: 1.7; }
.me-cards .text-sm { font-size: .89rem; }
.me-card .sub { line-height: 1.65; }

/* 页头：徽章减到两颗，绑定/验证/邮箱改成一行灰字 */
.me-meta { display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap; margin-top: 7px; }
.me-meta-i {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .82rem; color: var(--cdr-muted); max-width: 100%;
}
.me-meta-i svg { flex: none; opacity: .85; }
.me-meta-i.is-warn { color: var(--cdr-warning); }
.me-meta-i > :last-child { overflow-wrap: anywhere; }

/* ------------------------------- 三块安全设置：图标 + 一句话用途 + 后果提示 */
.sec-row { --sec-accent: var(--cdr-accent); }
.sec-row--email { --sec-accent: #2563EB; }
.sec-row--phone { --sec-accent: #B45309; }
.sec-row--security_email { --sec-accent: #15803D; }
html[data-color-mode="dark"] .sec-row--email,
html[data-dark-forced="1"] .sec-row--email { --sec-accent: #7CB0FF; }
html[data-color-mode="dark"] .sec-row--phone,
html[data-dark-forced="1"] .sec-row--phone { --sec-accent: #F0B44A; }
html[data-color-mode="dark"] .sec-row--security_email,
html[data-dark-forced="1"] .sec-row--security_email { --sec-accent: #5FD07A; }
@media (prefers-color-scheme: dark) {
  html[data-color-mode="auto"] .sec-row--email { --sec-accent: #7CB0FF; }
  html[data-color-mode="auto"] .sec-row--phone { --sec-accent: #F0B44A; }
  html[data-color-mode="auto"] .sec-row--security_email { --sec-accent: #5FD07A; }
}
/* 左边一道 3px 的主题色竖线：三块一眼能分开，又不至于做成三张花卡 */
.sec-row { border-left: 3px solid color-mix(in srgb, var(--sec-accent) 55%, transparent); padding-left: 12px; }
.sec-head { align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.sec-head-ic {
  flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--sec-accent) 15%, transparent); color: var(--sec-accent);
}
.sec-head-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.sec-head-title { font-size: .96rem; }
.sec-head-purpose { font-size: .84rem; line-height: 1.55; color: var(--cdr-muted); }
.sec-head-state { margin-left: auto; font-size: .85rem; display: inline-flex; align-items: center; gap: 5px; }
.sec-head-state svg { flex: none; }
.sec-head-state.is-ok { color: var(--polish-ok); }
.sec-head-state.is-warn { color: var(--cdr-warning); }
.sec-head-state.is-none { color: var(--cdr-muted); }
.sec-note {
  display: flex; align-items: flex-start; gap: 7px; margin-top: 10px;
  padding: 8px 11px; border-radius: 9px; font-size: .84rem; line-height: 1.6;
  color: var(--cdr-muted);
  background: color-mix(in srgb, var(--sec-accent) 8%, transparent);
  border-left: 2px solid color-mix(in srgb, var(--sec-accent) 55%, transparent);
}
.sec-note svg { flex: none; margin-top: 2px; color: var(--sec-accent); }

/* v1.9.83：个人中心「QQ 绑定」那一行 —— 头像 + 昵称 + 两颗按钮。
   头像来自 QQ 开放平台（figureurl_qq_2，40×40 或 100×100），圆着放、
   加一圈描边，官方图不裁不拉伸。 */
.sec-qq-avatar {
  flex: none; width: 36px; height: 36px; border-radius: 50%;
  object-fit: cover; border: 1px solid var(--cdr-border); background: var(--cdr-surface);
}
.sec-qq-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.sec-qq-actions form { margin: 0; }
/* 这一行里的按钮跟折叠块同一档高度，别用页面主按钮的 44px（会把一行撑得很空） */
.sec-qq-actions .qq-btn { min-height: 34px; padding: 0 13px; font-size: .86rem; }
.sec-qq-actions .qq-btn .qq-btn-ic { width: 16px; height: 16px; border-radius: 0; }

/* --------------------------------- 深色下"主色当字色"的散件：安全分卡与页头徽章
   .me-suggest-p（+25 这类加分）、评分徽章都是 PHP 里内联 var(--cdr-primary/danger)
   上色的，深色下同样会掉到 1.2 左右。这里只在两个很小的作用域里把语义色换成
   深色可读的版本 —— 不能放到 .me-cards 上：那会连 .btn-primary 一起变浅，
   白字写在浅蓝上反而更糟。 */
html[data-color-mode="dark"] .me-score,
html[data-dark-forced="1"] .me-score,
html[data-color-mode="dark"] .me-badges,
html[data-dark-forced="1"] .me-badges {
  --cdr-primary: #8CC2FF;
  --cdr-success: #5FD07A;
  --cdr-warning: #F0B44A;
  --cdr-danger: #FF9A91;
}
@media (prefers-color-scheme: dark) {
  html[data-color-mode="auto"] .me-score,
  html[data-color-mode="auto"] .me-badges {
    --cdr-primary: #8CC2FF;
    --cdr-success: #5FD07A;
    --cdr-warning: #F0B44A;
    --cdr-danger: #FF9A91;
  }
}

/* -------------------------------------------------------- 成就墙（内嵌版） */
.me-ach-wall { display: block; }
.me-ach-hint {
  display: flex; align-items: flex-start; gap: 6px; margin: 0 0 12px;
  font-size: .84rem; line-height: 1.6; color: var(--cdr-muted);
}
.me-ach-hint svg { flex: none; margin-top: 2px; }
.me-ach-wall .ach-grid { margin-top: 8px; }
.me-ach-wall > *:last-child { margin-bottom: 0; }

/* @fragment:playerstats —— 个人中心「玩家数据」卡（v9），追加在 style.css 末尾
   站长新需求：个人中心的「游戏档案」里要能看到自己的各项游戏数据，并且能切换角色查看。
   数据本身来自 players 表，字段与格式化统一在 Model\PlayerStats（与 /players 共用），
   这里只补两件样式：角色切换条、以及"这一格没有数据"时的弱化显示。 */

/* 角色切换条：做成一条浅色横条，和卡片里的其它小工具（绑定码 / 表单）区分开 */
.me-char-switch {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px;
  background: color-mix(in srgb, var(--cdr-accent) 6%, transparent);
  border: 1px solid var(--cdr-border); border-radius: 11px;
}
.me-char-switch .form-label { font-size: .85rem; color: var(--cdr-muted); }
.me-char-switch .form-select { max-width: 220px; }

/* 没有数据的格子里显示的是 "—"：把数字压成灰色、别用 1.22rem 的粗体抢注意力，
   一眼能看出"这项真的没有数据"，而不是 0 */
.stat-mini.is-empty .v { color: var(--cdr-muted); font-weight: 600; }

/* 这一卡的数值比玩家页那几个格子长（"1,527.95 公里"、"2026-09-13"），
   格子放宽一点、字号收一点，免得一个数字被折成两行。只作用于这张卡。 */
[data-me-card="gamestats"] .stats-grid { grid-template-columns: repeat(auto-fit, minmax(152px, 1fr)); }
[data-me-card="gamestats"] .stat-mini .v { font-size: 1.12rem; }

/* 窄屏：下拉占满一行，别和"查看"按钮挤在一起换行 */
@media (max-width: 520px) {
  .me-char-switch { gap: 6px; }
  .me-char-switch .form-select { max-width: none; flex: 1 1 100%; }
  .me-char-switch .btn { flex: 0 0 auto; }
}

/* @fragment:brand-logo —— 品牌 logo 尺寸的唯一权威规则（v1.8.4）
   ------------------------------------------------------------------
   站长反馈「手机版 logo 存在 bug」。实测有两个原因，都已修掉：
     ① 页头 <img srcset="logo.png 1x, logo@2x.png 2x">，而 logo@2x.png 是一张**坏图**
        （真彩色但不透明、可见像素平均亮度 9.5，几乎是全黑）。所有 DPR≥2 的手机都会
        选中 2x，所以手机上是一团黑蓝色块，桌面（DPR=1）却是好的。
        → 已由修好的 logo.png 最近邻放大重新生成 logo@2x.png。
     ② logo.png 自己是**调色板 PNG 且没有 tRNS 块** → 完全不透明，背景是实心 rgb(4,2,4)。
        → 已把该背景色抠成透明。

   这里同时收拾历史上互相打架的 .brand-logo 规则。旧文件里同一个类被声明了 6 次以上
   （约 122 / 132 / 271 / 281 / 1325 行是旧的，1775-1790 行是后加的），全靠"谁写在后面谁赢"，
   手机断点下 max-width 出现过 46vw / 34vw / 30vw / 42vw / 50vw 五个值，改一处根本不知道
   最终是哪一个生效。

   处理方式：本节用**更高的选择器优先级**（.cdr-header .brand > .brand-logo，
   权重 0-3-0，高于旧规则的 0-1-0 / 0-2-0）而不是继续往末尾堆同名规则 ——
   这样它不再依赖书写顺序，前面那些旧声明从此不可能覆盖它，本节就是唯一权威。
   （那几处旧规则已经是死代码，可以在后续清理时安全删除。） */
.cdr-header .brand > .brand-logo {
  display: block;
  flex: none;
  height: 54px;
  width: auto;
  max-width: min(260px, 40vw);
  object-fit: contain;
  /* 用 auto（浏览器的高质量缩放）而不是 pixelated：
     900px 宽的像素画在页头只有 54px 高，缩小 9 倍以上时最近邻会丢笔画，
     「云岭」「CloudRidge」会糊成一团；实测 auto 在小尺寸下明显更清楚。 */
  image-rendering: auto;
  transition: transform .18s ease;
}
.cdr-header .brand:hover > .brand-logo { transform: translateY(-1px); }

.cdr-footer .brand > .brand-logo-footer {
  display: block;
  flex: none;
  height: 64px;
  width: auto;
  max-width: 60vw;
  object-fit: contain;
  image-rendering: auto;
  opacity: .95;
}

@media (max-width: 980px) {
  .cdr-header .brand > .brand-logo { height: 46px; max-width: min(220px, 38vw); }
  .cdr-footer .brand > .brand-logo-footer { height: 56px; }
}
@media (max-width: 720px) {
  .cdr-header .brand > .brand-logo { height: 42px; max-width: 36vw; }
  .cdr-footer .brand > .brand-logo-footer { height: 52px; max-width: 56vw; }
}
/* 手机：页头两侧的工具按钮会收成图标，logo 可以留得比旧规则（曾压到 30vw）
   大一点，否则 390px 下「云岭」两个字根本看不清。 */
@media (max-width: 520px) {
  .cdr-header .brand > .brand-logo { height: 40px; max-width: 44vw; }
}
@media (max-width: 400px) {
  .cdr-header .brand > .brand-logo { height: 36px; max-width: 42vw; }
  .cdr-footer .brand > .brand-logo-footer { height: 46px; max-width: 62vw; }
}


/* ==========================================================================
   @fragment: webp-color-compat —— 不支持 color-mix() 的老浏览器的兜底
   --------------------------------------------------------------------------
   为什么要这一段：
     站长在桌面端把赞助条看成"一条空条"。真凶不是样式丢失，而是**整条声明被丢掉**——
     现代写法 `color-mix(in srgb, …)` 在 Chrome < 111 / Safari < 16.2 上解析不了，
     含它的那条声明（包括用 `background` 简写把底色和渐变写在一起的那种）会**整条作废**。

     顺带说明一个容易误判的现象：DevTools 的 computed 面板里显示的
     `background-image: linear-gradient(135deg, color(srgb 0.920784 …))`
     **不是**源码里写了 `color()` 函数 —— 源码里一处都没有；
     那是浏览器把 color-mix 的**计算结果**序列化成 `color(srgb …)` 的样子。
     所以真正要兜底的是 color-mix()，不是 color()。

   这一段怎么兜：
     用 `@supports not (color: color-mix(...))` 把声明**限定给不支持 color-mix 的浏览器**，
     里面全部改成纯色 / 不依赖 color-mix 的渐变。支持 color-mix 的浏览器一条都不生效，
     主题配色照旧动态计算。

   诚实说明覆盖范围：全站样式表里 color-mix() 共 **308** 处
   （口径：按 "color-mix" 加左括号的字面量出现次数统计、含注释里的提及；
   style.css 196、admin.css 71、me-upload.css 14、me-growth.css 13、perm.css 10、
   customizer.css 4），这里覆盖的是 **会直接影响可见性与可读性**的那一类（令牌 +
   按钮 + 徽章 + 提示条 + 赞助卡 + 导航/页签选中态 + 表格 + 进度条 + 首屏）。
   装饰性的细微色差（例如各处的 4%–8% 淡背景）在老浏览器上会退化成
   "没有这层淡背景"，不影响内容与操作。
   me-upload.css（个人中心的上传控件）那 8 处由它自己末尾的
   @fragment: upload-color-mix-fallback 兜底 —— 那份样式在 style.css 之后才内联
   进来，兜底只有写在它自己文件里才压得住（见那段的选择器说明）。
   ========================================================================== */
@supports not (color: color-mix(in srgb, red, blue)) {

  /* ---------------------------------------------------------- 令牌：整组换纯色 */
  :root {
    --polish-soft-bg: rgba(27, 47, 94, .13);
    --polish-soft-bg-hover: rgba(27, 47, 94, .22);
    /* 与 color-mix(in srgb, accent 32%, primary) 的实际结果**同值**（#235F8D），
       所以老浏览器上的观感与现代浏览器一致，不是"随便找个能看的颜色"。
       直接用强调色（#35C4F2）当兜底时白底对比度只有 2.0，小字根本读不清。 */
    --polish-kicker: #235F8D;
    --polish-nav-bg: rgba(27, 47, 94, .13);
  }
  html[data-color-mode="dark"],
  html[data-dark-forced="1"] {
    --polish-soft-bg: rgba(255, 255, 255, .16);
    --polish-soft-bg-hover: rgba(255, 255, 255, .26);
    --polish-soft-bd: rgba(255, 255, 255, .30);
    --polish-ghost-bd: rgba(230, 237, 247, .24);
    --polish-nav-bg: rgba(255, 255, 255, .16);
  }
  @media (prefers-color-scheme: dark) {
    html[data-color-mode="auto"] {
      --polish-soft-bg: rgba(255, 255, 255, .16);
      --polish-soft-bg-hover: rgba(255, 255, 255, .26);
      --polish-soft-bd: rgba(255, 255, 255, .30);
      --polish-ghost-bd: rgba(230, 237, 247, .24);
      --polish-nav-bg: rgba(255, 255, 255, .16);
    }
  }

  /* ---------------------------------------------------------------- 按钮 */
  /* 关键：background-image 里的 color-mix 一旦失效，按钮会变成"白字 + 透明底"，
     在浅色卡片上等于**按钮消失**。所以先给一条纯色 background-color，
     再给一条不含 color-mix 的渐变（现代浏览器仍是渐变，老浏览器至少是实心按钮）。 */
  .btn-primary {
    background-color: var(--cdr-primary, #1B2F5E);
    background-image: linear-gradient(100deg, var(--cdr-primary, #1B2F5E) 0%, var(--cdr-primary-2, #0A1C46) 100%);
    color: #fff;
  }
  .btn-soft { background: rgba(27, 47, 94, .12); color: var(--cdr-primary, #1B2F5E); }
  .btn-soft:hover { background: rgba(27, 47, 94, .20); }
  html[data-color-mode="dark"] .btn-soft,
  html[data-dark-forced="1"] .btn-soft { background: rgba(255, 255, 255, .16); color: var(--cdr-text, #e6edf7); }

  /* ---------------------------------------------------------------- 徽章 */
  .badge { background: rgba(27, 47, 94, .14); color: var(--cdr-primary, #1B2F5E); }
  .badge-soft { background: rgba(100, 116, 139, .15); color: var(--cdr-muted, #5C6E8F); }
  .badge-success { background: rgba(34, 197, 94, .17); color: var(--cdr-success, #16a34a); }
  .badge-warn { background: rgba(245, 158, 11, .20); color: var(--cdr-warning, #b45309); }
  .badge-danger { background: rgba(239, 68, 68, .16); color: var(--cdr-danger, #dc2626); }
  .badge-role { background: rgba(100, 116, 139, .16); }

  /* -------------------------------------------------------------- 提示条 */
  .alert-success { background: rgba(34, 197, 94, .13); color: var(--cdr-success, #16a34a); border-color: rgba(34, 197, 94, .30); }
  .alert-warn { background: rgba(245, 158, 11, .15); color: #b45309; border-color: rgba(245, 158, 11, .32); }
  .alert-danger { background: rgba(239, 68, 68, .13); color: var(--cdr-danger, #dc2626); border-color: rgba(239, 68, 68, .30); }

  /* ---------------------------------------------------- 赞助条（本次问题的主体） */
  /* 底色已是显式 background-color，这里再把渐变换成不含 color-mix 的版本，
     老浏览器也能看出"这是一张有底色的卡"，而不是一条透明细线。 */
  .sponsor-card {
    background-color: var(--cdr-surface, #fff);
    background-image: linear-gradient(135deg, rgba(53, 196, 242, .10), var(--cdr-surface, #fff) 62%);
    border-left-color: var(--cdr-accent, #35c4f2);
    min-height: 96px;
    color: var(--cdr-text, #12213F);
  }
  .sponsor-card .sponsor-inner { min-height: 66px; }
  /* 不再另外写 .sponsor-kicker 的颜色：令牌 --polish-kicker 已在上面换成 #235F8D，
     规则本身用的是 var(--polish-kicker)，一处改、处处生效。 */
  .sponsor-title { color: var(--cdr-text, #12213F); }
  .sponsor-card .sponsor-text p { color: var(--cdr-muted, #5C6E8F); }

  /* 深色模式下带内联 .badge 也是主色字 + 主色底，一起兜住 */
  html[data-color-mode="dark"] .badge:not(.badge-soft):not(.badge-success):not(.badge-warn):not(.badge-danger):not(.badge-role),
  html[data-dark-forced="1"] .badge:not(.badge-soft):not(.badge-success):not(.badge-warn):not(.badge-danger):not(.badge-role) {
    color: #8CC2FF;
    background: rgba(255, 255, 255, .16);
  }

  /* ------------------------------------------------- 首屏 / 页头 / 选中态 */
  .hero {
    background-color: var(--cdr-bg, #F4FBFE);
    background-image: radial-gradient(1100px 520px at 12% -10%, rgba(27, 47, 94, .16), transparent 62%),
                      radial-gradient(900px 460px at 88% 4%, rgba(10, 28, 70, .14), transparent 60%);
  }
  .hero::after { background-image: radial-gradient(circle at 1px 1px, rgba(100, 116, 139, .30) 1px, transparent 0); }
  .hero-eyebrow { background: rgba(255, 255, 255, .72); }
  .main-nav a:hover, .nav-group-btn:hover { background: rgba(27, 47, 94, .10); }
  .main-nav a.active, .nav-group.is-active .nav-group-btn { background: rgba(27, 47, 94, .13); }
  .dropdown-item:hover { background: rgba(27, 47, 94, .12); }
  .main-nav a.nav-sponsor:hover { background: rgba(225, 29, 72, .12); }
  .me-tab.is-on { background: rgba(27, 47, 94, .13); border-color: rgba(27, 47, 94, .40); }
  .ip-box { border-color: rgba(27, 47, 94, .42); }
  .guide-nav a.active { background: rgba(27, 47, 94, .13); }

  /* ------------------------------------------------------------ 表格 / 区块 */
  table.cdr-table th { background: rgba(100, 116, 139, .08); }
  table.cdr-table tbody tr:nth-child(even) { background: rgba(100, 116, 139, .04); }
  table.cdr-table tbody tr:hover { background: rgba(27, 47, 94, .05); }
  .raw-json { background: rgba(100, 116, 139, .10); }
  .section-alt { background: rgba(100, 116, 139, .06); }
  .cdr-footer { background: rgba(100, 116, 139, .06); }
  .fact, .tool-msg, .code-block { background: rgba(100, 116, 139, .06); }

  /* --------------------------------------------------------- 进度 / 骨架屏 */
  .meter-track, .percentile-bar, .nm-bar, .radar-bar { background: rgba(100, 116, 139, .18); }
  .skeleton, .skeleton-line {
    background: linear-gradient(90deg, rgba(100, 116, 139, .12) 25%, rgba(100, 116, 139, .20) 37%, rgba(100, 116, 139, .12) 63%);
    background-size: 400% 100%;
  }
  .spinner { border-color: rgba(27, 47, 94, .30); border-top-color: var(--cdr-primary, #1B2F5E); }
  .chart-yaxis-right { color: var(--cdr-warning, #b45309); }
  ::selection { background: rgba(27, 47, 94, .30); }
}

/* ---- 时长显示：主数字是小时，天数作小字副标（Helpers::durationHtml / app.js） ---- */
.dur-sub {
  font-size: .78em;
  font-weight: 400;
  color: var(--cdr-muted, #6b7280);
  white-space: nowrap;
}

/* ==========================================================================
   @fragment: player-level-badge — 名字右下角的「游戏等级」小徽章
   --------------------------------------------------------------------------
   用在哪：/players 列表的玩家名、/players/<名字> 顶部卡片的大标题。
   等级数字只有一个来源：Model\PlayerLevel（每累计 10 小时 1 级，Lv.1 起）。

   为什么用 rgba 而不是 color-mix()：这一段就是给"浏览器支持度"兜底的样式，
   自己再用 color-mix 会把老浏览器上的徽章变成"没有底色的一串数字"——
   和它旁边那段 activity-color-fallback 是同一个道理。
   ========================================================================== */
.name-lv {
  display: inline-flex;
  align-items: flex-end;      /* 徽章贴到名字右下角，不是垂直居中 */
  flex-wrap: wrap;            /* 390px 下折到名字下面一行，绝不顶宽 */
  gap: 2px 5px;
  min-width: 0;
}
.lv-badge {
  flex: none;
  white-space: nowrap;
  font-size: .64rem;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: .02em;
  padding: 0 5px;
  border-radius: 999px;
  color: var(--cdr-primary, #1B2F5E);
  background: rgba(27, 47, 94, .10);
  border: 1px solid rgba(27, 47, 94, .18);
  font-variant-numeric: tabular-nums;
  transform: translateY(1px);
}
/* 详情页顶部卡片：跟 h1 同排，名字在左、徽章蹲在名字右下角 */
.player-title { display: flex; align-items: flex-end; flex-wrap: wrap; gap: .3rem .5rem; }
.player-title-name { line-height: 1.15; }
.lv-badge-lg { font-size: .72rem; padding: 1px 7px; }
/* ---------------------------------------------------------------- 玩家页页头
 * v1.9.33：个性签名从「名字那一列」搬到了钉住行**外面**（钉住行会
 * `position: sticky`，签名留在里面就会跟着一起钉住）。
 * 搬出来之后位置靠这里的 92px 缩进补回来 —— 头像 76px + `.gap-2` 的 16px。
 * 窄屏下头像会换到名字上面，那一列从 x=0 开始，所以缩进要去掉，
 * 否则签名会悬在屏幕中间。 */
.player-head-sig { margin-left: 92px; }
@media (max-width: 560px) { .player-head-sig { margin-left: 0; } }

html[data-color-mode="dark"] .lv-badge,
html[data-dark-forced="1"] .lv-badge {
  color: #8CC2FF;
  background: rgba(140, 194, 255, .14);
  border-color: rgba(140, 194, 255, .28);
}
@media (prefers-color-scheme: dark) {
  html[data-color-mode="auto"] .lv-badge {
    color: #8CC2FF;
    background: rgba(140, 194, 255, .14);
    border-color: rgba(140, 194, 255, .28);
  }
}

/* ==========================================================================
   @fragment: activity-color-fallback — 活跃度（热力图 / 每日柱状图 / 图例 /
   图表 tooltip）与个人中心成长条在不支持 color-mix() 的浏览器上的兜底
   --------------------------------------------------------------------------
   站长反馈的 bug：老浏览器上"个人主页的活跃度"看着不对。根因不是样式"丢了"，
   而是**整条声明作废**：`background: color-mix(in srgb, …)` 在 Chrome < 111 /
   Safari < 16.2 上解析不了，含它的那条声明（包括 background 简写）整条被丢掉。

   于是最要命的一处是热力图：
     · `.heat-cell` 的底色被丢 → 格子完全透明，看不出"有没有活动"；
     · `.heat-cell.l1/.l2/.l3` 也被丢（`.l4` 用的是纯 var，所以只有最深那格还在）
       → 30 天热力图变成"全是白格 + 零星几个深格"，**深浅差异没了**，热力图等于失效；
     · 图例的 5 个色块原来写在 player.php 的**行内 style** 里 —— 行内样式 @supports
       管不到（能盖它的只有 !important 或更高优先级），所以图例整排是空白；
     · `.day-bar`（每日柱状图）与 `.chart-tip`（图表 tooltip 的底色）同理：
       tooltip 丢掉底色后，文字直接浮在曲线上，读不出来。

   这一段怎么兜：把声明**限定给不支持 color-mix 的浏览器**（@supports not），
   里面全部换成等价的 rgba()。注意 rgba(X, a) 与 color-mix(in srgb, X a%, transparent)
   在同一个 X 上是**完全等值**的（color-mix 就是按 alpha 合成），所以老浏览器上的
   观感与支持 color-mix 的浏览器逐格一致，不是为了"能看见"随便挑的颜色。
   深色模式下 --cdr-primary / --cdr-muted 会换值（#8CC2FF / #93A6C4），
   所以色阶也要跟着换一份，否则深底上的深蓝格子等于看不见。

   ⚠️ 主题色由后台「外观设置」注入时（Themes::css()），这里的 rgba 是固定色值，
   不会跟着自定义配色走 —— 与文件末尾那段赞助条兜底是同一个取舍：先保证"看得清"。
   ========================================================================== */

/* 图例色块：从 player.php 的行内 style 提出来（行内样式没法被 @supports 兜底）。 */
.heat-swatch { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }
.heat-swatch.s0 { background: color-mix(in srgb, var(--cdr-muted) 14%, transparent); }
.heat-swatch.s1 { background: color-mix(in srgb, var(--cdr-primary) 28%, transparent); }
.heat-swatch.s2 { background: color-mix(in srgb, var(--cdr-primary) 48%, transparent); }
.heat-swatch.s3 { background: color-mix(in srgb, var(--cdr-primary) 68%, transparent); }
.heat-swatch.s4 { background: var(--cdr-primary); }

@supports not (color: color-mix(in srgb, red, blue)) {

  :root {
    --heat-0:     rgba(92, 110, 143, .14);   /* 无活动格 / 图例第 1 格（muted 14%） */
    --heat-1:     rgba(27, 47, 94, .28);     /* 热力 1 级（primary 28%） */
    --heat-2:     rgba(27, 47, 94, .48);     /* 热力 2 级 */
    --heat-3:     rgba(27, 47, 94, .68);     /* 热力 3 级（4 级是纯 --cdr-primary，本来就没事） */
    --heat-track: rgba(92, 110, 143, .18);   /* 进度条轨道（muted 18%） */
    --heat-track2: rgba(92, 110, 143, .32);  /* 开关轨道（muted 32%） */
    --heat-bar:   rgba(27, 47, 94, .30);     /* 每日柱状图（primary 30%） */
    --heat-tint7: rgba(27, 47, 94, .07);     /* 成长卡片底（primary 7%） */
    --heat-tint12: rgba(27, 47, 94, .12);    /* 时间轴图标底（primary 12%） */
    --heat-tint-success: rgba(27, 47, 94, .16);   /* success 15~16%（本服 --cdr-success 就是藏青） */
    --heat-tint-warning: rgba(194, 130, 15, .18); /* warning 18% */
    --heat-hover: rgba(92, 110, 143, .05);        /* 开关行 hover（muted 5%） */
  }
  html[data-color-mode="dark"],
  html[data-dark-forced="1"] {
    --heat-0:     rgba(147, 166, 196, .14);
    --heat-1:     rgba(140, 194, 255, .28);
    --heat-2:     rgba(140, 194, 255, .48);
    --heat-3:     rgba(140, 194, 255, .68);
    --heat-track: rgba(147, 166, 196, .18);
    --heat-track2: rgba(147, 166, 196, .32);
    --heat-bar:   rgba(140, 194, 255, .30);
    --heat-tint7: rgba(140, 194, 255, .07);
    --heat-tint12: rgba(140, 194, 255, .12);
    /* 深色下故意比浅色亮一档：老浏览器没有 color-mix，宁可"看得见" */
    --heat-tint-success: rgba(140, 194, 255, .16);
    --heat-tint-warning: rgba(226, 164, 60, .22);
    --heat-hover: rgba(147, 166, 196, .06);
  }
  @media (prefers-color-scheme: dark) {
    html[data-color-mode="auto"] {
      --heat-0:     rgba(147, 166, 196, .14);
      --heat-1:     rgba(140, 194, 255, .28);
      --heat-2:     rgba(140, 194, 255, .48);
      --heat-3:     rgba(140, 194, 255, .68);
      --heat-track: rgba(147, 166, 196, .18);
      --heat-track2: rgba(147, 166, 196, .32);
      --heat-bar:   rgba(140, 194, 255, .30);
      --heat-tint7: rgba(140, 194, 255, .07);
      --heat-tint12: rgba(140, 194, 255, .12);
      --heat-tint-success: rgba(140, 194, 255, .16);
      --heat-tint-warning: rgba(226, 164, 60, .22);
      --heat-hover: rgba(147, 166, 196, .06);
    }
  }

  /* ------------------------------------------------------------ 活动热力图 */
  .heat-cell { background: var(--heat-0); }
  .heat-cell.l1 { background: var(--heat-1); }
  .heat-cell.l2 { background: var(--heat-2); }
  .heat-cell.l3 { background: var(--heat-3); }
  /* l4 是纯 var(--cdr-primary)，不受影响，这里也跟着写一遍只为"色阶四档齐全" */
  .heat-cell.l4 { background: var(--cdr-primary); }
  .heat-swatch.s0 { background: var(--heat-0); }
  .heat-swatch.s1 { background: var(--heat-1); }
  .heat-swatch.s2 { background: var(--heat-2); }
  .heat-swatch.s3 { background: var(--heat-3); }

  /* -------------------------------------------------------- 每日时长柱状图 */
  .day-bar { background: var(--heat-bar); }
  .day-bar[data-v="0"] { background: var(--heat-track); }

  /* ------------------------------------------- 图表 tooltip（底色丢了就读不出） */
  .chart-tip {
    background: var(--cdr-surface, #fff);
    border-color: var(--cdr-border, #DCE6F2);
  }

  /* ------------------------------------------------- 个人中心「成长 / 本周活跃」
     me-growth.css 是控制器内联进 <style> 的（在 style.css 之后），所以这一段
     **必须比它优先级更高**，光"写在后面"没用 —— 事实核查过的机制：
     `background: color-mix(in srgb, var(--x) 20%, transparent)` 里带 var()，
     而 var() 让浏览器必须把校验推迟到计算值时；于是老浏览器上这条声明
     不是"解析时丢掉"，而是"计算时失效 → 该属性回落到 initial（透明）"。
     它照样占着层叠的赢家位置，只在选择器优先级更低/更靠前时才轮得到我们的兜底。
     所以下面统一加一层 `body`（多一个类型选择器，优先级 +1），
     既不写 !important，也不会误伤 `:checked`（那条是 --cdr-success、本来就有效）。 */
  body .meg-growth { background: var(--heat-tint7); }
  body .meg-bar { background: var(--heat-track); }
  body .meg-bar > i { background: var(--cdr-primary); }
  body .meg-delta { background: var(--heat-0); }
  body .meg-delta.is-up { background: var(--heat-tint-success); }
  body .meg-delta.is-down { background: var(--heat-tint-warning); }
  body .meg-tl-ic { background: var(--heat-tint12); }
  body .meg-tl-row[data-tl-type="gallery"] .meg-tl-ic { background: rgba(53, 196, 242, .16); }
  body .meg-tl-row[data-tl-type="adv"] .meg-tl-ic,
  body .meg-tl-row[data-tl-type="badge"] .meg-tl-ic { background: var(--heat-tint-warning); }
  body .meg-tl-row[data-tl-type="hours"] .meg-tl-ic,
  body .meg-tl-row[data-tl-type="first_join"] .meg-tl-ic { background: var(--heat-tint-success); }
  body .meg-tl-char { background: var(--heat-0); }
  body .meg-sw:hover { background: var(--heat-hover); }
  body .meg-sw-track { background: var(--heat-track2); }
  html[data-color-mode="dark"] body .meg-bar > i,
  html[data-dark-forced="1"] body .meg-bar > i { background: var(--cdr-primary, #8CC2FF); }

  /* 图片上传区（me-upload.css 也是内联在后面的文件，同样要用 body 提升优先级）。
     这几处丢了只是"少了那层淡底"，不影响上传，但空状态横幅会变成完全透明，
     所以补一条底色；用 background-color 长写，正好压过失效的 background 简写。 */
  body .cdr-up-url { background-color: rgba(92, 110, 143, .06); }
  body .cdr-up-banner.is-empty { background-color: var(--cdr-bg, #F4F8FC); }
  body .cdr-up-box.is-drag .cdr-up-drop,
  body .cdr-up.is-drag .cdr-up-drop { background-color: rgba(27, 47, 94, .10); }
}


/* ==========================================================================
   @fragment: achievements-v10 —— 成就徽章墙（8 分类）/ 五维雷达说明 / 趋势图悬停提示
   --------------------------------------------------------------------------
   追加在 style.css 末尾（不改动上面任何既有规则，老浏览器不认识的新属性会自动忽略）。

   三块内容：
     · .ach-cats / .ach-cat-*  ：徽章按 8 个分类分组，组内是原来的 .ach-grid；
     · .radar-key / .radar-notes：雷达图的图例与「每一维量什么」的说明列表
       —— 站长反馈「五维图每个点没有备注可能不懂」，所以说明默认展开、不用点；
     · .chart-tip 在 390px 下的可用性：tooltip 宽度与落点已经在 app.js 里夹住了，
       这里只保证它不被窄屏挤成竖排、以及触摸时能正常出现。
   ========================================================================== */

.ach-lead { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ach-legend { display: inline-flex; align-items: center; gap: 6px; margin-left: 4px; }
.ach-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.ach-dot.is-on { background: var(--cdr-primary); }
.ach-dot.is-off { background: color-mix(in srgb, var(--cdr-text) 22%, transparent); }

.ach-cats { display: flex; flex-direction: column; gap: 14px; }
.ach-cat-head {
  display: flex; align-items: center; gap: 7px;
  margin-bottom: 7px; padding-bottom: 5px;
  border-bottom: 1px dashed var(--cdr-border);
  font-size: .9rem;
}
.ach-cat-ic { color: var(--cdr-primary); display: inline-flex; }
.ach-cat-head .badge { margin-left: auto; }

/* 徽章卡：名称 / 说明 / 进度（未达成灰显但仍可见 —— 这是驱动力） */
.ach.is-on { border-color: color-mix(in srgb, var(--cdr-primary) 32%, var(--cdr-border)); }
.ach-meta {
  font-size: .68rem; color: var(--cdr-muted);
  display: inline-flex; align-items: center; gap: 4px;
  font-variant-numeric: tabular-nums; line-height: 1.3;
}
.ach.is-on .ach-meta { color: color-mix(in srgb, var(--cdr-primary) 72%, var(--cdr-muted)); }
.ach-bar {
  width: 100%; height: 4px; margin-top: 2px;
  border-radius: var(--cdr-radius-pill); overflow: hidden;
  background: color-mix(in srgb, var(--cdr-text) 10%, transparent);
}
.ach-bar > i { display: block; height: 100%; border-radius: inherit; background: var(--cdr-muted); }

/* ---- 雷达图：图例 + 每一维的说明 ---- */
.radar-key { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: .78rem; color: var(--cdr-muted); }
.radar-key-item { display: inline-flex; align-items: center; gap: 6px; }
.radar-swatch { width: 14px; height: 10px; border-radius: 3px; display: inline-block; }
.radar-swatch.is-me { background: color-mix(in srgb, var(--cdr-primary) 30%, transparent); border: 1.5px solid var(--cdr-primary); }
.radar-swatch.is-avg { background: color-mix(in srgb, var(--cdr-muted) 12%, transparent); border: 1.5px dashed var(--cdr-muted); }

.radar-notes {
  border: 1px solid var(--cdr-border); border-radius: var(--cdr-radius);
  padding: 9px 11px; background: color-mix(in srgb, var(--cdr-text) 3%, transparent);
}
.radar-notes-title { display: flex; align-items: center; gap: 5px; font-size: .78rem; color: var(--cdr-muted); margin-bottom: 5px; }
.radar-notes ul { margin: 0; padding-left: 16px; }
.radar-notes li { font-size: .78rem; line-height: 1.55; color: var(--cdr-muted); }
.radar-notes li strong { color: var(--cdr-text); margin-right: 4px; }
/*
 * v1.9.51：这段说明改成了默认收起的 <details>（它原来把右栏撑得比左栏高一截，
 * 左栏下面因此空出一大块）。下面几条只做三件事：
 *   ① 去掉浏览器自带的小三角（.radar-notes-title 是 flex，三角本来也不显示），
 *      换成自己的箭头放在行尾，收/展时转 180°；
 *   ② 收起时不要留那 5px 的下边距（否则看着像悬空一行）；
 *   ③ 整行可点、有 hover 反馈，别让人猜这里能点。
 * 没用 JS —— 原生 <details> 键盘可达、读屏可读。
 */
.radar-notes > summary { list-style: none; cursor: pointer; margin-bottom: 0; }
.radar-notes > summary::-webkit-details-marker { display: none; }
.radar-notes > summary:hover { color: var(--cdr-text); }
.radar-notes > summary::after {
  content: '▾'; margin-left: auto; font-size: .68rem; line-height: 1;
  transition: transform .16s ease;
}
.radar-notes[open] > summary { margin-bottom: 5px; }
.radar-notes[open] > summary::after { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .radar-notes > summary::after { transition: none; }
}

/* ---- 图表悬停提示：窄屏（390px）下的可读性 ---- */
@media (max-width: 480px) {
  .chart-tip { min-width: 118px; max-width: 62vw; padding: 7px 9px; font-size: .74rem; }
  .chart-tip .ct-time { font-size: .68rem; }
  .ach-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
  .ach { padding: 10px 6px; }
  .ach-name { font-size: .78rem; }
  .radar-notes li { font-size: .76rem; }
}

/* ==========================================================================
   @fragment: chart-range —— 图表时间范围选择器 / 断线空白区 / 手机端（v11）
   --------------------------------------------------------------------------
   追加在 style.css 末尾（不改动上面任何既有规则）。

   解决站长截图里的三件事：
     · 窄屏卡片下方一大片空白 → 容器高度改由 app.js 按实际宽度算（这里只管布局），
       `.chart-holder` 不再吃固定高度；
     · 范围选择器在 390px 下不许挤成一团 → nowrap + 自动换行（宁可换行也不压扁）；
     · 悬停提示压在曲线上 / 越出卡片 → 落点由 app.js 夹在绘图区内，这里加个宽度上限。

   另外给「服务器离线」的空白区（.chart-gap）与提示里那句说明留了样式钩子。
   ========================================================================== */

/* 图表容器：高度交给 app.js 按实际渲染尺寸设置，这里只保证宽度铺满 */
.chart-holder { width: 100%; }

/* ---- 分段选择器（24 小时 / 7 日 / 30 日 / 1 年）---- */
.chart-range {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin: 0 0 10px;
}
.chart-range-btn {
  -webkit-appearance: none; appearance: none;
  border: 1px solid var(--cdr-border); background: transparent;
  color: var(--cdr-muted); font: inherit; font-size: .78rem; line-height: 1;
  padding: 7px 11px; border-radius: var(--cdr-radius-pill);
  cursor: pointer; white-space: nowrap;   /* 文案不换行：宁可整排换行，也不压成两行小字 */
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.chart-range-btn:hover {
  color: var(--cdr-text);
  border-color: color-mix(in srgb, var(--cdr-primary) 40%, var(--cdr-border));
}
.chart-range-btn.is-on {
  background: color-mix(in srgb, var(--cdr-primary) 14%, transparent);
  border-color: color-mix(in srgb, var(--cdr-primary) 46%, var(--cdr-border));
  color: color-mix(in srgb, var(--cdr-primary) 82%, var(--cdr-text));
  font-weight: 600;
}
.chart-range-btn:focus-visible { outline: 2px solid var(--cdr-primary); outline-offset: 2px; }

/* 取数中：整块降透明度，比插一个"加载中"占位符更不跳 */
[data-chart-block].is-chart-loading .chart-holder { opacity: .5; transition: opacity .12s ease; }

/* ---- 悬停提示 ---- */
.chart-tip { max-width: 72vw; }              /* 长语言（de/ru）也不至于横穿整张卡片 */
.chart-tip .ct-gap {                          /* 断点那句「此前服务器离线」 */
  color: var(--cdr-muted); font-size: .7rem; margin: 1px 0 4px;
}
.chart-tip .ct-row strong { white-space: nowrap; }

/* ---- 手机端：选择器与提示再收一点 ---- */
@media (max-width: 480px) {
  .chart-range { gap: 3px; margin-bottom: 8px; }
  .chart-range-btn { padding: 6px 9px; font-size: .74rem; }
  [data-chart-retention] { line-height: 1.5; }
}

/* ==========================================================================
   @fragment: heatmap-grid —— 「最近 30 天在线时长 / 活跃度」两张卡的横向溢出
   --------------------------------------------------------------------------
   站长反馈：/players/<名字> 上这两张卡"卡片和数据左右溢出"（1912px 屏明显）。

   真正的根因**不在 CSS**，在 player.php：雷达卡片后面多了一个 </div>，
   把 275 行打开的 .cdr-container 提前关掉 → 「核心统计」「近 30 天在线时长」
   「活跃度」以及整条 .grid-split-wide 都变成 section.section 的直接子元素，
   脱离 1240px 居中容器、直接铺满视口。1280px 下容器本来就只有 1240，
   左右各差 20px 看不出来；1912px 下左右各差 336px 才暴露。已删除那个 </div>。

   CSS 这边只有两处次要问题，都已就地修掉（这一段**故意不放任何 .heatmap 声明**，
   避免又变成"谁写在后面谁赢"）：
     1. 四条 .heatmap 规则打架（旧 889 / 1274 / 1276 / 1328 行）：同优先级、后写覆盖，
        repeat(15,1fr) 的"15 列 × 2 行"被 auto-fill+minmax 压掉，于是宽屏 1 行 30 格、
        1280 是 2 行(20+10)、390 是 3 行(14+14+2) 的残缺行。现在列数只在 .heatmap 一处定义。
     2. .heat-cell { min-height: 26px }（旧 1237 行）配 aspect-ratio:1 → 反过来把格子
        宽度顶到 26px，超过 auto-fill 算出的 22.6px 轨道 → 390px 下溢出 3px。已删。

   验收：七个宽度（1920/1600/1440/1280/1024/768/390）下每张卡与每个图表容器
   都满足 scrollWidth <= clientWidth，documentElement.scrollWidth == 视口宽。
   复测脚本：node .tools/_hm-probe.mjs /players/<名字> <输出前缀> 1920 1600 1440 1280 1024 768 390
   div 配平自检：php .tools/_hm-balance.php（最终深度必须为 0）
   ========================================================================== */

/* ==========================================================================
   @fragment: radar-beside-achievements —— 能力雷达并到成就墙右边
   --------------------------------------------------------------------------
   站长：雷达图不该独占整行，应该放到成就墙右边。

   落点（都在 player.php 一处 inline style + 这里两条规则，不动折叠那套 DOM/JS）：
     · 外层那个 .grid（player.php 438 行）把列定义从 1.3fr/.7fr 改成
       `minmax(0,1fr) minmax(0,384px)` —— 右列 384px 是「窄列」的宽度上限：
       雷达卡 content 340px，5 条进度条还排得开，SVG 仍按 max-width:320px 居中。
       ⚠️ 列定义**必须留在 inline style**：@media 盖不过 inline（0-1-0-0），
       当年 .heatmap 那个「后写覆盖」的坑就是这么踩的，所以堆叠只能在这儿用
       !important 强行翻成单列（见下）。
     · ≤900px 堆叠成上下：成就墙整行、雷达卡整行 —— 与 .grid-split-wide
       (style.css 343 行) 同一个断点，站长在平板竖屏下优先看成就。

   雷达卡内部：窄列里原来 `.radar-wrap` 的 1fr/1fr 两栏会把 SVG 和 5 条进度条
   各挤成 ~170px，所以这里改成上下（SVG 在上、图例在下）。
   ⚠️ `.cdr-body .radar-svg` 的 max-width:320px（上面 1943 行）**不能去掉前缀**，
   也不能在这段里重新声明 .radar-svg 的宽度，否则 1216 行的
   `.cdr-body svg { max-width:100% }` 会把雷达图放大回去。

   验收：node .tools/_rl2-probe.mjs after 1920 1600 1440 1280 1024 768 390
   ========================================================================== */
@media (max-width: 900px) {
  /* 盖 inline 的 grid-template-columns：窄屏一律单列（成就整行 → 雷达整行） */
  .grid[style*="minmax(0,1fr) minmax(0,384px)"] { grid-template-columns: minmax(0, 1fr) !important; }
}
/* 雷达卡：SVG 在上、五条进度条在下（窄列里再左右分栏会挤成一团） */
.radar-card .radar-wrap {
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

/* ==========================================================================
   @fragment: ui-motion —— 全站动效与加载反馈（A 滚动入场 / B 悬停点击 / C 骨架屏）
   --------------------------------------------------------------------------
   和 assets/js/ui-motion.js 是**一套东西的两半**，改一边记得看另一边。

   三条硬规矩（写在最前面，别在下面任何地方破例）：
     1. 本节所有新动效都在文件最底部那段 @media (prefers-reduced-motion: reduce) 里关掉；
     2. **默认可见**：滚动入场只在 html.js-anim（= JS 真的跑起来了，且用户没要求减少
        动效）时才把元素设为不可见。禁 JS / 观察器不支持的浏览器里 .reveal 没有任何
        效果，内容照常显示 —— 绝不能写成"默认 opacity:0 等 JS 来点亮"；
     3. 悬停反馈一律包在 @media (hover: hover) 里：触屏上 :hover 会"黏住"，
        手指离开后卡片还浮着。触摸设备要的反馈走 :active（不挑设备）。

   ⚠️ 本节**故意不写任何 .heatmap 声明**：那个网格只由文件前面唯一一处定义
      （后写覆盖的坑踩过一次，别再加第二条）。
   ========================================================================== */

/* ---------------------------------------------------------------- A. 滚动入场 */

/* .fade-in（首页卡片 / 动态 / 相册）是"进页面立刻播一次"的动画，而且
   animation-fill-mode: both —— 它的终态 opacity:1 属于动画来源，会盖掉
   .reveal 的 opacity:0。结果就是这些卡片从来不等滚动，一进页面就全亮。
   既然它们现在都交给 .reveal 了，js-anim 期间就让 .fade-in 让位。
   ⚠️ 只在 html.js-anim 下让位：JS 没跑时 .fade-in 照旧播，页面不会变成一潭死水。 */
html.js-anim .fade-in { animation: none; }

/* 表格行 / 列表行：只淡入、不位移（tr 上的 transform 在部分浏览器会牵动表格布局） */
html.js-anim .reveal.reveal-row { transform: none; }

/* 手机端把入场位移收小一点，长列表快速滚动时更稳。
   ⚠️ 必须带 `:not(.is-visible)`：这条选择器（4 类 + 1 元素）比
   `html.js-anim .reveal.is-visible`（3 类 + 1 元素）优先级高，
   少一个 :not 的话手机上元素淡入结束后会**永久停在 +12px**。 */
@media (max-width: 680px) {
  html.js-anim .reveal:not(.is-visible):not(.reveal-left):not(.reveal-scale) {
    transform: translateY(12px);
  }
}

/* ------------------------------------------------- B. 悬停 / 点击 / 复制反馈 */

/* 统一给这些"可点/可关注的块"加上同一条 transition（原来是各写各的、有的干脆没有，
   所以有的地方浮得动、有的地方是硬跳）。 */
.card, .card.is-hoverable, .stat, .gallery-item, .node-card, .me-card, .panel,
.tool-chip, .code-chip, .icon-btn {
  transition: transform .18s ease, box-shadow .18s ease,
              border-color .18s ease, background-color .18s ease, color .18s ease;
}

@media (hover: hover) {
  /* 卡片：轻微上浮 + 阴影加深（全站一套，各页不再各写一份） */
  .card:hover,
  .card.is-hoverable:hover,
  .stat:hover,
  .me-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--cdr-shadow-lg);
  }
  .gallery-item:hover { transform: translateY(-4px); box-shadow: var(--cdr-shadow-lg); }
  .node-card:hover { transform: translateY(-2px); box-shadow: var(--cdr-shadow-lg); }
  /* 大面板只加深描边、不位移：里面通常塞着表格和图表，整体晃一下很吵 */
  .panel:hover { border-color: color-mix(in srgb, var(--cdr-primary) 34%, var(--cdr-border)); }
  .tool-chip:hover, .code-chip:hover { transform: translateY(-1px); }

  /* ⚠️ 踩过的坑：被滚动入场标记过的元素同时带着 `.reveal.is-visible`，
     而 `html.js-anim .reveal.is-visible { transform: none }`（3 类 + 1 元素）的
     优先级**高于**上面的 `.card:hover`（1 类 + 1 伪类），会把上浮整个吃掉 ——
     表现是"阴影变了但卡片不动"。这里用同样带 html.js-anim 前缀的选择器盖回来：
     本段在文件末尾、同优先级后写为准，所以能赢；鼠标移开后仍由那条规则收回原位。 */
  html.js-anim .card.reveal:hover,
  html.js-anim .card.is-hoverable.reveal:hover,
  html.js-anim .stat.reveal:hover,
  html.js-anim .me-card.reveal:hover,
  html.js-anim .node-card.reveal:hover { transform: translateY(-2px); }
  html.js-anim .gallery-item.reveal:hover { transform: translateY(-4px); }
  html.js-anim .tool-chip.reveal:hover,
  html.js-anim .code-chip.reveal:hover { transform: translateY(-1px); }
}

/* 按下反馈：不放在 hover 媒体查询里 —— 触屏也需要"我点到了"的反馈 */
.btn:active { transform: translateY(1px) scale(.985); }
.icon-btn:active { transform: scale(.92); }
.tool-chip:active, .code-chip:active { transform: scale(.96); }
.nav-toggle:active { transform: scale(.94); }

/* 开关：按下时滑块轻微缩一下（轨道自己的 transform 保持不动，只缩外壳） */
.meg-sw-track { transition: background .18s ease, transform .12s ease; }
.meg-sw:active .meg-sw-track { transform: scale(.94); }
.sm-switch input:active { transform: scale(.9); }

/* 复制成功：按钮本体亮一下（toast 由 app.js / admin.js 负责，这里只补按钮反馈） */
[data-copy].is-copied {
  border-color: color-mix(in srgb, var(--cdr-success) 62%, var(--cdr-border));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cdr-success) 18%, transparent);
  animation: uiCopied .45s ease;
}
/* 只有浅底按钮才把文字也染绿：主色按钮是深蓝底，绿字对比度反而更差 */
[data-copy].is-copied:not(.btn-primary):not(.btn-danger) { color: var(--cdr-success); }
@keyframes uiCopied {
  0% { transform: scale(1); }
  45% { transform: scale(1.06); }
  100% { transform: scale(1); }
}

/* ---------------------------------------------------------------- C. 加载反馈 */

/* 顶部进度条：切页 / 提交 / 用户发起的请求（轮询那种定时请求不闪它） */
.ui-busy-bar {
  position: fixed; top: 0; left: 0; right: 0; height: 2.5px; z-index: 260;
  pointer-events: none; opacity: 0; transition: opacity .18s ease; overflow: hidden;
}
.ui-busy-bar.is-on { opacity: 1; }
.ui-busy-bar::after {
  content: ''; position: absolute; top: 0; left: 0; height: 100%; width: 40%;
  background: linear-gradient(90deg, transparent, var(--cdr-primary), var(--cdr-primary-2), transparent);
  transform: translateX(-100%);
  animation: uiBarSlide 1.05s ease-in-out infinite;
}
@keyframes uiBarSlide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

/* 转圈 */
.ui-spinner {
  display: inline-block; width: 14px; height: 14px; flex: none;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  animation: uiSpin .7s linear infinite;
}
@keyframes uiSpin { to { transform: rotate(360deg); } }

/* 提交按钮忙态：不用改 DOM，::after 画转圈。
   [aria-busy=true] 那条是给个人中心那些 AJAX 表单用的（me-center.js 自己会设）。 */
.btn.is-busy, .btn[aria-busy="true"] { pointer-events: none; cursor: progress; opacity: .82; }
.btn.is-busy::after, .btn[aria-busy="true"]::after {
  content: ''; flex: none; width: 12px; height: 12px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  animation: uiSpin .7s linear infinite;
}

/* 骨架屏：.ui-skel 用已有的 shimmer 关键帧（文件前面那段），不重复定义 */
.ui-skel {
  display: block; height: 11px; border-radius: 6px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--cdr-muted) 12%, transparent) 25%,
    color-mix(in srgb, var(--cdr-muted) 22%, transparent) 37%,
    color-mix(in srgb, var(--cdr-muted) 12%, transparent) 63%);
  background-size: 400% 100%; animation: shimmer 1.4s ease infinite;
}

/* 遮罩：挂在"范围容器"里（.card / .table-wrap / [data-me-cards]），绝对定位盖住旧内容。
   容器自己保持原高度，所以不会跳版。 */
.is-ui-loading { position: relative; }
.ui-veil {
  position: absolute; inset: 0; z-index: 6; overflow: hidden;
  padding: 16px 18px; border-radius: inherit;
  background: var(--cdr-surface);
  display: flex; flex-direction: column; gap: 13px;
  animation: uiVeilIn .16s ease both;
}
.ui-veil-cards { background: var(--cdr-bg); }
.ui-veil-list { padding: 18px; }
.ui-veil-in { display: block; }
.ui-veil-line { display: flex; gap: 12px; align-items: center; margin-bottom: 13px; }
.ui-veil-line:last-child { margin-bottom: 0; }
@keyframes uiVeilIn { from { opacity: 0; } to { opacity: 1; } }

/* 个人中心局部刷新完成后，新卡片淡进来（纯动画：不依赖 JS 也照常可见） */
.ui-swap > * { animation: uiSwapIn .34s ease both; }
.ui-swap > *:nth-child(2) { animation-delay: .04s; }
.ui-swap > *:nth-child(3) { animation-delay: .08s; }
.ui-swap > *:nth-child(4) { animation-delay: .12s; }
@keyframes uiSwapIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------- 动效降级 */
/* ⚠️ 无障碍要求，不是可选项：系统开了"减少动效"就必须全部停掉。
   只有一类例外 —— "正在加载"的指示本身**保留但不动**：
     顶栏进度条 → 一条静止的半透明线；按钮转圈 → 一个静止的缺口圆环
     （后台按钮同时还有"处理中…"文字，读屏也能读到 aria-busy）。
   把它们彻底藏掉的话，就变成站长最反感的"点了没反应"了。 */
@media (prefers-reduced-motion: reduce) {
  /* A. 入场：直接显示 */
  html.js-anim .reveal,
  html.js-anim .reveal.is-visible,
  html.js-anim .reveal.reveal-row {
    opacity: 1 !important; transform: none !important; transition: none !important;
  }
  .ui-swap > * { animation: none !important; }
  /* B. 悬停 / 按下：只保留颜色和描边（那不是动效），位移一律取消 */
  .card, .card.is-hoverable, .stat, .gallery-item, .node-card, .me-card,
  .panel, .tool-chip, .code-chip, .icon-btn, .meg-sw-track {
    transition: none !important;
  }
  .card:hover, .card.is-hoverable:hover, .stat:hover, .me-card:hover,
  .gallery-item:hover, .node-card:hover, .tool-chip:hover, .code-chip:hover,
  .tool-chip:active, .code-chip:active, .btn:active, .icon-btn:active,
  .nav-toggle:active, .meg-sw:active .meg-sw-track, .sm-switch input:active,
  html.js-anim .card.reveal:hover, html.js-anim .stat.reveal:hover,
  html.js-anim .me-card.reveal:hover, html.js-anim .node-card.reveal:hover,
  html.js-anim .gallery-item.reveal:hover,
  html.js-anim .tool-chip.reveal:hover, html.js-anim .code-chip.reveal:hover {
    transform: none !important;
  }
  [data-copy].is-copied { animation: none !important; }
  /* 按钮 / 色块 / 开关那几条 transition 与 .fade-in 是站点原有的，
     一并停掉 —— 这块本来就是"减少动效"，留着 0.12s 的位移没有意义 */
  .btn, .icon-btn, .tool-chip, .code-chip, [data-copy] { transition: none !important; }
  .fade-in { animation: none !important; }
  /* C. 加载指示：保留形状，停掉动画 */
  .ui-busy-bar::after { animation: none !important; transform: none; width: 100%; opacity: .5; }
  .ui-spinner,
  .btn.is-busy::after,
  .btn[aria-busy="true"]::after { animation: none !important; }
  .ui-skel { animation: none !important; }
  .ui-veil { animation: none !important; }
}

/* ==========================================================================
   @fragment: chart-gap-subtle —— 「服务器离线（无采样）」区间改成含蓄画法
   --------------------------------------------------------------------------
   追加在 style.css 末尾（不改动上面任何既有规则，也**不新增任何 .heatmap 声明**）。

   站长反馈：「服务器关掉的时候怎么会是灰的一片」—— 上一轮加的断开标记面积一大就抢戏。
   它不能删（删了「服务器关了」与「在线人数是 0」在折线上长得一模一样，会误导），
   所以改成三层信息，越靠外的越轻：

     · 底色    .chart-gap       极浅（3%，内联 opacity；这里只兜 pointer-events）
     · 断开处  .chart-gap-edge  一条竖直虚线 —— 真正指出"这里断了"的东西
     · 标记    .chart-gap-tag   很小的一行「离线」，区间够宽（≥34px）时才由 app.js 落上去

   悬停提示里「此前服务器离线（无采样）」那句不动（见 style.css 的 .chart-tip .ct-gap）
   —— 那是用户理解这个标记的唯一途径。
   ========================================================================== */
.chart-gap { pointer-events: none; }        /* 只是提示，别抢绘图区的命中 */
.chart-gap-edge { pointer-events: none; }   /* 虚线同理 */
.chart-gap-tag {
  pointer-events: none;
  font-size: 10px;
  letter-spacing: .02em;
  user-select: none;
}
/* @fragment: changelog-filter — 更新日志的筛选条
 *
 * 三个类型徽章 / 月份芯片现在是可点的筛选按钮。用 <a> 而不是 <button>：
 * 筛选状态在 URL 里，中键新开标签、复制链接给别人看到的是同一份结果。
 *
 * 背景色走 --cdr-* 品牌变量，深色模式自动跟随（深色块没有覆盖 --cdr-primary，
 * 仍是 :root 的藏青 #1B2F5E，所以白字前景安全）。
 * 选中态的 color: #fff 与 .pagination .current / .badge 等既有规则保持一致，
 * 是本表「品牌色底 + 白字」的固定搭配，不要单独换成变量。
 * 注意：若将来有主题把 --cdr-primary 换成浅色（如 .me-score 那种 #8CC2FF），
 * 必须像 2744 行那样把作用域收窄，否则白字会掉到 1.8:1 左右。
 */
.cl-chip {
  cursor: pointer;
  text-decoration: none;
  border: 1px solid var(--cdr-border);
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.cl-chip:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.cl-chip.is-on {
  background: var(--cdr-primary);
  border-color: var(--cdr-primary);
  color: #fff;
}
.cl-chip-sm {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: .8rem;
  color: var(--cdr-text-dim, #6b7280);
}
.cl-chip-sm.is-on { background: var(--cdr-primary); color: #fff; }
.cl-chip-n {
  font-size: .72rem;
  opacity: .65;
  font-variant-numeric: tabular-nums;
}

.cl-filters {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.cl-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  margin: 0;
}
.cl-filter-label {
  font-size: .82rem;
  color: var(--cdr-text-dim, #6b7280);
  margin-right: 2px;
}
.cl-filter-input {
  width: 12rem;
  max-width: 100%;
  padding: 5px 10px;
  font-size: .86rem;
  color: inherit;
  background: var(--cdr-surface, transparent);
  border: 1px solid var(--cdr-border);
  border-radius: 7px;
}
.cl-filter-input:focus {
  outline: none;
  border-color: var(--cdr-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cdr-primary) 22%, transparent);
}
.cl-filter-active {
  margin: 2px 0 0;
  font-size: .82rem;
  color: var(--cdr-text-dim, #6b7280);
}

/* 窄屏：月份芯片会换行，输入框占满一行 */
@media (max-width: 560px) {
  .cl-filter-input { width: 100%; }
}

/* @fragment: cdr-dialog — 通用弹窗（替代 window.prompt / confirm）
 *
 * 颜色全部走 --cdr-* ，深色模式自动跟随。动效只做淡入 + 轻微上浮，
 * 并且尊重 prefers-reduced-motion。
 */
.cdr-dlg-back {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(8, 15, 34, .48);
  backdrop-filter: blur(2px);
  animation: cdrDlgFade .14s ease;
}
.cdr-dlg-box {
  width: min(440px, 100%);
  max-height: calc(100vh - 60px);
  overflow-y: auto;
  padding: 18px 20px 16px;
  border-radius: var(--cdr-radius);
  border: 1px solid var(--cdr-border);
  background: var(--cdr-surface);
  box-shadow: var(--cdr-shadow-lg);
  animation: cdrDlgRise .16s ease;
}
.cdr-dlg-title { margin: 0 0 8px; font-size: 1.02rem; }
.cdr-dlg-text { margin: 0 0 12px; font-size: .9rem; line-height: 1.6; color: var(--cdr-text); }
.cdr-dlg-field { margin: 0 0 14px; }
.cdr-dlg-label { display: block; margin-bottom: 5px; font-size: .82rem; color: var(--cdr-muted); }
.cdr-dlg-input {
  width: 100%;
  padding: 8px 10px;
  font: inherit;
  font-size: .9rem;
  line-height: 1.5;
  color: inherit;
  background: var(--cdr-bg);
  border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius-sm);
  resize: vertical;
}
.cdr-dlg-input:focus {
  outline: none;
  border-color: var(--cdr-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cdr-primary) 20%, transparent);
}
.cdr-dlg-counter { margin-top: 4px; text-align: right; font-size: .74rem; color: var(--cdr-muted); font-variant-numeric: tabular-nums; }
.cdr-dlg-foot { display: flex; justify-content: flex-end; gap: 8px; }

/* 必填为空时抖一下，比弹一句提示更省事 */
.cdr-dlg-box.is-shake { animation: cdrDlgShake .3s ease; }
@keyframes cdrDlgFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes cdrDlgRise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes cdrDlgShake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}
@media (prefers-reduced-motion: reduce) {
  .cdr-dlg-back, .cdr-dlg-box, .cdr-dlg-box.is-shake { animation: none; }
}

/* 窄屏：按钮铺满一行，拇指好点 */
@media (max-width: 480px) {
  .cdr-dlg-box { padding: 16px 16px 14px; }
  .cdr-dlg-foot { flex-direction: column-reverse; }
  .cdr-dlg-foot .btn { width: 100%; }
}

/* @fragment: chat — 地图页聊天框
 *
 * 颜色全部走 --cdr-* 品牌变量（深色模式自动跟随，见文件顶部 40 行的深色块）。
 * 日志区固定高度 + 内部滚动：地图本身已经很高，聊天再撑开就没人往下翻了。
 * 操作按钮（撤回/举报）默认低透明度常驻而不是 hover 才出现 —— hover 才出现
 * 在触屏上等于永远点不到。
 */
.chat-section { padding-bottom: 34px; }

.chat-shell {
  border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius);
  background: var(--cdr-surface);
  overflow: hidden;
}

.chat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border-bottom: 1px solid var(--cdr-border);
}
.chat-title { display: flex; align-items: center; gap: 9px; }
.chat-title h2 { margin: 0; font-size: 1rem; }
.chat-live {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: .72rem;
  font-weight: 600;
  color: var(--cdr-accent);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.chat-tools { display: flex; align-items: center; gap: 8px; }
/* ---------------- 日期选择（取代了原来"选哪一次开服"的下拉框） ---------------- */
.chat-tools { position: relative; display: flex; align-items: center; gap: 8px; }

.chat-date-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font: inherit;
  font-size: .84rem;
  color: inherit;
  background: var(--cdr-surface);
  border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius-sm);
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.chat-date-btn:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.chat-date-btn.is-busy { opacity: .55; pointer-events: none; }

.chat-cal {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  padding: 12px;
  border-radius: var(--cdr-radius);
  border: 1px solid var(--cdr-border);
  background: var(--cdr-surface);
  box-shadow: var(--cdr-shadow-lg);
  width: min(430px, calc(100vw - 40px));
}
/*
 * 中间那组（年 / 月下拉 + 今天）居中，左右两个翻月按钮各贴一边：
 * 用 auto 外边距而不是 space-between —— 后者会把五个孩子平均摊开，
 * 年份下拉会飘到离左边框很远的地方。
 */
.chat-cal-nav { display: flex; align-items: center; justify-content: center; gap: 6px; margin-bottom: 8px; }
.chat-cal-nav [data-cal="-1"] { margin-right: auto; }
.chat-cal-nav [data-cal="1"] { margin-left: auto; }
.chat-cal-nav button,
.chat-cal-sel {
  border: 1px solid var(--cdr-border);
  background: var(--cdr-surface);
  color: inherit;
  border-radius: var(--cdr-radius-sm);
  padding: 2px 10px;
  font: inherit;
  cursor: pointer;
}
.chat-cal-nav button:hover,
.chat-cal-sel:hover { border-color: var(--cdr-primary); color: var(--cdr-primary); }
.chat-cal-sel { padding: 2px 4px; font-size: .8rem; font-variant-numeric: tabular-nums; }
.chat-cal-today { font-size: .8rem; }

.chat-cal-months { display: flex; gap: 14px; flex-wrap: wrap; min-width: 0; }
.chat-cal-m { flex: 1 1 180px; min-width: 0; max-width: 100%; }
.chat-cal-mt { text-align: center; font-size: .82rem; font-weight: 600; margin-bottom: 5px; font-variant-numeric: tabular-nums; }
/*
 * minmax(0, 1fr) 而不是 1fr：`1fr` 等价于 minmax(auto, 1fr)，轨道最小值由内容决定 ——
 * 只要某格内容比均分宽一点，整个网格就会被撑出容器（上一行、周六那一列就是这么
 * 被挤没的，而父级 .chat-shell 的 overflow:hidden 又把超出去的部分裁掉，
 * 看起来像"少了星期六"）。minmax(0, …) 把最小值钉成 0，轨道只按容器均分。
 */
.chat-cal-g { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
.chat-cal-w { text-align: center; font-size: .7rem; color: var(--cdr-muted); padding-bottom: 2px; min-width: 0; }
.chat-cal-d {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  min-width: 0;
  padding: 0;
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
  border-radius: var(--cdr-radius-sm);
  /* 没有聊天的那几天是灰的、且不可点 —— 日历上"哪几天有内容"一眼可见 */
  color: var(--cdr-muted);
  opacity: .45;
  border: 1px solid transparent;
}
.chat-cal-d.is-blank { visibility: hidden; }
.chat-cal-d.has-data {
  opacity: 1;
  font-weight: 700;
  color: var(--cdr-text);
  background: color-mix(in srgb, var(--cdr-primary) 8%, transparent);
  border: 0;
  font-family: inherit;
  cursor: pointer;
}
.chat-cal-d.has-data:hover { background: var(--cdr-primary); color: #fff; }
.chat-cal-d.is-today { box-shadow: inset 0 0 0 1px var(--cdr-accent); }
.chat-cal-d.is-sel { background: var(--cdr-primary); color: #fff; opacity: 1; }
.chat-cal-tip { margin: 8px 0 0; font-size: .72rem; color: var(--cdr-muted); text-align: center; }

/* 「未绑定」标记：比「网页」标记更淡一档，是补充信息不是主体 */
.chat-tag.is-unbound {
  border-color: color-mix(in srgb, var(--cdr-warning) 55%, transparent);
  color: var(--cdr-warning);
}

/* 未登录 / 未绑定时的占位 */
.chat-gate { padding: 30px 18px; text-align: center; }
.chat-gate-title { margin: 0 0 6px; font-weight: 600; }
.chat-gate .btn { margin-top: 12px; }

/* ---------------- 日志区 ---------------- */
.chat-log {
  height: 380px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  scrollbar-width: thin;
}
/*
 * 消息贴着底部（新消息在下方、旧的往上滚），和游戏内 / 控制台一致。
 * 用「第一个孩子 margin-top:auto」而不是 justify-content:flex-end ——
 * 后者在内容超出容器时会让顶部滚不到（Chromium 的已知行为）。
 */
.chat-log > :first-child { margin-top: auto; }
.chat-log:empty::after {
  content: attr(data-empty);
  margin: auto;
  color: var(--cdr-muted);
  font-size: .86rem;
}

/* ---------------- 消息行 ----------------
 * 排版照游戏内聊天框：**时间在行首**，然后是 20px 小头像，再是 `<名字> 内容`，
 * 一行一条、允许折行。颜色也照游戏内的约定（进服/退服黄、成就青、死亡普通色），
 * 这样从游戏切到网页不用重新认一套视觉。
 */
.chat-line {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 3px 8px;
  border-radius: var(--cdr-radius-sm);
  font-size: .88rem;
  line-height: 1.5;
  transition: background-color .12s ease;
}
.chat-line:hover { background: color-mix(in srgb, var(--cdr-primary) 5%, transparent); }

/* 时间放行首、等宽数字，顺着往下能一眼读时间 */
.chat-t {
  flex: 0 0 auto;
  font-size: .74rem;
  color: var(--cdr-muted);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;      /* 与首行文字基线对齐 */
  user-select: none;
}

.chat-av {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--cdr-bg);
}
.chat-av img {
  width: 20px;
  height: 20px;
  display: block;
  image-rendering: pixelated;
}

.chat-c {
  /*
   * 不设 flex-grow：让「撤回/举报」紧跟在内容后面，而不是被推到面板最右边 ——
   * 推到最右时，一条 5 个字的短消息和它的举报按钮之间会隔半个屏幕，
   * 看不出这个按钮属于哪一行。
   */
  flex: 0 1 auto;
  min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
  white-space: pre-wrap;   /* 保留玩家自己打的连续空格，但不解析 HTML */
}
.chat-n { font-weight: 600; margin-right: 2px; }

/* 颜色按游戏内约定 */
.chat-line.is-join .chat-c,
.chat-line.is-leave .chat-c { color: var(--cdr-warning); }
.chat-line.is-death .chat-c { color: var(--cdr-text); }
.chat-line.is-advance .chat-c { color: var(--cdr-accent); }
.chat-line.is-web .chat-n { color: var(--cdr-accent); }

/*
 * 服务器开关服：不是「某人做了什么」，所以整行压低、居中偏左，
 * 用一条竖线代替头像的位置 —— 一眼能看出这是服务器状态，不是聊天。
 */
.chat-line.is-boot .chat-c,
.chat-line.is-shutdown .chat-c {
  color: var(--cdr-muted);
  font-size: .82rem;
  letter-spacing: .02em;
}
.chat-line.is-shutdown .chat-c { opacity: .85; }
.chat-line.is-boot .chat-av::before,
.chat-line.is-shutdown .chat-av::before {
  content: '';
  display: block;
  width: 3px;
  height: 13px;
  margin: 2px auto 0;
  border-radius: 2px;
  background: color-mix(in srgb, var(--cdr-muted) 45%, transparent);
}
.chat-line.is-boot { background: color-mix(in srgb, var(--cdr-success) 7%, transparent); }
.chat-line.is-shutdown { background: color-mix(in srgb, var(--cdr-muted) 7%, transparent); }

/* 「网页」小标记：与游戏里 [网页] 前缀对应 */
.chat-tag {
  font-size: .68rem;
  padding: 0 5px;
  margin-left: 3px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--cdr-accent) 55%, transparent);
  color: var(--cdr-accent);
  white-space: nowrap;
  vertical-align: 1px;
}

/*
 * 撤回 / 举报：贴在行尾。
 * 静止时压到 .45 —— 完全藏起来（opacity 0）在触屏上等于永远点不到，
 * 而且用户根本不知道有这功能；.45 既看得见又不抢戏，悬停时提到全不透明。
 */
.chat-acts {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
  margin-left: 6px;
  opacity: .45;
  padding-top: 2px;
  transition: opacity .12s ease;
}
.chat-line:hover .chat-acts,
.chat-line:focus-within .chat-acts { opacity: 1; }
/* 触屏没有悬停这回事，直接给足可见度 */
@media (hover: none) {
  .chat-acts { opacity: 1; }
}
.chat-act {
  border: 0;
  background: none;
  padding: 0;
  font-size: .72rem;
  color: var(--cdr-muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}
.chat-act:hover { color: var(--cdr-danger); }

/* ---------------- 输入区 ---------------- */
.chat-form {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  padding: 10px 14px;
  border-top: 1px solid var(--cdr-border);
}
.chat-input-wrap { position: relative; flex: 1 1 auto; }
.chat-input-wrap textarea {
  width: 100%;
  min-height: 38px;
  max-height: 120px;
  resize: none;
  padding: 9px 62px 9px 11px;
  font: inherit;
  font-size: .9rem;
  line-height: 1.5;
  color: inherit;
  background: var(--cdr-bg);
  border: 1px solid var(--cdr-border);
  border-radius: var(--cdr-radius-sm);
}
.chat-input-wrap textarea:focus {
  outline: none;
  border-color: var(--cdr-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cdr-primary) 20%, transparent);
}
.chat-input-wrap textarea:disabled { opacity: .6; cursor: not-allowed; }
.chat-counter {
  position: absolute;
  right: 9px;
  bottom: 9px;
  font-size: .72rem;
  color: var(--cdr-muted);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.chat-send { flex: 0 0 auto; height: 38px; }
.chat-send:disabled { opacity: .5; cursor: not-allowed; }

.chat-hint {
  margin: 0;
  padding: 0 14px 12px;
  font-size: .78rem;
  color: var(--cdr-muted);
}
.chat-hint[data-state="muted"] { color: var(--cdr-danger); }
.chat-hint[data-state="offline"] { color: var(--cdr-warning); }
.chat-hint[data-state="connecting"] { color: var(--cdr-accent); }

@media (max-width: 560px) {
  .chat-log { height: 320px; padding: 10px 6px; }
  .chat-line { gap: 5px; padding: 3px 4px; font-size: .84rem; }
  /* 窄屏优先保证文字宽度：时间让位 */
  .chat-t { font-size: .68rem; }
  .chat-form { flex-wrap: wrap; }
  .chat-send { width: 100%; }

  /*
   * 日历在窄屏**不再浮在面板上**，而是作为头部的下一行铺满。
   *
   * 原来用的是绝对定位 + width: calc(100vw - 32px)。两个问题叠在一起：
   *   · 100vw 是视口宽度，比面板（还要减掉容器内边距和边框）宽 —— 日历直接
   *     顶出面板的边框；
   *   · 而 .chat-shell 有 overflow: hidden（为了圆角），超出去的部分被裁掉，
   *     看起来就是"日期跑到边框外面去了"。
   * 浮层在窄屏本来就没有好出路（要么盖住内容、要么被裁），进文档流最省事，
   * 也永远不会溢出 —— 它的宽度就是容器的宽度。
   */
  .chat-tools {
    display: block;
    position: static;      /* 去掉绝对定位的包含块，日历不再挂在它下面 */
    flex: 1 1 100%;        /* 自己占一整行，挤到标题下面 */
  }
  .chat-date-btn { max-width: 100%; }
  .chat-cal {
    position: static;
    width: 100%;
    max-width: 100%;
    margin-top: 10px;
    padding: 10px 0 0;
    border: 0;
    border-top: 1px solid var(--cdr-border);
    border-radius: 0;
    box-shadow: none;
  }
  .chat-cal-m { min-width: 0; }
  .chat-cal-m:nth-child(2) { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   v1.9.35 —— 按钮的「液态玻璃」折射（站长 m09031 第 2 件）
   v1.9.84 —— 材质重做：从「白描边」改成「有厚度的三层玻璃」

   全部挂在 `html.lg-on` 下面。这个类**只有三个条件同时满足**才挂得上：
     ① 后台「外观效果 → 按钮的液态玻璃折射」打开（默认关）
     ② 浏览器是 Chromium 内核（Chrome / Edge）
     ③ canvas 拿得到 2D 上下文，位移贴图生成成功
   具体的判断在 `assets/js/liquid-glass.js`。

   ⚠️ Safari 与火狐**不支持** `backdrop-filter: url(#…)`，而且是**静默失效**
      （不报错、也不画）。它们不会挂 lg-on，所以看到的是本文件里原本那套
      磨砂玻璃 —— 是"少一层折射"，不是"坏掉的按钮"。这不是本站的 bug。
   ⚠️ 同理，手机 / 平板也不挂 lg-on：liquid-glass.js:98 在 `(hover: none)`
      或有触摸点但 `(hover: hover)` 不成立时整个引擎就 return 了。所以下面
      手机降级那一段只有在「桌面浏览器把窗口拉窄到 ≤720px」时才看得到 ——
      但这恰恰是它存在的意义：窗口窄 = 每个按钮的采样面积也小，折射滤镜在
      小盒子上更容易露出上沿那个"十"字亮斑（见 v1.9.45 那条记录）。

   结构（三层，缺一层就不像玻璃）：
     ① 元素本身 `.btn` —— 背景必须是 none（折射要采样身后），只留 border
        （= 最外侧那道深色发丝环）与两圈外投影；
     ② `::after`（z-index:-1，落在自身背景之上、正文之下）—— 折射 + 顶部亮线 /
        底部暗线 / 内圈亮环 / 内圈柔光；
     ③ `::before`（z-index:1，压在正文之上）—— 着色，一层很淡的竖向渐变。

   为什么不能直接给 `.btn` 加 backdrop-filter：按钮原本就有不透明的品牌底色，
      直接加的话滤镜采到的是它自己那层实色，折射根本透不出来。

   ⚠️⚠️ v1.9.84 的根因（站长这次勾的三条症状全部出自它）：
      上一版把「玻璃」当成**白色高光**来做 —— `inset 0 1px 0 rgba(255,255,255,.78)`
      这种白线画在**近白色的页面**上（--cdr-bg #F4F8FC、卡片 #FFFFFF）等于看不见。
      苹果的材质之所以立得住，是**三件同时成立**：
        a. 顶部一道**亮**高光（光从上面来）—— 它得有一层底下的着色衬着才亮；
        b. 底部一道**暗**的极细分隔线 —— 这是白底上"立起形状"的关键，
           上一版**完全没有**（上一版这里是 `inset 0 -1px 0 rgba(255,255,255,.18)`，
           白底上的白线，等于没画）；
        c. 最外面 1px 极细的**深色发丝环** + 一圈很淡的环境投影 —— 上一版也没有，
           只有 `0 4px 24px rgba(0,0,0,.18)` 这种又大又重的黑投影，读起来是
           "浮起来的一张纸"，不是"有厚度的玻璃"。
   ═══════════════════════════════════════════════════════════════════════════ */
html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger) {
  position: relative;
  isolation: isolate;          /* 采样范围 = 按钮自己，不往外漏 */
  overflow: hidden;            /* 位移会把采样推到盒外，圆角外要裁掉 */
  /* ⚠️ 关键：**按钮自己不能有不透明底色**。
     `backdrop-filter` 采样的是"元素身后已经画好的一切"，而元素**自身的
     background 也在采样范围内**。如果底色留在 .btn 上、玻璃层放 ::after，
     采到的就是这块白底本身 —— 折射一块纯白等于什么都没折射（第一版就是
     这么写的，探针显示链条全通、肉眼却看不出任何效果）。
     所以底色全部搬去 ::before：那里是着色层。 */
  background: none;
  background-image: none;

  /* ── 材质取值集中在这一处 ─────────────────────────────────────────────────
     v1.9.84 把亮线/暗线/亮环/发丝环/投影收敛成 7 个变量：
     上一版这四类值散在 5 条选择器里，改一个数要追 5 处，还踩过权重（见下）。
     变体和深色**只覆盖变量**，不再各自重写一遍 box-shadow。
     ⚠️ 暗线 / 发丝环用 `color-mix(… var(--cdr-text) …)` 而不是写死藏青：
        深色模式下 --cdr-text 自己会变浅，这两条也就跟着变成"细亮线"，
        不用再单写一套深色选择器。 */
  --lg-hi: rgba(255, 255, 255, .95);                                 /* 顶部亮线 */
  --lg-hi-ring: rgba(255, 255, 255, .55);                            /* 内圈 1px 亮环 */
  --lg-hi-glow: rgba(255, 255, 255, .62);                            /* 顶部光斑 + 内圈柔光 */
  --lg-lo: color-mix(in srgb, var(--cdr-text) 12%, transparent);     /* 底部暗线 */
  --lg-in-bot: rgba(8, 15, 34, .07);                                 /* 底部内阴影 */
  --lg-ring: color-mix(in srgb, var(--cdr-text) 18%, transparent);   /* 外圈发丝环 */
  --lg-near: rgba(8, 15, 34, .10);                                   /* 接触影：贴地那一下 */
  --lg-far: rgba(8, 15, 34, .46);                                    /* 环境投影：把玻璃抬起来 */
  /* 悬停时要用的那一档。**单独存成变量、而不是在悬停里写
     `color-mix(… var(--lg-hi) …)`** —— 自定义属性引用自己是循环引用，
     那张声明的整条值会失效（按钮悬停时亮线直接消失）。 */
  --lg-hi-hot: rgba(255, 255, 255, 1);
  --lg-hi-ring-hot: rgba(255, 255, 255, .78);
  --lg-hi-glow-hot: rgba(255, 255, 255, .88);
  --lg-lo-hot: color-mix(in srgb, var(--cdr-text) 18%, transparent);

  /* 外侧发丝环走 border，不走 `0 0 0 1px` 的外阴影 —— 因为本元素
     `overflow: hidden`，子元素（::after）的外阴影会被裁掉一圈，元素自己的
     border/shadow 不受影响。顺带把上一版那条 `rgba(255,255,255,.38)` 的
     白描边换掉：白底上的白描边正是"没有边缘反光"的直接原因。 */
  border-color: var(--lg-ring);
  /* 三段投影：接触影（贴边 1px）+ 一层中景（把玻璃的"底"托出来）+
     一层远景（把玻璃从白底上抬起来）。单靠一层 -14px 扩散的大投影，
     边缘附近的浓度太低，白底上根本读不出"离地"。 */
  box-shadow: 0 1px 2px var(--lg-near),
              0 3px 8px -4px var(--lg-far),
              0 8px 20px -10px var(--lg-far);
  /* 站长第 3 件「按下 1~2px 回弹、悬停轻扫」的节奏：180ms + 先快后慢的缓动。
     只给 transform / box-shadow / background 三样，别把 color 也拉进来
     （全站按钮的 hover 都有各自的主题色规则，一起过渡会互相打架）。 */
  transition: transform 180ms cubic-bezier(.22, .8, .22, 1),
              box-shadow 180ms cubic-bezier(.22, .8, .22, 1),
              background 180ms cubic-bezier(.22, .8, .22, 1);
}
/* ⚠️ v1.9.42 补：加载中的按钮**既没有玻璃层、又被上面那条 `background: none` 抄了家**
   —— 转圈的那几秒里按钮整个变成透明的，只剩一个小 spinner 和一圈描边。
   这里把各变体本来的底色还回去，让它忙碌时还是它自己。 */
html.lg-on body.cdr-body .btn:not(.btn-danger):is(.is-busy, [aria-busy="true"]) {
  background: var(--cdr-surface);
}
html.lg-on body.cdr-body .btn-primary:is(.is-busy, [aria-busy="true"]) {
  background-image: linear-gradient(100deg, var(--cdr-primary) 0%,
                    color-mix(in srgb, var(--cdr-primary) 38%, var(--cdr-primary-2)) 100%);
}
html.lg-on body.cdr-body .btn-soft:is(.is-busy, [aria-busy="true"]) {
  background: color-mix(in srgb, var(--cdr-primary) 13%, transparent);
}
/* ⚠️⚠️ v1.9.42 正式版踩到的坑 —— **两个优先级 bug，都出在这一版**：
   `html.lg-on body.cdr-body .btn:not(.is-busy):not([aria-busy="true"])::before`
   是 **5 类 + 2 元素 + 1 伪元素**；而变体 `.btn-primary::before` 只有 3 类、
   悬停 `.btn:hover::before` 只有 4 类 —— **两个都输给基础着色层**。后果：
     · 主按钮的深蓝渐变永远画不出来；而 `background: none` 又把 `.btn-primary`
       自己的底色抹了 ⇒ **深蓝底 + 白字变成白底 + 白字，登录按钮整个消失**
       （站长 m11209 第 8 件报的就是它）；
     · 悬停那 30% 的提亮同样永远不生效 ⇒ 第 4 件「放上去和没放上去一样」并没修好。
   所以五个变体/悬停规则**全部补上同一个 `:not(.is-busy):not([aria-busy="true"])`**，
   靠"同等优先级 + 写在后面"取胜。**以后往这一段加规则，先数一遍 (类, 元素)。** */
/* ⚠️ v1.9.42：`:not(.is-busy):not([aria-busy="true"])` 这个守卫不是多余的 ——
   `.btn.is-busy::after`（本节上面那段 ui-motion 里）有一条
   `content:''; width:12px; height:12px; border: 2px solid currentColor;
   border-radius: 50%; animation: uiSpin`，它就是靠 `::after` 画那个转圈的。
   我这条（4 类 + 1 元素 + 1 伪元素）优先级比它（2 类 + 1 伪元素）高，不排除的话
   **加载中的按钮就不转了、改成一块玻璃**。排除之后 `.is-busy` / `[aria-busy]`
   的按钮仍然拿它自己的 `::after`。 */
html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  /* ⚠️ v1.9.42：参考的 `.glassDiv::after` 里确实带了 `isolation: isolate`，
     但**照抄到本站会出事** —— 加上之后 `backdrop-filter` 采样到的变成伪元素
     自己那个空层叠上下文，折射整条失效，按钮退化成一个纯白圆角块
     （试验台上棋盘格一格都透不出来）。实测去掉它折射立刻回来。
     参考能带是因为它的 DOM/层叠结构和本站不一样，不是这条属性本身对。 */
  /* 先折射，再叠自己的着色 —— 顺序就是 CSS 的绘制顺序：backdrop 在下。
     ⚠️ v1.9.36：blur 从 1.5px 收紧到 0.8px。站长反馈"开了不明显"，
        两个原因叠加：①位移量只有 14px（已提到 50，见 liquid-glass.js）；
        ②**模糊正好会抹掉位移要吃的那点细节** —— 折射的本质是把背景的
        邻近像素搬过来，blur 越大，"邻近"就越没有差别，最后只剩一块均匀的雾。
        收紧之后折射的边缘才看得出来。 */
  /* ⚠️⚠️ v1.9.38：**这里只剩 `url()`，blur / saturate / brightness 全部删掉。**
     站长 m10258 让我「做成 archisvaze/liquid-glass 那样」，我把那份的
     `index.css` 逐字读了一遍（`raw.githubusercontent.com/archisvaze/liquid-glass/main/index.css`），
     它的玻璃层只有一条声明：
         .glassDiv::after { backdrop-filter: url(#liquid-glass-filter); }
     —— **一个滤镜函数都不带**。而它的着色（挂在 `::before` 上）是
         --tint-color: 255,255,255;  --tint-opacity: 0.06;      ← 只有 6%
         --shadow-color: rgba(255,255,255,.45); --shadow-blur: 20px; --shadow-spread: -5px;
     对照下来我之前犯的正是同一个错：**模糊会把位移要吃的那点细节抹平**
     （实验数据：blur 3px 时试验台 A/B 从 7215 差异像素掉到 3154，
     肉眼完全退化成"普通磨砂"）。所以我一路把 blur 从 1.5px 收到 0.5px，
     却始终没敢去掉 —— 参考里根本就没有它。
     `saturate(200%) brightness(1.10)` 同理：那是我为了"把玻璃感做出来"加的
     补偿，实际效果是把折射后的画面整体推亮推艳，反而更不像真玻璃。 */
  /* ⚠️⚠️ v1.9.50 —— **这里不再引用任何滤镜 id 了。**
     参考（archisvaze/liquid-glass）那个 `url(#liquid-glass-filter)` 是
     **一个元素一套滤镜**：它的位移贴图按元素自身的像素尺寸生成，圆角是
     真正的圆角距离场，倒角宽度、玻璃厚度、折射率都参与计算。
     一页几十个按钮、尺寸各不相同，**一套共享滤镜做不到这件事**
     （贴图被拉伸后圆角会变成椭圆）。
     所以折射现在由 `assets/js/liquid-glass.js` 给**每个按钮**生成一套滤镜，
     再插一条 `html.lg-on body.cdr-body .btn:not(…)[data-cdr-lg="N"]::after`
     规则挂上去。这里保留下面的兜底：脚本没跑到（还在加载 / 出错 / 内核不支持）
     的按钮至少还是一块正常的磨砂玻璃，不会变成透明的空壳。 */
  backdrop-filter: blur(6px) saturate(180%);
  -webkit-backdrop-filter: blur(6px) saturate(180%);
  /* ⚠️⚠️ v1.9.42 —— 着色层**从这里搬走了**。
     站长 m10957 第 3 件：「玻璃必须按照 archisvaze/liquid-glass 来写」。
     参考的三层结构是：
        .glassDiv         { isolation: isolate; box-shadow: 0 4px 24px rgba(0,0,0,.18); }
        .glassDiv::after  { inset:0; z-index:-1; border-radius:inherit;
                            backdrop-filter: url(#liquid-glass-filter); isolation: isolate; }
        .glassDiv::before { inset:0; z-index:1;  border-radius:inherit;
                            background-color: rgba(var(--tint-color), var(--tint-opacity));  // 6%
                            box-shadow: inset 0 0 20px -5px rgba(255,255,255,.45); }
     也就是说：**玻璃层 `::after` 只负责折射、一点底色都不带；着色是一个独立的
     `::before`，而且 z-index 是 1、压在正文之上**。我原来把底色和折射挤在
     同一层 `::after` 上，跟参考不是一回事 —— 站长的第 2 件「玻璃不够透明」
     就是这件事的直观后果。 */
  background: none;
  /* ⚠️ v1.9.84 —— 玻璃的"四件套"（上一版只有①③，②④完全没有，
     这就是站长勾的「没有高光和边缘反光」+「形状细节不对」）：
       ① 上沿镜面亮线（光从上面来）—— 1px 实心白，`.92` 比上一版的 `.78`
          更实；它现在**有底下那层毛玻璃衬着**，所以白底上也亮得起来；
       ② 下沿暗线（`--lg-lo`，墨色 10%）—— 白底上把形状"坐"下去的那一条；
       ③ 内圈 1px 亮环（`.45`）+ 内圈柔光（`.55`）—— 参考里玻璃之所以"厚"，
          一半靠的是这圈从边缘往里晕开的柔光，而不是一条硬边；
       ④ ⚠️ **冷暖色散双边已经删掉了**（v1.9.39，别再试）。我原来在左沿加了
          `rgba(255,168,132,.28)`（暖）、右沿加了 `rgba(126,184,255,.32)`（冷），
          想模仿"折射率随波长变、边缘散出彩边"。站长 m10425 直接问
          「还有这个玻璃为什么会是这样的颜色？」并发了一张 `返回` 按钮的特写 ——
          那两条 1px 白边只有在**纯色背景**上才成立，可他那个按钮压在一片
          深色天空上，暖冷两条立刻读成"粉边 + 蓝边"，像做坏了而不是像玻璃。
          参考（archisvaze）里根本没有色散，只有纯白的 `--shadow-color`。
          结论：**不要在没有真折射的场合假装色散**。
     ⚠️ 外圈发丝环与两圈投影**不放在这一层**：父元素 `overflow: hidden` 会把
        它的外阴影裁成 0（写在这里等于没写），所以挪到了上面 `.btn` 那条
        `box-shadow` + `border-color`。 */
  box-shadow: inset 0 1px 0 var(--lg-hi),
              inset 0 3px 6px -3px var(--lg-hi-glow),
              inset 0 -1px 0 var(--lg-lo),
              inset 0 -3px 6px -4px var(--lg-in-bot),
              inset 0 0 0 1px var(--lg-hi-ring),
              inset 0 0 16px -4px var(--lg-hi-glow);
  /* 边缘的亮暗跟着悬停一点点走（变量在被父元素改），给它 180ms 让"扫过去"成立 */
  transition: box-shadow 180ms cubic-bezier(.22, .8, .22, 1);
}

/* ═══ 小按钮**不挂 backdrop-filter** ═══════════════════════════════════════
   ⚠️ v1.9.45 —— 修站长 m11209 第 12 件：「为什么这个返回键上会存在一个"十"
   字一样的东西」。那颗按钮是 `.btn-sm`（`返回`，58×35）。

   **真因：Chromium 在小元素上做 `backdrop-filter` 采样时，上边缘正中会留下
   一个亮斑** —— 约 6×3px，上窄下宽，看起来就是个「十」字。
   这颗按钮 `kids=0`（只有文字、没有图标），所以只能是渲染出来的。
   四轮 A/B 逐一排除：
     ① `::before` / `::after` 的 `box-shadow` 全部去掉 → 亮斑还在；
     ② `overflow: hidden` → `visible` → 还在；
     ③ 去掉 SVG 折射、换成 `blur(6px) saturate(180%)` → **还在**
        （说明与折射无关，是 blur 本身就有的）；
     ④ `backdrop-filter: none` → **亮斑消失** ✅
   为什么卡片没有这个字：卡片面积大，采样边缘这一小块被稀释掉了；
   58×35 的小盒子上它占了可见面积的可观比例。

   代价为零：真机上量过，这颗按钮开不开折射**差异是 0 / 1766688 像素**
   （最大单通道差 1）—— 58×35 的面积上根本没有任何细节可供采样。
   ⚠️ v1.9.84 复核：这条 `none` 目前**打不过脚本注入的那条**
      （`liquid-glass.js:384` 注入的选择器同样是 8 类 3 元素，而它所在的
      `<style>` 追加在 head 末尾 ⇒ 同权重后写者胜 ⇒ 实际生效的仍是 url()）。
      没在这一版顺手改它，是因为它不在这轮「材质」的范围内，且需要先确认
      那 58×35 上究竟还有没有「十」字（见汇报）。 */
html.lg-on body.cdr-body .btn.btn-sm[data-cdr-lg][data-cdr-lg]:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after,
html.lg-on body.cdr-body .btn.btn-sm:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* 少了 backdrop-filter，着色要自己把「磨砂」撑起来 —— 8% 在照片上太薄，
   深色字会跟着背景一起糊掉。14% 与原来 `blur(6px)` 的观感几乎一致
   （对比截图 `xa-z.png`）。 */
html.lg-on body.cdr-body .btn.btn-sm:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::before {
  background-color: rgba(255, 255, 255, .14);
}

/* ═══ 着色层（照参考，放在 `::before`、z-index: 1、压在正文之上）════════════
   参考的 `--tint-opacity` 是 **0.06**。我试过直接抄（v1.9.42）：按钮压在
   **白卡片**上时它等于完全消失（参考的玻璃压的是彩色照片，所以 6% 才立得住）。
   这里给一个既"明显更透"、又还能认出是个按钮的值。
   ⚠️ 内圈柔光（`inset 0 0 20px -5px`）也照参考放在这一层，v1.9.84 收到
      16px/-4px：20px 的晕染在 35px 高的药丸上会糊到正文边缘。 */
html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  /* ⚠️ v1.9.42 实测记录（别再反复试）：
     **12% / 20% 的平涂白都会把按钮糊成一块白**，只有把这一层让空
     （着色调成接近 0）折射才真正透出来，试验台上棋盘格一格一格看得清清楚楚、
     格子边界被掰弯。原因不难理解：这一层是 `z-index: 1`、**压在正文之上**的，
     它一白就把下面那层玻璃的折射整个盖住 —— 参考之所以能干到 6%，
     是因为它的玻璃压在彩色照片上；本站按钮绝大多数压在白卡片上，
     任何可观的白着色都会立刻读成"实心白按钮"。
     所以底色取 **8%**（几乎等于只留内圈柔光），把"看得见底下"这件事做足；
     可读性靠的是按钮文字本身的对比度，不靠这层底。
   ⚠️ v1.9.84：把平涂改成**竖向渐变**（上 .20 → 下 .06），总量仍是那个数量级
     （平均约 13%），但多了一点「上面亮、下面沉」的方向感 —— 平涂的 8% 是
     "均匀的一层雾"，从任何角度看都不像一块玻璃。**别再往上调**：
     超过这个量级立刻回到 v1.9.42 那个"糊成白块"的老坑（见上）。 */
  background-color: transparent;
  /* 三段：顶上亮（受光面）→ 中段薄 → 底下**掺一点品牌色**。
     最后那一点冷色是让"白底上的白玻璃"能读出来的关键：
     纯白渐变在白底上等于没有，底部一点点冷灰才让它像"坐在"页面上。 */
  background-image: linear-gradient(180deg,
                    rgba(255, 255, 255, .34) 0%,
                    rgba(255, 255, 255, .10) 50%,
                    color-mix(in srgb, var(--cdr-primary) 7%, transparent) 100%);
  /* ⚠️ v1.9.84 第三次迭代：**亮线/暗线/内圈亮环必须在这一层再画一遍**。
     原因是叠放顺序 —— `::before` 是 z-index: 1、压在 ::after 之上，而它
     自己那层着色（哪怕只有 12% 的蓝）也是**不透明地盖在 ::after 的 box-shadow
     之上**的：实测顶部那道 `rgba(255,255,255,.95)` 的 1px 亮线被 12% 的
     蓝压下去以后量出来是 `#eef0f3`，和周围的底色只差 3~4 级，
     肉眼等于没有（这就是"高光看不见"的真正原因，不是值太小）。
     所以最上面这一层承担可见的边缘：它自己的 inset 阴影画在自己的底色之上，
     不会被任何人压暗，量出来就是接近 #ffffff 的一道线。 */
  box-shadow: inset 0 1px 0 var(--lg-hi),
              inset 0 -1px 0 var(--lg-lo),
              inset 0 0 0 1px var(--lg-hi-ring),
              inset 0 0 16px -4px var(--lg-hi-glow);
}

/* ═══ 主按钮：**带色的玻璃**（tinted glass）═════════════════════════════════
   v1.9.42 修正：**主按钮不参与玻璃**。它是实心品牌按钮、文字是白的；
   `background: none` 一旦抹掉它自己的深蓝渐变，白字就落在白底上 ——
   这就是登录按钮消失的原因。34% / 56% 的浅色着色也托不住白字，所以那两条撤掉。
   ⚠️ v1.9.84 换了个做法，不再靠"不参与"来保对比度：苹果的 prominent button
      本来就是 tinted glass —— **底色仍是品牌色（元素自己身上），只把高光/
      描边/投影 + 一点竖向渐变加给它**。于是白字的对比度从元素底色来
      （深藏青 → 白字约 9:1，比阈值 4.5:1 高得多），玻璃的"厚度"从 ::after 来。
      这一层**不注入 backdrop-filter**：透过蓝底什么也看不到，还白费一次采样
      （liquid-glass.js:336 也把 btn-primary 排除在 targets() 之外，两边一致）。 */
html.lg-on body.cdr-body .btn-primary:not(.is-busy):not([aria-busy="true"]) {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* 竖向渐变：顶上加 12% 白（仍在深色区，对比度掉不到 4.5:1 以下）→ 底部回到
     原色。上一版是 100° 斜向渐变，斜角在按钮边缘会留 1px 异色像素（见
     style.css `.btn-primary` 那条注释），竖向没有这个问题。 */
  background-color: var(--cdr-primary);
  background-image: linear-gradient(180deg,
                    color-mix(in srgb, var(--cdr-primary) 84%, #fff) 0%,
                    var(--cdr-primary) 62%,
                    color-mix(in srgb, var(--cdr-primary) 92%, #000) 100%);
  border-color: transparent;              /* 底色由渐变画到 border-box，不透边 */
  /* 深底上白高光要收档（.95 的亮线压在藏青上会变成一条银边），暗线反而是
     压在下沿的实黑，玻璃的"底"才沉得下去。 */
  --lg-hi: rgba(255, 255, 255, .34);
  --lg-hi-ring: rgba(255, 255, 255, .18);
  --lg-hi-glow: rgba(255, 255, 255, .16);
  --lg-lo: rgba(0, 0, 0, .28);
  /* 这一段必须在父规则里也给出 `--lg-in-bot`：`var()` 取不到值会让**整条
     box-shadow 声明**在这个元素上失效（不是只丢那一段），玻璃层会整块消失。 */
  --lg-in-bot: rgba(0, 0, 0, .18);
  box-shadow: 0 1px 2px rgba(8, 15, 34, .18),
              0 4px 10px -6px rgba(27, 47, 94, .45),
              0 12px 26px -15px rgba(27, 47, 94, .55);
  transition: transform 180ms cubic-bezier(.22, .8, .22, 1),
              box-shadow 180ms cubic-bezier(.22, .8, .22, 1),
              background 180ms cubic-bezier(.22, .8, .22, 1);
}
html.lg-on body.cdr-body .btn-primary:not(.is-busy):not([aria-busy="true"])::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 1px 0 var(--lg-hi),
              inset 0 3px 6px -4px var(--lg-hi-glow),
              inset 0 -1px 0 var(--lg-lo),
              inset 0 -3px 6px -4px var(--lg-in-bot),
              inset 0 0 0 1px var(--lg-hi-ring),
              inset 0 0 14px -4px var(--lg-hi-glow);
  transition: box-shadow 180ms cubic-bezier(.22, .8, .22, 1);
}

/* ═══ 危险按钮：同样是带色的玻璃，但**只许往下压暗** ══════════════════════
   它和主按钮同一个处境（实心色 + 白字），区别在于 `--cdr-danger` #D0453C
   上白字的对比度本来就只有约 4.3:1 —— 照主按钮那样往顶上叠 12% 白，
   会把对比度压到 3.6:1 左右，**又白底白字**（v1.9.73 修过一次的那个坑）。
   所以这里的竖向渐变方向相反：顶部保持原色、底部混 12% 黑。
   立体感一样有（上亮下深），对比度反而是往上走的。 */
html.lg-on body.cdr-body .btn-danger:not(.is-busy):not([aria-busy="true"]) {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--cdr-danger);
  background-image: linear-gradient(180deg,
                    var(--cdr-danger) 0%,
                    color-mix(in srgb, var(--cdr-danger) 94%, #000) 58%,
                    color-mix(in srgb, var(--cdr-danger) 86%, #000) 100%);
  border-color: transparent;
  --lg-hi: rgba(255, 255, 255, .30);
  --lg-hi-ring: rgba(255, 255, 255, .16);
  --lg-hi-glow: rgba(255, 255, 255, .14);
  --lg-lo: rgba(0, 0, 0, .26);
  --lg-in-bot: rgba(0, 0, 0, .16);   /* 同主按钮：var() 缺值会带走整条 box-shadow */
  box-shadow: 0 1px 2px rgba(8, 15, 34, .16),
              0 4px 10px -6px rgba(150, 40, 32, .42),
              0 12px 24px -15px rgba(150, 40, 32, .50);
  transition: transform 180ms cubic-bezier(.22, .8, .22, 1),
              box-shadow 180ms cubic-bezier(.22, .8, .22, 1),
              background 180ms cubic-bezier(.22, .8, .22, 1);
}
html.lg-on body.cdr-body .btn-danger:not(.is-busy):not([aria-busy="true"])::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 1px 0 var(--lg-hi),
              inset 0 3px 6px -4px var(--lg-hi-glow),
              inset 0 -1px 0 var(--lg-lo),
              inset 0 -3px 6px -4px var(--lg-in-bot),
              inset 0 0 0 1px var(--lg-hi-ring),
              inset 0 0 14px -4px var(--lg-hi-glow);
  transition: box-shadow 180ms cubic-bezier(.22, .8, .22, 1);
}

/* ═══ `.btn-soft` 自己的着色 ═════════════════════════════════════════════════
   v1.9.51 修正「展开全部徽章」那类浅色按钮发灰发白：
   这两条原来是 `… .btn-soft:not(.is-busy):not([aria-busy])::before` ——
   **只有 2 个元素 + 4 个类**，而上面那条基础着色是 **2 个元素 + 6 个类**，
   于是基础那层白永远赢，`.btn-soft` 自己的蓝底被彻底盖掉；
   再叠上基础规则把 `.btn` 的 `background` 设成 `none`（抹掉了它的 13% 蓝），
   结果就是**一颗近乎纯白、只剩蓝灰文字的按钮** —— 看着像被禁用。
   站长截图里「展开全部徽章（还有 26 枚未达成）」就是这个状态。
   所以这里必须补上 `.btn` 与 `:not(.btn-primary)`，把权重抬到 7 个类才压得住。
   ⚠️ 以后再加变体着色，照抄这一行的选择器形状，少一个 `:not` 就会被基础层吃掉。 */
html.lg-on body.cdr-body .btn.btn-soft:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::before {
  background-color: color-mix(in srgb, var(--cdr-primary) 12%, transparent);
}

/* 深色下底色本来就深：白色高光收一档，投影换成纯黑（原来的藏青投影
   在黑底上是一团看不见的雾）。暗线/发丝环不在这里改 —— 它们由
   `--cdr-text` 混出来的，深色模式下自己就变浅了。
   ⚠️ 这一段必须写在下面的悬停那几条**之前**：两者权重都是 (6 类, 2 元素)，
      写在后面的话会把悬停抬起来的亮线整个压回去（深色下"放上去没反应"）。 */
html.lg-on:is([data-color-mode="dark"], [data-dark-forced="1"]) body.cdr-body .btn:not(.btn-primary):not(.btn-danger) {
  --lg-hi: rgba(255, 255, 255, .46);
  --lg-hi-ring: rgba(255, 255, 255, .16);
  --lg-hi-glow: rgba(255, 255, 255, .14);
  --lg-near: rgba(0, 0, 0, .30);
  --lg-far: rgba(0, 0, 0, .60);
  --lg-in-bot: rgba(0, 0, 0, .30);
  /* 深色下的悬停档位同样要收（.46 抬到 1 会变成贴了道银边） */
  --lg-hi-hot: rgba(255, 255, 255, .70);
  --lg-hi-ring-hot: rgba(255, 255, 255, .28);
  --lg-hi-glow-hot: rgba(255, 255, 255, .22);
  --lg-lo-hot: color-mix(in srgb, var(--cdr-text) 26%, transparent);
}

/* ═══ 悬停 / 按下：站长第 4 件 + 第 3 件 ═══════════════════════════════════
   ⚠️ v1.9.42 第 4 件 —— 站长原话：「很多地方的这个键放上去和没放上去颜色
   基本一样，修改」（截图圈的是首页那个「复制服务器地址」）。
   原来悬停只提亮了 1px 描边，在 30% 的底色上看几乎不可见。
   ⚠️ v1.9.84 换了一套更克制的做法：**不再把着色提到 30%**（那会和"克制"
     打架，也会把折射盖掉），改成"整体抬起来一点点"——
     亮线更亮、亮环更实、发丝环加深、环境投影变大、位置抬 1px。
     在**白底**上这几样才是看得见的（白色提亮在白底上不可见，这是上一版
     同样的老毛病换了个位置）。
   ⚠️ 悬停用的那一档亮暗单独存成 `--lg-*-hot`（见上面 `.btn` 里的定义），
      深色块里也各有一套 —— 这样深色悬停不用再写第三条选择器。
      **不要在悬停里写 `color-mix(… var(--lg-hi) …)`**：自定义属性引用自己是
      循环引用，整条声明失效，按钮一悬停亮线就整个没了。 */
html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger):hover {
  transform: translateY(-1px);
  /* 悬停时外环加深一档。**不要写成 color-mix(… var(--lg-ring) …, var(--cdr-text))**：
     `--lg-ring` 本身已经是 18% 的墨色，再和 100% 的墨色混，出来是约 54% 的重灰边
     —— 第一版就是这么写的，实测是一道铁灰框，过火到像"被选中"而不是"被指到"。
     加深幅度只给 18% → 22%（与静息态差 4 个点），够读出来、不至于抢戏。 */
  border-color: color-mix(in srgb, var(--cdr-text) 22%, transparent);
  box-shadow: 0 2px 5px var(--lg-near),
              0 6px 16px -8px var(--lg-far),
              0 14px 30px -16px var(--lg-far);
  --lg-hi: var(--lg-hi-hot);
  --lg-hi-ring: var(--lg-hi-ring-hot);
  --lg-hi-glow: var(--lg-hi-glow-hot);
  --lg-lo: var(--lg-lo-hot);
}
html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger):hover:not(.is-busy):not([aria-busy="true"])::before {
  background-image: linear-gradient(180deg,
                    rgba(255, 255, 255, .46) 0%,
                    rgba(255, 255, 255, .14) 50%,
                    color-mix(in srgb, var(--cdr-primary) 7%, transparent) 100%);
}
html.lg-on body.cdr-body .btn.btn-soft:not(.btn-primary):not(.btn-danger):hover:not(.is-busy):not([aria-busy="true"])::before {
  background-color: color-mix(in srgb, var(--cdr-primary) 16%, transparent);
}
/* 按下：回到 1~2px 的回弹区间，投影收紧（离桌面更近了），亮线反而更亮
   （压下去的一瞬间边缘受光最强）—— 这是 iOS 那颗按钮的节奏。
   ⚠️ 必须写在 :hover 之后：按下时 :hover 同样成立，同权重后写为准。 */
html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger):active {
  transform: translateY(.5px) scale(.985);
  box-shadow: 0 1px 2px var(--lg-near),
              0 4px 12px -10px var(--lg-far);
  --lg-hi: var(--lg-hi-hot);
}
/* 主按钮悬停 / 按下：跟随它自己的品牌色，只做"抬起来 + 渐变提亮"。
   ⚠️ 这里显式写 `border-color: transparent`：本节末尾还有一条
      `html[data-color-mode="dark"] body.cdr-body .btn:hover { border-color: … }`
      （不带 lg-on 的老规则，为了不动"开关关掉"的观感必须原样留着），
      它会顺手给主按钮描一道白边；同权重后写者胜，这里压住它。 */
html.lg-on body.cdr-body .btn-primary:not(.is-busy):not([aria-busy="true"]):hover {
  transform: translateY(-1px);
  border-color: transparent;
  background-image: linear-gradient(180deg,
                    color-mix(in srgb, var(--cdr-primary) 80%, #fff) 0%,
                    color-mix(in srgb, var(--cdr-primary) 96%, #000) 100%);
  box-shadow: 0 2px 4px rgba(8, 15, 34, .18),
              0 16px 32px -16px rgba(27, 47, 94, .70);
  --lg-hi: rgba(255, 255, 255, .46);
}
html.lg-on body.cdr-body .btn-primary:not(.is-busy):not([aria-busy="true"]):active {
  transform: translateY(.5px) scale(.985);
  border-color: transparent;
  box-shadow: 0 1px 2px rgba(8, 15, 34, .20),
              0 6px 14px -12px rgba(27, 47, 94, .60);
}
html.lg-on body.cdr-body .btn-danger:not(.is-busy):not([aria-busy="true"]):hover {
  transform: translateY(-1px);
  border-color: transparent;
  background-image: linear-gradient(180deg,
                    color-mix(in srgb, var(--cdr-danger) 96%, #fff) 0%,
                    color-mix(in srgb, var(--cdr-danger) 84%, #000) 100%);
  box-shadow: 0 2px 4px rgba(8, 15, 34, .16),
              0 16px 30px -16px rgba(150, 40, 32, .62);
  --lg-hi: rgba(255, 255, 255, .42);
}
html.lg-on body.cdr-body .btn-danger:not(.is-busy):not([aria-busy="true"]):active {
  transform: translateY(.5px) scale(.985);
  border-color: transparent;
  box-shadow: 0 1px 2px rgba(8, 15, 34, .18),
              0 6px 14px -12px rgba(150, 40, 32, .55);
}
/* ⚠️ 这一条**故意不带 `html.lg-on`**：它管的是「开关关掉时」深色悬停的描边
   （白底描边在黑底上偏亮，收一档），是这套老规则的既有观感。
   开关关掉时全站观感必须和改之前**完全一致**，所以原样保留、一个字没改。
   开关打开时它会被上面的悬停规则压住（权重更高），不参与玻璃。 */
html[data-color-mode="dark"] body.cdr-body .btn:hover,
html[data-dark-forced="1"] body.cdr-body .btn:hover {
  border-color: rgba(255, 255, 255, .52);
}

/* ═══ 同一套材质，铺到「非按钮的玻璃控件」═══════════════════════════════════
   站长要「全站所有玻璃控件（含输入框/标签）」，但同一句话里还有「克制」。
   这里的克制体现在一件硬指标上：**一律不挂 backdrop-filter**。
   这四类在页面上分别有 214 / 60 / 23 / 202 处，几十上百个合成层会让滚动
   直接掉帧（`.cdr-header.is-scrolled` 那条教训，见本文件 header 段）。
   材质只靠四件事做：内圈亮线 + 底部暗线 + 外发丝环 + 极淡竖向渐变。 */

/* ---- 输入类：控制台/表单里的是"凹进去的接收面"，不是"抬起来的玻璃" ---- */
html.lg-on body.cdr-body .form-input,
html.lg-on body.cdr-body .form-select,
html.lg-on body.cdr-body .form-textarea {
  /* 顶部一道内阴影 = 让它读成"往里陷"，和按钮的"往外浮"形成对位；
     下面两道是玻璃本身：内圈 1px 亮线 + 外发丝环。 */
  border-color: color-mix(in srgb, var(--cdr-text) 14%, transparent);
  /* 顶深下浅 = 「陷下去」。白底白面上，1px 的内阴影太轻，
     所以这里加第二层 `-3px` 的扩散，让"凹"在 1x 下也看得出来。
     ⚠️ v1.9.84 迭代：第一版只有 .025 且 45% 就收干净 —— 实测（3x 放大后
     读像素）输入框顶端只比白卡片暗 3~4 级，1x 下看着就是一块纯白矩形。
     所以改成**整条竖向都压一点点**（.06 → .008），底不再回到纯白：
     凹陷要读出来，靠的是"这整块比周围暗"，不是靠边缘那一条。 */
  background-image: linear-gradient(180deg,
                    rgba(8, 15, 34, .06) 0%,
                    rgba(8, 15, 34, .02) 42%,
                    rgba(8, 15, 34, .008) 100%);
  box-shadow: inset 0 2px 4px -1px rgba(8, 15, 34, .12),
              inset 0 1px 3px rgba(8, 15, 34, .06),
              inset 0 1px 0 rgba(255, 255, 255, .85),
              inset 0 -1px 0 rgba(255, 255, 255, .75),
              0 0 0 1px color-mix(in srgb, var(--cdr-text) 10%, transparent);
  transition: box-shadow 180ms cubic-bezier(.22, .8, .22, 1),
              border-color 180ms cubic-bezier(.22, .8, .22, 1);
}
/* ⚠️ 聚焦态必须重写：上面那条（3 类 + 2 元素）权重已经高过本文件前面
    `.form-input:focus`（2 类），不补这一条**聚焦环会整个消失**。
   颜色仍用原来的 --cdr-primary 18%，只是把"亮环加强"这一层加上去。 */
html.lg-on body.cdr-body .form-input:focus,
html.lg-on body.cdr-body .form-select:focus,
html.lg-on body.cdr-body .form-textarea:focus {
  border-color: color-mix(in srgb, var(--cdr-primary) 60%, transparent);
  background-image: linear-gradient(180deg, rgba(8, 15, 34, 0) 0%, rgba(8, 15, 34, 0) 100%);
  box-shadow: inset 0 1px 2px rgba(8, 15, 34, .04),
              inset 0 1px 0 rgba(255, 255, 255, .95),
              0 0 0 3px color-mix(in srgb, var(--cdr-primary) 18%, transparent);
}

/* ---- 标签类：语义色全部原样（.badge-success / -warn / -danger 一个都不动），
       渐变只是**叠在上面的一层白**，所以标签还是那个颜色，只是有了厚度。 ---- */
html.lg-on body.cdr-body .badge,
html.lg-on body.cdr-body .player-chip {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, .40), rgba(255, 255, 255, .04));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .62),
              inset 0 -1px 0 color-mix(in srgb, var(--cdr-text) 10%, transparent),
              0 0 0 1px color-mix(in srgb, var(--cdr-text) 10%, transparent);
}
/* `.game-chip` / `.tool-chip` 本来就有 1px 边框，不再叠外发丝环（会读成双线），
   只加内圈亮线和底部暗线；**不碰 border-color** —— 它们的 :hover 要靠它变蓝。 */
html.lg-on body.cdr-body .game-chip,
html.lg-on body.cdr-body .tool-chip {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, .44), rgba(255, 255, 255, .04));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .58),
              inset 0 -1px 0 color-mix(in srgb, var(--cdr-text) 10%, transparent);
}

/* ---- 吸顶导航：底部一道发丝分隔线 + 内圈一道很淡的高光。
       它自己已经有 `1px solid --cdr-border` 和 blur(16px)，这里只补"厚度"：
       亮线在上沿、暗线贴在下沿（border 外面那 1px）。
       ⚠️ `:not(.is-scrolled)`：滚动后那一条本来就把投影换成 --cdr-shadow，
          不排除的话会被这里盖掉（同权重、后写者胜）。 ---- */
html.lg-on body.cdr-body .cdr-header:not(.is-scrolled) {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .60),
              inset 0 -1px 0 rgba(255, 255, 255, .30),
              0 1px 0 color-mix(in srgb, var(--cdr-text) 7%, transparent),
              0 10px 26px -24px rgba(8, 15, 34, .50);
}

/* ═══ 窄屏 / 触摸设备：不折射，但材质要留住 ═════════════════════════════════
   站长要的是"手机上自动降级"，脚本那边本来就整个退出（liquid-glass.js:98），
   所以真机上这里其实一行都不会生效 —— 它真正的用处在**桌面窗口拉到 ≤720px**：
   那时脚本还在跑、每个按钮仍挂着一套 url() 折射滤镜，而窗口窄 = 采样面积小，
   上沿那个"十"字亮斑（v1.9.45）最容易露出来。换成 blur 就干净了。
   ⚠️ 下面的选择器为什么要重复写 `[data-cdr-lg][data-cdr-lg]`：
     脚本注入的规则是「8 类 + 3 元素」，和基础规则同权重但**写在 head 末尾**，
     同权重后写者胜 —— 只写一遍会被它吃掉（`backdrop-filter: none` 那条
     现在就是这么失效的）。重复一个属性选择器 = 9 类，才压得住。 */
@media (max-width: 720px), (hover: none) {
  html.lg-on body.cdr-body .btn:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after,
  html.lg-on body.cdr-body .btn[data-cdr-lg]:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after,
  html.lg-on body.cdr-body .btn[data-cdr-lg][data-cdr-lg]:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after {
    /* 手机上看到的"磨砂"就是这一层：模糊量放大到 10px（屏小、DPI 高，
       6px 基本看不出磨砂），饱和度收到 150%（180% 在手机上偏艳）。 */
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
  }
  /* 小按钮连磨砂都不要：v1.9.45 那个"十"字亮斑是 **blur 本身**在小盒子上
     画出来的（不是折射），手机上一样会出现。 */
  html.lg-on body.cdr-body .btn.btn-sm:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after,
  html.lg-on body.cdr-body .btn.btn-sm[data-cdr-lg]:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after,
  html.lg-on body.cdr-body .btn.btn-sm[data-cdr-lg][data-cdr-lg]:not(.btn-primary):not(.btn-danger):not(.is-busy):not([aria-busy="true"])::after {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* 系统开了「减少动态效果」时，折射**保留**（它是静态的、不闪），
   只把悬停 / 按下那一下的位移与过渡关掉 —— 与站点其它动效的口径一致。 */
@media (prefers-reduced-motion: reduce) {
  html.lg-on body.cdr-body .btn { transition: none; }
  html.lg-on body.cdr-body .btn::after { transition: none; }
  html.lg-on body.cdr-body .btn:hover,
  html.lg-on body.cdr-body .btn:active { transform: none; }
}

/* ══════════════════════════════════════════════════════════════════
   v1.9.49 头像裁剪层
   ──────────────────────────────────────────────────────────────────
   整块由 me-upload.js 在用户选中头像文件时创建（站点里没有它的静态
   HTML）。三条要点：
     · `.cdr-crop-stage` 是 `overflow: hidden` 的方形取景框，里面的
       `<img>` 由脚本用 left/top/width/height 摆位 —— 不用 transform，
       因为导出时要按同样的数值反推源图坐标，多一层矩阵容易算错。
     · 四角那圈白色直角线（`.cdr-crop-rule`）用 box-shadow 画，不额外
       加 4 个元素；它是"这里就是最终头像"的唯一视觉提示。
     · 整层 `z-index: 200`，压在导航（60）与钉住条（50）之上 —— 裁剪
       时那两条不该盖住取景框。
   ══════════════════════════════════════════════════════════════════ */
.cdr-crop {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(8, 15, 34, .62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.cdr-crop-panel {
  width: 100%;
  max-width: 380px;
  padding: 18px;
  border-radius: 18px;
  background: var(--cdr-surface);
  border: 1px solid var(--cdr-border);
  box-shadow: var(--cdr-shadow-lg);
  text-align: center;
}
.cdr-crop-head { font-weight: 700; font-size: 1.02rem; margin-bottom: 12px; }
.cdr-crop-stage {
  position: relative;
  width: 288px;
  height: 288px;
  max-width: 100%;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 12px;
  background: #0b1020;
  cursor: grab;
  touch-action: none;
}
.cdr-crop.is-drag .cdr-crop-stage { cursor: grabbing; }
.cdr-crop .cdr-crop-img {
  position: absolute;
  left: 0;
  top: 0;
  /* ⚠️ 全站有一条 `.cdr-body img { max-width: 100% }`（style.css:1230），
     它比单个类（.cdr-crop-img）更具体，会把这张图卡在取景框宽度上 ——
     实测 1200×800 的图被压成 288×288，长宽比都没了，取景框也就"盖不满"。
     所以这里必须写成**两级选择器** `.cdr-crop .cdr-crop-img`（0,2,0）才压得住，
     尺寸完全由脚本按自然尺寸 × 缩放倍数算出来。 */
  max-width: none;
  max-height: none;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}
/* 取景框的四角直角线 + 压暗框外（框外压暗靠四片阴影一次画完） */
.cdr-crop-rule {
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .55),
    0 0 0 9999px rgba(8, 15, 34, .34);
}
.cdr-crop-zoom {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 2px 0;
  font-size: .84rem;
  color: var(--cdr-muted);
}
.cdr-crop-range { flex: 1; accent-color: var(--cdr-primary); }
.cdr-crop-tip {
  margin: 10px 2px 0;
  font-size: .78rem;
  line-height: 1.6;
  color: var(--cdr-muted);
  text-align: left;
}
.cdr-crop-acts {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 16px;
}
@media (max-width: 420px) {
  .cdr-crop-stage { width: 232px; height: 232px; }
  .cdr-crop-panel { padding: 14px; }
}

/* ---- v1.9.83 追加 ---- */
/* ==========================================================================
   @fragment: polish-v83 —— v1.9.83 全站动效与质感（站长要求：配色不动，
   允许排版 / 质感 / 动效重做）

   和 assets/js/ui-motion.js 里「D. 质感增强」那一段是**一套东西的两半**，
   改一边记得看另一边。前面 3650 行附近那段 @fragment: ui-motion 是 v1.9.76
   就已经有的底盘（滚动入场 / 悬停按下 / 加载反馈），本段只做**新增**，
   不重写它，也不用再写第二套 .reveal。

   四条硬规矩（和 ui-motion 那一段一致，别在这里破例）：
     1. **配色一个都不许改** —— 只用已有 --cdr-* 令牌与 color-mix 派生色；
     2. 任何「默认不可见」的东西，都必须是我方 JS 亲自打上 class 之后才生效。
        JS 没跑 / 观察器不支持 / 用户开了减少动效 ⇒ 页面必须完全正常；
     3. 悬停类效果一律包在 @media (hover: hover) and (pointer: fine) 里
        （触屏上 :hover 会黏住，手指走了卡片还亮着）；
     4. 本段末尾自带 @media (prefers-reduced-motion: reduce) 兜底。
   ========================================================================== */

/* ------------------------------------------------------- 1. 排版与节奏（质感） */

/* 大字标题收紧字距、抬高行高 —— 这是"看起来更贵"最省力的一步，
   纯排版，不动任何颜色。 */
h1, h2, h3, .hero h1, .section-head h2 { letter-spacing: -.022em; }
h1, .hero h1, .page-head h1 { line-height: 1.16; }
h2, .section-head h2 { line-height: 1.22; }

/* text-wrap 只在不支持时静默失效，属于"锦上添花且零风险"的一类：
   balance 让大标题不要出现孤零零两三个字占一整行，pretty 让正文不出现
   只有一两个词的末行。 */
@supports (text-wrap: balance) {
  h1, h2, h3, .hero h1, .page-head h1, .section-head h2 { text-wrap: balance; }
  .hero-lead, .section-head p, .page-head .lead { text-wrap: pretty; }
}

/* 宽屏再放大一档：原来 clamp 上限 2.7rem 落在 1440 的页面上偏小，
   和右侧那张大卡片不成比例。只在 ≥981px 生效，手机那档（.hero h1 1.6rem）
   一个字都不碰。 */
@media (min-width: 981px) {
  h1 { font-size: clamp(2.2rem, 2.85vw, 3.05rem); }
  .hero h1 { font-size: clamp(2.25rem, 3.05vw, 3.2rem); }
  .section { padding: clamp(54px, 4.6vw, 78px) 0; }
  .section-head { margin-bottom: clamp(24px, 2vw, 34px); }
}

/* ------------------------------------------------- 2. 卡片光斑（跟随鼠标的柔光） */

/* JS 只在 pointerenter 时挂 data-ui-spot 并持续写入 --ui-mx/--ui-my，
   离开就摘掉 —— 所以"没有鼠标"的设备和 JS 失效时，这条规则根本不会命中。 */
.card,
.me-card,
.hero-card,
.node-card,
.gallery-item {
  position: relative;
  isolation: isolate;
}

[data-ui-spot]::before {
  content: '';
  position: absolute;
  inset: 0;
  /* ⚠️ 必须是 -1（不是 0）：负 z 的伪元素画在"卡片自己的背景之上、内容之下"
     那一层，正好是光斑该待的位置。写 0 的话它会盖住文字，就得反过来给卡片里
     每个孩子加 position/z-index —— 那会打坏本来就绝对定位的子元素（浮角标、
     关闭按钮之类），是绝对不能做的一类"顺手修"。 */
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s ease;
  background: radial-gradient(
    240px circle at var(--ui-mx, 50%) var(--ui-my, 50%),
    color-mix(in srgb, var(--cdr-accent) 15%, transparent),
    transparent 68%
  );
}

[data-ui-spot].ui-spot-on::before { opacity: 1; }

/* --------------------------------------------- 3. 首屏入场（JS 亲自打 class 才生效） */

html.js-anim .ui-enter {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity .72s cubic-bezier(.22, 1, .36, 1),
    transform .72s cubic-bezier(.22, 1, .36, 1);
  transition-delay: calc(var(--ui-i, 0) * 90ms);
}

html.js-anim .ui-enter.ui-enter-in {
  opacity: 1;
  transform: none;
}

/* ----------------------------------------------------------- 4. 顶部阅读进度条 */

/* 和 .ui-busy-bar（不定长滑动、z-index 260）不是一回事：这条表示
   "这页你读了多少"，定长、只随滚动推进，所以刻意压在它下面一层。 */
.ui-progress {
  position: fixed;
  inset: 0 0 auto;
  height: 2px;
  z-index: 259;
  pointer-events: none;
  opacity: 0;
  transition: opacity .35s ease;
}

.ui-progress.is-on { opacity: 1; }

.ui-progress > i {
  display: block;
  height: 100%;
  transform-origin: 0 50%;
  transform: scaleX(var(--ui-p, 0));
  background: linear-gradient(90deg, var(--cdr-primary), var(--cdr-accent));
}

/* 数字滚动期间用等宽数字，避免宽度跳动把旁边的文字挤来挤去 */
.ui-count { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ 5. 首屏轻视差（JS） */

/* 只给 --ui-par-y 这一个自定义属性，位移量由 JS 算（上限 14px，手机不启用）。
   ⚠️ 不加 will-change：hero 卡片长期带着一个合成层不值（v1.9.76 在 .reveal
   上踩过同样的坑）。 */
[data-ui-par] { transform: translate3d(0, var(--ui-par-y, 0px), 0); }

/* --------------------------------------------------------------- 6. 动效降级 */

/* 无障碍要求，不是可选项。配色和描边保留（那不是动效），位移与淡入一律取消。 */
@media (prefers-reduced-motion: reduce) {
  html.js-anim .ui-enter,
  html.js-anim .ui-enter.ui-enter-in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    transition-delay: 0ms !important;
  }
  [data-ui-spot]::before { transition: none !important; }
  .ui-progress,
  .ui-progress > i { transition: none !important; }
  [data-ui-par] { transform: none !important; }
}

/* 打印：进度条、光斑、视差全都不该出现在纸上 */
@media print {
  .ui-progress { display: none !important; }
  [data-ui-spot]::before { display: none !important; }
  [data-ui-par] { transform: none !important; }
}
/* @fragment: hero3d —— 与 assets/js/hero3d.js、hero3d-hero.js 是一套东西 */
/* ============================================================ hero3d ====
   v1.9.84：整屏 3D 首屏（站长定的：整个首屏都是 3D，标题和按钮浮在场景上）。
   几条硬规矩，别改：
   1. 配色全部走既有令牌，canvas 只画场景、不接管页面底色；
   2. 默认不可见的东西必须由 JS 打 class 之后才出现（.is-ready / .is-live）；
   3. 手机与无 WebGL2 只留静态海报，点「进入 3D」才加载渲染器；
   4. 减少动效时不进 3D 也行 —— 海报本来就是一帧静止画面。
   ==================================================================== */

.hero--3d {
  padding-bottom: clamp(96px, 11vh, 140px);
  position: relative;
  display: flex;
  align-items: center;
  min-height: min(86vh, 760px);
  padding: clamp(88px, 10vh, 132px) 0 clamp(64px, 8vh, 104px);
  overflow: hidden;
}
.hero3d-stage { position: absolute; inset: 0; z-index: 0; }

.hero3d-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; opacity: 0;
  transition: opacity .8s ease;
  touch-action: pan-y;      /* 竖着划还是滚页面，横着划才是转模型 */
  cursor: grab;
}
.hero3d-canvas.is-ready { opacity: 1; }
.hero3d-canvas:active { cursor: grabbing; }

/* 下沿淡出：3D 不该在第一屏底边"被切一刀" */
.hero--3d .hero3d-canvas {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 86%, rgba(0,0,0,.55) 95%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 86%, rgba(0,0,0,.55) 95%, transparent 100%);
}

.hero3d-poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 72% 50%;
  transition: opacity .6s ease;
}
/* 海报同样下沿淡出，手机上一进入页面就是这张图 */
.hero--3d .hero3d-poster {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 86%, rgba(0,0,0,.55) 95%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 86%, rgba(0,0,0,.55) 95%, transparent 100%);
}
.hero--3d.is-live .hero3d-poster { opacity: 0; }

/* 左侧给正文让出干净的底色：从 bg 渐到透明，标题永远读得清 */
.hero3d-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg,
    var(--cdr-bg) 4%,
    color-mix(in srgb, var(--cdr-bg) 72%, transparent) 28%,
    color-mix(in srgb, var(--cdr-bg) 18%, transparent) 44%,
    transparent 54%);
}
[data-color-mode="dark"] .hero3d-scrim,
.dark .hero3d-scrim {
  background: linear-gradient(90deg,
    var(--cdr-bg) 4%,
    color-mix(in srgb, var(--cdr-bg) 66%, transparent) 28%,
    color-mix(in srgb, var(--cdr-bg) 14%, transparent) 44%,
    transparent 54%);
}

.hero--3d .hero-inner {
  padding-bottom: 64px;   /* 给底部云带留位，别和「服务器地址」框叠在一起 */ position: relative; z-index: 2; }
.hero--3d .hero-card {
  background: color-mix(in srgb, var(--cdr-surface) 68%, transparent);
  backdrop-filter: blur(18px) saturate(120%);
}

@supports not (backdrop-filter: blur(4px)) {
  .hero--3d .hero-card { background: var(--cdr-surface); }
}

.hero3d-hint {
  position: absolute; right: 18px; bottom: 18px; z-index: 3;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: var(--cdr-radius-pill);
  background: color-mix(in srgb, var(--cdr-surface) 74%, transparent);
  border: 1px solid var(--cdr-border);
  backdrop-filter: blur(10px);
  font-size: .8rem; font-weight: 600; color: var(--cdr-muted);
  opacity: 0; transform: translateY(6px);
  transition: opacity .45s ease, transform .45s ease;
  pointer-events: none;
}
.hero--3d.is-live .hero3d-hint { opacity: 1; transform: none; }

.hero3d-enter {
  position: absolute; right: 18px; bottom: 18px; z-index: 3;
  display: none; align-items: center; gap: 8px;
  padding: 10px 18px; border-radius: var(--cdr-radius-pill);
  border: 1px solid var(--cdr-border);
  background: color-mix(in srgb, var(--cdr-surface) 88%, transparent);
  backdrop-filter: blur(10px);
  color: var(--cdr-text); font-weight: 650; font-size: .88rem;
  box-shadow: var(--cdr-shadow-lg); cursor: pointer;
}
.hero--3d.has-poster .hero3d-enter { display: inline-flex; }
.hero3d-enter:hover { border-color: color-mix(in srgb, var(--cdr-accent) 55%, var(--cdr-border)); }

@media (max-width: 940px) {
  .hero--3d { min-height: 0; padding: 64px 0 56px; }
  .hero3d-scrim {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--cdr-bg) 86%, transparent) 0%,
      color-mix(in srgb, var(--cdr-bg) 54%, transparent) 34%,
      color-mix(in srgb, var(--cdr-bg) 24%, transparent) 58%,
      transparent 82%);
  }
  /* v1.9.89：手机上模型当"淡背景"，位置偏下偏右，压暗到不抢标题与按钮 */
  .hero3d-poster { object-position: 68% 74%; }
  .hero--3d .hero3d-canvas,
  .hero--3d .hero3d-poster { opacity: .45; }
  .hero3d-hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero3d-canvas, .hero3d-poster, .hero3d-hint { transition: none; }
  .hero3d-hint { transform: none; }
}

@media print { .hero3d-stage, .hero3d-hint, .hero3d-enter { display: none; } }

/* ====================================================================
   v1.9.84：换页过渡（配套 assets/js/page-transition.js）
   - 离场只有 160ms 的淡出，别拖；
   - 进场是新页的顶层区块依次上滑，最多 6 块、每块 70ms；
   - **没有离场淡出**（v1.9.86 删掉）：旧页一淡就露白底，而新页要等网络往返才到；
   - 首屏 3D 的页面不参与（JS 里已经排除）；
   - 减少动效时这条规则整条不生效（JS 根本不加 class）。
   ==================================================================== */
.pt-rise { opacity: 0; transform: translateY(12px); }
html.pt-in-on .pt-rise {
  opacity: 1;
  transform: none;
  transition:
    opacity .52s cubic-bezier(.22, 1, .36, 1),
    transform .52s cubic-bezier(.22, 1, .36, 1);
  transition-delay: calc(var(--pt-i, 0) * 70ms);
}
@media (prefers-reduced-motion: reduce) {
  .pt-rise { opacity: 1; transform: none; }
  html.pt-in-on .pt-rise { transition: none; }
}

/* ---------------------------------------------------------------- v1.9.88
   首屏重排（站长：「把建模放在中间」）：标题在上、模型正中、按钮在下，
   原来的「当前在线」卡搬出首屏另起一条 .hero-live。
   这里的规则只负责"居中"，3D 舞台/遮罩/海报那套仍然是 v1.9.84 的。 */
.hero--3d .hero-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.hero--3d .hero-copy { max-width: 760px; }
.hero--3d .hero-copy .hero-eyebrow { margin-inline: auto; }
.hero--3d .hero-lead { margin-inline: auto; }

/* 中间留给模型的那段空白：桌面高一点，手机收起来（手机只有海报，不给空档） */
.hero3d-gap { height: clamp(180px, 34vh, 340px); width: 100%; }
@media (max-width: 940px) { .hero3d-gap { height: 0; } }

.hero--3d .hero-cta { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.hero--3d .hero-actions { justify-content: center; }
.hero--3d .ip-box { margin-inline: auto; }

/* 「当前在线」卡：从首屏右侧挪到首屏下面一条 */
.hero-live { padding: 6px 0 26px; }
.hero-live .hero-card {
  max-width: none;
  margin: 0;
  display: grid;
  grid-template-columns: minmax(190px, 260px) 1fr;
  gap: 8px 22px;
  align-items: center;
}
@media (max-width: 860px) {
  .hero-live .hero-card { display: block; }
}
/* ==========================================================================
   @fragment: hero3d-drag（v1.9.90）
   站长反馈「现在没办法拖动了」——v1.9.88 把标题/按钮/地址框搬进首屏正中以后，
   .hero-inner 与 .hero3d-gap 这两层是"整块盖在画布上的空 div"，指针全被它们接走。
   做法：让这两层对指针透明，只把可点的元素单独打开。
   改完必须跑 .tools/_h3d-dragcheck.js 确认 hits 里出现 hero3d canvas。
   ========================================================================== */
.hero--3d .hero-inner,
.hero--3d .hero3d-gap { pointer-events: none; }

.hero--3d .hero-inner a,
.hero--3d .hero-inner button,
.hero--3d .hero-inner summary,
.hero--3d .hero-inner input,
.hero--3d .hero-inner textarea,
.hero--3d .hero-inner select,
.hero--3d .hero-inner label,
.hero--3d .hero-inner .btn,
.hero--3d .hero-inner .ip-box,
.hero--3d .hero-inner .card { pointer-events: auto; }
/* ==========================================================================
   @fragment: hero-split（v1.9.90）
   站长：「把云岭之上与你同游放在上面，3D投影放在左边，加入服务器和状态栏都放在右边」。
   结构：.hero-copy（标题，居中整行）→ .hero-grid（左 .hero3d-slot / 右 .hero-side 两张卡）。
   舞台仍然是 v1.9.84 那套（canvas + 海报 + 遮罩），只是参照物从 section 换成了左栏。
   ========================================================================== */
.hero--3d { padding: clamp(48px, 6vh, 76px) 0 clamp(36px, 5vh, 56px); }
.hero--3d .hero-inner { padding-bottom: 56px; }
.hero--3d .hero-copy { max-width: 880px; margin: 0 auto; text-align: center; }
.hero--3d .hero-copy .hero-eyebrow { display: inline-flex; }
.hero--3d .hero-lead { margin-inline: auto; }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(300px, .7fr);
  gap: clamp(16px, 2vw, 30px);
  align-items: stretch;
  margin-top: clamp(16px, 2.6vh, 30px);
}
.hero3d-slot { position: relative; min-height: clamp(320px, 48vh, 580px); }
.hero3d-slot .hero3d-stage { position: absolute; inset: 0; border-radius: var(--cdr-radius); overflow: hidden; }
.hero3d-slot .hero3d-hint { right: 12px; bottom: 12px; }
.hero3d-slot .hero3d-enter { position: absolute; inset: 12px 12px auto auto; z-index: 3; }

.hero-side { display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 14px; }
.hero-side .card { margin: 0; }
.hero-join .hero-actions { display: flex; flex-wrap: wrap; gap: 9px; margin: 12px 0 14px; }
.hero-join .hero-actions .btn { flex: 1 1 auto; justify-content: center; }
.hero-join .ip-box { margin: 0; }
.hero-status-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.hero-status-more { font-size: .8rem; color: var(--cdr-muted); text-decoration: none; }
.hero-status-more:hover { color: var(--cdr-accent); }
.hero-status .hero-stat-row { margin-top: 6px; }
.hero-mini-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 14px; }
.hero-mini { background: var(--cdr-bg); border: 1px solid var(--cdr-border); border-radius: var(--cdr-radius-sm); padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; }
.hero-mini-k { font-size: .72rem; color: var(--cdr-muted); }
.hero-mini-v { font-size: 1.02rem; font-weight: 700; font-variant-numeric: tabular-nums; }
[data-color-mode="dark"] .hero-mini, .dark .hero-mini { background: var(--cdr-bg-dark); }

@media (max-width: 940px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); }
  .hero3d-slot { min-height: clamp(230px, 54vw, 380px); }
  .hero--3d .hero-inner { padding-bottom: 34px; }
}
/* v1.9.90：舞台搬进左栏之后，左侧那层白纱没必要了（它原来是为整屏铺底做的），
   改成只在下沿淡出，避免把模型洗成一片白。 */
.hero3d-slot .hero3d-scrim { background: linear-gradient(180deg, transparent 74%, color-mix(in srgb, var(--cdr-bg) 88%, transparent) 100%); }
/* v1.9.90：舞台已经搬进左栏，手机上不再是"衬在文字后面的淡背景"，
   模型可以正常不透明地显示，按钮只占右上角一小块。 */
.hero3d-slot .hero3d-poster { opacity: 1; }
@media (max-width: 940px) {
  .hero--3d .hero3d-canvas, .hero--3d .hero3d-poster { opacity: 1; }
  .hero3d-slot .hero3d-poster { object-position: 50% 46%; }
}
