@junoput01/junoui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +199 -0
- package/README.md +117 -0
- package/dist/android/colors.xml +94 -0
- package/dist/android/dimens.xml +49 -0
- package/dist/css/juno-fonts.css +45 -0
- package/dist/css/juno-tokens.css +345 -0
- package/dist/css/juno.css +4481 -0
- package/dist/flutter/juno_tokens.dart +143 -0
- package/dist/fonts/b612-400.woff2 +0 -0
- package/dist/fonts/b612-700.woff2 +0 -0
- package/dist/fonts/b612-mono-400.woff2 +0 -0
- package/dist/fonts/b612-mono-700.woff2 +0 -0
- package/dist/icons/juno-icons.svg +69 -0
- package/dist/ios/JunoTokens.swift +149 -0
- package/dist/js/tokens.d.ts +11 -0
- package/dist/js/tokens.js +244 -0
- package/dist/json/tokens.json +921 -0
- package/dist/scss/_juno-tokens.scss +176 -0
- package/package.json +92 -0
- package/src/css/base.css +120 -0
- package/src/css/components/accordion.css +83 -0
- package/src/css/components/alert.css +90 -0
- package/src/css/components/avatar.css +82 -0
- package/src/css/components/badge.css +55 -0
- package/src/css/components/breadcrumb.css +64 -0
- package/src/css/components/button.css +59 -0
- package/src/css/components/card.css +68 -0
- package/src/css/components/checkbox.css +89 -0
- package/src/css/components/chip.css +89 -0
- package/src/css/components/divider.css +50 -0
- package/src/css/components/dock.css +74 -0
- package/src/css/components/dot.css +35 -0
- package/src/css/components/drawer.css +80 -0
- package/src/css/components/field.css +37 -0
- package/src/css/components/gauge.css +78 -0
- package/src/css/components/icon.css +35 -0
- package/src/css/components/input.css +60 -0
- package/src/css/components/list.css +135 -0
- package/src/css/components/loader.css +159 -0
- package/src/css/components/menu.css +114 -0
- package/src/css/components/modal.css +161 -0
- package/src/css/components/navbar.css +103 -0
- package/src/css/components/pagination.css +72 -0
- package/src/css/components/pillbar.css +111 -0
- package/src/css/components/popover.css +73 -0
- package/src/css/components/rail.css +96 -0
- package/src/css/components/readout.css +45 -0
- package/src/css/components/segmented.css +99 -0
- package/src/css/components/select.css +38 -0
- package/src/css/components/skeleton.css +62 -0
- package/src/css/components/slider.css +117 -0
- package/src/css/components/spark.css +26 -0
- package/src/css/components/stepper.css +121 -0
- package/src/css/components/switch.css +131 -0
- package/src/css/components/table.css +443 -0
- package/src/css/components/tabs.css +66 -0
- package/src/css/components/thumb.css +69 -0
- package/src/css/components/toast.css +109 -0
- package/src/css/components/toggle-button.css +91 -0
- package/src/css/components/tooltip.css +155 -0
- package/src/css/density.css +40 -0
- package/src/css/fonts.css +45 -0
- package/src/css/layout.css +99 -0
- package/src/css/typescale.css +27 -0
- package/src/css/utilities.css +76 -0
- package/src/fonts/LICENSE +93 -0
- package/src/fonts/b612-400.woff2 +0 -0
- package/src/fonts/b612-700.woff2 +0 -0
- package/src/fonts/b612-mono-400.woff2 +0 -0
- package/src/fonts/b612-mono-700.woff2 +0 -0
- package/src/icons/LICENSE +21 -0
- package/src/icons/arrow-clockwise.svg +1 -0
- package/src/icons/arrow-down.svg +1 -0
- package/src/icons/arrow-left.svg +1 -0
- package/src/icons/arrow-right.svg +1 -0
- package/src/icons/arrow-up.svg +1 -0
- package/src/icons/arrows-clockwise.svg +1 -0
- package/src/icons/arrows-out.svg +1 -0
- package/src/icons/bell.svg +1 -0
- package/src/icons/calendar-blank.svg +1 -0
- package/src/icons/caret-down.svg +1 -0
- package/src/icons/caret-left.svg +1 -0
- package/src/icons/caret-right.svg +1 -0
- package/src/icons/caret-up.svg +1 -0
- package/src/icons/chat-circle.svg +1 -0
- package/src/icons/check-circle.svg +1 -0
- package/src/icons/check.svg +1 -0
- package/src/icons/clock.svg +1 -0
- package/src/icons/cloud-arrow-down.svg +1 -0
- package/src/icons/cloud.svg +1 -0
- package/src/icons/copy.svg +1 -0
- package/src/icons/cpu.svg +1 -0
- package/src/icons/dots-three-vertical.svg +1 -0
- package/src/icons/dots-three.svg +1 -0
- package/src/icons/download.svg +1 -0
- package/src/icons/envelope.svg +1 -0
- package/src/icons/eye-slash.svg +1 -0
- package/src/icons/eye.svg +1 -0
- package/src/icons/file.svg +1 -0
- package/src/icons/film-strip.svg +1 -0
- package/src/icons/floppy-disk.svg +1 -0
- package/src/icons/folder.svg +1 -0
- package/src/icons/funnel.svg +1 -0
- package/src/icons/gear.svg +1 -0
- package/src/icons/hard-drives.svg +1 -0
- package/src/icons/heart.svg +1 -0
- package/src/icons/hexagon.svg +1 -0
- package/src/icons/house.svg +1 -0
- package/src/icons/images.svg +1 -0
- package/src/icons/info.svg +1 -0
- package/src/icons/link.svg +1 -0
- package/src/icons/list.svg +1 -0
- package/src/icons/lock-open.svg +1 -0
- package/src/icons/lock.svg +1 -0
- package/src/icons/magnifying-glass.svg +1 -0
- package/src/icons/minus.svg +1 -0
- package/src/icons/moon.svg +1 -0
- package/src/icons/pencil-simple.svg +1 -0
- package/src/icons/play.svg +1 -0
- package/src/icons/plus.svg +1 -0
- package/src/icons/puzzle-piece.svg +1 -0
- package/src/icons/question.svg +1 -0
- package/src/icons/sliders-horizontal.svg +1 -0
- package/src/icons/sliders.svg +1 -0
- package/src/icons/squares-four.svg +1 -0
- package/src/icons/star.svg +1 -0
- package/src/icons/sun.svg +1 -0
- package/src/icons/trash.svg +1 -0
- package/src/icons/upload-simple.svg +1 -0
- package/src/icons/upload.svg +1 -0
- package/src/icons/user.svg +1 -0
- package/src/icons/users.svg +1 -0
- package/src/icons/warning-circle.svg +1 -0
- package/src/icons/warning.svg +1 -0
- package/src/icons/x-circle.svg +1 -0
- package/src/icons/x.svg +1 -0
|
@@ -0,0 +1,4481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* junoui — complete stylesheet (tokens + base + utilities + components).
|
|
3
|
+
* Generated bundle; edit sources in src/css/ and rebuild.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* junoui design tokens — CSS custom properties.
|
|
8
|
+
* Core tokens live on :root. Semantic color roles switch with
|
|
9
|
+
* [data-juno-palette] + [data-juno-mode]; defaults are standard / dark.
|
|
10
|
+
* Generated by Style Dictionary — do not edit by hand.
|
|
11
|
+
*/
|
|
12
|
+
:root {
|
|
13
|
+
--juno-border-width-1: 1px;
|
|
14
|
+
--juno-border-width-2: 2px;
|
|
15
|
+
--juno-border-width-3: 3px;
|
|
16
|
+
--juno-bp-sm: 640px;
|
|
17
|
+
--juno-bp-md: 768px;
|
|
18
|
+
--juno-bp-lg: 1024px;
|
|
19
|
+
--juno-bp-xl: 1280px;
|
|
20
|
+
--juno-bp-2xl: 1536px;
|
|
21
|
+
--juno-shadow-1: 0 1px 2px rgb(0 0 0 / 0.30);
|
|
22
|
+
--juno-shadow-2: 0 4px 14px rgb(0 0 0 / 0.35);
|
|
23
|
+
--juno-shadow-3: 0 12px 32px rgb(0 0 0 / 0.50);
|
|
24
|
+
--juno-motion-duration-instant: 80ms;
|
|
25
|
+
--juno-motion-duration-quick: 140ms;
|
|
26
|
+
--juno-motion-duration-base: 200ms;
|
|
27
|
+
--juno-motion-duration-deliberate: 300ms;
|
|
28
|
+
--juno-motion-ease-decel: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
29
|
+
--juno-motion-ease-accel: cubic-bezier(0.4, 0, 1, 1);
|
|
30
|
+
--juno-motion-ease-standard: cubic-bezier(0.2, 0.7, 0.3, 1);
|
|
31
|
+
--juno-motion-ease-spring: cubic-bezier(0.2, 0.9, 0.3, 1.2);
|
|
32
|
+
--juno-opacity-disabled: 0.45;
|
|
33
|
+
--juno-opacity-muted: 0.65;
|
|
34
|
+
--juno-opacity-scrim: 0.62;
|
|
35
|
+
--juno-radius-2: 2px;
|
|
36
|
+
--juno-radius-3: 3px;
|
|
37
|
+
--juno-radius-4: 4px;
|
|
38
|
+
--juno-radius-5: 5px;
|
|
39
|
+
--juno-radius-8: 8px;
|
|
40
|
+
--juno-size-tap-min: 24px;
|
|
41
|
+
--juno-size-tap-comfortable: 44px;
|
|
42
|
+
--juno-size-dot-sm: 8px;
|
|
43
|
+
--juno-size-dot-md: 10px;
|
|
44
|
+
--juno-space-2: 2px;
|
|
45
|
+
--juno-space-4: 4px;
|
|
46
|
+
--juno-space-8: 8px;
|
|
47
|
+
--juno-space-10: 10px;
|
|
48
|
+
--juno-space-12: 12px;
|
|
49
|
+
--juno-space-16: 16px;
|
|
50
|
+
--juno-space-20: 20px;
|
|
51
|
+
--juno-space-24: 24px;
|
|
52
|
+
--juno-space-28: 28px;
|
|
53
|
+
--juno-space-32: 32px;
|
|
54
|
+
--juno-space-40: 40px;
|
|
55
|
+
--juno-space-56: 56px;
|
|
56
|
+
--juno-space-72: 72px;
|
|
57
|
+
--juno-space-96: 96px;
|
|
58
|
+
--juno-font-family-sans: 'B612', sans-serif;
|
|
59
|
+
--juno-font-family-mono: 'B612 Mono', monospace;
|
|
60
|
+
--juno-font-weight-light: 300;
|
|
61
|
+
--juno-font-weight-regular: 400;
|
|
62
|
+
--juno-font-weight-medium: 500;
|
|
63
|
+
--juno-font-weight-semibold: 600;
|
|
64
|
+
--juno-font-weight-bold: 700;
|
|
65
|
+
--juno-font-size-10: calc(10px * var(--juno-font-scale, 1));
|
|
66
|
+
--juno-font-size-11: calc(11px * var(--juno-font-scale, 1));
|
|
67
|
+
--juno-font-size-12: calc(12px * var(--juno-font-scale, 1));
|
|
68
|
+
--juno-font-size-13: calc(13px * var(--juno-font-scale, 1));
|
|
69
|
+
--juno-font-size-14: calc(14px * var(--juno-font-scale, 1));
|
|
70
|
+
--juno-font-size-16: calc(16px * var(--juno-font-scale, 1));
|
|
71
|
+
--juno-font-size-18: calc(18px * var(--juno-font-scale, 1));
|
|
72
|
+
--juno-font-size-20: calc(20px * var(--juno-font-scale, 1));
|
|
73
|
+
--juno-font-size-28: calc(28px * var(--juno-font-scale, 1));
|
|
74
|
+
--juno-font-size-32: calc(32px * var(--juno-font-scale, 1));
|
|
75
|
+
--juno-font-size-36: calc(36px * var(--juno-font-scale, 1));
|
|
76
|
+
--juno-font-size-38: calc(38px * var(--juno-font-scale, 1));
|
|
77
|
+
--juno-font-size-48: calc(48px * var(--juno-font-scale, 1));
|
|
78
|
+
--juno-font-size-52: calc(52px * var(--juno-font-scale, 1));
|
|
79
|
+
--juno-font-lineHeight-none: 1;
|
|
80
|
+
--juno-font-lineHeight-tight: 1.1;
|
|
81
|
+
--juno-font-lineHeight-snug: 1.2;
|
|
82
|
+
--juno-font-lineHeight-normal: 1.4;
|
|
83
|
+
--juno-font-lineHeight-relaxed: 1.5;
|
|
84
|
+
--juno-font-lineHeight-loose: 1.7;
|
|
85
|
+
--juno-font-tracking-tight: 0.02em;
|
|
86
|
+
--juno-font-tracking-normal: 0.06em;
|
|
87
|
+
--juno-font-tracking-label: 0.08em;
|
|
88
|
+
--juno-font-tracking-wide: 0.12em;
|
|
89
|
+
--juno-font-tracking-caps: 0.15em;
|
|
90
|
+
--juno-font-tracking-wider: 0.2em;
|
|
91
|
+
--juno-font-tracking-widest: 0.3em;
|
|
92
|
+
--juno-z-surface: 0;
|
|
93
|
+
--juno-z-raised: 100;
|
|
94
|
+
--juno-z-anchored: 2000;
|
|
95
|
+
--juno-z-overlay: 4000;
|
|
96
|
+
--juno-z-alert: 5000;
|
|
97
|
+
|
|
98
|
+
--juno-nominal: oklch(73% 0.22 148);
|
|
99
|
+
--juno-active: oklch(76% 0.16 204);
|
|
100
|
+
--juno-target: oklch(65% 0.26 324);
|
|
101
|
+
--juno-caution: oklch(77% 0.17 73);
|
|
102
|
+
--juno-warning: oklch(63% 0.22 28);
|
|
103
|
+
--juno-data: oklch(93% 0.008 85);
|
|
104
|
+
--juno-data-dim: oklch(38% 0.010 85);
|
|
105
|
+
--juno-label: oklch(63% 0.012 85);
|
|
106
|
+
--juno-muted: oklch(48.5% 0.010 85);
|
|
107
|
+
--juno-border: oklch(30% 0.012 85);
|
|
108
|
+
--juno-border-strong: oklch(40% 0.012 85);
|
|
109
|
+
--juno-s0: oklch(11% 0.010 85);
|
|
110
|
+
--juno-s1: oklch(15% 0.012 85);
|
|
111
|
+
--juno-s2: oklch(19% 0.012 85);
|
|
112
|
+
--juno-s3: oklch(24% 0.012 85);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
:root[data-juno-palette="colorblind"]:not([data-juno-mode]) {
|
|
116
|
+
--juno-nominal: #78A9FF;
|
|
117
|
+
--juno-active: #BE95FF;
|
|
118
|
+
--juno-target: #FF7EB6;
|
|
119
|
+
--juno-caution: #F1C21B;
|
|
120
|
+
--juno-warning: #FF832B;
|
|
121
|
+
--juno-data: oklch(93% 0.008 85);
|
|
122
|
+
--juno-data-dim: oklch(38% 0.010 85);
|
|
123
|
+
--juno-label: oklch(63% 0.012 85);
|
|
124
|
+
--juno-muted: oklch(48.5% 0.010 85);
|
|
125
|
+
--juno-border: oklch(30% 0.012 85);
|
|
126
|
+
--juno-border-strong: oklch(40% 0.012 85);
|
|
127
|
+
--juno-s0: oklch(11% 0.010 85);
|
|
128
|
+
--juno-s1: oklch(15% 0.012 85);
|
|
129
|
+
--juno-s2: oklch(19% 0.012 85);
|
|
130
|
+
--juno-s3: oklch(24% 0.012 85);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:root[data-juno-palette="colorblind"][data-juno-mode="dark"] {
|
|
134
|
+
--juno-nominal: #78A9FF;
|
|
135
|
+
--juno-active: #BE95FF;
|
|
136
|
+
--juno-target: #FF7EB6;
|
|
137
|
+
--juno-caution: #F1C21B;
|
|
138
|
+
--juno-warning: #FF832B;
|
|
139
|
+
--juno-data: oklch(93% 0.008 85);
|
|
140
|
+
--juno-data-dim: oklch(38% 0.010 85);
|
|
141
|
+
--juno-label: oklch(63% 0.012 85);
|
|
142
|
+
--juno-muted: oklch(48.5% 0.010 85);
|
|
143
|
+
--juno-border: oklch(30% 0.012 85);
|
|
144
|
+
--juno-border-strong: oklch(40% 0.012 85);
|
|
145
|
+
--juno-s0: oklch(11% 0.010 85);
|
|
146
|
+
--juno-s1: oklch(15% 0.012 85);
|
|
147
|
+
--juno-s2: oklch(19% 0.012 85);
|
|
148
|
+
--juno-s3: oklch(24% 0.012 85);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
:root[data-juno-palette="colorblind"][data-juno-mode="light"] {
|
|
152
|
+
--juno-nominal: #0043CE;
|
|
153
|
+
--juno-active: #6929C4;
|
|
154
|
+
--juno-target: #9F1853;
|
|
155
|
+
--juno-caution: #7D4F00;
|
|
156
|
+
--juno-warning: #BA4300;
|
|
157
|
+
--juno-data: oklch(14% 0.008 85);
|
|
158
|
+
--juno-data-dim: oklch(70% 0.008 85);
|
|
159
|
+
--juno-label: oklch(42% 0.010 85);
|
|
160
|
+
--juno-muted: oklch(63% 0.008 85);
|
|
161
|
+
--juno-border: oklch(82% 0.010 85);
|
|
162
|
+
--juno-border-strong: oklch(74% 0.010 85);
|
|
163
|
+
--juno-s0: oklch(97% 0.008 85);
|
|
164
|
+
--juno-s1: oklch(93% 0.010 85);
|
|
165
|
+
--juno-s2: oklch(89% 0.010 85);
|
|
166
|
+
--juno-s3: oklch(85% 0.010 85);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
:root[data-juno-palette="soft"]:not([data-juno-mode]) {
|
|
170
|
+
--juno-nominal: oklch(63% 0.13 155);
|
|
171
|
+
--juno-active: oklch(67% 0.12 210);
|
|
172
|
+
--juno-target: oklch(58% 0.14 318);
|
|
173
|
+
--juno-caution: oklch(72% 0.12 75);
|
|
174
|
+
--juno-warning: oklch(62% 0.15 30);
|
|
175
|
+
--juno-data: oklch(90% 0.008 88);
|
|
176
|
+
--juno-data-dim: oklch(39% 0.010 88);
|
|
177
|
+
--juno-label: oklch(63% 0.010 88);
|
|
178
|
+
--juno-muted: oklch(48.5% 0.010 88);
|
|
179
|
+
--juno-border: oklch(31% 0.010 88);
|
|
180
|
+
--juno-border-strong: oklch(41% 0.010 88);
|
|
181
|
+
--juno-s0: oklch(12% 0.012 88);
|
|
182
|
+
--juno-s1: oklch(16% 0.012 88);
|
|
183
|
+
--juno-s2: oklch(20% 0.012 88);
|
|
184
|
+
--juno-s3: oklch(25% 0.012 88);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
:root[data-juno-palette="soft"][data-juno-mode="dark"] {
|
|
188
|
+
--juno-nominal: oklch(63% 0.13 155);
|
|
189
|
+
--juno-active: oklch(67% 0.12 210);
|
|
190
|
+
--juno-target: oklch(58% 0.14 318);
|
|
191
|
+
--juno-caution: oklch(72% 0.12 75);
|
|
192
|
+
--juno-warning: oklch(62% 0.15 30);
|
|
193
|
+
--juno-data: oklch(90% 0.008 88);
|
|
194
|
+
--juno-data-dim: oklch(39% 0.010 88);
|
|
195
|
+
--juno-label: oklch(63% 0.010 88);
|
|
196
|
+
--juno-muted: oklch(48.5% 0.010 88);
|
|
197
|
+
--juno-border: oklch(31% 0.010 88);
|
|
198
|
+
--juno-border-strong: oklch(41% 0.010 88);
|
|
199
|
+
--juno-s0: oklch(12% 0.012 88);
|
|
200
|
+
--juno-s1: oklch(16% 0.012 88);
|
|
201
|
+
--juno-s2: oklch(20% 0.012 88);
|
|
202
|
+
--juno-s3: oklch(25% 0.012 88);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
:root[data-juno-palette="soft"][data-juno-mode="light"] {
|
|
206
|
+
--juno-nominal: oklch(40% 0.11 155);
|
|
207
|
+
--juno-active: oklch(44% 0.11 210);
|
|
208
|
+
--juno-target: oklch(42% 0.12 318);
|
|
209
|
+
--juno-caution: oklch(50% 0.12 75);
|
|
210
|
+
--juno-warning: oklch(42% 0.13 30);
|
|
211
|
+
--juno-data: oklch(14% 0.008 88);
|
|
212
|
+
--juno-data-dim: oklch(69% 0.008 88);
|
|
213
|
+
--juno-label: oklch(44% 0.010 88);
|
|
214
|
+
--juno-muted: oklch(62.5% 0.008 88);
|
|
215
|
+
--juno-border: oklch(84% 0.010 88);
|
|
216
|
+
--juno-border-strong: oklch(76% 0.010 88);
|
|
217
|
+
--juno-s0: oklch(96% 0.010 88);
|
|
218
|
+
--juno-s1: oklch(92% 0.010 88);
|
|
219
|
+
--juno-s2: oklch(88% 0.010 88);
|
|
220
|
+
--juno-s3: oklch(84% 0.010 88);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
:root[data-juno-palette="standard"]:not([data-juno-mode]) {
|
|
224
|
+
--juno-nominal: oklch(73% 0.22 148);
|
|
225
|
+
--juno-active: oklch(76% 0.16 204);
|
|
226
|
+
--juno-target: oklch(65% 0.26 324);
|
|
227
|
+
--juno-caution: oklch(77% 0.17 73);
|
|
228
|
+
--juno-warning: oklch(63% 0.22 28);
|
|
229
|
+
--juno-data: oklch(93% 0.008 85);
|
|
230
|
+
--juno-data-dim: oklch(38% 0.010 85);
|
|
231
|
+
--juno-label: oklch(63% 0.012 85);
|
|
232
|
+
--juno-muted: oklch(48.5% 0.010 85);
|
|
233
|
+
--juno-border: oklch(30% 0.012 85);
|
|
234
|
+
--juno-border-strong: oklch(40% 0.012 85);
|
|
235
|
+
--juno-s0: oklch(11% 0.010 85);
|
|
236
|
+
--juno-s1: oklch(15% 0.012 85);
|
|
237
|
+
--juno-s2: oklch(19% 0.012 85);
|
|
238
|
+
--juno-s3: oklch(24% 0.012 85);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
:root[data-juno-palette="standard"][data-juno-mode="dark"] {
|
|
242
|
+
--juno-nominal: oklch(73% 0.22 148);
|
|
243
|
+
--juno-active: oklch(76% 0.16 204);
|
|
244
|
+
--juno-target: oklch(65% 0.26 324);
|
|
245
|
+
--juno-caution: oklch(77% 0.17 73);
|
|
246
|
+
--juno-warning: oklch(63% 0.22 28);
|
|
247
|
+
--juno-data: oklch(93% 0.008 85);
|
|
248
|
+
--juno-data-dim: oklch(38% 0.010 85);
|
|
249
|
+
--juno-label: oklch(63% 0.012 85);
|
|
250
|
+
--juno-muted: oklch(48.5% 0.010 85);
|
|
251
|
+
--juno-border: oklch(30% 0.012 85);
|
|
252
|
+
--juno-border-strong: oklch(40% 0.012 85);
|
|
253
|
+
--juno-s0: oklch(11% 0.010 85);
|
|
254
|
+
--juno-s1: oklch(15% 0.012 85);
|
|
255
|
+
--juno-s2: oklch(19% 0.012 85);
|
|
256
|
+
--juno-s3: oklch(24% 0.012 85);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
:root[data-juno-palette="standard"][data-juno-mode="light"] {
|
|
260
|
+
--juno-nominal: oklch(42% 0.18 148);
|
|
261
|
+
--juno-active: oklch(47% 0.16 220);
|
|
262
|
+
--juno-target: oklch(45% 0.22 324);
|
|
263
|
+
--juno-caution: oklch(50% 0.17 73);
|
|
264
|
+
--juno-warning: oklch(42% 0.21 26);
|
|
265
|
+
--juno-data: oklch(14% 0.008 85);
|
|
266
|
+
--juno-data-dim: oklch(70% 0.008 85);
|
|
267
|
+
--juno-label: oklch(42% 0.010 85);
|
|
268
|
+
--juno-muted: oklch(63% 0.008 85);
|
|
269
|
+
--juno-border: oklch(82% 0.010 85);
|
|
270
|
+
--juno-border-strong: oklch(74% 0.010 85);
|
|
271
|
+
--juno-s0: oklch(97% 0.008 85);
|
|
272
|
+
--juno-s1: oklch(93% 0.010 85);
|
|
273
|
+
--juno-s2: oklch(89% 0.010 85);
|
|
274
|
+
--juno-s3: oklch(85% 0.010 85);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* no data-juno-mode → follow the system color scheme */
|
|
278
|
+
@media (prefers-color-scheme: light) {
|
|
279
|
+
:root:not([data-juno-mode]) {
|
|
280
|
+
--juno-nominal: oklch(42% 0.18 148);
|
|
281
|
+
--juno-active: oklch(47% 0.16 220);
|
|
282
|
+
--juno-target: oklch(45% 0.22 324);
|
|
283
|
+
--juno-caution: oklch(50% 0.17 73);
|
|
284
|
+
--juno-warning: oklch(42% 0.21 26);
|
|
285
|
+
--juno-data: oklch(14% 0.008 85);
|
|
286
|
+
--juno-data-dim: oklch(70% 0.008 85);
|
|
287
|
+
--juno-label: oklch(42% 0.010 85);
|
|
288
|
+
--juno-muted: oklch(63% 0.008 85);
|
|
289
|
+
--juno-border: oklch(82% 0.010 85);
|
|
290
|
+
--juno-border-strong: oklch(74% 0.010 85);
|
|
291
|
+
--juno-s0: oklch(97% 0.008 85);
|
|
292
|
+
--juno-s1: oklch(93% 0.010 85);
|
|
293
|
+
--juno-s2: oklch(89% 0.010 85);
|
|
294
|
+
--juno-s3: oklch(85% 0.010 85);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
:root[data-juno-palette="colorblind"]:not([data-juno-mode]) {
|
|
298
|
+
--juno-nominal: #0043CE;
|
|
299
|
+
--juno-active: #6929C4;
|
|
300
|
+
--juno-target: #9F1853;
|
|
301
|
+
--juno-caution: #7D4F00;
|
|
302
|
+
--juno-warning: #BA4300;
|
|
303
|
+
--juno-data: oklch(14% 0.008 85);
|
|
304
|
+
--juno-data-dim: oklch(70% 0.008 85);
|
|
305
|
+
--juno-label: oklch(42% 0.010 85);
|
|
306
|
+
--juno-muted: oklch(63% 0.008 85);
|
|
307
|
+
--juno-border: oklch(82% 0.010 85);
|
|
308
|
+
--juno-border-strong: oklch(74% 0.010 85);
|
|
309
|
+
--juno-s0: oklch(97% 0.008 85);
|
|
310
|
+
--juno-s1: oklch(93% 0.010 85);
|
|
311
|
+
--juno-s2: oklch(89% 0.010 85);
|
|
312
|
+
--juno-s3: oklch(85% 0.010 85);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
:root[data-juno-palette="soft"]:not([data-juno-mode]) {
|
|
316
|
+
--juno-nominal: oklch(40% 0.11 155);
|
|
317
|
+
--juno-active: oklch(44% 0.11 210);
|
|
318
|
+
--juno-target: oklch(42% 0.12 318);
|
|
319
|
+
--juno-caution: oklch(50% 0.12 75);
|
|
320
|
+
--juno-warning: oklch(42% 0.13 30);
|
|
321
|
+
--juno-data: oklch(14% 0.008 88);
|
|
322
|
+
--juno-data-dim: oklch(69% 0.008 88);
|
|
323
|
+
--juno-label: oklch(44% 0.010 88);
|
|
324
|
+
--juno-muted: oklch(62.5% 0.008 88);
|
|
325
|
+
--juno-border: oklch(84% 0.010 88);
|
|
326
|
+
--juno-border-strong: oklch(76% 0.010 88);
|
|
327
|
+
--juno-s0: oklch(96% 0.010 88);
|
|
328
|
+
--juno-s1: oklch(92% 0.010 88);
|
|
329
|
+
--juno-s2: oklch(88% 0.010 88);
|
|
330
|
+
--juno-s3: oklch(84% 0.010 88);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
:root[data-juno-palette="standard"]:not([data-juno-mode]) {
|
|
334
|
+
--juno-nominal: oklch(42% 0.18 148);
|
|
335
|
+
--juno-active: oklch(47% 0.16 220);
|
|
336
|
+
--juno-target: oklch(45% 0.22 324);
|
|
337
|
+
--juno-caution: oklch(50% 0.17 73);
|
|
338
|
+
--juno-warning: oklch(42% 0.21 26);
|
|
339
|
+
--juno-data: oklch(14% 0.008 85);
|
|
340
|
+
--juno-data-dim: oklch(70% 0.008 85);
|
|
341
|
+
--juno-label: oklch(42% 0.010 85);
|
|
342
|
+
--juno-muted: oklch(63% 0.008 85);
|
|
343
|
+
--juno-border: oklch(82% 0.010 85);
|
|
344
|
+
--juno-border-strong: oklch(74% 0.010 85);
|
|
345
|
+
--juno-s0: oklch(97% 0.008 85);
|
|
346
|
+
--juno-s1: oklch(93% 0.010 85);
|
|
347
|
+
--juno-s2: oklch(89% 0.010 85);
|
|
348
|
+
--juno-s3: oklch(85% 0.010 85);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
353
|
+
* junoui — base layer
|
|
354
|
+
* Reset + document defaults. Sets B612 as the type family but does NOT
|
|
355
|
+
* fetch it — that would break under a strict CSP and phone home. Supply the
|
|
356
|
+
* fonts yourself, or opt into the self-hosted set: `import 'junoui/fonts.css'`
|
|
357
|
+
* (see src/css/fonts.css). Falls back to system sans/mono until then.
|
|
358
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
359
|
+
|
|
360
|
+
*,
|
|
361
|
+
*::before,
|
|
362
|
+
*::after { box-sizing: border-box; }
|
|
363
|
+
|
|
364
|
+
/* System color scheme. Without data-juno-mode the theme follows the OS
|
|
365
|
+
(`prefers-color-scheme`; see juno-tokens.css) — `dark light` here lets the
|
|
366
|
+
UA pick matching scrollbars/form chrome. An explicit mode pins both. */
|
|
367
|
+
:root { color-scheme: dark light; }
|
|
368
|
+
:root[data-juno-mode='dark'] { color-scheme: dark; }
|
|
369
|
+
|
|
370
|
+
/* Control surfaces. On dark panels the hairline border (~23% L) disappears, so
|
|
371
|
+
lift the control edge to `muted` and sit controls on `s2`; light mode keeps the
|
|
372
|
+
true border on `s0`. Form components read these instead of raw surface roles. */
|
|
373
|
+
:root,
|
|
374
|
+
:root[data-juno-mode='dark'] {
|
|
375
|
+
--juno-control-surface: var(--juno-s2);
|
|
376
|
+
--juno-control-edge: var(--juno-muted);
|
|
377
|
+
|
|
378
|
+
/* Knob + track-rim of the lever controls (switch / slider). Edge and knob
|
|
379
|
+
are lifted well clear of the track so the control reads at a squint: a
|
|
380
|
+
light grey lever (face hi→lo) with dark grip lines on a dark track. */
|
|
381
|
+
--juno-control-edge-strong: var(--juno-label);
|
|
382
|
+
--juno-knob-edge: var(--juno-label);
|
|
383
|
+
--juno-knob-face-hi: var(--juno-label);
|
|
384
|
+
--juno-knob-face-lo: var(--juno-muted);
|
|
385
|
+
--juno-knob-grip: var(--juno-s0);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
:root[data-juno-mode='light'] {
|
|
389
|
+
color-scheme: light;
|
|
390
|
+
|
|
391
|
+
--juno-control-surface: var(--juno-s0);
|
|
392
|
+
--juno-control-edge: var(--juno-border);
|
|
393
|
+
|
|
394
|
+
/* Light mode inverts: a dark grey lever with light grip lines on a near-
|
|
395
|
+
white track. Same vars, so switch + slider stay in lockstep. */
|
|
396
|
+
--juno-control-edge-strong: var(--juno-label);
|
|
397
|
+
--juno-knob-edge: var(--juno-label);
|
|
398
|
+
--juno-knob-face-hi: var(--juno-muted);
|
|
399
|
+
--juno-knob-face-lo: var(--juno-label);
|
|
400
|
+
--juno-knob-grip: var(--juno-s0);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* no data-juno-mode + a light OS → the light control surfaces above */
|
|
404
|
+
@media (prefers-color-scheme: light) {
|
|
405
|
+
:root:not([data-juno-mode]) {
|
|
406
|
+
--juno-control-surface: var(--juno-s0);
|
|
407
|
+
--juno-control-edge: var(--juno-border);
|
|
408
|
+
--juno-knob-face-hi: var(--juno-muted);
|
|
409
|
+
--juno-knob-face-lo: var(--juno-label);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
* { margin: 0; padding: 0; }
|
|
414
|
+
|
|
415
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
416
|
+
|
|
417
|
+
body {
|
|
418
|
+
font-family: var(--juno-font-family-sans);
|
|
419
|
+
font-size: var(--juno-font-size-14);
|
|
420
|
+
line-height: var(--juno-font-lineHeight-normal);
|
|
421
|
+
letter-spacing: var(--juno-font-tracking-tight);
|
|
422
|
+
color: var(--juno-data);
|
|
423
|
+
background: var(--juno-s0);
|
|
424
|
+
-webkit-font-smoothing: antialiased;
|
|
425
|
+
text-rendering: optimizelegibility;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
code, kbd, samp, pre { font-family: var(--juno-font-family-mono); }
|
|
429
|
+
|
|
430
|
+
/* Accessible focus — uses the ACTIVE role so it tracks the theme. */
|
|
431
|
+
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
|
|
432
|
+
outline: 2px solid var(--juno-active);
|
|
433
|
+
outline-offset: 2px;
|
|
434
|
+
border-radius: var(--juno-radius-2);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Touch ergonomics — on coarse pointers (finger, not mouse) every control
|
|
438
|
+
that sizes itself off the tap minimum grows to the comfortable target
|
|
439
|
+
(44px). A cascade override, not a token change: the token values stay
|
|
440
|
+
the contract; a phone just reads the bigger one. */
|
|
441
|
+
@media (pointer: coarse) {
|
|
442
|
+
:root { --juno-size-tap-min: var(--juno-size-tap-comfortable); }
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
@keyframes juno-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
|
446
|
+
|
|
447
|
+
@media (prefers-reduced-motion: reduce) {
|
|
448
|
+
*, *::before, *::after {
|
|
449
|
+
animation-duration: 0.01ms !important;
|
|
450
|
+
animation-iteration-count: 1 !important;
|
|
451
|
+
transition-duration: 0.01ms !important;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* Higher-contrast preference: hairline seams step up to the strong border
|
|
456
|
+
so panel edges stop disappearing on users who asked for more contrast. */
|
|
457
|
+
@media (prefers-contrast: more) {
|
|
458
|
+
:root { --juno-border: var(--juno-border-strong); }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Windows High Contrast / forced-colors: opt into the system palette so
|
|
462
|
+
borders and focus stay visible when our colors are overridden. */
|
|
463
|
+
@media (forced-colors: active) {
|
|
464
|
+
*:focus-visible { outline-color: Highlight; }
|
|
465
|
+
|
|
466
|
+
.juno-badge,
|
|
467
|
+
.juno-btn,
|
|
468
|
+
.juno-card,
|
|
469
|
+
.juno-readout { border: 1px solid CanvasText; }
|
|
470
|
+
.juno-badge { forced-color-adjust: none; } /* keep status fill meaningful */
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
474
|
+
* junoui — layout layer (intrinsic & responsive)
|
|
475
|
+
*
|
|
476
|
+
* Composition primitives that reflow by the space available — no media
|
|
477
|
+
* queries, no per-app config. Wrap a region in one of these and it adapts
|
|
478
|
+
* on resize. Every knob is a custom property with a token default, so you
|
|
479
|
+
* override per instance: <div class="juno-grid-auto" style="--juno-grid-min:280px">.
|
|
480
|
+
*
|
|
481
|
+
* Pattern vocabulary follows the well-known "Every Layout" primitives
|
|
482
|
+
* (stack / cluster / sidebar / switcher / grid) for familiarity.
|
|
483
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
484
|
+
|
|
485
|
+
/* Center — page/section wrapper with a max measure and fluid gutters. */
|
|
486
|
+
.juno-center {
|
|
487
|
+
box-sizing: content-box;
|
|
488
|
+
max-width: var(--juno-measure, var(--juno-bp-xl));
|
|
489
|
+
margin-inline: auto;
|
|
490
|
+
padding-inline: clamp(var(--juno-space-16), 4vw, var(--juno-space-40));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* Stack — vertical rhythm. Gap between siblings only. */
|
|
494
|
+
.juno-stack {
|
|
495
|
+
display: flex;
|
|
496
|
+
flex-direction: column;
|
|
497
|
+
}
|
|
498
|
+
.juno-stack > * { margin-block: 0; }
|
|
499
|
+
.juno-stack > * + * { margin-block-start: var(--juno-stack-space, var(--juno-space-16)); }
|
|
500
|
+
|
|
501
|
+
/* Cluster — wrapping inline group (toolbars, tag rows, button groups). */
|
|
502
|
+
.juno-cluster {
|
|
503
|
+
display: flex;
|
|
504
|
+
flex-wrap: wrap;
|
|
505
|
+
align-items: var(--juno-cluster-align, center);
|
|
506
|
+
gap: var(--juno-cluster-space, var(--juno-space-8));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/* Grid-auto — responsive cards. Columns collapse on their own; no breakpoints.
|
|
510
|
+
Each column is at least --juno-grid-min, then as many as fit. */
|
|
511
|
+
.juno-grid-auto {
|
|
512
|
+
display: grid;
|
|
513
|
+
gap: var(--juno-grid-space, var(--juno-space-16));
|
|
514
|
+
grid-template-columns: repeat(auto-fit, minmax(min(var(--juno-grid-min, 240px), 100%), 1fr));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* Tiles — a grid-auto wired to the density layer: min size + gap follow
|
|
518
|
+
[data-juno-density], so a whole media wall re-densifies from the one
|
|
519
|
+
attribute. auto-fill keeps tile size honest when few items remain. */
|
|
520
|
+
.juno-grid-auto--tiles {
|
|
521
|
+
gap: var(--juno-grid-space, var(--juno-gap-content));
|
|
522
|
+
grid-template-columns: repeat(auto-fill, minmax(min(var(--juno-grid-min, var(--juno-tile-min)), 100%), 1fr));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* Sidebar — a fixed-ish aside next to fluid content; stacks when the content
|
|
526
|
+
would get narrower than --juno-sidebar-content-min. No media query. */
|
|
527
|
+
.juno-sidebar {
|
|
528
|
+
display: flex;
|
|
529
|
+
flex-wrap: wrap;
|
|
530
|
+
gap: var(--juno-sidebar-space, var(--juno-space-24));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.juno-sidebar > .juno-sidebar__aside {
|
|
534
|
+
flex-grow: 1;
|
|
535
|
+
flex-basis: var(--juno-sidebar-width, 280px);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.juno-sidebar > .juno-sidebar__main {
|
|
539
|
+
flex-grow: 999;
|
|
540
|
+
flex-basis: 0;
|
|
541
|
+
min-inline-size: var(--juno-sidebar-content-min, 60%);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/* Switcher — N children side by side, or all stacked, flipping at a threshold
|
|
545
|
+
container width (--juno-switcher-threshold). Pure flex-basis math. */
|
|
546
|
+
.juno-switcher {
|
|
547
|
+
display: flex;
|
|
548
|
+
flex-wrap: wrap;
|
|
549
|
+
gap: var(--juno-switcher-space, var(--juno-space-16));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.juno-switcher > * {
|
|
553
|
+
flex-grow: 1;
|
|
554
|
+
flex-basis: calc((var(--juno-switcher-threshold, var(--juno-bp-sm)) - 100%) * 999);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/* Reel — horizontal scroll row (overflowing chips, timelines). */
|
|
558
|
+
.juno-reel {
|
|
559
|
+
display: flex;
|
|
560
|
+
gap: var(--juno-reel-space, var(--juno-space-12));
|
|
561
|
+
overflow-x: auto;
|
|
562
|
+
overscroll-behavior-inline: contain;
|
|
563
|
+
scroll-snap-type: inline mandatory;
|
|
564
|
+
}
|
|
565
|
+
.juno-reel > * { scroll-snap-align: start; flex: 0 0 auto; }
|
|
566
|
+
|
|
567
|
+
/* Hide/show by viewport breakpoint — the rare case an intrinsic primitive
|
|
568
|
+
can't express (e.g. swap a nav for a menu button). */
|
|
569
|
+
@media (width <= 767.98px) { .juno-hide-below-md { display: none !important; } }
|
|
570
|
+
|
|
571
|
+
@media (width >= 768px) { .juno-hide-from-md { display: none !important; } }
|
|
572
|
+
|
|
573
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
574
|
+
* junoui — density layer
|
|
575
|
+
* Semantic padding/gap aliases that switch with [data-juno-density].
|
|
576
|
+
* Components reference these instead of raw --juno-space-* for their
|
|
577
|
+
* internal padding, so the whole UI compacts from one attribute:
|
|
578
|
+
*
|
|
579
|
+
* <body data-juno-density="compact"> → dense, data-heavy layout
|
|
580
|
+
* <body> → comfortable (default)
|
|
581
|
+
*
|
|
582
|
+
* Density is intentionally NON-linear: compact removes more block
|
|
583
|
+
* (vertical) padding than inline, so text never crowds its edges.
|
|
584
|
+
* Interactive controls keep their min-height (WCAG tap target) — only
|
|
585
|
+
* padding shrinks. Add a new archetype only when a component needs one.
|
|
586
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
587
|
+
|
|
588
|
+
:root,
|
|
589
|
+
[data-juno-density='comfortable'] {
|
|
590
|
+
/* interactive controls — button, future input/select */
|
|
591
|
+
--juno-pad-control-block: var(--juno-space-10);
|
|
592
|
+
--juno-pad-control-inline: var(--juno-space-20);
|
|
593
|
+
--juno-gap-control: var(--juno-space-8);
|
|
594
|
+
|
|
595
|
+
/* surfaces — card / panel bodies */
|
|
596
|
+
--juno-pad-surface-block: var(--juno-space-16);
|
|
597
|
+
--juno-pad-surface-inline: var(--juno-space-16);
|
|
598
|
+
|
|
599
|
+
/* content — media/tile walls (.juno-grid-auto--tiles) re-densify too */
|
|
600
|
+
--juno-tile-min: 150px;
|
|
601
|
+
--juno-gap-content: var(--juno-space-10);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
[data-juno-density='compact'] {
|
|
605
|
+
--juno-pad-control-block: var(--juno-space-4);
|
|
606
|
+
--juno-pad-control-inline: var(--juno-space-12);
|
|
607
|
+
--juno-gap-control: var(--juno-space-4);
|
|
608
|
+
--juno-pad-surface-block: var(--juno-space-10);
|
|
609
|
+
--juno-pad-surface-inline: var(--juno-space-12);
|
|
610
|
+
--juno-tile-min: 108px;
|
|
611
|
+
--juno-gap-content: var(--juno-space-4);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
615
|
+
* junoui — type-scale layer
|
|
616
|
+
* One multiplier, --juno-font-scale, scales every --juno-font-size-*
|
|
617
|
+
* token (the generated token CSS routes each size through it). Set it
|
|
618
|
+
* per [data-juno-text] to enlarge all UI text without touching density:
|
|
619
|
+
*
|
|
620
|
+
* <html data-juno-text="large"> → +12.5% type
|
|
621
|
+
* <html data-juno-text="xl"> → +25% type
|
|
622
|
+
* <html> → base (default)
|
|
623
|
+
*
|
|
624
|
+
* Orthogonal to density: density changes padding/gap, type-scale changes
|
|
625
|
+
* glyph size. Combine freely. Larger text aids low-vision / squint
|
|
626
|
+
* legibility (WCAG 1.4.4 resize text).
|
|
627
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
628
|
+
|
|
629
|
+
:root,
|
|
630
|
+
[data-juno-text='base'] {
|
|
631
|
+
--juno-font-scale: 1;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
[data-juno-text='large'] {
|
|
635
|
+
--juno-font-scale: 1.125;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
[data-juno-text='xl'] {
|
|
639
|
+
--juno-font-scale: 1.25;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
643
|
+
* Component — Accordion / disclosure
|
|
644
|
+
* Stacked, expandable sections built on native <details>/<summary> —
|
|
645
|
+
* keyboard + screen-reader native, ZERO JS. A chevron rotates on open.
|
|
646
|
+
* Use a lone <details> for a single disclosure; group them in
|
|
647
|
+
* .juno-accordion for a bordered set.
|
|
648
|
+
* Usage:
|
|
649
|
+
* <div class="juno-accordion">
|
|
650
|
+
* <details class="juno-accordion__item" open>
|
|
651
|
+
* <summary class="juno-accordion__summary">Region · us-east-1</summary>
|
|
652
|
+
* <div class="juno-accordion__body">…</div>
|
|
653
|
+
* </details>
|
|
654
|
+
* <details class="juno-accordion__item">
|
|
655
|
+
* <summary class="juno-accordion__summary">Region · eu-west-1</summary>
|
|
656
|
+
* <div class="juno-accordion__body">…</div>
|
|
657
|
+
* </details>
|
|
658
|
+
* </div>
|
|
659
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
660
|
+
|
|
661
|
+
.juno-accordion {
|
|
662
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
663
|
+
border-radius: var(--juno-radius-5);
|
|
664
|
+
overflow: hidden;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.juno-accordion__item {
|
|
668
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.juno-accordion__item:last-child { border-block-end: none; }
|
|
672
|
+
|
|
673
|
+
.juno-accordion__summary {
|
|
674
|
+
display: flex;
|
|
675
|
+
align-items: center;
|
|
676
|
+
justify-content: space-between;
|
|
677
|
+
gap: var(--juno-space-12);
|
|
678
|
+
padding: var(--juno-pad-control-block) var(--juno-pad-surface-inline);
|
|
679
|
+
background: var(--juno-s1);
|
|
680
|
+
font-family: var(--juno-font-family-sans);
|
|
681
|
+
font-size: var(--juno-font-size-13);
|
|
682
|
+
font-weight: var(--juno-font-weight-medium);
|
|
683
|
+
color: var(--juno-data);
|
|
684
|
+
cursor: pointer;
|
|
685
|
+
list-style: none;
|
|
686
|
+
user-select: none;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* hide the native disclosure triangle (both engines) */
|
|
690
|
+
.juno-accordion__summary::-webkit-details-marker { display: none; }
|
|
691
|
+
|
|
692
|
+
.juno-accordion__summary:hover { background: var(--juno-s2); }
|
|
693
|
+
|
|
694
|
+
/* chevron — a rotated corner that flips when the item is open */
|
|
695
|
+
.juno-accordion__summary::after {
|
|
696
|
+
content: '';
|
|
697
|
+
flex-shrink: 0;
|
|
698
|
+
inline-size: var(--juno-space-8);
|
|
699
|
+
block-size: var(--juno-space-8);
|
|
700
|
+
border-inline-end: var(--juno-border-width-2) solid var(--juno-muted);
|
|
701
|
+
border-block-end: var(--juno-border-width-2) solid var(--juno-muted);
|
|
702
|
+
transform: translateY(-2px) rotate(45deg);
|
|
703
|
+
transition: transform var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.juno-accordion__item[open] > .juno-accordion__summary::after {
|
|
707
|
+
transform: translateY(2px) rotate(-135deg);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.juno-accordion__body {
|
|
711
|
+
padding: var(--juno-space-12) var(--juno-pad-surface-inline) var(--juno-pad-surface-block);
|
|
712
|
+
background: var(--juno-s0);
|
|
713
|
+
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
714
|
+
font-size: var(--juno-font-size-13);
|
|
715
|
+
line-height: var(--juno-font-lineHeight-relaxed);
|
|
716
|
+
color: var(--juno-label);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/* a lone disclosure (no .juno-accordion wrapper) gets its own frame */
|
|
720
|
+
details.juno-accordion__item:only-child {
|
|
721
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
722
|
+
border-radius: var(--juno-radius-5);
|
|
723
|
+
overflow: hidden;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
727
|
+
* Component — Alert / inline notification
|
|
728
|
+
* A role-tinted banner for in-context messages: validation summaries,
|
|
729
|
+
* system status, callouts. Soft-fill by default with a role accent rail;
|
|
730
|
+
* icon + title + text + optional actions + optional dismiss. Color comes
|
|
731
|
+
* from --juno-role (set by a .juno--<role> class); default informational.
|
|
732
|
+
* Usage:
|
|
733
|
+
* <div class="juno-alert juno--warning" role="alert">
|
|
734
|
+
* <span class="juno-alert__icon" aria-hidden="true">⚠</span>
|
|
735
|
+
* <div class="juno-alert__body">
|
|
736
|
+
* <span class="juno-alert__title">Deploy blocked</span>
|
|
737
|
+
* <span class="juno-alert__text">Two checks are still failing.</span>
|
|
738
|
+
* </div>
|
|
739
|
+
* <button class="juno-alert__close" aria-label="Dismiss">✕</button>
|
|
740
|
+
* </div>
|
|
741
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
742
|
+
|
|
743
|
+
.juno-alert {
|
|
744
|
+
--juno-role: var(--juno-active);
|
|
745
|
+
|
|
746
|
+
display: flex;
|
|
747
|
+
align-items: flex-start;
|
|
748
|
+
gap: var(--juno-space-12);
|
|
749
|
+
padding: var(--juno-pad-surface-block) var(--juno-pad-surface-inline);
|
|
750
|
+
background: color-mix(in srgb, var(--juno-role) 10%, transparent);
|
|
751
|
+
border: var(--juno-border-width-1) solid color-mix(in srgb, var(--juno-role) 30%, transparent);
|
|
752
|
+
border-inline-start: var(--juno-border-width-3) solid var(--juno-role);
|
|
753
|
+
border-radius: var(--juno-radius-5);
|
|
754
|
+
color: var(--juno-data);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.juno-alert__icon {
|
|
758
|
+
flex-shrink: 0;
|
|
759
|
+
color: var(--juno-role);
|
|
760
|
+
font-size: var(--juno-font-size-16);
|
|
761
|
+
line-height: var(--juno-font-lineHeight-none);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.juno-alert__body {
|
|
765
|
+
flex: 1;
|
|
766
|
+
min-inline-size: 0;
|
|
767
|
+
display: flex;
|
|
768
|
+
flex-direction: column;
|
|
769
|
+
gap: var(--juno-space-2);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.juno-alert__title {
|
|
773
|
+
font-family: var(--juno-font-family-sans);
|
|
774
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
775
|
+
font-size: var(--juno-font-size-13);
|
|
776
|
+
color: var(--juno-data);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.juno-alert__text {
|
|
780
|
+
font-size: var(--juno-font-size-13);
|
|
781
|
+
line-height: var(--juno-font-lineHeight-relaxed);
|
|
782
|
+
color: var(--juno-label);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.juno-alert__actions {
|
|
786
|
+
display: flex;
|
|
787
|
+
gap: var(--juno-space-8);
|
|
788
|
+
margin-block-start: var(--juno-space-8);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.juno-alert__close {
|
|
792
|
+
flex-shrink: 0;
|
|
793
|
+
appearance: none;
|
|
794
|
+
padding: 0;
|
|
795
|
+
background: transparent;
|
|
796
|
+
border: none;
|
|
797
|
+
color: var(--juno-muted);
|
|
798
|
+
font-size: var(--juno-font-size-16);
|
|
799
|
+
line-height: var(--juno-font-lineHeight-none);
|
|
800
|
+
cursor: pointer;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.juno-alert__close:hover { color: var(--juno-data); }
|
|
804
|
+
|
|
805
|
+
/* solid — high-emphasis variant: full role fill, text on the base surface */
|
|
806
|
+
.juno-alert--solid {
|
|
807
|
+
background: var(--juno-role);
|
|
808
|
+
border-color: var(--juno-role);
|
|
809
|
+
color: var(--juno-s0);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.juno-alert--solid .juno-alert__icon,
|
|
813
|
+
.juno-alert--solid .juno-alert__title,
|
|
814
|
+
.juno-alert--solid .juno-alert__text,
|
|
815
|
+
.juno-alert--solid .juno-alert__close { color: var(--juno-s0); }
|
|
816
|
+
|
|
817
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
818
|
+
* Component — Avatar · user / entity portrait
|
|
819
|
+
* A round (or squared) portrait that holds an <img>, initials, or an
|
|
820
|
+
* icon fallback. Sizes ride one custom property; an optional role ring
|
|
821
|
+
* marks status (online, alert…). Zero JS. Pairs with a
|
|
822
|
+
* .juno-skeleton--circle placeholder while the image loads.
|
|
823
|
+
* Usage:
|
|
824
|
+
* <span class="juno-avatar"><img src="…" alt="Ada Lovelace" /></span>
|
|
825
|
+
* <span class="juno-avatar juno-avatar--lg">AL</span>
|
|
826
|
+
* <span class="juno-avatar juno-avatar--ring juno--nominal">
|
|
827
|
+
* <svg class="juno-icon"><use href="…#juno-i-user" /></svg>
|
|
828
|
+
* </span>
|
|
829
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
830
|
+
|
|
831
|
+
.juno-avatar {
|
|
832
|
+
--juno-avatar-size: var(--juno-space-32);
|
|
833
|
+
|
|
834
|
+
display: inline-grid;
|
|
835
|
+
place-content: center;
|
|
836
|
+
flex-shrink: 0;
|
|
837
|
+
inline-size: var(--juno-avatar-size);
|
|
838
|
+
block-size: var(--juno-avatar-size);
|
|
839
|
+
overflow: hidden;
|
|
840
|
+
border-radius: 50%;
|
|
841
|
+
background: var(--juno-control-surface);
|
|
842
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
843
|
+
color: var(--juno-label);
|
|
844
|
+
font-family: var(--juno-font-family-sans);
|
|
845
|
+
|
|
846
|
+
/* initials scale with the avatar (~40% of its box) */
|
|
847
|
+
font-size: calc(var(--juno-avatar-size) * 0.4);
|
|
848
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
849
|
+
letter-spacing: var(--juno-font-tracking-tight);
|
|
850
|
+
text-transform: uppercase;
|
|
851
|
+
user-select: none;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/* the image fills the circle */
|
|
855
|
+
.juno-avatar img {
|
|
856
|
+
inline-size: 100%;
|
|
857
|
+
block-size: 100%;
|
|
858
|
+
object-fit: cover;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/* a square / rounded-rect portrait instead of a circle */
|
|
862
|
+
.juno-avatar--square {
|
|
863
|
+
border-radius: var(--juno-radius-4);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/* status ring — reads the role; pair with a label / title for meaning */
|
|
867
|
+
.juno-avatar--ring {
|
|
868
|
+
border-color: var(--juno-role);
|
|
869
|
+
box-shadow: 0 0 0 var(--juno-border-width-2) color-mix(in srgb, var(--juno-role) 30%, transparent);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* sizes */
|
|
873
|
+
.juno-avatar--sm {
|
|
874
|
+
--juno-avatar-size: var(--juno-space-24);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.juno-avatar--lg {
|
|
878
|
+
--juno-avatar-size: var(--juno-space-40);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.juno-avatar--xl {
|
|
882
|
+
--juno-avatar-size: var(--juno-space-56);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/* overlapping cluster — a row of avatars that tuck under each other */
|
|
886
|
+
.juno-avatar-group {
|
|
887
|
+
display: inline-flex;
|
|
888
|
+
align-items: center;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.juno-avatar-group .juno-avatar {
|
|
892
|
+
border-color: var(--juno-s0);
|
|
893
|
+
border-width: var(--juno-border-width-2);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.juno-avatar-group .juno-avatar + .juno-avatar {
|
|
897
|
+
margin-inline-start: calc(var(--juno-avatar-size) * -0.3);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
901
|
+
* Component — Badge / status indicator
|
|
902
|
+
* Usage: <span class="juno-badge juno--nominal">NOMINAL</span>
|
|
903
|
+
* <span class="juno-badge juno-badge--outline juno--caution">…</span>
|
|
904
|
+
* Color comes from --juno-role (set by an .juno--<role> class).
|
|
905
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
906
|
+
|
|
907
|
+
.juno-badge {
|
|
908
|
+
--juno-role: var(--juno-muted);
|
|
909
|
+
|
|
910
|
+
display: inline-flex;
|
|
911
|
+
align-items: center;
|
|
912
|
+
font-family: var(--juno-font-family-sans);
|
|
913
|
+
font-size: var(--juno-font-size-11);
|
|
914
|
+
font-weight: var(--juno-font-weight-bold);
|
|
915
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
916
|
+
text-transform: uppercase;
|
|
917
|
+
padding: var(--juno-space-4) var(--juno-space-10);
|
|
918
|
+
border-radius: var(--juno-radius-3);
|
|
919
|
+
border: var(--juno-border-width-1) solid transparent;
|
|
920
|
+
white-space: nowrap;
|
|
921
|
+
|
|
922
|
+
/* filled (default) */
|
|
923
|
+
background: var(--juno-role);
|
|
924
|
+
color: var(--juno-s0);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.juno-badge--outline {
|
|
928
|
+
background: transparent;
|
|
929
|
+
color: var(--juno-role);
|
|
930
|
+
border-color: var(--juno-role);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/* micro — data-UI atom below the badge scale: provenance/kind tags, model
|
|
934
|
+
names, threshold values. Mono, outline-only, 10px floor (the type scale
|
|
935
|
+
stops there on purpose). Role via juno--* as usual. */
|
|
936
|
+
.juno-badge--micro {
|
|
937
|
+
font-family: var(--juno-font-family-mono);
|
|
938
|
+
font-size: var(--juno-font-size-10);
|
|
939
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
940
|
+
letter-spacing: var(--juno-font-tracking-normal);
|
|
941
|
+
padding: var(--juno-space-2) var(--juno-space-4);
|
|
942
|
+
border-radius: var(--juno-radius-2);
|
|
943
|
+
background: transparent;
|
|
944
|
+
color: var(--juno-role);
|
|
945
|
+
border-color: color-mix(in srgb, var(--juno-role) 55%, transparent);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/* soft — low-fill chip: role text on a tinted wash + faint role border.
|
|
949
|
+
Quieter than filled; ideal for in-table status (a phase/state per row). */
|
|
950
|
+
.juno-badge--soft {
|
|
951
|
+
background: color-mix(in srgb, var(--juno-role) 13%, transparent);
|
|
952
|
+
color: var(--juno-role);
|
|
953
|
+
border-color: color-mix(in srgb, var(--juno-role) 30%, transparent);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
957
|
+
* Component — Breadcrumb · hierarchy trail
|
|
958
|
+
* A horizontal path of links to ancestor pages, ending at the current
|
|
959
|
+
* page. Built on a real <nav><ol>; the separator is a CSS chevron (no
|
|
960
|
+
* glyph font). Mark the last crumb aria-current="page". Zero JS.
|
|
961
|
+
* Usage:
|
|
962
|
+
* <nav class="juno-breadcrumb" aria-label="Breadcrumb">
|
|
963
|
+
* <ol>
|
|
964
|
+
* <li><a href="/">Home</a></li>
|
|
965
|
+
* <li><a href="/fleet">Fleet</a></li>
|
|
966
|
+
* <li><a aria-current="page">api-gateway</a></li>
|
|
967
|
+
* </ol>
|
|
968
|
+
* </nav>
|
|
969
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
970
|
+
|
|
971
|
+
.juno-breadcrumb ol {
|
|
972
|
+
display: flex;
|
|
973
|
+
flex-wrap: wrap;
|
|
974
|
+
align-items: center;
|
|
975
|
+
gap: var(--juno-space-8);
|
|
976
|
+
margin: 0;
|
|
977
|
+
padding: 0;
|
|
978
|
+
list-style: none;
|
|
979
|
+
font-family: var(--juno-font-family-sans);
|
|
980
|
+
font-size: var(--juno-font-size-13);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
.juno-breadcrumb li {
|
|
984
|
+
display: inline-flex;
|
|
985
|
+
align-items: center;
|
|
986
|
+
gap: var(--juno-space-8);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.juno-breadcrumb a {
|
|
990
|
+
color: var(--juno-label);
|
|
991
|
+
text-decoration: none;
|
|
992
|
+
border-radius: var(--juno-radius-2);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
.juno-breadcrumb a[href]:hover {
|
|
996
|
+
color: var(--juno-data);
|
|
997
|
+
text-decoration: underline;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.juno-breadcrumb a:focus-visible {
|
|
1001
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
1002
|
+
outline-offset: var(--juno-space-2);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/* current page — not a link; brightest */
|
|
1006
|
+
.juno-breadcrumb [aria-current='page'] {
|
|
1007
|
+
color: var(--juno-data);
|
|
1008
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* separator — a CSS chevron between items (no glyph font) */
|
|
1012
|
+
.juno-breadcrumb li + li::before {
|
|
1013
|
+
content: '';
|
|
1014
|
+
inline-size: var(--juno-space-4);
|
|
1015
|
+
block-size: var(--juno-space-4);
|
|
1016
|
+
border-block-start: var(--juno-border-width-1) solid var(--juno-muted);
|
|
1017
|
+
border-inline-end: var(--juno-border-width-1) solid var(--juno-muted);
|
|
1018
|
+
transform: rotate(45deg); /* points to the inline-end (flips under RTL via dir) */
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1022
|
+
* Component — Button
|
|
1023
|
+
* Usage: <button class="juno-btn juno--nominal">CONFIRM</button>
|
|
1024
|
+
* <button class="juno-btn juno-btn--ghost">CANCEL</button>
|
|
1025
|
+
* <button class="juno-btn juno-btn--sm">EDIT</button>
|
|
1026
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1027
|
+
|
|
1028
|
+
.juno-btn {
|
|
1029
|
+
--juno-role: var(--juno-active);
|
|
1030
|
+
|
|
1031
|
+
display: inline-flex;
|
|
1032
|
+
align-items: center;
|
|
1033
|
+
justify-content: center;
|
|
1034
|
+
gap: var(--juno-gap-control);
|
|
1035
|
+
min-height: var(--juno-size-tap-min);
|
|
1036
|
+
font-family: var(--juno-font-family-sans);
|
|
1037
|
+
font-size: var(--juno-font-size-13);
|
|
1038
|
+
font-weight: var(--juno-font-weight-bold);
|
|
1039
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
1040
|
+
text-transform: uppercase;
|
|
1041
|
+
padding: var(--juno-pad-control-block) var(--juno-pad-control-inline);
|
|
1042
|
+
border: var(--juno-border-width-1) solid transparent;
|
|
1043
|
+
border-radius: var(--juno-radius-4);
|
|
1044
|
+
cursor: pointer;
|
|
1045
|
+
|
|
1046
|
+
/* primary (filled) */
|
|
1047
|
+
background: var(--juno-role);
|
|
1048
|
+
color: var(--juno-s0);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.juno-btn:hover { filter: brightness(1.08); }
|
|
1052
|
+
.juno-btn:active { filter: brightness(0.94); }
|
|
1053
|
+
|
|
1054
|
+
.juno-btn--ghost {
|
|
1055
|
+
background: transparent;
|
|
1056
|
+
color: var(--juno-data);
|
|
1057
|
+
border-color: var(--juno-border);
|
|
1058
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
1059
|
+
}
|
|
1060
|
+
.juno-btn--ghost:hover { background: var(--juno-s2); filter: none; }
|
|
1061
|
+
|
|
1062
|
+
/* Small — for dense desktop toolbars (40–46px bars) where the default
|
|
1063
|
+
control would not fit. Deliberately under the WCAG tap-min target:
|
|
1064
|
+
keep the default size for touch surfaces. Combines with --ghost + roles. */
|
|
1065
|
+
.juno-btn--sm {
|
|
1066
|
+
min-height: var(--juno-space-24);
|
|
1067
|
+
font-size: var(--juno-font-size-11);
|
|
1068
|
+
letter-spacing: var(--juno-font-tracking-wide);
|
|
1069
|
+
padding: var(--juno-space-4) var(--juno-space-10);
|
|
1070
|
+
border-radius: var(--juno-radius-3);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.juno-btn:disabled {
|
|
1074
|
+
background: var(--juno-muted);
|
|
1075
|
+
color: var(--juno-label);
|
|
1076
|
+
border-color: transparent;
|
|
1077
|
+
cursor: not-allowed;
|
|
1078
|
+
filter: none;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1082
|
+
* Component — Card / panel
|
|
1083
|
+
* Usage:
|
|
1084
|
+
* <article class="juno-card">
|
|
1085
|
+
* <header class="juno-card__head">…<span class="juno-badge …">…</span></header>
|
|
1086
|
+
* <div class="juno-card__body">…</div>
|
|
1087
|
+
* </article>
|
|
1088
|
+
* Add a status accent edge: <article class="juno-card juno-card--accent juno--caution">
|
|
1089
|
+
* Critical full-border: <article class="juno-card juno-card--alert juno--warning">
|
|
1090
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1091
|
+
|
|
1092
|
+
.juno-card {
|
|
1093
|
+
--juno-role: var(--juno-active);
|
|
1094
|
+
|
|
1095
|
+
background: var(--juno-s1);
|
|
1096
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
1097
|
+
border-radius: var(--juno-radius-8);
|
|
1098
|
+
overflow: hidden;
|
|
1099
|
+
container-type: inline-size; /* rows below adapt to the card's own width */
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/* When the card itself is narrow, stack label/value rows. Driven by the
|
|
1103
|
+
card's container width, not the viewport — works in any layout. */
|
|
1104
|
+
@container (max-width: 320px) {
|
|
1105
|
+
.juno-card__row {
|
|
1106
|
+
flex-direction: column;
|
|
1107
|
+
align-items: flex-start;
|
|
1108
|
+
gap: var(--juno-space-2);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.juno-card--accent { border-top: var(--juno-border-width-2) solid var(--juno-role); }
|
|
1113
|
+
|
|
1114
|
+
.juno-card--alert { border-color: var(--juno-role); border-top-width: var(--juno-border-width-2); }
|
|
1115
|
+
|
|
1116
|
+
.juno-card__head {
|
|
1117
|
+
display: flex;
|
|
1118
|
+
align-items: center;
|
|
1119
|
+
justify-content: space-between;
|
|
1120
|
+
gap: var(--juno-space-8);
|
|
1121
|
+
padding: var(--juno-space-12) var(--juno-space-16);
|
|
1122
|
+
border-bottom: var(--juno-border-width-1) solid var(--juno-border);
|
|
1123
|
+
font-size: var(--juno-font-size-12);
|
|
1124
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
1125
|
+
letter-spacing: var(--juno-font-tracking-wide);
|
|
1126
|
+
text-transform: uppercase;
|
|
1127
|
+
color: var(--juno-data);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.juno-card--alert .juno-card__head {
|
|
1131
|
+
background: var(--juno-s2);
|
|
1132
|
+
border-bottom-color: var(--juno-role);
|
|
1133
|
+
color: var(--juno-role);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.juno-card__body {
|
|
1137
|
+
display: flex;
|
|
1138
|
+
flex-direction: column;
|
|
1139
|
+
gap: var(--juno-space-12);
|
|
1140
|
+
padding: var(--juno-pad-surface-block) var(--juno-pad-surface-inline);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/* Label / value row inside a card body */
|
|
1144
|
+
.juno-card__row {
|
|
1145
|
+
display: flex;
|
|
1146
|
+
align-items: baseline;
|
|
1147
|
+
justify-content: space-between;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1151
|
+
* Component — Checkbox & Radio
|
|
1152
|
+
* Custom-styled native inputs (appearance:none) — keyboard + screen-reader
|
|
1153
|
+
* native, zero JS. Checkbox is a targeting-reticle square whose solid core
|
|
1154
|
+
* drops in on check; radio is a ring with a center dot. Default tint ACTIVE.
|
|
1155
|
+
* Wrap with .juno-choice for an inline label row.
|
|
1156
|
+
* Usage:
|
|
1157
|
+
* <label class="juno-choice">
|
|
1158
|
+
* <input class="juno-checkbox" type="checkbox" checked /> TCAS
|
|
1159
|
+
* </label>
|
|
1160
|
+
* <label class="juno-choice">
|
|
1161
|
+
* <input class="juno-radio" type="radio" name="mode" /> MANAGED
|
|
1162
|
+
* </label>
|
|
1163
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1164
|
+
|
|
1165
|
+
.juno-choice {
|
|
1166
|
+
display: inline-flex;
|
|
1167
|
+
align-items: center;
|
|
1168
|
+
gap: var(--juno-gap-control);
|
|
1169
|
+
font-family: var(--juno-font-family-sans);
|
|
1170
|
+
font-size: var(--juno-font-size-13);
|
|
1171
|
+
color: var(--juno-data);
|
|
1172
|
+
cursor: pointer;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.juno-checkbox,
|
|
1176
|
+
.juno-radio {
|
|
1177
|
+
--juno-role: var(--juno-active);
|
|
1178
|
+
|
|
1179
|
+
appearance: none;
|
|
1180
|
+
margin: 0;
|
|
1181
|
+
flex-shrink: 0;
|
|
1182
|
+
inline-size: var(--juno-space-16);
|
|
1183
|
+
block-size: var(--juno-space-16);
|
|
1184
|
+
display: inline-grid;
|
|
1185
|
+
place-content: center;
|
|
1186
|
+
background: var(--juno-control-surface);
|
|
1187
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
1188
|
+
cursor: pointer;
|
|
1189
|
+
transition:
|
|
1190
|
+
border-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
1191
|
+
box-shadow var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.juno-checkbox { border-radius: var(--juno-radius-2); }
|
|
1195
|
+
.juno-radio { border-radius: 50%; }
|
|
1196
|
+
|
|
1197
|
+
/* the core / dot — drops in on check with a slight overshoot */
|
|
1198
|
+
.juno-checkbox::before,
|
|
1199
|
+
.juno-radio::before {
|
|
1200
|
+
content: '';
|
|
1201
|
+
inline-size: var(--juno-space-8);
|
|
1202
|
+
block-size: var(--juno-space-8);
|
|
1203
|
+
background: var(--juno-role);
|
|
1204
|
+
transform: scale(0);
|
|
1205
|
+
transition: transform var(--juno-motion-duration-quick) var(--juno-motion-ease-spring);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.juno-checkbox::before { border-radius: 1px; }
|
|
1209
|
+
.juno-radio::before { border-radius: 50%; }
|
|
1210
|
+
|
|
1211
|
+
.juno-checkbox:checked,
|
|
1212
|
+
.juno-radio:checked {
|
|
1213
|
+
border-color: var(--juno-role);
|
|
1214
|
+
box-shadow: 0 0 0 var(--juno-border-width-3) color-mix(in srgb, var(--juno-role) 22%, transparent);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.juno-checkbox:checked::before,
|
|
1218
|
+
.juno-radio:checked::before { transform: scale(1); }
|
|
1219
|
+
|
|
1220
|
+
/* indeterminate (e.g. a "select all" with a partial set) — a dash, not a core */
|
|
1221
|
+
.juno-checkbox:indeterminate {
|
|
1222
|
+
border-color: var(--juno-role);
|
|
1223
|
+
box-shadow: 0 0 0 var(--juno-border-width-3) color-mix(in srgb, var(--juno-role) 22%, transparent);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.juno-checkbox:indeterminate::before {
|
|
1227
|
+
transform: scale(1);
|
|
1228
|
+
block-size: 2px;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.juno-checkbox:disabled,
|
|
1232
|
+
.juno-radio:disabled {
|
|
1233
|
+
background: var(--juno-s1);
|
|
1234
|
+
opacity: var(--juno-opacity-disabled);
|
|
1235
|
+
cursor: not-allowed;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.juno-choice:has(:disabled) { color: var(--juno-muted); cursor: not-allowed; }
|
|
1239
|
+
|
|
1240
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1241
|
+
* Component — Chip / Tag · compact interactive token
|
|
1242
|
+
* A pill that represents a discrete entity: a filter, a selected value,
|
|
1243
|
+
* a removable tag. Unlike a badge (a static status label), a chip is
|
|
1244
|
+
* interactive — selectable and/or dismissible. Reads --juno-role for its
|
|
1245
|
+
* accent. Use a <button> for a toggle chip; add a remove button for a
|
|
1246
|
+
* dismissible one. Zero JS — the app owns selection / removal.
|
|
1247
|
+
* Usage:
|
|
1248
|
+
* <span class="juno-chip">us-east-1</span>
|
|
1249
|
+
* <button class="juno-chip juno-chip--toggle" aria-pressed="true">Active</button>
|
|
1250
|
+
* <span class="juno-chip juno--active">
|
|
1251
|
+
* filter:prod
|
|
1252
|
+
* <button class="juno-chip__remove" aria-label="Remove">
|
|
1253
|
+
* <svg class="juno-icon juno-icon--sm"><use href="…#juno-i-x" /></svg>
|
|
1254
|
+
* </button>
|
|
1255
|
+
* </span>
|
|
1256
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1257
|
+
|
|
1258
|
+
.juno-chip {
|
|
1259
|
+
--juno-role: var(--juno-label);
|
|
1260
|
+
|
|
1261
|
+
display: inline-flex;
|
|
1262
|
+
align-items: center;
|
|
1263
|
+
gap: var(--juno-space-8);
|
|
1264
|
+
block-size: var(--juno-space-24);
|
|
1265
|
+
padding-inline: var(--juno-space-12);
|
|
1266
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
1267
|
+
border-radius: 999px;
|
|
1268
|
+
background: var(--juno-control-surface);
|
|
1269
|
+
color: var(--juno-data);
|
|
1270
|
+
font-family: var(--juno-font-family-sans);
|
|
1271
|
+
font-size: var(--juno-font-size-12);
|
|
1272
|
+
letter-spacing: var(--juno-font-tracking-tight);
|
|
1273
|
+
white-space: nowrap;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/* leading accent dot — reads the role; appears when a role class is set */
|
|
1277
|
+
.juno-chip.juno--nominal,
|
|
1278
|
+
.juno-chip.juno--active,
|
|
1279
|
+
.juno-chip.juno--target,
|
|
1280
|
+
.juno-chip.juno--caution,
|
|
1281
|
+
.juno-chip.juno--warning {
|
|
1282
|
+
border-color: var(--juno-role);
|
|
1283
|
+
color: var(--juno-role);
|
|
1284
|
+
background: color-mix(in srgb, var(--juno-role) 12%, var(--juno-control-surface));
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/* toggle chip — a real <button>; pressed = filled accent */
|
|
1288
|
+
button.juno-chip {
|
|
1289
|
+
cursor: pointer;
|
|
1290
|
+
transition:
|
|
1291
|
+
border-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
1292
|
+
background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
.juno-chip--toggle[aria-pressed='true'] {
|
|
1296
|
+
border-color: var(--juno-role);
|
|
1297
|
+
background: color-mix(in srgb, var(--juno-role) 18%, var(--juno-control-surface));
|
|
1298
|
+
color: var(--juno-role);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.juno-chip:focus-visible {
|
|
1302
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
1303
|
+
outline-offset: var(--juno-space-2);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/* remove affordance — a tiny inset button at the trailing edge */
|
|
1307
|
+
.juno-chip__remove {
|
|
1308
|
+
display: inline-grid;
|
|
1309
|
+
place-content: center;
|
|
1310
|
+
margin-inline-end: calc(var(--juno-space-4) * -1);
|
|
1311
|
+
padding: 0;
|
|
1312
|
+
border: 0;
|
|
1313
|
+
background: none;
|
|
1314
|
+
color: inherit;
|
|
1315
|
+
opacity: var(--juno-opacity-muted);
|
|
1316
|
+
cursor: pointer;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.juno-chip__remove:hover,
|
|
1320
|
+
.juno-chip__remove:focus-visible {
|
|
1321
|
+
opacity: 1;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
.juno-chip:disabled,
|
|
1325
|
+
.juno-chip[aria-disabled='true'] {
|
|
1326
|
+
opacity: var(--juno-opacity-disabled);
|
|
1327
|
+
cursor: not-allowed;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1331
|
+
* Component — Divider · visual / semantic separation
|
|
1332
|
+
* A hairline rule. Horizontal by default; add --vertical for a column
|
|
1333
|
+
* separator (stretches to the row's cross size). An optional centered
|
|
1334
|
+
* label breaks the rule in two ("OR", "MORE"). Use a real <hr> for a
|
|
1335
|
+
* semantic break, or a styled element when it's purely visual.
|
|
1336
|
+
* Usage:
|
|
1337
|
+
* <hr class="juno-divider" />
|
|
1338
|
+
* <div class="juno-divider juno-divider--label">OR</div>
|
|
1339
|
+
* <span class="juno-divider juno-divider--vertical"></span>
|
|
1340
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1341
|
+
|
|
1342
|
+
.juno-divider {
|
|
1343
|
+
border: 0;
|
|
1344
|
+
margin: 0;
|
|
1345
|
+
block-size: var(--juno-border-width-1);
|
|
1346
|
+
inline-size: 100%;
|
|
1347
|
+
background: var(--juno-border);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/* vertical rule — sits inline; relies on the parent giving it block size */
|
|
1351
|
+
.juno-divider--vertical {
|
|
1352
|
+
inline-size: var(--juno-border-width-1);
|
|
1353
|
+
block-size: auto;
|
|
1354
|
+
align-self: stretch;
|
|
1355
|
+
min-block-size: var(--juno-space-16);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/* labeled rule — a centered caption with a line on each side */
|
|
1359
|
+
.juno-divider--label {
|
|
1360
|
+
display: flex;
|
|
1361
|
+
align-items: center;
|
|
1362
|
+
gap: var(--juno-space-12);
|
|
1363
|
+
border: 0;
|
|
1364
|
+
background: none;
|
|
1365
|
+
block-size: auto;
|
|
1366
|
+
color: var(--juno-label);
|
|
1367
|
+
font-family: var(--juno-font-family-sans);
|
|
1368
|
+
font-size: var(--juno-font-size-11);
|
|
1369
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
1370
|
+
text-transform: uppercase;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.juno-divider--label::before,
|
|
1374
|
+
.juno-divider--label::after {
|
|
1375
|
+
content: '';
|
|
1376
|
+
flex: 1;
|
|
1377
|
+
block-size: var(--juno-border-width-1);
|
|
1378
|
+
background: var(--juno-border);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1382
|
+
* Component — Dock (app-shell bottom navigation)
|
|
1383
|
+
* The narrow-viewport counterpart of the rail: 3–5 primary destinations
|
|
1384
|
+
* as icon-over-label tap targets pinned to the bottom edge. Zero JS —
|
|
1385
|
+
* the app sets aria-current="page"; the active item styles off the
|
|
1386
|
+
* attribute, exactly like the rail.
|
|
1387
|
+
*
|
|
1388
|
+
* Sticky (not fixed) so it stays in flow: put it last inside the
|
|
1389
|
+
* scrolling column and it pins itself without overlapping content.
|
|
1390
|
+
* Pads for the home-indicator via safe-area-inset. Pair with the rail
|
|
1391
|
+
* using the viewport helpers (see docs/layout.md#app-shell):
|
|
1392
|
+
* <nav class="juno-rail juno-hide-below-md" …>
|
|
1393
|
+
* <nav class="juno-dock juno-hide-from-md" aria-label="Primary">
|
|
1394
|
+
* <a class="juno-dock__item" href="…" aria-current="page">
|
|
1395
|
+
* <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
|
|
1396
|
+
* <span class="juno-dock__label">Library</span>
|
|
1397
|
+
* </a>
|
|
1398
|
+
* </nav>
|
|
1399
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1400
|
+
|
|
1401
|
+
.juno-dock {
|
|
1402
|
+
--juno-role: var(--juno-active);
|
|
1403
|
+
|
|
1404
|
+
position: sticky;
|
|
1405
|
+
inset-block-end: 0;
|
|
1406
|
+
z-index: var(--juno-z-raised);
|
|
1407
|
+
display: flex;
|
|
1408
|
+
align-items: stretch;
|
|
1409
|
+
background: var(--juno-s1);
|
|
1410
|
+
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
1411
|
+
padding-block-end: env(safe-area-inset-bottom, 0);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
.juno-dock__item {
|
|
1415
|
+
flex: 1 1 0;
|
|
1416
|
+
min-inline-size: 0;
|
|
1417
|
+
min-block-size: var(--juno-size-tap-comfortable);
|
|
1418
|
+
display: flex;
|
|
1419
|
+
flex-direction: column;
|
|
1420
|
+
align-items: center;
|
|
1421
|
+
justify-content: center;
|
|
1422
|
+
gap: var(--juno-space-2);
|
|
1423
|
+
padding-block: var(--juno-space-4);
|
|
1424
|
+
border-block-start: var(--juno-border-width-2) solid transparent;
|
|
1425
|
+
font-family: var(--juno-font-family-sans);
|
|
1426
|
+
font-size: var(--juno-font-size-10);
|
|
1427
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
1428
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
1429
|
+
text-transform: uppercase;
|
|
1430
|
+
text-decoration: none;
|
|
1431
|
+
color: var(--juno-label);
|
|
1432
|
+
cursor: pointer;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
.juno-dock__label {
|
|
1436
|
+
max-inline-size: 100%;
|
|
1437
|
+
overflow: hidden;
|
|
1438
|
+
text-overflow: ellipsis;
|
|
1439
|
+
white-space: nowrap;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
.juno-dock__item:hover { color: var(--juno-data); }
|
|
1443
|
+
|
|
1444
|
+
/* active = attribute, not class: <a aria-current="page"> */
|
|
1445
|
+
.juno-dock__item[aria-current] {
|
|
1446
|
+
border-block-start-color: var(--juno-role);
|
|
1447
|
+
background: var(--juno-s2);
|
|
1448
|
+
color: var(--juno-data);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.juno-dock__item:focus-visible {
|
|
1452
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
1453
|
+
outline-offset: calc(-1 * var(--juno-space-2));
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1457
|
+
* Component — Status dot + label
|
|
1458
|
+
* Usage:
|
|
1459
|
+
* <span class="juno-status juno--nominal">
|
|
1460
|
+
* <span class="juno-status__dot"></span>NOMINAL
|
|
1461
|
+
* </span>
|
|
1462
|
+
* Pair color with the text label — color is never the sole signal.
|
|
1463
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1464
|
+
|
|
1465
|
+
.juno-status {
|
|
1466
|
+
--juno-role: var(--juno-nominal);
|
|
1467
|
+
|
|
1468
|
+
display: inline-flex;
|
|
1469
|
+
align-items: center;
|
|
1470
|
+
gap: var(--juno-space-8);
|
|
1471
|
+
font-size: var(--juno-font-size-12);
|
|
1472
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
1473
|
+
color: var(--juno-role);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
.juno-status__dot {
|
|
1477
|
+
width: var(--juno-size-dot-sm);
|
|
1478
|
+
height: var(--juno-size-dot-sm);
|
|
1479
|
+
border-radius: 50%;
|
|
1480
|
+
background: var(--juno-role);
|
|
1481
|
+
flex-shrink: 0;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
.juno-status--lg .juno-status__dot {
|
|
1485
|
+
width: var(--juno-size-dot-md);
|
|
1486
|
+
height: var(--juno-size-dot-md);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/* Live/armed pulse — respects prefers-reduced-motion via base layer. */
|
|
1490
|
+
.juno-status--live .juno-status__dot { animation: juno-blink 1.4s ease-in-out infinite; }
|
|
1491
|
+
|
|
1492
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1493
|
+
* Component — Drawer
|
|
1494
|
+
* Edge-anchored <dialog> for secondary tasks and settings. Same scrim and
|
|
1495
|
+
* layer as the modal, but slides along one axis. App owns showModal()/close().
|
|
1496
|
+
* Default anchors to the inline-end edge; modifiers move it.
|
|
1497
|
+
* Usage:
|
|
1498
|
+
* <dialog class="juno-modal juno-drawer">…</dialog> (inline-end)
|
|
1499
|
+
* <dialog class="juno-modal juno-drawer juno-drawer--start">…</dialog>
|
|
1500
|
+
* <dialog class="juno-modal juno-drawer juno-drawer--bottom">…</dialog>
|
|
1501
|
+
* Reuses .juno-modal for the surface + scrim; this file re-pins + re-slides.
|
|
1502
|
+
* Selectors are compound (.juno-modal.juno-drawer) so they override the
|
|
1503
|
+
* modal's centered transform regardless of bundle order.
|
|
1504
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1505
|
+
|
|
1506
|
+
.juno-modal.juno-drawer {
|
|
1507
|
+
inline-size: 380px;
|
|
1508
|
+
max-inline-size: 100%;
|
|
1509
|
+
block-size: 100dvh;
|
|
1510
|
+
max-block-size: 100dvh;
|
|
1511
|
+
border-radius: 0;
|
|
1512
|
+
border: none;
|
|
1513
|
+
|
|
1514
|
+
/* pin to the inline-end edge (mirrors under RTL) */
|
|
1515
|
+
margin-block: 0;
|
|
1516
|
+
margin-inline: auto 0;
|
|
1517
|
+
border-inline-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
1518
|
+
|
|
1519
|
+
/* slide in from the edge; base speed (lighter than a modal) */
|
|
1520
|
+
transform: translateX(100%);
|
|
1521
|
+
transition:
|
|
1522
|
+
transform var(--juno-motion-duration-base) var(--juno-motion-ease-decel),
|
|
1523
|
+
overlay var(--juno-motion-duration-base) allow-discrete,
|
|
1524
|
+
display var(--juno-motion-duration-base) allow-discrete;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
.juno-modal.juno-drawer[open] { transform: translateX(0); }
|
|
1528
|
+
|
|
1529
|
+
@starting-style {
|
|
1530
|
+
.juno-modal.juno-drawer[open] { transform: translateX(100%); }
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.juno-modal.juno-drawer--start {
|
|
1534
|
+
margin-inline: 0 auto;
|
|
1535
|
+
border-inline: none;
|
|
1536
|
+
border-inline-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
1537
|
+
transform: translateX(-100%);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
.juno-modal.juno-drawer--start[open] { transform: translateX(0); }
|
|
1541
|
+
|
|
1542
|
+
@starting-style {
|
|
1543
|
+
.juno-modal.juno-drawer--start[open] { transform: translateX(-100%); }
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
.juno-modal.juno-drawer--bottom {
|
|
1547
|
+
inline-size: 100%;
|
|
1548
|
+
max-inline-size: 100%;
|
|
1549
|
+
block-size: 60dvh;
|
|
1550
|
+
margin-block: auto 0;
|
|
1551
|
+
margin-inline: 0;
|
|
1552
|
+
border: none;
|
|
1553
|
+
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
1554
|
+
transform: translateY(100%);
|
|
1555
|
+
|
|
1556
|
+
/* keep content above the home indicator */
|
|
1557
|
+
padding-block-end: env(safe-area-inset-bottom, 0);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.juno-modal.juno-drawer--bottom[open] { transform: translateY(0); }
|
|
1561
|
+
|
|
1562
|
+
@starting-style {
|
|
1563
|
+
.juno-modal.juno-drawer--bottom[open] { transform: translateY(100%); }
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/* ── narrow viewports ────────────────────────────────────────────────────
|
|
1567
|
+
Side drawers cap below the full width so a sliver of scrim survives —
|
|
1568
|
+
the visual cue (and tap target) that "outside" closes the panel. */
|
|
1569
|
+
@media (width <= 639.98px) {
|
|
1570
|
+
.juno-modal.juno-drawer:not(.juno-drawer--bottom) { inline-size: min(380px, 85vw); }
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1574
|
+
* Component — Field
|
|
1575
|
+
* Wraps any control with a label, optional help, and an error message.
|
|
1576
|
+
* Usage:
|
|
1577
|
+
* <div class="juno-field">
|
|
1578
|
+
* <label class="juno-field__label" for="cs">Order ID
|
|
1579
|
+
* <span class="juno-field__req" aria-hidden="true">*</span>
|
|
1580
|
+
* </label>
|
|
1581
|
+
* <input id="cs" class="juno-input juno-mono" type="text" />
|
|
1582
|
+
* <span class="juno-field__help">Prefix + number</span>
|
|
1583
|
+
* </div>
|
|
1584
|
+
* Error: add aria-invalid="true" to the control and swap help → __error.
|
|
1585
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1586
|
+
|
|
1587
|
+
.juno-field {
|
|
1588
|
+
display: flex;
|
|
1589
|
+
flex-direction: column;
|
|
1590
|
+
gap: var(--juno-space-4);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.juno-field__label {
|
|
1594
|
+
font-family: var(--juno-font-family-sans);
|
|
1595
|
+
font-size: var(--juno-font-size-10);
|
|
1596
|
+
letter-spacing: var(--juno-font-tracking-wider);
|
|
1597
|
+
text-transform: uppercase;
|
|
1598
|
+
color: var(--juno-label);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/* required marker — warning hue, paired with aria-required on the control */
|
|
1602
|
+
.juno-field__req {
|
|
1603
|
+
color: var(--juno-warning);
|
|
1604
|
+
margin-inline-start: var(--juno-space-2);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.juno-field__help { font-size: var(--juno-font-size-11); color: var(--juno-label); }
|
|
1608
|
+
|
|
1609
|
+
.juno-field__error { font-size: var(--juno-font-size-11); color: var(--juno-warning); }
|
|
1610
|
+
|
|
1611
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1612
|
+
* Component — Gauge (determinate metric ring)
|
|
1613
|
+
* A fixed-value ring for readouts (CPU / RAM / DISK …) — the metric
|
|
1614
|
+
* cousin of the arc loader. Set --juno-gauge-value (0–100) inline or
|
|
1615
|
+
* from JS; color comes from --juno-role. junoui ships no threshold
|
|
1616
|
+
* policy: the app maps value → role class (see gauge.md for the
|
|
1617
|
+
* 75/90 recipe).
|
|
1618
|
+
* Usage:
|
|
1619
|
+
* <div class="juno-gauge juno--nominal" style="--juno-gauge-value: 67">
|
|
1620
|
+
* <span class="juno-gauge__value">67</span>
|
|
1621
|
+
* </div>
|
|
1622
|
+
* <span class="juno-gauge__label">DISK</span>
|
|
1623
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1624
|
+
|
|
1625
|
+
/* registered so the sweep can transition when the value changes */
|
|
1626
|
+
@property --juno-gauge-value {
|
|
1627
|
+
syntax: '<number>';
|
|
1628
|
+
inherits: true;
|
|
1629
|
+
initial-value: 0;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.juno-gauge {
|
|
1633
|
+
--juno-role: var(--juno-active);
|
|
1634
|
+
--juno-gauge-size: 64px;
|
|
1635
|
+
--juno-gauge-width: 4px;
|
|
1636
|
+
|
|
1637
|
+
position: relative;
|
|
1638
|
+
display: inline-flex;
|
|
1639
|
+
align-items: center;
|
|
1640
|
+
justify-content: center;
|
|
1641
|
+
inline-size: var(--juno-gauge-size);
|
|
1642
|
+
block-size: var(--juno-gauge-size);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/* the ring lives on a pseudo-element: the punch-out mask would clip a
|
|
1646
|
+
child, so the value stays a real child of the unmasked wrapper */
|
|
1647
|
+
.juno-gauge::before {
|
|
1648
|
+
content: '';
|
|
1649
|
+
position: absolute;
|
|
1650
|
+
inset: 0;
|
|
1651
|
+
border-radius: 50%;
|
|
1652
|
+
background: conic-gradient(
|
|
1653
|
+
var(--juno-role) calc(var(--juno-gauge-value) * 1%),
|
|
1654
|
+
var(--juno-border) 0
|
|
1655
|
+
);
|
|
1656
|
+
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-gauge-width)), #000 calc(100% - var(--juno-gauge-width)));
|
|
1657
|
+
mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-gauge-width)), #000 calc(100% - var(--juno-gauge-width)));
|
|
1658
|
+
transition: --juno-gauge-value var(--juno-motion-duration-base) var(--juno-motion-ease-standard);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
.juno-gauge__value {
|
|
1662
|
+
font-family: var(--juno-font-family-mono);
|
|
1663
|
+
font-size: var(--juno-font-size-13);
|
|
1664
|
+
font-weight: var(--juno-font-weight-bold);
|
|
1665
|
+
color: var(--juno-role);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/* caption under (or beside) the ring — a sibling, app-positioned */
|
|
1669
|
+
.juno-gauge__label {
|
|
1670
|
+
font-family: var(--juno-font-family-sans);
|
|
1671
|
+
font-size: var(--juno-font-size-10);
|
|
1672
|
+
font-weight: var(--juno-font-weight-bold);
|
|
1673
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
1674
|
+
text-transform: uppercase;
|
|
1675
|
+
color: var(--juno-label);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
.juno-gauge--sm {
|
|
1679
|
+
--juno-gauge-size: 44px;
|
|
1680
|
+
--juno-gauge-width: 3px;
|
|
1681
|
+
}
|
|
1682
|
+
.juno-gauge--sm .juno-gauge__value { font-size: var(--juno-font-size-11); }
|
|
1683
|
+
|
|
1684
|
+
.juno-gauge--lg {
|
|
1685
|
+
--juno-gauge-size: 96px;
|
|
1686
|
+
--juno-gauge-width: 5px;
|
|
1687
|
+
}
|
|
1688
|
+
.juno-gauge--lg .juno-gauge__value { font-size: var(--juno-font-size-20); }
|
|
1689
|
+
|
|
1690
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1691
|
+
* Component — Icon · inline SVG glyph
|
|
1692
|
+
* A zero-JS icon primitive. Reference a symbol from the sprite; the glyph
|
|
1693
|
+
* inherits the current text color (so a .juno--<role> ancestor tints it)
|
|
1694
|
+
* and sizes in `em`, tracking font-size and the [data-juno-text] scale.
|
|
1695
|
+
* Usage:
|
|
1696
|
+
* <svg class="juno-icon" aria-hidden="true">
|
|
1697
|
+
* <use href="path/to/juno-icons.svg#juno-i-gear" />
|
|
1698
|
+
* </svg>
|
|
1699
|
+
* <svg class="juno-icon juno-icon--lg juno--warning" role="img"
|
|
1700
|
+
* aria-label="Warning">
|
|
1701
|
+
* <use href="…#juno-i-warning" />
|
|
1702
|
+
* </svg>
|
|
1703
|
+
* Sizing: --juno-icon-size (default 1.25em). Color: currentColor — set
|
|
1704
|
+
* color, or add .juno-icon--role to read var(--juno-role).
|
|
1705
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1706
|
+
|
|
1707
|
+
.juno-icon {
|
|
1708
|
+
--juno-icon-size: 1.25em;
|
|
1709
|
+
|
|
1710
|
+
display: inline-block;
|
|
1711
|
+
inline-size: var(--juno-icon-size);
|
|
1712
|
+
block-size: var(--juno-icon-size);
|
|
1713
|
+
flex-shrink: 0;
|
|
1714
|
+
vertical-align: -0.2em; /* optical baseline alignment next to text */
|
|
1715
|
+
fill: currentcolor;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/* tint by semantic role instead of the surrounding text color */
|
|
1719
|
+
.juno-icon--role { color: var(--juno-role); }
|
|
1720
|
+
|
|
1721
|
+
/* sizes — relative, so they ride the type scale */
|
|
1722
|
+
.juno-icon--sm { --juno-icon-size: 1em; }
|
|
1723
|
+
.juno-icon--lg { --juno-icon-size: 1.75em; }
|
|
1724
|
+
.juno-icon--xl { --juno-icon-size: 2.5em; }
|
|
1725
|
+
|
|
1726
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1727
|
+
* Component — Text input / textarea
|
|
1728
|
+
* Inputs are precise: mono by default (codes, IDs, tokens, values),
|
|
1729
|
+
* ACTIVE-cyan focus ring always visible, semantic color reserved for
|
|
1730
|
+
* validation. Add .juno-input--sans for free-text fields.
|
|
1731
|
+
* Usage: <input class="juno-input" type="text" value="ORD-519" />
|
|
1732
|
+
* <input class="juno-input juno-input--sans" placeholder="Type…" />
|
|
1733
|
+
* <input class="juno-input juno--target" value="38000" /> (managed)
|
|
1734
|
+
* <input class="juno-input" aria-invalid="true" /> (error)
|
|
1735
|
+
* <textarea class="juno-input juno-input--sans"></textarea>
|
|
1736
|
+
*
|
|
1737
|
+
* --juno-role defaults to the control edge, so the resting border is quiet;
|
|
1738
|
+
* a role class or aria-invalid re-colors it. Focus shows ACTIVE + a 1px ring.
|
|
1739
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1740
|
+
|
|
1741
|
+
.juno-input {
|
|
1742
|
+
--juno-role: var(--juno-control-edge-strong);
|
|
1743
|
+
|
|
1744
|
+
display: block;
|
|
1745
|
+
inline-size: 100%;
|
|
1746
|
+
min-block-size: var(--juno-size-tap-min);
|
|
1747
|
+
background: var(--juno-control-surface);
|
|
1748
|
+
border: var(--juno-border-width-1) solid var(--juno-role);
|
|
1749
|
+
border-radius: var(--juno-radius-4);
|
|
1750
|
+
padding: var(--juno-pad-control-block) var(--juno-space-12);
|
|
1751
|
+
font-family: var(--juno-font-family-mono);
|
|
1752
|
+
font-size: var(--juno-font-size-14);
|
|
1753
|
+
color: var(--juno-data);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
.juno-input--sans { font-family: var(--juno-font-family-sans); }
|
|
1757
|
+
|
|
1758
|
+
.juno-input::placeholder { color: var(--juno-muted); }
|
|
1759
|
+
|
|
1760
|
+
/* focus — ACTIVE border + 1px ring (replaces the base outline on inputs) */
|
|
1761
|
+
.juno-input:focus-visible {
|
|
1762
|
+
outline: none;
|
|
1763
|
+
border-color: var(--juno-active);
|
|
1764
|
+
box-shadow: 0 0 0 var(--juno-border-width-1) var(--juno-active);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/* invalid: warning border + text, tied to the a11y attribute */
|
|
1768
|
+
.juno-input[aria-invalid='true'] {
|
|
1769
|
+
--juno-role: var(--juno-warning);
|
|
1770
|
+
|
|
1771
|
+
color: var(--juno-warning);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.juno-input:disabled {
|
|
1775
|
+
background: var(--juno-s1);
|
|
1776
|
+
border-color: var(--juno-border);
|
|
1777
|
+
color: var(--juno-muted);
|
|
1778
|
+
cursor: not-allowed;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
textarea.juno-input {
|
|
1782
|
+
min-block-size: calc(var(--juno-size-tap-min) * 3);
|
|
1783
|
+
resize: vertical;
|
|
1784
|
+
line-height: var(--juno-font-lineHeight-loose);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1788
|
+
* Component — List (grouped rows / vertical data organization)
|
|
1789
|
+
* The settings-screen pattern: labeled groups of full-width rows, each
|
|
1790
|
+
* row a leading icon + label (+ support line) with a trailing value,
|
|
1791
|
+
* control, or drill-in chevron. Rows are plain <div>s for static data
|
|
1792
|
+
* or <a>/<button> when they navigate — the look is the same; only
|
|
1793
|
+
* interactive rows get hover/press feedback. Zero JS.
|
|
1794
|
+
* Usage:
|
|
1795
|
+
* <section class="juno-list">
|
|
1796
|
+
* <h3 class="juno-list__header">Playback</h3>
|
|
1797
|
+
* <ul class="juno-list__group">
|
|
1798
|
+
* <li><a class="juno-list__row" href="…">
|
|
1799
|
+
* <svg class="juno-icon juno-list__icon" aria-hidden="true"><use href="…#juno-i-sliders" /></svg>
|
|
1800
|
+
* <span class="juno-list__main">
|
|
1801
|
+
* <span class="juno-list__label">Quality</span>
|
|
1802
|
+
* <span class="juno-list__support">Streaming resolution</span>
|
|
1803
|
+
* </span>
|
|
1804
|
+
* <span class="juno-list__value">1080p</span>
|
|
1805
|
+
* <svg class="juno-icon juno-list__chevron" aria-hidden="true"><use href="…#juno-i-caret-right" /></svg>
|
|
1806
|
+
* </a></li>
|
|
1807
|
+
* </ul>
|
|
1808
|
+
* </section>
|
|
1809
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1810
|
+
|
|
1811
|
+
.juno-list {
|
|
1812
|
+
display: flex;
|
|
1813
|
+
flex-direction: column;
|
|
1814
|
+
gap: var(--juno-space-8);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
.juno-list + .juno-list { margin-block-start: var(--juno-space-20); }
|
|
1818
|
+
|
|
1819
|
+
.juno-list__header {
|
|
1820
|
+
margin: 0;
|
|
1821
|
+
padding-inline: var(--juno-space-16);
|
|
1822
|
+
font-family: var(--juno-font-family-sans);
|
|
1823
|
+
font-size: var(--juno-font-size-11);
|
|
1824
|
+
font-weight: var(--juno-font-weight-bold);
|
|
1825
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
1826
|
+
text-transform: uppercase;
|
|
1827
|
+
color: var(--juno-label);
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.juno-list__group {
|
|
1831
|
+
margin: 0;
|
|
1832
|
+
padding: 0;
|
|
1833
|
+
list-style: none;
|
|
1834
|
+
background: var(--juno-s1);
|
|
1835
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
1836
|
+
border-radius: var(--juno-radius-8);
|
|
1837
|
+
overflow: hidden;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
.juno-list__row {
|
|
1841
|
+
display: flex;
|
|
1842
|
+
align-items: center;
|
|
1843
|
+
gap: var(--juno-space-12);
|
|
1844
|
+
inline-size: 100%;
|
|
1845
|
+
min-block-size: var(--juno-size-tap-comfortable);
|
|
1846
|
+
padding: var(--juno-space-8) var(--juno-space-16);
|
|
1847
|
+
background: transparent;
|
|
1848
|
+
border: none;
|
|
1849
|
+
font-family: var(--juno-font-family-sans);
|
|
1850
|
+
font-size: var(--juno-font-size-13);
|
|
1851
|
+
text-align: start;
|
|
1852
|
+
text-decoration: none;
|
|
1853
|
+
color: var(--juno-data);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
/* hairline between rows only — works for <li>-wrapped and bare rows */
|
|
1857
|
+
.juno-list__group > li + li > .juno-list__row,
|
|
1858
|
+
.juno-list__group > .juno-list__row + .juno-list__row {
|
|
1859
|
+
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
/* feedback only where a row actually goes somewhere */
|
|
1863
|
+
a.juno-list__row,
|
|
1864
|
+
button.juno-list__row { cursor: pointer; }
|
|
1865
|
+
|
|
1866
|
+
a.juno-list__row:hover,
|
|
1867
|
+
button.juno-list__row:hover,
|
|
1868
|
+
a.juno-list__row:focus-visible,
|
|
1869
|
+
button.juno-list__row:focus-visible { background: var(--juno-s2); }
|
|
1870
|
+
|
|
1871
|
+
.juno-list__row:focus-visible {
|
|
1872
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
1873
|
+
outline-offset: calc(-1 * var(--juno-space-2));
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
button.juno-list__row:disabled {
|
|
1877
|
+
opacity: var(--juno-opacity-disabled);
|
|
1878
|
+
cursor: not-allowed;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
.juno-list__icon {
|
|
1882
|
+
flex-shrink: 0;
|
|
1883
|
+
color: var(--juno-label);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
.juno-list__main {
|
|
1887
|
+
flex: 1 1 auto;
|
|
1888
|
+
min-inline-size: 0;
|
|
1889
|
+
display: flex;
|
|
1890
|
+
flex-direction: column;
|
|
1891
|
+
gap: var(--juno-space-2);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.juno-list__label {
|
|
1895
|
+
overflow: hidden;
|
|
1896
|
+
text-overflow: ellipsis;
|
|
1897
|
+
white-space: nowrap;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.juno-list__support {
|
|
1901
|
+
font-size: var(--juno-font-size-11);
|
|
1902
|
+
color: var(--juno-label);
|
|
1903
|
+
overflow: hidden;
|
|
1904
|
+
text-overflow: ellipsis;
|
|
1905
|
+
white-space: nowrap;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
.juno-list__value {
|
|
1909
|
+
flex-shrink: 0;
|
|
1910
|
+
font-family: var(--juno-font-family-mono);
|
|
1911
|
+
font-size: var(--juno-font-size-12);
|
|
1912
|
+
color: var(--juno-label);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
.juno-list__chevron {
|
|
1916
|
+
flex-shrink: 0;
|
|
1917
|
+
|
|
1918
|
+
--juno-icon-size: var(--juno-space-16);
|
|
1919
|
+
|
|
1920
|
+
color: var(--juno-muted);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
1924
|
+
* Component — Loaders (Arc · Beacon · Linear bar)
|
|
1925
|
+
*
|
|
1926
|
+
* Indeterminate variants loop on their own (CSS only). Determinate
|
|
1927
|
+
* variants track real progress: set --juno-progress (0–100) on the
|
|
1928
|
+
* element, inline or from JS:
|
|
1929
|
+
* el.style.setProperty('--juno-progress', 63);
|
|
1930
|
+
* Color comes from --juno-role (default = active).
|
|
1931
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
1932
|
+
|
|
1933
|
+
@keyframes juno-arc-rot { to { transform: rotate(360deg); } }
|
|
1934
|
+
|
|
1935
|
+
@keyframes juno-beacon-pulse { 0% { transform: scale(0.28); opacity: 0.85; } 100% { transform: scale(1); opacity: 0; } }
|
|
1936
|
+
|
|
1937
|
+
@keyframes juno-bar-slide { 0% { left: -42%; } 100% { left: 100%; } }
|
|
1938
|
+
|
|
1939
|
+
/* ── ARC — circular ring ────────────────────────────────────────────── */
|
|
1940
|
+
.juno-arc {
|
|
1941
|
+
--juno-role: var(--juno-active);
|
|
1942
|
+
--juno-arc-size: 76px;
|
|
1943
|
+
--juno-arc-width: 4px;
|
|
1944
|
+
|
|
1945
|
+
position: relative;
|
|
1946
|
+
width: var(--juno-arc-size);
|
|
1947
|
+
height: var(--juno-arc-size);
|
|
1948
|
+
border-radius: 50%;
|
|
1949
|
+
|
|
1950
|
+
/* determinate sweep: filled portion = progress */
|
|
1951
|
+
background: conic-gradient(
|
|
1952
|
+
var(--juno-role) calc(var(--juno-progress, 0) * 1%),
|
|
1953
|
+
var(--juno-s3) 0
|
|
1954
|
+
);
|
|
1955
|
+
|
|
1956
|
+
/* punch out the centre to leave a ring */
|
|
1957
|
+
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-arc-width)), #000 calc(100% - var(--juno-arc-width)));
|
|
1958
|
+
mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-arc-width)), #000 calc(100% - var(--juno-arc-width)));
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
.juno-arc--indeterminate {
|
|
1962
|
+
background: conic-gradient(var(--juno-role) 0 70%, #0000 0);
|
|
1963
|
+
animation: juno-arc-rot 1.1s steps(12) infinite; /* mechanical, 12-step sweep */
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
/* centred readout for determinate arcs — place a span inside */
|
|
1967
|
+
.juno-arc__label {
|
|
1968
|
+
position: absolute;
|
|
1969
|
+
inset: 0;
|
|
1970
|
+
display: flex;
|
|
1971
|
+
align-items: center;
|
|
1972
|
+
justify-content: center;
|
|
1973
|
+
font-family: var(--juno-font-family-mono);
|
|
1974
|
+
font-weight: var(--juno-font-weight-bold);
|
|
1975
|
+
font-size: var(--juno-font-size-13);
|
|
1976
|
+
color: var(--juno-data);
|
|
1977
|
+
|
|
1978
|
+
/* the mask clips the ring, not this label: keep it on a sibling when
|
|
1979
|
+
a value is shown — see loader.md */
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
/* ── BEACON — radiating pulse ───────────────────────────────────────── */
|
|
1983
|
+
.juno-beacon {
|
|
1984
|
+
--juno-role: var(--juno-active);
|
|
1985
|
+
--juno-beacon-size: 76px;
|
|
1986
|
+
|
|
1987
|
+
position: relative;
|
|
1988
|
+
width: var(--juno-beacon-size);
|
|
1989
|
+
height: var(--juno-beacon-size);
|
|
1990
|
+
display: flex;
|
|
1991
|
+
align-items: center;
|
|
1992
|
+
justify-content: center;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
.juno-beacon__core {
|
|
1996
|
+
width: 24%;
|
|
1997
|
+
height: 24%;
|
|
1998
|
+
border-radius: 50%;
|
|
1999
|
+
background: var(--juno-role);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/* two pulse rings (staggered) */
|
|
2003
|
+
.juno-beacon::before,
|
|
2004
|
+
.juno-beacon::after {
|
|
2005
|
+
content: '';
|
|
2006
|
+
position: absolute;
|
|
2007
|
+
inset: 0;
|
|
2008
|
+
border-radius: 50%;
|
|
2009
|
+
border: 1.5px solid var(--juno-role);
|
|
2010
|
+
animation: juno-beacon-pulse 1.9s ease-out infinite;
|
|
2011
|
+
}
|
|
2012
|
+
.juno-beacon::after { animation-delay: 0.95s; }
|
|
2013
|
+
|
|
2014
|
+
/* determinate: fill grows centre → rim by progress, keeps pulsing + readout */
|
|
2015
|
+
.juno-beacon--determinate { --juno-beacon-fill-stop: calc(44% + 0.56% * var(--juno-progress, 0)); }
|
|
2016
|
+
.juno-beacon--determinate .juno-beacon__core { display: none; }
|
|
2017
|
+
|
|
2018
|
+
.juno-beacon__track {
|
|
2019
|
+
position: absolute;
|
|
2020
|
+
inset: 0;
|
|
2021
|
+
border-radius: 50%;
|
|
2022
|
+
border: 1px solid var(--juno-border);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
.juno-beacon__fill {
|
|
2026
|
+
position: absolute;
|
|
2027
|
+
inset: 0;
|
|
2028
|
+
border-radius: 50%;
|
|
2029
|
+
background: var(--juno-role);
|
|
2030
|
+
-webkit-mask: radial-gradient(circle closest-side, #0000 0 44%, #000 44% var(--juno-beacon-fill-stop), #0000 var(--juno-beacon-fill-stop));
|
|
2031
|
+
mask: radial-gradient(circle closest-side, #0000 0 44%, #000 44% var(--juno-beacon-fill-stop), #0000 var(--juno-beacon-fill-stop));
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
.juno-beacon__hub {
|
|
2035
|
+
position: absolute;
|
|
2036
|
+
left: 50%; top: 50%;
|
|
2037
|
+
transform: translate(-50%, -50%);
|
|
2038
|
+
width: 42%; height: 42%;
|
|
2039
|
+
border-radius: 50%;
|
|
2040
|
+
background: var(--juno-s1);
|
|
2041
|
+
border: 1px solid var(--juno-border);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.juno-beacon__label {
|
|
2045
|
+
position: relative;
|
|
2046
|
+
font-family: var(--juno-font-family-mono);
|
|
2047
|
+
font-weight: var(--juno-font-weight-bold);
|
|
2048
|
+
font-size: var(--juno-font-size-13);
|
|
2049
|
+
color: var(--juno-data);
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/* ── LINEAR BAR ─────────────────────────────────────────────────────── */
|
|
2053
|
+
.juno-bar {
|
|
2054
|
+
--juno-role: var(--juno-active);
|
|
2055
|
+
|
|
2056
|
+
position: relative;
|
|
2057
|
+
width: 100%;
|
|
2058
|
+
height: 6px;
|
|
2059
|
+
border-radius: var(--juno-radius-3);
|
|
2060
|
+
background: var(--juno-s2);
|
|
2061
|
+
overflow: hidden;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
.juno-bar__fill {
|
|
2065
|
+
position: absolute;
|
|
2066
|
+
top: 0;
|
|
2067
|
+
left: 0;
|
|
2068
|
+
height: 100%;
|
|
2069
|
+
border-radius: var(--juno-radius-3);
|
|
2070
|
+
background: var(--juno-role);
|
|
2071
|
+
width: calc(var(--juno-progress, 0) * 1%);
|
|
2072
|
+
transition: width var(--juno-motion-duration-quick) linear;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
/* indeterminate: a 40% segment slides across */
|
|
2076
|
+
.juno-bar--indeterminate .juno-bar__fill {
|
|
2077
|
+
width: 40%;
|
|
2078
|
+
left: -42%;
|
|
2079
|
+
transition: none;
|
|
2080
|
+
animation: juno-bar-slide 1.3s ease-in-out infinite;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2084
|
+
* Component — Menu / Dropdown
|
|
2085
|
+
* A list of immediate actions anchored to its trigger. Section dividers,
|
|
2086
|
+
* keyboard shortcuts, and a destructive item set apart by WARNING. junoui
|
|
2087
|
+
* ships the look; the app owns roving focus. Open/close, light-dismiss, and
|
|
2088
|
+
* ESC come from the native Popover API — the menu lives in the TOP LAYER, so
|
|
2089
|
+
* it is never clipped by an ancestor's overflow and never fights z-index.
|
|
2090
|
+
* Usage:
|
|
2091
|
+
* <button popovertarget="acts" aria-haspopup="menu" aria-expanded="false">ACTIONS ▾</button>
|
|
2092
|
+
* <ul class="juno-menu" id="acts" popover role="menu">
|
|
2093
|
+
* <li><button class="juno-menu__item" role="menuitem"
|
|
2094
|
+
* popovertarget="acts" popovertargetaction="hide">
|
|
2095
|
+
* <span class="juno-menu__icon">⤓</span>Import data
|
|
2096
|
+
* <span class="juno-menu__kbd">⌘O</span>
|
|
2097
|
+
* </button></li>
|
|
2098
|
+
* <li class="juno-menu__sep" role="separator"></li>
|
|
2099
|
+
* <li><button class="juno-menu__item juno--warning" role="menuitem"
|
|
2100
|
+
* popovertarget="acts" popovertargetaction="hide">
|
|
2101
|
+
* <span class="juno-menu__icon">⨯</span>Delete project
|
|
2102
|
+
* </button></li>
|
|
2103
|
+
* </ul>
|
|
2104
|
+
* (Each item carries popovertargetaction="hide" so activating it closes the
|
|
2105
|
+
* menu with no JS. The invoker is the menu's implicit anchor.)
|
|
2106
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2107
|
+
|
|
2108
|
+
.juno-menu {
|
|
2109
|
+
position: fixed;
|
|
2110
|
+
margin: 0;
|
|
2111
|
+
inset: auto;
|
|
2112
|
+
position-area: block-end span-inline-start;
|
|
2113
|
+
position-try-fallbacks: flip-block, flip-inline;
|
|
2114
|
+
margin-block-start: var(--juno-space-8);
|
|
2115
|
+
min-inline-size: 256px;
|
|
2116
|
+
background: var(--juno-s2);
|
|
2117
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
2118
|
+
border-radius: var(--juno-radius-5);
|
|
2119
|
+
box-shadow: var(--juno-shadow-2);
|
|
2120
|
+
padding: var(--juno-space-4);
|
|
2121
|
+
list-style: none;
|
|
2122
|
+
opacity: 0;
|
|
2123
|
+
transform: translateY(calc(-1 * var(--juno-space-4)));
|
|
2124
|
+
transition:
|
|
2125
|
+
opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
2126
|
+
transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
2127
|
+
overlay var(--juno-motion-duration-quick) allow-discrete,
|
|
2128
|
+
display var(--juno-motion-duration-quick) allow-discrete;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
.juno-menu:popover-open {
|
|
2132
|
+
opacity: 1;
|
|
2133
|
+
transform: none;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
@starting-style {
|
|
2137
|
+
.juno-menu:popover-open {
|
|
2138
|
+
opacity: 0;
|
|
2139
|
+
transform: translateY(calc(-1 * var(--juno-space-4)));
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
.juno-menu__item {
|
|
2144
|
+
--juno-role: var(--juno-data);
|
|
2145
|
+
|
|
2146
|
+
display: flex;
|
|
2147
|
+
align-items: center;
|
|
2148
|
+
gap: var(--juno-gap-control);
|
|
2149
|
+
inline-size: 100%;
|
|
2150
|
+
padding: var(--juno-space-8) var(--juno-space-10);
|
|
2151
|
+
background: transparent;
|
|
2152
|
+
border: none;
|
|
2153
|
+
border-radius: var(--juno-radius-4);
|
|
2154
|
+
font-family: var(--juno-font-family-sans);
|
|
2155
|
+
font-size: var(--juno-font-size-13);
|
|
2156
|
+
color: var(--juno-role);
|
|
2157
|
+
text-align: start;
|
|
2158
|
+
text-decoration: none; /* items may be <a> (nav menus), not only <button> */
|
|
2159
|
+
cursor: pointer;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
.juno-menu__item:hover,
|
|
2163
|
+
.juno-menu__item:focus-visible { background: var(--juno-s3); }
|
|
2164
|
+
|
|
2165
|
+
/* destructive item — .juno--warning sets --juno-role to warning */
|
|
2166
|
+
.juno-menu__item.juno--warning:hover,
|
|
2167
|
+
.juno-menu__item.juno--warning:focus-visible {
|
|
2168
|
+
background: color-mix(in srgb, var(--juno-role) 14%, transparent);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
.juno-menu__item:disabled,
|
|
2172
|
+
.juno-menu__item[aria-disabled='true'] { opacity: var(--juno-opacity-disabled); cursor: not-allowed; }
|
|
2173
|
+
|
|
2174
|
+
.juno-menu__icon {
|
|
2175
|
+
flex-shrink: 0;
|
|
2176
|
+
inline-size: var(--juno-space-16);
|
|
2177
|
+
font-family: var(--juno-font-family-mono);
|
|
2178
|
+
font-size: var(--juno-font-size-11);
|
|
2179
|
+
color: var(--juno-label);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
.juno-menu__item.juno--warning .juno-menu__icon { color: var(--juno-role); }
|
|
2183
|
+
|
|
2184
|
+
.juno-menu__kbd {
|
|
2185
|
+
margin-inline-start: auto;
|
|
2186
|
+
font-family: var(--juno-font-family-mono);
|
|
2187
|
+
font-size: var(--juno-font-size-10);
|
|
2188
|
+
color: var(--juno-muted);
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
.juno-menu__sep {
|
|
2192
|
+
block-size: var(--juno-border-width-1);
|
|
2193
|
+
background: var(--juno-border);
|
|
2194
|
+
margin-block: var(--juno-space-4);
|
|
2195
|
+
list-style: none;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2199
|
+
* Component — Modal / Dialog
|
|
2200
|
+
* Style a native <dialog>. The app owns open/close (dialog.showModal() /
|
|
2201
|
+
* .close()) and focus-trapping; junoui ships the look + entry motion.
|
|
2202
|
+
* Centered, scrim-backed. Reserved for decisions that must block the deck.
|
|
2203
|
+
* Usage:
|
|
2204
|
+
* <dialog class="juno-modal"> (or .juno-modal.juno--warning)
|
|
2205
|
+
* <div class="juno-modal__stripe"></div>
|
|
2206
|
+
* <div class="juno-modal__head">
|
|
2207
|
+
* <span class="juno-modal__tag">CONFIGURATION</span>
|
|
2208
|
+
* <button class="juno-modal__close" aria-label="Close">✕</button>
|
|
2209
|
+
* </div>
|
|
2210
|
+
* <div class="juno-modal__body">
|
|
2211
|
+
* <h2 class="juno-modal__title">Apply configuration change?</h2>
|
|
2212
|
+
* <p class="juno-modal__text">…</p>
|
|
2213
|
+
* <div class="juno-modal__foot">
|
|
2214
|
+
* <button class="juno-btn juno-btn--ghost">CANCEL</button>
|
|
2215
|
+
* <button class="juno-btn">APPLY</button>
|
|
2216
|
+
* </div>
|
|
2217
|
+
* </div>
|
|
2218
|
+
* </dialog>
|
|
2219
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2220
|
+
|
|
2221
|
+
.juno-modal {
|
|
2222
|
+
--juno-role: var(--juno-active);
|
|
2223
|
+
|
|
2224
|
+
inline-size: 440px;
|
|
2225
|
+
max-inline-size: 100%;
|
|
2226
|
+
padding: 0;
|
|
2227
|
+
background: var(--juno-s1);
|
|
2228
|
+
color: var(--juno-data);
|
|
2229
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
2230
|
+
border-radius: var(--juno-radius-8);
|
|
2231
|
+
overflow: hidden;
|
|
2232
|
+
box-shadow: var(--juno-shadow-3);
|
|
2233
|
+
|
|
2234
|
+
/* enter: scale + rise, decelerating (deliberate — it blocks the deck) */
|
|
2235
|
+
opacity: 0;
|
|
2236
|
+
transform: translateY(var(--juno-space-12)) scale(0.97);
|
|
2237
|
+
transition:
|
|
2238
|
+
opacity var(--juno-motion-duration-deliberate) var(--juno-motion-ease-decel),
|
|
2239
|
+
transform var(--juno-motion-duration-deliberate) var(--juno-motion-ease-decel),
|
|
2240
|
+
overlay var(--juno-motion-duration-deliberate) allow-discrete,
|
|
2241
|
+
display var(--juno-motion-duration-deliberate) allow-discrete;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
.juno-modal[open] {
|
|
2245
|
+
opacity: 1;
|
|
2246
|
+
transform: translateY(0) scale(1);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
@starting-style {
|
|
2250
|
+
.juno-modal[open] {
|
|
2251
|
+
opacity: 0;
|
|
2252
|
+
transform: translateY(var(--juno-space-12)) scale(0.97);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
/* scrim — one at a time; dark wash + faint blur */
|
|
2257
|
+
.juno-modal::backdrop {
|
|
2258
|
+
background: rgb(0 0 0);
|
|
2259
|
+
opacity: var(--juno-opacity-scrim);
|
|
2260
|
+
backdrop-filter: blur(1.5px);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
/* status accent stripe across the top (role-colored) */
|
|
2264
|
+
.juno-modal__stripe {
|
|
2265
|
+
block-size: var(--juno-border-width-3);
|
|
2266
|
+
background: var(--juno-role);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
.juno-modal__head {
|
|
2270
|
+
display: flex;
|
|
2271
|
+
align-items: center;
|
|
2272
|
+
gap: var(--juno-space-8);
|
|
2273
|
+
padding: var(--juno-space-16) var(--juno-pad-surface-inline) 0;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
.juno-modal__tag {
|
|
2277
|
+
font-family: var(--juno-font-family-mono);
|
|
2278
|
+
font-size: var(--juno-font-size-10);
|
|
2279
|
+
letter-spacing: var(--juno-font-tracking-wider);
|
|
2280
|
+
text-transform: uppercase;
|
|
2281
|
+
color: var(--juno-role);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
.juno-modal__close {
|
|
2285
|
+
margin-inline-start: auto;
|
|
2286
|
+
inline-size: var(--juno-size-tap-min);
|
|
2287
|
+
block-size: var(--juno-size-tap-min);
|
|
2288
|
+
display: inline-flex;
|
|
2289
|
+
align-items: center;
|
|
2290
|
+
justify-content: center;
|
|
2291
|
+
background: transparent;
|
|
2292
|
+
border: var(--juno-border-width-1) solid transparent;
|
|
2293
|
+
border-radius: var(--juno-radius-4);
|
|
2294
|
+
color: var(--juno-label);
|
|
2295
|
+
cursor: pointer;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
.juno-modal__close:hover { background: var(--juno-s2); color: var(--juno-data); }
|
|
2299
|
+
|
|
2300
|
+
.juno-modal__body { padding: var(--juno-space-12) var(--juno-pad-surface-inline) var(--juno-pad-surface-inline); }
|
|
2301
|
+
|
|
2302
|
+
.juno-modal__title {
|
|
2303
|
+
font-family: var(--juno-font-family-sans);
|
|
2304
|
+
font-size: var(--juno-font-size-20);
|
|
2305
|
+
font-weight: var(--juno-font-weight-light);
|
|
2306
|
+
line-height: var(--juno-font-lineHeight-snug);
|
|
2307
|
+
color: var(--juno-data);
|
|
2308
|
+
margin-block-end: var(--juno-space-12);
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
.juno-modal__text {
|
|
2312
|
+
font-size: var(--juno-font-size-13);
|
|
2313
|
+
line-height: var(--juno-font-lineHeight-loose);
|
|
2314
|
+
color: var(--juno-label);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
.juno-modal__foot {
|
|
2318
|
+
display: flex;
|
|
2319
|
+
gap: var(--juno-gap-control);
|
|
2320
|
+
justify-content: flex-end;
|
|
2321
|
+
margin-block-start: var(--juno-space-24);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
/* ── narrow viewports — the centered dialog becomes a bottom sheet ──────
|
|
2325
|
+
Phones get the native-feeling pattern: full-width, pinned to the bottom
|
|
2326
|
+
edge, top corners rounded, sliding up. Drawers opt out (:not) — they
|
|
2327
|
+
already pin themselves. Scoped to the viewport (not a container): a
|
|
2328
|
+
modal always lives in the top layer, so the screen IS its container. */
|
|
2329
|
+
@media (width <= 639.98px) {
|
|
2330
|
+
.juno-modal:not(.juno-drawer) {
|
|
2331
|
+
inline-size: 100%;
|
|
2332
|
+
max-inline-size: 100%;
|
|
2333
|
+
max-block-size: 85dvh;
|
|
2334
|
+
margin-block: auto 0;
|
|
2335
|
+
margin-inline: 0;
|
|
2336
|
+
border: none;
|
|
2337
|
+
border-block-start: var(--juno-border-width-1) solid var(--juno-border);
|
|
2338
|
+
border-start-start-radius: var(--juno-radius-8);
|
|
2339
|
+
border-start-end-radius: var(--juno-radius-8);
|
|
2340
|
+
border-end-start-radius: 0;
|
|
2341
|
+
border-end-end-radius: 0;
|
|
2342
|
+
transform: translateY(100%);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
.juno-modal:not(.juno-drawer)[open] { transform: translateY(0); }
|
|
2346
|
+
|
|
2347
|
+
@starting-style {
|
|
2348
|
+
.juno-modal:not(.juno-drawer)[open] { transform: translateY(100%); }
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/* keep the foot's actions above the home indicator */
|
|
2352
|
+
.juno-modal:not(.juno-drawer) .juno-modal__body {
|
|
2353
|
+
padding-block-end: calc(var(--juno-pad-surface-inline) + env(safe-area-inset-bottom, 0));
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
/* full-width actions read better on a sheet */
|
|
2357
|
+
.juno-modal:not(.juno-drawer) .juno-modal__foot > .juno-btn { flex: 1; }
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2361
|
+
* Component — Navbar (stack-navigation top bar)
|
|
2362
|
+
* The header of a pushed view on phones: a back control on the start
|
|
2363
|
+
* edge — always, so any screen can unwind — a centered truncating
|
|
2364
|
+
* title, and trailing actions. junoui ships the look; the app owns the
|
|
2365
|
+
* navigation stack (what "back" does).
|
|
2366
|
+
*
|
|
2367
|
+
* Sticky top, safe-area padded. Pair with the dock or pillbar for the
|
|
2368
|
+
* tab + stack recipe: tabs switch sections, the navbar backs out of
|
|
2369
|
+
* drilled-in views (see docs/layout.md#app-shell).
|
|
2370
|
+
* Usage:
|
|
2371
|
+
* <header class="juno-navbar">
|
|
2372
|
+
* <a class="juno-navbar__back" href="…">
|
|
2373
|
+
* <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-caret-left" /></svg>
|
|
2374
|
+
* <span class="juno-navbar__back-label">Library</span>
|
|
2375
|
+
* </a>
|
|
2376
|
+
* <h1 class="juno-navbar__title">Album</h1>
|
|
2377
|
+
* <div class="juno-navbar__actions">
|
|
2378
|
+
* <button class="juno-btn juno-btn--sm juno-btn--ghost">EDIT</button>
|
|
2379
|
+
* </div>
|
|
2380
|
+
* </header>
|
|
2381
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2382
|
+
|
|
2383
|
+
.juno-navbar {
|
|
2384
|
+
position: sticky;
|
|
2385
|
+
inset-block-start: 0;
|
|
2386
|
+
z-index: var(--juno-z-raised);
|
|
2387
|
+
display: grid;
|
|
2388
|
+
|
|
2389
|
+
/* sides never shrink below their (already-truncated) content, so a long
|
|
2390
|
+
title ellipsizes instead of sliding under the back control; with a short
|
|
2391
|
+
title the equal 1fr sides keep it optically centered */
|
|
2392
|
+
grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr);
|
|
2393
|
+
align-items: center;
|
|
2394
|
+
gap: var(--juno-space-8);
|
|
2395
|
+
padding-inline: var(--juno-space-8);
|
|
2396
|
+
padding-block-start: env(safe-area-inset-top, 0);
|
|
2397
|
+
min-block-size: var(--juno-size-tap-comfortable);
|
|
2398
|
+
background: var(--juno-s1);
|
|
2399
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
.juno-navbar__back {
|
|
2403
|
+
appearance: none;
|
|
2404
|
+
margin: 0;
|
|
2405
|
+
border: none;
|
|
2406
|
+
background: transparent;
|
|
2407
|
+
justify-self: start;
|
|
2408
|
+
display: inline-flex;
|
|
2409
|
+
align-items: center;
|
|
2410
|
+
gap: var(--juno-space-4);
|
|
2411
|
+
min-block-size: var(--juno-size-tap-comfortable);
|
|
2412
|
+
min-inline-size: var(--juno-size-tap-comfortable);
|
|
2413
|
+
padding-inline: var(--juno-space-4);
|
|
2414
|
+
font-family: var(--juno-font-family-sans);
|
|
2415
|
+
font-size: var(--juno-font-size-11);
|
|
2416
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
2417
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
2418
|
+
text-transform: uppercase;
|
|
2419
|
+
text-decoration: none;
|
|
2420
|
+
color: var(--juno-active);
|
|
2421
|
+
cursor: pointer;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
/* RTL: the caret glyph must point "back" in either direction */
|
|
2425
|
+
[dir='rtl'] .juno-navbar__back .juno-icon { transform: scaleX(-1); }
|
|
2426
|
+
|
|
2427
|
+
.juno-navbar__back:hover { color: var(--juno-data); }
|
|
2428
|
+
|
|
2429
|
+
.juno-navbar__back-label {
|
|
2430
|
+
max-inline-size: 14ch;
|
|
2431
|
+
overflow: hidden;
|
|
2432
|
+
text-overflow: ellipsis;
|
|
2433
|
+
white-space: nowrap;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
.juno-navbar__title {
|
|
2437
|
+
grid-column: 2;
|
|
2438
|
+
margin: 0;
|
|
2439
|
+
min-inline-size: 0;
|
|
2440
|
+
max-inline-size: 100%;
|
|
2441
|
+
font-family: var(--juno-font-family-sans);
|
|
2442
|
+
font-size: var(--juno-font-size-14);
|
|
2443
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
2444
|
+
color: var(--juno-data);
|
|
2445
|
+
overflow: hidden;
|
|
2446
|
+
text-overflow: ellipsis;
|
|
2447
|
+
white-space: nowrap;
|
|
2448
|
+
text-align: center;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
.juno-navbar__actions {
|
|
2452
|
+
grid-column: 3;
|
|
2453
|
+
justify-self: end;
|
|
2454
|
+
display: inline-flex;
|
|
2455
|
+
align-items: center;
|
|
2456
|
+
gap: var(--juno-space-4);
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
.juno-navbar__back:focus-visible {
|
|
2460
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
2461
|
+
outline-offset: calc(-1 * var(--juno-space-2));
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2465
|
+
* Component — Pagination · page navigation
|
|
2466
|
+
* A row of page controls: prev / next plus numbered pages, with the
|
|
2467
|
+
* current page marked aria-current="page". Built on a real <nav>; the
|
|
2468
|
+
* app owns which page is active and renders the list. Zero JS.
|
|
2469
|
+
* Usage:
|
|
2470
|
+
* <nav class="juno-pagination" aria-label="Pagination">
|
|
2471
|
+
* <button class="juno-pagination__item" aria-label="Previous" disabled>‹</button>
|
|
2472
|
+
* <button class="juno-pagination__item" aria-current="page">1</button>
|
|
2473
|
+
* <button class="juno-pagination__item">2</button>
|
|
2474
|
+
* <span class="juno-pagination__gap">…</span>
|
|
2475
|
+
* <button class="juno-pagination__item">9</button>
|
|
2476
|
+
* <button class="juno-pagination__item" aria-label="Next">›</button>
|
|
2477
|
+
* </nav>
|
|
2478
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2479
|
+
|
|
2480
|
+
.juno-pagination {
|
|
2481
|
+
display: inline-flex;
|
|
2482
|
+
flex-wrap: wrap;
|
|
2483
|
+
align-items: center;
|
|
2484
|
+
gap: var(--juno-space-4);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
.juno-pagination__item {
|
|
2488
|
+
display: inline-grid;
|
|
2489
|
+
place-content: center;
|
|
2490
|
+
min-inline-size: var(--juno-size-tap-min, var(--juno-space-32));
|
|
2491
|
+
block-size: var(--juno-space-32);
|
|
2492
|
+
padding-inline: var(--juno-space-8);
|
|
2493
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
2494
|
+
border-radius: var(--juno-radius-4);
|
|
2495
|
+
background: var(--juno-control-surface);
|
|
2496
|
+
color: var(--juno-data);
|
|
2497
|
+
font-family: var(--juno-font-family-mono);
|
|
2498
|
+
font-size: var(--juno-font-size-13);
|
|
2499
|
+
cursor: pointer;
|
|
2500
|
+
transition:
|
|
2501
|
+
border-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
2502
|
+
background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
.juno-pagination__item:hover:not(:disabled, [aria-current='page']) {
|
|
2506
|
+
border-color: var(--juno-active);
|
|
2507
|
+
color: var(--juno-active);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
.juno-pagination__item:focus-visible {
|
|
2511
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
2512
|
+
outline-offset: var(--juno-space-2);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
/* current page — filled active accent, not actionable */
|
|
2516
|
+
.juno-pagination__item[aria-current='page'] {
|
|
2517
|
+
border-color: var(--juno-active);
|
|
2518
|
+
background: color-mix(in srgb, var(--juno-active) 18%, var(--juno-control-surface));
|
|
2519
|
+
color: var(--juno-active);
|
|
2520
|
+
cursor: default;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
.juno-pagination__item:disabled {
|
|
2524
|
+
opacity: var(--juno-opacity-disabled);
|
|
2525
|
+
cursor: not-allowed;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
/* the … gap between page ranges */
|
|
2529
|
+
.juno-pagination__gap {
|
|
2530
|
+
display: inline-grid;
|
|
2531
|
+
place-content: center;
|
|
2532
|
+
min-inline-size: var(--juno-space-20);
|
|
2533
|
+
color: var(--juno-muted);
|
|
2534
|
+
font-family: var(--juno-font-family-mono);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2538
|
+
* Component — Pillbar (floating pill toolbar / tab bar)
|
|
2539
|
+
* A floating rounded bar hovering above the content's bottom edge —
|
|
2540
|
+
* the iOS-style alternative to the full-width dock. Holds 2–5 icon
|
|
2541
|
+
* actions or destinations; the active one styles off aria-current
|
|
2542
|
+
* (nav links) or aria-pressed (toggle buttons), zero JS.
|
|
2543
|
+
*
|
|
2544
|
+
* Sticky (not fixed): put it last inside the scrolling column and it
|
|
2545
|
+
* floats over the content while staying in flow. Clears the home
|
|
2546
|
+
* indicator via safe-area-inset.
|
|
2547
|
+
* Usage:
|
|
2548
|
+
* <nav class="juno-pillbar" aria-label="Primary">
|
|
2549
|
+
* <a class="juno-pillbar__item" href="…" aria-current="page" aria-label="Library">
|
|
2550
|
+
* <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-squares-four" /></svg>
|
|
2551
|
+
* <span class="juno-pillbar__label">Library</span>
|
|
2552
|
+
* </a>
|
|
2553
|
+
* <span class="juno-pillbar__sep"></span>
|
|
2554
|
+
* <button class="juno-pillbar__item" aria-label="Sync">
|
|
2555
|
+
* <svg class="juno-icon" aria-hidden="true"><use href="…#juno-i-arrows-clockwise" /></svg>
|
|
2556
|
+
* </button>
|
|
2557
|
+
* </nav>
|
|
2558
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2559
|
+
|
|
2560
|
+
.juno-pillbar {
|
|
2561
|
+
--juno-role: var(--juno-active);
|
|
2562
|
+
|
|
2563
|
+
position: sticky;
|
|
2564
|
+
inset-block-end: calc(var(--juno-space-16) + env(safe-area-inset-bottom, 0px));
|
|
2565
|
+
z-index: var(--juno-z-raised);
|
|
2566
|
+
margin-inline: auto;
|
|
2567
|
+
margin-block-end: var(--juno-space-16);
|
|
2568
|
+
inline-size: fit-content;
|
|
2569
|
+
max-inline-size: calc(100% - 2 * var(--juno-space-16));
|
|
2570
|
+
display: flex;
|
|
2571
|
+
align-items: center;
|
|
2572
|
+
gap: var(--juno-space-2);
|
|
2573
|
+
padding: var(--juno-space-4);
|
|
2574
|
+
background: color-mix(in srgb, var(--juno-s1) 88%, transparent);
|
|
2575
|
+
backdrop-filter: blur(12px);
|
|
2576
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
2577
|
+
border-radius: 999px;
|
|
2578
|
+
box-shadow: var(--juno-shadow-2);
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
.juno-pillbar__item {
|
|
2582
|
+
appearance: none;
|
|
2583
|
+
margin: 0;
|
|
2584
|
+
border: none;
|
|
2585
|
+
min-inline-size: var(--juno-size-tap-comfortable);
|
|
2586
|
+
min-block-size: var(--juno-size-tap-comfortable);
|
|
2587
|
+
display: inline-flex;
|
|
2588
|
+
align-items: center;
|
|
2589
|
+
justify-content: center;
|
|
2590
|
+
gap: var(--juno-space-4);
|
|
2591
|
+
padding-inline: var(--juno-space-10);
|
|
2592
|
+
background: transparent;
|
|
2593
|
+
border-radius: 999px;
|
|
2594
|
+
font-family: var(--juno-font-family-sans);
|
|
2595
|
+
font-size: var(--juno-font-size-10);
|
|
2596
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
2597
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
2598
|
+
text-transform: uppercase;
|
|
2599
|
+
text-decoration: none;
|
|
2600
|
+
color: var(--juno-label);
|
|
2601
|
+
cursor: pointer;
|
|
2602
|
+
transition:
|
|
2603
|
+
color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
2604
|
+
background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
.juno-pillbar__label {
|
|
2608
|
+
max-inline-size: 12ch;
|
|
2609
|
+
overflow: hidden;
|
|
2610
|
+
text-overflow: ellipsis;
|
|
2611
|
+
white-space: nowrap;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
.juno-pillbar__item:hover { color: var(--juno-data); }
|
|
2615
|
+
|
|
2616
|
+
/* active = attribute, not class: aria-current (links) / aria-pressed (buttons) */
|
|
2617
|
+
.juno-pillbar__item[aria-current],
|
|
2618
|
+
.juno-pillbar__item[aria-pressed='true'] {
|
|
2619
|
+
background: var(--juno-s3);
|
|
2620
|
+
color: var(--juno-role);
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
.juno-pillbar__item:focus-visible {
|
|
2624
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
2625
|
+
outline-offset: calc(-1 * var(--juno-space-2));
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
.juno-pillbar__item:disabled {
|
|
2629
|
+
opacity: var(--juno-opacity-disabled);
|
|
2630
|
+
cursor: not-allowed;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
.juno-pillbar__sep {
|
|
2634
|
+
align-self: center;
|
|
2635
|
+
inline-size: var(--juno-border-width-1);
|
|
2636
|
+
block-size: var(--juno-space-20);
|
|
2637
|
+
background: var(--juno-border);
|
|
2638
|
+
flex-shrink: 0;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
/* reduced-transparency preference → solid surface, no blur */
|
|
2642
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
2643
|
+
.juno-pillbar {
|
|
2644
|
+
background: var(--juno-s1);
|
|
2645
|
+
backdrop-filter: none;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2650
|
+
* Component — Popover
|
|
2651
|
+
* Anchored, click-triggered surface for rich, interactive content (filters,
|
|
2652
|
+
* quick-edits, detail cards). Unlike a tooltip it holds focusable controls
|
|
2653
|
+
* and persists until dismissed.
|
|
2654
|
+
*
|
|
2655
|
+
* Built on the native Popover API: the panel lives in the browser TOP LAYER,
|
|
2656
|
+
* so it is never clipped by an ancestor's overflow/transform and never fights
|
|
2657
|
+
* z-index. A `popovertarget` button is its implicit anchor; CSS anchor
|
|
2658
|
+
* positioning pins it below, inline-end aligned. Open/close, light-dismiss,
|
|
2659
|
+
* and ESC all come from the platform — zero JS.
|
|
2660
|
+
* Usage:
|
|
2661
|
+
* <button popovertarget="pf" aria-expanded="false">RANGE FILTER ▾</button>
|
|
2662
|
+
* <div class="juno-popover" id="pf" popover role="dialog">
|
|
2663
|
+
* …controls…
|
|
2664
|
+
* <span class="juno-popover__arrow"></span>
|
|
2665
|
+
* </div>
|
|
2666
|
+
* (Older engines without anchor positioning fall back to the app setting
|
|
2667
|
+
* inset on the `toggle` event — junoui ships the surface; the app may pin it.)
|
|
2668
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2669
|
+
|
|
2670
|
+
/* legacy grouping wrapper — kept for layout; positioning no longer needs it */
|
|
2671
|
+
.juno-popover-anchor { position: relative; }
|
|
2672
|
+
|
|
2673
|
+
.juno-popover {
|
|
2674
|
+
position: fixed;
|
|
2675
|
+
margin: 0;
|
|
2676
|
+
inset: auto;
|
|
2677
|
+
position-area: block-end span-inline-start;
|
|
2678
|
+
position-try-fallbacks: flip-block, flip-inline;
|
|
2679
|
+
margin-block-start: var(--juno-space-12);
|
|
2680
|
+
inline-size: 280px;
|
|
2681
|
+
max-inline-size: calc(100vw - var(--juno-space-24));
|
|
2682
|
+
background: var(--juno-s2);
|
|
2683
|
+
color: var(--juno-data);
|
|
2684
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
2685
|
+
border-radius: var(--juno-radius-5);
|
|
2686
|
+
box-shadow: var(--juno-shadow-2);
|
|
2687
|
+
padding: var(--juno-pad-surface-block) var(--juno-pad-surface-inline);
|
|
2688
|
+
opacity: 0;
|
|
2689
|
+
transform: translateY(calc(-1 * var(--juno-space-4)));
|
|
2690
|
+
transition:
|
|
2691
|
+
opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
2692
|
+
transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
2693
|
+
overlay var(--juno-motion-duration-quick) allow-discrete,
|
|
2694
|
+
display var(--juno-motion-duration-quick) allow-discrete;
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
.juno-popover:popover-open {
|
|
2698
|
+
opacity: 1;
|
|
2699
|
+
transform: none;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
@starting-style {
|
|
2703
|
+
.juno-popover:popover-open {
|
|
2704
|
+
opacity: 0;
|
|
2705
|
+
transform: translateY(calc(-1 * var(--juno-space-4)));
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
/* arrow back to the anchor (top-end corner) */
|
|
2710
|
+
.juno-popover__arrow {
|
|
2711
|
+
position: absolute;
|
|
2712
|
+
inset-block-start: calc(-1 * var(--juno-space-4) - var(--juno-border-width-1));
|
|
2713
|
+
inset-inline-end: var(--juno-space-24);
|
|
2714
|
+
inline-size: var(--juno-space-8);
|
|
2715
|
+
block-size: var(--juno-space-8);
|
|
2716
|
+
background: var(--juno-s2);
|
|
2717
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
2718
|
+
border-block-end: none;
|
|
2719
|
+
border-inline-end: none;
|
|
2720
|
+
transform: rotate(45deg);
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2724
|
+
* Component — Rail (app-shell side navigation)
|
|
2725
|
+
* A collapsible icon-and-label nav column: the left edge of the standard
|
|
2726
|
+
* junoui app shell (see docs/layout.md#app-shell). Zero JS here — the
|
|
2727
|
+
* app toggles .juno-rail--collapsed and sets aria-current="page"; the
|
|
2728
|
+
* active item is styled off the attribute, not a class.
|
|
2729
|
+
* Usage:
|
|
2730
|
+
* <nav class="juno-rail" aria-label="Primary">
|
|
2731
|
+
* <div class="juno-rail__brand">JUNO</div>
|
|
2732
|
+
* <a class="juno-rail__item" href="…" aria-current="page">
|
|
2733
|
+
* <svg class="juno-icon"><use href="…#juno-i-squares-four" /></svg>
|
|
2734
|
+
* <span class="juno-rail__label">Library</span>
|
|
2735
|
+
* </a>
|
|
2736
|
+
* </nav>
|
|
2737
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2738
|
+
|
|
2739
|
+
.juno-rail {
|
|
2740
|
+
--juno-role: var(--juno-active);
|
|
2741
|
+
--juno-rail-width: 180px;
|
|
2742
|
+
|
|
2743
|
+
display: flex;
|
|
2744
|
+
flex-direction: column;
|
|
2745
|
+
gap: var(--juno-space-2);
|
|
2746
|
+
inline-size: var(--juno-rail-width);
|
|
2747
|
+
min-block-size: 100%;
|
|
2748
|
+
padding-block: var(--juno-space-8);
|
|
2749
|
+
background: var(--juno-s1);
|
|
2750
|
+
border-inline-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
2751
|
+
transition: inline-size var(--juno-motion-duration-base) var(--juno-motion-ease-standard);
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
.juno-rail__brand {
|
|
2755
|
+
display: flex;
|
|
2756
|
+
align-items: center;
|
|
2757
|
+
gap: var(--juno-gap-control);
|
|
2758
|
+
padding: var(--juno-space-8) var(--juno-space-16);
|
|
2759
|
+
margin-block-end: var(--juno-space-8);
|
|
2760
|
+
font-family: var(--juno-font-family-mono);
|
|
2761
|
+
font-size: var(--juno-font-size-13);
|
|
2762
|
+
font-weight: var(--juno-font-weight-bold);
|
|
2763
|
+
letter-spacing: var(--juno-font-tracking-wide);
|
|
2764
|
+
text-transform: uppercase;
|
|
2765
|
+
color: var(--juno-data);
|
|
2766
|
+
white-space: nowrap;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
.juno-rail__item {
|
|
2770
|
+
display: flex;
|
|
2771
|
+
align-items: center;
|
|
2772
|
+
gap: var(--juno-gap-control);
|
|
2773
|
+
padding: var(--juno-space-8) var(--juno-space-16);
|
|
2774
|
+
border-inline-start: var(--juno-border-width-2) solid transparent;
|
|
2775
|
+
font-family: var(--juno-font-family-sans);
|
|
2776
|
+
font-size: var(--juno-font-size-12);
|
|
2777
|
+
font-weight: var(--juno-font-weight-semibold);
|
|
2778
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
2779
|
+
text-transform: uppercase;
|
|
2780
|
+
text-decoration: none;
|
|
2781
|
+
color: var(--juno-label);
|
|
2782
|
+
white-space: nowrap;
|
|
2783
|
+
cursor: pointer;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
.juno-rail__item:hover {
|
|
2787
|
+
background: var(--juno-s2);
|
|
2788
|
+
color: var(--juno-data);
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
/* active = attribute, not class: <a aria-current="page"> */
|
|
2792
|
+
.juno-rail__item[aria-current] {
|
|
2793
|
+
background: var(--juno-s3);
|
|
2794
|
+
border-inline-start-color: var(--juno-role);
|
|
2795
|
+
color: var(--juno-data);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
.juno-rail__item:focus-visible {
|
|
2799
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
2800
|
+
outline-offset: calc(-1 * var(--juno-space-2));
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
/* Collapsed — icons only; labels leave the accessibility tree too, so
|
|
2804
|
+
give collapsed items a title or aria-label from the app. */
|
|
2805
|
+
.juno-rail--collapsed {
|
|
2806
|
+
--juno-rail-width: var(--juno-space-56);
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
.juno-rail--collapsed .juno-rail__label,
|
|
2810
|
+
.juno-rail--collapsed .juno-rail__brand .juno-rail__label {
|
|
2811
|
+
display: none;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
.juno-rail--collapsed .juno-rail__item,
|
|
2815
|
+
.juno-rail--collapsed .juno-rail__brand {
|
|
2816
|
+
justify-content: center;
|
|
2817
|
+
padding-inline: 0;
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2821
|
+
* Component — Data readout tile
|
|
2822
|
+
* Usage:
|
|
2823
|
+
* <div class="juno-readout juno--nominal">
|
|
2824
|
+
* <span class="juno-readout__label">N1</span>
|
|
2825
|
+
* <span class="juno-readout__value">89.3</span>
|
|
2826
|
+
* <span class="juno-readout__unit">%</span>
|
|
2827
|
+
* </div>
|
|
2828
|
+
* The value takes --juno-role; default role is the neutral data color.
|
|
2829
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2830
|
+
|
|
2831
|
+
.juno-readout {
|
|
2832
|
+
--juno-role: var(--juno-data);
|
|
2833
|
+
|
|
2834
|
+
display: flex;
|
|
2835
|
+
flex-direction: column;
|
|
2836
|
+
background: var(--juno-s2);
|
|
2837
|
+
border-radius: var(--juno-radius-5);
|
|
2838
|
+
padding: var(--juno-space-20) var(--juno-space-16);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
.juno-readout--alert { border: var(--juno-border-width-1) solid var(--juno-role); }
|
|
2842
|
+
|
|
2843
|
+
.juno-readout__label {
|
|
2844
|
+
font-size: var(--juno-font-size-10);
|
|
2845
|
+
letter-spacing: var(--juno-font-tracking-wider);
|
|
2846
|
+
text-transform: uppercase;
|
|
2847
|
+
color: var(--juno-label);
|
|
2848
|
+
margin-bottom: var(--juno-space-8);
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
.juno-readout__value {
|
|
2852
|
+
font-family: var(--juno-font-family-mono);
|
|
2853
|
+
font-weight: var(--juno-font-weight-bold);
|
|
2854
|
+
font-size: var(--juno-font-size-38);
|
|
2855
|
+
line-height: var(--juno-font-lineHeight-none);
|
|
2856
|
+
color: var(--juno-role);
|
|
2857
|
+
font-variant-numeric: tabular-nums;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
.juno-readout__unit {
|
|
2861
|
+
font-size: var(--juno-font-size-11);
|
|
2862
|
+
color: var(--juno-label);
|
|
2863
|
+
margin-top: var(--juno-space-4);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2867
|
+
* Component — Segmented control
|
|
2868
|
+
* Exclusive-choice pill row (filters, mode switchers, sort pickers).
|
|
2869
|
+
* Zero JS: native radios carry the state; the checked pill takes the
|
|
2870
|
+
* role color (default label-neutral; add .juno--<role> to recolor).
|
|
2871
|
+
* Usage:
|
|
2872
|
+
* <fieldset class="juno-seg juno--active">
|
|
2873
|
+
* <label class="juno-seg__opt"><input type="radio" name="kind" checked /><span>ALL</span></label>
|
|
2874
|
+
* <label class="juno-seg__opt"><input type="radio" name="kind" /><span>PHOTOS</span></label>
|
|
2875
|
+
* </fieldset>
|
|
2876
|
+
* JS-driven apps may use buttons instead; the app flips aria-pressed:
|
|
2877
|
+
* <div class="juno-seg" role="group">
|
|
2878
|
+
* <button class="juno-seg__opt" aria-pressed="true">ALL</button>
|
|
2879
|
+
* </div>
|
|
2880
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2881
|
+
|
|
2882
|
+
.juno-seg {
|
|
2883
|
+
--juno-role: var(--juno-data);
|
|
2884
|
+
|
|
2885
|
+
display: inline-flex;
|
|
2886
|
+
flex-wrap: wrap;
|
|
2887
|
+
gap: var(--juno-space-4);
|
|
2888
|
+
margin: 0;
|
|
2889
|
+
padding: 0;
|
|
2890
|
+
border: 0;
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
.juno-seg__opt {
|
|
2894
|
+
display: inline-flex;
|
|
2895
|
+
cursor: pointer;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
/* visually hidden but still focusable + in the tab order */
|
|
2899
|
+
.juno-seg__opt input {
|
|
2900
|
+
position: absolute;
|
|
2901
|
+
inline-size: 1px;
|
|
2902
|
+
block-size: 1px;
|
|
2903
|
+
opacity: 0;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
/* the pill — a span after the radio, or the aria-pressed button itself */
|
|
2907
|
+
.juno-seg__opt input + span,
|
|
2908
|
+
button.juno-seg__opt {
|
|
2909
|
+
display: inline-flex;
|
|
2910
|
+
align-items: center;
|
|
2911
|
+
gap: var(--juno-gap-control);
|
|
2912
|
+
font-family: var(--juno-font-family-sans);
|
|
2913
|
+
font-size: var(--juno-font-size-11);
|
|
2914
|
+
font-weight: var(--juno-font-weight-bold);
|
|
2915
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
2916
|
+
text-transform: uppercase;
|
|
2917
|
+
padding: var(--juno-space-4) var(--juno-space-10);
|
|
2918
|
+
color: var(--juno-label);
|
|
2919
|
+
background: transparent;
|
|
2920
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
2921
|
+
border-radius: var(--juno-radius-3);
|
|
2922
|
+
transition:
|
|
2923
|
+
color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
2924
|
+
border-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
2925
|
+
background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
.juno-seg__opt:hover input:not(:checked) + span,
|
|
2929
|
+
button.juno-seg__opt[aria-pressed='false']:hover {
|
|
2930
|
+
background: var(--juno-s2);
|
|
2931
|
+
color: var(--juno-data);
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
.juno-seg__opt input:checked + span,
|
|
2935
|
+
button.juno-seg__opt[aria-pressed='true'] {
|
|
2936
|
+
background: var(--juno-s3);
|
|
2937
|
+
border-color: var(--juno-role);
|
|
2938
|
+
color: var(--juno-role);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
.juno-seg__opt input:focus-visible + span,
|
|
2942
|
+
button.juno-seg__opt:focus-visible {
|
|
2943
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
2944
|
+
outline-offset: var(--juno-space-2);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
.juno-seg__opt input:disabled + span,
|
|
2948
|
+
button.juno-seg__opt:disabled {
|
|
2949
|
+
opacity: var(--juno-opacity-disabled);
|
|
2950
|
+
cursor: not-allowed;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
.juno-seg:disabled .juno-seg__opt,
|
|
2954
|
+
.juno-seg__opt:has(:disabled) {
|
|
2955
|
+
cursor: not-allowed;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
/* Small — matches .juno-btn--sm; for dense toolbars */
|
|
2959
|
+
.juno-seg--sm .juno-seg__opt input + span,
|
|
2960
|
+
.juno-seg--sm > button.juno-seg__opt {
|
|
2961
|
+
font-size: var(--juno-font-size-10);
|
|
2962
|
+
letter-spacing: var(--juno-font-tracking-wide);
|
|
2963
|
+
padding: var(--juno-space-2) var(--juno-space-8);
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
2967
|
+
* Component — Select
|
|
2968
|
+
* A native <select> styled as an input, with a token-colored chevron.
|
|
2969
|
+
* Usage:
|
|
2970
|
+
* <div class="juno-select">
|
|
2971
|
+
* <select class="juno-input">
|
|
2972
|
+
* <option>NOMINAL</option><option>CAUTION</option>
|
|
2973
|
+
* </select>
|
|
2974
|
+
* </div>
|
|
2975
|
+
* The inner <select> reuses .juno-input for the box; the wrapper adds the
|
|
2976
|
+
* chevron and reserves room for it (inline-end, so it mirrors under RTL).
|
|
2977
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
2978
|
+
|
|
2979
|
+
.juno-select {
|
|
2980
|
+
position: relative;
|
|
2981
|
+
display: block;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
.juno-select > .juno-input {
|
|
2985
|
+
appearance: none;
|
|
2986
|
+
padding-inline-end: var(--juno-space-32);
|
|
2987
|
+
font-family: var(--juno-font-family-sans);
|
|
2988
|
+
cursor: pointer;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
/* chevron — CSS triangle so it inherits a token color, no SVG asset */
|
|
2992
|
+
.juno-select::after {
|
|
2993
|
+
content: '';
|
|
2994
|
+
position: absolute;
|
|
2995
|
+
inset-inline-end: var(--juno-space-12);
|
|
2996
|
+
inset-block-start: 50%;
|
|
2997
|
+
inline-size: 0;
|
|
2998
|
+
block-size: 0;
|
|
2999
|
+
border-inline: var(--juno-space-4) solid transparent;
|
|
3000
|
+
border-block-start: var(--juno-space-4) solid var(--juno-label);
|
|
3001
|
+
transform: translateY(-25%);
|
|
3002
|
+
pointer-events: none;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3006
|
+
* Component — Skeleton · loading placeholder
|
|
3007
|
+
* A shimmering block that stands in for content not yet loaded. Same
|
|
3008
|
+
* shimmer language as the table's row skeleton, generalized: text lines,
|
|
3009
|
+
* blocks, and circles (avatars). Zero JS — size it with width / height or
|
|
3010
|
+
* --juno-skeleton-h. Honors prefers-reduced-motion (shimmer → static).
|
|
3011
|
+
* Mark the loading region aria-busy="true" so AT announces it.
|
|
3012
|
+
* Usage:
|
|
3013
|
+
* <div aria-busy="true" aria-live="polite">
|
|
3014
|
+
* <span class="juno-skeleton juno-skeleton--circle" style="--juno-skeleton-h: 48px;"></span>
|
|
3015
|
+
* <span class="juno-skeleton juno-skeleton--text" style="inline-size: 60%;"></span>
|
|
3016
|
+
* <span class="juno-skeleton juno-skeleton--text"></span>
|
|
3017
|
+
* </div>
|
|
3018
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3019
|
+
|
|
3020
|
+
.juno-skeleton {
|
|
3021
|
+
--juno-skeleton-h: var(--juno-space-16);
|
|
3022
|
+
|
|
3023
|
+
display: block;
|
|
3024
|
+
inline-size: 100%;
|
|
3025
|
+
block-size: var(--juno-skeleton-h);
|
|
3026
|
+
border-radius: var(--juno-radius-3);
|
|
3027
|
+
background: linear-gradient(90deg, var(--juno-s2) 0%, var(--juno-s3) 50%, var(--juno-s2) 100%);
|
|
3028
|
+
background-size: 200% 100%;
|
|
3029
|
+
animation: juno-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
/* a text line — caps height to one line, soft radius */
|
|
3033
|
+
.juno-skeleton--text {
|
|
3034
|
+
--juno-skeleton-h: 1em;
|
|
3035
|
+
|
|
3036
|
+
border-radius: var(--juno-radius-2);
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
/* round avatar / icon placeholder — keep it square via the inline size */
|
|
3040
|
+
.juno-skeleton--circle {
|
|
3041
|
+
inline-size: var(--juno-skeleton-h);
|
|
3042
|
+
border-radius: 50%;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
/* a larger surface (card / media) */
|
|
3046
|
+
.juno-skeleton--block {
|
|
3047
|
+
--juno-skeleton-h: var(--juno-space-56);
|
|
3048
|
+
|
|
3049
|
+
border-radius: var(--juno-radius-4);
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
@keyframes juno-skeleton-shimmer {
|
|
3053
|
+
from {
|
|
3054
|
+
background-position: 200% 0;
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
to {
|
|
3058
|
+
background-position: -200% 0;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3063
|
+
.juno-skeleton {
|
|
3064
|
+
animation: none;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3069
|
+
* Component — Slider · track + grip lever
|
|
3070
|
+
* A recessed channel with detent notches, an accent fill, and a gripped
|
|
3071
|
+
* lever. Value-driven: the app sets --juno-slider-pct (0–100) and owns
|
|
3072
|
+
* drag / keyboard / ARIA (role="slider" + aria-value*), the same split as
|
|
3073
|
+
* the overlays. Two sizes: standard (lever always full) and --compact
|
|
3074
|
+
* (lever recedes to a slim marker until hover / focus / drag).
|
|
3075
|
+
* Usage:
|
|
3076
|
+
* <div class="juno-slider" style="--juno-slider-pct: 72" role="slider"
|
|
3077
|
+
* tabindex="0" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100">
|
|
3078
|
+
* <span class="juno-slider__channel"></span>
|
|
3079
|
+
* <span class="juno-slider__lever"></span>
|
|
3080
|
+
* </div>
|
|
3081
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3082
|
+
|
|
3083
|
+
.juno-slider {
|
|
3084
|
+
--juno-role: var(--juno-active);
|
|
3085
|
+
--juno-slider-pct: 0;
|
|
3086
|
+
|
|
3087
|
+
position: relative;
|
|
3088
|
+
display: flex;
|
|
3089
|
+
align-items: center;
|
|
3090
|
+
inline-size: 100%;
|
|
3091
|
+
block-size: 48px;
|
|
3092
|
+
cursor: pointer;
|
|
3093
|
+
touch-action: none;
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
.juno-slider__channel {
|
|
3097
|
+
position: absolute;
|
|
3098
|
+
inset-inline: 0;
|
|
3099
|
+
inset-block-start: 50%;
|
|
3100
|
+
transform: translateY(-50%);
|
|
3101
|
+
block-size: var(--juno-space-20);
|
|
3102
|
+
background-color: var(--juno-s0);
|
|
3103
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
3104
|
+
border-radius: var(--juno-radius-4);
|
|
3105
|
+
box-shadow: inset 0 2px 4px rgb(0 0 0 / 0.5);
|
|
3106
|
+
overflow: hidden;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
/* detent notches every 10% */
|
|
3110
|
+
.juno-slider__channel::before {
|
|
3111
|
+
content: '';
|
|
3112
|
+
position: absolute;
|
|
3113
|
+
inset: 0;
|
|
3114
|
+
background-image: repeating-linear-gradient(
|
|
3115
|
+
90deg,
|
|
3116
|
+
transparent 0 calc(10% - 1px),
|
|
3117
|
+
var(--juno-muted) calc(10% - 1px),
|
|
3118
|
+
var(--juno-muted) 10%
|
|
3119
|
+
);
|
|
3120
|
+
opacity: 0.5;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/* accent fill up to the value */
|
|
3124
|
+
.juno-slider__channel::after {
|
|
3125
|
+
content: '';
|
|
3126
|
+
position: absolute;
|
|
3127
|
+
inset-block: 0;
|
|
3128
|
+
inset-inline-start: 0;
|
|
3129
|
+
inline-size: calc(var(--juno-slider-pct) * 1%);
|
|
3130
|
+
background: linear-gradient(
|
|
3131
|
+
90deg,
|
|
3132
|
+
color-mix(in srgb, var(--juno-role) 8%, transparent),
|
|
3133
|
+
color-mix(in srgb, var(--juno-role) 34%, transparent)
|
|
3134
|
+
);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
/* gripped lever, positioned at the value */
|
|
3138
|
+
.juno-slider__lever {
|
|
3139
|
+
position: absolute;
|
|
3140
|
+
inset-inline-start: calc(var(--juno-slider-pct) * 1%);
|
|
3141
|
+
inset-block-start: 50%;
|
|
3142
|
+
transform: translate(-50%, -50%);
|
|
3143
|
+
inline-size: 26px;
|
|
3144
|
+
block-size: 40px;
|
|
3145
|
+
border-radius: var(--juno-radius-4);
|
|
3146
|
+
border: var(--juno-border-width-1) solid var(--juno-knob-edge);
|
|
3147
|
+
background:
|
|
3148
|
+
repeating-linear-gradient(90deg, var(--juno-knob-grip) 0 2px, transparent 2px 5px) center / 12px
|
|
3149
|
+
16px no-repeat,
|
|
3150
|
+
linear-gradient(180deg, var(--juno-knob-face-hi), var(--juno-knob-face-lo));
|
|
3151
|
+
box-shadow: 0 2px 4px rgb(0 0 0 / 0.5);
|
|
3152
|
+
pointer-events: none;
|
|
3153
|
+
transition:
|
|
3154
|
+
inline-size var(--juno-motion-duration-base) ease,
|
|
3155
|
+
block-size var(--juno-motion-duration-base) ease,
|
|
3156
|
+
box-shadow var(--juno-motion-duration-base) ease;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/* compact — lever minimizes to a slim marker until engaged */
|
|
3160
|
+
.juno-slider--compact .juno-slider__lever {
|
|
3161
|
+
inline-size: 10px;
|
|
3162
|
+
block-size: 22px;
|
|
3163
|
+
border-radius: var(--juno-radius-3);
|
|
3164
|
+
background: linear-gradient(180deg, var(--juno-knob-face-hi), var(--juno-knob-face-lo));
|
|
3165
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 0.45);
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
.juno-slider--compact:hover .juno-slider__lever,
|
|
3169
|
+
.juno-slider--compact:focus-visible .juno-slider__lever,
|
|
3170
|
+
.juno-slider--compact:active .juno-slider__lever {
|
|
3171
|
+
inline-size: 26px;
|
|
3172
|
+
block-size: 40px;
|
|
3173
|
+
border-radius: var(--juno-radius-4);
|
|
3174
|
+
background:
|
|
3175
|
+
repeating-linear-gradient(90deg, var(--juno-knob-grip) 0 2px, transparent 2px 5px) center / 12px
|
|
3176
|
+
16px no-repeat,
|
|
3177
|
+
linear-gradient(180deg, var(--juno-knob-face-hi), var(--juno-knob-face-lo));
|
|
3178
|
+
box-shadow: 0 2px 4px rgb(0 0 0 / 0.5);
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
.juno-slider[aria-disabled='true'] {
|
|
3182
|
+
opacity: var(--juno-opacity-disabled);
|
|
3183
|
+
cursor: not-allowed;
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3187
|
+
* Component — Spark (sparkline contract)
|
|
3188
|
+
* junoui ships no charting: the app generates the <polyline> points;
|
|
3189
|
+
* this class supplies the size / stroke / role-color contract so every
|
|
3190
|
+
* sparkline matches. Generator recipe: docs/components/spark.md.
|
|
3191
|
+
* Usage:
|
|
3192
|
+
* <svg class="juno-spark juno--nominal" viewBox="0 0 120 24" preserveAspectRatio="none">
|
|
3193
|
+
* <polyline points="0,20 8,14 16,17 …" />
|
|
3194
|
+
* </svg>
|
|
3195
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3196
|
+
|
|
3197
|
+
.juno-spark {
|
|
3198
|
+
--juno-role: var(--juno-active);
|
|
3199
|
+
|
|
3200
|
+
display: block;
|
|
3201
|
+
inline-size: 100%;
|
|
3202
|
+
block-size: var(--juno-space-24);
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
.juno-spark polyline {
|
|
3206
|
+
fill: none;
|
|
3207
|
+
stroke: var(--juno-role);
|
|
3208
|
+
stroke-width: 1.5;
|
|
3209
|
+
stroke-linejoin: round;
|
|
3210
|
+
stroke-linecap: round;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3214
|
+
* Component — Stepper · multi-step progress
|
|
3215
|
+
* A horizontal sequence of steps with numbered markers and connectors.
|
|
3216
|
+
* Each step carries data-state: complete · current · upcoming. Mark the
|
|
3217
|
+
* active step aria-current="step". Built on a real <ol>; the app sets the
|
|
3218
|
+
* states. Zero JS. Reads --juno-role (default active) for the accent.
|
|
3219
|
+
* Usage:
|
|
3220
|
+
* <ol class="juno-stepper">
|
|
3221
|
+
* <li class="juno-stepper__step" data-state="complete">
|
|
3222
|
+
* <span class="juno-stepper__marker">
|
|
3223
|
+
* <svg class="juno-icon juno-icon--sm"><use href="…#juno-i-check" /></svg>
|
|
3224
|
+
* </span>
|
|
3225
|
+
* <span class="juno-stepper__label">Plan</span>
|
|
3226
|
+
* </li>
|
|
3227
|
+
* <li class="juno-stepper__step" data-state="current" aria-current="step">
|
|
3228
|
+
* <span class="juno-stepper__marker">2</span>
|
|
3229
|
+
* <span class="juno-stepper__label">Build</span>
|
|
3230
|
+
* </li>
|
|
3231
|
+
* <li class="juno-stepper__step" data-state="upcoming">
|
|
3232
|
+
* <span class="juno-stepper__marker">3</span>
|
|
3233
|
+
* <span class="juno-stepper__label">Ship</span>
|
|
3234
|
+
* </li>
|
|
3235
|
+
* </ol>
|
|
3236
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3237
|
+
|
|
3238
|
+
.juno-stepper {
|
|
3239
|
+
--juno-stepper-marker: var(--juno-space-28);
|
|
3240
|
+
--juno-role: var(--juno-active);
|
|
3241
|
+
|
|
3242
|
+
display: flex;
|
|
3243
|
+
margin: 0;
|
|
3244
|
+
padding: 0;
|
|
3245
|
+
list-style: none;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
.juno-stepper__step {
|
|
3249
|
+
position: relative;
|
|
3250
|
+
flex: 1;
|
|
3251
|
+
display: flex;
|
|
3252
|
+
flex-direction: column;
|
|
3253
|
+
align-items: center;
|
|
3254
|
+
gap: var(--juno-space-8);
|
|
3255
|
+
text-align: center;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
/* connector — a line from the previous marker into this one */
|
|
3259
|
+
.juno-stepper__step:not(:first-child)::before {
|
|
3260
|
+
content: '';
|
|
3261
|
+
position: absolute;
|
|
3262
|
+
inset-block-start: calc(var(--juno-stepper-marker) / 2);
|
|
3263
|
+
inset-inline-end: 50%;
|
|
3264
|
+
inline-size: 100%;
|
|
3265
|
+
block-size: var(--juno-border-width-1);
|
|
3266
|
+
background: var(--juno-muted);
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
/* a reached step (complete or current) lights its incoming connector */
|
|
3270
|
+
.juno-stepper__step[data-state='complete']::before,
|
|
3271
|
+
.juno-stepper__step[data-state='current']::before {
|
|
3272
|
+
background: var(--juno-role);
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
.juno-stepper__marker {
|
|
3276
|
+
position: relative;
|
|
3277
|
+
z-index: 1;
|
|
3278
|
+
display: grid;
|
|
3279
|
+
place-content: center;
|
|
3280
|
+
inline-size: var(--juno-stepper-marker);
|
|
3281
|
+
block-size: var(--juno-stepper-marker);
|
|
3282
|
+
border-radius: 50%;
|
|
3283
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
3284
|
+
background: var(--juno-control-surface);
|
|
3285
|
+
color: var(--juno-label);
|
|
3286
|
+
font-family: var(--juno-font-family-mono);
|
|
3287
|
+
font-size: var(--juno-font-size-12);
|
|
3288
|
+
font-weight: var(--juno-font-weight-bold);
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
.juno-stepper__step[data-state='complete'] .juno-stepper__marker {
|
|
3292
|
+
border-color: var(--juno-role);
|
|
3293
|
+
background: color-mix(in srgb, var(--juno-role) 18%, var(--juno-control-surface));
|
|
3294
|
+
color: var(--juno-role);
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
.juno-stepper__step[data-state='current'] .juno-stepper__marker {
|
|
3298
|
+
border-color: var(--juno-role);
|
|
3299
|
+
color: var(--juno-role);
|
|
3300
|
+
box-shadow: 0 0 0 var(--juno-border-width-2) color-mix(in srgb, var(--juno-role) 30%, transparent);
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
.juno-stepper__label {
|
|
3304
|
+
max-inline-size: 14ch;
|
|
3305
|
+
color: var(--juno-label);
|
|
3306
|
+
font-family: var(--juno-font-family-sans);
|
|
3307
|
+
font-size: var(--juno-font-size-12);
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
.juno-stepper__step[data-state='complete'] .juno-stepper__label,
|
|
3311
|
+
.juno-stepper__step[data-state='current'] .juno-stepper__label {
|
|
3312
|
+
color: var(--juno-data);
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
/* vertical — steps stack; connector runs down the marker column */
|
|
3316
|
+
.juno-stepper--vertical {
|
|
3317
|
+
flex-direction: column;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
.juno-stepper--vertical .juno-stepper__step {
|
|
3321
|
+
flex-direction: row;
|
|
3322
|
+
align-items: flex-start;
|
|
3323
|
+
gap: var(--juno-space-12);
|
|
3324
|
+
text-align: start;
|
|
3325
|
+
padding-block-end: var(--juno-space-16);
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
.juno-stepper--vertical .juno-stepper__step:not(:first-child)::before {
|
|
3329
|
+
inset-block: calc(var(--juno-stepper-marker) / -2) auto;
|
|
3330
|
+
inset-inline: calc(var(--juno-stepper-marker) / 2) auto;
|
|
3331
|
+
inline-size: var(--juno-border-width-1);
|
|
3332
|
+
block-size: 100%;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3336
|
+
* Component — Switch (toggle) · labeled rocker
|
|
3337
|
+
* A wide squared slide-switch with the state printed inside the track
|
|
3338
|
+
* (ON / OFF, mono) and a gripped knob; lit nominal-green when ON. A real
|
|
3339
|
+
* checkbox drives it; the app owns state. Honors prefers-reduced-motion.
|
|
3340
|
+
* Usage:
|
|
3341
|
+
* <label class="juno-switch">
|
|
3342
|
+
* <input class="juno-switch__input" type="checkbox" role="switch" />
|
|
3343
|
+
* <span class="juno-switch__track"></span>
|
|
3344
|
+
* <span>AUTO-SCALING</span>
|
|
3345
|
+
* </label>
|
|
3346
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3347
|
+
|
|
3348
|
+
.juno-switch {
|
|
3349
|
+
display: inline-flex;
|
|
3350
|
+
align-items: center;
|
|
3351
|
+
gap: var(--juno-gap-control);
|
|
3352
|
+
font-family: var(--juno-font-family-sans);
|
|
3353
|
+
font-size: var(--juno-font-size-13);
|
|
3354
|
+
color: var(--juno-data);
|
|
3355
|
+
cursor: pointer;
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
/* visually hidden but still focusable + in the tab order */
|
|
3359
|
+
.juno-switch__input {
|
|
3360
|
+
position: absolute;
|
|
3361
|
+
inline-size: 1px;
|
|
3362
|
+
block-size: 1px;
|
|
3363
|
+
opacity: 0;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.juno-switch__track {
|
|
3367
|
+
--juno-role: var(--juno-nominal);
|
|
3368
|
+
|
|
3369
|
+
position: relative;
|
|
3370
|
+
flex-shrink: 0;
|
|
3371
|
+
inline-size: 70px;
|
|
3372
|
+
block-size: var(--juno-space-28);
|
|
3373
|
+
background: var(--juno-control-surface);
|
|
3374
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
3375
|
+
border-radius: var(--juno-radius-4);
|
|
3376
|
+
box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.4);
|
|
3377
|
+
transition:
|
|
3378
|
+
border-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
3379
|
+
background-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
/* ON / OFF legend, printed opposite the knob */
|
|
3383
|
+
.juno-switch__track::after {
|
|
3384
|
+
content: 'OFF';
|
|
3385
|
+
position: absolute;
|
|
3386
|
+
inset-block: 0;
|
|
3387
|
+
inset-inline-end: var(--juno-space-8);
|
|
3388
|
+
display: flex;
|
|
3389
|
+
align-items: center;
|
|
3390
|
+
font-family: var(--juno-font-family-mono);
|
|
3391
|
+
font-size: var(--juno-font-size-10);
|
|
3392
|
+
font-weight: var(--juno-font-weight-bold);
|
|
3393
|
+
letter-spacing: var(--juno-font-tracking-wide);
|
|
3394
|
+
color: var(--juno-muted);
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
/* knob — gradient face, three grip lines; slides via inset-inline-start (RTL) */
|
|
3398
|
+
.juno-switch__track::before {
|
|
3399
|
+
content: '';
|
|
3400
|
+
position: absolute;
|
|
3401
|
+
inset-block-start: var(--juno-space-2);
|
|
3402
|
+
inset-inline-start: var(--juno-space-2);
|
|
3403
|
+
inline-size: 26px;
|
|
3404
|
+
block-size: 22px;
|
|
3405
|
+
border-radius: var(--juno-radius-4);
|
|
3406
|
+
border: var(--juno-border-width-1) solid var(--juno-knob-edge);
|
|
3407
|
+
background:
|
|
3408
|
+
repeating-linear-gradient(90deg, var(--juno-knob-grip) 0 2px, transparent 2px 5px) center / 12px
|
|
3409
|
+
14px no-repeat,
|
|
3410
|
+
linear-gradient(180deg, var(--juno-knob-face-hi), var(--juno-knob-face-lo));
|
|
3411
|
+
box-shadow: 0 2px 4px rgb(0 0 0 / 0.5);
|
|
3412
|
+
transition: inset-inline-start var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
/* ON fills the track with a role tint — the filled/empty contrast reads at a
|
|
3416
|
+
squint in both modes, where a border-color flip alone does not. */
|
|
3417
|
+
.juno-switch__input:checked + .juno-switch__track {
|
|
3418
|
+
border-color: var(--juno-role);
|
|
3419
|
+
background: color-mix(in srgb, var(--juno-role) 28%, var(--juno-control-surface));
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
.juno-switch__input:checked + .juno-switch__track::after {
|
|
3423
|
+
content: 'ON';
|
|
3424
|
+
inset-inline: var(--juno-space-8) auto;
|
|
3425
|
+
color: var(--juno-data);
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
.juno-switch__input:checked + .juno-switch__track::before {
|
|
3429
|
+
inset-inline-start: calc(70px - 26px - var(--juno-space-2));
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
/* Small — quiet per-row toggle (plugin lists, table rows). Drops the printed
|
|
3433
|
+
ON/OFF legend; keeps grip, role tint and inset shadow so it reads junoui. */
|
|
3434
|
+
.juno-switch--sm .juno-switch__track {
|
|
3435
|
+
inline-size: var(--juno-space-40);
|
|
3436
|
+
block-size: var(--juno-space-20);
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
.juno-switch--sm .juno-switch__track::after,
|
|
3440
|
+
.juno-switch--sm .juno-switch__input:checked + .juno-switch__track::after {
|
|
3441
|
+
content: none;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
.juno-switch--sm .juno-switch__track::before {
|
|
3445
|
+
inline-size: var(--juno-space-16);
|
|
3446
|
+
block-size: var(--juno-space-12);
|
|
3447
|
+
background:
|
|
3448
|
+
repeating-linear-gradient(90deg, var(--juno-knob-grip) 0 2px, transparent 2px 5px) center / 8px
|
|
3449
|
+
8px no-repeat,
|
|
3450
|
+
linear-gradient(180deg, var(--juno-knob-face-hi), var(--juno-knob-face-lo));
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
.juno-switch--sm .juno-switch__input:checked + .juno-switch__track::before {
|
|
3454
|
+
inset-inline-start: calc(var(--juno-space-40) - var(--juno-space-16) - var(--juno-space-2));
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
.juno-switch__input:focus-visible + .juno-switch__track {
|
|
3458
|
+
outline: var(--juno-border-width-2) solid var(--juno-active);
|
|
3459
|
+
outline-offset: var(--juno-space-2);
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
.juno-switch__input:disabled + .juno-switch__track {
|
|
3463
|
+
opacity: var(--juno-opacity-disabled);
|
|
3464
|
+
cursor: not-allowed;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3468
|
+
* Component — Table / Data grid
|
|
3469
|
+
* Dense, scannable rows that read like instrument readouts: numbers in
|
|
3470
|
+
* mono, right-aligned; semantic color reserved for status; one selected
|
|
3471
|
+
* row carries the active rail. junoui ships the LOOK + a11y contract;
|
|
3472
|
+
* the app owns sort, selection, inline-edit, pagination, and filtering.
|
|
3473
|
+
*
|
|
3474
|
+
* Real <table> semantics — junoui only dresses it. Wrap in
|
|
3475
|
+
* `.juno-table-scroll` for a sticky-header, overflow-scrolling viewport.
|
|
3476
|
+
* Usage:
|
|
3477
|
+
* <div class="juno-table-scroll">
|
|
3478
|
+
* <table class="juno-table juno-table--zebra juno-table--sticky">
|
|
3479
|
+
* <thead><tr>
|
|
3480
|
+
* <th aria-sort="ascending">FLIGHT</th>
|
|
3481
|
+
* <th class="juno-table__num" aria-sort="none">ALT</th>
|
|
3482
|
+
* </tr></thead>
|
|
3483
|
+
* <tbody>
|
|
3484
|
+
* <tr aria-selected="true"><td>…</td><td class="juno-table__num">37,000</td></tr>
|
|
3485
|
+
* </tbody>
|
|
3486
|
+
* </table>
|
|
3487
|
+
* </div>
|
|
3488
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3489
|
+
|
|
3490
|
+
/* ── scroll viewport (sticky header lives here) ───────────────────────── */
|
|
3491
|
+
.juno-table-scroll {
|
|
3492
|
+
max-block-size: 480px;
|
|
3493
|
+
overflow: auto;
|
|
3494
|
+
scrollbar-width: thin;
|
|
3495
|
+
scrollbar-color: var(--juno-s3) transparent;
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
.juno-table-scroll::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
|
|
3499
|
+
.juno-table-scroll::-webkit-scrollbar-thumb { background: var(--juno-s3); border-radius: var(--juno-radius-5); }
|
|
3500
|
+
.juno-table-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
3501
|
+
|
|
3502
|
+
/* ── table ────────────────────────────────────────────────────────────── */
|
|
3503
|
+
.juno-table {
|
|
3504
|
+
inline-size: 100%;
|
|
3505
|
+
border-collapse: separate;
|
|
3506
|
+
border-spacing: 0;
|
|
3507
|
+
font-family: var(--juno-font-family-sans);
|
|
3508
|
+
font-size: var(--juno-font-size-13);
|
|
3509
|
+
color: var(--juno-data);
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
/* header */
|
|
3513
|
+
.juno-table thead th {
|
|
3514
|
+
padding: var(--juno-space-12) var(--juno-space-16);
|
|
3515
|
+
background: var(--juno-s2);
|
|
3516
|
+
font-size: var(--juno-font-size-10);
|
|
3517
|
+
font-weight: var(--juno-font-weight-medium);
|
|
3518
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
3519
|
+
text-transform: uppercase;
|
|
3520
|
+
color: var(--juno-label);
|
|
3521
|
+
text-align: start;
|
|
3522
|
+
white-space: nowrap;
|
|
3523
|
+
vertical-align: middle;
|
|
3524
|
+
user-select: none;
|
|
3525
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-control-edge);
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
.juno-table--sticky thead th {
|
|
3529
|
+
position: sticky;
|
|
3530
|
+
inset-block-start: 0;
|
|
3531
|
+
z-index: var(--juno-z-raised);
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
/* sortable header — set aria-sort; the arrow + active color follow */
|
|
3535
|
+
.juno-table thead th[aria-sort] { cursor: pointer; }
|
|
3536
|
+
|
|
3537
|
+
.juno-table thead th[aria-sort]::after {
|
|
3538
|
+
content: '';
|
|
3539
|
+
display: inline-block;
|
|
3540
|
+
inline-size: var(--juno-space-8);
|
|
3541
|
+
margin-inline-start: var(--juno-space-4);
|
|
3542
|
+
font-family: var(--juno-font-family-mono);
|
|
3543
|
+
font-size: 9px;
|
|
3544
|
+
color: var(--juno-active);
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
.juno-table thead th[aria-sort='none']:hover { color: var(--juno-data); }
|
|
3548
|
+
.juno-table thead th[aria-sort='none']:hover::after { content: '↕'; color: var(--juno-muted); }
|
|
3549
|
+
.juno-table thead th[aria-sort='ascending'] { color: var(--juno-data); }
|
|
3550
|
+
.juno-table thead th[aria-sort='ascending']::after { content: '▲'; }
|
|
3551
|
+
.juno-table thead th[aria-sort='descending'] { color: var(--juno-data); }
|
|
3552
|
+
.juno-table thead th[aria-sort='descending']::after { content: '▼'; }
|
|
3553
|
+
|
|
3554
|
+
/* body cells */
|
|
3555
|
+
.juno-table tbody td {
|
|
3556
|
+
padding: var(--juno-space-12) var(--juno-space-16);
|
|
3557
|
+
color: var(--juno-data);
|
|
3558
|
+
white-space: nowrap;
|
|
3559
|
+
vertical-align: middle;
|
|
3560
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
/* density — per-table, independent of the global density mode */
|
|
3564
|
+
.juno-table--compact thead th { padding: var(--juno-space-8) var(--juno-space-12); }
|
|
3565
|
+
.juno-table--compact tbody td { padding: var(--juno-space-8) var(--juno-space-16); }
|
|
3566
|
+
|
|
3567
|
+
/* zebra (set on the table; striping the odd body rows) */
|
|
3568
|
+
.juno-table--zebra tbody tr:nth-child(even) td { background: var(--juno-s2); }
|
|
3569
|
+
:root[data-juno-mode='light'] .juno-table--zebra tbody tr:nth-child(even) td { background: var(--juno-s1); }
|
|
3570
|
+
|
|
3571
|
+
/* row interaction — app keeps aria-selected in sync */
|
|
3572
|
+
.juno-table tbody tr { transition: background var(--juno-motion-duration-quick) var(--juno-motion-ease-standard); }
|
|
3573
|
+
.juno-table tbody tr:hover td { background: var(--juno-s2); }
|
|
3574
|
+
:root[data-juno-mode='light'] .juno-table tbody tr:hover td { background: var(--juno-s1); }
|
|
3575
|
+
|
|
3576
|
+
.juno-table tbody tr[aria-selected='true'] td {
|
|
3577
|
+
background: color-mix(in srgb, var(--juno-active) 11%, transparent);
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
/* selection rail — a left bar on the first cell of a selected row */
|
|
3581
|
+
.juno-table tbody td:first-child {
|
|
3582
|
+
border-inline-start: var(--juno-border-width-3) solid transparent;
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
.juno-table tbody tr[aria-selected='true'] td:first-child {
|
|
3586
|
+
border-inline-start-color: var(--juno-active);
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
/* ── cell flavors ─────────────────────────────────────────────────────── */
|
|
3590
|
+
|
|
3591
|
+
/* identifier — mono, bold (callsigns, codes, IDs) */
|
|
3592
|
+
.juno-table__id {
|
|
3593
|
+
font-family: var(--juno-font-family-mono);
|
|
3594
|
+
font-weight: var(--juno-font-weight-bold);
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
/* numeric — mono, bold, end-aligned (measured values) */
|
|
3598
|
+
.juno-table__num {
|
|
3599
|
+
font-family: var(--juno-font-family-mono);
|
|
3600
|
+
font-weight: var(--juno-font-weight-bold);
|
|
3601
|
+
text-align: end;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
/* secondary mono (routes, sub-codes) — quieter than data */
|
|
3605
|
+
.juno-table__mono {
|
|
3606
|
+
font-family: var(--juno-font-family-mono);
|
|
3607
|
+
color: var(--juno-label);
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
/* timestamp — mono, muted */
|
|
3611
|
+
.juno-table__time {
|
|
3612
|
+
font-family: var(--juno-font-family-mono);
|
|
3613
|
+
text-align: end;
|
|
3614
|
+
color: var(--juno-label);
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
/* trend / delta — role-colored mono; author supplies ▲ / ▼ + sign */
|
|
3618
|
+
.juno-table__trend {
|
|
3619
|
+
--juno-role: var(--juno-nominal);
|
|
3620
|
+
|
|
3621
|
+
display: inline-flex;
|
|
3622
|
+
align-items: center;
|
|
3623
|
+
gap: var(--juno-space-4);
|
|
3624
|
+
font-family: var(--juno-font-family-mono);
|
|
3625
|
+
font-weight: var(--juno-font-weight-bold);
|
|
3626
|
+
color: var(--juno-role);
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
/* inline meter — track + role fill + value; app sets --juno-table-fill (0–100) */
|
|
3630
|
+
.juno-table__meter {
|
|
3631
|
+
--juno-role: var(--juno-nominal);
|
|
3632
|
+
|
|
3633
|
+
display: inline-flex;
|
|
3634
|
+
align-items: center;
|
|
3635
|
+
gap: var(--juno-space-8);
|
|
3636
|
+
justify-content: flex-end;
|
|
3637
|
+
inline-size: 100%;
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
.juno-table__meter-track {
|
|
3641
|
+
inline-size: var(--juno-space-56);
|
|
3642
|
+
block-size: var(--juno-space-4);
|
|
3643
|
+
border-radius: var(--juno-radius-3);
|
|
3644
|
+
background: var(--juno-control-surface);
|
|
3645
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
3646
|
+
overflow: hidden;
|
|
3647
|
+
flex-shrink: 0;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
.juno-table__meter-fill {
|
|
3651
|
+
display: block;
|
|
3652
|
+
block-size: 100%;
|
|
3653
|
+
inline-size: calc(var(--juno-table-fill, 0) * 1%);
|
|
3654
|
+
background: var(--juno-role);
|
|
3655
|
+
border-radius: var(--juno-radius-3);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
.juno-table__meter-val {
|
|
3659
|
+
font-family: var(--juno-font-family-mono);
|
|
3660
|
+
font-size: var(--juno-font-size-12);
|
|
3661
|
+
color: var(--juno-role);
|
|
3662
|
+
min-inline-size: var(--juno-space-32);
|
|
3663
|
+
text-align: end;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
/* row actions — icon buttons, revealed when the row is hovered/focused */
|
|
3667
|
+
.juno-table__actions {
|
|
3668
|
+
display: inline-flex;
|
|
3669
|
+
gap: var(--juno-space-8);
|
|
3670
|
+
opacity: 0;
|
|
3671
|
+
transition: opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
.juno-table tbody tr:hover .juno-table__actions,
|
|
3675
|
+
.juno-table tbody tr:focus-within .juno-table__actions { opacity: 1; }
|
|
3676
|
+
|
|
3677
|
+
/* no hover to reveal them on touch — actions stay visible */
|
|
3678
|
+
@media (hover: none) {
|
|
3679
|
+
.juno-table__actions { opacity: 1; }
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
.juno-table__action {
|
|
3683
|
+
display: inline-flex;
|
|
3684
|
+
align-items: center;
|
|
3685
|
+
justify-content: center;
|
|
3686
|
+
inline-size: var(--juno-space-24);
|
|
3687
|
+
block-size: var(--juno-space-24);
|
|
3688
|
+
border-radius: var(--juno-radius-3);
|
|
3689
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge);
|
|
3690
|
+
background: transparent;
|
|
3691
|
+
color: var(--juno-label);
|
|
3692
|
+
font-size: var(--juno-font-size-13);
|
|
3693
|
+
cursor: pointer;
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
.juno-table__action:hover { color: var(--juno-data); background: var(--juno-s2); }
|
|
3697
|
+
|
|
3698
|
+
/* ── overflow behaviors (set on a cell) ───────────────────────────────────
|
|
3699
|
+
Scoped under `.juno-table td` so they out-specify the base `white-space:
|
|
3700
|
+
nowrap` on body cells — otherwise wrap / clamp would never break. */
|
|
3701
|
+
.juno-table td.juno-table__truncate {
|
|
3702
|
+
max-inline-size: var(--juno-cell-max, 240px);
|
|
3703
|
+
overflow: hidden;
|
|
3704
|
+
text-overflow: ellipsis;
|
|
3705
|
+
white-space: nowrap;
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
/* Line-clamp must live on a block child: a <td> stays `table-cell` and ignores
|
|
3709
|
+
`display: -webkit-box`, so clamping it leaks the overflowing line. The td just
|
|
3710
|
+
sets the width + wrap; the inner element does the clamp. */
|
|
3711
|
+
.juno-table td.juno-table__clamp {
|
|
3712
|
+
max-inline-size: var(--juno-cell-max, 240px);
|
|
3713
|
+
white-space: normal;
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
.juno-table__clamp-text {
|
|
3717
|
+
display: -webkit-box;
|
|
3718
|
+
-webkit-line-clamp: 2;
|
|
3719
|
+
line-clamp: 2;
|
|
3720
|
+
-webkit-box-orient: vertical;
|
|
3721
|
+
overflow: hidden;
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
.juno-table td.juno-table__wrap {
|
|
3725
|
+
max-inline-size: var(--juno-cell-max, 240px);
|
|
3726
|
+
white-space: normal;
|
|
3727
|
+
overflow-wrap: anywhere;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
/* ── selection column (checkbox) ──────────────────────────────────────── */
|
|
3731
|
+
.juno-table th.juno-table__check,
|
|
3732
|
+
.juno-table td.juno-table__check {
|
|
3733
|
+
inline-size: 44px;
|
|
3734
|
+
text-align: center;
|
|
3735
|
+
padding-inline: var(--juno-space-16) var(--juno-space-8);
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
/* ── editable cells (app swaps the mark for an input on edit) ──────────── */
|
|
3739
|
+
.juno-table td.juno-table__editable { cursor: cell; }
|
|
3740
|
+
|
|
3741
|
+
.juno-table td.juno-table__editable:hover {
|
|
3742
|
+
background: color-mix(in srgb, var(--juno-active) 8%, transparent);
|
|
3743
|
+
box-shadow: inset 0 0 0 var(--juno-border-width-1) var(--juno-active);
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
/* the dashed underline that marks a value as editable */
|
|
3747
|
+
.juno-table__mark {
|
|
3748
|
+
border-block-end: var(--juno-border-width-1) dashed color-mix(in srgb, var(--juno-label) 50%, transparent);
|
|
3749
|
+
padding-block-end: 1px;
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
/* the inline editor — a compact input that fills the cell */
|
|
3753
|
+
.juno-table__edit-input {
|
|
3754
|
+
inline-size: 100%;
|
|
3755
|
+
background: var(--juno-control-surface);
|
|
3756
|
+
border: var(--juno-border-width-1) solid var(--juno-active);
|
|
3757
|
+
border-radius: var(--juno-radius-3);
|
|
3758
|
+
padding: var(--juno-space-2) var(--juno-space-8);
|
|
3759
|
+
font-family: var(--juno-font-family-mono);
|
|
3760
|
+
font-size: var(--juno-font-size-13);
|
|
3761
|
+
color: var(--juno-data);
|
|
3762
|
+
text-align: inherit;
|
|
3763
|
+
outline: none;
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
/* ── toolbar / footer / bulk bar (framing rows) ───────────────────────── */
|
|
3767
|
+
.juno-table__toolbar,
|
|
3768
|
+
.juno-table__foot {
|
|
3769
|
+
display: flex;
|
|
3770
|
+
align-items: center;
|
|
3771
|
+
gap: var(--juno-space-12);
|
|
3772
|
+
flex-wrap: wrap;
|
|
3773
|
+
padding: var(--juno-space-12) var(--juno-space-16);
|
|
3774
|
+
background: var(--juno-s1);
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
.juno-table__toolbar { border-block-end: var(--juno-border-width-1) solid var(--juno-border); }
|
|
3778
|
+
.juno-table__foot { border-block-start: var(--juno-border-width-1) solid var(--juno-border); }
|
|
3779
|
+
|
|
3780
|
+
/* selection / bulk-action bar — shown by the app while rows are selected.
|
|
3781
|
+
The [hidden] override is required: a class `display` beats the UA [hidden]. */
|
|
3782
|
+
.juno-table__bulk {
|
|
3783
|
+
display: flex;
|
|
3784
|
+
align-items: center;
|
|
3785
|
+
gap: var(--juno-space-16);
|
|
3786
|
+
padding: var(--juno-space-10) var(--juno-space-16);
|
|
3787
|
+
background: color-mix(in srgb, var(--juno-active) 10%, transparent);
|
|
3788
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-active);
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
.juno-table__bulk[hidden] { display: none; }
|
|
3792
|
+
|
|
3793
|
+
.juno-table__bulk-count {
|
|
3794
|
+
font-family: var(--juno-font-family-mono);
|
|
3795
|
+
font-weight: var(--juno-font-weight-bold);
|
|
3796
|
+
font-size: var(--juno-font-size-12);
|
|
3797
|
+
color: var(--juno-active);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
/* ── skeleton + empty states ──────────────────────────────────────────── */
|
|
3801
|
+
.juno-table__skeleton {
|
|
3802
|
+
block-size: var(--juno-space-10);
|
|
3803
|
+
border-radius: var(--juno-radius-3);
|
|
3804
|
+
background: linear-gradient(
|
|
3805
|
+
90deg,
|
|
3806
|
+
var(--juno-s2) 0%,
|
|
3807
|
+
var(--juno-s3) 50%,
|
|
3808
|
+
var(--juno-s2) 100%
|
|
3809
|
+
);
|
|
3810
|
+
background-size: 200% 100%;
|
|
3811
|
+
animation: juno-table-shimmer 1.2s ease-in-out infinite;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
@keyframes juno-table-shimmer {
|
|
3815
|
+
from { background-position: 200% 0; }
|
|
3816
|
+
to { background-position: -200% 0; }
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
.juno-table__empty {
|
|
3820
|
+
display: flex;
|
|
3821
|
+
flex-direction: column;
|
|
3822
|
+
align-items: center;
|
|
3823
|
+
justify-content: center;
|
|
3824
|
+
gap: var(--juno-space-12);
|
|
3825
|
+
padding: var(--juno-space-56) var(--juno-space-24);
|
|
3826
|
+
text-align: center;
|
|
3827
|
+
color: var(--juno-label);
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
.juno-table__empty-icon {
|
|
3831
|
+
display: flex;
|
|
3832
|
+
align-items: center;
|
|
3833
|
+
justify-content: center;
|
|
3834
|
+
inline-size: var(--juno-space-40);
|
|
3835
|
+
block-size: var(--juno-space-40);
|
|
3836
|
+
border-radius: var(--juno-radius-8);
|
|
3837
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge);
|
|
3838
|
+
color: var(--juno-muted);
|
|
3839
|
+
font-family: var(--juno-font-family-mono);
|
|
3840
|
+
font-size: var(--juno-font-size-18);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3844
|
+
.juno-table__skeleton { animation: none; }
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
/* ── stacked mode (opt-in) — card rows for phone-width containers ────────
|
|
3848
|
+
Add `.juno-table--stack` on the table and `data-label="HEADER"` on each
|
|
3849
|
+
td. In a container narrower than 480px the grid flips: the header row
|
|
3850
|
+
hides, every row becomes a bordered card, every cell a label/value pair
|
|
3851
|
+
(::before renders the data-label). Container-driven, so it needs an
|
|
3852
|
+
ancestor with `container-type: inline-size` — usually the
|
|
3853
|
+
.juno-table-scroll wrapper. Semantics stay a real <table>; only the
|
|
3854
|
+
display changes.
|
|
3855
|
+
Usage:
|
|
3856
|
+
<div class="juno-table-scroll" style="container-type:inline-size">
|
|
3857
|
+
<table class="juno-table juno-table--stack">
|
|
3858
|
+
<tbody><tr>
|
|
3859
|
+
<td data-label="FLIGHT" class="juno-table__id">JU-204</td>
|
|
3860
|
+
<td data-label="ALT" class="juno-table__num">37,000</td>
|
|
3861
|
+
</tr></tbody> */
|
|
3862
|
+
@container (max-width: 480px) {
|
|
3863
|
+
.juno-table--stack thead { display: none; }
|
|
3864
|
+
|
|
3865
|
+
.juno-table--stack,
|
|
3866
|
+
.juno-table--stack tbody,
|
|
3867
|
+
.juno-table--stack tbody tr { display: block; }
|
|
3868
|
+
|
|
3869
|
+
.juno-table--stack tbody tr {
|
|
3870
|
+
margin-block-end: var(--juno-space-12);
|
|
3871
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
3872
|
+
border-radius: var(--juno-radius-5);
|
|
3873
|
+
overflow: hidden;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
.juno-table--stack tbody td {
|
|
3877
|
+
display: flex;
|
|
3878
|
+
align-items: center;
|
|
3879
|
+
justify-content: space-between;
|
|
3880
|
+
gap: var(--juno-space-16);
|
|
3881
|
+
padding: var(--juno-space-8) var(--juno-space-16);
|
|
3882
|
+
border-block-end: none;
|
|
3883
|
+
white-space: normal;
|
|
3884
|
+
text-align: end;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
/* the column header, rebuilt per cell from data-label */
|
|
3888
|
+
.juno-table--stack tbody td::before {
|
|
3889
|
+
content: attr(data-label);
|
|
3890
|
+
flex-shrink: 0;
|
|
3891
|
+
font-family: var(--juno-font-family-sans);
|
|
3892
|
+
font-size: var(--juno-font-size-10);
|
|
3893
|
+
font-weight: var(--juno-font-weight-medium);
|
|
3894
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
3895
|
+
text-transform: uppercase;
|
|
3896
|
+
text-align: start;
|
|
3897
|
+
color: var(--juno-label);
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
/* the selection rail moves from the first cell to the card edge */
|
|
3901
|
+
.juno-table--stack tbody td:first-child { border-inline-start: none; }
|
|
3902
|
+
|
|
3903
|
+
.juno-table--stack tbody tr[aria-selected='true'] {
|
|
3904
|
+
border-inline-start: var(--juno-border-width-3) solid var(--juno-active);
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
/* no hover on most narrow devices — keep row actions visible */
|
|
3908
|
+
.juno-table--stack .juno-table__actions { opacity: 1; }
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3912
|
+
* Component — Tabs
|
|
3913
|
+
* A horizontal tablist with an active-cyan underline indicator; the
|
|
3914
|
+
* selected tab shows its panel. junoui ships the look + the ARIA
|
|
3915
|
+
* contract; the app owns switching (aria-selected, panel visibility,
|
|
3916
|
+
* arrow-key roving focus).
|
|
3917
|
+
* Usage:
|
|
3918
|
+
* <div class="juno-tabs">
|
|
3919
|
+
* <div class="juno-tabs__list" role="tablist" aria-label="Service">
|
|
3920
|
+
* <button class="juno-tabs__tab" role="tab" id="t1"
|
|
3921
|
+
* aria-controls="p1" aria-selected="true">OVERVIEW</button>
|
|
3922
|
+
* <button class="juno-tabs__tab" role="tab" id="t2"
|
|
3923
|
+
* aria-controls="p2" aria-selected="false">METRICS</button>
|
|
3924
|
+
* </div>
|
|
3925
|
+
* <div class="juno-tabs__panel" role="tabpanel" id="p1" aria-labelledby="t1">…</div>
|
|
3926
|
+
* <div class="juno-tabs__panel" role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</div>
|
|
3927
|
+
* </div>
|
|
3928
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3929
|
+
|
|
3930
|
+
.juno-tabs__list {
|
|
3931
|
+
display: flex;
|
|
3932
|
+
gap: var(--juno-space-2);
|
|
3933
|
+
border-block-end: var(--juno-border-width-1) solid var(--juno-border);
|
|
3934
|
+
|
|
3935
|
+
/* tabs never wrap — when space runs out (phone widths) the strip
|
|
3936
|
+
scrolls sideways instead, keeping every tab reachable */
|
|
3937
|
+
overflow: auto hidden;
|
|
3938
|
+
overscroll-behavior-inline: contain;
|
|
3939
|
+
scrollbar-width: none;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
.juno-tabs__tab {
|
|
3943
|
+
appearance: none;
|
|
3944
|
+
margin: 0;
|
|
3945
|
+
margin-block-end: calc(-1 * var(--juno-border-width-1));
|
|
3946
|
+
padding: var(--juno-space-10) var(--juno-space-16);
|
|
3947
|
+
background: transparent;
|
|
3948
|
+
border: none;
|
|
3949
|
+
border-block-end: var(--juno-border-width-2) solid transparent;
|
|
3950
|
+
font-family: var(--juno-font-family-sans);
|
|
3951
|
+
font-size: var(--juno-font-size-13);
|
|
3952
|
+
font-weight: var(--juno-font-weight-medium);
|
|
3953
|
+
letter-spacing: var(--juno-font-tracking-label);
|
|
3954
|
+
color: var(--juno-label);
|
|
3955
|
+
white-space: nowrap;
|
|
3956
|
+
cursor: pointer;
|
|
3957
|
+
transition: color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
.juno-tabs__tab:hover { color: var(--juno-data); }
|
|
3961
|
+
|
|
3962
|
+
.juno-tabs__tab[aria-selected='true'] {
|
|
3963
|
+
color: var(--juno-active);
|
|
3964
|
+
border-block-end-color: var(--juno-active);
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
.juno-tabs__tab:disabled {
|
|
3968
|
+
opacity: var(--juno-opacity-disabled);
|
|
3969
|
+
cursor: not-allowed;
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
.juno-tabs__panel {
|
|
3973
|
+
padding-block-start: var(--juno-space-20);
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
.juno-tabs__panel[hidden] { display: none; }
|
|
3977
|
+
|
|
3978
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
3979
|
+
* Component — Thumb (media thumbnail + unavailable placeholder)
|
|
3980
|
+
* A media frame that never shows the browser's broken-image glyph:
|
|
3981
|
+
* a muted placeholder (icon on s2) sits under the media; the image
|
|
3982
|
+
* covers it while present. Loading state is .juno-skeleton's job —
|
|
3983
|
+
* this is for media that is missing or failed.
|
|
3984
|
+
* A missing thumb is not a warning: the placeholder stays neutral.
|
|
3985
|
+
* Usage:
|
|
3986
|
+
* <figure class="juno-thumb" style="aspect-ratio: 1">
|
|
3987
|
+
* <img src="…" alt="Sunset clip" onerror="this.remove()" />
|
|
3988
|
+
* </figure>
|
|
3989
|
+
* <!-- known-missing: ship no <img>; label the figure instead -->
|
|
3990
|
+
* <figure class="juno-thumb juno-thumb--video" role="img" aria-label="Preview unavailable"></figure>
|
|
3991
|
+
* The onerror one-liner is the whole JS contract (optional, stateless):
|
|
3992
|
+
* a broken image removes itself and the placeholder underneath shows.
|
|
3993
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
3994
|
+
|
|
3995
|
+
.juno-thumb {
|
|
3996
|
+
position: relative;
|
|
3997
|
+
display: flex;
|
|
3998
|
+
flex-direction: column;
|
|
3999
|
+
align-items: center;
|
|
4000
|
+
justify-content: center;
|
|
4001
|
+
gap: var(--juno-space-4);
|
|
4002
|
+
margin: 0;
|
|
4003
|
+
overflow: hidden;
|
|
4004
|
+
background: var(--juno-s2);
|
|
4005
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
4006
|
+
border-radius: var(--juno-radius-3);
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
/* placeholder glyph (Phosphor "images", masked so it takes any color) */
|
|
4010
|
+
.juno-thumb::before {
|
|
4011
|
+
--juno-thumb-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath 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'/%3E%3C/svg%3E");
|
|
4012
|
+
|
|
4013
|
+
content: '';
|
|
4014
|
+
inline-size: 28%;
|
|
4015
|
+
max-inline-size: var(--juno-space-32);
|
|
4016
|
+
aspect-ratio: 1;
|
|
4017
|
+
background: var(--juno-muted);
|
|
4018
|
+
-webkit-mask: var(--juno-thumb-glyph) center / contain no-repeat;
|
|
4019
|
+
mask: var(--juno-thumb-glyph) center / contain no-repeat;
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
/* video flavor — play glyph */
|
|
4023
|
+
.juno-thumb--video::before {
|
|
4024
|
+
--juno-thumb-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath 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'/%3E%3C/svg%3E");
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
/* optional text under the glyph (keeps color from being the only signal) */
|
|
4028
|
+
.juno-thumb__label {
|
|
4029
|
+
font-family: var(--juno-font-family-sans);
|
|
4030
|
+
font-size: var(--juno-font-size-10);
|
|
4031
|
+
font-weight: var(--juno-font-weight-bold);
|
|
4032
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
4033
|
+
text-transform: uppercase;
|
|
4034
|
+
color: var(--juno-muted);
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
/* the media covers the frame; while present the placeholder sits hidden
|
|
4038
|
+
underneath. On error the app's onerror removes the element. */
|
|
4039
|
+
.juno-thumb > img,
|
|
4040
|
+
.juno-thumb > video {
|
|
4041
|
+
position: absolute;
|
|
4042
|
+
inset: 0;
|
|
4043
|
+
inline-size: 100%;
|
|
4044
|
+
block-size: 100%;
|
|
4045
|
+
object-fit: cover;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
4049
|
+
* Component — Toast / snackbar
|
|
4050
|
+
* A transient, floating notification. Same role-rail language as the
|
|
4051
|
+
* alert, but elevated (shadow) and stacked in a fixed corner on the
|
|
4052
|
+
* reserved alert layer (--juno-z-alert, above modals). junoui ships the
|
|
4053
|
+
* look + enter/exit motion; the app owns creation, auto-dismiss timing,
|
|
4054
|
+
* and live-region announcement.
|
|
4055
|
+
* Usage:
|
|
4056
|
+
* <div class="juno-toast-stack" id="toasts" aria-live="polite"></div>
|
|
4057
|
+
* // app appends:
|
|
4058
|
+
* <div class="juno-toast juno--nominal" role="status">
|
|
4059
|
+
* <span class="juno-toast__icon" aria-hidden="true">✓</span>
|
|
4060
|
+
* <span class="juno-toast__text">Build promoted.</span>
|
|
4061
|
+
* <button class="juno-toast__close" aria-label="Dismiss">✕</button>
|
|
4062
|
+
* </div>
|
|
4063
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
4064
|
+
|
|
4065
|
+
.juno-toast-stack {
|
|
4066
|
+
position: fixed;
|
|
4067
|
+
z-index: var(--juno-z-alert);
|
|
4068
|
+
inset-block-end: var(--juno-space-24);
|
|
4069
|
+
inset-inline-end: var(--juno-space-24);
|
|
4070
|
+
display: flex;
|
|
4071
|
+
flex-direction: column;
|
|
4072
|
+
gap: var(--juno-space-12);
|
|
4073
|
+
inline-size: min(360px, calc(100vw - var(--juno-space-32)));
|
|
4074
|
+
pointer-events: none;
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
/* placements */
|
|
4078
|
+
.juno-toast-stack--top { inset-block: var(--juno-space-24) auto; }
|
|
4079
|
+
.juno-toast-stack--start { inset-inline: var(--juno-space-24) auto; }
|
|
4080
|
+
|
|
4081
|
+
.juno-toast {
|
|
4082
|
+
--juno-role: var(--juno-active);
|
|
4083
|
+
|
|
4084
|
+
pointer-events: auto;
|
|
4085
|
+
display: flex;
|
|
4086
|
+
align-items: center;
|
|
4087
|
+
gap: var(--juno-space-12);
|
|
4088
|
+
padding: var(--juno-space-12) var(--juno-space-16);
|
|
4089
|
+
background: var(--juno-s2);
|
|
4090
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
4091
|
+
border-inline-start: var(--juno-border-width-3) solid var(--juno-role);
|
|
4092
|
+
border-radius: var(--juno-radius-5);
|
|
4093
|
+
box-shadow: var(--juno-shadow-2);
|
|
4094
|
+
color: var(--juno-data);
|
|
4095
|
+
transition:
|
|
4096
|
+
opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
4097
|
+
transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel);
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
/* enter (freshly inserted) + leaving (app adds the class before removal) */
|
|
4101
|
+
@starting-style {
|
|
4102
|
+
.juno-toast { opacity: 0; transform: translateX(var(--juno-space-16)); }
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
.juno-toast--leaving { opacity: 0; transform: translateX(var(--juno-space-16)); }
|
|
4106
|
+
|
|
4107
|
+
.juno-toast__icon {
|
|
4108
|
+
flex-shrink: 0;
|
|
4109
|
+
color: var(--juno-role);
|
|
4110
|
+
font-size: var(--juno-font-size-16);
|
|
4111
|
+
line-height: var(--juno-font-lineHeight-none);
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
.juno-toast__text {
|
|
4115
|
+
flex: 1;
|
|
4116
|
+
min-inline-size: 0;
|
|
4117
|
+
font-size: var(--juno-font-size-13);
|
|
4118
|
+
color: var(--juno-data);
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
.juno-toast__close {
|
|
4122
|
+
flex-shrink: 0;
|
|
4123
|
+
appearance: none;
|
|
4124
|
+
padding: 0;
|
|
4125
|
+
background: transparent;
|
|
4126
|
+
border: none;
|
|
4127
|
+
color: var(--juno-muted);
|
|
4128
|
+
font-size: var(--juno-font-size-16);
|
|
4129
|
+
line-height: var(--juno-font-lineHeight-none);
|
|
4130
|
+
cursor: pointer;
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
.juno-toast__close:hover { color: var(--juno-data); }
|
|
4134
|
+
|
|
4135
|
+
/* ── narrow viewports — snackbar style ──────────────────────────────────
|
|
4136
|
+
The corner stack goes full-width at the bottom edge (thumb reach),
|
|
4137
|
+
clear of the home indicator; toasts slide up instead of sideways. */
|
|
4138
|
+
@media (width <= 639.98px) {
|
|
4139
|
+
.juno-toast-stack {
|
|
4140
|
+
inset-inline: var(--juno-space-12);
|
|
4141
|
+
inset-block-end: calc(var(--juno-space-12) + env(safe-area-inset-bottom, 0));
|
|
4142
|
+
inline-size: auto;
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
.juno-toast-stack--top { inset-block: var(--juno-space-12) auto; }
|
|
4146
|
+
|
|
4147
|
+
@starting-style {
|
|
4148
|
+
.juno-toast { transform: translateY(var(--juno-space-16)); }
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
.juno-toast--leaving { transform: translateY(var(--juno-space-16)); }
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4155
|
+
.juno-toast { transition: none; }
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
4159
|
+
* Component — Toggle button
|
|
4160
|
+
* Airbus-style illuminated-legend pushbutton: the body stays dark; a top
|
|
4161
|
+
* strip lights (NOMINAL) and the surround glows when engaged. A real
|
|
4162
|
+
* <button aria-pressed>; the app flips aria-pressed.
|
|
4163
|
+
* Usage:
|
|
4164
|
+
* <button class="juno-toggle-btn" aria-pressed="true">
|
|
4165
|
+
* <span class="juno-toggle-btn__strip"></span>
|
|
4166
|
+
* <span class="juno-toggle-btn__row">
|
|
4167
|
+
* <span class="juno-toggle-btn__text">
|
|
4168
|
+
* <span class="juno-toggle-btn__legend">AUTO-SCALING</span>
|
|
4169
|
+
* <span class="juno-toggle-btn__desc">Scale workers with load</span>
|
|
4170
|
+
* </span>
|
|
4171
|
+
* <span class="juno-toggle-btn__status">ON</span>
|
|
4172
|
+
* </span>
|
|
4173
|
+
* </button>
|
|
4174
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
4175
|
+
|
|
4176
|
+
.juno-toggle-btn {
|
|
4177
|
+
--juno-role: var(--juno-nominal);
|
|
4178
|
+
|
|
4179
|
+
display: flex;
|
|
4180
|
+
flex-direction: column;
|
|
4181
|
+
inline-size: 100%;
|
|
4182
|
+
padding: 0;
|
|
4183
|
+
overflow: hidden;
|
|
4184
|
+
background: var(--juno-control-surface);
|
|
4185
|
+
border: var(--juno-border-width-1) solid var(--juno-control-edge-strong);
|
|
4186
|
+
border-radius: var(--juno-radius-4);
|
|
4187
|
+
text-align: start;
|
|
4188
|
+
cursor: pointer;
|
|
4189
|
+
transition:
|
|
4190
|
+
border-color var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
4191
|
+
box-shadow var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
.juno-toggle-btn[aria-pressed='true'] {
|
|
4195
|
+
border-color: var(--juno-role);
|
|
4196
|
+
box-shadow: 0 0 12px color-mix(in srgb, var(--juno-role) 20%, transparent);
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
.juno-toggle-btn__strip {
|
|
4200
|
+
block-size: var(--juno-space-4);
|
|
4201
|
+
background: var(--juno-s3);
|
|
4202
|
+
transition:
|
|
4203
|
+
background var(--juno-motion-duration-quick) var(--juno-motion-ease-standard),
|
|
4204
|
+
box-shadow var(--juno-motion-duration-quick) var(--juno-motion-ease-standard);
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
.juno-toggle-btn[aria-pressed='true'] .juno-toggle-btn__strip {
|
|
4208
|
+
background: var(--juno-role);
|
|
4209
|
+
box-shadow: 0 0 8px var(--juno-role);
|
|
4210
|
+
}
|
|
4211
|
+
|
|
4212
|
+
.juno-toggle-btn__row {
|
|
4213
|
+
display: flex;
|
|
4214
|
+
align-items: center;
|
|
4215
|
+
justify-content: space-between;
|
|
4216
|
+
gap: var(--juno-space-12);
|
|
4217
|
+
padding: var(--juno-pad-control-block) var(--juno-space-16);
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
.juno-toggle-btn__text {
|
|
4221
|
+
display: flex;
|
|
4222
|
+
flex-direction: column;
|
|
4223
|
+
gap: var(--juno-space-2);
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
.juno-toggle-btn__legend {
|
|
4227
|
+
font-family: var(--juno-font-family-mono);
|
|
4228
|
+
font-size: var(--juno-font-size-13);
|
|
4229
|
+
font-weight: var(--juno-font-weight-bold);
|
|
4230
|
+
letter-spacing: var(--juno-font-tracking-tight);
|
|
4231
|
+
color: var(--juno-data);
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
.juno-toggle-btn__desc { font-size: var(--juno-font-size-11); color: var(--juno-label); }
|
|
4235
|
+
|
|
4236
|
+
.juno-toggle-btn__status {
|
|
4237
|
+
font-family: var(--juno-font-family-mono);
|
|
4238
|
+
font-size: var(--juno-font-size-10);
|
|
4239
|
+
letter-spacing: var(--juno-font-tracking-caps);
|
|
4240
|
+
color: var(--juno-muted);
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
.juno-toggle-btn[aria-pressed='true'] .juno-toggle-btn__status { color: var(--juno-role); }
|
|
4244
|
+
|
|
4245
|
+
.juno-toggle-btn:disabled {
|
|
4246
|
+
opacity: var(--juno-opacity-disabled);
|
|
4247
|
+
cursor: not-allowed;
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
4251
|
+
* Component — Tooltip
|
|
4252
|
+
* Transient hover/focus label. Pure CSS reveal (no JS): the bubble shows
|
|
4253
|
+
* on hover or keyboard focus of the wrapper. Four placements with an arrow.
|
|
4254
|
+
* Usage:
|
|
4255
|
+
* <span class="juno-tooltip">
|
|
4256
|
+
* <button tabindex="0">RPS</button>
|
|
4257
|
+
* <span class="juno-tooltip__bubble" role="tooltip">
|
|
4258
|
+
* Requests / sec · 248
|
|
4259
|
+
* <span class="juno-tooltip__arrow"></span>
|
|
4260
|
+
* </span>
|
|
4261
|
+
* </span>
|
|
4262
|
+
* Default placement is top; add --right / --bottom / --left on the bubble.
|
|
4263
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
4264
|
+
|
|
4265
|
+
.juno-tooltip {
|
|
4266
|
+
position: relative;
|
|
4267
|
+
display: inline-flex;
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
.juno-tooltip__bubble {
|
|
4271
|
+
position: absolute;
|
|
4272
|
+
z-index: var(--juno-z-anchored);
|
|
4273
|
+
inset-block-end: calc(100% + var(--juno-space-8));
|
|
4274
|
+
inset-inline-start: 50%;
|
|
4275
|
+
transform: translateX(-50%) translateY(var(--juno-space-4));
|
|
4276
|
+
white-space: nowrap;
|
|
4277
|
+
background: var(--juno-s3);
|
|
4278
|
+
color: var(--juno-data);
|
|
4279
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
4280
|
+
border-radius: var(--juno-radius-4);
|
|
4281
|
+
box-shadow: var(--juno-shadow-2);
|
|
4282
|
+
padding: var(--juno-space-4) var(--juno-space-10);
|
|
4283
|
+
font-family: var(--juno-font-family-mono);
|
|
4284
|
+
font-size: var(--juno-font-size-11);
|
|
4285
|
+
pointer-events: none;
|
|
4286
|
+
opacity: 0;
|
|
4287
|
+
visibility: hidden;
|
|
4288
|
+
transition:
|
|
4289
|
+
opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
4290
|
+
transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
4291
|
+
visibility var(--juno-motion-duration-quick);
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
.juno-tooltip:hover .juno-tooltip__bubble,
|
|
4295
|
+
.juno-tooltip:focus-within .juno-tooltip__bubble {
|
|
4296
|
+
opacity: 1;
|
|
4297
|
+
visibility: visible;
|
|
4298
|
+
transform: translateX(-50%) translateY(0);
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
/* ── top-layer mode ─────────────────────────────────────────────────────
|
|
4302
|
+
The pure-CSS reveal above is clipped if an ancestor sets overflow/clip.
|
|
4303
|
+
Promote the bubble to a `popover="hint"` (a stateless enhancer toggles it
|
|
4304
|
+
on hover/focus and points position-anchor at the trigger) and it renders in
|
|
4305
|
+
the TOP LAYER — never clipped. CSS anchor positioning replaces the
|
|
4306
|
+
wrapper-relative offsets; the arrow + placement classes still apply. */
|
|
4307
|
+
.juno-tooltip__bubble[popover] {
|
|
4308
|
+
position: fixed;
|
|
4309
|
+
margin: 0;
|
|
4310
|
+
inset: auto;
|
|
4311
|
+
position-area: block-start;
|
|
4312
|
+
position-try-fallbacks: flip-block;
|
|
4313
|
+
transform: translateY(var(--juno-space-4));
|
|
4314
|
+
transition:
|
|
4315
|
+
opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
4316
|
+
transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
|
|
4317
|
+
overlay var(--juno-motion-duration-quick) allow-discrete,
|
|
4318
|
+
display var(--juno-motion-duration-quick) allow-discrete;
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
.juno-tooltip__bubble--bottom[popover] {
|
|
4322
|
+
position-area: block-end;
|
|
4323
|
+
transform: translateY(calc(-1 * var(--juno-space-4)));
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
.juno-tooltip__bubble--right[popover] {
|
|
4327
|
+
position-area: inline-end;
|
|
4328
|
+
transform: translateX(calc(-1 * var(--juno-space-4)));
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
.juno-tooltip__bubble--left[popover] {
|
|
4332
|
+
position-area: inline-start;
|
|
4333
|
+
transform: translateX(var(--juno-space-4));
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
.juno-tooltip__bubble[popover]:popover-open {
|
|
4337
|
+
opacity: 1;
|
|
4338
|
+
visibility: visible;
|
|
4339
|
+
transform: none;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
@starting-style {
|
|
4343
|
+
.juno-tooltip__bubble[popover]:popover-open { opacity: 0; }
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
/* arrow — a rotated square straddling the bubble edge nearest the target.
|
|
4347
|
+
The bubble overlaps its inner half, leaving a clean notch. */
|
|
4348
|
+
.juno-tooltip__arrow {
|
|
4349
|
+
position: absolute;
|
|
4350
|
+
inline-size: var(--juno-space-8);
|
|
4351
|
+
block-size: var(--juno-space-8);
|
|
4352
|
+
background: var(--juno-s3);
|
|
4353
|
+
border: var(--juno-border-width-1) solid var(--juno-border);
|
|
4354
|
+
transform: rotate(45deg);
|
|
4355
|
+
inset-block-start: calc(100% - var(--juno-space-4));
|
|
4356
|
+
inset-inline-start: calc(50% - var(--juno-space-4));
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
/* ── placements (set on the bubble) ───────────────────────────────────── */
|
|
4360
|
+
.juno-tooltip__bubble--bottom {
|
|
4361
|
+
inset-block: calc(100% + var(--juno-space-8)) auto;
|
|
4362
|
+
transform: translateX(-50%) translateY(calc(-1 * var(--juno-space-4)));
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
.juno-tooltip:hover .juno-tooltip__bubble--bottom,
|
|
4366
|
+
.juno-tooltip:focus-within .juno-tooltip__bubble--bottom {
|
|
4367
|
+
transform: translateX(-50%) translateY(0);
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4370
|
+
.juno-tooltip__bubble--bottom .juno-tooltip__arrow {
|
|
4371
|
+
inset-block-start: calc(-1 * var(--juno-space-4));
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
.juno-tooltip__bubble--right {
|
|
4375
|
+
inset-block: 50% auto;
|
|
4376
|
+
inset-inline-start: calc(100% + var(--juno-space-8));
|
|
4377
|
+
transform: translateY(-50%) translateX(calc(-1 * var(--juno-space-4)));
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
.juno-tooltip:hover .juno-tooltip__bubble--right,
|
|
4381
|
+
.juno-tooltip:focus-within .juno-tooltip__bubble--right {
|
|
4382
|
+
transform: translateY(-50%) translateX(0);
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
.juno-tooltip__bubble--right .juno-tooltip__arrow {
|
|
4386
|
+
inset-block-start: calc(50% - var(--juno-space-4));
|
|
4387
|
+
inset-inline-start: calc(-1 * var(--juno-space-4));
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4390
|
+
.juno-tooltip__bubble--left {
|
|
4391
|
+
inset-block: 50% auto;
|
|
4392
|
+
inset-inline: auto calc(100% + var(--juno-space-8));
|
|
4393
|
+
transform: translateY(-50%) translateX(var(--juno-space-4));
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
.juno-tooltip:hover .juno-tooltip__bubble--left,
|
|
4397
|
+
.juno-tooltip:focus-within .juno-tooltip__bubble--left {
|
|
4398
|
+
transform: translateY(-50%) translateX(0);
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
.juno-tooltip__bubble--left .juno-tooltip__arrow {
|
|
4402
|
+
inset-block-start: calc(50% - var(--juno-space-4));
|
|
4403
|
+
inset-inline-start: calc(100% - var(--juno-space-4));
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
/* ════════════════════════════════════════════════════════════════════
|
|
4407
|
+
* junoui — utilities layer
|
|
4408
|
+
*
|
|
4409
|
+
* Role helpers set a single custom property --juno-role. Components read
|
|
4410
|
+
* var(--juno-role), so one class (.juno--warning) re-colors a badge, dot,
|
|
4411
|
+
* button, card accent, or readout identically. Color = status, always.
|
|
4412
|
+
* ════════════════════════════════════════════════════════════════════ */
|
|
4413
|
+
|
|
4414
|
+
.juno--nominal { --juno-role: var(--juno-nominal); }
|
|
4415
|
+
.juno--active { --juno-role: var(--juno-active); }
|
|
4416
|
+
.juno--target { --juno-role: var(--juno-target); }
|
|
4417
|
+
.juno--caution { --juno-role: var(--juno-caution); }
|
|
4418
|
+
.juno--warning { --juno-role: var(--juno-warning); }
|
|
4419
|
+
.juno--muted { --juno-role: var(--juno-muted); }
|
|
4420
|
+
|
|
4421
|
+
/* Text color roles */
|
|
4422
|
+
.juno-text-nominal { color: var(--juno-nominal); }
|
|
4423
|
+
.juno-text-active { color: var(--juno-active); }
|
|
4424
|
+
.juno-text-target { color: var(--juno-target); }
|
|
4425
|
+
.juno-text-caution { color: var(--juno-caution); }
|
|
4426
|
+
.juno-text-warning { color: var(--juno-warning); }
|
|
4427
|
+
.juno-text-data { color: var(--juno-data); }
|
|
4428
|
+
.juno-text-label { color: var(--juno-label); }
|
|
4429
|
+
.juno-text-muted { color: var(--juno-muted); }
|
|
4430
|
+
|
|
4431
|
+
/* Surface backgrounds (depth hierarchy) */
|
|
4432
|
+
.juno-bg-s0 { background: var(--juno-s0); }
|
|
4433
|
+
.juno-bg-s1 { background: var(--juno-s1); }
|
|
4434
|
+
.juno-bg-s2 { background: var(--juno-s2); }
|
|
4435
|
+
.juno-bg-s3 { background: var(--juno-s3); }
|
|
4436
|
+
|
|
4437
|
+
/* Typography helpers */
|
|
4438
|
+
.juno-mono { font-family: var(--juno-font-family-mono); }
|
|
4439
|
+
|
|
4440
|
+
.juno-value {
|
|
4441
|
+
font-family: var(--juno-font-family-mono);
|
|
4442
|
+
font-weight: var(--juno-font-weight-bold);
|
|
4443
|
+
color: var(--juno-data);
|
|
4444
|
+
line-height: var(--juno-font-lineHeight-none);
|
|
4445
|
+
font-variant-numeric: tabular-nums;
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4448
|
+
.juno-label {
|
|
4449
|
+
font-size: var(--juno-font-size-13);
|
|
4450
|
+
color: var(--juno-label);
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
/* Section eyebrow / micro label — 10px uppercase, widely tracked */
|
|
4454
|
+
.juno-eyebrow {
|
|
4455
|
+
font-size: var(--juno-font-size-10);
|
|
4456
|
+
letter-spacing: var(--juno-font-tracking-widest);
|
|
4457
|
+
text-transform: uppercase;
|
|
4458
|
+
color: var(--juno-label);
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
.juno-heading {
|
|
4462
|
+
font-family: var(--juno-font-family-sans);
|
|
4463
|
+
font-weight: var(--juno-font-weight-bold);
|
|
4464
|
+
font-size: var(--juno-font-size-18);
|
|
4465
|
+
letter-spacing: var(--juno-font-tracking-normal);
|
|
4466
|
+
text-transform: uppercase;
|
|
4467
|
+
color: var(--juno-data);
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
/* Visually hidden, still announced — legends, skip links, icon-only labels */
|
|
4471
|
+
.juno-sr-only {
|
|
4472
|
+
position: absolute;
|
|
4473
|
+
inline-size: 1px;
|
|
4474
|
+
block-size: 1px;
|
|
4475
|
+
padding: 0;
|
|
4476
|
+
margin: -1px;
|
|
4477
|
+
overflow: hidden;
|
|
4478
|
+
clip-path: inset(50%);
|
|
4479
|
+
white-space: nowrap;
|
|
4480
|
+
border: 0;
|
|
4481
|
+
}
|