@swiftbrowser/web 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +141 -63
  2. package/app/index.html +21 -10
  3. package/node/dist/node/apps.d.ts +16 -0
  4. package/node/dist/node/apps.d.ts.map +1 -1
  5. package/node/dist/node/apps.js +9 -0
  6. package/node/dist/node/apps.js.map +1 -1
  7. package/node/dist/node/config.d.ts +14 -1
  8. package/node/dist/node/config.d.ts.map +1 -1
  9. package/node/dist/node/config.js +20 -3
  10. package/node/dist/node/config.js.map +1 -1
  11. package/node/dist/node/examples.d.ts +2 -1
  12. package/node/dist/node/examples.d.ts.map +1 -1
  13. package/node/dist/node/examples.js +12 -2
  14. package/node/dist/node/examples.js.map +1 -1
  15. package/node/dist/plugins/app-manifests.d.ts +4 -1
  16. package/node/dist/plugins/app-manifests.d.ts.map +1 -1
  17. package/node/dist/plugins/app-manifests.js +3 -2
  18. package/node/dist/plugins/app-manifests.js.map +1 -1
  19. package/node/dist/plugins/asset-catalogs.d.ts +15 -3
  20. package/node/dist/plugins/asset-catalogs.d.ts.map +1 -1
  21. package/node/dist/plugins/asset-catalogs.js +111 -13
  22. package/node/dist/plugins/asset-catalogs.js.map +1 -1
  23. package/node/dist/plugins/included-sites.d.ts +26 -0
  24. package/node/dist/plugins/included-sites.d.ts.map +1 -0
  25. package/node/dist/plugins/included-sites.js +70 -0
  26. package/node/dist/plugins/included-sites.js.map +1 -0
  27. package/node/dist/plugins/pdfImages.d.ts +27 -0
  28. package/node/dist/plugins/pdfImages.d.ts.map +1 -0
  29. package/node/dist/plugins/pdfImages.js +145 -0
  30. package/node/dist/plugins/pdfImages.js.map +1 -0
  31. package/node/dist/plugins/swift-wasm.d.ts +19 -1
  32. package/node/dist/plugins/swift-wasm.d.ts.map +1 -1
  33. package/node/dist/plugins/swift-wasm.js +45 -7
  34. package/node/dist/plugins/swift-wasm.js.map +1 -1
  35. package/node/dist/plugins/symbolTemplates.d.ts +22 -0
  36. package/node/dist/plugins/symbolTemplates.d.ts.map +1 -0
  37. package/node/dist/plugins/symbolTemplates.js +83 -0
  38. package/node/dist/plugins/symbolTemplates.js.map +1 -0
  39. package/package.json +4 -2
  40. package/plugins/app-manifests.ts +3 -2
  41. package/plugins/asset-catalogs.ts +119 -14
  42. package/plugins/included-sites.ts +74 -0
  43. package/plugins/pdfImages.ts +163 -0
  44. package/plugins/swift-wasm.ts +52 -6
  45. package/plugins/symbolTemplates.ts +88 -0
  46. package/public/_headers +7 -0
  47. package/public/app/oauth-callback.html +62 -0
  48. package/src/animation.ts +235 -8
  49. package/src/appFiles.ts +201 -0
  50. package/src/base64.ts +43 -0
  51. package/src/bitmapPlan.ts +143 -0
  52. package/src/bitmapStore.ts +206 -0
  53. package/src/bitmapWorker.ts +41 -0
  54. package/src/bitmapWorkerProtocol.ts +15 -0
  55. package/src/bitmaps.ts +283 -0
  56. package/src/blockedHosts.ts +29 -0
  57. package/src/canvasDrawing.ts +361 -0
  58. package/src/chartDom.ts +2 -2
  59. package/src/chromeColor.ts +179 -0
  60. package/src/clipboard.ts +78 -0
  61. package/src/colorMultiply.ts +93 -0
  62. package/src/colorPicker.ts +44 -0
  63. package/src/corsProxy.ts +45 -0
  64. package/src/gaugeRing.ts +74 -0
  65. package/src/gif.ts +297 -0
  66. package/src/hostRequests.ts +426 -0
  67. package/src/imageOps.ts +592 -0
  68. package/src/interaction.ts +68 -3
  69. package/src/landing.ts +2 -2
  70. package/src/largeTitle.ts +79 -0
  71. package/src/layout/chart.ts +9 -3
  72. package/src/layout/engine.ts +681 -174
  73. package/src/layout/index.ts +3 -1
  74. package/src/layout/text.ts +217 -1
  75. package/src/layout/types.ts +31 -0
  76. package/src/layout/typography.ts +24 -7
  77. package/src/layout/video.ts +24 -0
  78. package/src/layoutDom.ts +66 -6
  79. package/src/main.ts +127 -4
  80. package/src/media.ts +132 -0
  81. package/src/mock.ts +4 -3
  82. package/src/notifications.ts +109 -0
  83. package/src/pathShape.ts +156 -0
  84. package/src/protocol.ts +782 -13
  85. package/src/renderer.ts +603 -44
  86. package/src/richText.ts +65 -0
  87. package/src/scanner.ts +234 -0
  88. package/src/scrollTo.ts +123 -0
  89. package/src/styles.css +521 -100
  90. package/src/styles.ts +61 -0
  91. package/src/swiftData.ts +128 -0
  92. package/src/symbols.ts +658 -28
  93. package/src/translation.ts +126 -0
  94. package/src/video.ts +299 -0
  95. package/src/vite-env.d.ts +6 -0
  96. package/src/wasm.ts +227 -32
  97. package/src/webAuth.ts +267 -0
  98. package/src/webSocket.ts +148 -0
  99. package/src/zoneinfo.ts +269 -0
package/src/styles.css CHANGED
@@ -63,13 +63,15 @@
63
63
  color-scheme: light;
64
64
  --sb-color-primary: #000000;
65
65
  --sb-color-secondary: rgba(60, 60, 67, 0.6);
66
- --sb-color-accent: #007aff;
66
+ --sb-color-accent: #0088ff;
67
67
  --sb-color-system-background: #ffffff;
68
68
  --sb-color-secondary-system-background: #f2f2f7;
69
69
  --sb-color-separator: rgba(60, 60, 67, 0.29);
70
+ /* iOS 26 draws the hairlines between list rows lighter than `separator`. */
71
+ --sb-color-row-separator: rgba(0, 0, 0, 0.09);
70
72
  --sb-color-tertiary-label: rgba(60, 60, 67, 0.3);
71
73
  --sb-color-quaternary-label: rgba(60, 60, 67, 0.18);
72
- --sb-color-red: #ff3b30;
74
+ --sb-color-red: #ff383c;
73
75
  --sb-color-green: #34c759;
74
76
  --sb-color-grouped-background: #f2f2f7;
75
77
  --sb-color-grouped-cell: #ffffff;
@@ -77,9 +79,22 @@
77
79
  --sb-color-secondary-fill: rgba(120, 120, 128, 0.16);
78
80
  --sb-color-tertiary-fill: rgba(118, 118, 128, 0.12);
79
81
  --sb-color-quaternary-fill: rgba(116, 116, 128, 0.08);
82
+ /* UIKit's semantic colors without a token above (`Color(uiColor:)`). */
83
+ --sb-color-tertiary-system-background: #ffffff;
84
+ --sb-color-tertiary-system-grouped-background: #f2f2f7;
85
+ --sb-color-opaque-separator: #c6c6c8;
86
+ --sb-color-system-gray2: #aeaeb2;
87
+ --sb-color-system-gray3: #c7c7cc;
88
+ --sb-color-system-gray4: #d1d1d6;
89
+ --sb-color-system-gray5: #e5e5ea;
90
+ --sb-color-system-gray6: #f2f2f7;
80
91
  /* Phase 6 materials: rgb components of the color blurred over the backdrop. */
81
92
  --sb-color-material-base: 255, 255, 255;
82
- --sb-color-switch-off: #e9e9ea;
93
+ /* iOS 26 (measured on its simulator, as the dark values below). */
94
+ --sb-color-switch-off: rgba(120, 120, 128, 0.42);
95
+ --sb-tint-fill-amount: 18%;
96
+ --sb-alert-scrim: rgba(0, 0, 0, 0.2);
97
+ --sb-alert-card: rgba(255, 255, 255, 0.7);
83
98
  --sb-color-field-border: #c6c6c8;
84
99
  --sb-color-field-background: #ffffff;
85
100
  --sb-color-placeholder: rgba(60, 60, 67, 0.3);
@@ -87,6 +102,12 @@
87
102
  --sb-color-grabber: rgba(60, 60, 67, 0.3);
88
103
  /* Phase 4 */
89
104
  --sb-color-tabbar-background: rgba(249, 249, 249, 0.94);
105
+ /* iOS 26 Liquid Glass, approximated: a near-white translucent fill over a
106
+ blurred backdrop with a soft shadow (bar buttons, the tab bar, the
107
+ bottom search field). */
108
+ --sb-glass-background: rgba(252, 252, 255, 0.86);
109
+ --sb-glass-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
110
+ --sb-glass-selected: rgba(120, 120, 128, 0.16);
90
111
  --sb-color-segment-track: rgba(118, 118, 128, 0.12);
91
112
  --sb-color-segment-selected: #ffffff;
92
113
  --sb-color-menu-background: rgba(250, 250, 250, 0.96);
@@ -96,13 +117,14 @@
96
117
  color-scheme: dark;
97
118
  --sb-color-primary: #ffffff;
98
119
  --sb-color-secondary: rgba(235, 235, 245, 0.6);
99
- --sb-color-accent: #0a84ff;
120
+ --sb-color-accent: #0091ff;
100
121
  --sb-color-system-background: #000000;
101
122
  --sb-color-secondary-system-background: #1c1c1e;
102
123
  --sb-color-separator: rgba(84, 84, 88, 0.6);
124
+ --sb-color-row-separator: rgba(84, 84, 88, 0.6);
103
125
  --sb-color-tertiary-label: rgba(235, 235, 245, 0.3);
104
126
  --sb-color-quaternary-label: rgba(235, 235, 245, 0.16);
105
- --sb-color-red: #ff453a;
127
+ --sb-color-red: #ff4245;
106
128
  --sb-color-green: #30d158;
107
129
  --sb-color-grouped-background: #000000;
108
130
  --sb-color-grouped-cell: #1c1c1e;
@@ -110,18 +132,32 @@
110
132
  --sb-color-secondary-fill: rgba(120, 120, 128, 0.32);
111
133
  --sb-color-tertiary-fill: rgba(118, 118, 128, 0.24);
112
134
  --sb-color-quaternary-fill: rgba(118, 118, 128, 0.18);
135
+ --sb-color-tertiary-system-background: #2c2c2e;
136
+ --sb-color-tertiary-system-grouped-background: #2c2c2e;
137
+ --sb-color-opaque-separator: #38383a;
138
+ --sb-color-system-gray2: #636366;
139
+ --sb-color-system-gray3: #48484a;
140
+ --sb-color-system-gray4: #3a3a3c;
141
+ --sb-color-system-gray5: #2c2c2e;
142
+ --sb-color-system-gray6: #1c1c1e;
113
143
  --sb-color-material-base: 30, 30, 30;
114
- --sb-color-switch-off: #39393d;
144
+ --sb-color-switch-off: rgba(120, 120, 128, 0.68);
145
+ --sb-tint-fill-amount: 25%;
146
+ --sb-alert-scrim: rgba(0, 0, 0, 0.46);
147
+ --sb-alert-card: rgba(22, 22, 23, 0.9);
115
148
  --sb-color-field-border: #3a3a3c;
116
149
  --sb-color-field-background: #1c1c1e;
117
150
  --sb-color-placeholder: rgba(235, 235, 245, 0.3);
151
+ --sb-glass-background: rgba(30, 30, 30, 0.84);
152
+ --sb-glass-shadow: 0 4px 24px rgba(0, 0, 0, 0.4), 0 0 0 0.5px rgba(255, 255, 255, 0.08);
153
+ --sb-glass-selected: rgba(120, 120, 128, 0.32);
118
154
  /* Sheets float above the window and take the elevated system background. */
119
155
  --sb-color-sheet-background: #1c1c1e;
120
156
  --sb-color-grabber: rgba(235, 235, 245, 0.3);
121
157
  /* Phase 4 */
122
158
  --sb-color-tabbar-background: rgba(29, 29, 29, 0.94);
123
159
  --sb-color-segment-track: rgba(118, 118, 128, 0.24);
124
- --sb-color-segment-selected: #636366;
160
+ --sb-color-segment-selected: #5a5a5f;
125
161
  --sb-color-menu-background: rgba(37, 37, 37, 0.96);
126
162
  }
127
163
 
@@ -336,7 +372,7 @@ body {
336
372
  position: absolute;
337
373
  top: 11px;
338
374
  left: 50%;
339
- width: 126px;
375
+ width: 125px;
340
376
  height: 37px;
341
377
  transform: translateX(-50%);
342
378
  border-radius: 20px;
@@ -353,23 +389,30 @@ body {
353
389
  display: flex;
354
390
  align-items: center;
355
391
  justify-content: space-between;
356
- padding: 0 32px 6px 40px;
392
+ /* As iOS 26 draws it (measured on an iPhone 16 simulator): the row is
393
+ centered 29.5pt down, the time centered 70pt from the leading edge, the
394
+ glyphs 7.67pt apart ending 32pt from the trailing edge. */
395
+ padding: 0 32px 0 38px;
357
396
  pointer-events: none;
358
397
  z-index: 2;
359
398
  color: var(--sb-color-primary);
360
399
  }
361
400
 
362
401
  .status-time {
402
+ min-width: 64px;
403
+ text-align: center;
404
+ /* The digits' optical center, not the line box's, sits on the row's. */
405
+ position: relative;
406
+ top: 1.67px;
363
407
  font-size: 17px;
364
408
  font-weight: 600;
365
409
  letter-spacing: -0.02em;
366
- font-variant-numeric: tabular-nums;
367
410
  }
368
411
 
369
412
  .status-glyphs {
370
413
  display: flex;
371
414
  align-items: center;
372
- gap: 7px;
415
+ gap: 7.67px;
373
416
  }
374
417
 
375
418
  .glyph {
@@ -377,6 +420,12 @@ body {
377
420
  fill: currentColor;
378
421
  }
379
422
 
423
+ .glyph-wifi .wifi-band {
424
+ fill: none;
425
+ stroke: currentColor;
426
+ stroke-linecap: round;
427
+ }
428
+
380
429
  .glyph-battery .battery-shell {
381
430
  fill: none;
382
431
  stroke: currentColor;
@@ -455,7 +504,21 @@ html[data-sb-mode="device"] body {
455
504
  overflow: hidden;
456
505
  overscroll-behavior: none;
457
506
  touch-action: manipulation;
458
- background: var(--sb-color-system-background, var(--page-bg));
507
+ }
508
+
509
+ /* The page behind the app is the OS appearance's system background from the
510
+ first paint (Safari tints its status bar strip from it); chromeColor.ts
511
+ then keeps it at the color the app draws at its top edge. */
512
+ html[data-sb-mode="device"],
513
+ html[data-sb-mode="device"] body {
514
+ background: #ffffff;
515
+ }
516
+
517
+ @media (prefers-color-scheme: dark) {
518
+ html[data-sb-mode="device"],
519
+ html[data-sb-mode="device"] body {
520
+ background: #000000;
521
+ }
459
522
  }
460
523
 
461
524
  html[data-sb-mode="device"] .toolbar {
@@ -542,6 +605,9 @@ html[data-sb-mode="device"] .status {
542
605
  -webkit-background-clip: var(--sb-text-clip, border-box);
543
606
  background-clip: var(--sb-text-clip, border-box);
544
607
  color: var(--sb-text-color);
608
+ /* View-level `.underline()` / `.strikethrough()` (renderer.ts `applyStyle`);
609
+ with both unset the declaration is invalid and the line is `none`. */
610
+ text-decoration-line: var(--sb-text-underline,) var(--sb-text-strikethrough,);
545
611
  }
546
612
 
547
613
  .sb-vstack,
@@ -589,7 +655,8 @@ html[data-sb-mode="device"] .status {
589
655
  }
590
656
 
591
657
  .sb-button[data-sb-style="bordered"] {
592
- background: var(--sb-color-fill);
658
+ /* iOS 26: secondarySystemFill, or the view's tint at 18% (dark 25%). */
659
+ background: var(--sb-tint-fill, var(--sb-color-secondary-fill));
593
660
  color: var(--sb-button-color, var(--sb-tint, var(--sb-color-accent)));
594
661
  }
595
662
 
@@ -607,6 +674,18 @@ html[data-sb-mode="device"] .status {
607
674
  color: #ffffff;
608
675
  }
609
676
 
677
+ /* iOS 26 draws a disabled bordered button gray rather than faded
678
+ (`.disabled` marks the element around the button and dims it inline; the
679
+ renderer keeps the undimmed opacity in `--sb-undimmed-opacity`). */
680
+ [data-sb-disabled]:has(> .sb-button:is([data-sb-style="bordered"], [data-sb-style="borderedProminent"])) {
681
+ opacity: var(--sb-undimmed-opacity, 1) !important;
682
+ }
683
+
684
+ [data-sb-disabled] > .sb-button:is([data-sb-style="bordered"], [data-sb-style="borderedProminent"]) {
685
+ background: var(--sb-color-secondary-fill);
686
+ color: var(--sb-color-tertiary-label);
687
+ }
688
+
610
689
  /* .disabled(true): opacity and pointer-events are set inline by the renderer
611
690
  (so update() clears them); the attribute carries the state for tooling. */
612
691
  [data-sb-disabled] {
@@ -629,6 +708,47 @@ html[data-sb-mode="device"] .status {
629
708
  stroke: currentColor;
630
709
  }
631
710
 
711
+ /* An indeterminate ProgressView: the activity indicator (symbols.ts), in
712
+ the secondary label color (or the view's tint). As on iOS the spokes stay
713
+ put and the fade steps around them clockwise, once a second: each spoke
714
+ runs the same ramp an eighth of a second after the one before it.
715
+ (Rotating the glyph instead resamples it at every step, which wobbles.) */
716
+ svg[data-sb-symbol="progress.indicator"] {
717
+ color: var(--sb-tint, var(--sb-color-secondary));
718
+ }
719
+
720
+ svg[data-sb-symbol="progress.indicator"] > line {
721
+ animation: sb-activity-spoke 1s step-end infinite;
722
+ }
723
+
724
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(2) { animation-delay: -0.875s; }
725
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(3) { animation-delay: -0.75s; }
726
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(4) { animation-delay: -0.625s; }
727
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(5) { animation-delay: -0.5s; }
728
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(6) { animation-delay: -0.375s; }
729
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(7) { animation-delay: -0.25s; }
730
+ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay: -0.125s; }
731
+
732
+ /* A spoke's strength from the moment it leads: the ramp symbols.ts draws. */
733
+ @keyframes sb-activity-spoke {
734
+ 0% {
735
+ stroke-opacity: 1;
736
+ }
737
+ 12.5% {
738
+ stroke-opacity: 0.83;
739
+ }
740
+ 25% {
741
+ stroke-opacity: 0.66;
742
+ }
743
+ 37.5% {
744
+ stroke-opacity: 0.49;
745
+ }
746
+ 50%,
747
+ 100% {
748
+ stroke-opacity: 0.31;
749
+ }
750
+ }
751
+
632
752
  /* Unknown symbol or catalog image: a dashed box named by `title`. */
633
753
  .sb-image[data-sb-missing] {
634
754
  border: 1px dashed currentColor;
@@ -639,7 +759,8 @@ html[data-sb-mode="device"] .status {
639
759
  /* Phase 5: a catalog image. The layout engine decides the frame (intrinsic
640
760
  size, or the proposal for `.resizable()`), so the bitmap fills the box;
641
761
  `.aspectRatio` fitting is a layout concern, not object-fit's. */
642
- .sb-image > img {
762
+ .sb-image > img,
763
+ .sb-image > canvas {
643
764
  display: block;
644
765
  width: 100%;
645
766
  height: 100%;
@@ -649,6 +770,12 @@ html[data-sb-mode="device"] .status {
649
770
  -webkit-user-select: none;
650
771
  }
651
772
 
773
+ /* `.interpolation(.none)`: a QR code's modules stay sharp when scaled up. */
774
+ .sb-image[data-sb-interpolation='none'] > img,
775
+ .sb-image[data-sb-interpolation='none'] > canvas {
776
+ image-rendering: pixelated;
777
+ }
778
+
652
779
  /* Phase 5: a `.symbolset` SVG drawn as a template: the SVG masks a box
653
780
  filled with the current foreground color, like an SF Symbol. */
654
781
  .sb-image > .sb-image-mask {
@@ -760,8 +887,9 @@ html[data-sb-mode="device"] .status {
760
887
  .sb-switch {
761
888
  appearance: none;
762
889
  position: absolute;
763
- width: 51px;
764
- height: 31px;
890
+ /* iOS 26: 63 × 28 with a pill knob. */
891
+ width: 63px;
892
+ height: 28px;
765
893
  margin: 0;
766
894
  padding: 0;
767
895
  border: 0;
@@ -786,9 +914,9 @@ html[data-sb-mode="device"] .status {
786
914
  position: absolute;
787
915
  top: 2px;
788
916
  left: 2px;
789
- width: 27px;
790
- height: 27px;
791
- border-radius: 50%;
917
+ width: 37px;
918
+ height: 24px;
919
+ border-radius: 12px;
792
920
  background: #ffffff;
793
921
  box-shadow:
794
922
  0 3px 8px rgba(0, 0, 0, 0.15),
@@ -797,7 +925,7 @@ html[data-sb-mode="device"] .status {
797
925
  }
798
926
 
799
927
  .sb-switch[aria-checked="true"] .sb-switch-knob {
800
- transform: translateX(20px);
928
+ transform: translateX(22px);
801
929
  }
802
930
 
803
931
  /* ---------------------------------------------------------------------------
@@ -828,7 +956,8 @@ html[data-sb-mode="device"] .status {
828
956
 
829
957
  .sb-section-rows {
830
958
  position: absolute;
831
- border-radius: 10px;
959
+ /* iOS 26's inset grouped cards. */
960
+ border-radius: 26px;
832
961
  background: var(--sb-color-grouped-cell);
833
962
  overflow: hidden;
834
963
  }
@@ -844,26 +973,56 @@ html[data-sb-mode="device"] .status {
844
973
 
845
974
  .sb-list-content > :not(.sb-section):first-child,
846
975
  .sb-list-content > .sb-section + :not(.sb-section) {
847
- border-top-left-radius: 10px;
848
- border-top-right-radius: 10px;
976
+ border-top-left-radius: 26px;
977
+ border-top-right-radius: 26px;
849
978
  }
850
979
 
851
980
  .sb-list-content > :not(.sb-section):last-child,
852
981
  .sb-list-content > :not(.sb-section):has(+ .sb-section) {
853
- border-bottom-left-radius: 10px;
854
- border-bottom-right-radius: 10px;
982
+ border-bottom-left-radius: 26px;
983
+ border-bottom-right-radius: 26px;
855
984
  }
856
985
 
857
- /* Hairline separators, inset 16px from the leading edge. */
986
+ /* Lazy rows not built yet (a trailing `lazyspacer`): no cell of their own, and
987
+ the row before them ends the card as the last row would. */
988
+ .sb-lazyspacer {
989
+ background: none !important;
990
+ pointer-events: none;
991
+ }
992
+
993
+ .sb-list-content > :not(.sb-section):has(+ .sb-lazyspacer:last-child) {
994
+ border-bottom-left-radius: 26px;
995
+ border-bottom-right-radius: 26px;
996
+ }
997
+
998
+ .sb-section-rows > :has(+ .sb-lazyspacer:last-child)::after,
999
+ .sb-list-content > :has(+ .sb-lazyspacer:last-child)::after,
1000
+ .sb-section-rows > .sb-lazyspacer::after,
1001
+ .sb-list-content > .sb-lazyspacer::after {
1002
+ display: none;
1003
+ }
1004
+
1005
+ /* `.listRowSeparator(.hidden)`: the row's own line (below it) or the
1006
+ previous row's (above it) is left out. */
1007
+ .sb-section-rows > [data-sb-separator-bottom="hidden"]::after,
1008
+ .sb-list-content > [data-sb-separator-bottom="hidden"]::after,
1009
+ .sb-section-rows > :has(+ [data-sb-separator-top="hidden"])::after,
1010
+ .sb-list-content > :has(+ [data-sb-separator-top="hidden"])::after {
1011
+ display: none !important;
1012
+ }
1013
+
1014
+ /* Separators: iOS 26 starts the line at the row's first text (the engine's
1015
+ `--sb-separator-leading`, else 16px) and ends it 16px before the trailing
1016
+ edge. */
858
1017
  .sb-section-rows > :not(:last-child)::after,
859
1018
  .sb-list-content > :not(.sb-section):not(:last-child):not(:has(+ .sb-section))::after {
860
1019
  content: "";
861
1020
  position: absolute;
862
- left: 16px;
863
- right: 0;
1021
+ left: var(--sb-separator-leading, 16px);
1022
+ right: 16px;
864
1023
  bottom: 0;
865
1024
  height: 1px;
866
- background: var(--sb-color-separator);
1025
+ background: var(--sb-color-row-separator);
867
1026
  pointer-events: none;
868
1027
  }
869
1028
 
@@ -902,7 +1061,7 @@ html[data-sb-mode="device"] .status {
902
1061
 
903
1062
  /* A screen with a list as its content takes the grouped background, like a
904
1063
  UINavigationController hosting an inset-grouped table view. */
905
- .sb-navscreen:has(> .sb-navscreen-body > .sb-navscreen-content > .sb-list) {
1064
+ .sb-navscreen:has(> .sb-navscreen-body > .sb-navscreen-content > .sb-list:not([data-sb-background="hidden"])) {
906
1065
  background: var(--sb-color-grouped-background);
907
1066
  }
908
1067
 
@@ -955,7 +1114,7 @@ html[data-sb-mode="device"] .status {
955
1114
  display: grid;
956
1115
  grid-template-columns: 1fr auto 1fr;
957
1116
  align-items: center;
958
- padding: 0 8px;
1117
+ padding: 0 16px;
959
1118
  box-sizing: border-box;
960
1119
  background: transparent;
961
1120
  /* Phase 8: content that ignores the safe area paints under the bar; the
@@ -978,14 +1137,21 @@ html[data-sb-mode="device"] .status {
978
1137
  justify-self: start;
979
1138
  display: inline-flex;
980
1139
  align-items: center;
1140
+ justify-content: center;
981
1141
  gap: 2px;
982
- /* Width comes from the layout engine (chrome.back); this is only a fallback. */
1142
+ /* iOS 26: a 44pt glass circle with the chevron (chrome.back). */
1143
+ width: 44px;
1144
+ height: 44px;
983
1145
  max-width: 45%;
984
1146
  margin: 0;
985
1147
  padding: 0;
986
1148
  border: 0;
987
- background: none;
988
- color: var(--sb-color-accent);
1149
+ border-radius: 22px;
1150
+ background: var(--sb-glass-background);
1151
+ box-shadow: var(--sb-glass-shadow);
1152
+ backdrop-filter: blur(12px) saturate(180%);
1153
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
1154
+ color: var(--sb-color-primary);
989
1155
  font: inherit;
990
1156
  font-size: var(--sb-font-size-body);
991
1157
  line-height: 1.3;
@@ -1005,11 +1171,15 @@ html[data-sb-mode="device"] .status {
1005
1171
  .sb-navbar-back > svg {
1006
1172
  width: 1.4em;
1007
1173
  height: 1.4em;
1008
- margin-left: -4px;
1009
- stroke-width: 2.5;
1174
+ margin-left: -2px;
1175
+ stroke-width: 2.25;
1010
1176
  flex-shrink: 0;
1011
1177
  }
1012
1178
 
1179
+ .sb-navbar-back-label:empty {
1180
+ display: none;
1181
+ }
1182
+
1013
1183
  .sb-navbar-back-label {
1014
1184
  overflow: hidden;
1015
1185
  text-overflow: ellipsis;
@@ -1047,14 +1217,16 @@ html[data-sb-mode="device"] .status {
1047
1217
  display: block;
1048
1218
  }
1049
1219
 
1050
- /* Large title: the engine's `largeTitle` rect (52px row), text already
1220
+ /* Large title: the engine's `largeTitle` rect (62px row), text already
1051
1221
  truncated to one line. */
1052
1222
  .sb-navbar-large-title {
1053
1223
  position: absolute;
1054
1224
  z-index: 1; /* Phase 8: above content that ignores the safe area */
1055
1225
  display: flex;
1056
- align-items: center;
1057
- padding: 0 16px;
1226
+ /* iOS 26: the title's line starts 13 into its 62px row. */
1227
+ align-items: flex-start;
1228
+ line-height: 41px;
1229
+ padding: 13px 16px 0;
1058
1230
  box-sizing: border-box;
1059
1231
  font-size: var(--sb-ts-largeTitle-size, 34px);
1060
1232
  font-weight: 700;
@@ -1069,8 +1241,34 @@ html[data-sb-mode="device"] .status {
1069
1241
  display: none;
1070
1242
  }
1071
1243
 
1244
+ /* The large title scrolls with the screen's root list or scroll view
1245
+ (src/largeTitle.ts moves it); drags on it scroll that view. */
1246
+ .sb-navbar-large-title {
1247
+ pointer-events: none;
1248
+ }
1249
+
1250
+ /* Until the large title has scrolled under the bar, the inline title (whose
1251
+ rect the engine reports for a title that can collapse) stays hidden. */
1252
+ .sb-navscreen[data-sb-display="large"] > .sb-navbar > .sb-navbar-title {
1253
+ opacity: 0;
1254
+ transition: opacity 0.15s ease;
1255
+ }
1256
+
1257
+ .sb-navscreen[data-sb-display="large"][data-sb-collapsed] > .sb-navbar > .sb-navbar-title {
1258
+ opacity: 1;
1259
+ }
1260
+
1261
+ /* iOS 26's scroll edge effect, simplified: scrolled content fades in over
1262
+ 16px below the bar instead of being cut off at it (src/largeTitle.ts). */
1263
+ [data-sb-scrolls-title][data-sb-scrolled] {
1264
+ -webkit-mask-image: linear-gradient(to bottom, transparent, #000 16px);
1265
+ mask-image: linear-gradient(to bottom, transparent, #000 16px);
1266
+ }
1267
+
1072
1268
  /* NavigationLink: a tappable label. Inside a list it is a row with a chevron
1073
- at the engine's `chevron` rect; elsewhere it reads as plain accent text. */
1269
+ at the engine's `chevron` rect, its label in the primary color (also when
1270
+ the row is wrapped: a selectable or deletable `listrow`, a modifier);
1271
+ elsewhere it reads as plain accent text. */
1074
1272
  .sb-navlink {
1075
1273
  color: var(--sb-color-accent);
1076
1274
  cursor: pointer;
@@ -1094,9 +1292,16 @@ html[data-sb-mode="device"] .status {
1094
1292
  display: none;
1095
1293
  }
1096
1294
 
1097
- .sb-section-rows > .sb-navlink,
1098
- .sb-list-content > .sb-navlink {
1099
- color: var(--sb-color-primary);
1295
+ /* An explicit `.foregroundStyle` on or around the link (which sets
1296
+ `--sb-button-color`) wins, as in SwiftUI. */
1297
+ .sb-list .sb-navlink {
1298
+ color: var(--sb-button-color, var(--sb-color-primary));
1299
+ }
1300
+
1301
+ /* A `Label`'s icon in a list row takes the tint; its title keeps the row's
1302
+ color. */
1303
+ .sb-list [data-sb-label-icon] {
1304
+ color: var(--sb-button-color, var(--sb-tint, var(--sb-color-accent)));
1100
1305
  }
1101
1306
 
1102
1307
  @media (prefers-reduced-motion: reduce) {
@@ -1161,6 +1366,26 @@ html[data-sb-mode="device"] .status {
1161
1366
  transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
1162
1367
  }
1163
1368
 
1369
+ /* iOS 26: a sheet at a partial detent floats inset from the screen edges,
1370
+ rounded all around, over a lighter dim. */
1371
+ .sb-sheet[data-sb-floating] > .sb-sheet-card {
1372
+ border-radius: 38px;
1373
+ background: color-mix(in srgb, var(--sb-color-sheet-background) 55%, transparent);
1374
+ backdrop-filter: blur(24px) saturate(180%);
1375
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
1376
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.18);
1377
+ }
1378
+
1379
+ /* The sheet's glass shows through a navigation stack in it, as on iOS 26
1380
+ (an opaque screen would paint over it). */
1381
+ .sb-sheet[data-sb-floating] .sb-navscreen {
1382
+ background: transparent;
1383
+ }
1384
+
1385
+ .sb-sheet[data-sb-floating] > .sb-sheet-scrim {
1386
+ background: rgba(0, 0, 0, 0.22);
1387
+ }
1388
+
1164
1389
  .sb-sheet-grabber {
1165
1390
  position: absolute;
1166
1391
  border-radius: 2.5px;
@@ -1300,8 +1525,8 @@ html[data-sb-mode="device"] .status {
1300
1525
  /* ---------------------------------------------------------------------------
1301
1526
  Phase 4: tab views. The tabs are stacked in the content slot (every tab has
1302
1527
  the tab view's frame); the selected one is visible, the others keep their
1303
- DOM hidden. The bar is the engine's `bar` rect (49 + bottom safe area):
1304
- translucent system background, hairline on top, items in the 49px row.
1528
+ DOM hidden. The bar is the engine's `bar` rect: iOS 26's glass capsule
1529
+ floating over the content, the selected item on a gray pill.
1305
1530
  ------------------------------------------------------------------------- */
1306
1531
 
1307
1532
  .sb-tabview {
@@ -1325,35 +1550,27 @@ html[data-sb-mode="device"] .status {
1325
1550
  .sb-tabbar {
1326
1551
  position: absolute;
1327
1552
  z-index: 3;
1328
- background: var(--sb-color-tabbar-background);
1553
+ border-radius: 31px;
1554
+ background: var(--sb-glass-background);
1555
+ box-shadow: var(--sb-glass-shadow);
1329
1556
  backdrop-filter: blur(20px) saturate(180%);
1330
1557
  -webkit-backdrop-filter: blur(20px) saturate(180%);
1331
1558
  }
1332
1559
 
1333
- .sb-tabbar::before {
1334
- content: "";
1335
- position: absolute;
1336
- left: 0;
1337
- right: 0;
1338
- top: 0;
1339
- height: 1px;
1340
- background: var(--sb-color-separator);
1341
- pointer-events: none;
1342
- }
1343
-
1344
1560
  .sb-tabbar-item {
1345
1561
  appearance: none;
1346
1562
  position: absolute;
1347
1563
  display: flex;
1348
1564
  flex-direction: column;
1349
1565
  align-items: center;
1350
- justify-content: flex-start;
1351
- gap: 2px;
1566
+ justify-content: center;
1567
+ gap: 1px;
1352
1568
  margin: 0;
1353
- padding: 6px 0 0;
1569
+ padding: 0;
1354
1570
  border: 0;
1571
+ border-radius: 27px;
1355
1572
  background: none;
1356
- color: var(--sb-color-secondary);
1573
+ color: var(--sb-color-primary);
1357
1574
  font-family: var(--sb-font-family);
1358
1575
  cursor: pointer;
1359
1576
  touch-action: manipulation;
@@ -1362,6 +1579,7 @@ html[data-sb-mode="device"] .status {
1362
1579
 
1363
1580
  .sb-tabbar-item[aria-selected="true"] {
1364
1581
  color: var(--sb-color-accent);
1582
+ background: var(--sb-glass-selected);
1365
1583
  }
1366
1584
 
1367
1585
  .sb-tabbar-item:focus-visible {
@@ -1401,11 +1619,35 @@ html[data-sb-mode="device"] .status {
1401
1619
  .sb-tabbar-title {
1402
1620
  font-size: 10px;
1403
1621
  line-height: 12px;
1404
- font-weight: 500;
1622
+ font-weight: 600;
1405
1623
  letter-spacing: -0.01em;
1406
1624
  white-space: nowrap;
1407
1625
  }
1408
1626
 
1627
+ /* iOS 18 `Tab(…).badge(…)`: a red capsule at the top trailing corner of the icon. */
1628
+ .sb-tabbar-badge {
1629
+ position: absolute;
1630
+ top: 2px;
1631
+ left: calc(50% + 5px);
1632
+ box-sizing: border-box;
1633
+ min-width: 18px;
1634
+ height: 18px;
1635
+ padding: 0 5px;
1636
+ border-radius: 9px;
1637
+ background: var(--sb-color-red);
1638
+ color: #fff;
1639
+ font-size: 13px;
1640
+ line-height: 18px;
1641
+ font-weight: 400;
1642
+ text-align: center;
1643
+ white-space: nowrap;
1644
+ pointer-events: none;
1645
+ }
1646
+
1647
+ .sb-tabbar-badge[hidden] {
1648
+ display: none;
1649
+ }
1650
+
1409
1651
  /* ---------------------------------------------------------------------------
1410
1652
  Phase 4: pickers. Segmented: a gray track with equal segments at the
1411
1653
  engine's `segment<i>` rects; the selected one carries a raised pill inset
@@ -1421,7 +1663,8 @@ html[data-sb-mode="device"] .status {
1421
1663
  box is the whole row, so the track cannot be the element's background). */
1422
1664
  .sb-segment-track {
1423
1665
  position: absolute;
1424
- border-radius: 8px;
1666
+ /* iOS 26: a capsule track with a capsule for the selected segment. */
1667
+ border-radius: 9999px;
1425
1668
  background: var(--sb-color-segment-track);
1426
1669
  pointer-events: none;
1427
1670
  }
@@ -1449,7 +1692,7 @@ html[data-sb-mode="device"] .status {
1449
1692
  content: "";
1450
1693
  position: absolute;
1451
1694
  inset: 2px;
1452
- border-radius: 7px;
1695
+ border-radius: 9999px;
1453
1696
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
1454
1697
  }
1455
1698
 
@@ -1658,6 +1901,28 @@ html[data-sb-mode="device"] .status {
1658
1901
  background: var(--sb-tint, var(--sb-color-accent));
1659
1902
  }
1660
1903
 
1904
+ .sb-gauge-ring {
1905
+ position: absolute;
1906
+ pointer-events: none;
1907
+ }
1908
+
1909
+ .sb-gauge-ring > svg {
1910
+ display: block;
1911
+ width: 100%;
1912
+ height: 100%;
1913
+ overflow: visible;
1914
+ }
1915
+
1916
+ .sb-gauge-ring-track {
1917
+ stroke: var(--sb-color-fill);
1918
+ stroke-linecap: round;
1919
+ }
1920
+
1921
+ .sb-gauge-ring-value {
1922
+ stroke: var(--sb-tint, var(--sb-color-accent));
1923
+ stroke-linecap: round;
1924
+ }
1925
+
1661
1926
  @media (prefers-reduced-motion: reduce) {
1662
1927
  .sb-picker-menu,
1663
1928
  .sb-segment::before {
@@ -1834,7 +2099,7 @@ html[data-sb-mode="device"] .status {
1834
2099
  right: 0;
1835
2100
  top: 0;
1836
2101
  height: 1px;
1837
- background: var(--sb-color-separator);
2102
+ background: var(--sb-color-row-separator);
1838
2103
  pointer-events: none;
1839
2104
  }
1840
2105
 
@@ -1857,6 +2122,25 @@ html[data-sb-mode="device"] .status {
1857
2122
  display: block;
1858
2123
  }
1859
2124
 
2125
+ /* iOS 26: a bar's leading and trailing items share a glass capsule (the
2126
+ engine sizes the group: 44 tall, each item in a slot at least 44 wide),
2127
+ drawn in the label color unless tinted. */
2128
+ .sb-toolbar[data-sb-placement="leading"],
2129
+ .sb-toolbar[data-sb-placement="trailing"] {
2130
+ border-radius: 22px;
2131
+ background: var(--sb-glass-background);
2132
+ box-shadow: var(--sb-glass-shadow);
2133
+ backdrop-filter: blur(12px) saturate(180%);
2134
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
2135
+ --sb-color-accent: var(--sb-color-primary);
2136
+ }
2137
+
2138
+ /* Their symbols are drawn larger and heavier, as iOS 26 draws bar buttons. */
2139
+ .sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"]) .sb-image > svg {
2140
+ transform: scale(1.25);
2141
+ stroke-width: 2.1;
2142
+ }
2143
+
1860
2144
  .sb-toolbar[data-sb-placement="bottom"] {
1861
2145
  z-index: 3;
1862
2146
  background: var(--sb-color-tabbar-background);
@@ -1875,16 +2159,21 @@ html[data-sb-mode="device"] .status {
1875
2159
  pointer-events: none;
1876
2160
  }
1877
2161
 
1878
- /* Search field: a 36px rounded gray field (engine `searchField` rect) with a
2162
+ /* Search field: a capsule (engine `searchField` rect) with a
1879
2163
  magnifier leading; the input sends `text` events like a textfield. */
1880
2164
  .sb-searchfield {
1881
2165
  display: flex;
1882
2166
  align-items: center;
1883
- gap: 6px;
1884
- padding: 0 8px;
1885
- border-radius: 10px;
1886
- background: var(--sb-color-fill);
2167
+ gap: 8px;
2168
+ padding: 0 16px;
2169
+ /* iOS 26: a glass capsule floating at the bottom (engine `searchField`). */
2170
+ border-radius: 9999px;
2171
+ background: var(--sb-glass-background);
2172
+ box-shadow: var(--sb-glass-shadow);
2173
+ backdrop-filter: blur(20px) saturate(180%);
2174
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
1887
2175
  color: var(--sb-color-primary);
2176
+ z-index: 2;
1888
2177
  }
1889
2178
 
1890
2179
  .sb-searchfield > svg {
@@ -2042,33 +2331,34 @@ html[data-sb-mode="device"] .status {
2042
2331
  }
2043
2332
 
2044
2333
  /* The SwiftUI chart palette (and the other system colors) as `--sb-color-<name>`,
2045
- which a named `Color` without a semantic token resolves to (src/styles.ts). */
2334
+ which a named `Color` without a semantic token resolves to (src/styles.ts).
2335
+ iOS 26's palette (measured in the iOS 26.2 simulator, Examples/Fidelity). */
2046
2336
  .device,
2047
2337
  .device[data-theme="light"] {
2048
- --sb-color-blue: #007aff;
2049
- --sb-color-orange: #ff9500;
2338
+ --sb-color-blue: #0088ff;
2339
+ --sb-color-orange: #ff8d28;
2050
2340
  --sb-color-yellow: #ffcc00;
2051
- --sb-color-purple: #af52de;
2341
+ --sb-color-purple: #cb30e0;
2052
2342
  --sb-color-pink: #ff2d55;
2053
- --sb-color-teal: #30b0c7;
2054
- --sb-color-indigo: #5856d6;
2055
- --sb-color-mint: #00c7be;
2056
- --sb-color-cyan: #32ade6;
2057
- --sb-color-brown: #a2845e;
2343
+ --sb-color-teal: #00c3d0;
2344
+ --sb-color-indigo: #6155f5;
2345
+ --sb-color-mint: #00c8b3;
2346
+ --sb-color-cyan: #00c0e8;
2347
+ --sb-color-brown: #ac7f5e;
2058
2348
  --sb-color-gray: #8e8e93;
2059
2349
  }
2060
2350
 
2061
2351
  .device[data-theme="dark"] {
2062
- --sb-color-blue: #0a84ff;
2063
- --sb-color-orange: #ff9f0a;
2064
- --sb-color-yellow: #ffd60a;
2065
- --sb-color-purple: #bf5af2;
2352
+ --sb-color-blue: #0091ff;
2353
+ --sb-color-orange: #ff9230;
2354
+ --sb-color-yellow: #ffd600;
2355
+ --sb-color-purple: #db34f2;
2066
2356
  --sb-color-pink: #ff375f;
2067
- --sb-color-teal: #40c8e0;
2068
- --sb-color-indigo: #5e5ce6;
2069
- --sb-color-mint: #63e6e2;
2070
- --sb-color-cyan: #64d2ff;
2071
- --sb-color-brown: #ac8e68;
2357
+ --sb-color-teal: #00d2e0;
2358
+ --sb-color-indigo: #6d7cff;
2359
+ --sb-color-mint: #00dac3;
2360
+ --sb-color-cyan: #3cd3fe;
2361
+ --sb-color-brown: #b78a66;
2072
2362
  --sb-color-gray: #8e8e93;
2073
2363
  }
2074
2364
 
@@ -2479,7 +2769,7 @@ html[data-sb-mode="device"] .status {
2479
2769
  .sb-alert-scrim {
2480
2770
  position: absolute;
2481
2771
  inset: 0;
2482
- background: rgba(0, 0, 0, 0.3);
2772
+ background: var(--sb-alert-scrim);
2483
2773
  animation: sb-alert-fade-in 0.25s ease-out both;
2484
2774
  }
2485
2775
 
@@ -2537,6 +2827,13 @@ html[data-sb-mode="device"] .status {
2537
2827
  background: var(--sb-color-sheet-background);
2538
2828
  }
2539
2829
 
2830
+ /* iOS 26: the alert is a rounder glass card. */
2831
+ .sb-alert[data-sb-style="alert"] .sb-alert-card {
2832
+ border-radius: 34px;
2833
+ background: var(--sb-alert-card);
2834
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12);
2835
+ }
2836
+
2540
2837
  .sb-alert-title,
2541
2838
  .sb-alert-message {
2542
2839
  position: absolute;
@@ -2547,6 +2844,16 @@ html[data-sb-mode="device"] .status {
2547
2844
  overflow: hidden;
2548
2845
  }
2549
2846
 
2847
+ /* iOS 26: an alert's title and message are leading-aligned, the message dim. */
2848
+ .sb-alert[data-sb-style="alert"] .sb-alert-title,
2849
+ .sb-alert[data-sb-style="alert"] .sb-alert-message {
2850
+ text-align: left;
2851
+ }
2852
+
2853
+ .sb-alert[data-sb-style="alert"] .sb-alert-message {
2854
+ color: var(--sb-color-secondary);
2855
+ }
2856
+
2550
2857
  /* The action sheet's title and message are a dim caption. */
2551
2858
  .sb-alert[data-sb-style="dialog"] .sb-alert-title,
2552
2859
  .sb-alert[data-sb-style="dialog"] .sb-alert-message {
@@ -2587,11 +2894,32 @@ html[data-sb-mode="device"] .status {
2587
2894
  pointer-events: none;
2588
2895
  }
2589
2896
 
2590
- .sb-alert-buttons > .sb-button[data-sb-role="cancel"] {
2897
+ /* iOS 26: an alert's buttons are gray capsules, in the label color. */
2898
+ .sb-alert[data-sb-style="alert"] .sb-alert-buttons > .sb-button,
2899
+ .sb-alert[data-sb-style="alert"] .sb-alert-ok {
2900
+ border-radius: 9999px;
2901
+ background: var(--sb-color-fill);
2902
+ color: var(--sb-color-primary);
2903
+ }
2904
+
2905
+ .sb-alert[data-sb-style="alert"] .sb-alert-buttons > .sb-button::before,
2906
+ .sb-alert[data-sb-style="alert"] .sb-alert-buttons > .sb-button::after,
2907
+ .sb-alert[data-sb-style="alert"] .sb-alert-ok::before {
2908
+ display: none;
2909
+ }
2910
+
2911
+ .sb-alert[data-sb-style="alert"] .sb-alert-buttons > .sb-button:active,
2912
+ .sb-alert[data-sb-style="alert"] .sb-alert-ok:active {
2913
+ background: var(--sb-color-secondary-fill);
2914
+ filter: brightness(0.92);
2915
+ }
2916
+
2917
+ .sb-alert[data-sb-style="dialog"] .sb-alert-buttons > .sb-button[data-sb-role="cancel"] {
2591
2918
  font-weight: 600;
2592
2919
  }
2593
2920
 
2594
- .sb-alert-buttons > .sb-button[data-sb-role="destructive"] {
2921
+ .sb-alert-buttons > .sb-button[data-sb-role="destructive"],
2922
+ .sb-alert[data-sb-style="alert"] .sb-alert-buttons > .sb-button[data-sb-role="destructive"] {
2595
2923
  color: var(--sb-color-red);
2596
2924
  }
2597
2925
 
@@ -2672,14 +3000,14 @@ html[data-sb-mode="device"] .status {
2672
3000
 
2673
3001
  /* ---------------------------------------------------------------------------
2674
3002
  Stepper: the label children are positioned by their frames; the 94×32
2675
- segmented control sits at the engine's `control` rect, its halves divided
2676
- by a hairline. A dimmed half (`aria-disabled`) swallows taps.
3003
+ capsule (iOS 26) sits at the engine's `control` rect, its halves divided
3004
+ by a short line. A dimmed half (`aria-disabled`) swallows taps.
2677
3005
  ------------------------------------------------------------------------- */
2678
3006
  .sb-stepper-control {
2679
3007
  position: absolute;
2680
3008
  display: flex;
2681
3009
  box-sizing: border-box;
2682
- border-radius: 8px;
3010
+ border-radius: 16px;
2683
3011
  background: var(--sb-color-tertiary-fill);
2684
3012
  overflow: hidden;
2685
3013
  }
@@ -2703,7 +3031,17 @@ html[data-sb-mode="device"] .status {
2703
3031
  }
2704
3032
 
2705
3033
  .sb-stepper-button + .sb-stepper-button {
2706
- box-shadow: -1px 0 0 var(--sb-color-separator);
3034
+ position: relative;
3035
+ }
3036
+
3037
+ .sb-stepper-button + .sb-stepper-button::before {
3038
+ content: "";
3039
+ position: absolute;
3040
+ left: 0;
3041
+ top: 7px;
3042
+ bottom: 7px;
3043
+ width: 1px;
3044
+ background: var(--sb-color-opaque-separator);
2707
3045
  }
2708
3046
 
2709
3047
  .sb-stepper-button > svg {
@@ -2724,8 +3062,9 @@ html[data-sb-mode="device"] .status {
2724
3062
  }
2725
3063
 
2726
3064
  /* ---------------------------------------------------------------------------
2727
- Slider: the label children are positioned by their frames; the 4pt track,
2728
- the accent fill and the 27pt thumb are chrome at the engine's rects. A
3065
+ Slider: the label children are positioned by their frames; the 6pt track,
3066
+ the accent fill and the 38 × 24 pill thumb (iOS 26) are chrome at the
3067
+ engine's rects. A
2729
3068
  pointer anywhere on the slider moves the thumb (touch-action none).
2730
3069
  ------------------------------------------------------------------------- */
2731
3070
  .sb-slider {
@@ -2736,20 +3075,20 @@ html[data-sb-mode="device"] .status {
2736
3075
 
2737
3076
  .sb-slider-track {
2738
3077
  position: absolute;
2739
- border-radius: 2px;
3078
+ border-radius: 3px;
2740
3079
  background: var(--sb-color-tertiary-fill);
2741
3080
  }
2742
3081
 
2743
3082
  .sb-slider-fill {
2744
3083
  position: absolute;
2745
- border-radius: 2px;
3084
+ border-radius: 3px;
2746
3085
  background: var(--sb-tint, var(--sb-color-accent));
2747
3086
  }
2748
3087
 
2749
3088
  .sb-slider-thumb {
2750
3089
  position: absolute;
2751
3090
  box-sizing: border-box;
2752
- border-radius: 50%;
3091
+ border-radius: 12px;
2753
3092
  background: #ffffff;
2754
3093
  box-shadow:
2755
3094
  0 3px 8px rgba(0, 0, 0, 0.15),
@@ -2832,3 +3171,85 @@ html[data-sb-mode="device"] .status {
2832
3171
  animation-duration: 0.01s;
2833
3172
  }
2834
3173
  }
3174
+
3175
+ /* Rich text (src/richText.ts): a block per line, the height the engine gave
3176
+ it; fragments share its baseline. An inline image sits on the baseline. */
3177
+ .sb-text-line {
3178
+ display: block;
3179
+ white-space: pre;
3180
+ }
3181
+
3182
+ .sb-text-image {
3183
+ display: inline-block;
3184
+ position: relative;
3185
+ vertical-align: baseline;
3186
+ overflow: hidden;
3187
+ }
3188
+
3189
+ /* A symbol's glyph is centered in its line-height square, as a standalone
3190
+ `Image(systemName:)` is. */
3191
+ .sb-text-symbol {
3192
+ display: inline-flex;
3193
+ vertical-align: text-bottom;
3194
+ }
3195
+
3196
+ /* A link: tinted unless the run has its own foreground. */
3197
+ .sb-link {
3198
+ color: var(--sb-tint, var(--sb-color-accent));
3199
+ cursor: pointer;
3200
+ }
3201
+
3202
+ /* ---------------------------------------------------------------------------
3203
+ A vertical text field (`TextField(_:text:axis: .vertical)`): a textarea the
3204
+ engine sizes to its lines, in the font it measured them in (layoutDom.ts);
3205
+ it scrolls inside past its `lineLimit`.
3206
+ ------------------------------------------------------------------------- */
3207
+ textarea.sb-textfield {
3208
+ box-sizing: border-box;
3209
+ margin: 0;
3210
+ padding: 0;
3211
+ resize: none;
3212
+ overflow-y: auto;
3213
+ scrollbar-width: none;
3214
+ white-space: pre-wrap;
3215
+ overflow-wrap: break-word;
3216
+ }
3217
+
3218
+ textarea.sb-textfield[data-sb-style="roundedBorder"] {
3219
+ padding: 5px 7px;
3220
+ }
3221
+
3222
+ /* A section's view header / footer: secondary text above or below the card
3223
+ (the engine sets the fonts of the texts inside: headline headers, footnote
3224
+ footers). */
3225
+ .sb-sectionheader,
3226
+ .sb-sectionfooter {
3227
+ color: var(--sb-color-secondary);
3228
+ text-align: left;
3229
+ }
3230
+
3231
+ /* `.scrollContentBackground(.hidden)`: the list's own background goes; rows keep theirs. */
3232
+ .sb-list[data-sb-background="hidden"] {
3233
+ background: transparent;
3234
+ }
3235
+
3236
+ /* A custom shape's path (src/pathShape.ts): an SVG filling the shape's box,
3237
+ whose view box is the size the path was drawn for. Strokes may lie outside
3238
+ the box, as in SwiftUI. */
3239
+ svg.sb-path {
3240
+ display: block;
3241
+ width: 100%;
3242
+ height: 100%;
3243
+ overflow: visible;
3244
+ pointer-events: none;
3245
+ }
3246
+
3247
+ /* A `Canvas` (src/canvasDrawing.ts): its drawing is clipped to its box. */
3248
+ .sb-canvas {
3249
+ overflow: hidden;
3250
+ }
3251
+
3252
+ .sb-canvas > svg {
3253
+ display: block;
3254
+ overflow: hidden;
3255
+ }