@aparte/core 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/composer/aparte-composer-attachments.d.ts +0 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +11 -11
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +20 -11
- 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 +19 -1
- 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 +9219 -6730
- 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 +1217 -396
- package/dist/index.d.ts +26 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2097 -295
- 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 {
|
|
@@ -4196,6 +4464,9 @@ aparte-chat-viewport[framework-managed] {
|
|
|
4196
4464
|
padding-bottom: calc(var(--aparte-viewport-padding) + var(--aparte-fw-spacer, 0px));
|
|
4197
4465
|
scrollbar-width: thin;
|
|
4198
4466
|
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
4467
|
+
/* Same gutter rule as `.aparte-viewport-container` above: the host is the scroll
|
|
4468
|
+
surface here, so it is the host that reserves it. */
|
|
4469
|
+
scrollbar-gutter: stable both-edges;
|
|
4199
4470
|
}
|
|
4200
4471
|
|
|
4201
4472
|
/* The framework wrapper renders bubbles as DIRECT flex children of the viewport
|
|
@@ -4631,6 +4902,31 @@ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.apar
|
|
|
4631
4902
|
.aparte-message[data-empty] {
|
|
4632
4903
|
display: none;
|
|
4633
4904
|
}
|
|
4905
|
+
|
|
4906
|
+
/* ─── The compaction notice ────────────────────────────────────────────────────
|
|
4907
|
+
A message with `compaction: true` — the summary `compact()` injected in place of
|
|
4908
|
+
the turns it replaced — is a notice, not a reply: centred, muted, without avatar,
|
|
4909
|
+
name, time or actions. Its role on the wire is `user` (context handed to the
|
|
4910
|
+
model), and the bubble would otherwise draw it as something the user said. */
|
|
4911
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-avatar,
|
|
4912
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-header,
|
|
4913
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-footer,
|
|
4914
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-action-bar,
|
|
4915
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-branch-picker {
|
|
4916
|
+
display: none;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-message {
|
|
4920
|
+
justify-content: center;
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4923
|
+
aparte-chat-bubble[data-kind="compaction"] .aparte-message-content {
|
|
4924
|
+
max-width: 100%;
|
|
4925
|
+
background: var(--aparte-surface-2);
|
|
4926
|
+
color: var(--aparte-text-muted);
|
|
4927
|
+
border: var(--aparte-border-width) dashed var(--aparte-border);
|
|
4928
|
+
font-size: var(--aparte-font-size-sm);
|
|
4929
|
+
}
|
|
4634
4930
|
/*
|
|
4635
4931
|
* aparté — The composer and everything it holds: editor, send, attachments, model picker.
|
|
4636
4932
|
*
|
|
@@ -4715,7 +5011,14 @@ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
|
|
|
4715
5011
|
border-radius: 50%;
|
|
4716
5012
|
opacity: 0;
|
|
4717
5013
|
}
|
|
4718
|
-
|
|
5014
|
+
/* Hover is not the only way in. The ✕ is the ONLY way to drop a pending attachment,
|
|
5015
|
+
and at `opacity: 0` it was invisible to a keyboard user standing on it — a focus
|
|
5016
|
+
ring drawn around nothing. `:focus-within` on the tile is the same pair the message
|
|
5017
|
+
action bar (bubble.css) and the conversation row (conversation.css) already use;
|
|
5018
|
+
this one had been written with the hover half alone. The touch half is in
|
|
5019
|
+
responsive.css, beside the row's ⋯, for the same reason: a finger cannot hover. */
|
|
5020
|
+
.aparte-thumb:hover .aparte-thumb__remove,
|
|
5021
|
+
.aparte-thumb:focus-within .aparte-thumb__remove {
|
|
4719
5022
|
opacity: 1;
|
|
4720
5023
|
}
|
|
4721
5024
|
.aparte-thumb__remove:hover {
|
|
@@ -5525,256 +5828,6 @@ details[open] > .aparte-tool-summary .aparte-tool-toggle { transform: rotate(180
|
|
|
5525
5828
|
border-collapse: collapse;
|
|
5526
5829
|
}
|
|
5527
5830
|
|
|
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
5831
|
/*
|
|
5779
5832
|
* aparté — The elicitation panel and the approval gate.
|
|
5780
5833
|
*
|
|
@@ -6023,6 +6076,43 @@ textarea.aparte-elic-text {
|
|
|
6023
6076
|
recipe. See `buildApprovalPanel` for why colour lost: an alert's vocabulary on a
|
|
6024
6077
|
control, and 2.19:1 on the dark palette when it was tried as a fill instead. */
|
|
6025
6078
|
|
|
6079
|
+
/* What is being decided, between the question and the options.
|
|
6080
|
+
|
|
6081
|
+
The panel used to carry the tool's NAME and nothing else, so the arguments — the
|
|
6082
|
+
whole of what a person approves — were only in the transcript row, behind a closed
|
|
6083
|
+
disclosure. Capped and scrollable rather than unbounded: the panel itself is capped
|
|
6084
|
+
at half the viewport, and a plan or a diff arriving as a tool argument would
|
|
6085
|
+
otherwise push the Approve button off the bottom of the surface it is asked on.
|
|
6086
|
+
|
|
6087
|
+
Monospace, because it is JSON: the model's own bytes, not prose about them. */
|
|
6088
|
+
.aparte-approval-args-label {
|
|
6089
|
+
margin: var(--aparte-space-3) var(--aparte-space-3) 0;
|
|
6090
|
+
font-size: var(--aparte-font-size-xs);
|
|
6091
|
+
text-transform: uppercase;
|
|
6092
|
+
letter-spacing: 0.04em;
|
|
6093
|
+
color: var(--aparte-text-muted);
|
|
6094
|
+
}
|
|
6095
|
+
|
|
6096
|
+
.aparte-approval-args {
|
|
6097
|
+
margin: var(--aparte-space-1) var(--aparte-space-3) 0;
|
|
6098
|
+
padding: var(--aparte-space-3);
|
|
6099
|
+
max-height: var(--aparte-approval-args-max-height, 8.5rem);
|
|
6100
|
+
overflow: auto;
|
|
6101
|
+
font-family: var(--aparte-code-font-family);
|
|
6102
|
+
font-size: var(--aparte-approval-args-size, var(--aparte-font-size-sm));
|
|
6103
|
+
line-height: var(--aparte-line-height-snug);
|
|
6104
|
+
color: var(--aparte-text);
|
|
6105
|
+
background: var(--aparte-surface-2);
|
|
6106
|
+
border-radius: var(--aparte-radius-sm);
|
|
6107
|
+
white-space: pre-wrap;
|
|
6108
|
+
overflow-wrap: anywhere;
|
|
6109
|
+
}
|
|
6110
|
+
|
|
6111
|
+
.aparte-approval-args:focus-visible {
|
|
6112
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
6113
|
+
outline-offset: 1px;
|
|
6114
|
+
}
|
|
6115
|
+
|
|
6026
6116
|
/* The refusal's reason, in the user's words. Quiet by default and one line tall: it is
|
|
6027
6117
|
the exception, not the expected act, and a box sized for a paragraph invites one. */
|
|
6028
6118
|
.aparte-approval-instruction {
|
|
@@ -6088,11 +6178,27 @@ textarea.aparte-elic-text {
|
|
|
6088
6178
|
margin-bottom: calc(-1 * var(--aparte-border-width));
|
|
6089
6179
|
}
|
|
6090
6180
|
|
|
6091
|
-
/* Answered: readable, but it is not where you are
|
|
6181
|
+
/* Answered: readable, but it is not where you are — and marked, because a colour is
|
|
6182
|
+
what the current chip has too. The mark is the `check` glyph, sized by the chip. */
|
|
6092
6183
|
.aparte-elic-step[data-answered] {
|
|
6093
6184
|
color: var(--aparte-text);
|
|
6094
6185
|
}
|
|
6095
6186
|
|
|
6187
|
+
.aparte-elic-step__mark {
|
|
6188
|
+
display: inline-flex;
|
|
6189
|
+
align-items: center;
|
|
6190
|
+
margin-inline-end: var(--aparte-space-1);
|
|
6191
|
+
color: var(--aparte-success);
|
|
6192
|
+
--aparte-icon-size: 0.85em;
|
|
6193
|
+
}
|
|
6194
|
+
|
|
6195
|
+
/* The tag beside a recommended option's label: the recipe draws it, this only keeps it
|
|
6196
|
+
from stretching the title line. */
|
|
6197
|
+
.aparte-elic-option-badge {
|
|
6198
|
+
vertical-align: middle;
|
|
6199
|
+
margin-inline-start: var(--aparte-space-1);
|
|
6200
|
+
}
|
|
6201
|
+
|
|
6096
6202
|
.aparte-elic-step[aria-selected='true'] {
|
|
6097
6203
|
color: var(--aparte-text);
|
|
6098
6204
|
font-weight: var(--aparte-font-weight-semibold);
|
|
@@ -6110,16 +6216,37 @@ textarea.aparte-elic-text {
|
|
|
6110
6216
|
* them in the order listed there, and check:derived-vars reads them concatenated in
|
|
6111
6217
|
* that same order, the way a browser does.
|
|
6112
6218
|
*/
|
|
6219
|
+
|
|
6220
|
+
/* A date group: its heading wears the menu's label recipe (`.aparte-menu__label`),
|
|
6221
|
+
because that is what a heading over a list of rows is. Only the rhythm is its own. */
|
|
6222
|
+
.aparte-conv-group {
|
|
6223
|
+
display: flex;
|
|
6224
|
+
flex-direction: column;
|
|
6225
|
+
gap: var(--aparte-conv-list-gap);
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
.aparte-conv-group + .aparte-conv-group {
|
|
6229
|
+
margin-block-start: var(--aparte-space-3);
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6113
6232
|
/* A row in a list, which is what `.aparte-menu__item` is. What stays is what belongs
|
|
6114
6233
|
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.
|
|
6234
|
+
measurements, because a sidebar row is tighter than a dropdown row.
|
|
6235
|
+
|
|
6236
|
+
The row is a flex box around two real buttons — the title, which selects, and the
|
|
6237
|
+
`⋯`. The padding sits on the title button, not the row, so that the button's focus
|
|
6238
|
+
ring is the row's shape and a click on the row's inner margin lands on the button
|
|
6239
|
+
rather than on nothing. */
|
|
6116
6240
|
.aparte-conv-item {
|
|
6241
|
+
position: relative;
|
|
6117
6242
|
min-width: 0;
|
|
6118
6243
|
gap: var(--aparte-conv-item-gap);
|
|
6119
|
-
padding:
|
|
6244
|
+
padding: 0;
|
|
6245
|
+
padding-inline-end: var(--aparte-space-2);
|
|
6120
6246
|
border-radius: var(--aparte-conv-item-radius);
|
|
6121
6247
|
color: var(--aparte-conv-item-color);
|
|
6122
6248
|
font-size: var(--aparte-conv-item-font-size);
|
|
6249
|
+
cursor: default;
|
|
6123
6250
|
}
|
|
6124
6251
|
|
|
6125
6252
|
/* Hover moves the ground UP, so the label has to move with it.
|
|
@@ -6134,31 +6261,36 @@ textarea.aparte-elic-text {
|
|
|
6134
6261
|
color: var(--aparte-conv-item-color-active);
|
|
6135
6262
|
}
|
|
6136
6263
|
|
|
6137
|
-
.aparte-conv-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6264
|
+
.aparte-conv-item__select {
|
|
6265
|
+
appearance: none;
|
|
6266
|
+
-webkit-appearance: none;
|
|
6267
|
+
display: flex;
|
|
6268
|
+
align-items: center;
|
|
6269
|
+
flex: 1;
|
|
6270
|
+
min-width: 0;
|
|
6271
|
+
padding: var(--aparte-conv-item-padding);
|
|
6272
|
+
border: none;
|
|
6273
|
+
border-radius: inherit;
|
|
6274
|
+
background: transparent;
|
|
6275
|
+
color: inherit;
|
|
6276
|
+
font: inherit;
|
|
6277
|
+
text-align: start;
|
|
6278
|
+
cursor: pointer;
|
|
6144
6279
|
}
|
|
6145
6280
|
|
|
6146
|
-
|
|
6147
|
-
|
|
6281
|
+
/* Inside the edge: the list clips horizontally (`overflow-x: hidden` in base.css), and
|
|
6282
|
+
a ring drawn outside the row was cut on both sides. */
|
|
6283
|
+
.aparte-conv-item__select:focus-visible {
|
|
6284
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-primary);
|
|
6285
|
+
outline-offset: calc(-1 * var(--aparte-focus-outline-width));
|
|
6148
6286
|
}
|
|
6149
6287
|
|
|
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);
|
|
6288
|
+
.aparte-conv-item__title {
|
|
6289
|
+
flex: 1;
|
|
6290
|
+
white-space: nowrap;
|
|
6291
|
+
overflow: hidden;
|
|
6292
|
+
text-overflow: ellipsis;
|
|
6293
|
+
min-width: 0;
|
|
6162
6294
|
}
|
|
6163
6295
|
|
|
6164
6296
|
/* The conversation you are in wears the mark's bar (display/mark.css) on its start
|
|
@@ -6166,7 +6298,6 @@ textarea.aparte-elic-text {
|
|
|
6166
6298
|
a checked field choice carry. Its ground keeps the list's own tokens: a navigation
|
|
6167
6299
|
list is read for a long time, and the mark's tint on every visit was too loud. */
|
|
6168
6300
|
.aparte-conv-item--active {
|
|
6169
|
-
position: relative;
|
|
6170
6301
|
background: var(--aparte-conv-item-bg-active);
|
|
6171
6302
|
color: var(--aparte-conv-item-color-active);
|
|
6172
6303
|
font-weight: var(--aparte-conv-item-font-weight-active);
|
|
@@ -6182,61 +6313,73 @@ textarea.aparte-elic-text {
|
|
|
6182
6313
|
pointer-events: none;
|
|
6183
6314
|
}
|
|
6184
6315
|
|
|
6185
|
-
.aparte-conv-item--
|
|
6186
|
-
|
|
6316
|
+
.aparte-conv-item--archived .aparte-conv-item__title {
|
|
6317
|
+
font-style: italic;
|
|
6318
|
+
opacity: 0.85;
|
|
6187
6319
|
}
|
|
6188
6320
|
|
|
6189
|
-
|
|
6321
|
+
/* The `⋯`: one quiet button per row, shown by hover, by focus, on the active row and
|
|
6322
|
+
while its menu is open — never a permanent control, and never a red one. What the
|
|
6323
|
+
recipe gives (shape, states, focus ring) it keeps; only the size and the resting
|
|
6324
|
+
colour are the row's. */
|
|
6325
|
+
.aparte-conv-item__more {
|
|
6326
|
+
flex-shrink: 0;
|
|
6190
6327
|
opacity: 0;
|
|
6328
|
+
width: var(--aparte-conv-action-btn-size);
|
|
6329
|
+
height: var(--aparte-conv-action-btn-size);
|
|
6330
|
+
color: var(--aparte-text-muted);
|
|
6191
6331
|
}
|
|
6192
6332
|
|
|
6193
|
-
.aparte-conv-
|
|
6333
|
+
.aparte-conv-item:hover .aparte-conv-item__more,
|
|
6334
|
+
.aparte-conv-item:focus-within .aparte-conv-item__more,
|
|
6335
|
+
.aparte-conv-item--active .aparte-conv-item__more,
|
|
6336
|
+
.aparte-conv-item__more[aria-expanded="true"] {
|
|
6337
|
+
opacity: 1;
|
|
6338
|
+
}
|
|
6339
|
+
|
|
6340
|
+
.aparte-conv-item__more:hover {
|
|
6341
|
+
color: var(--aparte-text);
|
|
6342
|
+
}
|
|
6343
|
+
|
|
6344
|
+
/* The rename input takes the title button's place, so it inherits the row's slot and
|
|
6345
|
+
only needs the field recipe plus a little air. */
|
|
6346
|
+
.aparte-conv-item__input {
|
|
6194
6347
|
flex: 1;
|
|
6195
|
-
white-space: nowrap;
|
|
6196
|
-
overflow: hidden;
|
|
6197
|
-
text-overflow: ellipsis;
|
|
6198
6348
|
min-width: 0;
|
|
6349
|
+
margin: var(--aparte-space-1) 0 var(--aparte-space-1) var(--aparte-space-1);
|
|
6199
6350
|
}
|
|
6200
6351
|
|
|
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);
|
|
6352
|
+
/* The row's menu: the menu recipe draws it, the element places it (`position: fixed`,
|
|
6353
|
+
`top`/`left` computed at open time, closed on any scroll). Fixed rather than
|
|
6354
|
+
absolute because the list scrolls, and an absolute menu on the last rows was clipped
|
|
6355
|
+
by that overflow. */
|
|
6356
|
+
.aparte-conv-menu {
|
|
6357
|
+
position: fixed;
|
|
6358
|
+
--aparte-icon-size: var(--aparte-icon-size-sm);
|
|
6216
6359
|
}
|
|
6217
6360
|
|
|
6218
|
-
.aparte-conv-
|
|
6219
|
-
color: var(--aparte-
|
|
6361
|
+
.aparte-conv-menu__item--danger {
|
|
6362
|
+
color: var(--aparte-error);
|
|
6220
6363
|
}
|
|
6221
6364
|
|
|
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)));
|
|
6365
|
+
/* The one question the menu asks. */
|
|
6366
|
+
.aparte-conv-menu__confirm {
|
|
6367
|
+
display: flex;
|
|
6368
|
+
flex-direction: column;
|
|
6369
|
+
gap: var(--aparte-space-3);
|
|
6370
|
+
padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
6231
6371
|
}
|
|
6232
6372
|
|
|
6233
|
-
.aparte-conv-
|
|
6234
|
-
|
|
6373
|
+
.aparte-conv-menu__question {
|
|
6374
|
+
margin: 0;
|
|
6375
|
+
font-size: var(--aparte-font-size-sm);
|
|
6376
|
+
line-height: var(--aparte-line-height-snug);
|
|
6235
6377
|
}
|
|
6236
6378
|
|
|
6237
|
-
.aparte-conv-
|
|
6238
|
-
|
|
6239
|
-
|
|
6379
|
+
.aparte-conv-menu__actions {
|
|
6380
|
+
display: flex;
|
|
6381
|
+
justify-content: flex-end;
|
|
6382
|
+
gap: var(--aparte-space-2);
|
|
6240
6383
|
}
|
|
6241
6384
|
/*
|
|
6242
6385
|
* aparté — <aparte-suggestions>: the row of prompt starters.
|
|
@@ -6309,6 +6452,682 @@ aparte-context[data-level="warn"] .aparte-progress__bar {
|
|
|
6309
6452
|
aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
6310
6453
|
background: var(--aparte-error);
|
|
6311
6454
|
}
|
|
6455
|
+
|
|
6456
|
+
/* The ring variant: a track and a value circle on a 36-unit box, sized by the two tokens.
|
|
6457
|
+
`pathLength="100"` on the value circle (set by the element) makes the dash the
|
|
6458
|
+
percentage itself, so the sheet needs no circumference. Rotated a quarter turn so the
|
|
6459
|
+
value starts at twelve o'clock. */
|
|
6460
|
+
.aparte-context--ring {
|
|
6461
|
+
gap: var(--aparte-space-1);
|
|
6462
|
+
}
|
|
6463
|
+
|
|
6464
|
+
.aparte-context__ring {
|
|
6465
|
+
flex: none;
|
|
6466
|
+
width: var(--aparte-context-ring-size);
|
|
6467
|
+
height: var(--aparte-context-ring-size);
|
|
6468
|
+
transform: rotate(-90deg);
|
|
6469
|
+
}
|
|
6470
|
+
|
|
6471
|
+
.aparte-context__track,
|
|
6472
|
+
.aparte-context__value {
|
|
6473
|
+
fill: none;
|
|
6474
|
+
stroke-width: var(--aparte-context-ring-stroke);
|
|
6475
|
+
}
|
|
6476
|
+
|
|
6477
|
+
.aparte-context__track {
|
|
6478
|
+
stroke: var(--aparte-surface-3);
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6481
|
+
.aparte-context__value {
|
|
6482
|
+
--aparte-context-ratio: 0;
|
|
6483
|
+
stroke: var(--aparte-progress-fill);
|
|
6484
|
+
stroke-linecap: round;
|
|
6485
|
+
stroke-dasharray: var(--aparte-context-ratio) 100;
|
|
6486
|
+
transition: stroke-dasharray var(--aparte-duration-fast) var(--aparte-ease);
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6489
|
+
.aparte-context__value--empty {
|
|
6490
|
+
visibility: hidden;
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
aparte-context[data-level="warn"] .aparte-context__value {
|
|
6494
|
+
stroke: var(--aparte-warning);
|
|
6495
|
+
}
|
|
6496
|
+
aparte-context[data-level="danger"] .aparte-context__value {
|
|
6497
|
+
stroke: var(--aparte-error);
|
|
6498
|
+
}
|
|
6499
|
+
/*
|
|
6500
|
+
* aparté — <aparte-scroll-rail>: the ticks beside the transcript.
|
|
6501
|
+
*
|
|
6502
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6503
|
+
* them concatenated in that same order, the way a browser does.
|
|
6504
|
+
*
|
|
6505
|
+
* The rail floats on the transcript's end edge, vertically centred, as a child of the
|
|
6506
|
+
* chat host. `:has()` gives the host its containing block only while a rail is inside
|
|
6507
|
+
* it — a bare `position: relative` on every chat would have moved any absolutely
|
|
6508
|
+
* positioned child a consumer already placed there.
|
|
6509
|
+
*/
|
|
6510
|
+
aparte-chat:has(> aparte-scroll-rail),
|
|
6511
|
+
[data-aparte-chat]:has(> aparte-scroll-rail) {
|
|
6512
|
+
position: relative;
|
|
6513
|
+
}
|
|
6514
|
+
|
|
6515
|
+
aparte-scroll-rail {
|
|
6516
|
+
position: absolute;
|
|
6517
|
+
inset-inline-end: var(--aparte-space-2);
|
|
6518
|
+
top: 50%;
|
|
6519
|
+
transform: translateY(-50%);
|
|
6520
|
+
z-index: var(--aparte-z-floating);
|
|
6521
|
+
display: block;
|
|
6522
|
+
width: var(--aparte-scroll-rail-width);
|
|
6523
|
+
max-height: calc(100% - 2 * var(--aparte-space-8));
|
|
6524
|
+
overflow: hidden;
|
|
6525
|
+
}
|
|
6526
|
+
|
|
6527
|
+
aparte-scroll-rail[hidden],
|
|
6528
|
+
aparte-scroll-rail[data-empty] {
|
|
6529
|
+
display: none;
|
|
6530
|
+
}
|
|
6531
|
+
|
|
6532
|
+
.aparte-scroll-rail__list {
|
|
6533
|
+
list-style: none;
|
|
6534
|
+
margin: 0;
|
|
6535
|
+
padding: 0;
|
|
6536
|
+
display: flex;
|
|
6537
|
+
flex-direction: column;
|
|
6538
|
+
align-items: flex-end;
|
|
6539
|
+
gap: var(--aparte-scroll-rail-gap);
|
|
6540
|
+
}
|
|
6541
|
+
|
|
6542
|
+
/* A tick is a real button, drawn as a line. The hit area is the gap around it, drawn
|
|
6543
|
+
by the pseudo-element so the line itself can stay two pixels. */
|
|
6544
|
+
.aparte-scroll-rail__tick {
|
|
6545
|
+
appearance: none;
|
|
6546
|
+
-webkit-appearance: none;
|
|
6547
|
+
position: relative;
|
|
6548
|
+
display: block;
|
|
6549
|
+
width: var(--aparte-scroll-rail-tick-size);
|
|
6550
|
+
height: var(--aparte-scroll-rail-tick-thickness);
|
|
6551
|
+
padding: 0;
|
|
6552
|
+
border: 0;
|
|
6553
|
+
border-radius: var(--aparte-radius-full);
|
|
6554
|
+
background: var(--aparte-border);
|
|
6555
|
+
cursor: pointer;
|
|
6556
|
+
transition:
|
|
6557
|
+
width var(--aparte-duration-fast) var(--aparte-ease),
|
|
6558
|
+
background var(--aparte-duration-fast) var(--aparte-ease);
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
.aparte-scroll-rail__tick::before {
|
|
6562
|
+
content: "";
|
|
6563
|
+
position: absolute;
|
|
6564
|
+
inset: calc(-1 * var(--aparte-scroll-rail-gap) / 2) calc(-1 * var(--aparte-space-2));
|
|
6565
|
+
}
|
|
6566
|
+
|
|
6567
|
+
.aparte-scroll-rail__tick:hover {
|
|
6568
|
+
background: var(--aparte-text-muted);
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
.aparte-scroll-rail__tick[aria-current="true"] {
|
|
6572
|
+
width: calc(var(--aparte-scroll-rail-tick-size) * 1.6);
|
|
6573
|
+
background: var(--aparte-primary);
|
|
6574
|
+
}
|
|
6575
|
+
|
|
6576
|
+
.aparte-scroll-rail__tick:focus-visible {
|
|
6577
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-primary);
|
|
6578
|
+
outline-offset: var(--aparte-focus-outline-offset);
|
|
6579
|
+
}
|
|
6580
|
+
|
|
6581
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6582
|
+
.aparte-scroll-rail__tick {
|
|
6583
|
+
transition: none;
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
/*
|
|
6587
|
+
* aparté — the shell: the chrome a whole application puts around the chat. A sidebar
|
|
6588
|
+
* of conversations, a header, and the grid that holds them beside the transcript —
|
|
6589
|
+
* what a ChatGPT-style page is made of, so a site can be built on aparté alone.
|
|
6590
|
+
*
|
|
6591
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6592
|
+
* them concatenated in that same order, the way a browser does.
|
|
6593
|
+
*/
|
|
6594
|
+
|
|
6595
|
+
/*
|
|
6596
|
+
* aparte-sidebar — the column beside the chat.
|
|
6597
|
+
* A sized column that becomes a drawer under 48rem of window. The recipe draws the
|
|
6598
|
+
* column and its four regions; `<aparte-sidebar>` adds the behaviour (collapse, the
|
|
6599
|
+
* drawer, the search filter) and sets `data-drawer` on itself from the media query,
|
|
6600
|
+
* which is the one attribute the rules below read.
|
|
6601
|
+
*
|
|
6602
|
+
* <aside class="aparte-sidebar">
|
|
6603
|
+
* <div class="aparte-sidebar__header">
|
|
6604
|
+
* <span class="aparte-sidebar__brand">aparté</span>
|
|
6605
|
+
* <button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="New chat">
|
|
6606
|
+
* <aparte-icon name="edit"></aparte-icon>
|
|
6607
|
+
* </button>
|
|
6608
|
+
* </div>
|
|
6609
|
+
* <div class="aparte-sidebar__search aparte-field-group">
|
|
6610
|
+
* <input class="aparte-field aparte-field--sm" type="search" placeholder="Search" data-aparte-sidebar-search>
|
|
6611
|
+
* </div>
|
|
6612
|
+
* <div class="aparte-sidebar__body">
|
|
6613
|
+
* <aparte-conversation-list></aparte-conversation-list>
|
|
6614
|
+
* </div>
|
|
6615
|
+
* <div class="aparte-sidebar__footer">
|
|
6616
|
+
* <span class="aparte-avatar aparte-avatar--sm">P</span> Paul
|
|
6617
|
+
* </div>
|
|
6618
|
+
* </aside>
|
|
6619
|
+
*
|
|
6620
|
+
* The element wears the same class and adds the behaviour; `<aside class="aparte-sidebar">`
|
|
6621
|
+
* is the look alone, which is what this page shows.
|
|
6622
|
+
*/
|
|
6623
|
+
aparte-sidebar,
|
|
6624
|
+
.aparte-sidebar {
|
|
6625
|
+
box-sizing: border-box;
|
|
6626
|
+
display: flex;
|
|
6627
|
+
flex-direction: column;
|
|
6628
|
+
width: var(--aparte-sidebar-width);
|
|
6629
|
+
min-width: 0;
|
|
6630
|
+
height: 100%;
|
|
6631
|
+
overflow: hidden;
|
|
6632
|
+
background: var(--aparte-sidebar-bg);
|
|
6633
|
+
border-inline-end: var(--aparte-border-width) solid var(--aparte-border);
|
|
6634
|
+
color: var(--aparte-text);
|
|
6635
|
+
transition: width var(--aparte-duration-slow) var(--aparte-ease);
|
|
6636
|
+
}
|
|
6637
|
+
|
|
6638
|
+
/* In the flow, collapsed means gone: the column folds to nothing and the chat takes
|
|
6639
|
+
the room. The children keep their width so the fold is a slide, not a squash. */
|
|
6640
|
+
.aparte-sidebar[collapsed]:not([data-drawer]) {
|
|
6641
|
+
width: 0;
|
|
6642
|
+
border-inline-end-width: 0;
|
|
6643
|
+
}
|
|
6644
|
+
|
|
6645
|
+
/* The children keep the column's width while it collapses, so nothing reflows during
|
|
6646
|
+
the transition. BORDER-box, or a padded child is the column plus its padding: the
|
|
6647
|
+
header's button sat flush against the edge and the search field ran off it, clipped
|
|
6648
|
+
— measured at 292px in a 260px column. The search field carries margins as well,
|
|
6649
|
+
which border-box does not cover; its own rule subtracts them. */
|
|
6650
|
+
.aparte-sidebar > * {
|
|
6651
|
+
box-sizing: border-box;
|
|
6652
|
+
flex-shrink: 0;
|
|
6653
|
+
min-width: var(--aparte-sidebar-width);
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
.aparte-sidebar__header {
|
|
6657
|
+
display: flex;
|
|
6658
|
+
align-items: center;
|
|
6659
|
+
gap: var(--aparte-space-3);
|
|
6660
|
+
padding: var(--aparte-space-4) var(--aparte-space-4) var(--aparte-space-2);
|
|
6661
|
+
}
|
|
6662
|
+
|
|
6663
|
+
.aparte-sidebar__brand {
|
|
6664
|
+
flex: 1;
|
|
6665
|
+
min-width: 0;
|
|
6666
|
+
font-weight: var(--aparte-font-weight-semibold);
|
|
6667
|
+
white-space: nowrap;
|
|
6668
|
+
overflow: hidden;
|
|
6669
|
+
text-overflow: ellipsis;
|
|
6670
|
+
}
|
|
6671
|
+
|
|
6672
|
+
.aparte-sidebar__search {
|
|
6673
|
+
margin: var(--aparte-space-2) var(--aparte-space-3);
|
|
6674
|
+
min-width: calc(var(--aparte-sidebar-width) - 2 * var(--aparte-space-3));
|
|
6675
|
+
}
|
|
6676
|
+
|
|
6677
|
+
/* The body is the one region that scrolls; the list inside keeps its own gap. */
|
|
6678
|
+
.aparte-sidebar__body {
|
|
6679
|
+
flex: 1 1 auto;
|
|
6680
|
+
min-height: 0;
|
|
6681
|
+
overflow-y: auto;
|
|
6682
|
+
overflow-x: hidden;
|
|
6683
|
+
padding: var(--aparte-space-2) var(--aparte-space-3);
|
|
6684
|
+
scrollbar-width: thin;
|
|
6685
|
+
scrollbar-color: var(--aparte-scrollbar-thumb) var(--aparte-scrollbar-track);
|
|
6686
|
+
}
|
|
6687
|
+
|
|
6688
|
+
.aparte-sidebar__footer {
|
|
6689
|
+
display: flex;
|
|
6690
|
+
align-items: center;
|
|
6691
|
+
gap: var(--aparte-space-3);
|
|
6692
|
+
padding: var(--aparte-space-3) var(--aparte-space-4);
|
|
6693
|
+
border-block-start: var(--aparte-border-width) solid var(--aparte-border);
|
|
6694
|
+
font-size: var(--aparte-font-size-sm);
|
|
6695
|
+
}
|
|
6696
|
+
|
|
6697
|
+
/* The drawer: the same element, out of the flow, over the page. `inset-inline-start`
|
|
6698
|
+
rather than a transform, so the scrim — a fixed child — stays relative to the
|
|
6699
|
+
viewport and can cover the page from inside the element. */
|
|
6700
|
+
.aparte-sidebar[data-drawer] {
|
|
6701
|
+
position: fixed;
|
|
6702
|
+
inset-block: 0;
|
|
6703
|
+
inset-inline-start: 0;
|
|
6704
|
+
z-index: var(--aparte-z-dropdown);
|
|
6705
|
+
width: var(--aparte-sidebar-width);
|
|
6706
|
+
box-shadow: var(--aparte-menu-shadow);
|
|
6707
|
+
transition: inset-inline-start var(--aparte-duration-slower) var(--aparte-ease);
|
|
6708
|
+
}
|
|
6709
|
+
|
|
6710
|
+
.aparte-sidebar[data-drawer][collapsed] {
|
|
6711
|
+
inset-inline-start: calc(-1 * var(--aparte-sidebar-width) - var(--aparte-space-4));
|
|
6712
|
+
box-shadow: none;
|
|
6713
|
+
}
|
|
6714
|
+
|
|
6715
|
+
.aparte-sidebar__scrim {
|
|
6716
|
+
position: fixed;
|
|
6717
|
+
inset: 0;
|
|
6718
|
+
z-index: -1;
|
|
6719
|
+
background: var(--aparte-scrim);
|
|
6720
|
+
}
|
|
6721
|
+
/*
|
|
6722
|
+
* aparté — the header row of an application shell.
|
|
6723
|
+
*
|
|
6724
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6725
|
+
* them concatenated in that same order, the way a browser does.
|
|
6726
|
+
*/
|
|
6727
|
+
|
|
6728
|
+
/*
|
|
6729
|
+
* aparte-app-header — the bar above the chat.
|
|
6730
|
+
* A recipe and nothing more, because a header has no behaviour of its own: a toggle
|
|
6731
|
+
* for the sidebar (which `<aparte-sidebar>` wires from the `data-aparte-sidebar-toggle`
|
|
6732
|
+
* attribute), the conversation's title, and an actions zone pushed to the end — a
|
|
6733
|
+
* model selector, a share button, whatever the page needs. The toggle shows only
|
|
6734
|
+
* under 48rem, where the sidebar is a drawer; above it the column is simply there.
|
|
6735
|
+
*
|
|
6736
|
+
* <header class="aparte-app-header">
|
|
6737
|
+
* <button class="aparte-btn aparte-btn--icon aparte-app-header__toggle" type="button" aria-label="Toggle the sidebar" data-aparte-sidebar-toggle>☰</button>
|
|
6738
|
+
* <span class="aparte-app-header__title">Deploy checklist</span>
|
|
6739
|
+
* <div class="aparte-app-header__actions">
|
|
6740
|
+
* <span class="aparte-tag">gpt-4.1</span>
|
|
6741
|
+
* <button class="aparte-btn aparte-btn--sm aparte-btn--ghost" type="button">Share</button>
|
|
6742
|
+
* </div>
|
|
6743
|
+
* </header>
|
|
6744
|
+
*/
|
|
6745
|
+
.aparte-app-header {
|
|
6746
|
+
box-sizing: border-box;
|
|
6747
|
+
display: flex;
|
|
6748
|
+
align-items: center;
|
|
6749
|
+
gap: var(--aparte-space-3);
|
|
6750
|
+
min-height: var(--aparte-app-header-height);
|
|
6751
|
+
padding: var(--aparte-space-2) var(--aparte-space-4);
|
|
6752
|
+
background: var(--aparte-surface-1);
|
|
6753
|
+
border-block-end: var(--aparte-border-width) solid var(--aparte-border);
|
|
6754
|
+
color: var(--aparte-text);
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6757
|
+
.aparte-app-header__title {
|
|
6758
|
+
flex: 1;
|
|
6759
|
+
min-width: 0;
|
|
6760
|
+
font-weight: var(--aparte-font-weight-medium);
|
|
6761
|
+
white-space: nowrap;
|
|
6762
|
+
overflow: hidden;
|
|
6763
|
+
text-overflow: ellipsis;
|
|
6764
|
+
}
|
|
6765
|
+
|
|
6766
|
+
.aparte-app-header__actions {
|
|
6767
|
+
display: flex;
|
|
6768
|
+
align-items: center;
|
|
6769
|
+
gap: var(--aparte-space-2);
|
|
6770
|
+
margin-inline-start: auto;
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
.aparte-app-header__toggle {
|
|
6774
|
+
display: none;
|
|
6775
|
+
}
|
|
6776
|
+
|
|
6777
|
+
/* The same 48rem the sidebar turns into a drawer at (`sidebar.css`). */
|
|
6778
|
+
@media (max-width: 48rem) {
|
|
6779
|
+
.aparte-app-header__toggle {
|
|
6780
|
+
display: inline-flex;
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
/*
|
|
6784
|
+
* aparté — the grid that holds a sidebar, a header and the chat.
|
|
6785
|
+
*
|
|
6786
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6787
|
+
* them concatenated in that same order, the way a browser does.
|
|
6788
|
+
*/
|
|
6789
|
+
|
|
6790
|
+
/*
|
|
6791
|
+
* aparte-app-shell — sidebar beside, header above, the chat in the rest.
|
|
6792
|
+
* A recipe: two columns and two rows, the sidebar spanning both rows, the header and
|
|
6793
|
+
* the main area stacked in the second column. The chat fills `__main`, which is what
|
|
6794
|
+
* gives it the height it needs to scroll (see the layout guide). Size the shell —
|
|
6795
|
+
* `height: 100dvh` for a page — and everything inside follows. Under 48rem the sidebar
|
|
6796
|
+
* leaves the grid to become a drawer, and the header's toggle appears.
|
|
6797
|
+
*
|
|
6798
|
+
* <div class="aparte-app-shell" style="height: 18rem">
|
|
6799
|
+
* <aside class="aparte-sidebar">
|
|
6800
|
+
* <div class="aparte-sidebar__header"><span class="aparte-sidebar__brand">aparté</span></div>
|
|
6801
|
+
* <div class="aparte-sidebar__body"><aparte-conversation-list></aparte-conversation-list></div>
|
|
6802
|
+
* </aside>
|
|
6803
|
+
* <header class="aparte-app-header"><span class="aparte-app-header__title">Deploy checklist</span></header>
|
|
6804
|
+
* <main class="aparte-app-shell__main">
|
|
6805
|
+
* <aparte-chat>
|
|
6806
|
+
* <aparte-chat-viewport></aparte-chat-viewport>
|
|
6807
|
+
* <aparte-composer>
|
|
6808
|
+
* <div class="aparte-composer-shell">
|
|
6809
|
+
* <div class="aparte-composer-row">
|
|
6810
|
+
* <aparte-composer-input style="flex: 1"></aparte-composer-input>
|
|
6811
|
+
* <aparte-composer-send></aparte-composer-send>
|
|
6812
|
+
* </div>
|
|
6813
|
+
* </div>
|
|
6814
|
+
* </aparte-composer>
|
|
6815
|
+
* </aparte-chat>
|
|
6816
|
+
* </main>
|
|
6817
|
+
* </div>
|
|
6818
|
+
*/
|
|
6819
|
+
.aparte-app-shell {
|
|
6820
|
+
box-sizing: border-box;
|
|
6821
|
+
display: grid;
|
|
6822
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
6823
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
6824
|
+
height: 100%;
|
|
6825
|
+
overflow: hidden;
|
|
6826
|
+
background: var(--aparte-surface);
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
.aparte-app-shell > aparte-sidebar,
|
|
6830
|
+
.aparte-app-shell > .aparte-sidebar {
|
|
6831
|
+
grid-row: 1 / -1;
|
|
6832
|
+
}
|
|
6833
|
+
|
|
6834
|
+
/* A drawer is out of the flow, so the grid closes the column it left. */
|
|
6835
|
+
.aparte-app-shell > .aparte-sidebar[data-drawer] {
|
|
6836
|
+
grid-row: auto;
|
|
6837
|
+
}
|
|
6838
|
+
|
|
6839
|
+
.aparte-app-shell > .aparte-app-header {
|
|
6840
|
+
grid-column: 2;
|
|
6841
|
+
}
|
|
6842
|
+
|
|
6843
|
+
.aparte-app-shell__main {
|
|
6844
|
+
grid-column: 2;
|
|
6845
|
+
min-height: 0;
|
|
6846
|
+
min-width: 0;
|
|
6847
|
+
display: flex;
|
|
6848
|
+
flex-direction: column;
|
|
6849
|
+
}
|
|
6850
|
+
|
|
6851
|
+
/* A chat — or a split holding one — fills the main area. Stated for the direct child
|
|
6852
|
+
because that is where the height comes from: `<aparte-chat>` is `height: 100%` of a
|
|
6853
|
+
parent that has none until this rule gives it one, and a split between the two is
|
|
6854
|
+
the same case one level out. A pane CONTAINS a chat; a chat never contains a split. */
|
|
6855
|
+
.aparte-app-shell__main > aparte-chat,
|
|
6856
|
+
.aparte-app-shell__main > [data-aparte-chat],
|
|
6857
|
+
.aparte-app-shell__main > aparte-split,
|
|
6858
|
+
.aparte-app-shell__main > .aparte-split {
|
|
6859
|
+
flex: 1;
|
|
6860
|
+
min-height: 0;
|
|
6861
|
+
height: auto;
|
|
6862
|
+
}
|
|
6863
|
+
/*
|
|
6864
|
+
* aparté — the seam: two panes side by side, and the grid that lets one grow while
|
|
6865
|
+
* the other gives way. The fourth piece of the shell, after the sidebar, the header
|
|
6866
|
+
* and the grid that holds them.
|
|
6867
|
+
*
|
|
6868
|
+
* One of the sheets src/index.ts imports in cascade order; check:derived-vars reads
|
|
6869
|
+
* them concatenated in that same order, the way a browser does.
|
|
6870
|
+
*/
|
|
6871
|
+
|
|
6872
|
+
/*
|
|
6873
|
+
* aparte-split — two panes and a draggable seam. The chat in one, your pane in the
|
|
6874
|
+
* other: a preview frame, an editor, an artifact card.
|
|
6875
|
+
*
|
|
6876
|
+
* The grid is the whole mechanism. `--aparte-split-position` is the primary pane's
|
|
6877
|
+
* size; `--aparte-split-min` and `--aparte-split-max` are clamp bounds, so the
|
|
6878
|
+
* browser does the clamping and nothing in JS parses a unit — px, %, rem and ch all
|
|
6879
|
+
* work. Used without <aparte-split> this is a static split: set the position from
|
|
6880
|
+
* your own media query and there is no drag, which is the Canvas shape.
|
|
6881
|
+
*
|
|
6882
|
+
* A pane CONTAINS a chat; a chat never contains a split.
|
|
6883
|
+
*
|
|
6884
|
+
* <div class="aparte-split" style="height: 18rem">
|
|
6885
|
+
* <div>the chat</div>
|
|
6886
|
+
* <div class="aparte-split__handle"></div>
|
|
6887
|
+
* <div class="aparte-split__pane">the pane</div>
|
|
6888
|
+
* </div>
|
|
6889
|
+
*
|
|
6890
|
+
* The element wears the same class and adds the behaviour (drag, arrow keys, the APG
|
|
6891
|
+
* separator's ARIA, one pane at a time under a breakpoint); `<div class="aparte-split">`
|
|
6892
|
+
* is the look alone, which is what this page shows.
|
|
6893
|
+
*/
|
|
6894
|
+
|
|
6895
|
+
/* The outer clamp is a guard, not decoration: a `--aparte-split-min` wider than the
|
|
6896
|
+
container would otherwise eat the free pane whole and push the handle off the end.
|
|
6897
|
+
Three tracks, always — the primary pane, the seam, the rest. */
|
|
6898
|
+
aparte-split,
|
|
6899
|
+
.aparte-split {
|
|
6900
|
+
box-sizing: border-box;
|
|
6901
|
+
display: grid;
|
|
6902
|
+
grid-template-columns:
|
|
6903
|
+
clamp(
|
|
6904
|
+
0px,
|
|
6905
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
6906
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
6907
|
+
)
|
|
6908
|
+
var(--aparte-split-handle-size)
|
|
6909
|
+
minmax(0, 1fr);
|
|
6910
|
+
height: 100%;
|
|
6911
|
+
min-width: 0;
|
|
6912
|
+
overflow: hidden;
|
|
6913
|
+
}
|
|
6914
|
+
|
|
6915
|
+
/* A grid item's automatic minimum size is its content, so one long line in either
|
|
6916
|
+
pane refuses to shrink and the seam stops tracking the pointer.
|
|
6917
|
+
|
|
6918
|
+
The tag is listed beside the class here and below for the reason the grid rule
|
|
6919
|
+
lists it: the element adds the class at connect, so between parse and upgrade a
|
|
6920
|
+
server-rendered split would be a grid whose items keep their content minimum. */
|
|
6921
|
+
aparte-split > *,
|
|
6922
|
+
.aparte-split > * {
|
|
6923
|
+
min-inline-size: 0;
|
|
6924
|
+
min-block-size: 0;
|
|
6925
|
+
}
|
|
6926
|
+
|
|
6927
|
+
/* The outside-the-shell half of the direct-child trap: <aparte-chat> is `height: 100%`
|
|
6928
|
+
of a parent that has none unless the grid track gives it one. Inside the app shell
|
|
6929
|
+
the same fix lives on `.aparte-app-shell__main`; split.css is imported after
|
|
6930
|
+
app-shell.css, so a chat in a pane inside a shell reads this one.
|
|
6931
|
+
|
|
6932
|
+
`box-sizing` with it, because the chat's own sheet gives it a bottom padding — the
|
|
6933
|
+
16px of breathing room under the composer that core owns on purpose. Left in the
|
|
6934
|
+
default content-box that padding lands OUTSIDE the 100%, so the chat's border box
|
|
6935
|
+
overflows its track by exactly the gap and `.aparte-split`'s `overflow: hidden`
|
|
6936
|
+
eats it: the composer sits flush on the seam's bottom edge and the gap is gone. */
|
|
6937
|
+
aparte-split > aparte-chat,
|
|
6938
|
+
aparte-split > [data-aparte-chat],
|
|
6939
|
+
.aparte-split > aparte-chat,
|
|
6940
|
+
.aparte-split > [data-aparte-chat] {
|
|
6941
|
+
box-sizing: border-box;
|
|
6942
|
+
height: 100%;
|
|
6943
|
+
min-height: 0;
|
|
6944
|
+
}
|
|
6945
|
+
|
|
6946
|
+
/* The seam. `touch-action: none` so a drag on a touch screen resizes rather than
|
|
6947
|
+
scrolling the page — without it the browser claims the gesture at the first move
|
|
6948
|
+
and the pointer events stop arriving. */
|
|
6949
|
+
.aparte-split__handle {
|
|
6950
|
+
position: relative;
|
|
6951
|
+
box-sizing: border-box;
|
|
6952
|
+
border: 0;
|
|
6953
|
+
padding: 0;
|
|
6954
|
+
background: var(--aparte-border);
|
|
6955
|
+
cursor: col-resize;
|
|
6956
|
+
touch-action: none;
|
|
6957
|
+
transition: background-color var(--aparte-duration-fast) var(--aparte-ease);
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6960
|
+
.aparte-split__handle:hover,
|
|
6961
|
+
.aparte-split__handle[data-dragging] {
|
|
6962
|
+
background: var(--aparte-primary);
|
|
6963
|
+
}
|
|
6964
|
+
|
|
6965
|
+
/* A real outline, and the ring kept as decoration.
|
|
6966
|
+
|
|
6967
|
+
The seam's whole keyboard story is arrowing it, and its only focus indicator was
|
|
6968
|
+
`--aparte-focus-ring` — a soft wash that measures 1.39:1 against the page in the
|
|
6969
|
+
light palette and 1.83:1 in the dark one, where WCAG asks 3:1 of a focus indicator.
|
|
6970
|
+
`outline: none` with nothing in its place is the shape of that failure: the
|
|
6971
|
+
indicator was not weak, it was absent, on a 4px band with `border: 0` that a
|
|
6972
|
+
keyboard user has no other way to find.
|
|
6973
|
+
|
|
6974
|
+
The outline is what carries the contrast (3.54:1 light, 7.36:1 dark, measured); the
|
|
6975
|
+
shadow stays because it reads as a glow AROUND the seam and the two do not fight.
|
|
6976
|
+
The forced-colors entry in responsive.css keeps overriding the colour only — it is
|
|
6977
|
+
now overriding an outline that exists rather than substituting for one that does
|
|
6978
|
+
not. */
|
|
6979
|
+
.aparte-split__handle:focus-visible {
|
|
6980
|
+
outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);
|
|
6981
|
+
outline-offset: 0;
|
|
6982
|
+
box-shadow: var(--aparte-focus-ring);
|
|
6983
|
+
}
|
|
6984
|
+
|
|
6985
|
+
/* The hit slop: a 4px seam is a 4px target, and a pointer misses it. The pseudo-element
|
|
6986
|
+
is wider than the seam and centred on it, so the grab zone reaches into both panes
|
|
6987
|
+
without the seam itself getting bigger. */
|
|
6988
|
+
.aparte-split__handle::after {
|
|
6989
|
+
content: '';
|
|
6990
|
+
position: absolute;
|
|
6991
|
+
inset-block: 0;
|
|
6992
|
+
inset-inline-start: calc(var(--aparte-split-hit-area) / -2 + var(--aparte-split-handle-size) / 2);
|
|
6993
|
+
inline-size: var(--aparte-split-hit-area);
|
|
6994
|
+
}
|
|
6995
|
+
|
|
6996
|
+
/* The drag overlay, a child of the HANDLE for the duration of one gesture. Being the
|
|
6997
|
+
topmost hit target it swallows an iframe pane's hit-testing (a drag over a frame is
|
|
6998
|
+
otherwise lost to the frame's document), stops a text selection from starting, and
|
|
6999
|
+
paints the drag cursor across the whole page. A child of the handle rather than of
|
|
7000
|
+
the body so `click` — and therefore `dblclick` — still dispatches at the handle,
|
|
7001
|
+
their nearest common ancestor.
|
|
7002
|
+
|
|
7003
|
+
It is `position: fixed`, so nothing between it and the viewport may establish a
|
|
7004
|
+
containing block: `.aparte-split` must never get `container-type`, `transform`,
|
|
7005
|
+
`filter` or `backdrop-filter`, or the overlay covers the split alone. */
|
|
7006
|
+
.aparte-split__scrim {
|
|
7007
|
+
position: fixed;
|
|
7008
|
+
inset: 0;
|
|
7009
|
+
z-index: var(--aparte-z-dropdown);
|
|
7010
|
+
cursor: col-resize;
|
|
7011
|
+
}
|
|
7012
|
+
|
|
7013
|
+
/* An optional wrapper for the pane that is not a chat — the pane equivalent of
|
|
7014
|
+
`.aparte-sidebar__body`. The chat pane does not use it: the viewport scrolls itself. */
|
|
7015
|
+
.aparte-split__pane {
|
|
7016
|
+
box-sizing: border-box;
|
|
7017
|
+
min-inline-size: 0;
|
|
7018
|
+
min-block-size: 0;
|
|
7019
|
+
overflow: auto;
|
|
7020
|
+
background: var(--aparte-surface-1);
|
|
7021
|
+
}
|
|
7022
|
+
|
|
7023
|
+
/* `primary="end"` sizes the LAST pane instead of the first: the same three tracks,
|
|
7024
|
+
read the other way round. */
|
|
7025
|
+
aparte-split[primary='end'],
|
|
7026
|
+
.aparte-split--primary-end {
|
|
7027
|
+
grid-template-columns:
|
|
7028
|
+
minmax(0, 1fr)
|
|
7029
|
+
var(--aparte-split-handle-size)
|
|
7030
|
+
clamp(
|
|
7031
|
+
0px,
|
|
7032
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
7033
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
7034
|
+
);
|
|
7035
|
+
}
|
|
7036
|
+
|
|
7037
|
+
/* The other axis. The attribute and the class are selected in the same rule so the
|
|
7038
|
+
static recipe and the live element can never draw two different things. */
|
|
7039
|
+
aparte-split[orientation='vertical'],
|
|
7040
|
+
.aparte-split--vertical {
|
|
7041
|
+
grid-template-columns: none;
|
|
7042
|
+
grid-template-rows:
|
|
7043
|
+
clamp(
|
|
7044
|
+
0px,
|
|
7045
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
7046
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
7047
|
+
)
|
|
7048
|
+
var(--aparte-split-handle-size)
|
|
7049
|
+
minmax(0, 1fr);
|
|
7050
|
+
}
|
|
7051
|
+
|
|
7052
|
+
/* The one rule in this sheet that out-specifies the stacked block below — two
|
|
7053
|
+
attributes against its one — so it has to opt out of the stacked state by hand or a
|
|
7054
|
+
vertical `primary="end"` split keeps three rows while showing one pane, and the two
|
|
7055
|
+
empty tracks take the height off the pane that is left. */
|
|
7056
|
+
aparte-split[orientation='vertical'][primary='end']:not([data-stacked]),
|
|
7057
|
+
.aparte-split--vertical.aparte-split--primary-end:not(.aparte-split--only-start):not(.aparte-split--only-end) {
|
|
7058
|
+
grid-template-rows:
|
|
7059
|
+
minmax(0, 1fr)
|
|
7060
|
+
var(--aparte-split-handle-size)
|
|
7061
|
+
clamp(
|
|
7062
|
+
0px,
|
|
7063
|
+
clamp(var(--aparte-split-min), var(--aparte-split-position), var(--aparte-split-max)),
|
|
7064
|
+
calc(100% - var(--aparte-split-handle-size))
|
|
7065
|
+
);
|
|
7066
|
+
}
|
|
7067
|
+
|
|
7068
|
+
aparte-split[orientation='vertical'] > .aparte-split__handle,
|
|
7069
|
+
.aparte-split--vertical > .aparte-split__handle {
|
|
7070
|
+
cursor: row-resize;
|
|
7071
|
+
}
|
|
7072
|
+
|
|
7073
|
+
aparte-split[orientation='vertical'] > .aparte-split__handle::after,
|
|
7074
|
+
.aparte-split--vertical > .aparte-split__handle::after {
|
|
7075
|
+
inset-block: calc(var(--aparte-split-hit-area) / -2 + var(--aparte-split-handle-size) / 2) auto;
|
|
7076
|
+
inset-inline: 0;
|
|
7077
|
+
inline-size: auto;
|
|
7078
|
+
block-size: var(--aparte-split-hit-area);
|
|
7079
|
+
}
|
|
7080
|
+
|
|
7081
|
+
aparte-split[orientation='vertical'] .aparte-split__scrim,
|
|
7082
|
+
.aparte-split--vertical .aparte-split__scrim {
|
|
7083
|
+
cursor: row-resize;
|
|
7084
|
+
}
|
|
7085
|
+
|
|
7086
|
+
/* One pane at a time. `data-stacked` is written by the element from its breakpoint;
|
|
7087
|
+
the two modifier classes are the byte-identical CSS route for a host that owns its
|
|
7088
|
+
own breakpoint. The shown pane spans all three tracks and the seam goes with the
|
|
7089
|
+
pane it no longer separates — including its tab stop, which the element removes.
|
|
7090
|
+
|
|
7091
|
+
Not "position: 100% and a zero-size handle": that leaves a focusable separator of
|
|
7092
|
+
no width sitting in the tab order. */
|
|
7093
|
+
aparte-split[data-stacked],
|
|
7094
|
+
.aparte-split--only-start,
|
|
7095
|
+
.aparte-split--only-end {
|
|
7096
|
+
grid-template-columns: minmax(0, 1fr);
|
|
7097
|
+
grid-template-rows: minmax(0, 1fr);
|
|
7098
|
+
}
|
|
7099
|
+
|
|
7100
|
+
aparte-split[data-stacked] > .aparte-split__handle,
|
|
7101
|
+
.aparte-split--only-start > .aparte-split__handle,
|
|
7102
|
+
.aparte-split--only-end > .aparte-split__handle {
|
|
7103
|
+
display: none;
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
aparte-split[data-stacked]:not([pane='end']) > :nth-child(3),
|
|
7107
|
+
.aparte-split--only-start > :nth-child(3) {
|
|
7108
|
+
display: none;
|
|
7109
|
+
}
|
|
7110
|
+
|
|
7111
|
+
aparte-split[data-stacked][pane='end'] > :nth-child(1),
|
|
7112
|
+
.aparte-split--only-end > :nth-child(1) {
|
|
7113
|
+
display: none;
|
|
7114
|
+
}
|
|
7115
|
+
|
|
7116
|
+
/* `disabled` is inert, so it must not look grabbable: the element takes the tab stop
|
|
7117
|
+
away and swallows the pointer, and a resize cursor plus a hover highlight on a seam
|
|
7118
|
+
that answers nothing is exactly the half-rendered affordance the house rule forbids.
|
|
7119
|
+
The seam itself stays drawn — it still separates two panes.
|
|
7120
|
+
|
|
7121
|
+
Last in the sheet because it must out-rank the vertical `cursor: row-resize` above,
|
|
7122
|
+
which it ties with on specificity. Attribute only: the static recipe has no
|
|
7123
|
+
behaviour, so there is no live-versus-static pair to keep in step. */
|
|
7124
|
+
aparte-split[disabled] > .aparte-split__handle {
|
|
7125
|
+
cursor: default;
|
|
7126
|
+
}
|
|
7127
|
+
|
|
7128
|
+
aparte-split[disabled] > .aparte-split__handle:hover {
|
|
7129
|
+
background: var(--aparte-border);
|
|
7130
|
+
}
|
|
6312
7131
|
/*
|
|
6313
7132
|
* aparté — Reset-agnostic typography for model-authored markdown.
|
|
6314
7133
|
*
|
|
@@ -6489,22 +7308,6 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6489
7308
|
overflow-x: hidden;
|
|
6490
7309
|
background: var(--aparte-code-bg);
|
|
6491
7310
|
}
|
|
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
7311
|
/*
|
|
6509
7312
|
* aparté — Container-width and pointer-modality adjustments. LAST on purpose.
|
|
6510
7313
|
*
|
|
@@ -6531,14 +7334,17 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6531
7334
|
}
|
|
6532
7335
|
|
|
6533
7336
|
/* Windows high contrast. In forced-colors mode the UA repaints colours and
|
|
6534
|
-
drops box-shadow entirely — so the
|
|
7337
|
+
drops box-shadow entirely — so the focus indicators drawn with
|
|
6535
7338
|
--aparte-focus-ring, and the error ring on an avatar, do not merely change
|
|
6536
7339
|
colour, they VANISH. An outline is painted, and `Highlight` is the system's
|
|
6537
7340
|
own focus colour, so the indicator survives without fighting the user's
|
|
6538
7341
|
palette. Only the indicators that disappear are restated here: everything
|
|
6539
|
-
whose affordance is text or a border already survives on its own
|
|
7342
|
+
whose affordance is text or a border already survives on its own — which the
|
|
7343
|
+
split's seam does not, being a 4px band with `border: 0` whose entire keyboard
|
|
7344
|
+
story is arrowing it. */
|
|
6540
7345
|
@media (forced-colors: active) {
|
|
6541
7346
|
.aparte-thumb--image[role='button']:focus-visible,
|
|
7347
|
+
.aparte-split__handle:focus-visible,
|
|
6542
7348
|
.aparte-provider-select:focus,
|
|
6543
7349
|
.aparte-model-select:focus {
|
|
6544
7350
|
outline: var(--aparte-focus-outline-width) solid Highlight;
|
|
@@ -6626,11 +7432,26 @@ aparte-context[data-level="danger"] .aparte-progress__bar {
|
|
|
6626
7432
|
opacity: 1;
|
|
6627
7433
|
}
|
|
6628
7434
|
|
|
6629
|
-
/*
|
|
6630
|
-
.aparte-
|
|
6631
|
-
|
|
7435
|
+
/* Nor the ✕ on a pending attachment, which is the only way to remove one. */
|
|
7436
|
+
.aparte-thumb__remove {
|
|
7437
|
+
opacity: 1;
|
|
7438
|
+
}
|
|
7439
|
+
|
|
7440
|
+
/* Hover cannot reveal the row's ⋯ on touch — keep it visible and tappable. */
|
|
7441
|
+
.aparte-conv-item__more {
|
|
6632
7442
|
opacity: 1;
|
|
6633
7443
|
--aparte-conv-action-btn-size: 28px;
|
|
6634
7444
|
}
|
|
7445
|
+
|
|
7446
|
+
/* A 4px seam is no target for a finger — the same modality rule the buttons above
|
|
7447
|
+
follow. The seam keeps its width; only the invisible grab zone grows. */
|
|
7448
|
+
.aparte-split__handle {
|
|
7449
|
+
--aparte-split-hit-area: var(--aparte-touch-target-size);
|
|
7450
|
+
}
|
|
7451
|
+
|
|
7452
|
+
/* A 2px tick is no target for a finger; the minimaps of the market hide on touch too. */
|
|
7453
|
+
aparte-scroll-rail {
|
|
7454
|
+
display: none;
|
|
7455
|
+
}
|
|
6635
7456
|
}
|
|
6636
7457
|
|