@aparte/core 0.16.7 → 0.16.9
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/dist/client/aparte-client.d.ts +57 -2
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/lifecycle-events.d.ts +8 -1
- package/dist/client/lifecycle-events.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +8 -0
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +39 -18
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +32 -25
- package/dist/components/chat/aparte-chat.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +2 -2
- package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +6 -6
- package/dist/components/composer/aparte-composer-send.d.ts +3 -3
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +9 -2
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts +0 -1
- package/dist/components/context/aparte-context.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +11 -6
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +20 -18
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts +11 -3
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -1
- package/dist/components/sidebar/aparte-sidebar.d.ts +16 -2
- package/dist/components/sidebar/aparte-sidebar.d.ts.map +1 -1
- package/dist/components/split/aparte-split.d.ts +9 -4
- package/dist/components/split/aparte-split.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +44 -5
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +27 -4
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/icon-provider.d.ts +6 -0
- package/dist/config/icon-provider.d.ts.map +1 -1
- package/dist/config/locale.d.ts +3 -1
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/custom-elements.json +13010 -12737
- package/dist/{dialog-triggers-Dw_DIlQ7.js → dialog-triggers-CApC7K1I.js} +119 -44
- package/dist/dialog-triggers-CApC7K1I.js.map +1 -0
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/generated/element-attributes.d.ts +13 -3
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/{glyphs-DQTeOj4w.js → glyphs-C8z2oJUg.js} +36 -27
- package/dist/{glyphs-DQTeOj4w.js.map → glyphs-C8z2oJUg.js.map} +1 -1
- package/dist/icons/extended.d.ts +0 -6
- package/dist/icons/extended.d.ts.map +1 -1
- package/dist/icons/glyphs.d.ts +13 -4
- package/dist/icons/glyphs.d.ts.map +1 -1
- package/dist/icons.js +28 -31
- package/dist/icons.js.map +1 -1
- package/dist/index.css +946 -386
- package/dist/index.js +321 -89
- package/dist/index.js.map +1 -1
- package/dist/index.node.js +1 -1
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/primitives/icon/aparte-icon.d.ts +1 -1
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts +2 -3
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts +4 -4
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +43 -12
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segments/code.d.ts.map +1 -1
- package/dist/renderers/segments/error.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/types/events.d.ts +4 -2
- package/dist/types/events.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/dialog-triggers-Dw_DIlQ7.js.map +0 -1
package/dist/index.css
CHANGED
|
@@ -183,7 +183,6 @@
|
|
|
183
183
|
below (it was a frozen plum hex that outlived every rebrand); the assistant is
|
|
184
184
|
plain prose (AI-chat convention) — flip it to get a two-bubble chat-app look. */
|
|
185
185
|
--aparte-message-content-bg-assistant: transparent;
|
|
186
|
-
--aparte-message-content-radius: 14px;
|
|
187
186
|
|
|
188
187
|
/* Aliases used by some segment renderers (artifact card, tool pill).
|
|
189
188
|
Mapped to existing tokens so consumers that override --aparte-surface-1/2
|
|
@@ -206,8 +205,6 @@
|
|
|
206
205
|
--aparte-thumb-remove-bg-hover: rgba(0, 0, 0, 0.85);
|
|
207
206
|
--aparte-thumb-remove-color: #ffffff;
|
|
208
207
|
|
|
209
|
-
/* Action Bar */
|
|
210
|
-
--aparte-action-bar-btn-size: 28px;
|
|
211
208
|
/* The mark (display/mark.css): how much of its intent a marked row's ground takes,
|
|
212
209
|
and how wide the bar on its start edge is. One pair for the select's chosen option,
|
|
213
210
|
the checked field choice, the active conversation and an answered question. */
|
|
@@ -215,12 +212,16 @@
|
|
|
215
212
|
--aparte-mark-bar: 2px;
|
|
216
213
|
|
|
217
214
|
/* Branch Picker */
|
|
218
|
-
--aparte-branch-picker-btn-size: 20px;
|
|
219
|
-
--aparte-branch-picker-btn-icon-size: 16px;
|
|
220
215
|
--aparte-branch-picker-label-min-width: 32px;
|
|
221
216
|
|
|
222
217
|
/* Avatars */
|
|
223
218
|
--aparte-avatar-size: 32px;
|
|
219
|
+
/* Fractions of the size, computed on the element (display/avatar.css): the initials'
|
|
220
|
+
type, the corner and the group's overlap follow the ramp instead of sitting at one
|
|
221
|
+
absolute value for 20px and 56px alike. */
|
|
222
|
+
--aparte-avatar-initials-ratio: 0.34;
|
|
223
|
+
--aparte-avatar-radius-ratio: 0.28;
|
|
224
|
+
--aparte-avatar-overlap-ratio: 0.28;
|
|
224
225
|
--aparte-avatar-image-user: none;
|
|
225
226
|
--aparte-avatar-image-assistant: none;
|
|
226
227
|
--aparte-avatar-image-size: 90%;
|
|
@@ -230,21 +231,18 @@
|
|
|
230
231
|
/* Input */
|
|
231
232
|
--aparte-input-editor-max-height: 200px;
|
|
232
233
|
|
|
233
|
-
/* Icons
|
|
234
|
-
|
|
235
|
-
--aparte-icon-size-sm: 12px;
|
|
236
|
-
--aparte-icon-size-lg: 16px;
|
|
237
|
-
--aparte-icon-size-xl: 20px;
|
|
234
|
+
/* Icons: one knob, inherited — a container declares `--aparte-icon-size` and every
|
|
235
|
+
glyph below follows. The steps are on the type scale, in the derived layer. */
|
|
238
236
|
|
|
239
|
-
/* Send button + inline action buttons */
|
|
240
|
-
--aparte-send-btn-size: 36px;
|
|
241
|
-
--aparte-input-action-btn-size: 36px;
|
|
237
|
+
/* Send button + inline action buttons (their size is the lg step, in the derived layer) */
|
|
242
238
|
--aparte-input-action-btn-icon-size: 20px;
|
|
243
239
|
--aparte-model-select-min-width: 120px;
|
|
244
240
|
|
|
245
241
|
/* Status Indicator */
|
|
246
242
|
--aparte-status-bg: transparent;
|
|
247
243
|
--aparte-status-dot-size: 6px;
|
|
244
|
+
/* The floor of every pulsing dot's opacity: above the perception threshold. */
|
|
245
|
+
--aparte-pulse-floor: 0.55;
|
|
248
246
|
|
|
249
247
|
/* Waiting Indicator (the bubble's own dots, before the first token) */
|
|
250
248
|
--aparte-waiting-height: 1.5em;
|
|
@@ -252,7 +250,6 @@
|
|
|
252
250
|
/* Thinking Segment */
|
|
253
251
|
--aparte-thinking-content-max-height: 300px;
|
|
254
252
|
--aparte-thinking-content-line-height: 1.55;
|
|
255
|
-
--aparte-thinking-toggle-size: 9px;
|
|
256
253
|
--aparte-thinking-rail-width: 2px;
|
|
257
254
|
|
|
258
255
|
/* Elicitation — the panel a tool's question is asked in.
|
|
@@ -282,8 +279,7 @@
|
|
|
282
279
|
--aparte-code-copy-icon-size: 14px;
|
|
283
280
|
--aparte-code-inline-font-size: 0.875em;
|
|
284
281
|
|
|
285
|
-
/* Scroll-to-bottom button */
|
|
286
|
-
--aparte-scroll-btn-size: 36px;
|
|
282
|
+
/* Scroll-to-bottom button (its size is the lg step, in the derived layer) */
|
|
287
283
|
--aparte-scroll-btn-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
288
284
|
|
|
289
285
|
/* Error segment */
|
|
@@ -291,8 +287,6 @@
|
|
|
291
287
|
--aparte-error-border: #fecaca;
|
|
292
288
|
--aparte-error-solid: #dc2626;
|
|
293
289
|
--aparte-error-text: #991b1b;
|
|
294
|
-
--aparte-error-title: #7f1d1d;
|
|
295
|
-
--aparte-error-icon-size: 20px;
|
|
296
290
|
--aparte-error-rail-width: 4px;
|
|
297
291
|
|
|
298
292
|
/* Prose (aparte-segment-content) */
|
|
@@ -327,21 +321,21 @@
|
|
|
327
321
|
|
|
328
322
|
/* Input (composer) */
|
|
329
323
|
|
|
330
|
-
/* Conversation List */
|
|
331
|
-
--aparte-conv-action-btn-size: 20px;
|
|
332
324
|
|
|
333
325
|
/* Scroll rail — the ticks beside the transcript. Off-scale on purpose: a 2px line
|
|
334
326
|
and a 14px mark are a minimap's measures, not spacing steps. */
|
|
335
|
-
--aparte-scroll-rail-width: 20px;
|
|
336
327
|
--aparte-scroll-rail-tick-size: 14px;
|
|
337
328
|
--aparte-scroll-rail-tick-thickness: 2px;
|
|
338
|
-
|
|
339
|
-
|
|
329
|
+
/* What a tick is to a POINTER, as opposed to what it is to the eye: WCAG 2.5.8's
|
|
330
|
+
24px minimum. The drawn line stays 14×2 — this sizes the pseudo-element around it,
|
|
331
|
+
and the gap below derives from it so the pitch rises with the zone. The rail is
|
|
332
|
+
`display: none` under `(pointer: coarse)`, so 2.5.8's 24px is the bar here and not
|
|
333
|
+
2.5.5's 44px. */
|
|
334
|
+
--aparte-scroll-rail-hit-size: 24px;
|
|
340
335
|
|
|
341
336
|
/* Context gauge, ring variant — a diameter, and a stroke in the ring's own 36-unit
|
|
342
337
|
box. Literals like the rail's: a 22px ring is a toolbar's measure, not a spacing step. */
|
|
343
338
|
--aparte-context-ring-size: 22px;
|
|
344
|
-
--aparte-context-ring-stroke: 4;
|
|
345
339
|
|
|
346
340
|
/* Shell — the sidebar's column, the header's row and the split's seam. */
|
|
347
341
|
--aparte-sidebar-width: 260px;
|
|
@@ -353,9 +347,15 @@
|
|
|
353
347
|
is 12px because that is a target, and it grows to the touch target on a coarse
|
|
354
348
|
pointer (responsive.css). */
|
|
355
349
|
--aparte-split-position: 38%;
|
|
356
|
-
|
|
350
|
+
/* Never more than the container has: at 320px this floor was wider than a phone, so
|
|
351
|
+
the primary pane took everything and the end pane was annihilated to 0. */
|
|
352
|
+
--aparte-split-min: min(20rem, 100%);
|
|
357
353
|
--aparte-split-max: 60%;
|
|
358
|
-
|
|
354
|
+
/* The seam's TRACK — the grab zone, and what the grid reserves. The painted line
|
|
355
|
+
inside it is `--aparte-split-seam-width` (derived layer): one token used to size
|
|
356
|
+
both, so the line could not be thinned without moving the layout, and at 4px it
|
|
357
|
+
was four times the kit's rule with no grip — an interaction drawn as a decoration. */
|
|
358
|
+
--aparte-split-handle-size: 12px;
|
|
359
359
|
--aparte-split-hit-area: 12px;
|
|
360
360
|
/* Sizes that are not steps of a scale. Each is off-scale on purpose — a 40px
|
|
361
361
|
file tile is a tile, not fourteen spacing units — so each gets a name rather
|
|
@@ -406,6 +406,9 @@
|
|
|
406
406
|
--aparte-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
407
407
|
--aparte-popover-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.16);
|
|
408
408
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
409
|
+
/* The composer laid over the transcript (overlay-composer): a thing that floats has to
|
|
410
|
+
be seen floating, and its ground is the transcript's own. */
|
|
411
|
+
--aparte-composer-overlay-shadow: 0 -4px 16px rgba(0, 0, 0, 0.06);
|
|
409
412
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
410
413
|
--aparte-range-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
411
414
|
|
|
@@ -444,11 +447,28 @@
|
|
|
444
447
|
--aparte-skeleton-text-last-width: 60%;
|
|
445
448
|
--aparte-progress-indeterminate-width: 40%;
|
|
446
449
|
|
|
450
|
+
/* The control height scale — four steps, and EVERY control reads one: a text
|
|
451
|
+
button used to have no height (23px of padding and line-height) beside 24, 29, 32,
|
|
452
|
+
36 and 44px neighbours, and the 36px family (send, the input's action button, the
|
|
453
|
+
scroll button) lived off the scale as literals. sm is the small control (action bar,
|
|
454
|
+
branch arrows, a row's ⋯), md the default button, lg the field and the composer's
|
|
455
|
+
controls at rest, xl the large button. */
|
|
447
456
|
--aparte-btn-size-sm: 24px;
|
|
448
457
|
--aparte-btn-size-md: 32px;
|
|
449
|
-
--aparte-btn-size-lg:
|
|
458
|
+
--aparte-btn-size-lg: 36px;
|
|
459
|
+
--aparte-btn-size-xl: 40px;
|
|
460
|
+
/* The field family's literal sizes, beside the button's: the derived knobs
|
|
461
|
+
(paddings, radius) sit in the anchored layer below, like the button's own. */
|
|
462
|
+
--aparte-field-textarea-min-height: 80px;
|
|
463
|
+
--aparte-checkbox-size: 18px;
|
|
464
|
+
--aparte-radio-size: 18px;
|
|
465
|
+
--aparte-radio-dot-size: 8px;
|
|
466
|
+
--aparte-switch-width: 40px;
|
|
467
|
+
--aparte-switch-height: 22px;
|
|
468
|
+
--aparte-switch-thumb-inset: 2px;
|
|
469
|
+
--aparte-range-thumb-size: 16px;
|
|
470
|
+
--aparte-range-track-height: 4px;
|
|
450
471
|
|
|
451
|
-
--aparte-focus-outline-offset: -2px;
|
|
452
472
|
--aparte-tool-spinner-size: 10px;
|
|
453
473
|
--aparte-model-select-chevron-room: 28px;
|
|
454
474
|
}
|
|
@@ -482,6 +502,9 @@
|
|
|
482
502
|
--aparte-scroll-btn-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
|
|
483
503
|
--aparte-select-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
484
504
|
--aparte-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
|
505
|
+
--aparte-composer-overlay-shadow: 0 -4px 16px rgba(0, 0, 0, 0.4);
|
|
506
|
+
/* Declared once at :root it darkened a dark page by 8/255: the modal state vanished. */
|
|
507
|
+
--aparte-scrim: rgba(0, 0, 0, 0.6);
|
|
485
508
|
--aparte-popover-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
486
509
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
487
510
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
@@ -491,7 +514,6 @@
|
|
|
491
514
|
--aparte-error-border: #7f1d1d;
|
|
492
515
|
--aparte-error-solid: #f87171;
|
|
493
516
|
--aparte-error-text: #fca5a5;
|
|
494
|
-
--aparte-error-title: #fecaca;
|
|
495
517
|
}
|
|
496
518
|
}
|
|
497
519
|
|
|
@@ -521,6 +543,10 @@
|
|
|
521
543
|
--aparte-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
522
544
|
--aparte-popover-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 10px 28px rgba(0, 0, 0, 0.16);
|
|
523
545
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
546
|
+
/* The composer laid over the transcript (overlay-composer): a thing that floats has to
|
|
547
|
+
be seen floating, and its ground is the transcript's own. */
|
|
548
|
+
--aparte-composer-overlay-shadow: 0 -4px 16px rgba(0, 0, 0, 0.06);
|
|
549
|
+
--aparte-scrim: rgba(0, 0, 0, 0.35);
|
|
524
550
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
525
551
|
--aparte-range-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
|
|
526
552
|
--aparte-ink-l: 0.40;
|
|
@@ -528,7 +554,6 @@
|
|
|
528
554
|
--aparte-error-border: #fecaca;
|
|
529
555
|
--aparte-error-solid: #dc2626;
|
|
530
556
|
--aparte-error-text: #991b1b;
|
|
531
|
-
--aparte-error-title: #7f1d1d;
|
|
532
557
|
}
|
|
533
558
|
|
|
534
559
|
|
|
@@ -568,6 +593,8 @@
|
|
|
568
593
|
--aparte-scroll-btn-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
|
|
569
594
|
--aparte-select-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
570
595
|
--aparte-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
|
596
|
+
--aparte-composer-overlay-shadow: 0 -4px 16px rgba(0, 0, 0, 0.4);
|
|
597
|
+
--aparte-scrim: rgba(0, 0, 0, 0.6);
|
|
571
598
|
--aparte-popover-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
572
599
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
573
600
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
@@ -587,7 +614,6 @@
|
|
|
587
614
|
--aparte-error-border: #7f1d1d;
|
|
588
615
|
--aparte-error-solid: #f87171;
|
|
589
616
|
--aparte-error-text: #fca5a5;
|
|
590
|
-
--aparte-error-title: #fecaca;
|
|
591
617
|
}
|
|
592
618
|
|
|
593
619
|
/* =========================================
|
|
@@ -653,8 +679,14 @@ aparte-chat {
|
|
|
653
679
|
--aparte-font-size-xs: calc(0.6875rem * var(--aparte-font-scale));
|
|
654
680
|
--aparte-font-size-sm: calc(0.75rem * var(--aparte-font-scale));
|
|
655
681
|
--aparte-font-size-md: calc(0.8125rem * var(--aparte-font-scale));
|
|
656
|
-
--aparte-font-size-lg: calc(0.875rem * var(--aparte-font-scale));
|
|
657
682
|
--aparte-font-size-base: calc(0.9375rem * var(--aparte-font-scale));
|
|
683
|
+
/* Above the body, in the order the names say. `lg` used to be 0.875rem — SMALLER than
|
|
684
|
+
`base` — with nothing above it, so a title and a placeholder shared a size and a
|
|
685
|
+
card's title was smaller than its body. A scale whose name lies is worse than a
|
|
686
|
+
short one. */
|
|
687
|
+
--aparte-font-size-lg: calc(1.0625rem * var(--aparte-font-scale));
|
|
688
|
+
--aparte-font-size-xl: calc(1.25rem * var(--aparte-font-scale));
|
|
689
|
+
--aparte-font-size-2xl: calc(1.5rem * var(--aparte-font-scale));
|
|
658
690
|
|
|
659
691
|
/* Elicitation and the conversation item, on the scale.
|
|
660
692
|
They were literals off every step — 0.76 / 0.78 / 0.8 / 0.82rem and a `7px 10px`
|
|
@@ -680,7 +712,6 @@ aparte-chat {
|
|
|
680
712
|
vertical half (#56) — a shorthand cannot be read by parts. */
|
|
681
713
|
--aparte-message-padding-block: var(--aparte-space-8);
|
|
682
714
|
--aparte-message-padding-inline: var(--aparte-space-6);
|
|
683
|
-
--aparte-message-padding: var(--aparte-message-padding-block) var(--aparte-message-padding-inline);
|
|
684
715
|
--aparte-message-content-padding: var(--aparte-space-5) var(--aparte-space-7);
|
|
685
716
|
--aparte-action-bar-gap: var(--aparte-space-2);
|
|
686
717
|
--aparte-branch-picker-gap: var(--aparte-space-2);
|
|
@@ -694,17 +725,23 @@ aparte-chat {
|
|
|
694
725
|
--aparte-viewport-padding: var(--aparte-space-8);
|
|
695
726
|
--aparte-scrollbar-thumb: var(--aparte-neutral);
|
|
696
727
|
--aparte-input-padding-x: var(--aparte-space-6);
|
|
697
|
-
|
|
728
|
+
/* The editor at rest is the control height: half of what the control size leaves once
|
|
729
|
+
one line of its own text is taken out. It used to be a flat 10px, which made the editor
|
|
730
|
+
44px beside 36px buttons and, the row aligning at the end, sat the send button 4px
|
|
731
|
+
below the editor's centre. Derived from the editor's own line (1lh), so a coarse
|
|
732
|
+
pointer's 44 and a changed font both keep the editor and its buttons on one centre. */
|
|
733
|
+
--aparte-input-padding-y: calc((var(--aparte-composer-control-size) - 1lh) / 2);
|
|
698
734
|
--aparte-conv-list-gap: var(--aparte-space-1);
|
|
699
735
|
--aparte-conv-item-gap: var(--aparte-space-3);
|
|
700
|
-
--aparte-elic-option-radius: var(--aparte-radius-
|
|
736
|
+
--aparte-elic-option-radius: var(--aparte-radius-md);
|
|
701
737
|
--aparte-elic-input-radius: var(--aparte-radius-md);
|
|
702
738
|
--aparte-tool-row-radius: var(--aparte-radius-md);
|
|
703
|
-
|
|
704
|
-
--aparte-
|
|
739
|
+
/* The split's painted seam: the kit's rule, inside a 12px track. */
|
|
740
|
+
--aparte-split-seam-width: var(--aparte-border-width);
|
|
741
|
+
--aparte-name-font-size: var(--aparte-font-size-md);
|
|
705
742
|
--aparte-timestamp-font-size: var(--aparte-font-size-sm);
|
|
706
743
|
--aparte-branch-picker-label-size: var(--aparte-font-size-sm);
|
|
707
|
-
--aparte-input-font-size: var(--aparte-font-size-
|
|
744
|
+
--aparte-input-font-size: var(--aparte-font-size-base);
|
|
708
745
|
--aparte-content-font-size: var(--aparte-font-size-base);
|
|
709
746
|
--aparte-input-editor-font-size: var(--aparte-font-size-base);
|
|
710
747
|
--aparte-status-font-size: var(--aparte-font-size-md);
|
|
@@ -717,6 +754,9 @@ aparte-chat {
|
|
|
717
754
|
moves --aparte-space-unit or --aparte-font-scale carries them along. */
|
|
718
755
|
/* A tooltip is an INVERTED surface: dark on a light theme, light on a dark one.
|
|
719
756
|
Reading the text pair gives exactly that, and for free — no third palette. */
|
|
757
|
+
/* The distance between a placed tooltip (`data-side`) and its anchor: the arrow's
|
|
758
|
+
half-diagonal, so the tip lands on the trigger rather than 2px short of it. */
|
|
759
|
+
--aparte-tooltip-gap: var(--aparte-space-3);
|
|
720
760
|
--aparte-tooltip-bg: var(--aparte-text);
|
|
721
761
|
--aparte-tooltip-color: var(--aparte-text-inverse);
|
|
722
762
|
|
|
@@ -752,6 +792,14 @@ aparte-chat {
|
|
|
752
792
|
--aparte-alert-padding: var(--aparte-space-4) var(--aparte-space-5);
|
|
753
793
|
--aparte-alert-radius: var(--aparte-radius-md);
|
|
754
794
|
--aparte-alert-rail-width: var(--aparte-space-1);
|
|
795
|
+
/* The icon scale, in rem times the type factor — the one scale in the theme that
|
|
796
|
+
was pinned in px while every type size followed `--aparte-font-scale`, so a glyph
|
|
797
|
+
beside text shrank optically the moment a reader enlarged the text. The loose
|
|
798
|
+
default is 16 (it was 14, and its documentation already said 16). */
|
|
799
|
+
--aparte-icon-size: calc(1rem * var(--aparte-font-scale));
|
|
800
|
+
--aparte-icon-size-sm: calc(0.75rem * var(--aparte-font-scale));
|
|
801
|
+
--aparte-icon-size-lg: calc(1.125rem * var(--aparte-font-scale));
|
|
802
|
+
--aparte-icon-size-xl: calc(1.25rem * var(--aparte-font-scale));
|
|
755
803
|
--aparte-alert-icon-size: var(--aparte-space-8);
|
|
756
804
|
--aparte-alert-dismiss-size: var(--aparte-btn-size-sm);
|
|
757
805
|
--aparte-alert-title-font-size: var(--aparte-font-size-md);
|
|
@@ -759,8 +807,7 @@ aparte-chat {
|
|
|
759
807
|
|
|
760
808
|
--aparte-avatar-fallback-bg: var(--aparte-surface-3);
|
|
761
809
|
--aparte-avatar-fallback-color: var(--aparte-text);
|
|
762
|
-
--aparte-avatar-group-
|
|
763
|
-
--aparte-avatar-group-ring-width: var(--aparte-border-width);
|
|
810
|
+
--aparte-avatar-group-ring-width: calc(var(--aparte-border-width) * 2);
|
|
764
811
|
--aparte-avatar-group-ring-color: var(--aparte-surface-1);
|
|
765
812
|
|
|
766
813
|
--aparte-badge-font-size: var(--aparte-font-size-2xs);
|
|
@@ -781,7 +828,7 @@ aparte-chat {
|
|
|
781
828
|
--aparte-card-header-padding: var(--aparte-space-4) var(--aparte-space-5);
|
|
782
829
|
--aparte-card-body-padding: var(--aparte-space-5);
|
|
783
830
|
--aparte-card-footer-padding: var(--aparte-space-4) var(--aparte-space-5);
|
|
784
|
-
--aparte-card-header-font-size: var(--aparte-font-size-
|
|
831
|
+
--aparte-card-header-font-size: var(--aparte-font-size-base);
|
|
785
832
|
|
|
786
833
|
--aparte-divider-margin: var(--aparte-space-5);
|
|
787
834
|
--aparte-divider-label-gap: var(--aparte-space-4);
|
|
@@ -797,22 +844,62 @@ aparte-chat {
|
|
|
797
844
|
--aparte-progress-height: var(--aparte-space-2);
|
|
798
845
|
--aparte-progress-fill: var(--aparte-primary);
|
|
799
846
|
|
|
800
|
-
|
|
847
|
+
/* The ground of a gauge or a placeholder — the thing a value is read against, so it
|
|
848
|
+
has to be seen. Derived RELATIVE to the page, not a surface level: a level sat at
|
|
849
|
+
two different distances from the ground in light and dark. */
|
|
850
|
+
--aparte-track: color-mix(in srgb, var(--aparte-text) 8%, var(--aparte-bg));
|
|
851
|
+
--aparte-skeleton-base: var(--aparte-track);
|
|
852
|
+
/* The rail is as wide as its widest tick (the current one is 1.6× the resting size);
|
|
853
|
+
it used to be a 20px literal clipping a 22.4px tick. The floor is one tick's
|
|
854
|
+
pressable zone: the rail clips, so a column narrower than the zone would cut the
|
|
855
|
+
target back below 24px on the very edge a reader aims at. */
|
|
856
|
+
--aparte-scroll-rail-width: max(var(--aparte-scroll-rail-hit-size), calc(var(--aparte-scroll-rail-tick-size) * 1.6));
|
|
857
|
+
/* The distance between two ticks IS the zone minus the line: gap + thickness = the
|
|
858
|
+
24px pitch, so the zones tile edge to edge. Growing the zone alone would have
|
|
859
|
+
overlapped them by 14px and left the z-order to decide every press. */
|
|
860
|
+
--aparte-scroll-rail-gap: calc(var(--aparte-scroll-rail-hit-size) - var(--aparte-scroll-rail-tick-thickness));
|
|
801
861
|
--aparte-skeleton-shine: var(--aparte-surface-3);
|
|
802
|
-
|
|
803
|
-
|
|
862
|
+
/* The line it stands for: as tall as the content type, and a gap that is the rest of
|
|
863
|
+
the content line, so the text that replaces it lands where it was (T21). It was 12px
|
|
864
|
+
on an 18px step for a 16px line on a 27px step — a third of a line of jump per line. */
|
|
865
|
+
--aparte-skeleton-text-height: var(--aparte-content-font-size);
|
|
866
|
+
--aparte-skeleton-text-gap: calc(var(--aparte-content-font-size) * (var(--aparte-content-line-height) - 1));
|
|
804
867
|
--aparte-skeleton-circle-size: var(--aparte-avatar-size);
|
|
805
868
|
|
|
806
869
|
--aparte-spinner-thickness: var(--aparte-space-1);
|
|
807
870
|
|
|
808
|
-
|
|
871
|
+
|
|
872
|
+
/* The field family's knobs, beside the button's — they used to be declared on
|
|
873
|
+
`.aparte-field` itself, which is why `.aparte-field-group` (their PARENT) and
|
|
874
|
+
`.aparte-color` (a sibling recipe) read a property that did not exist there and
|
|
875
|
+
rendered SQUARE: a custom property is substituted where it is declared and only
|
|
876
|
+
inherits downwards. Same reason they belong here rather than there: the theming
|
|
877
|
+
guide sends a consumer to `:root`, and every other family's knobs are here.
|
|
878
|
+
The *channels* stay local on purpose (`--aparte-checkbox-intent` and friends are
|
|
879
|
+
filled by a modifier, not set by a theme). */
|
|
880
|
+
--aparte-field-padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
881
|
+
--aparte-field-padding-sm: var(--aparte-space-1) var(--aparte-space-2);
|
|
882
|
+
--aparte-field-padding-lg: var(--aparte-space-3) var(--aparte-space-4);
|
|
883
|
+
--aparte-field-radius: var(--aparte-radius-md);
|
|
884
|
+
/* The field's resting height is the lg step of the control scale, one step above the
|
|
885
|
+
default button and level with the composer's controls; `--sm` and `--lg` sit one
|
|
886
|
+
step either side. It used to be padding plus line-height, ~29px, the second
|
|
887
|
+
tallest thing in a row of five different heights. */
|
|
888
|
+
--aparte-field-size: var(--aparte-btn-size-lg);
|
|
889
|
+
--aparte-field-size-sm: var(--aparte-btn-size-md);
|
|
890
|
+
--aparte-field-size-lg: var(--aparte-btn-size-xl);
|
|
891
|
+
/* The 36px family, on the scale: named, not literal. */
|
|
892
|
+
--aparte-send-btn-size: var(--aparte-btn-size-lg);
|
|
893
|
+
--aparte-input-action-btn-size: var(--aparte-btn-size-lg);
|
|
894
|
+
--aparte-scroll-btn-size: var(--aparte-btn-size-lg);
|
|
809
895
|
|
|
810
896
|
--aparte-btn-gap: var(--aparte-space-2);
|
|
811
897
|
--aparte-btn-padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
812
898
|
--aparte-btn-radius: var(--aparte-radius-md);
|
|
813
|
-
|
|
899
|
+
/* Control text is the md step (14px). One step lower was the origin of the "poor"
|
|
900
|
+
look: 13px labels in 32px boxes, beside 15px prose. */
|
|
901
|
+
--aparte-btn-font-size: var(--aparte-font-size-md);
|
|
814
902
|
--aparte-btn-bg-hover: var(--aparte-surface-2);
|
|
815
|
-
--aparte-btn-focus-offset: var(--aparte-space-1);
|
|
816
903
|
--aparte-btn-bg-active: var(--aparte-surface-3);
|
|
817
904
|
--aparte-btn-bg-toggled: var(--aparte-surface-2);
|
|
818
905
|
--aparte-btn-color-toggled: var(--aparte-text);
|
|
@@ -822,11 +909,17 @@ aparte-chat {
|
|
|
822
909
|
--aparte-btn-padding-sm: var(--aparte-space-1) var(--aparte-space-2);
|
|
823
910
|
--aparte-btn-padding-lg: var(--aparte-space-3) var(--aparte-space-5);
|
|
824
911
|
|
|
912
|
+
/* One knob for the composer's control set — the send button, the input's action
|
|
913
|
+
buttons, the editor's single-line height: the lg step at rest, the touch target
|
|
914
|
+
under a coarse pointer (responsive.css). It used to be undeclared, with `44px`
|
|
915
|
+
written as a fallback at every reader, so a desktop composer was a 62px bar around
|
|
916
|
+
15px of text and the coarse-pointer block had nothing left to enlarge. */
|
|
917
|
+
--aparte-composer-control-size: var(--aparte-btn-size-lg);
|
|
825
918
|
--aparte-input-container-min-height: var(--aparte-touch-target-size);
|
|
826
|
-
--aparte-elic-message-size: var(--aparte-font-size-
|
|
827
|
-
--aparte-elic-title-size: var(--aparte-font-size-
|
|
919
|
+
--aparte-elic-message-size: var(--aparte-font-size-base);
|
|
920
|
+
--aparte-elic-title-size: var(--aparte-font-size-base);
|
|
828
921
|
--aparte-elic-desc-size: var(--aparte-font-size-sm);
|
|
829
|
-
--aparte-elic-option-title-size: var(--aparte-font-size-
|
|
922
|
+
--aparte-elic-option-title-size: var(--aparte-font-size-md);
|
|
830
923
|
--aparte-elic-option-desc-size: var(--aparte-font-size-sm);
|
|
831
924
|
--aparte-elic-skip-size: var(--aparte-font-size-md);
|
|
832
925
|
--aparte-elic-step-size: var(--aparte-font-size-sm);
|
|
@@ -836,6 +929,17 @@ aparte-chat {
|
|
|
836
929
|
|
|
837
930
|
/* Borders */
|
|
838
931
|
--aparte-border-focus: var(--aparte-primary);
|
|
932
|
+
/* THE focus ring: a 2px outline in this colour, one spacing step OUTSIDE the box, on
|
|
933
|
+
every control. It used to be drawn two ways — this outline on seventeen recipes and a
|
|
934
|
+
soft box-shadow wash (30 % of the accent, 1.39:1 against the page) on five — and the
|
|
935
|
+
offset was −2px, inside the box, so on a bordered row the ring sat as a second
|
|
936
|
+
concentric line and the next row painted over its edge. */
|
|
937
|
+
--aparte-focus-outline-offset: var(--aparte-space-1);
|
|
938
|
+
/* The edge of a CONTROL — a field, a choice box, the select's trigger. `--aparte-border`
|
|
939
|
+
separates two regions and is right to be faint; a thing someone can touch has to be
|
|
940
|
+
seen, and six previews showed a control with no visible edge. Derived from the ink
|
|
941
|
+
and the ground so both schemes follow; the mix is a first setting. */
|
|
942
|
+
--aparte-border-control: color-mix(in srgb, var(--aparte-text) 30%, var(--aparte-surface-1));
|
|
839
943
|
|
|
840
944
|
/* Component-specific radius (overridable per app) */
|
|
841
945
|
--aparte-radius-input: var(--aparte-radius-lg);
|
|
@@ -843,19 +947,25 @@ aparte-chat {
|
|
|
843
947
|
--aparte-radius-send-btn: var(--aparte-radius-md);
|
|
844
948
|
--aparte-radius-action-btn: var(--aparte-radius-sm);
|
|
845
949
|
--aparte-radius-select: var(--aparte-radius-md);
|
|
846
|
-
--aparte-radius-avatar: var(--aparte-radius-md);
|
|
847
950
|
--aparte-radius-bubble: var(--aparte-radius-lg);
|
|
951
|
+
/* The bubble's corner IS the bubble radius above: it was a 14px literal off the scale,
|
|
952
|
+
the most visible corner of the transcript, and the knob named for it drove nothing. */
|
|
953
|
+
--aparte-message-content-radius: var(--aparte-radius-bubble);
|
|
848
954
|
--aparte-radius-code: var(--aparte-radius-lg);
|
|
849
955
|
--aparte-radius-thinking: var(--aparte-radius-lg);
|
|
850
956
|
--aparte-radius-error: var(--aparte-radius-md);
|
|
957
|
+
/* The floating surfaces. They read a step of the scale directly, so a theme that
|
|
958
|
+
wanted square menus and round bubbles had to move the step — and every other
|
|
959
|
+
reader of it. */
|
|
960
|
+
--aparte-radius-menu: var(--aparte-radius-lg);
|
|
961
|
+
--aparte-radius-popover: var(--aparte-radius-lg);
|
|
962
|
+
--aparte-radius-dialog: var(--aparte-radius-xl);
|
|
963
|
+
--aparte-radius-tooltip: var(--aparte-radius-sm);
|
|
851
964
|
|
|
852
965
|
/* The error ring on an avatar. A full shadow value, like
|
|
853
966
|
--aparte-scroll-btn-shadow: the whole ring is the knob, not its width. */
|
|
854
967
|
--aparte-avatar-error-ring: 0 0 0 2px var(--aparte-error-solid);
|
|
855
968
|
|
|
856
|
-
/* Focus ring — derived from the accent so it themes automatically. */
|
|
857
|
-
--aparte-focus-ring: 0 0 0 3px color-mix(in srgb, var(--aparte-primary) 30%, transparent);
|
|
858
|
-
|
|
859
969
|
/* Messages */
|
|
860
970
|
--aparte-message-content-text-user: var(--aparte-text);
|
|
861
971
|
--aparte-message-content-text-assistant: var(--aparte-text);
|
|
@@ -865,7 +975,9 @@ aparte-chat {
|
|
|
865
975
|
in sRGB rather than derived with `oklch(from …)`, because a wash has to keep the
|
|
866
976
|
SURFACE's lightness, not the accent's; 12 % is where the default palette reads as a
|
|
867
977
|
tint, not a fill, on both grounds. */
|
|
868
|
-
|
|
978
|
+
/* The mark's own tint: at 12 % the user bubble — the one mark that says "this turn is
|
|
979
|
+
yours" — sat at 1.03:1 on the light page. */
|
|
980
|
+
--aparte-message-content-bg-user: color-mix(in srgb, var(--aparte-primary) var(--aparte-mark-tint), var(--aparte-surface-1));
|
|
869
981
|
/* The third surface: the second, pulled 6 % toward the text. Close to what the old
|
|
870
982
|
literals encoded (`#e7dfce` from `#f1ebdf` light, `#342a40` from `#2a2333` dark —
|
|
871
983
|
5 to 9 % per channel, not one figure), now one rule that follows the masters. */
|
|
@@ -893,6 +1005,12 @@ aparte-chat {
|
|
|
893
1005
|
--aparte-thumb-radius: var(--aparte-radius-lg);
|
|
894
1006
|
|
|
895
1007
|
/* Action Bar */
|
|
1008
|
+
/* The small controls of a turn and of a conversation row are the recipe's small step
|
|
1009
|
+
(24px, the WCAG 2.5.8 floor). The action bar used to feed 28 with a 24 exception for
|
|
1010
|
+
the last user turn, the branch arrows redrew 20 over their own `--sm`, the row's
|
|
1011
|
+
`⋯` redrew 20: three control heights in one 14px row. */
|
|
1012
|
+
--aparte-action-bar-btn-size: var(--aparte-btn-size-sm);
|
|
1013
|
+
--aparte-conv-action-btn-size: var(--aparte-btn-size-sm);
|
|
896
1014
|
--aparte-action-bar-btn-color: var(--aparte-text-muted);
|
|
897
1015
|
--aparte-action-bar-btn-hover-bg: var(--aparte-surface-2);
|
|
898
1016
|
--aparte-action-bar-btn-hover-color: var(--aparte-text);
|
|
@@ -903,11 +1021,10 @@ aparte-chat {
|
|
|
903
1021
|
--aparte-branch-picker-label-color: var(--aparte-text-muted);
|
|
904
1022
|
|
|
905
1023
|
/* Avatars */
|
|
906
|
-
--aparte-avatar-radius: var(--aparte-radius-avatar);
|
|
907
1024
|
--aparte-avatar-bg-user: var(--aparte-primary);
|
|
908
1025
|
--aparte-avatar-text-user: var(--aparte-text-inverse);
|
|
909
1026
|
--aparte-avatar-bg-assistant: var(--aparte-surface-3);
|
|
910
|
-
--aparte-avatar-text-assistant: var(--aparte-text
|
|
1027
|
+
--aparte-avatar-text-assistant: var(--aparte-text);
|
|
911
1028
|
|
|
912
1029
|
/* Text Sizes */
|
|
913
1030
|
--aparte-name-color: var(--aparte-text);
|
|
@@ -936,6 +1053,10 @@ aparte-chat {
|
|
|
936
1053
|
--aparte-code-header-bg: var(--aparte-surface-2);
|
|
937
1054
|
--aparte-code-border: var(--aparte-border);
|
|
938
1055
|
--aparte-code-header-text: var(--aparte-text-muted);
|
|
1056
|
+
/* The block's own type. It used to inherit the prose's size and leading (16px on 1.7)
|
|
1057
|
+
under the browser's generic monospace — the largest, airiest text in the message. */
|
|
1058
|
+
--aparte-code-block-font-size: var(--aparte-font-size-md);
|
|
1059
|
+
--aparte-code-block-line-height: var(--aparte-line-height-normal);
|
|
939
1060
|
|
|
940
1061
|
/* Scroll-to-bottom button */
|
|
941
1062
|
|
|
@@ -955,6 +1076,9 @@ aparte-chat {
|
|
|
955
1076
|
|
|
956
1077
|
/* Shell */
|
|
957
1078
|
--aparte-sidebar-bg: var(--aparte-surface-2);
|
|
1079
|
+
/* The one inline inset of the column's four regions — header, search, body, footer.
|
|
1080
|
+
They used to carry three (16 / 12 / 12 / 16) and the content sat on two axes. */
|
|
1081
|
+
--aparte-sidebar-inset: var(--aparte-space-3);
|
|
958
1082
|
|
|
959
1083
|
/* Conversation List */
|
|
960
1084
|
--aparte-conv-item-radius: var(--aparte-radius-md);
|
|
@@ -1034,6 +1158,28 @@ aparte-chat {
|
|
|
1034
1158
|
animation: aparte-pulse var(--aparte-duration-pulse) infinite ease-in-out;
|
|
1035
1159
|
}
|
|
1036
1160
|
|
|
1161
|
+
/* Under reduced motion the loop stops outright. The duration tokens are reset to
|
|
1162
|
+
0.01ms in responsive.css, but that leaves `infinite` in place: a 0.01ms cycle
|
|
1163
|
+
is not stillness, it is a flicker. The descendant sweep there does not reach a
|
|
1164
|
+
recipe used outside aparté's own elements, so each looping recipe stops itself,
|
|
1165
|
+
the way display/icon.css already does. */
|
|
1166
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1167
|
+
.aparte-dot { animation: none; }
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/* `hidden` means hidden. 71 recipes in these sheets set `display` on their base class,
|
|
1171
|
+
and an author declaration beats the browser's own `[hidden] { display: none }` — so a
|
|
1172
|
+
hidden `.aparte-btn` was out of the accessibility tree and still painted: the copy
|
|
1173
|
+
button of a tool-only turn stood there with `hidden` on it. Scoped to our own classes
|
|
1174
|
+
(a prefix match, not a bare `[hidden]`, so a host's markup is not touched), and
|
|
1175
|
+
`!important` because the whole point is to outrank the recipe's `display`. The
|
|
1176
|
+
handful of per-element `[hidden]` rules that predate this stay; they are now
|
|
1177
|
+
redundant, not wrong. */
|
|
1178
|
+
[class^="aparte-"][hidden],
|
|
1179
|
+
[class*=" aparte-"][hidden] {
|
|
1180
|
+
display: none !important;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1037
1183
|
/* =========================================
|
|
1038
1184
|
aparte-conversation-list
|
|
1039
1185
|
========================================= */
|
|
@@ -1065,7 +1211,7 @@ aparte-conversation-list {
|
|
|
1065
1211
|
.aparte-link:hover { color: var(--aparte-link-color-hover); }
|
|
1066
1212
|
.aparte-link:focus-visible {
|
|
1067
1213
|
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1068
|
-
outline-offset: var(--aparte-
|
|
1214
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
1069
1215
|
}
|
|
1070
1216
|
/* Quiet — for a link inside a dense row, where a coloured one would shout. It keeps
|
|
1071
1217
|
the underline, which is the part that carries the meaning. */
|
|
@@ -1088,8 +1234,11 @@ aparte-conversation-list {
|
|
|
1088
1234
|
same thing at slightly different rhythms; a travelling wave, where a row wants one,
|
|
1089
1235
|
comes from an animation-delay on its later dots, not from a skewed curve. */
|
|
1090
1236
|
@keyframes aparte-pulse {
|
|
1091
|
-
|
|
1092
|
-
|
|
1237
|
+
/* Opacity alone: the scale that used to ride along made a row of waiting dots change
|
|
1238
|
+
width in a loop. And a floor above the perception threshold — at 0.3 the status
|
|
1239
|
+
dot spent half of every cycle at 1.55:1, and a capture froze it there. */
|
|
1240
|
+
0%, 100% { opacity: var(--aparte-pulse-floor); }
|
|
1241
|
+
50% { opacity: 1; }
|
|
1093
1242
|
}
|
|
1094
1243
|
|
|
1095
1244
|
/* One rotation, for every spinner and every spinning glyph. */
|
|
@@ -1110,9 +1259,9 @@ aparte-conversation-list {
|
|
|
1110
1259
|
* <aparte-icon name="copy"></aparte-icon>
|
|
1111
1260
|
* </button>
|
|
1112
1261
|
* <div class="aparte-btn-group" role="group" aria-label="Versions">
|
|
1113
|
-
* <button class="aparte-btn aparte-btn--surface"
|
|
1262
|
+
* <button class="aparte-btn aparte-btn--surface" aria-label="Previous"><aparte-icon name="prevBranch"></aparte-icon></button>
|
|
1114
1263
|
* <button class="aparte-btn aparte-btn--surface">1 / 2</button>
|
|
1115
|
-
* <button class="aparte-btn aparte-btn--surface"
|
|
1264
|
+
* <button class="aparte-btn aparte-btn--surface" aria-label="Next"><aparte-icon name="nextBranch"></aparte-icon></button>
|
|
1116
1265
|
* </div>
|
|
1117
1266
|
* <div class="aparte-btn-group" role="group" aria-label="View">
|
|
1118
1267
|
* <button class="aparte-btn aparte-btn--primary aparte-btn--outline" aria-pressed="true">Chat</button>
|
|
@@ -1139,6 +1288,13 @@ aparte-conversation-list {
|
|
|
1139
1288
|
*/
|
|
1140
1289
|
|
|
1141
1290
|
.aparte-btn {
|
|
1291
|
+
/* The glyph reads this: an `<aparte-icon>` (or a raw `svg.aparte-icon`) inside a button
|
|
1292
|
+
takes the button's icon size, per size modifier. */
|
|
1293
|
+
--aparte-icon-size: var(--aparte-btn-icon-size);
|
|
1294
|
+
/* The box, for EVERY button: `--icon` and `--circle` used to be the only readers of
|
|
1295
|
+
the size, so a text button was padding plus line-height — 23px beside a 32px icon
|
|
1296
|
+
button in the same row. The size is declared here and read below as a minimum. */
|
|
1297
|
+
--aparte-btn-size: var(--aparte-btn-size-md);
|
|
1142
1298
|
/* The intent, as a value. Every fill below reads THIS and nothing else, which is
|
|
1143
1299
|
what keeps the two axes independent. */
|
|
1144
1300
|
--aparte-btn-intent: var(--aparte-neutral);
|
|
@@ -1157,6 +1313,8 @@ aparte-conversation-list {
|
|
|
1157
1313
|
--aparte-btn-intent-ink: var(--aparte-btn-intent);
|
|
1158
1314
|
|
|
1159
1315
|
display: inline-flex;
|
|
1316
|
+
|
|
1317
|
+
vertical-align: middle;
|
|
1160
1318
|
align-items: center;
|
|
1161
1319
|
justify-content: center;
|
|
1162
1320
|
gap: var(--aparte-btn-gap);
|
|
@@ -1167,6 +1325,7 @@ aparte-conversation-list {
|
|
|
1167
1325
|
be set by the composer's own row rule, which is exactly the kind of thing a recipe
|
|
1168
1326
|
should not need a caller for. */
|
|
1169
1327
|
box-sizing: border-box;
|
|
1328
|
+
min-block-size: var(--aparte-btn-size);
|
|
1170
1329
|
padding: var(--aparte-btn-padding);
|
|
1171
1330
|
border: var(--aparte-border-width) solid transparent;
|
|
1172
1331
|
border-radius: var(--aparte-btn-radius);
|
|
@@ -1321,15 +1480,21 @@ aparte-conversation-list {
|
|
|
1321
1480
|
.aparte-btn--soft:active:not(:disabled) { background: color-mix(in srgb, var(--aparte-btn-intent) 30%, transparent); }
|
|
1322
1481
|
.aparte-btn--solid:active:not(:disabled) { background: var(--aparte-btn-intent); filter: brightness(0.93); }
|
|
1323
1482
|
|
|
1483
|
+
/* Drawn, not faded. Opacity on the whole button faded the glyph with its ground and
|
|
1484
|
+
read at 2:1 on the send button; "inactive" and "disappearing" are not the same
|
|
1485
|
+
message. A neutral ground, the muted ink, no intent — whatever the fill was. */
|
|
1324
1486
|
.aparte-btn:disabled,
|
|
1325
1487
|
.aparte-btn[aria-disabled='true'] {
|
|
1326
1488
|
cursor: not-allowed;
|
|
1327
|
-
|
|
1489
|
+
background: var(--aparte-surface-2);
|
|
1490
|
+
color: var(--aparte-text-muted);
|
|
1491
|
+
border-color: transparent;
|
|
1492
|
+
filter: none;
|
|
1328
1493
|
}
|
|
1329
1494
|
|
|
1330
1495
|
.aparte-btn:focus-visible {
|
|
1331
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
1332
|
-
outline-offset: var(--aparte-
|
|
1496
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1497
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
1333
1498
|
}
|
|
1334
1499
|
|
|
1335
1500
|
/* Toggled — a disclosure that is open, a toggle that is on, a tab that is current. */
|
|
@@ -1362,25 +1527,39 @@ aparte-conversation-list {
|
|
|
1362
1527
|
.aparte-btn--block { display: flex; width: 100%; }
|
|
1363
1528
|
|
|
1364
1529
|
.aparte-btn--sm { --aparte-btn-size: var(--aparte-btn-size-sm); --aparte-btn-icon-size: var(--aparte-icon-size-sm); font-size: var(--aparte-font-size-sm); padding: var(--aparte-btn-padding-sm); }
|
|
1365
|
-
.aparte-btn--lg { --aparte-btn-size: var(--aparte-btn-size-lg); --aparte-btn-icon-size: var(--aparte-icon-size-xl); font-size: var(--aparte-font-size-
|
|
1530
|
+
.aparte-btn--lg { --aparte-btn-size: var(--aparte-btn-size-lg); --aparte-btn-icon-size: var(--aparte-icon-size-xl); font-size: var(--aparte-font-size-base); padding: var(--aparte-btn-padding-lg); }
|
|
1531
|
+
.aparte-btn--xl { --aparte-btn-size: var(--aparte-btn-size-xl); --aparte-btn-icon-size: var(--aparte-icon-size-xl); font-size: var(--aparte-font-size-base); padding: var(--aparte-btn-padding-lg); }
|
|
1366
1532
|
.aparte-btn--sm.aparte-btn--icon,
|
|
1367
1533
|
.aparte-btn--sm.aparte-btn--circle,
|
|
1368
1534
|
.aparte-btn--lg.aparte-btn--icon,
|
|
1369
|
-
.aparte-btn--lg.aparte-btn--circle
|
|
1535
|
+
.aparte-btn--lg.aparte-btn--circle,
|
|
1536
|
+
.aparte-btn--xl.aparte-btn--icon,
|
|
1537
|
+
.aparte-btn--xl.aparte-btn--circle { padding: 0; }
|
|
1370
1538
|
|
|
1371
1539
|
/* An icon inside a button never takes the pointer: the click belongs to the button,
|
|
1372
|
-
and an SVG target makes `event.target` lie to a delegated listener.
|
|
1373
|
-
the button so the two axes stay in step. */
|
|
1540
|
+
and an SVG target makes `event.target` lie to a delegated listener. */
|
|
1374
1541
|
.aparte-btn > svg,
|
|
1542
|
+
.aparte-btn > aparte-icon,
|
|
1375
1543
|
.aparte-btn > .aparte-btn__icon {
|
|
1376
1544
|
pointer-events: none;
|
|
1377
1545
|
flex-shrink: 0;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/* A raw SVG or a consumer's own icon box is sized with the button so the two axes stay
|
|
1549
|
+
in step. An `<aparte-icon>` is NOT sized here: it renders its `<svg>` inside itself, a
|
|
1550
|
+
grandchild this child combinator never reached, so the documented markup kept the
|
|
1551
|
+
glyph at its loose 14px default in a button that asked for 16. The recipe feeds the
|
|
1552
|
+
icon's own knob instead — `--aparte-icon-size`, declared on `.aparte-btn` above — and
|
|
1553
|
+
`display/icon.css` draws it, at `--sm`'s 12 and `--lg`'s 20 like everything else. */
|
|
1554
|
+
.aparte-btn > svg:not(.aparte-icon),
|
|
1555
|
+
.aparte-btn > .aparte-btn__icon {
|
|
1378
1556
|
width: var(--aparte-btn-icon-size);
|
|
1379
1557
|
height: var(--aparte-btn-icon-size);
|
|
1380
1558
|
}
|
|
1381
1559
|
|
|
1382
1560
|
/* ── Group — buttons that read as one control. ───────────────────────────────── */
|
|
1383
|
-
.aparte-btn-group { display: inline-flex;
|
|
1561
|
+
.aparte-btn-group { display: inline-flex;
|
|
1562
|
+
vertical-align: middle; }
|
|
1384
1563
|
.aparte-btn-group > .aparte-btn { border-radius: 0; }
|
|
1385
1564
|
/* Logical properties, so the joined edge follows the writing direction instead of
|
|
1386
1565
|
assuming left-to-right. */
|
|
@@ -1464,24 +1643,24 @@ aparte-conversation-list {
|
|
|
1464
1643
|
/* ── The shared text-entry recipe ────────────────────────────────────────────── */
|
|
1465
1644
|
|
|
1466
1645
|
.aparte-field {
|
|
1467
|
-
/*
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
own element the way every sibling can. */
|
|
1474
|
-
--aparte-field-radius: var(--aparte-radius-md);
|
|
1475
|
-
|
|
1646
|
+
/* Every knob this family reads lives in `theme.css` on `:root` — including the
|
|
1647
|
+
radius and the paddings, which used to be declared right here. That was the one
|
|
1648
|
+
construction bug of the kit: `.aparte-field-group` is this element's PARENT and
|
|
1649
|
+
`.aparte-color` a sibling recipe, both read `--aparte-field-radius`, and a custom
|
|
1650
|
+
property declared here is invisible to both, so they computed `border-radius: 0`
|
|
1651
|
+
and every field group in the library rendered square. */
|
|
1476
1652
|
box-sizing: border-box;
|
|
1477
1653
|
display: block;
|
|
1478
1654
|
width: 100%;
|
|
1655
|
+
/* A resting height from the control scale, so a field and the button beside it stop
|
|
1656
|
+
disagreeing by 6px; the size modifiers move it a step either way. */
|
|
1657
|
+
min-block-size: var(--aparte-field-size);
|
|
1479
1658
|
font: inherit;
|
|
1480
|
-
font-size: var(--aparte-font-size-
|
|
1659
|
+
font-size: var(--aparte-font-size-md);
|
|
1481
1660
|
line-height: var(--aparte-line-height-normal);
|
|
1482
1661
|
color: var(--aparte-text);
|
|
1483
1662
|
background: var(--aparte-surface-1);
|
|
1484
|
-
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
1663
|
+
border: var(--aparte-border-width) solid var(--aparte-border-control);
|
|
1485
1664
|
border-radius: var(--aparte-field-radius);
|
|
1486
1665
|
padding: var(--aparte-field-padding);
|
|
1487
1666
|
transition:
|
|
@@ -1516,10 +1695,13 @@ aparte-conversation-list {
|
|
|
1516
1695
|
border-color: var(--aparte-border-focus);
|
|
1517
1696
|
}
|
|
1518
1697
|
|
|
1698
|
+
/* Drawn, not faded: the muted ink on the quieter ground, and the edge back to the region
|
|
1699
|
+
colour. Opacity faded the text with its box and read under 3:1. */
|
|
1519
1700
|
.aparte-field:disabled {
|
|
1520
1701
|
cursor: not-allowed;
|
|
1521
|
-
opacity: var(--aparte-disabled-opacity);
|
|
1522
1702
|
background: var(--aparte-surface-2);
|
|
1703
|
+
color: var(--aparte-text-muted);
|
|
1704
|
+
border-color: var(--aparte-border);
|
|
1523
1705
|
}
|
|
1524
1706
|
|
|
1525
1707
|
.aparte-field[readonly] {
|
|
@@ -1533,37 +1715,35 @@ aparte-conversation-list {
|
|
|
1533
1715
|
border-color: var(--aparte-error);
|
|
1534
1716
|
}
|
|
1535
1717
|
|
|
1718
|
+
/* The one ring (see --aparte-focus-outline-offset in theme.css): an outline outside the
|
|
1719
|
+
box, and the border left alone — a highlighted border under an offset ring read as two
|
|
1720
|
+
concentric lines. An invalid field's ring takes the error colour. */
|
|
1536
1721
|
.aparte-field:focus-visible {
|
|
1537
|
-
outline:
|
|
1538
|
-
|
|
1539
|
-
box-shadow: var(--aparte-focus-ring);
|
|
1722
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1723
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
1540
1724
|
}
|
|
1541
1725
|
|
|
1542
1726
|
.aparte-field[aria-invalid='true']:focus-visible {
|
|
1543
|
-
|
|
1544
|
-
box-shadow: var(--aparte-field-error-ring);
|
|
1727
|
+
outline-color: var(--aparte-error-solid);
|
|
1545
1728
|
}
|
|
1546
1729
|
|
|
1547
1730
|
/* ── Sizes ───────────────────────────────────────────────────────────────────── */
|
|
1548
1731
|
|
|
1549
1732
|
.aparte-field--sm {
|
|
1550
|
-
--aparte-field-
|
|
1551
|
-
|
|
1733
|
+
--aparte-field-size: var(--aparte-field-size-sm);
|
|
1552
1734
|
padding: var(--aparte-field-padding-sm);
|
|
1553
|
-
font-size: var(--aparte-font-size-
|
|
1735
|
+
font-size: var(--aparte-font-size-sm);
|
|
1554
1736
|
}
|
|
1555
1737
|
|
|
1556
1738
|
.aparte-field--lg {
|
|
1557
|
-
--aparte-field-
|
|
1558
|
-
|
|
1739
|
+
--aparte-field-size: var(--aparte-field-size-lg);
|
|
1559
1740
|
padding: var(--aparte-field-padding-lg);
|
|
1560
|
-
font-size: var(--aparte-font-size-
|
|
1741
|
+
font-size: var(--aparte-font-size-base);
|
|
1561
1742
|
}
|
|
1562
1743
|
|
|
1563
1744
|
/* ── Textarea ────────────────────────────────────────────────────────────────── */
|
|
1564
1745
|
|
|
1565
1746
|
.aparte-field--textarea {
|
|
1566
|
-
--aparte-field-textarea-min-height: 80px;
|
|
1567
1747
|
|
|
1568
1748
|
display: block;
|
|
1569
1749
|
min-block-size: var(--aparte-field-textarea-min-height);
|
|
@@ -1578,7 +1758,7 @@ aparte-conversation-list {
|
|
|
1578
1758
|
display: flex;
|
|
1579
1759
|
align-items: stretch;
|
|
1580
1760
|
background: var(--aparte-surface-1);
|
|
1581
|
-
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
1761
|
+
border: var(--aparte-border-width) solid var(--aparte-border-control);
|
|
1582
1762
|
border-radius: var(--aparte-field-radius);
|
|
1583
1763
|
/* The addons' ground follows the group's corners; the focus ring is a shadow on the
|
|
1584
1764
|
group itself, outside this box, so it is not clipped. */
|
|
@@ -1600,8 +1780,15 @@ aparte-conversation-list {
|
|
|
1600
1780
|
border-radius: 0;
|
|
1601
1781
|
}
|
|
1602
1782
|
|
|
1783
|
+
/* The GROUP draws the ring (below): the field inside it draws none. */
|
|
1603
1784
|
.aparte-field-group > .aparte-field:focus-visible {
|
|
1604
|
-
|
|
1785
|
+
outline: none;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
/* A button in the group is as tall as the field it sits beside — the one row where the
|
|
1789
|
+
two recipes meet, and the one where a 4px difference shows. */
|
|
1790
|
+
.aparte-field-group > .aparte-btn {
|
|
1791
|
+
--aparte-btn-size: var(--aparte-field-size);
|
|
1605
1792
|
}
|
|
1606
1793
|
|
|
1607
1794
|
/* An addon has a ground of its own and a rule against the field: on the field's own
|
|
@@ -1628,8 +1815,8 @@ aparte-conversation-list {
|
|
|
1628
1815
|
}
|
|
1629
1816
|
|
|
1630
1817
|
.aparte-field-group:focus-within {
|
|
1631
|
-
|
|
1632
|
-
|
|
1818
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1819
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
1633
1820
|
}
|
|
1634
1821
|
|
|
1635
1822
|
/* `aria-invalid` lives on the field itself, not the wrapper the app didn't write a
|
|
@@ -1640,7 +1827,7 @@ aparte-conversation-list {
|
|
|
1640
1827
|
}
|
|
1641
1828
|
|
|
1642
1829
|
.aparte-field-group:has(.aparte-field[aria-invalid='true']):focus-within {
|
|
1643
|
-
|
|
1830
|
+
outline-color: var(--aparte-error-solid);
|
|
1644
1831
|
}
|
|
1645
1832
|
|
|
1646
1833
|
/* ── Checkbox ────────────────────────────────────────────────────────────────── */
|
|
@@ -1664,7 +1851,6 @@ aparte-conversation-list {
|
|
|
1664
1851
|
--aparte-derived-ink: var(--aparte-on-intent);
|
|
1665
1852
|
/* No shared control-size token covers a checkbox yet (the button's own sizes are
|
|
1666
1853
|
button-specific), so its box is declared here. */
|
|
1667
|
-
--aparte-checkbox-size: 18px;
|
|
1668
1854
|
|
|
1669
1855
|
box-sizing: border-box;
|
|
1670
1856
|
appearance: none;
|
|
@@ -1675,7 +1861,7 @@ aparte-conversation-list {
|
|
|
1675
1861
|
place-content: center;
|
|
1676
1862
|
width: var(--aparte-checkbox-size);
|
|
1677
1863
|
height: var(--aparte-checkbox-size);
|
|
1678
|
-
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
1864
|
+
border: var(--aparte-border-width) solid var(--aparte-border-control);
|
|
1679
1865
|
border-radius: var(--aparte-radius-sm);
|
|
1680
1866
|
background: var(--aparte-surface-1);
|
|
1681
1867
|
cursor: pointer;
|
|
@@ -1760,7 +1946,7 @@ aparte-conversation-list {
|
|
|
1760
1946
|
}
|
|
1761
1947
|
|
|
1762
1948
|
.aparte-checkbox:focus-visible {
|
|
1763
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
1949
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1764
1950
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
1765
1951
|
}
|
|
1766
1952
|
|
|
@@ -1790,8 +1976,6 @@ aparte-conversation-list {
|
|
|
1790
1976
|
used. `middle` and not `text-bottom`: the label may wrap. */
|
|
1791
1977
|
vertical-align: middle;
|
|
1792
1978
|
--aparte-radio-intent: var(--aparte-primary);
|
|
1793
|
-
--aparte-radio-size: 18px;
|
|
1794
|
-
--aparte-radio-dot-size: 8px;
|
|
1795
1979
|
|
|
1796
1980
|
box-sizing: border-box;
|
|
1797
1981
|
appearance: none;
|
|
@@ -1802,7 +1986,7 @@ aparte-conversation-list {
|
|
|
1802
1986
|
place-content: center;
|
|
1803
1987
|
width: var(--aparte-radio-size);
|
|
1804
1988
|
height: var(--aparte-radio-size);
|
|
1805
|
-
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
1989
|
+
border: var(--aparte-border-width) solid var(--aparte-border-control);
|
|
1806
1990
|
border-radius: var(--aparte-radius-full);
|
|
1807
1991
|
background: var(--aparte-surface-1);
|
|
1808
1992
|
cursor: pointer;
|
|
@@ -1844,7 +2028,7 @@ aparte-conversation-list {
|
|
|
1844
2028
|
}
|
|
1845
2029
|
|
|
1846
2030
|
.aparte-radio:focus-visible {
|
|
1847
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
2031
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1848
2032
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
1849
2033
|
}
|
|
1850
2034
|
|
|
@@ -1875,9 +2059,6 @@ aparte-conversation-list {
|
|
|
1875
2059
|
has to live here where `:checked` can override it — a central default could never
|
|
1876
2060
|
react to the class + state combo. */
|
|
1877
2061
|
--aparte-switch-track: var(--aparte-surface-3);
|
|
1878
|
-
--aparte-switch-width: 40px;
|
|
1879
|
-
--aparte-switch-height: 22px;
|
|
1880
|
-
--aparte-switch-thumb-inset: 2px;
|
|
1881
2062
|
/* DERIVED from the track, not a fourth literal to keep in step with the other three:
|
|
1882
2063
|
the thumb used to be 16px in a 20px track with a 1px border and a 2px top inset —
|
|
1883
2064
|
2px of air above it and none below, visibly low ("le cercle pas centré"). The
|
|
@@ -1896,7 +2077,7 @@ aparte-conversation-list {
|
|
|
1896
2077
|
flex-shrink: 0;
|
|
1897
2078
|
inline-size: var(--aparte-switch-width);
|
|
1898
2079
|
block-size: var(--aparte-switch-height);
|
|
1899
|
-
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
2080
|
+
border: var(--aparte-border-width) solid var(--aparte-border-control);
|
|
1900
2081
|
border-radius: var(--aparte-radius-full);
|
|
1901
2082
|
background: var(--aparte-switch-track);
|
|
1902
2083
|
cursor: pointer;
|
|
@@ -1944,7 +2125,7 @@ aparte-conversation-list {
|
|
|
1944
2125
|
}
|
|
1945
2126
|
|
|
1946
2127
|
.aparte-switch:focus-visible {
|
|
1947
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
2128
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
1948
2129
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
1949
2130
|
}
|
|
1950
2131
|
|
|
@@ -1968,8 +2149,6 @@ aparte-conversation-list {
|
|
|
1968
2149
|
|
|
1969
2150
|
.aparte-range {
|
|
1970
2151
|
--aparte-range-intent: var(--aparte-primary);
|
|
1971
|
-
--aparte-range-thumb-size: 16px;
|
|
1972
|
-
--aparte-range-track-height: 4px;
|
|
1973
2152
|
|
|
1974
2153
|
appearance: none;
|
|
1975
2154
|
-webkit-appearance: none;
|
|
@@ -2039,11 +2218,11 @@ aparte-conversation-list {
|
|
|
2039
2218
|
.aparte-range:active::-moz-range-thumb { transform: scale(1.15); }
|
|
2040
2219
|
|
|
2041
2220
|
.aparte-range:focus-visible::-webkit-slider-thumb {
|
|
2042
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
2221
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
2043
2222
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
2044
2223
|
}
|
|
2045
2224
|
.aparte-range:focus-visible::-moz-range-thumb {
|
|
2046
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
2225
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
2047
2226
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
2048
2227
|
}
|
|
2049
2228
|
|
|
@@ -2064,6 +2243,7 @@ aparte-conversation-list {
|
|
|
2064
2243
|
|
|
2065
2244
|
.aparte-field-label {
|
|
2066
2245
|
display: inline-block;
|
|
2246
|
+
vertical-align: middle;
|
|
2067
2247
|
margin-block-end: var(--aparte-space-1);
|
|
2068
2248
|
font-size: var(--aparte-font-size-sm);
|
|
2069
2249
|
font-weight: var(--aparte-font-weight-medium);
|
|
@@ -2151,7 +2331,7 @@ aparte-conversation-list {
|
|
|
2151
2331
|
inline-size: var(--aparte-color-size);
|
|
2152
2332
|
block-size: var(--aparte-color-size);
|
|
2153
2333
|
padding: 0;
|
|
2154
|
-
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
2334
|
+
border: var(--aparte-border-width) solid var(--aparte-border-control);
|
|
2155
2335
|
border-radius: var(--aparte-field-radius);
|
|
2156
2336
|
background: none;
|
|
2157
2337
|
cursor: pointer;
|
|
@@ -2161,7 +2341,7 @@ aparte-conversation-list {
|
|
|
2161
2341
|
.aparte-color::-moz-color-swatch { border: none; border-radius: var(--aparte-radius-sm); }
|
|
2162
2342
|
.aparte-color:focus-visible {
|
|
2163
2343
|
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
2164
|
-
outline-offset: var(--aparte-
|
|
2344
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
2165
2345
|
}
|
|
2166
2346
|
.aparte-color:disabled { cursor: not-allowed; opacity: var(--aparte-disabled-opacity); }
|
|
2167
2347
|
|
|
@@ -2217,6 +2397,7 @@ aparte-conversation-list {
|
|
|
2217
2397
|
would invite a click. */
|
|
2218
2398
|
.aparte-output {
|
|
2219
2399
|
display: inline-block;
|
|
2400
|
+
vertical-align: middle;
|
|
2220
2401
|
padding: var(--aparte-output-padding);
|
|
2221
2402
|
border-radius: var(--aparte-radius-sm);
|
|
2222
2403
|
background: var(--aparte-output-bg);
|
|
@@ -2299,19 +2480,22 @@ aparte-conversation-list {
|
|
|
2299
2480
|
}
|
|
2300
2481
|
.aparte-field-choice:has(:focus-visible) {
|
|
2301
2482
|
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
2302
|
-
outline-offset: var(--aparte-
|
|
2483
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
2303
2484
|
}
|
|
2304
2485
|
|
|
2305
2486
|
.aparte-field-choice__body { flex: 1 1 auto; min-width: 0; }
|
|
2306
2487
|
/* =========================================
|
|
2307
2488
|
aparte-avatar — sizes, shape, image + initials fallback
|
|
2308
2489
|
=========================================
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2490
|
+
`--aparte-avatar-size` is NOT invented here: it already exists centrally for the
|
|
2491
|
+
message avatar (bubble.css), and this component reads the same one — a generic
|
|
2492
|
+
avatar and a chat-message avatar are the same object, and a size modifier applied
|
|
2493
|
+
here resizes a role-coloured message avatar too. The corner, the initials and the
|
|
2494
|
+
group's overlap are FRACTIONS of that size (`--aparte-avatar-radius-ratio`,
|
|
2495
|
+
`--aparte-avatar-initials-ratio`, `--aparte-avatar-overlap-ratio`), computed on the
|
|
2496
|
+
element so a size modifier moves them: as absolute tokens the 40px avatar drew the
|
|
2497
|
+
same 11px initials as the 32px one, the corner drifted from squircle to square up
|
|
2498
|
+
the ramp, and a 6px overlap was a fifth of a small avatar and a tenth of a large one.
|
|
2315
2499
|
|
|
2316
2500
|
<span class="aparte-avatar aparte-avatar--sm">AP</span>
|
|
2317
2501
|
<span class="aparte-avatar">JD</span>
|
|
@@ -2326,16 +2510,17 @@ aparte-conversation-list {
|
|
|
2326
2510
|
.aparte-avatar {
|
|
2327
2511
|
position: relative;
|
|
2328
2512
|
display: inline-flex;
|
|
2513
|
+
vertical-align: middle;
|
|
2329
2514
|
flex-shrink: 0;
|
|
2330
2515
|
align-items: center;
|
|
2331
2516
|
justify-content: center;
|
|
2332
2517
|
overflow: hidden;
|
|
2333
2518
|
width: var(--aparte-avatar-size);
|
|
2334
2519
|
height: var(--aparte-avatar-size);
|
|
2335
|
-
border-radius: var(--aparte-avatar-radius);
|
|
2520
|
+
border-radius: calc(var(--aparte-avatar-size) * var(--aparte-avatar-radius-ratio));
|
|
2336
2521
|
background: var(--aparte-avatar-fallback-bg);
|
|
2337
2522
|
color: var(--aparte-avatar-fallback-color);
|
|
2338
|
-
font-size: var(--aparte-avatar-
|
|
2523
|
+
font-size: calc(var(--aparte-avatar-size) * var(--aparte-avatar-initials-ratio));
|
|
2339
2524
|
font-weight: var(--aparte-font-weight-semibold);
|
|
2340
2525
|
line-height: var(--aparte-line-height-none);
|
|
2341
2526
|
text-transform: uppercase;
|
|
@@ -2359,13 +2544,13 @@ aparte-conversation-list {
|
|
|
2359
2544
|
|
|
2360
2545
|
/* Square is the default shape and needs no class (it is what the message avatar
|
|
2361
2546
|
already renders); named anyway so a square avatar can say so rather than omit. */
|
|
2362
|
-
.aparte-avatar--square { border-radius: var(--aparte-avatar-radius); }
|
|
2547
|
+
.aparte-avatar--square { border-radius: calc(var(--aparte-avatar-size) * var(--aparte-avatar-radius-ratio)); }
|
|
2363
2548
|
.aparte-avatar--circle { border-radius: var(--aparte-radius-full); }
|
|
2364
2549
|
|
|
2365
|
-
.aparte-avatar--xs { --aparte-avatar-size: var(--aparte-avatar-size-xs);
|
|
2366
|
-
.aparte-avatar--sm { --aparte-avatar-size: var(--aparte-avatar-size-sm);
|
|
2367
|
-
.aparte-avatar--lg { --aparte-avatar-size: var(--aparte-avatar-size-lg);
|
|
2368
|
-
.aparte-avatar--xl { --aparte-avatar-size: var(--aparte-avatar-size-xl);
|
|
2550
|
+
.aparte-avatar--xs { --aparte-avatar-size: var(--aparte-avatar-size-xs); }
|
|
2551
|
+
.aparte-avatar--sm { --aparte-avatar-size: var(--aparte-avatar-size-sm); }
|
|
2552
|
+
.aparte-avatar--lg { --aparte-avatar-size: var(--aparte-avatar-size-lg); }
|
|
2553
|
+
.aparte-avatar--xl { --aparte-avatar-size: var(--aparte-avatar-size-xl); }
|
|
2369
2554
|
|
|
2370
2555
|
/* ── Group — a fan of avatars that read as one party. ─────────────────────────
|
|
2371
2556
|
No z-index is set: later siblings simply paint over earlier ones in normal
|
|
@@ -2373,13 +2558,14 @@ aparte-conversation-list {
|
|
|
2373
2558
|
the one thing z-index would otherwise be needed for. */
|
|
2374
2559
|
.aparte-avatar-group {
|
|
2375
2560
|
display: inline-flex;
|
|
2561
|
+
vertical-align: middle;
|
|
2376
2562
|
align-items: center;
|
|
2377
2563
|
}
|
|
2378
2564
|
.aparte-avatar-group > .aparte-avatar {
|
|
2379
2565
|
box-shadow: 0 0 0 var(--aparte-avatar-group-ring-width) var(--aparte-avatar-group-ring-color);
|
|
2380
2566
|
}
|
|
2381
2567
|
.aparte-avatar-group > .aparte-avatar:not(:first-child) {
|
|
2382
|
-
margin-inline-start: calc(var(--aparte-avatar-
|
|
2568
|
+
margin-inline-start: calc(var(--aparte-avatar-size) * var(--aparte-avatar-overlap-ratio) * -1);
|
|
2383
2569
|
}
|
|
2384
2570
|
.aparte-avatar-group > .aparte-avatar:hover {
|
|
2385
2571
|
z-index: var(--aparte-z-raised);
|
|
@@ -2498,6 +2684,8 @@ aparte-icon {
|
|
|
2498
2684
|
--aparte-derived-ink: var(--aparte-on-intent);
|
|
2499
2685
|
|
|
2500
2686
|
display: inline-flex;
|
|
2687
|
+
|
|
2688
|
+
vertical-align: middle;
|
|
2501
2689
|
align-items: center;
|
|
2502
2690
|
justify-content: center;
|
|
2503
2691
|
gap: var(--aparte-badge-gap);
|
|
@@ -2595,6 +2783,7 @@ aparte-icon {
|
|
|
2595
2783
|
.aparte-tag {
|
|
2596
2784
|
box-sizing: border-box;
|
|
2597
2785
|
display: inline-flex;
|
|
2786
|
+
vertical-align: middle;
|
|
2598
2787
|
align-items: center;
|
|
2599
2788
|
gap: var(--aparte-tag-gap);
|
|
2600
2789
|
max-width: 100%;
|
|
@@ -2657,8 +2846,8 @@ aparte-icon {
|
|
|
2657
2846
|
* <span class="aparte-thumbnail aparte-thumbnail--lg"><img class="aparte-thumbnail__image"
|
|
2658
2847
|
* src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 3'%3E%3Crect width='2' height='3' fill='%23b07d33'/%3E%3C/svg%3E"
|
|
2659
2848
|
* alt="A screenshot"></span>
|
|
2660
|
-
* <span class="aparte-thumbnail"><span class="aparte-
|
|
2661
|
-
* <span class="aparte-thumbnail aparte-thumbnail--sm"><span class="aparte-
|
|
2849
|
+
* <span class="aparte-thumbnail"><span class="aparte-thumbnail__label">PDF</span></span>
|
|
2850
|
+
* <span class="aparte-thumbnail aparte-thumbnail--sm"><span class="aparte-thumbnail__label">MD</span></span>
|
|
2662
2851
|
*
|
|
2663
2852
|
* Distinct from `.aparte-avatar`, which is the same shape for a different reason: an
|
|
2664
2853
|
* avatar stands for a PERSON and falls back to initials, so it carries type styling and
|
|
@@ -2758,21 +2947,30 @@ aparte-icon {
|
|
|
2758
2947
|
--aparte-thumbnail-border: var(--aparte-attachment-chip-border);
|
|
2759
2948
|
}
|
|
2760
2949
|
|
|
2761
|
-
/* Only an image
|
|
2762
|
-
aparteGlobalConfig.setHostHandlers(). Undeclared, it is a picture, not a control.
|
|
2763
|
-
|
|
2950
|
+
/* Only an image the app declared it can preview is a button — see
|
|
2951
|
+
aparteGlobalConfig.setHostHandlers(). Undeclared, it is a picture, not a control.
|
|
2952
|
+
|
|
2953
|
+
Two selectors because the two strips carry the role in two places, and both are
|
|
2954
|
+
right. The sent-message strip has no ✕, so its TILE is the whole control. The
|
|
2955
|
+
composer's tile wraps the remove button, so the control there is the IMAGE — a
|
|
2956
|
+
button inside a button is what the role on the tile used to make. */
|
|
2957
|
+
.aparte-thumb--image[role='button'],
|
|
2958
|
+
.aparte-thumbnail__image[role='button'] {
|
|
2764
2959
|
cursor: pointer;
|
|
2765
2960
|
}
|
|
2766
2961
|
|
|
2767
2962
|
.aparte-thumb--image[role='button']:focus-visible {
|
|
2768
|
-
|
|
2963
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
2964
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
2769
2965
|
}
|
|
2770
2966
|
|
|
2771
|
-
.
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2967
|
+
/* Inset, unlike the tile's. The image fills the tile edge to edge and the tile is the
|
|
2968
|
+
frame (`overflow: hidden` above), so an outline drawn outward is cropped away — the
|
|
2969
|
+
focus ring would simply not exist. Pulled in by its own width, it lands just inside
|
|
2970
|
+
the border and stays whole. */
|
|
2971
|
+
.aparte-thumbnail__image[role='button']:focus-visible {
|
|
2972
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
2973
|
+
outline-offset: calc(-1 * var(--aparte-focus-outline-width));
|
|
2776
2974
|
}
|
|
2777
2975
|
|
|
2778
2976
|
.aparte-thumb--file {
|
|
@@ -2781,13 +2979,6 @@ aparte-icon {
|
|
|
2781
2979
|
justify-content: center;
|
|
2782
2980
|
}
|
|
2783
2981
|
|
|
2784
|
-
.aparte-thumb__ext {
|
|
2785
|
-
font-size: var(--aparte-font-size-xs);
|
|
2786
|
-
font-weight: var(--aparte-font-weight-bold);
|
|
2787
|
-
letter-spacing: 0.03em;
|
|
2788
|
-
color: var(--aparte-text-muted);
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
2982
|
.aparte-thumb__name {
|
|
2792
2983
|
position: absolute;
|
|
2793
2984
|
left: 0;
|
|
@@ -2818,9 +3009,13 @@ aparte-icon {
|
|
|
2818
3009
|
*/
|
|
2819
3010
|
|
|
2820
3011
|
.aparte-spinner {
|
|
2821
|
-
|
|
2822
3012
|
display: inline-block;
|
|
3013
|
+
vertical-align: middle;
|
|
2823
3014
|
flex-shrink: 0;
|
|
3015
|
+
/* The token is the ring's outer size, border included: without this the ring painted
|
|
3016
|
+
16/20/28 for tokens of 12/16/24 (the 2px stroke added on each side), and painted
|
|
3017
|
+
the token's value only on a host page with its own border-box reset. */
|
|
3018
|
+
box-sizing: border-box;
|
|
2824
3019
|
width: var(--aparte-spinner-size);
|
|
2825
3020
|
height: var(--aparte-spinner-size);
|
|
2826
3021
|
border: var(--aparte-spinner-thickness) solid currentColor;
|
|
@@ -2835,6 +3030,15 @@ aparte-icon {
|
|
|
2835
3030
|
|
|
2836
3031
|
.aparte-spinner--sm { --aparte-spinner-size: var(--aparte-spinner-size-sm); }
|
|
2837
3032
|
.aparte-spinner--lg { --aparte-spinner-size: var(--aparte-spinner-size-lg); }
|
|
3033
|
+
|
|
3034
|
+
/* Under reduced motion the loop stops outright. The duration tokens are reset to
|
|
3035
|
+
0.01ms in responsive.css, but that leaves `infinite` in place: a 0.01ms cycle
|
|
3036
|
+
is not stillness, it is a flicker. The descendant sweep there does not reach a
|
|
3037
|
+
recipe used outside aparté's own elements, so each looping recipe stops itself,
|
|
3038
|
+
the way display/icon.css already does. */
|
|
3039
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3040
|
+
.aparte-spinner { animation: none; }
|
|
3041
|
+
}
|
|
2838
3042
|
/* =========================================
|
|
2839
3043
|
aparte-progress — determinate bar + indeterminate variant
|
|
2840
3044
|
=========================================
|
|
@@ -2859,7 +3063,9 @@ aparte-icon {
|
|
|
2859
3063
|
width: 100%;
|
|
2860
3064
|
height: var(--aparte-progress-height);
|
|
2861
3065
|
border-radius: var(--aparte-radius-full);
|
|
2862
|
-
|
|
3066
|
+
/* The track: a reference the value is read against, so it has to be seen. A surface
|
|
3067
|
+
level put it at 1.06:1 in light and 1.20:1 in dark — one token, two distances. */
|
|
3068
|
+
background: var(--aparte-track);
|
|
2863
3069
|
}
|
|
2864
3070
|
|
|
2865
3071
|
.aparte-progress__bar {
|
|
@@ -2877,6 +3083,21 @@ aparte-icon {
|
|
|
2877
3083
|
animation: aparte-progress-slide var(--aparte-duration-pulse) var(--aparte-ease) infinite;
|
|
2878
3084
|
}
|
|
2879
3085
|
|
|
3086
|
+
/* Under reduced motion the loop stops outright. The duration tokens are reset to
|
|
3087
|
+
0.01ms in responsive.css, but that leaves `infinite` in place: a 0.01ms cycle
|
|
3088
|
+
is not stillness, it is a flicker. The descendant sweep there does not reach a
|
|
3089
|
+
recipe used outside aparté's own elements, so each looping recipe stops itself,
|
|
3090
|
+
the way display/icon.css already does.
|
|
3091
|
+
Stopped, the sliding segment would sit at one spot and read as a value; the whole
|
|
3092
|
+
track filled says "busy" without naming an amount. */
|
|
3093
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3094
|
+
.aparte-progress--indeterminate .aparte-progress__bar {
|
|
3095
|
+
animation: none;
|
|
3096
|
+
width: 100%;
|
|
3097
|
+
inset-inline-start: 0;
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
|
|
2880
3101
|
/* inset-inline-start, not transform: translateX — a transform has no logical
|
|
2881
3102
|
axis, so an RTL page would slide the indeterminate segment backwards. Animating
|
|
2882
3103
|
a logical inset instead makes the sweep follow reading direction automatically. */
|
|
@@ -2909,10 +3130,15 @@ aparte-icon {
|
|
|
2909
3130
|
<div>
|
|
2910
3131
|
<span class="aparte-skeleton aparte-skeleton--text" aria-hidden="true"></span>
|
|
2911
3132
|
<span class="aparte-skeleton aparte-skeleton--text" aria-hidden="true"></span>
|
|
2912
|
-
<span class="aparte-skeleton aparte-skeleton--text" aria-hidden="true"></span>
|
|
3133
|
+
<span class="aparte-skeleton aparte-skeleton--text" style="max-inline-size: 60%" aria-hidden="true"></span>
|
|
2913
3134
|
</div>
|
|
2914
|
-
<span class="aparte-skeleton aparte-skeleton--rect" style="grid-column:
|
|
3135
|
+
<span class="aparte-skeleton aparte-skeleton--rect" style="grid-column: 2" aria-hidden="true"></span>
|
|
2915
3136
|
</div>
|
|
3137
|
+
|
|
3138
|
+
The last line is shorter because the last line of a paragraph is, and the block sits
|
|
3139
|
+
in the text column, under the lines it stands for — not across the avatar's. The
|
|
3140
|
+
rect's height is the family's token (`--aparte-skeleton-rect-height`), not an inline
|
|
3141
|
+
value that contradicts it.
|
|
2916
3142
|
*/
|
|
2917
3143
|
|
|
2918
3144
|
.aparte-skeleton {
|
|
@@ -2929,6 +3155,19 @@ aparte-icon {
|
|
|
2929
3155
|
animation: aparte-skeleton-shimmer var(--aparte-duration-pulse) linear infinite;
|
|
2930
3156
|
}
|
|
2931
3157
|
|
|
3158
|
+
/* Under reduced motion the loop stops outright. The duration tokens are reset to
|
|
3159
|
+
0.01ms in responsive.css, but that leaves `infinite` in place: a 0.01ms cycle
|
|
3160
|
+
is not stillness, it is a flicker. The descendant sweep there does not reach a
|
|
3161
|
+
recipe used outside aparté's own elements, so each looping recipe stops itself,
|
|
3162
|
+
the way display/icon.css already does.
|
|
3163
|
+
The shine goes with the shimmer: a stopped gradient would sit as a pale band. */
|
|
3164
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3165
|
+
.aparte-skeleton {
|
|
3166
|
+
animation: none;
|
|
3167
|
+
background-image: none;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
|
|
2932
3171
|
.aparte-skeleton--text {
|
|
2933
3172
|
width: 100%;
|
|
2934
3173
|
height: var(--aparte-skeleton-text-height);
|
|
@@ -2936,7 +3175,7 @@ aparte-icon {
|
|
|
2936
3175
|
}
|
|
2937
3176
|
.aparte-skeleton--text + .aparte-skeleton--text { margin-block-start: var(--aparte-skeleton-text-gap); }
|
|
2938
3177
|
/* The ragged last line reads as "text", where a full-width block reads as a bar. */
|
|
2939
|
-
.aparte-skeleton--text:last-child { width: var(--aparte-skeleton-text-last-width); }
|
|
3178
|
+
.aparte-skeleton--text:last-child:not(:first-child) { width: var(--aparte-skeleton-text-last-width); }
|
|
2940
3179
|
|
|
2941
3180
|
.aparte-skeleton--circle {
|
|
2942
3181
|
width: var(--aparte-skeleton-circle-size);
|
|
@@ -3024,6 +3263,7 @@ aparte-icon {
|
|
|
3024
3263
|
<button class="aparte-alert__dismiss" aria-label="Dismiss"><aparte-icon name="close"></aparte-icon></button>
|
|
3025
3264
|
</div>
|
|
3026
3265
|
<div class="aparte-alert aparte-alert--danger" role="alert">
|
|
3266
|
+
<span class="aparte-alert__icon"><aparte-icon name="alertTriangle"></aparte-icon></span>
|
|
3027
3267
|
<div class="aparte-alert__body">
|
|
3028
3268
|
<p class="aparte-alert__message">The request was refused: no API key is set.</p>
|
|
3029
3269
|
</div>
|
|
@@ -3114,7 +3354,7 @@ aparte-icon {
|
|
|
3114
3354
|
}
|
|
3115
3355
|
.aparte-alert__dismiss:active:not(:disabled) { filter: brightness(0.93); }
|
|
3116
3356
|
.aparte-alert__dismiss:focus-visible {
|
|
3117
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
3357
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
3118
3358
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
3119
3359
|
}
|
|
3120
3360
|
.aparte-alert__dismiss:disabled { opacity: var(--aparte-disabled-opacity); cursor: not-allowed; pointer-events: none; }
|
|
@@ -3153,6 +3393,12 @@ aparte-icon {
|
|
|
3153
3393
|
padding: var(--aparte-card-body-padding);
|
|
3154
3394
|
}
|
|
3155
3395
|
|
|
3396
|
+
/* The body owns its edges: a paragraph's block margins fold into the padding instead of
|
|
3397
|
+
stacking on it. The sheet's own example puts a <p> here, and the body band measured
|
|
3398
|
+
twice the height of the header and footer beside it. */
|
|
3399
|
+
.aparte-card__body > :first-child { margin-block-start: 0; }
|
|
3400
|
+
.aparte-card__body > :last-child { margin-block-end: 0; }
|
|
3401
|
+
|
|
3156
3402
|
.aparte-card__footer {
|
|
3157
3403
|
display: flex;
|
|
3158
3404
|
align-items: center;
|
|
@@ -3291,11 +3537,12 @@ aparte-icon {
|
|
|
3291
3537
|
* script the caller owns: a <details open> toggling itself, a positioning library placing
|
|
3292
3538
|
* a menu next to its trigger. Nothing here reaches for position:fixed on your behalf.
|
|
3293
3539
|
*
|
|
3294
|
-
* <div class="aparte-tabs" role="tablist">
|
|
3295
|
-
* <button class="aparte-tabs__tab" role="tab" aria-selected="true">Preview</button>
|
|
3296
|
-
* <button class="aparte-tabs__tab" role="tab" aria-selected="false">Code</button>
|
|
3540
|
+
* <div class="aparte-tabs" role="tablist" aria-label="Result view">
|
|
3541
|
+
* <button class="aparte-tabs__tab" role="tab" type="button" id="tabs-a-preview" aria-controls="tabs-a-preview-panel" aria-selected="true" tabindex="0">Preview</button>
|
|
3542
|
+
* <button class="aparte-tabs__tab" role="tab" type="button" id="tabs-a-code" aria-controls="tabs-a-code-panel" aria-selected="false" tabindex="-1">Code</button>
|
|
3297
3543
|
* </div>
|
|
3298
|
-
* <div class="aparte-tabs__panel" role="tabpanel">The panel for the selected tab.</div>
|
|
3544
|
+
* <div class="aparte-tabs__panel" role="tabpanel" id="tabs-a-preview-panel" aria-labelledby="tabs-a-preview">The panel for the selected tab.</div>
|
|
3545
|
+
* <div class="aparte-tabs__panel" role="tabpanel" id="tabs-a-code-panel" aria-labelledby="tabs-a-code" hidden>The panel the other tab opens.</div>
|
|
3299
3546
|
*
|
|
3300
3547
|
* <div class="aparte-accordion">
|
|
3301
3548
|
* <details class="aparte-accordion__item" open>
|
|
@@ -3342,17 +3589,29 @@ aparte-icon {
|
|
|
3342
3589
|
the current tab, which reads as a control rather than as navigation. Neither moves
|
|
3343
3590
|
anything: `aria-selected` is what says which tab is current, and the app owns it.
|
|
3344
3591
|
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3592
|
+
The markup below is the whole contract of the role, and all of it is yours to write:
|
|
3593
|
+
ONE tab stop for the list (`tabindex="0"` on the selected tab, `-1` on the rest),
|
|
3594
|
+
`aria-controls` from each tab to its `role="tabpanel"`, and `aria-labelledby` back.
|
|
3595
|
+
The app also owns the ArrowLeft/ArrowRight/Home/End handler that moves the selection
|
|
3596
|
+
and the stop together — `@aparte/plugin-artifacts`' card has a working one to copy.
|
|
3597
|
+
A tablist without those is louder than plain buttons and says less: every tab a tab
|
|
3598
|
+
stop, and none of them naming what it opens.
|
|
3599
|
+
|
|
3600
|
+
<div class="aparte-tabs aparte-tabs--underline" role="tablist" aria-label="Result view">
|
|
3601
|
+
<button class="aparte-tabs__tab" role="tab" type="button" id="tabs-u-preview" aria-controls="tabs-u-preview-panel" aria-selected="true" tabindex="0">Preview</button>
|
|
3602
|
+
<button class="aparte-tabs__tab" role="tab" type="button" id="tabs-u-code" aria-controls="tabs-u-code-panel" aria-selected="false" tabindex="-1">Code</button>
|
|
3348
3603
|
</div>
|
|
3349
|
-
<div class="aparte-tabs__panel" role="tabpanel">What the selected tab shows.</div>
|
|
3604
|
+
<div class="aparte-tabs__panel" role="tabpanel" id="tabs-u-preview-panel" aria-labelledby="tabs-u-preview">What the selected tab shows.</div>
|
|
3605
|
+
<div class="aparte-tabs__panel" role="tabpanel" id="tabs-u-code-panel" aria-labelledby="tabs-u-code" hidden>What the other tab shows.</div>
|
|
3350
3606
|
|
|
3351
|
-
<div class="aparte-tabs aparte-tabs--segmented" role="tablist">
|
|
3352
|
-
<button class="aparte-tabs__tab" role="tab" aria-selected="true">Light</button>
|
|
3353
|
-
<button class="aparte-tabs__tab" role="tab" aria-selected="false">Dark</button>
|
|
3354
|
-
<button class="aparte-tabs__tab" role="tab" aria-selected="false">System</button>
|
|
3607
|
+
<div class="aparte-tabs aparte-tabs--segmented" role="tablist" aria-label="Theme">
|
|
3608
|
+
<button class="aparte-tabs__tab" role="tab" type="button" id="tabs-s-light" aria-controls="tabs-s-light-panel" aria-selected="true" tabindex="0">Light</button>
|
|
3609
|
+
<button class="aparte-tabs__tab" role="tab" type="button" id="tabs-s-dark" aria-controls="tabs-s-dark-panel" aria-selected="false" tabindex="-1">Dark</button>
|
|
3610
|
+
<button class="aparte-tabs__tab" role="tab" type="button" id="tabs-s-system" aria-controls="tabs-s-system-panel" aria-selected="false" tabindex="-1">System</button>
|
|
3355
3611
|
</div>
|
|
3612
|
+
<div class="aparte-tabs__panel" role="tabpanel" id="tabs-s-light-panel" aria-labelledby="tabs-s-light">The light theme's settings.</div>
|
|
3613
|
+
<div class="aparte-tabs__panel" role="tabpanel" id="tabs-s-dark-panel" aria-labelledby="tabs-s-dark" hidden>The dark theme's settings.</div>
|
|
3614
|
+
<div class="aparte-tabs__panel" role="tabpanel" id="tabs-s-system-panel" aria-labelledby="tabs-s-system" hidden>Whatever the system asks for.</div>
|
|
3356
3615
|
========================================= */
|
|
3357
3616
|
|
|
3358
3617
|
.aparte-tabs {
|
|
@@ -3427,7 +3686,7 @@ aparte-icon {
|
|
|
3427
3686
|
}
|
|
3428
3687
|
|
|
3429
3688
|
.aparte-tabs__tab:focus-visible {
|
|
3430
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
3689
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
3431
3690
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
3432
3691
|
}
|
|
3433
3692
|
|
|
@@ -3444,10 +3703,13 @@ aparte-icon {
|
|
|
3444
3703
|
|
|
3445
3704
|
.aparte-tabs__panel {
|
|
3446
3705
|
padding-block: var(--aparte-space-6);
|
|
3706
|
+
/* The tab's own inline inset, so the panel's text starts under the label of the tab
|
|
3707
|
+
that opened it rather than 11px to its left. */
|
|
3708
|
+
padding-inline: var(--aparte-space-5);
|
|
3447
3709
|
}
|
|
3448
3710
|
|
|
3449
3711
|
.aparte-tabs__panel:focus-visible {
|
|
3450
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
3712
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
3451
3713
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
3452
3714
|
}
|
|
3453
3715
|
|
|
@@ -3471,6 +3733,9 @@ aparte-icon {
|
|
|
3471
3733
|
its own raised chip, rather than an underline beneath a plain row. Same markup,
|
|
3472
3734
|
same states; only the container and the active tab's fill change. */
|
|
3473
3735
|
.aparte-tabs--segmented {
|
|
3736
|
+
/* A control, not a bar: the track hugs its segments. As a block-level flex row it
|
|
3737
|
+
inherited its container's width — 1207px of track for 160px of chips at 1280. */
|
|
3738
|
+
display: inline-flex;
|
|
3474
3739
|
gap: 0;
|
|
3475
3740
|
padding: var(--aparte-space-1);
|
|
3476
3741
|
background: var(--aparte-surface-2);
|
|
@@ -3490,6 +3755,11 @@ aparte-icon {
|
|
|
3490
3755
|
.aparte-tabs--segmented .aparte-tabs__tab[aria-selected='true'] {
|
|
3491
3756
|
background: var(--aparte-surface-1);
|
|
3492
3757
|
color: var(--aparte-text);
|
|
3758
|
+
/* Raised RELATIVE to the track, not an absolute surface level: surface-1 over
|
|
3759
|
+
surface-2 read as raised in light and as sunken in dark, the same rule inverting
|
|
3760
|
+
its elevation with the theme. A 1px ring in the border colour says "on top" on
|
|
3761
|
+
either ground. */
|
|
3762
|
+
box-shadow: 0 0 0 var(--aparte-border-width) var(--aparte-border);
|
|
3493
3763
|
}
|
|
3494
3764
|
/* =========================================
|
|
3495
3765
|
aparte-accordion — stacked disclosures
|
|
@@ -3506,7 +3776,21 @@ aparte-icon {
|
|
|
3506
3776
|
Shipping
|
|
3507
3777
|
<aparte-icon name="expand"></aparte-icon>
|
|
3508
3778
|
</summary>
|
|
3509
|
-
<div class="aparte-accordion__panel">Free above 50
|
|
3779
|
+
<div class="aparte-accordion__panel">Free above 50 €, otherwise 4,90 €. Two to four working days.</div>
|
|
3780
|
+
</details>
|
|
3781
|
+
<details class="aparte-accordion__item">
|
|
3782
|
+
<summary class="aparte-accordion__header">
|
|
3783
|
+
Returns
|
|
3784
|
+
<aparte-icon name="expand"></aparte-icon>
|
|
3785
|
+
</summary>
|
|
3786
|
+
<div class="aparte-accordion__panel">Thirty days, in the original packaging.</div>
|
|
3787
|
+
</details>
|
|
3788
|
+
<details class="aparte-accordion__item">
|
|
3789
|
+
<summary class="aparte-accordion__header">
|
|
3790
|
+
Payment
|
|
3791
|
+
<aparte-icon name="expand"></aparte-icon>
|
|
3792
|
+
</summary>
|
|
3793
|
+
<div class="aparte-accordion__panel">Card, PayPal, or an invoice for a company account.</div>
|
|
3510
3794
|
</details>
|
|
3511
3795
|
</div>
|
|
3512
3796
|
|
|
@@ -3574,7 +3858,7 @@ aparte-icon {
|
|
|
3574
3858
|
}
|
|
3575
3859
|
|
|
3576
3860
|
.aparte-accordion__header:focus-visible {
|
|
3577
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
3861
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
3578
3862
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
3579
3863
|
}
|
|
3580
3864
|
|
|
@@ -3584,13 +3868,22 @@ aparte-icon {
|
|
|
3584
3868
|
opacity: var(--aparte-disabled-opacity);
|
|
3585
3869
|
}
|
|
3586
3870
|
|
|
3587
|
-
|
|
3871
|
+
/* Two spellings for the chevron, because the header's documented markup is a bare
|
|
3872
|
+
`<aparte-icon>` and a renderer that wraps a raw glyph needs a class to hang it on:
|
|
3873
|
+
the wrapper `.aparte-accordion__icon` (the thinking block), or an `<aparte-icon>` /
|
|
3874
|
+
`svg.aparte-icon` sitting directly in the header. Both are sized and both turn — the
|
|
3875
|
+
documented form used to match neither rule, so the kit's own example showed an open
|
|
3876
|
+
panel under a chevron still pointing down. */
|
|
3877
|
+
.aparte-accordion__icon,
|
|
3878
|
+
.aparte-accordion__header > aparte-icon,
|
|
3879
|
+
.aparte-accordion__header > .aparte-icon {
|
|
3880
|
+
display: inline-flex;
|
|
3588
3881
|
flex-shrink: 0;
|
|
3589
|
-
/*
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3882
|
+
/* Fed to the glyph through its own knob, not drawn as a width/height: the icon
|
|
3883
|
+
recipe reads `--aparte-icon-size`, and a box declared here around it would leave
|
|
3884
|
+
the glyph at its loose default. The step is the one the button recipe uses for an
|
|
3885
|
+
icon glyph, so a disclosure chevron reads as the same weight of icon everywhere. */
|
|
3886
|
+
--aparte-icon-size: var(--aparte-space-8);
|
|
3594
3887
|
color: var(--aparte-text-muted);
|
|
3595
3888
|
transition: transform var(--aparte-duration-slow) var(--aparte-ease);
|
|
3596
3889
|
}
|
|
@@ -3599,7 +3892,11 @@ aparte-icon {
|
|
|
3599
3892
|
puts aria-expanded on the header itself; native <details> puts `open` on the
|
|
3600
3893
|
ancestor and gives the browser no attribute to select on at all. */
|
|
3601
3894
|
.aparte-accordion__header[aria-expanded='true'] .aparte-accordion__icon,
|
|
3602
|
-
|
|
3895
|
+
.aparte-accordion__header[aria-expanded='true'] > aparte-icon,
|
|
3896
|
+
.aparte-accordion__header[aria-expanded='true'] > .aparte-icon,
|
|
3897
|
+
details[open] > .aparte-accordion__header .aparte-accordion__icon,
|
|
3898
|
+
details[open] > .aparte-accordion__header > aparte-icon,
|
|
3899
|
+
details[open] > .aparte-accordion__header > .aparte-icon {
|
|
3603
3900
|
transform: rotate(180deg);
|
|
3604
3901
|
}
|
|
3605
3902
|
|
|
@@ -3673,10 +3970,13 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3673
3970
|
width: max-content;
|
|
3674
3971
|
min-width: var(--aparte-menu-min-width);
|
|
3675
3972
|
max-width: var(--aparte-menu-max-width);
|
|
3973
|
+
/* The two caps are the box's outer width, padding and border included — same rule
|
|
3974
|
+
as the popover, for the same reason. */
|
|
3975
|
+
box-sizing: border-box;
|
|
3676
3976
|
padding: var(--aparte-space-2);
|
|
3677
3977
|
background: var(--aparte-surface-1);
|
|
3678
3978
|
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
3679
|
-
border-radius: var(--aparte-radius-
|
|
3979
|
+
border-radius: var(--aparte-radius-menu);
|
|
3680
3980
|
box-shadow: var(--aparte-menu-shadow);
|
|
3681
3981
|
color: var(--aparte-text);
|
|
3682
3982
|
z-index: var(--aparte-z-dropdown);
|
|
@@ -3722,7 +4022,7 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3722
4022
|
}
|
|
3723
4023
|
|
|
3724
4024
|
.aparte-menu__item:focus-visible {
|
|
3725
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
4025
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
3726
4026
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
3727
4027
|
}
|
|
3728
4028
|
|
|
@@ -3735,14 +4035,14 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3735
4035
|
pointer-events: none;
|
|
3736
4036
|
}
|
|
3737
4037
|
|
|
3738
|
-
/* The check mark for menuitemcheckbox / menuitemradio.
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
a plain role="menuitem"
|
|
3742
|
-
rather than a fill or a box-shadow is deliberate: a
|
|
3743
|
-
which forced-colors mode preserves, so the checked state
|
|
3744
|
-
with no separate override. */
|
|
3745
|
-
.aparte-menu__item[aria-checked]::before {
|
|
4038
|
+
/* The check mark for menuitemcheckbox / menuitemradio. The gutter is reserved on EVERY
|
|
4039
|
+
item of a panel that holds a checkable one — as an invisible glyph — so all the
|
|
4040
|
+
labels of that panel share one edge. It used to be reserved per checkable item, and
|
|
4041
|
+
a plain role="menuitem" beside a checkable one started 16px further left: two label
|
|
4042
|
+
edges in one menu. Text colour rather than a fill or a box-shadow is deliberate: a
|
|
4043
|
+
glyph inherits `color`, which forced-colors mode preserves, so the checked state
|
|
4044
|
+
survives that mode with no separate override. */
|
|
4045
|
+
.aparte-menu:has(.aparte-menu__item[aria-checked]) .aparte-menu__item::before {
|
|
3746
4046
|
content: '✓';
|
|
3747
4047
|
flex-shrink: 0;
|
|
3748
4048
|
visibility: hidden;
|
|
@@ -3817,11 +4117,14 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3817
4117
|
display: flex;
|
|
3818
4118
|
flex-direction: column;
|
|
3819
4119
|
gap: var(--aparte-space-3);
|
|
4120
|
+
/* The cap is the box's outer width: it measured 342px for a documented 320 (padding
|
|
4121
|
+
and border outside the cap) until the recipe said so itself. */
|
|
4122
|
+
box-sizing: border-box;
|
|
3820
4123
|
max-width: var(--aparte-popover-max-width);
|
|
3821
4124
|
padding: var(--aparte-space-5);
|
|
3822
4125
|
background: var(--aparte-surface-1);
|
|
3823
4126
|
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
3824
|
-
border-radius: var(--aparte-radius-
|
|
4127
|
+
border-radius: var(--aparte-radius-popover);
|
|
3825
4128
|
box-shadow: var(--aparte-popover-shadow);
|
|
3826
4129
|
color: var(--aparte-text);
|
|
3827
4130
|
font-size: var(--aparte-font-size-md);
|
|
@@ -3884,7 +4187,7 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3884
4187
|
background: var(--aparte-surface-1);
|
|
3885
4188
|
color: var(--aparte-text);
|
|
3886
4189
|
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
3887
|
-
border-radius: var(--aparte-radius-
|
|
4190
|
+
border-radius: var(--aparte-radius-dialog);
|
|
3888
4191
|
box-shadow: var(--aparte-menu-shadow);
|
|
3889
4192
|
}
|
|
3890
4193
|
|
|
@@ -3960,11 +4263,20 @@ body:has(.aparte-dialog[open]) {
|
|
|
3960
4263
|
.aparte-dialog:modal {
|
|
3961
4264
|
width: 100%;
|
|
3962
4265
|
max-width: 100%;
|
|
3963
|
-
|
|
4266
|
+
/* As tall as its content, capped at the screen, sitting on the bottom edge: a sheet
|
|
4267
|
+
stretched to 100dvh held a label, a field and two buttons over 586px of nothing.
|
|
4268
|
+
`fit-content`, not `auto` — on a positioned box with both block insets set, an
|
|
4269
|
+
auto height FILLS them (CSS 2 §10.6.4: the auto margins go to 0 and the height
|
|
4270
|
+
is solved for), so `auto` measured the same 720 the explicit 100dvh had. What
|
|
4271
|
+
the UA gives a dialog is `fit-content`, and it is what lets the auto top margin
|
|
4272
|
+
push the sheet onto the bottom edge. And the safe areas: the ✕ used to sit 6px
|
|
4273
|
+
from the physical top. */
|
|
4274
|
+
height: fit-content;
|
|
3964
4275
|
max-height: 100dvh;
|
|
3965
|
-
margin: 0;
|
|
4276
|
+
margin: auto 0 0;
|
|
4277
|
+
padding-block: env(safe-area-inset-top, 0px) env(safe-area-inset-bottom, 0px);
|
|
3966
4278
|
border: 0;
|
|
3967
|
-
border-radius: 0;
|
|
4279
|
+
border-radius: var(--aparte-radius-dialog) var(--aparte-radius-dialog) 0 0;
|
|
3968
4280
|
}
|
|
3969
4281
|
}
|
|
3970
4282
|
|
|
@@ -4001,23 +4313,22 @@ body:has(.aparte-dialog[open]) {
|
|
|
4001
4313
|
and skips the dark-theme override block for that reason; the artifact "paper" tokens
|
|
4002
4314
|
are the same decision made for the same reason.
|
|
4003
4315
|
|
|
4004
|
-
<span
|
|
4005
|
-
<div class="aparte-tooltip" role="tooltip"
|
|
4006
|
-
style="position: absolute; bottom: calc(100% + 8px); left: 50%; translate: -50% 0">
|
|
4316
|
+
<span class="aparte-tooltip-anchor" style="margin-block-start: 2.5rem; margin-inline-start: 5rem">
|
|
4317
|
+
<div class="aparte-tooltip" data-side="top" role="tooltip">
|
|
4007
4318
|
Copy to clipboard
|
|
4008
4319
|
<span class="aparte-tooltip__arrow"></span>
|
|
4009
4320
|
</div>
|
|
4010
4321
|
<button class="aparte-btn aparte-btn--icon" aria-label="Copy"><aparte-icon name="copy"></aparte-icon></button>
|
|
4011
4322
|
</span>
|
|
4012
4323
|
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4324
|
+
`data-side` places it — `top`, `bottom`, `start` or `end` — against the
|
|
4325
|
+
`.aparte-tooltip-anchor` that wraps the trigger, `--aparte-tooltip-gap` away, and turns
|
|
4326
|
+
the arrow to match. The two inline styles that used to do this in the example were a
|
|
4327
|
+
knob missing, not a division of labour: a demo that needs inline styles to work is a
|
|
4328
|
+
recipe with a parameter it forgot. What stays yours is the viewport: nothing here flips
|
|
4329
|
+
a tooltip that would leave the screen — that needs script, a positioning library's job.
|
|
4330
|
+
Without `data-side` the box is not positioned at all, as before, and its placement is
|
|
4331
|
+
yours (the one inline style left in the example is the demo's own margin).
|
|
4021
4332
|
|
|
4022
4333
|
`pointer-events: none` is not a detail: a tooltip describes what the pointer is over,
|
|
4023
4334
|
so it must never itself become that thing, or hovering it re-triggers the hover that
|
|
@@ -4034,11 +4345,17 @@ body:has(.aparte-dialog[open]) {
|
|
|
4034
4345
|
|
|
4035
4346
|
position: relative;
|
|
4036
4347
|
display: inline-block;
|
|
4348
|
+
vertical-align: middle;
|
|
4349
|
+
/* As wide as its label, up to the cap. With a `max-width` and no intrinsic width an
|
|
4350
|
+
absolutely-positioned chip shrinks to `min-content`: "Copy to clipboard" broke into
|
|
4351
|
+
two lines at every width, 71px wide — the widest word plus padding. */
|
|
4352
|
+
width: max-content;
|
|
4353
|
+
box-sizing: border-box;
|
|
4037
4354
|
max-width: var(--aparte-tooltip-max-width);
|
|
4038
4355
|
padding: var(--aparte-space-2) var(--aparte-space-4);
|
|
4039
4356
|
background: var(--aparte-tooltip-bg);
|
|
4040
4357
|
color: var(--aparte-tooltip-color);
|
|
4041
|
-
border-radius: var(--aparte-radius-
|
|
4358
|
+
border-radius: var(--aparte-radius-tooltip);
|
|
4042
4359
|
box-shadow: var(--aparte-tooltip-shadow);
|
|
4043
4360
|
font-size: var(--aparte-font-size-sm);
|
|
4044
4361
|
line-height: var(--aparte-line-height-snug);
|
|
@@ -4050,12 +4367,49 @@ body:has(.aparte-dialog[open]) {
|
|
|
4050
4367
|
pointer-events: none;
|
|
4051
4368
|
}
|
|
4052
4369
|
|
|
4053
|
-
/*
|
|
4054
|
-
it
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4370
|
+
/* Where the box goes: the anchor is the trigger's wrapper and the tooltip is placed
|
|
4371
|
+
against it. `translate` centres the box on the axis across the side: with a logical
|
|
4372
|
+
inset of 50% the box's start edge sits at the anchor's middle, and moving it back by
|
|
4373
|
+
half its own size centres it — in both writing directions, because the inset flips
|
|
4374
|
+
with the direction and the translate is half the box either way. */
|
|
4375
|
+
.aparte-tooltip-anchor {
|
|
4376
|
+
position: relative;
|
|
4377
|
+
display: inline-block;
|
|
4378
|
+
vertical-align: middle;
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
.aparte-tooltip[data-side] {
|
|
4382
|
+
position: absolute;
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
.aparte-tooltip[data-side='top'] {
|
|
4386
|
+
inset-block-end: calc(100% + var(--aparte-tooltip-gap));
|
|
4387
|
+
inset-inline-start: 50%;
|
|
4388
|
+
translate: -50% 0;
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
.aparte-tooltip[data-side='bottom'] {
|
|
4392
|
+
inset-block-start: calc(100% + var(--aparte-tooltip-gap));
|
|
4393
|
+
inset-inline-start: 50%;
|
|
4394
|
+
translate: -50% 0;
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
.aparte-tooltip[data-side='start'] {
|
|
4398
|
+
inset-inline-end: calc(100% + var(--aparte-tooltip-gap));
|
|
4399
|
+
inset-block-start: 50%;
|
|
4400
|
+
translate: 0 -50%;
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
.aparte-tooltip[data-side='end'] {
|
|
4404
|
+
inset-inline-start: calc(100% + var(--aparte-tooltip-gap));
|
|
4405
|
+
inset-block-start: 50%;
|
|
4406
|
+
translate: 0 -50%;
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
/* The arrow points DOWN by default — the common case, a tooltip above the thing it
|
|
4410
|
+
labels — and each `data-side` turns it toward the anchor. Centring on the box's edge
|
|
4411
|
+
uses a logical inset plus a negative margin rather than a transform, so it re-centres
|
|
4412
|
+
under `direction: rtl` without a second rule. */
|
|
4059
4413
|
.aparte-tooltip__arrow {
|
|
4060
4414
|
position: absolute;
|
|
4061
4415
|
inset-inline-start: 50%;
|
|
@@ -4074,6 +4428,26 @@ body:has(.aparte-dialog[open]) {
|
|
|
4074
4428
|
inset-block-start: calc(var(--aparte-space-4) * -0.5);
|
|
4075
4429
|
}
|
|
4076
4430
|
|
|
4431
|
+
/* On the two inline sides the arrow sits on the edge that faces the anchor, centred
|
|
4432
|
+
vertically: the box is to the START of the trigger, so its arrow is on its END edge,
|
|
4433
|
+
and the reverse. */
|
|
4434
|
+
.aparte-tooltip[data-side='start'] .aparte-tooltip__arrow {
|
|
4435
|
+
inset-block-end: auto;
|
|
4436
|
+
inset-block-start: 50%;
|
|
4437
|
+
inset-inline-start: auto;
|
|
4438
|
+
inset-inline-end: calc(var(--aparte-space-4) * -0.5);
|
|
4439
|
+
margin-inline-start: 0;
|
|
4440
|
+
margin-block-start: calc(var(--aparte-space-4) * -0.5);
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
.aparte-tooltip[data-side='end'] .aparte-tooltip__arrow {
|
|
4444
|
+
inset-block-end: auto;
|
|
4445
|
+
inset-block-start: 50%;
|
|
4446
|
+
inset-inline-start: calc(var(--aparte-space-4) * -0.5);
|
|
4447
|
+
margin-inline-start: 0;
|
|
4448
|
+
margin-block-start: calc(var(--aparte-space-4) * -0.5);
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4077
4451
|
/* ── Forced colors ───────────────────────────────────────────────────────────────
|
|
4078
4452
|
The menu and the popover already carry a real border by default, so those two
|
|
4079
4453
|
boxes survive forced-colors mode once background and box-shadow are both
|
|
@@ -4116,10 +4490,16 @@ aparte-select {
|
|
|
4116
4490
|
align-items: center;
|
|
4117
4491
|
justify-content: space-between;
|
|
4118
4492
|
gap: var(--aparte-space-4);
|
|
4493
|
+
box-sizing: border-box;
|
|
4494
|
+
min-block-size: var(--aparte-field-size);
|
|
4119
4495
|
padding: var(--aparte-space-4) var(--aparte-space-6);
|
|
4496
|
+
/* Its own size: the trigger declared none and took the host page's, so every
|
|
4497
|
+
integrator saw a different select. The control step, like the field and the
|
|
4498
|
+
button. */
|
|
4499
|
+
font-size: var(--aparte-font-size-md);
|
|
4120
4500
|
background: var(--aparte-select-bg, var(--aparte-surface-1));
|
|
4121
|
-
border: var(--aparte-border-width) solid var(--aparte-select-border, var(--aparte-border));
|
|
4122
|
-
border-radius: var(--aparte-
|
|
4501
|
+
border: var(--aparte-border-width) solid var(--aparte-select-border, var(--aparte-border-control));
|
|
4502
|
+
border-radius: var(--aparte-radius-select);
|
|
4123
4503
|
cursor: pointer;
|
|
4124
4504
|
transition: border-color var(--aparte-duration-base), box-shadow var(--aparte-duration-base);
|
|
4125
4505
|
}
|
|
@@ -4128,15 +4508,16 @@ aparte-select {
|
|
|
4128
4508
|
border-color: var(--aparte-select-border-hover, var(--aparte-primary));
|
|
4129
4509
|
}
|
|
4130
4510
|
|
|
4131
|
-
.aparte-select-trigger:focus {
|
|
4132
|
-
outline:
|
|
4133
|
-
|
|
4134
|
-
box-shadow: var(--aparte-select-ring, var(--aparte-focus-ring));
|
|
4511
|
+
.aparte-select-trigger:focus-visible {
|
|
4512
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
4513
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
4135
4514
|
}
|
|
4136
4515
|
|
|
4137
4516
|
aparte-select[disabled] .aparte-select-trigger {
|
|
4138
|
-
opacity: var(--aparte-disabled-opacity);
|
|
4139
4517
|
cursor: not-allowed;
|
|
4518
|
+
background: var(--aparte-surface-2);
|
|
4519
|
+
color: var(--aparte-text-muted);
|
|
4520
|
+
border-color: var(--aparte-border);
|
|
4140
4521
|
}
|
|
4141
4522
|
|
|
4142
4523
|
/* The label is a grid with ONE cell: the visible text and, under it, a hidden stack of
|
|
@@ -4196,8 +4577,10 @@ aparte-select[open] .aparte-select-chevron {
|
|
|
4196
4577
|
position: fixed;
|
|
4197
4578
|
/* top / left / width are set dynamically by _updatePosition() */
|
|
4198
4579
|
background: var(--aparte-select-dropdown-bg, var(--aparte-surface-1));
|
|
4199
|
-
|
|
4200
|
-
|
|
4580
|
+
/* The panel is a region, so its edge is the region border; `--aparte-select-border`
|
|
4581
|
+
is the TRIGGER's edge, a control's, and defaults to the control border. */
|
|
4582
|
+
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
4583
|
+
border-radius: var(--aparte-radius-select);
|
|
4201
4584
|
box-shadow: var(--aparte-select-shadow);
|
|
4202
4585
|
z-index: var(--aparte-select-z, var(--aparte-z-dropdown));
|
|
4203
4586
|
max-height: var(--aparte-select-dropdown-max-height);
|
|
@@ -4221,8 +4604,11 @@ aparte-select[open] .aparte-select-chevron {
|
|
|
4221
4604
|
font-family: inherit;
|
|
4222
4605
|
}
|
|
4223
4606
|
|
|
4224
|
-
|
|
4225
|
-
|
|
4607
|
+
/* Inside, not outside: the field sits at the top of a scrolling panel, and an outset
|
|
4608
|
+
ring would be clipped by it. The one documented exception to the shared offset. */
|
|
4609
|
+
.aparte-select-search:focus-visible {
|
|
4610
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
4611
|
+
outline-offset: calc(-1 * var(--aparte-focus-outline-width));
|
|
4226
4612
|
}
|
|
4227
4613
|
|
|
4228
4614
|
/* Options container */
|
|
@@ -4359,6 +4745,7 @@ aparte-optgroup[collapsed] .aparte-optgroup-chevron {
|
|
|
4359
4745
|
}
|
|
4360
4746
|
|
|
4361
4747
|
.aparte-spinner-small {
|
|
4748
|
+
box-sizing: border-box;
|
|
4362
4749
|
width: var(--aparte-select-spinner-size);
|
|
4363
4750
|
height: var(--aparte-select-spinner-size);
|
|
4364
4751
|
border: 2px solid currentColor;
|
|
@@ -4366,6 +4753,12 @@ aparte-optgroup[collapsed] .aparte-optgroup-chevron {
|
|
|
4366
4753
|
border-radius: 50%;
|
|
4367
4754
|
/* `aparte-spin` is declared once, in base.css — see the shared-motion block there. */
|
|
4368
4755
|
animation: aparte-spin var(--aparte-duration-spin) linear infinite;
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4758
|
+
/* The element's own sweep in responsive.css already stops this one; the rule is here
|
|
4759
|
+
so the recipe stops on its own terms too, like every other looping recipe. */
|
|
4760
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4761
|
+
.aparte-spinner-small { animation: none; }
|
|
4369
4762
|
}/**
|
|
4370
4763
|
* AparteProgressSpinner — CSS
|
|
4371
4764
|
*
|
|
@@ -4391,16 +4784,23 @@ aparte-progress-spinner svg {
|
|
|
4391
4784
|
overflow: visible;
|
|
4392
4785
|
}
|
|
4393
4786
|
|
|
4787
|
+
/* The same stroke as the CSS ring (`.aparte-spinner`), in SCREEN pixels: without
|
|
4788
|
+
`vector-effect` a stroke is in viewBox units and scaled with the box — 2.5 of 24 was
|
|
4789
|
+
1.67px in a 16px spinner, antialiased everywhere and 50 % lighter than its sibling.
|
|
4790
|
+
The track is a visible reference in the control-edge colour: at 15 % of the ink a
|
|
4791
|
+
determinate 62 % was the percentage of a circle nobody could see. */
|
|
4394
4792
|
.aparte-spinner-track {
|
|
4395
4793
|
fill: none;
|
|
4396
|
-
stroke: var(--aparte-spinner-track,
|
|
4397
|
-
stroke-width: var(--aparte-spinner-
|
|
4794
|
+
stroke: var(--aparte-spinner-track, var(--aparte-border-control));
|
|
4795
|
+
stroke-width: var(--aparte-spinner-thickness);
|
|
4796
|
+
vector-effect: non-scaling-stroke;
|
|
4398
4797
|
}
|
|
4399
4798
|
|
|
4400
4799
|
.aparte-spinner-fill {
|
|
4401
4800
|
fill: none;
|
|
4402
4801
|
stroke: var(--aparte-spinner-color, currentColor);
|
|
4403
|
-
stroke-width: var(--aparte-spinner-
|
|
4802
|
+
stroke-width: var(--aparte-spinner-thickness);
|
|
4803
|
+
vector-effect: non-scaling-stroke;
|
|
4404
4804
|
stroke-linecap: round;
|
|
4405
4805
|
transition: stroke-dashoffset 0.25s var(--aparte-ease);
|
|
4406
4806
|
}
|
|
@@ -4435,7 +4835,10 @@ aparte-progress-spinner:not([value]) svg {
|
|
|
4435
4835
|
|
|
4436
4836
|
/* Both host shapes core recognises get the same layout: the vanilla
|
|
4437
4837
|
<aparte-chat> element AND a framework wrapper's `[data-aparte-chat]` root
|
|
4438
|
-
(React/Vue/Svelte
|
|
4838
|
+
(React/Vue/Svelte render `<div data-aparte-chat class="aparte-chat-container">`;
|
|
4839
|
+
Angular's host IS `<aparte-chat>` and its shell is the inner
|
|
4840
|
+
`.aparte-chat-container`, which carries no attribute here — the overlay rules
|
|
4841
|
+
below list that third shape for exactly that reason). `[data-aparte-chat]`
|
|
4439
4842
|
is the SAME selector core uses to resolve the host (see aparte-client /
|
|
4440
4843
|
aparte-chat-bubble), so one rule covers both — no wrapper-specific class. */
|
|
4441
4844
|
aparte-chat,
|
|
@@ -4448,6 +4851,10 @@ aparte-chat,
|
|
|
4448
4851
|
height: 100%;
|
|
4449
4852
|
width: 100%;
|
|
4450
4853
|
overflow: hidden;
|
|
4854
|
+
/* The height an author sets IS the box: with the bottom gap below outside it,
|
|
4855
|
+
`style="height: 24rem"` rendered 336px, and a split's seam hung 16px under both
|
|
4856
|
+
panels. */
|
|
4857
|
+
box-sizing: border-box;
|
|
4451
4858
|
/* The composer used to touch the bottom edge as soon as a conversation started: the
|
|
4452
4859
|
viewport gives 16px above the composer and there was nothing below it. An app
|
|
4453
4860
|
cannot fix this from outside without padding the container, which also shrinks the
|
|
@@ -4509,16 +4916,26 @@ aparte-chat[overlay-composer] > aparte-chat-viewport,
|
|
|
4509
4916
|
z-index: 0;
|
|
4510
4917
|
}
|
|
4511
4918
|
|
|
4512
|
-
/* The bottom stack paints over the absolutely positioned viewport. `:not()`
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4919
|
+
/* The bottom stack paints over the absolutely positioned viewport. The `:not()` names
|
|
4920
|
+
what is NOT the stack: the viewport (the scroll button lives inside it), and the
|
|
4921
|
+
scroll rail, which is a child of the shell too. `position: relative` here beat the
|
|
4922
|
+
rail's own `absolute`, so in overlay mode the floating rail dropped into the flow
|
|
4923
|
+
and pushed the composer down — the one child that floats by design has to be named. */
|
|
4924
|
+
aparte-chat[overlay-composer] > :not(aparte-chat-viewport, aparte-scroll-rail),
|
|
4925
|
+
[data-aparte-chat][overlay-composer] > :not(aparte-chat-viewport, aparte-scroll-rail),
|
|
4926
|
+
.aparte-chat-container[overlay-composer] > :not(aparte-chat-viewport, aparte-scroll-rail) {
|
|
4518
4927
|
position: relative;
|
|
4519
4928
|
z-index: var(--aparte-z-raised);
|
|
4520
4929
|
}
|
|
4521
4930
|
|
|
4931
|
+
/* A thing that floats has to be seen floating. The overlaid composer's shell shares the
|
|
4932
|
+
transcript's ground, so a z-index alone left it flat on the words it covers. */
|
|
4933
|
+
aparte-chat[overlay-composer] .aparte-composer-shell,
|
|
4934
|
+
[data-aparte-chat][overlay-composer] .aparte-composer-shell,
|
|
4935
|
+
.aparte-chat-container[overlay-composer] .aparte-composer-shell {
|
|
4936
|
+
box-shadow: var(--aparte-composer-overlay-shadow);
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4522
4939
|
/* center-empty: the shell centers the composer as a welcome state until the first
|
|
4523
4940
|
message, then slides to the normal layout. The component toggles [data-empty]
|
|
4524
4941
|
on itself by watching its viewport — only `flex-grow` animates, so the composer
|
|
@@ -4541,6 +4958,17 @@ aparte-chat[center-empty][data-empty] > aparte-chat-viewport {
|
|
|
4541
4958
|
height: auto;
|
|
4542
4959
|
}
|
|
4543
4960
|
|
|
4961
|
+
/* And no block room at all: at `auto` the empty viewport still stood 32px tall — the
|
|
4962
|
+
rows' wrapper keeps its block padding with no row in it — so `center` centred three
|
|
4963
|
+
items of which the first was invisible, and the welcome group landed 16px low,
|
|
4964
|
+
measured. The padding goes, not the box: capping the viewport itself left a 32px
|
|
4965
|
+
scroll surface inside a 0px box, and "an empty transcript must not overflow" is a
|
|
4966
|
+
browser test every example runs. Inline geometry is kept — the composer's inset is
|
|
4967
|
+
measured from this element. */
|
|
4968
|
+
aparte-chat[center-empty][data-empty] > aparte-chat-viewport .aparte-messages-wrapper {
|
|
4969
|
+
padding-block: 0;
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4544
4972
|
/* =========================================
|
|
4545
4973
|
aparte-chat-viewport
|
|
4546
4974
|
========================================= */
|
|
@@ -4607,16 +5035,16 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4607
5035
|
|
|
4608
5036
|
/* ─── Scroll-to-bottom button ─────────────────────────────────────────────── */
|
|
4609
5037
|
|
|
4610
|
-
/* The button wears `aparte-btn--lg aparte-btn--circle
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
is
|
|
5038
|
+
/* The button wears `aparte-btn--lg aparte-btn--circle`: `--lg` gives it the 20px glyph
|
|
5039
|
+
and the lg step of the scale (36px), `--circle` the round corner, and
|
|
5040
|
+
`--aparte-scroll-btn-size` (the same step, named for this button so a theme can move
|
|
5041
|
+
it alone) is fed to the recipe below. What stays here is only what is genuinely this
|
|
5042
|
+
button's — where it floats, and its own surface.
|
|
4614
5043
|
|
|
4615
5044
|
It used to redraw all three: its own width/height, its own `border-radius: 50%`, and
|
|
4616
5045
|
an icon-size override that existed only because the box was declared OUTSIDE the size
|
|
4617
|
-
scale, so the glyph stayed at the medium default — a 16px arrow in a 36px circle
|
|
4618
|
-
|
|
4619
|
-
the ratio fall out for free, and the override disappears with it.
|
|
5046
|
+
scale, so the glyph stayed at the medium default — a 16px arrow in a 36px circle.
|
|
5047
|
+
Naming the size on the recipe's own token makes the override disappear.
|
|
4620
5048
|
|
|
4621
5049
|
`--aparte-scroll-btn-size` stays the public knob, fed to the recipe. */
|
|
4622
5050
|
.aparte-scroll-btn {
|
|
@@ -4888,7 +5316,11 @@ aparte-chat-bubble {
|
|
|
4888
5316
|
box-sizing: border-box;
|
|
4889
5317
|
display: flex;
|
|
4890
5318
|
gap: var(--aparte-message-gap);
|
|
4891
|
-
|
|
5319
|
+
/* The two parts, not the composite `--aparte-message-padding` that used to join them
|
|
5320
|
+
on `:root`: a custom property is substituted where it is declared, so the composite
|
|
5321
|
+
froze both parts at the root and the narrow-container override of them in
|
|
5322
|
+
responsive.css never reached this row. */
|
|
5323
|
+
padding: var(--aparte-message-padding-block) var(--aparte-message-padding-inline);
|
|
4892
5324
|
width: 100%;
|
|
4893
5325
|
max-width: var(--aparte-message-max-width);
|
|
4894
5326
|
margin: 0 auto;
|
|
@@ -4976,6 +5408,11 @@ aparte-chat-bubble {
|
|
|
4976
5408
|
opacity: 0;
|
|
4977
5409
|
transition: opacity var(--aparte-duration-base) var(--aparte-ease);
|
|
4978
5410
|
height: var(--aparte-action-bar-btn-size);
|
|
5411
|
+
/* The INK meets the column, not the box: a glyph is centred in its 24px button, so
|
|
5412
|
+
its ink began half the slack — (24 − 12) / 2 — to the right of the paragraph above.
|
|
5413
|
+
Measured at +5 to +8px in 14 previews. The bar takes that slack back and the box
|
|
5414
|
+
overhangs into the message padding, where nothing else lives. */
|
|
5415
|
+
margin-inline-start: calc((var(--aparte-icon-size-sm) - var(--aparte-btn-size-sm)) / 2);
|
|
4979
5416
|
}
|
|
4980
5417
|
|
|
4981
5418
|
.aparte-message:hover .aparte-action-bar,
|
|
@@ -5040,8 +5477,6 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
5040
5477
|
inset-block-end: calc(-1 * (var(--aparte-message-padding-block) + var(--aparte-message-gap)));
|
|
5041
5478
|
margin: 0;
|
|
5042
5479
|
z-index: 1;
|
|
5043
|
-
/* Smaller than the last reply's 28px: this one lives in the padding. */
|
|
5044
|
-
--aparte-action-bar-btn-size: 24px;
|
|
5045
5480
|
}
|
|
5046
5481
|
}
|
|
5047
5482
|
|
|
@@ -5090,12 +5525,11 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
5090
5525
|
display: none;
|
|
5091
5526
|
}
|
|
5092
5527
|
|
|
5528
|
+
/* The arrows wear `--sm` and the recipe draws the box: this rule used to redraw
|
|
5529
|
+
width/height to a 20px token over it — two sources for one measure. */
|
|
5093
5530
|
.aparte-branch-prev,
|
|
5094
5531
|
.aparte-branch-next {
|
|
5095
5532
|
color: var(--aparte-branch-picker-btn-color);
|
|
5096
|
-
width: var(--aparte-branch-picker-btn-size);
|
|
5097
|
-
height: var(--aparte-branch-picker-btn-size);
|
|
5098
|
-
font-size: var(--aparte-branch-picker-btn-icon-size);
|
|
5099
5533
|
}
|
|
5100
5534
|
|
|
5101
5535
|
.aparte-branch-prev:hover:not(:disabled),
|
|
@@ -5104,9 +5538,11 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
5104
5538
|
background: var(--aparte-action-bar-btn-hover-bg);
|
|
5105
5539
|
}
|
|
5106
5540
|
|
|
5541
|
+
/* The button recipe draws the disabled state (a neutral ground, the muted ink); an
|
|
5542
|
+
opacity over it read the arrow at 1.74:1 — as absent, where the picker means "no
|
|
5543
|
+
further". `default` and not `not-allowed`: the end of a list is not a refusal. */
|
|
5107
5544
|
.aparte-branch-prev:disabled,
|
|
5108
5545
|
.aparte-branch-next:disabled {
|
|
5109
|
-
opacity: var(--aparte-disabled-opacity);
|
|
5110
5546
|
cursor: default;
|
|
5111
5547
|
}
|
|
5112
5548
|
|
|
@@ -5206,7 +5642,9 @@ aparte-chat-bubble[data-kind="compaction"] .aparte-message-content {
|
|
|
5206
5642
|
Unscoped so it greys the composer in EVERY host (vanilla shell + all wrappers),
|
|
5207
5643
|
not only a `[data-aparte-chat]` direct child. */
|
|
5208
5644
|
aparte-composer[data-model-gated] {
|
|
5209
|
-
|
|
5645
|
+
/* No group fade: each control draws its own disabled state (the editor's muted ink,
|
|
5646
|
+
the send button's neutral ground), so the text stays legible while it waits. */
|
|
5647
|
+
cursor: not-allowed;
|
|
5210
5648
|
}
|
|
5211
5649
|
aparte-composer[data-model-gated] aparte-composer-send {
|
|
5212
5650
|
pointer-events: none;
|
|
@@ -5362,7 +5800,7 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
5362
5800
|
|
|
5363
5801
|
/* Disabled state */
|
|
5364
5802
|
.aparte-editor[contenteditable="false"] {
|
|
5365
|
-
|
|
5803
|
+
color: var(--aparte-text-muted);
|
|
5366
5804
|
cursor: not-allowed;
|
|
5367
5805
|
}
|
|
5368
5806
|
|
|
@@ -5403,9 +5841,9 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
5403
5841
|
}
|
|
5404
5842
|
|
|
5405
5843
|
.aparte-send-button:disabled {
|
|
5406
|
-
opacity: var(--aparte-disabled-opacity);
|
|
5407
5844
|
cursor: not-allowed;
|
|
5408
|
-
background: var(--aparte-send-disabled-bg, var(--aparte-
|
|
5845
|
+
background: var(--aparte-send-disabled-bg, var(--aparte-surface-2));
|
|
5846
|
+
color: var(--aparte-text-muted);
|
|
5409
5847
|
}
|
|
5410
5848
|
|
|
5411
5849
|
/* =========================================
|
|
@@ -5499,11 +5937,10 @@ aparte-model-selector[hidden],
|
|
|
5499
5937
|
border-color: var(--aparte-primary);
|
|
5500
5938
|
}
|
|
5501
5939
|
|
|
5502
|
-
.aparte-provider-select:focus,
|
|
5503
|
-
.aparte-model-select:focus {
|
|
5504
|
-
outline:
|
|
5505
|
-
|
|
5506
|
-
box-shadow: var(--aparte-focus-ring);
|
|
5940
|
+
.aparte-provider-select:focus-visible,
|
|
5941
|
+
.aparte-model-select:focus-visible {
|
|
5942
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
5943
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
5507
5944
|
}
|
|
5508
5945
|
|
|
5509
5946
|
/* Dark mode overrides for selects */
|
|
@@ -5546,13 +5983,14 @@ aparte-composer {
|
|
|
5546
5983
|
it would then be the containing block of the `position: fixed` select dropdown
|
|
5547
5984
|
that exists to escape `overflow: hidden` (select.css, and the same warning in
|
|
5548
5985
|
split.css). So below 520px the transcript tightens to 8px and this stays at 16.
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5986
|
+
So the composer does not guess: the viewport MEASURES where its rows start — the
|
|
5987
|
+
transcript's padding plus the scrollbar gutter it reserves on both edges, at
|
|
5988
|
+
whatever step its container query has taken — and publishes it on the chat host as
|
|
5989
|
+
`--aparte-transcript-inset`. Padding by that puts the composer's box on the row's
|
|
5990
|
+
box at every width. Measured before: 10px apart at 768, the gutter's half at 1280,
|
|
5991
|
+
three left edges 12px apart on the reading-column demo. The fallback is for a
|
|
5992
|
+
composer with no viewport beside it. */
|
|
5993
|
+
padding-inline: var(--aparte-transcript-inset, var(--aparte-viewport-padding));
|
|
5556
5994
|
}
|
|
5557
5995
|
|
|
5558
5996
|
aparte-composer-input {
|
|
@@ -5565,7 +6003,7 @@ aparte-composer-input {
|
|
|
5565
6003
|
aparte-composer-input .aparte-ci-editor {
|
|
5566
6004
|
display: block;
|
|
5567
6005
|
width: 100%;
|
|
5568
|
-
min-height: var(--aparte-composer-control-size
|
|
6006
|
+
min-height: var(--aparte-composer-control-size);
|
|
5569
6007
|
padding: var(--aparte-input-padding-y) var(--aparte-input-padding-x);
|
|
5570
6008
|
font-size: var(--aparte-input-font-size);
|
|
5571
6009
|
line-height: var(--aparte-input-line-height);
|
|
@@ -5596,7 +6034,7 @@ aparte-composer-input .aparte-ci-editor:empty::before {
|
|
|
5596
6034
|
}
|
|
5597
6035
|
|
|
5598
6036
|
aparte-composer-input .aparte-ci-editor[aria-disabled="true"] {
|
|
5599
|
-
|
|
6037
|
+
color: var(--aparte-text-muted);
|
|
5600
6038
|
cursor: not-allowed;
|
|
5601
6039
|
}
|
|
5602
6040
|
|
|
@@ -5688,7 +6126,7 @@ aparte-composer.aparte-is-dragover:not(:has(.aparte-composer-shell)) {
|
|
|
5688
6126
|
|
|
5689
6127
|
/* Inner row — flex container for input + buttons */
|
|
5690
6128
|
/* Action buttons sized to the input's single-line height — they derive from
|
|
5691
|
-
--aparte-composer-control-size (default
|
|
6129
|
+
--aparte-composer-control-size (default: the lg step, 36px; the touch target under a coarse pointer), so a theme resizes the whole
|
|
5692
6130
|
composer control set with ONE variable and alignment holds. Combined with
|
|
5693
6131
|
the row's `align-items: flex-end` this makes the buttons read as vertically
|
|
5694
6132
|
centered against a single line of text, and keeps them anchored to the
|
|
@@ -5720,7 +6158,11 @@ aparte-composer.aparte-is-dragover:not(:has(.aparte-composer-shell)) {
|
|
|
5720
6158
|
align-items: flex-end;
|
|
5721
6159
|
gap: var(--aparte-space-4);
|
|
5722
6160
|
padding: var(--aparte-space-4) var(--aparte-space-6);
|
|
5723
|
-
|
|
6161
|
+
/* The lg step at rest — 36px, what `--aparte-send-btn-size` says — and the touch
|
|
6162
|
+
target under a coarse pointer (responsive.css). It was 44 everywhere, which made
|
|
6163
|
+
the send button the one control the coarse-pointer block could not enlarge because
|
|
6164
|
+
it already was, and a 62px bar around 15px of text on a desktop. */
|
|
6165
|
+
--aparte-composer-row-control-size: var(--aparte-composer-control-size);
|
|
5724
6166
|
--aparte-btn-size: var(--aparte-composer-row-control-size);
|
|
5725
6167
|
}
|
|
5726
6168
|
|
|
@@ -5838,6 +6280,10 @@ aparte-composer-cancel .aparte-cc-button:hover {
|
|
|
5838
6280
|
`.aparte-accordion__header` / `__item` / `__panel` and adds these two lines: a
|
|
5839
6281
|
quieter face than a real accordion header, and the padding the left rail needs. */
|
|
5840
6282
|
.aparte-thinking-header {
|
|
6283
|
+
/* The chevron sits beside the label: the accordion recipe puts it at the row's far
|
|
6284
|
+
end, which is what an accordion is, but this is a one-line disclosure, and "Reasoning"
|
|
6285
|
+
and its chevron were 692px apart at 1280 (T18). */
|
|
6286
|
+
justify-content: flex-start;
|
|
5841
6287
|
padding: var(--aparte-space-1) 0 var(--aparte-space-1) var(--aparte-space-5);
|
|
5842
6288
|
font-size: var(--aparte-font-size-sm);
|
|
5843
6289
|
color: var(--aparte-thinking-text);
|
|
@@ -5869,7 +6315,7 @@ aparte-composer-cancel .aparte-cc-button:hover {
|
|
|
5869
6315
|
margin-inline-end: var(--aparte-space-4);
|
|
5870
6316
|
/* Logical property */
|
|
5871
6317
|
text-transform: uppercase;
|
|
5872
|
-
opacity:
|
|
6318
|
+
/* The header's ink is already the muted one; an `opacity: .7` on top muted it twice. */
|
|
5873
6319
|
}
|
|
5874
6320
|
|
|
5875
6321
|
|
|
@@ -5928,11 +6374,14 @@ aparte-composer-cancel .aparte-cc-button:hover {
|
|
|
5928
6374
|
flex: none;
|
|
5929
6375
|
width: var(--aparte-space-8);
|
|
5930
6376
|
height: var(--aparte-space-8);
|
|
5931
|
-
opacity: 0.5
|
|
5932
|
-
|
|
6377
|
+
/* Quiet is a colour, not an opacity: at 0.5 the chevron measured 3.00:1 — on the
|
|
6378
|
+
WCAG floor exactly — and it is the control that reveals a `delete_file`'s
|
|
6379
|
+
arguments. The muted ink at rest, the full ink under the pointer. */
|
|
6380
|
+
color: var(--aparte-text-muted);
|
|
6381
|
+
transition: transform var(--aparte-duration-base), color var(--aparte-duration-base);
|
|
5933
6382
|
}
|
|
5934
6383
|
.aparte-tool-toggle svg { width: 100%; height: 100%; display: block; }
|
|
5935
|
-
.aparte-tool-summary:hover .aparte-tool-toggle {
|
|
6384
|
+
.aparte-tool-summary:hover .aparte-tool-toggle { color: var(--aparte-text); }
|
|
5936
6385
|
details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180deg); }
|
|
5937
6386
|
/* A rule down the leading edge, so the disclosure reads as belonging to the
|
|
5938
6387
|
pill above it rather than continuing the message text below. Without it
|
|
@@ -5947,7 +6396,7 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5947
6396
|
}
|
|
5948
6397
|
.aparte-tool-part-label {
|
|
5949
6398
|
display: block; font-size: var(--aparte-font-size-xs); text-transform: uppercase;
|
|
5950
|
-
letter-spacing: 0.04em;
|
|
6399
|
+
letter-spacing: 0.04em; color: var(--aparte-text-muted); margin-bottom: var(--aparte-space-1);
|
|
5951
6400
|
}
|
|
5952
6401
|
/* A tool's input and result WRAP. They are a message, a path, a JSON object — not
|
|
5953
6402
|
code to read line by line — and a `<pre>` keeps `white-space: pre` by default, so
|
|
@@ -5969,6 +6418,7 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5969
6418
|
green, which made a finished step shout louder than the reply it belongs to. */
|
|
5970
6419
|
.aparte-tool-label {
|
|
5971
6420
|
display: inline-flex;
|
|
6421
|
+
vertical-align: middle;
|
|
5972
6422
|
align-items: center;
|
|
5973
6423
|
gap: var(--aparte-space-3);
|
|
5974
6424
|
min-width: 0;
|
|
@@ -5985,7 +6435,7 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5985
6435
|
place-items: center;
|
|
5986
6436
|
width: 1em; height: 1em;
|
|
5987
6437
|
flex: none;
|
|
5988
|
-
|
|
6438
|
+
color: var(--aparte-text-muted);
|
|
5989
6439
|
}
|
|
5990
6440
|
.aparte-tool-icon svg { width: 1em; height: 1em; display: block; }
|
|
5991
6441
|
.aparte-tool-name { font-family: var(--aparte-code-font-family); }
|
|
@@ -5993,9 +6443,12 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5993
6443
|
Pushed to the far end by DOM order plus an auto inline-start margin, which is
|
|
5994
6444
|
the placement rule this repo already settled on: no left/right in a name, so
|
|
5995
6445
|
a right-to-left locale mirrors it for free. */
|
|
6446
|
+
/* Beside the tool's name, not at the row's far end: with `margin-inline-start: auto` the
|
|
6447
|
+
state word drifted 63px from the name at 375 and 596px at 1280. The state belongs to
|
|
6448
|
+
the label (T18). */
|
|
5996
6449
|
.aparte-tool-state {
|
|
5997
|
-
display: inline-flex;
|
|
5998
|
-
|
|
6450
|
+
display: inline-flex;
|
|
6451
|
+
vertical-align: middle; align-items: center; gap: var(--aparte-space-2);
|
|
5999
6452
|
font-size: var(--aparte-font-size-sm); font-weight: var(--aparte-font-weight-medium);
|
|
6000
6453
|
color: var(--aparte-text-muted);
|
|
6001
6454
|
user-select: none;
|
|
@@ -6031,44 +6484,22 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
6031
6484
|
}
|
|
6032
6485
|
|
|
6033
6486
|
/* Pipeline waiting */
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
height: var(--aparte-error-icon-size);
|
|
6045
|
-
}
|
|
6046
|
-
|
|
6047
|
-
.aparte-error-content {
|
|
6048
|
-
flex: 1;
|
|
6049
|
-
min-width: 0;
|
|
6050
|
-
}
|
|
6051
|
-
|
|
6052
|
-
.aparte-error-title {
|
|
6053
|
-
font-weight: var(--aparte-font-weight-semibold);
|
|
6054
|
-
font-size: var(--aparte-font-size-lg);
|
|
6055
|
-
margin-bottom: var(--aparte-space-2);
|
|
6056
|
-
color: var(--aparte-error-title);
|
|
6057
|
-
}
|
|
6058
|
-
|
|
6059
|
-
.aparte-error-message {
|
|
6060
|
-
font-size: var(--aparte-font-size-lg);
|
|
6061
|
-
line-height: var(--aparte-line-height-normal);
|
|
6062
|
-
}
|
|
6063
|
-
|
|
6064
|
-
.aparte-error-details {
|
|
6065
|
-
margin-top: var(--aparte-space-4);
|
|
6066
|
-
padding-top: var(--aparte-space-4);
|
|
6067
|
-
border-top: var(--aparte-border-width) solid var(--aparte-error-border);
|
|
6487
|
+
/* The error segment WEARS the alert recipe — frame and parts (`aparte-alert__icon`,
|
|
6488
|
+
`__body`, `__title`, `__message` come from display/alert.css). It used to put the
|
|
6489
|
+
recipe's class on its root and redraw every part under classes of its own, with a 20px
|
|
6490
|
+
literal for the icon among derived tokens and a title colour nothing else used. What is
|
|
6491
|
+
genuinely the segment's own is the one part the alert has no word for: the details
|
|
6492
|
+
block — a transport's stack, a status line — set as code under the message. */
|
|
6493
|
+
.aparte-segment-error__details {
|
|
6494
|
+
margin: var(--aparte-space-4) 0 0;
|
|
6495
|
+
padding-block-start: var(--aparte-space-4);
|
|
6496
|
+
border-block-start: var(--aparte-border-width) solid var(--aparte-error-border);
|
|
6068
6497
|
font-family: var(--aparte-code-font-family);
|
|
6069
6498
|
font-size: var(--aparte-font-size-sm);
|
|
6070
|
-
|
|
6499
|
+
line-height: var(--aparte-line-height-normal);
|
|
6500
|
+
color: var(--aparte-text-secondary);
|
|
6071
6501
|
white-space: pre-wrap;
|
|
6502
|
+
overflow-wrap: anywhere;
|
|
6072
6503
|
}
|
|
6073
6504
|
|
|
6074
6505
|
/* ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -6182,7 +6613,7 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
6182
6613
|
a consumer's screenshot, then here. The padding gives the ring that much room and
|
|
6183
6614
|
the negative margin gives it back to the layout, so nothing moves; `scroll-padding`
|
|
6184
6615
|
keeps a keyboard-focused option's ring inside the viewport when the list scrolls. */
|
|
6185
|
-
--aparte-elic-ring-room: calc(var(--aparte-focus-outline-width) + var(--aparte-
|
|
6616
|
+
--aparte-elic-ring-room: calc(var(--aparte-focus-outline-width) + var(--aparte-focus-outline-offset));
|
|
6186
6617
|
padding: var(--aparte-elic-ring-room);
|
|
6187
6618
|
margin: calc(-1 * var(--aparte-elic-ring-room));
|
|
6188
6619
|
scroll-padding: var(--aparte-elic-ring-room);
|
|
@@ -6226,7 +6657,10 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
6226
6657
|
.aparte-elic-options {
|
|
6227
6658
|
display: flex;
|
|
6228
6659
|
flex-direction: column;
|
|
6229
|
-
|
|
6660
|
+
/* At least the focus ring's outset: at 2px the next row painted over the bottom of
|
|
6661
|
+
the focused row's ring and ~8px of it survived. Any list of options that wears an
|
|
6662
|
+
outset ring needs a gap at least as wide as the offset. */
|
|
6663
|
+
gap: var(--aparte-space-3);
|
|
6230
6664
|
}
|
|
6231
6665
|
|
|
6232
6666
|
/* The row is `.aparte-field-choice`; the panel only tightens its padding, because a
|
|
@@ -6300,22 +6734,19 @@ textarea.aparte-elic-text {
|
|
|
6300
6734
|
min-height: var(--aparte-elic-textarea-min-height);
|
|
6301
6735
|
}
|
|
6302
6736
|
|
|
6303
|
-
.aparte-elic-other-input:focus,
|
|
6304
|
-
.aparte-elic-text:focus {
|
|
6305
|
-
border-color: var(--aparte-primary);
|
|
6306
|
-
}
|
|
6307
|
-
|
|
6308
6737
|
.aparte-elic-option--recommended {
|
|
6309
6738
|
border-color: var(--aparte-primary);
|
|
6310
6739
|
background: color-mix(in srgb, var(--aparte-primary) 6%, transparent);
|
|
6311
6740
|
}
|
|
6312
6741
|
/* One ring at a time. The recommended option is the first to receive focus, and its
|
|
6313
6742
|
tinted border under the recipe's focus ring read as a double outline (UI audit
|
|
6314
|
-
2026-08-28, §8.3 #9). While it holds focus the ring is the
|
|
6315
|
-
|
|
6743
|
+
2026-08-28, §8.3 #9). While it holds focus the ring is the one edge, so the border
|
|
6744
|
+
steps aside — and ONLY the border. This rule used to clear the background too, which
|
|
6745
|
+
made the recommended row the only one in the list with no ground at all: sampled on
|
|
6746
|
+
the preview it rendered the page byte for byte while its sisters did not. The
|
|
6747
|
+
recommended state draws in addition to the resting state, never instead of it. */
|
|
6316
6748
|
.aparte-elic-option--recommended:focus-within {
|
|
6317
|
-
|
|
6318
|
-
border-color: var(--aparte-border-focus);
|
|
6749
|
+
border-color: transparent;
|
|
6319
6750
|
}
|
|
6320
6751
|
|
|
6321
6752
|
/* The whole-request escape, in the corner and out of the way of anything that
|
|
@@ -6424,7 +6855,7 @@ textarea.aparte-elic-text {
|
|
|
6424
6855
|
|
|
6425
6856
|
.aparte-approval-args:focus-visible {
|
|
6426
6857
|
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
6427
|
-
outline-offset:
|
|
6858
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
6428
6859
|
}
|
|
6429
6860
|
|
|
6430
6861
|
/* The refusal's reason, in the user's words. Quiet by default and one line tall: it is
|
|
@@ -6444,9 +6875,9 @@ textarea.aparte-elic-text {
|
|
|
6444
6875
|
min-height: 1.6em;
|
|
6445
6876
|
}
|
|
6446
6877
|
|
|
6447
|
-
.aparte-approval-instruction:focus {
|
|
6448
|
-
outline:
|
|
6449
|
-
|
|
6878
|
+
.aparte-approval-instruction:focus-visible {
|
|
6879
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
6880
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
6450
6881
|
}
|
|
6451
6882
|
|
|
6452
6883
|
.aparte-approval-instruction::placeholder {
|
|
@@ -6595,8 +7026,8 @@ textarea.aparte-elic-text {
|
|
|
6595
7026
|
/* Inside the edge: the list clips horizontally (`overflow-x: hidden` in base.css), and
|
|
6596
7027
|
a ring drawn outside the row was cut on both sides. */
|
|
6597
7028
|
.aparte-conv-item__select:focus-visible {
|
|
6598
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
6599
|
-
outline-offset:
|
|
7029
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
7030
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
6600
7031
|
}
|
|
6601
7032
|
|
|
6602
7033
|
.aparte-conv-item__title {
|
|
@@ -6617,15 +7048,22 @@ textarea.aparte-elic-text {
|
|
|
6617
7048
|
font-weight: var(--aparte-conv-item-font-weight-active);
|
|
6618
7049
|
}
|
|
6619
7050
|
|
|
7051
|
+
/* The selection mark: a bar the width of `--aparte-mark-bar` down the row's start
|
|
7052
|
+
edge. Painted as the first pixels of a pseudo the SIZE of the row that inherits its
|
|
7053
|
+
radius, so the bar follows the curve — drawn as a 2px box of its own it stood square
|
|
7054
|
+
in a 9px corner and a sliver of the page's ground showed between the two. Not
|
|
7055
|
+
`overflow: hidden` on the row: that would clip the title button's focus ring. */
|
|
6620
7056
|
.aparte-conv-item--active::before {
|
|
6621
7057
|
content: "";
|
|
6622
7058
|
position: absolute;
|
|
6623
|
-
inset
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
background: var(--aparte-primary-ink);
|
|
7059
|
+
inset: 0;
|
|
7060
|
+
border-radius: inherit;
|
|
7061
|
+
background: linear-gradient(to right, var(--aparte-primary-ink) var(--aparte-mark-bar), transparent 0);
|
|
6627
7062
|
pointer-events: none;
|
|
6628
7063
|
}
|
|
7064
|
+
.aparte-conv-item--active:dir(rtl)::before {
|
|
7065
|
+
background: linear-gradient(to left, var(--aparte-primary-ink) var(--aparte-mark-bar), transparent 0);
|
|
7066
|
+
}
|
|
6629
7067
|
|
|
6630
7068
|
.aparte-conv-item--archived .aparte-conv-item__title {
|
|
6631
7069
|
font-style: italic;
|
|
@@ -6639,8 +7077,8 @@ textarea.aparte-elic-text {
|
|
|
6639
7077
|
.aparte-conv-item__more {
|
|
6640
7078
|
flex-shrink: 0;
|
|
6641
7079
|
opacity: 0;
|
|
6642
|
-
|
|
6643
|
-
|
|
7080
|
+
/* Fed to the recipe, not drawn here: the token names the size, the button draws it. */
|
|
7081
|
+
--aparte-btn-size: var(--aparte-conv-action-btn-size);
|
|
6644
7082
|
color: var(--aparte-text-muted);
|
|
6645
7083
|
}
|
|
6646
7084
|
|
|
@@ -6718,6 +7156,11 @@ aparte-suggestions[data-empty] {
|
|
|
6718
7156
|
flex-wrap: wrap;
|
|
6719
7157
|
gap: var(--aparte-space-2);
|
|
6720
7158
|
padding-block: var(--aparte-space-2);
|
|
7159
|
+
/* The composer's column: past its 800px cap the chips no longer followed the composer,
|
|
7160
|
+
188px of offset on a wide host. Same measure, same centring. */
|
|
7161
|
+
box-sizing: border-box;
|
|
7162
|
+
max-inline-size: var(--aparte-message-max-width);
|
|
7163
|
+
margin-inline: auto;
|
|
6721
7164
|
}
|
|
6722
7165
|
|
|
6723
7166
|
/* A starter's label is a short phrase; it must not become a two-line chip. */
|
|
@@ -6753,6 +7196,9 @@ aparte-context[data-empty] {
|
|
|
6753
7196
|
.aparte-context .aparte-progress {
|
|
6754
7197
|
flex: 1;
|
|
6755
7198
|
min-width: 4rem;
|
|
7199
|
+
/* A gauge has a measure: unbounded, the bar stretched to 1046×4px across a toolbar,
|
|
7200
|
+
88 % of its width for a line that says one number. */
|
|
7201
|
+
max-inline-size: 16rem;
|
|
6756
7202
|
}
|
|
6757
7203
|
|
|
6758
7204
|
.aparte-context__text {
|
|
@@ -6782,10 +7228,13 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6782
7228
|
transform: rotate(-90deg);
|
|
6783
7229
|
}
|
|
6784
7230
|
|
|
7231
|
+
/* As heavy as the bar variant, in screen pixels: the same gauge was drawn at two
|
|
7232
|
+
weights — a 4px bar and a 2.44px antialiased ring. */
|
|
6785
7233
|
.aparte-context__track,
|
|
6786
7234
|
.aparte-context__value {
|
|
6787
7235
|
fill: none;
|
|
6788
|
-
stroke-width: var(--aparte-
|
|
7236
|
+
stroke-width: var(--aparte-progress-height);
|
|
7237
|
+
vector-effect: non-scaling-stroke;
|
|
6789
7238
|
}
|
|
6790
7239
|
|
|
6791
7240
|
.aparte-context__track {
|
|
@@ -6820,12 +7269,30 @@ aparte-context[data-level="danger"] .aparte-context__value {
|
|
|
6820
7269
|
* chat host. `:has()` gives the host its containing block only while a rail is inside
|
|
6821
7270
|
* it — a bare `position: relative` on every chat would have moved any absolutely
|
|
6822
7271
|
* positioned child a consumer already placed there.
|
|
7272
|
+
*
|
|
7273
|
+
* The three shell shapes shell.css already lays out, because an absolute element lands
|
|
7274
|
+
* in the nearest POSITIONED ancestor and a shape missing from this list is a rail that
|
|
7275
|
+
* escaped to the page: the vanilla `<aparte-chat>`, the `[data-aparte-chat]` div
|
|
7276
|
+
* React/Vue/Svelte render, and Angular's inner `.aparte-chat-container` (its host IS
|
|
7277
|
+
* `<aparte-chat>`, but the rail is a child of that inner div). The first two shapes are
|
|
7278
|
+
* both true of the wrapper div, which carries the class and the attribute — listing all
|
|
7279
|
+
* three costs one selector and closes the Angular hole.
|
|
6823
7280
|
*/
|
|
6824
7281
|
aparte-chat:has(> aparte-scroll-rail),
|
|
6825
|
-
[data-aparte-chat]:has(> aparte-scroll-rail)
|
|
7282
|
+
[data-aparte-chat]:has(> aparte-scroll-rail),
|
|
7283
|
+
.aparte-chat-container:has(> aparte-scroll-rail) {
|
|
6826
7284
|
position: relative;
|
|
6827
7285
|
}
|
|
6828
7286
|
|
|
7287
|
+
/* The block padding is the room the END zones need, and it is not decoration.
|
|
7288
|
+
The rail clips (`overflow: hidden`, which cuts at the PADDING box) and the list has
|
|
7289
|
+
none of its own, so with no padding here the first tick's top edge sits exactly on the
|
|
7290
|
+
clip line. A zone grows symmetrically — half of `hit − thickness` above and below the
|
|
7291
|
+
line — so the first tick would lose its upper half and the last tick its lower half,
|
|
7292
|
+
to paint AND to hit-testing: 13px of the 24px, on the two ticks a reader aims at most
|
|
7293
|
+
("jump to the first message", "jump to the latest"). Padding by exactly that half puts
|
|
7294
|
+
the clip line outside every zone instead of through the two end ones. `box-sizing`
|
|
7295
|
+
keeps `max-height` measuring the same box it measured before. */
|
|
6829
7296
|
aparte-scroll-rail {
|
|
6830
7297
|
position: absolute;
|
|
6831
7298
|
inset-inline-end: var(--aparte-space-2);
|
|
@@ -6833,7 +7300,9 @@ aparte-scroll-rail {
|
|
|
6833
7300
|
transform: translateY(-50%);
|
|
6834
7301
|
z-index: var(--aparte-z-floating);
|
|
6835
7302
|
display: block;
|
|
7303
|
+
box-sizing: border-box;
|
|
6836
7304
|
width: var(--aparte-scroll-rail-width);
|
|
7305
|
+
padding-block: calc((var(--aparte-scroll-rail-hit-size) - var(--aparte-scroll-rail-tick-thickness)) / 2);
|
|
6837
7306
|
max-height: calc(100% - 2 * var(--aparte-space-8));
|
|
6838
7307
|
overflow: hidden;
|
|
6839
7308
|
}
|
|
@@ -6853,7 +7322,7 @@ aparte-scroll-rail[data-empty] {
|
|
|
6853
7322
|
gap: var(--aparte-scroll-rail-gap);
|
|
6854
7323
|
}
|
|
6855
7324
|
|
|
6856
|
-
/* A tick is a real button, drawn as a line. The hit area is the
|
|
7325
|
+
/* A tick is a real button, drawn as a line. The hit area is the zone around it, drawn
|
|
6857
7326
|
by the pseudo-element so the line itself can stay two pixels. */
|
|
6858
7327
|
.aparte-scroll-rail__tick {
|
|
6859
7328
|
appearance: none;
|
|
@@ -6865,17 +7334,32 @@ aparte-scroll-rail[data-empty] {
|
|
|
6865
7334
|
padding: 0;
|
|
6866
7335
|
border: 0;
|
|
6867
7336
|
border-radius: var(--aparte-radius-full);
|
|
6868
|
-
|
|
7337
|
+
/* A tick is a thing you can press; at rest in the region colour it sat at 1.15:1, a
|
|
7338
|
+
14×2px dash under the perception threshold. */
|
|
7339
|
+
background: var(--aparte-border-control);
|
|
6869
7340
|
cursor: pointer;
|
|
6870
7341
|
transition:
|
|
6871
7342
|
width var(--aparte-duration-fast) var(--aparte-ease),
|
|
6872
7343
|
background var(--aparte-duration-fast) var(--aparte-ease);
|
|
6873
7344
|
}
|
|
6874
7345
|
|
|
7346
|
+
/* The pressable zone: 24px square around a 14×2 line (WCAG 2.5.8). It used to be 22×10
|
|
7347
|
+
— half the 8px gap in the block axis, one spacing step in the inline one — and no
|
|
7348
|
+
spacing exemption covered it, because at a 10px pitch a neighbour's 24px circle
|
|
7349
|
+
overlaps this one's.
|
|
7350
|
+
Block: grow symmetrically to the hit size. The gap is `hit − thickness`, so the pitch
|
|
7351
|
+
is exactly the hit size and two zones meet without overlapping — and the rail pads its
|
|
7352
|
+
block axis by the same half-growth, so the clip does not eat the first and last zone.
|
|
7353
|
+
Inline: grow INWARD only. The rail clips (`overflow: hidden`) and ticks are
|
|
7354
|
+
end-aligned, so a centred zone would hang past the end edge and come back cut to
|
|
7355
|
+
19px; anchored to the tick's end edge it stays whole, and the rail's width carries
|
|
7356
|
+
the hit size as its floor so the column has room for it. */
|
|
6875
7357
|
.aparte-scroll-rail__tick::before {
|
|
6876
7358
|
content: "";
|
|
6877
7359
|
position: absolute;
|
|
6878
|
-
inset: calc(
|
|
7360
|
+
inset-block: calc((var(--aparte-scroll-rail-tick-thickness) - var(--aparte-scroll-rail-hit-size)) / 2);
|
|
7361
|
+
inset-inline-end: 0;
|
|
7362
|
+
inset-inline-start: calc(var(--aparte-scroll-rail-tick-size) - var(--aparte-scroll-rail-hit-size));
|
|
6879
7363
|
}
|
|
6880
7364
|
|
|
6881
7365
|
.aparte-scroll-rail__tick:hover {
|
|
@@ -6888,7 +7372,7 @@ aparte-scroll-rail[data-empty] {
|
|
|
6888
7372
|
}
|
|
6889
7373
|
|
|
6890
7374
|
.aparte-scroll-rail__tick:focus-visible {
|
|
6891
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-
|
|
7375
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
6892
7376
|
outline-offset: var(--aparte-focus-outline-offset);
|
|
6893
7377
|
}
|
|
6894
7378
|
|
|
@@ -6971,7 +7455,10 @@ aparte-sidebar,
|
|
|
6971
7455
|
display: flex;
|
|
6972
7456
|
align-items: center;
|
|
6973
7457
|
gap: var(--aparte-space-3);
|
|
6974
|
-
|
|
7458
|
+
/* Inline, every region reads `--aparte-sidebar-inset`: the header and footer padded
|
|
7459
|
+
16 while the search and the body padded 12, and the column's content stood on two
|
|
7460
|
+
vertical axes 4px apart — measured as four left edges on the app-shell demo. */
|
|
7461
|
+
padding: var(--aparte-space-4) var(--aparte-sidebar-inset) var(--aparte-space-2);
|
|
6975
7462
|
}
|
|
6976
7463
|
|
|
6977
7464
|
.aparte-sidebar__brand {
|
|
@@ -6984,8 +7471,8 @@ aparte-sidebar,
|
|
|
6984
7471
|
}
|
|
6985
7472
|
|
|
6986
7473
|
.aparte-sidebar__search {
|
|
6987
|
-
margin: var(--aparte-space-2) var(--aparte-
|
|
6988
|
-
min-width: calc(var(--aparte-sidebar-width) - 2 * var(--aparte-
|
|
7474
|
+
margin: var(--aparte-space-2) var(--aparte-sidebar-inset);
|
|
7475
|
+
min-width: calc(var(--aparte-sidebar-width) - 2 * var(--aparte-sidebar-inset));
|
|
6989
7476
|
}
|
|
6990
7477
|
|
|
6991
7478
|
/* The body is the one region that scrolls; the list inside keeps its own gap. */
|
|
@@ -6994,7 +7481,7 @@ aparte-sidebar,
|
|
|
6994
7481
|
min-height: 0;
|
|
6995
7482
|
overflow-y: auto;
|
|
6996
7483
|
overflow-x: hidden;
|
|
6997
|
-
padding: var(--aparte-space-2) var(--aparte-
|
|
7484
|
+
padding: var(--aparte-space-2) var(--aparte-sidebar-inset);
|
|
6998
7485
|
scrollbar-width: thin;
|
|
6999
7486
|
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
7000
7487
|
}
|
|
@@ -7003,7 +7490,7 @@ aparte-sidebar,
|
|
|
7003
7490
|
display: flex;
|
|
7004
7491
|
align-items: center;
|
|
7005
7492
|
gap: var(--aparte-space-3);
|
|
7006
|
-
padding: var(--aparte-space-3) var(--aparte-
|
|
7493
|
+
padding: var(--aparte-space-3) var(--aparte-sidebar-inset);
|
|
7007
7494
|
border-block-start: var(--aparte-border-width) solid var(--aparte-border);
|
|
7008
7495
|
font-size: var(--aparte-font-size-sm);
|
|
7009
7496
|
}
|
|
@@ -7048,7 +7535,7 @@ aparte-sidebar,
|
|
|
7048
7535
|
* under 48rem, where the sidebar is a drawer; above it the column is simply there.
|
|
7049
7536
|
*
|
|
7050
7537
|
* <header class="aparte-app-header">
|
|
7051
|
-
* <button class="aparte-btn aparte-btn--icon aparte-app-header__toggle" type="button" aria-label="Toggle the sidebar" data-aparte-sidebar-toggle
|
|
7538
|
+
* <button class="aparte-btn aparte-btn--icon aparte-app-header__toggle" type="button" aria-label="Toggle the sidebar" data-aparte-sidebar-toggle><aparte-icon name="menu"></aparte-icon></button>
|
|
7052
7539
|
* <span class="aparte-app-header__title">Deploy checklist</span>
|
|
7053
7540
|
* <div class="aparte-app-header__actions">
|
|
7054
7541
|
* <span class="aparte-tag">gpt-4.1</span>
|
|
@@ -7109,15 +7596,38 @@ aparte-sidebar,
|
|
|
7109
7596
|
* `height: 100dvh` for a page — and everything inside follows. Under 48rem the sidebar
|
|
7110
7597
|
* leaves the grid to become a drawer, and the header's toggle appears.
|
|
7111
7598
|
*
|
|
7112
|
-
* <div class="aparte-app-shell" style="height:
|
|
7599
|
+
* <div class="aparte-app-shell" style="height: 24rem">
|
|
7113
7600
|
* <aside class="aparte-sidebar">
|
|
7114
|
-
* <div class="aparte-sidebar__header"
|
|
7115
|
-
*
|
|
7601
|
+
* <div class="aparte-sidebar__header">
|
|
7602
|
+
* <span class="aparte-sidebar__brand">aparté</span>
|
|
7603
|
+
* <button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="New chat"><aparte-icon name="edit"></aparte-icon></button>
|
|
7604
|
+
* </div>
|
|
7605
|
+
* <div class="aparte-sidebar__search aparte-field-group">
|
|
7606
|
+
* <input class="aparte-field aparte-field--sm" type="search" placeholder="Search">
|
|
7607
|
+
* </div>
|
|
7608
|
+
* <div class="aparte-sidebar__body">
|
|
7609
|
+
* <div class="aparte-conv-group" role="group" aria-label="Today">
|
|
7610
|
+
* <div class="aparte-menu__label aparte-conv-group__label" aria-hidden="true">Today</div>
|
|
7611
|
+
* <div class="aparte-menu__item aparte-conv-item aparte-conv-item--active"><button class="aparte-conv-item__select" type="button"><span class="aparte-conv-item__title">Deploy checklist</span></button></div>
|
|
7612
|
+
* <div class="aparte-menu__item aparte-conv-item"><button class="aparte-conv-item__select" type="button"><span class="aparte-conv-item__title">Rename the staging bucket</span></button></div>
|
|
7613
|
+
* </div>
|
|
7614
|
+
* <div class="aparte-conv-group" role="group" aria-label="Yesterday">
|
|
7615
|
+
* <div class="aparte-menu__label aparte-conv-group__label" aria-hidden="true">Yesterday</div>
|
|
7616
|
+
* <div class="aparte-menu__item aparte-conv-item"><button class="aparte-conv-item__select" type="button"><span class="aparte-conv-item__title">Why is total a float?</span></button></div>
|
|
7617
|
+
* </div>
|
|
7618
|
+
* </div>
|
|
7619
|
+
* <div class="aparte-sidebar__footer"><span class="aparte-avatar aparte-avatar--sm">P</span> Paul</div>
|
|
7116
7620
|
* </aside>
|
|
7117
|
-
* <header class="aparte-app-header"
|
|
7621
|
+
* <header class="aparte-app-header">
|
|
7622
|
+
* <span class="aparte-app-header__title">Deploy checklist</span>
|
|
7623
|
+
* <div class="aparte-app-header__actions"><span class="aparte-tag">gpt-4.1</span></div>
|
|
7624
|
+
* </header>
|
|
7118
7625
|
* <main class="aparte-app-shell__main">
|
|
7119
7626
|
* <aparte-chat>
|
|
7120
|
-
* <aparte-chat-viewport
|
|
7627
|
+
* <aparte-chat-viewport>
|
|
7628
|
+
* <aparte-chat-bubble message-id="u1" data-role="user" content="Walk me through the deploy."></aparte-chat-bubble>
|
|
7629
|
+
* <aparte-chat-bubble message-id="a1" data-role="assistant" name="Assistant" content="Build, run the gate, tag, then push the tag — the workflow publishes from it."></aparte-chat-bubble>
|
|
7630
|
+
* </aparte-chat-viewport>
|
|
7121
7631
|
* <aparte-composer>
|
|
7122
7632
|
* <div class="aparte-composer-shell">
|
|
7123
7633
|
* <div class="aparte-composer-row">
|
|
@@ -7174,6 +7684,28 @@ aparte-sidebar,
|
|
|
7174
7684
|
min-height: 0;
|
|
7175
7685
|
height: auto;
|
|
7176
7686
|
}
|
|
7687
|
+
|
|
7688
|
+
/* A phone folds the shell to one column. The sidebar ELEMENT already leaves the grid
|
|
7689
|
+
under 48rem — it becomes a fixed drawer behind the header's toggle — but the grid
|
|
7690
|
+
kept its first column for it, and a class-only sidebar had no story at all: at 375px
|
|
7691
|
+
the shell gave 259 of its 303px to the sidebar and left the chat a 43px band with a
|
|
7692
|
+
send button cut in half. Same query as the element's own, so the recipe teaches the
|
|
7693
|
+
screen the element renders. */
|
|
7694
|
+
@media (max-width: 48rem) {
|
|
7695
|
+
.aparte-app-shell {
|
|
7696
|
+
grid-template-columns: minmax(0, 1fr);
|
|
7697
|
+
}
|
|
7698
|
+
.aparte-app-shell > .aparte-app-header,
|
|
7699
|
+
.aparte-app-shell__main {
|
|
7700
|
+
grid-column: 1;
|
|
7701
|
+
}
|
|
7702
|
+
/* A sidebar that is not (yet) a drawer — the class recipe alone, or the element
|
|
7703
|
+
before it upgrades — folds out of the flow rather than eating the column. */
|
|
7704
|
+
.aparte-app-shell > .aparte-sidebar:not([data-drawer]),
|
|
7705
|
+
.aparte-app-shell > aparte-sidebar:not([data-drawer]) {
|
|
7706
|
+
display: none;
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7177
7709
|
/*
|
|
7178
7710
|
* aparté — the seam: two panes side by side, and the grid that lets one grow while
|
|
7179
7711
|
* the other gives way. The fourth piece of the shell, after the sidebar, the header
|
|
@@ -7265,35 +7797,47 @@ aparte-split > [data-aparte-chat],
|
|
|
7265
7797
|
box-sizing: border-box;
|
|
7266
7798
|
border: 0;
|
|
7267
7799
|
padding: 0;
|
|
7268
|
-
|
|
7800
|
+
/* A transparent TRACK: the seam is the 1px line painted by `::before`, centred in it,
|
|
7801
|
+
and the grip is a short bar drawn on the track under the pointer. The track used
|
|
7802
|
+
to be the seam — 4px of paint, four times the kit's rule, with nothing to hold. */
|
|
7803
|
+
background: transparent;
|
|
7804
|
+
background-repeat: no-repeat;
|
|
7805
|
+
background-position: center;
|
|
7269
7806
|
cursor: col-resize;
|
|
7270
7807
|
touch-action: none;
|
|
7808
|
+
}
|
|
7809
|
+
|
|
7810
|
+
.aparte-split__handle::before {
|
|
7811
|
+
content: '';
|
|
7812
|
+
position: absolute;
|
|
7813
|
+
inset-block: 0;
|
|
7814
|
+
inset-inline-start: calc(50% - var(--aparte-split-seam-width) / 2);
|
|
7815
|
+
inline-size: var(--aparte-split-seam-width);
|
|
7816
|
+
background: var(--aparte-border);
|
|
7271
7817
|
transition: background-color var(--aparte-duration-fast) var(--aparte-ease);
|
|
7272
7818
|
}
|
|
7273
7819
|
|
|
7274
|
-
.aparte-split__handle:hover,
|
|
7275
|
-
.aparte-split__handle[data-dragging] {
|
|
7820
|
+
.aparte-split__handle:hover::before,
|
|
7821
|
+
.aparte-split__handle[data-dragging]::before {
|
|
7276
7822
|
background: var(--aparte-primary);
|
|
7277
7823
|
}
|
|
7278
7824
|
|
|
7279
|
-
/*
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
keyboard user has no other way to find.
|
|
7825
|
+
/* The grip: the convention of every editor's split, so the seam reads as something to
|
|
7826
|
+
take hold of. Drawn as a background so it needs no third element. */
|
|
7827
|
+
.aparte-split__handle:hover,
|
|
7828
|
+
.aparte-split__handle[data-dragging] {
|
|
7829
|
+
background-image: linear-gradient(var(--aparte-primary), var(--aparte-primary));
|
|
7830
|
+
background-size: calc(var(--aparte-split-seam-width) * 3) var(--aparte-space-8);
|
|
7831
|
+
}
|
|
7287
7832
|
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7833
|
+
/* The one ring. The seam's whole keyboard story is arrowing it, and its only focus
|
|
7834
|
+
indicator used to be a soft wash at 1.39:1 against the page — not weak, absent, on a
|
|
7835
|
+
4px band with `border: 0` that a keyboard user has no other way to find. The outline
|
|
7836
|
+
carries the contrast (3.54:1 light, 7.36:1 dark, measured), and it is the same outline
|
|
7837
|
+
every other control draws. */
|
|
7293
7838
|
.aparte-split__handle:focus-visible {
|
|
7294
7839
|
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
7295
|
-
outline-offset:
|
|
7296
|
-
box-shadow: var(--aparte-focus-ring);
|
|
7840
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
7297
7841
|
}
|
|
7298
7842
|
|
|
7299
7843
|
/* The hit slop: a 4px seam is a 4px target, and a pointer misses it. The pseudo-element
|
|
@@ -7382,6 +7926,14 @@ aparte-split[orientation='vertical'][primary='end']:not([data-stacked]),
|
|
|
7382
7926
|
aparte-split[orientation='vertical'] > .aparte-split__handle,
|
|
7383
7927
|
.aparte-split--vertical > .aparte-split__handle {
|
|
7384
7928
|
cursor: row-resize;
|
|
7929
|
+
background-size: var(--aparte-space-8) calc(var(--aparte-split-seam-width) * 3);
|
|
7930
|
+
}
|
|
7931
|
+
aparte-split[orientation='vertical'] > .aparte-split__handle::before,
|
|
7932
|
+
.aparte-split--vertical > .aparte-split__handle::before {
|
|
7933
|
+
inset-block: calc(50% - var(--aparte-split-seam-width) / 2) auto;
|
|
7934
|
+
inset-inline: 0;
|
|
7935
|
+
inline-size: auto;
|
|
7936
|
+
block-size: var(--aparte-split-seam-width);
|
|
7385
7937
|
}
|
|
7386
7938
|
|
|
7387
7939
|
aparte-split[orientation='vertical'] > .aparte-split__handle::after,
|
|
@@ -7639,6 +8191,13 @@ aparte-split[disabled] > .aparte-split__handle:hover {
|
|
|
7639
8191
|
.aparte-code-content-wrapper pre {
|
|
7640
8192
|
margin: 0;
|
|
7641
8193
|
padding: var(--aparte-space-6);
|
|
8194
|
+
/* The code typeface, at a code size. This rule declared neither and the block fell
|
|
8195
|
+
into the UA's generic `monospace` (Courier New on Windows) at the PROSE's size and
|
|
8196
|
+
leading; only inline code read the family. On the wrapper's `pre`, whatever class
|
|
8197
|
+
the highlighter puts on its replacement. */
|
|
8198
|
+
font-family: var(--aparte-code-font-family);
|
|
8199
|
+
font-size: var(--aparte-code-block-font-size);
|
|
8200
|
+
line-height: var(--aparte-code-block-line-height);
|
|
7642
8201
|
/* Wrap long lines instead of a horizontal scrollbar. `pre-wrap` honours
|
|
7643
8202
|
newlines + indentation but soft-wraps overflowing lines; `overflow-wrap:
|
|
7644
8203
|
anywhere` breaks unbreakable tokens (long URLs, hashes). Applies to both
|
|
@@ -7674,23 +8233,11 @@ aparte-split[disabled] > .aparte-split__handle:hover {
|
|
|
7674
8233
|
}
|
|
7675
8234
|
}
|
|
7676
8235
|
|
|
7677
|
-
/* Windows high contrast. In forced-colors mode the UA repaints colours and
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
own focus colour, so the indicator survives without fighting the user's
|
|
7682
|
-
palette. Only the indicators that disappear are restated here: everything
|
|
7683
|
-
whose affordance is text or a border already survives on its own — which the
|
|
7684
|
-
split's seam does not, being a 4px band with `border: 0` whose entire keyboard
|
|
7685
|
-
story is arrowing it. */
|
|
8236
|
+
/* Windows high contrast. In forced-colors mode the UA repaints colours and drops
|
|
8237
|
+
box-shadow entirely. Every focus indicator is an outline now, which forced colours
|
|
8238
|
+
paint on their own — the one indicator drawn with a box-shadow is the error ring on
|
|
8239
|
+
an avatar, restated here as an outline so it does not vanish. */
|
|
7686
8240
|
@media (forced-colors: active) {
|
|
7687
|
-
.aparte-thumb--image[role='button']:focus-visible,
|
|
7688
|
-
.aparte-split__handle:focus-visible,
|
|
7689
|
-
.aparte-provider-select:focus,
|
|
7690
|
-
.aparte-model-select:focus {
|
|
7691
|
-
outline: var(--aparte-focus-outline-width) solid Highlight;
|
|
7692
|
-
}
|
|
7693
|
-
|
|
7694
8241
|
.aparte-message[data-error] .aparte-avatar {
|
|
7695
8242
|
outline: var(--aparte-focus-outline-width) solid CanvasText;
|
|
7696
8243
|
}
|
|
@@ -7774,15 +8321,28 @@ aparte-split[disabled] > .aparte-split__handle:hover {
|
|
|
7774
8321
|
opacity: 1;
|
|
7775
8322
|
}
|
|
7776
8323
|
|
|
7777
|
-
/* Nor the ✕ on a pending attachment, which is the only way to remove one
|
|
8324
|
+
/* Nor the ✕ on a pending attachment, which is the only way to remove one — and
|
|
8325
|
+
seeing it is no use at 18px, under the 24 of WCAG 2.2 SC 2.5.8. 24 is not a new
|
|
8326
|
+
number: it is the box `aparte-btn--sm` already draws, so this is the component
|
|
8327
|
+
giving the recipe back its own size on touch rather than out-specifying it.
|
|
8328
|
+
Not `--aparte-touch-target-size`: 44px inside a 72px tile would swallow the
|
|
8329
|
+
picture, and full parity with the neighbouring controls needs the tile to grow
|
|
8330
|
+
with it — a second decision, not this floor. */
|
|
7778
8331
|
.aparte-thumb__remove {
|
|
7779
8332
|
opacity: 1;
|
|
8333
|
+
--aparte-thumb-remove-size: var(--aparte-btn-size-sm);
|
|
7780
8334
|
}
|
|
7781
8335
|
|
|
7782
8336
|
/* Hover cannot reveal the row's ⋯ on touch — keep it visible and tappable. */
|
|
7783
8337
|
.aparte-conv-item__more {
|
|
7784
8338
|
opacity: 1;
|
|
7785
|
-
--aparte-conv-action-btn-size:
|
|
8339
|
+
--aparte-conv-action-btn-size: var(--aparte-touch-target-size);
|
|
8340
|
+
}
|
|
8341
|
+
|
|
8342
|
+
/* The composer's own controls: 36 at rest, the touch target here — the send button, the
|
|
8343
|
+
input's action button and the editor's single-line height move together. */
|
|
8344
|
+
.aparte-composer-row {
|
|
8345
|
+
--aparte-composer-control-size: var(--aparte-touch-target-size);
|
|
7786
8346
|
}
|
|
7787
8347
|
|
|
7788
8348
|
/* A 4px seam is no target for a finger — the same modality rule the buttons above
|