adonis-atlas 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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/build/chunk-7QVYU63E.js +7 -0
  4. package/build/chunk-7QVYU63E.js.map +1 -0
  5. package/build/client/app.js +20 -0
  6. package/build/client/app.js.map +1 -0
  7. package/build/client/boot.js +23 -0
  8. package/build/client/boot.js.map +1 -0
  9. package/build/client/resources/components/DataTable.vue +103 -0
  10. package/build/client/resources/components/FormField.vue +40 -0
  11. package/build/client/resources/components/Layout.vue +68 -0
  12. package/build/client/resources/components/Pagination.vue +60 -0
  13. package/build/client/resources/components/SearchBar.vue +41 -0
  14. package/build/client/resources/components/fields/BooleanField.vue +26 -0
  15. package/build/client/resources/components/fields/DateTimeField.vue +26 -0
  16. package/build/client/resources/components/fields/EmailField.vue +27 -0
  17. package/build/client/resources/components/fields/NumberField.vue +27 -0
  18. package/build/client/resources/components/fields/PasswordField.vue +28 -0
  19. package/build/client/resources/components/fields/TextField.vue +27 -0
  20. package/build/client/resources/css/atlas.css +662 -0
  21. package/build/client/resources/pages/atlas/Create.vue +132 -0
  22. package/build/client/resources/pages/atlas/Edit.vue +145 -0
  23. package/build/client/resources/pages/atlas/Index.vue +138 -0
  24. package/build/commands/main.js +9 -0
  25. package/build/commands/main.js.map +1 -0
  26. package/build/commands/make_resource.js +42 -0
  27. package/build/commands/make_resource.js.map +1 -0
  28. package/build/configure.js +17 -0
  29. package/build/configure.js.map +1 -0
  30. package/build/index.js +29 -0
  31. package/build/index.js.map +1 -0
  32. package/build/providers/atlas_provider.js +56 -0
  33. package/build/providers/atlas_provider.js.map +1 -0
  34. package/build/stubs/config.stub +18 -0
  35. package/build/stubs/resource.stub +25 -0
  36. package/package.json +81 -0
  37. package/stubs/config.stub +18 -0
  38. package/stubs/main.ts +3 -0
  39. package/stubs/resource.stub +25 -0
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ modelValue: string
4
+ field: { name: string; label: string }
5
+ error?: string
6
+ }>()
7
+
8
+ defineEmits<{
9
+ 'update:modelValue': [value: string]
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div class="atlas-form__group">
15
+ <label :for="`field-${field.name}`" class="atlas-form__label">{{ field.label }}</label>
16
+ <input
17
+ :id="`field-${field.name}`"
18
+ type="password"
19
+ class="atlas-form__input"
20
+ :class="{ 'atlas-form__input--error': error }"
21
+ :value="modelValue"
22
+ :placeholder="`Enter ${field.label.toLowerCase()}`"
23
+ autocomplete="new-password"
24
+ @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
25
+ />
26
+ <div v-if="error" class="atlas-form__error">{{ error }}</div>
27
+ </div>
28
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ modelValue: string
4
+ field: { name: string; label: string }
5
+ error?: string
6
+ }>()
7
+
8
+ defineEmits<{
9
+ 'update:modelValue': [value: string]
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <div class="atlas-form__group">
15
+ <label :for="`field-${field.name}`" class="atlas-form__label">{{ field.label }}</label>
16
+ <input
17
+ :id="`field-${field.name}`"
18
+ type="text"
19
+ class="atlas-form__input"
20
+ :class="{ 'atlas-form__input--error': error }"
21
+ :value="modelValue"
22
+ :placeholder="`Enter ${field.label.toLowerCase()}`"
23
+ @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
24
+ />
25
+ <div v-if="error" class="atlas-form__error">{{ error }}</div>
26
+ </div>
27
+ </template>
@@ -0,0 +1,662 @@
1
+ /* ──────────────────────────────────────────────
2
+ Atlas Design System — Dark Theme
3
+ ────────────────────────────────────────────── */
4
+
5
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
6
+
7
+ :root {
8
+ /* Colors — Zinc/Slate dark palette */
9
+ --atlas-bg: #0a0a0b;
10
+ --atlas-surface: #111113;
11
+ --atlas-surface-2: #19191c;
12
+ --atlas-surface-3: #222226;
13
+ --atlas-border: #2e2e33;
14
+ --atlas-border-hover: #3e3e44;
15
+ --atlas-text: #ededef;
16
+ --atlas-text-secondary: #9898a0;
17
+ --atlas-text-muted: #6e6e78;
18
+ --atlas-accent: #6366f1;
19
+ --atlas-accent-hover: #818cf8;
20
+ --atlas-accent-subtle: rgba(99, 102, 241, 0.12);
21
+ --atlas-danger: #ef4444;
22
+ --atlas-danger-hover: #f87171;
23
+ --atlas-danger-subtle: rgba(239, 68, 68, 0.12);
24
+ --atlas-success: #22c55e;
25
+ --atlas-success-subtle: rgba(34, 197, 94, 0.12);
26
+ --atlas-warning: #f59e0b;
27
+
28
+ /* Spacing */
29
+ --atlas-sidebar-width: 260px;
30
+
31
+ /* Radius */
32
+ --atlas-radius-sm: 6px;
33
+ --atlas-radius: 8px;
34
+ --atlas-radius-lg: 12px;
35
+
36
+ /* Shadows */
37
+ --atlas-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
38
+ --atlas-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5);
39
+
40
+ /* Transitions */
41
+ --atlas-transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
42
+ }
43
+
44
+ /* ── Global ────────────────────────────────── */
45
+
46
+ .atlas-app {
47
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
48
+ background: var(--atlas-bg);
49
+ color: var(--atlas-text);
50
+ min-height: 100vh;
51
+ -webkit-font-smoothing: antialiased;
52
+ -moz-osx-font-smoothing: grayscale;
53
+ }
54
+
55
+ /* ── Layout ────────────────────────────────── */
56
+
57
+ .atlas-layout {
58
+ display: flex;
59
+ min-height: 100vh;
60
+ }
61
+
62
+ .atlas-sidebar {
63
+ width: var(--atlas-sidebar-width);
64
+ background: var(--atlas-surface);
65
+ border-right: 1px solid var(--atlas-border);
66
+ display: flex;
67
+ flex-direction: column;
68
+ position: fixed;
69
+ top: 0;
70
+ left: 0;
71
+ bottom: 0;
72
+ z-index: 40;
73
+ }
74
+
75
+ .atlas-sidebar__brand {
76
+ padding: 20px 20px 16px;
77
+ border-bottom: 1px solid var(--atlas-border);
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 10px;
81
+ }
82
+
83
+ .atlas-sidebar__brand-icon {
84
+ width: 32px;
85
+ height: 32px;
86
+ background: linear-gradient(135deg, var(--atlas-accent), #a855f7);
87
+ border-radius: var(--atlas-radius);
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ color: white;
92
+ font-weight: 700;
93
+ font-size: 14px;
94
+ }
95
+
96
+ .atlas-sidebar__brand-text {
97
+ font-size: 16px;
98
+ font-weight: 600;
99
+ letter-spacing: -0.02em;
100
+ }
101
+
102
+ .atlas-sidebar__label {
103
+ padding: 16px 20px 8px;
104
+ font-size: 11px;
105
+ font-weight: 600;
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.05em;
108
+ color: var(--atlas-text-muted);
109
+ }
110
+
111
+ .atlas-sidebar__nav {
112
+ padding: 0 8px;
113
+ flex: 1;
114
+ overflow-y: auto;
115
+ }
116
+
117
+ .atlas-sidebar__link {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 10px;
121
+ padding: 8px 12px;
122
+ border-radius: var(--atlas-radius-sm);
123
+ color: var(--atlas-text-secondary);
124
+ text-decoration: none;
125
+ font-size: 14px;
126
+ font-weight: 450;
127
+ transition: all var(--atlas-transition);
128
+ margin-bottom: 2px;
129
+ }
130
+
131
+ .atlas-sidebar__link:hover {
132
+ background: var(--atlas-surface-2);
133
+ color: var(--atlas-text);
134
+ }
135
+
136
+ .atlas-sidebar__link--active {
137
+ background: var(--atlas-accent-subtle);
138
+ color: var(--atlas-accent-hover);
139
+ }
140
+
141
+ .atlas-sidebar__link-icon {
142
+ width: 18px;
143
+ height: 18px;
144
+ opacity: 0.6;
145
+ }
146
+
147
+ .atlas-sidebar__link--active .atlas-sidebar__link-icon {
148
+ opacity: 1;
149
+ }
150
+
151
+ .atlas-main {
152
+ flex: 1;
153
+ margin-left: var(--atlas-sidebar-width);
154
+ }
155
+
156
+ .atlas-topbar {
157
+ height: 56px;
158
+ background: var(--atlas-surface);
159
+ border-bottom: 1px solid var(--atlas-border);
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: space-between;
163
+ padding: 0 24px;
164
+ position: sticky;
165
+ top: 0;
166
+ z-index: 30;
167
+ backdrop-filter: blur(8px);
168
+ background: rgba(17, 17, 19, 0.85);
169
+ }
170
+
171
+ .atlas-topbar__breadcrumb {
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 8px;
175
+ font-size: 14px;
176
+ }
177
+
178
+ .atlas-topbar__breadcrumb-sep {
179
+ color: var(--atlas-text-muted);
180
+ font-size: 12px;
181
+ }
182
+
183
+ .atlas-topbar__breadcrumb a {
184
+ color: var(--atlas-text-secondary);
185
+ text-decoration: none;
186
+ transition: color var(--atlas-transition);
187
+ }
188
+
189
+ .atlas-topbar__breadcrumb a:hover {
190
+ color: var(--atlas-text);
191
+ }
192
+
193
+ .atlas-topbar__breadcrumb span {
194
+ color: var(--atlas-text);
195
+ font-weight: 500;
196
+ }
197
+
198
+ .atlas-content {
199
+ padding: 24px;
200
+ max-width: 1280px;
201
+ }
202
+
203
+ /* ── Page Header ───────────────────────────── */
204
+
205
+ .atlas-page-header {
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: space-between;
209
+ margin-bottom: 24px;
210
+ }
211
+
212
+ .atlas-page-header__title {
213
+ font-size: 24px;
214
+ font-weight: 600;
215
+ letter-spacing: -0.02em;
216
+ }
217
+
218
+ .atlas-page-header__actions {
219
+ display: flex;
220
+ gap: 8px;
221
+ }
222
+
223
+ /* ── Buttons ───────────────────────────────── */
224
+
225
+ .atlas-btn {
226
+ display: inline-flex;
227
+ align-items: center;
228
+ gap: 6px;
229
+ padding: 8px 16px;
230
+ border-radius: var(--atlas-radius-sm);
231
+ font-size: 13px;
232
+ font-weight: 500;
233
+ font-family: inherit;
234
+ cursor: pointer;
235
+ border: none;
236
+ transition: all var(--atlas-transition);
237
+ text-decoration: none;
238
+ line-height: 1.4;
239
+ }
240
+
241
+ .atlas-btn--primary {
242
+ background: var(--atlas-accent);
243
+ color: white;
244
+ }
245
+
246
+ .atlas-btn--primary:hover {
247
+ background: var(--atlas-accent-hover);
248
+ box-shadow: 0 0 0 3px var(--atlas-accent-subtle);
249
+ }
250
+
251
+ .atlas-btn--secondary {
252
+ background: var(--atlas-surface-3);
253
+ color: var(--atlas-text);
254
+ border: 1px solid var(--atlas-border);
255
+ }
256
+
257
+ .atlas-btn--secondary:hover {
258
+ background: var(--atlas-surface-2);
259
+ border-color: var(--atlas-border-hover);
260
+ }
261
+
262
+ .atlas-btn--danger {
263
+ background: var(--atlas-danger-subtle);
264
+ color: var(--atlas-danger);
265
+ border: 1px solid transparent;
266
+ }
267
+
268
+ .atlas-btn--danger:hover {
269
+ background: var(--atlas-danger);
270
+ color: white;
271
+ }
272
+
273
+ .atlas-btn--sm {
274
+ padding: 5px 10px;
275
+ font-size: 12px;
276
+ }
277
+
278
+ .atlas-btn--icon {
279
+ padding: 6px;
280
+ min-width: 32px;
281
+ justify-content: center;
282
+ }
283
+
284
+ .atlas-btn:disabled {
285
+ opacity: 0.5;
286
+ cursor: not-allowed;
287
+ }
288
+
289
+ /* ── Card ──────────────────────────────────── */
290
+
291
+ .atlas-card {
292
+ background: var(--atlas-surface);
293
+ border: 1px solid var(--atlas-border);
294
+ border-radius: var(--atlas-radius-lg);
295
+ overflow: hidden;
296
+ }
297
+
298
+ .atlas-card__header {
299
+ padding: 16px 20px;
300
+ border-bottom: 1px solid var(--atlas-border);
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: space-between;
304
+ }
305
+
306
+ .atlas-card__body {
307
+ padding: 20px;
308
+ }
309
+
310
+ /* ── Table ─────────────────────────────────── */
311
+
312
+ .atlas-table-wrapper {
313
+ overflow-x: auto;
314
+ }
315
+
316
+ .atlas-table {
317
+ width: 100%;
318
+ border-collapse: collapse;
319
+ font-size: 13px;
320
+ }
321
+
322
+ .atlas-table th {
323
+ text-align: left;
324
+ padding: 10px 16px;
325
+ font-weight: 500;
326
+ font-size: 12px;
327
+ text-transform: uppercase;
328
+ letter-spacing: 0.04em;
329
+ color: var(--atlas-text-muted);
330
+ border-bottom: 1px solid var(--atlas-border);
331
+ white-space: nowrap;
332
+ user-select: none;
333
+ }
334
+
335
+ .atlas-table th.sortable {
336
+ cursor: pointer;
337
+ transition: color var(--atlas-transition);
338
+ }
339
+
340
+ .atlas-table th.sortable:hover {
341
+ color: var(--atlas-text);
342
+ }
343
+
344
+ .atlas-table th.sorted {
345
+ color: var(--atlas-accent-hover);
346
+ }
347
+
348
+ .atlas-table th .sort-indicator {
349
+ display: inline-block;
350
+ margin-left: 4px;
351
+ opacity: 0.4;
352
+ transition: opacity var(--atlas-transition);
353
+ }
354
+
355
+ .atlas-table th.sorted .sort-indicator {
356
+ opacity: 1;
357
+ }
358
+
359
+ .atlas-table td {
360
+ padding: 12px 16px;
361
+ border-bottom: 1px solid var(--atlas-border);
362
+ color: var(--atlas-text-secondary);
363
+ max-width: 300px;
364
+ overflow: hidden;
365
+ text-overflow: ellipsis;
366
+ white-space: nowrap;
367
+ }
368
+
369
+ .atlas-table tbody tr {
370
+ transition: background var(--atlas-transition);
371
+ }
372
+
373
+ .atlas-table tbody tr:hover {
374
+ background: var(--atlas-surface-2);
375
+ }
376
+
377
+ .atlas-table tbody tr:last-child td {
378
+ border-bottom: none;
379
+ }
380
+
381
+ .atlas-table__actions {
382
+ display: flex;
383
+ gap: 4px;
384
+ justify-content: flex-end;
385
+ }
386
+
387
+ /* ── Search ────────────────────────────────── */
388
+
389
+ .atlas-search {
390
+ position: relative;
391
+ }
392
+
393
+ .atlas-search__icon {
394
+ position: absolute;
395
+ left: 12px;
396
+ top: 50%;
397
+ transform: translateY(-50%);
398
+ color: var(--atlas-text-muted);
399
+ width: 16px;
400
+ height: 16px;
401
+ pointer-events: none;
402
+ }
403
+
404
+ .atlas-search__input {
405
+ width: 280px;
406
+ padding: 8px 12px 8px 36px;
407
+ background: var(--atlas-surface-2);
408
+ border: 1px solid var(--atlas-border);
409
+ border-radius: var(--atlas-radius-sm);
410
+ color: var(--atlas-text);
411
+ font-size: 13px;
412
+ font-family: inherit;
413
+ outline: none;
414
+ transition: all var(--atlas-transition);
415
+ }
416
+
417
+ .atlas-search__input::placeholder {
418
+ color: var(--atlas-text-muted);
419
+ }
420
+
421
+ .atlas-search__input:focus {
422
+ border-color: var(--atlas-accent);
423
+ box-shadow: 0 0 0 3px var(--atlas-accent-subtle);
424
+ }
425
+
426
+ /* ── Pagination ────────────────────────────── */
427
+
428
+ .atlas-pagination {
429
+ display: flex;
430
+ align-items: center;
431
+ justify-content: space-between;
432
+ padding: 12px 16px;
433
+ border-top: 1px solid var(--atlas-border);
434
+ font-size: 13px;
435
+ color: var(--atlas-text-secondary);
436
+ }
437
+
438
+ .atlas-pagination__info {
439
+ color: var(--atlas-text-muted);
440
+ }
441
+
442
+ .atlas-pagination__controls {
443
+ display: flex;
444
+ gap: 4px;
445
+ }
446
+
447
+ .atlas-pagination__btn {
448
+ padding: 6px 12px;
449
+ background: var(--atlas-surface-2);
450
+ border: 1px solid var(--atlas-border);
451
+ border-radius: var(--atlas-radius-sm);
452
+ color: var(--atlas-text-secondary);
453
+ font-size: 13px;
454
+ font-family: inherit;
455
+ cursor: pointer;
456
+ transition: all var(--atlas-transition);
457
+ }
458
+
459
+ .atlas-pagination__btn:hover:not(:disabled) {
460
+ background: var(--atlas-surface-3);
461
+ color: var(--atlas-text);
462
+ border-color: var(--atlas-border-hover);
463
+ }
464
+
465
+ .atlas-pagination__btn:disabled {
466
+ opacity: 0.35;
467
+ cursor: not-allowed;
468
+ }
469
+
470
+ .atlas-pagination__btn--active {
471
+ background: var(--atlas-accent);
472
+ border-color: var(--atlas-accent);
473
+ color: white;
474
+ }
475
+
476
+ /* ── Form ──────────────────────────────────── */
477
+
478
+ .atlas-form {
479
+ max-width: 640px;
480
+ }
481
+
482
+ .atlas-form__group {
483
+ margin-bottom: 20px;
484
+ }
485
+
486
+ .atlas-form__label {
487
+ display: block;
488
+ font-size: 13px;
489
+ font-weight: 500;
490
+ color: var(--atlas-text);
491
+ margin-bottom: 6px;
492
+ }
493
+
494
+ .atlas-form__input {
495
+ width: 100%;
496
+ padding: 9px 12px;
497
+ background: var(--atlas-surface-2);
498
+ border: 1px solid var(--atlas-border);
499
+ border-radius: var(--atlas-radius-sm);
500
+ color: var(--atlas-text);
501
+ font-size: 14px;
502
+ font-family: inherit;
503
+ outline: none;
504
+ transition: all var(--atlas-transition);
505
+ box-sizing: border-box;
506
+ }
507
+
508
+ .atlas-form__input::placeholder {
509
+ color: var(--atlas-text-muted);
510
+ }
511
+
512
+ .atlas-form__input:focus {
513
+ border-color: var(--atlas-accent);
514
+ box-shadow: 0 0 0 3px var(--atlas-accent-subtle);
515
+ }
516
+
517
+ .atlas-form__input--error {
518
+ border-color: var(--atlas-danger);
519
+ box-shadow: 0 0 0 3px var(--atlas-danger-subtle);
520
+ }
521
+
522
+ .atlas-form__error {
523
+ margin-top: 4px;
524
+ font-size: 12px;
525
+ color: var(--atlas-danger);
526
+ }
527
+
528
+ .atlas-form__actions {
529
+ display: flex;
530
+ gap: 8px;
531
+ padding-top: 8px;
532
+ }
533
+
534
+ /* ── Toggle ────────────────────────────────── */
535
+
536
+ .atlas-toggle {
537
+ display: flex;
538
+ align-items: center;
539
+ gap: 10px;
540
+ cursor: pointer;
541
+ }
542
+
543
+ .atlas-toggle__track {
544
+ width: 40px;
545
+ height: 22px;
546
+ background: var(--atlas-surface-3);
547
+ border: 1px solid var(--atlas-border);
548
+ border-radius: 11px;
549
+ position: relative;
550
+ transition: all var(--atlas-transition);
551
+ }
552
+
553
+ .atlas-toggle__track--active {
554
+ background: var(--atlas-accent);
555
+ border-color: var(--atlas-accent);
556
+ }
557
+
558
+ .atlas-toggle__knob {
559
+ width: 16px;
560
+ height: 16px;
561
+ background: white;
562
+ border-radius: 50%;
563
+ position: absolute;
564
+ top: 2px;
565
+ left: 2px;
566
+ transition: transform var(--atlas-transition);
567
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
568
+ }
569
+
570
+ .atlas-toggle__track--active .atlas-toggle__knob {
571
+ transform: translateX(18px);
572
+ }
573
+
574
+ /* ── Empty State ───────────────────────────── */
575
+
576
+ .atlas-empty {
577
+ text-align: center;
578
+ padding: 48px 24px;
579
+ color: var(--atlas-text-muted);
580
+ }
581
+
582
+ .atlas-empty__icon {
583
+ width: 48px;
584
+ height: 48px;
585
+ margin: 0 auto 12px;
586
+ opacity: 0.3;
587
+ }
588
+
589
+ .atlas-empty__title {
590
+ font-size: 15px;
591
+ font-weight: 500;
592
+ color: var(--atlas-text-secondary);
593
+ margin-bottom: 4px;
594
+ }
595
+
596
+ .atlas-empty__text {
597
+ font-size: 13px;
598
+ }
599
+
600
+ /* ── Notification / Toast ──────────────────── */
601
+
602
+ .atlas-toast {
603
+ position: fixed;
604
+ top: 16px;
605
+ right: 16px;
606
+ padding: 12px 16px;
607
+ border-radius: var(--atlas-radius);
608
+ font-size: 13px;
609
+ font-weight: 500;
610
+ z-index: 100;
611
+ animation: atlas-slide-in 0.3s ease-out;
612
+ box-shadow: var(--atlas-shadow-lg);
613
+ }
614
+
615
+ .atlas-toast--success {
616
+ background: var(--atlas-success-subtle);
617
+ color: var(--atlas-success);
618
+ border: 1px solid var(--atlas-success);
619
+ }
620
+
621
+ .atlas-toast--error {
622
+ background: var(--atlas-danger-subtle);
623
+ color: var(--atlas-danger);
624
+ border: 1px solid var(--atlas-danger);
625
+ }
626
+
627
+ @keyframes atlas-slide-in {
628
+ from {
629
+ opacity: 0;
630
+ transform: translateY(-8px);
631
+ }
632
+ to {
633
+ opacity: 1;
634
+ transform: translateY(0);
635
+ }
636
+ }
637
+
638
+ /* ── Animations ────────────────────────────── */
639
+
640
+ .atlas-fade-in {
641
+ animation: atlas-fade-in 0.25s ease-out;
642
+ }
643
+
644
+ @keyframes atlas-fade-in {
645
+ from { opacity: 0; transform: translateY(4px); }
646
+ to { opacity: 1; transform: translateY(0); }
647
+ }
648
+
649
+ /* ── Spinner ───────────────────────────────── */
650
+
651
+ .atlas-spinner {
652
+ width: 16px;
653
+ height: 16px;
654
+ border: 2px solid var(--atlas-border);
655
+ border-top-color: var(--atlas-accent);
656
+ border-radius: 50%;
657
+ animation: atlas-spin 0.6s linear infinite;
658
+ }
659
+
660
+ @keyframes atlas-spin {
661
+ to { transform: rotate(360deg); }
662
+ }