@mp-consulting/homebridge-ui-kit 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/kit.css CHANGED
@@ -1,9 +1,18 @@
1
- /* @mp-consulting/homebridge-ui-kit v1.2.2 */
1
+ /* @mp-consulting/homebridge-ui-kit v1.3.0 */
2
2
 
3
3
  /* ── Design tokens ──────────────────────────────────────────────────────────
4
4
  CSS custom properties used throughout the design system.
5
5
  Override these in your plugin's styles.css to customize per-plugin.
6
- Light values live on :root; dark values follow Bootstrap's data-bs-theme.
6
+ Light values live on :root. Dark values apply under Bootstrap's
7
+ [data-bs-theme="dark"] or under the selector-agnostic .mp-theme-dark class,
8
+ for hosts whose dark mode does not use Bootstrap's attribute (e.g. a
9
+ `body.dark-mode` app): add .mp-theme-dark next to your own class instead of
10
+ copying these values.
11
+
12
+ The kit's rules are deliberately NOT wrapped in a cascade layer:
13
+ unlayered styles beat every layered style regardless of specificity or
14
+ order, so unlayered Bootstrap would then override the kit's Bootstrap
15
+ remaps (primary button, tabs, spinner) and break them.
7
16
  ────────────────────────────────────────────────────────────────────────── */
8
17
 
9
18
  :root {
@@ -19,10 +28,19 @@
19
28
  --mp-status-offline: #ef4444;
20
29
  --mp-status-checking: #94a3b8;
21
30
 
31
+ /* Accessible text and focus colours (plain values, so they hold contrast
32
+ whether or not the host switches Bootstrap's variables) */
33
+ --mp-text-subtle: #5c636a; /* secondary hints; 6.1:1 on white */
34
+ --mp-focus-ring: #4f46e5; /* focus outlines; 6.3:1 on white */
35
+
22
36
  /* Surfaces */
23
37
  --mp-surface: rgba(0, 0, 0, 0.02);
24
38
  --mp-border: rgba(0, 0, 0, 0.1);
25
39
 
40
+ /* Skeleton shimmer */
41
+ --mp-skeleton-base: rgba(0, 0, 0, 0.08);
42
+ --mp-skeleton-shine: rgba(0, 0, 0, 0.03);
43
+
26
44
  /* Shape */
27
45
  --mp-radius: 0.5rem;
28
46
  --mp-shadow-hover: 0 0.5rem 1rem rgba(0, 0, 0, 0.12);
@@ -75,12 +93,42 @@
75
93
  initial-value: 0deg;
76
94
  }
77
95
 
78
- [data-bs-theme="dark"] {
96
+ /* Dark values that do not depend on Bootstrap. */
97
+ [data-bs-theme="dark"],
98
+ .mp-theme-dark {
79
99
  --mp-primary-text: #818cf8;
100
+ --mp-text-subtle: #a3a9b0; /* 6.5:1 on #212529 */
101
+ --mp-focus-ring: #818cf8; /* 5.2:1 on #212529 (the brand indigo is 2.5:1) */
80
102
  --mp-surface: rgba(255, 255, 255, 0.03);
81
103
  --mp-border: rgba(255, 255, 255, 0.08);
82
104
  --mp-shadow-hover: 0 0.5rem 1rem rgba(0, 0, 0, 0.3);
105
+ --mp-skeleton-base: rgba(255, 255, 255, 0.08);
106
+ --mp-skeleton-shine: rgba(255, 255, 255, 0.16);
83
107
  --mp-ai-text: #d8b4fe; /* 9.6:1 on #212529 */
108
+ --mp-ai-glow-opacity: 0.7;
109
+ --mp-ai-edge-glow-opacity: 0.8;
110
+ --mp-ai-diff-add-bg: rgba(34, 197, 94, 0.16);
111
+ --mp-ai-diff-add-text: #bbf7d0;
112
+ --mp-ai-diff-del-bg: rgba(239, 68, 68, 0.18);
113
+ --mp-ai-diff-del-text: #fecaca;
114
+ }
115
+
116
+ /* .mp-theme-dark alone: the host does not switch Bootstrap's variables, so
117
+ the host colours are plain values (Bootstrap's dark palette). */
118
+ .mp-theme-dark {
119
+ --mp-ai-surface: #212529;
120
+ --mp-ai-fg: #dee2e6;
121
+ --mp-ai-fg-muted: rgba(222, 226, 230, 0.75);
122
+ --mp-ai-bg: transparent;
123
+ --mp-ai-border-color: #495057;
124
+ --mp-ai-success-text: #75b798;
125
+ --mp-ai-danger-text: #ea868f;
126
+ }
127
+
128
+ /* Bootstrap's dark theme: follow its variables, so a plugin that customises
129
+ them is respected. Declared after .mp-theme-dark so it wins when an element
130
+ has both. */
131
+ [data-bs-theme="dark"] {
84
132
  --mp-ai-surface: var(--bs-body-bg, #212529);
85
133
  --mp-ai-fg: var(--bs-body-color, #dee2e6);
86
134
  --mp-ai-fg-muted: var(--bs-secondary-color, rgba(222, 226, 230, 0.75));
@@ -88,12 +136,6 @@
88
136
  --mp-ai-border-color: var(--bs-border-color, #495057);
89
137
  --mp-ai-success-text: var(--bs-success-text-emphasis, #75b798);
90
138
  --mp-ai-danger-text: var(--bs-danger-text-emphasis, #ea868f);
91
- --mp-ai-glow-opacity: 0.7;
92
- --mp-ai-edge-glow-opacity: 0.8;
93
- --mp-ai-diff-add-bg: rgba(34, 197, 94, 0.16);
94
- --mp-ai-diff-add-text: #bbf7d0;
95
- --mp-ai-diff-del-bg: rgba(239, 68, 68, 0.18);
96
- --mp-ai-diff-del-text: #fecaca;
97
139
  }
98
140
 
99
141
  /* ── Bootstrap 5 overrides ──────────────────────────────────────────────────
@@ -119,6 +161,12 @@
119
161
  color: var(--mp-primary) !important;
120
162
  }
121
163
 
164
+ /* Outline secondary button: Bootstrap's #6c757d text is 4.49:1 on a light
165
+ surface and 3:1 on the dark background; use the contrast-safe token. */
166
+ .btn-outline-secondary {
167
+ --bs-btn-color: var(--mp-text-subtle);
168
+ }
169
+
122
170
  /* ── Components ─────────────────────────────────────────────────────────────
123
171
  Reusable UI components used across all Homebridge plugins.
124
172
  ────────────────────────────────────────────────────────────────────────── */
@@ -162,6 +210,12 @@
162
210
  vertical-align: middle;
163
211
  }
164
212
 
213
+ /* Spacing between a status dot and its badge label (logical, so RTL works
214
+ without Bootstrap's RTL build). */
215
+ .badge > .mp-status {
216
+ margin-inline-end: 0.25rem;
217
+ }
218
+
165
219
  .mp-status-online {
166
220
  background-color: var(--mp-status-online);
167
221
  box-shadow: 0 0 5px var(--mp-status-online-glow);
@@ -194,10 +248,152 @@
194
248
  }
195
249
 
196
250
  .mp-device-card:focus-visible {
197
- outline: 2px solid var(--mp-primary);
251
+ outline: 2px solid var(--mp-focus-ring);
252
+ outline-offset: 2px;
253
+ }
254
+
255
+ /* Default card content rendered by MpKit.DeviceList */
256
+ .mp-device-card-body {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 0.75rem;
260
+ }
261
+
262
+ .mp-device-card-icon {
263
+ flex: none;
264
+ font-size: 1.5rem;
265
+ line-height: 1;
266
+ color: var(--mp-primary-text);
267
+ }
268
+
269
+ .mp-device-card-text {
270
+ flex: 1;
271
+ min-width: 0;
272
+ }
273
+
274
+ .mp-device-card-name {
275
+ font-weight: 600;
276
+ overflow: hidden;
277
+ text-overflow: ellipsis;
278
+ white-space: nowrap;
279
+ }
280
+
281
+ .mp-device-card-subtitle {
282
+ font-size: 0.8125rem;
283
+ color: var(--bs-secondary-color);
284
+ overflow: hidden;
285
+ text-overflow: ellipsis;
286
+ white-space: nowrap;
287
+ }
288
+
289
+ /* ── Device list ────────────────────────────────────────── */
290
+
291
+ .mp-device-list-search {
292
+ margin-bottom: 0.75rem;
293
+ }
294
+
295
+ .mp-device-list-items {
296
+ display: grid;
297
+ gap: 0.5rem;
298
+ }
299
+
300
+ .mp-device-card-skeleton {
301
+ cursor: default;
302
+ }
303
+
304
+ .mp-device-card-skeleton:hover {
305
+ transform: none;
306
+ box-shadow: none !important;
307
+ }
308
+
309
+ /* ── Skeleton shimmer ───────────────────────────────────── */
310
+
311
+ .mp-skeleton {
312
+ display: block;
313
+ border-radius: 0.25rem;
314
+ background: linear-gradient(90deg, var(--mp-skeleton-base) 25%, var(--mp-skeleton-shine) 50%, var(--mp-skeleton-base) 75%);
315
+ background-size: 200% 100%;
316
+ animation: mp-shimmer 1.4s ease-in-out infinite;
317
+ }
318
+
319
+ .mp-skeleton-text {
320
+ height: 0.75rem;
321
+ width: 100%;
322
+ }
323
+
324
+ .mp-skeleton-text + .mp-skeleton-text {
325
+ margin-top: 0.5rem;
326
+ }
327
+
328
+ .mp-skeleton-circle {
329
+ flex: none;
330
+ width: 2rem;
331
+ height: 2rem;
332
+ border-radius: 50%;
333
+ }
334
+
335
+ .mp-skeleton-group {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: 0.75rem;
339
+ }
340
+
341
+ .mp-skeleton-lines {
342
+ flex: 1;
343
+ min-width: 0;
344
+ }
345
+
346
+ @keyframes mp-shimmer {
347
+ from { background-position: 200% 0; }
348
+ to { background-position: -200% 0; }
349
+ }
350
+
351
+ /* RTL: shimmer travels the other way */
352
+ [dir="rtl"] .mp-skeleton {
353
+ animation-direction: reverse;
354
+ }
355
+
356
+ /* ── Log viewer ─────────────────────────────────────────── */
357
+
358
+ .mp-log {
359
+ position: relative;
360
+ }
361
+
362
+ .mp-log-body {
363
+ direction: ltr;
364
+ unicode-bidi: isolate;
365
+ max-height: var(--mp-log-height, 20rem);
366
+ overflow: auto;
367
+ padding: 0.5rem 0.75rem;
368
+ border: 1px solid var(--mp-border);
369
+ border-radius: var(--mp-radius);
370
+ background: var(--mp-surface);
371
+ font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
372
+ font-size: 0.8125rem;
373
+ line-height: 1.5;
374
+ }
375
+
376
+ .mp-log-body:focus-visible {
377
+ outline: 2px solid var(--mp-focus-ring);
198
378
  outline-offset: 2px;
199
379
  }
200
380
 
381
+ .mp-log-line {
382
+ min-height: 1.5em;
383
+ white-space: pre;
384
+ }
385
+
386
+ .mp-log-wrap .mp-log-line {
387
+ white-space: pre-wrap;
388
+ overflow-wrap: anywhere;
389
+ }
390
+
391
+ .mp-log-jump {
392
+ position: absolute;
393
+ inset-block-end: 0.75rem;
394
+ inset-inline-end: 1.25rem;
395
+ }
396
+
201
397
  /* ── Settings surface card ──────────────────────────────── */
202
398
 
203
399
  .mp-settings-card {
@@ -231,11 +427,11 @@
231
427
  margin: 0 0 0.25rem;
232
428
  }
233
429
 
430
+ /* A token instead of opacity: the old 0.7 opacity dropped it to 3.3:1. */
234
431
  .mp-empty-state-hint {
235
432
  font-size: 0.8125rem;
236
- color: var(--bs-secondary-color);
433
+ color: var(--mp-text-subtle);
237
434
  margin: 0;
238
- opacity: 0.7;
239
435
  }
240
436
 
241
437
  /* ── Loading state ──────────────────────────────────────── */
@@ -270,12 +466,19 @@
270
466
  border-color: transparent;
271
467
  }
272
468
 
273
- .mp-tabs .nav-link.active {
469
+ .mp-tabs .nav-link.active,
470
+ .mp-tabs .nav-link[aria-selected="true"] {
274
471
  color: var(--mp-primary-text);
275
472
  background: transparent;
276
473
  border-bottom-color: var(--mp-primary);
277
474
  }
278
475
 
476
+ .mp-tabs .nav-link:focus-visible {
477
+ outline: 2px solid var(--mp-focus-ring);
478
+ outline-offset: -2px;
479
+ box-shadow: none;
480
+ }
481
+
279
482
  /* ── Metadata label ─────────────────────────────────────── */
280
483
 
281
484
  .mp-label {
@@ -295,6 +498,26 @@
295
498
  display: block;
296
499
  }
297
500
 
501
+ /* View.show() moves focus to the view's heading (tabindex="-1"); a
502
+ programmatic focus target needs no ring. */
503
+ .mp-focus-target:focus:not(:focus-visible) {
504
+ outline: none;
505
+ }
506
+
507
+ /* ── Screen-reader only ─────────────────────────────────── */
508
+
509
+ .mp-sr-only {
510
+ position: absolute !important;
511
+ width: 1px !important;
512
+ height: 1px !important;
513
+ padding: 0 !important;
514
+ margin: -1px !important;
515
+ overflow: hidden !important;
516
+ clip: rect(0, 0, 0, 0) !important;
517
+ white-space: nowrap !important;
518
+ border: 0 !important;
519
+ }
520
+
298
521
  /* ── Support footer ─────────────────────────────────────── */
299
522
 
300
523
  .mp-footer {
@@ -335,10 +558,450 @@
335
558
  user-select: none;
336
559
  }
337
560
 
561
+ /* ── Forms ──────────────────────────────────────────────── */
562
+
563
+ /* Bootstrap's invalid colour (#dc3545) is 4.3:1 on light surfaces. */
564
+ .mp-field .invalid-feedback {
565
+ color: var(--bs-danger-text-emphasis, #b02a37);
566
+ }
567
+
568
+ .mp-required {
569
+ color: var(--bs-danger-text-emphasis, #b02a37);
570
+ }
571
+
572
+ .mp-secret-input {
573
+ font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
574
+ }
575
+
576
+ .mp-copy-btn.is-copied {
577
+ color: var(--bs-success-text-emphasis, #0a3622);
578
+ border-color: var(--bs-success, #198754);
579
+ }
580
+
581
+ /* Sticky Save bar shown by MpKit.Form.track() while there are unsaved changes */
582
+ .mp-savebar {
583
+ position: sticky;
584
+ inset-block-end: 0;
585
+ z-index: 1020;
586
+ display: flex;
587
+ flex-wrap: wrap;
588
+ align-items: center;
589
+ justify-content: space-between;
590
+ gap: 0.5rem 1rem;
591
+ margin-top: 1rem;
592
+ padding: 0.625rem 0.875rem;
593
+ border: 1px solid var(--mp-border);
594
+ border-radius: var(--mp-radius);
595
+ background: var(--bs-body-bg, #fff);
596
+ box-shadow: 0 -0.25rem 1rem rgba(0, 0, 0, 0.08);
597
+ font-size: 0.875rem;
598
+ }
599
+
600
+ .mp-savebar[hidden] {
601
+ display: none;
602
+ }
603
+
604
+ .mp-savebar-actions {
605
+ display: inline-flex;
606
+ gap: 0.5rem;
607
+ margin-inline-start: auto;
608
+ }
609
+
610
+ /* ── HomeKit setup code ─────────────────────────────────── */
611
+
612
+ .mp-pin {
613
+ display: inline-flex;
614
+ flex-wrap: wrap;
615
+ align-items: center;
616
+ gap: 1rem;
617
+ margin: 0;
618
+ }
619
+
620
+ .mp-pin-label-box {
621
+ display: inline-flex;
622
+ flex-direction: column;
623
+ align-items: center;
624
+ gap: 0.25rem;
625
+ padding: 0.5rem 1rem 0.625rem;
626
+ border: 2px solid currentColor;
627
+ border-radius: 0.5rem;
628
+ color: var(--bs-body-color, #212529);
629
+ }
630
+
631
+ .mp-pin-label {
632
+ font-size: 0.6875rem;
633
+ font-weight: 600;
634
+ letter-spacing: 0.04em;
635
+ text-transform: uppercase;
636
+ color: var(--bs-secondary-color, #6c757d);
637
+ }
638
+
639
+ .mp-pin-code {
640
+ direction: ltr;
641
+ font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
642
+ font-size: 1.75rem;
643
+ font-weight: 600;
644
+ letter-spacing: 0.08em;
645
+ line-height: 1.1;
646
+ white-space: nowrap;
647
+ }
648
+
649
+ .mp-pin-qr {
650
+ display: block;
651
+ width: var(--mp-pin-qr-size, 8rem);
652
+ height: var(--mp-pin-qr-size, 8rem);
653
+ padding: 0.375rem;
654
+ border-radius: 0.375rem;
655
+ background: #fff; /* QR codes need a light quiet zone, also in dark mode */
656
+ }
657
+
658
+ .mp-pin-qr-slot:empty {
659
+ display: none;
660
+ }
661
+
662
+ .mp-pin-actions {
663
+ flex-basis: 100%;
664
+ }
665
+
666
+ /* ── Sign-in (OAuth / device code) card ─────────────────── */
667
+
668
+ .mp-auth-card {
669
+ border-color: var(--mp-border);
670
+ }
671
+
672
+ .mp-auth-title {
673
+ margin: 0 0 0.5rem;
674
+ font-size: 1.0625rem;
675
+ font-weight: 600;
676
+ }
677
+
678
+ .mp-auth-instructions {
679
+ color: var(--bs-secondary-color);
680
+ font-size: 0.875rem;
681
+ }
682
+
683
+ .mp-auth-steps {
684
+ display: grid;
685
+ gap: 0.75rem;
686
+ margin: 0 0 1rem;
687
+ padding-inline-start: 1.25rem;
688
+ }
689
+
690
+ .mp-auth-steps > li {
691
+ padding-inline-start: 0.25rem;
692
+ }
693
+
694
+ .mp-auth-step-text {
695
+ display: block;
696
+ margin-bottom: 0.375rem;
697
+ font-size: 0.875rem;
698
+ }
699
+
700
+ .mp-auth-code-row {
701
+ display: inline-flex;
702
+ flex-wrap: wrap;
703
+ align-items: center;
704
+ gap: 0.5rem;
705
+ }
706
+
707
+ .mp-auth-code {
708
+ direction: ltr;
709
+ padding: 0.25rem 0.75rem;
710
+ border: 1px dashed var(--mp-border);
711
+ border-radius: 0.375rem;
712
+ background: var(--mp-surface);
713
+ color: var(--bs-body-color, #212529);
714
+ font-size: 1.375rem;
715
+ font-weight: 600;
716
+ letter-spacing: 0.12em;
717
+ }
718
+
719
+ .mp-auth-status {
720
+ display: flex;
721
+ align-items: center;
722
+ gap: 0.5rem;
723
+ margin: 0;
724
+ font-size: 0.875rem;
725
+ color: var(--bs-secondary-color);
726
+ }
727
+
728
+ .mp-auth-status-icon {
729
+ display: none;
730
+ flex: none;
731
+ width: 1rem;
732
+ height: 1rem;
733
+ border-radius: 50%;
734
+ }
735
+
736
+ .mp-auth-card.is-polling .mp-auth-status-icon {
737
+ display: inline-block;
738
+ border: 2px solid currentColor;
739
+ border-inline-end-color: transparent;
740
+ animation: mp-spin 0.75s linear infinite;
741
+ }
742
+
743
+ .mp-auth-card.is-success .mp-auth-status {
744
+ color: var(--bs-success-text-emphasis, #0a3622);
745
+ }
746
+
747
+ .mp-auth-card.is-success .mp-auth-status-icon {
748
+ display: inline-block;
749
+ background: var(--bs-success, #198754);
750
+ }
751
+
752
+ .mp-auth-card.is-error .mp-auth-status,
753
+ .mp-auth-card.is-expired .mp-auth-status {
754
+ color: var(--bs-danger-text-emphasis, #b02a37);
755
+ }
756
+
757
+ .mp-auth-retry {
758
+ margin-top: 0.75rem;
759
+ }
760
+
761
+ @keyframes mp-spin {
762
+ to { transform: rotate(360deg); }
763
+ }
764
+
765
+ /* ── Steps (wizard progress) ────────────────────────────── */
766
+
767
+ .mp-steps {
768
+ display: flex;
769
+ gap: 0.5rem;
770
+ margin: 0 0 1.5rem;
771
+ padding: 0;
772
+ list-style: none;
773
+ counter-reset: none;
774
+ }
775
+
776
+ .mp-step {
777
+ position: relative;
778
+ display: flex;
779
+ flex: 1 1 0;
780
+ flex-direction: column;
781
+ align-items: center;
782
+ gap: 0.375rem;
783
+ min-width: 0;
784
+ color: var(--bs-secondary-color);
785
+ font-size: 0.8125rem;
786
+ text-align: center;
787
+ }
788
+
789
+ /* Connector line to the previous step */
790
+ .mp-step + .mp-step::before {
791
+ content: '';
792
+ position: absolute;
793
+ inset-block-start: 0.875rem;
794
+ inset-inline-end: calc(50% + 1.125rem);
795
+ width: calc(100% - 2.25rem + 0.5rem);
796
+ height: 2px;
797
+ background: var(--mp-border);
798
+ }
799
+
800
+ .mp-step.is-complete::before,
801
+ .mp-step.is-current::before {
802
+ background: var(--mp-primary);
803
+ }
804
+
805
+ .mp-step-marker {
806
+ display: inline-flex;
807
+ align-items: center;
808
+ justify-content: center;
809
+ width: 1.75rem;
810
+ height: 1.75rem;
811
+ border: 2px solid var(--mp-border);
812
+ border-radius: 50%;
813
+ background: var(--bs-body-bg, #fff);
814
+ font-weight: 600;
815
+ }
816
+
817
+ .mp-step.is-current {
818
+ color: var(--mp-primary-text);
819
+ font-weight: 600;
820
+ }
821
+
822
+ .mp-step.is-current .mp-step-marker {
823
+ border-color: var(--mp-primary);
824
+ color: var(--mp-primary-text);
825
+ }
826
+
827
+ .mp-step.is-complete .mp-step-marker {
828
+ border-color: var(--mp-primary);
829
+ background: var(--mp-primary);
830
+ color: #fff;
831
+ }
832
+
833
+ .mp-step-title {
834
+ max-width: 100%;
835
+ overflow-wrap: anywhere;
836
+ }
837
+
838
+ /* Narrow: keep markers, hide titles except the current one */
839
+ @media (max-width: 30rem) {
840
+ .mp-step:not(.is-current) .mp-step-title {
841
+ position: absolute;
842
+ width: 1px;
843
+ height: 1px;
844
+ overflow: hidden;
845
+ clip: rect(0, 0, 0, 0);
846
+ white-space: nowrap;
847
+ }
848
+ }
849
+
850
+ /* ── Toasts (fallback outside Homebridge) ───────────────── */
851
+
852
+ .mp-toast-container {
853
+ position: fixed;
854
+ inset-block-end: 1rem;
855
+ inset-inline-end: 1rem;
856
+ z-index: 1095;
857
+ display: flex;
858
+ flex-direction: column;
859
+ gap: 0.5rem;
860
+ width: min(22rem, calc(100vw - 2rem));
861
+ pointer-events: none;
862
+ }
863
+
864
+ .mp-toast {
865
+ display: flex;
866
+ align-items: flex-start;
867
+ gap: 0.625rem;
868
+ padding: 0.75rem 0.875rem;
869
+ border: 1px solid var(--bs-border-color, #dee2e6);
870
+ border-inline-start: 4px solid var(--mp-toast-accent, var(--mp-primary));
871
+ border-radius: var(--mp-radius);
872
+ background: var(--bs-body-bg, #fff);
873
+ color: var(--bs-body-color, #212529);
874
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
875
+ font-size: 0.875rem;
876
+ pointer-events: auto;
877
+ animation: mp-toast-in 0.2s ease-out;
878
+ }
879
+
880
+ .mp-toast-success { --mp-toast-accent: var(--bs-success, #198754); }
881
+ .mp-toast-error { --mp-toast-accent: var(--bs-danger, #dc3545); }
882
+ .mp-toast-warning { --mp-toast-accent: var(--bs-warning, #ffc107); }
883
+ .mp-toast-info { --mp-toast-accent: var(--mp-primary); }
884
+
885
+ .mp-toast-icon {
886
+ display: inline-flex;
887
+ align-items: center;
888
+ justify-content: center;
889
+ flex: none;
890
+ width: 1.25rem;
891
+ height: 1.25rem;
892
+ border-radius: 50%;
893
+ background: var(--mp-toast-accent);
894
+ color: #fff;
895
+ font-size: 0.75rem;
896
+ font-weight: 700;
897
+ line-height: 1;
898
+ }
899
+
900
+ .mp-toast-warning .mp-toast-icon {
901
+ color: #000;
902
+ }
903
+
904
+ .mp-toast-body {
905
+ flex: 1;
906
+ min-width: 0;
907
+ overflow-wrap: anywhere;
908
+ }
909
+
910
+ .mp-toast-title {
911
+ margin: 0 0 0.125rem;
912
+ font-weight: 600;
913
+ }
914
+
915
+ .mp-toast-message {
916
+ margin: 0;
917
+ }
918
+
919
+ .mp-toast-close,
920
+ .mp-icon-button {
921
+ flex: none;
922
+ padding: 0 0.25rem;
923
+ border: 0;
924
+ border-radius: 0.25rem;
925
+ background: transparent;
926
+ color: var(--bs-secondary-color, #6c757d);
927
+ font-size: 1.25rem;
928
+ line-height: 1;
929
+ cursor: pointer;
930
+ }
931
+
932
+ .mp-toast-close:hover {
933
+ color: var(--bs-body-color, #212529);
934
+ }
935
+
936
+ .mp-toast-close:focus-visible,
937
+ .mp-icon-button:focus-visible {
938
+ outline: 2px solid var(--mp-focus-ring);
939
+ outline-offset: 1px;
940
+ }
941
+
942
+ @keyframes mp-toast-in {
943
+ from { opacity: 0; transform: translateY(0.5rem); }
944
+ }
945
+
946
+ /* ── Confirm dialog ─────────────────────────────────────── */
947
+
948
+ .mp-dialog-backdrop {
949
+ position: fixed;
950
+ inset: 0;
951
+ z-index: 1100;
952
+ display: flex;
953
+ align-items: center;
954
+ justify-content: center;
955
+ padding: 1rem;
956
+ background: rgba(0, 0, 0, 0.5);
957
+ animation: mp-fade-in 0.15s ease-out;
958
+ }
959
+
960
+ .mp-dialog {
961
+ width: min(28rem, 100%);
962
+ padding: 1.25rem;
963
+ border: 1px solid var(--bs-border-color, #dee2e6);
964
+ border-radius: var(--mp-radius);
965
+ background: var(--bs-body-bg, #fff);
966
+ color: var(--bs-body-color, #212529);
967
+ box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.3);
968
+ }
969
+
970
+ .mp-dialog-title {
971
+ margin: 0 0 0.5rem;
972
+ font-size: 1.125rem;
973
+ font-weight: 600;
974
+ }
975
+
976
+ .mp-dialog-message {
977
+ margin: 0;
978
+ color: var(--bs-secondary-color, #6c757d);
979
+ }
980
+
981
+ .mp-dialog-actions {
982
+ display: flex;
983
+ flex-wrap: wrap;
984
+ justify-content: flex-end;
985
+ gap: 0.5rem;
986
+ margin-top: 1.25rem;
987
+ }
988
+
989
+ .mp-dialog-open {
990
+ overflow: hidden;
991
+ }
992
+
993
+ @keyframes mp-fade-in {
994
+ from { opacity: 0; }
995
+ }
996
+
338
997
  /* ── Reduced motion ─────────────────────────────────────── */
339
998
 
340
999
  @media (prefers-reduced-motion: reduce) {
341
- .mp-status-checking {
1000
+ .mp-skeleton,
1001
+ .mp-auth-card.is-polling .mp-auth-status-icon,
1002
+ .mp-status-checking,
1003
+ .mp-toast,
1004
+ .mp-dialog-backdrop {
342
1005
  animation: none;
343
1006
  }
344
1007
 
@@ -629,7 +1292,7 @@
629
1292
  display: inline-block;
630
1293
  width: 0.5em;
631
1294
  height: 1.1em;
632
- margin-left: 2px;
1295
+ margin-inline-start: 2px;
633
1296
  vertical-align: text-bottom;
634
1297
  border-radius: 1px;
635
1298
  background: var(--mp-ai-text);
@@ -655,7 +1318,7 @@
655
1318
 
656
1319
  .mp-ai-prose ul,
657
1320
  .mp-ai-prose ol {
658
- padding-left: 1.25rem;
1321
+ padding-inline-start: 1.25rem;
659
1322
  }
660
1323
 
661
1324
  .mp-ai-prose code {
@@ -668,7 +1331,10 @@
668
1331
  font-size: 0.85em;
669
1332
  }
670
1333
 
1334
+ /* Code reads left to right even on an RTL page. */
671
1335
  .mp-ai-prose pre {
1336
+ direction: ltr;
1337
+ unicode-bidi: isolate;
672
1338
  padding: 0.625rem 0.75rem;
673
1339
  overflow-x: auto;
674
1340
  border-radius: 0.375rem;
@@ -739,14 +1405,14 @@
739
1405
  }
740
1406
 
741
1407
  .is-user > .mp-ai-chat-bubble {
742
- border-bottom-right-radius: 0.25rem;
1408
+ border-end-end-radius: 0.25rem;
743
1409
  background: var(--mp-primary, #4f46e5);
744
1410
  color: #fff;
745
1411
  }
746
1412
 
747
1413
  .is-assistant > .mp-ai-chat-bubble {
748
1414
  border: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
749
- border-bottom-left-radius: 0.25rem;
1415
+ border-end-start-radius: 0.25rem;
750
1416
  background: var(--mp-surface, rgba(0, 0, 0, 0.02));
751
1417
  }
752
1418
 
@@ -816,7 +1482,7 @@
816
1482
  .mp-ai-diff-stats {
817
1483
  flex: none;
818
1484
  display: inline-flex;
819
- margin-left: auto;
1485
+ margin-inline-start: auto;
820
1486
  gap: 0.5rem;
821
1487
  font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
822
1488
  }
@@ -830,6 +1496,8 @@
830
1496
  }
831
1497
 
832
1498
  .mp-ai-diff-body {
1499
+ direction: ltr;
1500
+ unicode-bidi: isolate;
833
1501
  max-height: 24rem;
834
1502
  margin: 0;
835
1503
  padding: 0.375rem 0;
@@ -842,7 +1510,7 @@
842
1510
  .mp-ai-diff-line {
843
1511
  display: block;
844
1512
  min-width: max-content;
845
- padding-right: 0.75rem;
1513
+ padding-inline-end: 0.75rem;
846
1514
  white-space: pre;
847
1515
  }
848
1516
 
@@ -879,7 +1547,7 @@
879
1547
  }
880
1548
 
881
1549
  .mp-ai-diff-status {
882
- margin-right: auto;
1550
+ margin-inline-end: auto;
883
1551
  color: var(--mp-ai-fg-muted);
884
1552
  font-size: 0.8125rem;
885
1553
  }