diff --git a/master-frontend/src/App.vue b/master-frontend/src/App.vue index 17ff68e..8ff879a 100644 --- a/master-frontend/src/App.vue +++ b/master-frontend/src/App.vue @@ -164,8 +164,8 @@ html, body, #app { body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; - background: #11111b; - color: #cdd6f4; + background: var(--c-crust); + color: var(--c-text); } .app-layout { @@ -186,34 +186,34 @@ body { .toolbar-area { grid-area: toolbar; - background: #1e1e2e; - border-bottom: 1px solid #313244; + background: var(--c-base); + border-bottom: 1px solid var(--c-surface0); } .tree-area { grid-area: tree; - background: #181825; - border-right: 1px solid #313244; + background: var(--c-mantle); + border-right: 1px solid var(--c-surface0); overflow-y: auto; } .content-area { grid-area: content; - background: #11111b; + background: var(--c-crust); overflow: hidden; } .panel-area { grid-area: panel; - background: #181825; - border-left: 1px solid #313244; + background: var(--c-mantle); + border-left: 1px solid var(--c-surface0); overflow-y: auto; } .log-area { grid-area: log; - background: #1e1e2e; - border-top: 1px solid #313244; + background: var(--c-base); + border-top: 1px solid var(--c-surface0); overflow: hidden; } diff --git a/master-frontend/src/components/CommunicationSettings.vue b/master-frontend/src/components/CommunicationSettings.vue index 7f4d73c..ae1a46c 100644 --- a/master-frontend/src/components/CommunicationSettings.vue +++ b/master-frontend/src/components/CommunicationSettings.vue @@ -54,12 +54,12 @@ const { t } = useI18n() diff --git a/master-frontend/src/components/ConnectionTree.vue b/master-frontend/src/components/ConnectionTree.vue index 559667b..f3a9ee2 100644 --- a/master-frontend/src/components/ConnectionTree.vue +++ b/master-frontend/src/components/ConnectionTree.vue @@ -195,13 +195,13 @@ function fcLabel(fn: string): string { padding: 8px 12px; font-size: 11px; text-transform: uppercase; - color: #6c7086; + color: var(--c-subtext0); letter-spacing: 0.5px; } .tree-empty { padding: 24px 12px; - color: #6c7086; + color: var(--c-subtext0); text-align: center; } @@ -216,18 +216,18 @@ function fcLabel(fn: string): string { } .tree-node:hover { - background: #1e1e2e; + background: var(--c-hover-bg); } .tree-node.selected { - background: #89b4fa; - color: #1e1e2e; + background: var(--c-selection-bg); + color: var(--c-selection-text); } .tree-node.selected .node-meta, .tree-node.selected .node-slave { - color: #1e1e2e; - opacity: 0.7; + color: var(--c-selection-text); + opacity: 1; } .tree-child { @@ -238,7 +238,7 @@ function fcLabel(fn: string): string { font-size: 8px; width: 12px; text-align: center; - color: #6c7086; + color: var(--c-subtext0); } .node-status { @@ -248,11 +248,11 @@ function fcLabel(fn: string): string { flex-shrink: 0; } -.node-status.connected { background: #a6e3a1; } -.node-status.disconnected { background: #f38ba8; } -.node-status.error { background: #fab387; } +.node-status.connected { background: var(--c-green); } +.node-status.disconnected { background: var(--c-red); } +.node-status.error { background: var(--c-peach); } .node-status.reconnecting { - background: #f9e2af; + background: var(--c-yellow); animation: reconnecting-pulse 1s ease-in-out infinite; } @@ -266,12 +266,12 @@ function fcLabel(fn: string): string { height: 6px; border-radius: 50%; flex-shrink: 0; - background: #45475a; + background: var(--c-surface1); } .poll-indicator.active { - background: #a6e3a1; - box-shadow: 0 0 4px #a6e3a1; + background: var(--c-green); + box-shadow: 0 0 4px var(--c-green); } .node-label { @@ -283,20 +283,20 @@ function fcLabel(fn: string): string { .node-slave { font-size: 10px; - color: #6c7086; + color: var(--c-subtext0); } .node-meta { font-size: 10px; - color: #6c7086; + color: var(--c-subtext0); font-family: monospace; } /* Context Menu */ .context-menu { position: fixed; - background: #1e1e2e; - border: 1px solid #45475a; + background: var(--c-base); + border: 1px solid var(--c-surface1); border-radius: 6px; padding: 4px 0; z-index: 999; @@ -311,10 +311,10 @@ function fcLabel(fn: string): string { } .ctx-item:hover { - background: #313244; + background: var(--c-control-bg); } .ctx-item.danger { - color: #f38ba8; + color: var(--c-red); } diff --git a/master-frontend/src/components/DataTable.vue b/master-frontend/src/components/DataTable.vue index f59a669..c5287f1 100644 --- a/master-frontend/src/components/DataTable.vue +++ b/master-frontend/src/components/DataTable.vue @@ -418,26 +418,26 @@ const valueColumnLabel = computed(() => { align-items: center; justify-content: center; height: 100%; - color: #6c7086; + color: var(--c-subtext0); font-size: 13px; text-align: center; } .empty-hint { font-size: 11px; - color: #45475a; + color: var(--c-subtext0); margin-top: 6px; } .error-text { - color: #f38ba8; + color: var(--c-red); font-size: 12px; } .error-badge { font-size: 10px; - background: #f38ba8; - color: #1e1e2e; + background: var(--c-red); + color: var(--c-on-accent); padding: 1px 6px; border-radius: 3px; font-weight: 600; @@ -450,9 +450,9 @@ const valueColumnLabel = computed(() => { align-items: center; gap: 8px; padding: 6px 10px; - border-bottom: 1px solid #313244; + border-bottom: 1px solid var(--c-surface0); flex-shrink: 0; - background: #1e1e2e; + background: var(--c-base); } .header-title { @@ -462,24 +462,24 @@ const valueColumnLabel = computed(() => { text-overflow: ellipsis; font-size: 12px; font-weight: 600; - color: #89b4fa; + color: var(--c-blue); white-space: nowrap; } .format-select { flex: none; padding: 2px 6px; - background: #313244; - border: 1px solid #45475a; + background: var(--c-control-bg); + border: 1px solid var(--c-surface1); border-radius: 4px; - color: #cdd6f4; + color: var(--c-text); font-size: 11px; cursor: pointer; } .format-select:focus { outline: none; - border-color: #89b4fa; + border-color: var(--c-blue); } .search-input { @@ -487,37 +487,37 @@ const valueColumnLabel = computed(() => { min-width: min(140px, 100%); max-width: 160px; padding: 3px 8px; - background: #313244; - border: 1px solid #45475a; + background: var(--c-control-bg); + border: 1px solid var(--c-surface1); border-radius: 4px; - color: #cdd6f4; + color: var(--c-text); font-size: 12px; margin-left: auto; } .search-input:focus { outline: none; - border-color: #89b4fa; + border-color: var(--c-blue); } .mode-btn { padding: 2px 8px; - background: #313244; - border: 1px solid #45475a; + background: var(--c-control-bg); + border: 1px solid var(--c-surface1); border-radius: 4px; - color: #cdd6f4; + color: var(--c-text); cursor: pointer; font-size: 11px; font-family: monospace; } .mode-btn:hover { - background: #45475a; + background: var(--c-surface1); } .register-count { font-size: 11px; - color: #6c7086; + color: var(--c-subtext0); white-space: nowrap; } @@ -543,12 +543,12 @@ const valueColumnLabel = computed(() => { } .table th { - background: #1e1e2e; - color: #6c7086; + background: var(--c-mantle); + color: var(--c-subtext0); font-weight: 500; padding: 6px 10px; text-align: left; - border-bottom: 1px solid #313244; + border-bottom: 1px solid var(--c-surface0); z-index: 1; } @@ -557,37 +557,37 @@ const valueColumnLabel = computed(() => { } .table tbody tr:hover { - background: #1e1e2e; + background: var(--c-hover-bg); } .table tbody tr.selected { - background: #89b4fa; - color: #1e1e2e; + background: var(--c-selection-bg); + color: var(--c-selection-text); } .table td { padding: 4px 10px; - border-bottom: 1px solid #1e1e2e; + border-bottom: 1px solid var(--c-row-border); } .col-addr { - font-family: 'SF Mono', 'Fira Code', monospace; + font-family: var(--font-mono); width: 90px; - color: #89b4fa; + color: var(--c-blue); } .table tbody tr.selected .col-addr { - color: #1e1e2e; + color: var(--c-selection-text); } .col-raw { font-family: monospace; width: 120px; - color: #6c7086; + color: var(--c-subtext0); } .col-display { - font-family: 'SF Mono', 'Fira Code', monospace; + font-family: var(--font-mono); } .bool-value { @@ -599,13 +599,13 @@ const valueColumnLabel = computed(() => { } .bool-value.on { - background: #a6e3a1; - color: #1e1e2e; + background: var(--c-success-bg); + color: var(--c-success-text); } .bool-value.off { - background: #45475a; - color: #6c7086; + background: var(--c-neutral-bg); + color: var(--c-neutral-text); } /* Virtual rows */ @@ -614,20 +614,20 @@ const valueColumnLabel = computed(() => { align-items: center; cursor: pointer; font-size: 12px; - border-bottom: 1px solid #1e1e2e; + border-bottom: 1px solid var(--c-row-border); } .virtual-row:hover { - background: #1e1e2e; + background: var(--c-hover-bg); } .virtual-row.selected { - background: #89b4fa; - color: #1e1e2e; + background: var(--c-selection-bg); + color: var(--c-selection-text); } .virtual-row.selected .col-addr { - color: #1e1e2e; + color: var(--c-selection-text); } .vcol { @@ -648,12 +648,12 @@ const valueColumnLabel = computed(() => { width: 120px; min-width: 120px; font-family: monospace; - color: #6c7086; + color: var(--c-subtext0); } .vcol.col-display { flex: 1; min-width: 0; - font-family: 'SF Mono', 'Fira Code', monospace; + font-family: var(--font-mono); } diff --git a/master-frontend/src/components/NewConnectionDialog.vue b/master-frontend/src/components/NewConnectionDialog.vue index 3084b69..3c60ec3 100644 --- a/master-frontend/src/components/NewConnectionDialog.vue +++ b/master-frontend/src/components/NewConnectionDialog.vue @@ -393,25 +393,25 @@ async function submit() { diff --git a/master-frontend/src/components/NewScanGroupDialog.vue b/master-frontend/src/components/NewScanGroupDialog.vue index 2080d17..2a13d30 100644 --- a/master-frontend/src/components/NewScanGroupDialog.vue +++ b/master-frontend/src/components/NewScanGroupDialog.vue @@ -93,17 +93,17 @@ async function submit() { diff --git a/master-frontend/src/components/ScanDialog.vue b/master-frontend/src/components/ScanDialog.vue index 54101ea..fa7721e 100644 --- a/master-frontend/src/components/ScanDialog.vue +++ b/master-frontend/src/components/ScanDialog.vue @@ -345,46 +345,46 @@ async function scanAndAddAllRegistersForSlaves(slaveIds: number[]) { diff --git a/master-frontend/src/components/ValuePanel.vue b/master-frontend/src/components/ValuePanel.vue index 67f3534..b1868be 100644 --- a/master-frontend/src/components/ValuePanel.vue +++ b/master-frontend/src/components/ValuePanel.vue @@ -355,17 +355,17 @@ const writeCategory = computed<'register' | 'coil' | null>(() => { diff --git a/master-frontend/src/components/WriteDialog.vue b/master-frontend/src/components/WriteDialog.vue index 37d3a6c..843873f 100644 --- a/master-frontend/src/components/WriteDialog.vue +++ b/master-frontend/src/components/WriteDialog.vue @@ -180,26 +180,26 @@ async function submit() { diff --git a/master-frontend/src/main.ts b/master-frontend/src/main.ts index 2578901..4975400 100644 --- a/master-frontend/src/main.ts +++ b/master-frontend/src/main.ts @@ -1,6 +1,8 @@ import 'shared-frontend/src/styles/tokens.css' import 'shared-frontend/src/styles/toolbar.css' +import { initTheme } from 'shared-frontend' import { createApp } from 'vue' import App from './App.vue' +initTheme() createApp(App).mount('#app') diff --git a/shared-frontend/src/components/LangToggle.vue b/shared-frontend/src/components/LangToggle.vue index bca445d..9b2dff0 100644 --- a/shared-frontend/src/components/LangToggle.vue +++ b/shared-frontend/src/components/LangToggle.vue @@ -34,7 +34,7 @@ function pick(next: Locale) { margin-right: 8px; border-radius: 4px; overflow: hidden; - background: var(--c-surface0); + background: var(--c-control-bg); border: 1px solid var(--c-surface1); } @@ -50,13 +50,13 @@ function pick(next: Locale) { } .lang-toggle-btn:hover:not(.active) { - background: var(--c-surface1); + background: var(--c-hover-bg); color: var(--c-text); } .lang-toggle-btn.active { - background: var(--c-surface1); - color: var(--c-text); + background: var(--c-selection-bg); + color: var(--c-selection-text); font-weight: 600; } diff --git a/shared-frontend/src/components/ThemeToggle.vue b/shared-frontend/src/components/ThemeToggle.vue index bd636ad..5e161a0 100644 --- a/shared-frontend/src/components/ThemeToggle.vue +++ b/shared-frontend/src/components/ThemeToggle.vue @@ -34,7 +34,7 @@ function toggle() { height: 24px; margin-right: 8px; border-radius: 4px; - background: var(--c-surface0); + background: var(--c-control-bg); border: 1px solid var(--c-surface1); color: var(--c-subtext0); cursor: pointer; @@ -42,7 +42,7 @@ function toggle() { } .theme-toggle:hover { - background: var(--c-surface1); + background: var(--c-hover-bg); color: var(--c-text); } diff --git a/shared-frontend/src/styles/tokens.css b/shared-frontend/src/styles/tokens.css index e96b4a2..1ba2464 100644 --- a/shared-frontend/src/styles/tokens.css +++ b/shared-frontend/src/styles/tokens.css @@ -1,6 +1,6 @@ -/* Catppuccin palettes + semantic aliases. +/* Theme palettes + semantic aliases. Imported once from main.ts so every component can use var(--…). - Mocha (dark) is the default; [data-theme='light'] switches to Latte. */ + Mocha (dark) is the default; light uses neutral white and gray surfaces. */ :root, :root[data-theme='dark'] { /* Base layers (dark → light) */ @@ -29,33 +29,56 @@ --c-row-danger-bg: #3d2a30; + /* Roles whose foreground/background relationship changes with the theme. */ + --c-on-accent: var(--c-base); + --c-accent-hover: var(--c-sapphire); + --c-control-bg: var(--c-surface0); + --c-hover-bg: var(--c-base); + --c-selection-bg: var(--c-blue); + --c-selection-text: var(--c-base); + --c-row-border: var(--c-base); + --c-success-bg: var(--c-green); + --c-success-text: var(--c-base); + --c-neutral-bg: var(--c-surface1); + --c-neutral-text: var(--c-subtext1); + /* Mono stack with broader fallback chain than the original SF Mono / Fira Code. */ --font-mono: 'SF Mono', 'Cascadia Code', 'JetBrains Mono', 'Fira Code', Menlo, Consolas, monospace; } :root[data-theme='light'] { - /* Catppuccin Latte */ - --c-crust: #dce0e8; - --c-mantle: #e6e9ef; - --c-base: #eff1f5; - --c-surface0: #ccd0da; - --c-surface1: #bcc0cc; - --c-surface2: #acb0be; - --c-overlay0: #9ca0b0; - --c-subtext0: #6c6f85; - --c-subtext1: #5c5f77; - --c-text: #4c4f69; - - --c-blue: #1e66f5; - --c-lavender: #7287fd; - --c-sapphire: #209fb5; - --c-sky: #04a5e5; - --c-teal: #179299; - --c-green: #40a02b; - --c-yellow: #df8e1d; - --c-peach: #fe640b; - --c-red: #d20f39; - --c-mauve: #8839ef; - - --c-row-danger-bg: #ecd6de; + --c-crust: #ffffff; + --c-mantle: #f5f7fa; + --c-base: #ffffff; + --c-surface0: #e2e7ee; + --c-surface1: #c7d0db; + --c-surface2: #aebbc9; + --c-overlay0: #66758a; + --c-subtext0: #526176; + --c-subtext1: #3f4e63; + --c-text: #202c3c; + + --c-blue: #245dcc; + --c-lavender: #5753bb; + --c-sapphire: #176b86; + --c-sky: #126e99; + --c-teal: #16746d; + --c-green: #217345; + --c-yellow: #8a6400; + --c-peach: #a94d12; + --c-red: #b42340; + --c-mauve: #793ac1; + + --c-row-danger-bg: #fff0f2; + --c-on-accent: #ffffff; + --c-accent-hover: #1c4ca8; + --c-control-bg: #ffffff; + --c-hover-bg: #edf2f8; + --c-selection-bg: #e5efff; + --c-selection-text: #193e80; + --c-row-border: #e9edf2; + --c-success-bg: #e4f3e9; + --c-success-text: #21663e; + --c-neutral-bg: #edf0f4; + --c-neutral-text: #546174; }