Skip to main content

max / makenotwork

33.1 KB · 1897 lines History Blame Raw
1 /* Multithreaded, Forum Stylesheet. Adapted from Makenot.work design language */
2
3 /* FONTS */
4
5 @font-face {
6 font-family: "Young Serif";
7 src: url("/static/fonts/ysrf.woff2") format("woff2"),
8 url("/static/fonts/ysrf.ttf") format("truetype");
9 font-display: swap;
10 }
11
12 @font-face {
13 font-family: "IBM Plex Mono";
14 src: url("/static/fonts/IBMPlexMono-Regular.woff2") format("woff2"),
15 url("/static/fonts/IBMPlexMono-Regular.ttf") format("truetype");
16 font-weight: normal;
17 font-display: swap;
18 }
19
20 @font-face {
21 font-family: "IBM Plex Mono";
22 src: url("/static/fonts/IBMPlexMono-Bold.woff2") format("woff2"),
23 url("/static/fonts/IBMPlexMono-Bold.ttf") format("truetype");
24 font-weight: bold;
25 font-display: swap;
26 }
27
28 @font-face {
29 font-family: "Lato";
30 src: url("/static/fonts/Lato-Regular.woff2") format("woff2"),
31 url("/static/fonts/Lato-Regular.ttf") format("truetype");
32 font-weight: normal;
33 font-display: swap;
34 }
35
36 @font-face {
37 font-family: "Lato";
38 src: url("/static/fonts/Lato-Bold.woff2") format("woff2"),
39 url("/static/fonts/Lato-Bold.ttf") format("truetype");
40 font-weight: bold;
41 font-display: swap;
42 }
43
44 /* VARIABLES */
45
46 :root {
47 --background: #ede8e1;
48 --detail: #3d3530;
49 --highlight: #6c5ce7;
50 --light-background: #f4f0eb;
51
52 --surface-muted: #ddd7c5;
53 --surface-alt: #ebe7db;
54 --surface-border: #d0cbb8;
55 --border: #d0cbb8;
56 --text-muted: #8a8480;
57 --input-background: #e2dad2;
58
59 --primary-dark: #000000;
60 --primary-light: #ffffff;
61 --success: #2d5016;
62 --warning: #8b7355;
63 --danger: #d62828;
64 --error: #d62828;
65
66 --focus-ring: #6c5ce7;
67 }
68
69 /* RESET */
70
71 * {
72 margin: 0;
73 padding: 0;
74 box-sizing: border-box;
75 }
76
77 /* BASE */
78
79 body {
80 margin: 0;
81 background-color: var(--background);
82 color: var(--detail);
83 font-family: "Lato", sans-serif;
84 line-height: 1.5;
85 }
86
87 h1 {
88 font-family: "Young Serif", serif;
89 font-weight: normal;
90 color: var(--detail);
91 text-align: left;
92 font-size: 1.5rem;
93 }
94
95 h2,
96 h3 {
97 font-family: "IBM Plex Mono", monospace;
98 font-weight: normal;
99 color: var(--detail);
100 }
101
102 p {
103 font-family: "Lato", sans-serif;
104 color: var(--detail);
105 }
106
107 a {
108 color: var(--detail);
109 text-decoration: none;
110 }
111
112 a:hover {
113 text-decoration: underline;
114 }
115
116 /* The signature dot */
117 .dot {
118 color: var(--highlight);
119 }
120
121 /* LAYOUT */
122
123 .container {
124 max-width: 1200px;
125 margin: 0 auto;
126 padding: 1rem;
127 }
128
129 .padded-page {
130 padding: 1.25rem;
131 }
132
133 /* SITE HEADER & NAV */
134
135 .site-header {
136 display: flex;
137 justify-content: space-between;
138 align-items: center;
139 padding: 0.75rem 1.25rem;
140 margin-bottom: 0.5rem;
141 }
142
143 .site-header nav {
144 margin: 0;
145 }
146
147 .site-header .nav-links {
148 display: flex;
149 gap: 1.5rem;
150 align-items: center;
151 }
152
153 .nav-links a {
154 color: var(--detail);
155 text-decoration: none;
156 font-family: "IBM Plex Mono", monospace;
157 transition: opacity 0.2s ease;
158 }
159
160 .nav-links a:hover {
161 opacity: 0.6;
162 text-decoration: none;
163 }
164
165 .site-logo {
166 font-family: "Young Serif", serif;
167 font-size: 1.25rem;
168 text-decoration: none;
169 color: var(--detail);
170 }
171
172 .site-logo:hover {
173 opacity: 0.8;
174 text-decoration: none;
175 }
176
177 .link-button {
178 background: none;
179 border: none;
180 color: var(--detail);
181 font-family: "IBM Plex Mono", monospace;
182 font-size: 1rem;
183 cursor: pointer;
184 padding: 0;
185 text-decoration: none;
186 transition: opacity 0.2s ease;
187 }
188
189 .link-button:hover {
190 opacity: 0.6;
191 text-decoration: underline;
192 }
193
194 .nav-form {
195 display: contents;
196 }
197
198 /* Hamburger toggle */
199 .nav-toggle-checkbox {
200 display: none;
201 }
202
203 .nav-toggle-label {
204 display: none;
205 cursor: pointer;
206 padding: 0.5rem;
207 z-index: 11;
208 }
209
210 .nav-toggle-label span {
211 display: block;
212 width: 22px;
213 height: 2px;
214 background: var(--detail);
215 margin: 5px 0;
216 transition: transform 0.2s ease, opacity 0.2s ease;
217 }
218
219 /* BUTTONS */
220
221 button {
222 color: var(--detail);
223 background: var(--light-background);
224 padding: 6px 12px;
225 border: 1px solid var(--detail);
226 font-family: inherit;
227 cursor: pointer;
228 transition: opacity 0.2s ease;
229 }
230
231 button:hover {
232 opacity: 0.8;
233 }
234
235 button.primary,
236 .btn-primary {
237 background: var(--primary-dark);
238 color: var(--primary-light);
239 border: none;
240 padding: 0.5rem 1rem;
241 font-family: inherit;
242 font-size: 0.85rem;
243 cursor: pointer;
244 transition: opacity 0.2s ease;
245 }
246
247 button.primary:hover,
248 .btn-primary:hover {
249 opacity: 0.8;
250 }
251
252 button.secondary,
253 .btn-secondary {
254 background: var(--surface-muted);
255 color: var(--detail);
256 border: none;
257 padding: 0.5rem 1rem;
258 font-family: "IBM Plex Mono", monospace;
259 font-size: 0.85rem;
260 cursor: pointer;
261 transition: opacity 0.2s ease;
262 }
263
264 button.secondary:hover,
265 .btn-secondary:hover {
266 opacity: 0.8;
267 }
268
269 /* Irreversible actions. The class was in the markup before it was in here (the
270 admin clean-slate button, the chat wipe), rendering as an ordinary button,
271 which is the one thing a destructive control must not look like. */
272 button.danger {
273 background: var(--danger);
274 color: var(--primary-light);
275 border: none;
276 padding: 0.5rem 1rem;
277 font-family: inherit;
278 font-size: 0.85rem;
279 cursor: pointer;
280 transition: opacity 0.2s ease;
281 }
282
283 button.danger:hover {
284 opacity: 0.8;
285 }
286
287 /* FORMS */
288
289 form {
290 display: flex;
291 flex-direction: column;
292 gap: 1rem;
293 width: 100%;
294 }
295
296 .form-container {
297 max-width: 600px;
298 background: var(--light-background);
299 padding: 1.5rem;
300 }
301
302 label {
303 font-family: "IBM Plex Mono", monospace;
304 color: var(--detail);
305 font-size: 14px;
306 }
307
308 input[type="text"],
309 input[type="email"],
310 input[type="password"],
311 input[type="number"],
312 textarea,
313 select {
314 width: 100%;
315 background: var(--input-background);
316 border: none;
317 padding: 0.6rem;
318 font-family: "IBM Plex Mono", monospace;
319 font-size: 0.85rem;
320 color: var(--detail);
321 }
322
323 input::placeholder,
324 textarea::placeholder {
325 opacity: 0.5;
326 }
327
328 input[type="text"]:focus,
329 input[type="email"]:focus,
330 input[type="password"]:focus,
331 input[type="number"]:focus,
332 textarea:focus,
333 select:focus {
334 outline: none;
335 border: 2px solid var(--highlight);
336 }
337
338 textarea {
339 min-height: 150px;
340 resize: vertical;
341 }
342
343 textarea.drag-over {
344 border: 2px dashed var(--highlight);
345 background: var(--surface-muted);
346 }
347
348 /* Inline form validation error, inserted by mt.js on a 422 submit, replacing
349 the old error toast. Persists next to the form with the input preserved. */
350 .form-error {
351 background: var(--surface-muted);
352 border-left: 3px solid var(--error);
353 color: var(--error);
354 padding: 0.6rem 0.75rem;
355 margin-bottom: 0.75rem;
356 font-size: 0.85rem;
357 }
358
359 input[aria-invalid="true"],
360 textarea[aria-invalid="true"],
361 select[aria-invalid="true"] {
362 border: 2px solid var(--error);
363 }
364
365 select {
366 cursor: pointer;
367 }
368
369 input[type="submit"],
370 form button {
371 font-family: "Young Serif", serif;
372 font-size: 16px;
373 color: var(--detail);
374 background: var(--light-background);
375 padding: 10px 20px;
376 border: 1px solid var(--detail);
377 cursor: pointer;
378 }
379
380 input[type="submit"]:hover,
381 form button:hover {
382 background: var(--background);
383 }
384
385 .form-row {
386 display: grid;
387 grid-template-columns: 1fr 1fr;
388 gap: 1rem;
389 }
390
391 .form-group {
392 display: flex;
393 flex-direction: column;
394 gap: 0.25rem;
395 }
396
397 .form-actions {
398 display: flex;
399 gap: 1rem;
400 margin-top: 0.5rem;
401 }
402
403 .form-help {
404 display: block;
405 font-size: 0.8rem;
406 color: var(--text-muted);
407 margin-top: 0.25rem;
408 }
409
410 .input-narrow {
411 width: 6rem;
412 }
413
414 /* DIRECTORY TABLE (forum home) */
415
416 .directory-table {
417 width: 100%;
418 border-collapse: collapse;
419 font-size: 0.85rem;
420 font-family: "Lato", sans-serif;
421 }
422
423 .directory-table thead {
424 background: var(--surface-muted);
425 }
426
427 .directory-table th {
428 text-align: left;
429 padding: 0.35rem 0.75rem;
430 font-family: "IBM Plex Mono", monospace;
431 font-size: 0.7rem;
432 font-weight: normal;
433 text-transform: uppercase;
434 letter-spacing: 0.05em;
435 color: var(--text-muted);
436 }
437
438 .directory-table tbody tr {
439 transition: background 0.1s ease;
440 }
441
442 .directory-table tbody tr:nth-child(odd) {
443 background: var(--surface-alt);
444 }
445
446 .directory-table tbody tr:nth-child(even) {
447 background: var(--light-background);
448 }
449
450 .directory-table tbody tr:hover {
451 background: var(--surface-muted);
452 }
453
454 .directory-table td {
455 padding: 0.4rem 0.75rem;
456 border-top: 1px solid var(--border);
457 }
458
459 .directory-name {
460 font-family: "Young Serif", serif;
461 font-size: 1rem;
462 }
463
464 .directory-name a {
465 color: var(--detail);
466 text-decoration: none;
467 }
468
469 .directory-name a:hover {
470 text-decoration: underline;
471 }
472
473 .directory-desc {
474 font-size: 0.8rem;
475 color: var(--text-muted);
476 margin-top: 0.1rem;
477 }
478
479 .directory-table .col-creator {
480 white-space: nowrap;
481 width: 130px;
482 font-family: "IBM Plex Mono", monospace;
483 font-size: 0.8rem;
484 }
485
486 .directory-table .col-type {
487 white-space: nowrap;
488 width: 100px;
489 font-family: "IBM Plex Mono", monospace;
490 font-size: 0.8rem;
491 color: var(--text-muted);
492 }
493
494 .directory-table .col-items {
495 white-space: nowrap;
496 width: 60px;
497 text-align: right;
498 font-family: "IBM Plex Mono", monospace;
499 font-size: 0.8rem;
500 color: var(--text-muted);
501 }
502
503 /* DATA TABLE (dense thread listings) */
504
505 .data-table {
506 width: 100%;
507 border-collapse: collapse;
508 font-size: 0.85rem;
509 font-family: "Lato", sans-serif;
510 margin-bottom: 1rem;
511 }
512
513 .data-table thead {
514 background: var(--surface-muted);
515 }
516
517 .data-table th {
518 text-align: left;
519 padding: 0.35rem 0.75rem;
520 font-family: "IBM Plex Mono", monospace;
521 font-size: 0.7rem;
522 font-weight: normal;
523 text-transform: uppercase;
524 letter-spacing: 0.05em;
525 color: var(--text-muted);
526 }
527
528 .data-table th.sortable {
529 cursor: pointer;
530 transition: background 0.2s ease;
531 user-select: none;
532 }
533
534 .data-table th.sortable:hover {
535 background: var(--border);
536 }
537
538 .data-table th.sortable a {
539 color: inherit;
540 text-decoration: none;
541 display: block;
542 }
543
544 .data-table th.sortable a:hover {
545 text-decoration: none;
546 }
547
548 .data-table th.sort-active {
549 color: var(--detail);
550 }
551
552 .data-table tbody tr {
553 transition: background 0.1s ease;
554 }
555
556 .data-table tbody tr:nth-child(odd) {
557 background: var(--surface-alt);
558 }
559
560 .data-table tbody tr:nth-child(even) {
561 background: var(--light-background);
562 }
563
564 .data-table tbody tr:hover {
565 background: var(--surface-muted);
566 }
567
568 .data-table td {
569 padding: 0.4rem 0.75rem;
570 border-top: 1px solid var(--border);
571 }
572
573 /* Thread-specific table columns */
574 .data-table .thread-title {
575 font-weight: bold;
576 }
577
578 .data-table .thread-title a {
579 color: var(--detail);
580 text-decoration: none;
581 }
582
583 .data-table .thread-title a:hover {
584 text-decoration: underline;
585 }
586
587 .data-table .col-replies,
588 .data-table .col-activity {
589 white-space: nowrap;
590 text-align: right;
591 width: 100px;
592 }
593
594 .data-table .col-author {
595 white-space: nowrap;
596 width: 130px;
597 }
598
599 /* Member list columns */
600 .data-table .col-role {
601 white-space: nowrap;
602 width: 100px;
603 }
604
605 .data-table .col-joined {
606 white-space: nowrap;
607 text-align: right;
608 width: 100px;
609 font-family: "IBM Plex Mono", monospace;
610 font-size: 0.8rem;
611 color: var(--text-muted);
612 }
613
614 /* Pinned row highlight */
615 .data-table tr.pinned {
616 background: var(--surface-muted);
617 }
618
619 .data-table tr.pinned:hover {
620 background: var(--border);
621 }
622
623 /* BADGES */
624
625 .badge {
626 display: inline-block;
627 padding: 0.15rem 0.4rem;
628 font-size: 0.7rem;
629 font-family: "IBM Plex Mono", monospace;
630 background: var(--primary-dark);
631 color: var(--primary-light);
632 }
633
634 .badge-success {
635 background: var(--success);
636 }
637
638 .badge-warning {
639 background: var(--warning);
640 }
641
642 .badge-danger {
643 background: var(--danger);
644 }
645
646 .badge-muted {
647 background: var(--text-muted);
648 }
649
650 .badge-ban {
651 background: var(--danger);
652 color: var(--primary-light);
653 }
654
655 .badge-mute-type {
656 background: var(--warning);
657 color: var(--primary-light);
658 }
659
660 /* Role badges */
661 .badge-role-owner {
662 background: var(--primary-dark);
663 color: var(--primary-light);
664 }
665
666 .badge-role-moderator {
667 background: var(--warning);
668 color: var(--primary-light);
669 }
670
671 .badge-role-member {
672 background: var(--surface-muted);
673 color: var(--detail);
674 border: 1px solid var(--border);
675 }
676
677 .badge-role-guest {
678 background: var(--surface-muted);
679 color: var(--text-muted);
680 border: 1px solid var(--border);
681 }
682
683 /* Thread status badges, text-only */
684 .badge-pinned {
685 background: var(--surface-muted);
686 color: var(--detail);
687 border: 1px solid var(--border);
688 }
689
690 .badge-locked {
691 background: var(--surface-muted);
692 color: var(--text-muted);
693 border: 1px solid var(--border);
694 }
695
696 /* BREADCRUMBS */
697
698 .breadcrumb {
699 font-size: 0.8rem;
700 color: var(--text-muted);
701 margin-bottom: 0.5rem;
702 font-family: "IBM Plex Mono", monospace;
703 }
704
705 .breadcrumb a {
706 color: var(--detail);
707 text-decoration: none;
708 }
709
710 .breadcrumb a:hover {
711 text-decoration: underline;
712 }
713
714 .breadcrumb .sep {
715 margin: 0 0.3rem;
716 opacity: 0.5;
717 }
718
719 /* POST ITEM (thread view) */
720
721 .post-list {
722 display: flex;
723 flex-direction: column;
724 gap: 0;
725 }
726
727 .post-item {
728 padding: 0.75rem 1rem;
729 border-bottom: 1px solid var(--border);
730 }
731
732 .post-item:first-child {
733 border-top: 1px solid var(--border);
734 }
735
736 .post-header {
737 display: flex;
738 justify-content: space-between;
739 align-items: baseline;
740 margin-bottom: 0.25rem;
741 }
742
743 .post-author {
744 font-family: "IBM Plex Mono", monospace;
745 font-weight: bold;
746 font-size: 0.85rem;
747 }
748
749 .post-timestamp {
750 font-family: "IBM Plex Mono", monospace;
751 font-size: 0.75rem;
752 color: var(--text-muted);
753 }
754
755 .post-edited {
756 font-family: "IBM Plex Mono", monospace;
757 font-size: 0.7rem;
758 color: var(--text-muted);
759 font-style: italic;
760 margin-left: 0.5rem;
761 }
762
763 .post-body {
764 font-size: 0.9rem;
765 line-height: 1.5;
766 }
767
768 .post-body p {
769 margin-bottom: 0.5rem;
770 }
771
772 .post-body p:last-child {
773 margin-bottom: 0;
774 }
775
776 .post-body code {
777 font-family: "IBM Plex Mono", monospace;
778 font-size: 0.8rem;
779 background: var(--surface-muted);
780 padding: 0.1rem 0.35rem;
781 }
782
783 .post-body pre {
784 background: var(--primary-dark);
785 color: var(--primary-light);
786 padding: 0.75rem;
787 overflow-x: auto;
788 margin: 0.5rem 0;
789 font-family: "IBM Plex Mono", monospace;
790 font-size: 0.8rem;
791 line-height: 1.5;
792 }
793
794 .post-body pre code {
795 background: none;
796 padding: 0;
797 color: inherit;
798 }
799
800 .post-body blockquote {
801 border-left: 3px solid var(--border);
802 padding-left: 1rem;
803 margin: 0.5rem 0;
804 color: var(--text-muted);
805 }
806
807 .post-body img {
808 max-width: 100%;
809 height: auto;
810 border-radius: 4px;
811 margin: 0.5rem 0;
812 cursor: pointer;
813 }
814
815 /* Post action links (edit/delete) */
816 .post-actions {
817 margin-left: 0.75rem;
818 }
819
820 .post-action-link {
821 font-family: "IBM Plex Mono", monospace;
822 font-size: 0.7rem;
823 color: var(--text-muted);
824 background: none;
825 border: none;
826 padding: 0;
827 cursor: pointer;
828 text-decoration: none;
829 margin-left: 0.5rem;
830 }
831
832 .post-action-link:hover {
833 color: var(--detail);
834 text-decoration: underline;
835 }
836
837 .post-action-delete:hover {
838 color: var(--danger);
839 }
840
841 /* Deleted posts */
842 .post-deleted .post-body {
843 color: var(--text-muted);
844 font-style: italic;
845 }
846
847 /* OP indicator */
848 .post-item.op {
849 background: var(--light-background);
850 }
851
852 /* DRAFT INDICATOR */
853
854 .draft-indicator {
855 font-family: "IBM Plex Mono", monospace;
856 font-size: 0.75rem;
857 color: var(--text-muted);
858 margin-bottom: 0.25rem;
859 }
860
861 .draft-discard {
862 cursor: pointer;
863 color: var(--text-muted);
864 }
865
866 .draft-discard:hover {
867 color: var(--danger);
868 }
869
870 /* REPLY FORM */
871
872 .reply-section {
873 padding: 1rem;
874 margin-top: 0.5rem;
875 }
876
877 .reply-section h3 {
878 margin-bottom: 0.75rem;
879 }
880
881 /* PAGE HEADER (title + actions row) */
882
883 .page-header {
884 display: flex;
885 justify-content: space-between;
886 align-items: center;
887 margin-bottom: 0.5rem;
888 }
889
890 .page-header h1 {
891 text-align: left;
892 font-size: 1.5rem;
893 margin: 0;
894 }
895
896 .page-header h2 {
897 font-size: 1.2rem;
898 margin: 0;
899 }
900
901 /* TOAST NOTIFICATIONS */
902
903 .toast-container {
904 position: fixed;
905 bottom: 1.5rem;
906 right: 1.5rem;
907 z-index: 1000;
908 display: flex;
909 flex-direction: column;
910 gap: 0.5rem;
911 }
912
913 .toast {
914 background: var(--primary-dark);
915 color: var(--primary-light);
916 padding: 0.6rem 1rem;
917 font-family: "IBM Plex Mono", monospace;
918 font-size: 0.85rem;
919 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
920 animation: toast-in 0.3s ease-out;
921 max-width: 300px;
922 }
923
924 .toast-success {
925 background: var(--success);
926 }
927
928 .toast-error {
929 background: var(--error);
930 }
931
932 .toast-warning {
933 background: var(--warning);
934 }
935
936 .toast-info {
937 background: var(--primary-dark);
938 }
939
940 .toast-retry-btn {
941 background: none;
942 border: 1px solid currentColor;
943 color: inherit;
944 font-family: "IBM Plex Mono", monospace;
945 font-size: 0.75rem;
946 font-weight: 600;
947 padding: 0.1rem 0.4rem;
948 margin-left: 0.75rem;
949 cursor: pointer;
950 }
951
952 .toast-retry-btn:hover {
953 background: rgba(255, 255, 255, 0.15);
954 }
955
956 .toast.fade-out {
957 animation: toast-out 0.3s ease-in forwards;
958 }
959
960 @keyframes toast-in {
961 from { transform: translateX(100%); }
962 to { transform: translateX(0); }
963 }
964
965 @keyframes toast-out {
966 from { transform: translateX(0); }
967 to { transform: translateX(100%); }
968 }
969
970 /* SETTINGS PAGE */
971
972 .settings-section {
973 margin-bottom: 2rem;
974 }
975
976 .settings-section h3 {
977 margin-bottom: 0.75rem;
978 }
979
980 .settings-table {
981 width: 100%;
982 border-collapse: collapse;
983 font-size: 0.85rem;
984 font-family: "Lato", sans-serif;
985 margin-bottom: 1rem;
986 }
987
988 .settings-table thead {
989 background: var(--surface-muted);
990 }
991
992 .settings-table th {
993 text-align: left;
994 padding: 0.35rem 0.75rem;
995 font-family: "IBM Plex Mono", monospace;
996 font-size: 0.7rem;
997 font-weight: normal;
998 text-transform: uppercase;
999 letter-spacing: 0.05em;
1000 color: var(--text-muted);
1001 }
1002
1003 .settings-table tbody tr {
1004 transition: background 0.1s ease;
1005 }
1006
1007 .settings-table tbody tr:nth-child(odd) {
1008 background: var(--surface-alt);
1009 }
1010
1011 .settings-table tbody tr:nth-child(even) {
1012 background: var(--light-background);
1013 }
1014
1015 .settings-table tbody tr:hover {
1016 background: var(--surface-muted);
1017 }
1018
1019 .settings-table td {
1020 padding: 0.4rem 0.75rem;
1021 border-top: 1px solid var(--border);
1022 }
1023
1024 .settings-cat-name {
1025 font-family: "Young Serif", serif;
1026 font-size: 1rem;
1027 }
1028
1029 .settings-cat-desc {
1030 display: block;
1031 font-size: 0.8rem;
1032 color: var(--text-muted);
1033 margin-top: 0.1rem;
1034 }
1035
1036 .settings-mono {
1037 font-family: "IBM Plex Mono", monospace;
1038 font-size: 0.8rem;
1039 color: var(--text-muted);
1040 }
1041
1042 .settings-actions {
1043 white-space: nowrap;
1044 }
1045
1046 /* ACCESSIBILITY */
1047
1048 .skip-to-main {
1049 position: absolute;
1050 top: -100px;
1051 left: 0;
1052 background: var(--primary-dark);
1053 color: var(--primary-light);
1054 padding: 8px;
1055 z-index: 100;
1056 transition: top 0.2s ease;
1057 }
1058
1059 .skip-to-main:focus {
1060 top: 0;
1061 }
1062
1063 *:focus-visible {
1064 outline: 2px solid var(--focus-ring);
1065 outline-offset: 2px;
1066 }
1067
1068 /* PAGINATION */
1069
1070 .pagination {
1071 display: flex;
1072 justify-content: center;
1073 align-items: center;
1074 gap: 1rem;
1075 padding: 0.75rem 0;
1076 font-family: "IBM Plex Mono", monospace;
1077 font-size: 0.85rem;
1078 }
1079
1080 .pagination-link {
1081 color: var(--detail);
1082 text-decoration: none;
1083 padding: 0.3rem 0.6rem;
1084 border: 1px solid var(--border);
1085 background: var(--light-background);
1086 transition: background 0.2s ease;
1087 }
1088
1089 .pagination-link:hover {
1090 background: var(--surface-muted);
1091 text-decoration: none;
1092 }
1093
1094 .pagination-info {
1095 color: var(--text-muted);
1096 }
1097
1098 /* ERROR PAGES */
1099
1100 .error-page {
1101 text-align: center;
1102 padding: 3rem 1rem;
1103 }
1104
1105 .error-page h2 {
1106 font-family: "Young Serif", serif;
1107 font-size: 3rem;
1108 margin-bottom: 0.5rem;
1109 }
1110
1111 .error-page p {
1112 color: var(--text-muted);
1113 margin-bottom: 1.5rem;
1114 }
1115
1116 /* EMPTY STATE */
1117
1118 .empty-state {
1119 text-align: center;
1120 padding: 1rem;
1121 color: var(--text-muted);
1122 font-family: "IBM Plex Mono", monospace;
1123 }
1124
1125 /* SITE FOOTER */
1126
1127 .site-footer {
1128 text-align: center;
1129 padding: 1rem;
1130 margin-top: 1rem;
1131 border-top: 1px solid var(--border);
1132 font-family: "IBM Plex Mono", monospace;
1133 font-size: 0.75rem;
1134 color: var(--text-muted);
1135 }
1136
1137 .site-footer a {
1138 color: var(--detail);
1139 text-decoration: none;
1140 }
1141
1142 .site-footer a:hover {
1143 text-decoration: underline;
1144 }
1145
1146 .site-footer-links {
1147 margin-bottom: 0.5rem;
1148 }
1149
1150 .site-footer-links a {
1151 margin: 0 0.75rem;
1152 }
1153
1154 .footer-sep {
1155 margin: 0 0.4rem;
1156 }
1157
1158 /* FOOTNOTES + IMMUTABLE POSTS */
1159
1160 .post-footnotes {
1161 margin-top: 0.75rem;
1162 padding-top: 0.5rem;
1163 border-top: 1px solid var(--border);
1164 }
1165
1166 .footnote {
1167 font-size: 0.85rem;
1168 color: var(--text-muted);
1169 margin-bottom: 0.5rem;
1170 }
1171
1172 .footnote p {
1173 margin: 0.15rem 0;
1174 }
1175
1176 .footnote-prefix {
1177 font-family: "IBM Plex Mono", monospace;
1178 font-size: 0.75rem;
1179 }
1180
1181 .footnote-form-toggle {
1182 margin-top: 0.5rem;
1183 }
1184
1185 .footnote-form-toggle textarea {
1186 width: 100%;
1187 min-height: 60px;
1188 margin-top: 0.5rem;
1189 }
1190
1191 .badge-unread {
1192 background: var(--highlight);
1193 color: var(--primary-light);
1194 font-size: 0.75rem;
1195 padding: 0.1rem 0.4rem;
1196 border-radius: 3px;
1197 font-family: "IBM Plex Mono", monospace;
1198 }
1199
1200 tr.unread .thread-title a {
1201 font-weight: 700;
1202 }
1203
1204 tr.unread .thread-title::before {
1205 content: "";
1206 display: inline-block;
1207 width: 6px;
1208 height: 6px;
1209 background: var(--highlight);
1210 border-radius: 50%;
1211 margin-right: 0.35rem;
1212 vertical-align: middle;
1213 }
1214
1215 /* Mention indicator on thread listings */
1216 .badge-mention {
1217 color: var(--highlight);
1218 font-size: 0.75rem;
1219 font-weight: 700;
1220 font-family: "IBM Plex Mono", monospace;
1221 background: none;
1222 border: none;
1223 padding: 0;
1224 }
1225
1226 tr.mentioned {
1227 border-left: 3px solid var(--highlight);
1228 }
1229
1230 /* Tracking settings */
1231 .tracking-settings {
1232 display: flex;
1233 align-items: center;
1234 gap: 0.75rem;
1235 margin-bottom: 1rem;
1236 font-size: 0.85rem;
1237 }
1238
1239 .toggle-label {
1240 display: flex;
1241 align-items: center;
1242 gap: 0.4rem;
1243 cursor: pointer;
1244 }
1245
1246 .toggle-checkbox {
1247 accent-color: var(--highlight);
1248 }
1249
1250 /* Info/static page sections */
1251 .info-section h3 {
1252 margin-top: 1.5rem;
1253 margin-bottom: 0.5rem;
1254 }
1255
1256 .info-section ul {
1257 padding-left: 1.5rem;
1258 margin-bottom: 0.75rem;
1259 }
1260
1261 .info-section li {
1262 margin-bottom: 0.3rem;
1263 line-height: 1.5;
1264 }
1265
1266 .tag-filter {
1267 margin-bottom: 1rem;
1268 display: flex;
1269 flex-wrap: wrap;
1270 align-items: center;
1271 gap: 0.35rem;
1272 }
1273
1274 .tag-filter-label {
1275 font-family: "IBM Plex Mono", monospace;
1276 font-size: 0.8rem;
1277 color: var(--text-muted);
1278 margin-right: 0.25rem;
1279 }
1280
1281 .tag-chip {
1282 display: inline-block;
1283 padding: 0.15rem 0.5rem;
1284 font-size: 0.8rem;
1285 border: 1px solid var(--border);
1286 border-radius: 3px;
1287 text-decoration: none;
1288 color: var(--text-primary);
1289 }
1290
1291 .tag-chip:hover {
1292 background: var(--surface-muted);
1293 }
1294
1295 .tag-chip.tag-active {
1296 background: var(--primary-dark);
1297 color: var(--primary-light);
1298 border-color: var(--primary-dark);
1299 }
1300
1301 .tag-badge {
1302 display: inline-block;
1303 font-size: 0.7rem;
1304 padding: 0.05rem 0.35rem;
1305 background: var(--surface-muted);
1306 border-radius: 2px;
1307 margin-left: 0.25rem;
1308 vertical-align: middle;
1309 font-family: "IBM Plex Mono", monospace;
1310 color: var(--text-muted);
1311 }
1312
1313 .tag-fieldset {
1314 border: none;
1315 padding: 0;
1316 }
1317
1318 .tag-fieldset legend {
1319 font-weight: 600;
1320 margin-bottom: 0.35rem;
1321 }
1322
1323 .tag-checkbox {
1324 display: inline-block;
1325 margin-right: 1rem;
1326 font-size: 0.9rem;
1327 cursor: pointer;
1328 }
1329
1330 .flag-form-toggle {
1331 margin-top: 0.5rem;
1332 }
1333
1334 .flag-form {
1335 margin-top: 0.5rem;
1336 display: flex;
1337 flex-direction: column;
1338 gap: 0.35rem;
1339 }
1340
1341 .flag-form label {
1342 font-size: 0.85rem;
1343 cursor: pointer;
1344 }
1345
1346 .flag-form textarea {
1347 width: 100%;
1348 min-height: 40px;
1349 margin-top: 0.25rem;
1350 }
1351
1352 .flag-btn {
1353 align-self: flex-start;
1354 font-size: 0.8rem;
1355 padding: 0.25rem 0.75rem;
1356 }
1357
1358 .post-removed .post-body {
1359 color: var(--text-muted);
1360 font-style: italic;
1361 }
1362
1363 .quote-attribution {
1364 display: block;
1365 font-size: 0.8rem;
1366 font-family: "IBM Plex Mono", monospace;
1367 color: var(--text-muted);
1368 margin-top: 0.25rem;
1369 font-style: normal;
1370 }
1371
1372 .quote-attribution a {
1373 color: var(--text-muted);
1374 text-decoration: none;
1375 }
1376
1377 .quote-attribution a:hover {
1378 text-decoration: underline;
1379 }
1380
1381 .quote-btn {
1382 position: absolute;
1383 z-index: 100;
1384 background: var(--surface);
1385 border: 1px solid var(--border);
1386 padding: 0.25rem 0.5rem;
1387 font-size: 0.75rem;
1388 font-family: "IBM Plex Mono", monospace;
1389 cursor: pointer;
1390 }
1391
1392 .quote-btn:hover {
1393 background: var(--light-background);
1394 }
1395
1396 /* UTILITY CLASSES */
1397
1398 .form-inline { display: inline; }
1399 .form-inline-row { display: inline-flex; gap: 0.25rem; flex-direction: row; align-items: center; }
1400 .btn-secondary { text-decoration: none; }
1401 .section-heading { margin-bottom: 1.5rem; }
1402 .section-heading-top { margin-top: 1.5rem; }
1403 .textarea-short { min-height: 60px; }
1404 .textarea-medium { min-height: 80px; }
1405 .footnote-btn { margin-top: 0.25rem; }
1406 .input-compact { width: 150px; padding: 0.3rem; }
1407 .search-form { flex-direction: row; gap: 0.5rem; align-items: flex-end; }
1408 .flex-1 { flex: 1; }
1409
1410 .profile-header { display: flex; align-items: center; gap: 1.25rem; margin-bottom: 1.5rem; }
1411 .profile-avatar { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; }
1412 .profile-avatar-placeholder {
1413 width: 64px; height: 64px; border-radius: 50%; background: var(--border-color);
1414 display: flex; align-items: center; justify-content: center;
1415 font-size: 1.5rem; font-weight: bold; color: var(--text-muted); flex-shrink: 0;
1416 }
1417 .profile-name { margin: 0; }
1418 .text-muted { color: var(--text-muted); }
1419 .text-muted-sm { color: var(--text-muted); font-size: 0.85rem; }
1420 .text-muted-md { color: var(--text-muted); font-size: 0.9rem; }
1421 .community-desc { margin-bottom: 0.5rem; color: var(--text-muted); }
1422 .activity-category { color: var(--text-muted); font-size: 0.85rem; }
1423
1424 /* LINK PREVIEWS */
1425
1426 .post-link-previews {
1427 margin-top: 0.75rem;
1428 display: flex;
1429 flex-direction: column;
1430 gap: 0.5rem;
1431 }
1432
1433 .link-preview-card {
1434 display: block;
1435 padding: 0.5rem 0.75rem;
1436 border: 1px solid var(--border);
1437 background: var(--light-background);
1438 text-decoration: none;
1439 color: var(--detail);
1440 transition: background 0.2s ease;
1441 }
1442
1443 .link-preview-card:hover {
1444 background: var(--surface-muted);
1445 text-decoration: none;
1446 }
1447
1448 .lp-title {
1449 display: block;
1450 font-family: "IBM Plex Mono", monospace;
1451 font-size: 0.85rem;
1452 font-weight: bold;
1453 }
1454
1455 .lp-desc {
1456 display: block;
1457 font-size: 0.8rem;
1458 color: var(--text-muted);
1459 margin-top: 0.15rem;
1460 }
1461
1462 .lp-url {
1463 display: block;
1464 font-size: 0.7rem;
1465 font-family: "IBM Plex Mono", monospace;
1466 color: var(--text-muted);
1467 margin-top: 0.15rem;
1468 }
1469
1470 /* SEARCH MODAL */
1471
1472 .search-toggle {
1473 font-family: "IBM Plex Mono", monospace;
1474 font-size: 0.85rem;
1475 color: var(--text-muted);
1476 background: none;
1477 border: 1px solid var(--border);
1478 padding: 0.25rem 0.75rem;
1479 cursor: pointer;
1480 transition: opacity 0.2s ease;
1481 }
1482
1483 .search-toggle:hover {
1484 opacity: 0.7;
1485 }
1486
1487 .search-modal {
1488 position: fixed;
1489 top: 0;
1490 left: 0;
1491 right: 0;
1492 bottom: 0;
1493 z-index: 200;
1494 display: flex;
1495 justify-content: center;
1496 padding-top: 10vh;
1497 }
1498
1499 .search-modal[hidden] {
1500 display: none;
1501 }
1502
1503 .search-modal-backdrop {
1504 position: fixed;
1505 top: 0;
1506 left: 0;
1507 right: 0;
1508 bottom: 0;
1509 background: rgba(0, 0, 0, 0.3);
1510 }
1511
1512 .search-modal-content {
1513 position: relative;
1514 width: 100%;
1515 max-width: 600px;
1516 max-height: 70vh;
1517 display: flex;
1518 flex-direction: column;
1519 background: var(--light-background);
1520 border: 1px solid var(--border);
1521 box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
1522 z-index: 1;
1523 }
1524
1525 .search-input {
1526 width: 100%;
1527 padding: 0.75rem 1rem;
1528 font-size: 1rem;
1529 font-family: "IBM Plex Mono", monospace;
1530 background: var(--background);
1531 border: none;
1532 border-bottom: 1px solid var(--border);
1533 color: var(--detail);
1534 outline: none;
1535 }
1536
1537 .search-input:focus {
1538 border-bottom-color: var(--highlight);
1539 }
1540
1541 .search-results-container {
1542 overflow-y: auto;
1543 max-height: calc(70vh - 3rem);
1544 }
1545
1546 .search-results {
1547 list-style: none;
1548 margin: 0;
1549 padding: 0;
1550 }
1551
1552 .search-result {
1553 border-bottom: 1px solid var(--border);
1554 }
1555
1556 .search-result:last-child {
1557 border-bottom: none;
1558 }
1559
1560 .search-result-link {
1561 display: block;
1562 padding: 0.6rem 1rem;
1563 text-decoration: none;
1564 color: var(--detail);
1565 transition: background 0.1s ease;
1566 }
1567
1568 .search-result-link:hover,
1569 .search-result.search-active .search-result-link {
1570 background: var(--surface-muted);
1571 text-decoration: none;
1572 }
1573
1574 .search-result-title {
1575 display: block;
1576 font-weight: bold;
1577 font-size: 0.9rem;
1578 }
1579
1580 .search-result-meta {
1581 display: block;
1582 font-family: "IBM Plex Mono", monospace;
1583 font-size: 0.7rem;
1584 color: var(--text-muted);
1585 margin-top: 0.15rem;
1586 }
1587
1588 .search-result-snippet {
1589 display: block;
1590 font-size: 0.8rem;
1591 color: var(--text-muted);
1592 margin-top: 0.15rem;
1593 overflow: hidden;
1594 text-overflow: ellipsis;
1595 white-space: nowrap;
1596 }
1597
1598 .search-empty {
1599 padding: 1.5rem 1rem;
1600 text-align: center;
1601 color: var(--text-muted);
1602 font-family: "IBM Plex Mono", monospace;
1603 font-size: 0.85rem;
1604 }
1605
1606 /* POST ENDORSEMENTS */
1607
1608 .endorsed {
1609 color: var(--highlight);
1610 font-weight: bold;
1611 }
1612
1613 .endorsement-count {
1614 color: var(--detail);
1615 font-size: 0.85rem;
1616 margin-left: 0.25rem;
1617 }
1618
1619 /* CHAT ROOM */
1620
1621 .chat-room {
1622 display: flex;
1623 flex-direction: column;
1624 border: 1px solid var(--border);
1625 background: var(--light-background);
1626 }
1627
1628 /* The log scrolls, the composer does not. A fixed height rather than a growing
1629 page is what lets the island keep the newest message in view without moving
1630 the thing the reader is typing into. */
1631 .chat-log {
1632 list-style: none;
1633 margin: 0;
1634 padding: 0.5rem;
1635 height: 60vh;
1636 min-height: 15rem;
1637 overflow-y: auto;
1638 overscroll-behavior: contain;
1639 }
1640
1641 .chat-message {
1642 display: flex;
1643 align-items: baseline;
1644 gap: 0.4rem;
1645 padding: 0.25rem 0.35rem;
1646 line-height: 1.45;
1647 }
1648
1649 .chat-message:hover {
1650 background: var(--surface-alt);
1651 }
1652
1653 .chat-avatar {
1654 align-self: center;
1655 border-radius: 50%;
1656 flex-shrink: 0;
1657 }
1658
1659 .chat-author {
1660 font-weight: bold;
1661 color: var(--detail);
1662 white-space: nowrap;
1663 }
1664
1665 .chat-time {
1666 font-family: "IBM Plex Mono", monospace;
1667 font-size: 0.75rem;
1668 color: var(--text-muted);
1669 white-space: nowrap;
1670 }
1671
1672 .chat-body {
1673 flex: 1;
1674 min-width: 0;
1675 overflow-wrap: anywhere;
1676 }
1677
1678 /* Sent, not yet echoed back by the server. Dimmed rather than hidden: the
1679 message is on screen the instant it is typed, and the dimming is the only
1680 thing saying it is not confirmed. */
1681 .chat-message.is-pending {
1682 opacity: 0.55;
1683 }
1684
1685 .chat-message.is-failed {
1686 opacity: 1;
1687 border-left: 2px solid var(--danger);
1688 }
1689
1690 /* Someone named this reader. A tint and a rule rather than anything louder:
1691 chat scrolls, the highlight has to survive being one of forty messages, and
1692 there is deliberately no notification behind it. */
1693 .chat-message.is-mention {
1694 background: var(--surface-alt);
1695 border-left: 2px solid var(--highlight);
1696 }
1697
1698 .chat-error {
1699 color: var(--danger);
1700 font-size: 0.85rem;
1701 }
1702
1703 /* Delete, and the retry on a failed send. Both stay out of the way until the
1704 message is under the pointer, and both are always reachable by keyboard. */
1705 .chat-remove,
1706 .chat-retry {
1707 background: none;
1708 border: 1px solid transparent;
1709 color: var(--text-muted);
1710 cursor: pointer;
1711 font-family: "IBM Plex Mono", monospace;
1712 font-size: 0.75rem;
1713 padding: 0 0.3rem;
1714 }
1715
1716 .chat-remove {
1717 opacity: 0;
1718 margin-left: auto;
1719 }
1720
1721 .chat-message:hover .chat-remove,
1722 .chat-remove:focus-visible {
1723 opacity: 1;
1724 }
1725
1726 .chat-remove:hover,
1727 .chat-retry:hover {
1728 color: var(--danger);
1729 border-color: var(--border);
1730 }
1731
1732 .chat-status {
1733 margin: 0;
1734 padding: 0.35rem 0.6rem;
1735 border-top: 1px solid var(--border);
1736 background: var(--surface-alt);
1737 color: var(--text-muted);
1738 font-size: 0.85rem;
1739 }
1740
1741 .chat-composer {
1742 display: flex;
1743 gap: 0.5rem;
1744 padding: 0.5rem;
1745 border-top: 1px solid var(--border);
1746 }
1747
1748 .chat-composer input[type="text"] {
1749 flex: 1;
1750 min-width: 0;
1751 background: var(--input-background);
1752 border: 1px solid var(--border);
1753 color: var(--detail);
1754 padding: 0.45rem 0.6rem;
1755 }
1756
1757 .chat-notice {
1758 padding: 0.6rem;
1759 border-top: 1px solid var(--border);
1760 color: var(--text-muted);
1761 font-size: 0.9rem;
1762 }
1763
1764 /* RESPONSIVE, 768px (tablet) */
1765
1766 @media (max-width: 768px) {
1767 .container {
1768 padding: 0.75rem;
1769 }
1770
1771 .padded-page {
1772 padding: 0.75rem;
1773 }
1774
1775 .form-row {
1776 grid-template-columns: 1fr;
1777 }
1778
1779 .data-table th,
1780 .data-table td {
1781 padding: 0.35rem 0.5rem;
1782 font-size: 0.8rem;
1783 }
1784
1785 .directory-table th,
1786 .directory-table td {
1787 padding: 0.35rem 0.5rem;
1788 font-size: 0.8rem;
1789 }
1790
1791 .page-header {
1792 flex-direction: column;
1793 align-items: flex-start;
1794 gap: 0.5rem;
1795 }
1796
1797 /* Hamburger menu */
1798 .nav-toggle-label {
1799 display: block;
1800 }
1801
1802 .site-header nav {
1803 display: none;
1804 position: absolute;
1805 top: 100%;
1806 left: 0;
1807 right: 0;
1808 background: var(--background);
1809 border-bottom: 1px solid var(--border);
1810 padding: 0.75rem 1.25rem;
1811 z-index: 10;
1812 }
1813
1814 .site-header {
1815 position: relative;
1816 }
1817
1818 .nav-toggle-checkbox:checked ~ nav {
1819 display: block;
1820 }
1821
1822 .site-header .nav-links {
1823 flex-direction: column;
1824 gap: 0.75rem;
1825 }
1826
1827 /* Animate hamburger to X */
1828 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(1) {
1829 transform: rotate(45deg) translate(5px, 5px);
1830 }
1831
1832 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(2) {
1833 opacity: 0;
1834 }
1835
1836 .nav-toggle-checkbox:checked ~ .nav-toggle-label span:nth-child(3) {
1837 transform: rotate(-45deg) translate(5px, -5px);
1838 }
1839
1840 /* Hide less important columns on mobile */
1841 .data-table .col-author {
1842 display: none;
1843 }
1844
1845 .directory-table .col-type,
1846 .directory-table .col-items {
1847 display: none;
1848 }
1849
1850 .col-replies {
1851 width: auto;
1852 }
1853 }
1854
1855 /* RESPONSIVE, 480px (phone) */
1856
1857 @media (max-width: 480px) {
1858 .container {
1859 padding: 0.5rem;
1860 }
1861
1862 .padded-page {
1863 padding: 0.5rem;
1864 }
1865
1866 h1 {
1867 font-size: 1.6rem;
1868 }
1869
1870 .toast-container {
1871 left: 0.75rem;
1872 right: 0.75rem;
1873 }
1874
1875 .toast {
1876 max-width: 100%;
1877 }
1878
1879 .post-header {
1880 flex-direction: column;
1881 gap: 0.2rem;
1882 }
1883
1884 /* The composer and the on-screen keyboard together take most of a phone, so
1885 the log gives up height rather than pushing the input off the bottom. */
1886 .chat-log {
1887 height: 55vh;
1888 padding: 0.35rem;
1889 }
1890
1891 /* No hover on a touch screen, so a delete control that only appears on hover
1892 is a control that does not exist. */
1893 .chat-remove {
1894 opacity: 1;
1895 }
1896 }
1897