/* ==========================================================================
   课程中心页（`/course.html` = `/course/index.html`）· 件自带样式
   落点：web/static/widgets/css/portal_course.css
   用处：四个件共用同一份 —— `CourseFiltersWidget` / `SubjectFilterWidget` /
        `CourseStatusFilterWidget`（筛选行 `.ph-filter*`）与 `CourseListWidget`（卡片网格
        `.ph-courses` / `.ph-course*`）。四个件各声明本文件，框架按文件去重 ⇒ 只出一个 <link>。
   为什么单独一份：它们是**同一页的一族**（筛选区 + 列表），共用同一套筛选行语言；拆成两份会在
        两个文件里维护同一套间距与状态色。
   · 骨架（`.ph` / `.ph__wrap` / `.ph-sec*`）在 `portal_base.css`；
   · 空态 / 分页 / 输入框 / 按钮用设计系统的件（`.ui-empty*` / `.ui-pagination*` / `.ui-input`
     / `.ui-btn`）的门户副本，在 `portal/list.css`（该文件同时给本页，见视图 `pageCss`）；
   · 颜色 / 间距 / 字号一律 `--c-* / --sp-* / --fs-*` 语义 token（换肤自动生效）。
   ⚠ 课程卡与首页「课程推荐」的 `.ph-hot` **同构**（16:9 + 1px 细边 + 圆角 + 名称两行）——
     两处的卡片语言必须一致，别在这里另造一套（口径见 `web/static/widgets/css/portal_course_recommend.css`）。
   ========================================================================== */

/* ── 筛选区：一行一个条件（类型 / 学科 / 状态 / 排序），行间一道细线 ──
   ⚠ 线的颜色**不能**用 `--c-divider`（#eff3f9）：门户页面底是 #f6f6f6，它比底色还亮 ⇒
     屏幕上看不见（口径与 `portal/list.css` 的新闻条目线一致）。用 `--c-text` 兑 12% 透明，
     深浅跟着主题走。 */
/* ⚠ 两个类名同一份规则：`.ph-course__filters` 是课程中心的写法（保持不动 ⇒ 课程页 DOM/样式零变化），
   `.ph-filters` 是门户**筛选区容器**的中性别名（实验中心等其它列表页用）。 */
.ph-course__filters,
.ph-filters {
    display: flex;
    flex-direction: column;
    padding-top: var(--sp-4, 16px);
}

.ph-filter {
    display: flex;
    align-items: baseline;
    gap: var(--sp-3, 12px);
    padding: var(--sp-3, 12px) 0;
    border-top: 1px solid color-mix(in srgb, var(--c-text) 12%, transparent);
}
.ph-filter:first-child { border-top: 0; padding-top: 0; }

/* 工具条（bar）：筛选区与列表之间的"排序 + 搜索"，做成一整条**白底容器**
   （用户口径："排序和搜索输入框那行做成一个 bar 的形式"）。
   克制到只有一层底 + 一条细边（无阴影、无渐变）——与门户的"区块 = 白底 + 细边"同一套语言。 */
.ph-course__bar {
    display: flex;
    flex-wrap: wrap;
    /* 垂直居中（用户口径）：bar 里是"文本框 + 按钮"和"文字链+标签"，按基线对齐会看着偏上 */
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2, 8px) var(--sp-4, 16px);
    margin-top: var(--sp-4, 16px);
    padding: var(--sp-3, 12px) var(--sp-4, 16px);
    background-color: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-md, 12px);
}
/* 放进工具条的那一组条件（排序）：不再占一整个筛选行（无上分隔线、无纵向内边距） */
.ph-filter--inline { padding: 0; border-top: 0; }
/* ⚠ 工具条里的条件名**不要**那栏固定宽（`min-width: 5em` 是给筛选区内四行对齐用的；
   放进 bar 会把"排序："和后面的选项撑开一段空白 —— 用户口径："排序这两个字的标签不要设置固定宽度"） */
.ph-filter--inline .ph-filter__t { min-width: 0; }

/* 条件名：固定一栏宽，让四行的选项左边缘对齐（"结构即信息"，不靠加粗抢层级） */
.ph-filter__t {
    flex: none;
    min-width: 5em;
    font-size: var(--fs-sm, 14px);
    line-height: 1.6;
    color: var(--c-text-tertiary);
}

.ph-filter__o {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-1, 4px) var(--sp-4, 16px);
    min-width: 0;
}

.ph-filter__link {
    font-size: var(--fs-sm, 14px);
    line-height: 1.6;
    color: var(--c-text-secondary);
    white-space: nowrap;
    transition: color var(--dur-fast, .14s) var(--ease-out, ease);
}
.ph-filter__link:hover { color: var(--c-primary-text); }
/* 当前条件：主色 + 半粗（不用底色胶囊 —— 一行里多个胶囊会变成色块噪声） */
.ph-filter__link.active {
    color: var(--c-primary-text);
    font-weight: var(--fw-medium, 500);
}

/* ── 列表：封面卡网格（与首页课程推荐同构）──
   列数由**面板的「显示列数」**写进元素上的 `--ph-cols`（模板 `style="--ph-cols:N"`）；
   窄屏仍要按断点收列 ⇒ 断点里用 `!important` 压过**内联**的 `--ph-cols`
   （口径与 `portal_course_recommend.css` 的 `.ph-hots--grid` 完全一致）。 */
.ph-courses {
    /* 兜底列数（= 面板默认值 3 列；模板会把配置值写成内联 `--ph-cols`，正常情况下走内联） */
    --ph-cols: 3;
    display: grid;
    grid-template-columns: repeat(var(--ph-cols), minmax(0, 1fr));
    gap: var(--sp-5, 24px) var(--sp-4, 16px);
}

/* ── 课程卡（列表页）──
   白卡（白底 + 12px 圆角 + 极轻描边 + 浅阴影 + hover 抬起）。
   ⚠ 2026-10-10 用户口径最终形态（"卡片中图片不要留边距" + "文字不要贴边"）：
     **封面满宽贴顶（图片不留边距），文字区保留内边距（不贴边）**。
   做法 = 卡片 `padding: 0 16px 16px`（顶部 0，封面贴顶）+ 封面**负 margin 把左右抵出去**；
   封面自己的圆角/描边已删 —— 圆角由卡 `overflow: hidden` 裁、描边由卡给。
   ⚠ 内边距曾在同日一度归零（"课程卡片不要内补白"），随后用户改口"文字不要贴边" ⇒ 恢复本值；
     封面与标题的间距由 `.ph-course__name` 的 `margin-top: 8px` 给。
   ⚠ 该卡同时被 **labs 项目页 / 中心首页项目位**复用（`LabCenterProjectWidget`）：它们也是"卡片列表"，
     一起改是**预期**的（风格统一），不是误伤。 */
.ph-course {
    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-course:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md, var(--shadow-sm));
    border-color: var(--c-primary-border);
}

.ph-course__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-course--card`）：中心首页项目位专用 ──
   2026-10-10 用户口径"实验中心主页的课程卡片样式和门户首页保持一致"：
   门户首页课程卡（`.ph-hot`）= **卡本体不是链接、底部一枚整行宽实心按钮**；
   本件首页位照搬这个交互（项目页仍是整卡链接的列表卡，不加按钮）。
   ⚠ 按钮值与 `portal_course_recommend.css` 的 `.ph-hot__cta` **逐条同值**，改一处要对照另一处。 */
.ph-course--card {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2, 8px);             /* 各层间距由 gap 给（列表形态靠各自的 margin-top） */
}
/* 封面→标题的 8px 在 card 形态下由 gap 给 ⇒ 抵消列表形态的 margin-top，避免叠成 16px */
.ph-course--card .ph-course__name { margin-top: 0; }
/* 卡形态下标题不再 hover 变主色（它不可点；列表卡的整卡链接形态保留变色） */
.ph-course--card:hover .ph-course__name { color: var(--c-text); }
.ph-course__cta {
    /* 钉在卡底：同一行卡片高度不齐时，按钮仍落在同一基线（同 `.ph-hot__cta`） */
    margin-top: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 40px;
    border-radius: var(--radius-sm, 6px);
    background-color: var(--c-primary-solid);
    color: var(--c-on-primary);
    font-size: var(--fs-sm, 14px);
    font-weight: var(--fw-semibold, 600);
    line-height: 1;
    text-decoration: none;
    transition: background-color var(--dur-fast, .14s) var(--ease-out, ease);
}
.ph-course--card:hover .ph-course__cta { background-color: var(--c-primary-hover); }
.ph-course__cta:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }
/* ⚠ 仍是**直接子级**：封面只放图（角标/标签已全部挪进卡里），但这条限定保留 ——
   它挡的是"封面框里的任何小图被撑满"这一类问题（口径同 `portal_course_recommend.css`）。 */
.ph-course__cover > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--dur-base, .24s) var(--ease-out, ease);
}
.ph-course:hover .ph-course__cover > img { transform: scale(1.03); }
.ph-course:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }

/* ── 卡片的三层文字（**用字号 / 色阶分层，不用盒子分层**）──
   用户的反馈是"看着像在堆砌文字"：原来封面下压着标题 + 三枚同形胶囊 + 信息行，四行并列、
   三个一样的圆角块 ⇒ 没有层级。现在改成"强 → 弱 → 数据"三段（用户口径：**标题放文字最顶部**）：

     ① `.ph-course__name`   标题：`--fs-sm` 14px / 600（**全卡唯一的"重"**，文字内容的第一行）
     ② `.ph-course__attrs`  属性行：类型（11px 三级色，字距 .02em）+ 右侧标签徽章（全卡唯一有底色的元素，
        本库 25 门里只有 2 门有标签 ⇒ 稀有本身就是强调）
     ③ `.ph-course__meta`   数据行：状态（`::before` 小圆点 + 主色字，**全卡唯一的"亮"**）
        ＋ 学院 · 老师 ＋ 👥人数 ＋ ★均分，一排 12px 二级色小字

   ⚠ 与首页「课程推荐」卡的 `.ph-hot__attrs / .ph-hot__type / .ph-hot__tag` 是**同一套观感**
     （值逐条对齐，改一处要改两处：`portal_course_recommend.css`；两处的 DOM 顺序也必须一致：
      封面 → **标题** → 属性行 → 数据行）。 */
.ph-course__attrs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: var(--sp-1, 4px);
}
/* 类型：不是"块"，是贴在标题上方的分类小字（弱、略宽字距，读起来像标签而不是内容） */
.ph-course__type {
    font-size: 11px;
    font-weight: var(--fw-medium, 500);
    letter-spacing: .02em;
    line-height: 20px;
    color: var(--c-text-tertiary);
}
/* 标签徽章：`margin-left:auto` 推到属性行最右（与左侧类型形成一条安静的横线） */
.ph-course__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-course__tag--img { padding: 0; background: none; display: block; height: 20px; width: auto; object-fit: contain; }

.ph-course__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-course:hover .ph-course__name { color: var(--c-primary-text); }

/* ③ 数据行（开课状态 · 学院 · 教师 · 学习人数 · 评价均分）：一排 12px 小字、单行省略；
   整行都缺时行不输出（用户口径：**不显示学年学期**）。
   ⚠ 与首页「课程推荐」的 `.ph-hot__meta` 是**同一套观感**（值逐条对齐，改一处要改两处：
     `portal_course_recommend.css` 的 `.ph-hot__meta/__who/__college/__teacher/__study/__ico`）。 */
.ph-course__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);
}
/* 开课状态：**点 + 字**（不是胶囊）—— 点用 `currentColor`，颜色换一处即可整体换肤。
   "开课中"是全卡唯一的强调色（它是"现在就能学"的信号）；未开课 / 已结课保持三级色。 */
.ph-course__status {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: none;
    font-weight: var(--fw-medium, 500);
    color: var(--c-text-tertiary);
}
.ph-course__status::before {
    content: '';
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: 50%;
    background-color: currentColor;
}
.ph-course__status.is-started { color: var(--c-primary-text); }

/* 学院 · 教师这一组**吃掉剩余宽度** ⇒ 右边的"人数 / 均分"自然贴到卡的右边缘（网格里成列对齐）；
   ⚠ 学院名可能很长（用户口径"学院名称可能会比较长"）⇒ 让位顺序是明确的：
     · 状态 / 人数 / 均分 = `flex: none`：**永远不被截断**；
     · 学院 = `flex: 0 1 auto` + 省略号：**先被压缩的是它**（长学院名不吞掉后面的内容）；
     · 教师 = `flex: 0 1 auto` + `min-width: 3.5em`：再挤也留得下一个 3~4 字的姓名。 */
.ph-course__who {
    display: flex;
    align-items: center;
    gap: var(--sp-1, 4px);
    flex: 1 1 auto;
    min-width: 0;
}
.ph-course__college,
.ph-course__teacher { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-course__college { flex: 0 1 auto; min-width: 0; }
.ph-course__teacher { flex: 0 1 auto; min-width: 3.5em; }
.ph-course__sep { flex: none; opacity: .6; }
/* 学习人数：钉在信息行末尾（不参与省略），数字前一个 users 图标 */
.ph-course__study {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: none;
}

/* 评价均分：排在信息行**最后**（用户口径："评价分数放在学习人数后面"），
   星固定亮橙（`--c-star` 是产品色、不随主题变） */
.ph-course__score {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: none;
}
.ph-course__score .ph-course__ico { color: var(--c-star); }
.ph-course__ico {
    width: 14px;
    height: 14px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* 列表件配了 `more_url` 时，在分页条下方出一个"更多"链接（本页默认不配 ⇒ 不输出） */
.ph-courses__more {
    display: block;
    width: fit-content;
    margin: var(--sp-4, 16px) auto 0;
    font-size: var(--fs-sm, 14px);
    color: var(--c-text-secondary);
}
.ph-courses__more:hover { color: var(--c-primary-text); }

@media (max-width: 1024px) {
    .ph-courses { --ph-cols: 3 !important; gap: var(--sp-4, 16px); }
    .ph-filter,
    .ph-course__bar { flex-direction: column; gap: var(--sp-1, 4px); }
}
@media (max-width: 640px) {
    /* 行距给到 18px：窄屏里"类型"小字贴在标题上方，两卡之间太挤会让人误读成上一张卡的尾行 */
    .ph-courses { --ph-cols: 2 !important; gap: 18px 12px; }
    .ph-filter__o { gap: var(--sp-1, 4px) var(--sp-3, 12px); }
    /* 窄屏卡片只有 ~150px 宽：数据行放不下六项 ⇒ 让**学院 / 教师**整组退场（它们最不关键），
       保住 状态 · 人数 · 均分 不被省略号吃掉。 */
    .ph-course__who { display: none; }
    .ph-course__meta { gap: 6px; }
}

@media (prefers-reduced-motion: reduce) {
    .ph-course__cover > img,
    .ph-course__name,
    .ph-filter__link { transition: none !important; }
}
