# 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)
<ThemeHead />              (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 `<html>` (or `<body>`) so the CSS selector matches all descendants. The toggle implementation (next task) should write `document.documentElement.setAttribute('data-bs-theme', 'dark')`.
