From ebdc44cd9030492a751cae8cc8bde76bec9f4ccc Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 17 Oct 2026 17:46:51 -0700 Subject: [PATCH] fix(web_viewer): status and accent text passes contrast in both themes Bootstrap's text-success, text-warning, text-info and text-danger are fill tones, so used as text they failed contrast (amber on white was 1.6:1, info 2.0:1), and nothing adjusted them for dark mode. base.html now defines per-theme status text tokens that pass 4.5:1 on every surface tier, and the text utilities, text-muted, form-text, code, links and the outline buttons resolve through them. Info and warning badges carry dark text. Ink Muted moves a step darker so it holds on paper-sunk. The dark message bubbles on Realtime were solid navy, forest green and purple, which overpowered the page and dropped the sender names to 1.7:1. They are now low-alpha tints of the signal and lamp hues over the card, like the dashboard delta chips, and the error bubble is red in both themes instead of purple in dark. Also: the light log levels and level toggles move to tones that pass on the log well, the dark ERROR toggle uses dark text, Realtime drops its local orange text-warning and its opacity on message times, and the four copies of the dark .table td * rule no longer force body text color onto badges and buttons. DESIGN.md records the tokens and the rule that a fill hue never goes on text directly. --- DESIGN.md | 18 ++++- modules/web_viewer/templates/base.html | 87 +++++++++++++++++++++- modules/web_viewer/templates/contacts.html | 6 +- modules/web_viewer/templates/feeds.html | 6 +- modules/web_viewer/templates/greeter.html | 6 +- modules/web_viewer/templates/logs.html | 17 +++-- modules/web_viewer/templates/radio.html | 6 +- modules/web_viewer/templates/realtime.html | 45 ++++++----- 8 files changed, 146 insertions(+), 45 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index e65e270..c3ad67a 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -8,7 +8,7 @@ colors: paper-raised: "#f8f9fa" paper-sunk: "#e9ecef" ink: "#212529" - ink-muted: "#6c757d" + ink-muted: "#5f666d" rule: "#dee2e6" night: "#1a1a1a" night-raised: "#2d2d2d" @@ -19,6 +19,15 @@ colors: lamp-green: "#198754" lamp-red: "#dc3545" lamp-amber: "#ffc107" + status-text-success: "#146c43" + status-text-warning: "#7a5d02" + status-text-danger: "#b02a37" + status-text-info: "#0a6c80" + night-status-text-success: "#75b798" + night-status-text-warning: "#ffda6a" + night-status-text-danger: "#f1aeb5" + night-status-text-info: "#6edff6" + night-signal-blue-text: "#6ea8fe" route-direct: "#20c997" scope-black: "#0d1117" scope-rule: "#30363d" @@ -162,12 +171,15 @@ A neutral Bootstrap-gray desk with one blue signal and a set of lamp colors that - **Lamp Amber** (`lamp-amber`): warning and degraded states, and the packet-entry rule in the activity log. - **Lamp Red** (`lamp-red`): disconnected, error, negative deltas, destructive actions. +### Status text +The lamp hues are fill tones. As text they fail contrast (amber on white is 1.6:1), so text that carries state uses a separate per-theme set, exposed in `base.html` as `--text-success`, `--text-warning`, `--text-danger`, `--text-info`, `--text-primary`, `--link-color` and `--code-color`. Bootstrap's `.text-*` utilities, `.text-muted`, `.form-text`, and the outline buttons all resolve through these, so a page gets the right tone in both themes by using the utility. Light values pass 4.5:1 on paper, paper-raised and paper-sunk; dark values pass on night, night-raised and night-sunk. Solid `bg-info` and `bg-warning` badges carry dark text. + ### Neutral - **Paper** (`paper`) / **Night** (`night`): page background in light and dark. - **Paper Raised** (`paper-raised`) / **Night Raised** (`night-raised`): card headers, footer, log-entry blocks in light; cards themselves in dark. - **Paper Sunk** (`paper-sunk`) / **Night Sunk** (`night-sunk`): meter tracks, empty chip fills, connection-info panels, striped and hovered table rows in dark. - **Ink** (`ink`) / **Night Ink** (`night-ink`): body text. -- **Ink Muted** (`ink-muted`) / **Night Ink Muted** (`night-ink-muted`): labels, secondary values, empty states, inactive icons. +- **Ink Muted** (`ink-muted`) / **Night Ink Muted** (`night-ink-muted`): labels, secondary values, empty states, inactive icons. Ink Muted is a step darker than Bootstrap's `#6c757d` so it holds 4.5:1 on paper-sunk too. On the console-black rail and table headers, muted text uses the night tone in either theme. - **Rule** (`rule`) / **Night Rule** (`night-rule`): 1px borders, dividers, the hairline after section titles. - **Console Black** (`console-black`): the navbar rail in both themes. - **Scope Black**, **Scope Rule**, **Scope Ink** (`scope-black`, `scope-rule`, `scope-ink`): the log console's own dark surface, border, and default line color, borrowed from a terminal rather than from the page theme. @@ -178,6 +190,8 @@ Charts use their own validated palettes in `static/js/dashboard.js`, not the lam ### Named Rules **The Lamp Rule.** Green, amber, and red mean connected, degraded, and failed. They never decorate a heading, an icon, or a button that isn't about state. +**The Fill-Is-Not-Text Rule.** A lamp or accent hue used as text goes through its status-text token, never the raw fill hex. Tinted surfaces (message bubbles, delta chips) are a low-alpha wash of the hue over the card, so body text and the status-text tokens keep their contrast on them. + **The Twin Token Rule.** A color that is hard-coded for one theme ships with its `[data-theme="dark"]` (or light) counterpart in the same change. Bootstrap 5.1.3 has no dark theme of its own here; nothing inherits one for free. **The Legend Rule.** Several chart hues sit under 3:1 against their card, so a series is always identified by a legend or a label as well as by color. diff --git a/modules/web_viewer/templates/base.html b/modules/web_viewer/templates/base.html index 00bc92b..2a16ffd 100644 --- a/modules/web_viewer/templates/base.html +++ b/modules/web_viewer/templates/base.html @@ -33,7 +33,7 @@ --bg-secondary: #f8f9fa; --bg-tertiary: #e9ecef; --text-color: #212529; - --text-muted: #6c757d; + --text-muted: #5f666d; --border-color: #dee2e6; --card-bg: #ffffff; --card-header-bg: #f8f9fa; @@ -41,6 +41,16 @@ --log-entry-bg: #f8f9fa; --connection-info-bg: #e9ecef; --shadow-color: rgba(0, 0, 0, 0.1); + /* Status and accent colors used as text. Bootstrap's own text-* + colors are background tones and fail contrast as text; these + pass 4.5:1 on paper, paper-raised and paper-sunk. */ + --text-success: #146c43; + --text-warning: #7a5d02; + --text-danger: #b02a37; + --text-info: #0a6c80; + --text-primary: #0d6efd; + --link-color: #0d6efd; + --code-color: #ab296a; } /* CSS Variables for Dark Mode */ @@ -57,6 +67,14 @@ --log-entry-bg: #2d2d2d; --connection-info-bg: #3d3d3d; --shadow-color: rgba(0, 0, 0, 0.3); + /* Pass 4.5:1 on night, night-raised and night-sunk. */ + --text-success: #75b798; + --text-warning: #ffda6a; + --text-danger: #f1aeb5; + --text-info: #6edff6; + --text-primary: #6ea8fe; + --link-color: #6ea8fe; + --code-color: #e685b5; } /* Apply theme variables */ @@ -255,11 +273,74 @@ color: var(--text-muted); } - /* Dark mode text muted */ - [data-theme="dark"] .text-muted { + /* Muted and status text follow the theme in both themes */ + .text-muted, + .form-text { color: var(--text-muted) !important; } + /* On the console-black table headers and navbar, muted text uses the + night tone in either theme. */ + .table-dark .text-muted, + .navbar-dark .text-muted { + color: #adb5bd !important; + } + + .alert code { color: inherit; } + + .text-success { color: var(--text-success) !important; } + .text-warning { color: var(--text-warning) !important; } + .text-danger { color: var(--text-danger) !important; } + .text-info { color: var(--text-info) !important; } + .text-primary { color: var(--text-primary) !important; } + code { color: var(--code-color); } + + /* Solid info and warning fills are light, so they carry dark text + (Bootstrap 5.1 gives every badge white text). */ + .badge.bg-info, + .badge.bg-warning { + color: #212529 !important; + } + + [data-theme="dark"] a:not(.btn):not(.nav-link):not(.navbar-brand):not(.dropdown-item) { + color: var(--link-color); + } + + /* Outline buttons: Bootstrap's outline text colors are its fill tones, + which fail as text on raised surfaces and on the dark theme. */ + .btn-outline-secondary { + color: var(--text-muted); + } + .btn-outline-secondary:hover, + .btn-outline-secondary.active { + color: #fff; + background-color: #6c757d; + } + .btn-outline-danger { + color: var(--text-danger); + } + .btn-outline-danger:hover, + .btn-outline-danger.active { + color: #fff; + } + [data-theme="dark"] .btn-outline-primary { + color: var(--text-primary); + } + [data-theme="dark"] .btn-outline-primary:hover, + [data-theme="dark"] .btn-outline-primary.active { + color: #fff; + } + .btn-outline-warning { + color: var(--text-warning); + } + .btn-outline-warning:hover, + .btn-outline-warning.active { + color: #212529; + } + [data-theme="dark"] .btn-link { + color: var(--link-color); + } + /* Dark mode borders. The directional utilities need naming too — the all-sides rule does not cover them, so a .border-start divider drew Bootstrap's light #dee2e6 straight onto a dark card. */ diff --git a/modules/web_viewer/templates/contacts.html b/modules/web_viewer/templates/contacts.html index f474925..e15ea88 100644 --- a/modules/web_viewer/templates/contacts.html +++ b/modules/web_viewer/templates/contacts.html @@ -2777,9 +2777,9 @@ document.addEventListener('DOMContentLoaded', () => { color: var(--text-color); } -/* Ensure all text in table cells is light in dark mode */ -[data-theme="dark"] .table td, -[data-theme="dark"] .table td * { +/* Table cells take the light text color in dark mode; children with their + own color (badges, buttons, status text) keep it. */ +[data-theme="dark"] .table td { color: var(--text-color) !important; } diff --git a/modules/web_viewer/templates/feeds.html b/modules/web_viewer/templates/feeds.html index 3ead05b..52c22d8 100644 --- a/modules/web_viewer/templates/feeds.html +++ b/modules/web_viewer/templates/feeds.html @@ -1216,9 +1216,9 @@ document.addEventListener('DOMContentLoaded', () => { color: var(--text-color); } -/* Ensure all text in table cells is light in dark mode */ -[data-theme="dark"] .table td, -[data-theme="dark"] .table td * { +/* Table cells take the light text color in dark mode; children with their + own color (badges, buttons, status text) keep it. */ +[data-theme="dark"] .table td { color: var(--text-color) !important; } diff --git a/modules/web_viewer/templates/greeter.html b/modules/web_viewer/templates/greeter.html index b7a73b0..d592440 100644 --- a/modules/web_viewer/templates/greeter.html +++ b/modules/web_viewer/templates/greeter.html @@ -574,9 +574,9 @@ document.addEventListener('DOMContentLoaded', () => { color: var(--text-color); } -/* Ensure all text in table cells is light in dark mode */ -[data-theme="dark"] .table td, -[data-theme="dark"] .table td * { +/* Table cells take the light text color in dark mode; children with their + own color (badges, buttons, status text) keep it. */ +[data-theme="dark"] .table td { color: var(--text-color) !important; } diff --git a/modules/web_viewer/templates/logs.html b/modules/web_viewer/templates/logs.html index b0131cb..de8211d 100644 --- a/modules/web_viewer/templates/logs.html +++ b/modules/web_viewer/templates/logs.html @@ -43,10 +43,11 @@ .log-line.level-error { color: #f85149; } .log-line.level-critical{ color: #ff79c6; font-weight: bold; } - [data-theme="light"] .log-line.level-debug { color: #0d9488; } - [data-theme="light"] .log-line.level-info { color: #0d6efd; } - [data-theme="light"] .log-line.level-warning { color: #fd7e14; } - [data-theme="light"] .log-line.level-error { color: #dc3545; } + /* Light set: each passes 4.5:1 on the light log well (#f8f9fa). */ + [data-theme="light"] .log-line.level-debug { color: #0f766e; } + [data-theme="light"] .log-line.level-info { color: #0a58ca; } + [data-theme="light"] .log-line.level-warning { color: #a85300; } + [data-theme="light"] .log-line.level-error { color: #b02a37; } [data-theme="light"] .log-line.level-critical{ color: #6610f2; font-weight: bold; } .log-controls { @@ -97,14 +98,14 @@ .log-level-toggle[data-level="DEBUG"].is-on { background: #2dd4bf; color: #0d1117; border-color: #2dd4bf; } .log-level-toggle[data-level="INFO"].is-on { background: #58a6ff; color: #0d1117; border-color: #58a6ff; } .log-level-toggle[data-level="WARNING"].is-on { background: #d29922; color: #0d1117; border-color: #d29922; } - .log-level-toggle[data-level="ERROR"].is-on { background: #f85149; color: #fff; border-color: #f85149; } + .log-level-toggle[data-level="ERROR"].is-on { background: #f85149; color: #0d1117; border-color: #f85149; } .log-level-toggle[data-level="CRITICAL"].is-on { background: #ff79c6; color: #0d1117; border-color: #ff79c6; } /* Light theme — on */ - [data-theme="light"] .log-level-toggle[data-level="DEBUG"].is-on { background: #0d9488; color: #fff; border-color: #0d9488; } - [data-theme="light"] .log-level-toggle[data-level="INFO"].is-on { background: #0d6efd; color: #fff; border-color: #0d6efd; } + [data-theme="light"] .log-level-toggle[data-level="DEBUG"].is-on { background: #0f766e; color: #fff; border-color: #0f766e; } + [data-theme="light"] .log-level-toggle[data-level="INFO"].is-on { background: #0a58ca; color: #fff; border-color: #0a58ca; } [data-theme="light"] .log-level-toggle[data-level="WARNING"].is-on { background: #fd7e14; color: #212529; border-color: #fd7e14; } - [data-theme="light"] .log-level-toggle[data-level="ERROR"].is-on { background: #dc3545; color: #fff; border-color: #dc3545; } + [data-theme="light"] .log-level-toggle[data-level="ERROR"].is-on { background: #b02a37; color: #fff; border-color: #b02a37; } [data-theme="light"] .log-level-toggle[data-level="CRITICAL"].is-on { background: #6610f2; color: #fff; border-color: #6610f2; } {% endblock %} diff --git a/modules/web_viewer/templates/radio.html b/modules/web_viewer/templates/radio.html index 1c0d0bc..6b85698 100644 --- a/modules/web_viewer/templates/radio.html +++ b/modules/web_viewer/templates/radio.html @@ -1968,9 +1968,9 @@ document.addEventListener('DOMContentLoaded', () => { color: var(--text-color); } -/* Ensure all text in table cells is light in dark mode */ -[data-theme="dark"] .table td, -[data-theme="dark"] .table td * { +/* Table cells take the light text color in dark mode; children with their + own color (badges, buttons, status text) keep it. */ +[data-theme="dark"] .table td { color: var(--text-color) !important; } diff --git a/modules/web_viewer/templates/realtime.html b/modules/web_viewer/templates/realtime.html index 0e411d0..be51a13 100644 --- a/modules/web_viewer/templates/realtime.html +++ b/modules/web_viewer/templates/realtime.html @@ -102,7 +102,6 @@ .live-msg-time { font-size: 0.58rem; line-height: 1.2; - opacity: 0.88; } .stream-entry:hover { @@ -162,10 +161,6 @@ color: #fff; } - .text-warning { - color: #fd7e14 !important; - } - .conversation-entry { border-left: 3px solid #007bff; padding-left: 10px; @@ -179,11 +174,23 @@ color: var(--text-color); } + /* Dark bubbles are light tints of the lamp and signal hues over the + card, like the dashboard delta chips, so body text and the theme's + status text colors keep their contrast inside them. */ [data-theme="dark"] .user-message { - background-color: #1a237e; - border-color: #283593; - color: #e3f2fd; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + background-color: rgba(13, 110, 253, 0.18); + border-color: rgba(110, 168, 254, 0.35); + color: var(--text-color); + } + + /* Sender names on the light blue bubble need a deeper blue than the + page's link blue. */ + .user-message .text-primary { + color: #0a58ca !important; + } + + [data-theme="dark"] .user-message .text-primary { + color: #8bb9fe !important; } .bot-message-success { @@ -194,10 +201,9 @@ } [data-theme="dark"] .bot-message-success { - background-color: #1b5e20; - border-color: #2e7d32; - color: #c8e6c9; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + background-color: rgba(25, 135, 84, 0.2); + border-color: rgba(117, 183, 152, 0.35); + color: var(--text-color); } .bot-message-danger { @@ -208,10 +214,9 @@ } [data-theme="dark"] .bot-message-danger { - background-color: #4a148c; - border-color: #6a1b9a; - color: #f8bbd0; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + background-color: rgba(220, 53, 69, 0.18); + border-color: rgba(234, 134, 143, 0.35); + color: var(--text-color); } .response-text { @@ -529,15 +534,15 @@ Show:
- +
- +
- +