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:
parent
8a758754dd
commit
3f679b83f6
8 changed files with 16 additions and 12 deletions
|
|
@ -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()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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: () => {} }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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 }>()
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue