/* shell.css — omscs 双端壳的单一真源(#458 壳统一刀)。
   四个文档共用:index.html(脸=button,JS 状态机)与 stats/profs/research(脸=回链 <a>)。

   #550 daisyUI 收编刀(UI-COMPONENT-POOL 裁决的 omscs 首消费):框架件的**骨架**交给
   daisyUI 组件,本文件只留「组件没有的那些」——
     顶栏 = .navbar(+ .navbar-start/.navbar-end)· 手机底栏 = .dock · 桌面侧栏 = .menu。
   断点分岔从本文件的 @media 搬到 markup 的 max-[900px]: / min-[900px]: 变体:900 仍是
   单一真源,Tailwind 出的 `not all and (min-width:900px)` 与 `(min-width:900px)` 恰为
   互补集(与旧的 `(width < 900px)` / `(min-width:900px)` 一字不差)。
   壳的规则只住这里 —— 在页内 <style> 里再写 .side/.shell = 开第二真源,禁。 */

/* 跨文档缝合(2026 平台三件套之一,另两件:Speculation Rules 预渲染 + bfcache):
   同名 view-transition-name 让侧栏跨页「原地保持」,内容区默认淡切 ——
   渐进增强,不支持的浏览器就是普通整页导航。 */
@view-transition{navigation:auto}
.topbar{view-transition-name:top-bar}
/* .side 的 view-transition-name 只在桌面给(见下面桌面块)。它保的是**桌面 rail 跨页
   原地保持**,手机端本来就没有常驻 rail —— 而 view-transition-name 会**生成层叠上下文**,
   手机端底栏(dock)是 .side 的后代,被关进这个上下文里就再也升不到主列内容之上:
   实测 elementFromPoint 打在底栏正中拿到的是 .ev(时间线行)而不是底栏,
   底栏自己的 z-index:1 完全不管用(层叠上下文是它爹的)。旧版没这个病,是因为当时
   .side 自己就是那条 fixed 底栏、带着 z-index:40 —— 上下文是它自己的。 */
/* 当前页态的统计入口(stats/profs/research 页侧栏):accent 点亮,链接语言不变 */
.side>.statslink[aria-current="page"]{color:var(--accent);font-weight:600}

/* ── 顶栏 ── 骨架归 .navbar(display/align-items/width/min-height/padding 全是它的)。
   这里只补 navbar 不管的五件:粘住、压住内容、纸底、下沿 hairline、刘海机上安全区。
   一条规则跨两端 —— 旧版把整块顶栏在手机/桌面各写一遍(44 与 48 两套魔数),
   navbar 的 min-height:4rem 统一了两端,重复的那一份整块删掉。 */
.topbar{position:sticky;top:0;z-index:60;background:var(--bg);
  border-bottom:1px solid var(--line);
  padding-top:calc(.5rem + env(safe-area-inset-top,0px))}
/* 顶栏高度的单一真源 = navbar 的 min-height。侧栏 sticky 阈值与分隔线起点从它算 ——
   写成变量纯粹是因为 CSS 读不到别的元素解出来的高。 */
:root{--tb-h:4rem}
/* mark 22px:flex 盒居中对齐,禁 vertical-align 魔数(设计律)。两端同尺寸(旧版
   手机 20 / 桌面 22 的一档之差随 navbar 统一,少一处分岔)。 */
.tb-ico{width:22px;height:22px;display:block;flex:none;border-radius:5px}
.tb-name{font-weight:700;font-size:14px;letter-spacing:-.01em;white-space:nowrap}
/* React account trigger 的 active 语义。尺寸/骨架仍归 daisyUI btn-sm/btn-ghost；这里只接
   OMSCS 壳已有的 active accent 语汇。 */
#tbauth .account-trigger-active{background:var(--accent-wash);color:var(--accent)}

/* ── 导航项 ── #facenav 一份 <li> DOM 上叠两个组件:桌面 .menu、手机 .dock。
   两者对「项」的取位不同 —— menu 认「li 里的那个元素」,dock 认「li 自己」——
   下面两条就是这唯一的接缝,不是像素补丁:
   ① 两个组件都不重置 UA 的 <button> 皮(daisy 官方示例一律用 <a>),补一次;
   ② dock 的格子是 li,真正可点的是里面的按钮,让它铺满并沿用 dock 的竖排。 */
#facenav :is(button,a){border:0;font:inherit;text-decoration:none;cursor:pointer}
/* 底色/字色只补**静息态**,并显式避开 menu 要上色的三个状态。为什么必须避开:
   shell.css 是无层样式,daisyUI 全在 @layer 里 —— **无层永远赢 @layer,与特异度无关**。
   一条不设防的 background:none 会把 menu 的 hover/active 底色一并抹掉,而且抹得
   零信号(2026-08-20 实测:D13 的假红就是这么来的)。 */
#facenav :is(button,a):not(.menu-active,:hover,:focus-visible){background:none;color:inherit}
/* ②的落点在下面的手机块里 —— 这里**不能**写 .dock>li>…:元素上的类是
   `max-[900px]:dock`(Tailwind 变体),不是裸 `.dock`,那条选择器永远不命中。
   踩过一次:探针全绿而底栏的图标与文字并排挤成一行(截图才看出来),
   补了 M8 钉住「项是竖排的」。 */
/* 激活语汇走 menu 自己的变量口(daisyUI 的定制 API,值仍来自 suite token —— 与
   shared/tailwind.src.css 那 27 行桥同一姿势,不是覆写组件规则)。默认值是 neutral
   实心药丸;换成 accent-wash 底 + accent 字,与顶栏人形激活态解出同一组值(探针 D13
   对账的就是这个「同一组」)。 */
/* margin/list-style:UA 给 <ul> 的默认外边距(1em)在本仓是活的 —— tokens.css 拥有 base、
   全仓无 preflight,daisyUI 自己的 reset 层又在 exclude 名单里(shared/tailwind.src.css)。
   menu 和 dock 都不重置它,于是桌面顶出 14px、手机把 bottom:0 的底栏抬离视口 16px
   (探针 D6/D9/M2 三处同时喊,一条 margin 一次修好)。 */
#facenav{margin:0;list-style:none;
  --menu-active-bg:var(--accent-wash);--menu-active-fg:var(--accent)}

/* 手机:内容给底栏让位。数按 dock 自己的账算(4rem + 安全区),不再是 74 这种魔数。 */
@media (width < 900px){
  body{padding-bottom:calc(4rem + env(safe-area-inset-bottom))}
  /* 底栏这端由 dock 画激活(li 的 ::after 指示条),按钮本身不该上色 —— 上面那条
     状态豁免在这端会漏出 UA 的 buttonface(灰底 outset 边),所以手机端无条件补平。 */
  #facenav :is(button,a){background:none;color:inherit}
  /* 接缝②:dock 把 <li> 当格子,真正可点的是里面的按钮 —— 让它铺满格子并沿用
     dock 的竖排(图标在上、文字在下)。写 #facenav 而不是 .dock:元素上挂的是
     Tailwind 变体类 max-[900px]:dock,裸 .dock 选不中它。 */
  #facenav>li>:is(button,a){display:flex;flex-direction:column;align-items:center;
    justify-content:center;gap:1px;width:100%;height:100%}
}

.shell{max-width:680px;margin:0 auto;padding:0 16px}
.composition-group-host{min-height:2.5rem;margin:0 0 12px;overflow:hidden}
.composition-group-host[hidden]{display:none}
.composition-group-nav{min-width:0;overflow-x:auto;scrollbar-width:none}
.composition-group-nav::-webkit-scrollbar{display:none}
.composition-group-tabs{min-width:max-content;white-space:nowrap}
.composition-group-tabs .tab{height:2.5rem;min-height:2.5rem;padding-inline:14px}
.context-rail{display:none}
.sidelinks{display:none}
#sidetoggle{display:none}
.bmc{color:var(--muted);text-decoration:none;font-size:12px}
.bmc:hover{color:var(--text)}
.side>.bmc{display:none} /* 侧栏 BMC 只在桌面出现;手机端走页脚 .bmc-mobile */
.side>.glink{display:none} /* 站点出口同策略:手机端走页脚 .foot-exit,底栏不放链接 */
.side>.statslink{display:none} /* 统计入口同策略:手机端走主列 .statcard */
.statcard{display:flex;justify-content:space-between;align-items:center;gap:12px;
  margin-top:16px;text-decoration:none;color:inherit} /* 形收进类:宽屏要能被 display:none 盖掉,行内样式盖不掉 */
[data-shell-course-stats][hidden]{display:none} /* 一次性 flow 不拼入全局统计入口 */
.bmc-mobile{display:inline-block;margin-top:8px}
.foot-exit{display:block;margin-top:8px;color:var(--faint,var(--muted));text-decoration:none}
@media(min-width:900px){.bmc-mobile,.foot-exit{display:none}
body>footer{display:none}}
@media(min-width:900px){
  body{padding-bottom:0}
  /* 对齐修正 ①(owner 对标设计稿):侧栏 rail 贴视口左缘,整页不再居中。
     rail 宽 240 = 内容 + 呼吸;右缘一条竖分隔线(::before;fixed 是因为侧栏 sticky
     高随内容,border-right 画不满全高)。内容列在余宽里居中,宽度上限留在 .maincol。
     padding-right 28 = 与列间 gap 同值 —— 内容列两侧呼吸对称是「余宽居中」的前提。
     padding-top 20 = 静止态两列共同的呼吸,长在滚动流里、滚动时自然让位 ——
     侧栏 sticky 阈值因此收在顶栏下沿(对齐修正 ④)。 */
  .shell{display:grid;grid-template-columns:var(--rail-w) minmax(0,1fr);gap:28px;
    --rail-w:240px;max-width:none;margin:0;padding:20px 28px 0 0}
  .shell.collapsed{--rail-w:96px}
  .shell::before{content:"";position:fixed;left:var(--rail-w);top:var(--tb-h);bottom:0;
    width:1px;background:var(--line);pointer-events:none}
  [data-frame="phone"] .shell::before{content:none} /* 手机框演示模式:fixed 逃得出框,免画 */
  /* 侧栏容器:导航是 chrome 不是内容,退成纸底上的安静轨,不跟内容卡抢「白卡描边」那层皮。
     左内边距 = .5rem,与 navbar 的 padding 同值 —— 顶栏品牌与侧栏内容同一条左缘节奏
     (对齐修正 ①;探针 D2 钉的就是这两个值相等)。旧版这里手写 20px 的 --tb-pad,现在
     两端都由组件的 .5rem 出,那个变量整个删掉。
     对齐修正 ④(owner 抓的滚动天花板):sticky 阈值 = 顶栏下沿(--tb-h),静止态的
     20px 呼吸由 .shell 的 padding-top 出、滚动时随流让位 —— 呼吸写回 .side 自己的
     padding-top 就是双计:滚起来侧栏首项钉在 84 而内容在 64 被切,两列的「顶」不是一条线。 */
  .side{position:sticky;top:var(--tb-h);align-self:start;
    padding:0 .5rem 2px;display:flex;flex-direction:column;gap:2px;
    view-transition-name:side-nav}   /* 跨页原地保持,只桌面要(理由见文件头) */
  /* 统计入口是「离开本页」的一组链接,不是第五到第七张脸。组前只画一条分隔线；
     每项保留原生安静文字,不用 button 的字重/圆角/pill 悬停冒充页内导航。 */
  .side>.statslink{display:flex;align-items:center;gap:8px;flex:none;font-size:14px;
    padding:6px 12px;margin:0;border:0;border-radius:0;color:var(--muted);text-decoration:none}
  .side>nav+.statslink{margin-top:8px;padding-top:10px;border-top:1px solid var(--line)}
  .side>.statslink:hover{color:var(--text)}
  .shell.collapsed .side>.statslink{justify-content:center;padding:6px 0;margin:0}
  .shell.collapsed .side>nav+.statslink{margin-top:8px;padding-top:10px}
  .statcard{display:none} /* 宽屏入口住侧栏,主列不重复 */
  .sidelinks{display:flex;flex-direction:column;gap:10px;
    font-size:12px;color:var(--muted);padding:12px 12px 2px}
  .sidelinks a,.sidelinks footer{color:inherit;font-size:12px}
  /* 收编进来的公开面 footer 自带边距/分隔,这里只留 sidelinks 那一条线 */
  .sidelinks footer{margin:0;padding:0;border:0}
  .sidelinks footer a{text-decoration:none}
  #sidetoggle{display:block;text-align:left;color:var(--muted);padding:6px 12px 2px;flex:none;
    border:0;background:none}
  /* 站点级出口 = 轨底之下的 colophon,不属于工具导航层 */
  .side>.glink{display:flex;align-items:center;gap:6px;margin:16px 0 0;padding:0 12px;font-size:12px;flex:none;
    color:var(--faint,var(--muted));text-decoration:none}
  .side>.glink:hover{color:var(--muted)}
  .shell.collapsed .side>.glink{justify-content:center;padding:0;font-size:15px}
  .shell.collapsed .side{padding:0 .5rem}
  /* 收起态:菜单项收成图标居中(标签由 .lbl 隐藏)。项的内边距/圆角/悬停仍归 menu。 */
  .shell.collapsed #facenav :is(button,a){justify-content:center;padding-inline:0}
  .shell.collapsed .lbl{display:none}
  .shell.collapsed .sidelinks{padding:10px 0 2px;gap:12px;align-items:center;margin:12px 6px 0}
  /* 侧栏底部的非导航区(suite 约定,手机端页脚同一顺序):相关链接 → 分隔线 →
     BMC(Buy Me a Coffee)支持位 → 「← ylab3」站点出口恒最底。
     BMC 支持位 = 原生安静文字(#460 撤官方黄图);侧栏收起时只显 ☕(.lbl 随收起隐藏)。 */
  .side>.bmc{display:flex;align-items:center;gap:8px;margin:16px 12px 0;padding-top:12px;
    border-top:1px solid var(--line);border-radius:0}
  .shell.collapsed .side>.bmc{margin:12px 6px 0;padding-top:10px;justify-content:center;font-size:15px}
  .shell.collapsed #sidetoggle{text-align:center;padding:6px 0}
  /* 身份住侧栏,主列页头让位给 hero(index 的 header.sw 品牌区已随 B2 刀整块退役 ——
     品牌每视口一次,两端都由顶栏承载)。顶部呼吸随修正 ④ 移交 .shell(padding-top 归零)。
     内容列在余宽里水平居中(FINDINGS 判决 = Supabase ScaffoldContainer / Tailwind
     Catalyst 姿势:rail 贴左之下,有界内容余宽居中带 max-width)。
     [Decision] max-width=1140px:FINDINGS 映射表锚点 ~1140,档内(1100-1200)取偏收敛端
     贴本壳原 680 窄列语感;1440 参考视口余宽恰 1144(1440−240−2×28),cap 从参考视口
     起用 —— 常见桌面读作舒展满列,更宽才渐显居中。 */
  .maincol{padding-top:0;max-width:1140px;width:100%;margin:0 auto}
}

/* Context rail is an earned surface, not a permanent third column. The shell policy must offer
   context and a widget must report real content before the column exists. At 1180-1359px it is
   affordable only while the Focus navigation is compressed; from 1360px both rails can coexist.
   Below that threshold the canonical Compare content remains in the main column, so no data or
   action disappears and no duplicate mobile state is created. */
@media(min-width:1180px){
  .shell.collapsed[data-context-visible="true"]{
    grid-template-columns:var(--rail-w) minmax(24px,1fr) minmax(0,1140px) 24px
      minmax(var(--context-rail-min,240px),var(--context-rail-max,280px)) minmax(24px,1fr);
    gap:0;padding-right:0}
  .shell.collapsed[data-context-visible="true"] .maincol{grid-column:3}
  .shell.collapsed[data-context-visible="true"] .context-rail{display:flex}
}
@media(min-width:1360px){
  .shell[data-context-visible="true"]{
    grid-template-columns:var(--rail-w) minmax(24px,1fr) minmax(0,1140px) 24px
      minmax(var(--context-rail-min,240px),var(--context-rail-max,280px)) minmax(24px,1fr);
    gap:0;padding-right:0}
  .shell[data-context-visible="true"] .maincol{grid-column:3}
  .shell[data-context-visible="true"] .context-rail{display:flex}
}
@media(min-width:1180px){
  .shell[data-context-width="comfortable"]{
    --context-rail-min:280px;--context-rail-max:340px}
  .context-rail{grid-column:5;position:sticky;top:calc(var(--tb-h) + 20px);align-self:start;
    flex-direction:column;gap:14px;
    min-width:0;max-height:calc(100vh - var(--tb-h) - 40px);overflow-y:auto;
    border-left:1px solid var(--line);padding:2px 0 8px 20px}
  .context-rail>[data-composition-order="1"]{border-top:1px solid var(--line);padding-top:14px}
  .composition-context-head{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
  .compare-context-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
  .compare-context-tools{display:flex;align-items:center;gap:8px;flex:none}
  .compare-context-width{display:inline-flex}
  .compare-context-width .btn{min-width:32px}
  .compare-context-head .sub{display:block;margin:0 0 2px;font-size:11px}
  .compare-context-head h2{margin:0;font-size:16px}
  .compare-context-menu{width:100%;padding:8px 0 0;gap:4px;background:transparent}
  .compare-context-row{display:grid;grid-template-columns:minmax(0,1fr) 32px;align-items:start;
    gap:4px;padding:0;background:transparent}
  .compare-context-course{width:100%;height:auto;min-height:0;justify-content:flex-start;
    padding:8px;text-align:left;white-space:normal}
  .compare-context-copy{display:grid;min-width:0;gap:2px;width:100%}
  .compare-context-copy>span:not(.badge){font-size:12px;line-height:1.35;color:var(--muted);
    overflow-wrap:anywhere}
  .compare-context-copy .badge{margin-top:3px;justify-self:start}
  .compare-context-remove{margin-top:5px}
  .compare-context-primary{width:100%;margin-top:12px}
  .compare-context-note{margin:10px 0 0;font-size:11px;line-height:1.45}
}

/* 紧凑开关(owner 2026-08-20:两个短词不该抻成半屏宽药丸)。加在 .seg 上收成随内容宽;
   多组开关外包 .segrow 并成一行。 */
.segrow{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.seg.fit{display:inline-flex;width:auto}
.seg.fit button{flex:none;padding:8px 14px}

/* FAQ/feedback is a shell utility, not a floating content destination. One React module mounts
   into the desktop sidebar and mobile footer; the breakpoint makes exactly one launcher visible. */
.shell-utility{display:none}
.community-mobile{display:block;margin-top:12px}
@media(min-width:900px){
  .shell-utility{display:block;margin-top:8px;padding:8px 12px 0;border-top:1px solid var(--line)}
  .shell.collapsed .shell-utility{margin-inline:6px;padding-inline:0;text-align:center}
  .community-mobile{display:none}
}

/* ── 可搜索下拉(combobox)── 壳规则,index 与 stats 两页共用(#606 把 glue 抽成
   combo-glue.mjs 时一并把样式收到这里:两页各留一份 = 第二个真源)。
   挂载成功才出现;调用方的回落体(原生 select / 朴素 input)藏而不删。颜色全走 tokens。 */
.combo{max-width:480px}
.combo-ctl{position:relative}
.combo-ctl input{width:100%;box-sizing:border-box;background:var(--well);color:var(--text);
  border:1px solid var(--line);border-radius:var(--radius-sm);font:inherit;font-size:15px;padding:9px 38px 9px 12px}
.combo-ctl input:focus-visible{outline:2px solid var(--ring);outline-offset:1px;border-color:transparent}
.combo-ctl .trig{position:absolute;right:2px;top:2px;bottom:2px;width:32px;border:0;background:none;
  color:var(--muted);font-size:12px;cursor:pointer;border-radius:var(--radius-sm)}
/* !important 在这里是**对的**:Zag 的 positioner 把 z-index 写进**内联样式**(实测计算值
   z-index:auto),样式表再高的特异性也赢不了内联 —— 只有 !important 能。
   不加的话弹层会被 DOM 里更靠后的兄弟(选课台的 tabs)盖住:实测第一个选项被
   List/Compare 两个 tab 压住半行。 */
.combo-pop{z-index:30 !important}
.combo-list{list-style:none;margin:0;padding:4px;max-height:290px;overflow-y:auto;
  min-width:var(--reference-width,100%);max-width:min(92vw,560px);
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-sm);
  box-shadow:0 8px 24px rgba(0,0,0,.35)}
.combo-list li[role="option"]{padding:7px 10px;border-radius:calc(var(--radius-sm) - 2px);cursor:pointer;font-size:14px}
.combo-list li[data-highlighted]{background:var(--accent-wash)}
.combo-list li[data-state="checked"]{color:var(--accent);font-weight:600}
.combo-empty{padding:9px 10px;color:var(--muted);font-size:14px}
