# Dark Mode Fix — Powder Coating Logix

Supplemental brief for the in-progress redesign. **Read `README.md` and `CLAUDE.md` in this folder first.** This document only covers the dark-theme gaps.

## Ground rules

1. **Two surfaces, one accent system.** `data-surface="paper"` (default, light) and `data-surface="ink"` (dark). Accent hues stay the same across both; only *lightness* and *chroma* of tints change.
2. **Bind Bootstrap's theme alongside ours.** Whenever `data-surface` changes on `<html>`, `data-bs-theme` must follow (`paper` → `light`, `ink` → `dark`). Without this, dropdowns, modals, toasts, form inputs, and tooltips will stay light-on-light or light-on-dark.
3. **User preference, persisted.** Default to `prefers-color-scheme`. Allow override via a toggle. Persist in `localStorage` under key `pcl.surface`.
4. **Every screen must be verified on both surfaces** before marking done.

## Replace the token block

Overwrite the `:root` + `[data-surface="ink"]` block in `wwwroot/css/tokens.css` with this. Key changes: chip tints get dark-surface variants, rule contrast bumps, swatch hairline flips, focus offset uses card color.

```css
:root {
  /* Neutrals — warm paper (light surface) */
  --pcl-ink:       #0F0F10;
  --pcl-graphite:  #1A1A1C;
  --pcl-slate:     #3A3A3E;
  --pcl-steel:     #6B6B70;
  --pcl-mute:      #9A9A9F;
  --pcl-rule:      #E4E2DC;
  --pcl-rule-soft: #EFEDE7;
  --pcl-paper:     #FAFAF7;
  --pcl-paper-2:   #F3F1EB;
  --pcl-card:      #FFFFFF;

  /* Signal — hue preserved across surfaces */
  --pcl-ember:      oklch(0.68 0.17 50);
  --pcl-ok:         oklch(0.62 0.11 155);
  --pcl-warn:       oklch(0.76 0.13 80);
  --pcl-bad:        oklch(0.60 0.19 25);
  --pcl-cool:       oklch(0.58 0.09 240);

  /* Chip tints — LIGHT surface (high-lightness wash) */
  --pcl-ember-tint: oklch(0.95 0.04 55);
  --pcl-ember-ink:  oklch(0.42 0.15 40);
  --pcl-ok-tint:    oklch(0.95 0.04 155);
  --pcl-ok-ink:     oklch(0.35 0.09 155);
  --pcl-warn-tint:  oklch(0.96 0.05 85);
  --pcl-warn-ink:   oklch(0.42 0.10 80);
  --pcl-bad-tint:   oklch(0.95 0.04 25);
  --pcl-bad-ink:    oklch(0.40 0.15 25);
  --pcl-cool-tint:  oklch(0.95 0.03 240);
  --pcl-cool-ink:   oklch(0.38 0.08 240);

  /* Hairlines + misc — light */
  --pcl-swatch-edge: rgba(0, 0, 0, 0.14);
  --pcl-focus-offset: var(--pcl-paper);

  /* Bootstrap binding */
  --bs-body-bg:      var(--pcl-paper);
  --bs-body-color:   var(--pcl-ink);
  --bs-border-color: var(--pcl-rule);
  --bs-primary:      var(--pcl-ink);
}

/* DARK surface */
[data-surface="ink"] {
  --pcl-paper:    #0E0E10;
  --pcl-paper-2:  #17171A;
  --pcl-card:     #1C1C20;
  --pcl-ink:      #F4F2EC;
  --pcl-graphite: #E8E6DF;
  --pcl-slate:    #C2C0BA;
  --pcl-steel:    #8A8883;
  --pcl-mute:     #6A6864;

  /* Rules — bumped from #2B2B2F → better contrast on --pcl-card */
  --pcl-rule:      #34343A;
  --pcl-rule-soft: #26262B;

  /* Chip tints — DARK surface (low-lightness wash) */
  --pcl-ember-tint: oklch(0.26 0.06 55);
  --pcl-ember-ink:  oklch(0.82 0.15 55);
  --pcl-ok-tint:    oklch(0.24 0.05 155);
  --pcl-ok-ink:     oklch(0.80 0.12 155);
  --pcl-warn-tint:  oklch(0.26 0.05 85);
  --pcl-warn-ink:   oklch(0.85 0.13 85);
  --pcl-bad-tint:   oklch(0.26 0.07 25);
  --pcl-bad-ink:    oklch(0.80 0.16 25);
  --pcl-cool-tint:  oklch(0.26 0.04 240);
  --pcl-cool-ink:   oklch(0.80 0.10 240);

  /* Hairlines flip to light */
  --pcl-swatch-edge:  rgba(255, 255, 255, 0.18);
  --pcl-focus-offset: var(--pcl-card);

  /* Bootstrap binding */
  --bs-body-bg:      var(--pcl-paper);
  --bs-body-color:   var(--pcl-ink);
  --bs-border-color: var(--pcl-rule);
  --bs-primary:      var(--pcl-ink);
}
```

## Update `_StatusChip` to use the paired tokens

Old: chip foreground was `--pcl-{kind}` (the signal color itself). That's fine on light, unreadable on dark. New: chip foreground is `--pcl-{kind}-ink`, which is lightness-adjusted per surface.

```css
.pcl-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 8px; border-radius: 99px;
  font: 500 10.5px/1.2 "IBM Plex Mono", ui-monospace, monospace;
  font-feature-settings: "zero";
  letter-spacing: 0.02em;
}
.pcl-chip-dot { width: 6px; height: 6px; border-radius: 99px; }

.pcl-chip-ember  { background: var(--pcl-ember-tint); color: var(--pcl-ember-ink); }
.pcl-chip-ember  .pcl-chip-dot { background: var(--pcl-ember); }
.pcl-chip-ok     { background: var(--pcl-ok-tint);    color: var(--pcl-ok-ink); }
.pcl-chip-ok     .pcl-chip-dot { background: var(--pcl-ok); }
.pcl-chip-warn   { background: var(--pcl-warn-tint);  color: var(--pcl-warn-ink); }
.pcl-chip-warn   .pcl-chip-dot { background: var(--pcl-warn); }
.pcl-chip-bad    { background: var(--pcl-bad-tint);   color: var(--pcl-bad-ink); }
.pcl-chip-bad    .pcl-chip-dot { background: var(--pcl-bad); }
.pcl-chip-cool   { background: var(--pcl-cool-tint);  color: var(--pcl-cool-ink); }
.pcl-chip-cool   .pcl-chip-dot { background: var(--pcl-cool); }
.pcl-chip-neutral{ background: var(--pcl-paper-2);    color: var(--pcl-slate); }
.pcl-chip-neutral .pcl-chip-dot { background: var(--pcl-steel); }
```

## Swatch hairline

```css
.pcl-swatch {
  display: inline-block; width: 9px; height: 9px;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--pcl-swatch-edge);
}
```

No more hardcoded `rgba(0,0,0,0.12)` — it disappears on dark.

## Focus ring

```css
:focus-visible {
  outline: 2px solid var(--pcl-ember);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--pcl-focus-offset); /* "hole" matching surface */
}
```

## Oven meter pulse

Raise the minimum opacity so the dot remains visible on dark:

```css
@keyframes pcl-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.5; }  /* was 0.3 */
}
```

## Bind Bootstrap theme

### `_Layout.cshtml`

Inside `<head>`, **before** the CSS includes, inline this script so first paint is correct:

```html
<script>
  (function () {
    var saved = localStorage.getItem('pcl.surface');
    var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    var surface = saved || (prefersDark ? 'ink' : 'paper');
    document.documentElement.setAttribute('data-surface', surface);
    document.documentElement.setAttribute('data-bs-theme', surface === 'ink' ? 'dark' : 'light');
  })();
</script>
```

This avoids the light-flash-then-dark-flip on every page load.

### `wwwroot/js/theme-toggle.js` (new)

```js
(function () {
  'use strict';

  function apply(surface) {
    document.documentElement.setAttribute('data-surface', surface);
    document.documentElement.setAttribute('data-bs-theme', surface === 'ink' ? 'dark' : 'light');
    localStorage.setItem('pcl.surface', surface);
    document.querySelectorAll('[data-theme-toggle]').forEach(function (btn) {
      btn.setAttribute('aria-pressed', surface === 'ink');
      var icon = btn.querySelector('i');
      if (icon) icon.className = surface === 'ink' ? 'bi bi-sun' : 'bi bi-moon';
    });
  }

  document.addEventListener('click', function (e) {
    var btn = e.target.closest('[data-theme-toggle]');
    if (!btn) return;
    e.preventDefault();
    var current = document.documentElement.getAttribute('data-surface') || 'paper';
    apply(current === 'ink' ? 'paper' : 'ink');
  });

  // Follow system changes only if user hasn't explicitly chosen.
  if (window.matchMedia) {
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
      if (!localStorage.getItem('pcl.surface')) apply(e.matches ? 'ink' : 'paper');
    });
  }

  // Sync toggle UI on load
  apply(document.documentElement.getAttribute('data-surface') || 'paper');
})();
```

Include it at the bottom of `_Layout.cshtml`:

```html
<script src="~/js/theme-toggle.js" asp-append-version="true"></script>
```

### Toggle affordance

Add next to the user menu in the top bar:

```html
<button type="button" class="btn btn-sm btn-link pcl-theme-toggle"
        data-theme-toggle aria-pressed="false" aria-label="Toggle theme">
  <i class="bi bi-moon"></i>
</button>
```

Minimal CSS:

```css
.pcl-theme-toggle {
  color: var(--pcl-slate);
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 6px;
}
.pcl-theme-toggle:hover { color: var(--pcl-ink); background: var(--pcl-paper-2); }
```

## Sidebar on dark surface

The ink sidebar was already using dark tokens, so most of it works. Two fixes:

1. Active-item left-bar stays ember (`--pcl-ember`), no change.
2. Group headers: change `rgba(255,255,255,0.35)` to `var(--pcl-steel)` so they track the token and stay consistent in both surfaces.

## Bootstrap components — verify, don't override blindly

Once `data-bs-theme` is bound, these should now render correctly. Spot-check each:

- [ ] Dropdown menus (`.dropdown-menu`) — background, item hover, divider.
- [ ] Modals — header/body/footer backgrounds, close button X visibility.
- [ ] Form controls — `input`, `select`, `textarea` background, border, placeholder color, focus ring.
- [ ] Toasts (Toastr) — Toastr isn't Bootstrap-themed; override its CSS manually:
  ```css
  [data-surface="ink"] .toast-success { background: var(--pcl-ok-tint);   color: var(--pcl-ok-ink); }
  [data-surface="ink"] .toast-error   { background: var(--pcl-bad-tint);  color: var(--pcl-bad-ink); }
  [data-surface="ink"] .toast-warning { background: var(--pcl-warn-tint); color: var(--pcl-warn-ink); }
  [data-surface="ink"] .toast-info    { background: var(--pcl-cool-tint); color: var(--pcl-cool-ink); }
  ```
- [ ] Tooltips — Bootstrap 5.3+ inherits `data-bs-theme` fine.
- [ ] Tables — `.table` uses Bootstrap's dark variables when `data-bs-theme="dark"`. If your rows look striped in a way you don't want, set `.table { --bs-table-bg: transparent; }`.
- [ ] DataTables (if used on list pages) — needs `<table class="table" data-bs-theme="…">` inherited from `<html>`; spot-check pagination + search input colors.

## Per-screen dark-mode checklist

Paste this into your review for each of Dashboard, Jobs list, Jobs board, Customers, Invoices, Equipment, Maintenance, Vendors, PO:

- [ ] Toggle to ink. Everything readable, no white flashes.
- [ ] All status chips legible (check `ok`, `warn`, `bad`, `cool`, `ember`, `neutral`).
- [ ] Powder swatches have a visible hairline.
- [ ] Mono numerics are `--pcl-ink`, not faded.
- [ ] Card rule lines visible against card background.
- [ ] Focus ring visible on `Tab` through all interactive elements.
- [ ] Dropdowns, modals, forms on this screen flip correctly.
- [ ] Toastr notifications triggered on this screen are readable.
- [ ] Oven meter dot pulse visible (Jobs board).
- [ ] Red left-edge on hot jobs readable (Jobs list + Board).

## Order of work

1. Swap the token block. **Commit.**
2. Add the first-paint script + `theme-toggle.js` + toggle button. **Commit.**
3. Update `_StatusChip` and `_PowderSwatch` CSS to use paired tokens. **Commit.**
4. Toastr overrides. **Commit.**
5. Walk every screen in the checklist order above.

~1 dev day if the light-mode implementation is already solid.
