@dillingerstaffing/strand-ui 0.1.1 → 0.2.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.
Files changed (33) hide show
  1. package/dist/css/strand-ui.css +239 -76
  2. package/dist/index.d.ts +1 -1
  3. package/package.json +15 -2
  4. package/src/__tests__/build-output.test.ts +123 -0
  5. package/src/__tests__/design-language.test.ts +137 -0
  6. package/src/__tests__/static.test.tsx +60 -0
  7. package/src/components/Alert/Alert.css +11 -3
  8. package/src/components/Badge/Badge.css +1 -1
  9. package/src/components/Breadcrumb/Breadcrumb.css +6 -1
  10. package/src/components/Button/Button.css +15 -8
  11. package/src/components/Card/Card.css +12 -3
  12. package/src/components/Checkbox/Checkbox.css +4 -4
  13. package/src/components/Dialog/Dialog.css +7 -6
  14. package/src/components/FormField/FormField.css +1 -1
  15. package/src/components/Grid/Grid.css +21 -0
  16. package/src/components/Input/Input.css +11 -4
  17. package/src/components/Link/Link.css +6 -0
  18. package/src/components/Nav/Nav.css +13 -3
  19. package/src/components/Progress/Progress.css +3 -3
  20. package/src/components/Radio/Radio.css +4 -4
  21. package/src/components/Select/Select.css +11 -4
  22. package/src/components/Skeleton/Skeleton.css +2 -2
  23. package/src/components/Slider/Slider.css +5 -5
  24. package/src/components/Stack/Stack.css +15 -0
  25. package/src/components/Switch/Switch.css +4 -4
  26. package/src/components/Table/Table.css +6 -1
  27. package/src/components/Tabs/Tabs.css +7 -2
  28. package/src/components/Tag/Tag.css +7 -7
  29. package/src/components/Textarea/Textarea.css +11 -4
  30. package/src/components/Toast/Toast.css +3 -3
  31. package/src/components/Tooltip/Tooltip.css +2 -2
  32. package/src/index.ts +1 -1
  33. package/src/static.css +47 -0
@@ -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 ── */
@@ -676,7 +705,7 @@
676
705
  display: flex;
677
706
  align-items: center;
678
707
  justify-content: center;
679
- background: rgba(15, 23, 42, 0.5);
708
+ background: var(--strand-backdrop);
680
709
  }
681
710
 
682
711
  /* ── Panel ── */
@@ -685,7 +714,7 @@
685
714
  width: 100%;
686
715
  max-width: 560px;
687
716
  margin: var(--strand-space-4);
688
- padding: var(--strand-space-6);
717
+ padding: var(--strand-space-8);
689
718
  background: var(--strand-surface-elevated);
690
719
  border-radius: var(--strand-radius-xl);
691
720
  box-shadow: var(--strand-elevation-4);
@@ -711,8 +740,8 @@
711
740
  /* ── Close button ── */
712
741
  .strand-dialog__close {
713
742
  position: absolute;
714
- top: var(--strand-space-4);
715
- right: var(--strand-space-4);
743
+ top: var(--strand-space-6);
744
+ right: var(--strand-space-6);
716
745
  display: inline-flex;
717
746
  align-items: center;
718
747
  justify-content: center;
@@ -725,8 +754,8 @@
725
754
  color: var(--strand-gray-500);
726
755
  font-size: var(--strand-text-lg);
727
756
  cursor: pointer;
728
- transition: background var(--strand-duration-fast) ease,
729
- color var(--strand-duration-fast) ease;
757
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart),
758
+ color var(--strand-duration-fast) var(--strand-ease-out-quart);
730
759
  }
731
760
 
732
761
  .strand-dialog__close:hover {
@@ -736,6 +765,7 @@
736
765
 
737
766
  /* ── Body ── */
738
767
  .strand-dialog__body {
768
+ padding-top: var(--strand-space-6);
739
769
  color: var(--strand-gray-600);
740
770
  font-size: var(--strand-text-sm);
741
771
  }
@@ -802,7 +832,7 @@
802
832
  .strand-form-field {
803
833
  display: flex;
804
834
  flex-direction: column;
805
- gap: var(--strand-space-1);
835
+ gap: var(--strand-space-2);
806
836
  }
807
837
 
808
838
  /* ── Label ── */
@@ -851,8 +881,29 @@
851
881
  /* ── Base ── */
852
882
  .strand-grid {
853
883
  display: grid;
884
+ overflow: hidden;
885
+ max-width: 100%;
886
+ box-sizing: border-box;
887
+ }
888
+
889
+ .strand-grid > * {
890
+ min-width: 0;
854
891
  }
855
892
 
893
+ /* ── Column utilities ── */
894
+ .strand-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
895
+ .strand-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
896
+ .strand-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
897
+
898
+ /* ── Gap utilities ── */
899
+ .strand-grid--gap-1 { gap: var(--strand-space-1); }
900
+ .strand-grid--gap-2 { gap: var(--strand-space-2); }
901
+ .strand-grid--gap-3 { gap: var(--strand-space-3); }
902
+ .strand-grid--gap-4 { gap: var(--strand-space-4); }
903
+ .strand-grid--gap-5 { gap: var(--strand-space-5); }
904
+ .strand-grid--gap-6 { gap: var(--strand-space-6); }
905
+ .strand-grid--gap-8 { gap: var(--strand-space-8); }
906
+
856
907
 
857
908
  /* Input */
858
909
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -865,13 +916,13 @@
865
916
  border: 1px solid var(--strand-gray-200);
866
917
  border-radius: var(--strand-radius-md);
867
918
  transition:
868
- border-color var(--strand-duration-fast) ease,
869
- box-shadow var(--strand-duration-fast) ease;
919
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
920
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
870
921
  }
871
922
 
872
923
  .strand-input:focus-within {
873
924
  border-color: var(--strand-blue-primary);
874
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
925
+ box-shadow: var(--strand-focus-ring);
875
926
  }
876
927
 
877
928
  /* ── Field ── */
@@ -923,7 +974,7 @@
923
974
 
924
975
  .strand-input--error:focus-within {
925
976
  border-color: var(--strand-red-alert);
926
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
977
+ box-shadow: var(--strand-focus-ring-error);
927
978
  }
928
979
 
929
980
  /* ── Disabled ── */
@@ -936,6 +987,13 @@
936
987
  cursor: not-allowed;
937
988
  }
938
989
 
990
+ /* ── Reduced motion ── */
991
+ @media (prefers-reduced-motion: reduce) {
992
+ .strand-input {
993
+ transition: none;
994
+ }
995
+ }
996
+
939
997
 
940
998
  /* Link */
941
999
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -956,6 +1014,12 @@
956
1014
  background-size: 100% 1px;
957
1015
  }
958
1016
 
1017
+ /* ── Focus ring ── */
1018
+ .strand-link:focus-visible {
1019
+ outline: 2px solid var(--strand-blue-primary);
1020
+ outline-offset: 2px;
1021
+ }
1022
+
959
1023
  /* ── Reduced motion ── */
960
1024
  @media (prefers-reduced-motion: reduce) {
961
1025
  .strand-link {
@@ -1007,13 +1071,18 @@
1007
1071
  text-decoration: none;
1008
1072
  font-size: var(--strand-text-sm);
1009
1073
  font-weight: var(--strand-weight-medium);
1010
- transition: color var(--strand-duration-fast) ease;
1074
+ transition: color var(--strand-duration-fast) var(--strand-ease-out-quart);
1011
1075
  }
1012
1076
 
1013
1077
  .strand-nav__link:hover {
1014
1078
  color: var(--strand-gray-900);
1015
1079
  }
1016
1080
 
1081
+ .strand-nav__link:focus-visible {
1082
+ outline: 2px solid var(--strand-blue-primary);
1083
+ outline-offset: 2px;
1084
+ }
1085
+
1017
1086
  .strand-nav__link--active {
1018
1087
  color: var(--strand-blue-primary);
1019
1088
  font-weight: var(--strand-weight-medium);
@@ -1041,13 +1110,18 @@
1041
1110
  background: transparent;
1042
1111
  color: var(--strand-gray-600);
1043
1112
  cursor: pointer;
1044
- transition: background var(--strand-duration-fast) ease;
1113
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
1045
1114
  }
1046
1115
 
1047
1116
  .strand-nav__hamburger:hover {
1048
1117
  background: var(--strand-gray-200);
1049
1118
  }
1050
1119
 
1120
+ .strand-nav__hamburger:focus-visible {
1121
+ outline: 2px solid var(--strand-blue-primary);
1122
+ outline-offset: 2px;
1123
+ }
1124
+
1051
1125
  .strand-nav__hamburger-icon {
1052
1126
  display: block;
1053
1127
  width: 20px;
@@ -1091,7 +1165,7 @@
1091
1165
  text-decoration: none;
1092
1166
  font-size: var(--strand-text-sm);
1093
1167
  font-weight: var(--strand-weight-medium);
1094
- transition: color var(--strand-duration-fast) ease;
1168
+ transition: color var(--strand-duration-fast) var(--strand-ease-out-quart);
1095
1169
  }
1096
1170
 
1097
1171
  .strand-nav__mobile-link:hover {
@@ -1165,13 +1239,13 @@
1165
1239
  height: 100%;
1166
1240
  background: var(--strand-blue-primary);
1167
1241
  border-radius: var(--strand-radius-full);
1168
- transition: width var(--strand-duration-normal) ease;
1242
+ transition: width var(--strand-duration-normal) var(--strand-ease-out-quart);
1169
1243
  }
1170
1244
 
1171
1245
  /* ── Bar indeterminate ── */
1172
1246
  .strand-progress--bar.strand-progress--indeterminate .strand-progress__fill {
1173
1247
  width: 40%;
1174
- animation: strand-progress-shimmer 1.5s ease-in-out infinite;
1248
+ animation: strand-progress-shimmer 1.5s var(--strand-ease-in-out-sine) infinite;
1175
1249
  }
1176
1250
 
1177
1251
  @keyframes strand-progress-shimmer {
@@ -1200,7 +1274,7 @@
1200
1274
  }
1201
1275
 
1202
1276
  .strand-progress--ring .strand-progress__ring {
1203
- transition: stroke-dashoffset var(--strand-duration-normal) ease;
1277
+ transition: stroke-dashoffset var(--strand-duration-normal) var(--strand-ease-out-quart);
1204
1278
  }
1205
1279
 
1206
1280
  /* ── Ring indeterminate ── */
@@ -1273,9 +1347,9 @@
1273
1347
  background: var(--strand-surface-elevated);
1274
1348
  flex-shrink: 0;
1275
1349
  transition:
1276
- background var(--strand-duration-fast) ease,
1277
- border-color var(--strand-duration-fast) ease,
1278
- box-shadow var(--strand-duration-fast) ease;
1350
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1351
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
1352
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
1279
1353
  }
1280
1354
 
1281
1355
  .strand-radio__dot {
@@ -1290,7 +1364,7 @@
1290
1364
  /* ── Focus ring ── */
1291
1365
  .strand-radio__native:focus-visible ~ .strand-radio__control {
1292
1366
  border-color: var(--strand-blue-primary);
1293
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1367
+ box-shadow: var(--strand-focus-ring);
1294
1368
  }
1295
1369
 
1296
1370
  /* ── Checked ── */
@@ -1376,13 +1450,13 @@
1376
1450
  border: 1px solid var(--strand-gray-200);
1377
1451
  border-radius: var(--strand-radius-md);
1378
1452
  transition:
1379
- border-color var(--strand-duration-fast) ease,
1380
- box-shadow var(--strand-duration-fast) ease;
1453
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
1454
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
1381
1455
  }
1382
1456
 
1383
1457
  .strand-select:focus-within {
1384
1458
  border-color: var(--strand-blue-primary);
1385
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1459
+ box-shadow: var(--strand-focus-ring);
1386
1460
  }
1387
1461
 
1388
1462
  /* ── Field ── */
@@ -1421,7 +1495,7 @@
1421
1495
 
1422
1496
  .strand-select--error:focus-within {
1423
1497
  border-color: var(--strand-red-alert);
1424
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
1498
+ box-shadow: var(--strand-focus-ring-error);
1425
1499
  }
1426
1500
 
1427
1501
  /* ── Disabled ── */
@@ -1434,6 +1508,13 @@
1434
1508
  cursor: not-allowed;
1435
1509
  }
1436
1510
 
1511
+ /* ── Reduced motion ── */
1512
+ @media (prefers-reduced-motion: reduce) {
1513
+ .strand-select {
1514
+ transition: none;
1515
+ }
1516
+ }
1517
+
1437
1518
 
1438
1519
  /* Skeleton */
1439
1520
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -1453,7 +1534,7 @@
1453
1534
  var(--strand-gray-100) 75%
1454
1535
  );
1455
1536
  background-size: 200% 100%;
1456
- animation: strand-skeleton-shimmer 1.8s ease-in-out infinite;
1537
+ animation: strand-skeleton-shimmer 1.8s var(--strand-ease-in-out-sine) infinite;
1457
1538
  }
1458
1539
 
1459
1540
  @keyframes strand-skeleton-shimmer {
@@ -1468,7 +1549,7 @@
1468
1549
  /* ── Text variant ── */
1469
1550
  .strand-skeleton--text {
1470
1551
  height: 1em;
1471
- border-radius: 4px;
1552
+ border-radius: var(--strand-radius-sm);
1472
1553
  }
1473
1554
 
1474
1555
  /* ── Rectangle variant ── */
@@ -1510,7 +1591,7 @@
1510
1591
  border-radius: var(--strand-radius-full);
1511
1592
  outline: none;
1512
1593
  cursor: pointer;
1513
- transition: background var(--strand-duration-fast) ease;
1594
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
1514
1595
  }
1515
1596
 
1516
1597
  /* ── Thumb: Webkit ── */
@@ -1524,7 +1605,7 @@
1524
1605
  cursor: pointer;
1525
1606
  box-shadow: var(--strand-elevation-1);
1526
1607
  transition:
1527
- background var(--strand-duration-fast) ease,
1608
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1528
1609
  transform var(--strand-duration-fast) var(--strand-ease-out-expo);
1529
1610
  }
1530
1611
 
@@ -1548,7 +1629,7 @@
1548
1629
  cursor: pointer;
1549
1630
  box-shadow: var(--strand-elevation-1);
1550
1631
  transition:
1551
- background var(--strand-duration-fast) ease,
1632
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1552
1633
  transform var(--strand-duration-fast) var(--strand-ease-out-expo);
1553
1634
  }
1554
1635
 
@@ -1571,11 +1652,11 @@
1571
1652
 
1572
1653
  /* ── Focus ── */
1573
1654
  .strand-slider__field:focus-visible::-webkit-slider-thumb {
1574
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1655
+ box-shadow: var(--strand-focus-ring);
1575
1656
  }
1576
1657
 
1577
1658
  .strand-slider__field:focus-visible::-moz-range-thumb {
1578
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1659
+ box-shadow: var(--strand-focus-ring);
1579
1660
  }
1580
1661
 
1581
1662
  /* ── Disabled ── */
@@ -1670,6 +1751,12 @@
1670
1751
  /* ── Base ── */
1671
1752
  .strand-stack {
1672
1753
  display: flex;
1754
+ max-width: 100%;
1755
+ box-sizing: border-box;
1756
+ }
1757
+
1758
+ .strand-stack > * {
1759
+ min-width: 0;
1673
1760
  }
1674
1761
 
1675
1762
  /* ── Direction ── */
@@ -1722,6 +1809,15 @@
1722
1809
  flex-wrap: wrap;
1723
1810
  }
1724
1811
 
1812
+ /* ── Gap utilities ── */
1813
+ .strand-stack--gap-1 { gap: var(--strand-space-1); }
1814
+ .strand-stack--gap-2 { gap: var(--strand-space-2); }
1815
+ .strand-stack--gap-3 { gap: var(--strand-space-3); }
1816
+ .strand-stack--gap-4 { gap: var(--strand-space-4); }
1817
+ .strand-stack--gap-5 { gap: var(--strand-space-5); }
1818
+ .strand-stack--gap-6 { gap: var(--strand-space-6); }
1819
+ .strand-stack--gap-8 { gap: var(--strand-space-8); }
1820
+
1725
1821
 
1726
1822
  /* Switch */
1727
1823
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -1753,14 +1849,14 @@
1753
1849
  cursor: pointer;
1754
1850
  flex-shrink: 0;
1755
1851
  transition:
1756
- background var(--strand-duration-fast) ease,
1757
- border-color var(--strand-duration-fast) ease,
1758
- box-shadow var(--strand-duration-fast) ease;
1852
+ background var(--strand-duration-fast) var(--strand-ease-out-quart),
1853
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
1854
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
1759
1855
  }
1760
1856
 
1761
1857
  .strand-switch__track:focus-visible {
1762
1858
  border-color: var(--strand-blue-primary);
1763
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1859
+ box-shadow: var(--strand-focus-ring);
1764
1860
  outline: none;
1765
1861
  }
1766
1862
 
@@ -1870,6 +1966,11 @@
1870
1966
  color: var(--strand-gray-600);
1871
1967
  }
1872
1968
 
1969
+ .strand-table__sort-btn:focus-visible {
1970
+ outline: 2px solid var(--strand-blue-primary);
1971
+ outline-offset: 2px;
1972
+ }
1973
+
1873
1974
  .strand-table__sort-indicator {
1874
1975
  font-size: var(--strand-text-xs);
1875
1976
  opacity: 0.6;
@@ -1886,7 +1987,7 @@
1886
1987
 
1887
1988
  /* ── Row hover ── */
1888
1989
  .strand-table__row {
1889
- transition: background var(--strand-duration-fast) ease;
1990
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart);
1890
1991
  }
1891
1992
 
1892
1993
  .strand-table__row:hover {
@@ -1924,14 +2025,19 @@
1924
2025
  color: var(--strand-gray-500);
1925
2026
  cursor: pointer;
1926
2027
  transition:
1927
- color var(--strand-duration-fast) ease,
1928
- border-color var(--strand-duration-normal) var(--strand-ease-out-expo);
2028
+ color var(--strand-duration-fast) var(--strand-ease-out-quart),
2029
+ border-color var(--strand-duration-fast) var(--strand-ease-out-expo);
1929
2030
  }
1930
2031
 
1931
2032
  .strand-tabs__tab:hover {
1932
2033
  color: var(--strand-gray-600);
1933
2034
  }
1934
2035
 
2036
+ .strand-tabs__tab:focus-visible {
2037
+ outline: 2px solid var(--strand-blue-primary);
2038
+ outline-offset: 2px;
2039
+ }
2040
+
1935
2041
  .strand-tabs__tab--active {
1936
2042
  color: var(--strand-blue-primary);
1937
2043
  border-bottom-color: var(--strand-blue-primary);
@@ -1976,7 +2082,7 @@
1976
2082
 
1977
2083
  .strand-tag--solid.strand-tag--teal {
1978
2084
  background: rgba(20, 184, 166, 0.1);
1979
- color: #0D7377;
2085
+ color: var(--strand-on-teal-tint);
1980
2086
  }
1981
2087
 
1982
2088
  .strand-tag--solid.strand-tag--blue {
@@ -1986,12 +2092,12 @@
1986
2092
 
1987
2093
  .strand-tag--solid.strand-tag--amber {
1988
2094
  background: rgba(245, 158, 11, 0.1);
1989
- color: #92400E;
2095
+ color: var(--strand-on-amber-tint);
1990
2096
  }
1991
2097
 
1992
2098
  .strand-tag--solid.strand-tag--red {
1993
2099
  background: rgba(239, 68, 68, 0.1);
1994
- color: #991B1B;
2100
+ color: var(--strand-on-red-tint);
1995
2101
  }
1996
2102
 
1997
2103
  /* ── Outlined variant ── */
@@ -2006,7 +2112,7 @@
2006
2112
 
2007
2113
  .strand-tag--outlined.strand-tag--teal {
2008
2114
  border-color: var(--strand-teal-vital);
2009
- color: #0D7377;
2115
+ color: var(--strand-on-teal-tint);
2010
2116
  }
2011
2117
 
2012
2118
  .strand-tag--outlined.strand-tag--blue {
@@ -2016,12 +2122,12 @@
2016
2122
 
2017
2123
  .strand-tag--outlined.strand-tag--amber {
2018
2124
  border-color: var(--strand-amber-caution);
2019
- color: #92400E;
2125
+ color: var(--strand-on-amber-tint);
2020
2126
  }
2021
2127
 
2022
2128
  .strand-tag--outlined.strand-tag--red {
2023
2129
  border-color: var(--strand-red-alert);
2024
- color: #991B1B;
2130
+ color: var(--strand-on-red-tint);
2025
2131
  }
2026
2132
 
2027
2133
  /* ── Remove button ── */
@@ -2036,7 +2142,7 @@
2036
2142
  opacity: 0.6;
2037
2143
  cursor: pointer;
2038
2144
  border-radius: var(--strand-radius-sm);
2039
- transition: opacity var(--strand-duration-fast) ease;
2145
+ transition: opacity var(--strand-duration-fast) var(--strand-ease-out-quart);
2040
2146
  }
2041
2147
 
2042
2148
  .strand-tag__remove:hover {
@@ -2063,13 +2169,13 @@
2063
2169
  border: 1px solid var(--strand-gray-200);
2064
2170
  border-radius: var(--strand-radius-md);
2065
2171
  transition:
2066
- border-color var(--strand-duration-fast) ease,
2067
- box-shadow var(--strand-duration-fast) ease;
2172
+ border-color var(--strand-duration-fast) var(--strand-ease-out-quart),
2173
+ box-shadow var(--strand-duration-fast) var(--strand-ease-out-quart);
2068
2174
  }
2069
2175
 
2070
2176
  .strand-textarea:focus-within {
2071
2177
  border-color: var(--strand-blue-primary);
2072
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
2178
+ box-shadow: var(--strand-focus-ring);
2073
2179
  }
2074
2180
 
2075
2181
  /* ── Field ── */
@@ -2113,7 +2219,7 @@
2113
2219
 
2114
2220
  .strand-textarea--error:focus-within {
2115
2221
  border-color: var(--strand-red-alert);
2116
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
2222
+ box-shadow: var(--strand-focus-ring-error);
2117
2223
  }
2118
2224
 
2119
2225
  /* ── Disabled ── */
@@ -2126,6 +2232,13 @@
2126
2232
  cursor: not-allowed;
2127
2233
  }
2128
2234
 
2235
+ /* ── Reduced motion ── */
2236
+ @media (prefers-reduced-motion: reduce) {
2237
+ .strand-textarea {
2238
+ transition: none;
2239
+ }
2240
+ }
2241
+
2129
2242
 
2130
2243
  /* Toast */
2131
2244
  /*! Strand UI | MIT License | dillingerstaffing.com */
@@ -2149,7 +2262,7 @@
2149
2262
  justify-content: space-between;
2150
2263
  min-width: 280px;
2151
2264
  max-width: 420px;
2152
- padding: var(--strand-space-4);
2265
+ padding: var(--strand-space-4) var(--strand-space-5);
2153
2266
  background: var(--strand-surface-elevated);
2154
2267
  border-radius: var(--strand-radius-lg);
2155
2268
  border-left: 4px solid transparent;
@@ -2200,8 +2313,8 @@
2200
2313
  color: var(--strand-gray-500);
2201
2314
  font-size: var(--strand-text-base);
2202
2315
  cursor: pointer;
2203
- transition: background var(--strand-duration-fast) ease,
2204
- color var(--strand-duration-fast) ease;
2316
+ transition: background var(--strand-duration-fast) var(--strand-ease-out-quart),
2317
+ color var(--strand-duration-fast) var(--strand-ease-out-quart);
2205
2318
  }
2206
2319
 
2207
2320
  .strand-toast__dismiss:hover {
@@ -2248,14 +2361,14 @@
2248
2361
  z-index: 1200;
2249
2362
  padding: var(--strand-space-1) var(--strand-space-2);
2250
2363
  background: var(--strand-gray-900);
2251
- color: #fff;
2364
+ color: var(--strand-on-blue-primary);
2252
2365
  font-family: var(--strand-font-sans);
2253
2366
  font-size: var(--strand-text-xs);
2254
2367
  border-radius: var(--strand-radius-md);
2255
2368
  white-space: nowrap;
2256
2369
  pointer-events: none;
2257
2370
  opacity: 0;
2258
- transition: opacity var(--strand-duration-fast) ease;
2371
+ transition: opacity var(--strand-duration-fast) var(--strand-ease-out-quart);
2259
2372
  }
2260
2373
 
2261
2374
  .strand-tooltip--visible {
@@ -2299,3 +2412,53 @@
2299
2412
  }
2300
2413
 
2301
2414
 
2415
+ /* Static */
2416
+ /*! Strand UI | MIT License | dillingerstaffing.com */
2417
+
2418
+ /* ── Presentation mode ──
2419
+ Add .strand-static to a parent element to render components
2420
+ at full visual fidelity without interaction.
2421
+ Use for documentation, showcases, and screenshots. */
2422
+
2423
+ .strand-static {
2424
+ pointer-events: none;
2425
+ }
2426
+
2427
+ .strand-static [disabled],
2428
+ .strand-static [aria-disabled="true"] {
2429
+ opacity: 1;
2430
+ cursor: default;
2431
+ }
2432
+
2433
+ .strand-static .strand-btn:disabled {
2434
+ opacity: 1;
2435
+ cursor: default;
2436
+ }
2437
+
2438
+ .strand-static .strand-toast {
2439
+ position: static;
2440
+ }
2441
+
2442
+ .strand-static .strand-tooltip {
2443
+ position: static;
2444
+ }
2445
+
2446
+ .strand-static *,
2447
+ .strand-static *::before,
2448
+ .strand-static *::after {
2449
+ transition: none !important;
2450
+ animation: none !important;
2451
+ }
2452
+
2453
+ /* ── Recessed viewport ──
2454
+ The instrument viewport sits below the card surface.
2455
+ Use for component previews, showcases, and documentation. */
2456
+
2457
+ .strand-viewport {
2458
+ background: var(--strand-surface-recessed);
2459
+ box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06);
2460
+ border-radius: var(--strand-radius-lg);
2461
+ padding: var(--strand-space-6);
2462
+ }
2463
+
2464
+