/* ==========================================================================
   门户列表页 · 页面级样式
   落点：web/static/portal/list.css
   用处：`views/lab-center/index.php`（`/labs.html` 实验中心）、`views/news/index.php`（`/news.html`
   新闻中心）与 `views/course/index.php`（`/course.html` 课程中心）—— 三个列表页共用同一套门户副本、
   撑满与空态居中规则（课程页的区块骨架 / 卡片样式随**件**走，见 `widgets/css/portal_course.css`）。
   P5 旧类清理（2026-10-11）：三张门户入口页（`/lab-center.html`、`/vrzone.html`、`/micro.html`）
   迁入设计系统后同样引本文件（入口件的卡片 = `.ph-labs` 封面卡语言；件自带注册）。
   骨架（`.ph` / `.ph__wrap` / `.ph-sec*`）在 `widgets/css/portal_base.css`；
   空态 / 分页 / 输入框 / 按钮用**设计系统**的件（`common/ui/components.css` 的
   `.ui-empty*` / `.ui-pagination*` / `.ui-input` / `.ui-btn`）——但门户**不引整包**（口径见
   `reference_portal_shell.md`：整包会把 `.ui-dropdown*`、layui/flatpickr 皮肤等一并带进来，
   改到门户既有 chrome 的观感）⇒ 这里按 `components.css` **逐条同值**抄本页用到的那部分，
   并全部**限定在 `.ph` 下**（不外溢）。
   颜色 / 间距 / 字号一律 `--c-* / --sp-* / --fs-*` 语义 token。
   ========================================================================== */

/* ── 解除旧门户的固定宽度 ──
   `common/css/common-style.css` 给 `html, body` 钉了 `min-width: 1100px` ⇒ 移动端收不下来。
   与 `portal-page.css`（主页）同款处理；本文件只被这两个列表页加载。 */
html, body { min-width: 0; }

/* --------------------------------------------------------------------------
   区块头：标题 + 搜索（搜索贴右，窄屏折行占满）
   `align-self: center` 覆盖 `.ph-sec__head` 的 baseline 对齐（输入框是盒子，按基线对不齐）
   -------------------------------------------------------------------------- */
.ph-sec__head { flex-wrap: wrap; row-gap: var(--sp-2, 8px); }

.ph-search {
    display: flex;
    align-items: center;
    gap: var(--sp-2, 8px);
    align-self: center;
    margin-left: auto;
}
.ph-search__input {
    width: 240px;
    min-height: 34px;
    font-size: var(--fs-sm, 14px);
}
.ph-search__go { flex: none; padding: 0 var(--sp-3, 12px); }
.ph-search__go .ui-ico { width: 16px; height: 16px; }

@media (max-width: 640px) {
    .ph-search { width: 100%; margin-left: 0; }
    .ph-search__input { flex: 1 1 auto; width: auto; }
}

/* --------------------------------------------------------------------------
   门户入口页（P5 迁入设计系统的 `/lab-center.html`、`/vrzone.html`、`/micro.html`）
   · `.ph-banner` —— 站点/固定横幅图（全宽，图等比缩放；替代旧 `common/school/banner` 片段
     与 micro 旧 `section.banner`，两者样式原散在 school1.css / micro-index.css）
   · `.ph-entry__search` —— 搜索条**独立放在列表上方**（区块头里没有它时靠这条右对齐）
   -------------------------------------------------------------------------- */
.ph-banner { display: block; }
.ph-banner > img { display: block; width: 100%; height: auto; }

.ph-entry__search {
    justify-content: flex-end;
    margin-left: 0;
    padding-top: var(--sp-4, 16px);
}

/* 入口卡元信息行（智慧教室）：负责人 = 不压缩、不隐藏（窄屏也留着）；
   开设机构走 `.ph-lab__college`（先被省略号、超窄屏隐藏）—— 与中心卡"院系让位"同一取向。 */
.ph-lab__leader { flex: none; white-space: nowrap; }

/* 入口件根容器：分页居中 + 空态留白（`{$link}`/`.ui-empty` 是件根 div 的直接子级，
   列表页的 `.ph-sec__body > …` 选择器够不到 ⇒ 这里补一条同值规则） */
.ph-entry > .ui-pagination { justify-content: center; margin-top: var(--sp-6, 32px); }
.ph-entry > .ui-empty { padding: var(--sp-8, 56px) var(--sp-4, 16px); }

/* --------------------------------------------------------------------------
   实验中心卡片（与课程卡 `.ph-course` **同一套层级语言**：字号 / 色阶分层，不用盒子分层）
   ① `.ph-lab__name`  标题：`--fs-sm` / 600（全卡唯一的"重"，文字内容的第一行）
   ② `.ph-lab__attrs` 属性行：实训基地等级徽章（全卡唯一有底色的元素；默认只在省级及以上出）
   ③ `.ph-lab__meta`  数据行：实验数（主色 + 图标，**全卡唯一的"亮"**）· 院系（可省略号）· 浏览量（贴右）
   ④ `.ph-lab__d`     简介：`--fs-xs` 三级色、2 行省略（退到最后一层，不抢数据行）
   ⚠ 与首页「课程推荐」`.ph-hot` / 课程卡 `.ph-course` 的取值逐条对齐（改一处要改三处）。
   -------------------------------------------------------------------------- */
.ph-labs {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-6, 32px) var(--sp-4, 16px);
}

/* ── 实验中心卡（列表页）──
   白卡（白底 + 12px 圆角 + 极轻描边 + 浅阴影 + hover 抬起）。
   ⚠ 2026-10-10 起与课程卡 `.ph-course`（`widgets/css/portal_course.css`）**不再逐条同款**：
     用户口径"课程卡片不要内补白"只点名了**课程卡**（其内边距已归零、内容贴卡边），
     本卡（实验中心列表）未获点名 ⇒ 保留"封面满宽 + 文字区 16px 内边距"的现状。
     目前没有两种卡并排的页面 ⇒ 不一致不可见；若要同步等用户发话。
   ⚠ 2026-10-10 用户口径"卡片中图片不要留边距"：**内边距只留给文字区，封面满宽贴边**。
   做法 = 卡片 `padding: 0 16px 16px`（顶部 0，封面贴顶）+ 封面用**负 margin 把左右抵出去**，
   并去掉封面自己的圆角/描边（圆角改由卡片的 `overflow: hidden` 裁）。
   ⚠ 负 margin 撑不破版式：卡片有 `overflow: hidden` 兜着。
   ⚠ 封面与标题之间的间距仍由 `.ph-lab__name` 自己的 `margin-top` 给 ⇒ 不会"贴死"。 */
.ph-lab {
    display: block;
    color: inherit;
    overflow: hidden;
    padding: 0 var(--sp-4, 16px) var(--sp-4, 16px);
    border-radius: var(--radius-md, 12px);
    background-color: var(--c-surface);
    border: 1px solid var(--c-border-soft);
    box-shadow: var(--shadow-sm);
    transition: transform var(--dur-fast, .14s) var(--ease-out, ease),
                box-shadow var(--dur-fast, .14s) var(--ease-out, ease),
                border-color var(--dur-fast, .14s) var(--ease-out, ease);
}
.ph-lab:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md, var(--shadow-sm));
    border-color: var(--c-primary-border);
}
.ph-lab__cover {
    display: block;
    aspect-ratio: 16 / 9;
    /* 负 margin 抵出卡片内边距 ⇒ 图满宽贴边（卡片 `overflow: hidden` 兜底） */
    margin: 0 calc(-1 * var(--sp-4, 16px));
    overflow: hidden;
    background-color: var(--c-surface-2);
}
/* 只作用于**直接子级**主封面（口径同课程卡/文章条目：容器里的小图不被撑大） */
.ph-lab__cover > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--dur-base, .24s) var(--ease-out, ease);
}
.ph-lab:hover .ph-lab__cover > img { transform: scale(1.03); }
.ph-lab:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }

.ph-lab__body { display: block; min-width: 0; }

.ph-lab__name {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: var(--sp-2, 8px);
    font-size: var(--fs-sm, 14px);
    font-weight: var(--fw-semibold, 600);
    line-height: 1.55;
    color: var(--c-text);
    transition: color var(--dur-fast, .14s) var(--ease-out, ease);
}
.ph-lab:hover .ph-lab__name { color: var(--c-primary-text); }

/* ② 属性行：徽章 `margin-left: auto` 推到行右端（与课程卡的 `.ph-course__tag` 同值） */
.ph-lab__attrs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: var(--sp-1, 4px);
}
.ph-lab__tag {
    flex: none;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 7px;
    border-radius: var(--radius-sm, 6px);
    background-color: var(--c-primary-soft);
    color: var(--c-primary-text);
    font-size: 11px;
    font-weight: var(--fw-medium, 500);
    line-height: 1;
    white-space: nowrap;
}

/* ③ 数据行：实验数（主色，唯一的"亮"）· 院系（二级色，先被压缩）· 浏览量（贴右、三级色） */
.ph-lab__meta {
    display: flex;
    align-items: center;
    gap: var(--sp-2, 8px);
    margin-top: 3px;
    min-width: 0;
    font-size: var(--fs-xs, 12px);
    line-height: 1.6;
    color: var(--c-text-secondary);
}
.ph-lab__exp {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: none;
    font-weight: var(--fw-medium, 500);
    color: var(--c-primary-text);
    white-space: nowrap;
}
/* 院系名可能很长 ⇒ **先被压缩的是它**（省略号），实验数与浏览量永不被截断 */
.ph-lab__college {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 浏览量：`margin-left: auto` 钉在数据行右端（不依赖院系是否存在） */
.ph-lab__views {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    flex: none;
    color: var(--c-text-tertiary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.ph-lab__ico {
    width: 14px;
    height: 14px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* ④ 简介：最后一行，2 行省略 */
.ph-lab__d {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: var(--sp-1, 4px);
    font-size: var(--fs-xs, 12px);
    line-height: 1.7;
    color: var(--c-text-tertiary);
}

@media (max-width: 1024px) {
    .ph-labs { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5, 24px); }
}
@media (max-width: 640px) {
    .ph-labs { grid-template-columns: minmax(0, 1fr); }
    /* 窄屏一列时卡片只有 ~330px：数据行三项仍放得下，但院系最不关键 ⇒ 它让位（保住实验数与浏览量） */
    .ph-lab__college { display: none; }
}

/* 编辑器配了「显示列数」时（`--ph-cols` 由模板按配置写在元素上）。
   行数**不写进网格**：条数 = 行 × 列，多出来的自然换行 ⇒ 任意「行 × 列」都成立。 */
.ph-labs--grid { grid-template-columns: repeat(var(--ph-cols, 3), minmax(0, 1fr)); }

/* ⚠ 窄屏仍要按断点收列 ⇒ 用 `!important` 压过元素上的**内联** `--ph-cols`
   （内联样式优先于普通规则，但低于 `!important`）。与首页「课程推荐」`.ph-hots--grid` 同一手法。 */
@media (max-width: 1024px) {
    .ph-labs--grid { --ph-cols: 2 !important; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .ph-labs--grid { --ph-cols: 1 !important; grid-template-columns: minmax(0, 1fr); }
}

/* --------------------------------------------------------------------------
   新闻中心（`/news.html`）：左「栏目导航」+ 右文章列表
   —— 与主题包门户的新闻列表同构（`themes/school/Vega/news/index.php` 与
      `views/news/column.php` 都是这个骨架：一列栏目、一列文章）。
   栏目导航是从 `school_column` 派生的导航（不是内容区块）⇒ 页面级渲染，不做成小部件。
   -------------------------------------------------------------------------- */
.ph-news {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr);
    gap: var(--sp-8, 48px);
    align-items: start;
}

/* ⚠ 左栏「栏目导航」自身的样式**不在这里** —— 它随**件**走：见
   `web/static/widgets/css/portal_column_nav.css`（`app\widgets\school\ColumnNavWidget` 自带，
   类名 `ph-cnav*`）。本文件只管这一页的**两列布局**（左栏 + 右栏的宽度与折叠）。 */

/* 右栏：**档案式条目**（列表页专属的排版）
   —— 带左侧栏目列后行宽约 700–900px，够放「日期锚点 ｜ 标题 + 摘要 + 元信息 ｜ 封面」三列；
      两列网格会各剩 400 出头、标题被挤成两三行，所以这里一列到底。
   为什么这么排：同一条列表里**有封面**与**没封面**的文章混排，两列式的「封面 + 文字」会让
   没封面的那条文字缩在 240px 窄列里、右侧整片空（实测）。改成三列后两种条目共用同一骨架：
   日期列与正文列永远在位，去掉封面只是少一列 ⇒ 文字自然向右铺满，不用为"缺图"另做一套样式。
   节奏靠**一道通栏细线**（`--c-divider`）+ 行内留白，不用卡片、不放阴影。 */
.ph-news__main .ph-arts { grid-template-columns: minmax(0, 1fr); gap: 0; }
.ph-news__main .ph-art {
    grid-template-columns: 56px minmax(0, 1fr) 240px;
    gap: var(--sp-5, 24px);
    align-items: start;
    padding: var(--sp-5, 24px) 0;
}
/* ⚠ 线的颜色**不能**用 `--c-divider`（#eff3f9）：门户页面底是 #f6f6f6，
   它比底色还亮 ⇒ 这条线在屏幕上看不见（实测）。用 `--c-text` 兑 12% 透明 ——
   深浅跟着主题走（换肤自动适配），且在灰底上真的看得见。 */
.ph-news__main .ph-art + .ph-art { border-top: 1px solid color-mix(in srgb, var(--c-text) 12%, transparent); }
.ph-news__main .ph-art:first-child { padding-top: 0; }
/* ⚠ 必须用**同权重**选择器补这一条：`.ph-news__main .ph-art`(0,2,0) 会压过
   `portal_article_list.css` 里的 `.ph-art--text`(0,1,0) ⇒ 少了它，没封面的条目仍按
   三列排，正文被塞进中间列（右列那块空位还在 = "文字缩在左边"）。 */
.ph-news__main .ph-art.ph-art--text { grid-template-columns: 56px minmax(0, 1fr); }

/* 日期锚点：月-日（大字、等宽数字）+ 年份（小字），让"什么时候发的"一眼可扫 */
.ph-news__main .ph-art__date { display: flex; flex-direction: column; gap: 2px; }
.ph-news__main .ph-art__md {
    font-size: var(--fs-lg, 16px);
    font-weight: var(--fw-semibold, 600);
    line-height: 1.2;
    color: var(--c-text);
    font-variant-numeric: tabular-nums;
}
.ph-news__main .ph-art__y {
    font-size: var(--fs-xs, 12px);
    line-height: 1.4;
    color: var(--c-text-tertiary);
    font-variant-numeric: tabular-nums;
}
/* 没封面的条目：标题升一档（纯文字条目里标题就是主角，也让"没图"看着是有意为之），摘要多给一行 */
.ph-news__main .ph-art--text .ph-art__t { font-size: var(--fs-lg, 16px); }
.ph-news__main .ph-art--text .ph-art__s { -webkit-line-clamp: 3; }
/* 日期已挪到左列 ⇒ 元信息行不再钉底（宽行里跟着摘要走更顺） */
.ph-news__main .ph-art__m { margin-top: var(--sp-2, 8px); }

@media (max-width: 1024px) {
    .ph-news__main .ph-art { grid-template-columns: 48px minmax(0, 1fr) 160px; gap: var(--sp-3, 12px); }
    .ph-news__main .ph-art.ph-art--text { grid-template-columns: 48px minmax(0, 1fr); }
}
@media (max-width: 640px) {
    /* 窄屏：一列，日期折回标题上方一行；封面落到文字下面（DOM 顺序 = 日期 → 正文 → 封面） */
    .ph-news__main .ph-art { grid-template-columns: minmax(0, 1fr); gap: var(--sp-2, 8px); padding: var(--sp-4, 16px) 0; }
    .ph-news__main .ph-art.ph-art--text { grid-template-columns: minmax(0, 1fr); }
    .ph-news__main .ph-art__date { flex-direction: row; align-items: baseline; gap: var(--sp-2, 8px); }
}

/* 窄屏：栏目导航折到列表上方，横排换行（竖排一列会把正文推到屏幕外两屏） */
@media (max-width: 1024px) {
    .ph-news { grid-template-columns: minmax(0, 1fr); gap: var(--sp-5, 24px); }
    /* 窄屏把导航折到列表上方（横排本身的外观由件的 CSS 负责，见 portal_column_nav.css） */
}

/* ==========================================================================
   设计系统件的「门户副本」——与 `common/ui/components.css` **逐条同值**，
   只改作用域（限在 `.ph` 下）。改这里时请回对源文件，别各改各的。
   ========================================================================== */

/* ── 按钮 ── */
.ph .ui-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
    height: 34px;
    padding: 0 var(--sp-4);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}
.ph .ui-btn:active:not(:disabled) { transform: scale(.985); }
.ph .ui-btn:disabled,
.ph .ui-btn.is-disabled { cursor: not-allowed; opacity: .55; }

.ph .ui-btn--default {
    color: var(--c-text);
    background-color: var(--c-surface);
    border-color: var(--c-border-strong);
}
.ph .ui-btn--default:hover:not(:disabled) {
    color: var(--c-primary-text);
    border-color: var(--c-primary-border);
    background-color: var(--c-primary-softer);
}

/* 文字档：只留文字（无内边距 / 无背景 / 无边框） */
.ph .ui-btn--text {
    height: auto;
    padding: 0;
    color: var(--c-primary-text);
    background: none;
    border: 0;
}
.ph .ui-btn--text:hover:not(:disabled) { color: var(--c-primary-text-hover); }

/* ── 输入框 ── */
.ph .ui-input {
    display: block;
    width: 100%;
    min-height: 34px;
    padding: 6px var(--sp-3);
    color: var(--c-text);
    background-color: var(--c-surface);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    font-size: var(--fs-base);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}
.ph .ui-input::placeholder { color: var(--c-text-tertiary); }
.ph .ui-input:hover { border-color: var(--c-text-tertiary); }
.ph .ui-input:focus {
    outline: none;
    border-color: var(--c-primary-text);
    box-shadow: var(--focus-ring);
}

/* ── 分页（`LinkPager` 的 `<ul>`：状态类在 `<li>`，条目类是 `.ui-pagination__item`）── */
.ph .ui-pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1);
}
.ph .ui-pagination > li {
    display: inline-flex;
    list-style: none;
}
.ph .ui-pagination__item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    height: 32px;
    padding: 0 var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    color: var(--c-text-secondary);
    background-color: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}
.ph .ui-pagination__item:hover:not(.is-disabled) {
    color: var(--c-primary-text);
    background-color: var(--c-primary-softer);
}
.ph .ui-pagination__item.is-active,
.ph .ui-pagination > .is-active > .ui-pagination__item {
    color: var(--c-on-primary);
    background-color: var(--c-primary-solid);
    box-shadow: var(--shadow-brand);
}
.ph .ui-pagination__item.is-disabled {
    cursor: not-allowed;
    color: var(--c-text-tertiary);
    background-color: transparent;
}

/* ── 空态（插画 = 共享 sprite 的 `#i-empty` / 搜索无结果用 `#i-search`）── */
/* ⚠ 空态（`.ui-empty` 系列）的尺寸**已上移**到 `widgets/css/portal_base.css`（2026-10-09）——
   原来只写在**本文件**里，而本文件只有列表页加载 ⇒ 详情页 / 内容页的空态图标一直是 `.ui-ico`
   的 16px（"空态只有一个小方块"的来源）。`portal_base.css` 是门户各页都会加载的骨架，
   放那儿才能全局生效，也避免两处维护同一套尺寸。 */

/* ==========================================================================
   页面级摆位（放在上面「门户副本」之后 ⇒ 同权重时靠顺序取胜）
   ========================================================================== */

/* 分页居中（设计系统的 `.ui-pagination` 默认靠左） */
.ph-sec__body > .ui-pagination {
    justify-content: center;
    margin-top: var(--sp-6, 32px);
}

/* 整段区域的空态：给足上下留白，且**不放盒型**（门户区块本来就靠细线与间距分区，
   再套一层虚线盒子是多余的装饰）。 */
.ph-sec__body > .ui-empty { padding: var(--sp-8, 56px) var(--sp-4, 16px); }

@media (prefers-reduced-motion: reduce) {
    .ph-lab__cover > img,
    .ph-lab__name,
    .ph .ui-btn,
    .ph .ui-pagination__item { transition: none !important; }
}

/* ==========================================================================
   列表页：内容（尤其是**空态**）不足一屏时，也要撑满剩余高度
   —— 用户口径："文章列表为空时，页面高度至少达到浏览器窗口 100%"。
   壳那半截在 `portal/chrome.css`（`body` flex 列 + `#container` 吃掉剩余 ⇒ 页脚贴底），
   这里接页面这半截：把 `#container` → `.ph` → `.ph-sec` → `.ph__wrap` → 列表容器 → 空态
   一路串成 flex 链，最后让**空态在剩余空间里垂直居中**（而不是缩在页面上半截、
   下面留一整片灰底）。三个列表页都引本文件：`/news.html`、`/col/<id>.html`、`/labs.html`。
   ⚠ 只在 `:last-child` 的区块上 grow：列表页只有一个区块，用 `:last-child` 是为了将来
     某页在列表下面再加内容时，撑开的仍然是最后一个（不会把中间区块拉变形）。
   ⚠ 列表**非空**时本来就会超过一屏，flex-grow 不起作用 ⇒ 对正常渲染零影响。 */
.ph { flex: 1 0 auto; display: flex; flex-direction: column; }
.ph > .ph-sec:last-child { flex: 1 0 auto; display: flex; flex-direction: column; }
.ph > .ph-sec:last-child > .ph__wrap { flex: 1 0 auto; display: flex; flex-direction: column; }
/* `news/col`：左侧栏目导航 + 右栏，两列都要拉伸（base 的 `align-items: start` 会挡住撑高） */
.ph-news { flex: 1 0 auto; align-items: stretch; }
.ph-news__main { display: flex; flex-direction: column; }
/* 列表容器：吃东西两页各有两层壳（`/labs.html` 的 `.ph-sec__body` 直接在 `.ph__wrap` 下） */
.ph__wrap > .ph-sec__body,
.ph-news__main .ph-sec__body { flex: 1 0 auto; display: flex; flex-direction: column; }
/* 空态：吃掉剩余高度并居中 */
.ph__wrap > .ph-sec__body > .ui-empty,
.ph-news__main .ph-sec__body > .ui-empty {
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
