From: Jérôme Benoit Date: Wed, 18 Mar 2026 23:14:13 +0000 (+0100) Subject: feat(ui): apply Tokyo Night Storm theme with semantic CSS tokens X-Git-Tag: ocpp-server@v3.2.0~50 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=9cc3a26a490813d549b4c3c0f6e86d21957f6701;p=e-mobility-charging-stations-simulator.git feat(ui): apply Tokyo Night Storm theme with semantic CSS tokens Add theme.css with two-layer token system: primitive tokens from the official Tokyo Night Storm palette, semantic tokens mapping UI roles to primitives. All components use semantic tokens exclusively. Replace all hardcoded colors across 13 Vue components. Theme native HTML elements (button, input, select, a, headings) globally. Toggle button pressed state uses palette-semantic active bg + accent border + inset shadow for clear visual distinction. Remove dead code: simulatorButtonClass computed and associated CSS classes that duplicated global button styles. --- diff --git a/ui/web/src/App.vue b/ui/web/src/App.vue index 1a0b1d64..8bf915f2 100644 --- a/ui/web/src/App.vue +++ b/ui/web/src/App.vue @@ -21,8 +21,8 @@ import Container from '@/components/Container.vue' -moz-osx-font-smoothing: grayscale; display: flex; flex-direction: row; - color: black; - background-color: white; + color: var(--color-text); + background-color: var(--color-bg); } #action-container { @@ -38,7 +38,7 @@ import Container from '@/components/Container.vue' margin-right: 0.2%; margin-left: 0.2%; padding: 0.4%; - border: solid 0.25px black; + border: solid 0.25px var(--color-border); } body { diff --git a/ui/web/src/assets/theme.css b/ui/web/src/assets/theme.css new file mode 100644 index 00000000..2bb633b2 --- /dev/null +++ b/ui/web/src/assets/theme.css @@ -0,0 +1,84 @@ +/* Tokyo Night Storm */ + +:root { + /* Palette */ + --tn-bg-dark: #1f2335; + --tn-bg-base: #24283b; + --tn-bg-raised: #1b1e2e; + --tn-bg-hover: #292e42; + --tn-bg-active: #2c324a; + --tn-fg-base: #a9b1d6; + --tn-fg-bright: #c0caf5; + --tn-fg-muted: #8089b3; + --tn-blue: #7aa2f7; + --tn-accent: #3d59a1; + --tn-white: #ffffff; + + /* Semantic */ + --color-bg: var(--tn-bg-base); + --color-bg-surface: var(--tn-bg-dark); + --color-bg-input: var(--tn-bg-raised); + --color-bg-hover: var(--tn-bg-hover); + --color-bg-active: var(--tn-bg-active); + --color-bg-header: var(--tn-bg-dark); + --color-bg-caption: var(--tn-bg-raised); + --color-bg-button: var(--tn-accent); + --color-bg-button-hover: color-mix(in srgb, var(--tn-accent) 67%, transparent); + --color-text: var(--tn-fg-base); + --color-text-strong: var(--tn-fg-bright); + --color-text-muted: var(--tn-fg-muted); + --color-text-on-button: var(--tn-white); + --color-primary: var(--tn-blue); + --color-border: var(--tn-bg-raised); + --color-border-row: var(--tn-bg-hover); + --color-accent: var(--tn-accent); + --color-shadow-inset: rgba(0, 0, 0, 0.4); +} + +body { + color: var(--color-text); + background-color: var(--color-bg); +} + +button, +.button { + color: var(--color-text-on-button); + background-color: var(--color-bg-button); + border: 1px solid var(--color-border); + cursor: pointer; +} + +button:hover, +.button:hover { + background-color: var(--color-bg-button-hover); +} + +input, +select, +textarea { + color: var(--color-text); + background-color: var(--color-bg-input); + border: 1px solid var(--color-border-row); +} + +input::placeholder { + color: var(--color-text-muted); +} + +input:focus, +select:focus, +textarea:focus { + outline: 1px solid var(--color-primary); +} + +a { + color: var(--color-primary); +} + +h1, +h2, +h3, +p, +li { + color: var(--color-text); +} diff --git a/ui/web/src/components/buttons/ToggleButton.vue b/ui/web/src/components/buttons/ToggleButton.vue index de1d2692..04195d77 100644 --- a/ui/web/src/components/buttons/ToggleButton.vue +++ b/ui/web/src/components/buttons/ToggleButton.vue @@ -50,8 +50,9 @@ const click = (): void => { diff --git a/ui/web/src/components/charging-stations/CSData.vue b/ui/web/src/components/charging-stations/CSData.vue index 28a8b4d3..7d1f4ce3 100644 --- a/ui/web/src/components/charging-stations/CSData.vue +++ b/ui/web/src/components/charging-stations/CSData.vue @@ -267,7 +267,7 @@ const deleteChargingStation = (): void => { #connectors-table { display: flex; flex-direction: column; - background-color: white; + background-color: var(--color-bg-surface); overflow: auto hidden; border-collapse: collapse; empty-cells: show; @@ -283,15 +283,15 @@ const deleteChargingStation = (): void => { flex-direction: row; justify-content: center; align-items: center; - border: solid 0.25px black; + border: solid 0.25px var(--color-border-row); } .connectors-table__row:nth-of-type(even) { - background-color: whitesmoke; + background-color: var(--color-bg-hover); } #connectors-table__head .connectors-table__row { - background-color: lightgrey; + background-color: var(--color-bg-header); } .connectors-table__column { diff --git a/ui/web/src/components/charging-stations/CSTable.vue b/ui/web/src/components/charging-stations/CSTable.vue index 26bd9b52..3ed3b6b8 100644 --- a/ui/web/src/components/charging-stations/CSTable.vue +++ b/ui/web/src/components/charging-stations/CSTable.vue @@ -106,11 +106,11 @@ const $emit = defineEmits(['need-refresh']) #cs-table { height: fit-content; width: 100%; - background-color: white; + background-color: var(--color-bg-surface); display: flex; flex-direction: column; overflow: auto hidden; - border: solid 0.25px black; + border: solid 0.25px var(--color-border); border-collapse: collapse; empty-cells: show; } @@ -123,8 +123,8 @@ const $emit = defineEmits(['need-refresh']) } #cs-table__caption { - color: ivory; - background-color: black; + color: var(--color-text-strong); + background-color: var(--color-bg-caption); font-size: 1.5rem; font-weight: bold; padding: 0.5rem; @@ -137,11 +137,11 @@ const $emit = defineEmits(['need-refresh']) flex-direction: row; justify-content: center; align-items: center; - border: solid 0.25px black; + border: solid 0.25px var(--color-border-row); } .cs-table__row:nth-of-type(even) { - background-color: whitesmoke; + background-color: var(--color-bg-hover); } .cs-table__column { @@ -153,7 +153,7 @@ const $emit = defineEmits(['need-refresh']) } #cs-table__head .cs-table__row { - background-color: lightgrey; + background-color: var(--color-bg-header); } .cs-table__connectors-column { diff --git a/ui/web/src/main.ts b/ui/web/src/main.ts index 77eb8d1d..d40f9a25 100644 --- a/ui/web/src/main.ts +++ b/ui/web/src/main.ts @@ -9,6 +9,8 @@ import { router } from '@/router' import 'vue-toast-notification/dist/theme-bootstrap.css' +import '@/assets/theme.css' + const app = createApp(App as Component) const initializeApp = (app: AppType, config: ConfigurationData) => { diff --git a/ui/web/src/views/ChargingStationsView.vue b/ui/web/src/views/ChargingStationsView.vue index c530223d..ecf937ba 100644 --- a/ui/web/src/views/ChargingStationsView.vue +++ b/ui/web/src/views/ChargingStationsView.vue @@ -64,7 +64,6 @@ (undefined) const simulatorStarted = computed((): boolean | undefined => simulatorState.value?.started) -const simulatorButtonClass = computed((): string => - simulatorState.value?.started === true ? 'simulator-stop-button' : 'simulator-start-button' -) const simulatorButtonMessage = computed( (): string => `${simulatorState.value?.started === true ? 'Stop' : 'Start'} Simulator${ @@ -355,18 +351,19 @@ const stopSimulator = (): void => { #ui-server-container { display: flex; justify-content: center; - border-style: outset; + border: 1px solid var(--color-border-row); } #ui-server-selector { width: 100%; - background-color: rgb(239, 239, 239); + background-color: var(--color-bg-input); + color: var(--color-text); font: small-caption; text-align: center; } #ui-server-selector:hover { - background-color: rgb(229, 229, 229); + background-color: var(--color-bg-hover); } #buttons-container { @@ -376,46 +373,22 @@ const stopSimulator = (): void => { top: 0; } -.simulator-start-button { - color: ivory; - background-color: green; -} - -.simulator-start-button:hover { - background-color: rgb(0, 98, 0); -} - -.simulator-stop-button { - color: ivory; - background-color: red; -} - -.simulator-stop-button:hover { - background-color: rgb(225, 0, 0); -} - #action-button { flex: none; } #reload-button { - color: ivory; - background-color: blue; font-size: 1.5rem; } -#reload-button:hover { - background-color: rgb(0, 0, 225); -} - #reload-button:active { - background-color: darkblue; + background-color: var(--color-primary); } #action { min-width: max-content; - color: ivory; - background-color: black; + color: var(--color-text-strong); + background-color: var(--color-bg-caption); padding: 0.8%; }