Skip to content

Commit 7642b09

Browse files
committed
refactor(website): sweep the last two duplicated theme palettes
The layout and .ui-preview were not the only places writing the dark half twice. The syntax-highlight classes in public/input.css repeated five colours across both theme selectors, and the home page's code-sample tokens repeated three. Same collapse onto light-dark(). Replace the per-file assertion with a sweep over every stylesheet and every page, layout, and component that writes CSS, asserting the rule rather than the two known sites: no colour may be declared under a per-theme selector, only the three non-colour tokens may. A per-file check is what let these two survive the first pass. Docs code samples are excluded, since a pre block teaching the reader is not this site's own styling. Re-verified against main in a real browser after the change: 360 resolved readings across the six theme states, now covering the highlight classes and the code tokens as well, zero differences.
1 parent 9d1f1b6 commit 7642b09

3 files changed

Lines changed: 68 additions & 27 deletions

File tree

website/app/page.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,12 @@ export default function LandingPage() {
125125
--editor-fg: var(--fg);
126126
--editor-gutter-fg: var(--fg-subtle);
127127
--editor-gutter-border: var(--border);
128-
--code-tag: oklch(0.55 0.13 250);
129-
--code-attr: oklch(0.52 0.16 150);
130-
--code-str: oklch(0.55 0.13 145);
128+
--code-tag: light-dark(oklch(0.55 0.13 250), oklch(0.78 0.13 250));
129+
--code-attr: light-dark(oklch(0.52 0.16 150), oklch(0.66 0.16 150));
130+
--code-str: light-dark(oklch(0.55 0.13 145), oklch(0.80 0.15 145));
131131
--code-text: var(--fg);
132132
--code-punc: var(--fg-muted);
133133
}
134-
:root[data-theme='dark'] {
135-
--code-tag: oklch(0.78 0.13 250); --code-attr: oklch(0.66 0.16 150); --code-str: oklch(0.80 0.15 145);
136-
}
137-
@media (prefers-color-scheme: dark) {
138-
:root:not([data-theme='light']) { --code-tag: oklch(0.78 0.13 250); --code-attr: oklch(0.66 0.16 150); --code-str: oklch(0.80 0.15 145); }
139-
}
140134
/* Syntax-highlight token colors (.t-kw / .t-str / ...) are defined
141135
globally in public/input.css so every surface (this page and the
142136
blog code fences) shares one palette. */

website/public/input.css

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -209,24 +209,14 @@
209209
* one tokenizer, so the palette is defined once here, globally, rather than
210210
* inline per page). Class names: t-com/t-str/t-kw/t-fn/t-type/t-num/t-punc/t-id.
211211
*/
212+
/* One light-dark() per token, same rule as the palettes above: the dark half
213+
used to be written twice, once per theme selector. .t-com / .t-punc / .t-id
214+
read palette tokens that already carry their own pair. */
212215
.t-com { color: var(--fg-subtle); font-style: italic; }
213-
.t-str { color: oklch(0.52 0.13 150); }
214-
.t-kw { color: oklch(0.52 0.16 295); font-weight: 600; }
215-
.t-fn { color: oklch(0.52 0.15 250); }
216-
.t-type{ color: oklch(0.52 0.10 200); }
217-
.t-num { color: oklch(0.55 0.12 215); }
216+
.t-str { color: light-dark(oklch(0.52 0.13 150), oklch(0.80 0.14 150)); }
217+
.t-kw { color: light-dark(oklch(0.52 0.16 295), oklch(0.76 0.14 295)); font-weight: 600; }
218+
.t-fn { color: light-dark(oklch(0.52 0.15 250), oklch(0.75 0.13 250)); }
219+
.t-type{ color: light-dark(oklch(0.52 0.10 200), oklch(0.80 0.10 200)); }
220+
.t-num { color: light-dark(oklch(0.55 0.12 215), oklch(0.82 0.12 215)); }
218221
.t-punc{ color: var(--fg-muted); }
219222
.t-id { color: var(--fg); }
220-
221-
:root[data-theme='dark'] .t-str { color: oklch(0.80 0.14 150); }
222-
:root[data-theme='dark'] .t-kw { color: oklch(0.76 0.14 295); }
223-
:root[data-theme='dark'] .t-fn { color: oklch(0.75 0.13 250); }
224-
:root[data-theme='dark'] .t-type{ color: oklch(0.80 0.10 200); }
225-
:root[data-theme='dark'] .t-num { color: oklch(0.82 0.12 215); }
226-
@media (prefers-color-scheme: dark) {
227-
:root:not([data-theme='light']) .t-str { color: oklch(0.80 0.14 150); }
228-
:root:not([data-theme='light']) .t-kw { color: oklch(0.76 0.14 295); }
229-
:root:not([data-theme='light']) .t-fn { color: oklch(0.75 0.13 250); }
230-
:root:not([data-theme='light']) .t-type{ color: oklch(0.80 0.10 200); }
231-
:root:not([data-theme='light']) .t-num { color: oklch(0.82 0.12 215); }
232-
}

website/test/ssr/kit-surfaces.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,60 @@ test('.ui-preview keeps no duplicate dark block', () => {
5555
'.ui-preview resolves its theme through inherited color-scheme, not a per-theme selector',
5656
);
5757
});
58+
59+
/**
60+
* The general rule, swept across every stylesheet and every page/layout that
61+
* writes CSS. The two palettes above are the ones that were duplicated worst,
62+
* but they were not the only ones: the syntax-highlight classes in input.css
63+
* and the home page's code-sample tokens each carried the same pair of
64+
* verbatim dark blocks. A per-file assertion would have kept missing them, so
65+
* this asserts the rule itself.
66+
*/
67+
test('no colour is declared under a per-theme selector anywhere', () => {
68+
const files = [
69+
'public/input.css',
70+
...['app', 'lib', 'components'].flatMap(function walk(dir: string): string[] {
71+
const abs = resolve(ROOT, dir);
72+
if (!existsSync(abs)) return [];
73+
return readdirSync(abs, { withFileTypes: true }).flatMap((e) =>
74+
e.isDirectory() ? walk(`${dir}/${e.name}`) : e.name.endsWith('.ts') ? [`${dir}/${e.name}`] : [],
75+
);
76+
}),
77+
];
78+
79+
// Only these three NON-colour tokens may sit under a theme selector. Every
80+
// colour belongs in a light-dark() pair instead.
81+
const ALLOWED = new Set(['--glow-strength', '--cta-mix', '--shadow-spread']);
82+
const offenders: string[] = [];
83+
84+
for (const rel of files) {
85+
// Comments are stripped so prose naming a selector is not read as a rule,
86+
// and `pre` blocks in the docs pages are left alone: those are code
87+
// SAMPLES teaching the reader, not this site's own styling.
88+
const src = readFileSync(resolve(ROOT, rel), 'utf8')
89+
.replace(/\/\*[\s\S]*?\*\//g, '')
90+
.replace(/<pre[\s\S]*?<\/pre>/g, '');
91+
// Terminate on the first closing brace, NOT on one at the start of a line:
92+
// a single-line rule (`:root[data-theme='dark'] .t-str { color: ... }`) is
93+
// the exact shape the highlight classes used, and anchoring to a newline
94+
// walked straight past it.
95+
const re = /(?:@media\s*\(prefers-color-scheme:\s*dark\)|\[data-theme=['"]dark['"]\])([\s\S]{0,900}?)\}/g;
96+
for (const m of src.matchAll(re)) {
97+
for (const d of m[1].matchAll(/(--[a-z-]+):\s*([^;]+);/g)) {
98+
if (ALLOWED.has(d[1])) continue;
99+
offenders.push(`${rel}: ${d[1]}`);
100+
}
101+
// A bare colour on a class inside a theme block (the .t-* highlight
102+
// shape) has no custom property to catch, so look for it directly.
103+
for (const d of m[1].matchAll(/\bcolor:\s*(oklch|#|rgb|hsl)/g)) {
104+
offenders.push(`${rel}: a bare ${d[1]} colour`);
105+
}
106+
}
107+
}
108+
109+
assert.deepEqual(
110+
offenders,
111+
[],
112+
'these belong in a light-dark(LIGHT, DARK) pair, not under a per-theme selector',
113+
);
114+
});

0 commit comments

Comments
 (0)