feat(ui): design tokens + light/dark theming, migrate in-game chrome (F1a)
Tests · UI / test (push) Successful in 2m4s
Tests · UI / test (push) Successful in 2m4s
Introduce the shared design-token system under ui/frontend/src/lib/theme/: tokens.css (dark default + light palette, plus spacing/radii/typography scales), base.css global baseline (document background, text, token focus ring, selection), and theme.svelte.ts (system/light/dark choice, persisted to localStorage, applied via data-theme on <html>). A pre-paint guard in app.html resolves the theme before the app boots to avoid a flash, and the theme picker is wired into the previously-disabled account-menu stub. Migrate the always-visible in-game chrome to the tokens (header, account menu, sidebar, tab-bar, bottom-tabs, shell background): dark renders as before, light comes for free. The default stays dark during the incremental migration; the remaining view bodies migrate in F1b. Docs: ui/docs/design-system.md (+ index entry). Test: tests/theme.test.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,20 @@ Sessions and Theme) take over.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { i18n, SUPPORTED_LOCALES, type Locale } from "$lib/i18n/index.svelte";
|
||||
import {
|
||||
i18n,
|
||||
SUPPORTED_LOCALES,
|
||||
type Locale,
|
||||
type TranslationKey,
|
||||
} from "$lib/i18n/index.svelte";
|
||||
import { session } from "$lib/session-store.svelte";
|
||||
import { theme, type ThemeChoice } from "$lib/theme/theme.svelte";
|
||||
|
||||
const THEME_CHOICES: ReadonlyArray<{ id: ThemeChoice; key: TranslationKey }> = [
|
||||
{ id: "system", key: "game.shell.menu.theme_system" },
|
||||
{ id: "light", key: "game.shell.menu.theme_light" },
|
||||
{ id: "dark", key: "game.shell.menu.theme_dark" },
|
||||
];
|
||||
|
||||
let open = $state(false);
|
||||
let rootEl: HTMLDivElement | null = $state(null);
|
||||
@@ -27,6 +39,11 @@ Sessions and Theme) take over.
|
||||
i18n.setLocale(value);
|
||||
}
|
||||
|
||||
function selectTheme(event: Event): void {
|
||||
const value = (event.target as HTMLSelectElement).value as ThemeChoice;
|
||||
theme.setChoice(value);
|
||||
}
|
||||
|
||||
function onKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === "Escape" && open) {
|
||||
open = false;
|
||||
@@ -69,10 +86,19 @@ Sessions and Theme) take over.
|
||||
<button type="button" role="menuitem" data-testid="account-menu-sessions" disabled>
|
||||
{i18n.t("game.shell.menu.sessions")}
|
||||
</button>
|
||||
<button type="button" role="menuitem" data-testid="account-menu-theme" disabled>
|
||||
{i18n.t("game.shell.menu.theme")}
|
||||
</button>
|
||||
<label class="locale" data-testid="account-menu-language">
|
||||
<label class="field" data-testid="account-menu-theme">
|
||||
<span>{i18n.t("game.shell.menu.theme")}</span>
|
||||
<select
|
||||
data-testid="account-menu-theme-select"
|
||||
value={theme.choice}
|
||||
onchange={selectTheme}
|
||||
>
|
||||
{#each THEME_CHOICES as entry (entry.id)}
|
||||
<option value={entry.id}>{i18n.t(entry.key)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
<label class="field" data-testid="account-menu-language">
|
||||
<span>{i18n.t("game.shell.menu.language")}</span>
|
||||
<select
|
||||
data-testid="account-menu-language-select"
|
||||
@@ -106,12 +132,12 @@ Sessions and Theme) take over.
|
||||
padding: 0.25rem 0.6rem;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border: 1px solid #2a3150;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
.trigger:hover {
|
||||
background: #1c2238;
|
||||
background: var(--color-surface-hover);
|
||||
}
|
||||
.surface {
|
||||
position: absolute;
|
||||
@@ -120,10 +146,10 @@ Sessions and Theme) take over.
|
||||
min-width: 12rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #14182a;
|
||||
border: 1px solid #2a3150;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
|
||||
background: var(--color-surface-overlay);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 50;
|
||||
}
|
||||
.surface > button,
|
||||
@@ -137,23 +163,23 @@ Sessions and Theme) take over.
|
||||
cursor: pointer;
|
||||
}
|
||||
.surface > button:hover:not(:disabled) {
|
||||
background: #1c2238;
|
||||
background: var(--color-surface-hover);
|
||||
}
|
||||
.surface > button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.locale {
|
||||
.field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.locale select {
|
||||
.field select {
|
||||
font: inherit;
|
||||
background: #1c2238;
|
||||
background: var(--color-surface-raised);
|
||||
color: inherit;
|
||||
border: 1px solid #2a3150;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.15rem 0.35rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -84,10 +84,10 @@ absent until Phase 24 wires push-event state.
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #0a0e1a;
|
||||
color: #e8eaf6;
|
||||
border-bottom: 1px solid #20253a;
|
||||
font-family: system-ui, sans-serif;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.left,
|
||||
.right {
|
||||
@@ -108,13 +108,13 @@ absent until Phase 24 wires push-event state.
|
||||
padding: 0.25rem 0.6rem;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border: 1px solid #2a3150;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
.sidebar-toggle:hover {
|
||||
background: #1c2238;
|
||||
background: var(--color-surface-hover);
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 1023.98px) {
|
||||
.sidebar-toggle {
|
||||
|
||||
Reference in New Issue
Block a user