@guanjinke/artifact-client 1.14.1 → 1.15.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.
@@ -1,1619 +1,1619 @@
1
- /* AFM Environment Manager — dark glassmorphism theme.
2
- Everything is class-driven: the page is served under a strict CSP that forbids
3
- inline <style> and style="" attributes. */
4
-
5
- :root {
6
- --bg: #05070d;
7
- --glass: rgba(255, 255, 255, .055);
8
- --glass-strong: rgba(255, 255, 255, .09);
9
- --border: rgba(255, 255, 255, .10);
10
- --border-strong: rgba(255, 255, 255, .18);
11
- --text: #eaeefb;
12
- --muted: #98a2bd;
13
- --accent: #4d6bfe;
14
- --accent-soft: rgba(77, 107, 254, .16);
15
- --cyan: #35d0e8;
16
- --ok: #3ddc97;
17
- --warn: #ffc24b;
18
- --err: #ff6b81;
19
- --radius: 18px;
20
- /* One height for every single-line control, so a <select> and an <input> in
21
- the same row cannot disagree. */
22
- --control-height: 38px;
23
- --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
24
- --panel: linear-gradient(160deg, var(--glass-strong), var(--glass));
25
- --panel-shadow: 0 18px 50px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .06);
26
- }
27
-
28
- *,
29
- *::before,
30
- *::after {
31
- box-sizing: border-box;
32
- }
33
-
34
- html,
35
- body {
36
- margin: 0;
37
- padding: 0;
38
- }
39
-
40
- body {
41
- min-height: 100vh;
42
- background:
43
- radial-gradient(900px 520px at 12% -8%, rgba(77, 107, 254, .30), transparent 60%),
44
- radial-gradient(800px 480px at 88% -4%, rgba(53, 208, 232, .20), transparent 55%),
45
- var(--bg);
46
- background-repeat: no-repeat;
47
- background-attachment: fixed;
48
- color: var(--text);
49
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
50
- font-size: 14px;
51
- line-height: 1.55;
52
- -webkit-font-smoothing: antialiased;
53
- text-rendering: optimizeLegibility;
54
- }
55
-
56
- h1,
57
- h2,
58
- h3 {
59
- line-height: 1.25;
60
- }
61
-
62
- p {
63
- margin: 0;
64
- }
65
-
66
- button {
67
- font: inherit;
68
- }
69
-
70
- code,
71
- .mono,
72
- pre {
73
- font-family: var(--mono);
74
- }
75
-
76
- a {
77
- color: var(--cyan);
78
- }
79
-
80
- ::selection {
81
- background: var(--accent-soft);
82
- }
83
-
84
- :focus-visible {
85
- outline: 2px solid rgba(77, 107, 254, .4);
86
- outline-offset: 2px;
87
- border-radius: 10px;
88
- }
89
-
90
- .sr-only {
91
- position: absolute;
92
- width: 1px;
93
- height: 1px;
94
- padding: 0;
95
- margin: -1px;
96
- overflow: hidden;
97
- clip: rect(0 0 0 0);
98
- white-space: nowrap;
99
- border: 0;
100
- }
101
-
102
- .muted { color: var(--muted); }
103
- .small { font-size: 12px; }
104
- .strong { font-weight: 650; }
105
- .err-text { color: var(--err); }
106
- .warn-text { color: var(--warn); }
107
- .danger-text { color: #ffd7dc; }
108
-
109
- /* ------------------------------------------------------------------ layout */
110
-
111
- .app {
112
- min-height: 100vh;
113
- max-width: 1440px;
114
- margin: 0 auto;
115
- padding: 26px 22px 40px;
116
- display: grid;
117
- gap: 18px;
118
- align-content: start;
119
- }
120
-
121
- .app-main {
122
- display: grid;
123
- gap: 18px;
124
- }
125
-
126
- .app-footer {
127
- display: flex;
128
- flex-wrap: wrap;
129
- gap: 8px 16px;
130
- justify-content: space-between;
131
- padding: 4px 2px;
132
- color: var(--muted);
133
- font-size: 11.5px;
134
- }
135
-
136
- .toolbar {
137
- display: flex;
138
- flex-wrap: wrap;
139
- align-items: center;
140
- gap: 12px;
141
- }
142
-
143
- /* ------------------------------------------------------------------ header */
144
-
145
- .topbar {
146
- display: grid;
147
- gap: 14px;
148
- padding: 18px 20px;
149
- background: var(--panel);
150
- backdrop-filter: blur(18px) saturate(140%);
151
- -webkit-backdrop-filter: blur(18px) saturate(140%);
152
- border: 1px solid var(--border);
153
- border-radius: var(--radius);
154
- box-shadow: var(--panel-shadow);
155
- }
156
-
157
- .brand {
158
- display: flex;
159
- align-items: center;
160
- gap: 12px;
161
- min-width: 0;
162
- }
163
-
164
- .brand-mark {
165
- flex: 0 0 auto;
166
- display: grid;
167
- place-items: center;
168
- width: 42px;
169
- height: 42px;
170
- border-radius: 13px;
171
- background: linear-gradient(135deg, rgba(77, 107, 254, .95), rgba(53, 208, 232, .8));
172
- color: #f8faff;
173
- font-weight: 700;
174
- font-size: 13px;
175
- letter-spacing: .04em;
176
- box-shadow: 0 10px 24px rgba(77, 107, 254, .35);
177
- }
178
-
179
- .brand-mark.big {
180
- width: 56px;
181
- height: 56px;
182
- border-radius: 17px;
183
- font-size: 15px;
184
- }
185
-
186
- .brand-name {
187
- margin: 0;
188
- font-size: 18px;
189
- font-weight: 680;
190
- letter-spacing: .01em;
191
- }
192
-
193
- .brand-sub {
194
- margin: 4px 0 0;
195
- color: var(--muted);
196
- font-size: 12.5px;
197
- }
198
-
199
- .topbar-meta {
200
- display: flex;
201
- flex-wrap: wrap;
202
- gap: 10px;
203
- }
204
-
205
- .meta-chip {
206
- display: grid;
207
- gap: 4px;
208
- min-width: 150px;
209
- padding: 8px 12px;
210
- background: rgba(255, 255, 255, .04);
211
- border: 1px solid var(--border);
212
- border-radius: 12px;
213
- font-size: 12px;
214
- }
215
-
216
- .meta-label {
217
- color: var(--muted);
218
- font-size: 11px;
219
- letter-spacing: .06em;
220
- text-transform: uppercase;
221
- }
222
-
223
- .meta-value {
224
- word-break: break-all;
225
- }
226
-
227
- .providers {
228
- display: flex;
229
- gap: 8px;
230
- }
231
-
232
- .provider-badge {
233
- display: inline-flex;
234
- align-items: center;
235
- gap: 6px;
236
- padding: 3px 10px;
237
- border: 1px solid var(--border);
238
- border-radius: 999px;
239
- font-size: 11.5px;
240
- letter-spacing: .06em;
241
- text-transform: uppercase;
242
- }
243
-
244
- .provider-badge .dot {
245
- width: 7px;
246
- height: 7px;
247
- border-radius: 50%;
248
- background: currentColor;
249
- box-shadow: 0 0 8px currentColor;
250
- }
251
-
252
- .provider-badge.on {
253
- color: var(--ok);
254
- border-color: rgba(61, 220, 151, .40);
255
- background: rgba(61, 220, 151, .12);
256
- }
257
-
258
- .provider-badge.off {
259
- color: var(--warn);
260
- border-color: rgba(255, 194, 75, .35);
261
- background: rgba(255, 194, 75, .10);
262
- }
263
-
264
- .provider-hint {
265
- grid-column: 1 / -1;
266
- margin: 0;
267
- padding: 10px 12px;
268
- color: #ffe0ab;
269
- background: rgba(255, 194, 75, .09);
270
- border: 1px solid rgba(255, 194, 75, .28);
271
- border-radius: 12px;
272
- font-size: 12.5px;
273
- line-height: 1.7;
274
- }
275
-
276
- .provider-hint .code {
277
- margin: 0 4px;
278
- }
279
-
280
- /* ----------------------------------------------------------------- banners */
281
-
282
- .banner {
283
- display: flex;
284
- align-items: center;
285
- justify-content: space-between;
286
- gap: 12px;
287
- padding: 12px 16px;
288
- background: var(--panel);
289
- backdrop-filter: blur(18px) saturate(140%);
290
- -webkit-backdrop-filter: blur(18px) saturate(140%);
291
- border: 1px solid var(--border);
292
- border-radius: 14px;
293
- box-shadow: 0 12px 34px rgba(0, 0, 0, .35);
294
- }
295
-
296
- .banner.error {
297
- border-color: rgba(255, 107, 129, .42);
298
- background: linear-gradient(160deg, rgba(255, 107, 129, .16), rgba(255, 107, 129, .07));
299
- }
300
-
301
- .banner.success {
302
- border-color: rgba(61, 220, 151, .40);
303
- background: linear-gradient(160deg, rgba(61, 220, 151, .14), rgba(61, 220, 151, .06));
304
- }
305
-
306
- .banner.info {
307
- border-color: rgba(77, 107, 254, .40);
308
- background: linear-gradient(160deg, rgba(77, 107, 254, .16), rgba(77, 107, 254, .06));
309
- }
310
-
311
- .banner-text {
312
- font-size: 13px;
313
- line-height: 1.6;
314
- }
315
-
316
- .banner-actions {
317
- display: flex;
318
- flex: 0 0 auto;
319
- gap: 8px;
320
- }
321
-
322
- /* ------------------------------------------------------------------ panels */
323
-
324
- .panel {
325
- display: grid;
326
- gap: 14px;
327
- padding: 18px 20px;
328
- background: var(--panel);
329
- backdrop-filter: blur(18px) saturate(140%);
330
- -webkit-backdrop-filter: blur(18px) saturate(140%);
331
- border: 1px solid var(--border);
332
- border-radius: var(--radius);
333
- box-shadow: var(--panel-shadow);
334
- }
335
-
336
- .panel-head {
337
- display: flex;
338
- align-items: center;
339
- gap: 10px;
340
- }
341
-
342
- .panel-title {
343
- margin: 0;
344
- font-size: 15px;
345
- font-weight: 650;
346
- }
347
-
348
- .count {
349
- padding: 1px 8px;
350
- color: var(--muted);
351
- background: rgba(255, 255, 255, .05);
352
- border: 1px solid var(--border);
353
- border-radius: 999px;
354
- font-size: 11.5px;
355
- }
356
-
357
- .panel-actions {
358
- display: flex;
359
- align-items: center;
360
- gap: 8px;
361
- margin-left: auto;
362
- }
363
-
364
- /* ----------------------------------------------------------------- buttons */
365
-
366
- .btn {
367
- display: inline-flex;
368
- align-items: center;
369
- justify-content: center;
370
- gap: 6px;
371
- padding: 8px 14px;
372
- color: var(--text);
373
- background: linear-gradient(160deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .045));
374
- border: 1px solid var(--border-strong);
375
- border-radius: 10px;
376
- font-size: 13px;
377
- font-weight: 500;
378
- cursor: pointer;
379
- transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease, background-color .16s ease;
380
- }
381
-
382
- .btn:hover:not(:disabled) {
383
- transform: translateY(-1px);
384
- border-color: rgba(255, 255, 255, .30);
385
- }
386
-
387
- .btn:active:not(:disabled) {
388
- transform: translateY(0);
389
- }
390
-
391
- .btn.primary {
392
- background: linear-gradient(135deg, var(--accent), #3f7ef7);
393
- border-color: rgba(155, 175, 255, .45);
394
- box-shadow: 0 10px 26px rgba(77, 107, 254, .35);
395
- }
396
-
397
- .btn.primary:hover:not(:disabled) {
398
- box-shadow: 0 14px 30px rgba(77, 107, 254, .45);
399
- }
400
-
401
- .btn.danger {
402
- color: #ffd9de;
403
- background: linear-gradient(160deg, rgba(255, 107, 129, .22), rgba(255, 107, 129, .12));
404
- border-color: rgba(255, 107, 129, .45);
405
- }
406
-
407
- .btn.ghost {
408
- background: transparent;
409
- border-color: var(--border);
410
- }
411
-
412
- .btn.small {
413
- padding: 6px 10px;
414
- border-radius: 9px;
415
- font-size: 12px;
416
- }
417
-
418
- .btn.tiny {
419
- padding: 2px 8px;
420
- font-size: 11px;
421
- line-height: 1.5;
422
- border-radius: 8px;
423
- }
424
-
425
- .btn:disabled {
426
- opacity: .45;
427
- cursor: not-allowed;
428
- transform: none;
429
- box-shadow: none;
430
- }
431
-
432
- /* ------------------------------------------------------------------- pills */
433
-
434
- .pill {
435
- display: inline-flex;
436
- align-items: center;
437
- padding: 3px 10px;
438
- border: 1px solid var(--border-strong);
439
- border-radius: 999px;
440
- font-size: 10.5px;
441
- font-weight: 650;
442
- letter-spacing: .09em;
443
- text-transform: uppercase;
444
- white-space: nowrap;
445
- }
446
-
447
- .pill.s-ok,
448
- .pill.s-job-succeeded {
449
- color: var(--ok);
450
- background: rgba(61, 220, 151, .14);
451
- border-color: rgba(61, 220, 151, .38);
452
- }
453
-
454
- .pill.s-accent,
455
- .pill.s-job-running {
456
- color: #b9c6ff;
457
- background: var(--accent-soft);
458
- border-color: rgba(77, 107, 254, .45);
459
- }
460
-
461
- .pill.s-warn,
462
- .pill.s-job-queued {
463
- color: var(--warn);
464
- background: rgba(255, 194, 75, .14);
465
- border-color: rgba(255, 194, 75, .38);
466
- }
467
-
468
- .pill.s-warn-muted {
469
- color: var(--warn);
470
- background: rgba(255, 194, 75, .08);
471
- border-color: rgba(255, 194, 75, .22);
472
- }
473
-
474
- .pill.s-err,
475
- .pill.s-job-failed {
476
- color: var(--err);
477
- background: rgba(255, 107, 129, .14);
478
- border-color: rgba(255, 107, 129, .40);
479
- }
480
-
481
- .pill.s-muted {
482
- color: var(--muted);
483
- background: rgba(255, 255, 255, .05);
484
- border-color: var(--border-strong);
485
- }
486
-
487
- /* ---------------------------------------------------------- environments */
488
-
489
- /* One compact row per environment instead of a card grid: every row shows the
490
- same columns, so a long list stays scannable and uses the width. */
491
- .env-list {
492
- display: grid;
493
- gap: 0;
494
- margin: 0;
495
- padding: 0;
496
- list-style: none;
497
- }
498
-
499
- .env-row {
500
- display: grid;
501
- gap: 4px;
502
- padding: 12px 4px;
503
- border-top: 1px solid var(--border);
504
- transition: background-color .16s ease;
505
- }
506
-
507
- .env-row:first-child {
508
- border-top: none;
509
- }
510
-
511
- .env-row:hover {
512
- background: rgba(255, 255, 255, .028);
513
- }
514
-
515
- .env-row-main {
516
- display: grid;
517
- grid-template-columns: minmax(170px, 1fr) minmax(200px, auto) auto minmax(180px, 2fr) auto;
518
- align-items: center;
519
- gap: 10px 16px;
520
- }
521
-
522
- .env-id {
523
- display: flex;
524
- align-items: center;
525
- gap: 8px;
526
- min-width: 0;
527
- }
528
-
529
- .env-name {
530
- font-size: 14.5px;
531
- font-weight: 650;
532
- overflow: hidden;
533
- text-overflow: ellipsis;
534
- white-space: nowrap;
535
- }
536
-
537
- .env-url {
538
- display: inline-flex;
539
- align-items: center;
540
- gap: 6px;
541
- min-width: 0;
542
- }
543
-
544
- .env-site {
545
- color: var(--cyan);
546
- font-size: 12.5px;
547
- text-decoration: none;
548
- border-bottom: 1px dashed rgba(53, 208, 232, .35);
549
- white-space: nowrap;
550
- }
551
-
552
- .env-site:hover {
553
- color: #8ee7f5;
554
- border-bottom-color: rgba(142, 231, 245, .7);
555
- }
556
-
557
- .env-site.muted {
558
- color: var(--muted);
559
- border-bottom-color: var(--border);
560
- }
561
-
562
- .env-meta {
563
- color: var(--muted);
564
- font-size: 12px;
565
- white-space: nowrap;
566
- }
567
-
568
- .env-chips {
569
- min-width: 0;
570
- overflow: hidden;
571
- max-height: 22px;
572
- }
573
-
574
- .env-row-sub {
575
- display: flex;
576
- flex-wrap: wrap;
577
- align-items: center;
578
- gap: 6px 14px;
579
- min-width: 0;
580
- font-size: 11.5px;
581
- }
582
-
583
- .env-root {
584
- margin: 0;
585
- color: var(--muted);
586
- font-size: 11.5px;
587
- overflow: hidden;
588
- text-overflow: ellipsis;
589
- white-space: nowrap;
590
- max-width: 100%;
591
- }
592
-
593
- .env-problem {
594
- margin: 0;
595
- padding: 2px 8px;
596
- color: #ffd7dc;
597
- background: rgba(255, 107, 129, .10);
598
- border: 1px solid rgba(255, 107, 129, .30);
599
- border-radius: 8px;
600
- font-size: 11.5px;
601
- line-height: 1.6;
602
- word-break: break-word;
603
- }
604
-
605
- .env-actions {
606
- display: flex;
607
- flex-wrap: wrap;
608
- justify-content: flex-end;
609
- gap: 6px;
610
- }
611
-
612
- .card {
613
- background: var(--panel);
614
- backdrop-filter: blur(18px) saturate(140%);
615
- -webkit-backdrop-filter: blur(18px) saturate(140%);
616
- border: 1px solid var(--border);
617
- border-radius: var(--radius);
618
- box-shadow: var(--panel-shadow);
619
- transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
620
- }
621
-
622
- /* ------------------------------------------------------------------- chips */
623
-
624
- .chips {
625
- display: flex;
626
- flex-wrap: wrap;
627
- align-items: center;
628
- gap: 6px;
629
- }
630
-
631
- .chips.inline {
632
- display: inline-flex;
633
- }
634
-
635
- .chip-tag {
636
- display: inline-flex;
637
- align-items: center;
638
- padding: 2px 9px;
639
- color: var(--text);
640
- background: rgba(255, 255, 255, .06);
641
- border: 1px solid var(--border);
642
- border-radius: 999px;
643
- font-size: 11.5px;
644
- font-family: var(--mono);
645
- }
646
-
647
- .chip-tag.local {
648
- color: var(--cyan);
649
- background: rgba(53, 208, 232, .12);
650
- border-color: rgba(53, 208, 232, .35);
651
- letter-spacing: .04em;
652
- text-transform: uppercase;
653
- }
654
-
655
- .chip-tag.warn {
656
- color: var(--warn);
657
- background: rgba(255, 194, 75, .12);
658
- border-color: rgba(255, 194, 75, .35);
659
- }
660
-
661
- .chip-row {
662
- display: flex;
663
- flex-wrap: wrap;
664
- gap: 8px;
665
- margin-top: 2px;
666
- }
667
-
668
- .chip {
669
- padding: 4px 11px;
670
- color: var(--text);
671
- background: rgba(255, 255, 255, .06);
672
- border: 1px solid var(--border);
673
- border-radius: 999px;
674
- font-family: var(--mono);
675
- font-size: 12px;
676
- cursor: pointer;
677
- transition: transform .14s ease, border-color .14s ease, background-color .14s ease;
678
- }
679
-
680
- .chip:hover:not(:disabled) {
681
- transform: translateY(-1px);
682
- border-color: var(--border-strong);
683
- background: rgba(255, 255, 255, .10);
684
- }
685
-
686
- .chip.active,
687
- .chip.accent {
688
- color: #cdd7ff;
689
- background: var(--accent-soft);
690
- border-color: rgba(77, 107, 254, .48);
691
- }
692
-
693
- .chip:disabled {
694
- opacity: .4;
695
- cursor: not-allowed;
696
- }
697
-
698
- /* -------------------------------------------------------------------- jobs */
699
-
700
- .jobs {
701
- display: grid;
702
- gap: 12px;
703
- }
704
-
705
- .job {
706
- display: grid;
707
- gap: 10px;
708
- padding: 12px 14px;
709
- background: rgba(255, 255, 255, .035);
710
- border: 1px solid var(--border);
711
- border-radius: 14px;
712
- }
713
-
714
- .job.running {
715
- border-color: rgba(53, 208, 232, .34);
716
- }
717
-
718
- .job.queued {
719
- border-color: rgba(255, 194, 75, .28);
720
- }
721
-
722
- .job.failed {
723
- border-color: rgba(255, 107, 129, .32);
724
- }
725
-
726
- .job-head {
727
- display: flex;
728
- flex-wrap: wrap;
729
- align-items: center;
730
- gap: 8px 10px;
731
- font-size: 12.5px;
732
- }
733
-
734
- .job-kind {
735
- color: var(--muted);
736
- }
737
-
738
- .job-target {
739
- font-size: 13.5px;
740
- }
741
-
742
- .job-time {
743
- margin-left: auto;
744
- color: var(--muted);
745
- font-size: 11.5px;
746
- }
747
-
748
- .job-log {
749
- display: grid;
750
- gap: 4px;
751
- max-height: 220px;
752
- overflow-y: auto;
753
- padding: 10px 12px;
754
- background: rgba(0, 0, 0, .35);
755
- border: 1px solid var(--border);
756
- border-radius: 12px;
757
- font-size: 12px;
758
- }
759
-
760
- .log-line {
761
- display: grid;
762
- grid-template-columns: 66px minmax(0, 1fr);
763
- gap: 10px;
764
- margin: 0;
765
- align-items: start;
766
- }
767
-
768
- .log-time {
769
- color: rgba(152, 162, 189, .75);
770
- }
771
-
772
- .log-text {
773
- font-family: var(--mono);
774
- white-space: pre-wrap;
775
- word-break: break-word;
776
- }
777
-
778
- .job-error {
779
- margin: 0;
780
- padding: 8px 10px;
781
- color: #ffd7dc;
782
- background: rgba(255, 107, 129, .12);
783
- border: 1px solid rgba(255, 107, 129, .38);
784
- border-radius: 12px;
785
- font-size: 12.5px;
786
- line-height: 1.6;
787
- }
788
-
789
- .job-details summary {
790
- padding: 2px 0;
791
- color: var(--muted);
792
- font-size: 12.5px;
793
- cursor: pointer;
794
- }
795
-
796
- .job-details[open] summary {
797
- margin-bottom: 10px;
798
- }
799
-
800
- /* ----------------------------------------------------------------- results */
801
-
802
- .result {
803
- display: grid;
804
- gap: 8px;
805
- font-size: 12.5px;
806
- }
807
-
808
- .result-title {
809
- font-size: 12.5px;
810
- font-weight: 650;
811
- }
812
-
813
- .result-list {
814
- display: grid;
815
- gap: 6px;
816
- margin: 0;
817
- padding: 0;
818
- list-style: none;
819
- color: var(--muted);
820
- }
821
-
822
- .result-list li {
823
- display: flex;
824
- flex-wrap: wrap;
825
- align-items: baseline;
826
- gap: 8px;
827
- }
828
-
829
- .result-arrow {
830
- display: inline-flex;
831
- align-items: baseline;
832
- gap: 4px;
833
- }
834
-
835
- .result-line {
836
- color: var(--muted);
837
- }
838
-
839
- .warn-list {
840
- color: var(--warn);
841
- }
842
-
843
- .callout {
844
- padding: 10px 12px;
845
- border-radius: 12px;
846
- font-size: 12.5px;
847
- line-height: 1.6;
848
- }
849
-
850
- .callout.warn {
851
- color: #ffe0ab;
852
- background: rgba(255, 194, 75, .10);
853
- border: 1px solid rgba(255, 194, 75, .30);
854
- }
855
-
856
- .callout.info {
857
- color: #c9d4ff;
858
- background: var(--accent-soft);
859
- border: 1px solid rgba(77, 107, 254, .35);
860
- }
861
-
862
- .raw {
863
- font-size: 12px;
864
- color: var(--muted);
865
- }
866
-
867
- .raw summary {
868
- cursor: pointer;
869
- }
870
-
871
- .pre {
872
- max-height: 260px;
873
- overflow: auto;
874
- margin: 8px 0 0;
875
- padding: 10px;
876
- background: rgba(0, 0, 0, .35);
877
- border: 1px solid var(--border);
878
- border-radius: 10px;
879
- font-size: 11.5px;
880
- line-height: 1.5;
881
- white-space: pre-wrap;
882
- word-break: break-word;
883
- }
884
-
885
- /* ------------------------------------------------------------------- forms */
886
-
887
- .form {
888
- display: grid;
889
- gap: 16px;
890
- }
891
-
892
- .field {
893
- display: grid;
894
- gap: 6px;
895
- /* Never stretch a control to fill a row: a taller neighbour must not change
896
- the height of this field's input. */
897
- align-content: start;
898
- }
899
-
900
- /* A row of fields plus a note that belongs to the row as a whole. */
901
- .field-block {
902
- display: grid;
903
- gap: 6px;
904
- }
905
-
906
- .field-row {
907
- display: grid;
908
- grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
909
- gap: 14px;
910
- align-items: start;
911
- }
912
-
913
- .label {
914
- color: var(--muted);
915
- font-size: 12.5px;
916
- font-weight: 600;
917
- letter-spacing: .04em;
918
- }
919
-
920
- .helper {
921
- color: var(--muted);
922
- font-size: 12px;
923
- line-height: 1.6;
924
- }
925
-
926
- .hint {
927
- margin: 0;
928
- color: var(--muted);
929
- font-size: 12px;
930
- line-height: 1.6;
931
- word-break: break-all;
932
- }
933
-
934
- .fieldset {
935
- display: grid;
936
- gap: 10px;
937
- margin: 0;
938
- padding: 12px 14px;
939
- border: 1px solid var(--border);
940
- border-radius: 14px;
941
- }
942
-
943
- .input,
944
- .select,
945
- .textarea {
946
- width: 100%;
947
- padding: 10px 12px;
948
- color: var(--text);
949
- background: rgba(4, 7, 14, .55);
950
- border: 1px solid var(--border);
951
- border-radius: 12px;
952
- font-size: 13px;
953
- transition: border-color .16s ease, box-shadow .16s ease;
954
- }
955
-
956
- .input::placeholder,
957
- .textarea::placeholder {
958
- color: rgba(152, 162, 189, .65);
959
- }
960
-
961
- .input:focus,
962
- .select:focus,
963
- .textarea:focus {
964
- border-color: rgba(77, 107, 254, .65);
965
- box-shadow: 0 0 0 3px var(--accent-soft);
966
- outline: none;
967
- }
968
-
969
- .textarea {
970
- resize: vertical;
971
- min-height: 76px;
972
- line-height: 1.6;
973
- }
974
-
975
- /* A <select> is intrinsically a little taller than an <input>; pin both to the
976
- same height so a row of controls lines up exactly. `box-sizing: border-box`
977
- is global, so this includes the padding and the border. */
978
- .input,
979
- .select {
980
- height: var(--control-height);
981
- line-height: 1.25;
982
- }
983
-
984
- .select {
985
- appearance: none;
986
- background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
987
- background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
988
- background-size: 5px 5px, 5px 5px;
989
- background-repeat: no-repeat;
990
- }
991
-
992
- .checks,
993
- .radios {
994
- display: flex;
995
- flex-wrap: wrap;
996
- gap: 10px 18px;
997
- }
998
-
999
- .checkbox,
1000
- .radio {
1001
- display: inline-flex;
1002
- align-items: center;
1003
- gap: 8px;
1004
- font-size: 13px;
1005
- cursor: pointer;
1006
- }
1007
-
1008
- .checkbox input,
1009
- .radio input {
1010
- width: 16px;
1011
- height: 16px;
1012
- accent-color: var(--accent);
1013
- cursor: pointer;
1014
- }
1015
-
1016
- .radio.disabled {
1017
- opacity: .55;
1018
- cursor: not-allowed;
1019
- }
1020
-
1021
- .radio.disabled input {
1022
- cursor: not-allowed;
1023
- }
1024
-
1025
- .checkbox.solo {
1026
- padding: 10px 12px;
1027
- background: rgba(255, 255, 255, .03);
1028
- border: 1px solid var(--border);
1029
- border-radius: 12px;
1030
- }
1031
-
1032
- .form-error {
1033
- padding: 10px 12px;
1034
- color: #ffd7dc;
1035
- background: rgba(255, 107, 129, .12);
1036
- border: 1px solid rgba(255, 107, 129, .40);
1037
- border-radius: 12px;
1038
- font-size: 12.5px;
1039
- line-height: 1.6;
1040
- }
1041
-
1042
- .status-strip {
1043
- display: flex;
1044
- align-items: center;
1045
- gap: 10px;
1046
- padding: 8px 12px;
1047
- color: var(--muted);
1048
- background: rgba(255, 255, 255, .04);
1049
- border: 1px solid var(--border);
1050
- border-radius: 12px;
1051
- font-size: 12.5px;
1052
- }
1053
-
1054
- .spec-forms {
1055
- margin: 4px 0 0;
1056
- padding-left: 18px;
1057
- color: var(--muted);
1058
- font-size: 12px;
1059
- line-height: 1.75;
1060
- }
1061
-
1062
- .code {
1063
- padding: 1px 6px;
1064
- background: rgba(255, 255, 255, .07);
1065
- border: 1px solid var(--border);
1066
- border-radius: 6px;
1067
- font-size: 12px;
1068
- }
1069
-
1070
- /* ------------------------------------------------------------------ modals */
1071
-
1072
- .scrim {
1073
- position: fixed;
1074
- inset: 0;
1075
- z-index: 50;
1076
- display: grid;
1077
- place-items: center;
1078
- padding: 24px;
1079
- overflow-y: auto;
1080
- background: rgba(3, 5, 10, .62);
1081
- backdrop-filter: blur(6px);
1082
- -webkit-backdrop-filter: blur(6px);
1083
- }
1084
-
1085
- .modal {
1086
- display: flex;
1087
- flex-direction: column;
1088
- width: min(680px, 100%);
1089
- max-height: 88vh;
1090
- overflow: hidden;
1091
- background: linear-gradient(160deg, rgba(255, 255, 255, .105), rgba(255, 255, 255, .045));
1092
- backdrop-filter: blur(22px) saturate(150%);
1093
- -webkit-backdrop-filter: blur(22px) saturate(150%);
1094
- border: 1px solid var(--border-strong);
1095
- border-radius: 22px;
1096
- box-shadow: 0 30px 80px rgba(0, 0, 0, .60), inset 0 1px 0 rgba(255, 255, 255, .08);
1097
- }
1098
-
1099
- .modal-head {
1100
- display: flex;
1101
- align-items: center;
1102
- justify-content: space-between;
1103
- gap: 12px;
1104
- padding: 18px 22px;
1105
- border-bottom: 1px solid var(--border);
1106
- }
1107
-
1108
- .modal-title {
1109
- margin: 0;
1110
- font-size: 16px;
1111
- font-weight: 650;
1112
- word-break: break-all;
1113
- }
1114
-
1115
- .modal-body {
1116
- overflow-y: auto;
1117
- padding: 20px 22px 22px;
1118
- }
1119
-
1120
- .modal-foot {
1121
- display: flex;
1122
- justify-content: flex-end;
1123
- gap: 10px;
1124
- padding-top: 4px;
1125
- }
1126
-
1127
- /* ------------------------------------------------------------ empty/loading */
1128
-
1129
- .empty {
1130
- padding: 28px;
1131
- color: var(--muted);
1132
- border: 1px dashed var(--border);
1133
- border-radius: var(--radius);
1134
- text-align: center;
1135
- }
1136
-
1137
- .empty-title {
1138
- margin: 0 0 6px;
1139
- color: var(--text);
1140
- font-weight: 650;
1141
- }
1142
-
1143
- .auth-screen {
1144
- min-height: 100vh;
1145
- display: grid;
1146
- place-items: center;
1147
- padding: 24px;
1148
- }
1149
-
1150
- .auth-card {
1151
- display: grid;
1152
- gap: 14px;
1153
- justify-items: start;
1154
- max-width: 640px;
1155
- padding: 30px;
1156
- }
1157
-
1158
- .auth-title {
1159
- margin: 0;
1160
- font-size: 19px;
1161
- font-weight: 680;
1162
- }
1163
-
1164
- .auth-body {
1165
- display: grid;
1166
- gap: 12px;
1167
- color: var(--text);
1168
- font-size: 13.5px;
1169
- line-height: 1.75;
1170
- }
1171
-
1172
- .loading-bar {
1173
- width: 100%;
1174
- max-width: 260px;
1175
- height: 3px;
1176
- overflow: hidden;
1177
- background: rgba(255, 255, 255, .08);
1178
- border-radius: 999px;
1179
- }
1180
-
1181
- .loading-bar span {
1182
- display: block;
1183
- width: 40%;
1184
- height: 100%;
1185
- background: linear-gradient(90deg, var(--accent), var(--cyan));
1186
- border-radius: 999px;
1187
- animation: loading-slide 1.2s ease-in-out infinite;
1188
- }
1189
-
1190
- @keyframes loading-slide {
1191
- 0% { transform: translateX(-60%); }
1192
- 100% { transform: translateX(260%); }
1193
- }
1194
-
1195
- /* -------------------------------------------------------------- scrollbars */
1196
-
1197
- .job-log::-webkit-scrollbar,
1198
- .modal-body::-webkit-scrollbar,
1199
- .pre::-webkit-scrollbar,
1200
- .scrim::-webkit-scrollbar {
1201
- width: 10px;
1202
- height: 10px;
1203
- }
1204
-
1205
- .job-log::-webkit-scrollbar-thumb,
1206
- .modal-body::-webkit-scrollbar-thumb,
1207
- .pre::-webkit-scrollbar-thumb,
1208
- .scrim::-webkit-scrollbar-thumb {
1209
- background: rgba(255, 255, 255, .14);
1210
- background-clip: content-box;
1211
- border: 3px solid transparent;
1212
- border-radius: 999px;
1213
- }
1214
-
1215
- .job-log::-webkit-scrollbar-track,
1216
- .modal-body::-webkit-scrollbar-track,
1217
- .pre::-webkit-scrollbar-track,
1218
- .scrim::-webkit-scrollbar-track {
1219
- background: transparent;
1220
- }
1221
-
1222
- /* -------------------------------------------------------------- settings -- */
1223
-
1224
- .settings-block {
1225
- display: grid;
1226
- gap: 10px;
1227
- padding: 14px;
1228
- background: rgba(255, 255, 255, .02);
1229
- border: 1px solid var(--border);
1230
- border-radius: 14px;
1231
- }
1232
-
1233
- .settings-title {
1234
- margin: 0;
1235
- font-size: 13.5px;
1236
- font-weight: 650;
1237
- }
1238
-
1239
- .settings-current {
1240
- margin: 0;
1241
- color: var(--muted);
1242
- font-size: 12.5px;
1243
- word-break: break-all;
1244
- }
1245
-
1246
- .settings-row {
1247
- display: flex;
1248
- flex-wrap: wrap;
1249
- align-items: flex-end;
1250
- gap: 10px;
1251
- }
1252
-
1253
- .settings-row > .field {
1254
- flex: 1 1 200px;
1255
- }
1256
-
1257
- .settings-row > .input {
1258
- flex: 1 1 240px;
1259
- }
1260
-
1261
- .settings-provider {
1262
- display: grid;
1263
- gap: 10px;
1264
- padding-top: 12px;
1265
- border-top: 1px dashed var(--border);
1266
- }
1267
-
1268
- .settings-provider-head {
1269
- display: flex;
1270
- align-items: center;
1271
- flex-wrap: wrap;
1272
- gap: 10px;
1273
- }
1274
-
1275
- .settings-actions {
1276
- display: flex;
1277
- justify-content: flex-end;
1278
- gap: 8px;
1279
- }
1280
-
1281
- /* The header chips now carry a small action button. */
1282
- .meta-chip .btn.small {
1283
- justify-self: start;
1284
- padding: 3px 10px;
1285
- }
1286
-
1287
- .provider-hint .btn.small {
1288
- margin: 0 6px;
1289
- vertical-align: middle;
1290
- }
1291
-
1292
- /* --------------------------------------------------------------- responsive */
1293
-
1294
- @media (min-width: 861px) {
1295
- .topbar {
1296
- grid-template-columns: minmax(0, 1fr) auto;
1297
- align-items: center;
1298
- }
1299
- }
1300
-
1301
- @media (max-width: 860px) {
1302
- .app {
1303
- padding: 18px 14px 30px;
1304
- }
1305
-
1306
- /* One column per row: the link, modules and actions stack instead of
1307
- squeezing into a single line. */
1308
- .env-row-main {
1309
- grid-template-columns: minmax(0, 1fr);
1310
- align-items: start;
1311
- gap: 6px;
1312
- }
1313
-
1314
- .env-actions {
1315
- justify-content: flex-start;
1316
- }
1317
-
1318
- .env-chips {
1319
- max-height: none;
1320
- }
1321
-
1322
- .job-time {
1323
- margin-left: 0;
1324
- }
1325
-
1326
- .log-line {
1327
- grid-template-columns: 56px minmax(0, 1fr);
1328
- }
1329
-
1330
- .scrim {
1331
- padding: 14px;
1332
- }
1333
-
1334
- .modal {
1335
- max-height: 92vh;
1336
- }
1337
- }
1338
-
1339
- @media (prefers-reduced-motion: reduce) {
1340
- *,
1341
- *::before,
1342
- *::after {
1343
- animation-duration: .01ms !important;
1344
- animation-iteration-count: 1 !important;
1345
- transition-duration: .01ms !important;
1346
- }
1347
- }
1348
-
1349
- /* ------------------------------------------- environment configuration -- */
1350
-
1351
- /* The per-environment "完整配置" dialog (opened from the 配置 row button): the
1352
- summary grid, the manifest module table and the foldable Wyn.conf lists.
1353
- Everything is introduced here and prefixed `config-`, so the rest of the page
1354
- is untouched. */
1355
- .config-view {
1356
- display: grid;
1357
- gap: 14px;
1358
- }
1359
-
1360
- /* This dialog is a data view (a five-column module table and long paths), so it
1361
- gets a wider modal than the compact action dialogs. */
1362
- .modal.modal-wide {
1363
- width: min(1080px, 94vw);
1364
- }
1365
-
1366
- .config-table {
1367
- table-layout: fixed;
1368
- }
1369
-
1370
- /* 模块 · 版本 · 来源 · 部署目标 · 校验 */
1371
- .config-table th:nth-child(1), .config-table td:nth-child(1) { width: 20%; }
1372
- .config-table th:nth-child(2), .config-table td:nth-child(2) { width: 10%; }
1373
- .config-table th:nth-child(3), .config-table td:nth-child(3) { width: 18%; }
1374
- .config-table th:nth-child(4), .config-table td:nth-child(4) { width: 36%; }
1375
- .config-table th:nth-child(5), .config-table td:nth-child(5) { width: 16%; }
1376
-
1377
- .config-table td:nth-child(2) { white-space: nowrap; font-variant-numeric: tabular-nums; }
1378
- .config-table td:nth-child(4) { overflow-wrap: anywhere; }
1379
-
1380
- /* A path, artifact name or digest may be long: wrap it instead of clipping it. */
1381
- .config-sub, .config-field-value, .config-digest { overflow-wrap: anywhere; }
1382
- .config-digest { white-space: nowrap; }
1383
-
1384
- .config-block {
1385
- display: grid;
1386
- gap: 10px;
1387
- padding: 14px;
1388
- background: rgba(255, 255, 255, .02);
1389
- border: 1px solid var(--border);
1390
- border-radius: 14px;
1391
- }
1392
-
1393
- .config-title {
1394
- margin: 0;
1395
- font-size: 13.5px;
1396
- font-weight: 650;
1397
- }
1398
-
1399
- .config-grid {
1400
- display: grid;
1401
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1402
- gap: 8px 16px;
1403
- }
1404
-
1405
- .config-field {
1406
- display: grid;
1407
- grid-template-columns: 96px minmax(0, 1fr);
1408
- align-items: baseline;
1409
- gap: 8px;
1410
- min-width: 0;
1411
- font-size: 12.5px;
1412
- }
1413
-
1414
- .config-field-label {
1415
- color: var(--muted);
1416
- }
1417
-
1418
- .config-field-value {
1419
- min-width: 0;
1420
- word-break: break-all;
1421
- }
1422
-
1423
- .config-site {
1424
- color: var(--cyan);
1425
- }
1426
-
1427
- .config-site.muted {
1428
- color: var(--muted);
1429
- }
1430
-
1431
- /* A value with its inline action button (copy the address, open the directory). */
1432
- .config-inline {
1433
- display: inline-flex;
1434
- align-items: center;
1435
- flex-wrap: wrap;
1436
- gap: 8px;
1437
- min-width: 0;
1438
- }
1439
-
1440
- .config-sublist {
1441
- display: grid;
1442
- gap: 6px;
1443
- }
1444
-
1445
- .config-sublist-label {
1446
- color: var(--muted);
1447
- font-size: 12px;
1448
- }
1449
-
1450
- /* A scroll box keeps a wide module table from stretching the dialog. */
1451
- .config-table-wrap {
1452
- overflow-x: auto;
1453
- border: 1px solid var(--border);
1454
- border-radius: 12px;
1455
- }
1456
-
1457
- .config-table {
1458
- width: 100%;
1459
- border-collapse: collapse;
1460
- font-size: 12.5px;
1461
- }
1462
-
1463
- .config-table th {
1464
- padding: 8px 10px;
1465
- color: var(--muted);
1466
- font-weight: 600;
1467
- text-align: left;
1468
- white-space: nowrap;
1469
- background: rgba(255, 255, 255, .03);
1470
- border-bottom: 1px solid var(--border);
1471
- }
1472
-
1473
- .config-table td {
1474
- padding: 8px 10px;
1475
- vertical-align: top;
1476
- border-bottom: 1px solid rgba(255, 255, 255, .06);
1477
- }
1478
-
1479
- .config-table tr:last-child td {
1480
- border-bottom: none;
1481
- }
1482
-
1483
- .config-module-cell {
1484
- display: grid;
1485
- gap: 4px;
1486
- min-width: 140px;
1487
- }
1488
-
1489
- .config-module-tags {
1490
- display: flex;
1491
- flex-wrap: wrap;
1492
- gap: 4px;
1493
- }
1494
-
1495
- /* A second, quieter line inside a cell (assembly, artifact path, override path). */
1496
- .config-sub {
1497
- display: block;
1498
- max-width: 280px;
1499
- color: var(--muted);
1500
- font-size: 11px;
1501
- line-height: 1.45;
1502
- word-break: break-all;
1503
- }
1504
-
1505
- .chip-tag.source-remote {
1506
- color: #c9d4ff;
1507
- border-color: rgba(77, 107, 254, .35);
1508
- }
1509
-
1510
- .chip-tag.source-local {
1511
- color: var(--cyan);
1512
- background: rgba(53, 208, 232, .12);
1513
- border-color: rgba(53, 208, 232, .35);
1514
- }
1515
-
1516
- /* A worker is a mapping of its own: the chip makes it stand out in the table. */
1517
- .chip-tag.source-worker {
1518
- color: var(--ok);
1519
- background: rgba(61, 220, 151, .12);
1520
- border-color: rgba(61, 220, 151, .35);
1521
- }
1522
-
1523
- /* One line per manifest mapping, directly under the module name. */
1524
- .config-mappings {
1525
- display: grid;
1526
- gap: 3px;
1527
- min-width: 0;
1528
- }
1529
-
1530
- .config-mappings-label {
1531
- color: var(--muted);
1532
- font-size: 11px;
1533
- }
1534
-
1535
- .config-mapping {
1536
- display: flex;
1537
- flex-wrap: wrap;
1538
- align-items: baseline;
1539
- gap: 4px;
1540
- min-width: 0;
1541
- }
1542
-
1543
- .config-mapping-text {
1544
- max-width: 300px;
1545
- font-size: 11.5px;
1546
- line-height: 1.45;
1547
- word-break: break-all;
1548
- }
1549
-
1550
- .config-sep {
1551
- color: var(--muted);
1552
- }
1553
-
1554
- .config-mappings-fold summary {
1555
- cursor: pointer;
1556
- }
1557
-
1558
- .config-mappings-fold .config-mappings {
1559
- margin-top: 6px;
1560
- }
1561
-
1562
- .config-digest {
1563
- color: var(--muted);
1564
- white-space: nowrap;
1565
- }
1566
-
1567
- .config-overrides,
1568
- .config-services,
1569
- .config-paths {
1570
- display: grid;
1571
- gap: 6px;
1572
- margin: 0;
1573
- padding: 0;
1574
- list-style: none;
1575
- }
1576
-
1577
- .config-override,
1578
- .config-service {
1579
- display: flex;
1580
- flex-wrap: wrap;
1581
- align-items: baseline;
1582
- gap: 8px;
1583
- min-width: 0;
1584
- }
1585
-
1586
- .config-override .config-sub {
1587
- max-width: 100%;
1588
- }
1589
-
1590
- .config-paths {
1591
- max-height: 200px;
1592
- overflow: auto;
1593
- margin-top: 8px;
1594
- padding: 10px;
1595
- background: rgba(0, 0, 0, .35);
1596
- border: 1px solid var(--border);
1597
- border-radius: 10px;
1598
- font-size: 11.5px;
1599
- }
1600
-
1601
- .config-paths li {
1602
- word-break: break-all;
1603
- }
1604
-
1605
- .config-collapse summary {
1606
- cursor: pointer;
1607
- }
1608
-
1609
- @media (max-width: 620px) {
1610
- /* One line per field: the label no longer squeezes the value column. */
1611
- .config-field {
1612
- grid-template-columns: minmax(0, 1fr);
1613
- gap: 2px;
1614
- }
1615
-
1616
- .config-sub {
1617
- max-width: none;
1618
- }
1619
- }
1
+ /* AFM Environment Manager — dark glassmorphism theme.
2
+ Everything is class-driven: the page is served under a strict CSP that forbids
3
+ inline <style> and style="" attributes. */
4
+
5
+ :root {
6
+ --bg: #05070d;
7
+ --glass: rgba(255, 255, 255, .055);
8
+ --glass-strong: rgba(255, 255, 255, .09);
9
+ --border: rgba(255, 255, 255, .10);
10
+ --border-strong: rgba(255, 255, 255, .18);
11
+ --text: #eaeefb;
12
+ --muted: #98a2bd;
13
+ --accent: #4d6bfe;
14
+ --accent-soft: rgba(77, 107, 254, .16);
15
+ --cyan: #35d0e8;
16
+ --ok: #3ddc97;
17
+ --warn: #ffc24b;
18
+ --err: #ff6b81;
19
+ --radius: 18px;
20
+ /* One height for every single-line control, so a <select> and an <input> in
21
+ the same row cannot disagree. */
22
+ --control-height: 38px;
23
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
24
+ --panel: linear-gradient(160deg, var(--glass-strong), var(--glass));
25
+ --panel-shadow: 0 18px 50px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .06);
26
+ }
27
+
28
+ *,
29
+ *::before,
30
+ *::after {
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ html,
35
+ body {
36
+ margin: 0;
37
+ padding: 0;
38
+ }
39
+
40
+ body {
41
+ min-height: 100vh;
42
+ background:
43
+ radial-gradient(900px 520px at 12% -8%, rgba(77, 107, 254, .30), transparent 60%),
44
+ radial-gradient(800px 480px at 88% -4%, rgba(53, 208, 232, .20), transparent 55%),
45
+ var(--bg);
46
+ background-repeat: no-repeat;
47
+ background-attachment: fixed;
48
+ color: var(--text);
49
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif;
50
+ font-size: 14px;
51
+ line-height: 1.55;
52
+ -webkit-font-smoothing: antialiased;
53
+ text-rendering: optimizeLegibility;
54
+ }
55
+
56
+ h1,
57
+ h2,
58
+ h3 {
59
+ line-height: 1.25;
60
+ }
61
+
62
+ p {
63
+ margin: 0;
64
+ }
65
+
66
+ button {
67
+ font: inherit;
68
+ }
69
+
70
+ code,
71
+ .mono,
72
+ pre {
73
+ font-family: var(--mono);
74
+ }
75
+
76
+ a {
77
+ color: var(--cyan);
78
+ }
79
+
80
+ ::selection {
81
+ background: var(--accent-soft);
82
+ }
83
+
84
+ :focus-visible {
85
+ outline: 2px solid rgba(77, 107, 254, .4);
86
+ outline-offset: 2px;
87
+ border-radius: 10px;
88
+ }
89
+
90
+ .sr-only {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ padding: 0;
95
+ margin: -1px;
96
+ overflow: hidden;
97
+ clip: rect(0 0 0 0);
98
+ white-space: nowrap;
99
+ border: 0;
100
+ }
101
+
102
+ .muted { color: var(--muted); }
103
+ .small { font-size: 12px; }
104
+ .strong { font-weight: 650; }
105
+ .err-text { color: var(--err); }
106
+ .warn-text { color: var(--warn); }
107
+ .danger-text { color: #ffd7dc; }
108
+
109
+ /* ------------------------------------------------------------------ layout */
110
+
111
+ .app {
112
+ min-height: 100vh;
113
+ max-width: 1440px;
114
+ margin: 0 auto;
115
+ padding: 26px 22px 40px;
116
+ display: grid;
117
+ gap: 18px;
118
+ align-content: start;
119
+ }
120
+
121
+ .app-main {
122
+ display: grid;
123
+ gap: 18px;
124
+ }
125
+
126
+ .app-footer {
127
+ display: flex;
128
+ flex-wrap: wrap;
129
+ gap: 8px 16px;
130
+ justify-content: space-between;
131
+ padding: 4px 2px;
132
+ color: var(--muted);
133
+ font-size: 11.5px;
134
+ }
135
+
136
+ .toolbar {
137
+ display: flex;
138
+ flex-wrap: wrap;
139
+ align-items: center;
140
+ gap: 12px;
141
+ }
142
+
143
+ /* ------------------------------------------------------------------ header */
144
+
145
+ .topbar {
146
+ display: grid;
147
+ gap: 14px;
148
+ padding: 18px 20px;
149
+ background: var(--panel);
150
+ backdrop-filter: blur(18px) saturate(140%);
151
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
152
+ border: 1px solid var(--border);
153
+ border-radius: var(--radius);
154
+ box-shadow: var(--panel-shadow);
155
+ }
156
+
157
+ .brand {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 12px;
161
+ min-width: 0;
162
+ }
163
+
164
+ .brand-mark {
165
+ flex: 0 0 auto;
166
+ display: grid;
167
+ place-items: center;
168
+ width: 42px;
169
+ height: 42px;
170
+ border-radius: 13px;
171
+ background: linear-gradient(135deg, rgba(77, 107, 254, .95), rgba(53, 208, 232, .8));
172
+ color: #f8faff;
173
+ font-weight: 700;
174
+ font-size: 13px;
175
+ letter-spacing: .04em;
176
+ box-shadow: 0 10px 24px rgba(77, 107, 254, .35);
177
+ }
178
+
179
+ .brand-mark.big {
180
+ width: 56px;
181
+ height: 56px;
182
+ border-radius: 17px;
183
+ font-size: 15px;
184
+ }
185
+
186
+ .brand-name {
187
+ margin: 0;
188
+ font-size: 18px;
189
+ font-weight: 680;
190
+ letter-spacing: .01em;
191
+ }
192
+
193
+ .brand-sub {
194
+ margin: 4px 0 0;
195
+ color: var(--muted);
196
+ font-size: 12.5px;
197
+ }
198
+
199
+ .topbar-meta {
200
+ display: flex;
201
+ flex-wrap: wrap;
202
+ gap: 10px;
203
+ }
204
+
205
+ .meta-chip {
206
+ display: grid;
207
+ gap: 4px;
208
+ min-width: 150px;
209
+ padding: 8px 12px;
210
+ background: rgba(255, 255, 255, .04);
211
+ border: 1px solid var(--border);
212
+ border-radius: 12px;
213
+ font-size: 12px;
214
+ }
215
+
216
+ .meta-label {
217
+ color: var(--muted);
218
+ font-size: 11px;
219
+ letter-spacing: .06em;
220
+ text-transform: uppercase;
221
+ }
222
+
223
+ .meta-value {
224
+ word-break: break-all;
225
+ }
226
+
227
+ .providers {
228
+ display: flex;
229
+ gap: 8px;
230
+ }
231
+
232
+ .provider-badge {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ gap: 6px;
236
+ padding: 3px 10px;
237
+ border: 1px solid var(--border);
238
+ border-radius: 999px;
239
+ font-size: 11.5px;
240
+ letter-spacing: .06em;
241
+ text-transform: uppercase;
242
+ }
243
+
244
+ .provider-badge .dot {
245
+ width: 7px;
246
+ height: 7px;
247
+ border-radius: 50%;
248
+ background: currentColor;
249
+ box-shadow: 0 0 8px currentColor;
250
+ }
251
+
252
+ .provider-badge.on {
253
+ color: var(--ok);
254
+ border-color: rgba(61, 220, 151, .40);
255
+ background: rgba(61, 220, 151, .12);
256
+ }
257
+
258
+ .provider-badge.off {
259
+ color: var(--warn);
260
+ border-color: rgba(255, 194, 75, .35);
261
+ background: rgba(255, 194, 75, .10);
262
+ }
263
+
264
+ .provider-hint {
265
+ grid-column: 1 / -1;
266
+ margin: 0;
267
+ padding: 10px 12px;
268
+ color: #ffe0ab;
269
+ background: rgba(255, 194, 75, .09);
270
+ border: 1px solid rgba(255, 194, 75, .28);
271
+ border-radius: 12px;
272
+ font-size: 12.5px;
273
+ line-height: 1.7;
274
+ }
275
+
276
+ .provider-hint .code {
277
+ margin: 0 4px;
278
+ }
279
+
280
+ /* ----------------------------------------------------------------- banners */
281
+
282
+ .banner {
283
+ display: flex;
284
+ align-items: center;
285
+ justify-content: space-between;
286
+ gap: 12px;
287
+ padding: 12px 16px;
288
+ background: var(--panel);
289
+ backdrop-filter: blur(18px) saturate(140%);
290
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
291
+ border: 1px solid var(--border);
292
+ border-radius: 14px;
293
+ box-shadow: 0 12px 34px rgba(0, 0, 0, .35);
294
+ }
295
+
296
+ .banner.error {
297
+ border-color: rgba(255, 107, 129, .42);
298
+ background: linear-gradient(160deg, rgba(255, 107, 129, .16), rgba(255, 107, 129, .07));
299
+ }
300
+
301
+ .banner.success {
302
+ border-color: rgba(61, 220, 151, .40);
303
+ background: linear-gradient(160deg, rgba(61, 220, 151, .14), rgba(61, 220, 151, .06));
304
+ }
305
+
306
+ .banner.info {
307
+ border-color: rgba(77, 107, 254, .40);
308
+ background: linear-gradient(160deg, rgba(77, 107, 254, .16), rgba(77, 107, 254, .06));
309
+ }
310
+
311
+ .banner-text {
312
+ font-size: 13px;
313
+ line-height: 1.6;
314
+ }
315
+
316
+ .banner-actions {
317
+ display: flex;
318
+ flex: 0 0 auto;
319
+ gap: 8px;
320
+ }
321
+
322
+ /* ------------------------------------------------------------------ panels */
323
+
324
+ .panel {
325
+ display: grid;
326
+ gap: 14px;
327
+ padding: 18px 20px;
328
+ background: var(--panel);
329
+ backdrop-filter: blur(18px) saturate(140%);
330
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
331
+ border: 1px solid var(--border);
332
+ border-radius: var(--radius);
333
+ box-shadow: var(--panel-shadow);
334
+ }
335
+
336
+ .panel-head {
337
+ display: flex;
338
+ align-items: center;
339
+ gap: 10px;
340
+ }
341
+
342
+ .panel-title {
343
+ margin: 0;
344
+ font-size: 15px;
345
+ font-weight: 650;
346
+ }
347
+
348
+ .count {
349
+ padding: 1px 8px;
350
+ color: var(--muted);
351
+ background: rgba(255, 255, 255, .05);
352
+ border: 1px solid var(--border);
353
+ border-radius: 999px;
354
+ font-size: 11.5px;
355
+ }
356
+
357
+ .panel-actions {
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 8px;
361
+ margin-left: auto;
362
+ }
363
+
364
+ /* ----------------------------------------------------------------- buttons */
365
+
366
+ .btn {
367
+ display: inline-flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ gap: 6px;
371
+ padding: 8px 14px;
372
+ color: var(--text);
373
+ background: linear-gradient(160deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .045));
374
+ border: 1px solid var(--border-strong);
375
+ border-radius: 10px;
376
+ font-size: 13px;
377
+ font-weight: 500;
378
+ cursor: pointer;
379
+ transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease, background-color .16s ease;
380
+ }
381
+
382
+ .btn:hover:not(:disabled) {
383
+ transform: translateY(-1px);
384
+ border-color: rgba(255, 255, 255, .30);
385
+ }
386
+
387
+ .btn:active:not(:disabled) {
388
+ transform: translateY(0);
389
+ }
390
+
391
+ .btn.primary {
392
+ background: linear-gradient(135deg, var(--accent), #3f7ef7);
393
+ border-color: rgba(155, 175, 255, .45);
394
+ box-shadow: 0 10px 26px rgba(77, 107, 254, .35);
395
+ }
396
+
397
+ .btn.primary:hover:not(:disabled) {
398
+ box-shadow: 0 14px 30px rgba(77, 107, 254, .45);
399
+ }
400
+
401
+ .btn.danger {
402
+ color: #ffd9de;
403
+ background: linear-gradient(160deg, rgba(255, 107, 129, .22), rgba(255, 107, 129, .12));
404
+ border-color: rgba(255, 107, 129, .45);
405
+ }
406
+
407
+ .btn.ghost {
408
+ background: transparent;
409
+ border-color: var(--border);
410
+ }
411
+
412
+ .btn.small {
413
+ padding: 6px 10px;
414
+ border-radius: 9px;
415
+ font-size: 12px;
416
+ }
417
+
418
+ .btn.tiny {
419
+ padding: 2px 8px;
420
+ font-size: 11px;
421
+ line-height: 1.5;
422
+ border-radius: 8px;
423
+ }
424
+
425
+ .btn:disabled {
426
+ opacity: .45;
427
+ cursor: not-allowed;
428
+ transform: none;
429
+ box-shadow: none;
430
+ }
431
+
432
+ /* ------------------------------------------------------------------- pills */
433
+
434
+ .pill {
435
+ display: inline-flex;
436
+ align-items: center;
437
+ padding: 3px 10px;
438
+ border: 1px solid var(--border-strong);
439
+ border-radius: 999px;
440
+ font-size: 10.5px;
441
+ font-weight: 650;
442
+ letter-spacing: .09em;
443
+ text-transform: uppercase;
444
+ white-space: nowrap;
445
+ }
446
+
447
+ .pill.s-ok,
448
+ .pill.s-job-succeeded {
449
+ color: var(--ok);
450
+ background: rgba(61, 220, 151, .14);
451
+ border-color: rgba(61, 220, 151, .38);
452
+ }
453
+
454
+ .pill.s-accent,
455
+ .pill.s-job-running {
456
+ color: #b9c6ff;
457
+ background: var(--accent-soft);
458
+ border-color: rgba(77, 107, 254, .45);
459
+ }
460
+
461
+ .pill.s-warn,
462
+ .pill.s-job-queued {
463
+ color: var(--warn);
464
+ background: rgba(255, 194, 75, .14);
465
+ border-color: rgba(255, 194, 75, .38);
466
+ }
467
+
468
+ .pill.s-warn-muted {
469
+ color: var(--warn);
470
+ background: rgba(255, 194, 75, .08);
471
+ border-color: rgba(255, 194, 75, .22);
472
+ }
473
+
474
+ .pill.s-err,
475
+ .pill.s-job-failed {
476
+ color: var(--err);
477
+ background: rgba(255, 107, 129, .14);
478
+ border-color: rgba(255, 107, 129, .40);
479
+ }
480
+
481
+ .pill.s-muted {
482
+ color: var(--muted);
483
+ background: rgba(255, 255, 255, .05);
484
+ border-color: var(--border-strong);
485
+ }
486
+
487
+ /* ---------------------------------------------------------- environments */
488
+
489
+ /* One compact row per environment instead of a card grid: every row shows the
490
+ same columns, so a long list stays scannable and uses the width. */
491
+ .env-list {
492
+ display: grid;
493
+ gap: 0;
494
+ margin: 0;
495
+ padding: 0;
496
+ list-style: none;
497
+ }
498
+
499
+ .env-row {
500
+ display: grid;
501
+ gap: 4px;
502
+ padding: 12px 4px;
503
+ border-top: 1px solid var(--border);
504
+ transition: background-color .16s ease;
505
+ }
506
+
507
+ .env-row:first-child {
508
+ border-top: none;
509
+ }
510
+
511
+ .env-row:hover {
512
+ background: rgba(255, 255, 255, .028);
513
+ }
514
+
515
+ .env-row-main {
516
+ display: grid;
517
+ grid-template-columns: minmax(170px, 1fr) minmax(200px, auto) auto minmax(180px, 2fr) auto;
518
+ align-items: center;
519
+ gap: 10px 16px;
520
+ }
521
+
522
+ .env-id {
523
+ display: flex;
524
+ align-items: center;
525
+ gap: 8px;
526
+ min-width: 0;
527
+ }
528
+
529
+ .env-name {
530
+ font-size: 14.5px;
531
+ font-weight: 650;
532
+ overflow: hidden;
533
+ text-overflow: ellipsis;
534
+ white-space: nowrap;
535
+ }
536
+
537
+ .env-url {
538
+ display: inline-flex;
539
+ align-items: center;
540
+ gap: 6px;
541
+ min-width: 0;
542
+ }
543
+
544
+ .env-site {
545
+ color: var(--cyan);
546
+ font-size: 12.5px;
547
+ text-decoration: none;
548
+ border-bottom: 1px dashed rgba(53, 208, 232, .35);
549
+ white-space: nowrap;
550
+ }
551
+
552
+ .env-site:hover {
553
+ color: #8ee7f5;
554
+ border-bottom-color: rgba(142, 231, 245, .7);
555
+ }
556
+
557
+ .env-site.muted {
558
+ color: var(--muted);
559
+ border-bottom-color: var(--border);
560
+ }
561
+
562
+ .env-meta {
563
+ color: var(--muted);
564
+ font-size: 12px;
565
+ white-space: nowrap;
566
+ }
567
+
568
+ .env-chips {
569
+ min-width: 0;
570
+ overflow: hidden;
571
+ max-height: 22px;
572
+ }
573
+
574
+ .env-row-sub {
575
+ display: flex;
576
+ flex-wrap: wrap;
577
+ align-items: center;
578
+ gap: 6px 14px;
579
+ min-width: 0;
580
+ font-size: 11.5px;
581
+ }
582
+
583
+ .env-root {
584
+ margin: 0;
585
+ color: var(--muted);
586
+ font-size: 11.5px;
587
+ overflow: hidden;
588
+ text-overflow: ellipsis;
589
+ white-space: nowrap;
590
+ max-width: 100%;
591
+ }
592
+
593
+ .env-problem {
594
+ margin: 0;
595
+ padding: 2px 8px;
596
+ color: #ffd7dc;
597
+ background: rgba(255, 107, 129, .10);
598
+ border: 1px solid rgba(255, 107, 129, .30);
599
+ border-radius: 8px;
600
+ font-size: 11.5px;
601
+ line-height: 1.6;
602
+ word-break: break-word;
603
+ }
604
+
605
+ .env-actions {
606
+ display: flex;
607
+ flex-wrap: wrap;
608
+ justify-content: flex-end;
609
+ gap: 6px;
610
+ }
611
+
612
+ .card {
613
+ background: var(--panel);
614
+ backdrop-filter: blur(18px) saturate(140%);
615
+ -webkit-backdrop-filter: blur(18px) saturate(140%);
616
+ border: 1px solid var(--border);
617
+ border-radius: var(--radius);
618
+ box-shadow: var(--panel-shadow);
619
+ transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
620
+ }
621
+
622
+ /* ------------------------------------------------------------------- chips */
623
+
624
+ .chips {
625
+ display: flex;
626
+ flex-wrap: wrap;
627
+ align-items: center;
628
+ gap: 6px;
629
+ }
630
+
631
+ .chips.inline {
632
+ display: inline-flex;
633
+ }
634
+
635
+ .chip-tag {
636
+ display: inline-flex;
637
+ align-items: center;
638
+ padding: 2px 9px;
639
+ color: var(--text);
640
+ background: rgba(255, 255, 255, .06);
641
+ border: 1px solid var(--border);
642
+ border-radius: 999px;
643
+ font-size: 11.5px;
644
+ font-family: var(--mono);
645
+ }
646
+
647
+ .chip-tag.local {
648
+ color: var(--cyan);
649
+ background: rgba(53, 208, 232, .12);
650
+ border-color: rgba(53, 208, 232, .35);
651
+ letter-spacing: .04em;
652
+ text-transform: uppercase;
653
+ }
654
+
655
+ .chip-tag.warn {
656
+ color: var(--warn);
657
+ background: rgba(255, 194, 75, .12);
658
+ border-color: rgba(255, 194, 75, .35);
659
+ }
660
+
661
+ .chip-row {
662
+ display: flex;
663
+ flex-wrap: wrap;
664
+ gap: 8px;
665
+ margin-top: 2px;
666
+ }
667
+
668
+ .chip {
669
+ padding: 4px 11px;
670
+ color: var(--text);
671
+ background: rgba(255, 255, 255, .06);
672
+ border: 1px solid var(--border);
673
+ border-radius: 999px;
674
+ font-family: var(--mono);
675
+ font-size: 12px;
676
+ cursor: pointer;
677
+ transition: transform .14s ease, border-color .14s ease, background-color .14s ease;
678
+ }
679
+
680
+ .chip:hover:not(:disabled) {
681
+ transform: translateY(-1px);
682
+ border-color: var(--border-strong);
683
+ background: rgba(255, 255, 255, .10);
684
+ }
685
+
686
+ .chip.active,
687
+ .chip.accent {
688
+ color: #cdd7ff;
689
+ background: var(--accent-soft);
690
+ border-color: rgba(77, 107, 254, .48);
691
+ }
692
+
693
+ .chip:disabled {
694
+ opacity: .4;
695
+ cursor: not-allowed;
696
+ }
697
+
698
+ /* -------------------------------------------------------------------- jobs */
699
+
700
+ .jobs {
701
+ display: grid;
702
+ gap: 12px;
703
+ }
704
+
705
+ .job {
706
+ display: grid;
707
+ gap: 10px;
708
+ padding: 12px 14px;
709
+ background: rgba(255, 255, 255, .035);
710
+ border: 1px solid var(--border);
711
+ border-radius: 14px;
712
+ }
713
+
714
+ .job.running {
715
+ border-color: rgba(53, 208, 232, .34);
716
+ }
717
+
718
+ .job.queued {
719
+ border-color: rgba(255, 194, 75, .28);
720
+ }
721
+
722
+ .job.failed {
723
+ border-color: rgba(255, 107, 129, .32);
724
+ }
725
+
726
+ .job-head {
727
+ display: flex;
728
+ flex-wrap: wrap;
729
+ align-items: center;
730
+ gap: 8px 10px;
731
+ font-size: 12.5px;
732
+ }
733
+
734
+ .job-kind {
735
+ color: var(--muted);
736
+ }
737
+
738
+ .job-target {
739
+ font-size: 13.5px;
740
+ }
741
+
742
+ .job-time {
743
+ margin-left: auto;
744
+ color: var(--muted);
745
+ font-size: 11.5px;
746
+ }
747
+
748
+ .job-log {
749
+ display: grid;
750
+ gap: 4px;
751
+ max-height: 220px;
752
+ overflow-y: auto;
753
+ padding: 10px 12px;
754
+ background: rgba(0, 0, 0, .35);
755
+ border: 1px solid var(--border);
756
+ border-radius: 12px;
757
+ font-size: 12px;
758
+ }
759
+
760
+ .log-line {
761
+ display: grid;
762
+ grid-template-columns: 66px minmax(0, 1fr);
763
+ gap: 10px;
764
+ margin: 0;
765
+ align-items: start;
766
+ }
767
+
768
+ .log-time {
769
+ color: rgba(152, 162, 189, .75);
770
+ }
771
+
772
+ .log-text {
773
+ font-family: var(--mono);
774
+ white-space: pre-wrap;
775
+ word-break: break-word;
776
+ }
777
+
778
+ .job-error {
779
+ margin: 0;
780
+ padding: 8px 10px;
781
+ color: #ffd7dc;
782
+ background: rgba(255, 107, 129, .12);
783
+ border: 1px solid rgba(255, 107, 129, .38);
784
+ border-radius: 12px;
785
+ font-size: 12.5px;
786
+ line-height: 1.6;
787
+ }
788
+
789
+ .job-details summary {
790
+ padding: 2px 0;
791
+ color: var(--muted);
792
+ font-size: 12.5px;
793
+ cursor: pointer;
794
+ }
795
+
796
+ .job-details[open] summary {
797
+ margin-bottom: 10px;
798
+ }
799
+
800
+ /* ----------------------------------------------------------------- results */
801
+
802
+ .result {
803
+ display: grid;
804
+ gap: 8px;
805
+ font-size: 12.5px;
806
+ }
807
+
808
+ .result-title {
809
+ font-size: 12.5px;
810
+ font-weight: 650;
811
+ }
812
+
813
+ .result-list {
814
+ display: grid;
815
+ gap: 6px;
816
+ margin: 0;
817
+ padding: 0;
818
+ list-style: none;
819
+ color: var(--muted);
820
+ }
821
+
822
+ .result-list li {
823
+ display: flex;
824
+ flex-wrap: wrap;
825
+ align-items: baseline;
826
+ gap: 8px;
827
+ }
828
+
829
+ .result-arrow {
830
+ display: inline-flex;
831
+ align-items: baseline;
832
+ gap: 4px;
833
+ }
834
+
835
+ .result-line {
836
+ color: var(--muted);
837
+ }
838
+
839
+ .warn-list {
840
+ color: var(--warn);
841
+ }
842
+
843
+ .callout {
844
+ padding: 10px 12px;
845
+ border-radius: 12px;
846
+ font-size: 12.5px;
847
+ line-height: 1.6;
848
+ }
849
+
850
+ .callout.warn {
851
+ color: #ffe0ab;
852
+ background: rgba(255, 194, 75, .10);
853
+ border: 1px solid rgba(255, 194, 75, .30);
854
+ }
855
+
856
+ .callout.info {
857
+ color: #c9d4ff;
858
+ background: var(--accent-soft);
859
+ border: 1px solid rgba(77, 107, 254, .35);
860
+ }
861
+
862
+ .raw {
863
+ font-size: 12px;
864
+ color: var(--muted);
865
+ }
866
+
867
+ .raw summary {
868
+ cursor: pointer;
869
+ }
870
+
871
+ .pre {
872
+ max-height: 260px;
873
+ overflow: auto;
874
+ margin: 8px 0 0;
875
+ padding: 10px;
876
+ background: rgba(0, 0, 0, .35);
877
+ border: 1px solid var(--border);
878
+ border-radius: 10px;
879
+ font-size: 11.5px;
880
+ line-height: 1.5;
881
+ white-space: pre-wrap;
882
+ word-break: break-word;
883
+ }
884
+
885
+ /* ------------------------------------------------------------------- forms */
886
+
887
+ .form {
888
+ display: grid;
889
+ gap: 16px;
890
+ }
891
+
892
+ .field {
893
+ display: grid;
894
+ gap: 6px;
895
+ /* Never stretch a control to fill a row: a taller neighbour must not change
896
+ the height of this field's input. */
897
+ align-content: start;
898
+ }
899
+
900
+ /* A row of fields plus a note that belongs to the row as a whole. */
901
+ .field-block {
902
+ display: grid;
903
+ gap: 6px;
904
+ }
905
+
906
+ .field-row {
907
+ display: grid;
908
+ grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
909
+ gap: 14px;
910
+ align-items: start;
911
+ }
912
+
913
+ .label {
914
+ color: var(--muted);
915
+ font-size: 12.5px;
916
+ font-weight: 600;
917
+ letter-spacing: .04em;
918
+ }
919
+
920
+ .helper {
921
+ color: var(--muted);
922
+ font-size: 12px;
923
+ line-height: 1.6;
924
+ }
925
+
926
+ .hint {
927
+ margin: 0;
928
+ color: var(--muted);
929
+ font-size: 12px;
930
+ line-height: 1.6;
931
+ word-break: break-all;
932
+ }
933
+
934
+ .fieldset {
935
+ display: grid;
936
+ gap: 10px;
937
+ margin: 0;
938
+ padding: 12px 14px;
939
+ border: 1px solid var(--border);
940
+ border-radius: 14px;
941
+ }
942
+
943
+ .input,
944
+ .select,
945
+ .textarea {
946
+ width: 100%;
947
+ padding: 10px 12px;
948
+ color: var(--text);
949
+ background: rgba(4, 7, 14, .55);
950
+ border: 1px solid var(--border);
951
+ border-radius: 12px;
952
+ font-size: 13px;
953
+ transition: border-color .16s ease, box-shadow .16s ease;
954
+ }
955
+
956
+ .input::placeholder,
957
+ .textarea::placeholder {
958
+ color: rgba(152, 162, 189, .65);
959
+ }
960
+
961
+ .input:focus,
962
+ .select:focus,
963
+ .textarea:focus {
964
+ border-color: rgba(77, 107, 254, .65);
965
+ box-shadow: 0 0 0 3px var(--accent-soft);
966
+ outline: none;
967
+ }
968
+
969
+ .textarea {
970
+ resize: vertical;
971
+ min-height: 76px;
972
+ line-height: 1.6;
973
+ }
974
+
975
+ /* A <select> is intrinsically a little taller than an <input>; pin both to the
976
+ same height so a row of controls lines up exactly. `box-sizing: border-box`
977
+ is global, so this includes the padding and the border. */
978
+ .input,
979
+ .select {
980
+ height: var(--control-height);
981
+ line-height: 1.25;
982
+ }
983
+
984
+ .select {
985
+ appearance: none;
986
+ background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
987
+ background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
988
+ background-size: 5px 5px, 5px 5px;
989
+ background-repeat: no-repeat;
990
+ }
991
+
992
+ .checks,
993
+ .radios {
994
+ display: flex;
995
+ flex-wrap: wrap;
996
+ gap: 10px 18px;
997
+ }
998
+
999
+ .checkbox,
1000
+ .radio {
1001
+ display: inline-flex;
1002
+ align-items: center;
1003
+ gap: 8px;
1004
+ font-size: 13px;
1005
+ cursor: pointer;
1006
+ }
1007
+
1008
+ .checkbox input,
1009
+ .radio input {
1010
+ width: 16px;
1011
+ height: 16px;
1012
+ accent-color: var(--accent);
1013
+ cursor: pointer;
1014
+ }
1015
+
1016
+ .radio.disabled {
1017
+ opacity: .55;
1018
+ cursor: not-allowed;
1019
+ }
1020
+
1021
+ .radio.disabled input {
1022
+ cursor: not-allowed;
1023
+ }
1024
+
1025
+ .checkbox.solo {
1026
+ padding: 10px 12px;
1027
+ background: rgba(255, 255, 255, .03);
1028
+ border: 1px solid var(--border);
1029
+ border-radius: 12px;
1030
+ }
1031
+
1032
+ .form-error {
1033
+ padding: 10px 12px;
1034
+ color: #ffd7dc;
1035
+ background: rgba(255, 107, 129, .12);
1036
+ border: 1px solid rgba(255, 107, 129, .40);
1037
+ border-radius: 12px;
1038
+ font-size: 12.5px;
1039
+ line-height: 1.6;
1040
+ }
1041
+
1042
+ .status-strip {
1043
+ display: flex;
1044
+ align-items: center;
1045
+ gap: 10px;
1046
+ padding: 8px 12px;
1047
+ color: var(--muted);
1048
+ background: rgba(255, 255, 255, .04);
1049
+ border: 1px solid var(--border);
1050
+ border-radius: 12px;
1051
+ font-size: 12.5px;
1052
+ }
1053
+
1054
+ .spec-forms {
1055
+ margin: 4px 0 0;
1056
+ padding-left: 18px;
1057
+ color: var(--muted);
1058
+ font-size: 12px;
1059
+ line-height: 1.75;
1060
+ }
1061
+
1062
+ .code {
1063
+ padding: 1px 6px;
1064
+ background: rgba(255, 255, 255, .07);
1065
+ border: 1px solid var(--border);
1066
+ border-radius: 6px;
1067
+ font-size: 12px;
1068
+ }
1069
+
1070
+ /* ------------------------------------------------------------------ modals */
1071
+
1072
+ .scrim {
1073
+ position: fixed;
1074
+ inset: 0;
1075
+ z-index: 50;
1076
+ display: grid;
1077
+ place-items: center;
1078
+ padding: 24px;
1079
+ overflow-y: auto;
1080
+ background: rgba(3, 5, 10, .62);
1081
+ backdrop-filter: blur(6px);
1082
+ -webkit-backdrop-filter: blur(6px);
1083
+ }
1084
+
1085
+ .modal {
1086
+ display: flex;
1087
+ flex-direction: column;
1088
+ width: min(680px, 100%);
1089
+ max-height: 88vh;
1090
+ overflow: hidden;
1091
+ background: linear-gradient(160deg, rgba(255, 255, 255, .105), rgba(255, 255, 255, .045));
1092
+ backdrop-filter: blur(22px) saturate(150%);
1093
+ -webkit-backdrop-filter: blur(22px) saturate(150%);
1094
+ border: 1px solid var(--border-strong);
1095
+ border-radius: 22px;
1096
+ box-shadow: 0 30px 80px rgba(0, 0, 0, .60), inset 0 1px 0 rgba(255, 255, 255, .08);
1097
+ }
1098
+
1099
+ .modal-head {
1100
+ display: flex;
1101
+ align-items: center;
1102
+ justify-content: space-between;
1103
+ gap: 12px;
1104
+ padding: 18px 22px;
1105
+ border-bottom: 1px solid var(--border);
1106
+ }
1107
+
1108
+ .modal-title {
1109
+ margin: 0;
1110
+ font-size: 16px;
1111
+ font-weight: 650;
1112
+ word-break: break-all;
1113
+ }
1114
+
1115
+ .modal-body {
1116
+ overflow-y: auto;
1117
+ padding: 20px 22px 22px;
1118
+ }
1119
+
1120
+ .modal-foot {
1121
+ display: flex;
1122
+ justify-content: flex-end;
1123
+ gap: 10px;
1124
+ padding-top: 4px;
1125
+ }
1126
+
1127
+ /* ------------------------------------------------------------ empty/loading */
1128
+
1129
+ .empty {
1130
+ padding: 28px;
1131
+ color: var(--muted);
1132
+ border: 1px dashed var(--border);
1133
+ border-radius: var(--radius);
1134
+ text-align: center;
1135
+ }
1136
+
1137
+ .empty-title {
1138
+ margin: 0 0 6px;
1139
+ color: var(--text);
1140
+ font-weight: 650;
1141
+ }
1142
+
1143
+ .auth-screen {
1144
+ min-height: 100vh;
1145
+ display: grid;
1146
+ place-items: center;
1147
+ padding: 24px;
1148
+ }
1149
+
1150
+ .auth-card {
1151
+ display: grid;
1152
+ gap: 14px;
1153
+ justify-items: start;
1154
+ max-width: 640px;
1155
+ padding: 30px;
1156
+ }
1157
+
1158
+ .auth-title {
1159
+ margin: 0;
1160
+ font-size: 19px;
1161
+ font-weight: 680;
1162
+ }
1163
+
1164
+ .auth-body {
1165
+ display: grid;
1166
+ gap: 12px;
1167
+ color: var(--text);
1168
+ font-size: 13.5px;
1169
+ line-height: 1.75;
1170
+ }
1171
+
1172
+ .loading-bar {
1173
+ width: 100%;
1174
+ max-width: 260px;
1175
+ height: 3px;
1176
+ overflow: hidden;
1177
+ background: rgba(255, 255, 255, .08);
1178
+ border-radius: 999px;
1179
+ }
1180
+
1181
+ .loading-bar span {
1182
+ display: block;
1183
+ width: 40%;
1184
+ height: 100%;
1185
+ background: linear-gradient(90deg, var(--accent), var(--cyan));
1186
+ border-radius: 999px;
1187
+ animation: loading-slide 1.2s ease-in-out infinite;
1188
+ }
1189
+
1190
+ @keyframes loading-slide {
1191
+ 0% { transform: translateX(-60%); }
1192
+ 100% { transform: translateX(260%); }
1193
+ }
1194
+
1195
+ /* -------------------------------------------------------------- scrollbars */
1196
+
1197
+ .job-log::-webkit-scrollbar,
1198
+ .modal-body::-webkit-scrollbar,
1199
+ .pre::-webkit-scrollbar,
1200
+ .scrim::-webkit-scrollbar {
1201
+ width: 10px;
1202
+ height: 10px;
1203
+ }
1204
+
1205
+ .job-log::-webkit-scrollbar-thumb,
1206
+ .modal-body::-webkit-scrollbar-thumb,
1207
+ .pre::-webkit-scrollbar-thumb,
1208
+ .scrim::-webkit-scrollbar-thumb {
1209
+ background: rgba(255, 255, 255, .14);
1210
+ background-clip: content-box;
1211
+ border: 3px solid transparent;
1212
+ border-radius: 999px;
1213
+ }
1214
+
1215
+ .job-log::-webkit-scrollbar-track,
1216
+ .modal-body::-webkit-scrollbar-track,
1217
+ .pre::-webkit-scrollbar-track,
1218
+ .scrim::-webkit-scrollbar-track {
1219
+ background: transparent;
1220
+ }
1221
+
1222
+ /* -------------------------------------------------------------- settings -- */
1223
+
1224
+ .settings-block {
1225
+ display: grid;
1226
+ gap: 10px;
1227
+ padding: 14px;
1228
+ background: rgba(255, 255, 255, .02);
1229
+ border: 1px solid var(--border);
1230
+ border-radius: 14px;
1231
+ }
1232
+
1233
+ .settings-title {
1234
+ margin: 0;
1235
+ font-size: 13.5px;
1236
+ font-weight: 650;
1237
+ }
1238
+
1239
+ .settings-current {
1240
+ margin: 0;
1241
+ color: var(--muted);
1242
+ font-size: 12.5px;
1243
+ word-break: break-all;
1244
+ }
1245
+
1246
+ .settings-row {
1247
+ display: flex;
1248
+ flex-wrap: wrap;
1249
+ align-items: flex-end;
1250
+ gap: 10px;
1251
+ }
1252
+
1253
+ .settings-row > .field {
1254
+ flex: 1 1 200px;
1255
+ }
1256
+
1257
+ .settings-row > .input {
1258
+ flex: 1 1 240px;
1259
+ }
1260
+
1261
+ .settings-provider {
1262
+ display: grid;
1263
+ gap: 10px;
1264
+ padding-top: 12px;
1265
+ border-top: 1px dashed var(--border);
1266
+ }
1267
+
1268
+ .settings-provider-head {
1269
+ display: flex;
1270
+ align-items: center;
1271
+ flex-wrap: wrap;
1272
+ gap: 10px;
1273
+ }
1274
+
1275
+ .settings-actions {
1276
+ display: flex;
1277
+ justify-content: flex-end;
1278
+ gap: 8px;
1279
+ }
1280
+
1281
+ /* The header chips now carry a small action button. */
1282
+ .meta-chip .btn.small {
1283
+ justify-self: start;
1284
+ padding: 3px 10px;
1285
+ }
1286
+
1287
+ .provider-hint .btn.small {
1288
+ margin: 0 6px;
1289
+ vertical-align: middle;
1290
+ }
1291
+
1292
+ /* --------------------------------------------------------------- responsive */
1293
+
1294
+ @media (min-width: 861px) {
1295
+ .topbar {
1296
+ grid-template-columns: minmax(0, 1fr) auto;
1297
+ align-items: center;
1298
+ }
1299
+ }
1300
+
1301
+ @media (max-width: 860px) {
1302
+ .app {
1303
+ padding: 18px 14px 30px;
1304
+ }
1305
+
1306
+ /* One column per row: the link, modules and actions stack instead of
1307
+ squeezing into a single line. */
1308
+ .env-row-main {
1309
+ grid-template-columns: minmax(0, 1fr);
1310
+ align-items: start;
1311
+ gap: 6px;
1312
+ }
1313
+
1314
+ .env-actions {
1315
+ justify-content: flex-start;
1316
+ }
1317
+
1318
+ .env-chips {
1319
+ max-height: none;
1320
+ }
1321
+
1322
+ .job-time {
1323
+ margin-left: 0;
1324
+ }
1325
+
1326
+ .log-line {
1327
+ grid-template-columns: 56px minmax(0, 1fr);
1328
+ }
1329
+
1330
+ .scrim {
1331
+ padding: 14px;
1332
+ }
1333
+
1334
+ .modal {
1335
+ max-height: 92vh;
1336
+ }
1337
+ }
1338
+
1339
+ @media (prefers-reduced-motion: reduce) {
1340
+ *,
1341
+ *::before,
1342
+ *::after {
1343
+ animation-duration: .01ms !important;
1344
+ animation-iteration-count: 1 !important;
1345
+ transition-duration: .01ms !important;
1346
+ }
1347
+ }
1348
+
1349
+ /* ------------------------------------------- environment configuration -- */
1350
+
1351
+ /* The per-environment "完整配置" dialog (opened from the 配置 row button): the
1352
+ summary grid, the manifest module table and the foldable Wyn.conf lists.
1353
+ Everything is introduced here and prefixed `config-`, so the rest of the page
1354
+ is untouched. */
1355
+ .config-view {
1356
+ display: grid;
1357
+ gap: 14px;
1358
+ }
1359
+
1360
+ /* This dialog is a data view (a five-column module table and long paths), so it
1361
+ gets a wider modal than the compact action dialogs. */
1362
+ .modal.modal-wide {
1363
+ width: min(1080px, 94vw);
1364
+ }
1365
+
1366
+ .config-table {
1367
+ table-layout: fixed;
1368
+ }
1369
+
1370
+ /* 模块 · 版本 · 来源 · 部署目标 · 校验 */
1371
+ .config-table th:nth-child(1), .config-table td:nth-child(1) { width: 20%; }
1372
+ .config-table th:nth-child(2), .config-table td:nth-child(2) { width: 10%; }
1373
+ .config-table th:nth-child(3), .config-table td:nth-child(3) { width: 18%; }
1374
+ .config-table th:nth-child(4), .config-table td:nth-child(4) { width: 36%; }
1375
+ .config-table th:nth-child(5), .config-table td:nth-child(5) { width: 16%; }
1376
+
1377
+ .config-table td:nth-child(2) { white-space: nowrap; font-variant-numeric: tabular-nums; }
1378
+ .config-table td:nth-child(4) { overflow-wrap: anywhere; }
1379
+
1380
+ /* A path, artifact name or digest may be long: wrap it instead of clipping it. */
1381
+ .config-sub, .config-field-value, .config-digest { overflow-wrap: anywhere; }
1382
+ .config-digest { white-space: nowrap; }
1383
+
1384
+ .config-block {
1385
+ display: grid;
1386
+ gap: 10px;
1387
+ padding: 14px;
1388
+ background: rgba(255, 255, 255, .02);
1389
+ border: 1px solid var(--border);
1390
+ border-radius: 14px;
1391
+ }
1392
+
1393
+ .config-title {
1394
+ margin: 0;
1395
+ font-size: 13.5px;
1396
+ font-weight: 650;
1397
+ }
1398
+
1399
+ .config-grid {
1400
+ display: grid;
1401
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1402
+ gap: 8px 16px;
1403
+ }
1404
+
1405
+ .config-field {
1406
+ display: grid;
1407
+ grid-template-columns: 96px minmax(0, 1fr);
1408
+ align-items: baseline;
1409
+ gap: 8px;
1410
+ min-width: 0;
1411
+ font-size: 12.5px;
1412
+ }
1413
+
1414
+ .config-field-label {
1415
+ color: var(--muted);
1416
+ }
1417
+
1418
+ .config-field-value {
1419
+ min-width: 0;
1420
+ word-break: break-all;
1421
+ }
1422
+
1423
+ .config-site {
1424
+ color: var(--cyan);
1425
+ }
1426
+
1427
+ .config-site.muted {
1428
+ color: var(--muted);
1429
+ }
1430
+
1431
+ /* A value with its inline action button (copy the address, open the directory). */
1432
+ .config-inline {
1433
+ display: inline-flex;
1434
+ align-items: center;
1435
+ flex-wrap: wrap;
1436
+ gap: 8px;
1437
+ min-width: 0;
1438
+ }
1439
+
1440
+ .config-sublist {
1441
+ display: grid;
1442
+ gap: 6px;
1443
+ }
1444
+
1445
+ .config-sublist-label {
1446
+ color: var(--muted);
1447
+ font-size: 12px;
1448
+ }
1449
+
1450
+ /* A scroll box keeps a wide module table from stretching the dialog. */
1451
+ .config-table-wrap {
1452
+ overflow-x: auto;
1453
+ border: 1px solid var(--border);
1454
+ border-radius: 12px;
1455
+ }
1456
+
1457
+ .config-table {
1458
+ width: 100%;
1459
+ border-collapse: collapse;
1460
+ font-size: 12.5px;
1461
+ }
1462
+
1463
+ .config-table th {
1464
+ padding: 8px 10px;
1465
+ color: var(--muted);
1466
+ font-weight: 600;
1467
+ text-align: left;
1468
+ white-space: nowrap;
1469
+ background: rgba(255, 255, 255, .03);
1470
+ border-bottom: 1px solid var(--border);
1471
+ }
1472
+
1473
+ .config-table td {
1474
+ padding: 8px 10px;
1475
+ vertical-align: top;
1476
+ border-bottom: 1px solid rgba(255, 255, 255, .06);
1477
+ }
1478
+
1479
+ .config-table tr:last-child td {
1480
+ border-bottom: none;
1481
+ }
1482
+
1483
+ .config-module-cell {
1484
+ display: grid;
1485
+ gap: 4px;
1486
+ min-width: 140px;
1487
+ }
1488
+
1489
+ .config-module-tags {
1490
+ display: flex;
1491
+ flex-wrap: wrap;
1492
+ gap: 4px;
1493
+ }
1494
+
1495
+ /* A second, quieter line inside a cell (assembly, artifact path, override path). */
1496
+ .config-sub {
1497
+ display: block;
1498
+ max-width: 280px;
1499
+ color: var(--muted);
1500
+ font-size: 11px;
1501
+ line-height: 1.45;
1502
+ word-break: break-all;
1503
+ }
1504
+
1505
+ .chip-tag.source-remote {
1506
+ color: #c9d4ff;
1507
+ border-color: rgba(77, 107, 254, .35);
1508
+ }
1509
+
1510
+ .chip-tag.source-local {
1511
+ color: var(--cyan);
1512
+ background: rgba(53, 208, 232, .12);
1513
+ border-color: rgba(53, 208, 232, .35);
1514
+ }
1515
+
1516
+ /* A worker is a mapping of its own: the chip makes it stand out in the table. */
1517
+ .chip-tag.source-worker {
1518
+ color: var(--ok);
1519
+ background: rgba(61, 220, 151, .12);
1520
+ border-color: rgba(61, 220, 151, .35);
1521
+ }
1522
+
1523
+ /* One line per manifest mapping, directly under the module name. */
1524
+ .config-mappings {
1525
+ display: grid;
1526
+ gap: 3px;
1527
+ min-width: 0;
1528
+ }
1529
+
1530
+ .config-mappings-label {
1531
+ color: var(--muted);
1532
+ font-size: 11px;
1533
+ }
1534
+
1535
+ .config-mapping {
1536
+ display: flex;
1537
+ flex-wrap: wrap;
1538
+ align-items: baseline;
1539
+ gap: 4px;
1540
+ min-width: 0;
1541
+ }
1542
+
1543
+ .config-mapping-text {
1544
+ max-width: 300px;
1545
+ font-size: 11.5px;
1546
+ line-height: 1.45;
1547
+ word-break: break-all;
1548
+ }
1549
+
1550
+ .config-sep {
1551
+ color: var(--muted);
1552
+ }
1553
+
1554
+ .config-mappings-fold summary {
1555
+ cursor: pointer;
1556
+ }
1557
+
1558
+ .config-mappings-fold .config-mappings {
1559
+ margin-top: 6px;
1560
+ }
1561
+
1562
+ .config-digest {
1563
+ color: var(--muted);
1564
+ white-space: nowrap;
1565
+ }
1566
+
1567
+ .config-overrides,
1568
+ .config-services,
1569
+ .config-paths {
1570
+ display: grid;
1571
+ gap: 6px;
1572
+ margin: 0;
1573
+ padding: 0;
1574
+ list-style: none;
1575
+ }
1576
+
1577
+ .config-override,
1578
+ .config-service {
1579
+ display: flex;
1580
+ flex-wrap: wrap;
1581
+ align-items: baseline;
1582
+ gap: 8px;
1583
+ min-width: 0;
1584
+ }
1585
+
1586
+ .config-override .config-sub {
1587
+ max-width: 100%;
1588
+ }
1589
+
1590
+ .config-paths {
1591
+ max-height: 200px;
1592
+ overflow: auto;
1593
+ margin-top: 8px;
1594
+ padding: 10px;
1595
+ background: rgba(0, 0, 0, .35);
1596
+ border: 1px solid var(--border);
1597
+ border-radius: 10px;
1598
+ font-size: 11.5px;
1599
+ }
1600
+
1601
+ .config-paths li {
1602
+ word-break: break-all;
1603
+ }
1604
+
1605
+ .config-collapse summary {
1606
+ cursor: pointer;
1607
+ }
1608
+
1609
+ @media (max-width: 620px) {
1610
+ /* One line per field: the label no longer squeezes the value column. */
1611
+ .config-field {
1612
+ grid-template-columns: minmax(0, 1fr);
1613
+ gap: 2px;
1614
+ }
1615
+
1616
+ .config-sub {
1617
+ max-width: none;
1618
+ }
1619
+ }