@junoput01/junoui 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/README.md +29 -0
- package/dist/css/juno.css +144 -21
- package/dist/icons/inline.js +7 -9
- package/dist/icons/install.js +26 -0
- package/docs/browser-support.md +173 -0
- package/docs/getting-started.md +22 -0
- package/docs/icon-subsetting.md +29 -0
- package/docs/ios-conformance.md +206 -4
- package/docs/layout.md +35 -0
- package/package.json +11 -2
- package/src/css/base.css +79 -20
- package/src/css/components/input.css +12 -0
- package/src/css/components/pillbar.css +1 -1
- package/src/css/overrides.css +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 71c069c: Two primitives stop encoding junoui's own dimensions and start exposing the
|
|
8
|
+
derivation, so a consumer that parameterizes them stays correct.
|
|
9
|
+
|
|
10
|
+
- **`junoui/icons/install`** — the sprite injector without the sprite (~1 kB).
|
|
11
|
+
A consumer that subsets with `junoui/subset` can now keep the same-document
|
|
12
|
+
injection Safari requires without pulling the 66-symbol payload that
|
|
13
|
+
`junoui/icons/inline` carries; both share one id-guarded holder, so mixing
|
|
14
|
+
them cannot produce two. `icons/inline` is unchanged for everyone else.
|
|
15
|
+
- **`--juno-dock-clearance` / `--juno-pillbar-clearance` are derived**, from
|
|
16
|
+
new published parts: `--juno-dock-h`, `--juno-pillbar-h` and
|
|
17
|
+
`--juno-dock-clearance-scale`. They were constants that promised to track the
|
|
18
|
+
dock's geometry and did not — past a 58px bubble they reserved less than the
|
|
19
|
+
pill's own height plus its margin, hiding content under the dock. Values at
|
|
20
|
+
the default 44px bubble change from 92px to 86px (dock) and 72px to 78px
|
|
21
|
+
(pillbar), both now equal to what the control actually measures plus its
|
|
22
|
+
margin and a breathing gap.
|
|
23
|
+
|
|
24
|
+
- 0ee41a8: Declare the browser-support baseline, and guard the one gap that is functional.
|
|
25
|
+
|
|
26
|
+
New `docs/browser-support.md`: the supported floor (Safari/iOS 17.5, Chrome/Edge
|
|
27
|
+
117, Firefox 129), the hard floor below which things break (17.0 / 114 / 125),
|
|
28
|
+
and a per-feature audit of the built bundle with a degrade-vs-break verdict on
|
|
29
|
+
each. `package.json` now carries a matching `browserslist`; README and
|
|
30
|
+
getting-started state the floor.
|
|
31
|
+
|
|
32
|
+
`base.css` ships one `@supports not selector(:popover-open)` guard. Below Safari
|
|
33
|
+
17.0 the Popover API is absent, and because the UA rule that hides a closed
|
|
34
|
+
popover is absent with it, `.juno-menu` and `.juno-popover` were rendering as
|
|
35
|
+
invisible fixed panels that swallowed taps. The guard hides them instead —
|
|
36
|
+
absent beats invisibly-present, and apps can branch on
|
|
37
|
+
`CSS.supports('selector(:popover-open)')`. Guards are for functional failures
|
|
38
|
+
only; cosmetic gaps (missing entry animations, unanchored placement) are
|
|
39
|
+
documented, not wrapped.
|
|
40
|
+
|
|
41
|
+
`docs/ios-conformance.md` gains the viewport-unit decision it was missing:
|
|
42
|
+
`dvh` stays at both `.juno-app-shell` and `.juno-drawer`, with the reasoning,
|
|
43
|
+
what each option costs at the moment browser chrome retracts, and a rule for
|
|
44
|
+
applying the choice to a new component. It also now records the iOS Home-Screen
|
|
45
|
+
standalone letterbox — iOS sizes the window from the document's resting
|
|
46
|
+
scrollability at launch — which is why `base.css` carries the standalone unlock.
|
|
47
|
+
|
|
48
|
+
Docs and defaults only; no token or component API changed.
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- cacdb21: Fix: the 16px `.juno-input` font floor on touch never applied.
|
|
53
|
+
|
|
54
|
+
The rule lived in `base.css`'s `@media (pointer: coarse)` block, but a media
|
|
55
|
+
query adds no specificity — so `components/input.css`'s own
|
|
56
|
+
`.juno-input { font-size: var(--juno-font-size-14) }`, same 0,1,0 selector and
|
|
57
|
+
later in the bundle, won every time. On a coarse pointer the field measured
|
|
58
|
+
14px, i.e. exactly the condition the floor exists to avoid (iOS Safari zooming
|
|
59
|
+
the page onto a focused sub-16px field). The rule now lives in `input.css`,
|
|
60
|
+
after the declaration it has to beat.
|
|
61
|
+
|
|
62
|
+
Found by the new coarse-pointer visual-regression project (20260815-006): the
|
|
63
|
+
suite ran only `Desktop Chrome`, where `(pointer: coarse)` never matches, so
|
|
64
|
+
nothing had ever exercised the rule.
|
|
65
|
+
|
|
66
|
+
- da03666: Fix: the forced-colors (Windows High Contrast) border never applied.
|
|
67
|
+
|
|
68
|
+
`@media (forced-colors: active) { .juno-badge, .juno-btn, .juno-card,
|
|
69
|
+
.juno-readout { border: 1px solid CanvasText } }` lived in `base.css`, which the
|
|
70
|
+
bundler emits before `components/`. A media query adds no specificity, so each
|
|
71
|
+
component's own `border` declaration won on source order alone. Measured under
|
|
72
|
+
emulated forced-colors: `.juno-badge` computed a transparent border — it sets
|
|
73
|
+
`forced-color-adjust: none` to keep its status fill, and that opt-out also
|
|
74
|
+
disables the UA repaint that was silently rescuing `.juno-btn` and `.juno-card`.
|
|
75
|
+
|
|
76
|
+
New `src/css/overrides.css`, bundled last, is where cross-cutting `@media` /
|
|
77
|
+
`@supports` gates live now — after everything they guard. Same reasoning the
|
|
78
|
+
bundler already applied to `utilities.css`.
|
|
79
|
+
|
|
80
|
+
- 2ec7cff: Stop publishing junoui's internal release process to consumers: the pre-release
|
|
81
|
+
consumer gate document moves from `docs/release-gate.md` to `RELEASING.md` at
|
|
82
|
+
the repo root, beside CONTRIBUTING.md, which has never shipped. `files` carries
|
|
83
|
+
`docs` wholesale, so the rule is now positional — `docs/` IS the published
|
|
84
|
+
manual, and contributor or process documents live at the root. The published
|
|
85
|
+
surface loses one file (204 → 203) and no consumer-facing content changes.
|
|
86
|
+
|
|
3
87
|
## 0.5.0
|
|
4
88
|
|
|
5
89
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -61,6 +61,21 @@ build-time sRGB hex (identical rendering).
|
|
|
61
61
|
| ---------------- | -------------------------- | ------------------------------- |
|
|
62
62
|
| CSS, SCSS, JS/TS | Android (xml), iOS (Swift) | Flutter (Dart), JSON (W3C DTCG) |
|
|
63
63
|
|
|
64
|
+
## Browser support
|
|
65
|
+
|
|
66
|
+
**Supported floor: Safari / iOS 17.5, Chrome / Edge 117, Firefox 129.**
|
|
67
|
+
Below **17.0 / 114 / 125** (the `browserslist` in `package.json`) the Popover
|
|
68
|
+
API is absent and the overlay surfaces — menu, popover, tooltip, and the
|
|
69
|
+
pillbar's overflow slot — stop working; junoui ships an `@supports` guard so
|
|
70
|
+
they are absent rather than invisibly blocking taps. Between the two floors
|
|
71
|
+
everything functions and entry animations are missing. Full fidelity, including
|
|
72
|
+
anchored placement for those surfaces, wants Safari 26.
|
|
73
|
+
|
|
74
|
+
CSS failures are silent — an unsupported at-rule is dropped, never reported —
|
|
75
|
+
so the degrade-vs-break audit, the version table and the guard rule live in
|
|
76
|
+
**[browser-support.md](./docs/browser-support.md)**. Read it before adding a
|
|
77
|
+
feature newer than the floor.
|
|
78
|
+
|
|
64
79
|
## Documentation
|
|
65
80
|
|
|
66
81
|
| | |
|
|
@@ -73,8 +88,10 @@ build-time sRGB hex (identical rendering).
|
|
|
73
88
|
| [Boot shell](./docs/boot-shell.md) | Fast first paint: pre-bundle shell, lazy screens, warming |
|
|
74
89
|
| [Accessibility](./docs/accessibility.md) | WCAG 2.2 + WAI-ARIA references, focus, motion, targets, RTL |
|
|
75
90
|
| [iOS conformance](./docs/ios-conformance.md) | Sourced iOS metrics — safe areas, tap targets, viewport units |
|
|
91
|
+
| [Browser support](./docs/browser-support.md) | The supported floor, what degrades below it, what breaks |
|
|
76
92
|
| [Token reference](./docs/tokens-reference.md) | Every token + value + platform name (generated) |
|
|
77
93
|
| [Components](./docs/components/README.md) | 30+ — forms, overlays, table, alerts, tabs, icons, nav, more |
|
|
94
|
+
| [Release gate](./RELEASING.md) | The consumer build every release candidate must pass |
|
|
78
95
|
| [Contributing](./CONTRIBUTING.md) | Add tokens/components, lint, test, release |
|
|
79
96
|
| [Roadmap](./docs/roadmap.md) | Missing capabilities, prioritised |
|
|
80
97
|
|
|
@@ -116,6 +133,18 @@ remain — publishes `@junoput01/junoui` to npm automatically. No manual `npm ru
|
|
|
116
133
|
version` step. Bump by the token contract: remove/rename a token or class → major,
|
|
117
134
|
additive → minor, fix → patch. See [CONTRIBUTING](./CONTRIBUTING.md#releasing).
|
|
118
135
|
|
|
136
|
+
One step is **not** automated, on purpose. Before merging the version PR, run
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
npm run gate:consumer
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
which packs the candidate, installs that tarball into a throwaway checkout of a real
|
|
143
|
+
consumer, and runs the consumer's typecheck, tests and production build against it. A
|
|
144
|
+
red gate blocks the release. It is a local step because the consumer lives on the dev
|
|
145
|
+
box, not in this repo's CI — [RELEASING.md](./RELEASING.md) explains
|
|
146
|
+
what it asserts and what it would take to move it into CI.
|
|
147
|
+
|
|
119
148
|
## License
|
|
120
149
|
|
|
121
150
|
MIT
|
package/dist/css/juno.css
CHANGED
|
@@ -370,13 +370,48 @@
|
|
|
370
370
|
/* Floating-nav scroll clearance — a page/scroller that a fixed dock or
|
|
371
371
|
pillbar floats over reserves this much room at its foot so the last row
|
|
372
372
|
clears the overlay. Consumers write `padding-block-end:
|
|
373
|
-
var(--juno-dock-clearance)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
373
|
+
var(--juno-dock-clearance)`. Web-only (they carry env()), so they live in
|
|
374
|
+
the CSS layer, not the cross-platform token set.
|
|
375
|
+
|
|
376
|
+
DERIVED, not constant. These used to be `space-72 + space-20 + env()`,
|
|
377
|
+
which promised in this very comment to "stay correct when the dock
|
|
378
|
+
geometry changes" and could not: the floating pill measures 62px with the
|
|
379
|
+
default 44px bubble, and junoui explicitly invites a consumer to
|
|
380
|
+
parameterize that bubble (--juno-size-tap-comfortable). Past a 58px bubble
|
|
381
|
+
the old constant reserved LESS than the pill's own height plus its margin
|
|
382
|
+
— content hid under the dock, silently, on exactly the consumers who took
|
|
383
|
+
the invitation (20260815-026).
|
|
384
|
+
|
|
385
|
+
So the parts are named and the arithmetic is visible:
|
|
386
|
+
--juno-dock-h the pill's laid-out height: the bubble, the item's
|
|
387
|
+
padding-block, the pill's own padding, its border.
|
|
388
|
+
* -clearance-scale consumers that SHRINK the bar while scrolling
|
|
389
|
+
reserve at the small size, not the live one — a
|
|
390
|
+
reservation that tracks a value which changes after
|
|
391
|
+
the last scroll event relayouts content under a
|
|
392
|
+
finger that has already stopped moving. Default 1.
|
|
393
|
+
+ space-16 + env() the pill's own bottom margin (dock.css), additive
|
|
394
|
+
because a floating element sits OFF the edge —
|
|
395
|
+
docs/ios-conformance.md, "max() vs addition".
|
|
396
|
+
+ space-8 breathing room, so the last row is not flush. */
|
|
397
|
+
--juno-dock-h: calc(
|
|
398
|
+
var(--juno-size-tap-comfortable) + 4 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
399
|
+
);
|
|
400
|
+
--juno-dock-clearance-scale: 1;
|
|
401
|
+
--juno-dock-clearance: calc(
|
|
402
|
+
var(--juno-dock-h) * var(--juno-dock-clearance-scale) + var(--juno-space-16) +
|
|
403
|
+
var(--juno-space-8) + env(safe-area-inset-bottom, 0px)
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
/* The pillbar is the same shape one size down: no bubble, so its height is
|
|
407
|
+
the tap target plus its own padding and border. */
|
|
408
|
+
--juno-pillbar-h: calc(
|
|
409
|
+
var(--juno-size-tap-comfortable) + 2 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
410
|
+
);
|
|
411
|
+
--juno-pillbar-clearance: calc(
|
|
412
|
+
var(--juno-pillbar-h) * var(--juno-dock-clearance-scale) + var(--juno-space-16) +
|
|
413
|
+
var(--juno-space-8) + env(safe-area-inset-bottom, 0px)
|
|
414
|
+
);
|
|
380
415
|
|
|
381
416
|
/* JS-readable motion contract. CSS-only `prefers-reduced-motion` never reaches
|
|
382
417
|
imperative JS (scrollTo/scrollIntoView smooth behavior, rAF-driven transforms,
|
|
@@ -478,8 +513,11 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
478
513
|
is harmless either way: hold text-entry controls (.juno-input covers input,
|
|
479
514
|
textarea and the select's inner control) at a 16px floor on touch. max()
|
|
480
515
|
keeps the floor even under a scaled-down --juno-font-scale, and still grows
|
|
481
|
-
when scaled up.
|
|
482
|
-
|
|
516
|
+
when scaled up.
|
|
517
|
+
THE RULE ITSELF LIVES IN components/input.css, not here: a media query adds
|
|
518
|
+
no specificity, so this file's `.juno-input` would lose to input.css's own
|
|
519
|
+
`.juno-input` font-size later in the bundle. It did, silently, until
|
|
520
|
+
20260815-006's coarse-pointer project measured it. */
|
|
483
521
|
|
|
484
522
|
/* Kill the UA tap-highlight square on the interactive surfaces so it never
|
|
485
523
|
flashes past a rounded control on tap. Consumers were adding this by hand
|
|
@@ -580,17 +618,11 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
580
618
|
:root { --juno-border: var(--juno-border-strong); }
|
|
581
619
|
}
|
|
582
620
|
|
|
583
|
-
/* Windows High Contrast / forced-colors:
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
.juno-badge,
|
|
589
|
-
.juno-btn,
|
|
590
|
-
.juno-card,
|
|
591
|
-
.juno-readout { border: 1px solid CanvasText; }
|
|
592
|
-
.juno-badge { forced-color-adjust: none; } /* keep status fill meaningful */
|
|
593
|
-
}
|
|
621
|
+
/* Windows High Contrast / forced-colors: the opt-in lives in
|
|
622
|
+
overrides.css, NOT here. A media query adds no specificity, and this file is
|
|
623
|
+
bundled BEFORE components/, so `.juno-btn { border: … }` in button.css beat
|
|
624
|
+
the forced-colors border every time — same 0,1,0 selector, later in the
|
|
625
|
+
bundle. See src/css/overrides.css and ticket 20260815-029. */
|
|
594
626
|
|
|
595
627
|
/* ── iOS standalone letterbox unlock ─────────────────────────────────────────
|
|
596
628
|
iOS WebKit (observed through 26.6, iPhone 16 Pro, iOS 18.7) sizes a
|
|
@@ -640,6 +672,33 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
640
672
|
}
|
|
641
673
|
}
|
|
642
674
|
|
|
675
|
+
/* ── Popover API absent (Safari/iOS < 17.0) ──────────────────────────────────
|
|
676
|
+
junoui's overlay surfaces are built on the native Popover API. Without it
|
|
677
|
+
`popovertarget` does nothing, so they cannot open — an inconvenience. The
|
|
678
|
+
part that is a BUG is that the UA rule which hides a closed popover
|
|
679
|
+
(`[popover]:not(:popover-open) { display: none }`) does not exist either, so
|
|
680
|
+
the panel participates in layout: .juno-menu and .juno-popover are
|
|
681
|
+
`position: fixed` with `opacity: 0` and no pointer-events reset, i.e. a
|
|
682
|
+
256-280px INVISIBLE panel parked at its static position, swallowing taps on
|
|
683
|
+
whatever it covers. (.juno-tooltip__bubble escapes it — it inherits
|
|
684
|
+
`pointer-events: none` from the CSS-only tooltip rule — and is listed anyway
|
|
685
|
+
so the set matches the docs.)
|
|
686
|
+
|
|
687
|
+
So: hide them. Absent beats invisibly-present. Apps detect the same condition
|
|
688
|
+
with CSS.supports('selector(:popover-open)') and render a fallback.
|
|
689
|
+
|
|
690
|
+
@supports selector() is itself Safari 14.1+; below that this condition is
|
|
691
|
+
invalid, the block is skipped, and behaviour is what it is today. Fail-open,
|
|
692
|
+
and far below any supported floor. Guards live here rather than per component
|
|
693
|
+
so the next popover-based component inherits it — see docs/browser-support.md
|
|
694
|
+
for the rule about when a feature gap earns a guard at all (functional
|
|
695
|
+
failures only; a missing animation does not get one). */
|
|
696
|
+
@supports not selector(:popover-open) {
|
|
697
|
+
.juno-menu[popover],
|
|
698
|
+
.juno-popover[popover],
|
|
699
|
+
.juno-tooltip__bubble[popover] { display: none; }
|
|
700
|
+
}
|
|
701
|
+
|
|
643
702
|
/* ════════════════════════════════════════════════════════════════════
|
|
644
703
|
* junoui — layout layer (intrinsic & responsive)
|
|
645
704
|
*
|
|
@@ -2522,6 +2581,18 @@ button.juno-chip {
|
|
|
2522
2581
|
color: var(--juno-data);
|
|
2523
2582
|
}
|
|
2524
2583
|
|
|
2584
|
+
/* The iOS focus-zoom floor, and it has to live HERE, not in base.css's
|
|
2585
|
+
`@media (pointer: coarse)` block where it was written. A media query adds no
|
|
2586
|
+
specificity, so `.juno-input { font-size: var(--juno-font-size-14) }` above —
|
|
2587
|
+
same 0,1,0 selector, later in the bundle (base.css sorts before components/)
|
|
2588
|
+
— simply won it, and the floor never applied on any touch device. Nothing
|
|
2589
|
+
caught it because the suite had no coarse-pointer project until 20260815-006
|
|
2590
|
+
added one; the numeric assertion in test/visual/tap-targets.spec.mjs is what
|
|
2591
|
+
surfaced it. See base.css for the sourcing caveat on the behavior itself. */
|
|
2592
|
+
@media (pointer: coarse) {
|
|
2593
|
+
.juno-input { font-size: max(16px, var(--juno-font-size-16)); }
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2525
2596
|
.juno-input--sans { font-family: var(--juno-font-family-sans); }
|
|
2526
2597
|
|
|
2527
2598
|
.juno-input::placeholder { color: var(--juno-muted); }
|
|
@@ -3734,7 +3805,7 @@ button.juno-list__row:disabled {
|
|
|
3734
3805
|
<svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-dots-three" /></svg>
|
|
3735
3806
|
</button>
|
|
3736
3807
|
<div class="juno-pillbar__tray">
|
|
3737
|
-
<div
|
|
3808
|
+
<div>\3c !-- single wrapper, any element -->
|
|
3738
3809
|
…the usual __item / __sep / __input children…
|
|
3739
3810
|
</div>
|
|
3740
3811
|
</div>
|
|
@@ -5794,3 +5865,55 @@ button.juno-seg__opt:disabled {
|
|
|
5794
5865
|
white-space: nowrap;
|
|
5795
5866
|
border: 0;
|
|
5796
5867
|
}
|
|
5868
|
+
|
|
5869
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
5870
|
+
* Overrides — cross-cutting gates, bundled LAST
|
|
5871
|
+
* ════════════════════════════════════════════════════════════════════
|
|
5872
|
+
* A @media or @supports block adds NO specificity. So a gate written in
|
|
5873
|
+
* base.css — which the bundler emits before components/ — loses to any
|
|
5874
|
+
* component rule declaring the same property on the same selector, purely
|
|
5875
|
+
* on source order, and loses SILENTLY: the gate looks right in the file it
|
|
5876
|
+
* was written in, and there is no error anywhere.
|
|
5877
|
+
*
|
|
5878
|
+
* Four instances of that shipped before this file existed (2026-08-15):
|
|
5879
|
+
* - .juno-input's 16px iOS focus-zoom floor, beaten by input.css. The floor
|
|
5880
|
+
* never applied on any touch device, in any release (20260815-011).
|
|
5881
|
+
* - the forced-colors border below, beaten by button.css / badge.css /
|
|
5882
|
+
* card.css (20260815-029).
|
|
5883
|
+
* - the Popover fallback, which happens to win today only because nothing
|
|
5884
|
+
* later declares `display` on those selectors — pinned by a test rather
|
|
5885
|
+
* than by structure (20260815-013).
|
|
5886
|
+
*
|
|
5887
|
+
* So: gates whose whole job is to BEAT a component default live here, after
|
|
5888
|
+
* everything they guard. Same reasoning the bundler already applies to
|
|
5889
|
+
* utilities.css (role helpers must outrank component defaults) — see
|
|
5890
|
+
* scripts/bundle-css.mjs.
|
|
5891
|
+
*
|
|
5892
|
+
* What does NOT belong here: a gate that only has to beat the UA or another
|
|
5893
|
+
* rule in its own file. Keep those next to what they modify; this file is for
|
|
5894
|
+
* the cross-file case, or it becomes a dumping ground and the cascade gets
|
|
5895
|
+
* harder to read rather than easier.
|
|
5896
|
+
*
|
|
5897
|
+
* A build test (test/build.test.mjs) asserts the bundle contains no gated
|
|
5898
|
+
* declaration that a later ungated rule overrides at equal-or-lower
|
|
5899
|
+
* specificity. It asserts an EMPTY SET — no allowlist, because an allowlist
|
|
5900
|
+
* is where violations go to be forgotten.
|
|
5901
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
5902
|
+
|
|
5903
|
+
/* Windows High Contrast / forced-colors: opt into the system palette so
|
|
5904
|
+
borders and focus stay visible when the user's palette replaces ours.
|
|
5905
|
+
|
|
5906
|
+
.juno-badge additionally sets `forced-color-adjust: none` to keep its status
|
|
5907
|
+
fill meaningful — and that opt-out is why the badge was the one element where
|
|
5908
|
+
the lost cascade actually reached the screen: it also disables the UA's own
|
|
5909
|
+
repaint, which was silently rescuing .juno-btn and .juno-card. Measured, not
|
|
5910
|
+
inferred (20260815-029). */
|
|
5911
|
+
@media (forced-colors: active) {
|
|
5912
|
+
*:focus-visible { outline-color: Highlight; }
|
|
5913
|
+
|
|
5914
|
+
.juno-badge,
|
|
5915
|
+
.juno-btn,
|
|
5916
|
+
.juno-card,
|
|
5917
|
+
.juno-readout { border: 1px solid CanvasText; }
|
|
5918
|
+
.juno-badge { forced-color-adjust: none; } /* keep status fill meaningful */
|
|
5919
|
+
}
|
package/dist/icons/inline.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
// junoui — inline icon sprite injector. Generated; do not edit.
|
|
1
|
+
// junoui — inline icon sprite injector (full set). Generated; do not edit.
|
|
2
2
|
// Fixes Safari dropping external <use href="file.svg#id"> refs: this injects
|
|
3
3
|
// the sprite into the document so you reference icons same-document instead:
|
|
4
4
|
// import '@junoput01/junoui/icons/inline';
|
|
5
5
|
// <svg class="juno-icon"><use href="#juno-i-gear" /></svg>
|
|
6
|
+
// Shipping a SUBSET instead? Import junoui/icons/install and pass it, so this
|
|
7
|
+
// module's 25 kB of symbols never enters your bundle.
|
|
8
|
+
import { installSprite } from './install.js';
|
|
9
|
+
|
|
6
10
|
const SPRITE = "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display:none\" fill=\"currentColor\">\n <symbol id=\"juno-i-arrow-clockwise\" viewBox=\"0 0 256 256\"><path d=\"M244,56v48a12,12,0,0,1-12,12H184a12,12,0,1,1,0-24H201.1l-19-17.38c-.13-.12-.26-.24-.38-.37A76,76,0,1,0,127,204h1a75.53,75.53,0,0,0,52.15-20.72,12,12,0,0,1,16.49,17.45A99.45,99.45,0,0,1,128,228h-1.37A100,100,0,1,1,198.51,57.06L220,76.72V56a12,12,0,0,1,24,0Z\"/></symbol>\n <symbol id=\"juno-i-arrow-down\" viewBox=\"0 0 256 256\"><path d=\"M208.49,152.49l-72,72a12,12,0,0,1-17,0l-72-72a12,12,0,0,1,17-17L116,187V40a12,12,0,0,1,24,0V187l51.51-51.52a12,12,0,0,1,17,17Z\"/></symbol>\n <symbol id=\"juno-i-arrow-left\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H69l51.52,51.51a12,12,0,0,1-17,17l-72-72a12,12,0,0,1,0-17l72-72a12,12,0,0,1,17,17L69,116H216A12,12,0,0,1,228,128Z\"/></symbol>\n <symbol id=\"juno-i-arrow-right\" viewBox=\"0 0 256 256\"><path d=\"M224.49,136.49l-72,72a12,12,0,0,1-17-17L187,140H40a12,12,0,0,1,0-24H187L135.51,64.48a12,12,0,0,1,17-17l72,72A12,12,0,0,1,224.49,136.49Z\"/></symbol>\n <symbol id=\"juno-i-arrow-up\" viewBox=\"0 0 256 256\"><path d=\"M208.49,120.49a12,12,0,0,1-17,0L140,69V216a12,12,0,0,1-24,0V69L64.49,120.49a12,12,0,0,1-17-17l72-72a12,12,0,0,1,17,0l72,72A12,12,0,0,1,208.49,120.49Z\"/></symbol>\n <symbol id=\"juno-i-arrows-clockwise\" viewBox=\"0 0 256 256\"><path d=\"M228,48V96a12,12,0,0,1-12,12H168a12,12,0,0,1,0-24h19l-7.8-7.8a75.55,75.55,0,0,0-53.32-22.26h-.43A75.49,75.49,0,0,0,72.39,75.57,12,12,0,1,1,55.61,58.41a99.38,99.38,0,0,1,69.87-28.47H126A99.42,99.42,0,0,1,196.2,59.23L204,67V48a12,12,0,0,1,24,0ZM183.61,180.43a75.49,75.49,0,0,1-53.09,21.63h-.43A75.55,75.55,0,0,1,76.77,179.8L69,172H88a12,12,0,0,0,0-24H40a12,12,0,0,0-12,12v48a12,12,0,0,0,24,0V189l7.8,7.8A99.42,99.42,0,0,0,130,226.06h.56a99.38,99.38,0,0,0,69.87-28.47,12,12,0,0,0-16.78-17.16Z\"/></symbol>\n <symbol id=\"juno-i-arrows-out\" viewBox=\"0 0 256 256\"><path d=\"M220,48V96a12,12,0,0,1-24,0V77l-35.51,35.52a12,12,0,0,1-17-17L179,60H160a12,12,0,0,1,0-24h48A12,12,0,0,1,220,48ZM95.51,143.51,60,179V160a12,12,0,0,0-24,0v48a12,12,0,0,0,12,12H96a12,12,0,0,0,0-24H77l35.52-35.51a12,12,0,0,0-17-17ZM208,148a12,12,0,0,0-12,12v19l-35.51-35.52a12,12,0,0,0-17,17L179,196H160a12,12,0,0,0,0,24h48a12,12,0,0,0,12-12V160A12,12,0,0,0,208,148ZM77,60H96a12,12,0,0,0,0-24H48A12,12,0,0,0,36,48V96a12,12,0,0,0,24,0V77l35.51,35.52a12,12,0,0,0,17-17Z\"/></symbol>\n <symbol id=\"juno-i-bell\" viewBox=\"0 0 256 256\"><path d=\"M225.29,165.93C216.61,151,212,129.57,212,104a84,84,0,0,0-168,0c0,25.58-4.59,47-13.27,61.93A20.08,20.08,0,0,0,30.66,186,19.77,19.77,0,0,0,48,196H84.18a44,44,0,0,0,87.64,0H208a19.77,19.77,0,0,0,17.31-10A20.08,20.08,0,0,0,225.29,165.93ZM128,212a20,20,0,0,1-19.6-16h39.2A20,20,0,0,1,128,212ZM54.66,172C63.51,154,68,131.14,68,104a60,60,0,0,1,120,0c0,27.13,4.48,50,13.33,68Z\"/></symbol>\n <symbol id=\"juno-i-calendar-blank\" viewBox=\"0 0 256 256\"><path d=\"M208,28H188V24a12,12,0,0,0-24,0v4H92V24a12,12,0,0,0-24,0v4H48A20,20,0,0,0,28,48V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V48A20,20,0,0,0,208,28ZM68,52a12,12,0,0,0,24,0h72a12,12,0,0,0,24,0h16V76H52V52ZM52,204V100H204V204Z\"/></symbol>\n <symbol id=\"juno-i-caret-down\" viewBox=\"0 0 256 256\"><path d=\"M216.49,104.49l-80,80a12,12,0,0,1-17,0l-80-80a12,12,0,0,1,17-17L128,159l71.51-71.52a12,12,0,0,1,17,17Z\"/></symbol>\n <symbol id=\"juno-i-caret-left\" viewBox=\"0 0 256 256\"><path d=\"M168.49,199.51a12,12,0,0,1-17,17l-80-80a12,12,0,0,1,0-17l80-80a12,12,0,0,1,17,17L97,128Z\"/></symbol>\n <symbol id=\"juno-i-caret-right\" viewBox=\"0 0 256 256\"><path d=\"M184.49,136.49l-80,80a12,12,0,0,1-17-17L159,128,87.51,56.49a12,12,0,1,1,17-17l80,80A12,12,0,0,1,184.49,136.49Z\"/></symbol>\n <symbol id=\"juno-i-caret-up\" viewBox=\"0 0 256 256\"><path d=\"M216.49,168.49a12,12,0,0,1-17,0L128,97,56.49,168.49a12,12,0,0,1-17-17l80-80a12,12,0,0,1,17,0l80,80A12,12,0,0,1,216.49,168.49Z\"/></symbol>\n <symbol id=\"juno-i-chat-circle\" viewBox=\"0 0 256 256\"><path d=\"M128,20A108,108,0,0,0,31.85,177.23L21,209.66A20,20,0,0,0,46.34,235l32.43-10.81A108,108,0,1,0,128,20Zm0,192a84,84,0,0,1-42.06-11.27,12,12,0,0,0-6-1.62,12.1,12.1,0,0,0-3.8.62l-29.79,9.93,9.93-29.79a12,12,0,0,0-1-9.81A84,84,0,1,1,128,212Z\"/></symbol>\n <symbol id=\"juno-i-check-circle\" viewBox=\"0 0 256 256\"><path d=\"M176.49,95.51a12,12,0,0,1,0,17l-56,56a12,12,0,0,1-17,0l-24-24a12,12,0,1,1,17-17L112,143l47.51-47.52A12,12,0,0,1,176.49,95.51ZM236,128A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Z\"/></symbol>\n <symbol id=\"juno-i-check\" viewBox=\"0 0 256 256\"><path d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/></symbol>\n <symbol id=\"juno-i-clock\" viewBox=\"0 0 256 256\"><path d=\"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm68-84a12,12,0,0,1-12,12H128a12,12,0,0,1-12-12V72a12,12,0,0,1,24,0v44h44A12,12,0,0,1,196,128Z\"/></symbol>\n <symbol id=\"juno-i-cloud-arrow-down\" viewBox=\"0 0 256 256\"><path d=\"M192.49,167.51a12,12,0,0,1,0,17l-32,32a12,12,0,0,1-17,0l-32-32a12,12,0,1,1,17-17L140,179V128a12,12,0,0,1,24,0v51l11.51-11.52A12,12,0,0,1,192.49,167.51ZM160,36A92.08,92.08,0,0,0,79,84.37,68,68,0,1,0,72,220H84a12,12,0,0,0,0-24H72a44,44,0,0,1-1.81-87.95A91.7,91.7,0,0,0,68,128a12,12,0,0,0,24,0,68,68,0,0,1,136,0,67.27,67.27,0,0,1-7.25,30.59,12,12,0,1,0,21.42,10.82A91.08,91.08,0,0,0,252,128,92.1,92.1,0,0,0,160,36Z\"/></symbol>\n <symbol id=\"juno-i-cloud-slash\" viewBox=\"0 0 256 256\"><path d=\"M56.88,31.93A12,12,0,1,0,39.12,48.07L71.79,84A68,68,0,0,0,72,220h88a91.26,91.26,0,0,0,30.66-5.24l8.46,9.31a12,12,0,0,0,17.76-16.14ZM160,196H72a44,44,0,0,1-1.8-87.95A91.91,91.91,0,0,0,68,128a12,12,0,0,0,24,0,68.22,68.22,0,0,1,2.66-18.84l77.88,85.67A68.67,68.67,0,0,1,160,196Zm92-68a91.32,91.32,0,0,1-17.53,54,12,12,0,1,1-19.41-14.11,68,68,0,0,0-89.57-98.53,12,12,0,0,1-12.2-20.66A92,92,0,0,1,252,128Z\"/></symbol>\n <symbol id=\"juno-i-cloud\" viewBox=\"0 0 256 256\"><path d=\"M160,36A92.09,92.09,0,0,0,79,84.36,68,68,0,1,0,72,220h88a92,92,0,0,0,0-184Zm0,160H72a44,44,0,0,1-1.82-88A91.86,91.86,0,0,0,68,128a12,12,0,0,0,24,0,68,68,0,1,1,68,68Z\"/></symbol>\n <symbol id=\"juno-i-copy\" viewBox=\"0 0 256 256\"><path d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/></symbol>\n <symbol id=\"juno-i-cpu\" viewBox=\"0 0 256 256\"><path d=\"M156,88H100a12,12,0,0,0-12,12v56a12,12,0,0,0,12,12h56a12,12,0,0,0,12-12V100A12,12,0,0,0,156,88Zm-12,56H112V112h32Zm88-4H220V116h12a12,12,0,0,0,0-24H220V56a20,20,0,0,0-20-20H164V24a12,12,0,0,0-24,0V36H116V24a12,12,0,0,0-24,0V36H56A20,20,0,0,0,36,56V92H24a12,12,0,0,0,0,24H36v24H24a12,12,0,0,0,0,24H36v36a20,20,0,0,0,20,20H92v12a12,12,0,0,0,24,0V220h24v12a12,12,0,0,0,24,0V220h36a20,20,0,0,0,20-20V164h12a12,12,0,0,0,0-24Zm-36,56H60V60H196Z\"/></symbol>\n <symbol id=\"juno-i-dots-three-vertical\" viewBox=\"0 0 256 256\"><path d=\"M112,60a16,16,0,1,1,16,16A16,16,0,0,1,112,60Zm16,52a16,16,0,1,0,16,16A16,16,0,0,0,128,112Zm0,68a16,16,0,1,0,16,16A16,16,0,0,0,128,180Z\"/></symbol>\n <symbol id=\"juno-i-dots-three\" viewBox=\"0 0 256 256\"><path d=\"M144,128a16,16,0,1,1-16-16A16,16,0,0,1,144,128ZM60,112a16,16,0,1,0,16,16A16,16,0,0,0,60,112Zm136,0a16,16,0,1,0,16,16A16,16,0,0,0,196,112Z\"/></symbol>\n <symbol id=\"juno-i-download\" viewBox=\"0 0 256 256\"><path d=\"M71.51,88.49a12,12,0,0,1,17-17L116,99V24a12,12,0,0,1,24,0V99l27.51-27.52a12,12,0,0,1,17,17l-48,48a12,12,0,0,1-17,0ZM224,116H188a12,12,0,0,0,0,24h32v56H36V140H68a12,12,0,0,0,0-24H32a20,20,0,0,0-20,20v64a20,20,0,0,0,20,20H224a20,20,0,0,0,20-20V136A20,20,0,0,0,224,116Zm-20,52a16,16,0,1,0-16,16A16,16,0,0,0,204,168Z\"/></symbol>\n <symbol id=\"juno-i-envelope\" viewBox=\"0 0 256 256\"><path d=\"M224,44H32A12,12,0,0,0,20,56V192a20,20,0,0,0,20,20H216a20,20,0,0,0,20-20V56A12,12,0,0,0,224,44Zm-96,83.72L62.85,68h130.3ZM92.79,128,44,172.72V83.28Zm17.76,16.28,9.34,8.57a12,12,0,0,0,16.22,0l9.34-8.57L193.15,188H62.85ZM163.21,128,212,83.28v89.44Z\"/></symbol>\n <symbol id=\"juno-i-eye-slash\" viewBox=\"0 0 256 256\"><path d=\"M56.88,31.93A12,12,0,1,0,39.12,48.07l16,17.65C20.67,88.66,5.72,121.58,5,123.13a12.08,12.08,0,0,0,0,9.75c.37.82,9.13,20.26,28.49,39.61C59.37,198.34,92,212,128,212a131.34,131.34,0,0,0,51-10l20.09,22.1a12,12,0,0,0,17.76-16.14ZM128,188c-29.59,0-55.47-10.73-76.91-31.88A130.69,130.69,0,0,1,29.52,128c5.27-9.31,18.79-29.9,42-44.29l90.09,99.11A109.33,109.33,0,0,1,128,188Zm123-55.12c-.36.81-9,20-28,39.16a12,12,0,1,1-17-16.9A130.48,130.48,0,0,0,226.48,128a130.36,130.36,0,0,0-21.57-28.12C183.46,78.73,157.59,68,128,68c-3.35,0-6.7.14-10,.42a12,12,0,1,1-2-23.91c3.93-.34,8-.51,12-.51,36,0,68.63,13.67,94.49,39.52,19.35,19.35,28.11,38.8,28.48,39.61A12.08,12.08,0,0,1,251,132.88Z\"/></symbol>\n <symbol id=\"juno-i-eye\" viewBox=\"0 0 256 256\"><path d=\"M251,123.13c-.37-.81-9.13-20.26-28.48-39.61C196.63,57.67,164,44,128,44S59.37,57.67,33.51,83.52C14.16,102.87,5.4,122.32,5,123.13a12.08,12.08,0,0,0,0,9.75c.37.82,9.13,20.26,28.49,39.61C59.37,198.34,92,212,128,212s68.63-13.66,94.48-39.51c19.36-19.35,28.12-38.79,28.49-39.61A12.08,12.08,0,0,0,251,123.13Zm-46.06,33C183.47,177.27,157.59,188,128,188s-55.47-10.73-76.91-31.88A130.36,130.36,0,0,1,29.52,128,130.45,130.45,0,0,1,51.09,99.89C72.54,78.73,98.41,68,128,68s55.46,10.73,76.91,31.89A130.36,130.36,0,0,1,226.48,128,130.45,130.45,0,0,1,204.91,156.12ZM128,84a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,84Zm0,64a20,20,0,1,1,20-20A20,20,0,0,1,128,148Z\"/></symbol>\n <symbol id=\"juno-i-file\" viewBox=\"0 0 256 256\"><path d=\"M216.49,79.52l-56-56A12,12,0,0,0,152,20H56A20,20,0,0,0,36,40V216a20,20,0,0,0,20,20H200a20,20,0,0,0,20-20V88A12,12,0,0,0,216.49,79.52ZM160,57l23,23H160ZM60,212V44h76V92a12,12,0,0,0,12,12h48V212Z\"/></symbol>\n <symbol id=\"juno-i-film-strip\" viewBox=\"0 0 256 256\"><path d=\"M216,36H40A20,20,0,0,0,20,56V200a20,20,0,0,0,20,20H216a20,20,0,0,0,20-20V56A20,20,0,0,0,216,36ZM44,100h72v56H44Zm96-24V60h24V76Zm-24,0H92V60h24Zm0,104v16H92V180Zm24,0h24v16H140Zm0-24V100h72v56Zm72-80H188V60h24ZM68,60V76H44V60ZM44,180H68v16H44Zm144,16V180h24v16Z\"/></symbol>\n <symbol id=\"juno-i-floppy-disk\" viewBox=\"0 0 256 256\"><path d=\"M222.14,69.17,186.83,33.86A19.86,19.86,0,0,0,172.69,28H48A20,20,0,0,0,28,48V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V83.31A19.86,19.86,0,0,0,222.14,69.17ZM164,204H92V160h72Zm40,0H188V156a20,20,0,0,0-20-20H88a20,20,0,0,0-20,20v48H52V52H171l33,33ZM164,84a12,12,0,0,1-12,12H96a12,12,0,0,1,0-24h56A12,12,0,0,1,164,84Z\"/></symbol>\n <symbol id=\"juno-i-folder\" viewBox=\"0 0 256 256\"><path d=\"M216,68H133.39l-26-29.29a20,20,0,0,0-15-6.71H40A20,20,0,0,0,20,52V200.62A19.41,19.41,0,0,0,39.38,220H216.89A19.13,19.13,0,0,0,236,200.89V88A20,20,0,0,0,216,68ZM44,56H90.61l10.67,12H44ZM212,196H44V92H212Z\"/></symbol>\n <symbol id=\"juno-i-funnel\" viewBox=\"0 0 256 256\"><path d=\"M234.29,47.91A20,20,0,0,0,216,36H40A20,20,0,0,0,25.2,69.45l.12.14L92,140.75V216a20,20,0,0,0,31.1,16.64l32-21.33A20,20,0,0,0,164,194.66V140.75l66.67-71.16.12-.14A20,20,0,0,0,234.29,47.91Zm-91,79.89A12,12,0,0,0,140,136v56.52l-24,16V136a12,12,0,0,0-3.25-8.2L49.23,60H206.77Z\"/></symbol>\n <symbol id=\"juno-i-gear\" viewBox=\"0 0 256 256\"><path d=\"M128,76a52,52,0,1,0,52,52A52.06,52.06,0,0,0,128,76Zm0,80a28,28,0,1,1,28-28A28,28,0,0,1,128,156Zm92-27.21v-1.58l14-17.51a12,12,0,0,0,2.23-10.59A111.75,111.75,0,0,0,225,71.89,12,12,0,0,0,215.89,66L193.61,63.5l-1.11-1.11L190,40.1A12,12,0,0,0,184.11,31a111.67,111.67,0,0,0-27.23-11.27A12,12,0,0,0,146.3,22L128.79,36h-1.58L109.7,22a12,12,0,0,0-10.59-2.23A111.75,111.75,0,0,0,71.89,31.05,12,12,0,0,0,66,40.11L63.5,62.39,62.39,63.5,40.1,66A12,12,0,0,0,31,71.89,111.67,111.67,0,0,0,19.77,99.12,12,12,0,0,0,22,109.7l14,17.51v1.58L22,146.3a12,12,0,0,0-2.23,10.59,111.75,111.75,0,0,0,11.29,27.22A12,12,0,0,0,40.11,190l22.28,2.48,1.11,1.11L66,215.9A12,12,0,0,0,71.89,225a111.67,111.67,0,0,0,27.23,11.27A12,12,0,0,0,109.7,234l17.51-14h1.58l17.51,14a12,12,0,0,0,10.59,2.23A111.75,111.75,0,0,0,184.11,225a12,12,0,0,0,5.91-9.06l2.48-22.28,1.11-1.11L215.9,190a12,12,0,0,0,9.06-5.91,111.67,111.67,0,0,0,11.27-27.23A12,12,0,0,0,234,146.3Zm-24.12-4.89a70.1,70.1,0,0,1,0,8.2,12,12,0,0,0,2.61,8.22l12.84,16.05A86.47,86.47,0,0,1,207,166.86l-20.43,2.27a12,12,0,0,0-7.65,4,69,69,0,0,1-5.8,5.8,12,12,0,0,0-4,7.65L166.86,207a86.47,86.47,0,0,1-10.49,4.35l-16.05-12.85a12,12,0,0,0-7.5-2.62c-.24,0-.48,0-.72,0a70.1,70.1,0,0,1-8.2,0,12.06,12.06,0,0,0-8.22,2.6L99.63,211.33A86.47,86.47,0,0,1,89.14,207l-2.27-20.43a12,12,0,0,0-4-7.65,69,69,0,0,1-5.8-5.8,12,12,0,0,0-7.65-4L49,166.86a86.47,86.47,0,0,1-4.35-10.49l12.84-16.05a12,12,0,0,0,2.61-8.22,70.1,70.1,0,0,1,0-8.2,12,12,0,0,0-2.61-8.22L44.67,99.63A86.47,86.47,0,0,1,49,89.14l20.43-2.27a12,12,0,0,0,7.65-4,69,69,0,0,1,5.8-5.8,12,12,0,0,0,4-7.65L89.14,49a86.47,86.47,0,0,1,10.49-4.35l16.05,12.85a12.06,12.06,0,0,0,8.22,2.6,70.1,70.1,0,0,1,8.2,0,12,12,0,0,0,8.22-2.6l16.05-12.85A86.47,86.47,0,0,1,166.86,49l2.27,20.43a12,12,0,0,0,4,7.65,69,69,0,0,1,5.8,5.8,12,12,0,0,0,7.65,4L207,89.14a86.47,86.47,0,0,1,4.35,10.49l-12.84,16.05A12,12,0,0,0,195.88,123.9Z\"/></symbol>\n <symbol id=\"juno-i-hard-drives\" viewBox=\"0 0 256 256\"><path d=\"M208,36H48A20,20,0,0,0,28,56V200a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V56A20,20,0,0,0,208,36Zm-4,24v56H52V60ZM52,196V140H204v56ZM160,88a16,16,0,1,1,16,16A16,16,0,0,1,160,88Zm32,80a16,16,0,1,1-16-16A16,16,0,0,1,192,168Z\"/></symbol>\n <symbol id=\"juno-i-heart\" viewBox=\"0 0 256 256\"><path d=\"M178,36c-20.09,0-37.92,7.93-50,21.56C115.92,43.93,98.09,36,78,36a66.08,66.08,0,0,0-66,66c0,72.34,105.81,130.14,110.31,132.57a12,12,0,0,0,11.38,0C138.19,232.14,244,174.34,244,102A66.08,66.08,0,0,0,178,36Zm-5.49,142.36A328.69,328.69,0,0,1,128,210.16a328.69,328.69,0,0,1-44.51-31.8C61.82,159.77,36,131.42,36,102A42,42,0,0,1,78,60c17.8,0,32.7,9.4,38.89,24.54a12,12,0,0,0,22.22,0C145.3,69.4,160.2,60,178,60a42,42,0,0,1,42,42C220,131.42,194.18,159.77,172.51,178.36Z\"/></symbol>\n <symbol id=\"juno-i-hexagon\" viewBox=\"0 0 256 256\"><path d=\"M225.6,62.64l-88-48.17a19.91,19.91,0,0,0-19.2,0l-88,48.17A20,20,0,0,0,20,80.19v95.62a20,20,0,0,0,10.4,17.55l88,48.17a19.89,19.89,0,0,0,19.2,0l88-48.17A20,20,0,0,0,236,175.81V80.19A20,20,0,0,0,225.6,62.64ZM212,173.44l-84,46-84-46V82.56l84-46,84,46Z\"/></symbol>\n <symbol id=\"juno-i-house\" viewBox=\"0 0 256 256\"><path d=\"M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z\"/></symbol>\n <symbol id=\"juno-i-images\" viewBox=\"0 0 256 256\"><path d=\"M160,88a16,16,0,1,1,16,16A16,16,0,0,1,160,88Zm76-32V160a20,20,0,0,1-20,20H204v20a20,20,0,0,1-20,20H40a20,20,0,0,1-20-20V88A20,20,0,0,1,40,68H60V56A20,20,0,0,1,80,36H216A20,20,0,0,1,236,56ZM180,180H80a20,20,0,0,1-20-20V92H44V196H180Zm-21.66-24L124,121.66,89.66,156ZM212,60H84v67.72l25.86-25.86a20,20,0,0,1,28.28,0L192.28,156H212Z\"/></symbol>\n <symbol id=\"juno-i-info\" viewBox=\"0 0 256 256\"><path d=\"M108,84a16,16,0,1,1,16,16A16,16,0,0,1,108,84Zm128,44A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Zm-72,36.68V132a20,20,0,0,0-20-20,12,12,0,0,0-4,23.32V168a20,20,0,0,0,20,20,12,12,0,0,0,4-23.32Z\"/></symbol>\n <symbol id=\"juno-i-link\" viewBox=\"0 0 256 256\"><path d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/></symbol>\n <symbol id=\"juno-i-list\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128ZM40,76H216a12,12,0,0,0,0-24H40a12,12,0,0,0,0,24ZM216,180H40a12,12,0,0,0,0,24H216a12,12,0,0,0,0-24Z\"/></symbol>\n <symbol id=\"juno-i-lock-open\" viewBox=\"0 0 256 256\"><path d=\"M208,76H100V56a28,28,0,0,1,28-28c13.51,0,25.65,9.62,28.24,22.39a12,12,0,1,0,23.52-4.78C174.87,21.5,153.1,4,128,4A52.06,52.06,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76Zm-4,128H52V100H204Zm-92-52a16,16,0,1,1,16,16A16,16,0,0,1,112,152Z\"/></symbol>\n <symbol id=\"juno-i-lock\" viewBox=\"0 0 256 256\"><path d=\"M208,76H180V56A52,52,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76ZM100,56a28,28,0,0,1,56,0V76H100ZM204,204H52V100H204Zm-60-52a16,16,0,1,1-16-16A16,16,0,0,1,144,152Z\"/></symbol>\n <symbol id=\"juno-i-magnifying-glass\" viewBox=\"0 0 256 256\"><path d=\"M232.49,215.51,185,168a92.12,92.12,0,1,0-17,17l47.53,47.54a12,12,0,0,0,17-17ZM44,112a68,68,0,1,1,68,68A68.07,68.07,0,0,1,44,112Z\"/></symbol>\n <symbol id=\"juno-i-minus\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128Z\"/></symbol>\n <symbol id=\"juno-i-moon\" viewBox=\"0 0 256 256\"><path d=\"M236.37,139.4a12,12,0,0,0-12-3A84.07,84.07,0,0,1,119.6,31.59a12,12,0,0,0-15-15A108.86,108.86,0,0,0,49.69,55.07,108,108,0,0,0,136,228a107.09,107.09,0,0,0,64.93-21.69,108.86,108.86,0,0,0,38.44-54.94A12,12,0,0,0,236.37,139.4Zm-49.88,47.74A84,84,0,0,1,68.86,69.51,84.93,84.93,0,0,1,92.27,48.29Q92,52.13,92,56A108.12,108.12,0,0,0,200,164q3.87,0,7.71-.27A84.79,84.79,0,0,1,186.49,187.14Z\"/></symbol>\n <symbol id=\"juno-i-pencil-simple\" viewBox=\"0 0 256 256\"><path d=\"M230.14,70.54,185.46,25.85a20,20,0,0,0-28.29,0L33.86,149.17A19.85,19.85,0,0,0,28,163.31V208a20,20,0,0,0,20,20H92.69a19.86,19.86,0,0,0,14.14-5.86L230.14,98.82a20,20,0,0,0,0-28.28ZM91,204H52V165l84-84,39,39ZM192,103,153,64l18.34-18.34,39,39Z\"/></symbol>\n <symbol id=\"juno-i-play\" viewBox=\"0 0 256 256\"><path d=\"M234.49,111.07,90.41,22.94A20,20,0,0,0,60,39.87V216.13a20,20,0,0,0,30.41,16.93l144.08-88.13a19.82,19.82,0,0,0,0-33.86ZM84,208.85V47.15L216.16,128Z\"/></symbol>\n <symbol id=\"juno-i-plus\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H140v76a12,12,0,0,1-24,0V140H40a12,12,0,0,1,0-24h76V40a12,12,0,0,1,24,0v76h76A12,12,0,0,1,228,128Z\"/></symbol>\n <symbol id=\"juno-i-puzzle-piece\" viewBox=\"0 0 256 256\"><path d=\"M222.41,155.16a12,12,0,0,0-11.56-.69A16,16,0,0,1,188,139,16.2,16.2,0,0,1,202.8,124a15.83,15.83,0,0,1,8,1.5A12,12,0,0,0,228,114.7V72a20,20,0,0,0-20-20H176a40.15,40.15,0,0,0-12.62-29.16,39.67,39.67,0,0,0-29.94-10.76,40.08,40.08,0,0,0-37.34,37C96,50.07,96,51,96,52H64A20,20,0,0,0,44,72v28a40.15,40.15,0,0,0-29.16,12.62A40,40,0,0,0,41.1,179.9a28.3,28.3,0,0,0,2.9.1v28a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V165.31A12,12,0,0,0,222.41,155.16ZM204,204H68V165.31a12,12,0,0,0-17.15-10.84A15.9,15.9,0,0,1,42.8,156,16.2,16.2,0,0,1,28,141.06a16,16,0,0,1,22.82-15.52A12,12,0,0,0,68,114.7V76h42.7a12,12,0,0,0,10.83-17.15A15.9,15.9,0,0,1,120,50.8,16.19,16.19,0,0,1,134.94,36a16,16,0,0,1,15.53,22.81A12,12,0,0,0,161.31,76H204v24c-1,0-1.93,0-2.9.11A40,40,0,0,0,204,180h0Z\"/></symbol>\n <symbol id=\"juno-i-question\" viewBox=\"0 0 256 256\"><path d=\"M144,180a16,16,0,1,1-16-16A16,16,0,0,1,144,180Zm92-52A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128ZM128,64c-24.26,0-44,17.94-44,40v4a12,12,0,0,0,24,0v-4c0-8.82,9-16,20-16s20,7.18,20,16-9,16-20,16a12,12,0,0,0-12,12v8a12,12,0,0,0,23.73,2.56C158.31,137.88,172,122.37,172,104,172,81.94,152.26,64,128,64Z\"/></symbol>\n <symbol id=\"juno-i-sliders-horizontal\" viewBox=\"0 0 256 256\"><path d=\"M40,92H70.06a36,36,0,0,0,67.88,0H216a12,12,0,0,0,0-24H137.94a36,36,0,0,0-67.88,0H40a12,12,0,0,0,0,24Zm64-24A12,12,0,1,1,92,80,12,12,0,0,1,104,68Zm112,96H201.94a36,36,0,0,0-67.88,0H40a12,12,0,0,0,0,24h94.06a36,36,0,0,0,67.88,0H216a12,12,0,0,0,0-24Zm-48,24a12,12,0,1,1,12-12A12,12,0,0,1,168,188Z\"/></symbol>\n <symbol id=\"juno-i-sliders\" viewBox=\"0 0 256 256\"><path d=\"M68,102.06V40a12,12,0,0,0-24,0v62.06a36,36,0,0,0,0,67.88V216a12,12,0,0,0,24,0V169.94a36,36,0,0,0,0-67.88ZM56,148a12,12,0,1,1,12-12A12,12,0,0,1,56,148ZM164,88a36.07,36.07,0,0,0-24-33.94V40a12,12,0,0,0-24,0V54.06a36,36,0,0,0,0,67.88V216a12,12,0,0,0,24,0V121.94A36.07,36.07,0,0,0,164,88Zm-36,12a12,12,0,1,1,12-12A12,12,0,0,1,128,100Zm108,68a36.07,36.07,0,0,0-24-33.94V40a12,12,0,0,0-24,0v94.06a36,36,0,0,0,0,67.88V216a12,12,0,0,0,24,0V201.94A36.07,36.07,0,0,0,236,168Zm-36,12a12,12,0,1,1,12-12A12,12,0,0,1,200,180Z\"/></symbol>\n <symbol id=\"juno-i-squares-four\" viewBox=\"0 0 256 256\"><path d=\"M100,36H56A20,20,0,0,0,36,56v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V56A20,20,0,0,0,100,36ZM96,96H60V60H96ZM200,36H156a20,20,0,0,0-20,20v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V56A20,20,0,0,0,200,36Zm-4,60H160V60h36Zm-96,40H56a20,20,0,0,0-20,20v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V156A20,20,0,0,0,100,136Zm-4,60H60V160H96Zm104-60H156a20,20,0,0,0-20,20v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V156A20,20,0,0,0,200,136Zm-4,60H160V160h36Z\"/></symbol>\n <symbol id=\"juno-i-star\" viewBox=\"0 0 256 256\"><path d=\"M243,96a20.33,20.33,0,0,0-17.74-14l-56.59-4.57L146.83,24.62a20.36,20.36,0,0,0-37.66,0L87.35,77.44,30.76,82A20.45,20.45,0,0,0,19.1,117.88l43.18,37.24-13.2,55.7A20.37,20.37,0,0,0,79.57,233L128,203.19,176.43,233a20.39,20.39,0,0,0,30.49-22.15l-13.2-55.7,43.18-37.24A20.43,20.43,0,0,0,243,96ZM172.53,141.7a12,12,0,0,0-3.84,11.86L181.58,208l-47.29-29.08a12,12,0,0,0-12.58,0L74.42,208l12.89-54.4a12,12,0,0,0-3.84-11.86L41.2,105.24l55.4-4.47a12,12,0,0,0,10.13-7.38L128,41.89l21.27,51.5a12,12,0,0,0,10.13,7.38l55.4,4.47Z\"/></symbol>\n <symbol id=\"juno-i-sun\" viewBox=\"0 0 256 256\"><path d=\"M116,36V20a12,12,0,0,1,24,0V36a12,12,0,0,1-24,0Zm80,92a68,68,0,1,1-68-68A68.07,68.07,0,0,1,196,128Zm-24,0a44,44,0,1,0-44,44A44.05,44.05,0,0,0,172,128ZM51.51,68.49a12,12,0,1,0,17-17l-12-12a12,12,0,0,0-17,17Zm0,119-12,12a12,12,0,0,0,17,17l12-12a12,12,0,1,0-17-17ZM196,72a12,12,0,0,0,8.49-3.51l12-12a12,12,0,0,0-17-17l-12,12A12,12,0,0,0,196,72Zm8.49,115.51a12,12,0,0,0-17,17l12,12a12,12,0,0,0,17-17ZM48,128a12,12,0,0,0-12-12H20a12,12,0,0,0,0,24H36A12,12,0,0,0,48,128Zm80,80a12,12,0,0,0-12,12v16a12,12,0,0,0,24,0V220A12,12,0,0,0,128,208Zm108-92H220a12,12,0,0,0,0,24h16a12,12,0,0,0,0-24Z\"/></symbol>\n <symbol id=\"juno-i-trash\" viewBox=\"0 0 256 256\"><path d=\"M216,48H180V36A28,28,0,0,0,152,8H104A28,28,0,0,0,76,36V48H40a12,12,0,0,0,0,24h4V208a20,20,0,0,0,20,20H192a20,20,0,0,0,20-20V72h4a12,12,0,0,0,0-24ZM100,36a4,4,0,0,1,4-4h48a4,4,0,0,1,4,4V48H100Zm88,168H68V72H188ZM116,104v64a12,12,0,0,1-24,0V104a12,12,0,0,1,24,0Zm48,0v64a12,12,0,0,1-24,0V104a12,12,0,0,1,24,0Z\"/></symbol>\n <symbol id=\"juno-i-upload-simple\" viewBox=\"0 0 256 256\"><path d=\"M228,144v64a12,12,0,0,1-12,12H40a12,12,0,0,1-12-12V144a12,12,0,0,1,24,0v52H204V144a12,12,0,0,1,24,0ZM96.49,80.49,116,61v83a12,12,0,0,0,24,0V61l19.51,19.52a12,12,0,1,0,17-17l-40-40a12,12,0,0,0-17,0l-40,40a12,12,0,1,0,17,17Z\"/></symbol>\n <symbol id=\"juno-i-upload\" viewBox=\"0 0 256 256\"><path d=\"M188,184a16,16,0,1,1,16-16A16,16,0,0,1,188,184Zm36-68H180a12,12,0,0,0,0,24h40v56H36V140H76a12,12,0,0,0,0-24H32a20,20,0,0,0-20,20v64a20,20,0,0,0,20,20H224a20,20,0,0,0,20-20V136A20,20,0,0,0,224,116ZM88.49,80.49,116,53v75a12,12,0,0,0,24,0V53l27.51,27.52a12,12,0,1,0,17-17l-48-48a12,12,0,0,0-17,0l-48,48a12,12,0,1,0,17,17Z\"/></symbol>\n <symbol id=\"juno-i-user\" viewBox=\"0 0 256 256\"><path d=\"M234.38,210a123.36,123.36,0,0,0-60.78-53.23,76,76,0,1,0-91.2,0A123.36,123.36,0,0,0,21.62,210a12,12,0,1,0,20.77,12c18.12-31.32,50.12-50,85.61-50s67.49,18.69,85.61,50a12,12,0,0,0,20.77-12ZM76,96a52,52,0,1,1,52,52A52.06,52.06,0,0,1,76,96Z\"/></symbol>\n <symbol id=\"juno-i-users\" viewBox=\"0 0 256 256\"><path d=\"M125.18,156.94a64,64,0,1,0-82.36,0,100.23,100.23,0,0,0-39.49,32,12,12,0,0,0,19.35,14.2,76,76,0,0,1,122.64,0,12,12,0,0,0,19.36-14.2A100.33,100.33,0,0,0,125.18,156.94ZM44,108a40,40,0,1,1,40,40A40,40,0,0,1,44,108Zm206.1,97.67a12,12,0,0,1-16.78-2.57A76.31,76.31,0,0,0,172,172a12,12,0,0,1,0-24,40,40,0,1,0-10.3-78.67,12,12,0,1,1-6.16-23.19,64,64,0,0,1,57.64,110.8,100.23,100.23,0,0,1,39.49,32A12,12,0,0,1,250.1,205.67Z\"/></symbol>\n <symbol id=\"juno-i-warning-circle\" viewBox=\"0 0 256 256\"><path d=\"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm-12-80V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,172Z\"/></symbol>\n <symbol id=\"juno-i-warning\" viewBox=\"0 0 256 256\"><path d=\"M240.26,186.1,152.81,34.23h0a28.74,28.74,0,0,0-49.62,0L15.74,186.1a27.45,27.45,0,0,0,0,27.71A28.31,28.31,0,0,0,40.55,228h174.9a28.31,28.31,0,0,0,24.79-14.19A27.45,27.45,0,0,0,240.26,186.1Zm-20.8,15.7a4.46,4.46,0,0,1-4,2.2H40.55a4.46,4.46,0,0,1-4-2.2,3.56,3.56,0,0,1,0-3.73L124,46.2a4.77,4.77,0,0,1,8,0l87.44,151.87A3.56,3.56,0,0,1,219.46,201.8ZM116,136V104a12,12,0,0,1,24,0v32a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,176Z\"/></symbol>\n <symbol id=\"juno-i-x-circle\" viewBox=\"0 0 256 256\"><path d=\"M168.49,104.49,145,128l23.52,23.51a12,12,0,0,1-17,17L128,145l-23.51,23.52a12,12,0,0,1-17-17L111,128,87.51,104.49a12,12,0,0,1,17-17L128,111l23.51-23.52a12,12,0,0,1,17,17ZM236,128A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Z\"/></symbol>\n <symbol id=\"juno-i-x\" viewBox=\"0 0 256 256\"><path d=\"M208.49,191.51a12,12,0,0,1-17,17L128,145,64.49,208.49a12,12,0,0,1-17-17L111,128,47.51,64.49a12,12,0,0,1,17-17L128,111l63.51-63.52a12,12,0,0,1,17,17L145,128Z\"/></symbol>\n</svg>\n";
|
|
7
11
|
|
|
8
|
-
/** Inject the sprite into `doc` once (id-guarded, safe to call repeatedly). */
|
|
12
|
+
/** Inject the full sprite into `doc` once (id-guarded, safe to call repeatedly). */
|
|
9
13
|
export function installJunoIcons(doc = typeof document !== 'undefined' ? document : undefined) {
|
|
10
|
-
|
|
11
|
-
const holder = doc.createElement('div');
|
|
12
|
-
holder.id = 'juno-icon-sprite';
|
|
13
|
-
holder.style.display = 'none';
|
|
14
|
-
holder.setAttribute('aria-hidden', 'true');
|
|
15
|
-
holder.innerHTML = SPRITE;
|
|
16
|
-
(doc.body || doc.documentElement).prepend(holder);
|
|
14
|
+
installSprite(SPRITE, doc);
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
// Auto-install on import in a browser (no-op server-side / pre-hydration).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// junoui — icon sprite injector, no payload. Generated; do not edit.
|
|
2
|
+
// Same-document injection, because Safari drops external <use href="file.svg#id">
|
|
3
|
+
// refs intermittently. Pair with junoui/subset when you ship a subset:
|
|
4
|
+
// import { installSprite } from '@junoput01/junoui/icons/install';
|
|
5
|
+
// import sprite from 'virtual:my-icon-subset';
|
|
6
|
+
// installSprite(sprite);
|
|
7
|
+
export const JUNO_SPRITE_ID = 'juno-icon-sprite';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Inject `svg` (sprite source) into `doc` once. Id-guarded, so calling it
|
|
11
|
+
* repeatedly — or alongside junoui/icons/inline — installs exactly one sprite:
|
|
12
|
+
* whichever ran first wins, and a second call is a no-op rather than a second
|
|
13
|
+
* hidden holder shadowing the first.
|
|
14
|
+
*/
|
|
15
|
+
export function installSprite(svg, doc = typeof document !== 'undefined' ? document : undefined) {
|
|
16
|
+
if (!doc || !svg || doc.getElementById(JUNO_SPRITE_ID)) return false;
|
|
17
|
+
const holder = doc.createElement('div');
|
|
18
|
+
holder.id = JUNO_SPRITE_ID;
|
|
19
|
+
holder.style.display = 'none';
|
|
20
|
+
holder.setAttribute('aria-hidden', 'true');
|
|
21
|
+
holder.innerHTML = svg;
|
|
22
|
+
(doc.body || doc.documentElement).prepend(holder);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default installSprite;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Browser support
|
|
2
|
+
|
|
3
|
+
What junoui requires, what it loses gracefully below that, and the short list of
|
|
4
|
+
things that genuinely **break**. Stated because the failures are otherwise
|
|
5
|
+
silent: an engine that does not understand a CSS at-rule, property or selector
|
|
6
|
+
**drops it without reporting anything**. Nobody finds out from a console; they
|
|
7
|
+
find out from a screenshot.
|
|
8
|
+
|
|
9
|
+
Audited against `dist/css/juno.css` at v0.5.0 (2026-08-15).
|
|
10
|
+
|
|
11
|
+
## The floor
|
|
12
|
+
|
|
13
|
+
| | Safari / iOS | Chrome / Edge | Firefox |
|
|
14
|
+
| ------------------------------------------------------------ | ------------ | ------------- | ---------------------------------- |
|
|
15
|
+
| **Hard floor** — below this, things break | **17.0** | **114** | **125** |
|
|
16
|
+
| **Supported floor** — everything works, minor polish missing | **17.5** | 117 | 129 |
|
|
17
|
+
| **Full fidelity** — nothing missing | **26.0** | 125 | — (anchor positioning not shipped) |
|
|
18
|
+
|
|
19
|
+
**junoui's supported floor is Safari / iOS 17.5.** That is the version to put in
|
|
20
|
+
a consumer's own support matrix.
|
|
21
|
+
|
|
22
|
+
Read the three rows as three different questions:
|
|
23
|
+
|
|
24
|
+
- Below **17.0** the Popover API is absent and junoui's overlay surfaces stop
|
|
25
|
+
being overlays. See [What breaks](#what-breaks) — this is not a polish
|
|
26
|
+
question, and one of the two failure modes is worse than "the menu does not
|
|
27
|
+
open".
|
|
28
|
+
- Between **17.0 and 17.5** everything functions; entry animations are missing.
|
|
29
|
+
- Between **17.5 and 26.0** everything functions and animates; anchored
|
|
30
|
+
surfaces (menu, popover, tooltip) land at their static position instead of
|
|
31
|
+
next to their trigger, because CSS anchor positioning is a Safari 26 feature.
|
|
32
|
+
This is a real visual defect, not a nicety — but the surface still opens,
|
|
33
|
+
still closes, still light-dismisses, and the app can pin it (see below).
|
|
34
|
+
|
|
35
|
+
`package.json` carries a `browserslist` set to the **hard floor**, so tooling
|
|
36
|
+
reads the line below which junoui is broken rather than the line below which it
|
|
37
|
+
is imperfect. junoui itself runs no autoprefixer or transpiler — the field is a
|
|
38
|
+
declaration for consumers' build tools, not something junoui acts on.
|
|
39
|
+
|
|
40
|
+
## Feature audit
|
|
41
|
+
|
|
42
|
+
Counts are occurrences in the built bundle.
|
|
43
|
+
|
|
44
|
+
| Feature | Uses | Safari / iOS | Below that |
|
|
45
|
+
| ------------------------------------------------------------------------------------- | ---- | ------------ | -------------------------------------------------------------------------------------------- |
|
|
46
|
+
| CSS anchor positioning (`position-area`, `position-try-fallbacks`, `position-anchor`) | 10 | **26.0** | degrades — [misplaced surfaces](#anchored-surfaces-lose-their-anchor) |
|
|
47
|
+
| `scrollbar-width` | 4 | 18.2 | **guarded** — `::-webkit-scrollbar` fallback ships |
|
|
48
|
+
| `backdrop-filter` (unprefixed) | 14 | 18.0 | **guarded** — `-webkit-backdrop-filter` ships beside every use |
|
|
49
|
+
| `@starting-style` | 10 | 17.5 | degrades — no entry animation |
|
|
50
|
+
| `transition-behavior` / `overlay` + `allow-discrete` | 10 | 17.4 | degrades — surfaces pop instead of fading |
|
|
51
|
+
| **Popover API** (`popover`, `popovertarget`, `:popover-open`) | 31 | **17.0** | **breaks** |
|
|
52
|
+
| `prefers-reduced-transparency` | 3 | 17.0 | degrades — blur stays on for users who asked it off |
|
|
53
|
+
| `@property` | 1 | 16.4 | degrades — the gauge jumps to its value instead of sweeping |
|
|
54
|
+
| `color-mix()` | 28 | 16.2 | degrades — role tints vanish (see the caveat below) |
|
|
55
|
+
| `@container` | 2 | 16.0 | degrades — card and table stop reflowing at narrow widths |
|
|
56
|
+
| `:has()` | 5 | 15.4 | degrades — the collapsible pillbar never collapses |
|
|
57
|
+
| `dvh` / `lvh` | 10 | 15.4 | breaks layout — `block-size` falls back to `auto`, the app shell collapses to content height |
|
|
58
|
+
| `oklch()` | 190 | 15.4 | breaks colour — every token value is `oklch()` |
|
|
59
|
+
| `aspect-ratio` | 7 | 15.0 | degrades — media tiles lose their ratio |
|
|
60
|
+
| `@supports selector()` | 1 | 14.1 | the Popover guard below is inert (fails open to today's behaviour) |
|
|
61
|
+
|
|
62
|
+
Two rows deserve a note.
|
|
63
|
+
|
|
64
|
+
**`color-mix()` is listed as a degrade, and that is technically true and
|
|
65
|
+
practically uncomfortable.** All 28 uses are tints, borders and shadows built
|
|
66
|
+
from `var(--juno-role)`; when the function is unsupported the declaration is
|
|
67
|
+
invalid and the property falls back to `transparent` or the previous cascade
|
|
68
|
+
value. Text and icon colour, which take `var(--juno-role)` directly, survive. So
|
|
69
|
+
nothing breaks — but role tinting is exactly the thing junoui exists to encode,
|
|
70
|
+
and losing it erodes the contract even while the layout holds. It sits below the
|
|
71
|
+
floor; it is listed so nobody discovers it by shipping to iOS 16.
|
|
72
|
+
|
|
73
|
+
**`oklch()` and `dvh` are below every floor in this document** and are listed for
|
|
74
|
+
completeness. If you are on iOS 15 you are not using junoui.
|
|
75
|
+
|
|
76
|
+
## What breaks
|
|
77
|
+
|
|
78
|
+
Exactly one thing, at exactly one version, and it has two failure modes.
|
|
79
|
+
|
|
80
|
+
### Popover API, Safari / iOS < 17.0
|
|
81
|
+
|
|
82
|
+
junoui's `.juno-menu`, `.juno-popover` and the top-layer `.juno-tooltip__bubble`
|
|
83
|
+
are built on the native Popover API. The open/close, light-dismiss and ESC
|
|
84
|
+
behaviour is the platform's; junoui ships only the surface. On an engine without
|
|
85
|
+
it:
|
|
86
|
+
|
|
87
|
+
1. **They never open.** `popovertarget` does nothing, so there is no way to
|
|
88
|
+
reveal the panel. The pillbar's overflow slot routes to `.juno-menu`, so the
|
|
89
|
+
overflow items are unreachable — which on a phone is where the secondary
|
|
90
|
+
navigation lives.
|
|
91
|
+
2. **Worse: `.juno-menu` and `.juno-popover` become invisible click-eaters.**
|
|
92
|
+
The UA rule that hides a closed popover (`display: none` on any `[popover]`
|
|
93
|
+
that is not `:popover-open`) does not exist on an unsupporting engine, so the
|
|
94
|
+
panel participates in layout. junoui's own base rule sets `opacity: 0`, so it is
|
|
95
|
+
invisible, but both surfaces are `position: fixed` with no `pointer-events`
|
|
96
|
+
reset. The result is a 256–280 px invisible fixed panel sitting over the page
|
|
97
|
+
at its static position, swallowing taps on whatever it covers.
|
|
98
|
+
|
|
99
|
+
(`.juno-tooltip__bubble` escapes this: it inherits `pointer-events: none`
|
|
100
|
+
from the CSS-only tooltip rule it shares.)
|
|
101
|
+
|
|
102
|
+
Failure mode 2 is the one that justifies a guard. A feature that is missing is
|
|
103
|
+
an inconvenience; a feature that is missing and eats input is a bug report that
|
|
104
|
+
looks like something else entirely. `base.css` therefore ships:
|
|
105
|
+
|
|
106
|
+
```css
|
|
107
|
+
@supports not selector(:popover-open) {
|
|
108
|
+
.juno-menu[popover],
|
|
109
|
+
.juno-popover[popover],
|
|
110
|
+
.juno-tooltip__bubble[popover] {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This makes failure mode 1 the _only_ failure mode: the panel is absent instead
|
|
117
|
+
of invisibly present, the page underneath is fully usable, and the app can
|
|
118
|
+
detect the same condition in one line
|
|
119
|
+
(`CSS.supports('selector(:popover-open)')`) and render a non-popover fallback.
|
|
120
|
+
|
|
121
|
+
`@supports selector()` itself needs Safari 14.1; below that the guard's
|
|
122
|
+
condition is invalid, the block is skipped, and behaviour is exactly what it is
|
|
123
|
+
today. Fail-open, and far below any floor here.
|
|
124
|
+
|
|
125
|
+
### Anchored surfaces lose their anchor
|
|
126
|
+
|
|
127
|
+
Not a break — recorded here because it is the largest visual regression between
|
|
128
|
+
17.5 and 26.0 and it is easy to mistake for one.
|
|
129
|
+
|
|
130
|
+
`.juno-menu`, `.juno-popover` and the top-layer tooltip position themselves with
|
|
131
|
+
`position-area` / `position-try-fallbacks`, which is Safari **26.0**. Below it
|
|
132
|
+
those declarations drop and the surfaces keep `position: fixed; inset: auto;
|
|
133
|
+
margin: 0` — so they render at their static position rather than beside their
|
|
134
|
+
trigger. They still open, close, light-dismiss and trap nothing.
|
|
135
|
+
|
|
136
|
+
The documented remedy is the app's: pin `inset` on the element's `toggle` event.
|
|
137
|
+
It is already noted in [popover.md](./components/popover.md) and is the reason
|
|
138
|
+
the legacy `.juno-popover-anchor` wrapper is still exported.
|
|
139
|
+
|
|
140
|
+
## Why this document exists
|
|
141
|
+
|
|
142
|
+
Two silent failures of this exact class shipped and were found by looking at a
|
|
143
|
+
phone, not by any tool:
|
|
144
|
+
|
|
145
|
+
- **`backdrop-filter` unprefixed only.** Unsupported below Safari 18.0, so the
|
|
146
|
+
declaration was dropped and every glass surface — pillbar, dock, modal scrim —
|
|
147
|
+
rendered flat on iOS 17. Fixed 2026-08-05 by shipping
|
|
148
|
+
`-webkit-backdrop-filter` beside each use; both are in the bundle now, and the
|
|
149
|
+
audit table above tracks them as guarded.
|
|
150
|
+
- **`scrollbar-width: none`.** Unsupported below Safari 18.2, so the scrollable
|
|
151
|
+
tab strip kept a visible scrollbar on iOS 17 and 18.0–18.1. Fixed the same day
|
|
152
|
+
with a `::-webkit-scrollbar { display: none }` companion.
|
|
153
|
+
|
|
154
|
+
Neither produced an error. Both were "modern CSS, ships everywhere" right up
|
|
155
|
+
until someone opened the page on the device. That is the argument for stating a
|
|
156
|
+
floor and for auditing against the built bundle rather than against intent: the
|
|
157
|
+
cost of a missing feature is paid by the consumer, silently, at a time and place
|
|
158
|
+
junoui never sees.
|
|
159
|
+
|
|
160
|
+
**When you add a feature below the floor, say so here.** A new at-rule,
|
|
161
|
+
selector or property whose support is newer than Safari 17.5 belongs in the
|
|
162
|
+
audit table with its degrade-or-break verdict, and if the verdict is _break_ it
|
|
163
|
+
needs an `@supports` guard in the same change. Guards are for functional
|
|
164
|
+
failures only — a missing animation does not get one, and wrapping cosmetic
|
|
165
|
+
declarations in `@supports` costs bytes and buys nothing.
|
|
166
|
+
|
|
167
|
+
## Related
|
|
168
|
+
|
|
169
|
+
- [ios-conformance.md](./ios-conformance.md) — iOS metrics and behaviour: safe
|
|
170
|
+
areas, tap targets, the viewport-unit decision, and the Home-Screen standalone
|
|
171
|
+
letterbox.
|
|
172
|
+
- [getting-started.md](./getting-started.md) — the required `viewport` meta,
|
|
173
|
+
which is a separate silent failure with the same shape.
|
package/docs/getting-started.md
CHANGED
|
@@ -48,6 +48,28 @@ Two things depend on it, and both fail **silently**:
|
|
|
48
48
|
|
|
49
49
|
Details and sources: [ios-conformance.md](./ios-conformance.md).
|
|
50
50
|
|
|
51
|
+
## Browser support
|
|
52
|
+
|
|
53
|
+
| | Safari / iOS | Chrome / Edge | Firefox |
|
|
54
|
+
| ------------------------------------------- | ------------ | ------------- | ------- |
|
|
55
|
+
| **Supported floor** — everything works | **17.5** | 117 | 129 |
|
|
56
|
+
| **Hard floor** — below this, overlays break | 17.0 | 114 | 125 |
|
|
57
|
+
|
|
58
|
+
The hard floor is the Popover API. Below it, `.juno-menu`, `.juno-popover`, the
|
|
59
|
+
top-layer tooltip and the pillbar's overflow slot cannot open; junoui hides them
|
|
60
|
+
with an `@supports` guard so they are absent rather than invisible-and-blocking,
|
|
61
|
+
and your app can branch on `CSS.supports('selector(:popover-open)')`. Between
|
|
62
|
+
the two floors the only loss is entry animations (`@starting-style`). Anchored
|
|
63
|
+
placement for those surfaces is a Safari 26 feature — below it they open at
|
|
64
|
+
their static position, and the app can pin `inset` on the `toggle` event.
|
|
65
|
+
|
|
66
|
+
The hard floor is what `package.json`'s `browserslist` declares. junoui runs no
|
|
67
|
+
autoprefixer of its own; the field is there for your build tooling.
|
|
68
|
+
|
|
69
|
+
**Every one of these failures is silent** — an engine drops CSS it does not
|
|
70
|
+
understand without reporting anything. The full audit, per feature, with the
|
|
71
|
+
degrade-vs-break verdict: [browser-support.md](./browser-support.md).
|
|
72
|
+
|
|
51
73
|
## The model
|
|
52
74
|
|
|
53
75
|
- **Palette** — `standard` · `colorblind` · `soft`
|
package/docs/icon-subsetting.md
CHANGED
|
@@ -57,3 +57,32 @@ const iconSubset = {
|
|
|
57
57
|
Keep the manifest where the app's own type for icon names is derived from it,
|
|
58
58
|
so a name that is not in the manifest is a **compile** error rather than a
|
|
59
59
|
blank space at runtime.
|
|
60
|
+
|
|
61
|
+
## Injecting a subset
|
|
62
|
+
|
|
63
|
+
Safari intermittently drops external sprite refs (`<use href="file.svg#id">`),
|
|
64
|
+
so the reliable path is a **same-document** ref — which means the sprite has to
|
|
65
|
+
live in the document. `junoui/icons/inline` does that for the full set and
|
|
66
|
+
auto-installs on import; a consumer that subsets wants the injection without
|
|
67
|
+
the 25 kB of symbols, so the mechanism ships separately:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import { installSprite } from '@junoput01/junoui/icons/install';
|
|
71
|
+
import sprite from 'virtual:my-icon-subset'; // your build's subset (see above)
|
|
72
|
+
|
|
73
|
+
installSprite(sprite);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Export | Carries | Use |
|
|
77
|
+
| ---------------------- | ------------------------- | --------------------------------------------- |
|
|
78
|
+
| `junoui/icons/install` | ~1 kB, no icons | You subset. Pass your own sprite. |
|
|
79
|
+
| `junoui/icons/inline` | the full 66-symbol sprite | You don't subset. Import for the side effect. |
|
|
80
|
+
|
|
81
|
+
- **Both are id-guarded on the same `juno-icon-sprite` holder**, so importing
|
|
82
|
+
the full module alongside a subset does not produce two hidden holders
|
|
83
|
+
shadowing each other — whichever installs first wins and the second call is a
|
|
84
|
+
no-op returning `false`.
|
|
85
|
+
- `installSprite` returns `true` when it injected, `false` when a sprite was
|
|
86
|
+
already present or there is no document (server-side, pre-hydration).
|
|
87
|
+
- Subsetting only pays off if the full module never enters the bundle: import
|
|
88
|
+
`icons/install`, not `icons/inline`.
|
package/docs/ios-conformance.md
CHANGED
|
@@ -7,6 +7,10 @@ Most numbers the design community attributes to Apple are not in Apple's text.
|
|
|
7
7
|
This page exists so nobody re-derives folklore, and so nobody "fixes" a correct
|
|
8
8
|
value into a wrong one later.
|
|
9
9
|
|
|
10
|
+
This page is about **metrics and behaviour** on iOS. For _which iOS versions
|
|
11
|
+
junoui runs on at all_ — the supported floor, what degrades below it, and what
|
|
12
|
+
breaks — see [browser-support.md](./browser-support.md).
|
|
13
|
+
|
|
10
14
|
> **Verifying anything here.** Apple's HIG is a JavaScript app: a plain `curl`
|
|
11
15
|
> returns an empty shell. Check the backing DocC JSON instead —
|
|
12
16
|
> `developer.apple.com/tutorials/data/design/human-interface-guidelines/<page>.json`.
|
|
@@ -157,11 +161,10 @@ to `lv*`** — that is the spec-level cause of the classic `100vh` overflow: a
|
|
|
157
161
|
junoui uses **zero raw `vh`**. Full-height surfaces use `dvh`
|
|
158
162
|
(`layout.css`, `drawer.css`) and `85dvh` caps the bottom sheet.
|
|
159
163
|
|
|
160
|
-
Caveats
|
|
164
|
+
Caveats that constrain the choice:
|
|
161
165
|
|
|
162
166
|
- `dv*` is explicitly **not stable** and not guaranteed to update every frame,
|
|
163
|
-
so it can churn while the address bar collapses. `sv*`
|
|
164
|
-
a surface must never overflow.
|
|
167
|
+
so it can churn while the address bar collapses. `sv*` never overflows.
|
|
165
168
|
- iOS shipped viewport-unit bugs into the **iOS 26** era: Safari 26.0 fixed
|
|
166
169
|
`lvh`/`vh` being sized against the _small_ viewport in `SFSafariViewController`.
|
|
167
170
|
The underlying WebKit bug (255708, filed 2023) is **still open**, so Apple's
|
|
@@ -176,6 +179,193 @@ Caveats worth knowing before changing any of that:
|
|
|
176
179
|
[Safari 26.0 release notes](https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes),
|
|
177
180
|
[WebKit bug 255708](https://bugs.webkit.org/show_bug.cgi?id=255708).
|
|
178
181
|
|
|
182
|
+
### The decision: `dvh` stays at both call sites
|
|
183
|
+
|
|
184
|
+
Decided 2026-08-15 (ticket 20260803-033, step 1). `dvh` is kept for **both**
|
|
185
|
+
`.juno-app-shell` and `.juno-drawer`, against the "`sv*` is the calm choice"
|
|
186
|
+
instinct above. The reasoning below is the part to read: it generalises, the
|
|
187
|
+
verdict does not.
|
|
188
|
+
|
|
189
|
+
**First, price the two options at the only moment they differ** — when browser
|
|
190
|
+
chrome retracts. There is no other moment. Before it and after it, whichever
|
|
191
|
+
unit you picked is simply the current viewport.
|
|
192
|
+
|
|
193
|
+
| | what it costs when chrome retracts |
|
|
194
|
+
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
195
|
+
| `dvh` | The surface **grows**, so it relayouts. `dv*` is not frame-guaranteed, so that growth can arrive late or in steps. If the surface contains a scroller, the scroll port changes height _during the scroll that caused the retraction_. |
|
|
196
|
+
| `svh` | The surface **does not move** — and is now short by `lvh − svh`. That gap is permanent for the rest of the session, and it is at the bottom edge, where the page background shows through under whatever the surface pinned there. |
|
|
197
|
+
|
|
198
|
+
So this is not "stable vs twitchy". It is **one relayout** against **a permanent
|
|
199
|
+
dead strip**. Pick by asking which of those the surface can afford.
|
|
200
|
+
|
|
201
|
+
**`.juno-app-shell` — `block-size: 100dvh` (`layout.css`).** The shell's whole
|
|
202
|
+
job is to hold the dock against the bottom edge. Under `svh` the dock detaches
|
|
203
|
+
from that edge the first time the user scrolls and stays detached, floating
|
|
204
|
+
`lvh − svh` px up with page background beneath it — a visible defect on every
|
|
205
|
+
subsequent frame, under the primary navigation. The `dvh` relayout it avoids is
|
|
206
|
+
cheap here by construction: the shell is `overflow: hidden` and the scrolling
|
|
207
|
+
lives in `.juno-app-shell__main`, so growing the shell grows the scroll port and
|
|
208
|
+
moves the dock. **No text reflows** — no line breaking, no measured content, no
|
|
209
|
+
intrinsic sizing is touched. One cheap relayout beats a permanent gap.
|
|
210
|
+
|
|
211
|
+
**`.juno-drawer` — `block-size: 100dvh; max-block-size: 100dvh`
|
|
212
|
+
(`drawer.css`), and the same for the `--bottom` sheet's `60dvh`/`92dvh` and the
|
|
213
|
+
modal's `85dvh` cap.** Here the `dv*` instability **cannot bite at all**, and
|
|
214
|
+
that is the whole argument. The drawer is a `<dialog>` opened with
|
|
215
|
+
`showModal()`: it is in the top layer and the document beneath it is inert. iOS
|
|
216
|
+
Safari retracts and expands chrome in response to _document_ scroll — an inner
|
|
217
|
+
overflow scroller does not drive it — so chrome cannot change state for the
|
|
218
|
+
drawer's whole lifetime. `dvh` therefore never churns, and `svh` cannot prevent
|
|
219
|
+
an overflow that cannot happen. What `svh` _would_ still do is leave the gap:
|
|
220
|
+
open the drawer after scrolling (the ordinary case — you scroll, then reach for
|
|
221
|
+
the menu) and chrome is already retracted, so a `svh` drawer stops
|
|
222
|
+
`lvh − svh` px short of the bottom of a screen it is supposed to fill. `svh`
|
|
223
|
+
here is a pure loss.
|
|
224
|
+
|
|
225
|
+
> **The exception, and it is the app's to own.** A `<dialog>` opened with the
|
|
226
|
+
> `open` **attribute** instead of `showModal()` is _not_ in the top layer and
|
|
227
|
+
> does **not** block page scroll. Behind such a drawer the page scrolls, chrome
|
|
228
|
+
> retracts, and the churn is live. junoui's CSS cannot tell the two open paths
|
|
229
|
+
> apart. The drawer is documented as `showModal()`
|
|
230
|
+
> ([drawer.md](./components/drawer.md)); open it non-modally and the churn is yours.
|
|
231
|
+
|
|
232
|
+
### The rule, for a component that does not exist yet
|
|
233
|
+
|
|
234
|
+
Ask one question — **can browser chrome change state while this surface is on
|
|
235
|
+
screen?** — and then:
|
|
236
|
+
|
|
237
|
+
1. **It can, and the surface is anchored to the bottom edge** (or contains
|
|
238
|
+
something that is: a dock, a pillbar, a sticky footer) → **`dvh`**. You are
|
|
239
|
+
buying edge-adherence and paying one relayout per chrome transition.
|
|
240
|
+
2. **It can, and the surface must not resize once laid out** — content whose
|
|
241
|
+
height JS measures, a canvas, an animation mid-flight, anything where a
|
|
242
|
+
late reflow is worse than a gap → **`svh`**. You are buying stability and
|
|
243
|
+
paying up to `lvh − svh` of dead space, permanently.
|
|
244
|
+
3. **It cannot** — top-layer surfaces (`showModal()` dialogs, `popover`), where
|
|
245
|
+
the page beneath is inert → **`dvh`**, always. `svh` there buys nothing and
|
|
246
|
+
still pays the gap on any surface opened while chrome is retracted.
|
|
247
|
+
4. **Never `vh` / `lvh` for a height that must fit.** `vh == lvh` sizes as if
|
|
248
|
+
chrome were retracted, so on a page where it is _not_, the box overflows by
|
|
249
|
+
exactly the chrome's height. That is the classic `100vh` bug and it is a
|
|
250
|
+
spec consequence, not a browser bug. The one legitimate `lvh` in this
|
|
251
|
+
codebase is the standalone unlock's spacer (`base.css`), which is _supposed_
|
|
252
|
+
to overflow.
|
|
253
|
+
|
|
254
|
+
**In `display-mode: standalone` this whole decision is moot** — there is no
|
|
255
|
+
retractable chrome, so `svh == dvh`. Measured on the device: `100dvh`,
|
|
256
|
+
`100svh` and `100%` all resolve to 812 while `100lvh` and `100vh` resolve to
|
|
257
|
+
874, which is the letterbox defect below, not a chrome transition.
|
|
258
|
+
|
|
259
|
+
### A note on raw `vw`
|
|
260
|
+
|
|
261
|
+
"Zero raw `vh`" is about **`vh`**. junoui does use raw `vw` in five places
|
|
262
|
+
(`layout.css:18`, `pillbar.css:231`, `popover.css:33`, `toast.css:26`,
|
|
263
|
+
`drawer.css:116`) and that is deliberate: horizontal chrome does not retract,
|
|
264
|
+
so `vw`/`lvw`/`svw`/`dvw` are the same number and the `vh` trap has no
|
|
265
|
+
horizontal twin. The real `vw` hazard is different — `100vw` includes the
|
|
266
|
+
classic scrollbar gutter, so a full-bleed `100vw` box overflows a desktop page
|
|
267
|
+
that has a scrollbar. Every call site above either caps well below `100vw`
|
|
268
|
+
(`min(…, 240px)`, `min(360px, …)`, `85vw`, `clamp()`) or subtracts more than a
|
|
269
|
+
scrollbar's width (`calc(100vw - var(--juno-space-24))`). Keep it that way; a
|
|
270
|
+
bare `inline-size: 100vw` is a bug.
|
|
271
|
+
|
|
272
|
+
## Home-Screen standalone: the letterbox, and why `base.css` unlocks it
|
|
273
|
+
|
|
274
|
+
**The fact, and it is the most expensive thing this codebase has learned about
|
|
275
|
+
iOS: in `display-mode: standalone`, iOS sizes the window from the document's
|
|
276
|
+
RESTING scrollability at launch, and letterboxes a document that cannot scroll
|
|
277
|
+
by exactly `env(safe-area-inset-top)`.**
|
|
278
|
+
|
|
279
|
+
Measured, not inferred. iPhone 16 Pro (402×874 pt), iOS 18.7 / Safari 26.6:
|
|
280
|
+
|
|
281
|
+
| | |
|
|
282
|
+
| ---------------------------------------------- | ------- |
|
|
283
|
+
| `screen.height` | **874** |
|
|
284
|
+
| `window.innerHeight` · `visualViewport.height` | **812** |
|
|
285
|
+
| `100dvh` · `100svh` · `100%` | **812** |
|
|
286
|
+
| `100lvh` · `100vh` | **874** |
|
|
287
|
+
| `env(safe-area-inset-top)` | **62** |
|
|
288
|
+
| `env(safe-area-inset-bottom)` | 34 |
|
|
289
|
+
| `window.screenY` | 0 |
|
|
290
|
+
|
|
291
|
+
`874 − 812 = 62 = env(safe-area-inset-top)`, exactly. WebKit sizes the
|
|
292
|
+
standalone window as if a retractable toolbar existed, subtracts its height,
|
|
293
|
+
pins the window to the **top**, and then never covers the strip it reserved — so
|
|
294
|
+
the bottom 62 px of the display sits outside the web view and paints black on
|
|
295
|
+
every screen. This is a spec violation on its face: in standalone there is no
|
|
296
|
+
retractable browser UI, so the large and dynamic viewports **must** be equal
|
|
297
|
+
(css-values-4 §6.1.2.1), and here they differ by 62.
|
|
298
|
+
|
|
299
|
+
### It is the resting structure, and only the resting structure
|
|
300
|
+
|
|
301
|
+
One install, four document structures, switched by a pill and **persisted across
|
|
302
|
+
cold launches** (`localStorage`), verdict taken per launch:
|
|
303
|
+
|
|
304
|
+
| document structure at rest | window |
|
|
305
|
+
| --------------------------------------------------------------------------------------- | --------------------- |
|
|
306
|
+
| document scrolls normally | **874** — full screen |
|
|
307
|
+
| fixed shell, an inner scroller, document cannot scroll | 812 — letterboxed |
|
|
308
|
+
| fixed shell, nothing scrollable anywhere | 812 — letterboxed |
|
|
309
|
+
| fixed shell **+ the document left scrollable behind it** by an invisible in-flow spacer | **874** — full screen |
|
|
310
|
+
|
|
311
|
+
**Transient scrollability is not enough.** Seven in-page interventions across
|
|
312
|
+
five controlled runs — with a placebo pinned to the first slot, rotation of the
|
|
313
|
+
rest, and `prior`/`during`/`afterUndo` sampling — all measured 812. Every one of
|
|
314
|
+
them varied scrollability for ~300 ms mid-session and undid itself. iOS samples
|
|
315
|
+
the structure at launch; the axis that decides the window was never varied.
|
|
316
|
+
|
|
317
|
+
Two device-proven negatives, recorded so nobody spends another round on them:
|
|
318
|
+
|
|
319
|
+
- **`apple-mobile-web-app-status-bar-style` makes no difference.** `black` and
|
|
320
|
+
`black-translucent` were each tested with a fresh Home-Screen install. Both
|
|
321
|
+
letterbox identically.
|
|
322
|
+
- **Nothing applied after first paint reaches it** — see the seven interventions
|
|
323
|
+
above. The window does correct itself to 874 spontaneously, between 6 seconds
|
|
324
|
+
and 43 minutes after navigation, and then holds for the life of that document;
|
|
325
|
+
a reload starts a new document, which starts letterboxed again.
|
|
326
|
+
|
|
327
|
+
### What junoui does about it
|
|
328
|
+
|
|
329
|
+
`base.css` carries the **iOS standalone letterbox unlock**: keep the document
|
|
330
|
+
scrollable behind the app, using an invisible `body::after` spacer taller than
|
|
331
|
+
the large viewport, so the document always overflows whatever window iOS grants.
|
|
332
|
+
`overscroll-behavior: none` stops the ghost scroller rubber-banding; apps put
|
|
333
|
+
`overscroll-behavior: contain` on their real scrollers so an inner fling never
|
|
334
|
+
chains into it.
|
|
335
|
+
|
|
336
|
+
The gate is three conditions, all required — `display-mode: standalone` (only
|
|
337
|
+
installed apps letterbox), `pointer: coarse` (keeps macOS Dock apps out), and
|
|
338
|
+
`@supports (-webkit-touch-callout: none)` (iOS/iPadOS WebKit only). Selectors
|
|
339
|
+
carry `html:root` (specificity 0,1,2) deliberately: app resets commonly declare
|
|
340
|
+
`body { overflow: hidden }` at (0,0,1) _after_ this sheet, and the unlock has to
|
|
341
|
+
win the cascade without `!important`.
|
|
342
|
+
|
|
343
|
+
Consumer obligations, both silent if missed:
|
|
344
|
+
|
|
345
|
+
- **Ship the unlock at first parse.** iOS samples at launch, and a bundled
|
|
346
|
+
stylesheet arrives after it. An app whose shell paints before its CSS bundle
|
|
347
|
+
must inline a copy of the unlock in the document head — junoui's copy in
|
|
348
|
+
`juno.css` is too late on its own.
|
|
349
|
+
- **Do not override `body::after`.** The spacer is `body::after`; a consumer
|
|
350
|
+
that needs that pseudo-element for itself must reproduce the spacer at the
|
|
351
|
+
same gate. None of junoui's own components use it.
|
|
352
|
+
|
|
353
|
+
### How to know it is fixed upstream
|
|
354
|
+
|
|
355
|
+
In standalone, `window.innerHeight === screen.height` **and
|
|
356
|
+
`100lvh === 100dvh`**, for a document that **cannot** scroll — that is the case
|
|
357
|
+
that still misbehaves. When that holds, the unlock is dead weight and can be
|
|
358
|
+
removed. Until then it is harmless where it does not apply, because the gate
|
|
359
|
+
excludes every non-iOS and non-installed context.
|
|
360
|
+
|
|
361
|
+
- Sources: nexora `CLAUDE.md` §15, entry dated 2026-08-13; the four-mode testbed
|
|
362
|
+
`web/public/expansion-demo.html` (kept as a standing rig); 201 device readings
|
|
363
|
+
collected by `scripts/viewport_probe_collect.py`. Tracked upstream as ticket
|
|
364
|
+
20260812-006, drafted for WebKit Bugzilla / Feedback Assistant and awaiting
|
|
365
|
+
filing. Related Apple Developer Forums threads: 800798, 798014 (iOS 26
|
|
366
|
+
safe-area insets wrong until a background/resume — the "corrects itself later"
|
|
367
|
+
shape matches exactly), 797124.
|
|
368
|
+
|
|
179
369
|
## Typography
|
|
180
370
|
|
|
181
371
|
iOS: **17 pt default body size, 11 pt minimum**; Dynamic Type must accommodate
|
|
@@ -194,6 +384,14 @@ iOS Safari is widely observed to zoom the page onto a focused field under 16px.
|
|
|
194
384
|
behavior, not published spec. The mitigation is harmless, so it stays — but do
|
|
195
385
|
not cite it as documented, and re-verify it on iOS 26.
|
|
196
386
|
|
|
387
|
+
The floor is now a checked claim, not an asserted one:
|
|
388
|
+
`test/visual/tap-targets.spec.mjs` reads the computed `font-size` of a
|
|
389
|
+
`.juno-input` under the coarse-pointer Playwright project and asserts `>= 16`.
|
|
390
|
+
That check is also what found the floor had never worked — the rule sat in
|
|
391
|
+
`base.css`'s `@media (pointer: coarse)` block, where a media query adds no
|
|
392
|
+
specificity, so `components/input.css`'s own `.juno-input` font-size beat it on
|
|
393
|
+
source order. It now lives in `input.css`. Keep it there.
|
|
394
|
+
|
|
197
395
|
## Things a stylesheet controls that Apple says nothing about
|
|
198
396
|
|
|
199
397
|
No primary Apple source was found for any of: `touch-action`,
|
|
@@ -214,7 +412,11 @@ One confirmed change raises the stakes: as of iOS/iPadOS 26, **every website
|
|
|
214
412
|
added to the Home Screen opens as a web app by default** — "there are now zero
|
|
215
413
|
requirements for 'installability'". junoui's CSS may therefore run in a
|
|
216
414
|
standalone context, where `viewport-fit` and `env()` govern home-indicator and
|
|
217
|
-
Dynamic Island clearance, for sites that never opted in.
|
|
415
|
+
Dynamic Island clearance, for sites that never opted in. The concrete
|
|
416
|
+
consequence is measured above in
|
|
417
|
+
[Home-Screen standalone: the letterbox](#home-screen-standalone-the-letterbox-and-why-basecss-unlocks-it) —
|
|
418
|
+
a site that never asked to be a web app now inherits both the letterbox and the
|
|
419
|
+
unlock.
|
|
218
420
|
|
|
219
421
|
Unconfirmed leads, tracked in ticket 20260803-034: `vh` reportedly pinning to
|
|
220
422
|
`window.outerHeight`; three new tab modes yielding different `innerHeight`; a
|
package/docs/layout.md
CHANGED
|
@@ -269,3 +269,38 @@ You opt in per region; nothing forces a markup shape.
|
|
|
269
269
|
These are all CSS. Behavior that needs state (resize observers feeding app state,
|
|
270
270
|
virtualization, drag-resizable panels) belongs in your app or a sibling
|
|
271
271
|
`junoui-<framework>` package — not the design system.
|
|
272
|
+
|
|
273
|
+
## Floating-nav clearance
|
|
274
|
+
|
|
275
|
+
A scroller that a fixed dock or pillbar floats over reserves room at its foot so
|
|
276
|
+
the last row clears the overlay:
|
|
277
|
+
|
|
278
|
+
```css
|
|
279
|
+
.my-scroller {
|
|
280
|
+
padding-block-end: var(--juno-dock-clearance);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The value is **derived from the control it clears**, not a constant:
|
|
285
|
+
|
|
286
|
+
| Prop | Is |
|
|
287
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------ |
|
|
288
|
+
| `--juno-dock-h` | the floating pill's laid-out height — bubble + item padding + pill padding + border |
|
|
289
|
+
| `--juno-pillbar-h` | the pillbar's, one size down (no bubble) |
|
|
290
|
+
| `--juno-dock-clearance-scale` | multiplier on that height, default `1` — see below |
|
|
291
|
+
| `--juno-dock-clearance` | `height × scale + space-16 (the bar's own margin) + space-8 + env(safe-area-inset-bottom)` |
|
|
292
|
+
|
|
293
|
+
**Why derived matters.** Set `--juno-size-tap-comfortable` at `:root` — which
|
|
294
|
+
junoui invites, and which is the whole point of a token — and the pill grows;
|
|
295
|
+
the clearance grows with it. The previous constant did not, and past a 58px
|
|
296
|
+
bubble it reserved less than the pill's own height plus its margin, so content
|
|
297
|
+
hid under the dock with nothing reporting it.
|
|
298
|
+
|
|
299
|
+
**The scale knob is for bars that shrink while scrolling.** Reserve at the SMALL
|
|
300
|
+
size by setting `--juno-dock-clearance-scale` to your compact factor. Reserving
|
|
301
|
+
at the live size means the reservation changes after the last scroll event, and
|
|
302
|
+
content relayouts under a finger that has already stopped moving.
|
|
303
|
+
|
|
304
|
+
**Custom properties resolve where they are declared.** Override
|
|
305
|
+
`--juno-size-tap-comfortable` at `:root` (or override `--juno-dock-h` directly);
|
|
306
|
+
overriding it on a subtree does not re-derive a token computed at `:root`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junoput01/junoui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "junoui — a token-driven design system. Color carries semantic meaning, never decoration: every hue has one assigned role (NOMINAL / ACTIVE / TARGET / CAUTION / WARNING). Ships multi-platform tokens (CSS, SCSS, JS/TS, JSON, Android, iOS, Flutter) plus a framework-agnostic CSS component layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"./icons": "./dist/icons/juno-icons.svg",
|
|
52
52
|
"./subset": "./tools/subset-sprite.mjs",
|
|
53
53
|
"./icons/inline": "./dist/icons/inline.js",
|
|
54
|
+
"./icons/install": "./dist/icons/install.js",
|
|
54
55
|
"./package.json": "./package.json"
|
|
55
56
|
},
|
|
56
57
|
"files": [
|
|
@@ -80,6 +81,7 @@
|
|
|
80
81
|
"showcase": "npm run build && node scripts/serve.mjs",
|
|
81
82
|
"test:visual": "playwright test",
|
|
82
83
|
"test:visual:update": "playwright test --update-snapshots",
|
|
84
|
+
"gate:consumer": "node scripts/consumer-gate.mjs",
|
|
83
85
|
"changeset": "changeset",
|
|
84
86
|
"version": "changeset version",
|
|
85
87
|
"release": "npm run build && changeset publish"
|
|
@@ -94,5 +96,12 @@
|
|
|
94
96
|
},
|
|
95
97
|
"engines": {
|
|
96
98
|
"node": ">=18"
|
|
97
|
-
}
|
|
99
|
+
},
|
|
100
|
+
"browserslist": [
|
|
101
|
+
"iOS >= 17.0",
|
|
102
|
+
"Safari >= 17.0",
|
|
103
|
+
"Chrome >= 114",
|
|
104
|
+
"Edge >= 114",
|
|
105
|
+
"Firefox >= 125"
|
|
106
|
+
]
|
|
98
107
|
}
|
package/src/css/base.css
CHANGED
|
@@ -19,13 +19,48 @@
|
|
|
19
19
|
/* Floating-nav scroll clearance — a page/scroller that a fixed dock or
|
|
20
20
|
pillbar floats over reserves this much room at its foot so the last row
|
|
21
21
|
clears the overlay. Consumers write `padding-block-end:
|
|
22
|
-
var(--juno-dock-clearance)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
var(--juno-dock-clearance)`. Web-only (they carry env()), so they live in
|
|
23
|
+
the CSS layer, not the cross-platform token set.
|
|
24
|
+
|
|
25
|
+
DERIVED, not constant. These used to be `space-72 + space-20 + env()`,
|
|
26
|
+
which promised in this very comment to "stay correct when the dock
|
|
27
|
+
geometry changes" and could not: the floating pill measures 62px with the
|
|
28
|
+
default 44px bubble, and junoui explicitly invites a consumer to
|
|
29
|
+
parameterize that bubble (--juno-size-tap-comfortable). Past a 58px bubble
|
|
30
|
+
the old constant reserved LESS than the pill's own height plus its margin
|
|
31
|
+
— content hid under the dock, silently, on exactly the consumers who took
|
|
32
|
+
the invitation (20260815-026).
|
|
33
|
+
|
|
34
|
+
So the parts are named and the arithmetic is visible:
|
|
35
|
+
--juno-dock-h the pill's laid-out height: the bubble, the item's
|
|
36
|
+
padding-block, the pill's own padding, its border.
|
|
37
|
+
* -clearance-scale consumers that SHRINK the bar while scrolling
|
|
38
|
+
reserve at the small size, not the live one — a
|
|
39
|
+
reservation that tracks a value which changes after
|
|
40
|
+
the last scroll event relayouts content under a
|
|
41
|
+
finger that has already stopped moving. Default 1.
|
|
42
|
+
+ space-16 + env() the pill's own bottom margin (dock.css), additive
|
|
43
|
+
because a floating element sits OFF the edge —
|
|
44
|
+
docs/ios-conformance.md, "max() vs addition".
|
|
45
|
+
+ space-8 breathing room, so the last row is not flush. */
|
|
46
|
+
--juno-dock-h: calc(
|
|
47
|
+
var(--juno-size-tap-comfortable) + 4 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
48
|
+
);
|
|
49
|
+
--juno-dock-clearance-scale: 1;
|
|
50
|
+
--juno-dock-clearance: calc(
|
|
51
|
+
var(--juno-dock-h) * var(--juno-dock-clearance-scale) + var(--juno-space-16) +
|
|
52
|
+
var(--juno-space-8) + env(safe-area-inset-bottom, 0px)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/* The pillbar is the same shape one size down: no bubble, so its height is
|
|
56
|
+
the tap target plus its own padding and border. */
|
|
57
|
+
--juno-pillbar-h: calc(
|
|
58
|
+
var(--juno-size-tap-comfortable) + 2 * var(--juno-space-4) + 2 * var(--juno-border-width-1)
|
|
59
|
+
);
|
|
60
|
+
--juno-pillbar-clearance: calc(
|
|
61
|
+
var(--juno-pillbar-h) * var(--juno-dock-clearance-scale) + var(--juno-space-16) +
|
|
62
|
+
var(--juno-space-8) + env(safe-area-inset-bottom, 0px)
|
|
63
|
+
);
|
|
29
64
|
|
|
30
65
|
/* JS-readable motion contract. CSS-only `prefers-reduced-motion` never reaches
|
|
31
66
|
imperative JS (scrollTo/scrollIntoView smooth behavior, rAF-driven transforms,
|
|
@@ -127,8 +162,11 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
127
162
|
is harmless either way: hold text-entry controls (.juno-input covers input,
|
|
128
163
|
textarea and the select's inner control) at a 16px floor on touch. max()
|
|
129
164
|
keeps the floor even under a scaled-down --juno-font-scale, and still grows
|
|
130
|
-
when scaled up.
|
|
131
|
-
|
|
165
|
+
when scaled up.
|
|
166
|
+
THE RULE ITSELF LIVES IN components/input.css, not here: a media query adds
|
|
167
|
+
no specificity, so this file's `.juno-input` would lose to input.css's own
|
|
168
|
+
`.juno-input` font-size later in the bundle. It did, silently, until
|
|
169
|
+
20260815-006's coarse-pointer project measured it. */
|
|
132
170
|
|
|
133
171
|
/* Kill the UA tap-highlight square on the interactive surfaces so it never
|
|
134
172
|
flashes past a rounded control on tap. Consumers were adding this by hand
|
|
@@ -229,17 +267,11 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
229
267
|
:root { --juno-border: var(--juno-border-strong); }
|
|
230
268
|
}
|
|
231
269
|
|
|
232
|
-
/* Windows High Contrast / forced-colors:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.juno-badge,
|
|
238
|
-
.juno-btn,
|
|
239
|
-
.juno-card,
|
|
240
|
-
.juno-readout { border: 1px solid CanvasText; }
|
|
241
|
-
.juno-badge { forced-color-adjust: none; } /* keep status fill meaningful */
|
|
242
|
-
}
|
|
270
|
+
/* Windows High Contrast / forced-colors: the opt-in lives in
|
|
271
|
+
overrides.css, NOT here. A media query adds no specificity, and this file is
|
|
272
|
+
bundled BEFORE components/, so `.juno-btn { border: … }` in button.css beat
|
|
273
|
+
the forced-colors border every time — same 0,1,0 selector, later in the
|
|
274
|
+
bundle. See src/css/overrides.css and ticket 20260815-029. */
|
|
243
275
|
|
|
244
276
|
/* ── iOS standalone letterbox unlock ─────────────────────────────────────────
|
|
245
277
|
iOS WebKit (observed through 26.6, iPhone 16 Pro, iOS 18.7) sizes a
|
|
@@ -288,3 +320,30 @@ code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
|
288
320
|
}
|
|
289
321
|
}
|
|
290
322
|
}
|
|
323
|
+
|
|
324
|
+
/* ── Popover API absent (Safari/iOS < 17.0) ──────────────────────────────────
|
|
325
|
+
junoui's overlay surfaces are built on the native Popover API. Without it
|
|
326
|
+
`popovertarget` does nothing, so they cannot open — an inconvenience. The
|
|
327
|
+
part that is a BUG is that the UA rule which hides a closed popover
|
|
328
|
+
(`[popover]:not(:popover-open) { display: none }`) does not exist either, so
|
|
329
|
+
the panel participates in layout: .juno-menu and .juno-popover are
|
|
330
|
+
`position: fixed` with `opacity: 0` and no pointer-events reset, i.e. a
|
|
331
|
+
256-280px INVISIBLE panel parked at its static position, swallowing taps on
|
|
332
|
+
whatever it covers. (.juno-tooltip__bubble escapes it — it inherits
|
|
333
|
+
`pointer-events: none` from the CSS-only tooltip rule — and is listed anyway
|
|
334
|
+
so the set matches the docs.)
|
|
335
|
+
|
|
336
|
+
So: hide them. Absent beats invisibly-present. Apps detect the same condition
|
|
337
|
+
with CSS.supports('selector(:popover-open)') and render a fallback.
|
|
338
|
+
|
|
339
|
+
@supports selector() is itself Safari 14.1+; below that this condition is
|
|
340
|
+
invalid, the block is skipped, and behaviour is what it is today. Fail-open,
|
|
341
|
+
and far below any supported floor. Guards live here rather than per component
|
|
342
|
+
so the next popover-based component inherits it — see docs/browser-support.md
|
|
343
|
+
for the rule about when a feature gap earns a guard at all (functional
|
|
344
|
+
failures only; a missing animation does not get one). */
|
|
345
|
+
@supports not selector(:popover-open) {
|
|
346
|
+
.juno-menu[popover],
|
|
347
|
+
.juno-popover[popover],
|
|
348
|
+
.juno-tooltip__bubble[popover] { display: none; }
|
|
349
|
+
}
|
|
@@ -28,6 +28,18 @@
|
|
|
28
28
|
color: var(--juno-data);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* The iOS focus-zoom floor, and it has to live HERE, not in base.css's
|
|
32
|
+
`@media (pointer: coarse)` block where it was written. A media query adds no
|
|
33
|
+
specificity, so `.juno-input { font-size: var(--juno-font-size-14) }` above —
|
|
34
|
+
same 0,1,0 selector, later in the bundle (base.css sorts before components/)
|
|
35
|
+
— simply won it, and the floor never applied on any touch device. Nothing
|
|
36
|
+
caught it because the suite had no coarse-pointer project until 20260815-006
|
|
37
|
+
added one; the numeric assertion in test/visual/tap-targets.spec.mjs is what
|
|
38
|
+
surfaced it. See base.css for the sourcing caveat on the behavior itself. */
|
|
39
|
+
@media (pointer: coarse) {
|
|
40
|
+
.juno-input { font-size: max(16px, var(--juno-font-size-16)); }
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
.juno-input--sans { font-family: var(--juno-font-family-sans); }
|
|
32
44
|
|
|
33
45
|
.juno-input::placeholder { color: var(--juno-muted); }
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
<svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-dots-three" /></svg>
|
|
258
258
|
</button>
|
|
259
259
|
<div class="juno-pillbar__tray">
|
|
260
|
-
<div
|
|
260
|
+
<div>\3c !-- single wrapper, any element -->
|
|
261
261
|
…the usual __item / __sep / __input children…
|
|
262
262
|
</div>
|
|
263
263
|
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2
|
+
* Overrides — cross-cutting gates, bundled LAST
|
|
3
|
+
* ════════════════════════════════════════════════════════════════════
|
|
4
|
+
* A @media or @supports block adds NO specificity. So a gate written in
|
|
5
|
+
* base.css — which the bundler emits before components/ — loses to any
|
|
6
|
+
* component rule declaring the same property on the same selector, purely
|
|
7
|
+
* on source order, and loses SILENTLY: the gate looks right in the file it
|
|
8
|
+
* was written in, and there is no error anywhere.
|
|
9
|
+
*
|
|
10
|
+
* Four instances of that shipped before this file existed (2026-08-15):
|
|
11
|
+
* - .juno-input's 16px iOS focus-zoom floor, beaten by input.css. The floor
|
|
12
|
+
* never applied on any touch device, in any release (20260815-011).
|
|
13
|
+
* - the forced-colors border below, beaten by button.css / badge.css /
|
|
14
|
+
* card.css (20260815-029).
|
|
15
|
+
* - the Popover fallback, which happens to win today only because nothing
|
|
16
|
+
* later declares `display` on those selectors — pinned by a test rather
|
|
17
|
+
* than by structure (20260815-013).
|
|
18
|
+
*
|
|
19
|
+
* So: gates whose whole job is to BEAT a component default live here, after
|
|
20
|
+
* everything they guard. Same reasoning the bundler already applies to
|
|
21
|
+
* utilities.css (role helpers must outrank component defaults) — see
|
|
22
|
+
* scripts/bundle-css.mjs.
|
|
23
|
+
*
|
|
24
|
+
* What does NOT belong here: a gate that only has to beat the UA or another
|
|
25
|
+
* rule in its own file. Keep those next to what they modify; this file is for
|
|
26
|
+
* the cross-file case, or it becomes a dumping ground and the cascade gets
|
|
27
|
+
* harder to read rather than easier.
|
|
28
|
+
*
|
|
29
|
+
* A build test (test/build.test.mjs) asserts the bundle contains no gated
|
|
30
|
+
* declaration that a later ungated rule overrides at equal-or-lower
|
|
31
|
+
* specificity. It asserts an EMPTY SET — no allowlist, because an allowlist
|
|
32
|
+
* is where violations go to be forgotten.
|
|
33
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
34
|
+
|
|
35
|
+
/* Windows High Contrast / forced-colors: opt into the system palette so
|
|
36
|
+
borders and focus stay visible when the user's palette replaces ours.
|
|
37
|
+
|
|
38
|
+
.juno-badge additionally sets `forced-color-adjust: none` to keep its status
|
|
39
|
+
fill meaningful — and that opt-out is why the badge was the one element where
|
|
40
|
+
the lost cascade actually reached the screen: it also disables the UA's own
|
|
41
|
+
repaint, which was silently rescuing .juno-btn and .juno-card. Measured, not
|
|
42
|
+
inferred (20260815-029). */
|
|
43
|
+
@media (forced-colors: active) {
|
|
44
|
+
*:focus-visible { outline-color: Highlight; }
|
|
45
|
+
|
|
46
|
+
.juno-badge,
|
|
47
|
+
.juno-btn,
|
|
48
|
+
.juno-card,
|
|
49
|
+
.juno-readout { border: 1px solid CanvasText; }
|
|
50
|
+
.juno-badge { forced-color-adjust: none; } /* keep status fill meaningful */
|
|
51
|
+
}
|