:root {
  --primary: #1a5cff; --primary-dark: #0d47d0; --primary-light: #e8f0ff;
  --bg: #f5f7fa; --card: #ffffff;
  --text: #1f2937; --text-2: #6b7280; --text-3: #9ca3af;
  --border: #e5e7eb; --radius: 12px;
  --shadow: 0 1px 3px rgba(16, 24, 40, .06), 0 1px 2px rgba(16, 24, 40, .04);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
       "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
       background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.65; }
a { color: var(--primary); text-decoration: none; }
a:hover { color: var(--primary-dark); }

/* ---------- 顶部导航（与 SPA index.html 的 .topbar 同款）---------- */
.topbar { background: var(--card); border-bottom: 1px solid var(--border);
          position: sticky; top: 0; z-index: 100; box-shadow: var(--shadow); }
/* 🔴 内宽与 .container 同栅格（2026-10-08）：原 1200 会比 1100 的内容区宽出 100（每侧 50），
   logo 与正文左缘错位。与 public/css/style.css 的 .topbar-inner 必须同值。 */
.topbar-inner { max-width: 1100px; margin: 0 auto; padding: 0 20px;
                height: 60px; display: flex; align-items: center; gap: 28px; }
.brand { display: flex; align-items: center; flex-shrink: 0; }
.brand-img { height: 40px; width: auto; display: block; }
.nav { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.nav a { color: var(--text-2); padding: 6px 14px; border-radius: 8px; font-size: 15px;
         transition: all .15s; white-space: nowrap; }
.nav a:hover { color: var(--primary); background: var(--primary-light); }
/* 当前栏目高亮（与 SPA 的 .nav a.active 同款；由 page() 的 nav_is_active() 打标） */
.nav a.active { color: var(--primary); background: var(--primary-light); font-weight: 600; }
/* 顶栏右侧「登录 / 注册」与登录后的「用户名 + 退出」（2026-10-13 补：此前预渲染页
   只有 logo + 导航，SPA 有的登录态入口在静态页完全缺失）。
   2026-10-14 再改：原来是**一支纯文字链接「个人中心」**，与其余页面（SPA 的 .auth-area
   由 AuthUI 渲染成 `用户名 + [退出]` / `[登录][注册]`）不一致 —— 用户指出「要和其他页面一致」。
   现在服务端直接渲染**未登录态**（多数访客与爬虫看到的形态），已登录时由 /js/seo_nav.js
   换成同样的 `用户名 + [退出]`。
   🔴 下面 .btn / .btn-ghost / .btn-primary / .btn-auth / .auth-user 的每个数值都必须与
      public/css/style.css 的同名规则**逐值一致** —— 静态页只外链 seo.css，拿不到 style.css，
      所以这里是那份样式的镜像；改一边必须改另一边（否则又会出现"两个页面长得不一样"）。
      ⚠️ 生成页里此前没有任何 class="btn"，新增通用 .btn 不会波及既有元素。 */
.auth-area { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-shrink: 0; }
.auth-user { color: var(--text); font-size: 14px; font-weight: 600; text-decoration: none;
             max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.auth-user:hover { color: var(--primary); }
.btn { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer;
       font-size: 14px; font-weight: 600; transition: all .15s; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-dark); }
.btn-ghost { background: #fff; color: var(--text-2); border: 1px solid var(--border); }
.btn-ghost:hover { color: var(--primary); border-color: var(--primary); }
.btn-auth { display: inline-block; text-decoration: none; text-align: center;
            padding: 6px 14px; font-size: 13px; border-radius: 6px; line-height: 1.4; }
/* 右下角「需求反馈」浮动入口：与 SPA 里 js/views/feedback.js 动态创建的那个按钮同位同款
   （SPA 那边是 <button>，静态页只能给链接 → 指向 /feedback（SPA 路由，打开同一个表单弹窗））。 */
.fb-float { position: fixed; right: 20px; bottom: 24px; z-index: 250; background: #fff;
            color: var(--text); border: 1px solid var(--border); border-radius: 999px;
            padding: 11px 20px; font-size: 14px; box-shadow: 0 4px 14px rgba(0, 0, 0, .12); }
.fb-float:hover { color: var(--primary); border-color: var(--primary); }
@media (max-width: 760px) {
  .fb-float { right: 14px; bottom: 14px; padding: 9px 16px; font-size: 13px; }
  .auth-area { margin-left: 0; }
}

/* ---------- 容器 / 面包屑 ---------- */
.container { max-width: 1100px; margin: 24px auto 0; padding: 0 20px; }
.crumb { font-size: 13px; color: var(--text-3); margin-bottom: 12px; }
.crumb a { color: var(--text-2); }
.crumb a:hover { color: var(--primary); }

/* ---------- 正文 ---------- */
h1 { font-size: 22px; font-weight: 700; line-height: 1.4; margin: 0 0 6px; }
h2 { font-size: 17px; font-weight: 700; margin: 18px 0 8px;
     padding-left: 10px; border-left: 4px solid var(--primary); }
h3 { font-size: 15px; font-weight: 600; margin: 14px 0 6px; }
p { margin: 8px 0; }
p.lead { color: var(--text-2); font-size: 13px; margin: 0 0 14px; }
strong { font-weight: 600; }
ul { padding-left: 22px; margin: 6px 0; }
li { margin: 3px 0; }

/* ---------- 紧凑链接清单（标段清单）：多列 + 单行省略 ---------- */
/* 标段清单动辄几十到几百条，一列到底会把页面拉到上万像素（实测组合页 878 条约 23,870px、
   月度页 170 条约 4,780px）。改成多列后高度按列数分摊，链接一个不少。
   ⚠️ 多列必须同时保证「每条只占一行」，否则条目在窄列里折行，高度会回到原样。
   做法：li 用 flex 单行，a 占满剩余宽度并 overflow 省略，金额用固定宽度的 .amt 右挂。
   完整名称仍在 HTML 里（只是视觉截断），锚文本不缩水。 */
ul.lk { list-style: none; padding-left: 0; }
ul.lk li { display: flex; align-items: baseline; gap: 8px; margin: 3px 0; break-inside: avoid; }
ul.lk li::before { content: '·'; flex: 0 0 auto; width: 12px; color: var(--text-3); }
ul.lk li > a { flex: 1 1 auto; min-width: 0; overflow: hidden;
               text-overflow: ellipsis; white-space: nowrap; }
ul.lk li > .amt { flex: 0 0 auto; color: var(--text-3); font-size: 12px;
                  font-variant-numeric: tabular-nums; }
ul.lk.c2 { columns: 2; column-gap: 32px; }
ul.lk.c3 { columns: 3; column-gap: 26px; }

/* ---------- 表格（对齐 SPA 卡片表：无竖线、表头浅灰、行底边线、圆角）---------- */
/* 行距 10px→8px：这些页常有 30~200 行的表，每行省 4px 在长表上就是数百像素 */
table { width: 100%; border-collapse: separate; border-spacing: 0;
        background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
        overflow: hidden; font-size: 13px; margin: 8px 0 16px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: none; }
th { background: #fafbfc; color: var(--text-2); font-weight: 600; }
tbody tr:hover { background: #fafbfc; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* 表格横向滚动容器：窄屏下宽表（t-sec、标段页 11 列投标人表）靠它滚动而不撑破页面。
   ⚠️ 滚动必须由**外层容器**承担 —— 不能把 overflow-x 加在 table 自己身上，
      因为那样 table 就成了滚动容器，再给它 min-width 会把容器本身撑宽、overflow 永不触发。 */
.t-wrap { overflow-x: auto; }

/* 标段表格（预览榜）：固定列宽 + 序号/日期/金额不折行，剩余宽度全给标段名称列。
   ⚠️ 不加这组规则时 table-layout:auto 会把宽度几乎全部分给长名称列，
   把序号/中标公示时间/中标价挤到折行（实测「13.31 亿元」断成两行、表头也断行）。 */
table.t-sec { table-layout: fixed; }
table.t-sec th { white-space: nowrap; }
table.t-sec th:nth-child(1), table.t-sec td:nth-child(1) { width: 52px; }
table.t-sec th:nth-child(2), table.t-sec td:nth-child(2) { width: 116px; white-space: nowrap; }
table.t-sec th:nth-child(4), table.t-sec td:nth-child(4) { width: 130px; white-space: nowrap; }
/* 标段表格（聚合页的「最近 30 个标段」）—— **与 SPA「查项目」页的 .b-table 逐值一致**：
   2026-09-13 用户要求该表与「查项目」页同表头、同列宽。
   2026-09-14 用户先让「查项目」页去掉工程类型/地区，再让本表也去掉同一批列 →
   两处重新收敛为 **6 列 + 同一组列宽**（5/11/25.33/22.33/22.34/14%，腾出的 16% 均分给
   标段名称/招标人/中标单位）。⚠️ 改列宽时必须与 public/css/style.css 的同名规则逐值同步。*/
table.b-table { table-layout: fixed; width: 100%; }
/* 行高固定 = 与「查项目」页同款（用户 2026-09-13：「行高要固定，说了要和查项目页面的一致」）。
   SPA 的 th/td 是 `padding: 10px 12px; white-space: nowrap`，而行高 = padding 上下 + 行框高度。
   ⚠️ 两个坑，缺一个行高就对不上：
      1) 不写 white-space:nowrap，长标段名会折成 2~3 行 → 行高参差 + 视觉一片乱；
      2) 本页 body 是 line-height:1.65（SPA 是 1.6），不显式写死则每行差 0.65px、30 行累计 ~20px。
   故此处显式 padding 10/12 + line-height 1.6 + nowrap，与 style.css 的 `.b-table` 逐值等价。*/
table.b-table th, table.b-table td { padding: 10px 12px; line-height: 1.6;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
table.b-table th:nth-child(1) { width: 5%; }
table.b-table th:nth-child(2) { width: 11%; }
table.b-table th:nth-child(3) { width: 25.33%; }
table.b-table th:nth-child(4) { width: 22.33%; }
table.b-table th:nth-child(5) { width: 22.34%; }
table.b-table th:nth-child(6) { width: 14%; }
/* 投标人明细表（11 列）：
   固定 px 列宽（colgroup，由 gen_seo_bids.BIDDER_COLS 输出 —— **唯一真源**）
   + 表格宽度由 gen_seo_bids 内联 style="width:1250px" 显式给出（= 列宽之和）。
   🔴 **不要把表宽交回 `width: max-content`**（2026-10-01 踩坑）：fixed 布局下 max-content
      是按**内容**算的 —— 一个 100+ 字的联合体投标人名把「投标人」列从 230px 撑到 1314px、
      整表 2214px（容器才 1018px），列宽声明形同虚设。内联 px 才是「列宽固定」的保证；
      下面这条 max-content 只作兜底（万一某页缺内联属性，至少不会塌成折行）。
   ⚠️ 表头**单独**收紧左右内边距：全局 th 是 12px，8 字表头（评标专家推优组别）需
      104+8 = 112px，故该列取 140px、「是否进入票选范围」取 120px（112 ≤ 120，余量 8）。
   ⚠️ 显式写 padding/line-height：本页 body 是 line-height:1.65、SPA 是 1.6，
      不写死行高就会比站内版每行高 0.65px（同 .b-table 的坑）。*/
table.bd-table { table-layout: fixed; width: max-content; min-width: 100%; }
table.bd-table th, table.bd-table td { padding: 10px 12px; line-height: 1.6;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; vertical-align: middle; }
table.bd-table th { padding: 10px 4px; }
/* 横向拖动条加粗常驻（11 列在 1020px 容器里放不下，右侧 3 列要靠拖动查看） */
.bd-scroll { overflow-x: auto; scrollbar-gutter: stable; }
.bd-scroll::-webkit-scrollbar { height: 10px; }
.bd-scroll::-webkit-scrollbar-track { background: #f2f3f5; border-radius: 5px; }
.bd-scroll::-webkit-scrollbar-thumb { background: #c4c8ce; border-radius: 5px; }
.bd-scroll::-webkit-scrollbar-thumb:hover { background: #9099a4; }
/* 末行也要保留底边线：全局有 `tbody tr:last-child td { border-bottom: none }`，
   那会让最后一行比别的行矮 1px（SPA 那边没有这条）。表格卡有 20px 内边距，
   底边线离卡片圆角还远，画上不会难看 —— 换来的是与站内版**逐像素同高**。*/
table.bd-table tbody tr:last-child td,
table.b-table tbody tr:last-child td { border-bottom: 1px solid var(--border); }

/* ---------- 红色警示表（/warnings 栏目页）----------
   列宽 / 对齐 / 截断策略**逐值抄** public/css/style.css 的 .warn-table
   （5 列：序号 / 责任主体 / 警示期限 / 警示事由 / 警示部门；本页没有「状态」列 —— 状态由分节标题承担）。
   ⚠️ table-layout:fixed + 百分比列宽 + nowrap/ellipsis **三者缺一不可**：警示事由最长 347 字，
      用 auto 布局会把宽度几乎全分给该列，把「警示期限」「警示部门」挤成折行（与 t-sec 同一个坑）。
      截断只发生在**视觉层**，完整文本仍在 DOM 里（爬虫可读、可参与收录），
      另外挂 title 属性供 hover 查看 —— 所以「截断」不影响可抓取内容。
   ⚠️ 显式写 padding/line-height：本页 body 是 line-height:1.65、SPA 是 1.6，
      不写死行高就会比站内版每行高（同 b-table 的坑）。 */
table.warn-table { table-layout: fixed; width: 100%; }
table.warn-table th, table.warn-table td { padding: 10px 12px; line-height: 1.6;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; }
table.warn-table th:nth-child(1), table.warn-table td:nth-child(1) { width: 7%; }
table.warn-table th:nth-child(2), table.warn-table td:nth-child(2) { width: 25%; }
table.warn-table th:nth-child(3), table.warn-table td:nth-child(3) { width: 18%; }
table.warn-table th:nth-child(4), table.warn-table td:nth-child(4) { width: 35%; }
table.warn-table th:nth-child(5), table.warn-table td:nth-child(5) { width: 15%; }
/* 警示事由列左对齐（SPA 侧用户明确要求「内容不用居中」），表头仍居中 */
table.warn-table td:nth-child(4) { text-align: left; padding-left: 14px; padding-right: 14px; }

/* ---------- 三状态标签页（警示中 / 到期 / 撤销）· 纯 CSS，零 JS ----------
   与 SPA 的 .warn-tabs / .warn-tab 逐值一致（见 public/css/style.css）。

   ⚠️ 为什么用 <input type=radio> + <label> 而不是 <button>：预渲染页**不加载任何 JS**
      （这是爬虫可见的前提），所以「点击切换」只能用 CSS 的 :checked 实现。
      三个 radio 与 .warn-tabs / .warn-sec 必须是**兄弟节点**，靠通用兄弟选择器 `~` 联动
      （不能用 :has()，项目红线：需 Chrome 105+）。
   ⚠️ 默认显示「警示中」（与 SPA 的默认 tab 一致）。被隐藏的两段用 display:none，
      但**表格仍在 HTML 里**（爬虫读源码/渲染 DOM 都能拿到 106 行全量）——
      若 CSS 加载失败则三段全显示，信息不丢（优雅降级）。
   ⚠️ radio 用 opacity:0 + 1px 尺寸隐藏而**不是 display:none**：后者会让键盘无法聚焦、方向键无法切换。 */
.warn-radio { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.warn-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 4px; }
.warn-tab { padding: 6px 16px; border-radius: 999px; border: 1px solid var(--border);
  background: #fff; color: var(--text-2); font-size: 13px; cursor: pointer;
  transition: all .15s; }
.warn-tab:hover { border-color: var(--primary); color: var(--primary); }
.warn-sec { display: none; }
#warn-now:checked ~ .warn-tabs label[for="warn-now"],
#warn-exp:checked ~ .warn-tabs label[for="warn-exp"],
#warn-rev:checked ~ .warn-tabs label[for="warn-rev"] {
  background: var(--primary); color: #fff; border-color: var(--primary); }
#warn-now:checked ~ #sec-now,
#warn-exp:checked ~ #sec-exp,
#warn-rev:checked ~ #sec-rev { display: block; }
#warn-now:focus-visible ~ .warn-tabs label[for="warn-now"],
#warn-exp:focus-visible ~ .warn-tabs label[for="warn-exp"],
#warn-rev:focus-visible ~ .warn-tabs label[for="warn-rev"] {
  outline: 2px solid var(--primary); outline-offset: 2px; }
/* 部门列的可点文本（跳到 /warnings?dept=…）—— 与 style.css 的 td.link-cell 同观感 */
table.warn-table td a { color: var(--primary); }
table.warn-table td a:hover { text-decoration: underline; }
/* 图表元素可点击（数据穿透）时的指针提示 */
.pie path[data-drill], .hbar-row[data-drill], .hbar-row[data-sec] { cursor: pointer; }
.hbar-row[data-drill]:hover .hbar-bar, .hbar-row[data-sec]:hover .hbar-bar { filter: brightness(.94); }
/* 面积图的数据点：真正可点的是那条**透明整列热区**（见 area_block 的 drill 参数），
   圆点本身只有 2.6px 半径、点不中。 */
.ac-hit { cursor: pointer; }
/* 表内可点文本（招标人 / 中标单位）—— **逐值复刻 public/css/style.css 的 span.link-cell**。
   ⚠️ 刻意用 span[data-tender|data-company] 而**不是真锚链接**：
   /tenders/{名称} 与 /companies/{名称} 没有预渲染页，nginx 会兜到 SPA 外壳（5.5KB、标题同首页），
   对爬虫是无内容的薄页 —— 给 14 个地区页各挂 30 行 ×2 列这种链接只会浪费抓取预算。
   这与 SPA 的做法一致（查项目页的这两列也是 span + 点击赋值）。跳转由 agg_filter.js 的事件委托承担：
   location.href = '/tenders|/companies/' + encodeURIComponent(name)（整页跳到 SPA 路由）。*/
span.link-cell { display: inline; color: var(--primary); cursor: pointer; }
span.link-cell:hover { text-decoration: underline; }
.unit-sep { color: var(--text-3); margin: 0 3px; }
/* 状态标签 —— **逐值复刻 public/css/style.css 的 .tag 系列**（投标人明细的结果列用它）；
   配色规则同 UI.statusTag：是/合格/中标 = 绿，不合格/不予受理/废标 = 红，其余（含空）= 灰。*/
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
.tag-ok { background: #dcfce7; color: #166534; }
.tag-fail { background: #fee2e2; color: #991b1b; }
.tag-gray { background: #f3f4f6; color: #6b7280; }
/* ---------- 标段详情页：区块标题 + 详情网格 ----------
   ⚠️ 与 public/css/style.css 的 .section-title / .detail-grid / .detail-item **逐值一致**
   （2026-09-13 用户要求「静态版页面样式要和站内版完全一致」：站内点击进详情走 SPA、
   直接打开或刷新走本静态页，两处视觉必须相同）。
   预渲染页的 h2 带全局左蓝条/缩进，故 h2.section-title 需先显式归零再套用 SPA 的视觉。*/
h2.section-title { font-size: 17px; font-weight: 700; margin: 24px 0 12px; padding: 0 0 0 10px;
                   border: 0; border-left: 4px solid var(--primary); color: var(--text); }
.detail-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px;
               background: var(--border); border: 1px solid var(--border);
               border-radius: var(--radius); overflow: hidden; }
.detail-item { background: #fff; padding: 12px 16px; }
.detail-item .label { color: var(--text-3); font-size: 12px; }
.detail-item .value { font-size: 14px; margin-top: 2px; word-break: break-all; }
.detail-full { grid-column: 1 / -1; }
.bd-titlebar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.bd-title-text { min-width: 0; }
@media (max-width: 768px) { .detail-grid { grid-template-columns: 1fr; } }

/* ---------- KPI ---------- */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
        gap: 12px; margin: 14px 0 18px; }
.kpi { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
       padding: 14px 16px; box-shadow: var(--shadow); }
.kpi b { display: block; font-size: 22px; font-weight: 700; line-height: 1.2;
         font-variant-numeric: tabular-nums; }
.kpi span { font-size: 13px; color: var(--text-2); }

/* 首页「市场洞察」纯文本区块（P1-4，2026-09-17）。
   ⚠️ 与 SPA 的 public/css/style.css 里的 .insight-list 同名同款 —— 两处改要一起改，
      否则同一个网站在用户版与爬虫版会呈现不同样式。 */
.insight-list { list-style: none; margin: 0 0 18px; padding: 0; }
.insight-list li { position: relative; padding-left: 16px; margin-bottom: 8px;
  font-size: 13.5px; line-height: 1.75; color: var(--text-2); }
.insight-list li:last-child { margin-bottom: 0; }
.insight-list li::before { content: ''; position: absolute; left: 2px; top: 9px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--primary); }
.insight-list b { color: var(--text); font-weight: 600; }

/* ---------- 报表卡片体系（与 SPA public/css/style.css 同名同款） ---------- */
/* 「其他页面」（SPA 的首页/查项目/查招标人/查投标人/各详情页）的报表统一是：
   外层 .chart-grid-2 双列栅格，里面每格是一个 .card，card 内首行是 .chart-title 标题。
   即 **两列等宽 + 每卡 min-height:280px + 标题在卡片内**。预渲染页沿用同一套类名与同一组
   数值（gap 16 / padding 20 / min-height 280 / 标题 15px·600），因此聚合页、栏目页与 SPA
   的报表在形状、大小、风格上完全一致。
   ⚠️ 注释里不要写示例标签（如 div class="card"）—— 它会让按标签扫描的校验脚本
   把注释当成真实元素（本项目实测踩过）。 */
.card { background: var(--card); border: 1px solid var(--border);
        border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow); }
/* ⚠️ 轨道必须写 minmax(0, 1fr) 而不是 1fr：`1fr` 的隐含最小值是 auto = min-content，
   而条形图的名称列是 flex:0 0 46%（不可收缩）+ nowrap，其 max-content（超长标段名）
   会把轨道顶宽，导致整个栅格溢出容器（实测 1280 视口下 scrollW 由 1280 变 1351）。
   minmax(0,1fr) 把轨道最小值钉成 0，百分比宽度才在「确定宽度」下解析、名称正常省略。 */
.chart-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
                gap: 16px; margin-bottom: 16px; }
.chart-grid-2 .card { min-height: 280px; }
/* 同一行的卡片由栅格拉成**等高**（用户要求「形状大小保持一致」）。
   2026-09-13 起三类图形（圆环/条形/面积）的图形区都固定 250px = SPA `.chart-box` 的高度，
   卡片自然都是 328px，不再需要用 flex 拉伸去填平高度差；只剩「内容比同行矮」时的兜底：
   让图区吸收多余高度、内容仍贴着顶部（与 ECharts 的行为一致）。 */
.chart-grid-2 > .card { display: flex; flex-direction: column; }
.chart-grid-2 > .card > .pie-stage,
.chart-grid-2 > .card > .hbar { flex: 0 0 auto; }
.chart-row-full { margin-bottom: 16px; }
/* 标题用 h2 而非 SPA 的 div：视觉走 .chart-title，但保留标题语义（SEO 可读的层级）。
   必须把全局 h2 的左侧蓝条/padding 归零，否则与 SPA 的 chart-title 不同。 */
.chart-title { font-size: 15px; font-weight: 600; margin: 0 0 12px; padding: 0; border: 0; }
.chart-title-hint { color: var(--text-3); font-size: 12px; font-weight: 400; margin-left: 2px; }
/* 卡片内的表格/清单：边框与底色由卡片提供，自身不再重复一层卡片外框 */
.card table { border: 0; border-radius: 0; background: transparent; margin: 0; }

/* ---------- 扇形（圆环）图 · 纯静态 SVG + 引出标签 ---------- */
/* 逐项复刻 SPA 的 `Charts.pie`（public/js/charts.js）：圆环 + 引出标签，**没有图例**
   （SPA 各页调用一律传 {legend:false}）。图形区 480×250 = SPA `.chart-box` 的尺寸，
   所以卡片高度（328px）与首页/查招标人/查投标人/详情页完全一致。
   图形由 pie_block() 服务端算好弧线；引出标签是**绝对定位的 HTML**（不是 SVG text），
   这样标签里的类型名仍是真 <a> —— /area/{区}/{类型} 那 95 个组合页靠它入链。 */
.pie-stage { position: relative; width: 100%; max-width: 480px; margin: 0 auto; }
/* width:100% + height:auto：viewBox 自带 480:250 比例（与 .ac 同一手法，不依赖 aspect-ratio，
   老内核浏览器也认）。高度必须由 SVG 的固有比例撑出来，标签的百分比定位才对得上。 */
.pie { display: block; width: 100%; height: auto; }
.pie-total { font-size: 25px; font-weight: 700; fill: var(--text);
             font-variant-numeric: tabular-nums; }
.pie-unit { font-size: 12px; fill: var(--text-3); }
.pie-lead { fill: none; stroke: #cbd5e1; stroke-width: 1; }
.pie-lab { position: absolute; transform: translateY(-50%); font-size: 12px;
           line-height: 1.2; color: #4b5563; white-space: nowrap; }
.pie-lab a { color: inherit; }
.pie-lab a:hover { color: var(--primary); }
/* 窄屏兜底图例：桌面端隐藏（见文件末尾媒体查询）。留着它还有一个好处 ——
   同一批类型链接多一条通路，不依赖引出标签的定位是否被浏览器正确处理。 */
.pie-fb { display: none; }
.pie-legend { list-style: none; padding: 0; margin: 0; display: grid;
              grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 5px 20px; }
.pie-legend li { display: flex; align-items: baseline; gap: 7px; font-size: 13px; min-width: 0; }
.pie-legend i { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; align-self: center; }
.pie-legend a, .pie-legend .nm { flex: 1 1 auto; min-width: 0; overflow: hidden;
                                 text-overflow: ellipsis; white-space: nowrap; }
.pie-legend b { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.pie-legend span { flex: 0 0 auto; width: 48px; text-align: right;
                   color: var(--text-3); font-size: 12px; }

/* ---------- 横向条形图（HTML/CSS 复刻 SPA 的 `Charts.hbar`） ---------- */
/* 几何逐项对齐 charts.js：类目轴名称右对齐/12px #6b7280/150px 截断、数值轴 0 起 + nice 刻度
   （#f0f1f3 splitLine）、条高 18px ≈ barMaxWidth 22、圆角 [0,4,4,0]、颜色随值连续变化、
   条尾数值 11px #374151。整个图形区固定 250px 高 = SPA `.chart-box{height:250px}`。
   ⚠️ 名称列 150px 与右侧 40px 预留必须与 .hbar-grids / .hbar-axis 的左右定位一致，
      三处是同一套刻度：左边缘 150px、右边缘 40px（数值标签写在那 40px 里）。 */
.hbar { position: relative; height: 250px; padding-bottom: 20px; box-sizing: border-box;
        display: flex; flex-direction: column; }
.hbar-grids { position: absolute; left: 150px; right: 40px; top: 0; bottom: 20px;
              border-left: 1px solid #e0e6f1; }
.hbar-grids i { position: absolute; top: 0; bottom: 0; width: 1px; background: #f0f1f3; }
.hbar-rows { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
.hbar-row { flex: 1 1 0; display: flex; align-items: center; min-height: 0; }
.hbar-name { flex: 0 0 150px; padding-right: 10px; box-sizing: border-box; text-align: right;
             font-size: 12px; color: #6b7280; overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
.hbar-name a, .hbar-name span { color: inherit; }
.hbar-cell { flex: 1 1 auto; position: relative; display: flex; align-items: center;
             padding-right: 40px; box-sizing: border-box; }
.hbar-bar { position: relative; display: block; height: 18px; border-radius: 0 4px 4px 0; }
/* ⚠️ 数值标签包在 <i class="hbar-bar"> 里，而 <b> 只重置 font-weight、**不重置 font-style**，
   于是继承了浏览器对 <i> 的默认 italic → 图上数值全是斜体（2026-09-14 用户反馈
   「数值不要显示为斜体」）。故这里必须显式 font-style: normal；
   若日后把外层 <i> 换成 <span>，本行仍要保留（防回退）。 */
.hbar-bar b { position: absolute; left: 100%; margin-left: 5px; top: 50%;
              transform: translateY(-50%); font-size: 11px; font-weight: 400;
              font-style: normal;
              color: #374151; font-variant-numeric: tabular-nums; }
.hbar-axis { position: absolute; left: 150px; right: 40px; bottom: 0; height: 20px;
             border-top: 1px solid #e0e6f1; }
.hbar-tick { position: absolute; top: 3px; transform: translateX(-50%);
             font-size: 12px; color: #6b7280; }

/* ---------- 面积图（纯静态 SVG，季度趋势用） ---------- */
/* 图形由 gen_seo_pages.area_block() 服务端算好路径，不依赖 JS。
   规范对齐 public/js/charts.js 的面积图（两处要保持一致，否则 SEO 页与 SPA 观感不同）：
     · 线色 #3b82f6、面积填充 opacity .22、折线平滑；
     · **不画 Y 轴刻度数字**（用户 2026-09-11 明确要求「所有面积图和曲线图，取消 Y 轴」），
       数值全部由数据点标签承载，#374151；保留横向网格线（#f0f1f3）作高度参考。 */
/* width:100% + height:auto：viewBox 自带 2.4:1 比例，图形随卡片宽度自适应缩放 */
.ac { display: block; width: 100%; height: auto; overflow: visible; }
.ac-gridline { stroke: #f0f1f3; stroke-width: 1; }
.ac-val { font-size: 12px; fill: #374151; font-variant-numeric: tabular-nums; }
.ac-x { font-size: 12.5px; fill: var(--text-3); }

/* ---------- 侧栏筛选组件（静态链接版） ---------- */
/* 视觉与 SPA public/css/style.css 的 .home-filter 一族**同名同款**（.filter-group /
   .filter-group-title / .filter-count / .filter-panel / .filter-quick / .quick-opt /
   .filter-reset 全部沿用），保证「查项目」页与聚合页看起来是同一个组件。

   两处**刻意不同**，都是为了「无 JS」这个前提：
   1. 选项是真 a 链接，不是复选框 —— 预渲染页没有 JS，复选框点了不会有任何反应，是死 UI；
      而链接既能真的完成筛选（跳到对应聚合页），爬虫也走得到。
   2. 分组用 details[open] 而非折叠 div —— 原生折叠不需要 JS，且默认展开保证选项
      在 HTML 里就是可见内容（纯 CSS 隐藏的内容对搜索引擎权重更低）。 */
/* 侧栏定位与 SPA 的 .home-filter **逐值对齐**（那是站内其它页面「筛选」面板的标准位置）：
   fixed 于视口右缘 18px、垂直居中、宽 200px、限高自身滚动、z-index 998、同一档阴影。
   因此聚合页的筛选面板与「查项目」等页面出现在**完全相同的位置**。
   容器宽度保持 1100、不再加宽 —— 面板已脱离文档流，正文列与其它预渲染页等宽居中。
   ⚠️ 正因为「逐值对齐」，这里与 SPA 会**同患一个毛病**：贴视口右缘的 fixed 浮层在
   1081~1496px 视口会压住正文列（溢出量 = 748 − vw/2）。已在下方把断点一并上调到 1520px，
   两边必须同值 —— 只改一边会让「筛选面板的位置」在 SPA 与预渲染页之间对不上。 */
.page-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.page-main { min-width: 0; }
.page-side { position: fixed; right: 18px; top: 50%; transform: translateY(-50%);
             width: 200px; max-height: calc(100vh - 120px); overflow-y: auto; z-index: 998;
             background: var(--card); border: 1px solid var(--border);
             border-radius: var(--radius); padding: 12px 10px;
             box-shadow: 0 6px 24px rgba(15, 23, 42, .12); }

/* .fpanel 不再自带卡片外观：卡片职责上移到 .page-side，与 SPA 一致
   （那边 .home-filter 本身就是卡片，内部直接放 .home-filter-head 与 .filter-group），
   否则会出现「卡中卡」的双层边框与双层阴影。 */
.fpanel { background: transparent; border: 0; border-radius: 0; padding: 0; box-shadow: none; }
.fpanel-head { display: flex; align-items: center; justify-content: space-between;
               margin-bottom: 10px; }
.fpanel-head > span { font-size: 14px; font-weight: 700; color: var(--text); }
.filter-reset { border: 1px solid var(--border); background: transparent; color: var(--text-2);
                font-size: 12px; padding: 3px 10px; border-radius: 6px; transition: all .15s; }
.filter-reset:hover { color: var(--primary); border-color: var(--primary); }
.filter-group { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px;
                overflow: hidden; }
.filter-group-title { display: flex; align-items: center; font-size: 13px; font-weight: 600;
                      color: var(--text); padding: 9px 10px; background: #f7f8fa;
                      cursor: pointer; user-select: none; list-style: none; }
.filter-group-title::-webkit-details-marker { display: none; }
.filter-group-title::after { content: '▸'; color: var(--text-2); font-size: 11px;
                             transition: transform .15s; }
.filter-group.open .filter-group-title::after { transform: rotate(90deg); }
.filter-count { font-size: 11px; font-weight: 400; color: var(--text-2);
                margin-left: auto; padding-right: 4px; }
/* 折叠：**默认展开**（无 JS 时选项必须可见、可被爬虫读到），
   只有 agg_filter.js 加载后给 <html> 加了 .js-ready 才收起未展开的组
   —— 与 SPA 的 .filter-group.open 同一套语义（那边是默认收起，靠 JS 才有内容）。 */
.filter-panel { padding: 8px 10px; max-height: 300px; overflow: auto; }
html.js-ready .filter-group:not(.open) > .filter-panel { display: none; }
/* 复选框选项（2026-10-20 新增）：与 public/css/style.css 的 .filter-check 逐值一致 */
.filter-check { display: flex; align-items: center; gap: 7px; font-size: 13px;
                color: var(--text); padding: 4px 2px; cursor: pointer; }
.filter-check input { width: 14px; height: 14px; accent-color: var(--primary); cursor: pointer; }
.filter-quick-label { font-size: 12px; color: var(--text-3); margin: 8px 2px 6px; }
.filter-quick-label:first-child { margin-top: 0; }
.filter-quick { display: flex; flex-wrap: wrap; gap: 6px; }
.quick-opt { border: 1px solid var(--border); background: #fff; color: var(--text);
             font-size: 12px; padding: 5px 10px; border-radius: 6px; transition: all .15s;
             white-space: nowrap; }
a.quick-opt:hover { color: var(--primary); border-color: var(--primary); }
.quick-opt.active { background: var(--primary); color: #fff; border-color: var(--primary);
                    font-weight: 600; }
a.quick-opt.active:hover { color: #fff; }
.fpanel-note { font-size: 11px; color: var(--text-3); line-height: 1.5; margin: 6px 2px 0; }
/* 「自定义日期」原生表单：规格对齐 SPA 的 .filter-date（列向、12px 标签 + 输入框） */
.filter-date { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.filter-date label { font-size: 12px; color: var(--text-2); display: flex;
                     align-items: center; gap: 6px; }
.filter-date input { flex: 1; min-width: 0; border: 1px solid var(--border);
                     border-radius: 6px; padding: 4px 6px; font-size: 12px;
                     color: var(--text); font-family: inherit; }
/* 表单提交按钮（现仅「工程类型」多选在用 —— 日期区间已改为 onchange 即时提交、无按钮） */
.form-go { border: 1px solid var(--border); background: #fff; color: var(--text);
           font-size: 12px; padding: 5px 10px; border-radius: 6px; cursor: pointer;
           font-family: inherit; transition: all .15s; }
.form-go:hover { color: var(--primary); border-color: var(--primary); }
/* 「工程类型」多选：把原生 checkbox 做成芯片 —— 预渲染页没有 JS，
   复选框是唯一能真正实现「可多选」的原生控件。
   ⚠️ 选中态用 `input:checked + .quick-opt`，**不要用 :has()** ——
   :has() 需要 Chrome 105+，站内用户环境含 360 极速浏览器等老内核，整条规则会失效。 */
.filter-type { margin: 0; }
.filter-type .filter-quick { margin-bottom: 8px; }
.quick-check { position: relative; display: inline-flex; }
.quick-check input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0;
                     pointer-events: none; }
.quick-check input:checked + .quick-opt { background: var(--primary); color: #fff;
                                          border-color: var(--primary); font-weight: 600; }
.quick-check input:focus-visible + .quick-opt { outline: 2px solid var(--primary);
                                                outline-offset: 1px; }

/* ---------- 标段清单（2026-10-20：与「查项目」页同款 7 列 + 分页 + 状态标签） ---------- */
/* 列序与 views/bids.js 的 .b-table.with-sel 相同，只是**去掉了勾选列**（聚合页不做多选导出）
   ⇒ 原 6% 序号 / 7% 状态 / 10% 公告发布时间 / 28% 标段名称 / 8% 工程类型 / 26% 招标人 / 15% 招标估价。
   ⚠️ 用修饰类 .agg-list **叠加覆盖**基类 .b-table 的 6 列宽度（基类规则不动，其它页可能复用）；
      窄列同样把左右 padding 收到 4px（理由见 style.css 同名注释：1024 视口下 24px 内边距会挤爆表头）。 */
.b-table.agg-list th:nth-child(1) { width: 6%; }    /* 序号 */
.b-table.agg-list th:nth-child(2) { width: 7%; }    /* 状态 */
.b-table.agg-list th:nth-child(3) { width: 10%; }   /* 公告发布时间 */
.b-table.agg-list th:nth-child(4) { width: 28%; }   /* 标段名称 */
.b-table.agg-list th:nth-child(5) { width: 8%; }    /* 工程类型 */
.b-table.agg-list th:nth-child(6) { width: 26%; }   /* 招标人 */
.b-table.agg-list th:nth-child(7) { width: 15%; }   /* 招标估价（万元） */
.b-table.agg-list th:nth-child(1), .b-table.agg-list td:nth-child(1),
.b-table.agg-list th:nth-child(2), .b-table.agg-list td:nth-child(2),
.b-table.agg-list th:nth-child(3), .b-table.agg-list td:nth-child(3),
.b-table.agg-list th:nth-child(5), .b-table.agg-list td:nth-child(5),
.b-table.agg-list th:nth-child(7), .b-table.agg-list td:nth-child(7) {
  padding-left: 4px; padding-right: 4px;
}
.b-table.agg-list td.agg-idx { color: var(--text-3); }
.b-table.agg-list td.st-cell { white-space: nowrap; }
/* 状态四态标签 —— 与 public/css/style.css 的 .st-tag 一族逐值一致 */
.st-tag { display: inline-block; font-size: 12px; line-height: 18px; padding: 1px 6px;
          border-radius: 3px; vertical-align: middle; white-space: nowrap;
          border: 1px solid transparent; }
.st-tag.st-done { color: #5b6b7c; background: #eef1f5; border-color: #dfe4ea; }
.st-tag.st-ongoing { color: #1c5fa8; background: #e8f1fc; border-color: #c3ddf8; }
.st-tag.st-lost { color: #8a6100; background: #fdf4e3; border-color: #f2ddb5; }
.st-tag.st-stopped { color: #8a4b4b; background: #f7eeee; border-color: #e8d3d3; }
/* 结果计数条 —— 与 style.css 的 .result-count 同款；.agg-hint 是右侧的排序/分页口径提示 */
.result-count { color: var(--text-2); font-size: 13px; margin: 4px 0 12px; }
.result-count b { color: var(--primary); }
.agg-hint { color: var(--text-3); font-size: 12px; margin-left: 10px; }
/* 分页器 —— 与 public/css/style.css 的 .pagination 逐值一致（UI.pager 的输出结构） */
.pagination { display: flex; align-items: center; gap: 6px; margin-top: 16px; flex-wrap: wrap; }
/* 分页控件双形态（2026-10-25，移动端方案 B）—— 与 public/css/style.css 逐值一致。
   聚合页的静态分页（gen_seo_aggregates.list_block）与 agg_filter.js 动态分页
   都输出这两套，默认只显示桌面版，≤768px 反过来。 */
.pg-desk { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
.pg-mob { display: none; }
.pg-mob .pg-nav {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; justify-content: space-between;
  height: 44px; padding: 0 2px;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  white-space: nowrap;
}
.pg-mob .pg-ar {
  flex: 0 0 auto; width: 40px; height: 40px; padding: 0; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; border-radius: 6px;
  font-size: 20px; line-height: 1; color: var(--primary); cursor: pointer;
}
.pg-mob .pg-ar:disabled { color: #d4d8de; cursor: not-allowed; }
.pg-mob .pg-cur { flex: 1 1 auto; text-align: center; font-size: 14px; color: var(--text-2); }
.pg-mob .pg-cur b { color: var(--text); font-weight: 600; }
.pg-mob .pg-total { flex: 0 0 auto; font-size: 12px; color: var(--text-3); }
.pagination .page-size-sel { display: inline-flex; align-items: center; margin-right: 4px; }
.pagination .page-size-sel select { border: 1px solid var(--border); border-radius: 6px;
  padding: 4px 8px; font-size: 13px; background: #fff; color: var(--text); cursor: pointer;
  outline: none; transition: border-color .15s; }
.pagination .page-size-sel select:focus { border-color: var(--primary); }
.pagination button { padding: 6px 12px; border: 1px solid var(--border); background: #fff;
  border-radius: 6px; cursor: pointer; color: var(--text-2); font-size: 13px; }
.pagination button:hover:not(:disabled) { color: var(--primary); border-color: var(--primary); }
.pagination button.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.pagination button:disabled { opacity: .4; cursor: not-allowed; }
.pagination .info { color: var(--text-3); font-size: 13px; margin-left: 8px; }
.empty { text-align: center; padding: 40px 0; color: var(--text-3); }

/* ---------- 提示 / FAQ ---------- */
.note { background: #fffbeb; border: 1px solid #fde68a; border-radius: 8px;
        padding: 10px 14px; font-size: 13px; color: #92400e; margin: 14px 0; }
.faq-q { font-size: 15px; font-weight: 600; margin: 18px 0 6px; }
.faq-a { color: var(--text-2); font-size: 14px; margin: 0 0 6px; }
/* 相关标段列表（2026-10-26 用户要求）：正文收到 13px，与投标人明细表 table.bd-table 同号；
   h3.faq-q 小标题保持 15px 不动。行高从 body 的 1.65 收到 1.6（同表格），
   条目间距 3px→2px —— 这个区块动辄列 15~20 条，行距松了会把页面撑很长。
   ⚠️ 只作用于 .rel-list，别改成全局 ul（站内其它列表不在此列）。 */
.rel-list { font-size: 13px; line-height: 1.6; margin: 4px 0 6px; }
.rel-list li { margin: 2px 0; }
.rel-list a { color: var(--primary); text-decoration: none; }
.rel-list a:hover { text-decoration: underline; }

/* ---------- 页脚（与 SPA .footer 同款）---------- */
.footer { text-align: center; padding: 24px 20px; margin-top: 20px;
          color: var(--text-3); font-size: 12px; border-top: 1px solid var(--border); }
.footer .friend-links { margin: 8px 0 4px; }
.footer .friend-links a { color: var(--primary); margin: 0 4px; }
.footer .friend-links a:hover { text-decoration: underline; }
.footer .ver-mark { color: #c0c7d1; font-size: 11px; margin-top: 4px; }

/* 双列报表在 900px 以下回退单列。
   ⚠️ 比 SPA 的 768px 提前：圆环图的引出标签是按「图形区 ≥460px 宽」排的版
   （左右各约 120px 文字 + 环 165px），而 2 列时图形区 = (视口-28-16)/2-40，
   901px 时只剩 388px（最长标签已探出 7px），再窄就会顶到卡片外。
   条形图/面积图没有这个约束，但整块一起切单列，视觉上更整齐。 */
@media (max-width: 900px) {
  .chart-grid-2 { grid-template-columns: 1fr; }
  /* 标段清单（7 列）：768 视口下百分比列宽只有 740px，表头「公告发布时间 / 工程类型 /
     招标估价（万元）」会被 nowrap+ellipsis 静默截成「公告发布…」（实测缺 31px、780px 时「公告发布时间」仍差 8px）。
     给一个最小宽度，让 .t-wrap 横向滚动而不是切碎表头（与手机端 t-sec 同一处理方式）。
     ⚠️ 断点用 900 而不是 760：768 视口也要生效；900 时列宽本就更宽，这条规则自然不触发。 */
  .t-wrap table.b-table.agg-list { min-width: 870px; }
}

/* 单列后图形区 = min(480, 视口-68)。窄于约 380px 时引出标签仍会溢出卡片
   → 隐藏标签、回落到 .pie-fb 里的图例，保证手机上数据仍可读、且不产生横向滚动。
   560px 视口留了充足余量（真实触发点约 448px）。 */
@media (max-width: 560px) {
  .pie-lab, .pie-lead { display: none; }
  .pie-fb { display: block; }
}

/* 🔴 断点 1520px（2026-10-08 与 SPA 同步由 1080px 上调）。原由见 public/css/style.css
   的 @media (max-width:1520px) 注：fixed 右侧浮层在 1081~1496px 会整块压住正文列。
   SPA 此时保留 max-height + 内部滚动，这里同样不覆盖，避免面板占满整个首屏。 */
@media (max-width: 1520px) {
  .page-side { position: static; transform: none; width: 100%; order: -1;
               margin: 0; box-shadow: var(--shadow); }
}

@media (max-width: 760px) {
  .topbar-inner { height: auto; padding: 10px 16px; flex-wrap: wrap; gap: 6px; }
  .nav { gap: 2px; }
  .nav a { padding: 5px 10px; font-size: 14px; border-radius: 6px; }
  .container { padding: 0 14px; }
  .brand-img { height: 34px; }
  /* 宽表（标段页投标人明细 11 列、t-sec 4 列）在窄屏比视口宽，横向滚动交给外层 .t-wrap。
     t-sec 的固定列宽（52+116+130=298px）在 360px 里会把名称列挤到只剩约 34px，
     被压成竖排单字，所以补一个最小宽度。 */
  table.t-sec { min-width: 620px; }
  /* 窄屏放不下多列，回退单列 */
  ul.lk.c2, ul.lk.c3 { columns: 1; }
  /* 报表栅格已在 900px 断点回退单列（见上方 @media(max-width:900px)），此处只需处理图例列数 */
  .pie-legend { grid-template-columns: 1fr; }
  /* 条形图：名称列 150px + 数值轴右预留 40px 在 360px 视口下会把绘图区压得几乎没有宽度。
     窄屏把名称列收到 96px、右预留收到 30px（三处刻度必须一起改：.hbar-grids / .hbar-axis）。 */
  .hbar-name { flex-basis: 96px; }
  .hbar-cell { padding-right: 30px; }
  .hbar-grids, .hbar-axis { left: 96px; right: 30px; }
  /* 面积图在窄屏会按比例缩小（约 0.69×），标签字会跟着缩到 8px 上下；
     这里把 SVG 内的字号按比例放大补偿，保证移动端仍可读。 */
  .ac-val, .ac-x { font-size: 14px; }
}

/* ==========================================================================
   移动端列表卡片化（≤768px）· 2026-10-24
   —— 与 public/css/style.css 末尾的同名块**同一套做法**，此处只服务聚合页
      （/area、/year、/quarter 的「最近 30 个标段」表格 class="b-table agg-list"）。
   改前实测（390px）：7 列被 table-layout:fixed 压到平均 44px/列，标段名称约 110px。
   做法：<table>→block、<tr>→flex column 卡片、<td>→flex 行，字段名走 td::before。
   ⚠️ 聚合页列序与 SPA「查项目」页**不同**（无勾选列、序号在第 1 列）：
      1 序号 / 2 状态 / 3 公告发布 / 4 标段名称 / 5 工程类型 / 6 招标人 / 7 招标估价
      改列序必须同步改下面的 nth-child 与 order。
   ========================================================================== */
@media (max-width: 768px) {
  /* 包列表的 .card 去壳（与 style.css 同理，用 :has() 精确锁定不波及图表卡）*/
  .card:has(> .t-wrap > table.b-table) {
    padding: 0; border: 0; border-radius: 0; box-shadow: none; background: transparent;
  }
  .card:has(> .t-wrap > table.b-table) > .t-wrap { overflow: visible; }

  table.b-table { display: block; width: 100%; table-layout: auto; min-width: 0; }
  /* 🔴 必须覆盖上方 760px 断点里的 `.t-wrap table.b-table.agg-list{min-width:870px}`：
     那条是「横向滚动版」的产物（特异性 0,2,2 > 本条 0,1,1），不覆盖的话
     卡片模式下表格仍被钉在 870px，10 张卡片横向撑爆、右侧被 body 裁掉（实测）。*/
  .t-wrap table.b-table.agg-list { min-width: 0; }
  table.b-table thead { display: none; }
  table.b-table tbody { display: block; }

  table.b-table tr {
    display: flex; flex-direction: column; position: relative;
    margin: 0 0 10px; padding: 12px;
    background: var(--card); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .05);
  }
  table.b-table tbody tr:last-child { margin-bottom: 0; }

  table.b-table td {
    display: flex; align-items: flex-start; flex-wrap: wrap; gap: 8px;
    width: auto !important;
    padding: 6px 0;
    border: 0;
    text-align: left !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
    font-size: 13px; line-height: 1.6; color: var(--text-2);
  }
  table.b-table td::before {
    flex: 0 0 4em; font-size: 12px; font-weight: 400; line-height: 1.75; color: var(--text-3);
  }
  /* 可点击单元格（标段名称 / 招标人）撑到 44px 高 */
  table.b-table td.link-cell { min-height: 44px; align-items: center; }
  /* 聚合页的链接是 td 内部的 <a> 与 <span class="link-cell">（不是 td 本身带类），
     故单独补：整行宽度都可点、高度 44px。改前实测行内链接高 20px，手指点不中。
     ⚠️ 两种链接要分开写：<a> 在标题列（td 是 block）里用 width:100%；
        <span class="link-cell"> 在 flex 的 td 里**不能**用 width:100%，
        否则会被挤到第二行、把这一行撑到 85px（实测）。*/
  table.b-table td > a {
    display: flex; align-items: center; width: 100%; min-height: 44px;
  }
  table.b-table td > .link-cell {
    display: flex; align-items: center;
    flex: 1 1 auto; min-width: 0; min-height: 44px;
  }

  /* 主标题 = 标段名称（第 4 列） */
  table.b-table td:nth-child(4) {
    order: 2; display: block; padding: 0 0 6px;
    font-size: 15px; font-weight: 600; line-height: 1.5;
  }
  table.b-table td:nth-child(4)::before { content: none; }
  /* 状态徽标（第 2 列）置顶独占一行 */
  table.b-table td:nth-child(2) { order: 1; display: block; padding: 0 0 6px; }
  table.b-table td:nth-child(2)::before { content: none; }
  /* 序号（第 1 列）卡片里没有意义 */
  table.b-table td:nth-child(1) { display: none; }

  table.b-table td:nth-child(3) { order: 3; }
  table.b-table td:nth-child(5) { order: 4; }
  table.b-table td:nth-child(6) { order: 5; }
  table.b-table td:nth-child(7) { order: 6; }
  table.b-table td:nth-child(3)::before { content: '公告时间'; }
  table.b-table td:nth-child(5)::before { content: '工程类型'; }
  table.b-table td:nth-child(6)::before { content: '招标人'; }
  table.b-table td:nth-child(7)::before { content: '招标估价'; }

  /* 分页触控补到 44px（agg_filter.js 生成的也是 .pagination，与 SPA 同款）*/
  .pagination { gap: 8px; }
  .pagination button,
  .pagination .page-size-sel select {
    min-width: 44px; min-height: 44px; padding: 10px 12px; font-size: 14px;
  }
  .pagination .info { flex: 1 0 100%; margin: 4px 0 0; }
  /* 分页切到移动版（方案 B）：桌面那套整块收起，只留「‹ 第 x/y 页 › + N 条」一行 */
  .pg-desk { display: none; }
  .pg-mob { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
}

/* 平板（560~768px）两列网格，理由同 style.css：单列会被拉到 700px+ 太散。
   ⚠️ 必须 minmax(0,1fr) + tr{min-width:0}，否则长标段名会把网格撑破。*/
@media (min-width: 560px) and (max-width: 768px) {
  table.b-table tbody {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px; align-content: start;
  }
  table.b-table tbody tr { margin: 0; min-width: 0; }
}

/* ==========================================================================
   标段详情页窄屏两处硬伤（≤768px）· 2026-10-24
   --------------------------------------------------------------------------
   两处都是**既有问题**（新版与 v20261020a 旧 CSS 实测逐值相同，与列表卡片化无关），
   3300 多个标段页 + 所有预渲染页共用，移动端访问必现。
   ---- ① 标题单字竖排：实测 .bd-title-text 仅 34px 宽、高 973px ----
   .bd-titlebar 是 `flex + wrap`，右侧「☆订阅该标段 + 说明文字」带**内联**
   `style="flex-shrink:0;text-align:right"` ⇒ 它占满 312px 不让步，标题 div
   （内联 flex:1 1 0;min-width:0）只剩 362-312-16(gap) = 34px，22px 字号下每行一个字。
   修法：窄屏强制单列、订阅区改为左对齐；内联样式只能用 !important 压。
   ---- ② 顶部菜单竖排：实测 .nav 仅 138px 宽、高 244px ----
   8 个菜单项（48~76px/项）在 138px 里 flex-wrap ⇒ 一行一项。而 SPA 版有汉堡菜单，
   预渲染页没有对应 JS。修法：窄屏让 .nav 独占第二行（flex-basis 100%）并改成
   **横向滑动**（不引 JS，SEO 页面也不该依赖脚本）。
   ========================================================================== */
@media (max-width: 768px) {
  /* ① 顶栏：菜单独占第二行 + 横向滑动 */
  .nav {
    flex: 1 0 100%; order: 3;
    flex-wrap: nowrap; gap: 4px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; padding-bottom: 2px;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { flex: 0 0 auto; white-space: nowrap; }

  /* ② 标段标题区：单列 */
  .bd-titlebar { flex-direction: column; align-items: stretch; gap: 4px; }
  .bd-titlebar > .bd-title-text { flex: 0 0 auto !important; width: 100%; }
  .bd-titlebar > div:last-child {
    flex-shrink: 1 !important;
    text-align: left !important;
    margin-bottom: 0 !important;
  }

  /* ③ 横向条形图的名称列：`.hbar-name` 本体已有 ellipsis，但里面是 inline `<a>`，
     省略号只在**块级容器直接含文本**时生效 ⇒ 长项目名（实测最宽 807px）被硬切、没有「…」。
     让 `<a>` 成为块级并自带 ellipsis 即可。*/
  .hbar-name > a, .hbar-name > span {
    display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* ④ 红色警示表（/seo/warnings.html 与 SPA /warnings 同构 5 列）卡片化
     —— 与 public/css/style.css 的同名块保持一致：序号/责任主体/警示期限/警示事由/警示部门 */
  .card:has(> .t-wrap > table.warn-table) {
    padding: 0; border: 0; border-radius: 0; box-shadow: none; background: transparent;
  }
  .card:has(> .t-wrap > table.warn-table) > .t-wrap { overflow: visible; }
  table.warn-table { display: block; width: auto !important; table-layout: auto; min-width: 0 !important; }
  table.warn-table thead { display: none; }
  table.warn-table tbody { display: block; }
  table.warn-table tr {
    display: flex; flex-direction: column; position: relative;
    margin: 0 0 10px; padding: 12px;
    background: var(--card); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .05);
  }
  table.warn-table tbody tr:last-child { margin-bottom: 0; }
  table.warn-table td {
    display: flex; align-items: flex-start; flex-wrap: wrap; gap: 8px;
    width: auto !important; max-width: none !important;
    padding: 5px 0; border: 0;
    text-align: left !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
    font-size: 13px; line-height: 1.6; color: var(--text-2);
  }
  table.warn-table td::before {
    flex: 0 0 4em;
    font-size: 12px; font-weight: 400; line-height: 1.75; color: var(--text-3);
  }
  table.warn-table td:nth-child(1) { display: none; }
  table.warn-table td:nth-child(2) {
    order: 1; display: block; padding: 0 0 6px;
    font-size: 15px; font-weight: 600; line-height: 1.5;
  }
  table.warn-table td:nth-child(2)::before { content: none; }
  table.warn-table td:nth-child(3) { order: 2; }
  table.warn-table td:nth-child(4) { order: 3; }
  table.warn-table td:nth-child(5) { order: 4; }
  table.warn-table td:nth-child(3)::before { content: '警示期限'; }
  table.warn-table td:nth-child(4)::before { content: '警示事由'; }
  table.warn-table td:nth-child(5)::before { content: '警示部门'; }

  /* ⑤ 标段详情页「投标人明细」11 列（/bids/{id}，table.bd-table 1250px）卡片化
     —— 这是标段页的核心数据（谁投标、谁中标），窄屏横滑 1250px 等于取不到信息。
     列序见 tools/gen_seo_bids.py 的 BIDDER_HEADS（改列序须同步本节）：
     序号/投标人/资审结果/投标总价/下浮率/开标结果/是否入围/评标结果/推优组别/票选范围/是否中标 */
  .card:has(> .bd-scroll > table.bd-table) {
    padding: 0; border: 0; border-radius: 0; box-shadow: none; background: transparent;
  }
  .card:has(> .bd-scroll > table.bd-table) > .bd-scroll { overflow: visible; }
  table.bd-table { display: block; width: auto !important; table-layout: auto; min-width: 0 !important; }
  table.bd-table thead { display: none; }
  table.bd-table tbody { display: block; }
  table.bd-table colgroup { display: none; }
  table.bd-table tr {
    display: flex; flex-direction: column; position: relative;
    margin: 0 0 10px; padding: 12px;
    background: var(--card); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .05);
  }
  table.bd-table tbody tr:last-child { margin-bottom: 0; }
  table.bd-table td {
    display: flex; align-items: flex-start; flex-wrap: wrap; gap: 8px;
    width: auto !important; max-width: none !important;
    padding: 4px 0; border: 0;
    text-align: left !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
    font-size: 13px; line-height: 1.6; color: var(--text-2);
  }
  table.bd-table td::before {
    flex: 0 0 4em;
    font-size: 12px; font-weight: 400; line-height: 1.7; color: var(--text-3);
  }
  table.bd-table td:nth-child(1) { display: none; }
  table.bd-table td:nth-child(2) {
    order: 1; display: block; padding: 0 0 6px;
    font-size: 15px; font-weight: 600; line-height: 1.5;
  }
  table.bd-table td:nth-child(2)::before { content: none; }
  table.bd-table td:nth-child(3) { order: 2; }
  table.bd-table td:nth-child(4) { order: 3; }
  table.bd-table td:nth-child(5) { order: 4; }
  table.bd-table td:nth-child(6) { order: 5; }
  table.bd-table td:nth-child(7) { order: 6; }
  table.bd-table td:nth-child(8) { order: 7; }
  table.bd-table td:nth-child(9) { order: 8; }
  table.bd-table td:nth-child(10) { order: 9; }
  table.bd-table td:nth-child(11) { order: 10; }
  table.bd-table td:nth-child(3)::before { content: '资审'; }
  table.bd-table td:nth-child(4)::before { content: '投标总价'; }
  table.bd-table td:nth-child(5)::before { content: '下浮率'; }
  table.bd-table td:nth-child(6)::before { content: '开标结果'; }
  table.bd-table td:nth-child(7)::before { content: '是否入围'; }
  table.bd-table td:nth-child(8)::before { content: '评标结果'; }
  table.bd-table td:nth-child(9)::before { content: '推优组别'; }
  table.bd-table td:nth-child(10)::before { content: '票选范围'; }
  table.bd-table td:nth-child(11)::before { content: '是否中标'; }
}

/* ==========================================================================
   同类项目下浮率参考卡（2026-10-26）
   —— 标段详情页「中标结果」之后，「招标概况」之前。数据来自 data/pct_ref.json
      预聚合（tools/build_pct_ref.py），页面只查表不计算。
   无样本时整块不渲染（非施工类源站基本不公布下浮率），所以这里不用考虑空态样式。
   ========================================================================== */
.card.pct-ref { padding: 16px 18px; }
.pct-scope { margin: 0 0 12px; font-size: 14px; color: #374151; font-weight: 600; }
.pct-scope span { display: block; margin-top: 3px; font-size: 12px; color: #9ca3af; font-weight: 400; }
.pct-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.pct-cell { background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px 6px; text-align: center; }
.pct-cell b { display: block; font-size: 20px; line-height: 1.2; color: #1f2937; font-variant-numeric: tabular-nums; }
.pct-cell span { display: block; margin-top: 4px; font-size: 12px; color: #6b7280; }
.pct-cell.pct-main { background: #eff6ff; border-color: #bfdbfe; }
.pct-cell.pct-main b { color: #1d4ed8; }
.pct-pos { margin: 12px 0 0; font-size: 13px; line-height: 1.7; color: #374151; }
.pct-pos b { color: #1d4ed8; }
.pct-note { margin: 10px 0 0; font-size: 12px; line-height: 1.7; color: #9ca3af; }
@media (max-width: 560px) {
  .card.pct-ref { padding: 14px 12px; }
  .pct-cell b { font-size: 17px; }
  .pct-cell span { font-size: 11px; }
}