Auto-compute contrast text color for accent backgrounds

Add --primary-contrast CSS variable that auto-switches between white and
dark text based on accent color luminance (WCAG relative luminance).
Replace all hardcoded #fff/white on primary-color backgrounds with
var(--primary-contrast) so light accent colors like yellow remain readable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 16:22:45 +03:00
parent 46a2ebf61e
commit 2bca119ad4
9 changed files with 23 additions and 11 deletions

View File

@@ -7,6 +7,7 @@
:root { :root {
--primary-color: #4CAF50; --primary-color: #4CAF50;
--primary-hover: #5cb860; --primary-hover: #5cb860;
--primary-contrast: #ffffff;
--danger-color: #f44336; --danger-color: #f44336;
--warning-color: #ff9800; --warning-color: #ff9800;
--info-color: #2196F3; --info-color: #2196F3;

View File

@@ -399,7 +399,7 @@ body.cs-drag-active .card-drag-handle {
padding: 1px 5px; padding: 1px 5px;
border-radius: 3px; border-radius: 3px;
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
font-weight: 600; font-weight: 600;
vertical-align: middle; vertical-align: middle;
margin-right: 2px; margin-right: 2px;

View File

@@ -7,7 +7,7 @@
.badge.processing { .badge.processing {
background: var(--primary-color); background: var(--primary-color);
color: white; color: var(--primary-contrast);
} }
.badge.idle { .badge.idle {
@@ -80,7 +80,7 @@
.btn-primary { .btn-primary {
background: var(--primary-color); background: var(--primary-color);
color: white; color: var(--primary-contrast);
} }
.btn-danger { .btn-danger {

View File

@@ -259,7 +259,7 @@
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 600; font-weight: 600;
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
flex-shrink: 0; flex-shrink: 0;
} }

View File

@@ -161,7 +161,7 @@ h2 {
.tab-btn.active .tab-badge { .tab-btn.active .tab-badge {
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
} }
.tab-panel { .tab-panel {
@@ -410,11 +410,12 @@ h2 {
.cp-result.cp-active { .cp-result.cp-active {
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
} }
.cp-result.cp-active .cp-detail { .cp-result.cp-active .cp-detail {
color: rgba(255, 255, 255, 0.7); color: var(--primary-contrast);
opacity: 0.7;
} }
.cp-icon { .cp-icon {

View File

@@ -832,7 +832,7 @@
.gradient-stop-bidir-btn.active { .gradient-stop-bidir-btn.active {
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
border-color: var(--primary-color); border-color: var(--primary-color);
opacity: 1; opacity: 1;
} }

View File

@@ -73,11 +73,11 @@
.stream-card-link:hover { .stream-card-link:hover {
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
} }
.stream-card-link:hover .icon { .stream-card-link:hover .icon {
color: #fff; color: var(--primary-contrast);
} }
@keyframes cardHighlight { @keyframes cardHighlight {

View File

@@ -578,7 +578,7 @@
.stream-tab-btn.active .stream-tab-count { .stream-tab-btn.active .stream-tab-count {
background: var(--primary-color); background: var(--primary-color);
color: #fff; color: var(--primary-contrast);
} }
.cs-expand-collapse-group { .cs-expand-collapse-group {

View File

@@ -223,12 +223,22 @@
return '#'+[rr,gg,bb].map(x=>Math.round(x*255).toString(16).padStart(2,'0')).join(''); return '#'+[rr,gg,bb].map(x=>Math.round(x*255).toString(16).padStart(2,'0')).join('');
} }
function contrastColor(hex) {
const r = parseInt(hex.slice(1,3),16)/255;
const g = parseInt(hex.slice(3,5),16)/255;
const b = parseInt(hex.slice(5,7),16)/255;
const lin = c => c <= 0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4);
const L = 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
return L > 0.36 ? '#1a1a1a' : '#ffffff';
}
function applyAccentColor(hex, silent) { function applyAccentColor(hex, silent) {
const root = document.documentElement; const root = document.documentElement;
root.style.setProperty('--primary-color', hex); root.style.setProperty('--primary-color', hex);
const theme = root.getAttribute('data-theme'); const theme = root.getAttribute('data-theme');
root.style.setProperty('--primary-text-color', adjustLightness(hex, theme === 'dark' ? 15 : -15)); root.style.setProperty('--primary-text-color', adjustLightness(hex, theme === 'dark' ? 15 : -15));
root.style.setProperty('--primary-hover', adjustLightness(hex, 8)); root.style.setProperty('--primary-hover', adjustLightness(hex, 8));
root.style.setProperty('--primary-contrast', contrastColor(hex));
document.getElementById('accent-swatch').style.background = hex; document.getElementById('accent-swatch').style.background = hex;
document.getElementById('accent-picker').value = hex; document.getElementById('accent-picker').value = hex;
// Mark the active preset dot // Mark the active preset dot