aerolith 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +61 -108
  3. package/library/dist/aerolith.css +171 -16
  4. package/library/dist/aerolith.js +289 -36
  5. package/library/dist/fonts/OFL-Aerolith-Display.txt +100 -0
  6. package/library/dist/fonts/OFL-Aerolith-Text.txt +97 -0
  7. package/library/dist/fonts/OFL-Istok-Web.txt +94 -0
  8. package/library/dist/fonts/aerolith-display-aqua.bmp.woff2 +0 -0
  9. package/library/dist/fonts/aerolith-display-aqua.svg.woff2 +0 -0
  10. package/library/dist/fonts/aerolith-display-glass-dark.bmp.woff2 +0 -0
  11. package/library/dist/fonts/aerolith-display-glass-dark.svg.woff2 +0 -0
  12. package/library/dist/fonts/aerolith-display-glass.bmp.woff2 +0 -0
  13. package/library/dist/fonts/aerolith-display-glass.svg.woff2 +0 -0
  14. package/library/dist/fonts/aerolith-display-pearl.bmp.woff2 +0 -0
  15. package/library/dist/fonts/aerolith-display-pearl.svg.woff2 +0 -0
  16. package/library/dist/fonts/aerolith-text-bold.woff2 +0 -0
  17. package/library/dist/fonts/aerolith-text-light.woff2 +0 -0
  18. package/library/dist/fonts/aerolith-text-regular.woff2 +0 -0
  19. package/library/dist/fonts/aerolith-text-semibold.woff2 +0 -0
  20. package/library/dist/fonts/istok-web-bold-italic.woff2 +0 -0
  21. package/library/dist/fonts/istok-web-bold.woff2 +0 -0
  22. package/library/dist/fonts/istok-web-italic.woff2 +0 -0
  23. package/library/dist/fonts/istok-web-regular.woff2 +0 -0
  24. package/library/dist/sprites/lens.dark@1x.png +0 -0
  25. package/library/dist/sprites/lens.dark@2x.png +0 -0
  26. package/library/dist/sprites/lens.dark@3x.png +0 -0
  27. package/library/dist/sprites/lens@1x.png +0 -0
  28. package/library/dist/sprites/lens@2x.png +0 -0
  29. package/library/dist/sprites/lens@3x.png +0 -0
  30. package/library/dist/sprites/logo-aqua@1x.png +0 -0
  31. package/library/dist/sprites/logo-aqua@2x.png +0 -0
  32. package/library/dist/sprites/logo-aqua@3x.png +0 -0
  33. package/library/dist/sprites/logo-liquid@1x.png +0 -0
  34. package/library/dist/sprites/logo-liquid@2x.png +0 -0
  35. package/library/dist/sprites/logo-liquid@3x.png +0 -0
  36. package/library/dist/sprites.json +88 -0
  37. package/package.json +7 -5
package/LICENSE CHANGED
@@ -21,5 +21,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
22
 
23
23
  The rendered sprite images in library/dist/ are covered by the same license.
24
+ The fonts in library/dist/fonts/ are not: they are under the SIL Open Font
25
+ License 1.1 (Istok Web, Aerolith Text, which is derived from Selawik, and
26
+ Aerolith Display, which is derived from Istok Web). Their license files are in
27
+ that folder.
24
28
  Bryce is a trademark of its respective owner. This project is not affiliated
25
29
  with or endorsed by it, and contains no part of the Bryce software.
package/README.md CHANGED
@@ -1,32 +1,21 @@
1
1
  # Aerolith
2
2
 
3
- A Frutiger Aero UI library for the web. Every button, field, pane and icon is a sprite rendered in
4
- Bryce 2, the 1996 landscape renderer, and drawn onto native HTML elements with CSS.
3
+ Tactile UI for the web. Every button, field, pane, icon and title letter is a Bryce 2 render
4
+ (1996), laid over plain HTML elements.
5
5
 
6
6
  **[Live showcase](https://immineal.github.io/aerolith/)** · [npm](https://www.npmjs.com/package/aerolith) · [Changelog](CHANGELOG.md)
7
7
 
8
- ![Aerolith components in light and dark mode](docs/images/hero.png)
9
-
10
- The glossy parts reflect a sunny meadow, with blue sky above and green hills below, that sits
11
- behind the camera in every render. Glass is real Bryce glass, rendered over two backdrops and
12
- matted so it stays translucent on your page. Pressed buttons sink into a bowl, so the highlight
13
- moves to the bottom edge. Every surface was rendered, not painted in CSS.
8
+ ![Aerolith buttons in light and dark mode](docs/images/hero.png)
14
9
 
15
10
  ## Use it
16
11
 
17
- No build step. Link the stylesheet and, optionally, the script:
12
+ No build step:
18
13
 
19
14
  ```html
20
15
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aerolith@1/library/dist/aerolith.css">
21
16
  <script src="https://cdn.jsdelivr.net/npm/aerolith@1/library/dist/aerolith.js" defer></script>
22
17
  ```
23
18
 
24
- The same files are on GitHub through jsDelivr
25
- (`https://cdn.jsdelivr.net/gh/immineal/aerolith@v1.0.0/library/dist/aerolith.css`) and in the
26
- release zip. Keep `sprites/` next to `aerolith.css`; the CSS finds the images relative to itself.
27
-
28
- Then use native elements with Aerolith classes:
29
-
30
19
  ```html
31
20
  <button class="aer-button">Convert to PDF</button>
32
21
  <button class="aer-button aer-secondary aer-l">Start</button>
@@ -41,39 +30,19 @@ Then use native elements with Aerolith classes:
41
30
  </button>
42
31
  ```
43
32
 
44
- Add `class="aer"` to `<body>` (or any container) to get the focus outline and reduced-motion
45
- handling. The showcase uses every component, so its source doubles as reference markup.
46
-
47
- ### Package managers
33
+ `class="aer"` on `<body>` adds the focus ring and reduced-motion rules. The showcase source has an
34
+ example of every component. Self-hosting: keep `sprites/` and `fonts/` next to `aerolith.css`.
48
35
 
49
36
  | Where | Install | Then |
50
37
  |---|---|---|
51
- | npm | `npm i aerolith` | `import "aerolith/aerolith.css"` (and `import "aerolith/aerolith.js"`) |
52
- | GitHub Packages | `npm i @immineal/aerolith --registry=https://npm.pkg.github.com` | same, under `@immineal/aerolith` |
38
+ | npm | `npm i aerolith` | `import "aerolith/aerolith.css"` (and `aerolith/aerolith.js`) |
39
+ | GitHub Packages | `npm i @immineal/aerolith --registry=https://npm.pkg.github.com` | same |
53
40
  | Composer | `composer require immineal/aerolith` | serve `vendor/immineal/aerolith/library/dist/` |
54
41
  | PyPI | `pip install aerolith` | `aerolith.static_dir()`; Django: add `"aerolith"` to `INSTALLED_APPS` |
55
- | CDN | unpkg, jsDelivr, esm.sh mirror the npm package | see above |
56
-
57
- ### Frameworks
58
-
59
- Aerolith is plain CSS classes on native elements, so it needs no wrapper components. Import the
60
- stylesheet once:
61
-
62
- ```js
63
- // React (Vite, Next.js), Vue, Svelte, Solid: in your entry file or root layout
64
- import "aerolith/aerolith.css";
65
- import "aerolith/aerolith.js"; // optional: slider fill, drop zone, theme switch, lens
66
- ```
67
-
68
- ```astro
69
- ---
70
- // Astro: in a layout
71
- import "aerolith/aerolith.css";
72
- ---
73
- ```
42
+ | CDN | jsDelivr, unpkg, esm.sh | or `cdn.jsdelivr.net/gh/immineal/aerolith@v1.2.0/library/dist/aerolith.css` |
74
43
 
75
- In JSX write `className="aer-button"`. When you add components after page load (client-side
76
- routing), call `window.Aerolith.init(container)` to wire up the optional behaviour.
44
+ React, Vue, Svelte, Astro: import the CSS once in your entry file and use the classes
45
+ (`className` in JSX). Content added after load needs `window.Aerolith.init(container)`.
77
46
 
78
47
  ## Components
79
48
 
@@ -86,7 +55,7 @@ routing), call `window.Aerolith.init(container)` to wire up the optional behavio
86
55
  | `.aer-check`, `.aer-radio` | checkbox, radio | checked, mixed, disabled |
87
56
  | `.aer-field` | `<input>`, `<textarea>`, `<select>` | focus, disabled |
88
57
  | `.aer-select` | wrapper around `select.aer-field` | |
89
- | `.aer-segmented` | radios in labels, or links with `aria-current` | hover, selected |
58
+ | `.aer-segmented` | radios in labels, or links with `aria-current` | hover, selected (slides with the JS) |
90
59
  | `.aer-choice` | `<label>` with a radio or checkbox | hover, checked, focus |
91
60
  | `.aer-drop` | `<label>` with `<input type="file">` | drag-over, focus |
92
61
  | `.aer-progress` | `<progress>` | value |
@@ -98,95 +67,79 @@ routing), call `window.Aerolith.init(container)` to wire up the optional behavio
98
67
  | `.aer-dialog`, `.aer-close` | `<dialog>`, `<button>` | open, backdrop |
99
68
  | `.aer-popover`, `.aer-menu` | `[popover]` | item hover/focus |
100
69
  | `.aer-bar` | `<header>`, `<nav>` | |
101
- | `.aer-lens` | any block | rim refraction in Chromium |
70
+ | `.aer-lens` | any block | rim refraction; `data-aer-drag` makes it movable |
102
71
  | `.aer-compare` | two `<img>` and an `<input type="range">` | |
72
+ | `.aer-display` (+ `.aer-aqua`, `.aer-glass`, `.aer-pearl`) | any heading | 116 characters |
103
73
 
104
- Dark mode follows `prefers-color-scheme`. Force it with `<html data-theme="dark">` (or `"light"`),
105
- or scope it with `.aer-dark`. The theme switch (`input[data-aer-theme]`) sets the attribute and
106
- remembers the choice.
74
+ ### Type
107
75
 
108
- ## Accessibility
76
+ ```html
77
+ <h1 class="aer-display">Welcome back</h1> <!-- aqua; glass on dark pages -->
78
+ <h2 class="aer-display aer-glass">Photos</h2> <!-- clear glass: needs colour behind it -->
79
+ <p class="aer-display aer-pearl">Ready</p> <!-- white, for coloured backgrounds -->
80
+ ```
109
81
 
110
- - Every skin sits on the native element, so keyboard use, form submission, screen readers and
111
- autofill behave as they would without Aerolith.
112
- - Label contrast was measured on the rendered sprite pixels, at the darkest 5 % of the label
113
- area, before the text halo is added: white on aqua 4.4:1, white on green 4.7:1, dark ink on
114
- clear glass at least 4.6:1 in both themes. Hover and pressed states are lighter, so their
115
- labels get a stronger halo.
116
- - Controls are 44 px high by default. Small controls (checkbox, radio, close orb) get a 44 px hit
117
- area through their label or a pseudo-element.
118
- - `forced-colors` (Windows high contrast) drops the sprites and falls back to system colours.
119
- Icons switch to `CanvasText`.
120
- - `prefers-reduced-motion` stops the spinner and the toggle, toast and dialog animations.
121
- - Where look and accessibility conflict, the look won, but only after the cheap fixes (ink
122
- choice, halos, hit areas) were in. Without their halo, glossy hover states measure 3.3 to
123
- 3.6:1 and pressed states about 2.4:1, because the sunken face catches more light. Both are
124
- momentary states. `python3 library/build/a11y.py` prints the numbers for every labelled sprite.
82
+ Aerolith Display is a colour font of rendered gel letters, so titles stay real, selectable text.
83
+ Use it from 32 px up. Each material is about 0.6 MB, loaded only where used. Browsers without
84
+ colour fonts show heavy Istok.
125
85
 
126
- ## How it's made
86
+ Body text is **Aerolith Text**, Selawik (an open Segoe UI stand-in) with a kern table, because Selawik
87
+ ships without kerning. `data-aer-type="istok"` switches to **Istok Web**. Dark mode follows the system; `data-theme="dark"` or `"light"` on `<html>` forces it,
88
+ and the sun/moon switch (`input[data-aer-theme]`) sets that for you.
127
89
 
128
- ![The three render passes of one sprite](docs/images/passes.png)
129
-
130
- Bryce 2.1 for Windows runs under Wine on a virtual X display, seven instances side by side. Python
131
- writes each scene directly as a Bryce `.brc` file. The format was reverse-engineered for this:
132
- see [docs/brc-format.md](docs/brc-format.md). Bryce then renders it to disk. Each sprite is
133
- rendered three times:
90
+ ## Accessibility
134
91
 
135
- 1. **K:** over black, which gives the colour and every glint.
136
- 2. **W:** over mid grey, so the difference to K shows how much light the glass lets through.
137
- 3. **M:** flat colours, a mask that says which pixels are solid and which are glass.
92
+ Real form controls sit under every skin, so keyboards, screen readers and autofill work as usual.
138
93
 
139
- The matting step turns those into PNGs with real alpha: solid gloss is opaque, glass keeps its
140
- transparency, and a second matte for dark pages turns frosted glass into smoked glass.
94
+ - Label contrast, measured on the rendered pixels: white on aqua 4.4:1, white on green 4.7:1,
95
+ dark on glass 4.6:1 or better. Hover states measure 3.3 to 3.6:1 without their halo, pressed
96
+ about 2.4:1. There the look won. `python3 library/build/a11y.py` prints every number.
97
+ - Controls are 44 px high; small ones get a 44 px hit area.
98
+ - `forced-colors` swaps the sprites for system colours. `prefers-reduced-motion` stops the
99
+ animations.
141
100
 
142
- Each sprite is rendered alone in its own scene. Rendered together, sprites reflect and refract
143
- their neighbours, which looks random once the sheet is cut up. The camera sits 4000 units away
144
- with a long lens, so the projection is near-orthographic and one CSS pixel is exactly six render
145
- pixels. That is downsampled to 3x, 2x and 1x files.
101
+ ## How it's made
146
102
 
147
- ![Button states and orbs at 3x: the sky in the top half, the meadow in the lower bevel](docs/images/closeup.png)
103
+ ![The three render passes of one sprite](docs/images/passes.png)
148
104
 
149
- Behind the camera is the environment every gloss surface reflects: a custom sky, a big softbox
150
- for the highlight band, and a tilted meadow with two hills. The camera never sees any of it,
151
- but every glossy surface shows sky in its upper half and grass along its lower bevel.
105
+ Bryce 2.1 runs under Wine on virtual displays, seven at once. Python writes each scene as a
106
+ `.brc` file; I reverse-engineered the format in [docs/brc-format.md](docs/brc-format.md). Each
107
+ sprite is rendered alone, three times: over black (colour and glints), over grey (how much light
108
+ the glass lets through) and as a flat mask. From those the build makes PNGs where gloss is solid
109
+ and glass is see-through.
152
110
 
153
- Shapes are Bryce symmetrical lattices (height fields) computed in NumPy: rounded rectangles with
154
- an eased bevel, a blurred-mask bowl for pressed states, a recessed well for fields, and icons
155
- embossed from vector drawings. The CSS draws every sprite with `border-image`, with slices
156
- measured in raw image pixels per resolution. That behaves the same in Chromium, Firefox and
157
- WebKit, where `image-set()` inside `border-image` does not.
111
+ ![Button states and orbs at 3x](docs/images/closeup.png)
158
112
 
159
- ## Rebuild it yourself
113
+ Behind the camera is a sky and a meadow with two hills, so every button reflects blue on top and
114
+ green below. The shapes are height fields computed in NumPy. The CSS draws sprites with
115
+ `border-image`.
160
116
 
161
- The rendered output is committed in `library/dist/`, so you only need this to change sprites.
162
- You need your own copy of Bryce 2 for Windows and Wine. Neither is included, and Bryce never will
163
- be.
117
+ To render new sprites you need Wine and your own copy of Bryce 2 (not included):
164
118
 
165
119
  ```sh
166
120
  cd library/build
167
- python3 build.py # renders what changed (hash cache), exports, writes dist/
168
- python3 build.py btn- # only sprites whose names start with btn-
121
+ python3 build.py # renders what changed, exports to dist/
122
+ python3 build.py btn- # only sprites starting with btn-
169
123
  python3 build.py --export-only
170
- python3 shots.py # screenshots in Chromium, Firefox, WebKit, light and dark
171
124
  ```
172
125
 
173
- The renderer setup is in [harness/README.md](harness/README.md), the design rules in
126
+ Setup: [harness/README.md](harness/README.md). Design rules:
174
127
  [docs/ui-library/foundations.md](docs/ui-library/foundations.md).
175
128
 
176
129
  ## What it does not do
177
130
 
178
- - **No runtime colours.** The colours are baked into the sprites. A new colour means new renders.
179
- - **Fixed control heights.** Buttons come in 32, 44 and 56 px. They stretch in width (3- and
180
- 9-slice), not in height.
181
- - **Glass distortion is Chromium-only.** The lens rim refraction uses an SVG filter in
182
- `backdrop-filter`, which only Chromium supports. Elsewhere you get the clear glass without
183
- distortion.
184
- - **No JS framework components.** There are no React or Vue wrappers. The classes go on your
185
- own elements.
186
- - **No icon font or SVG set.** The 35 icons are bitmaps at 16 and 24 px. They don't scale up
187
- cleanly.
131
+ - **No colour picker.** Colours are baked into the renders.
132
+ - **Three heights.** 32, 44 or 56 px; buttons stretch sideways only.
133
+ - **The lens bends light in Chromium, and in Firefox with `data-aer-lens-backdrop="#el"`** (the
134
+ element behind it). Safari shows plain glass.
135
+ - **No framework components.** Put the classes on your own elements.
136
+ - **Bitmap icons and titles.** Icons are 16 and 24 px. Titles are sharp up to about 120 px and
137
+ cover Latin with German and French accents.
188
138
 
189
139
  ## License
190
140
 
191
- MIT, for the code and the rendered sprites. See [LICENSE](LICENSE). Bryce is a trademark of its
192
- owner. This project is not affiliated with it and contains no part of the Bryce software.
141
+ MIT for code and sprites ([LICENSE](LICENSE)). Fonts in `library/dist/fonts/` are SIL OFL 1.1:
142
+ Istok Web (Andrey V. Panov) unmodified; Aerolith Text, derived from Selawik (Microsoft) with a kern
143
+ table added and renamed because the OFL reserves "Selawik"; Aerolith Display, derived from
144
+ Istok Web Bold. Bryce is a trademark of its owner; Aerolith is not affiliated and contains no
145
+ Bryce code.
@@ -21,7 +21,7 @@
21
21
  --aer-target-gap: var(--aer-space-2);
22
22
 
23
23
  /* type: modular scale 1.2 */
24
- --aer-font-ui: "Segoe UI", "Frutiger", "Myriad Pro", "Lucida Grande", Tahoma, system-ui, sans-serif;
24
+ --aer-font-ui: "Aerolith Text", "Selawik", "Segoe UI", "Istok Web", "Myriad Pro", Tahoma, system-ui, sans-serif; /* bundled: type.css */
25
25
  --aer-font-text: var(--aer-font-ui);
26
26
  --aer-font-mono: "Consolas", "Lucida Console", ui-monospace, monospace;
27
27
  --aer-text-xs: 0.833rem;
@@ -68,6 +68,13 @@
68
68
  /* 10-foot / distance media: scale everything so the visual angle stays comfortable at ~3 m */
69
69
  .aer-distance { font-size: 150%; --aer-control: var(--aer-control-l); }
70
70
 
71
+ /* Chromium on Linux and Windows rounds every glyph advance to whole pixels unless asked for precise
72
+ geometry: a 16 px label came out 1.6 px wider with an open gap after the f in "verfügbar".
73
+ Scoped to our own controls; a host page keeps its own text-rendering. */
74
+ :is(.aer-button, .aer-field, .aer-select, .aer-badge, .aer-segmented, .aer-menu, .aer-label, .aer-option,
75
+ .aer-choice, .aer-check, .aer-radio, .aer-toggle, .aer-panel, .aer-card, .aer-callout, .aer-dialog,
76
+ .aer-popover, .aer-toast) { text-rendering: geometricPrecision; }
77
+
71
78
  /* label text on glossy sprites: white + halo (WCAG measures text against its halo) */
72
79
  .aer-on-gloss {
73
80
  color: var(--aer-on-gloss);
@@ -89,7 +96,7 @@
89
96
  @media (forced-colors: active) {
90
97
  .aer [class*="aer-"] { border-color: ButtonText; }
91
98
  .aer-button, .aer-field, .aer-panel { border: 1px solid ButtonText !important; }
92
- .aer-button:focus-visible { outline: 2px solid Highlight; }
99
+ .aer-button:focus-visible, .aer .aer-field:focus-visible, .aer-field:focus-visible { outline: 2px solid Highlight; }
93
100
  }
94
101
 
95
102
  /* utilities */
@@ -98,6 +105,80 @@
98
105
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
99
106
  }
100
107
 
108
+ /* ---- type.css ---- */
109
+ /* Aerolith type.
110
+ Text: Aerolith Text (default; Selawik with a kern table, renamed because the OFL reserves "Selawik")
111
+ or Istok Web (data-aer-type="istok"), both bundled, both SIL OFL 1.1.
112
+ Titles: Aerolith Display, gel letters rendered in Bryce and packed into colour fonts. Each
113
+ material comes as OpenType-SVG (Firefox, Safari) and sbix (Chromium); tech() makes a browser
114
+ download only the file it can draw. The fonts keep outlines, so a browser without colour-font
115
+ support still shows the title, in plain heavy Istok. */
116
+
117
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-regular.woff2) format("woff2");
118
+ font-weight: 400; font-style: normal; font-display: swap; }
119
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-italic.woff2) format("woff2");
120
+ font-weight: 400; font-style: italic; font-display: swap; }
121
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-bold.woff2) format("woff2");
122
+ font-weight: 700; font-style: normal; font-display: swap; }
123
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-bold-italic.woff2) format("woff2");
124
+ font-weight: 700; font-style: italic; font-display: swap; }
125
+
126
+ @font-face { font-family: "Aerolith Text"; src: url(fonts/aerolith-text-light.woff2) format("woff2");
127
+ font-weight: 300; font-display: swap; }
128
+ @font-face { font-family: "Aerolith Text"; src: url(fonts/aerolith-text-regular.woff2) format("woff2");
129
+ font-weight: 400; font-display: swap; }
130
+ @font-face { font-family: "Aerolith Text"; src: url(fonts/aerolith-text-semibold.woff2) format("woff2");
131
+ font-weight: 600; font-display: swap; }
132
+ @font-face { font-family: "Aerolith Text"; src: url(fonts/aerolith-text-bold.woff2) format("woff2");
133
+ font-weight: 700; font-display: swap; }
134
+
135
+ @font-face { font-family: "Aerolith Display Aqua"; font-display: swap;
136
+ src: url(fonts/aerolith-display-aqua.svg.woff2) format("woff2") tech(color-SVG),
137
+ url(fonts/aerolith-display-aqua.bmp.woff2) format("woff2") tech(color-sbix); }
138
+ @font-face { font-family: "Aerolith Display Glass"; font-display: swap;
139
+ src: url(fonts/aerolith-display-glass.svg.woff2) format("woff2") tech(color-SVG),
140
+ url(fonts/aerolith-display-glass.bmp.woff2) format("woff2") tech(color-sbix); }
141
+ @font-face { font-family: "Aerolith Display Glass Dark"; font-display: swap;
142
+ src: url(fonts/aerolith-display-glass-dark.svg.woff2) format("woff2") tech(color-SVG),
143
+ url(fonts/aerolith-display-glass-dark.bmp.woff2) format("woff2") tech(color-sbix); }
144
+ @font-face { font-family: "Aerolith Display Pearl"; font-display: swap;
145
+ src: url(fonts/aerolith-display-pearl.svg.woff2) format("woff2") tech(color-SVG),
146
+ url(fonts/aerolith-display-pearl.bmp.woff2) format("woff2") tech(color-sbix); }
147
+
148
+ [data-aer-type="istok"], .aer-type-istok {
149
+ --aer-font-ui: "Istok Web", "Aerolith Text", "Segoe UI", Tahoma, system-ui, sans-serif;
150
+ }
151
+
152
+ /* Big titles only: the letters are rendered at about 80 px per em and read best from 32 px up.
153
+ Weight 700 picks the bold text face while the font loads; the colour face itself has one weight and is
154
+ never synthesised. */
155
+ .aer-display {
156
+ --_face: "Aerolith Display Aqua";
157
+ font-family: var(--_face), var(--aer-font-ui);
158
+ font-weight: 700; font-style: normal; font-synthesis: none;
159
+ line-height: 1.12; letter-spacing: 0; text-shadow: none;
160
+ color: var(--aer-ink);
161
+ }
162
+ /* Glass is clear: it needs colour or a picture behind it and all but vanishes on a pale page.
163
+ On dark pages the default face is Glass Dark, because Aqua's deep blue sinks into them
164
+ (APCA Lc about -22 against Glass Dark's -54). .aer-aqua keeps Aqua anyway, e.g. on a light
165
+ panel inside a dark page. */
166
+ .aer-display.aer-aqua { --_face: "Aerolith Display Aqua"; }
167
+ .aer-display.aer-glass { --_face: "Aerolith Display Glass"; }
168
+ .aer-display.aer-pearl { --_face: "Aerolith Display Pearl"; }
169
+ :root[data-theme="dark"] .aer-display:not(.aer-aqua, .aer-pearl),
170
+ .aer-dark .aer-display:not(.aer-aqua, .aer-pearl),
171
+ .aer-display.aer-dark:not(.aer-aqua, .aer-pearl) { --_face: "Aerolith Display Glass Dark"; }
172
+ @media (prefers-color-scheme: dark) {
173
+ :root:not([data-theme="light"]) .aer-display:not(.aer-aqua, .aer-pearl) { --_face: "Aerolith Display Glass Dark"; }
174
+ }
175
+ @media (forced-colors: active) {
176
+ .aer-display { font-family: var(--aer-font-ui); }
177
+ }
178
+
179
+ /* Vista-style glow: a soft halo of page colour behind dark text on glass or busy backgrounds */
180
+ .aer-glow { text-shadow: 0 0 1px var(--aer-page), 0 0 6px var(--aer-page), 0 0 12px var(--aer-page); }
181
+
101
182
  /* ---- sprites.css ---- */
102
183
  :root {
103
184
  --aer-badge-error-w: 60px; --aer-badge-error-h: 22px;
@@ -256,6 +337,10 @@
256
337
  --aer-lens-w: 160px; --aer-lens-h: 120px;
257
338
  --aer-lens-border: 37px 37px 37px 37px;
258
339
  --aer-lens-outset: 1px 1px 1px 1px;
340
+ --aer-logo-aqua-w: 36px; --aer-logo-aqua-h: 36px;
341
+ --aer-logo-aqua-outset: 0 0 0 0;
342
+ --aer-logo-liquid-w: 36px; --aer-logo-liquid-h: 36px;
343
+ --aer-logo-liquid-outset: 0 0 0 0;
259
344
  --aer-orb-error-w: 16px; --aer-orb-error-h: 16px;
260
345
  --aer-orb-error-outset: 1px 1px 1px 1px;
261
346
  --aer-orb-info-w: 16px; --aer-orb-info-h: 16px;
@@ -420,6 +505,8 @@
420
505
  --aer-iconbtn-primary-hover: url("sprites/iconbtn-primary-hover@1x.png");
421
506
  --aer-lens: url("sprites/lens@1x.png");
422
507
  --aer-lens-slice: 37 37 37 37 fill;
508
+ --aer-logo-aqua: url("sprites/logo-aqua@1x.png");
509
+ --aer-logo-liquid: url("sprites/logo-liquid@1x.png");
423
510
  --aer-orb-error: url("sprites/orb-error@1x.png");
424
511
  --aer-orb-info: url("sprites/orb-info@1x.png");
425
512
  --aer-orb-ok: url("sprites/orb-ok@1x.png");
@@ -562,6 +649,8 @@
562
649
  --aer-iconbtn-primary-hover: url("sprites/iconbtn-primary-hover@2x.png");
563
650
  --aer-lens: url("sprites/lens@2x.png");
564
651
  --aer-lens-slice: 74 74 74 74 fill;
652
+ --aer-logo-aqua: url("sprites/logo-aqua@2x.png");
653
+ --aer-logo-liquid: url("sprites/logo-liquid@2x.png");
565
654
  --aer-orb-error: url("sprites/orb-error@2x.png");
566
655
  --aer-orb-info: url("sprites/orb-info@2x.png");
567
656
  --aer-orb-ok: url("sprites/orb-ok@2x.png");
@@ -705,6 +794,8 @@
705
794
  --aer-iconbtn-primary-hover: url("sprites/iconbtn-primary-hover@3x.png");
706
795
  --aer-lens: url("sprites/lens@3x.png");
707
796
  --aer-lens-slice: 111 111 111 111 fill;
797
+ --aer-logo-aqua: url("sprites/logo-aqua@3x.png");
798
+ --aer-logo-liquid: url("sprites/logo-liquid@3x.png");
708
799
  --aer-orb-error: url("sprites/orb-error@3x.png");
709
800
  --aer-orb-info: url("sprites/orb-info@3x.png");
710
801
  --aer-orb-ok: url("sprites/orb-ok@3x.png");
@@ -1119,31 +1210,31 @@
1119
1210
 
1120
1211
  /* ---- skins.css ---- */
1121
1212
  /* generated by build/build.py: component state -> sprite */
1122
- .aer-button { --_img: var(--aer-btn-primary-m); --_slice: var(--aer-btn-primary-m-slice); --_bw: var(--aer-btn-primary-m-border); --_out: var(--aer-btn-primary-m-outset); }
1213
+ .aer-button { --_img: var(--aer-btn-primary-m); --_slice: var(--aer-btn-primary-m-slice); --_bw: var(--aer-btn-primary-m-border); --_out: var(--aer-btn-primary-m-outset); --_pre: var(--aer-btn-primary-m-hover), var(--aer-btn-primary-m-active); }
1123
1214
  .aer-button:hover { --_img: var(--aer-btn-primary-m-hover); --_slice: var(--aer-btn-primary-m-hover-slice); --_bw: var(--aer-btn-primary-m-hover-border); --_out: var(--aer-btn-primary-m-hover-outset); }
1124
1215
  .aer-button:active, .aer-button[aria-pressed="true"] { --_img: var(--aer-btn-primary-m-active); --_slice: var(--aer-btn-primary-m-active-slice); --_bw: var(--aer-btn-primary-m-active-border); --_out: var(--aer-btn-primary-m-active-outset); }
1125
- .aer-button.aer-s { --_img: var(--aer-btn-primary-s); --_slice: var(--aer-btn-primary-s-slice); --_bw: var(--aer-btn-primary-s-border); --_out: var(--aer-btn-primary-s-outset); }
1216
+ .aer-button.aer-s { --_img: var(--aer-btn-primary-s); --_slice: var(--aer-btn-primary-s-slice); --_bw: var(--aer-btn-primary-s-border); --_out: var(--aer-btn-primary-s-outset); --_pre: var(--aer-btn-primary-s-hover), var(--aer-btn-primary-s-active); }
1126
1217
  .aer-button.aer-s:hover { --_img: var(--aer-btn-primary-s-hover); --_slice: var(--aer-btn-primary-s-hover-slice); --_bw: var(--aer-btn-primary-s-hover-border); --_out: var(--aer-btn-primary-s-hover-outset); }
1127
1218
  .aer-button.aer-s:active, .aer-button.aer-s[aria-pressed="true"] { --_img: var(--aer-btn-primary-s-active); --_slice: var(--aer-btn-primary-s-active-slice); --_bw: var(--aer-btn-primary-s-active-border); --_out: var(--aer-btn-primary-s-active-outset); }
1128
- .aer-button.aer-l { --_img: var(--aer-btn-primary-l); --_slice: var(--aer-btn-primary-l-slice); --_bw: var(--aer-btn-primary-l-border); --_out: var(--aer-btn-primary-l-outset); }
1219
+ .aer-button.aer-l { --_img: var(--aer-btn-primary-l); --_slice: var(--aer-btn-primary-l-slice); --_bw: var(--aer-btn-primary-l-border); --_out: var(--aer-btn-primary-l-outset); --_pre: var(--aer-btn-primary-l-hover), var(--aer-btn-primary-l-active); }
1129
1220
  .aer-button.aer-l:hover { --_img: var(--aer-btn-primary-l-hover); --_slice: var(--aer-btn-primary-l-hover-slice); --_bw: var(--aer-btn-primary-l-hover-border); --_out: var(--aer-btn-primary-l-hover-outset); }
1130
1221
  .aer-button.aer-l:active, .aer-button.aer-l[aria-pressed="true"] { --_img: var(--aer-btn-primary-l-active); --_slice: var(--aer-btn-primary-l-active-slice); --_bw: var(--aer-btn-primary-l-active-border); --_out: var(--aer-btn-primary-l-active-outset); }
1131
- .aer-button.aer-secondary { --_img: var(--aer-btn-secondary-m); --_slice: var(--aer-btn-secondary-m-slice); --_bw: var(--aer-btn-secondary-m-border); --_out: var(--aer-btn-secondary-m-outset); }
1222
+ .aer-button.aer-secondary { --_img: var(--aer-btn-secondary-m); --_slice: var(--aer-btn-secondary-m-slice); --_bw: var(--aer-btn-secondary-m-border); --_out: var(--aer-btn-secondary-m-outset); --_pre: var(--aer-btn-secondary-m-hover), var(--aer-btn-secondary-m-active); }
1132
1223
  .aer-button.aer-secondary:hover { --_img: var(--aer-btn-secondary-m-hover); --_slice: var(--aer-btn-secondary-m-hover-slice); --_bw: var(--aer-btn-secondary-m-hover-border); --_out: var(--aer-btn-secondary-m-hover-outset); }
1133
1224
  .aer-button.aer-secondary:active, .aer-button.aer-secondary[aria-pressed="true"] { --_img: var(--aer-btn-secondary-m-active); --_slice: var(--aer-btn-secondary-m-active-slice); --_bw: var(--aer-btn-secondary-m-active-border); --_out: var(--aer-btn-secondary-m-active-outset); }
1134
- .aer-button.aer-secondary.aer-s { --_img: var(--aer-btn-secondary-s); --_slice: var(--aer-btn-secondary-s-slice); --_bw: var(--aer-btn-secondary-s-border); --_out: var(--aer-btn-secondary-s-outset); }
1225
+ .aer-button.aer-secondary.aer-s { --_img: var(--aer-btn-secondary-s); --_slice: var(--aer-btn-secondary-s-slice); --_bw: var(--aer-btn-secondary-s-border); --_out: var(--aer-btn-secondary-s-outset); --_pre: var(--aer-btn-secondary-s-hover), var(--aer-btn-secondary-s-active); }
1135
1226
  .aer-button.aer-secondary.aer-s:hover { --_img: var(--aer-btn-secondary-s-hover); --_slice: var(--aer-btn-secondary-s-hover-slice); --_bw: var(--aer-btn-secondary-s-hover-border); --_out: var(--aer-btn-secondary-s-hover-outset); }
1136
1227
  .aer-button.aer-secondary.aer-s:active, .aer-button.aer-secondary.aer-s[aria-pressed="true"] { --_img: var(--aer-btn-secondary-s-active); --_slice: var(--aer-btn-secondary-s-active-slice); --_bw: var(--aer-btn-secondary-s-active-border); --_out: var(--aer-btn-secondary-s-active-outset); }
1137
- .aer-button.aer-secondary.aer-l { --_img: var(--aer-btn-secondary-l); --_slice: var(--aer-btn-secondary-l-slice); --_bw: var(--aer-btn-secondary-l-border); --_out: var(--aer-btn-secondary-l-outset); }
1228
+ .aer-button.aer-secondary.aer-l { --_img: var(--aer-btn-secondary-l); --_slice: var(--aer-btn-secondary-l-slice); --_bw: var(--aer-btn-secondary-l-border); --_out: var(--aer-btn-secondary-l-outset); --_pre: var(--aer-btn-secondary-l-hover), var(--aer-btn-secondary-l-active); }
1138
1229
  .aer-button.aer-secondary.aer-l:hover { --_img: var(--aer-btn-secondary-l-hover); --_slice: var(--aer-btn-secondary-l-hover-slice); --_bw: var(--aer-btn-secondary-l-hover-border); --_out: var(--aer-btn-secondary-l-hover-outset); }
1139
1230
  .aer-button.aer-secondary.aer-l:active, .aer-button.aer-secondary.aer-l[aria-pressed="true"] { --_img: var(--aer-btn-secondary-l-active); --_slice: var(--aer-btn-secondary-l-active-slice); --_bw: var(--aer-btn-secondary-l-active-border); --_out: var(--aer-btn-secondary-l-active-outset); }
1140
- .aer-button.aer-quiet { --_img: var(--aer-btn-glass-m); --_slice: var(--aer-btn-glass-m-slice); --_bw: var(--aer-btn-glass-m-border); --_out: var(--aer-btn-glass-m-outset); }
1231
+ .aer-button.aer-quiet { --_img: var(--aer-btn-glass-m); --_slice: var(--aer-btn-glass-m-slice); --_bw: var(--aer-btn-glass-m-border); --_out: var(--aer-btn-glass-m-outset); --_pre: var(--aer-btn-glass-m-hover), var(--aer-btn-glass-m-active); }
1141
1232
  .aer-button.aer-quiet:hover { --_img: var(--aer-btn-glass-m-hover); --_slice: var(--aer-btn-glass-m-hover-slice); --_bw: var(--aer-btn-glass-m-hover-border); --_out: var(--aer-btn-glass-m-hover-outset); }
1142
1233
  .aer-button.aer-quiet:active, .aer-button.aer-quiet[aria-pressed="true"] { --_img: var(--aer-btn-glass-m-active); --_slice: var(--aer-btn-glass-m-active-slice); --_bw: var(--aer-btn-glass-m-active-border); --_out: var(--aer-btn-glass-m-active-outset); }
1143
- .aer-button.aer-quiet.aer-s { --_img: var(--aer-btn-glass-s); --_slice: var(--aer-btn-glass-s-slice); --_bw: var(--aer-btn-glass-s-border); --_out: var(--aer-btn-glass-s-outset); }
1234
+ .aer-button.aer-quiet.aer-s { --_img: var(--aer-btn-glass-s); --_slice: var(--aer-btn-glass-s-slice); --_bw: var(--aer-btn-glass-s-border); --_out: var(--aer-btn-glass-s-outset); --_pre: var(--aer-btn-glass-s-hover), var(--aer-btn-glass-s-active); }
1144
1235
  .aer-button.aer-quiet.aer-s:hover { --_img: var(--aer-btn-glass-s-hover); --_slice: var(--aer-btn-glass-s-hover-slice); --_bw: var(--aer-btn-glass-s-hover-border); --_out: var(--aer-btn-glass-s-hover-outset); }
1145
1236
  .aer-button.aer-quiet.aer-s:active, .aer-button.aer-quiet.aer-s[aria-pressed="true"] { --_img: var(--aer-btn-glass-s-active); --_slice: var(--aer-btn-glass-s-active-slice); --_bw: var(--aer-btn-glass-s-active-border); --_out: var(--aer-btn-glass-s-active-outset); }
1146
- .aer-button.aer-quiet.aer-l { --_img: var(--aer-btn-glass-l); --_slice: var(--aer-btn-glass-l-slice); --_bw: var(--aer-btn-glass-l-border); --_out: var(--aer-btn-glass-l-outset); }
1237
+ .aer-button.aer-quiet.aer-l { --_img: var(--aer-btn-glass-l); --_slice: var(--aer-btn-glass-l-slice); --_bw: var(--aer-btn-glass-l-border); --_out: var(--aer-btn-glass-l-outset); --_pre: var(--aer-btn-glass-l-hover), var(--aer-btn-glass-l-active); }
1147
1238
  .aer-button.aer-quiet.aer-l:hover { --_img: var(--aer-btn-glass-l-hover); --_slice: var(--aer-btn-glass-l-hover-slice); --_bw: var(--aer-btn-glass-l-hover-border); --_out: var(--aer-btn-glass-l-hover-outset); }
1148
1239
  .aer-button.aer-quiet.aer-l:active, .aer-button.aer-quiet.aer-l[aria-pressed="true"] { --_img: var(--aer-btn-glass-l-active); --_slice: var(--aer-btn-glass-l-active-slice); --_bw: var(--aer-btn-glass-l-active-border); --_out: var(--aer-btn-glass-l-active-outset); }
1149
1240
  .aer-button:disabled, .aer-button[aria-disabled="true"] { --_img: var(--aer-btn-disabled-m); --_slice: var(--aer-btn-disabled-m-slice); --_bw: var(--aer-btn-disabled-m-border); --_out: var(--aer-btn-disabled-m-outset); }
@@ -1371,6 +1462,8 @@
1371
1462
  .aer-field:focus {
1372
1463
  border-image: var(--aer-field-focus) var(--aer-field-focus-slice) / var(--aer-field-focus-border) / var(--aer-field-focus-outset) stretch;
1373
1464
  }
1465
+ /* the rendered aqua rim is the focus indicator: the square outline would cut across the rounded well */
1466
+ .aer .aer-field:focus-visible, .aer-field:focus-visible { outline: none; }
1374
1467
  textarea.aer-field { block-size: auto; min-block-size: calc(var(--aer-control) * 2); padding-block: var(--aer-space-3); resize: vertical; line-height: var(--aer-leading); }
1375
1468
  .aer-field:disabled, .aer-field[readonly] { color: var(--aer-ink-muted); }
1376
1469
  .aer-label { display: grid; gap: var(--aer-space-1); font: 600 var(--aer-text-s) / 1.3 var(--aer-font-ui); color: var(--aer-ink); }
@@ -1546,6 +1639,20 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1546
1639
  }
1547
1640
  .aer-segmented > :has(:focus-visible) { outline: 2px solid var(--aer-focus); outline-offset: 2px; }
1548
1641
  .aer-segmented > :has(:disabled) { opacity: 0.5; cursor: not-allowed; }
1642
+ /* with aerolith.js: one pill carries the chip and slides; the items' own chips give way */
1643
+ .aer-segmented[data-aer-slide] { position: relative; isolation: isolate; }
1644
+ .aer-seg-pill {
1645
+ position: absolute; left: 0; top: 0; z-index: -1; box-sizing: border-box; pointer-events: none;
1646
+ border-style: solid; border-color: transparent; border-width: 0;
1647
+ border-image: var(--aer-seg-chip) var(--aer-seg-chip-slice) / var(--aer-seg-chip-border) / var(--aer-seg-chip-outset) stretch;
1648
+ }
1649
+ .aer-segmented[data-aer-ready] > .aer-seg-pill {
1650
+ transition: transform 300ms cubic-bezier(.3, 1.2, .5, 1), width 300ms cubic-bezier(.3, 1.2, .5, 1);
1651
+ }
1652
+ .aer-segmented[data-aer-slide] > :is(:has(:checked), [aria-current]:not([aria-current="false"]), [aria-selected="true"], [aria-pressed="true"]) {
1653
+ border-image: none;
1654
+ }
1655
+ .aer-segmented[data-aer-ready] > :not(.aer-seg-pill) { transition: color 200ms, text-shadow 200ms; }
1549
1656
 
1550
1657
  /* ------------------------------------------------------------------ choice card: a whole tile is the radio/checkbox
1551
1658
  <label class="aer-choice"><input type="radio" class="aer-radio" name="f"> <b>PDF</b> <small>…</small></label> */
@@ -1622,9 +1729,14 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1622
1729
  border-style: solid; border-color: transparent; border-width: 0;
1623
1730
  border-image: var(--aer-card) var(--aer-card-slice) / var(--aer-card-border) / var(--aer-card-outset) stretch;
1624
1731
  color: var(--aer-ink); font: var(--aer-text-m) / 1.4 var(--aer-font-text);
1732
+ border-radius: 14px; -webkit-backdrop-filter: blur(12px) saturate(1.3); backdrop-filter: blur(12px) saturate(1.3);
1625
1733
  animation: aer-toast-in 260ms cubic-bezier(.3, 1.4, .5, 1);
1626
1734
  }
1627
1735
  @keyframes aer-toast-in { from { opacity: 0; translate: 0 12px; } }
1736
+ @media (max-width: 600px) { /* phones: centred, clear of the screen edges */
1737
+ .aer-toaster { inset-inline: var(--aer-space-4); justify-items: center; }
1738
+ .aer-toast { max-inline-size: 100%; }
1739
+ }
1628
1740
 
1629
1741
  /* ------------------------------------------------------------------ dialog: frosted window on native <dialog>
1630
1742
  <dialog class="aer-dialog"><header>…<button class="aer-close" aria-label="Close">…</button></header>…</dialog> */
@@ -1635,7 +1747,8 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1635
1747
  background: none; color: var(--aer-ink); font: var(--aer-text-m) / var(--aer-leading) var(--aer-font-text);
1636
1748
  overflow: visible;
1637
1749
  }
1638
- .aer-dialog::backdrop { background: rgba(8, 40, 80, 0.28); backdrop-filter: blur(3px) saturate(1.2); }
1750
+ .aer-dialog::backdrop { background: rgba(8, 40, 80, 0.34);
1751
+ -webkit-backdrop-filter: blur(8px) saturate(1.2); backdrop-filter: blur(8px) saturate(1.2); }
1639
1752
  .aer-dialog > header { display: flex; align-items: center; justify-content: space-between; gap: var(--aer-space-3);
1640
1753
  margin: calc(-1 * var(--aer-space-2)) 0 var(--aer-space-3); font: 600 var(--aer-text-l) / 1.3 var(--aer-font-ui); }
1641
1754
  .aer-dialog[open] { animation: aer-dialog-in 220ms cubic-bezier(.3, 1.3, .5, 1); }
@@ -1647,7 +1760,7 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1647
1760
  display: inline-grid; place-items: center; inline-size: 24px; block-size: 24px;
1648
1761
  border-style: solid; border-color: transparent; border-width: 0;
1649
1762
  border-image: var(--aer-close) 0 fill / 0 / var(--aer-close-outset) stretch;
1650
- background: none; cursor: pointer;
1763
+ background: none; cursor: pointer; border-radius: 50%; /* the focus ring follows the gem */
1651
1764
  }
1652
1765
  .aer-close::after { content: ""; position: absolute; inset: -10px; }
1653
1766
  .aer-close:hover { border-image: var(--aer-close-hover) 0 fill / 0 / var(--aer-close-hover-outset) stretch; }
@@ -1662,8 +1775,10 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1662
1775
  border-image: var(--aer-card) var(--aer-card-slice) / var(--aer-card-border) / var(--aer-card-outset) stretch;
1663
1776
  background: none; color: var(--aer-ink); font: var(--aer-text-m) / 1.4 var(--aer-font-text);
1664
1777
  overflow: visible;
1778
+ border-radius: 14px; -webkit-backdrop-filter: blur(12px) saturate(1.3); backdrop-filter: blur(12px) saturate(1.3);
1665
1779
  }
1666
1780
  .aer-menu { display: grid; gap: 2px; }
1781
+ [data-aer-menu] { -webkit-touch-callout: none; } /* long-press opens our menu, not the system's */
1667
1782
  .aer-menu > :is(a, button) {
1668
1783
  display: flex; align-items: center; gap: var(--aer-space-2); box-sizing: border-box;
1669
1784
  min-block-size: 36px; padding: 0 var(--aer-space-3); margin: 0;
@@ -1735,20 +1850,59 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1735
1850
  }
1736
1851
  .aer-bar > .aer-spacer { flex: 1; }
1737
1852
 
1738
- /* ------------------------------------------------------------------ lens: clear thick glass; in Chromium aerolith.js
1739
- adds the refraction of its rim (an SVG displacement map computed from the same bevel profile) */
1853
+ /* ------------------------------------------------------------------ lens: clear thick glass. aerolith.js adds the
1854
+ refraction of its rim (an SVG displacement map computed from the same bevel profile): in Chromium, and in
1855
+ Firefox when data-aer-lens-backdrop names the element behind it. Safari shows plain glass. */
1740
1856
  .aer-lens {
1741
1857
  position: relative; box-sizing: border-box; padding: var(--aer-space-5);
1742
1858
  border-style: solid; border-color: transparent; border-width: 0;
1743
1859
  border-image: var(--aer-lens) var(--aer-lens-slice) / var(--aer-lens-border) / var(--aer-lens-outset) stretch;
1744
1860
  color: var(--aer-ink);
1745
1861
  }
1862
+ /* with a backdrop copy: copy at the bottom, glass sprite over it, content on top */
1863
+ .aer-lens[data-aer-lens-view] { isolation: isolate; border-image: none; }
1864
+ .aer-lens[data-aer-lens-view]::before {
1865
+ content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
1866
+ border-style: solid; border-color: transparent; border-width: 0;
1867
+ border-image: var(--aer-lens) var(--aer-lens-slice) / var(--aer-lens-border) / var(--aer-lens-outset) stretch;
1868
+ }
1869
+ .aer-lens-view { position: absolute; z-index: -2; overflow: hidden; pointer-events: none; } /* placed by aerolith.js */
1870
+ /* the focus ring follows the glass, not a square box */
1871
+ .aer-lens[data-aer-drag] { border-radius: 24px; }
1872
+ .aer-lens[data-aer-drag] { cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
1873
+ .aer-lens[data-aer-dragging] { cursor: grabbing; }
1746
1874
 
1747
1875
  /* ------------------------------------------------------------------ preferences */
1876
+ /* ------------------------------------------------------------------ state sprites, fetched up front
1877
+ Each control lists its hover / pressed / checked sprites in --_pre; they hang off it as zero-size
1878
+ backgrounds, so the browser downloads them with the page (at the same resolution and theme the state
1879
+ will use) and a first tap never shows an empty control while its state sprite loads. */
1880
+ .aer-iconbtn { --_pre: var(--aer-iconbtn-primary-hover), var(--aer-iconbtn-primary-active); }
1881
+ .aer-iconbtn.aer-quiet { --_pre: var(--aer-iconbtn-glass-hover), var(--aer-iconbtn-glass-active); }
1882
+ .aer-toggle { --_pre: var(--aer-toggle-track-on), var(--aer-toggle-knob-on); }
1883
+ .aer-toggle.aer-theme { --_pre: var(--aer-toggle-knob-moon); }
1884
+ .aer-check { --_pre: var(--aer-check-on), var(--aer-check-mixed); }
1885
+ .aer-radio { --_pre: var(--aer-radio-on); }
1886
+ .aer-segmented, .aer-menu { --_pre: var(--aer-seg-chip-hover), var(--aer-seg-chip); }
1887
+ .aer-choice { --_pre: var(--aer-card-hover), var(--aer-choice-on); }
1888
+ a.aer-card, button.aer-card, .aer-card[data-interactive] { --_pre: var(--aer-card-hover); }
1889
+ .aer-close { --_pre: var(--aer-close-hover); }
1890
+ .aer-preload { /* added by aerolith.js when the page has dialogs, popovers or toasts */
1891
+ position: fixed; inset-inline-start: -1px; inset-block-start: -1px; inline-size: 1px; block-size: 1px;
1892
+ overflow: hidden; pointer-events: none;
1893
+ --_pre: var(--aer-dialog), var(--aer-close), var(--aer-close-hover), var(--aer-card), var(--aer-seg-chip-hover),
1894
+ var(--aer-orb-ok);
1895
+ }
1896
+ .aer-preload, .aer-button, .aer-iconbtn, .aer-toggle, .aer-check, .aer-radio, .aer-segmented, .aer-menu, .aer-choice,
1897
+ a.aer-card, button.aer-card, .aer-card[data-interactive], .aer-close {
1898
+ background-image: var(--_pre, none); background-size: 0 0; background-repeat: no-repeat;
1899
+ }
1900
+
1748
1901
  @media (prefers-reduced-motion: reduce) {
1749
1902
  .aer-spinner::before { animation: none; }
1750
1903
  .aer-toggle::before, .aer-progress::-webkit-progress-value { transition: none; }
1751
1904
  .aer-toast, .aer-dialog[open] { animation: none; }
1905
+ .aer-segmented[data-aer-ready] > .aer-seg-pill, .aer-segmented[data-aer-ready] > :not(.aer-seg-pill) { transition: none; }
1752
1906
  }
1753
1907
  /* forced colours: sprites are dropped, native rendering and system colours take over */
1754
1908
  @media (forced-colors: active) {
@@ -1761,6 +1915,7 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1761
1915
  background: Highlight; color: HighlightText; forced-color-adjust: none;
1762
1916
  }
1763
1917
  .aer-toggle, .aer-check, .aer-radio { appearance: auto; border-image: none; }
1764
- .aer-toggle::before, .aer-spinner::before, .aer-callout::before { display: none; }
1918
+ .aer-toggle::before, .aer-spinner::before, .aer-callout::before, .aer-seg-pill,
1919
+ .aer-lens::before, .aer-lens-view { display: none; }
1765
1920
  .aer-spinner { border: 3px dotted CanvasText; border-radius: 50%; }
1766
1921
  }