Blog/wwwroot/app.css 24 K · 1059 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 /* A link is one line, cut off: a saved note's first line can be a paragraph, and a bare URL can
726 be several kilobytes of base64. A fieldset is at least as wide as its content
727 unless told otherwise, which would push the page sideways instead. */
728 fieldset:has(> .grid-stores) {
729 min-inline-size: 0;
730 }
731
732 .store-link {
733 min-inline-size: 0;
734 overflow: hidden;
735 text-overflow: ellipsis;
736 white-space: nowrap;
737 }
738
739 /* -- Warframe drops ------------------------------------------------------- */
740
741 /* Chances are compared down the column, so they align right and shrink to fit. */
742 .chance {
743 text-align: end;
744 font-variant-numeric: tabular-nums;
745 inline-size: 1%;
746 white-space: nowrap;
747 }
748
749 /* Up to forty names, so columns rather than one long list. Grid rather than
750 `columns`, which fragments: a name too long for its column carried its last
751 word over into the next one. A grid item cannot break across columns, so a
752 long name wraps inside its own cell instead, and filling row by row puts the
753 closest match first in reading order. 26ch fits nine names in ten on one
754 line while still leaving room for a second column; the min() caps that floor
755 at the container, which a bare minmax() would overflow on a phone. */
756 .matches {
757 list-style: none;
758 margin: 0;
759 padding: 0;
760 display: grid;
761 grid-template-columns: repeat(auto-fill, minmax(min(26ch, 100%), 1fr));
762 /* The row gap is what keeps two names apart once they are narrow enough to
763 wrap onto a second line each. */
764 gap: var(--s-2) var(--s0);
765 }
766
767 .matches a.active {
768 font-weight: 700;
769 }
770
771 .matches a.active::before {
772 content: "> ";
773 }
774
775 /* Follows the search box or the match list, neither of which leaves room under
776 itself. */
777 .item-name {
778 margin-block-start: var(--s1);
779 }
780
781 /* Labels the table under it. Outside .table-scroll on purpose: inside, it
782 scrolled out of sight on a narrow screen. */
783 .relic-lead {
784 margin-block: var(--s-2) 0;
785 }
786
787 /* An offsite link sitting beside a name that already links within the site.
788 Dimmed and smaller so the two read as different kinds of thing, and it does
789 not compete with the name it follows. */
790 .wiki {
791 font-size: 0.8em;
792 font-weight: 400;
793 opacity: 50%;
794 white-space: nowrap;
795 }
796
797 .wiki:hover,
798 .wiki:focus {
799 opacity: 100%;
800 }
801
802 /* Each relic is a heading and its own small table. */
803 .relic + .relic {
804 margin-block-start: var(--s0);
805 padding-block-start: var(--s0);
806 border-block-start: var(--rule);
807 }
808
809 /* Name, rarity and both chances on one line, stacking when there is no room. */
810 .relic > h3 {
811 display: flex;
812 flex-wrap: wrap;
813 gap: var(--s-1);
814 align-items: baseline;
815 font-size: 1em;
816 }
817
818
819 /* -- Send (peer-to-peer file transfer) ------------------------------------ */
820
821 /* A target big enough to aim a dragged file at, dashed so it reads as a place to drop rather
822 than a box with something in it. */
823 .dropzone {
824 border: var(--border-width) dashed var(--border);
825 border-radius: var(--radius);
826 padding: var(--s0);
827 text-align: center;
828 }
829
830 .dropzone > p {
831 margin-block-end: 0;
832 }
833
834 /* The only feedback that the drop will land here. Translucent so it works over either scheme
835 without a token of its own. */
836 .dropzone.over {
837 background-color: color-mix(in srgb, var(--color) 12%, transparent);
838 }
839
840 .transfers {
841 list-style: none;
842 margin: 0;
843 padding: 0;
844 }
845
846 .transfers:not(:empty) {
847 margin-block-start: var(--s0);
848 }
849
850 /* Name and size on one line, bar and status under them: two rows of two, with the name free to
851 wrap and the two right-hand cells sized to their text. */
852 .transfer {
853 display: grid;
854 grid-template-columns: minmax(0, 1fr) auto;
855 column-gap: var(--s0);
856 align-items: baseline;
857 }
858
859 .transfer + .transfer {
860 margin-block-start: var(--s-1);
861 }
862
863 /* A file name is not prose and has no spaces to break at. */
864 .transfer-name {
865 overflow-wrap: anywhere;
866 }
867
868 .transfer-size,
869 .transfer-status {
870 white-space: nowrap;
871 font-variant-numeric: tabular-nums;
872 }
873
874 /* The one control that arrives with a colour of its own: the browser's accent is nowhere else
875 on this site. */
876 .transfer progress {
877 inline-size: 100%;
878 accent-color: var(--color);
879 }
880
881 .transfer.failed .transfer-status {
882 color: var(--error);
883 }
884
885 /* Both sides in one column, oldest first, told apart by the name on each rather than by which
886 side of the panel they sit on: a bubble layout needs a colour or a width to spend, and this
887 page has an 80ch measure and no colour. */
888 .messages {
889 list-style: none;
890 margin: 0 0 var(--s0);
891 padding: 0;
892 max-block-size: 60vh;
893 overflow-y: auto;
894 }
895
896 .messages:empty {
897 display: none;
898 }
899
900 /* Who, when and a copy button on one line, the text under them across the whole row. */
901 .message {
902 display: grid;
903 grid-template-columns: auto 1fr auto;
904 column-gap: var(--s0);
905 align-items: baseline;
906 }
907
908 .message + .message {
909 border-block-start: var(--rule);
910 margin-block-start: var(--s-1);
911 padding-block-start: var(--s-1);
912 }
913
914 .message-from {
915 font-weight: bold;
916 }
917
918 .message.from-you .message-from {
919 font-weight: normal;
920 }
921
922 .message time {
923 font-variant-numeric: tabular-nums;
924 }
925
926 .message-copy {
927 font-size: 0.8em;
928 }
929
930 /* What was typed, as it was typed: its own line breaks, and a pasted URL or token with no spaces
931 still wrapping instead of pushing the panel wide. */
932 .message-text {
933 grid-column: 1 / -1;
934 margin: 0;
935 white-space: pre-wrap;
936 overflow-wrap: anywhere;
937 }
938
939
940 /* -- Note (rich text editor) ---------------------------------------------- */
941
942 .note-toolbar {
943 display: flex;
944 flex-wrap: wrap;
945 gap: var(--s-1);
946 margin-block-end: var(--s-1);
947 }
948
949 /* The row and column buttons, on a row of their own under the rest and only while the caret is
950 in a table. */
951 .note-toolbar-group {
952 flex-basis: 100%;
953 display: flex;
954 flex-wrap: wrap;
955 gap: var(--s-1);
956 }
957
958 .note-toolbar-group[hidden] {
959 display: none;
960 }
961
962 /* One letter or two words each: the 8ch every other button gets would make this a row of gaps. */
963 .note-toolbar button {
964 min-inline-size: 4ch;
965 padding-inline: var(--s-1);
966 }
967
968 /* On for the text under the caret: drawn the way a hovered button is, since that is the only
969 emphasis a button here has. */
970 .note-toolbar button[aria-pressed="true"] {
971 background-color: var(--color);
972 color: var(--background);
973 }
974
975 .note-actions {
976 margin-block-start: var(--s-1);
977 }
978
979 /* Dressed as the textarea it replaced. */
980 .note-editor {
981 border: var(--border-width) solid var(--border);
982 border-radius: var(--radius);
983 padding: 0.1em 0.5em;
984 overflow-y: auto;
985 overflow-wrap: break-word;
986 cursor: text;
987 }
988
989 /* A nested list is one more line of its parent, not a new paragraph. */
990 .note-editor :is(ul, ol) :is(ul, ol) {
991 margin-block: 0;
992 }
993
994 .note-editor table {
995 margin-block: var(--s-1);
996 }
997
998 /* Every cell boxed, unlike the rules-only tables elsewhere: an empty cell has to show where to
999 click. Written to match the specificity of the last-row rule under Tables, which it has to
1000 beat by coming later. */
1001 .note-editor table tr :is(th, td) {
1002 border: var(--border-width) dotted var(--border);
1003 padding-inline: var(--s-1);
1004 vertical-align: top;
1005 }
1006
1007
1008 /* -- Scroll shortcuts ------------------------------------------------------ */
1009
1010 /* Docked to the viewport edge rather than the page flow, so it stays
1011 reachable no matter how far down a long page (e.g. Query's history and
1012 favorites) you've scrolled. */
1013 .scroll-nav {
1014 position: fixed;
1015 inset-inline-end: var(--s0);
1016 inset-block-start: 50%;
1017 transform: translateY(-50%);
1018 display: flex;
1019 flex-direction: column;
1020 gap: var(--s-1);
1021 z-index: 100;
1022 }
1023
1024 .scroll-nav button {
1025 line-height: 1;
1026 padding: var(--s-1);
1027 }
1028
1029
1030 /* 7. Media ================================================================= */
1031
1032 #logo {
1033 block-size: 5em;
1034 flex: none;
1035 }
1036
1037 img {
1038 display: block;
1039 max-inline-size: 100%;
1040 block-size: auto;
1041 margin-inline: auto;
1042 margin-block: var(--s0);
1043 }
1044
1045 video {
1046 display: block;
1047 inline-size: 100%;
1048 block-size: auto;
1049 border-radius: var(--radius);
1050 }
1051
1052 #qrcode {
1053 margin-block: var(--s0);
1054 text-align: center;
1055 }
1056
1057 #qrcode :is(canvas, table) {
1058 margin-inline: auto;
1059 }