@jungherz-de/glasskit 1.31.0 → 1.32.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/glasskit.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* ============================================================
2
2
  GLASSKIT COMPONENT LIBRARY
3
- Jungherz GmbH – v1.31.0
3
+ Jungherz GmbH – v1.32.0
4
4
 
5
5
  Features:
6
6
  - Scoped Reset (box-sizing fix)
@@ -799,12 +799,15 @@ a.glass-btn { display: inline-flex; text-decoration: none; }
799
799
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
800
800
  }
801
801
 
802
- /* Primary */
802
+ /* Primary. --gl-btn-primary-bg (since 1.32.0) replaces the whole
803
+ background, as the toggle and the radio have theirs: a product that keeps
804
+ white text starts the gradient darker without touching the brand colour,
805
+ which also draws focus rings, borders and the accent as text. */
803
806
  .glass-btn--primary {
804
- background: linear-gradient(135deg,
807
+ background: var(--gl-btn-primary-bg, linear-gradient(135deg,
805
808
  var(--gl-color-primary) 0%, var(--gl-color-primary-mid) 50%,
806
809
  var(--gl-color-primary-dark) 100%
807
- );
810
+ ));
808
811
  color: var(--gl-color-on-primary, #ffffff);
809
812
  border: 1px solid var(--gl-border-warm);
810
813
  box-shadow: var(--gl-shadow-btn-primary), var(--gl-inset-strong);
@@ -1336,7 +1339,9 @@ a.glass-btn { display: inline-flex; text-decoration: none; }
1336
1339
  .glass-radio__dot {
1337
1340
  width: var(--gl-radio-dot-size, 10px); height: var(--gl-radio-dot-size, 10px);
1338
1341
  border-radius: var(--gl-radius-pill);
1339
- background: var(--gl-radio-dot-color, white);
1342
+ /* Unset, the ink on the primary (since 1.32.0), so the dot turns dark
1343
+ with the checkbox tick where a project switches the ink. */
1344
+ background: var(--gl-radio-dot-color, var(--gl-color-on-primary, white));
1340
1345
  opacity: 0; transform: scale(0);
1341
1346
  transition: var(--gl-transition-fast);
1342
1347
  }
@@ -1984,7 +1989,16 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
1984
1989
  on the item below it. Without the script a row keeps its usual look:
1985
1990
  the tab bar its spotlight, the segmented row its raised option. GlassKit
1986
1991
  Elements' <glk-tab-bar droplet> and <glk-segmented droplet> do all of
1987
- this; for plain markup the docs have the script. */
1992
+ this; for plain markup the docs have the script.
1993
+
1994
+ The motion is tokens (since 1.32.0), unset today's values, read by the
1995
+ stylesheet and the script alike: --gl-droplet-duration and -easing for
1996
+ the leading edge, -trail-duration, -trail-delay and -trail-easing for the
1997
+ trailing one, -lift (0.86; 1 squashes nothing), -lift-duration (280ms),
1998
+ -settle-duration and -settle-easing for the spring back, -lens-duration
1999
+ and -lens-easing for the item below the drop. In a column — a vertical
2000
+ tab bar, a sidebar — the script sets data-droplet-axis="y" and the
2001
+ directions "down" and "up", and the drop runs along y. */
1988
2002
  .glass-tab-bar--floating.glass-tab-bar--droplet,
1989
2003
  .glass-segmented.glass-segmented--droplet { position: relative; }
1990
2004
  .glass-tab-bar.glass-tab-bar--droplet[data-droplet],
@@ -2012,9 +2026,11 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2012
2026
  -webkit-backdrop-filter: blur(var(--gl-blur-light)) saturate(1.6);
2013
2027
  pointer-events: none;
2014
2028
  transition:
2015
- transform 0.55s cubic-bezier(0.34, 1.8, 0.5, 1),
2029
+ transform var(--gl-droplet-settle-duration, 0.55s) var(--gl-droplet-settle-easing, cubic-bezier(0.34, 1.8, 0.5, 1)),
2016
2030
  box-shadow 0.3s ease;
2017
2031
  }
2032
+ /* A column hands sideways swipes to the page and keeps the vertical drag. */
2033
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-axis="y"] { touch-action: pan-x; }
2018
2034
  /* The tab bar's drop is a capsule, in the active item's surface where a
2019
2035
  project set one (--gl-tab-bar-active-bg, -shadow). */
2020
2036
  .glass-tab-bar.glass-tab-bar--droplet[data-droplet]::before {
@@ -2042,18 +2058,35 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2042
2058
  after it and overshoots, so the drop stretches and draws together. */
2043
2059
  :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-dir="right"]::before {
2044
2060
  transition:
2045
- right 0.3s cubic-bezier(0.3, 1.2, 0.5, 1),
2046
- left 0.5s cubic-bezier(0.5, 1.5, 0.45, 0.92) 0.04s,
2047
- top 0.3s ease, bottom 0.3s ease,
2048
- transform 0.55s cubic-bezier(0.34, 1.8, 0.5, 1),
2061
+ right var(--gl-droplet-duration, 0.3s) var(--gl-droplet-easing, cubic-bezier(0.3, 1.2, 0.5, 1)),
2062
+ left var(--gl-droplet-trail-duration, 0.5s) var(--gl-droplet-trail-easing, cubic-bezier(0.5, 1.5, 0.45, 0.92)) var(--gl-droplet-trail-delay, 0.04s),
2063
+ top var(--gl-droplet-duration, 0.3s) ease, bottom var(--gl-droplet-duration, 0.3s) ease,
2064
+ transform var(--gl-droplet-settle-duration, 0.55s) var(--gl-droplet-settle-easing, cubic-bezier(0.34, 1.8, 0.5, 1)),
2049
2065
  box-shadow 0.3s ease;
2050
2066
  }
2051
2067
  :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-dir="left"]::before {
2052
2068
  transition:
2053
- left 0.3s cubic-bezier(0.3, 1.2, 0.5, 1),
2054
- right 0.5s cubic-bezier(0.5, 1.5, 0.45, 0.92) 0.04s,
2055
- top 0.3s ease, bottom 0.3s ease,
2056
- transform 0.55s cubic-bezier(0.34, 1.8, 0.5, 1),
2069
+ left var(--gl-droplet-duration, 0.3s) var(--gl-droplet-easing, cubic-bezier(0.3, 1.2, 0.5, 1)),
2070
+ right var(--gl-droplet-trail-duration, 0.5s) var(--gl-droplet-trail-easing, cubic-bezier(0.5, 1.5, 0.45, 0.92)) var(--gl-droplet-trail-delay, 0.04s),
2071
+ top var(--gl-droplet-duration, 0.3s) ease, bottom var(--gl-droplet-duration, 0.3s) ease,
2072
+ transform var(--gl-droplet-settle-duration, 0.55s) var(--gl-droplet-settle-easing, cubic-bezier(0.34, 1.8, 0.5, 1)),
2073
+ box-shadow 0.3s ease;
2074
+ }
2075
+ /* In a column the same journey runs along y (since 1.32.0). */
2076
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-dir="down"]::before {
2077
+ transition:
2078
+ bottom var(--gl-droplet-duration, 0.3s) var(--gl-droplet-easing, cubic-bezier(0.3, 1.2, 0.5, 1)),
2079
+ top var(--gl-droplet-trail-duration, 0.5s) var(--gl-droplet-trail-easing, cubic-bezier(0.5, 1.5, 0.45, 0.92)) var(--gl-droplet-trail-delay, 0.04s),
2080
+ left var(--gl-droplet-duration, 0.3s) ease, right var(--gl-droplet-duration, 0.3s) ease,
2081
+ transform var(--gl-droplet-settle-duration, 0.55s) var(--gl-droplet-settle-easing, cubic-bezier(0.34, 1.8, 0.5, 1)),
2082
+ box-shadow 0.3s ease;
2083
+ }
2084
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-dir="up"]::before {
2085
+ transition:
2086
+ top var(--gl-droplet-duration, 0.3s) var(--gl-droplet-easing, cubic-bezier(0.3, 1.2, 0.5, 1)),
2087
+ bottom var(--gl-droplet-trail-duration, 0.5s) var(--gl-droplet-trail-easing, cubic-bezier(0.5, 1.5, 0.45, 0.92)) var(--gl-droplet-trail-delay, 0.04s),
2088
+ left var(--gl-droplet-duration, 0.3s) ease, right var(--gl-droplet-duration, 0.3s) ease,
2089
+ transform var(--gl-droplet-settle-duration, 0.55s) var(--gl-droplet-settle-easing, cubic-bezier(0.34, 1.8, 0.5, 1)),
2057
2090
  box-shadow 0.3s ease;
2058
2091
  }
2059
2092
  /* While it travels the drop runs flatter and lifts off the row; it comes
@@ -2068,7 +2101,7 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2068
2101
  var(--gl-inset-strong);
2069
2102
  }
2070
2103
  :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-lift]::before {
2071
- transform: scale(1, 0.86);
2104
+ transform: scale(1, var(--gl-droplet-lift, 0.86));
2072
2105
  }
2073
2106
  :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-drag]::before {
2074
2107
  transform: scale(1.1, 1.22);
@@ -2076,10 +2109,22 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2076
2109
  left 0.1s ease-out, right 0.1s ease-out,
2077
2110
  transform 0.25s ease-out, box-shadow 0.25s ease;
2078
2111
  }
2112
+ /* Along y the drop runs narrower instead, and grows less across its long
2113
+ side when held: a sidebar's item is wide. */
2114
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-axis="y"][data-droplet-lift]::before {
2115
+ transform: scale(var(--gl-droplet-lift, 0.86), 1);
2116
+ }
2117
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-axis="y"][data-droplet-drag]::before {
2118
+ transform: scale(1.04, 1.18);
2119
+ transition:
2120
+ top 0.1s ease-out, bottom 0.1s ease-out,
2121
+ transform 0.25s ease-out, box-shadow 0.25s ease;
2122
+ }
2079
2123
 
2080
2124
  /* The drop is the chosen item's surface, so the item draws none; on the
2081
2125
  tab bar neither spotlight, dot nor glow. */
2082
2126
  .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__item.is-active { background: none; box-shadow: none; }
2127
+ .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__item.is-active:focus-visible { box-shadow: var(--gl-shadow-focus); }
2083
2128
  .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__item.is-active::before { display: none; }
2084
2129
  .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__item.is-active .glass-tab-bar__icon::after { display: none; }
2085
2130
  .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__item.is-active .glass-tab-bar__icon > :where(:not(.glass-tab-bar__badge)),
@@ -2093,9 +2138,9 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2093
2138
  a lens. While a finger drags the drop, the item below it takes the
2094
2139
  chosen colour and the chosen item lets it go. The tab icon keeps the
2095
2140
  fade of its colour next to the lens. */
2096
- .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__icon { transition: var(--gl-transition), transform 0.4s cubic-bezier(0.34, 1.6, 0.5, 1); }
2141
+ .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__icon { transition: var(--gl-transition), transform var(--gl-droplet-lens-duration, 0.4s) var(--gl-droplet-lens-easing, cubic-bezier(0.34, 1.6, 0.5, 1)); }
2097
2142
  .glass-segmented--droplet[data-droplet] .glass-segmented__dot,
2098
- .glass-segmented--droplet[data-droplet] .glass-segmented__icon { transition: transform 0.4s cubic-bezier(0.34, 1.6, 0.5, 1); }
2143
+ .glass-segmented--droplet[data-droplet] .glass-segmented__icon { transition: transform var(--gl-droplet-lens-duration, 0.4s) var(--gl-droplet-lens-easing, cubic-bezier(0.34, 1.6, 0.5, 1)); }
2099
2144
  .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__item.is-under-droplet .glass-tab-bar__icon { transform: scale(1.15); }
2100
2145
  .glass-segmented--droplet[data-droplet] .glass-segmented__item.is-under-droplet .glass-segmented__dot { transform: scale(1.3); }
2101
2146
  .glass-segmented--droplet[data-droplet] .glass-segmented__item.is-under-droplet .glass-segmented__icon { transform: scale(1.15); }
@@ -2107,13 +2152,184 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2107
2152
  @media (prefers-reduced-motion: reduce) {
2108
2153
  :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet]::before,
2109
2154
  :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-dir]::before,
2110
- :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-drag]::before { transition: none; }
2155
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-drag]::before,
2156
+ :is(.glass-tab-bar.glass-tab-bar--droplet, .glass-segmented.glass-segmented--droplet)[data-droplet][data-droplet-axis="y"][data-droplet-drag]::before { transition: none; }
2111
2157
  .glass-tab-bar--droplet[data-droplet] .glass-tab-bar__icon { transition: var(--gl-transition), transform 0s; }
2112
2158
  .glass-segmented--droplet[data-droplet] .glass-segmented__dot,
2113
2159
  .glass-segmented--droplet[data-droplet] .glass-segmented__icon { transition: none; }
2114
2160
  }
2115
2161
 
2116
2162
 
2163
+ /* ============================================================
2164
+ 18d. TAB BAR – SIDEBAR, RAIL, TOP BAR, SIDE DOCK
2165
+ ============================================================ */
2166
+ /* One navigation in more places than the bottom edge (since 1.32.0). The
2167
+ items stay the same — icon, label, badge, the active state, the drop —
2168
+ and only the bar's layout changes:
2169
+
2170
+ --vertical a column for a sidebar: placed by the page's layout (a grid
2171
+ column) and as high as its container, one item per row with
2172
+ the icon before the label at body size and the badge at the
2173
+ row's end. With --start or --end it is fixed to that edge of
2174
+ the viewport instead, full height and clear of the edge's
2175
+ safe area — the side a phone's thumb reaches when the system
2176
+ moves its own controls there. Start and end follow the
2177
+ writing direction.
2178
+ --rail with --vertical: icons alone, --gl-tab-bar-rail-width wide
2179
+ (72px). The label stays the item's name for assistive
2180
+ technology, hidden from view — show it in a tooltip — and the
2181
+ badge goes back to the icon's corner.
2182
+ --top a horizontal bar at the top: in the flow (put it in a sticky
2183
+ header), its border at the bottom, no room for the home
2184
+ indicator, and from 640px the icon beside the label.
2185
+
2186
+ The floating bar stands upright in a dock with --vertical and --start or
2187
+ --end: its items stacked as on the bottom edge, the accessory below them,
2188
+ above with --accessory-left. In a column the drop runs along y; the
2189
+ droplet script reads the flex direction. */
2190
+
2191
+ /* Every bar: a ring for the keyboard, which the items had none of. An
2192
+ item may be a link, to open a view in a new tab. */
2193
+ .glass-tab-bar__item:focus-visible { box-shadow: var(--gl-shadow-focus); }
2194
+ a.glass-tab-bar__item { text-decoration: none; color: inherit; }
2195
+
2196
+ /* ---- Sidebar ---- */
2197
+ .glass-tab-bar.glass-tab-bar--vertical {
2198
+ position: relative; inset: auto;
2199
+ height: 100%; width: auto;
2200
+ flex-direction: column; justify-content: flex-start; align-items: stretch;
2201
+ gap: var(--gl-space-2xs);
2202
+ padding: var(--gl-space-sm);
2203
+ border-top: none;
2204
+ border-inline-end: 1px solid var(--gl-border-subtle);
2205
+ overflow-y: auto; overscroll-behavior: contain;
2206
+ box-shadow: var(--gl-inset-subtle);
2207
+ }
2208
+ .glass-tab-bar--vertical .glass-tab-bar__item {
2209
+ position: relative;
2210
+ flex-direction: row; justify-content: flex-start;
2211
+ gap: var(--gl-space-sm);
2212
+ width: 100%; min-width: 0; min-height: 44px;
2213
+ padding: var(--gl-space-xs) var(--gl-space-md);
2214
+ text-align: start; text-decoration: none;
2215
+ }
2216
+ .glass-tab-bar--vertical .glass-tab-bar__label {
2217
+ flex: 1 1 auto; min-width: 0;
2218
+ font-size: var(--gl-font-size-base);
2219
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2220
+ }
2221
+ .glass-tab-bar--vertical .glass-tab-bar__item:not(.is-active):hover { background: var(--gl-surface-1); }
2222
+ /* The active row stands on a surface of its own, unset --gl-surface-3,
2223
+ where the bottom bar has its dot. */
2224
+ .glass-tab-bar--vertical .glass-tab-bar__item.is-active { background: var(--gl-tab-bar-active-bg, var(--gl-surface-3)); }
2225
+ .glass-tab-bar--vertical .glass-tab-bar__item.is-active .glass-tab-bar__icon::after { display: none; }
2226
+ /* The badge at the row's end: positioned on the item, not the icon. */
2227
+ .glass-tab-bar--vertical:not(.glass-tab-bar--rail, .glass-tab-bar--floating) .glass-tab-bar__icon { position: static; }
2228
+ .glass-tab-bar--vertical:not(.glass-tab-bar--rail, .glass-tab-bar--floating) .glass-tab-bar__badge {
2229
+ top: 50%; inset-inline-end: var(--gl-space-md);
2230
+ transform: translateY(-50%);
2231
+ }
2232
+ /* A long label ends before the badge. */
2233
+ .glass-tab-bar--vertical:not(.glass-tab-bar--rail, .glass-tab-bar--floating) .glass-tab-bar__item:has(.glass-tab-bar__badge:not([hidden])) .glass-tab-bar__label {
2234
+ padding-inline-end: var(--gl-tab-bar-badge-room, 32px);
2235
+ }
2236
+
2237
+ /* Fixed to an edge, full height, clear of that edge's safe area. */
2238
+ .glass-tab-bar.glass-tab-bar--vertical:is(.glass-tab-bar--start, .glass-tab-bar--end) {
2239
+ position: fixed; top: 0; bottom: 0; height: auto;
2240
+ padding-top: max(var(--gl-space-sm), env(safe-area-inset-top, 0px));
2241
+ padding-bottom: max(var(--gl-space-sm), env(safe-area-inset-bottom, 0px));
2242
+ z-index: var(--gl-tab-bar-z-index, 900);
2243
+ }
2244
+ .glass-tab-bar.glass-tab-bar--vertical.glass-tab-bar--start { inset-inline-start: 0; inset-inline-end: auto; }
2245
+ .glass-tab-bar.glass-tab-bar--vertical.glass-tab-bar--end {
2246
+ inset-inline-end: 0; inset-inline-start: auto;
2247
+ border-inline-end: none; border-inline-start: 1px solid var(--gl-border-subtle);
2248
+ }
2249
+ .glass-tab-bar.glass-tab-bar--vertical:is(.glass-tab-bar--start:dir(ltr), .glass-tab-bar--end:dir(rtl)) {
2250
+ padding-left: max(var(--gl-space-sm), env(safe-area-inset-left, 0px));
2251
+ }
2252
+ .glass-tab-bar.glass-tab-bar--vertical:is(.glass-tab-bar--end:dir(ltr), .glass-tab-bar--start:dir(rtl)) {
2253
+ padding-right: max(var(--gl-space-sm), env(safe-area-inset-right, 0px));
2254
+ }
2255
+
2256
+ /* ---- Rail ---- */
2257
+ .glass-tab-bar.glass-tab-bar--vertical.glass-tab-bar--rail {
2258
+ width: var(--gl-tab-bar-rail-width, 72px);
2259
+ align-items: center;
2260
+ padding-inline: var(--gl-space-xs);
2261
+ }
2262
+ .glass-tab-bar--rail .glass-tab-bar__item {
2263
+ justify-content: center;
2264
+ width: auto; min-width: 48px; min-height: 48px;
2265
+ padding: var(--gl-space-sm);
2266
+ }
2267
+ .glass-tab-bar--rail .glass-tab-bar__label {
2268
+ position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
2269
+ overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
2270
+ }
2271
+
2272
+ /* ---- Floating, upright ---- */
2273
+ .glass-tab-bar.glass-tab-bar--floating.glass-tab-bar--vertical {
2274
+ flex-direction: column; align-items: center;
2275
+ width: auto; height: auto; max-height: 100%;
2276
+ padding: 10px 6px;
2277
+ border: 1px solid var(--gl-border-subtle);
2278
+ border-radius: var(--gl-tab-bar-floating-radius);
2279
+ }
2280
+ .glass-tab-bar--floating.glass-tab-bar--vertical .glass-tab-bar__item {
2281
+ flex-direction: column; gap: var(--gl-space-2xs);
2282
+ width: auto; min-height: 0;
2283
+ padding: var(--gl-space-2xs) var(--gl-space-sm);
2284
+ }
2285
+ .glass-tab-bar--floating.glass-tab-bar--vertical .glass-tab-bar__label {
2286
+ flex: none; font-size: var(--gl-tab-bar-label-size, 10px);
2287
+ }
2288
+ .glass-tab-bar--floating.glass-tab-bar--vertical .glass-tab-bar__item.is-active { background: var(--gl-tab-bar-active-bg, none); }
2289
+
2290
+ .glass-tab-bar-dock.glass-tab-bar-dock--vertical {
2291
+ flex-direction: column;
2292
+ top: 50%; bottom: auto;
2293
+ transform: translateY(-50%);
2294
+ max-width: none; max-height: calc(100dvh - 2 * var(--gl-space-md));
2295
+ /* The edge by the writing direction, for browsers without :dir(); the
2296
+ rules below add the safe area of the physical side. */
2297
+ left: auto; right: auto;
2298
+ inset-inline-end: calc(var(--gl-tab-bar-floating-bottom) + env(safe-area-inset-right, 0px));
2299
+ }
2300
+ .glass-tab-bar-dock.glass-tab-bar-dock--vertical.glass-tab-bar-dock--start {
2301
+ inset-inline-end: auto;
2302
+ inset-inline-start: calc(var(--gl-tab-bar-floating-bottom) + env(safe-area-inset-left, 0px));
2303
+ }
2304
+ .glass-tab-bar-dock.glass-tab-bar-dock--vertical.glass-tab-bar-dock--accessory-left { flex-direction: column-reverse; }
2305
+ /* Unset or --end, at the end edge; --start at the other. */
2306
+ .glass-tab-bar-dock.glass-tab-bar-dock--vertical:is(:not(.glass-tab-bar-dock--start):dir(ltr), .glass-tab-bar-dock--start:dir(rtl)) {
2307
+ left: auto; right: calc(var(--gl-tab-bar-floating-bottom) + env(safe-area-inset-right, 0px));
2308
+ }
2309
+ .glass-tab-bar-dock.glass-tab-bar-dock--vertical:is(.glass-tab-bar-dock--start:dir(ltr), :not(.glass-tab-bar-dock--start):dir(rtl)) {
2310
+ right: auto; left: calc(var(--gl-tab-bar-floating-bottom) + env(safe-area-inset-left, 0px));
2311
+ }
2312
+
2313
+ /* ---- Top bar ---- */
2314
+ .glass-tab-bar.glass-tab-bar--top {
2315
+ position: relative; inset: auto;
2316
+ height: auto;
2317
+ align-items: center;
2318
+ padding-bottom: var(--gl-space-xs);
2319
+ border-top: none; border-bottom: 1px solid var(--gl-border-subtle);
2320
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08), var(--gl-inset-subtle);
2321
+ }
2322
+ @media (min-width: 640px) {
2323
+ .glass-tab-bar--top .glass-tab-bar__item {
2324
+ flex-direction: row; gap: var(--gl-space-xs);
2325
+ padding-inline: var(--gl-space-md);
2326
+ }
2327
+ .glass-tab-bar--top .glass-tab-bar__label { font-size: var(--gl-font-size-sm); }
2328
+ /* Clear of the badge at the icon's corner. */
2329
+ .glass-tab-bar--top .glass-tab-bar__item:has(.glass-tab-bar__badge:not([hidden])) .glass-tab-bar__label { margin-inline-start: 8px; }
2330
+ }
2331
+
2332
+
2117
2333
  /* ============================================================
2118
2334
  19. ACCORDION
2119
2335
  ============================================================ */
@@ -2440,6 +2656,171 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2440
2656
  .glass-popover--end:dir(rtl) { transform-origin: top left; }
2441
2657
  .glass-popover--end.is-open { transform: translateY(0) scale(1); }
2442
2658
 
2659
+ /* In the top layer (since 1.32.0): with the popover attribute the panel is
2660
+ shown with showPopover() and placed by script, fixed at the coordinates
2661
+ it writes. It is no longer cut off by a box with overflow: hidden around
2662
+ it, does not leave the screen at its edges, and closed it takes no room.
2663
+ GlassKit Elements' <glk-popover> does this. */
2664
+ /* It fades out with transitions that keep it in the top layer until they
2665
+ end (transition-behavior: allow-discrete), and in with an animation —
2666
+ written in longhands and keyframes, which the minifier keeps. */
2667
+ .glass-popover[popover] {
2668
+ position: fixed; inset: auto; margin: 0;
2669
+ transform: translateY(-4px) scale(0.96);
2670
+ transition-property: opacity, transform, overlay, display;
2671
+ transition-duration: .18s;
2672
+ transition-timing-function: ease;
2673
+ transition-behavior: allow-discrete;
2674
+ }
2675
+ .glass-popover[popover]:popover-open {
2676
+ opacity: 1; visibility: visible; pointer-events: auto;
2677
+ transform: none;
2678
+ animation: glass-popover-in .22s cubic-bezier(.2,.8,.2,1);
2679
+ }
2680
+ @keyframes glass-popover-in { from { opacity: 0; transform: translateY(-4px) scale(0.96); } }
2681
+ .glass-popover[popover][data-side="top"] { transform-origin: bottom center; }
2682
+
2683
+
2684
+ /* ============================================================
2685
+ 24b. TOOLTIP
2686
+ ============================================================ */
2687
+ /* A short name or hint for a control on hover and focus (since 1.32.0):
2688
+ an icon-only button, the items of a rail, a row action. Small text on an
2689
+ opaque surface of the theme, readable over any glass, at most
2690
+ --gl-tooltip-max-width (240px) wide; --gl-tooltip-bg and
2691
+ --gl-tooltip-color recolour it. role="tooltip"; the control points to it
2692
+ with aria-describedby, or aria-labelledby where it is the control's only
2693
+ name. Opened in the top layer (popover="hint") and placed by script next
2694
+ to its control, flipped and shifted to stay in the viewport; the pointer
2695
+ can move onto it without it closing (WCAG 1.4.13). GlassKit Elements'
2696
+ <glk-tooltip> does all of this. */
2697
+ .glass-tooltip {
2698
+ max-width: var(--gl-tooltip-max-width, 240px);
2699
+ padding: 6px 12px;
2700
+ border: 1px solid var(--gl-border-medium);
2701
+ border-radius: var(--gl-radius-sm);
2702
+ /* The popover's glass, tinted far enough to keep the text readable over
2703
+ whatever lies below it. */
2704
+ background: var(--gl-tooltip-bg, color-mix(in srgb, var(--gl-color-bg-mid) 80%, transparent));
2705
+ backdrop-filter: blur(var(--gl-blur));
2706
+ -webkit-backdrop-filter: blur(var(--gl-blur));
2707
+ box-shadow: var(--gl-shadow-card), var(--gl-inset-strong);
2708
+ color: var(--gl-tooltip-color, var(--gl-color-text));
2709
+ font-family: var(--gl-font-family);
2710
+ font-size: var(--gl-font-size-xs); font-weight: var(--gl-font-weight-medium);
2711
+ line-height: 1.4;
2712
+ overflow-wrap: anywhere;
2713
+ }
2714
+ .glass-tooltip[popover] {
2715
+ inset: auto; margin: 0; overflow: visible;
2716
+ opacity: 0;
2717
+ transition-property: opacity, overlay, display;
2718
+ transition-duration: .12s;
2719
+ transition-behavior: allow-discrete;
2720
+ }
2721
+ .glass-tooltip[popover]:popover-open { opacity: 1; animation: glass-fade-in .12s ease; }
2722
+ @keyframes glass-fade-in { from { opacity: 0; } }
2723
+
2724
+
2725
+ /* ============================================================
2726
+ 24c. MENU
2727
+ ============================================================ */
2728
+ /* A list of actions behind a button (since 1.32.0): the overflow menu of a
2729
+ header, the actions of a table row. On the popover's surface, in the top
2730
+ layer and placed like the tooltip. Items are buttons with
2731
+ role="menuitem" — or menuitemcheckbox and menuitemradio with aria-checked
2732
+ (or .is-checked), which shows the check mark — with an icon before the
2733
+ label and a hint at the end, such as a shortcut. --danger takes the error
2734
+ colour for something destructive, aria-disabled="true" leaves an item
2735
+ announced but inert; __separator and __group-label part the list. The
2736
+ keyboard — arrows, Home and End, typing to jump, Escape back to the
2737
+ button — is GlassKit Elements' <glk-menu>. */
2738
+ .glass-menu {
2739
+ min-width: 180px; max-width: min(320px, calc(100vw - 16px));
2740
+ max-height: min(70vh, 480px);
2741
+ overflow-y: auto; overscroll-behavior: contain;
2742
+ padding: 6px;
2743
+ background: var(--gl-surface-3);
2744
+ backdrop-filter: blur(var(--gl-blur));
2745
+ -webkit-backdrop-filter: blur(var(--gl-blur));
2746
+ border: 1px solid var(--gl-border-medium);
2747
+ border-radius: var(--gl-radius-card);
2748
+ box-shadow: var(--gl-shadow-card), var(--gl-inset-strong);
2749
+ color: var(--gl-color-text);
2750
+ font-family: var(--gl-font-family);
2751
+ }
2752
+ .glass-menu[popover] {
2753
+ inset: auto; margin: 0;
2754
+ opacity: 0; transform: scale(0.97);
2755
+ transition-property: opacity, transform, overlay, display;
2756
+ transition-duration: .14s;
2757
+ transition-behavior: allow-discrete;
2758
+ }
2759
+ .glass-menu[popover]:popover-open { opacity: 1; transform: none; animation: glass-menu-in .14s ease; }
2760
+ @keyframes glass-menu-in { from { opacity: 0; transform: scale(0.97); } }
2761
+ .glass-menu__item {
2762
+ display: flex; align-items: center; gap: 10px;
2763
+ width: 100%; min-height: 40px;
2764
+ padding: 8px 12px;
2765
+ /* Concentric with the panel's corners. */
2766
+ border: 0; border-radius: calc(var(--gl-radius-card) - 6px);
2767
+ background: none;
2768
+ color: var(--gl-color-text);
2769
+ font: inherit; font-size: var(--gl-font-size-sm); text-align: start;
2770
+ cursor: pointer; outline: none; user-select: none;
2771
+ -webkit-tap-highlight-color: transparent;
2772
+ }
2773
+ .glass-menu__item:hover,
2774
+ .glass-menu__item:focus,
2775
+ .glass-menu__item.is-focused { background: var(--gl-surface-2); }
2776
+ .glass-menu__item:focus-visible,
2777
+ .glass-menu__item.is-focus-visible { box-shadow: inset 0 0 0 2px var(--gl-border-focus); }
2778
+ .glass-menu__item--danger { color: var(--gl-color-error-on-surface, var(--gl-color-error)); }
2779
+ .glass-menu__item[aria-disabled="true"],
2780
+ .glass-menu__item.is-disabled { opacity: 0.45; cursor: not-allowed; }
2781
+ .glass-menu__icon {
2782
+ flex: none; width: 18px; height: 18px;
2783
+ display: flex; align-items: center; justify-content: center;
2784
+ color: var(--gl-icon-default);
2785
+ }
2786
+ .glass-menu__item--danger .glass-menu__icon { color: inherit; }
2787
+ .glass-menu__icon:empty, .glass-menu__icon[hidden] { display: none; }
2788
+ .glass-menu__icon svg,
2789
+ .glass-menu__icon ::slotted(svg) {
2790
+ width: 18px; height: 18px; fill: none;
2791
+ stroke: currentColor; stroke-width: 1.8;
2792
+ stroke-linecap: round; stroke-linejoin: round;
2793
+ }
2794
+ .glass-menu__check {
2795
+ flex: none; width: 16px; height: 16px;
2796
+ color: var(--gl-color-primary-text);
2797
+ visibility: hidden;
2798
+ }
2799
+ .glass-menu__check svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
2800
+ .glass-menu__item:is([aria-checked="true"], .is-checked) .glass-menu__check { visibility: visible; }
2801
+ .glass-menu__label {
2802
+ flex: 1 1 auto; min-width: 0;
2803
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
2804
+ }
2805
+ .glass-menu__trailing {
2806
+ flex: none; margin-inline-start: auto; padding-inline-start: var(--gl-space-sm);
2807
+ color: var(--gl-color-text-muted); font-size: var(--gl-font-size-xs);
2808
+ }
2809
+ .glass-menu__separator {
2810
+ height: 1px; margin: var(--gl-space-2xs) var(--gl-space-xs);
2811
+ border: 0; background: var(--gl-border-subtle);
2812
+ }
2813
+ .glass-menu__group-label {
2814
+ padding: 6px 12px 2px;
2815
+ color: var(--gl-color-text-muted);
2816
+ font-size: var(--gl-font-size-xs); font-weight: var(--gl-font-weight-semibold);
2817
+ }
2818
+ @media (prefers-reduced-motion: reduce) {
2819
+ .glass-popover[popover], .glass-popover[popover]:popover-open,
2820
+ .glass-tooltip[popover], .glass-tooltip[popover]:popover-open,
2821
+ .glass-menu[popover], .glass-menu[popover]:popover-open { transition: none; animation: none; }
2822
+ }
2823
+
2443
2824
 
2444
2825
  /* ============================================================
2445
2826
  25. LIST
@@ -2558,6 +2939,9 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2558
2939
  background: var(--gl-surface-2);
2559
2940
  outline: none;
2560
2941
  }
2942
+ /* A ring for the keyboard (since 1.32.0), inside the row: the list clips
2943
+ what lies outside it. The tint alone was too faint to find the focus. */
2944
+ .glass-list__item--interactive:focus-visible { box-shadow: inset 0 0 0 2px var(--gl-border-focus); }
2561
2945
  .glass-list__item--interactive:active {
2562
2946
  background: var(--gl-surface-3);
2563
2947
  }
@@ -2689,6 +3073,249 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2689
3073
  }
2690
3074
 
2691
3075
 
3076
+ /* ============================================================
3077
+ 25b. TREE
3078
+ ============================================================ */
3079
+ /* Levels that open and close (since 1.32.0): the structure of an app, menus
3080
+ with submenus. A row per item — a twisty that turns when the item opens,
3081
+ an icon, the label, something at the end (a badge, a status, an action) —
3082
+ indented by --gl-tree-indent (20px) per level, with the item's children
3083
+ in a __group below it. role="tree" on the container, "treeitem" on each
3084
+ item with aria-level, aria-expanded on the ones with children and
3085
+ aria-selected; one tab stop and the arrow keys of the WAI-ARIA tree view,
3086
+ which GlassKit Elements' <glk-tree> brings. No surface of its own: put it
3087
+ in a card or a sidebar. */
3088
+ .glass-tree {
3089
+ display: flex; flex-direction: column; gap: 1px;
3090
+ margin: 0; padding: 0; list-style: none;
3091
+ font-family: var(--gl-font-family);
3092
+ font-size: var(--gl-font-size-sm);
3093
+ color: var(--gl-color-text);
3094
+ }
3095
+ .glass-tree__row {
3096
+ position: relative;
3097
+ display: flex; align-items: center; gap: var(--gl-space-xs);
3098
+ min-height: var(--gl-tree-row-height, 36px);
3099
+ padding-block: 4px;
3100
+ padding-inline: calc(var(--gl-space-xs) + (var(--gl-tree-level, 1) - 1) * var(--gl-tree-indent, 20px)) var(--gl-space-sm);
3101
+ border-radius: var(--gl-radius-sm);
3102
+ cursor: pointer; user-select: none;
3103
+ transition: background .15s ease;
3104
+ }
3105
+ .glass-tree__row:hover { background: var(--gl-surface-1); }
3106
+ /* The chosen row stands on raised glass, as the active row of a sidebar
3107
+ does, its icon in the accent. */
3108
+ .glass-tree__row.is-selected {
3109
+ background: var(--gl-tree-selected-bg, var(--gl-surface-4));
3110
+ box-shadow: var(--gl-inset-strong), var(--gl-shadow-btn);
3111
+ color: var(--gl-color-text-heading);
3112
+ font-weight: var(--gl-font-weight-semibold);
3113
+ }
3114
+ .glass-tree__row.is-selected .glass-tree__icon { color: var(--gl-color-primary-text); }
3115
+ .glass-tree__row.is-focus-visible,
3116
+ .glass-tree__row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--gl-border-focus); }
3117
+ /* In plain markup the treeitem takes the focus, its row shows it. */
3118
+ .glass-tree [role="treeitem"]:focus-visible { outline: none; }
3119
+ .glass-tree [role="treeitem"]:focus-visible > .glass-tree__row { box-shadow: inset 0 0 0 2px var(--gl-border-focus); }
3120
+ .glass-tree__row.is-disabled { opacity: 0.45; cursor: not-allowed; }
3121
+ .glass-tree__twisty {
3122
+ flex: none; width: 20px; height: 20px;
3123
+ display: flex; align-items: center; justify-content: center;
3124
+ color: var(--gl-icon-default);
3125
+ border-radius: var(--gl-radius-xs);
3126
+ transition: transform .15s ease;
3127
+ }
3128
+ .glass-tree__twisty svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
3129
+ .glass-tree__twisty:dir(rtl) { transform: scaleX(-1); }
3130
+ .glass-tree__row.is-expanded .glass-tree__twisty { transform: rotate(90deg); }
3131
+ .glass-tree__row.is-leaf .glass-tree__twisty { visibility: hidden; }
3132
+ /* Children on their way: the twisty turns into a small spinner. */
3133
+ .glass-tree__row.is-loading .glass-tree__twisty svg { display: none; }
3134
+ .glass-tree__row.is-loading .glass-tree__twisty::after {
3135
+ content: ''; width: 12px; height: 12px;
3136
+ border: 2px solid var(--gl-border-medium); border-top-color: var(--gl-color-primary);
3137
+ border-radius: 50%;
3138
+ animation: glass-tree-spin .8s linear infinite;
3139
+ }
3140
+ @keyframes glass-tree-spin { to { transform: rotate(360deg); } }
3141
+ .glass-tree__icon {
3142
+ flex: none; width: 18px; height: 18px;
3143
+ display: flex; align-items: center; justify-content: center;
3144
+ color: var(--gl-icon-default);
3145
+ }
3146
+ .glass-tree__icon:empty, .glass-tree__icon[hidden] { display: none; }
3147
+ .glass-tree__icon svg,
3148
+ .glass-tree__icon ::slotted(svg) {
3149
+ width: 18px; height: 18px; fill: none;
3150
+ stroke: currentColor; stroke-width: 1.8;
3151
+ stroke-linecap: round; stroke-linejoin: round;
3152
+ }
3153
+ .glass-tree__label {
3154
+ flex: 1 1 auto; min-width: 0;
3155
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
3156
+ }
3157
+ .glass-tree__trailing {
3158
+ flex: none; display: flex; align-items: center; gap: var(--gl-space-2xs);
3159
+ margin-inline-start: auto;
3160
+ color: var(--gl-color-text-muted); font-size: var(--gl-font-size-xs);
3161
+ }
3162
+ .glass-tree__trailing[hidden] { display: none; }
3163
+ .glass-tree__group { display: flex; flex-direction: column; gap: 1px; margin: 0; padding: 0; list-style: none; }
3164
+ .glass-tree__group[hidden] { display: none; }
3165
+ /* Children that could not load: the twisty turns into a mark in the error
3166
+ colour, and opening the item again tries once more. */
3167
+ .glass-tree__row.is-error .glass-tree__twisty { color: var(--gl-color-error-on-surface, var(--gl-color-error)); }
3168
+ .glass-tree__row.is-error .glass-tree__twisty svg { display: none; }
3169
+ .glass-tree__row.is-error .glass-tree__twisty::after {
3170
+ content: '!';
3171
+ display: flex; align-items: center; justify-content: center;
3172
+ width: 14px; height: 14px; box-sizing: border-box;
3173
+ border: 1.5px solid currentColor; border-radius: 50%;
3174
+ font-size: 10px; font-weight: var(--gl-font-weight-semibold); line-height: 1;
3175
+ }
3176
+
3177
+ /* A short flash on an item that changed elsewhere — on another device, by
3178
+ an undo — so the eye finds it among the others (since 1.32.0), on a tree
3179
+ row or a list item; --gl-flash-color tints it. */
3180
+ .glass-tree__row.is-flash,
3181
+ .glass-list__item.is-flash { animation: glass-flash 1.2s ease-out; }
3182
+ @keyframes glass-flash { from { background-color: var(--gl-flash-color, color-mix(in srgb, var(--gl-color-primary) 30%, transparent)); } }
3183
+ @media (prefers-reduced-motion: reduce) {
3184
+ .glass-tree__twisty, .glass-tree__row { transition: none; }
3185
+ .glass-tree__row.is-loading .glass-tree__twisty::after { animation-duration: 2.4s; }
3186
+ .glass-tree__row.is-flash, .glass-list__item.is-flash { animation: none; }
3187
+ }
3188
+
3189
+
3190
+ /* ============================================================
3191
+ 25c. BREADCRUMBS
3192
+ ============================================================ */
3193
+ /* The path through nested levels (since 1.32.0): My app › Main menu ›
3194
+ Services › Online booking. A <nav aria-label="Breadcrumb"> around an
3195
+ <ol>; each crumb a link (or a button), the current level plain text with
3196
+ aria-current="page". The separators are drawn by CSS, hidden from
3197
+ assistive technology and mirrored right to left. A crumb longer than
3198
+ --gl-breadcrumbs-crumb-width (200px) is cut with an ellipsis. Where the
3199
+ path does not fit, __overflow stands for the middle levels — a button
3200
+ that opens them as a menu; GlassKit Elements' <glk-breadcrumbs> folds
3201
+ them by itself and makes the crumbs drop targets for a sortable list. */
3202
+ .glass-breadcrumbs { min-width: 0; font-family: var(--gl-font-family); }
3203
+ .glass-breadcrumbs__list {
3204
+ display: flex; align-items: center; flex-wrap: nowrap;
3205
+ min-width: 0; margin: 0; padding: 0; list-style: none;
3206
+ font-size: var(--gl-font-size-sm);
3207
+ }
3208
+ .glass-breadcrumbs__item { display: flex; align-items: center; min-width: 0; }
3209
+ .glass-breadcrumbs__item[hidden] { display: none; }
3210
+ .glass-breadcrumbs__item + .glass-breadcrumbs__item::before {
3211
+ content: ''; flex: none;
3212
+ width: 6px; height: 6px; margin-inline: 4px 8px;
3213
+ border-top: 1.5px solid var(--gl-icon-muted);
3214
+ border-right: 1.5px solid var(--gl-icon-muted);
3215
+ transform: rotate(45deg);
3216
+ }
3217
+ .glass-breadcrumbs__item + .glass-breadcrumbs__item:dir(rtl)::before { transform: rotate(-135deg); }
3218
+ .glass-breadcrumbs__crumb,
3219
+ .glass-breadcrumbs__current {
3220
+ display: block; min-width: 0;
3221
+ max-width: var(--gl-breadcrumbs-crumb-width, 200px);
3222
+ padding: 4px 8px;
3223
+ border-radius: var(--gl-radius-full);
3224
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
3225
+ }
3226
+ .glass-breadcrumbs__crumb {
3227
+ border: 0; background: none;
3228
+ color: var(--gl-color-text-muted);
3229
+ font: inherit; text-decoration: none;
3230
+ cursor: pointer;
3231
+ transition: background .15s ease, color .15s ease;
3232
+ }
3233
+ .glass-breadcrumbs__crumb:hover { color: var(--gl-color-text); background: var(--gl-surface-1); }
3234
+ .glass-breadcrumbs__crumb:focus-visible { outline: none; box-shadow: var(--gl-shadow-focus); }
3235
+ .glass-breadcrumbs__current { color: var(--gl-color-text-heading); font-weight: var(--gl-font-weight-semibold); }
3236
+ .glass-breadcrumbs__overflow { min-width: 32px; text-align: center; letter-spacing: 1px; }
3237
+ /* The "…" crumb keeps its width where the path still does not fit. */
3238
+ .glass-breadcrumbs__item--overflow { flex: none; }
3239
+ /* A crumb as the target of a dragged item: it stands for its level. */
3240
+ .glass-breadcrumbs__crumb.is-drop-target,
3241
+ .glass-breadcrumbs__current.is-drop-target {
3242
+ color: var(--gl-color-text);
3243
+ background: color-mix(in srgb, var(--gl-color-primary) 18%, transparent);
3244
+ box-shadow: inset 0 0 0 1.5px var(--gl-border-focus);
3245
+ }
3246
+ .glass-breadcrumbs__crumb.is-drop-refused { box-shadow: inset 0 0 0 1.5px var(--gl-color-error-border); background: var(--gl-color-error-surface); }
3247
+
3248
+
3249
+ /* ============================================================
3250
+ 25d. SORTING
3251
+ ============================================================ */
3252
+ /* The states of a drag that reorders a list or a tree (since 1.32.0). The
3253
+ dragged item stays where it was, faded (.is-sort-source); a raised copy,
3254
+ __ghost, follows the pointer in the top layer. A line shows where it
3255
+ lands before or after an item — .is-sort-before / .is-sort-after on the
3256
+ target, indented to the level it lands on (--gl-tree-level) — and an
3257
+ item that takes it inside lights up (.is-sort-inside); one that refuses
3258
+ it shows the error colour (.is-sort-refused). The motion of the items
3259
+ that make room and of the drop gliding into place is GlassKit
3260
+ Elements'; --gl-sort-duration (180ms) and --gl-sort-easing (ease-out)
3261
+ time it. None of it moves under prefers-reduced-motion. */
3262
+ .is-sort-source { opacity: 0.4; }
3263
+ .is-sort-before, .is-sort-after { position: relative; }
3264
+ .is-sort-before::before,
3265
+ .is-sort-after::before {
3266
+ content: ''; position: absolute; z-index: 2;
3267
+ inset-inline-start: calc(var(--gl-space-xs) + (var(--gl-tree-level, 1) - 1) * var(--gl-tree-indent, 20px));
3268
+ inset-inline-end: var(--gl-space-xs);
3269
+ height: 2px; border-radius: 1px;
3270
+ background: var(--gl-color-primary);
3271
+ box-shadow: 0 0 6px color-mix(in srgb, var(--gl-color-primary) 50%, transparent);
3272
+ pointer-events: none;
3273
+ }
3274
+ .is-sort-before::before { top: -1px; }
3275
+ .is-sort-after::before { bottom: -1px; }
3276
+ .is-sort-inside {
3277
+ background: color-mix(in srgb, var(--gl-color-primary) 16%, transparent);
3278
+ box-shadow: inset 0 0 0 1.5px var(--gl-border-focus);
3279
+ }
3280
+ .is-sort-refused {
3281
+ background: var(--gl-color-error-surface);
3282
+ box-shadow: inset 0 0 0 1.5px var(--gl-color-error-border);
3283
+ }
3284
+ .is-sort-refused::before { background: var(--gl-color-error); box-shadow: none; }
3285
+ .glass-sort__ghost {
3286
+ display: flex; align-items: center; gap: var(--gl-space-xs);
3287
+ max-width: min(360px, calc(100vw - 16px));
3288
+ padding: 10px 14px;
3289
+ border: 1px solid var(--gl-border-medium);
3290
+ border-radius: var(--gl-radius-sm);
3291
+ background: var(--gl-surface-4);
3292
+ backdrop-filter: blur(var(--gl-blur));
3293
+ -webkit-backdrop-filter: blur(var(--gl-blur));
3294
+ box-shadow: 0 12px 32px rgba(0,0,0,0.28), 0 2px 8px rgba(0,0,0,0.14), var(--gl-inset-strong);
3295
+ color: var(--gl-color-text);
3296
+ font-family: var(--gl-font-family); font-size: var(--gl-font-size-sm);
3297
+ font-weight: var(--gl-font-weight-medium);
3298
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
3299
+ pointer-events: none;
3300
+ }
3301
+ .glass-sort__ghost[popover] { inset: auto; margin: 0; overflow: hidden; }
3302
+ /* Closed, out of the page: its own display would keep it on screen. */
3303
+ .glass-sort__ghost[popover]:not(:popover-open) { display: none; }
3304
+ /* A handle: with one, only the handle starts a drag, so a tap on the item
3305
+ still chooses it; a finger on it drags at once, without the long press. */
3306
+ .glass-sort__handle {
3307
+ flex: none; display: flex; align-items: center; justify-content: center;
3308
+ color: var(--gl-icon-muted);
3309
+ cursor: grab; touch-action: none;
3310
+ }
3311
+ .glass-sort__handle[hidden] { display: none; }
3312
+ .glass-sort__handle svg,
3313
+ .glass-sort__handle ::slotted(svg) {
3314
+ width: 18px; height: 18px; fill: none;
3315
+ stroke: currentColor; stroke-width: 1.8; stroke-linecap: round;
3316
+ }
3317
+
3318
+
2692
3319
  /* ============================================================
2693
3320
  26. SKELETON
2694
3321
  ============================================================ */
@@ -2728,12 +3355,12 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2728
3355
  font-size: var(--gl-font-size-sm); color: var(--gl-color-text);
2729
3356
  }
2730
3357
  .glass-table th {
2731
- text-align: start; padding: 8px 10px; white-space: nowrap;
3358
+ text-align: start; padding: var(--gl-table-header-padding, 8px 10px); white-space: nowrap;
2732
3359
  font-weight: var(--gl-font-weight-medium); color: var(--gl-color-text-muted);
2733
3360
  border-bottom: 1px solid var(--gl-border-medium);
2734
3361
  }
2735
3362
  .glass-table td {
2736
- padding: 9px 10px; vertical-align: top;
3363
+ padding: var(--gl-table-cell-padding, 9px 10px); vertical-align: top;
2737
3364
  border-bottom: 1px solid var(--gl-border-subtle);
2738
3365
  }
2739
3366
  .glass-table tbody tr:last-child td { border-bottom: 0; }
@@ -2741,6 +3368,131 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
2741
3368
  .glass-table__num { text-align: end; font-variant-numeric: tabular-nums; }
2742
3369
  .glass-table__muted { color: var(--gl-color-text-muted); }
2743
3370
 
3371
+ /* More since 1.32.0, mostly brought by GlassKit Elements' <glk-table>
3372
+ around the table, which keeps it a native table in the page:
3373
+
3374
+ __sort a button filling a header cell that sorts by its column;
3375
+ the th carries aria-sort and the arrow shows it.
3376
+ --sticky the header cells stick to the top of a wrap that scrolls
3377
+ both ways (.glass-table-wrap--sticky, as high as
3378
+ --gl-table-max-height, 60vh), on --gl-table-header-bg.
3379
+ __actions a cell for row actions — a menu, icon buttons — at the
3380
+ row's end, kept there while the table scrolls sideways
3381
+ (on a surface of its own once it does: --scrolls).
3382
+ --list each row a card with the column's name before each value
3383
+ (data-label on the cells), the actions at its top end —
3384
+ for narrow places.
3385
+
3386
+ --gl-table-cell-padding and --gl-table-header-padding set the cells'
3387
+ room. */
3388
+ .glass-table__sort {
3389
+ display: inline-flex; align-items: center; gap: 4px;
3390
+ margin: -4px -6px; padding: 4px 6px;
3391
+ border: 0; border-radius: var(--gl-radius-xs);
3392
+ background: none; color: inherit;
3393
+ font: inherit; font-weight: inherit; white-space: nowrap;
3394
+ cursor: pointer;
3395
+ transition: background .15s ease, color .15s ease;
3396
+ }
3397
+ .glass-table__sort:hover { color: var(--gl-color-text); background: var(--gl-surface-1); }
3398
+ .glass-table__sort:focus-visible { outline: none; box-shadow: var(--gl-shadow-focus); }
3399
+ .glass-table__sort-icon {
3400
+ flex: none; width: 12px; height: 12px;
3401
+ fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
3402
+ opacity: 0.35;
3403
+ transition: transform .15s ease, opacity .15s ease;
3404
+ }
3405
+ .glass-table th:is([aria-sort="ascending"], [aria-sort="descending"]) .glass-table__sort { color: var(--gl-color-text); }
3406
+ .glass-table th:is([aria-sort="ascending"], [aria-sort="descending"]) .glass-table__sort-icon { opacity: 1; }
3407
+ .glass-table th[aria-sort="descending"] .glass-table__sort-icon { transform: rotate(180deg); }
3408
+
3409
+ .glass-table-wrap--sticky { overflow: auto; max-height: var(--gl-table-max-height, 60vh); overscroll-behavior: contain; }
3410
+ .glass-table--sticky thead th {
3411
+ position: sticky; top: 0; z-index: 2;
3412
+ /* The line goes with the cell: a collapsed border stays with the table. */
3413
+ border-bottom: 0;
3414
+ box-shadow: inset 0 -1px 0 var(--gl-border-medium);
3415
+ background: var(--gl-table-header-bg, color-mix(in srgb, var(--gl-color-bg-mid) 94%, transparent));
3416
+ backdrop-filter: blur(var(--gl-blur-light));
3417
+ -webkit-backdrop-filter: blur(var(--gl-blur-light));
3418
+ }
3419
+
3420
+ .glass-table__actions {
3421
+ position: sticky; inset-inline-end: 0; z-index: 1;
3422
+ width: 1%; text-align: end; white-space: nowrap;
3423
+ }
3424
+ .glass-table--sticky thead th.glass-table__actions { z-index: 3; }
3425
+ .glass-table--scrolls .glass-table__actions {
3426
+ background: var(--gl-table-header-bg, color-mix(in srgb, var(--gl-color-bg-mid) 94%, transparent));
3427
+ box-shadow: -8px 0 8px -8px rgba(0,0,0,0.3);
3428
+ }
3429
+ .glass-table--scrolls .glass-table__actions:dir(rtl) { box-shadow: 8px 0 8px -8px rgba(0,0,0,0.3); }
3430
+
3431
+ /* The edges of a wrap that scrolls sideways shade where more columns wait
3432
+ (data-more-start, data-more-end on the frame, set by <glk-table>). */
3433
+ .glass-table-frame { position: relative; min-width: 0; }
3434
+ .glass-table-frame::before,
3435
+ .glass-table-frame::after {
3436
+ content: ''; position: absolute; top: 0; bottom: 0; z-index: 4;
3437
+ width: 16px; pointer-events: none;
3438
+ opacity: 0; transition: opacity .15s ease;
3439
+ }
3440
+ .glass-table-frame::before { inset-inline-start: 0; background: linear-gradient(to right, rgba(0,0,0,0.22), transparent); }
3441
+ .glass-table-frame::after { inset-inline-end: 0; background: linear-gradient(to left, rgba(0,0,0,0.22), transparent); }
3442
+ .glass-table-frame:dir(rtl)::before { background: linear-gradient(to left, rgba(0,0,0,0.22), transparent); }
3443
+ .glass-table-frame:dir(rtl)::after { background: linear-gradient(to right, rgba(0,0,0,0.22), transparent); }
3444
+ .glass-table-frame[data-more-start]::before,
3445
+ .glass-table-frame[data-more-end]::after { opacity: 1; }
3446
+ .glass-table__empty { padding: var(--gl-space-lg) var(--gl-space-md); }
3447
+ .glass-table__empty[hidden] { display: none; }
3448
+
3449
+ /* As cards. The header stays for assistive technology, hidden from view;
3450
+ the labels are generated content without a text of their own, so a
3451
+ screen reader does not hear the column twice. */
3452
+ .glass-table--list thead {
3453
+ position: absolute; width: 1px; height: 1px; margin: -1px;
3454
+ overflow: hidden; clip-path: inset(50%); white-space: nowrap;
3455
+ }
3456
+ .glass-table--list,
3457
+ .glass-table--list tbody,
3458
+ .glass-table--list tr,
3459
+ .glass-table--list td { display: block; width: 100%; box-sizing: border-box; }
3460
+ .glass-table--list tbody { display: flex; flex-direction: column; gap: var(--gl-space-sm); }
3461
+ .glass-table--list tr {
3462
+ position: relative;
3463
+ display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--gl-space-sm);
3464
+ padding: var(--gl-space-sm) var(--gl-space-md);
3465
+ border: 1px solid var(--gl-border-subtle);
3466
+ /* Glass like the list's and the accordion's items. */
3467
+ border-radius: var(--gl-radius-btn);
3468
+ background: var(--gl-surface-1);
3469
+ box-shadow: var(--gl-inset-subtle);
3470
+ }
3471
+ .glass-table--list td {
3472
+ display: flex; justify-content: space-between; align-items: baseline; gap: var(--gl-space-md);
3473
+ grid-column: 1 / -1;
3474
+ padding: 4px 0; border: 0;
3475
+ text-align: end; overflow-wrap: anywhere;
3476
+ }
3477
+ /* The actions at the card's top end, beside its first two values. */
3478
+ .glass-table--list tr:has(> .glass-table__actions) > td:nth-child(-n+2):not(.glass-table__actions) { grid-column: 1; }
3479
+ .glass-table--list td::before {
3480
+ content: attr(data-label);
3481
+ content: attr(data-label) / "";
3482
+ flex: none; max-width: 50%;
3483
+ color: var(--gl-color-text-muted); font-weight: var(--gl-font-weight-medium);
3484
+ text-align: start;
3485
+ }
3486
+ .glass-table--list td:is(:not([data-label]), [data-label=""])::before { content: none; }
3487
+ .glass-table--list tbody tr:hover td { background: none; }
3488
+ .glass-table--list .glass-table__actions {
3489
+ position: static; grid-column: 2; grid-row: 1 / span 2; align-self: start;
3490
+ width: auto; padding: 0; background: none; box-shadow: none;
3491
+ }
3492
+ @media (prefers-reduced-motion: reduce) {
3493
+ .glass-table__sort, .glass-table__sort-icon, .glass-table-frame::before, .glass-table-frame::after { transition: none; }
3494
+ }
3495
+
2744
3496
 
2745
3497
  /* ============================================================
2746
3498
  28. PROSE
@@ -3010,8 +3762,11 @@ dialog.glass-modal-overlay::backdrop { background: transparent; }
3010
3762
  color: var(--gl-color-success-on-surface);
3011
3763
  }
3012
3764
  .glass-steps__item--current { color: var(--gl-color-text-heading); }
3765
+ /* The flat accent fills that carry text — this number, the chosen day of
3766
+ the date strip and of the calendar — take --gl-color-primary-fill where
3767
+ it is set (since 1.32.0), else the primary. */
3013
3768
  .glass-steps__item--current .glass-steps__num {
3014
- background: var(--gl-color-primary); border-color: transparent;
3769
+ background: var(--gl-color-primary-fill, var(--gl-color-primary)); border-color: transparent;
3015
3770
  color: var(--gl-color-on-primary);
3016
3771
  }
3017
3772
  .glass-steps__line { flex: 1 1 8px; min-width: 8px; height: 1px; background: var(--gl-border-subtle); }
@@ -3184,7 +3939,7 @@ dialog.glass-sheet-overlay::backdrop { background: transparent; }
3184
3939
  .glass-date-strip__day:hover { background: var(--gl-surface-3); }
3185
3940
  .glass-date-strip__day[aria-pressed="true"] {
3186
3941
  --gl-date-strip-ink: var(--gl-color-on-primary);
3187
- background: var(--gl-color-primary);
3942
+ background: var(--gl-color-primary-fill, var(--gl-color-primary));
3188
3943
  color: var(--gl-color-on-primary);
3189
3944
  border-color: transparent;
3190
3945
  box-shadow: var(--gl-shadow-btn-primary);
@@ -3201,6 +3956,10 @@ dialog.glass-sheet-overlay::backdrop { background: transparent; }
3201
3956
  font-size: var(--gl-font-size-xs); opacity: 0.75;
3202
3957
  text-transform: uppercase; letter-spacing: 0.04em;
3203
3958
  }
3959
+ /* On the chosen chip the weekday is as strong as the number (since
3960
+ 1.32.0): faded, it stayed below 4.5:1 even on a fill that the number
3961
+ reaches it on. */
3962
+ .glass-date-strip__day[aria-pressed="true"] .glass-date-strip__wd { opacity: 1; }
3204
3963
  .glass-date-strip__num {
3205
3964
  font-size: var(--gl-font-size-lg); font-weight: var(--gl-font-weight-semibold);
3206
3965
  line-height: 1.1;
@@ -3290,7 +4049,7 @@ dialog.glass-sheet-overlay::backdrop { background: transparent; }
3290
4049
  .glass-calendar__day--today { border-color: var(--gl-border-warm); }
3291
4050
  .glass-calendar__day[aria-pressed="true"] {
3292
4051
  --gl-calendar-ink: var(--gl-color-on-primary);
3293
- background: var(--gl-color-primary);
4052
+ background: var(--gl-color-primary-fill, var(--gl-color-primary));
3294
4053
  color: var(--gl-color-on-primary);
3295
4054
  }
3296
4055
  .glass-calendar__day:focus-visible { box-shadow: var(--gl-shadow-focus); }