anentrypoint-design 0.0.322 → 0.0.324
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/README.md +22 -2
- package/app-shell.css +24 -4782
- package/colors_and_type.css +43 -0
- package/dist/247420.css +74 -3
- package/dist/247420.js +16 -16
- package/package.json +14 -8
- package/src/bootstrap.js +10 -1
- package/src/components/chat-message-parts.js +355 -0
- package/src/components/chat.js +23 -320
- package/src/components/data-density.js +15 -10
- package/src/components/files-modals.js +14 -2
- package/src/components/files.js +5 -1
- package/src/components/freddie/helpers.js +46 -8
- package/src/css/app-shell/base.css +157 -0
- package/src/css/app-shell/catalog-theme.css +144 -0
- package/src/css/app-shell/chat-basic.css +12 -0
- package/src/css/app-shell/chat-polish.css +459 -0
- package/src/css/app-shell/data-density.css +150 -0
- package/src/css/app-shell/files.css +709 -0
- package/src/css/app-shell/hero-content.css +407 -0
- package/src/css/app-shell/kits-appended.css +361 -0
- package/src/css/app-shell/loading-alerts.css +120 -0
- package/src/css/app-shell/panel-row.css +177 -0
- package/src/css/app-shell/primitives.css +197 -0
- package/src/css/app-shell/responsive.css +266 -0
- package/src/css/app-shell/responsive2-workspace.css +465 -0
- package/src/css/app-shell/row-print.css +75 -0
- package/src/css/app-shell/sidebar-misc.css +79 -0
- package/src/css/app-shell/states-interactions.css +696 -0
- package/src/css/app-shell/topbar.css +319 -0
- package/src/deck-stage.js +6 -4
- package/src/highlight.js +20 -4
- package/src/index.js +15 -5
- package/src/kits/os/freddie/pages-chat.js +1 -1
- package/src/kits/os/freddie/pages-core.js +1 -1
- package/src/kits/os/freddie/pages-os.js +1 -1
- package/src/kits/os/freddie/pages-tools.js +1 -1
- package/src/kits/os/wm.js +38 -0
- package/src/kits/slides/deck-stage-overlay.js +68 -0
- package/src/kits/slides/deck-stage-state.js +81 -0
- package/src/kits/slides/deck-stage-style.js +117 -0
- package/src/kits/slides/deck-stage.js +167 -0
- package/src/kits/slides/index.js +5 -0
- package/src/markdown.js +89 -11
- package/src/kits/os/freddie/helpers.js +0 -33
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Enhanced UX: Component States, Drag-and-Drop, Context Menus
|
|
3
|
+
Comprehensive improvements for perfect UX across all surfaces
|
|
4
|
+
============================================================ */
|
|
5
|
+
|
|
6
|
+
/* ------------------------------------------------------------
|
|
7
|
+
Component States — Disabled, Loading, Error, Success
|
|
8
|
+
-------------------------------------------------------------- */
|
|
9
|
+
|
|
10
|
+
/* Disabled state for all interactive elements. The `.is-disabled` class
|
|
11
|
+
variants cover Btn()'s anchor-rendered case (shell.js) — an <a> has no
|
|
12
|
+
:disabled pseudo-class, so a disabled link-styled button relies entirely
|
|
13
|
+
on this class selector for its visual treatment (aria-disabled + tabindex
|
|
14
|
+
are set in JS; this is the paired CSS half). */
|
|
15
|
+
.btn:disabled, .btn-primary:disabled, .btn-ghost:disabled,
|
|
16
|
+
.btn.is-disabled, .btn-primary.is-disabled, .btn-ghost.is-disabled,
|
|
17
|
+
button:disabled, [disabled] {
|
|
18
|
+
opacity: 0.6;
|
|
19
|
+
cursor: not-allowed;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Loading state — spinning indicator */
|
|
24
|
+
@keyframes spinner-rotate {
|
|
25
|
+
from { transform: rotate(0deg); }
|
|
26
|
+
to { transform: rotate(360deg); }
|
|
27
|
+
}
|
|
28
|
+
/* Reusable spin utility: rotates the element (e.g. a refresh Icon used as a
|
|
29
|
+
busy indicator) via the shared spinner-rotate keyframe. Respects reduced
|
|
30
|
+
motion. Use instead of a decorative rotation glyph. */
|
|
31
|
+
.ds-spin { animation: spinner-rotate 0.8s linear infinite; transform-origin: center; }
|
|
32
|
+
@media (prefers-reduced-motion: reduce) { .ds-spin { animation: none; } }
|
|
33
|
+
|
|
34
|
+
.loading { position: relative; }
|
|
35
|
+
.loading::after {
|
|
36
|
+
content: '';
|
|
37
|
+
position: absolute; top: 50%; right: 12px;
|
|
38
|
+
width: 16px; height: 16px;
|
|
39
|
+
border: 2px solid var(--bg-3);
|
|
40
|
+
border-top-color: var(--accent);
|
|
41
|
+
border-radius: 50%;
|
|
42
|
+
animation: spinner-rotate 0.8s linear infinite;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Error state */
|
|
46
|
+
.error, [data-state="error"],
|
|
47
|
+
.row.error, input.error,
|
|
48
|
+
textarea.error {
|
|
49
|
+
border-color: var(--warn) !important;
|
|
50
|
+
background-color: color-mix(in oklab, var(--warn) 6%, var(--bg));
|
|
51
|
+
}
|
|
52
|
+
.error::placeholder { color: color-mix(in oklab, var(--warn) 60%, var(--fg-3)); }
|
|
53
|
+
|
|
54
|
+
/* Success state */
|
|
55
|
+
.success, [data-state="success"],
|
|
56
|
+
.row.success { background-color: color-mix(in oklab, var(--green-2) 6%, var(--bg)); }
|
|
57
|
+
|
|
58
|
+
/* Readonly state */
|
|
59
|
+
[readonly], .readonly {
|
|
60
|
+
background: var(--bg-2);
|
|
61
|
+
cursor: default;
|
|
62
|
+
opacity: 0.8;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ------------------------------------------------------------
|
|
66
|
+
Enhanced Button Micro-Interactions
|
|
67
|
+
-------------------------------------------------------------- */
|
|
68
|
+
|
|
69
|
+
/* Button scale feedback on active */
|
|
70
|
+
.btn:active:not(:disabled),
|
|
71
|
+
.btn-primary:active:not(:disabled),
|
|
72
|
+
.btn-ghost:active:not(:disabled),
|
|
73
|
+
button:active:not(:disabled) {
|
|
74
|
+
transform: scale(0.98);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Enhanced hover transitions — transition only the properties that actually
|
|
78
|
+
change (not `all`, which animates layout and is a perf trap), and drop the
|
|
79
|
+
blanket `will-change` (it forces a permanent compositor layer per element). */
|
|
80
|
+
.btn, .btn-primary, .btn-ghost,
|
|
81
|
+
button, a.row, .row,
|
|
82
|
+
.chip, [role="button"],
|
|
83
|
+
input[type="checkbox"], input[type="radio"] {
|
|
84
|
+
transition: background-color var(--dur-snap) var(--ease),
|
|
85
|
+
color var(--dur-snap) var(--ease),
|
|
86
|
+
box-shadow var(--dur-snap) var(--ease),
|
|
87
|
+
transform var(--dur-snap) var(--ease);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Native checkbox/radio tint — browser default is UA blue, which fights the
|
|
91
|
+
single lime lead accent wherever a raw control lands outside a bespoke
|
|
92
|
+
toggle (e.g. signin's "remember me"). accent-color re-tints the native
|
|
93
|
+
control box without hand-drawing a replacement. */
|
|
94
|
+
input[type="checkbox"], input[type="radio"] {
|
|
95
|
+
accent-color: var(--accent-ink);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Prevent double-tap zoom on buttons (iOS) */
|
|
99
|
+
.btn, .btn-primary, .btn-ghost, button {
|
|
100
|
+
-webkit-user-select: none;
|
|
101
|
+
-webkit-tap-highlight-color: transparent;
|
|
102
|
+
-webkit-touch-callout: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ------------------------------------------------------------
|
|
106
|
+
Drag-and-Drop Styles
|
|
107
|
+
-------------------------------------------------------------- */
|
|
108
|
+
|
|
109
|
+
/* Draggable element */
|
|
110
|
+
[draggable="true"], .draggable {
|
|
111
|
+
cursor: grab;
|
|
112
|
+
user-select: none;
|
|
113
|
+
}
|
|
114
|
+
[draggable="true"]:active { cursor: grabbing; }
|
|
115
|
+
|
|
116
|
+
/* Drag-over state */
|
|
117
|
+
.drag-over {
|
|
118
|
+
border: 2px dashed var(--accent);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Drag-ghost (semi-transparent dragging element) */
|
|
122
|
+
.drag-ghost {
|
|
123
|
+
opacity: 0.5;
|
|
124
|
+
transform: rotate(2deg) scale(0.95);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Drop-zone indicator */
|
|
128
|
+
.drop-zone {
|
|
129
|
+
border: 2px dashed var(--rule-strong);
|
|
130
|
+
border-radius: var(--r-2);
|
|
131
|
+
padding: var(--space-4);
|
|
132
|
+
background: color-mix(in oklab, var(--accent) 4%, var(--bg));
|
|
133
|
+
min-height: 120px;
|
|
134
|
+
display: flex; align-items: center; justify-content: center;
|
|
135
|
+
text-align: center; color: var(--fg-3);
|
|
136
|
+
transition: all var(--dur-snap) var(--ease);
|
|
137
|
+
}
|
|
138
|
+
.drop-zone.active {
|
|
139
|
+
border-color: var(--accent);
|
|
140
|
+
background: color-mix(in oklab, var(--accent) 10%, var(--bg));
|
|
141
|
+
color: var(--accent-ink);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* List item reorder indicator */
|
|
145
|
+
.list-item.dragging { opacity: 0.5; }
|
|
146
|
+
.list-item.drag-before::before,
|
|
147
|
+
.list-item.drag-after::after {
|
|
148
|
+
content: '';
|
|
149
|
+
position: absolute;
|
|
150
|
+
left: 0; right: 0; height: 2px;
|
|
151
|
+
background: var(--accent);
|
|
152
|
+
}
|
|
153
|
+
.list-item.drag-before::before { top: -2px; }
|
|
154
|
+
.list-item.drag-after::after { bottom: -2px; }
|
|
155
|
+
|
|
156
|
+
@keyframes pulse-line {
|
|
157
|
+
0%, 100% { opacity: 0.3; }
|
|
158
|
+
50% { opacity: 1; }
|
|
159
|
+
}
|
|
160
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
161
|
+
.list-item.drag-before::before,
|
|
162
|
+
.list-item.drag-after::after { animation: pulse-line 0.6s ease-in-out infinite; }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ------------------------------------------------------------
|
|
166
|
+
Context Menu Styles
|
|
167
|
+
-------------------------------------------------------------- */
|
|
168
|
+
|
|
169
|
+
.ds-context-menu {
|
|
170
|
+
position: absolute;
|
|
171
|
+
background: var(--bg-2);
|
|
172
|
+
border: 1px solid var(--rule);
|
|
173
|
+
border-radius: var(--r-2);
|
|
174
|
+
box-shadow: var(--shadow-overlay);
|
|
175
|
+
min-width: 160px;
|
|
176
|
+
z-index: calc(var(--z-overlay, 1000) + 1);
|
|
177
|
+
padding: 4px 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@keyframes context-menu-in {
|
|
181
|
+
from {
|
|
182
|
+
opacity: 0;
|
|
183
|
+
transform: scale(0.95) translateY(-4px);
|
|
184
|
+
}
|
|
185
|
+
to {
|
|
186
|
+
opacity: 1;
|
|
187
|
+
transform: scale(1) translateY(0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
191
|
+
.ds-context-menu { animation: context-menu-in 0.15s ease-out; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.ds-context-menu-item {
|
|
195
|
+
display: flex; align-items: center; gap: 10px;
|
|
196
|
+
padding: 10px 14px;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
color: var(--fg);
|
|
199
|
+
font-size: var(--fs-sm);
|
|
200
|
+
transition: background var(--dur-snap) var(--ease);
|
|
201
|
+
}
|
|
202
|
+
.ds-context-menu-item:hover {
|
|
203
|
+
background: var(--bg-3);
|
|
204
|
+
color: var(--accent-ink);
|
|
205
|
+
}
|
|
206
|
+
.ds-context-menu-item:active {
|
|
207
|
+
background: color-mix(in oklab, var(--accent) 20%, var(--bg-3));
|
|
208
|
+
}
|
|
209
|
+
.ds-context-menu-item.danger {
|
|
210
|
+
color: var(--warn);
|
|
211
|
+
}
|
|
212
|
+
.ds-context-menu-item.danger:hover {
|
|
213
|
+
background: color-mix(in oklab, var(--warn) 12%, var(--bg));
|
|
214
|
+
}
|
|
215
|
+
.ds-context-menu-divider {
|
|
216
|
+
height: 1px;
|
|
217
|
+
background: var(--rule);
|
|
218
|
+
margin: 4px 0;
|
|
219
|
+
}
|
|
220
|
+
.ds-context-menu-icon {
|
|
221
|
+
width: 16px; height: 16px;
|
|
222
|
+
flex-shrink: 0;
|
|
223
|
+
opacity: 0.7;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Context menu trigger indicator */
|
|
227
|
+
[data-has-context-menu] {
|
|
228
|
+
cursor: context-menu;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ------------------------------------------------------------
|
|
232
|
+
Enhanced Form Inputs
|
|
233
|
+
-------------------------------------------------------------- */
|
|
234
|
+
|
|
235
|
+
/* Base chrome for any BARE native control (outside .ds-field/.row-form/.input):
|
|
236
|
+
the same editorial tonal-fill + printed-baseline-rule as .ds-field, so a raw
|
|
237
|
+
<input> is never a browser-default white box in dark mode. The guards are
|
|
238
|
+
wrapped in :where() so they add ZERO specificity — the whole selector stays
|
|
239
|
+
at (0,1,1), exactly like `.ds-field input`, so the later .ds-field / .ds-field--sm
|
|
240
|
+
/ .row-form descendant rules win by source order instead of being out-specified
|
|
241
|
+
(a bare :not(.input) counts as a class and would beat every .ds-field rule,
|
|
242
|
+
which clobbered the size variants). */
|
|
243
|
+
input[type="text"]:where(:not(.input):not(.ds-search-input)),
|
|
244
|
+
input[type="email"]:where(:not(.input):not(.ds-search-input)),
|
|
245
|
+
input[type="password"]:where(:not(.input):not(.ds-search-input)),
|
|
246
|
+
input[type="number"]:where(:not(.input):not(.ds-search-input)),
|
|
247
|
+
input[type="search"]:where(:not(.input):not(.ds-search-input)),
|
|
248
|
+
input[type="url"]:where(:not(.input):not(.ds-search-input)),
|
|
249
|
+
input[type="tel"]:where(:not(.input):not(.ds-search-input)),
|
|
250
|
+
textarea:where(:not(.input)),
|
|
251
|
+
select:where(:not(.ds-select)) {
|
|
252
|
+
font: inherit; color: var(--fg);
|
|
253
|
+
background: var(--bg-2);
|
|
254
|
+
border: 0;
|
|
255
|
+
box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
|
|
256
|
+
inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
|
|
257
|
+
border-radius: var(--r-1);
|
|
258
|
+
padding: 0 var(--space-3);
|
|
259
|
+
transition: background var(--dur-snap) var(--ease),
|
|
260
|
+
border-color var(--dur-snap) var(--ease),
|
|
261
|
+
box-shadow var(--dur-snap) var(--ease);
|
|
262
|
+
}
|
|
263
|
+
/* Single-line bare controls snap to the field height; textarea keeps auto
|
|
264
|
+
height with symmetric padding (its content decides the box). */
|
|
265
|
+
input[type="text"]:where(:not(.input):not(.ds-search-input)),
|
|
266
|
+
input[type="email"]:where(:not(.input):not(.ds-search-input)),
|
|
267
|
+
input[type="password"]:where(:not(.input):not(.ds-search-input)),
|
|
268
|
+
input[type="number"]:where(:not(.input):not(.ds-search-input)),
|
|
269
|
+
input[type="search"]:where(:not(.input):not(.ds-search-input)),
|
|
270
|
+
input[type="url"]:where(:not(.input):not(.ds-search-input)),
|
|
271
|
+
input[type="tel"]:where(:not(.input):not(.ds-search-input)),
|
|
272
|
+
select:where(:not(.ds-select)) {
|
|
273
|
+
height: var(--field-height);
|
|
274
|
+
}
|
|
275
|
+
textarea:where(:not(.input)) { padding: var(--space-2) var(--space-3); min-height: calc(4 * 1.5em); }
|
|
276
|
+
input[type="text"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
277
|
+
input[type="email"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
278
|
+
input[type="password"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
279
|
+
input[type="number"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
280
|
+
input[type="search"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
281
|
+
input[type="url"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
282
|
+
input[type="tel"]:where(:not(.input):not(.ds-search-input)):focus-visible,
|
|
283
|
+
textarea:where(:not(.input)):focus-visible,
|
|
284
|
+
select:where(:not(.ds-select)):focus-visible {
|
|
285
|
+
outline: none;
|
|
286
|
+
background: var(--bg);
|
|
287
|
+
box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
|
|
288
|
+
inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
input[type="text"]::placeholder,
|
|
292
|
+
input[type="email"]::placeholder,
|
|
293
|
+
input[type="password"]::placeholder,
|
|
294
|
+
input[type="number"]::placeholder,
|
|
295
|
+
input[type="search"]::placeholder,
|
|
296
|
+
input[type="url"]::placeholder,
|
|
297
|
+
textarea::placeholder {
|
|
298
|
+
color: var(--fg-3);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* Long-form reading voice — quiet sans, generous line-height, capped to a
|
|
302
|
+
readable measure so a wide viewport never stretches body copy edge to
|
|
303
|
+
edge. Links use --accent-ink (never the bare fill, which is unreadable
|
|
304
|
+
as text). */
|
|
305
|
+
.prose {
|
|
306
|
+
max-width: var(--measure);
|
|
307
|
+
font-family: var(--ff-body); font-size: var(--fs-body); line-height: 1.6;
|
|
308
|
+
color: var(--fg);
|
|
309
|
+
}
|
|
310
|
+
.prose p { margin: 0 0 var(--space-4) 0; }
|
|
311
|
+
.prose p:last-child { margin-bottom: 0; }
|
|
312
|
+
.prose a { color: var(--accent-ink); text-decoration-color: var(--rule-strong); text-underline-offset: 0.15em; }
|
|
313
|
+
.prose a:hover { text-decoration-color: currentColor; }
|
|
314
|
+
|
|
315
|
+
/* Rubber-stamp motif — a bordered, uppercase, letter-spaced badge with a
|
|
316
|
+
slight built-in rotation (variants may override via inline transform for
|
|
317
|
+
a stronger tilt). Max 1 per page per the component's own caption. */
|
|
318
|
+
.stamp {
|
|
319
|
+
display: inline-block;
|
|
320
|
+
font-family: var(--ff-mono); font-size: var(--fs-xs); font-weight: 700;
|
|
321
|
+
text-transform: uppercase; letter-spacing: 0.08em;
|
|
322
|
+
padding: var(--space-1-5, var(--space-2)) var(--space-3);
|
|
323
|
+
border: 2px solid var(--fg);
|
|
324
|
+
border-radius: var(--r-hair);
|
|
325
|
+
color: var(--fg);
|
|
326
|
+
transform: rotate(-2deg);
|
|
327
|
+
white-space: nowrap;
|
|
328
|
+
}
|
|
329
|
+
.stamp.ink { background: var(--ink); border-color: var(--ink); color: var(--paper); }
|
|
330
|
+
.stamp.acid { background: var(--accent); border-color: var(--accent-ink); color: var(--accent-ink); }
|
|
331
|
+
.stamp.purple { border-color: var(--purple-2); color: var(--purple-2); }
|
|
332
|
+
.stamp.mascot { border-color: var(--mascot); color: var(--mascot-deep); }
|
|
333
|
+
|
|
334
|
+
/* Same rubber-stamp face, but a real interactive <button> — resets UA button
|
|
335
|
+
chrome so it isn't blue link text in a browser default box. */
|
|
336
|
+
.btn-stamp {
|
|
337
|
+
display: inline-block; cursor: pointer;
|
|
338
|
+
font-family: var(--ff-mono); font-size: var(--fs-xs); font-weight: 700;
|
|
339
|
+
text-transform: uppercase; letter-spacing: 0.08em;
|
|
340
|
+
padding: var(--space-1-5, var(--space-2)) var(--space-3);
|
|
341
|
+
border: 2px solid var(--fg);
|
|
342
|
+
border-radius: var(--r-hair);
|
|
343
|
+
background: transparent; color: var(--fg);
|
|
344
|
+
transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
|
|
345
|
+
}
|
|
346
|
+
.btn-stamp:hover { background: var(--panel-hover); }
|
|
347
|
+
.btn-stamp.acid { border-color: var(--accent-ink); color: var(--accent-ink); }
|
|
348
|
+
.btn-stamp.acid:hover { background: var(--accent-tint); }
|
|
349
|
+
.btn-stamp.purple { border-color: var(--purple-2); color: var(--purple-2); }
|
|
350
|
+
.btn-stamp.mascot { border-color: var(--mascot); color: var(--mascot-deep); }
|
|
351
|
+
|
|
352
|
+
/* Structural hairlines — "no boxes, no cards, structure comes from rules."
|
|
353
|
+
Three weights: .rule (default separator), .rule-double (major section
|
|
354
|
+
break), .rule-dotted (optional/skippable). Applied to a bare <hr>, which
|
|
355
|
+
otherwise falls back to the browser's default inset groove line. */
|
|
356
|
+
hr.rule, hr.rule-double, hr.rule-dotted {
|
|
357
|
+
width: 100%; height: 0; border: 0;
|
|
358
|
+
}
|
|
359
|
+
hr.rule { border-top: 1px solid var(--rule); }
|
|
360
|
+
hr.rule-double { border-top: 3px double var(--rule-strong); }
|
|
361
|
+
hr.rule-dotted { border-top: 1px dotted var(--rule-strong); }
|
|
362
|
+
|
|
363
|
+
/* Hairline-only text input — no box, no fill. A single bottom rule that
|
|
364
|
+
thickens to 2px on focus, matching the row/rule zero-border aesthetic
|
|
365
|
+
instead of a browser-default boxed field. */
|
|
366
|
+
.input {
|
|
367
|
+
display: block; width: 100%; box-sizing: border-box;
|
|
368
|
+
border: 0; border-bottom: 1px solid var(--rule-strong);
|
|
369
|
+
border-radius: var(--r-0);
|
|
370
|
+
background: transparent;
|
|
371
|
+
padding: var(--space-2) var(--space-1);
|
|
372
|
+
font: inherit; color: var(--fg);
|
|
373
|
+
}
|
|
374
|
+
.input::placeholder { color: var(--fg-3); }
|
|
375
|
+
.input:focus-visible {
|
|
376
|
+
outline: none;
|
|
377
|
+
border-bottom-width: 2px;
|
|
378
|
+
border-bottom-color: var(--accent-ink);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* -- Field controls: themed base for TextField / Select / SearchInput --
|
|
382
|
+
Root fix: previously only `transition` was set, so themed apps got
|
|
383
|
+
browser-default white boxes in dark mode and labels collided with inputs
|
|
384
|
+
because `.ds-field` had no layout. */
|
|
385
|
+
.ds-field {
|
|
386
|
+
display: flex;
|
|
387
|
+
flex-direction: column;
|
|
388
|
+
gap: var(--space-1, 6px);
|
|
389
|
+
align-items: stretch;
|
|
390
|
+
width: 100%;
|
|
391
|
+
}
|
|
392
|
+
.ds-field-label {
|
|
393
|
+
font-size: var(--fs-sm, 14px);
|
|
394
|
+
color: var(--fg-2);
|
|
395
|
+
line-height: 1.3;
|
|
396
|
+
}
|
|
397
|
+
.ds-field-hint {
|
|
398
|
+
font-size: var(--fs-tiny, 13px);
|
|
399
|
+
color: var(--fg-3);
|
|
400
|
+
line-height: 1.35;
|
|
401
|
+
}
|
|
402
|
+
.ds-field-count {
|
|
403
|
+
font-size: var(--fs-tiny, 13px);
|
|
404
|
+
color: var(--fg-3);
|
|
405
|
+
align-self: flex-end;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.ds-field input,
|
|
409
|
+
.ds-field textarea,
|
|
410
|
+
.ds-field .ds-select,
|
|
411
|
+
.ds-search-input {
|
|
412
|
+
width: 100%;
|
|
413
|
+
box-sizing: border-box;
|
|
414
|
+
font: inherit;
|
|
415
|
+
color: var(--fg);
|
|
416
|
+
background: var(--bg-2);
|
|
417
|
+
border: 0;
|
|
418
|
+
/* Editorial field — tonal fill with a printed baseline rule that thickens to
|
|
419
|
+
the electric lead on focus, instead of a uniform hairline box. */
|
|
420
|
+
box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
|
|
421
|
+
inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
|
|
422
|
+
/* Tighter corner than the old --r-2 (14px): a dense pro control, not a soft
|
|
423
|
+
consumer box. Single-line controls snap to --field-height with h-only
|
|
424
|
+
padding so they align pixel-exact with buttons on the same --ctl-md rung. */
|
|
425
|
+
border-radius: var(--r-1);
|
|
426
|
+
height: var(--field-height);
|
|
427
|
+
padding: 0 var(--space-3);
|
|
428
|
+
transition: box-shadow var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
|
|
429
|
+
}
|
|
430
|
+
/* textarea is multi-line: height is auto (min-height instead), keep symmetric
|
|
431
|
+
vertical padding so text doesn't sit flush to the top edge. */
|
|
432
|
+
.ds-field textarea { height: auto; min-height: calc(4 * 1.5em); padding: var(--space-2) var(--space-3); resize: vertical; }
|
|
433
|
+
|
|
434
|
+
/* Field size ladder (size prop on TextField/Select/SearchInput). md == base.
|
|
435
|
+
sm/lg retune height + h-padding + font off the --ctl-* ladder, matching the
|
|
436
|
+
button ladder so a form of mixed controls stays on one rhythm. */
|
|
437
|
+
.ds-field--sm input, .ds-field--sm .ds-select, .ds-field--sm .ds-search-input {
|
|
438
|
+
height: var(--ctl-sm); padding: 0 var(--space-2-5); font-size: var(--fs-tiny);
|
|
439
|
+
}
|
|
440
|
+
.ds-field--sm textarea { height: auto; padding: var(--space-1-75) var(--space-2-5); font-size: var(--fs-tiny); }
|
|
441
|
+
.ds-field--lg input, .ds-field--lg .ds-select, .ds-field--lg .ds-search-input {
|
|
442
|
+
height: var(--ctl-lg); padding: 0 var(--space-4); font-size: var(--fs-body);
|
|
443
|
+
}
|
|
444
|
+
.ds-field--lg textarea { height: auto; padding: var(--space-2-5) var(--space-4); font-size: var(--fs-body); }
|
|
445
|
+
.ds-search-input::placeholder { color: var(--fg-3); }
|
|
446
|
+
/* Only present when SearchInput is passed resultCount or has a value (clear
|
|
447
|
+
button) - transparent to any flex/grid layout the bare input previously
|
|
448
|
+
sat in directly. */
|
|
449
|
+
.ds-search-input-wrap { display: contents; }
|
|
450
|
+
|
|
451
|
+
/* Visible clear (X) button for SearchInput - mirrors .ds-alert-dismiss's
|
|
452
|
+
ghost-icon treatment but sized to the kit's 44px touch-target floor
|
|
453
|
+
(app-shell.css already establishes this convention for .ds-file-check /
|
|
454
|
+
.ds-density-btn / .ds-upload-act etc). Positioned by the consumer's own
|
|
455
|
+
flex/grid context (ds-search-input-wrap is display:contents), so it simply
|
|
456
|
+
sits after the input in DOM order. */
|
|
457
|
+
.ds-search-clear {
|
|
458
|
+
display: inline-flex;
|
|
459
|
+
align-items: center; justify-content: center;
|
|
460
|
+
min-width: 44px; min-height: 44px;
|
|
461
|
+
flex-shrink: 0;
|
|
462
|
+
cursor: pointer;
|
|
463
|
+
color: var(--fg-3);
|
|
464
|
+
background: transparent;
|
|
465
|
+
border: 0;
|
|
466
|
+
padding: 0;
|
|
467
|
+
border-radius: var(--r-1);
|
|
468
|
+
transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
|
|
469
|
+
}
|
|
470
|
+
.ds-search-clear:hover { background: var(--bg-2); color: var(--fg); }
|
|
471
|
+
.ds-search-clear:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
|
|
472
|
+
|
|
473
|
+
.ds-field input:focus-visible,
|
|
474
|
+
.ds-field textarea:focus-visible,
|
|
475
|
+
.ds-field .ds-select:focus-visible,
|
|
476
|
+
.ds-search-input:focus-visible {
|
|
477
|
+
outline: none;
|
|
478
|
+
background: var(--bg);
|
|
479
|
+
/* Focus thickens the baseline to the lead accent — the printed-rule tell. */
|
|
480
|
+
box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
|
|
481
|
+
inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
|
|
482
|
+
}
|
|
483
|
+
.ds-field input:disabled,
|
|
484
|
+
.ds-field textarea:disabled,
|
|
485
|
+
.ds-field .ds-select:disabled {
|
|
486
|
+
opacity: .55;
|
|
487
|
+
cursor: not-allowed;
|
|
488
|
+
}
|
|
489
|
+
.ds-field input[aria-invalid="true"],
|
|
490
|
+
.ds-field textarea[aria-invalid="true"] {
|
|
491
|
+
box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--flame);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Clear button for text inputs */
|
|
495
|
+
input[type="text"]:not(:placeholder-shown) + .input-clear,
|
|
496
|
+
input[type="email"]:not(:placeholder-shown) + .input-clear,
|
|
497
|
+
input[type="password"]:not(:placeholder-shown) + .input-clear {
|
|
498
|
+
display: inline-flex;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.input-clear {
|
|
502
|
+
display: none;
|
|
503
|
+
align-items: center; justify-content: center;
|
|
504
|
+
width: 32px; height: 32px;
|
|
505
|
+
cursor: pointer;
|
|
506
|
+
color: var(--fg-3);
|
|
507
|
+
background: transparent;
|
|
508
|
+
border: 0;
|
|
509
|
+
padding: 0;
|
|
510
|
+
margin-right: 8px;
|
|
511
|
+
border-radius: var(--r-1);
|
|
512
|
+
transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
|
|
513
|
+
}
|
|
514
|
+
.input-clear:hover {
|
|
515
|
+
background: var(--bg-2);
|
|
516
|
+
color: var(--fg);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* ------------------------------------------------------------
|
|
520
|
+
Accessibility Enhancements
|
|
521
|
+
-------------------------------------------------------------- */
|
|
522
|
+
|
|
523
|
+
/* Base focus-visible: 2px ring + 2px offset clears dark bg-3 surfaces (WCAG 1.4.11) */
|
|
524
|
+
:focus-visible {
|
|
525
|
+
outline: var(--focus-w) solid var(--focus-color);
|
|
526
|
+
outline-offset: var(--focus-offset);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* Enhanced focus-visible for all interactive elements */
|
|
530
|
+
[role="button"]:focus-visible,
|
|
531
|
+
[role="link"]:focus-visible,
|
|
532
|
+
[role="tab"]:focus-visible,
|
|
533
|
+
.row:focus-visible,
|
|
534
|
+
.row[tabindex]:focus-visible {
|
|
535
|
+
outline: var(--focus-w) solid var(--focus-color);
|
|
536
|
+
outline-offset: var(--focus-offset);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* Skip to main content link */
|
|
540
|
+
.skip-to-main {
|
|
541
|
+
position: absolute; top: -40px; left: 0;
|
|
542
|
+
background: var(--fg); color: var(--bg);
|
|
543
|
+
padding: 8px 16px; border-radius: var(--r-1);
|
|
544
|
+
text-decoration: none; z-index: var(--z-overlay, 1000);
|
|
545
|
+
}
|
|
546
|
+
.skip-to-main:focus { top: 10px; }
|
|
547
|
+
|
|
548
|
+
/* Reduced motion preferences — scoped to the DS surface so it doesn't reach
|
|
549
|
+
into and neutralize the host document's own motion. */
|
|
550
|
+
@media (prefers-reduced-motion: reduce) {
|
|
551
|
+
.ds-247420 *, .app * {
|
|
552
|
+
animation-duration: 0.01ms !important;
|
|
553
|
+
animation-iteration-count: 1 !important;
|
|
554
|
+
transition-duration: 0.01ms !important;
|
|
555
|
+
}
|
|
556
|
+
/* Kill the residual paint frame of looping skeleton/shimmer placeholders and
|
|
557
|
+
give reduced-motion users a steady placeholder fill instead. */
|
|
558
|
+
.skeleton, .ds-skeleton, .ds-session-row-skeleton .ds-skel {
|
|
559
|
+
animation: none !important;
|
|
560
|
+
background: var(--bg-2) !important;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/* ------------------------------------------------------------
|
|
565
|
+
Performance
|
|
566
|
+
-------------------------------------------------------------- */
|
|
567
|
+
|
|
568
|
+
/* Limit layout/style containment to self-contained components. `paint` is
|
|
569
|
+
intentionally omitted — it clips focus rings, tooltips and dropdowns that
|
|
570
|
+
emanate from a contained .row/.btn. */
|
|
571
|
+
.panel, .row {
|
|
572
|
+
contain: layout style;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/* ------------------------------------------------------------
|
|
576
|
+
Mobile Touch Optimizations
|
|
577
|
+
-------------------------------------------------------------- */
|
|
578
|
+
|
|
579
|
+
@media (hover: none) and (pointer: coarse) {
|
|
580
|
+
/* Mobile devices: larger touch targets, faster responses */
|
|
581
|
+
.btn, .btn-primary, .btn-ghost,
|
|
582
|
+
button, a.row, [role="button"],
|
|
583
|
+
.ds-icon-btn, .composer-btn {
|
|
584
|
+
min-height: 48px;
|
|
585
|
+
min-width: 48px;
|
|
586
|
+
padding: 12px 20px;
|
|
587
|
+
}
|
|
588
|
+
/* Icon buttons stay square — the padding bump above would distort them. */
|
|
589
|
+
.ds-icon-btn, .composer-btn { padding: 0; }
|
|
590
|
+
|
|
591
|
+
/* Remove hover effects on touch devices (use active instead) */
|
|
592
|
+
.btn:hover, .btn-primary:hover, .btn-ghost:hover {
|
|
593
|
+
background-color: inherit;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* Momentum scrolling for iOS */
|
|
597
|
+
.app-side, .panel, .row {
|
|
598
|
+
-webkit-overflow-scrolling: touch;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* Long-press feedback for drag-enabled items */
|
|
602
|
+
[draggable="true"], .draggable {
|
|
603
|
+
-webkit-user-select: none;
|
|
604
|
+
-webkit-touch-callout: none;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* Touch floor also for coarse-pointer devices that report hover (hybrid
|
|
609
|
+
laptops/tablets with mice) — the block above requires hover:none too. */
|
|
610
|
+
@media (pointer: coarse) {
|
|
611
|
+
.ds-icon-btn, .ds-icon-btn-xs, .ds-icon-btn-sm, .ds-icon-btn-base {
|
|
612
|
+
min-width: 44px; min-height: 44px;
|
|
613
|
+
}
|
|
614
|
+
.btn, .btn-primary, .btn-ghost,
|
|
615
|
+
.app-topbar nav a, .app-side a { min-height: 44px; }
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* ------------------------------------------------------------
|
|
619
|
+
Theme Transition Smoothness
|
|
620
|
+
-------------------------------------------------------------- */
|
|
621
|
+
|
|
622
|
+
/* Scoped to the DS wrapper so the design system never reaches out and
|
|
623
|
+
animates the host document's html/body. */
|
|
624
|
+
.ds-247420, .app {
|
|
625
|
+
transition: background-color var(--dur-base) var(--ease),
|
|
626
|
+
color var(--dur-base) var(--ease);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/* ------------------------------------------------------------
|
|
630
|
+
Empty State & Loading States
|
|
631
|
+
-------------------------------------------------------------- */
|
|
632
|
+
|
|
633
|
+
/* Skeleton loading state */
|
|
634
|
+
.skeleton {
|
|
635
|
+
background: linear-gradient(
|
|
636
|
+
90deg,
|
|
637
|
+
var(--bg-2) 0%,
|
|
638
|
+
var(--bg-3) 50%,
|
|
639
|
+
var(--bg-2) 100%
|
|
640
|
+
);
|
|
641
|
+
background-size: 200% 100%;
|
|
642
|
+
animation: skeleton-load 1.5s infinite;
|
|
643
|
+
border-radius: var(--r-2);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
@keyframes skeleton-load {
|
|
647
|
+
0% { background-position: 200% 0; }
|
|
648
|
+
100% { background-position: -200% 0; }
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.skeleton-line { height: 12px; margin-bottom: 12px; }
|
|
652
|
+
.skeleton-title { height: 24px; margin-bottom: 16px; width: 60%; }
|
|
653
|
+
|
|
654
|
+
/* ------------------------------------------------------------
|
|
655
|
+
Toast Notification Styles
|
|
656
|
+
-------------------------------------------------------------- */
|
|
657
|
+
|
|
658
|
+
.toast {
|
|
659
|
+
position: fixed;
|
|
660
|
+
bottom: 24px; right: 24px;
|
|
661
|
+
background: var(--bg-2);
|
|
662
|
+
border: 1px solid var(--rule);
|
|
663
|
+
border-radius: var(--r-2);
|
|
664
|
+
padding: var(--space-3) var(--space-4);
|
|
665
|
+
max-width: 320px;
|
|
666
|
+
box-shadow: var(--shadow-overlay);
|
|
667
|
+
z-index: calc(var(--z-overlay, 1000) + 10);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
@keyframes toast-slide-in {
|
|
671
|
+
from {
|
|
672
|
+
opacity: 0;
|
|
673
|
+
transform: translateX(400px);
|
|
674
|
+
}
|
|
675
|
+
to {
|
|
676
|
+
opacity: 1;
|
|
677
|
+
transform: translateX(0);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
681
|
+
.toast { animation: toast-slide-in 0.3s ease-out; }
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.toast.error { border-color: var(--warn); }
|
|
685
|
+
.toast.success { border-color: var(--green-2); }
|
|
686
|
+
/* --sun measures well under 3:1 against every app background (1.1-1.5:1) -
|
|
687
|
+
a border effectively invisible in light theme; --amber is the tuned sibling
|
|
688
|
+
warning tone already used for this exact severity elsewhere. */
|
|
689
|
+
.toast.warning { border-color: var(--amber); }
|
|
690
|
+
|
|
691
|
+
@media (max-width: 480px) {
|
|
692
|
+
.toast {
|
|
693
|
+
bottom: 16px; right: 16px; left: 16px;
|
|
694
|
+
max-width: none;
|
|
695
|
+
}
|
|
696
|
+
}
|