diff --git a/docs/DESIGN.md b/docs/DESIGN.md index e87db404..1fd51994 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -257,6 +257,12 @@ AI 打标签的建议在应用或放弃前一直存着,刷新或重开分拣 - 内置播放框的 UP 标签行只给关注数据里有的 UP:在关注的可以加标签;已取消关注的只显示原来的标签、没有「+ 标签」;从没关注过的整行不显示。为什么:UP 标签挂在关注列表的 UP 上,没关注的人没有地方挂;另存一份「打了标签但没关注的 UP」会多出一套看不见、不跟着刷新的数据。视频投稿同理反过来:那些视频不在收藏夹,只能给 UP 打标签,不能给视频打。 - B站 自动连播到下一个视频时,两种模式的内置播放框都跟着它:粉色卡片、标签行和 T 都换到新视频。关注里认不出新视频的 UP(不在视频投稿、也不在 UP 卡片的最近投稿里)时,UP 标签行不显示,T 不做事。为什么:留在前一个视频上,T 会给前一个 UP 打标签。 +### 专注模式 + +- 右上角的图标按钮是 36×36 的正圆,按钮组的右边缘和字幕栏的右边缘对齐。 +- 字幕行的时间戳比正文轻:`--mdg-faint`、常规字重、等宽数字。 +- 设置面板里的控件都是 28px 高;「跟随」「章节」是勾选框加文字,外面不套框。 + ### B站 页面上的标记 #### 定下来的决定(改之前先问用户) @@ -359,6 +365,9 @@ B站 页面上的 AI 判断照上表:值得留绿、可清理红、拿不准 - 设置页各节按这个顺序排:读字幕、笔记格式、观看进度、AI 平台、分拣台、MCP、Obsidian、数据与存储、高级。用到 AI 的三节(AI 平台、分拣台、MCP)挨在一起,和 AI 无关的观看进度排在它们前面;MCP 排在分拣台后面,因为它的开关管分拣台和收藏。为什么:按和 AI 有没有关系分组,找 AI 相关的设置不用跳着看(2026-11-10 用户定)。 - 设置页「AI 平台」的「视频」「网页」是页签,照 B站 空间页的页签:16px,当前的加粗、下面 3px 粉线,旁边小灰字写用在哪(「B站 · YouTube」「其他网站」),一次只显示一组;左右方向键切换。每次打开设置页在「视频」。为什么:两组的项同名,摆在一起分不清在改哪一组(2026-10-22 用户定)。 +- 设置页的输入框、下拉框和同一行里的按钮都是 32px 高(`--control-h`)。左侧目录的当前项照「颜色」表:灰底、左边一道粉线、字是 `--text`。 +- 设置页 Obsidian 的「测试连接」在 Key 输入框右边,结果写在「Local REST API Key」名字下面(成功绿字、失败红字),没测过时不占位置。2026-11-14 用户定:填完 Key 手就在那里,也和 Obsidian 插件自己「左名字右输入框」的设置页对得上。 +- 弹窗里能滚动的框(字幕预览)有边框,不能滚动的(「详情」)只有底色。2026-11-14 用户定:边框表示这里可以滚动。 #### 定下来的决定(改之前先问用户) @@ -374,12 +383,15 @@ B站 页面上的 AI 判断照上表:值得留绿、可清理红、拿不准 - 1024 宽时,第 2 行的搜索框和按钮换到页签下面一行。720 宽时没有左栏:左栏变成顶栏的下拉框(选项带数量),⚙ 放到第 2 行最后。 - 头部收起:第 2 行最前面有一个按钮,展开时是 ▴「收起头部」,收起时是 ▾「展开头部」,只有它收起和展开头部。收起后是一条细栏,只剩「名字 · 数量」、第 2 行的页签和右边的搜索 / 刷新 / 导出,没有封面,细栏自己是一张圆角白卡,上面留的空和展开时一样。滚动、回到顶上、焦点进到头部都不改它;切换收藏夹、页签和模式也不改。两种模式各记各的,重开分拣台后照旧;切到另一种模式时,它的收起状态当场生效,和重开分拣台一样不播动画。列表滚动后头部下沿出现阴影(展开时在第 4 行下面,收起时在细栏下面),滚回顶上就消失,说明卡片在头部底下;阴影不改收起状态。列表从头部底下穿过,收起和展开只是平移和淡出,卡片不跳;内置播放框的上沿跟着头部的下沿走。收起和展开用 0.15 秒,系统设了减少动态效果就不动画。760 及以下一样。选中栏和弹窗不管头部收没收,窗口矮也不特殊处理。不设快捷键:它是偶尔才改的布局开关,不是分拣时一直要按的。 - 内置播放框(点卡片后在右边打开的 B站 页面)没有顶栏。从上往下是:收藏夹的「视频标签」行、「UP 标签」行,然后是 B站 页面。关闭是播放框右上角骑在边框上的圆形 ×(`#viewerCloseBtn`,一半在框外,tooltip「关闭播放(Esc)」),或者按 Esc。标签行右边留出空,× 不压住标签。760 及以下内置播放框占掉列表的位置,× 还在同一个角上。 +- 封面上只放时长、观看进度和「看完了」,不放 AI 判断。2026-11-14 用户定:判断行的徽标和右边按钮的颜色已经在说同一个结论。 +- 当前卡片(键盘和鼠标所在的那张)只有 2px 深色框,没有阴影,和选中、播放两种状态一样只靠框。AI 没给判断时「保留」和「取消收藏」是同一种按钮样子。 +- 关注的 UP 卡片和收藏夹卡片同一套间距(卡片之间 12px、内边距 12px)。关注列表上面的普通提示只有灰字和按钮,没有框;警告才有琥珀框。 #### 定下来的决定(改之前先问用户) - 第 3、4 行开头有灰色大字「状态」「标签」,行与行之间有间距和细线。为什么:宽屏下一行两头离得太远,两行胶囊容易看混。 - 第 3 行「全部」后面有竖线,组和组之间也有竖线。 -- 卡片上不放勾选框:蓝框加蓝底已经够看出选中,圆勾在小封面上会压住 AI 判断标签和「看到 X%」标签(2026-10-09 去掉)。 +- 卡片上不放勾选框:蓝框加蓝底已经够看出选中,圆勾在小封面上会压住 AI 判断标签和「看到 X%」标签(2026-10-09 去掉;封面上的 AI 判断标签 2026-11-14 也去掉了)。 - 头部只由按钮收起和展开,不随滚动变(2026-10-14 用户定);封面不缩,静态排版不变。为什么:屏幕高度留给卡片,同时随时能操作筛选。头部放的是排序、筛选、标签这些长期设置,收不收由你定,跟着滚动自己变反而打断你。 - 内置播放框的关闭是右上角骑在边框上的圆形 ×,不放在标签行末尾(会像关闭那一行)。 @@ -389,7 +401,8 @@ B站 页面上的 AI 判断照上表:值得留绿、可清理红、拿不准 - 头部三行是一张白卡,和分拣台的一样(`--surface` 底、`--border` 描边、`--radius-lg` 圆角),上面留 14px、左右 16px(600 及以下都是 8px),行与行之间一道 `--border` 细线。第 2、3 行开头的行名、筛选片、右边的按钮和分拣台第 3、4 行一样。 - 卡片是分拣台的卡片:`--radius-lg` 圆角,卡片之间 12px,选中是蓝框加蓝底。标题 15px、字重 500,信息行 `--faint` 12px。星标单独一列,标题、信息行和正文左边对齐;★ 用 `--star` 黄,对着标题第一行居中。 -- 卡片右上角的按钮是两列一样宽的网格,继续问是灰底;「删除 AI 对话」是 `.quiet` 红字。 +- 卡片右上角的按钮是两列一样宽的网格,继续问是灰底;「删除 AI 对话」是 `.quiet` 红字,12px、22px 高,和同一行的「查看 N 轮问答」一样大。 +- 第 1 行的状态文字和弹窗的状态行是同一套:进行中是转圈,失败是红框,其余是绿点加粗。2026-11-14 用户定。 - 「全选」跟在第 2 行的状态片后面、同一行最右,放不下才换到下一行靠右。为什么:分拣台 760 及以下让状态片占满一行、按钮另起一行,是因为它的状态片多;摘读记录只有两三个,另起一行会空出一条。2026-10-22 用户定 - 760 及以下:两行按钮挪到正文和问答那一行下面,从正文左边开始,标题占满整行;「删除 AI 对话」照旧在问答那一行最右。第 1 行放不下时,搜索框和「清空全部 AI 对话」一起换到下一行,搜索框占满剩下的宽度。 diff --git a/extension/badges.css b/extension/badges.css index 22ef60bf..55d57152 100644 --- a/extension/badges.css +++ b/extension/badges.css @@ -65,8 +65,8 @@ span.mdg-badge.mdg-above { display: flex !important; max-width: 100%; margin: 0 gap: 8px; margin-top: 8px; padding: 6px 10px; - border-left: 3px solid #7c3ed1; - border-radius: 6px; + border-left: 2px solid #7c3ed1; + border-radius: 0 6px 6px 0; background: rgba(124, 62, 209, 0.07); font-size: 15px; line-height: 22px; @@ -186,8 +186,8 @@ span.mdg-ups.mdg-ups-line[data-mid] { display: flex !important; max-width: 100%; gap: 6px; margin-bottom: 8px; padding: 8px 12px; - border-left: 3px solid #7c3ed1; - border-radius: 6px; + border-left: 2px solid #7c3ed1; + border-radius: 0 6px 6px 0; background: var(--bg1, #fff); color: var(--text2, #61666d); font: 13px/18px -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; diff --git a/extension/content.css b/extension/content.css index 0edb01e9..bacc387d 100644 --- a/extension/content.css +++ b/extension/content.css @@ -262,7 +262,7 @@ html[data-mdg-reader-mode="1"][data-mdg-reader-theme="dark"] { display: flex; inset: 0; justify-content: center; - padding: 18px; + padding: var(--mdg-space-4); position: fixed; z-index: 2147483647; } @@ -271,17 +271,17 @@ html[data-mdg-reader-mode="1"][data-mdg-reader-theme="dark"] { background: var(--mdg-surface); border: 1px solid var(--mdg-border); border-radius: var(--mdg-radius-lg); - box-shadow: 0 20px 60px rgba(15, 23, 42, 0.22); + box-shadow: var(--mdg-lift); color: var(--mdg-text); font-family: var(--mdg-font-sans); max-width: 360px; - padding: 18px; - width: calc(100vw - 36px); + padding: var(--mdg-space-4); + width: calc(100vw - 32px); } .mdg-confirm-title { font-size: var(--mdg-fs-lg); - font-weight: 700; + font-weight: 600; margin-bottom: var(--mdg-space-2); } @@ -300,7 +300,7 @@ html[data-mdg-reader-mode="1"][data-mdg-reader-theme="dark"] { line-height: 1.45; max-height: 96px; overflow: auto; - padding: 9px 10px; + padding: var(--mdg-space-2) var(--mdg-space-3); word-break: break-all; } @@ -308,11 +308,11 @@ html[data-mdg-reader-mode="1"][data-mdg-reader-theme="dark"] { display: flex; gap: var(--mdg-space-2); justify-content: flex-end; - margin-top: 14px; + margin-top: var(--mdg-space-4); } .mdg-confirm-actions button { - height: 34px; + height: 32px; padding: 0 14px; } @@ -388,7 +388,7 @@ html[data-mdg-reader-mode="1"][data-mdg-reader-theme="dark"] { background: var(--mdg-warn-bg); border: 1px solid var(--mdg-warn-border); border-radius: var(--mdg-radius-md); - box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18); + box-shadow: var(--mdg-shadow-float); color: var(--mdg-warn); font-family: var(--mdg-font-sans); font-size: var(--mdg-fs-sm); @@ -581,7 +581,6 @@ body[data-mdg-reader-mode="1"] .mdg-reading-actions { gap: 10px; flex-wrap: wrap; justify-content: flex-end; - margin-right: -20px; margin-top: -8px; pointer-events: auto; transition: opacity 0.2s ease; @@ -606,10 +605,9 @@ body[data-mdg-reader-mode="1"] .mdg-reading-icon-btn { display: inline-flex; align-items: center; justify-content: center; - width: 34px; - height: 34px; - min-height: 36px; - padding: 0 14px; + width: 36px; + height: 36px; + padding: 0; border: 1px solid var(--mdg-border); border-radius: 999px; background: var(--mdg-surface); @@ -747,11 +745,7 @@ body[data-mdg-reader-mode="1"] .mdg-reading-toggle { align-items: center; gap: 6px; width: fit-content; - min-height: 32px; - padding: 0 11px; - border: 1px solid var(--mdg-border); - border-radius: 999px; - background: var(--mdg-surface); + min-height: 28px; color: var(--mdg-text); font-size: var(--mdg-fs-xs); box-sizing: border-box; @@ -765,11 +759,11 @@ body[data-mdg-reader-mode="1"] .mdg-reading-toggle input { body[data-mdg-reader-mode="1"] .mdg-reading-select { width: auto; min-width: 118px; - min-height: 36px; + min-height: 28px; margin: 0; padding: 0 12px; border: 1px solid var(--mdg-border); - border-radius: 999px; + border-radius: var(--mdg-radius-md); background: var(--mdg-surface); color: var(--mdg-text); font-size: var(--mdg-fs-xs); @@ -935,8 +929,9 @@ body[data-mdg-reader-mode="1"] .mdg-reading-item.is-active { body[data-mdg-reader-mode="1"] .mdg-reading-time { font-size: var(--mdg-reader-transcript-font-size); - font-weight: 600; - color: var(--mdg-text) !important; + font-weight: 400; + font-variant-numeric: tabular-nums; + color: var(--mdg-faint) !important; padding-top: 2px; line-height: 1.4; } diff --git a/extension/history/history.css b/extension/history/history.css index 320424d7..00322ddc 100644 --- a/extension/history/history.css +++ b/extension/history/history.css @@ -4,15 +4,19 @@ body { padding: 14px 16px 0; } .rec-head, .records { max-width: 1200px; margin: 0 auto; } /* ---------- the head: one white card as the 分拣台's, rows split by its thin rules (DESIGN 视觉设计 · 分拣台) ---------- */ -.rec-head { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); } +.rec-head { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); } .rec-head > * { padding: 8px 16px; } .rec-head > * + * { border-top: 1px solid var(--border); } .rec-row1 { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-block: 12px; } /* The search and 清空全部 wrap as one, so the rule never starts a line. */ .rec-find { display: flex; align-items: center; gap: 12px; margin-left: auto; } -.status { color: var(--muted); font-size: var(--fs-xs); } +.status { display: inline-flex; align-items: baseline; gap: 5px; color: var(--ok); font-size: var(--fs-xs); font-weight: 600; } .status:empty { display: none; } -.status.is-error { color: var(--danger); } +.status::before { content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%; background: currentColor; transform: translateY(-1px); } +.status[aria-busy="true"] { border: 0; background: none; font-weight: 400; } +.status[aria-busy="true"]::before { width: 0.9em; height: 0.9em; margin: 0; background: none; transform: translateY(0.1em); } +.status.is-error { padding: 3px var(--space-2); border: 1px solid var(--danger-border); border-radius: var(--radius-sm); background: var(--danger-bg); color: var(--danger); font-weight: 400; } +.status.is-error::before { content: none; } /* 清空全部 AI 对话: red text, no frame, a thin rule between it and the search. */ .rec-row1 .danger { position: relative; margin-left: 9px; border-color: transparent; background: none; color: var(--danger); } .rec-row1 .danger:hover { background: var(--danger-bg); } @@ -70,13 +74,13 @@ summary { cursor: pointer; color: var(--text-2); font-size: var(--fs-xs); } /* Two rows of two equal columns, right-aligned: 继续问 and 复制 Markdown beside the title, 下载 .md and 写入 Obsidian beside the meta line. */ -.entry-acts { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-left: 8px; } +.entry-acts { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); margin-left: 8px; } .entry-acts button { width: 100%; } /* The 「查看 N 轮问答」 line under the body and the buttons, 删除 AI 对话 at its right end (red text, no frame) and staying on that line when the 问答 open. */ .entry > .qa { grid-row: 2; grid-column: 2 / -1; display: flex; align-items: flex-start; gap: 8px; } .qa > details { flex: 1; min-width: 0; } -.qa > [data-act="delete"] { margin-top: -1px; color: var(--danger); } +.qa > [data-act="delete"] { height: 22px; color: var(--danger); font-size: var(--fs-xs); } .qa > [data-act="delete"]:hover { background: var(--danger-bg); color: var(--danger); } /* 继续问 is each card's main action: a gray fill with plain text (buttons never use blue text, DESIGN 视觉设计 · 颜色). */ button.ask { background: var(--field-bg); } @@ -91,7 +95,7 @@ button:disabled:has(> .write-label [aria-busy="true"]) { opacity: 0.7; cursor: p .rec-find { flex: 1 1 320px; min-width: 0; } .rec-find .search { flex: 1 1 auto; width: auto; min-width: 0; } /* The buttons go under the body, from its left edge, so nothing squeezes the title. */ - .entry-acts { grid-row: 3; grid-column: 2; justify-self: start; margin: 10px 0 0; } + .entry-acts { grid-row: 3; grid-column: 2; justify-self: start; margin: var(--space-2) 0 0; } } @media (max-width: 600px) { body { padding: 8px 8px 0; } diff --git a/extension/history/history.js b/extension/history/history.js index b92e4594..63e043a1 100644 --- a/extension/history/history.js +++ b/extension/history/history.js @@ -299,7 +299,8 @@ function aiProviderId() { } } const RECENT_CHECK = "在记录上逐个看,不对的按 T 或点「+ 标签」改"; -const aiProgress = (text) => (TagDialogs.ai.progress(aiTags, text), setStatus(text)); +// A batch's line (「AI 正在处理第 1 批…」, a wait) does not start with 正在 but is still in progress. +const aiProgress = (text) => (TagDialogs.ai.progress(aiTags, text), setStatus(text, false, null, true)); const aiTags = { who: "记录", sees: `标题、是视频还是网页、现有标签和你与 AI 的对话(问和答,每条记录最多 ${UI.READING_AI_CHARS} 字);没有对话的记录看分拣台的 AI 总结和备注`, @@ -385,7 +386,8 @@ async function runAi({ instruction, scope, allowRemove }) { const last = r.errors.at(-1); // A stop for a setup or access error says so with its remedy, the dialog open or not, as the 分拣台's banner. if (r.error) setStatus(r.proposal ? last : UI.aiTagsFailed(last), true, aiRemedy(r)); - else if (open) return; + // The dialog shows the result; the last batch's line is over. + else if (open) return setStatus(""); else if (r.proposal) setStatus("AI 打标签已完成,按 I 或点「✦ AI 打标签」确认建议"); else setStatus(UI.aiTagsFailed(last), true); } @@ -642,14 +644,14 @@ function render() { // 「正在…」 lines are in progress: aria-busy grays them (tokens.css) until the result replaces them. // error: a failure the user must handle (red); action: { label, run }, its remedy button after the text. -function setStatus(text, error = false, action = null) { +function setStatus(text, error = false, action = null, busy = text.startsWith("正在")) { els.status.textContent = text; if (action) { const btn = Object.assign(document.createElement("button"), { type: "button", className: "act-btn", textContent: action.label, onclick: action.run }); els.status.append(" ", btn); } els.status.classList.toggle("is-error", error); - if (text.startsWith("正在")) els.status.setAttribute("aria-busy", "true"); + if (busy) els.status.setAttribute("aria-busy", "true"); else els.status.removeAttribute("aria-busy"); } diff --git a/extension/options.css b/extension/options.css index bc6dd912..af285be8 100644 --- a/extension/options.css +++ b/extension/options.css @@ -58,7 +58,7 @@ input[type="password"], select { width: 100%; min-width: 0; - height: 34px; + height: var(--control-h); padding: 0 10px; } @@ -68,7 +68,7 @@ select { input[type="number"] { width: 72px; - height: 34px; + height: var(--control-h); padding: 0 8px; } @@ -275,10 +275,10 @@ h1 .brand-mark { text-decoration: none; } -.toc a[aria-current="true"], -.toc a[aria-current="true"] .ico { - background: var(--action-bg); - color: var(--link); +.toc a[aria-current="true"] { + background: var(--surface-muted); + box-shadow: inset 3px 0 0 var(--accent); + color: var(--text); font-weight: 600; } @@ -308,9 +308,9 @@ main { align-items: center; gap: 10px; margin: 0; - padding: 6px 10px; + padding: 6px 23px; border: 1px solid var(--danger-border); - border-radius: var(--radius-sm); + border-radius: var(--radius-lg); background: var(--danger-bg); color: var(--danger); font-size: var(--fs-sm); @@ -322,7 +322,7 @@ main { .noai { margin: 0; - padding: 12px 16px; + padding: 12px 24px; border-radius: var(--radius-lg); background: var(--surface); color: var(--text-2); @@ -456,8 +456,7 @@ input.switch:disabled { .kind-tabs { display: flex; gap: 32px; - margin-top: 8px; - border-top: 1px solid var(--border); + margin-top: var(--space-4); border-bottom: 1px solid var(--border); } @@ -676,7 +675,8 @@ details.adv[open] > summary::before { margin-bottom: 8px; } -.ai-provider-row :is(input, select) { +.ai-provider-row :is(input, select), +.ai-provider-test { height: var(--control-h); font-size: var(--fs-sm); } @@ -855,8 +855,12 @@ details.adv[open] > summary::before { } /* Obsidian */ +.guide-box + .row { + border-top: 0; +} + .guide-box { - margin: 4px 0 8px; + margin: 4px 0 var(--space-3); padding: 10px 16px; border-radius: var(--radius-md); background: var(--bg); @@ -916,15 +920,31 @@ details.adv[open] > summary::before { color: var(--text); } +/* The Obsidian key with 测试连接 beside it; the answer goes under the key's name. */ .test-line { display: flex; align-items: center; - gap: 12px; - font-size: var(--fs-sm); + gap: var(--space-2); +} + +.test-line .key { + flex: 1; + min-width: 0; +} + +.test-line button { + height: var(--control-h); +} + +.test-result { color: var(--ok); } -.test-line [data-error="true"] { +.test-result:empty { + display: none; +} + +.test-result[data-error="true"] { color: var(--danger); } @@ -954,6 +974,11 @@ button.var .mono { } /* Data */ +/* In a row the row's own padding is the gap below. */ +.row table.data { + margin-bottom: 0; +} + table.data { width: 100%; margin: 4px 0 20px; diff --git a/extension/options.html b/extension/options.html index 20dd8537..10104bde 100644 --- a/extension/options.html +++ b/extension/options.html @@ -279,12 +279,12 @@

MCP

打开后,Claude Code、Cursor、Codex 等 Agent 能通过本机的 MCP 服务用你的登录:搜索和读视频(字幕、评论、画面);读你的 B站收藏夹、关注、历史和稍后再看,以及 YouTube 的稍后观看、赞过的视频、订阅、历史记录和 YouTube 播放列表;改它们:移动、复制、移出收藏夹,新建、改名、删除收藏夹,写分拣台的 AI 判断、保留、备注和标签。把视频加进或移出稍后再看和 YouTube 播放列表。每次修改都记在下面,可以撤销。不含 API Key 和 AI 对话。开着时插件每分钟试连一次本机 127.0.0.1:47823,连上后插件后台会一直运行,不用时关掉。

-
+
安装

在 Agent 里添加 MCP 服务,要先装好 Node 20 或更高版本。这样装的服务每次随 Agent 启动取最新版。Agent 说 MCP 服务比插件旧时(以前按本地文件夹装的会这样),用这里的命令重新添加一次,再重启 Agent。

    -
    +
    Agent 最近的修改

    B站和 YouTube 上的修改留最近 100 次,分拣台里的修改另留最近 100 次。撤销 B站上的修改会在 B站改回去:移走的移回去,移出收藏夹的加回原收藏夹。

      @@ -317,17 +317,16 @@

      Obsidian

      -
      -
      - - +
      + +

      -
      -
      -
      +
      + + +
      -
      diff --git a/extension/popup.css b/extension/popup.css index dc3f7e65..db36cf20 100644 --- a/extension/popup.css +++ b/extension/popup.css @@ -340,7 +340,6 @@ body { } .props { - border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-muted); padding: var(--space-2); diff --git a/extension/sidepanel.css b/extension/sidepanel.css index 8fd38366..f5f0873e 100644 --- a/extension/sidepanel.css +++ b/extension/sidepanel.css @@ -346,9 +346,9 @@ body { .sp-prev { flex-shrink: 0; - margin: 8px 14px 6px; + margin: var(--space-3) var(--space-3) 0; padding-left: 2px; - border: 1px solid var(--border-strong); + border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); overflow: hidden; @@ -488,7 +488,7 @@ body { .sp-messages { flex: 1; overflow-y: auto; - padding: 12px 14px 14px; + padding: var(--space-3) var(--space-3) 14px; display: flex; flex-direction: column; gap: 14px; @@ -693,9 +693,7 @@ body:has(#spInput:not(:placeholder-shown)) .sp-summary-btn { display: flex; justify-content: flex-start; gap: 4px; - margin-top: 12px; - padding-top: 8px; - border-top: 1px solid var(--border); + margin-top: var(--space-2); } .sp-msg-copy-btn { @@ -876,9 +874,16 @@ body:has(#spInput:not(:placeholder-shown)) .sp-summary-btn { font-size: var(--fs-xs); } +/* A reply that failed before its first word: the error box stands alone, without the bubble around it. */ +.sp-msg-assistant:has(> .sp-msg-error:first-child) { + background: none; + padding: 0; +} + .sp-grant-btn { margin-top: 6px; padding: 6px 10px; + border-radius: var(--radius-sm); font-size: var(--fs-xs); } @@ -1158,7 +1163,7 @@ body:has(#spInput:not(:placeholder-shown)) .sp-summary-btn { grid-template-columns: minmax(0, 1fr) 24px; gap: 6px; align-items: center; - border: 1px solid var(--border); + border: 1px solid transparent; border-radius: var(--radius-md); background: var(--surface-muted); padding: 4px; @@ -1201,7 +1206,7 @@ body:has(#spInput:not(:placeholder-shown)) .sp-summary-btn { align-items: baseline; gap: 4px; min-width: 0; - font-size: var(--fs-xs); + font-size: var(--fs-sm); line-height: 1.35; } @@ -1217,11 +1222,11 @@ body:has(#spInput:not(:placeholder-shown)) .sp-summary-btn { flex: 0 0 auto; white-space: nowrap; color: var(--text-2); - font-size: 11px; + font-size: var(--fs-xs); } .sp-history-meta { - font-size: 11px; + font-size: var(--fs-xs); line-height: 1.3; color: var(--muted); white-space: nowrap; diff --git a/extension/triage/fav-folders.js b/extension/triage/fav-folders.js index 7777154e..2b911d43 100644 --- a/extension/triage/fav-folders.js +++ b/extension/triage/fav-folders.js @@ -73,13 +73,10 @@ function coverHtml(it) { const faint = !seen && known && S.seenCfg.mark; const label = seenWords(known, seen); const mark = seen ? `${label}${label}` : faint ? `${label}` : ""; - // Once the user has decided, their decision replaces the AI's verdict label; the AI's reason and summary stay. - const v = isProcessed(it.bvid) ? { verdict: "none" } : verdictOf(it); - const tag = S.analyzing.has(it.bvid) ? `${ANALYZING}` : VERDICTS[v.verdict] ? `${VERDICTS[v.verdict]}` : ""; const dur = it.duration ? `${fmtDuration(it.duration)}` : ""; // A real link for right-click 在新标签页中打开 and ⌘-click; a plain click selects the card. Out of the tab order: the title // is the same link for the keyboard, and the title is what plays. - return ``; + return ``; } async function loadTagsAndCriteria() { diff --git a/extension/triage/follow.css b/extension/triage/follow.css index ecc4b9d2..2a752067 100644 --- a/extension/triage/follow.css +++ b/extension/triage/follow.css @@ -25,15 +25,15 @@ body:not(.follow-mode) :is(.fw-side-slot, .follow-help) { display: none !importa .fw-side-slot { display: none; } .fw-side-select { height: 32px; max-width: 200px; font-weight: 600; } -.fw-list { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; margin: 0 -8px; padding: calc(var(--head-h, 0px) + 8px) 8px 90px; } -.fw-hint { margin: 0; padding: 8px 12px; border: 1px solid var(--info-border); border-radius: var(--radius-md); background: var(--info-bg); color: var(--text-2); line-height: 1.6; } -.fw-hint.warn { border-color: var(--warn-border); background: var(--warn-bg); color: var(--warn); } +.fw-list { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-3); margin: 0 -8px; padding: calc(var(--head-h, 0px) + 8px) 8px 80px; } +.fw-hint { margin: 0; padding: 0 var(--space-3); color: var(--text-2); line-height: 1.6; } +.fw-hint.warn { padding: 8px 12px; border: 1px solid var(--warn-border); border-radius: var(--radius-md); background: var(--warn-bg); color: var(--warn); } .fw-hint button { height: 26px; margin-left: 8px; font-size: var(--fs-xs); } /* ---------- UP card: select box over the avatar | name, status, meta, sign, 3 titles, tags | ★ ---------- */ -.fw-up { display: grid; grid-template-columns: 56px minmax(0, 1fr) auto; gap: 14px; padding: 12px 14px; } -/* Hover as on 收藏夹's cards: the dark ring and the lift; the select box and 「+ 标签」 show. */ -.fw-up:hover:not(.selected, .playing) { outline: 2px solid var(--text-2); outline-offset: -1px; box-shadow: var(--lift); } +.fw-up { display: grid; grid-template-columns: 56px minmax(0, 1fr) auto; gap: var(--space-4); padding: var(--space-3); } +/* Hover as on 收藏夹's cards: the dark ring; the select box and 「+ 标签」 show. */ +.fw-up:hover:not(.selected, .playing) { outline: 2px solid var(--text-2); outline-offset: -1px; } .fw-avatar { width: 56px; height: 56px; border-radius: 50%; overflow: hidden; background: var(--field-bg); } .fw-avatar img { display: block; width: 100%; height: 100%; object-fit: cover; } .fw-name-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; } diff --git a/extension/triage/triage.css b/extension/triage/triage.css index f60ba62e..231ee359 100644 --- a/extension/triage/triage.css +++ b/extension/triage/triage.css @@ -74,7 +74,7 @@ select { height: 28px; max-width: 240px; } /* Whatever is running (粗看, 细看, unfavorite batch, loading, refresh, tag AI, a wait) shows here on every tab. */ /* What is running: gray (blue is for the next step, DESIGN 视觉设计 · 颜色); a wait turns it amber. */ -.activity { display: inline-flex; align-items: center; gap: 8px; height: 28px; padding: 0 4px 0 12px; border: 1px solid var(--action-border); border-radius: 15px; background: var(--action-bg); color: var(--link); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; min-width: 0; } +.activity { display: inline-flex; align-items: center; gap: 8px; height: 28px; padding: 0 4px 0 12px; border: 1px solid var(--action-border); border-radius: var(--radius-pill); background: var(--action-bg); color: var(--link); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; min-width: 0; } /* A narrow window cuts the line, not the countdown or buttons after it; the pill's title is the whole line. */ .activity-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; } .activity > :not(.activity-text) { flex: none; } @@ -91,7 +91,7 @@ select { height: 28px; max-width: 240px; } /* 已刷新 is a pill; its full text and list open below it, right-aligned to it, and slide right to stay on screen. */ .sync-wrap { position: relative; anchor-scope: --sync; } .sync-pill { anchor-name: --sync; } -.pill { height: 30px; padding: 0 12px; border-radius: 15px; white-space: nowrap; } +.pill { height: 28px; padding: 0 12px; border-radius: var(--radius-pill); white-space: nowrap; } .sync-pill::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 6px; border-radius: 50%; background: var(--action); vertical-align: 1px; } .sync-pill.warn { border-color: var(--warn-border); background: var(--warn-bg); color: var(--warn); } .sync-pill.warn::before { background: var(--warn); } @@ -217,7 +217,7 @@ select { height: 28px; max-width: 240px; } :is(.folder-head, .tabrow, .stagebar, .tagbar)::before { content: ""; position: absolute; inset: 0; z-index: -1; border: 1px solid var(--border); border-width: 0 1px; background: var(--surface); } .folder-head::before { border-top-width: 1px; border-radius: var(--radius-lg) var(--radius-lg) 0 0; } :is(.tabrow, .stagebar)::before { border-bottom-width: 1px; } -.tagbar::before { border-bottom-width: 1px; border-radius: 0 0 var(--radius-lg) var(--radius-lg); box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); } +.tagbar::before { border-bottom-width: 1px; border-radius: 0 0 var(--radius-lg) var(--radius-lg); } .viewer, .viewer-close { transition: margin-top 0.15s; } .head-folded :is(.folder-head, .stagebar, .tagbar) { visibility: hidden; opacity: 0; } /* --head1-h is measured from .main's padding edge, so the slim bar keeps the gap above the card. */ @@ -256,7 +256,7 @@ select { height: 28px; max-width: 240px; } .tabs:empty { display: none; } /* Row 2: the tabs; search and the mode's buttons sit on the right of the same line. */ .tabrow { display: flex; flex-wrap: wrap; align-items: center; column-gap: 24px; } -.row-tools { display: flex; align-items: center; gap: 2px; margin-left: auto; padding: 6px 0; } +.row-tools { display: flex; align-items: center; gap: 2px; margin: 0 -8px 0 auto; padding: 6px 0; } /* While it reads, 刷新 gives its place to the progress pill: one thing on screen, not two. */ .row-tools:has(.activity:not([hidden])) .act-btn[aria-busy="true"] { display: none; } .tabs { display: flex; flex: 0 1 auto; min-width: 0; min-height: 40px; align-items: center; gap: 2px; } @@ -286,7 +286,7 @@ select { height: 28px; max-width: 240px; } .stagebar .step-actions { flex-wrap: nowrap; } .stagebar .class-filter, .tagbar .tagfilter { min-height: 28px; } .tagbar > :last-child { grid-column: 2; } -.tags-acts { display: flex; flex: none; flex-wrap: nowrap; justify-content: flex-end; gap: 2px; } +.tags-acts { display: flex; flex: none; flex-wrap: nowrap; justify-content: flex-end; gap: 2px; margin-right: -8px; } /* Row 3's groups (shared.js stateGroup), split by a thin rule. A group wraps whole (inside only when it alone is too wide); the rule trails its group. */ /* Rows 3 and 4 start with their name (shared.js labeledRow), a gray row heading of one width, so both rows' first pills @@ -379,10 +379,10 @@ select { height: 28px; max-width: 240px; } .sel-actions .danger::before { content: ""; position: absolute; left: -12px; top: 8px; height: 16px; border-left: 1px solid var(--border-strong); } .list { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; margin: 0 -8px; padding: calc(var(--head-h, 0px) + 8px) 8px 80px; } -/* Two card states. Current (hover or J/K) is the lifted box; selected (a click on the card or X) is the blue outline and tint. Both can show at once. */ +/* Two card states. Current (hover or J/K) is the dark ring; selected (a click on the card or X) is the blue outline and tint. Both can show at once. */ .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); transition: border-color 0.12s, box-shadow 0.12s; } -/* Current: a dark ring plus the lift, so it reads at a glance; selected (below) swaps the ring for blue. */ -.card.focused { outline: 2px solid var(--text-2); outline-offset: -1px; box-shadow: var(--lift); } +/* Current: a dark ring, so it reads at a glance; selected (below) swaps the ring for blue. */ +.card.focused { outline: 2px solid var(--text-2); outline-offset: -1px; } .card.selected { outline: 2px solid var(--action); outline-offset: -1px; background: var(--action-bg); } /* Playing in the viewer: pink, where you are. Blue stays for 选中. */ .card.playing:not(.selected) { outline: 2px solid var(--accent); outline-offset: -1px; } @@ -394,16 +394,11 @@ select { height: 28px; max-width: 240px; } .empty-state button { display: inline-flex; align-items: center; gap: 6px; height: 36px; padding: 0 20px; } .empty-state button svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } -/* ---------- cover: progress bar and marks, the duration bottom-right, the AI verdict top-left after the select box ---------- */ -.cover-wrap { position: relative; display: block; text-decoration: none; align-self: start; width: 100%; aspect-ratio: 16 / 10; border-radius: var(--radius-md); overflow: hidden; background: var(--action-bg); color: var(--on-image); } +/* ---------- cover: progress bar and marks, the duration bottom-right ---------- */ +.cover-wrap { position: relative; display: block; text-decoration: none; align-self: start; width: 100%; aspect-ratio: 16 / 10; border-radius: var(--radius-md); overflow: hidden; background: var(--surface-muted); color: var(--on-image); } .cover-wrap::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 38%; background: linear-gradient(transparent, var(--cover-scrim)); pointer-events: none; } .cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; } .cover-dur { position: absolute; right: 8px; bottom: 6px; z-index: 1; font-size: var(--fs-xs); font-variant-numeric: tabular-nums; } -.cover-tag { position: absolute; left: 6px; top: 6px; z-index: 1; height: 20px; padding: 0 6px; border-radius: 4px; font-size: var(--fs-xs); font-weight: 600; line-height: 20px; white-space: nowrap; } -.cover-tag.keep { background: var(--ok-bg); color: var(--ok); } -.cover-tag.drop { background: var(--cover-chip); color: var(--on-image); } -.cover-tag.unsure { background: var(--on-image); color: var(--warn); } -.cover-tag.running { background: var(--cover-chip); color: var(--on-image); font-weight: 400; } /* 观看进度: html[data-seen-style] picks the corner tag or the veil (设置页「看完了标记样式」). */ .seen-bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; height: 3px; background: var(--cover-track); pointer-events: none; } .seen-bar i { display: block; height: 100%; background: var(--accent); } @@ -467,7 +462,7 @@ html[data-seen-style="veil"] .cover-wrap.seen .seen-veil { display: flex; } Touch has no hover and no keys, so there they always show (both modes). */ @media (hover: hover) { .card:not(.focused, :focus-within) .more button:not(.on) { visibility: hidden; } } .note { width: 100%; max-width: 560px; margin-top: 6px; padding: 4px 8px; border: 0; border-radius: var(--radius-sm); background: var(--bg); resize: vertical; min-height: 28px; font-size: var(--fs-sm); color: var(--text-2); field-sizing: content; } -.note-add { font-size: var(--fs-xs); height: auto; } +.note-add { font-size: var(--fs-xs); height: auto; margin-left: -8px; } .group-head { margin-top: 10px; padding: 2px 0; color: var(--muted); font-size: var(--fs-xs); } .group-head button { height: 24px; padding: 0 8px; font-size: inherit; } .title-row { display: flex; align-items: center; gap: 6px; min-width: 0; } @@ -480,14 +475,12 @@ button.icon { width: 26px; height: 24px; padding: 0; } .list .pair > button { position: absolute; right: 12px; width: 112px; height: 32px; padding: 0; justify-content: center; font-size: var(--fs-sm); } .list .pair > button:first-child { top: calc(50% - 36px); } .list .pair > button:last-child { top: calc(50% + 4px); } -.list .pair > button:last-child:not(.solid) { border-color: transparent; background: var(--field-bg); } .list .actions .more { margin-left: auto; } /* Beside the viewer the list is narrow: the smaller cover, and the buttons back in the foot row, the pair first. */ .main.viewing .list .card { grid-template-columns: 128px minmax(0, 1fr); gap: 10px; padding-right: 12px; } .main.viewing .list .actions { display: flex; justify-content: flex-end; } .main.viewing .list .pair { display: flex; } .main.viewing .list .pair > button { position: static; width: auto; height: 26px; padding: 0 8px; font-size: var(--fs-xs); } -.main.viewing .list .pair > button:last-child:not(.solid) { border-color: var(--border-strong); background: var(--surface); } .main.viewing .list .actions .more { margin-left: 0; } .main.viewing .list .key { display: none; } .shell:has(.main.viewing) { grid-template-columns: minmax(0, 1fr); } @@ -538,7 +531,7 @@ button.icon { width: 26px; height: 24px; padding: 0; } .viewer-line { --row-label-w: 4.5em; padding-left: calc(12px + var(--row-label-w) + 8px); } .viewer-line > .row-label { margin-left: calc(-1 * var(--row-label-w) - 8px); font-size: inherit; line-height: inherit; } .viewer-line .chip[data-pick], .viewer-line .chip[data-vtag] { cursor: pointer; } -.viewer { display: flex; flex-direction: column; min-height: 0; min-width: 0; margin-top: calc(var(--head-vis) + 8px); background: var(--surface); border: 1px solid var(--border); border-radius: 9px; overflow: hidden; } +.viewer { display: flex; flex-direction: column; min-height: 0; min-width: 0; margin-top: calc(var(--head-vis) + 8px); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; } /* No top bar. × rides on the panel's top-right corner, half outside (its own grid item, so .viewer's overflow does not clip it); the lines keep their right end clear of it. The panel starts 8px down, as the list's first card does, so the × clears row 4's buttons. */ @@ -561,7 +554,7 @@ button.icon { width: 26px; height: 24px; padding: 0; } flex-direction: column; background: var(--surface); border: 1px solid var(--border); - border-radius: 12px; + border-radius: var(--radius-lg); box-shadow: var(--shadow-float); } .basket.collapsed { width: auto; border-color: var(--border-strong); border-radius: var(--radius-md); } @@ -569,7 +562,7 @@ button.icon { width: 26px; height: 24px; padding: 0; } .listpane:has(.selbar) .basket { margin-bottom: 84px; } .basket.collapsed .basket-body { display: none; } .basket-toggle { font-weight: 600; border: 0; background: transparent; border-radius: var(--radius-md); } -.basket:not(.collapsed) .basket-toggle { border-top: 1px solid var(--border); border-radius: 0 0 12px 12px; } +.basket:not(.collapsed) .basket-toggle { border-top: 1px solid var(--border); border-radius: 0 0 var(--radius-lg) var(--radius-lg); } .basket:not(.collapsed) .chev { display: inline-block; transform: rotate(180deg); } .basket.bump { animation: basket-bump 0.5s ease; } @keyframes basket-bump { 40% { transform: scale(1.08); } } @@ -630,8 +623,6 @@ button.icon { width: 26px; height: 24px; padding: 0; } .keys kbd { min-width: 1.6em; padding: 0 4px; border: 1px solid var(--border); border-radius: 4px; background: var(--surface); font: 600 var(--fs-xs)/1.3 ui-monospace, Menlo, monospace; text-align: center; } @media (max-width: 899px) { - .card { grid-template-columns: 96px minmax(0, 1fr); } - .cover { width: 96px; height: 54px; } .toast { bottom: 64px; } }