@shabboo/kit 0.0.0-stage → 0.1.1
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 +13 -0
- package/DESIGN.md +128 -0
- package/LICENSE +21 -0
- package/README.md +75 -2
- package/dist/motion.d.ts +14 -0
- package/dist/motion.js +23 -0
- package/dist/ui/icons.d.ts +24 -0
- package/dist/ui/icons.js +23 -0
- package/package.json +83 -3
- package/src/motion.tsx +33 -0
- package/src/styles/fonts.css +8 -0
- package/src/styles/print.css +36 -0
- package/src/styles/styles.css +246 -0
- package/src/styles/tokens.css +303 -0
- package/src/ui/Badge.astro +95 -0
- package/src/ui/Breadcrumbs.astro +28 -0
- package/src/ui/Button.astro +190 -0
- package/src/ui/Callout.astro +112 -0
- package/src/ui/Card.astro +79 -0
- package/src/ui/Dialog.astro +168 -0
- package/src/ui/Disclosure.astro +197 -0
- package/src/ui/EntryItem.astro +43 -0
- package/src/ui/Field.astro +167 -0
- package/src/ui/Figure.astro +47 -0
- package/src/ui/HCard.astro +86 -0
- package/src/ui/Icon.astro +59 -0
- package/src/ui/Kbd.astro +35 -0
- package/src/ui/Label.astro +15 -0
- package/src/ui/Pagination.astro +124 -0
- package/src/ui/PixelFrame.astro +121 -0
- package/src/ui/Quote.astro +49 -0
- package/src/ui/Tag.astro +91 -0
- package/src/ui/TagList.astro +68 -0
- package/src/ui/Timeline.astro +23 -0
- package/src/ui/TimelineItem.astro +105 -0
- package/src/ui/icons.ts +27 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
@import "./tokens.css";
|
|
2
|
+
@import "./print.css";
|
|
3
|
+
|
|
4
|
+
@layer reset, base, prose, utilities;
|
|
5
|
+
|
|
6
|
+
@layer reset {
|
|
7
|
+
*,
|
|
8
|
+
*::before,
|
|
9
|
+
*::after {
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
}
|
|
12
|
+
* {
|
|
13
|
+
margin: 0;
|
|
14
|
+
}
|
|
15
|
+
html {
|
|
16
|
+
-webkit-text-size-adjust: none;
|
|
17
|
+
text-size-adjust: none;
|
|
18
|
+
}
|
|
19
|
+
img,
|
|
20
|
+
svg,
|
|
21
|
+
video,
|
|
22
|
+
picture {
|
|
23
|
+
display: block;
|
|
24
|
+
max-inline-size: 100%;
|
|
25
|
+
block-size: auto;
|
|
26
|
+
}
|
|
27
|
+
input,
|
|
28
|
+
button,
|
|
29
|
+
textarea,
|
|
30
|
+
select {
|
|
31
|
+
font: inherit;
|
|
32
|
+
}
|
|
33
|
+
p,
|
|
34
|
+
h1,
|
|
35
|
+
h2,
|
|
36
|
+
h3,
|
|
37
|
+
h4 {
|
|
38
|
+
overflow-wrap: break-word;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@layer base {
|
|
43
|
+
html {
|
|
44
|
+
background: var(--surface);
|
|
45
|
+
color: var(--text);
|
|
46
|
+
font-family: var(--font-serif);
|
|
47
|
+
font-size: 100%;
|
|
48
|
+
scroll-behavior: smooth;
|
|
49
|
+
}
|
|
50
|
+
@media (prefers-reduced-motion: reduce) {
|
|
51
|
+
html {
|
|
52
|
+
scroll-behavior: auto;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
:lang(fa),
|
|
56
|
+
:lang(ar) {
|
|
57
|
+
font-family: var(--font-arabic);
|
|
58
|
+
/* Vazirmatn has no italic; never fake-slant Arabic script */
|
|
59
|
+
font-synthesis-style: none;
|
|
60
|
+
}
|
|
61
|
+
body {
|
|
62
|
+
min-block-size: 100svh;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
font-size: var(--step-0);
|
|
66
|
+
line-height: var(--leading);
|
|
67
|
+
font-optical-sizing: auto;
|
|
68
|
+
-webkit-font-smoothing: antialiased;
|
|
69
|
+
}
|
|
70
|
+
/* film grain over everything, never in the way */
|
|
71
|
+
body::after {
|
|
72
|
+
content: "";
|
|
73
|
+
position: fixed;
|
|
74
|
+
inset: 0;
|
|
75
|
+
pointer-events: none;
|
|
76
|
+
z-index: var(--z-grain);
|
|
77
|
+
opacity: var(--grain-opacity);
|
|
78
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
79
|
+
}
|
|
80
|
+
main {
|
|
81
|
+
flex: 1;
|
|
82
|
+
}
|
|
83
|
+
::selection {
|
|
84
|
+
background: var(--fill-selection);
|
|
85
|
+
}
|
|
86
|
+
a {
|
|
87
|
+
color: inherit;
|
|
88
|
+
text-decoration-line: underline;
|
|
89
|
+
text-decoration-color: var(--stroke-strong);
|
|
90
|
+
text-decoration-thickness: 1px;
|
|
91
|
+
text-underline-offset: 0.2em;
|
|
92
|
+
transition:
|
|
93
|
+
color var(--dur) var(--ease),
|
|
94
|
+
text-decoration-color var(--dur) var(--ease);
|
|
95
|
+
}
|
|
96
|
+
a:hover {
|
|
97
|
+
color: var(--text-accent);
|
|
98
|
+
text-decoration-color: var(--text-accent);
|
|
99
|
+
}
|
|
100
|
+
:focus-visible {
|
|
101
|
+
outline: 2px dashed var(--focus-ring);
|
|
102
|
+
outline-offset: 3px;
|
|
103
|
+
}
|
|
104
|
+
h1,
|
|
105
|
+
h2,
|
|
106
|
+
h3,
|
|
107
|
+
h4 {
|
|
108
|
+
font-weight: 400;
|
|
109
|
+
line-height: 1.15;
|
|
110
|
+
letter-spacing: -0.01em;
|
|
111
|
+
text-wrap: balance;
|
|
112
|
+
}
|
|
113
|
+
h1 {
|
|
114
|
+
font-size: var(--step-4);
|
|
115
|
+
}
|
|
116
|
+
h2 {
|
|
117
|
+
font-size: var(--step-2);
|
|
118
|
+
}
|
|
119
|
+
h3 {
|
|
120
|
+
font-size: var(--step-1);
|
|
121
|
+
}
|
|
122
|
+
code,
|
|
123
|
+
kbd,
|
|
124
|
+
samp,
|
|
125
|
+
pre {
|
|
126
|
+
font-family: var(--font-mono);
|
|
127
|
+
font-size: 0.85em;
|
|
128
|
+
}
|
|
129
|
+
hr {
|
|
130
|
+
border: 0;
|
|
131
|
+
border-block-start: 1px dashed var(--stroke);
|
|
132
|
+
margin-block: var(--space-7);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@layer prose {
|
|
137
|
+
.prose {
|
|
138
|
+
max-inline-size: var(--measure);
|
|
139
|
+
text-wrap: pretty;
|
|
140
|
+
}
|
|
141
|
+
.prose > * + * {
|
|
142
|
+
margin-block-start: var(--space-5);
|
|
143
|
+
}
|
|
144
|
+
.prose :is(h2, h3, h4) {
|
|
145
|
+
margin-block-start: var(--space-7);
|
|
146
|
+
}
|
|
147
|
+
.prose :is(h2, h3, h4) + * {
|
|
148
|
+
margin-block-start: var(--space-3);
|
|
149
|
+
}
|
|
150
|
+
.prose :is(ul, ol) {
|
|
151
|
+
padding-inline-start: var(--space-5);
|
|
152
|
+
}
|
|
153
|
+
.prose li + li {
|
|
154
|
+
margin-block-start: var(--space-2);
|
|
155
|
+
}
|
|
156
|
+
.prose li::marker {
|
|
157
|
+
color: var(--text-accent);
|
|
158
|
+
font-family: var(--font-mono);
|
|
159
|
+
}
|
|
160
|
+
.prose blockquote {
|
|
161
|
+
border-inline-start: 2px solid var(--stroke-strong);
|
|
162
|
+
padding-inline-start: var(--space-5);
|
|
163
|
+
font-style: italic;
|
|
164
|
+
color: var(--text-muted);
|
|
165
|
+
}
|
|
166
|
+
.prose :not(pre) > code {
|
|
167
|
+
background: var(--surface-sunken);
|
|
168
|
+
padding: 0.1em 0.35em;
|
|
169
|
+
border-radius: var(--radius);
|
|
170
|
+
}
|
|
171
|
+
.prose pre {
|
|
172
|
+
padding: var(--space-4) var(--space-5);
|
|
173
|
+
overflow-x: auto;
|
|
174
|
+
border: 1px solid var(--stroke);
|
|
175
|
+
border-radius: var(--radius);
|
|
176
|
+
line-height: 1.5;
|
|
177
|
+
direction: ltr;
|
|
178
|
+
}
|
|
179
|
+
.prose img {
|
|
180
|
+
border-radius: var(--radius);
|
|
181
|
+
}
|
|
182
|
+
.prose table {
|
|
183
|
+
inline-size: 100%;
|
|
184
|
+
border-collapse: collapse;
|
|
185
|
+
font-size: var(--step--1);
|
|
186
|
+
}
|
|
187
|
+
.prose :is(th, td) {
|
|
188
|
+
border-block-end: 1px solid var(--stroke);
|
|
189
|
+
padding: var(--space-2) var(--space-3);
|
|
190
|
+
text-align: start;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@layer utilities {
|
|
195
|
+
.wrap {
|
|
196
|
+
inline-size: min(100% - 2 * var(--gutter), var(--wide));
|
|
197
|
+
margin-inline: auto;
|
|
198
|
+
}
|
|
199
|
+
.wrap-narrow {
|
|
200
|
+
inline-size: min(100% - 2 * var(--gutter), var(--measure));
|
|
201
|
+
margin-inline: auto;
|
|
202
|
+
}
|
|
203
|
+
/* the punk/terminal label used for metadata everywhere */
|
|
204
|
+
.label {
|
|
205
|
+
font-family: var(--font-mono);
|
|
206
|
+
font-size: var(--step--2);
|
|
207
|
+
letter-spacing: var(--tracking-mono);
|
|
208
|
+
text-transform: uppercase;
|
|
209
|
+
color: var(--text-muted);
|
|
210
|
+
}
|
|
211
|
+
/* letter-spacing breaks Arabic-script joining; use the Arabic face instead of mono */
|
|
212
|
+
:is(:lang(fa), :lang(ar)) .label {
|
|
213
|
+
font-family: var(--font-arabic);
|
|
214
|
+
font-size: var(--step--1);
|
|
215
|
+
letter-spacing: 0;
|
|
216
|
+
}
|
|
217
|
+
.accent {
|
|
218
|
+
color: var(--text-accent);
|
|
219
|
+
}
|
|
220
|
+
.signal {
|
|
221
|
+
color: var(--text-signal);
|
|
222
|
+
}
|
|
223
|
+
.visually-hidden {
|
|
224
|
+
position: absolute;
|
|
225
|
+
inline-size: 1px;
|
|
226
|
+
block-size: 1px;
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
clip-path: inset(50%);
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
}
|
|
231
|
+
.skip-link {
|
|
232
|
+
position: absolute;
|
|
233
|
+
inset-block-start: var(--space-2);
|
|
234
|
+
inset-inline-start: var(--space-2);
|
|
235
|
+
z-index: var(--z-skip);
|
|
236
|
+
padding: var(--space-2) var(--space-4);
|
|
237
|
+
background: var(--text);
|
|
238
|
+
color: var(--surface);
|
|
239
|
+
font-family: var(--font-mono);
|
|
240
|
+
font-size: var(--step--1);
|
|
241
|
+
transform: translateY(-200%);
|
|
242
|
+
}
|
|
243
|
+
.skip-link:focus {
|
|
244
|
+
transform: none;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shab Boo design tokens: the only place colors, fonts, sizes and motion live.
|
|
3
|
+
* Night is the default and the only theme the four sites use. Day (data-theme="light") redefines the
|
|
4
|
+
* semantic layer for the UI kit and its showcase. Dark green is the accent; violet is reserved for the matthiola.
|
|
5
|
+
* No braces inside comments here: apps/ui/src/lib/tokens.ts splits this file into blocks on them.
|
|
6
|
+
*/
|
|
7
|
+
:root {
|
|
8
|
+
/* night palette (green-tinted blacks) */
|
|
9
|
+
--ink: #090c0a;
|
|
10
|
+
--ink-2: #0f1411;
|
|
11
|
+
--ink-3: #161d18;
|
|
12
|
+
--line: #24302a;
|
|
13
|
+
--paper: #e6e4da;
|
|
14
|
+
--paper-dim: #9a9d93;
|
|
15
|
+
|
|
16
|
+
/* accent: dark green. --accent for fills/borders, --accent-text where it carries text */
|
|
17
|
+
--accent: #1f5a3d;
|
|
18
|
+
--accent-strong: #2c7a53;
|
|
19
|
+
--accent-text: #6cbf8e;
|
|
20
|
+
--accent-glow: #2c7a5333;
|
|
21
|
+
|
|
22
|
+
/* supporting colors */
|
|
23
|
+
--bloom: #b996e6; /* matthiola violet, art only */
|
|
24
|
+
--signal: #ff6b3d; /* sodium streetlight, tiny doses */
|
|
25
|
+
--selection: #2c7a5366;
|
|
26
|
+
|
|
27
|
+
/* night extras: one more ink step, faint text, washes for hover and press, the modal scrim */
|
|
28
|
+
--ink-4: #1d2620;
|
|
29
|
+
--line-dim: #1a221d;
|
|
30
|
+
--line-control: #6a7369; /* a control's edge: 3:1 on the page, a panel and a field */
|
|
31
|
+
--paper-faint: #848880;
|
|
32
|
+
--paper-off: #666b63; /* disabled text: ~3:1, readable but plainly off */
|
|
33
|
+
--wash-1: #e6e4da0d;
|
|
34
|
+
--wash-2: #e6e4da1a;
|
|
35
|
+
--scrim: #090c0acc;
|
|
36
|
+
|
|
37
|
+
/* status: error, warning, info. success is the accent. never --signal or --bloom for these */
|
|
38
|
+
--red: #f08a7e;
|
|
39
|
+
--red-wash: #f08a7e1f;
|
|
40
|
+
--amber: #e2bd5c;
|
|
41
|
+
--amber-wash: #e2bd5c1f;
|
|
42
|
+
--blue: #86b4ec;
|
|
43
|
+
--blue-wash: #86b4ec1f;
|
|
44
|
+
|
|
45
|
+
/* pixel figures: landing art only. never UI, never text */
|
|
46
|
+
--fig-turquoise: #4fbfcf;
|
|
47
|
+
--fig-turquoise-pale: #86d8e2;
|
|
48
|
+
--fig-chestnut: #9a5a2c;
|
|
49
|
+
--fig-azure: #3f7fe6;
|
|
50
|
+
--fig-ultramarine: #5b4fd0;
|
|
51
|
+
--fig-indigo: #2c3f8a;
|
|
52
|
+
--fig-navy: #1b2240;
|
|
53
|
+
--fig-bill: #4a5563;
|
|
54
|
+
--fig-straw: #d8d68f;
|
|
55
|
+
--fig-straw-shade: #a3a35a;
|
|
56
|
+
--fig-straw-deep: #7a7a3c;
|
|
57
|
+
--fig-skin: #e0a985;
|
|
58
|
+
--fig-skin-shade: #b9805f;
|
|
59
|
+
--fig-skin-deep: #8a5a42;
|
|
60
|
+
--fig-umber: #5c4030;
|
|
61
|
+
--fig-mint: #b8e6d2;
|
|
62
|
+
--fig-mint-shade: #8fc4ae;
|
|
63
|
+
--fig-denim: #4a6fae;
|
|
64
|
+
--fig-denim-shade: #304f86;
|
|
65
|
+
--fig-denim-light: #7aa0dc;
|
|
66
|
+
--fig-brass: #d6a642;
|
|
67
|
+
--fig-sock-red: #8e2f4a;
|
|
68
|
+
|
|
69
|
+
/* type */
|
|
70
|
+
--font-serif: "Newsreader Variable", "Iowan Old Style", Georgia, serif;
|
|
71
|
+
--font-mono: "JetBrains Mono Variable", ui-monospace, "SF Mono", Menlo, monospace;
|
|
72
|
+
--font-arabic: "Vazirmatn Variable", "Noto Naskh Arabic", Tahoma, sans-serif;
|
|
73
|
+
|
|
74
|
+
--step--2: 0.72rem;
|
|
75
|
+
--step--1: 0.85rem;
|
|
76
|
+
--step-0: 1.125rem;
|
|
77
|
+
--step-1: 1.35rem;
|
|
78
|
+
--step-2: 1.7rem;
|
|
79
|
+
--step-3: 2.2rem;
|
|
80
|
+
--step-4: 3rem;
|
|
81
|
+
--step-5: clamp(2.8rem, 6vw + 1rem, 5.2rem);
|
|
82
|
+
|
|
83
|
+
--leading: 1.65;
|
|
84
|
+
--tracking-mono: 0.06em;
|
|
85
|
+
|
|
86
|
+
/* space */
|
|
87
|
+
--space-1: 0.25rem;
|
|
88
|
+
--space-2: 0.5rem;
|
|
89
|
+
--space-3: 0.75rem;
|
|
90
|
+
--space-4: 1rem;
|
|
91
|
+
--space-5: 1.5rem;
|
|
92
|
+
--space-6: 2rem;
|
|
93
|
+
--space-7: 3rem;
|
|
94
|
+
--space-8: 5rem;
|
|
95
|
+
--gutter: clamp(1rem, 4vw, 2.5rem);
|
|
96
|
+
--measure: 68ch;
|
|
97
|
+
--wide: 72rem;
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* breakpoints: var() can't sit in a media query, so these are constants to copy, not tokens.
|
|
101
|
+
* sm 40rem (phone to tablet) · md 52rem (sidebar collapses) · lg 64rem · xl 80rem.
|
|
102
|
+
* Inside components prefer container queries, so a part reflows by its own width.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
/* shape */
|
|
106
|
+
--radius-sm: 2px;
|
|
107
|
+
--radius: var(--radius-sm);
|
|
108
|
+
--radius-pill: 999px; /* fully round ends: the Badge pill */
|
|
109
|
+
--border: 1px solid var(--line);
|
|
110
|
+
|
|
111
|
+
/* icons: em-based so they follow the text they sit in; a touch target is never smaller than 44px */
|
|
112
|
+
--icon-sm: 1em;
|
|
113
|
+
--icon-md: 1.25em;
|
|
114
|
+
--icon-lg: 1.75em;
|
|
115
|
+
--target-min: 2.75rem;
|
|
116
|
+
|
|
117
|
+
/* stacking: name the layer, never a number */
|
|
118
|
+
--z-base: 0;
|
|
119
|
+
--z-raised: 1;
|
|
120
|
+
--z-sticky: 10;
|
|
121
|
+
--z-overlay: 50;
|
|
122
|
+
--z-toast: 60;
|
|
123
|
+
--z-grain: 100;
|
|
124
|
+
--z-skip: 200;
|
|
125
|
+
|
|
126
|
+
/* motion: smooth. fast for small state changes, moderate for most, slow for large moves */
|
|
127
|
+
--dur-fast: 110ms;
|
|
128
|
+
--dur-moderate: 220ms;
|
|
129
|
+
--dur-slow: 400ms;
|
|
130
|
+
--dur: var(--dur-moderate);
|
|
131
|
+
--ease: cubic-bezier(0.22, 1, 0.36, 1);
|
|
132
|
+
--ease-enter: cubic-bezier(0, 0, 0.3, 1);
|
|
133
|
+
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
|
|
134
|
+
/* pixel motion: no tweening, just frames. UI kit hovers, presses, blinks and scans use these */
|
|
135
|
+
--ease-step: steps(3, end);
|
|
136
|
+
--ease-dither: steps(4, end); /* a dither in or out: four hard frames */
|
|
137
|
+
--dur-step: 180ms;
|
|
138
|
+
--blink: 1.1s;
|
|
139
|
+
--grain-opacity: 0.07;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
* semantic layer: what a color is *for*. UI components (src/ui/) use only these,
|
|
144
|
+
* so a theme swaps this block and nothing else. Apps may still use the palette above.
|
|
145
|
+
* It also answers to data-theme="dark", so a night panel can sit inside a day page.
|
|
146
|
+
*/
|
|
147
|
+
:root,
|
|
148
|
+
[data-theme="dark"] {
|
|
149
|
+
color-scheme: dark;
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* layers: the page, a panel on it, a well cut into it, and the step above a panel.
|
|
153
|
+
* A well is one step off the page toward the text: lighter at night, darker by day.
|
|
154
|
+
*/
|
|
155
|
+
--surface: var(--ink);
|
|
156
|
+
--surface-raised: var(--ink-2);
|
|
157
|
+
--surface-sunken: var(--ink-3);
|
|
158
|
+
--field: var(--ink-3);
|
|
159
|
+
--field-raised: var(--ink-4);
|
|
160
|
+
|
|
161
|
+
--text: var(--paper);
|
|
162
|
+
--text-muted: var(--paper-dim);
|
|
163
|
+
--text-helper: var(--text-muted); /* alias of muted, kept so helper text can diverge later */
|
|
164
|
+
--text-placeholder: var(--paper-faint);
|
|
165
|
+
--text-disabled: var(--paper-off);
|
|
166
|
+
--text-accent: var(--accent-text);
|
|
167
|
+
--text-signal: var(--signal);
|
|
168
|
+
--text-on-fill: var(--paper);
|
|
169
|
+
|
|
170
|
+
--fill-accent: var(--accent);
|
|
171
|
+
--fill-accent-strong: var(--accent-strong);
|
|
172
|
+
--fill-glow: var(--accent-glow);
|
|
173
|
+
--fill-disabled: var(--ink-3);
|
|
174
|
+
--fill-selection: var(--selection);
|
|
175
|
+
|
|
176
|
+
/* states: laid over whatever layer the control sits on. hover a half step, press a full one */
|
|
177
|
+
--state-hover: var(--wash-1);
|
|
178
|
+
--state-active: var(--wash-2);
|
|
179
|
+
--state-selected: var(--accent-glow);
|
|
180
|
+
|
|
181
|
+
--stroke-subtle: var(--line-dim);
|
|
182
|
+
--stroke: var(--line);
|
|
183
|
+
--stroke-strong: var(--accent-strong);
|
|
184
|
+
--stroke-control: var(--line-control);
|
|
185
|
+
--focus-ring: var(--signal);
|
|
186
|
+
--overlay: var(--scrim);
|
|
187
|
+
|
|
188
|
+
/* status: always with an icon or a word, never color alone */
|
|
189
|
+
--status-error: var(--red);
|
|
190
|
+
--status-error-fill: var(--red-wash);
|
|
191
|
+
--status-warning: var(--amber);
|
|
192
|
+
--status-warning-fill: var(--amber-wash);
|
|
193
|
+
--status-info: var(--blue);
|
|
194
|
+
--status-info-fill: var(--blue-wash);
|
|
195
|
+
--status-success: var(--accent-text);
|
|
196
|
+
--status-success-fill: var(--accent-glow);
|
|
197
|
+
|
|
198
|
+
/* charts: categorical, in this order. Reserved for Chart (planned); the colors page grades them now */
|
|
199
|
+
--chart-1: var(--accent-text);
|
|
200
|
+
--chart-2: var(--blue);
|
|
201
|
+
--chart-3: var(--amber);
|
|
202
|
+
--chart-4: var(--red);
|
|
203
|
+
--chart-5: var(--paper-dim);
|
|
204
|
+
|
|
205
|
+
/* controls (buttons, inputs): block size and inline padding per size */
|
|
206
|
+
--control-h-sm: 1.75rem;
|
|
207
|
+
--control-h-md: 2.25rem;
|
|
208
|
+
--control-h-lg: 2.75rem;
|
|
209
|
+
--control-pad-sm: var(--space-2);
|
|
210
|
+
--control-pad-md: var(--space-3);
|
|
211
|
+
--control-pad-lg: var(--space-4);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* Day palette: warm paper, ink text, the same green. Declared at the root so print.css can share its inks,
|
|
216
|
+
* but only the day block below and print.css may use it: never an app, never the UI kit.
|
|
217
|
+
* The light selector is redundant for CSS; it marks the block as day for the ui app's token reader.
|
|
218
|
+
*/
|
|
219
|
+
:root,
|
|
220
|
+
[data-theme="light"] {
|
|
221
|
+
--day-paper: #f4f1e6;
|
|
222
|
+
--day-paper-raised: #fbf9f2;
|
|
223
|
+
--day-paper-sunken: #e8e4d6;
|
|
224
|
+
--day-paper-deep: #ddd8c8;
|
|
225
|
+
--day-line: #cfcabb;
|
|
226
|
+
--day-line-dim: #ddd9cb;
|
|
227
|
+
--day-line-control: #7f7a6b;
|
|
228
|
+
--day-ink: #141a16;
|
|
229
|
+
--day-ink-dim: #565c54;
|
|
230
|
+
--day-ink-faint: #5f635a;
|
|
231
|
+
--day-ink-off: #827f74;
|
|
232
|
+
--day-accent-text: #1f6b45;
|
|
233
|
+
--day-accent-glow: #2c7a5326;
|
|
234
|
+
--day-signal: #c23f0c;
|
|
235
|
+
--day-wash-1: #141a160d;
|
|
236
|
+
--day-wash-2: #141a161a;
|
|
237
|
+
--day-scrim: #f4f1e6cc;
|
|
238
|
+
--day-selection: #2c7a5340;
|
|
239
|
+
--day-red: #b3261e;
|
|
240
|
+
--day-red-wash: #b3261e1a;
|
|
241
|
+
--day-amber: #855a00;
|
|
242
|
+
--day-amber-wash: #855a001f;
|
|
243
|
+
--day-blue: #1f5fae;
|
|
244
|
+
--day-blue-wash: #1f5fae1a;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* Day: the semantic layer redefined on the day palette. Only the UI kit and ui.shab.boo use it;
|
|
249
|
+
* the four sites still style with the night palette directly.
|
|
250
|
+
*/
|
|
251
|
+
:root[data-theme="light"],
|
|
252
|
+
[data-theme="light"] {
|
|
253
|
+
color-scheme: light;
|
|
254
|
+
|
|
255
|
+
--grain-opacity: 0.045;
|
|
256
|
+
|
|
257
|
+
--surface: var(--day-paper);
|
|
258
|
+
--surface-raised: var(--day-paper-raised);
|
|
259
|
+
--surface-sunken: var(--day-paper-sunken);
|
|
260
|
+
--field: var(--day-paper-raised);
|
|
261
|
+
--field-raised: var(--day-paper);
|
|
262
|
+
|
|
263
|
+
--text: var(--day-ink);
|
|
264
|
+
--text-muted: var(--day-ink-dim);
|
|
265
|
+
--text-helper: var(--text-muted);
|
|
266
|
+
--text-placeholder: var(--day-ink-faint);
|
|
267
|
+
--text-disabled: var(--day-ink-off);
|
|
268
|
+
--text-accent: var(--day-accent-text);
|
|
269
|
+
--text-signal: var(--day-signal);
|
|
270
|
+
--text-on-fill: var(--day-paper);
|
|
271
|
+
|
|
272
|
+
--fill-accent: var(--accent);
|
|
273
|
+
--fill-accent-strong: var(--accent-strong);
|
|
274
|
+
--fill-glow: var(--day-accent-glow);
|
|
275
|
+
--fill-disabled: var(--day-paper-deep);
|
|
276
|
+
--fill-selection: var(--day-selection);
|
|
277
|
+
|
|
278
|
+
--state-hover: var(--day-wash-1);
|
|
279
|
+
--state-active: var(--day-wash-2);
|
|
280
|
+
--state-selected: var(--day-accent-glow);
|
|
281
|
+
|
|
282
|
+
--stroke-subtle: var(--day-line-dim);
|
|
283
|
+
--stroke: var(--day-line);
|
|
284
|
+
--stroke-strong: var(--accent-strong);
|
|
285
|
+
--stroke-control: var(--day-line-control);
|
|
286
|
+
--focus-ring: var(--day-signal);
|
|
287
|
+
--overlay: var(--day-scrim);
|
|
288
|
+
|
|
289
|
+
--status-error: var(--day-red);
|
|
290
|
+
--status-error-fill: var(--day-red-wash);
|
|
291
|
+
--status-warning: var(--day-amber);
|
|
292
|
+
--status-warning-fill: var(--day-amber-wash);
|
|
293
|
+
--status-info: var(--day-blue);
|
|
294
|
+
--status-info-fill: var(--day-blue-wash);
|
|
295
|
+
--status-success: var(--day-accent-text);
|
|
296
|
+
--status-success-fill: var(--day-accent-glow);
|
|
297
|
+
|
|
298
|
+
--chart-1: var(--day-accent-text);
|
|
299
|
+
--chart-2: var(--day-blue);
|
|
300
|
+
--chart-3: var(--day-amber);
|
|
301
|
+
--chart-4: var(--day-red);
|
|
302
|
+
--chart-5: var(--day-ink-dim);
|
|
303
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* A status marker. variant "text": colored text that sits inside a label line · "pill": a bordered pill.
|
|
4
|
+
* tone: accent (live things) · muted (past or dormant) · signal (needs a look; tiny doses)
|
|
5
|
+
* · error · warning · success · info: status tones, which always lead with an icon so color is never alone.
|
|
6
|
+
*/
|
|
7
|
+
import type { HTMLAttributes } from "astro/types";
|
|
8
|
+
import Icon from "./Icon.astro";
|
|
9
|
+
import type { IconName } from "./icons";
|
|
10
|
+
|
|
11
|
+
type Status = "error" | "warning" | "success" | "info";
|
|
12
|
+
|
|
13
|
+
interface Props extends HTMLAttributes<"span"> {
|
|
14
|
+
tone?: "accent" | "muted" | "signal" | Status;
|
|
15
|
+
variant?: "text" | "pill";
|
|
16
|
+
size?: "sm" | "md";
|
|
17
|
+
/** a blinking dot in front: on air, recording, happening now */
|
|
18
|
+
live?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const STATUS_ICON: Record<Status, IconName> = { error: "close", warning: "warning", success: "check", info: "info" };
|
|
22
|
+
|
|
23
|
+
const { tone = "accent", variant = "pill", size = "sm", live = false, class: className, ...rest } = Astro.props;
|
|
24
|
+
const icon = STATUS_ICON[tone as Status];
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<span class:list={["badge", className, { live }]} data-tone={tone} data-variant={variant} data-size={size} {...rest}
|
|
28
|
+
>{icon && <Icon name={icon} size="sm" class="badge-icon" />}<slot /></span
|
|
29
|
+
>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.badge {
|
|
33
|
+
--badge-color: var(--text-accent);
|
|
34
|
+
color: var(--badge-color);
|
|
35
|
+
}
|
|
36
|
+
.badge[data-tone="muted"] {
|
|
37
|
+
--badge-color: var(--text-muted);
|
|
38
|
+
}
|
|
39
|
+
.badge[data-tone="signal"] {
|
|
40
|
+
--badge-color: var(--text-signal);
|
|
41
|
+
}
|
|
42
|
+
.badge[data-tone="error"] {
|
|
43
|
+
--badge-color: var(--status-error);
|
|
44
|
+
--badge-fill: var(--status-error-fill);
|
|
45
|
+
}
|
|
46
|
+
.badge[data-tone="warning"] {
|
|
47
|
+
--badge-color: var(--status-warning);
|
|
48
|
+
--badge-fill: var(--status-warning-fill);
|
|
49
|
+
}
|
|
50
|
+
.badge[data-tone="success"] {
|
|
51
|
+
--badge-color: var(--status-success);
|
|
52
|
+
--badge-fill: var(--status-success-fill);
|
|
53
|
+
}
|
|
54
|
+
.badge[data-tone="info"] {
|
|
55
|
+
--badge-color: var(--status-info);
|
|
56
|
+
--badge-fill: var(--status-info-fill);
|
|
57
|
+
}
|
|
58
|
+
.badge-icon {
|
|
59
|
+
margin-inline-end: 0.35em;
|
|
60
|
+
}
|
|
61
|
+
.badge[data-variant="pill"] {
|
|
62
|
+
display: inline-block;
|
|
63
|
+
padding: 0.15em 0.65em;
|
|
64
|
+
border: 1px solid currentColor;
|
|
65
|
+
border-radius: var(--radius-pill);
|
|
66
|
+
background: var(--badge-fill, none);
|
|
67
|
+
font-family: var(--font-mono);
|
|
68
|
+
font-size: var(--step--2);
|
|
69
|
+
letter-spacing: var(--tracking-mono);
|
|
70
|
+
text-transform: uppercase;
|
|
71
|
+
line-height: 1.4;
|
|
72
|
+
}
|
|
73
|
+
.badge[data-variant="pill"][data-size="md"] {
|
|
74
|
+
font-size: var(--step--1);
|
|
75
|
+
}
|
|
76
|
+
.live::before {
|
|
77
|
+
content: "●" / "";
|
|
78
|
+
margin-inline-end: 0.4em;
|
|
79
|
+
}
|
|
80
|
+
/* four blinks, under five seconds, then it holds: nothing blinks on with no way to stop it (WCAG 2.2.2) */
|
|
81
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
82
|
+
.live::before {
|
|
83
|
+
animation: badge-blink var(--blink) steps(1, end) 4;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
@keyframes badge-blink {
|
|
87
|
+
50% {
|
|
88
|
+
opacity: 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
.badge[data-variant="pill"]:is(:lang(fa), :lang(ar)) {
|
|
92
|
+
font-family: var(--font-arabic);
|
|
93
|
+
letter-spacing: 0;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The trail back up, as a label line: links separated by slashes.
|
|
4
|
+
* `trailing` keeps a final slash, for when the page's own h1 follows as the last crumb.
|
|
5
|
+
*/
|
|
6
|
+
import type { HTMLAttributes } from "astro/types";
|
|
7
|
+
|
|
8
|
+
interface Props extends HTMLAttributes<"nav"> {
|
|
9
|
+
items: { label: string; href: string }[];
|
|
10
|
+
/** accessible name of the nav landmark */
|
|
11
|
+
label?: string;
|
|
12
|
+
trailing?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { items, label = "Breadcrumb", trailing = true, class: className, ...rest } = Astro.props;
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<nav aria-label={label} class:list={["label crumbs", className]} {...rest}>
|
|
19
|
+
{
|
|
20
|
+
items.map((item, i) => (
|
|
21
|
+
<>
|
|
22
|
+
<a href={item.href}>{item.label}</a>
|
|
23
|
+
{(i < items.length - 1 || trailing) && <span aria-hidden="true"> /</span>}
|
|
24
|
+
{i < items.length - 1 && " "}
|
|
25
|
+
</>
|
|
26
|
+
))
|
|
27
|
+
}
|
|
28
|
+
</nav>
|