rift-ds 1.6.0 → 1.8.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/README.md +34 -13
- package/bin/rift-ds.mjs +231 -44
- package/bin/templates/chat/.env.chat.example +21 -0
- package/bin/templates/chat/app/api/chat/feedback/route.ts +12 -0
- package/bin/templates/chat/app/api/chat/followups/route.ts +23 -0
- package/bin/templates/chat/app/api/chat/guardrails.ts +18 -0
- package/bin/templates/chat/app/api/chat/route.ts +31 -0
- package/bin/templates/chat/components/chat/ChatMount.tsx +52 -0
- package/bin/templates/chat/components/chat/models.ts +29 -0
- package/chat/AssistantTurn.d.ts +5 -0
- package/chat/AssistantTurn.js +82 -0
- package/chat/ChatAgentPane.css +56 -0
- package/chat/ChatAgentPane.d.ts +21 -0
- package/chat/ChatAgentPane.js +25 -0
- package/chat/ChatLauncher.d.ts +64 -0
- package/chat/ChatLauncher.js +347 -0
- package/chat/ChatProvider.d.ts +53 -0
- package/chat/ChatProvider.js +181 -0
- package/chat/ChatTabs.d.ts +13 -0
- package/chat/ChatTabs.js +24 -0
- package/chat/ChatThreads.d.ts +26 -0
- package/chat/ChatThreads.js +55 -0
- package/chat/ChatWidget.css +1072 -0
- package/chat/ChatWidget.d.ts +113 -0
- package/chat/ChatWidget.js +495 -0
- package/chat/DemoWelcome.d.ts +13 -0
- package/chat/DemoWelcome.js +131 -0
- package/chat/FollowupSuggestions.d.ts +23 -0
- package/chat/FollowupSuggestions.js +34 -0
- package/chat/ResponseActions.d.ts +18 -0
- package/chat/ResponseActions.js +79 -0
- package/chat/TurnContent.d.ts +9 -0
- package/chat/TurnContent.js +12 -0
- package/chat/context.d.ts +65 -0
- package/chat/context.js +27 -0
- package/chat/demo-agent-panes.d.ts +5 -0
- package/chat/demo-agent-panes.js +49 -0
- package/chat/demo-agent.d.ts +3 -0
- package/chat/demo-agent.js +28 -0
- package/chat/demo-card-components.d.ts +46 -0
- package/chat/demo-card-components.js +59 -0
- package/chat/demo-cards.d.ts +32 -0
- package/chat/demo-cards.js +59 -0
- package/chat/demo-fixtures.d.ts +20 -0
- package/chat/demo-fixtures.js +113 -0
- package/chat/demo-script.d.ts +6 -0
- package/chat/demo-script.js +215 -0
- package/chat/demo.css +46 -0
- package/chat/demo.d.ts +9 -0
- package/chat/demo.js +24 -0
- package/chat/followups.d.ts +41 -0
- package/chat/followups.js +55 -0
- package/chat/greeting.d.ts +11 -0
- package/chat/greeting.js +21 -0
- package/chat/index.d.ts +34 -0
- package/chat/index.js +80 -0
- package/chat/placement.d.ts +48 -0
- package/chat/placement.js +39 -0
- package/chat/queries.d.ts +9 -0
- package/chat/queries.js +22 -0
- package/chat/registry.d.ts +25 -0
- package/chat/registry.js +9 -0
- package/chat/registry.json.d.ts +69 -0
- package/chat/registry.json.js +11 -0
- package/chat/server.d.ts +217 -0
- package/chat/server.js +488 -0
- package/chat/sim.d.ts +71 -0
- package/chat/sim.js +125 -0
- package/chat/starters.d.ts +15 -0
- package/chat/starters.js +38 -0
- package/chat/suggestions.d.ts +22 -0
- package/chat/suggestions.js +9 -0
- package/chat/threads.d.ts +73 -0
- package/chat/threads.js +270 -0
- package/chat/transport.d.ts +49 -0
- package/chat/transport.js +105 -0
- package/chat/types.d.ts +210 -0
- package/chat/types.js +16 -0
- package/chat/upstash.d.ts +63 -0
- package/chat/upstash.js +147 -0
- package/chat/useChat.d.ts +64 -0
- package/chat/useChat.js +241 -0
- package/cli/templates/chat/app/api/chat/feedback/route.d.ts +3 -0
- package/cli/templates/chat/app/api/chat/followups/route.d.ts +3 -0
- package/cli/templates/chat/app/api/chat/guardrails.d.ts +10 -0
- package/cli/templates/chat/app/api/chat/route.d.ts +4 -0
- package/cli/templates/chat/components/chat/ChatMount.d.ts +1 -0
- package/cli/templates/chat/components/chat/models.d.ts +11 -0
- package/components/AgentRail/AgentRail.css +2 -2
- package/components/Attachment/Attachment.css +14 -0
- package/components/Attachment/AttachmentGroup.d.ts +2 -2
- package/components/Attachment/AttachmentGroup.js +7 -2
- package/components/Composer/Composer.css +88 -4
- package/components/Composer/Composer.d.ts +21 -0
- package/components/Composer/Composer.js +199 -14
- package/components/Mention/Mention.css +195 -0
- package/components/Mention/Mention.d.ts +68 -0
- package/components/Mention/Mention.js +145 -0
- package/components/Mention/mentions.d.ts +81 -0
- package/components/Mention/mentions.js +83 -0
- package/components/ShaderField/ShaderField.d.ts +6 -5
- package/components/ShaderField/useShaderField.d.ts +2 -1
- package/components/ShaderField/useShaderField.js +6 -6
- package/components/StatusDot/StatusDot.css +36 -13
- package/components/StatusDot/StatusDot.d.ts +16 -4
- package/components/StatusDot/StatusDot.js +25 -5
- package/components/ThreadPanel/ThreadPanel.css +20 -11
- package/components/ThreadPanel/ThreadPanel.d.ts +15 -3
- package/components/ThreadPanel/ThreadPanel.js +30 -6
- package/components/ThreadTabs/ThreadTabs.css +18 -8
- package/components/ThreadTabs/ThreadTabs.d.ts +8 -2
- package/components/ThreadTabs/ThreadTabs.js +24 -5
- package/components/registry.json +9 -1
- package/components/registry.json.d.ts +9 -1
- package/components/registry.json.js +1 -1
- package/fonts/material-symbols.css +2 -1
- package/index.d.ts +1 -0
- package/index.js +11 -0
- package/package.json +43 -2
- package/tokens/presets/coral.css +18 -2
- package/tokens/presets/forest.css +18 -2
- package/tokens/presets/gold.css +18 -2
- package/tokens/presets/mono.css +18 -2
- package/tokens/presets/pink.css +18 -2
- package/tokens/presets/terminal.css +18 -2
- package/tokens/presets/violet.css +18 -2
- package/tokens/presets/warm.css +18 -2
- package/tokens/presets/zest.css +18 -2
- package/tokens/registry.json +6 -0
- package/tokens/registry.json.d.ts +6 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +10 -0
- package/tokens/tokens-light.css +10 -0
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
CHAT WIDGET — the composed chat and the launcher's panel chrome.
|
|
3
|
+
|
|
4
|
+
.ds-chat-widget is the widget's internals: header, thread, composer,
|
|
5
|
+
starters, disclaimer, and the optional rails. It fills whichever container
|
|
6
|
+
hosts it; compact hosts (phone-width panels) set data-compact for the
|
|
7
|
+
narrower insets.
|
|
8
|
+
|
|
9
|
+
.ds-chat-launcher__* is ChatLauncher's chrome: the floating button, the
|
|
10
|
+
fixed panel in its docked, overlay, floating and takeover forms, the scrim
|
|
11
|
+
and the grips. The three variables on :root are the panel's geometry and
|
|
12
|
+
the one contract a host stylesheet reads: --ds-chat-width (rewritten on
|
|
13
|
+
<html> by the widen grip), --ds-chat-inset and --ds-chat-float-height.
|
|
14
|
+
============================================ */
|
|
15
|
+
|
|
16
|
+
:root {
|
|
17
|
+
/* The docked panel's resting width; DOCK_MIN_WIDTH in placement.ts mirrors it. */
|
|
18
|
+
--ds-chat-width: 420px;
|
|
19
|
+
/* The gap the panel and the launcher float off the viewport edges. */
|
|
20
|
+
--ds-chat-inset: 20px;
|
|
21
|
+
/* The floating card's resting height. */
|
|
22
|
+
--ds-chat-float-height: 640px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ds-chat-widget {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
min-height: 0;
|
|
31
|
+
/* A small breath above the header on every host: it seats the title row on
|
|
32
|
+
the same line as the threads rail's own header, whose panel carries this
|
|
33
|
+
inset built in. */
|
|
34
|
+
padding-top: var(--padding-200);
|
|
35
|
+
/* The one side inset every row shares (header controls, greeting, thread
|
|
36
|
+
bubbles, composer edge, starter pills, disclaimer), so they sit on a
|
|
37
|
+
single rail. A step tighter on compact hosts. */
|
|
38
|
+
--ds-chat-widget-inset: var(--padding-500);
|
|
39
|
+
/* A size container, so the welcome dressing below can step aside when the
|
|
40
|
+
host is too short for it. */
|
|
41
|
+
container-type: size;
|
|
42
|
+
container-name: ds-chat-widget;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ds-chat-widget[data-compact] {
|
|
46
|
+
--ds-chat-widget-inset: var(--padding-400);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* The header rides the same rail as everything under it; the component's
|
|
50
|
+
own default is a step tighter than the widget wants. */
|
|
51
|
+
.ds-chat-widget .ds-chat-header {
|
|
52
|
+
padding-inline: var(--ds-chat-widget-inset);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* With the controls lifted to the card's edge, the header row has nothing
|
|
56
|
+
40px tall left in it and would collapse to the title's own line. The row
|
|
57
|
+
holds the height its buttons were giving it: the control plus the
|
|
58
|
+
header's own block padding either side. */
|
|
59
|
+
.ds-chat-widget[data-lifted-actions] .ds-chat-header {
|
|
60
|
+
min-height: calc(40px + var(--padding-150) * 2);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* The close button hugs the panel's clipped right edge, so its centred
|
|
64
|
+
bottom tooltip would lose its right cap to overflow: hidden. End-align
|
|
65
|
+
the last header tooltip to the button and walk the arrow back to the
|
|
66
|
+
button's centre (20px in, minus half the 8px arrow). */
|
|
67
|
+
.ds-chat-widget .ds-chat-header__actions .ds-tooltip:last-child .ds-tooltip__panel--bottom,
|
|
68
|
+
.ds-chat-widget__rail-actions .ds-tooltip:last-child .ds-tooltip__panel--bottom {
|
|
69
|
+
left: auto;
|
|
70
|
+
right: 0;
|
|
71
|
+
transform: translateY(-4px);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.ds-chat-widget .ds-chat-header__actions .ds-tooltip:last-child .ds-tooltip__panel--bottom.ds-tooltip__panel--visible,
|
|
75
|
+
.ds-chat-widget__rail-actions .ds-tooltip:last-child .ds-tooltip__panel--bottom.ds-tooltip__panel--visible {
|
|
76
|
+
transform: translateY(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ds-chat-widget .ds-chat-header__actions .ds-tooltip:last-child .ds-tooltip__panel--bottom .ds-tooltip__arrow,
|
|
80
|
+
.ds-chat-widget__rail-actions .ds-tooltip:last-child .ds-tooltip__panel--bottom .ds-tooltip__arrow {
|
|
81
|
+
left: auto;
|
|
82
|
+
right: 16px;
|
|
83
|
+
margin-left: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* The placement menu's wrapper is only there to catch Escape; it must not
|
|
87
|
+
become a box of its own in the header's action row. */
|
|
88
|
+
.ds-chat-widget__placement-menu {
|
|
89
|
+
display: contents;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Open, the menu says what the button is, so the tooltip stands down. And
|
|
93
|
+
the trigger holds the passive wash while its menu is up: a control that
|
|
94
|
+
is open says so. */
|
|
95
|
+
.ds-chat-widget__placement-menu .ds-tooltip:has([aria-expanded="true"]) .ds-tooltip__panel {
|
|
96
|
+
display: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ds-chat-widget__placement-menu .ds-circular-button[aria-expanded="true"],
|
|
100
|
+
.ds-chat-widget__placement-menu .ds-circular-button[aria-expanded="true"]:hover {
|
|
101
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* The open-sessions tab strip: its own row under the header, on the shared
|
|
105
|
+
side inset. */
|
|
106
|
+
.ds-chat-widget__tabs-row {
|
|
107
|
+
min-width: 0;
|
|
108
|
+
padding: var(--padding-150) var(--ds-chat-widget-inset) 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* The header brand: the mark beside the chat name. */
|
|
112
|
+
.ds-chat-widget__brand {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: var(--gap-200);
|
|
116
|
+
min-width: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.ds-chat-widget__brand img {
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ds-chat-widget__brand-name {
|
|
124
|
+
font-family: var(--font-title-body-family);
|
|
125
|
+
font-size: var(--font-title-body-size);
|
|
126
|
+
font-weight: var(--font-title-body-weight);
|
|
127
|
+
line-height: var(--font-title-body-line-height);
|
|
128
|
+
color: var(--color-text-primary);
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
text-overflow: ellipsis;
|
|
131
|
+
white-space: nowrap;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* The top region: header + thread. Zero basis so welcome mode splits the
|
|
135
|
+
height exactly with the bottom region, centring the composer. */
|
|
136
|
+
.ds-chat-widget__top {
|
|
137
|
+
flex: 1 1 0;
|
|
138
|
+
min-height: 0;
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* The body hosts the thread, with the welcome greeting overlaid while the
|
|
144
|
+
conversation is empty. */
|
|
145
|
+
.ds-chat-widget__body {
|
|
146
|
+
flex: 1;
|
|
147
|
+
min-height: 0;
|
|
148
|
+
position: relative;
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* The thread owns scrolling, gutters, fades, and send anchoring. However
|
|
154
|
+
wide the container gets, the conversation column caps at 768px and
|
|
155
|
+
centres; the header above is the one row that spans the full width. */
|
|
156
|
+
.ds-chat-widget__thread {
|
|
157
|
+
flex: 1;
|
|
158
|
+
--ds-chat-thread-content-max-width: 768px;
|
|
159
|
+
/* Match the composer and header inset, so bubbles, composer edge, and
|
|
160
|
+
the header controls all share one right rail. */
|
|
161
|
+
--ds-chat-thread-padding-inline: var(--ds-chat-widget-inset);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ============================================
|
|
165
|
+
WELCOME — the new-chat empty state
|
|
166
|
+
============================================ */
|
|
167
|
+
|
|
168
|
+
.ds-chat-widget__welcome {
|
|
169
|
+
position: absolute;
|
|
170
|
+
inset: 0;
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-direction: column;
|
|
173
|
+
justify-content: flex-end;
|
|
174
|
+
width: 100%;
|
|
175
|
+
max-width: 768px;
|
|
176
|
+
margin-inline: auto;
|
|
177
|
+
/* The block-end padding is the gap between the greeting and the composer;
|
|
178
|
+
block-start is inert, since the overlay bottom-aligns its content. */
|
|
179
|
+
padding: var(--padding-500) var(--ds-chat-widget-inset) var(--gap-1000);
|
|
180
|
+
pointer-events: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.ds-chat-widget__greeting {
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
gap: var(--gap-200);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Small and semibold over the ask line's large light face: the pair
|
|
190
|
+
separates by weight contrast. Tertiary text, a step quieter than the ask
|
|
191
|
+
line, so the time-of-day pleasantry supports the invitation rather than
|
|
192
|
+
competing with it. */
|
|
193
|
+
.ds-chat-widget__hello {
|
|
194
|
+
margin: 0;
|
|
195
|
+
/* The greeting is read from the visitor's clock after mount, so this line
|
|
196
|
+
is empty for the first paint. An empty block has no line box at all:
|
|
197
|
+
reserve the height, or the ask line below jumps up a row and back. */
|
|
198
|
+
min-height: var(--font-heading-3-line-height);
|
|
199
|
+
font-family: var(--font-heading-3-family);
|
|
200
|
+
font-size: var(--font-heading-3-size);
|
|
201
|
+
font-weight: var(--font-heading-3-weight);
|
|
202
|
+
line-height: var(--font-heading-3-line-height);
|
|
203
|
+
letter-spacing: var(--font-heading-3-letter-spacing);
|
|
204
|
+
color: var(--color-text-tertiary);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.ds-chat-widget__ask {
|
|
208
|
+
margin: 0;
|
|
209
|
+
font-family: var(--font-sub-display-family);
|
|
210
|
+
font-size: var(--font-sub-display-size);
|
|
211
|
+
font-weight: var(--font-sub-display-weight);
|
|
212
|
+
line-height: var(--font-sub-display-line-height);
|
|
213
|
+
letter-spacing: var(--font-sub-display-letter-spacing);
|
|
214
|
+
color: var(--color-text-primary);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* The panel is a viewport of its own. tokens-typography.css collapses
|
|
218
|
+
sub-display to 24px/36px under a 768px *viewport*, but a docked panel is
|
|
219
|
+
phone-width on a desktop window, where that media query still serves the
|
|
220
|
+
30px desktop size into a 420px card. So the ask line takes the same step
|
|
221
|
+
on the *container's* width, at the same 768px line. The values restate
|
|
222
|
+
the token layer's own phone step rather than referencing it, because a
|
|
223
|
+
value inside an unmatched media query is unreachable; tokens-typography.css
|
|
224
|
+
owns the pair, and the two must move together. */
|
|
225
|
+
/* ds-allow(typography): the token layer's 768px sub-display step, restated for the panel's container width — tokens-typography.css owns the values */
|
|
226
|
+
@container ds-chat-widget (max-width: 768px) {
|
|
227
|
+
.ds-chat-widget__ask {
|
|
228
|
+
--font-sub-display-size: 24px;
|
|
229
|
+
--font-sub-display-line-height: 36px;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* The bottom region: starters at its top, disclaimer pinned to its bottom.
|
|
234
|
+
Grown while the chat is empty (centring the composer against the
|
|
235
|
+
zero-basis top region), collapsing to the disclaimer's height on the
|
|
236
|
+
first utterance. Basis stays 0 in both states so only flex-grow ever
|
|
237
|
+
animates: the flow-down is one continuous glide. Reduced motion zeroes it
|
|
238
|
+
via the token. */
|
|
239
|
+
.ds-chat-widget__bottom {
|
|
240
|
+
display: flex;
|
|
241
|
+
flex-direction: column;
|
|
242
|
+
flex: 0 1 0;
|
|
243
|
+
transition: flex-grow var(--motion-duration-slower) var(--motion-ease-entrance);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.ds-chat-widget__bottom--welcome {
|
|
247
|
+
flex-grow: 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.ds-chat-widget__starters {
|
|
251
|
+
width: 100%;
|
|
252
|
+
max-width: 768px;
|
|
253
|
+
margin-inline: auto;
|
|
254
|
+
padding: var(--gap-1000) var(--ds-chat-widget-inset) 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* The widget slot: any content, under the composer (and under the starters
|
|
258
|
+
when both show), on the composer's own bounding box. The cap is the
|
|
259
|
+
conversation column plus the side insets, so the slot's content edges
|
|
260
|
+
land exactly on the composer's. */
|
|
261
|
+
.ds-chat-widget__widgets {
|
|
262
|
+
width: 100%;
|
|
263
|
+
max-width: calc(768px + 2 * var(--ds-chat-widget-inset));
|
|
264
|
+
margin-inline: auto;
|
|
265
|
+
padding: var(--gap-1000) var(--ds-chat-widget-inset) var(--padding-400);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* With widgets the region takes its content's height before the slack is
|
|
269
|
+
split, so the composer sits above centre by half the widgets' height
|
|
270
|
+
rather than being squeezed against them. */
|
|
271
|
+
.ds-chat-widget__bottom--widgets {
|
|
272
|
+
flex-basis: auto;
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* The widgets claim their height first, so on a short window an even split
|
|
277
|
+
of what is left would crowd the greeting into the header. The top region
|
|
278
|
+
keeps room for the header and the greeting. A phone never centres, so it
|
|
279
|
+
has nothing to protect. */
|
|
280
|
+
.ds-chat-widget[data-widgets="fit"]:not([data-phone]) .ds-chat-widget__top {
|
|
281
|
+
min-height: 236px;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* The welcome dressing needs roughly 580px of widget for the greeting and
|
|
285
|
+
460px for the starters. Shorter than that, each is decoration the layout
|
|
286
|
+
cannot afford, so it steps aside and the empty state falls back to header,
|
|
287
|
+
centred composer, disclaimer. */
|
|
288
|
+
@container ds-chat-widget (max-height: 579px) {
|
|
289
|
+
.ds-chat-widget__welcome {
|
|
290
|
+
display: none;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@container ds-chat-widget (max-height: 459px) {
|
|
295
|
+
.ds-chat-widget__starters {
|
|
296
|
+
display: none;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* ============================================
|
|
301
|
+
WIDGETS AS A PAGE — data-widgets="scroll"
|
|
302
|
+
The welcome screen stops fitting itself to one viewport: the top region
|
|
303
|
+
takes a fixed share of the height, the widgets take whatever they need on
|
|
304
|
+
a wider column, and the widget scrolls as one page with the composer
|
|
305
|
+
travelling with it. Nothing here outlives the welcome.
|
|
306
|
+
============================================ */
|
|
307
|
+
|
|
308
|
+
.ds-chat-widget[data-widgets="scroll"] {
|
|
309
|
+
overflow-y: auto;
|
|
310
|
+
overscroll-behavior: contain;
|
|
311
|
+
|
|
312
|
+
/* The thread's scroll treatment, on the welcome page too. The scrollbar
|
|
313
|
+
is invisible until the page is actually scrolling (data-scrolling, set
|
|
314
|
+
by the widget and settled by MOTION_SCROLL_SETTLE_MS), with its gutter
|
|
315
|
+
reserved so it never reflows the content sideways. */
|
|
316
|
+
scrollbar-width: thin;
|
|
317
|
+
scrollbar-gutter: stable;
|
|
318
|
+
scrollbar-color: transparent transparent;
|
|
319
|
+
transition: scrollbar-color var(--motion-duration-base) var(--motion-ease-standard);
|
|
320
|
+
|
|
321
|
+
/* The bottom fade the thread has under the conversation: a CSS mask, so
|
|
322
|
+
the widgets scrolling out through the floor pass through it. The root
|
|
323
|
+
carries no background of its own, so only the content fades.
|
|
324
|
+
ds-allow(color): mask alpha keywords (black/transparent), not palette colours */
|
|
325
|
+
-webkit-mask-image: linear-gradient(to bottom, black calc(100% - var(--gap-1000)), transparent);
|
|
326
|
+
mask-image: linear-gradient(to bottom, black calc(100% - var(--gap-1000)), transparent);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.ds-chat-widget[data-widgets="scroll"][data-scrolling] {
|
|
330
|
+
scrollbar-color: var(--color-divider) transparent;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.ds-chat-widget[data-widgets="scroll"] .ds-chat-widget__top {
|
|
334
|
+
flex: 0 0 auto;
|
|
335
|
+
height: max(236px, 36cqh);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.ds-chat-widget[data-widgets="scroll"] .ds-chat-widget__footer {
|
|
339
|
+
flex: 0 0 auto;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Never smaller than its content, and it takes any height left over, so
|
|
343
|
+
when the widgets are short the disclaimer still rides the floor. */
|
|
344
|
+
.ds-chat-widget[data-widgets="scroll"] .ds-chat-widget__bottom {
|
|
345
|
+
flex: 1 0 auto;
|
|
346
|
+
transition: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* A scrolling slot is never dropped for want of height, and ends with room
|
|
350
|
+
under its last row. */
|
|
351
|
+
.ds-chat-widget[data-widgets="scroll"] .ds-chat-widget__widgets {
|
|
352
|
+
display: block;
|
|
353
|
+
padding-block: var(--gap-1000) var(--padding-1000);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* A wide slot breaks out of the composer's box: a dashboard wants more
|
|
357
|
+
width than a line of prose does. */
|
|
358
|
+
.ds-chat-widget[data-widgets-wide] .ds-chat-widget__widgets {
|
|
359
|
+
max-width: 1080px;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* The phone's bottom-pinned composer has nowhere to pin on a scrolling
|
|
363
|
+
page, so the greeting goes back to sitting on the composer. */
|
|
364
|
+
.ds-chat-widget[data-widgets="scroll"][data-phone] .ds-chat-widget__welcome {
|
|
365
|
+
justify-content: flex-end;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* Widgets are the first dressing to go: they are the tallest, and a window
|
|
369
|
+
too short for them still has room for the greeting and the composer. */
|
|
370
|
+
@container ds-chat-widget (max-height: 719px) {
|
|
371
|
+
.ds-chat-widget__widgets {
|
|
372
|
+
display: none;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ============================================
|
|
377
|
+
WELCOME ON A PHONE — data-phone
|
|
378
|
+
The soft keyboard claims the lower half of a phone screen the moment the
|
|
379
|
+
composer is focused, so a phone never centres: the top region takes all
|
|
380
|
+
the slack, which pins the composer to the bottom from the first paint,
|
|
381
|
+
the greeting floats in the middle of the empty thread, and the starters
|
|
382
|
+
render directly above the composer.
|
|
383
|
+
============================================ */
|
|
384
|
+
|
|
385
|
+
.ds-chat-widget[data-phone] .ds-chat-widget__welcome {
|
|
386
|
+
/* Restated, not inherited: the desktop height guard above hides the
|
|
387
|
+
overlay under 580px, which is most phones once the keyboard is up. */
|
|
388
|
+
display: flex;
|
|
389
|
+
justify-content: center;
|
|
390
|
+
padding-block: var(--padding-400);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.ds-chat-widget[data-phone] .ds-chat-widget__bottom--welcome {
|
|
394
|
+
flex-grow: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.ds-chat-widget[data-phone] .ds-chat-widget__bottom {
|
|
398
|
+
transition: none;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* Above the composer: its gap is the block-end padding now. */
|
|
402
|
+
.ds-chat-widget[data-phone] .ds-chat-widget__starters {
|
|
403
|
+
display: block;
|
|
404
|
+
padding-block: 0 var(--gap-400);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.ds-chat-widget[data-phone][data-widgets="fit"] .ds-chat-widget__widgets {
|
|
408
|
+
padding-block: 0 var(--gap-400);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.ds-chat-widget[data-phone][data-widgets="scroll"] .ds-chat-widget__widgets {
|
|
412
|
+
padding-block: var(--gap-500) var(--padding-500);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.ds-chat-widget[data-phone][data-widgets="scroll"] .ds-chat-widget__bottom--welcome {
|
|
416
|
+
flex-grow: 1;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* The phone stack needs less height than the centred layout, and it is the
|
|
420
|
+
keyboard, not a short window, that usually takes it away. The greeting
|
|
421
|
+
steps aside first, the starters only when even they would crowd the
|
|
422
|
+
composer. */
|
|
423
|
+
@container ds-chat-widget (max-height: 499px) {
|
|
424
|
+
.ds-chat-widget[data-phone] .ds-chat-widget__welcome {
|
|
425
|
+
display: none;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@container ds-chat-widget (max-height: 379px) {
|
|
430
|
+
.ds-chat-widget[data-phone] .ds-chat-widget__starters {
|
|
431
|
+
display: none;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* Locked to the bottom of the widget, in every state. */
|
|
436
|
+
.ds-chat-widget__disclaimer-row {
|
|
437
|
+
margin-top: auto;
|
|
438
|
+
padding: var(--gap-100) var(--ds-chat-widget-inset) var(--padding-200);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.ds-chat-widget__response {
|
|
442
|
+
display: flex;
|
|
443
|
+
flex-direction: column;
|
|
444
|
+
gap: var(--gap-500);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* No divider: the composer floats free of the transcript. */
|
|
448
|
+
.ds-chat-widget__footer {
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
/* The composer never gives up height in a squeeze. */
|
|
452
|
+
flex-shrink: 0;
|
|
453
|
+
padding: 0 var(--ds-chat-widget-inset);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/* The composer shares the conversation column's cap and centre. */
|
|
457
|
+
.ds-chat-widget__composer {
|
|
458
|
+
width: 100%;
|
|
459
|
+
max-width: 768px;
|
|
460
|
+
margin-inline: auto;
|
|
461
|
+
/* The panel is narrow and its welcome screen centres the composer, so
|
|
462
|
+
queued files sit in smaller tiles here than a full-width composer's. */
|
|
463
|
+
--ds-attachment-tile-size: 104px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* One quiet line, centred with the composer column, at the floor of the
|
|
467
|
+
type scale: a visitor can find it, and never has to read it to chat. */
|
|
468
|
+
.ds-chat-widget__disclaimer {
|
|
469
|
+
margin: 0;
|
|
470
|
+
text-align: center;
|
|
471
|
+
font-family: var(--font-caption-family);
|
|
472
|
+
font-size: var(--font-caption-size);
|
|
473
|
+
font-weight: var(--font-caption-weight);
|
|
474
|
+
line-height: var(--font-caption-line-height);
|
|
475
|
+
letter-spacing: var(--font-caption-letter-spacing);
|
|
476
|
+
color: var(--color-text-tertiary);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.ds-chat-widget__disclaimer a {
|
|
480
|
+
color: inherit;
|
|
481
|
+
text-decoration: underline;
|
|
482
|
+
text-underline-offset: 2px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* ============================================
|
|
486
|
+
MESSAGE ACTIONS — the copy and thumbs row under a committed assistant turn.
|
|
487
|
+
============================================ */
|
|
488
|
+
|
|
489
|
+
.ds-chat-widget__action {
|
|
490
|
+
display: grid;
|
|
491
|
+
place-items: center;
|
|
492
|
+
padding: var(--padding-100);
|
|
493
|
+
border: none;
|
|
494
|
+
border-radius: var(--radius-pill);
|
|
495
|
+
background: transparent;
|
|
496
|
+
cursor: pointer;
|
|
497
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.ds-chat-widget__action:hover {
|
|
501
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.ds-chat-widget__action:active {
|
|
505
|
+
background-color: var(--color-action-passive-bg-active);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.ds-chat-widget__action-icon {
|
|
509
|
+
/* Both of the copy button's icons share this single grid cell, so the
|
|
510
|
+
swap happens in place rather than reflowing the row. */
|
|
511
|
+
grid-area: 1 / 1;
|
|
512
|
+
--icon-size: var(--icon-size-500);
|
|
513
|
+
color: var(--color-icon-secondary);
|
|
514
|
+
transition:
|
|
515
|
+
opacity var(--motion-duration-fast) var(--motion-ease-standard),
|
|
516
|
+
transform var(--motion-duration-fast) var(--motion-ease-standard),
|
|
517
|
+
color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.ds-chat-widget__action:hover .ds-chat-widget__action-icon {
|
|
521
|
+
color: var(--color-icon-primary);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* The hidden half of the copy swap: faded and slightly shrunk, so the
|
|
525
|
+
arriving icon reads as popping in rather than teleporting. */
|
|
526
|
+
.ds-chat-widget__action-icon--hidden {
|
|
527
|
+
opacity: 0;
|
|
528
|
+
transform: scale(0.6);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* The tick wears the positive status role while it shows. */
|
|
532
|
+
.ds-chat-widget__action-icon--tick,
|
|
533
|
+
.ds-chat-widget__action:hover .ds-chat-widget__action-icon--tick {
|
|
534
|
+
color: var(--color-status-positive-border);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* A chosen thumb: filled glyph, full-strength colour, no hover needed. */
|
|
538
|
+
.ds-chat-widget__action[aria-pressed="true"] .ds-chat-widget__action-icon {
|
|
539
|
+
--material-symbols-fill: 1;
|
|
540
|
+
color: var(--color-icon-primary);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* ============================================
|
|
544
|
+
FOLLOW-UP SUGGESTIONS — the chips under an answer.
|
|
545
|
+
============================================ */
|
|
546
|
+
|
|
547
|
+
.ds-chat-widget__followups {
|
|
548
|
+
width: 100%;
|
|
549
|
+
margin-top: var(--gap-200);
|
|
550
|
+
/* They land a beat after the answer commits, so they arrive rather than
|
|
551
|
+
appear. Reduced motion zeroes the duration through the token. */
|
|
552
|
+
animation: ds-chat-widget-followups-in var(--motion-duration-slow) var(--motion-ease-entrance) both;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
@keyframes ds-chat-widget-followups-in {
|
|
556
|
+
from {
|
|
557
|
+
opacity: 0;
|
|
558
|
+
transform: translateY(var(--gap-100));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* Visually hidden, still announced. */
|
|
563
|
+
.ds-chat-widget__sr-only {
|
|
564
|
+
position: absolute;
|
|
565
|
+
width: 1px;
|
|
566
|
+
height: 1px;
|
|
567
|
+
margin: -1px;
|
|
568
|
+
padding: 0;
|
|
569
|
+
overflow: hidden;
|
|
570
|
+
clip: rect(0 0 0 0);
|
|
571
|
+
white-space: nowrap;
|
|
572
|
+
border: 0;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/* ============================================
|
|
576
|
+
THE RAILS — the optional session-history rail (leading) and agent pane
|
|
577
|
+
(trailing). Wide widgets seat them inline; narrow ones summon the history
|
|
578
|
+
from the header hamburger as a slide-over sheet and the agent pane from a
|
|
579
|
+
header button as a bottom sheet, each behind a scrim.
|
|
580
|
+
============================================ */
|
|
581
|
+
|
|
582
|
+
.ds-chat-widget__rails {
|
|
583
|
+
position: relative;
|
|
584
|
+
display: flex;
|
|
585
|
+
width: 100%;
|
|
586
|
+
height: 100%;
|
|
587
|
+
min-height: 0;
|
|
588
|
+
overflow: hidden;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.ds-chat-widget--with-threads {
|
|
592
|
+
flex: 1;
|
|
593
|
+
min-width: 0;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.ds-chat-widget__threads-rail {
|
|
597
|
+
flex: none;
|
|
598
|
+
display: flex;
|
|
599
|
+
min-height: 0;
|
|
600
|
+
border-right: var(--border-025) solid var(--color-bg-container-border);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* The sheet layer: hidden entirely (visibility) while closed, so nothing in
|
|
604
|
+
it can take focus or reach assistive technology. */
|
|
605
|
+
.ds-chat-widget__threads-overlay,
|
|
606
|
+
.ds-chat-widget__aside-overlay {
|
|
607
|
+
position: absolute;
|
|
608
|
+
inset: 0;
|
|
609
|
+
z-index: 5;
|
|
610
|
+
visibility: hidden;
|
|
611
|
+
transition: visibility 0s var(--motion-duration-slow);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.ds-chat-widget__threads-overlay--open,
|
|
615
|
+
.ds-chat-widget__aside-overlay--open {
|
|
616
|
+
visibility: visible;
|
|
617
|
+
transition: visibility 0s;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.ds-chat-widget__rail-scrim {
|
|
621
|
+
position: absolute;
|
|
622
|
+
inset: 0;
|
|
623
|
+
border: none;
|
|
624
|
+
padding: 0;
|
|
625
|
+
background-color: var(--color-scrim);
|
|
626
|
+
cursor: pointer;
|
|
627
|
+
opacity: 0;
|
|
628
|
+
transition: opacity var(--motion-duration-slow) var(--motion-ease-standard);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.ds-chat-widget__threads-overlay--open .ds-chat-widget__rail-scrim,
|
|
632
|
+
.ds-chat-widget__aside-overlay--open .ds-chat-widget__rail-scrim {
|
|
633
|
+
opacity: 1;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/* The glass recipe: the translucent fill over a backdrop blur, so the
|
|
637
|
+
dimmed chat shows through the sheet. */
|
|
638
|
+
.ds-chat-widget__threads-sheet {
|
|
639
|
+
position: absolute;
|
|
640
|
+
top: 0;
|
|
641
|
+
bottom: 0;
|
|
642
|
+
left: 0;
|
|
643
|
+
display: flex;
|
|
644
|
+
background-color: var(--color-bg-glass);
|
|
645
|
+
backdrop-filter: blur(24px);
|
|
646
|
+
border-right: var(--border-025) solid var(--color-bg-container-border);
|
|
647
|
+
box-shadow: var(--shadow-floating);
|
|
648
|
+
transform: translateX(-100%);
|
|
649
|
+
transition: transform var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.ds-chat-widget__threads-overlay--open .ds-chat-widget__threads-sheet {
|
|
653
|
+
transform: translateX(0);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/* The agent rail floats rather than butting against the widget's edges: its
|
|
657
|
+
own rounded card, inset on three sides. The top margin is the header
|
|
658
|
+
row's exact height (the chat column's top padding, plus the header's own
|
|
659
|
+
block padding either side of a 40px control), so the panel begins where
|
|
660
|
+
that row ends and the lifted controls sit in the clear above it. */
|
|
661
|
+
.ds-chat-widget__aside-rail {
|
|
662
|
+
flex: none;
|
|
663
|
+
min-height: 0;
|
|
664
|
+
margin: calc(var(--padding-200) + var(--padding-150) * 2 + 40px) 0 var(--padding-500) 0;
|
|
665
|
+
/* The open and close glide, the threads rail's own: a one-column grid
|
|
666
|
+
whose track runs 0fr to 1fr, so the pane's width animates without the
|
|
667
|
+
widget knowing it, while the pane's own card fades with it. Closed, the
|
|
668
|
+
rail keeps no width, no inset and no border, and goes invisible once
|
|
669
|
+
the collapse has finished (the visibility delay), so nothing of it can
|
|
670
|
+
be seen, hovered or reached. The reduced-motion guard in
|
|
671
|
+
tokens-motion.css zeroes every duration here. */
|
|
672
|
+
display: grid;
|
|
673
|
+
grid-template-columns: 0fr;
|
|
674
|
+
opacity: 0;
|
|
675
|
+
visibility: hidden;
|
|
676
|
+
transition:
|
|
677
|
+
grid-template-columns var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
678
|
+
margin var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
679
|
+
opacity var(--motion-duration-base) var(--motion-ease-standard),
|
|
680
|
+
visibility 0s linear var(--motion-duration-slow);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.ds-chat-widget__aside-rail--open {
|
|
684
|
+
grid-template-columns: 1fr;
|
|
685
|
+
margin-right: var(--padding-500);
|
|
686
|
+
opacity: 1;
|
|
687
|
+
visibility: visible;
|
|
688
|
+
transition:
|
|
689
|
+
grid-template-columns var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
690
|
+
margin var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
691
|
+
opacity var(--motion-duration-base) var(--motion-ease-standard) calc(var(--motion-duration-slow) - var(--motion-duration-base)),
|
|
692
|
+
visibility 0s linear 0s;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* The pane's card: the border and radius live here, inside the animated
|
|
696
|
+
track, so the collapsing rail never shows a sliver of frame. */
|
|
697
|
+
.ds-chat-widget__aside-rail-inner {
|
|
698
|
+
display: flex;
|
|
699
|
+
min-width: 0;
|
|
700
|
+
min-height: 0;
|
|
701
|
+
border: var(--border-025) solid var(--color-bg-container-border);
|
|
702
|
+
border-radius: var(--radius-600);
|
|
703
|
+
overflow: hidden;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* The widget's controls, lifted out of the chat header to the card's own
|
|
707
|
+
right edge while the rail is seated inline. */
|
|
708
|
+
.ds-chat-widget__rail-actions {
|
|
709
|
+
position: absolute;
|
|
710
|
+
top: calc(var(--padding-200) + var(--padding-150));
|
|
711
|
+
right: var(--padding-500);
|
|
712
|
+
z-index: 6;
|
|
713
|
+
display: flex;
|
|
714
|
+
align-items: center;
|
|
715
|
+
gap: var(--gap-100);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* Narrow hosts raise the agent pane from the bottom: a phone's secondary
|
|
719
|
+
surface comes up from the edge the thumb is nearest. Deliberately short
|
|
720
|
+
of the full height, so the last turns stay visible above it. */
|
|
721
|
+
.ds-chat-widget__aside-sheet {
|
|
722
|
+
position: absolute;
|
|
723
|
+
right: 0;
|
|
724
|
+
bottom: 0;
|
|
725
|
+
left: 0;
|
|
726
|
+
display: flex;
|
|
727
|
+
flex-direction: column;
|
|
728
|
+
max-height: 82%;
|
|
729
|
+
background-color: var(--color-bg-glass);
|
|
730
|
+
backdrop-filter: blur(24px);
|
|
731
|
+
border-top: var(--border-025) solid var(--color-bg-container-border);
|
|
732
|
+
border-radius: var(--radius-600) var(--radius-600) 0 0;
|
|
733
|
+
box-shadow: var(--shadow-floating);
|
|
734
|
+
transform: translateY(100%);
|
|
735
|
+
transition: transform var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.ds-chat-widget__aside-overlay--open .ds-chat-widget__aside-sheet {
|
|
739
|
+
transform: translateY(0);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.ds-chat-widget__aside-grabber {
|
|
743
|
+
flex: none;
|
|
744
|
+
width: 36px;
|
|
745
|
+
height: 4px;
|
|
746
|
+
margin: var(--gap-200) auto 0;
|
|
747
|
+
border-radius: var(--radius-pill);
|
|
748
|
+
background-color: var(--color-divider);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.ds-chat-widget__aside-sheet-body {
|
|
752
|
+
display: flex;
|
|
753
|
+
flex: 1;
|
|
754
|
+
min-height: 0;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/* The agent toggle's on-state: the passive wash, held down while the panel
|
|
758
|
+
is up. Paired with the library class so it beats the variant's resting
|
|
759
|
+
background whatever order the stylesheets load in. */
|
|
760
|
+
.ds-circular-button.ds-chat-widget__agent-toggle--on,
|
|
761
|
+
.ds-circular-button.ds-chat-widget__agent-toggle--on:hover {
|
|
762
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/* ============================================
|
|
766
|
+
THE LAUNCHER'S PANEL — a card inset from the viewport edges, docked above
|
|
767
|
+
the threshold to the edge the visitor chose; an overlay with a scrim below
|
|
768
|
+
it; a free, non-modal card when floated; a blurred full takeover in
|
|
769
|
+
fullscreen view. On phones it fills the viewport.
|
|
770
|
+
============================================ */
|
|
771
|
+
|
|
772
|
+
.ds-chat-launcher__panel {
|
|
773
|
+
position: fixed;
|
|
774
|
+
top: var(--ds-chat-inset);
|
|
775
|
+
/* Placed from the left on both docks, so one property carries every move
|
|
776
|
+
the card makes and they all glide. */
|
|
777
|
+
left: calc(100% - var(--ds-chat-width) - var(--ds-chat-inset));
|
|
778
|
+
height: calc(100dvh - var(--ds-chat-inset) * 2);
|
|
779
|
+
width: var(--ds-chat-width);
|
|
780
|
+
max-width: calc(100vw - var(--ds-chat-inset) * 2);
|
|
781
|
+
display: flex;
|
|
782
|
+
flex-direction: column;
|
|
783
|
+
/* The glass token: deliberately translucent in both themes, so the page's
|
|
784
|
+
colours reach the surface; the blur is what keeps the transcript
|
|
785
|
+
readable over them. */
|
|
786
|
+
background: var(--color-bg-glass);
|
|
787
|
+
border: var(--border-025) solid var(--color-bg-container-border);
|
|
788
|
+
border-radius: var(--radius-600);
|
|
789
|
+
box-shadow: var(--shadow-floating);
|
|
790
|
+
overflow: hidden;
|
|
791
|
+
backdrop-filter: blur(24px);
|
|
792
|
+
z-index: 1100;
|
|
793
|
+
/* The panel-to-takeover glide: the same card grows to fill the viewport,
|
|
794
|
+
so every property that differs between the two views animates together. */
|
|
795
|
+
transition:
|
|
796
|
+
top var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
797
|
+
left var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
798
|
+
width var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
799
|
+
height var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
800
|
+
max-width var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
801
|
+
border-radius var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
802
|
+
border-color var(--motion-duration-slow) var(--motion-ease-emphasized),
|
|
803
|
+
backdrop-filter var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
804
|
+
/* Open: the card fades in with a small rise from its resting spot. Plays
|
|
805
|
+
on mount, so the view toggle, which keeps the node, never replays it. */
|
|
806
|
+
animation: ds-chat-launcher-panel-in var(--motion-duration-slow) var(--motion-ease-entrance);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/* The widen grip drives --ds-chat-width frame by frame; the width glide
|
|
810
|
+
above would rubber-band behind the pointer, so resizing suspends it. */
|
|
811
|
+
html[data-chat-resizing] .ds-chat-launcher__panel {
|
|
812
|
+
transition: none;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/* Close: the mirror image. The provider keeps the panel mounted for
|
|
816
|
+
MOTION_EXIT_SYNC_MS while this plays, and pointer-events off so a dying
|
|
817
|
+
panel swallows no clicks. */
|
|
818
|
+
.ds-chat-launcher__panel--closing {
|
|
819
|
+
animation: ds-chat-launcher-panel-out var(--motion-duration-base) var(--motion-ease-standard) forwards;
|
|
820
|
+
pointer-events: none;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
@keyframes ds-chat-launcher-panel-in {
|
|
824
|
+
from {
|
|
825
|
+
opacity: 0;
|
|
826
|
+
transform: translateY(10px);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
to {
|
|
830
|
+
opacity: 1;
|
|
831
|
+
transform: none;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
@keyframes ds-chat-launcher-panel-out {
|
|
836
|
+
to {
|
|
837
|
+
opacity: 0;
|
|
838
|
+
transform: translateY(10px);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/* The left dock: the same card on the other edge. */
|
|
843
|
+
.ds-chat-launcher__panel--left {
|
|
844
|
+
left: var(--ds-chat-inset);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/* Fullscreen view: a true viewport takeover. The stronger blur keeps the
|
|
848
|
+
transcript readable while the page's colours still shine through. The
|
|
849
|
+
border fades rather than vanishing, so the geometry never snaps mid-glide. */
|
|
850
|
+
.ds-chat-launcher__panel--full {
|
|
851
|
+
top: 0;
|
|
852
|
+
left: 0;
|
|
853
|
+
height: 100dvh;
|
|
854
|
+
width: 100vw;
|
|
855
|
+
max-width: 100vw;
|
|
856
|
+
border-color: transparent;
|
|
857
|
+
border-radius: 0;
|
|
858
|
+
backdrop-filter: blur(40px);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/* Below the dock threshold the panel overlays the page behind a scrim. */
|
|
862
|
+
.ds-chat-launcher__scrim {
|
|
863
|
+
position: fixed;
|
|
864
|
+
inset: 0;
|
|
865
|
+
background: var(--color-scrim);
|
|
866
|
+
z-index: 1090;
|
|
867
|
+
animation: ds-chat-launcher-scrim-in var(--motion-duration-slow) var(--motion-ease-standard);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
@keyframes ds-chat-launcher-scrim-in {
|
|
871
|
+
from {
|
|
872
|
+
opacity: 0;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/* The widen grip: a 20px grab zone straddling the panel's inner edge, with
|
|
877
|
+
a hover-revealed 4×48px pill. A fixed sibling of the panel, positioned
|
|
878
|
+
from the same variables, so it tracks the edge as the drag rewrites
|
|
879
|
+
--ds-chat-width. */
|
|
880
|
+
.ds-chat-launcher__dock-handle {
|
|
881
|
+
position: fixed;
|
|
882
|
+
top: var(--ds-chat-inset);
|
|
883
|
+
height: calc(100dvh - var(--ds-chat-inset) * 2);
|
|
884
|
+
right: calc(var(--ds-chat-inset) + var(--ds-chat-width) - 10px);
|
|
885
|
+
width: 20px;
|
|
886
|
+
display: flex;
|
|
887
|
+
align-items: center;
|
|
888
|
+
justify-content: center;
|
|
889
|
+
cursor: ew-resize;
|
|
890
|
+
touch-action: none;
|
|
891
|
+
z-index: 1101;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.ds-chat-launcher__dock-handle--left {
|
|
895
|
+
right: auto;
|
|
896
|
+
left: calc(var(--ds-chat-inset) + var(--ds-chat-width) - 10px);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.ds-chat-launcher__dock-handle::after {
|
|
900
|
+
content: "";
|
|
901
|
+
width: 4px;
|
|
902
|
+
height: 48px;
|
|
903
|
+
border-radius: var(--radius-pill);
|
|
904
|
+
background: transparent;
|
|
905
|
+
transition: background var(--motion-duration-fast) var(--motion-ease-standard);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.ds-chat-launcher__dock-handle:hover::after,
|
|
909
|
+
.ds-chat-launcher__dock-handle--resizing::after {
|
|
910
|
+
background: var(--color-text-tertiary);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.ds-chat-launcher__dock-handle:active::after {
|
|
914
|
+
background: var(--color-text-secondary);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/* On short viewports the vertical insets are height the widget needs more
|
|
918
|
+
than the chrome does. */
|
|
919
|
+
@media (max-height: 619px) {
|
|
920
|
+
.ds-chat-launcher__panel {
|
|
921
|
+
top: 0;
|
|
922
|
+
height: 100dvh;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.ds-chat-launcher__dock-handle {
|
|
926
|
+
top: 0;
|
|
927
|
+
height: 100dvh;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/* Floating: popped out of the dock into a shorter card the visitor drags by
|
|
932
|
+
its header. The launcher writes the position variables once it has been
|
|
933
|
+
moved; until then it rests in the launcher's corner. After the
|
|
934
|
+
short-viewport rule above on purpose, so the card keeps its own height. */
|
|
935
|
+
.ds-chat-launcher__panel--floating {
|
|
936
|
+
--ds-chat-float-h: min(
|
|
937
|
+
var(--ds-chat-float-height),
|
|
938
|
+
calc(100dvh - var(--ds-chat-inset) * 2)
|
|
939
|
+
);
|
|
940
|
+
top: var(--ds-chat-float-y, calc(100dvh - var(--ds-chat-float-h) - var(--ds-chat-inset)));
|
|
941
|
+
left: var(--ds-chat-float-x, calc(100% - var(--ds-chat-width) - var(--ds-chat-inset)));
|
|
942
|
+
width: var(--ds-chat-float-w, var(--ds-chat-width));
|
|
943
|
+
height: var(--ds-chat-float-h);
|
|
944
|
+
max-height: 100dvh;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/* The size grips: an 8px zone along each edge and a 16px square in each
|
|
948
|
+
corner, all inside the card. */
|
|
949
|
+
.ds-chat-launcher__float-grip {
|
|
950
|
+
position: absolute;
|
|
951
|
+
z-index: 10;
|
|
952
|
+
display: flex;
|
|
953
|
+
align-items: center;
|
|
954
|
+
justify-content: center;
|
|
955
|
+
touch-action: none;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.ds-chat-launcher__float-grip[data-dir="n"],
|
|
959
|
+
.ds-chat-launcher__float-grip[data-dir="s"] {
|
|
960
|
+
left: 16px;
|
|
961
|
+
right: 16px;
|
|
962
|
+
height: 8px;
|
|
963
|
+
cursor: ns-resize;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.ds-chat-launcher__float-grip[data-dir="e"],
|
|
967
|
+
.ds-chat-launcher__float-grip[data-dir="w"] {
|
|
968
|
+
top: 16px;
|
|
969
|
+
bottom: 16px;
|
|
970
|
+
width: 8px;
|
|
971
|
+
cursor: ew-resize;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.ds-chat-launcher__float-grip[data-dir="n"] { top: 0; }
|
|
975
|
+
.ds-chat-launcher__float-grip[data-dir="s"] { bottom: 0; }
|
|
976
|
+
.ds-chat-launcher__float-grip[data-dir="e"] { right: 0; }
|
|
977
|
+
.ds-chat-launcher__float-grip[data-dir="w"] { left: 0; }
|
|
978
|
+
|
|
979
|
+
.ds-chat-launcher__float-grip[data-dir="ne"],
|
|
980
|
+
.ds-chat-launcher__float-grip[data-dir="nw"],
|
|
981
|
+
.ds-chat-launcher__float-grip[data-dir="se"],
|
|
982
|
+
.ds-chat-launcher__float-grip[data-dir="sw"] {
|
|
983
|
+
width: 16px;
|
|
984
|
+
height: 16px;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.ds-chat-launcher__float-grip[data-dir="ne"] { top: 0; right: 0; cursor: nesw-resize; }
|
|
988
|
+
.ds-chat-launcher__float-grip[data-dir="sw"] { bottom: 0; left: 0; cursor: nesw-resize; }
|
|
989
|
+
.ds-chat-launcher__float-grip[data-dir="nw"] { top: 0; left: 0; cursor: nwse-resize; }
|
|
990
|
+
.ds-chat-launcher__float-grip[data-dir="se"] { bottom: 0; right: 0; cursor: nwse-resize; }
|
|
991
|
+
|
|
992
|
+
.ds-chat-launcher__float-grip[data-dir="n"]::after,
|
|
993
|
+
.ds-chat-launcher__float-grip[data-dir="s"]::after,
|
|
994
|
+
.ds-chat-launcher__float-grip[data-dir="e"]::after,
|
|
995
|
+
.ds-chat-launcher__float-grip[data-dir="w"]::after {
|
|
996
|
+
content: "";
|
|
997
|
+
border-radius: var(--radius-pill);
|
|
998
|
+
background: transparent;
|
|
999
|
+
transition: background var(--motion-duration-fast) var(--motion-ease-standard);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.ds-chat-launcher__float-grip[data-dir="n"]::after,
|
|
1003
|
+
.ds-chat-launcher__float-grip[data-dir="s"]::after {
|
|
1004
|
+
width: 48px;
|
|
1005
|
+
height: 4px;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.ds-chat-launcher__float-grip[data-dir="e"]::after,
|
|
1009
|
+
.ds-chat-launcher__float-grip[data-dir="w"]::after {
|
|
1010
|
+
width: 4px;
|
|
1011
|
+
height: 48px;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.ds-chat-launcher__float-grip:hover::after {
|
|
1015
|
+
background: var(--color-text-tertiary);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
.ds-chat-launcher__float-grip:active::after {
|
|
1019
|
+
background: var(--color-text-secondary);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.ds-chat-launcher__panel--floating .ds-chat-header {
|
|
1023
|
+
cursor: grab;
|
|
1024
|
+
touch-action: none;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
html[data-chat-resizing] .ds-chat-launcher__panel--floating .ds-chat-header {
|
|
1028
|
+
cursor: grabbing;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/* Phone widths: the chat is the document, not an overlay. The panel leaves
|
|
1032
|
+
the fixed layer for normal flow at 100dvh, and the host hides the page
|
|
1033
|
+
around it on the takeover attribute, so the soft keyboard is handled the
|
|
1034
|
+
way the browser handles any page with a field at the bottom. The launcher
|
|
1035
|
+
reads the same threshold as TAKEOVER_QUERY; this rule covers the first
|
|
1036
|
+
render, before a media query has been read, so the two must stay in step.
|
|
1037
|
+
No transition: there is no card form to glide from. */
|
|
1038
|
+
@media (max-width: 719px) {
|
|
1039
|
+
.ds-chat-launcher__panel {
|
|
1040
|
+
position: relative;
|
|
1041
|
+
top: auto;
|
|
1042
|
+
left: auto;
|
|
1043
|
+
height: 100dvh;
|
|
1044
|
+
width: 100%;
|
|
1045
|
+
max-width: none;
|
|
1046
|
+
border: none;
|
|
1047
|
+
border-radius: 0;
|
|
1048
|
+
box-shadow: none;
|
|
1049
|
+
transition: none;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* ============================================
|
|
1054
|
+
THE FAB — the chat's persistent entry point, bottom right on every page.
|
|
1055
|
+
Hidden while the panel is open; the panel's close returns here.
|
|
1056
|
+
============================================ */
|
|
1057
|
+
|
|
1058
|
+
.ds-chat-launcher__fab {
|
|
1059
|
+
position: fixed;
|
|
1060
|
+
right: var(--ds-chat-inset);
|
|
1061
|
+
bottom: var(--ds-chat-inset);
|
|
1062
|
+
z-index: 1080;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/* The button's own solid field keeps it legible over whatever content
|
|
1066
|
+
scrolls beneath it; the FAB only adds the floating elevation. No
|
|
1067
|
+
backdrop-filter here: the fill is opaque, and an ancestor backdrop root
|
|
1068
|
+
would blind the summary panel's frost. */
|
|
1069
|
+
.ds-chat-launcher__fab .ds-ai-button {
|
|
1070
|
+
border-radius: var(--radius-pill);
|
|
1071
|
+
box-shadow: var(--shadow-floating);
|
|
1072
|
+
}
|