@etamong-playground/ui 0.34.2

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,2312 @@
1
+ /*
2
+ * @etamong-lab/ui — command-palette + notification styles + namespaced tokens.
3
+ *
4
+ * Import once at the app root: `import "@etamong-lab/ui/styles.css"`.
5
+ *
6
+ * Components are styled from `--etu-*` tokens (deliberately NAMESPACED so this
7
+ * file is safe to import into any app — including shadcn/Tailwind apps that
8
+ * already own `--accent`/`--border`/`--ring`). Defaults below match the etamong
9
+ * look; an app theming to its own palette overrides just a few, e.g.
10
+ * :root { --etu-accent: var(--primary); --etu-surface: var(--popover); }
11
+ *
12
+ * Dark mode triggers on EITHER `[data-theme="dark"]` (Vite-family convention)
13
+ * or the `.dark` class (Next/shadcn convention).
14
+ */
15
+
16
+ /* ----------------------------------------------------------------------------
17
+ * iOS PWA safety reset (v0.22.0). Locks against the standalone-mode quirks
18
+ * where Korean (and other CJK) body text shrinks because iOS's auto text-size
19
+ * adjust kicks in without the Safari toolbar; also resets tap-highlight + sets
20
+ * a sane default body font-size + color-scheme. Safe-area padding is NOT
21
+ * painted on `body` here on purpose — apps already manage insets on fixed
22
+ * bars (MobileTabBar, top headers). Apps that need a single-place safe-area
23
+ * wrapper can use the `.etu-app-shell` utility class below.
24
+ * See planning wiki: concepts/pwa-cache-and-ios-shell + concepts/ios-pwa-safe-area.
25
+ * ------------------------------------------------------------------------- */
26
+ :root { color-scheme: light dark; }
27
+ html {
28
+ -webkit-text-size-adjust: 100%;
29
+ text-size-adjust: 100%;
30
+ -webkit-tap-highlight-color: transparent;
31
+ }
32
+ body {
33
+ font-size: 16px;
34
+ -webkit-font-smoothing: antialiased;
35
+ -moz-osx-font-smoothing: grayscale;
36
+ }
37
+ /* iOS form-control auto-zoom-on-focus guard. */
38
+ input, select, textarea { font-size: max(16px, 1rem); }
39
+
40
+ /* Opt-in shell wrapper for apps that want a single-place safe-area inset
41
+ * container. Apps with bespoke chrome (existing fixed headers + MobileTabBar)
42
+ * should keep their per-bar `env(safe-area-inset-*)` rules and not bother
43
+ * with this class. */
44
+ .etu-app-shell {
45
+ padding-top: env(safe-area-inset-top);
46
+ padding-bottom: env(safe-area-inset-bottom);
47
+ padding-left: env(safe-area-inset-left);
48
+ padding-right: env(safe-area-inset-right);
49
+ }
50
+ .etu-app-shell.etu-no-safe-padding,
51
+ body.etu-no-safe-padding { padding: 0; }
52
+
53
+ :root {
54
+ --etu-bg: #ffffff;
55
+ --etu-surface: #ffffff;
56
+ --etu-surface-2: #eef0f3;
57
+ --etu-border: #e3e6ea;
58
+ --etu-text: #1a1d21;
59
+ --etu-text-muted: #5c636e;
60
+ --etu-text-subtle: #8b929c;
61
+ --etu-accent: #5b8cff;
62
+ --etu-accent-soft: #eef1f6;
63
+ --etu-on-accent: #ffffff;
64
+ --etu-ok: #2faf6b;
65
+ --etu-err: #e5484d;
66
+ --etu-r: 12px;
67
+ --etu-r-sm: 8px;
68
+ --etu-font: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
69
+ "Helvetica Neue", "Noto Sans KR", sans-serif;
70
+ --etu-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
71
+ --etu-shadow: 0 16px 40px rgba(16, 19, 26, 0.14);
72
+ }
73
+
74
+ :root[data-theme="dark"],
75
+ .dark {
76
+ --etu-bg: #0f1115;
77
+ --etu-surface: #181b22;
78
+ --etu-surface-2: #1f232c;
79
+ --etu-border: #262b35;
80
+ --etu-text: #e6e8ec;
81
+ --etu-text-muted: #98a1ad;
82
+ --etu-text-subtle: #6b7480;
83
+ --etu-accent: #5b8cff;
84
+ --etu-accent-soft: #232838;
85
+ --etu-on-accent: #0b0e13;
86
+ --etu-ok: #34d399;
87
+ --etu-err: #ff6b6b;
88
+ --etu-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
89
+ }
90
+
91
+ /* ----------------------------------------------------------------------------
92
+ * Command palette (CommandPalette). cmdk emits the [cmdk-*] attributes.
93
+ * ------------------------------------------------------------------------- */
94
+
95
+ .etu-cmdk-overlay {
96
+ position: fixed;
97
+ inset: 0;
98
+ z-index: 50;
99
+ background: rgba(0, 0, 0, 0.4);
100
+ }
101
+
102
+ .etu-cmdk-content {
103
+ position: fixed;
104
+ inset: 0;
105
+ z-index: 50;
106
+ display: flex;
107
+ align-items: flex-start;
108
+ justify-content: center;
109
+ padding-top: 20vh;
110
+ }
111
+
112
+ .etu-cmdk {
113
+ width: 100%;
114
+ max-width: 32rem;
115
+ overflow: hidden;
116
+ border-radius: var(--etu-r);
117
+ border: 1px solid var(--etu-border);
118
+ background: var(--etu-surface);
119
+ box-shadow: var(--etu-shadow);
120
+ font-family: var(--etu-font);
121
+ }
122
+
123
+ .etu-cmdk-input {
124
+ width: 100%;
125
+ border: none;
126
+ border-bottom: 1px solid var(--etu-border);
127
+ background: transparent;
128
+ padding: 0.85rem 1rem;
129
+ font-size: 0.9rem;
130
+ color: var(--etu-text);
131
+ outline: none;
132
+ }
133
+
134
+ .etu-cmdk-input::placeholder {
135
+ color: var(--etu-text-subtle);
136
+ }
137
+
138
+ .etu-cmdk-list {
139
+ max-height: 18rem;
140
+ overflow-y: auto;
141
+ padding: 0.5rem;
142
+ }
143
+
144
+ .etu-cmdk-empty {
145
+ padding: 1.5rem 0;
146
+ text-align: center;
147
+ font-size: 0.85rem;
148
+ color: var(--etu-text-muted);
149
+ }
150
+
151
+ .etu-cmdk [cmdk-group-heading] {
152
+ padding: 0.5rem 0.5rem 0.35rem;
153
+ font-size: 0.72rem;
154
+ font-weight: 600;
155
+ letter-spacing: 0.02em;
156
+ color: var(--etu-text-subtle);
157
+ }
158
+
159
+ .etu-cmdk-item {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 0.65rem;
163
+ border-radius: var(--etu-r-sm);
164
+ padding: 0.5rem 0.5rem;
165
+ font-size: 0.875rem;
166
+ color: var(--etu-text);
167
+ cursor: pointer;
168
+ }
169
+
170
+ .etu-cmdk-item[data-selected="true"] {
171
+ background: var(--etu-accent-soft);
172
+ }
173
+
174
+ .etu-cmdk-search [cmdk-group-heading] {
175
+ margin-top: 0.25rem;
176
+ border-top: 1px solid var(--etu-border);
177
+ padding-top: 0.6rem;
178
+ }
179
+
180
+ .etu-cmdk-item-search {
181
+ color: var(--etu-text-muted);
182
+ }
183
+
184
+ .etu-cmdk-item-icon {
185
+ display: inline-flex;
186
+ flex-shrink: 0;
187
+ color: var(--etu-text-subtle);
188
+ }
189
+
190
+ .etu-cmdk-item-label {
191
+ overflow: hidden;
192
+ text-overflow: ellipsis;
193
+ white-space: nowrap;
194
+ }
195
+
196
+ .etu-cmdk-item-sub {
197
+ margin-left: auto;
198
+ flex-shrink: 0;
199
+ font-size: 0.75rem;
200
+ color: var(--etu-text-subtle);
201
+ }
202
+
203
+ .etu-cmdk-footer {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 0.75rem;
207
+ border-top: 1px solid var(--etu-border);
208
+ padding: 0.45rem 1rem;
209
+ color: var(--etu-text-subtle);
210
+ }
211
+
212
+ .etu-cmdk-kbd {
213
+ border-radius: 4px;
214
+ background: var(--etu-surface-2);
215
+ padding: 0.1rem 0.35rem;
216
+ font-family: var(--etu-mono);
217
+ font-size: 0.65rem;
218
+ line-height: 1.4;
219
+ }
220
+
221
+ /* ----------------------------------------------------------------------------
222
+ * Command palette trigger (CommandPaletteTrigger) — a search-box that opens it.
223
+ * ------------------------------------------------------------------------- */
224
+
225
+ .etu-palette-trigger {
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 0.5rem;
229
+ width: 100%;
230
+ border-radius: var(--etu-r-sm);
231
+ border: 1px solid var(--etu-border);
232
+ background: var(--etu-surface-2);
233
+ color: var(--etu-text-subtle);
234
+ font-family: var(--etu-font);
235
+ font-size: 0.8rem;
236
+ padding: 0.4rem 0.6rem;
237
+ cursor: pointer;
238
+ transition: background 140ms ease;
239
+ }
240
+
241
+ .etu-palette-trigger:hover {
242
+ background: var(--etu-accent-soft);
243
+ }
244
+
245
+ .etu-palette-trigger-icon {
246
+ flex-shrink: 0;
247
+ }
248
+
249
+ .etu-palette-trigger-label {
250
+ flex: 1;
251
+ text-align: left;
252
+ overflow: hidden;
253
+ text-overflow: ellipsis;
254
+ white-space: nowrap;
255
+ }
256
+
257
+ .etu-palette-trigger-kbd {
258
+ flex-shrink: 0;
259
+ font-family: var(--etu-mono);
260
+ font-size: 0.62rem;
261
+ opacity: 0.85;
262
+ }
263
+
264
+ /* ----------------------------------------------------------------------------
265
+ * Toast (toast() / <Toaster/>)
266
+ * ------------------------------------------------------------------------- */
267
+
268
+ .etu-toaster {
269
+ position: fixed;
270
+ left: 50%;
271
+ bottom: 1.5rem;
272
+ transform: translateX(-50%);
273
+ z-index: 60;
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: 0.5rem;
277
+ align-items: center;
278
+ pointer-events: none;
279
+ padding: 0 1rem;
280
+ max-width: 100%;
281
+ }
282
+
283
+ .etu-toast {
284
+ pointer-events: auto;
285
+ cursor: pointer;
286
+ max-width: 32rem;
287
+ border-radius: var(--etu-r-sm);
288
+ border: 1px solid var(--etu-border);
289
+ border-left: 3px solid var(--etu-text-subtle);
290
+ background: var(--etu-surface);
291
+ box-shadow: var(--etu-shadow);
292
+ color: var(--etu-text);
293
+ font-family: var(--etu-font);
294
+ font-size: 0.85rem;
295
+ padding: 0.6rem 0.9rem;
296
+ animation: etu-toast-in 160ms ease-out;
297
+ }
298
+
299
+ .etu-toast-ok {
300
+ border-left-color: var(--etu-ok);
301
+ }
302
+ .etu-toast-err {
303
+ border-left-color: var(--etu-err);
304
+ }
305
+ .etu-toast-info {
306
+ border-left-color: var(--etu-accent);
307
+ }
308
+
309
+ @keyframes etu-toast-in {
310
+ from {
311
+ opacity: 0;
312
+ transform: translateY(8px);
313
+ }
314
+ to {
315
+ opacity: 1;
316
+ transform: translateY(0);
317
+ }
318
+ }
319
+
320
+ /* ----------------------------------------------------------------------------
321
+ * Dialog (DialogHost / uiConfirm / uiPrompt)
322
+ * ------------------------------------------------------------------------- */
323
+
324
+ .etu-dialog-overlay {
325
+ position: fixed;
326
+ inset: 0;
327
+ z-index: 60;
328
+ background: rgba(0, 0, 0, 0.4);
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ padding: 1rem;
333
+ }
334
+
335
+ .etu-dialog {
336
+ width: 100%;
337
+ max-width: 26rem;
338
+ border-radius: var(--etu-r);
339
+ border: 1px solid var(--etu-border);
340
+ background: var(--etu-surface);
341
+ box-shadow: var(--etu-shadow);
342
+ color: var(--etu-text);
343
+ font-family: var(--etu-font);
344
+ padding: 1.25rem;
345
+ }
346
+
347
+ .etu-dialog-title {
348
+ font-size: 1rem;
349
+ font-weight: 600;
350
+ margin-bottom: 0.5rem;
351
+ }
352
+
353
+ .etu-dialog-body {
354
+ font-size: 0.875rem;
355
+ color: var(--etu-text-muted);
356
+ margin-bottom: 0.9rem;
357
+ white-space: pre-wrap;
358
+ }
359
+
360
+ .etu-dialog-input {
361
+ width: 100%;
362
+ border-radius: var(--etu-r-sm);
363
+ border: 1px solid var(--etu-border);
364
+ background: var(--etu-bg);
365
+ color: var(--etu-text);
366
+ padding: 0.55rem 0.7rem;
367
+ font-size: 0.875rem;
368
+ font-family: var(--etu-font);
369
+ margin-bottom: 0.9rem;
370
+ outline: none;
371
+ }
372
+
373
+ .etu-dialog-input:focus {
374
+ border-color: var(--etu-accent);
375
+ }
376
+
377
+ .etu-dialog-actions {
378
+ display: flex;
379
+ justify-content: flex-end;
380
+ gap: 0.5rem;
381
+ }
382
+
383
+ .etu-dialog-btn {
384
+ border-radius: var(--etu-r-sm);
385
+ border: 1px solid var(--etu-border);
386
+ background: var(--etu-surface-2);
387
+ color: var(--etu-text);
388
+ font-family: var(--etu-font);
389
+ font-size: 0.85rem;
390
+ padding: 0.45rem 0.9rem;
391
+ cursor: pointer;
392
+ }
393
+
394
+ .etu-dialog-btn-primary {
395
+ background: var(--etu-accent);
396
+ border-color: var(--etu-accent);
397
+ color: var(--etu-on-accent);
398
+ }
399
+
400
+ .etu-dialog-btn-danger {
401
+ background: var(--etu-err);
402
+ border-color: var(--etu-err);
403
+ color: #ffffff;
404
+ }
405
+
406
+ /* ----------------------------------------------------------------------------
407
+ * DeployInfo (<DeployInfo version builtAt />)
408
+ * ------------------------------------------------------------------------- */
409
+
410
+ .etu-deploy-info {
411
+ display: inline-flex;
412
+ align-items: center;
413
+ gap: 0.35rem;
414
+ font-family: var(--etu-font);
415
+ font-size: 0.72rem;
416
+ line-height: 1.2;
417
+ color: var(--etu-text-subtle);
418
+ white-space: nowrap;
419
+ }
420
+
421
+ .etu-deploy-info-label {
422
+ text-transform: lowercase;
423
+ letter-spacing: 0.01em;
424
+ }
425
+
426
+ .etu-deploy-info-ver {
427
+ font-family: var(--etu-mono);
428
+ font-size: 0.72rem;
429
+ color: var(--etu-text-muted);
430
+ text-decoration: none;
431
+ }
432
+
433
+ a.etu-deploy-info-ver:hover {
434
+ color: var(--etu-accent);
435
+ text-decoration: underline;
436
+ }
437
+
438
+ .etu-deploy-info-time {
439
+ color: var(--etu-text-subtle);
440
+ }
441
+
442
+ /* ----------------------------------------------------------------------------
443
+ * InstallBanner (<InstallBanner />)
444
+ * Mobile-only PWA install affordance. Hidden on >= 768px.
445
+ * ------------------------------------------------------------------------- */
446
+
447
+ .etu-install-banner {
448
+ position: fixed;
449
+ left: 0.75rem;
450
+ right: 0.75rem;
451
+ bottom: 0.75rem;
452
+ z-index: 50;
453
+ display: flex;
454
+ align-items: center;
455
+ gap: 0.65rem;
456
+ padding: 0.65rem 0.75rem;
457
+ border-radius: var(--etu-r);
458
+ border: 1px solid var(--etu-border);
459
+ background: var(--etu-surface);
460
+ box-shadow: var(--etu-shadow);
461
+ font-family: var(--etu-font);
462
+ font-size: 0.85rem;
463
+ color: var(--etu-text);
464
+ }
465
+
466
+ @media (min-width: 768px) {
467
+ .etu-install-banner {
468
+ display: none;
469
+ }
470
+ }
471
+
472
+ /* When the same app also renders <MobileTabBar>, lift the banner above it so
473
+ * it doesn't sit on top of the tab bar. Apps can override
474
+ * --etu-mobile-tab-bar-h if their bar uses non-default icon/label sizing. */
475
+ body:has(.etu-mobile-tab-bar) .etu-install-banner {
476
+ bottom: calc(0.75rem + var(--etu-mobile-tab-bar-h, 56px) + env(safe-area-inset-bottom));
477
+ }
478
+
479
+ .etu-install-banner-icon {
480
+ flex-shrink: 0;
481
+ color: var(--etu-accent);
482
+ display: inline-flex;
483
+ }
484
+
485
+ .etu-install-banner-body {
486
+ flex: 1;
487
+ min-width: 0;
488
+ margin: 0;
489
+ font-size: 0.78rem;
490
+ color: var(--etu-text-muted);
491
+ line-height: 1.3;
492
+ }
493
+
494
+ .etu-install-banner-cta {
495
+ flex-shrink: 0;
496
+ border: none;
497
+ border-radius: var(--etu-r-sm);
498
+ background: var(--etu-accent);
499
+ color: var(--etu-on-accent);
500
+ font-family: var(--etu-font);
501
+ font-size: 0.78rem;
502
+ font-weight: 600;
503
+ padding: 0.4rem 0.75rem;
504
+ cursor: pointer;
505
+ }
506
+
507
+ .etu-install-banner-cta:hover {
508
+ filter: brightness(1.08);
509
+ }
510
+
511
+ .etu-install-banner-close {
512
+ flex-shrink: 0;
513
+ border: none;
514
+ background: transparent;
515
+ color: var(--etu-text-subtle);
516
+ border-radius: var(--etu-r-sm);
517
+ padding: 0.25rem;
518
+ cursor: pointer;
519
+ display: inline-flex;
520
+ }
521
+
522
+ .etu-install-banner-close:hover {
523
+ background: var(--etu-surface-2);
524
+ color: var(--etu-text);
525
+ }
526
+
527
+ /* ----------------------------------------------------------------------------
528
+ * ErrorPage (<ErrorPage />)
529
+ * Full-page friendly error surface for boundary / 500 / not-found routes.
530
+ * Pairs with the httperr `ref` pattern (no raw error / repo links leak).
531
+ * ------------------------------------------------------------------------- */
532
+
533
+ .etu-error-page {
534
+ min-height: 100dvh;
535
+ display: flex;
536
+ align-items: center;
537
+ justify-content: center;
538
+ padding: 1.5rem;
539
+ background: var(--etu-bg);
540
+ color: var(--etu-text);
541
+ font-family: var(--etu-font);
542
+ }
543
+
544
+ .etu-error-page-card {
545
+ max-width: 32rem;
546
+ width: 100%;
547
+ display: flex;
548
+ flex-direction: column;
549
+ align-items: center;
550
+ text-align: center;
551
+ padding: 2rem 1.5rem;
552
+ border-radius: var(--etu-r);
553
+ border: 1px solid var(--etu-border);
554
+ background: var(--etu-surface);
555
+ box-shadow: var(--etu-shadow);
556
+ }
557
+
558
+ .etu-error-page-icon {
559
+ color: var(--etu-err);
560
+ margin-bottom: 0.85rem;
561
+ display: inline-flex;
562
+ }
563
+
564
+ .etu-error-page-title {
565
+ margin: 0 0 0.5rem;
566
+ font-size: 1.15rem;
567
+ font-weight: 600;
568
+ color: var(--etu-text);
569
+ }
570
+
571
+ .etu-error-page-body {
572
+ margin: 0 0 1.25rem;
573
+ font-size: 0.92rem;
574
+ line-height: 1.5;
575
+ color: var(--etu-text-muted);
576
+ }
577
+
578
+ .etu-error-page-actions {
579
+ display: flex;
580
+ gap: 0.5rem;
581
+ margin-bottom: 1rem;
582
+ flex-wrap: wrap;
583
+ justify-content: center;
584
+ }
585
+
586
+ .etu-error-page-cta {
587
+ border: none;
588
+ border-radius: var(--etu-r-sm);
589
+ background: var(--etu-accent);
590
+ color: var(--etu-on-accent);
591
+ font-family: var(--etu-font);
592
+ font-size: 0.88rem;
593
+ font-weight: 600;
594
+ padding: 0.5rem 1rem;
595
+ cursor: pointer;
596
+ }
597
+
598
+ .etu-error-page-cta:hover {
599
+ filter: brightness(1.08);
600
+ }
601
+
602
+ .etu-error-page-secondary {
603
+ border: 1px solid var(--etu-border);
604
+ border-radius: var(--etu-r-sm);
605
+ background: transparent;
606
+ color: var(--etu-text);
607
+ font-family: var(--etu-font);
608
+ font-size: 0.88rem;
609
+ padding: 0.5rem 1rem;
610
+ cursor: pointer;
611
+ }
612
+
613
+ .etu-error-page-secondary:hover {
614
+ background: var(--etu-surface-2);
615
+ }
616
+
617
+ .etu-error-page-ref {
618
+ margin: 0;
619
+ font-size: 0.78rem;
620
+ color: var(--etu-text-subtle);
621
+ }
622
+
623
+ .etu-error-page-ref-label {
624
+ text-transform: lowercase;
625
+ letter-spacing: 0.01em;
626
+ }
627
+
628
+ .etu-error-page-ref-code {
629
+ font-family: var(--etu-mono);
630
+ font-size: 0.78rem;
631
+ color: var(--etu-text-muted);
632
+ background: var(--etu-surface-2);
633
+ border-radius: 0.25rem;
634
+ padding: 0.1rem 0.35rem;
635
+ user-select: all;
636
+ }
637
+
638
+ /* BackButton — small ghost button with chevron + label. Sits left of a
639
+ page heading or above a detail view. */
640
+ .etu-back-button {
641
+ display: inline-flex;
642
+ align-items: center;
643
+ gap: 0.35rem;
644
+ padding: 0.3rem 0.55rem 0.3rem 0.4rem;
645
+ background: transparent;
646
+ border: 1px solid transparent;
647
+ border-radius: 0.5rem;
648
+ color: var(--etu-text-muted);
649
+ font: inherit;
650
+ font-size: 0.85rem;
651
+ cursor: pointer;
652
+ transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
653
+ }
654
+
655
+ .etu-back-button:hover {
656
+ background: var(--etu-surface-2);
657
+ color: var(--etu-text);
658
+ border-color: var(--etu-border);
659
+ }
660
+
661
+ .etu-back-button:focus-visible {
662
+ outline: 2px solid var(--etu-accent);
663
+ outline-offset: 2px;
664
+ }
665
+
666
+ .etu-back-button-icon {
667
+ display: inline-flex;
668
+ align-items: center;
669
+ justify-content: center;
670
+ }
671
+
672
+ .etu-back-button-label {
673
+ line-height: 1;
674
+ }
675
+
676
+ /* EmptyState — centered card for "nothing here yet" views. */
677
+ .etu-empty-state {
678
+ display: flex;
679
+ flex-direction: column;
680
+ align-items: center;
681
+ justify-content: center;
682
+ gap: 0.6rem;
683
+ padding: 3rem 1.5rem;
684
+ text-align: center;
685
+ color: var(--etu-text-muted);
686
+ border: 1px dashed var(--etu-border);
687
+ border-radius: 0.75rem;
688
+ background: var(--etu-surface);
689
+ }
690
+
691
+ .etu-empty-state--compact {
692
+ padding: 1.4rem 1rem;
693
+ gap: 0.4rem;
694
+ }
695
+
696
+ .etu-empty-state-icon {
697
+ color: var(--etu-text-subtle);
698
+ margin-bottom: 0.25rem;
699
+ }
700
+
701
+ .etu-empty-state-title {
702
+ font-size: 1rem;
703
+ font-weight: 600;
704
+ color: var(--etu-text);
705
+ }
706
+
707
+ .etu-empty-state--compact .etu-empty-state-title {
708
+ font-size: 0.9rem;
709
+ }
710
+
711
+ .etu-empty-state-body {
712
+ font-size: 0.85rem;
713
+ max-width: 28rem;
714
+ line-height: 1.45;
715
+ }
716
+
717
+ .etu-empty-state-actions {
718
+ margin-top: 0.6rem;
719
+ display: flex;
720
+ gap: 0.5rem;
721
+ flex-wrap: wrap;
722
+ justify-content: center;
723
+ }
724
+
725
+ /* CopyButton — compact ghost button with copy / check icon. */
726
+ .etu-copy-button {
727
+ display: inline-flex;
728
+ align-items: center;
729
+ gap: 0.3rem;
730
+ padding: 0.25rem 0.5rem;
731
+ background: transparent;
732
+ border: 1px solid var(--etu-border);
733
+ border-radius: 0.4rem;
734
+ color: var(--etu-text-muted);
735
+ font: inherit;
736
+ font-size: 0.78rem;
737
+ cursor: pointer;
738
+ transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
739
+ }
740
+
741
+ .etu-copy-button:hover {
742
+ background: var(--etu-surface-2);
743
+ color: var(--etu-text);
744
+ }
745
+
746
+ .etu-copy-button:focus-visible {
747
+ outline: 2px solid var(--etu-accent);
748
+ outline-offset: 2px;
749
+ }
750
+
751
+ .etu-copy-button--copied {
752
+ color: var(--etu-ok, var(--etu-accent));
753
+ border-color: var(--etu-ok, var(--etu-accent));
754
+ }
755
+
756
+ .etu-copy-button--icon-only {
757
+ padding: 0.25rem;
758
+ }
759
+
760
+ .etu-copy-button-icon {
761
+ display: inline-flex;
762
+ align-items: center;
763
+ justify-content: center;
764
+ }
765
+
766
+ .etu-copy-button-label {
767
+ line-height: 1;
768
+ }
769
+
770
+ .etu-open-in-browser-button {
771
+ display: inline-flex;
772
+ align-items: center;
773
+ gap: 0.3rem;
774
+ padding: 0.25rem 0.5rem;
775
+ border-radius: 0.4rem;
776
+ font: inherit;
777
+ font-size: 0.78rem;
778
+ text-decoration: none;
779
+ cursor: pointer;
780
+ transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
781
+ }
782
+
783
+ .etu-open-in-browser-button--ghost {
784
+ background: transparent;
785
+ border: 1px solid var(--etu-border);
786
+ color: var(--etu-text-muted);
787
+ }
788
+
789
+ .etu-open-in-browser-button--ghost:hover {
790
+ background: var(--etu-surface-2);
791
+ color: var(--etu-text);
792
+ }
793
+
794
+ .etu-open-in-browser-button--primary {
795
+ background: var(--etu-accent);
796
+ border: 1px solid var(--etu-accent);
797
+ color: var(--etu-accent-on, #fff);
798
+ }
799
+
800
+ .etu-open-in-browser-button--primary:hover {
801
+ filter: brightness(1.05);
802
+ }
803
+
804
+ .etu-open-in-browser-button:focus-visible {
805
+ outline: 2px solid var(--etu-accent);
806
+ outline-offset: 2px;
807
+ }
808
+
809
+ .etu-open-in-browser-button--icon-only {
810
+ padding: 0.25rem;
811
+ }
812
+
813
+ .etu-open-in-browser-button-icon {
814
+ display: inline-flex;
815
+ align-items: center;
816
+ justify-content: center;
817
+ }
818
+
819
+ .etu-open-in-browser-button-label {
820
+ line-height: 1;
821
+ }
822
+
823
+ /* Backoffice scaffold — gate + badge + page-head layout. */
824
+ .etu-admin-badge {
825
+ display: inline-flex;
826
+ align-items: center;
827
+ gap: 0.3rem;
828
+ padding: 0.15rem 0.45rem;
829
+ border: 1px solid var(--etu-border);
830
+ border-radius: 999px;
831
+ background: var(--etu-surface-2);
832
+ color: var(--etu-text-muted);
833
+ font-size: 0.7rem;
834
+ font-weight: 500;
835
+ letter-spacing: 0.01em;
836
+ vertical-align: middle;
837
+ line-height: 1.2;
838
+ }
839
+
840
+ .etu-backoffice {
841
+ display: flex;
842
+ flex-direction: column;
843
+ gap: 1.5rem;
844
+ }
845
+
846
+ .etu-backoffice-head {
847
+ display: flex;
848
+ align-items: flex-start;
849
+ justify-content: space-between;
850
+ gap: 1rem;
851
+ flex-wrap: wrap;
852
+ }
853
+
854
+ .etu-backoffice-head-text {
855
+ flex: 1 1 auto;
856
+ min-width: 0;
857
+ }
858
+
859
+ .etu-backoffice-title {
860
+ font-size: 1.25rem;
861
+ font-weight: 700;
862
+ color: var(--etu-text);
863
+ margin: 0;
864
+ display: inline-flex;
865
+ align-items: center;
866
+ gap: 0.6rem;
867
+ flex-wrap: wrap;
868
+ }
869
+
870
+ .etu-backoffice-description {
871
+ margin: 0.35rem 0 0;
872
+ color: var(--etu-text-muted);
873
+ font-size: 0.85rem;
874
+ line-height: 1.5;
875
+ }
876
+
877
+ .etu-backoffice-actions {
878
+ display: flex;
879
+ gap: 0.5rem;
880
+ flex-wrap: wrap;
881
+ }
882
+
883
+ .etu-backoffice-body {
884
+ display: flex;
885
+ flex-direction: column;
886
+ gap: 1rem;
887
+ }
888
+
889
+ /* AppInfoSection — canonical "앱 정보" card. */
890
+ .etu-app-info {
891
+ display: flex;
892
+ flex-direction: column;
893
+ gap: 0.75rem;
894
+ }
895
+
896
+ .etu-app-info-heading {
897
+ font-size: 0.95rem;
898
+ font-weight: 600;
899
+ color: var(--etu-text);
900
+ margin: 0;
901
+ }
902
+
903
+ .etu-app-info-card {
904
+ display: flex;
905
+ flex-direction: column;
906
+ gap: 1rem;
907
+ padding: 1.1rem 1.2rem;
908
+ background: var(--etu-surface);
909
+ border: 1px solid var(--etu-border);
910
+ border-radius: 0.75rem;
911
+ }
912
+
913
+ .etu-app-info-identity {
914
+ display: flex;
915
+ align-items: center;
916
+ gap: 0.85rem;
917
+ }
918
+
919
+ .etu-app-info-icon {
920
+ width: 2.4rem;
921
+ height: 2.4rem;
922
+ flex: none;
923
+ display: inline-flex;
924
+ align-items: center;
925
+ justify-content: center;
926
+ border-radius: 0.5rem;
927
+ background: var(--etu-surface-2);
928
+ color: var(--etu-text-muted);
929
+ font-size: 1.4rem;
930
+ overflow: hidden;
931
+ }
932
+
933
+ .etu-app-info-icon img,
934
+ .etu-app-info-icon svg {
935
+ width: 100%;
936
+ height: 100%;
937
+ object-fit: contain;
938
+ }
939
+
940
+ .etu-app-info-name {
941
+ font-size: 1.02rem;
942
+ font-weight: 600;
943
+ color: var(--etu-text);
944
+ }
945
+
946
+ .etu-app-info-description {
947
+ margin-top: 0.15rem;
948
+ font-size: 0.82rem;
949
+ color: var(--etu-text-muted);
950
+ }
951
+
952
+ .etu-app-info-meta {
953
+ margin: 0;
954
+ display: grid;
955
+ gap: 0.5rem;
956
+ }
957
+
958
+ .etu-app-info-row {
959
+ display: grid;
960
+ grid-template-columns: 5.5rem 1fr;
961
+ align-items: center;
962
+ gap: 0.6rem;
963
+ font-size: 0.85rem;
964
+ }
965
+
966
+ .etu-app-info-row dt {
967
+ color: var(--etu-text-muted);
968
+ font-weight: 500;
969
+ margin: 0;
970
+ }
971
+
972
+ .etu-app-info-row dd {
973
+ margin: 0;
974
+ color: var(--etu-text);
975
+ min-width: 0;
976
+ }
977
+
978
+ .etu-app-info-links {
979
+ display: flex;
980
+ gap: 0.85rem;
981
+ flex-wrap: wrap;
982
+ padding-top: 0.55rem;
983
+ border-top: 1px solid var(--etu-border);
984
+ font-size: 0.82rem;
985
+ }
986
+
987
+ .etu-app-info-link {
988
+ color: var(--etu-text-muted);
989
+ text-decoration: none;
990
+ }
991
+
992
+ .etu-app-info-link:hover {
993
+ color: var(--etu-text);
994
+ text-decoration: underline;
995
+ }
996
+
997
+ /* Legal section primitives — `<LegalMenuItem>` row on `/more` + `<LegalPage>`
998
+ list on `/more/legal`. Mirrors the legal/contact card shape from
999
+ planning concepts/mobile-more-page. */
1000
+ .etu-legal-card {
1001
+ border: 1px solid var(--etu-border);
1002
+ border-radius: 0.5rem;
1003
+ background: var(--etu-surface);
1004
+ overflow: hidden;
1005
+ }
1006
+
1007
+ .etu-legal-row {
1008
+ display: flex;
1009
+ align-items: center;
1010
+ gap: 0.75rem;
1011
+ padding: 0.75rem 0.875rem;
1012
+ min-height: 3rem;
1013
+ color: var(--etu-text);
1014
+ font-size: 0.875rem;
1015
+ text-decoration: none;
1016
+ }
1017
+
1018
+ .etu-legal-row--divided {
1019
+ border-top: 1px solid var(--etu-border);
1020
+ }
1021
+
1022
+ .etu-legal-row:hover {
1023
+ background: var(--etu-surface-2);
1024
+ }
1025
+
1026
+ .etu-legal-row-icon {
1027
+ width: 1.5rem;
1028
+ text-align: center;
1029
+ font-size: 1.125rem;
1030
+ flex: none;
1031
+ }
1032
+
1033
+ .etu-legal-row-label {
1034
+ flex: 1;
1035
+ min-width: 0;
1036
+ }
1037
+
1038
+ .etu-legal-row-trailing {
1039
+ color: var(--etu-text-muted);
1040
+ flex: none;
1041
+ }
1042
+
1043
+ .etu-legal-empty {
1044
+ padding: 0.75rem 0.875rem;
1045
+ margin: 0;
1046
+ border-top: 1px solid var(--etu-border);
1047
+ color: var(--etu-text-muted);
1048
+ font-size: 0.82rem;
1049
+ }
1050
+
1051
+ /* Avatar — round profile picture with an initial-letter fallback. */
1052
+ .etu-avatar {
1053
+ display: inline-flex;
1054
+ align-items: center;
1055
+ justify-content: center;
1056
+ border-radius: 999px;
1057
+ background: var(--etu-surface-2);
1058
+ color: var(--etu-text-muted);
1059
+ font-weight: 600;
1060
+ font-size: 0.8rem;
1061
+ overflow: hidden;
1062
+ flex: none;
1063
+ vertical-align: middle;
1064
+ user-select: none;
1065
+ }
1066
+
1067
+ .etu-avatar img {
1068
+ width: 100%;
1069
+ height: 100%;
1070
+ object-fit: cover;
1071
+ display: block;
1072
+ }
1073
+
1074
+ .etu-avatar-initial {
1075
+ line-height: 1;
1076
+ text-transform: uppercase;
1077
+ }
1078
+
1079
+ /* UserMenu — avatar trigger + dropdown with profile / 내 정보 / 로그아웃. */
1080
+ .etu-user-menu {
1081
+ position: relative;
1082
+ display: inline-block;
1083
+ }
1084
+
1085
+ .etu-user-menu-trigger {
1086
+ background: transparent;
1087
+ border: none;
1088
+ padding: 0;
1089
+ border-radius: 999px;
1090
+ cursor: pointer;
1091
+ display: inline-flex;
1092
+ align-items: center;
1093
+ justify-content: center;
1094
+ }
1095
+
1096
+ .etu-user-menu-trigger:focus-visible {
1097
+ outline: 2px solid var(--etu-accent);
1098
+ outline-offset: 2px;
1099
+ }
1100
+
1101
+ .etu-user-menu-sign-in {
1102
+ font-size: 0.8rem;
1103
+ color: var(--etu-text-muted);
1104
+ text-decoration: none;
1105
+ }
1106
+
1107
+ .etu-user-menu-sign-in:hover {
1108
+ color: var(--etu-text);
1109
+ }
1110
+
1111
+ .etu-user-menu-dropdown {
1112
+ position: absolute;
1113
+ min-width: 14rem;
1114
+ max-width: 18rem;
1115
+ background: var(--etu-surface);
1116
+ border: 1px solid var(--etu-border);
1117
+ border-radius: 0.6rem;
1118
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
1119
+ z-index: 50;
1120
+ padding: 0.6rem;
1121
+ display: flex;
1122
+ flex-direction: column;
1123
+ gap: 0.55rem;
1124
+ }
1125
+
1126
+ .etu-user-menu-dropdown--bottom-right { right: 0; top: calc(100% + 0.4rem); }
1127
+ .etu-user-menu-dropdown--bottom-left { left: 0; top: calc(100% + 0.4rem); }
1128
+ .etu-user-menu-dropdown--top-right { right: 0; bottom: calc(100% + 0.4rem); }
1129
+ .etu-user-menu-dropdown--top-left { left: 0; bottom: calc(100% + 0.4rem); }
1130
+
1131
+ .etu-user-menu-header {
1132
+ display: flex;
1133
+ align-items: center;
1134
+ gap: 0.65rem;
1135
+ padding: 0.1rem 0.1rem 0.2rem;
1136
+ }
1137
+
1138
+ .etu-user-menu-header-text {
1139
+ min-width: 0;
1140
+ flex: 1 1 auto;
1141
+ }
1142
+
1143
+ .etu-user-menu-name {
1144
+ font-size: 0.88rem;
1145
+ font-weight: 600;
1146
+ color: var(--etu-text);
1147
+ display: flex;
1148
+ align-items: center;
1149
+ gap: 0.4rem;
1150
+ overflow: hidden;
1151
+ text-overflow: ellipsis;
1152
+ white-space: nowrap;
1153
+ }
1154
+
1155
+ .etu-user-menu-email {
1156
+ font-size: 0.75rem;
1157
+ color: var(--etu-text-muted);
1158
+ overflow: hidden;
1159
+ text-overflow: ellipsis;
1160
+ white-space: nowrap;
1161
+ }
1162
+
1163
+ .etu-user-menu-admin {
1164
+ font-size: 0.62rem;
1165
+ font-weight: 600;
1166
+ text-transform: uppercase;
1167
+ letter-spacing: 0.05em;
1168
+ background: var(--etu-surface-2);
1169
+ color: var(--etu-text-muted);
1170
+ padding: 0.05rem 0.35rem;
1171
+ border-radius: 999px;
1172
+ }
1173
+
1174
+ .etu-user-menu-divider {
1175
+ height: 1px;
1176
+ background: var(--etu-border);
1177
+ margin: 0 -0.6rem;
1178
+ }
1179
+
1180
+ .etu-user-menu-items {
1181
+ display: flex;
1182
+ flex-direction: column;
1183
+ gap: 0.1rem;
1184
+ }
1185
+
1186
+ .etu-user-menu-item {
1187
+ display: flex;
1188
+ align-items: center;
1189
+ gap: 0.5rem;
1190
+ padding: 0.45rem 0.6rem;
1191
+ margin: 0;
1192
+ background: transparent;
1193
+ border: none;
1194
+ border-radius: 0.4rem;
1195
+ text-align: left;
1196
+ font: inherit;
1197
+ font-size: 0.85rem;
1198
+ color: var(--etu-text);
1199
+ cursor: pointer;
1200
+ text-decoration: none;
1201
+ transition: background 0.1s ease;
1202
+ }
1203
+
1204
+ .etu-user-menu-item:hover {
1205
+ background: var(--etu-surface-2);
1206
+ }
1207
+
1208
+ .etu-user-menu-item:focus-visible {
1209
+ outline: 2px solid var(--etu-accent);
1210
+ outline-offset: -2px;
1211
+ }
1212
+
1213
+ .etu-user-menu-item--danger {
1214
+ color: var(--etu-text-muted);
1215
+ }
1216
+
1217
+ .etu-user-menu-item--danger:hover {
1218
+ color: var(--etu-danger, var(--etu-text));
1219
+ }
1220
+
1221
+ /* ----------------------------------------------------------------------------
1222
+ * NotificationBell — bell trigger + badge + desktop popover / mobile sheet.
1223
+ * ------------------------------------------------------------------------- */
1224
+
1225
+ .etu-notif-bell {
1226
+ position: relative;
1227
+ display: inline-block;
1228
+ }
1229
+
1230
+ .etu-notif-bell-trigger {
1231
+ position: relative;
1232
+ background: transparent;
1233
+ border: none;
1234
+ padding: 0.4rem;
1235
+ border-radius: 999px;
1236
+ cursor: pointer;
1237
+ color: var(--etu-text);
1238
+ display: inline-flex;
1239
+ align-items: center;
1240
+ justify-content: center;
1241
+ line-height: 0;
1242
+ }
1243
+
1244
+ .etu-notif-bell-trigger:hover {
1245
+ background: var(--etu-surface-2);
1246
+ }
1247
+
1248
+ .etu-notif-bell-trigger:focus-visible {
1249
+ outline: 2px solid var(--etu-accent);
1250
+ outline-offset: 2px;
1251
+ }
1252
+
1253
+ .etu-notif-bell-badge {
1254
+ position: absolute;
1255
+ top: 0;
1256
+ right: 0;
1257
+ min-width: 16px;
1258
+ height: 16px;
1259
+ padding: 0 4px;
1260
+ border-radius: 999px;
1261
+ background: var(--etu-danger, #dc2626);
1262
+ color: white;
1263
+ font-size: 10px;
1264
+ font-weight: 600;
1265
+ line-height: 16px;
1266
+ text-align: center;
1267
+ pointer-events: none;
1268
+ box-shadow: 0 0 0 2px var(--etu-surface);
1269
+ }
1270
+
1271
+ .etu-notif-bell-popover {
1272
+ position: absolute;
1273
+ width: min(360px, calc(100vw - 1rem));
1274
+ max-height: min(70vh, 520px);
1275
+ background: var(--etu-surface);
1276
+ border: 1px solid var(--etu-border);
1277
+ border-radius: 0.6rem;
1278
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.16);
1279
+ z-index: 60;
1280
+ display: flex;
1281
+ flex-direction: column;
1282
+ overflow: hidden;
1283
+ }
1284
+
1285
+ .etu-notif-bell-popover--bottom-right { right: 0; top: calc(100% + 0.4rem); }
1286
+ .etu-notif-bell-popover--bottom-left { left: 0; top: calc(100% + 0.4rem); }
1287
+ .etu-notif-bell-popover--top-right { right: 0; bottom: calc(100% + 0.4rem); }
1288
+ .etu-notif-bell-popover--top-left { left: 0; bottom: calc(100% + 0.4rem); }
1289
+
1290
+ .etu-notif-bell-backdrop {
1291
+ position: fixed;
1292
+ inset: 0;
1293
+ background: rgba(0, 0, 0, 0.4);
1294
+ z-index: 70;
1295
+ animation: etu-notif-bell-fade 0.15s ease-out;
1296
+ }
1297
+
1298
+ .etu-notif-bell-sheet {
1299
+ position: fixed;
1300
+ left: 0;
1301
+ right: 0;
1302
+ bottom: 0;
1303
+ z-index: 71;
1304
+ background: var(--etu-surface);
1305
+ border-radius: 1rem 1rem 0 0;
1306
+ box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.18);
1307
+ max-height: 85vh;
1308
+ display: flex;
1309
+ flex-direction: column;
1310
+ overflow: hidden;
1311
+ padding-bottom: env(safe-area-inset-bottom, 0);
1312
+ animation: etu-notif-bell-slide-up 0.2s ease-out;
1313
+ }
1314
+
1315
+ .etu-notif-bell-sheet-grabber {
1316
+ width: 36px;
1317
+ height: 4px;
1318
+ background: var(--etu-border);
1319
+ border-radius: 999px;
1320
+ margin: 0.55rem auto 0.25rem;
1321
+ flex: 0 0 auto;
1322
+ }
1323
+
1324
+ @keyframes etu-notif-bell-fade {
1325
+ from { opacity: 0; }
1326
+ to { opacity: 1; }
1327
+ }
1328
+
1329
+ @keyframes etu-notif-bell-slide-up {
1330
+ from { transform: translateY(100%); }
1331
+ to { transform: translateY(0); }
1332
+ }
1333
+
1334
+ .etu-notif-bell-header {
1335
+ display: flex;
1336
+ align-items: center;
1337
+ justify-content: space-between;
1338
+ padding: 0.6rem 0.9rem;
1339
+ border-bottom: 1px solid var(--etu-border);
1340
+ flex: 0 0 auto;
1341
+ }
1342
+
1343
+ .etu-notif-bell-title {
1344
+ font-size: 0.95rem;
1345
+ font-weight: 600;
1346
+ color: var(--etu-text);
1347
+ }
1348
+
1349
+ .etu-notif-bell-close {
1350
+ background: transparent;
1351
+ border: none;
1352
+ color: var(--etu-text-muted);
1353
+ font-size: 1.4rem;
1354
+ line-height: 1;
1355
+ padding: 0.1rem 0.4rem;
1356
+ cursor: pointer;
1357
+ border-radius: 0.3rem;
1358
+ }
1359
+
1360
+ .etu-notif-bell-close:hover {
1361
+ background: var(--etu-surface-2);
1362
+ color: var(--etu-text);
1363
+ }
1364
+
1365
+ .etu-notif-bell-items {
1366
+ flex: 1 1 auto;
1367
+ overflow-y: auto;
1368
+ -webkit-overflow-scrolling: touch;
1369
+ padding: 0.3rem 0;
1370
+ }
1371
+
1372
+ .etu-notif-bell-item {
1373
+ padding: 0.7rem 0.9rem;
1374
+ border-bottom: 1px solid var(--etu-border);
1375
+ font-size: 0.85rem;
1376
+ color: var(--etu-text);
1377
+ }
1378
+
1379
+ .etu-notif-bell-item:last-child {
1380
+ border-bottom: none;
1381
+ }
1382
+
1383
+ .etu-notif-bell-empty {
1384
+ padding: 2rem 1rem;
1385
+ text-align: center;
1386
+ color: var(--etu-text-muted);
1387
+ font-size: 0.85rem;
1388
+ }
1389
+
1390
+ .etu-notif-bell-footer {
1391
+ padding: 0.6rem 0.9rem;
1392
+ border-top: 1px solid var(--etu-border);
1393
+ flex: 0 0 auto;
1394
+ font-size: 0.82rem;
1395
+ }
1396
+
1397
+ /* ----------------------------------------------------------------------------
1398
+ * MobileTabBar — fixed bottom tab bar; hidden at desktop widths.
1399
+ * ------------------------------------------------------------------------- */
1400
+
1401
+ /* ----------------------------------------------------------------------------
1402
+ * Liquid Glass utility — translucent depth-aware material (iOS 26 / Tahoe).
1403
+ * Paired with a solid fallback for browsers without backdrop-filter.
1404
+ * ------------------------------------------------------------------------- */
1405
+
1406
+ .etu-glass {
1407
+ background: color-mix(in srgb, var(--etu-surface) 65%, transparent);
1408
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
1409
+ backdrop-filter: blur(24px) saturate(180%);
1410
+ border: 0.5px solid color-mix(in srgb, var(--etu-text) 10%, transparent);
1411
+ }
1412
+
1413
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
1414
+ .etu-glass {
1415
+ background: var(--etu-surface);
1416
+ }
1417
+ }
1418
+
1419
+ /* ----------------------------------------------------------------------------
1420
+ * NavigationBar — iOS-style small-title bar. Single 48px row, optional
1421
+ * sticky + safe-area-inset-top, fade-on-scroll opacity + shadow ramp.
1422
+ * ------------------------------------------------------------------------- */
1423
+
1424
+ .etu-navbar {
1425
+ z-index: 35;
1426
+ width: 100%;
1427
+ box-sizing: border-box;
1428
+ border-radius: 0;
1429
+ border-left: 0;
1430
+ border-right: 0;
1431
+ border-top: 0;
1432
+ border-bottom: 1px solid color-mix(in srgb, var(--etu-text) 8%, transparent);
1433
+ opacity: 0.92;
1434
+ transition: opacity 160ms ease, box-shadow 160ms ease;
1435
+ }
1436
+
1437
+ .etu-navbar--sticky {
1438
+ position: sticky;
1439
+ top: 0;
1440
+ padding-top: env(safe-area-inset-top);
1441
+ }
1442
+
1443
+ /* `safeAreaTop={false}` — for apps that already have a global top chrome
1444
+ * bar above the per-page NavigationBar. Drops the safe-area padding so
1445
+ * the two don't stack into an offscreen title row on notched iOS PWAs. */
1446
+ .etu-navbar--no-safe-top {
1447
+ padding-top: 0;
1448
+ }
1449
+
1450
+ .etu-navbar--borderless {
1451
+ border-bottom: 0;
1452
+ }
1453
+
1454
+ .etu-navbar--scrolled {
1455
+ opacity: 1;
1456
+ box-shadow: 0 1px 0 color-mix(in srgb, var(--etu-text) 6%, transparent),
1457
+ 0 8px 24px color-mix(in srgb, var(--etu-text) 8%, transparent);
1458
+ }
1459
+
1460
+ .etu-navbar-inner {
1461
+ display: grid;
1462
+ grid-template-columns: auto 1fr auto;
1463
+ align-items: center;
1464
+ gap: 0.5rem;
1465
+ min-height: 48px;
1466
+ padding: 0 0.5rem;
1467
+ }
1468
+
1469
+ .etu-navbar-leading,
1470
+ .etu-navbar-trailing {
1471
+ display: inline-flex;
1472
+ align-items: center;
1473
+ gap: 0.25rem;
1474
+ min-height: 48px;
1475
+ }
1476
+
1477
+ .etu-navbar-trailing {
1478
+ justify-content: flex-end;
1479
+ }
1480
+
1481
+ .etu-navbar-title {
1482
+ margin: 0;
1483
+ /* Hard-coded 17px (HIG default for navigation title) instead of 1rem to
1484
+ * survive any host-side root font-size scaling. iOS PWA report: title
1485
+ * appeared visibly smaller in standalone than in Safari, traced to a
1486
+ * consumer that set html font-size < 16px; rem-based sizing inherited
1487
+ * the shrink. Fixed pixel keeps the title at HIG size in every host. */
1488
+ font-size: 17px;
1489
+ font-weight: 600;
1490
+ line-height: 1.2;
1491
+ color: var(--etu-text);
1492
+ text-align: center;
1493
+ white-space: nowrap;
1494
+ overflow: hidden;
1495
+ text-overflow: ellipsis;
1496
+ font-family: var(--etu-font);
1497
+ }
1498
+
1499
+ .etu-navbar-back {
1500
+ display: inline-flex;
1501
+ align-items: center;
1502
+ gap: 0.15rem;
1503
+ min-width: 48px;
1504
+ min-height: 48px;
1505
+ padding: 0 0.4rem;
1506
+ background: transparent;
1507
+ border: 0;
1508
+ color: var(--etu-accent);
1509
+ font: inherit;
1510
+ cursor: pointer;
1511
+ }
1512
+
1513
+ .etu-navbar-back:focus-visible {
1514
+ outline: 2px solid var(--etu-accent);
1515
+ outline-offset: -2px;
1516
+ border-radius: 0.4rem;
1517
+ }
1518
+
1519
+ .etu-navbar-chevron {
1520
+ font-size: 1.5rem;
1521
+ line-height: 1;
1522
+ display: inline-block;
1523
+ transform: translateY(-1px);
1524
+ }
1525
+
1526
+ .etu-navbar-back-label {
1527
+ font-size: 0.95rem;
1528
+ }
1529
+
1530
+ /* ----------------------------------------------------------------------------
1531
+ * MobileTabBar — floating pill, iOS 26 Liquid Glass. Hidden at desktop.
1532
+ * ------------------------------------------------------------------------- */
1533
+
1534
+ .etu-mobile-tab-bar {
1535
+ position: fixed;
1536
+ left: 0.75rem;
1537
+ right: 0.75rem;
1538
+ bottom: calc(env(safe-area-inset-bottom) + 0.5rem);
1539
+ z-index: 40;
1540
+ display: flex;
1541
+ align-items: stretch;
1542
+ justify-content: space-around;
1543
+ gap: 0.25rem;
1544
+ padding: 0.35rem;
1545
+ border-radius: 28px;
1546
+ box-shadow: 0 12px 32px color-mix(in srgb, var(--etu-text) 14%, transparent);
1547
+ }
1548
+
1549
+ @media (min-width: 720px) {
1550
+ .etu-mobile-tab-bar {
1551
+ display: none;
1552
+ }
1553
+ }
1554
+
1555
+ .etu-mobile-tab-bar-item {
1556
+ flex: 1 1 0;
1557
+ display: flex;
1558
+ flex-direction: column;
1559
+ align-items: center;
1560
+ justify-content: center;
1561
+ gap: 0.1rem;
1562
+ min-height: 48px;
1563
+ padding: 0.3rem 0.25rem;
1564
+ background: transparent;
1565
+ border: 0;
1566
+ border-radius: 22px;
1567
+ color: var(--etu-text-subtle);
1568
+ font: inherit;
1569
+ text-decoration: none;
1570
+ cursor: pointer;
1571
+ transition: background 120ms ease, color 120ms ease;
1572
+ }
1573
+
1574
+ .etu-mobile-tab-bar-item:focus-visible {
1575
+ outline: 2px solid var(--etu-accent);
1576
+ outline-offset: -2px;
1577
+ }
1578
+
1579
+ .etu-mobile-tab-bar-item--active {
1580
+ color: var(--etu-on-accent);
1581
+ background: var(--etu-accent);
1582
+ }
1583
+
1584
+ .etu-mobile-tab-bar-icon {
1585
+ display: inline-flex;
1586
+ align-items: center;
1587
+ justify-content: center;
1588
+ }
1589
+
1590
+ .etu-mobile-tab-bar-icon > svg {
1591
+ width: 22px;
1592
+ height: 22px;
1593
+ }
1594
+
1595
+ .etu-mobile-tab-bar-label {
1596
+ font-size: 0.65rem;
1597
+ line-height: 1;
1598
+ }
1599
+
1600
+ /* ----------------------------------------------------------------------------
1601
+ * Sidebar — desktop nav shell. Hidden at mobile widths; counterpart to
1602
+ * <MobileTabBar>. Composition: AppHeader / Primary / Secondary / Footer.
1603
+ * ------------------------------------------------------------------------- */
1604
+
1605
+ .etu-sidebar {
1606
+ width: var(--etu-sidebar-w, 240px);
1607
+ flex: 0 0 var(--etu-sidebar-w, 240px);
1608
+ position: sticky;
1609
+ top: 0;
1610
+ align-self: flex-start;
1611
+ height: 100dvh;
1612
+ overflow-y: auto;
1613
+ display: flex;
1614
+ flex-direction: column;
1615
+ gap: 0.75rem;
1616
+ padding: 0.75rem 0.5rem;
1617
+ background: var(--etu-surface);
1618
+ border-right: 1px solid var(--etu-border);
1619
+ box-sizing: border-box;
1620
+ }
1621
+
1622
+ @media (max-width: 719px) {
1623
+ .etu-sidebar {
1624
+ display: none;
1625
+ }
1626
+ }
1627
+
1628
+ .etu-sidebar-header {
1629
+ display: flex;
1630
+ flex-direction: column;
1631
+ gap: 0.5rem;
1632
+ padding: 0.5rem 0.5rem 0.25rem;
1633
+ }
1634
+
1635
+ .etu-sidebar-header-app {
1636
+ display: flex;
1637
+ align-items: center;
1638
+ gap: 0.5rem;
1639
+ }
1640
+
1641
+ .etu-sidebar-header-icon {
1642
+ display: inline-flex;
1643
+ align-items: center;
1644
+ justify-content: center;
1645
+ width: 1.5rem;
1646
+ height: 1.5rem;
1647
+ }
1648
+
1649
+ .etu-sidebar-header-icon > svg {
1650
+ width: 22px;
1651
+ height: 22px;
1652
+ }
1653
+
1654
+ .etu-sidebar-header-name {
1655
+ font-size: 0.95rem;
1656
+ font-weight: 600;
1657
+ color: var(--etu-text);
1658
+ letter-spacing: -0.01em;
1659
+ }
1660
+
1661
+ .etu-sidebar-header-extra {
1662
+ font-size: 0.8rem;
1663
+ color: var(--etu-text-muted);
1664
+ }
1665
+
1666
+ .etu-sidebar-section {
1667
+ display: flex;
1668
+ flex-direction: column;
1669
+ gap: 0.1rem;
1670
+ }
1671
+
1672
+ .etu-sidebar-section--secondary {
1673
+ margin-top: 0.25rem;
1674
+ padding-top: 0.5rem;
1675
+ border-top: 1px solid var(--etu-border);
1676
+ }
1677
+
1678
+ .etu-sidebar-section--secondary + .etu-sidebar-section--secondary {
1679
+ margin-top: 0.5rem;
1680
+ padding-top: 0.25rem;
1681
+ border-top: 0;
1682
+ }
1683
+
1684
+ .etu-sidebar-caption {
1685
+ font-size: 0.7rem;
1686
+ font-weight: 600;
1687
+ text-transform: uppercase;
1688
+ letter-spacing: 0.04em;
1689
+ color: var(--etu-text-subtle);
1690
+ padding: 0.25rem 0.5rem;
1691
+ }
1692
+
1693
+ .etu-sidebar-section-caption {
1694
+ font-size: 0.7rem;
1695
+ font-weight: 600;
1696
+ text-transform: uppercase;
1697
+ letter-spacing: 0.04em;
1698
+ color: var(--etu-text-subtle);
1699
+ padding: 0.4rem 0.5rem 0.15rem;
1700
+ }
1701
+
1702
+ .etu-sidebar-item {
1703
+ display: flex;
1704
+ align-items: center;
1705
+ gap: 0.6rem;
1706
+ padding: 0.5rem 0.6rem;
1707
+ background: transparent;
1708
+ border: 0;
1709
+ border-radius: var(--etu-r-sm);
1710
+ color: var(--etu-text-muted);
1711
+ font: inherit;
1712
+ font-size: 0.9rem;
1713
+ text-align: left;
1714
+ text-decoration: none;
1715
+ cursor: pointer;
1716
+ width: 100%;
1717
+ box-sizing: border-box;
1718
+ }
1719
+
1720
+ .etu-sidebar-item:hover {
1721
+ background: var(--etu-surface-2);
1722
+ color: var(--etu-text);
1723
+ }
1724
+
1725
+ .etu-sidebar-item:focus-visible {
1726
+ outline: 2px solid var(--etu-accent);
1727
+ outline-offset: -2px;
1728
+ }
1729
+
1730
+ .etu-sidebar-item--active {
1731
+ background: var(--etu-accent-soft);
1732
+ color: var(--etu-text);
1733
+ font-weight: 600;
1734
+ }
1735
+
1736
+ .etu-sidebar-item-icon {
1737
+ display: inline-flex;
1738
+ align-items: center;
1739
+ justify-content: center;
1740
+ width: 1.25rem;
1741
+ height: 1.25rem;
1742
+ flex-shrink: 0;
1743
+ }
1744
+
1745
+ .etu-sidebar-item-icon > svg {
1746
+ width: 18px;
1747
+ height: 18px;
1748
+ }
1749
+
1750
+ .etu-sidebar-item-label {
1751
+ flex: 1 1 auto;
1752
+ min-width: 0;
1753
+ overflow: hidden;
1754
+ text-overflow: ellipsis;
1755
+ white-space: nowrap;
1756
+ }
1757
+
1758
+ .etu-sidebar-footer {
1759
+ margin-top: auto;
1760
+ padding: 0.5rem;
1761
+ border-top: 1px solid var(--etu-border);
1762
+ font-size: 0.85rem;
1763
+ color: var(--etu-text-muted);
1764
+ }
1765
+
1766
+ /* --------------------------------------------------------------------------
1767
+ * StatusBanner — top-of-app strip surfacing service-admin declared incidents.
1768
+ * Outage incidents take origin offline and never reach the banner; the strip
1769
+ * only renders for degraded / maintenance.
1770
+ * -------------------------------------------------------------------------- */
1771
+
1772
+ .etu-status-banner {
1773
+ position: sticky;
1774
+ top: 0;
1775
+ z-index: 40;
1776
+ display: flex;
1777
+ align-items: center;
1778
+ gap: 0.6rem;
1779
+ padding: 0.45rem 0.85rem;
1780
+ font-family: var(--etu-font);
1781
+ font-size: 0.83rem;
1782
+ line-height: 1.3;
1783
+ border-bottom: 1px solid color-mix(in srgb, currentColor 22%, transparent);
1784
+ }
1785
+
1786
+ .etu-status-banner-label {
1787
+ flex-shrink: 0;
1788
+ font-weight: 600;
1789
+ letter-spacing: 0.01em;
1790
+ }
1791
+
1792
+ .etu-status-banner-msg {
1793
+ flex: 1;
1794
+ min-width: 0;
1795
+ overflow: hidden;
1796
+ text-overflow: ellipsis;
1797
+ white-space: nowrap;
1798
+ }
1799
+
1800
+ .etu-status-banner-eta {
1801
+ flex-shrink: 0;
1802
+ opacity: 0.85;
1803
+ font-variant-numeric: tabular-nums;
1804
+ }
1805
+
1806
+ .etu-status-banner-close {
1807
+ flex-shrink: 0;
1808
+ background: transparent;
1809
+ border: 0;
1810
+ color: inherit;
1811
+ font-size: 1.1rem;
1812
+ line-height: 1;
1813
+ cursor: pointer;
1814
+ padding: 0.1rem 0.35rem;
1815
+ border-radius: var(--etu-r-sm);
1816
+ opacity: 0.7;
1817
+ }
1818
+ .etu-status-banner-close:hover { opacity: 1; }
1819
+
1820
+ /* Degraded (warning): amber */
1821
+ .etu-status-banner-degraded {
1822
+ background: color-mix(in srgb, #f5a524 20%, var(--etu-bg));
1823
+ color: color-mix(in srgb, #f5a524 80%, var(--etu-text));
1824
+ }
1825
+ /* Maintenance (planned): muted accent */
1826
+ .etu-status-banner-maintenance {
1827
+ background: color-mix(in srgb, var(--etu-accent) 18%, var(--etu-bg));
1828
+ color: color-mix(in srgb, var(--etu-accent) 75%, var(--etu-text));
1829
+ }
1830
+ /* Outage shouldn't reach here, but if a caller forces it, render danger-coloured */
1831
+ .etu-status-banner-outage {
1832
+ background: color-mix(in srgb, var(--etu-err) 22%, var(--etu-bg));
1833
+ color: color-mix(in srgb, var(--etu-err) 80%, var(--etu-text));
1834
+ }
1835
+
1836
+ @media (max-width: 480px) {
1837
+ .etu-status-banner {
1838
+ flex-wrap: wrap;
1839
+ }
1840
+ .etu-status-banner-msg {
1841
+ white-space: normal;
1842
+ }
1843
+ }
1844
+
1845
+ /* ----------------------------------------------------------------------------
1846
+ * PolicyChangeBanner — hub-driven upcoming legal-change announcement.
1847
+ * ------------------------------------------------------------------------- */
1848
+
1849
+ .etu-policy-change-banner {
1850
+ display: flex;
1851
+ align-items: center;
1852
+ gap: 0.6rem;
1853
+ padding: 0.45rem 0.85rem;
1854
+ font-family: var(--etu-font);
1855
+ font-size: 0.83rem;
1856
+ line-height: 1.3;
1857
+ background: color-mix(in srgb, #f5a524 18%, var(--etu-bg));
1858
+ color: color-mix(in srgb, #f5a524 82%, var(--etu-text));
1859
+ border-bottom: 1px solid color-mix(in srgb, currentColor 22%, transparent);
1860
+ }
1861
+
1862
+ /* Adverse (불리/중대): danger-coloured for stronger emphasis. */
1863
+ .etu-policy-change-banner--adverse {
1864
+ background: color-mix(in srgb, var(--etu-err) 20%, var(--etu-bg));
1865
+ color: color-mix(in srgb, var(--etu-err) 82%, var(--etu-text));
1866
+ font-weight: 500;
1867
+ }
1868
+
1869
+ .etu-policy-change-banner-body {
1870
+ flex: 1;
1871
+ min-width: 0;
1872
+ }
1873
+
1874
+ .etu-policy-change-banner-body a {
1875
+ font-weight: 600;
1876
+ text-decoration: underline;
1877
+ text-underline-offset: 2px;
1878
+ color: inherit;
1879
+ white-space: nowrap;
1880
+ }
1881
+
1882
+ .etu-policy-change-banner-close {
1883
+ flex-shrink: 0;
1884
+ background: transparent;
1885
+ border: 0;
1886
+ color: inherit;
1887
+ font-size: 1.1rem;
1888
+ line-height: 1;
1889
+ cursor: pointer;
1890
+ padding: 0.1rem 0.35rem;
1891
+ border-radius: var(--etu-r-sm);
1892
+ opacity: 0.7;
1893
+ }
1894
+ .etu-policy-change-banner-close:hover { opacity: 1; }
1895
+
1896
+ /* ----------------------------------------------------------------------------
1897
+ * DocsHub — in-app docs surface with optional skill download (alpha).
1898
+ * ------------------------------------------------------------------------- */
1899
+
1900
+ .etu-docs-hub {
1901
+ display: flex;
1902
+ flex-direction: column;
1903
+ gap: 18px;
1904
+ }
1905
+
1906
+ .etu-docs-hub-head {
1907
+ display: flex;
1908
+ align-items: flex-start;
1909
+ justify-content: space-between;
1910
+ gap: 16px;
1911
+ flex-wrap: wrap;
1912
+ }
1913
+
1914
+ .etu-docs-hub-head-text {
1915
+ display: flex;
1916
+ flex-direction: column;
1917
+ gap: 4px;
1918
+ min-width: 0;
1919
+ }
1920
+
1921
+ .etu-docs-hub-app-name {
1922
+ margin: 0;
1923
+ font-size: 22px;
1924
+ font-weight: 700;
1925
+ letter-spacing: -0.01em;
1926
+ color: var(--etu-text);
1927
+ }
1928
+
1929
+ .etu-docs-hub-description {
1930
+ margin: 0;
1931
+ color: var(--etu-text-muted);
1932
+ font-size: 14px;
1933
+ }
1934
+
1935
+ .etu-docs-hub-skill-btn {
1936
+ display: inline-flex;
1937
+ align-items: center;
1938
+ gap: 6px;
1939
+ padding: 8px 14px;
1940
+ border-radius: var(--etu-r, 10px);
1941
+ border: 1px solid var(--etu-border);
1942
+ background: var(--etu-surface);
1943
+ color: var(--etu-text);
1944
+ font: inherit;
1945
+ font-weight: 600;
1946
+ font-size: 13px;
1947
+ cursor: pointer;
1948
+ transition: background 140ms, border-color 140ms;
1949
+ }
1950
+
1951
+ .etu-docs-hub-skill-btn:hover {
1952
+ background: var(--etu-surface-2);
1953
+ border-color: var(--etu-accent-soft);
1954
+ }
1955
+
1956
+ .etu-docs-hub-body {
1957
+ display: grid;
1958
+ grid-template-columns: 200px 1fr;
1959
+ gap: 24px;
1960
+ align-items: start;
1961
+ }
1962
+
1963
+ .etu-docs-hub-nav {
1964
+ display: flex;
1965
+ flex-direction: column;
1966
+ gap: 2px;
1967
+ position: sticky;
1968
+ top: 16px;
1969
+ }
1970
+
1971
+ .etu-docs-hub-nav-item {
1972
+ display: block;
1973
+ width: 100%;
1974
+ padding: 8px 12px;
1975
+ border-radius: var(--etu-r, 8px);
1976
+ border: none;
1977
+ background: transparent;
1978
+ color: var(--etu-text-muted);
1979
+ font: inherit;
1980
+ font-weight: 500;
1981
+ font-size: 13.5px;
1982
+ text-align: left;
1983
+ cursor: pointer;
1984
+ transition: background 140ms, color 140ms;
1985
+ }
1986
+
1987
+ .etu-docs-hub-nav-item:hover {
1988
+ background: var(--etu-surface-2);
1989
+ color: var(--etu-text);
1990
+ }
1991
+
1992
+ .etu-docs-hub-nav-item--active {
1993
+ background: var(--etu-accent-soft);
1994
+ color: var(--etu-text);
1995
+ font-weight: 600;
1996
+ }
1997
+
1998
+ .etu-docs-hub-section {
1999
+ min-width: 0;
2000
+ }
2001
+
2002
+ .etu-docs-hub-section-summary {
2003
+ margin: 0 0 16px;
2004
+ color: var(--etu-text-muted);
2005
+ font-size: 14px;
2006
+ }
2007
+
2008
+ @media (max-width: 768px) {
2009
+ .etu-docs-hub-body {
2010
+ grid-template-columns: 1fr;
2011
+ }
2012
+ .etu-docs-hub-nav {
2013
+ position: static;
2014
+ flex-direction: row;
2015
+ flex-wrap: wrap;
2016
+ gap: 4px;
2017
+ overflow-x: auto;
2018
+ }
2019
+ .etu-docs-hub-nav-item {
2020
+ width: auto;
2021
+ }
2022
+ }
2023
+
2024
+ .etu-docs-hub-skill-usage h4 {
2025
+ margin: 18px 0 6px;
2026
+ font-size: 13px;
2027
+ font-weight: 600;
2028
+ color: var(--etu-text);
2029
+ }
2030
+ .etu-docs-hub-skill-usage p,
2031
+ .etu-docs-hub-skill-usage li {
2032
+ font-size: 13.5px;
2033
+ line-height: 1.6;
2034
+ color: var(--etu-text);
2035
+ }
2036
+ .etu-docs-hub-skill-usage code {
2037
+ font-family: var(--etu-mono, ui-monospace, "SF Mono", monospace);
2038
+ font-size: 12.5px;
2039
+ padding: 1px 5px;
2040
+ border-radius: 4px;
2041
+ background: var(--etu-surface-2);
2042
+ color: var(--etu-text);
2043
+ }
2044
+ .etu-docs-hub-skill-usage ol {
2045
+ padding-left: 20px;
2046
+ margin: 6px 0 12px;
2047
+ }
2048
+ .etu-docs-hub-skill-install {
2049
+ display: flex;
2050
+ align-items: center;
2051
+ gap: 8px;
2052
+ padding: 10px 12px;
2053
+ margin: 8px 0 12px;
2054
+ background: var(--etu-surface-2);
2055
+ border: 1px solid var(--etu-border);
2056
+ border-radius: 6px;
2057
+ overflow-x: auto;
2058
+ }
2059
+ .etu-docs-hub-skill-install-cmd {
2060
+ flex: 1 1 auto;
2061
+ font-family: var(--etu-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
2062
+ font-size: 13px;
2063
+ white-space: nowrap;
2064
+ color: var(--etu-text);
2065
+ background: transparent;
2066
+ padding: 0;
2067
+ }
2068
+ .etu-docs-hub-skill-install-note {
2069
+ font-size: 12px;
2070
+ color: var(--etu-text-muted);
2071
+ margin: 0 0 12px;
2072
+ }
2073
+
2074
+ /* --- DataTable -------------------------------------------------------- */
2075
+ /* Wide = real <table>. Narrow (container < 720px) = row-per-field cards.
2076
+ Container-query driven so an open sidebar collapsing the main area
2077
+ below 720px flips the table to cards on desktop too, not just on
2078
+ phones. Falls back to viewport @media for browsers without
2079
+ container queries. */
2080
+
2081
+ .etu-dt {
2082
+ container-type: inline-size;
2083
+ container-name: etu-dt;
2084
+ width: 100%;
2085
+ }
2086
+
2087
+ /* shared cell styling */
2088
+ .etu-dt-cell {
2089
+ text-align: left;
2090
+ vertical-align: top;
2091
+ }
2092
+ .etu-dt-align-right { text-align: right; }
2093
+ .etu-dt-align-center { text-align: center; }
2094
+ .etu-dt-nowrap { white-space: nowrap; }
2095
+
2096
+ /* WIDE — real table */
2097
+ .etu-dt-table {
2098
+ width: 100%;
2099
+ border-collapse: collapse;
2100
+ font-size: 0.9rem;
2101
+ }
2102
+ .etu-dt-table thead th {
2103
+ text-align: left;
2104
+ font-weight: 600;
2105
+ font-size: 0.8rem;
2106
+ color: var(--etu-dim, #999);
2107
+ text-transform: none;
2108
+ padding: 0.5rem 0.75rem;
2109
+ border-bottom: 1px solid var(--etu-border, #333);
2110
+ white-space: nowrap;
2111
+ }
2112
+ .etu-dt-table tbody td {
2113
+ padding: 0.6rem 0.75rem;
2114
+ border-bottom: 1px solid var(--etu-border-subtle, #222);
2115
+ color: var(--etu-text, inherit);
2116
+ }
2117
+ .etu-dt-table tbody tr:last-child td { border-bottom: 0; }
2118
+ .etu-dt-actions { text-align: right; }
2119
+ .etu-dt-actions > * + * { margin-left: 0.4rem; }
2120
+
2121
+ /* CARDS — row-per-field stacked card */
2122
+ .etu-dt-cards {
2123
+ list-style: none;
2124
+ padding: 0;
2125
+ margin: 0;
2126
+ display: grid;
2127
+ gap: 0.6rem;
2128
+ }
2129
+ .etu-dt-card {
2130
+ border: 1px solid var(--etu-border, #333);
2131
+ border-radius: 8px;
2132
+ padding: 0.75rem 0.9rem;
2133
+ background: var(--etu-surface, transparent);
2134
+ }
2135
+ .etu-dt-card-header {
2136
+ font-weight: 600;
2137
+ font-size: 1rem;
2138
+ margin-bottom: 0.4rem;
2139
+ color: var(--etu-text, inherit);
2140
+ word-break: break-word;
2141
+ }
2142
+ .etu-dt-card-fields {
2143
+ margin: 0;
2144
+ display: grid;
2145
+ gap: 0.35rem;
2146
+ }
2147
+ .etu-dt-card-field {
2148
+ display: grid;
2149
+ grid-template-columns: minmax(7rem, auto) 1fr;
2150
+ gap: 0.75rem;
2151
+ align-items: baseline;
2152
+ }
2153
+ .etu-dt-card-label {
2154
+ margin: 0;
2155
+ font-size: 0.8rem;
2156
+ color: var(--etu-dim, #999);
2157
+ white-space: nowrap;
2158
+ }
2159
+ .etu-dt-card-value {
2160
+ margin: 0;
2161
+ font-size: 0.9rem;
2162
+ text-align: right;
2163
+ word-break: break-word;
2164
+ }
2165
+ .etu-dt-card-actions {
2166
+ margin-top: 0.6rem;
2167
+ padding-top: 0.6rem;
2168
+ border-top: 1px solid var(--etu-border-subtle, #222);
2169
+ display: flex;
2170
+ gap: 0.4rem;
2171
+ flex-wrap: wrap;
2172
+ justify-content: flex-end;
2173
+ }
2174
+
2175
+ /* AUTO mode (default): show table on wide, cards on narrow. */
2176
+ .etu-dt .etu-dt-cards { display: none; }
2177
+ .etu-dt .etu-dt-table { display: table; }
2178
+ @container etu-dt (max-width: 720px) {
2179
+ .etu-dt:not(.etu-dt--force-wide) .etu-dt-table { display: none; }
2180
+ .etu-dt:not(.etu-dt--force-wide) .etu-dt-cards { display: grid; }
2181
+ }
2182
+ /* @container fallback for browsers without support */
2183
+ @supports not (container-type: inline-size) {
2184
+ @media (max-width: 720px) {
2185
+ .etu-dt:not(.etu-dt--force-wide) .etu-dt-table { display: none; }
2186
+ .etu-dt:not(.etu-dt--force-wide) .etu-dt-cards { display: grid; }
2187
+ }
2188
+ }
2189
+
2190
+ /* FORCE modes */
2191
+ .etu-dt--force-wide .etu-dt-cards { display: none !important; }
2192
+ .etu-dt--force-wide .etu-dt-table { display: table !important; }
2193
+ .etu-dt--force-cards .etu-dt-table { display: none !important; }
2194
+ .etu-dt--force-cards .etu-dt-cards { display: grid !important; }
2195
+
2196
+ /* ----------------------------------------------------------------------------
2197
+ * 3-tier responsive contract (v0.28).
2198
+ *
2199
+ * `data-vp` is set on <html> by `noFlashViewportScript` + the
2200
+ * <ViewportProvider>. Tier names mirror `responsive-3tier.md`:
2201
+ *
2202
+ * mobile : < 720px <MobileTabBar> owns nav; <Sidebar> hidden
2203
+ * tablet : 720–1023 <Sidebar tabletMode="rail|drawer|full">
2204
+ * desktop : ≥ 1024px <Sidebar> full width
2205
+ *
2206
+ * Media-query fallbacks are kept so apps that haven't injected the
2207
+ * no-flash script still get the right layout (CSS-only, slightly more
2208
+ * verbose since data-vp isn't available).
2209
+ * ------------------------------------------------------------------------- */
2210
+
2211
+ /* Tablet tier — rail mode (default). 64px icon-only column. */
2212
+ @media (min-width: 720px) and (max-width: 1023px) {
2213
+ .etu-sidebar[data-tablet-mode="rail"] {
2214
+ width: 64px;
2215
+ flex: 0 0 64px;
2216
+ padding: 0.5rem 0.25rem;
2217
+ }
2218
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-header-name,
2219
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-header-extra,
2220
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-item-label,
2221
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-caption,
2222
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-section-caption,
2223
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-footer {
2224
+ display: none;
2225
+ }
2226
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-item {
2227
+ justify-content: center;
2228
+ padding: 0.5rem 0;
2229
+ gap: 0;
2230
+ }
2231
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-header {
2232
+ padding: 0.25rem 0;
2233
+ justify-content: center;
2234
+ align-items: center;
2235
+ }
2236
+ .etu-sidebar[data-tablet-mode="rail"] .etu-sidebar-header-app {
2237
+ justify-content: center;
2238
+ }
2239
+
2240
+ /* Drawer mode — hidden by default, slides in over a scrim when open. */
2241
+ .etu-sidebar[data-tablet-mode="drawer"] {
2242
+ position: fixed;
2243
+ inset: 0 auto 0 0;
2244
+ z-index: 50;
2245
+ width: 280px;
2246
+ max-width: 86vw;
2247
+ transform: translateX(-100%);
2248
+ transition: transform 200ms ease;
2249
+ box-shadow: 4px 0 24px color-mix(in srgb, var(--etu-text) 18%, transparent);
2250
+ }
2251
+ .etu-sidebar[data-tablet-mode="drawer"][data-open="true"] {
2252
+ transform: translateX(0);
2253
+ }
2254
+ .etu-sidebar-scrim {
2255
+ position: fixed;
2256
+ inset: 0;
2257
+ z-index: 49;
2258
+ background: color-mix(in srgb, var(--etu-text) 32%, transparent);
2259
+ backdrop-filter: blur(2px);
2260
+ }
2261
+
2262
+ /* Toggle button is visible only at the tablet tier. */
2263
+ .etu-sidebar-toggle {
2264
+ display: inline-flex;
2265
+ }
2266
+ }
2267
+
2268
+ /* Mobile + desktop hide the toggle (mobile uses MobileTabBar, desktop the
2269
+ * full sidebar is already visible). */
2270
+ @media (max-width: 719px), (min-width: 1024px) {
2271
+ .etu-sidebar-toggle {
2272
+ display: none;
2273
+ }
2274
+ }
2275
+
2276
+ /* Drawer in desktop tier — show full inline (defeat tablet's fixed positioning
2277
+ * in case the consumer left tabletMode="drawer" wired up). */
2278
+ @media (min-width: 1024px) {
2279
+ .etu-sidebar[data-tablet-mode="drawer"] {
2280
+ position: sticky;
2281
+ top: 0;
2282
+ transform: none;
2283
+ width: var(--etu-sidebar-w, 240px);
2284
+ flex: 0 0 var(--etu-sidebar-w, 240px);
2285
+ box-shadow: none;
2286
+ }
2287
+ .etu-sidebar-scrim {
2288
+ display: none;
2289
+ }
2290
+ }
2291
+
2292
+ /* Toggle button — chrome-bar hamburger. */
2293
+ .etu-sidebar-toggle {
2294
+ align-items: center;
2295
+ justify-content: center;
2296
+ width: 40px;
2297
+ height: 40px;
2298
+ padding: 0;
2299
+ background: transparent;
2300
+ border: 0;
2301
+ border-radius: 10px;
2302
+ color: var(--etu-text);
2303
+ cursor: pointer;
2304
+ transition: background 120ms ease;
2305
+ }
2306
+ .etu-sidebar-toggle:hover {
2307
+ background: color-mix(in srgb, var(--etu-text) 8%, transparent);
2308
+ }
2309
+ .etu-sidebar-toggle:focus-visible {
2310
+ outline: 2px solid var(--etu-accent);
2311
+ outline-offset: 2px;
2312
+ }