@waveso/docs 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/README.md +490 -75
- package/dist/code-frame.d.ts +29 -0
- package/dist/code-frame.js +41 -0
- package/dist/code-meta.d.ts +48 -0
- package/dist/code-meta.js +72 -0
- package/dist/docs-content-id.d.ts +19 -0
- package/dist/docs-content-id.js +19 -0
- package/dist/docs-error.d.ts +2 -57
- package/dist/docs-error.js +3 -15
- package/dist/errors.d.ts +94 -0
- package/dist/errors.js +45 -0
- package/dist/next.d.ts +153 -28
- package/dist/next.js +65 -33
- package/dist/plugins/rehype-capture-toc.js +26 -5
- package/dist/plugins/rehype-code-frame.d.ts +10 -0
- package/dist/plugins/rehype-code-frame.js +88 -0
- package/dist/plugins/rehype-code-language.js +7 -1
- package/dist/react/code-runtime.d.ts +14 -0
- package/dist/react/code-runtime.js +161 -0
- package/dist/react/doc-content.d.ts +39 -2
- package/dist/react/doc-content.js +42 -10
- package/dist/react/layout.d.ts +44 -0
- package/dist/react/layout.js +65 -0
- package/dist/react/nav.d.ts +28 -0
- package/dist/react/nav.js +70 -0
- package/dist/react/nearest-scroll-top.d.ts +45 -0
- package/dist/react/nearest-scroll-top.js +44 -0
- package/dist/react/next-link.d.ts +34 -0
- package/dist/react/next-link.js +30 -0
- package/dist/react/next-nav.d.ts +11 -0
- package/dist/react/next-nav.js +32 -0
- package/dist/react/next-search.d.ts +22 -0
- package/dist/react/next-search.js +52 -0
- package/dist/react/search-dialog.d.ts +20 -8
- package/dist/react/search-dialog.js +15 -10
- package/dist/react/shell-labels.d.ts +43 -0
- package/dist/react/shell-labels.js +27 -0
- package/dist/react/sidebar.d.ts +38 -3
- package/dist/react/sidebar.js +104 -12
- package/dist/react/skip-link.d.ts +1 -9
- package/dist/react/skip-link.js +6 -5
- package/dist/react/toc.d.ts +12 -4
- package/dist/react/toc.js +18 -7
- package/dist/react/youtube.d.ts +31 -5
- package/dist/react/youtube.js +76 -54
- package/dist/render.d.ts +35 -1
- package/dist/render.js +35 -14
- package/dist/route-path.d.ts +46 -0
- package/dist/route-path.js +51 -0
- package/dist/search-index.d.ts +6 -23
- package/dist/search-index.js +6 -51
- package/dist/sitemap-limit.d.ts +34 -0
- package/dist/sitemap-limit.js +37 -0
- package/dist/source.d.ts +1 -23
- package/dist/source.js +40 -43
- package/dist/styles.css +939 -93
- package/dist/types.d.ts +11 -2
- package/package.json +58 -23
package/dist/styles.css
CHANGED
|
@@ -110,18 +110,67 @@
|
|
|
110
110
|
--wave-docs-callout-caution: oklch(0.55 0.2 24);
|
|
111
111
|
--wave-docs-callout-caution-bg: oklch(0.97 0.025 24);
|
|
112
112
|
|
|
113
|
+
/* The table's "there is more this way" shadow.
|
|
114
|
+
*
|
|
115
|
+
* A token, not a literal: it was a hardcoded `oklch(0 0 0 / 0.12)`, and
|
|
116
|
+
* black at 12% over a `0.19` background is invisible — so in dark mode the
|
|
117
|
+
* one affordance telling a reader a table scrolls sideways was not there at
|
|
118
|
+
* all. A shadow darkens a light page and lightens a dark one; it cannot be
|
|
119
|
+
* a single colour. */
|
|
120
|
+
--wave-docs-scroll-shadow: oklch(0 0 0 / 0.12);
|
|
121
|
+
/* Elevation, for the one element that floats over the page. Same
|
|
122
|
+
* reasoning as `scroll-shadow`: a black shadow over a dark surface is
|
|
123
|
+
* no shadow, so the dark ramp deepens it rather than reusing it. */
|
|
124
|
+
--wave-docs-shadow: oklch(0 0 0 / 0.3);
|
|
125
|
+
|
|
113
126
|
--wave-docs-radius: 0.5rem;
|
|
114
127
|
--wave-docs-radius-sm: 0.375rem;
|
|
115
|
-
/*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
|
|
128
|
+
/* The shell, per `docs/adr/001-shell-contract.md`. These four plus
|
|
129
|
+
* `--wave-docs-measure` are the whole settable layout surface; the gutter
|
|
130
|
+
* and the drawer width are literals, because each appears once and an
|
|
131
|
+
* ordinary override is already the cleanest tool for them. */
|
|
132
|
+
--wave-docs-header-height: 3.5rem;
|
|
133
|
+
--wave-docs-sidebar-width: 16rem;
|
|
134
|
+
--wave-docs-toc-width: 15rem;
|
|
135
|
+
--wave-docs-shell-width: 100rem;
|
|
136
|
+
|
|
137
|
+
/* How far below the top of the scrollport an anchored heading parks.
|
|
138
|
+
*
|
|
139
|
+
* Derived, not set: it is the sticky header's height plus a rem of air, and
|
|
140
|
+
* the shell's sticky columns offset by the same token — so a host that
|
|
141
|
+
* changes the header height moves all three together. An earlier version of
|
|
142
|
+
* this comment said nothing else in the sheet read it, which stopped being
|
|
143
|
+
* true the moment the layout landed. */
|
|
144
|
+
--wave-docs-scroll-padding: calc(var(--wave-docs-header-height) + 1rem);
|
|
119
145
|
--wave-docs-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo,
|
|
120
146
|
Consolas, "Liberation Mono", monospace;
|
|
121
147
|
|
|
122
|
-
/*
|
|
123
|
-
|
|
124
|
-
|
|
148
|
+
/* The body typeface.
|
|
149
|
+
*
|
|
150
|
+
* A default rather than an inheritance, because inheriting means a host
|
|
151
|
+
* that never set one renders documentation in the UA serif — which is what
|
|
152
|
+
* this package did until now, and which reads as broken rather than as
|
|
153
|
+
* unstyled.
|
|
154
|
+
*
|
|
155
|
+
* A host with its own typeface sets `--wave-docs-font-sans: inherit`, and
|
|
156
|
+
* the whole package follows `body` again. That is one line, and it is the
|
|
157
|
+
* documented opt-out; overriding the family on every element this sheet
|
|
158
|
+
* touches is not. */
|
|
159
|
+
--wave-docs-font-sans: ui-sans-serif, system-ui, -apple-system,
|
|
160
|
+
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
161
|
+
|
|
162
|
+
/* Prose column width.
|
|
163
|
+
*
|
|
164
|
+
* 46rem is 736px at a 16px root, about 83 `ch` in the stack above — wider
|
|
165
|
+
* than a pure-prose measure wants, and deliberately: this column also
|
|
166
|
+
* carries code blocks, API tables and callouts, and narrowing it to suit
|
|
167
|
+
* paragraphs pushes those into horizontal scroll. Without any measure at
|
|
168
|
+
* all, a 1440px viewport produced a ~140-character line.
|
|
169
|
+
*
|
|
170
|
+
* `--wave-docs-measure: none` opts out entirely, for a host whose own
|
|
171
|
+
* shell already constrains the column. See `docs/adr/001-shell-contract.md`
|
|
172
|
+
* — this is one of the five settable layout tokens. */
|
|
173
|
+
--wave-docs-measure: 46rem;
|
|
125
174
|
}
|
|
126
175
|
|
|
127
176
|
/* Only for a host that has explicitly delegated the choice to the OS. */
|
|
@@ -142,6 +191,9 @@
|
|
|
142
191
|
--wave-docs-accent-fg: oklch(0.18 0.02 265);
|
|
143
192
|
--wave-docs-accent-subtle: oklch(0.26 0.06 248);
|
|
144
193
|
|
|
194
|
+
--wave-docs-scroll-shadow: oklch(1 0 0 / 0.16);
|
|
195
|
+
--wave-docs-shadow: oklch(0 0 0 / 0.6);
|
|
196
|
+
|
|
145
197
|
--wave-docs-code-bg: oklch(0.26 0.016 265);
|
|
146
198
|
--wave-docs-code-fg: oklch(0.86 0.04 300);
|
|
147
199
|
--wave-docs-code-block-bg: oklch(0.17 0.015 265);
|
|
@@ -182,6 +234,9 @@
|
|
|
182
234
|
--wave-docs-accent-fg: oklch(0.18 0.02 265);
|
|
183
235
|
--wave-docs-accent-subtle: oklch(0.26 0.06 248);
|
|
184
236
|
|
|
237
|
+
--wave-docs-scroll-shadow: oklch(1 0 0 / 0.16);
|
|
238
|
+
--wave-docs-shadow: oklch(0 0 0 / 0.6);
|
|
239
|
+
|
|
185
240
|
--wave-docs-code-bg: oklch(0.26 0.016 265);
|
|
186
241
|
--wave-docs-code-fg: oklch(0.86 0.04 300);
|
|
187
242
|
--wave-docs-code-block-bg: oklch(0.17 0.015 265);
|
|
@@ -213,6 +268,25 @@
|
|
|
213
268
|
* `src/styles.test.ts` a fact about the shipped page rather than about a
|
|
214
269
|
* background nobody declared.
|
|
215
270
|
*/
|
|
271
|
+
/*
|
|
272
|
+
* ⚠️ `.wave-docs-layout` IS IN THIS LIST, AND IT WAS NOT. Painting only the
|
|
273
|
+
* three content containers left the grid's own gutters — 1.5rem between each
|
|
274
|
+
* column, plus the inline padding — showing whatever the browser paints the
|
|
275
|
+
* canvas, because `body` is deliberately never touched.
|
|
276
|
+
*
|
|
277
|
+
* In light mode that is white against a white token and invisible. In dark
|
|
278
|
+
* mode the UA canvas and `--wave-docs-bg` are two different darks, so the
|
|
279
|
+
* sidebar and the table of contents rendered as slightly lighter panels
|
|
280
|
+
* floating on a darker page. Caught by looking at a screenshot of the real
|
|
281
|
+
* site; every contrast ratio in `styles.test.ts` was correct throughout,
|
|
282
|
+
* because each one was a fact about a container that *was* painted.
|
|
283
|
+
*
|
|
284
|
+
* The grid is a container this package owns, so painting it breaks no rule
|
|
285
|
+
* — `body` still belongs to the host, and a host whose page is a different
|
|
286
|
+
* colour overrides the token rather than fighting a `body` rule they did not
|
|
287
|
+
* write.
|
|
288
|
+
*/
|
|
289
|
+
.wave-docs-layout,
|
|
216
290
|
.wave-docs-prose,
|
|
217
291
|
.wave-docs-sidebar,
|
|
218
292
|
.wave-docs-toc {
|
|
@@ -224,6 +298,28 @@
|
|
|
224
298
|
scroll-padding-top: var(--wave-docs-scroll-padding);
|
|
225
299
|
}
|
|
226
300
|
|
|
301
|
+
/*
|
|
302
|
+
* The typeface, on every root this package owns.
|
|
303
|
+
*
|
|
304
|
+
* Separate from the grounding rule above because these six are not the same
|
|
305
|
+
* set: the dialog is portalled out of the prose and the trigger usually sits
|
|
306
|
+
* in the host's own header, so neither inherits from anything this sheet
|
|
307
|
+
* paints — while the skip link and the trigger have their own backgrounds and
|
|
308
|
+
* must not be handed the page one.
|
|
309
|
+
*
|
|
310
|
+
* Declared at all because inheriting means a host that never set a family
|
|
311
|
+
* renders its documentation in the UA serif. `--wave-docs-font-sans: inherit`
|
|
312
|
+
* gives the host's typeface back in one line.
|
|
313
|
+
*/
|
|
314
|
+
.wave-docs-prose,
|
|
315
|
+
.wave-docs-sidebar,
|
|
316
|
+
.wave-docs-toc,
|
|
317
|
+
.wave-docs-skip-link,
|
|
318
|
+
.wave-docs-search-trigger,
|
|
319
|
+
.wave-docs-search-dialog {
|
|
320
|
+
font-family: var(--wave-docs-font-sans);
|
|
321
|
+
}
|
|
322
|
+
|
|
227
323
|
/*
|
|
228
324
|
* No `scroll-behavior: smooth` anywhere, and not for the usual reason.
|
|
229
325
|
* Next 16 only suppresses smooth scrolling across a route change when
|
|
@@ -279,15 +375,18 @@
|
|
|
279
375
|
* plain `:focus` for the same reason. */
|
|
280
376
|
.wave-docs-skip-link:focus {
|
|
281
377
|
transform: translateY(0);
|
|
282
|
-
/*
|
|
283
|
-
*
|
|
284
|
-
* `outline
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
|
|
378
|
+
/* `outline`, here and at every other focus ring in this file, never
|
|
379
|
+
* `box-shadow`. Forced-colors mode drops `box-shadow` and forces
|
|
380
|
+
* `outline-color` to a system colour, so an outline is repainted for free
|
|
381
|
+
* where a shadow ring had to be restored by hand — which is what a 24-line
|
|
382
|
+
* `forced-colors` block used to do, for all eleven of them, with a test
|
|
383
|
+
* policing the list. An outline also follows the element's own
|
|
384
|
+
* `border-radius`, and shows the real surface through `outline-offset`
|
|
385
|
+
* instead of painting `--wave-docs-bg` as a spacer — which was a lie on the
|
|
386
|
+
* active search result, where the row is `accent-subtle` and the ring drew
|
|
387
|
+
* a white gap across it. */
|
|
388
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
289
389
|
outline-offset: 2px;
|
|
290
|
-
box-shadow: var(--wave-docs-ring);
|
|
291
390
|
}
|
|
292
391
|
|
|
293
392
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -312,47 +411,110 @@
|
|
|
312
411
|
* tests at. `break-word` and not `word-break: break-all`, which would also
|
|
313
412
|
* chop ordinary prose mid-syllable. */
|
|
314
413
|
overflow-wrap: break-word;
|
|
315
|
-
/*
|
|
414
|
+
/* The comment here used to say the docs shell owns column width. No shell
|
|
415
|
+
* shipped, so in practice every consumer's first override was the same
|
|
416
|
+
* container CSS — a default the package should have carried itself. */
|
|
417
|
+
max-width: var(--wave-docs-measure);
|
|
316
418
|
}
|
|
317
419
|
|
|
318
420
|
.wave-docs-prose > * + * {
|
|
319
421
|
margin-block-start: 1.25em;
|
|
320
422
|
}
|
|
321
423
|
|
|
424
|
+
/* `pretty`, not `balance`: balance is for headings, where every line matters
|
|
425
|
+
* and there are two or three of them. On a paragraph it is capped at four
|
|
426
|
+
* lines in most engines anyway; `pretty` is the one that stops a paragraph
|
|
427
|
+
* ending on a single orphaned word. */
|
|
428
|
+
.wave-docs-prose p {
|
|
429
|
+
text-wrap: pretty;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/*
|
|
433
|
+
* A 1.2 minor third, four sizes, six unmistakable levels.
|
|
434
|
+
*
|
|
435
|
+
* The old scale stepped 1.50 / 1.20 / 1.18 / 1.06 / 1.00 — one enormous jump
|
|
436
|
+
* and then four nearly flat ones, so h3, h4 and h5 were interchangeable at a
|
|
437
|
+
* glance. h5 and h6 were the same size *and* weight as body text, differing
|
|
438
|
+
* only in colour: a coloured paragraph, not a level. They are an eyebrow now,
|
|
439
|
+
* which is a different axis (tracking and case) rather than a fifth size
|
|
440
|
+
* nobody can distinguish from the fourth.
|
|
441
|
+
*
|
|
442
|
+
* Leading falls as size rises — 1.15 through 1.5 — because one shared 1.25
|
|
443
|
+
* let the 36px h1 float apart and crowded the 16px h5. Tracking tightens the
|
|
444
|
+
* same way, for the same reason.
|
|
445
|
+
*/
|
|
322
446
|
.wave-docs-prose :is(h1, h2, h3, h4, h5, h6) {
|
|
323
|
-
position: relative;
|
|
324
447
|
color: var(--wave-docs-fg);
|
|
325
448
|
font-weight: 650;
|
|
326
|
-
line-height: 1.25;
|
|
327
449
|
text-wrap: balance;
|
|
328
450
|
scroll-margin-top: var(--wave-docs-scroll-padding);
|
|
329
451
|
}
|
|
330
452
|
|
|
331
|
-
|
|
453
|
+
/* Top margin scales with the size of the break the heading signals, so a new
|
|
454
|
+
* section reads as a bigger pause than a subsection. */
|
|
455
|
+
.wave-docs-prose > h2 {
|
|
456
|
+
margin-block-start: 2.5em;
|
|
457
|
+
}
|
|
458
|
+
.wave-docs-prose > h3 {
|
|
332
459
|
margin-block-start: 2em;
|
|
333
460
|
}
|
|
461
|
+
.wave-docs-prose > :is(h4, h5, h6) {
|
|
462
|
+
margin-block-start: 1.75em;
|
|
463
|
+
}
|
|
464
|
+
/* ...and a heading sits close to what it introduces. */
|
|
465
|
+
.wave-docs-prose :is(h1, h2, h3, h4, h5, h6) + * {
|
|
466
|
+
margin-block-start: 0.75em;
|
|
467
|
+
}
|
|
334
468
|
|
|
335
469
|
.wave-docs-prose h1 {
|
|
336
|
-
|
|
337
|
-
|
|
470
|
+
/* The one fluid size. A fixed 2.0736rem h1 wraps an ordinary page title to
|
|
471
|
+
* three lines at 390px; everything below it stays fixed, because fluid type
|
|
472
|
+
* against a fixed measure means characters-per-line silently drifts and the
|
|
473
|
+
* measure token stops meaning what it says. */
|
|
474
|
+
font-size: clamp(1.75rem, 1.45rem + 1.3vw, 2.0736rem);
|
|
475
|
+
line-height: 1.15;
|
|
476
|
+
letter-spacing: -0.021em;
|
|
477
|
+
font-weight: 680;
|
|
338
478
|
}
|
|
339
479
|
.wave-docs-prose h2 {
|
|
340
|
-
font-size: 1.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
480
|
+
font-size: 1.44rem;
|
|
481
|
+
line-height: 1.25;
|
|
482
|
+
letter-spacing: -0.014em;
|
|
483
|
+
font-weight: 660;
|
|
344
484
|
}
|
|
345
485
|
.wave-docs-prose h3 {
|
|
346
|
-
font-size: 1.
|
|
486
|
+
font-size: 1.2rem;
|
|
487
|
+
line-height: 1.35;
|
|
488
|
+
letter-spacing: -0.008em;
|
|
347
489
|
}
|
|
348
490
|
.wave-docs-prose h4 {
|
|
349
|
-
font-size:
|
|
491
|
+
font-size: 1rem;
|
|
492
|
+
line-height: 1.45;
|
|
350
493
|
}
|
|
494
|
+
/* An eyebrow, not a fifth size: at this depth the distinction has to come
|
|
495
|
+
* from case and tracking, because two more steps of a 1.2 scale would land
|
|
496
|
+
* inside a rounding error of the body text. */
|
|
351
497
|
.wave-docs-prose :is(h5, h6) {
|
|
352
|
-
font-size:
|
|
498
|
+
font-size: 0.875rem;
|
|
499
|
+
line-height: 1.5;
|
|
500
|
+
letter-spacing: 0.02em;
|
|
501
|
+
text-transform: uppercase;
|
|
353
502
|
color: var(--wave-docs-fg-muted);
|
|
354
503
|
}
|
|
355
504
|
|
|
505
|
+
/*
|
|
506
|
+
* No rule under `h2`. A full-width hairline under every section heading is
|
|
507
|
+
* the single loudest "rendered GitHub README" signal a page can carry, and
|
|
508
|
+
* with no measure it ran the entire viewport. A long reference page that
|
|
509
|
+
* genuinely wants them is one attribute away, and shipping the opt-out in the
|
|
510
|
+
* same breath as the deletion is what stops someone forking the stylesheet
|
|
511
|
+
* to get it back:
|
|
512
|
+
*/
|
|
513
|
+
.wave-docs-prose[data-rules] > h2 {
|
|
514
|
+
padding-block-end: 0.3em;
|
|
515
|
+
border-block-end: 1px solid var(--wave-docs-border);
|
|
516
|
+
}
|
|
517
|
+
|
|
356
518
|
.wave-docs-prose a {
|
|
357
519
|
color: var(--wave-docs-accent);
|
|
358
520
|
text-decoration: underline;
|
|
@@ -376,9 +538,8 @@
|
|
|
376
538
|
.wave-docs-sidebar :is(a, button):focus-visible,
|
|
377
539
|
.wave-docs-toc a:focus-visible,
|
|
378
540
|
.wave-docs-youtube__facade:focus-visible {
|
|
379
|
-
outline: 2px solid
|
|
541
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
380
542
|
outline-offset: 2px;
|
|
381
|
-
box-shadow: var(--wave-docs-ring);
|
|
382
543
|
}
|
|
383
544
|
|
|
384
545
|
.wave-docs-prose :is(ul, ol) {
|
|
@@ -414,7 +575,10 @@
|
|
|
414
575
|
padding-inline-start: 1rem;
|
|
415
576
|
border-inline-start: 3px solid var(--wave-docs-border-strong);
|
|
416
577
|
color: var(--wave-docs-fg-muted);
|
|
417
|
-
|
|
578
|
+
/* Not italic. The rule and the muted colour already say "quotation"; a
|
|
579
|
+
* long italic passage is measurably slower to read, and markdown authors
|
|
580
|
+
* use blockquotes for asides and notes, not only for speech. */
|
|
581
|
+
font-style: normal;
|
|
418
582
|
}
|
|
419
583
|
|
|
420
584
|
.wave-docs-prose hr {
|
|
@@ -466,21 +630,96 @@
|
|
|
466
630
|
overflow-x: auto;
|
|
467
631
|
border: 1px solid var(--wave-docs-border);
|
|
468
632
|
border-radius: var(--wave-docs-radius);
|
|
633
|
+
scrollbar-width: thin;
|
|
634
|
+
scrollbar-color: var(--wave-docs-border-strong) transparent;
|
|
635
|
+
/*
|
|
636
|
+
* ⚠️ NOT DECORATIVE, AND NOT DELETABLE AS SUCH.
|
|
637
|
+
*
|
|
638
|
+
* This is the whole scroll affordance, and it is four gradients because
|
|
639
|
+
* that is what makes it stateless. The two `local` covers are painted in
|
|
640
|
+
* the page colour and scroll *with* the content, so each one sits over its
|
|
641
|
+
* shadow only while that edge is at rest; the two `scroll` shadows are
|
|
642
|
+
* pinned to the box. The result is a shadow that appears on precisely the
|
|
643
|
+
* side that has content off-screen, with no listener, no state and no
|
|
644
|
+
* hydration — on a table the reader may well meet before JS arrives.
|
|
645
|
+
*/
|
|
646
|
+
background:
|
|
647
|
+
linear-gradient(to right, var(--wave-docs-bg) 30%, transparent) left /
|
|
648
|
+
2rem 100% no-repeat local,
|
|
649
|
+
linear-gradient(to left, var(--wave-docs-bg) 30%, transparent) right /
|
|
650
|
+
2rem 100% no-repeat local,
|
|
651
|
+
radial-gradient(
|
|
652
|
+
farthest-side at 0 50%,
|
|
653
|
+
var(--wave-docs-scroll-shadow),
|
|
654
|
+
transparent
|
|
655
|
+
)
|
|
656
|
+
left / 0.75rem 100% no-repeat scroll,
|
|
657
|
+
radial-gradient(
|
|
658
|
+
farthest-side at 100% 50%,
|
|
659
|
+
var(--wave-docs-scroll-shadow),
|
|
660
|
+
transparent
|
|
661
|
+
)
|
|
662
|
+
right / 0.75rem 100% no-repeat scroll;
|
|
469
663
|
}
|
|
470
664
|
|
|
471
665
|
.wave-docs-table-scroll:focus-visible {
|
|
472
|
-
outline: 2px solid
|
|
666
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
473
667
|
outline-offset: 2px;
|
|
474
|
-
box-shadow: var(--wave-docs-ring);
|
|
475
668
|
}
|
|
476
669
|
|
|
477
670
|
.wave-docs-table {
|
|
478
671
|
width: 100%;
|
|
672
|
+
/*
|
|
673
|
+
* A FLOOR, NOT A SIZING MODE — and `width: 100%` is not one.
|
|
674
|
+
*
|
|
675
|
+
* Auto table layout floors at min-content, so `width: 100%` inside an
|
|
676
|
+
* `overflow-x: auto` container does not stop a table from being squeezed;
|
|
677
|
+
* it only stops it from being wider than the container. What actually
|
|
678
|
+
* collapses the columns is `.wave-docs-prose a { overflow-wrap: anywhere }`
|
|
679
|
+
* above: per CSS Text 3, `anywhere` affects *intrinsic* sizing and
|
|
680
|
+
* `break-word` does not, so a cell holding a link — i.e. every column of a
|
|
681
|
+
* real API table — reports a min-content width of about one character. The
|
|
682
|
+
* table then fits 320px, does not overflow, and renders rows five lines
|
|
683
|
+
* tall with one word per line.
|
|
684
|
+
*
|
|
685
|
+
* `width: max-content` is the obvious fix and is worse: it forbids a
|
|
686
|
+
* description cell from wrapping at all, so a table with one sentence in it
|
|
687
|
+
* is over 1600px wide at every viewport and you scroll sideways to read
|
|
688
|
+
* one sentence.
|
|
689
|
+
*
|
|
690
|
+
* A floor solves both. Below 40rem of available width the table stops
|
|
691
|
+
* shrinking and the scroll container takes over; above it, `width: 100%`
|
|
692
|
+
* takes over and the description column wraps as it should.
|
|
693
|
+
*
|
|
694
|
+
* `min-width: 40rem`, NOT `min(100%, 40rem)` — that spelling is a no-op at
|
|
695
|
+
* exactly the widths it is meant to govern, because the `100%` resolves
|
|
696
|
+
* against the box being floored: in a 318px scroll container it computes to
|
|
697
|
+
* `min(318px, 640px)` = 318px, i.e. the container's own width, so nothing
|
|
698
|
+
* ever overflows and the table squeezes exactly as before. The browser tier
|
|
699
|
+
* caught it at 318 vs 318.
|
|
700
|
+
*
|
|
701
|
+
* A consumer with an eight-column monster overrides `min-width` from their
|
|
702
|
+
* own unlayered rule and wins by the cascade contract this file already
|
|
703
|
+
* guarantees — so no token for it.
|
|
704
|
+
*/
|
|
705
|
+
min-width: 40rem;
|
|
479
706
|
border-collapse: collapse;
|
|
707
|
+
font-variant-numeric: tabular-nums;
|
|
480
708
|
font-size: 0.9375rem;
|
|
481
709
|
line-height: 1.6;
|
|
482
710
|
}
|
|
483
711
|
|
|
712
|
+
/* `box-shadow`, not `border-block-end`: with `border-collapse: collapse` the
|
|
713
|
+
* border belongs to the table, not the cell, so it scrolls away from a
|
|
714
|
+
* sticky header instead of staying under it. */
|
|
715
|
+
.wave-docs-table thead th {
|
|
716
|
+
position: sticky;
|
|
717
|
+
top: 0;
|
|
718
|
+
z-index: 1;
|
|
719
|
+
background: var(--wave-docs-bg-subtle);
|
|
720
|
+
box-shadow: inset 0 -1px 0 var(--wave-docs-border);
|
|
721
|
+
}
|
|
722
|
+
|
|
484
723
|
.wave-docs-table :is(th, td) {
|
|
485
724
|
padding: 0.625rem 0.875rem;
|
|
486
725
|
text-align: start;
|
|
@@ -549,6 +788,210 @@
|
|
|
549
788
|
* ------------------------------------------------------------------------ */
|
|
550
789
|
|
|
551
790
|
@layer components {
|
|
791
|
+
/* -------------------------------------------------------------------------
|
|
792
|
+
* Code frame
|
|
793
|
+
*
|
|
794
|
+
* The `<figure>` `rehypeCodeFrame` wraps every highlighted fence in: an
|
|
795
|
+
* optional title bar, a copy button, and the `<pre>` itself.
|
|
796
|
+
* ---------------------------------------------------------------------- */
|
|
797
|
+
|
|
798
|
+
.wave-docs-code {
|
|
799
|
+
position: relative;
|
|
800
|
+
/* The `<pre>` inside carries the border and background; the figure is only
|
|
801
|
+
* a positioning context, and a default `<figure>` margin would indent the
|
|
802
|
+
* whole block by 40px. */
|
|
803
|
+
margin: 0;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.wave-docs-code__title {
|
|
807
|
+
display: flex;
|
|
808
|
+
align-items: center;
|
|
809
|
+
/* Room for the button, which is absolutely positioned over this row. */
|
|
810
|
+
padding: 0.5rem 3rem 0.5rem 1.125rem;
|
|
811
|
+
border: 1px solid var(--wave-docs-border);
|
|
812
|
+
border-block-end: 0;
|
|
813
|
+
border-start-start-radius: var(--wave-docs-radius);
|
|
814
|
+
border-start-end-radius: var(--wave-docs-radius);
|
|
815
|
+
background-color: var(--wave-docs-bg-subtle);
|
|
816
|
+
color: var(--wave-docs-fg-muted);
|
|
817
|
+
font-family: var(--wave-docs-font-mono);
|
|
818
|
+
font-size: 0.8125rem;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/* Square off the top of a `<pre>` that sits under a title bar.
|
|
822
|
+
*
|
|
823
|
+
* `.wave-docs-prose` first, even though `.wave-docs-code` is our class and
|
|
824
|
+
* only ever appears inside it: `styles.test.ts` requires every `.shiki` rule
|
|
825
|
+
* to name the prose scope, so the package can never style a code block it
|
|
826
|
+
* did not render. One form for that invariant is worth the extra selector. */
|
|
827
|
+
/*
|
|
828
|
+
* ⚠️ THE `<pre>`'s USER-AGENT MARGIN, WHICH UNDID THE FRAME.
|
|
829
|
+
*
|
|
830
|
+
* A `<pre>` defaults to `margin-block: 1em`, and at this block's 0.875rem
|
|
831
|
+
* that is 14px — measured in Chromium on the real site, not reasoned about.
|
|
832
|
+
* So the title bar dropped its bottom border and squared its bottom corners
|
|
833
|
+
* to join the code below it, and then the browser pushed the two 14px apart:
|
|
834
|
+
* a caption floating over a gap, which is the one arrangement the frame was
|
|
835
|
+
* designed not to be.
|
|
836
|
+
*
|
|
837
|
+
* It mis-seated the copy button too. The button is positioned against the
|
|
838
|
+
* `<figure>`, so on a fence with no title the margin slid the code down out
|
|
839
|
+
* from under it.
|
|
840
|
+
*
|
|
841
|
+
* Invisible to every test here before the site existed: jsdom has no layout,
|
|
842
|
+
* and `styles.test.ts` reads rules as text. It took a screenshot of a real
|
|
843
|
+
* page to see it.
|
|
844
|
+
*/
|
|
845
|
+
.wave-docs-prose .wave-docs-code .shiki {
|
|
846
|
+
margin: 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.wave-docs-prose .wave-docs-code:has(.wave-docs-code__title) .shiki {
|
|
850
|
+
border-start-start-radius: 0;
|
|
851
|
+
border-start-end-radius: 0;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.wave-docs-code__copy {
|
|
855
|
+
position: absolute;
|
|
856
|
+
top: 0.5rem;
|
|
857
|
+
inset-inline-end: 0.5rem;
|
|
858
|
+
display: inline-flex;
|
|
859
|
+
align-items: center;
|
|
860
|
+
justify-content: center;
|
|
861
|
+
width: 2rem;
|
|
862
|
+
height: 2rem;
|
|
863
|
+
border: 1px solid var(--wave-docs-border);
|
|
864
|
+
border-radius: var(--wave-docs-radius-sm);
|
|
865
|
+
background-color: var(--wave-docs-bg);
|
|
866
|
+
color: var(--wave-docs-fg-muted);
|
|
867
|
+
font-size: 0.875rem;
|
|
868
|
+
cursor: pointer;
|
|
869
|
+
/*
|
|
870
|
+
* ⚠️ HIDDEN UNTIL THE RUNTIME SAYS OTHERWISE, AND THIS IS STRUCTURAL.
|
|
871
|
+
*
|
|
872
|
+
* The button is in the HTML whether or not any JavaScript runs — a reader
|
|
873
|
+
* with scripts off, a `renderToStaticMarkup` consumer, anyone rendering
|
|
874
|
+
* the hast by hand. `visibility: hidden` also takes it out of the tab
|
|
875
|
+
* order, which `opacity: 0` would not: without it a keyboard reader meets
|
|
876
|
+
* a control that is invisible, focusable, and does nothing.
|
|
877
|
+
*/
|
|
878
|
+
visibility: hidden;
|
|
879
|
+
opacity: 0;
|
|
880
|
+
transition: opacity 120ms ease;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
@media (prefers-reduced-motion: reduce) {
|
|
884
|
+
.wave-docs-code__copy {
|
|
885
|
+
transition: none;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
html[data-wave-docs-code-ready] .wave-docs-code__copy {
|
|
890
|
+
visibility: visible;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.wave-docs-code:hover .wave-docs-code__copy,
|
|
894
|
+
.wave-docs-code:focus-within .wave-docs-code__copy,
|
|
895
|
+
/* A title bar already reserves the space, so there is nothing to reveal. */
|
|
896
|
+
.wave-docs-code:has(.wave-docs-code__title) .wave-docs-code__copy {
|
|
897
|
+
opacity: 1;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/*
|
|
901
|
+
* A control that only appears on hover does not exist on a phone. This is
|
|
902
|
+
* the whole of the mobile story for copy, and it is one query.
|
|
903
|
+
*/
|
|
904
|
+
@media (hover: none) {
|
|
905
|
+
.wave-docs-code__copy {
|
|
906
|
+
opacity: 1;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
.wave-docs-code__copy:hover {
|
|
911
|
+
background-color: var(--wave-docs-bg-subtle);
|
|
912
|
+
color: var(--wave-docs-fg);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/* The success state, swapped by CSS rather than by React: no component owns
|
|
916
|
+
* a button, so there is no state to re-render and nothing to hydrate. */
|
|
917
|
+
.wave-docs-code__copy[data-copied='true'] {
|
|
918
|
+
border-color: var(--wave-docs-callout-tip);
|
|
919
|
+
color: var(--wave-docs-callout-tip);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.wave-docs-code__copy[data-copied='true'] > * {
|
|
923
|
+
visibility: hidden;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.wave-docs-code__copy[data-copied='true']::after {
|
|
927
|
+
content: '\2713';
|
|
928
|
+
position: absolute;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/*
|
|
932
|
+
* ⚠️ AND THE FAILURE STATE, WHICH HAD NO RULE AT ALL. The runtime has always
|
|
933
|
+
* written `data-copied="false"` and announced "Copy failed. Select the code
|
|
934
|
+
* and press Control or Command + C." — to a live region, so a screen-reader
|
|
935
|
+
* user was told and a sighted user saw a button that simply did nothing.
|
|
936
|
+
*
|
|
937
|
+
* The most common way to land here is not exotic: `next dev` reached from a
|
|
938
|
+
* phone over `http://192.168.x.x:3000` is not a secure context, so
|
|
939
|
+
* `navigator.clipboard` is undefined and no amount of pressing helps. A
|
|
940
|
+
* silent no-op is the worst possible answer to that.
|
|
941
|
+
*/
|
|
942
|
+
.wave-docs-code__copy[data-copied='false'] {
|
|
943
|
+
border-color: var(--wave-docs-callout-caution);
|
|
944
|
+
color: var(--wave-docs-callout-caution);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.wave-docs-code__copy[data-copied='false'] > * {
|
|
948
|
+
visibility: hidden;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.wave-docs-code__copy[data-copied='false']::after {
|
|
952
|
+
/* A cross, not a warning triangle: it pairs with the tick above at the
|
|
953
|
+
* same weight, and the triangle reads as a page-level alert. */
|
|
954
|
+
content: '\00d7';
|
|
955
|
+
position: absolute;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/*
|
|
959
|
+
* The live region. Visually hidden rather than `display: none`, which most
|
|
960
|
+
* screen readers skip entirely — an announcement nobody hears is not one.
|
|
961
|
+
*/
|
|
962
|
+
.wave-docs-code__status {
|
|
963
|
+
position: absolute;
|
|
964
|
+
width: 1px;
|
|
965
|
+
height: 1px;
|
|
966
|
+
margin: -1px;
|
|
967
|
+
padding: 0;
|
|
968
|
+
overflow: hidden;
|
|
969
|
+
clip-path: inset(50%);
|
|
970
|
+
white-space: nowrap;
|
|
971
|
+
border: 0;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/*
|
|
975
|
+
* A fence whose language is in `excludeLangs` never reaches Shiki, so it has
|
|
976
|
+
* none of the rules below: no background, no border, no padding, no radius,
|
|
977
|
+
* no monospace sizing and no horizontal scroll. It rendered as a wall of UA
|
|
978
|
+
* default text bleeding out of the reading column — `excludeLangs` shipped
|
|
979
|
+
* as half a feature, and this is the other half. Everything a consumer then
|
|
980
|
+
* renders in its place (a Mermaid diagram, say) overrides it from their own
|
|
981
|
+
* unlayered CSS, which beats every layer here.
|
|
982
|
+
*/
|
|
983
|
+
.wave-docs-prose pre:not(.shiki) {
|
|
984
|
+
padding: 1rem 1.125rem;
|
|
985
|
+
border: 1px solid var(--wave-docs-border);
|
|
986
|
+
border-radius: var(--wave-docs-radius);
|
|
987
|
+
background-color: var(--wave-docs-code-block-bg);
|
|
988
|
+
overflow-x: auto;
|
|
989
|
+
font-family: var(--wave-docs-font-mono);
|
|
990
|
+
font-size: 0.875rem;
|
|
991
|
+
line-height: 1.7;
|
|
992
|
+
tab-size: 2;
|
|
993
|
+
}
|
|
994
|
+
|
|
552
995
|
.wave-docs-prose .shiki {
|
|
553
996
|
padding: 1rem 1.125rem;
|
|
554
997
|
border: 1px solid var(--wave-docs-border);
|
|
@@ -572,9 +1015,8 @@
|
|
|
572
1015
|
* wide block — which makes it the one focusable element in this package that
|
|
573
1016
|
* had no focus style. */
|
|
574
1017
|
.wave-docs-prose .shiki:focus-visible {
|
|
575
|
-
outline: 2px solid
|
|
1018
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
576
1019
|
outline-offset: 2px;
|
|
577
|
-
box-shadow: var(--wave-docs-ring);
|
|
578
1020
|
}
|
|
579
1021
|
|
|
580
1022
|
.wave-docs-prose .shiki code {
|
|
@@ -678,6 +1120,87 @@
|
|
|
678
1120
|
border: 0;
|
|
679
1121
|
background: none;
|
|
680
1122
|
cursor: pointer;
|
|
1123
|
+
/* The UA disclosure triangle, which would sit on top of the thumbnail. */
|
|
1124
|
+
list-style: none;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/* Safari still needs the pseudo-element form; it ignores `list-style` here. */
|
|
1128
|
+
.wave-docs-youtube__facade::-webkit-details-marker {
|
|
1129
|
+
display: none;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/*
|
|
1133
|
+
* ⚠️ HIDDEN, NOT REMOVED, AND FOCUSABLE EITHER WAY. `display: none` here
|
|
1134
|
+
* would destroy the element the reader just activated, dropping focus to the
|
|
1135
|
+
* top of the document — which is the bug the previous, client-side version
|
|
1136
|
+
* needed a `useEffect` to paper over. Visually hidden keeps focus where the
|
|
1137
|
+
* reader put it and leaves a way to collapse the player again.
|
|
1138
|
+
*/
|
|
1139
|
+
.wave-docs-youtube[open] .wave-docs-youtube__facade {
|
|
1140
|
+
position: absolute;
|
|
1141
|
+
width: 1px;
|
|
1142
|
+
height: 1px;
|
|
1143
|
+
margin: -1px;
|
|
1144
|
+
overflow: hidden;
|
|
1145
|
+
clip-path: inset(50%);
|
|
1146
|
+
white-space: nowrap;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/*
|
|
1150
|
+
* ⚠️ AND VISIBLE AGAIN THE MOMENT IT HAS FOCUS. The rule above ran
|
|
1151
|
+
* unconditionally, so a keyboard reader who pressed Enter to start the video
|
|
1152
|
+
* was left holding focus on a 1×1 clipped element: the focus indicator
|
|
1153
|
+
* disappeared from the page entirely (WCAG 2.4.7), with no way to see that
|
|
1154
|
+
* the next Enter would collapse the player again. `youtube.browser.test.tsx`
|
|
1155
|
+
* asserts focus deliberately *stays* here, which made the hiding rule a
|
|
1156
|
+
* guaranteed failure rather than a possible one.
|
|
1157
|
+
*
|
|
1158
|
+
* Same shape as the skip link: off-screen until focused, a real control when
|
|
1159
|
+
* focused. It overlays the player rather than displacing it, because the
|
|
1160
|
+
* `<details>` has a fixed aspect ratio and reflowing the video under the
|
|
1161
|
+
* reader's cursor to reveal a button is its own defect.
|
|
1162
|
+
*
|
|
1163
|
+
* `:focus-visible` here, where the skip link 700 lines up deliberately uses
|
|
1164
|
+
* `:focus` — the divergence is the point. Clicking the thumbnail focuses this
|
|
1165
|
+
* summary as a side effect of opening the player, and a mouse user does not
|
|
1166
|
+
* want a "Hide video" control appearing over the video they just started.
|
|
1167
|
+
* The reader who needs it is the one who pressed Enter, which is exactly what
|
|
1168
|
+
* `:focus-visible` matches. Unlike the skip link, this element is also inert
|
|
1169
|
+
* to a pointer while hidden — it is 1px, under nothing — so there is no
|
|
1170
|
+
* unreachable-focused-control case to guard against.
|
|
1171
|
+
*/
|
|
1172
|
+
.wave-docs-youtube[open] .wave-docs-youtube__facade:focus-visible {
|
|
1173
|
+
position: absolute;
|
|
1174
|
+
z-index: 1;
|
|
1175
|
+
inset-block-start: 0.5rem;
|
|
1176
|
+
inset-inline-start: 0.5rem;
|
|
1177
|
+
width: auto;
|
|
1178
|
+
height: auto;
|
|
1179
|
+
margin: 0;
|
|
1180
|
+
padding: 0.5rem 0.75rem;
|
|
1181
|
+
overflow: visible;
|
|
1182
|
+
clip-path: none;
|
|
1183
|
+
border-radius: var(--wave-docs-radius-sm);
|
|
1184
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
1185
|
+
/* Inside, not outside: this control is inset 0.5rem over a black player,
|
|
1186
|
+
* and an offset ring would be drawn half onto the video and half off the
|
|
1187
|
+
* rounded corner of the frame. */
|
|
1188
|
+
outline-offset: -2px;
|
|
1189
|
+
background: var(--wave-docs-bg);
|
|
1190
|
+
color: var(--wave-docs-fg);
|
|
1191
|
+
font-size: 0.875rem;
|
|
1192
|
+
font-weight: 600;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/*
|
|
1196
|
+
* The label follows the action. A `<summary>` that reads "Play video" while
|
|
1197
|
+
* activating it *stops* the video is a control that lies about what it does,
|
|
1198
|
+
* and the state is in the DOM already — no script needed to keep the two in
|
|
1199
|
+
* step.
|
|
1200
|
+
*/
|
|
1201
|
+
.wave-docs-youtube[open] .wave-docs-youtube__label-play,
|
|
1202
|
+
.wave-docs-youtube:not([open]) .wave-docs-youtube__label-hide {
|
|
1203
|
+
display: none;
|
|
681
1204
|
}
|
|
682
1205
|
|
|
683
1206
|
.wave-docs-youtube__thumbnail {
|
|
@@ -739,6 +1262,22 @@
|
|
|
739
1262
|
|
|
740
1263
|
/* Nested levels get a guide rail instead of raw indentation, so a deep tree
|
|
741
1264
|
* stays readable at 240px wide. */
|
|
1265
|
+
/*
|
|
1266
|
+
* `__item` and `__group-title` are emitted by `sidebar.tsx` and had no rules
|
|
1267
|
+
* at all — markup with a public class name and no definition, which is the
|
|
1268
|
+
* worst of the three options: a consumer targeting it gets no baseline to
|
|
1269
|
+
* override, and a maintainer cannot tell whether it is a hook or a leftover.
|
|
1270
|
+
* They are hooks: the drawer styles items at a different density, and a group
|
|
1271
|
+
* title is the one place a sidebar needs a label that is not a link.
|
|
1272
|
+
*/
|
|
1273
|
+
.wave-docs-sidebar__item {
|
|
1274
|
+
margin-block: 0.125rem;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.wave-docs-sidebar__group-title {
|
|
1278
|
+
font-weight: 600;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
742
1281
|
.wave-docs-sidebar__list:not([data-depth='0']) {
|
|
743
1282
|
margin-inline-start: 0.5rem;
|
|
744
1283
|
padding-inline-start: 0.5rem;
|
|
@@ -865,17 +1404,36 @@
|
|
|
865
1404
|
text-decoration: none;
|
|
866
1405
|
}
|
|
867
1406
|
|
|
868
|
-
/* Indent by heading depth rather than by nesting, so
|
|
869
|
-
*
|
|
1407
|
+
/* Indent by heading depth rather than by nesting, so the rule survives a
|
|
1408
|
+
* document that skips a level. Only depth 3 exists — `rehypeCaptureToc`
|
|
1409
|
+
* captures h2 and h3 and nothing deeper, so the rules for 4, 5 and 6 that
|
|
1410
|
+
* used to sit here styled entries that can no longer be produced. */
|
|
870
1411
|
.wave-docs-toc__link[data-depth='3'] {
|
|
871
1412
|
padding-inline-start: 1.5rem;
|
|
872
1413
|
}
|
|
873
|
-
|
|
874
|
-
|
|
1414
|
+
|
|
1415
|
+
/* The back-to-top link, styled as a peer of the entries rather than as a
|
|
1416
|
+
* floating control — it is the last stop in the rail's reading order. */
|
|
1417
|
+
.wave-docs-toc__top {
|
|
1418
|
+
display: inline-block;
|
|
1419
|
+
margin-block-start: 0.75rem;
|
|
1420
|
+
padding-inline-start: 0.75rem;
|
|
1421
|
+
border-inline-start: 2px solid transparent;
|
|
1422
|
+
color: var(--wave-docs-fg-subtle);
|
|
1423
|
+
font-size: 0.8125rem;
|
|
1424
|
+
text-decoration: none;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.wave-docs-toc__top:hover {
|
|
1428
|
+
color: var(--wave-docs-fg);
|
|
875
1429
|
}
|
|
876
|
-
|
|
877
|
-
.wave-docs-
|
|
878
|
-
|
|
1430
|
+
|
|
1431
|
+
.wave-docs-sidebar__external {
|
|
1432
|
+
/* Baseline-ish against the label, and never a flex item that grows. */
|
|
1433
|
+
margin-inline-start: 0.25rem;
|
|
1434
|
+
flex: none;
|
|
1435
|
+
vertical-align: -0.05em;
|
|
1436
|
+
color: var(--wave-docs-fg-subtle);
|
|
879
1437
|
}
|
|
880
1438
|
|
|
881
1439
|
.wave-docs-toc__link:hover {
|
|
@@ -896,6 +1454,334 @@
|
|
|
896
1454
|
}
|
|
897
1455
|
}
|
|
898
1456
|
|
|
1457
|
+
/* ---------------------------------------------------------------------------
|
|
1458
|
+
* Layout
|
|
1459
|
+
*
|
|
1460
|
+
* The shell `docs.Layout` renders. Every class here is fixed by
|
|
1461
|
+
* `docs/adr/001-shell-contract.md` and is public API from 0.3.0.
|
|
1462
|
+
*
|
|
1463
|
+
* These are the first width-based media queries in this file. Before them there
|
|
1464
|
+
* were none at all — every `@media` was `prefers-color-scheme`,
|
|
1465
|
+
* `prefers-reduced-motion` or `forced-colors` — so there was no mobile layout,
|
|
1466
|
+
* and no CSS for a layout component to use.
|
|
1467
|
+
* ------------------------------------------------------------------------ */
|
|
1468
|
+
|
|
1469
|
+
@layer components {
|
|
1470
|
+
/*
|
|
1471
|
+
* Mobile first, and single-column by construction: the sidebar and the TOC
|
|
1472
|
+
* are not laid out at all below 64rem, so nothing reserves a track for them.
|
|
1473
|
+
*/
|
|
1474
|
+
.wave-docs-layout {
|
|
1475
|
+
display: grid;
|
|
1476
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1477
|
+
gap: 0 1.5rem;
|
|
1478
|
+
/* 1rem on a phone, not 1.5rem: 48px of side padding is 15% of a 320px
|
|
1479
|
+
* screen, and it took the reading column to 272px — under the 280px this
|
|
1480
|
+
* package treats as the floor for a usable measure. Measured. */
|
|
1481
|
+
padding-inline: 1rem;
|
|
1482
|
+
/* Without this, `max-width` at the 100rem cap applies to the *content* box
|
|
1483
|
+
* and the shell renders 48px wider than the token says — measured 1648
|
|
1484
|
+
* against a declared 1600. */
|
|
1485
|
+
box-sizing: border-box;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
@media (min-width: 30rem) {
|
|
1489
|
+
.wave-docs-layout {
|
|
1490
|
+
padding-inline: 1.5rem;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/*
|
|
1495
|
+
* ⚠️ `contents`, NOT `none`, AND THAT IS NOT A STYLE PREFERENCE.
|
|
1496
|
+
*
|
|
1497
|
+
* The drawer `<dialog>` lives inside this wrapper, so that one nav DOM can
|
|
1498
|
+
* serve both breakpoints. An element inside a `display: none` subtree
|
|
1499
|
+
* generates no boxes at all — including a dialog promoted to the top layer —
|
|
1500
|
+
* so `showModal()` would open a drawer that paints nothing, on every
|
|
1501
|
+
* viewport narrower than 64rem. Which is to say: on the phones this drawer
|
|
1502
|
+
* exists for.
|
|
1503
|
+
*
|
|
1504
|
+
* `contents` makes the wrapper generate no box of its own, so it reserves no
|
|
1505
|
+
* grid track, while the closed dialog inside stays hidden by the UA's own
|
|
1506
|
+
* `dialog:not([open]) { display: none }`.
|
|
1507
|
+
*/
|
|
1508
|
+
.wave-docs-layout__sidebar {
|
|
1509
|
+
display: contents;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
.wave-docs-layout__toc {
|
|
1513
|
+
display: none;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/*
|
|
1517
|
+
* ⚠️ `min-width: 0`, AND `minmax(0, 1fr)` ON EVERY CONTENT TRACK BELOW.
|
|
1518
|
+
*
|
|
1519
|
+
* `1fr` is shorthand for `minmax(auto, 1fr)`, and `auto` floors at the
|
|
1520
|
+
* content's min-content width — so a wide table pushes the track past the
|
|
1521
|
+
* viewport and takes the whole document into horizontal scroll. Measured with
|
|
1522
|
+
* both removed: 1048px of document inside a 1024px viewport.
|
|
1523
|
+
*
|
|
1524
|
+
* MEASURED, AND WORTH KNOWING BEFORE YOU DELETE ONE: they are redundant with
|
|
1525
|
+
* each other, not jointly required. Removing `minmax(0, 1fr)` alone passes,
|
|
1526
|
+
* and removing `min-width: 0` alone passes; only removing both reproduces the
|
|
1527
|
+
* overflow. They are kept as a pair because they fail in opposite directions
|
|
1528
|
+
* — the track constraint protects a child that does not carry `min-width`,
|
|
1529
|
+
* and `min-width: 0` protects a track someone respells — and because the one
|
|
1530
|
+
* that is left looks arbitrary without the other beside it.
|
|
1531
|
+
*
|
|
1532
|
+
* No unit test can see any of this: the bug needs a wide child and a real
|
|
1533
|
+
* layout engine, and jsdom reports every width as 0.
|
|
1534
|
+
* `src/styles.browser.test.ts` is what stops a "simplification" here from
|
|
1535
|
+
* reintroducing it silently.
|
|
1536
|
+
*/
|
|
1537
|
+
.wave-docs-layout__main {
|
|
1538
|
+
min-width: 0;
|
|
1539
|
+
padding-block: 2rem 4rem;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/* 16rem sidebar + 46rem measure + two 1.5rem gutters = 65rem, so the sidebar
|
|
1543
|
+
* arrives when there is room for it and not one pixel sooner. A 60rem
|
|
1544
|
+
* breakpoint introduces it exactly where it starts eating the measure. */
|
|
1545
|
+
@media (min-width: 64rem) {
|
|
1546
|
+
.wave-docs-layout {
|
|
1547
|
+
grid-template-columns: var(--wave-docs-sidebar-width) minmax(0, 1fr);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/*
|
|
1551
|
+
* ⚠️ THE MOST DELETABLE-LOOKING LINE IN THIS FILE. IT IS NOT DELETABLE.
|
|
1552
|
+
*
|
|
1553
|
+
* `display: contents` makes the dialog box vanish from layout **and from
|
|
1554
|
+
* the accessibility tree**, so the `<DocsSidebar>` inside it becomes the
|
|
1555
|
+
* sticky column directly. That is what lets one nav serve both breakpoints:
|
|
1556
|
+
* one landmark, one copy of the links in the payload, nothing to keep in
|
|
1557
|
+
* step. Remove it and the desktop sidebar disappears entirely — or, if
|
|
1558
|
+
* someone "fixes" that by rendering a second sidebar outside the dialog,
|
|
1559
|
+
* every page ships its nav twice and a screen-reader user gets two
|
|
1560
|
+
* identical navigation landmarks.
|
|
1561
|
+
*
|
|
1562
|
+
* `:not(:modal)` so the drawer still opens as a real modal at any width;
|
|
1563
|
+
* it is the same element, and a 64rem viewport with a 200% zoom is a
|
|
1564
|
+
* narrow one.
|
|
1565
|
+
*/
|
|
1566
|
+
dialog.wave-docs-layout__drawer:not(:modal) {
|
|
1567
|
+
display: contents;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
.wave-docs-layout__sidebar {
|
|
1571
|
+
display: block;
|
|
1572
|
+
position: sticky;
|
|
1573
|
+
top: var(--wave-docs-header-height);
|
|
1574
|
+
/* `dvh`, not `vh`: with a mobile URL bar showing, `vh` is taller than the
|
|
1575
|
+
* visible viewport, so the last few nav items sit under the chrome and
|
|
1576
|
+
* cannot be reached. */
|
|
1577
|
+
max-height: calc(100dvh - var(--wave-docs-header-height));
|
|
1578
|
+
overflow-y: auto;
|
|
1579
|
+
/* Without this, reaching the end of the nav keeps scrolling — the article
|
|
1580
|
+
* jumps under the reader's cursor while they are looking at the sidebar. */
|
|
1581
|
+
overscroll-behavior: contain;
|
|
1582
|
+
/* Reserves the scrollbar's width whether or not it is showing, so the
|
|
1583
|
+
* column does not jump when the nav grows past a screen. Costs ~15px on
|
|
1584
|
+
* platforms with classic scrollbars; a jumping column costs more. */
|
|
1585
|
+
scrollbar-gutter: stable;
|
|
1586
|
+
padding-block: 2rem;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
@media (min-width: 80rem) {
|
|
1591
|
+
/*
|
|
1592
|
+
* Inside the query on purpose. As a top-level rule this `:has()` reserves
|
|
1593
|
+
* `--wave-docs-toc-width` of nothing at every width — measured at 390px it
|
|
1594
|
+
* squeezed the article to 94px — because the TOC element is in the DOM at
|
|
1595
|
+
* all sizes and only its `display` changes.
|
|
1596
|
+
*/
|
|
1597
|
+
.wave-docs-layout:has(.wave-docs-layout__toc) {
|
|
1598
|
+
grid-template-columns:
|
|
1599
|
+
var(--wave-docs-sidebar-width)
|
|
1600
|
+
minmax(0, 1fr)
|
|
1601
|
+
var(--wave-docs-toc-width);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
.wave-docs-layout__toc {
|
|
1605
|
+
display: block;
|
|
1606
|
+
position: sticky;
|
|
1607
|
+
/* `align-self: start`, or the sticky box stretches to the row height and
|
|
1608
|
+
* has nothing left to travel within. */
|
|
1609
|
+
align-self: start;
|
|
1610
|
+
top: var(--wave-docs-header-height);
|
|
1611
|
+
max-height: calc(100dvh - var(--wave-docs-header-height));
|
|
1612
|
+
overflow-y: auto;
|
|
1613
|
+
overscroll-behavior: contain;
|
|
1614
|
+
padding-block: 2rem;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/* One declaration, for the only screenshot anyone takes on a 2560px display:
|
|
1619
|
+
* uncapped, the sidebar pins to the far-left bezel and the TOC to the
|
|
1620
|
+
* far-right, about 1500px apart, with the text floating between them. */
|
|
1621
|
+
@media (min-width: 100rem) {
|
|
1622
|
+
.wave-docs-layout {
|
|
1623
|
+
max-width: var(--wave-docs-shell-width);
|
|
1624
|
+
margin-inline: auto;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/* -------------------------------------------------------------------------
|
|
1629
|
+
* Header
|
|
1630
|
+
* ---------------------------------------------------------------------- */
|
|
1631
|
+
|
|
1632
|
+
.wave-docs-layout__header {
|
|
1633
|
+
position: sticky;
|
|
1634
|
+
top: 0;
|
|
1635
|
+
/* Above the sticky sidebar and TOC, which establish their own stacking
|
|
1636
|
+
* contexts; below the search dialog, which portals to `<body>`. */
|
|
1637
|
+
z-index: 20;
|
|
1638
|
+
height: var(--wave-docs-header-height);
|
|
1639
|
+
background-color: var(--wave-docs-bg);
|
|
1640
|
+
border-block-end: 1px solid var(--wave-docs-border);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.wave-docs-layout__header-inner {
|
|
1644
|
+
display: flex;
|
|
1645
|
+
align-items: center;
|
|
1646
|
+
gap: 0.75rem;
|
|
1647
|
+
height: 100%;
|
|
1648
|
+
/* Matches the grid's own cap and gutters, so the brand lines up with the
|
|
1649
|
+
* sidebar and the actions with the TOC rather than floating past both. */
|
|
1650
|
+
max-width: var(--wave-docs-shell-width);
|
|
1651
|
+
margin-inline: auto;
|
|
1652
|
+
padding-inline: 1rem;
|
|
1653
|
+
box-sizing: border-box;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
@media (min-width: 30rem) {
|
|
1657
|
+
.wave-docs-layout__header-inner {
|
|
1658
|
+
padding-inline: 1.5rem;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.wave-docs-layout__title {
|
|
1663
|
+
font-weight: 600;
|
|
1664
|
+
/* The brand is not a link target here and must not swallow the row: a long
|
|
1665
|
+
* product name would otherwise push search off the right edge. */
|
|
1666
|
+
min-width: 0;
|
|
1667
|
+
overflow: hidden;
|
|
1668
|
+
text-overflow: ellipsis;
|
|
1669
|
+
white-space: nowrap;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/* Pushes search and actions to the end, and stays correct when `title` is
|
|
1673
|
+
* absent — which is why it is on the search wrapper rather than a margin on
|
|
1674
|
+
* the title that would not exist. */
|
|
1675
|
+
.wave-docs-layout__search {
|
|
1676
|
+
margin-inline-start: auto;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.wave-docs-layout__actions {
|
|
1680
|
+
display: flex;
|
|
1681
|
+
align-items: center;
|
|
1682
|
+
gap: 0.5rem;
|
|
1683
|
+
/* When `search={false}` there is no auto margin ahead of this, so it
|
|
1684
|
+
* carries its own. Both set, only one ever has room to apply. */
|
|
1685
|
+
margin-inline-start: auto;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
.wave-docs-layout__nav-trigger {
|
|
1689
|
+
display: inline-flex;
|
|
1690
|
+
align-items: center;
|
|
1691
|
+
justify-content: center;
|
|
1692
|
+
/* 44px: the smallest target iOS and WCAG 2.5.8 both accept, and this is
|
|
1693
|
+
* the only control on a phone that reaches the rest of the site. */
|
|
1694
|
+
width: 2.75rem;
|
|
1695
|
+
height: 2.75rem;
|
|
1696
|
+
/* Optical alignment: the icon's own padding already insets it from the
|
|
1697
|
+
* screen edge, so the button hangs left of the text below it without this. */
|
|
1698
|
+
margin-inline-start: -0.75rem;
|
|
1699
|
+
border: 0;
|
|
1700
|
+
border-radius: var(--wave-docs-radius-sm);
|
|
1701
|
+
background: none;
|
|
1702
|
+
color: var(--wave-docs-fg-muted);
|
|
1703
|
+
cursor: pointer;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
.wave-docs-layout__nav-trigger:hover {
|
|
1707
|
+
background-color: var(--wave-docs-bg-subtle);
|
|
1708
|
+
color: var(--wave-docs-fg);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/* -------------------------------------------------------------------------
|
|
1712
|
+
* Drawer
|
|
1713
|
+
* ---------------------------------------------------------------------- */
|
|
1714
|
+
|
|
1715
|
+
dialog.wave-docs-layout__drawer {
|
|
1716
|
+
/* A full-height panel against the inline start edge, not a centred box.
|
|
1717
|
+
* `margin` rather than `inset`, because a dialog is centred by `margin:
|
|
1718
|
+
* auto` in the UA sheet and only a margin overrides that. */
|
|
1719
|
+
margin: 0 auto 0 0;
|
|
1720
|
+
box-sizing: border-box;
|
|
1721
|
+
width: min(20rem, 85vw);
|
|
1722
|
+
max-width: none;
|
|
1723
|
+
height: 100dvh;
|
|
1724
|
+
max-height: none;
|
|
1725
|
+
padding: 1.5rem 1rem;
|
|
1726
|
+
border: 0;
|
|
1727
|
+
border-inline-end: 1px solid var(--wave-docs-border);
|
|
1728
|
+
background-color: var(--wave-docs-bg);
|
|
1729
|
+
color: var(--wave-docs-fg);
|
|
1730
|
+
overflow-y: auto;
|
|
1731
|
+
/* Reaching the end of the nav must not start scrolling the page behind. */
|
|
1732
|
+
overscroll-behavior: contain;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
dialog.wave-docs-layout__drawer::backdrop {
|
|
1736
|
+
background-color: oklch(0.2 0.02 262 / 0.55);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.wave-docs-layout__drawer-close {
|
|
1740
|
+
display: inline-flex;
|
|
1741
|
+
align-items: center;
|
|
1742
|
+
justify-content: center;
|
|
1743
|
+
width: 2.75rem;
|
|
1744
|
+
height: 2.75rem;
|
|
1745
|
+
margin-block-end: 0.5rem;
|
|
1746
|
+
margin-inline-start: auto;
|
|
1747
|
+
border: 0;
|
|
1748
|
+
border-radius: var(--wave-docs-radius-sm);
|
|
1749
|
+
background: none;
|
|
1750
|
+
color: var(--wave-docs-fg-muted);
|
|
1751
|
+
cursor: pointer;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
.wave-docs-layout__drawer-close:hover {
|
|
1755
|
+
background-color: var(--wave-docs-bg-subtle);
|
|
1756
|
+
color: var(--wave-docs-fg);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/*
|
|
1760
|
+
* ⚠️ SCOPED TO OUR DRAWER, DELIBERATELY. `html:has(dialog:modal)` would lock
|
|
1761
|
+
* the page for a consumer's own modal too — and unlock it again the moment
|
|
1762
|
+
* ours closed underneath theirs.
|
|
1763
|
+
*/
|
|
1764
|
+
html:has(dialog.wave-docs-layout__drawer:modal) {
|
|
1765
|
+
overflow: hidden;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
/*
|
|
1769
|
+
* Drawer furniture, hidden once the drawer is a column.
|
|
1770
|
+
*
|
|
1771
|
+
* ⚠️ AFTER the rules it overrides, not up with the other 64rem block. Same
|
|
1772
|
+
* specificity means source order decides, and declared earlier this lost to
|
|
1773
|
+
* the `display: inline-flex` above at every width — leaving a menu button
|
|
1774
|
+
* and a close button sitting in a desktop layout that has nothing to open or
|
|
1775
|
+
* close. Measured at 1280px: `flex`, not `none`.
|
|
1776
|
+
*/
|
|
1777
|
+
@media (min-width: 64rem) {
|
|
1778
|
+
.wave-docs-layout__nav-trigger,
|
|
1779
|
+
.wave-docs-layout__drawer-close {
|
|
1780
|
+
display: none;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
899
1785
|
/* ---------------------------------------------------------------------------
|
|
900
1786
|
* Search
|
|
901
1787
|
*
|
|
@@ -929,11 +1815,9 @@
|
|
|
929
1815
|
}
|
|
930
1816
|
|
|
931
1817
|
.wave-docs-search-trigger:focus-visible,
|
|
932
|
-
.wave-docs-search-close:focus-visible
|
|
933
|
-
|
|
934
|
-
outline: 2px solid transparent;
|
|
1818
|
+
.wave-docs-search-close:focus-visible {
|
|
1819
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
935
1820
|
outline-offset: 2px;
|
|
936
|
-
box-shadow: var(--wave-docs-ring);
|
|
937
1821
|
}
|
|
938
1822
|
|
|
939
1823
|
/* The label is the flexible half of the trigger; the shortcut hint beside it
|
|
@@ -962,7 +1846,10 @@
|
|
|
962
1846
|
justify-content: center;
|
|
963
1847
|
/* Not centred: a dialog that grows downward from a fixed top does not
|
|
964
1848
|
* shift under the reader as results stream in. */
|
|
965
|
-
|
|
1849
|
+
/* `dvh`, not `vh`: on a phone `vh` is the viewport with the URL bar
|
|
1850
|
+
* *retracted*, so a dialog sized against it is taller than what the reader
|
|
1851
|
+
* can actually see and its bottom edge sits under the browser chrome. */
|
|
1852
|
+
padding: 10dvh 1rem 1rem;
|
|
966
1853
|
background: oklch(0.2 0.02 265 / 0.5);
|
|
967
1854
|
backdrop-filter: blur(2px);
|
|
968
1855
|
}
|
|
@@ -972,12 +1859,12 @@
|
|
|
972
1859
|
flex-direction: column;
|
|
973
1860
|
width: 100%;
|
|
974
1861
|
max-width: 34rem;
|
|
975
|
-
max-height: min(32rem,
|
|
1862
|
+
max-height: min(32rem, 80dvh);
|
|
976
1863
|
overflow: hidden;
|
|
977
1864
|
border: 1px solid var(--wave-docs-border);
|
|
978
1865
|
border-radius: var(--wave-docs-radius);
|
|
979
1866
|
background: var(--wave-docs-bg);
|
|
980
|
-
box-shadow: 0 24px 48px -12px
|
|
1867
|
+
box-shadow: 0 24px 48px -12px var(--wave-docs-shadow);
|
|
981
1868
|
}
|
|
982
1869
|
|
|
983
1870
|
.wave-docs-search-input-row {
|
|
@@ -1015,9 +1902,9 @@
|
|
|
1015
1902
|
font-size: 1rem;
|
|
1016
1903
|
}
|
|
1017
1904
|
|
|
1018
|
-
/*
|
|
1019
|
-
* row
|
|
1020
|
-
* would double up
|
|
1905
|
+
/* The one `outline: none` in the file, and it is not a missing indicator:
|
|
1906
|
+
* the row around the input draws a real outline on `:has(:focus-visible)`,
|
|
1907
|
+
* and a second one on the input itself would double up. */
|
|
1021
1908
|
.wave-docs-search-input:focus {
|
|
1022
1909
|
outline: none;
|
|
1023
1910
|
}
|
|
@@ -1136,44 +2023,3 @@
|
|
|
1136
2023
|
white-space: nowrap;
|
|
1137
2024
|
}
|
|
1138
2025
|
}
|
|
1139
|
-
|
|
1140
|
-
/* ---------------------------------------------------------------------------
|
|
1141
|
-
* Forced colours
|
|
1142
|
-
*
|
|
1143
|
-
* Windows High Contrast and its kin drop `box-shadow` entirely and replace
|
|
1144
|
-
* every colour with one from the user's palette. Every focus indicator in this
|
|
1145
|
-
* package is a `box-shadow` ring, so without this block the package would
|
|
1146
|
-
* *remove* the UA outline from every focusable surface it styles and draw
|
|
1147
|
-
* nothing in its place — a strictly worse page than shipping no CSS at all.
|
|
1148
|
-
*
|
|
1149
|
-
* `Highlight` rather than a token: user-chosen system colours are the whole
|
|
1150
|
-
* point of the mode, and `--wave-docs-accent` is forced to something else
|
|
1151
|
-
* anyway. This list must stay in step with every `:focus-visible` rule above;
|
|
1152
|
-
* `src/styles.test.ts` fails if one appears without its entry here.
|
|
1153
|
-
* ------------------------------------------------------------------------ */
|
|
1154
|
-
|
|
1155
|
-
@layer components {
|
|
1156
|
-
@media (forced-colors: active) {
|
|
1157
|
-
.wave-docs-skip-link:focus,
|
|
1158
|
-
.wave-docs-prose a:focus-visible,
|
|
1159
|
-
.wave-docs-sidebar :is(a, button):focus-visible,
|
|
1160
|
-
.wave-docs-toc a:focus-visible,
|
|
1161
|
-
.wave-docs-youtube__facade:focus-visible,
|
|
1162
|
-
.wave-docs-table-scroll:focus-visible,
|
|
1163
|
-
.wave-docs-prose .shiki:focus-visible,
|
|
1164
|
-
.wave-docs-search-trigger:focus-visible,
|
|
1165
|
-
.wave-docs-search-close:focus-visible,
|
|
1166
|
-
.wave-docs-search-result-link:focus-visible {
|
|
1167
|
-
outline: 3px solid Highlight;
|
|
1168
|
-
outline-offset: 2px;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
/* Inward, like their normal-mode rules: one is clipped by the dialog, the
|
|
1172
|
-
* other by the scrolling results list. */
|
|
1173
|
-
.wave-docs-search-input-row:has(.wave-docs-search-input:focus-visible),
|
|
1174
|
-
.wave-docs-search-result-active {
|
|
1175
|
-
outline: 2px solid Highlight;
|
|
1176
|
-
outline-offset: -2px;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|