-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbase.css
More file actions
366 lines (329 loc) · 16.2 KB
/
Copy pathbase.css
File metadata and controls
366 lines (329 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/* Base layer: retarget Discourse's own variables at DFP tokens.
*
* This file does more visual work than every component file combined, and it
* is worth understanding why.
*
* Discourse's stylesheets — 26 plugin sheets plus six theme components on this
* instance — are written against a small set of custom properties. Reassigning
* those properties restyles hundreds of rules we never had to write a selector
* for, and it keeps working when Discourse refactors its markup. Writing our
* own selector for each of those rules would be both larger and more brittle.
*
* Cascade note: DFP styles are deliberately UNLAYERED. Discourse's CSS is
* unlayered too, and an @layer always loses to unlayered rules regardless of
* specificity. Putting a design system in a cascade layer here would be the
* one change guaranteed to make none of it apply.
*/
html[data-dfp] {
/* Discourse dark schemes use --primary for TEXT and --secondary for
* BACKGROUND, which reads backwards until you have been bitten by it once. */
--primary: var(--dfp-text);
--secondary: var(--dfp-bg);
--tertiary: var(--dfp-accent);
--quaternary: var(--dfp-accent);
--highlight: var(--dfp-accent-soft);
--danger: var(--dfp-danger);
--success: var(--dfp-success);
--love: var(--dfp-danger);
/* Derived neutral scale. Discourse composes almost every border, muted
* label and subtle fill out of these. */
--primary-very-low: var(--dfp-surface-1);
--primary-low: var(--dfp-border);
--primary-low-mid: var(--dfp-border-strong);
--primary-medium: var(--dfp-text-3);
--primary-high: var(--dfp-text-2);
--primary-very-high: var(--dfp-text);
--secondary-low: var(--dfp-text-3);
--secondary-medium: var(--dfp-surface-3);
--secondary-high: var(--dfp-surface-2);
--secondary-very-high: var(--dfp-surface-1);
/* Discourse's `dark-light-choose` blends. These are computed by the *server*
* from the forum's own colour scheme, so retargeting the two halves above
* does not touch them — they stay frozen at the forum's values while DFP
* repaints everything underneath.
*
* Measured: `.top-date-string` on /top renders rgb(77,81,104) on our
* background at 2.47:1, and the badge grant list uses `-med` at rgb(120,126,155).
* Both fail even the 3:1 non-text floor.
*
* Safe to map to text colours: across the 34 stylesheets the forum actually
* serves, `-high` has 48 uses and every one is a `color`; `-med` has 168, of
* which the only non-`color` is a select-kit scrollbar thumb, and a mid grey
* is a better thumb than a stale purple anyway. */
--primary-med-or-secondary-high: var(--dfp-text-2);
--primary-med-or-secondary-med: var(--dfp-text-3);
--tertiary-low: var(--dfp-accent-soft);
--tertiary-medium: var(--dfp-accent);
--tertiary-high: var(--dfp-accent);
--tertiary-hover: var(--dfp-accent);
--danger-low: var(--dfp-danger-soft);
--danger-medium: var(--dfp-danger);
--danger-hover: var(--dfp-danger);
--success-low: var(--dfp-success-soft);
--success-medium: var(--dfp-success);
--highlight-low: var(--dfp-accent-soft);
--highlight-medium: var(--dfp-accent-soft);
--highlight-high: var(--dfp-accent);
/* Numeric ramp. Discourse generates --primary-100 … --primary-900 from the
* colour scheme; in a dark scheme they run from near-background to
* near-text, so the mapping is monotonic in the same direction. */
--primary-50: var(--dfp-surface-1);
--primary-100: var(--dfp-surface-2);
--primary-200: var(--dfp-surface-3);
--primary-300: var(--dfp-border);
--primary-400: var(--dfp-border-strong);
--primary-500: var(--dfp-text-3);
--primary-600: var(--dfp-text-3);
--primary-700: var(--dfp-text-2);
--primary-800: var(--dfp-text-2);
--primary-900: var(--dfp-text);
--header_background: var(--dfp-surface-1);
--header_primary: var(--dfp-text);
--hover: var(--dfp-surface-2);
--d-hover: var(--dfp-surface-2);
--d-selected: var(--dfp-surface-3);
--d-content-background: var(--dfp-bg);
--d-sidebar-background: var(--dfp-bg);
--d-border-radius: var(--dfp-r-sm);
--d-border-radius-large: var(--dfp-r-md);
--d-input-border-radius: var(--dfp-r-sm);
--d-button-border-radius: var(--dfp-r-sm);
--d-max-width: var(--dfp-content-max);
--scheme-type: dark;
}
html[data-dfp][data-dfp-theme="light"] {
--scheme-type: light;
}
/* ── Page frame ─────────────────────────────────────────────────────────── */
html[data-dfp] body {
background: var(--dfp-bg);
color: var(--dfp-text);
line-height: var(--dfp-lh-body);
font-size: var(--dfp-fs-md);
letter-spacing: var(--dfp-track-body);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
html[data-dfp] #main-outlet {
max-width: var(--dfp-content-max);
}
/* ── Icons ────────────────────────────────────────────────────────────────
* Discourse paints its SVG sprite icons with a hardcoded `fill` (measured on
* the live site as rgb(123,123,123) in the header, rgb(152,152,152) in
* controls) rather than `currentColor`. The result is that every icon ignores
* the theme and sits at roughly 3:1 against our background — which is why the
* header read as empty at a glance.
*
* Routing fill through currentColor is the single highest-impact rule in this
* file: icons inherit whatever colour their button already has, so hover
* states, disabled states and semantic colours all start working for free. */
html[data-dfp] svg.d-icon,
html[data-dfp] .d-icon {
/* `color: inherit` is the load-bearing half. Discourse sets a hardcoded
* `color` on the icon element itself (measured rgb(123,123,123) in the
* header), so `fill: currentColor` alone resolves right back to that grey.
* Inheriting from the button first is what makes the icon follow the theme,
* and it makes hover and disabled states work for free. */
color: inherit;
fill: currentColor;
}
/* ── Focus ────────────────────────────────────────────────────────────────
* DevForum's focus treatment is inconsistent — some controls show a ring,
* some show nothing. One rule, applied to everything focusable, is both more
* accessible and less code than the status quo. */
html[data-dfp] :focus-visible {
outline: var(--dfp-focus-ring) solid var(--dfp-accent);
outline-offset: 2px;
border-radius: var(--dfp-r-xs);
}
html[data-dfp] :focus:not(:focus-visible) {
outline: none;
}
/* ── Selection ────────────────────────────────────────────────────────────
* Its own pair, generated and asserted in build-tokens.ts. This used to be
* `--dfp-accent-soft` under `--dfp-text` — a chip tint doing a highlighter's
* job. Measured on the shipped hex it sat 0.067 / 0.062 / 0.095 / 0.040Δ from
* surface-2 (dark / dim / black / light), the `<pre>` background people select
* on most, against the 0.13Δ the generator calls perceptible: a dragged
* selection was a faint stain on the dark themes and absent on light. Nothing
* caught it because no assertion covered the pair; the generator now checks it
* the way it checks `<mark>`, plus once more against surface-2. */
html[data-dfp] ::selection {
background: var(--dfp-selection-bg);
color: var(--dfp-selection-fg);
}
/* ── Highlighted text ───────────────────────────────────────────────────────
* `<mark>`, which is what `==this==` compiles to in a post.
*
* Discourse sets `background: var(--highlight)` on it and sets no `color`, so
* the foreground falls through to the browser's UA default `marktext` — BLACK.
* DFP retargets `--highlight` to `--dfp-accent-soft`, a near-black navy built to
* sit *behind* light text. The pair rendered black on dark navy: invisible. DFP
* had no `mark` rule at all, which is why nothing caught it.
*
* Same shape as the `--secondary`-as-a-foreground bug: a variable retargeted for
* one role, consumed in the other. Setting both halves here means neither
* Discourse nor the UA gets to supply the missing one.
*
* Amber rather than the accent, deliberately — `::selection` above takes the
* accent hue, and an author's highlight that looks identical to the reader's
* own selection is unreadable in a different way. */
html[data-dfp] mark {
background: var(--dfp-mark-bg);
color: var(--dfp-mark-fg);
padding: 0.06em 0.28em;
border-radius: var(--dfp-r-xs);
/* Marks inherit the muted colour of a quote or an excerpt otherwise. */
font-weight: inherit;
}
/* A selection *over* a mark has to stay legible too: without this the
* selection's own foreground lands on the amber. */
html[data-dfp] mark::selection {
background: var(--dfp-accent);
color: var(--dfp-on-accent);
}
/* ── Scrollbars ─────────────────────────────────────────────────────────── */
html[data-dfp] * {
scrollbar-color: var(--dfp-border-strong) transparent;
scrollbar-width: thin;
}
/* ── Links ──────────────────────────────────────────────────────────────── */
html[data-dfp] a {
color: var(--dfp-accent);
text-underline-offset: 0.18em;
transition: color var(--dfp-dur-1) var(--dfp-ease);
}
/* ── Typography inside posts ─────────────────────────────────────────────
*
* `:is(.cooked, .d-editor-preview)` on every prose rule from here to the
* image rule, and the reason is the composer preview. It renders the same
* cooked HTML as a post, and only three prose rules in the whole stylesheet
* had a preview twin (pre, pre code, the details box) — so a `>` quote had its
* rail in the post and not in the preview, a table had a header fill in one
* and hairlines in the other, inline code had a chip in one and nothing in
* the other. What you previewed was visibly not what you posted.
*
* `:is()` takes the specificity of its heaviest argument, and both arguments
* are one class, so every rule keeps exactly the weight it had against
* Discourse — the cascade that was bisected against the live site is
* untouched; only the set of elements each rule reaches grows. */
html[data-dfp] :is(.cooked, .d-editor-preview) {
font-size: var(--dfp-fs-md);
line-height: var(--dfp-lh-body);
color: var(--dfp-text-2);
}
html[data-dfp] :is(.cooked, .d-editor-preview) > * {
color: var(--dfp-text-2);
}
html[data-dfp] :is(.cooked, .d-editor-preview) strong,
html[data-dfp] :is(.cooked, .d-editor-preview) b {
color: var(--dfp-text);
font-weight: var(--dfp-fw-semibold);
}
html[data-dfp] :is(.cooked, .d-editor-preview) h1,
html[data-dfp] :is(.cooked, .d-editor-preview) h2,
html[data-dfp] :is(.cooked, .d-editor-preview) h3,
html[data-dfp] :is(.cooked, .d-editor-preview) h4 {
color: var(--dfp-text);
line-height: var(--dfp-lh-tight);
letter-spacing: var(--dfp-track-title);
font-weight: var(--dfp-fw-semibold);
margin-block: var(--dfp-s-5) var(--dfp-s-3);
}
html[data-dfp] :is(.cooked, .d-editor-preview) p {
margin-block: 0 var(--dfp-s-4);
}
/* A bare `>` quote — not the aside.quote a quote-reply produces.
*
* This was surface-1, which is what the post card is too (post.css), so inside
* a plain post the quote had no fill at all and only the rail and the muted
* colour marked it; inside a tinted staff, OP or solution card it read as a
* darker hole. The quote-reply beside it sits at surface-2 with a hairline
* (post.css, "Quotes"), so two kinds of quote in one thread looked like two
* products. Same surface and the same hairline now, and the 3px rail is what
* still says "quoted" rather than "replied to". post.css resets `aside.quote
* blockquote` to transparent and borderless, so the quote-reply and the
* solved-panel excerpt do not pick this up. The shorthand comes first so the
* rail wins its own edge. */
html[data-dfp] :is(.cooked, .d-editor-preview) blockquote {
border: 1px solid var(--dfp-border);
border-inline-start: 3px solid var(--dfp-border-strong);
background: var(--dfp-surface-2);
border-radius: 0 var(--dfp-r-sm) var(--dfp-r-sm) 0;
padding: var(--dfp-s-3) var(--dfp-s-4);
margin-block: var(--dfp-s-4);
color: var(--dfp-text-3);
}
/* ── Tables ──────────────────────────────────────────────────────────────
* Discourse's markdown pipeline wraps every cooked table in `div.md-table`,
* and that wrapper is the scroll container. The table itself used to be
* `display: block; overflow-x: auto` — a scroll container of its own that
* throws away table layout: columns could no longer share the measure, so a
* wide table never wrapped a cell and a narrow one had no width of its own.
* The wrapper scrolls; the table is a table again, sized to its content and
* capped at the column, which is where a three-column comparison belongs. */
html[data-dfp] :is(.cooked, .d-editor-preview) .md-table {
overflow-x: auto;
max-width: 100%;
}
html[data-dfp] :is(.cooked, .d-editor-preview) table {
display: table;
inline-size: max-content;
max-inline-size: 100%;
border-collapse: collapse;
}
/* A table with no wrapper — cooked HTML baked before Discourse added it and
* not rebaked since — keeps the old block-scroll shape, so it still cannot
* push the page sideways. `max-content` capped at 100% then sizes the scroller
* to the table rather than to the column. */
html[data-dfp] :is(.cooked, .d-editor-preview) table:not(.md-table > table) {
display: block;
overflow-x: auto;
}
html[data-dfp] :is(.cooked, .d-editor-preview) table th,
html[data-dfp] :is(.cooked, .d-editor-preview) table td {
border: 1px solid var(--dfp-border);
padding: var(--dfp-s-2) var(--dfp-s-3);
}
/* Surface-3, not surface-2, and a 2px rule under it. The card is surface-1
* and surface-2 sits 0.031Δ above it on dark (#1c1f25 on #14171c) — a header
* fill nobody could see, so a twelve-row benchmark was a grid of hairlines
* with no top. The rule is the half that survives the light theme, where the
* three surfaces are within 0.04Δ of each other and a fill alone would not. */
html[data-dfp] :is(.cooked, .d-editor-preview) table th {
background: var(--dfp-surface-3);
border-block-end: 2px solid var(--dfp-border-strong);
color: var(--dfp-text);
font-weight: var(--dfp-fw-semibold);
text-align: start;
}
/* Striping mixed from the text colour rather than a surface token, for the
* reason post.css gives for the post-chrome hover: in the light theme the
* surfaces are near-white and a surface-based stripe vanishes, while 4% of the
* text colour reads on every theme. 4% is a whisper on purpose — enough to
* follow a row across five columns, not enough to compete with the header. */
html[data-dfp] :is(.cooked, .d-editor-preview) table tbody tr:nth-child(even) td {
background: color-mix(in oklab, var(--dfp-text) 4%, transparent);
}
html[data-dfp] :is(.cooked, .d-editor-preview) img:not(.emoji):not(.avatar) {
border-radius: var(--dfp-r-sm);
max-width: 100%;
height: auto;
}
/* ── Theme "off" ─────────────────────────────────────────────────────────
* Tokens stay defined so JS features that read them keep working, but every
* visual change stands down. This is the setting for someone who wants the
* features and none of the redesign. */
html[data-dfp][data-dfp-theme="off"] {
--primary: initial;
--secondary: initial;
--tertiary: initial;
--quaternary: initial;
--highlight: initial;
--danger: initial;
--success: initial;
--love: initial;
--d-border-radius: initial;
--header_background: initial;
--header_primary: initial;
}