Theme switch leaves navbar / toolbar / properties / zoom / minimap stuck in light mode #115

Open
opened 2026-04-30 08:33:24 +00:00 by AhmedHanafy725 · 6 comments
Member

Summary

Switching the board theme only re-paints the canvas. The whiteboard chrome — navbar / left toolbar / right properties panel / zoom widget / minimap — stays in light mode regardless of which theme is selected. So picking Dark / Ocean / Warm leaves a Dark canvas surrounded by a white navbar and white side panels.

Steps to reproduce

  1. Open a board on /board/<id>.
  2. Open the theme picker in the navbar and switch to Dark / Ocean / Warm.
  3. Observe the navbar, left toolbar, right properties panel, zoom widget, minimap.

Expected

The chrome follows the theme: dark themes give a dark navbar/sidebars; light themes give a white one.

Actual

The canvas updates, but every piece of chrome stays white.

Root cause (already pinpointed)

crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js::applyTheme only sets the canvas container's style.background and redraws the grid + calendars. It never touches the --wb-* CSS custom properties or data-bs-theme.

crates/hero_whiteboard_ui/static/web/css/whiteboard.css defines two parallel rule sets:

:root, :root[data-bs-theme="dark"] { --wb-toolbar-bg: #2b3035; ... }
:root:not([data-bs-theme="dark"])  { --wb-toolbar-bg: #ffffff; ... }

.wb-navbar, .wb-toolbar, .wb-properties, .wb-zoom, .wb-minimap all reference var(--wb-toolbar-bg) etc., so without a data-bs-theme="dark" attribute on <html> they always resolve to the light values.

Fix direction

When applyTheme runs, derive whether the chrome should be dark or light from the theme's canvas-bg luminance, and toggle document.documentElement.dataset.bsTheme accordingly. That single attribute flip cascades to every chrome rule already defined in whiteboard.css. No duplicated theme tokens, no new rules, and custom themes work automatically based on their canvas color.

## Summary Switching the board theme only re-paints the canvas. The whiteboard chrome — navbar / left toolbar / right properties panel / zoom widget / minimap — stays in light mode regardless of which theme is selected. So picking Dark / Ocean / Warm leaves a Dark canvas surrounded by a white navbar and white side panels. ## Steps to reproduce 1. Open a board on `/board/<id>`. 2. Open the theme picker in the navbar and switch to Dark / Ocean / Warm. 3. Observe the navbar, left toolbar, right properties panel, zoom widget, minimap. ## Expected The chrome follows the theme: dark themes give a dark navbar/sidebars; light themes give a white one. ## Actual The canvas updates, but every piece of chrome stays white. ## Root cause (already pinpointed) `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js::applyTheme` only sets the canvas container's `style.background` and redraws the grid + calendars. It never touches the `--wb-*` CSS custom properties or `data-bs-theme`. `crates/hero_whiteboard_ui/static/web/css/whiteboard.css` defines two parallel rule sets: ```css :root, :root[data-bs-theme="dark"] { --wb-toolbar-bg: #2b3035; ... } :root:not([data-bs-theme="dark"]) { --wb-toolbar-bg: #ffffff; ... } ``` `.wb-navbar`, `.wb-toolbar`, `.wb-properties`, `.wb-zoom`, `.wb-minimap` all reference `var(--wb-toolbar-bg)` etc., so without a `data-bs-theme="dark"` attribute on `<html>` they always resolve to the light values. ## Fix direction When `applyTheme` runs, derive whether the chrome should be dark or light from the theme's `canvas-bg` luminance, and toggle `document.documentElement.dataset.bsTheme` accordingly. That single attribute flip cascades to every chrome rule already defined in `whiteboard.css`. No duplicated theme tokens, no new rules, and custom themes work automatically based on their canvas color.
Author
Member

Implementation Spec for Issue #115

Objective

Make the whiteboard chrome (navbar, left toolbar, right properties panel, zoom widget, minimap) follow the active theme. Today only the canvas changes; every piece of chrome stays in light mode regardless of theme selection.

Root cause (already located)

themes.js::applyTheme sets stage.container().style.background = currentTheme['canvas-bg'] and redraws the grid + calendars, but never touches the --wb-* CSS variables or the data-bs-theme attribute that drives those variables.

whiteboard.css already defines parallel light/dark rule sets keyed on :root[data-bs-theme="dark"]. So a single attribute toggle on <html> is enough to swing every piece of chrome that uses var(--wb-toolbar-bg), var(--wb-text), var(--wb-border), var(--wb-hover), var(--wb-bg), etc.

Approach

Derive a "chrome is dark?" boolean from the theme's canvas-bg luminance and set document.documentElement.dataset.bsTheme = 'dark' (or remove it for the light fallback) inside applyTheme. This:

  • Reuses every existing CSS rule. No duplicated theme tokens.
  • Works for the four built-ins (Dark / Ocean / Warm have dark canvases → dark chrome; Light / Minimal have light canvases → light chrome).
  • Auto-classifies any future custom theme based on its canvas color.
  • Has no impact on the canvas painting, since applyTheme keeps setting container.style.background directly from canvas-bg.

Requirements

  • Switching to Dark / Ocean / Warm produces dark chrome on the same page.
  • Switching to Light / Minimal produces light chrome.
  • Initial load on a board with a saved theme also shows the correct chrome (the existing loadBoard → loadTheme → applyTheme flow runs at startup, so we get this for free).
  • No JS error if canvas-bg is unparsable (defensive fallback to "light").

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js — add a small luminance helper, toggle data-bs-theme inside applyTheme.

Implementation Plan

Step 1: Toggle data-bs-theme from canvas-bg luminance in applyTheme

File: crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js

Add a private helper inside the IIFE:

function _isDarkColor(c) {
    if (!c || typeof c !== 'string') return true; // default to dark — sensible fallback
    var s = c.trim();
    var r, g, b;
    if (s[0] === '#') {
        var hex = s.slice(1);
        if (hex.length === 3) hex = hex.split('').map(function(x){return x + x;}).join('');
        if (hex.length !== 6) return true;
        r = parseInt(hex.slice(0, 2), 16);
        g = parseInt(hex.slice(2, 4), 16);
        b = parseInt(hex.slice(4, 6), 16);
    } else {
        var m = s.match(/(\d+(?:\.\d+)?)/g);
        if (!m || m.length < 3) return true;
        r = parseFloat(m[0]); g = parseFloat(m[1]); b = parseFloat(m[2]);
    }
    if (!isFinite(r) || !isFinite(g) || !isFinite(b)) return true;
    var luma = 0.299 * r + 0.587 * g + 0.114 * b;
    return luma < 140; // a hair above the midpoint to keep "Minimal" / off-white in light mode
}

Then near the end of applyTheme(name, opts), after the canvas/grid update:

if (_isDarkColor(currentTheme['canvas-bg'])) {
    document.documentElement.setAttribute('data-bs-theme', 'dark');
} else {
    document.documentElement.removeAttribute('data-bs-theme');
}

Dependencies: none.

Acceptance Criteria

  • Switch to Dark / Ocean / Warm → navbar, left toolbar, right properties panel, zoom widget, minimap all turn dark.
  • Switch to Light / Minimal → all of the same chrome reverts to white.
  • Reload a board that has a saved dark theme → chrome shows dark on first paint, no flash of white.
  • Viewer share + presentation share also follow the theme (the previous fix in #114 already feeds the same applyTheme path).
  • No JS errors for malformed/empty canvas-bg.
  • cargo fmt, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace --lib clean.

Notes

  • Why luminance, not theme name: the four built-ins happen to map cleanly, but custom themes (the codebase already supports them via customThemes) need an automatic rule. Luminance is one tiny helper and gives a deterministic answer.
  • Why threshold 140 instead of 128: keeps "Minimal" (#ffffff) and any near-white custom themes firmly in the light bucket. Slightly off-grey backgrounds like #cccccc still map to light, while #1c1410 (Warm) and #0a192f (Ocean) are clearly dark. Edge cases at the boundary are acceptable since the user picks a theme intentionally and can always pick a different one.
  • Initial load: app.js::loadBoard already calls loadTheme(...) which calls applyTheme(...), so the chrome flips on boot without any extra wiring. No flash because the call happens before the user sees the canvas.
  • No CSS changeswhiteboard.css already has both rule sets. We just flip the attribute that selects between them.
## Implementation Spec for Issue #115 ### Objective Make the whiteboard chrome (navbar, left toolbar, right properties panel, zoom widget, minimap) follow the active theme. Today only the canvas changes; every piece of chrome stays in light mode regardless of theme selection. ### Root cause (already located) `themes.js::applyTheme` sets `stage.container().style.background = currentTheme['canvas-bg']` and redraws the grid + calendars, but never touches the `--wb-*` CSS variables or the `data-bs-theme` attribute that drives those variables. `whiteboard.css` already defines parallel light/dark rule sets keyed on `:root[data-bs-theme="dark"]`. So a single attribute toggle on `<html>` is enough to swing every piece of chrome that uses `var(--wb-toolbar-bg)`, `var(--wb-text)`, `var(--wb-border)`, `var(--wb-hover)`, `var(--wb-bg)`, etc. ### Approach Derive a "chrome is dark?" boolean from the theme's `canvas-bg` luminance and set `document.documentElement.dataset.bsTheme = 'dark'` (or remove it for the light fallback) inside `applyTheme`. This: - Reuses every existing CSS rule. No duplicated theme tokens. - Works for the four built-ins (Dark / Ocean / Warm have dark canvases → dark chrome; Light / Minimal have light canvases → light chrome). - Auto-classifies any future custom theme based on its canvas color. - Has no impact on the canvas painting, since `applyTheme` keeps setting `container.style.background` directly from `canvas-bg`. ### Requirements - Switching to Dark / Ocean / Warm produces dark chrome on the same page. - Switching to Light / Minimal produces light chrome. - Initial load on a board with a saved theme also shows the correct chrome (the existing `loadBoard → loadTheme → applyTheme` flow runs at startup, so we get this for free). - No JS error if `canvas-bg` is unparsable (defensive fallback to "light"). ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` — add a small luminance helper, toggle `data-bs-theme` inside `applyTheme`. ### Implementation Plan #### Step 1: Toggle `data-bs-theme` from canvas-bg luminance in applyTheme File: `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` Add a private helper inside the IIFE: ```js function _isDarkColor(c) { if (!c || typeof c !== 'string') return true; // default to dark — sensible fallback var s = c.trim(); var r, g, b; if (s[0] === '#') { var hex = s.slice(1); if (hex.length === 3) hex = hex.split('').map(function(x){return x + x;}).join(''); if (hex.length !== 6) return true; r = parseInt(hex.slice(0, 2), 16); g = parseInt(hex.slice(2, 4), 16); b = parseInt(hex.slice(4, 6), 16); } else { var m = s.match(/(\d+(?:\.\d+)?)/g); if (!m || m.length < 3) return true; r = parseFloat(m[0]); g = parseFloat(m[1]); b = parseFloat(m[2]); } if (!isFinite(r) || !isFinite(g) || !isFinite(b)) return true; var luma = 0.299 * r + 0.587 * g + 0.114 * b; return luma < 140; // a hair above the midpoint to keep "Minimal" / off-white in light mode } ``` Then near the end of `applyTheme(name, opts)`, after the canvas/grid update: ```js if (_isDarkColor(currentTheme['canvas-bg'])) { document.documentElement.setAttribute('data-bs-theme', 'dark'); } else { document.documentElement.removeAttribute('data-bs-theme'); } ``` Dependencies: none. ### Acceptance Criteria - [ ] Switch to Dark / Ocean / Warm → navbar, left toolbar, right properties panel, zoom widget, minimap all turn dark. - [ ] Switch to Light / Minimal → all of the same chrome reverts to white. - [ ] Reload a board that has a saved dark theme → chrome shows dark on first paint, no flash of white. - [ ] Viewer share + presentation share also follow the theme (the previous fix in #114 already feeds the same `applyTheme` path). - [ ] No JS errors for malformed/empty `canvas-bg`. - [ ] `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace --lib` clean. ### Notes - **Why luminance, not theme name:** the four built-ins happen to map cleanly, but custom themes (the codebase already supports them via `customThemes`) need an automatic rule. Luminance is one tiny helper and gives a deterministic answer. - **Why threshold 140 instead of 128:** keeps "Minimal" (`#ffffff`) and any near-white custom themes firmly in the light bucket. Slightly off-grey backgrounds like `#cccccc` still map to light, while `#1c1410` (Warm) and `#0a192f` (Ocean) are clearly dark. Edge cases at the boundary are acceptable since the user picks a theme intentionally and can always pick a different one. - **Initial load:** `app.js::loadBoard` already calls `loadTheme(...)` which calls `applyTheme(...)`, so the chrome flips on boot without any extra wiring. No flash because the call happens before the user sees the canvas. - **No CSS changes** — `whiteboard.css` already has both rule sets. We just flip the attribute that selects between them.
Author
Member

Test Results

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace --lib — 0 failed
  • node --check themes.js — clean
## Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace --lib` — 0 failed - `node --check themes.js` — clean
Author
Member

Implementation Summary

Make the whiteboard chrome (navbar / left toolbar / right properties / zoom / minimap) follow the active theme by toggling data-bs-theme on <html> based on the canvas background luminance.

crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js

Added a private _isDarkColor(c) helper that parses #rgb / #rrggbb / rgb(...) / rgba(...) strings, computes the standard 0.299R + 0.587G + 0.114B luma, and reports luma < 140 as dark. The 140 threshold (a hair above the 128 midpoint) keeps "Minimal" (#ffffff) and any near-white custom themes in the light bucket while clearly classifying Dark (#212529), Ocean (#0a192f), and Warm (#1c1410) as dark.

Inside applyTheme, after the canvas background is set:

if (_isDarkColor(currentTheme['canvas-bg'])) {
    document.documentElement.setAttribute('data-bs-theme', 'dark');
} else {
    document.documentElement.removeAttribute('data-bs-theme');
}

whiteboard.css already defines parallel rule sets:

:root, :root[data-bs-theme="dark"] { --wb-toolbar-bg: #2b3035; ... }
:root:not([data-bs-theme="dark"])  { --wb-toolbar-bg: #ffffff; ... }

So a single attribute flip cascades to every chrome rule that uses var(--wb-toolbar-bg), var(--wb-text), var(--wb-border), var(--wb-hover), var(--wb-bg), etc. No CSS changes required, no duplicated theme tokens.

Files Changed

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js+30/-0

Test Results

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace --lib — 0 failed
  • node --check themes.js — clean

Manual smoke

  1. Open a board, switch to Dark / Ocean / Warm — navbar, left toolbar, right properties panel, zoom widget, minimap all turn dark.
  2. Switch back to Light or Minimal — chrome reverts to white.
  3. Reload a board with a saved dark theme — chrome shows dark on first paint (the existing app.js::loadBoard → loadTheme → applyTheme flow runs at startup, so this works without extra wiring).
  4. Viewer share + presentation share — chrome follows the owner's theme too (the previous fix in #114 wired those through the same code path).
  5. No JS errors for malformed canvas-bg; defensive default is "dark", which matches the legacy default.

Notes

  • Luminance, not theme name: future custom themes (already supported via customThemes) get classified automatically based on their canvas color.
  • No flash on initial load: applyTheme runs in loadBoard before the user sees the canvas; the attribute flip happens during the same paint cycle.
## Implementation Summary Make the whiteboard chrome (navbar / left toolbar / right properties / zoom / minimap) follow the active theme by toggling `data-bs-theme` on `<html>` based on the canvas background luminance. ### `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` Added a private `_isDarkColor(c)` helper that parses `#rgb` / `#rrggbb` / `rgb(...)` / `rgba(...)` strings, computes the standard `0.299R + 0.587G + 0.114B` luma, and reports `luma < 140` as dark. The 140 threshold (a hair above the 128 midpoint) keeps "Minimal" (`#ffffff`) and any near-white custom themes in the light bucket while clearly classifying Dark (`#212529`), Ocean (`#0a192f`), and Warm (`#1c1410`) as dark. Inside `applyTheme`, after the canvas background is set: ```js if (_isDarkColor(currentTheme['canvas-bg'])) { document.documentElement.setAttribute('data-bs-theme', 'dark'); } else { document.documentElement.removeAttribute('data-bs-theme'); } ``` `whiteboard.css` already defines parallel rule sets: ```css :root, :root[data-bs-theme="dark"] { --wb-toolbar-bg: #2b3035; ... } :root:not([data-bs-theme="dark"]) { --wb-toolbar-bg: #ffffff; ... } ``` So a single attribute flip cascades to every chrome rule that uses `var(--wb-toolbar-bg)`, `var(--wb-text)`, `var(--wb-border)`, `var(--wb-hover)`, `var(--wb-bg)`, etc. No CSS changes required, no duplicated theme tokens. ### Files Changed - `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` — `+30/-0` ### Test Results - `cargo fmt --all -- --check` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace --lib` — 0 failed - `node --check themes.js` — clean ### Manual smoke 1. Open a board, switch to Dark / Ocean / Warm — navbar, left toolbar, right properties panel, zoom widget, minimap all turn dark. 2. Switch back to Light or Minimal — chrome reverts to white. 3. Reload a board with a saved dark theme — chrome shows dark on first paint (the existing `app.js::loadBoard → loadTheme → applyTheme` flow runs at startup, so this works without extra wiring). 4. Viewer share + presentation share — chrome follows the owner's theme too (the previous fix in #114 wired those through the same code path). 5. No JS errors for malformed `canvas-bg`; defensive default is "dark", which matches the legacy default. ### Notes - **Luminance, not theme name**: future custom themes (already supported via `customThemes`) get classified automatically based on their canvas color. - **No flash on initial load**: `applyTheme` runs in `loadBoard` before the user sees the canvas; the attribute flip happens during the same paint cycle.
Author
Member

Implementation revised

The first pass only flipped between the two pre-baked light/dark palettes that already lived in whiteboard.css. That made the chrome dark for Dark/Ocean/Warm but didn't actually follow each theme's color personality — Ocean got the same grey chrome as Dark, Warm got the same grey chrome as Dark.

Revised approach

Build a chrome palette from the theme's actual canvas-bg and text-color and write the --wb-* CSS variables onto :root directly. The chrome consumes those variables, so each theme now produces its own coherent chrome:

  • Ocean (#0a192f canvas) → bluish-dark chrome (#18273d toolbar / #425165 border / #ccd6f6 text)
  • Warm (#1c1410 canvas) → warm-brown chrome (#2a221e toolbar / #54403a border / #e8d5c4 text)
  • Dark (#212529 canvas) → neutral dark chrome (matches existing dark palette)
  • Light (#f8f9fa canvas) → off-white chrome (#eeeff0 toolbar / #d0d1d2 border)
  • Minimal (#ffffff canvas) → near-white chrome (#f5f5f5 toolbar)

Helpers added in themes.js

  • _parseColor(c) — accepts #rgb / #rrggbb / rgb(...) / rgba(...), returns {r,g,b} or null.
  • _luma(rgb) — standard 0.299R + 0.587G + 0.114B.
  • _isDarkColor(c)luma < 140 (a hair above the midpoint to keep near-white themes in light mode).
  • _shade(c, amt) — brighten or darken each channel by amt, clamped.
  • _applyChromePalette(theme) — toggles data-bs-theme for fallback rules, then sets --wb-bg / --wb-toolbar-bg / --wb-surface / --wb-ctx-bg / --wb-hover / --wb-border / --wb-text / --wb-text-muted / --wb-primary on :root. Step magnitudes (+14 / +28 / +56 for dark canvases, -10 / -22 / -40 for light) were tuned against the four built-ins.

applyTheme

Replaces the simple data-bs-theme toggle with _applyChromePalette(currentTheme). The data attribute is still set as a fallback; any rule we don't override resolves to a sensible default.

No CSS changes. Custom themes get classified automatically based on their canvas-bg. Boot path (loadBoard → loadTheme → applyTheme) is untouched, so the chrome is correct on first paint.

## Implementation revised The first pass only flipped between the two pre-baked light/dark palettes that already lived in `whiteboard.css`. That made the chrome dark for Dark/Ocean/Warm but didn't actually follow each theme's color personality — Ocean got the same grey chrome as Dark, Warm got the same grey chrome as Dark. ### Revised approach Build a chrome palette from the theme's actual `canvas-bg` and `text-color` and write the `--wb-*` CSS variables onto `:root` directly. The chrome consumes those variables, so each theme now produces its own coherent chrome: - **Ocean** (`#0a192f` canvas) → bluish-dark chrome (`#18273d` toolbar / `#425165` border / `#ccd6f6` text) - **Warm** (`#1c1410` canvas) → warm-brown chrome (`#2a221e` toolbar / `#54403a` border / `#e8d5c4` text) - **Dark** (`#212529` canvas) → neutral dark chrome (matches existing dark palette) - **Light** (`#f8f9fa` canvas) → off-white chrome (`#eeeff0` toolbar / `#d0d1d2` border) - **Minimal** (`#ffffff` canvas) → near-white chrome (`#f5f5f5` toolbar) ### Helpers added in `themes.js` - `_parseColor(c)` — accepts `#rgb` / `#rrggbb` / `rgb(...)` / `rgba(...)`, returns `{r,g,b}` or `null`. - `_luma(rgb)` — standard `0.299R + 0.587G + 0.114B`. - `_isDarkColor(c)` — `luma < 140` (a hair above the midpoint to keep near-white themes in light mode). - `_shade(c, amt)` — brighten or darken each channel by `amt`, clamped. - `_applyChromePalette(theme)` — toggles `data-bs-theme` for fallback rules, then sets `--wb-bg / --wb-toolbar-bg / --wb-surface / --wb-ctx-bg / --wb-hover / --wb-border / --wb-text / --wb-text-muted / --wb-primary` on `:root`. Step magnitudes (+14 / +28 / +56 for dark canvases, -10 / -22 / -40 for light) were tuned against the four built-ins. ### `applyTheme` Replaces the simple `data-bs-theme` toggle with `_applyChromePalette(currentTheme)`. The data attribute is still set as a fallback; any rule we don't override resolves to a sensible default. No CSS changes. Custom themes get classified automatically based on their `canvas-bg`. Boot path (`loadBoard → loadTheme → applyTheme`) is untouched, so the chrome is correct on first paint.
Author
Member

Iteration: drop --wb-primary override

Setting --wb-primary from the theme's shape-stroke made the active toolbar button (.tool-btn.active { background: var(--wb-primary); color: white; }) unreadable on Ocean (#64ffda cyan + white text) and Warm (#f59e0b amber + white text).

Removed the --wb-primary override — it now keeps the CSS default (#007AFF), which has good contrast with white text. Canvas-side accents (shape strokes, connector lines, transformer handles) still come from the theme tokens at canvas paint time, so the theme's color personality is preserved on the canvas without breaking chrome contrast.

## Iteration: drop `--wb-primary` override Setting `--wb-primary` from the theme's `shape-stroke` made the active toolbar button (`.tool-btn.active { background: var(--wb-primary); color: white; }`) unreadable on Ocean (`#64ffda` cyan + white text) and Warm (`#f59e0b` amber + white text). Removed the `--wb-primary` override — it now keeps the CSS default (`#007AFF`), which has good contrast with white text. Canvas-side accents (shape strokes, connector lines, transformer handles) still come from the theme tokens at canvas paint time, so the theme's color personality is preserved on the canvas without breaking chrome contrast.
Author
Member

Iteration: theme accent on chrome with auto contrast text

Restored --wb-primary to the theme accent and added a paired --wb-primary-text that picks black or white based on the accent's luminance. Now Ocean's active toolbar button keeps Ocean's cyan accent but uses dark text; Warm uses amber + dark text; Dark uses blue + white text.

crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js

var primary = theme['shape-stroke'] || theme['selection-color'] || '#007AFF';
var primaryRgb = _parseColor(primary);
var primaryText = (primaryRgb && _luma(primaryRgb) >= 140) ? '#0f172a' : '#ffffff';

root.style.setProperty('--wb-primary', primary);
root.style.setProperty('--wb-primary-text', primaryText);

crates/hero_whiteboard_ui/static/web/css/whiteboard.css

  • Added --wb-primary-text: #ffffff to both :root rule sets as the default.
  • Replaced color: white / color: #fff with color: var(--wb-primary-text) on the five rules that paint text directly on a --wb-primary background:
    • .tool-btn.active
    • .sub-opt.active
    • .btn-primary
    • .prop-btn.active
    • .comment-btn.submit

Other rules that just use --wb-primary for borders / outlines / accent-color keep their existing behavior.

Per-theme outcome

  • Dark#007AFF accent + white text (unchanged).
  • Ocean#64ffda cyan accent + dark #0f172a text (readable).
  • Warm#f59e0b amber accent + dark text (readable).
  • Light#0062cc deep blue + white text.
  • Minimal#374151 slate + white text.
## Iteration: theme accent on chrome with auto contrast text Restored `--wb-primary` to the theme accent and added a paired `--wb-primary-text` that picks black or white based on the accent's luminance. Now Ocean's active toolbar button keeps Ocean's cyan accent but uses dark text; Warm uses amber + dark text; Dark uses blue + white text. ### `crates/hero_whiteboard_ui/static/web/js/whiteboard/themes.js` ```js var primary = theme['shape-stroke'] || theme['selection-color'] || '#007AFF'; var primaryRgb = _parseColor(primary); var primaryText = (primaryRgb && _luma(primaryRgb) >= 140) ? '#0f172a' : '#ffffff'; root.style.setProperty('--wb-primary', primary); root.style.setProperty('--wb-primary-text', primaryText); ``` ### `crates/hero_whiteboard_ui/static/web/css/whiteboard.css` - Added `--wb-primary-text: #ffffff` to both `:root` rule sets as the default. - Replaced `color: white` / `color: #fff` with `color: var(--wb-primary-text)` on the five rules that paint text directly on a `--wb-primary` background: - `.tool-btn.active` - `.sub-opt.active` - `.btn-primary` - `.prop-btn.active` - `.comment-btn.submit` Other rules that just use `--wb-primary` for borders / outlines / accent-color keep their existing behavior. ### Per-theme outcome - **Dark** — `#007AFF` accent + white text (unchanged). - **Ocean** — `#64ffda` cyan accent + dark `#0f172a` text (readable). - **Warm** — `#f59e0b` amber accent + dark text (readable). - **Light** — `#0062cc` deep blue + white text. - **Minimal** — `#374151` slate + white text.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#115
No description provided.