@swiftbrowser/web 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/styles.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /* ---------------------------------------------------------------------------
2
2
  iOS design tokens. Light values live on the device frame; dark ones switch
3
- with the frame's data-theme so the page chrome and the "phone" are
4
- independent.
3
+ with the frame's data-theme so the page around it (page.css) and the
4
+ "phone" are independent.
5
5
  ------------------------------------------------------------------------- */
6
6
 
7
7
  :root {
@@ -15,15 +15,6 @@
15
15
  overrides this with the current size (see .device below). */
16
16
  --sb-font-size-body: 17px;
17
17
 
18
- /* Page chrome (not iOS) */
19
- --page-bg: #e9e9ee;
20
- --page-fg: #1c1c1e;
21
- --page-muted: #6e6e73;
22
- --page-border: rgba(0, 0, 0, 0.08);
23
- --page-surface: rgba(255, 255, 255, 0.8);
24
- --page-error-bg: #ffe5e5;
25
- --page-error-fg: #a1000d;
26
-
27
18
  /* Device dimensions (iPhone 15 Pro logical points) */
28
19
  --device-screen-width: 393px;
29
20
  --device-screen-height: 852px;
@@ -41,18 +32,6 @@
41
32
  --sb-inset-left: env(safe-area-inset-left, 0px);
42
33
  }
43
34
 
44
- @media (prefers-color-scheme: dark) {
45
- :root {
46
- --page-bg: #111114;
47
- --page-fg: #f2f2f7;
48
- --page-muted: #98989f;
49
- --page-border: rgba(255, 255, 255, 0.1);
50
- --page-surface: rgba(28, 28, 30, 0.85);
51
- --page-error-bg: #3a0b10;
52
- --page-error-fg: #ff8a8a;
53
- }
54
- }
55
-
56
35
  .device {
57
36
  /* Dynamic Type: main.ts writes --sb-ts-<style>-size / -lh / -scale on the
58
37
  device frame for the selected size (typography.ts); the fallbacks are the
@@ -164,159 +143,28 @@
164
143
  }
165
144
 
166
145
  /* ---------------------------------------------------------------------------
167
- Page layout
146
+ The renderer's boxes size by their borders. Only its own DOM: a page that
147
+ draws an island (@swiftbrowser/react) keeps its own box model.
168
148
  ------------------------------------------------------------------------- */
169
149
 
170
- *,
171
- *::before,
172
- *::after {
150
+ .device,
151
+ .device *,
152
+ .device *::before,
153
+ .device *::after {
173
154
  box-sizing: border-box;
174
155
  }
175
156
 
176
- html,
177
- body {
178
- margin: 0;
179
- min-height: 100%;
180
- }
181
-
182
- body {
183
- display: flex;
184
- flex-direction: column;
185
- min-height: 100vh;
186
- background: var(--page-bg);
187
- color: var(--page-fg);
188
- font-family: var(--sb-font-family);
189
- font-size: 14px;
190
- -webkit-font-smoothing: antialiased;
191
- }
192
-
193
- .toolbar {
194
- position: sticky;
195
- top: 0;
196
- z-index: 10;
197
- display: flex;
198
- align-items: center;
199
- gap: 10px;
200
- padding: 10px 16px;
201
- background: var(--page-surface);
202
- border-bottom: 1px solid var(--page-border);
203
- backdrop-filter: blur(16px);
204
- }
205
-
206
- .toolbar-brand {
207
- font-weight: 600;
208
- letter-spacing: -0.01em;
209
- }
210
-
211
- .toolbar-sep {
212
- width: 1px;
213
- height: 18px;
214
- background: var(--page-border);
215
- }
216
-
217
- .toolbar-label {
218
- color: var(--page-muted);
219
- }
220
-
221
- .toolbar-value {
222
- font-variant-numeric: tabular-nums;
223
- padding: 2px 8px;
224
- border-radius: 6px;
225
- background: var(--page-border);
226
- }
227
-
228
- .toolbar-spacer {
229
- flex: 1 1 0;
230
- }
231
-
232
- /* Hot-reload indicator, driven by the dev server's sb:build-* events. */
233
- .toolbar-build {
234
- display: inline-flex;
235
- align-items: center;
236
- gap: 6px;
237
- color: var(--page-muted);
238
- }
239
-
240
- .toolbar-build[hidden] {
241
- display: none;
242
- }
243
-
244
- .toolbar-build::before {
245
- content: "";
246
- width: 10px;
247
- height: 10px;
248
- border-radius: 50%;
249
- border: 1.5px solid currentColor;
250
- border-top-color: transparent;
251
- animation: sb-spin 0.9s linear infinite;
252
- }
253
-
254
- .toolbar-build[data-state="error"] {
255
- color: var(--page-error-fg);
256
- }
257
-
258
- .toolbar-build[data-state="error"]::before {
259
- animation: none;
260
- border-color: currentColor;
261
- }
262
-
263
- .toolbar-button {
264
- font: inherit;
265
- padding: 6px 12px;
266
- border: 1px solid var(--page-border);
267
- border-radius: 8px;
268
- background: transparent;
269
- color: var(--page-fg);
270
- cursor: pointer;
271
- }
272
-
273
- .toolbar-button:hover {
274
- background: var(--page-border);
275
- }
276
-
277
- .toolbar-button[aria-pressed="true"] {
278
- background: var(--page-fg);
279
- color: var(--page-bg);
280
- border-color: transparent;
281
- }
282
-
283
- .toolbar-select {
284
- font: inherit;
285
- padding: 5px 8px;
286
- border: 1px solid var(--page-border);
287
- border-radius: 8px;
288
- background: transparent;
289
- color: var(--page-fg);
290
- cursor: pointer;
291
- }
292
-
293
- .stage {
294
- display: flex;
295
- flex-direction: column;
296
- align-items: center;
297
- gap: 16px;
298
- padding: 32px 16px 48px;
299
- }
300
-
301
- .status {
302
- max-width: 560px;
303
- margin: 0;
304
- padding: 10px 14px;
305
- border-radius: 10px;
306
- background: var(--page-error-bg);
307
- color: var(--page-error-fg);
308
- font-family: ui-monospace, "SF Mono", Menlo, monospace;
309
- font-size: 12px;
310
- white-space: pre-wrap;
311
- word-break: break-word;
312
- }
313
-
314
157
  /* ---------------------------------------------------------------------------
315
158
  Device frame
316
159
  ------------------------------------------------------------------------- */
317
160
 
318
161
  .device {
319
162
  position: relative;
163
+ }
164
+
165
+ /* The iPhone's size and bezel: the frame's only. An island (`.sb-island`,
166
+ below) is the page's box, sized by the page's own CSS. */
167
+ .device:not(.sb-island) {
320
168
  flex: 0 0 auto;
321
169
  width: calc(var(--device-screen-width) + 2 * var(--device-bezel));
322
170
  height: calc(var(--device-screen-height) + 2 * var(--device-bezel));
@@ -485,53 +333,13 @@ body {
485
333
  /* ---------------------------------------------------------------------------
486
334
  Device mode (html[data-sb-mode="device"], see src/deviceMode.ts)
487
335
 
488
- On a real phone there is no toolbar and no fake bezel: the OS draws the
489
- status bar and #screen fills the viewport. The layout engine still gets a
490
- status-bar / home-indicator safe area, but from env(safe-area-inset-*)
491
- (via --sb-inset-*), so it is only non-zero in standalone mode / landscape.
336
+ On a real phone there is no fake bezel: the OS draws the status bar and
337
+ #screen fills the viewport (the page's part, its toolbar and background,
338
+ is page.css). The layout engine still gets a status-bar / home-indicator
339
+ safe area, but from env(safe-area-inset-*) (via --sb-inset-*), so it is
340
+ only non-zero in standalone mode / landscape.
492
341
  ------------------------------------------------------------------------- */
493
342
 
494
- html[data-sb-mode="device"] {
495
- height: 100%;
496
- overflow: hidden;
497
- overscroll-behavior: none;
498
- /* No double-tap zoom, no text inflation in landscape. */
499
- touch-action: manipulation;
500
- -webkit-text-size-adjust: 100%;
501
- text-size-adjust: 100%;
502
- }
503
-
504
- html[data-sb-mode="device"] body {
505
- min-height: 100dvh;
506
- overflow: hidden;
507
- overscroll-behavior: none;
508
- touch-action: manipulation;
509
- }
510
-
511
- /* The page behind the app is the OS appearance's system background from the
512
- first paint (Safari tints its status bar strip from it); chromeColor.ts
513
- then keeps it at the color the app draws at its top edge. */
514
- html[data-sb-mode="device"],
515
- html[data-sb-mode="device"] body {
516
- background: #ffffff;
517
- }
518
-
519
- @media (prefers-color-scheme: dark) {
520
- html[data-sb-mode="device"],
521
- html[data-sb-mode="device"] body {
522
- background: #000000;
523
- }
524
- }
525
-
526
- html[data-sb-mode="device"] .toolbar {
527
- display: none;
528
- }
529
-
530
- html[data-sb-mode="device"] .stage {
531
- padding: 0;
532
- gap: 0;
533
- }
534
-
535
343
  html[data-sb-mode="device"] .device {
536
344
  position: fixed;
537
345
  top: 0;
@@ -558,18 +366,6 @@ html[data-sb-mode="device"] .device-screen {
558
366
  border-radius: 0;
559
367
  }
560
368
 
561
- /* The error banner floats over the app, clear of the home indicator. */
562
- html[data-sb-mode="device"] .status {
563
- position: fixed;
564
- left: 16px;
565
- right: 16px;
566
- bottom: calc(16px + var(--sb-inset-bottom));
567
- z-index: 10;
568
- max-width: none;
569
- max-height: 40dvh;
570
- overflow: auto;
571
- }
572
-
573
369
  /* ---------------------------------------------------------------------------
574
370
  Rendered elements
575
371
 
@@ -1337,8 +1133,12 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
1337
1133
  mask-image: linear-gradient(to bottom, #000 60%, transparent);
1338
1134
  }
1339
1135
 
1136
+ /* Its strength follows the content under the bar, ramping up over the
1137
+ first 40px scrolled (`--sb-edge-top`, src/scrollEdge.ts); the transition
1138
+ smooths a scroll that jumps. */
1340
1139
  .sb-navscreen[data-sb-scrolled] > .sb-navbar::before {
1341
- opacity: 1;
1140
+ opacity: var(--sb-edge-top, 1);
1141
+ transition: opacity 0.12s linear;
1342
1142
  }
1343
1143
 
1344
1144
  /* At the bottom: a list or scroll view runs under the tab bar, or its
@@ -1369,21 +1169,24 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
1369
1169
  pointer-events: none;
1370
1170
  opacity: 0;
1371
1171
  transition: opacity 0.2s ease;
1172
+ /* iOS 26 mostly fades the content here (it stays faintly legible under
1173
+ the bar) and barely blurs it. */
1372
1174
  background: linear-gradient(
1373
1175
  to top,
1374
- var(--sb-edge-color) 0%,
1375
- color-mix(in srgb, var(--sb-edge-color) 75%, transparent) 45%,
1176
+ color-mix(in srgb, var(--sb-edge-color) 70%, transparent) 0%,
1177
+ color-mix(in srgb, var(--sb-edge-color) 55%, transparent) 45%,
1376
1178
  transparent 100%
1377
1179
  );
1378
- -webkit-backdrop-filter: blur(6px);
1379
- backdrop-filter: blur(6px);
1180
+ -webkit-backdrop-filter: blur(1.5px);
1181
+ backdrop-filter: blur(1.5px);
1380
1182
  -webkit-mask-image: linear-gradient(to top, #000 60%, transparent);
1381
1183
  mask-image: linear-gradient(to top, #000 60%, transparent);
1382
1184
  }
1383
1185
 
1384
1186
  .sb-navscreen[data-sb-scrolled-bottom] > .sb-navscreen-body::after,
1385
1187
  .sb-tab[data-sb-scrolled-bottom]::after {
1386
- opacity: 1;
1188
+ opacity: var(--sb-edge-bottom, 1);
1189
+ transition: opacity 0.12s linear;
1387
1190
  }
1388
1191
 
1389
1192
  /* `.scrollEdgeEffectStyle(.hard, for:)`: an opaque backing exactly behind
@@ -2039,6 +1842,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2039
1842
  .sb-grid,
2040
1843
  .sb-gridrow,
2041
1844
  .sb-lazyvgrid,
1845
+ .sb-lazyhgrid,
2042
1846
  .sb-decorated {
2043
1847
  display: block;
2044
1848
  }
@@ -2315,30 +2119,41 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2315
2119
 
2316
2120
  /* Their symbols are drawn a little larger and heavier (semibold), as iOS 26
2317
2121
  draws bar buttons: a plus is 17.7 pt with 2.33 pt bars there. */
2318
- .sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"]) .sb-image > svg {
2122
+ .sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"], [data-sb-placement="bottom"]) .sb-image > svg {
2319
2123
  width: 1.57em;
2320
2124
  height: 1.57em;
2321
2125
  stroke-width: 1.85;
2322
2126
  }
2323
2127
 
2128
+ /* A bottom toolbar's symbols are as large but regular weight. */
2129
+ .sb-toolbar[data-sb-placement="bottom"] .sb-image > svg {
2130
+ stroke-width: 1.5;
2131
+ }
2132
+
2133
+ /* iOS 26: a bottom toolbar's items float over the content on Liquid Glass
2134
+ capsules (engine `bottomGlass0` …, src/layoutDom.ts), drawn in the label
2135
+ color unless tinted, above the scroll edge effect. */
2324
2136
  .sb-toolbar[data-sb-placement="bottom"] {
2325
2137
  z-index: 3;
2326
- background: var(--sb-color-tabbar-background);
2327
- backdrop-filter: blur(20px) saturate(180%);
2328
- -webkit-backdrop-filter: blur(20px) saturate(180%);
2138
+ --sb-color-accent: var(--sb-color-primary);
2329
2139
  }
2330
2140
 
2331
- .sb-toolbar[data-sb-placement="bottom"]::before {
2332
- content: "";
2141
+ .sb-bottom-glass {
2333
2142
  position: absolute;
2334
- left: 0;
2335
- right: 0;
2336
- top: 0;
2337
- height: 1px;
2338
- background: var(--sb-color-separator);
2143
+ z-index: 2;
2144
+ border-radius: 24px;
2145
+ background: var(--sb-glass-background);
2146
+ box-shadow: var(--sb-glass-shadow);
2147
+ backdrop-filter: blur(12px) saturate(180%);
2148
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
2339
2149
  pointer-events: none;
2340
2150
  }
2341
2151
 
2152
+ /* A ToolbarSpacer draws nothing (the engine also hides it). */
2153
+ .sb-toolbar[data-sb-spacer] {
2154
+ display: none;
2155
+ }
2156
+
2342
2157
  /* Search field: a capsule (engine `searchField` rect) with a
2343
2158
  magnifier leading; the input sends `text` events like a textfield. */
2344
2159
  .sb-searchfield {
@@ -2641,6 +2456,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2641
2456
  .sb-grid,
2642
2457
  .sb-gridrow,
2643
2458
  .sb-lazyvgrid,
2459
+ .sb-lazyhgrid,
2644
2460
  .sb-viewthatfits,
2645
2461
  .sb-decorated,
2646
2462
  .sb-gesture,
@@ -2648,8 +2464,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2648
2464
  pointer-events: none;
2649
2465
  }
2650
2466
 
2651
- :is(.sb-vstack, .sb-hstack, .sb-zstack, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture)
2652
- > [data-sb-kind]:not(.sb-vstack, .sb-hstack, .sb-zstack, .sb-spacer, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture) {
2467
+ :is(.sb-vstack, .sb-hstack, .sb-zstack, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-lazyhgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture)
2468
+ > [data-sb-kind]:not(.sb-vstack, .sb-hstack, .sb-zstack, .sb-spacer, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-lazyhgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture) {
2653
2469
  pointer-events: auto;
2654
2470
  }
2655
2471
 
@@ -3629,19 +3445,12 @@ svg.sb-path {
3629
3445
 
3630
3446
  /* ---------------------------------------------------------------------------
3631
3447
  Islands (@swiftbrowser/react's `SwiftUIView`): a SwiftUI root inside a
3632
- page's own layout, with the device's tokens and none of its frame. The
3633
- page sizes the island; in `content` fit the renderer sets the root's height
3448
+ page's own layout, with the device's tokens and none of its frame: the
3449
+ frame's size, bezel and shadow are `.device:not(.sb-island)`'s above, so
3450
+ the page's own CSS, however specific, gives the island its width and
3451
+ height. In `content` fit the renderer sets the root's height
3634
3452
  from the layout.
3635
3453
  ------------------------------------------------------------------------- */
3636
- .device.sb-island {
3637
- width: auto;
3638
- padding: 0;
3639
- border-radius: 0;
3640
- background: none;
3641
- box-shadow: none;
3642
- flex: initial;
3643
- }
3644
-
3645
3454
  .device.sb-island::before,
3646
3455
  .device.sb-island::after {
3647
3456
  display: none;
package/src/symbols.ts CHANGED
@@ -215,6 +215,7 @@ import {
215
215
  Link2,
216
216
  List,
217
217
  ListChecks,
218
+ ListFilter,
218
219
  ListIndentIncrease,
219
220
  ListMusic,
220
221
  ListOrdered,
@@ -571,7 +572,8 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
571
572
  'tag.fill': solid(Tag),
572
573
  textformat: outline(CaseSensitive),
573
574
  'line.3.horizontal': outline(Menu),
574
- 'line.3.horizontal.decrease': outline(Funnel),
575
+ // Three lines, each shorter than the one above.
576
+ 'line.3.horizontal.decrease': outline(ListFilter),
575
577
  'line.3.horizontal.decrease.circle': outline(Funnel),
576
578
  'slider.horizontal.3': outline(SlidersHorizontal),
577
579
  'list.bullet': outline(List),