Clockwork Control features a multi-scheme theme system built on Tailwind CSS v4 custom property blocks and Alpine.js. Rather than a binary dark/light toggle, the control panel supports named curated palettes with seamless switching, zero flash of unstyled content (FOUC), and dynamic chart re-theming.
Available Color Schemes
- Light (
light): The canonical clean white surface palette (#ffffff), tailored ink contrasts, and subtle borders. - Dark (
dark): Built on the native--color-surface-darktoken (#181e25) with deep slate tones and soft inverted text. - High Contrast (
high-contrast): An accessibility-first high visibility palette utilizing pure#000000surface with maximum contrast borders and white ink. - Auto / System (
system): Follows the client operating system’sprefers-color-schememedia query, automatically switching between light and dark.
Operators can toggle quickly between Light and Dark via the solar icon button (fa-sun / fa-moon) in the top navigation bar, or select specific palettes and configure system sync in Settings → Appearance.
Font Scaling (Typography Accessibility)
Clockwork Control provides three dynamic typography scales:
- Compact (
compact): Higher density for smaller screens or high-information-density monitoring. - Standard (
standard): Canonical balanced interface typography. - Comfortable (
comfortable): Increased font size and line spacing for relaxed reading.
Font scaling applies data-font-scale to <html> and persists the selection in localStorage under cw_font_scale.
Removed: Midnight. The midnight scheme (a navy / Darcula palette) was removed in v1.4.0 in favor of the 4-option grid above. Operators who previously selected Midnight aren’t left with a broken preference: the :root[data-theme="dark"] CSS block also matches :root[data-theme="midnight"] (so any stale data-theme="midnight" attribute still renders the Dark palette), and resources/js/theme.js normalizes a stored cw_theme=midnight cookie to dark on init. AppearanceSettingsController::VALID_THEMES no longer accepts midnight as a value to persist going forward.
Architecture & How It Works
1. CSS Custom Property Palettes (resources/css/app.css)
All semantic design tokens are registered in Tailwind v4’s @theme directive, making utility classes such as bg-[var(--color-surface)], text-[var(--color-ink)], and border-[var(--color-border-light)] immediately available.
Each named palette is defined as a scoped selector block on the root element:
:root,
:root[data-theme="light"] {
--color-surface: #ffffff;
--color-surface-alt: #f4f5f7;
--color-border: #e5e7eb;
--color-border-light: #f2f3f5;
--color-ink: #222222;
--color-ink-strong: #18181b;
--color-ink-muted: #45515e;
...
}
:root[data-theme="dark"] {
--color-surface: #181e25;
--color-surface-alt: #1e2630;
--color-border: #333e4e;
--color-border-light: #252e3a;
--color-ink: #e6e8eb;
--color-ink-strong: #f8fafc;
--color-ink-muted: #94a3b8;
...
}
Components (.btn-pill-nav, .status-pill, .cw-switch) reference these variables directly, instantly adapting to whatever data-theme attribute is active on <html>. .card is the one exception: its light-mode background is a hardcoded #f9f9f9 (a deliberately subtler tone than --color-surface’s pure white) with an explicit :root[data-theme="dark"] .card override falling back to --color-surface in dark mode.
Tailwind’s dark: variant is remapped via a @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *, [data-theme="high-contrast"], [data-theme="high-contrast"] *)); declaration at the top of app.css, so dark: utility classes key off the active data-theme attribute rather than the OS-level prefers-color-scheme media query. This closed a bleed bug where a device set to dark mode at the OS level would override an explicit Light selection in the app.
2. Zero-FOUC Head Script (layouts/app.blade.php)
To eliminate flashes of incorrect theme before stylesheets and scripts hydrate, an inline blocking script sits in <head> prior to the @vite compiled CSS bundle:
<script>
(function () {
try {
var match = document.cookie.match(/(?:^|; )cw_theme=([^;]*)/);
var theme = match ? decodeURIComponent(match[1]) : 'system';
var resolved = theme;
if (!resolved || resolved === 'system') {
resolved = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.setAttribute('data-theme', resolved);
} catch (e) {}
})();
</script>
3. Persistence Flow
- When a user selects a scheme in the gear dropdown, the Alpine component
themePickerimmediately setsdata-themeondocument.documentElementand dispatches atheme-changedcustom event. - It synchronously sets the
cw_theme1-year cookie viadocument.cookie(SameSite=Lax), ensuring sub-requests and browser navigation render without delay. - It asynchronously sends a
POST /settings/appearancerequest toAppearanceSettingsController. If authenticated, the preference is saved tousers.themein the database.
4. Dynamic ECharts Re-Theming
ECharts render via HTML <canvas> elements and do not automatically inherit CSS variable changes. resources/js/theme.js listens to theme-changed window events, resolves current computed colors, and invokes chart.setOption() on all active chart instances to update text, axis lines, and grid styling live.
Adding a New Named Scheme
To add a new theme (e.g. forest or nord):
- Open
resources/css/app.cssand add an override block::root[data-theme="nord"] { --color-surface: #2e3440; --color-surface-alt: #3b4252; --color-border: #4c566a; --color-border-light: #434c5e; --color-ink: #eceff4; --color-ink-strong: #ffffff; --color-ink-muted: #d8dee9; ... } - Add the scheme key to
App\Http\Controllers\AppearanceSettingsController::VALID_THEMES. - Add the scheme descriptor to
schemesarray inresources/js/theme.jsso the swatch thumbnail renders in the gear menu.
Developer Styleguide Workbench
The application provides an interactive design system styleguide at /styleguide powered by App\Http\Controllers\StyleguideController (resources/views/styleguide/index.blade.php). It serves as a live workbench for verifying themes, typography scale, buttons, form controls, badges, cards, alerts, and modal dialogs under every supported color scheme.
