@lettuceai/crisp 1.0.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/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/components/Accordion.d.ts +15 -0
- package/dist/components/ActionBar.d.ts +25 -0
- package/dist/components/Alert.d.ts +19 -0
- package/dist/components/AppBar.d.ts +29 -0
- package/dist/components/AppShell.d.ts +42 -0
- package/dist/components/AudioItem.d.ts +22 -0
- package/dist/components/Avatar.d.ts +11 -0
- package/dist/components/AvatarGroup.d.ts +17 -0
- package/dist/components/AvatarPicker.d.ts +20 -0
- package/dist/components/Badge.d.ts +8 -0
- package/dist/components/Banner.d.ts +17 -0
- package/dist/components/Breadcrumb.d.ts +23 -0
- package/dist/components/Button.d.ts +15 -0
- package/dist/components/ButtonGroup.d.ts +21 -0
- package/dist/components/Card.d.ts +16 -0
- package/dist/components/Carousel.d.ts +16 -0
- package/dist/components/CharacterCard.d.ts +30 -0
- package/dist/components/Chart.d.ts +35 -0
- package/dist/components/Checkbox.d.ts +11 -0
- package/dist/components/Chip.d.ts +11 -0
- package/dist/components/ChoiceCard.d.ts +22 -0
- package/dist/components/CodeBlock.d.ts +16 -0
- package/dist/components/Collapsible.d.ts +20 -0
- package/dist/components/ColorPicker.d.ts +15 -0
- package/dist/components/Combobox.d.ts +30 -0
- package/dist/components/CommandPalette.d.ts +34 -0
- package/dist/components/Composer.d.ts +49 -0
- package/dist/components/ContextMenu.d.ts +18 -0
- package/dist/components/CopyButton.d.ts +13 -0
- package/dist/components/DatePicker.d.ts +23 -0
- package/dist/components/Dialog.d.ts +15 -0
- package/dist/components/Diff.d.ts +17 -0
- package/dist/components/Dock.d.ts +6 -0
- package/dist/components/DownloadItem.d.ts +28 -0
- package/dist/components/Dropzone.d.ts +21 -0
- package/dist/components/EmptyState.d.ts +24 -0
- package/dist/components/Field.d.ts +20 -0
- package/dist/components/FilterBar.d.ts +21 -0
- package/dist/components/Form.d.ts +44 -0
- package/dist/components/Highlight.d.ts +14 -0
- package/dist/components/HoverCard.d.ts +15 -0
- package/dist/components/Icon.d.ts +37 -0
- package/dist/components/IconButton.d.ts +14 -0
- package/dist/components/ImageViewer.d.ts +16 -0
- package/dist/components/InfiniteScroll.d.ts +14 -0
- package/dist/components/Input.d.ts +14 -0
- package/dist/components/Kbd.d.ts +5 -0
- package/dist/components/List.d.ts +25 -0
- package/dist/components/Masonry.d.ts +16 -0
- package/dist/components/MediaCard.d.ts +23 -0
- package/dist/components/MediaTile.d.ts +21 -0
- package/dist/components/Menu.d.ts +34 -0
- package/dist/components/Message.d.ts +76 -0
- package/dist/components/Meter.d.ts +20 -0
- package/dist/components/ModelSelect.d.ts +35 -0
- package/dist/components/MultiItemDownloadCard.d.ts +39 -0
- package/dist/components/NavRail.d.ts +37 -0
- package/dist/components/NumberInput.d.ts +19 -0
- package/dist/components/Page.d.ts +18 -0
- package/dist/components/Pagination.d.ts +10 -0
- package/dist/components/Panel.d.ts +18 -0
- package/dist/components/PartitionBar.d.ts +39 -0
- package/dist/components/Popover.d.ts +45 -0
- package/dist/components/Progress.d.ts +9 -0
- package/dist/components/Prose.d.ts +13 -0
- package/dist/components/Radio.d.ts +19 -0
- package/dist/components/ReorderableList.d.ts +21 -0
- package/dist/components/Resizable.d.ts +20 -0
- package/dist/components/ScrollArea.d.ts +17 -0
- package/dist/components/SearchField.d.ts +14 -0
- package/dist/components/SecretInput.d.ts +26 -0
- package/dist/components/Segmented.d.ts +16 -0
- package/dist/components/Select.d.ts +15 -0
- package/dist/components/SelectMenu.d.ts +48 -0
- package/dist/components/Sheet.d.ts +50 -0
- package/dist/components/Shelf.d.ts +17 -0
- package/dist/components/Skeleton.d.ts +12 -0
- package/dist/components/Slider.d.ts +11 -0
- package/dist/components/Spinner.d.ts +13 -0
- package/dist/components/StatTile.d.ts +33 -0
- package/dist/components/Stepper.d.ts +21 -0
- package/dist/components/Surface.d.ts +56 -0
- package/dist/components/Switch.d.ts +9 -0
- package/dist/components/TabBar.d.ts +12 -0
- package/dist/components/Table.d.ts +35 -0
- package/dist/components/Tabs.d.ts +31 -0
- package/dist/components/TagInput.d.ts +18 -0
- package/dist/components/Text.d.ts +293 -0
- package/dist/components/ThemeEditor.d.ts +8 -0
- package/dist/components/Timeline.d.ts +17 -0
- package/dist/components/TitleBar.d.ts +17 -0
- package/dist/components/Toast.d.ts +50 -0
- package/dist/components/ToggleGroup.d.ts +22 -0
- package/dist/components/Toolbar.d.ts +18 -0
- package/dist/components/Tooltip.d.ts +15 -0
- package/dist/components/Tour.d.ts +22 -0
- package/dist/components/Tree.d.ts +21 -0
- package/dist/components/VirtualList.d.ts +18 -0
- package/dist/components/WindowControls.d.ts +38 -0
- package/dist/components/Wizard.d.ts +33 -0
- package/dist/components/confirm.d.ts +24 -0
- package/dist/components/index.d.ts +103 -0
- package/dist/components/nav.d.ts +19 -0
- package/dist/components/primitives.d.ts +14 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +8035 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/Box.d.ts +6 -0
- package/dist/layout/Center.d.ts +2 -0
- package/dist/layout/Container.d.ts +8 -0
- package/dist/layout/Divider.d.ts +9 -0
- package/dist/layout/Flex.d.ts +16 -0
- package/dist/layout/Grid.d.ts +13 -0
- package/dist/layout/Spacer.d.ts +4 -0
- package/dist/layout/Stack.d.ts +13 -0
- package/dist/layout/index.d.ts +8 -0
- package/dist/lib/cn.d.ts +2 -0
- package/dist/lib/color.d.ts +16 -0
- package/dist/lib/format.d.ts +6 -0
- package/dist/lib/frame.d.ts +25 -0
- package/dist/lib/icon.d.ts +24 -0
- package/dist/lib/imageBrightness.d.ts +8 -0
- package/dist/lib/motion.d.ts +54 -0
- package/dist/lib/polymorphic.d.ts +12 -0
- package/dist/lib/tint.d.ts +9 -0
- package/dist/lib/useLongPress.d.ts +19 -0
- package/dist/lib/useMediaQuery.d.ts +8 -0
- package/dist/lib/useModal.d.ts +8 -0
- package/dist/theme/ThemeProvider.d.ts +19 -0
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/theme.d.ts +36 -0
- package/package.json +81 -0
- package/src/styles.css +392 -0
package/src/styles.css
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/* ──────────────────────────────────────────────────────────────
|
|
2
|
+
Crisp — LettuceAI's design system.
|
|
3
|
+
Palette names match the app's runtime vars 1:1 so the
|
|
4
|
+
ColorCustomization feature keeps working when this ships.
|
|
5
|
+
────────────────────────────────────────────────────────────── */
|
|
6
|
+
@theme {
|
|
7
|
+
--color-surface: #050505;
|
|
8
|
+
/* Two visible steps of elevation. Raised carries cards; overlay carries anything
|
|
9
|
+
floating above the page. Both lift toward the light in either theme. */
|
|
10
|
+
--color-surface-1: #131313;
|
|
11
|
+
--color-surface-2: #1e1e1e;
|
|
12
|
+
--color-surface-el: #131313;
|
|
13
|
+
--color-nav: #131313;
|
|
14
|
+
--color-fg: #ffffff;
|
|
15
|
+
--color-accent: #65c789;
|
|
16
|
+
/* Text that sits on a solid accent fill. Derived per theme, because a pale accent
|
|
17
|
+
needs dark text and that is not a decision a call site should be making. */
|
|
18
|
+
--color-on-accent: #050505;
|
|
19
|
+
--color-knob: #ffffff;
|
|
20
|
+
--color-danger: #ef4444;
|
|
21
|
+
--color-warning: #f59e0b;
|
|
22
|
+
--color-info: #3b82f6;
|
|
23
|
+
--color-secondary: #a78bfa;
|
|
24
|
+
|
|
25
|
+
/* Text tiers. These are the only four strengths text may use.
|
|
26
|
+
fg → primary (titles, values, body)
|
|
27
|
+
fg-2 → secondary (labels, supporting copy) ~10:1
|
|
28
|
+
fg-3 → muted (hints, meta, placeholders) ~5:1 — last readable tier
|
|
29
|
+
fg-4 → faint (disabled, decorative only) ~3:1 — never carries meaning
|
|
30
|
+
|
|
31
|
+
fg-4 is below AA at any size. It is for things you look past: chevrons, separators,
|
|
32
|
+
list markers, placeholders, a step you have not reached. The moment a value is read
|
|
33
|
+
— a timestamp, a count, a name, a shortcut — it belongs to fg-3 or above. */
|
|
34
|
+
--color-fg-2: color-mix(in oklab, var(--color-fg) 72%, var(--color-surface));
|
|
35
|
+
--color-fg-3: color-mix(in oklab, var(--color-fg) 50%, var(--color-surface));
|
|
36
|
+
--color-fg-4: color-mix(in oklab, var(--color-fg) 34%, var(--color-surface));
|
|
37
|
+
|
|
38
|
+
/* Fills and lines are fg-tinted, never grey hexes, so they follow custom colors. */
|
|
39
|
+
--color-fill: color-mix(in oklab, var(--color-fg) 5%, transparent);
|
|
40
|
+
--color-fill-2: color-mix(in oklab, var(--color-fg) 8%, transparent);
|
|
41
|
+
--color-fill-3: color-mix(in oklab, var(--color-fg) 12%, transparent);
|
|
42
|
+
--color-line: color-mix(in oklab, var(--color-fg) 10%, transparent);
|
|
43
|
+
--color-line-2: color-mix(in oklab, var(--color-fg) 16%, transparent);
|
|
44
|
+
--color-line-3: color-mix(in oklab, var(--color-fg) 26%, transparent);
|
|
45
|
+
|
|
46
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
47
|
+
|
|
48
|
+
/* Type scale — eight steps, 11px floor. Replaces the 15 ad-hoc px sizes. */
|
|
49
|
+
--text-2xs: 11px; --text-2xs--line-height: 14px;
|
|
50
|
+
--text-xs: 12px; --text-xs--line-height: 16px;
|
|
51
|
+
--text-sm: 13px; --text-sm--line-height: 18px;
|
|
52
|
+
--text-base: 14px; --text-base--line-height: 20px;
|
|
53
|
+
--text-lg: 16px; --text-lg--line-height: 22px;
|
|
54
|
+
--text-xl: 20px; --text-xl--line-height: 26px;
|
|
55
|
+
--text-2xl: 24px; --text-2xl--line-height: 30px;
|
|
56
|
+
--text-3xl: 28px; --text-3xl--line-height: 34px;
|
|
57
|
+
|
|
58
|
+
/* Radius roles: controls lg(8) · fields/cards xl(12) · sheets 2xl(16) · pills full */
|
|
59
|
+
|
|
60
|
+
--edge-light: rgb(255 255 255 / 0.08);
|
|
61
|
+
--edge-dark: rgb(0 0 0 / 0.55);
|
|
62
|
+
--edge-ambient: rgb(0 0 0 / 0.35);
|
|
63
|
+
--shadow-raised: 0 8px 24px rgb(0 0 0 / 0.35);
|
|
64
|
+
--shadow-sheet: 0 -12px 40px rgb(0 0 0 / 0.5);
|
|
65
|
+
|
|
66
|
+
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
|
|
67
|
+
|
|
68
|
+
/* Density. Controls and rows read from these so `data-density="compact"` on the root
|
|
69
|
+
tightens everything at once — dense screens (tables, logs, long lists) can breathe
|
|
70
|
+
differently from open ones without every call site knowing. */
|
|
71
|
+
--control-sm: 2rem;
|
|
72
|
+
--control-md: 2.5rem;
|
|
73
|
+
--control-lg: 2.75rem;
|
|
74
|
+
--row-py: 0.75rem;
|
|
75
|
+
--card-p: 1rem;
|
|
76
|
+
|
|
77
|
+
/* Motion. Mirrors src/lib/motion.ts — pick by what the motion is for. */
|
|
78
|
+
--motion-instant: 120ms;
|
|
79
|
+
--motion-quick: 180ms;
|
|
80
|
+
--motion-slow: 300ms;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:root {
|
|
84
|
+
color-scheme: dark;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
html,
|
|
88
|
+
body {
|
|
89
|
+
height: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Safe areas go through these rather than env() directly, so a simulated device on the
|
|
93
|
+
lab page can supply the inset its hardware would. In the app they are env() and
|
|
94
|
+
nothing else. */
|
|
95
|
+
:root {
|
|
96
|
+
--safe-top: env(safe-area-inset-top);
|
|
97
|
+
--safe-bottom: env(safe-area-inset-bottom);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* min-height, not height: a sticky header's containing block is #root, so a fixed
|
|
101
|
+
100% height makes it stop sticking as soon as you scroll past the first screen. */
|
|
102
|
+
#root {
|
|
103
|
+
min-height: 100%;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
body {
|
|
107
|
+
margin: 0;
|
|
108
|
+
background: var(--color-surface);
|
|
109
|
+
color: var(--color-fg);
|
|
110
|
+
font-family: var(--font-sans);
|
|
111
|
+
font-size: var(--text-base);
|
|
112
|
+
line-height: var(--text-base--line-height);
|
|
113
|
+
-webkit-font-smoothing: antialiased;
|
|
114
|
+
-moz-osx-font-smoothing: grayscale;
|
|
115
|
+
text-rendering: optimizeLegibility;
|
|
116
|
+
overscroll-behavior: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Keyboard focus is visible everywhere; pointer focus is not. One rule, whole app. */
|
|
120
|
+
:focus-visible {
|
|
121
|
+
outline: 2px solid var(--color-accent);
|
|
122
|
+
outline-offset: 2px;
|
|
123
|
+
}
|
|
124
|
+
:focus:not(:focus-visible) {
|
|
125
|
+
outline: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Text fields carry their own focus treatment — a border and fill change drawn tight
|
|
129
|
+
to the control. The offset ring on top of that is a second signal for one state, and
|
|
130
|
+
around a composer's textarea it boxes a box. Opt them out; `.focus-ring` opts back in
|
|
131
|
+
for a field that has no styling of its own. */
|
|
132
|
+
input:focus-visible,
|
|
133
|
+
textarea:focus-visible,
|
|
134
|
+
select:focus-visible {
|
|
135
|
+
outline: none;
|
|
136
|
+
}
|
|
137
|
+
input.focus-ring:focus-visible,
|
|
138
|
+
textarea.focus-ring:focus-visible,
|
|
139
|
+
select.focus-ring:focus-visible {
|
|
140
|
+
outline: 2px solid var(--color-accent);
|
|
141
|
+
outline-offset: 2px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* A dialog takes focus on open so the keyboard lands inside it. That is plumbing, not
|
|
145
|
+
a target the person picked, and ringing the whole sheet in accent says otherwise. */
|
|
146
|
+
[role="dialog"][tabindex="-1"]:focus,
|
|
147
|
+
[role="dialog"][tabindex="-1"]:focus-visible {
|
|
148
|
+
outline: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (prefers-reduced-motion: reduce) {
|
|
152
|
+
*,
|
|
153
|
+
*::before,
|
|
154
|
+
*::after {
|
|
155
|
+
animation-duration: 0.01ms !important;
|
|
156
|
+
animation-iteration-count: 1 !important;
|
|
157
|
+
transition-duration: 0.01ms !important;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
[data-density="compact"] {
|
|
162
|
+
--control-sm: 1.75rem;
|
|
163
|
+
--control-md: 2.125rem;
|
|
164
|
+
--control-lg: 2.375rem;
|
|
165
|
+
--row-py: 0.5rem;
|
|
166
|
+
--card-p: 0.75rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@layer utilities {
|
|
170
|
+
/* Use these instead of `duration-150` and friends: three intents, not five numbers. */
|
|
171
|
+
.motion-instant { transition-duration: var(--motion-instant); }
|
|
172
|
+
.motion-quick { transition-duration: var(--motion-quick); }
|
|
173
|
+
.motion-slow { transition-duration: var(--motion-slow); }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@layer components {
|
|
177
|
+
/*
|
|
178
|
+
* A control that sits slightly proud of the page: a light hairline along its top edge,
|
|
179
|
+
* a 1px lip beneath it, and a 2px ambient. Pressing removes the lip and sinks the
|
|
180
|
+
* surface, so the movement is the feedback rather than a colour change alone.
|
|
181
|
+
*
|
|
182
|
+
* Deliberately shallow. Large blur radii are what make a webview struggle, and the
|
|
183
|
+
* effect being asked for is a key, not a floating card.
|
|
184
|
+
*/
|
|
185
|
+
.raise {
|
|
186
|
+
box-shadow:
|
|
187
|
+
inset 0 1px 0 var(--edge-light),
|
|
188
|
+
0 1px 0 var(--edge-dark),
|
|
189
|
+
0 2px 3px -1px var(--edge-ambient);
|
|
190
|
+
transition: box-shadow var(--motion-instant), transform var(--motion-instant),
|
|
191
|
+
background-color var(--motion-instant), border-color var(--motion-instant),
|
|
192
|
+
color var(--motion-instant);
|
|
193
|
+
}
|
|
194
|
+
.raise:hover:not(:disabled):not([aria-disabled="true"]),
|
|
195
|
+
.raise[data-force~="hover"] {
|
|
196
|
+
box-shadow:
|
|
197
|
+
inset 0 1px 0 var(--edge-light),
|
|
198
|
+
0 1px 0 var(--edge-dark),
|
|
199
|
+
0 3px 6px -2px var(--edge-ambient);
|
|
200
|
+
}
|
|
201
|
+
/* Pressed: the lip is gone and the face sits a pixel lower. */
|
|
202
|
+
.raise:active:not(:disabled):not([aria-disabled="true"]),
|
|
203
|
+
.raise[data-force~="active"] {
|
|
204
|
+
transform: translateY(1px);
|
|
205
|
+
box-shadow: inset 0 1px 2px var(--edge-dark);
|
|
206
|
+
}
|
|
207
|
+
/* Disabled reads as already pressed rather than as a hover state with the lights off. */
|
|
208
|
+
.raise:disabled,
|
|
209
|
+
.raise[aria-disabled="true"] {
|
|
210
|
+
transform: translateY(1px);
|
|
211
|
+
box-shadow: inset 0 1px 2px var(--edge-dark);
|
|
212
|
+
}
|
|
213
|
+
/* Ghost has no face to lift, so it only sinks when pressed. */
|
|
214
|
+
.raise-flat {
|
|
215
|
+
transition: box-shadow var(--motion-instant), transform var(--motion-instant),
|
|
216
|
+
background-color var(--motion-instant), color var(--motion-instant);
|
|
217
|
+
}
|
|
218
|
+
.raise-flat:active:not(:disabled):not([aria-disabled="true"]),
|
|
219
|
+
.raise-flat[data-force~="active"] {
|
|
220
|
+
transform: translateY(1px);
|
|
221
|
+
box-shadow: inset 0 1px 2px var(--edge-dark);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* The composer's row height. With a mouse its buttons are `md` and the pill is slim;
|
|
226
|
+
* on a touch screen touch-target lifts every button to 44px, and the row has to
|
|
227
|
+
* follow or the text drops out of line with them. One variable, read by the textarea
|
|
228
|
+
* and the recording wave alike.
|
|
229
|
+
*/
|
|
230
|
+
.composer-row {
|
|
231
|
+
--composer-row: var(--control-md);
|
|
232
|
+
@media (pointer: coarse) {
|
|
233
|
+
--composer-row: max(var(--control-md), 44px);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
[data-coarse] .composer-row {
|
|
237
|
+
--composer-row: max(var(--control-md), 44px);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Controls take their height from the density scale, never a literal. */
|
|
241
|
+
.control-sm { height: var(--control-sm); }
|
|
242
|
+
.control-md { height: var(--control-md); }
|
|
243
|
+
.control-lg { height: var(--control-lg); }
|
|
244
|
+
.row-py { padding-block: var(--row-py); }
|
|
245
|
+
.card-p { padding: var(--card-p); }
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* Two ways to be reachable by a thumb.
|
|
249
|
+
*
|
|
250
|
+
* `touch-target` grows the control itself, for anything that can afford the room —
|
|
251
|
+
* rows, buttons, calendar cells. Width as well as height: a square icon button that
|
|
252
|
+
* is 44 tall and 28 wide is still a miss.
|
|
253
|
+
*
|
|
254
|
+
* `tap-target` leaves the drawing alone and reaches out with an invisible pad, for
|
|
255
|
+
* the small controls inside dense content — the × on a chip, on an attachment — where
|
|
256
|
+
* growing the box would wreck the layout it sits in. Desktop keeps its compact
|
|
257
|
+
* density in both cases; nothing changes where there is a mouse.
|
|
258
|
+
*/
|
|
259
|
+
.touch-target {
|
|
260
|
+
@media (pointer: coarse) {
|
|
261
|
+
min-height: 44px;
|
|
262
|
+
min-width: 44px;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
.tap-target {
|
|
266
|
+
position: relative;
|
|
267
|
+
}
|
|
268
|
+
@media (pointer: coarse) {
|
|
269
|
+
.tap-target::after {
|
|
270
|
+
content: "";
|
|
271
|
+
position: absolute;
|
|
272
|
+
top: 50%;
|
|
273
|
+
left: 50%;
|
|
274
|
+
width: max(100%, 44px);
|
|
275
|
+
height: max(100%, 44px);
|
|
276
|
+
transform: translate(-50%, -50%);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/* A simulated phone on the lab page: the monitor has a mouse, so the media query above
|
|
280
|
+
never fires, and the frame would show desktop hit areas on a phone. `data-coarse`
|
|
281
|
+
on the frame applies the same rules to everything inside it. */
|
|
282
|
+
[data-coarse] .touch-target {
|
|
283
|
+
min-height: 44px;
|
|
284
|
+
min-width: 44px;
|
|
285
|
+
}
|
|
286
|
+
[data-coarse] .tap-target::after {
|
|
287
|
+
content: "";
|
|
288
|
+
position: absolute;
|
|
289
|
+
top: 50%;
|
|
290
|
+
left: 50%;
|
|
291
|
+
width: max(100%, 44px);
|
|
292
|
+
height: max(100%, 44px);
|
|
293
|
+
transform: translate(-50%, -50%);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@layer utilities {
|
|
298
|
+
.scrollbar-thin {
|
|
299
|
+
scrollbar-width: thin;
|
|
300
|
+
scrollbar-color: var(--color-line-2) transparent;
|
|
301
|
+
}
|
|
302
|
+
.scrollbar-none {
|
|
303
|
+
scrollbar-width: none;
|
|
304
|
+
}
|
|
305
|
+
.scrollbar-none::-webkit-scrollbar {
|
|
306
|
+
display: none;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* ── Slider (native range, styled with tokens) ─────────────────────────── */
|
|
311
|
+
input[type="range"].slider {
|
|
312
|
+
-webkit-appearance: none;
|
|
313
|
+
appearance: none;
|
|
314
|
+
width: 100%;
|
|
315
|
+
height: 24px;
|
|
316
|
+
background: transparent;
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
}
|
|
319
|
+
input[type="range"].slider::-webkit-slider-runnable-track {
|
|
320
|
+
height: 4px;
|
|
321
|
+
border-radius: 999px;
|
|
322
|
+
background: linear-gradient(var(--color-accent), var(--color-accent)) 0 / var(--slider-fill, 0%) 100% no-repeat,
|
|
323
|
+
var(--color-fill-3);
|
|
324
|
+
}
|
|
325
|
+
input[type="range"].slider::-moz-range-track {
|
|
326
|
+
height: 4px;
|
|
327
|
+
border-radius: 999px;
|
|
328
|
+
background: var(--color-fill-3);
|
|
329
|
+
}
|
|
330
|
+
input[type="range"].slider::-moz-range-progress {
|
|
331
|
+
height: 4px;
|
|
332
|
+
border-radius: 999px;
|
|
333
|
+
background: var(--color-accent);
|
|
334
|
+
}
|
|
335
|
+
input[type="range"].slider::-webkit-slider-thumb {
|
|
336
|
+
-webkit-appearance: none;
|
|
337
|
+
appearance: none;
|
|
338
|
+
margin-top: -7px;
|
|
339
|
+
height: 18px;
|
|
340
|
+
width: 18px;
|
|
341
|
+
border-radius: 999px;
|
|
342
|
+
background: var(--color-knob);
|
|
343
|
+
border: 2px solid var(--color-accent);
|
|
344
|
+
box-shadow: 0 1px 4px rgb(0 0 0 / 0.4);
|
|
345
|
+
transition: transform 120ms ease;
|
|
346
|
+
}
|
|
347
|
+
input[type="range"].slider::-moz-range-thumb {
|
|
348
|
+
height: 18px;
|
|
349
|
+
width: 18px;
|
|
350
|
+
border-radius: 999px;
|
|
351
|
+
background: var(--color-knob);
|
|
352
|
+
border: 2px solid var(--color-accent);
|
|
353
|
+
box-shadow: 0 1px 4px rgb(0 0 0 / 0.4);
|
|
354
|
+
}
|
|
355
|
+
input[type="range"].slider:active::-webkit-slider-thumb { transform: scale(1.15); }
|
|
356
|
+
input[type="range"].slider:disabled { cursor: not-allowed; opacity: 0.4; }
|
|
357
|
+
input[type="range"].slider:focus-visible { outline: none; }
|
|
358
|
+
input[type="range"].slider:focus-visible::-webkit-slider-thumb {
|
|
359
|
+
box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-accent) 30%, transparent);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* ── Skeleton shimmer ──────────────────────────────────────────────────── */
|
|
363
|
+
@keyframes shimmer {
|
|
364
|
+
from { transform: translateX(-100%); }
|
|
365
|
+
to { transform: translateX(100%); }
|
|
366
|
+
}
|
|
367
|
+
.skeleton {
|
|
368
|
+
position: relative;
|
|
369
|
+
overflow: hidden;
|
|
370
|
+
background: var(--color-fill-2);
|
|
371
|
+
}
|
|
372
|
+
.skeleton::after {
|
|
373
|
+
content: "";
|
|
374
|
+
position: absolute;
|
|
375
|
+
inset: 0;
|
|
376
|
+
background: linear-gradient(90deg, transparent, var(--color-fill-2), transparent);
|
|
377
|
+
animation: shimmer 1.6s infinite;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@keyframes indeterminate {
|
|
381
|
+
0% { transform: translateX(-100%); }
|
|
382
|
+
100% { transform: translateX(300%); }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Hover-revealed affordances must stay visible where there is no hover. */
|
|
386
|
+
@custom-variant hoverable (@media (hover: hover) and (pointer: fine));
|
|
387
|
+
|
|
388
|
+
/* Interaction states can be forced with `data-force`, so a component's hover, press and
|
|
389
|
+
focus can be reviewed side by side instead of one at a time under a cursor. */
|
|
390
|
+
@custom-variant hover (&:hover, &[data-force~="hover"]);
|
|
391
|
+
@custom-variant active (&:active, &[data-force~="active"]);
|
|
392
|
+
@custom-variant focus-visible (&:focus-visible, &[data-force~="focus"]);
|