/* ============================================================================
   website.css —— 内容门户（Website）模块的**全局**外壳/正文样式。

   ⚠ 为什么这个文件是**全局**的（而不是放进组件的 `*.razor.css`）：
     ① 正文由 Quill 在浏览器里渲染 ⇒ 阅读页的 `.ql-editor` 元素是**JS 生成**的，
        不带 Blazor 的组件作用域属性（`b-xxxxxxxxxx`）。
        放在组件 scoped CSS 里的规则会写成 `.article-reading-body .ql-editor[b-xxx]`，
        对 JS 新生成的节点**恒不命中** —— 表现就是"正文段落全部贴在一起、没有标题层级"，
        而且不报任何错。故这一层排版放全局，⛔ 不要挪回组件 scoped CSS。
     ② 外壳（品牌 / 模块导航 / 账号下拉）由**内部页与公开页共用的布局**渲染
        （`WebsiteLayout` / `PortalLayout`），属于"模块级"外观 ⇒ 同样收在这里，
        ⛔ 不在 `*.razor` 里写内联样式（那会形成"顶栏一份、正文另一份"的漂移）。

   ⚠ 作用域：正文那几条都限定在 `.article-reading-body`（阅读页正文容器）之下
     ⇒ 编辑页的 Quill（`.quill-editor-box` 在编辑页、不在该容器内）不受影响。

   ⚠ 引用位置：`Components/App.razor` 的每页引用（与 `documents.css` 同一形态），
     并已登记在 `Authorization/PageAccessDefaults.MiddlewareSkipPrefixes`
     —— 漏登记会让它在**匿名可见的登录页**上被 302 到登录页（本仓实测过一次）。
     ⛔ 本文件是模块外壳/正文样式，任何"页面级容器宽度上限"都不许写在这里
       （那是内容属性，见 `ArticleReadingView.razor.css` 的文件头）。

   ⚠ 打印：本文件**一条 `@media print` 都没有**（打印段的唯一出处是 `app.css`）。
   ============================================================================ */

/* ══════════════════════════════════════════════════════════════════════════════
   一、管理台外壳：分组侧栏 + 主内容区（`PortalAdminLayout`，本批按行业标准重做）

   形态对齐同类后台（Halo 管理台 / WordPress / Strapi / Odoo 后台）：
     侧栏 16rem（固定、自身滚动、白底右描边）＋ 主内容区（左偏移 16rem、自身滚动）。
   为什么整块放这里而不是组件 scoped CSS：本仓的模块外壳样式统一在
     `wwwroot/<module>.css`（与 `documents.css` 一致），且 `NavLink` 生成的 `.active`
     需要覆盖——放一处才看得清"选中态长什么样"。
   ⛔ 一条 `@media print` 都没有：打印段唯一出处是 `app.css`；侧栏靠 `d-print-none` 隐藏。
   ══════════════════════════════════════════════════════════════════════════════ */

/* Halo 的 `theme("width.64")` = 16rem，本仓沿用同一量级（不要随手改成 240px 之类）。 */
.portal-shell {
    --portal-sidebar-width: 16rem;
    display: flex;
    min-height: 100dvh;
    background-color: var(--bs-body-bg, #fff);
}

.portal-sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    z-index: 1020;
    display: none;                 /* 窄屏隐藏（与 Halo 的 sidebar 同一处理：靠 MobileMenu 兜底） */
    flex-direction: column;
    width: var(--portal-sidebar-width);
    background-color: var(--bs-body-bg, #fff);
    border-right: 1px solid var(--bs-border-color, #dee2e6);
    overflow-y: auto;
}

@media (min-width: 768px) {
    .portal-sidebar {
        display: flex;
    }
}

/* ── 窄屏顶栏（`.portal-mobilebar`：汉堡按钮 + 模块名）──────────────────────────
   🔴 **本规则此前漏写，是一个真实缺陷**：标记里用了 `.portal-mobilebar`，而本文件
   （以及全仓任何 CSS）**从来没有为它写过任何规则** ⇒ 它带着 `display: block` 的默认值
   在**所有宽度**都显示，于是每个页面顶部都多出一条"☰ 内容门户"，
   压在页面标题 `<h1>` 之上 —— 看起来就是"标题被挤到了第二行"。
   ⚠ 标记里的注释早就写着它应当是"≥768px 隐藏"，即**意图明确、只是样式没落地**。
   ⚠ 与 `.portal-sidebar` 用**同一个断点**：两者是互补关系 ——
     窄屏只有顶栏（侧栏 `display: none`），≥768px 只有侧栏（顶栏隐藏）。
     ⛔ 不要给这个断点改成别的值，否则会出现"两者同时显示"或"两者都不显示"的宽度区间。
   ⚠ `display: none` 而不是 `visibility`：它必须**不占位**，否则 ≥768px 仍会撑出一条空带。
   ⚠ 打印隐藏由标记上的 `d-print-none` 负责（本文件按约定不写 `@media print`）。 */
.portal-mobilebar {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .75rem;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
    background-color: var(--bs-body-bg, #fff);
}

@media (min-width: 768px) {
    .portal-mobilebar {
        display: none;
    }
}

.portal-sidebar__brand {
    flex: none;
    display: flex;
    justify-content: center;
    padding: 1rem .75rem;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

/* 侧栏里的品牌。
   ★ 本批：**去掉「内容门户」文字**后的形态 —— 标志居中、独占一行。
   ⚠ 原先这里是 `flex-direction: column; gap: .25rem`，那是为"标志在上、文字在下"的
     竖排而设（16rem 宽放不下"标志 + 4 个汉字"一行）。文字删了这两条就没有意义了，
     ⇒ 规则整条删除，而不是留一条 `gap: 0` 之类的空壳（那会变成"看不出为什么存在、
     后人不敢删"的规则）。
   ⚠ 类名 `portal-brand` 仍保留在标记上：它是抽屉与侧栏共用的定位钩子，
     且下面那条放大规则要靠 `.portal-sidebar__brand` 这一层来限定作用范围。 */

/* ★ 侧栏标志**放大一档**（本批要求）。
   ⚠ 选择器刻意带上 `.portal-sidebar__brand` 这一层：样式**只作用于侧栏**。
     ⛔ 不要写成裸 `.site-logo` 或 `.portal-brand .site-logo` ——
     前者会连带放大文档管理顶栏与大屏顶栏，后者会连带放大**窄屏抽屉**里的标志
     （抽屉的品牌与侧栏共用 `.portal-brand`，但它那里保持默认尺寸）。
   ⚠ 数值怎么定的：侧栏内容区宽 = 16rem − 左右内边距 1.5rem ≈ 13rem；
     锁定版标志宽高比 174:51 ≈ 3.41 ⇒ 2.5rem 高时宽约 8.5rem，居中后左右各留余量约 2rem。
     ⛔ 不要再往上加：源图只有 174×51，超过约 1.4× 会明显变软；
     且撑满内边距会显得"顶格"而不是"放大"。
   ⚠ 共用的 `.site-logo` 里的 `max-width: 9rem` 仍然生效（8.5rem < 9rem，不冲突）。 */
.portal-sidebar__brand .site-logo {
    height: 2.5rem;
}

.portal-sidebar__nav {
    flex: 1;
    padding: .75rem .5rem;
    overflow-y: auto;
}

.portal-sidebar__profile {
    flex: none;
    padding: .5rem;
    border-top: 1px solid var(--bs-border-color, #dee2e6);
}

/* 分组：标题是小号大写感的说明文字，不抢条目注意力（对齐 Halo 的 `VMenuLabel`）。 */
.portal-nav-group + .portal-nav-group {
    margin-top: 1rem;
}

.portal-nav-group__label {
    padding: .25rem .5rem;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .04em;
    color: var(--bs-secondary-color, #6c757d);
}

/* 条目：整行可点、圆角、有 hover；选中态用浅色底 + 主色文字（后台通用做法）。 */
.portal-nav-item {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .4rem .5rem;
    border-radius: .375rem;
    color: var(--bs-body-color, #212529);
    text-decoration: none;
    font-size: .9375rem;
}

.portal-nav-item > i {
    width: 1.1rem;
    text-align: center;
    color: var(--bs-secondary-color, #6c757d);
}

.portal-nav-item:hover {
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    text-decoration: none;
}

.portal-nav-item.active {
    background-color: var(--bs-primary-bg-subtle, #cfe2ff);
    color: var(--bs-primary-text-emphasis, #052c65);
    font-weight: 500;
}

.portal-nav-item.active > i {
    color: inherit;
}

/* 侧栏底部的用户胶囊：铺满宽度、左对齐（`AccountCluster Compact="true"`）。 */
.portal-profile-button {
    text-align: start;
}

/* 主内容区：桌面端左偏移侧栏宽度；自身滚动容器由页面决定（列表页长表、阅读页长文都要滚动）。 */
.portal-main {
    flex: auto;
    min-width: 0;                 /* ⛔ 必须：否则子元素的 `table-responsive` 会把 flex 项撑破 */
    width: 100%;
}

@media (min-width: 768px) {
    .portal-main {
        width: calc(100% - var(--portal-sidebar-width));
        margin-inline-start: var(--portal-sidebar-width);
    }
}

/* ── 提示区域（`PortalAlertRegion`）：**固定浮层**，行业标准做法 ──────────────────
   `toast` 三要素：
     ① `position: fixed` —— 提示出现/消失**不推动**页面内容（旧版是文档流内块，
        既会把内容下推，长页面下又会被滚出视口 ⇒ "看不见反馈"）；
     ② `z-index: 1080` —— 高于 `.portal-sidebar`(1020) 与 Bootstrap 的 `.modal-backdrop`(1050)，
        低于 `.modal`(1055 及以上由 Bootstrap 自己管) ⇒ 弹窗打开时提示仍在最上层可读；
     ③ 成功自动消失 / 失败需关闭 —— 由 `PortalAlertRegion` 的代码决定（见 `PortalAlertKind`）。

   ⚠ 打印**不适用** `@media print`：本文件按约定不写打印段（唯一出处是 `app.css`）；
     标记上已带 `d-print-none`（提示属"操作反馈"，不该印到纸上）。
   ⚠ 只看视口宽度（不看侧栏是否存在）：宽屏给侧栏留出 16rem 左边距，窄屏（侧栏被
     `.portal-mobilebar` 取代）则占满宽度 —— 断点与 `.portal-sidebar`/`.portal-mobilebar`
     保持同一个 768px，⛔ 不要另起一个值（否则会出现"提示压在侧栏上"的区间）。 */
.portal-alert-region {
    position: fixed;
    top: 1rem;
    inset-inline-end: 1rem;
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    width: min(26rem, calc(100vw - 2rem));
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    pointer-events: none;          /* 浮层本身不吃点击，只有每条提示可交互 */
}

.portal-alert-region > .alert {
    pointer-events: auto;          /* 关闭按钮必须可点 */
    margin: 0;
}

/* 提示条内部布局（2026-10-19 修 × 位置）：文案主列 + × 次列，⛔ 不用 `alert-dismissible`
   的绝对定位 —— 那种写法在短文案下会让 × 离文字很远（现场反馈）。
   `align-items: flex-start` + `mt-1` 让 × 与首行文字基线对齐，多行文案时不会被拉到中间。 */
.portal-alert-region .portal-alert-text {
    min-width: 0;                  /* 允许长英文/URL 换行，不撑破浮层 */
    overflow-wrap: anywhere;
}
.portal-alert-region .btn-close {
    margin-top: .175rem;           /* 与 14px 正文首行对齐 */
}

@media (min-width: 768px) {
    .portal-alert-region {
        inset-inline-end: 1.5rem;
        width: min(28rem, calc(100vw - var(--portal-sidebar-width) - 3rem));
    }
}
/* ── 页面内的「填写说明」折叠区（`.portal-help`）───────────────────────────────
   设计意图：**关键提示内联保留，细节收进折叠**。
   改造前内容编辑页有约 30 处常显说明文字，把表单挤得很长，而其中多数（分类语义、
   发布范围现状、素材格式清单…）只在**第一次填**或**遇到问题时**才需要看。
   ⚠ 折叠**不删信息** —— 那些说明里有几条是实打实的约束（改地址会让旧链接立即失效、
     受控文件必须填生效时间、发布范围目前只判「公开/内部」），删掉会让人踩坑。
   ⚠ 用原生 `<details>` 而非 Bootstrap `collapse`：这页**没有任何 JS 依赖**也能工作
     （`<summary>` 自带键盘与无障碍语义），而 collapse 需要 `data-bs-*` + 唯一 id。 */
.portal-help {
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background-color: var(--bs-tertiary-bg);
}

.portal-help > summary {
    cursor: pointer;
    padding: .5rem .75rem;
    font-size: .875rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
    list-style: none;   /* 藏原生三角，改用下面的 ::before 图标 */
}

.portal-help > summary::-webkit-details-marker {
    display: none;      /* Safari 需要这一条 */
}

.portal-help > summary::before {
    content: "\F285";   /* bi-chevron-right */
    font-family: "bootstrap-icons";
    display: inline-block;
    width: 1rem;
    transition: transform .15s ease-in-out;
}

.portal-help[open] > summary::before {
    transform: rotate(90deg);
}

.portal-help > summary:hover {
    color: var(--bs-body-color);
}

.portal-help__body {
    padding: 0 .75rem .75rem;
    font-size: .8125rem;
    color: var(--bs-secondary-color);
}

.portal-help__body dt {
    color: var(--bs-body-color);
    font-weight: 600;
    margin-top: .6rem;
}

.portal-help__body dd {
    margin-bottom: 0;
}

/* ── 设置页的预览卡：`≥lg` 时**跟随滚动** ──────────────────────────────────────
   为什么 sticky 而不是普通卡片：设置页是"左改右看"的形态 —— 用户在左边改公司名/备案号，
   右边实时预览。若预览滚出视野，他就得来回滚动才能确认效果，
   而这一页**恰好**在改对外可见的法定信息（填错是违规陈述）。
   ⚠ 只在 `≥lg` 生效：窄屏下右列会折到正文下方，此时 sticky 会把它钉在屏幕中间挡住内容。
   ⚠ `top` 的取值要避开 sticky 顶栏（`.website-topbar`）的高度，否则卡片会钻到顶栏底下；
     这里取 5rem（顶栏 ≈ 3.5rem + 一点间距）。⛔ 不要用大数值（那会让卡片在视口里偏下）。 */
@media (min-width: 992px) {
    .portal-settings-preview {
        position: sticky;
        top: 5rem;
    }
}

/* ── 页头与工具行（行业标准三层里的第二、三层）────────────────────────────────
   · 页头：**页面标题 + 右侧主按钮**（`h1` 级标题，不放在卡片里）；
   · 工具行：搜索 / 筛选 / 批量动作，紧贴内容之上，与内容同宽。
   ⛔ 不要把筛选塞回页头：那是本批从顶栏搬下来的同一个错误，只是换了个位置。 */
.portal-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .5rem 1rem;
    padding: 1rem 1rem 0;
}

.portal-page-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
}

/* 页头右侧的**动作组**（编辑页：保存 / 提交审核 / 发布 / 排定 / 撤回 / 返回）。
   ⚠ 为什么要一个容器而不是让按钮直接做 `.portal-page-header` 的 flex 子项：
     外层是 `justify-content: space-between`，按钮各自做子项会在窄屏上被拆到不同行、
     还会把标题挤到中间；收进一个 `gap` 容器后**整体换行**，成组不散。
   ⚠ 窄屏（`flex-wrap`）动作组自动落到标题下一行 —— 与列表页的页头行为一致。 */
.portal-page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

.portal-page-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    padding: .75rem 1rem;
}

/* 工具行里的搜索框与状态下拉：**固定宽度**而不是内联 `style="width:…"`。
   ⚠ 宽度写在这里而不是标记里：内联样式散在页面里会让"工具行的节奏"无法统一调整。 */
.portal-search {
    width: 16rem;
    max-width: 100%;
}

.portal-status-select {
    width: auto;
}

/* ── 表格的"操作"列宽（内容门户各页共用）──────────────────────────────────────
   ⚠ 从内联 `style="width:…"` 收上来：内联宽度散在标记里既没法统一调、
     也让"表格列宽口径"无从盘查（本批一度在三个页面各写了一个数）。 */
.portal-col-actions {
    width: 340px;
}

.portal-col-actions-wide {
    width: 220px;
}

.portal-page-body {
    padding: 0 1rem 1rem;
}

/* 管理台里的卡片**不再需要阴影**：分层已由页头/工具行/内容三层表达，
   卡片再加阴影会让"卡片套卡片"的观感变脏（Halo 的卡片同样是无阴影细描边）。 */
.portal-main .card {
    box-shadow: none !important;
    border: 1px solid var(--bs-border-color, #dee2e6);
}

/* ── 二、模块外壳：顶栏（`WebsiteLayout`，仅**阅读页**在用）─────────────────────
   ⚠ 阅读页刻意不用管理台侧栏（侧栏会吃掉 16rem 宽度，而阅读页刚按"全屏"诉求重做过）。
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── 二之一、公开门户外壳（`PortalLayout`：`/pages` 与 `/pages/{slug}`）─────────
   ⚠ 这是**匿名可达**的外壳，CSS 必须与其它外壳同源（都在本文件 + `app.css`），
     ⛔ 不要给它单独引一份样式（那会出现"公开页样式没加载"的闪烁）。 */
.public-portal__logo {
    display: block;
    height: 2.25rem;
    width: auto;
    /* ⚠ 必须显式给 `max-width`：这里**没有**挂共用的 `.site-logo`（为避免牵动三个
       服务端外壳的顶栏高度），所以那条 9rem 上限不会自动生效。 */
    max-width: 12rem;
    object-fit: contain;
}

/* 品牌：**可点，回站点首页 `/`**（`app.css` 的 `.site-brand` 给形状）。
   ⚠ 它只在阅读页的顶栏出现（管理台那边是侧栏里的 `.portal-brand`）。
   ⚠ 字号/字重等共用部分在 `.site-brand-text`（`app.css`），这里只给本模块的配色。
   ⚠ 文字与标志**同处一行**：顶栏是 `flex-nowrap`（见 `WebsiteLayout.razor` 顶栏注释），
     窄屏时品牌文字由 `app.css` 隐藏、只留标志（⛔ 不要靠给它加回 `flex-wrap` 来解决拥挤）。 */
.website-brand {
    color: var(--bs-body-color, #212529);
}

.website-brand:hover {
    color: var(--bs-primary, #0d6efd);
}

/* 模块导航的**配色**（形状即内边距/下划线在 `app.css` 的 `.site-nav`）——
   ⛔ 这里不重写形状，否则又变成"同一件事两处口径"。 */
.website-nav .nav-link {
    color: var(--bs-secondary-color, #6c757d);
}

.website-nav .nav-link:hover {
    color: var(--bs-body-color, #212529);
}

/* `NavLink` 命中时自己加 `.active`（本仓既有形态），这里只定义它的颜色。 */
.website-nav .nav-link.active {
    color: var(--bs-primary, #0d6efd);
    border-bottom-color: var(--bs-primary, #0d6efd);
}

/* 账号下拉里的人名走 `.site-account-name`（`app.css`）—— 与文档管理共用一份，
   ⛔ 本文件不再重复定义"窄屏隐藏人名"那条（曾经叫 `.website-account-name`）。 */

/* ══════════════════════════════════════════════════════════════════════════════
   二、阅读页正文（Quill 只读渲染出的 `.ql-editor`）
   ══════════════════════════════════════════════════════════════════════════════ */

/* 🔴 为什么必须有这一段：段落/标题/列表的**间距节奏全靠这里**，
   否则只剩 `quill.snow.css` 的 `margin: 0`（所有段落贴在一起，看起来就是"不专业"）。 */
.article-reading-body .ql-editor {
    /* `.ql-editor` 自带 `padding: 12px 15px`（quill.snow.css）—— 阅读页要的是
       "正文与标题同一左边界"，故横向内边距归零。 */
    padding: 0;
    /* 🔴 **宽度不在这里**：阅读列宽由 `ArticleReadingView.razor.css` 的 96rem 容器给。
       这里必须 `none` —— `.ql-editor` 是**图片的父链**，给它加上限会连带把图压窄
       （本仓踩过：写 `min(80ch,100%)` 时图永远只有 712px）。
       ⛔ 同理不要给 `.ql-container` / `.quill-editor-box` 加上限。 */
    max-width: none;
    font-size: 1rem;
    line-height: 1.8;
}

/* ── 阅读页正文链路与外壳：**一律不画边框 / 底 / 阴影** ───────────────────────────
   这条线找了四轮，根因在 `app.css` 的编辑器兜底（见那里的长注释）：
     `.ql-container.ql-snow { border: 1px solid #ced4da !important; background: #fff }`，
   而**阅读页正文容器同样带 `ql-container ql-snow`**（Quill 初始化时加的）。

   🔴 所以这里必须带 `!important`：对方是 `!important`，普通声明压不住 —— 前四轮"改了没反应"的死因。
      特异性 (0,3,0) 也高于对方的 (0,2,0)；两条同时满足才稳。
      ⛔ 不要因为"看起来多余"去掉 `!important`（`ArticleReadingBorderGuardTests` 钉着这一条）。
   ⚠ 逐个列出而不是只写 `.ql-container`：Quill 只给"容器"加类，内层 `.ql-editor` 与外面那层
      `[data-article-prose]` 都不在其列；只压一层时，剩下那层仍会露出一条线。
   ⛔ 不要图省事写成 `* { border: 0 }`：正文里的表格、引用块、代码块的边框是有语义的。
   ⚠ 只作用于 `.article-reading-body` 之下 ⇒ 编辑页 `.quill-frame` 的输入框边框不受影响。 */
.article-reading-body .ql-container.ql-snow,
.article-reading-body .ql-container,
.article-reading-body .quill-editor-box,
.article-reading-body .website-quill-readonly,
.article-reading-body > [data-article-prose],
.article-reading-body .ql-editor {
    border: 0 !important;
    border-radius: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

/* 阅读页的**外壳**（页面容器 / main）显式归零：第二道防线 —— 将来谁给外壳加边框都能兜住。
   ⛔ 只针对阅读页的这两层，不影响列表页。 */
.article-view-page,
.website-main {
    border: 0;
}

/* 任何**包含**阅读视图的祖先（`main`、布局根、页面容器及其中间层）同样不画边框/阴影。
   用 `:has()` 而不是逐个类名：根因排查阶段正是"猜不到是哪个容器"才拖了四轮。
   ⚠ 自身与后代两种形态都要写：只写后代形式会漏掉"线画在容器自身"的情况。
   ⚠ 浏览器要求：Chrome/Edge 105+、Safari 15.4+；不支持时静默失效，不影响其它规则。
   ⚠ `:not(.article-reading)` 排除正文自身 —— 它的上/下分隔线要保留。 */
.website-main:has(.article-reading),
.website-main :has(.article-reading):not(.article-reading),
.portal-main:has(.article-reading),
.portal-main :has(.article-reading):not(.article-reading) {
    border: 0;
    box-shadow: none;
}

/* 文本块宽度：**跟随阅读列**（2026-10-17）。
   上一版给正文压了 `min(80ch, 100%)`（≈712px），而标题 / 目录 / 配图都比它宽
   ⇒ 页面上出现四条不同的左右边界、且全部贴左，现场反馈"不专业、左右不对称"。
   现在整页只有**一个宽度**（`ArticleReadingView.razor.css` 的 96rem 内容列）：
   文本、标题、目录、配图、上下篇全部 100% ⇒ 左右边界是一条竖线，整块居中。
   ⚠ 中文行宽因此等于列宽（1536px 屏约 90 字/行）；这是"对称 + 同宽"的代价，
     ⛔ 不要再把 80ch 加回来 —— 那会立刻让正文比配图窄一截，退回老观感。
   ⚠ 媒体（img / video / iframe）另有两条规则（见下），宽度同样跟列、高度自适应。 */
.article-reading-body .ql-editor > :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, pre, table) {
    max-width: 100%;
}

/* 段落节奏：中文长文按"段间距 > 行距"给。 */
.article-reading-body .ql-editor > p {
    margin-bottom: .9rem;
}

.article-reading-body .ql-editor > p:last-child {
    margin-bottom: 0;
}

/* 标题层级（正文内的 h1 一般不会出现：页面标题由组件自己渲染）。 */
.article-reading-body .ql-editor :is(h1, h2, h3, h4, h5, h6) {
    margin-top: 1.75rem;
    margin-bottom: .75rem;
    line-height: 1.4;
    font-weight: 600;
}

.article-reading-body .ql-editor > :is(h1, h2, h3, h4, h5, h6):first-child {
    margin-top: 0;
}

/* 列表：缩进 + 条目间距（Quill 的 `li` 自带 `padding-left`，这里只补节奏）。 */
.article-reading-body .ql-editor :is(ul, ol) {
    margin-bottom: .9rem;
    padding-left: 1.5rem;
}

.article-reading-body .ql-editor li {
    margin-bottom: .25rem;
}

/* 引用块：左侧色条（不引 Bootstrap 的 `blockquote` 类，避免与富文本自带样式打架）。 */
.article-reading-body .ql-editor blockquote {
    margin: 0 0 .9rem;
    padding: .25rem 0 .25rem .9rem;
    border-left: 3px solid var(--bs-border-color, #dee2e6);
    color: var(--bs-secondary-color, #6c757d);
}

/* 行内代码与代码块：浅底 + 超宽横向滚动（⛔ 不撑破布局）。 */
.article-reading-body .ql-editor :not(pre) > code {
    padding: .1rem .3rem;
    border-radius: .25rem;
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    font-size: .9em;
}

.article-reading-body .ql-editor pre {
    padding: .75rem 1rem;
    border-radius: .5rem;
    background-color: var(--bs-tertiary-bg, #f8f9fa);
    overflow-x: auto;
}

/* 链接：正文里的链接要能一眼看出可点。 */
.article-reading-body .ql-editor a {
    text-decoration: underline;
    text-underline-offset: .15em;
}

/* ── 正文媒体（img / video / iframe）：宽度跟阅读列、高度自适应 ─────────────
   口径：**图片宽度 = 阅读列宽**（`max-width: 100%` 解析到父块，而父块已是列宽），
        高度 `auto` 按比例 ⇒ 图永远完整、不被裁、也不会把小图放大。
   🔴 历史（三种写法都被现场推翻，⛔ 别再走回头路）：
     ① 图片 `max-width: min(100%, 72rem)` ⇒ 百分比解析到图片所在 `<p>` 的量度
        （当时 80ch ≈ 712px），上限够不到 ⇒ 图永远 712；
     ② 改用视口单位 `width: min(88vw, …)` 才铺开，但**图片与正文列脱钩** ⇒
        1920 屏上图宽 ≈1690、比一屏还高 ⇒ "图显示不全"；
     ③ 收窄整页 + 视口高度封顶 ⇒ 图是小了，正文却被一起挤窄、大屏上图又过小。
   ⚠ 若又要用 `vw` 写媒体宽度：Quill 主题自带 `.ql-snow .ql-editor img{max-width:100%}`
     与本文件同特异度且加载更晚，会覆盖它（只能用 `!important` 压住）。

   ── 图片分两种情形（行业口径：独占一段 = 配图；图文同段 = 行内）── */
/* ── 正文图片：分两种情形（2026-10 按现场反馈"图片宽度应该和浏览器一样"重做）──────────
   ⭐ 行业标准口径（GitHub / MDN / 各家 CMS 一致）：
      · **独占一段的图片**（作者单独插的一张图）＝ 配图 ⇒ 铺开、醒目；
      · **图文同段**（`<p>文字<img></p>`）＝ 行内小图标一类 ⇒ 留在文本流内，跟着行宽。

   🔴 这一段被实测推翻过三次，把结论记在这里，⛔ 别再走回头路：
     ① `max-width: min(100%, 72rem)` ⇒ 那个 `100%` 解析成**图片所在文本块的宽**（80ch ≈ 712px），
        把更大的上限压回去 ⇒ 写了等于没写；
     ② 换成 `width:100%` 同理 —— 宽度来自父宽，一样 712px；
     ③ 用 `left:50% + translateX(-50%)` 居中 ⇒ `50%` 同样以父宽为基准，图片落到 **left=-325px**
        被推出屏幕左边缘（实测 imgLeft=-325 / imgRight=1061）；
     ④ 用对称负外边距 `margin-inline: calc((100% - …)/2)` ⇒ 实测**父框并不在视口左边**
        （图片被推到 -325），于是"居中"反而把图拖出屏幕。
     ⑤ 决定性实验（在真实 DOM 上逐项对照）：
          父满宽 + 图片 `margin:0`      ⇒ img[12..1399]  ✅ 与文本左对齐、铺开（采用）
          父满宽 + 图片 `margin:auto`   ⇒ img[87..1474]  居中，但右侧与文本不对齐
      ⇒ 结论：**让量度落在文本块上、图片 margin 归零**，图片自然从文本左边界开始铺开。
   ⚠ 因此 `.ql-editor` 的 `max-width` 必须是 `none`（见上面那一段的注释），
     ⛔ 不要把 80ch 加回编辑器：那会把两种情形一起压窄。 */

/* 行内图片（图文同段）：留在文本流内，不溢出文本块。 */
.article-reading-body .ql-editor img {
    max-width: 100%;
    height: auto;
}

/* 独占一段的配图：**先解除那一 <p> 的宽度限制**，再让图片按列宽铺满。
   若这个 `<p>` 被任何量度限死，图片的百分比就会解析到那个值
   （本仓实测过：规则命中、`max-width:none`，宽度仍 712px）。
   ⇒ 本条只负责"放开"，具体宽度由下面 `> img` 的 `max-width: 100%` 决定。 */
.article-reading-body .ql-editor > p:has(> img:only-child) {
    max-width: none;       /* ← 这一行才是让图片铺开的关键 */
    overflow: visible;     /* 图片可能比文本量度宽，别裁掉 */
}

.article-reading-body .ql-editor > p:has(> img:only-child) > img {
    display: block;
    max-width: 100%;
    width: auto;
    margin-inline: 0;
    height: auto;
}

/* 表格：紧凑边框 + 不超标。 */
.article-reading-body .ql-editor table {
    margin-bottom: .9rem;
    max-width: 100%;
}

.article-reading-body .ql-editor table td,
.article-reading-body .ql-editor table th {
    padding: .35rem .6rem;
}

/* 只读容器不该显示文本光标（它是阅读区，不是编辑区）。 */
.article-reading-body .ql-editor.ql-disabled,
.article-reading-body .website-quill-readonly .ql-editor {
    cursor: default;
}

/* ══ 企业站顶栏（2026-10-20 对齐 Halo 主题 header）═════════════════════════════
   结构：`.public-portal__header` → `.public-portal__header-inner`
          ├─ `.public-portal__brand`（标志 + 模块名）
          ├─ `.public-portal__nav`（桌面主导航；窄屏隐藏）
          └─ `.public-portal__actions`（内部入口 + 窄屏抽屉）
   ⚠ 三段式 + `sticky`：长文章滚动时品牌与导航不消失（企业站惯例，Halo 主题同样吸顶）。
   ⚠ 展开**不引 JS**：桌面 `:hover` / `:focus-within`（键盘可达），窄屏 `<details>` 抽屉；
     touch 设备没有 hover，故窄屏一律走抽屉，⛔ 不给窄屏留绝对定位的下拉。
   ⚠ `z-index: 1020`：高于正文与吸顶表头，**低于** `.portal-alert-region`(1080) 与
     `.portal-sidebar`(1020 同级但不同面) ⇒ 操作反馈永远压在最上层。 */

.public-portal__header {
    position: sticky;
    top: 0;
    z-index: 1020;
    background: rgba(255, 255, 255, .94);
    backdrop-filter: saturate(180%) blur(8px);
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.public-portal__header-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem 1.5rem;
    padding-top: .75rem;
    padding-bottom: .75rem;
}

/* ── 品牌 ─────────────────────────────────────────────────────────────── */
.public-portal__brand {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    text-decoration: none;
    color: var(--bs-body-color, #212529);
    flex-shrink: 0;
}

.public-portal__brand-name {
    font-weight: 600;
    letter-spacing: .01em;
    font-size: 1.125rem;
}

.public-portal__logo {
    height: 2.25rem;
    width: auto;
}

/* ── 桌面主导航 ───────────────────────────────────────────────────────── */
.public-portal__nav {
    display: flex;
    align-items: center;
    margin-inline-start: auto;
}

.public-portal__nav-list {
    display: flex;
    align-items: center;
    gap: .25rem;
}

.public-portal__nav-link {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: .375rem .75rem;
    border-radius: .375rem;
    color: var(--bs-secondary-color, #6c757d);
    text-decoration: none;
    white-space: nowrap;
    line-height: 1.4;
}

.public-portal__nav-link:hover,
.public-portal__nav-link:focus-visible {
    color: var(--bs-body-color, #212529);
    background: var(--bs-tertiary-bg, #f8f9fa);
}

/* 当前页：文字加深 + 2px 下划线（企业站惯例；不靠颜色区分，色弱也能看出来） */
.public-portal__nav-link.is-active {
    color: var(--bs-body-color, #212529);
    font-weight: 600;
    box-shadow: inset 0 -2px 0 0 var(--bs-primary, #0d6efd);
}

.public-portal__nav-link--group {
    cursor: default;
}

.public-portal__caret {
    font-size: .75em;
    transition: transform .15s ease;
}

.public-portal__nav-group {
    position: relative;
}

/* ── 二级下拉（桌面）：默认隐藏，hover / 键盘聚焦时展开 ───────────────── */
.public-portal__nav-menu {
    position: absolute;
    top: calc(100% + .25rem);
    left: 0;
    min-width: 13rem;
    padding: .375rem;
    background: #fff;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .5rem;
    box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .08);
    z-index: 1030;
    display: none;
}

.public-portal__nav-menu-link {
    display: block;
    padding: .375rem .625rem;
    border-radius: .375rem;
    color: var(--bs-body-color, #212529);
    text-decoration: none;
    white-space: nowrap;
}

.public-portal__nav-menu-link:hover,
.public-portal__nav-menu-link:focus-visible {
    background: var(--bs-tertiary-bg, #f8f9fa);
    color: var(--bs-link-color, #0d6efd);
}

.public-portal__nav-menu-link.is-active {
    background: var(--bs-tertiary-bg, #f8f9fa);
    font-weight: 600;
}

@media (min-width: 768px) {
    .public-portal__nav-group:hover > .public-portal__nav-menu,
    .public-portal__nav-group:focus-within > .public-portal__nav-menu {
        display: block;
    }

    .public-portal__nav-group:hover > .public-portal__nav-link .public-portal__caret,
    .public-portal__nav-group:focus-within > .public-portal__nav-link .public-portal__caret {
        transform: rotate(180deg);
    }
}

/* ── 右侧动作区 ───────────────────────────────────────────────────────── */
.public-portal__actions {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-shrink: 0;
    margin-inline-start: auto;
}

.public-portal__action-link {
    color: var(--bs-secondary-color, #6c757d);
    text-decoration: none;
    font-size: .875rem;
    padding: .375rem .625rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .375rem;
    white-space: nowrap;
}

.public-portal__action-link:hover,
.public-portal__action-link:focus-visible {
    color: var(--bs-body-color, #212529);
    border-color: var(--bs-secondary-color, #6c757d);
}

/* ── 窄屏抽屉（`<details>`，无 JS）────────────────────────────────────── */
.public-portal__drawer-toggle {
    position: relative;
    display: none;          /* 桌面隐藏；窄屏见下方媒体查询 */
}

.public-portal__drawer-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .375rem;
    cursor: pointer;
    list-style: none;
    color: var(--bs-body-color, #212529);
}

/* 去掉 Chrome/Safari 的默认三角标记 */
.public-portal__drawer-trigger::-webkit-details-marker { display: none; }
.public-portal__drawer-trigger::marker { content: ""; }

.public-portal__drawer-trigger:hover,
.public-portal__drawer-trigger:focus-visible {
    background: var(--bs-tertiary-bg, #f8f9fa);
}

.public-portal__drawer {
    position: absolute;
    top: calc(100% + .5rem);
    right: 0;
    min-width: 16rem;
    max-height: min(70vh, 28rem);
    overflow-y: auto;
    padding: .5rem;
    background: #fff;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .5rem;
    box-shadow: 0 .75rem 1.5rem rgba(0, 0, 0, .12);
    z-index: 1031;
}

/* 抽屉里的导航是**竖排**：与桌面共用同一份结构，只换方向 */
.public-portal__drawer .public-portal__nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: .125rem;
}

.public-portal__drawer .public-portal__nav-menu {
    position: static;
    display: block;
    min-width: 0;
    padding: .25rem 0 .25rem .75rem;
    border: 0;
    box-shadow: none;
    background: transparent;
}

.public-portal__drawer .public-portal__caret {
    display: none;          /* 抽屉里二级项直接展开，不需要折叠箭头 */
}

@media (max-width: 767.98px) {
    .public-portal__nav {
        display: none;
    }

    .public-portal__drawer-toggle {
        display: block;
    }

    .public-portal__actions {
        margin-inline-start: auto;
    }
}
/* 分类归档页（`/categories`）的分组与子分类列表 */
.public-category-group + .public-category-group {
    border-top: 1px solid var(--bs-border-color, #dee2e6);
    padding-top: 1rem;
}

.public-category-children .list-inline-item:not(:last-child)::after {
    content: "·";
    margin: 0 .375rem;
    color: var(--bs-secondary-color, #6c757d);
}

/* ── 顶栏搜索框 + 列表页搜索/分页（2026-10-20 站内搜索）────────────────────────
   顶栏搜索在企业站里是"右侧窄框"：宽度固定，不挤占主导航。
   ⚠ 窄屏隐藏顶栏搜索（抽屉里放不下，页面内有全宽表单），⛔ 不要两处都显示。 */
.public-portal__search {
    width: 14rem;
}

.public-portal__search .form-control {
    min-width: 0;
}

.public-portal-list-header {
    margin-bottom: 1.25rem;
}

.public-portal-search {
    max-width: 34rem;
}

@media (max-width: 991.98px) {
    .public-portal__search {
        display: none;
    }
}

/* ── 独立页面（`/p/{slug}`，对齐 Halo SinglePage）──────────────────────────────
   正文区居中限宽（与文章阅读页同一阅读宽度量级），但不做左右边框/卡片 ——
   "关于我们"这类页面应当是干净的长文，⛔ 不要套卡片。 */
.public-single-page {
    max-width: 56rem;
    margin-inline: auto;
}

.public-single-page__content {
    line-height: 1.85;
    font-size: 1rem;
}

.public-single-page__content img {
    max-width: 100%;
    height: auto;
}

.public-single-page__content h2 {
    margin-top: 2rem;
    margin-bottom: .75rem;
    font-size: 1.35rem;
}

.public-single-page__content h3 {
    margin-top: 1.5rem;
    margin-bottom: .5rem;
    font-size: 1.15rem;
}

/* ── 页面区块（对齐 Halo 可视化编排的"模块"层）─────────────────────────────────
   模块清单在 `WebsitePageBlocks`（白名单），样式在这里统一给 —— 这就是"模块样式由主题定义"
   在本仓（无主题系统）的落点：运营只能选模块与排序，⛔ 不能自定义样式。 */
.page-block {
    margin-bottom: 2.5rem;
}

.page-block__title {
    font-size: 1.35rem;
    margin-bottom: .875rem;
}

.page-block__image {
    max-width: 100%;
    height: auto;
    border-radius: .5rem;
    margin-bottom: 1rem;
}

/* 数据指标：等宽卡片行，窄屏自动换行 */
.page-block__metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 1rem;
    margin: 0;
}

.page-block__metric {
    padding: 1rem 1.125rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .5rem;
    background: var(--bs-tertiary-bg, #f8f9fa);
}

.page-block__metric dt {
    font-size: .875rem;
    color: var(--bs-secondary-color, #6c757d);
    font-weight: 400;
}

.page-block__metric dd {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.3;
}

.page-block__metric-note {
    margin-top: .25rem;
    font-size: .8125rem;
    color: var(--bs-secondary-color, #6c757d);
}

/* 发展时间线：左侧时间列 + 右侧事件，用竖线串联 */
.page-block__timeline {
    list-style: none;
    margin: 0;
    padding: 0 0 0 .25rem;
}

.page-block__timeline-item {
    display: flex;
    gap: 1rem;
    padding: .75rem 0;
    border-left: 2px solid var(--bs-border-color, #dee2e6);
    padding-left: 1.25rem;
    position: relative;
}

.page-block__timeline-item::before {
    content: "";
    position: absolute;
    left: -.4rem;
    top: 1.15rem;
    width: .6rem;
    height: .6rem;
    border-radius: 50%;
    background: var(--bs-primary, #0d6efd);
}

.page-block__timeline-year {
    min-width: 5.5rem;
    font-weight: 600;
    color: var(--bs-primary, #0d6efd);
}

.page-block__timeline-event {
    font-weight: 500;
}

.page-block__timeline-note {
    font-size: .875rem;
    color: var(--bs-secondary-color, #6c757d);
    margin-top: .125rem;
}

/* 团队成员：自适应网格 */
.page-block__team {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 1rem;
}

.page-block__member {
    padding: 1rem 1.125rem;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .5rem;
}

.page-block__member-name {
    font-weight: 600;
    font-size: 1.05rem;
}

.page-block__member-role {
    font-size: .875rem;
    color: var(--bs-primary, #0d6efd);
    margin-bottom: .25rem;
}

.page-block__member-note {
    font-size: .875rem;
    color: var(--bs-secondary-color, #6c757d);
}

/* ── 富文本编辑器（`RichTextEditor`，Quill）────────────────────────────────────
   Quill 会往容器里注入 `.ql-toolbar` + `.ql-container`；这里只保证：
   ① 容器有最小高度（否则空文档时点不到编辑区）；② 白底（与只读阅读区的透明底区分）；
   ③ 降级文本域与之等高的视觉重量。 */
.rich-text-editor__surface {
    min-height: 12rem;
    background: #fff;
    border-radius: .375rem;
}

.rich-text-editor__surface .ql-container {
    min-height: 10rem;
    font-size: 1rem;
}

/* ── 可编排首页（`/pages` 顶部区块）────────────────────────────────────────────
   区块本身由 `.page-block*` 负责；这里只给"首页区"与下方「最新内容」之间的分隔，
   ⛔ 不要给首页区套卡片/边框（与独立页面同一阅读口径）。 */
.public-portal-home {
    max-width: 56rem;
    margin: 0 auto 2.5rem;
    padding-bottom: 2rem;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

/* ── 运营看板的阅读量趋势（纯 CSS 条形，无 JS / 无图表库）───────────────────────
   每根条形高度按"当日 / 最大值"百分比给；精确数字在 `title` 与表格里，
   ⛔ 不要在条形上标数字（30 根挤在一起会全部叠字）。 */
.insights-trend {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 6rem;
    padding: .25rem 0;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.insights-trend__bar {
    flex: 1 1 0;
    min-height: 1px;
    background: var(--bs-primary, #0d6efd);
    opacity: .75;
    border-radius: 2px 2px 0 0;
    transition: opacity .15s ease;
}

.insights-trend__bar:hover {
    opacity: 1;
}

/* ── 联系我们：蜜罐字段 ───────────────────────────────────────────────────────
   真人（视觉与读屏）都不该看到它；⛔ 不用 `display:none`（部分机器人会跳过隐藏元素），
   而是移出可视区并 `aria-hidden`：能被表单解析，但没人会填。 */
.contact-honeypot {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ── 定时发布时间轴（7 天分布）─────────────────────────────────────────────────
   行结构：日期标签 | 轨道 | 条数。条形宽度用内联 style 的百分比（由纯规则算好），
   ⛔ 不要用 JS 画（后台页面无需为此引脚本）。 */
.insights-timeline__row {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .15rem 0;
}

.insights-timeline__label {
    flex: 0 0 5.5rem;
    color: var(--bs-secondary-color, #6c757d);
}

.insights-timeline__track {
    flex: 1 1 auto;
    height: .6rem;
    background: var(--bs-tertiary-bg, #f8f9fa);
    border-radius: 999px;
    overflow: hidden;
}

.insights-timeline__bar {
    display: block;
    height: 100%;
    background: var(--bs-primary, #0d6efd);
    opacity: .7;
}

.insights-timeline__row.is-overdue .insights-timeline__bar {
    background: var(--bs-danger, #dc3545);
    opacity: .85;
}

.insights-timeline__count {
    flex: 0 0 2rem;
    text-align: right;
}

/* ══════════════════════════════════════════════════════════════════════════════
   二十三、Halo 设计令牌（2026-10-23，对齐 `halo-main/ui/tailwind.config.ts`）

   来源（实读 `ui/tailwind.config.ts` 的 themer 默认主题）：
     primary   #4CCBA0   secondary #0E1731   danger #D71D1D   borderRadius.base 4px
   以及 `ui/console-src/layouts/BasicLayout.vue`：侧栏 16rem、≥md 时侧栏 gray-100、内容区白底。

   ⚠ 作用域**只限管理台外壳**（`.portal-shell` 内）：公开门户有自己的企业站视觉，
     ⛔ 不要把管理台令牌套到 `/pages`、`/p/{slug}` 这些访客页面上。
   ⚠ 按钮文字用 secondary（深海军蓝）而不是白：teal #4CCBA0 上白字对比度仅 ≈1.9:1（不达 WCAG AA），
     深色字 ≈8:1。样式对齐不等于照抄，可读性优先（已在文档记录该偏差）。
   ══════════════════════════════════════════════════════════════════════════════ */
.portal-shell {
    --halo-primary: #4CCBA0;
    --halo-secondary: #0E1731;
    --halo-danger: #D71D1D;
    --halo-radius: 4px;
    --halo-sidebar-bg: #f3f4f6;   /* Tailwind gray-100，与 Halo 侧栏同色 */
    --halo-border: #e5e7eb;       /* gray-200 */
    --halo-muted: #6b7280;        /* gray-500 */
}

/* ── 侧栏：灰底 + teal 选中态 ─────────────────────────────────────────────────── */
.portal-shell .portal-sidebar {
    background-color: var(--halo-sidebar-bg);
    border-right-color: var(--halo-border);
}

.portal-shell .portal-sidebar__brand {
    background-color: #fff;
    border-bottom-color: var(--halo-border);
}

.portal-shell .portal-nav-group__label {
    color: var(--halo-muted);
    letter-spacing: .04em;
}

.portal-shell .portal-nav-item {
    border-radius: var(--halo-radius);
    color: var(--halo-secondary);
}

.portal-shell .portal-nav-item:hover {
    background-color: #e5e7eb;   /* gray-200：hover 比底色调深一档，而不是变蓝 */
}

/* 选中态用 teal 前景色而非整块填充（Halo 的激活项也是"文字变色 + 轻底色"）。 */
.portal-shell .portal-nav-item.active {
    background-color: rgba(76, 203, 160, .14);
    color: #0b7a5d;              /* teal 的深一档：保证在浅底上对比度 ≥ 4.5:1 */
    font-weight: 600;
}

/* ── 主内容区：白底 + 4px 圆角卡片 + 轻描边表格 ─────────────────────────────── */
.portal-shell .portal-main {
    background-color: #fff;
}

.portal-shell .card {
    border-radius: var(--halo-radius);
    border-color: var(--halo-border);
}

.portal-shell .card-header {
    background-color: #fafafa;
    border-bottom-color: var(--halo-border);
}

.portal-shell .table > thead th {
    background-color: #fafafa;
    border-bottom-color: var(--halo-border);
    color: var(--halo-muted);
    font-weight: 600;
    font-size: .8125rem;
    text-transform: none;
}

/* ── 按钮与徽章：4px 圆角、teal 主色 ──────────────────────────────────────────── */
.portal-shell .btn {
    border-radius: var(--halo-radius);
}

.portal-shell .btn-primary {
    background-color: var(--halo-primary);
    border-color: var(--halo-primary);
    color: var(--halo-secondary);
}

.portal-shell .btn-primary:hover,
.portal-shell .btn-primary:focus {
    background-color: #3fbb92;
    border-color: #3fbb92;
    color: var(--halo-secondary);
}

.portal-shell .btn-outline-primary {
    color: #0b7a5d;
    border-color: var(--halo-primary);
}

.portal-shell .btn-danger {
    background-color: var(--halo-danger);
    border-color: var(--halo-danger);
}

.portal-shell .badge {
    border-radius: 999px;         /* Halo 的徽章是胶囊形 */
    font-weight: 500;
}

/* 焦点环统一成 teal：Bootstrap 默认是蓝色，与令牌冲突。 */
.portal-shell :focus-visible {
    outline: 2px solid rgba(76, 203, 160, .55);
    outline-offset: 2px;
}

/* ── 联系我们（行业标准排版）────────────────────────────────────────────────────
   两栏：左"联系方式"紧凑信息块，右"在线留言"表单。
   ⚠ 左侧**不拉伸等高**（不加 h-100）：内容少的部署只配公司名时，等高的空面板会显得页面没做完。
   行容器已加 `align-items-start`，这里只负责面板观感与信息表排版。 */
.public-contact__panel {
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, .375rem);
    background: #fff;
    padding: 1.25rem 1.25rem 1.5rem;
}

/* 信息表：标签列固定窄宽、值可换行；行距比默认 dl 更松一档（读屏与扫读都更清楚）。 */
.public-contact__facts {
    display: grid;
    grid-template-columns: 4.5rem 1fr;
    gap: .5rem 1rem;
    font-size: .9375rem;
}

.public-contact__facts dt {
    color: var(--bs-secondary-color, #6c757d);
    font-weight: 400;
}

.public-contact__facts dd {
    margin: 0;
    word-break: break-word;
}

@media (min-width: 992px) {
    /* 桌面端左侧信息块跟随滚动（表单较长时仍然看得到联系方式）。 */
    .public-contact__panel {
        position: sticky;
        top: 1rem;
    }
}

/* ── 跳过导航（WCAG 2.4.1）────────────────────────────────────────────────────
   默认视觉隐藏（不可见但仍可聚焦），键盘 Tab 第一次停留时出现在左上角。
   ⛔ 不要用 `display:none` / `visibility:hidden` —— 那会让它无法聚焦，"跳过导航"就失效了。 */
.public-skip-link {
    position: absolute;
    left: 0.5rem;
    top: -100px;
    z-index: 1080;
    padding: 0.5rem 0.75rem;
    background: #fff;
    color: var(--bs-body-color, #212529);
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 4px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .12);
    transition: top .15s ease;
}

.public-skip-link:focus,
.public-skip-link:focus-visible {
    top: 0.5rem;
    text-decoration: none;
    outline: 2px solid rgba(13, 110, 253, .45);
    outline-offset: 2px;
}

/* ── 尊重"减少动态效果"（WCAG 2.3.3 / 系统级偏好）──────────────────────────────
   门户的过渡与动画只用于装饰；系统开启 reduce 时一律关闭，避免前庭不适。
   ⚠ 只覆盖本模块的类，⛔ 不用 `*`（那会连带关掉需要保留的状态反馈）。 */
@media (prefers-reduced-motion: reduce) {
    .public-portal *,
    .public-portal *::before,
    .public-portal *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }

    .public-skip-link {
        transition: none;
    }
}

/* ── 公网企业官网首页（`/`；主题色由 WebsiteSettings 注入 CSS 变量）── */
.public-hero {
    background: linear-gradient(135deg, var(--public-primary, #0d3b66) 0%, #1f6f8b 55%, var(--public-accent, #f5a623) 100%);
    color: #fff;
    padding: 72px 0 64px;
}
.public-hero__inner { max-width: 860px; }
.public-hero__eyebrow { font-size: .9rem; letter-spacing: .08em; opacity: .9; margin-bottom: .75rem; }
.public-hero__title { font-size: clamp(2rem, 4vw, 3rem); font-weight: 700; margin-bottom: 1rem; }
.public-hero__lead { font-size: 1.05rem; line-height: 1.9; opacity: .95; margin-bottom: 1.5rem; }
.public-hero .btn-primary { background: var(--public-accent, #f5a623); border-color: var(--public-accent, #f5a623); color: #1b1b1b; font-weight: 600; }
.public-hero .btn-primary:hover { filter: brightness(1.1); }
.public-hero .btn-outline-light:hover { color: var(--public-primary, #0d3b66); }
/* Publica 对齐：数据概览 / 合作伙伴 */
.public-stats { border-top: 1px solid #eee; border-bottom: 1px solid #eee; padding: 24px 0; }
.public-stat__num { font-size: 1.9rem; font-weight: 700; color: var(--public-primary, #0d3b66); line-height: 1.2; }
.public-stat__label { font-size: .85rem; color: #6c757d; margin-top: .25rem; }
.public-partners span { white-space: nowrap; }
/* Odoo 20 风格「编辑」浮标（仅登录用户） */
.public-portal__edit-fab {
    position: fixed;
    top: 84px;
    left: 12px;
    z-index: 1050;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .45rem .8rem;
    background: #714B67;
    color: #fff;
    font-size: .875rem;
    font-weight: 600;
    text-decoration: none;
    border-radius: 0 .4rem .4rem 0;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .25);
}
.public-portal__edit-fab:hover { background: #5d3d55; color: #fff; }
@media (max-width: 576px) {
    .public-portal__edit-fab { top: auto; bottom: 16px; left: 12px; border-radius: .4rem; }
}
/* 公网顶栏 Logo：36px 桌面 / 32px 窄屏（与 18px 品牌字匹配） */
@media (max-width: 576px) {
    .public-portal__logo { height: 2rem; }
    .public-portal__brand-name { font-size: 1rem; }
}

/* ══════════════════════════════════════════════════════════════════════
   企业建站专业化（2026-10-31）
   目标：统一的排版节奏 / 卡片层次 / 信任与转化模块；不改布局骨架，
   全部为增量规则，避免影响已上线的公开页与内部页。
   ══════════════════════════════════════════════════════════════════════ */

.public-portal {
    font-family: "Inter", "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, sans-serif;
    color: #1f2937;
    -webkit-font-smoothing: antialiased;
}

.public-portal h1, .public-portal h2, .public-portal h3, .public-portal h4 {
    letter-spacing: -.01em;
    text-wrap: balance;
}

.public-portal p { line-height: 1.75; }

/* 小标签（行业/栏目眉标） */
.public-eyebrow {
    display: inline-block;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--public-primary, #0d3b66);
    margin-bottom: .35rem;
}

/* 卡片：统一阴影与悬停层次（仅公网页面，不影响后台） */
.public-portal .card {
    border: 1px solid rgba(15, 23, 42, .06);
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}

.public-portal .card:hover {
    transform: translateY(-2px);
    box-shadow: 0 .75rem 1.75rem rgba(15, 23, 42, .10) !important;
    border-color: rgba(13, 59, 102, .18);
}

/* 价值点卡片（首页「为什么选择知行」） */
.public-value {
    background: #fff;
    border: 1px solid rgba(15, 23, 42, .07);
    border-radius: .9rem;
    padding: 1.25rem;
    transition: transform .18s ease, box-shadow .18s ease;
}

.public-value i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: .7rem;
    background: rgba(13, 59, 102, .08);
    color: var(--public-primary, #0d3b66);
    font-size: 1.2rem;
}

.public-value:hover {
    transform: translateY(-2px);
    box-shadow: 0 .75rem 1.75rem rgba(15, 23, 42, .10);
}

/* 合作品牌：胶囊墙（文字，不使用未授权 Logo） */
.public-partners__wall {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .6rem;
}

.public-partner-pill {
    display: inline-flex;
    align-items: center;
    padding: .45rem .9rem;
    border: 1px solid rgba(15, 23, 42, .10);
    border-radius: 999px;
    background: #fff;
    color: #4b5563;
    font-size: .9rem;
    white-space: nowrap;
}

/* 转化带（首页底部） */
.public-cta-band {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1.25rem;
    padding: 1.9rem 2rem;
    border-radius: 1rem;
    background: linear-gradient(135deg, var(--public-primary, #0d3b66) 0%, #1b6ca8 100%);
    color: #fff;
}

.public-cta-band__actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}

/* 产品分类快速跳转 */
.public-cat-nav {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    align-items: center;
}

.public-cat-nav a {
    display: inline-flex;
    align-items: center;
    padding: .45rem .9rem;
    border: 1px solid rgba(13, 59, 102, .18);
    border-radius: 999px;
    background: #fff;
    color: var(--public-primary, #0d3b66);
    font-size: .9rem;
    font-weight: 600;
    text-decoration: none;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}

.public-cat-nav a:hover,
.public-cat-nav a:focus-visible {
    background: var(--public-primary, #0d3b66);
    border-color: var(--public-primary, #0d3b66);
    color: #fff;
}

.public-cat-nav__more {
    margin-left: auto;
    border-style: dashed !important;
}

/* 面包屑 */
.public-portal .breadcrumb {
    font-size: .875rem;
    margin-bottom: 1.25rem;
}

.public-portal .breadcrumb a {
    color: #6b7280;
    text-decoration: none;
}

.public-portal .breadcrumb a:hover { color: var(--public-primary, #0d3b66); text-decoration: underline; }

/* 键盘可达性：统一可见焦点 */
.public-portal a:focus-visible,
.public-portal button:focus-visible {
    outline: 3px solid rgba(27, 108, 168, .55);
    outline-offset: 2px;
    border-radius: .25rem;
}

/* 窄屏：转化带与分类导航改为纵向 */
@media (max-width: 767.98px) {
    .public-cta-band { padding: 1.4rem; }
    .public-cta-band__actions { width: 100%; }
    .public-cat-nav__more { margin-left: 0; }
}

/* 尊重系统「减少动态效果」 */
@media (prefers-reduced-motion: reduce) {
    .public-portal .card,
    .public-value,
    .public-cat-nav a {
        transition: none;
    }
    .public-portal .card:hover,
    .public-value:hover { transform: none; }
}