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.
This commit is contained in:
agessaman committed 2026-10-17 17:46:51 -07:00
1 parent b5bf8c26e2
commit ebdc44cd90
8 files changed
+146 -45

No files matched your search

+16 -2
View File
@@ -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.
+84 -3
View File
@@ -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. */
+3 -3
View File
@@ -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;
}
+3 -3
View File
@@ -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;
}
+3 -3
View File
@@ -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;
}
+9 -8
View File
@@ -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; }
</style>
{% endblock %}
+3 -3
View File
@@ -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;
}
+25 -20
View File
@@ -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 @@
<small class="text-muted me-1">Show:</small>
<div class="form-check form-check-inline mb-0">
<input class="form-check-input rt-filter-cb" type="checkbox" id="rt-filter-command" data-target="command-card" checked>
<label class="form-check-label" for="rt-filter-command" style="font-size:0.8rem;color:#6c757d">Commands</label>
<label class="form-check-label text-muted" for="rt-filter-command" style="font-size:0.8rem">Commands</label>
</div>
<div class="form-check form-check-inline mb-0">
<input class="form-check-input rt-filter-cb" type="checkbox" id="rt-filter-packet" data-target="packet-card" checked>
<label class="form-check-label" for="rt-filter-packet" style="font-size:0.8rem;color:#0dcaf0">Packets</label>
<label class="form-check-label text-info" for="rt-filter-packet" style="font-size:0.8rem">Packets</label>
</div>
<div class="form-check form-check-inline mb-0">
<input class="form-check-input rt-filter-cb" type="checkbox" id="rt-filter-message" data-target="message-card" checked>
<label class="form-check-label" for="rt-filter-message" style="font-size:0.8rem;color:#0d6efd">Messages</label>
<label class="form-check-label text-primary" for="rt-filter-message" style="font-size:0.8rem">Messages</label>
</div>
</div>
</div>