skrift 0.1.0a1__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 (68) hide show
  1. skrift/__init__.py +1 -0
  2. skrift/__main__.py +17 -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 +91 -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.ini +77 -0
  14. skrift/asgi.py +545 -0
  15. skrift/auth/__init__.py +58 -0
  16. skrift/auth/guards.py +130 -0
  17. skrift/auth/roles.py +94 -0
  18. skrift/auth/services.py +184 -0
  19. skrift/cli.py +45 -0
  20. skrift/config.py +192 -0
  21. skrift/controllers/__init__.py +4 -0
  22. skrift/controllers/auth.py +371 -0
  23. skrift/controllers/web.py +67 -0
  24. skrift/db/__init__.py +3 -0
  25. skrift/db/base.py +7 -0
  26. skrift/db/models/__init__.py +6 -0
  27. skrift/db/models/page.py +26 -0
  28. skrift/db/models/role.py +56 -0
  29. skrift/db/models/setting.py +13 -0
  30. skrift/db/models/user.py +36 -0
  31. skrift/db/services/__init__.py +1 -0
  32. skrift/db/services/page_service.py +217 -0
  33. skrift/db/services/setting_service.py +206 -0
  34. skrift/lib/__init__.py +3 -0
  35. skrift/lib/exceptions.py +168 -0
  36. skrift/lib/template.py +108 -0
  37. skrift/setup/__init__.py +14 -0
  38. skrift/setup/config_writer.py +211 -0
  39. skrift/setup/controller.py +751 -0
  40. skrift/setup/middleware.py +89 -0
  41. skrift/setup/providers.py +163 -0
  42. skrift/setup/state.py +134 -0
  43. skrift/static/css/style.css +998 -0
  44. skrift/templates/admin/admin.html +19 -0
  45. skrift/templates/admin/base.html +24 -0
  46. skrift/templates/admin/pages/edit.html +32 -0
  47. skrift/templates/admin/pages/list.html +62 -0
  48. skrift/templates/admin/settings/site.html +32 -0
  49. skrift/templates/admin/users/list.html +58 -0
  50. skrift/templates/admin/users/roles.html +42 -0
  51. skrift/templates/auth/login.html +125 -0
  52. skrift/templates/base.html +52 -0
  53. skrift/templates/error-404.html +19 -0
  54. skrift/templates/error-500.html +19 -0
  55. skrift/templates/error.html +19 -0
  56. skrift/templates/index.html +9 -0
  57. skrift/templates/page.html +26 -0
  58. skrift/templates/setup/admin.html +24 -0
  59. skrift/templates/setup/auth.html +110 -0
  60. skrift/templates/setup/base.html +407 -0
  61. skrift/templates/setup/complete.html +17 -0
  62. skrift/templates/setup/database.html +125 -0
  63. skrift/templates/setup/restart.html +28 -0
  64. skrift/templates/setup/site.html +39 -0
  65. skrift-0.1.0a1.dist-info/METADATA +233 -0
  66. skrift-0.1.0a1.dist-info/RECORD +68 -0
  67. skrift-0.1.0a1.dist-info/WHEEL +4 -0
  68. skrift-0.1.0a1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,998 @@
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"])::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:hover {
192
+ color: var(--color-primary-hover);
193
+ }
194
+
195
+ a:not([role="button"]):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::after {
795
+ display: none;
796
+ }
797
+
798
+ .admin-content {
799
+ min-width: 0;
800
+ }
801
+
802
+ /* Admin header with title and actions */
803
+ .admin-header {
804
+ display: flex;
805
+ justify-content: space-between;
806
+ align-items: flex-start;
807
+ gap: var(--spacing-lg);
808
+ }
809
+
810
+ .admin-header [role="button"] {
811
+ margin-top: 0;
812
+ flex-shrink: 0;
813
+ }
814
+
815
+ /* Admin Quick Links */
816
+ .admin-quick-links {
817
+ display: grid;
818
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
819
+ gap: var(--spacing-lg);
820
+ margin-top: var(--spacing-lg);
821
+ }
822
+
823
+ .admin-card {
824
+ display: block;
825
+ padding: var(--spacing-lg);
826
+ background-color: var(--color-surface);
827
+ border-radius: var(--radius-md);
828
+ transition: box-shadow var(--transition-fast), transform var(--transition-fast);
829
+ }
830
+
831
+ .admin-card:hover {
832
+ box-shadow: var(--shadow-md);
833
+ transform: translateY(-2px);
834
+ }
835
+
836
+ .admin-card::after {
837
+ display: none;
838
+ }
839
+
840
+ .admin-card h3 {
841
+ margin-bottom: var(--spacing-xs);
842
+ font-size: 1.125rem;
843
+ }
844
+
845
+ .admin-card p {
846
+ color: var(--color-text-muted);
847
+ font-size: 0.875rem;
848
+ margin: 0;
849
+ }
850
+
851
+ /* Admin Tables */
852
+ .admin-table {
853
+ width: 100%;
854
+ margin-top: var(--spacing-lg);
855
+ }
856
+
857
+ .admin-table th,
858
+ .admin-table td {
859
+ padding: var(--spacing-sm) var(--spacing-md);
860
+ text-align: left;
861
+ vertical-align: middle;
862
+ }
863
+
864
+ .admin-table th {
865
+ font-size: 0.75rem;
866
+ text-transform: uppercase;
867
+ letter-spacing: 0.05em;
868
+ color: var(--color-text-muted);
869
+ }
870
+
871
+ .user-cell {
872
+ display: flex;
873
+ align-items: center;
874
+ gap: var(--spacing-sm);
875
+ }
876
+
877
+ .user-avatar {
878
+ width: 32px;
879
+ height: 32px;
880
+ border-radius: 50%;
881
+ object-fit: cover;
882
+ }
883
+
884
+ .user-avatar-large {
885
+ width: 64px;
886
+ height: 64px;
887
+ border-radius: 50%;
888
+ object-fit: cover;
889
+ }
890
+
891
+ .user-info-card {
892
+ display: flex;
893
+ align-items: center;
894
+ gap: var(--spacing-md);
895
+ padding: var(--spacing-lg);
896
+ background-color: var(--color-surface);
897
+ border-radius: var(--radius-md);
898
+ margin-bottom: var(--spacing-lg);
899
+ }
900
+
901
+ .user-info-card p {
902
+ margin: 0;
903
+ }
904
+
905
+ /* Badges */
906
+ .role-badge {
907
+ display: inline-block;
908
+ padding: 0.125rem 0.5rem;
909
+ background-color: var(--color-surface);
910
+ border-radius: var(--radius-pill);
911
+ font-size: 0.75rem;
912
+ font-weight: 500;
913
+ margin-right: var(--spacing-xs);
914
+ }
915
+
916
+ .status-badge {
917
+ display: inline-block;
918
+ padding: 0.125rem 0.5rem;
919
+ border-radius: var(--radius-pill);
920
+ font-size: 0.75rem;
921
+ font-weight: 500;
922
+ }
923
+
924
+ .status-published {
925
+ background-color: var(--color-success);
926
+ color: white;
927
+ }
928
+
929
+ .status-draft {
930
+ background-color: var(--color-surface);
931
+ color: var(--color-text-muted);
932
+ }
933
+
934
+ .type-badge {
935
+ display: inline-block;
936
+ padding: 0.125rem 0.5rem;
937
+ background-color: var(--color-surface);
938
+ border-radius: var(--radius-pill);
939
+ font-size: 0.75rem;
940
+ font-weight: 500;
941
+ }
942
+
943
+ /* Small buttons */
944
+ button.small,
945
+ [role="button"].small {
946
+ padding: 0.375rem 0.75rem;
947
+ font-size: 0.75rem;
948
+ }
949
+
950
+ /* Danger button variant */
951
+ button.danger,
952
+ [role="button"].danger {
953
+ color: var(--color-error);
954
+ border-color: var(--color-error);
955
+ }
956
+
957
+ button.danger:hover,
958
+ [role="button"].danger:hover {
959
+ background-color: var(--color-error);
960
+ color: white;
961
+ border-color: var(--color-error);
962
+ }
963
+
964
+ /* Inline forms */
965
+ .inline-form {
966
+ display: inline-block;
967
+ margin: 0;
968
+ }
969
+
970
+ .inline-form button {
971
+ margin-top: 0;
972
+ }
973
+
974
+ /* Actions cell */
975
+ .actions-cell {
976
+ display: flex;
977
+ gap: var(--spacing-xs);
978
+ flex-wrap: wrap;
979
+ }
980
+
981
+ /* Form actions */
982
+ .form-actions {
983
+ display: flex;
984
+ gap: var(--spacing-md);
985
+ margin-top: var(--spacing-lg);
986
+ }
987
+
988
+ .form-actions button,
989
+ .form-actions [role="button"] {
990
+ margin-top: 0;
991
+ }
992
+
993
+ /* No items message */
994
+ .no-items {
995
+ text-align: center;
996
+ color: var(--color-text-muted);
997
+ padding: var(--spacing-xl) 0;
998
+ }