aerolith 1.0.0 → 1.1.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 (31) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +57 -106
  3. package/library/dist/aerolith.css +128 -12
  4. package/library/dist/aerolith.js +103 -0
  5. package/library/dist/fonts/OFL-Aerolith-Display.txt +100 -0
  6. package/library/dist/fonts/OFL-Istok-Web.txt +94 -0
  7. package/library/dist/fonts/OFL-Selawik.txt +91 -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/istok-web-bold-italic.woff2 +0 -0
  17. package/library/dist/fonts/istok-web-bold.woff2 +0 -0
  18. package/library/dist/fonts/istok-web-italic.woff2 +0 -0
  19. package/library/dist/fonts/istok-web-regular.woff2 +0 -0
  20. package/library/dist/fonts/selawik-bold.woff2 +0 -0
  21. package/library/dist/fonts/selawik-light.woff2 +0 -0
  22. package/library/dist/fonts/selawik-regular.woff2 +0 -0
  23. package/library/dist/fonts/selawik-semibold.woff2 +0 -0
  24. package/library/dist/sprites/logo-aqua@1x.png +0 -0
  25. package/library/dist/sprites/logo-aqua@2x.png +0 -0
  26. package/library/dist/sprites/logo-aqua@3x.png +0 -0
  27. package/library/dist/sprites/logo-liquid@1x.png +0 -0
  28. package/library/dist/sprites/logo-liquid@2x.png +0 -0
  29. package/library/dist/sprites/logo-liquid@3x.png +0 -0
  30. package/library/dist/sprites.json +88 -0
  31. package/package.json +5 -3
package/LICENSE CHANGED
@@ -21,5 +21,8 @@ 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, Selawik, and Aerolith Display, which is derived from
26
+ Istok Web). Their license files are in that folder.
24
27
  Bryce is a trademark of its respective owner. This project is not affiliated
25
28
  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
+ Frutiger Aero 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.1.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
 
@@ -100,93 +69,75 @@ routing), call `window.Aerolith.init(container)` to wire up the optional behavio
100
69
  | `.aer-bar` | `<header>`, `<nav>` | |
101
70
  | `.aer-lens` | any block | rim refraction in Chromium |
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 **Selawik**, an open Segoe UI stand-in. `data-aer-type="istok"` switches to
87
+ **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 only in Chromium.** Firefox and Safari show plain glass.
134
+ - **No framework components.** Put the classes on your own elements.
135
+ - **Bitmap icons and titles.** Icons are 16 and 24 px. Titles are sharp up to about 120 px and
136
+ cover Latin with German and French accents.
188
137
 
189
138
  ## License
190
139
 
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.
140
+ MIT for code and sprites ([LICENSE](LICENSE)). Fonts in `library/dist/fonts/` are SIL OFL 1.1:
141
+ Istok Web (Andrey V. Panov) and Selawik (Microsoft) unmodified, Aerolith Display derived from
142
+ Istok Web Bold. Bryce is a trademark of its owner; Aerolith is not affiliated and contains no
143
+ 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: "Selawik", "Segoe UI", "Istok Web", "Frutiger", "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;
@@ -98,6 +98,79 @@
98
98
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
99
99
  }
100
100
 
101
+ /* ---- type.css ---- */
102
+ /* Aerolith type.
103
+ Text: Selawik (default) or Istok Web (data-aer-type="istok"), both bundled, both SIL OFL 1.1.
104
+ Titles: Aerolith Display, gel letters rendered in Bryce and packed into colour fonts. Each
105
+ material comes as OpenType-SVG (Firefox, Safari) and sbix (Chromium); tech() makes a browser
106
+ download only the file it can draw. The fonts keep outlines, so a browser without colour-font
107
+ support still shows the title, in plain heavy Istok. */
108
+
109
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-regular.woff2) format("woff2");
110
+ font-weight: 400; font-style: normal; font-display: swap; }
111
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-italic.woff2) format("woff2");
112
+ font-weight: 400; font-style: italic; font-display: swap; }
113
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-bold.woff2) format("woff2");
114
+ font-weight: 700; font-style: normal; font-display: swap; }
115
+ @font-face { font-family: "Istok Web"; src: url(fonts/istok-web-bold-italic.woff2) format("woff2");
116
+ font-weight: 700; font-style: italic; font-display: swap; }
117
+
118
+ @font-face { font-family: "Selawik"; src: url(fonts/selawik-light.woff2) format("woff2");
119
+ font-weight: 300; font-display: swap; }
120
+ @font-face { font-family: "Selawik"; src: url(fonts/selawik-regular.woff2) format("woff2");
121
+ font-weight: 400; font-display: swap; }
122
+ @font-face { font-family: "Selawik"; src: url(fonts/selawik-semibold.woff2) format("woff2");
123
+ font-weight: 600; font-display: swap; }
124
+ @font-face { font-family: "Selawik"; src: url(fonts/selawik-bold.woff2) format("woff2");
125
+ font-weight: 700; font-display: swap; }
126
+
127
+ @font-face { font-family: "Aerolith Display Aqua"; font-display: swap;
128
+ src: url(fonts/aerolith-display-aqua.svg.woff2) format("woff2") tech(color-SVG),
129
+ url(fonts/aerolith-display-aqua.bmp.woff2) format("woff2") tech(color-sbix); }
130
+ @font-face { font-family: "Aerolith Display Glass"; font-display: swap;
131
+ src: url(fonts/aerolith-display-glass.svg.woff2) format("woff2") tech(color-SVG),
132
+ url(fonts/aerolith-display-glass.bmp.woff2) format("woff2") tech(color-sbix); }
133
+ @font-face { font-family: "Aerolith Display Glass Dark"; font-display: swap;
134
+ src: url(fonts/aerolith-display-glass-dark.svg.woff2) format("woff2") tech(color-SVG),
135
+ url(fonts/aerolith-display-glass-dark.bmp.woff2) format("woff2") tech(color-sbix); }
136
+ @font-face { font-family: "Aerolith Display Pearl"; font-display: swap;
137
+ src: url(fonts/aerolith-display-pearl.svg.woff2) format("woff2") tech(color-SVG),
138
+ url(fonts/aerolith-display-pearl.bmp.woff2) format("woff2") tech(color-sbix); }
139
+
140
+ [data-aer-type="istok"], .aer-type-istok {
141
+ --aer-font-ui: "Istok Web", "Selawik", "Segoe UI", Tahoma, system-ui, sans-serif;
142
+ }
143
+
144
+ /* Big titles only: the letters are rendered at about 80 px per em and read best from 32 px up.
145
+ Weight 700 picks the bold text face while the font loads; the colour face itself has one weight and is
146
+ never synthesised. */
147
+ .aer-display {
148
+ --_face: "Aerolith Display Aqua";
149
+ font-family: var(--_face), var(--aer-font-ui);
150
+ font-weight: 700; font-style: normal; font-synthesis: none;
151
+ line-height: 1.12; letter-spacing: 0; text-shadow: none;
152
+ color: var(--aer-ink);
153
+ }
154
+ /* Glass is clear: it needs colour or a picture behind it and all but vanishes on a pale page.
155
+ On dark pages the default face is Glass Dark, because Aqua's deep blue sinks into them
156
+ (APCA Lc about -22 against Glass Dark's -54). .aer-aqua keeps Aqua anyway, e.g. on a light
157
+ panel inside a dark page. */
158
+ .aer-display.aer-aqua { --_face: "Aerolith Display Aqua"; }
159
+ .aer-display.aer-glass { --_face: "Aerolith Display Glass"; }
160
+ .aer-display.aer-pearl { --_face: "Aerolith Display Pearl"; }
161
+ :root[data-theme="dark"] .aer-display:not(.aer-aqua, .aer-pearl),
162
+ .aer-dark .aer-display:not(.aer-aqua, .aer-pearl),
163
+ .aer-display.aer-dark:not(.aer-aqua, .aer-pearl) { --_face: "Aerolith Display Glass Dark"; }
164
+ @media (prefers-color-scheme: dark) {
165
+ :root:not([data-theme="light"]) .aer-display:not(.aer-aqua, .aer-pearl) { --_face: "Aerolith Display Glass Dark"; }
166
+ }
167
+ @media (forced-colors: active) {
168
+ .aer-display { font-family: var(--aer-font-ui); }
169
+ }
170
+
171
+ /* Vista-style glow: a soft halo of page colour behind dark text on glass or busy backgrounds */
172
+ .aer-glow { text-shadow: 0 0 1px var(--aer-page), 0 0 6px var(--aer-page), 0 0 12px var(--aer-page); }
173
+
101
174
  /* ---- sprites.css ---- */
102
175
  :root {
103
176
  --aer-badge-error-w: 60px; --aer-badge-error-h: 22px;
@@ -256,6 +329,10 @@
256
329
  --aer-lens-w: 160px; --aer-lens-h: 120px;
257
330
  --aer-lens-border: 37px 37px 37px 37px;
258
331
  --aer-lens-outset: 1px 1px 1px 1px;
332
+ --aer-logo-aqua-w: 36px; --aer-logo-aqua-h: 36px;
333
+ --aer-logo-aqua-outset: 0 0 0 0;
334
+ --aer-logo-liquid-w: 36px; --aer-logo-liquid-h: 36px;
335
+ --aer-logo-liquid-outset: 0 0 0 0;
259
336
  --aer-orb-error-w: 16px; --aer-orb-error-h: 16px;
260
337
  --aer-orb-error-outset: 1px 1px 1px 1px;
261
338
  --aer-orb-info-w: 16px; --aer-orb-info-h: 16px;
@@ -420,6 +497,8 @@
420
497
  --aer-iconbtn-primary-hover: url("sprites/iconbtn-primary-hover@1x.png");
421
498
  --aer-lens: url("sprites/lens@1x.png");
422
499
  --aer-lens-slice: 37 37 37 37 fill;
500
+ --aer-logo-aqua: url("sprites/logo-aqua@1x.png");
501
+ --aer-logo-liquid: url("sprites/logo-liquid@1x.png");
423
502
  --aer-orb-error: url("sprites/orb-error@1x.png");
424
503
  --aer-orb-info: url("sprites/orb-info@1x.png");
425
504
  --aer-orb-ok: url("sprites/orb-ok@1x.png");
@@ -562,6 +641,8 @@
562
641
  --aer-iconbtn-primary-hover: url("sprites/iconbtn-primary-hover@2x.png");
563
642
  --aer-lens: url("sprites/lens@2x.png");
564
643
  --aer-lens-slice: 74 74 74 74 fill;
644
+ --aer-logo-aqua: url("sprites/logo-aqua@2x.png");
645
+ --aer-logo-liquid: url("sprites/logo-liquid@2x.png");
565
646
  --aer-orb-error: url("sprites/orb-error@2x.png");
566
647
  --aer-orb-info: url("sprites/orb-info@2x.png");
567
648
  --aer-orb-ok: url("sprites/orb-ok@2x.png");
@@ -705,6 +786,8 @@
705
786
  --aer-iconbtn-primary-hover: url("sprites/iconbtn-primary-hover@3x.png");
706
787
  --aer-lens: url("sprites/lens@3x.png");
707
788
  --aer-lens-slice: 111 111 111 111 fill;
789
+ --aer-logo-aqua: url("sprites/logo-aqua@3x.png");
790
+ --aer-logo-liquid: url("sprites/logo-liquid@3x.png");
708
791
  --aer-orb-error: url("sprites/orb-error@3x.png");
709
792
  --aer-orb-info: url("sprites/orb-info@3x.png");
710
793
  --aer-orb-ok: url("sprites/orb-ok@3x.png");
@@ -1119,31 +1202,31 @@
1119
1202
 
1120
1203
  /* ---- skins.css ---- */
1121
1204
  /* 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); }
1205
+ .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
1206
  .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
1207
  .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); }
1208
+ .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
1209
  .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
1210
  .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); }
1211
+ .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
1212
  .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
1213
  .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); }
1214
+ .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
1215
  .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
1216
  .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); }
1217
+ .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
1218
  .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
1219
  .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); }
1220
+ .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
1221
  .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
1222
  .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); }
1223
+ .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
1224
  .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
1225
  .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); }
1226
+ .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
1227
  .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
1228
  .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); }
1229
+ .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
1230
  .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
1231
  .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
1232
  .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); }
@@ -1622,9 +1705,14 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1622
1705
  border-style: solid; border-color: transparent; border-width: 0;
1623
1706
  border-image: var(--aer-card) var(--aer-card-slice) / var(--aer-card-border) / var(--aer-card-outset) stretch;
1624
1707
  color: var(--aer-ink); font: var(--aer-text-m) / 1.4 var(--aer-font-text);
1708
+ border-radius: 14px; -webkit-backdrop-filter: blur(12px) saturate(1.3); backdrop-filter: blur(12px) saturate(1.3);
1625
1709
  animation: aer-toast-in 260ms cubic-bezier(.3, 1.4, .5, 1);
1626
1710
  }
1627
1711
  @keyframes aer-toast-in { from { opacity: 0; translate: 0 12px; } }
1712
+ @media (max-width: 600px) { /* phones: centred, clear of the screen edges */
1713
+ .aer-toaster { inset-inline: var(--aer-space-4); justify-items: center; }
1714
+ .aer-toast { max-inline-size: 100%; }
1715
+ }
1628
1716
 
1629
1717
  /* ------------------------------------------------------------------ dialog: frosted window on native <dialog>
1630
1718
  <dialog class="aer-dialog"><header>…<button class="aer-close" aria-label="Close">…</button></header>…</dialog> */
@@ -1635,7 +1723,8 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1635
1723
  background: none; color: var(--aer-ink); font: var(--aer-text-m) / var(--aer-leading) var(--aer-font-text);
1636
1724
  overflow: visible;
1637
1725
  }
1638
- .aer-dialog::backdrop { background: rgba(8, 40, 80, 0.28); backdrop-filter: blur(3px) saturate(1.2); }
1726
+ .aer-dialog::backdrop { background: rgba(8, 40, 80, 0.34);
1727
+ -webkit-backdrop-filter: blur(8px) saturate(1.2); backdrop-filter: blur(8px) saturate(1.2); }
1639
1728
  .aer-dialog > header { display: flex; align-items: center; justify-content: space-between; gap: var(--aer-space-3);
1640
1729
  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
1730
  .aer-dialog[open] { animation: aer-dialog-in 220ms cubic-bezier(.3, 1.3, .5, 1); }
@@ -1647,7 +1736,7 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1647
1736
  display: inline-grid; place-items: center; inline-size: 24px; block-size: 24px;
1648
1737
  border-style: solid; border-color: transparent; border-width: 0;
1649
1738
  border-image: var(--aer-close) 0 fill / 0 / var(--aer-close-outset) stretch;
1650
- background: none; cursor: pointer;
1739
+ background: none; cursor: pointer; border-radius: 50%; /* the focus ring follows the gem */
1651
1740
  }
1652
1741
  .aer-close::after { content: ""; position: absolute; inset: -10px; }
1653
1742
  .aer-close:hover { border-image: var(--aer-close-hover) 0 fill / 0 / var(--aer-close-hover-outset) stretch; }
@@ -1662,8 +1751,10 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1662
1751
  border-image: var(--aer-card) var(--aer-card-slice) / var(--aer-card-border) / var(--aer-card-outset) stretch;
1663
1752
  background: none; color: var(--aer-ink); font: var(--aer-text-m) / 1.4 var(--aer-font-text);
1664
1753
  overflow: visible;
1754
+ border-radius: 14px; -webkit-backdrop-filter: blur(12px) saturate(1.3); backdrop-filter: blur(12px) saturate(1.3);
1665
1755
  }
1666
1756
  .aer-menu { display: grid; gap: 2px; }
1757
+ [data-aer-menu] { -webkit-touch-callout: none; } /* long-press opens our menu, not the system's */
1667
1758
  .aer-menu > :is(a, button) {
1668
1759
  display: flex; align-items: center; gap: var(--aer-space-2); box-sizing: border-box;
1669
1760
  min-block-size: 36px; padding: 0 var(--aer-space-3); margin: 0;
@@ -1745,6 +1836,31 @@ a.aer-card:hover, button.aer-card:hover, .aer-card[data-interactive]:hover {
1745
1836
  }
1746
1837
 
1747
1838
  /* ------------------------------------------------------------------ preferences */
1839
+ /* ------------------------------------------------------------------ state sprites, fetched up front
1840
+ Each control lists its hover / pressed / checked sprites in --_pre; they hang off it as zero-size
1841
+ backgrounds, so the browser downloads them with the page (at the same resolution and theme the state
1842
+ will use) and a first tap never shows an empty control while its state sprite loads. */
1843
+ .aer-iconbtn { --_pre: var(--aer-iconbtn-primary-hover), var(--aer-iconbtn-primary-active); }
1844
+ .aer-iconbtn.aer-quiet { --_pre: var(--aer-iconbtn-glass-hover), var(--aer-iconbtn-glass-active); }
1845
+ .aer-toggle { --_pre: var(--aer-toggle-track-on), var(--aer-toggle-knob-on); }
1846
+ .aer-toggle.aer-theme { --_pre: var(--aer-toggle-knob-moon); }
1847
+ .aer-check { --_pre: var(--aer-check-on), var(--aer-check-mixed); }
1848
+ .aer-radio { --_pre: var(--aer-radio-on); }
1849
+ .aer-segmented, .aer-menu { --_pre: var(--aer-seg-chip-hover), var(--aer-seg-chip); }
1850
+ .aer-choice { --_pre: var(--aer-card-hover), var(--aer-choice-on); }
1851
+ a.aer-card, button.aer-card, .aer-card[data-interactive] { --_pre: var(--aer-card-hover); }
1852
+ .aer-close { --_pre: var(--aer-close-hover); }
1853
+ .aer-preload { /* added by aerolith.js when the page has dialogs, popovers or toasts */
1854
+ position: fixed; inset-inline-start: -1px; inset-block-start: -1px; inline-size: 1px; block-size: 1px;
1855
+ overflow: hidden; pointer-events: none;
1856
+ --_pre: var(--aer-dialog), var(--aer-close), var(--aer-close-hover), var(--aer-card), var(--aer-seg-chip-hover),
1857
+ var(--aer-orb-ok);
1858
+ }
1859
+ .aer-preload, .aer-button, .aer-iconbtn, .aer-toggle, .aer-check, .aer-radio, .aer-segmented, .aer-menu, .aer-choice,
1860
+ a.aer-card, button.aer-card, .aer-card[data-interactive], .aer-close {
1861
+ background-image: var(--_pre, none); background-size: 0 0; background-repeat: no-repeat;
1862
+ }
1863
+
1748
1864
  @media (prefers-reduced-motion: reduce) {
1749
1865
  .aer-spinner::before { animation: none; }
1750
1866
  .aer-toggle::before, .aer-progress::-webkit-progress-value { transition: none; }
@@ -4,6 +4,8 @@
4
4
  // - drop zone: [data-aer-over] while files are dragged over an .aer-drop
5
5
  // - theme switch: input[data-aer-theme] toggles <html data-theme> and remembers the choice
6
6
  // - lens: the rim refraction of .aer-lens (Chromium only: backdrop-filter with an SVG filter)
7
+ // - menus: popovers drop down from their button; [data-aer-menu] opens one at the pointer
8
+ // - preload: sprites of closed dialogs and popovers load with the page
7
9
  (function () {
8
10
  "use strict";
9
11
  var KEY = "aerolith-theme";
@@ -98,9 +100,110 @@
98
100
  }
99
101
  }
100
102
 
103
+ // ---------------------------------------------------------------- menus: placed like a context menu
104
+ // A [popover].aer-popover opened by a popovertarget button drops down from that button (or opens
105
+ // above it when there is no room); [data-aer-menu="id"] on any element opens that popover at the
106
+ // pointer on right-click or long-press. Either way it stays 8 px inside the window.
107
+ var invoker = null;
108
+ function clamp(v, lo, hi) { return Math.max(lo, Math.min(v, hi)); }
109
+ function place(pop, at) {
110
+ var M = 8, vw = document.documentElement.clientWidth, vh = window.innerHeight;
111
+ var w = pop.offsetWidth, h = pop.offsetHeight, left, top;
112
+ if (at.nodeType) {
113
+ var r = at.getBoundingClientRect();
114
+ left = r.left; top = r.bottom + 6;
115
+ if (top + h > vh - M && r.top - 6 - h >= M) top = r.top - 6 - h;
116
+ if (left + w > vw - M) left = r.right - w;
117
+ } else {
118
+ left = at.x; top = at.y;
119
+ if (left + w > vw - M) left = at.x - w;
120
+ if (top + h > vh - M) top = at.y - h;
121
+ }
122
+ pop.style.left = clamp(left, M, vw - M - w) + "px";
123
+ pop.style.top = clamp(top, M, vh - M - h) + "px";
124
+ }
125
+ function openAt(pop, at) {
126
+ pop.style.position = "fixed"; pop.style.inset = "auto"; pop.style.margin = "0";
127
+ pop._aerAt = at;
128
+ if (!pop.matches(":popover-open")) pop.showPopover(); else place(pop, at);
129
+ }
130
+ function bindPopover(pop) {
131
+ function close() { if (pop.matches(":popover-open")) pop.hidePopover(); }
132
+ // before it shows: anchor it, so it never flashes at the window's corner
133
+ pop.addEventListener("beforetoggle", function (e) {
134
+ if (e.newState !== "open") return;
135
+ if (!pop._aerAt && invoker && invoker.getAttribute("popovertarget") === pop.id) pop._aerAt = invoker;
136
+ if (!pop._aerAt) return;
137
+ pop.style.position = "fixed"; pop.style.inset = "auto"; pop.style.margin = "0";
138
+ place(pop, pop._aerAt); // size unknown yet: a first guess
139
+ });
140
+ pop.addEventListener("toggle", function (e) {
141
+ if (e.newState === "open") {
142
+ if (pop._aerAt) place(pop, pop._aerAt); // measured: flip or clamp if needed
143
+ addEventListener("resize", close);
144
+ addEventListener("scroll", close, { capture: true, passive: true });
145
+ } else {
146
+ pop._aerAt = null;
147
+ removeEventListener("resize", close);
148
+ removeEventListener("scroll", close, { capture: true });
149
+ }
150
+ });
151
+ // picking an item closes the menu
152
+ pop.addEventListener("click", function (e) {
153
+ if (e.target.closest(".aer-menu > a, .aer-menu > button") && pop.matches(":popover-open")) pop.hidePopover();
154
+ });
155
+ }
156
+ function bindContext(el) {
157
+ var pop = document.getElementById(el.getAttribute("data-aer-menu"));
158
+ if (!pop || !pop.showPopover) return;
159
+ var timer = 0, start = null, pending = null, down = false;
160
+ el.addEventListener("contextmenu", function (e) {
161
+ e.preventDefault();
162
+ var at = { x: e.clientX, y: e.clientY };
163
+ // Linux and macOS fire contextmenu on mouse-down, phones while the finger is down. Opened
164
+ // then, the release would light-dismiss the menu, so it opens when the button or finger lifts.
165
+ if (down) pending = at; else openAt(pop, at);
166
+ });
167
+ el.addEventListener("pointerdown", function (e) {
168
+ if (e.pointerType === "mouse" && e.button !== 2) return;
169
+ down = true;
170
+ if (e.pointerType !== "touch") return;
171
+ start = { x: e.clientX, y: e.clientY };
172
+ clearTimeout(timer);
173
+ timer = setTimeout(function () { pending = start; }, 500); // iOS sends no contextmenu
174
+ });
175
+ el.addEventListener("pointermove", function (e) {
176
+ if (start && Math.hypot(e.clientX - start.x, e.clientY - start.y) > 10) { clearTimeout(timer); start = null; }
177
+ });
178
+ function release() {
179
+ if (!down) return;
180
+ down = false; clearTimeout(timer); start = null;
181
+ if (pending) { var at = pending; pending = null; setTimeout(function () { openAt(pop, at); }, 0); }
182
+ }
183
+ addEventListener("pointerup", release, true); // on the window: the pointer may have left el
184
+ addEventListener("pointercancel", release, true);
185
+ }
186
+ document.addEventListener("click", function (e) {
187
+ var b = e.target.closest && e.target.closest("[popovertarget]");
188
+ if (b) invoker = b;
189
+ }, true);
190
+
191
+ // ---------------------------------------------------------------- sprites of things that start hidden
192
+ // A closed dialog or popover is not rendered, so its sprites would load only when it first opens
193
+ // (one empty frame). One off-screen probe fetches them with the page (.aer-preload in the CSS).
194
+ function preload(root) {
195
+ if (document.querySelector(".aer-preload") || !root.querySelector(".aer-dialog, .aer-popover, .aer-toaster")) return;
196
+ var i = document.createElement("i");
197
+ i.className = "aer-preload"; i.setAttribute("aria-hidden", "true");
198
+ document.body.appendChild(i);
199
+ }
200
+
101
201
  // ---------------------------------------------------------------- init
102
202
  function init(root) {
103
203
  root = root || document;
204
+ preload(root);
205
+ root.querySelectorAll(".aer-popover[popover]").forEach(function (p) { if (once(p, "aerBound")) bindPopover(p); });
206
+ root.querySelectorAll("[data-aer-menu]").forEach(function (el) { if (once(el, "aerCtxBound")) bindContext(el); });
104
207
  root.querySelectorAll(".aer-slider > input[type=range], .aer-compare > input[type=range]").forEach(function (input) {
105
208
  if (!once(input, "aerBound")) return;
106
209
  input.parentElement.setAttribute("data-aer-ready", "");
@@ -0,0 +1,100 @@
1
+ Aerolith Display is derived from Istok Web Bold by Andrey V. Panov. The outlines were made
2
+ heavier and each glyph was rendered in Bryce; it is a Modified Version under the SIL Open
3
+ Font License 1.1 below and does not use the Reserved Font Name "Istok".
4
+
5
+ Copyright (c) 2026, Immineal (colour glyphs).
6
+
7
+ Copyright (c) 2008-2012, 2014, Andrey V. Panov (panov@canopus.iacp.dvo.ru),
8
+ with Reserved Font Name Istok.
9
+
10
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
11
+ This license is copied below, and is also available with a FAQ at:
12
+ http://scripts.sil.org/OFL
13
+
14
+
15
+ -----------------------------------------------------------
16
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
17
+ -----------------------------------------------------------
18
+
19
+ PREAMBLE
20
+ The goals of the Open Font License (OFL) are to stimulate worldwide
21
+ development of collaborative font projects, to support the font creation
22
+ efforts of academic and linguistic communities, and to provide a free and
23
+ open framework in which fonts may be shared and improved in partnership
24
+ with others.
25
+
26
+ The OFL allows the licensed fonts to be used, studied, modified and
27
+ redistributed freely as long as they are not sold by themselves. The
28
+ fonts, including any derivative works, can be bundled, embedded,
29
+ redistributed and/or sold with any software provided that any reserved
30
+ names are not used by derivative works. The fonts and derivatives,
31
+ however, cannot be released under any other type of license. The
32
+ requirement for fonts to remain under this license does not apply
33
+ to any document created using the fonts or their derivatives.
34
+
35
+ DEFINITIONS
36
+ "Font Software" refers to the set of files released by the Copyright
37
+ Holder(s) under this license and clearly marked as such. This may
38
+ include source files, build scripts and documentation.
39
+
40
+ "Reserved Font Name" refers to any names specified as such after the
41
+ copyright statement(s).
42
+
43
+ "Original Version" refers to the collection of Font Software components as
44
+ distributed by the Copyright Holder(s).
45
+
46
+ "Modified Version" refers to any derivative made by adding to, deleting,
47
+ or substituting -- in part or in whole -- any of the components of the
48
+ Original Version, by changing formats or by porting the Font Software to a
49
+ new environment.
50
+
51
+ "Author" refers to any designer, engineer, programmer, technical
52
+ writer or other person who contributed to the Font Software.
53
+
54
+ PERMISSION & CONDITIONS
55
+ Permission is hereby granted, free of charge, to any person obtaining
56
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
57
+ redistribute, and sell modified and unmodified copies of the Font
58
+ Software, subject to the following conditions:
59
+
60
+ 1) Neither the Font Software nor any of its individual components,
61
+ in Original or Modified Versions, may be sold by itself.
62
+
63
+ 2) Original or Modified Versions of the Font Software may be bundled,
64
+ redistributed and/or sold with any software, provided that each copy
65
+ contains the above copyright notice and this license. These can be
66
+ included either as stand-alone text files, human-readable headers or
67
+ in the appropriate machine-readable metadata fields within text or
68
+ binary files as long as those fields can be easily viewed by the user.
69
+
70
+ 3) No Modified Version of the Font Software may use the Reserved Font
71
+ Name(s) unless explicit written permission is granted by the corresponding
72
+ Copyright Holder. This restriction only applies to the primary font name as
73
+ presented to the users.
74
+
75
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
76
+ Software shall not be used to promote, endorse or advertise any
77
+ Modified Version, except to acknowledge the contribution(s) of the
78
+ Copyright Holder(s) and the Author(s) or with their explicit written
79
+ permission.
80
+
81
+ 5) The Font Software, modified or unmodified, in part or in whole,
82
+ must be distributed entirely under this license, and must not be
83
+ distributed under any other license. The requirement for fonts to
84
+ remain under this license does not apply to any document created
85
+ using the Font Software.
86
+
87
+ TERMINATION
88
+ This license becomes null and void if any of the above conditions are
89
+ not met.
90
+
91
+ DISCLAIMER
92
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
93
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
94
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
95
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
96
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
97
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
98
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
99
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
100
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Copyright (c) 2008-2012, 2014, Andrey V. Panov (panov@canopus.iacp.dvo.ru),
2
+ with Reserved Font Name Istok.
3
+
4
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
5
+ This license is copied below, and is also available with a FAQ at:
6
+ http://scripts.sil.org/OFL
7
+
8
+
9
+ -----------------------------------------------------------
10
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11
+ -----------------------------------------------------------
12
+
13
+ PREAMBLE
14
+ The goals of the Open Font License (OFL) are to stimulate worldwide
15
+ development of collaborative font projects, to support the font creation
16
+ efforts of academic and linguistic communities, and to provide a free and
17
+ open framework in which fonts may be shared and improved in partnership
18
+ with others.
19
+
20
+ The OFL allows the licensed fonts to be used, studied, modified and
21
+ redistributed freely as long as they are not sold by themselves. The
22
+ fonts, including any derivative works, can be bundled, embedded,
23
+ redistributed and/or sold with any software provided that any reserved
24
+ names are not used by derivative works. The fonts and derivatives,
25
+ however, cannot be released under any other type of license. The
26
+ requirement for fonts to remain under this license does not apply
27
+ to any document created using the fonts or their derivatives.
28
+
29
+ DEFINITIONS
30
+ "Font Software" refers to the set of files released by the Copyright
31
+ Holder(s) under this license and clearly marked as such. This may
32
+ include source files, build scripts and documentation.
33
+
34
+ "Reserved Font Name" refers to any names specified as such after the
35
+ copyright statement(s).
36
+
37
+ "Original Version" refers to the collection of Font Software components as
38
+ distributed by the Copyright Holder(s).
39
+
40
+ "Modified Version" refers to any derivative made by adding to, deleting,
41
+ or substituting -- in part or in whole -- any of the components of the
42
+ Original Version, by changing formats or by porting the Font Software to a
43
+ new environment.
44
+
45
+ "Author" refers to any designer, engineer, programmer, technical
46
+ writer or other person who contributed to the Font Software.
47
+
48
+ PERMISSION & CONDITIONS
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
51
+ redistribute, and sell modified and unmodified copies of the Font
52
+ Software, subject to the following conditions:
53
+
54
+ 1) Neither the Font Software nor any of its individual components,
55
+ in Original or Modified Versions, may be sold by itself.
56
+
57
+ 2) Original or Modified Versions of the Font Software may be bundled,
58
+ redistributed and/or sold with any software, provided that each copy
59
+ contains the above copyright notice and this license. These can be
60
+ included either as stand-alone text files, human-readable headers or
61
+ in the appropriate machine-readable metadata fields within text or
62
+ binary files as long as those fields can be easily viewed by the user.
63
+
64
+ 3) No Modified Version of the Font Software may use the Reserved Font
65
+ Name(s) unless explicit written permission is granted by the corresponding
66
+ Copyright Holder. This restriction only applies to the primary font name as
67
+ presented to the users.
68
+
69
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70
+ Software shall not be used to promote, endorse or advertise any
71
+ Modified Version, except to acknowledge the contribution(s) of the
72
+ Copyright Holder(s) and the Author(s) or with their explicit written
73
+ permission.
74
+
75
+ 5) The Font Software, modified or unmodified, in part or in whole,
76
+ must be distributed entirely under this license, and must not be
77
+ distributed under any other license. The requirement for fonts to
78
+ remain under this license does not apply to any document created
79
+ using the Font Software.
80
+
81
+ TERMINATION
82
+ This license becomes null and void if any of the above conditions are
83
+ not met.
84
+
85
+ DISCLAIMER
86
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,91 @@
1
+ Copyright 2015, Microsoft Corporation (www.microsoft.com), with Reserved Font Name Selawik. All Rights Reserved. Selawik is a trademark of Microsoft Corporation in the United States and/or other countries.
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+ PREAMBLE
11
+ The goals of the Open Font License (OFL) are to stimulate worldwide
12
+ development of collaborative font projects, to support the font creation
13
+ efforts of academic and linguistic communities, and to provide a free and
14
+ open framework in which fonts may be shared and improved in partnership
15
+ with others.
16
+
17
+ The OFL allows the licensed fonts to be used, studied, modified and
18
+ redistributed freely as long as they are not sold by themselves. The
19
+ fonts, including any derivative works, can be bundled, embedded,
20
+ redistributed and/or sold with any software provided that any reserved
21
+ names are not used by derivative works. The fonts and derivatives,
22
+ however, cannot be released under any other type of license. The
23
+ requirement for fonts to remain under this license does not apply
24
+ to any document created using the fonts or their derivatives.
25
+
26
+ DEFINITIONS
27
+ "Font Software" refers to the set of files released by the Copyright
28
+ Holder(s) under this license and clearly marked as such. This may
29
+ include source files, build scripts and documentation.
30
+
31
+ "Reserved Font Name" refers to any names specified as such after the
32
+ copyright statement(s).
33
+
34
+ "Original Version" refers to the collection of Font Software components as
35
+ distributed by the Copyright Holder(s).
36
+
37
+ "Modified Version" refers to any derivative made by adding to, deleting,
38
+ or substituting -- in part or in whole -- any of the components of the
39
+ Original Version, by changing formats or by porting the Font Software to a
40
+ new environment.
41
+
42
+ "Author" refers to any designer, engineer, programmer, technical
43
+ writer or other person who contributed to the Font Software.
44
+
45
+ PERMISSION & CONDITIONS
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
48
+ redistribute, and sell modified and unmodified copies of the Font
49
+ Software, subject to the following conditions:
50
+
51
+ 1) Neither the Font Software nor any of its individual components,
52
+ in Original or Modified Versions, may be sold by itself.
53
+
54
+ 2) Original or Modified Versions of the Font Software may be bundled,
55
+ redistributed and/or sold with any software, provided that each copy
56
+ contains the above copyright notice and this license. These can be
57
+ included either as stand-alone text files, human-readable headers or
58
+ in the appropriate machine-readable metadata fields within text or
59
+ binary files as long as those fields can be easily viewed by the user.
60
+
61
+ 3) No Modified Version of the Font Software may use the Reserved Font
62
+ Name(s) unless explicit written permission is granted by the corresponding
63
+ Copyright Holder. This restriction only applies to the primary font name as
64
+ presented to the users.
65
+
66
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
67
+ Software shall not be used to promote, endorse or advertise any
68
+ Modified Version, except to acknowledge the contribution(s) of the
69
+ Copyright Holder(s) and the Author(s) or with their explicit written
70
+ permission.
71
+
72
+ 5) The Font Software, modified or unmodified, in part or in whole,
73
+ must be distributed entirely under this license, and must not be
74
+ distributed under any other license. The requirement for fonts to
75
+ remain under this license does not apply to any document created
76
+ using the Font Software.
77
+
78
+ TERMINATION
79
+ This license becomes null and void if any of the above conditions are
80
+ not met.
81
+
82
+ DISCLAIMER
83
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
84
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
85
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
86
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
87
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
88
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
89
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
90
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
91
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -4403,6 +4403,94 @@
4403
4403
  37
4404
4404
  ]
4405
4405
  },
4406
+ "logo-aqua": {
4407
+ "name": "logo-aqua",
4408
+ "box": [
4409
+ 60,
4410
+ 60,
4411
+ 276,
4412
+ 276
4413
+ ],
4414
+ "slice": null,
4415
+ "shape": [
4416
+ 60,
4417
+ 60,
4418
+ 276,
4419
+ 276
4420
+ ],
4421
+ "label": null,
4422
+ "text": null,
4423
+ "hit": [
4424
+ 36,
4425
+ 36
4426
+ ],
4427
+ "meta": {
4428
+ "sheet": "logo-aqua"
4429
+ },
4430
+ "size": [
4431
+ 36,
4432
+ 36
4433
+ ],
4434
+ "files": {
4435
+ "1": "sprites/logo-aqua@1x.png",
4436
+ "2": "sprites/logo-aqua@2x.png",
4437
+ "3": "sprites/logo-aqua@3x.png"
4438
+ },
4439
+ "outset": [
4440
+ 0,
4441
+ 0,
4442
+ 0,
4443
+ 0
4444
+ ],
4445
+ "shape_size": [
4446
+ 36,
4447
+ 36
4448
+ ]
4449
+ },
4450
+ "logo-liquid": {
4451
+ "name": "logo-liquid",
4452
+ "box": [
4453
+ 60,
4454
+ 60,
4455
+ 276,
4456
+ 276
4457
+ ],
4458
+ "slice": null,
4459
+ "shape": [
4460
+ 60,
4461
+ 60,
4462
+ 276,
4463
+ 276
4464
+ ],
4465
+ "label": null,
4466
+ "text": null,
4467
+ "hit": [
4468
+ 36,
4469
+ 36
4470
+ ],
4471
+ "meta": {
4472
+ "sheet": "logo-liquid"
4473
+ },
4474
+ "size": [
4475
+ 36,
4476
+ 36
4477
+ ],
4478
+ "files": {
4479
+ "1": "sprites/logo-liquid@1x.png",
4480
+ "2": "sprites/logo-liquid@2x.png",
4481
+ "3": "sprites/logo-liquid@3x.png"
4482
+ },
4483
+ "outset": [
4484
+ 0,
4485
+ 0,
4486
+ 0,
4487
+ 0
4488
+ ],
4489
+ "shape_size": [
4490
+ 36,
4491
+ 36
4492
+ ]
4493
+ },
4406
4494
  "orb-error": {
4407
4495
  "name": "orb-error",
4408
4496
  "box": [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aerolith",
3
- "version": "1.0.0",
4
- "description": "Frutiger Aero UI for the web. Every button, field and pane is a sprite rendered in Bryce 2 (1996), skinned onto native HTML elements.",
3
+ "version": "1.1.0",
4
+ "description": "Glossy buttons and real glass for the web, in the Frutiger Aero style. Every sprite rendered in Bryce 2 (1996) and laid over plain HTML.",
5
5
  "keywords": [
6
6
  "frutiger-aero", "css", "ui", "ui-library", "design-system", "skeuomorphism", "glassmorphism",
7
7
  "retro", "y2k", "aero", "bryce", "sprites", "dark-mode", "accessibility", "vanilla-css"
@@ -22,13 +22,15 @@
22
22
  "./aerolith.js": "./library/dist/aerolith.js",
23
23
  "./sprites/*": "./library/dist/sprites/*",
24
24
  "./sprites.json": "./library/dist/sprites.json",
25
+ "./fonts/*": "./library/dist/fonts/*",
25
26
  "./package.json": "./package.json"
26
27
  },
27
28
  "files": [
28
29
  "library/dist/aerolith.css",
29
30
  "library/dist/aerolith.js",
30
31
  "library/dist/sprites.json",
31
- "library/dist/sprites/"
32
+ "library/dist/sprites/",
33
+ "library/dist/fonts/"
32
34
  ],
33
35
  "sideEffects": ["*.css", "./library/dist/aerolith.js"]
34
36
  }