Blog/wwwroot/app.css 23.6 K · 1041 lines · raw · history

1 /* ==========================================================================
2 bes.is — a small terminal-flavoured stylesheet.
3
4 1. Tokens design decisions, in one place
5 2. Reset & base the same starting point in every browser
6 3. Typography headings, text, lists, links
7 4. Layout page frame and layout utilities
8 5. Controls buttons, fields, everything the browser styles for us
9 6. Components header, footer, log, panels, json, dialogs
10 7. Media logo, images, video
11 ========================================================================== */
12
13
14 /* 1. Tokens ================================================================ */
15
16 :root {
17 /* Tells the browser to theme native UI (scrollbars, select popups, focus
18 rings, spinners) to match the page. Without it those stay light in a
19 dark page, which is the main reason controls look different per
20 browser. It also enables light-dark() below. */
21 color-scheme: light dark;
22
23 --black: #050505;
24 --white: #fafafa;
25
26 --background: light-dark(var(--white), var(--black));
27 --color: light-dark(var(--black), var(--white));
28 --border: var(--color);
29
30 --error: light-dark(#b00000, #ff6b6b);
31 --info: light-dark(#3d6f8e, #86b6d3);
32 --green: light-dark(#2f7a2d, #6fce6d);
33
34 /* Modular scale. Every gap, margin and padding comes from here so
35 spacing stays proportional to the fluid type size. */
36 --ratio: 1.5;
37 --s-2: calc(var(--s-1) / var(--ratio));
38 --s-1: calc(var(--s0) / var(--ratio));
39 --s0: 1rem;
40 --s1: calc(var(--s0) * var(--ratio));
41 --s2: calc(var(--s1) * var(--ratio));
42
43 /* A concrete stack instead of bare `monospace`: browsers disagree both on
44 which font that is and on its default size. */
45 --font: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
46
47 --measure: 80ch;
48 --radius: 0.5em;
49 --border-width: 1px;
50 --rule: 2px dotted var(--border);
51 }
52
53
54 /* 2. Reset & base ========================================================== */
55
56 *,
57 *::before,
58 *::after {
59 box-sizing: border-box;
60 }
61
62 html {
63 font-family: var(--font);
64 font-size: calc(1rem + 0.4vw);
65 line-height: 1.5;
66 -webkit-text-size-adjust: 100%;
67 tab-size: 4;
68 }
69
70 body {
71 margin: 0;
72 min-block-size: 100dvh;
73 display: flex;
74 flex-direction: column;
75 background-color: var(--background);
76 color: var(--color);
77 }
78
79 /* One focus style for every focusable thing, instead of six browser defaults.
80 `:focus:not(:focus-visible)` keeps programmatic focus (FocusOnNavigate
81 lands on the <h1> after every navigation) from flashing a ring. */
82 :focus-visible {
83 outline: 2px solid var(--color);
84 outline-offset: 2px;
85 }
86
87 :focus:not(:focus-visible) {
88 outline: none;
89 }
90
91 ::selection {
92 background-color: var(--color);
93 color: var(--background);
94 }
95
96
97 /* 3. Typography ============================================================ */
98
99 h1,
100 h2,
101 h3,
102 h4,
103 h5,
104 h6 {
105 margin-block: 0 var(--s-1);
106 font-weight: 700;
107 line-height: 1.2;
108 text-wrap: balance;
109 }
110
111 h1 {
112 font-size: calc(1em * var(--ratio));
113 }
114
115 h2 {
116 font-size: calc(1em * 1.25);
117 }
118
119 h3,
120 h4,
121 h5,
122 h6 {
123 font-size: 1em;
124 }
125
126 p {
127 margin-block: var(--s-1);
128 text-wrap: pretty;
129 }
130
131 ul,
132 ol {
133 margin-block: var(--s-1);
134 padding-inline-start: var(--s1);
135 }
136
137 pre {
138 margin-block: var(--s-1);
139 /* pre-line, so examples can stay indented in the .razor source */
140 white-space: pre-line;
141 overflow-wrap: anywhere;
142 }
143
144 small {
145 font-size: 0.75em;
146 }
147
148 a {
149 color: inherit;
150 text-decoration-line: underline;
151 text-decoration-thickness: var(--border-width);
152 text-underline-offset: 0.2em;
153 }
154
155 a:hover,
156 a:focus-visible {
157 background-color: var(--color);
158 color: var(--background);
159 text-decoration: none;
160 }
161
162
163 /* 4. Layout ================================================================ */
164
165 /* The frame a layout wraps its whole page in: header, content and footer in one column, with
166 `main` stretching so the footer sits at the bottom of a short page. It carries the rhythm that
167 used to be on <body>, so that /git can lay itself out its own way. */
168 .page {
169 flex: 1;
170 display: flex;
171 flex-direction: column;
172 gap: var(--s0);
173 padding-block: var(--s0);
174 }
175
176 .center {
177 box-sizing: content-box;
178 max-inline-size: var(--measure);
179 margin-inline: auto;
180 padding-inline: var(--s1);
181 }
182
183 main {
184 flex: 1;
185 border-block-start: var(--rule);
186 border-block-end: var(--rule);
187 padding-block: var(--s0);
188 }
189
190 .flex-row {
191 display: flex;
192 flex-wrap: wrap;
193 align-items: center;
194 gap: var(--s0);
195 }
196
197 /* A field shares the row instead of claiming a whole one to itself. */
198 .flex-row > :is(input[type="text"], textarea) {
199 flex: 1;
200 }
201
202 .flex-spread {
203 display: flex;
204 flex-wrap: wrap;
205 gap: var(--s0);
206 justify-content: space-between;
207 }
208
209 /* A stack on the left, the program left to run on the right (the log rows and
210 the worked examples in the docs). A grid rather than a spread flex row, so a
211 long program wraps inside its own column instead of taking a full-width line
212 of its own and leaving its stack stranded on the line above. */
213 .stack-step {
214 display: grid;
215 grid-template-columns: minmax(0, max-content) 1fr;
216 column-gap: var(--s0);
217 }
218
219 /* break-word, not anywhere: it keeps the column's minimum at one whole word, so
220 a long stack can never squeeze the program down to a letter per line. */
221 .stack-step > :last-child {
222 text-align: right;
223 overflow-wrap: break-word;
224 }
225
226 .row + .row {
227 margin-block-start: var(--s0);
228 }
229
230 /* Two columns, but only while they stay wide enough to read. */
231 .two-column {
232 columns: 2 24ch;
233 gap: var(--s1);
234 }
235
236 .noselect {
237 user-select: none;
238 -webkit-user-select: none;
239 }
240
241
242 /* 5. Controls ============================================================== */
243
244 /* Controls do not inherit the page font on their own — this is what keeps
245 fields and buttons from turning into system UI in each browser. */
246 button,
247 input,
248 select,
249 textarea {
250 font: inherit;
251 color: inherit;
252 background-color: var(--background);
253 border: var(--border-width) solid var(--border);
254 border-radius: var(--radius);
255 padding: 0.1em 0.5em;
256 max-inline-size: 100%;
257 }
258
259 label {
260 display: block;
261 }
262
263 input[type="text"] {
264 /* min-inline-size lets it shrink inside a .flex-row instead of overflowing */
265 inline-size: 100%;
266 min-inline-size: 0;
267 }
268
269 textarea {
270 display: block;
271 inline-size: 100%;
272 min-block-size: 4lh;
273 resize: vertical;
274 white-space: pre-wrap;
275 }
276
277 /* A block textarea leaves its button hard against it otherwise. */
278 textarea + button,
279 textarea + input[type="submit"] {
280 margin-block-start: var(--s-1);
281 }
282
283 /* Editors sized against the viewport rather than a row count. */
284 .editor {
285 block-size: 20vh;
286 }
287
288 .editor-tall {
289 block-size: 60vh;
290 }
291
292 select {
293 cursor: pointer;
294 }
295
296 button,
297 input[type="button"],
298 input[type="submit"],
299 input[type="reset"] {
300 min-inline-size: 8ch;
301 padding-inline: var(--s0);
302 text-align: center;
303 cursor: pointer;
304 }
305
306 button:hover,
307 input[type="button"]:hover,
308 input[type="submit"]:hover,
309 input[type="reset"]:hover {
310 background-color: var(--color);
311 color: var(--background);
312 }
313
314 button:active,
315 input[type="button"]:active,
316 input[type="submit"]:active,
317 input[type="reset"]:active {
318 translate: 0 1px;
319 }
320
321 /* The file input itself is a bare label for its button. */
322 input[type="file"] {
323 border: none;
324 background: none;
325 padding: 0;
326 cursor: pointer;
327 }
328
329 /* Kept in its own rule: an unsupported pseudo-element would invalidate the
330 whole selector list if it shared one with the buttons above. */
331 ::file-selector-button {
332 font: inherit;
333 color: inherit;
334 background-color: var(--background);
335 border: var(--border-width) solid var(--border);
336 border-radius: var(--radius);
337 min-inline-size: 8ch;
338 padding: 0.1em var(--s0);
339 margin-inline-end: var(--s-1);
340 cursor: pointer;
341 }
342
343 ::file-selector-button:hover {
344 background-color: var(--color);
345 color: var(--background);
346 }
347
348 input[type="checkbox"] {
349 appearance: none;
350 inline-size: 3ch;
351 padding: 0;
352 border: none;
353 background: none;
354 text-align: center;
355 cursor: pointer;
356 }
357
358 input[type="checkbox"]::after {
359 content: "[ ]";
360 }
361
362 input[type="checkbox"]:checked::after {
363 content: "[x]";
364 }
365
366 fieldset {
367 /* min-inline-size overrides the min-content floor browsers give fieldsets,
368 which otherwise makes them overflow flex and grid containers. */
369 min-inline-size: 0;
370 margin: 0;
371 border: var(--border-width) solid var(--border);
372 border-radius: var(--radius);
373 padding: var(--s-1) var(--s0);
374 }
375
376 legend {
377 padding-inline: var(--s-2);
378 }
379
380 legend::before {
381 content: "( ";
382 }
383
384 legend::after {
385 content: " )";
386 }
387
388 summary {
389 cursor: help;
390 }
391
392 summary::marker {
393 content: "> ";
394 }
395
396 details[open] > summary::marker {
397 content: "x ";
398 }
399
400 /* A heading is allowed to be a summary without breaking the line. */
401 summary > :is(h1, h2, h3, h4, h5, h6) {
402 display: inline;
403 }
404
405
406 /* 6. Components ============================================================ */
407
408 /* -- Site header --------------------------------------------------------- */
409
410 header {
411 display: flex;
412 flex-wrap: wrap;
413 align-items: center;
414 gap: var(--s0);
415 }
416
417 header > h1 {
418 margin-block: 0;
419 }
420
421 /* -- Site footer --------------------------------------------------------- */
422
423 /* Markers stay outside the text column, in a gutter wide enough for the
424 longest of them ("==> "). That way the arrow slides right on hover and
425 focus while the link itself never moves. The column gap is measured in the
426 same unit, so the second column's markers cannot reach the first. */
427 footer ul {
428 columns: 2 20ch;
429 padding-inline-start: 4ch;
430 column-gap: 5ch;
431 }
432
433 footer li::marker {
434 content: "> ";
435 }
436
437 footer li:has(a:hover)::marker {
438 content: " >";
439 }
440
441 footer li:has(a:focus)::marker {
442 content: "==> ";
443 }
444
445 footer li:has(a:hover:focus)::marker {
446 content: "==>";
447 }
448
449 /* NavLink marks the current page for us. */
450 footer li:has(a.active)::marker {
451 content: "* ";
452 }
453
454 /* -- Panels (bordered boxes with a title) -------------------------------- */
455
456 .panel {
457 margin-block: var(--s1);
458 border: 2px dashed var(--border);
459 padding: var(--s-1) var(--s0);
460 }
461
462 /* -- Log ------------------------------------------------------------------ */
463
464 #log {
465 margin-block-start: var(--s0);
466 }
467
468 .error::before,
469 .info::before,
470 .debug::before {
471 /* not selectable, so copying a log line copies the message only */
472 user-select: none;
473 -webkit-user-select: none;
474 }
475
476 .error::before {
477 content: "ERR: ";
478 color: var(--error);
479 }
480
481 .info::before {
482 content: "INF: ";
483 color: var(--info);
484 }
485
486 .debug::before {
487 content: "DBG: ";
488 color: var(--info);
489 }
490
491 /* Debug lines stay hidden until the checkbox next to the log is ticked. */
492 label:has(> #debug:not(:checked)) + #log .debug {
493 display: none;
494 }
495
496 /* Earlier runs, folded away and dimmed. */
497 .history {
498 opacity: 30%;
499 }
500
501 * + .history {
502 margin-block-start: var(--s1);
503 }
504
505 /* -- Rendered JSON -------------------------------------------------------- */
506
507 li.dash {
508 list-style-type: "- ";
509 }
510
511 span.name {
512 opacity: 50%;
513 }
514
515 .json-true {
516 color: var(--green);
517 }
518
519 .json-false {
520 color: var(--error);
521 }
522
523 .json-number {
524 color: var(--info);
525 }
526
527 .json-null {
528 opacity: 50%;
529 }
530
531 /* -- Dialogs -------------------------------------------------------------- */
532
533 dialog {
534 background-color: transparent;
535 color: var(--color);
536 border: none;
537 padding: 0;
538 max-inline-size: min(100% - var(--s2), 60ch);
539 }
540
541 dialog::backdrop {
542 backdrop-filter: blur(calc(var(--s1) / 20));
543 }
544
545 /* The panel inside supplies the surface, so the dialog itself can stay bare. */
546 dialog > .panel {
547 margin-block: 0;
548 background-color: var(--background);
549 }
550
551 dialog > .panel > legend {
552 background-color: var(--background);
553 border-radius: var(--radius);
554 }
555
556 /* -- Tables --------------------------------------------------------------- */
557
558 table {
559 inline-size: 100%;
560 border-collapse: collapse;
561 }
562
563 :is(th, td) {
564 text-align: start;
565 padding: var(--s-2) var(--s-1);
566 padding-inline-start: 0;
567 border-block-end: var(--border-width) solid var(--border);
568 vertical-align: baseline;
569 }
570
571 tbody tr:last-child :is(th, td) {
572 border-block-end: none;
573 }
574
575 /* A table with more columns than the page has room for scrolls inside its own
576 box rather than pushing the whole page sideways. */
577 .table-scroll {
578 overflow-x: auto;
579 }
580
581
582 /* -- rvrb stats ----------------------------------------------------------- */
583
584 .status-up {
585 color: var(--green);
586 }
587
588 .status-idle {
589 color: var(--info);
590 }
591
592 .status-down {
593 color: var(--error);
594 }
595
596 .muted {
597 opacity: 50%;
598 }
599
600 .now-playing {
601 display: flex;
602 flex-wrap: wrap;
603 gap: var(--s0);
604 align-items: start;
605 }
606
607 /* Overrides the block margin and auto-centring the base `img` rule gives every
608 image, which would otherwise push the art away from the track it belongs to. */
609 .now-playing .album-art {
610 margin: 0;
611 flex: none;
612 inline-size: 8em;
613 block-size: 8em;
614 border-radius: var(--radius);
615 }
616
617 .now-playing > div {
618 flex: 1 1 20ch;
619 }
620
621 progress {
622 inline-size: 100%;
623 block-size: var(--s-1);
624 }
625
626 /* Counts big enough to read at a glance, laid out as many per row as fit. */
627 .stats {
628 display: grid;
629 grid-template-columns: repeat(auto-fit, minmax(10ch, 1fr));
630 gap: var(--s-1);
631 margin-block: 0;
632 }
633
634 .stats dt {
635 opacity: 60%;
636 }
637
638 .stats dd {
639 margin-inline-start: 0;
640 font-size: calc(1em * var(--ratio));
641 }
642
643 /* Plays per day. One <li> per day, so a quiet day is an empty column rather
644 than a gap - see Rvrb.Stats.plays_per_day/1.
645
646 A grid rather than a flex row, because a bar's height is a percentage and
647 can only resolve against a track with a height of its own. Sharing one 6em
648 box with the labels made the two compete for it: flex shrank whichever bar
649 was tall enough to overflow *and* the labels under it, so the tallest bars
650 ended up hanging below the baseline the rest stood on. */
651 .bars {
652 /* A container, so the labels below can react to how much room each column
653 actually got rather than to the width of the viewport. */
654 container-type: inline-size;
655 display: grid;
656 grid-auto-flow: column;
657 grid-auto-columns: 1fr;
658 /* the bar track, then a row each for the count and the weekday */
659 grid-template-rows: 6em auto auto;
660 /* Column gap only: the labels belong against the bar they describe. */
661 gap: 0 var(--border-width);
662 margin-block: 0;
663 padding-inline: 0;
664 list-style: none;
665 }
666
667 /* subgrid, so every column's count and weekday line up on the same two rows
668 however tall the bar above them is. */
669 .bars li {
670 display: grid;
671 grid-row: span 3;
672 grid-template-rows: subgrid;
673 align-items: end;
674 justify-items: center;
675 }
676
677 .bars .bar {
678 inline-size: 100%;
679 background-color: var(--color);
680 }
681
682 .bars :is(.bar-label, .bar-day) {
683 font-size: 0.7em;
684 opacity: 60%;
685 }
686
687 /* Two weeks of columns in one text column: the day has to give way before the
688 count does, since the count is the number and the day is the axis. */
689 .bars .bar-day {
690 overflow: hidden;
691 max-inline-size: 100%;
692 /* Clipped rather than wrapped: three letters over two lines would push the
693 bars up and misalign every column. */
694 white-space: nowrap;
695 }
696
697 .bars .day-initial {
698 display: none;
699 }
700
701 /* "Thu" wants about 3.2ch, so a fortnight of them needs roughly this much
702 chart before the columns stop being wide enough to hold three letters. On a
703 phone they don't, and the day falls back to its initial rather than being
704 clipped mid-letter. */
705 @container (width < 21rem) {
706 .bars .day-abbrev {
707 display: none;
708 }
709
710 .bars .day-initial {
711 display: inline;
712 }
713 }
714
715
716 /* -- Storage ------------------------------------------------------------- */
717
718 .grid-stores {
719 display: grid;
720 grid-template-columns: 1fr auto auto;
721 align-items: center;
722 gap: var(--s-1) var(--s0);
723 }
724
725 /* -- Warframe drops ------------------------------------------------------- */
726
727 /* Chances are compared down the column, so they align right and shrink to fit. */
728 .chance {
729 text-align: end;
730 font-variant-numeric: tabular-nums;
731 inline-size: 1%;
732 white-space: nowrap;
733 }
734
735 /* Up to forty names, so columns rather than one long list. Grid rather than
736 `columns`, which fragments: a name too long for its column carried its last
737 word over into the next one. A grid item cannot break across columns, so a
738 long name wraps inside its own cell instead, and filling row by row puts the
739 closest match first in reading order. 26ch fits nine names in ten on one
740 line while still leaving room for a second column; the min() caps that floor
741 at the container, which a bare minmax() would overflow on a phone. */
742 .matches {
743 list-style: none;
744 margin: 0;
745 padding: 0;
746 display: grid;
747 grid-template-columns: repeat(auto-fill, minmax(min(26ch, 100%), 1fr));
748 /* The row gap is what keeps two names apart once they are narrow enough to
749 wrap onto a second line each. */
750 gap: var(--s-2) var(--s0);
751 }
752
753 .matches a.active {
754 font-weight: 700;
755 }
756
757 .matches a.active::before {
758 content: "> ";
759 }
760
761 /* Follows the search box or the match list, neither of which leaves room under
762 itself. */
763 .item-name {
764 margin-block-start: var(--s1);
765 }
766
767 /* Labels the table under it. Outside .table-scroll on purpose: inside, it
768 scrolled out of sight on a narrow screen. */
769 .relic-lead {
770 margin-block: var(--s-2) 0;
771 }
772
773 /* An offsite link sitting beside a name that already links within the site.
774 Dimmed and smaller so the two read as different kinds of thing, and it does
775 not compete with the name it follows. */
776 .wiki {
777 font-size: 0.8em;
778 font-weight: 400;
779 opacity: 50%;
780 white-space: nowrap;
781 }
782
783 .wiki:hover,
784 .wiki:focus {
785 opacity: 100%;
786 }
787
788 /* Each relic is a heading and its own small table. */
789 .relic + .relic {
790 margin-block-start: var(--s0);
791 padding-block-start: var(--s0);
792 border-block-start: var(--rule);
793 }
794
795 /* Name, rarity and both chances on one line, stacking when there is no room. */
796 .relic > h3 {
797 display: flex;
798 flex-wrap: wrap;
799 gap: var(--s-1);
800 align-items: baseline;
801 font-size: 1em;
802 }
803
804
805 /* -- Send (peer-to-peer file transfer) ------------------------------------ */
806
807 /* A target big enough to aim a dragged file at, dashed so it reads as a place to drop rather
808 than a box with something in it. */
809 .dropzone {
810 border: var(--border-width) dashed var(--border);
811 border-radius: var(--radius);
812 padding: var(--s0);
813 text-align: center;
814 }
815
816 .dropzone > p {
817 margin-block-end: 0;
818 }
819
820 /* The only feedback that the drop will land here. Translucent so it works over either scheme
821 without a token of its own. */
822 .dropzone.over {
823 background-color: color-mix(in srgb, var(--color) 12%, transparent);
824 }
825
826 .transfers {
827 list-style: none;
828 margin: 0;
829 padding: 0;
830 }
831
832 .transfers:not(:empty) {
833 margin-block-start: var(--s0);
834 }
835
836 /* Name and size on one line, bar and status under them: two rows of two, with the name free to
837 wrap and the two right-hand cells sized to their text. */
838 .transfer {
839 display: grid;
840 grid-template-columns: minmax(0, 1fr) auto;
841 column-gap: var(--s0);
842 align-items: baseline;
843 }
844
845 .transfer + .transfer {
846 margin-block-start: var(--s-1);
847 }
848
849 /* A file name is not prose and has no spaces to break at. */
850 .transfer-name {
851 overflow-wrap: anywhere;
852 }
853
854 .transfer-size,
855 .transfer-status {
856 white-space: nowrap;
857 font-variant-numeric: tabular-nums;
858 }
859
860 /* The one control that arrives with a colour of its own: the browser's accent is nowhere else
861 on this site. */
862 .transfer progress {
863 inline-size: 100%;
864 accent-color: var(--color);
865 }
866
867 .transfer.failed .transfer-status {
868 color: var(--error);
869 }
870
871 /* Both sides in one column, oldest first, told apart by the name on each rather than by which
872 side of the panel they sit on: a bubble layout needs a colour or a width to spend, and this
873 page has an 80ch measure and no colour. */
874 .messages {
875 list-style: none;
876 margin: 0 0 var(--s0);
877 padding: 0;
878 max-block-size: 60vh;
879 overflow-y: auto;
880 }
881
882 .messages:empty {
883 display: none;
884 }
885
886 /* Who, when and a copy button on one line, the text under them across the whole row. */
887 .message {
888 display: grid;
889 grid-template-columns: auto 1fr auto;
890 column-gap: var(--s0);
891 align-items: baseline;
892 }
893
894 .message + .message {
895 border-block-start: var(--rule);
896 margin-block-start: var(--s-1);
897 padding-block-start: var(--s-1);
898 }
899
900 .message-from {
901 font-weight: bold;
902 }
903
904 .message.from-you .message-from {
905 font-weight: normal;
906 }
907
908 .message time {
909 font-variant-numeric: tabular-nums;
910 }
911
912 .message-copy {
913 font-size: 0.8em;
914 }
915
916 /* What was typed, as it was typed: its own line breaks, and a pasted URL or token with no spaces
917 still wrapping instead of pushing the panel wide. */
918 .message-text {
919 grid-column: 1 / -1;
920 margin: 0;
921 white-space: pre-wrap;
922 overflow-wrap: anywhere;
923 }
924
925
926 /* -- Note (rich text editor) ---------------------------------------------- */
927
928 .note-toolbar {
929 display: flex;
930 flex-wrap: wrap;
931 gap: var(--s-1);
932 margin-block-end: var(--s-1);
933 }
934
935 /* The row and column buttons, on a row of their own under the rest and only while the caret is
936 in a table. */
937 .note-toolbar-group {
938 flex-basis: 100%;
939 display: flex;
940 flex-wrap: wrap;
941 gap: var(--s-1);
942 }
943
944 .note-toolbar-group[hidden] {
945 display: none;
946 }
947
948 /* One letter or two words each: the 8ch every other button gets would make this a row of gaps. */
949 .note-toolbar button {
950 min-inline-size: 4ch;
951 padding-inline: var(--s-1);
952 }
953
954 /* On for the text under the caret: drawn the way a hovered button is, since that is the only
955 emphasis a button here has. */
956 .note-toolbar button[aria-pressed="true"] {
957 background-color: var(--color);
958 color: var(--background);
959 }
960
961 /* Dressed as the textarea it replaced. */
962 .note-editor {
963 border: var(--border-width) solid var(--border);
964 border-radius: var(--radius);
965 padding: 0.1em 0.5em;
966 overflow-y: auto;
967 overflow-wrap: break-word;
968 cursor: text;
969 }
970
971 /* A nested list is one more line of its parent, not a new paragraph. */
972 .note-editor :is(ul, ol) :is(ul, ol) {
973 margin-block: 0;
974 }
975
976 .note-editor table {
977 margin-block: var(--s-1);
978 }
979
980 /* Every cell boxed, unlike the rules-only tables elsewhere: an empty cell has to show where to
981 click. Written to match the specificity of the last-row rule under Tables, which it has to
982 beat by coming later. */
983 .note-editor table tr :is(th, td) {
984 border: var(--border-width) dotted var(--border);
985 padding-inline: var(--s-1);
986 vertical-align: top;
987 }
988
989
990 /* -- Scroll shortcuts ------------------------------------------------------ */
991
992 /* Docked to the viewport edge rather than the page flow, so it stays
993 reachable no matter how far down a long page (e.g. Query's history and
994 favorites) you've scrolled. */
995 .scroll-nav {
996 position: fixed;
997 inset-inline-end: var(--s0);
998 inset-block-start: 50%;
999 transform: translateY(-50%);
1000 display: flex;
1001 flex-direction: column;
1002 gap: var(--s-1);
1003 z-index: 100;
1004 }
1005
1006 .scroll-nav button {
1007 line-height: 1;
1008 padding: var(--s-1);
1009 }
1010
1011
1012 /* 7. Media ================================================================= */
1013
1014 #logo {
1015 block-size: 5em;
1016 flex: none;
1017 }
1018
1019 img {
1020 display: block;
1021 max-inline-size: 100%;
1022 block-size: auto;
1023 margin-inline: auto;
1024 margin-block: var(--s0);
1025 }
1026
1027 video {
1028 display: block;
1029 inline-size: 100%;
1030 block-size: auto;
1031 border-radius: var(--radius);
1032 }
1033
1034 #qrcode {
1035 margin-block: var(--s0);
1036 text-align: center;
1037 }
1038
1039 #qrcode :is(canvas, table) {
1040 margin-inline: auto;
1041 }