feat(frontend): default site theme to Amber instead of Classic

New visitors (no saved lv_theme) now get the Amber accent; reconciled the
static CSS accent, demo scene colors and console greeting to match so there
is no blue/emerald flash before the theme provider applies. Updated the
default-theme assertions in the theme tests.
This commit is contained in:
loki5512344 2026-09-29 10:55:18 +02:00
parent 8a758754dd
commit 3f679b83f6
Signed by: boba
GPG key ID: 253067914055423B
8 changed files with 16 additions and 12 deletions

View file

@ -36,6 +36,7 @@ test('a theme card inside the preview loads that preset into the draft, not the
await user.click(within(gui).getByRole('option', { name: 'Тема Nitro' })) await user.click(within(gui).getByRole('option', { name: 'Тема Nitro' }))
expect(screen.getByDisplayValue('Nitro')).toBeInTheDocument() expect(screen.getByDisplayValue('Nitro')).toBeInTheDocument()
expect(within(gui).getByRole('option', { name: 'Тема Nitro' })).toHaveAttribute('aria-selected', 'true') expect(within(gui).getByRole('option', { name: 'Тема Nitro' })).toHaveAttribute('aria-selected', 'true')
expect(screen.getByRole('status', { name: 'site theme' })).toHaveTextContent(PRESETS[0].id) // The site keeps its default theme (amber) even though the draft loaded Classic.
expect(screen.getByRole('status', { name: 'site theme' })).toHaveTextContent('amber')
expect(window.localStorage.getItem('lv_theme')).toBeNull() expect(window.localStorage.getItem('lv_theme')).toBeNull()
}) })

View file

@ -21,7 +21,7 @@ afterEach(() => {
test('a user pick persists to lv_theme, re-accents the page and locks out demo switches', () => { test('a user pick persists to lv_theme, re-accents the page and locks out demo switches', () => {
render(<ActiveThemeProvider><Probe /></ActiveThemeProvider>) render(<ActiveThemeProvider><Probe /></ActiveThemeProvider>)
expect(screen.getByTestId('id')).toHaveTextContent('classic') expect(screen.getByTestId('id')).toHaveTextContent('amber')
act(() => screen.getByText('demo').click()) act(() => screen.getByText('demo').click())
expect(screen.getByTestId('id')).toHaveTextContent('sunset') expect(screen.getByTestId('id')).toHaveTextContent('sunset')
expect(window.localStorage.getItem('lv_theme')).toBeNull() expect(window.localStorage.getItem('lv_theme')).toBeNull()
@ -47,7 +47,7 @@ test('restores the stored theme and survives blocked storage', () => {
throw new Error('blocked') throw new Error('blocked')
}) })
render(<ActiveThemeProvider><Probe /></ActiveThemeProvider>) render(<ActiveThemeProvider><Probe /></ActiveThemeProvider>)
expect(screen.getByTestId('id')).toHaveTextContent('classic') expect(screen.getByTestId('id')).toHaveTextContent('amber')
act(() => screen.getByText('user').click()) act(() => screen.getByText('user').click())
expect(screen.getByTestId('id')).toHaveTextContent('nitro') expect(screen.getByTestId('id')).toHaveTextContent('nitro')
vi.restoreAllMocks() vi.restoreAllMocks()

View file

@ -3,7 +3,9 @@ import { PRESETS } from './presets.generated'
import { parseArgb, type ThemeEntry, withAlpha } from './types' import { parseArgb, type ThemeEntry, withAlpha } from './types'
const STORAGE_KEY = 'lv_theme' const STORAGE_KEY = 'lv_theme'
const FALLBACK = PRESETS[0] // Amber is the site default for visitors who never picked a theme; Classic stays at PRESETS[0]
// (mirroring the mod's own default) and is only reached if the amber preset ever goes missing.
const FALLBACK = PRESETS.find((p) => p.id === 'amber') ?? PRESETS[0]
type ActiveTheme = { type ActiveTheme = {
theme: ThemeEntry theme: ThemeEntry
@ -66,7 +68,7 @@ export function ActiveThemeProvider({ children, applyToPage = true }: { children
return createElement(ActiveThemeContext.Provider, { value }, children) return createElement(ActiveThemeContext.Provider, { value }, children)
} }
/** The active ClickGui theme. Outside a provider it falls back to Classic with a no-op setter. */ /** The active ClickGui theme. Outside a provider it falls back to the default with a no-op setter. */
export function useActiveTheme(): ActiveTheme { export function useActiveTheme(): ActiveTheme {
return useContext(ActiveThemeContext) ?? { theme: FALLBACK, userPicked: false, setTheme: () => {} } return useContext(ActiveThemeContext) ?? { theme: FALLBACK, userPicked: false, setTheme: () => {} }
} }

View file

@ -9,8 +9,9 @@
--color-shoal: #1f3238; --color-shoal: #1f3238;
--color-frost: #9fb3b0; --color-frost: #9fb3b0;
--color-snow: #eef5f3; --color-snow: #eef5f3;
--color-ice: #34d399;
--color-ember: #e8835a; --color-ember: #e8835a;
/* Matches the Amber preset (#FFE69A43) so first paint agrees with the default theme. */
--color-ice: #e69a43;
--font-display: "Unbounded Variable", system-ui, sans-serif; --font-display: "Unbounded Variable", system-ui, sans-serif;
--font-sans: "Onest Variable", system-ui, sans-serif; --font-sans: "Onest Variable", system-ui, sans-serif;
@ -29,12 +30,12 @@
@property --color-ice { @property --color-ice {
syntax: "<color>"; syntax: "<color>";
inherits: true; inherits: true;
initial-value: #34d399; initial-value: #e69a43;
} }
@property --page-accent-glow { @property --page-accent-glow {
syntax: "<color>"; syntax: "<color>";
inherits: true; inherits: true;
initial-value: rgb(52 211 153 / 0.35); initial-value: rgb(230 154 67 / 0.35);
} }
@layer base { @layer base {

View file

@ -4,7 +4,7 @@ import { VoxelScene } from '@features/landing/effects/VoxelScene'
import { SlotGrid } from '@features/configs/components/SlotGrid' import { SlotGrid } from '@features/configs/components/SlotGrid'
import { PageTitle } from '@shared/layout/PageTitle' import { PageTitle } from '@shared/layout/PageTitle'
const ACCENT = 'rgb(52, 211, 153)' const ACCENT = 'rgb(230, 154, 67)'
export default function ConfigsPage() { export default function ConfigsPage() {
const { t } = useTranslation('configs') const { t } = useTranslation('configs')

View file

@ -3,7 +3,7 @@ import { ParticleField } from '@features/landing/effects/ParticleField'
import { VoxelScene } from '@features/landing/effects/VoxelScene' import { VoxelScene } from '@features/landing/effects/VoxelScene'
import { ListingDetail } from '@features/showcase/components/ListingDetail' import { ListingDetail } from '@features/showcase/components/ListingDetail'
const ACCENT = 'rgb(52, 211, 153)' const ACCENT = 'rgb(230, 154, 67)'
export default function ListingPage() { export default function ListingPage() {
const { id } = useParams<{ id: string }>() const { id } = useParams<{ id: string }>()

View file

@ -4,7 +4,7 @@ import { VoxelScene } from '@features/landing/effects/VoxelScene'
import { ListingList } from '@features/showcase/components/ListingList' import { ListingList } from '@features/showcase/components/ListingList'
import { PageTitle } from '@shared/layout/PageTitle' import { PageTitle } from '@shared/layout/PageTitle'
const ACCENT = 'rgb(52, 211, 153)' const ACCENT = 'rgb(230, 154, 67)'
export default function ShowcasePage() { export default function ShowcasePage() {
const { t } = useTranslation('showcase') const { t } = useTranslation('showcase')

View file

@ -2,7 +2,7 @@ import { LOGO_ART } from './art.generated'
export function printConsoleGreeting(lang: string): void { export function printConsoleGreeting(lang: string): void {
const ru = lang.startsWith('ru') const ru = lang.startsWith('ru')
console.log(`%c${LOGO_ART}`, 'color:#34d399;font-family:monospace;font-weight:bold') console.log(`%c${LOGO_ART}`, 'color:#e69a43;font-family:monospace;font-weight:bold')
console.log( console.log(
`%c${ru ? 'Открыл DevTools? Значит, тебе тут рады.' : 'Opened DevTools? You are welcome here.'}`, `%c${ru ? 'Открыл DevTools? Значит, тебе тут рады.' : 'Opened DevTools? You are welcome here.'}`,
'color:#eef5f3;font-size:13px', 'color:#eef5f3;font-size:13px',