/*
 * 外壳补偿样式。必须在页面原型样式表之前加载。
 *
 * 原型把可点击元素写成 <div>；正式实现按无障碍要求（docs/ux-spec.md §10）
 * 改用 <button>。st/about 两页的原型样式表没有 button 复位，浏览器默认的
 * padding/border/background 会让几何与原型偏离，这里统一复位。
 *
 * 加载顺序在前，因此加载了 style.min.css 的页面仍以原型样式为准。
 */
.menu__icon,
.app__aside-toggle,
.app__aside-body-close,
.popup__body-close,
.notes-all-modal__close {
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/*
 * 案例卡片封面。原型里 `.case-category__thumb` 是个空色块（没有 <img>），
 * 这里让后台上传的封面填满它。原型样式表里没有针对该 img 的规则，
 * 因此本文件先加载也不会被覆盖。
 */
.case-category__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

/*
 * 案例分类页的两处（用户要求，2026-09-18）：
 *
 * 1. **横线上那行**（分类名 + `>>more`）改成品牌渐变字。基线 `prototype-case.css`
 *    给的是纯白（`.case-category__head h2` / `.case-category__more`），
 *    本文件在它之前加载，同分要输 —— 所以前面都挂一层 `.case-category`。
 *
 *    渐变是按**盒子**铺的：h2 若是块级、盒子撑满整行而文字只占左边一段，
 *    见到的就只剩渐变开头那一种颜色（hero 的按钮、课程弹窗的分类名都踩过这个坑）。
 *    所以这里 `width: fit-content` 把盒子收到文字宽。
 *    （别写 `display: inline-block`：`.case-category__head` 是 flex 容器，
 *    子项的 display 会被**块化**成 block，写了也不生效 —— 实测过。）
 *
 * 2. **案例名挪到图片下面 + 换宋体**。间距也从「下 10px」改成「上 10px」——
 *    标题原来在图片上方（下面留白给图片），现在在下方，留白应该在它上面。
 */
.case-category .case-category__head h2 {
  width: fit-content;
  /*
   * 字号**缩小 20%**（用户要求）：基线 `prototype-case.css` 桌面是 24px → **19.2px**。
   * 只有这一行分类标题缩，右边那个 `>>more` 不动（用户点名的是「分类标题文字」）。
   */
  font-size: 19.2px;
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

/*
 * 窄屏那条基线是 `.case-category__head h2 { font-size: 20px }`（≤600px），
 * 按同一比例缩就是 **16px**。必须显式写：上面那条带 `.case-category`
 * 特异性更高，不写的话手机上会被拉到 19.2px —— 比原来还大。
 */
@media (max-width: 600px) {
  .case-category .case-category__head h2 {
    font-size: 16px;
  }
}

.case-category .case-category__more {
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.case-category .case-category__card h3 {
  margin: 10px 0 0;
  font-family: "SimSun", "宋体", "Songti SC", "Noto Serif CJK SC", serif;
}

/*
 * 案例方案格子的「案例整理中」按钮：板块已上线但还没有已发布案例。
 * 入口保留，只是不给渐变的「可点主按钮」观感。
 *
 * 选择器多带一层 `.prices__cell`：`.btn-gradient` 与本规则同为单类选择器，
 * 而本文件先加载，同级会输掉，必须提高一级特异性。
 */
.prices__cell .prices__case-button--pending {
  background-image: none;
  background-color: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .22);
  color: rgba(255, 255, 255, .62);
}

.prices__cell .prices__case-button--pending:hover {
  background-color: rgba(255, 255, 255, .12);
  color: #fff;
}

/*
 * 资料库侧栏与顶部导航的间距。本布局里 `.app__aside-head`（含侧栏自己的 logo）
 * 是 display:none，侧栏可见内容直接从「查看全部」开始，紧贴吸顶头部的 logo。
 * 这里把侧栏正文整体下移 20px。属于有意偏离原型的调整。
 *
 * 选择器多带一层 `.app`：本文件先于原型样式表加载，同优先级会输给
 * `app-notes.css` 的 `.app__aside-body`。
 */
/*
 * 只作用于资料库与案例详情页：别的页面用的是同一套 `.app__aside` 结构，
 * 选择器不收窄会一起中招（实测 /hr 平板侧栏整列下移 20px，像素差 0.498%）。
 *
 * 案例阅读器同资料库阅读器，侧栏可见内容从「查看全部」开始，同样需要这一段间距。
 */
.app .app__aside--library .app__aside-body,
.app .app__aside--case .app__aside-body {
  padding-top: 20px;
}

/*
 * 资料库正文列的最小高度。
 *
 * 文章比侧栏还短时（例如只有标题、正文为空的章节），网格行高会塌到侧栏自身的高度，
 * sticky 的侧栏因此失去可偏移的空间，被夹到容器顶部——实测从 100px 跳到 86px，
 * 看起来就是「点这一篇时左侧目录往上蹦」。
 *
 * 给正文列兜一个视口高度，行高就不再被短文章拖塌。只在桌面断点生效，
 * 移动端侧栏是抽屉，不参与这套布局。
 */
@media (min-width: 769px) {
  .app .notes-main {
    min-height: calc(100vh - 120px);
  }
}


/*
 * 案例/课程列表的卡片网格：修掉原型 `gap: 7.5%` 的**百分比行间距**。
 *
 * 原型 `prototype-case.css` 里 `.case-category__grid` 写的是
 * `grid-template-columns: repeat(4,1fr); gap: 7.5%`。列间距按容器**宽度**算
 * （1440 下约 103px，正是作者想要的疏朗间距），但行间距没有宽度可依，
 * Chrome 拿容器的**高度**去解 —— 而那个高度本身取决于行间距，成了循环。
 * 结果是网格高度少算了 (行数 − 1) × 行间距：卡片排到网格外面，
 * 越出分类区块，被 footer 盖住。
 *
 * 原型里永远只有 1 行 4 张占位卡，没有行间距，所以看不出来。
 * **实验证明这是原型自身的缺陷**：往原型页面注入到 10 张卡，同样溢出 83px。
 *
 * 修法：只把**行**间距换成确定值，列间距（`7.5%`）一律不动，版面与原型逐像素一致。
 * `clamp(28px, 3vw, 42px)` 在 1440/1280/1024/901 四个宽度上分别解出
 * 42/38/30/28px —— 与修之前实测的行间距完全相同，即视觉零变化，只是高度算对了。
 *
 * 限定 `min-width: 801px`：≤800 的断点原样写的是 `gap: 28px 20px`（确定值，无此问题），
 * 而这条选择器特异性更高，不加范围会把它一起盖掉。
 */
@media (min-width: 801px) {
  .case-category .case-category__grid {
    row-gap: clamp(28px, 3vw, 42px);
  }
}

/*
 * 案例详情页「查看全部」里的案例卡：封面 + 标题。
 * 资料库的弹窗只列文字按钮，案例这边用户要求带配图，所以另起一套。
 */
.notes-all-modal__cases section {
  margin-bottom: 22px;
}

.notes-all-modal__cases h3 {
  margin: 0 0 10px;
  color: #fff;
  font-size: 15px;
}

.notes-all-modal__case {
  display: grid;
  gap: 8px;
  text-decoration: none;
  color: #fff;
}

.notes-all-modal__cover {
  display: grid;
  place-items: center;
  width: 100%;
  /*
   * **高度跟着图走，不写死比例**（用户要求：去掉上下那条底色带）。
   *
   * 原先固定 `aspect-ratio: 1.68/1`，封面比例不一致时就会上下留底色 ——
   * 而框的圆角（10px）只裁到框，图缩在框里、**自己的直角全露着**，看着就是"没做圆角"。
   * 让框贴着图之后：图占满框、四角被框的圆角裁住，两个问题一起没了。
   *
   * 代价：同一行卡片高度不再一致（2.2:1 的图比 1.68:1 的矮），行内顶对齐、标题各自跟在图下。
   * 比例本来就匹配的图（案例那批 319×190）**结果完全不变**。
   */
  border-radius: 10px;
  background: #160b31;
  overflow: hidden;
  color: rgba(255, 255, 255, .3);
  font-size: 11px;
}

/*
 * 封面**完整显示、且不留边**。
 *
 * 上传的封面比例不统一 —— 实测课程封面里 3 张是 760×345（2.2:1）、5 张是 319×190（1.68:1），
 * 而后台对封面尺寸没有任何要求（字段只写「课程封面（可选）」），不能靠"让用户按某个比例传"来兜。
 *
 * 走过的三步（每一步都是用户反馈推出来的）：
 * 1. `cover` + 固定 1.68 框 → 2.2 的那几张**左右各裁掉 12%**（"配图显示不全，是裁剪了?"）；
 * 2. `contain` + 固定 1.68 框 → 不裁了，但上下留底色带，图缩在框里、直角外露
 *    （"多出高度的背景框处理一下，同时做圆角处理"）；
 * 3. **框不写比例、高度跟着图走**（本文件上面的 `.notes-all-modal__cover`）——
 *    图既完整、又没有留边，四角还被框的圆角裁住。
 *
 * 于是这里的 `img` 只要 `width: 100%; height: auto`（保持自身比例）。
 * 比例本来就匹配的图（案例那批 319×190）三版结果都一样，观感没变过。
 */
.notes-all-modal__cover img {
  width: 100%;
  height: auto;
}

/*
 * 没配图的占位块：**它没有图，也就没有自然高度** —— 不给比例会塌成一行文字那么矮。
 * 所以"框不写比例"这条只对**有图的**生效，占位块仍用 1.68:1。
 */
.notes-all-modal__cover--empty {
  aspect-ratio: 1.68 / 1;
  border: 1px dashed rgba(255, 255, 255, .18);
}

/*
 * 分类名（「AI全体系-课程」「Ai岗位-对应课程」）用品牌渐变字（用户要求）。
 *
 * 渐变铺在**文字自己的盒子**上，所以 h3 改成 `inline-block`：块级元素会撑满整行，
 * 渐变按整行宽铺开、而文字只占左边一小段，看到的就只剩最左端的粉红，蓝紫那半截
 * 永远露不出来（`.hero__btn-text` 那边踩过同一个坑）。
 * 改 `inline-block` 只影响这一行的盒宽 —— 后面那个 `.notes-all-modal__grid` 仍是块级，
 * 照旧另起一行。
 *
 * 渐变值抄自全站那套品牌渐变（`.header__tagline` / `.site-lib-modal__group-name` 同一串），
 * 不另调一个。
 *
 * **两个弹窗共用这一条**：`CourseModal`（Ai课程）与 `CaseReaderView` 的「查看全部」
 * （案例分类名，2026-09-18 用户要求也上渐变）。靠面板上的钩子类收窄：
 * `.site-course-modal` / `.site-case-modal`。
 *
 * 还有第三处用同一个 `<h3>` 的是 `CourseReaderView` 的「查看全部」（课程阅读器那个弹窗），
 * 它**不在**这条规则里 —— 那个弹窗没有钩子类，观感保持原样。
 */
.site-course-modal .notes-all-modal__cases h3,
.site-case-modal .notes-all-modal__cases h3 {
  display: inline-block;
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  background-size: 100% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/*
 * 课程弹窗卡片配图下面的标题：**放大一倍、居中**，随后两次各缩 20%（用户三轮要求）。
 *
 * 基线 `.notes-all-modal__case-title` 是 13px、左对齐（案例阅读器的「查看全部」用的就是那个尺寸），
 * 所以这条**只作用在课程弹窗里** —— 靠 `CourseModal` 给面板多带的 `site-course-modal`
 * 收窄，不去动案例那边的观感。
 *
 * 字号来历：13 × 2 = 26 → × 0.8 = 20.8 取整 21 → 再 × 0.8 = 16.8 取整 17px。
 *
 * `font-weight: 400` 是**显式写死**的（用户要求「不要粗体」）：这一层的计算值本来就是 400，
 * 但那靠的是「没有任何一条规则给它加粗」—— 哪天有人给卡片标题加个强调样式，
 * 这里会**静默**跟着变粗。写死之后这条要求有个明确的落点。
 */
.site-course-modal .notes-all-modal__case-title {
  font-size: 17px;
  line-height: 1.35;
  text-align: center;
  font-weight: 400;
  /*
   * 默认 **58% 白**（用户 2026-10-19 要求；2026-10-18 曾是紫色 #b84cff）。
   * 悬停/键盘聚焦时回到 100% 白，见下面那条 `.site-course-modal .notes-all-modal__case:hover …` 规则。
   */
  color: rgba(255, 255, 255, .58);
}

.notes-all-modal__case-title {
  font-size: 13px;
  line-height: 1.4;
}

/*
 * 卡片封面图：**鼠标不在上面时 50% 透明，悬停恢复到 100%**（用户要求，两处）：
 *   1. Ai课程弹窗（`CourseModal`）；
 *   2. 案例阅读器的「查看全部」弹窗（`CaseReaderView`）。
 *
 * 两处用的都是 `.notes-all-modal__case` / `.notes-all-modal__cover` 这一套类名，
 * 所以规则写通用的即可（资料库那个「全部资料」弹窗用的是 `__groups` / `__group`，
 * 不含 `__cover`，不受影响）。
 *
 * 「未配图」那个空态（`.notes-all-modal__cover--empty`）**不加透明度** —— 它是占位文案，
 * 调暗反而更难读。
 *
 * `transition` 只动 `opacity`（合成层属性，不触发重排）；`:focus-visible` 与 `:hover`
 * 同效，否则键盘走到这张卡片时永远看不到不透明的封面。
 */
.notes-all-modal__cover img {
  opacity: .5;
  transition: opacity .25s ease;
}

/*
 * **技术咨询弹窗**里封面的默认透明度改 85%（用户 2026-10-18 要求；其余两处仍是 50%）。
 * 只收窄到 `.site-course-modal`，案例阅读器的「查看全部」不动。
 * 位置必须在下面那条 hover 规则**之前** —— 两者特异性相同，靠顺序决定悬停仍然回到 100%。
 */
.site-course-modal .notes-all-modal__cover img {
  opacity: .85;
}

/*
 * 课程弹窗里的封面缩到 98%（用户 2026-10-19：图缩 2%、位置不变，让图之间的间距稍大）。
 *
 * 缩的是**封面框**而不是里面的 `<img>`：框自带 10px 圆角和 `overflow: hidden`，
 * 只缩图会在框里露出一圈底色，看着像加了内边距；缩框则是整张图连圆角一起缩。
 * `justify-self: center` 让它在自己的网格格里居中 —— 卡片位置不动，格与格之间的
 * 视觉留白因此变大（213.5px 的图缩 2% ≈ 每边多 2px）。
 */
.site-course-modal .notes-all-modal__cover {
  width: 98%;
  justify-self: center;
}

.notes-all-modal__case:hover .notes-all-modal__cover img,
.notes-all-modal__case:focus-visible .notes-all-modal__cover img {
  opacity: 1;
}

/*
 * 案例板块页（`/cases/<板块>`，8 个板块）的案例卡片封面：同上，未悬停 50%、悬停 100%。
 *
 * 只作用在**卡片图**上：页头那张 `board.heroImage`（`.aigc-hero__visual`，590×240）
 * 是不可点的装饰图，没有 hover 目标 —— 给它加透明度只会让页头一直灰着。
 */
.case-category__thumb img {
  opacity: .5;
  transition: opacity .25s ease;
}

.case-category__card:hover .case-category__thumb img,
.case-category__card:focus-visible .case-category__thumb img {
  opacity: 1;
}

/*
 * 左侧栏那个「📂 >>> 全部…」按钮：**与下面二级目录按钮同尺寸，但不填渐变**。
 *
 * 尺寸实测自 `.notes-category-button`（就是下面那条分类横条）：
 * 高 44、字号 18、圆角 6、内边距 9/12；宽度占满同一列 ——
 * 它在 `left=48、宽 220`（ul 的 40 + li 的 8px 左边距 → 左边缘 48；
 * 右边缘 268）。所以这里用 `margin: 0 16px 12px 8px` 让宽度落在同一列，
 * 而不是居中：居中会得到 left=52，比下面那排缩进 4px，一眼能看出没对齐。
 *
 * 基线 `app-notes.css` 给的是 `.notes-case-button { width: auto; margin: 0 16px 12px }`，
 * 加上 `.btn-gradient` 的渐变与胶囊圆角。这里只改尺寸与描边，**不加渐变**：
 * 白描边 + 透明底，与下面的实心渐变横条区分开，一眼看出是「展开全部」而不是某个分类。
 *
 * 选择器用 `[data-notes-all-open]`（按钮的身份）而不是 class，并多带一级
 * `.app__aside-body` 压过基线 —— 本文件在基线之前加载，同特异性会输。
 */
.app__aside-body [data-notes-all-open] {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  /*
   * 宽度必须显式给：`<button>` 即使设成 `display: flex`，
   * `width: auto` 仍然按内容收缩（不撑满容器）——按钮元素的特殊行为，
   * 实测给 auto 时是 168px（内容宽），给了 calc 才是 220px。
   * 244（容器内容宽）− 8（左）− 16（右）= 220，与下面那排同宽。
   */
  width: calc(100% - 24px);
  margin: 0 16px 12px 8px;
  padding: 9px 12px;
  border: 1px solid #fff;
  border-radius: 6px;
  background-image: none;
  font-size: 18px;
}

/*
 * 封面卡片栅格的**基础**规则（`display: grid` + 3 列 + 间距）。
 *
 * 这条本来只在基线 `app-notes.css` 里，而那份样式表**只有阅读器路由加载** ——
 * 上面那四条卡片规则（`__cases`/`__case`/`__cover`/`__case-title`）当初搬进本文件时，
 * 唯独漏了这条。于是「全部课程」弹窗从**导航/首页**打开时没有栅格：
 * 一张封面撑满一整行（用户反馈"顶部导航里的 Ai课程 弹出框还是不对"，就是这个）。
 *
 * 补一份同值的。阅读器那边 `app-notes.css` 后加载、同权重、值也一样，不受影响。
 */
.notes-all-modal__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

/*
 * 「查看全部」弹窗每行 4 个。三个阅读器共用 `.notes-all-modal__grid`（基线里 3 列），
 * 各自在自己的分支里覆盖，不去改共用类：
 * - 案例 / 课程：`.notes-all-modal__cases`（封面卡片）
 * - 资料库：`.notes-all-modal__groups`（二级目录按钮，分组排列）
 *
 * 只在 ≥768px 生效，窄屏保持共用类的 3 列：4 列在 390px 上每格不到 80px，
 * 封面看不清、目录名也会被挤断。
 * （原型那份 `app-notes.css` 里本来有一条移动端 `repeat(2,minmax(0,1fr)`，
 * 括号不闭合被浏览器整条丢弃，所以窄屏实际一直是 3 列。那是原型基线文件，不去改它。）
 */
@media (min-width: 768px) {
  .notes-all-modal__groups .notes-all-modal__grid,
  .notes-all-modal__cases .notes-all-modal__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.notes-all-modal__case:hover .notes-all-modal__case-title {
  color: #b66cff;
}

/*
 * **技术咨询弹窗**的卡片悬停效果：
 *   1. 标题默认 58% 白（见 `.site-course-modal .notes-all-modal__case-title`），悬停回到 **100% 白**；
 *   2. 整张卡片**轻微上移**（4px）。
 * 只收窄到 `.site-course-modal`，案例阅读器的「查看全部」保持原来的浅紫标题、不位移。
 *
 * `transform` 不参与布局，所以上移不会把网格撑开；`transition` 只动 transform（合成层）。
 * `prefers-reduced-motion` 下关掉位移，标题变色保留。
 */
.site-course-modal .notes-all-modal__case {
  transition: transform .18s ease;
}
.site-course-modal .notes-all-modal__case:hover,
.site-course-modal .notes-all-modal__case:focus-visible {
  transform: translateY(-4px);
}
.site-course-modal .notes-all-modal__case:hover .notes-all-modal__case-title,
.site-course-modal .notes-all-modal__case:focus-visible .notes-all-modal__case-title {
  color: #fff;
}
@media (prefers-reduced-motion: reduce) {
  .site-course-modal .notes-all-modal__case { transition: none; }
  .site-course-modal .notes-all-modal__case:hover,
  .site-course-modal .notes-all-modal__case:focus-visible { transform: none; }
}

/*
 * 侧栏的三级条目。**三个阅读器都要，不能只给案例。**
 *
 * 基线 `app-notes.css` 的 `.notes-chapter-button` 只写了 `width: 100%`、`padding: 7px 10px`、
 * `line-height: 1.45`，**没写 `display`** —— 因为原型那边这类条目是 `<button>`
 * （UA 默认 `inline-block`，垂直内边距能撑开行盒）。
 *
 * 我们为了给每篇文章独立地址，把它们渲染成了 `<a>`（`display: inline`）。
 * 行内元素的**垂直内边距不参与行盒高度**，于是相邻两条互相重叠：
 * 实测条目高 38、行距 -12px，看起来就是「标题挤在一起」。
 * 同时 `width: 100%` 与 `text-align: left` 在行内元素上本来也不生效。
 *
 * 这条规则原先只写在 `.app__aside--case` 下 —— 案例与课程阅读器（`--case` /
 * `--case --course`）是对的，**资料库（`--library`）漏了**。
 * 现在去掉作用域，三个阅读器统一。
 */
.notes-chapter-button {
  display: block;
  width: 100%;
  text-align: left;
  text-decoration: none;
}

/*
 * 左栏条目（文章名 / 课程名）的字体：**比基线小 20%，换成宋体**（用户要求）。
 *
 * 18px × 0.8 = **14.4px**；行高按同一比例走（`1.45` 保持不动，26.1 → 20.88）——
 * 字小了而行距照旧的话，条目之间会空得发虚。
 *
 * 为什么不直接改上面那条：**本文件在基线之前加载**（见文件头），与基线
 * `app-notes.css` 的 `.notes-chapter-button` 特异性相同（都是 0,1,0），同分时后者赢。
 * 这里多套一层 `.app` 提到 0,2,0 才盖得住。
 *
 * 只改「具体文章/课程」这一层：上面那行分类名（`.notes-category-button span`）
 * 与「全部资料」按钮不在其列，仍按基线。
 */
.app .notes-chapter-button {
  font-family: "SimSun", "宋体", "Songti SC", "Noto Serif CJK SC", serif;
  font-size: 14.4px;
  line-height: 1.45;
  /*
   * 颜色（用户要求）：**默认纯白**，原来基线给的是 48% 白（灰扑扑的），
   * 悬停改成紫色 `#c787ff` —— 与顶部导航悬停**同一个色值**（实测顶部导航
   * 悬停是 rgb(199,135,255)）。
   *
   * 但**不要**照搬顶部导航那个 `translateY(-2px)`：用户明确说「不要像顶部那样
   * 鼠标在上面会缩小/位移」。所以这两条只改颜色，不加任何 transform。
   *
   * 选中项（`.active`）仍由基线的 `color:#fff` + 紫色底 `rgba(130,128,238,.2)`
   * 区分 —— 默认也是白了之后，靠**底色**认选中，这一点没变。
   */
  color: #fff;
}

.app .notes-chapter-button:hover {
  color: #c787ff;
}

/*
 * 右侧「本页目录」的条目：**换宋体，字号与颜色一律不动**（用户要求）。
 *
 * 只写 `font-family` 一条：基线 `app-notes.css` 的 `.notes-outline a` 给了
 * 18px / `rgba(255,255,255,.45)`、悬停与选中是 `#73c9ef`，这些照旧。
 * 宋体栈与左栏条目同一个（见上面那条）。
 *
 * 特异性：基线是 `.notes-outline a`（0,1,1），本文件在它**之前**加载，
 * 同分要输，所以前面挂一层 `.app` 提到 0,2,1。
 */
.app .notes-outline a {
  font-family: "SimSun", "宋体", "Songti SC", "Noto Serif CJK SC", serif;
}

/*
 * 正文最顶上那个标题（`withTitleHeading` 补出来的 `<h1>`）：**宋体 + 居中**（用户要求）。
 *
 * 用 `:first-of-type` 而不是裸的 `.notes-document h1`：标题是正文里**唯一**那个 h1
 * （三个阅读器实测都是 1 个），但 CMS 正文里真写了个 h1 的话，那条规则会把正文中间
 * 的小标题也一起居中、换宋体 —— 限定第一个就只动标题。
 *
 * 字号（`clamp(34px, 4vw, 52px)`）、字重、颜色、`letter-spacing` 都保持基线不动。
 * 基线是 `.notes-document h1`（0,1,1），本文件在其之前加载，所以挂一层 `.app` 提到 0,2,2。
 */
.app .notes-document h1:first-of-type {
  font-family: "SimSun", "宋体", "Songti SC", "Noto Serif CJK SC", serif;
  text-align: center;
}

/*
 * 资料库「查看全部」的分组：一级目录名 → 横线 → 二级目录按钮。
 * 原型只有单层的按钮网格，这里是三级结构带来的一层。
 */
.notes-all-modal__groups {
  display: grid;
  gap: 26px;
}

.notes-all-modal__group-name {
  margin: 0;
  color: #fff;
  font-size: 18px;
  font-weight: 600;
}

.notes-all-modal__group-name.is-current {
  color: #b66cff;
}

.notes-all-modal__group-rule {
  margin: 10px 0 16px;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, .18);
}

/*
 * 「查看全部」里空目录的按钮置灰。之前只设了 disabled 属性，
 * 视觉上和可点的完全一样——用户看不出「这个分类还没内容」。
 */
.notes-all-modal__link:disabled {
  opacity: .38;
  cursor: not-allowed;
  border-style: dashed;
}

/*
 * 首页社区区块的分类按钮：**分成两组、共 7 行**（用户要求）——
 * 前 4 行是资料库的（第 4 行第 4 格换成「>>查看全部资料」），
 * 后 3 行是 Ai岗位的（第 7 行第 4 格换成「>>全部Ai岗位」）。
 *
 * 每行几个由基线 `style.min.css` 的断点决定（>1200 四列 / ≤1200 三列 / ≤767 两列），
 * 每组的按钮上限就按「整行」算 —— 组内按钮数 + 那一个入口 = 列数 × 行数：
 *
 *   4 列 → 资料库 15+1=16（4 行）、Ai岗位 11+1=12（3 行）
 *   3 列 → 资料库 11+1=12（4 行）、Ai岗位  8+1= 9（3 行）
 *   2 列 → 资料库  7+1= 8（4 行）、Ai岗位  5+1= 6（3 行）
 *
 * **两组各套一层 `.community__category-group`，且必须是 `display: contents`**：
 * 这样 `<a>` 仍旧是外层那个基线栅格的直接栅格项，`style.min.css` 里的列数、
 * 间距、三个断点一个字都不用动（那份是受保护的基线，而 `shell-fixes.css` 先加载，
 * 同权重覆盖不了它）。换成「两组各自成栅格」就得把基线那段整个搬过来重写。
 *
 * **这三条上限必须按组写**：套了容器之后，原来那条
 * `.community__category-links > a:nth-of-type(...)` 的直接子选择器**全部失配**，
 * 症状是裁剪彻底失效、几十个按钮一次全铺出来，而且不报任何错。
 *
 * **服务端不 slice**：它不知道视口宽度。这里用 `:nth-of-type` 裁 ——
 * 两个「更多」入口都用 `<span>` 包着（Ai岗位 那个里面还是个 `<button>`），
 * 都不是 `<a>`，因此不会被算进按钮的序号里，任何宽度下都留得住。
 *
 * 上限数字与断点是**一一对应**的，改基线的列数必须同步改这里，故写死不用变量。
 */
.community__category-links > .community__category-group {
  display: contents;
}

.community__category-links > .community__category-group--library > a:nth-of-type(n + 16) {
  display: none;
}

.community__category-links > .community__category-group--ai-jobs > a:nth-of-type(n + 12) {
  display: none;
}

@media (max-width: 1200px) {
  .community__category-links > .community__category-group--library > a:nth-of-type(n + 12) {
    display: none;
  }

  .community__category-links > .community__category-group--ai-jobs > a:nth-of-type(n + 9) {
    display: none;
  }
}

@media (max-width: 767px) {
  .community__category-links > .community__category-group--library > a:nth-of-type(n + 8) {
    display: none;
  }

  .community__category-links > .community__category-group--ai-jobs > a:nth-of-type(n + 6) {
    display: none;
  }
}

/* 两个「更多」入口：文本链接，不占按钮的样式，自己撑满一格并垂直居中。 */
.community__category-more {
  display: flex;
  align-items: center;
  min-height: 44px;
}

/*
 * 资料库那个是 `<Link>`（`<a>`），Ai岗位 那个是弹框触发器（`<button>`）——
 * 选择器要同时覆盖两种，否则按钮那个会带着浏览器默认的灰底方框出现在这里。
 */
.community__category-more a,
.community__category-more button {
  color: #b66cff;
  font-size: 14px;
  text-decoration: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
}

.community__category-more a:hover,
.community__category-more button:hover {
  text-decoration: underline;
}

/*
 * 点顶部导航「资料库」弹出的目录框（`components/site/LibraryNavModal.tsx`）。
 *
 * **不复用阅读器弹窗的 `.notes-all-modal*` 类名**：那套样式在 `app-notes.css` 里，
 * 而那份样式表只被阅读器路由加载；导航在每个页面上，全局加载 `app-notes.css` 又不行
 * —— 它里面有 `.header` / `.wrapper` / `.footer` / `html` 这些会打到所有页面的规则
 * （实测清单见下），一加载整站像素对照全变。
 *
 * 所以几何**照搬**阅读器那个弹窗（同样的面板尺寸、圆角、遮罩、关闭按钮大小），
 * 但用自己的类名，跟着 shell-fixes 在每个页面上都可用。
 */
.site-lib-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.site-lib-modal.opened {
  display: flex;
}

.site-lib-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .72);
  backdrop-filter: blur(6px);
}

.site-lib-modal__panel {
  position: relative;
  width: min(75vw, 1080px);
  height: min(800px, calc(100vh - 40px));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 34px;
  /*
   * 边框改成透明而不是删掉：`box-sizing` 是 border-box，删掉边框会让内容区
   * 每边多出 1px（内宽 1080 → 1082），框内布局就动了。用户要求「框内布局不变」，
   * 所以留一条看不见的边占住位置。
   */
  border: 1px solid transparent;
  border-radius: 24px;
  /*
   * 背景与「在线咨询」弹窗一致：底色 `.popup__body` 的 `#05010d`（实色），
   * 上面再叠一层光晕 `.site-lib-modal__blur`（那个弹窗里是 `.popup__blur`）。
   * `isolation` 给光晕的负 z-index 兜一个层叠上下文，理由见那条规则。
   */
  background: #05010d;
  isolation: isolate;
  box-shadow: 0 20px 70px rgba(0, 0, 0, .55);
  outline: none;
}

/*
 * 关闭按钮。导航弹窗（`.site-lib-modal__close`）与阅读器三个「查看全部」弹窗
 * （`.notes-all-modal__close`）是同一套写法：64px 的文字 `×`，绝对定位在右上角。
 *
 * **`right` 给 30px 而不是 18px**，是为了悬停旋转不留横向滑动条：
 * 按钮外框是 31×64，转 45° 时外框最大到 (31+64)/√2 ≈ 67px —— 左右各外扩约 18px。
 * 给 18px 时它正好顶到面板内缘，面板又是 `overflow-y: auto`（另一轴按规范也算 auto），
 * 于是转动过程中出现横向滚动条。实测：转完溢出 9px、转动中途 11px；让到 30px 后为 0。
 *
 * 悬停旋转与「在线咨询」弹窗的关闭按钮一致（那边是
 * `.popup__body-close:hover img { transform: rotate(-90deg) }`）。
 * 转的是文字 `×`（U+00D7）本身：它 90° 后形状与原来一样，**看得见的是动画过程**
 * —— 转到 45° 时是个 "+"，所以过渡时长要跟那边一致，否则一闪而过看不见。
 */
.site-lib-modal__close,
.notes-all-modal .notes-all-modal__close {
  position: absolute;
  top: 16px;
  right: 30px;
  border: 0;
  background: transparent;
  color: #fff;
  font-size: 64px;
  line-height: 1;
  transition: transform .3s ease;
  cursor: pointer;
}

/*
 * **顶部导航那三个弹窗**（AI课程体系 / 资料库 / Ai岗位）的关闭按钮：
 * 滚动面板内容时固定在面板右上角（用户 2026-10-19 要求）。
 *
 * 面板本身是滚动容器（`overflow-y: auto`），绝对定位的按钮会跟着内容滚走。
 * 这里用 `position: fixed` 钉在视口上，坐标按面板的居中公式反推 —— 面板是
 * `.site-lib-modal { padding: 20px }` 里居中的 `min(800px, 100vh - 40px)` 高、
 * `min(75vw, 1080px)` 宽：
 *   面板上沿 = 20 + (视口高 - 40 - 面板高) / 2
 *   面板右沿 = 视口右 - (20 + (视口宽 - 40 - 面板宽) / 2)
 * 按钮再各内缩 16 / 30（与原来的 `top: 16px; right: 30px` 一致）。
 *
 * ⚠️ 只覆盖 `.site-lib-modal__close`，不改同一条规则里的
 * `.notes-all-modal .notes-all-modal__close`（阅读器的「查看全部」弹窗保持原样）。
 *
 * 移动端（≤767px）面板宽度变 100%，右沿固定离视口 20px，所以 right = 20 + 30。
 */
.site-lib-modal__close {
  position: fixed;
  top: calc(20px + (100vh - 40px - min(800px, 100vh - 40px)) / 2 + 16px);
  right: calc(20px + (100vw - 40px - min(75vw, 1080px)) / 2 + 30px);
  z-index: 3;
}
@media (max-width: 767px) {
  .site-lib-modal__close { right: 50px; }
}

.site-lib-modal__close:hover,
.notes-all-modal .notes-all-modal__close:hover {
  transform: rotate(-90deg);
}

/*
 * 面板里的彩色光晕 —— 「在线咨询」弹窗的背景渐变色就是它（那边叫 `.popup__blur`）：
 * 一块 520×520 的渐变方块，`blur(208px)` 糊开、opacity .8。
 *
 * 四个弹窗共用这一个类：顶部导航的「资料库」弹窗，以及案例 / 课程 / 资料库
 * 三个阅读器的「查看全部」弹窗。它们的面板都要配 `isolation: isolate`，
 * 理由见下。
 *
 * **负 z-index 是必须的**：它是绝对定位元素，会盖在后面的普通内容之上；
 * 给 -1 之后按绘制顺序落在「面板自己的背景之后、内容之前」，正好当背景用。
 * 为此面板要建一个自己的层叠上下文，否则负 z-index 会掉到外层弹窗容器
 * （`position: fixed` + `z-index: 1000`）的背景之后，整块看不见。
 */
.modal-glow {
  position: absolute;
  z-index: -1;
  width: 520px;
  aspect-ratio: 1 / 1;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  filter: blur(208px);
  background: linear-gradient(131.19deg, #f85e76 3.025%, #a324b0 22.986%, #7807cd 45.185%, #514fd8 72.222%, #2baedf 100.153%);
  opacity: .8;
  pointer-events: none;
}

.site-lib-modal__groups {
  display: grid;
  gap: 26px;
}

/*
 * 一级目录名：做成「在线咨询」那个按钮的样子（用户要求），但**不是链接** ——
 * 仍然是个 `<h3>`，只是长得像按钮，点了没有任何行为。
 *
 * 取值照抄顶部导航「在线咨询」按钮（`.header__button.btn-gradient`）的实测计算值：
 * 内边距 10/40、圆角 100px、五段渐变、14px/400、白色。
 * 字体不用写：这个弹窗渲染在 `<header class="header">` 里，Manrope 是继承来的。
 *
 * 用 `inline-flex` 而不是 `flex`：它在 `<section>`（块级）里，inline-flex 才会
 * 收缩到文字宽度成为一颗胶囊；写成 flex 会撑满整行变成一条渐变横条。
 */
.site-lib-modal__group-name {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 10px 40px;
  border-radius: 100px;
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  background-size: 100% auto;
  background-position: left center;
  color: #fff;
  font-size: 14px;
  font-weight: 400;
}

.site-lib-modal__group-rule {
  margin: 10px 0 16px;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, .18);
}

/* 每行 4 个，与资料库阅读器那个弹窗一致；窄屏退到 2 个（名字会被挤断）。 */
.site-lib-modal__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.site-lib-modal__link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 8px 10px;
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 999px;
  color: #fff;
  text-align: center;
  text-decoration: none;
  background: transparent;
  transition: .3s;
}

.site-lib-modal__link:hover {
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
}

/* 空目录置灰但仍列出来，免得「后台明明有这个目录，前台却找不到」。 */
.site-lib-modal__link.is-empty {
  opacity: .38;
  cursor: not-allowed;
  border-style: dashed;
}

.site-lib-modal__hint {
  margin: 0;
  color: rgba(255, 255, 255, .6);
}

/*
 * 弹框顶部的 logo（原来那里是一行「进入…首页 →」链接，用户要求换成 logo）。
 *
 * **高度不得超过右上角关闭按钮的 64px**（用户明确要求）—— 关闭按钮实测 31.2×64，
 * 这里给 48px，既在限制内又跟面板的 34px 内边距同一量级。
 * 宽度 `auto`：`logo2.webp` 是 2:1，48px 高就是 96px 宽。
 *
 * 位置沿用那行链接的：顶部居中、下方 26px 间距。
 */
.site-lib-modal__logo {
  display: block;
  height: 48px;
  width: auto;
  margin: 0 auto 26px;
}

@media (max-width: 767px) {
  .site-lib-modal__panel {
    width: 100%;
    padding: 26px 18px;
  }

  .site-lib-modal__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/*
 * logo 尺寸：容器宽度改由**后台设置的显示高度反推**（2026-09-26）。
 *
 * ## 为什么是「反推宽度」而不是「直接锁高度」
 *
 * 页头 logo 的尺寸**由宽度决定**：`.header__logo` 是 `.header__brand` 这个 flex 行里的项，
 * 基线给的是「容器 `max-width` + `.header__logo img{width:100%;height:auto}`」——
 * 高度是宽度的副产品（2:1 时正好一半）。所以只要把容器的 `max-width` 按
 * 「高度 × 2」算出来，既保住了这套布局，又让高度变得可调。
 *
 * 试过、**都不能用**的两种写法（都在真机上量过）：
 *
 * | 写法 | 结果 |
 * |---|---|
 * | `img{height: var(--h); width: auto}` | 900~1200px 档页头 90 → 106（那档本来把 logo 压成 0 宽，固定高度仍造出 66px 的盒子）；1440 页头 103 → 106（固定 66 比按比例算出的 63 高 3px）；390 下 logo 80×40 → 46×23（`width:auto` 让图片固有宽度成了 flex 基准，挤压时它吃掉过多负空间） |
 * | `img{width:100%; max-height: var(--h)}` | `max-height` 会让浏览器把尺寸「解出来」（2:1 → 132），这个 132 同样进 flex 基准 → 390 下 80×40 被挤成 46×23 |
 *
 * 换成反推宽度之后，**默认值 66 算出来正好是 132 —— 与改造前那条 `max-width: 132px`
 * 一字不差**，所以默认状态下是逐像素中性的（这也是 `verify:visual` 能过的前提）。
 *
 * ⚠️ 代价说清楚：这里假设 logo 是 **2:1 横版**（推出来的宽度才是「高度 × 2」）。
 * 传一张正方形图时，容器还是 132 宽 → 图变成 132 高，页头会被顶高 ——
 * 这时把「Logo 显示高度上限」调小（比如 32）就能按比例收到合适大小。
 * 后台那行提示写的就是这个口径；`site.logoHeight` 的取值区间是 16–120。
 *
 * `--site-logo-h` 由三段外壳各注入一次（`SiteShell` / `app/wap/layout.tsx` /
 * `app/admin/layout.tsx`），默认 66px。**fallback 必须写**（`var(--site-logo-h, 66px)`）：
 * 少了它，没走到那三段外壳的渲染会让整条声明失效。
 */
.header .header__logo,
.footer .footer__logo {
  max-width: calc(var(--site-logo-h, 66px) * 2);
}

/*
 * 窄屏的**硬上限**：改造前是 80 / 64 宽（= 40 / 32 高），与上面那条取小值。
 * 用 `min()` 而不是直接写死：设置调小到 40 以下时，窄屏才跟着一起变小。
 */
@media (max-width: 600px) {
  .header .header__logo {
    max-width: min(calc(var(--site-logo-h, 66px) * 2), 80px);
  }
}

@media (max-width: 359px) {
  .header .header__logo {
    max-width: min(calc(var(--site-logo-h, 66px) * 2), 64px);
  }
}

/*
 * 图片那条 `.header__logo img{width:100%;height:auto;object-fit:contain}` 是**基线的**，
 * 这里一个字都不改 —— 上面推出来的容器宽度就是尺寸的唯一来源。
 */

/*
 * 移动端抽屉里那个 logo（`.menu__body-logo`）。
 *
 * 原先是另一个文件 `/img/logo.svg`（165×46，比例 3.6:1），靠容器的
 * `max-width:120px` + 全局 `img{max-width:100%}` 得出 120×33。
 * 现在换成站点 logo（2:1，跟着设置走），若不管它：img 上的 `width/height` 属性
 * （按 2:1 给的占位值）会被 `max-width:100%` 压宽度而高度不动 → **拉伸**。
 * 所以这里让它按高度出图：33px 高（= 改造前那个实际高度），宽度按比例来。
 */
.header .menu__body-logo img {
  height: auto;
  max-height: 33px;
  width: auto;
  object-fit: contain;
}

/*
 * WAP 顶栏。**不能直接用 `--site-logo-h`**：`.topbar` 高 64px，减掉
 * `padding: 13px … 12px`，内容盒只有 **39px** —— 66px 的 logo 会把顶栏撑破，
 * 用 `max-height: 44px` 夹也不够（44 > 39）。
 * 改成 `min()` 夹到今天的 40px（顶栏 logo 宽 80px ÷ 2:1），只有把设置调到 40 以下
 * 才会跟着变小 —— 与「锁高度」同一套思路，只是这里多一个容器的硬上限。
 *
 * 选择器带 `.wap` 前缀（`app/wap/layout.tsx` 的根 `<div className="wap">`）：
 * `shell-fixes.css` 排在 `prototype-wap.css` **前面**，同权重（`.brand img`）会输给它。
 */
.wap .brand img {
  height: min(var(--site-logo-h, 66px), 40px);
  width: auto;
  object-fit: contain;
}

/*
 * 页脚那三条文案现在是**纯文本**，不再是指向 /course、「#」、/community 的链接
 * （用户要求）。基线里给 `.footer__menu a` 的排版没有对应元素可依附了，
 * 这里照抄同一份到 `.footer__menu-text`，静态观感逐像素不变：
 * inline-block、上下 8px 内边距、白色、14px、行高 133.333333%。
 *
 * 只去掉 `transition` 与 `:hover`（没有链接了就不该有悬停变色）。
 * 本文件先加载，但 `.footer__menu span` 在基线里本来就没有对应规则，不存在竞争。
 */
.footer__menu-text {
  display: inline-block;
  padding: 8px 0;
  color: #fff;
  font-family: Manrope, "Microsoft YaHei", sans-serif;
  font-size: 14px;
  font-weight: 400;
  line-height: 133.333333%;
}

/*
 * 阅读器三个「查看全部」弹窗（案例 / 课程 / 资料库）的面板：外观与导航那个、
 * 以及与「在线咨询」弹窗对齐。
 *
 * 基线 `app-notes.css` 里 `.notes-all-modal__panel` 写的是
 * `border: 1px solid rgba(190,185,210,.5)` + `background: rgba(8,3,20,.96)`，
 * 那是基线文件、不改，所以抬一级权重覆盖（`.notes-all-modal .notes-all-modal__panel`）。
 *
 * 边框同样改成透明而不是删掉：`box-sizing: border-box`，删掉会让内容区每边多 1px，
 * 弹窗内的网格布局就动了。
 */
.notes-all-modal .notes-all-modal__panel {
  border: 1px solid transparent;
  background: #05010d;
  /* 给 `.modal-glow` 的负 z-index 兜一个层叠上下文，否则光晕会掉到外层容器背景之后。 */
  isolation: isolate;
}

/*
 * 案例详情页的配图。用户要求：**放在所有文案之上**（标题之前、最顶部），
 * 尺寸是板块列表卡片（264×157）的 1.5 倍 = 396×236。
 *
 * 圆角与底色照抄列表卡片的 `.case-category__thumb`（10px / #160b31），
 * 让详情页的图和列表里看到的是同一种观感。
 *
 * 宽度写 `min(396px, 100%)` 而不是死值 396：正文列在窄屏不足 396px，
 * 写死会横向溢出。比例用 `aspect-ratio` 保持 1.68:1，高度自动。
 */
.case-cover {
  margin: 0 0 26px;
}

.case-cover img {
  /* 只管「多大」和「怎么裁」；display / max-width / 圆角 / 上下外边距
     都交给基线 （8px 圆角、24px 外边距）——
     正文里所有图都是那个样子，配图不该是例外。 */
  width: min(396px, 100%);
  aspect-ratio: 1.68 / 1;
  object-fit: cover;
  background: #160b31;
}

/*
 * 首页 hero 那张大图的圆角（用户指定 16px）。
 *
 * 选择器与基线 `prototype-home.css` 的 `.hero .hero__art img` 一模一样：基线只声明
 * 了 width / object-fit，**没有 border-radius**，所以这里不存在优先级之争，
 * 沿用同一条选择器只是为了让「改在哪儿」一眼可查。
 *
 * 同时覆盖两种状态，因为它们共用这一个元素：
 * - 没配轮播 → `HomePageHero` 的内置 `<img src="/img/333.png">`；
 * - 配了轮播 → `HeroSlides` 里的 `<img>`（可能被 `<a>` / `<Link>` 包着，圆角在 img 上，不受影响）。
 *
 * 为什么改在 `shell-fixes.css` 而不是基线：基线 CSS 是像素对照的基准，受保护不许动。
 * 像素影响：home 的 `.hero__art` 已在 `tools/visual/routes.mjs` 里遮罩（内容来自 CMS），
 * 所以这条改动不会让像素对照变色。
 *
 * `hero__art` 只在 `前端原型/index.html` 出现（全站 grep 过），因此不用再按页面收窄。
 */
.hero .hero__art img {
  border-radius: 16px;
}

/*
 * 价格区每格「查看真实案例」按钮上面那摞叠图（该案例板块的前 4 张配图）。
 *
 * 叠法照抄「海量资料」左边那排头像（`style.min.css:527-534` 的 `.hero__user`）：
 * 一行 flex、靠 `:not(:first-child)` 的负外边距叠起来。**形状按用户要求用方形**，
 * 不是头像那种圆形。
 *
 * **总宽正好等于按钮宽度**（用户明确要求），靠两件事凑成，缺一不可：
 *   ① 这一摞与按钮在**同一个包含块**里 —— `.prices__cell-items` 的内容盒
 *      （实测桌面 269px、移动 148px），两者的 `width: 100%` 指的是同一个宽度；
 *   ② 按钮本身要撑满：`.prices__cell .hero__button .btn-gradient` 加了 `width: 100%`。
 *      它原来是 `inline-flex` 的收缩宽度（桌面只有 246px，比包含块窄 23px），
 *      不撑满的话这一摞会比按钮宽。
 *
 * 4 张怎么分：`flex: 1 1 0` 让每张均分「包含块宽 + 3 处重叠」，
 *   每张宽 = (W + 3×12) / n，n 张总宽 = W，**n 少几张也是整宽**
 *   （有的板块配图不足 4 张时不会短一截）。
 *
 * 深色描边是叠图必需的：几张照片直接叠在一起会糊成一片，看不清是几张。
 * 用页面底色而不是白色，是为了不喧宾夺主。
 */
.prices__case-stack {
  display: flex;
  align-items: center;
  width: 100%;
  margin: 0 0 10px;
}

.prices__case-stack img {
  flex: 1 1 0;
  min-width: 0;
  height: 48px;
  border-radius: 4px;
  object-fit: cover;
  background: #160b31;
  box-shadow: 0 0 0 2px rgba(5, 1, 13, .9);
}

.prices__case-stack img:not(:first-child) {
  margin-left: -12px;
}

/* 按钮撑满格子内宽，好让上面那一摞有同一个宽度基准。 */
.prices__cell .hero__button .btn-gradient {
  width: 100%;
  max-width: 100%;
}

/*
 * 用户要求：收掉按钮下面多出来的留白。
 *
 * 基线 `.prices__cell` 是 `padding: 20px 20px 80px`（桌面），按钮下面实测空出 100px；
 * 加了那一摞叠图之后这段空白显得更空。这里把下内边距砍到 40px。
 * 移动端基线本来就是 40px，一并不动（同一条规则覆盖两端，值一样）。
 *
 * 选择器必须带 `.prices__table` 这一层：本文件是**先**加载的，同优先级压不过基线
 * （第一版写成 `.prices__cell`，优先级 0-1-0 与基线相同、位置在后 → 静默失效，
 * 实测留白仍是 100px 才发现）。这也是本文件所有覆盖都要多带一层类的原因。
 *
 * 30px 是用户给的数。**它只是下限**：同一行里格子会被拉齐到行高，内容矮的格子
 * 按钮下面还会多出余量（实测 30–55px）。要每一格都恒等于 30px，得把按钮用
 * `margin-top: auto` 钉到格底 —— 那样矮格子里按钮会离开它上面那摞图，不好看，没做。
 */
.prices .prices__table .prices__cell {
  padding-bottom: 30px;
}

/* 悬停时抬到前面来，看得清是哪一张。 */
.prices__case-stack img:hover {
  position: relative;
  z-index: 1;
}

/*
 * 课程弹窗的标题。
 *
 * 弹窗的外壳与卡片都复用现成的全局类名（`.site-lib-modal*` 是导航弹窗那套、
 * `.notes-all-modal__*` 是案例阅读器「查看全部」那套，都在本文件里且全站加载），
 * 只有这个标题是新的 —— 阅读器那个弹窗的 `h2` 样式挂在 `.notes-all-modal__panel h2` 下，
 * 管不到 `.site-lib-modal__panel`。取值照抄那一条，观感一致。
 */
/*
 * 面板本身**去掉那条 1px 的边**（用户 2026-10-19：「弹出框没有边框」）。
 *
 * 基线 `.site-lib-modal__panel` 留的是 `border: 1px solid transparent` —— 目的是
 * 用一条看不见的边占住 2px 宽度，让内容区尺寸不因删边而变。这里删边之后把
 * padding 从 34 补到 35（border-box 下和原来的内容起点/内容宽完全等价），
 * 所以「去边框」不会让框内布局动 1px。
 */
.site-course-modal {
  border: 0;
  padding: 35px;
}

/* 标题与 logo 整行居中（用户 2026-10-19 要求）。 */
.site-course-modal__title {
  margin: 0 0 24px;
  color: #fff;
  font-size: 28px;
  text-align: center;
}

/*
 * 标题里那个 logo，放在「全部课程」正前面（用户要求）。
 *
 * 高度**不超过标题字号 28px**（用户要求「不要超过字体高度」）—— 这里给 24px，
 * 既是硬上限内的，视觉上也跟 28px 的粗体字同一量级。
 * `logo2.webp` 是 2:1，24px 高就是 48px 宽（与资料库弹框顶部用的是同一张，品牌标一致）。
 *
 * `vertical-align: middle` 必须有：行内图片默认坐在**基线上**，比文字明显偏低，
 * 而这里要的是"跟文字并排居中"。`alt=""` 是因为标题里已经有「全部课程」四个字，
 * 图再报一遍名字，读屏会念成「全部课程 全部课程」。
 */
.site-course-modal__title-logo {
  height: 24px;
  width: auto;
  vertical-align: middle;
}

/*
 * 首页 hero 的卡片图现在可能是 `<button>` 而不是 `<a>`（Ai课程那张打开课程弹窗）。
 * 锚点自带手型光标、背景透明、无边框；按钮默认三样都有，要抹掉才对得上。
 *
 * 卡片上原有一行文字按钮（`.hero__button` / `.hero__btn-text`）与它的角标，
 * 用户要求去掉，所以这里只剩下**图那一层**的复位。
 */
.hero__cards .hero__card-media {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/*
 * 正文里「段落对齐」要能带动图片。
 *
 * 基线 `app-notes.css` 给正文图片写的是 `display: block; margin: 24px auto` ——
 * **`margin: auto` 会压过 `text-align`**：块级元素加自动左右外边距之后，
 * 父元素的对齐对它一点作用都没有。所以后台把段落设成左/右对齐时，
 * 图片会一动不动地留在中间。
 *
 * 这里只改**设了对齐的段落**里的图：换成行内块、清掉左右外边距，于是跟随 `text-align`；
 * 上下 24px 保留（`margin-inline` 不碰纵向）。没设对齐的段落不受影响，
 * 仍按基线居中 —— 与改造前完全一致。
 *
 * 用 `[style*="text-align"]` 而不是带具体值的写法：白名单会把样式串规范化，
 * 而且 left / center / right 三种都要覆盖。
 *
 * 后台编辑器里有一条**对称的规则**（`admin-fixes.css` 的 `.ok-img`），两边要一起改。
 */
.notes-source-content p[style*="text-align"] img,
.notes-source-content h2[style*="text-align"] img,
.notes-source-content h3[style*="text-align"] img {
  display: inline-block;
  /* 行内块默认按基线对齐，行高的下沿会在图下面垫出一条缝；贴底就没有了。 */
  vertical-align: bottom;
  margin-inline: 0;
}

/*
 * 正文图片的链接（后台「图片链接」功能，存成 `<a href><img></a>`）。
 * 链接默认带下划线，包在图片上会画出一条横线 —— 去掉，颜色也不改。
 */
/*
 * hero 右侧卡片下面那 **2 个纯文案滚动框**（用户要求：只有文字、从下往上滚）。
 *
 * 它们是 `.hero__cards`（2 列栅格）的最后两个子项，所以自然落在第 3 行、各占一格。
 * 用新的 `.hero__notice` 而**不复用 `.hero__text`**：卡片那套是
 * `transform: scale(.65)` + `margin: -17.5%`，跟着套上去文案会被缩到 65%，
 * 12px 的字缩完只剩 8px，读不了。
 *
 * 高度受一条硬约束（用户要求）：**框的底边不得超过左侧那句副文案的底边**
 * （`.hero__art-copy--sub`）。实测 1440 下副文案底边 617px、4 张卡片视觉底边 428px，
 * 中间剩 189px；这里把整框（含边框与内边距）压在 150px 以内，第 3 行排完仍在 617 以内。
 * 视口高度给得比框小，是为了让滚动"露一半"、一眼看得出它在动。
 */
.hero__notice {
  /*
   * **必须给 order**：基线把 4 张卡片排成了 `order: 1..4`（见 `prototype-home.css`），
   * 而默认 `order: 0` 会排在它们**前面** —— 两个框会跑到第 1 行去（第一版就是这样）。
   * 5 > 4，于是它们落在卡片之后的第 3 行；两个同为 5 时按 DOM 顺序，左、右各一个。
   */
  order: 5;
  min-width: 0;
  min-height: 0;
  /*
   * 让开卡片的下沿。卡片的 `transform` 只改渲染、不改布局，所以它们的**视觉盒**
   * 比所在栅格行低 8–21px（实测 1440 差 21.1、1280 差 12.3、1100 差 2.4）。
   * 不留这段距离，框会压在卡片上。
   */
  margin-top: 24px;
  /*
   * 高度**随宽度自适应**，目的是守住「框底不超过左侧副文案底边」这条要求。
   *
   * 试过让栅格自己算（第 3 行给 `1fr` / `minmax(0,1fr)`）——不行：栅格容器的高度本身
   * 由内容决定，`fr` 在没有确定高度时退化成 `auto`，框里的文案反而把容器顶高，
   * 底边掉得更低（实测全宽度超出 25–90px）。
   *
   * 于是退回来按实测拟合。约束可以化简成一句话：**卡片列不得高于左侧艺术列**
   * （左侧内容是垂直居中的，卡片列更高时艺术列被居中，副文案的底边就跑到容器底边之上）。
   * 各宽度下「卡片两行之后还剩多少」实测为：992→58px、1024→97、1200→120、1280→143、
   * 1440→186。`28vw - 230px` 在这些点上都在上限以内（992→48、1200→106、1440→173），
   * 上下再用 clamp 卡住：窄屏至少 56px（还能看到约 3 行），宽屏最多 180px。
   */
  height: clamp(56px, calc(28vw - 230px), 180px);
  box-sizing: border-box;
  padding: 12px 16px;
  border: 1px solid rgba(180, 180, 190, .32);
  border-radius: 16px;
  background: rgba(255, 255, 255, .02);
  overflow: hidden;
  /* 标题固定在上、滚动区吃掉剩下的高度。 */
  display: flex;
  flex-direction: column;
}

/*
 * 框配了链接时整个框变成 `<a>`（用户要求：整框一条链接）。这里只补"当链接用"的
 * 那点差异：颜色继承、去掉下划线、hover 给个反馈。尺寸/边框/`order` 仍由上面那条管。
 *
 * 顺带一提，框里那摞字是**在动**的：整框可点就意味着它没法被选中复制，
 * 这是"整框一条链接"这个选择的必然代价（用户已知）。
 */
.hero__notice--link {
  color: inherit;
  text-decoration: none;
  transition: border-color .2s ease, background-color .2s ease;
}

.hero__notice--link:hover {
  border-color: rgba(180, 180, 190, .6);
  background: rgba(255, 255, 255, .05);
}

/*
 * 框顶部那行标题（「企业交付案例」「成功就职Ai岗位学员」）：**居中、品牌渐变字、
 * 不参与滚动**（用户要求）。它在 `.hero__notice-viewport` 外面，所以滚的是它下面那一摞。
 *
 * 渐变铺在**内层 span** 上而不是 `<p>` 上：`<p>` 是块级、会撑满整行，
 * 渐变按整行宽铺开、而文字只占中间一小段，看到的就只剩渐变中段那一种颜色
 * （课程弹窗的分类名、hero 的按钮文字都踩过这个坑）。
 */
.hero__notice-title {
  flex: 0 0 auto;
  margin: 0 0 8px;
  text-align: center;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.3;
  /* 标题比框宽时按省略号截断，不要撑破框。 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hero__notice-title span {
  display: inline-block;
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  background-size: 100% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.hero__notice-viewport {
  /* 吃掉标题之后剩下的高度；`min-height: 0` 让它在空间不够时能被压扁。 */
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

/*
 * 文案**上下各滚一遍**：DOM 里那串文案是复制了一份的（见 `HomePageHero.tsx`），
 * 所以滚到 -50% 正好接回自己的开头，接缝处不跳。
 * 用 `linear`：匀速才像滚动字幕，带缓动会一冲一停。
 *
 * 时长 22s 是**配合换行调过的**：每条文案从半行变一至两行，轨道高度几乎翻倍
 * （实测第 1 框 542→776px、第 2 框 652→1120px），还按原来的 14s 走就等于速度翻倍
 * （27.7 / 40.0 px/秒），来不及看。换 22s 后回到 17.6 / 25.5 px/秒，
 * 与换行前的 19.4 / 23.3 同档。
 *
 * 一个时长管两个框，而两框的行数与换行情况不同，所以速度本来就差一档
 * （改动前后都如此）。想调快慢改这一个数字就行。
 */
.hero__notice-track {
  display: flex;
  flex-direction: column;
  gap: 8px;
  animation: hero-notice-roll 22s linear infinite;
}

.hero__notice-line {
  margin: 0;
  color: rgba(255, 255, 255, .82);
  font-size: 13px;
  line-height: 1.5;
  /*
   * 一行放不下就**换行**，不再省略号截断（用户要求）：后台那行文案按"一条一案例"
   * 写，常常 20 个字以上，截掉尾巴等于把关键信息丢了（`-4周-源码交付` 全在尾巴上）。
   * 换行后每条占几行不定，框的高度是固定的，多出来的靠滚动看。
   *
   * `overflow-wrap: break-word` 是给长串英文/链接用的：中文本来就能在两字之间断，
   * 一串没有空格的英文不会断，会把框顶宽。
   */
  white-space: normal;
  overflow-wrap: break-word;
}

@keyframes hero-notice-roll {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}

/* 系统开了「减弱动态效果」就不滚：这类无限动画对前庭敏感的人不友好。 */
@media (prefers-reduced-motion: reduce) {
  .hero__notice-track {
    animation: none;
  }
}

/*
 * 课程正文第一张表的第一列（「✅[适合人群]」）：**每行不超过 10 个字**（用户要求，
 * 先是 8 个字，随后放宽到 10）。
 *
 * 放在版式而不是正文里：内容里的 `width` 会被服务端白名单剥掉（那里只放行
 * `text-align` 与 `span` 上的 `font-size`），而且列宽本来就是版式的事。
 *
 * `10em` 正好是 10 个字：中日韩字符的宽度就是 1em，与字号无关，所以这条不会因为
 * 正文字号变化而失效。超出 10 个字的浏览器自己换行 —— 中文可以在任意两字之间断行，
 * 不需要 `word-break` 之类额外配置。
 *
 * 只作用于**课程阅读器**（`--course` 钩子）：资料库与案例的文章里也有表格，
 * 它们的第一列未必是这种短标签，全局改会波及。
 */
.notes-source-content--course > table:first-of-type tr > *:first-child {
  /*
   * `width` 按**内容盒**算。不加这句时它套在边框盒上，单元格的左右内边距（共 28px）
   * 会从这 10em 里再吃掉两个字的位置 —— 实测只剩 8 个字/行。
   */
  box-sizing: content-box;
  width: 10em;
}

.notes-source-content a:has(> img) {
  text-decoration: none;
  color: inherit;
}

/*
 * 首页 hero 四张卡片的边框——用户要求去掉。
 *
 * 基线 `prototype-home.css` 里 `.hero .hero__text` 是 `border: 1px solid rgba(180,180,190,.58)`
 * 加 `border-radius: 24px`。这里只清边框：**圆角与内边距都不动**（圆角没了边框本就看不出来，
 * 内边距去掉会改卡片内部版心）。选择器多带一层 `.hero__cards` 提高优先级压过基线那条。
 *
 * 像素影响**实测**（不是估算）：home 桌面 3.53%、移动 8.93%，移动端整页还矮了 3px ——
 * 边框没了卡片的盒子尺寸跟着变，下面整段内容都挪位，所以远超 0.5% 阈值。
 * 这不是回归而是用户要的改动，按本仓库的判据（设计变更 / CMS 内容都不该拿原型量）
 * 把 `.hero__cards` 整块移出像素对照，见 `tools/visual/routes.mjs`。
 */
.hero .hero__cards .hero__text {
  border: 0;
}

/*
 * 用户要求：右侧四张小图的**顶部**与左侧主标题里那截强调文字（`.accent`，「Ai入门宝典」）
 * 的顶部对齐。
 *
 * 只在**并排布局**下有意义：≤991px 时 `prototype-home.css` 把 `.hero__body` 收成一列，
 * 卡片整块落到大图下面，跟主标题不在同一行，谈不上对齐。
 *
 * 这件事比看上去麻烦。基线那一套是「先按原尺寸排版、再 `transform: scale(.65)` 缩小、
 * 用 `margin: -17.5%` 把缩掉占的位收回来」，所以**布局盒与看得见的盒子不是同一个盒子**。
 * 三层偏差叠在一起，处理方式各不相同：
 *
 * **① 整块被垂直居中（最大，97px）**。基线 `.hero .hero__cards` 是 `align-content: center`；
 * 卡片列被 `.hero__body` 的 `align-items: stretch` 拉到和左侧一样高（1440 下 531px），
 * 而四张卡的总高只有 336px，于是上下各留 97.26px。→ `align-content: start`。
 *
 * **② 缩放基准在「中心」（十几像素，且随宽度变）**。居中缩放的顶边会往下让出
 * `17.5% × 卡片自身高`，那个高度又随文字换行变 —— 实测 1060 与 1080 之间有个
 * 5px 的**台阶**，正是描述文字换行造成的。按视口写死一串阶梯值会栽在这上面：
 * 后台一改描述文字，台阶就挪位。
 * → 把缩放基准改成**上边**（`transform-origin: top center`），缩放的顶边不再让位，
 *   看得见的顶 = 布局盒顶，与卡片多高、文字几行彻底无关。
 *
 * **③ 负边距挖掉的 17.5% × 列宽（几十像素，随宽度线性）**。把 ② 换掉之后，
 * 布局盒顶落在容器顶上方 `17.5% × 列宽` 处，把它推回来即可。
 * 列宽本身也是算出来的而不是查出来的：**列宽 = 0.25 × 视口宽 − 30.5px**
 * （992/1024/1060/1080/1100/1120/1200/1280/1366 九个点全部对上，是精确式），
 * 于是 `17.5% × 列宽 = 4.375vw − 5.34px`。
 *
 * **④ 强调文字自己的行高留白（21px）**。`.accent` 是 `font-size: 3.2em` 的 inline-block，
 * `line-height: 1.45`，**字形顶在盒顶下方 21px**（用基线探针实测，不是按字体度量推的）。
 * 用户说的「文字顶部」是看得见的那条边，所以这 21px 也要算进去。
 * 它随字号变，而字号是 `clamp(16px, 1.8vw, 26px) × 3.2em` → 又是 `1.5vw`。
 *
 * ③ + ④ 合起来就是下面那个 `5.875vw − 21.33px`；1444 起容器与字号双双到顶，用定值。
 * 逐宽度实测（图顶 − 字形顶）：992–2560 全部落在 **±1.7px** 内，绝大多数 < 1px。
 *
 * **四张卡都要加，不能只加顶排两张**：只加顶排的话它单独下移几十像素，
 * 而两排本来就贴得极紧（负边距压过行距，原始间隙只有 0.06px），
 * 顶排的文字会立刻被下面那排的图盖住。四张同位移量 ⇒ 行距逐像素保持原样。
 *
 * 顶排是 DOM 里的第 2、3 项（基线用 `order` 排过位，见 `prototype-home.css`），
 * 但这里四张规则相同，所以不用管顺序。
 */
@media (min-width: 992px) {
  /* 选择器多带一层 `.hero__body`：`shell-fixes.css` 排在最前，
     同权重会输给基线那条 `.hero .hero__cards`（这个坑本文件里踩过多次）。
     只写 `.hero .hero__cards` 的话 `align-content: start` 会被静默盖掉。 */
  .hero .hero__body .hero__cards {
    align-content: start;
  }

  .hero .hero__cards .hero__text {
    transform-origin: top center;
    transform: translateY(calc(5.875vw - 21.33px)) scale(.65);
  }
}

/* 1444 起容器吃到 `max-width`（1440）、强调文字字号吃到 `clamp` 上限（1444），用定值。 */
@media (min-width: 1444px) {
  .hero .hero__cards .hero__text {
    transform: translateY(63.22px) scale(.65);
  }
}

/*
 * 首页 hero 轮播（后台配了轮播项时才挂载，见 `components/site/HeroSlides.tsx`）。
 *
 * 外层这层 `.hero__slides` 只为挂「鼠标悬停暂停」而存在，但它的加入让标题 / 图 /
 * 副文案不再是 `.hero__art` 的直接 flex item —— 基线里 `.hero .hero__art` 的
 * `display: flex; flex-direction: column; gap: 10px; align-items: center` 与
 * text-align 就都落不到它们身上了。所以这里把同一套排版复制一遍，
 * 保证「配了轮播」和「用内置」两种状态的版面完全一致。
 */
.hero__slides {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-width: 0;
  text-align: center;
}

/*
 * 轮播图外面那层：只做「左右按钮的定位锚」。`display: block` 让它的盒子贴着图，
 * `.hero__slides` 里仍旧是一个 flex item —— 外面看到的排版与加这层之前一致。
 */
.hero__slide-media {
  position: relative;
  display: block;
  line-height: 0;
}

/*
 * 左右切换按钮（用户要求：半透明）。绝对定位到图片两侧、垂直居中。
 *
 * 半透明用 `rgba(255,255,255,.14)` 打底而不是纯黑：hero 图本身偏暗紫，
 * 黑底按钮在暗部会糊成一块，白底半透明反而分得清；悬停时提到 .26 并加一点描边。
 * 图标复用站内已有的 `chevron.svg`（一个向下的 V），靠 `rotate` 转成左右。
 * `backdrop-filter` 让按钮下的图虚化，图标更清楚。
 */
.hero__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 50%;
  background: rgba(255, 255, 255, .14);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  cursor: pointer;
  opacity: .8;
  transition: background .2s ease, opacity .2s ease, border-color .2s ease;
}

.hero__nav:hover {
  background: rgba(255, 255, 255, .26);
  border-color: rgba(255, 255, 255, .34);
  opacity: 1;
}

/*
 * 选择器必须带 `.hero .hero__art`：基线 `prototype-home.css` 有一条
 * `.hero .hero__art img { width: 100%; height: auto; transform: none }`（0,2,1），
 * 按钮里的图标也是 `.hero__art` 的后代，**同样会被它命中** ——
 * 只写 `.hero__nav img`（0,1,1）的话宽度被拉成 100%、旋转也被 `transform: none` 抹平
 * （实测：图标 34px 宽、变换 none，看着就是两个向下的 V）。
 */
.hero .hero__art .hero__nav img {
  width: 14px;
  height: auto;
  /* 基线那条还会给 16px 圆角（给大图的），小图标要抹掉。 */
  border-radius: 0;
  /* chevron.svg 是「向下」的 V：顺时针 90° 指向左，逆时针 90° 指向右。 */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
}

.hero__nav--prev {
  left: 10px;
}

.hero .hero__art .hero__nav--prev img {
  transform: rotate(90deg);
}

.hero__nav--next {
  right: 10px;
}

.hero .hero__art .hero__nav--next img {
  transform: rotate(-90deg);
}

/* 窄屏按钮小一点、贴边一点，别吃掉图。 */
@media (max-width: 600px) {
  .hero__nav {
    width: 30px;
    height: 30px;
  }

  .hero__nav--prev { left: 6px; }
  .hero__nav--next { right: 6px; }
}

/*
 * 定位点：**圆角方形**，宽是「当前这个圆的直径」的 3 倍（用户要求），
 * 高度仍是原来圆点的直径 8px —— 于是 24×8 的一条短横。
 *
 * 圆角取 3px 而不是 4px（=高度一半，那就成了胶囊形）：用户说的是「圆角方形」。
 * 想要更圆就把这一条往上调，4px 就是全圆角。
 *
 * `is-active` 原来带 `transform: scale(1.2)`，这里**去掉了**：放大 1.2 倍就
 * 不再是"直径 3 倍"了（激活那条会宽出 4.8px，一排点看起来参差不齐）。
 * 选中状态改用亮度区分——底色从 35% 白提到纯白，对比够，也不再牵动排布。
 */
.hero__dots {
  display: flex;
  gap: 8px;
  margin-top: 2px;
  /*
   * 换行 + 居中：短横比圆点宽得多（24px vs 8px），轮播张数一多这一行会长得很快
   * —— 11 张时 344px，已经超过窄屏（390px 视口）下艺术列那 358px 的宽度。
   * 换行后哪怕几十张也不会把版面顶出去。
   */
  flex-wrap: wrap;
  justify-content: center;
}

.hero__dot {
  width: 24px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: rgba(255, 255, 255, .35);
  cursor: pointer;
  transition: background .2s ease;
}

.hero__dot:hover {
  background: rgba(255, 255, 255, .6);
}

.hero__dot.is-active {
  background: #fff;
}

/*
 * 页脚要盖在「背景图带」上面。
 *
 * `.main__image` 是 `position: absolute`、`height: auto`（高度由那张 100% 宽的底图撑出来，
 * 1440 屏下 1366px），**不随页面内容收缩**。企业入口页的 main 只有 1289px 高，
 * 这条背景带就多出 77px 压到页脚上；而页脚是 `static` —— 定位元素一律画在 static 元素
 * 之上，所以页脚顶部被盖住了。实测（用户反馈「页脚被盖住」）：
 * 页脚顶部 20~70px 处 `elementFromPoint` 取到的最上层元素是背景图的 `<img>`，
 * 首页同一位置取到的是 `FOOTER.footer`（首页内容比背景带高，本来就不重叠）。
 *
 * 给页脚一个定位与层级即可。别的页面不受影响 —— 那边背景带没伸到页脚。
 * 基线 `.footer` 只写了 `border-top`，没有 `position`，所以本文件先加载也不会被覆盖。
 */
.footer {
  position: relative;
  z-index: 1;
}

/*
 * 「在线咨询」弹窗的样式**副本**（逐字抄自 `style.min.css:1543-1865`）。
 *
 * 为什么要抄一份：这个弹窗是**外壳组件**（`ConsultPopup` 挂在 `SiteShell` 上，每页都有），
 * 而它的样式全部来自 `style.min.css` 的 `.popup*` 那一套 —— 但那份样式表**不是每页都加载**：
 * `PrototypeStyles` 会按当前页的清单停用其它样式表，而 `/about` 与 `/cooperation`
 * 的原型只带自己那两份（`prototype-about.css` / `prototype-st.css`），于是这两页上
 * 弹窗是**裸的**（实测：`display: block`、面板撑满 1440 宽；正常是 385×776 居中）。
 *
 * 本文件固定排在所有原型样式**之前**，所以：
 * - 带 `style.min.css` 的页面：同权重、后加载的那条赢，观感零变化；
 * - 不带的页面：这一份兜底。
 *
 * 只去掉了登录/注册弹窗专用的 `.registration-popup*` / `.popup__aside` /
 * `.popup__main` / `.password-eye*`（咨询弹窗用不到）。
 * **两处要一起改**：`style.min.css` 那边动了，这里也得跟着动 ——
 * 与 `featured.css` 是同一类取舍。
 */

/*
 * 弹窗**继承来的基准**也要钉死。
 *
 * 抄下来的那批规则全是 px，没有 rem/em，但弹窗里还有一批**没有显式字号**的元素
 * （字段报错行、说明文字…）走的是继承 —— `style.min.css` 给 `body` 设的是
 * `14px / 135% / Manrope`，而 `/about` `/cooperation` 不加载那份表，body 就是浏览器
 * 默认的 16px。实测两边的弹窗因此不一样高：首页 385×776，about 385×865。
 *
 * 钉在弹窗根上即可。带 `style.min.css` 的页面本来继承的就是这个值，零变化
 * （`.consult-popup` 是类选择器，比 `body` 的元素选择器权重高，但**值相同**）。
 */
.consult-popup {
  font-family: Manrope;
  font-size: 14px;
  line-height: 135%;
}

/*
 * 弹窗子树的两块**基础**，同样抄自 `style.min.css`，同样限定在 `.consult-popup` 内：
 *
 * 1. 复位 —— 那份表开头是 `* { margin: 0; padding: 0; border: 0 }`。缺了它，
 *    弹窗里的 `<p>` 带着 UA 默认的 `margin: 1em`（实测 `.popup__body-att` 多出 10px），
 *    提交按钮带着 UA 默认的 `padding: 1px 6px` 与边框。
 *    **不能把这条写成全局**：`/about` `/cooperation` 的原型本来就是照"没有这条复位"
 *    画的，全局加上会把整页像素改掉。
 * 2. `.btn-gradient` 的按钮基础 —— 同上，`/about` 上没有这个类，
 *    弹窗的提交按钮因此是裸的（实测 padding 1px 6px、没有渐变）。
 *
 * 顺序有讲究：这两条要在下面那一整套 `.popup*` **之前**，否则同权重会反过来
 * 把弹窗自己的内边距（`.popup__body { padding: 56px 26px }` 等）清零。
 */
.consult-popup,
.consult-popup * {
  margin: 0;
  padding: 0;
  border: 0;
}

.consult-popup .btn-gradient {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 40px;
  border-radius: 100px;
  color: #fff;
  font-family: Manrope;
  font-size: 14px;
  font-weight: 400;
  line-height: 133.333333%;
  letter-spacing: .03em;
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  background-size: 100% auto;
  background-position: left center;
  transition: all .3s ease 0s;
}

/*
 * 弹窗里的 `select` 与 `textarea` **本来就没人管**。
 *
 * `style.min.css` 只写了 `.popup__body-form-area input`（1632 行），
 * 同一组里的 `select` / `textarea` 一条规则都没有 —— 于是它们保持浏览器默认：
 * 白底黑字，而且 `textarea` 没有 `width: 100%`，比别的输入框窄一截、位置也缩进，
 * 整段「需求描述 + 勾选框」看着是错位的（三页都一样，首页也不例外）。
 *
 * 这里照 **input 那条规则**补齐（同边框、同底色、同内边距、同字号），
 * 只多两处必要的差别：`textarea` 给最小高度与可拖拽、下拉里的 `option`
 * 显式给深色底（否则系统菜单会白字白底看不见）。
 *
 * 顺带钉住字号：`/about` `/cooperation` 的原型样式表各有一条
 * `button, input, select, textarea { font: inherit }`（`prototype-about.css:33`、
 * `prototype-st.css:33`），会把控件字号抬到继承值 —— 显式给 12px 之后三页一致。
 */
.consult-popup .popup__body-form-area select,
.consult-popup .popup__body-form-area textarea {
  width: 100%;
  border: .7px solid rgba(255, 255, 255, .3);
  border-radius: 5px;
  background: rgba(255, 255, 255, .1);
  padding: 6px 10px;
  color: #fff;
  font-family: Manrope;
  font-size: 12px;
  font-weight: 400;
  line-height: 200%;
}

.consult-popup .popup__body-form-area select option {
  background: #0b0518;
  color: #fff;
}

.consult-popup .popup__body-form-area textarea {
  min-height: 72px;
  line-height: 1.7;
  resize: vertical;
}

/*
 * 「在线咨询」弹窗（2026-09-18 改版，用户要求）：
 * - **宽度 ×1.5**：基线 `.popup__body` 是 `min(100% - 30px, 385px)`，这里 385 × 1.5 = 578。
 *   挂一层 `.consult-popup` 提特异性 —— 本文件在 `style.min.css` 之前加载，同分要输。
 *   居中不用管：`.popup` 是 `position: fixed` + `align-items: center`，改宽度不影响。
 * - 内容是**两张二维码 + 四行联系方式**（原来是表单，已整块去掉），版式规则写在下面。
 */
.consult-popup .popup__body {
  width: min(100% - 30px, 578px);
  /* 内容比视口高时可滚（小屏横屏、放大字号），不然底部会被裁掉。 */
  max-height: calc(100vh - 60px);
  overflow-y: auto;
}

/*
 * 顶部标题行：LOGO + 「在线咨询」（用户要求 LOGO 在前）。
 *
 * **LOGO 高度不能超过标题**（用户明确要求）：标题是 20px / 行高 137.5% ≈ 27.5px 高，
 * 所以这里把 logo 卡在 26px，宽度按原比例（`width: auto`，logo2 是横版长条）。
 * 顶部那段距离本来由 `.popup__body-title` 的 `margin-bottom: 12px` 提供，
 * 现在标题在 flex 行里，间距挪到这一行上。
 */
.consult-popup__head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-bottom: 12px;
}

.consult-popup__head .popup__body-title {
  margin-bottom: 0;
}

.consult-popup__logo {
  height: 26px;
  width: auto;
  display: block;
}

/*
 * 二维码上面那句说明（「企业定制/Ai业务咨询」「了解Ai岗位-学习Ai课程」）：
 * **字号与样式跟顶部「在线咨询」完全一致**（用户要求）—— 就是标题那一套 20px / 600 / 居中，
 * 所以这里直接照抄 `.popup__body-title` 的值，别单独调。
 */
.consult-popup__qr-title {
  margin: 0 0 8px;
  color: #fff;
  font-size: 20px;
  font-weight: 600;
  line-height: 137.5%;
  text-align: center;
}

/*
 * 合作页 / about 页「多元项目」卡片：**选中态改用属性选择器**。
 *
 * 原型里靠 `.program-card.selected`（`prototype-st.css`：上浮 7px + 紫边）。
 * 但我们的卡片还挂着 `.reveal`，那个 `visible` 类是 `useReveal()` 命令式加的 ——
 * React 一改 `className` 就会把它冲掉，卡片连同图片一起变透明（用户报的 bug）。
 * 所以 React 那边只写 data 属性，样式在这儿认。
 *
 * 值与原型那条一致（`transform: translateY(-7px); border-color: #9a5cff`），
 * 只是把 `.selected` 换成 `[data-selected='true']`。
 */
/*
 * 特异性要压过 `.reveal.visible { transform: none }`（0,2,0，且在原型样式表里更靠后）——
 * 所以前面挂一层 `.program-grid` 提到 0,3,0：同一条规则在原型里本来是**失效**的
 * （上浮被 `transform: none` 盖掉，只剩紫边），用户要求让它真的动起来。
 */
.program-grid .program-card[data-selected='true'] {
  transform: translateY(-7px);
  border-color: #9a5cff;
}

/*
 * 合作页申请弹窗的标题行：LOGO + 「提交项目申请」**同一行、并居中**（用户要求）。
 *
 * `.dialog-head` 是 `flex + space-between`（右边是关闭按钮），所以先把左边那块的 `flex: 1`
 * 撑满，再让标题行自己居中 —— 这样标题是相对**整个弹窗**居中的。
 * 「青年成长计划」那行小标一并居中，否则上面左、下面中会歪。
 *
 * LOGO 高度 26px：标题字号 24px、行盒约 29px，26 ≤ 29 ✓（用户要求不超过文字高度）。
 */
/*
 * 让「LOGO + 提交项目申请」这一行**相对弹窗正中**居中，而不是相对"减去关闭按钮后的剩余宽度"居中。
 *
 * 基线 `.dialog-head` 是 `flex + space-between`，右边那个关闭按钮（40×40）会把标题块挤窄，
 * 于是标题的中心比弹窗中心偏左 (40 + 20gap) / 2 = **30px**（实测 -30px）。
 * 改成三列网格：左右各留 40px（= 关闭按钮宽度），标题放中间那列 —— 两边对称，中心自然对正。
 * 选择器带 `dialog` 前缀是为了压过基线（本文件先加载，同特异性会输）。
 */
dialog .dialog-head {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) 40px;
  align-items: center;
  gap: 20px;
}

dialog .dialog-head > .dialog-head__title {
  grid-column: 2;
  text-align: center;
}

dialog .dialog-head > .dialog-close {
  grid-column: 3;
}

.dialog-title-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.dialog-title-logo {
  height: 26px;
  width: auto;
  display: block;
}

/*
 * 申请弹窗接线后的三处新样式（勾选同意 / 提交失败提示 / 成功页的申请编号）。
 * 原型没有这三个元素 —— 前两个是后端接口要求的（`privacyConsent` 必填、
 * 错误码要有落点），第三个是回执里真实带的业务编号。
 */
.apply-consent {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1.5;
}

/*
 * **选择器必须带 `[type='checkbox']`，不能只写 `.apply-consent input`。**
 *
 * `shell-fixes.css` 是**固定排在所有原型样式之前**加载的（见
 * `components/site/prototype-styles.tsx` 的说明），所以同权重的规则一律输给原型那份。
 * 原型 `prototype-st.css` / `prototype-about.css` 里有一条
 * `.field input, .field select, .field textarea { width: 100%; … }` ——
 * 与本条同为 (0,1,1)，于是 `width: 16px` 被覆盖成 `100%`：
 * 勾选框横占整行，把后面的同意文案挤成竖排一列贴在边线上（实测 span 只剩 26px 宽）。
 *
 * 加上属性选择器后权重变成 (0,2,1)，与加载顺序无关，稳定生效。
 * 其余属性（height / margin / flex）原型没声明，本来就没被覆盖。
 */
.apply-consent input[type='checkbox'] {
  flex: none;
  width: 16px;
  height: 16px;
  margin: 3px 0 0;
  accent-color: #7807cd;
}

.apply-error {
  margin-top: 12px;
  color: #d93a34;
  font-size: 14px;
  line-height: 1.6;
}

.apply-receipt-no {
  color: #7807cd;
  letter-spacing: .04em;
}

/* 两张二维码并排；窄屏各占一半，等比缩。 */
.consult-popup__qrs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px;
  margin: 22px 0 24px;
}

/*
 * `position/z-index` 不是排版需要，是**为了压过那层彩色光晕**。
 *
 * `.popup__blur`（`style.min.css:1727`）是弹窗里那团紫色渐变光（`filter: blur(208px)`），
 * 它在 DOM 里排在 `.popup__body` 的**最后**、`z-index` 又是 `auto` ——
 * 于是它按文档顺序画在**所有前面兄弟之上**，二维码首当其冲：
 * 白底被染成淡紫、黑块被染成深紫，看着像"半透明"（用户 2026-09-24 报的就是这个）。
 *
 * 光晕本身是设计的一部分（弹窗那圈紫渐变就是它），**不能删**；文字被染一点无所谓，
 * 二维码被染就影响扫码。所以只把二维码这一组抬到光晕之上。
 *
 * 验收方式：弹窗里的二维码中心点上 `elementFromPoint` 应当返回 `img`，
 * 而不是 `div.popup__blur`。
 */
.consult-popup__qr {
  position: relative;
  z-index: 1;
  margin: 0;
  display: grid;
  gap: 8px;
  justify-items: center;
}

.consult-popup__qr img {
  width: 100%;
  /* 上限按大屏那档给；窄屏跟着列宽缩。 */
  max-width: 220px;
  aspect-ratio: 1;
  border-radius: 10px;
  background: #fff;
}

/*
 * 四行联系方式：左标签右内容，两列对齐。
 *
 * **整块居中、块内文字左对齐**（用户要求）：`width: fit-content` 让这一块的宽度由内容撑开
 * （标签列取最宽那个标签），`margin-inline: auto` 把它摆在弹窗中间；
 * 块内的两列仍是左对齐，所以四行的标签与数值各自成一条竖线。
 * 不给 fit-content 的话它会被拉满弹窗宽度，看着是「贴左边」而不是居中。
 */
.consult-popup__rows {
  display: grid;
  gap: 10px;
  width: fit-content;
  margin-inline: auto;
  text-align: left;
}

.consult-popup__row {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}

.consult-popup__row-label {
  color: rgba(255, 255, 255, .55);
  font-size: 14px;
}

.consult-popup__row-value {
  color: #fff;
  font-size: 14px;
  /* 长邮箱/长号码在窄屏不要顶破两列。 */
  overflow-wrap: anywhere;
}

/* 能点的（电话、邮箱）用品牌紫，和站内其它可点文字一致。 */
a.consult-popup__row-value {
  color: #c787ff;
  text-decoration: none;
}

a.consult-popup__row-value:hover {
  text-decoration: underline;
}

.popup {
   position: fixed;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   z-index: 999;
   background: rgba(0, 0, 0, .5);
   -webkit-backdrop-filter: blur(10px);
   backdrop-filter: blur(10px);
   display: grid;
   grid-template-columns: 1fr;
   align-items: center;
   padding: 30px 0;
   opacity: 0;
   visibility: hidden;
   pointer-events: none;
   transition: all .4s cubic-bezier(.075, .82, .165, 1) 0s
}

.popup.opened {
   opacity: 1;
   visibility: visible;
   pointer-events: all
}

.popup.opened .popup__body {
   transform: translateY(0)
}

.popup__body {
   width: min(100% - 30px, 385px);
   margin-inline: auto;
   position: relative;
   padding: 56px 26px;
   border-radius: 10px;
   background: #05010d;
   overflow: hidden;
   color: #fff;
   transform: translateY(-50px);
   font-family: Manrope;
   transition: all .4s cubic-bezier(.075, .82, .165, 1) 0s
}

.popup__body-title {
   font-size: 20px;
   font-weight: 600;
   line-height: 137.5%;
   text-align: center;
   margin-bottom: 12px;
   position: relative;
   z-index: 2
}

.popup__body-subtitle {
   font-size: 12px;
   font-weight: 400;
   line-height: 137.5%;
   text-align: center;
   position: relative;
   z-index: 2;
   text-wrap: balance;
   margin: 0 auto 30px auto
}

.popup__body-form {
   position: relative;
   z-index: 2
}

.popup__body-form .btn-gradient {
   margin-top: 35px;
   border-radius: 5px;
   width: 100%
}

.popup__body-att {
   font-size: 10px;
   text-align: center;
   color: rgba(255, 255, 255, .5);
   margin-top: 8px
}

.popup__body-form-area {
   margin-bottom: 15px;
   width: 100%;
   position: relative
}

.popup__body-form-area input {
   border: .7px solid rgba(255, 255, 255, .3);
   border-radius: 5px;
   background: rgba(255, 255, 255, .1);
   padding: 6px 10px;
   width: 100%;
   color: #fff;
   font-family: Manrope;
   font-size: 12px;
   font-weight: 400;
   line-height: 200%
}

.popup__body-form-area input::-moz-placeholder {
   color: rgba(255, 255, 255, .4)
}

.popup__body-form-area input::placeholder {
   color: rgba(255, 255, 255, .4)
}

.popup__body-form-help {
   display: flex;
   align-items: center;
   justify-content: space-between;
   gap: 12px;
   transform: translateY(-8px)
}

.popup__body-form-help a {
   color: rgba(255, 255, 255, .4);
   font-size: 12px
}

.popup__body-form-help label {
   display: flex;
   align-items: center;
   gap: 6px;
   color: rgba(255, 255, 255, .4);
   font-size: 12px;
   cursor: pointer
}

.popup__body-form-help label span {
   display: inline-block;
   width: 12px;
   height: 12px;
   position: relative;
   border: .7px solid rgba(255, 255, 255, .1);
   border-radius: 2px;
   background: rgba(255, 255, 255, .1)
}

.popup__body-form-help label span::after {
   content: "";
   position: absolute;
   display: block;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   background-image: url(../img/icons/check2.svg);
   background-repeat: no-repeat;
   background-size: 14px 14px;
   background-position: center center;
   transition: all .2s ease 0s;
   opacity: 0
}

.popup__body-form-help label input {
   display: none
}

.popup__body-form-help label input:checked+span::after {
   opacity: 1
}

.popup__body-close {
   position: absolute;
   top: 10px;
   right: 6px;
   width: 40px;
   height: 40px;
   z-index: 5;
   cursor: pointer;
   transform-origin: center
}

.popup__body-close img {
   display: inline-block;
   width: 30px;
   transition: all .3s ease 0s;
   height: 30px
}

.popup__blur {
   position: absolute;
   width: 520px;
   height: auto;
   aspect-ratio: 1/1;
   top: 0;
   left: 50%;
   transform: translate(-50%, -50%);
   -moz-filter: blur(208px);
   -o-filter: blur(208px);
   -ms-filter: blur(208px);
   filter: blur(208px);
   background: linear-gradient(131.19deg, #f85e76 3.025%, #a324b0 22.986%, #7807cd 45.185%, #514fd8 72.222%, #2baedf 100.153%);
   opacity: .8
}

/*
 * 首页 hero 右侧四张卡片的配图：**默认缩到 90% 且 85% 透明；鼠标移上去回到 100% 与不透明**
 * （用户要求）。
 *
 * 用 `transform: scale()` 而**不是**改宽高：`transform` 只作用于合成层、
 * **不参与布局**，所以缩放既不会撑动右侧那一块、也不影响相邻卡片的位置
 * （用户明确要求「图片的缩放不要影响整个右侧框的布局和大小」）。
 * 实测：布局盒四张恒为 421×169，视觉盒在 246↔274 之间变。
 *
 * 悬停判定挂在**图自己**身上而不是外层：四张卡的包裹元素有两种
 * （两张是 `<Link>`、两张是 `.hero__card-media` 按钮），挂在图上才能一份规则覆盖全部。
 * `:focus-visible` 同效，否则键盘走到这张卡时看不到反馈。
 *
 * ## 为什么选择器里多一层 `.hero__text`
 *
 * 那 **不是**可省的修饰。原型 `prototype-home.css` 在窄屏有一条
 * `.hero .hero__cards .hero__title > img { transform: none }`（0,3,1），
 * 而本文件固定先于原型样式表加载 —— 同权重一律输。带上 `.hero__text` 之后
 * 本条升到 (0,4,1)，与断点无关地稳定生效（窄屏那条比的是同一个 img，
 * 它会把桌面写死的 `margin-bottom` 清掉，那是它自己的用意，不冲突）。
 */
.hero .hero__cards .hero__text .hero__title img {
  opacity: .85;
  transform: scale(.9);
  transition: opacity .25s ease, transform .25s ease;
}

.hero .hero__cards .hero__text .hero__title img:hover,
.hero .hero__cards .hero__text .hero__title a:focus-visible img,
.hero .hero__cards .hero__text .hero__title button:focus-visible img {
  opacity: 1;
  transform: scale(1);
}

/*
 * 案例板块页每个分类右边的「>>more」现在是个 `<button>`（点了弹「全部案例」弹窗，
 * 用户要求；原来是跳到 `/enterprise` 的 `<a>`）。按钮默认带背景色、边框、内边距
 * 与系统 UI 字体，要抹掉才对得上锚点。
 *
 * **只能清 `background-color`，不能写 `background: none`** —— 上面那条渐变文字规则
 * （`.case-category .case-category__more`）用的正是 `background-image` 配
 * `background-clip: text`，简写会把渐变一起清掉，文字直接消失。
 */
.case-category .case-category__more {
  padding: 0;
  border: 0;
  background-color: transparent;
  cursor: pointer;
  font-family: inherit;
  line-height: inherit;
}

/*
 * 顶部导航链接的悬停：变紫 + 上移 2px。
 *
 * 原型只把这条写在 `style.min.css` 里（`@media (hover:hover)` 内），而
 * **关于页与校企合作页不加载那份样式表** —— 它们的清单是
 * `prototype-about.css` / `prototype-st.css` + `prototype-inline.css`（实测确认）。
 * 那两份里各有一条 `.footer__menu a:hover`（页脚菜单），**不是**头部导航这条。
 * 于是那两页的导航悬停没有任何反馈：实测首页 hover 到 `rgb(199,135,255)`
 * 且 `translateY(-2px)`，另两页纹丝不动。
 *
 * 导航是全站共用的组件，规则却跟着「当前页加载了哪几份原型样式表」走 ——
 * 补在这里（本文件每页都加载）。色值与位移逐字抄自 `style.min.css`，不另调。
 */
.menu ul a:hover {
  color: #c787ff;
  transform: translateY(-2px);
}

/*
 * 首页 hero 轮播图的缩放：**默认 95%、鼠标移上去 100%**（用户要求）。
 *
 * 与上面 `.hero__cards` 那四张同一套做法：用 `transform: scale()` 而不是改宽高 —
 * `transform` 只作用于合成层、**不参与布局**，所以缩放不会撑动 hero 那一块。
 *
 * ## 两个选择器上的注意点，都不能"简化"
 *
 * 1. **必须带 `.hero .hero__art` 前缀**：基线 `prototype-home.css` 有一条
 *    `.hero .hero__art img { transform: none }`（0,2,1）。只写
 *    `.hero__slide-media > img`（0,1,1）的话基线赢 —— **悬停能生效、默认态不生效**
 *    （悬停那条是 (0,2,2)，恰好压过它），实测就是这样：默认 `transform: none`、
 *    移上去反而变成 scale(1)。带上前缀后是 (0,3,2)，两侧都稳。
 *    （这条基线的坑在本文件上面 `.hero__nav img` 那段已经踩过一次。）
 * 2. **必须写 `> img` / `> a > img`**：左右切换按钮（`.hero__nav`）也在
 *    `.hero__slide-media` 里，按钮里的 `chevron.svg` 同样是 `img` ——
 *    写成 `.hero__slide-media img` 会把那两颗图标一起缩放。
 */
.hero .hero__art .hero__slide-media > img,
.hero .hero__art .hero__slide-media > a > img {
  transform: scale(.95);
  transition: transform .25s ease;
}

.hero .hero__art .hero__slide-media > img:hover,
.hero .hero__art .hero__slide-media > a > img:hover,
.hero .hero__art .hero__slide-media > a:focus-visible > img {
  transform: scale(1);
}
/*
 * 「ok学院」按钮（header 右侧，2026-10-09 由「加群交流」改来）：默认显示一圈渐变描边。
 *
 * `.btn` 自带的是 `border: 1px solid transparent`（`style.min.css`），平时看不见边。
 * 这里不改 `border`（改颜色只能得到一个纯色边），而是用 `::before` 铺一层品牌渐变、
 * 再用双 mask 抠掉中间，只留 1px 边框 —— 写法与既有 `.app__main-upgrade::before`
 * （`style.min.css`）完全一致，取值就是站点那五段品牌渐变。
 *
 * `pointer-events: none` 必加：`::before` 盖住整个按钮，不关掉的话点不到链接。
 */
.header__buttons .btn-gradient-border {
  position: relative;
  isolation: isolate;
}

.header__buttons .btn-gradient-border::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  background-image: linear-gradient(131.19deg, #f85e76 .853%, #a324b0 21.993%, #7807cd 45.503%, #514fd8 74.136%, #2baedf 103.716%);
  -webkit-mask: conic-gradient(#000 0 0) content-box, conic-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: conic-gradient(#000 0 0) content-box exclude, conic-gradient(#000 0 0);
  pointer-events: none;
}

/* 桌面顶部导航：中文导航项保持单行；宽度按最长文案自适应，避免新「Ai技术咨询」
 * 把后面的「校企合作 / 关于OK」挤成两行。移动端抽屉继续使用原有纵向布局。 */
@media (min-width: 768px) {
  .header .header__button--single-line {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    min-width: max-content;
    white-space: nowrap !important;
    word-break: keep-all;
  }

  .header .header__buttons .btn,
  .header .header__buttons .btn-gradient {
    flex-shrink: 0;
    white-space: nowrap !important;
  }

  .header .menu__body {
    min-width: max-content !important;
  }

  .header .menu ul {
    flex-wrap: nowrap;
  }

  .header .menu ul li,
  .header .menu ul a {
    white-space: nowrap !important;
  }

  .header .menu ul a {
    display: inline-block !important;
  }
}

.consult-popup__qrs--single {
  grid-template-columns: 1fr;
}

/*
 * ---------- 手机顶栏两个按钮的左右内边距：20px → 12px ----------
 *
 * 用户 2026-09-22：「加群交流 变成了 3 行（改回 2 行显示）」。
 *
 * 顶栏在 390 屏上内宽 358px，而 `.header__button` 左右各 20px 内边距 ——
 * 按钮只剩 83px 宽、**内容区 41px**，「💬 加群交流」在那里必然排成 3 行
 * （💬加 / 群交 / 流）。
 *
 * 上面那条 `.header .header__button--single-line { white-space: nowrap }` 的单行规则
 * 包在 `@media (min-width: 768px)` 里，**手机压根不命中** —— 所以这里一直是自动换行，
 * 三个字一行的排法不是「变」出来的，是本来就有的。
 *
 * 收到 12px 之后内容区 52px，正好放得下「💬 加群」（46px），排成 2 行
 * （💬 加群 / 交流），与「在线咨询」的排法一致。
 *
 * 留 6px 余量不是随手：**真机的字体渲染比桌面 Chrome 略宽**，
 * 这个坑在底部 tabbar 上踩过一次（那里因此折成了两行，见 prototype-wap.css 的说明）。
 * 余量不够的话真机会退回 3 行。
 *
 * 实测（390 视口，把内容克隆到等宽块里数行）：
 *   padding 20 → 「加群交流」3 行
 *   padding 16 / 14 / 12 / 10 → 2 行
 * 「在线咨询」在 20 与 12 下都是 2 行，不受影响。
 *
 * 选择器带 `.header__buttons` 提高特异性：这条表排在最前（见 prototype-styles.tsx），
 * 只靠 `.header__button` 压不过后面的 `style.min.css`。
 */
@media (max-width: 767px) {
  .header .header__buttons .header__button {
    padding-left: 12px;
    padding-right: 12px;
  }
}

/*
 * ---------- 手机顶栏那颗汉堡按钮的抽屉：宽度 90% → 190px ----------
 *
 * 用户 2026-09-22：「logo 旁边的那个按钮，弹出框太宽了，设置成文章菜单弹出一样的宽度」，
 * 随后明确「两个弹出菜单的宽度都 190」。
 *
 * 按钮是 `.menu__icon`（在 logo 左边），弹出的是 `.menu__body` ——
 * `style.min.css` 里它是 `width: 90%`，390 屏上量出来 311px，占了屏幕八成。
 * 190px 与阅读器左栏那个目录抽屉（`.app__aside-body`）同宽。
 *
 * 选择器必须带 `.header`（0,2,0）：`style.min.css` 里那条是 `.menu__body`（0,1,0），
 * 而本文件**排在它之前**加载 —— 同特异性会输。
 *
 * ⚠️ 本文件上面还有一条 `.header .menu__body { min-width: max-content !important }`，
 * 那条在 `@media (min-width: 768px)` 里（给桌面顶部导航保持单行用的），
 * **手机不命中**，不会把这里定下的宽度顶开。
 */
@media (max-width: 767px) {
  .header .menu__body {
    width: 190px;
  }
}


/*
 * 页头「OK学院 / 在线咨询」悬停轻微上移（用户 2026-10-22）。
 * 原先只在 `/about` 的页面样式里生效，现在挪到全站外壳层，所有页面一致。
 */
.header .header__buttons > a,
.header .header__buttons > button {
   transition: transform .2s ease;
}

.header .header__buttons > a:hover,
.header .header__buttons > button:hover {
   transform: translateY(-3px);
}

@media (prefers-reduced-motion: reduce) {
   .header .header__buttons > a,
   .header .header__buttons > button {
      transition: none;
   }

   .header .header__buttons > a:hover,
   .header .header__buttons > button:hover {
      transform: none;
   }
}

/*
 * 「AI智能转型-企业专属定制」区块标题行（用户 2026-10-23）：
 * 标题与「项目进度查询」入口图同一行，图高不超过文字；标题行 + 副标题整体居中。
 */
.prices__head {
   display: flex;
   flex-direction: column;
   align-items: center;
   gap: 4px;
}

.prices__title-row {
   display: flex;
   align-items: center;
   justify-content: center;
   gap: 16px;
   flex-wrap: wrap;
}

.prices__title-row .prices__title {
   /* 行内对齐：标题自身的上下外边距要清掉，图片才能和文字垂直居中。 */
   margin: 0;
}

.prices__progress-entry {
   display: block;
   flex: 0 0 auto;
   align-self: center;
   /* 用户 2026-10-23：136px 缩小 25% → 102px；默认按 90% 显示，悬停回到 100%。 */
   height: 102px;
   transform: scale(.9);
   transition: transform .2s ease, filter .2s ease;
}

.prices__progress-entry img {
   display: block;
   height: 100%;
   width: auto;
}

.prices__progress-entry:hover {
   transform: scale(1);
   filter: drop-shadow(0 12px 26px rgba(40, 90, 190, .42));
}

@media (max-width: 900px) {
   .prices__title-row {
      gap: 12px;
   }

   .prices__progress-entry {
      height: 84px;
   }
}

@media (prefers-reduced-motion: reduce) {
   .prices__progress-entry {
      transition: none;
   }

   .prices__progress-entry:hover {
      transform: none;
   }
}

/*
 * 页头「在线咨询」按钮改成微信绿（用户 2026-10-23）。
 *
 * 必须用三层选择器：`prototype-about.css` / `prototype-st.css` 里写的是
 * `.header .btn-gradient`（0-2-0）且在本文件**之后**加载，同权重会压掉这里的规则。
 * 桌面的按钮在 `.header__buttons` 里，窄屏抽屉的在 `.menu__body-plan` 里，两处都要覆盖。
 */
.header .header__buttons .btn-gradient,
.header .menu__body-plan .btn-gradient {
   background-image: linear-gradient(131.19deg, #31e57a 0%, #07c160 55%, #059c4b 100%);
}

.header .header__buttons .btn-gradient:hover,
.header .menu__body-plan .btn-gradient:hover {
   background-image: linear-gradient(131.19deg, #4bf08f 0%, #0ad268 55%, #06b356 100%);
}
