create-fff-app 0.1.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 (30) hide show
  1. package/package.json +21 -0
  2. package/src/index.ts +119 -0
  3. package/template/.config/dotnet-tools.json +5 -0
  4. package/template/dev-server.ts +415 -0
  5. package/template/index.html +23 -0
  6. package/template/migrate.ts +166 -0
  7. package/template/migrations/0001_init.sql +25 -0
  8. package/template/migrations/0002_employees.sql +16 -0
  9. package/template/package.json +29 -0
  10. package/template/public/app.js +156 -0
  11. package/template/public/runtime.js +149 -0
  12. package/template/public/style.css +769 -0
  13. package/template/schema.sql +25 -0
  14. package/template/src/client/App.fs +55 -0
  15. package/template/src/client/Client.fsproj +24 -0
  16. package/template/src/client/Nav.fs +75 -0
  17. package/template/src/server/Domain/Employees/Employee.fs +34 -0
  18. package/template/src/server/Endpoints/Employees/Routes.fs +35 -0
  19. package/template/src/server/Endpoints/Pages/About.fjsx +66 -0
  20. package/template/src/server/Endpoints/Pages/EmployeeEdit.fjsx +66 -0
  21. package/template/src/server/Endpoints/Pages/EmployeeList.fjsx +54 -0
  22. package/template/src/server/Endpoints/Pages/EmployeeNew.fjsx +55 -0
  23. package/template/src/server/Endpoints/Pages/Layout.fjsx +40 -0
  24. package/template/src/server/Endpoints/Pages/Routes.fs +117 -0
  25. package/template/src/server/Endpoints/Pages/Templates.fs +45 -0
  26. package/template/src/server/Server.fsproj +28 -0
  27. package/template/src/server/Worker.fs +45 -0
  28. package/template/src/server/Workflows/Employees/EmployeeWorkflows.fs +86 -0
  29. package/template/webpack.client.js +18 -0
  30. package/template/wrangler.toml +23 -0
@@ -0,0 +1,769 @@
1
+ /* ═══════════════════════════════════════════════════════════
2
+ fff-stack UI — Cloudflare-inspired business dashboard kit
3
+ CSS custom-property based, zero build step required.
4
+ ═══════════════════════════════════════════════════════════ */
5
+
6
+ /* ── Reset ─────────────────────────────────────────────────────────────── */
7
+
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+ img, video { display: block; max-width: 100%; }
10
+ button, input, textarea, select { font: inherit; }
11
+
12
+ /* ── Design tokens ─────────────────────────────────────────────────────── */
13
+
14
+ :root {
15
+ /* Brand */
16
+ --fff-accent: #F6821F;
17
+ --fff-accent-dark: #D96D0C;
18
+ --fff-accent-dim: rgba(246, 130, 31, 0.10);
19
+
20
+ /* Surfaces */
21
+ --fff-bg: #F3F4F6;
22
+ --fff-surface: #FFFFFF;
23
+ --fff-sidebar: #1C2333;
24
+
25
+ /* Borders */
26
+ --fff-border: #E5E7EB;
27
+ --fff-border-dark: #D1D5DB;
28
+
29
+ /* Text */
30
+ --fff-text: #111827;
31
+ --fff-muted: #6B7280;
32
+ --fff-subtle: #9CA3AF;
33
+
34
+ /* Sidebar text */
35
+ --fff-sidebar-text: #C9D1DE;
36
+ --fff-sidebar-active: #FFFFFF;
37
+ --fff-sidebar-hover: rgba(255,255,255,0.06);
38
+
39
+ /* Semantic colors */
40
+ --fff-success: #16A34A;
41
+ --fff-success-dim: rgba(22, 163, 74, 0.10);
42
+ --fff-warning: #D97706;
43
+ --fff-warning-dim: rgba(217, 119, 6, 0.10);
44
+ --fff-danger: #DC2626;
45
+ --fff-danger-dim: rgba(220, 38, 38, 0.10);
46
+ --fff-info: #2563EB;
47
+ --fff-info-dim: rgba(37, 99, 235, 0.10);
48
+
49
+ /* Typography */
50
+ --fff-font: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
51
+ --fff-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, 'Cascadia Code', monospace;
52
+
53
+ /* Shape & shadow */
54
+ --fff-radius: 6px;
55
+ --fff-radius-lg: 10px;
56
+ --fff-shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
57
+ --fff-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
58
+ --fff-shadow-md: 0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.04);
59
+
60
+ /* Legacy aliases — keeps existing demo components working */
61
+ --bg: var(--fff-bg);
62
+ --surface: var(--fff-surface);
63
+ --border: var(--fff-border);
64
+ --accent: var(--fff-accent);
65
+ --accent2: var(--fff-accent);
66
+ --text: var(--fff-text);
67
+ --muted: var(--fff-muted);
68
+ --danger: var(--fff-danger);
69
+ --success: var(--fff-success);
70
+ --radius: var(--fff-radius);
71
+ --font: var(--fff-font);
72
+ --mono: var(--fff-mono);
73
+ }
74
+
75
+ /* ── Base ───────────────────────────────────────────────────────────────── */
76
+
77
+ html { font-size: 15px; }
78
+
79
+ body {
80
+ background: var(--fff-bg);
81
+ color: var(--fff-text);
82
+ font-family: var(--fff-font);
83
+ line-height: 1.6;
84
+ min-height: 100vh;
85
+ -webkit-font-smoothing: antialiased;
86
+ }
87
+
88
+ a { color: var(--fff-accent); text-decoration: none; }
89
+ a:hover { text-decoration: underline; }
90
+
91
+ code, kbd, pre {
92
+ font-family: var(--fff-mono);
93
+ font-size: 0.875em;
94
+ }
95
+
96
+ /* ── Layout ─────────────────────────────────────────────────────────────── */
97
+
98
+ .container {
99
+ max-width: 960px;
100
+ margin: 0 auto;
101
+ padding: 0 1.5rem;
102
+ }
103
+
104
+ /* Two-column shell: sidebar + content */
105
+ .app-shell {
106
+ display: flex;
107
+ min-height: 100vh;
108
+ }
109
+
110
+ .app-sidebar {
111
+ width: 220px;
112
+ flex-shrink: 0;
113
+ background: var(--fff-sidebar);
114
+ display: flex;
115
+ flex-direction: column;
116
+ padding: 0;
117
+ }
118
+
119
+ .app-content {
120
+ flex: 1;
121
+ min-width: 0;
122
+ padding: 2rem 2.5rem;
123
+ }
124
+
125
+ /* ── Sidebar nav ─────────────────────────────────────────────────────────── */
126
+
127
+ .sidebar-logo {
128
+ padding: 1.25rem 1.25rem 0.75rem;
129
+ font-size: 1.1rem;
130
+ font-weight: 800;
131
+ color: var(--fff-sidebar-active);
132
+ letter-spacing: -0.02em;
133
+ text-decoration: none;
134
+ }
135
+ .sidebar-logo span { color: var(--fff-accent); }
136
+
137
+ .sidebar-nav {
138
+ flex: 1;
139
+ padding: 0.5rem 0.75rem;
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 2px;
143
+ }
144
+
145
+ .sidebar-nav a {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 0.5rem;
149
+ padding: 0.45rem 0.75rem;
150
+ border-radius: var(--fff-radius);
151
+ color: var(--fff-sidebar-text);
152
+ font-size: var(--fff-font-sm, 0.875rem);
153
+ font-weight: 500;
154
+ text-decoration: none;
155
+ transition: background 0.12s, color 0.12s;
156
+ }
157
+ .sidebar-nav a:hover { background: var(--fff-sidebar-hover); color: var(--fff-sidebar-active); }
158
+ .sidebar-nav a.active { background: rgba(246,130,31,0.15); color: var(--fff-accent); }
159
+
160
+ .sidebar-section {
161
+ padding: 0.75rem 1rem 0.25rem;
162
+ font-size: 0.7rem;
163
+ font-weight: 600;
164
+ letter-spacing: 0.08em;
165
+ text-transform: uppercase;
166
+ color: var(--fff-sidebar-text);
167
+ opacity: 0.5;
168
+ }
169
+
170
+ /* ── Top header (single-column layout) ──────────────────────────────────── */
171
+
172
+ .site-header {
173
+ background: var(--fff-surface);
174
+ border-bottom: 1px solid var(--fff-border);
175
+ padding: 0.875rem 0;
176
+ position: sticky;
177
+ top: 0;
178
+ z-index: 100;
179
+ box-shadow: var(--fff-shadow-sm);
180
+ }
181
+
182
+ .site-header .container {
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: space-between;
186
+ gap: 1rem;
187
+ }
188
+
189
+ .logo {
190
+ font-size: 1.25rem;
191
+ font-weight: 800;
192
+ letter-spacing: -0.03em;
193
+ color: var(--fff-text);
194
+ text-decoration: none;
195
+ }
196
+ .logo span { color: var(--fff-accent); }
197
+
198
+ .tagline {
199
+ color: var(--fff-muted);
200
+ font-size: 0.8rem;
201
+ margin-top: 0.1rem;
202
+ font-family: var(--fff-mono);
203
+ }
204
+
205
+ /* ── Top navigation ──────────────────────────────────────────────────────── */
206
+
207
+ .site-nav { display: flex; gap: 0.15rem; }
208
+
209
+ .site-nav a {
210
+ color: var(--fff-muted);
211
+ text-decoration: none;
212
+ font-size: 0.875rem;
213
+ font-weight: 500;
214
+ padding: 0.35rem 0.75rem;
215
+ border-radius: var(--fff-radius);
216
+ transition: color 0.12s, background 0.12s;
217
+ }
218
+ .site-nav a:hover { color: var(--fff-text); background: var(--fff-bg); text-decoration: none; }
219
+ .site-nav a.active { color: var(--fff-accent); background: var(--fff-accent-dim); }
220
+
221
+ /* ── Page content ────────────────────────────────────────────────────────── */
222
+
223
+ main {
224
+ padding: 2rem 0 3rem;
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: 1.25rem;
228
+ max-width: 960px;
229
+ margin: 0 auto;
230
+ padding: 24px 1.5rem;
231
+ }
232
+
233
+ /* ── Footer ──────────────────────────────────────────────────────────────── */
234
+
235
+ .site-footer {
236
+ border-top: 1px solid var(--fff-border);
237
+ padding: 1rem 0;
238
+ color: var(--fff-muted);
239
+ font-size: 0.8rem;
240
+ }
241
+
242
+ /* ── Cards ───────────────────────────────────────────────────────────────── */
243
+
244
+ .card {
245
+ background: var(--fff-surface);
246
+ border: 1px solid var(--fff-border);
247
+ border-radius: var(--fff-radius-lg);
248
+ padding: 1.5rem 1.75rem;
249
+ box-shadow: var(--fff-shadow-sm);
250
+ position: relative;
251
+ }
252
+
253
+ .card-label {
254
+ position: absolute;
255
+ top: -0.6rem;
256
+ left: 1rem;
257
+ background: var(--fff-surface);
258
+ border: 1px solid var(--fff-border);
259
+ border-radius: var(--fff-radius);
260
+ padding: 0 0.6rem;
261
+ font-size: 0.7rem;
262
+ font-family: var(--fff-mono);
263
+ color: var(--fff-muted);
264
+ letter-spacing: 0.05em;
265
+ text-transform: uppercase;
266
+ }
267
+
268
+ .card h1 { font-size: 1.25rem; font-weight: 700; color: var(--fff-text); margin-bottom: 1.25rem; }
269
+ .card h2 { font-size: 1rem; font-weight: 600; color: var(--fff-text); margin-bottom: 0.75rem; }
270
+ .card h3 {
271
+ font-size: 0.8rem;
272
+ font-weight: 600;
273
+ color: var(--fff-muted);
274
+ text-transform: uppercase;
275
+ letter-spacing: 0.07em;
276
+ margin: 1.25rem 0 0.6rem;
277
+ }
278
+
279
+ /* ── Buttons ─────────────────────────────────────────────────────────────── */
280
+
281
+ .btn {
282
+ display: inline-flex;
283
+ align-items: center;
284
+ gap: 0.35rem;
285
+ border: 1px solid transparent;
286
+ border-radius: var(--fff-radius);
287
+ cursor: pointer;
288
+ font-size: 0.875rem;
289
+ font-weight: 500;
290
+ padding: 0.45rem 1rem;
291
+ line-height: 1.4;
292
+ text-decoration: none;
293
+ transition: background 0.12s, border-color 0.12s, opacity 0.12s, transform 0.1s;
294
+ white-space: nowrap;
295
+ }
296
+ .btn:active { transform: scale(0.98); }
297
+
298
+ .btn-primary {
299
+ background: var(--fff-accent);
300
+ border-color: var(--fff-accent-dark);
301
+ color: #fff;
302
+ }
303
+ .btn-primary:hover { background: var(--fff-accent-dark); text-decoration: none; }
304
+
305
+ .btn-secondary {
306
+ background: var(--fff-surface);
307
+ border-color: var(--fff-border-dark);
308
+ color: var(--fff-text);
309
+ }
310
+ .btn-secondary:hover { background: var(--fff-bg); text-decoration: none; }
311
+
312
+ .btn-danger {
313
+ background: var(--fff-danger-dim);
314
+ border-color: var(--fff-danger);
315
+ color: var(--fff-danger);
316
+ }
317
+ .btn-danger:hover { background: var(--fff-danger); color: #fff; text-decoration: none; }
318
+
319
+ .btn-ghost {
320
+ background: transparent;
321
+ border-color: var(--fff-border);
322
+ color: var(--fff-muted);
323
+ }
324
+ .btn-ghost:hover { background: var(--fff-bg); color: var(--fff-text); text-decoration: none; }
325
+
326
+ .btn-sm { font-size: 0.8rem; padding: 0.3rem 0.7rem; }
327
+ .btn-lg { font-size: 1rem; padding: 0.6rem 1.4rem; }
328
+
329
+ /* ── Forms ───────────────────────────────────────────────────────────────── */
330
+
331
+ .form-group {
332
+ display: flex;
333
+ flex-direction: column;
334
+ gap: 0.35rem;
335
+ margin-bottom: 1rem;
336
+ }
337
+
338
+ .form-group label {
339
+ font-size: 0.875rem;
340
+ font-weight: 500;
341
+ color: var(--fff-text);
342
+ }
343
+
344
+ .form-group input[type="text"],
345
+ .form-group input[type="email"],
346
+ .form-group input[type="password"],
347
+ .form-group input[type="number"],
348
+ .form-group input[type="date"],
349
+ .form-group input[type="search"],
350
+ .form-group input[type="url"],
351
+ .form-group input[type="tel"],
352
+ .form-group textarea,
353
+ .form-group select {
354
+ background: var(--fff-surface);
355
+ border: 1px solid var(--fff-border-dark);
356
+ border-radius: var(--fff-radius);
357
+ color: var(--fff-text);
358
+ font-size: 0.9rem;
359
+ padding: 0.5rem 0.8rem;
360
+ outline: none;
361
+ transition: border-color 0.12s, box-shadow 0.12s;
362
+ width: 100%;
363
+ }
364
+ .form-group input:focus,
365
+ .form-group textarea:focus,
366
+ .form-group select:focus {
367
+ border-color: var(--fff-accent);
368
+ box-shadow: 0 0 0 3px var(--fff-accent-dim);
369
+ }
370
+ .form-group textarea { resize: vertical; min-height: 7rem; }
371
+
372
+ .form-group select {
373
+ appearance: none;
374
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%236B7280' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
375
+ background-repeat: no-repeat;
376
+ background-position: right 0.75rem center;
377
+ padding-right: 2.25rem;
378
+ }
379
+
380
+ .form-group .hint {
381
+ font-size: 0.8rem;
382
+ color: var(--fff-muted);
383
+ }
384
+ .form-group .field-error {
385
+ font-size: 0.8rem;
386
+ color: var(--fff-danger);
387
+ }
388
+
389
+ .form-checkbox {
390
+ display: flex;
391
+ align-items: flex-start;
392
+ gap: 0.5rem;
393
+ margin-bottom: 1rem;
394
+ cursor: pointer;
395
+ }
396
+ .form-checkbox input[type="checkbox"],
397
+ .form-checkbox input[type="radio"] {
398
+ margin-top: 0.2rem;
399
+ width: 1rem;
400
+ height: 1rem;
401
+ flex-shrink: 0;
402
+ accent-color: var(--fff-accent);
403
+ }
404
+ .form-checkbox label {
405
+ font-size: 0.9rem;
406
+ color: var(--fff-text);
407
+ cursor: pointer;
408
+ line-height: 1.5;
409
+ }
410
+
411
+ .form-actions {
412
+ display: flex;
413
+ align-items: center;
414
+ gap: 0.75rem;
415
+ margin-top: 1.5rem;
416
+ padding-top: 1.25rem;
417
+ border-top: 1px solid var(--fff-border);
418
+ }
419
+
420
+ .form-row {
421
+ display: grid;
422
+ grid-template-columns: 1fr 1fr;
423
+ gap: 1rem;
424
+ }
425
+
426
+ /* ── Page header (list-header) ───────────────────────────────────────────── */
427
+
428
+ .list-header {
429
+ display: flex;
430
+ align-items: center;
431
+ justify-content: space-between;
432
+ margin-bottom: 1.25rem;
433
+ }
434
+
435
+ .list-header h1 {
436
+ font-size: 1.15rem;
437
+ font-weight: 700;
438
+ color: var(--fff-text);
439
+ margin: 0;
440
+ }
441
+
442
+ /* ── Data table ──────────────────────────────────────────────────────────── */
443
+
444
+ .data-table {
445
+ width: 100%;
446
+ border-collapse: collapse;
447
+ font-size: 0.875rem;
448
+ }
449
+
450
+ .data-table thead tr {
451
+ border-bottom: 2px solid var(--fff-border);
452
+ }
453
+
454
+ .data-table th {
455
+ padding: 0.6rem 0.875rem;
456
+ text-align: left;
457
+ font-size: 0.75rem;
458
+ font-weight: 600;
459
+ color: var(--fff-muted);
460
+ text-transform: uppercase;
461
+ letter-spacing: 0.05em;
462
+ white-space: nowrap;
463
+ }
464
+
465
+ .data-table td {
466
+ padding: 0.7rem 0.875rem;
467
+ color: var(--fff-text);
468
+ border-bottom: 1px solid var(--fff-border);
469
+ vertical-align: middle;
470
+ }
471
+
472
+ .data-table tbody tr:last-child td { border-bottom: none; }
473
+
474
+ .data-table tbody tr:hover td { background: var(--fff-bg); }
475
+
476
+ .data-table td.actions {
477
+ text-align: right;
478
+ white-space: nowrap;
479
+ display: flex;
480
+ gap: 0.35rem;
481
+ }
482
+
483
+ .data-table td code {
484
+ font-family: var(--fff-mono);
485
+ font-size: 0.8em;
486
+ color: var(--fff-muted);
487
+ }
488
+
489
+ /* ── Badges ──────────────────────────────────────────────────────────────── */
490
+
491
+ .badge {
492
+ display: inline-flex;
493
+ align-items: center;
494
+ gap: 0.3rem;
495
+ padding: 0.2rem 0.55rem;
496
+ border-radius: 100px;
497
+ font-size: 0.75rem;
498
+ font-weight: 500;
499
+ line-height: 1.4;
500
+ border: 1px solid currentColor;
501
+ }
502
+
503
+ .badge-success { color: var(--fff-success); background: var(--fff-success-dim); }
504
+ .badge-warning { color: var(--fff-warning); background: var(--fff-warning-dim); }
505
+ .badge-danger { color: var(--fff-danger); background: var(--fff-danger-dim); }
506
+ .badge-info { color: var(--fff-info); background: var(--fff-info-dim); }
507
+ .badge-neutral { color: var(--fff-muted); background: var(--fff-bg); border-color: var(--fff-border); }
508
+
509
+ .badge::before { content: '●'; font-size: 0.6em; }
510
+
511
+ /* ── Flash / alert messages ──────────────────────────────────────────────── */
512
+
513
+ .flash {
514
+ display: flex;
515
+ align-items: flex-start;
516
+ gap: 0.6rem;
517
+ padding: 0.75rem 1rem;
518
+ border-radius: var(--fff-radius);
519
+ font-size: 0.875rem;
520
+ line-height: 1.5;
521
+ border: 1px solid var(--fff-border);
522
+ background: var(--fff-surface);
523
+ margin-bottom: 1.25rem;
524
+ }
525
+
526
+ .flash-success {
527
+ background: var(--fff-success-dim);
528
+ border-color: var(--fff-success);
529
+ color: var(--fff-success);
530
+ }
531
+ .flash-error {
532
+ background: var(--fff-danger-dim);
533
+ border-color: var(--fff-danger);
534
+ color: var(--fff-danger);
535
+ }
536
+ .flash-warning {
537
+ background: var(--fff-warning-dim);
538
+ border-color: var(--fff-warning);
539
+ color: var(--fff-warning);
540
+ }
541
+ .flash-info {
542
+ background: var(--fff-info-dim);
543
+ border-color: var(--fff-info);
544
+ color: var(--fff-info);
545
+ }
546
+
547
+ /* ── Empty state ─────────────────────────────────────────────────────────── */
548
+
549
+ .empty-state {
550
+ text-align: center;
551
+ padding: 2.5rem 1.5rem;
552
+ color: var(--fff-muted);
553
+ font-size: 0.9rem;
554
+ }
555
+
556
+ .empty-state .empty-icon {
557
+ font-size: 2.5rem;
558
+ margin-bottom: 0.75rem;
559
+ opacity: 0.4;
560
+ }
561
+
562
+ .empty-state .empty-title {
563
+ font-size: 1rem;
564
+ font-weight: 600;
565
+ color: var(--fff-text);
566
+ margin-bottom: 0.4rem;
567
+ }
568
+
569
+ /* ── Divider ─────────────────────────────────────────────────────────────── */
570
+
571
+ .divider {
572
+ border: none;
573
+ border-top: 1px solid var(--fff-border);
574
+ margin: 1.25rem 0;
575
+ }
576
+
577
+ /* ── Code block ──────────────────────────────────────────────────────────── */
578
+
579
+ .code-block, pre {
580
+ background: var(--fff-bg);
581
+ border: 1px solid var(--fff-border);
582
+ border-radius: var(--fff-radius);
583
+ padding: 1rem 1.25rem;
584
+ font-family: var(--fff-mono);
585
+ font-size: 0.83rem;
586
+ color: var(--fff-text);
587
+ overflow-x: auto;
588
+ line-height: 1.7;
589
+ }
590
+ .code-block code, pre code { font-family: inherit; color: inherit; }
591
+
592
+ /* ── Inline code ─────────────────────────────────────────────────────────── */
593
+
594
+ :not(pre) > code {
595
+ background: var(--fff-bg);
596
+ border: 1px solid var(--fff-border);
597
+ border-radius: 4px;
598
+ padding: 0.1em 0.35em;
599
+ font-family: var(--fff-mono);
600
+ font-size: 0.85em;
601
+ color: var(--fff-accent-dark);
602
+ }
603
+
604
+ /* ── Error display ───────────────────────────────────────────────────────── */
605
+
606
+ .error-text {
607
+ color: var(--fff-danger);
608
+ font-size: 0.82rem;
609
+ min-height: 1.2em;
610
+ margin-bottom: 0.5rem;
611
+ }
612
+
613
+ /* ── Stat bar ─────────────────────────────────────────────────────────────── */
614
+
615
+ .stat-bar {
616
+ display: flex;
617
+ gap: 0.5rem;
618
+ flex-wrap: wrap;
619
+ margin-bottom: 1.25rem;
620
+ }
621
+
622
+ /* ── Radio group ─────────────────────────────────────────────────────────────── */
623
+
624
+ .radio-group {
625
+ display: flex;
626
+ gap: 1.5rem;
627
+ margin-top: 0.25rem;
628
+ }
629
+
630
+ /* ── Utility classes ─────────────────────────────────────────────────────────── */
631
+
632
+ .font-medium { font-weight: 500; }
633
+ .text-muted { color: var(--fff-muted); }
634
+ .text-subtle { color: var(--fff-subtle); }
635
+ .text-mono { font-family: var(--fff-mono); font-size: 0.83em; }
636
+
637
+ /* ═══════════════════════════════════════════════════════════
638
+ Demo-specific component styles (Counter + UserList + About)
639
+ ═══════════════════════════════════════════════════════════ */
640
+
641
+ /* ── Counter component ───────────────────────────────────────────────────── */
642
+
643
+ .counter-title {
644
+ font-size: 0.8rem;
645
+ font-weight: 600;
646
+ color: var(--fff-muted);
647
+ text-transform: uppercase;
648
+ letter-spacing: 0.08em;
649
+ margin-bottom: 0.875rem;
650
+ }
651
+
652
+ .counter-value {
653
+ font-size: 2.5rem;
654
+ font-weight: 700;
655
+ font-variant-numeric: tabular-nums;
656
+ letter-spacing: -0.02em;
657
+ color: var(--fff-accent);
658
+ margin-bottom: 1.25rem;
659
+ }
660
+
661
+ .counter-buttons { display: flex; gap: 0.5rem; }
662
+
663
+ /* ── User form ───────────────────────────────────────────────────────────── */
664
+
665
+ .user-form {
666
+ display: flex;
667
+ gap: 0.5rem;
668
+ margin-bottom: 0.5rem;
669
+ }
670
+
671
+ .user-form input[type="text"] {
672
+ flex: 1;
673
+ background: var(--fff-surface);
674
+ border: 1px solid var(--fff-border-dark);
675
+ border-radius: var(--fff-radius);
676
+ color: var(--fff-text);
677
+ font-size: 0.9rem;
678
+ padding: 0.45rem 0.8rem;
679
+ outline: none;
680
+ transition: border-color 0.12s, box-shadow 0.12s;
681
+ }
682
+ .user-form input[type="text"]:focus {
683
+ border-color: var(--fff-accent);
684
+ box-shadow: 0 0 0 3px var(--fff-accent-dim);
685
+ }
686
+
687
+ /* ── User list ───────────────────────────────────────────────────────────── */
688
+
689
+ .user-list-items {
690
+ list-style: none;
691
+ margin-top: 0.75rem;
692
+ display: flex;
693
+ flex-direction: column;
694
+ gap: 0.35rem;
695
+ }
696
+
697
+ .user-item {
698
+ display: flex;
699
+ align-items: center;
700
+ justify-content: space-between;
701
+ background: var(--fff-bg);
702
+ border: 1px solid var(--fff-border);
703
+ border-radius: var(--fff-radius);
704
+ padding: 0.5rem 0.875rem;
705
+ font-size: 0.9rem;
706
+ }
707
+
708
+ .user-name { color: var(--fff-text); font-weight: 500; }
709
+
710
+ .btn-delete {
711
+ background: none;
712
+ border: none;
713
+ color: var(--fff-subtle);
714
+ cursor: pointer;
715
+ font-size: 1rem;
716
+ line-height: 1;
717
+ padding: 0 0.2rem;
718
+ transition: color 0.12s;
719
+ }
720
+ .btn-delete:hover { color: var(--fff-danger); }
721
+
722
+ .empty { color: var(--fff-muted); font-size: 0.875rem; padding: 0.25rem 0; }
723
+
724
+ /* ── About page ──────────────────────────────────────────────────────────── */
725
+
726
+ .about-lead {
727
+ color: var(--fff-muted);
728
+ margin-bottom: 1.25rem;
729
+ line-height: 1.7;
730
+ }
731
+
732
+ .about-list {
733
+ list-style: none;
734
+ display: flex;
735
+ flex-direction: column;
736
+ gap: 0.4rem;
737
+ }
738
+
739
+ .about-list li {
740
+ padding: 0.5rem 0.875rem;
741
+ background: var(--fff-bg);
742
+ border: 1px solid var(--fff-border);
743
+ border-radius: var(--fff-radius);
744
+ font-size: 0.875rem;
745
+ line-height: 1.5;
746
+ }
747
+
748
+ /* ── Stack info grid ─────────────────────────────────────────────────────── */
749
+
750
+ .stack-grid { display: flex; flex-direction: column; gap: 0.35rem; }
751
+
752
+ .stack-item {
753
+ display: flex;
754
+ align-items: center;
755
+ justify-content: space-between;
756
+ padding: 0.45rem 0.875rem;
757
+ background: var(--fff-bg);
758
+ border: 1px solid var(--fff-border);
759
+ border-radius: var(--fff-radius);
760
+ font-size: 0.875rem;
761
+ }
762
+
763
+ .stack-name { color: var(--fff-muted); }
764
+ .stack-value {
765
+ color: var(--fff-text);
766
+ font-weight: 500;
767
+ font-family: var(--fff-mono);
768
+ font-size: 0.83rem;
769
+ }