
:root{
  /* 文字档位四档（18/17/14/12），理由见文件顶部注释 */
  --fs-h1:18px; --fs-title:17px; --fs-body:17px; --fs-sub:14px; --fs-cap:12px;
  --lh-body:1.55; --lh-sub:1.5;
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px; --sp-6:32px;
  --r-card:12px; --r-el:8px;
  /* 非文字档位（字形/字标/长文页），理由见文件顶部注释 */
  --fs-10:10px;
  --fs-22:22px;
  --fs-23:23px;
  --fs-26:26px;
  --r-pill:999px;
  --r-10:10px;
  --r-11:11px;
  --r-12:12px;
  --r-14:14px;
  --r-16:16px;
  --r-18:18px;
  --r-26:26px;
  --r-3:3px;
  --r-4:4px;
  --r-6:6px;
  --r-7:7px;
  --r-8:8px;
  --r-9:9px;
  --row-h:56px; --tap:44px; --nav-h:44px; --tab-h:49px;
  --t-fast:150ms; --t-page:250ms; --t-dlg:300ms; --ease:cubic-bezier(.2,.8,.2,1);
  /* 语义色兜底：主题若未定义则由这里提供（22 套皮肤均无 danger/success 定义） */
  --danger:#C0392B; --success:#07C160;
}
/* ── 去浏览器化（App 化）：禁右键菜单/长按菜单/拖拽、禁双击缩放（保留双指，照顾低视力）── */
*{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}
html,body{touch-action:manipulation;overscroll-behavior:none}
body{user-select:none;-webkit-user-select:none}
input,textarea,.msg,.dlgbd,[data-selectable]{user-select:text;-webkit-user-select:text}
::-webkit-scrollbar{width:0;height:0}
*{scrollbar-width:none}
/* ── 加载态（P7）：提交后顶部一条细进度条 + 提交按钮置灰。只靠 body 属性触发，不加元素 ── */
body[data-busy="1"]::before{content:"";position:fixed;top:0;left:0;right:0;height:3px;z-index:99;pointer-events:none;
  background:linear-gradient(90deg,transparent,var(--brand-ink,var(--brand)),transparent);
  background-size:38% 100%;background-repeat:no-repeat;animation:cgBusy .9s linear infinite}
@keyframes cgBusy{from{background-position:-38% 0}to{background-position:138% 0}}
button.cg-busy{opacity:.6;cursor:progress}
/* ── 应用外壳：顶栏 44 + 内容区（唯一滚动容器）+ 底部 Tab 49 ── */
.shell{display:flex;flex-direction:column;min-height:100dvh}
.shell[hidden]{display:none}
.nav{flex:0 0 auto;display:flex;align-items:center;gap:var(--sp-2);min-height:calc(var(--nav-h) + env(safe-area-inset-top));
  padding:env(safe-area-inset-top) calc(var(--sp-4) + env(safe-area-inset-right)) 0 calc(var(--sp-4) + env(safe-area-inset-left));background:var(--hd-bg);color:var(--hd-fg);border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:5}
.nav .nav-t{font-size:var(--fs-title);font-weight:600;letter-spacing:.04em}
.nav .nav-s{font-size:var(--fs-cap);color:var(--hd-dim);margin-left:var(--sp-1)}
.nav .nav-r{margin-left:auto;display:flex;align-items:center;gap:var(--sp-1)}
.nav .back{min-width:var(--tap);min-height:var(--tap);margin-left:calc(var(--sp-3) * -1);display:grid;place-items:center;
  border:0;background:transparent;color:var(--hd-fg);cursor:pointer;border-radius:var(--r-el);
  font:inherit}   /* 禁掉 UA 给 button 的默认字 13.3333px */
/* ⚠️ 2026-09-24 补（owner 复核定性：返回键是**全站唯一没有按下反馈**的可点元素）。
   实测证据：按住返回键时，计算样式与未按下**完全一致**（bg: rgba(0,0,0,0)、transform: none）
   —— 因为 .nav .back 从来没有 :active 规则。学员对着一个"按下去毫无变化"的按钮等
   ⇒ 必然再点一次 ⇒ 正好触发"连点多退一页"（见 APP_JS 里的导航锁）。
   与 .bar button:active 同一手法：**瞬时位移，不加过渡**（transition 会让"按下了"变成"过一会儿才动"，
   反馈的意义就是在 0ms 内告诉手指"我收到了"）。
   background 用 --me-bg（25 套皮肤全部定义，且 .row:active 已在用）⇒ 不新增令牌；
   同时给 translateY：万一某套皮肤的 --me-bg 与 --hd-bg 接近，位移仍保证"计算样式确实变了"可被断言。 */
.nav .back:active{background:var(--bg-active);transform:translateY(1px)}
.page{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-right)) calc(var(--sp-5) + env(safe-area-inset-bottom)) calc(var(--sp-4) + env(safe-area-inset-left))}
.page.no-pad{padding:0 0 calc(var(--sp-4) + env(safe-area-inset-bottom))}
.tabbar{flex:0 0 auto;display:grid;grid-template-columns:repeat(4,1fr);background:var(--panel);
  border-top:1px solid var(--line);z-index:6;
  padding-bottom:env(safe-area-inset-bottom);
  padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right)}
.tabbar a{min-height:var(--tab-h);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  font-size:var(--fs-cap);color:var(--dim);text-decoration:none}
.tabbar a.on{color:var(--brand-ink,var(--brand))}
.tabbar svg{width:24px;height:24px;display:block}
/* ── 列表 / 卡片 / 分组（新外壳内的通用件）── */
.grp{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-card);overflow:hidden}
.grp + .grp{margin-top:var(--sp-3)}
/* ⚠️ 2026-09-26：卡片(.card-x)后面直接接分组(.grp)时也要留同样 12px。
   不加的话两张卡的 1px 边框**贴在一起**，看上去就是一条 2px 的粗线
   （实测：首页 y=149 处 4 个物理像素 = 2 CSS px 的 --line 色带；而"最厚 ≤1.5px"那条判据要求 ≤1.5px）。
   这也是本文件既有的排版约定（其它页面的卡片都写 margin-top:var(--sp-3)），只是首页漏了。 */
.card-x + .grp{margin-top:var(--sp-3)}
/* 分割线：左右各内缩 16px（::after 实现）——口径与哪些页有线见文件顶部注释 */
.row{display:flex;align-items:center;gap:var(--sp-3);min-height:var(--row-h);padding:0 var(--sp-4);
  color:var(--fg);text-decoration:none;font-size:var(--fs-body);position:relative}
.row::after{content:'';position:absolute;left:var(--sp-4);right:var(--sp-4);bottom:0;height:1px;background:var(--line)}
.row:last-child::after{content:none}
/* 首页「老师联系人」行：卡片式 —— 靠 var(--sp-2) 间距分组，**不用分隔线**（更干净）。
   home 在视觉判据的 divider_pages 名单之外（即"卡片式"，见 /var/lib/cg-visual/invariants.json），
   这里把它的行间距显式给出来：第 2 条起的联系人行之间留 8px，不再靠 1px 线分开。
   ⚠️ 2026-09-24 改成**类名**（原来是 .row[href="/chat"]）。为什么必须改：
      首页联系人行要带 ?teacher=／?sid= 参数，一旦 href 不再是字面 "/chat"，那两条按 href 选的选择器
      **全部失配** ⇒ 首页会**悄悄**从"卡片式"退回"带分隔线的列表"（视觉回归，且不留任何报错）。
      用类名之后，href 从此可以自由变。这条与 .row:active 一样属于"通用件"，改前先看这里。 */
.row--sess::after{content:'';left:0;right:0;
  background:linear-gradient(90deg,transparent,var(--line) var(--sp-4),var(--line) calc(100% - var(--sp-4)),transparent)}
/* 联系人头像：与对话页顶栏那个 .ava 同一观感（首字 + 微信式方形圆角 + 品牌描边），只是尺寸小一号 */
.row .ava-s{width:40px;height:40px;flex:0 0 40px;border-radius:var(--r-6);overflow:hidden;display:grid;place-items:center;
  background:var(--panel);border:1px solid var(--brand);color:var(--brand-ink,var(--brand));
  font-size:var(--fs-body);font-weight:600}
body[data-decor~="paper"] .row .ava-s{border-radius:0;font-family:var(--serif)}
.row .row-sub{display:block;color:var(--dim);font-size:var(--fs-sub);margin-top:2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row .row-s{margin-left:auto;color:var(--dim);font-size:var(--fs-sub)}
.row .chev{color:var(--dim);flex:0 0 auto}
.row:active{background:var(--me-bg)}
.sec-t{margin:var(--sp-5) 0 var(--sp-2);font-size:var(--fs-cap);color:var(--dim);letter-spacing:.08em}
.card-x{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-card);padding:var(--sp-4)}
.empty-x{text-align:center;color:var(--dim);padding:var(--sp-6) var(--sp-4)}
.empty-x b{display:block;color:var(--fg);font-size:var(--fs-body);margin-bottom:var(--sp-2)}
.btn-x{display:inline-flex;align-items:center;justify-content:center;min-height:var(--tap);padding:0 var(--sp-4);
  border-radius:var(--r-el);border:0;background:var(--btn-bg);color:var(--btn-fg);font-size:var(--fs-body);cursor:pointer}
.btn-x.ghost{background:transparent;border:1px solid var(--line);color:var(--dim)}
/* 二级页嵌入外壳内容区时，中和页面自带的 app/doc 包裹层 */
.page>.app,.page>.doc{display:block;min-height:0;padding:0}
.page>.app>.wrap,.page>.doc>.wrap{max-width:none;margin:0;padding:0}
/* 页面转场（P4）：内容区淡入 + 轻微上移；减少动效时由下面这条统一关掉 */
@keyframes cgPageIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.page{animation:cgPageIn var(--t-page) var(--ease) both}
@keyframes cgNavIn{from{opacity:.6}to{opacity:1}}
.nav{animation:cgNavIn var(--t-fast) var(--ease) both}
/* 点击态与焦点态（P4）：微信式"变浅底"，不用缩放；键盘焦点必须看得见 */
.row:active,.tabbar a:active{opacity:.72}
.tabbar a:focus-visible,.nav button:focus-visible,.btn-x:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation-duration:.001ms!important;animation:none!important}}

/* ── 统一自适应框架（2026-09-27 · owner：电脑上每个页面的比例和尺寸都不一样）────────────
   实测（1440px 屏，各页主容器宽）：首页 332 ／ 对话 430 ／ 成长 419 ／ 服务 560 ／ 我的 288 ／
   须知 396 ／ 价格 526 ／ 开关 352 ／ 选粉色 1397 —— 从 288 到 1397，**每页一个比例**。
   根因是三套互不认识的宽度规则并存：.app 固定 430px、.shell 上限 560px、各页 .wrap 640/720/860/1000px。
   办法：**全站只留一个宽度变量**。
     · 手机（<600px）：满屏 —— 手机观感是基准，本次一行没动。
     · ≥600px：所有页面共用同一个居中框架（--frame-w）。
     · ≥768px：框架变成一张卡片（固定高度、内部滚动），长文再套一个"可读行宽"（--read-w），
       免得 880px 宽的行读起来累；嵌在外壳里的二级页仍然铺满，不会缩成卡片中间的一条。 */
:root{--frame-w:100%;--read-w:680px;--msg-w:714px}   /* --read-w=40em×17px；--msg-w=42em×17px —— 用 px 是因为 em 会跟着各页继承字号变，实测 15px 的页里只剩 600px */
@media (min-width:600px){
  :root{--frame-w:min(94vw,880px)}
  .shell,.app{width:100%;max-width:var(--frame-w);margin-left:auto;margin-right:auto}
}
@media (min-width:768px){
  :root{--frame-h:min(880px,calc(100dvh - 52px))}
  body{align-items:center;justify-content:center;padding:26px 16px}
  .shell,.app{flex:0 0 auto;height:var(--frame-h);min-height:0;
    border-radius:var(--r-26);border:1px solid var(--line);box-shadow:0 24px 60px rgba(0,0,0,.18);overflow:hidden}
  /* 长文页（须知/协议/隐私/交付物说明）：正文按可读行宽居中 */
  .doc .wrap{max-width:var(--read-w);margin:0 auto}
  /* 正文段落也按可读行宽收住：实测 880px 卡片里 p.tip-x / p.dim-x 会拉到 812~846px，
     一行 48 个汉字读起来累，而且和同屏的其它页长短不一。
     **只收正文**：.row（联系人/设置行）与 .card 仍然铺满 —— 那两类本来就该满宽。 */
  .page p,.page li,.page .tip-x,.page .dim-x,.page .lead,.page pre{max-width:var(--read-w)}
  .page .row p,.page .row .dim-x,.page .row .tip-x{max-width:none}
  /* 聊天气泡：桌面别拉到 714px 那么宽（微信桌面版气泡约占面板六成）。
     用变量而不是直接写 .msg——struct.css 先于 app.css 加载，同权重会被 app.css 的 .msg 压掉。 */
  :root{--msg-w:578px}   /* 34em×17px */
  /* 二级页嵌在外壳里时中和掉卡片与宽限。
     ⚠️ 真实链路是 .shell > main.page > .wrap —— 第 717 行那条按 .page>.app>.wrap 写的选择器
     **从来就没匹配上过**，所以各页 .wrap 的 640/720/860/1000px 一直在悄悄生效，
     这就是"每页比例都不一样"的真根因之一。这里按真实链路写，并且用 .page>.wrap（0,2,0）
     压过各页自己的 .wrap（0,1,0）。 */
  .page>.app,.page>.doc,.page .app,.page .doc{
    max-width:none;height:auto;margin:0;padding:0;border:0;border-radius:0;box-shadow:none;overflow:visible}
  .page>.wrap,.page .app .wrap,.page .doc .wrap{max-width:none;margin:0;padding:0}
}
