signalk-maintenance-tracker 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +201 -0
  3. package/README.md +75 -0
  4. package/dist/api/router.js +193 -0
  5. package/dist/api/router.js.map +1 -0
  6. package/dist/auth.js +22 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/config.js +50 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/db/database.js +47 -0
  11. package/dist/db/database.js.map +1 -0
  12. package/dist/db/logs.repo.js +91 -0
  13. package/dist/db/logs.repo.js.map +1 -0
  14. package/dist/db/migrations.js +57 -0
  15. package/dist/db/migrations.js.map +1 -0
  16. package/dist/db/tags.repo.js +78 -0
  17. package/dist/db/tags.repo.js.map +1 -0
  18. package/dist/db/tasks.repo.js +78 -0
  19. package/dist/db/tasks.repo.js.map +1 -0
  20. package/dist/domain/slug.js +27 -0
  21. package/dist/domain/slug.js.map +1 -0
  22. package/dist/domain/status.js +110 -0
  23. package/dist/domain/status.js.map +1 -0
  24. package/dist/index.js +130 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/service.js +370 -0
  27. package/dist/service.js.map +1 -0
  28. package/dist/signalk/notifications.js +115 -0
  29. package/dist/signalk/notifications.js.map +1 -0
  30. package/dist/signalk/runtime.js +103 -0
  31. package/dist/signalk/runtime.js.map +1 -0
  32. package/dist/types.js +11 -0
  33. package/dist/types.js.map +1 -0
  34. package/docs/screenshots/completed.png +0 -0
  35. package/docs/screenshots/dark-mode.png +0 -0
  36. package/docs/screenshots/edit-task.png +0 -0
  37. package/docs/screenshots/homepage.png +0 -0
  38. package/docs/screenshots/main-log.png +0 -0
  39. package/docs/screenshots/task-detail.png +0 -0
  40. package/package.json +82 -0
  41. package/public/app/api/client.js +75 -0
  42. package/public/app/api/hooks.js +159 -0
  43. package/public/app/api/resource.js +156 -0
  44. package/public/app/api/signalkPaths.js +66 -0
  45. package/public/app/app.js +71 -0
  46. package/public/app/auth/auth.js +158 -0
  47. package/public/app/components/AuthControl.js +22 -0
  48. package/public/app/components/ConfirmModal.js +55 -0
  49. package/public/app/components/LogEntryModal.js +150 -0
  50. package/public/app/components/LoginModal.js +86 -0
  51. package/public/app/components/MarkdownView.js +17 -0
  52. package/public/app/components/Modal.js +89 -0
  53. package/public/app/components/Pagination.js +35 -0
  54. package/public/app/components/PathPicker.js +66 -0
  55. package/public/app/components/ProgressBar.js +26 -0
  56. package/public/app/components/StatusBadge.js +9 -0
  57. package/public/app/components/Table.js +99 -0
  58. package/public/app/components/TagInput.js +93 -0
  59. package/public/app/components/TaskFormModal.js +310 -0
  60. package/public/app/components/ThemeToggle.js +17 -0
  61. package/public/app/components/Toaster.js +24 -0
  62. package/public/app/lib/format.js +90 -0
  63. package/public/app/lib/html.js +6 -0
  64. package/public/app/lib/router.js +105 -0
  65. package/public/app/lib/sanitize.js +64 -0
  66. package/public/app/lib/slug.js +16 -0
  67. package/public/app/lib/theme.js +52 -0
  68. package/public/app/lib/toasts.js +33 -0
  69. package/public/app/lib/useListParams.js +28 -0
  70. package/public/app/main.js +20 -0
  71. package/public/app/pages/MasterLogPage.js +279 -0
  72. package/public/app/pages/TaskDetailPage.js +294 -0
  73. package/public/app/pages/TaskListPage.js +279 -0
  74. package/public/app/types.js +90 -0
  75. package/public/icons/apple-touch-icon.png +0 -0
  76. package/public/icons/favicon-16.png +0 -0
  77. package/public/icons/favicon-32.png +0 -0
  78. package/public/icons/favicon.ico +0 -0
  79. package/public/icons/icon-192.png +0 -0
  80. package/public/icons/icon-512.png +0 -0
  81. package/public/index.html +82 -0
  82. package/public/manifest.webmanifest +37 -0
  83. package/public/styles/main.css +787 -0
  84. package/public/styles/tokens.css +55 -0
  85. package/public/vendor/VERSIONS.json +9 -0
  86. package/public/vendor/bootstrap-icons/bootstrap-icons.css +2106 -0
  87. package/public/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  88. package/public/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  89. package/public/vendor/dayjs/constant.js +25 -0
  90. package/public/vendor/dayjs/index.d.ts +1 -0
  91. package/public/vendor/dayjs/index.js +541 -0
  92. package/public/vendor/dayjs/locale/en.js +12 -0
  93. package/public/vendor/dayjs/utils.js +58 -0
  94. package/public/vendor/htm.d.ts +1 -0
  95. package/public/vendor/htm.js +1 -0
  96. package/public/vendor/preact-hooks.d.ts +1 -0
  97. package/public/vendor/preact-hooks.js +2 -0
  98. package/public/vendor/preact.d.ts +1 -0
  99. package/public/vendor/preact.js +2 -0
  100. package/public/vendor/signals-core.d.ts +1 -0
  101. package/public/vendor/signals-core.js +1 -0
  102. package/public/vendor/signals.d.ts +1 -0
  103. package/public/vendor/signals.js +1 -0
  104. package/public/vendor/snarkdown.d.ts +1 -0
  105. package/public/vendor/snarkdown.js +2 -0
@@ -0,0 +1,787 @@
1
+ /* Chromium-69-safe baseline (§7.9): custom properties, flexbox (no flex gap —
2
+ grid gap or margins instead), CSS grid + gap, position: sticky. */
3
+
4
+ * {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ html,
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ body {
15
+ background: var(--bg);
16
+ color: var(--text);
17
+ font-family:
18
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
19
+ sans-serif;
20
+ font-size: 15px;
21
+ line-height: 1.45;
22
+ }
23
+
24
+ a {
25
+ color: var(--accent);
26
+ text-decoration: none;
27
+ }
28
+ a:hover {
29
+ text-decoration: underline;
30
+ }
31
+
32
+ code,
33
+ pre {
34
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
35
+ background: var(--bg-inset);
36
+ border-radius: 4px;
37
+ }
38
+ code {
39
+ padding: 1px 4px;
40
+ }
41
+ pre {
42
+ padding: 8px 10px;
43
+ overflow-x: auto;
44
+ }
45
+
46
+ /* ---- layout shell ---- */
47
+
48
+ .shell {
49
+ min-height: 100vh;
50
+ /* iOS Safari: 100vh includes the area behind the collapsible URL bar, which
51
+ pushes the footer offscreen on short pages; dvh tracks the visible viewport.
52
+ Older browsers (Chromium 69) ignore this line and keep the vh fallback. */
53
+ min-height: 100dvh;
54
+ display: flex;
55
+ flex-direction: column;
56
+ }
57
+
58
+ .shell-header {
59
+ position: sticky;
60
+ top: 0;
61
+ z-index: 10;
62
+ background: var(--bg-raised);
63
+ border-bottom: 1px solid var(--border);
64
+ display: grid;
65
+ grid-template-columns: auto 1fr auto;
66
+ grid-gap: 16px;
67
+ align-items: center;
68
+ padding: 0 16px;
69
+ height: 52px;
70
+ }
71
+
72
+ .shell-title {
73
+ font-size: 16px;
74
+ font-weight: 700;
75
+ color: var(--text);
76
+ white-space: nowrap;
77
+ }
78
+ .shell-title:hover {
79
+ text-decoration: none;
80
+ }
81
+
82
+ .shell-nav {
83
+ display: flex;
84
+ align-items: center;
85
+ }
86
+ .shell-nav > * {
87
+ margin-right: 12px;
88
+ }
89
+
90
+ .nav-link {
91
+ padding: 4px 10px;
92
+ border-radius: 6px;
93
+ color: var(--text-muted);
94
+ white-space: nowrap;
95
+ }
96
+ .nav-link:hover {
97
+ text-decoration: none;
98
+ color: var(--text);
99
+ background: var(--bg-inset);
100
+ }
101
+ .nav-link.active {
102
+ color: var(--accent);
103
+ font-weight: 600;
104
+ }
105
+
106
+ .shell-main {
107
+ flex: 1 0 auto;
108
+ width: 100%;
109
+ max-width: 1100px;
110
+ margin: 0 auto;
111
+ padding: 20px 16px;
112
+ }
113
+
114
+ .shell-footer {
115
+ background: var(--bg-raised);
116
+ border-top: 1px solid var(--border);
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ padding: 8px 16px;
121
+ }
122
+
123
+ .shell-footer-links {
124
+ color: var(--text-muted);
125
+ font-size: 13px;
126
+ white-space: nowrap;
127
+ }
128
+ .shell-footer-links .dot-spacer {
129
+ margin: 0 8px;
130
+ }
131
+
132
+ .page-header {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: space-between;
136
+ flex-wrap: wrap;
137
+ margin-bottom: 6px;
138
+ }
139
+ .page-header > * {
140
+ margin-bottom: 8px;
141
+ margin-right: 12px;
142
+ }
143
+ .page-header > *:last-child {
144
+ margin-right: 0;
145
+ }
146
+ .page-title {
147
+ display: flex;
148
+ align-items: center;
149
+ flex-wrap: wrap;
150
+ font-size: 20px;
151
+ font-weight: 700;
152
+ margin: 0;
153
+ }
154
+ /* flex drops the whitespace between title text and badge */
155
+ .page-title .badge {
156
+ margin-left: 10px;
157
+ }
158
+
159
+ /* header action buttons; wraps below the title on narrow screens */
160
+ .page-actions {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ align-items: center;
164
+ }
165
+ .page-actions > * {
166
+ margin-right: 8px;
167
+ margin-bottom: 4px;
168
+ }
169
+ .page-actions > *:last-child {
170
+ margin-right: 0;
171
+ }
172
+ @media (max-width: 640px) {
173
+ .page-actions {
174
+ margin-top: 8px;
175
+ margin-bottom: 4px;
176
+ }
177
+ }
178
+
179
+ /* ---- buttons ---- */
180
+
181
+ .btn {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ padding: 6px 14px;
186
+ border: 1px solid var(--border-strong);
187
+ border-radius: 6px;
188
+ background: var(--bg-raised);
189
+ color: var(--text);
190
+ font-size: 14px;
191
+ font-family: inherit;
192
+ cursor: pointer;
193
+ white-space: nowrap;
194
+ }
195
+ .btn:hover {
196
+ background: var(--bg-inset);
197
+ }
198
+ .btn:disabled {
199
+ opacity: 0.5;
200
+ cursor: default;
201
+ }
202
+ .btn .bi {
203
+ margin-right: 6px;
204
+ }
205
+
206
+ .btn-primary {
207
+ background: var(--accent);
208
+ border-color: var(--accent);
209
+ color: var(--accent-text);
210
+ }
211
+ .btn-primary:hover {
212
+ background: var(--accent-hover);
213
+ border-color: var(--accent-hover);
214
+ }
215
+
216
+ .btn-danger {
217
+ background: var(--danger);
218
+ border-color: var(--danger);
219
+ color: #fff;
220
+ }
221
+ .btn-danger:hover {
222
+ background: var(--danger-hover);
223
+ border-color: var(--danger-hover);
224
+ }
225
+
226
+ .btn-success {
227
+ background: var(--ok);
228
+ border-color: var(--ok);
229
+ color: #fff;
230
+ }
231
+ .btn-success:hover {
232
+ background: var(--ok-hover);
233
+ border-color: var(--ok-hover);
234
+ }
235
+
236
+ .btn-icon {
237
+ display: inline-flex;
238
+ align-items: center;
239
+ justify-content: center;
240
+ width: 30px;
241
+ height: 30px;
242
+ padding: 0;
243
+ border: none;
244
+ border-radius: 6px;
245
+ background: transparent;
246
+ color: var(--text-muted);
247
+ font-size: 15px;
248
+ cursor: pointer;
249
+ }
250
+ .btn-icon:hover {
251
+ background: var(--bg-inset);
252
+ color: var(--text);
253
+ }
254
+ .btn-icon.primary {
255
+ color: var(--accent);
256
+ }
257
+ .btn-icon.primary:hover {
258
+ background: var(--accent-bg);
259
+ color: var(--accent);
260
+ }
261
+ .btn-icon.success {
262
+ color: var(--ok);
263
+ }
264
+ .btn-icon.success:hover {
265
+ background: var(--ok-bg);
266
+ color: var(--ok);
267
+ }
268
+ .btn-icon.danger {
269
+ color: var(--danger);
270
+ }
271
+ .btn-icon.danger:hover {
272
+ background: var(--danger-bg);
273
+ color: var(--danger);
274
+ }
275
+
276
+ .btn-link {
277
+ border: none;
278
+ background: none;
279
+ color: var(--accent);
280
+ font-size: inherit;
281
+ font-family: inherit;
282
+ cursor: pointer;
283
+ padding: 0;
284
+ }
285
+ .btn-link:hover {
286
+ text-decoration: underline;
287
+ }
288
+
289
+ /* ---- inputs & forms ---- */
290
+
291
+ .input,
292
+ .select,
293
+ .textarea {
294
+ width: 100%;
295
+ padding: 6px 10px;
296
+ border: 1px solid var(--border-strong);
297
+ border-radius: 6px;
298
+ background: var(--bg-raised);
299
+ color: var(--text);
300
+ font-size: 14px;
301
+ font-family: inherit;
302
+ }
303
+ .textarea {
304
+ min-height: 110px;
305
+ resize: vertical;
306
+ }
307
+ /* iOS gives date inputs native chrome with its own oversized intrinsic height;
308
+ flatten it so it matches the other inputs (min-height keeps it from collapsing) */
309
+ .input[type='date'] {
310
+ -webkit-appearance: none;
311
+ appearance: none;
312
+ min-height: 34px;
313
+ }
314
+ .input:focus,
315
+ .select:focus,
316
+ .textarea:focus {
317
+ outline: none;
318
+ border-color: var(--accent);
319
+ }
320
+
321
+ .field {
322
+ margin-bottom: 14px;
323
+ }
324
+ .field-label {
325
+ display: block;
326
+ font-size: 13px;
327
+ font-weight: 600;
328
+ margin-bottom: 4px;
329
+ }
330
+ .field-hint {
331
+ font-size: 12px;
332
+ color: var(--text-muted);
333
+ margin-top: 3px;
334
+ }
335
+ .field-error {
336
+ font-size: 12px;
337
+ color: var(--danger);
338
+ margin-top: 3px;
339
+ }
340
+ .field-row {
341
+ display: grid;
342
+ grid-template-columns: 1fr 1fr;
343
+ grid-gap: 12px;
344
+ }
345
+
346
+ .form-error {
347
+ background: var(--danger-bg);
348
+ color: var(--danger);
349
+ border-radius: 6px;
350
+ padding: 8px 12px;
351
+ margin-bottom: 12px;
352
+ font-size: 13px;
353
+ }
354
+
355
+ .search-box {
356
+ position: relative;
357
+ }
358
+ .search-box .bi {
359
+ position: absolute;
360
+ left: 9px;
361
+ top: 50%;
362
+ transform: translateY(-50%);
363
+ color: var(--text-muted);
364
+ pointer-events: none;
365
+ }
366
+ .search-box .input {
367
+ padding-left: 30px;
368
+ }
369
+
370
+ /* ---- toolbar (list controls) ---- */
371
+
372
+ /* children/chips carry wrap margins, so the toolbar itself adds almost none */
373
+ .toolbar {
374
+ display: flex;
375
+ flex-wrap: wrap;
376
+ align-items: center;
377
+ margin-bottom: 2px;
378
+ }
379
+ .toolbar > * {
380
+ margin-right: 10px;
381
+ margin-bottom: 6px;
382
+ }
383
+ .toolbar .search-box {
384
+ width: 260px;
385
+ }
386
+
387
+ /* ---- tag chips ---- */
388
+
389
+ .chips {
390
+ display: flex;
391
+ flex-wrap: wrap;
392
+ align-items: center;
393
+ }
394
+ .chips > * {
395
+ margin-right: 6px;
396
+ margin-bottom: 4px;
397
+ }
398
+
399
+ .chip {
400
+ display: inline-flex;
401
+ align-items: center;
402
+ padding: 2px 10px;
403
+ border: 1px solid var(--border-strong);
404
+ border-radius: 999px;
405
+ background: var(--bg-raised);
406
+ color: var(--text-muted);
407
+ font-size: 12.5px;
408
+ font-family: inherit;
409
+ cursor: pointer;
410
+ white-space: nowrap;
411
+ }
412
+ .chip:hover {
413
+ border-color: var(--accent);
414
+ color: var(--accent);
415
+ }
416
+ .chip.selected {
417
+ background: var(--accent);
418
+ border-color: var(--accent);
419
+ color: var(--accent-text);
420
+ }
421
+ .chip .chip-count {
422
+ opacity: 0.7;
423
+ margin-left: 5px;
424
+ }
425
+ .chip .bi {
426
+ margin-left: 6px;
427
+ }
428
+
429
+ .tag {
430
+ display: inline-block;
431
+ padding: 1px 8px;
432
+ border-radius: 999px;
433
+ background: var(--bg-inset);
434
+ color: var(--text-muted);
435
+ font-size: 12px;
436
+ white-space: nowrap;
437
+ }
438
+
439
+ /* ---- table ---- */
440
+
441
+ .table-wrap {
442
+ background: var(--bg-raised);
443
+ border: 1px solid var(--border);
444
+ border-radius: 8px;
445
+ overflow-x: auto;
446
+ }
447
+
448
+ .table {
449
+ width: 100%;
450
+ border-collapse: collapse;
451
+ font-size: 14px;
452
+ }
453
+ .table th,
454
+ .table td {
455
+ text-align: left;
456
+ padding: 8px 12px;
457
+ border-bottom: 1px solid var(--border);
458
+ vertical-align: middle;
459
+ }
460
+ .table tbody tr:last-child td {
461
+ border-bottom: none;
462
+ }
463
+ .table tbody tr:hover td {
464
+ background: var(--bg-inset);
465
+ }
466
+ .table th {
467
+ font-size: 12.5px;
468
+ text-transform: uppercase;
469
+ letter-spacing: 0.03em;
470
+ color: var(--text-muted);
471
+ white-space: nowrap;
472
+ }
473
+ .table th.sortable {
474
+ cursor: pointer;
475
+ user-select: none;
476
+ }
477
+ .table th.sortable:hover {
478
+ color: var(--text);
479
+ }
480
+ .table th .bi {
481
+ margin-left: 4px;
482
+ }
483
+ .table td.actions,
484
+ .table th.actions {
485
+ text-align: right;
486
+ white-space: nowrap;
487
+ }
488
+ .table td.num {
489
+ font-variant-numeric: tabular-nums;
490
+ }
491
+
492
+ /* full-width detail row (log notes) rendered under its entry row */
493
+ .table tr.has-detail td {
494
+ border-bottom: none;
495
+ }
496
+ .table tr.detail-row td {
497
+ padding-top: 0;
498
+ }
499
+ .table tbody tr.detail-row:hover td {
500
+ background: transparent;
501
+ }
502
+
503
+ .log-notes {
504
+ display: flex;
505
+ }
506
+ .log-notes-label {
507
+ flex-shrink: 0;
508
+ margin-right: 6px;
509
+ }
510
+ .log-notes-body {
511
+ min-width: 0;
512
+ }
513
+
514
+ /* columns hidden on small screens */
515
+ @media (max-width: 640px) {
516
+ .table th.hide-sm,
517
+ .table td.hide-sm {
518
+ display: none;
519
+ }
520
+ }
521
+
522
+ .table-empty {
523
+ padding: 32px 16px;
524
+ text-align: center;
525
+ color: var(--text-muted);
526
+ }
527
+
528
+ .table-loading {
529
+ padding: 32px 16px;
530
+ text-align: center;
531
+ color: var(--text-muted);
532
+ }
533
+
534
+ /* ---- status badges ---- */
535
+
536
+ .badge {
537
+ display: inline-block;
538
+ padding: 2px 9px;
539
+ border-radius: 999px;
540
+ font-size: 12px;
541
+ font-weight: 600;
542
+ white-space: nowrap;
543
+ }
544
+ .badge.overdue {
545
+ background: var(--danger-bg);
546
+ color: var(--danger);
547
+ }
548
+ .badge.due_soon {
549
+ background: var(--warn-bg);
550
+ color: var(--warn);
551
+ }
552
+ .badge.ok {
553
+ background: var(--ok-bg);
554
+ color: var(--ok);
555
+ }
556
+ .badge.unknown {
557
+ background: var(--unknown-bg);
558
+ color: var(--unknown);
559
+ }
560
+
561
+ /* remaining time/runtime cells, colored by per-interval status */
562
+ .remaining.ok {
563
+ color: var(--ok);
564
+ }
565
+ .remaining.due_soon {
566
+ color: var(--warn);
567
+ }
568
+ .remaining.overdue {
569
+ color: var(--danger);
570
+ }
571
+
572
+ /* ---- progress bars ---- */
573
+
574
+ .progress {
575
+ height: 8px;
576
+ border-radius: 999px;
577
+ background: var(--bg-inset);
578
+ overflow: hidden;
579
+ }
580
+ .progress-fill {
581
+ height: 100%;
582
+ border-radius: 999px;
583
+ background: var(--ok);
584
+ }
585
+ .progress-fill.due_soon {
586
+ background: var(--warn);
587
+ }
588
+ .progress-fill.overdue {
589
+ background: var(--danger);
590
+ }
591
+
592
+ /* ---- pagination ---- */
593
+
594
+ .pagination {
595
+ display: flex;
596
+ align-items: center;
597
+ justify-content: flex-end;
598
+ margin-top: 12px;
599
+ font-size: 13px;
600
+ color: var(--text-muted);
601
+ }
602
+ .pagination > * {
603
+ margin-left: 8px;
604
+ }
605
+
606
+ /* ---- modal ---- */
607
+
608
+ .modal-overlay {
609
+ position: fixed;
610
+ top: 0;
611
+ left: 0;
612
+ right: 0;
613
+ bottom: 0;
614
+ background: var(--overlay);
615
+ z-index: 100;
616
+ display: flex;
617
+ align-items: flex-start;
618
+ justify-content: center;
619
+ padding: 40px 16px;
620
+ overflow-y: auto;
621
+ }
622
+
623
+ .modal {
624
+ background: var(--bg-raised);
625
+ border: 1px solid var(--border);
626
+ border-radius: 10px;
627
+ box-shadow: var(--shadow);
628
+ width: 100%;
629
+ max-width: 560px;
630
+ }
631
+ .modal.narrow {
632
+ max-width: 400px;
633
+ }
634
+
635
+ .modal-header {
636
+ display: flex;
637
+ align-items: center;
638
+ justify-content: space-between;
639
+ padding: 14px 18px;
640
+ border-bottom: 1px solid var(--border);
641
+ }
642
+ .modal-title {
643
+ font-size: 16px;
644
+ font-weight: 700;
645
+ margin: 0;
646
+ }
647
+ .modal-body {
648
+ padding: 18px;
649
+ }
650
+ .modal-footer {
651
+ display: flex;
652
+ justify-content: flex-end;
653
+ padding: 14px 18px;
654
+ border-top: 1px solid var(--border);
655
+ }
656
+ .modal-footer > * {
657
+ margin-left: 10px;
658
+ }
659
+
660
+ /* ---- toasts ---- */
661
+
662
+ .toaster {
663
+ position: fixed;
664
+ bottom: 16px;
665
+ right: 16px;
666
+ z-index: 200;
667
+ display: grid;
668
+ grid-gap: 8px;
669
+ max-width: 340px;
670
+ }
671
+
672
+ .toast {
673
+ background: var(--bg-raised);
674
+ border: 1px solid var(--border-strong);
675
+ border-left: 4px solid var(--accent);
676
+ border-radius: 8px;
677
+ box-shadow: var(--shadow);
678
+ padding: 10px 14px;
679
+ font-size: 14px;
680
+ }
681
+ .toast.error {
682
+ border-left-color: var(--danger);
683
+ }
684
+ .toast.success {
685
+ border-left-color: var(--ok);
686
+ }
687
+
688
+ /* ---- detail page ---- */
689
+
690
+ .detail-grid {
691
+ display: grid;
692
+ grid-template-columns: 1fr;
693
+ grid-gap: 16px;
694
+ }
695
+ @media (min-width: 800px) {
696
+ .detail-grid {
697
+ grid-template-columns: 3fr 2fr;
698
+ }
699
+ }
700
+
701
+ .card {
702
+ background: var(--bg-raised);
703
+ border: 1px solid var(--border);
704
+ border-radius: 8px;
705
+ padding: 16px 18px;
706
+ }
707
+ .card h3 {
708
+ margin: 0 0 10px;
709
+ font-size: 13px;
710
+ text-transform: uppercase;
711
+ letter-spacing: 0.03em;
712
+ color: var(--text-muted);
713
+ }
714
+
715
+ .stat-row {
716
+ margin-bottom: 12px;
717
+ }
718
+ .stat-row .stat-label {
719
+ display: flex;
720
+ justify-content: space-between;
721
+ font-size: 13px;
722
+ color: var(--text-muted);
723
+ margin-bottom: 4px;
724
+ }
725
+ .stat-row .stat-value {
726
+ color: var(--text);
727
+ font-weight: 600;
728
+ }
729
+
730
+ .markdown p:first-child {
731
+ margin-top: 0;
732
+ }
733
+ .markdown p:last-child {
734
+ margin-bottom: 0;
735
+ }
736
+ .markdown img {
737
+ max-width: 100%;
738
+ }
739
+
740
+ /* ---- misc ---- */
741
+
742
+ .muted {
743
+ color: var(--text-muted);
744
+ }
745
+ .error-box {
746
+ background: var(--danger-bg);
747
+ color: var(--danger);
748
+ border-radius: 8px;
749
+ padding: 12px 16px;
750
+ }
751
+
752
+ .slug-preview {
753
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
754
+ font-size: 13px;
755
+ }
756
+
757
+ /* autocomplete dropdown (runtime path picker, tag input) */
758
+ .combo {
759
+ position: relative;
760
+ }
761
+ .combo-list {
762
+ position: absolute;
763
+ top: 100%;
764
+ left: 0;
765
+ right: 0;
766
+ z-index: 50;
767
+ margin: 2px 0 0;
768
+ padding: 4px;
769
+ list-style: none;
770
+ background: var(--bg-raised);
771
+ border: 1px solid var(--border-strong);
772
+ border-radius: 6px;
773
+ box-shadow: var(--shadow);
774
+ max-height: 220px;
775
+ overflow-y: auto;
776
+ }
777
+ .combo-list li {
778
+ padding: 5px 8px;
779
+ border-radius: 4px;
780
+ cursor: pointer;
781
+ font-size: 13px;
782
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
783
+ }
784
+ .combo-list li:hover,
785
+ .combo-list li.active {
786
+ background: var(--bg-inset);
787
+ }