@aparte/core 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/aparte-client.d.ts +53 -51
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/lifecycle-events.d.ts +0 -12
- package/dist/client/lifecycle-events.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +2 -12
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +45 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +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 -2
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +12 -12
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +29 -12
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts +31 -8
- package/dist/components/context/aparte-context.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +93 -36
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/conversation-list/index.d.ts +1 -1
- package/dist/components/conversation-list/index.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +20 -2
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/index.d.ts +7 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts +105 -0
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -0
- package/dist/components/scroll-rail/index.d.ts +3 -0
- package/dist/components/scroll-rail/index.d.ts.map +1 -0
- package/dist/components/sidebar/aparte-sidebar.d.ts +186 -0
- package/dist/components/sidebar/aparte-sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/index.d.ts +3 -0
- package/dist/components/sidebar/index.d.ts.map +1 -0
- package/dist/components/split/aparte-split.d.ts +354 -0
- package/dist/components/split/aparte-split.d.ts.map +1 -0
- package/dist/components/split/geometry.d.ts +65 -0
- package/dist/components/split/geometry.d.ts.map +1 -0
- package/dist/components/split/index.d.ts +3 -0
- package/dist/components/split/index.d.ts.map +1 -0
- package/dist/components/suggestions/aparte-suggestions.d.ts +7 -1
- package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +52 -2
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +95 -33
- package/dist/config/aparte-config.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/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/locale.d.ts +140 -27
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/config/sanitize.d.ts +9 -0
- package/dist/config/sanitize.d.ts.map +1 -1
- package/dist/conversations/conversation-controller.d.ts +13 -1
- package/dist/conversations/conversation-controller.d.ts.map +1 -1
- package/dist/conversations/conversation-manager.d.ts +9 -0
- package/dist/conversations/conversation-manager.d.ts.map +1 -1
- package/dist/custom-elements.json +10278 -7784
- package/dist/{is-awaiting-reply-BZ_iJ3s3.js → dialog-triggers-CVCxqKg3.js} +718 -1182
- package/dist/dialog-triggers-CVCxqKg3.js.map +1 -0
- package/dist/elicitation/approval-panel.d.ts +1 -1
- package/dist/elicitation/approval-panel.d.ts.map +1 -1
- package/dist/elicitation/panel.d.ts +24 -16
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/elicitation/types.d.ts +31 -0
- package/dist/elicitation/types.d.ts.map +1 -1
- package/dist/generated/element-attributes.d.ts +47 -1
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/{glyphs-B3BZ2TIb.js → glyphs-DQTeOj4w.js} +22 -13
- package/dist/{glyphs-B3BZ2TIb.js.map → glyphs-DQTeOj4w.js.map} +1 -1
- package/dist/host/aparte-chat-host.d.ts +10 -2
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/icons/extended.d.ts +4 -2
- package/dist/icons/extended.d.ts.map +1 -1
- package/dist/icons/glyphs.d.ts +9 -0
- package/dist/icons/glyphs.d.ts.map +1 -1
- package/dist/icons.js +16 -15
- package/dist/icons.js.map +1 -1
- package/dist/index.css +1284 -397
- package/dist/index.d.ts +26 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2099 -296
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +21 -5
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +39 -34
- package/dist/index.node.js.map +1 -1
- package/dist/interop/dialog-triggers.d.ts +27 -0
- package/dist/interop/dialog-triggers.d.ts.map +1 -0
- package/dist/interop/element-props.d.ts +16 -8
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/parsers/aparte-stream-parser.d.ts +52 -22
- package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
- package/dist/parsers/index.d.ts +1 -1
- package/dist/parsers/index.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/code.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/runtime/message-repository.d.ts.map +1 -1
- package/dist/transport/backend-handler.d.ts.map +1 -1
- package/dist/transport/backend-transport.d.ts +6 -1
- package/dist/transport/backend-transport.d.ts.map +1 -1
- package/dist/types/chat.d.ts +8 -14
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/element-map.d.ts +54 -4
- package/dist/types/element-map.d.ts.map +1 -1
- package/dist/types/event-map.d.ts +16 -10
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +59 -117
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/imperative-api.d.ts +3 -1
- package/dist/types/imperative-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/models.d.ts +11 -31
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/segments.d.ts +11 -47
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/stream-blocks.d.ts +57 -0
- package/dist/types/stream-blocks.d.ts.map +1 -0
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/copy-text.d.ts +24 -0
- package/dist/utils/copy-text.d.ts.map +1 -0
- package/dist/utils/css-escape.d.ts +5 -0
- package/dist/utils/css-escape.d.ts.map +1 -1
- package/dist/utils/tool-input.d.ts +20 -0
- package/dist/utils/tool-input.d.ts.map +1 -0
- package/package.json +10 -3
- package/dist/is-awaiting-reply-BZ_iJ3s3.js.map +0 -1
- package/dist/renderers/segments/artifact/binary-file.d.ts +0 -45
- package/dist/renderers/segments/artifact/binary-file.d.ts.map +0 -1
- package/dist/renderers/segments/artifact/card.d.ts +0 -3
- package/dist/renderers/segments/artifact/card.d.ts.map +0 -1
- package/dist/renderers/segments/artifact/preview-document.d.ts +0 -27
- package/dist/renderers/segments/artifact/preview-document.d.ts.map +0 -1
- package/dist/renderers/segments/artifact/shared.d.ts +0 -29
- package/dist/renderers/segments/artifact/shared.d.ts.map +0 -1
package/dist/index.css
CHANGED
|
@@ -35,13 +35,15 @@
|
|
|
35
35
|
--aparte-surface-1: #fffdf8;
|
|
36
36
|
/* Bubbles, cards */
|
|
37
37
|
--aparte-surface-2: #f1ebdf;
|
|
38
|
-
/*
|
|
39
|
-
|
|
38
|
+
/* `--aparte-surface-3` (code blocks, headers) and `--aparte-text-inverse` are no
|
|
39
|
+
longer literals here: both are derived from the masters in the anchored layer
|
|
40
|
+
below, so the eight-line rebrand the theming guide documents actually covers them.
|
|
41
|
+
They were the two palette hexes that survived a rebrand — a consumer on a blue
|
|
42
|
+
brand kept the brass palette's cream for its tooltip ink and its code headers. */
|
|
40
43
|
|
|
41
44
|
/* Text */
|
|
42
45
|
--aparte-text: #241f2b;
|
|
43
46
|
--aparte-text-muted: #6d6479;
|
|
44
|
-
--aparte-text-inverse: #fffdf8;
|
|
45
47
|
|
|
46
48
|
/* Borders */
|
|
47
49
|
--aparte-border: #e5ddce;
|
|
@@ -87,8 +89,8 @@
|
|
|
87
89
|
a large-type page grow twice over. Wanting rem spacing anyway is now a
|
|
88
90
|
one-line change here — which is the point of having a master at all. */
|
|
89
91
|
--aparte-space-unit: 2px;
|
|
90
|
-
--aparte-radius-unit:
|
|
91
|
-
--aparte-font-scale: 1;
|
|
92
|
+
--aparte-radius-unit: 3px;
|
|
93
|
+
--aparte-font-scale: 1.08;
|
|
92
94
|
|
|
93
95
|
/* Not a step of the radius scale: a pill is a shape, not a size. */
|
|
94
96
|
--aparte-radius-full: 9999px;
|
|
@@ -177,9 +179,9 @@
|
|
|
177
179
|
/* Messages */
|
|
178
180
|
--aparte-message-max-width: 800px;
|
|
179
181
|
|
|
180
|
-
/* Bubbles
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
/* Bubbles. The user's bubble tint is DERIVED from the accent in the anchored layer
|
|
183
|
+
below (it was a frozen plum hex that outlived every rebrand); the assistant is
|
|
184
|
+
plain prose (AI-chat convention) — flip it to get a two-bubble chat-app look. */
|
|
183
185
|
--aparte-message-content-bg-assistant: transparent;
|
|
184
186
|
--aparte-message-content-radius: 14px;
|
|
185
187
|
|
|
@@ -327,6 +329,34 @@
|
|
|
327
329
|
|
|
328
330
|
/* Conversation List */
|
|
329
331
|
--aparte-conv-action-btn-size: 20px;
|
|
332
|
+
|
|
333
|
+
/* Scroll rail — the ticks beside the transcript. Off-scale on purpose: a 2px line
|
|
334
|
+
and a 14px mark are a minimap's measures, not spacing steps. */
|
|
335
|
+
--aparte-scroll-rail-width: 20px;
|
|
336
|
+
--aparte-scroll-rail-tick-size: 14px;
|
|
337
|
+
--aparte-scroll-rail-tick-thickness: 2px;
|
|
338
|
+
|
|
339
|
+
--aparte-scroll-rail-gap: 8px;
|
|
340
|
+
|
|
341
|
+
/* Context gauge, ring variant — a diameter, and a stroke in the ring's own 36-unit
|
|
342
|
+
box. Literals like the rail's: a 22px ring is a toolbar's measure, not a spacing step. */
|
|
343
|
+
--aparte-context-ring-size: 22px;
|
|
344
|
+
--aparte-context-ring-stroke: 4;
|
|
345
|
+
|
|
346
|
+
/* Shell — the sidebar's column, the header's row and the split's seam. */
|
|
347
|
+
--aparte-sidebar-width: 260px;
|
|
348
|
+
--aparte-app-header-height: 48px;
|
|
349
|
+
--aparte-scrim: rgba(0, 0, 0, 0.35);
|
|
350
|
+
/* The split's primary pane and the bounds it moves between. Any length or
|
|
351
|
+
percentage: the browser clamps them in the grid template, so nothing in JS
|
|
352
|
+
parses a unit. The seam is 4px because that is a seam; the grab zone around it
|
|
353
|
+
is 12px because that is a target, and it grows to the touch target on a coarse
|
|
354
|
+
pointer (responsive.css). */
|
|
355
|
+
--aparte-split-position: 38%;
|
|
356
|
+
--aparte-split-min: 20rem;
|
|
357
|
+
--aparte-split-max: 60%;
|
|
358
|
+
--aparte-split-handle-size: 4px;
|
|
359
|
+
--aparte-split-hit-area: 12px;
|
|
330
360
|
/* Sizes that are not steps of a scale. Each is off-scale on purpose — a 40px
|
|
331
361
|
file tile is a tile, not fourteen spacing units — so each gets a name rather
|
|
332
362
|
than sitting as a magic number in a rule. Literal, like every other size
|
|
@@ -358,28 +388,6 @@
|
|
|
358
388
|
/* Select. Its dropdown shadow used to live as a fallback inside select.css,
|
|
359
389
|
which meant it had no dark value at all — rgba(0,0,0,0.1) over a dark
|
|
360
390
|
surface is no shadow. Declared here, it themes like every other. */
|
|
361
|
-
/* Paper. An artifact preview is a DOCUMENT shown inside the chat — a spreadsheet,
|
|
362
|
-
a rendered page — so it stays light whatever the app theme is, the way a PDF
|
|
363
|
-
viewer shows a white page in a dark editor. That intent was already written in a
|
|
364
|
-
comment beside a literal `#fff`; naming it is what makes it overridable, and it
|
|
365
|
-
is deliberately absent from the dark block so the paper does not follow. */
|
|
366
|
-
--aparte-art-paper-bg: #fff;
|
|
367
|
-
--aparte-art-paper-text: #1f2937;
|
|
368
|
-
--aparte-art-paper-row-alt: #f9fafb;
|
|
369
|
-
--aparte-art-paper-head-bg: #f3f4f6;
|
|
370
|
-
--aparte-art-paper-head-text: #111827;
|
|
371
|
-
--aparte-art-paper-border: rgba(0, 0, 0, 0.1);
|
|
372
|
-
|
|
373
|
-
/* File-type tiles. Brand colours, so they are literals on purpose — but named,
|
|
374
|
-
because an app with its own file-type palette has nowhere else to put it. */
|
|
375
|
-
--aparte-art-file-icon-bg: linear-gradient(135deg, #1d6f42, #0f5132);
|
|
376
|
-
/* Fixed, like the tiles it sits on. --aparte-text-inverse would be right for a
|
|
377
|
-
THEMED background and is wrong here: it turns near-black in dark mode, which
|
|
378
|
-
would put black lettering on a dark green tile. */
|
|
379
|
-
--aparte-art-file-icon-color: #fff;
|
|
380
|
-
--aparte-art-file-icon-bg-pdf: linear-gradient(135deg, #c0392b, #7d1f17);
|
|
381
|
-
--aparte-art-file-icon-bg-docx: linear-gradient(135deg, #1e5288, #0f3060);
|
|
382
|
-
--aparte-art-file-error-msg-bg: rgba(0, 0, 0, 0.04);
|
|
383
391
|
|
|
384
392
|
--aparte-line-height-snug: 1.4;
|
|
385
393
|
|
|
@@ -436,19 +444,13 @@
|
|
|
436
444
|
--aparte-skeleton-text-last-width: 60%;
|
|
437
445
|
--aparte-progress-indeterminate-width: 40%;
|
|
438
446
|
|
|
439
|
-
--aparte-btn-size-sm:
|
|
440
|
-
--aparte-btn-size-md:
|
|
441
|
-
--aparte-btn-size-lg:
|
|
447
|
+
--aparte-btn-size-sm: 24px;
|
|
448
|
+
--aparte-btn-size-md: 32px;
|
|
449
|
+
--aparte-btn-size-lg: 40px;
|
|
442
450
|
|
|
443
451
|
--aparte-focus-outline-offset: -2px;
|
|
444
452
|
--aparte-tool-spinner-size: 10px;
|
|
445
453
|
--aparte-model-select-chevron-room: 28px;
|
|
446
|
-
--aparte-art-card-header-min-height: 36px;
|
|
447
|
-
--aparte-art-card-pulse-size: 8px;
|
|
448
|
-
--aparte-art-card-btn-size: 28px;
|
|
449
|
-
--aparte-art-file-icon-size: 40px;
|
|
450
|
-
--aparte-art-file-preview-padding: 20px;
|
|
451
|
-
--aparte-art-file-error-padding-inline: 18px;
|
|
452
454
|
}
|
|
453
455
|
|
|
454
456
|
/* =========================================
|
|
@@ -460,10 +462,11 @@
|
|
|
460
462
|
--aparte-bg: #17141c;
|
|
461
463
|
--aparte-surface-1: #211b28;
|
|
462
464
|
--aparte-surface-2: #2a2333;
|
|
463
|
-
|
|
465
|
+
/* `--aparte-surface-3`, `--aparte-text-inverse` and the user bubble's tint are not
|
|
466
|
+
re-declared here: they derive from the masters (anchored layer), so the dark
|
|
467
|
+
masters above are enough to move them. */
|
|
464
468
|
|
|
465
469
|
/* Bubbles — user only; assistant is plain prose (AI-chat convention) */
|
|
466
|
-
--aparte-message-content-bg-user: #2f2740;
|
|
467
470
|
--aparte-message-content-bg-assistant: transparent;
|
|
468
471
|
|
|
469
472
|
/* Text */
|
|
@@ -473,7 +476,6 @@
|
|
|
473
476
|
dark for the only reason that this line was missing — which left the scrollbar
|
|
474
477
|
thumb, and now a neutral badge, at the light value on a dark ground. */
|
|
475
478
|
--aparte-neutral: #a89bb6;
|
|
476
|
-
--aparte-text-inverse: #17141c;
|
|
477
479
|
|
|
478
480
|
/* Borders */
|
|
479
481
|
--aparte-border: #342a40;
|
|
@@ -490,9 +492,6 @@
|
|
|
490
492
|
--aparte-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
491
493
|
--aparte-switch-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
492
494
|
--aparte-range-thumb-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
493
|
-
/* Not the paper — that stays light on purpose. This is a code block inside the
|
|
494
|
-
error panel, which does follow the theme. */
|
|
495
|
-
--aparte-art-file-error-msg-bg: rgba(255, 255, 255, 0.06);
|
|
496
495
|
|
|
497
496
|
/* Error segment */
|
|
498
497
|
/* The text inks, going the other way: on a dark ground the accent gains
|
|
@@ -771,11 +770,26 @@ aparte-chat {
|
|
|
771
770
|
--aparte-avatar-error-ring: 0 0 0 2px var(--aparte-error-solid);
|
|
772
771
|
|
|
773
772
|
/* Focus ring — derived from the accent so it themes automatically. */
|
|
774
|
-
--aparte-focus-ring: 0 0 0 3px color-mix(in srgb, var(--aparte-primary)
|
|
773
|
+
--aparte-focus-ring: 0 0 0 3px color-mix(in srgb, var(--aparte-primary) 30%, transparent);
|
|
775
774
|
|
|
776
775
|
/* Messages */
|
|
777
776
|
--aparte-message-content-text-user: var(--aparte-text);
|
|
778
777
|
--aparte-message-content-text-assistant: var(--aparte-text);
|
|
778
|
+
/* The user's bubble: a wash of the accent over the card surface. It was a literal
|
|
779
|
+
(`#efe7f6` light / `#2f2740` dark) — the one palette colour a consumer's eight-line
|
|
780
|
+
rebrand could not reach, so a chat moved to a blue brand kept a plum bubble. Mixed
|
|
781
|
+
in sRGB rather than derived with `oklch(from …)`, because a wash has to keep the
|
|
782
|
+
SURFACE's lightness, not the accent's; 12 % is where the default palette reads as a
|
|
783
|
+
tint, not a fill, on both grounds. */
|
|
784
|
+
--aparte-message-content-bg-user: color-mix(in srgb, var(--aparte-primary) 12%, var(--aparte-surface-1));
|
|
785
|
+
/* The third surface: the second, pulled 6 % toward the text. Close to what the old
|
|
786
|
+
literals encoded (`#e7dfce` from `#f1ebdf` light, `#342a40` from `#2a2333` dark —
|
|
787
|
+
5 to 9 % per channel, not one figure), now one rule that follows the masters. */
|
|
788
|
+
--aparte-surface-3: color-mix(in srgb, var(--aparte-text) 6%, var(--aparte-surface-2));
|
|
789
|
+
/* Text on the text colour used as a fill (the tooltip, the avatar). The card
|
|
790
|
+
surface is the lightest tone in light and the darkest-but-one in dark, which is
|
|
791
|
+
what the two literals were. */
|
|
792
|
+
--aparte-text-inverse: var(--aparte-surface-1);
|
|
779
793
|
--aparte-surface: var(--aparte-surface-1);
|
|
780
794
|
--aparte-surface-hover: var(--aparte-surface-2);
|
|
781
795
|
--aparte-text-secondary: var(--aparte-text-muted);
|
|
@@ -855,6 +869,9 @@ aparte-chat {
|
|
|
855
869
|
/* Input (composer) */
|
|
856
870
|
--aparte-input-line-height: var(--aparte-line-height-normal);
|
|
857
871
|
|
|
872
|
+
/* Shell */
|
|
873
|
+
--aparte-sidebar-bg: var(--aparte-surface-2);
|
|
874
|
+
|
|
858
875
|
/* Conversation List */
|
|
859
876
|
--aparte-conv-item-radius: var(--aparte-radius-md);
|
|
860
877
|
--aparte-conv-item-color: var(--aparte-text-muted);
|
|
@@ -862,10 +879,6 @@ aparte-chat {
|
|
|
862
879
|
--aparte-conv-item-bg-hover: var(--aparte-surface-3);
|
|
863
880
|
--aparte-conv-item-bg-active: var(--aparte-surface-3);
|
|
864
881
|
--aparte-conv-item-font-weight-active: var(--aparte-font-weight-medium);
|
|
865
|
-
--aparte-conv-delete-color: var(--aparte-text-muted);
|
|
866
|
-
--aparte-conv-delete-bg-hover: var(--aparte-error);
|
|
867
|
-
--aparte-conv-delete-color-hover: var(--aparte-text-inverse);
|
|
868
|
-
--aparte-conv-delete-radius: var(--aparte-radius-sm);
|
|
869
882
|
}
|
|
870
883
|
|
|
871
884
|
/*
|
|
@@ -1292,6 +1305,7 @@ aparte-conversation-list {
|
|
|
1292
1305
|
* </label>
|
|
1293
1306
|
* <input id="email" class="aparte-field" type="email" placeholder="you@example.com" />
|
|
1294
1307
|
* <p class="aparte-field-hint">We only use this to send a receipt.</p>
|
|
1308
|
+
* <p class="aparte-field-warning">Changing it signs you out of other devices.</p>
|
|
1295
1309
|
*
|
|
1296
1310
|
* <div class="aparte-field-group">
|
|
1297
1311
|
* <span class="aparte-field-group__prefix">https://</span>
|
|
@@ -1437,6 +1451,9 @@ aparte-conversation-list {
|
|
|
1437
1451
|
background: var(--aparte-surface-1);
|
|
1438
1452
|
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
1439
1453
|
border-radius: var(--aparte-field-radius);
|
|
1454
|
+
/* The addons' ground follows the group's corners; the focus ring is a shadow on the
|
|
1455
|
+
group itself, outside this box, so it is not clipped. */
|
|
1456
|
+
overflow: hidden;
|
|
1440
1457
|
transition:
|
|
1441
1458
|
border-color var(--aparte-duration-fast) var(--aparte-ease),
|
|
1442
1459
|
box-shadow var(--aparte-duration-fast) var(--aparte-ease);
|
|
@@ -1458,15 +1475,24 @@ aparte-conversation-list {
|
|
|
1458
1475
|
box-shadow: none;
|
|
1459
1476
|
}
|
|
1460
1477
|
|
|
1478
|
+
/* An addon has a ground of its own and a rule against the field: on the field's own
|
|
1479
|
+
ground, in muted text, "https://" read as the start of what the user had typed. */
|
|
1461
1480
|
.aparte-field-group__prefix,
|
|
1462
1481
|
.aparte-field-group__suffix {
|
|
1463
1482
|
display: inline-flex;
|
|
1464
1483
|
align-items: center;
|
|
1465
1484
|
padding-inline: var(--aparte-space-3);
|
|
1485
|
+
background: var(--aparte-surface-2);
|
|
1466
1486
|
font-size: var(--aparte-font-size-sm);
|
|
1467
1487
|
color: var(--aparte-text-muted);
|
|
1468
1488
|
white-space: nowrap;
|
|
1469
1489
|
}
|
|
1490
|
+
.aparte-field-group__prefix {
|
|
1491
|
+
border-inline-end: var(--aparte-border-width) solid var(--aparte-border);
|
|
1492
|
+
}
|
|
1493
|
+
.aparte-field-group__suffix {
|
|
1494
|
+
border-inline-start: var(--aparte-border-width) solid var(--aparte-border);
|
|
1495
|
+
}
|
|
1470
1496
|
|
|
1471
1497
|
.aparte-field-group:hover:not(:has(.aparte-field:disabled)) {
|
|
1472
1498
|
background: var(--aparte-surface-hover);
|
|
@@ -1720,10 +1746,15 @@ aparte-conversation-list {
|
|
|
1720
1746
|
has to live here where `:checked` can override it — a central default could never
|
|
1721
1747
|
react to the class + state combo. */
|
|
1722
1748
|
--aparte-switch-track: var(--aparte-surface-3);
|
|
1723
|
-
--aparte-switch-width:
|
|
1724
|
-
--aparte-switch-height:
|
|
1725
|
-
--aparte-switch-thumb-size: 16px;
|
|
1749
|
+
--aparte-switch-width: 40px;
|
|
1750
|
+
--aparte-switch-height: 22px;
|
|
1726
1751
|
--aparte-switch-thumb-inset: 2px;
|
|
1752
|
+
/* DERIVED from the track, not a fourth literal to keep in step with the other three:
|
|
1753
|
+
the thumb used to be 16px in a 20px track with a 1px border and a 2px top inset —
|
|
1754
|
+
2px of air above it and none below, visibly low ("le cercle pas centré"). The
|
|
1755
|
+
thumb is what is left once the border and the inset are paid on both sides, so
|
|
1756
|
+
it is centred by construction, whatever the track's size. */
|
|
1757
|
+
--aparte-switch-thumb-size: calc(var(--aparte-switch-height) - 2 * var(--aparte-border-width) - 2 * var(--aparte-switch-thumb-inset));
|
|
1727
1758
|
/* No shared shadow token fits a small thumb like this one, so it's a plain literal —
|
|
1728
1759
|
kept the same in both themes rather than reaching for a per-theme split this file
|
|
1729
1760
|
has no other precedent for. */
|
|
@@ -1926,6 +1957,19 @@ aparte-conversation-list {
|
|
|
1926
1957
|
color: var(--aparte-error-text);
|
|
1927
1958
|
}
|
|
1928
1959
|
|
|
1960
|
+
/* The third tone of a field's sub-text: a consequence, not a fault. "This setting
|
|
1961
|
+
invalidates the saved states" is neither a hint nor an error, and a shell that had
|
|
1962
|
+
only those two painted it as one of them. Same shape as the error line; the warning
|
|
1963
|
+
ink is the derived one every other warning reads. */
|
|
1964
|
+
.aparte-field-warning {
|
|
1965
|
+
display: flex;
|
|
1966
|
+
align-items: center;
|
|
1967
|
+
gap: var(--aparte-space-1);
|
|
1968
|
+
margin-block-start: var(--aparte-space-1);
|
|
1969
|
+
font-size: var(--aparte-font-size-xs);
|
|
1970
|
+
color: var(--aparte-warning-ink);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1929
1973
|
/* A bare colour on the marker, not injected `*` content: a locale that doesn't mark
|
|
1930
1974
|
required fields with an asterisk still gets a place to say so, in its own text. */
|
|
1931
1975
|
.aparte-field-required {
|
|
@@ -2726,11 +2770,20 @@ aparte-icon {
|
|
|
2726
2770
|
=========================================
|
|
2727
2771
|
Decorative: give the element aria-hidden="true" (or wrap the group behind a
|
|
2728
2772
|
single aria-busy region) so a screen reader does not read out an empty box.
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2773
|
+
A skeleton is a block with no margin of its own: the recipe spaces consecutive
|
|
2774
|
+
text lines and nothing else, so a GROUP is laid out by its container — a grid
|
|
2775
|
+
with a gap, the way the real card it stands in for is. Four lines glued edge to
|
|
2776
|
+
edge is what the example used to show.
|
|
2777
|
+
|
|
2778
|
+
<div style="display: grid; grid-template-columns: auto 1fr; gap: var(--aparte-space-3); align-items: start" aria-busy="true">
|
|
2779
|
+
<span class="aparte-skeleton aparte-skeleton--circle" aria-hidden="true"></span>
|
|
2780
|
+
<div>
|
|
2781
|
+
<span class="aparte-skeleton aparte-skeleton--text" aria-hidden="true"></span>
|
|
2782
|
+
<span class="aparte-skeleton aparte-skeleton--text" aria-hidden="true"></span>
|
|
2783
|
+
<span class="aparte-skeleton aparte-skeleton--text" aria-hidden="true"></span>
|
|
2784
|
+
</div>
|
|
2785
|
+
<span class="aparte-skeleton aparte-skeleton--rect" style="grid-column: 1 / -1; block-size: 64px" aria-hidden="true"></span>
|
|
2786
|
+
</div>
|
|
2734
2787
|
*/
|
|
2735
2788
|
|
|
2736
2789
|
.aparte-skeleton {
|
|
@@ -3099,9 +3152,11 @@ aparte-icon {
|
|
|
3099
3152
|
* aparté — layered surfaces. Ready-made classes for the things that float ABOVE the
|
|
3100
3153
|
* transcript: tabs, an accordion, a dropdown menu, a popover, a tooltip.
|
|
3101
3154
|
*
|
|
3102
|
-
*
|
|
3103
|
-
*
|
|
3104
|
-
*
|
|
3155
|
+
* Drawer and toast are deliberately NOT here: each needs a stack manager — something
|
|
3156
|
+
* that owns the order of what floats over the page — and that is the consuming
|
|
3157
|
+
* application's job, not a component stylesheet's. The dialog was on that list until
|
|
3158
|
+
* 0.16.0; it has its own recipe now (`dialog.css`), because the browser's `<dialog>`
|
|
3159
|
+
* brings the top layer, the focus trap and Escape with it.
|
|
3105
3160
|
*
|
|
3106
3161
|
* Everything below is the LOOK only. Where and when a surface opens is markup and
|
|
3107
3162
|
* script the caller owns: a <details open> toggling itself, a positioning library placing
|
|
@@ -3439,8 +3494,28 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3439
3494
|
<button class="aparte-menu__item" role="menuitem">Retry</button>
|
|
3440
3495
|
<div class="aparte-menu__separator" role="separator"></div>
|
|
3441
3496
|
<button class="aparte-menu__item" role="menuitem" disabled>Delete</button>
|
|
3497
|
+
<div class="aparte-menu__separator" role="separator"></div>
|
|
3498
|
+
<span class="aparte-menu__label">Mode</span>
|
|
3499
|
+
<button class="aparte-menu__item" role="menuitemradio" aria-checked="true">
|
|
3500
|
+
<span class="aparte-menu__body">
|
|
3501
|
+
Ask
|
|
3502
|
+
<span class="aparte-menu__description">Every tool call waits for you</span>
|
|
3503
|
+
</span>
|
|
3504
|
+
</button>
|
|
3505
|
+
<button class="aparte-menu__item" role="menuitemradio" aria-checked="false">
|
|
3506
|
+
<span class="aparte-menu__body">
|
|
3507
|
+
Auto
|
|
3508
|
+
<span class="aparte-menu__description">Runs everything, reports after</span>
|
|
3509
|
+
</span>
|
|
3510
|
+
</button>
|
|
3442
3511
|
</div>
|
|
3443
3512
|
|
|
3513
|
+
The check mark is DRAWN HERE, not by you: an item carrying `aria-checked` (a
|
|
3514
|
+
`menuitemradio` or `menuitemcheckbox`) reserves a gutter, and the glyph shows when
|
|
3515
|
+
the value is `"true"` — so set the attribute and do not add a "✓" of your own, or the
|
|
3516
|
+
row shows two. A two-line item wraps its name and its `__description` in a `__body`;
|
|
3517
|
+
the gutter then spans both lines.
|
|
3518
|
+
|
|
3444
3519
|
WHERE it sits is yours. There is no positioning here and no anchoring logic —
|
|
3445
3520
|
floating a box next to a trigger needs collision detection and a scroll listener,
|
|
3446
3521
|
which is an interaction library's job, not a stylesheet's. Same split as the popover
|
|
@@ -3548,6 +3623,30 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3548
3623
|
visibility: visible;
|
|
3549
3624
|
}
|
|
3550
3625
|
|
|
3626
|
+
/* A two-line item: a name and, under it, what choosing it means (a mode picker, a
|
|
3627
|
+
model with its size). `__body` stacks the two so the check gutter spans both lines
|
|
3628
|
+
— a consumer laid a grid over the item to get exactly this, and that grid was the
|
|
3629
|
+
only piece of specificity CSS his menu needed. */
|
|
3630
|
+
.aparte-menu__body {
|
|
3631
|
+
display: flex;
|
|
3632
|
+
flex-direction: column;
|
|
3633
|
+
align-items: flex-start;
|
|
3634
|
+
gap: calc(var(--aparte-space-1) / 2);
|
|
3635
|
+
min-width: 0;
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
.aparte-menu__description {
|
|
3639
|
+
font-size: var(--aparte-font-size-xs);
|
|
3640
|
+
font-weight: var(--aparte-font-weight-normal);
|
|
3641
|
+
color: var(--aparte-text-muted);
|
|
3642
|
+
text-align: start;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
.aparte-menu__item:disabled .aparte-menu__description,
|
|
3646
|
+
.aparte-menu__item[aria-disabled='true'] .aparte-menu__description {
|
|
3647
|
+
color: inherit;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3551
3650
|
.aparte-menu__separator {
|
|
3552
3651
|
height: var(--aparte-border-width);
|
|
3553
3652
|
margin-block: var(--aparte-space-2);
|
|
@@ -3600,6 +3699,170 @@ details[open] > .aparte-accordion__header .aparte-accordion__icon {
|
|
|
3600
3699
|
line-height: var(--aparte-line-height-normal);
|
|
3601
3700
|
z-index: var(--aparte-z-dropdown);
|
|
3602
3701
|
}
|
|
3702
|
+
/*
|
|
3703
|
+
* aparté — the modal, on the browser's own <dialog>.
|
|
3704
|
+
*
|
|
3705
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
3706
|
+
* them concatenated in that same order, the way a browser does.
|
|
3707
|
+
*/
|
|
3708
|
+
|
|
3709
|
+
/*
|
|
3710
|
+
* aparte-dialog — a modal, drawn on the native <dialog>.
|
|
3711
|
+
* The kit said for a year that a dialog was "deliberately absent — a modal needs a
|
|
3712
|
+
* portal and a stack manager". The browser has had both since 2022: `<dialog>` with
|
|
3713
|
+
* `showModal()` puts the box in the top layer above everything, traps focus, closes
|
|
3714
|
+
* on Escape, hands the focus back, and draws a `::backdrop`. So this recipe styles
|
|
3715
|
+
* THAT element, and core's one addition is the wiring — a `data-aparte-dialog-open`
|
|
3716
|
+
* on any control opens the dialog it names, a `data-aparte-dialog-close` inside one
|
|
3717
|
+
* closes it, and a click on the backdrop closes it unless the dialog carries
|
|
3718
|
+
* `data-aparte-dialog-static`. Nothing here reaches for position:fixed on your behalf:
|
|
3719
|
+
* the top layer is the browser's.
|
|
3720
|
+
*
|
|
3721
|
+
* <dialog class="aparte-dialog" id="settings" aria-labelledby="settings-title">
|
|
3722
|
+
* <div class="aparte-dialog__header">
|
|
3723
|
+
* <h2 class="aparte-dialog__title" id="settings-title">Settings</h2>
|
|
3724
|
+
* <button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-dialog__close" type="button" aria-label="Close" data-aparte-dialog-close>
|
|
3725
|
+
* <aparte-icon name="close"></aparte-icon>
|
|
3726
|
+
* </button>
|
|
3727
|
+
* </div>
|
|
3728
|
+
* <div class="aparte-dialog__body">
|
|
3729
|
+
* <label class="aparte-field-label" for="endpoint">Endpoint</label>
|
|
3730
|
+
* <input class="aparte-field" id="endpoint" value="http://localhost:11434/v1">
|
|
3731
|
+
* </div>
|
|
3732
|
+
* <div class="aparte-dialog__footer">
|
|
3733
|
+
* <button class="aparte-btn aparte-btn--ghost" type="button" data-aparte-dialog-close>Cancel</button>
|
|
3734
|
+
* <button class="aparte-btn aparte-btn--primary aparte-btn--solid" type="button" data-aparte-dialog-close="saved">Save</button>
|
|
3735
|
+
* </div>
|
|
3736
|
+
* </dialog>
|
|
3737
|
+
* <button class="aparte-btn aparte-btn--surface" type="button" data-aparte-dialog-open="settings">Open settings</button>
|
|
3738
|
+
*
|
|
3739
|
+
* Sizes: `--sm` (24rem), the default (32rem), `--lg` (48rem); under 30rem of window
|
|
3740
|
+
* every size becomes a full-screen sheet, which is what a settings hub is on a phone.
|
|
3741
|
+
* The value passed to `data-aparte-dialog-close="…"` becomes the dialog's
|
|
3742
|
+
* `returnValue`, read in its native `close` event.
|
|
3743
|
+
*/
|
|
3744
|
+
.aparte-dialog {
|
|
3745
|
+
--aparte-dialog-width: 32rem;
|
|
3746
|
+
|
|
3747
|
+
box-sizing: border-box;
|
|
3748
|
+
width: min(var(--aparte-dialog-width), calc(100vw - 2 * var(--aparte-space-8)));
|
|
3749
|
+
max-height: calc(100dvh - 2 * var(--aparte-space-8));
|
|
3750
|
+
margin: auto;
|
|
3751
|
+
padding: 0;
|
|
3752
|
+
display: none;
|
|
3753
|
+
flex-direction: column;
|
|
3754
|
+
overflow: hidden;
|
|
3755
|
+
background: var(--aparte-surface-1);
|
|
3756
|
+
color: var(--aparte-text);
|
|
3757
|
+
border: var(--aparte-border-width) solid var(--aparte-border);
|
|
3758
|
+
border-radius: var(--aparte-radius-xl);
|
|
3759
|
+
box-shadow: var(--aparte-menu-shadow);
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
/* `display: flex` only while open: a closed <dialog> must stay `display: none`, and a
|
|
3763
|
+
flex declaration on the bare class would show it. */
|
|
3764
|
+
.aparte-dialog[open] {
|
|
3765
|
+
display: flex;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
.aparte-dialog--sm {
|
|
3769
|
+
--aparte-dialog-width: 24rem;
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
.aparte-dialog--lg {
|
|
3773
|
+
--aparte-dialog-width: 48rem;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
.aparte-dialog::backdrop {
|
|
3777
|
+
background: var(--aparte-scrim);
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
.aparte-dialog__header {
|
|
3781
|
+
display: flex;
|
|
3782
|
+
align-items: center;
|
|
3783
|
+
gap: var(--aparte-space-3);
|
|
3784
|
+
padding: var(--aparte-space-5) var(--aparte-space-6) var(--aparte-space-3);
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
.aparte-dialog__title {
|
|
3788
|
+
flex: 1;
|
|
3789
|
+
min-width: 0;
|
|
3790
|
+
margin: 0;
|
|
3791
|
+
font-size: var(--aparte-font-size-lg);
|
|
3792
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
3793
|
+
line-height: var(--aparte-line-height-snug);
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
.aparte-dialog__close {
|
|
3797
|
+
flex-shrink: 0;
|
|
3798
|
+
color: var(--aparte-text-muted);
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
/* The body is the one region that scrolls, so a long form never pushes the footer
|
|
3802
|
+
off screen. */
|
|
3803
|
+
.aparte-dialog__body {
|
|
3804
|
+
flex: 1 1 auto;
|
|
3805
|
+
min-height: 0;
|
|
3806
|
+
overflow-y: auto;
|
|
3807
|
+
padding: var(--aparte-space-3) var(--aparte-space-6);
|
|
3808
|
+
display: flex;
|
|
3809
|
+
flex-direction: column;
|
|
3810
|
+
gap: var(--aparte-space-4);
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
.aparte-dialog__footer {
|
|
3814
|
+
display: flex;
|
|
3815
|
+
justify-content: flex-end;
|
|
3816
|
+
gap: var(--aparte-space-3);
|
|
3817
|
+
padding: var(--aparte-space-3) var(--aparte-space-6) var(--aparte-space-5);
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
/* The page behind a modal does not scroll — the browser inerts it, but does not
|
|
3821
|
+
freeze it. Scoped to this class, so a host's own dialogs keep their behaviour. */
|
|
3822
|
+
body:has(.aparte-dialog[open]) {
|
|
3823
|
+
overflow: hidden;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
/* A phone: every size is the whole screen — when shown MODALLY. `:modal` and not the
|
|
3827
|
+
bare class: a dialog opened with `show()` sits in the flow of its container (the
|
|
3828
|
+
kit's own preview does that), and a sheet the height of the viewport inside a
|
|
3829
|
+
narrow column was the wrong reading of a rule meant for a phone. */
|
|
3830
|
+
@media (max-width: 30rem) {
|
|
3831
|
+
.aparte-dialog:modal {
|
|
3832
|
+
width: 100%;
|
|
3833
|
+
max-width: 100%;
|
|
3834
|
+
height: 100dvh;
|
|
3835
|
+
max-height: 100dvh;
|
|
3836
|
+
margin: 0;
|
|
3837
|
+
border: 0;
|
|
3838
|
+
border-radius: 0;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
/* Open and close with a short fade and lift; the reader who prefers reduced motion
|
|
3843
|
+
gets the box where it lands. */
|
|
3844
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
3845
|
+
.aparte-dialog[open] {
|
|
3846
|
+
animation: aparte-dialog-in var(--aparte-duration-slow) var(--aparte-ease);
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
.aparte-dialog[open]::backdrop {
|
|
3850
|
+
animation: aparte-dialog-backdrop-in var(--aparte-duration-slow) var(--aparte-ease);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
@keyframes aparte-dialog-in {
|
|
3855
|
+
from {
|
|
3856
|
+
opacity: 0;
|
|
3857
|
+
transform: translateY(var(--aparte-slide-distance));
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
@keyframes aparte-dialog-backdrop-in {
|
|
3862
|
+
from {
|
|
3863
|
+
opacity: 0;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3603
3866
|
/* =========================================
|
|
3604
3867
|
aparte-tooltip — a short label, with an arrow
|
|
3605
3868
|
=========================================
|
|
@@ -4122,6 +4385,11 @@ aparte-chat-viewport {
|
|
|
4122
4385
|
overscroll-behavior: contain;
|
|
4123
4386
|
scrollbar-width: thin;
|
|
4124
4387
|
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
4388
|
+
/* The gutter is reserved on BOTH edges whether or not the bar is showing, so the
|
|
4389
|
+
centred column does not shift by half a scrollbar the moment the first reply
|
|
4390
|
+
overflows — the same rule the reference kits' scroll surfaces carry. Symmetric,
|
|
4391
|
+
because the column is centred: a gutter on one side alone moves it. */
|
|
4392
|
+
scrollbar-gutter: stable both-edges;
|
|
4125
4393
|
}
|
|
4126
4394
|
|
|
4127
4395
|
.aparte-viewport-container::-webkit-scrollbar {
|
|
@@ -4186,6 +4454,15 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4186
4454
|
so it can't collide with the framework's children). */
|
|
4187
4455
|
aparte-chat-viewport.aparte-viewport--framework,
|
|
4188
4456
|
aparte-chat-viewport[framework-managed] {
|
|
4457
|
+
/* The base rule gives this element `width: 100%`, and core ships no global
|
|
4458
|
+
border-box reset — every `box-sizing` in these sheets is per-element, on purpose.
|
|
4459
|
+
So the padding below was ADDED to 100% and the host overflowed its own chat by
|
|
4460
|
+
32px, which `aparte-chat`'s `overflow: hidden` then clipped: measured on a 1500px
|
|
4461
|
+
chat, the host was 1532 wide and hung 32px past the end edge. An app with its own
|
|
4462
|
+
`* { box-sizing: border-box }` never saw it, which is exactly why it survived —
|
|
4463
|
+
every example in this repo has one. The composer cannot share a column with a
|
|
4464
|
+
transcript that is off-centre, so it is fixed here rather than compensated there. */
|
|
4465
|
+
box-sizing: border-box;
|
|
4189
4466
|
overflow-y: auto;
|
|
4190
4467
|
overflow-x: hidden;
|
|
4191
4468
|
overscroll-behavior: contain;
|
|
@@ -4196,6 +4473,9 @@ aparte-chat-viewport[framework-managed] {
|
|
|
4196
4473
|
padding-bottom: calc(var(--aparte-viewport-padding) + var(--aparte-fw-spacer, 0px));
|
|
4197
4474
|
scrollbar-width: thin;
|
|
4198
4475
|
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
4476
|
+
/* Same gutter rule as `.aparte-viewport-container` above: the host is the scroll
|
|
4477
|
+
surface here, so it is the host that reserves it. */
|
|
4478
|
+
scrollbar-gutter: stable both-edges;
|
|
4199
4479
|
}
|
|
4200
4480
|
|
|
4201
4481
|
/* The framework wrapper renders bubbles as DIRECT flex children of the viewport
|
|
@@ -4631,6 +4911,31 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
4631
4911
|
.aparte-message[data-empty] {
|
|
4632
4912
|
display: none;
|
|
4633
4913
|
}
|
|
4914
|
+
|
|
4915
|
+
/* ─── The compaction notice ────────────────────────────────────────────────────
|
|
4916
|
+
A message with `compaction: true` — the summary `compact()` injected in place of
|
|
4917
|
+
the turns it replaced — is a notice, not a reply: centred, muted, without avatar,
|
|
4918
|
+
name, time or actions. Its role on the wire is `user` (context handed to the
|
|
4919
|
+
model), and the bubble would otherwise draw it as something the user said. */
|
|
4920
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-avatar,
|
|
4921
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-header,
|
|
4922
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-footer,
|
|
4923
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-action-bar,
|
|
4924
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-branch-picker {
|
|
4925
|
+
display: none;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-message {
|
|
4929
|
+
justify-content: center;
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-message-content {
|
|
4933
|
+
max-width: 100%;
|
|
4934
|
+
background: var(--aparte-surface-2);
|
|
4935
|
+
color: var(--aparte-text-muted);
|
|
4936
|
+
border: var(--aparte-border-width) dashed var(--aparte-border);
|
|
4937
|
+
font-size: var(--aparte-font-size-sm);
|
|
4938
|
+
}
|
|
4634
4939
|
/*
|
|
4635
4940
|
* aparté — The composer and everything it holds: editor, send, attachments, model picker.
|
|
4636
4941
|
*
|
|
@@ -4715,7 +5020,14 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4715
5020
|
border-radius: 50%;
|
|
4716
5021
|
opacity: 0;
|
|
4717
5022
|
}
|
|
4718
|
-
|
|
5023
|
+
/* Hover is not the only way in. The ✕ is the ONLY way to drop a pending attachment,
|
|
5024
|
+
and at `opacity: 0` it was invisible to a keyboard user standing on it — a focus
|
|
5025
|
+
ring drawn around nothing. `:focus-within` on the tile is the same pair the message
|
|
5026
|
+
action bar (bubble.css) and the conversation row (conversation.css) already use;
|
|
5027
|
+
this one had been written with the hover half alone. The touch half is in
|
|
5028
|
+
responsive.css, beside the row's ⋯, for the same reason: a finger cannot hover. */
|
|
5029
|
+
.aparte-thumb:hover .aparte-thumb__remove,
|
|
5030
|
+
.aparte-thumb:focus-within .aparte-thumb__remove {
|
|
4719
5031
|
opacity: 1;
|
|
4720
5032
|
}
|
|
4721
5033
|
.aparte-thumb__remove:hover {
|
|
@@ -4956,6 +5268,34 @@ aparte-composer {
|
|
|
4956
5268
|
display: block;
|
|
4957
5269
|
/* Container-query root — see aparte-chat-viewport. */
|
|
4958
5270
|
container-type: inline-size;
|
|
5271
|
+
/* The transcript's own inline gutter, read from the token that produces it.
|
|
5272
|
+
`.aparte-composer-shell` and `.aparte-message` both cap at
|
|
5273
|
+
`--aparte-message-max-width` and centre with `margin: 0 auto`, so on a container
|
|
5274
|
+
WIDER than the cap they lined up by construction. Below it the cap stops applying,
|
|
5275
|
+
each becomes 100% of its own parent — and this element had no padding, so the
|
|
5276
|
+
composer went flush to the chat's edges while the transcript kept its inset.
|
|
5277
|
+
Measured at a 512px chat: message column 26/26, composer 0/0. Every chat narrower
|
|
5278
|
+
than 800px was hit — a phone, an embedded widget, either pane of `<aparte-split>`,
|
|
5279
|
+
an app shell whose docked sidebar leaves the chat narrow on a wide window.
|
|
5280
|
+
|
|
5281
|
+
INLINE ONLY, and that is not shorthand-avoidance: the block axis is already spaced
|
|
5282
|
+
deliberately, 16px above from the viewport and 16px below from
|
|
5283
|
+
`--aparte-chat-bottom-gap` (shell.css), which were tuned to match. The `padding`
|
|
5284
|
+
shorthand would add a second 16px to each.
|
|
5285
|
+
|
|
5286
|
+
No 520px step to follow the transcript's, and it is not an oversight: a container
|
|
5287
|
+
query cannot match its own container, and this element IS one (the line above).
|
|
5288
|
+
Nothing above it is a container either, and `aparte-chat` may never become one —
|
|
5289
|
+
it would then be the containing block of the `position: fixed` select dropdown
|
|
5290
|
+
that exists to escape `overflow: hidden` (select.css, and the same warning in
|
|
5291
|
+
split.css). So below 520px the transcript tightens to 8px and this stays at 16.
|
|
5292
|
+
A flat 16 was measured against a step: the step wins for vanilla at phone width
|
|
5293
|
+
and LOSES for framework-managed, which is the majority path, because the
|
|
5294
|
+
transcript's own step targets a class framework mode never builds. Flat is a
|
|
5295
|
+
uniform 10px worst case in both modes — which is the scrollbar gutter the
|
|
5296
|
+
transcript reserves and the composer cannot, and which is 0 on the overlay
|
|
5297
|
+
scrollbars every phone has. */
|
|
5298
|
+
padding-inline: var(--aparte-viewport-padding);
|
|
4959
5299
|
}
|
|
4960
5300
|
|
|
4961
5301
|
aparte-composer-input {
|
|
@@ -5290,7 +5630,16 @@ aparte-composer-cancel .aparte-cc-button:hover {
|
|
|
5290
5630
|
reply text beside it for attention it did not deserve. */
|
|
5291
5631
|
.aparte-tool-summary {
|
|
5292
5632
|
display: flex; align-items: center; gap: var(--aparte-space-4);
|
|
5293
|
-
|
|
5633
|
+
/* The negative margin is what puts the row's CONTENT on the message column rather
|
|
5634
|
+
than one padding in from it — the padding here is the hover surface, not the
|
|
5635
|
+
column. So it has to be given back on BOTH sides. It used to be
|
|
5636
|
+
`margin-inline-start` alone: the name, the icon and the chevron landed on the
|
|
5637
|
+
column, and the trailing state stopped `--aparte-space-3` short of the right edge
|
|
5638
|
+
every line of prose reaches. Measured at a 512px chat: text L26/R26, the state's
|
|
5639
|
+
right edge at R32 — six pixels, on the one line of a turn whose whole job is to
|
|
5640
|
+
read as a quiet aside beside the text. `margin-inline` puts it at R26 and makes
|
|
5641
|
+
the hover surface symmetric with it. */
|
|
5642
|
+
padding: var(--aparte-space-2) var(--aparte-space-3); margin-inline: calc(var(--aparte-space-3) * -1);
|
|
5294
5643
|
border-radius: var(--aparte-tool-row-radius);
|
|
5295
5644
|
cursor: pointer; list-style: none;
|
|
5296
5645
|
}
|
|
@@ -5525,256 +5874,6 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5525
5874
|
border-collapse: collapse;
|
|
5526
5875
|
}
|
|
5527
5876
|
|
|
5528
|
-
/*
|
|
5529
|
-
* aparté — The artifact card and the artifact file preview.
|
|
5530
|
-
*
|
|
5531
|
-
* One of the sheets split out of aparte.css. Order matters: src/index.ts imports
|
|
5532
|
-
* them in the order listed there, and check:derived-vars reads them concatenated in
|
|
5533
|
-
* that same order, the way a browser does.
|
|
5534
|
-
*/
|
|
5535
|
-
/* Artifact card */
|
|
5536
|
-
/* The shell is `.aparte-card`; what stays here is what a card in a TRANSCRIPT needs
|
|
5537
|
-
and a card in general does not — the vertical rhythm between messages, and `font:
|
|
5538
|
-
inherit` so a segment never picks up a host's form-control font. */
|
|
5539
|
-
.aparte-segment-artifact-card,
|
|
5540
|
-
.aparte-segment-artifact-file {
|
|
5541
|
-
margin: var(--aparte-space-4) 0;
|
|
5542
|
-
font: inherit;
|
|
5543
|
-
}
|
|
5544
|
-
.aparte-art-card__header {
|
|
5545
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
5546
|
-
padding: var(--aparte-space-4) var(--aparte-space-5);
|
|
5547
|
-
border-bottom: var(--aparte-border-width) solid var(--aparte-border);
|
|
5548
|
-
background: var(--aparte-surface-2);
|
|
5549
|
-
min-height: var(--aparte-art-card-header-min-height);
|
|
5550
|
-
}
|
|
5551
|
-
.aparte-art-card__title-block { display: flex; align-items: center; gap: var(--aparte-space-4); min-width: 0; }
|
|
5552
|
-
/* An outline `.aparte-badge`. What stays is the lettering: a language tag reads as a
|
|
5553
|
-
code marker, so it is upper-cased and tracked out — which a badge in general is not. */
|
|
5554
|
-
.aparte-art-card__kind {
|
|
5555
|
-
text-transform: uppercase;
|
|
5556
|
-
letter-spacing: 0.04em;
|
|
5557
|
-
--aparte-badge-font-size: var(--aparte-font-size-xs);
|
|
5558
|
-
--aparte-badge-radius: var(--aparte-radius-sm);
|
|
5559
|
-
}
|
|
5560
|
-
.aparte-art-card__title {
|
|
5561
|
-
font-size: var(--aparte-font-size-lg); font-weight: var(--aparte-font-weight-medium);
|
|
5562
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
5563
|
-
}
|
|
5564
|
-
/* The geometry is `.aparte-dot`; a streaming card breathes on the accent rather than
|
|
5565
|
-
the status colour, and larger, because it marks a whole card and not a line. */
|
|
5566
|
-
.aparte-art-card__pulse {
|
|
5567
|
-
--aparte-status-dot-size: var(--aparte-art-card-pulse-size);
|
|
5568
|
-
--aparte-status-color: var(--aparte-accent);
|
|
5569
|
-
}
|
|
5570
|
-
.aparte-art-card__actions { display: flex; gap: var(--aparte-space-2); }
|
|
5571
|
-
.aparte-art-card__btn {
|
|
5572
|
-
/* Same as .aparte-action-btn: declare the size, let the recipe draw. */
|
|
5573
|
-
--aparte-btn-size: var(--aparte-art-card-btn-size);
|
|
5574
|
-
}
|
|
5575
|
-
.aparte-art-card__btn:hover:not(:disabled) {
|
|
5576
|
-
background: var(--aparte-surface-hover);
|
|
5577
|
-
border-color: var(--aparte-border);
|
|
5578
|
-
color: var(--aparte-text);
|
|
5579
|
-
}
|
|
5580
|
-
.aparte-art-card__btn:disabled { opacity: var(--aparte-disabled-opacity); cursor: not-allowed; }
|
|
5581
|
-
.aparte-art-card__tabs {
|
|
5582
|
-
/* justify-content and the padding are DECLARED, not left to a default.
|
|
5583
|
-
Core is light DOM on purpose - no shadow root, no ::part(), any selector
|
|
5584
|
-
reaches in - and the corollary is that a component must state what its
|
|
5585
|
-
layout depends on, because an undeclared property has nothing to override
|
|
5586
|
-
a host rule with. A consuming page with a bare nav selector setting
|
|
5587
|
-
justify-content: space-between and padding-top (this library's own docs
|
|
5588
|
-
site had exactly that) otherwise pushes these two tabs to opposite ends of
|
|
5589
|
-
the card and pads the row out.
|
|
5590
|
-
|
|
5591
|
-
flex-end, and Code first in the DOM. The card OPENS on Code - mounting the
|
|
5592
|
-
preview would execute model-authored code with no gesture (ratified
|
|
5593
|
-
decision #8) - and a selected tab sitting second reads backwards. Right,
|
|
5594
|
-
because the header above puts the artifact's identity on the left and its
|
|
5595
|
-
copy/download buttons on the right, so this keeps every control in one
|
|
5596
|
-
column. DOM order is also keyboard order, so the tab a reader lands on
|
|
5597
|
-
first is the one already showing. */
|
|
5598
|
-
display: flex; justify-content: flex-end; align-items: stretch; gap: var(--aparte-space-1);
|
|
5599
|
-
padding: var(--aparte-space-2) var(--aparte-space-4) 0;
|
|
5600
|
-
border-bottom: var(--aparte-border-width) solid var(--aparte-border);
|
|
5601
|
-
background: var(--aparte-surface-2);
|
|
5602
|
-
}
|
|
5603
|
-
/* The tabs are `.aparte-tabs--underline`, like the elicitation panel's steps — one
|
|
5604
|
-
tab in this library, not one per component. Only the row's own business stays. */
|
|
5605
|
-
.aparte-art-card__tabs button { font-size: var(--aparte-font-size-sm); }
|
|
5606
|
-
/* The card's heights, as variables with ONE owner each.
|
|
5607
|
-
They were four hardcoded numbers in the only part of this card that did not
|
|
5608
|
-
use a variable - everything else here already reads var(--aparte-code-bg) and
|
|
5609
|
-
friends - and two of them had to agree while a third contradicted a fourth:
|
|
5610
|
-
the code pane repeated the body's 600px, and the "press Preview" placeholder
|
|
5611
|
-
was 120px tall inside a body whose min-height said 80, so that minimum applied
|
|
5612
|
-
to nothing.
|
|
5613
|
-
The frame stays a FIXED height rather than an aspect ratio, which is what
|
|
5614
|
-
embeds of arbitrary HTML actually do - CodeSandbox documents 500px, StackBlitz
|
|
5615
|
-
takes a height parameter - because a frame with an opaque origin cannot be
|
|
5616
|
-
measured and a 16/10 ratio on a wide card is enormous. The vh cap is the part
|
|
5617
|
-
that was missing: a fixed 480px should not own a phone screen.
|
|
5618
|
-
Each default lives in its read, as var(--x, literal), the way every other
|
|
5619
|
-
value in this file already does - not in a declaration block on top of the
|
|
5620
|
-
fallbacks, which would be two owners of one number again. It also means the
|
|
5621
|
-
docs' CSS-variable generator finds them: its sweep looks for reads that
|
|
5622
|
-
carry a fallback, so a read without one is a public knob nobody documents. */
|
|
5623
|
-
.aparte-art-card__body {
|
|
5624
|
-
position: relative;
|
|
5625
|
-
/* The placeholder is the tallest thing this box can hold while empty, so it
|
|
5626
|
-
IS the minimum - one number instead of two that disagreed. */
|
|
5627
|
-
min-height: var(--aparte-artifact-pending-height, 120px);
|
|
5628
|
-
max-height: var(--aparte-artifact-body-max, 600px);
|
|
5629
|
-
overflow: hidden;
|
|
5630
|
-
}
|
|
5631
|
-
.aparte-art-card__pane { display: none; height: 100%; }
|
|
5632
|
-
.aparte-segment-artifact-card[data-tab="code"] .aparte-art-card__pane[data-pane="code"] { display: block; }
|
|
5633
|
-
.aparte-segment-artifact-card[data-tab="preview"] .aparte-art-card__pane[data-pane="preview"] { display: block; }
|
|
5634
|
-
.aparte-art-card__pane[data-pane="code"] {
|
|
5635
|
-
/* The body already caps this; repeating the number was the second owner. */
|
|
5636
|
-
max-height: var(--aparte-artifact-body-max, 600px); overflow: auto;
|
|
5637
|
-
}
|
|
5638
|
-
.aparte-art-card__frame {
|
|
5639
|
-
display: block;
|
|
5640
|
-
width: 100%;
|
|
5641
|
-
height: min(var(--aparte-artifact-frame-height, 480px), var(--aparte-artifact-frame-max, 70vh));
|
|
5642
|
-
border: 0;
|
|
5643
|
-
background: var(--aparte-art-paper-bg);
|
|
5644
|
-
}
|
|
5645
|
-
.aparte-art-card__pending {
|
|
5646
|
-
display: flex; align-items: center; justify-content: center;
|
|
5647
|
-
height: var(--aparte-artifact-pending-height, 120px);
|
|
5648
|
-
color: var(--aparte-text-muted);
|
|
5649
|
-
font-size: var(--aparte-font-size-lg);
|
|
5650
|
-
font-style: italic;
|
|
5651
|
-
}
|
|
5652
|
-
/* ── Binary file artifact (xlsx/pdf/docx) ──────────────────── */
|
|
5653
|
-
.aparte-art-file__card {
|
|
5654
|
-
display: flex; align-items: center; gap: var(--aparte-space-6);
|
|
5655
|
-
padding: var(--aparte-space-6) var(--aparte-space-7);
|
|
5656
|
-
background: var(--aparte-surface-2);
|
|
5657
|
-
border-bottom: var(--aparte-border-width) solid var(--aparte-border);
|
|
5658
|
-
}
|
|
5659
|
-
.aparte-art-file__body {
|
|
5660
|
-
position: relative;
|
|
5661
|
-
}
|
|
5662
|
-
.aparte-art-file__code-pane {
|
|
5663
|
-
max-height: var(--aparte-artifact-file-code-max, 360px); overflow: auto;
|
|
5664
|
-
background: var(--aparte-code-bg);
|
|
5665
|
-
}
|
|
5666
|
-
.aparte-art-file__preview-pane {
|
|
5667
|
-
max-height: var(--aparte-artifact-file-preview-max, 460px);
|
|
5668
|
-
overflow: auto;
|
|
5669
|
-
background: var(--aparte-art-paper-bg);
|
|
5670
|
-
/* Preview is a document view — force light scheme regardless of
|
|
5671
|
-
the app theme, with a dark text colour so cells stay readable. */
|
|
5672
|
-
color: var(--aparte-art-paper-text);
|
|
5673
|
-
}
|
|
5674
|
-
.aparte-art-file__icon {
|
|
5675
|
-
width: var(--aparte-art-file-icon-size); height: var(--aparte-art-file-icon-size);
|
|
5676
|
-
border-radius: var(--aparte-radius-lg);
|
|
5677
|
-
display: flex; align-items: center; justify-content: center;
|
|
5678
|
-
background: var(--aparte-art-file-icon-bg);
|
|
5679
|
-
color: var(--aparte-art-file-icon-color);
|
|
5680
|
-
font-weight: var(--aparte-font-weight-bold);
|
|
5681
|
-
font-size: var(--aparte-font-size-sm);
|
|
5682
|
-
letter-spacing: 0.04em;
|
|
5683
|
-
flex-shrink: 0;
|
|
5684
|
-
}
|
|
5685
|
-
.aparte-art-file__icon[data-kind="pdf"] { background: var(--aparte-art-file-icon-bg-pdf); }
|
|
5686
|
-
.aparte-art-file__icon[data-kind="docx"] { background: var(--aparte-art-file-icon-bg-docx); }
|
|
5687
|
-
.aparte-art-file__meta { flex: 1 1 auto; min-width: 0; }
|
|
5688
|
-
.aparte-art-file__meta-name {
|
|
5689
|
-
font-weight: var(--aparte-font-weight-semibold); font-size: var(--aparte-font-size-base);
|
|
5690
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
5691
|
-
}
|
|
5692
|
-
.aparte-art-file__meta-sub {
|
|
5693
|
-
font-size: var(--aparte-font-size-sm);
|
|
5694
|
-
color: var(--aparte-text-muted);
|
|
5695
|
-
}
|
|
5696
|
-
.aparte-art-file__actions { display: flex; gap: var(--aparte-space-3); flex-shrink: 0; }
|
|
5697
|
-
/* The chrome is `.aparte-btn`; every one of these is a `--primary --solid`, so the
|
|
5698
|
-
fill comes from the recipe. Its own measurements stay: a labelled button in a card
|
|
5699
|
-
is roomier than an icon in a row. */
|
|
5700
|
-
.aparte-art-file__btn {
|
|
5701
|
-
padding: var(--aparte-space-3) var(--aparte-space-5);
|
|
5702
|
-
font-size: var(--aparte-font-size-md);
|
|
5703
|
-
}
|
|
5704
|
-
/* NOTHING here repaints the button, and the comment above is why. These seven lines
|
|
5705
|
-
re-declared `background`, `color`, `border-color` and a hover the recipe already
|
|
5706
|
-
paints — a migration leftover that contradicted the sentence directly above it.
|
|
5707
|
-
|
|
5708
|
-
Five of them were inert duplicates. The sixth was not: `color: var(--aparte-text-inverse)`
|
|
5709
|
-
overrode `--aparte-btn-on-intent`, which the recipe derives from the fill. Measured in a
|
|
5710
|
-
browser on the built stylesheet: 3.54:1 in the light theme against the recipe's 5.27 —
|
|
5711
|
-
an AA failure on a visible button label, in the default theme. It was also the last
|
|
5712
|
-
place in `styles/` forcing `--aparte-text-inverse` as ink on a coloured fill; badge and
|
|
5713
|
-
field had already stopped.
|
|
5714
|
-
|
|
5715
|
-
The consumer cost was the sharper one: the one-attribute rebrand this library documents
|
|
5716
|
-
re-derives the ink on every other solid-primary button and, here alone, kept a token
|
|
5717
|
-
bound to core's own palette. `.aparte-art-file__btn:hover` went with them — at 0,2,0 it
|
|
5718
|
-
was always beaten by `.aparte-btn--solid:hover:not(:disabled)` at 0,3,0, so it never
|
|
5719
|
-
applied either. */
|
|
5720
|
-
/* Hide the download button until the sandbox has produced the buffer.
|
|
5721
|
-
Avoids confusing the user with a disabled-but-styled-primary button
|
|
5722
|
-
during streaming / compiling. */
|
|
5723
|
-
.aparte-segment-artifact-file:not([data-state="ready"]) .aparte-art-file__btn[data-action="download"] {
|
|
5724
|
-
display: none;
|
|
5725
|
-
}
|
|
5726
|
-
.aparte-art-file__preview-pane th,
|
|
5727
|
-
.aparte-art-file__preview-pane td {
|
|
5728
|
-
border: var(--aparte-border-width) solid var(--aparte-art-paper-border);
|
|
5729
|
-
padding: var(--aparte-space-3) var(--aparte-space-5);
|
|
5730
|
-
text-align: left;
|
|
5731
|
-
white-space: nowrap;
|
|
5732
|
-
color: var(--aparte-art-paper-text);
|
|
5733
|
-
background: var(--aparte-art-paper-bg);
|
|
5734
|
-
}
|
|
5735
|
-
.aparte-art-file__preview-pane tr:nth-child(odd) td { background: var(--aparte-art-paper-row-alt); }
|
|
5736
|
-
.aparte-art-file__preview-pane tr:first-child td {
|
|
5737
|
-
background: var(--aparte-art-paper-head-bg);
|
|
5738
|
-
font-weight: var(--aparte-font-weight-semibold);
|
|
5739
|
-
position: sticky; top: 0;
|
|
5740
|
-
color: var(--aparte-art-paper-head-text);
|
|
5741
|
-
}
|
|
5742
|
-
.aparte-art-file__preview-empty {
|
|
5743
|
-
padding: var(--aparte-art-file-preview-padding); text-align: center;
|
|
5744
|
-
color: var(--aparte-text-muted);
|
|
5745
|
-
font-size: var(--aparte-font-size-lg); font-style: italic;
|
|
5746
|
-
}
|
|
5747
|
-
.aparte-segment-artifact-file[data-state="error"] .aparte-art-file__icon {
|
|
5748
|
-
background: var(--aparte-art-file-icon-bg-pdf);
|
|
5749
|
-
}
|
|
5750
|
-
.aparte-art-file__error {
|
|
5751
|
-
padding: var(--aparte-space-8) var(--aparte-art-file-error-padding-inline);
|
|
5752
|
-
background: var(--aparte-error-bg);
|
|
5753
|
-
border-top: var(--aparte-border-width) solid var(--aparte-error-border);
|
|
5754
|
-
color: var(--aparte-text);
|
|
5755
|
-
}
|
|
5756
|
-
.aparte-art-file__error-title {
|
|
5757
|
-
font-weight: var(--aparte-font-weight-semibold);
|
|
5758
|
-
font-size: var(--aparte-font-size-lg);
|
|
5759
|
-
margin-bottom: var(--aparte-space-3);
|
|
5760
|
-
color: var(--aparte-error-title);
|
|
5761
|
-
}
|
|
5762
|
-
/* A computed value shown back, which is `.aparte-output`. What stays is that this
|
|
5763
|
-
one is an ERROR — tinted text on its own quiet ground — and that a raw message has
|
|
5764
|
-
no spaces to wrap on, so it must be allowed to break mid-word. */
|
|
5765
|
-
.aparte-art-file__error-msg {
|
|
5766
|
-
display: block;
|
|
5767
|
-
padding: var(--aparte-space-3) var(--aparte-space-5);
|
|
5768
|
-
margin-bottom: var(--aparte-space-4);
|
|
5769
|
-
background: var(--aparte-art-file-error-msg-bg);
|
|
5770
|
-
color: var(--aparte-error-text);
|
|
5771
|
-
word-break: break-word;
|
|
5772
|
-
}
|
|
5773
|
-
.aparte-art-file__error-hint {
|
|
5774
|
-
font-size: var(--aparte-font-size-sm);
|
|
5775
|
-
color: var(--aparte-text-muted);
|
|
5776
|
-
font-style: italic;
|
|
5777
|
-
}
|
|
5778
5877
|
/*
|
|
5779
5878
|
* aparté — The elicitation panel and the approval gate.
|
|
5780
5879
|
*
|
|
@@ -6023,6 +6122,43 @@ textarea.aparte-elic-text {
|
|
|
6023
6122
|
recipe. See `buildApprovalPanel` for why colour lost: an alert's vocabulary on a
|
|
6024
6123
|
control, and 2.19:1 on the dark palette when it was tried as a fill instead. */
|
|
6025
6124
|
|
|
6125
|
+
/* What is being decided, between the question and the options.
|
|
6126
|
+
|
|
6127
|
+
The panel used to carry the tool's NAME and nothing else, so the arguments — the
|
|
6128
|
+
whole of what a person approves — were only in the transcript row, behind a closed
|
|
6129
|
+
disclosure. Capped and scrollable rather than unbounded: the panel itself is capped
|
|
6130
|
+
at half the viewport, and a plan or a diff arriving as a tool argument would
|
|
6131
|
+
otherwise push the Approve button off the bottom of the surface it is asked on.
|
|
6132
|
+
|
|
6133
|
+
Monospace, because it is JSON: the model's own bytes, not prose about them. */
|
|
6134
|
+
.aparte-approval-args-label {
|
|
6135
|
+
margin: var(--aparte-space-3) var(--aparte-space-3) 0;
|
|
6136
|
+
font-size: var(--aparte-font-size-xs);
|
|
6137
|
+
text-transform: uppercase;
|
|
6138
|
+
letter-spacing: 0.04em;
|
|
6139
|
+
color: var(--aparte-text-muted);
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6142
|
+
.aparte-approval-args {
|
|
6143
|
+
margin: var(--aparte-space-1) var(--aparte-space-3) 0;
|
|
6144
|
+
padding: var(--aparte-space-3);
|
|
6145
|
+
max-height: var(--aparte-approval-args-max-height, 8.5rem);
|
|
6146
|
+
overflow: auto;
|
|
6147
|
+
font-family: var(--aparte-code-font-family);
|
|
6148
|
+
font-size: var(--aparte-approval-args-size, var(--aparte-font-size-sm));
|
|
6149
|
+
line-height: var(--aparte-line-height-snug);
|
|
6150
|
+
color: var(--aparte-text);
|
|
6151
|
+
background: var(--aparte-surface-2);
|
|
6152
|
+
border-radius: var(--aparte-radius-sm);
|
|
6153
|
+
white-space: pre-wrap;
|
|
6154
|
+
overflow-wrap: anywhere;
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
.aparte-approval-args:focus-visible {
|
|
6158
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
6159
|
+
outline-offset: 1px;
|
|
6160
|
+
}
|
|
6161
|
+
|
|
6026
6162
|
/* The refusal's reason, in the user's words. Quiet by default and one line tall: it is
|
|
6027
6163
|
the exception, not the expected act, and a box sized for a paragraph invites one. */
|
|
6028
6164
|
.aparte-approval-instruction {
|
|
@@ -6088,11 +6224,27 @@ textarea.aparte-elic-text {
|
|
|
6088
6224
|
margin-bottom: calc(-1 * var(--aparte-border-width));
|
|
6089
6225
|
}
|
|
6090
6226
|
|
|
6091
|
-
/* Answered: readable, but it is not where you are
|
|
6227
|
+
/* Answered: readable, but it is not where you are — and marked, because a colour is
|
|
6228
|
+
what the current chip has too. The mark is the `check` glyph, sized by the chip. */
|
|
6092
6229
|
.aparte-elic-step[data-answered] {
|
|
6093
6230
|
color: var(--aparte-text);
|
|
6094
6231
|
}
|
|
6095
6232
|
|
|
6233
|
+
.aparte-elic-step__mark {
|
|
6234
|
+
display: inline-flex;
|
|
6235
|
+
align-items: center;
|
|
6236
|
+
margin-inline-end: var(--aparte-space-1);
|
|
6237
|
+
color: var(--aparte-success);
|
|
6238
|
+
--aparte-icon-size: 0.85em;
|
|
6239
|
+
}
|
|
6240
|
+
|
|
6241
|
+
/* The tag beside a recommended option's label: the recipe draws it, this only keeps it
|
|
6242
|
+
from stretching the title line. */
|
|
6243
|
+
.aparte-elic-option-badge {
|
|
6244
|
+
vertical-align: middle;
|
|
6245
|
+
margin-inline-start: var(--aparte-space-1);
|
|
6246
|
+
}
|
|
6247
|
+
|
|
6096
6248
|
.aparte-elic-step[aria-selected='true'] {
|
|
6097
6249
|
color: var(--aparte-text);
|
|
6098
6250
|
font-weight: var(--aparte-font-weight-semibold);
|
|
@@ -6110,16 +6262,37 @@ textarea.aparte-elic-text {
|
|
|
6110
6262
|
* them in the order listed there, and check:derived-vars reads them concatenated in
|
|
6111
6263
|
* that same order, the way a browser does.
|
|
6112
6264
|
*/
|
|
6265
|
+
|
|
6266
|
+
/* A date group: its heading wears the menu's label recipe (`.aparte-menu__label`),
|
|
6267
|
+
because that is what a heading over a list of rows is. Only the rhythm is its own. */
|
|
6268
|
+
.aparte-conv-group {
|
|
6269
|
+
display: flex;
|
|
6270
|
+
flex-direction: column;
|
|
6271
|
+
gap: var(--aparte-conv-list-gap);
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6274
|
+
.aparte-conv-group + .aparte-conv-group {
|
|
6275
|
+
margin-block-start: var(--aparte-space-3);
|
|
6276
|
+
}
|
|
6277
|
+
|
|
6113
6278
|
/* A row in a list, which is what `.aparte-menu__item` is. What stays is what belongs
|
|
6114
6279
|
to a CONVERSATION row: it truncates, so it needs `min-width: 0`; and its own
|
|
6115
|
-
measurements, because a sidebar row is tighter than a dropdown row.
|
|
6280
|
+
measurements, because a sidebar row is tighter than a dropdown row.
|
|
6281
|
+
|
|
6282
|
+
The row is a flex box around two real buttons — the title, which selects, and the
|
|
6283
|
+
`⋯`. The padding sits on the title button, not the row, so that the button's focus
|
|
6284
|
+
ring is the row's shape and a click on the row's inner margin lands on the button
|
|
6285
|
+
rather than on nothing. */
|
|
6116
6286
|
.aparte-conv-item {
|
|
6287
|
+
position: relative;
|
|
6117
6288
|
min-width: 0;
|
|
6118
6289
|
gap: var(--aparte-conv-item-gap);
|
|
6119
|
-
padding:
|
|
6290
|
+
padding: 0;
|
|
6291
|
+
padding-inline-end: var(--aparte-space-2);
|
|
6120
6292
|
border-radius: var(--aparte-conv-item-radius);
|
|
6121
6293
|
color: var(--aparte-conv-item-color);
|
|
6122
6294
|
font-size: var(--aparte-conv-item-font-size);
|
|
6295
|
+
cursor: default;
|
|
6123
6296
|
}
|
|
6124
6297
|
|
|
6125
6298
|
/* Hover moves the ground UP, so the label has to move with it.
|
|
@@ -6134,31 +6307,36 @@ textarea.aparte-elic-text {
|
|
|
6134
6307
|
color: var(--aparte-conv-item-color-active);
|
|
6135
6308
|
}
|
|
6136
6309
|
|
|
6137
|
-
.aparte-conv-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6310
|
+
.aparte-conv-item__select {
|
|
6311
|
+
appearance: none;
|
|
6312
|
+
-webkit-appearance: none;
|
|
6313
|
+
display: flex;
|
|
6314
|
+
align-items: center;
|
|
6315
|
+
flex: 1;
|
|
6316
|
+
min-width: 0;
|
|
6317
|
+
padding: var(--aparte-conv-item-padding);
|
|
6318
|
+
border: none;
|
|
6319
|
+
border-radius: inherit;
|
|
6320
|
+
background: transparent;
|
|
6321
|
+
color: inherit;
|
|
6322
|
+
font: inherit;
|
|
6323
|
+
text-align: start;
|
|
6324
|
+
cursor: pointer;
|
|
6144
6325
|
}
|
|
6145
6326
|
|
|
6146
|
-
|
|
6147
|
-
|
|
6327
|
+
/* Inside the edge: the list clips horizontally (`overflow-x: hidden` in base.css), and
|
|
6328
|
+
a ring drawn outside the row was cut on both sides. */
|
|
6329
|
+
.aparte-conv-item__select:focus-visible {
|
|
6330
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-primary);
|
|
6331
|
+
outline-offset: calc(-1 * var(--aparte-focus-outline-width));
|
|
6148
6332
|
}
|
|
6149
6333
|
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
.aparte-conv-item__archive:focus-visible,
|
|
6158
|
-
.aparte-conv-item__delete:focus-visible {
|
|
6159
|
-
opacity: 1;
|
|
6160
|
-
outline: var(--aparte-focus-outline-width) solid var(--aparte-primary);
|
|
6161
|
-
outline-offset: var(--aparte-focus-outline-offset);
|
|
6334
|
+
.aparte-conv-item__title {
|
|
6335
|
+
flex: 1;
|
|
6336
|
+
white-space: nowrap;
|
|
6337
|
+
overflow: hidden;
|
|
6338
|
+
text-overflow: ellipsis;
|
|
6339
|
+
min-width: 0;
|
|
6162
6340
|
}
|
|
6163
6341
|
|
|
6164
6342
|
/* The conversation you are in wears the mark's bar (display/mark.css) on its start
|
|
@@ -6166,7 +6344,6 @@ textarea.aparte-elic-text {
|
|
|
6166
6344
|
a checked field choice carry. Its ground keeps the list's own tokens: a navigation
|
|
6167
6345
|
list is read for a long time, and the mark's tint on every visit was too loud. */
|
|
6168
6346
|
.aparte-conv-item--active {
|
|
6169
|
-
position: relative;
|
|
6170
6347
|
background: var(--aparte-conv-item-bg-active);
|
|
6171
6348
|
color: var(--aparte-conv-item-color-active);
|
|
6172
6349
|
font-weight: var(--aparte-conv-item-font-weight-active);
|
|
@@ -6182,61 +6359,73 @@ textarea.aparte-elic-text {
|
|
|
6182
6359
|
pointer-events: none;
|
|
6183
6360
|
}
|
|
6184
6361
|
|
|
6185
|
-
.aparte-conv-item--
|
|
6186
|
-
|
|
6362
|
+
.aparte-conv-item--archived .aparte-conv-item__title {
|
|
6363
|
+
font-style: italic;
|
|
6364
|
+
opacity: 0.85;
|
|
6187
6365
|
}
|
|
6188
6366
|
|
|
6189
|
-
|
|
6367
|
+
/* The `⋯`: one quiet button per row, shown by hover, by focus, on the active row and
|
|
6368
|
+
while its menu is open — never a permanent control, and never a red one. What the
|
|
6369
|
+
recipe gives (shape, states, focus ring) it keeps; only the size and the resting
|
|
6370
|
+
colour are the row's. */
|
|
6371
|
+
.aparte-conv-item__more {
|
|
6372
|
+
flex-shrink: 0;
|
|
6190
6373
|
opacity: 0;
|
|
6374
|
+
width: var(--aparte-conv-action-btn-size);
|
|
6375
|
+
height: var(--aparte-conv-action-btn-size);
|
|
6376
|
+
color: var(--aparte-text-muted);
|
|
6191
6377
|
}
|
|
6192
6378
|
|
|
6193
|
-
.aparte-conv-
|
|
6379
|
+
.aparte-conv-item:hover .aparte-conv-item__more,
|
|
6380
|
+
.aparte-conv-item:focus-within .aparte-conv-item__more,
|
|
6381
|
+
.aparte-conv-item--active .aparte-conv-item__more,
|
|
6382
|
+
.aparte-conv-item__more[aria-expanded="true"] {
|
|
6383
|
+
opacity: 1;
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
.aparte-conv-item__more:hover {
|
|
6387
|
+
color: var(--aparte-text);
|
|
6388
|
+
}
|
|
6389
|
+
|
|
6390
|
+
/* The rename input takes the title button's place, so it inherits the row's slot and
|
|
6391
|
+
only needs the field recipe plus a little air. */
|
|
6392
|
+
.aparte-conv-item__input {
|
|
6194
6393
|
flex: 1;
|
|
6195
|
-
white-space: nowrap;
|
|
6196
|
-
overflow: hidden;
|
|
6197
|
-
text-overflow: ellipsis;
|
|
6198
6394
|
min-width: 0;
|
|
6395
|
+
margin: var(--aparte-space-1) 0 var(--aparte-space-1) var(--aparte-space-1);
|
|
6199
6396
|
}
|
|
6200
6397
|
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
`.aparte-conv-item__delete:hover` only 0,2,0, so the recipe won the background and
|
|
6209
|
-
this button's red never applied — while the `color` below DID, because the recipe's
|
|
6210
|
-
hover rule sets no colour. The result was the ink for a solid red fill painted on a
|
|
6211
|
-
neutral surface: 1.17:1 in the light theme, 1.20 in the dark, measured in a browser.
|
|
6212
|
-
The cross was invisible in both. Feeding the recipe its own token is the rule this
|
|
6213
|
-
file's neighbours already follow, and it makes the pair agree again — 3.70 and 4.84,
|
|
6214
|
-
both clear of the 3:1 a graphical object needs. */
|
|
6215
|
-
--aparte-btn-bg-hover: var(--aparte-conv-delete-bg-hover);
|
|
6398
|
+
/* The row's menu: the menu recipe draws it, the element places it (`position: fixed`,
|
|
6399
|
+
`top`/`left` computed at open time, closed on any scroll). Fixed rather than
|
|
6400
|
+
absolute because the list scrolls, and an absolute menu on the last rows was clipped
|
|
6401
|
+
by that overflow. */
|
|
6402
|
+
.aparte-conv-menu {
|
|
6403
|
+
position: fixed;
|
|
6404
|
+
--aparte-icon-size: var(--aparte-icon-size-sm);
|
|
6216
6405
|
}
|
|
6217
6406
|
|
|
6218
|
-
.aparte-conv-
|
|
6219
|
-
color: var(--aparte-
|
|
6407
|
+
.aparte-conv-menu__item--danger {
|
|
6408
|
+
color: var(--aparte-error);
|
|
6220
6409
|
}
|
|
6221
6410
|
|
|
6222
|
-
.
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
/* Same reason as the delete button above: the recipe out-weighs a plain `:hover` here,
|
|
6229
|
-
so this surface has to be declared as its token or it never applies. */
|
|
6230
|
-
--aparte-btn-bg-hover: var(--aparte-conv-archive-bg-hover, var(--aparte-surface-4, var(--aparte-surface-3)));
|
|
6411
|
+
/* The one question the menu asks. */
|
|
6412
|
+
.aparte-conv-menu__confirm {
|
|
6413
|
+
display: flex;
|
|
6414
|
+
flex-direction: column;
|
|
6415
|
+
gap: var(--aparte-space-3);
|
|
6416
|
+
padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
6231
6417
|
}
|
|
6232
6418
|
|
|
6233
|
-
.aparte-conv-
|
|
6234
|
-
|
|
6419
|
+
.aparte-conv-menu__question {
|
|
6420
|
+
margin: 0;
|
|
6421
|
+
font-size: var(--aparte-font-size-sm);
|
|
6422
|
+
line-height: var(--aparte-line-height-snug);
|
|
6235
6423
|
}
|
|
6236
6424
|
|
|
6237
|
-
.aparte-conv-
|
|
6238
|
-
|
|
6239
|
-
|
|
6425
|
+
.aparte-conv-menu__actions {
|
|
6426
|
+
display: flex;
|
|
6427
|
+
justify-content: flex-end;
|
|
6428
|
+
gap: var(--aparte-space-2);
|
|
6240
6429
|
}
|
|
6241
6430
|
/*
|
|
6242
6431
|
* aparté — <aparte-suggestions>: the row of prompt starters.
|
|
@@ -6309,6 +6498,682 @@ aparte-context[data-level="warn"] .aparte-progress__bar {
|
|
|
6309
6498
|
aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
6310
6499
|
background: var(--aparte-error);
|
|
6311
6500
|
}
|
|
6501
|
+
|
|
6502
|
+
/* The ring variant: a track and a value circle on a 36-unit box, sized by the two tokens.
|
|
6503
|
+
`pathLength="100"` on the value circle (set by the element) makes the dash the
|
|
6504
|
+
percentage itself, so the sheet needs no circumference. Rotated a quarter turn so the
|
|
6505
|
+
value starts at twelve o'clock. */
|
|
6506
|
+
.aparte-context--ring {
|
|
6507
|
+
gap: var(--aparte-space-1);
|
|
6508
|
+
}
|
|
6509
|
+
|
|
6510
|
+
.aparte-context__ring {
|
|
6511
|
+
flex: none;
|
|
6512
|
+
width: var(--aparte-context-ring-size);
|
|
6513
|
+
height: var(--aparte-context-ring-size);
|
|
6514
|
+
transform: rotate(-90deg);
|
|
6515
|
+
}
|
|
6516
|
+
|
|
6517
|
+
.aparte-context__track,
|
|
6518
|
+
.aparte-context__value {
|
|
6519
|
+
fill: none;
|
|
6520
|
+
stroke-width: var(--aparte-context-ring-stroke);
|
|
6521
|
+
}
|
|
6522
|
+
|
|
6523
|
+
.aparte-context__track {
|
|
6524
|
+
stroke: var(--aparte-surface-3);
|
|
6525
|
+
}
|
|
6526
|
+
|
|
6527
|
+
.aparte-context__value {
|
|
6528
|
+
--aparte-context-ratio: 0;
|
|
6529
|
+
stroke: var(--aparte-progress-fill);
|
|
6530
|
+
stroke-linecap: round;
|
|
6531
|
+
stroke-dasharray: var(--aparte-context-ratio) 100;
|
|
6532
|
+
transition: stroke-dasharray var(--aparte-duration-fast) var(--aparte-ease);
|
|
6533
|
+
}
|
|
6534
|
+
|
|
6535
|
+
.aparte-context__value--empty {
|
|
6536
|
+
visibility: hidden;
|
|
6537
|
+
}
|
|
6538
|
+
|
|
6539
|
+
aparte-context[data-level="warn"] .aparte-context__value {
|
|
6540
|
+
stroke: var(--aparte-warning);
|
|
6541
|
+
}
|
|
6542
|
+
aparte-context[data-level="danger"] .aparte-context__value {
|
|
6543
|
+
stroke: var(--aparte-error);
|
|
6544
|
+
}
|
|
6545
|
+
/*
|
|
6546
|
+
* aparté — <aparte-scroll-rail>: the ticks beside the transcript.
|
|
6547
|
+
*
|
|
6548
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6549
|
+
* them concatenated in that same order, the way a browser does.
|
|
6550
|
+
*
|
|
6551
|
+
* The rail floats on the transcript's end edge, vertically centred, as a child of the
|
|
6552
|
+
* chat host. `:has()` gives the host its containing block only while a rail is inside
|
|
6553
|
+
* it — a bare `position: relative` on every chat would have moved any absolutely
|
|
6554
|
+
* positioned child a consumer already placed there.
|
|
6555
|
+
*/
|
|
6556
|
+
aparte-chat:has(> aparte-scroll-rail),
|
|
6557
|
+
[data-aparte-chat]:has(> aparte-scroll-rail) {
|
|
6558
|
+
position: relative;
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
aparte-scroll-rail {
|
|
6562
|
+
position: absolute;
|
|
6563
|
+
inset-inline-end: var(--aparte-space-2);
|
|
6564
|
+
top: 50%;
|
|
6565
|
+
transform: translateY(-50%);
|
|
6566
|
+
z-index: var(--aparte-z-floating);
|
|
6567
|
+
display: block;
|
|
6568
|
+
width: var(--aparte-scroll-rail-width);
|
|
6569
|
+
max-height: calc(100% - 2 * var(--aparte-space-8));
|
|
6570
|
+
overflow: hidden;
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
aparte-scroll-rail[hidden],
|
|
6574
|
+
aparte-scroll-rail[data-empty] {
|
|
6575
|
+
display: none;
|
|
6576
|
+
}
|
|
6577
|
+
|
|
6578
|
+
.aparte-scroll-rail__list {
|
|
6579
|
+
list-style: none;
|
|
6580
|
+
margin: 0;
|
|
6581
|
+
padding: 0;
|
|
6582
|
+
display: flex;
|
|
6583
|
+
flex-direction: column;
|
|
6584
|
+
align-items: flex-end;
|
|
6585
|
+
gap: var(--aparte-scroll-rail-gap);
|
|
6586
|
+
}
|
|
6587
|
+
|
|
6588
|
+
/* A tick is a real button, drawn as a line. The hit area is the gap around it, drawn
|
|
6589
|
+
by the pseudo-element so the line itself can stay two pixels. */
|
|
6590
|
+
.aparte-scroll-rail__tick {
|
|
6591
|
+
appearance: none;
|
|
6592
|
+
-webkit-appearance: none;
|
|
6593
|
+
position: relative;
|
|
6594
|
+
display: block;
|
|
6595
|
+
width: var(--aparte-scroll-rail-tick-size);
|
|
6596
|
+
height: var(--aparte-scroll-rail-tick-thickness);
|
|
6597
|
+
padding: 0;
|
|
6598
|
+
border: 0;
|
|
6599
|
+
border-radius: var(--aparte-radius-full);
|
|
6600
|
+
background: var(--aparte-border);
|
|
6601
|
+
cursor: pointer;
|
|
6602
|
+
transition:
|
|
6603
|
+
width var(--aparte-duration-fast) var(--aparte-ease),
|
|
6604
|
+
background var(--aparte-duration-fast) var(--aparte-ease);
|
|
6605
|
+
}
|
|
6606
|
+
|
|
6607
|
+
.aparte-scroll-rail__tick::before {
|
|
6608
|
+
content: "";
|
|
6609
|
+
position: absolute;
|
|
6610
|
+
inset: calc(-1 * var(--aparte-scroll-rail-gap) / 2) calc(-1 * var(--aparte-space-2));
|
|
6611
|
+
}
|
|
6612
|
+
|
|
6613
|
+
.aparte-scroll-rail__tick:hover {
|
|
6614
|
+
background: var(--aparte-text-muted);
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6617
|
+
.aparte-scroll-rail__tick[aria-current="true"] {
|
|
6618
|
+
width: calc(var(--aparte-scroll-rail-tick-size) * 1.6);
|
|
6619
|
+
background: var(--aparte-primary);
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6622
|
+
.aparte-scroll-rail__tick:focus-visible {
|
|
6623
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-primary);
|
|
6624
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
6625
|
+
}
|
|
6626
|
+
|
|
6627
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6628
|
+
.aparte-scroll-rail__tick {
|
|
6629
|
+
transition: none;
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
/*
|
|
6633
|
+
* aparté — the shell: the chrome a whole application puts around the chat. A sidebar
|
|
6634
|
+
* of conversations, a header, and the grid that holds them beside the transcript —
|
|
6635
|
+
* what a ChatGPT-style page is made of, so a site can be built on aparté alone.
|
|
6636
|
+
*
|
|
6637
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6638
|
+
* them concatenated in that same order, the way a browser does.
|
|
6639
|
+
*/
|
|
6640
|
+
|
|
6641
|
+
/*
|
|
6642
|
+
* aparte-sidebar — the column beside the chat.
|
|
6643
|
+
* A sized column that becomes a drawer under 48rem of window. The recipe draws the
|
|
6644
|
+
* column and its four regions; `<aparte-sidebar>` adds the behaviour (collapse, the
|
|
6645
|
+
* drawer, the search filter) and sets `data-drawer` on itself from the media query,
|
|
6646
|
+
* which is the one attribute the rules below read.
|
|
6647
|
+
*
|
|
6648
|
+
* <aside class="aparte-sidebar">
|
|
6649
|
+
* <div class="aparte-sidebar__header">
|
|
6650
|
+
* <span class="aparte-sidebar__brand">aparté</span>
|
|
6651
|
+
* <button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="New chat">
|
|
6652
|
+
* <aparte-icon name="edit"></aparte-icon>
|
|
6653
|
+
* </button>
|
|
6654
|
+
* </div>
|
|
6655
|
+
* <div class="aparte-sidebar__search aparte-field-group">
|
|
6656
|
+
* <input class="aparte-field aparte-field--sm" type="search" placeholder="Search" data-aparte-sidebar-search>
|
|
6657
|
+
* </div>
|
|
6658
|
+
* <div class="aparte-sidebar__body">
|
|
6659
|
+
* <aparte-conversation-list></aparte-conversation-list>
|
|
6660
|
+
* </div>
|
|
6661
|
+
* <div class="aparte-sidebar__footer">
|
|
6662
|
+
* <span class="aparte-avatar aparte-avatar--sm">P</span> Paul
|
|
6663
|
+
* </div>
|
|
6664
|
+
* </aside>
|
|
6665
|
+
*
|
|
6666
|
+
* The element wears the same class and adds the behaviour; `<aside class="aparte-sidebar">`
|
|
6667
|
+
* is the look alone, which is what this page shows.
|
|
6668
|
+
*/
|
|
6669
|
+
aparte-sidebar,
|
|
6670
|
+
.aparte-sidebar {
|
|
6671
|
+
box-sizing: border-box;
|
|
6672
|
+
display: flex;
|
|
6673
|
+
flex-direction: column;
|
|
6674
|
+
width: var(--aparte-sidebar-width);
|
|
6675
|
+
min-width: 0;
|
|
6676
|
+
height: 100%;
|
|
6677
|
+
overflow: hidden;
|
|
6678
|
+
background: var(--aparte-sidebar-bg);
|
|
6679
|
+
border-inline-end: var(--aparte-border-width) solid var(--aparte-border);
|
|
6680
|
+
color: var(--aparte-text);
|
|
6681
|
+
transition: width var(--aparte-duration-slow) var(--aparte-ease);
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
/* In the flow, collapsed means gone: the column folds to nothing and the chat takes
|
|
6685
|
+
the room. The children keep their width so the fold is a slide, not a squash. */
|
|
6686
|
+
.aparte-sidebar[collapsed]:not([data-drawer]) {
|
|
6687
|
+
width: 0;
|
|
6688
|
+
border-inline-end-width: 0;
|
|
6689
|
+
}
|
|
6690
|
+
|
|
6691
|
+
/* The children keep the column's width while it collapses, so nothing reflows during
|
|
6692
|
+
the transition. BORDER-box, or a padded child is the column plus its padding: the
|
|
6693
|
+
header's button sat flush against the edge and the search field ran off it, clipped
|
|
6694
|
+
— measured at 292px in a 260px column. The search field carries margins as well,
|
|
6695
|
+
which border-box does not cover; its own rule subtracts them. */
|
|
6696
|
+
.aparte-sidebar > * {
|
|
6697
|
+
box-sizing: border-box;
|
|
6698
|
+
flex-shrink: 0;
|
|
6699
|
+
min-width: var(--aparte-sidebar-width);
|
|
6700
|
+
}
|
|
6701
|
+
|
|
6702
|
+
.aparte-sidebar__header {
|
|
6703
|
+
display: flex;
|
|
6704
|
+
align-items: center;
|
|
6705
|
+
gap: var(--aparte-space-3);
|
|
6706
|
+
padding: var(--aparte-space-4) var(--aparte-space-4) var(--aparte-space-2);
|
|
6707
|
+
}
|
|
6708
|
+
|
|
6709
|
+
.aparte-sidebar__brand {
|
|
6710
|
+
flex: 1;
|
|
6711
|
+
min-width: 0;
|
|
6712
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
6713
|
+
white-space: nowrap;
|
|
6714
|
+
overflow: hidden;
|
|
6715
|
+
text-overflow: ellipsis;
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
.aparte-sidebar__search {
|
|
6719
|
+
margin: var(--aparte-space-2) var(--aparte-space-3);
|
|
6720
|
+
min-width: calc(var(--aparte-sidebar-width) - 2 * var(--aparte-space-3));
|
|
6721
|
+
}
|
|
6722
|
+
|
|
6723
|
+
/* The body is the one region that scrolls; the list inside keeps its own gap. */
|
|
6724
|
+
.aparte-sidebar__body {
|
|
6725
|
+
flex: 1 1 auto;
|
|
6726
|
+
min-height: 0;
|
|
6727
|
+
overflow-y: auto;
|
|
6728
|
+
overflow-x: hidden;
|
|
6729
|
+
padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
6730
|
+
scrollbar-width: thin;
|
|
6731
|
+
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
6732
|
+
}
|
|
6733
|
+
|
|
6734
|
+
.aparte-sidebar__footer {
|
|
6735
|
+
display: flex;
|
|
6736
|
+
align-items: center;
|
|
6737
|
+
gap: var(--aparte-space-3);
|
|
6738
|
+
padding: var(--aparte-space-3) var(--aparte-space-4);
|
|
6739
|
+
border-block-start: var(--aparte-border-width) solid var(--aparte-border);
|
|
6740
|
+
font-size: var(--aparte-font-size-sm);
|
|
6741
|
+
}
|
|
6742
|
+
|
|
6743
|
+
/* The drawer: the same element, out of the flow, over the page. `inset-inline-start`
|
|
6744
|
+
rather than a transform, so the scrim — a fixed child — stays relative to the
|
|
6745
|
+
viewport and can cover the page from inside the element. */
|
|
6746
|
+
.aparte-sidebar[data-drawer] {
|
|
6747
|
+
position: fixed;
|
|
6748
|
+
inset-block: 0;
|
|
6749
|
+
inset-inline-start: 0;
|
|
6750
|
+
z-index: var(--aparte-z-dropdown);
|
|
6751
|
+
width: var(--aparte-sidebar-width);
|
|
6752
|
+
box-shadow: var(--aparte-menu-shadow);
|
|
6753
|
+
transition: inset-inline-start var(--aparte-duration-slower) var(--aparte-ease);
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6756
|
+
.aparte-sidebar[data-drawer][collapsed] {
|
|
6757
|
+
inset-inline-start: calc(-1 * var(--aparte-sidebar-width) - var(--aparte-space-4));
|
|
6758
|
+
box-shadow: none;
|
|
6759
|
+
}
|
|
6760
|
+
|
|
6761
|
+
.aparte-sidebar__scrim {
|
|
6762
|
+
position: fixed;
|
|
6763
|
+
inset: 0;
|
|
6764
|
+
z-index: -1;
|
|
6765
|
+
background: var(--aparte-scrim);
|
|
6766
|
+
}
|
|
6767
|
+
/*
|
|
6768
|
+
* aparté — the header row of an application shell.
|
|
6769
|
+
*
|
|
6770
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6771
|
+
* them concatenated in that same order, the way a browser does.
|
|
6772
|
+
*/
|
|
6773
|
+
|
|
6774
|
+
/*
|
|
6775
|
+
* aparte-app-header — the bar above the chat.
|
|
6776
|
+
* A recipe and nothing more, because a header has no behaviour of its own: a toggle
|
|
6777
|
+
* for the sidebar (which `<aparte-sidebar>` wires from the `data-aparte-sidebar-toggle`
|
|
6778
|
+
* attribute), the conversation's title, and an actions zone pushed to the end — a
|
|
6779
|
+
* model selector, a share button, whatever the page needs. The toggle shows only
|
|
6780
|
+
* under 48rem, where the sidebar is a drawer; above it the column is simply there.
|
|
6781
|
+
*
|
|
6782
|
+
* <header class="aparte-app-header">
|
|
6783
|
+
* <button class="aparte-btn aparte-btn--icon aparte-app-header__toggle" type="button" aria-label="Toggle the sidebar" data-aparte-sidebar-toggle>☰</button>
|
|
6784
|
+
* <span class="aparte-app-header__title">Deploy checklist</span>
|
|
6785
|
+
* <div class="aparte-app-header__actions">
|
|
6786
|
+
* <span class="aparte-tag">gpt-4.1</span>
|
|
6787
|
+
* <button class="aparte-btn aparte-btn--sm aparte-btn--ghost" type="button">Share</button>
|
|
6788
|
+
* </div>
|
|
6789
|
+
* </header>
|
|
6790
|
+
*/
|
|
6791
|
+
.aparte-app-header {
|
|
6792
|
+
box-sizing: border-box;
|
|
6793
|
+
display: flex;
|
|
6794
|
+
align-items: center;
|
|
6795
|
+
gap: var(--aparte-space-3);
|
|
6796
|
+
min-height: var(--aparte-app-header-height);
|
|
6797
|
+
padding: var(--aparte-space-2) var(--aparte-space-4);
|
|
6798
|
+
background: var(--aparte-surface-1);
|
|
6799
|
+
border-block-end: var(--aparte-border-width) solid var(--aparte-border);
|
|
6800
|
+
color: var(--aparte-text);
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6803
|
+
.aparte-app-header__title {
|
|
6804
|
+
flex: 1;
|
|
6805
|
+
min-width: 0;
|
|
6806
|
+
font-weight: var(--aparte-font-weight-medium);
|
|
6807
|
+
white-space: nowrap;
|
|
6808
|
+
overflow: hidden;
|
|
6809
|
+
text-overflow: ellipsis;
|
|
6810
|
+
}
|
|
6811
|
+
|
|
6812
|
+
.aparte-app-header__actions {
|
|
6813
|
+
display: flex;
|
|
6814
|
+
align-items: center;
|
|
6815
|
+
gap: var(--aparte-space-2);
|
|
6816
|
+
margin-inline-start: auto;
|
|
6817
|
+
}
|
|
6818
|
+
|
|
6819
|
+
.aparte-app-header__toggle {
|
|
6820
|
+
display: none;
|
|
6821
|
+
}
|
|
6822
|
+
|
|
6823
|
+
/* The same 48rem the sidebar turns into a drawer at (`sidebar.css`). */
|
|
6824
|
+
@media (max-width: 48rem) {
|
|
6825
|
+
.aparte-app-header__toggle {
|
|
6826
|
+
display: inline-flex;
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
/*
|
|
6830
|
+
* aparté — the grid that holds a sidebar, a header and the chat.
|
|
6831
|
+
*
|
|
6832
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6833
|
+
* them concatenated in that same order, the way a browser does.
|
|
6834
|
+
*/
|
|
6835
|
+
|
|
6836
|
+
/*
|
|
6837
|
+
* aparte-app-shell — sidebar beside, header above, the chat in the rest.
|
|
6838
|
+
* A recipe: two columns and two rows, the sidebar spanning both rows, the header and
|
|
6839
|
+
* the main area stacked in the second column. The chat fills `__main`, which is what
|
|
6840
|
+
* gives it the height it needs to scroll (see the layout guide). Size the shell —
|
|
6841
|
+
* `height: 100dvh` for a page — and everything inside follows. Under 48rem the sidebar
|
|
6842
|
+
* leaves the grid to become a drawer, and the header's toggle appears.
|
|
6843
|
+
*
|
|
6844
|
+
* <div class="aparte-app-shell" style="height: 18rem">
|
|
6845
|
+
* <aside class="aparte-sidebar">
|
|
6846
|
+
* <div class="aparte-sidebar__header"><span class="aparte-sidebar__brand">aparté</span></div>
|
|
6847
|
+
* <div class="aparte-sidebar__body"><aparte-conversation-list></aparte-conversation-list></div>
|
|
6848
|
+
* </aside>
|
|
6849
|
+
* <header class="aparte-app-header"><span class="aparte-app-header__title">Deploy checklist</span></header>
|
|
6850
|
+
* <main class="aparte-app-shell__main">
|
|
6851
|
+
* <aparte-chat>
|
|
6852
|
+
* <aparte-chat-viewport></aparte-chat-viewport>
|
|
6853
|
+
* <aparte-composer>
|
|
6854
|
+
* <div class="aparte-composer-shell">
|
|
6855
|
+
* <div class="aparte-composer-row">
|
|
6856
|
+
* <aparte-composer-input></aparte-composer-input>
|
|
6857
|
+
* <aparte-composer-send></aparte-composer-send>
|
|
6858
|
+
* </div>
|
|
6859
|
+
* </div>
|
|
6860
|
+
* </aparte-composer>
|
|
6861
|
+
* </aparte-chat>
|
|
6862
|
+
* </main>
|
|
6863
|
+
* </div>
|
|
6864
|
+
*/
|
|
6865
|
+
.aparte-app-shell {
|
|
6866
|
+
box-sizing: border-box;
|
|
6867
|
+
display: grid;
|
|
6868
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
6869
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
6870
|
+
height: 100%;
|
|
6871
|
+
overflow: hidden;
|
|
6872
|
+
background: var(--aparte-surface);
|
|
6873
|
+
}
|
|
6874
|
+
|
|
6875
|
+
.aparte-app-shell > aparte-sidebar,
|
|
6876
|
+
.aparte-app-shell > .aparte-sidebar {
|
|
6877
|
+
grid-row: 1 / -1;
|
|
6878
|
+
}
|
|
6879
|
+
|
|
6880
|
+
/* A drawer is out of the flow, so the grid closes the column it left. */
|
|
6881
|
+
.aparte-app-shell > .aparte-sidebar[data-drawer] {
|
|
6882
|
+
grid-row: auto;
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
.aparte-app-shell > .aparte-app-header {
|
|
6886
|
+
grid-column: 2;
|
|
6887
|
+
}
|
|
6888
|
+
|
|
6889
|
+
.aparte-app-shell__main {
|
|
6890
|
+
grid-column: 2;
|
|
6891
|
+
min-height: 0;
|
|
6892
|
+
min-width: 0;
|
|
6893
|
+
display: flex;
|
|
6894
|
+
flex-direction: column;
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
/* A chat — or a split holding one — fills the main area. Stated for the direct child
|
|
6898
|
+
because that is where the height comes from: `<aparte-chat>` is `height: 100%` of a
|
|
6899
|
+
parent that has none until this rule gives it one, and a split between the two is
|
|
6900
|
+
the same case one level out. A pane CONTAINS a chat; a chat never contains a split. */
|
|
6901
|
+
.aparte-app-shell__main > aparte-chat,
|
|
6902
|
+
.aparte-app-shell__main > [data-aparte-chat],
|
|
6903
|
+
.aparte-app-shell__main > aparte-split,
|
|
6904
|
+
.aparte-app-shell__main > .aparte-split {
|
|
6905
|
+
flex: 1;
|
|
6906
|
+
min-height: 0;
|
|
6907
|
+
height: auto;
|
|
6908
|
+
}
|
|
6909
|
+
/*
|
|
6910
|
+
* aparté — the seam: two panes side by side, and the grid that lets one grow while
|
|
6911
|
+
* the other gives way. The fourth piece of the shell, after the sidebar, the header
|
|
6912
|
+
* and the grid that holds them.
|
|
6913
|
+
*
|
|
6914
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6915
|
+
* them concatenated in that same order, the way a browser does.
|
|
6916
|
+
*/
|
|
6917
|
+
|
|
6918
|
+
/*
|
|
6919
|
+
* aparte-split — two panes and a draggable seam. The chat in one, your pane in the
|
|
6920
|
+
* other: a preview frame, an editor, an artifact card.
|
|
6921
|
+
*
|
|
6922
|
+
* The grid is the whole mechanism. `--aparte-split-position` is the primary pane's
|
|
6923
|
+
* size; `--aparte-split-min` and `--aparte-split-max` are clamp bounds, so the
|
|
6924
|
+
* browser does the clamping and nothing in JS parses a unit — px, %, rem and ch all
|
|
6925
|
+
* work. Used without <aparte-split> this is a static split: set the position from
|
|
6926
|
+
* your own media query and there is no drag, which is the Canvas shape.
|
|
6927
|
+
*
|
|
6928
|
+
* A pane CONTAINS a chat; a chat never contains a split.
|
|
6929
|
+
*
|
|
6930
|
+
* <div class="aparte-split" style="height: 18rem">
|
|
6931
|
+
* <div>the chat</div>
|
|
6932
|
+
* <div class="aparte-split__handle"></div>
|
|
6933
|
+
* <div class="aparte-split__pane">the pane</div>
|
|
6934
|
+
* </div>
|
|
6935
|
+
*
|
|
6936
|
+
* The element wears the same class and adds the behaviour (drag, arrow keys, the APG
|
|
6937
|
+
* separator's ARIA, one pane at a time under a breakpoint); `<div class="aparte-split">`
|
|
6938
|
+
* is the look alone, which is what this page shows.
|
|
6939
|
+
*/
|
|
6940
|
+
|
|
6941
|
+
/* The outer clamp is a guard, not decoration: a `--aparte-split-min` wider than the
|
|
6942
|
+
container would otherwise eat the free pane whole and push the handle off the end.
|
|
6943
|
+
Three tracks, always — the primary pane, the seam, the rest. */
|
|
6944
|
+
aparte-split,
|
|
6945
|
+
.aparte-split {
|
|
6946
|
+
box-sizing: border-box;
|
|
6947
|
+
display: grid;
|
|
6948
|
+
grid-template-columns:
|
|
6949
|
+
clamp(
|
|
6950
|
+
0px,
|
|
6951
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
6952
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
6953
|
+
)
|
|
6954
|
+
var(--aparte-split-handle-size)
|
|
6955
|
+
minmax(0, 1fr);
|
|
6956
|
+
height: 100%;
|
|
6957
|
+
min-width: 0;
|
|
6958
|
+
overflow: hidden;
|
|
6959
|
+
}
|
|
6960
|
+
|
|
6961
|
+
/* A grid item's automatic minimum size is its content, so one long line in either
|
|
6962
|
+
pane refuses to shrink and the seam stops tracking the pointer.
|
|
6963
|
+
|
|
6964
|
+
The tag is listed beside the class here and below for the reason the grid rule
|
|
6965
|
+
lists it: the element adds the class at connect, so between parse and upgrade a
|
|
6966
|
+
server-rendered split would be a grid whose items keep their content minimum. */
|
|
6967
|
+
aparte-split > *,
|
|
6968
|
+
.aparte-split > * {
|
|
6969
|
+
min-inline-size: 0;
|
|
6970
|
+
min-block-size: 0;
|
|
6971
|
+
}
|
|
6972
|
+
|
|
6973
|
+
/* The outside-the-shell half of the direct-child trap: <aparte-chat> is `height: 100%`
|
|
6974
|
+
of a parent that has none unless the grid track gives it one. Inside the app shell
|
|
6975
|
+
the same fix lives on `.aparte-app-shell__main`; split.css is imported after
|
|
6976
|
+
app-shell.css, so a chat in a pane inside a shell reads this one.
|
|
6977
|
+
|
|
6978
|
+
`box-sizing` with it, because the chat's own sheet gives it a bottom padding — the
|
|
6979
|
+
16px of breathing room under the composer that core owns on purpose. Left in the
|
|
6980
|
+
default content-box that padding lands OUTSIDE the 100%, so the chat's border box
|
|
6981
|
+
overflows its track by exactly the gap and `.aparte-split`'s `overflow: hidden`
|
|
6982
|
+
eats it: the composer sits flush on the seam's bottom edge and the gap is gone. */
|
|
6983
|
+
aparte-split > aparte-chat,
|
|
6984
|
+
aparte-split > [data-aparte-chat],
|
|
6985
|
+
.aparte-split > aparte-chat,
|
|
6986
|
+
.aparte-split > [data-aparte-chat] {
|
|
6987
|
+
box-sizing: border-box;
|
|
6988
|
+
height: 100%;
|
|
6989
|
+
min-height: 0;
|
|
6990
|
+
}
|
|
6991
|
+
|
|
6992
|
+
/* The seam. `touch-action: none` so a drag on a touch screen resizes rather than
|
|
6993
|
+
scrolling the page — without it the browser claims the gesture at the first move
|
|
6994
|
+
and the pointer events stop arriving. */
|
|
6995
|
+
.aparte-split__handle {
|
|
6996
|
+
position: relative;
|
|
6997
|
+
box-sizing: border-box;
|
|
6998
|
+
border: 0;
|
|
6999
|
+
padding: 0;
|
|
7000
|
+
background: var(--aparte-border);
|
|
7001
|
+
cursor: col-resize;
|
|
7002
|
+
touch-action: none;
|
|
7003
|
+
transition: background-color var(--aparte-duration-fast) var(--aparte-ease);
|
|
7004
|
+
}
|
|
7005
|
+
|
|
7006
|
+
.aparte-split__handle:hover,
|
|
7007
|
+
.aparte-split__handle[data-dragging] {
|
|
7008
|
+
background: var(--aparte-primary);
|
|
7009
|
+
}
|
|
7010
|
+
|
|
7011
|
+
/* A real outline, and the ring kept as decoration.
|
|
7012
|
+
|
|
7013
|
+
The seam's whole keyboard story is arrowing it, and its only focus indicator was
|
|
7014
|
+
`--aparte-focus-ring` — a soft wash that measures 1.39:1 against the page in the
|
|
7015
|
+
light palette and 1.83:1 in the dark one, where WCAG asks 3:1 of a focus indicator.
|
|
7016
|
+
`outline: none` with nothing in its place is the shape of that failure: the
|
|
7017
|
+
indicator was not weak, it was absent, on a 4px band with `border: 0` that a
|
|
7018
|
+
keyboard user has no other way to find.
|
|
7019
|
+
|
|
7020
|
+
The outline is what carries the contrast (3.54:1 light, 7.36:1 dark, measured); the
|
|
7021
|
+
shadow stays because it reads as a glow AROUND the seam and the two do not fight.
|
|
7022
|
+
The forced-colors entry in responsive.css keeps overriding the colour only — it is
|
|
7023
|
+
now overriding an outline that exists rather than substituting for one that does
|
|
7024
|
+
not. */
|
|
7025
|
+
.aparte-split__handle:focus-visible {
|
|
7026
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
7027
|
+
outline-offset: 0;
|
|
7028
|
+
box-shadow: var(--aparte-focus-ring);
|
|
7029
|
+
}
|
|
7030
|
+
|
|
7031
|
+
/* The hit slop: a 4px seam is a 4px target, and a pointer misses it. The pseudo-element
|
|
7032
|
+
is wider than the seam and centred on it, so the grab zone reaches into both panes
|
|
7033
|
+
without the seam itself getting bigger. */
|
|
7034
|
+
.aparte-split__handle::after {
|
|
7035
|
+
content: '';
|
|
7036
|
+
position: absolute;
|
|
7037
|
+
inset-block: 0;
|
|
7038
|
+
inset-inline-start: calc(var(--aparte-split-hit-area) / -2 + var(--aparte-split-handle-size) / 2);
|
|
7039
|
+
inline-size: var(--aparte-split-hit-area);
|
|
7040
|
+
}
|
|
7041
|
+
|
|
7042
|
+
/* The drag overlay, a child of the HANDLE for the duration of one gesture. Being the
|
|
7043
|
+
topmost hit target it swallows an iframe pane's hit-testing (a drag over a frame is
|
|
7044
|
+
otherwise lost to the frame's document), stops a text selection from starting, and
|
|
7045
|
+
paints the drag cursor across the whole page. A child of the handle rather than of
|
|
7046
|
+
the body so `click` — and therefore `dblclick` — still dispatches at the handle,
|
|
7047
|
+
their nearest common ancestor.
|
|
7048
|
+
|
|
7049
|
+
It is `position: fixed`, so nothing between it and the viewport may establish a
|
|
7050
|
+
containing block: `.aparte-split` must never get `container-type`, `transform`,
|
|
7051
|
+
`filter` or `backdrop-filter`, or the overlay covers the split alone. */
|
|
7052
|
+
.aparte-split__scrim {
|
|
7053
|
+
position: fixed;
|
|
7054
|
+
inset: 0;
|
|
7055
|
+
z-index: var(--aparte-z-dropdown);
|
|
7056
|
+
cursor: col-resize;
|
|
7057
|
+
}
|
|
7058
|
+
|
|
7059
|
+
/* An optional wrapper for the pane that is not a chat — the pane equivalent of
|
|
7060
|
+
`.aparte-sidebar__body`. The chat pane does not use it: the viewport scrolls itself. */
|
|
7061
|
+
.aparte-split__pane {
|
|
7062
|
+
box-sizing: border-box;
|
|
7063
|
+
min-inline-size: 0;
|
|
7064
|
+
min-block-size: 0;
|
|
7065
|
+
overflow: auto;
|
|
7066
|
+
background: var(--aparte-surface-1);
|
|
7067
|
+
}
|
|
7068
|
+
|
|
7069
|
+
/* `primary="end"` sizes the LAST pane instead of the first: the same three tracks,
|
|
7070
|
+
read the other way round. */
|
|
7071
|
+
aparte-split[primary='end'],
|
|
7072
|
+
.aparte-split--primary-end {
|
|
7073
|
+
grid-template-columns:
|
|
7074
|
+
minmax(0, 1fr)
|
|
7075
|
+
var(--aparte-split-handle-size)
|
|
7076
|
+
clamp(
|
|
7077
|
+
0px,
|
|
7078
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
7079
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
7080
|
+
);
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7083
|
+
/* The other axis. The attribute and the class are selected in the same rule so the
|
|
7084
|
+
static recipe and the live element can never draw two different things. */
|
|
7085
|
+
aparte-split[orientation='vertical'],
|
|
7086
|
+
.aparte-split--vertical {
|
|
7087
|
+
grid-template-columns: none;
|
|
7088
|
+
grid-template-rows:
|
|
7089
|
+
clamp(
|
|
7090
|
+
0px,
|
|
7091
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
7092
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
7093
|
+
)
|
|
7094
|
+
var(--aparte-split-handle-size)
|
|
7095
|
+
minmax(0, 1fr);
|
|
7096
|
+
}
|
|
7097
|
+
|
|
7098
|
+
/* The one rule in this sheet that out-specifies the stacked block below — two
|
|
7099
|
+
attributes against its one — so it has to opt out of the stacked state by hand or a
|
|
7100
|
+
vertical `primary="end"` split keeps three rows while showing one pane, and the two
|
|
7101
|
+
empty tracks take the height off the pane that is left. */
|
|
7102
|
+
aparte-split[orientation='vertical'][primary='end']:not([data-stacked]),
|
|
7103
|
+
.aparte-split--vertical.aparte-split--primary-end:not(.aparte-split--only-start):not(.aparte-split--only-end) {
|
|
7104
|
+
grid-template-rows:
|
|
7105
|
+
minmax(0, 1fr)
|
|
7106
|
+
var(--aparte-split-handle-size)
|
|
7107
|
+
clamp(
|
|
7108
|
+
0px,
|
|
7109
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
7110
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
7111
|
+
);
|
|
7112
|
+
}
|
|
7113
|
+
|
|
7114
|
+
aparte-split[orientation='vertical'] > .aparte-split__handle,
|
|
7115
|
+
.aparte-split--vertical > .aparte-split__handle {
|
|
7116
|
+
cursor: row-resize;
|
|
7117
|
+
}
|
|
7118
|
+
|
|
7119
|
+
aparte-split[orientation='vertical'] > .aparte-split__handle::after,
|
|
7120
|
+
.aparte-split--vertical > .aparte-split__handle::after {
|
|
7121
|
+
inset-block: calc(var(--aparte-split-hit-area) / -2 + var(--aparte-split-handle-size) / 2) auto;
|
|
7122
|
+
inset-inline: 0;
|
|
7123
|
+
inline-size: auto;
|
|
7124
|
+
block-size: var(--aparte-split-hit-area);
|
|
7125
|
+
}
|
|
7126
|
+
|
|
7127
|
+
aparte-split[orientation='vertical'] .aparte-split__scrim,
|
|
7128
|
+
.aparte-split--vertical .aparte-split__scrim {
|
|
7129
|
+
cursor: row-resize;
|
|
7130
|
+
}
|
|
7131
|
+
|
|
7132
|
+
/* One pane at a time. `data-stacked` is written by the element from its breakpoint;
|
|
7133
|
+
the two modifier classes are the byte-identical CSS route for a host that owns its
|
|
7134
|
+
own breakpoint. The shown pane spans all three tracks and the seam goes with the
|
|
7135
|
+
pane it no longer separates — including its tab stop, which the element removes.
|
|
7136
|
+
|
|
7137
|
+
Not "position: 100% and a zero-size handle": that leaves a focusable separator of
|
|
7138
|
+
no width sitting in the tab order. */
|
|
7139
|
+
aparte-split[data-stacked],
|
|
7140
|
+
.aparte-split--only-start,
|
|
7141
|
+
.aparte-split--only-end {
|
|
7142
|
+
grid-template-columns: minmax(0, 1fr);
|
|
7143
|
+
grid-template-rows: minmax(0, 1fr);
|
|
7144
|
+
}
|
|
7145
|
+
|
|
7146
|
+
aparte-split[data-stacked] > .aparte-split__handle,
|
|
7147
|
+
.aparte-split--only-start > .aparte-split__handle,
|
|
7148
|
+
.aparte-split--only-end > .aparte-split__handle {
|
|
7149
|
+
display: none;
|
|
7150
|
+
}
|
|
7151
|
+
|
|
7152
|
+
aparte-split[data-stacked]:not([pane='end']) > :nth-child(3),
|
|
7153
|
+
.aparte-split--only-start > :nth-child(3) {
|
|
7154
|
+
display: none;
|
|
7155
|
+
}
|
|
7156
|
+
|
|
7157
|
+
aparte-split[data-stacked][pane='end'] > :nth-child(1),
|
|
7158
|
+
.aparte-split--only-end > :nth-child(1) {
|
|
7159
|
+
display: none;
|
|
7160
|
+
}
|
|
7161
|
+
|
|
7162
|
+
/* `disabled` is inert, so it must not look grabbable: the element takes the tab stop
|
|
7163
|
+
away and swallows the pointer, and a resize cursor plus a hover highlight on a seam
|
|
7164
|
+
that answers nothing is exactly the half-rendered affordance the house rule forbids.
|
|
7165
|
+
The seam itself stays drawn — it still separates two panes.
|
|
7166
|
+
|
|
7167
|
+
Last in the sheet because it must out-rank the vertical `cursor: row-resize` above,
|
|
7168
|
+
which it ties with on specificity. Attribute only: the static recipe has no
|
|
7169
|
+
behaviour, so there is no live-versus-static pair to keep in step. */
|
|
7170
|
+
aparte-split[disabled] > .aparte-split__handle {
|
|
7171
|
+
cursor: default;
|
|
7172
|
+
}
|
|
7173
|
+
|
|
7174
|
+
aparte-split[disabled] > .aparte-split__handle:hover {
|
|
7175
|
+
background: var(--aparte-border);
|
|
7176
|
+
}
|
|
6312
7177
|
/*
|
|
6313
7178
|
* aparté — Reset-agnostic typography for model-authored markdown.
|
|
6314
7179
|
*
|
|
@@ -6476,6 +7341,26 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6476
7341
|
overflow-y: auto;
|
|
6477
7342
|
}
|
|
6478
7343
|
|
|
7344
|
+
/* A fenced block that came through MARKDOWN, not through the `code` segment.
|
|
7345
|
+
`@aparte/plugin-marked` renders ``` as a bare <pre><code>, and the recipe below is
|
|
7346
|
+
scoped to `.aparte-code-content-wrapper` — a class only the code SEGMENT renderer
|
|
7347
|
+
emits, which marked cannot produce. So a markdown block matched no `pre` rule at all
|
|
7348
|
+
and kept the UA's `white-space: pre`: it never wrapped, laid out at its own intrinsic
|
|
7349
|
+
width, and `aparte-chat-bubble`'s `overflow: hidden` amputated the tail. No scrollbar,
|
|
7350
|
+
no ellipsis — the code was simply gone. Measured on the vanilla example: one block's
|
|
7351
|
+
`scrollWidth` was a CONSTANT 963px at chat widths 1500, 800, 600, 512 and 380, against
|
|
7352
|
+
client widths 776 / 724 / 524 / 460 / 328. It overflowed even at 1500.
|
|
7353
|
+
|
|
7354
|
+
Only the two wrapping declarations are shared, not the surface: the padding and the
|
|
7355
|
+
background below are the code CARD's, and a markdown block has no card. Giving it one
|
|
7356
|
+
is a look decision, not this fix. */
|
|
7357
|
+
.aparte-content pre,
|
|
7358
|
+
.aparte-segment-content pre,
|
|
7359
|
+
.aparte-thinking-content pre {
|
|
7360
|
+
white-space: pre-wrap;
|
|
7361
|
+
overflow-wrap: anywhere;
|
|
7362
|
+
}
|
|
7363
|
+
|
|
6479
7364
|
.aparte-code-content-wrapper pre {
|
|
6480
7365
|
margin: 0;
|
|
6481
7366
|
padding: var(--aparte-space-6);
|
|
@@ -6489,22 +7374,6 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6489
7374
|
overflow-x: hidden;
|
|
6490
7375
|
background: var(--aparte-code-bg);
|
|
6491
7376
|
}
|
|
6492
|
-
.aparte-art-card__pane[data-pane="code"] pre {
|
|
6493
|
-
margin: 0; padding: var(--aparte-space-6);
|
|
6494
|
-
font-size: var(--aparte-font-size-md);
|
|
6495
|
-
background: var(--aparte-code-bg);
|
|
6496
|
-
}
|
|
6497
|
-
.aparte-art-file__code-pane pre {
|
|
6498
|
-
margin: 0; padding: var(--aparte-space-6);
|
|
6499
|
-
font-size: var(--aparte-font-size-md);
|
|
6500
|
-
font-family: var(--aparte-code-font-family);
|
|
6501
|
-
}
|
|
6502
|
-
.aparte-art-file__preview-pane table {
|
|
6503
|
-
border-collapse: collapse;
|
|
6504
|
-
width: 100%;
|
|
6505
|
-
font-size: var(--aparte-font-size-md);
|
|
6506
|
-
font-family: inherit;
|
|
6507
|
-
}
|
|
6508
7377
|
/*
|
|
6509
7378
|
* aparté — Container-width and pointer-modality adjustments. LAST on purpose.
|
|
6510
7379
|
*
|
|
@@ -6531,14 +7400,17 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6531
7400
|
}
|
|
6532
7401
|
|
|
6533
7402
|
/* Windows high contrast. In forced-colors mode the UA repaints colours and
|
|
6534
|
-
drops box-shadow entirely — so the
|
|
7403
|
+
drops box-shadow entirely — so the focus indicators drawn with
|
|
6535
7404
|
--aparte-focus-ring, and the error ring on an avatar, do not merely change
|
|
6536
7405
|
colour, they VANISH. An outline is painted, and `Highlight` is the system's
|
|
6537
7406
|
own focus colour, so the indicator survives without fighting the user's
|
|
6538
7407
|
palette. Only the indicators that disappear are restated here: everything
|
|
6539
|
-
whose affordance is text or a border already survives on its own
|
|
7408
|
+
whose affordance is text or a border already survives on its own — which the
|
|
7409
|
+
split's seam does not, being a 4px band with `border: 0` whose entire keyboard
|
|
7410
|
+
story is arrowing it. */
|
|
6540
7411
|
@media (forced-colors: active) {
|
|
6541
7412
|
.aparte-thumb--image[role='button']:focus-visible,
|
|
7413
|
+
.aparte-split__handle:focus-visible,
|
|
6542
7414
|
.aparte-provider-select:focus,
|
|
6543
7415
|
.aparte-model-select:focus {
|
|
6544
7416
|
outline: var(--aparte-focus-outline-width) solid Highlight;
|
|
@@ -6626,11 +7498,26 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6626
7498
|
opacity: 1;
|
|
6627
7499
|
}
|
|
6628
7500
|
|
|
6629
|
-
/*
|
|
6630
|
-
.aparte-
|
|
6631
|
-
|
|
7501
|
+
/* Nor the ✕ on a pending attachment, which is the only way to remove one. */
|
|
7502
|
+
.aparte-thumb__remove {
|
|
7503
|
+
opacity: 1;
|
|
7504
|
+
}
|
|
7505
|
+
|
|
7506
|
+
/* Hover cannot reveal the row's ⋯ on touch — keep it visible and tappable. */
|
|
7507
|
+
.aparte-conv-item__more {
|
|
6632
7508
|
opacity: 1;
|
|
6633
7509
|
--aparte-conv-action-btn-size: 28px;
|
|
6634
7510
|
}
|
|
7511
|
+
|
|
7512
|
+
/* A 4px seam is no target for a finger — the same modality rule the buttons above
|
|
7513
|
+
follow. The seam keeps its width; only the invisible grab zone grows. */
|
|
7514
|
+
.aparte-split__handle {
|
|
7515
|
+
--aparte-split-hit-area: var(--aparte-touch-target-size);
|
|
7516
|
+
}
|
|
7517
|
+
|
|
7518
|
+
/* A 2px tick is no target for a finger; the minimaps of the market hide on touch too. */
|
|
7519
|
+
aparte-scroll-rail {
|
|
7520
|
+
display: none;
|
|
7521
|
+
}
|
|
6635
7522
|
}
|
|
6636
7523
|
|