feat(look): en akse mere — hvordan det læses, ikke hvor lyst det er

Fire udseender ved siden af de fem temaer: Verdande, Rolig (serif, mere luft),
Tæt (flere rækker, mindre luft) og Terminal (én bredde til det hele). De ganges ud
til tyve udseender fra otte små blokke CSS frem for at kræve en blok hver.

To akser, fordi det er to spørgsmål. Den, der vil have en serif om natten, skal
ikke opgive den mørke palet for at få den — og prøven holder netop det fast: at
vælge en skrift må ikke trække temaet med sig.

Udseendet sættes før første optegning, ligesom temaet. Ellers bliver siden sat op
i én skrift og derefter i en anden, og det er et synligt spring.

Det er også det ærlige svar på at ligne noget andet. Standarden er bevidst
genkendelig — det var det, der blev bedt om — men et program med ét muligt
udseende har lånt en form, det ikke kan give tilbage. Med udseender er ligheden en
indstilling blandt flere frem for det eneste, det kan være.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-19 17:07:39 +02:00
co-authored by Claude Opus 5
parent 1bbae698d5
commit 90bdf4b725
7 changed files with 235 additions and 1 deletions
+39
View File
@@ -844,6 +844,45 @@ export const THEMES = [
{ id: 'contrast', name: 'theme.contrast', note: 'theme.contrastNote', dark: false }
];
/**
* The looks: how it reads, as opposed to how bright it is.
*
* Separate from the themes because they are separate questions. Somebody who
* wants a serif face at night should not have to give up the dark palette to get
* one, and the two multiply out to fifteen appearances from eight small blocks of
* CSS rather than needing one block each.
*/
export const LOOKS = [
{ id: 'verdande', name: 'look.verdande', note: 'look.verdandeNote' },
{ id: 'rolig', name: 'look.rolig', note: 'look.roligNote' },
{ id: 'taet', name: 'look.taet', note: 'look.taetNote' },
{ id: 'terminal', name: 'look.terminal', note: 'look.terminalNote' }
];
class Look {
current = $state(
typeof document === 'undefined'
? 'verdande'
: (document.documentElement.dataset.look ?? 'verdande')
);
set(id) {
if (!LOOKS.some((l) => l.id === id)) return;
this.current = id;
// The default carries no attribute, so the plain tokens are the plain tokens
// and there is nothing to override them back to.
if (id === 'verdande') delete document.documentElement.dataset.look;
else document.documentElement.dataset.look = id;
try {
localStorage.setItem('verdande:look', id);
} catch {
// Private browsing; the choice simply will not persist.
}
}
}
export const look = new Look();
/** Theme, kept in localStorage and applied to the document element. */
class Theme {
/**