sanjang 0.3.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.
@@ -0,0 +1,2112 @@
1
+ /* ============================================================
2
+ 산장 (Sanjang) — SMPLY Local Dev Tool
3
+ ============================================================
4
+ Aesthetic: Industrial-craft — precise, purposeful, clean.
5
+ 셀파스가 세팅해주는 안전한 산장에서 바이브코딩.
6
+ ============================================================ */
7
+
8
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@400;500;600;700&display=swap');
9
+
10
+ *, *::before, *::after {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ font-family: inherit;
15
+ }
16
+
17
+ :root {
18
+ --bg-body: #0c0e14;
19
+ --bg-card: #12151e;
20
+ --bg-header: #0c0e14;
21
+ --bg-elevated: #181c28;
22
+ --bg-inset: #0a0c11;
23
+ --border: #1c2030;
24
+ --border-subtle: #161a26;
25
+ --text-primary: #e4e8f0;
26
+ --text-secondary: #6b7394;
27
+ --text-muted: #4a5170;
28
+
29
+ --accent: #6366f1;
30
+ --accent-hover: #5254cc;
31
+ --accent-glow: rgba(99, 102, 241, 0.12);
32
+ --green: #22c55e;
33
+ --green-dim: rgba(34, 197, 94, 0.12);
34
+ --blue: #3b82f6;
35
+ --blue-dim: rgba(59, 130, 246, 0.10);
36
+ --amber: #f59e0b;
37
+ --amber-dim: rgba(245, 158, 11, 0.12);
38
+ --red: #ef4444;
39
+ --red-dim: rgba(239, 68, 68, 0.10);
40
+
41
+ --btn-primary: var(--accent);
42
+ --btn-primary-hover: var(--accent-hover);
43
+ --btn-danger: var(--red);
44
+ --btn-ghost: var(--bg-elevated);
45
+ --btn-ghost-hover: #222738;
46
+
47
+ --status-stopped-bg: var(--bg-elevated);
48
+ --status-stopped-fg: var(--text-muted);
49
+ --status-starting-bg: var(--blue-dim);
50
+ --status-starting-fg: var(--blue);
51
+ --status-running-bg: var(--green-dim);
52
+ --status-running-fg: var(--green);
53
+ --status-error-bg: var(--red-dim);
54
+ --status-error-fg: var(--red);
55
+
56
+ --font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
57
+ --font-mono: 'JetBrains Mono', 'SF Mono', monospace;
58
+
59
+ --radius: 12px;
60
+ --radius-sm: 8px;
61
+ --radius-xs: 6px;
62
+ }
63
+
64
+ html, body {
65
+ height: 100%;
66
+ }
67
+
68
+ body {
69
+ background: var(--bg-body);
70
+ color: var(--text-primary);
71
+ font-family: var(--font-sans);
72
+ font-size: 14px;
73
+ line-height: 1.5;
74
+ min-height: 100vh;
75
+ /* Subtle noise texture */
76
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.02'/%3E%3C/svg%3E");
77
+ }
78
+
79
+ /* ============================================================
80
+ Header
81
+ ============================================================ */
82
+
83
+ header {
84
+ background: var(--bg-header);
85
+ border-bottom: 1px solid var(--border);
86
+ padding: 0 32px;
87
+ height: 60px;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: space-between;
91
+ position: sticky;
92
+ top: 0;
93
+ z-index: 100;
94
+ backdrop-filter: blur(12px);
95
+ -webkit-backdrop-filter: blur(12px);
96
+ }
97
+
98
+ header h1 {
99
+ font-size: 18px;
100
+ font-weight: 700;
101
+ color: var(--text-primary);
102
+ letter-spacing: -0.02em;
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 10px;
106
+ }
107
+
108
+ header h1::before {
109
+ content: '⛰';
110
+ display: inline-block;
111
+ font-size: 18px;
112
+ flex-shrink: 0;
113
+ filter: saturate(0.7);
114
+ }
115
+
116
+ /* ============================================================
117
+ Buttons
118
+ ============================================================ */
119
+
120
+ .btn {
121
+ display: inline-flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ gap: 6px;
125
+ padding: 9px 16px;
126
+ border-radius: var(--radius-xs);
127
+ border: none;
128
+ cursor: pointer;
129
+ font-family: var(--font-sans);
130
+ font-size: 13px;
131
+ font-weight: 500;
132
+ line-height: 1.4;
133
+ transition: all 0.15s ease;
134
+ white-space: nowrap;
135
+ min-height: 36px;
136
+ }
137
+
138
+ .btn:disabled {
139
+ opacity: 0.35;
140
+ cursor: not-allowed;
141
+ }
142
+
143
+ .btn:active:not(:disabled) {
144
+ transform: scale(0.97);
145
+ }
146
+
147
+ .btn-primary {
148
+ background: var(--btn-primary);
149
+ color: #fff;
150
+ box-shadow: 0 1px 2px rgba(99, 102, 241, 0.3);
151
+ }
152
+ .btn-primary:hover:not(:disabled) {
153
+ background: var(--btn-primary-hover);
154
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25);
155
+ }
156
+
157
+ .btn-danger {
158
+ background: var(--btn-danger);
159
+ color: #fff;
160
+ }
161
+ .btn-danger:hover:not(:disabled) {
162
+ background: #dc2626;
163
+ }
164
+
165
+ .btn-danger-ghost {
166
+ background: transparent;
167
+ color: var(--red);
168
+ border: 1px solid transparent;
169
+ }
170
+ .btn-danger-ghost:hover:not(:disabled) {
171
+ background: rgba(239, 68, 68, 0.1);
172
+ border-color: var(--red);
173
+ }
174
+
175
+ .btn-ghost {
176
+ background: var(--btn-ghost);
177
+ color: var(--text-secondary);
178
+ border: 1px solid var(--border);
179
+ }
180
+ .btn-ghost:hover:not(:disabled) {
181
+ background: var(--btn-ghost-hover);
182
+ color: var(--text-primary);
183
+ border-color: #2a3040;
184
+ }
185
+
186
+ .btn-sm {
187
+ padding: 5px 12px;
188
+ font-size: 12px;
189
+ }
190
+
191
+ .btn-xs {
192
+ padding: 3px 10px;
193
+ font-size: 11px;
194
+ font-weight: 500;
195
+ border-radius: var(--radius-xs);
196
+ }
197
+
198
+ .btn-accent {
199
+ background: var(--accent);
200
+ color: #fff;
201
+ box-shadow: 0 1px 2px rgba(99, 102, 241, 0.3);
202
+ }
203
+ .btn-accent:hover:not(:disabled) {
204
+ background: var(--accent-hover);
205
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25);
206
+ }
207
+
208
+ .btn-ghost-danger {
209
+ background: transparent;
210
+ color: var(--text-muted);
211
+ border: 1px solid var(--border);
212
+ }
213
+ .btn-ghost-danger:hover:not(:disabled) {
214
+ color: var(--red);
215
+ border-color: rgba(239, 68, 68, 0.3);
216
+ background: var(--red-dim);
217
+ }
218
+
219
+ /* ============================================================
220
+ Grid
221
+ ============================================================ */
222
+
223
+ .grid {
224
+ display: grid;
225
+ grid-template-columns: repeat(auto-fill, minmax(480px, 560px));
226
+ gap: 24px;
227
+ padding: 36px 48px;
228
+ max-width: 1200px;
229
+ margin: 0 auto;
230
+ justify-content: center;
231
+ }
232
+
233
+ /* ============================================================
234
+ Empty State
235
+ ============================================================ */
236
+
237
+ #empty-state {
238
+ display: none;
239
+ flex-direction: column;
240
+ align-items: center;
241
+ justify-content: center;
242
+ min-height: calc(100vh - 60px);
243
+ gap: 16px;
244
+ color: var(--text-secondary);
245
+ }
246
+
247
+ #empty-state.visible {
248
+ display: flex;
249
+ }
250
+
251
+ #empty-state .empty-icon {
252
+ font-size: 56px;
253
+ opacity: 0.15;
254
+ filter: grayscale(1);
255
+ }
256
+
257
+ #empty-state p {
258
+ font-size: 16px;
259
+ font-weight: 500;
260
+ color: var(--text-secondary);
261
+ }
262
+
263
+ #empty-state .empty-hint {
264
+ font-size: 13px;
265
+ color: var(--text-muted);
266
+ }
267
+
268
+ /* ============================================================
269
+ Card
270
+ ============================================================ */
271
+
272
+ .card {
273
+ background: var(--bg-card);
274
+ border: 1px solid var(--border);
275
+ border-radius: var(--radius);
276
+ overflow: hidden;
277
+ display: flex;
278
+ flex-direction: column;
279
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
280
+ position: relative;
281
+ z-index: 0;
282
+ }
283
+
284
+ .card::before {
285
+ content: '';
286
+ position: absolute;
287
+ top: 0;
288
+ left: 24px;
289
+ right: 24px;
290
+ height: 1px;
291
+ background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.3), transparent);
292
+ }
293
+
294
+ .card:hover {
295
+ border-color: #262a3a;
296
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
297
+ }
298
+
299
+ .card-header {
300
+ padding: 22px 24px 14px;
301
+ display: flex;
302
+ align-items: flex-start;
303
+ justify-content: space-between;
304
+ gap: 12px;
305
+ border-bottom: 1px solid var(--border-subtle);
306
+ position: relative;
307
+ z-index: 1;
308
+ }
309
+
310
+ .card-header-left {
311
+ display: flex;
312
+ align-items: center;
313
+ gap: 14px;
314
+ min-width: 0;
315
+ }
316
+
317
+ .card-header-text {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: 3px;
321
+ min-width: 0;
322
+ }
323
+
324
+ .card-header-right {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 10px;
328
+ flex-shrink: 0;
329
+ }
330
+
331
+ .card-name {
332
+ font-size: 18px;
333
+ font-weight: 600;
334
+ color: var(--text-primary);
335
+ white-space: nowrap;
336
+ overflow: hidden;
337
+ text-overflow: ellipsis;
338
+ letter-spacing: -0.02em;
339
+ }
340
+
341
+ .card-branch {
342
+ font-size: 11px;
343
+ color: var(--text-muted);
344
+ font-family: var(--font-mono);
345
+ white-space: nowrap;
346
+ overflow: hidden;
347
+ text-overflow: ellipsis;
348
+ }
349
+
350
+ /* Status badge */
351
+ .badge {
352
+ display: inline-flex;
353
+ align-items: center;
354
+ gap: 5px;
355
+ padding: 3px 10px;
356
+ border-radius: 999px;
357
+ font-size: 11px;
358
+ font-weight: 600;
359
+ letter-spacing: 0.04em;
360
+ text-transform: uppercase;
361
+ flex-shrink: 0;
362
+ }
363
+
364
+ .badge::before {
365
+ content: '';
366
+ display: inline-block;
367
+ width: 6px;
368
+ height: 6px;
369
+ border-radius: 50%;
370
+ background: currentColor;
371
+ }
372
+
373
+ .badge-stopped { background: var(--status-stopped-bg); color: var(--status-stopped-fg); }
374
+ .badge-starting { background: var(--status-starting-bg); color: var(--status-starting-fg); }
375
+ .badge-running { background: var(--status-running-bg); color: var(--status-running-fg); }
376
+ .badge-error { background: var(--status-error-bg); color: var(--status-error-fg); }
377
+
378
+ .badge-starting::before {
379
+ animation: pulse 1.2s ease-in-out infinite;
380
+ }
381
+
382
+ @keyframes pulse {
383
+ 0%, 100% { opacity: 1; }
384
+ 50% { opacity: 0.2; }
385
+ }
386
+
387
+ /* Card body — the main content area */
388
+ .card-body {
389
+ padding: 16px 24px 24px;
390
+ display: flex;
391
+ flex-direction: column;
392
+ gap: 16px;
393
+ flex: 1;
394
+ position: relative;
395
+ z-index: 1;
396
+ }
397
+
398
+ /* URL link */
399
+ .card-urls {
400
+ padding: 0;
401
+ }
402
+
403
+ .card-url-link {
404
+ display: inline-flex;
405
+ align-items: center;
406
+ gap: 6px;
407
+ color: var(--blue);
408
+ text-decoration: none;
409
+ font-size: 13px;
410
+ font-weight: 500;
411
+ font-family: var(--font-mono);
412
+ padding: 7px 14px;
413
+ border-radius: var(--radius-xs);
414
+ background: var(--blue-dim);
415
+ transition: all 0.15s;
416
+ min-height: 34px;
417
+ }
418
+ .card-url-link:hover {
419
+ background: rgba(59, 130, 246, 0.18);
420
+ color: #60a5fa;
421
+ }
422
+
423
+ /* Error hint */
424
+ .card-error-hint {
425
+ font-size: 12px;
426
+ color: var(--red);
427
+ padding: 8px 12px;
428
+ background: var(--red-dim);
429
+ border-radius: var(--radius-xs);
430
+ border-left: 3px solid var(--red);
431
+ }
432
+
433
+ /* Changes badge */
434
+ .card-changes { min-height: 0; }
435
+
436
+ .changes-badge {
437
+ display: inline-flex;
438
+ align-items: center;
439
+ gap: 5px;
440
+ padding: 3px 10px;
441
+ border-radius: 999px;
442
+ font-size: 11px;
443
+ font-weight: 600;
444
+ background: var(--amber-dim);
445
+ color: var(--amber);
446
+ cursor: pointer;
447
+ transition: background 0.15s;
448
+ }
449
+ .changes-badge:hover { background: rgba(245, 158, 11, 0.2); }
450
+
451
+ .changes-badge::before {
452
+ content: '';
453
+ display: inline-block;
454
+ width: 5px;
455
+ height: 5px;
456
+ border-radius: 50%;
457
+ background: currentColor;
458
+ }
459
+
460
+ /* Main actions (변경 내역 + 보내기) */
461
+ .card-actions-main {
462
+ display: grid;
463
+ grid-template-columns: 1fr 1fr;
464
+ gap: 10px;
465
+ }
466
+ .card-actions-main .btn {
467
+ padding: 12px 16px;
468
+ font-size: 13px;
469
+ font-weight: 600;
470
+ min-height: 44px;
471
+ border-radius: var(--radius-sm);
472
+ }
473
+
474
+ /* Secondary actions — icon grid */
475
+ .card-actions-sub {
476
+ display: grid;
477
+ grid-template-columns: repeat(5, 1fr);
478
+ gap: 6px;
479
+ padding-top: 14px;
480
+ margin-top: 4px;
481
+ border-top: 1px solid var(--border-subtle);
482
+ }
483
+
484
+ .btn-sub {
485
+ display: flex;
486
+ flex-direction: column;
487
+ align-items: center;
488
+ gap: 4px;
489
+ padding: 10px 4px;
490
+ font-size: 10px;
491
+ font-weight: 500;
492
+ color: var(--text-muted);
493
+ background: var(--bg-inset);
494
+ border: 1px solid transparent;
495
+ border-radius: var(--radius-sm);
496
+ cursor: pointer;
497
+ transition: all 0.15s;
498
+ font-family: var(--font-sans);
499
+ line-height: 1.2;
500
+ min-height: 0;
501
+ }
502
+
503
+ .btn-sub:hover {
504
+ color: var(--text-secondary);
505
+ background: var(--bg-elevated);
506
+ border-color: var(--border);
507
+ }
508
+
509
+ .btn-sub:active {
510
+ transform: scale(0.96);
511
+ }
512
+
513
+ .btn-sub-danger:hover {
514
+ color: var(--red);
515
+ background: var(--red-dim);
516
+ border-color: rgba(239, 68, 68, 0.2);
517
+ }
518
+
519
+ /* Card footer */
520
+ .card-footer {
521
+ border-top: 1px solid var(--border-subtle);
522
+ }
523
+
524
+ /* ============================================================
525
+ Log panel
526
+ ============================================================ */
527
+
528
+ .log-toggle {
529
+ display: flex;
530
+ align-items: center;
531
+ justify-content: space-between;
532
+ padding: 10px 24px;
533
+ cursor: pointer;
534
+ font-size: 11px;
535
+ font-weight: 500;
536
+ color: var(--text-muted);
537
+ user-select: none;
538
+ transition: color 0.15s;
539
+ letter-spacing: 0.03em;
540
+ text-transform: uppercase;
541
+ }
542
+
543
+ .log-toggle:hover {
544
+ color: var(--text-secondary);
545
+ }
546
+
547
+ .log-toggle-arrow {
548
+ font-size: 10px;
549
+ transition: transform 0.2s;
550
+ }
551
+
552
+ .log-toggle.open .log-toggle-arrow {
553
+ transform: rotate(180deg);
554
+ }
555
+
556
+ .log-panel {
557
+ display: none;
558
+ border-top: 1px solid var(--border-subtle);
559
+ background: var(--bg-inset);
560
+ max-height: 220px;
561
+ overflow-y: auto;
562
+ padding: 12px 16px;
563
+ }
564
+
565
+ .log-panel.open {
566
+ display: block;
567
+ }
568
+
569
+ .log-panel pre {
570
+ font-family: var(--font-mono);
571
+ font-size: 11px;
572
+ color: var(--text-muted);
573
+ white-space: pre-wrap;
574
+ word-break: break-all;
575
+ line-height: 1.7;
576
+ }
577
+
578
+ .log-line-be { color: #818cf8; }
579
+ .log-line-fe { color: #34d399; }
580
+ .log-line-err { color: var(--red); }
581
+
582
+ /* Scrollbar */
583
+ .log-panel::-webkit-scrollbar { width: 4px; }
584
+ .log-panel::-webkit-scrollbar-track { background: transparent; }
585
+ .log-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
586
+
587
+ /* ============================================================
588
+ Diagnostics panel
589
+ ============================================================ */
590
+
591
+ .diag-panel {
592
+ border-top: 1px solid var(--border-subtle);
593
+ padding: 12px 20px;
594
+ display: flex;
595
+ flex-direction: column;
596
+ gap: 6px;
597
+ }
598
+
599
+ .diag-title {
600
+ font-size: 11px;
601
+ font-weight: 600;
602
+ color: var(--text-muted);
603
+ text-transform: uppercase;
604
+ letter-spacing: 0.05em;
605
+ margin-bottom: 2px;
606
+ }
607
+
608
+ .diag-item {
609
+ display: flex;
610
+ align-items: flex-start;
611
+ gap: 8px;
612
+ font-size: 12px;
613
+ }
614
+
615
+ .diag-icon {
616
+ flex-shrink: 0;
617
+ font-size: 13px;
618
+ line-height: 1.4;
619
+ }
620
+
621
+ .diag-text {
622
+ color: var(--text-muted);
623
+ line-height: 1.5;
624
+ }
625
+
626
+ .diag-text strong {
627
+ color: var(--text-primary);
628
+ font-weight: 500;
629
+ }
630
+
631
+ .diag-guide {
632
+ margin-top: 4px;
633
+ padding: 6px 10px;
634
+ background: var(--bg-input);
635
+ border-radius: 4px;
636
+ font-size: 12px;
637
+ color: var(--text-primary);
638
+ line-height: 1.5;
639
+ }
640
+
641
+ /* ============================================================
642
+ Modal
643
+ ============================================================ */
644
+
645
+ .modal-backdrop {
646
+ display: none;
647
+ position: fixed;
648
+ inset: 0;
649
+ background: rgba(0, 0, 0, 0.65);
650
+ z-index: 200;
651
+ align-items: center;
652
+ justify-content: center;
653
+ backdrop-filter: blur(6px);
654
+ -webkit-backdrop-filter: blur(6px);
655
+ }
656
+
657
+ .modal-backdrop.open {
658
+ display: flex;
659
+ }
660
+
661
+ .modal {
662
+ background: var(--bg-card);
663
+ border: 1px solid var(--border);
664
+ border-radius: var(--radius);
665
+ width: 100%;
666
+ max-width: 480px;
667
+ padding: 28px;
668
+ display: flex;
669
+ flex-direction: column;
670
+ gap: 20px;
671
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255,255,255,0.03);
672
+ animation: modalIn 0.15s ease;
673
+ }
674
+
675
+ @keyframes modalIn {
676
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
677
+ to { opacity: 1; transform: translateY(0) scale(1); }
678
+ }
679
+
680
+ .modal h2 {
681
+ font-size: 17px;
682
+ font-weight: 600;
683
+ color: var(--text-primary);
684
+ letter-spacing: -0.01em;
685
+ }
686
+
687
+ /* Form elements */
688
+ .form-group {
689
+ display: flex;
690
+ flex-direction: column;
691
+ gap: 6px;
692
+ }
693
+
694
+ .form-label {
695
+ font-size: 12px;
696
+ font-weight: 500;
697
+ color: var(--text-secondary);
698
+ letter-spacing: 0.01em;
699
+ }
700
+
701
+ .form-input,
702
+ .form-select {
703
+ background: var(--bg-inset);
704
+ border: 1px solid var(--border);
705
+ border-radius: var(--radius-xs);
706
+ padding: 9px 14px;
707
+ color: var(--text-primary);
708
+ font-family: var(--font-sans);
709
+ font-size: 13px;
710
+ outline: none;
711
+ transition: border-color 0.15s, box-shadow 0.15s;
712
+ width: 100%;
713
+ }
714
+
715
+ .form-input:focus,
716
+ .form-select:focus {
717
+ border-color: var(--accent);
718
+ box-shadow: 0 0 0 2px var(--accent-glow);
719
+ }
720
+
721
+ .form-input::placeholder {
722
+ color: var(--text-muted);
723
+ }
724
+
725
+ .form-select option {
726
+ background: var(--bg-body);
727
+ }
728
+
729
+ .form-hint {
730
+ font-size: 11px;
731
+ color: var(--text-muted);
732
+ }
733
+
734
+ .modal-actions {
735
+ display: flex;
736
+ justify-content: flex-end;
737
+ gap: 8px;
738
+ padding-top: 4px;
739
+ }
740
+
741
+ /* Snapshot list */
742
+ .snap-list {
743
+ display: flex;
744
+ flex-direction: column;
745
+ gap: 6px;
746
+ max-height: 240px;
747
+ overflow-y: auto;
748
+ }
749
+
750
+ .snap-list::-webkit-scrollbar { width: 4px; }
751
+ .snap-list::-webkit-scrollbar-track { background: transparent; }
752
+ .snap-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
753
+
754
+ .snap-item {
755
+ display: flex;
756
+ align-items: center;
757
+ justify-content: space-between;
758
+ padding: 9px 14px;
759
+ background: var(--bg-elevated);
760
+ border: 1px solid var(--border-subtle);
761
+ border-radius: var(--radius-xs);
762
+ gap: 10px;
763
+ transition: border-color 0.15s;
764
+ }
765
+
766
+ .snap-item:hover {
767
+ border-color: var(--border);
768
+ }
769
+
770
+ .snap-label {
771
+ font-size: 12px;
772
+ color: var(--text-primary);
773
+ font-family: var(--font-mono);
774
+ overflow: hidden;
775
+ text-overflow: ellipsis;
776
+ white-space: nowrap;
777
+ }
778
+
779
+ .snap-empty {
780
+ text-align: center;
781
+ color: var(--text-muted);
782
+ font-size: 12px;
783
+ padding: 20px;
784
+ }
785
+
786
+ .snap-save-row {
787
+ display: flex;
788
+ gap: 8px;
789
+ }
790
+
791
+ .snap-save-row .form-input {
792
+ flex: 1;
793
+ }
794
+
795
+ /* ============================================================
796
+ Toast
797
+ ============================================================ */
798
+
799
+ #toast-container {
800
+ position: fixed;
801
+ bottom: 24px;
802
+ right: 24px;
803
+ z-index: 300;
804
+ display: flex;
805
+ flex-direction: column;
806
+ gap: 8px;
807
+ pointer-events: none;
808
+ }
809
+
810
+ .toast {
811
+ background: var(--bg-elevated);
812
+ border: 1px solid var(--border);
813
+ border-radius: var(--radius-xs);
814
+ padding: 10px 16px;
815
+ font-size: 13px;
816
+ color: var(--text-primary);
817
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
818
+ animation: toastIn 0.2s ease;
819
+ max-width: 320px;
820
+ }
821
+
822
+ .toast.toast-error {
823
+ border-color: rgba(239, 68, 68, 0.3);
824
+ color: var(--red);
825
+ }
826
+
827
+ .toast.toast-success {
828
+ border-color: rgba(34, 197, 94, 0.3);
829
+ color: var(--green);
830
+ }
831
+
832
+ @keyframes toastIn {
833
+ from { opacity: 0; transform: translateY(8px); }
834
+ to { opacity: 1; transform: translateY(0); }
835
+ }
836
+
837
+ /* ============================================================
838
+ Changes badge & modal
839
+ ============================================================ */
840
+
841
+ .changes-file-list {
842
+ max-height: 340px;
843
+ overflow-y: auto;
844
+ margin: 4px 0;
845
+ }
846
+
847
+ .changes-file-item {
848
+ display: flex;
849
+ align-items: center;
850
+ gap: 10px;
851
+ padding: 8px 10px;
852
+ border-bottom: 1px solid var(--border-subtle);
853
+ font-size: 13px;
854
+ transition: background 0.1s;
855
+ }
856
+ .changes-file-item:hover { background: rgba(255,255,255,0.02); }
857
+ .changes-file-item input[type="checkbox"] { flex-shrink: 0; accent-color: var(--accent); }
858
+
859
+ .changes-status {
860
+ font-size: 10px;
861
+ font-weight: 600;
862
+ padding: 2px 7px;
863
+ border-radius: var(--radius-xs);
864
+ flex-shrink: 0;
865
+ letter-spacing: 0.02em;
866
+ }
867
+ .changes-status-mod { background: var(--blue-dim); color: var(--blue); }
868
+ .changes-status-new { background: var(--green-dim); color: var(--green); }
869
+ .changes-status-del { background: var(--red-dim); color: var(--red); }
870
+
871
+ .changes-path {
872
+ font-family: var(--font-mono);
873
+ font-size: 11px;
874
+ color: var(--text-muted);
875
+ overflow: hidden;
876
+ text-overflow: ellipsis;
877
+ white-space: nowrap;
878
+ flex: 1;
879
+ }
880
+
881
+ .changes-section-title {
882
+ font-size: 10px;
883
+ font-weight: 600;
884
+ text-transform: uppercase;
885
+ letter-spacing: 0.06em;
886
+ color: var(--text-muted);
887
+ padding: 10px 10px 6px;
888
+ }
889
+
890
+ .changes-action-item {
891
+ display: flex;
892
+ align-items: center;
893
+ gap: 10px;
894
+ padding: 10px;
895
+ border-bottom: 1px solid var(--border-subtle);
896
+ }
897
+
898
+ .changes-action-dot {
899
+ width: 7px;
900
+ height: 7px;
901
+ border-radius: 50%;
902
+ background: var(--accent);
903
+ flex-shrink: 0;
904
+ box-shadow: 0 0 6px var(--accent-glow);
905
+ }
906
+
907
+ .changes-action-body {
908
+ flex: 1;
909
+ display: flex;
910
+ align-items: center;
911
+ gap: 10px;
912
+ }
913
+
914
+ .changes-action-desc {
915
+ font-size: 13px;
916
+ color: var(--text-primary);
917
+ font-weight: 500;
918
+ }
919
+
920
+ .changes-action-time {
921
+ font-size: 11px;
922
+ color: var(--text-muted);
923
+ flex-shrink: 0;
924
+ font-family: var(--font-mono);
925
+ }
926
+
927
+ .changes-files-detail { margin-top: 4px; }
928
+ .changes-files-detail summary { list-style: none; cursor: pointer; }
929
+ .changes-files-detail summary::marker { display: none; }
930
+ .changes-files-detail summary::-webkit-details-summary { display: none; }
931
+
932
+ /* ============================================================
933
+ Workspace View — full-screen SPA route
934
+ ============================================================ */
935
+
936
+ .workspace {
937
+ position: fixed;
938
+ inset: 0;
939
+ background: var(--bg-body);
940
+ z-index: 100;
941
+ display: flex;
942
+ flex-direction: column;
943
+ }
944
+
945
+ .workspace.hidden {
946
+ display: none;
947
+ }
948
+
949
+ /* Top bar — floating over preview */
950
+ .ws-topbar {
951
+ position: absolute;
952
+ top: 0;
953
+ left: 0;
954
+ right: 0;
955
+ display: flex;
956
+ align-items: center;
957
+ gap: 8px;
958
+ padding: 8px 16px;
959
+ background: rgba(17, 17, 17, 0.85);
960
+ backdrop-filter: blur(8px);
961
+ border-bottom: 1px solid var(--border);
962
+ z-index: 110;
963
+ flex-shrink: 0;
964
+ }
965
+
966
+ .workspace-title {
967
+ font-size: 14px;
968
+ font-weight: 600;
969
+ }
970
+
971
+ /* Preview — full screen behind topbar */
972
+ .ws-preview-full {
973
+ position: absolute;
974
+ top: 44px;
975
+ left: 0;
976
+ right: 0;
977
+ bottom: 0;
978
+ overflow: hidden;
979
+ }
980
+
981
+ .ws-preview-iframe {
982
+ width: 100%;
983
+ height: 100%;
984
+ border: none;
985
+ background: #fff;
986
+ }
987
+
988
+ .ws-preview-fallback {
989
+ width: 100%;
990
+ height: 100%;
991
+ display: flex;
992
+ align-items: center;
993
+ justify-content: center;
994
+ }
995
+
996
+ /* Slide panel — right side */
997
+ .ws-panel {
998
+ position: absolute;
999
+ top: 44px;
1000
+ right: 0;
1001
+ bottom: 0;
1002
+ width: 340px;
1003
+ background: var(--bg-card);
1004
+ border-left: 1px solid var(--border);
1005
+ display: flex;
1006
+ flex-direction: column;
1007
+ transform: translateX(100%);
1008
+ transition: transform 0.25s ease;
1009
+ z-index: 120;
1010
+ box-shadow: -4px 0 20px rgba(0,0,0,0.3);
1011
+ }
1012
+
1013
+ .ws-panel.open {
1014
+ transform: translateX(0);
1015
+ }
1016
+
1017
+ .ws-panel-content {
1018
+ flex: 1;
1019
+ overflow-y: auto;
1020
+ padding: 16px;
1021
+ display: flex;
1022
+ flex-direction: column;
1023
+ gap: 16px;
1024
+ }
1025
+
1026
+ .ws-panel-actions {
1027
+ padding: 12px 16px;
1028
+ border-top: 1px solid var(--border);
1029
+ display: flex;
1030
+ flex-direction: column;
1031
+ gap: 8px;
1032
+ flex-shrink: 0;
1033
+ }
1034
+
1035
+ .workspace-section h3 {
1036
+ font-size: 13px;
1037
+ font-weight: 600;
1038
+ margin-bottom: 8px;
1039
+ color: var(--text-muted);
1040
+ }
1041
+
1042
+ .ws-log-panel {
1043
+ max-height: 200px;
1044
+ overflow-y: auto;
1045
+ background: var(--bg-inset);
1046
+ border-radius: 6px;
1047
+ padding: 8px;
1048
+ font-size: 11px;
1049
+ font-family: var(--font-mono);
1050
+ }
1051
+
1052
+ .ws-log-panel pre {
1053
+ margin: 0;
1054
+ white-space: pre-wrap;
1055
+ word-break: break-all;
1056
+ }
1057
+
1058
+ .ws-log-panel::-webkit-scrollbar { width: 4px; }
1059
+ .ws-log-panel::-webkit-scrollbar-track { background: transparent; }
1060
+ .ws-log-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
1061
+
1062
+ /* Browser error panel */
1063
+ .ws-browser-error-panel {
1064
+ max-height: 200px;
1065
+ overflow-y: auto;
1066
+ background: var(--bg-inset);
1067
+ border-radius: 6px;
1068
+ padding: 8px;
1069
+ font-size: 11px;
1070
+ font-family: var(--font-mono);
1071
+ }
1072
+
1073
+ .ws-browser-error-item {
1074
+ padding: 3px 0;
1075
+ border-bottom: 1px solid var(--border-subtle);
1076
+ display: flex;
1077
+ gap: 6px;
1078
+ align-items: baseline;
1079
+ }
1080
+
1081
+ .ws-browser-error-level {
1082
+ color: #ef4444;
1083
+ font-weight: 600;
1084
+ font-size: 10px;
1085
+ flex-shrink: 0;
1086
+ }
1087
+
1088
+ .ws-browser-error-msg {
1089
+ color: var(--text-primary);
1090
+ word-break: break-all;
1091
+ }
1092
+
1093
+ .ws-error-badge {
1094
+ background: #ef4444;
1095
+ color: white;
1096
+ font-size: 10px;
1097
+ padding: 1px 6px;
1098
+ border-radius: 10px;
1099
+ margin-left: 6px;
1100
+ }
1101
+
1102
+ .ws-file-item {
1103
+ display: flex;
1104
+ align-items: center;
1105
+ gap: 8px;
1106
+ padding: 3px 0;
1107
+ font-size: 13px;
1108
+ }
1109
+
1110
+ .ws-action-item {
1111
+ font-size: 13px;
1112
+ padding: 2px 0;
1113
+ color: var(--text-secondary);
1114
+ }
1115
+
1116
+ /* workspace-actions removed — replaced by ws-panel-actions */
1117
+
1118
+ .workspace-btn-row {
1119
+ display: flex;
1120
+ gap: 8px;
1121
+ flex-wrap: wrap;
1122
+ }
1123
+
1124
+ /* Hidden utility for header/grid when workspace is open */
1125
+ header.hidden {
1126
+ display: none;
1127
+ }
1128
+
1129
+ .grid.hidden {
1130
+ display: none;
1131
+ }
1132
+
1133
+ /* ============================================================
1134
+ Misc
1135
+ ============================================================ */
1136
+
1137
+ /* ============================================================
1138
+ Branch picker dropdown
1139
+ ============================================================ */
1140
+
1141
+ .branch-picker {
1142
+ position: relative;
1143
+ }
1144
+
1145
+ .branch-dropdown {
1146
+ display: none;
1147
+ position: absolute;
1148
+ top: calc(100% + 4px);
1149
+ left: 0;
1150
+ right: 0;
1151
+ max-height: 280px;
1152
+ overflow-y: auto;
1153
+ background: var(--bg-inset);
1154
+ border: 1px solid var(--border);
1155
+ border-radius: var(--radius-sm);
1156
+ z-index: 10;
1157
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
1158
+ }
1159
+
1160
+ .branch-dropdown.open {
1161
+ display: block;
1162
+ }
1163
+
1164
+ .branch-dropdown::-webkit-scrollbar { width: 4px; }
1165
+ .branch-dropdown::-webkit-scrollbar-track { background: transparent; }
1166
+ .branch-dropdown::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
1167
+
1168
+ .branch-group-label {
1169
+ font-size: 10px;
1170
+ font-weight: 600;
1171
+ text-transform: uppercase;
1172
+ letter-spacing: 0.06em;
1173
+ color: var(--text-muted);
1174
+ padding: 10px 14px 4px;
1175
+ position: sticky;
1176
+ top: 0;
1177
+ background: var(--bg-inset);
1178
+ }
1179
+
1180
+ .branch-item {
1181
+ display: flex;
1182
+ align-items: center;
1183
+ justify-content: space-between;
1184
+ padding: 8px 14px;
1185
+ cursor: pointer;
1186
+ transition: background 0.1s;
1187
+ gap: 8px;
1188
+ }
1189
+
1190
+ .branch-item:hover {
1191
+ background: var(--bg-elevated);
1192
+ }
1193
+
1194
+ .branch-item-name {
1195
+ font-size: 13px;
1196
+ font-family: var(--font-mono);
1197
+ color: var(--text-primary);
1198
+ overflow: hidden;
1199
+ text-overflow: ellipsis;
1200
+ white-space: nowrap;
1201
+ min-width: 0;
1202
+ }
1203
+
1204
+ .branch-item-meta {
1205
+ display: flex;
1206
+ align-items: center;
1207
+ gap: 6px;
1208
+ flex-shrink: 0;
1209
+ }
1210
+
1211
+ .branch-date {
1212
+ font-size: 11px;
1213
+ color: var(--text-muted);
1214
+ white-space: nowrap;
1215
+ }
1216
+
1217
+ .branch-tag {
1218
+ font-size: 9px;
1219
+ font-weight: 600;
1220
+ text-transform: uppercase;
1221
+ letter-spacing: 0.04em;
1222
+ padding: 1px 6px;
1223
+ border-radius: 4px;
1224
+ background: var(--amber-dim);
1225
+ color: var(--amber);
1226
+ }
1227
+
1228
+ .branch-empty {
1229
+ text-align: center;
1230
+ color: var(--text-muted);
1231
+ font-size: 12px;
1232
+ padding: 20px;
1233
+ }
1234
+
1235
+ /* ============================================================
1236
+ Pixel Characters — 산장 캠프 상태 표현
1237
+ ============================================================
1238
+ box-shadow pixel art, 4px per pixel unit.
1239
+ Each character ~10x10 grid = 40x40px display.
1240
+ ============================================================ */
1241
+
1242
+ .camp-avatar {
1243
+ position: relative;
1244
+ flex-shrink: 0;
1245
+ }
1246
+
1247
+ .camp-pixel-wrap {
1248
+ position: relative;
1249
+ flex-shrink: 0;
1250
+ width: 52px;
1251
+ height: 52px;
1252
+ display: flex;
1253
+ align-items: center;
1254
+ justify-content: center;
1255
+ background: var(--bg-inset);
1256
+ border-radius: var(--radius-sm);
1257
+ border: 1px solid var(--border-subtle);
1258
+ overflow: hidden;
1259
+ }
1260
+
1261
+ .camp-pixel {
1262
+ width: 4px;
1263
+ height: 4px;
1264
+ position: absolute;
1265
+ flex-shrink: 0;
1266
+ }
1267
+
1268
+ /* Center each character type within the wrap */
1269
+ .camp-pixel-running { left: 10px; top: 10px; }
1270
+ .camp-pixel-starting { left: 12px; top: 10px; }
1271
+ .camp-pixel-stopped { left: 10px; top: 12px; }
1272
+ .camp-pixel-error { left: 6px; top: 12px; }
1273
+
1274
+ /* --- Speech bubble --- */
1275
+ .camp-bubble {
1276
+ position: absolute;
1277
+ top: -20px;
1278
+ left: 50%;
1279
+ transform: translateX(-50%);
1280
+ background: var(--bg-elevated);
1281
+ border: 1px solid var(--border);
1282
+ border-radius: 8px;
1283
+ padding: 3px 8px;
1284
+ font-size: 10px;
1285
+ white-space: nowrap;
1286
+ color: var(--text-secondary);
1287
+ pointer-events: none;
1288
+ opacity: 0;
1289
+ animation: bubble-show 8s ease infinite;
1290
+ z-index: 5;
1291
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
1292
+ }
1293
+
1294
+ .camp-bubble::after {
1295
+ content: '';
1296
+ position: absolute;
1297
+ bottom: -5px;
1298
+ left: 50%;
1299
+ transform: translateX(-50%);
1300
+ border-left: 5px solid transparent;
1301
+ border-right: 5px solid transparent;
1302
+ border-top: 5px solid var(--bg-elevated);
1303
+ }
1304
+
1305
+ @keyframes bubble-show {
1306
+ 0%, 70%, 100% { opacity: 0; transform: translateX(-50%) translateY(3px); }
1307
+ 75%, 92% { opacity: 1; transform: translateX(-50%) translateY(0); }
1308
+ 95% { opacity: 0; transform: translateX(-50%) translateY(-2px); }
1309
+ }
1310
+
1311
+ /* --- Card scene background --- */
1312
+ .card-scene {
1313
+ position: absolute;
1314
+ bottom: 0;
1315
+ left: 0;
1316
+ right: 0;
1317
+ height: 60px;
1318
+ pointer-events: none;
1319
+ overflow: hidden;
1320
+ border-radius: 0 0 var(--radius) var(--radius);
1321
+ opacity: 0.06;
1322
+ }
1323
+
1324
+ .card-scene-mountains {
1325
+ background: linear-gradient(135deg, transparent 33%, var(--text-muted) 33%, var(--text-muted) 40%, transparent 40%),
1326
+ linear-gradient(160deg, transparent 45%, var(--text-muted) 45%, var(--text-muted) 55%, transparent 55%),
1327
+ linear-gradient(145deg, transparent 55%, var(--text-muted) 55%, var(--text-muted) 65%, transparent 65%);
1328
+ height: 100%;
1329
+ }
1330
+
1331
+ .card-scene-stars {
1332
+ background-image:
1333
+ radial-gradient(1px 1px at 20% 30%, var(--text-muted) 50%, transparent 50%),
1334
+ radial-gradient(1px 1px at 60% 15%, var(--text-muted) 50%, transparent 50%),
1335
+ radial-gradient(1px 1px at 80% 45%, var(--text-muted) 50%, transparent 50%),
1336
+ radial-gradient(1px 1px at 35% 60%, var(--text-muted) 50%, transparent 50%),
1337
+ radial-gradient(1px 1px at 90% 25%, var(--text-muted) 50%, transparent 50%);
1338
+ height: 100%;
1339
+ }
1340
+
1341
+ /* --- Running: 셰르파 등산 (4px/pixel) --- */
1342
+ .camp-pixel-running {
1343
+ animation: sherpa-walk 0.6s steps(1) infinite;
1344
+ color: transparent;
1345
+ box-shadow:
1346
+ /* hat */
1347
+ 8px 0 0 #5b8c5a, 12px 0 0 #5b8c5a, 16px 0 0 #5b8c5a,
1348
+ /* head + face */
1349
+ 8px 4px 0 #f4c07a, 12px 4px 0 #f4c07a, 16px 4px 0 #f4c07a,
1350
+ 8px 8px 0 #f4c07a, 12px 8px 0 #2a2040, 16px 8px 0 #2a2040,
1351
+ /* body */
1352
+ 4px 12px 0 #e85d3a, 8px 12px 0 #e85d3a, 12px 12px 0 #e85d3a, 16px 12px 0 #e85d3a,
1353
+ 4px 16px 0 #e85d3a, 8px 16px 0 #e85d3a, 12px 16px 0 #e85d3a, 16px 16px 0 #e85d3a,
1354
+ /* backpack */
1355
+ 20px 12px 0 #6366f1, 20px 16px 0 #6366f1, 24px 12px 0 #5254cc,
1356
+ /* arms */
1357
+ 0px 16px 0 #f4c07a, 24px 16px 0 #f4c07a,
1358
+ /* legs */
1359
+ 4px 20px 0 #3b3f5c, 16px 20px 0 #3b3f5c,
1360
+ 0px 24px 0 #3b3f5c, 20px 24px 0 #3b3f5c,
1361
+ /* boots */
1362
+ 0px 28px 0 #5b4a3a, 20px 28px 0 #5b4a3a;
1363
+ }
1364
+
1365
+ @keyframes sherpa-walk {
1366
+ 0%, 100% {
1367
+ box-shadow:
1368
+ 8px 0 0 #5b8c5a, 12px 0 0 #5b8c5a, 16px 0 0 #5b8c5a,
1369
+ 8px 4px 0 #f4c07a, 12px 4px 0 #f4c07a, 16px 4px 0 #f4c07a,
1370
+ 8px 8px 0 #f4c07a, 12px 8px 0 #2a2040, 16px 8px 0 #2a2040,
1371
+ 4px 12px 0 #e85d3a, 8px 12px 0 #e85d3a, 12px 12px 0 #e85d3a, 16px 12px 0 #e85d3a,
1372
+ 4px 16px 0 #e85d3a, 8px 16px 0 #e85d3a, 12px 16px 0 #e85d3a, 16px 16px 0 #e85d3a,
1373
+ 20px 12px 0 #6366f1, 20px 16px 0 #6366f1, 24px 12px 0 #5254cc,
1374
+ 0px 16px 0 #f4c07a, 24px 16px 0 #f4c07a,
1375
+ 4px 20px 0 #3b3f5c, 16px 20px 0 #3b3f5c,
1376
+ 0px 24px 0 #3b3f5c, 20px 24px 0 #3b3f5c,
1377
+ 0px 28px 0 #5b4a3a, 20px 28px 0 #5b4a3a;
1378
+ }
1379
+ 50% {
1380
+ box-shadow:
1381
+ 8px 0 0 #5b8c5a, 12px 0 0 #5b8c5a, 16px 0 0 #5b8c5a,
1382
+ 8px 4px 0 #f4c07a, 12px 4px 0 #f4c07a, 16px 4px 0 #f4c07a,
1383
+ 8px 8px 0 #f4c07a, 12px 8px 0 #2a2040, 16px 8px 0 #2a2040,
1384
+ 4px 12px 0 #e85d3a, 8px 12px 0 #e85d3a, 12px 12px 0 #e85d3a, 16px 12px 0 #e85d3a,
1385
+ 4px 16px 0 #e85d3a, 8px 16px 0 #e85d3a, 12px 16px 0 #e85d3a, 16px 16px 0 #e85d3a,
1386
+ 20px 12px 0 #6366f1, 20px 16px 0 #6366f1, 24px 12px 0 #5254cc,
1387
+ 24px 12px 0 #f4c07a, 0px 12px 0 #f4c07a,
1388
+ 8px 20px 0 #3b3f5c, 12px 20px 0 #3b3f5c,
1389
+ 12px 24px 0 #3b3f5c, 4px 24px 0 #3b3f5c,
1390
+ 12px 28px 0 #5b4a3a, 4px 28px 0 #5b4a3a;
1391
+ }
1392
+ }
1393
+
1394
+ /* --- Starting: 캠프파이어 (4px/pixel) --- */
1395
+ .camp-pixel-starting {
1396
+ animation: fire-flicker 0.35s steps(1) infinite;
1397
+ color: transparent;
1398
+ box-shadow:
1399
+ /* logs */
1400
+ 0px 28px 0 #5b4a3a, 4px 28px 0 #5b4a3a, 8px 28px 0 #5b4a3a,
1401
+ 12px 28px 0 #5b4a3a, 16px 28px 0 #5b4a3a, 20px 28px 0 #5b4a3a, 24px 28px 0 #5b4a3a,
1402
+ 4px 24px 0 #5b4a3a, 20px 24px 0 #5b4a3a,
1403
+ /* fire */
1404
+ 8px 24px 0 #e85d3a, 12px 24px 0 #f59e0b, 16px 24px 0 #e85d3a,
1405
+ 4px 20px 0 #e85d3a, 8px 20px 0 #f59e0b, 12px 20px 0 #fbbf24, 16px 20px 0 #f59e0b, 20px 20px 0 #e85d3a,
1406
+ 8px 16px 0 #f59e0b, 12px 16px 0 #fbbf24, 16px 16px 0 #f59e0b,
1407
+ 8px 12px 0 #e85d3a, 12px 12px 0 #f59e0b, 16px 12px 0 #e85d3a,
1408
+ 12px 8px 0 #f59e0b,
1409
+ /* sparks */
1410
+ 4px 8px 0 #fbbf24, 20px 4px 0 #f59e0b;
1411
+ }
1412
+
1413
+ @keyframes fire-flicker {
1414
+ 0%, 100% {
1415
+ box-shadow:
1416
+ 0px 28px 0 #5b4a3a, 4px 28px 0 #5b4a3a, 8px 28px 0 #5b4a3a,
1417
+ 12px 28px 0 #5b4a3a, 16px 28px 0 #5b4a3a, 20px 28px 0 #5b4a3a, 24px 28px 0 #5b4a3a,
1418
+ 4px 24px 0 #5b4a3a, 20px 24px 0 #5b4a3a,
1419
+ 8px 24px 0 #e85d3a, 12px 24px 0 #f59e0b, 16px 24px 0 #e85d3a,
1420
+ 4px 20px 0 #e85d3a, 8px 20px 0 #f59e0b, 12px 20px 0 #fbbf24, 16px 20px 0 #f59e0b, 20px 20px 0 #e85d3a,
1421
+ 8px 16px 0 #f59e0b, 12px 16px 0 #fbbf24, 16px 16px 0 #f59e0b,
1422
+ 8px 12px 0 #e85d3a, 12px 12px 0 #f59e0b, 16px 12px 0 #e85d3a,
1423
+ 12px 8px 0 #f59e0b,
1424
+ 4px 8px 0 #fbbf24, 20px 4px 0 #f59e0b;
1425
+ }
1426
+ 50% {
1427
+ box-shadow:
1428
+ 0px 28px 0 #5b4a3a, 4px 28px 0 #5b4a3a, 8px 28px 0 #5b4a3a,
1429
+ 12px 28px 0 #5b4a3a, 16px 28px 0 #5b4a3a, 20px 28px 0 #5b4a3a, 24px 28px 0 #5b4a3a,
1430
+ 4px 24px 0 #5b4a3a, 20px 24px 0 #5b4a3a,
1431
+ 8px 24px 0 #f59e0b, 12px 24px 0 #e85d3a, 16px 24px 0 #f59e0b,
1432
+ 4px 20px 0 #f59e0b, 8px 20px 0 #e85d3a, 12px 20px 0 #f59e0b, 16px 20px 0 #fbbf24, 20px 20px 0 #f59e0b,
1433
+ 4px 16px 0 #e85d3a, 8px 16px 0 #fbbf24, 12px 16px 0 #f59e0b, 16px 16px 0 #fbbf24, 20px 16px 0 #e85d3a,
1434
+ 8px 12px 0 #f59e0b, 12px 12px 0 #e85d3a, 16px 12px 0 #f59e0b,
1435
+ 12px 8px 0 #e85d3a, 8px 4px 0 #f59e0b,
1436
+ 20px 8px 0 #fbbf24, 0px 4px 0 #e85d3a;
1437
+ }
1438
+ }
1439
+
1440
+ /* --- Stopped: 텐트 + zzz (4px/pixel) --- */
1441
+ .camp-pixel-stopped {
1442
+ color: transparent;
1443
+ box-shadow:
1444
+ /* tent peak */
1445
+ 12px 4px 0 #4a5170, 16px 4px 0 #4a5170,
1446
+ 8px 8px 0 #4a5170, 12px 8px 0 #3b3f5c, 16px 8px 0 #3b3f5c, 20px 8px 0 #4a5170,
1447
+ 4px 12px 0 #4a5170, 8px 12px 0 #3b3f5c, 12px 12px 0 #3b3f5c, 16px 12px 0 #3b3f5c, 20px 12px 0 #3b3f5c, 24px 12px 0 #4a5170,
1448
+ 0px 16px 0 #4a5170, 4px 16px 0 #3b3f5c, 8px 16px 0 #3b3f5c, 12px 16px 0 #3b3f5c, 16px 16px 0 #3b3f5c, 20px 16px 0 #3b3f5c, 24px 16px 0 #3b3f5c, 28px 16px 0 #4a5170,
1449
+ 0px 20px 0 #4a5170, 4px 20px 0 #3b3f5c, 8px 20px 0 #3b3f5c, 12px 20px 0 #1c2030, 16px 20px 0 #1c2030, 20px 20px 0 #3b3f5c, 24px 20px 0 #3b3f5c, 28px 20px 0 #4a5170,
1450
+ /* ground */
1451
+ 0px 24px 0 #1c2030, 4px 24px 0 #1c2030, 8px 24px 0 #1c2030, 12px 24px 0 #1c2030,
1452
+ 16px 24px 0 #1c2030, 20px 24px 0 #1c2030, 24px 24px 0 #1c2030, 28px 24px 0 #1c2030;
1453
+ }
1454
+
1455
+ /* Zzz animation */
1456
+ .camp-zzz {
1457
+ position: absolute;
1458
+ top: -2px;
1459
+ right: -6px;
1460
+ font-size: 9px;
1461
+ color: var(--text-muted);
1462
+ animation: zzz-float 2s ease-in-out infinite;
1463
+ font-family: var(--font-mono);
1464
+ letter-spacing: -1px;
1465
+ z-index: 3;
1466
+ }
1467
+ @keyframes zzz-float {
1468
+ 0%, 100% { opacity: 0.3; transform: translateY(0); }
1469
+ 50% { opacity: 0.8; transform: translateY(-4px); }
1470
+ }
1471
+
1472
+ /* --- Error: 넘어진 셰르파 (4px/pixel) --- */
1473
+ .camp-pixel-error {
1474
+ animation: error-shake 0.3s ease-in-out infinite alternate;
1475
+ color: transparent;
1476
+ box-shadow:
1477
+ /* head on ground */
1478
+ 0px 20px 0 #f4c07a, 4px 20px 0 #f4c07a, 8px 20px 0 #f4c07a,
1479
+ 0px 16px 0 #5b8c5a, 4px 16px 0 #5b8c5a,
1480
+ 4px 20px 0 #2a2040, 8px 20px 0 #2a2040,
1481
+ /* body sprawled */
1482
+ 12px 20px 0 #e85d3a, 16px 20px 0 #e85d3a, 20px 20px 0 #e85d3a, 24px 20px 0 #e85d3a,
1483
+ 12px 16px 0 #e85d3a, 16px 16px 0 #e85d3a, 20px 16px 0 #e85d3a,
1484
+ /* backpack fallen */
1485
+ 24px 16px 0 #6366f1, 28px 16px 0 #5254cc,
1486
+ /* legs up */
1487
+ 28px 20px 0 #3b3f5c, 28px 24px 0 #3b3f5c,
1488
+ 32px 20px 0 #3b3f5c, 32px 12px 0 #5b4a3a,
1489
+ /* arm reaching */
1490
+ 8px 16px 0 #f4c07a,
1491
+ /* sweat/stars */
1492
+ 0px 8px 0 #f59e0b, 8px 4px 0 #f59e0b, 16px 8px 0 #f59e0b,
1493
+ /* ground */
1494
+ 0px 24px 0 #1c2030, 4px 24px 0 #1c2030, 8px 24px 0 #1c2030, 12px 24px 0 #1c2030,
1495
+ 16px 24px 0 #1c2030, 20px 24px 0 #1c2030, 24px 24px 0 #1c2030, 28px 24px 0 #1c2030;
1496
+ }
1497
+
1498
+ @keyframes error-shake {
1499
+ from { transform: translateX(-1px); }
1500
+ to { transform: translateX(1px); }
1501
+ }
1502
+
1503
+
1504
+ /* ============================================================
1505
+ Portal Home
1506
+ ============================================================ */
1507
+
1508
+ #portal {
1509
+ max-width: 720px;
1510
+ margin: 0 auto;
1511
+ padding: 20px;
1512
+ }
1513
+
1514
+ #portal.hidden {
1515
+ display: none;
1516
+ }
1517
+
1518
+ .portal-section {
1519
+ margin-bottom: 32px;
1520
+ }
1521
+
1522
+ .portal-section.hidden {
1523
+ display: none;
1524
+ }
1525
+
1526
+ .portal-section-title {
1527
+ font-size: 15px;
1528
+ font-weight: 600;
1529
+ color: var(--text-muted);
1530
+ margin-bottom: 12px;
1531
+ }
1532
+
1533
+ .portal-work-list {
1534
+ display: flex;
1535
+ flex-direction: column;
1536
+ gap: 4px;
1537
+ }
1538
+
1539
+ .portal-work-item {
1540
+ display: flex;
1541
+ align-items: center;
1542
+ justify-content: space-between;
1543
+ padding: 12px 16px;
1544
+ background: var(--bg-card);
1545
+ border: 1px solid var(--border);
1546
+ border-radius: 8px;
1547
+ cursor: pointer;
1548
+ transition: border-color 0.15s;
1549
+ }
1550
+
1551
+ .portal-work-item:hover {
1552
+ border-color: var(--accent);
1553
+ }
1554
+
1555
+ .portal-work-left {
1556
+ display: flex;
1557
+ align-items: center;
1558
+ gap: 12px;
1559
+ }
1560
+
1561
+ .portal-work-icon {
1562
+ font-size: 16px;
1563
+ }
1564
+
1565
+ .portal-work-title {
1566
+ font-size: 14px;
1567
+ font-weight: 500;
1568
+ }
1569
+
1570
+ .portal-work-meta {
1571
+ font-size: 12px;
1572
+ color: var(--text-muted);
1573
+ margin-top: 2px;
1574
+ }
1575
+
1576
+ .portal-work-status {
1577
+ font-size: 12px;
1578
+ padding: 3px 8px;
1579
+ border-radius: 4px;
1580
+ white-space: nowrap;
1581
+ }
1582
+
1583
+ .portal-status-pending { background: #f5a62320; color: #f5a623; }
1584
+ .portal-status-approved { background: #4caf5020; color: #4caf50; }
1585
+ .portal-status-changes { background: #f4433620; color: #f44336; }
1586
+ .portal-status-draft { background: #9e9e9e20; color: #9e9e9e; }
1587
+ .portal-status-active { background: #2196f320; color: #2196f3; }
1588
+
1589
+ .portal-empty {
1590
+ padding: 24px;
1591
+ text-align: center;
1592
+ color: var(--text-muted);
1593
+ font-size: 13px;
1594
+ }
1595
+
1596
+ .portal-loading {
1597
+ padding: 24px;
1598
+ text-align: center;
1599
+ color: var(--text-muted);
1600
+ font-size: 13px;
1601
+ }
1602
+
1603
+ .portal-quickstart {
1604
+ display: flex;
1605
+ gap: 8px;
1606
+ }
1607
+
1608
+ .portal-quickstart-input {
1609
+ flex: 1;
1610
+ }
1611
+
1612
+ .portal-quickstart-hint {
1613
+ font-size: 12px;
1614
+ color: var(--text-muted);
1615
+ margin-top: 6px;
1616
+ }
1617
+
1618
+ /* Portal camps section uses the existing .grid layout */
1619
+ #portal-camps-section .grid {
1620
+ padding: 0;
1621
+ margin: 0;
1622
+ max-width: none;
1623
+ grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
1624
+ }
1625
+
1626
+ .hidden { display: none !important; }
1627
+
1628
+ .ws-file-new {
1629
+ animation: highlight-fade 2s ease-out;
1630
+ }
1631
+ @keyframes highlight-fade {
1632
+ 0% { background: rgba(255, 214, 0, 0.25); }
1633
+ 100% { background: transparent; }
1634
+ }
1635
+ .ws-changes-summary {
1636
+ font-size: 12px;
1637
+ color: var(--text-muted);
1638
+ font-weight: normal;
1639
+ margin-left: 4px;
1640
+ }
1641
+ .ws-unsaved-section {
1642
+ background: rgba(255, 214, 0, 0.08);
1643
+ border: 1px solid rgba(255, 214, 0, 0.2);
1644
+ border-radius: 8px;
1645
+ padding: 12px !important;
1646
+ }
1647
+ .ws-unsaved-section.ws-no-changes {
1648
+ background: transparent;
1649
+ border-color: transparent;
1650
+ }
1651
+ .ws-summary-text {
1652
+ font-size: 14px;
1653
+ color: var(--text-primary);
1654
+ padding: 4px 0 8px;
1655
+ line-height: 1.4;
1656
+ }
1657
+ .ws-save-row {
1658
+ display: flex;
1659
+ gap: 8px;
1660
+ align-items: center;
1661
+ margin-bottom: 8px;
1662
+ }
1663
+ .btn-save {
1664
+ background: var(--yellow, #eab308);
1665
+ color: #000;
1666
+ font-weight: 600;
1667
+ flex: 1;
1668
+ padding: 8px;
1669
+ border-radius: 6px;
1670
+ }
1671
+ .ws-autosave-toggle {
1672
+ display: flex;
1673
+ align-items: center;
1674
+ gap: 4px;
1675
+ font-size: 11px;
1676
+ color: var(--text-muted);
1677
+ cursor: pointer;
1678
+ flex-shrink: 0;
1679
+ }
1680
+ .ws-autosave-toggle input {
1681
+ accent-color: var(--accent);
1682
+ }
1683
+ .btn-save:hover:not(:disabled) {
1684
+ background: #ca9a06;
1685
+ }
1686
+ .btn-save:disabled {
1687
+ opacity: 0.4;
1688
+ cursor: not-allowed;
1689
+ }
1690
+ .ws-file-details,
1691
+ .ws-log-details {
1692
+ font-size: 13px;
1693
+ }
1694
+ .ws-file-details summary,
1695
+ .ws-log-details summary {
1696
+ cursor: pointer;
1697
+ color: var(--text-muted);
1698
+ font-size: 12px;
1699
+ user-select: none;
1700
+ padding: 4px 0;
1701
+ }
1702
+ .ws-file-details summary:hover,
1703
+ .ws-log-details summary:hover {
1704
+ color: var(--text-secondary);
1705
+ }
1706
+ .ws-log-details .ws-log-panel {
1707
+ margin-top: 8px;
1708
+ }
1709
+ .ws-commit-item {
1710
+ display: flex;
1711
+ justify-content: space-between;
1712
+ align-items: baseline;
1713
+ gap: 8px;
1714
+ padding: 4px 0;
1715
+ font-size: 13px;
1716
+ border-bottom: 1px solid var(--border);
1717
+ }
1718
+ .ws-commit-item:last-child {
1719
+ border-bottom: none;
1720
+ }
1721
+ .ws-commit-msg {
1722
+ color: var(--text-primary);
1723
+ }
1724
+ .ws-commit-date {
1725
+ color: var(--text-muted);
1726
+ font-size: 11px;
1727
+ white-space: nowrap;
1728
+ flex-shrink: 0;
1729
+ }
1730
+ .ws-revert-btn {
1731
+ opacity: 0;
1732
+ transition: opacity 0.15s;
1733
+ font-size: 12px;
1734
+ padding: 2px 6px;
1735
+ flex-shrink: 0;
1736
+ }
1737
+ .ws-commit-item:hover .ws-revert-btn {
1738
+ opacity: 1;
1739
+ }
1740
+
1741
+ /* === Gamification: Unsaved pixel blocks === */
1742
+ .ws-blocks {
1743
+ display: flex;
1744
+ align-items: flex-end;
1745
+ gap: 2px;
1746
+ height: 36px;
1747
+ padding: 4px 0;
1748
+ }
1749
+ .ws-block {
1750
+ width: 20px;
1751
+ border-radius: 0;
1752
+ image-rendering: pixelated;
1753
+ border: 2px solid rgba(0,0,0,0.3);
1754
+ animation: block-drop 0.3s steps(4);
1755
+ transition: height 0.15s steps(3);
1756
+ }
1757
+ .ws-block-mod { background: #f59e0b; height: 16px; }
1758
+ .ws-block-new { background: #22c55e; height: 24px; }
1759
+ .ws-block-del { background: #ef4444; height: 12px; }
1760
+
1761
+ @keyframes block-drop {
1762
+ 0% { transform: translateY(-16px); opacity: 0; }
1763
+ 50% { transform: translateY(2px); }
1764
+ 100% { transform: translateY(0); opacity: 1; }
1765
+ }
1766
+
1767
+ .ws-blocks.ws-blocks-wobble {
1768
+ animation: blocks-wobble 1.5s steps(4) infinite;
1769
+ }
1770
+ @keyframes blocks-wobble {
1771
+ 0%, 100% { transform: rotate(0deg); }
1772
+ 25% { transform: rotate(1deg); }
1773
+ 75% { transform: rotate(-1deg); }
1774
+ }
1775
+
1776
+ /* === Gamification: Save effects === */
1777
+ .ws-blocks.ws-blocks-flush .ws-block {
1778
+ animation: block-flush 0.4s steps(6) forwards;
1779
+ }
1780
+ @keyframes block-flush {
1781
+ 0% { transform: translateY(0); opacity: 1; }
1782
+ 33% { transform: translateY(-8px); }
1783
+ 66% { transform: translateY(-20px) scale(0.5); opacity: 0.5; }
1784
+ 100% { transform: translateY(-30px) scale(0); opacity: 0; }
1785
+ }
1786
+
1787
+ /* Save history slide-in animation */
1788
+ .ws-commit-slide-in {
1789
+ animation: commit-slide-in 0.4s ease-out;
1790
+ }
1791
+ @keyframes commit-slide-in {
1792
+ 0% { transform: translateX(40px); opacity: 0; max-height: 0; }
1793
+ 50% { max-height: 40px; }
1794
+ 100% { transform: translateX(0); opacity: 1; max-height: 40px; }
1795
+ }
1796
+
1797
+ .ws-save-flash {
1798
+ position: fixed;
1799
+ top: 0; left: 0; right: 0; bottom: 0;
1800
+ background: rgba(251, 191, 36, 0.12);
1801
+ pointer-events: none;
1802
+ z-index: 9999;
1803
+ animation: save-flash 0.4s steps(3) forwards;
1804
+ }
1805
+ @keyframes save-flash {
1806
+ 0% { opacity: 1; }
1807
+ 33% { opacity: 0.6; }
1808
+ 66% { opacity: 0.2; }
1809
+ 100% { opacity: 0; }
1810
+ }
1811
+
1812
+ .ws-sparkles {
1813
+ position: absolute;
1814
+ inset: -24px;
1815
+ pointer-events: none;
1816
+ overflow: visible;
1817
+ }
1818
+ .ws-sparkle {
1819
+ position: absolute;
1820
+ width: 4px;
1821
+ height: 4px;
1822
+ border-radius: 0;
1823
+ background: #fbbf24;
1824
+ animation: sparkle-fly 0.6s steps(6) forwards;
1825
+ }
1826
+ .ws-sparkle:nth-child(odd) { background: #f59e0b; }
1827
+ .ws-sparkle:nth-child(3n) { background: #fff; width: 2px; height: 2px; }
1828
+ @keyframes sparkle-fly {
1829
+ 0% { transform: translate(0, 0) scale(1); opacity: 1; }
1830
+ 50% { opacity: 1; }
1831
+ 100% { transform: translate(var(--sx), var(--sy)) scale(0); opacity: 0; }
1832
+ }
1833
+
1834
+ /* === Gamification: Topbar mini character === */
1835
+ .ws-mini-char {
1836
+ width: 2px;
1837
+ height: 2px;
1838
+ position: relative;
1839
+ margin: 4px 16px 4px 12px;
1840
+ flex-shrink: 0;
1841
+ }
1842
+ /* Sherpa load levels — backpack grows with unsaved changes */
1843
+ .ws-mini-char-running {
1844
+ animation: mini-walk 0.5s steps(1) infinite;
1845
+ box-shadow:
1846
+ 2px 0 0 #5b8c5a, 4px 0 0 #5b8c5a,
1847
+ 2px 2px 0 #f4c07a, 4px 2px 0 #f4c07a,
1848
+ 0px 4px 0 #e85d3a, 2px 4px 0 #e85d3a, 4px 4px 0 #e85d3a, 6px 4px 0 #e85d3a,
1849
+ 0px 6px 0 #3b3f5c, 4px 6px 0 #3b3f5c,
1850
+ 0px 8px 0 #5b4a3a, 4px 8px 0 #5b4a3a;
1851
+ }
1852
+ /* light load: small backpack */
1853
+ .ws-mini-char-load-light {
1854
+ animation: mini-walk 0.5s steps(1) infinite;
1855
+ box-shadow:
1856
+ 2px 0 0 #5b8c5a, 4px 0 0 #5b8c5a,
1857
+ 2px 2px 0 #f4c07a, 4px 2px 0 #f4c07a,
1858
+ 0px 4px 0 #e85d3a, 2px 4px 0 #e85d3a, 4px 4px 0 #e85d3a, 6px 4px 0 #e85d3a,
1859
+ 8px 4px 0 #6366f1,
1860
+ 0px 6px 0 #3b3f5c, 4px 6px 0 #3b3f5c,
1861
+ 0px 8px 0 #5b4a3a, 4px 8px 0 #5b4a3a;
1862
+ }
1863
+ /* medium load: bigger backpack, slower */
1864
+ .ws-mini-char-load-medium {
1865
+ animation: mini-walk-slow 0.8s steps(1) infinite;
1866
+ box-shadow:
1867
+ 2px 0 0 #5b8c5a, 4px 0 0 #5b8c5a,
1868
+ 2px 2px 0 #f4c07a, 4px 2px 0 #f4c07a,
1869
+ 0px 4px 0 #e85d3a, 2px 4px 0 #e85d3a, 4px 4px 0 #e85d3a, 6px 4px 0 #e85d3a,
1870
+ 8px 2px 0 #6366f1, 8px 4px 0 #6366f1, 10px 4px 0 #5254cc,
1871
+ 0px 6px 0 #3b3f5c, 4px 6px 0 #3b3f5c,
1872
+ 0px 8px 0 #5b4a3a, 4px 8px 0 #5b4a3a;
1873
+ }
1874
+ /* heavy load: huge backpack, stumbling, sweat */
1875
+ .ws-mini-char-load-heavy {
1876
+ animation: mini-stumble 1.2s steps(1) infinite;
1877
+ box-shadow:
1878
+ 2px 0 0 #5b8c5a, 4px 0 0 #5b8c5a,
1879
+ -2px -2px 0 #64b5f6,
1880
+ 2px 2px 0 #f4c07a, 4px 2px 0 #f4c07a,
1881
+ 0px 4px 0 #e85d3a, 2px 4px 0 #e85d3a, 4px 4px 0 #e85d3a, 6px 4px 0 #e85d3a,
1882
+ 8px 0 0 #6366f1, 10px 0 0 #5254cc,
1883
+ 8px 2px 0 #6366f1, 10px 2px 0 #5254cc,
1884
+ 8px 4px 0 #6366f1, 10px 4px 0 #5254cc, 12px 4px 0 #4a46b0,
1885
+ 0px 6px 0 #3b3f5c, 4px 6px 0 #3b3f5c,
1886
+ 0px 8px 0 #5b4a3a, 4px 8px 0 #5b4a3a;
1887
+ }
1888
+ /* saved celebration — jump + spin */
1889
+ .ws-mini-char-saved {
1890
+ animation: mini-celebrate 0.8s steps(1) forwards;
1891
+ box-shadow:
1892
+ 2px 0 0 #5b8c5a, 4px 0 0 #5b8c5a,
1893
+ 2px 2px 0 #f4c07a, 4px 2px 0 #f4c07a,
1894
+ 0px 4px 0 #e85d3a, 2px 4px 0 #e85d3a, 4px 4px 0 #e85d3a, 6px 4px 0 #e85d3a,
1895
+ -2px 4px 0 #f4c07a, 8px 2px 0 #f4c07a,
1896
+ 0px 6px 0 #3b3f5c, 4px 6px 0 #3b3f5c,
1897
+ 0px 8px 0 #5b4a3a, 4px 8px 0 #5b4a3a;
1898
+ }
1899
+ @keyframes mini-celebrate {
1900
+ 0% { transform: translateY(0); }
1901
+ 20% { transform: translateY(-6px); }
1902
+ 40% { transform: translateY(-8px) rotate(15deg); }
1903
+ 60% { transform: translateY(-4px) rotate(-10deg); }
1904
+ 80% { transform: translateY(-1px) rotate(5deg); }
1905
+ 100% { transform: translateY(0) rotate(0deg); }
1906
+ }
1907
+
1908
+ @keyframes mini-walk {
1909
+ 0%, 100% { transform: translateY(0); }
1910
+ 50% { transform: translateY(-2px); }
1911
+ }
1912
+ @keyframes mini-walk-slow {
1913
+ 0%, 100% { transform: translateY(0); }
1914
+ 50% { transform: translateY(-1px); }
1915
+ }
1916
+ @keyframes mini-stumble {
1917
+ 0% { transform: translateY(0) rotate(0deg); }
1918
+ 25% { transform: translateY(-1px) rotate(2deg); }
1919
+ 50% { transform: translateY(0) rotate(0deg); }
1920
+ 75% { transform: translateY(-1px) rotate(-3deg); }
1921
+ 100% { transform: translateY(0) rotate(0deg); }
1922
+ }
1923
+
1924
+ .ws-mini-char-starting {
1925
+ animation: mini-fire 0.35s steps(1) infinite;
1926
+ box-shadow:
1927
+ 2px 6px 0 #5b4a3a, 4px 6px 0 #5b4a3a, 6px 6px 0 #5b4a3a,
1928
+ 2px 4px 0 #e85d3a, 4px 4px 0 #f59e0b, 6px 4px 0 #e85d3a,
1929
+ 4px 2px 0 #fbbf24,
1930
+ 4px 0px 0 #f59e0b;
1931
+ }
1932
+ @keyframes mini-fire {
1933
+ 0%, 100% { box-shadow:
1934
+ 2px 6px 0 #5b4a3a, 4px 6px 0 #5b4a3a, 6px 6px 0 #5b4a3a,
1935
+ 2px 4px 0 #e85d3a, 4px 4px 0 #f59e0b, 6px 4px 0 #e85d3a,
1936
+ 4px 2px 0 #fbbf24, 4px 0 0 #f59e0b; }
1937
+ 50% { box-shadow:
1938
+ 2px 6px 0 #5b4a3a, 4px 6px 0 #5b4a3a, 6px 6px 0 #5b4a3a,
1939
+ 2px 4px 0 #f59e0b, 4px 4px 0 #e85d3a, 6px 4px 0 #fbbf24,
1940
+ 2px 2px 0 #f59e0b, 6px 0px 0 #e85d3a; }
1941
+ }
1942
+
1943
+ .ws-mini-char-stopped {
1944
+ box-shadow:
1945
+ 2px 0 0 #4a5170, 4px 0 0 #4a5170,
1946
+ 0px 2px 0 #4a5170, 2px 2px 0 #3b3f5c, 4px 2px 0 #3b3f5c, 6px 2px 0 #4a5170,
1947
+ 0px 4px 0 #4a5170, 2px 4px 0 #3b3f5c, 4px 4px 0 #1c2030, 6px 4px 0 #4a5170;
1948
+ }
1949
+
1950
+ .ws-mini-char-error {
1951
+ animation: mini-sos 0.4s steps(1) infinite;
1952
+ box-shadow:
1953
+ 2px 2px 0 #f4c07a,
1954
+ 0px 4px 0 #e85d3a, 2px 4px 0 #e85d3a, 4px 4px 0 #e85d3a,
1955
+ 0px 6px 0 #f4c07a, 6px 6px 0 #3b3f5c,
1956
+ 0px 8px 0 #3b3f5c;
1957
+ transform: rotate(90deg);
1958
+ }
1959
+ @keyframes mini-sos {
1960
+ 0%, 100% { transform: rotate(90deg) translateX(0); }
1961
+ 50% { transform: rotate(90deg) translateX(2px); }
1962
+ }
1963
+
1964
+ /* === Gamification: Quest progress (NES style) === */
1965
+ .ws-quest {
1966
+ display: flex;
1967
+ align-items: center;
1968
+ margin-left: 16px;
1969
+ }
1970
+ .ws-quest-step {
1971
+ display: flex;
1972
+ align-items: center;
1973
+ gap: 3px;
1974
+ opacity: 0.25;
1975
+ transition: opacity 0.3s steps(3);
1976
+ }
1977
+ .ws-quest-step span {
1978
+ font-size: 9px;
1979
+ color: var(--text-muted);
1980
+ font-family: var(--font-mono);
1981
+ letter-spacing: 0.5px;
1982
+ }
1983
+ .ws-quest-step.ws-quest-active,
1984
+ .ws-quest-step.ws-quest-done {
1985
+ opacity: 1;
1986
+ }
1987
+ .ws-quest-dot {
1988
+ width: 8px;
1989
+ height: 8px;
1990
+ border-radius: 0;
1991
+ background: var(--border);
1992
+ border: 2px solid rgba(0,0,0,0.3);
1993
+ transition: background 0.2s steps(2);
1994
+ }
1995
+ .ws-quest-active .ws-quest-dot {
1996
+ background: #fbbf24;
1997
+ border-color: #b45309;
1998
+ animation: quest-pulse 1.5s steps(2) infinite;
1999
+ }
2000
+ .ws-quest-done .ws-quest-dot {
2001
+ background: #22c55e;
2002
+ border-color: #15803d;
2003
+ }
2004
+ @keyframes quest-pulse {
2005
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.4); }
2006
+ 50% { box-shadow: 0 0 0 3px rgba(251, 191, 36, 0); }
2007
+ }
2008
+ .ws-quest-line {
2009
+ width: 12px;
2010
+ height: 2px;
2011
+ background: var(--border);
2012
+ margin: 0 2px;
2013
+ }
2014
+ .ws-quest-done + .ws-quest-line {
2015
+ background: #22c55e;
2016
+ }
2017
+
2018
+ /* === Ship progress steps === */
2019
+ .ship-steps {
2020
+ margin: 12px 0;
2021
+ display: flex;
2022
+ flex-direction: column;
2023
+ gap: 6px;
2024
+ }
2025
+ .ship-steps.hidden { display: none; }
2026
+ .ship-step {
2027
+ font-size: 13px;
2028
+ color: var(--text-muted);
2029
+ padding: 4px 0;
2030
+ opacity: 0.4;
2031
+ }
2032
+ .ship-step.ship-step-active {
2033
+ opacity: 1;
2034
+ color: #fbbf24;
2035
+ }
2036
+ .ship-step.ship-step-done {
2037
+ opacity: 1;
2038
+ color: #22c55e;
2039
+ }
2040
+ .ship-step.ship-step-fail {
2041
+ opacity: 1;
2042
+ color: #ef4444;
2043
+ }
2044
+
2045
+ /* ============================================================
2046
+ Onboarding Tutorial
2047
+ ============================================================ */
2048
+
2049
+ .onboarding-overlay {
2050
+ position: fixed;
2051
+ top: 0; left: 0; right: 0; bottom: 0;
2052
+ background: rgba(0, 0, 0, 0.6);
2053
+ z-index: 10000;
2054
+ }
2055
+
2056
+ .onboarding-highlight {
2057
+ position: fixed;
2058
+ border: 2px solid var(--accent, #6366f1);
2059
+ border-radius: 8px;
2060
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5), 0 0 20px rgba(99, 102, 241, 0.4);
2061
+ z-index: 10001;
2062
+ pointer-events: none;
2063
+ animation: onboard-pulse 1.5s ease-in-out infinite;
2064
+ }
2065
+
2066
+ @keyframes onboard-pulse {
2067
+ 0%, 100% { box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5), 0 0 20px rgba(99, 102, 241, 0.3); }
2068
+ 50% { box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5), 0 0 30px rgba(99, 102, 241, 0.6); }
2069
+ }
2070
+
2071
+ .onboarding-tooltip {
2072
+ position: fixed;
2073
+ background: var(--bg-elevated, #1e1e2e);
2074
+ border: 1px solid var(--border, #333);
2075
+ border-radius: 12px;
2076
+ padding: 16px 20px;
2077
+ max-width: 320px;
2078
+ z-index: 10002;
2079
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
2080
+ animation: onboard-fade-in 0.3s ease-out;
2081
+ }
2082
+
2083
+ @keyframes onboard-fade-in {
2084
+ from { opacity: 0; transform: translateY(8px); }
2085
+ to { opacity: 1; transform: translateY(0); }
2086
+ }
2087
+
2088
+ .onboarding-title {
2089
+ font-size: 15px;
2090
+ font-weight: 700;
2091
+ color: var(--text-primary, #fff);
2092
+ margin-bottom: 6px;
2093
+ }
2094
+
2095
+ .onboarding-text {
2096
+ font-size: 13px;
2097
+ color: var(--text-secondary, #aaa);
2098
+ line-height: 1.5;
2099
+ margin-bottom: 12px;
2100
+ }
2101
+
2102
+ .onboarding-actions {
2103
+ display: flex;
2104
+ align-items: center;
2105
+ gap: 8px;
2106
+ }
2107
+
2108
+ .onboarding-step {
2109
+ font-size: 11px;
2110
+ color: var(--text-muted, #666);
2111
+ flex: 1;
2112
+ }