feat: settings become five groups that state their own answer

The panel was one open block of everything in two columns. That reads fine at
four games and stops working at eighteen: the game list alone is eighteen rows
of four controls each, and it sat above the checkbox somebody had scrolled down
here to tick. Nothing was findable and everything was in the way.

Collapsing it into groups is only half an answer, though — a group showing
nothing but its name turns "what is my region set to?" into a click, which
trades one kind of friction for another. So each summary carries its group's
current state: "Europe · Dark", "17 of 18 on · A–Z", "plus finished, not
started". Closed, the panel is a five-line report of how the app is configured;
opening one is for changing an answer rather than reading it. The states are
derived from `prefs` at render, never stored, so they cannot drift from the
controls they describe.

Native `<details>`, for the reason the reorder arrows are ordinary buttons:
keyboard and screen reader reach it with no second implementation. `summary` is
not an `a`, `button` or `[tabindex]`, so it needed its own focus-visible rule —
the shared one does not reach it.

Two things fell out along the way. The region and appearance rows had no
accessible name at all, so a screen reader read six unlabelled buttons in a row;
they and the board's split pills are one `PillGroup` now, with the `role="group"`
the board's own controls already carry. And the empty states that point at
"Show events that haven't started" now name the group holding it, which is what
makes shipping the groups closed safe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5 committed 2026-08-20 06:29:28 +02:00
1 parent 14f6f2f0dc
commit fb390c1af8
8 files changed
+498 -215

No files matched your search

+39
View File
@@ -423,6 +423,45 @@ body {
border-radius: 3px;
}
/* ---- Settings: collapsible groups -------------------------------------- */
/*
* Native `<details>`, so the disclosure is keyboard- and screen-reader-reachable
* without a second implementation of the same interaction — the argument the
* game-order arrows already make. Only three things need CSS. The browser's own
* marker cannot be placed and differs between engines, so it goes and the
* summary draws its own chevron; and `summary` is not an `a`, a `button` or a
* `[tabindex]`, so the focus rule above does not reach it and the one control
* that opens a group would have no visible focus at all.
*/
.settings-group > summary {
list-style: none;
cursor: pointer;
}
.settings-group > summary::-webkit-details-marker {
display: none;
}
.settings-group > summary:focus-visible {
outline: 2px solid var(--color-near);
outline-offset: 2px;
border-radius: 3px;
}
.settings-chevron {
transition: transform 150ms ease;
}
.settings-group[open] > summary .settings-chevron {
transform: rotate(90deg);
}
@media (prefers-reduced-motion: reduce) {
.settings-chevron {
transition: none;
}
}
/* Horizontal scrollers keep their gutter but hide the bar chrome. */
.scroll-x {
overflow-x: auto;