:root{
  --bg:#0a0e1a; --bg2:#0e1424; --surface:rgba(18,23,40,.90); --nav-surface:#0d1226; --nav-glow:rgba(124,156,255,.12); --surface2:rgba(255,255,255,.08);
  --surface3:rgba(255,255,255,.12); --border:rgba(255,255,255,.10); --border2:rgba(255,255,255,.16);
  --text:#eef2fb; --muted:#9aa6c0; --muted2:#7682a0;
  --brand:#7c9cff; --brand2:#b388ff; --brand3:#ff9ecf;
  --good:#41d6a3; --warn:#ffcf6b; --bad:#ff7a8a; --info:#7cd4ff;
  --radius:20px; --radius-sm:12px; --radius-lg:28px;
  --shadow:0 20px 60px rgba(0,0,0,.45); --shadow-sm:0 8px 24px rgba(0,0,0,.28);
  --grad:linear-gradient(120deg,#7c9cff 0%,#b388ff 55%,#ff9ecf 100%);
  --grad-soft:linear-gradient(135deg,rgba(124,156,255,.16),rgba(179,136,255,.10) 60%,rgba(255,158,207,.08));
  --on-grad:#0b0f1a;
  --ease:cubic-bezier(.16,1,.3,1); --ease-out:cubic-bezier(.22,1,.36,1);
}
[data-theme="light"]{
  --bg:#f5f7fc; --bg2:#eef1f8; --surface:rgba(255,255,255,.90); --nav-surface:#ffffff; --nav-glow:rgba(59,91,255,.06); --surface2:rgba(20,30,60,.075);
  --surface3:rgba(20,30,60,.11); --border:rgba(20,30,60,.10); --border2:rgba(20,30,60,.16);
  --text:#16203a; --muted:#5b6a86; --muted2:#76849c;
  --brand:#3b5bff; --brand2:#9a55ff; --brand3:#ff6fae;
  --shadow:0 16px 50px rgba(40,60,120,.14); --shadow-sm:0 6px 20px rgba(40,60,120,.10);
  --grad:linear-gradient(120deg,#3b5bff 0%,#9a55ff 55%,#ff6fae 100%);
  --grad-soft:linear-gradient(135deg,rgba(59,91,255,.10),rgba(154,85,255,.07) 60%,rgba(255,111,174,.06));
}
*{box-sizing:border-box}
/* 滚动条统一美化（暗/亮双主题和谐，全站生效） */
*{scrollbar-width:thin;scrollbar-color:rgba(124,156,255,.42) transparent}
::-webkit-scrollbar{width:9px;height:9px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(124,156,255,.30);border-radius:999px;border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:rgba(124,156,255,.55);border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-corner{background:transparent}
[data-theme="light"] *{scrollbar-color:rgba(59,91,255,.35) transparent}
[data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(59,91,255,.22);border:2px solid transparent;background-clip:padding-box}
[data-theme="light"] ::-webkit-scrollbar-thumb:hover{background:rgba(59,91,255,.42);border:2px solid transparent;background-clip:padding-box}
@keyframes pgIn{from{opacity:0}to{opacity:1}}
body{animation:pgIn .28s ease both}
html,body{margin:0;padding:0}
body{
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
  color:var(--text); background:
    radial-gradient(1200px 600px at 80% -10%, rgba(124,156,255,.20), transparent 60%),
    radial-gradient(900px 500px at -10% 10%, rgba(179,136,255,.18), transparent 55%),
    radial-gradient(700px 400px at 50% 110%, rgba(255,158,207,.10), transparent 60%),
    var(--bg);
  background-attachment:fixed;
  min-height:100vh; transition:background .4s ease, color .4s ease; -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
::selection{background:rgba(124,156,255,.32);color:var(--text)}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px}
.wrap{max-width:1080px;margin:0 auto;padding:0 24px 80px}
.hero{padding:84px 0 44px;text-align:center}
.pill{display:inline-flex;align-items:center;gap:6px;padding:7px 16px;border-radius:999px;border:1px solid var(--border);
  background:var(--grad-soft);color:var(--muted);font-size:13px;margin-bottom:24px;letter-spacing:.2px}
.pill::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--brand);box-shadow:0 0 8px var(--brand)}
h1.title{font-size:clamp(34px,6vw,62px);line-height:1.04;margin:0 0 20px;font-weight:850;letter-spacing:-.5px}
.grad{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
  background-size:200% auto;animation:gradShift 8s ease infinite}
@keyframes gradShift{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
.lead{color:var(--muted);font-size:18px;max-width:640px;margin:0 auto 32px;line-height:1.75}
.btn{display:inline-flex;align-items:center;gap:8px;padding:14px 28px;border-radius:14px;border:none;cursor:pointer;
  font-size:16px;font-weight:700;color:#0b0f1a;background:var(--grad);background-size:150% auto;
  box-shadow:0 12px 30px rgba(124,156,255,.4);
  transition:transform .25s var(--ease), box-shadow .25s var(--ease), background-position .6s var(--ease)}
.btn:hover{transform:translateY(-3px) scale(1.02);box-shadow:0 18px 42px rgba(124,156,255,.55);background-position:100% 50%}
.btn:active{transform:translateY(-1px) scale(.99)}
.btn.ghost{background:var(--surface2);color:var(--text);border:1px solid var(--border);box-shadow:none}
.btn.ghost:hover{transform:translateY(-2px);background:var(--surface3);border-color:var(--border2);box-shadow:var(--shadow-sm)}
.row{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;margin-top:8px}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px;
  box-shadow:var(--shadow);
  transition:transform .3s var(--ease),border-color .3s var(--ease),box-shadow .3s var(--ease)}
.card:hover{transform:translateY(-3px);border-color:var(--border2);box-shadow:0 24px 70px rgba(0,0,0,.5)}
.section-title{font-size:28px;font-weight:800;margin:48px 0 18px;letter-spacing:-.3px}
.section-sub{color:var(--muted);font-size:15px;margin:-8px 0 24px;line-height:1.7}
.grid{display:grid;gap:16px}
.cols-2{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.cols-3{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.muted{color:var(--muted)}
.muted2{color:var(--muted2)}
.small{font-size:13px}
.mt{margin-top:18px}.mb{margin-bottom:18px}
.tag{font-size:11px;padding:3px 10px;border-radius:999px;border:1px solid var(--border);color:var(--muted);font-weight:600;letter-spacing:.2px}
.tag.high{color:#0b0f1a;background:var(--bad);border-color:var(--bad)}
.tag.good{color:#0b0f1a;background:var(--good);border-color:var(--good)}
.tag.brand{color:var(--brand);background:rgba(124,156,255,.12);border-color:rgba(124,156,255,.3)}
.magnetic{transition:transform .18s var(--ease)}
/* 页脚外壳（.foot / .foot-main / .foot-crisis / .foot-meta / .foot-brand）的规则
   在 FOOT_CSS 常量里，由 SHARED_CSS 拼进本表 —— 自建 head 的 demo 三页不加载
   SHARED_CSS，它们外链 demo_css_link() 的资产（同一份 FOOT_CSS），页脚样式不会分成两份。 */
.flow{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
.step{position:relative;padding:24px;border-radius:18px;border:1px solid var(--border);background:var(--surface2);
  transition:transform .3s var(--ease),border-color .3s var(--ease)}
.step:hover{transform:translateY(-3px);border-color:var(--border2)}
.step .num{width:36px;height:36px;border-radius:11px;background:var(--grad);color:#0b0f1a;display:grid;place-items:center;font-weight:800;margin-bottom:12px;box-shadow:0 6px 16px rgba(124,156,255,.4)}
.step h4{margin:0 0 6px;font-size:16px}
.step p{margin:0;color:var(--muted);font-size:13px;line-height:1.65}
.trust li{margin:10px 0;line-height:1.7}
.funnel-arrow{color:var(--brand2);font-weight:800}
.divider{height:1px;background:var(--border);margin:32px 0;border:none}
@keyframes fadeUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
.fade-up{animation:fadeUp .6s var(--ease) both}

/* ===== 响应式适配：电脑 / 平板 / 手机 / iPhone ===== */
@media (max-width:900px){
  .hero{padding:56px 0 32px}
}
@media (max-width:680px){
  .wrap{padding:0 16px 64px}
  .hero{padding:38px 0 22px}
  h1.title{font-size:clamp(28px,8vw,40px)}
  .lead{font-size:16px;line-height:1.7}
  .section-title{font-size:22px;margin:34px 0 14px}
  .card{padding:20px}
  .btn{padding:13px 22px;font-size:15px}
  .step{padding:18px}
}
@media (max-width:480px){
  .flow,.grid,.cols-2,.cols-3{grid-template-columns:1fr}
  h1.title{font-size:clamp(26px,9vw,34px)}
}


.foot-note{text-align:center;color:var(--muted);font-size:13px;margin:40px auto 0;max-width:760px;line-height:1.8}
.foot{text-align:center;color:var(--muted);font-size:13px;margin:60px auto 0;max-width:1080px;line-height:1.8;padding:32px 24px 0;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:flex-end;gap:20px;flex-wrap:wrap}
.foot-main{flex:1 1 620px;min-width:0;text-align:left}
.foot-name{font-size:14px;font-weight:700;color:var(--text)}
/* 危机热线是法务口径，必须醒目且独立成段：窄屏下它原本紧贴「支持与法律」的最后一个
   链接（margin-top:1px），看起来像那条链接的下一行。压一条细线把它划成独立区块。 */
.foot-crisis{margin-top:18px;padding-top:14px;border-top:1px solid var(--border);color:var(--muted)}
.foot-meta{margin-top:6px;font-size:12px;color:var(--muted2,#8b93a7)}
.foot-brand{flex-shrink:0;opacity:.9;align-self:flex-end}
.foot-brand img{display:block;height:58px;width:auto;border-radius:10px;border:1px solid var(--border2,var(--border));background:var(--surface,transparent)}
@media (max-width:680px){
  .foot{padding:26px 16px 0;gap:14px}
  /* 内容独占整行、logo 落到下一行右对齐：原实现让 .foot-main 与 logo 并排，
     窄屏下 flex:1 + min-width:0 会把正文压到 ~180px 宽（见 ≤480px 说明）。 */
  .foot-main{flex:1 1 100%}
  .foot-brand{margin-left:auto}
}
@media (max-width:480px){
  /* 页脚：文字块与法舟盟 logo 曾并排钉在同一行（flex-wrap:nowrap + min-width:0），
     390px 下正文只剩 ~180px 宽，四栏塌成一条又长又窄的竖列（实测高 785px）。
     现在内容占满整行，logo 单独落到下一行。 */
  .foot{align-items:flex-start;gap:14px;text-align:left}
  .foot-main{flex:1 1 100%;font-size:12px;line-height:1.62}
  .foot-brand{margin-left:auto}
  .foot-brand img{height:44px;border-radius:9px}
}

.nav{position:sticky;top:0;z-index:30;display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 24px;background:var(--nav-surface);
  background-image:linear-gradient(180deg,var(--nav-glow),transparent 72%);
  border-bottom:1px solid var(--border);
  box-shadow:0 12px 34px rgba(0,0,0,.32)}
.nav::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(124,156,255,.55),transparent);opacity:.8}
.brand{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:.5px;flex:0 0 auto;min-width:0}
.brand-name{display:inline-block;font-size:15px;letter-spacing:.4px;max-width:34vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.logo{width:32px;height:32px;border-radius:10px;background:url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%237c9cff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23b388ff%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%228%22%20fill%3D%22url%28%23g%29%22%2F%3E%3Cpath%20d%3D%22M16%2026%20C10%2020%206%2016%206%2012%20C6%209%208%206%2012%206%20C14%206%2016%208%2016%2010%20C16%208%2018%206%2020%206%20C24%206%2026%209%2026%2012%20C26%2016%2022%2020%2016%2026%20Z%22%20fill%3D%22white%22%20opacity%3D%22.92%22%2F%3E%3Cpath%20d%3D%22M9%2024c0%200%202.5-2%207-2s7%202%207%202%22%20stroke%3D%22white%22%20stroke-width%3D%221.2%22%20fill%3D%22none%22%20opacity%3D%22.6%22%20stroke-linecap%3D%22round%22%2F%3E%3Cpath%20d%3D%22M6%2026c0%200%203.5-2%2010-2s10%202%2010%202%22%20stroke%3D%22white%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20opacity%3D%22.45%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E') center/cover no-repeat;box-shadow:0 6px 18px rgba(124,156,255,.5)}
.nav-links{display:flex;gap:4px;align-items:center;flex:1 1 auto;justify-content:center;min-width:0;
  flex-wrap:wrap;row-gap:2px}
/* 2026-09-20：这里原本是 flex-wrap:nowrap + overflow-x:auto（9 个栏目靠横向滚动兜住）。
   现在一级只有 6 项 + 「更多」，改成换行显示，同时**去掉 overflow**——
   overflow:auto 会形成裁剪上下文，把「更多」下拉的浮层切掉一半（菜单看起来“点了没反应”）。
   换行比横向滚动对用户更友好：滚动条藏在 sticky 顶栏里，很多人根本不知道右边还有栏目。
   折行点不随页面变化：一级项数固定，宽度只由字体栈决定（见 test_frontend_nav.py 字重断言）。 */
.nav-more{position:relative;display:inline-flex}
.nav-more>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:5px;
  color:var(--muted);font-size:14px;font-weight:600;padding:8px 14px;border-radius:10px;white-space:nowrap;
  transition:color .2s var(--ease),background .2s var(--ease);user-select:none}
.nav-more>summary::-webkit-details-marker{display:none}
.nav-more>summary::marker{content:""}
.nav-more>summary::after{content:"";width:5px;height:5px;border-right:1.6px solid currentColor;
  border-bottom:1.6px solid currentColor;transform:rotate(45deg) translate(-1px,-1px);
  transition:transform .2s var(--ease)}
.nav-more[open]>summary::after{transform:rotate(-135deg) translate(-2px,-2px)}
.nav-more>summary:hover{color:var(--text);background:var(--surface2)}
/* 与 .nav a.active 同一套视觉（只改颜色/底色/描边，不改盒模型） */
.nav-more>summary.active{color:var(--text);background:var(--surface2);box-shadow:inset 0 0 0 1px var(--border)}
.nav-more-menu{position:absolute;top:calc(100% + 8px);right:0;z-index:40;min-width:172px;margin:0;
  padding:6px;list-style:none;display:flex;flex-direction:column;gap:2px;
  background:var(--surface);border:1px solid var(--border2);border-radius:14px;box-shadow:var(--shadow)}
.nav-more-menu a{display:block;padding:9px 12px;border-radius:9px;font-size:14px}
.nav-actions{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.nav a{color:var(--muted);text-decoration:none;font-size:14px;padding:8px 14px;border-radius:10px;white-space:nowrap;flex:0 0 auto;
  /* 关键：所有链接（含激活项）必须同字重。激活态曾用 font-weight:700，而中文加粗会变宽，
     菜单又是整行居中——于是每换一个页面，全部链接都跟着横移一次，看起来像「导航变形」。
     字重全站恒定后，激活态只改颜色/底色/描边，零布局位移。 */
  font-weight:600;transition:color .2s var(--ease),background .2s var(--ease)}
.nav a:hover{color:var(--text);background:var(--surface2)}
/* 当前所在栏目：全站唯一高亮实现（此前 .nav a.active 根本没有样式，等于没提示）。
   只允许改视觉（颜色/底色/inset 描边），不允许改任何盒模型属性（字号/字重/内外边距）。 */
.nav a.active{color:var(--text);background:var(--surface2);box-shadow:inset 0 0 0 1px var(--border)}
.nav a.active:hover{background:var(--surface3)}
.icon-btn{width:40px;height:40px;border-radius:12px;border:1px solid var(--border);background:var(--surface2);
  color:var(--text);cursor:pointer;font-size:16px;transition:transform .25s var(--ease),background .2s var(--ease)}
.icon-btn:hover{transform:translateY(-2px);background:var(--surface3)}
.acct{display:inline-flex;align-items:center;justify-content:center;font-size:13px;padding:8px 14px;border-radius:999px;
  border:1px solid var(--border);background:var(--surface2);color:var(--text);cursor:pointer;font-weight:700;
  text-decoration:none;transition:.2s var(--ease);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  /* min-width：账号按钮在「登录(2字)/我的档案/系统管理员(5字)」之间变宽会让居中的菜单整体横移，
     全站钉住同一盒宽，跨页只换文字不挪菜单。 */
  min-width:96px}
.acct:hover{border-color:var(--brand);background:var(--surface3)}
/* 单一断点：≤1160px 一律折成两行（第一行品牌+账号+主题，第二行菜单横向滚动）。
   刻意不用「装不下才折行」——那样同一个宽度下，不同页面会因为账号按钮文字长度不同
   （登录 / 我的档案 / 系统管理员）而折行点不同，导航高度随之在 65/69/83/93px 之间跳变。 */
@media (max-width:1160px){
  .nav{flex-wrap:wrap;padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top,0px));gap:4px}
  .nav .brand{order:0;margin-right:auto}
  .nav .nav-actions{order:1;margin-left:auto}
  .nav .nav-links{order:2;width:100%;flex:1 0 100%;justify-content:flex-start;padding-top:6px;border-top:1px solid var(--border);gap:2px}
  .nav .nav-links a,.nav-more>summary{padding:7px 10px;font-size:13px}
  .icon-btn{width:42px;height:42px}
}
@media (max-width:560px){
  .brand-name{font-size:13px;letter-spacing:.3px;max-width:46vw}
  .nav a,.nav-more>summary{padding:7px 9px;font-size:12.5px}
  .acct{padding:9px 13px}
}
/* 小屏：一级 6 项本来就要换行，这里只做两件事 ——
   ①把「更多」钉在行尾（margin-left:auto）：它的浮层菜单是 right:0 向左展开的，
     钉在行尾就永远有空间，不会在窄屏上被屏幕边缘切掉；
   ②菜单本身限宽，别挤到屏幕外。菜单仍是浮层（.nav-links 已不再 overflow，
     浮层不会被裁），不占顶栏高度。 */
@media (max-width:600px){
  .nav-more{margin-left:auto}
  .nav-more-menu{min-width:0;max-width:min(180px,calc(100vw - 28px))}
}
/* 小屏（≤430px）第一行还要放账号 / 后台 / 主题三个按钮，不压缩就会把按钮行
   挤成两行（导航 106px → 142px）。这里只压后台按钮的文字（保留图标），
   品牌名继续显示——比整条顶栏换高更容易看懂自己在哪。 */
@media (max-width:430px){
  .brand-name{font-size:12.5px;letter-spacing:.2px;max-width:38vw}
  .acct{max-width:112px;padding:9px 11px;font-size:12.5px}
  #adminBtn{font-size:0;padding:9px 11px}
  #adminBtn::before{content:"后台";font-size:12.5px;letter-spacing:.5px}
}

/* ==== 动效令牌（design_system.py） ==== */
:root{
  --dur-1:120ms; --dur-2:180ms; --dur-3:260ms; --dur-4:380ms; --dur-5:560ms;
  --ease-standard:cubic-bezier(.2,.7,.3,1);
  --ease-enter:cubic-bezier(.16,.84,.24,1);
  --ease-exit:cubic-bezier(.4,0,1,1);
  --ease-spring:cubic-bezier(.34,1.56,.64,1);
  /* 间距/圆角/层级：与既有 --radius-*/--shadow-* 对齐，补齐产品化缺的两档 */
  --radius-xs:6px; --radius-pill:999px;
  --z-nav:30; --z-tabbar:40; --z-palette:80; --z-toast:90;
  --tap:44px;                 /* 移动端最小可点区域 */
}
/* 入场：只做 6px 位移 + 透明度，260ms 内结束；不做缩放（缩放会触发文字重排，肉眼更「抖」） */
@keyframes dsViewIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes dsFadeIn{from{opacity:0}to{opacity:1}}
@keyframes dsScaleIn{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}
@keyframes dsToastIn{from{opacity:0;transform:translateY(10px) scale(.98)}to{opacity:1;transform:none}}
.ds-enter{animation:dsViewIn var(--dur-3) var(--ease-enter) both}
.ds-enter-lg{animation:dsScaleIn var(--dur-4) var(--ease-enter) both}
/* 列表错峰入场：父级加 .ds-stagger，子项自动按序延迟（最多 8 项，避免第 20 项等 1 秒） */
.ds-stagger>*{animation:dsViewIn var(--dur-3) var(--ease-enter) both;
  animation-delay:calc(var(--i,0) * 36ms)}
.ds-stagger>*:nth-child(1){--i:0}.ds-stagger>*:nth-child(2){--i:1}.ds-stagger>*:nth-child(3){--i:2}
.ds-stagger>*:nth-child(4){--i:3}.ds-stagger>*:nth-child(5){--i:4}.ds-stagger>*:nth-child(6){--i:5}
.ds-stagger>*:nth-child(7){--i:6}.ds-stagger>*:nth-child(n+8){--i:7}
/* 区块入场：滚动进入视口时淡入上移一次。给元素加 data-reveal 即可（见 SHELL_JS.reveal）。
   渐进增强——.ds-reveal 由 JS 附加，JS 关闭/失败时正文保持可见，绝不把内容藏起来。 */
.ds-reveal{opacity:0;transform:translateY(12px)}
.ds-reveal.ds-in{opacity:1;transform:none;
  transition:opacity var(--dur-4) var(--ease-enter),transform var(--dur-4) var(--ease-enter)}
/* 交互反馈：悬停抬升 2px、按压回落 —— 全站唯一写法 */
.ds-lift{transition:transform var(--dur-2) var(--ease-standard),box-shadow var(--dur-2) var(--ease-standard)}
.ds-lift:hover{transform:translateY(-2px)}
.ds-press{transition:transform var(--dur-1) var(--ease-standard)}
.ds-press:active{transform:scale(.985)}
button:not(:disabled):active,.btn:not(:disabled):active{transform:translateY(1px) scale(.995)}
/* 键盘可达性：全站统一焦点环（此前只有 10 处 focus-visible） */
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:var(--radius-xs)}
a,button,input,select,textarea{-webkit-tap-highlight-color:transparent}
html{scroll-behavior:smooth}
/* 跨页转场：Chromium 126+ 原生支持，纯 CSS 渐进增强（不支持则完全无行为） */
@view-transition{navigation:auto}
::view-transition-old(root){animation:dsFadeIn var(--dur-2) var(--ease-exit) reverse}
::view-transition-new(root){animation:dsFadeIn var(--dur-3) var(--ease-enter)}
/* 顶栏不参与转场：转场期间顶栏重绘会造成「导航抖一下」 */
.nav{view-transition-name:none}
/* 减动效：尊重系统设置（焦虑/前庭敏感用户群体尤其重要）。
   不是"关掉动画"就完事——状态必须仍然可读，所以只把时长压到 1ms、去掉循环。 */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:1ms !important;animation-iteration-count:1 !important;
    transition-duration:1ms !important;scroll-behavior:auto !important;
  }
  .ds-lift:hover{transform:none}
  /* 区块入场在减动效下不做位移/淡入：直接可见（信息不靠动画传达） */
  .ds-reveal{opacity:1;transform:none}
  html{scroll-behavior:auto}
}


/* ==== 状态原语（design_system.py） ==== */
.ds-skeleton{position:relative;overflow:hidden;background:var(--surface2);
  border-radius:var(--radius-sm);border:1px solid var(--border)}
.ds-skeleton::after{content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.07) 45%,transparent);
  animation:dsShimmer 1.35s var(--ease-standard) infinite;transform:translateX(-100%)}
@keyframes dsShimmer{to{transform:translateX(100%)}}
.ds-skeleton-line{height:12px;margin:10px 0}
.ds-skeleton-line.w60{width:60%}.ds-skeleton-line.w80{width:80%}
.ds-skeleton-title{height:18px;width:38%;margin-bottom:14px}
.ds-skeleton-card{height:96px;margin:12px 0}
.ds-skeleton-ring{width:48px;height:48px;border-radius:50%}
/* 统一状态块：空态/错误态/加载态同一骨架，避免每页各画一套 */
.ds-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;
  text-align:center;padding:40px 22px;color:var(--muted)}
.ds-state-ico{width:46px;height:46px;border-radius:14px;display:grid;place-items:center;
  background:var(--surface2);border:1px solid var(--border);color:var(--brand);font-size:20px}
.ds-state-title{color:var(--text);font-weight:700;font-size:15px}
.ds-state-desc{font-size:13px;line-height:1.65;max-width:46ch}
.ds-state-actions{display:flex;gap:8px;margin-top:6px;flex-wrap:wrap;justify-content:center}
.ds-state.is-error .ds-state-ico{color:var(--bad);border-color:rgba(255,111,138,.35)}
/* 忙碌按钮：.ds-busy 期间锁交互 + 内联转圈（替换「按钮文案变…」这类无反馈写法） */
.ds-spinner{width:14px;height:14px;border-radius:50%;flex:0 0 auto;
  border:2px solid currentColor;border-top-color:transparent;animation:dsSpin .7s linear infinite}
@keyframes dsSpin{to{transform:rotate(360deg)}}
.ds-busy{pointer-events:none;opacity:.75}
.ds-busy .ds-spinner{display:inline-block}
/* 顶部滚动进度（长页面可用性） */
#dsProgress{position:fixed;top:0;left:0;height:2px;width:0;z-index:var(--z-palette);
  background:linear-gradient(90deg,var(--brand),var(--accent));opacity:0;
  transition:width var(--dur-1) linear,opacity var(--dur-2) var(--ease-standard)}
#dsProgress.on{opacity:.9}
/* 跳过导航（键盘/读屏用户第一个 Tab 就能直达正文） */
.ds-skip{position:absolute;left:8px;top:-60px;z-index:var(--z-palette);
  background:var(--surface3);color:var(--text);border:1px solid var(--border);
  padding:10px 14px;border-radius:var(--radius-sm);font-size:13px;text-decoration:none;
  transition:top var(--dur-2) var(--ease-enter)}
.ds-skip:focus{top:8px}


/* ==== 应用壳层（design_system.py） ==== */
/* 导航分组（IA）：9 个栏目按「探索 / 成长 / 服务」的顺序平铺（顺序定义在
   psy_ui._NAV_ZONES，NAV_CONTRACT 由它派生）。
   2026-09-20：宽屏下曾额外渲染 .nav-zone 分组文字标签，实测「11px 灰字 + 竖线」
   夹在链接之间只增加噪声（用户反馈顶栏臃肿），已移除；不再提供 .nav-zone 样式，
   避免有人照旧样式把标签加回来。 */
/* 顶栏不放全局搜索 / 命令面板（2026-09-20 用户反馈：快速跳转用不上，顶栏只要
   「登录 + 主题」两个入口）。导航本身已是 9 个平级栏目的单层结构，直达页面；
   原先那套搜索按钮、快捷键提示与命令面板的样式和 JS 已一并删除，别再加回来。 */
/* 页脚 v2：品牌 / 导航 / 支持与法律 / 危机热线 四段（原页脚只有三段且挤在一起）
   2026-09-20 调整：① 品牌栏收窄（1.6fr→1.4fr），链接栏拿到更多横向空间，四栏在
   1080 容器里更均衡；② 窄屏改「品牌通栏 + 两列链接」——原 ≤620px 直接塌成一列，
   而 .foot-main 又被并排的 logo 挤到 ~180px 宽，于是页脚变成一条又长又窄的竖列。 */
.foot-v2{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:24px 28px;align-items:start;
  max-width:1080px;margin:0 auto;text-align:left}
.foot-v2 .foot-col-title{color:var(--text);font-weight:700;font-size:12.5px;letter-spacing:.08em;
  text-transform:uppercase;opacity:.9;margin-bottom:10px}
.foot-v2 a{color:var(--muted);text-decoration:none;font-size:13px;display:block;padding:5px 0;
  transition:color var(--dur-2) var(--ease-standard)}
.foot-v2 a:hover{color:var(--brand)}
.foot-v2 .foot-tagline{color:var(--muted);font-size:13px;line-height:1.75;max-width:34ch}
.foot-v2 .foot-ver{color:var(--muted);font-size:12px;opacity:.72;margin-top:12px}
@media (max-width:900px){
  .foot-v2{grid-template-columns:1fr 1fr;gap:22px 26px}
  /* 品牌栏通栏：否则 2 列时被压成半宽，简介每行只放得下几个字 */
  .foot-v2>.foot-col:first-child{grid-column:1/-1}
  /* 「支持与法律」独占一行：它排在 2 列网格的下一行首格，右半会留一大块空白，
     而紧跟着就是危机热线分隔线——通栏后这一行才收得住。 */
  .foot-v2>.foot-col:last-child{grid-column:1/-1}
}
@media (max-width:620px){
  .foot-v2{grid-template-columns:1fr 1fr;gap:18px 18px}
  .foot-v2 .foot-tagline{max-width:none}
}
/* 390px 这类主流手机宽度仍保持 2 列：单列会把 4 栏堆成 ~795px 高的竖条，
   正是「页脚又长又臃肿」的来源；最窄（≤360px，如 iPhone SE）才退单列。 */
@media (max-width:360px){.foot-v2{grid-template-columns:1fr}}
/* 移动端底部导航（≤620px）：
   桌面隐匿、移动固定；页面可用 <body data-shell="no-tab"> 主动关闭
   （如陪伴伙伴页底部是聊天输入区，见 S2 落地说明）。 */
.ds-tabbar{display:none}
@media (max-width:620px){
  :root{--tabbar-h:56px}
  body:not([data-shell="no-tab"]) .ds-tabbar{
    display:grid;grid-template-columns:repeat(5,1fr);position:fixed;left:0;right:0;bottom:0;
    z-index:var(--z-tabbar);background:var(--nav-surface,var(--surface));
    border-top:1px solid var(--border);padding-bottom:env(safe-area-inset-bottom,0px);
    box-shadow:0 -10px 30px rgba(0,0,0,.28)}
  body:not([data-shell="no-tab"]){padding-bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom,0px))}
  .ds-tabbar a,.ds-tabbar button{
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
    min-height:var(--tabbar-h);border:0;background:transparent;color:var(--muted);text-decoration:none;
    font-size:11px;cursor:pointer;transition:color var(--dur-2) var(--ease-standard)}
  .ds-tabbar a.active,.ds-tabbar button.active{color:var(--brand)}
  .ds-tabbar svg{width:20px;height:20px}
  /* 页面级悬浮元素（客服 FAB / 页内 Toast）原本贴屏幕底部，会被底部导航压住——
     实测 companion 页 #kfFab 与第 5 个 tab 重叠（FAB bottom 822 / 导航顶 787），
     属于「看得到点不到」的经典回归。统一抬到导航之上；页面若自带底栏，
     用 <body data-shell="no-tab"> 关闭底部导航（此时本规则自动失效）。 */
  body:not([data-shell="no-tab"]) .kf-fab,
  body:not([data-shell="no-tab"]) [class*="fab"]{
    bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom,0px) + 14px) !important}
  body:not([data-shell="no-tab"]) [id$="Toast"],
  body:not([data-shell="no-tab"]) .seAa-toast{
    bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom,0px) + 8px) !important}
  #dsToastHost{bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom,0px) + 14px) !important}
}


.ds-ico{display:inline-block;vertical-align:-.16em;flex:none;line-height:1}
.ds-ico-brand{color:var(--accent)}
.ds-ico-muted{color:var(--muted)}
h1 .ds-ico,h2 .ds-ico,h3 .ds-ico,.card h3 .ds-ico{vertical-align:-.14em}


.seAc-wrap{position:fixed;inset:0;z-index:200;display:none;align-items:center;justify-content:center;background:rgba(5,8,16,.42);backdrop-filter:blur(14px) saturate(160%);padding:16px}
[data-theme="light"] .seAc-wrap{background:rgba(238,241,248,.62)}
.seAc-wrap.show{display:flex;animation:seAcFade .18s ease-out}
@keyframes seAcFade{from{opacity:0}to{opacity:1}}
/* 两个 tab 的**宽度只有一档**（旧写法给注册栏加了 `.wide` 变体，切一下左右各挪 22px）——
   宽度不变是「切 tab 不突兀」的主因，这条由结构保证。
   高度**按当前栏的内容走**（登录 2 个字段 / 注册 9 个），由 JS 做一次 260ms 的高度过渡
   （见 growCardTo）：刻意不再强制两栏等高。
   为什么要改回来（2026-09-21 晚用户反馈「布局很丑不协调」）：等高 = 卡片高度取两栏最大值，
   于是只有 2 个字段的登录栏被拉出一大块空白（实测 145px），标签与主按钮之间空得像坏掉了。
   用户的原始诉求是「切换时不要突兀」，不是「两栏必须一样高」；用动画抹掉高度差才是根因修复。
   配套改动：两栏不再共处一格参与排版（非当前栏 display:none），卡片高度 = 当前栏内容高；
   窄屏放不下时仍按整卡等比缩放（fitCard），两栏共用同一个 k（否则视觉宽度又跳，见 docstring）。 */
.seAc-card{position:relative;width:100%;max-width:436px;max-height:calc(100vh - 16px);max-height:calc(100dvh - 16px);overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;display:grid;grid-template-rows:auto auto auto auto;background:var(--surface);border:1px solid var(--border2);border-radius:20px;padding:26px 24px 24px;box-shadow:var(--shadow);backdrop-filter:blur(22px) saturate(170%);transition:height var(--dur-3) var(--ease-standard)}
.seAc-card.seAc-anim{overflow:hidden}
.seAc-close{position:absolute;top:12px;right:13px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:9px;color:var(--muted);font-size:16px;line-height:1;cursor:pointer;transition:color .15s,background .15s}
.seAc-close:hover{color:var(--text);background:var(--surface2)}
.seAc-head{grid-row:1;text-align:center;margin-bottom:16px}
.seAc-head h2{margin:0 0 6px;font-size:22px;font-weight:800;letter-spacing:.2px}
.seAc-head p{color:var(--muted);font-size:13px;margin:0;line-height:1.55}
/* 分段控件：**不再用渐变**。渐变是这个卡片里唯一的「主操作」语言，被 tab 用掉之后
   视觉上和下面的主按钮抢焦点（用户 2026-09-21 反馈「布局很丑不协调」的其中一半）。
   改成「轨道 + 实底药丸」：选中项是卡片同色的浮起块，主按钮才是全卡唯一的渐变。 */
/* 分段控件到表单的间距：**只给 5**，因为表单首个标签自带 13 的上边距 —— 合计 18，
   与「字段之间 13」同一套节奏、且是本卡里最大的一段留白（真正的分段）。
   旧写法 margin-bottom:18 与标签的 13 叠加成 **31**：登录栏在“账号”上方凭空多出一个
   31px 的洞，而密码框到主按钮只有 16、按钮到页脚只有 14 —— 用户 2026-09-22 反馈
   「账号和密码、文字和输入框在整个页面当中的位置不对。上面空太多了，下面太紧凑了」。
   为什么不用 ``label:first-child{margin-top:0}`` 这种“首元素不留上边距”的正统写法：
   注册栏的首行标签不在 ``.seAc-box`` 直接子级里，而在 ``.seAc-grid`` 的单元格里 ——
   选择器要么够不到它（两栏间距变成 5 / 18 不一致），要么连网格第 2 行的行距一起清掉
   （网格的行距全靠 label 的 margin-top，``gap:0 10px`` 的 row-gap 是 0）。 */
.seAc-tabs{grid-row:2;display:flex;gap:4px;background:var(--surface2);border-radius:12px;padding:4px;margin-bottom:5px}
.seAc-tabs button{flex:1;padding:8px;border:none;border-radius:9px;background:none;color:var(--muted);font-size:13.5px;font-weight:600;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}
.seAc-tabs button:hover{color:var(--text)}
.seAc-tabs button.on{background:var(--nav-surface);color:var(--text);font-weight:700;box-shadow:0 1px 3px rgba(0,0,0,.16)}
/* 表单字段一律**顶对齐**（旧写法 justify-content:center 会把 2 个字段的登录栏
   在「两栏同高」的长行里居中，于是标签上方凭空出现一大块空白 —— 那正是「不协调」）。
   动作区（提示 + 主按钮）用 margin-top:auto 贴到本栏底部：注册栏内容本来就占满整行，
   两栏的主按钮因此落在**同一条水平线**上。 */
.seAc-box{grid-row:3;grid-column:1;display:flex;flex-direction:column;justify-content:flex-start}
.seAc-box.off{display:none}
.seAc-box label{display:block;font-size:12.5px;font-weight:600;color:var(--muted);margin:13px 0 6px}
.seAc-box input{width:100%;padding:11px 13px;border-radius:10px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-size:14px;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}
.seAc-box input::placeholder{color:var(--muted)}
.seAc-box input:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(124,156,255,.16)}
/* 动作区到最后一个字段的间距（旧 16 → 22）：字段块上移 13px 之后，这里必须比“字段之间”
   明显更宽 —— 否则整块表单看着是贴着按钮的（用户原话「下面太紧凑了」）。
   padding 而非 margin：提示条（.seAc-msg）出现时也要跟着一起下移。 */
.seAc-act{margin-top:auto;padding-top:22px}
.seAc-opt{color:var(--muted);font-weight:400;font-size:11.5px}
.seAc-sel{width:100%;padding:11px 12px;border-radius:10px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-size:14px;box-sizing:border-box;cursor:pointer;appearance:auto;transition:border-color .15s,box-shadow .15s}
.seAc-sel:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px rgba(124,156,255,.16)}
.seAc-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 10px}
.seAc-span2{grid-column:1/-1}
.seAc-birth{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
.seAc-code{display:flex;gap:8px}
.seAc-code input{flex:1;min-width:0}
.seAc-code button{flex-shrink:0;padding:0 14px;border-radius:10px;border:1px solid var(--brand);background:none;color:var(--brand);font-size:12.5px;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s}
.seAc-code button:hover:not(:disabled){background:var(--brand);color:#0b0f1a}
.seAc-code button:disabled{opacity:.6;cursor:not-allowed}
.seAc-msg{display:none;margin:0 0 10px;padding:8px 11px;border-radius:8px;font-size:12.5px;line-height:1.5}
.seAc-msg.err{display:block;background:rgba(255,122,138,.12);color:var(--bad);border:1px solid rgba(255,122,138,.25)}
.seAc-msg.ok{display:block;background:rgba(65,214,163,.12);color:var(--good);border:1px solid rgba(65,214,163,.25)}
.seAc-btn{width:100%;padding:12px;border:none;border-radius:10px;background:var(--grad);color:#0b0f1a;font-size:15px;font-weight:700;cursor:pointer;box-shadow:0 8px 20px rgba(124,156,255,.28);transition:transform .12s,box-shadow .15s,opacity .15s}
.seAc-btn:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 10px 24px rgba(124,156,255,.34)}
.seAc-btn:active:not(:disabled){transform:translateY(0)}
.seAc-btn:disabled{opacity:.6;cursor:not-allowed;box-shadow:none}
/* 选填项（出生年月日时）**默认收起**：它原来在注册栏里占掉一整行 + 说明，
   把「两栏同高」的行高顶上去 —— 登录栏（就 2 个字段）于是被拉出一大块空白。
   收起来之后注册栏短了一截，两栏的留白都回到正常量级。
   用原生 <details>：键盘可达、语义正确，不用自己写 aria-expanded。 */
.seAc-more{margin-top:14px;border-top:1px solid var(--border);padding-top:12px}
.seAc-more summary{display:flex;align-items:center;gap:8px;cursor:pointer;list-style:none;font-size:12.5px;font-weight:600;color:var(--muted);padding:2px 0;transition:color .15s}
.seAc-more summary::-webkit-details-marker{display:none}
.seAc-more summary::after{content:"";width:6px;height:6px;margin-left:auto;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg) translate(-2px,-2px);transition:transform .2s}
.seAc-more[open] summary::after{transform:rotate(225deg) translate(-1px,-1px)}
.seAc-more summary:hover{color:var(--text)}
.seAc-more .seAc-birth{margin-top:9px}
.seAc-foot{grid-row:4;text-align:center;margin-top:16px;font-size:12px;color:var(--muted)}
.seAc-tip{font-size:11.5px;color:var(--muted);text-align:left;margin:8px 0 0;line-height:1.5}
/* ---- 移动端：底部抽屉、单列、加大触控区；输入 16px 防 iOS 聚焦缩放 ---- */
@media (max-width:600px){
  .seAc-wrap{padding:0;align-items:flex-end}
  .seAc-card{max-width:100%;max-height:92vh;max-height:92dvh;border-radius:20px 20px 0 0;padding:20px 16px calc(22px + env(safe-area-inset-bottom,0px))}
  .seAc-head{margin-bottom:12px}
  .seAc-head h2{font-size:19px}
  .seAc-head p{font-size:12.5px}
  /* 与桌面同一套算术：5 + 首个标签自带的 11 = 16（窄屏的字段节奏更紧） */
  .seAc-tabs{padding:5px;margin-bottom:5px}
  .seAc-tabs button{min-height:var(--tap,44px);font-size:14px}
  .seAc-grid{grid-template-columns:1fr 1fr;gap:0 8px}
  .seAc-birth{grid-template-columns:1fr 1fr;gap:8px}
  .seAc-box label{margin:11px 0 6px}
  .seAc-box input,.seAc-sel,.seAc-code input{font-size:16px;padding:11px 12px}
  .seAc-code button{padding:0 12px;font-size:13px}
  .seAc-act{padding-top:18px}
  .seAc-btn{padding:13px;font-size:15.5px}
  .seAc-more{margin-top:12px;padding-top:11px}
  .seAc-more summary{padding:8px 0}
  .seAc-close{top:9px;right:9px;width:34px;height:34px;font-size:17px}
}


/* ==== 组件库 v1（design_system.py）==== */
/* Button：变体(primary/ghost/subtle/danger) × 尺寸(sm/md/lg) × loading/block。
   与旧 .btn 并存（旧页不受影响）。 */
.ds-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;
  height:var(--btn-h,40px);padding:0 16px;border:1px solid transparent;border-radius:var(--radius-sm);
  font:600 14px/1 inherit;color:var(--text);background:var(--surface2);cursor:pointer;white-space:nowrap;
  text-decoration:none;transition:transform var(--dur-2) var(--ease-standard),
  background var(--dur-2) var(--ease-standard),border-color var(--dur-2) var(--ease-standard),
  box-shadow var(--dur-2) var(--ease-standard)}
.ds-btn:hover{transform:translateY(-1px)}
.ds-btn:active{transform:none}
.ds-btn[disabled],.ds-btn[aria-disabled="true"]{opacity:.5;cursor:not-allowed;transform:none}
.ds-btn.primary{background:var(--grad);color:var(--on-grad);box-shadow:var(--shadow-sm)}
.ds-btn.ghost{background:var(--surface2);border-color:var(--border)}
.ds-btn.ghost:hover{background:var(--surface3);border-color:var(--border2)}
.ds-btn.subtle{background:transparent;color:var(--muted)}
.ds-btn.subtle:hover{background:var(--surface2);color:var(--text)}
.ds-btn.danger{background:transparent;border-color:var(--bad);color:var(--bad)}
.ds-btn.danger:hover{background:var(--surface2)}
.ds-btn.sm{--btn-h:32px;padding:0 12px;font-size:13px}
.ds-btn.lg{--btn-h:48px;padding:0 22px;font-size:15px}
.ds-btn.block{display:flex;width:100%}
.ds-btn.loading{pointer-events:none}
.ds-btn.loading .ds-btn-lbl{visibility:hidden}
.ds-btn.loading::after{content:"";position:absolute;inset:0;margin:auto;width:16px;height:16px;
  border-radius:50%;border:2px solid currentColor;border-top-color:transparent;animation:dsSpin .7s linear infinite}
@keyframes dsSpin{to{transform:rotate(360deg)}}
/* Field：label + 控件 + 提示/错误（错误态同时改边框与文案色） */
.ds-field{display:flex;flex-direction:column;gap:6px;margin:0 0 14px}
.ds-field>label{font-size:13px;font-weight:600;color:var(--muted)}
.ds-input-row{display:flex;align-items:center;gap:8px}
.ds-input-row .ds-input{flex:1}
.ds-input{width:100%;height:var(--input-h,42px);padding:0 12px;border:1px solid var(--border);
  border-radius:var(--radius-sm);background:var(--surface2);color:var(--text);font-size:14px;
  transition:border-color var(--dur-2) var(--ease-standard),box-shadow var(--dur-2) var(--ease-standard)}
textarea.ds-input{height:auto;min-height:88px;padding:10px 12px;line-height:1.6;resize:vertical}
.ds-input::placeholder{color:var(--muted2)}
.ds-input:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px var(--focus-ring,rgba(124,156,255,.22))}
.ds-hint{font-size:12px;color:var(--muted)}
.ds-field.err .ds-input{border-color:var(--bad)}
.ds-field.err .ds-hint{color:var(--bad)}
.ds-field.ok .ds-input{border-color:var(--good)}
/* Card：可选头部/尾部；hoverable 用于可点卡片 */
.ds-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow);overflow:hidden}
.ds-card-hd{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--border);font-weight:700}
.ds-card-bd{padding:18px}
.ds-card-ft{display:flex;justify-content:flex-end;gap:10px;padding:12px 18px;border-top:1px solid var(--border)}
.ds-card.hoverable{transition:transform var(--dur-2) var(--ease-standard),
  border-color var(--dur-2) var(--ease-standard),box-shadow var(--dur-2) var(--ease-standard)}
.ds-card.hoverable:hover{transform:translateY(-2px);border-color:var(--border2)}
/* Tabs：底部指示条；面板 hidden 切换（渐进增强，无 JS 时全部面板可见） */
.ds-tabs{display:flex;gap:4px;margin:0 0 16px;border-bottom:1px solid var(--border);overflow-x:auto}
.ds-tab{appearance:none;border:0;background:transparent;color:var(--muted);font:600 14px inherit;
  padding:10px 14px;cursor:pointer;white-space:nowrap;border-bottom:2px solid transparent;
  transition:color var(--dur-2) var(--ease-standard),border-color var(--dur-2) var(--ease-standard)}
.ds-tab:hover{color:var(--text)}
.ds-tab[aria-selected="true"]{color:var(--text);border-bottom-color:var(--brand)}
.ds-tab[aria-selected="true"] .ds-badge{background:var(--surface3)}
.ds-tabpanel{animation:dsViewIn var(--dur-3) var(--ease-enter) both}
.ds-tabpanel[hidden]{display:none}
.ds-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;
  padding:0 6px;border-radius:999px;background:var(--surface2);color:var(--muted);font-size:11px;font-weight:700}
/* Drawer：右侧抽屉 + 遮罩；焦点陷阱与 Esc 由 COMPONENT_JS 负责 */
.ds-drawer{position:fixed;inset:0;z-index:var(--z-palette,90);display:none}
.ds-drawer.open{display:block}
.ds-drawer-bg{position:absolute;inset:0;background:rgba(6,9,18,.6);backdrop-filter:blur(4px);
  animation:dsFadeIn var(--dur-2) var(--ease-enter)}
.ds-drawer-panel{position:absolute;top:0;right:0;bottom:0;width:min(440px,92vw);display:flex;
  flex-direction:column;background:var(--surface);border-left:1px solid var(--border);box-shadow:var(--shadow);
  animation:dsDrawerIn var(--dur-3) var(--ease-enter)}
@keyframes dsDrawerIn{from{transform:translateX(24px);opacity:0}to{transform:none;opacity:1}}
@keyframes dsFadeIn{from{opacity:0}to{opacity:1}}
.ds-drawer-hd{display:flex;align-items:center;gap:10px;padding:16px 18px;border-bottom:1px solid var(--border);font-weight:700}
.ds-drawer-bd{flex:1;overflow:auto;padding:18px}
.ds-drawer-ft{display:flex;justify-content:flex-end;gap:10px;padding:14px 18px;border-top:1px solid var(--border)}
/* 减动效由 MOTION_CSS 的全局 prefers-reduced-motion 规则统一兜住（时长压到最小），
   组件不再各自复写。 */
/* Table：可排序表头（点 th[data-sort] 切升/降序） */
.ds-table-wrap{border:1px solid var(--border);border-radius:var(--radius-sm);overflow:auto}
.ds-table{width:100%;border-collapse:collapse;font-size:13.5px}
.ds-table th,.ds-table td{padding:10px 12px;text-align:left;border-bottom:1px solid var(--border)}
.ds-table th{position:sticky;top:0;z-index:1;background:var(--surface);color:var(--muted);
  font-size:12.5px;font-weight:600}
.ds-table th[data-sort]{cursor:pointer;user-select:none}
.ds-table th[data-sort]:hover{color:var(--text)}
.ds-table th .ds-arrow{margin-left:4px;opacity:.4}
.ds-table th.sorted .ds-arrow{opacity:1}
.ds-table tbody tr:last-child td{border-bottom:0}
.ds-table tbody tr:hover{background:var(--surface2)}
.ds-table td.num{text-align:right;font-variant-numeric:tabular-nums}
.ds-table .ds-empty td{text-align:center;color:var(--muted);padding:28px 12px}
