Skip to content

Feat 212 dark mode - #336

Open
theultimatewasteofspace wants to merge 31 commits into
devfrom
feat-212-dark-mode
Open

Feat 212 dark mode#336
theultimatewasteofspace wants to merge 31 commits into
devfrom
feat-212-dark-mode

Conversation

@theultimatewasteofspace

Copy link
Copy Markdown
Collaborator

Summary

Implements Issue [#212] — light/dark theme for CARE, with the toggle in the avatar dropdown and the choice saved per user.

Bootstrap 5.3 already ships dark mode; setting data-bs-theme on <html> switches every stock component. CARE wasn't using it because ~210 places hardcoded Bootstrap's own hex values instead of referencing its variables. Most of this PR is replacing those, each keeping the original value as a fallback so light mode is unchanged.

A handful of the changed lines are incidental fixes where a hardcoded value turned out to be broken rather than just un-themed (a four-digit hex, a CSS variable that was never defined, a border with no colour or width set).

What's included

Theme switching

  • Toggle in the avatar dropdown; data-bs-theme set on <html>
  • app.theme.mode in user_setting via the existing appSettingSet event — no backend changes
  • Resolution order: saved setting → localStorage cache (care.theme) → prefers-color-scheme → light
  • initTheme() in App.vue rather than Topbar.vue, since the topbar doesn't render on login/register/wizard routes
  • applyTheme extracted to assets/utils.js; isDarkMode derived from the store

Colour migration

  • ~210 hardcoded values across 56 files replaced with var(--bs-*, fallback)
  • Non-theme-aware Bootstrap classes swapped (bg-lightbg-body-tertiary, navbar-light removed)
  • global.css overrides for classes with a fixed colour baked in: btn-light, btn-dark, btn-outline-dark, btn-outline-secondary, text-dark
  • color-scheme tied to data-bs-theme so browser-drawn controls (scrollbars, date pickers) follow CARE rather than the OS

Accent colour

  • Dark mode uses CARE's logo yellow with dark text instead of Bootstrap blue
  • Values centralised as --care-accent / --care-on-accent; Bootstrap sets button and form-control colours per component, so buttons, pagination, switches and checkboxes each need their own rule
  • Light mode keeps the existing blue

Third-party components

  • Quill (ql-*) — surfaces, toolbar, dropdowns; its icons are SVG so they need stroke/fill
  • bootstrap-vue-3 toasts — close button only (see limitations)

PDF viewer

  • Pages inverted and tinted so the document goes dark, not just the surrounding chrome
  • Per-document light toggle in the PDF toolbar, remembered in localStorage (care.pdfDark)
  • Light-toggled pages softened with brightness(0.93) so they don't glare against the dark UI
  • Annotation highlights switched from multiply to screen blending in dark mode
  • @media print disables inversion becase printing a dark page wastes ink and reads badly

Architectural decisions

  • ~49 hardcoded colours left in place. Two cases: white text on a fixed-colour background (the avatar is #fff on darkblue — converting the text would break light mode), and semantic colours where the value carries meaning (Assessment button states, danger reds, success greens, annotation highlights).
  • PDF pages inverted rather than chrome-only. Chrome-only is what Chrome and Firefox's built-in viewers do and never distorts anything. Inverting the page is better for long reading but affects photos and figures, so the per-document light toggle exists as the escape hatch.
  • Filter tuned toward figure colour over an exactly neutral background. A CSS filter transforms every pixel uniformly, so it can't treat the paper and a diagram differently. Heavy desaturation gives a neutral grey page but flattens figures; the current values favour keeping figure colours readable. An exact match for both would need per-pixel processing.
  • Browser preference used as the first-time default. Most users then never have to click anything.
  • Highlight blending changed rather than highlight colours. The highlight layer uses mix-blend-mode: multiply, which works like a marker pen on white paper and produces almost nothing over a dark page. Adjusting the fill colours had no effect; screen does.

Known limitations

  • Charts aren't themed. Chart.js sets axis, gridline and legend colours in JS config rather than CSS, and the component only renders inside a study session with collected data, so a change couldn't be tested. Suggest a follow-up issue.
  • Toasts stay light in both themes — bootstrap-vue-3 hardcodes their colours with !important. Only the close button needed fixing, since Bootstrap inverts it to white on a light background.
  • Browser theme detection depends on the browser reporting prefers-color-scheme. Brave suppresses it as anti-tracking, so CARE defaults to light there until the user chooses. Verified working in Safari.
  • Logging out doesn't clear the cached theme, so a shared browser shows the previous user's theme on the login page. Only a colour; nothing is exposed.
  • PDF inversion applies to the whole page, so photographs and scanned documents look like negatives. The per-document light toggle covers this case.
  • Text selection over an inverted PDF uses the browser default colour. Overriding it fought the canvas filter and looked worse.
  • Browser-drawn controls follow the theme now, but their exact appearance might differ between Chrome, Safari and Firefox.
  • The .text-dark override wasn't verified against a bg-warning badge — no templates existed to render one. If a badge ever looks unreadable, that rule is the cause.

Out of scope

  • Charts (above)
  • User story for user_stories.rst — drafted, but not inserted into the file yet.
  • A brightness or paper-tone control for the PDF viewer. Most reading apps offer one and it would remove the guesswork around exact values, but it's a feature rather than a fix.

Testing

Manual, both themes: dashboard, navigation, tables and modals, annotator and PDF viewer, Quill editor, settings, and the logged-out login page. Confirmed the user_setting row is written and the choice survives a hard reload, including with localStorage cleared so the theme has to come from the backend. Printing checked and dark mode reverts to light automatically, but the PDF inversion carried through and had to be disabled explicitly.

Closes (#212)

Ilyas Mohammed added 30 commits August 3, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant