@dillingerstaffing/strand-ui 0.1.1 → 0.2.1

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.
Files changed (41) hide show
  1. package/HTML_REFERENCE.md +753 -0
  2. package/dist/components/DataReadout/DataReadout.d.ts +2 -0
  3. package/dist/components/DataReadout/DataReadout.d.ts.map +1 -1
  4. package/dist/css/strand-ui.css +248 -76
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +13 -9
  7. package/package.json +13 -10
  8. package/src/__tests__/build-output.test.ts +123 -0
  9. package/src/__tests__/design-language.test.ts +137 -0
  10. package/src/__tests__/static.test.tsx +60 -0
  11. package/src/components/Alert/Alert.css +11 -3
  12. package/src/components/Badge/Badge.css +1 -1
  13. package/src/components/Breadcrumb/Breadcrumb.css +6 -1
  14. package/src/components/Button/Button.css +15 -8
  15. package/src/components/Card/Card.css +12 -3
  16. package/src/components/Checkbox/Checkbox.css +4 -4
  17. package/src/components/DataReadout/DataReadout.css +9 -0
  18. package/src/components/DataReadout/DataReadout.test.tsx +36 -0
  19. package/src/components/DataReadout/DataReadout.tsx +8 -2
  20. package/src/components/Dialog/Dialog.css +7 -6
  21. package/src/components/FormField/FormField.css +1 -1
  22. package/src/components/Grid/Grid.css +21 -0
  23. package/src/components/Input/Input.css +11 -4
  24. package/src/components/Link/Link.css +6 -0
  25. package/src/components/Nav/Nav.css +13 -3
  26. package/src/components/Progress/Progress.css +3 -3
  27. package/src/components/Radio/Radio.css +4 -4
  28. package/src/components/Select/Select.css +11 -4
  29. package/src/components/Skeleton/Skeleton.css +2 -2
  30. package/src/components/Slider/Slider.css +5 -5
  31. package/src/components/Stack/Stack.css +15 -0
  32. package/src/components/Switch/Switch.css +4 -4
  33. package/src/components/Table/Table.css +6 -1
  34. package/src/components/Tabs/Tabs.css +7 -2
  35. package/src/components/Tag/Tag.css +7 -7
  36. package/src/components/Textarea/Textarea.css +11 -4
  37. package/src/components/Toast/Toast.css +3 -3
  38. package/src/components/Tooltip/Tooltip.css +2 -2
  39. package/src/index.ts +1 -1
  40. package/src/static.css +47 -0
  41. package/LICENSE +0 -21
@@ -1,4 +1,4 @@
1
- /*! Strand UI v0.1.1 | MIT License | dillingerstaffing.com */
1
+ /*! Strand UI v0.2.0 | MIT License | dillingerstaffing.com */
2
2
 
3
3
  /* Alert */
4
4
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -10,7 +10,8 @@
10
10
  align-items: flex-start;
11
11
  justify-content: space-between;
12
12
  width: 100%;
13
- padding: var(--strand-space-4);
13
+ padding: var(--strand-space-6);
14
+ padding-left: var(--strand-space-5);
14
15
  border-radius: var(--strand-radius-md);
15
16
  border-left: 4px solid transparent;
16
17
  font-family: var(--strand-font-sans);
@@ -60,8 +61,8 @@
60
61
  color: var(--strand-gray-500);
61
62
  font-size: var(--strand-text-base);
62
63
  cursor: pointer;
63
- transition: background var(--strand-duration-fast) ease,
64
- color var(--strand-duration-fast) ease;
64
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart),
65
+ color var(--strand-duration-fast) var(--strand-ease-out-quart);
65
66
  }
66
67
 
67
68
  .strand-alert__dismiss:hover {
@@ -69,6 +70,13 @@
69
70
  color: var(--strand-gray-600);
70
71
  }
71
72
 
73
+ /* ── Reduced motion ── */
74
+ @media (prefers-reduced-motion: reduce) {
75
+ .strand-alert__dismiss {
76
+ transition: none;
77
+ }
78
+ }
79
+
72
80
 
73
81
  /* Avatar */
74
82
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -149,7 +157,7 @@
149
157
  justify-content: center;
150
158
  font-family: var(--strand-font-sans);
151
159
  font-weight: var(--strand-weight-semibold);
152
- color: #fff;
160
+ color: var(--strand-on-blue-primary);
153
161
  }
154
162
 
155
163
  /* Position at top-right when wrapping children */
@@ -231,13 +239,18 @@
231
239
  .strand-breadcrumb__link {
232
240
  color: var(--strand-gray-500);
233
241
  text-decoration: none;
234
- transition: color var(--strand-duration-fast) ease;
242
+ transition: color var(--strand-duration-fast) var(--strand-ease-out-quart);
235
243
  }
236
244
 
237
245
  .strand-breadcrumb__link:hover {
238
246
  color: var(--strand-gray-600);
239
247
  }
240
248
 
249
+ .strand-breadcrumb__link:focus-visible {
250
+ outline: 2px solid var(--strand-blue-primary);
251
+ outline-offset: 2px;
252
+ }
253
+
241
254
  .strand-breadcrumb__current {
242
255
  color: var(--strand-gray-600);
243
256
  font-weight: var(--strand-weight-medium);
@@ -270,11 +283,11 @@
270
283
  cursor: pointer;
271
284
  user-select: none;
272
285
  transition:
273
- background var(--strand-duration-fast) ease,
274
- border-color var(--strand-duration-fast) ease,
275
- color var(--strand-duration-fast) ease,
286
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
287
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
288
+ color var(--strand-duration-fast) var(--strand-ease-out-quart),
276
289
  transform var(--strand-duration-fast) var(--strand-ease-out-expo),
277
- box-shadow var(--strand-duration-fast) ease;
290
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
278
291
  }
279
292
 
280
293
  .strand-btn:active:not(:disabled) {
@@ -336,7 +349,7 @@
336
349
  .strand-btn--primary:hover:not(:disabled) {
337
350
  background: var(--strand-blue-vivid);
338
351
  transform: translateY(-1px);
339
- box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2);
352
+ box-shadow: var(--strand-hover-shadow-primary);
340
353
  }
341
354
 
342
355
  .strand-btn--primary:active:not(:disabled) {
@@ -369,6 +382,7 @@
369
382
 
370
383
  .strand-btn--ghost:hover:not(:disabled) {
371
384
  background: var(--strand-blue-glow);
385
+ transform: translateY(-1px);
372
386
  }
373
387
 
374
388
  .strand-btn--ghost:active:not(:disabled) {
@@ -382,13 +396,13 @@
382
396
  }
383
397
 
384
398
  .strand-btn--danger:hover:not(:disabled) {
385
- background: #DC2626;
399
+ background: var(--strand-red-alert-vivid);
386
400
  transform: translateY(-1px);
387
- box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
401
+ box-shadow: var(--strand-hover-shadow-danger);
388
402
  }
389
403
 
390
404
  .strand-btn--danger:active:not(:disabled) {
391
- background: #B91C1C;
405
+ background: var(--strand-red-alert-deep);
392
406
  }
393
407
 
394
408
  /* ── Loading state ── */
@@ -425,6 +439,12 @@
425
439
  gap: var(--strand-space-2);
426
440
  }
427
441
 
442
+ /* ── Focus ring (Part XII: Accessibility Ring) ── */
443
+ .strand-btn:focus-visible {
444
+ outline: 2px solid var(--strand-blue-primary);
445
+ outline-offset: 2px;
446
+ }
447
+
428
448
  /* ── Reduced motion ── */
429
449
  @media (prefers-reduced-motion: reduce) {
430
450
  .strand-btn {
@@ -450,6 +470,9 @@
450
470
  border-radius: var(--strand-radius-lg);
451
471
  background: var(--strand-surface-elevated);
452
472
  font-family: var(--strand-font-sans);
473
+ overflow: hidden;
474
+ box-sizing: border-box;
475
+ max-width: 100%;
453
476
  }
454
477
 
455
478
  /* ── Variants ── */
@@ -481,15 +504,21 @@
481
504
  }
482
505
 
483
506
  .strand-card--pad-sm {
484
- padding: var(--strand-space-3);
507
+ padding: var(--strand-space-4);
485
508
  }
486
509
 
487
510
  .strand-card--pad-md {
488
- padding: var(--strand-space-5);
511
+ padding: var(--strand-space-6);
489
512
  }
490
513
 
491
514
  .strand-card--pad-lg {
492
- padding: var(--strand-space-8);
515
+ padding: var(--strand-space-10);
516
+ }
517
+
518
+ /* ── Focus ring (interactive cards) ── */
519
+ .strand-card--interactive:focus-visible {
520
+ outline: 2px solid var(--strand-blue-primary);
521
+ outline-offset: 2px;
493
522
  }
494
523
 
495
524
  /* ── Reduced motion ── */
@@ -546,9 +575,9 @@
546
575
  color: var(--strand-on-blue-primary);
547
576
  flex-shrink: 0;
548
577
  transition:
549
- background var(--strand-duration-fast) ease,
550
- border-color var(--strand-duration-fast) ease,
551
- box-shadow var(--strand-duration-fast) ease;
578
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
579
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
580
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
552
581
  }
553
582
 
554
583
  .strand-checkbox__icon {
@@ -559,7 +588,7 @@
559
588
  /* ── Focus ring ── */
560
589
  .strand-checkbox__native:focus-visible ~ .strand-checkbox__control {
561
590
  border-color: var(--strand-blue-primary);
562
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
591
+ box-shadow: var(--strand-focus-ring);
563
592
  }
564
593
 
565
594
  /* ── Checked ── */
@@ -664,6 +693,15 @@
664
693
  font-variant-numeric: tabular-nums;
665
694
  }
666
695
 
696
+ /* ── Size variants ── */
697
+ .strand-data-readout--sm .strand-data-readout__value {
698
+ font-size: var(--strand-text-xl);
699
+ }
700
+
701
+ .strand-data-readout--lg .strand-data-readout__value {
702
+ font-size: var(--strand-text-4xl);
703
+ }
704
+
667
705
 
668
706
  /* Dialog */
669
707
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -676,7 +714,7 @@
676
714
  display: flex;
677
715
  align-items: center;
678
716
  justify-content: center;
679
- background: rgba(15, 23, 42, 0.5);
717
+ background: var(--strand-backdrop);
680
718
  }
681
719
 
682
720
  /* ── Panel ── */
@@ -685,7 +723,7 @@
685
723
  width: 100%;
686
724
  max-width: 560px;
687
725
  margin: var(--strand-space-4);
688
- padding: var(--strand-space-6);
726
+ padding: var(--strand-space-8);
689
727
  background: var(--strand-surface-elevated);
690
728
  border-radius: var(--strand-radius-xl);
691
729
  box-shadow: var(--strand-elevation-4);
@@ -711,8 +749,8 @@
711
749
  /* ── Close button ── */
712
750
  .strand-dialog__close {
713
751
  position: absolute;
714
- top: var(--strand-space-4);
715
- right: var(--strand-space-4);
752
+ top: var(--strand-space-6);
753
+ right: var(--strand-space-6);
716
754
  display: inline-flex;
717
755
  align-items: center;
718
756
  justify-content: center;
@@ -725,8 +763,8 @@
725
763
  color: var(--strand-gray-500);
726
764
  font-size: var(--strand-text-lg);
727
765
  cursor: pointer;
728
- transition: background var(--strand-duration-fast) ease,
729
- color var(--strand-duration-fast) ease;
766
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart),
767
+ color var(--strand-duration-fast) var(--strand-ease-out-quart);
730
768
  }
731
769
 
732
770
  .strand-dialog__close:hover {
@@ -736,6 +774,7 @@
736
774
 
737
775
  /* ── Body ── */
738
776
  .strand-dialog__body {
777
+ padding-top: var(--strand-space-6);
739
778
  color: var(--strand-gray-600);
740
779
  font-size: var(--strand-text-sm);
741
780
  }
@@ -802,7 +841,7 @@
802
841
  .strand-form-field {
803
842
  display: flex;
804
843
  flex-direction: column;
805
- gap: var(--strand-space-1);
844
+ gap: var(--strand-space-2);
806
845
  }
807
846
 
808
847
  /* ── Label ── */
@@ -851,8 +890,29 @@
851
890
  /* ── Base ── */
852
891
  .strand-grid {
853
892
  display: grid;
893
+ overflow: hidden;
894
+ max-width: 100%;
895
+ box-sizing: border-box;
854
896
  }
855
897
 
898
+ .strand-grid > * {
899
+ min-width: 0;
900
+ }
901
+
902
+ /* ── Column utilities ── */
903
+ .strand-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
904
+ .strand-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
905
+ .strand-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
906
+
907
+ /* ── Gap utilities ── */
908
+ .strand-grid--gap-1 { gap: var(--strand-space-1); }
909
+ .strand-grid--gap-2 { gap: var(--strand-space-2); }
910
+ .strand-grid--gap-3 { gap: var(--strand-space-3); }
911
+ .strand-grid--gap-4 { gap: var(--strand-space-4); }
912
+ .strand-grid--gap-5 { gap: var(--strand-space-5); }
913
+ .strand-grid--gap-6 { gap: var(--strand-space-6); }
914
+ .strand-grid--gap-8 { gap: var(--strand-space-8); }
915
+
856
916
 
857
917
  /* Input */
858
918
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -865,13 +925,13 @@
865
925
  border: 1px solid var(--strand-gray-200);
866
926
  border-radius: var(--strand-radius-md);
867
927
  transition:
868
- border-color var(--strand-duration-fast) ease,
869
- box-shadow var(--strand-duration-fast) ease;
928
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
929
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
870
930
  }
871
931
 
872
932
  .strand-input:focus-within {
873
933
  border-color: var(--strand-blue-primary);
874
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
934
+ box-shadow: var(--strand-focus-ring);
875
935
  }
876
936
 
877
937
  /* ── Field ── */
@@ -923,7 +983,7 @@
923
983
 
924
984
  .strand-input--error:focus-within {
925
985
  border-color: var(--strand-red-alert);
926
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
986
+ box-shadow: var(--strand-focus-ring-error);
927
987
  }
928
988
 
929
989
  /* ── Disabled ── */
@@ -936,6 +996,13 @@
936
996
  cursor: not-allowed;
937
997
  }
938
998
 
999
+ /* ── Reduced motion ── */
1000
+ @media (prefers-reduced-motion: reduce) {
1001
+ .strand-input {
1002
+ transition: none;
1003
+ }
1004
+ }
1005
+
939
1006
 
940
1007
  /* Link */
941
1008
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -956,6 +1023,12 @@
956
1023
  background-size: 100% 1px;
957
1024
  }
958
1025
 
1026
+ /* ── Focus ring ── */
1027
+ .strand-link:focus-visible {
1028
+ outline: 2px solid var(--strand-blue-primary);
1029
+ outline-offset: 2px;
1030
+ }
1031
+
959
1032
  /* ── Reduced motion ── */
960
1033
  @media (prefers-reduced-motion: reduce) {
961
1034
  .strand-link {
@@ -1007,13 +1080,18 @@
1007
1080
  text-decoration: none;
1008
1081
  font-size: var(--strand-text-sm);
1009
1082
  font-weight: var(--strand-weight-medium);
1010
- transition: color var(--strand-duration-fast) ease;
1083
+ transition: color var(--strand-duration-fast) var(--strand-ease-out-quart);
1011
1084
  }
1012
1085
 
1013
1086
  .strand-nav__link:hover {
1014
1087
  color: var(--strand-gray-900);
1015
1088
  }
1016
1089
 
1090
+ .strand-nav__link:focus-visible {
1091
+ outline: 2px solid var(--strand-blue-primary);
1092
+ outline-offset: 2px;
1093
+ }
1094
+
1017
1095
  .strand-nav__link--active {
1018
1096
  color: var(--strand-blue-primary);
1019
1097
  font-weight: var(--strand-weight-medium);
@@ -1041,13 +1119,18 @@
1041
1119
  background: transparent;
1042
1120
  color: var(--strand-gray-600);
1043
1121
  cursor: pointer;
1044
- transition: background var(--strand-duration-fast) ease;
1122
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
1045
1123
  }
1046
1124
 
1047
1125
  .strand-nav__hamburger:hover {
1048
1126
  background: var(--strand-gray-200);
1049
1127
  }
1050
1128
 
1129
+ .strand-nav__hamburger:focus-visible {
1130
+ outline: 2px solid var(--strand-blue-primary);
1131
+ outline-offset: 2px;
1132
+ }
1133
+
1051
1134
  .strand-nav__hamburger-icon {
1052
1135
  display: block;
1053
1136
  width: 20px;
@@ -1091,7 +1174,7 @@
1091
1174
  text-decoration: none;
1092
1175
  font-size: var(--strand-text-sm);
1093
1176
  font-weight: var(--strand-weight-medium);
1094
- transition: color var(--strand-duration-fast) ease;
1177
+ transition: color var(--strand-duration-fast) var(--strand-ease-out-quart);
1095
1178
  }
1096
1179
 
1097
1180
  .strand-nav__mobile-link:hover {
@@ -1165,13 +1248,13 @@
1165
1248
  height: 100%;
1166
1249
  background: var(--strand-blue-primary);
1167
1250
  border-radius: var(--strand-radius-full);
1168
- transition: width var(--strand-duration-normal) ease;
1251
+ transition: width var(--strand-duration-normal) var(--strand-ease-out-quart);
1169
1252
  }
1170
1253
 
1171
1254
  /* ── Bar indeterminate ── */
1172
1255
  .strand-progress--bar.strand-progress--indeterminate .strand-progress__fill {
1173
1256
  width: 40%;
1174
- animation: strand-progress-shimmer 1.5s ease-in-out infinite;
1257
+ animation: strand-progress-shimmer 1.5s var(--strand-ease-in-out-sine) infinite;
1175
1258
  }
1176
1259
 
1177
1260
  @keyframes strand-progress-shimmer {
@@ -1200,7 +1283,7 @@
1200
1283
  }
1201
1284
 
1202
1285
  .strand-progress--ring .strand-progress__ring {
1203
- transition: stroke-dashoffset var(--strand-duration-normal) ease;
1286
+ transition: stroke-dashoffset var(--strand-duration-normal) var(--strand-ease-out-quart);
1204
1287
  }
1205
1288
 
1206
1289
  /* ── Ring indeterminate ── */
@@ -1273,9 +1356,9 @@
1273
1356
  background: var(--strand-surface-elevated);
1274
1357
  flex-shrink: 0;
1275
1358
  transition:
1276
- background var(--strand-duration-fast) ease,
1277
- border-color var(--strand-duration-fast) ease,
1278
- box-shadow var(--strand-duration-fast) ease;
1359
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1360
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
1361
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
1279
1362
  }
1280
1363
 
1281
1364
  .strand-radio__dot {
@@ -1290,7 +1373,7 @@
1290
1373
  /* ── Focus ring ── */
1291
1374
  .strand-radio__native:focus-visible ~ .strand-radio__control {
1292
1375
  border-color: var(--strand-blue-primary);
1293
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1376
+ box-shadow: var(--strand-focus-ring);
1294
1377
  }
1295
1378
 
1296
1379
  /* ── Checked ── */
@@ -1376,13 +1459,13 @@
1376
1459
  border: 1px solid var(--strand-gray-200);
1377
1460
  border-radius: var(--strand-radius-md);
1378
1461
  transition:
1379
- border-color var(--strand-duration-fast) ease,
1380
- box-shadow var(--strand-duration-fast) ease;
1462
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
1463
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
1381
1464
  }
1382
1465
 
1383
1466
  .strand-select:focus-within {
1384
1467
  border-color: var(--strand-blue-primary);
1385
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1468
+ box-shadow: var(--strand-focus-ring);
1386
1469
  }
1387
1470
 
1388
1471
  /* ── Field ── */
@@ -1421,7 +1504,7 @@
1421
1504
 
1422
1505
  .strand-select--error:focus-within {
1423
1506
  border-color: var(--strand-red-alert);
1424
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
1507
+ box-shadow: var(--strand-focus-ring-error);
1425
1508
  }
1426
1509
 
1427
1510
  /* ── Disabled ── */
@@ -1434,6 +1517,13 @@
1434
1517
  cursor: not-allowed;
1435
1518
  }
1436
1519
 
1520
+ /* ── Reduced motion ── */
1521
+ @media (prefers-reduced-motion: reduce) {
1522
+ .strand-select {
1523
+ transition: none;
1524
+ }
1525
+ }
1526
+
1437
1527
 
1438
1528
  /* Skeleton */
1439
1529
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -1453,7 +1543,7 @@
1453
1543
  var(--strand-gray-100) 75%
1454
1544
  );
1455
1545
  background-size: 200% 100%;
1456
- animation: strand-skeleton-shimmer 1.8s ease-in-out infinite;
1546
+ animation: strand-skeleton-shimmer 1.8s var(--strand-ease-in-out-sine) infinite;
1457
1547
  }
1458
1548
 
1459
1549
  @keyframes strand-skeleton-shimmer {
@@ -1468,7 +1558,7 @@
1468
1558
  /* ── Text variant ── */
1469
1559
  .strand-skeleton--text {
1470
1560
  height: 1em;
1471
- border-radius: 4px;
1561
+ border-radius: var(--strand-radius-sm);
1472
1562
  }
1473
1563
 
1474
1564
  /* ── Rectangle variant ── */
@@ -1510,7 +1600,7 @@
1510
1600
  border-radius: var(--strand-radius-full);
1511
1601
  outline: none;
1512
1602
  cursor: pointer;
1513
- transition: background var(--strand-duration-fast) ease;
1603
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
1514
1604
  }
1515
1605
 
1516
1606
  /* ── Thumb: Webkit ── */
@@ -1524,7 +1614,7 @@
1524
1614
  cursor: pointer;
1525
1615
  box-shadow: var(--strand-elevation-1);
1526
1616
  transition:
1527
- background var(--strand-duration-fast) ease,
1617
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1528
1618
  transform var(--strand-duration-fast) var(--strand-ease-out-expo);
1529
1619
  }
1530
1620
 
@@ -1548,7 +1638,7 @@
1548
1638
  cursor: pointer;
1549
1639
  box-shadow: var(--strand-elevation-1);
1550
1640
  transition:
1551
- background var(--strand-duration-fast) ease,
1641
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1552
1642
  transform var(--strand-duration-fast) var(--strand-ease-out-expo);
1553
1643
  }
1554
1644
 
@@ -1571,11 +1661,11 @@
1571
1661
 
1572
1662
  /* ── Focus ── */
1573
1663
  .strand-slider__field:focus-visible::-webkit-slider-thumb {
1574
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1664
+ box-shadow: var(--strand-focus-ring);
1575
1665
  }
1576
1666
 
1577
1667
  .strand-slider__field:focus-visible::-moz-range-thumb {
1578
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1668
+ box-shadow: var(--strand-focus-ring);
1579
1669
  }
1580
1670
 
1581
1671
  /* ── Disabled ── */
@@ -1670,6 +1760,12 @@
1670
1760
  /* ── Base ── */
1671
1761
  .strand-stack {
1672
1762
  display: flex;
1763
+ max-width: 100%;
1764
+ box-sizing: border-box;
1765
+ }
1766
+
1767
+ .strand-stack > * {
1768
+ min-width: 0;
1673
1769
  }
1674
1770
 
1675
1771
  /* ── Direction ── */
@@ -1722,6 +1818,15 @@
1722
1818
  flex-wrap: wrap;
1723
1819
  }
1724
1820
 
1821
+ /* ── Gap utilities ── */
1822
+ .strand-stack--gap-1 { gap: var(--strand-space-1); }
1823
+ .strand-stack--gap-2 { gap: var(--strand-space-2); }
1824
+ .strand-stack--gap-3 { gap: var(--strand-space-3); }
1825
+ .strand-stack--gap-4 { gap: var(--strand-space-4); }
1826
+ .strand-stack--gap-5 { gap: var(--strand-space-5); }
1827
+ .strand-stack--gap-6 { gap: var(--strand-space-6); }
1828
+ .strand-stack--gap-8 { gap: var(--strand-space-8); }
1829
+
1725
1830
 
1726
1831
  /* Switch */
1727
1832
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -1753,14 +1858,14 @@
1753
1858
  cursor: pointer;
1754
1859
  flex-shrink: 0;
1755
1860
  transition:
1756
- background var(--strand-duration-fast) ease,
1757
- border-color var(--strand-duration-fast) ease,
1758
- box-shadow var(--strand-duration-fast) ease;
1861
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1862
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
1863
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
1759
1864
  }
1760
1865
 
1761
1866
  .strand-switch__track:focus-visible {
1762
1867
  border-color: var(--strand-blue-primary);
1763
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1868
+ box-shadow: var(--strand-focus-ring);
1764
1869
  outline: none;
1765
1870
  }
1766
1871
 
@@ -1870,6 +1975,11 @@
1870
1975
  color: var(--strand-gray-600);
1871
1976
  }
1872
1977
 
1978
+ .strand-table__sort-btn:focus-visible {
1979
+ outline: 2px solid var(--strand-blue-primary);
1980
+ outline-offset: 2px;
1981
+ }
1982
+
1873
1983
  .strand-table__sort-indicator {
1874
1984
  font-size: var(--strand-text-xs);
1875
1985
  opacity: 0.6;
@@ -1886,7 +1996,7 @@
1886
1996
 
1887
1997
  /* ── Row hover ── */
1888
1998
  .strand-table__row {
1889
- transition: background var(--strand-duration-fast) ease;
1999
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
1890
2000
  }
1891
2001
 
1892
2002
  .strand-table__row:hover {
@@ -1924,14 +2034,19 @@
1924
2034
  color: var(--strand-gray-500);
1925
2035
  cursor: pointer;
1926
2036
  transition:
1927
- color var(--strand-duration-fast) ease,
1928
- border-color var(--strand-duration-normal) var(--strand-ease-out-expo);
2037
+ color var(--strand-duration-fast) var(--strand-ease-out-quart),
2038
+ border-color var(--strand-duration-fast) var(--strand-ease-out-expo);
1929
2039
  }
1930
2040
 
1931
2041
  .strand-tabs__tab:hover {
1932
2042
  color: var(--strand-gray-600);
1933
2043
  }
1934
2044
 
2045
+ .strand-tabs__tab:focus-visible {
2046
+ outline: 2px solid var(--strand-blue-primary);
2047
+ outline-offset: 2px;
2048
+ }
2049
+
1935
2050
  .strand-tabs__tab--active {
1936
2051
  color: var(--strand-blue-primary);
1937
2052
  border-bottom-color: var(--strand-blue-primary);
@@ -1976,7 +2091,7 @@
1976
2091
 
1977
2092
  .strand-tag--solid.strand-tag--teal {
1978
2093
  background: rgba(20, 184, 166, 0.1);
1979
- color: #0D7377;
2094
+ color: var(--strand-on-teal-tint);
1980
2095
  }
1981
2096
 
1982
2097
  .strand-tag--solid.strand-tag--blue {
@@ -1986,12 +2101,12 @@
1986
2101
 
1987
2102
  .strand-tag--solid.strand-tag--amber {
1988
2103
  background: rgba(245, 158, 11, 0.1);
1989
- color: #92400E;
2104
+ color: var(--strand-on-amber-tint);
1990
2105
  }
1991
2106
 
1992
2107
  .strand-tag--solid.strand-tag--red {
1993
2108
  background: rgba(239, 68, 68, 0.1);
1994
- color: #991B1B;
2109
+ color: var(--strand-on-red-tint);
1995
2110
  }
1996
2111
 
1997
2112
  /* ── Outlined variant ── */
@@ -2006,7 +2121,7 @@
2006
2121
 
2007
2122
  .strand-tag--outlined.strand-tag--teal {
2008
2123
  border-color: var(--strand-teal-vital);
2009
- color: #0D7377;
2124
+ color: var(--strand-on-teal-tint);
2010
2125
  }
2011
2126
 
2012
2127
  .strand-tag--outlined.strand-tag--blue {
@@ -2016,12 +2131,12 @@
2016
2131
 
2017
2132
  .strand-tag--outlined.strand-tag--amber {
2018
2133
  border-color: var(--strand-amber-caution);
2019
- color: #92400E;
2134
+ color: var(--strand-on-amber-tint);
2020
2135
  }
2021
2136
 
2022
2137
  .strand-tag--outlined.strand-tag--red {
2023
2138
  border-color: var(--strand-red-alert);
2024
- color: #991B1B;
2139
+ color: var(--strand-on-red-tint);
2025
2140
  }
2026
2141
 
2027
2142
  /* ── Remove button ── */
@@ -2036,7 +2151,7 @@
2036
2151
  opacity: 0.6;
2037
2152
  cursor: pointer;
2038
2153
  border-radius: var(--strand-radius-sm);
2039
- transition: opacity var(--strand-duration-fast) ease;
2154
+ transition: opacity var(--strand-duration-fast) var(--strand-ease-out-quart);
2040
2155
  }
2041
2156
 
2042
2157
  .strand-tag__remove:hover {
@@ -2063,13 +2178,13 @@
2063
2178
  border: 1px solid var(--strand-gray-200);
2064
2179
  border-radius: var(--strand-radius-md);
2065
2180
  transition:
2066
- border-color var(--strand-duration-fast) ease,
2067
- box-shadow var(--strand-duration-fast) ease;
2181
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
2182
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
2068
2183
  }
2069
2184
 
2070
2185
  .strand-textarea:focus-within {
2071
2186
  border-color: var(--strand-blue-primary);
2072
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
2187
+ box-shadow: var(--strand-focus-ring);
2073
2188
  }
2074
2189
 
2075
2190
  /* ── Field ── */
@@ -2113,7 +2228,7 @@
2113
2228
 
2114
2229
  .strand-textarea--error:focus-within {
2115
2230
  border-color: var(--strand-red-alert);
2116
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
2231
+ box-shadow: var(--strand-focus-ring-error);
2117
2232
  }
2118
2233
 
2119
2234
  /* ── Disabled ── */
@@ -2126,6 +2241,13 @@
2126
2241
  cursor: not-allowed;
2127
2242
  }
2128
2243
 
2244
+ /* ── Reduced motion ── */
2245
+ @media (prefers-reduced-motion: reduce) {
2246
+ .strand-textarea {
2247
+ transition: none;
2248
+ }
2249
+ }
2250
+
2129
2251
 
2130
2252
  /* Toast */
2131
2253
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -2149,7 +2271,7 @@
2149
2271
  justify-content: space-between;
2150
2272
  min-width: 280px;
2151
2273
  max-width: 420px;
2152
- padding: var(--strand-space-4);
2274
+ padding: var(--strand-space-4) var(--strand-space-5);
2153
2275
  background: var(--strand-surface-elevated);
2154
2276
  border-radius: var(--strand-radius-lg);
2155
2277
  border-left: 4px solid transparent;
@@ -2200,8 +2322,8 @@
2200
2322
  color: var(--strand-gray-500);
2201
2323
  font-size: var(--strand-text-base);
2202
2324
  cursor: pointer;
2203
- transition: background var(--strand-duration-fast) ease,
2204
- color var(--strand-duration-fast) ease;
2325
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart),
2326
+ color var(--strand-duration-fast) var(--strand-ease-out-quart);
2205
2327
  }
2206
2328
 
2207
2329
  .strand-toast__dismiss:hover {
@@ -2248,14 +2370,14 @@
2248
2370
  z-index: 1200;
2249
2371
  padding: var(--strand-space-1) var(--strand-space-2);
2250
2372
  background: var(--strand-gray-900);
2251
- color: #fff;
2373
+ color: var(--strand-on-blue-primary);
2252
2374
  font-family: var(--strand-font-sans);
2253
2375
  font-size: var(--strand-text-xs);
2254
2376
  border-radius: var(--strand-radius-md);
2255
2377
  white-space: nowrap;
2256
2378
  pointer-events: none;
2257
2379
  opacity: 0;
2258
- transition: opacity var(--strand-duration-fast) ease;
2380
+ transition: opacity var(--strand-duration-fast) var(--strand-ease-out-quart);
2259
2381
  }
2260
2382
 
2261
2383
  .strand-tooltip--visible {
@@ -2299,3 +2421,53 @@
2299
2421
  }
2300
2422
 
2301
2423
 
2424
+ /* Static */
2425
+ /*! Strand UI | MIT License | dillingerstaffing.com */
2426
+
2427
+ /* ── Presentation mode ──
2428
+ Add .strand-static to a parent element to render components
2429
+ at full visual fidelity without interaction.
2430
+ Use for documentation, showcases, and screenshots. */
2431
+
2432
+ .strand-static {
2433
+ pointer-events: none;
2434
+ }
2435
+
2436
+ .strand-static [disabled],
2437
+ .strand-static [aria-disabled="true"] {
2438
+ opacity: 1;
2439
+ cursor: default;
2440
+ }
2441
+
2442
+ .strand-static .strand-btn:disabled {
2443
+ opacity: 1;
2444
+ cursor: default;
2445
+ }
2446
+
2447
+ .strand-static .strand-toast {
2448
+ position: static;
2449
+ }
2450
+
2451
+ .strand-static .strand-tooltip {
2452
+ position: static;
2453
+ }
2454
+
2455
+ .strand-static *,
2456
+ .strand-static *::before,
2457
+ .strand-static *::after {
2458
+ transition: none !important;
2459
+ animation: none !important;
2460
+ }
2461
+
2462
+ /* ── Recessed viewport ──
2463
+ The instrument viewport sits below the card surface.
2464
+ Use for component previews, showcases, and documentation. */
2465
+
2466
+ .strand-viewport {
2467
+ background: var(--strand-surface-recessed);
2468
+ box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06);
2469
+ border-radius: var(--strand-radius-lg);
2470
+ padding: var(--strand-space-6);
2471
+ }
2472
+
2473
+