dsh-ai-courses 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +28 -23
- package/lib/styles.css +2 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -85,7 +85,7 @@ window.__ModuleLoader__.load({
|
|
|
85
85
|
|
|
86
86
|
/* ---- 样式:构建期内联(同一个文件也以真实文件随包发布)---- */
|
|
87
87
|
|
|
88
|
-
const AIC_COURSES_CSS = "/* ============================================================================\n AI 课程插件样式(交付物:package/lib/styles.css)\n 由 scripts/build.mjs 内联进 lib/client.js,同时以真实文件形式随包发布\n\n 约束(来自 06-ui-design.md §4):\n · 只用 14 个宿主主题 token;字面颜色零出现(artwork 除外)\n · 所有 class 带 aic- 前缀(与宿主/其他插件共用同一文档)\n · 需要「比边框更醒目又不写字面色」时,朝前景色混色 —— 两个主题自动正确\n · 明暗两套都成立\n ============================================================================ */\n\n.aic-root,\n.aic-shell {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font-size: 13px;\n line-height: 1.55;\n -webkit-font-smoothing: antialiased;\n}\n\n.aic-root { height: 100%; }\n\n/* 高度链:html/body → .aic-root → .aic-shell 都是确定高度,\n 这样页面内部的 height:100% 才有参照物(百分比高度在 auto 父级上会被当成 auto)。 */\n.aic-shell {\n display: flex;\n height: 100%;\n min-height: 0;\n}\n\n.aic-main {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n /* **唯一的滚动容器**(课程库 / 大纲 / 学习三页共用):\n `scrollbar-gutter: stable` 预留给滚动条,避免内容一长一短时整列横向抖动 */\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-gutter: stable;\n padding: 20px 24px 40px;\n}\n\n/* --- 大纲页根容器:高度默认 100%(撑满所在列,内容更高时由列滚动) --- */\n.aic-outline {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* --- 大纲页头 --- */\n.aic-outline__head { margin-bottom: 16px; }\n.aic-outline__titlerow { display: flex; align-items: center; gap: 12px; }\n.aic-outline__titlerow .aic-h1 { flex: 1 1 auto; min-width: 0; margin: 0; }\n\n/* --- 通用排版 --- */\n.aic-h1 { font-size: 17px; font-weight: 600; margin: 0 0 2px; }\n.aic-h2 { font-size: 14px; font-weight: 600; margin: 0 0 8px; }\n.aic-sub { color: var(--dsw-alias-label-secondary); margin: 0; }\n.aic-muted { color: var(--dsw-alias-label-secondary); }\n.aic-pagehead {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 16px;\n}\n\n/* --- 工具栏 --- */\n.aic-toolbar {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.aic-input,\n.aic-select {\n height: 30px;\n padding: 0 10px;\n font: inherit;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n}\n.aic-input { min-width: 200px; }\n.aic-input::placeholder { color: var(--dsw-alias-label-secondary); }\n.aic-input:focus-visible,\n.aic-select:focus-visible,\n.aic-btn:focus-visible,\n.aic-navbtn:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.aic-btn {\n /* inline-flex + gap:让「图标 + 文字」的按钮能对齐;纯文字按钮同样正常 */\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n font: inherit;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n white-space: nowrap;\n}\n.aic-btn:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-btn--primary {\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n border-color: var(--dsw-alias-brand-primary);\n}\n.aic-btn--primary:hover { opacity: .88; } /* 悬停用不透明度,避免引入字面色 */\n.aic-btn--quiet { border-color: transparent; background: transparent; }\n.aic-btn--quiet:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-btn[disabled] { opacity: .5; cursor: not-allowed; }\n.aic-btn--sm { height: 24px; padding: 0 8px; font-size: 12px; }\n\n/* --- 标签筛选行(取代原来的标签下拉) --- */\n/* 标签控制区**不是卡片**:无边框、无圆角、无底色、无内边距 ——\n 内容与工具栏、分组标题左对齐,直接排在页面上。 */\n.aic-tagbar {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.aic-tagbar__label {\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n padding-top: 2px;\n}\n.aic-tagbar__list { display: flex; gap: 6px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }\n.aic-tagbar__clear { flex: 0 0 auto; }\n.aic-tagbar__count {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 标签控件:选中态即过滤条件 */\n.aic-chip {\n font: inherit;\n font-size: 12px;\n height: 24px;\n padding: 0 10px;\n border-radius: 999px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n}\n.aic-chip:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-chip[aria-pressed=\"true\"] {\n background: var(--dsw-alias-brand-primary);\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base); /* brand-primary 的天然反色,明暗两套都成立 */\n font-weight: 600;\n}\n.aic-chip__count { opacity: .7; margin-left: 4px; }\n.aic-chip:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n\n/* --- 徽标 / 标签 --- */\n.aic-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 20px;\n padding: 0 7px;\n border-radius: 5px;\n font-size: 11px;\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n.aic-badge--type { color: var(--dsw-alias-label-primary); }\n.aic-tag--on {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-label-primary);\n}\n.aic-tag {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n/* 进度状态点:用状态 token,且**不只用颜色**表达(06 §8 无障碍) */\n.aic-dot {\n width: 7px; height: 7px; border-radius: 50%;\n flex: 0 0 auto;\n background: var(--dsw-alias-state-idle-primary);\n}\n.aic-dot--doing { background: var(--dsw-alias-state-warn-primary); }\n.aic-dot--done { background: var(--dsw-alias-state-success-primary); }\n.aic-dot--fail { background: var(--dsw-alias-state-error-primary); }\n\n/* --- 进度条 --- */\n.aic-progress {\n height: 6px;\n border-radius: 999px;\n background: var(--dsw-alias-state-idle-primary);\n overflow: hidden;\n}\n.aic-progress__bar {\n height: 100%;\n background: var(--dsw-alias-brand-primary);\n border-radius: 999px;\n}\n/* 课程卡片里的进度:占满卡片内容宽度,标签在上、条在下 */\n.aic-card__progress { display: flex; flex-direction: column; gap: 4px; }\n.aic-card__progress .aic-progress { width: 100%; }\n\n/* --- 课程卡片 --- */\n.aic-group { margin-bottom: 22px; }\n.aic-group__head {\n display: flex; align-items: baseline; gap: 8px;\n margin: 0 0 8px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n.aic-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 10px;\n}\n.aic-card {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n padding: 12px 13px;\n background: var(--dsw-alias-bg-layer-1);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.aic-card__name { font-weight: 600; font-size: 13.5px; }\n.aic-card__desc {\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.aic-card__meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.aic-card__actions { display: flex; gap: 6px; margin-top: 2px; justify-content: flex-end; }\n\n/* --- 大纲:章节与知识点(章节**不是卡片**:无边框、无圆角、无底色) --- */\n.aic-chapter { margin-bottom: 22px; }\n.aic-chapter__head {\n display: flex; align-items: center; gap: 10px;\n padding: 0 0 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l2); /* 分隔线,不是卡片边框 */\n}\n.aic-chapter__no {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n}\n.aic-chapter__title { font-weight: 600; font-size: 14px; flex: 1 1 auto; min-width: 0; }\n.aic-chapter__body { padding: 0; }\n.aic-kp {\n display: flex; align-items: center; gap: 10px;\n padding: 8px 0 8px 14px; /* 左缩进表达从属关系 */\n}\n.aic-kp__no {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n min-width: 31px; /* 原来是 62px —— 编号后的留白太大,减半 */\n}\n.aic-kp__type {\n display: inline-flex; align-items: center;\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n}\n\n/* 知识点状态图标:4 态(未生成 / 已生成未开始 / 已生成进行中 / 已生成已掌握)\n ⚠️ 其中「已生成未开始」与「已生成进行中」形状相同(实心圆),只靠颜色区分\n —— 这两态对色觉障碍用户不可分;缓解手段是 aria-label/title 给全文字。\n 详见 06 号文档 §3.5.5。 */\n/* ---- 两套状态图标(生成 / 学习),互不替代 ---- */\n.aic-genstate, .aic-learnstate { display: inline-flex; align-items: center; flex: 0 0 auto; }\n.aic-genstate { margin-left: 6px; }\n.aic-learnstate { margin-left: 6px; }\n/* ① AI 生成状态:未生成/排队中 = 灰空心;生成中 = 橙实心;已生成 = 绿实心;生成失败 = 红空心 */\n.aic-genstate--idle { color: var(--dsw-alias-state-idle-primary); }\n.aic-genstate--warn { color: var(--dsw-alias-state-warn-primary); }\n.aic-genstate--success { color: var(--dsw-alias-state-success-primary); }\n.aic-genstate--error { color: var(--dsw-alias-state-error-primary); }\n/* ② 学习状态:学习开始/学习中 = 黑(前景色);学习完成 = 绿 */\n.aic-learnstate--primary { color: var(--dsw-alias-label-primary); }\n.aic-learnstate--success { color: var(--dsw-alias-state-success-primary); }\n\n/* 行内右侧成组对齐:进度 / 图标按钮 / 状态点 */\n/* 结构模式(章节编辑)里的 +/- 按钮组:排在编号**前面**,与右侧行内控件分开 */\n.aic-structops {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n margin-right: 6px;\n}\n/* 结构模式的 +/-:**圆形**按钮(描边圆 + 居中字形)。\n 为什么不用默认的圆角方块:它们排在编号前面、成对出现,圆形读起来像「加减一个条目」,\n 与右侧那些「打开某个东西」的方形图标按钮在形状上就能区分开。 */\n.aic-structops .aic-iconbtn {\n width: 20px; height: 20px;\n border-radius: 50%;\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-structops .aic-iconbtn:hover:not([disabled]) {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n background: transparent;\n}\n.aic-structops .aic-iconbtn[disabled] {\n color: var(--dsw-alias-state-idle-primary);\n border-color: var(--dsw-alias-border-l1);\n cursor: default;\n}\n.aic-structops .aic-iconbtn svg { width: 11px; height: 11px; }\n\n.aic-row__right {\n display: inline-flex; align-items: center; gap: 8px;\n flex: 0 0 auto;\n}\n\n/* 图标按钮:无边框、无底色;悬停/聚焦才出现反馈(工具提示由 title + aria-label 承担) */\n.aic-iconbtn {\n display: inline-grid; place-items: center;\n width: 24px; height: 24px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n.aic-iconbtn:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }\n.aic-iconbtn--on { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }\n.aic-iconbtn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n/* 图标按钮禁用态(结构模式的 +/- 会用到):收敛为灰、不给悬停反馈 */\n.aic-iconbtn[disabled] { color: var(--dsw-alias-state-idle-primary); cursor: default; }\n.aic-iconbtn[disabled]:hover { background: transparent; color: var(--dsw-alias-state-idle-primary); }\n\n/* 「章节编辑」开关:**默认灰、选中黑**,不铺底色(用户需求 2026-10-18)。\n 用 `[aria-pressed]` 而不是再加一个类:状态本来就由 aria 表达,样式跟着它走不会跑偏。 */\n.aic-iconbtn--structure { color: var(--dsw-alias-label-secondary); background: transparent; }\n.aic-iconbtn--structure[aria-pressed=\"true\"] { color: var(--dsw-alias-label-primary); background: transparent; }\n.aic-iconbtn--structure:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-iconbtn--structure[aria-pressed=\"true\"]:hover { color: var(--dsw-alias-label-primary); }\n.aic-kp + .aic-kp { border-top: 1px solid var(--dsw-alias-border-l1); }\n/* 本章最后一行的下方也要有线:把知识点列表「收口」,否则末行看着像悬空 */\n.aic-chapter__body > .aic-kp:last-child { border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.aic-kp__name { flex: 1 1 auto; min-width: 0; }\n.aic-kp__name[role=\"button\"] { cursor: pointer; }\n.aic-kp__name[role=\"button\"]:hover { text-decoration: underline; }\n.aic-kp__tools { display: flex; gap: 6px; align-items: center; }\n\n/* --- 面包屑(学习页顶部区域) --- */\n.aic-crumbs { margin-bottom: 8px; }\n.aic-crumbs__list {\n display: flex; flex-wrap: wrap; align-items: center;\n gap: 4px; margin: 0; padding: 0; list-style: none;\n}\n.aic-crumbs__item { display: inline-flex; align-items: center; gap: 4px; }\n.aic-crumbs__sep { color: var(--dsw-alias-label-secondary); }\n.aic-crumbs__link {\n font: inherit; padding: 0; border: 0; border-radius: 3px;\n background: transparent; cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-crumbs__link:hover { color: var(--dsw-alias-label-primary); text-decoration: underline; }\n.aic-crumbs__link:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }\n.aic-crumbs__current { color: var(--dsw-alias-label-primary); }\n\n/* --- 学习页 meta 行(学习时长) --- */\n.aic-lesson__meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }\n.aic-duration {\n display: inline-flex; align-items: center; gap: 5px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 只给读屏用的文本:视觉隐藏但不脱离可访问树 */\n.aic-sr-only {\n position: absolute;\n width: 1px; height: 1px;\n margin: -1px; padding: 0; border: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* --- 学习页 --- */\n/* 学习页:**宽度 100% 撑满所在容器**(不再限宽 860px —— 内容列跟着面板宽度走)。\n *\n * ⚠️ **滚动只留一层**(`.aic-main`)。这里曾经给 `.aic-lesson` 定了 `height:100%`、\n * 又给 `.aic-lesson__body` 加 `overflow:auto`,于是出现**两个滚动容器**:\n * · 内层(body)的滚动条左边是内容、右边还有 `.aic-main` 的 `padding-right: 24px` ——\n * 正是用户看到的「滚动条右侧仍有空白区」;\n * · 内层高度还是「`.aic-main` 的内容高 − pagehead」,算出来常常比实际可见高度长,\n * 于是外层也跟着能滚(双层滚动)。\n * 现在:`.aic-lesson` 用 `min-height: 100%`(短内容仍撑满一屏、导航卡落底),\n * 高度不给上限、内部不滚动 —— 长内容自然撑高,滚动统一交给 `.aic-main`(垫到一个滚动条宽,\n * 布局不会随滚动条出现/消失而横向跳动)。 */\n.aic-lesson {\n width: 100%;\n min-height: 100%;\n display: flex;\n flex-direction: column;\n}\n.aic-lesson__body { flex: 1 1 auto; min-height: 0; }\n/* 内容列跟着撑高;导航卡在内容短时仍被推到底部 */\n.aic-lesson__inner { flex: 1 1 auto; display: flex; flex-direction: column; }\n.aic-lesson__content { flex: 1 1 auto; min-width: 0; }\n\n/* --- 内容底部:上一节 / 下一节 导航卡 --- */\n.aic-lesson__nav {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n margin-top: 24px;\n padding-top: 18px;\n border-top: 1px solid var(--dsw-alias-border-l2); /* 与正文划分区域 */\n}\n.aic-navcard {\n display: flex; flex-direction: column; gap: 4px;\n min-width: 0; padding: 10px 12px;\n font: inherit; text-align: left;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n.aic-navcard:hover { border-color: var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); }\n.aic-navcard:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }\n.aic-navcard--next { text-align: right; }\n.aic-navcard--next .aic-navcard__dir { justify-content: flex-end; }\n.aic-navcard__dir {\n display: inline-flex; align-items: center; gap: 5px;\n color: var(--dsw-alias-label-secondary); font-size: 12px;\n}\n.aic-navcard__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n/* 首节/末节没有相邻项 → 等宽占位,保持两列对齐 */\n.aic-navcard--empty { visibility: hidden; }\n.aic-lesson__head {\n display: flex; align-items: center; gap: 8px;\n margin-bottom: 14px; flex-wrap: wrap;\n}\n.aic-prose { font-size: 13.5px; }\n.aic-prose h1 { font-size: 19px; margin: 18px 0 8px; }\n.aic-prose h2 { font-size: 16px; margin: 16px 0 6px; }\n.aic-prose h3 { font-size: 14px; margin: 14px 0 6px; }\n.aic-prose p { margin: 8px 0; }\n.aic-prose ul, .aic-prose ol { margin: 8px 0; padding-left: 22px; }\n.aic-prose li { margin: 3px 0; }\n.aic-prose code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n.aic-prose pre {\n margin: 10px 0;\n padding: 11px 13px;\n border-radius: 8px;\n overflow: auto;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n.aic-prose pre code { border: 0; background: transparent; padding: 0; }\n\n/* --- 代码高亮 token(映射自 highlight.js;只用宿主主题 token,见 check.mjs 的白名单) ---\n 为什么不用 highlight.js 自带主题:那些主题写的是字面色值,与本插件「零字面颜色 +\n 只用 14 个主题 token」的约束冲突。这里按语义挑色,深浅主题都能看清。 */\n.aic-code { position: relative; }\n.aic-tok-keyword { color: var(--dsw-alias-brand-primary); font-weight: 600; }\n.aic-tok-string { color: var(--dsw-alias-state-success-primary); }\n.aic-tok-number { color: var(--dsw-alias-state-warn-primary); }\n.aic-tok-comment { color: var(--dsw-alias-label-secondary); font-style: italic; }\n.aic-tok-title { color: var(--dsw-alias-brand-primary); }\n.aic-tok-attr { color: var(--dsw-alias-label-primary); font-weight: 500; }\n.aic-tok-type { color: var(--dsw-alias-state-error-primary); }\n.aic-tok-meta { color: var(--dsw-alias-state-idle-primary); }\n.aic-tok-punct { color: var(--dsw-alias-label-secondary); }\n.aic-prose blockquote {\n margin: 10px 0; padding: 2px 0 2px 12px;\n border-left: 3px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n.aic-prose table { border-collapse: collapse; margin: 10px 0; }\n.aic-prose th, .aic-prose td {\n border: 1px solid var(--dsw-alias-border-l2);\n padding: 5px 9px; text-align: left;\n}\n.aic-prose th { background: var(--dsw-alias-bg-layer-2); font-weight: 600; }\n.aic-prose img { max-width: 100%; border-radius: 8px; }\n.aic-figure {\n margin: 10px 0; padding: 10px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n color: var(--dsw-alias-label-secondary);\n text-align: center;\n font-size: 12px;\n}\n.aic-link { color: var(--dsw-alias-label-primary); text-underline-offset: 2px; }\n\n/* --- 幻灯片(Slidev 子集):播放器形态 --- */\n/* 上部展示区:一次只放一页 */\n.aic-player {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n padding: 14px;\n}\n.aic-player .aic-slide { border: 0; background: transparent; padding: 12px 16px; }\n/* 控制条:上一页 / 页码 / 下一页 */\n.aic-player__bar {\n display: flex; align-items: center; justify-content: center; gap: 12px;\n margin-top: 12px;\n}\n.aic-player__pos {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n min-width: 56px; text-align: center;\n}\n/* 下部:当前页讲稿 */\n.aic-notes {\n margin-top: 20px;\n padding-top: 14px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.aic-notes .aic-h2 { margin: 0 0 8px; font-size: 13px; color: var(--dsw-alias-label-secondary); font-weight: 600; }\n.aic-slide {\n aspect-ratio: 16 / 9;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n padding: 26px 30px;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.aic-slide__no {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n display: flex; gap: 8px; align-items: center;\n}\n.aic-slide--cover { justify-content: center; text-align: center; }\n.aic-slide--cover .aic-slide__title { font-size: 26px; }\n.aic-slide__title { font-size: 20px; font-weight: 600; margin: 0; }\n.aic-slide--center { justify-content: center; text-align: center; }\n.aic-slide--quote { justify-content: center; }\n.aic-slide--quote blockquote {\n font-size: 18px; margin: 0; padding-left: 16px;\n border-left: 3px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n.aic-slide--two-cols > .aic-slide__cols,\n.aic-slide--two-cols-header > .aic-slide__cols {\n display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1 1 auto;\n}\n.aic-slide--image-right > .aic-slide__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1 1 auto; }\n.aic-slide--image-left > .aic-slide__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1 1 auto; }\n.aic-slide--image-left > .aic-slide__cols > :first-child { order: 1; }\n\n/* --- 代码 / 测验 --- */\n/* --- 通知条(降级 / 告警 / 提示;`__mark` 是行首的记号) --- */\n.aic-notice {\n display: flex; align-items: flex-start; gap: 8px;\n margin: 10px 0; padding: 8px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-left-width: 3px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n}\n.aic-notice__mark { color: var(--dsw-alias-state-warn-primary); font-weight: 700; flex: 0 0 auto; }\n\n/* --- 空态 / 骨架 / 错误态(课程库的几种页面状态) --- */\n.aic-library { min-width: 0; }\n.aic-empty {\n display: flex; flex-direction: column; align-items: center; gap: 6px;\n padding: 48px 16px; text-align: center;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-empty__title { color: var(--dsw-alias-label-primary); font-weight: 600; }\n.aic-empty__hint { font-size: 12px; }\n.aic-skel {\n height: 92px; margin-bottom: 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n}\n.aic-error {\n display: flex; flex-direction: column; gap: 6px;\n padding: 20px 16px;\n border: 1px solid var(--dsw-alias-state-error-primary);\n border-radius: 10px;\n}\n.aic-error__title { color: var(--dsw-alias-state-error-primary); font-weight: 600; }\n\n/* --- 编辑 / 生成弹窗(三套弹窗共用外壳,只有字段不同) --- */\n.aic-modal {\n position: fixed; inset: 0; z-index: 40;\n display: grid; place-items: center;\n padding: 24px;\n background: var(--dsw-alias-bg-overlay);\n}\n.aic-modal__panel {\n width: min(460px, 100%);\n max-height: 100%;\n overflow: auto;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-base);\n padding: 14px 16px 16px;\n}\n.aic-modal__head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }\n.aic-modal__head .aic-h2 { flex: 1 1 auto; margin: 0; }\n.aic-modal__body { display: flex; flex-direction: column; gap: 10px; }\n.aic-modal__foot {\n display: flex; justify-content: flex-end; gap: 8px;\n margin-top: 16px; padding-top: 12px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n.aic-field { display: flex; flex-direction: column; gap: 4px; }\n.aic-field__label { color: var(--dsw-alias-label-secondary); font-size: 12px; }\n.aic-input--block, .aic-select--block { width: 100%; box-sizing: border-box; }\n.aic-input--area { height: auto; padding: 8px 10px; resize: vertical; line-height: 1.5; }\n\n/* --- 生成弹窗里的属性表 --- */\n.aic-facts {\n margin: 0 0 14px;\n display: flex; flex-direction: column; gap: 6px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n.aic-facts__row { display: flex; gap: 12px; align-items: baseline; }\n.aic-facts__label { flex: 0 0 72px; color: var(--dsw-alias-label-secondary); font-size: 12px; }\n.aic-facts__value { margin: 0; flex: 1 1 auto; min-width: 0; }\n\n/* --- 右侧抽屉(只读大纲) --- */\n.aic-drawer {\n flex: 0 0 340px;\n width: 340px;\n border-left: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-base);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n/* `display: flex` + 关闭按钮上的 `margin-left:auto` 才能把它推到**最右**;\n 这条规则曾经被整块误删,于是关闭图标挤在「只读」徽标后面。 */\n.aic-drawer__head {\n display: flex; align-items: center; gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n}\n.aic-drawer__title { font-weight: 600; }\n.aic-drawer__body { padding: 14px; overflow: auto; flex: 1 1 auto; }\n.aic-drawer__body .aic-row__right { gap: 4px; }\n\n/* ============================================================================\n 主会话里的 `/ai-course` 实时状态卡片(src/client/55-status-card.js)\n\n 这张卡渲染在**宿主的对话流里**(`conversation.chat.commandview`),不在 `.aic-root`\n 容器内 —— 所以规则自带颜色与排版,不依赖父级继承。\n 仍遵守同一套约束:只用宿主主题 token、字面颜色零出现、class 全带 aic- 前缀。\n ============================================================================ */\n\n.aic-cmd {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n padding: 2px 0;\n color: var(--dsw-alias-label-primary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 2px);\n line-height: 1.5;\n}\n\n.aic-cmd__row { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.aic-cmd__row--sub {\n padding-left: 24px;\n color: var(--dsw-alias-label-secondary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 3px);\n}\n.aic-cmd__row--act { padding-left: 24px; margin-top: 4px; }\n\n.aic-cmd__glyph { flex: none; width: 16px; height: 16px; color: var(--dsw-alias-brand-primary); }\n.aic-cmd__glyph--spin { animation: aic-cmd-spin 0.9s linear infinite; }\n@keyframes aic-cmd-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) {\n .aic-cmd__glyph--spin { animation: none; }\n}\n\n.aic-cmd__title { flex: none; font-weight: 600; }\n\n.aic-cmd__badge {\n flex: none;\n padding: 0 8px;\n border-radius: 999px;\n color: var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-bg-layer-2);\n font-size: calc(var(--dsh-content-font-size, 14px) - 4px);\n}\n\n.aic-cmd__step { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.aic-cmd__meta { flex: none; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; }\n.aic-cmd__error { padding-left: 24px; color: var(--dsw-alias-state-error-primary); }\n\n.aic-cmd__open {\n height: 24px;\n padding: 0 10px;\n font: inherit;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n cursor: pointer;\n}\n.aic-cmd__open:hover { background: var(--dsw-alias-bg-layer-2); }\n\n.aic-cmd--done .aic-cmd__badge { color: var(--dsw-alias-state-success-primary); }\n.aic-cmd--done .aic-cmd__glyph { color: var(--dsw-alias-state-success-primary); }\n.aic-cmd--error .aic-cmd__badge { color: var(--dsw-alias-state-error-primary); }\n.aic-cmd--error .aic-cmd__glyph { color: var(--dsw-alias-state-error-primary); }\n.aic-cmd--running .aic-cmd__badge { color: var(--dsw-alias-brand-primary); }\n\n/* ============================================================================\n 主会话回复末尾的**课程大纲卡片**(src/client/65-outline-card.js)\n\n 与进度卡(55-status-card.js)同样渲染在宿主的对话流里:规则自带颜色与排版。\n 约束不变:只用宿主主题 token、字面颜色零出现、class 全带 aic- 前缀。\n ============================================================================ */\n\n.aic-outcard {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n max-width: 100%;\n margin-top: 4px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 2px);\n line-height: 1.5;\n}\n\n.aic-outcard__head { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.aic-outcard__icon { flex: none; width: 16px; height: 16px; color: var(--dsw-alias-brand-primary); }\n.aic-outcard__title { flex: none; font-weight: 600; }\n.aic-outcard__name {\n min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-outcard__badge {\n flex: none;\n padding: 0 8px;\n border-radius: 999px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n font-size: calc(var(--dsh-content-font-size, 14px) - 4px);\n}\n.aic-outcard__tools { margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 6px; }\n\n.aic-outcard__meta {\n display: flex; align-items: center; gap: 12px; flex-wrap: wrap;\n color: var(--dsw-alias-label-secondary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 3px);\n}\n.aic-outcard__progress { display: inline-flex; align-items: center; gap: 6px; }\n.aic-outcard__progress .aic-progress { width: 96px; }\n.aic-outcard__tags { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }\n\n/* 卡片内的大纲树:外面已经有一层卡片,这里不再加边框/底色,只收紧留白 */\n.aic-outcard__body { min-width: 0; }\n.aic-outcard__body .aic-chapter { margin-bottom: 14px; }\n.aic-outcard__body .aic-chapter:last-child { margin-bottom: 0; }\n.aic-outcard__body .aic-kp { padding-left: 10px; }\n/* 嵌入态:补充课程描述与课程级操作(表头由卡片承担) */\n.aic-outline__embedhead { margin: 0 0 10px; }\n.aic-outline__embedrow { display: flex; align-items: center; gap: 8px; margin-top: 6px; }\n\n/* --- 删除(破坏性操作):确认按钮 + 卡片左下角的删除入口 --- */\n/* 危险色只用宿主 token;卡片的删除按钮靠 `margin-right:auto` 与两个入口分开,降低误点 */\n.aic-btn--danger {\n color: var(--dsw-alias-state-error-primary);\n border-color: var(--dsw-alias-state-error-primary);\n background: var(--dsw-alias-bg-layer-1);\n}\n.aic-btn--danger:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-btn--danger:disabled:hover { background: var(--dsw-alias-bg-layer-1); }\n.aic-card__delete { margin-right: auto; }\n/* 确认弹窗的按钮组:取消在左、确认在右 */\n.aic-modal__footrow { display: inline-flex; align-items: center; gap: 8px; }\n\n\n/* --- 内容区(学习页的图文 / 幻灯片) --- */\n.aic-content { min-width: 0; }\n.aic-content__media { margin-top: 10px; font-size: 12px; }\n/* 围栏代码块的语言标记类:有意不定义规则(只是标记,配色/排版由 .aic-prose pre 承担) */\n.aic-code--lang { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n/* Slidev 版式修饰类中唯一没有专门规则的那个:`default` 就是「不加修饰」 */\n.aic-slide--default { /* 默认版式:靠 .aic-slide 的基础样式 */ }\n\n/* --- 版式的结构件(SlidePage 产出的 .aic-slide__cols 两栏) --- */\n/* 两栏都要能收缩:`min-width:0` 否则长单词/表格会把栅格撑破 */\n.aic-slide__cols > * { min-width: 0; }\n/* 侧栏里的图:限高、居中、不拉爆版面(幻灯片是固定比例的画布) */\n.aic-slide__cols .aic-prose { min-height: 0; }\n.aic-slide__cols img {\n max-width: 100%;\n max-height: 240px;\n object-fit: contain;\n display: block;\n margin: 0 auto;\n border-radius: 8px;\n}\n/* 单栏版式里的图同样限高,避免一张竖图把整页顶出画布 */\n.aic-slide > .aic-prose img { max-height: 260px; object-fit: contain; }\n/* 幻灯片里的表格:容器内可横向滚动,不撑破版式 */\n.aic-slide .aic-prose table { display: block; overflow-x: auto; }\n\n/* --- 大纲页顶部的生成进度条(06 §4.5) --- */\n\n/* --- 主会话的常驻 agent 状态条(58-agent-dock.js) --- */\n/* 渲染在输入框上方:自带颜色与排版(不在 .aic-root 里),与对话流里的两张卡片同款约束 */\n.aic-dock {\n display: flex;\n flex-direction: column;\n gap: 2px;\n /* **宽度与输入框一致**:宿主把输入卡片约束成 `max-width: var(--dsh-composer-card-max-width)`\n (= `--dsh-chat-content-width` + 32px),早先我们按 `--dsh-chat-content-width` 走,\n 结果比输入框窄一截、右侧对不齐。`box-sizing` 保证内边距不会撑破这个宽度。 */\n box-sizing: border-box;\n width: 100%;\n max-width: var(--dsh-composer-card-max-width, 100%);\n margin: 0 auto 6px;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 3px);\n}\n.aic-dock__row { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.aic-dock__main { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }\n.aic-dock__label { font-weight: 600; }\n.aic-dock__kp { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary); }\n.aic-dock__failed { color: var(--dsw-alias-state-error-primary); }\n.aic-dock__tools { margin-left: auto; flex: none; }\n\n/* --- 灰显按钮(卡片上的纯图标删除按钮用它):不可用时明确「弱一级」,但仍是可点的图标按钮 --- */\n.aic-btn--muted { color: var(--dsw-alias-label-secondary); }\n.aic-btn--muted:hover { color: var(--dsw-alias-label-primary); }\n.aic-btn--muted[disabled] { color: var(--dsw-alias-state-idle-primary); }\n/* 纯图标按钮不留多余内边距,保持与右侧两个入口的视觉分组 */\n.aic-card__delete.aic-btn--sm { padding: 0 6px; }\n\n/* --- Mermaid 图表(lesson 正文里的 ```mermaid 围栏) --- */\n.aic-mermaid {\n margin: 12px 0;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n overflow-x: auto;\n}\n/* Mermaid 自己产出的 SVG:撑满可用宽度、高度自适应 */\n.aic-mermaid svg { max-width: 100%; height: auto; display: block; margin: 0 auto; }\n.aic-mermaid--pending { color: var(--dsw-alias-label-secondary); font-size: 12px; }\n.aic-mermaid--failed { border-style: dashed; }\n.aic-mermaid__source {\n margin: 8px 0 0;\n padding: 8px 10px;\n overflow: auto;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n}\n\n/* --- 图片(含「内联 SVG 自救」后的容器) --- */\n.aic-figure { display: block; max-width: 100%; }\n.aic-figure svg { max-width: 100%; height: auto; display: block; margin: 0 auto; }\n.aic-figure--broken { font-size: 12px; }\n\n/* --- 「保留原有内容 / 覆盖」单选(生成弹窗) --- */\n.aic-choices { display: flex; flex-direction: column; gap: 6px; }\n.aic-choice { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }\n.aic-choice input { margin: 3px 0 0; flex: none; }\n\n/* --- 生成中的加载图标(不用进度条,见 06 §4.4) --- */\n.aic-spinner { display: inline-flex; align-items: center; color: var(--dsw-alias-brand-primary); }\n.aic-spinner svg { animation: aic-spin 0.9s linear infinite; }\n@keyframes aic-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) {\n .aic-spinner svg { animation: none; }\n}\n\n/* --- 生成中:转动的加载图标替代橙色实心圆(06 §4.3) --- */\n.aic-genstate__spin { animation: aic-spin 0.9s linear infinite; }\n@media (prefers-reduced-motion: reduce) {\n .aic-genstate__spin { animation: none; }\n}\n"
|
|
88
|
+
const AIC_COURSES_CSS = "/* ============================================================================\n AI 课程插件样式(交付物:package/lib/styles.css)\n 由 scripts/build.mjs 内联进 lib/client.js,同时以真实文件形式随包发布\n\n 约束(来自 06-ui-design.md §4):\n · 只用 14 个宿主主题 token;字面颜色零出现(artwork 除外)\n · 所有 class 带 aic- 前缀(与宿主/其他插件共用同一文档)\n · 需要「比边框更醒目又不写字面色」时,朝前景色混色 —— 两个主题自动正确\n · 明暗两套都成立\n ============================================================================ */\n\n.aic-root,\n.aic-shell {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font-size: 13px;\n line-height: 1.55;\n -webkit-font-smoothing: antialiased;\n}\n\n.aic-root { height: 100%; }\n\n/* 高度链:html/body → .aic-root → .aic-shell 都是确定高度,\n 这样页面内部的 height:100% 才有参照物(百分比高度在 auto 父级上会被当成 auto)。 */\n.aic-shell {\n display: flex;\n height: 100%;\n min-height: 0;\n}\n\n.aic-main {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n /* **唯一的滚动容器**(课程库 / 大纲 / 学习三页共用):\n `scrollbar-gutter: stable` 预留给滚动条,避免内容一长一短时整列横向抖动 */\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-gutter: stable;\n padding: 20px 24px 40px;\n}\n\n/* --- 大纲页根容器:高度默认 100%(撑满所在列,内容更高时由列滚动) --- */\n.aic-outline {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* --- 大纲页头 --- */\n.aic-outline__head { margin-bottom: 16px; }\n.aic-outline__titlerow { display: flex; align-items: center; gap: 12px; }\n.aic-outline__titlerow .aic-h1 { flex: 1 1 auto; min-width: 0; margin: 0; }\n\n/* --- 通用排版 --- */\n.aic-h1 { font-size: 17px; font-weight: 600; margin: 0 0 2px; }\n.aic-h2 { font-size: 14px; font-weight: 600; margin: 0 0 8px; }\n.aic-sub { color: var(--dsw-alias-label-secondary); margin: 0; }\n.aic-muted { color: var(--dsw-alias-label-secondary); }\n.aic-pagehead {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 16px;\n}\n\n/* --- 工具栏 --- */\n.aic-toolbar {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.aic-input,\n.aic-select {\n height: 30px;\n padding: 0 10px;\n font: inherit;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n}\n.aic-input { min-width: 200px; }\n.aic-input::placeholder { color: var(--dsw-alias-label-secondary); }\n.aic-input:focus-visible,\n.aic-select:focus-visible,\n.aic-btn:focus-visible,\n.aic-navbtn:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.aic-btn {\n /* inline-flex + gap:让「图标 + 文字」的按钮能对齐;纯文字按钮同样正常 */\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 30px;\n padding: 0 12px;\n font: inherit;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n white-space: nowrap;\n}\n.aic-btn:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-btn--primary {\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n border-color: var(--dsw-alias-brand-primary);\n}\n.aic-btn--primary:hover { opacity: .88; } /* 悬停用不透明度,避免引入字面色 */\n.aic-btn--quiet { border-color: transparent; background: transparent; }\n.aic-btn--quiet:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-btn[disabled] { opacity: .5; cursor: not-allowed; }\n.aic-btn--sm { height: 24px; padding: 0 8px; font-size: 12px; }\n\n/* --- 标签筛选行(取代原来的标签下拉) --- */\n/* 标签控制区**不是卡片**:无边框、无圆角、无底色、无内边距 ——\n 内容与工具栏、分组标题左对齐,直接排在页面上。 */\n.aic-tagbar {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n flex-wrap: wrap;\n margin-bottom: 16px;\n}\n.aic-tagbar__label {\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n padding-top: 2px;\n}\n.aic-tagbar__list { display: flex; gap: 6px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }\n.aic-tagbar__clear { flex: 0 0 auto; }\n.aic-tagbar__count {\n flex: 0 0 auto;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 标签控件:选中态即过滤条件 */\n.aic-chip {\n font: inherit;\n font-size: 12px;\n height: 24px;\n padding: 0 10px;\n border-radius: 999px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n}\n.aic-chip:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-chip[aria-pressed=\"true\"] {\n background: var(--dsw-alias-brand-primary);\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base); /* brand-primary 的天然反色,明暗两套都成立 */\n font-weight: 600;\n}\n.aic-chip__count { opacity: .7; margin-left: 4px; }\n.aic-chip:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n\n/* --- 徽标 / 标签 --- */\n.aic-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 20px;\n padding: 0 7px;\n border-radius: 5px;\n font-size: 11px;\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n.aic-badge--type { color: var(--dsw-alias-label-primary); }\n.aic-tag--on {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-label-primary);\n}\n.aic-tag {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n/* 进度状态点:用状态 token,且**不只用颜色**表达(06 §8 无障碍) */\n.aic-dot {\n width: 7px; height: 7px; border-radius: 50%;\n flex: 0 0 auto;\n background: var(--dsw-alias-state-idle-primary);\n}\n.aic-dot--doing { background: var(--dsw-alias-state-warn-primary); }\n.aic-dot--done { background: var(--dsw-alias-state-success-primary); }\n.aic-dot--fail { background: var(--dsw-alias-state-error-primary); }\n\n/* --- 进度条 --- */\n.aic-progress {\n height: 6px;\n border-radius: 999px;\n background: var(--dsw-alias-state-idle-primary);\n overflow: hidden;\n}\n.aic-progress__bar {\n height: 100%;\n background: var(--dsw-alias-brand-primary);\n border-radius: 999px;\n}\n/* 课程卡片里的进度:占满卡片内容宽度,标签在上、条在下 */\n.aic-card__progress { display: flex; flex-direction: column; gap: 4px; }\n.aic-card__progress .aic-progress { width: 100%; }\n\n/* --- 课程卡片 --- */\n.aic-group { margin-bottom: 22px; }\n.aic-group__head {\n display: flex; align-items: baseline; gap: 8px;\n margin: 0 0 8px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n.aic-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 10px;\n}\n.aic-card {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n padding: 12px 13px;\n background: var(--dsw-alias-bg-layer-1);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.aic-card__name { font-weight: 600; font-size: 13.5px; }\n.aic-card__desc {\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.aic-card__meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.aic-card__actions { display: flex; gap: 6px; margin-top: 2px; justify-content: flex-end; }\n\n/* --- 大纲:章节与知识点(章节**不是卡片**:无边框、无圆角、无底色) --- */\n.aic-chapter { margin-bottom: 22px; }\n.aic-chapter__head {\n display: flex; align-items: center; gap: 10px;\n padding: 0 0 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l2); /* 分隔线,不是卡片边框 */\n}\n.aic-chapter__no {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n}\n.aic-chapter__title { font-weight: 600; font-size: 14px; flex: 1 1 auto; min-width: 0; }\n.aic-chapter__body { padding: 0; }\n.aic-kp {\n display: flex; align-items: center; gap: 10px;\n padding: 8px 0 8px 14px; /* 左缩进表达从属关系 */\n}\n.aic-kp__no {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n min-width: 31px; /* 原来是 62px —— 编号后的留白太大,减半 */\n}\n.aic-kp__type {\n display: inline-flex; align-items: center;\n color: var(--dsw-alias-label-secondary);\n flex: 0 0 auto;\n}\n\n/* 知识点状态图标:4 态(未生成 / 已生成未开始 / 已生成进行中 / 已生成已掌握)\n ⚠️ 其中「已生成未开始」与「已生成进行中」形状相同(实心圆),只靠颜色区分\n —— 这两态对色觉障碍用户不可分;缓解手段是 aria-label/title 给全文字。\n 详见 06 号文档 §3.5.5。 */\n/* ---- 两套状态图标(生成 / 学习),互不替代 ---- */\n.aic-genstate, .aic-learnstate { display: inline-flex; align-items: center; flex: 0 0 auto; }\n.aic-genstate { margin-left: 6px; }\n.aic-learnstate { margin-left: 6px; }\n/* ① AI 生成状态:未生成/排队中 = 灰空心;生成中 = 橙实心;已生成 = 绿实心;生成失败 = 红空心 */\n.aic-genstate--idle { color: var(--dsw-alias-state-idle-primary); }\n.aic-genstate--warn { color: var(--dsw-alias-state-warn-primary); }\n.aic-genstate--success { color: var(--dsw-alias-state-success-primary); }\n.aic-genstate--error { color: var(--dsw-alias-state-error-primary); }\n/* ② 学习状态:学习开始/学习中 = 黑(前景色);学习完成 = 绿 */\n.aic-learnstate--primary { color: var(--dsw-alias-label-primary); }\n.aic-learnstate--success { color: var(--dsw-alias-state-success-primary); }\n\n/* 行内右侧成组对齐:进度 / 图标按钮 / 状态点 */\n/* 结构模式(章节编辑)里的 +/- 按钮组:排在编号**前面**,与右侧行内控件分开 */\n.aic-structops {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n margin-right: 6px;\n}\n/* 结构模式的 +/-:**圆形**按钮(描边圆 + 居中字形)。\n 为什么不用默认的圆角方块:它们排在编号前面、成对出现,圆形读起来像「加减一个条目」,\n 与右侧那些「打开某个东西」的方形图标按钮在形状上就能区分开。 */\n.aic-structops .aic-iconbtn {\n width: 20px; height: 20px;\n border-radius: 50%;\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-structops .aic-iconbtn:hover:not([disabled]) {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n background: transparent;\n}\n.aic-structops .aic-iconbtn[disabled] {\n color: var(--dsw-alias-state-idle-primary);\n border-color: var(--dsw-alias-border-l1);\n cursor: default;\n}\n.aic-structops .aic-iconbtn svg { width: 11px; height: 11px; }\n\n.aic-row__right {\n display: inline-flex; align-items: center; gap: 8px;\n flex: 0 0 auto;\n}\n\n/* 图标按钮:无边框、无底色;悬停/聚焦才出现反馈(工具提示由 title + aria-label 承担) */\n.aic-iconbtn {\n display: inline-grid; place-items: center;\n width: 24px; height: 24px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n.aic-iconbtn:hover { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }\n.aic-iconbtn--on { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); }\n.aic-iconbtn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n/* 图标按钮禁用态(结构模式的 +/- 会用到):收敛为灰、不给悬停反馈 */\n.aic-iconbtn[disabled] { color: var(--dsw-alias-state-idle-primary); cursor: default; }\n.aic-iconbtn[disabled]:hover { background: transparent; color: var(--dsw-alias-state-idle-primary); }\n\n/* 「章节编辑」开关:**默认灰、选中黑**,不铺底色(用户需求 2026-10-18)。\n 用 `[aria-pressed]` 而不是再加一个类:状态本来就由 aria 表达,样式跟着它走不会跑偏。 */\n.aic-iconbtn--structure { color: var(--dsw-alias-label-secondary); background: transparent; }\n.aic-iconbtn--structure[aria-pressed=\"true\"] { color: var(--dsw-alias-label-primary); background: transparent; }\n.aic-iconbtn--structure:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-iconbtn--structure[aria-pressed=\"true\"]:hover { color: var(--dsw-alias-label-primary); }\n.aic-kp + .aic-kp { border-top: 1px solid var(--dsw-alias-border-l1); }\n/* 本章最后一行的下方也要有线:把知识点列表「收口」,否则末行看着像悬空 */\n.aic-chapter__body > .aic-kp:last-child { border-bottom: 1px solid var(--dsw-alias-border-l1); }\n.aic-kp__name { flex: 1 1 auto; min-width: 0; }\n.aic-kp__name[role=\"button\"] { cursor: pointer; }\n.aic-kp__name[role=\"button\"]:hover { text-decoration: underline; }\n.aic-kp__tools { display: flex; gap: 6px; align-items: center; }\n\n/* --- 面包屑(学习页顶部区域) --- */\n.aic-crumbs { margin-bottom: 8px; }\n.aic-crumbs__list {\n display: flex; flex-wrap: wrap; align-items: center;\n gap: 4px; margin: 0; padding: 0; list-style: none;\n}\n.aic-crumbs__item { display: inline-flex; align-items: center; gap: 4px; }\n.aic-crumbs__sep { color: var(--dsw-alias-label-secondary); }\n.aic-crumbs__link {\n font: inherit; padding: 0; border: 0; border-radius: 3px;\n background: transparent; cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-crumbs__link:hover { color: var(--dsw-alias-label-primary); text-decoration: underline; }\n.aic-crumbs__link:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }\n.aic-crumbs__current { color: var(--dsw-alias-label-primary); }\n\n/* --- 学习页 meta 行(学习时长) --- */\n.aic-lesson__meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }\n.aic-duration {\n display: inline-flex; align-items: center; gap: 5px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 只给读屏用的文本:视觉隐藏但不脱离可访问树 */\n.aic-sr-only {\n position: absolute;\n width: 1px; height: 1px;\n margin: -1px; padding: 0; border: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* --- 学习页 --- */\n/* 学习页:**宽度 100% 撑满所在容器**(不再限宽 860px —— 内容列跟着面板宽度走)。\n *\n * ⚠️ **滚动只留一层**(`.aic-main`)。这里曾经给 `.aic-lesson` 定了 `height:100%`、\n * 又给 `.aic-lesson__body` 加 `overflow:auto`,于是出现**两个滚动容器**:\n * · 内层(body)的滚动条左边是内容、右边还有 `.aic-main` 的 `padding-right: 24px` ——\n * 正是用户看到的「滚动条右侧仍有空白区」;\n * · 内层高度还是「`.aic-main` 的内容高 − pagehead」,算出来常常比实际可见高度长,\n * 于是外层也跟着能滚(双层滚动)。\n * 现在:`.aic-lesson` 用 `min-height: 100%`(短内容仍撑满一屏、导航卡落底),\n * 高度不给上限、内部不滚动 —— 长内容自然撑高,滚动统一交给 `.aic-main`(垫到一个滚动条宽,\n * 布局不会随滚动条出现/消失而横向跳动)。 */\n.aic-lesson {\n width: 100%;\n min-height: 100%;\n display: flex;\n flex-direction: column;\n}\n.aic-lesson__body { flex: 1 1 auto; min-height: 0; }\n/* 内容列跟着撑高;导航卡在内容短时仍被推到底部 */\n.aic-lesson__inner { flex: 1 1 auto; display: flex; flex-direction: column; }\n.aic-lesson__content { flex: 1 1 auto; min-width: 0; }\n\n/* --- 内容底部:上一节 / 下一节 导航卡 --- */\n.aic-lesson__nav {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n margin-top: 24px;\n padding-top: 18px;\n border-top: 1px solid var(--dsw-alias-border-l2); /* 与正文划分区域 */\n}\n.aic-navcard {\n display: flex; flex-direction: column; gap: 4px;\n min-width: 0; padding: 10px 12px;\n font: inherit; text-align: left;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n.aic-navcard:hover { border-color: var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); }\n.aic-navcard:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }\n.aic-navcard--next { text-align: right; }\n.aic-navcard--next .aic-navcard__dir { justify-content: flex-end; }\n.aic-navcard__dir {\n display: inline-flex; align-items: center; gap: 5px;\n color: var(--dsw-alias-label-secondary); font-size: 12px;\n}\n.aic-navcard__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n/* 首节/末节没有相邻项 → 等宽占位,保持两列对齐 */\n.aic-navcard--empty { visibility: hidden; }\n.aic-lesson__head {\n display: flex; align-items: center; gap: 8px;\n margin-bottom: 14px; flex-wrap: wrap;\n}\n.aic-prose { font-size: 13.5px; }\n.aic-prose h1 { font-size: 19px; margin: 18px 0 8px; }\n.aic-prose h2 { font-size: 16px; margin: 16px 0 6px; }\n.aic-prose h3 { font-size: 14px; margin: 14px 0 6px; }\n.aic-prose p { margin: 8px 0; }\n.aic-prose ul, .aic-prose ol { margin: 8px 0; padding-left: 22px; }\n.aic-prose li { margin: 3px 0; }\n.aic-prose code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n.aic-prose pre {\n margin: 10px 0;\n padding: 11px 13px;\n border-radius: 8px;\n overflow: auto;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n.aic-prose pre code { border: 0; background: transparent; padding: 0; }\n\n/* --- 代码高亮 token(映射自 highlight.js;只用宿主主题 token,见 check.mjs 的白名单) ---\n 为什么不用 highlight.js 自带主题:那些主题写的是字面色值,与本插件「零字面颜色 +\n 只用 14 个主题 token」的约束冲突。这里按语义挑色,深浅主题都能看清。 */\n.aic-code { position: relative; }\n.aic-tok-keyword { color: var(--dsw-alias-brand-primary); font-weight: 600; }\n.aic-tok-string { color: var(--dsw-alias-state-success-primary); }\n.aic-tok-number { color: var(--dsw-alias-state-warn-primary); }\n.aic-tok-comment { color: var(--dsw-alias-label-secondary); font-style: italic; }\n.aic-tok-title { color: var(--dsw-alias-brand-primary); }\n.aic-tok-attr { color: var(--dsw-alias-label-primary); font-weight: 500; }\n.aic-tok-type { color: var(--dsw-alias-state-error-primary); }\n.aic-tok-meta { color: var(--dsw-alias-state-idle-primary); }\n.aic-tok-punct { color: var(--dsw-alias-label-secondary); }\n.aic-prose blockquote {\n margin: 10px 0; padding: 2px 0 2px 12px;\n border-left: 3px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n.aic-prose table { border-collapse: collapse; margin: 10px 0; }\n.aic-prose th, .aic-prose td {\n border: 1px solid var(--dsw-alias-border-l2);\n padding: 5px 9px; text-align: left;\n}\n.aic-prose th { background: var(--dsw-alias-bg-layer-2); font-weight: 600; }\n.aic-prose img { max-width: 100%; border-radius: 8px; }\n.aic-figure {\n margin: 10px 0; padding: 10px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n color: var(--dsw-alias-label-secondary);\n text-align: center;\n font-size: 12px;\n}\n.aic-link { color: var(--dsw-alias-label-primary); text-underline-offset: 2px; }\n\n/* --- 幻灯片(Slidev 子集):播放器形态 --- */\n/* 上部展示区:一次只放一页 */\n.aic-player {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n padding: 14px;\n}\n.aic-player .aic-slide { border: 0; background: transparent; padding: 12px 16px; }\n/* 控制条:上一页 / 页码 / 下一页 */\n.aic-player__bar {\n display: flex; align-items: center; justify-content: center; gap: 12px;\n margin-top: 12px;\n}\n.aic-player__pos {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n min-width: 56px; text-align: center;\n}\n/* 下部:当前页讲稿 */\n.aic-notes {\n margin-top: 20px;\n padding-top: 14px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.aic-notes .aic-h2 { margin: 0 0 8px; font-size: 13px; color: var(--dsw-alias-label-secondary); font-weight: 600; }\n.aic-slide {\n aspect-ratio: 16 / 9;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n padding: 26px 30px;\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.aic-slide__no {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n display: flex; gap: 8px; align-items: center;\n}\n.aic-slide--cover { justify-content: center; text-align: center; }\n.aic-slide--cover .aic-slide__title { font-size: 26px; }\n.aic-slide__title { font-size: 20px; font-weight: 600; margin: 0; }\n.aic-slide--center { justify-content: center; text-align: center; }\n.aic-slide--quote { justify-content: center; }\n.aic-slide--quote blockquote {\n font-size: 18px; margin: 0; padding-left: 16px;\n border-left: 3px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n.aic-slide--two-cols > .aic-slide__cols,\n.aic-slide--two-cols-header > .aic-slide__cols {\n display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1 1 auto;\n}\n.aic-slide--image-right > .aic-slide__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1 1 auto; }\n.aic-slide--image-left > .aic-slide__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; flex: 1 1 auto; }\n.aic-slide--image-left > .aic-slide__cols > :first-child { order: 1; }\n\n/* --- 代码 / 测验 --- */\n/* --- 通知条(降级 / 告警 / 提示;`__mark` 是行首的记号) --- */\n.aic-notice {\n display: flex; align-items: flex-start; gap: 8px;\n margin: 10px 0; padding: 8px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-left-width: 3px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n}\n.aic-notice__mark { color: var(--dsw-alias-state-warn-primary); font-weight: 700; flex: 0 0 auto; }\n\n/* --- 空态 / 骨架 / 错误态(课程库的几种页面状态) --- */\n.aic-library { min-width: 0; }\n.aic-empty {\n display: flex; flex-direction: column; align-items: center; gap: 6px;\n padding: 48px 16px; text-align: center;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-empty__title { color: var(--dsw-alias-label-primary); font-weight: 600; }\n.aic-empty__hint { font-size: 12px; }\n.aic-skel {\n height: 92px; margin-bottom: 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n}\n.aic-error {\n display: flex; flex-direction: column; gap: 6px;\n padding: 20px 16px;\n border: 1px solid var(--dsw-alias-state-error-primary);\n border-radius: 10px;\n}\n.aic-error__title { color: var(--dsw-alias-state-error-primary); font-weight: 600; }\n\n/* --- 编辑 / 生成弹窗(三套弹窗共用外壳,只有字段不同) --- */\n.aic-modal {\n position: fixed; inset: 0; z-index: 40;\n display: grid; place-items: center;\n padding: 24px;\n background: var(--dsw-alias-bg-overlay);\n}\n.aic-modal__panel {\n width: min(460px, 100%);\n max-height: 100%;\n overflow: auto;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-base);\n padding: 14px 16px 16px;\n}\n.aic-modal__head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }\n.aic-modal__head .aic-h2 { flex: 1 1 auto; margin: 0; }\n.aic-modal__body { display: flex; flex-direction: column; gap: 10px; }\n.aic-modal__foot {\n display: flex; justify-content: flex-end; gap: 8px;\n margin-top: 16px; padding-top: 12px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n.aic-field { display: flex; flex-direction: column; gap: 4px; }\n.aic-field__label { color: var(--dsw-alias-label-secondary); font-size: 12px; }\n.aic-input--block, .aic-select--block { width: 100%; box-sizing: border-box; }\n.aic-input--area { height: auto; padding: 8px 10px; resize: vertical; line-height: 1.5; }\n\n/* --- 生成弹窗里的属性表 --- */\n.aic-facts {\n margin: 0 0 14px;\n display: flex; flex-direction: column; gap: 6px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n.aic-facts__row { display: flex; gap: 12px; align-items: baseline; }\n.aic-facts__label { flex: 0 0 72px; color: var(--dsw-alias-label-secondary); font-size: 12px; }\n.aic-facts__value { margin: 0; flex: 1 1 auto; min-width: 0; }\n\n/* --- 右侧抽屉(只读大纲) --- */\n.aic-drawer {\n flex: 0 0 340px;\n width: 340px;\n border-left: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-base);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n/* `display: flex` + 关闭按钮上的 `margin-left:auto` 才能把它推到**最右**;\n 这条规则曾经被整块误删,于是关闭图标挤在「只读」徽标后面。 */\n.aic-drawer__head {\n display: flex; align-items: center; gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n}\n.aic-drawer__title { font-weight: 600; }\n.aic-drawer__body { padding: 14px; overflow: auto; flex: 1 1 auto; }\n.aic-drawer__body .aic-row__right { gap: 4px; }\n\n/* ============================================================================\n 主会话里的 `/ai-course` 实时状态卡片(src/client/55-status-card.js)\n\n 这张卡渲染在**宿主的对话流里**(`conversation.chat.commandview`),不在 `.aic-root`\n 容器内 —— 所以规则自带颜色与排版,不依赖父级继承。\n 仍遵守同一套约束:只用宿主主题 token、字面颜色零出现、class 全带 aic- 前缀。\n ============================================================================ */\n\n.aic-cmd {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n padding: 2px 0;\n color: var(--dsw-alias-label-primary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 2px);\n line-height: 1.5;\n}\n\n.aic-cmd__row { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.aic-cmd__row--sub {\n padding-left: 24px;\n color: var(--dsw-alias-label-secondary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 3px);\n}\n.aic-cmd__row--act { padding-left: 24px; margin-top: 4px; }\n\n.aic-cmd__glyph { flex: none; width: 16px; height: 16px; color: var(--dsw-alias-brand-primary); }\n.aic-cmd__glyph--spin { animation: aic-cmd-spin 0.9s linear infinite; }\n@keyframes aic-cmd-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) {\n .aic-cmd__glyph--spin { animation: none; }\n}\n\n.aic-cmd__title { flex: none; font-weight: 600; }\n\n.aic-cmd__badge {\n flex: none;\n padding: 0 8px;\n border-radius: 999px;\n color: var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-bg-layer-2);\n font-size: calc(var(--dsh-content-font-size, 14px) - 4px);\n}\n\n.aic-cmd__step { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.aic-cmd__meta { flex: none; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; }\n.aic-cmd__error { padding-left: 24px; color: var(--dsw-alias-state-error-primary); }\n\n.aic-cmd__open {\n height: 24px;\n padding: 0 10px;\n font: inherit;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-1);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n cursor: pointer;\n}\n.aic-cmd__open:hover { background: var(--dsw-alias-bg-layer-2); }\n\n.aic-cmd--done .aic-cmd__badge { color: var(--dsw-alias-state-success-primary); }\n.aic-cmd--done .aic-cmd__glyph { color: var(--dsw-alias-state-success-primary); }\n.aic-cmd--error .aic-cmd__badge { color: var(--dsw-alias-state-error-primary); }\n.aic-cmd--error .aic-cmd__glyph { color: var(--dsw-alias-state-error-primary); }\n.aic-cmd--running .aic-cmd__badge { color: var(--dsw-alias-brand-primary); }\n\n/* ============================================================================\n 主会话回复末尾的**课程大纲卡片**(src/client/65-outline-card.js)\n\n 与进度卡(55-status-card.js)同样渲染在宿主的对话流里:规则自带颜色与排版。\n 约束不变:只用宿主主题 token、字面颜色零出现、class 全带 aic- 前缀。\n ============================================================================ */\n\n.aic-outcard {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n max-width: 100%;\n margin-top: 4px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 2px);\n line-height: 1.5;\n}\n\n.aic-outcard__head { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.aic-outcard__icon { flex: none; width: 16px; height: 16px; color: var(--dsw-alias-brand-primary); }\n.aic-outcard__title { flex: none; font-weight: 600; }\n.aic-outcard__name {\n min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n.aic-outcard__badge {\n flex: none;\n padding: 0 8px;\n border-radius: 999px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n font-size: calc(var(--dsh-content-font-size, 14px) - 4px);\n}\n.aic-outcard__tools { margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 6px; }\n\n.aic-outcard__meta {\n display: flex; align-items: center; gap: 12px; flex-wrap: wrap;\n color: var(--dsw-alias-label-secondary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 3px);\n}\n.aic-outcard__progress { display: inline-flex; align-items: center; gap: 6px; }\n.aic-outcard__progress .aic-progress { width: 96px; }\n.aic-outcard__tags { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }\n\n/* 卡片内的大纲树:外面已经有一层卡片,这里不再加边框/底色,只收紧留白 */\n.aic-outcard__body { min-width: 0; }\n.aic-outcard__body .aic-chapter { margin-bottom: 14px; }\n.aic-outcard__body .aic-chapter:last-child { margin-bottom: 0; }\n.aic-outcard__body .aic-kp { padding-left: 10px; }\n/* 嵌入态:补充课程描述与课程级操作(表头由卡片承担) */\n.aic-outline__embedhead { margin: 0 0 10px; }\n.aic-outline__embedrow { display: flex; align-items: center; gap: 8px; margin-top: 6px; }\n\n/* --- 删除(破坏性操作):确认按钮 + 卡片左下角的删除入口 --- */\n/* 危险色只用宿主 token;卡片的删除按钮靠 `margin-right:auto` 与两个入口分开,降低误点 */\n.aic-btn--danger {\n color: var(--dsw-alias-state-error-primary);\n border-color: var(--dsw-alias-state-error-primary);\n background: var(--dsw-alias-bg-layer-1);\n}\n.aic-btn--danger:hover { background: var(--dsw-alias-bg-layer-2); }\n.aic-btn--danger:disabled:hover { background: var(--dsw-alias-bg-layer-1); }\n.aic-card__delete { margin-right: auto; }\n/* 弹窗底部的动作组:把「生成 / 恢复默认 + 保存」收成一组,排在**外壳渲染的取消**右侧。\n 「取消」不在这里(每个弹窗只有外壳那一个),所以别再往组里放取消按钮。 */\n.aic-modal__footrow { display: inline-flex; align-items: center; gap: 8px; }\n\n\n/* --- 内容区(学习页的图文 / 幻灯片) --- */\n.aic-content { min-width: 0; }\n.aic-content__media { margin-top: 10px; font-size: 12px; }\n/* 围栏代码块的语言标记类:有意不定义规则(只是标记,配色/排版由 .aic-prose pre 承担) */\n.aic-code--lang { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n/* Slidev 版式修饰类中唯一没有专门规则的那个:`default` 就是「不加修饰」 */\n.aic-slide--default { /* 默认版式:靠 .aic-slide 的基础样式 */ }\n\n/* --- 版式的结构件(SlidePage 产出的 .aic-slide__cols 两栏) --- */\n/* 两栏都要能收缩:`min-width:0` 否则长单词/表格会把栅格撑破 */\n.aic-slide__cols > * { min-width: 0; }\n/* 侧栏里的图:限高、居中、不拉爆版面(幻灯片是固定比例的画布) */\n.aic-slide__cols .aic-prose { min-height: 0; }\n.aic-slide__cols img {\n max-width: 100%;\n max-height: 240px;\n object-fit: contain;\n display: block;\n margin: 0 auto;\n border-radius: 8px;\n}\n/* 单栏版式里的图同样限高,避免一张竖图把整页顶出画布 */\n.aic-slide > .aic-prose img { max-height: 260px; object-fit: contain; }\n/* 幻灯片里的表格:容器内可横向滚动,不撑破版式 */\n.aic-slide .aic-prose table { display: block; overflow-x: auto; }\n\n/* --- 大纲页顶部的生成进度条(06 §4.5) --- */\n\n/* --- 主会话的常驻 agent 状态条(58-agent-dock.js) --- */\n/* 渲染在输入框上方:自带颜色与排版(不在 .aic-root 里),与对话流里的两张卡片同款约束 */\n.aic-dock {\n display: flex;\n flex-direction: column;\n gap: 2px;\n /* **宽度与输入框一致**:宿主把输入卡片约束成 `max-width: var(--dsh-composer-card-max-width)`\n (= `--dsh-chat-content-width` + 32px),早先我们按 `--dsh-chat-content-width` 走,\n 结果比输入框窄一截、右侧对不齐。`box-sizing` 保证内边距不会撑破这个宽度。 */\n box-sizing: border-box;\n width: 100%;\n max-width: var(--dsh-composer-card-max-width, 100%);\n margin: 0 auto 6px;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: calc(var(--dsh-content-font-size, 14px) - 3px);\n}\n.aic-dock__row { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.aic-dock__main { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }\n.aic-dock__label { font-weight: 600; }\n.aic-dock__kp { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary); }\n.aic-dock__failed { color: var(--dsw-alias-state-error-primary); }\n.aic-dock__tools { margin-left: auto; flex: none; }\n\n/* --- 灰显按钮(卡片上的纯图标删除按钮用它):不可用时明确「弱一级」,但仍是可点的图标按钮 --- */\n.aic-btn--muted { color: var(--dsw-alias-label-secondary); }\n.aic-btn--muted:hover { color: var(--dsw-alias-label-primary); }\n.aic-btn--muted[disabled] { color: var(--dsw-alias-state-idle-primary); }\n/* 纯图标按钮不留多余内边距,保持与右侧两个入口的视觉分组 */\n.aic-card__delete.aic-btn--sm { padding: 0 6px; }\n\n/* --- Mermaid 图表(lesson 正文里的 ```mermaid 围栏) --- */\n.aic-mermaid {\n margin: 12px 0;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n overflow-x: auto;\n}\n/* Mermaid 自己产出的 SVG:撑满可用宽度、高度自适应 */\n.aic-mermaid svg { max-width: 100%; height: auto; display: block; margin: 0 auto; }\n.aic-mermaid--pending { color: var(--dsw-alias-label-secondary); font-size: 12px; }\n.aic-mermaid--failed { border-style: dashed; }\n.aic-mermaid__source {\n margin: 8px 0 0;\n padding: 8px 10px;\n overflow: auto;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n}\n\n/* --- 图片(含「内联 SVG 自救」后的容器) --- */\n.aic-figure { display: block; max-width: 100%; }\n.aic-figure svg { max-width: 100%; height: auto; display: block; margin: 0 auto; }\n.aic-figure--broken { font-size: 12px; }\n\n/* --- 「保留原有内容 / 覆盖」单选(生成弹窗) --- */\n.aic-choices { display: flex; flex-direction: column; gap: 6px; }\n.aic-choice { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }\n.aic-choice input { margin: 3px 0 0; flex: none; }\n\n/* --- 生成中的加载图标(不用进度条,见 06 §4.4) --- */\n.aic-spinner { display: inline-flex; align-items: center; color: var(--dsw-alias-brand-primary); }\n.aic-spinner svg { animation: aic-spin 0.9s linear infinite; }\n@keyframes aic-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) {\n .aic-spinner svg { animation: none; }\n}\n\n/* --- 生成中:转动的加载图标替代橙色实心圆(06 §4.3) --- */\n.aic-genstate__spin { animation: aic-spin 0.9s linear infinite; }\n@media (prefers-reduced-motion: reduce) {\n .aic-genstate__spin { animation: none; }\n}\n"
|
|
89
89
|
|
|
90
90
|
/* 「AI 备注」上限(构建期从 src/host/05-constants.mjs 的 PROMPT_MAX 注入) */
|
|
91
91
|
const AIC_PROMPT_MAX = 2000
|
|
@@ -2466,6 +2466,7 @@ function aicRegisterAgentDock (ctx, slots, t) {
|
|
|
2466
2466
|
function ModalShell (props) {
|
|
2467
2467
|
const [host] = React.useState(() => (typeof document === 'undefined' ? null : document.createElement('div')))
|
|
2468
2468
|
const panelRef = React.useRef(null)
|
|
2469
|
+
const cancelRef = React.useRef(null)
|
|
2469
2470
|
const onCloseRef = React.useRef(props.onClose)
|
|
2470
2471
|
onCloseRef.current = props.onClose
|
|
2471
2472
|
React.useEffect(() => {
|
|
@@ -2486,9 +2487,11 @@ function ModalShell (props) {
|
|
|
2486
2487
|
return Array.from(panel.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'))
|
|
2487
2488
|
.filter((el) => !el.disabled)
|
|
2488
2489
|
}
|
|
2489
|
-
/*
|
|
2490
|
+
/* 打开时聚焦:破坏性弹窗(`focusCancel`)优先落在**取消**上(避免一个回车就删了),
|
|
2491
|
+
其余聚焦第一个可聚焦元素;都没有则聚焦面板本身,保证键盘流有落点。 */
|
|
2490
2492
|
const list = focusables()
|
|
2491
|
-
|
|
2493
|
+
const wanted = props.focusCancel && cancelRef.current && !cancelRef.current.disabled ? cancelRef.current : list[0]
|
|
2494
|
+
if (wanted) wanted.focus()
|
|
2492
2495
|
else if (panelRef.current && typeof panelRef.current.focus === 'function') panelRef.current.focus()
|
|
2493
2496
|
const onKey = (ev) => {
|
|
2494
2497
|
if (ev.key === 'Escape') { ev.preventDefault(); onCloseRef.current(); return }
|
|
@@ -2529,7 +2532,15 @@ function ModalShell (props) {
|
|
|
2529
2532
|
h(
|
|
2530
2533
|
'div',
|
|
2531
2534
|
{ className: 'aic-modal__foot' },
|
|
2532
|
-
|
|
2535
|
+
/* 「取消」只由外壳渲染**一个**:各弹窗的 `foot` 只放自己的动作按钮
|
|
2536
|
+
(保存 / 生成 / 删除确认 …),不要再自带取消 —— 否则会出现两个取消。 */
|
|
2537
|
+
h('button', {
|
|
2538
|
+
className: 'aic-btn',
|
|
2539
|
+
type: 'button',
|
|
2540
|
+
ref: cancelRef,
|
|
2541
|
+
disabled: !!props.cancelDisabled,
|
|
2542
|
+
onClick: props.onClose,
|
|
2543
|
+
}, props.t('modal.cancel')),
|
|
2533
2544
|
props.foot,
|
|
2534
2545
|
),
|
|
2535
2546
|
),
|
|
@@ -2544,10 +2555,11 @@ function FieldLabel (props) {
|
|
|
2544
2555
|
}
|
|
2545
2556
|
|
|
2546
2557
|
/**
|
|
2547
|
-
*
|
|
2558
|
+
* 二次确认弹窗(破坏性操作专用:删除课程 / 批量生成前的覆盖确认)。
|
|
2548
2559
|
*
|
|
2549
2560
|
* 与编辑/生成弹窗共用同一套外壳(`ModalShell`):Escape / 遮罩点击 = 取消。
|
|
2550
|
-
*
|
|
2561
|
+
* 「取消」由外壳渲染(全项目只有这一处),这里只放**确认**按钮;确认按钮是危险色,
|
|
2562
|
+
* 默认焦点落在取消上(`focusCancel`)—— 避免一个回车就把课删了。
|
|
2551
2563
|
*/
|
|
2552
2564
|
function ConfirmModal (props) {
|
|
2553
2565
|
const t = props.t
|
|
@@ -2558,23 +2570,16 @@ function ConfirmModal (props) {
|
|
|
2558
2570
|
t: t,
|
|
2559
2571
|
title: props.title,
|
|
2560
2572
|
onClose: loading ? () => {} : props.onClose,
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
h('button', {
|
|
2572
|
-
className: 'aic-btn aic-btn--danger',
|
|
2573
|
-
type: 'button',
|
|
2574
|
-
disabled: loading,
|
|
2575
|
-
onClick: props.onConfirm,
|
|
2576
|
-
}, loading ? t('modal.deleting') : props.confirmLabel),
|
|
2577
|
-
),
|
|
2573
|
+
/* 提交中禁用取消(也一并挡住 Escape / 遮罩关闭,onClose 此时是空操作) */
|
|
2574
|
+
cancelDisabled: loading,
|
|
2575
|
+
/* 破坏性操作:打开时聚焦「取消」 */
|
|
2576
|
+
focusCancel: true,
|
|
2577
|
+
foot: h('button', {
|
|
2578
|
+
className: 'aic-btn aic-btn--danger',
|
|
2579
|
+
type: 'button',
|
|
2580
|
+
disabled: loading,
|
|
2581
|
+
onClick: props.onConfirm,
|
|
2582
|
+
}, loading ? t('modal.deleting') : props.confirmLabel),
|
|
2578
2583
|
},
|
|
2579
2584
|
h(
|
|
2580
2585
|
'div',
|
package/lib/styles.css
CHANGED
|
@@ -810,7 +810,8 @@
|
|
|
810
810
|
.aic-btn--danger:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
811
811
|
.aic-btn--danger:disabled:hover { background: var(--dsw-alias-bg-layer-1); }
|
|
812
812
|
.aic-card__delete { margin-right: auto; }
|
|
813
|
-
/*
|
|
813
|
+
/* 弹窗底部的动作组:把「生成 / 恢复默认 + 保存」收成一组,排在**外壳渲染的取消**右侧。
|
|
814
|
+
「取消」不在这里(每个弹窗只有外壳那一个),所以别再往组里放取消按钮。 */
|
|
814
815
|
.aic-modal__footrow { display: inline-flex; align-items: center; gap: 8px; }
|
|
815
816
|
|
|
816
817
|
|