steid

@jamesgill /

steid/styles.css
10.4 KBCode·Blame·Raw
af126c7feat: tailwind theme and the first components25d
1/* Steid's theme.
2 Started from Topcoat's `neutral` theme and retuned; see plans/ui.md.
3
4 The theme is a small set of design tokens. Components refer to tokens only,
5 never to raw colors, so the whole product restyles by editing `:root` and
6 `.dark` below. A component that hardcodes a color is a bug — it will not
7 follow a palette change and it will not adapt to the color scheme.
8
9 --background the page background
10 --surface raised panels: cards, menus, flash messages
11 --foreground text and iconography on the background
12 --muted-foreground secondary text: captions, descriptions, placeholders
13 --primary the emphasis color: primary actions, selection, links
14 --primary-foreground text on a primary-filled surface
15 --success confirmations, and git additions
16 --success-foreground text on a success-filled surface
17 --warning caution, and git modifications
18 --warning-foreground text on a warning-filled surface
19 --destructive destructive actions, errors, and git deletions
20 --destructive-foreground text on a destructive-filled surface
21 --border hairline borders and dividers
22 --ring the focus ring
23 --shadow-xs a subtle shadow for controls: buttons, inputs
24 --shadow-sm a shadow for raised surfaces: cards, menus
25
979134bfeat: code reads as code, in classes rather than colours17h
26 Syntax highlighting adds a --syntax-* family, derived from the same palette and
27 mapped onto the highlighter's classes at the bottom of this file.
28
af126c7feat: tailwind theme and the first components25d
29 Interactive states (hover, press) have no tokens of their own: components
30 derive them by applying the fill or foreground color at reduced opacity,
31 which adapts to both color schemes automatically.
32
33 Steid is dark-first — the layout sets `class="dark"` on <html>. Light mode
34 is supported and secondary. */
35
36@import "tailwindcss";
37
38/* Scan the project's Rust sources for the Tailwind classes they use. This
39 file is installed at the project root, so `./src` is the crate's source. */
40@source "./src/**/*.rs";
41
42/* Dark mode is opted into by putting the `dark` class on an ancestor,
43 typically <html>. */
44@custom-variant dark (&:is(.dark *));
45
46/* Expose the tokens as Tailwind utilities (`bg-background`, `text-primary`,
47 `border-border`, ...). The indirection through plain variables on `:root`
48 and `.dark` is what lets the values swap when the color scheme changes. */
49@theme inline {
50 --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
51 /* Paths, commit hashes, branch names, and code. */
52 --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", monospace;
53 --color-background: var(--background);
54 --color-surface: var(--surface);
55 --color-foreground: var(--foreground);
56 --color-muted-foreground: var(--muted-foreground);
57 --color-primary: var(--primary);
58 --color-primary-foreground: var(--primary-foreground);
59 --color-success: var(--success);
60 --color-success-foreground: var(--success-foreground);
61 --color-warning: var(--warning);
62 --color-warning-foreground: var(--warning-foreground);
63 --color-destructive: var(--destructive);
64 --color-destructive-foreground: var(--destructive-foreground);
65 --color-border: var(--border);
66 --color-ring: var(--ring);
67 --shadow-xs: var(--shadow-xs);
68 --shadow-sm: var(--shadow-sm);
69}
70
71/* Light mode. Secondary, but it should not look like an afterthought. */
72:root {
73 --background: oklch(0.99 0.003 260);
74 --surface: oklch(1 0 0);
75 --foreground: oklch(0.24 0.012 260);
76 --muted-foreground: oklch(0.51 0.014 260);
77 --primary: oklch(0.5 0.18 250);
78 --primary-foreground: oklch(0.99 0.003 260);
79 --success: oklch(0.52 0.15 145);
80 --success-foreground: oklch(0.99 0.01 145);
81 --warning: oklch(0.62 0.14 85);
82 --warning-foreground: oklch(0.99 0.01 85);
83 --destructive: oklch(0.55 0.2 27);
84 --destructive-foreground: oklch(0.99 0.012 27);
85 --border: oklch(0.9 0.008 260);
86 --ring: oklch(0.5 0.18 250);
87 --shadow-xs: 0 1px 2px oklch(0.24 0.02 260 / 12%);
88 --shadow-sm:
89 0 1px 2px oklch(0.24 0.02 260 / 10%),
90 0 2px 8px -2px oklch(0.24 0.02 260 / 9%);
979134bfeat: code reads as code, in classes rather than colours17h
91
92 --syntax-comment: oklch(0.55 0.015 260);
93 --syntax-punctuation: oklch(0.46 0.015 260);
94 --syntax-variable: oklch(0.3 0.014 260);
95 --syntax-type: oklch(0.45 0.09 195);
96 --syntax-function: oklch(0.48 0.15 250);
97 --syntax-constant: oklch(0.5 0.12 62);
98 --syntax-string: oklch(0.45 0.12 150);
99 --syntax-keyword: oklch(0.45 0.17 300);
100 --syntax-heading: oklch(0.42 0.16 250);
af126c7feat: tailwind theme and the first components25d
101}
102
103/* Dark mode. The primary surface for this product. */
104.dark {
105 --background: oklch(0.16 0.02 260);
106 --surface: oklch(0.2 0.02 260);
107 --foreground: oklch(0.9 0.01 260);
108 --muted-foreground: oklch(0.66 0.014 260);
109 --primary: oklch(0.65 0.15 250);
110 --primary-foreground: oklch(0.16 0.02 260);
111 --success: oklch(0.7 0.17 145);
112 --success-foreground: oklch(0.16 0.02 145);
113 --warning: oklch(0.8 0.15 85);
114 --warning-foreground: oklch(0.16 0.02 85);
115 --destructive: oklch(0.65 0.2 25);
116 --destructive-foreground: oklch(0.14 0.02 25);
117 --border: oklch(0.3 0.012 260);
118 --ring: oklch(0.65 0.15 250);
119 /* Light-mode shadows disappear on a dark background, so these are deeper. */
120 --shadow-xs: 0 1px 2px oklch(0 0 0 / 35%);
121 --shadow-sm: 0 1px 2px oklch(0 0 0 / 35%), 0 2px 8px -2px oklch(0 0 0 / 30%);
979134bfeat: code reads as code, in classes rather than colours17h
122
123 --syntax-comment: oklch(0.6 0.014 260);
124 --syntax-punctuation: oklch(0.72 0.014 260);
125 --syntax-variable: oklch(0.86 0.02 260);
126 --syntax-type: oklch(0.82 0.08 195);
127 --syntax-function: oklch(0.78 0.11 240);
128 --syntax-constant: oklch(0.82 0.11 70);
129 --syntax-string: oklch(0.78 0.12 150);
130 --syntax-keyword: oklch(0.75 0.12 300);
131 --syntax-heading: oklch(0.75 0.13 250);
af126c7feat: tailwind theme and the first components25d
132}
133
134@layer base {
135 body {
136 @apply bg-background font-sans text-foreground;
137 }
138}
979134bfeat: code reads as code, in classes rather than colours17h
139
140/* Syntax highlighting.
141 `infrastructure/highlight.rs` emits classes, never inline colors, so the theme
142 lives here with everything else and follows the color scheme. Tailwind never sees
143 these class names — the markup is generated, not written in `src/` — so this is
144 plain CSS against the tokens above, as ui.md requires.
145
146 The classes are the *atoms* of a TextMate scope, flattened and prefixed: the scope
147 `storage.type.function.rust` becomes `class="hl-storage hl-type hl-function hl-rust"`.
148 Atoms are therefore not positional — `hl-function` is on `meta.function`, which
149 wraps a whole function body, as well as on the name itself — so a single-atom
150 selector is only safe for an atom that leads its scope. The rest are spelled out as
151 the compound they really are.
152
153 Order is load-bearing. Spans nest, so an inner span with two matching rules of equal
154 specificity takes the later one; `punctuation.definition.string` carries both
155 `hl-punctuation` and `hl-string`, and a quote should read as its string. Weakest
156 first, strongest last. */
157
158.hl-punctuation,
159.hl-keyword.hl-operator {
160 color: var(--syntax-punctuation);
161}
162
163.hl-variable,
164.hl-entity.hl-other.hl-attribute-name {
165 color: var(--syntax-variable);
166}
167
168.hl-entity.hl-name,
169.hl-support.hl-function {
170 color: var(--syntax-function);
171}
172
173.hl-entity.hl-name.hl-type,
174.hl-entity.hl-name.hl-class,
175.hl-entity.hl-name.hl-struct,
176.hl-entity.hl-name.hl-enum,
177.hl-entity.hl-name.hl-trait,
178.hl-entity.hl-name.hl-namespace,
179.hl-support.hl-type,
180.hl-support.hl-class {
181 color: var(--syntax-type);
182}
183
184.hl-constant,
185.hl-support.hl-constant {
186 color: var(--syntax-constant);
187}
188
189.hl-storage,
190.hl-keyword,
191.hl-entity.hl-name.hl-tag {
192 color: var(--syntax-keyword);
193}
194
195.hl-string {
196 color: var(--syntax-string);
197}
198
199/* Markdown and other markup: the source is read as prose, so structure carries the
200 color and the body stays at foreground. */
201.hl-markup.hl-heading {
202 color: var(--syntax-heading);
203 font-weight: 600;
204}
205
206.hl-markup.hl-raw {
207 color: var(--syntax-string);
208}
209
210.hl-markup.hl-underline.hl-link {
211 color: var(--syntax-function);
212}
213
214.hl-markup.hl-bold {
215 font-weight: 600;
216}
217
218.hl-markup.hl-italic {
219 font-style: italic;
220}
221
222/* Last, so a comment is a comment whatever it contains. */
223.hl-comment {
224 color: var(--syntax-comment);
225}
226
227.hl-invalid {
228 color: var(--destructive);
229}
2268309feat: a commit is a page, and two revisions can be compared17h
230
231/* Unified diff rows.
232
233 Plain CSS rather than Tailwind utilities because these are theme tokens at
234 low alpha, and `color-mix` against a custom property is not something a
235 utility class can express. The alpha is the point: a tint has to sit under
236 text that stays readable in both color schemes, which a solid `--success`
237 or `--destructive` would not.
238
239 Never a raw color here — every value is a token mixed with transparency, so
240 the diff follows a palette change like everything else. The gutters take a
241 stronger mix than the line so the numbers stay legible against it. */
242.diff-line-add {
243 background-color: color-mix(in oklab, var(--success) 12%, transparent);
244}
245
246.diff-line-del {
247 background-color: color-mix(in oklab, var(--destructive) 12%, transparent);
248}
249
250.diff-gutter-add {
251 background-color: color-mix(in oklab, var(--success) 20%, transparent);
252}
253
254.diff-gutter-del {
255 background-color: color-mix(in oklab, var(--destructive) 20%, transparent);
256}
257
258/* A hunk header separates two parts of a file, so it reads as a rule rather
259 than as a change: no tint, a hairline above it, and muted text. */
260.diff-line-hunk td {
261 border-top: 1px solid var(--border);
262 padding-top: 0.25rem;
263 padding-bottom: 0.25rem;
264}
265
266.diff-line-hunk:first-child td {
267 border-top: 0;
268}
f42a185feat: a file read by who last changed each line, and a way between the two views17h
269
270/* Blame's age tint.
271
272 A hairline down the left of each run of lines, its opacity scaled by how
273 recently that commit touched the file relative to the file's own oldest and
274 newest. Five steps, deliberately faint: it should read as texture telling you
275 which end of a file is moving, not as a heat map with values to read off it.
276
277 Plain CSS rather than Tailwind because the colour is the primary token at a
278 fraction of an alpha, which is not a utility — and `color-mix` keeps it a
279 token, so it follows a palette change and both colour schemes. */
280.blame-age {
281 border-left: 2px solid transparent;
282}
283.blame-age-0 {
284 border-left-color: color-mix(in oklab, var(--primary) 5%, transparent);
285}
286.blame-age-1 {
287 border-left-color: color-mix(in oklab, var(--primary) 9%, transparent);
288}
289.blame-age-2 {
290 border-left-color: color-mix(in oklab, var(--primary) 14%, transparent);
291}
292.blame-age-3 {
293 border-left-color: color-mix(in oklab, var(--primary) 21%, transparent);
294}
295.blame-age-4 {
296 border-left-color: color-mix(in oklab, var(--primary) 30%, transparent);
297}