Microservice to bring 2FA to self hosted PDSes
1:root,
2:root.light-mode {
3 --brand-color: rgb(16, 131, 254);
4 --primary-color: rgb(7, 10, 13);
5 --secondary-color: rgb(66, 86, 108);
6 --bg-primary-color: rgb(255, 255, 255);
7 --bg-secondary-color: rgb(240, 242, 245);
8 --border-color: rgb(220, 225, 230);
9 --danger-color: rgb(220, 38, 38);
10 --success-color: rgb(22, 163, 74);
11 --warning-color: rgb(234, 179, 8);
12 --table-stripe: rgba(0, 0, 0, 0.02);
13}
14
15@media (prefers-color-scheme: dark) {
16 :root {
17 --brand-color: rgb(16, 131, 254);
18 --primary-color: rgb(255, 255, 255);
19 --secondary-color: rgb(133, 152, 173);
20 --bg-primary-color: rgb(7, 10, 13);
21 --bg-secondary-color: rgb(13, 18, 23);
22 --border-color: rgb(40, 45, 55);
23 --table-stripe: rgba(255, 255, 255, 0.02);
24 }
25}
26
27:root.dark-mode {
28 --brand-color: rgb(16, 131, 254);
29 --primary-color: rgb(255, 255, 255);
30 --secondary-color: rgb(133, 152, 173);
31 --bg-primary-color: rgb(7, 10, 13);
32 --bg-secondary-color: rgb(13, 18, 23);
33 --border-color: rgb(40, 45, 55);
34 --table-stripe: rgba(255, 255, 255, 0.02);
35}
36
37/* --- Reset & Base ------------------------------------------- */
38
39* {
40 margin: 0;
41 padding: 0;
42 box-sizing: border-box;
43}
44
45body {
46 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
47 background: var(--bg-secondary-color);
48 color: var(--primary-color);
49 text-rendering: optimizeLegibility;
50 -webkit-font-smoothing: antialiased;
51}
52
53/* --- Layout: Sidebar + Main --------------------------------- */
54
55.layout {
56 display: flex;
57 min-height: 100vh;
58}
59
60.sidebar {
61 width: 220px;
62 background: var(--bg-primary-color);
63 border-right: 1px solid var(--border-color);
64 padding: 20px 0;
65 position: fixed;
66 top: 0;
67 left: 0;
68 bottom: 0;
69 overflow-y: auto;
70 display: flex;
71 flex-direction: column;
72}
73
74.sidebar-title {
75 font-size: 0.8125rem;
76 font-weight: 700;
77 padding: 0 20px;
78 margin-bottom: 4px;
79 white-space: nowrap;
80 overflow: hidden;
81 text-overflow: ellipsis;
82}
83
84.sidebar-subtitle {
85 font-size: 0.6875rem;
86 color: var(--secondary-color);
87 padding: 0 20px;
88 margin-bottom: 20px;
89}
90
91.sidebar nav {
92 flex: 1;
93}
94
95.sidebar nav a {
96 display: block;
97 padding: 8px 20px;
98 font-size: 0.8125rem;
99 color: var(--secondary-color);
100 text-decoration: none;
101 transition: background 0.1s, color 0.1s;
102}
103
104.sidebar nav a:hover {
105 background: var(--bg-secondary-color);
106 color: var(--primary-color);
107}
108
109.sidebar nav a.active {
110 color: var(--brand-color);
111 font-weight: 500;
112}
113
114.sidebar-footer {
115 padding: 16px 20px 0;
116 border-top: 1px solid var(--border-color);
117 margin-top: 16px;
118}
119
120.sidebar-footer .session-info {
121 font-size: 0.75rem;
122 color: var(--secondary-color);
123 margin-bottom: 8px;
124}
125
126.sidebar-footer form {
127 display: inline;
128}
129
130.sidebar-footer button {
131 background: none;
132 border: none;
133 font-size: 0.75rem;
134 color: var(--secondary-color);
135 cursor: pointer;
136 padding: 0;
137 text-decoration: underline;
138}
139
140.sidebar-footer button:hover {
141 color: var(--primary-color);
142}
143
144.main {
145 margin-left: 220px;
146 flex: 1;
147 padding: 32px;
148 max-width: 960px;
149}
150
151/* --- Common Components -------------------------------------- */
152
153.page-title {
154 font-size: 1.5rem;
155 font-weight: 700;
156 margin-bottom: 24px;
157}
158
159.page-subtitle {
160 font-size: 0.8125rem;
161 color: var(--secondary-color);
162 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
163 margin-top: -20px;
164 margin-bottom: 24px;
165 word-break: break-all;
166}
167
168.page-description {
169 font-size: 0.875rem;
170 color: var(--secondary-color);
171 margin-top: -16px;
172 margin-bottom: 24px;
173}
174
175.flash-success {
176 background: rgba(22, 163, 74, 0.1);
177 color: var(--success-color);
178 border: 1px solid rgba(22, 163, 74, 0.2);
179 border-radius: 8px;
180 padding: 10px 14px;
181 font-size: 0.875rem;
182 margin-bottom: 20px;
183}
184
185.flash-error {
186 background: rgba(220, 38, 38, 0.1);
187 color: var(--danger-color);
188 border: 1px solid rgba(220, 38, 38, 0.2);
189 border-radius: 8px;
190 padding: 10px 14px;
191 font-size: 0.875rem;
192 margin-bottom: 20px;
193}
194
195/* --- Buttons ------------------------------------------------ */
196
197.btn {
198 display: inline-flex;
199 align-items: center;
200 justify-content: center;
201 padding: 10px 20px;
202 font-size: 0.875rem;
203 font-weight: 500;
204 border: none;
205 border-radius: 8px;
206 cursor: pointer;
207 transition: opacity 0.15s;
208 text-decoration: none;
209}
210
211.btn:hover {
212 opacity: 0.85;
213}
214
215.btn-primary {
216 background: var(--brand-color);
217 color: #fff;
218}
219
220.btn-danger {
221 background: var(--danger-color);
222 color: #fff;
223 border-color: var(--danger-color);
224}
225
226.btn-warning {
227 background: var(--warning-color);
228 color: #000;
229 border-color: var(--warning-color);
230}
231
232.btn-small {
233 padding: 6px 12px;
234 font-size: 0.75rem;
235}
236
237.btn-outline-danger {
238 background: transparent;
239 color: var(--danger-color);
240 border: 1px solid var(--danger-color);
241}
242
243/* --- Detail Sections ---------------------------------------- */
244
245.detail-section {
246 background: var(--bg-primary-color);
247 border: 1px solid var(--border-color);
248 border-radius: 10px;
249 padding: 20px;
250 margin-bottom: 16px;
251}
252
253.detail-section h3 {
254 font-size: 0.875rem;
255 font-weight: 600;
256 margin-bottom: 12px;
257}
258
259.detail-row {
260 display: flex;
261 justify-content: space-between;
262 align-items: center;
263 padding: 8px 0;
264 font-size: 0.8125rem;
265 border-bottom: 1px solid var(--border-color);
266}
267
268.detail-row:last-child {
269 border-bottom: none;
270}
271
272.detail-row .label {
273 color: var(--secondary-color);
274 flex-shrink: 0;
275}
276
277.detail-row .value {
278 font-weight: 500;
279 word-break: break-all;
280 text-align: right;
281 max-width: 65%;
282}
283
284.detail-row .value a {
285 color: var(--brand-color);
286 text-decoration: none;
287}
288
289.detail-row .value a:hover {
290 text-decoration: underline;
291}
292
293/* --- Tables ------------------------------------------------- */
294
295.table-container {
296 background: var(--bg-primary-color);
297 border: 1px solid var(--border-color);
298 border-radius: 10px;
299 overflow: hidden;
300}
301
302table {
303 width: 100%;
304 border-collapse: collapse;
305}
306
307thead th {
308 text-align: left;
309 padding: 12px 16px;
310 font-size: 0.75rem;
311 font-weight: 600;
312 color: var(--secondary-color);
313 text-transform: uppercase;
314 letter-spacing: 0.5px;
315 border-bottom: 1px solid var(--border-color);
316}
317
318tbody tr {
319 border-bottom: 1px solid var(--border-color);
320}
321
322tbody tr:last-child {
323 border-bottom: none;
324}
325
326tbody tr:nth-child(even) {
327 background: var(--table-stripe);
328}
329
330tbody td {
331 padding: 10px 16px;
332 font-size: 0.8125rem;
333}
334
335tbody td a {
336 color: var(--brand-color);
337 text-decoration: none;
338}
339
340tbody td a:hover {
341 text-decoration: underline;
342}
343
344/* --- Badges ------------------------------------------------- */
345
346.badge {
347 display: inline-block;
348 padding: 2px 8px;
349 border-radius: 4px;
350 font-size: 0.75rem;
351 font-weight: 500;
352}
353
354.badge-success {
355 background: rgba(22, 163, 74, 0.1);
356 color: var(--success-color);
357}
358
359.badge-danger {
360 background: rgba(220, 38, 38, 0.1);
361 color: var(--danger-color);
362}
363
364.badge-warning {
365 background: rgba(234, 179, 8, 0.1);
366 color: var(--warning-color);
367}
368
369/* --- Forms -------------------------------------------------- */
370
371.form-card {
372 background: var(--bg-primary-color);
373 border: 1px solid var(--border-color);
374 border-radius: 10px;
375 padding: 24px;
376 max-width: 480px;
377}
378
379.form-group {
380 margin-bottom: 16px;
381}
382
383.form-group label {
384 display: block;
385 font-size: 0.8125rem;
386 font-weight: 500;
387 margin-bottom: 6px;
388 color: var(--primary-color);
389}
390
391.form-group input {
392 width: 100%;
393 padding: 10px 12px;
394 font-size: 0.875rem;
395 border: 1px solid var(--border-color);
396 border-radius: 8px;
397 background: var(--bg-primary-color);
398 color: var(--primary-color);
399 outline: none;
400 transition: border-color 0.15s;
401}
402
403.form-group input:focus {
404 border-color: var(--brand-color);
405}
406
407.form-group .hint {
408 font-size: 0.75rem;
409 color: var(--secondary-color);
410 margin-top: 4px;
411}
412
413.search-form {
414 display: flex;
415 gap: 8px;
416 margin-bottom: 24px;
417}
418
419.search-form input {
420 flex: 1;
421 padding: 10px 12px;
422 font-size: 0.875rem;
423 border: 1px solid var(--border-color);
424 border-radius: 8px;
425 background: var(--bg-primary-color);
426 color: var(--primary-color);
427 outline: none;
428}
429
430.search-form input:focus {
431 border-color: var(--brand-color);
432}
433
434.create-form {
435 display: flex;
436 gap: 8px;
437 align-items: flex-end;
438 margin-bottom: 24px;
439}
440
441.create-form .form-group {
442 display: flex;
443 flex-direction: column;
444 gap: 4px;
445}
446
447.create-form label {
448 font-size: 0.75rem;
449 font-weight: 500;
450 color: var(--secondary-color);
451}
452
453.create-form input[type="number"] {
454 padding: 10px 12px;
455 font-size: 0.875rem;
456 border: 1px solid var(--border-color);
457 border-radius: 8px;
458 background: var(--bg-primary-color);
459 color: var(--primary-color);
460 outline: none;
461 width: 120px;
462}
463
464.create-form input[type="number"]:focus {
465 border-color: var(--brand-color);
466}
467
468/* --- Utility ------------------------------------------------ */
469
470.mono-text {
471 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
472 font-size: 0.75rem;
473}
474
475.inline-form {
476 display: inline;
477}
478
479.empty-state {
480 text-align: center;
481 padding: 40px 20px;
482 color: var(--secondary-color);
483 font-size: 0.875rem;
484}
485
486.load-more {
487 text-align: center;
488 padding: 16px;
489}
490
491.load-more a {
492 color: var(--brand-color);
493 text-decoration: none;
494 font-size: 0.875rem;
495 font-weight: 500;
496}
497
498.load-more a:hover {
499 text-decoration: underline;
500}
501
502/* --- Dashboard: Cards --------------------------------------- */
503
504.cards {
505 display: grid;
506 grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
507 gap: 16px;
508 margin-bottom: 32px;
509}
510
511.card {
512 background: var(--bg-primary-color);
513 border: 1px solid var(--border-color);
514 border-radius: 10px;
515 padding: 20px;
516}
517
518.card-label {
519 font-size: 0.75rem;
520 font-weight: 500;
521 color: var(--secondary-color);
522 text-transform: uppercase;
523 letter-spacing: 0.5px;
524 margin-bottom: 6px;
525}
526
527.card-value {
528 font-size: 1.25rem;
529 font-weight: 600;
530}
531
532.card-value.success {
533 color: var(--success-color);
534}
535
536.card-value.danger {
537 color: var(--danger-color);
538}
539
540/* --- Accounts: DID Cell ------------------------------------- */
541
542.did-cell {
543 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
544 font-size: 0.75rem;
545 color: var(--secondary-color);
546}
547
548/* --- Account Detail ----------------------------------------- */
549
550.actions {
551 display: flex;
552 flex-wrap: wrap;
553 gap: 8px;
554 margin-top: 8px;
555}
556
557.actions form {
558 display: inline;
559}
560
561.actions .btn {
562 padding: 8px 16px;
563 font-size: 0.8125rem;
564 border: 1px solid var(--border-color);
565 background: var(--bg-primary-color);
566 color: var(--primary-color);
567}
568
569.actions .btn-primary {
570 background: var(--brand-color);
571 color: #fff;
572 border-color: var(--brand-color);
573}
574
575.actions .btn-danger {
576 background: var(--danger-color);
577 color: #fff;
578 border-color: var(--danger-color);
579}
580
581.actions .btn-warning {
582 background: var(--warning-color);
583 color: #000;
584 border-color: var(--warning-color);
585}
586
587.password-box {
588 background: rgba(22, 163, 74, 0.08);
589 border: 1px solid rgba(22, 163, 74, 0.2);
590 border-radius: 10px;
591 padding: 16px 20px;
592 margin-bottom: 16px;
593}
594
595.password-box .pw-label {
596 font-size: 0.75rem;
597 font-weight: 600;
598 color: var(--success-color);
599 margin-bottom: 6px;
600}
601
602.password-box .pw-value {
603 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
604 font-size: 1rem;
605 font-weight: 600;
606 user-select: all;
607}
608
609.back-link {
610 display: inline-block;
611 color: var(--brand-color);
612 text-decoration: none;
613 font-size: 0.8125rem;
614 margin-bottom: 16px;
615}
616
617.back-link:hover {
618 text-decoration: underline;
619}
620
621.collection-list {
622 max-height: 200px;
623 overflow-y: auto;
624 padding: 8px 0;
625}
626
627.collection-item {
628 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
629 font-size: 0.75rem;
630 padding: 4px 0;
631 color: var(--secondary-color);
632 border-bottom: 1px solid var(--border-color);
633}
634
635.collection-item:last-child {
636 border-bottom: none;
637}
638
639.threat-sig {
640 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
641 font-size: 0.75rem;
642 padding: 4px 0;
643 color: var(--secondary-color);
644}
645
646/* --- Create Account ----------------------------------------- */
647
648.success-card {
649 background: var(--bg-primary-color);
650 border: 1px solid var(--border-color);
651 border-radius: 10px;
652 padding: 24px;
653 max-width: 480px;
654}
655
656.success-card h3 {
657 font-size: 1rem;
658 font-weight: 600;
659 color: var(--success-color);
660 margin-bottom: 16px;
661}
662
663.success-card .detail-row .value {
664 display: flex;
665 align-items: center;
666 gap: 6px;
667}
668
669.password-highlight {
670 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
671 background: rgba(22, 163, 74, 0.08);
672 padding: 2px 6px;
673 border-radius: 4px;
674 user-select: all;
675}
676
677.copy-btn {
678 background: none;
679 border: 1px solid var(--border-color);
680 border-radius: 4px;
681 padding: 2px 6px;
682 font-size: 0.6875rem;
683 cursor: pointer;
684 color: var(--secondary-color);
685 transition: color 0.15s, border-color 0.15s;
686 white-space: nowrap;
687}
688
689.copy-btn:hover {
690 color: var(--primary-color);
691 border-color: var(--primary-color);
692}
693
694/* --- Invite Codes ------------------------------------------- */
695
696.code-box {
697 background: rgba(22, 163, 74, 0.08);
698 border: 1px solid rgba(22, 163, 74, 0.2);
699 border-radius: 10px;
700 padding: 16px 20px;
701 margin-bottom: 24px;
702}
703
704.code-box .code-label {
705 font-size: 0.75rem;
706 font-weight: 600;
707 color: var(--success-color);
708 margin-bottom: 6px;
709}
710
711.code-box .code-value {
712 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
713 font-size: 1rem;
714 font-weight: 600;
715 user-select: all;
716}
717
718.code-cell {
719 font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
720 font-size: 0.75rem;
721}
722
723/* --- Login Page --------------------------------------------- */
724
725.login-card {
726 background: var(--bg-primary-color);
727 border: 1px solid var(--border-color);
728 border-radius: 12px;
729 padding: 40px;
730 width: 100%;
731 max-width: 400px;
732 margin: 20px;
733}
734
735.login-title {
736 font-size: 1.5rem;
737 font-weight: 700;
738 text-align: center;
739 margin-bottom: 4px;
740}
741
742.login-subtitle {
743 font-size: 0.875rem;
744 color: var(--secondary-color);
745 text-align: center;
746 margin-bottom: 32px;
747}
748
749.login-card .form-group {
750 margin-bottom: 20px;
751}
752
753.login-card .form-group label {
754 font-size: 0.875rem;
755}
756
757.login-card .btn-primary {
758 width: 100%;
759}
760
761.error-msg {
762 background: rgba(220, 38, 38, 0.1);
763 color: var(--danger-color);
764 border: 1px solid rgba(220, 38, 38, 0.2);
765 border-radius: 8px;
766 padding: 10px 14px;
767 font-size: 0.875rem;
768 margin-bottom: 20px;
769}
770
771/* --- Error Page ---------------------------------------------- */
772
773.centered {
774 display: flex;
775 align-items: center;
776 justify-content: center;
777 min-height: 100vh;
778}
779
780.error-card {
781 background: var(--bg-primary-color);
782 border: 1px solid var(--border-color);
783 border-radius: 12px;
784 padding: 40px;
785 text-align: center;
786 max-width: 480px;
787 width: 100%;
788 margin: 20px;
789}
790
791.error-card--inset {
792 margin: 60px auto;
793}
794
795.error-icon {
796 font-size: 2.5rem;
797 margin-bottom: 16px;
798 color: var(--danger-color);
799}
800
801.error-title {
802 font-size: 1.25rem;
803 font-weight: 700;
804 margin-bottom: 8px;
805}
806
807.error-message {
808 font-size: 0.875rem;
809 color: var(--secondary-color);
810 margin-bottom: 24px;
811 line-height: 1.5;
812}
813
814.error-link {
815 display: inline-flex;
816 align-items: center;
817 justify-content: center;
818 padding: 10px 20px;
819 font-size: 0.875rem;
820 font-weight: 500;
821 border: none;
822 border-radius: 8px;
823 cursor: pointer;
824 text-decoration: none;
825 background: var(--brand-color);
826 color: #fff;
827 transition: opacity 0.15s;
828}
829
830.error-link:hover {
831 opacity: 0.85;
832}
833
834/* --- Responsive --------------------------------------------- */
835
836@media (max-width: 768px) {
837 .sidebar {
838 display: none;
839 }
840
841 .main {
842 margin-left: 0;
843 }
844}