From c3fda0011bf8ca5bf37d878628a2e86557c8721d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87A=C4=9EAN=20TURGUT?= Date: Mon, 19 Oct 2026 23:43:36 +0300 Subject: [PATCH] Themes: text on clip previews gets its own backing; selected scene row readable (#770) Clip cells that show a scene preview or an image drew their texts over a 40% picture of user content: on a white scene the text was below 3:1, and .cd-meta was not positioned, so the preview was painted over it. The name, info line and countdown now sit on a --card backing, and the audit no longer skips any clip cell. Auditing every category with a populated profile (scenes, preview cells, a lyric file, open folds) found the selected scene's info line (--muted2 on accent-soft) at 3.90:1 in Light; the self-test loads its own settings without saved scenes and never saw it. That row uses --muted, and the rules now cover --text and --muted on --accent-rgb .16 (143 rules). Co-authored-by: caganturgut <271973834+caganturgut@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++-- ROADMAP.md | 6 ++++-- src/admin/admin.css | 16 +++++++++++++++- src/shared/ui-contrast-audit.js | 8 ++++---- src/shared/ui-theme-contrast.js | 4 +++- tests/ui-themes.test.js | 16 ++++++++++++++++ 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 933ebf3..3deca7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ GitHub. The full reasoning and the measurements behind each change are in [ROADM theme changes. The window opens in the theme's background, so a light theme does not flash dark first. - **Readability is tested, not assumed.** - - Every theme is checked against 128 contrast rules: 4.5:1 for text on every surface, button + - Every theme is checked against 143 contrast rules: 4.5:1 for text on every surface, button text in the middle of the gradient, the code editors, focus rings and switch knobs at 3:1. - Every rule in the panel's CSS that sets both a text colour and a background is measured in every theme. @@ -61,7 +61,12 @@ GitHub. The full reasoning and the measurements behind each change are in [ROADM - A test checks every text in a cell, in every state, on white, black, grey and the six primary and secondary colours. The self-test now measures clip cells too and reads `color-mix()` backgrounds. Before, it skipped them and measured the text against the card underneath. - Only cells that show a scene preview are left out, because their text sits on a picture. + - In a cell that shows a scene preview or an image, the name, info line and countdown now sit on + their own card-coloured backing. The preview stays visible around them. On a white scene + the text used to fall below 3:1, and the info line was drawn underneath the preview. +- **The selected scene's info line** was 3.9:1 in Light and 4.1–4.4:1 + in Midnight and CaYaDev. On the selected row's accent tint it now uses the secondary text + colour, and the contrast rules cover body and secondary text on selected rows. - **Two labels were unreadable in every theme.** The empty side of the A/B crossfader and the "built-in" tag in Studio's list were dimmed with opacity to 2.9:1 and 3.7:1. They now use the secondary text colour. The decorative "·" separators in status lines are hidden from screen diff --git a/ROADMAP.md b/ROADMAP.md index a1d56ad..8c78b41 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3607,14 +3607,16 @@ The panel had one look. It now has seven themes and Device default, chosen under - A slot with no source was faded with opacity, which brought its name to 3.3:1. - The tint is now 10% (12% on hover), the countdowns use `--warn-text`, and the sourceless slot is dimmed with colour instead of opacity. - A test measures every text in a cell, in every state, against nine extreme slot colours, and forbids opacity on the cell. - - The self-test also read `color-mix()` backgrounds (`color(srgb …)`) as nothing and measured the text against the card underneath. It now parses them and measures clip cells. Only cells with a scene preview are skipped. + - The self-test also read `color-mix()` backgrounds (`color(srgb …)`) as nothing and measured the text against the card underneath. It now parses them and measures every clip cell. + - A cell with a scene preview or an image puts its texts on their own `--card` backing. On a white scene the text was below 3:1, and `.cd-meta` was not positioned, so the preview was drawn over it. +- **Profile with content.** Running the audit through every category with scenes, preview cells, a lyric file and open folds found the selected scene's info line (`--muted2` on `accent-soft`) at 3.90:1 in Light. The self-test missed it because it loads its own settings with no saved scenes. That row now uses `--muted`, and the rules cover `--text` and `--muted` on `--accent-rgb .16`. After the fix, every category in every theme measured 2,966–3,005 texts, none below the threshold. - **Tests that fail on misuse.** - Every theme defines exactly the token set. - `:root` equals the CaYaDev table. - No colour literal outside `:root`. - Every `var()` is defined, and channel tokens are used only as channels. - Panel scripts carry no colour outside the content list, and the list cannot go stale. - - 128 contrast rules per theme. + - 143 contrast rules per theme. - Every CSS rule with both a text colour and a background is measured in every theme. This caught the "Live" pill in Light and Sepia at 4.17:1. - The head script was tested in a fake DOM. - **Self-test.** Every theme is picked from the panel's own picker, every category is scrolled through, and the colour Chromium draws for each visible text is measured against its composited background. diff --git a/src/admin/admin.css b/src/admin/admin.css index 92049cb..91e0489 100644 --- a/src/admin/admin.css +++ b/src/admin/admin.css @@ -926,6 +926,8 @@ video.logo-lib-thumb { pointer-events: none; } } .scene-name:focus { background: var(--card); border-bottom: 1px solid var(--accent); } .scene-meta { font-size: 9.5px; color: var(--muted2); padding-left: 3px; } +/* Seçili satırın vurgu zemininde --muted2 Aydınlık'ta 3.9:1'e iniyordu (#770) */ +.scene-item.active .scene-meta { color: var(--muted); } .scene-acts { display: flex; gap: 4px; flex-shrink: 0; } .scene-toolbar { display: flex; gap: 6px; margin-top: 10px; flex-shrink: 0; } .scene-toolbar .btn { flex: 1; } @@ -1445,9 +1447,21 @@ input[type=color].tl-swatch.big { width: 34px; height: 24px; } .cd-thumb, .perf-thumb { position: absolute; inset: 0; opacity: .40; pointer-events: none; border-radius: inherit; } -.cd-name, .perf-name { position: relative; z-index: 1; } +.cd-name, .cd-meta, .perf-name { position: relative; z-index: 1; } .cd-count, .perf-count { z-index: 2; } .perf-cell.filled .perf-thumb { opacity: .32; } +/* Önizleme kullanıcının sahne rengi ya da görseli: beyaz bir sahnede yazı + 3:1'in altına iniyordu. Önizlemeli hücrede yazılar kendi kart zeminiyle + durur; önizleme etraflarında görünmeye devam eder (#770). */ +.cd-cell:has(> .cd-thumb) > .cd-name, +.cd-cell:has(> .cd-thumb) > .cd-meta, +.cd-cell:has(> .cd-thumb) > .cd-count:not(:empty), +.perf-cell:has(> .perf-thumb) > .perf-name, +.perf-cell:has(> .perf-thumb) > .perf-count:not(:empty) { + background: var(--card); border-radius: 4px; padding: 0 4px; +} +.cd-cell:has(> .cd-thumb) > .cd-name, +.cd-cell:has(> .cd-thumb) > .cd-meta { align-self: flex-start; max-width: 100%; } /* Çakışma uyarısı — engellemiyoruz ama görünür olmalı. */ .settings-io-note.warn { color: var(--warn-text); border-left: 2px solid var(--warn); padding-left: 8px; } diff --git a/src/shared/ui-contrast-audit.js b/src/shared/ui-contrast-audit.js index a322ba4..9a6724b 100644 --- a/src/shared/ui-contrast-audit.js +++ b/src/shared/ui-contrast-audit.js @@ -99,10 +99,10 @@ const SKIP = [ '.preview-stage', '.studio-preview', '.md-thumb-box', '.logo-lib-thumb', '.img-thumb', '.scene-thumb', '.layer-preview', '.md-cell .md-fav', '.preset', '.up-swatch', - /* Klip hücreleri ölçülür (zemin kullanıcının rengiyle boyalı ama oran - her renkte yetecek kadar düşük); yalnız sahne önizlemesi taşıyanlar - görüntü üstünde sayılır. */ - '.tpl-swatch', '.tl-swatch', '.cd-cell:has(> .cd-thumb)', '.perf-cell:has(> .perf-thumb)', '[aria-hidden="true"]', + /* Klip hücreleri ölçülür: zemin kullanıcının rengiyle boyalı ama oran + her renkte yetecek kadar düşük; önizlemeli hücrede yazılar kendi kart + zeminiyle duruyor. */ + '.tpl-swatch', '.tl-swatch', '[aria-hidden="true"]', '.ui-theme-preview', ].join(','); diff --git a/src/shared/ui-theme-contrast.js b/src/shared/ui-theme-contrast.js index 26d0680..09ab5d4 100644 --- a/src/shared/ui-theme-contrast.js +++ b/src/shared/ui-theme-contrast.js @@ -43,7 +43,9 @@ ['--ok-text', '--ok-rgb', 0.14], ['--warn-text', '--warn-rgb', 0.16], ['--danger-text', '--danger-rgb', 0.14], ['--danger-text', '--danger-rgb', 0.22], ['--ok', '--ok-rgb', 0.14], ['--text-strong', '--accent-rgb', 0.3], - ['--text', '--sel-rgb', 0.22], ['--text', '--fg-rgb', 0.08]].forEach(([fg, rgb, a]) => { + ['--text', '--sel-rgb', 0.22], ['--text', '--fg-rgb', 0.08], + // Seçili satır (accent-soft, arama sonucu): gövde ve ikincil yazı. --muted2 burada yetmez + ['--text', '--accent-rgb', 0.16], ['--muted', '--accent-rgb', 0.16]].forEach(([fg, rgb, a]) => { ['--bg2', '--card', '--card2'].forEach((base) => { out.push({ fg, on: { tint: rgb, alpha: a, base }, min: 'text', why: fg + ' / ' + rgb + ' ' + a + ' / ' + base }); }); diff --git a/tests/ui-themes.test.js b/tests/ui-themes.test.js index eda45f7..0bdf718 100644 --- a/tests/ui-themes.test.js +++ b/tests/ui-themes.test.js @@ -207,6 +207,15 @@ test('klip hücreleri: içindeki yazılar her yuva renginde ve her durumda okunu { bg: ['.perf-cell.armed'], fg: ['.perf-cell.filled', '.perf-count'] }, { bg: ['.perf-cell.active'], fg: ['.perf-cell.active', '.perf-count'] }, { bg: ['.cd-cell.filled.noref'], fg: ['.cd-cell.filled.noref', '.cd-meta', '.cd-count'] }, + // Önizlemeli hücre: yazı sahnenin resminin değil kendi zemininin üstünde + { + bg: ['.cd-cell:has(> .cd-thumb) > .cd-name', '.cd-cell:has(> .cd-thumb) > .cd-meta', '.cd-cell:has(> .cd-thumb) > .cd-count:not(:empty)'], + fg: ['.cd-cell.filled', '.cd-meta', '.cd-count'], + }, + { + bg: ['.perf-cell:has(> .perf-thumb) > .perf-name', '.perf-cell:has(> .perf-thumb) > .perf-count:not(:empty)'], + fg: ['.perf-cell.filled', '.perf-cell.active', '.perf-count'], + }, ]; const fails = []; let checked = 0; @@ -231,6 +240,13 @@ test('klip hücreleri: içindeki yazılar her yuva renginde ve her durumda okunu // Soluk gösterim opaklıkla değil renkle: opaklık yazıyı da soldurur rules.filter((r) => r.sel.split(',').some((x) => /\.(cd|perf)-cell[\w.:-]*$/.test(x.trim())) && r.decls.some((d) => d.prop === 'opacity')) .forEach((r) => fails.push(r.sel + ' { opacity }')); + /* Önizleme konumlu ve yazıların üstüne çizilir: yazı konumlu değilse + önizlemenin altında kalır (.cd-meta öyleydi). */ + ['.cd-name', '.cd-meta', '.perf-name'].forEach((sel) => { + if (prop(sel, 'position') !== 'relative') fails.push(sel + ' önizlemenin altında kalıyor'); + }); + // Tarama önizlemeli hücreleri artık atlamıyor + assert.doesNotMatch(read('src/shared/ui-contrast-audit.js'), /(cd|perf)-(thumb|cell)/); assert.deepStrictEqual(fails, []); });