@kerfjs/ui 5.0.0-beta.100 → 5.0.0-beta.101

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 (148) hide show
  1. package/README.md +3 -1
  2. package/ai/component-catalog.json +214 -18
  3. package/ai/component-composition.json +216 -8
  4. package/ai/components/README.md +2 -0
  5. package/ai/components/backdrop-image.md +84 -0
  6. package/ai/components/nav-stack.md +6 -1
  7. package/ai/components/pane.md +11 -1
  8. package/ai/components/recipe-media-detail.md +57 -0
  9. package/ai/components/surface-scaffold.md +13 -6
  10. package/ai/components/tab-navigator.md +6 -1
  11. package/ai/components/toolbar-control-group.md +1 -0
  12. package/ai/public-api-signatures-v1.md +356 -32
  13. package/ai/skill.md +53 -5
  14. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  15. package/dist/backdrop-image.d.ts +132 -0
  16. package/dist/backdrop-image.js +5 -0
  17. package/dist/backdrop-image.js.map +1 -0
  18. package/dist/browser/backdrop-image.js +3 -0
  19. package/dist/catalog-resources.d.ts +2 -2
  20. package/dist/catalog.d.ts +2 -2
  21. package/dist/catalog.js +10 -9
  22. package/dist/catalog.js.map +1 -1
  23. package/dist/{chunk-QIG4IGUP.js → chunk-2PXZBFEC.js} +3 -1
  24. package/dist/chunk-2PXZBFEC.js.map +1 -0
  25. package/dist/{chunk-T4WJ5VGP.js → chunk-3SKAEPPZ.js} +2 -2
  26. package/dist/{chunk-T4WJ5VGP.js.map → chunk-3SKAEPPZ.js.map} +1 -1
  27. package/dist/chunk-5NQF35VO.js +67 -0
  28. package/dist/chunk-5NQF35VO.js.map +1 -0
  29. package/dist/{chunk-CMETHPKR.js → chunk-6E2H2E5A.js} +29 -8
  30. package/dist/chunk-6E2H2E5A.js.map +1 -0
  31. package/dist/{chunk-PIVY32AE.js → chunk-BYFTHA4I.js} +16 -3
  32. package/dist/chunk-BYFTHA4I.js.map +1 -0
  33. package/dist/{chunk-HGV7J4T6.js → chunk-EKHDLBRP.js} +1 -1
  34. package/dist/chunk-EKHDLBRP.js.map +1 -0
  35. package/dist/{chunk-QROYYGWU.js → chunk-FNZQOUZ5.js} +2 -2
  36. package/dist/{chunk-QROYYGWU.js.map → chunk-FNZQOUZ5.js.map} +1 -1
  37. package/dist/{chunk-EOO6PYGF.js → chunk-IXLUZHGT.js} +4 -4
  38. package/dist/{chunk-EOO6PYGF.js.map → chunk-IXLUZHGT.js.map} +1 -1
  39. package/dist/{chunk-UMKV7YVQ.js → chunk-LPHU44S3.js} +5 -1
  40. package/dist/chunk-LPHU44S3.js.map +1 -0
  41. package/dist/{chunk-INI4WDWC.js → chunk-M5KZ26VV.js} +27 -4
  42. package/dist/chunk-M5KZ26VV.js.map +1 -0
  43. package/dist/{chunk-TLMMFQWM.js → chunk-NS762OVM.js} +3 -3
  44. package/dist/{chunk-TLMMFQWM.js.map → chunk-NS762OVM.js.map} +1 -1
  45. package/dist/{chunk-CXZXHZTR.js → chunk-OZ6X76G5.js} +34 -1
  46. package/dist/chunk-OZ6X76G5.js.map +1 -0
  47. package/dist/{chunk-YOA5W5ZH.js → chunk-PKL6QJKK.js} +31 -3
  48. package/dist/chunk-PKL6QJKK.js.map +1 -0
  49. package/dist/chunk-QTSMHH5J.js +85 -0
  50. package/dist/chunk-QTSMHH5J.js.map +1 -0
  51. package/dist/chunk-SCDWURBO.js +136 -0
  52. package/dist/chunk-SCDWURBO.js.map +1 -0
  53. package/dist/{chunk-RHD7RKGI.js → chunk-SIW64V65.js} +3 -3
  54. package/dist/{chunk-RHD7RKGI.js.map → chunk-SIW64V65.js.map} +1 -1
  55. package/dist/chunk-SQ5DH66U.js +37 -0
  56. package/dist/chunk-SQ5DH66U.js.map +1 -0
  57. package/dist/{chunk-PYBBYSZ3.js → chunk-YBWQR2OK.js} +5 -5
  58. package/dist/{chunk-PYBBYSZ3.js.map → chunk-YBWQR2OK.js.map} +1 -1
  59. package/dist/{chunk-3GLYIIFR.js → chunk-ZYU5WRC3.js} +2 -2
  60. package/dist/{chunk-3GLYIIFR.js.map → chunk-ZYU5WRC3.js.map} +1 -1
  61. package/dist/collapsible-panel.js +3 -2
  62. package/dist/collapsible-panel.js.map +1 -1
  63. package/dist/index.d.ts +5 -3
  64. package/dist/index.js +24 -20
  65. package/dist/list-action-row.js +1 -1
  66. package/dist/list-header.js +1 -1
  67. package/dist/list-item.js +1 -1
  68. package/dist/{nav-stack-VXTumzKa.d.ts → nav-stack-CU_0dKtI.d.ts} +41 -5
  69. package/dist/nav-stack.d.ts +4 -4
  70. package/dist/nav-stack.js +2 -1
  71. package/dist/pane.d.ts +76 -4
  72. package/dist/pane.js +2 -1
  73. package/dist/select.js +1 -1
  74. package/dist/split-view.d.ts +4 -4
  75. package/dist/split-view.js +3 -2
  76. package/dist/split-view.js.map +1 -1
  77. package/dist/state-banner.js +1 -1
  78. package/dist/styles/backdrop-image.css +241 -0
  79. package/dist/styles/components/actions/toolbar/toolbar.css +9 -0
  80. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +128 -0
  81. package/dist/styles/components/layout/pane/pane.css +121 -1
  82. package/dist/styles/components/navigation/nav-stack/nav-stack.css +207 -6
  83. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +121 -3
  84. package/dist/styles/components/surfaces/backdrop-image/backdrop-image.css +241 -0
  85. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +166 -2
  86. package/dist/styles/foundation.css +165 -8
  87. package/dist/styles/nav-stack.css +207 -6
  88. package/dist/styles/overlay-sheet.css +211 -0
  89. package/dist/styles/pane.css +121 -1
  90. package/dist/styles/styles.css +1 -0
  91. package/dist/styles/surface-scaffold.css +166 -2
  92. package/dist/styles/tab-navigator.css +121 -3
  93. package/dist/styles/toolbar-control-group.css +128 -0
  94. package/dist/styles/toolbar.css +9 -0
  95. package/dist/styles/webawesome.css +10 -0
  96. package/dist/surface-scaffold-register.d.ts +3 -2
  97. package/dist/surface-scaffold-register.js +113 -10
  98. package/dist/surface-scaffold-register.js.map +1 -1
  99. package/dist/surface-scaffold.d.ts +38 -5
  100. package/dist/surface-scaffold.js +1 -1
  101. package/dist/tab-navigator.d.ts +43 -3
  102. package/dist/tab-navigator.js +35 -6
  103. package/dist/tab-navigator.js.map +1 -1
  104. package/dist/toolbar-control-group.d.ts +13 -2
  105. package/dist/toolbar-control-group.js +1 -1
  106. package/dist/toolbar-text.d.ts +6 -1
  107. package/dist/toolbar-text.js +1 -1
  108. package/dist/toolbar.d.ts +1 -0
  109. package/dist/wire-catalog.d.ts +2 -2
  110. package/dist/wire-catalog.js +1 -1
  111. package/dist/wire-nav-stack.js +29 -3
  112. package/dist/wire-nav-stack.js.map +1 -1
  113. package/dist/wire-overlay-sheets.d.ts +20 -0
  114. package/dist/wire-overlay-sheets.js +4 -0
  115. package/dist/wire-overlay-sheets.js.map +1 -0
  116. package/dist/wire-scroll-dividers.js +1 -1
  117. package/dist/wire-token-search-fields.js +1 -1
  118. package/dist/workbench.d.ts +2 -2
  119. package/dist/workbench.js +2 -1
  120. package/docs/accessibility.md +35 -0
  121. package/docs/app-layouts.md +13 -0
  122. package/docs/backdrop-image.md +128 -0
  123. package/docs/component-contract.md +2 -0
  124. package/docs/component-selection.md +11 -2
  125. package/docs/design/templates/toolbar-control-group/glass-dark.svg +1 -0
  126. package/docs/design/templates/toolbar-control-group/glass.svg +1 -0
  127. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  128. package/docs/design/templates/toolbar-control-group.svg +1 -1
  129. package/docs/design/templates.md +8 -0
  130. package/docs/layout.md +135 -5
  131. package/docs/manual-test-plan.md +29 -0
  132. package/docs/nav-stack.md +37 -1
  133. package/docs/recipes.md +24 -0
  134. package/docs/surface-scaffold.md +149 -3
  135. package/docs/tab-navigator.md +36 -0
  136. package/docs/ux-demo.md +11 -1
  137. package/llms.txt +42 -4
  138. package/package.json +13 -1
  139. package/ux-demo/recipes/loaders.ts +2 -0
  140. package/ux-demo/recipes/media-detail.tsx +272 -0
  141. package/dist/chunk-CMETHPKR.js.map +0 -1
  142. package/dist/chunk-CXZXHZTR.js.map +0 -1
  143. package/dist/chunk-HGV7J4T6.js.map +0 -1
  144. package/dist/chunk-INI4WDWC.js.map +0 -1
  145. package/dist/chunk-PIVY32AE.js.map +0 -1
  146. package/dist/chunk-QIG4IGUP.js.map +0 -1
  147. package/dist/chunk-UMKV7YVQ.js.map +0 -1
  148. package/dist/chunk-YOA5W5ZH.js.map +0 -1
@@ -420,6 +420,134 @@
420
420
  --kui-toolbar-control-color: var(--kui-color-text);
421
421
  }
422
422
 
423
+ /* `material="glass"`: the group floats over imagery or a clear toolbar on the
424
+ translucent chrome material (foundation.css), blurred and saturated, the
425
+ per-control glass pills of overlay chrome. The fill and filter come from the
426
+ shared tokens, so the group inherits every opaque fallback they carry
427
+ (reduced transparency, increased contrast, static effects, no
428
+ backdrop-filter). The group's own 1px border becomes the specular edge, a
429
+ translucent white highlight, instead of a gray border plus a highlight; a
430
+ soft two-layer shadow lifts it off the content beneath.
431
+
432
+ Controls take the normal text color: over its own 80% surface material the
433
+ quiet control color falls below AA over the worst-case backdrop, while the
434
+ text color stays well above it (about 10:1 light over black, 8:1 dark over
435
+ white). Hover mixes a text-color tint into the material itself, so a lone
436
+ control's whole-group hover keeps a fill rather than replacing it with a
437
+ bare tint. A raised or outline selection paints an opaque surface pill, so
438
+ the brand selected color keeps AA over any content. A borderless group has
439
+ no surface and ignores the material. */
440
+ .kui-toolbar-control-group[data-material="glass"]:not(
441
+ [data-appearance="borderless"]
442
+ ) {
443
+ --kui-toolbar-control-border: light-dark(
444
+ rgb(255 255 255 / 0.72),
445
+ rgb(255 255 255 / 0.16)
446
+ );
447
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
448
+ backdrop-filter: var(--kui-chrome-material-filter);
449
+ box-shadow:
450
+ 0 0.0625rem 0.1875rem light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.32)),
451
+ 0 0.375rem 1.125rem light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.28));
452
+ }
453
+
454
+ .kui-toolbar-control-group[data-material="glass"]:not(
455
+ [data-appearance="borderless"],
456
+ [data-tone="dark"],
457
+ [data-scrim="true"]
458
+ ) {
459
+ --kui-toolbar-control-background: var(--kui-chrome-material);
460
+ --kui-toolbar-control-color: var(--kui-color-text);
461
+ --kui-toolbar-control-hover-background: color-mix(
462
+ in srgb,
463
+ var(--kui-color-text) 12%,
464
+ var(--kui-chrome-material)
465
+ );
466
+ }
467
+
468
+ /* A dark glass group keeps its loud translucent fill and on-loud colors; its
469
+ edge is a faint white rim in either scheme. */
470
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
471
+ [data-appearance="borderless"]
472
+ ) {
473
+ --kui-toolbar-control-border: rgb(255 255 255 / 0.18);
474
+ }
475
+
476
+ .kui-toolbar-control-group[data-material="glass"][data-selected-chrome="outline"] {
477
+ --kui-toolbar-control-selected-background: var(--kui-color-surface);
478
+ }
479
+
480
+ /* Wherever the chrome material turns opaque, the glass edge returns to the
481
+ group's normal border and a dark glass group to its opaque loud fill. The
482
+ default glass fill already follows the material tokens. */
483
+ @supports not (
484
+ (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
485
+ ) {
486
+ .kui-toolbar-control-group[data-material="glass"]:not(
487
+ [data-appearance="borderless"]
488
+ ) {
489
+ --kui-toolbar-control-border: var(--kui-color-neutral-border-normal);
490
+ }
491
+
492
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
493
+ [data-appearance="borderless"]
494
+ ) {
495
+ --kui-toolbar-control-background: var(--kui-color-neutral-fill-loud);
496
+ --kui-toolbar-control-border: var(
497
+ --kui-toolbar-control-dark-border,
498
+ #353536
499
+ );
500
+ }
501
+ }
502
+
503
+ @media (prefers-reduced-transparency: reduce),
504
+ (prefers-contrast: more),
505
+ (update: slow) {
506
+ .kui-toolbar-control-group[data-material="glass"]:not(
507
+ [data-appearance="borderless"]
508
+ ) {
509
+ --kui-toolbar-control-border: var(--kui-color-neutral-border-normal);
510
+ }
511
+
512
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
513
+ [data-appearance="borderless"]
514
+ ) {
515
+ --kui-toolbar-control-background: var(--kui-color-neutral-fill-loud);
516
+ --kui-toolbar-control-border: var(
517
+ --kui-toolbar-control-dark-border,
518
+ #353536
519
+ );
520
+ }
521
+ }
522
+
523
+ :root[data-kui-effects="static"]
524
+ .kui-toolbar-control-group[data-material="glass"]:not(
525
+ [data-appearance="borderless"]
526
+ ) {
527
+ --kui-toolbar-control-border: var(--kui-color-neutral-border-normal);
528
+ }
529
+
530
+ :root[data-kui-effects="static"]
531
+ .kui-toolbar-control-group[data-material="glass"][data-tone="dark"]:not(
532
+ [data-appearance="borderless"]
533
+ ) {
534
+ --kui-toolbar-control-background: var(--kui-color-neutral-fill-loud);
535
+ --kui-toolbar-control-border: var(--kui-toolbar-control-dark-border, #353536);
536
+ }
537
+
538
+ @media (forced-colors: active) {
539
+ .kui-toolbar-control-group[data-material="glass"]:not(
540
+ [data-appearance="borderless"]
541
+ ) {
542
+ -webkit-backdrop-filter: none;
543
+ backdrop-filter: none;
544
+ border-color: CanvasText;
545
+ color: CanvasText;
546
+ background-color: Canvas;
547
+ box-shadow: none;
548
+ }
549
+ }
550
+
423
551
  /* `selectedChrome="none"`: a pressed control paints like its unpressed
424
552
  neighbors, for an icon-state toggle whose glyph already shows the state.
425
553
  One button opts out alone with `data-selected-chrome="none"`. After the
@@ -61,6 +61,32 @@
61
61
  background: var(--kui-pane-chrome-background, var(--kui-color-surface));
62
62
  }
63
63
 
64
+ /* `background`: an app-chosen color paints the whole pane, header and footer
65
+ included, and replaces the appearance's fill, so a sunken pane's content
66
+ and auto chrome turn transparent over it. Forced colors paint the system
67
+ surface instead. */
68
+ .kui-pane[data-background] {
69
+ background: var(--_kui-pane-background);
70
+ }
71
+
72
+ .kui-pane[data-background][data-appearance="sunken"] > .kui-pane__content,
73
+ .kui-pane[data-background][data-appearance="sunken"][data-chrome-placement="auto"]
74
+ > :is(.kui-pane__header, .kui-pane__footer) {
75
+ background: transparent;
76
+ }
77
+
78
+ /* `tint`: the pane paints its re-derived surface, under a sunken pane's
79
+ lowered layer, unless an explicit `background` already paints it. */
80
+ .kui-pane[data-kui-tint]:not([data-background]) {
81
+ background: var(--kui-color-surface);
82
+ }
83
+
84
+ @media (forced-colors: active) {
85
+ .kui-pane[data-background] {
86
+ background: Canvas;
87
+ }
88
+ }
89
+
64
90
  /* Safe areas. The pane root never pads, so its background and separators paint
65
91
  through an unsafe area; its slots compensate on the edges the pane still
66
92
  touches. The inherited `--kui-edge-inset-*` context says how far each edge
@@ -158,6 +184,9 @@
158
184
  pane edge while keeping their own content aligned with ordinary items. */
159
185
  --kui-pane-content-inset-inline-start: var(--_kui-pane-safe-inline-start);
160
186
  --kui-pane-content-inset-inline-end: var(--_kui-pane-safe-inline-end);
187
+ /* The resolved block-start inset (safe area, deep inset, and the clearance
188
+ of overlay or clear chrome), so a first child can run up beneath it. */
189
+ --kui-pane-content-inset-block-start: var(--_kui-pane-content-block-start);
161
190
  --_kui-pane-deep-block-inset: 0px;
162
191
  --_kui-pane-clearance-block-start: 0px;
163
192
  --_kui-pane-clearance-block-end: 0px;
@@ -263,6 +292,97 @@
263
292
  }
264
293
  }
265
294
 
295
+ /* `headerPresentation="clear"`: an overlay header (the same measuring,
296
+ placement, and clearance as above) that is fully transparent, for chrome
297
+ laid over imagery. Legibility comes from glass control groups or a scrim
298
+ in the content, not from the header. With `scrollEdge="material"` a
299
+ pseudo-element carrying the chrome material fades in (opacity only, never
300
+ the filter) while `wireScrollDividers` reports content scrolled beneath
301
+ the header, and out at rest; the clear header draws no scroll divider. */
302
+ .kui-pane[data-header-material="clear"] > .kui-pane__header {
303
+ -webkit-backdrop-filter: none;
304
+ backdrop-filter: none;
305
+ background: transparent;
306
+ }
307
+
308
+ :where(.kui-pane[data-header-material="clear"][data-scroll-edge])
309
+ > .kui-pane__header {
310
+ position: relative;
311
+ isolation: isolate;
312
+ }
313
+
314
+ /* The scroll edge fades in as content scrolls beneath the bar: the wiring
315
+ publishes `--kui-scroll-edge-progress` (0 at rest, 1 once content has
316
+ scrolled `scrollEdgeDistance` past the edge) and only the layer's opacity
317
+ follows it; the filter never animates. Unwired, it stays clear. */
318
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
319
+ > .kui-pane__header::before {
320
+ position: absolute;
321
+ z-index: -1;
322
+ content: "";
323
+ inset: 0;
324
+ opacity: var(--kui-scroll-edge-progress, 0);
325
+ pointer-events: none;
326
+ }
327
+
328
+ .kui-pane[data-header-material="clear"][data-scroll-edge="material"]
329
+ > .kui-pane__header::before {
330
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
331
+ backdrop-filter: var(--kui-chrome-material-filter);
332
+ background: var(--kui-chrome-material);
333
+ }
334
+
335
+ .kui-pane[data-header-material="clear"][data-scroll-edge="surface"]
336
+ > .kui-pane__header::before {
337
+ background: var(--kui-color-surface);
338
+ }
339
+
340
+ /* Static effects and slow displays snap the edge on once content lies
341
+ beneath the bar instead of tracking the scroll. */
342
+ :root[data-kui-effects="static"]
343
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
344
+ > .kui-pane__header::before {
345
+ opacity: 0;
346
+ }
347
+
348
+ :root[data-kui-effects="static"]
349
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
350
+ > .kui-pane__header[data-scroll-divider*="b"]::before {
351
+ opacity: 1;
352
+ }
353
+
354
+ @media (update: slow) {
355
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
356
+ > .kui-pane__header::before {
357
+ opacity: 0;
358
+ }
359
+
360
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
361
+ > .kui-pane__header[data-scroll-divider*="b"]::before {
362
+ opacity: 1;
363
+ }
364
+ }
365
+
366
+ /* Clear chrome turns opaque where transparency would hurt, like the
367
+ material itself. */
368
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
369
+ .kui-pane[data-header-material="clear"] > .kui-pane__header {
370
+ background: var(--kui-chrome-material);
371
+ }
372
+ }
373
+
374
+ @media (forced-colors: active) {
375
+ .kui-pane[data-header-material="clear"] > .kui-pane__header {
376
+ background: Canvas;
377
+ border-block-end: 1px solid CanvasText;
378
+ }
379
+
380
+ .kui-pane[data-header-material="clear"][data-scroll-edge]
381
+ > .kui-pane__header::before {
382
+ content: none;
383
+ }
384
+ }
385
+
266
386
  /* Add one outer gutter without changing the inherited item-margin token.
267
387
  Nested Row, List, and Grid therefore keep their own default 8px insets. */
268
388
  .kui-pane[data-deep-inset="true"] > .kui-pane__content {
@@ -313,7 +433,7 @@
313
433
  box-shadow: inset 0 1px var(--_kui-pane-chrome-divider);
314
434
  }
315
435
 
316
- .kui-pane:not([data-chrome-dividers])
436
+ .kui-pane:not([data-chrome-dividers], [data-header-material="clear"])
317
437
  > :is(.kui-pane__header, .kui-pane__toolbar)[data-scroll-divider*="b"],
318
438
  .kui-pane:not([data-chrome-dividers])
319
439
  > .kui-pane__footer[data-scroll-divider*="t"],
@@ -6,7 +6,7 @@
6
6
  height: 100%;
7
7
  overflow: hidden;
8
8
  flex-direction: column;
9
- background: var(--kui-color-surface);
9
+ background: var(--_kui-nav-stack-background, var(--kui-color-surface));
10
10
  /* Safe areas: the stack's surfaces paint through unsafe areas; its chrome,
11
11
  bottom toolbar, and each view compensate for the edges the stack still
12
12
  touches (see pane.css for the `--kui-edge-inset-*` edge context). */
@@ -60,6 +60,25 @@
60
60
  opacity: 0;
61
61
  }
62
62
 
63
+ /* `background`: the stack's color paints the stack, its docked chrome and
64
+ bottom toolbar, and every view (a view's own `pane.background` paints over
65
+ it). Overlay chrome keeps the chrome material. A nested stack never
66
+ inherits the color. Forced colors paint the system surface. */
67
+ .kui-nav-stack:not([data-background]) {
68
+ --_kui-nav-stack-background: initial;
69
+ }
70
+
71
+ @media (forced-colors: active) {
72
+ .kui-nav-stack[data-background],
73
+ .kui-nav-stack[data-background]
74
+ > :is(.kui-nav-stack__chrome, .kui-nav-stack__bottom),
75
+ .kui-nav-stack[data-background]
76
+ > .kui-nav-stack__viewport
77
+ > .kui-nav-stack__view {
78
+ background: Canvas;
79
+ }
80
+ }
81
+
63
82
  /* The chrome hosts the stack's real top Toolbar. It hands that toolbar the
64
83
  edges the stack still touches through the edge context (never the bottom,
65
84
  which the viewport owns); the toolbar pads for the sides it claims with
@@ -68,7 +87,7 @@
68
87
  --kui-edge-inset-block-start: var(--_kui-nav-stack-safe-block-start);
69
88
  --kui-edge-inset-inline-start: var(--_kui-nav-stack-safe-inline-start);
70
89
  --kui-edge-inset-inline-end: var(--_kui-nav-stack-safe-inline-end);
71
- background: var(--kui-color-surface);
90
+ background: var(--_kui-nav-stack-background, var(--kui-color-surface));
72
91
  }
73
92
 
74
93
  .kui-nav-stack__viewport {
@@ -97,7 +116,7 @@
97
116
  .kui-nav-stack__view {
98
117
  position: absolute;
99
118
  overflow: auto;
100
- background: var(--kui-color-surface);
119
+ background: var(--_kui-nav-stack-background, var(--kui-color-surface));
101
120
  transition: transform var(--kui-nav-stack-transition-duration, 200ms) ease;
102
121
  inset: 0;
103
122
  }
@@ -179,7 +198,7 @@
179
198
  --kui-edge-inset-inline-end: var(--_kui-nav-stack-safe-inline-end);
180
199
  border-block-start: 1px solid
181
200
  var(--_kui-nav-stack-bottom-divider, transparent);
182
- background: var(--kui-color-surface);
201
+ background: var(--_kui-nav-stack-background, var(--kui-color-surface));
183
202
  }
184
203
 
185
204
  /* The live chrome is the containing block of its scroll divider; a
@@ -272,6 +291,182 @@
272
291
  }
273
292
  }
274
293
 
294
+ /* `clear` chrome and bottom toolbar: overlay bars (the same measuring,
295
+ placement, and edge insets as above) that are fully transparent, for
296
+ chrome over imagery. With `scrollEdge="material"` a pseudo-element
297
+ carrying the chrome material fades in (opacity only) while the active
298
+ view's content lies beneath the bar, as `wireScrollDividers` reports, and
299
+ out at rest. Clear bars draw no scroll divider. */
300
+ .kui-nav-stack[data-chrome-material="clear"] > .kui-nav-stack__chrome {
301
+ -webkit-backdrop-filter: none;
302
+ backdrop-filter: none;
303
+ background: transparent;
304
+ }
305
+
306
+ :where(.kui-nav-stack[data-chrome-material="clear"][data-scroll-edge])
307
+ > .kui-nav-stack__chrome:not(.kui-nav-stack__chrome-copy) {
308
+ position: relative;
309
+ isolation: isolate;
310
+ }
311
+
312
+ /* The scroll edge fades in as content scrolls beneath the bar: the wiring
313
+ publishes `--kui-scroll-edge-progress` (0 at rest, 1 once content lies
314
+ `scrollEdgeDistance` beyond the bar) and only the layer's opacity follows
315
+ it; the filter never animates. Unwired, it stays clear. */
316
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge]
317
+ > .kui-nav-stack__chrome::before {
318
+ position: absolute;
319
+ z-index: -1;
320
+ content: "";
321
+ inset: 0;
322
+ opacity: var(--kui-scroll-edge-progress, 0);
323
+ pointer-events: none;
324
+ }
325
+
326
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge="material"]
327
+ > .kui-nav-stack__chrome::before {
328
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
329
+ backdrop-filter: var(--kui-chrome-material-filter);
330
+ background: var(--kui-chrome-material);
331
+ }
332
+
333
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge="surface"]
334
+ > .kui-nav-stack__chrome::before {
335
+ background: var(--kui-color-surface);
336
+ }
337
+
338
+ /* Static effects and slow displays snap the edge on while content lies
339
+ beneath the bar instead of tracking the scroll. */
340
+ :root[data-kui-effects="static"]
341
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge]
342
+ > .kui-nav-stack__chrome::before {
343
+ opacity: 0;
344
+ }
345
+
346
+ :root[data-kui-effects="static"]
347
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge]
348
+ > .kui-nav-stack__chrome[data-scroll-divider*="b"]::before {
349
+ opacity: 1;
350
+ }
351
+
352
+ @media (update: slow) {
353
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge]
354
+ > .kui-nav-stack__chrome::before {
355
+ opacity: 0;
356
+ }
357
+
358
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge]
359
+ > .kui-nav-stack__chrome[data-scroll-divider*="b"]::before {
360
+ opacity: 1;
361
+ }
362
+ }
363
+
364
+ /* Clear chrome turns opaque where transparency would hurt, like the
365
+ material itself. */
366
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
367
+ .kui-nav-stack[data-chrome-material="clear"] > .kui-nav-stack__chrome {
368
+ background: var(--kui-chrome-material);
369
+ }
370
+ }
371
+
372
+ @media (forced-colors: active) {
373
+ .kui-nav-stack[data-chrome-material="clear"] > .kui-nav-stack__chrome {
374
+ background: Canvas;
375
+ border-block-end: 1px solid CanvasText;
376
+ }
377
+
378
+ .kui-nav-stack[data-chrome-material="clear"][data-scroll-edge]
379
+ > .kui-nav-stack__chrome::before {
380
+ content: none;
381
+ }
382
+ }
383
+
384
+ .kui-nav-stack[data-bottom-toolbar-material="clear"] > .kui-nav-stack__bottom {
385
+ -webkit-backdrop-filter: none;
386
+ backdrop-filter: none;
387
+ background: transparent;
388
+ }
389
+
390
+ :where(.kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge])
391
+ > .kui-nav-stack__bottom:not(.kui-nav-stack__chrome-copy) {
392
+ position: relative;
393
+ isolation: isolate;
394
+ }
395
+
396
+ /* The scroll edge fades in as content scrolls beneath the bar: the wiring
397
+ publishes `--kui-scroll-edge-progress` (0 at rest, 1 once content lies
398
+ `scrollEdgeDistance` beyond the bar) and only the layer's opacity follows
399
+ it; the filter never animates. Unwired, it stays clear. */
400
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge]
401
+ > .kui-nav-stack__bottom::before {
402
+ position: absolute;
403
+ z-index: -1;
404
+ content: "";
405
+ inset: 0;
406
+ opacity: var(--kui-scroll-edge-progress, 0);
407
+ pointer-events: none;
408
+ }
409
+
410
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge="material"]
411
+ > .kui-nav-stack__bottom::before {
412
+ -webkit-backdrop-filter: var(--kui-chrome-material-filter);
413
+ backdrop-filter: var(--kui-chrome-material-filter);
414
+ background: var(--kui-chrome-material);
415
+ }
416
+
417
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge="surface"]
418
+ > .kui-nav-stack__bottom::before {
419
+ background: var(--kui-color-surface);
420
+ }
421
+
422
+ /* Static effects and slow displays snap the edge on while content lies
423
+ beneath the bar instead of tracking the scroll. */
424
+ :root[data-kui-effects="static"]
425
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge]
426
+ > .kui-nav-stack__bottom::before {
427
+ opacity: 0;
428
+ }
429
+
430
+ :root[data-kui-effects="static"]
431
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge]
432
+ > .kui-nav-stack__bottom[data-scroll-divider*="t"]::before {
433
+ opacity: 1;
434
+ }
435
+
436
+ @media (update: slow) {
437
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge]
438
+ > .kui-nav-stack__bottom::before {
439
+ opacity: 0;
440
+ }
441
+
442
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge]
443
+ > .kui-nav-stack__bottom[data-scroll-divider*="t"]::before {
444
+ opacity: 1;
445
+ }
446
+ }
447
+
448
+ /* Clear chrome turns opaque where transparency would hurt, like the
449
+ material itself. */
450
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
451
+ .kui-nav-stack[data-bottom-toolbar-material="clear"]
452
+ > .kui-nav-stack__bottom {
453
+ background: var(--kui-chrome-material);
454
+ }
455
+ }
456
+
457
+ @media (forced-colors: active) {
458
+ .kui-nav-stack[data-bottom-toolbar-material="clear"]
459
+ > .kui-nav-stack__bottom {
460
+ background: Canvas;
461
+ border-block-start-color: CanvasText;
462
+ }
463
+
464
+ .kui-nav-stack[data-bottom-toolbar-material="clear"][data-scroll-edge]
465
+ > .kui-nav-stack__bottom::before {
466
+ content: none;
467
+ }
468
+ }
469
+
275
470
  /* Chrome dividers: with `chromeDividers="scroll"` (the default, no
276
471
  attribute) the top chrome draws a line along its bottom edge while the
277
472
  active view's content is scrolled beneath it, and the bottom toolbar
@@ -290,7 +485,10 @@
290
485
  > [data-component="pane"][data-appearance="sunken"]
291
486
  )
292
487
  > .kui-nav-stack__chrome::after,
293
- .kui-nav-stack[data-separator="auto"]:not([data-chrome-dividers])
488
+ .kui-nav-stack[data-separator="auto"]:not(
489
+ [data-chrome-dividers],
490
+ [data-chrome-material="clear"]
491
+ )
294
492
  > .kui-nav-stack__chrome[data-scroll-divider*="b"]::after,
295
493
  .kui-nav-stack[data-separator="auto"][data-chrome-dividers="always"]
296
494
  > .kui-nav-stack__chrome::after {
@@ -309,7 +507,10 @@
309
507
  > [data-component="pane"][data-appearance="sunken"]
310
508
  )
311
509
  > .kui-nav-stack__bottom,
312
- .kui-nav-stack[data-separator="auto"]:not([data-chrome-dividers])
510
+ .kui-nav-stack[data-separator="auto"]:not(
511
+ [data-chrome-dividers],
512
+ [data-bottom-toolbar-material="clear"]
513
+ )
313
514
  > .kui-nav-stack__bottom[data-scroll-divider*="t"],
314
515
  .kui-nav-stack[data-separator="auto"][data-chrome-dividers="always"]
315
516
  > .kui-nav-stack__bottom {