skrift 0.1.0a12__py3-none-any.whl

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 (74) hide show
  1. skrift/__init__.py +1 -0
  2. skrift/__main__.py +12 -0
  3. skrift/admin/__init__.py +11 -0
  4. skrift/admin/controller.py +452 -0
  5. skrift/admin/navigation.py +105 -0
  6. skrift/alembic/env.py +92 -0
  7. skrift/alembic/script.py.mako +26 -0
  8. skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py +70 -0
  9. skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py +57 -0
  10. skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py +31 -0
  11. skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py +43 -0
  12. skrift/alembic/versions/20260122_200000_add_settings_table.py +38 -0
  13. skrift/alembic/versions/20260129_add_oauth_accounts.py +141 -0
  14. skrift/alembic/versions/20260129_add_provider_metadata.py +29 -0
  15. skrift/alembic.ini +77 -0
  16. skrift/asgi.py +670 -0
  17. skrift/auth/__init__.py +58 -0
  18. skrift/auth/guards.py +130 -0
  19. skrift/auth/roles.py +129 -0
  20. skrift/auth/services.py +184 -0
  21. skrift/cli.py +143 -0
  22. skrift/config.py +259 -0
  23. skrift/controllers/__init__.py +4 -0
  24. skrift/controllers/auth.py +595 -0
  25. skrift/controllers/web.py +67 -0
  26. skrift/db/__init__.py +3 -0
  27. skrift/db/base.py +7 -0
  28. skrift/db/models/__init__.py +7 -0
  29. skrift/db/models/oauth_account.py +50 -0
  30. skrift/db/models/page.py +26 -0
  31. skrift/db/models/role.py +56 -0
  32. skrift/db/models/setting.py +13 -0
  33. skrift/db/models/user.py +36 -0
  34. skrift/db/services/__init__.py +1 -0
  35. skrift/db/services/oauth_service.py +195 -0
  36. skrift/db/services/page_service.py +217 -0
  37. skrift/db/services/setting_service.py +206 -0
  38. skrift/lib/__init__.py +3 -0
  39. skrift/lib/exceptions.py +168 -0
  40. skrift/lib/template.py +108 -0
  41. skrift/setup/__init__.py +14 -0
  42. skrift/setup/config_writer.py +213 -0
  43. skrift/setup/controller.py +888 -0
  44. skrift/setup/middleware.py +89 -0
  45. skrift/setup/providers.py +214 -0
  46. skrift/setup/state.py +315 -0
  47. skrift/static/css/style.css +1003 -0
  48. skrift/templates/admin/admin.html +19 -0
  49. skrift/templates/admin/base.html +24 -0
  50. skrift/templates/admin/pages/edit.html +32 -0
  51. skrift/templates/admin/pages/list.html +62 -0
  52. skrift/templates/admin/settings/site.html +32 -0
  53. skrift/templates/admin/users/list.html +58 -0
  54. skrift/templates/admin/users/roles.html +42 -0
  55. skrift/templates/auth/dummy_login.html +102 -0
  56. skrift/templates/auth/login.html +139 -0
  57. skrift/templates/base.html +52 -0
  58. skrift/templates/error-404.html +19 -0
  59. skrift/templates/error-500.html +19 -0
  60. skrift/templates/error.html +19 -0
  61. skrift/templates/index.html +9 -0
  62. skrift/templates/page.html +26 -0
  63. skrift/templates/setup/admin.html +24 -0
  64. skrift/templates/setup/auth.html +110 -0
  65. skrift/templates/setup/base.html +407 -0
  66. skrift/templates/setup/complete.html +17 -0
  67. skrift/templates/setup/configuring.html +158 -0
  68. skrift/templates/setup/database.html +125 -0
  69. skrift/templates/setup/restart.html +28 -0
  70. skrift/templates/setup/site.html +39 -0
  71. skrift-0.1.0a12.dist-info/METADATA +235 -0
  72. skrift-0.1.0a12.dist-info/RECORD +74 -0
  73. skrift-0.1.0a12.dist-info/WHEEL +4 -0
  74. skrift-0.1.0a12.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,1003 @@
1
+ /* Custom Minimalist CSS Framework */
2
+
3
+ /* ========================================
4
+ CSS Variables / Theme
5
+ ======================================== */
6
+
7
+ :root {
8
+ /* Light theme (default) */
9
+ --color-bg: #fafafa;
10
+ --color-surface: #f0f0f0;
11
+ --color-text: #1a1a1a;
12
+ --color-text-muted: #6b6b6b;
13
+ --color-primary: #1a1a1a;
14
+ --color-primary-hover: #333333;
15
+ --color-primary-text: #fafafa;
16
+ --color-primary-focus: rgba(26, 26, 26, 0.2);
17
+ --color-border: #e0e0e0;
18
+ --color-success: #10b981;
19
+ --color-error: #ef4444;
20
+
21
+ /* Spacing */
22
+ --spacing-xs: 0.25rem;
23
+ --spacing-sm: 0.5rem;
24
+ --spacing-md: 1rem;
25
+ --spacing-lg: 1.5rem;
26
+ --spacing-xl: 2rem;
27
+ --spacing-2xl: 3rem;
28
+
29
+ /* Typography */
30
+ --font-family: 'Lora', Georgia, 'Times New Roman', serif;
31
+ --font-family-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
32
+ --line-height: 1.6;
33
+ --line-height-tight: 1.3;
34
+
35
+ /* Borders */
36
+ --radius-sm: 4px;
37
+ --radius-md: 8px;
38
+ --radius-lg: 12px;
39
+ --radius-pill: 999px;
40
+
41
+ /* Shadows */
42
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
43
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
44
+
45
+ /* Transitions */
46
+ --transition-fast: 150ms ease;
47
+ --transition-normal: 200ms ease;
48
+ }
49
+
50
+ /* Dark theme - system preference */
51
+ @media (prefers-color-scheme: dark) {
52
+ :root {
53
+ --color-bg: #121212;
54
+ --color-surface: #1e1e1e;
55
+ --color-text: #e5e5e5;
56
+ --color-text-muted: #9a9a9a;
57
+ --color-primary: #e5e5e5;
58
+ --color-primary-hover: #ffffff;
59
+ --color-primary-text: #121212;
60
+ --color-primary-focus: rgba(229, 229, 229, 0.25);
61
+ --color-border: #2e2e2e;
62
+ --color-success: #34d399;
63
+ --color-error: #f87171;
64
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
65
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
66
+ }
67
+ }
68
+
69
+ /* Dark theme - manual override */
70
+ [data-theme="dark"] {
71
+ --color-bg: #121212;
72
+ --color-surface: #1e1e1e;
73
+ --color-text: #e5e5e5;
74
+ --color-text-muted: #9a9a9a;
75
+ --color-primary: #e5e5e5;
76
+ --color-primary-hover: #ffffff;
77
+ --color-primary-text: #121212;
78
+ --color-primary-focus: rgba(229, 229, 229, 0.25);
79
+ --color-border: #2e2e2e;
80
+ --color-success: #34d399;
81
+ --color-error: #f87171;
82
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
83
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
84
+ }
85
+
86
+ /* Light theme - manual override */
87
+ [data-theme="light"] {
88
+ --color-bg: #fafafa;
89
+ --color-surface: #f0f0f0;
90
+ --color-text: #1a1a1a;
91
+ --color-text-muted: #6b6b6b;
92
+ --color-primary: #1a1a1a;
93
+ --color-primary-hover: #333333;
94
+ --color-primary-text: #fafafa;
95
+ --color-primary-focus: rgba(26, 26, 26, 0.2);
96
+ --color-border: #e0e0e0;
97
+ --color-success: #10b981;
98
+ --color-error: #ef4444;
99
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
100
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
101
+ }
102
+
103
+ /* ========================================
104
+ Reset & Base
105
+ ======================================== */
106
+
107
+ *, *::before, *::after {
108
+ box-sizing: border-box;
109
+ }
110
+
111
+ * {
112
+ margin: 0;
113
+ }
114
+
115
+ html {
116
+ scroll-behavior: smooth;
117
+ -webkit-text-size-adjust: 100%;
118
+ }
119
+
120
+ body {
121
+ font-family: var(--font-family);
122
+ font-size: 1rem;
123
+ line-height: var(--line-height);
124
+ color: var(--color-text);
125
+ background-color: var(--color-bg);
126
+ min-height: 100vh;
127
+ -webkit-font-smoothing: antialiased;
128
+ -moz-osx-font-smoothing: grayscale;
129
+ }
130
+
131
+ /* ========================================
132
+ Typography
133
+ ======================================== */
134
+
135
+ h1, h2, h3, h4, h5, h6 {
136
+ line-height: var(--line-height-tight);
137
+ letter-spacing: -0.025em;
138
+ font-weight: 600;
139
+ margin-bottom: var(--spacing-md);
140
+ }
141
+
142
+ h1, h2 {
143
+ font-family: 'Cinzel', serif;
144
+ letter-spacing: 0.02em;
145
+ }
146
+
147
+ h1 { font-size: 2.25rem; }
148
+ h2 { font-size: 1.875rem; }
149
+ h3 { font-size: 1.5rem; }
150
+ h4 { font-size: 1.25rem; }
151
+ h5 { font-size: 1.125rem; }
152
+ h6 { font-size: 1rem; }
153
+
154
+ p {
155
+ margin-bottom: var(--spacing-md);
156
+ }
157
+
158
+ p:last-child {
159
+ margin-bottom: 0;
160
+ }
161
+
162
+ small {
163
+ font-size: 0.875rem;
164
+ color: var(--color-text-muted);
165
+ }
166
+
167
+ strong {
168
+ font-weight: 600;
169
+ }
170
+
171
+ a {
172
+ color: var(--color-primary);
173
+ text-decoration: none;
174
+ transition: color var(--transition-fast);
175
+ position: relative;
176
+ }
177
+
178
+ a:not([role="button"]):not(.oauth-btn)::after {
179
+ content: '';
180
+ position: absolute;
181
+ left: 0;
182
+ bottom: -1px;
183
+ width: 100%;
184
+ height: 1px;
185
+ background-color: currentColor;
186
+ transform: scaleX(0);
187
+ transform-origin: right;
188
+ transition: transform 500ms ease-out;
189
+ }
190
+
191
+ a:not([role="button"]):not(.oauth-btn):hover {
192
+ color: var(--color-primary-hover);
193
+ }
194
+
195
+ a:not([role="button"]):not(.oauth-btn):hover::after {
196
+ transform: scaleX(1);
197
+ transform-origin: left;
198
+ }
199
+
200
+ code {
201
+ font-family: var(--font-family-mono);
202
+ font-size: 0.875em;
203
+ background-color: var(--color-surface);
204
+ padding: 0.125rem 0.375rem;
205
+ border-radius: var(--radius-sm);
206
+ }
207
+
208
+ pre {
209
+ font-family: var(--font-family-mono);
210
+ font-size: 0.875rem;
211
+ background-color: var(--color-surface);
212
+ padding: var(--spacing-md);
213
+ border-radius: var(--radius-md);
214
+ overflow-x: auto;
215
+ margin-bottom: var(--spacing-md);
216
+ }
217
+
218
+ pre code {
219
+ background: none;
220
+ padding: 0;
221
+ }
222
+
223
+ hgroup {
224
+ margin-bottom: var(--spacing-lg);
225
+ }
226
+
227
+ hgroup h1, hgroup h2, hgroup h3,
228
+ hgroup h4, hgroup h5, hgroup h6 {
229
+ margin-bottom: var(--spacing-xs);
230
+ }
231
+
232
+ hgroup p {
233
+ color: var(--color-text-muted);
234
+ margin-bottom: 0;
235
+ }
236
+
237
+ /* ========================================
238
+ Layout
239
+ ======================================== */
240
+
241
+ .container {
242
+ width: 100%;
243
+ max-width: 720px;
244
+ margin-left: auto;
245
+ margin-right: auto;
246
+ padding-left: var(--spacing-md);
247
+ padding-right: var(--spacing-md);
248
+ }
249
+
250
+ @media (min-width: 640px) {
251
+ .container {
252
+ padding-left: var(--spacing-lg);
253
+ padding-right: var(--spacing-lg);
254
+ }
255
+ }
256
+
257
+ header {
258
+ padding-top: var(--spacing-lg);
259
+ padding-bottom: var(--spacing-md);
260
+ }
261
+
262
+ main {
263
+ padding-top: var(--spacing-xl);
264
+ padding-bottom: var(--spacing-2xl);
265
+ min-height: calc(100vh - 200px);
266
+ }
267
+
268
+ footer {
269
+ padding-bottom: var(--spacing-xl);
270
+ }
271
+
272
+ section {
273
+ margin-bottom: var(--spacing-2xl);
274
+ }
275
+
276
+ section:last-child {
277
+ margin-bottom: 0;
278
+ }
279
+
280
+ /* ========================================
281
+ Navigation
282
+ ======================================== */
283
+
284
+ nav {
285
+ display: flex;
286
+ justify-content: space-between;
287
+ align-items: center;
288
+ flex-wrap: wrap;
289
+ gap: var(--spacing-md);
290
+ }
291
+
292
+ nav ul {
293
+ list-style: none;
294
+ padding: 0;
295
+ margin: 0;
296
+ display: flex;
297
+ align-items: center;
298
+ gap: var(--spacing-md);
299
+ }
300
+
301
+ nav li {
302
+ margin-bottom: 0;
303
+ }
304
+
305
+ nav a {
306
+ color: var(--color-text);
307
+ font-weight: 500;
308
+ }
309
+
310
+ nav a:hover {
311
+ color: var(--color-primary);
312
+ }
313
+
314
+ .site-name {
315
+ font-family: 'Cinzel', serif;
316
+ font-size: 1.5rem;
317
+ font-weight: 600;
318
+ letter-spacing: 0.05em;
319
+ }
320
+
321
+ nav .user-info {
322
+ display: flex;
323
+ align-items: center;
324
+ gap: var(--spacing-sm);
325
+ }
326
+
327
+ nav .user-info img {
328
+ width: 32px;
329
+ height: 32px;
330
+ border-radius: 50%;
331
+ object-fit: cover;
332
+ }
333
+
334
+ nav .user-info span {
335
+ color: var(--color-text-muted);
336
+ font-size: 0.875rem;
337
+ }
338
+
339
+ /* ========================================
340
+ Buttons (Pill Style)
341
+ ======================================== */
342
+
343
+ button,
344
+ [role="button"],
345
+ input[type="submit"],
346
+ input[type="button"],
347
+ input[type="reset"] {
348
+ display: inline-flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ gap: var(--spacing-sm);
352
+ font-family: inherit;
353
+ font-size: 0.875rem;
354
+ font-weight: 600;
355
+ line-height: 1;
356
+ padding: 0.625rem 1.25rem;
357
+ border: 1px solid transparent;
358
+ border-radius: var(--radius-pill);
359
+ background-color: var(--color-primary);
360
+ color: var(--color-primary-text);
361
+ cursor: pointer;
362
+ text-decoration: none;
363
+ transition: background-color var(--transition-fast),
364
+ box-shadow var(--transition-fast),
365
+ transform var(--transition-fast);
366
+ box-shadow: var(--shadow-sm);
367
+ }
368
+
369
+ button:hover,
370
+ [role="button"]:hover,
371
+ input[type="submit"]:hover,
372
+ input[type="button"]:hover,
373
+ input[type="reset"]:hover {
374
+ background-color: var(--color-primary-hover);
375
+ color: var(--color-primary-text);
376
+ box-shadow: var(--shadow-md);
377
+ }
378
+
379
+ button:active,
380
+ [role="button"]:active,
381
+ input[type="submit"]:active,
382
+ input[type="button"]:active,
383
+ input[type="reset"]:active {
384
+ transform: translateY(1px);
385
+ box-shadow: var(--shadow-sm);
386
+ }
387
+
388
+ button:focus-visible,
389
+ [role="button"]:focus-visible,
390
+ input[type="submit"]:focus-visible,
391
+ input[type="button"]:focus-visible,
392
+ input[type="reset"]:focus-visible {
393
+ outline: 2px solid var(--color-primary);
394
+ outline-offset: 2px;
395
+ }
396
+
397
+ button:disabled,
398
+ [role="button"][aria-disabled="true"],
399
+ input[type="submit"]:disabled,
400
+ input[type="button"]:disabled,
401
+ input[type="reset"]:disabled {
402
+ opacity: 0.5;
403
+ cursor: not-allowed;
404
+ transform: none;
405
+ }
406
+
407
+ /* Outline button variant */
408
+ button.outline,
409
+ [role="button"].outline,
410
+ .outline[role="button"] {
411
+ background-color: transparent;
412
+ border-color: var(--color-border);
413
+ color: var(--color-text);
414
+ }
415
+
416
+ button.outline:hover,
417
+ [role="button"].outline:hover,
418
+ .outline[role="button"]:hover {
419
+ background-color: var(--color-surface);
420
+ border-color: var(--color-text-muted);
421
+ color: var(--color-text);
422
+ }
423
+
424
+ /* Ensure link buttons don't stretch in flex containers */
425
+ a[role="button"] {
426
+ align-self: center;
427
+ }
428
+
429
+ /* ========================================
430
+ Forms (Minimal Style)
431
+ ======================================== */
432
+
433
+ label {
434
+ display: block;
435
+ font-size: 0.875rem;
436
+ font-weight: 500;
437
+ margin-bottom: var(--spacing-xs);
438
+ color: var(--color-text);
439
+ }
440
+
441
+ input[type="text"],
442
+ input[type="email"],
443
+ input[type="password"],
444
+ input[type="search"],
445
+ input[type="url"],
446
+ input[type="tel"],
447
+ input[type="number"],
448
+ input[type="date"],
449
+ input[type="time"],
450
+ input[type="datetime-local"],
451
+ textarea,
452
+ select {
453
+ width: 100%;
454
+ font-family: inherit;
455
+ font-size: 1rem;
456
+ line-height: 1.5;
457
+ padding: 0.625rem 0.875rem;
458
+ border: 1px solid var(--color-border);
459
+ border-radius: var(--radius-md);
460
+ background-color: var(--color-bg);
461
+ color: var(--color-text);
462
+ transition: border-color var(--transition-fast),
463
+ box-shadow var(--transition-fast);
464
+ }
465
+
466
+ input::placeholder,
467
+ textarea::placeholder {
468
+ color: var(--color-text-muted);
469
+ opacity: 0.7;
470
+ }
471
+
472
+ input:focus,
473
+ textarea:focus,
474
+ select:focus {
475
+ outline: none;
476
+ border-color: var(--color-primary);
477
+ box-shadow: 0 0 0 3px var(--color-primary-focus);
478
+ }
479
+
480
+ input:disabled,
481
+ textarea:disabled,
482
+ select:disabled {
483
+ background-color: var(--color-surface);
484
+ opacity: 0.7;
485
+ cursor: not-allowed;
486
+ }
487
+
488
+ textarea {
489
+ resize: vertical;
490
+ min-height: 100px;
491
+ }
492
+
493
+ fieldset {
494
+ border: 1px solid var(--color-border);
495
+ border-radius: var(--radius-md);
496
+ padding: var(--spacing-lg);
497
+ margin-bottom: var(--spacing-md);
498
+ }
499
+
500
+ legend {
501
+ font-weight: 500;
502
+ padding: 0 var(--spacing-sm);
503
+ }
504
+
505
+ /* Checkbox & Radio */
506
+ input[type="checkbox"],
507
+ input[type="radio"] {
508
+ -webkit-appearance: none;
509
+ appearance: none;
510
+ width: 1.25rem;
511
+ height: 1.25rem;
512
+ border: 1px solid var(--color-border);
513
+ background-color: var(--color-surface);
514
+ cursor: pointer;
515
+ transition: border-color var(--transition-fast),
516
+ background-color var(--transition-fast),
517
+ box-shadow var(--transition-fast);
518
+ flex-shrink: 0;
519
+ }
520
+
521
+ input[type="checkbox"] {
522
+ border-radius: var(--radius-sm);
523
+ }
524
+
525
+ input[type="radio"] {
526
+ border-radius: 50%;
527
+ }
528
+
529
+ input[type="checkbox"]:hover,
530
+ input[type="radio"]:hover {
531
+ border-color: var(--color-text-muted);
532
+ }
533
+
534
+ input[type="checkbox"]:focus,
535
+ input[type="radio"]:focus {
536
+ outline: none;
537
+ border-color: var(--color-primary);
538
+ box-shadow: 0 0 0 3px var(--color-primary-focus);
539
+ }
540
+
541
+ input[type="checkbox"]:checked,
542
+ input[type="radio"]:checked {
543
+ background-color: var(--color-primary);
544
+ border-color: var(--color-primary);
545
+ }
546
+
547
+ input[type="checkbox"]:checked::after {
548
+ content: '';
549
+ display: block;
550
+ width: 0.35rem;
551
+ height: 0.65rem;
552
+ border: solid var(--color-primary-text);
553
+ border-width: 0 2px 2px 0;
554
+ margin: 0.15rem auto 0;
555
+ transform: rotate(45deg);
556
+ }
557
+
558
+ input[type="radio"]:checked::after {
559
+ content: '';
560
+ display: block;
561
+ width: 0.5rem;
562
+ height: 0.5rem;
563
+ background-color: var(--color-primary-text);
564
+ border-radius: 50%;
565
+ margin: 0.3rem auto 0;
566
+ }
567
+
568
+ input[type="checkbox"]:disabled,
569
+ input[type="radio"]:disabled {
570
+ background-color: var(--color-surface);
571
+ border-color: var(--color-border);
572
+ opacity: 0.7;
573
+ cursor: not-allowed;
574
+ }
575
+
576
+ /* Inline checkbox/radio labels */
577
+ label:has(input[type="checkbox"]),
578
+ label:has(input[type="radio"]) {
579
+ display: flex;
580
+ align-items: center;
581
+ gap: var(--spacing-sm);
582
+ font-weight: 400;
583
+ cursor: pointer;
584
+ }
585
+
586
+ /* Form button spacing */
587
+ form button[type="submit"],
588
+ form input[type="submit"],
589
+ form [role="button"] {
590
+ margin-top: var(--spacing-lg);
591
+ }
592
+
593
+ /* ========================================
594
+ Articles / Cards
595
+ ======================================== */
596
+
597
+ article {
598
+ margin-bottom: var(--spacing-lg);
599
+ }
600
+
601
+ article:last-child {
602
+ margin-bottom: 0;
603
+ }
604
+
605
+ article header {
606
+ padding: 0;
607
+ margin-bottom: var(--spacing-md);
608
+ }
609
+
610
+ article footer {
611
+ padding: 0;
612
+ margin-top: var(--spacing-lg);
613
+ }
614
+
615
+ /* ========================================
616
+ Alerts
617
+ ======================================== */
618
+
619
+ [role="alert"] {
620
+ background-color: var(--color-surface);
621
+ border-left: 4px solid var(--color-primary);
622
+ border-radius: var(--radius-md);
623
+ padding: var(--spacing-md) var(--spacing-lg);
624
+ margin-bottom: var(--spacing-md);
625
+ }
626
+
627
+ [role="alert"]:last-child {
628
+ margin-bottom: 0;
629
+ }
630
+
631
+ /* Flash messages */
632
+ .flash-messages {
633
+ margin-bottom: var(--spacing-lg);
634
+ }
635
+
636
+ .flash-messages [role="alert"] {
637
+ margin-bottom: var(--spacing-sm);
638
+ }
639
+
640
+ /* ========================================
641
+ Horizontal Rule
642
+ ======================================== */
643
+
644
+ hr {
645
+ border: none;
646
+ border-top: 1px solid var(--color-border);
647
+ margin: var(--spacing-lg) 0;
648
+ }
649
+
650
+ /* ========================================
651
+ Lists
652
+ ======================================== */
653
+
654
+ ul, ol {
655
+ padding-left: var(--spacing-lg);
656
+ margin-bottom: var(--spacing-md);
657
+ }
658
+
659
+ li {
660
+ margin-bottom: var(--spacing-xs);
661
+ }
662
+
663
+ /* ========================================
664
+ Tables
665
+ ======================================== */
666
+
667
+ table {
668
+ width: 100%;
669
+ border-collapse: collapse;
670
+ margin-bottom: var(--spacing-md);
671
+ }
672
+
673
+ th, td {
674
+ text-align: left;
675
+ padding: var(--spacing-sm) var(--spacing-md);
676
+ border-bottom: 1px solid var(--color-border);
677
+ }
678
+
679
+ th {
680
+ font-weight: 600;
681
+ background-color: var(--color-surface);
682
+ }
683
+
684
+ /* ========================================
685
+ Utilities
686
+ ======================================== */
687
+
688
+ .text-center { text-align: center; }
689
+ .text-muted { color: var(--color-text-muted); }
690
+
691
+ /* Error page vertical centering */
692
+ .error-page {
693
+ display: flex;
694
+ flex-direction: column;
695
+ justify-content: center;
696
+ min-height: calc(100vh - 160px);
697
+ padding-top: 0;
698
+ padding-bottom: 0;
699
+ }
700
+
701
+ .error-page article {
702
+ text-align: center;
703
+ background-color: transparent;
704
+ }
705
+
706
+ /* Focus visible for accessibility */
707
+ :focus-visible {
708
+ outline: 2px solid var(--color-primary);
709
+ outline-offset: 2px;
710
+ }
711
+
712
+ /* Remove default focus for mouse users */
713
+ :focus:not(:focus-visible) {
714
+ outline: none;
715
+ }
716
+
717
+ /* ========================================
718
+ Admin Layout
719
+ ======================================== */
720
+
721
+ main.container.admin-layout {
722
+ max-width: none;
723
+ margin-left: var(--spacing-lg);
724
+ margin-right: var(--spacing-lg);
725
+ padding-top: var(--spacing-lg);
726
+ }
727
+
728
+ .admin-container {
729
+ display: grid;
730
+ grid-template-columns: 200px 1fr;
731
+ gap: var(--spacing-xl);
732
+ min-height: calc(100vh - 200px);
733
+ }
734
+
735
+ @media (max-width: 768px) {
736
+ .admin-container {
737
+ grid-template-columns: 1fr;
738
+ }
739
+ }
740
+
741
+ .admin-sidebar {
742
+ border-right: 1px solid var(--color-border);
743
+ padding-right: var(--spacing-lg);
744
+ }
745
+
746
+ @media (max-width: 768px) {
747
+ .admin-sidebar {
748
+ border-right: none;
749
+ border-bottom: 1px solid var(--color-border);
750
+ padding-right: 0;
751
+ padding-bottom: var(--spacing-lg);
752
+ }
753
+ }
754
+
755
+ .admin-nav ul {
756
+ list-style: none;
757
+ padding: 0;
758
+ margin: 0;
759
+ display: flex;
760
+ flex-direction: column;
761
+ gap: var(--spacing-xs);
762
+ width: 100%;
763
+ }
764
+
765
+ .admin-nav li {
766
+ width: 100%;
767
+ }
768
+
769
+ @media (max-width: 768px) {
770
+ .admin-nav ul {
771
+ flex-direction: row;
772
+ flex-wrap: wrap;
773
+ }
774
+ }
775
+
776
+ .admin-nav a {
777
+ display: block;
778
+ width: 100%;
779
+ padding: var(--spacing-sm) var(--spacing-md);
780
+ border-radius: var(--radius-md);
781
+ color: var(--color-text);
782
+ transition: background-color var(--transition-fast);
783
+ }
784
+
785
+ .admin-nav a:hover {
786
+ background-color: var(--color-surface);
787
+ }
788
+
789
+ .admin-nav a.active {
790
+ background-color: var(--color-primary);
791
+ color: var(--color-primary-text);
792
+ }
793
+
794
+ .admin-nav a.active:hover {
795
+ background-color: var(--color-primary-hover);
796
+ color: var(--color-primary-text);
797
+ }
798
+
799
+ .admin-nav a::after {
800
+ display: none;
801
+ }
802
+
803
+ .admin-content {
804
+ min-width: 0;
805
+ }
806
+
807
+ /* Admin header with title and actions */
808
+ .admin-header {
809
+ display: flex;
810
+ justify-content: space-between;
811
+ align-items: flex-start;
812
+ gap: var(--spacing-lg);
813
+ }
814
+
815
+ .admin-header [role="button"] {
816
+ margin-top: 0;
817
+ flex-shrink: 0;
818
+ }
819
+
820
+ /* Admin Quick Links */
821
+ .admin-quick-links {
822
+ display: grid;
823
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
824
+ gap: var(--spacing-lg);
825
+ margin-top: var(--spacing-lg);
826
+ }
827
+
828
+ .admin-card {
829
+ display: block;
830
+ padding: var(--spacing-lg);
831
+ background-color: var(--color-surface);
832
+ border-radius: var(--radius-md);
833
+ transition: box-shadow var(--transition-fast), transform var(--transition-fast);
834
+ }
835
+
836
+ .admin-card:hover {
837
+ box-shadow: var(--shadow-md);
838
+ transform: translateY(-2px);
839
+ }
840
+
841
+ .admin-card::after {
842
+ display: none;
843
+ }
844
+
845
+ .admin-card h3 {
846
+ margin-bottom: var(--spacing-xs);
847
+ font-size: 1.125rem;
848
+ }
849
+
850
+ .admin-card p {
851
+ color: var(--color-text-muted);
852
+ font-size: 0.875rem;
853
+ margin: 0;
854
+ }
855
+
856
+ /* Admin Tables */
857
+ .admin-table {
858
+ width: 100%;
859
+ margin-top: var(--spacing-lg);
860
+ }
861
+
862
+ .admin-table th,
863
+ .admin-table td {
864
+ padding: var(--spacing-sm) var(--spacing-md);
865
+ text-align: left;
866
+ vertical-align: middle;
867
+ }
868
+
869
+ .admin-table th {
870
+ font-size: 0.75rem;
871
+ text-transform: uppercase;
872
+ letter-spacing: 0.05em;
873
+ color: var(--color-text-muted);
874
+ }
875
+
876
+ .user-cell {
877
+ display: flex;
878
+ align-items: center;
879
+ gap: var(--spacing-sm);
880
+ }
881
+
882
+ .user-avatar {
883
+ width: 32px;
884
+ height: 32px;
885
+ border-radius: 50%;
886
+ object-fit: cover;
887
+ }
888
+
889
+ .user-avatar-large {
890
+ width: 64px;
891
+ height: 64px;
892
+ border-radius: 50%;
893
+ object-fit: cover;
894
+ }
895
+
896
+ .user-info-card {
897
+ display: flex;
898
+ align-items: center;
899
+ gap: var(--spacing-md);
900
+ padding: var(--spacing-lg);
901
+ background-color: var(--color-surface);
902
+ border-radius: var(--radius-md);
903
+ margin-bottom: var(--spacing-lg);
904
+ }
905
+
906
+ .user-info-card p {
907
+ margin: 0;
908
+ }
909
+
910
+ /* Badges */
911
+ .role-badge {
912
+ display: inline-block;
913
+ padding: 0.125rem 0.5rem;
914
+ background-color: var(--color-surface);
915
+ border-radius: var(--radius-pill);
916
+ font-size: 0.75rem;
917
+ font-weight: 500;
918
+ margin-right: var(--spacing-xs);
919
+ }
920
+
921
+ .status-badge {
922
+ display: inline-block;
923
+ padding: 0.125rem 0.5rem;
924
+ border-radius: var(--radius-pill);
925
+ font-size: 0.75rem;
926
+ font-weight: 500;
927
+ }
928
+
929
+ .status-published {
930
+ background-color: var(--color-success);
931
+ color: white;
932
+ }
933
+
934
+ .status-draft {
935
+ background-color: var(--color-surface);
936
+ color: var(--color-text-muted);
937
+ }
938
+
939
+ .type-badge {
940
+ display: inline-block;
941
+ padding: 0.125rem 0.5rem;
942
+ background-color: var(--color-surface);
943
+ border-radius: var(--radius-pill);
944
+ font-size: 0.75rem;
945
+ font-weight: 500;
946
+ }
947
+
948
+ /* Small buttons */
949
+ button.small,
950
+ [role="button"].small {
951
+ padding: 0.375rem 0.75rem;
952
+ font-size: 0.75rem;
953
+ }
954
+
955
+ /* Danger button variant */
956
+ button.danger,
957
+ [role="button"].danger {
958
+ color: var(--color-error);
959
+ border-color: var(--color-error);
960
+ }
961
+
962
+ button.danger:hover,
963
+ [role="button"].danger:hover {
964
+ background-color: var(--color-error);
965
+ color: white;
966
+ border-color: var(--color-error);
967
+ }
968
+
969
+ /* Inline forms */
970
+ .inline-form {
971
+ display: inline-block;
972
+ margin: 0;
973
+ }
974
+
975
+ .inline-form button {
976
+ margin-top: 0;
977
+ }
978
+
979
+ /* Actions cell */
980
+ .actions-cell {
981
+ display: flex;
982
+ gap: var(--spacing-xs);
983
+ flex-wrap: wrap;
984
+ }
985
+
986
+ /* Form actions */
987
+ .form-actions {
988
+ display: flex;
989
+ gap: var(--spacing-md);
990
+ margin-top: var(--spacing-lg);
991
+ }
992
+
993
+ .form-actions button,
994
+ .form-actions [role="button"] {
995
+ margin-top: 0;
996
+ }
997
+
998
+ /* No items message */
999
+ .no-items {
1000
+ text-align: center;
1001
+ color: var(--color-text-muted);
1002
+ padding: var(--spacing-xl) 0;
1003
+ }