@webority/theme 0.0.9

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,3296 @@
1
+ // ============================================================================
2
+ // _wui-components.scss
3
+ // ----------------------------------------------------------------------------
4
+ // Extracted portal component layer — verbatim CSS pulled from the Lumex
5
+ // customer portal's `design-system.css`, `components.css`, and
6
+ // `portal-helpers.css` (Lumex.Web.Customer/src/assets/styles/).
7
+ //
8
+ // Only rules whose subject is a genuinely shared, reusable UI-component class
9
+ // were kept (fields, badges, modals, tables, drawers, phone/date/search
10
+ // selects, avatars, empty states, etc). App-screen / marketing / page-specific
11
+ // rules were dropped. Custom-property references (--color-*, --radius-*,
12
+ // --shadow-*, etc.) are left exactly as written — a separate aliases partial
13
+ // (_wui-aliases.scss) resolves them onto --wui-* tokens.
14
+ //
15
+ // Plain CSS is valid SCSS; nothing here has been re-nested or renamed.
16
+ // ============================================================================
17
+
18
+ // ── Checkbox & Radio ──
19
+ // Lumex selection controls: a 20px glyph centred in a 36px circular hit area
20
+ // that shows a soft primary-tinted ripple on hover. Unchecked = muted
21
+ // grey-blue outline; checked = solid primary. Checkbox = squircle box + white
22
+ // tick; radio = ring + filled centre dot. Pure CSS, no JS — drives the native
23
+ // input via `appearance: none`, so it stays keyboard- and screen-reader-
24
+ // accessible.
25
+ //
26
+ // Markup:
27
+ // <label class="lx-control">
28
+ // <input type="checkbox" class="lx-check"> (or type="radio" class="lx-radio")
29
+ // <span class="lx-control-label">Full-time</span>
30
+ // </label>
31
+ // Group rows in a `.lx-control-group` (horizontal, wraps) or stack them.
32
+ .lx-control {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: 6px;
36
+ cursor: pointer;
37
+ user-select: none;
38
+ font-size: 14px;
39
+ color: var(--color-text);
40
+ line-height: 1.4;
41
+ }
42
+ .lx-control-group {
43
+ display: flex;
44
+ flex-wrap: wrap;
45
+ align-items: center;
46
+ gap: 8px 20px;
47
+ }
48
+ .lx-control-label {
49
+ padding-right: 2px;
50
+ }
51
+
52
+ // The control glyph: 20px box inside a 36px circular tap target (8px ring).
53
+ .lx-check,
54
+ .lx-radio {
55
+ appearance: none;
56
+ -webkit-appearance: none;
57
+ flex: none;
58
+ box-sizing: border-box;
59
+ width: 20px;
60
+ height: 20px;
61
+ // 8px transparent ring → 36px hit area + room for the hover ripple
62
+ margin: 8px;
63
+ position: relative;
64
+ border: 1.6px solid var(--color-border-strong);
65
+ background: transparent;
66
+ cursor: pointer;
67
+ transition:
68
+ border-color 0.15s ease,
69
+ background-color 0.15s ease;
70
+ }
71
+ .lx-check {
72
+ border-radius: 6px;
73
+ corner-shape: squircle;
74
+ }
75
+ .lx-radio {
76
+ border-radius: 50%;
77
+ }
78
+
79
+ // Soft primary circular ripple behind the glyph on hover (the Minimals halo).
80
+ .lx-check::after,
81
+ .lx-radio::after {
82
+ content: "";
83
+ position: absolute;
84
+ left: 50%;
85
+ top: 50%;
86
+ width: 36px;
87
+ height: 36px;
88
+ border-radius: 50%;
89
+ transform: translate(-50%, -50%) scale(0.5);
90
+ background: var(--color-primary-soft);
91
+ opacity: 0;
92
+ z-index: -1;
93
+ pointer-events: none;
94
+ transition:
95
+ opacity 0.15s ease,
96
+ transform 0.15s ease;
97
+ }
98
+ .lx-control:hover .lx-check::after,
99
+ .lx-control:hover .lx-radio::after,
100
+ .lx-check:hover::after,
101
+ .lx-radio:hover::after {
102
+ opacity: 1;
103
+ transform: translate(-50%, -50%) scale(1);
104
+ }
105
+
106
+ // Checked — primary fill (checkbox) / primary ring + dot (radio)
107
+ .lx-check:checked {
108
+ border-color: var(--color-primary);
109
+ background: var(--color-primary);
110
+ // white tick drawn with a rotated box-shadow-free pseudo
111
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
112
+ background-size: 15px 15px;
113
+ background-position: center;
114
+ background-repeat: no-repeat;
115
+ }
116
+ .lx-radio:checked {
117
+ border-color: var(--color-primary);
118
+ }
119
+ .lx-radio:checked::before {
120
+ content: "";
121
+ position: absolute;
122
+ left: 50%;
123
+ top: 50%;
124
+ width: 10px;
125
+ height: 10px;
126
+ border-radius: 50%;
127
+ background: var(--color-primary);
128
+ transform: translate(-50%, -50%);
129
+ }
130
+
131
+ // Keyboard focus ring (only on focus-visible, not mouse)
132
+ .lx-check:focus-visible,
133
+ .lx-radio:focus-visible {
134
+ outline: none;
135
+ box-shadow: 0 0 0 3px var(--color-primary-soft);
136
+ }
137
+
138
+ // Disabled
139
+ .lx-check:disabled,
140
+ .lx-radio:disabled,
141
+ .lx-control:has(:disabled) {
142
+ cursor: not-allowed;
143
+ }
144
+ .lx-check:disabled,
145
+ .lx-radio:disabled {
146
+ border-color: var(--color-border);
147
+ background-color: var(--color-bg-muted);
148
+ }
149
+ .lx-check:disabled:checked,
150
+ .lx-radio:disabled:checked {
151
+ border-color: var(--color-border-strong);
152
+ }
153
+ .lx-check:disabled:checked {
154
+ background-color: var(--color-border-strong);
155
+ }
156
+ .lx-radio:disabled:checked::before {
157
+ background: var(--color-border-strong);
158
+ }
159
+ .lx-control:has(:disabled) {
160
+ color: var(--color-text-subtle);
161
+ }
162
+ .lx-check:disabled::after,
163
+ .lx-radio:disabled::after {
164
+ display: none;
165
+ }
166
+
167
+ @media (prefers-reduced-motion: reduce) {
168
+ .lx-check,
169
+ .lx-radio,
170
+ .lx-check::after,
171
+ .lx-radio::after {
172
+ transition: none;
173
+ }
174
+ }
175
+
176
+ // Row-selection checkbox variant used inside a .table-ledger select column.
177
+ .lx-check {
178
+ width: 16px;
179
+ height: 16px;
180
+ accent-color: var(--color-primary);
181
+ cursor: pointer;
182
+ vertical-align: middle;
183
+ }
184
+
185
+ // ── Field / Input ──
186
+ // Inputs — always-white fill, 16px squircle, soft brand border tint on focus
187
+ // (no glow).
188
+ .input,
189
+ .select,
190
+ .textarea {
191
+ box-sizing: border-box;
192
+ width: 100%;
193
+ height: 45px;
194
+ padding: 12px;
195
+ border-radius: 8px;
196
+ border: 1px solid var(--color-border);
197
+ background: var(--color-surface);
198
+ font-size: 14px;
199
+ line-height: 1.5;
200
+ color: var(--color-text);
201
+ transition:
202
+ border-color 0.12s,
203
+ box-shadow 0.12s;
204
+ font-family: inherit;
205
+ }
206
+ .textarea {
207
+ height: auto;
208
+ min-height: 120px;
209
+ padding: 12px 15px 16px;
210
+ line-height: 1.5;
211
+ resize: vertical;
212
+ }
213
+ .input:hover:not(:focus):not(:disabled),
214
+ .select:hover:not(:focus):not(:disabled),
215
+ .textarea:hover:not(:focus):not(:disabled) {
216
+ background-color: var(--color-bg-muted);
217
+ border-color: var(--color-border-strong);
218
+ }
219
+ .input:focus,
220
+ .select:focus,
221
+ .textarea:focus {
222
+ outline: none;
223
+ background-color: var(--color-bg-muted);
224
+ color: var(--color-text);
225
+ border-color: rgba(47, 80, 228, 0.3);
226
+ box-shadow: none;
227
+ }
228
+ // Textarea renders as .form-control.textarea (react-bootstrap). Match the
229
+ // shared theme's .form-control:hover specificity so it hovers to
230
+ // --color-border-strong too.
231
+ textarea.textarea.form-control:hover:not(:focus):not(:disabled):not([readonly]) {
232
+ border-color: var(--color-border-strong);
233
+ }
234
+ textarea.textarea:focus {
235
+ outline: none;
236
+ border-color: rgba(47, 80, 228, 0.3);
237
+ box-shadow: none;
238
+ }
239
+ .input::placeholder,
240
+ .textarea::placeholder {
241
+ color: var(--color-text-subtle);
242
+ }
243
+ // Edge/IE add a native password reveal (and clear) button — hide them so only
244
+ // the app's own eye toggle shows.
245
+ .input::-ms-reveal,
246
+ .input::-ms-clear {
247
+ display: none;
248
+ }
249
+ .input:disabled,
250
+ .select:disabled,
251
+ .textarea:disabled {
252
+ background: var(--color-bg-muted);
253
+ border-color: var(--color-border);
254
+ color: var(--color-text-subtle);
255
+ cursor: not-allowed;
256
+ }
257
+ .field:has(.input:disabled) .label {
258
+ color: var(--color-text-subtle);
259
+ }
260
+
261
+ // Read-only inputs: darker muted fill to signal non-editable.
262
+ .input[readonly],
263
+ .select[readonly],
264
+ .textarea[readonly] {
265
+ background: var(--color-bg-muted);
266
+ color: var(--color-text-muted);
267
+ cursor: not-allowed;
268
+ }
269
+
270
+ // Invalid field state (required-field validation) — soft danger-tinted
271
+ // border on the surface.
272
+ .input.is-invalid,
273
+ .select.is-invalid,
274
+ .textarea.is-invalid,
275
+ .input.is-invalid:focus,
276
+ .select.is-invalid:focus,
277
+ .textarea.is-invalid:focus {
278
+ background: var(--color-surface);
279
+ border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
280
+ box-shadow: none;
281
+ }
282
+ .input.is-invalid {
283
+ padding-right: 41px;
284
+ }
285
+ .field-error {
286
+ font-size: 12px;
287
+ line-height: normal;
288
+ color: var(--color-danger);
289
+ }
290
+
291
+ // Native select with a rounded squircle, custom chevron, off-surface fill.
292
+ .select {
293
+ appearance: none;
294
+ -webkit-appearance: none;
295
+ padding-right: 40px;
296
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19.92 8.95l-6.52 6.52c-.77.77-2.03.77-2.8 0L4.08 8.95' fill='none' stroke='%234B5675' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10'/%3E%3C/svg%3E");
297
+ background-repeat: no-repeat;
298
+ background-position: right 14px center;
299
+ background-size: 16px;
300
+ cursor: pointer;
301
+ }
302
+
303
+ // Date inputs — a brand calendar glyph that fills the field so a click
304
+ // anywhere opens the native picker; muted text until a value is chosen.
305
+ .input[type="date"] {
306
+ position: relative;
307
+ cursor: pointer;
308
+ }
309
+ .input[type="date"]:not(.has-value):not(:focus) {
310
+ color: var(--color-text-subtle);
311
+ }
312
+ .input[type="date"]::-webkit-calendar-picker-indicator {
313
+ position: absolute;
314
+ inset: 0;
315
+ width: auto;
316
+ height: auto;
317
+ margin: 0;
318
+ padding: 0;
319
+ cursor: pointer;
320
+ opacity: 0;
321
+ }
322
+ .input[type="date"]::-webkit-datetime-edit {
323
+ padding-right: 28px;
324
+ }
325
+ .input-date-wrap {
326
+ position: relative;
327
+ }
328
+
329
+ // Number input: hide the native spinner so a custom stepper can be rendered.
330
+ .input[type="number"]::-webkit-inner-spin-button,
331
+ .input[type="number"]::-webkit-outer-spin-button {
332
+ -webkit-appearance: none;
333
+ margin: 0;
334
+ }
335
+ .input[type="number"] {
336
+ -moz-appearance: textfield;
337
+ appearance: textfield;
338
+ }
339
+
340
+ // Custom number stepper — up/down arrows, no background.
341
+ .num-stepper {
342
+ position: relative;
343
+ width: 100%;
344
+ }
345
+ .field > .num-stepper {
346
+ align-self: stretch;
347
+ }
348
+ .num-stepper .input {
349
+ width: 100%;
350
+ padding-right: 36px;
351
+ }
352
+ .num-stepper-btns {
353
+ position: absolute;
354
+ top: 50%;
355
+ right: 8px;
356
+ transform: translateY(-50%);
357
+ display: flex;
358
+ flex-direction: column;
359
+ gap: 0;
360
+ }
361
+ .num-stepper-btns button {
362
+ display: grid;
363
+ place-items: center;
364
+ width: 20px;
365
+ height: 15px;
366
+ border: none;
367
+ background: transparent;
368
+ padding: 0;
369
+ cursor: pointer;
370
+ color: var(--color-text-muted);
371
+ }
372
+ .num-stepper-btns button:hover {
373
+ color: var(--color-text);
374
+ }
375
+ .num-stepper-btns button:active {
376
+ color: var(--color-primary);
377
+ }
378
+ .input-date-wrap > .input {
379
+ padding-right: 38px;
380
+ }
381
+ .input-date-icon {
382
+ position: absolute;
383
+ right: 12px;
384
+ top: 50%;
385
+ transform: translateY(-50%);
386
+ color: var(--color-text-muted);
387
+ pointer-events: none;
388
+ }
389
+
390
+ // Error state — danger-tinted border + room for a trailing affix.
391
+ .input.error,
392
+ .input.error:focus,
393
+ .textarea.error,
394
+ .textarea.error:focus {
395
+ background: var(--color-surface);
396
+ border-color: color-mix(in srgb, var(--color-danger) 45%, var(--color-surface));
397
+ box-shadow: none;
398
+ }
399
+ .input.error {
400
+ padding-right: 41px;
401
+ }
402
+ // Dark mode: keep the value in an errored field legible.
403
+ :root[data-theme="dark"] .input.error,
404
+ :root[data-theme="dark"] .input.error:focus,
405
+ :root[data-theme="dark"] .input.error:-webkit-autofill,
406
+ :root[data-theme="dark"] .input.error:focus:-webkit-autofill {
407
+ color: var(--color-text);
408
+ -webkit-text-fill-color: var(--color-text);
409
+ }
410
+
411
+ // Field-relative wrap for trailing affixes (error icon, password toggle).
412
+ .input-wrap {
413
+ position: relative;
414
+ width: 100%;
415
+ align-self: stretch;
416
+ }
417
+ .input-wrap .input {
418
+ width: 100%;
419
+ }
420
+ .input-affix {
421
+ position: absolute;
422
+ right: 14px;
423
+ top: 22.5px;
424
+ transform: translateY(-50%);
425
+ display: grid;
426
+ place-items: center;
427
+ pointer-events: none;
428
+ color: var(--color-danger);
429
+ }
430
+
431
+ // Leading-icon input — icon turns brand on focus.
432
+ .input-icon {
433
+ position: relative;
434
+ display: flex;
435
+ align-items: center;
436
+ width: 100%;
437
+ }
438
+ .input-icon > .ic {
439
+ position: absolute;
440
+ left: 13px;
441
+ top: 50%;
442
+ transform: translateY(-50%);
443
+ display: grid;
444
+ place-items: center;
445
+ color: var(--color-text-subtle);
446
+ pointer-events: none;
447
+ }
448
+ .input-icon .input {
449
+ padding-left: 40px;
450
+ }
451
+ .input-icon:focus-within > .ic {
452
+ color: var(--color-primary);
453
+ }
454
+
455
+ // Password reveal toggle (place inside .input-wrap).
456
+ .input.pw {
457
+ padding-right: 44px;
458
+ }
459
+ .input.pw[type="password"]::placeholder {
460
+ letter-spacing: normal;
461
+ color: var(--color-text-subtle);
462
+ }
463
+ .pw-toggle {
464
+ position: absolute;
465
+ right: 7px;
466
+ top: 22px;
467
+ transform: translateY(-50%);
468
+ display: grid;
469
+ place-items: center;
470
+ width: 32px;
471
+ height: 32px;
472
+ border: none;
473
+ background: transparent;
474
+ color: var(--color-text-subtle);
475
+ border-radius: 8px;
476
+ cursor: pointer;
477
+ transition:
478
+ color 0.12s ease,
479
+ background 0.12s ease;
480
+ }
481
+ .pw-toggle:hover {
482
+ color: var(--color-primary);
483
+ background: transparent;
484
+ }
485
+
486
+ .hint,
487
+ .field-hint {
488
+ margin-top: 2px;
489
+ font-size: 12px;
490
+ color: var(--color-text-subtle);
491
+ }
492
+ .label {
493
+ display: block;
494
+ font-size: 14px;
495
+ line-height: normal;
496
+ font-weight: 500;
497
+ color: var(--color-text);
498
+ margin-bottom: 6px;
499
+ }
500
+ .label-hint {
501
+ color: var(--color-text-subtle);
502
+ font-weight: 400;
503
+ margin-left: 6px;
504
+ font-size: 12px;
505
+ }
506
+ .label-optional {
507
+ font-weight: 400;
508
+ font-size: 14px;
509
+ color: var(--color-text-subtle);
510
+ }
511
+ .required {
512
+ color: var(--color-danger);
513
+ }
514
+ .field {
515
+ display: flex;
516
+ flex-direction: column;
517
+ align-items: flex-start;
518
+ gap: 6px;
519
+ margin-bottom: 16px;
520
+ }
521
+ .field > .label,
522
+ .field label {
523
+ margin-bottom: 0;
524
+ }
525
+ .field-row {
526
+ display: grid;
527
+ grid-template-columns: 1fr 1fr;
528
+ gap: 16px;
529
+ margin-bottom: 16px;
530
+ }
531
+ .field-row-3 {
532
+ grid-template-columns: 1fr 1fr 1fr;
533
+ }
534
+ // min-width:0 — grid/flex items default to min-width:auto, which lets a long
535
+ // control (e.g. a search-select label) push its track past 1fr and defeat
536
+ // text truncation. Reset to 0 so the ellipsis engages instead of overflowing.
537
+ .field-row .field {
538
+ margin-bottom: 0;
539
+ min-width: 0;
540
+ }
541
+ // Controls fill their field even though .field aligns children flex-start.
542
+ .field > .search-select,
543
+ .field > .input-with-unit,
544
+ .field > .input,
545
+ .field > .datepicker,
546
+ .field > .field-error {
547
+ align-self: stretch;
548
+ }
549
+ @media (max-width: 575.98px) {
550
+ .field-row,
551
+ .field-row-3 {
552
+ grid-template-columns: 1fr;
553
+ }
554
+ }
555
+
556
+ // red border around a non-input group (e.g. a structure-form card row)
557
+ .group-invalid {
558
+ border: 1px solid var(--color-danger);
559
+ border-radius: 10px;
560
+ padding: 8px;
561
+ }
562
+
563
+ // One field, one border: the WRAPPER owns the border + hover/focus/error ring
564
+ // so the numeric input and its trailing unit pill light up together.
565
+ .input-with-unit {
566
+ display: flex;
567
+ align-items: stretch;
568
+ border: 1px solid var(--color-border);
569
+ border-radius: 16px;
570
+ corner-shape: squircle;
571
+ background: var(--color-surface);
572
+ transition:
573
+ border-color 0.12s,
574
+ background-color 0.12s;
575
+ overflow: hidden;
576
+ }
577
+ .input-with-unit:hover:not(:focus-within) {
578
+ background: var(--color-bg-muted);
579
+ border-color: var(--color-border-strong);
580
+ }
581
+ .input-with-unit:focus-within {
582
+ background: var(--color-bg-muted);
583
+ border-color: rgba(47, 80, 228, 0.3);
584
+ }
585
+ .input-with-unit:has(.num-input.error) {
586
+ border-color: var(--color-danger);
587
+ }
588
+
589
+ // Numeric inputs are plain text fields — no native spinner, no stepper. The
590
+ // field flows seamlessly into the trailing unit pill; the wrapper carries the
591
+ // border.
592
+ .input-with-unit .num-input {
593
+ border: none;
594
+ border-radius: 0;
595
+ background: transparent;
596
+ }
597
+ .input-with-unit .num-input:focus {
598
+ box-shadow: none;
599
+ }
600
+ .input-with-unit .unit {
601
+ background: var(--color-bg-muted);
602
+ border: none;
603
+ border-left: 1px solid var(--color-border);
604
+ padding: 9px 14px;
605
+ font-size: 14px;
606
+ font-weight: 500;
607
+ color: var(--color-text-muted);
608
+ font-family: "Inter", sans-serif;
609
+ display: inline-flex;
610
+ align-items: center;
611
+ white-space: nowrap;
612
+ }
613
+
614
+ // ── Status badge ──
615
+ .badge {
616
+ display: inline-flex;
617
+ align-items: center;
618
+ gap: 4px;
619
+ padding: 3px 9px;
620
+ border-radius: 999px;
621
+ font-size: 12px;
622
+ font-weight: 500;
623
+ background: var(--color-bg-muted);
624
+ color: var(--color-text-muted);
625
+ border: 1px solid var(--color-border);
626
+ }
627
+ .badge-primary {
628
+ background: var(--color-primary-soft);
629
+ color: var(--color-primary);
630
+ border-color: color-mix(in srgb, var(--color-primary) 25%, transparent);
631
+ }
632
+ .badge-success {
633
+ background: var(--color-success-soft);
634
+ color: var(--color-success-strong);
635
+ border-color: transparent;
636
+ }
637
+ .badge-warn {
638
+ background: var(--color-warning-soft);
639
+ color: var(--color-warning-strong);
640
+ border-color: transparent;
641
+ }
642
+ .badge-danger {
643
+ background: var(--color-danger-soft);
644
+ color: var(--color-danger-strong);
645
+ border-color: transparent;
646
+ }
647
+ .badge-info {
648
+ background: var(--color-info-soft);
649
+ color: var(--color-info-strong);
650
+ border-color: transparent;
651
+ }
652
+ .badge-dot::before {
653
+ content: "";
654
+ width: 6px;
655
+ height: 6px;
656
+ border-radius: 999px;
657
+ background: currentColor;
658
+ }
659
+
660
+ // Keystone "soft" status badge — pill, 12/600, muted tone fill.
661
+ .status-label {
662
+ display: inline-flex;
663
+ align-items: center;
664
+ gap: 5px;
665
+ padding: 0 10px;
666
+ height: 24px;
667
+ border-radius: 999px;
668
+ font-size: 12px;
669
+ font-weight: 600;
670
+ line-height: 1;
671
+ white-space: nowrap;
672
+ }
673
+ .status-label.tone-green {
674
+ background: rgba(34, 197, 94, 0.16);
675
+ color: var(--color-success-strong);
676
+ }
677
+ .status-label.tone-amber {
678
+ background: rgba(255, 171, 0, 0.16);
679
+ color: var(--color-warning-strong);
680
+ }
681
+ .status-label.tone-red {
682
+ background: rgba(255, 86, 48, 0.16);
683
+ color: var(--color-danger-strong);
684
+ }
685
+ .status-label.tone-blue {
686
+ background: rgba(0, 184, 217, 0.16);
687
+ color: var(--color-info-strong);
688
+ }
689
+ .status-label.tone-grey {
690
+ background: rgba(145, 158, 171, 0.16);
691
+ color: var(--color-text-subtle);
692
+ }
693
+
694
+ // ── Choice chip ──
695
+ // Inline selectable chip — native control hidden, pill adopts the primary
696
+ // accent when checked.
697
+ .choice-chip {
698
+ display: inline-flex;
699
+ align-items: center;
700
+ gap: 8px;
701
+ padding: 9px 14px;
702
+ border: 1.5px solid var(--color-border);
703
+ border-radius: 999px;
704
+ font-size: 14px;
705
+ font-weight: 500;
706
+ color: var(--color-text);
707
+ background: var(--color-surface);
708
+ cursor: pointer;
709
+ user-select: none;
710
+ transition:
711
+ border-color 0.12s ease,
712
+ background 0.12s ease,
713
+ color 0.12s ease;
714
+ }
715
+ .choice-chip:hover {
716
+ border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
717
+ }
718
+ .choice-chip input {
719
+ position: absolute;
720
+ opacity: 0;
721
+ pointer-events: none;
722
+ }
723
+ .choice-chip.checked {
724
+ border-color: var(--color-primary);
725
+ background: var(--color-primary-soft);
726
+ color: var(--color-primary);
727
+ }
728
+
729
+ // ── Structure card (struct-card*) ──
730
+ // Single-select radio-card control (e.g. "type of structure" picker).
731
+ .struct-card {
732
+ position: relative;
733
+ display: flex;
734
+ align-items: center;
735
+ gap: 11px;
736
+ padding: 20px;
737
+ border: 1.5px solid var(--color-border);
738
+ border-radius: 10px;
739
+ background: var(--color-surface);
740
+ cursor: pointer;
741
+ transition:
742
+ border-color 0.12s ease,
743
+ background 0.12s ease;
744
+ }
745
+ .struct-card:hover {
746
+ border-color: rgba(47, 80, 228, 0.3);
747
+ }
748
+ .struct-card.checked {
749
+ border-color: rgba(47, 80, 228, 0.3);
750
+ background: color-mix(in srgb, var(--color-primary) 5%, transparent);
751
+ }
752
+ .struct-card.checked .struct-card-title {
753
+ color: var(--color-primary);
754
+ }
755
+ .struct-card-input {
756
+ position: absolute;
757
+ opacity: 0;
758
+ pointer-events: none;
759
+ }
760
+ .struct-card-ic {
761
+ width: 44px;
762
+ height: 44px;
763
+ border-radius: 99px;
764
+ flex-shrink: 0;
765
+ display: inline-flex;
766
+ align-items: center;
767
+ justify-content: center;
768
+ background: var(--color-bg-muted);
769
+ color: var(--color-text-subtle);
770
+ transition:
771
+ background 0.12s,
772
+ color 0.12s;
773
+ }
774
+ .struct-card.checked .struct-card-ic {
775
+ background: var(--color-surface);
776
+ color: var(--color-primary);
777
+ }
778
+ // Icon illustrations are coloured PNGs: greyscale them until the card is
779
+ // selected, then show the full-colour (blue) art.
780
+ .struct-card-ic img {
781
+ filter: grayscale(1);
782
+ opacity: 0.6;
783
+ transition:
784
+ filter 0.12s,
785
+ opacity 0.12s;
786
+ }
787
+ .struct-card.checked .struct-card-ic img {
788
+ filter: none;
789
+ opacity: 1;
790
+ }
791
+ .struct-card-body {
792
+ display: flex;
793
+ flex-direction: column;
794
+ min-width: 0;
795
+ }
796
+ .struct-card-title {
797
+ font-weight: 600;
798
+ font-size: 16px;
799
+ color: var(--color-text);
800
+ }
801
+ .struct-card-desc {
802
+ font-size: 14px;
803
+ font-weight: 400;
804
+ color: var(--color-text-muted);
805
+ line-height: 1.35;
806
+ margin-top: 2px;
807
+ }
808
+ .struct-card-check {
809
+ position: absolute;
810
+ top: 10px;
811
+ right: 10px;
812
+ display: inline-flex;
813
+ align-items: center;
814
+ justify-content: center;
815
+ background: transparent;
816
+ color: var(--color-primary);
817
+ opacity: 0;
818
+ transform: scale(0.5);
819
+ transition:
820
+ opacity 0.12s ease,
821
+ transform 0.12s ease;
822
+ }
823
+ .struct-card.checked .struct-card-check {
824
+ opacity: 1;
825
+ transform: scale(1);
826
+ }
827
+
828
+ // Tile variant: big centered illustration + label. Opt in with
829
+ // className="struct-card-tile".
830
+ .struct-card-tile {
831
+ flex-direction: column;
832
+ align-items: center;
833
+ text-align: center;
834
+ gap: 16px;
835
+ padding: 26px 18px 20px;
836
+ }
837
+ // Drop the icon disc — the illustration stands on its own, sized large.
838
+ .struct-card-tile .struct-card-ic,
839
+ .struct-card-tile.checked .struct-card-ic {
840
+ width: auto;
841
+ height: auto;
842
+ border-radius: 0;
843
+ background: transparent;
844
+ }
845
+ // Full-colour at all times (override the greyscale-until-selected treatment).
846
+ .struct-card-tile .struct-card-ic img,
847
+ .struct-card-tile.checked .struct-card-ic img {
848
+ width: 100px;
849
+ height: 100px;
850
+ filter: none;
851
+ opacity: 1;
852
+ }
853
+ .struct-card-tile .struct-card-body {
854
+ align-items: center;
855
+ }
856
+ .struct-card-tile .struct-card-title {
857
+ font-size: 17px;
858
+ }
859
+ .struct-card-tile .struct-card-desc {
860
+ margin-top: 4px;
861
+ }
862
+ .struct-card-tile .struct-card-check {
863
+ display: none;
864
+ }
865
+
866
+ // ── Search select ──
867
+ .search-select {
868
+ position: relative;
869
+ min-width: 0;
870
+ }
871
+ .search-select-control {
872
+ display: flex;
873
+ align-items: center;
874
+ justify-content: space-between;
875
+ gap: 8px;
876
+ text-align: left;
877
+ cursor: pointer;
878
+ width: 100%;
879
+ min-width: 0;
880
+ }
881
+ .search-select-control > span {
882
+ min-width: 0;
883
+ overflow: hidden;
884
+ text-overflow: ellipsis;
885
+ white-space: nowrap;
886
+ }
887
+ .search-select-control:disabled {
888
+ cursor: not-allowed;
889
+ opacity: 0.6;
890
+ }
891
+ .search-select-control:hover:not(:disabled) {
892
+ border-color: var(--color-border-strong);
893
+ }
894
+ .search-select-control.open {
895
+ background: var(--color-bg-muted);
896
+ border-color: rgba(47, 80, 228, 0.3);
897
+ }
898
+ .search-select-caret {
899
+ flex-shrink: 0;
900
+ color: var(--color-text-muted);
901
+ transition: transform 0.15s ease;
902
+ }
903
+ .search-select-caret path {
904
+ stroke-width: 2px;
905
+ }
906
+ .search-select-control.open .search-select-caret {
907
+ transform: rotate(180deg);
908
+ }
909
+ .search-select-placeholder {
910
+ color: var(--color-text-subtle);
911
+ }
912
+ // Menu matches the themed-select dropdown: 16px squircle, 4px padding, soft
913
+ // shadow.
914
+ .search-select-menu {
915
+ position: absolute;
916
+ z-index: 40;
917
+ top: calc(100% + 6px);
918
+ left: 0;
919
+ right: 0;
920
+ background: var(--color-surface);
921
+ border: 1px solid var(--color-border);
922
+ border-radius: 16px;
923
+ corner-shape: squircle;
924
+ box-shadow: 0 12px 32px rgba(7, 20, 55, 0.12);
925
+ padding: 4px;
926
+ overflow: hidden;
927
+ }
928
+ // Portalled to <body>: fixed-positioned (top/left set inline from the
929
+ // trigger rect) so the menu overlays instead of being clipped by a
930
+ // scrollable modal / table card. Sits above the modal layer.
931
+ .search-select-menu-fixed {
932
+ position: fixed;
933
+ z-index: 1600;
934
+ right: auto;
935
+ top: auto;
936
+ left: auto;
937
+ }
938
+ // Search box matches the search bar: rounded field, leading icon turns
939
+ // primary.
940
+ .search-select-search {
941
+ display: flex;
942
+ align-items: center;
943
+ gap: 8px;
944
+ margin: 4px;
945
+ padding: 0 12px;
946
+ height: 40px;
947
+ border: 1px solid var(--color-border);
948
+ border-radius: 14px;
949
+ corner-shape: squircle;
950
+ background: var(--color-surface);
951
+ color: var(--color-text-subtle);
952
+ }
953
+ .search-select-search {
954
+ position: relative;
955
+ }
956
+ .search-select-search:focus-within {
957
+ background: var(--color-bg-muted);
958
+ }
959
+ .search-select-search:focus-within svg {
960
+ color: var(--color-primary);
961
+ }
962
+ .search-select-clear {
963
+ display: grid;
964
+ place-items: center;
965
+ width: 24px;
966
+ height: 24px;
967
+ border: none;
968
+ background: transparent;
969
+ padding: 0;
970
+ cursor: pointer;
971
+ color: var(--color-text-subtle);
972
+ }
973
+ .search-select-clear:hover {
974
+ color: var(--color-text);
975
+ }
976
+ .search-select-input {
977
+ flex: 1;
978
+ border: none;
979
+ outline: none;
980
+ font-size: 14px;
981
+ font-family: inherit;
982
+ color: var(--color-text);
983
+ background: transparent;
984
+ }
985
+ .search-select-options {
986
+ max-height: 300px;
987
+ overflow-y: auto;
988
+ padding: 4px 4px 4px;
989
+ display: flex;
990
+ flex-direction: column;
991
+ gap: 4px;
992
+ scrollbar-gutter: stable;
993
+ }
994
+ .search-select-option {
995
+ display: flex;
996
+ align-items: center;
997
+ justify-content: space-between;
998
+ gap: 8px;
999
+ width: 100%;
1000
+ text-align: left;
1001
+ padding: 9px 14px;
1002
+ line-height: 1.4;
1003
+ border: none;
1004
+ border-radius: 12px;
1005
+ corner-shape: squircle;
1006
+ background: transparent;
1007
+ font-size: 14px;
1008
+ color: var(--color-text);
1009
+ cursor: pointer;
1010
+ transition:
1011
+ background 0.12s ease,
1012
+ color 0.12s ease;
1013
+ }
1014
+ .search-select-option:hover,
1015
+ .search-select-option.is-active {
1016
+ background: var(--color-primary-soft);
1017
+ color: var(--color-primary);
1018
+ }
1019
+ .search-select-option.selected {
1020
+ background: var(--color-primary-soft);
1021
+ color: var(--color-primary);
1022
+ font-weight: 500;
1023
+ }
1024
+ .search-select-empty {
1025
+ padding: 12px 10px;
1026
+ font-size: 13px;
1027
+ color: var(--color-text-subtle);
1028
+ text-align: center;
1029
+ }
1030
+
1031
+ // ── Date picker ──
1032
+ // Custom DatePicker — themed popover calendar (the native popup can't be
1033
+ // styled).
1034
+ .datepicker {
1035
+ position: relative;
1036
+ }
1037
+ .datepicker-trigger {
1038
+ text-align: left;
1039
+ cursor: pointer;
1040
+ }
1041
+ .datepicker-trigger:not(.has-value) {
1042
+ color: var(--color-text-subtle);
1043
+ }
1044
+ .datepicker.open .datepicker-trigger {
1045
+ background-color: var(--color-bg-muted);
1046
+ border-color: rgba(47, 80, 228, 0.3);
1047
+ box-shadow: none;
1048
+ }
1049
+ // Portalled to <body> and fixed-positioned from JS so a modal's
1050
+ // overflow:hidden can't clip it; top/left come from inline styles. z-index
1051
+ // sits above the modal layer.
1052
+ .datepicker-pop {
1053
+ position: fixed;
1054
+ z-index: 1600;
1055
+ width: 312px;
1056
+ max-width: calc(100vw - 16px);
1057
+ padding: 18px;
1058
+ background: var(--color-surface);
1059
+ border: 1px solid var(--color-border);
1060
+ border-radius: 18px;
1061
+ corner-shape: squircle;
1062
+ animation: datepicker-in 0.14s ease;
1063
+ }
1064
+ .datepicker-pop.drop-up {
1065
+ animation: datepicker-in-up 0.14s ease;
1066
+ }
1067
+ @keyframes datepicker-in {
1068
+ from {
1069
+ opacity: 0;
1070
+ transform: translateY(-4px);
1071
+ }
1072
+ to {
1073
+ opacity: 1;
1074
+ transform: translateY(0);
1075
+ }
1076
+ }
1077
+ @keyframes datepicker-in-up {
1078
+ from {
1079
+ opacity: 0;
1080
+ transform: translateY(4px);
1081
+ }
1082
+ to {
1083
+ opacity: 1;
1084
+ transform: translateY(0);
1085
+ }
1086
+ }
1087
+ .datepicker-head {
1088
+ display: flex;
1089
+ align-items: center;
1090
+ justify-content: space-between;
1091
+ margin-bottom: 14px;
1092
+ }
1093
+ .datepicker-month {
1094
+ font-size: 15px;
1095
+ font-weight: 600;
1096
+ color: var(--color-text);
1097
+ letter-spacing: -0.01em;
1098
+ }
1099
+ .datepicker-nav {
1100
+ display: inline-flex;
1101
+ gap: 6px;
1102
+ }
1103
+ .datepicker-nav-btn {
1104
+ display: inline-flex;
1105
+ align-items: center;
1106
+ justify-content: center;
1107
+ width: 30px;
1108
+ height: 30px;
1109
+ border: 1px solid var(--color-border);
1110
+ border-radius: 999px;
1111
+ background: transparent;
1112
+ color: var(--color-text-muted);
1113
+ cursor: pointer;
1114
+ transition:
1115
+ background 0.12s,
1116
+ color 0.12s,
1117
+ border-color 0.12s;
1118
+ }
1119
+ .datepicker-nav-btn:hover {
1120
+ background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
1121
+ border-color: var(--color-primary);
1122
+ color: var(--color-primary);
1123
+ }
1124
+ .datepicker-weekdays {
1125
+ display: grid;
1126
+ grid-template-columns: repeat(7, 1fr);
1127
+ gap: 4px;
1128
+ margin-bottom: 6px;
1129
+ }
1130
+ .datepicker-grid {
1131
+ display: flex;
1132
+ flex-direction: column;
1133
+ gap: 4px;
1134
+ }
1135
+ .datepicker-row {
1136
+ display: grid;
1137
+ grid-template-columns: repeat(7, 1fr);
1138
+ gap: 4px;
1139
+ }
1140
+ .datepicker-weekdays span {
1141
+ text-align: center;
1142
+ font-size: 11px;
1143
+ font-weight: 600;
1144
+ text-transform: uppercase;
1145
+ letter-spacing: 0.04em;
1146
+ color: var(--color-text-subtle);
1147
+ padding: 4px 0;
1148
+ }
1149
+ .datepicker-day {
1150
+ display: inline-flex;
1151
+ align-items: center;
1152
+ justify-content: center;
1153
+ aspect-ratio: 1;
1154
+ width: 100%;
1155
+ border: none;
1156
+ border-radius: 50%;
1157
+ background: transparent;
1158
+ font-size: 13px;
1159
+ font-weight: 500;
1160
+ color: var(--color-text);
1161
+ cursor: pointer;
1162
+ transition:
1163
+ background 0.12s,
1164
+ color 0.12s,
1165
+ box-shadow 0.12s;
1166
+ }
1167
+ .datepicker-day:hover:not(:disabled):not(.is-selected) {
1168
+ background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
1169
+ color: var(--color-primary);
1170
+ }
1171
+ .datepicker-day.is-outside {
1172
+ color: var(--color-text-subtle);
1173
+ font-weight: 400;
1174
+ }
1175
+ .datepicker-day.is-today {
1176
+ font-weight: 700;
1177
+ color: var(--color-primary);
1178
+ box-shadow: inset 0 0 0 1.5px var(--color-primary);
1179
+ }
1180
+ .datepicker-day.is-selected {
1181
+ background: var(--color-primary);
1182
+ color: #fff;
1183
+ font-weight: 600;
1184
+ }
1185
+ .datepicker-day:disabled {
1186
+ color: var(--color-border-strong);
1187
+ cursor: not-allowed;
1188
+ }
1189
+ .datepicker-foot {
1190
+ display: flex;
1191
+ align-items: center;
1192
+ justify-content: space-between;
1193
+ margin-top: 14px;
1194
+ padding-top: 14px;
1195
+ border-top: 1px solid var(--color-border);
1196
+ }
1197
+ .datepicker-link {
1198
+ border: none;
1199
+ background: transparent;
1200
+ padding: 6px 12px;
1201
+ border-radius: 8px;
1202
+ font-size: 13px;
1203
+ font-weight: 600;
1204
+ color: var(--color-primary);
1205
+ cursor: pointer;
1206
+ transition: background 0.12s;
1207
+ }
1208
+ .datepicker-link:hover:not(:disabled) {
1209
+ background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
1210
+ }
1211
+ .datepicker-link:disabled {
1212
+ color: var(--color-text-subtle);
1213
+ cursor: not-allowed;
1214
+ }
1215
+
1216
+ // ── Phone input ──
1217
+ .app-phone-number-input {
1218
+ width: 100%;
1219
+ }
1220
+ .app-phone-number-input .phone-input-wrap {
1221
+ position: relative;
1222
+ width: 100%;
1223
+ }
1224
+ .app-phone-number-input .phone-input-wrap > .input {
1225
+ width: 100%;
1226
+ }
1227
+ .app-phone-number-input .phone-country-adornment {
1228
+ position: absolute;
1229
+ left: 0;
1230
+ top: 0;
1231
+ height: 100%;
1232
+ display: flex;
1233
+ align-items: center;
1234
+ padding-left: 12px;
1235
+ z-index: 5;
1236
+ }
1237
+ .country-dropdown .country-toggle {
1238
+ display: flex;
1239
+ align-items: center;
1240
+ gap: 6px;
1241
+ padding: 0;
1242
+ border: 0;
1243
+ background: transparent;
1244
+ box-shadow: none;
1245
+ color: var(--color-text);
1246
+ }
1247
+ .country-dropdown .country-toggle::after {
1248
+ display: none;
1249
+ }
1250
+ .country-dropdown .country-toggle:hover,
1251
+ .country-dropdown .country-toggle:focus,
1252
+ .country-dropdown .country-toggle:active {
1253
+ background: transparent !important;
1254
+ box-shadow: none !important;
1255
+ color: var(--color-text) !important;
1256
+ }
1257
+ .app-phone-number-input .phone-flag {
1258
+ width: 22px;
1259
+ height: 15px;
1260
+ border-radius: 2px;
1261
+ object-fit: cover;
1262
+ box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
1263
+ }
1264
+ .app-phone-number-input .phone-chevron {
1265
+ color: var(--color-text-muted);
1266
+ transition: transform 0.2s ease;
1267
+ }
1268
+ .country-dropdown.show .phone-chevron {
1269
+ transform: rotate(180deg);
1270
+ }
1271
+ .app-phone-number-input .phone-dial {
1272
+ font-size: 13px;
1273
+ font-weight: 500;
1274
+ color: var(--color-text);
1275
+ }
1276
+
1277
+ .country-dropdown-menu {
1278
+ width: 320px;
1279
+ padding: 8px 0;
1280
+ border: 1px solid var(--color-border);
1281
+ border-radius: 12px;
1282
+ background: var(--color-surface);
1283
+ box-shadow: 0 10px 24px -4px rgba(15, 42, 81, 0.14);
1284
+ overflow: hidden;
1285
+ }
1286
+ .dropdown-search-wrapper {
1287
+ position: relative;
1288
+ padding: 4px 12px 8px;
1289
+ }
1290
+ .dropdown-search-icon {
1291
+ position: absolute;
1292
+ left: 24px;
1293
+ top: calc(50% - 3px);
1294
+ transform: translateY(-50%);
1295
+ display: flex;
1296
+ color: var(--color-text-muted);
1297
+ pointer-events: none;
1298
+ }
1299
+ .dropdown-search-input {
1300
+ width: 100%;
1301
+ height: 38px;
1302
+ padding: 0 12px 0 36px;
1303
+ border: 1px solid var(--color-border);
1304
+ border-radius: 999px;
1305
+ font-size: 14px;
1306
+ color: var(--color-text);
1307
+ background: var(--color-surface);
1308
+ outline: none;
1309
+ }
1310
+ .dropdown-search-input:hover:not(:focus) {
1311
+ background: var(--color-bg-muted);
1312
+ border-color: var(--color-border-strong);
1313
+ }
1314
+ .dropdown-search-input:focus {
1315
+ background: var(--color-bg-muted);
1316
+ border-color: var(--color-primary);
1317
+ box-shadow: 0 0 0 3px var(--color-primary-soft);
1318
+ }
1319
+ .country-list-scroll {
1320
+ max-height: 250px;
1321
+ overflow-y: auto;
1322
+ overflow-x: hidden;
1323
+ scrollbar-width: thin;
1324
+ }
1325
+ .country-list-scroll::-webkit-scrollbar {
1326
+ width: 6px;
1327
+ }
1328
+ .country-list-scroll::-webkit-scrollbar-thumb {
1329
+ background: var(--color-border);
1330
+ border-radius: 3px;
1331
+ }
1332
+ .country-dropdown-item {
1333
+ display: flex !important;
1334
+ align-items: center;
1335
+ gap: 10px;
1336
+ padding: 9px 16px !important;
1337
+ cursor: pointer;
1338
+ color: var(--color-text) !important;
1339
+ background: transparent !important;
1340
+ border: 0 !important;
1341
+ }
1342
+ .country-dropdown-item:hover,
1343
+ .country-dropdown-item:focus {
1344
+ background: var(--color-bg-subtle) !important;
1345
+ }
1346
+ .country-dropdown-item.selected {
1347
+ background: var(--color-primary-soft) !important;
1348
+ }
1349
+ .country-dropdown-menu .phone-flag-lg {
1350
+ width: 22px;
1351
+ height: 15px;
1352
+ border-radius: 2px;
1353
+ object-fit: cover;
1354
+ box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
1355
+ flex-shrink: 0;
1356
+ }
1357
+ .country-name-text {
1358
+ font-size: 14px;
1359
+ font-weight: 500;
1360
+ color: var(--color-text);
1361
+ }
1362
+ .country-code-text {
1363
+ margin-left: 6px;
1364
+ font-weight: 400;
1365
+ color: var(--color-text-muted);
1366
+ }
1367
+ .selected-check-icon {
1368
+ margin-left: auto;
1369
+ display: flex;
1370
+ color: var(--color-primary);
1371
+ }
1372
+ .country-empty {
1373
+ padding: 10px 16px;
1374
+ text-align: center;
1375
+ color: var(--color-text-subtle);
1376
+ font-size: 14px;
1377
+ }
1378
+
1379
+ // ── Avatar ──
1380
+ // A photo fills the avatar disc edge to edge; the circle is the frame, so
1381
+ // the image is cropped to it rather than letterboxed.
1382
+ .avatar > .avatar-img {
1383
+ width: 100%;
1384
+ height: 100%;
1385
+ border-radius: inherit;
1386
+ object-fit: cover;
1387
+ display: block;
1388
+ }
1389
+ .avatar {
1390
+ width: 32px;
1391
+ height: 32px;
1392
+ border-radius: 999px;
1393
+ background: color-mix(in srgb, var(--color-primary) 15%, var(--color-surface));
1394
+ color: var(--color-primary);
1395
+ font-size: 12px;
1396
+ font-weight: 600;
1397
+ display: inline-flex;
1398
+ align-items: center;
1399
+ justify-content: center;
1400
+ }
1401
+ .avatar-lg {
1402
+ width: 48px;
1403
+ height: 48px;
1404
+ font-size: 16px;
1405
+ }
1406
+ .avatar-xl {
1407
+ width: 64px;
1408
+ height: 64px;
1409
+ font-size: 22px;
1410
+ }
1411
+ .avatar-sm {
1412
+ width: 24px;
1413
+ height: 24px;
1414
+ font-size: 10px;
1415
+ }
1416
+
1417
+ // ── File attach ──
1418
+ // Support-form attachment strip: drop zone → attached-image preview.
1419
+ .attach-drop {
1420
+ display: flex;
1421
+ width: 100%;
1422
+ align-items: center;
1423
+ gap: 12px;
1424
+ padding: 12px 14px;
1425
+ border: 1px dashed var(--color-border);
1426
+ border-radius: 12px;
1427
+ background: var(--color-surface);
1428
+ cursor: pointer;
1429
+ transition:
1430
+ border-color 0.3s cubic-bezier(0.22, 1, 0.36, 1),
1431
+ background 0.3s cubic-bezier(0.22, 1, 0.36, 1);
1432
+ }
1433
+ .attach-drop:hover {
1434
+ border-color: var(--color-primary);
1435
+ background: var(--color-primary-soft);
1436
+ }
1437
+ .attach-drop-icon {
1438
+ flex: 0 0 auto;
1439
+ width: 36px;
1440
+ height: 36px;
1441
+ display: inline-flex;
1442
+ align-items: center;
1443
+ justify-content: center;
1444
+ border-radius: 999px;
1445
+ background: var(--color-primary-soft);
1446
+ color: var(--color-primary);
1447
+ }
1448
+ .attach-drop:hover .attach-drop-icon {
1449
+ background: var(--color-surface);
1450
+ }
1451
+ .attach-drop-copy {
1452
+ display: flex;
1453
+ flex-direction: column;
1454
+ gap: 2px;
1455
+ flex: 1;
1456
+ min-width: 0;
1457
+ }
1458
+ .attach-drop-title {
1459
+ font-weight: 600;
1460
+ font-size: 14px;
1461
+ line-height: 1.4;
1462
+ color: var(--color-text);
1463
+ }
1464
+ .attach-drop-hint {
1465
+ font-size: 12px;
1466
+ color: var(--color-text-subtle);
1467
+ }
1468
+ .attach-drop-cta {
1469
+ flex: 0 0 auto;
1470
+ font-size: 12px;
1471
+ font-weight: 600;
1472
+ color: var(--color-primary);
1473
+ border: 1px solid var(--color-border);
1474
+ border-radius: 999px;
1475
+ padding: 5px 14px;
1476
+ background: var(--color-surface);
1477
+ }
1478
+ // Attached image renders full width so the user can actually check what
1479
+ // they're sending.
1480
+ .attach-preview {
1481
+ width: 100%;
1482
+ border: 1px solid var(--color-border);
1483
+ border-radius: 12px;
1484
+ background: var(--color-surface);
1485
+ overflow: hidden;
1486
+ }
1487
+ .attach-figure {
1488
+ position: relative;
1489
+ // A lighter elevated surface behind the (contained) image so the
1490
+ // letterbox reads as a surface, not near-black — dark mode uses the
1491
+ // muted/elevated step instead.
1492
+ background: var(--color-bg-muted);
1493
+ }
1494
+ .attach-figure img {
1495
+ display: block;
1496
+ width: 100%;
1497
+ max-height: 260px;
1498
+ object-fit: contain;
1499
+ }
1500
+ .attach-meta {
1501
+ display: flex;
1502
+ align-items: baseline;
1503
+ justify-content: space-between;
1504
+ gap: 12px;
1505
+ padding: 10px 14px;
1506
+ border-top: 1px solid var(--color-border);
1507
+ min-width: 0;
1508
+ }
1509
+ .attach-name {
1510
+ font-size: 13px;
1511
+ font-weight: 500;
1512
+ color: var(--color-text);
1513
+ overflow: hidden;
1514
+ text-overflow: ellipsis;
1515
+ white-space: nowrap;
1516
+ }
1517
+ .attach-size {
1518
+ font-size: 12px;
1519
+ color: var(--color-text-subtle);
1520
+ flex: 0 0 auto;
1521
+ }
1522
+ .attach-remove {
1523
+ position: absolute;
1524
+ top: 8px;
1525
+ right: 8px;
1526
+ display: inline-flex;
1527
+ align-items: center;
1528
+ justify-content: center;
1529
+ width: 28px;
1530
+ height: 28px;
1531
+ border: 0;
1532
+ background: rgba(0, 0, 0, 0.62);
1533
+ backdrop-filter: blur(10px);
1534
+ -webkit-backdrop-filter: blur(10px);
1535
+ color: #fff;
1536
+ border-radius: 999px;
1537
+ cursor: pointer;
1538
+ transition: background 0.2s;
1539
+ }
1540
+ .attach-remove:hover {
1541
+ background: var(--color-danger);
1542
+ }
1543
+ // Iconsax hardcodes both stroke colour and width on its paths, so force
1544
+ // white here — otherwise the glyph vanishes into the button in dark mode.
1545
+ .attach-remove svg {
1546
+ width: 18px;
1547
+ height: 18px;
1548
+ display: block;
1549
+ }
1550
+ .attach-remove svg path {
1551
+ stroke: #fff;
1552
+ stroke-width: 2px;
1553
+ }
1554
+ @media (prefers-reduced-motion: reduce) {
1555
+ .attach-drop,
1556
+ .attach-remove {
1557
+ transition: none;
1558
+ }
1559
+ }
1560
+
1561
+ // ── File picker ──
1562
+ // File input — segmented "Upload" button + placeholder, matches the .input
1563
+ // shell.
1564
+ .file-input {
1565
+ box-sizing: border-box;
1566
+ display: flex;
1567
+ align-items: stretch;
1568
+ width: 100%;
1569
+ border-radius: 16px;
1570
+ corner-shape: squircle;
1571
+ border: 1px solid var(--color-border);
1572
+ background: var(--color-surface);
1573
+ overflow: hidden;
1574
+ cursor: pointer;
1575
+ transition:
1576
+ border-color 0.12s,
1577
+ box-shadow 0.12s;
1578
+ }
1579
+ .file-input:hover {
1580
+ border-color: var(--color-border-strong);
1581
+ }
1582
+ .file-input-btn {
1583
+ display: inline-flex;
1584
+ align-items: center;
1585
+ flex-shrink: 0;
1586
+ padding: 12px 20px;
1587
+ font-size: 14px;
1588
+ font-weight: 600;
1589
+ color: var(--color-text);
1590
+ background: var(--color-bg-muted);
1591
+ border-right: 1px solid var(--color-border);
1592
+ }
1593
+ .file-input-text {
1594
+ display: inline-flex;
1595
+ align-items: center;
1596
+ min-width: 0;
1597
+ padding: 12px 16px;
1598
+ font-size: 14px;
1599
+ color: var(--color-text-subtle);
1600
+ white-space: nowrap;
1601
+ overflow: hidden;
1602
+ text-overflow: ellipsis;
1603
+ }
1604
+ .file-input-text.has-file {
1605
+ color: var(--color-text);
1606
+ }
1607
+
1608
+ // Tall empty state (AppFilePicker): a drag-and-drop dropzone — dashed
1609
+ // border, circular icon badge, click-or-drop copy. Highlights on hover and
1610
+ // while a file is dragged over.
1611
+ .file-input.file-input-tall {
1612
+ position: relative;
1613
+ flex-direction: column;
1614
+ align-items: center;
1615
+ justify-content: center;
1616
+ gap: 10px;
1617
+ height: 350px;
1618
+ padding: 20px;
1619
+ text-align: center;
1620
+ background: var(--color-bg-subtle);
1621
+ border: 1px dashed var(--color-border-strong);
1622
+ transition:
1623
+ border-color 0.15s ease,
1624
+ background-color 0.15s ease;
1625
+ }
1626
+ .file-input.file-input-tall:hover,
1627
+ .file-input.file-input-tall.dragging {
1628
+ border-color: var(--color-primary);
1629
+ background: var(--color-bg-muted);
1630
+ }
1631
+ // Fill-label associates click with the hidden file input; children don't
1632
+ // steal drag hits.
1633
+ .file-input.file-input-tall > .file-input-zone-label {
1634
+ position: absolute;
1635
+ inset: 0;
1636
+ display: flex;
1637
+ flex-direction: column;
1638
+ align-items: center;
1639
+ justify-content: center;
1640
+ gap: 10px;
1641
+ margin: 0;
1642
+ cursor: pointer;
1643
+ }
1644
+ .file-input.file-input-tall > .file-input-zone-label > * {
1645
+ pointer-events: none;
1646
+ }
1647
+ .file-input.file-input-tall .file-input-icon {
1648
+ display: inline-flex;
1649
+ align-items: center;
1650
+ justify-content: center;
1651
+ width: 54px;
1652
+ height: 54px;
1653
+ border-radius: 50%;
1654
+ background: var(--color-primary-soft);
1655
+ color: var(--color-primary);
1656
+ transition: transform 0.15s ease;
1657
+ }
1658
+ .file-input.file-input-tall.dragging .file-input-icon {
1659
+ transform: translateY(-2px) scale(1.06);
1660
+ }
1661
+ .file-input-title {
1662
+ font-size: 14px;
1663
+ font-weight: 600;
1664
+ color: var(--color-text);
1665
+ }
1666
+ .file-input-title .browse {
1667
+ color: var(--color-primary);
1668
+ }
1669
+ .file-input.file-input-tall .file-input-text {
1670
+ padding: 0;
1671
+ font-size: 12px;
1672
+ }
1673
+
1674
+ // Large preview shown once an image exists; hover reveals a "Change image"
1675
+ // overlay.
1676
+ .file-preview {
1677
+ position: relative;
1678
+ box-sizing: border-box;
1679
+ display: block;
1680
+ width: 100%;
1681
+ height: 350px;
1682
+ border-radius: 16px;
1683
+ corner-shape: squircle;
1684
+ border: 1px solid var(--color-border);
1685
+ overflow: hidden;
1686
+ cursor: pointer;
1687
+ background: var(--color-bg-subtle);
1688
+ transition: border-color 0.12s;
1689
+ }
1690
+ .file-preview:hover {
1691
+ border-color: var(--color-border-strong);
1692
+ }
1693
+ .file-preview.error {
1694
+ border-color: var(--color-danger);
1695
+ }
1696
+ // Open-picker control fills the shell (remove button is a sibling, not
1697
+ // nested).
1698
+ .file-preview-open {
1699
+ display: block;
1700
+ box-sizing: border-box;
1701
+ width: 100%;
1702
+ height: 100%;
1703
+ padding: 0;
1704
+ border: none;
1705
+ background: transparent;
1706
+ cursor: pointer;
1707
+ position: relative;
1708
+ font: inherit;
1709
+ color: inherit;
1710
+ text-align: left;
1711
+ }
1712
+ .file-preview-img {
1713
+ display: block;
1714
+ width: 100%;
1715
+ height: 100%;
1716
+ object-fit: cover;
1717
+ transition: transform 0.25s ease;
1718
+ }
1719
+ // Gentle zoom on hover so the field feels interactive.
1720
+ .file-preview:hover .file-preview-img {
1721
+ transform: scale(1.03);
1722
+ }
1723
+ .file-preview-overlay {
1724
+ position: absolute;
1725
+ inset: 0;
1726
+ display: flex;
1727
+ align-items: center;
1728
+ justify-content: center;
1729
+ // Subtle dim so the glassy CTA reads on any image, without hiding it.
1730
+ background: rgba(16, 24, 40, 0.22);
1731
+ opacity: 0;
1732
+ transition: opacity 0.15s ease;
1733
+ }
1734
+ .file-preview:hover .file-preview-overlay,
1735
+ .file-preview:focus-within .file-preview-overlay {
1736
+ opacity: 1;
1737
+ }
1738
+ // The "Change image" call-to-action as a glassy pill button.
1739
+ .file-preview-cta {
1740
+ display: inline-flex;
1741
+ align-items: center;
1742
+ gap: 8px;
1743
+ padding: 9px 18px;
1744
+ background: rgba(255, 255, 255, 0.96);
1745
+ color: var(--color-text);
1746
+ font-size: 14px;
1747
+ font-weight: 600;
1748
+ border-radius: 999px;
1749
+ box-shadow: 0 6px 18px rgba(16, 24, 40, 0.22);
1750
+ }
1751
+ .file-preview-remove {
1752
+ position: absolute;
1753
+ top: 8px;
1754
+ right: 8px;
1755
+ z-index: 2;
1756
+ box-sizing: border-box;
1757
+ display: inline-flex;
1758
+ align-items: center;
1759
+ justify-content: center;
1760
+ width: 30px;
1761
+ height: 30px;
1762
+ padding: 0;
1763
+ border: 2px solid transparent;
1764
+ border-radius: 50%;
1765
+ background: rgba(16, 24, 40, 0.55);
1766
+ color: #fff;
1767
+ cursor: pointer;
1768
+ opacity: 0;
1769
+ transition:
1770
+ border-color 0.12s,
1771
+ opacity 0.15s ease;
1772
+ }
1773
+ .file-preview:hover .file-preview-remove,
1774
+ .file-preview:focus-within .file-preview-remove {
1775
+ opacity: 1;
1776
+ }
1777
+ // Hovering the cross: keep it plain — just a slightly deeper disc, no
1778
+ // coloured ring.
1779
+ .file-preview-remove:hover {
1780
+ background: rgba(16, 24, 40, 0.78);
1781
+ color: #fff;
1782
+ }
1783
+
1784
+ // ── Logo picker ──
1785
+ // Round dropzone uploader (workspace/brand logo) and the settings-page photo
1786
+ // ring share this same interactive-uploader pattern.
1787
+ .set-uploader {
1788
+ display: inline-flex;
1789
+ padding: 0;
1790
+ border: none;
1791
+ background: transparent;
1792
+ font: inherit;
1793
+ color: inherit;
1794
+ cursor: pointer;
1795
+ }
1796
+ .set-uploader:disabled {
1797
+ cursor: progress;
1798
+ }
1799
+ .set-uploader-img {
1800
+ width: 100%;
1801
+ height: 100%;
1802
+ border-radius: 50%;
1803
+ object-fit: cover;
1804
+ }
1805
+ .set-uploader-ring {
1806
+ position: relative;
1807
+ width: 120px;
1808
+ height: 120px;
1809
+ border-radius: 50%;
1810
+ padding: 8px;
1811
+ border: 1px dashed color-mix(in srgb, var(--color-border-strong) 75%, transparent);
1812
+ cursor: pointer;
1813
+ transition: border-color 0.18s;
1814
+ }
1815
+ .set-uploader-ring:hover,
1816
+ .set-uploader:hover .set-uploader-ring,
1817
+ .set-uploader:focus-visible .set-uploader-ring {
1818
+ border-color: var(--color-primary);
1819
+ }
1820
+ .set-uploader-logo {
1821
+ width: 100%;
1822
+ height: 100%;
1823
+ border-radius: 50%;
1824
+ display: grid;
1825
+ place-items: center;
1826
+ font-size: 38px;
1827
+ font-weight: 700;
1828
+ letter-spacing: -0.02em;
1829
+ color: var(--color-primary);
1830
+ background: var(--color-primary-soft);
1831
+ }
1832
+ .set-uploader-overlay {
1833
+ position: absolute;
1834
+ inset: 8px;
1835
+ border-radius: 50%;
1836
+ display: flex;
1837
+ flex-direction: column;
1838
+ align-items: center;
1839
+ justify-content: center;
1840
+ gap: 4px;
1841
+ background: rgba(20, 26, 33, 0.62);
1842
+ color: #fff;
1843
+ font-size: 11px;
1844
+ font-weight: 600;
1845
+ opacity: 0;
1846
+ transition: opacity 0.18s;
1847
+ }
1848
+ .set-uploader-ring:hover .set-uploader-overlay,
1849
+ .set-uploader:hover .set-uploader-overlay,
1850
+ .set-uploader:focus-visible .set-uploader-overlay {
1851
+ opacity: 1;
1852
+ }
1853
+ // While uploading: keep the overlay (spinner) visible and signal it's not
1854
+ // clickable.
1855
+ .set-uploader-overlay.is-loading {
1856
+ opacity: 1;
1857
+ }
1858
+ .set-uploader[aria-busy="true"] {
1859
+ cursor: progress;
1860
+ }
1861
+ .set-uploader-hint {
1862
+ margin: 18px auto 0;
1863
+ max-width: 240px;
1864
+ font-size: 12px;
1865
+ line-height: 1.5;
1866
+ color: var(--color-text-subtle);
1867
+ }
1868
+
1869
+ // Minimals-style rounded logo dropzone (workspace branding form).
1870
+ .logo-upload {
1871
+ display: flex;
1872
+ align-items: center;
1873
+ gap: 20px;
1874
+ flex-wrap: wrap;
1875
+ }
1876
+ .logo-dropzone {
1877
+ position: relative;
1878
+ width: 104px;
1879
+ height: 104px;
1880
+ flex: none;
1881
+ border-radius: var(--radius-lg);
1882
+ border: 1.5px dashed var(--color-border-strong);
1883
+ background: var(--color-bg-subtle);
1884
+ display: grid;
1885
+ place-items: center;
1886
+ overflow: hidden;
1887
+ cursor: pointer;
1888
+ padding: 6px;
1889
+ transition:
1890
+ border-color 0.15s ease,
1891
+ background 0.15s ease;
1892
+ }
1893
+ .logo-dropzone:hover,
1894
+ .logo-dropzone:focus-visible {
1895
+ border-color: var(--color-primary);
1896
+ background: var(--color-primary-soft);
1897
+ outline: none;
1898
+ }
1899
+ .logo-dropzone.has-image {
1900
+ border-style: solid;
1901
+ background: var(--color-surface);
1902
+ }
1903
+ .logo-dropzone-empty {
1904
+ display: grid;
1905
+ place-items: center;
1906
+ color: var(--color-text-subtle);
1907
+ }
1908
+ .logo-dropzone-img {
1909
+ max-width: 100%;
1910
+ max-height: 100%;
1911
+ object-fit: contain;
1912
+ border-radius: var(--radius);
1913
+ }
1914
+ // hover overlay (camera + label), like the Minimals avatar uploader
1915
+ .logo-dropzone-overlay {
1916
+ position: absolute;
1917
+ inset: 0;
1918
+ display: flex;
1919
+ flex-direction: column;
1920
+ align-items: center;
1921
+ justify-content: center;
1922
+ gap: 4px;
1923
+ background: var(--color-scrim);
1924
+ color: var(--color-on-scrim);
1925
+ font-size: 12px;
1926
+ font-weight: 600;
1927
+ opacity: 0;
1928
+ transition: opacity 0.15s ease;
1929
+ }
1930
+ .logo-dropzone:hover .logo-dropzone-overlay,
1931
+ .logo-dropzone:focus-visible .logo-dropzone-overlay {
1932
+ opacity: 1;
1933
+ }
1934
+ .logo-upload-body {
1935
+ flex: 1;
1936
+ min-width: 200px;
1937
+ }
1938
+
1939
+ // ── Drawer / Sidebar menu ──
1940
+ // AppSidebarMenu — react-bootstrap Offcanvas mapped onto the app tokens plus
1941
+ // a flex column layout for the header / scrolling body / sticky footer.
1942
+ .app-sidebar-menu.offcanvas {
1943
+ --bs-offcanvas-width: var(--asm-width, 420px);
1944
+ --bs-offcanvas-bg: var(--color-surface);
1945
+ --bs-offcanvas-color: var(--color-text);
1946
+ --bs-offcanvas-border-color: var(--color-border);
1947
+ --bs-offcanvas-border-width: 1px;
1948
+ --bs-offcanvas-padding-x: 0;
1949
+ --bs-offcanvas-padding-y: 0;
1950
+ max-width: 100vw;
1951
+ display: flex;
1952
+ flex-direction: column;
1953
+ }
1954
+ .app-sidebar-menu .offcanvas-body {
1955
+ padding: 0;
1956
+ display: flex;
1957
+ flex-direction: column;
1958
+ flex: 1;
1959
+ min-height: 0;
1960
+ }
1961
+ .app-sidebar-menu .asm-footer {
1962
+ padding: 16px 20px;
1963
+ border-top: 1px solid var(--color-border);
1964
+ background: var(--color-surface);
1965
+ }
1966
+
1967
+ .drawer {
1968
+ position: fixed;
1969
+ top: 0;
1970
+ right: 0;
1971
+ width: 420px;
1972
+ max-width: 100vw;
1973
+ height: 100vh;
1974
+ background: var(--color-surface);
1975
+ border-left: 1px solid var(--color-border);
1976
+ z-index: 200;
1977
+ transform: translateX(100%);
1978
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
1979
+ display: flex;
1980
+ flex-direction: column;
1981
+ }
1982
+ .drawer.open {
1983
+ transform: translateX(0);
1984
+ }
1985
+ .drawer-backdrop {
1986
+ position: fixed;
1987
+ inset: 0;
1988
+ background: rgba(15, 23, 42, 0.4);
1989
+ opacity: 0;
1990
+ pointer-events: none;
1991
+ transition: opacity 0.2s;
1992
+ z-index: 150;
1993
+ }
1994
+ .drawer-backdrop.open {
1995
+ opacity: 1;
1996
+ pointer-events: auto;
1997
+ }
1998
+ .drawer-header {
1999
+ padding: 18px 20px;
2000
+ border-bottom: 1px solid var(--color-border);
2001
+ display: flex;
2002
+ align-items: center;
2003
+ justify-content: space-between;
2004
+ background: var(--color-surface);
2005
+ }
2006
+ .drawer-title {
2007
+ font-weight: 600;
2008
+ font-size: 16px;
2009
+ letter-spacing: -0.01em;
2010
+ display: flex;
2011
+ align-items: center;
2012
+ gap: 10px;
2013
+ color: var(--color-text);
2014
+ }
2015
+ .drawer-title-ic {
2016
+ flex: none;
2017
+ display: grid;
2018
+ place-items: center;
2019
+ width: 30px;
2020
+ height: 30px;
2021
+ border-radius: 9px;
2022
+ corner-shape: squircle;
2023
+ background: var(--color-primary-soft);
2024
+ color: var(--color-primary);
2025
+ }
2026
+ // Sticky tools zone: search + section meta stay put while the list scrolls.
2027
+ .drawer-tools {
2028
+ padding: 16px 20px 12px;
2029
+ border-bottom: 1px solid var(--color-border);
2030
+ background: var(--color-surface);
2031
+ }
2032
+ .drawer-body {
2033
+ flex: 1;
2034
+ overflow-y: auto;
2035
+ padding: 14px 16px 20px;
2036
+ }
2037
+
2038
+ // ── Empty state ──
2039
+ .empty-state {
2040
+ text-align: center;
2041
+ padding: 48px 24px;
2042
+ color: var(--color-text-subtle);
2043
+ }
2044
+ .empty-state-icon {
2045
+ width: 48px;
2046
+ height: 48px;
2047
+ border-radius: 12px;
2048
+ background: var(--color-primary-soft);
2049
+ color: var(--color-primary);
2050
+ display: inline-flex;
2051
+ align-items: center;
2052
+ justify-content: center;
2053
+ margin-bottom: 14px;
2054
+ }
2055
+
2056
+ // The empty state a DataTable renders in place of its rows. Distinct from
2057
+ // the card-level .empty-state above: a 50px primary-tinted disc, then a 32px
2058
+ // gap to the title and 12px to the subtitle.
2059
+ .tbl-empty {
2060
+ display: flex;
2061
+ flex-direction: column;
2062
+ align-items: center;
2063
+ text-align: center;
2064
+ padding: 40px 24px;
2065
+ }
2066
+ .tbl-empty-icon {
2067
+ display: inline-grid;
2068
+ place-items: center;
2069
+ flex: none;
2070
+ width: 50px;
2071
+ height: 50px;
2072
+ border-radius: 50%;
2073
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
2074
+ color: var(--color-primary);
2075
+ margin-bottom: 16px;
2076
+ }
2077
+ .tbl-empty-title {
2078
+ font-size: 18px;
2079
+ font-weight: 600;
2080
+ line-height: 1.35;
2081
+ color: var(--color-text);
2082
+ }
2083
+ .tbl-empty-text {
2084
+ margin-top: 8px;
2085
+ font-size: 16px;
2086
+ font-weight: 400;
2087
+ line-height: 1.5;
2088
+ color: var(--color-text-muted);
2089
+ }
2090
+ // An action (e.g. "New assessment") sits under the subtitle when the caller
2091
+ // supplies one.
2092
+ .tbl-empty-action {
2093
+ margin-top: 20px;
2094
+ }
2095
+
2096
+ .lx-table-empty {
2097
+ padding: 36px 24px;
2098
+ text-align: center;
2099
+ color: var(--color-text-muted);
2100
+ }
2101
+ // The empty state owns its own padding and is not a row you can act on — no
2102
+ // cell padding, no border, and no hover fill from .table / .table-ledger.
2103
+ .table td.lx-table-empty:has(> .tbl-empty) {
2104
+ padding: 0;
2105
+ border-bottom: 0;
2106
+ }
2107
+ .table tbody tr:hover td.lx-table-empty:has(> .tbl-empty),
2108
+ .table-ledger tbody tr:hover td.lx-table-empty:has(> .tbl-empty) {
2109
+ background: transparent;
2110
+ }
2111
+
2112
+ // ── Table + pagination ──
2113
+ // DataTable cell helpers
2114
+ .table-ledger .lx-cell-end,
2115
+ .table-ledger thead th.lx-cell-end {
2116
+ text-align: right;
2117
+ }
2118
+
2119
+ // Ledger chrome — the shared table look. Applies to every .table-ledger: a
2120
+ // floating 42px header band, airy rows on a hairline rule, and an inset from
2121
+ // the card edge. Column geometry is NOT set here.
2122
+ .table-wrap:has(> .table-ledger) {
2123
+ padding: 20px 20px 0;
2124
+ }
2125
+ .table-ledger {
2126
+ border-spacing: 0;
2127
+ }
2128
+ .table-ledger thead th {
2129
+ height: 42px;
2130
+ padding: 0 12px;
2131
+ background: var(--color-bg-muted);
2132
+ border-bottom: none;
2133
+ font-family: "Inter", var(--font-sans, sans-serif);
2134
+ font-size: 12px;
2135
+ font-weight: 500;
2136
+ line-height: 17px;
2137
+ letter-spacing: 0.04em;
2138
+ text-transform: uppercase;
2139
+ color: var(--color-text-subtle);
2140
+ vertical-align: middle;
2141
+ }
2142
+ .table-ledger thead th:first-child {
2143
+ padding-left: 20px;
2144
+ border-radius: 6px 0 0 6px;
2145
+ }
2146
+ .table-ledger thead th:last-child {
2147
+ padding-right: 20px;
2148
+ border-radius: 0 6px 6px 0;
2149
+ }
2150
+ .table-ledger tbody td {
2151
+ height: 54px;
2152
+ padding: 12px;
2153
+ border-bottom: 0.5px solid var(--color-border);
2154
+ font-family: "Inter", var(--font-sans, sans-serif);
2155
+ font-size: 12px;
2156
+ font-weight: 500;
2157
+ line-height: 15px;
2158
+ color: var(--color-text-muted);
2159
+ }
2160
+ .table-ledger tbody td:first-child {
2161
+ padding-left: 20px;
2162
+ }
2163
+ .table-ledger tbody td:last-child {
2164
+ padding-right: 20px;
2165
+ }
2166
+ .table-ledger tbody tr:last-child td {
2167
+ border-bottom: none;
2168
+ }
2169
+ .table-ledger tbody tr:hover td {
2170
+ background: var(--color-bg-subtle);
2171
+ }
2172
+ .table-ledger .u-meta {
2173
+ gap: 2px;
2174
+ }
2175
+ .table-ledger .u-name {
2176
+ font-size: 14px;
2177
+ font-weight: 500;
2178
+ line-height: 19px;
2179
+ color: var(--color-text);
2180
+ }
2181
+ @media (max-width: 575.98px) {
2182
+ .table-ledger .u-name {
2183
+ font-size: 12px;
2184
+ line-height: normal;
2185
+ margin-bottom: 2px;
2186
+ }
2187
+ }
2188
+ // An identity-cell name is often a link, so it inherits Bootstrap's
2189
+ // underline-on-hover. The colour shift is the affordance here.
2190
+ .table-ledger a.u-name,
2191
+ .table-ledger a.u-name:hover {
2192
+ text-decoration: none;
2193
+ }
2194
+ // Identity-cell subline.
2195
+ .table-ledger .u-meta > .lx-text-xs {
2196
+ font-size: 12px;
2197
+ font-weight: 400;
2198
+ line-height: 15px;
2199
+ color: var(--color-text-subtle);
2200
+ }
2201
+ .table-ledger .u-row-actions {
2202
+ gap: 6px;
2203
+ }
2204
+
2205
+ // Ledger tables: 20px edge padding so header labels + first/last cells align
2206
+ // with the card edge — shared across every .table-ledger surface.
2207
+ .table-ledger thead th:first-child,
2208
+ .table-ledger tbody td:first-child {
2209
+ padding-left: 20px;
2210
+ }
2211
+ .table-ledger thead th:last-child,
2212
+ .table-ledger tbody td:last-child {
2213
+ padding-right: 20px;
2214
+ }
2215
+
2216
+ // Pagination footer — rows-per-page + range + prev/next, sits flush under a
2217
+ // table.
2218
+ .table-pagination {
2219
+ display: flex;
2220
+ align-items: center;
2221
+ justify-content: flex-end;
2222
+ gap: 22px;
2223
+ padding: 12px 16px;
2224
+ border-top: 1px solid var(--color-border);
2225
+ font-size: 13px;
2226
+ color: var(--color-text-muted);
2227
+ flex-wrap: wrap;
2228
+ }
2229
+ // Rows-per-page sits at the leading edge; range + nav stay grouped on the
2230
+ // right.
2231
+ .tpg-rows {
2232
+ display: inline-flex;
2233
+ align-items: center;
2234
+ gap: 8px;
2235
+ white-space: nowrap;
2236
+ margin-right: auto;
2237
+ font-size: 12px;
2238
+ font-weight: 500;
2239
+ text-transform: uppercase;
2240
+ color: var(--color-text-subtle);
2241
+ }
2242
+ .select.tpg-select {
2243
+ width: auto;
2244
+ min-width: 64px;
2245
+ height: 32px;
2246
+ min-height: 32px;
2247
+ padding: 0 28px 0 12px;
2248
+ font-size: 13px;
2249
+ font-weight: 600;
2250
+ border-radius: 16px;
2251
+ background-position: right 8px center;
2252
+ background-size: 14px;
2253
+ }
2254
+ .tpg-range {
2255
+ font-variant-numeric: tabular-nums;
2256
+ white-space: nowrap;
2257
+ font-size: 14px;
2258
+ font-weight: 500;
2259
+ color: var(--color-text-muted);
2260
+ }
2261
+ .tpg-nav {
2262
+ display: inline-flex;
2263
+ align-items: center;
2264
+ gap: 4px;
2265
+ }
2266
+ .tpg-arrow {
2267
+ display: grid;
2268
+ place-items: center;
2269
+ width: 32px;
2270
+ height: 32px;
2271
+ border: none;
2272
+ background: transparent;
2273
+ color: var(--color-text-muted);
2274
+ border-radius: 50%;
2275
+ cursor: pointer;
2276
+ transition:
2277
+ background 0.14s ease,
2278
+ color 0.14s ease;
2279
+ }
2280
+ .tpg-arrow:hover:not(:disabled) {
2281
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
2282
+ color: var(--color-primary);
2283
+ }
2284
+ // Text-5 already reads as "unavailable" — no opacity dimming on top of it.
2285
+ .tpg-arrow:disabled {
2286
+ color: var(--color-border-strong);
2287
+ cursor: not-allowed;
2288
+ }
2289
+
2290
+ // Horizontal scroll guard + flush-fit inside a card. Wrapping the table here
2291
+ // also stops `.card > .table` header-corner rounding from applying mid-card.
2292
+ .table-wrap {
2293
+ overflow-x: auto;
2294
+ max-width: 100%;
2295
+ // Keep the horizontal swipe inside the table — don't let it drag the
2296
+ // page — and give touch devices momentum scrolling. Scrollbar is
2297
+ // thinned/hidden so the scroll feels native.
2298
+ overscroll-behavior-x: contain;
2299
+ -webkit-overflow-scrolling: touch;
2300
+ scrollbar-width: thin;
2301
+ scrollbar-color: var(--color-border-strong) transparent;
2302
+ }
2303
+ .table-wrap::-webkit-scrollbar {
2304
+ height: 6px;
2305
+ }
2306
+ .table-wrap::-webkit-scrollbar-track {
2307
+ background: transparent;
2308
+ }
2309
+ .table-wrap::-webkit-scrollbar-thumb {
2310
+ background: var(--color-border-strong);
2311
+ border-radius: 999px;
2312
+ }
2313
+
2314
+ // Row-selection checkbox column.
2315
+ .table-ledger th.lx-select-cell,
2316
+ .table-ledger td.lx-select-cell {
2317
+ width: 44px;
2318
+ min-width: 44px;
2319
+ max-width: 44px;
2320
+ text-align: center;
2321
+ padding-left: 12px;
2322
+ padding-right: 0;
2323
+ }
2324
+ .table-ledger tbody tr.is-selected {
2325
+ background: var(--color-bg-muted);
2326
+ }
2327
+
2328
+ // Row actions — icon-only buttons replacing a kebab dropdown.
2329
+ .u-row-actions {
2330
+ display: flex;
2331
+ align-items: center;
2332
+ justify-content: flex-end;
2333
+ gap: 6px;
2334
+ // Action cells never wrap — a status hint stays on one line.
2335
+ white-space: nowrap;
2336
+ }
2337
+ .u-action {
2338
+ display: inline-grid;
2339
+ place-items: center;
2340
+ flex: none;
2341
+ width: 36px;
2342
+ height: 36px;
2343
+ padding: 0;
2344
+ border: none;
2345
+ background: var(--color-bg-subtle);
2346
+ border-radius: 999px;
2347
+ color: var(--color-text-subtle);
2348
+ cursor: pointer;
2349
+ transition:
2350
+ background 0.12s ease,
2351
+ color 0.12s ease;
2352
+ position: relative;
2353
+ }
2354
+ // Touch pointers need a 44px minimum target. Grow the hit area, not the
2355
+ // ink, so row density and alignment are untouched.
2356
+ @media (pointer: coarse) {
2357
+ .u-action::after {
2358
+ content: "";
2359
+ position: absolute;
2360
+ top: 50%;
2361
+ left: 50%;
2362
+ transform: translate(-50%, -50%);
2363
+ width: 44px;
2364
+ height: 44px;
2365
+ }
2366
+ }
2367
+ .u-action svg {
2368
+ flex: none;
2369
+ }
2370
+ .u-action:hover:not(:disabled) {
2371
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
2372
+ color: var(--color-primary);
2373
+ }
2374
+ .u-action:disabled {
2375
+ opacity: 0.4;
2376
+ cursor: not-allowed;
2377
+ }
2378
+ .u-action.danger {
2379
+ color: var(--color-danger);
2380
+ }
2381
+ .u-action.danger:hover:not(:disabled) {
2382
+ background: color-mix(in srgb, var(--color-danger) 10%, transparent);
2383
+ color: var(--color-danger);
2384
+ }
2385
+ .u-action.u-action-preview {
2386
+ background: var(--color-primary-soft);
2387
+ color: #2f50e4;
2388
+ }
2389
+ .u-action.u-action-download {
2390
+ background: var(--color-success-soft);
2391
+ color: #04b440;
2392
+ }
2393
+ .u-action.u-action-share {
2394
+ background: var(--color-warning-soft);
2395
+ color: #dfa000;
2396
+ }
2397
+ .u-action.u-action-preview:hover:not(:disabled) {
2398
+ background: var(--color-primary-soft);
2399
+ color: #2f50e4;
2400
+ }
2401
+ .u-action.u-action-download:hover:not(:disabled) {
2402
+ background: var(--color-success-soft);
2403
+ color: #04b440;
2404
+ }
2405
+ .u-action.u-action-share:hover:not(:disabled) {
2406
+ background: var(--color-warning-soft);
2407
+ color: #dfa000;
2408
+ }
2409
+ .u-action.u-action-danger:hover {
2410
+ color: var(--color-danger);
2411
+ background: color-mix(in srgb, var(--color-danger) 10%, transparent);
2412
+ }
2413
+
2414
+ // Identity cell — avatar + stacked name/email.
2415
+ .u-cell {
2416
+ display: flex;
2417
+ align-items: center;
2418
+ gap: 14px;
2419
+ }
2420
+ .u-avatar {
2421
+ width: 40px;
2422
+ height: 40px;
2423
+ border-radius: 50%;
2424
+ flex: none;
2425
+ display: grid;
2426
+ place-items: center;
2427
+ font-size: 16px;
2428
+ font-weight: 600;
2429
+ color: var(--color-primary);
2430
+ background: color-mix(in srgb, var(--color-primary) 20%, transparent);
2431
+ }
2432
+ .u-meta {
2433
+ display: flex;
2434
+ flex-direction: column;
2435
+ min-width: 0;
2436
+ }
2437
+ .u-name {
2438
+ display: inline-flex;
2439
+ align-items: center;
2440
+ gap: 8px;
2441
+ font-size: 14px;
2442
+ font-weight: 600;
2443
+ color: var(--color-text);
2444
+ }
2445
+ .u-email {
2446
+ font-size: 14px;
2447
+ font-weight: 400;
2448
+ color: var(--color-text-muted);
2449
+ }
2450
+ .u-you {
2451
+ padding: 1px 7px;
2452
+ font-size: 10.5px;
2453
+ }
2454
+
2455
+ // ── Modal ──
2456
+ // Chrome stays on `.lx-modal-*`. The shell is react-bootstrap <Modal>:
2457
+ // .modal.lx-modal-shell ← className (layout container)
2458
+ // .modal-backdrop.lx-modal-dim ← backdropClassName (dim only)
2459
+ // .modal-dialog.lx-rb-dialog ← dialogClassName
2460
+ // .modal-content.lx-modal ← contentClassName (the card)
2461
+ // Do NOT put layout on backdropClassName — that targets a sibling, not
2462
+ // .modal.
2463
+ .modal-backdrop.lx-modal-dim {
2464
+ background: rgba(15, 23, 42, 0.45);
2465
+ z-index: 1500;
2466
+ }
2467
+ .modal.lx-modal-shell {
2468
+ --bs-modal-bg: transparent;
2469
+ display: flex !important;
2470
+ align-items: center;
2471
+ justify-content: center;
2472
+ z-index: 1505;
2473
+ padding: 20px;
2474
+ }
2475
+ // RB dialog shell: no Bootstrap max-width/margin so `.lx-modal` owns sizing.
2476
+ .modal.lx-modal-shell .modal-dialog.lx-rb-dialog {
2477
+ margin: 0;
2478
+ max-width: none;
2479
+ width: auto;
2480
+ pointer-events: none;
2481
+ display: flex;
2482
+ align-items: center;
2483
+ justify-content: center;
2484
+ }
2485
+ .modal.lx-modal-shell .modal-dialog.lx-rb-dialog .modal-content {
2486
+ pointer-events: auto;
2487
+ border: none;
2488
+ // Lift off the dim backdrop: a hairline ring defines the edge (carries
2489
+ // elevation in dark mode where a drop shadow barely reads) + a soft,
2490
+ // deep drop shadow.
2491
+ box-shadow:
2492
+ 0 0 0 1px color-mix(in srgb, var(--color-text) 10%, transparent),
2493
+ 0 24px 70px -18px rgba(0, 0, 0, 0.55);
2494
+ background: var(--color-surface);
2495
+ }
2496
+ .lx-modal {
2497
+ background: var(--color-surface);
2498
+ border-radius: 24px;
2499
+ corner-shape: squircle;
2500
+ border: none;
2501
+ width: 100%;
2502
+ max-width: 440px;
2503
+ max-height: calc(100vh - 40px);
2504
+ display: flex;
2505
+ flex-direction: column;
2506
+ overflow: hidden;
2507
+ }
2508
+ .lx-modal-wide {
2509
+ max-width: 1240px;
2510
+ }
2511
+
2512
+ // Side drawer — the dialog docks to the right edge at 30% of the viewport
2513
+ // and slides in, instead of sitting centred. Same chrome, different
2514
+ // geometry.
2515
+ .modal.lx-modal-shell.is-side {
2516
+ align-items: stretch;
2517
+ justify-content: flex-end;
2518
+ padding: 0;
2519
+ }
2520
+ .modal.lx-modal-shell.is-side .modal-dialog.lx-rb-dialog {
2521
+ height: 100%;
2522
+ width: auto;
2523
+ max-width: none;
2524
+ align-items: stretch;
2525
+ justify-content: flex-end;
2526
+ }
2527
+ .lx-modal-side {
2528
+ width: 30vw;
2529
+ min-width: 420px;
2530
+ max-width: none;
2531
+ max-height: 100vh;
2532
+ height: 100vh;
2533
+ border-radius: 0;
2534
+ corner-shape: normal;
2535
+ animation: lx-drawer-in 0.32s cubic-bezier(0.22, 1, 0.36, 1);
2536
+ }
2537
+ @media (max-width: 991.98px) {
2538
+ .lx-modal-side {
2539
+ width: 70vw;
2540
+ }
2541
+ }
2542
+ @media (max-width: 575.98px) {
2543
+ .lx-modal-side {
2544
+ width: 100vw;
2545
+ }
2546
+ }
2547
+ @keyframes lx-drawer-in {
2548
+ from {
2549
+ transform: translateX(100%);
2550
+ }
2551
+ to {
2552
+ transform: translateX(0);
2553
+ }
2554
+ }
2555
+ @media (prefers-reduced-motion: reduce) {
2556
+ .lx-modal-side {
2557
+ animation: none;
2558
+ }
2559
+ }
2560
+ .lx-modal-header {
2561
+ flex: 0 0 auto;
2562
+ display: flex;
2563
+ align-items: center;
2564
+ justify-content: space-between;
2565
+ gap: 12px;
2566
+ padding: 16px 20px;
2567
+ border-bottom: 1px solid var(--color-border);
2568
+ }
2569
+ .lx-modal-body {
2570
+ flex: 1 1 auto;
2571
+ min-height: 0;
2572
+ overflow-y: auto;
2573
+ overscroll-behavior: contain;
2574
+ scroll-behavior: smooth;
2575
+ -webkit-overflow-scrolling: touch;
2576
+ scrollbar-width: thin;
2577
+ scrollbar-color: var(--color-border-strong) transparent;
2578
+ padding: 20px 22px 24px;
2579
+ }
2580
+ .lx-modal-body::-webkit-scrollbar {
2581
+ width: 8px;
2582
+ }
2583
+ .lx-modal-body::-webkit-scrollbar-track {
2584
+ background: transparent;
2585
+ }
2586
+ .lx-modal-body::-webkit-scrollbar-thumb {
2587
+ background: transparent;
2588
+ border-radius: 999px;
2589
+ border: 2px solid transparent;
2590
+ background-clip: padding-box;
2591
+ }
2592
+ .lx-modal-body:hover::-webkit-scrollbar-thumb {
2593
+ background: var(--color-border-strong);
2594
+ background-clip: padding-box;
2595
+ }
2596
+ @media (prefers-reduced-motion: reduce) {
2597
+ .lx-modal-body {
2598
+ scroll-behavior: auto;
2599
+ }
2600
+ }
2601
+ // Modal heading group: optional contextual (Bulk) icon tile + title/subtitle.
2602
+ .lx-modal-heading {
2603
+ display: flex;
2604
+ align-items: center;
2605
+ gap: 10px;
2606
+ min-width: 0;
2607
+ }
2608
+ .lx-modal-headtext {
2609
+ min-width: 0;
2610
+ }
2611
+ .lx-modal-headicon {
2612
+ display: inline-flex;
2613
+ align-items: center;
2614
+ justify-content: center;
2615
+ width: 34px;
2616
+ height: 34px;
2617
+ border-radius: 10px;
2618
+ corner-shape: squircle;
2619
+ flex: none;
2620
+ background: var(--color-primary-soft);
2621
+ color: var(--color-primary);
2622
+ }
2623
+
2624
+ // Break long unbroken names so they wrap within the heading instead of
2625
+ // running under the close button.
2626
+ .lx-modal-title {
2627
+ font-size: 20px;
2628
+ font-weight: 600;
2629
+ color: var(--color-text);
2630
+ overflow-wrap: anywhere;
2631
+ }
2632
+ .lx-modal-subtitle {
2633
+ margin-top: 1px;
2634
+ font-size: 12px;
2635
+ font-weight: 400;
2636
+ color: var(--color-text-muted);
2637
+ overflow-wrap: anywhere;
2638
+ }
2639
+ .lx-form-section {
2640
+ margin-top: 24px;
2641
+ }
2642
+ .lx-form-section:first-child {
2643
+ margin-top: 0;
2644
+ }
2645
+ // Stretch controls to fill the column even though .field aligns to
2646
+ // flex-start.
2647
+ .lx-modal-body .field > .input,
2648
+ .lx-modal-body .field > .search-select,
2649
+ .lx-modal-body .field > .input-date-wrap,
2650
+ .lx-modal-body .field > .datepicker,
2651
+ .lx-modal-body .field > .phone-input-wrap,
2652
+ .lx-modal-body .field > .field-error {
2653
+ align-self: stretch;
2654
+ }
2655
+ .lx-form-section-head {
2656
+ margin-bottom: 16px;
2657
+ }
2658
+ .lx-form-section-title {
2659
+ font-size: 16px;
2660
+ font-weight: 600;
2661
+ text-transform: uppercase;
2662
+ letter-spacing: 0.04em;
2663
+ color: var(--color-text-subtle);
2664
+ }
2665
+ .lx-modal-footer {
2666
+ flex: 0 0 auto;
2667
+ display: flex;
2668
+ align-items: center;
2669
+ justify-content: flex-end;
2670
+ gap: 10px;
2671
+ padding: 16px 22px;
2672
+ border-top: 1px solid var(--color-border);
2673
+ }
2674
+
2675
+ // Modal close — a squircle at rest that morphs into a circle on hover while
2676
+ // taking a danger tint, so dismissing reads as the destructive-ish gesture
2677
+ // it is. Slower on the way in than the way out.
2678
+ .lx-modal-close {
2679
+ margin-left: auto;
2680
+ display: grid;
2681
+ place-items: center;
2682
+ flex: none;
2683
+ width: 36px;
2684
+ height: 36px;
2685
+ padding: 0;
2686
+ border: none;
2687
+ cursor: pointer;
2688
+ line-height: 1;
2689
+ background: var(--color-bg-subtle);
2690
+ color: var(--color-text-muted);
2691
+ border-radius: 999px;
2692
+ corner-shape: squircle;
2693
+ transition:
2694
+ background 180ms cubic-bezier(0.4, 0, 0.2, 1),
2695
+ color 180ms cubic-bezier(0.4, 0, 0.2, 1),
2696
+ corner-shape 180ms cubic-bezier(0.4, 0, 0.2, 1);
2697
+ }
2698
+ .lx-modal-close:hover {
2699
+ background: var(--color-danger-soft);
2700
+ color: var(--color-danger);
2701
+ corner-shape: round;
2702
+ transition:
2703
+ background 280ms cubic-bezier(0.16, 1, 0.3, 1),
2704
+ color 280ms cubic-bezier(0.16, 1, 0.3, 1),
2705
+ corner-shape 280ms cubic-bezier(0.16, 1, 0.3, 1);
2706
+ }
2707
+ .lx-modal-close:focus-visible {
2708
+ outline: 2px solid var(--color-primary);
2709
+ outline-offset: 2px;
2710
+ }
2711
+ // The shape morph is decorative; hold the colour cue for reduced-motion
2712
+ // users.
2713
+ @media (prefers-reduced-motion: reduce) {
2714
+ .lx-modal-close,
2715
+ .lx-modal-close:hover {
2716
+ corner-shape: squircle;
2717
+ transition:
2718
+ background 1ms linear,
2719
+ color 1ms linear;
2720
+ }
2721
+ }
2722
+
2723
+ // ── Confirm dialog ──
2724
+ // Confirmation dialog — centred status icon + title + message, two equal
2725
+ // side-by-side CTAs. Runs headerless (Modal hideHeader), so the body
2726
+ // carries the spacing and the footer drops its divider.
2727
+ .lx-confirm-content {
2728
+ display: flex;
2729
+ flex-direction: column;
2730
+ align-items: center;
2731
+ text-align: center;
2732
+ padding: 16px 12px 6px;
2733
+ }
2734
+ .lx-confirm-icon {
2735
+ line-height: 0;
2736
+ margin-bottom: 22px;
2737
+ }
2738
+ .lx-confirm-title {
2739
+ margin: 0 0 10px;
2740
+ font-size: 22px;
2741
+ font-weight: 700;
2742
+ letter-spacing: -0.01em;
2743
+ color: var(--color-text);
2744
+ }
2745
+ .lx-confirm-msg {
2746
+ margin: 0;
2747
+ font-size: 15px;
2748
+ line-height: 1.55;
2749
+ color: var(--color-text-muted);
2750
+ max-width: 40ch;
2751
+ }
2752
+ .lx-confirm-actions {
2753
+ display: flex;
2754
+ gap: 12px;
2755
+ width: 100%;
2756
+ }
2757
+ .lx-modal-footer:has(.lx-confirm-actions) {
2758
+ border-top: none;
2759
+ padding-top: 0;
2760
+ }
2761
+ .lx-confirm-actions .btn {
2762
+ flex: 1;
2763
+ min-height: 45px;
2764
+ height: 45px;
2765
+ border-radius: 16px;
2766
+ corner-shape: squircle;
2767
+ padding-left: 16px;
2768
+ padding-right: 16px;
2769
+ font-size: 14px;
2770
+ font-weight: 500;
2771
+ }
2772
+
2773
+ // ── Grid / layout utils ──
2774
+ // Responsive card grids (Bootstrap has no grid-template utility).
2775
+ .lx-grid {
2776
+ display: grid;
2777
+ grid-template-columns: 1fr;
2778
+ }
2779
+ .lx-cols-2 {
2780
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2781
+ }
2782
+ .lx-cols-3 {
2783
+ grid-template-columns: repeat(3, minmax(0, 1fr));
2784
+ }
2785
+ .lx-cols-4 {
2786
+ grid-template-columns: repeat(4, minmax(0, 1fr));
2787
+ }
2788
+ @media (min-width: 768px) {
2789
+ .lx-grid-md-2 {
2790
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2791
+ }
2792
+ .lx-grid-md-3 {
2793
+ grid-template-columns: repeat(3, minmax(0, 1fr));
2794
+ }
2795
+ .lx-grid-md-4 {
2796
+ grid-template-columns: repeat(4, minmax(0, 1fr));
2797
+ }
2798
+ }
2799
+ @media (min-width: 992px) {
2800
+ .lx-grid-lg-2 {
2801
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2802
+ }
2803
+ .lx-grid-lg-3 {
2804
+ grid-template-columns: repeat(3, minmax(0, 1fr));
2805
+ }
2806
+ .lx-grid-lg-4 {
2807
+ grid-template-columns: repeat(4, minmax(0, 1fr));
2808
+ }
2809
+ }
2810
+ .lx-col-span-2 {
2811
+ grid-column: span 2 / span 2;
2812
+ }
2813
+ .lx-col-span-3 {
2814
+ grid-column: span 3 / span 3;
2815
+ }
2816
+ .lx-col-span-full {
2817
+ grid-column: 1 / -1;
2818
+ }
2819
+
2820
+ // ── Info tip ──
2821
+ // Inline term help (InfoTip).
2822
+ .infotip {
2823
+ position: relative;
2824
+ display: inline-flex;
2825
+ vertical-align: middle;
2826
+ margin-left: 5px;
2827
+ }
2828
+ .infotip-btn {
2829
+ display: inline-flex;
2830
+ align-items: center;
2831
+ justify-content: center;
2832
+ width: 16px;
2833
+ height: 16px;
2834
+ padding: 0;
2835
+ border: none;
2836
+ border-radius: 999px;
2837
+ background: transparent;
2838
+ color: var(--color-text-subtle);
2839
+ cursor: help;
2840
+ transform: scaleY(-1);
2841
+ transition: color 0.15s;
2842
+ }
2843
+ .infotip-btn:hover,
2844
+ .infotip-btn:focus-visible {
2845
+ color: var(--color-primary);
2846
+ outline: none;
2847
+ }
2848
+ .infotip-pop {
2849
+ position: absolute;
2850
+ bottom: calc(100% + 8px);
2851
+ left: 50%;
2852
+ transform: translateX(-50%);
2853
+ z-index: 220;
2854
+ display: flex;
2855
+ flex-direction: column;
2856
+ gap: 4px;
2857
+ width: 280px;
2858
+ max-width: 78vw;
2859
+ padding: 12px 14px;
2860
+ background: var(--color-surface);
2861
+ border: none;
2862
+ border-radius: 10px;
2863
+ box-shadow: 0 8px 28px rgba(7, 20, 55, 0.14);
2864
+ text-align: left;
2865
+ white-space: normal;
2866
+ cursor: default;
2867
+ }
2868
+ .infotip-pop::after {
2869
+ content: "";
2870
+ position: absolute;
2871
+ top: 100%;
2872
+ left: 50%;
2873
+ transform: translateX(-50%);
2874
+ border: 6px solid transparent;
2875
+ border-top-color: var(--color-surface);
2876
+ }
2877
+ // Portalled to <body>: position is set inline (fixed top/left), so drop the
2878
+ // anchored offsets and the centering transform, and hide the arrow. z-index
2879
+ // sits above the modal backdrop so it shows over the modal, not behind it.
2880
+ .infotip-pop.is-portal {
2881
+ bottom: auto;
2882
+ transform: none;
2883
+ z-index: 1600;
2884
+ }
2885
+ .infotip-pop.is-portal::after {
2886
+ display: none;
2887
+ }
2888
+ .infotip-term {
2889
+ display: flex;
2890
+ align-items: center;
2891
+ gap: 8px;
2892
+ font-size: 13px;
2893
+ font-weight: 600;
2894
+ color: var(--color-text);
2895
+ }
2896
+ .infotip-full {
2897
+ font-size: 12px;
2898
+ font-weight: 500;
2899
+ color: var(--color-text);
2900
+ }
2901
+ .infotip-def {
2902
+ font-size: 12px;
2903
+ color: var(--color-text-muted);
2904
+ line-height: 1.5;
2905
+ }
2906
+
2907
+ // ── Page header ──
2908
+ .page-header {
2909
+ margin-bottom: 24px;
2910
+ }
2911
+ .page-title {
2912
+ font-size: 24px;
2913
+ font-weight: 600;
2914
+ letter-spacing: -0.02em;
2915
+ color: var(--color-text);
2916
+ }
2917
+ .page-subtitle {
2918
+ font-size: 14px;
2919
+ font-weight: 400;
2920
+ color: var(--color-text-muted);
2921
+ margin-top: 2px;
2922
+ }
2923
+ .page-actions {
2924
+ display: flex;
2925
+ gap: 8px;
2926
+ align-items: center;
2927
+ }
2928
+
2929
+ // ── Row-action / icon buttons ──
2930
+ .btn-icon {
2931
+ min-height: 0;
2932
+ padding: 8px;
2933
+ border-radius: 999px;
2934
+ corner-shape: round;
2935
+ }
2936
+
2937
+ // Circular icon buttons in the top bar (minimal dashboard chrome).
2938
+ .topbar-icon-btn {
2939
+ position: relative;
2940
+ display: inline-flex;
2941
+ align-items: center;
2942
+ justify-content: center;
2943
+ width: 36px;
2944
+ height: 36px;
2945
+ flex: 0 0 auto;
2946
+ border: none;
2947
+ border-radius: 999px;
2948
+ background: transparent;
2949
+ color: var(--color-text-muted);
2950
+ cursor: pointer;
2951
+ transition:
2952
+ background 0.15s ease,
2953
+ color 0.15s ease;
2954
+ }
2955
+ .topbar-icon-btn:hover {
2956
+ background: var(--color-primary-soft);
2957
+ color: var(--color-primary);
2958
+ }
2959
+ .topbar-icon-btn.has-badge::after {
2960
+ content: "";
2961
+ position: absolute;
2962
+ top: 9px;
2963
+ right: 9px;
2964
+ width: 8px;
2965
+ height: 8px;
2966
+ border-radius: 999px;
2967
+ background: var(--color-danger);
2968
+ border: 2px solid var(--color-bg);
2969
+ }
2970
+
2971
+ // ── Spinner / shimmer ──
2972
+ .lx-spinner {
2973
+ width: 18px;
2974
+ height: 18px;
2975
+ border: 2.5px solid var(--color-border);
2976
+ border-top-color: var(--color-primary);
2977
+ border-radius: 999px;
2978
+ animation: lx-spin 0.7s linear infinite;
2979
+ display: inline-block;
2980
+ flex-shrink: 0;
2981
+ }
2982
+ @keyframes lx-spin {
2983
+ to {
2984
+ transform: rotate(360deg);
2985
+ }
2986
+ }
2987
+ // Small (14px) spinner variant.
2988
+ .lx-spinner-sm {
2989
+ width: 14px;
2990
+ height: 14px;
2991
+ border-width: 2px;
2992
+ }
2993
+ // Spinner for the Button loading state.
2994
+ .lx-spin {
2995
+ animation: lx-spin 0.8s linear infinite;
2996
+ }
2997
+
2998
+ // Skeleton shimmer — used ONLY while loading the first data (loading &&
2999
+ // empty), to tell "loading first data" apart from "loaded but empty".
3000
+ .skeleton {
3001
+ position: relative;
3002
+ overflow: hidden;
3003
+ display: block;
3004
+ background: var(--color-bg-muted);
3005
+ border-radius: 6px;
3006
+ }
3007
+ .skeleton::after {
3008
+ content: "";
3009
+ position: absolute;
3010
+ inset: 0;
3011
+ transform: translateX(-100%);
3012
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
3013
+ animation: skeleton-shimmer 1.4s ease-in-out infinite;
3014
+ }
3015
+ @keyframes skeleton-shimmer {
3016
+ 100% {
3017
+ transform: translateX(100%);
3018
+ }
3019
+ }
3020
+ :root[data-theme="dark"] .skeleton::after {
3021
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
3022
+ }
3023
+ @media (prefers-reduced-motion: reduce) {
3024
+ .skeleton::after {
3025
+ animation: none;
3026
+ }
3027
+ }
3028
+
3029
+ // ── Text-scale utilities (lx-text-*) ──
3030
+ .lx-text-xs {
3031
+ font-size: 12px;
3032
+ line-height: 1.4;
3033
+ }
3034
+ .lx-text-sm {
3035
+ font-size: 13.5px;
3036
+ line-height: 1.5;
3037
+ }
3038
+ .lx-text-base {
3039
+ font-size: 15px;
3040
+ line-height: 1.5;
3041
+ }
3042
+ .lx-text-lg {
3043
+ font-size: 17px;
3044
+ line-height: 1.4;
3045
+ }
3046
+ .lx-text-xl {
3047
+ font-size: 20px;
3048
+ line-height: 1.4;
3049
+ }
3050
+ .lx-text-2xl {
3051
+ font-size: 24px;
3052
+ line-height: 1.3;
3053
+ }
3054
+ .lx-text-3xl {
3055
+ font-size: 30px;
3056
+ line-height: 1.2;
3057
+ }
3058
+ .lx-text-4xl {
3059
+ font-size: 36px;
3060
+ line-height: 1.15;
3061
+ }
3062
+
3063
+ /* ── Chip (token / tag) ── */
3064
+ .chip {
3065
+ display: inline-flex;
3066
+ align-items: center;
3067
+ gap: 6px;
3068
+ max-width: 100%;
3069
+ padding: 4px 10px;
3070
+ border: 1px solid var(--color-border);
3071
+ border-radius: 999px;
3072
+ background: var(--color-bg-muted);
3073
+ color: var(--color-text);
3074
+ font-size: 0.8125rem;
3075
+ font-weight: 500;
3076
+ line-height: 1.2;
3077
+ vertical-align: middle;
3078
+ }
3079
+ .chip-sm {
3080
+ gap: 4px;
3081
+ padding: 2px 8px;
3082
+ font-size: 0.75rem;
3083
+ }
3084
+ .chip-label {
3085
+ overflow: hidden;
3086
+ text-overflow: ellipsis;
3087
+ white-space: nowrap;
3088
+ }
3089
+ .chip-ic {
3090
+ display: inline-flex;
3091
+ color: var(--color-text-subtle);
3092
+ }
3093
+ .chip-avatar {
3094
+ display: inline-flex;
3095
+ width: 18px;
3096
+ height: 18px;
3097
+ margin-left: -2px;
3098
+ border-radius: 999px;
3099
+ overflow: hidden;
3100
+ flex: none;
3101
+ }
3102
+ .chip-avatar img {
3103
+ width: 100%;
3104
+ height: 100%;
3105
+ object-fit: cover;
3106
+ }
3107
+ .chip-body {
3108
+ display: inline-flex;
3109
+ align-items: center;
3110
+ gap: inherit;
3111
+ padding: 0;
3112
+ border: 0;
3113
+ background: none;
3114
+ font: inherit;
3115
+ color: inherit;
3116
+ cursor: pointer;
3117
+ }
3118
+ .chip-clickable {
3119
+ cursor: pointer;
3120
+ }
3121
+ .chip-clickable:hover {
3122
+ border-color: var(--color-border-strong);
3123
+ }
3124
+ .chip-remove {
3125
+ display: inline-flex;
3126
+ align-items: center;
3127
+ justify-content: center;
3128
+ margin: 0 -4px 0 2px;
3129
+ padding: 2px;
3130
+ border: 0;
3131
+ border-radius: 999px;
3132
+ background: none;
3133
+ color: var(--color-text-subtle);
3134
+ line-height: 0;
3135
+ cursor: pointer;
3136
+ }
3137
+ .chip-remove:hover {
3138
+ background: color-mix(in srgb, var(--color-text) 8%, transparent);
3139
+ color: var(--color-text);
3140
+ }
3141
+ .chip.is-disabled {
3142
+ opacity: 0.55;
3143
+ pointer-events: none;
3144
+ }
3145
+
3146
+ /* Tones — tinted surface + on-tint text; icon/remove inherit the tone colour. */
3147
+ .chip.primary {
3148
+ border-color: transparent;
3149
+ background: var(--color-primary-soft);
3150
+ color: var(--color-primary);
3151
+ }
3152
+ .chip.success {
3153
+ border-color: transparent;
3154
+ background: var(--color-success-soft);
3155
+ color: var(--color-success-active);
3156
+ }
3157
+ .chip.warning {
3158
+ border-color: transparent;
3159
+ background: var(--color-warning-soft);
3160
+ color: var(--color-warning-active);
3161
+ }
3162
+ .chip.danger {
3163
+ border-color: transparent;
3164
+ background: var(--color-danger-soft);
3165
+ color: var(--color-danger-active);
3166
+ }
3167
+ .chip.info {
3168
+ border-color: transparent;
3169
+ background: var(--color-info-soft);
3170
+ color: var(--color-info-active);
3171
+ }
3172
+ .chip.primary .chip-ic,
3173
+ .chip.success .chip-ic,
3174
+ .chip.warning .chip-ic,
3175
+ .chip.danger .chip-ic,
3176
+ .chip.info .chip-ic,
3177
+ .chip.primary .chip-remove,
3178
+ .chip.success .chip-remove,
3179
+ .chip.warning .chip-remove,
3180
+ .chip.danger .chip-remove,
3181
+ .chip.info .chip-remove {
3182
+ color: currentColor;
3183
+ }
3184
+ .chip.primary .chip-remove:hover,
3185
+ .chip.success .chip-remove:hover,
3186
+ .chip.warning .chip-remove:hover,
3187
+ .chip.danger .chip-remove:hover,
3188
+ .chip.info .chip-remove:hover {
3189
+ background: color-mix(in srgb, currentColor 16%, transparent);
3190
+ color: currentColor;
3191
+ }
3192
+
3193
+ /* ── Autocomplete (input-first combobox; extends .search-select) ── */
3194
+ .search-select-ac {
3195
+ justify-content: flex-start;
3196
+ cursor: text;
3197
+ padding-right: 8px;
3198
+ }
3199
+ .search-select-ac-input {
3200
+ flex: 1 1 auto;
3201
+ min-width: 0;
3202
+ padding: 0;
3203
+ border: 0;
3204
+ background: none;
3205
+ color: inherit;
3206
+ font: inherit;
3207
+ outline: none;
3208
+ }
3209
+ .search-select-ac-input::placeholder {
3210
+ color: var(--color-text-subtle);
3211
+ }
3212
+ .search-select-spinner {
3213
+ flex: none;
3214
+ width: 1rem;
3215
+ height: 1rem;
3216
+ color: var(--color-text-subtle);
3217
+ border-width: 2px;
3218
+ }
3219
+ .search-select-hint {
3220
+ padding: 10px 12px;
3221
+ color: var(--color-text-subtle);
3222
+ font-size: 0.8125rem;
3223
+ }
3224
+
3225
+ /* ── Multi-select (chips in the control; check-list dropdown; extends .search-select) ── */
3226
+ .search-select-multi {
3227
+ align-items: flex-start;
3228
+ gap: 6px;
3229
+ min-height: 40px;
3230
+ height: auto;
3231
+ padding-top: 5px;
3232
+ padding-bottom: 5px;
3233
+ flex-wrap: nowrap;
3234
+ }
3235
+ .ms-chips {
3236
+ display: flex;
3237
+ flex-wrap: wrap;
3238
+ gap: 6px;
3239
+ flex: 1 1 auto;
3240
+ min-width: 0;
3241
+ align-items: center;
3242
+ }
3243
+ .search-select-multi .search-select-caret {
3244
+ align-self: center;
3245
+ }
3246
+ .ms-check {
3247
+ display: inline-flex;
3248
+ align-items: center;
3249
+ justify-content: center;
3250
+ width: 16px;
3251
+ flex: none;
3252
+ color: var(--color-primary);
3253
+ opacity: 0;
3254
+ }
3255
+ .search-select-option[aria-selected='true'] .ms-check {
3256
+ opacity: 1;
3257
+ }
3258
+ .ms-opt-label {
3259
+ min-width: 0;
3260
+ overflow: hidden;
3261
+ text-overflow: ellipsis;
3262
+ }
3263
+ .search-select-multi .search-select-option {
3264
+ display: flex;
3265
+ align-items: center;
3266
+ gap: 8px;
3267
+ }
3268
+
3269
+ /* ── Choice card: image / custom-template variant ── */
3270
+ .struct-card-img-card {
3271
+ flex-direction: column;
3272
+ align-items: stretch;
3273
+ padding: 0;
3274
+ overflow: hidden;
3275
+ }
3276
+ .struct-card-media {
3277
+ display: block;
3278
+ width: 100%;
3279
+ aspect-ratio: 16 / 9;
3280
+ background: var(--color-bg-muted);
3281
+ overflow: hidden;
3282
+ }
3283
+ .struct-card-img {
3284
+ width: 100%;
3285
+ height: 100%;
3286
+ object-fit: cover;
3287
+ display: block;
3288
+ }
3289
+ .struct-card-img-card .struct-card-body {
3290
+ padding: 12px 14px;
3291
+ }
3292
+ .struct-card-img-card .struct-card-check {
3293
+ position: absolute;
3294
+ top: 8px;
3295
+ right: 8px;
3296
+ }