From c0aaba17ea3075c27df75dd48ada0ab5fb5d9579 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 18 Jun 2026 19:18:49 -0400 Subject: [PATCH] docs(m10): T34 theme dark-mode feasibility spike findings --- .../2026-06-18-m10-t34-spike-findings.md | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 docs/plans/2026-06-18-m10-t34-spike-findings.md diff --git a/docs/plans/2026-06-18-m10-t34-spike-findings.md b/docs/plans/2026-06-18-m10-t34-spike-findings.md new file mode 100644 index 00000000..c12d9bf0 --- /dev/null +++ b/docs/plans/2026-06-18-m10-t34-spike-findings.md @@ -0,0 +1,239 @@ +# T34 Spike: Theme Dark-Mode Feasibility Findings + +**Date:** 2026-06-18 +**Task:** Verify whether the external `ZB.MOM.WW.Theme` shell can go dark via `data-bs-theme` CSS override. + +--- + +## 1. CSS Load Order + +Confirmed from `src/ZB.MOM.WW.ScadaBridge.Host/Components/App.razor`: + +``` +bootstrap.min.css (line 8) +bootstrap-icons.css (line 9) + (line 10) → injects theme.css then layout.css +Host.styles.css (line 11) +site.css (line 12) ← our override point +``` + +`site.css` loads **after** theme.css and layout.css. A `[data-bs-theme="dark"]` block in site.css has sufficient specificity to shadow every `:root` token defined in theme.css, because `[attr]` selector specificity (0,1,0) beats `:root` (0,0,1) at equal cascade layer. + +--- + +## 2. Token Inventory (theme.css `:root`) + +All tokens defined in the `:root` block of `~/.nuget/packages/zb.mom.ww.theme/0.3.1/staticwebassets/css/theme.css`: + +### Surfaces & ink +| Token | Light value | Role | +|---|---|---| +| `--paper` | `#f4f4f1` | page background | +| `--card` | `#ffffff` | raised surfaces, bars, table heads | +| `--ink` | `#1b1d21` | primary text | +| `--ink-soft` | `#5a6066` | secondary text, labels | +| `--ink-faint` | `#8b9097` | tertiary text, captions | +| `--rule` | `#e4e4df` | hairline borders, row dividers | +| `--rule-strong` | `#d2d2cb` | emphasised hairlines, bar underline, pills | + +### Accent +| Token | Light value | Role | +|---|---|---| +| `--accent` | `#2f5fd0` | links, sort arrows, primary actions | +| `--accent-deep` | `#1e3f99` | hover/pressed accent | + +### Status — foreground +| Token | Light value | +|---|---| +| `--ok` | `#2f9e44` | +| `--warn` | `#e8920c` | +| `--bad` | `#e03131` | +| `--idle` | `#868e96` | + +### Status — tinted backgrounds +| Token | Light value | +|---|---| +| `--ok-bg` | `#e9f6ec` | +| `--warn-bg` | `#fdf1dd` | +| `--bad-bg` | `#fceaea` | +| `--idle-bg` | `#eef0f2` | + +### Status — borders +| Token | Light value | +|---|---| +| `--ok-border` | `#c6e6cd` | +| `--warn-border` | `#efd6a6` | +| `--bad-border` | `#eec3c3` | +| `--ok-border-soft` | `#bfe3c6` | +| `--warn-border-soft` | `#f0d9ab` | +| `--bad-border-soft` | `#f0c0c0` | + +### Warning ink +| Token | Light value | +|---|---| +| `--warn-ink` | `#b56a00` | +| `--warn-ink-deep` | `#8a5a00` | + +### Info tint +| Token | Light value | +|---|---| +| `--info-bg` | `#e7ecfb` | +| `--info-border` | `#cdd9f7` | + +### Neutral surface washes +| Token | Light value | Role | +|---|---|---| +| `--zebra-bg` | `#fbfbf9` | even-row / sticky-head fill | +| `--hover-bg` | `#f3f6fd` | row / rail-link hover | +| `--active-bg` | `#eef2fc` | active rail-link fill | + +### Bootstrap overrides (derive from above) +| Token | Value | +|---|---| +| `--bs-body-bg` | `var(--paper)` | +| `--bs-body-color` | `var(--ink)` | +| `--bs-body-font-family` | `var(--sans)` | +| `--bs-body-font-size` | `0.9rem` | +| `--bs-primary` | `var(--accent)` | +| `--bs-border-color` | `var(--rule)` | +| `--bs-emphasis-color` | `var(--ink)` | + +--- + +## 3. Rail Tokenization Audit (layout.css) + +Every colour-bearing rule in the side-rail uses CSS custom properties. No hard-coded hex or rgb values appear anywhere in layout.css. Key rules: + +**Rail background and border:** +```css +.side-rail { + background: var(--card); + border-right: 1px solid var(--rule-strong); +} +``` + +**Brand block:** +```css +.side-rail .brand { + color: var(--ink); + border-bottom: 1px solid var(--rule); +} +.side-rail .brand .mark { color: var(--accent); } +``` + +**Section eyebrow / toggle:** +```css +.rail-eyebrow-toggle { color: var(--ink-faint); } +.rail-eyebrow-toggle:hover { color: var(--ink); } +.rail-section > summary::before { color: var(--ink-faint); } +``` + +**Nav links (default / hover / active):** +```css +.rail-link { color: var(--ink-soft); } +.rail-link:hover { background: var(--hover-bg); color: var(--ink); } +.rail-link.active { background: var(--active-bg); border-left-color: var(--accent); color: var(--accent-deep); } +``` + +**Rail foot / session block:** +```css +.rail-foot { border-top: 1px solid var(--rule); } +.rail-btn { color: var(--ink-soft); background: var(--card); border: 1px solid var(--rule-strong); } +.rail-btn:hover { border-color: var(--accent); color: var(--accent); } +.rail-roles { color: var(--ink-faint); } +.rail-ico { color: var(--ink-faint); } +``` + +**Login title:** +```css +.login-title { color: var(--ink); } +``` + +--- + +## 4. VERDICT + +**RAIL TOKENIZES → dark feasible by token override.** + +Every rail background, text, and border colour in layout.css resolves through a CSS custom property from the token set. Overriding the token values under `[data-bs-theme="dark"]` in site.css (which loads after theme.css/layout.css) will fully recolour the side-rail, app chrome, panels, data tables, chips, and status elements — no Theme-package coordination required. + +--- + +## 5. Concrete Dark Token Override List + +Paste this entire block under `[data-bs-theme="dark"]` in site.css. Accent colour (`--accent`) is kept at `#4d7fe8` — a lighter shade of the brand blue that reads well on dark backgrounds while preserving brand identity. + +```css +[data-bs-theme="dark"] { + /* Surfaces & ink */ + --paper: #1b1d21; + --card: #25282d; + --ink: #e6e8ec; + --ink-soft: #9aa0aa; + --ink-faint: #6b727c; + --rule: #33373e; + --rule-strong: #3a3f46; + + /* Accent — lightened for contrast on dark */ + --accent: #4d7fe8; + --accent-deep: #7aa0ef; + + /* Status — foregrounds unchanged (inherently visible on dark) */ + --ok: #40b856; + --warn: #f0a030; + --bad: #f05050; + --idle: #7a8290; + + /* Status — tinted backgrounds (dark surface tints) */ + --ok-bg: #1a2e1e; + --warn-bg: #2e2210; + --bad-bg: #2e1616; + --idle-bg: #25282d; + + /* Status — borders on dark tints */ + --ok-border: #2a5030; + --warn-border: #4a3510; + --bad-border: #4a2020; + --ok-border-soft: #264a2c; + --warn-border-soft: #44300e; + --bad-border-soft: #44201e; + + /* Warning ink — brightened amber for dark contrast */ + --warn-ink: #d4891a; + --warn-ink-deep: #b8741a; + + /* Info tint */ + --info-bg: #1a2240; + --info-border: #2a3660; + + /* Neutral surface washes */ + --zebra-bg: #202328; + --hover-bg: #2a2e38; + --active-bg: #1e2640; + + /* Bootstrap bridge tokens */ + --bs-body-bg: var(--paper); + --bs-body-color: var(--ink); + --bs-border-color: var(--rule); + --bs-emphasis-color: var(--ink); +} +``` + +--- + +## 6. Caveats + +1. **Bootstrap intrinsic dark utilities**: Bootstrap 5.3 has its own `[data-bs-theme="dark"]` rules for components (alerts, badges, modals, etc.). Our token block sits in site.css at cascade level 0; Bootstrap's own dark-mode block in bootstrap.min.css (loaded earlier) will also fire. The Bootstrap rules and our token rules are additive and complementary — they should not conflict, but any Bootstrap component that ignores our tokens and uses Bootstrap's built-in dark-mode palette may have a slightly different feel. Inspect on first run. + +2. **`body` gradient in theme.css**: The `body` rule in theme.css is: + ```css + body { + background: radial-gradient(1200px 480px at 88% -8%, var(--card) 0%, rgba(255,255,255,0) 70%), + var(--paper); + } + ``` + On dark mode, `rgba(255,255,255,0)` in the gradient is transparent white and will remain unchanged. The gradient will still render; `var(--card)` will resolve to the dark card value and the effect will be a subtle lighter sheen in the top-right corner — acceptable, but the gradient can be suppressed in site.css for a flatter dark background if preferred. + +3. **Status tints on dark**: The status background tokens (`--ok-bg`, `--warn-bg`, `--bad-bg`) are deliberately dark-surface equivalents (very low-saturation tints). Verify contrast ratios of status chip text against these backgrounds before shipping. + +4. **`data-bs-theme` placement**: The attribute must be set on `` (or ``) so the CSS selector matches all descendants. The toggle implementation (next task) should write `document.documentElement.setAttribute('data-bs-theme', 'dark')`.