@frontpanel/css 0.4.0 → 0.5.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/components.css CHANGED
@@ -1046,6 +1046,413 @@
1046
1046
  }
1047
1047
  }
1048
1048
 
1049
+ /**
1050
+ * A row of latching piano keys in a dark well, after the key row on an old cassette deck. Its
1051
+ * children alternate: an fp-silk label printed over the key, then the fp-key. Give it
1052
+ * `role="radiogroup"` round radio keys, or `role="group"` round checkbox keys, and a name; one
1053
+ * checkbox key needs no role. With `data-orientation="vertical"` the keys lie on their side,
1054
+ * stacked, each label before its key.
1055
+ */
1056
+ .fp-keys {
1057
+ display: inline-grid;
1058
+ grid-auto-flow: column;
1059
+
1060
+ /* Every key in a row is one width: the widest label's, or the key's own. */
1061
+ grid-auto-columns: 1fr;
1062
+ grid-template-rows: auto auto;
1063
+ row-gap: 0.5rem;
1064
+ justify-items: center;
1065
+ align-items: end;
1066
+
1067
+ /* A label, with its LED over it when the light is above the key. */
1068
+ & > .fp-silk {
1069
+ display: inline-flex;
1070
+ flex-direction: column;
1071
+ align-items: center;
1072
+ gap: 0.375rem;
1073
+ padding-inline: 0.25rem;
1074
+ white-space: nowrap;
1075
+ }
1076
+
1077
+ &[data-orientation="vertical"] {
1078
+ grid-auto-flow: row;
1079
+ grid-auto-columns: auto;
1080
+ grid-auto-rows: 1fr;
1081
+ grid-template-columns: auto auto;
1082
+ grid-template-rows: none;
1083
+ column-gap: 0.75rem;
1084
+ row-gap: 0;
1085
+ justify-items: stretch;
1086
+ align-items: center;
1087
+
1088
+ & > .fp-silk {
1089
+ flex-direction: row;
1090
+ justify-content: flex-end;
1091
+ padding-inline: 0;
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ /**
1097
+ * A piano key in an fp-keys row: knurled plastic, silver or dark by skin, with a strip at its foot,
1098
+ * standing in its stretch of the well. Put it on a button with `role="radio"` or `role="checkbox"` and
1099
+ * `aria-checked`; checked, it latches part way down, smaller and darker under the well's lip, and
1100
+ * a press sinks it deeper. The strip is an LED in the key's `fp-led-*` colour (green), lit while
1101
+ * latched; with `data-strip="plain"` it is cream plastic, for keys whose LED sits over the label,
1102
+ * or that have none.
1103
+ */
1104
+ .fp-key {
1105
+ /* How far the key is shaded, latched or held down. */
1106
+ --fp-key-shade: 0;
1107
+
1108
+ /* The well round the key: a narrow seam between keys, wider at the ends of the row. */
1109
+ --fp-key-start: 2px;
1110
+ --fp-key-end: 2px;
1111
+
1112
+ /* The well's edge, drawn on the outside of the row only, so neighbours show no seam. */
1113
+ --fp-key-edge-start: 0 0 transparent;
1114
+ --fp-key-edge-end: 0 0 transparent;
1115
+ position: relative;
1116
+ justify-self: stretch;
1117
+ min-width: calc(3.5rem + 4px);
1118
+ min-height: calc(4.75rem + 14px);
1119
+ padding: 0;
1120
+ border: 0;
1121
+ border-radius: 0;
1122
+ background-color: var(--fp-color-key-well);
1123
+ box-shadow:
1124
+ inset 0 3px 5px rgb(0 0 0 / 0.8),
1125
+ 0 -1px 0 var(--fp-color-edge),
1126
+ 0 1px 0 var(--fp-color-edge),
1127
+ 0 2px 0 var(--fp-color-bevel-hi),
1128
+ var(--fp-key-edge-start),
1129
+ var(--fp-key-edge-end);
1130
+
1131
+ @media (pointer: fine) {
1132
+ min-width: calc(3rem + 4px);
1133
+ min-height: calc(4.125rem + 14px);
1134
+ }
1135
+
1136
+ &:nth-child(1 of .fp-key) {
1137
+ --fp-key-start: 4px;
1138
+ --fp-key-edge-start: -1px 0 0 var(--fp-color-edge);
1139
+ border-start-start-radius: 6px;
1140
+ border-end-start-radius: 6px;
1141
+ }
1142
+
1143
+ &:nth-last-child(1 of .fp-key) {
1144
+ --fp-key-end: 4px;
1145
+ --fp-key-edge-end: 1px 0 0 var(--fp-color-edge);
1146
+ border-start-end-radius: 6px;
1147
+ border-end-end-radius: 6px;
1148
+ }
1149
+
1150
+ /*
1151
+ * The key itself: a glossy frame lit from the top left, round a knurled face, a fine diamond grid
1152
+ * light on one diagonal and shaded on the other. It stands 4px below the well's top, with room
1153
+ * under it to travel down.
1154
+ */
1155
+ &::before {
1156
+ content: "";
1157
+ position: absolute;
1158
+ inset-block: 4px 10px;
1159
+ inset-inline: var(--fp-key-start) var(--fp-key-end);
1160
+ border-radius: 5px;
1161
+ background:
1162
+ linear-gradient(var(--fp-color-key-knurl-lo), transparent 2px) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1163
+ repeating-linear-gradient(45deg, var(--fp-color-key-knurl-hi) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1164
+ repeating-linear-gradient(-45deg, var(--fp-color-key-knurl-lo) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1165
+ linear-gradient(var(--fp-color-key-hi), color-mix(in srgb, var(--fp-color-key-hi) 50%, var(--fp-color-key))) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1166
+ linear-gradient(160deg, var(--fp-color-key-hi) 0%, var(--fp-color-key) 35%, var(--fp-color-key-lo) 100%);
1167
+ box-shadow:
1168
+ inset 1px 1px 0 rgb(255 255 255 / 0.28),
1169
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1170
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1171
+ 0 3px 0 rgb(0 0 0 / 0.9);
1172
+ transform-origin: 50% 100%;
1173
+ transition-property: translate, scale, filter, box-shadow;
1174
+ transition-duration: 70ms;
1175
+ transition-timing-function: linear;
1176
+ }
1177
+
1178
+ /*
1179
+ * The strip at the key's foot: an LED lens, dark in its colour until the key latches, set in a
1180
+ * rim of the well's colour, so a lit strip stands out on a pale key too.
1181
+ */
1182
+ &::after {
1183
+ content: "";
1184
+ position: absolute;
1185
+ inset-block-end: 15px;
1186
+ inset-inline: calc(var(--fp-key-start) + 5px) calc(var(--fp-key-end) + 5px);
1187
+ height: 7px;
1188
+ border-radius: 2px;
1189
+ background: color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 32%, black);
1190
+ box-shadow:
1191
+ inset 0 1px 1px rgb(0 0 0 / 0.6),
1192
+ 0 0 0 1.5px var(--fp-color-key-well);
1193
+
1194
+ /* It moves with the key: scaled from the key's foot, 5px below its own. */
1195
+ transform-origin: 50% calc(100% + 5px);
1196
+ transition-property: translate, scale, filter;
1197
+ transition-duration: 70ms;
1198
+ transition-timing-function: linear;
1199
+ }
1200
+
1201
+ @media (prefers-reduced-motion: reduce) {
1202
+ &::before,
1203
+ &::after {
1204
+ transition-duration: 0ms;
1205
+ }
1206
+ }
1207
+
1208
+ /* A plain strip: cream plastic, shaded with the key. */
1209
+ &[data-strip="plain"]::after {
1210
+ height: 8px;
1211
+ border-radius: 1px;
1212
+ background: linear-gradient(
1213
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, white),
1214
+ var(--fp-color-key-strip) 60%,
1215
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, black)
1216
+ );
1217
+ box-shadow: 0 0 0 1.5px var(--fp-color-key-well);
1218
+ filter: brightness(calc(1 - var(--fp-key-shade)));
1219
+ }
1220
+
1221
+ &:hover:not(:disabled)::before {
1222
+ filter: brightness(1.1);
1223
+ }
1224
+
1225
+ /* Latched: well down, a little smaller and darker, the well's lip shading its top edge. */
1226
+ &[aria-checked="true"] {
1227
+ --fp-key-shade: 0.24;
1228
+
1229
+ &::before {
1230
+ translate: 0 6px;
1231
+ scale: 0.96;
1232
+ box-shadow:
1233
+ inset 0 6px 5px -2px rgb(0 0 0 / 0.9),
1234
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1235
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1236
+ 0 0 0 transparent;
1237
+ }
1238
+
1239
+ &::after {
1240
+ translate: 0 6px;
1241
+ scale: 0.96;
1242
+ }
1243
+ }
1244
+
1245
+ /* A latched LED strip lights, brightest in the middle, and spills its glow up over the face. */
1246
+ &[aria-checked="true"]:not([data-strip="plain"])::after {
1247
+ background: radial-gradient(
1248
+ ellipse 60% 120% at 50% 50%,
1249
+ color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 65%, white),
1250
+ var(--fp-led, var(--fp-color-led-on))
1251
+ );
1252
+ box-shadow:
1253
+ 0 0 0 1.5px var(--fp-color-key-well),
1254
+ 0 0 6px 1px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 70%, transparent),
1255
+ 0 -5px 12px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 35%, transparent);
1256
+ }
1257
+
1258
+ /* Held under a finger: past the latch, deeper and darker still. */
1259
+ &:active:not(:disabled) {
1260
+ --fp-key-shade: 0.36;
1261
+
1262
+ &::before {
1263
+ translate: 0 9px;
1264
+ scale: 0.94;
1265
+ box-shadow:
1266
+ inset 0 7px 5px -2px rgb(0 0 0 / 0.95),
1267
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1268
+ 0 0 0 transparent;
1269
+ }
1270
+
1271
+ &::after {
1272
+ translate: 0 9px;
1273
+ scale: 0.94;
1274
+ }
1275
+ }
1276
+
1277
+ /*
1278
+ * The ring goes round the key, in the dark well, where the skin's own ring could vanish: its
1279
+ * own colour, with the usual glow outside it. Over its neighbours, not under them.
1280
+ */
1281
+ &:focus-visible {
1282
+ z-index: 1;
1283
+ outline: none;
1284
+ }
1285
+
1286
+ &:focus-visible::before {
1287
+ outline: 2px solid var(--fp-color-key-ring);
1288
+ outline-offset: 2px;
1289
+ filter: drop-shadow(0 0 3px var(--fp-color-focus));
1290
+ }
1291
+
1292
+ /* The hover filter outranks the focus rule, so a hovered key keeps its glow here. */
1293
+ &:focus-visible:hover:not(:disabled)::before {
1294
+ filter: brightness(1.1) drop-shadow(0 0 3px var(--fp-color-focus));
1295
+ }
1296
+
1297
+ /* Disabled, the key stays up and fades; its stretch of the well stays dark. */
1298
+ &:disabled::before,
1299
+ &:disabled::after {
1300
+ opacity: 0.45;
1301
+ }
1302
+
1303
+ /*
1304
+ * On its side, in a vertical fp-keys: the well runs top to bottom, the strip is at the key's
1305
+ * right end, and a latched key slides right, toward it, the well's left lip shading its near edge.
1306
+ */
1307
+ [data-orientation="vertical"] > & {
1308
+ --fp-key-edge-start: 0 0 transparent;
1309
+ --fp-key-edge-end: 0 0 transparent;
1310
+ min-width: calc(4.75rem + 14px);
1311
+ min-height: calc(3.5rem + 4px);
1312
+ border-radius: 0;
1313
+ box-shadow:
1314
+ inset 3px 0 5px rgb(0 0 0 / 0.8),
1315
+ -1px 0 0 var(--fp-color-edge),
1316
+ 1px 0 0 var(--fp-color-edge),
1317
+ 1px 1px 0 var(--fp-color-bevel-hi),
1318
+ var(--fp-key-edge-start),
1319
+ var(--fp-key-edge-end);
1320
+
1321
+ @media (pointer: fine) {
1322
+ min-width: calc(4.125rem + 14px);
1323
+ min-height: calc(3rem + 4px);
1324
+ }
1325
+
1326
+ &:nth-child(1 of .fp-key) {
1327
+ --fp-key-edge-start: 0 -1px 0 var(--fp-color-edge);
1328
+ border-radius: 6px 6px 0 0;
1329
+ }
1330
+
1331
+ &:nth-last-child(1 of .fp-key) {
1332
+ --fp-key-edge-end: 0 1px 0 var(--fp-color-edge), 0 2px 0 var(--fp-color-bevel-hi);
1333
+ border-radius: 0 0 6px 6px;
1334
+ }
1335
+
1336
+ &:nth-child(1 of .fp-key):nth-last-child(1 of .fp-key) {
1337
+ border-radius: 6px;
1338
+ }
1339
+
1340
+ &::before {
1341
+ inset: var(--fp-key-start) 10px var(--fp-key-end) 4px;
1342
+ background:
1343
+ linear-gradient(90deg, rgb(0 0 0 / 0.5), transparent 2px) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1344
+ repeating-linear-gradient(45deg, rgb(255 255 255 / 0.11) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1345
+ repeating-linear-gradient(-45deg, rgb(0 0 0 / 0.45) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1346
+ linear-gradient(var(--fp-color-key-hi), color-mix(in srgb, var(--fp-color-key-hi) 50%, var(--fp-color-key))) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1347
+ linear-gradient(160deg, var(--fp-color-key-hi) 0%, var(--fp-color-key) 35%, var(--fp-color-key-lo) 100%);
1348
+ box-shadow:
1349
+ inset 1px 1px 0 rgb(255 255 255 / 0.28),
1350
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1351
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1352
+ 3px 0 0 rgb(0 0 0 / 0.9);
1353
+ transform-origin: 100% 50%;
1354
+ }
1355
+
1356
+ &::after {
1357
+ inset: calc(var(--fp-key-start) + 5px) 15px calc(var(--fp-key-end) + 5px) auto;
1358
+ width: 7px;
1359
+ height: auto;
1360
+ transform-origin: calc(100% + 5px) 50%;
1361
+ }
1362
+
1363
+ &[data-strip="plain"]::after {
1364
+ width: 8px;
1365
+ background: linear-gradient(
1366
+ 90deg,
1367
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, white),
1368
+ var(--fp-color-key-strip) 60%,
1369
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, black)
1370
+ );
1371
+ }
1372
+
1373
+ &[aria-checked="true"] {
1374
+ &::before {
1375
+ translate: 6px 0;
1376
+ box-shadow:
1377
+ inset 6px 0 5px -2px rgb(0 0 0 / 0.9),
1378
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1379
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1380
+ 0 0 0 transparent;
1381
+ }
1382
+
1383
+ &::after {
1384
+ translate: 6px 0;
1385
+ }
1386
+ }
1387
+
1388
+ &[aria-checked="true"]:not([data-strip="plain"])::after {
1389
+ background: radial-gradient(
1390
+ ellipse 120% 60% at 50% 50%,
1391
+ color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 65%, white),
1392
+ var(--fp-led, var(--fp-color-led-on))
1393
+ );
1394
+ box-shadow:
1395
+ 0 0 0 1.5px var(--fp-color-key-well),
1396
+ 0 0 6px 1px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 70%, transparent),
1397
+ -5px 0 12px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 35%, transparent);
1398
+ }
1399
+
1400
+ &:active:not(:disabled) {
1401
+ &::before {
1402
+ translate: 9px 0;
1403
+ box-shadow:
1404
+ inset 7px 0 5px -2px rgb(0 0 0 / 0.95),
1405
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1406
+ 0 0 0 transparent;
1407
+ }
1408
+
1409
+ &::after {
1410
+ translate: 9px 0;
1411
+ }
1412
+ }
1413
+ }
1414
+
1415
+ /*
1416
+ * Forced colours drop the plastic, the well and the light: each key and its strip keep an outline,
1417
+ * and a latched key opts out to fill in the system's selected colour, its strip in the selected
1418
+ * text colour, so it reads apart from the rest by fill as well as by its travel. The selectors
1419
+ * match the rules they override, the vertical ones too.
1420
+ */
1421
+ @media (forced-colors: active) {
1422
+ &::before,
1423
+ &::after {
1424
+ outline: 1px solid ButtonText;
1425
+ }
1426
+
1427
+ &[aria-checked="true"]::before,
1428
+ [data-orientation="vertical"] > &[aria-checked="true"]::before {
1429
+ forced-color-adjust: none;
1430
+ background: SelectedItem;
1431
+ box-shadow: none;
1432
+ }
1433
+
1434
+ &[aria-checked="true"]::after,
1435
+ &[aria-checked="true"]:not([data-strip="plain"])::after,
1436
+ [data-orientation="vertical"] > &[aria-checked="true"]::after,
1437
+ [data-orientation="vertical"] > &[aria-checked="true"]:not([data-strip="plain"])::after {
1438
+ forced-color-adjust: none;
1439
+ background: SelectedItemText;
1440
+ box-shadow: none;
1441
+ filter: none;
1442
+ }
1443
+
1444
+ &:focus-visible::before {
1445
+ outline: 2px solid CanvasText;
1446
+ }
1447
+
1448
+ &:disabled::before,
1449
+ &:disabled::after {
1450
+ opacity: 1;
1451
+ outline-color: GrayText;
1452
+ }
1453
+ }
1454
+ }
1455
+
1049
1456
  /**
1050
1457
  * Level meter: a strip of LED segments in a dark housing, lit green, then amber, then red. Set
1051
1458
  * `--fp-meter-level` (0 to 1, in whole segments), `--fp-meter-segments` (12), and where amber and
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@frontpanel/css",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "description": "Early-2000s hardware skin as plain CSS: liquid-chrome bezels, gel buttons, LCDs and LEDs. Tokens, classes and an optional Tailwind 4 entry.",
6
6
  "author": "Dulan Katukurundage",
7
7
  "license": "MIT",
package/tailwind.css CHANGED
@@ -32,6 +32,14 @@
32
32
  --color-fp-led-amber: var(--fp-color-led-amber);
33
33
  --color-fp-led-red: var(--fp-color-led-red);
34
34
  --color-fp-led-off: var(--fp-color-led-off);
35
+ --color-fp-key-hi: var(--fp-color-key-hi);
36
+ --color-fp-key: var(--fp-color-key);
37
+ --color-fp-key-lo: var(--fp-color-key-lo);
38
+ --color-fp-key-well: var(--fp-color-key-well);
39
+ --color-fp-key-knurl-hi: var(--fp-color-key-knurl-hi);
40
+ --color-fp-key-knurl-lo: var(--fp-color-key-knurl-lo);
41
+ --color-fp-key-strip: var(--fp-color-key-strip);
42
+ --color-fp-key-ring: var(--fp-color-key-ring);
35
43
  --color-fp-cap: var(--fp-color-cap);
36
44
  --color-fp-cap-ink: var(--fp-color-cap-ink);
37
45
  --color-fp-cap-blue: var(--fp-color-cap-blue);
@@ -1105,6 +1113,413 @@
1105
1113
  }
1106
1114
  }
1107
1115
 
1116
+ /**
1117
+ * A row of latching piano keys in a dark well, after the key row on an old cassette deck. Its
1118
+ * children alternate: an fp-silk label printed over the key, then the fp-key. Give it
1119
+ * `role="radiogroup"` round radio keys, or `role="group"` round checkbox keys, and a name; one
1120
+ * checkbox key needs no role. With `data-orientation="vertical"` the keys lie on their side,
1121
+ * stacked, each label before its key.
1122
+ */
1123
+ @utility fp-keys {
1124
+ display: inline-grid;
1125
+ grid-auto-flow: column;
1126
+
1127
+ /* Every key in a row is one width: the widest label's, or the key's own. */
1128
+ grid-auto-columns: 1fr;
1129
+ grid-template-rows: auto auto;
1130
+ row-gap: 0.5rem;
1131
+ justify-items: center;
1132
+ align-items: end;
1133
+
1134
+ /* A label, with its LED over it when the light is above the key. */
1135
+ & > .fp-silk {
1136
+ display: inline-flex;
1137
+ flex-direction: column;
1138
+ align-items: center;
1139
+ gap: 0.375rem;
1140
+ padding-inline: 0.25rem;
1141
+ white-space: nowrap;
1142
+ }
1143
+
1144
+ &[data-orientation="vertical"] {
1145
+ grid-auto-flow: row;
1146
+ grid-auto-columns: auto;
1147
+ grid-auto-rows: 1fr;
1148
+ grid-template-columns: auto auto;
1149
+ grid-template-rows: none;
1150
+ column-gap: 0.75rem;
1151
+ row-gap: 0;
1152
+ justify-items: stretch;
1153
+ align-items: center;
1154
+
1155
+ & > .fp-silk {
1156
+ flex-direction: row;
1157
+ justify-content: flex-end;
1158
+ padding-inline: 0;
1159
+ }
1160
+ }
1161
+ }
1162
+
1163
+ /**
1164
+ * A piano key in an fp-keys row: knurled plastic, silver or dark by skin, with a strip at its foot,
1165
+ * standing in its stretch of the well. Put it on a button with `role="radio"` or `role="checkbox"` and
1166
+ * `aria-checked`; checked, it latches part way down, smaller and darker under the well's lip, and
1167
+ * a press sinks it deeper. The strip is an LED in the key's `fp-led-*` colour (green), lit while
1168
+ * latched; with `data-strip="plain"` it is cream plastic, for keys whose LED sits over the label,
1169
+ * or that have none.
1170
+ */
1171
+ @utility fp-key {
1172
+ /* How far the key is shaded, latched or held down. */
1173
+ --fp-key-shade: 0;
1174
+
1175
+ /* The well round the key: a narrow seam between keys, wider at the ends of the row. */
1176
+ --fp-key-start: 2px;
1177
+ --fp-key-end: 2px;
1178
+
1179
+ /* The well's edge, drawn on the outside of the row only, so neighbours show no seam. */
1180
+ --fp-key-edge-start: 0 0 transparent;
1181
+ --fp-key-edge-end: 0 0 transparent;
1182
+ position: relative;
1183
+ justify-self: stretch;
1184
+ min-width: calc(3.5rem + 4px);
1185
+ min-height: calc(4.75rem + 14px);
1186
+ padding: 0;
1187
+ border: 0;
1188
+ border-radius: 0;
1189
+ background-color: var(--fp-color-key-well);
1190
+ box-shadow:
1191
+ inset 0 3px 5px rgb(0 0 0 / 0.8),
1192
+ 0 -1px 0 var(--fp-color-edge),
1193
+ 0 1px 0 var(--fp-color-edge),
1194
+ 0 2px 0 var(--fp-color-bevel-hi),
1195
+ var(--fp-key-edge-start),
1196
+ var(--fp-key-edge-end);
1197
+
1198
+ @media (pointer: fine) {
1199
+ min-width: calc(3rem + 4px);
1200
+ min-height: calc(4.125rem + 14px);
1201
+ }
1202
+
1203
+ &:nth-child(1 of .fp-key) {
1204
+ --fp-key-start: 4px;
1205
+ --fp-key-edge-start: -1px 0 0 var(--fp-color-edge);
1206
+ border-start-start-radius: 6px;
1207
+ border-end-start-radius: 6px;
1208
+ }
1209
+
1210
+ &:nth-last-child(1 of .fp-key) {
1211
+ --fp-key-end: 4px;
1212
+ --fp-key-edge-end: 1px 0 0 var(--fp-color-edge);
1213
+ border-start-end-radius: 6px;
1214
+ border-end-end-radius: 6px;
1215
+ }
1216
+
1217
+ /*
1218
+ * The key itself: a glossy frame lit from the top left, round a knurled face, a fine diamond grid
1219
+ * light on one diagonal and shaded on the other. It stands 4px below the well's top, with room
1220
+ * under it to travel down.
1221
+ */
1222
+ &::before {
1223
+ content: "";
1224
+ position: absolute;
1225
+ inset-block: 4px 10px;
1226
+ inset-inline: var(--fp-key-start) var(--fp-key-end);
1227
+ border-radius: 5px;
1228
+ background:
1229
+ linear-gradient(var(--fp-color-key-knurl-lo), transparent 2px) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1230
+ repeating-linear-gradient(45deg, var(--fp-color-key-knurl-hi) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1231
+ repeating-linear-gradient(-45deg, var(--fp-color-key-knurl-lo) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1232
+ linear-gradient(var(--fp-color-key-hi), color-mix(in srgb, var(--fp-color-key-hi) 50%, var(--fp-color-key))) 5px 5px / calc(100% - 10px) calc(100% - 20px) no-repeat,
1233
+ linear-gradient(160deg, var(--fp-color-key-hi) 0%, var(--fp-color-key) 35%, var(--fp-color-key-lo) 100%);
1234
+ box-shadow:
1235
+ inset 1px 1px 0 rgb(255 255 255 / 0.28),
1236
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1237
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1238
+ 0 3px 0 rgb(0 0 0 / 0.9);
1239
+ transform-origin: 50% 100%;
1240
+ transition-property: translate, scale, filter, box-shadow;
1241
+ transition-duration: 70ms;
1242
+ transition-timing-function: linear;
1243
+ }
1244
+
1245
+ /*
1246
+ * The strip at the key's foot: an LED lens, dark in its colour until the key latches, set in a
1247
+ * rim of the well's colour, so a lit strip stands out on a pale key too.
1248
+ */
1249
+ &::after {
1250
+ content: "";
1251
+ position: absolute;
1252
+ inset-block-end: 15px;
1253
+ inset-inline: calc(var(--fp-key-start) + 5px) calc(var(--fp-key-end) + 5px);
1254
+ height: 7px;
1255
+ border-radius: 2px;
1256
+ background: color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 32%, black);
1257
+ box-shadow:
1258
+ inset 0 1px 1px rgb(0 0 0 / 0.6),
1259
+ 0 0 0 1.5px var(--fp-color-key-well);
1260
+
1261
+ /* It moves with the key: scaled from the key's foot, 5px below its own. */
1262
+ transform-origin: 50% calc(100% + 5px);
1263
+ transition-property: translate, scale, filter;
1264
+ transition-duration: 70ms;
1265
+ transition-timing-function: linear;
1266
+ }
1267
+
1268
+ @media (prefers-reduced-motion: reduce) {
1269
+ &::before,
1270
+ &::after {
1271
+ transition-duration: 0ms;
1272
+ }
1273
+ }
1274
+
1275
+ /* A plain strip: cream plastic, shaded with the key. */
1276
+ &[data-strip="plain"]::after {
1277
+ height: 8px;
1278
+ border-radius: 1px;
1279
+ background: linear-gradient(
1280
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, white),
1281
+ var(--fp-color-key-strip) 60%,
1282
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, black)
1283
+ );
1284
+ box-shadow: 0 0 0 1.5px var(--fp-color-key-well);
1285
+ filter: brightness(calc(1 - var(--fp-key-shade)));
1286
+ }
1287
+
1288
+ &:hover:not(:disabled)::before {
1289
+ filter: brightness(1.1);
1290
+ }
1291
+
1292
+ /* Latched: well down, a little smaller and darker, the well's lip shading its top edge. */
1293
+ &[aria-checked="true"] {
1294
+ --fp-key-shade: 0.24;
1295
+
1296
+ &::before {
1297
+ translate: 0 6px;
1298
+ scale: 0.96;
1299
+ box-shadow:
1300
+ inset 0 6px 5px -2px rgb(0 0 0 / 0.9),
1301
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1302
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1303
+ 0 0 0 transparent;
1304
+ }
1305
+
1306
+ &::after {
1307
+ translate: 0 6px;
1308
+ scale: 0.96;
1309
+ }
1310
+ }
1311
+
1312
+ /* A latched LED strip lights, brightest in the middle, and spills its glow up over the face. */
1313
+ &[aria-checked="true"]:not([data-strip="plain"])::after {
1314
+ background: radial-gradient(
1315
+ ellipse 60% 120% at 50% 50%,
1316
+ color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 65%, white),
1317
+ var(--fp-led, var(--fp-color-led-on))
1318
+ );
1319
+ box-shadow:
1320
+ 0 0 0 1.5px var(--fp-color-key-well),
1321
+ 0 0 6px 1px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 70%, transparent),
1322
+ 0 -5px 12px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 35%, transparent);
1323
+ }
1324
+
1325
+ /* Held under a finger: past the latch, deeper and darker still. */
1326
+ &:active:not(:disabled) {
1327
+ --fp-key-shade: 0.36;
1328
+
1329
+ &::before {
1330
+ translate: 0 9px;
1331
+ scale: 0.94;
1332
+ box-shadow:
1333
+ inset 0 7px 5px -2px rgb(0 0 0 / 0.95),
1334
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1335
+ 0 0 0 transparent;
1336
+ }
1337
+
1338
+ &::after {
1339
+ translate: 0 9px;
1340
+ scale: 0.94;
1341
+ }
1342
+ }
1343
+
1344
+ /*
1345
+ * The ring goes round the key, in the dark well, where the skin's own ring could vanish: its
1346
+ * own colour, with the usual glow outside it. Over its neighbours, not under them.
1347
+ */
1348
+ &:focus-visible {
1349
+ z-index: 1;
1350
+ outline: none;
1351
+ }
1352
+
1353
+ &:focus-visible::before {
1354
+ outline: 2px solid var(--fp-color-key-ring);
1355
+ outline-offset: 2px;
1356
+ filter: drop-shadow(0 0 3px var(--fp-color-focus));
1357
+ }
1358
+
1359
+ /* The hover filter outranks the focus rule, so a hovered key keeps its glow here. */
1360
+ &:focus-visible:hover:not(:disabled)::before {
1361
+ filter: brightness(1.1) drop-shadow(0 0 3px var(--fp-color-focus));
1362
+ }
1363
+
1364
+ /* Disabled, the key stays up and fades; its stretch of the well stays dark. */
1365
+ &:disabled::before,
1366
+ &:disabled::after {
1367
+ opacity: 0.45;
1368
+ }
1369
+
1370
+ /*
1371
+ * On its side, in a vertical fp-keys: the well runs top to bottom, the strip is at the key's
1372
+ * right end, and a latched key slides right, toward it, the well's left lip shading its near edge.
1373
+ */
1374
+ [data-orientation="vertical"] > & {
1375
+ --fp-key-edge-start: 0 0 transparent;
1376
+ --fp-key-edge-end: 0 0 transparent;
1377
+ min-width: calc(4.75rem + 14px);
1378
+ min-height: calc(3.5rem + 4px);
1379
+ border-radius: 0;
1380
+ box-shadow:
1381
+ inset 3px 0 5px rgb(0 0 0 / 0.8),
1382
+ -1px 0 0 var(--fp-color-edge),
1383
+ 1px 0 0 var(--fp-color-edge),
1384
+ 1px 1px 0 var(--fp-color-bevel-hi),
1385
+ var(--fp-key-edge-start),
1386
+ var(--fp-key-edge-end);
1387
+
1388
+ @media (pointer: fine) {
1389
+ min-width: calc(4.125rem + 14px);
1390
+ min-height: calc(3rem + 4px);
1391
+ }
1392
+
1393
+ &:nth-child(1 of .fp-key) {
1394
+ --fp-key-edge-start: 0 -1px 0 var(--fp-color-edge);
1395
+ border-radius: 6px 6px 0 0;
1396
+ }
1397
+
1398
+ &:nth-last-child(1 of .fp-key) {
1399
+ --fp-key-edge-end: 0 1px 0 var(--fp-color-edge), 0 2px 0 var(--fp-color-bevel-hi);
1400
+ border-radius: 0 0 6px 6px;
1401
+ }
1402
+
1403
+ &:nth-child(1 of .fp-key):nth-last-child(1 of .fp-key) {
1404
+ border-radius: 6px;
1405
+ }
1406
+
1407
+ &::before {
1408
+ inset: var(--fp-key-start) 10px var(--fp-key-end) 4px;
1409
+ background:
1410
+ linear-gradient(90deg, rgb(0 0 0 / 0.5), transparent 2px) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1411
+ repeating-linear-gradient(45deg, rgb(255 255 255 / 0.11) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1412
+ repeating-linear-gradient(-45deg, rgb(0 0 0 / 0.45) 0 1px, transparent 1px 3.5px) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1413
+ linear-gradient(var(--fp-color-key-hi), color-mix(in srgb, var(--fp-color-key-hi) 50%, var(--fp-color-key))) 5px 5px / calc(100% - 20px) calc(100% - 10px) no-repeat,
1414
+ linear-gradient(160deg, var(--fp-color-key-hi) 0%, var(--fp-color-key) 35%, var(--fp-color-key-lo) 100%);
1415
+ box-shadow:
1416
+ inset 1px 1px 0 rgb(255 255 255 / 0.28),
1417
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1418
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1419
+ 3px 0 0 rgb(0 0 0 / 0.9);
1420
+ transform-origin: 100% 50%;
1421
+ }
1422
+
1423
+ &::after {
1424
+ inset: calc(var(--fp-key-start) + 5px) 15px calc(var(--fp-key-end) + 5px) auto;
1425
+ width: 7px;
1426
+ height: auto;
1427
+ transform-origin: calc(100% + 5px) 50%;
1428
+ }
1429
+
1430
+ &[data-strip="plain"]::after {
1431
+ width: 8px;
1432
+ background: linear-gradient(
1433
+ 90deg,
1434
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, white),
1435
+ var(--fp-color-key-strip) 60%,
1436
+ color-mix(in srgb, var(--fp-color-key-strip) 80%, black)
1437
+ );
1438
+ }
1439
+
1440
+ &[aria-checked="true"] {
1441
+ &::before {
1442
+ translate: 6px 0;
1443
+ box-shadow:
1444
+ inset 6px 0 5px -2px rgb(0 0 0 / 0.9),
1445
+ inset -1px -1px 0 rgb(0 0 0 / 0.6),
1446
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1447
+ 0 0 0 transparent;
1448
+ }
1449
+
1450
+ &::after {
1451
+ translate: 6px 0;
1452
+ }
1453
+ }
1454
+
1455
+ &[aria-checked="true"]:not([data-strip="plain"])::after {
1456
+ background: radial-gradient(
1457
+ ellipse 120% 60% at 50% 50%,
1458
+ color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 65%, white),
1459
+ var(--fp-led, var(--fp-color-led-on))
1460
+ );
1461
+ box-shadow:
1462
+ 0 0 0 1.5px var(--fp-color-key-well),
1463
+ 0 0 6px 1px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 70%, transparent),
1464
+ -5px 0 12px color-mix(in srgb, var(--fp-led, var(--fp-color-led-on)) 35%, transparent);
1465
+ }
1466
+
1467
+ &:active:not(:disabled) {
1468
+ &::before {
1469
+ translate: 9px 0;
1470
+ box-shadow:
1471
+ inset 7px 0 5px -2px rgb(0 0 0 / 0.95),
1472
+ inset 0 0 0 99px rgb(0 0 0 / var(--fp-key-shade)),
1473
+ 0 0 0 transparent;
1474
+ }
1475
+
1476
+ &::after {
1477
+ translate: 9px 0;
1478
+ }
1479
+ }
1480
+ }
1481
+
1482
+ /*
1483
+ * Forced colours drop the plastic, the well and the light: each key and its strip keep an outline,
1484
+ * and a latched key opts out to fill in the system's selected colour, its strip in the selected
1485
+ * text colour, so it reads apart from the rest by fill as well as by its travel. The selectors
1486
+ * match the rules they override, the vertical ones too.
1487
+ */
1488
+ @media (forced-colors: active) {
1489
+ &::before,
1490
+ &::after {
1491
+ outline: 1px solid ButtonText;
1492
+ }
1493
+
1494
+ &[aria-checked="true"]::before,
1495
+ [data-orientation="vertical"] > &[aria-checked="true"]::before {
1496
+ forced-color-adjust: none;
1497
+ background: SelectedItem;
1498
+ box-shadow: none;
1499
+ }
1500
+
1501
+ &[aria-checked="true"]::after,
1502
+ &[aria-checked="true"]:not([data-strip="plain"])::after,
1503
+ [data-orientation="vertical"] > &[aria-checked="true"]::after,
1504
+ [data-orientation="vertical"] > &[aria-checked="true"]:not([data-strip="plain"])::after {
1505
+ forced-color-adjust: none;
1506
+ background: SelectedItemText;
1507
+ box-shadow: none;
1508
+ filter: none;
1509
+ }
1510
+
1511
+ &:focus-visible::before {
1512
+ outline: 2px solid CanvasText;
1513
+ }
1514
+
1515
+ &:disabled::before,
1516
+ &:disabled::after {
1517
+ opacity: 1;
1518
+ outline-color: GrayText;
1519
+ }
1520
+ }
1521
+ }
1522
+
1108
1523
  /**
1109
1524
  * Level meter: a strip of LED segments in a dark housing, lit green, then amber, then red. Set
1110
1525
  * `--fp-meter-level` (0 to 1, in whole segments), `--fp-meter-segments` (12), and where amber and
package/tokens.css CHANGED
@@ -32,6 +32,14 @@
32
32
  --fp-color-led-amber: #ffb000;
33
33
  --fp-color-led-red: #ff4a3d;
34
34
  --fp-color-led-off: #1f252e;
35
+ --fp-color-key-hi: #e8ecf2;
36
+ --fp-color-key: #b9c2cf;
37
+ --fp-color-key-lo: #7d8898;
38
+ --fp-color-key-well: #3a4250;
39
+ --fp-color-key-knurl-hi: rgb(255 255 255 / 0.3);
40
+ --fp-color-key-knurl-lo: rgb(0 0 0 / 0.2);
41
+ --fp-color-key-strip: #ece5d3;
42
+ --fp-color-key-ring: #8fbaff;
35
43
  --fp-color-cap: #f2f4f7;
36
44
  --fp-color-cap-ink: #2a303b;
37
45
  --fp-color-cap-blue: #2f6bc4;
@@ -89,6 +97,13 @@
89
97
  --fp-color-cap: #d5dae1;
90
98
  --fp-color-cap-graphite: #5a6373;
91
99
  --fp-color-focus-ring: #4fd2ff;
100
+ --fp-color-key-hi: #5a5e65;
101
+ --fp-color-key: #34373c;
102
+ --fp-color-key-lo: #222428;
103
+ --fp-color-key-well: #0b0c0e;
104
+ --fp-color-key-knurl-hi: rgb(255 255 255 / 0.11);
105
+ --fp-color-key-knurl-lo: rgb(0 0 0 / 0.45);
106
+ --fp-color-key-ring: #4fd2ff;
92
107
  --fp-color-select: #e7eaf0;
93
108
  --fp-color-error-text: #ff8a80;
94
109
  --fp-color-backdrop: #0d1016;
@@ -127,6 +142,13 @@
127
142
  --fp-color-cap: #d5dae1;
128
143
  --fp-color-cap-graphite: #5a6373;
129
144
  --fp-color-focus-ring: #4fd2ff;
145
+ --fp-color-key-hi: #5a5e65;
146
+ --fp-color-key: #34373c;
147
+ --fp-color-key-lo: #222428;
148
+ --fp-color-key-well: #0b0c0e;
149
+ --fp-color-key-knurl-hi: rgb(255 255 255 / 0.11);
150
+ --fp-color-key-knurl-lo: rgb(0 0 0 / 0.45);
151
+ --fp-color-key-ring: #4fd2ff;
130
152
  --fp-color-select: #e7eaf0;
131
153
  --fp-color-error-text: #ff8a80;
132
154
  --fp-color-backdrop: #0d1016;