feat: offer the theme in settings, beside the region

Both are "how do I read this?" rather than what the page knows, so they
sit together. Dark reads first because it is what the control is being
offered from, System last because it defers rather than decides.

Switching saves nothing and costs nothing: no reload, and nothing marked,
typed or ticked is touched.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-18 23:30:41 +02:00
co-authored by Claude Opus 5
parent 3ad74df5ea
commit eca5ad7a0a
+37
View File
@@ -2,6 +2,7 @@ import type { LaneId } from "../../shared/custom.ts";
import type { Region } from "../../shared/schema.ts"; import type { Region } from "../../shared/schema.ts";
import { REGION_LABEL } from "../../shared/time.ts"; import { REGION_LABEL } from "../../shared/time.ts";
import { useGameMeta } from "../state/gameMeta.tsx"; import { useGameMeta } from "../state/gameMeta.tsx";
import type { ThemeChoice } from "../state/theme.ts";
import type { Prefs } from "../state/usePrefs.ts"; import type { Prefs } from "../state/usePrefs.ts";
import { YourOwn } from "./YourOwn.tsx"; import { YourOwn } from "./YourOwn.tsx";
@@ -9,6 +10,17 @@ const REGIONS: Array<{ id: Region; label: string }> = (
["america", "europe", "asia"] as const ["america", "europe", "asia"] as const
).map((id) => ({ id, label: REGION_LABEL[id] })); ).map((id) => ({ id, label: REGION_LABEL[id] }));
/**
* Dark first, because that is what the app is and what this control is offered
* *from*; `System` last, because it is the answer that defers rather than
* decides.
*/
const THEMES: Array<{ id: ThemeChoice; label: string }> = [
{ id: "dark", label: "Dark" },
{ id: "light", label: "Light" },
{ id: "system", label: "System" },
];
export function Controls({ export function Controls({
games, games,
prefs, prefs,
@@ -86,6 +98,31 @@ export function Controls({
</div> </div>
</div> </div>
{/* Next to the region rather than off in a corner: both are
"how do I read this?", and neither changes what the page
knows. Switching is instant and costs nothing — no reload, and
nothing marked, typed or ticked is touched. */}
<div>
<p className="eyebrow">Appearance</p>
<div className="mt-2 flex gap-1.5">
{THEMES.map((t) => (
<button
key={t.id}
type="button"
onClick={() => onUpdate({ theme: t.id })}
aria-pressed={prefs.theme === t.id}
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
prefs.theme === t.id
? "border-ink/70 text-ink"
: "border-hairline text-faint hover:text-muted"
}`}
>
{t.label}
</button>
))}
</div>
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="flex cursor-pointer select-none items-center gap-2 text-xs text-muted"> <label className="flex cursor-pointer select-none items-center gap-2 text-xs text-muted">
<input <input