From 92cfabe74020d150d2eff2c7de22df40a12c2390 Mon Sep 17 00:00:00 2001 From: hectorqin Date: Thu, 22 Oct 2026 11:54:48 +0800 Subject: [PATCH] feat(web): primary buttons fill with brand-text so they outweigh secondaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The light themes' brand fill (#F2B79F etc.) is softer than the white secondary button beside it, so 保存/播放 read as the quieter choice. Primary buttons now use brand-text with bg on it, the same recipe as danger, and a pair the token spec already holds to 4.5:1 in every theme. brand stays for state fills (selected, progress, switches). Spec updated (rule 6). Co-Authored-By: Claude Opus 5.5 --- docs/prototypes/Reader V3 主题语义令牌.md | 13 +++++++------ web/src/features/media/styles/media.css | 2 +- web/src/features/media/styles/presentation.css | 8 +++++--- web/src/features/media/styles/v2.css | 2 +- web/src/styles/reader.css | 10 ++++++---- web/src/styles/theme-tokens.css | 3 +++ 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/prototypes/Reader V3 主题语义令牌.md b/docs/prototypes/Reader V3 主题语义令牌.md index 7a1b8bf..65593c5 100644 --- a/docs/prototypes/Reader V3 主题语义令牌.md +++ b/docs/prototypes/Reader V3 主题语义令牌.md @@ -34,10 +34,10 @@ Reader V3 内置 4 个主题,每个主题含浅色和深色两套取值,共 | ink | `--color-ink` | 正文与标题 | | muted | `--color-muted` | 次要文字、图标 | | line | `--color-line` | 分隔线、描边 | -| brand | `--color-brand` | 主按钮底色、选中态、进度条填充(只作填充,不作文字色) | +| brand | `--color-brand` | 选中态、进度条、开关等状态填充(只作填充,不作文字色) | | brand-soft | `--color-brand-soft` | 选中底色、标签底色 | | on-brand | `--color-on-brand` | 品牌色填充上的文字和图标 | -| brand-text | `--color-brand-text` | 品牌色的文字、图标、链接(新增) | +| brand-text | `--color-brand-text` | 品牌色的文字、图标、链接;主按钮底色(新增) | | ch-{read\|video\|music\|audio} | `--color-ch-read` 等 | 频道图标底色、进度条填充 | | ch-\*-soft | `--color-ch-read-soft` 等 | 继续卡片、频道入口底色 | | ch-\*-text | `--color-ch-read-text` 等 | 频道色的文字(新增) | @@ -153,9 +153,10 @@ Reader V3 内置 4 个主题,每个主题含浅色和深色两套取值,共 3. 品牌色的文字、链接、选中态图标,用 brand-text;频道色的文字用 ch-*-text。不要直接把 brand 或 ch-* 当文字色,浅色主题下对比度不够。 4. 继续卡片、频道入口的底色用 ch-*-soft,文字用 ink 或 ch-*-text。 5. 选中的标签、分段控件用 brand-soft 底加 brand-text 文字。 -6. 抽屉、弹层的遮罩用 scrim。 -7. 影视、音乐播放页是固定深色沉浸界面,使用当前主题的深色取值,不随浅色切换。 -8. 不使用纯黑 `#000` 作背景;纯白只作浅色主题的 surface。 +6. 主按钮(每屏唯一的主要操作,如「保存」「播放」)用 brand-text 底加 bg 文字,与危险按钮(danger 底加 bg 文字)同一种画法。浅色主题的 brand 太淡,作主按钮底色时比旁边的白色次按钮还轻,看不出主次。brand 填充保留给选中、进度、开关这类状态。 +7. 抽屉、弹层的遮罩用 scrim。 +8. 影视、音乐播放页是固定深色沉浸界面,使用当前主题的深色取值,不随浅色切换。 +9. 不使用纯黑 `#000` 作背景;纯白只作浅色主题的 surface。 ## 5. 对比度要求 @@ -165,7 +166,7 @@ Reader V3 内置 4 个主题,每个主题含浅色和深色两套取值,共 | --- | --- | | ink 对 bg、surface | 4.5:1 以上 | | muted 对 bg、surface-2 | 4.5:1 以上 | -| brand-text 对 brand-soft、bg | 4.5:1 以上 | +| brand-text 对 brand-soft、bg(bg 文字在 brand-text 主按钮上即此组合) | 4.5:1 以上 | | on-brand 对 brand | 4.5:1 以上 | | ch-*-text 对 ch-*-soft | 4.5:1 以上 | | on-ch 对 ch-\* | 4.5:1 以上 | diff --git a/web/src/features/media/styles/media.css b/web/src/features/media/styles/media.css index 081317f..96be32f 100644 --- a/web/src/features/media/styles/media.css +++ b/web/src/features/media/styles/media.css @@ -72,7 +72,7 @@ .media-manager .media-library-row{padding:14px;border:1px solid var(--border,#dce2d4);border-radius:12px;background:var(--surface,#f0f0e8);gap:8px}.media-library-row strong{font-size:15px;font-weight:600}.media-library-row small{margin-top:5px}.media-manager .media-library-row button{background:color-mix(in srgb,var(--surface) 96%,var(--text));min-height:40px;font-size:13px} .media-manager .media-library-create,.media-manager .media-library-editor{border:1px solid var(--border,#dce2d4);border-radius:12px;padding:16px;margin:18px 0;background:var(--surface,#f0f0e8);box-sizing:border-box;max-width:100%}.media-manager .media-library-create h2,.media-manager .media-library-editor h2{margin:0 0 14px;font-size:16px}.media-manager .media-form{max-width:100%;margin:0;gap:12px}.media-manager .media-form label{font-size:13px;gap:6px}.media-manager .media-form input,.media-manager .media-form select{width:100%;box-sizing:border-box;background:color-mix(in srgb,var(--surface) 96%,var(--text));min-width:0}.media-manager .media-form small{font-size:12px;line-height:1.6;opacity:.7}.media-manager .media-form button[type=submit]{background:var(--media-fill);color:var(--media-on-accent);border-color:var(--media-fill);font-weight:600} @media(min-width:760px){.media-manager .media-library-row>span{flex-basis:auto}.media-manager .media-library-row button{flex:0 0 auto;min-width:66px}.media-manager .media-library-create,.media-manager .media-library-editor{max-width:720px}.media-manager .media-library-create .media-permission-fields{grid-template-columns:1fr 1fr;gap:14px 18px}.media-manager .media-library-create label:nth-child(2){grid-column:1/-1;grid-row:2}.media-manager .media-form button[type=submit]{justify-self:start;min-width:150px}} -.media-album-playback .media-album-summary>button{font-size:12px;padding:4px 8px}.media-album-playback .media-album-actions>.media-primary{background:var(--media-fill);color:var(--media-on-accent);border-color:var(--media-fill)} +.media-album-playback .media-album-summary>button{font-size:12px;padding:4px 8px}.media-album-playback .media-album-actions>.media-primary{background:var(--media-primary);color:var(--media-on-primary);border-color:var(--media-primary)} .media-album-summary{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;min-height:24px}.media-album-summary>button{font-size:12px;padding:4px 8px}.media-album-actions{justify-content:flex-start;gap:8px}.media-album-start{font-size:12px;line-height:1.5}.media-album-playback .media-season-list{gap:0}.media-album-playback .media-album-disc{font-size:12px;margin:18px 0 4px;color:var(--muted);font-weight:500}.media-album-playback .media-season-row{padding:2px 0;gap:4px}.media-album-playback .media-season-row[data-selected=true]{background:var(--media-selection);border-radius:6px}.media-album-playback .media-album-track-link{display:flex;align-items:center;gap:8px;min-width:0;background:transparent;border:0;padding:8px 6px;min-height:44px}.media-album-number{flex:0 0 22px;text-align:center;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}.media-album-track-title{flex:1;min-width:0;overflow-wrap:anywhere;font-size:14px}.media-album-track-title>small{display:block;font-size:11px;color:var(--muted);margin-top:3px}.media-album-duration{flex-shrink:0;font-size:12px;font-variant-numeric:tabular-nums;color:var(--muted)} .media-grid.media-season-picker{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}.media-season-picker .media-tile{display:grid;grid-template-columns:42px minmax(0,1fr);align-content:center;align-items:center;column-gap:10px;padding:8px;border:1px solid var(--border);border-radius:8px;background:var(--surface);min-height:80px}.media-season-picker .media-cover{grid-row:1 / span 3;width:42px;height:63px;min-width:42px;padding:3px;box-sizing:border-box;font-size:10px;line-height:1.4}.media-season-picker .media-cover-title{-webkit-line-clamp:3}.media-season-picker .media-tile>strong{margin:0;white-space:normal;overflow-wrap:anywhere;font-size:13px}.media-season-picker .media-tile>small{grid-column:2;margin-top:4px;min-width:0}.media-season-picker .media-tile>small:empty{display:none}@media(max-width:700px){.media-grid.media-season-picker{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}} .media-season-playback .media-toolbar>button{flex-shrink:0;white-space:nowrap}.media-season-playback .media-toolbar>span{min-width:0;overflow-wrap:anywhere} diff --git a/web/src/features/media/styles/presentation.css b/web/src/features/media/styles/presentation.css index 0c17dbd..3fd57e0 100644 --- a/web/src/features/media/styles/presentation.css +++ b/web/src/features/media/styles/presentation.css @@ -12,6 +12,8 @@ --media-accent: var(--color-brand-text); --media-fill: var(--color-brand); --media-on-accent: var(--color-on-brand); + --media-primary: var(--color-brand-text); + --media-on-primary: var(--color-bg); --media-selection: var(--color-brand-soft); --media-error-ink: var(--color-danger); --media-error-background: var(--color-danger-soft); @@ -110,8 +112,8 @@ body:has(>#app>.media-screen) { background: var(--color-bg); } .media-cover.square {border-radius:6px} .media-hero {gap:26px;margin-top:24px} .media-hero .media-cover {width:170px;min-width:170px} -.media-screen .media-primary {background:var(--media-fill);color:var(--media-on-accent)} -.media-screen button.media-primary:hover:not(:disabled) {background:var(--media-fill);color:var(--media-on-accent);filter:brightness(.94)} +.media-screen .media-primary {background:var(--media-primary);color:var(--media-on-primary)} +.media-screen button.media-primary:hover:not(:disabled) {background:var(--media-primary);color:var(--media-on-primary);filter:brightness(.94)} .media-task-retry {display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:40px;padding:0 10px;border:1px solid var(--border);border-radius:8px;background:var(--media-selection);color:var(--media-accent);font-size:12px;font-weight:600} .media-task-retry:disabled {opacity:.5} @media(max-width:430px){.media-task-retry span{display:inline}.media-task-retry{min-width:64px}.media-scan-job .media-task-icon{min-width:40px}} @@ -765,7 +767,7 @@ body:has(>#app>.media-screen) { background: var(--color-bg); } .media-playback-problem>p {font-size:12px;line-height:1.8;margin:0;max-width:480px} .media-playback-problem>div {display:flex;justify-content:center;gap:10px;flex-wrap:wrap;margin-top:4px} .media-playback-problem button {font-size:12px} -.media-video-workspace .media-playback-problem .media-primary {background:var(--media-fill);color:var(--media-on-accent)} +.media-video-workspace .media-playback-problem .media-primary {background:var(--media-primary);color:var(--media-on-primary)} .media-video-controls .media-screen-error {margin:18px 0;font-size:12px} .media-video-controls .media-screen-error-actions {display:flex;gap:8px} .media-video-sheet .sheet-body {font-size:14px;line-height:1.6} diff --git a/web/src/features/media/styles/v2.css b/web/src/features/media/styles/v2.css index 8fa6587..f6fa3c3 100644 --- a/web/src/features/media/styles/v2.css +++ b/web/src/features/media/styles/v2.css @@ -757,7 +757,7 @@ .media-screen .media-browse-tools {height:auto;flex-wrap:wrap} .media-loading-status {color:var(--muted);font-size:13px;min-height:24px} .media-scan-job .media-task-retry {grid-column:1/-1;justify-self:start;min-height:44px} -.media-player .media-playback-problem .media-primary {background:var(--media-fill);color:var(--media-on-accent)} +.media-player .media-playback-problem .media-primary {background:var(--media-primary);color:var(--media-on-primary)} .media-player .dplayer-host .dplayer-bar-wrap .dplayer-played,.media-player .dplayer-host .dplayer-volume-bar-inner {background:var(--media-fill)!important} .media-player .dplayer-host .dplayer-setting-box,.media-player .dplayer-host .dplayer-setting-item {background:var(--media-paper);color:var(--text)} .media-player .dplayer-host .dplayer-setting-item:hover {background:var(--media-selection)} diff --git a/web/src/styles/reader.css b/web/src/styles/reader.css index 9253702..ce55ef8 100644 --- a/web/src/styles/reader.css +++ b/web/src/styles/reader.css @@ -698,10 +698,12 @@ book-content { text-decoration: none; } +/* The one action on a screen: the deep brand colour, so it outweighs the quiet secondary + buttons beside it. The soft --reader-fill stays for states (selected, progress, on). */ .button.primary { - background: var(--reader-fill); - border-color: var(--reader-fill); - color: var(--reader-on-fill); + background: var(--reader-primary); + border-color: var(--reader-primary); + color: var(--reader-on-primary); } .button:disabled { @@ -812,7 +814,7 @@ book-content { } .button.primary:hover { - background: color-mix(in srgb, var(--reader-fill) 88%, var(--reader-ink)); + background: color-mix(in srgb, var(--reader-primary) 88%, var(--reader-ink)); border-color: transparent; } diff --git a/web/src/styles/theme-tokens.css b/web/src/styles/theme-tokens.css index 336d7b1..bccdf8b 100644 --- a/web/src/styles/theme-tokens.css +++ b/web/src/styles/theme-tokens.css @@ -288,6 +288,9 @@ --reader-accent: var(--color-brand-text); --reader-fill: var(--color-brand); --reader-on-fill: var(--color-on-brand); + /* Primary action: brand-text filled, bg on it (spec §5 already holds this pair to 4.5:1). */ + --reader-primary: var(--color-brand-text); + --reader-on-primary: var(--color-bg); --reader-scrim: var(--color-scrim); } :root, [data-theme][data-mode="light"] { color-scheme: light; }