@frontpanel/css 0.2.0 → 0.4.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/tailwind.css CHANGED
@@ -86,6 +86,19 @@
86
86
  opacity: 1;
87
87
  }
88
88
  }
89
+
90
+ /* A menu dropping open from its button: a short slide down as it fades in. */
91
+ @keyframes fp-menu-drop {
92
+ from {
93
+ opacity: 0;
94
+ translate: 0 -4px;
95
+ }
96
+
97
+ to {
98
+ opacity: 1;
99
+ translate: 0 0;
100
+ }
101
+ }
89
102
  }
90
103
 
91
104
  /** Redraws an LCD line left to right in four steps, as when its value changes. */
@@ -150,6 +163,11 @@
150
163
  overflow: clip;
151
164
  border-radius: 26px;
152
165
  }
166
+
167
+ /* Forced colours drop the chrome: an outline keeps the device's edge. */
168
+ @media (forced-colors: active) {
169
+ outline: 1px solid CanvasText;
170
+ }
153
171
  }
154
172
 
155
173
  /** Molded body plastic the modules sit on. */
@@ -254,6 +272,35 @@
254
272
  &:disabled {
255
273
  opacity: 0.45;
256
274
  }
275
+
276
+ /*
277
+ * Forced colours drop the chrome and its shadows: an outline keeps the button's edge, the system's
278
+ * selected colours show it pressed, and GrayText, not a fade, shows it disabled.
279
+ */
280
+ @media (forced-colors: active) {
281
+ outline: 1px solid ButtonText;
282
+ outline-offset: -1px;
283
+
284
+ &[aria-pressed="true"],
285
+ &[aria-expanded="true"],
286
+ &[aria-current="page"] {
287
+ forced-color-adjust: none;
288
+ background: SelectedItem;
289
+ box-shadow: none;
290
+ color: SelectedItemText;
291
+
292
+ /* Opted out, its focus ring would keep the skin's colour: the system's text colour shows on any ground. */
293
+ &:focus-visible {
294
+ outline-color: CanvasText;
295
+ }
296
+ }
297
+
298
+ &:disabled {
299
+ opacity: 1;
300
+ color: GrayText;
301
+ outline-color: GrayText;
302
+ }
303
+ }
257
304
  }
258
305
 
259
306
  /**
@@ -317,6 +364,46 @@
317
364
  &:disabled {
318
365
  opacity: 0.45;
319
366
  }
367
+
368
+ /* Forced colours drop the gel and its shadows: an outline keeps its edge, and GrayText, not a fade, shows it disabled. */
369
+ @media (forced-colors: active) {
370
+ outline: 1px solid ButtonText;
371
+ outline-offset: -1px;
372
+
373
+ &:disabled {
374
+ opacity: 1;
375
+ color: GrayText;
376
+ outline-color: GrayText;
377
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Chrome buttons joined side by side into one bezel, as on a transport row: fp-chrome-btn children
383
+ * share their seams, and only the outer corners stay round. Give it `role="group"` and a name.
384
+ */
385
+ @utility fp-button-group {
386
+ display: inline-flex;
387
+ align-items: stretch;
388
+
389
+ /* The 1 px edge round each button falls in the gap, so neighbours share one seam. */
390
+ gap: 1px;
391
+
392
+ & > .fp-chrome-btn:not(:first-child) {
393
+ border-start-start-radius: 0;
394
+ border-end-start-radius: 0;
395
+ }
396
+
397
+ & > .fp-chrome-btn:not(:last-child) {
398
+ border-start-end-radius: 0;
399
+ border-end-end-radius: 0;
400
+ }
401
+
402
+ /* A focused button's ring goes over its neighbours, not under them. */
403
+ & > .fp-chrome-btn:focus-visible {
404
+ position: relative;
405
+ z-index: 1;
406
+ }
320
407
  }
321
408
 
322
409
  /**
@@ -389,6 +476,34 @@
389
476
  &:disabled {
390
477
  opacity: 0.5;
391
478
  }
479
+
480
+ /*
481
+ * Forced colours drop the plastic and its ring: an outline keeps the cap's edge, the system's
482
+ * selected colours show it selected, and GrayText, not a fade, shows it disabled.
483
+ */
484
+ @media (forced-colors: active) {
485
+ outline: 1px solid ButtonText;
486
+ outline-offset: -1px;
487
+
488
+ &[aria-pressed="true"],
489
+ &[aria-checked="true"] {
490
+ forced-color-adjust: none;
491
+ background: SelectedItem;
492
+ box-shadow: none;
493
+ color: SelectedItemText;
494
+
495
+ /* Opted out, its focus ring would keep the skin's colour: the system's text colour shows on any ground. */
496
+ &:focus-visible {
497
+ outline-color: CanvasText;
498
+ }
499
+ }
500
+
501
+ &:disabled {
502
+ opacity: 1;
503
+ color: GrayText;
504
+ outline-color: GrayText;
505
+ }
506
+ }
392
507
  }
393
508
 
394
509
  /** Blue plastic for an fp-cap, with its own label colour. */
@@ -416,14 +531,21 @@
416
531
  }
417
532
 
418
533
  /**
419
- * A control with its silkscreen label under it: a knob, fader or meter and an fp-silk. A length
420
- * set here, such as `--fp-fader-length`, reaches the control inside.
534
+ * A control with its silkscreen label under it: a knob, fader, meter or switch and then an fp-silk.
535
+ * With `data-label-placement="start"` the label sits level with the control, before it in the
536
+ * line. A length set here, such as `--fp-fader-length`, reaches the control inside.
421
537
  */
422
538
  @utility fp-unit {
423
539
  display: inline-flex;
424
540
  flex-direction: column;
425
541
  align-items: center;
426
542
  gap: 0.375rem;
543
+
544
+ /* Reversed, so the markup stays control then label either way. */
545
+ &[data-label-placement="start"] {
546
+ flex-direction: row-reverse;
547
+ gap: 0.5rem;
548
+ }
427
549
  }
428
550
 
429
551
  /**
@@ -482,6 +604,15 @@
482
604
  opacity: 0.45;
483
605
  cursor: not-allowed;
484
606
  }
607
+
608
+ /* Forced colours drop the knurling, the dome and their shadows: outlines draw the skirt and the cap. */
609
+ @media (forced-colors: active) {
610
+ outline: 1px solid CanvasText;
611
+
612
+ &::before {
613
+ outline: 1px solid CanvasText;
614
+ }
615
+ }
485
616
  }
486
617
 
487
618
  /** The pointer line on a knob's cap. Turn it with `rotate`: -135deg at the minimum, 135deg at the maximum. */
@@ -501,6 +632,14 @@
501
632
  border-radius: 2px;
502
633
  background-color: var(--fp-color-ink);
503
634
  }
635
+
636
+ /* Forced colours would paint the line in the page colour, on the page colour. */
637
+ @media (forced-colors: active) {
638
+ &::after {
639
+ forced-color-adjust: none;
640
+ background-color: CanvasText;
641
+ }
642
+ }
504
643
  }
505
644
 
506
645
  /**
@@ -557,6 +696,13 @@
557
696
  height: 6px;
558
697
  translate: 0 -50%;
559
698
  }
699
+
700
+ /* Forced colours: the slot as a solid line, so the cap's travel shows. */
701
+ @media (forced-colors: active) {
702
+ forced-color-adjust: none;
703
+ background-color: CanvasText;
704
+ box-shadow: none;
705
+ }
560
706
  }
561
707
 
562
708
  /**
@@ -619,6 +765,344 @@
619
765
  outline-offset: 2px;
620
766
  filter: drop-shadow(0 0 3px var(--fp-color-focus));
621
767
  }
768
+
769
+ /* Forced colours: a cap in the page colour hides the slot under it; an outline and the grip draw it. */
770
+ @media (forced-colors: active) {
771
+ background-color: Canvas;
772
+ outline: 1px solid CanvasText;
773
+ outline-offset: -1px;
774
+
775
+ &::after {
776
+ forced-color-adjust: none;
777
+ background-color: CanvasText;
778
+ }
779
+ }
780
+ }
781
+
782
+ /**
783
+ * Illuminated rocker switch: a lamp-lit paddle in a dark bezel, marked I at the top and O at the
784
+ * bottom. Off, the O half is pressed in; on, the I half is, and the lamp behind the lens lights.
785
+ * Put it on a button with `role="switch"` and `aria-checked`, holding an fp-switch-rocker. The
786
+ * lamp is red; colour it with fp-led-green or fp-led-amber. 44 x 56 px, 36 x 46 px with a mouse,
787
+ * trackpad or pen. With `data-orientation="horizontal"` it lies on its side, 56 x 44 px (46 x 36),
788
+ * O on the left and I on the right. After an illuminated rocker pen by ykadosh (CodePen, MIT), rebuilt on the kit's
789
+ * tokens.
790
+ */
791
+ @utility fp-switch {
792
+ --fp-switch-lamp: var(--fp-led, var(--fp-color-led-red));
793
+ position: relative;
794
+ display: inline-block;
795
+ flex-shrink: 0;
796
+ width: 2.75rem;
797
+ height: 3.5rem;
798
+ padding: 5px;
799
+ border: 0;
800
+ border-radius: 4px;
801
+ background-color: color-mix(in srgb, var(--fp-color-housing) 40%, black);
802
+ box-shadow:
803
+ 0 0 0 1px var(--fp-color-edge),
804
+ inset 1px 1px 0 rgb(255 255 255 / 0.25),
805
+ inset 0 0 1px 3px var(--fp-color-housing),
806
+ inset 0 0 1px 5px color-mix(in srgb, var(--fp-color-housing) 40%, black),
807
+ 1px 2px 0 var(--fp-color-cast);
808
+ perspective: 216px;
809
+ cursor: pointer;
810
+
811
+ @media (pointer: fine) {
812
+ width: 2.25rem;
813
+ height: 2.875rem;
814
+ padding: 4px;
815
+ }
816
+
817
+ /* On: the I half goes in, the shine comes up, the shading goes, and the lamp lights as it lands. */
818
+ &[aria-checked="true"] .fp-switch-rocker {
819
+ transform: translateZ(6px) rotateX(25deg);
820
+ box-shadow: 0 -3px 6px var(--fp-switch-lamp);
821
+ }
822
+
823
+ &[aria-checked="true"] .fp-switch-face::before {
824
+ opacity: 1;
825
+ }
826
+
827
+ &[aria-checked="true"] .fp-switch-face::after {
828
+ opacity: 0;
829
+ }
830
+
831
+ &[aria-checked="true"] .fp-switch-lamp {
832
+ opacity: 1;
833
+ transition-duration: 60ms;
834
+ transition-timing-function: linear;
835
+ transition-delay: 250ms;
836
+ }
837
+
838
+ /*
839
+ * On its side the paddle tips across instead, and the marks stay upright. With the light at the
840
+ * top left, the right end is now the one in shade that casts the shadow, and the left end is lit.
841
+ */
842
+ &[data-orientation="horizontal"] {
843
+ width: 3.5rem;
844
+ height: 2.75rem;
845
+
846
+ @media (pointer: fine) {
847
+ width: 2.875rem;
848
+ height: 2.25rem;
849
+ }
850
+
851
+ & .fp-switch-rocker {
852
+ transform: translateZ(6px) rotateY(-25deg);
853
+ background: linear-gradient(
854
+ 90deg,
855
+ color-mix(in srgb, var(--fp-switch-lamp) 60%, black) 0%,
856
+ color-mix(in srgb, var(--fp-switch-lamp) 44%, black) 30%,
857
+ color-mix(in srgb, var(--fp-switch-lamp) 44%, black) 70%,
858
+ color-mix(in srgb, var(--fp-switch-lamp) 60%, black) 100%
859
+ );
860
+ }
861
+
862
+ /* The right end, in shade, with the shadow the paddle casts into the bezel. */
863
+ & .fp-switch-rocker::before {
864
+ left: auto;
865
+ right: 0;
866
+ width: 15px;
867
+ height: 100%;
868
+ transform-origin: right;
869
+ transform: rotateY(-90deg);
870
+ background: linear-gradient(
871
+ 270deg,
872
+ color-mix(in srgb, var(--fp-switch-lamp) 40%, black),
873
+ color-mix(in srgb, var(--fp-switch-lamp) 20%, black)
874
+ );
875
+ box-shadow:
876
+ -15px 0 3px 0 black,
877
+ -24px 0 6px 0 rgb(0 0 0 / 0.5);
878
+ }
879
+
880
+ /* The left end, lit from the left while it stands proud. */
881
+ & .fp-switch-rocker::after {
882
+ top: 0;
883
+ bottom: auto;
884
+ width: 15px;
885
+ height: 100%;
886
+ transform-origin: right;
887
+ transform: translateX(-15px) rotateY(-90deg);
888
+ background:
889
+ linear-gradient(
890
+ 270deg,
891
+ rgb(255 255 255 / 0.8) 10%,
892
+ rgb(255 255 255 / 0.3) 30%,
893
+ color-mix(in srgb, var(--fp-switch-lamp) 40%, black) 75%,
894
+ color-mix(in srgb, var(--fp-switch-lamp) 20%, black)
895
+ )
896
+ 50% 50% / 97% 97% no-repeat,
897
+ color-mix(in srgb, var(--fp-switch-lamp) 70%, black);
898
+ box-shadow: none;
899
+ }
900
+
901
+ & .fp-switch-face {
902
+ background:
903
+ linear-gradient(#fff, #fff) 78% 50% / max(2px, 4%) 28% no-repeat,
904
+ radial-gradient(circle, transparent 50%, #fff 52%, #fff 70%, transparent 72%) 22% 50% / 25% 33% no-repeat,
905
+ radial-gradient(transparent 30%, color-mix(in srgb, color-mix(in srgb, var(--fp-switch-lamp) 40%, black) 70%, transparent) 70%) 0 0 / 3px 3px;
906
+ }
907
+
908
+ & .fp-switch-face::before {
909
+ background:
910
+ linear-gradient(#fff, transparent 3%) 50% 50% / 97% 97% no-repeat,
911
+ linear-gradient(270deg, rgb(255 255 255 / 0.5), transparent 50%, transparent 80%, rgb(255 255 255 / 0.5)) 50% 50% / 97% 97% no-repeat;
912
+ }
913
+
914
+ & .fp-switch-face::after {
915
+ background-image: linear-gradient(270deg, transparent 70%, rgb(0 0 0 / 0.8));
916
+ }
917
+
918
+ &[aria-checked="true"] .fp-switch-rocker {
919
+ transform: translateZ(6px) rotateY(25deg);
920
+ box-shadow: 3px 0 6px var(--fp-switch-lamp);
921
+ }
922
+ }
923
+
924
+ /* A solid ring with a glow outside it: the glow alone fails non-text contrast on light chrome. */
925
+ &:focus-visible {
926
+ outline: 2px solid var(--fp-color-focus-ring);
927
+ outline-offset: 2px;
928
+ filter: drop-shadow(0 0 3px var(--fp-color-focus));
929
+ }
930
+
931
+ &:disabled,
932
+ &[aria-disabled="true"] {
933
+ opacity: 0.45;
934
+ cursor: not-allowed;
935
+ }
936
+
937
+ /*
938
+ * Forced colours drop the paddle's paint: an outline keeps the bezel's edge, and the rocker fills
939
+ * when on, as a lit LED does, so the state reads by shape as well as by the tilt.
940
+ */
941
+ @media (forced-colors: active) {
942
+ outline: 1px solid ButtonText;
943
+
944
+ &[data-orientation="horizontal"] .fp-switch-rocker {
945
+ background: Canvas;
946
+ }
947
+
948
+ &[aria-checked="true"] .fp-switch-rocker,
949
+ &[data-orientation="horizontal"][aria-checked="true"] .fp-switch-rocker {
950
+ background: CanvasText;
951
+ box-shadow: none;
952
+ }
953
+
954
+ &:disabled,
955
+ &[aria-disabled="true"] {
956
+ opacity: 1;
957
+ outline-color: GrayText;
958
+ }
959
+ }
960
+ }
961
+
962
+ /**
963
+ * The paddle of an fp-switch, tilted in 3D. It holds an fp-switch-lamp and then an fp-switch-face;
964
+ * its top and bottom ends show as it tips.
965
+ */
966
+ @utility fp-switch-rocker {
967
+ position: relative;
968
+ display: block;
969
+ width: 100%;
970
+ height: 100%;
971
+ transform-origin: 50% 50% -6px;
972
+ transform: translateZ(6px) rotateX(-25deg);
973
+ transform-style: preserve-3d;
974
+ background: linear-gradient(
975
+ color-mix(in srgb, var(--fp-switch-lamp) 60%, black) 0%,
976
+ color-mix(in srgb, var(--fp-switch-lamp) 44%, black) 30%,
977
+ color-mix(in srgb, var(--fp-switch-lamp) 44%, black) 70%,
978
+ color-mix(in srgb, var(--fp-switch-lamp) 60%, black) 100%
979
+ );
980
+
981
+ /* Snaps over at the end of its travel, as a rocker's spring does. */
982
+ transition: transform 300ms cubic-bezier(1, 0, 1, 1), box-shadow 300ms cubic-bezier(1, 0, 1, 1);
983
+
984
+ @media (prefers-reduced-motion: reduce) {
985
+ transition-duration: 0ms;
986
+ }
987
+
988
+ /* The top end, lit from above while it stands proud. */
989
+ &::before {
990
+ content: "";
991
+ position: absolute;
992
+ top: 0;
993
+ left: 0;
994
+ width: 100%;
995
+ height: 15px;
996
+ transform-origin: top;
997
+ transform: rotateX(-90deg);
998
+ background:
999
+ linear-gradient(
1000
+ rgb(255 255 255 / 0.8) 10%,
1001
+ rgb(255 255 255 / 0.3) 30%,
1002
+ color-mix(in srgb, var(--fp-switch-lamp) 40%, black) 75%,
1003
+ color-mix(in srgb, var(--fp-switch-lamp) 20%, black)
1004
+ )
1005
+ 50% 50% / 97% 97% no-repeat,
1006
+ color-mix(in srgb, var(--fp-switch-lamp) 70%, black);
1007
+ }
1008
+
1009
+ /* The bottom end, in shadow, with the shadow the paddle casts into the bezel. */
1010
+ &::after {
1011
+ content: "";
1012
+ position: absolute;
1013
+ bottom: 0;
1014
+ left: 0;
1015
+ width: 100%;
1016
+ height: 15px;
1017
+ transform-origin: top;
1018
+ transform: translateY(15px) rotateX(-90deg);
1019
+ background-image: linear-gradient(
1020
+ color-mix(in srgb, var(--fp-switch-lamp) 40%, black),
1021
+ color-mix(in srgb, var(--fp-switch-lamp) 20%, black)
1022
+ );
1023
+ box-shadow:
1024
+ 0 15px 3px 0 black,
1025
+ 0 24px 6px 0 rgb(0 0 0 / 0.5);
1026
+ }
1027
+
1028
+ /* Forced colours: a plain paddle in the system's colours; fp-switch fills it when on. */
1029
+ @media (forced-colors: active) {
1030
+ forced-color-adjust: none;
1031
+ background: Canvas;
1032
+ outline: 1px solid CanvasText;
1033
+ outline-offset: -1px;
1034
+
1035
+ &::before,
1036
+ &::after {
1037
+ content: none;
1038
+ }
1039
+ }
1040
+ }
1041
+
1042
+ /** The lamp behind an fp-switch's lens: lit when the switch is on, and slow to fade, like a filament cooling. */
1043
+ @utility fp-switch-lamp {
1044
+ position: absolute;
1045
+ inset: 0;
1046
+ opacity: 0;
1047
+ background-image: radial-gradient(
1048
+ color-mix(in srgb, var(--fp-switch-lamp) 35%, #fff2c8),
1049
+ var(--fp-switch-lamp) 40%,
1050
+ transparent 70%
1051
+ );
1052
+ transition: opacity 600ms ease-out;
1053
+
1054
+ @media (prefers-reduced-motion: reduce) {
1055
+ transition-duration: 0ms;
1056
+ }
1057
+
1058
+ /* Forced colours: the rocker's fill shows the state instead. */
1059
+ @media (forced-colors: active) {
1060
+ display: none;
1061
+ }
1062
+ }
1063
+
1064
+ /**
1065
+ * The front of an fp-switch's paddle: the dotted lens over the lamp, the I and O marks, and the
1066
+ * shine that comes up on the pressed-in half. Decorative; the switch says its state.
1067
+ */
1068
+ @utility fp-switch-face {
1069
+ position: absolute;
1070
+ inset: 0;
1071
+ background:
1072
+ linear-gradient(#fff, #fff) 50% 20% / max(2px, 5%) 20% no-repeat,
1073
+ radial-gradient(circle, transparent 50%, #fff 52%, #fff 70%, transparent 72%) 50% 80% / 33% 25% no-repeat,
1074
+ radial-gradient(transparent 30%, color-mix(in srgb, color-mix(in srgb, var(--fp-switch-lamp) 40%, black) 70%, transparent) 70%) 0 0 / 3px 3px;
1075
+
1076
+ /* The shine: faint while the I half stands out, full once it is pressed in. */
1077
+ &::before,
1078
+ &::after {
1079
+ content: "";
1080
+ position: absolute;
1081
+ inset: 0;
1082
+ transition: opacity 300ms cubic-bezier(1, 0, 1, 1);
1083
+
1084
+ @media (prefers-reduced-motion: reduce) {
1085
+ transition-duration: 0ms;
1086
+ }
1087
+ }
1088
+
1089
+ &::before {
1090
+ opacity: 0.3;
1091
+ background:
1092
+ linear-gradient(#fff, transparent 3%) 50% 50% / 97% 97% no-repeat,
1093
+ linear-gradient(rgb(255 255 255 / 0.5), transparent 50%, transparent 80%, rgb(255 255 255 / 0.5)) 50% 50% / 97% 97% no-repeat;
1094
+ }
1095
+
1096
+ /* The shading on the O half while it is pressed in. */
1097
+ &::after {
1098
+ opacity: 1;
1099
+ background-image: linear-gradient(transparent 70%, rgb(0 0 0 / 0.8));
1100
+ }
1101
+
1102
+ /* Forced colours: no lens or marks; the rocker's fill shows the state. */
1103
+ @media (forced-colors: active) {
1104
+ display: none;
1105
+ }
622
1106
  }
623
1107
 
624
1108
  /**
@@ -926,6 +1410,11 @@
926
1410
  inset 1px 1px 0 var(--fp-color-bevel-hi),
927
1411
  inset -1px -1px 0 var(--fp-color-bevel-lo),
928
1412
  2px 3px 0 var(--fp-color-cast);
1413
+
1414
+ /* Forced colours drop the shadow that draws its edge: an outline keeps it. */
1415
+ @media (forced-colors: active) {
1416
+ outline: 1px solid CanvasText;
1417
+ }
929
1418
  }
930
1419
 
931
1420
  /** A module's own little title bar, in brushed chrome. */
@@ -980,6 +1469,130 @@
980
1469
  padding: 0.75rem;
981
1470
  }
982
1471
 
1472
+ /**
1473
+ * Faceplate menu: a small brushed-chrome plate that drops from the button that opens it, holding
1474
+ * fp-menu-item rows and fp-menu-separator grooves. Placing it is up to you, or to Reka's
1475
+ * DropdownMenu, which also sets the `data-state="open"` it drops in on.
1476
+ */
1477
+ @utility fp-menu {
1478
+ color-scheme: var(--fp-scheme);
1479
+ z-index: 50;
1480
+ box-sizing: border-box;
1481
+ min-width: 10rem;
1482
+ overflow-y: auto;
1483
+ padding: 0.25rem;
1484
+ border-radius: 6px;
1485
+ color: var(--fp-color-ink);
1486
+ background-image: linear-gradient(
1487
+ 180deg,
1488
+ color-mix(in srgb, var(--fp-color-chrome-hi) 60%, var(--fp-color-chrome-100)) 0%,
1489
+ var(--fp-color-chrome-100) 40%,
1490
+ var(--fp-color-chrome-300) 100%
1491
+ );
1492
+ box-shadow:
1493
+ 0 0 0 1px var(--fp-color-edge),
1494
+ inset 1px 1px 0 var(--fp-color-bevel-hi),
1495
+ inset -1px -1px 0 var(--fp-color-bevel-lo),
1496
+ 2px 3px 0 var(--fp-color-cast),
1497
+ 0 12px 18px -8px var(--fp-color-cast);
1498
+
1499
+ /* The plate takes focus when a pointer opens it; its rows show where the keys are. */
1500
+ outline: none;
1501
+
1502
+ &[data-state="open"] {
1503
+ animation: fp-menu-drop 90ms ease-out;
1504
+ }
1505
+
1506
+ @media (prefers-reduced-motion: reduce) {
1507
+ &[data-state="open"] {
1508
+ animation: none;
1509
+ }
1510
+ }
1511
+
1512
+ /* Forced colours drop the chrome and its shadows: an outline keeps the plate's edge. */
1513
+ @media (forced-colors: active) {
1514
+ outline: 1px solid CanvasText;
1515
+ }
1516
+ }
1517
+
1518
+ /**
1519
+ * A row on an fp-menu: a silkscreened label, with an fp-icon before it if you like. The highlighted
1520
+ * row presses into the plate; Reka sets `data-highlighted` on the row under the pointer or the
1521
+ * arrow keys, and `data-disabled` on one that can't be chosen. 44 px tall, 36 px with a mouse,
1522
+ * trackpad or pen.
1523
+ */
1524
+ @utility fp-menu-item {
1525
+ display: flex;
1526
+ align-items: center;
1527
+ gap: 0.5rem;
1528
+ min-height: 2.75rem;
1529
+ padding-inline: 0.625rem;
1530
+ border-radius: 4px;
1531
+ color: var(--fp-color-ink);
1532
+ font-family: var(--fp-font-display);
1533
+ font-size: 11px;
1534
+ line-height: 1.3;
1535
+ letter-spacing: 0.08em;
1536
+ text-transform: uppercase;
1537
+ text-decoration: none;
1538
+ white-space: nowrap;
1539
+ cursor: default;
1540
+ user-select: none;
1541
+ outline: none;
1542
+
1543
+ @media (pointer: fine) {
1544
+ min-height: 2.25rem;
1545
+ }
1546
+
1547
+ &[data-highlighted] {
1548
+ background-color: var(--fp-color-chrome-300);
1549
+ box-shadow:
1550
+ inset 1px 1px 3px var(--fp-color-bevel-lo),
1551
+ inset -1px -1px 0 var(--fp-color-bevel-hi);
1552
+ }
1553
+
1554
+ /* The keyboard's row also gets the ring every focused control has, inside the row. */
1555
+ &:focus-visible {
1556
+ outline: 2px solid var(--fp-color-focus-ring);
1557
+ outline-offset: -2px;
1558
+ }
1559
+
1560
+ &[data-disabled] {
1561
+ opacity: 0.45;
1562
+ }
1563
+
1564
+ /*
1565
+ * Forced colours drop the pressed-in shading: the system's highlight colours show the highlighted
1566
+ * row, and GrayText, not a fade, shows a disabled one.
1567
+ */
1568
+ @media (forced-colors: active) {
1569
+ &[data-highlighted] {
1570
+ forced-color-adjust: none;
1571
+ background: Highlight;
1572
+ box-shadow: none;
1573
+ color: HighlightText;
1574
+
1575
+ /* Opted out, its focus ring would keep the skin's colour: the system's text colour shows on any ground. */
1576
+ &:focus-visible {
1577
+ outline-color: HighlightText;
1578
+ }
1579
+ }
1580
+
1581
+ &[data-disabled] {
1582
+ opacity: 1;
1583
+ color: GrayText;
1584
+ }
1585
+ }
1586
+ }
1587
+
1588
+ /** A groove between groups of rows on an fp-menu, cut into the plate: shaded above, lit below. */
1589
+ @utility fp-menu-separator {
1590
+ height: 0;
1591
+ margin: 0.25rem 0.375rem;
1592
+ border-top: 1px solid var(--fp-color-bevel-lo);
1593
+ border-bottom: 1px solid var(--fp-color-bevel-hi);
1594
+ }
1595
+
983
1596
  /** Recessed well inside a matte panel, for secondary content or an empty slot. */
984
1597
  @utility fp-well {
985
1598
  color-scheme: var(--fp-scheme);
@@ -988,6 +1601,11 @@
988
1601
  box-shadow:
989
1602
  inset 1px 1px 2px var(--fp-color-bevel-lo),
990
1603
  inset -1px -1px 0 var(--fp-color-bevel-hi);
1604
+
1605
+ /* Forced colours drop the shading that recesses it: an outline keeps an empty well in view. */
1606
+ @media (forced-colors: active) {
1607
+ outline: 1px solid CanvasText;
1608
+ }
991
1609
  }
992
1610
 
993
1611
  /** Silkscreened hardware label. */
@@ -1010,6 +1628,11 @@
1010
1628
  0 0 0 2px var(--fp-color-chrome-800),
1011
1629
  inset 2px 2px 4px rgb(0 0 0 / 0.35),
1012
1630
  inset -1px -1px 0 rgb(255 255 255 / 0.2);
1631
+
1632
+ /* Forced colours drop the chrome bezel: an outline frames the glass. */
1633
+ @media (forced-colors: active) {
1634
+ outline: 2px solid CanvasText;
1635
+ }
1013
1636
  }
1014
1637
 
1015
1638
  /** One line of LCD text. Cells of fp-lcd-ghost stacked behind it show the unlit segments, as on real glass. */
@@ -1027,6 +1650,11 @@
1027
1650
  /** The unlit segments behind an LCD line. Decorative: give it aria-hidden="true". */
1028
1651
  @utility fp-lcd-ghost {
1029
1652
  color: var(--fp-color-lcd-ghost);
1653
+
1654
+ /* Forced colours would draw the unlit segments as solid as the text over them. */
1655
+ @media (forced-colors: active) {
1656
+ visibility: hidden;
1657
+ }
1030
1658
  }
1031
1659
 
1032
1660
  /** Dark housing that LEDs and small readouts sit in. */
@@ -1043,6 +1671,11 @@
1043
1671
  box-shadow:
1044
1672
  inset 1px 1px 2px rgb(0 0 0 / 0.5),
1045
1673
  0 1px 0 var(--fp-color-bevel-hi);
1674
+
1675
+ /* Forced colours turn the dark housing into the page colour: an outline keeps it in view. */
1676
+ @media (forced-colors: active) {
1677
+ outline: 1px solid CanvasText;
1678
+ }
1046
1679
  }
1047
1680
 
1048
1681
  /** An LED with its own dark bezel ring, so a lit one reads on light chrome too. Unlit by default. */