/* 海誓山盟 · 基础样式
 * 原则一：内容的可见性不依赖任何动画。开了"减少动效"的系统设置下，
 * 这里所有 transition 都会被关掉，而文字、按钮、表单必须照常全部可见。
 *
 * 原则二（这一批加的）：前台与后台共用这一份表，但深色只属于前台。
 * 所以 :root 是深色那一套，下面 body.admin 那一块把后台今天用到的每一个值原样写回——
 * 后台因此一个像素都没变。代价是共享的那些类（.panel .msg .badge .btn .note .pager .form
 * .field .prose code h1）一律不许写死颜色，只能读令牌：写死一处，另一侧就跟不上。
 * 所以新增的令牌多数是"同一个位置在两侧各要一个值"。
 *
 * 配色「山海为证」：深海蓝 #0b1d33 做底，雾霭蓝 #3a5a7a 做遮罩与描边，月光白 #f5f1e8 做字，
 * 珊瑚金 #d4a373 做实心，星光金 #e8c87a 点缀。两处是量过之后才落地的，不是照简报原样抄：
 * 1) 实心那格上压的是深字（#0b1d33 于 #d4a373 = 7.50:1）；白字压珊瑚金只有 2.26:1，
 *    所以这一条走 --on-accent：前台是深，后台仍是白。
 * 2) 雾霭蓝 #3a5a7a 压在深海底上只有 2.36:1，它不承载任何文字——只出现在描边、遮罩与渐变里；
 *    次要文字另开一档（--ink-soft、--sky）。
 * 这些数是从 WCAG 相对亮度算的。同一把尺复算旧的那两条（白字压粉 5.01:1、深粉压纸 7.22:1）
 * 与 DEPLOY.md 里写的分毫不差，说明量的是同一件事。断言在自测第 40 节，改配色跑不掉它。
 *
 * 字体一律用系统自带，不引外部字体 CDN：国内取不到就是白等三秒。
 * 标题那一串把 Georgia 放在最前：拉丁字母取它的衬线字形（这就是"英文标题用衬线 + 字距"的落地办法），
 * 汉字它没有，自动退到宋体那一串——不下载任何东西，两头都照顾到。 */

:root {
    /* 文字与底 */
    --ink: #f5f1e8;            /* 月光白：正文与标题。压在深海蓝上 15.05:1 */
    --ink-soft: #a9bfd2;       /* 次要文字：浅一档的雾白 */
    --line: #2a4560;
    --line-soft: #1e374f;
    --paper: #0b1d33;          /* 深海蓝：全站底。图来之前首屏就该是这一个颜色，不闪白 */
    --card: #13293f;           /* 底板那一档，比底亮一点 */

    /* 金：实心与点缀 */
    --accent: #d4a373;         /* 实心填充：按钮底、焦点圈。上面压的字见 --on-accent */
    --accent-dark: #e8c87a;    /* 链接与强调字。名字里的 dark 在深底上是反的：它必须比实心那格更亮 */
    --accent-bright: #f2d9a4;  /* 只装饰：描边、光晕、圆点 */
    --accent-tint: rgba(212, 163, 115, .16);   /* hover 与提示条那层底 */
    --accent-hair: rgba(232, 200, 122, .34);   /* 极细的一道边 */
    --on-accent: #0b1d33;      /* 压在实心那格上的字色：前台深、后台白 */
    --btn-shadow: rgba(3, 10, 20, .55);
    --btn-shadow-danger: rgba(3, 10, 20, .55);

    /* 蓝与状态 */
    --sky: #8fb3d0;            /* 计数与次要标签 */
    --sky-tint: rgba(58, 90, 122, .34);
    --note-ink: #c6d5e2;
    --sun: #e8c87a;            /* "要处理"那一档 */
    --ok: #8fd3a8;
    --ok-tint: rgba(143, 211, 168, .16);
    --ok-hair: rgba(143, 211, 168, .40);
    --err: #e8938a;
    --err-tint: rgba(232, 147, 138, .16);
    --err-hair: rgba(232, 147, 138, .40);
    --err-dark: #cf7468;

    /* 控件与杂项 */
    --field-bg: #16304b;
    --field-line: #5b82ac;     /* 输入框、文本域、select、验证码图那圈描边专用的一条 */
    --placeholder: #8ba2b8;    /* 占位字也是字，走 4.5 那一档：压在 --field-bg 那一格上量出来 5.11:1，由第 40 节钉住 */
    --line-hover: #6b97c2;     /* hover 时那一圈描边。它只当线，不当字。比静态那条更亮一档：
                                  静态已经提到 3.69:1，hover 若回落到 #3a5a7a（2.06:1）就成了"一划上去框反而更淡"，
                                  那是这一批自己造出来的倒退，所以把这一条抬到静态之上 */
    --hairline-bg: #1a334e;
    --code-bg: #16304b;
    --media-matte: #071527;    /* 图与视频还没进来时那一块 matte，两侧都用深色 */
    --glass: rgba(13, 32, 55, .80);     /* 浮在背景上的半透明底板 */
    --glass-head: rgba(13, 32, 55, .86);
    --glass-border: rgba(232, 200, 122, .14);

    /* 海面那三段渐变：背景图来之前它就是首屏全部的颜色，图来了它垫在图的下面补边缘。
       只有 body.front 的规则读这三个，所以与 --glass 那三条同批，不在 body.admin 里复原。 */
    --sea-top: #0a2740;
    --sea-mid: #0b1d33;
    --sea-deep: #071527;

    --radius: 16px;
    --radius-sm: 11px;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .35), 0 6px 18px rgba(0, 0, 0, .28);
    --shadow: 0 2px 6px rgba(0, 0, 0, .40), 0 14px 34px rgba(0, 0, 0, .45);
    --font-ui: system-ui, -apple-system, "Segoe UI", "Microsoft YaHei UI", "PingFang SC",
               "Hiragino Sans GB", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", SimSun, serif;
    --font-display: Georgia, "Noto Serif SC", "Source Han Serif SC", "Songti SC", SimSun, serif;
    --font-mono: ui-monospace, Consolas, monospace;
}

/* 后台那一侧：把上面每一个被前台反过来的令牌写回它今天的老值。
   这一块的唯一标准是"后台看起来和这一批之前完全一样"，所以每条都是照 :root 旧值抄回去的，
   不是重新调的。--glass 那三条不在这里：只有 body.front 的规则读它们，后台用 --card 实心底。 */
body.admin {
    --ink: #2b2a33;
    --ink-soft: #6a6878;
    --line: #ece7de;
    --line-soft: #f2eee7;
    --paper: #fdf9f5;
    --card: #ffffff;
    --accent: #c8395a;
    --accent-dark: #9c2740;
    --accent-bright: #f56d8a;
    --accent-tint: #fff1f4;
    --accent-hair: #f6d7de;
    --on-accent: #ffffff;
    --btn-shadow: rgba(156, 39, 64, .18);
    --btn-shadow-danger: rgba(176, 54, 41, .18);
    --sky: #2f6d94;
    --sky-tint: #eef5f9;
    --note-ink: #55514d;
    --sun: #e0a02e;
    --ok: #2f6b4f;
    --ok-tint: #eef6f0;
    --ok-hair: #cfe3d6;
    --err: #b03629;
    --err-tint: #fcefec;
    --err-hair: #eecfcb;
    --err-dark: #8d2a20;
    --field-bg: #ffffff;
    --field-line: #ece7de;      /* 后台这一格还是它那一条浅色描边：输入框看起来和今天一模一样 */
    --placeholder: #a8a5b3;
    --line-hover: #dccfca;
    --hairline-bg: #faf8f4;
    --code-bg: #f4f1ea;
    --media-matte: #1c1a17;
    --shadow-sm: 0 1px 2px rgba(43, 42, 51, .05), 0 6px 18px rgba(43, 42, 51, .06);
    --shadow: 0 2px 6px rgba(43, 42, 51, .07), 0 14px 34px rgba(43, 42, 51, .10);
    /* 标题字体也要写回：后台的抬头全是汉字，两边取到的其实是同一个字形，
       但只要标题里混进一个数字或一段英文就换样子了。 */
    --font-display: "Noto Serif SC", "Source Han Serif SC", "Songti SC", SimSun, serif;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font: 16px/1.8 var(--font-ui);
    -webkit-text-size-adjust: 100%;
    text-rendering: optimizeLegibility;
}

/* 背景层：前台整片都铺，后台不铺（后台用的是另一个布局，那一头根本没有这个 div）。
   等比 cover 铺满整个视口——不变形、不重复、始终居中；放在一个 position: fixed 的层里，
   滚动与 PJAX 换内容时它都钉在原地。既然它不动，就不给它任何视差位移：动效只加在内容上。
   不用 background-attachment: fixed 做这件事：iOS Safari 上那条会跟着地址栏收起展开而重新定位，
   还会把整层位图塞进合成内存；换成一个固定的元素是同一件事的稳妥写法。
   最上面那层是遮罩（--scrim-a/--scrim-b）：图再亮也要压得住月光白的字；
   非首页压得更重，因为那些页要读的正文更长。
   --bg-url 由布局那一行内联写进来，只有文件真在的时候才写。占位期没有图，
   下面那两层渐变自己就是一张完整的深海背景：不出现破图，也不会白闪一下。 */
.bg-layer {
    position: fixed;
    inset: 0;
    z-index: -1;
    display: none;
    pointer-events: none;
}
/* color-scheme 这一条是给浏览器原生控件看的：日期框那个小日历、数字框的上下箭头、
   select 弹出的那张菜单、自动填充的淡黄底——它们不读我们的 CSS，只跟着主题色走。
   不给 dark，它们就按浅色主题画，落在 --field-bg 上是一个亮块压深色，看着像画歪了。
   给了 dark，浏览器自己把那些小图标翻成浅色，所以我们不需要再写 filter: invert。
   html 上那条是给「整页画布 + 滚动条」用的：规范只认根元素上的 color-scheme 影响画布。
   :has() 不认识的老浏览器会整条跳过，那时 body 上那条仍然管住控件，只是滚动条还是浅色——
   掉的是观感，不是可用性。后台不写这一条，它继续用系统默认的浅色。 */
html:has(body.front) {
    color-scheme: dark;
}
/* 遮罩为什么压到 .82/.88/.92 这么深：这一层不是装饰，是正文的唯一底线。
   首页顶部那一片（站名、副标题、提示语）、页脚那两行、状态条、首页数字那一格都不在任何面板里，
   字是直接压在背景层上的——也就是说，只要图够亮，遮罩就是"字还在不在"的那一道。
   按最坏情况（图的这一段是纯白）现算，每个数都由自测第 40 节从这份文件里量出来：
     .42（上一批那一条）压纯白得 rgb(151,157,164)：月光白 2.43:1、次要字 1.44:1，等于字没了。
     .82 压纯白得 rgb(52,63,78)：正文 9.47、次要字 5.63、链接金 6.60、hover 那档珊瑚金 4.72、计数蓝 4.84，五档全过 4.5。
     .88 与 .92 是渐变往下走的那两条，同一块遮罩只深不浅，所以那一组数只会更高（往纯白方向压得更少）。
   反过来，简报里那种夜色图几乎不受影响：图是暗的（比如 rgb(10,39,64)）时压完还是近于原色，
   遮罩深浅只对"图里最亮的那一块"起作用——那正是会吃掉字的一块。
   曾经给首页单独留一档更透的 .78/.86：同一批字在那一档上，hover 那档珊瑚金掉到 4.5 以下，
   所以两档并成一档。简报里"背景图要看得见"这件事交给首页顶部那团雾霭蓝的光（它在遮罩下面，只调 hue 不调深浅），
   不拿正文去换。哪一天想再分档，先把第 40 节那一组裸字断言跑一遍。 */
body.front {
    --scrim-a: rgba(7, 21, 39, .82);
    --scrim-b: rgba(7, 21, 39, .88);
    --scrim-c: rgba(7, 21, 39, .92);
    color-scheme: dark;
}
body.front .bg-layer {
    display: block;
    background:
        linear-gradient(180deg, var(--scrim-a) 0%, var(--scrim-b) 55%, var(--scrim-c) 100%),
        var(--bg-url, none) no-repeat center center / cover,
        radial-gradient(120% 78% at 50% 8%, rgba(58, 90, 122, .55) 0%, rgba(58, 90, 122, 0) 62%),
        linear-gradient(180deg, var(--sea-top) 0%, var(--sea-mid) 46%, var(--sea-deep) 100%);
}

/* 潮汐粒子：两层纯 CSS 叠在背景上，不写脚本——pjax.js 一看见内联 <script> 就退回整页跳转，
   为几个光点把无刷新换页打掉不值。
   ::before 是星点（几团极小的 radial-gradient 各按自己的像素尺寸平铺，慢慢偏一点再偏回来；
   用 alternate 往复而不是转圈循环，是为了永远不出现"跳回起点"那一下）。
   ::after 是潮光（海面那一段亮起来再退下去）。
   两层的静止态都是能看的：动画被掐掉时它们停在规则里那一个 opacity 上，不会变成一团黑。 */
.bg-layer::before {
    content: "";
    position: absolute;
    inset: -8%;
    background-image:
        radial-gradient(1.2px 1.2px at 20% 30%, rgba(245, 241, 232, .85), rgba(245, 241, 232, 0) 60%),
        radial-gradient(1.6px 1.6px at 65% 12%, rgba(232, 200, 122, .75), rgba(232, 200, 122, 0) 60%),
        radial-gradient(1.2px 1.2px at 42% 68%, rgba(245, 241, 232, .60), rgba(245, 241, 232, 0) 60%),
        radial-gradient(1.8px 1.8px at 85% 55%, rgba(245, 241, 232, .50), rgba(245, 241, 232, 0) 60%),
        radial-gradient(1.2px 1.2px at 8% 82%, rgba(232, 200, 122, .45), rgba(232, 200, 122, 0) 60%);
    background-size: 320px 320px, 460px 460px, 380px 380px, 560px 560px, 420px 420px;
    opacity: .5;
    animation: star-drift 72s ease-in-out infinite alternate;
}
.bg-layer::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(130% 46% at 50% 100%, rgba(58, 90, 122, .58) 0%, rgba(58, 90, 122, 0) 70%);
    opacity: .7;
    animation: tide-band 34s ease-in-out infinite alternate;
}

/* 前台的底板换成浮在背景上的半透明那一档，卡片像压在海面上。
   先把层序说清楚，因为对比度是拿这个顺序算的：海面渐变 → 背景图 → 遮罩 → 这一层玻璃 → 字。
   也就是说玻璃背后最坏不是"纯白"，是"纯白压过遮罩"的那一块（rgb(52,63,78)）；玻璃比它更暗，
   只会把底子往下压，不会往上抬，所以字的那道底线是遮罩定的，玻璃是加上去的余量。
   最坏情况下这一格合成出来是 rgb(21,38,60)，比实心的 --card 还暗一点，上面那六个颜色现算都过：
   正文 13.56、链接金 9.44、hover 那档珊瑚金 6.76、计数蓝 6.93、成了那档字 8.77、坏了那档字 6.53。
   自测第 40 节按这个层序现算，并把 .80/.86 这两个不透明度钉住；它同时留了一条负数断言：
   同一批字如果跳过遮罩那一层直接压"纯白 + 玻璃"，计数蓝只剩 3.93、报错字只剩 3.70——
   那正是"遮罩动不得"的凭据。 */
body.front .site-head,
body.front .panel,
body.front .vcard,
body.front .vow-list > li {
    background: var(--glass);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    box-shadow: var(--shadow);
}
body.front .site-head {
    background: var(--glass-head);
    border-color: var(--glass-border);
}

a { color: var(--accent-dark); text-decoration-thickness: 1px; text-underline-offset: 3px; }
/* hover 这一档两侧共用同一个数：实心珊瑚金压在浅纸上是后台它今天的样子，压在最坏情况的遮罩上是 4.72:1，
   过 4.5。两侧都不需要额外的 hover 令牌——曾经给前台单开过一档亮金，是因为那一个数手算错了，
   量准以后这一条回到了共用。断言在第 40 节（裸字那一组）。 */
a:hover { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---------- 结构 ---------- */
/* 页眉居中：站名在上，导航在下。窄屏不用折行挤成一团，天然就是居中的一竖排。 */
.site-head {
    display: flex; flex-direction: column; align-items: center; gap: 12px;
    padding: 24px 20px 18px; text-align: center;
    border-bottom: 1px solid var(--line);
    background: var(--card);
}
.brand {
    font: 700 22px/1.4 var(--font-display); letter-spacing: 4px;
    text-decoration: none; color: var(--ink);
}
.brand:hover { color: var(--accent-dark); }
.nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; font-size: 15px; }
.nav a {
    text-decoration: none; color: var(--ink-soft);
    padding: 5px 12px; border-radius: 999px; border: 1px solid transparent;
}
.nav a:hover { color: var(--accent-dark); background: var(--accent-tint); border-color: var(--accent-hair); }
.nav .cta {
    background: var(--accent); border: 1px solid var(--accent); color: var(--on-accent); font-weight: 600;
    box-shadow: 0 2px 0 var(--btn-shadow);
}
.nav .cta:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: var(--on-accent); }
.nav .who { color: var(--ink); font-weight: 600; padding: 5px 4px; }
.nav .inline { margin: 0; }

/* 标题的字距只加在前台：后台那几个抬头一字不改，连它们身边的空隙也不例外。 */
body.front .brand { letter-spacing: 8px; }
body.front h1 { letter-spacing: 3px; }

.wrap { max-width: 760px; margin: 0 auto; padding: 34px 20px 64px; }
/* PJAX 换的正是这一块；换完把焦点放这里，读屏的人跟着到新页，不会停在原地的旧导航上。 */
.wrap[tabindex="-1"]:focus { outline: none; }
.site-foot {
    border-top: 1px solid var(--line); padding: 26px 20px 44px;
    text-align: center; color: var(--ink-soft); font-size: 13px;
}
.site-foot p { margin: 6px 0; }
.site-foot .legal { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
.site-foot .legal a { color: var(--ink-soft); text-decoration: none; border-bottom: 1px solid var(--line); }
.site-foot .legal a:hover { color: var(--accent-dark); border-bottom-color: var(--accent-bright); }

/* ---------- 前场的两条通路：跳到内容、回到顶部 ----------
   这两个类只存在于前台布局的 DOM 里（后台用的是另一个布局，它压根没有这两节），
   所以规则不披 body.front 也不会碰到后台的一个像素——这一条由自测第 40 节按"后台源码里没有这两个类"盯住。
   隐藏靠 top: -999px 而不是 opacity/transform：前者与动画无关，减动效那一档、后台设成"强制不动"
   那一档，聚焦时它都照样落下来。 */
.skip-link {
    position: fixed; top: -999px; left: 12px; z-index: 30;
    text-decoration: none; font-size: 15px; font-weight: 600;
    padding: 10px 18px; border-radius: 999px;
}
.skip-link:focus {
    top: 12px;
    /* 实心珊瑚金压 --on-accent 那一档深字：与页眉「立誓约」那一格同一对颜色，
       第 40 节量的是同一组数（前台 7.50:1）。 */
    background: var(--accent); color: var(--on-accent);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
}
/* 回到顶部：一条锚点，滚不滚得滑由下面那一条 scroll-behavior 说，这里只负责待得住、点得着。
   底板用实心的 --card 而不是 --glass-head：第 40 节把 --glass 那三条读它的语句圈死在背景层那一段里
   （后台拿不到深色底板那一条断言），浮在角落的这一颗不需要透，正好不必越那道线。 */
.to-top {
    position: fixed; right: 16px; bottom: 18px; z-index: 20;
    display: flex; align-items: center; justify-content: center;
    width: 40px; height: 40px;
    border: 1px solid var(--accent-hair); border-radius: 999px;
    background: var(--card); color: var(--accent-dark);
    box-shadow: var(--shadow-sm);
}
.to-top:hover { color: var(--accent-bright); border-color: var(--accent-bright); }
/* 窄屏与底部的系统手势条、滚动条挨着，让开一格。 */
@media (max-width: 560px) {
    .to-top { right: 10px; bottom: 12px; }
}

h1 { font: 700 28px/1.5 var(--font-display); letter-spacing: 1px; margin: 0 0 12px; }
h2 { font-size: 19px; letter-spacing: .5px; margin: 28px 0 8px; }
.lede { color: var(--ink-soft); margin: 0 0 24px; }

.panel {
    background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 28px; box-shadow: var(--shadow-sm);
}
.panel.narrow { max-width: 480px; margin: 0 auto; }
.panel.center { text-align: center; }
.panel.empty, .empty { color: var(--ink-soft); }
/* 说明页与誓约页的长文：正文换成衬线，读起来才像一封信。 */
.prose { font-family: var(--font-serif); }
.prose p { margin: 0 0 15px; }
.prose h2 { font-family: var(--font-ui); margin-top: 26px; }

/* 首页即广场：顶部这一条要窄，因为下面紧接着就是卡。三个数字收在这一条里，
   入口只留页眉那一颗——首屏再来一颗同义钮，两个动词只会互相抢。 */
.hero { text-align: center; padding: 22px 0 8px; }
.hero .stats { justify-content: center; }
.hero .stats li { flex: 0 1 auto; padding: 8px 16px; }
.hero .stats strong { font-size: 22px; }
.hero .aside { max-width: 560px; margin: 10px auto 0; font-size: 13px; }
.hero h1 { letter-spacing: 6px; }
/* 标题下面一道短横：纯装饰，不承载任何信息，去掉也不影响读。 */
.hero h1::after {
    content: ""; display: block; width: 54px; height: 3px; margin: 14px auto 0;
    border-radius: 3px; background: linear-gradient(90deg, var(--accent-bright), var(--accent));
}
.hero .lede { font-size: 17px; font-family: var(--font-serif); margin-bottom: 12px; }
.greeting { font-size: 17px; margin: 0 0 6px; }

/* ---------- 表单 ---------- */
.form { display: grid; gap: 18px; margin-top: 6px; }
.field { display: grid; gap: 6px; }
.field > span { font-size: 14px; color: var(--ink-soft); }
/* 控件的描边单开一条令牌（--field-line），不复用 --line。
   理由是 WCAG 1.4.11（非文本对比）：一个界面的边界要能认出来，得和相邻颜色差到 3:1。
   --line 那条在深色底上只有 1.50:1（旧浅色那套更差，白底压米纸 1.23:1，是历史欠账不是这批改坏的），
   它同时要管全站的分隔线——分隔线要是提到 3:1，正文里就到处是亮线，读起来像一张网。
   所以只把控件那一圈提上来：#5b82ac 压在 --card 上 3.69:1、压在 --paper 上 4.23:1，过线；
   后台那一格写回 #ece7de，也就是它原来读的 --line 的值，于是后台一个像素没变。
   焦点态不靠这条线认：那时有 --accent 的描边加 4px 光圈（#d4a373 于 #13293f 是 6.55:1）。 */
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=date], input[type=number], textarea {
    font: inherit; padding: 11px 14px; width: 100%;
    border: 1px solid var(--field-line); border-radius: var(--radius-sm);
    background: var(--field-bg); color: var(--ink);
    transition: border-color .15s, box-shadow .15s;
}
input[type=date], input[type=number] { width: auto; }
input::placeholder, textarea::placeholder { color: var(--placeholder); }
input[type=text]:hover, input[type=email]:hover, input[type=password]:hover,
input[type=search]:hover, input[type=date]:hover, textarea:hover { border-color: var(--line-hover); }
input:focus, textarea:focus {
    outline: none; border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-tint), 0 0 0 4px var(--accent-bright);
}
input[type=file] {
    font: inherit; width: 100%; padding: 10px 12px;
    border: 1px dashed var(--field-line); border-radius: var(--radius-sm);
    background: var(--field-bg); color: var(--ink);
}
input[type=checkbox], input[type=radio] { width: 16px; height: 16px; accent-color: var(--accent); margin: 0; }

.btn {
    font: inherit; font-size: 16px; font-weight: 600; letter-spacing: 1px; cursor: pointer;
    background: var(--accent); color: var(--on-accent); border: 1px solid var(--accent);
    border-radius: 999px; padding: 11px 26px; text-decoration: none; display: inline-block;
    box-shadow: 0 2px 0 var(--btn-shadow);
    transition: background-color .15s, box-shadow .15s, transform .15s;
}
.btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: var(--on-accent); }
.btn:active { transform: translateY(1px); box-shadow: none; }
.linklike {
    background: none; border: 1px solid transparent; border-radius: 999px;
    padding: 5px 12px; font: inherit; font-size: 15px; color: var(--ink-soft); cursor: pointer;
    text-decoration: none;
}
.linklike:hover { color: var(--accent-dark); background: var(--accent-tint); border-color: var(--accent-hair); }
.actions { display: flex; gap: 14px; align-items: center; justify-content: center; flex-wrap: wrap; }
.quiet { color: var(--ink-soft); border-radius: 999px; padding: 5px 12px; }
.quiet:hover { color: var(--accent-dark); background: var(--accent-tint); }
.aside { color: var(--ink-soft); font-size: 14px; }
.note {
    font-size: 14px; color: var(--note-ink); background: var(--sky-tint);
    border-left: 3px solid var(--sky); border-radius: 8px; padding: 11px 15px;
}

.msg { border-radius: var(--radius-sm); padding: 12px 16px; margin: 0 0 20px; font-size: 15px; border: 1px solid; }
.msg.ok { background: var(--ok-tint); color: var(--ok); border-color: var(--ok-hair); }
.msg.err { background: var(--err-tint); color: var(--err); border-color: var(--err-hair); }
/* 状态条落在 <main> 的第一行，不在任何面板里——也就是说前台这两条的底是"半透明的 tint 直接压在遮罩上"，
   图一亮就跟着变亮。现算过：tint 压在最坏情况的那块底上，成了那档只有 4.37、坏了那档 3.55，都够不到 4.5。
   所以前台给它们一块实心的底（先铺 --card 那一档，再把 tint 画成一层不透明的同色渐变盖在上面，
   出来的还是简报里那两档颜色的淡底，只是不再透光）：成了 5.94、坏了 4.83，两条都过。
   写法用 background-color + background-image，是为了不跟上面那条 .msg.ok 的简写打架，也不用重复一遍颜色。
   后台不跟着动：它那一侧 --ok-tint/--err-tint 本来就是实心浅色，走原来的规则。 */
body.front .msg.ok { background-color: var(--card); background-image: linear-gradient(var(--ok-tint), var(--ok-tint)); }
body.front .msg.err { background-color: var(--card); background-image: linear-gradient(var(--err-tint), var(--err-tint)); }
.err { color: var(--err); font-size: 13px; font-style: normal; margin: 2px 0 0; }

/* ---------- 验证码 ---------- */
.captcha { display: grid; gap: 10px; padding: 16px 0 4px; border-top: 1px dashed var(--line); }
.captcha-tip { font-size: 14px; color: var(--ink-soft); margin: 0; }
.captcha.has-error { border-top-color: var(--err); }
.captcha.has-error .captcha-tip { color: var(--err); }
.captcha-img img { display: block; border: 1px solid var(--line); border-radius: 8px; background: var(--field-bg); }
.captcha-text .q {
    font-size: 20px; letter-spacing: 2px; font-family: var(--font-mono);
    background: var(--accent-tint); border-radius: 8px; padding: 6px 14px; display: inline-block;
}
.switch { font-size: 13px; color: var(--ink-soft); margin: 0; }

/* 蜜罐：移出可视区域但保留在无障碍树之外，脚本仍会照填 */
.hp { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; }

/* ---------- 首页数字 ---------- */
.stats { list-style: none; display: flex; gap: 14px; padding: 0; margin: 10px 0 0; flex-wrap: wrap; }
.stats li {
    display: grid; gap: 2px; flex: 1 1 150px; padding: 14px 18px;
    border-radius: var(--radius-sm); background: var(--accent-tint); border: 1px solid var(--accent-hair);
}
.stats strong { font-size: 26px; line-height: 1.2; color: var(--accent-dark); font-family: var(--font-mono); }
.stats span { font-size: 13px; color: var(--ink-soft); }
/* 这一格在首页是不套面板的：底就是"半透明的实心金压一层 .16"，下面直接是遮罩与图。
   图够亮时这层 tint 会把格子提亮，量出来 13px 的那行小字只有 4.31:1，够不到 4.5。
   和状态条同一个办法：先铺一块实心的 --card，再把 tint 画成一层不透光的同色渐变盖上去，
   出来的还是那一片淡金，但不再跟着图走（次要字回到 5.84:1，数字那一档更高）。后台不跟着动。 */
body.front .stats li { background-color: var(--card); background-image: linear-gradient(var(--accent-tint), var(--accent-tint)); }

/* ---------- 誓约 ---------- */
.badge-line { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }
.badge {
    font-size: 13px; letter-spacing: 1px; border: 1px solid var(--accent-bright);
    color: var(--accent-dark); background: var(--accent-tint);
    border-radius: 999px; padding: 3px 12px;
}
.badge.dim { border-color: var(--line); color: var(--ink-soft); background: var(--hairline-bg); }
.badge.ghost { border-style: dashed; background: transparent; }
/* 卡上类型那一枚记号跟着卡自己的主色走（.vcard 行内那两个变量给的）。
   一屏十几张卡的时候，眼睛先分清"一个人守"与"两个人守"，再去读那两个字。
   排在 .badge.ghost 之后是必要的：ghost 那一条把底色刷成透明，而这一条要的是按类型的那一层薄底——
   它只接管字、边、底三样，虚线那条（还没被认领）一个字都没动，所以两个轴各说各的。
   这条选不到后台：.vcard 只存在于前台的广场与搜索两张卡上，后台的表格里没有这个类。 */
.vcard .badge.vtype { color: var(--vow-accent); border-color: var(--vow-hair); background: var(--vow-tint); }
.vow-meta { color: var(--ink-soft); font-size: 14px; margin: 0 0 18px; }
/* 编号水印：右下角那一个很大很淡的数字，纯粹是这一页的底纹。
   它 aria-hidden、pointer-events:none、user-select:none——不念、不点、不跟着正文一起被选中。
   同一个号在 .vow-meta 里另外印成真字，所以看不见它的人一点信息都没少。
   两样都得写上：position:relative 给它一个参照，isolation:isolate 造一个层叠上下文——
   少了后一条，z-index:-1 会穿过面板的底色掉到背景层后面，等于没有。
   这里故意不写 overflow:hidden：正文里一个长地址本来就会横向溢出，加上它就从"顶出去"变成"被裁掉"，
   那是丢字。水印靠右下锚定，自己往左上长，多位数的编号也在框里。
   全站只有三条 z-index（背景层、页眉、回到顶部），没有哪一格会因为这里多造一个上下文而变样。 */
.panel.vow { position: relative; isolation: isolate; }
.vow-no {
    position: absolute; z-index: -1; right: 14px; bottom: 10px;
    font-family: var(--font-display); font-weight: 700; line-height: 1;
    font-size: clamp(56px, 14vw, 132px); letter-spacing: 2px;
    color: var(--accent-bright); opacity: .07;
    pointer-events: none; user-select: none;
}
/* 发给 TA 那一行。输入框里的地址是主角：没有脚本它也看得见、选得了、复制得了。
   那颗钮默认不出现（HTML 里的 hidden），只有脚本确认浏览器肯让脚本代它剪贴时才露面。
   下面 .vow-copy[hidden] 那一条是给它兜底的：.btn 的 display 排在它之后，同一条特异度会把 hidden 顶掉。 */
.vow-link { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 24px 0 0; }
.vow-link > label { font-size: 14px; color: var(--ink-soft); }
.vow-link .url {
    flex: 1 1 220px; min-width: 0; padding: 7px 10px;
    font: 400 13px/1.5 var(--font-ui); color: var(--ink-soft);
    background: var(--field-bg); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.vow-link .vow-copy { padding: 7px 16px; }
.vow-copy[hidden] { display: none; }
.copy-said { font-size: 13px; color: var(--ok); }
/* 誓约本体与条款走衬线：这一页是这个站的正文，字要像手写的承诺。 */
.vow-body {
    border-left: 3px solid var(--accent-bright); padding-left: 18px; margin: 20px 0;
    font-family: var(--font-serif); font-size: 17px;
}
.vow-title { font-family: var(--font-serif); }
/* 时间胶囊：正文那一格里换成的一条提示，加上它下面那组控件（阶段 7）。
   正文被挡住的这段时间，页面剩下的东西越少越好读，所以这块用实线左边框压在最上面。 */
.capsule-sealed { border-left: 3px solid var(--accent); padding-left: 18px; margin: 20px 0; }
.capsule-sealed .note { display: block; }
.capsule { margin: 20px 0; padding-top: 18px; border-top: 1px dashed var(--line); }
.capsule h2 { font-size: 17px; margin: 0 0 8px; }
.capsule form { margin: 12px 0 0; }
.capsule .aside { margin: 8px 0 0; }
.clauses h2 { font-size: 17px; margin: 24px 0 8px; }
.clauses ol { margin: 0; padding-left: 22px; display: grid; gap: 14px; }
.clause-text { margin: 0; font-family: var(--font-serif); }
.clause-meta { margin: 2px 0 0; font-size: 13px; color: var(--ink-soft); display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.attrib { font-style: italic; }

.vow-numbers { margin-top: 22px; }
.vow-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; }
.vow-list > li {
    border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px;
    background: var(--card); box-shadow: var(--shadow-sm);
}
.vow-list li.archived { opacity: .6; }
.vow-list .vow-title { font-size: 18px; text-decoration: none; }
.vow-list .vow-title:hover { text-decoration: underline; }

.hint { font-size: 13px; color: var(--ink-soft); font-style: normal; margin: 0; }
.radio { display: grid; grid-template-columns: auto auto 1fr; gap: 8px; align-items: baseline; font-size: 15px; }
.radio strong { font-size: 15px; }
.radio i { font-style: normal; color: var(--ink-soft); font-size: 13px; }
.radio.compact { display: inline-grid; grid-template-columns: auto auto; align-items: center; margin-right: 14px; }
.checkbox { display: flex; gap: 8px; align-items: center; font-size: 14px; color: var(--ink-soft); }

.clause-row { display: grid; gap: 6px; border-top: 1px dashed var(--line); padding-top: 12px; }
.clause-opt { display: flex; gap: 14px; flex-wrap: wrap; font-size: 13px; color: var(--ink-soft); }
.clause-opt label { display: flex; gap: 6px; align-items: center; }
select {
    font: inherit; font-size: 14px; padding: 7px 10px;
    border: 1px solid var(--field-line); border-radius: 8px; background: var(--field-bg); color: var(--ink);
}
select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.more-clauses summary { cursor: pointer; font-size: 14px; color: var(--ink-soft); margin-top: 6px; }
.more-clauses[open] summary { color: var(--accent-dark); }

.inline-form { display: inline-block; margin-right: 12px; }
.owner, .ask { margin-top: 20px; }
.clause-ask { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.clause-ask li { border-bottom: 1px solid var(--line); padding-bottom: 14px; }
.clause-ask li:last-child { border-bottom: 0; padding-bottom: 0; }
.owner h2, .ask h2 { font-size: 17px; margin: 0 0 10px; }
.owner form { border-top: 1px dashed var(--line); padding-top: 14px; }
.btn.danger { background: var(--err); border-color: var(--err); box-shadow: 0 2px 0 var(--btn-shadow-danger); }
.btn.danger:hover { background: var(--err-dark); border-color: var(--err-dark); }
.btn.quiet-btn { background: var(--field-bg); color: var(--ink-soft); border-color: var(--line); box-shadow: none; }
.btn.quiet-btn:hover { background: var(--hairline-bg); color: var(--ink); border-color: var(--line-hover); }

.media-gallery { margin-top: 24px; }
.media-gallery h2 { font-size: 17px; margin: 0 0 14px; }
.media-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px; }
.media-item img, .media-item video {
    display: block; max-width: 100%; max-height: 60vh; border-radius: var(--radius-sm); background: var(--media-matte);
    box-shadow: var(--shadow-sm);
}
.media-meta { display: flex; flex-wrap: wrap; gap: 10px; font-size: 13px; color: var(--ink-soft); margin: 8px 0 0; align-items: center; }
.media-meta .caption { color: var(--ink); font-size: 14px; }
.media-item.waiting { border-left: 3px solid var(--accent-bright); padding-left: 14px; }
.upload { margin-top: 20px; }
.upload h2 { font-size: 17px; margin: 0 0 8px; }
/* 进度那句话是纯文字，不靠颜色也不靠动效，看不见动效的人同样知道传到哪一步。 */
.upload [data-upload-status] { min-height: 20px; font-size: 14px; color: var(--accent-dark); margin: 10px 0 0; }

/* ---------- 广场 ---------- */
.plaza { margin-bottom: 26px; }
/* 广场那一块现在排在首页的窄条下面，标题与另外两块榜单同级，不再用页标题那一号字。 */
.plaza h2 { font-size: 17px; margin: 0 0 6px; }
.plaza h2 + .lede { margin-bottom: 20px; }

.search { display: flex; gap: 12px; align-items: flex-end; margin: 0 0 20px; }
.search .field { flex: 1; }
.search .btn { padding: 11px 24px; }
/* 页眉里那一只：跟着居中的导航收窄成一条，不把页眉撑成横幅，也不要那一格底部留白。
   必须写在上面三条之后——同为一条类选择器，写在前面会被 .search 的 margin 顶回去。
   窄屏那一段的折行对它同样生效：输入框与按钮各自整宽，好点。 */
.head-search { width: 100%; max-width: 420px; margin: 0; }

.filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 15px; margin: 0 0 24px; }
.filters a {
    text-decoration: none; color: var(--ink-soft); border: 1px solid var(--line);
    border-radius: 999px; padding: 5px 15px; background: var(--field-bg);
}
.filters a:hover { color: var(--accent-dark); border-color: var(--accent-bright); }
.filters a.on {
    color: var(--on-accent); background: var(--accent); border-color: var(--accent); font-weight: 600;
}
.filters .aside { padding-left: 6px; }

.vcards {
    list-style: none; margin: 0; padding: 0; display: grid; gap: 18px;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
}
.vcards > li { display: flex; }

.vcard {
    display: flex; flex-direction: column; gap: 8px; width: 100%;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px;
    box-shadow: var(--shadow-sm);
    transition: box-shadow .15s, transform .15s;
}
.vcard:hover { box-shadow: var(--shadow); transform: translateY(-2px); }
.vcard .badge-line { margin: 0; }
.vcard-cover img {
    display: block; width: 100%; height: 160px; object-fit: cover;
    border-radius: var(--radius-sm); background: var(--media-matte);
}
.vcard-title { font-size: 18px; margin: 0; line-height: 1.5; }
.vcard-title a { color: var(--ink); }
.vcard-names { font-size: 14px; color: var(--ink-soft); margin: 0; }
.vcard-names span { color: var(--accent-bright); padding: 0 4px; }
.vcard-tally { font-size: 14px; margin: 0; color: var(--sky); }
/* 页脚顶到卡片底部：一排卡里长短不一，「这不是我」应该落在同一水平线上。 */
.vcard-foot {
    margin: auto 0 0; padding-top: 12px; border-top: 1px dashed var(--line);
    display: flex; justify-content: space-between; gap: 12px; font-size: 13px; align-items: center;
}
.vcard-foot .aside { font-size: 13px; }
.vcard-foot .not-mine {
    color: var(--ink-soft); white-space: nowrap; text-decoration: none;
    border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px;
}
.vcard-foot .not-mine:hover { color: var(--accent-dark); border-color: var(--accent-bright); background: var(--accent-tint); }

.pager {
    display: flex; gap: 14px; align-items: center; justify-content: center;
    margin-top: 28px; flex-wrap: wrap;
}
.pager a {
    text-decoration: none; color: var(--accent-dark); border: 1px solid var(--accent-bright);
    border-radius: 999px; padding: 6px 18px; background: var(--field-bg);
}
.pager a:hover { background: var(--accent-tint); }

/* 广场那一条窗口化分页（只在前台：后台五张列表页用的是上面那节 .pager，这一批没动它）。
   当前这一格不给描边可点的样子，直接上实心珊瑚金——和页眉「立誓约」、按钮同一对颜色，
   第 40 节量的是同一组数；省略号不是链接，退到次要字那一档。 */
.pages {
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: center;
    margin-top: 26px;
}
.pages a, .pages .cur, .pages .gap {
    min-width: 40px; padding: 7px 12px; text-align: center;
    border: 1px solid var(--line); border-radius: 999px; font-size: 14px;
}
.pages a { text-decoration: none; color: var(--accent-dark); background: var(--card); }
.pages a:hover { color: var(--accent-bright); border-color: var(--accent-hair); }
.pages .cur {
    background: var(--accent); border-color: var(--accent);
    color: var(--on-accent); font-weight: 600;
}
.pages .gap { border-color: transparent; background: none; color: var(--ink-soft); min-width: auto; padding: 7px 2px; }

/* 空墙那一格：一段说明前面加一个记号，下面留一个能按的钮。
   记号是纯装饰（aria-hidden 写在标记里），颜色走"只装饰"那一档点缀金，不参与压字。 */
.empty-mark { display: flex; justify-content: center; color: var(--accent-bright); margin-bottom: 12px; }
body.front .empty h3 { font-size: 19px; letter-spacing: 1px; margin: 0 0 8px; color: var(--ink); }
body.front .empty p { margin: 0 0 8px; }
body.front .empty .actions { margin-top: 16px; }

.boards { margin-top: 26px; }
.boards h2 { font-size: 17px; margin: 24px 0 4px; }
.boards h2:first-of-type { margin-top: 0; }
.board { margin: 10px 0 0; padding-left: 24px; display: grid; gap: 8px; }
.board li { font-size: 15px; }
.board .aside { font-size: 13px; }

.right-check { margin-top: 24px; }
.right-check h2 { font-size: 17px; margin: 0 0 8px; }

/* 「这不是我」那一页对来人解释会发生什么。她不登录、看不懂术语，所以要一条条写清楚。 */
.promise { margin: 0 0 18px; padding-left: 22px; }
.promise li { font-size: 15px; color: var(--ink-soft); margin-bottom: 6px; }
.foot-note { margin: 14px 0 0; }

.debug pre { font-family: var(--font-mono); font-size: 12px; white-space: pre-wrap; word-break: break-all; background: var(--code-bg); padding: 12px; border-radius: 8px; }

/* ---------- 动效 ----------
   三条原则：
   1) 可见的终态不依赖动画。淡入与展开只写在 @keyframes 的 from 里，规则本体一个
      opacity: 0 都没有——动画被关掉（系统的减动效，或后台那一格设成"不动"）时，元素停在
      它自己的正常样子上，也就是看得见的那一态。这一条由自测第 40 节按字符串盯住。
   2) 背景层不动（见上面那一节），视差只加在内容上。
   3) 三态只靠 <body> 上的类名：不带类=跟着系统走，motion-on=强制动，motion-off=强制不动。
      开关是后台「站点」里那一格 theme.motion，一次设定对全站生效——设成"强制动"就是
      连操作系统说了减动效也照动，effect 那一格把这句话说在页面上。 */
@keyframes fade-in { from { opacity: 0; } }
@keyframes letter-open {
    from { clip-path: inset(0 0 100% 0); opacity: 0; }
    to   { clip-path: inset(0 0 0 0); opacity: 1; }
}
@keyframes star-drift { from { translate: -18px 10px; } to { translate: 18px -14px; } }
@keyframes tide-band { from { translate: 0 2.5%; } to { translate: 0 -1.5%; } }
@keyframes drift-text { from { translate: 0 10px; } to { translate: 0 -10px; } }
@keyframes drift-card { from { translate: 0 6px; } to { translate: 0 -6px; } }

/* 文字淡入：PJAX 换完这一块内容会重新触发一次，翻页时看着像把信铺开来。 */
body.front .wrap > * { animation: fade-in .5s ease both; }
/* 信笺：正文与条款是"从上往下展开"，不是翻面——不做 rotate，正面就是正面。 */
body.front .vow-body,
body.front .clauses ol { animation: letter-open .6s ease both; }

/* 内容滚动视差只在支持滚动时间线的浏览器里存在（Chromium 115 起）。
   不支持的浏览器拿到的就是同一套静态版式：不 polyfill、不为它多写一个脚本。
   用 translate 这个独立属性而不是 transform，是因为 .vcard 的 hover 抬那一格靠的是
   transform——两件事各占一个属性，谁也不把谁顶掉。位移只有 6 到 10 像素：要的是分层，不是晃眼。 */
@supports (animation-timeline: view()) {
    body.front .hero h1 {
        animation: drift-text 1s linear both;
        animation-timeline: view(cover 0% 100%);
    }
    body.front .vcard {
        animation: drift-card 1s linear both;
        animation-timeline: view(cover 0% 100%);
    }
}

/* 后台那一格说"强制不动"。伪元素那两行不能省：星点与潮光就挂在伪元素上，
   而伪元素选不中 *，只写 * 的话它们会在最该安静的时候继续漂。 */
body.motion-off *,
body.motion-off *::before,
body.motion-off *::after {
    transition: none !important;
    animation: none !important;
}

/* 锚点跳转（跳到主要内容、回到顶部）滑过去，不硬切。
   挂在 html 上而不是 body 上：真正被滚的是根元素。上面那两条 * 覆盖不到它——html 不是 body 的后代，
   所以减动效那一档要在下面单独按回去一次，这一条由自测第 40 节盯住。
   三态与上面同源：不带类=跟系统走，motion-on=照样滑，motion-off=瞬间到位。
   老浏览器不认识 :has() 就整条跳过，拿到的是默认的瞬间跳转——掉的是观感，不是那条通路。 */
html:has(body:not(.motion-off)) { scroll-behavior: smooth; }

/* ---------- 后台 ----------
   表格是这个站后台的主要形态：一屏要看十几行，卡片装不下。
   每一格状态都同时写汉字（「要处理」）与配色，看不见颜色的人也拿得到同一格信息。
   边框从"每格四周一圈"换成"行与行之间一道发丝线 + 隔行浅底"：网格线一多，
   扫一行要跨过七道竖线，反而是运维找不到的那一格。 */
body.admin { background: #f7f4ee; }
body.admin .site-head { padding-bottom: 14px; }
.head-left { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; justify-content: center; }

.tabs {
    display: flex; flex-wrap: wrap; gap: 6px; padding: 12px 20px; justify-content: center;
    background: var(--card); border-bottom: 1px solid var(--line);
}
.tabs a {
    text-decoration: none; color: var(--ink-soft); font-size: 15px;
    padding: 7px 18px; border-radius: 999px; border: 1px solid var(--line); background: #fff;
}
.tabs a:hover { color: var(--accent-dark); border-color: var(--accent-bright); }
.tabs a.on {
    color: #fff; background: var(--accent); border-color: var(--accent); font-weight: 600;
}

.wrapAdmin { max-width: 1160px; margin: 0 auto; padding: 28px 20px 60px; }
.wrapAdmin h1 { font-size: 25px; }
.wrapAdmin h2 { font-size: 18px; margin: 30px 0 8px; }
.sub { font-size: 15px; color: var(--ink-soft); margin: 18px 0 4px; font-weight: 400; }

.tbl {
    width: 100%; border-collapse: separate; border-spacing: 0; font-size: 14px;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: var(--shadow-sm); overflow: hidden;
}
.tbl caption { text-align: left; color: var(--ink-soft); font-size: 13px; padding: 0 0 8px; }
.tbl th, .tbl td {
    border: 0; border-bottom: 1px solid var(--line-soft);
    padding: 11px 14px; text-align: left; vertical-align: top;
}
.tbl tr:last-child > th, .tbl tr:last-child > td { border-bottom: 0; }
.tbl thead th {
    background: #faf6f0; font-weight: 700; white-space: nowrap; font-size: 13px;
    letter-spacing: .5px; color: var(--ink-soft); border-bottom: 1px solid var(--line);
}
.tbl tbody th { font-weight: 400; color: var(--ink-soft); white-space: nowrap; }
.tbl tbody tr:nth-child(even) { background: #fcfaf7; }
.tbl tbody tr:hover { background: var(--accent-tint); }
.tbl .num { text-align: right; font-family: var(--font-mono); white-space: nowrap; }
.tbl .state { white-space: nowrap; }
.tbl .why { margin: 0; color: var(--ink-soft); }
.tbl .fix { margin: 6px 0 0; }
/* 出问题的行不只靠颜色：左边一道实线，扫一眼就知道往哪一行看。 */
.tbl tr.st-bad > th:first-child, .tbl tr.st-warn > th:first-child { border-left: 4px solid var(--line); }
.tbl tr.st-bad > th:first-child { border-left-color: var(--err); }
.tbl tr.st-bad > th:first-child, .tbl tr.st-bad > td:first-child { background: var(--err-tint); }
.tbl tr.st-warn > th:first-child { border-left-color: var(--sun); }
.tbl tr.st-warn > th:first-child, .tbl tr.st-warn > td:first-child { background: #fdf6e8; }

.badge.ok { border-color: #cfe3d6; color: var(--ok); background: var(--ok-tint); }
.badge.warn { border-color: #eddcbe; color: #8a5b1e; background: #faf3e6; }
.badge.bad { border-color: #eecfcb; color: var(--err); background: var(--err-tint); }
.badge.info { border-color: var(--line); color: var(--ink-soft); background: #faf8f4; }

code { font-family: var(--font-mono); font-size: 13px; background: #f4f1ea; padding: 1px 6px; border-radius: 5px; }
.cmd { margin: 6px 0 0; }
.cmd code { display: block; padding: 12px 14px; font-size: 13px; white-space: pre-wrap; word-break: break-all; }

.steps { padding-left: 0; list-style: none; display: grid; gap: 20px; margin: 14px 0 0; }
.steps li { border-left: 3px solid var(--line); padding-left: 18px; }
.step-t { font-size: 16px; margin: 0 0 6px; font-weight: 700; }
.steps p { margin: 0 0 8px; font-size: 15px; }
.narrow-wrap { max-width: 760px; }

/* 后台里的动作：一行一个方向，按钮文字就是落地的意思，不靠弹层确认。 */
.act { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; margin: 0 0 10px; }
.act .field { min-width: 180px; flex: 1 1 220px; }
.btn.sm { font-size: 14px; padding: 7px 16px; }
.btn.ghost { background: #fff; color: var(--accent-dark); border: 1px solid var(--accent); box-shadow: none; }
.btn.ghost:hover { background: var(--accent-tint); color: var(--accent-dark); border-color: var(--accent); }
.inline-note { font-size: 13px; color: var(--ink-soft); margin: 4px 0 0; }

/* 表格格子里的动作竖着排：横向 flex 会让一个输入框把整张表顶宽，读的人反而找不到按钮。 */
.tbl .act { flex-direction: column; align-items: stretch; gap: 6px; }
.tbl .act .field { min-width: 0; flex: none; }
.tbl .act input[type=text], .tbl .act input[type=email], .tbl .act input[type=password] { width: 100%; }
.tbl .act .inline-note { font-size: 12px; }

/* ---------- 窄屏 ---------- */
@media (max-width: 720px) {
    .wrap { padding: 22px 14px 46px; }
    .wrapAdmin { padding: 20px 14px 46px; }
    h1 { font-size: 24px; }
    .hero { padding: 14px 0 4px; }
    .hero h1 { letter-spacing: 3px; }
    .panel { padding: 20px 16px; }
    .nav { gap: 2px; }
    .nav a { padding: 5px 9px; font-size: 14px; }
    .stats { gap: 10px; }
    .stats li { flex: 1 1 100%; }
    /* 搜索那一行在窄屏折成两行：输入框拉满，按钮整宽好点。 */
    .search { flex-direction: column; align-items: stretch; }
    .search .btn { width: 100%; }
    .vcards { grid-template-columns: 1fr; }
    .btn { padding: 11px 22px; }
    .media-item img, .media-item video { max-height: 44vh; }
    /* 窄屏把字距收回来：一行只有那么多地方，字距拉太开会一个字一行。 */
    body.front .brand { letter-spacing: 4px; }
    body.front h1 { letter-spacing: 1px; }
}

/* 后台表格在窄屏改横向滚动：display 换成 block，表里生成匿名表格盒，
   超出容器的列自己出横向滚动条。比给每张表外面套一层 div 划算——本站有 37 张表。
   代价是隔行浅底与圆角跟着内容一起滚，读仍然读得动。 */
@media (max-width: 760px) {
    body.admin .tbl { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
    body.admin .tbl th, body.admin .tbl td { white-space: normal; }
    body.admin .tbl thead th, body.admin .tbl .state { white-space: nowrap; }
}

/* 系统说"减动效"：除非后台那一格明确设了"强制开"，否则所有过渡与动画一律掐掉。
   伪元素那两行同样不能省，理由与上面 motion-off 那一条一样。
   掐掉之后 fade-in 与 letter-open 停在它们自己的终态（看得见），hover 与 active 的终态
   （换色、阴影、位移）照常生效，只是没有中间过程。 */
@media (prefers-reduced-motion: reduce) {
    body:not(.motion-on) *,
    body:not(.motion-on) *::before,
    body:not(.motion-on) *::after {
        transition: none !important;
        animation: none !important;
        scroll-behavior: auto !important;
    }
    /* 系统说了减动效，上面那三条管的是 body 的后代，管不到 html 自己；
       锚点滑不滑恰恰写在 html 上，所以这里补一条把它按回瞬间。
       后台那一格说"强制动"的那一档仍然赢：它带 motion-on，选择器不匹配。 */
    html:not(:has(body.motion-on)) { scroll-behavior: auto; }
}
