sgiant-ai-widget 1.0.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/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/ai-invalidation.d.ts +29 -0
- package/dist/ai-invalidation.d.ts.map +1 -0
- package/dist/ai-invalidation.js +121 -0
- package/dist/ai-invalidation.js.map +1 -0
- package/dist/contrast.d.ts +87 -0
- package/dist/contrast.d.ts.map +1 -0
- package/dist/contrast.js +151 -0
- package/dist/contrast.js.map +1 -0
- package/dist/directive.d.ts +37 -0
- package/dist/directive.d.ts.map +1 -0
- package/dist/directive.js +87 -0
- package/dist/directive.js.map +1 -0
- package/dist/dom.d.ts +83 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +146 -0
- package/dist/dom.js.map +1 -0
- package/dist/host-actions.d.ts +192 -0
- package/dist/host-actions.d.ts.map +1 -0
- package/dist/host-actions.js +443 -0
- package/dist/host-actions.js.map +1 -0
- package/dist/icons.d.ts +21 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +84 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +600 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4833 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.d.ts +187 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +232 -0
- package/dist/labels.js.map +1 -0
- package/dist/limits.d.ts +43 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +48 -0
- package/dist/limits.js.map +1 -0
- package/dist/markdown.d.ts +23 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +0 -0
- package/dist/markdown.js.map +1 -0
- package/dist/message-chrome.d.ts +45 -0
- package/dist/message-chrome.d.ts.map +1 -0
- package/dist/message-chrome.js +101 -0
- package/dist/message-chrome.js.map +1 -0
- package/dist/pane-follow.d.ts +67 -0
- package/dist/pane-follow.d.ts.map +1 -0
- package/dist/pane-follow.js +61 -0
- package/dist/pane-follow.js.map +1 -0
- package/dist/prefix.d.ts +9 -0
- package/dist/prefix.d.ts.map +1 -0
- package/dist/prefix.js +9 -0
- package/dist/prefix.js.map +1 -0
- package/dist/proposal-summary.d.ts +19 -0
- package/dist/proposal-summary.d.ts.map +1 -0
- package/dist/proposal-summary.js +73 -0
- package/dist/proposal-summary.js.map +1 -0
- package/dist/replay.d.ts +125 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +172 -0
- package/dist/replay.js.map +1 -0
- package/dist/safe-url.d.ts +47 -0
- package/dist/safe-url.d.ts.map +1 -0
- package/dist/safe-url.js +80 -0
- package/dist/safe-url.js.map +1 -0
- package/dist/sgiant-ai-widget.global.js +778 -0
- package/dist/sgiant-ai-widget.global.js.map +7 -0
- package/dist/specs.d.ts +173 -0
- package/dist/specs.d.ts.map +1 -0
- package/dist/specs.js +231 -0
- package/dist/specs.js.map +1 -0
- package/dist/storage.d.ts +77 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +124 -0
- package/dist/storage.js.map +1 -0
- package/dist/styles.d.ts +29 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +876 -0
- package/dist/styles.js.map +1 -0
- package/dist/ui-control.d.ts +8 -0
- package/dist/ui-control.d.ts.map +1 -0
- package/dist/ui-control.js +8 -0
- package/dist/ui-control.js.map +1 -0
- package/dist/ui-render.d.ts +75 -0
- package/dist/ui-render.d.ts.map +1 -0
- package/dist/ui-render.js +617 -0
- package/dist/ui-render.js.map +1 -0
- package/dist/ui-spec.d.ts +143 -0
- package/dist/ui-spec.d.ts.map +1 -0
- package/dist/ui-spec.js +288 -0
- package/dist/ui-spec.js.map +1 -0
- package/package.json +61 -0
package/dist/styles.js
ADDED
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widget's stylesheet, and the one decision that decides how it is themed.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from index.ts (#320), which was 8278 lines with 5822 of them inside
|
|
5
|
+
* a single function. This is the largest region that is NOT inside that
|
|
6
|
+
* closure — 730 lines that depend on exactly three things (the class prefix,
|
|
7
|
+
* the motion tokens, and which side the launcher sits on) and are read by one
|
|
8
|
+
* caller. Moving it is mechanical and changes no behaviour, which is the point:
|
|
9
|
+
* the file it came from is the one #306 proposes publishing, and 8278 lines is
|
|
10
|
+
* not something a third party can read.
|
|
11
|
+
*/
|
|
12
|
+
import { motionCssVars } from "./limits.js";
|
|
13
|
+
import { PREFIX } from "./prefix.js";
|
|
14
|
+
let stylesInjected = false;
|
|
15
|
+
/**
|
|
16
|
+
* Does the page around us define the PLATFORM's control variables?
|
|
17
|
+
*
|
|
18
|
+
* When the widget is mounted inside one of our own apps, `--input` / `--ring` /
|
|
19
|
+
* `--card` / … are already on `<html>` (set by the host's own stylesheet), and
|
|
20
|
+
* a text field in chat should be the same object as a text field on the page
|
|
21
|
+
* behind it — same border, same focus ring, same radius, flipping together when
|
|
22
|
+
* the app toggles `.dark`. When the widget is embedded on a customer's site,
|
|
23
|
+
* none of that exists and it must fall back to its own `--aiw-*` palette.
|
|
24
|
+
*
|
|
25
|
+
* The check is deliberately shape-based, not merely presence-based: `--input`
|
|
26
|
+
* is a plausible name for anyone to invent, but our tokens are HSL COMPONENTS
|
|
27
|
+
* ("220 9% 85%") so they can be used with an alpha. A site whose `--input` is
|
|
28
|
+
* `#fff` fails the test and we leave its page alone — the failure mode of
|
|
29
|
+
* guessing wrong here is an unreadable chat form on someone else's website.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The near-black the user bubble mixes the accent into.
|
|
33
|
+
*
|
|
34
|
+
* Exported because `index.ts` derives that bubble's foreground against the
|
|
35
|
+
* RESULT of the mix, not against the accent (#307 one level down: over the teal
|
|
36
|
+
* all three hosts pass, the bubble is `#4a9d9e` and the old literal `#fff` was
|
|
37
|
+
* 3.18:1). Two copies of this number would put the derivation and the paint on
|
|
38
|
+
* different backgrounds, which is the same bug again.
|
|
39
|
+
*/
|
|
40
|
+
export const USER_BUBBLE_INK = "#04191b";
|
|
41
|
+
export function hostDefinesPlatformTokens() {
|
|
42
|
+
try {
|
|
43
|
+
const cs = getComputedStyle(document.documentElement);
|
|
44
|
+
const hslTriplet = /^-?[\d.]+\s+[\d.]+%\s+[\d.]+%$/;
|
|
45
|
+
const required = [
|
|
46
|
+
"--input",
|
|
47
|
+
"--ring",
|
|
48
|
+
"--card",
|
|
49
|
+
"--foreground",
|
|
50
|
+
"--muted-foreground",
|
|
51
|
+
"--primary-foreground",
|
|
52
|
+
];
|
|
53
|
+
if (!required.every((v) => hslTriplet.test(cs.getPropertyValue(v).trim())))
|
|
54
|
+
return false;
|
|
55
|
+
return cs.getPropertyValue("--radius").trim().length > 0;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// No DOM/computed style (SSR, hostile sandbox) — own palette is always safe.
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* DARK MODE: the host's own switch first, the OS only as a fallback.
|
|
64
|
+
*
|
|
65
|
+
* This sheet expressed dark ONLY as `@media (prefers-color-scheme:dark)`, which
|
|
66
|
+
* is right for a widget dropped on a stranger's page and wrong inside our own
|
|
67
|
+
* apps: they carry a light/dark/system switch of their own
|
|
68
|
+
* (a host theme toggle flips `.dark` on the document
|
|
69
|
+
* element). A user whose OS was dark and whose app was set to LIGHT got a dark
|
|
70
|
+
* widget in a light app, and the reverse broke exactly as hard. Reported from
|
|
71
|
+
* the product, and no gate could have caught it — nothing type-checks a media
|
|
72
|
+
* query, and the widget looks correct in whichever mode the person who changed
|
|
73
|
+
* it happened to be in.
|
|
74
|
+
*
|
|
75
|
+
* `-host-tokens` already answers "are we inside one of our apps": it is set at
|
|
76
|
+
* mount by `hostDefinesPlatformTokens()`, which tests for the platform's own
|
|
77
|
+
* HSL-COMPONENT control variables rather than merely for a name anyone could
|
|
78
|
+
* invent. Where it is present the app owns the scheme in BOTH directions, so
|
|
79
|
+
* the OS query must not apply. Where it is absent nothing else knows the answer
|
|
80
|
+
* and the OS query is all there is.
|
|
81
|
+
*
|
|
82
|
+
' * The pane's colours are NOT in here any more. It used to carry a complete
|
|
83
|
+
* parallel palette in literals — #f4f5f7 / #eef0f3 / #e7e8ec / #777 light and
|
|
84
|
+
* #121212 / #1a1a1a / #262626 / #9b9b9b dark — which is what #306 measured as
|
|
85
|
+
* "the advanced pane does not participate in the token system at all": a themed
|
|
86
|
+
* host repainted the whole widget and this pane stayed grey. Every one of those
|
|
87
|
+
* eight values was the light or dark reading of a token that already existed,
|
|
88
|
+
* so the pane now reads the tokens and its dark half falls out for free. Only
|
|
89
|
+
* the shadow differs by scheme, because that one genuinely is not a colour
|
|
90
|
+
* token.
|
|
91
|
+
*
|
|
92
|
+
* It also lifts the URL strip off the floor: #777 on #eef0f3 measured 3.92:1,
|
|
93
|
+
* under the 4.5:1 body-text minimum. --aiw-muted is documented above as a
|
|
94
|
+
* CONTRAST FLOOR and clears it on both surfaces.
|
|
95
|
+
*
|
|
96
|
+
* ONE source string, interpolated under two selector scopes. A hand-copied
|
|
97
|
+
* palette is a palette that drifts, and this one has three separate blocks.
|
|
98
|
+
*/
|
|
99
|
+
const darkRules = (bubble, panel) => `
|
|
100
|
+
${bubble},${panel}{--aiw-accent-ink:var(--aiw-accent-ink-dark,var(--aiw-accent));--aiw-surface:#161616;--aiw-surface-raised:#1d1d1d;--aiw-surface-2:#2c2c2c;--aiw-bg:#101010;--aiw-text:#eee;--aiw-text-2:#ddd;--aiw-muted:#9b9b9b;--aiw-border:#2a2a2a;--aiw-border-strong:#444;--aiw-border-soft:#262626;--aiw-danger-bg:#231613;--aiw-danger-border:#5a2c1d;--aiw-danger-text:#ff9b7a;--aiw-danger-text-2:#d3a08d;--aiw-ok-bg:#122017;--aiw-ok-border:#2c4d36;--aiw-ok-text:#7fd39a}
|
|
101
|
+
${panel} .${PREFIX}-assistant code{background:rgba(255,255,255,.1)}
|
|
102
|
+
${panel} .${PREFIX}-assistant blockquote{color:#aaa;border-left-color:color-mix(in srgb,var(--aiw-accent) 53%,transparent)}
|
|
103
|
+
${panel} .${PREFIX}-assistant table.md-table th{color:#aaa;border-bottom-color:#2a2a2a}
|
|
104
|
+
${panel} .${PREFIX}-assistant table.md-table td{border-bottom-color:#222}
|
|
105
|
+
${panel} .${PREFIX}-assistant hr{border-top-color:#2a2a2a}
|
|
106
|
+
${panel} .${PREFIX}-pane-frame{box-shadow:0 1px 4px rgba(0,0,0,.4)}
|
|
107
|
+
${panel}.${PREFIX}-advanced .${PREFIX}-chatcol{border-right-color:#262626}
|
|
108
|
+
${panel}.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-chatcol{border-right:0}
|
|
109
|
+
`;
|
|
110
|
+
export function injectStyles(side) {
|
|
111
|
+
if (stylesInjected)
|
|
112
|
+
return;
|
|
113
|
+
stylesInjected = true;
|
|
114
|
+
// WARNING, and it costs a round trip every time: this whole stylesheet is a
|
|
115
|
+
// TEMPLATE LITERAL. A backtick anywhere below — including inside a CSS
|
|
116
|
+
// comment, where it reads as harmless prose quoting a token name — closes the
|
|
117
|
+
// JS string, and the file fails to compile with an error that points at the
|
|
118
|
+
// wrong line and says only "';' expected". Do not quote identifiers with
|
|
119
|
+
// backticks in these comments.
|
|
120
|
+
// Computed HERE and not interpolated as calls, because the sheet below is a
|
|
121
|
+
// template literal and `aiw-launcher.test.ts` refuses a backtick anywhere
|
|
122
|
+
// inside it. That rule is worth keeping exactly as blunt as it is — a stray
|
|
123
|
+
// backtick in a CSS comment closes the string and reports as "';' expected"
|
|
124
|
+
// on the wrong line — so the calls come out rather than the guard being
|
|
125
|
+
// loosened to understand `${...}` spans.
|
|
126
|
+
const darkWhenHostSaysSo = darkRules(`.dark .${PREFIX}-bubble`, `.dark .${PREFIX}-panel`);
|
|
127
|
+
const darkWhenNobodySaid = darkRules(`.${PREFIX}-bubble:not(.${PREFIX}-host-tokens)`, `.${PREFIX}-panel:not(.${PREFIX}-host-tokens)`);
|
|
128
|
+
const css = `
|
|
129
|
+
/* Theme tokens — every color in this sheet reads from these. Light defaults
|
|
130
|
+
here; the dark media block below only REDEFINES tokens; explicit host
|
|
131
|
+
overrides (accent/gradient/theme) land INLINE on the roots and win over
|
|
132
|
+
both, so a themed host is never surprised by the OS color scheme. */
|
|
133
|
+
/* Status surfaces (error card, failed job, flag notes) are TOKENS, not literal
|
|
134
|
+
* colours. They used to be hardcoded light hexes with no dark counterpart, so
|
|
135
|
+
* in dark mode a failed job painted #fdf3f3 (near-white) while its title stayed
|
|
136
|
+
* var(--aiw-text) = #eee — white on white, i.e. invisible. Same class of bug
|
|
137
|
+
* left the error card's rust text on its own near-black background. Anything
|
|
138
|
+
* that needs a red/green surface must use these four vars so the dark override
|
|
139
|
+
* below reaches it automatically.
|
|
140
|
+
*
|
|
141
|
+
* NOTE: this whole sheet is a JS template literal — no backticks in here.
|
|
142
|
+
* --aiw-muted is a CONTRAST FLOOR, not a shade preference. It was #888,
|
|
143
|
+
* which measures 3.54:1 on this sheet's own white surface — under the 4.5:1
|
|
144
|
+
* body-text minimum, on the token carrying timestamps, job states and event
|
|
145
|
+
* detail. #6e6e6e clears it against every surface the widget paints
|
|
146
|
+
* (5.10:1 on #fff, 4.76:1 on the darkest, #f7f7f8). The dark counterpart
|
|
147
|
+
* (#9b9b9b) already passes at 6.51:1 / 5.02:1 and is unchanged. Anything
|
|
148
|
+
' * lighter than this in light mode is a regression, not a restyle.
|
|
149
|
+
*
|
|
150
|
+
* --aiw-header-bg / --aiw-header-fg are the HEADER's own pair, and they exist
|
|
151
|
+
* because the bar is not an accent-filled control. Its buttons read
|
|
152
|
+
* --aiw-accent-contrast, which #307 correctly made DERIVED from the accent
|
|
153
|
+
* ("white on the teal every host actually passes is 2.00:1"). Against the teal
|
|
154
|
+
* all three of our apps pass, that derivation returns brand navy #151D2F —
|
|
155
|
+
* which is the header background. Navy on navy: 1.00:1, an invisible toolbar,
|
|
156
|
+
* shipped and reported from the product. A token means "readable on the
|
|
157
|
+
* ACCENT"; the header is not the accent. Cream on navy is 15.66:1.
|
|
158
|
+
*
|
|
159
|
+
* These two deliberately do NOT flip with the colour scheme, and neither does
|
|
160
|
+
* the bar. --aiw-surface does, which is how the active-icon chip came to paint
|
|
161
|
+
* #161616 on #151D2F in dark mode. */
|
|
162
|
+
.${PREFIX}-bubble,.${PREFIX}-panel{${motionCssVars()}--aiw-font-3xs:10px;--aiw-font-2xs:11px;--aiw-font-xs:12px;--aiw-font-sm:13px;--aiw-font-md:14px;--aiw-font-lg:15px;--aiw-font-xl:16px;--aiw-font-2xl:18px;--aiw-font-3xl:30px;--aiw-radius-2xs:2px;--aiw-radius-xs:4px;--aiw-radius-sm:6px;--aiw-radius-md:8px;--aiw-radius-lg:10px;--aiw-radius-xl:12px;--aiw-radius-2xl:14px;--aiw-radius-3xl:18px;--aiw-radius-pill:999px;--aiw-accent:#6d28d9;--aiw-accent-contrast:#fff;--aiw-accent-ink:var(--aiw-accent-ink-light,var(--aiw-accent));--aiw-header-bg:#151D2F;--aiw-header-fg:#FCF7E3;--aiw-gradient:linear-gradient(135deg,var(--aiw-accent),var(--aiw-accent));--aiw-surface:#fff;--aiw-surface-raised:#fff;--aiw-surface-2:#f7f7f8;--aiw-bg:#fafafa;--aiw-text:#111;--aiw-text-2:#555;--aiw-muted:#6e6e6e;--aiw-border:#e6e6e6;--aiw-border-strong:#ddd;--aiw-border-soft:#f0f0f0;--aiw-danger-bg:#fff6f2;--aiw-danger-border:#f3c5b6;--aiw-danger-text:#b23b18;--aiw-danger-text-2:#8a5648;--aiw-ok-bg:#f2fbf5;--aiw-ok-border:#bfe3c8;--aiw-ok-text:#2f7d43}
|
|
163
|
+
@keyframes ${PREFIX}-spin{to{transform:rotate(360deg)}}
|
|
164
|
+
@keyframes ${PREFIX}-rise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
|
165
|
+
@keyframes ${PREFIX}-blink{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}
|
|
166
|
+
@keyframes ${PREFIX}-richin{from{opacity:.15;transform:translateY(3px)}to{opacity:1;transform:none}}
|
|
167
|
+
.${PREFIX}-rich-in{animation:${PREFIX}-richin var(--duration-base) var(--ease-out)}
|
|
168
|
+
@keyframes ${PREFIX}-tokin{from{opacity:0;filter:blur(5px)}to{opacity:1;filter:blur(0)}}
|
|
169
|
+
.${PREFIX}-tok{animation:${PREFIX}-tokin var(--duration-base) var(--ease-out) forwards}
|
|
170
|
+
.${PREFIX}-activity{align-self:flex-start;display:inline-flex;align-items:center;gap:7px;max-width:92%;border:1px solid color-mix(in srgb,var(--aiw-accent) 15%,transparent);background:color-mix(in srgb,var(--aiw-accent) 5%,transparent);border-radius:var(--aiw-radius-lg);padding:5px 10px;font-size:var(--aiw-font-xs);font-weight:500;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
171
|
+
.${PREFIX}-activity-done{opacity:.72}
|
|
172
|
+
.${PREFIX}-act-label{color:var(--aiw-text-2)}
|
|
173
|
+
.${PREFIX}-act-agent{flex:0 0 auto;font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--aiw-accent) 24%,transparent);border-radius:var(--aiw-radius-sm);padding:1px 6px}
|
|
174
|
+
.${PREFIX}-act-model{flex:0 0 auto;font-size:var(--aiw-font-3xs);font-weight:600;letter-spacing:.2px;color:var(--aiw-muted);background:#9aa0a614;border:1px solid #9aa0a630;border-radius:var(--aiw-radius-sm);padding:1px 5px;font-variant-numeric:tabular-nums}
|
|
175
|
+
.${PREFIX}-act-spin{width:11px;height:11px;flex:0 0 auto;border-radius:50%;border:2px solid color-mix(in srgb,var(--aiw-accent) 27%,transparent);border-top-color:var(--aiw-accent);animation:${PREFIX}-spin .7s linear infinite}
|
|
176
|
+
.${PREFIX}-act-ok{color:var(--aiw-ok-text);font-weight:700}
|
|
177
|
+
.${PREFIX}-act-x{color:var(--aiw-danger-text);font-weight:700}
|
|
178
|
+
.${PREFIX}-job{align-self:flex-start;display:flex;flex-direction:column;gap:6px;max-width:92%;min-width:min(260px,100%);border:1px solid color-mix(in srgb,var(--aiw-accent) 18%,transparent);background:color-mix(in srgb,var(--aiw-accent) 5%,transparent);border-radius:var(--aiw-radius-xl);padding:9px 11px;font-size:var(--aiw-font-xs);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
179
|
+
.${PREFIX}-job-done{opacity:.85}
|
|
180
|
+
.${PREFIX}-job-failed{border-color:var(--aiw-danger-border);background:var(--aiw-danger-bg)}
|
|
181
|
+
.${PREFIX}-job-head{display:flex;align-items:center;gap:7px}
|
|
182
|
+
.${PREFIX}-job-title{font-weight:600;color:var(--aiw-text)}
|
|
183
|
+
.${PREFIX}-job-state{margin-left:auto;font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:var(--aiw-muted)}
|
|
184
|
+
.${PREFIX}-job-counts{color:var(--aiw-text-2);font-variant-numeric:tabular-nums}
|
|
185
|
+
.${PREFIX}-job-bar{height:4px;border-radius:var(--aiw-radius-2xs);background:color-mix(in srgb,var(--aiw-accent) 14%,transparent);overflow:hidden}
|
|
186
|
+
/* scaleX, not width (#309). Animating width reflows on every frame; a
|
|
187
|
+
transform is composited. transform-origin:left so it grows from the start of
|
|
188
|
+
the track rather than its middle. */
|
|
189
|
+
.${PREFIX}-job-bar>i{display:block;height:100%;width:100%;transform-origin:left;background:var(--aiw-accent);transition:transform var(--duration-base) var(--ease-out)}
|
|
190
|
+
.${PREFIX}-job-detail{color:var(--aiw-muted);font-size:var(--aiw-font-2xs);word-break:break-word}
|
|
191
|
+
.${PREFIX}-job-mirrored{color:var(--aiw-muted);font-size:var(--aiw-font-2xs);font-style:italic}
|
|
192
|
+
.${PREFIX}-pane-widget{width:100%;height:100%;overflow:auto;background:var(--aiw-surface);border-radius:var(--aiw-radius-xl);padding:16px;box-sizing:border-box}
|
|
193
|
+
.${PREFIX}-job-flow{list-style:none;margin:6px 0 0;padding:0 0 0 2px;display:flex;flex-direction:column;gap:3px}
|
|
194
|
+
.${PREFIX}-job-ev{position:relative;padding-left:12px;color:var(--aiw-text-2);font-size:var(--aiw-font-2xs);line-height:1.35;word-break:break-word}
|
|
195
|
+
.${PREFIX}-job-ev::before{content:"";position:absolute;left:0;top:6px;width:5px;height:5px;border-radius:50%;background:var(--aiw-muted)}
|
|
196
|
+
.${PREFIX}-job-ev-decision{color:var(--aiw-text)}
|
|
197
|
+
.${PREFIX}-job-ev-decision::before{background:var(--aiw-accent,currentColor)}
|
|
198
|
+
.${PREFIX}-job-ev-problem{color:var(--aiw-danger-text)}
|
|
199
|
+
.${PREFIX}-job-ev-problem::before{background:currentColor}
|
|
200
|
+
.${PREFIX}-job-flow-all{margin-top:4px}
|
|
201
|
+
.${PREFIX}-job-flow-all>summary{cursor:pointer;color:var(--aiw-muted);font-size:var(--aiw-font-2xs);user-select:none}
|
|
202
|
+
.${PREFIX}-replay-note{align-self:flex-start;display:inline-flex;align-items:center;border:1px dashed var(--aiw-border-strong);border-radius:var(--aiw-radius-md);padding:4px 10px;font-size:var(--aiw-font-xs);color:var(--aiw-muted);background:var(--aiw-bg)}
|
|
203
|
+
.${PREFIX}-flag-note{align-self:center;border-style:solid;border-color:var(--aiw-danger-border);color:var(--aiw-danger-text);background:var(--aiw-danger-bg);font-weight:600}
|
|
204
|
+
.${PREFIX}-flag-ok{border-color:var(--aiw-ok-border);color:var(--aiw-ok-text);background:var(--aiw-ok-bg)}
|
|
205
|
+
.${PREFIX}-usage{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;margin-top:-4px;padding:0 2px;font-size:var(--aiw-font-3xs);color:var(--aiw-muted);font-variant-numeric:tabular-nums}
|
|
206
|
+
.${PREFIX}-usage-pill{display:inline-flex;align-items:center;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-sm);padding:0 5px;line-height:16px}
|
|
207
|
+
.${PREFIX}-usage-sep{opacity:.5}
|
|
208
|
+
@keyframes ${PREFIX}-pulse{0%{box-shadow:0 0 0 0 rgba(96,199,200,.5)}70%{box-shadow:0 0 0 12px rgba(96,199,200,0)}100%{box-shadow:0 0 0 0 rgba(96,199,200,0)}}
|
|
209
|
+
@keyframes ${PREFIX}-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-2.5px)}}
|
|
210
|
+
@keyframes ${PREFIX}-blink2{0%,90%,100%{transform:scaleY(1)}95%{transform:scaleY(.12)}}
|
|
211
|
+
/* The crescent (#305). No float loop, no drop-shadow glow: those were two of
|
|
212
|
+
the eight effects the launcher stacked at rest, and a control that is loud at
|
|
213
|
+
rest has nothing left to say when a reply is waiting. */
|
|
214
|
+
.${PREFIX}-ayca{display:block;width:100%;height:100%;overflow:visible}
|
|
215
|
+
/* THE INVERSION, in two rules. Light ground: navy disc, gradient mark. Ink
|
|
216
|
+
ground: cream disc, navy mark. Nothing else needs to know about grounds. */
|
|
217
|
+
.${PREFIX}-av-mark{fill:url(#${PREFIX}-av-sweep)}
|
|
218
|
+
.${PREFIX}-on-ink .${PREFIX}-av-mark{fill:#151D2F}
|
|
219
|
+
/* THE OBJECT INVERTS, not just the mark. Inverting only the fill left a navy
|
|
220
|
+
mark on a navy disc — invisible, and precisely the failure the rule exists to
|
|
221
|
+
prevent: "a navy pebble on a navy section is a hole with a moon in it". Found
|
|
222
|
+
by looking at it on the dark marketing hero, where the class was correct and
|
|
223
|
+
the result was still nothing. */
|
|
224
|
+
/* The ink variant carries a heavier shadow (it has less to push against) and a
|
|
225
|
+
HAIRLINE of the opposite value. The hairline is what makes a stale ground
|
|
226
|
+
reading survivable: a cream disc that ends up over white is 1.06:1 and
|
|
227
|
+
effectively gone, and the ground under a fixed launcher genuinely changes as
|
|
228
|
+
the reader scrolls. One pixel of navy is enough to keep it an object. */
|
|
229
|
+
.${PREFIX}-bubble.${PREFIX}-on-ink{background:#FCF7E3;box-shadow:0 8px 24px rgba(0,0,0,.45),0 0 0 1px rgba(21,29,47,.55)}
|
|
230
|
+
.${PREFIX}-bubble.${PREFIX}-on-ink .${PREFIX}-bubble-label{color:#151D2F}
|
|
231
|
+
.${PREFIX}-bubble.${PREFIX}-on-ink .${PREFIX}-bubble-dot{box-shadow:0 0 0 2px #FCF7E3}
|
|
232
|
+
.${PREFIX}-eyes{transform-origin:24px 23px;animation:${PREFIX}-blink2 5.5s ease-in-out infinite}
|
|
233
|
+
/* The collapsed launcher is a real, recognisable FAB: a circular surface chip
|
|
234
|
+
with a soft shadow and a hairline ring, the animated mascot centred inside.
|
|
235
|
+
Without the chip the mascot floated as a formless gradient blob that did not
|
|
236
|
+
read as "a chat" at all — the reported "I can't see any chatbox" (#88): the
|
|
237
|
+
widget was mounted and working, just invisible as an affordance. */
|
|
238
|
+
/* Layering: the widget sits ABOVE page content and content-level overlays
|
|
239
|
+
(viewers/drawers, z<=40) but BELOW the app's Radix modals (z-50) — a dialog
|
|
240
|
+
the user opens must cover the chat, not hide behind it. The old max-int
|
|
241
|
+
z-index put the chat over every modal. */
|
|
242
|
+
/* THE LAUNCHER AT REST IS STILL. It is a navy disc carrying the gradient
|
|
243
|
+
crescent — the object straddles navy, per the ground rule above — with one
|
|
244
|
+
shadow to lift it off the page. The gradient RING is gone with the glow and
|
|
245
|
+
the float: the mark already carries the brand, and the ring was a third
|
|
246
|
+
place saying the same thing. #88 ("I can't see any chatbox") is answered by
|
|
247
|
+
the disc's own contrast (navy on white is 16.82:1) and, on a first visit, by
|
|
248
|
+
the named pill below — a word rather than a puzzle. */
|
|
249
|
+
.${PREFIX}-bubble{position:fixed;bottom:18px;${side}:18px;z-index:48;width:56px;height:56px;border:none;border-radius:50%;background:#151D2F;color:#FCF7E3;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:9px;padding:0;box-shadow:0 6px 20px rgba(21,29,47,.22);transition:transform var(--duration-fast,150ms) var(--ease-out),box-shadow var(--duration-fast,150ms) var(--ease-out)}
|
|
250
|
+
/* 26 in 56. The mark was 34 at first and the object read flat — a disc with a
|
|
251
|
+
shape stamped on it rather than a mark sitting in space. The ratio is the
|
|
252
|
+
richness here, not an effect. */
|
|
253
|
+
.${PREFIX}-bubble .${PREFIX}-ayca{width:26px;height:26px;transition:transform var(--duration-base,300ms) var(--ease-out)}
|
|
254
|
+
.${PREFIX}-bubble:active{transform:translateY(0) scale(.96)}
|
|
255
|
+
/* The first-visit variant: a word, not a puzzle. Collapses to the pebble once
|
|
256
|
+
the reader has opened it. */
|
|
257
|
+
.${PREFIX}-bubble-pill{width:auto;height:48px;border-radius:var(--aiw-radius-pill);padding:0 18px 0 14px}
|
|
258
|
+
.${PREFIX}-bubble-pill .${PREFIX}-ayca{width:22px;height:22px}
|
|
259
|
+
.${PREFIX}-bubble-pill .${PREFIX}-bubble-label{display:block}
|
|
260
|
+
.${PREFIX}-bubble-label{display:none;font:inherit;font-size:var(--aiw-font-md);font-weight:600;white-space:nowrap}
|
|
261
|
+
/* Only TWO of the six states move, which is what makes movement mean
|
|
262
|
+
something again. */
|
|
263
|
+
.${PREFIX}-bubble-unread{animation:${PREFIX}-pulse var(--duration-slow,420ms) var(--ease-out) 1}
|
|
264
|
+
.${PREFIX}-bubble-working .${PREFIX}-ayca{animation:${PREFIX}-breathe 2.4s var(--ease-in-out) infinite}
|
|
265
|
+
.${PREFIX}-bubble-offline{opacity:.72}
|
|
266
|
+
/* A DOT, not the count badge in grey. It reuses the same element, and the
|
|
267
|
+
count's geometry (18px min-width plus 5px of padding, sized for two digits)
|
|
268
|
+
renders an empty string as a wide lozenge — which reads as a broken badge
|
|
269
|
+
rather than a status light. */
|
|
270
|
+
.${PREFIX}-bubble-offline .${PREFIX}-bubble-dot{min-width:0;width:10px;height:10px;padding:0;top:4px;background:var(--aiw-muted);box-shadow:0 0 0 2px #151D2F}
|
|
271
|
+
.${PREFIX}-bubble-offline.${PREFIX}-on-ink .${PREFIX}-bubble-dot{box-shadow:0 0 0 2px #FCF7E3}
|
|
272
|
+
.${PREFIX}-bubble-parked{width:40px;height:40px;opacity:.6}
|
|
273
|
+
.${PREFIX}-bubble-parked .${PREFIX}-ayca{width:20px;height:20px}
|
|
274
|
+
/* The unread count and the offline dot — one badge element, two looks. */
|
|
275
|
+
.${PREFIX}-bubble-dot{position:absolute;top:2px;${side === "left" ? "right" : "left"}:2px;min-width:18px;height:18px;padding:0 5px;border-radius:var(--aiw-radius-pill);background:#FA712D;color:#FCF7E3;font-size:var(--aiw-font-2xs);font-weight:700;line-height:18px;text-align:center;box-shadow:0 0 0 2px #151D2F}
|
|
276
|
+
@keyframes ${PREFIX}-breathe{0%,100%{opacity:1}50%{opacity:.55}}
|
|
277
|
+
/* GATED. There was no hover:hover query anywhere in this widget, so on a
|
|
278
|
+
touchscreen the lift-and-scale stuck after the tap. The file already carries
|
|
279
|
+
a correct hover:none block for the per-message actions — the launcher was
|
|
280
|
+
simply never given the same treatment. Lift only; the scale went with it,
|
|
281
|
+
because a control that grows under the cursor is a third thing saying "I am
|
|
282
|
+
interactive" after the shadow and the pointer. */
|
|
283
|
+
@media (hover: hover) and (pointer: fine){
|
|
284
|
+
.${PREFIX}-bubble:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(21,29,47,.3)}
|
|
285
|
+
.${PREFIX}-bubble:hover .${PREFIX}-ayca{transform:rotate(-12deg)}
|
|
286
|
+
}
|
|
287
|
+
.${PREFIX}-bubble-av{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
|
288
|
+
.${PREFIX}-av-img{width:100%;height:100%;object-fit:cover;border-radius:50%}
|
|
289
|
+
.${PREFIX}-bubble svg{width:26px;height:26px}
|
|
290
|
+
.${PREFIX}-panel{position:fixed;bottom:20px;${side}:20px;z-index:48;width:368px;max-width:calc(100vw - 32px);height:540px;max-height:calc(100vh - 40px);background:var(--aiw-surface);color:var(--aiw-text);border-radius:var(--aiw-radius-3xl);box-shadow:0 18px 52px rgba(0,0,0,.32);display:flex;flex-direction:column;overflow:hidden;font-family:system-ui,-apple-system,sans-serif;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out);}
|
|
291
|
+
/* NAVY, with the sweep as a 2px rule under it (#305). It was the gradient bar
|
|
292
|
+
itself, which is why the avatar needed an rgba(12,17,30,.55) scrim to survive
|
|
293
|
+
— a scrim over a gradient is a patch, not a decision. On navy the mark
|
|
294
|
+
inverts by the rule above and the scrim is unnecessary, so it is gone. */
|
|
295
|
+
.${PREFIX}-header{background:var(--aiw-header-bg);color:var(--aiw-header-fg);border-bottom:2px solid transparent;border-image:var(--aiw-gradient) 1;padding:12px 14px;display:flex;align-items:center;gap:10px}
|
|
296
|
+
/* Drag-to-reposition. touch-action:none is what makes this work on a
|
|
297
|
+
touchscreen — without it the browser claims the gesture as a scroll and the
|
|
298
|
+
panel never moves. user-select:none stops the title being selected mid-drag. */
|
|
299
|
+
/* Question card — the assistant asking the human to decide. Visually distinct
|
|
300
|
+
from a proposal card: this one BLOCKS progress until answered, so it should
|
|
301
|
+
read as something needing you, not as another message. */
|
|
302
|
+
.${PREFIX}-question{margin:8px 0;padding:12px;border-radius:var(--aiw-radius-xl);background:var(--aiw-surface-raised);border:1px solid var(--aiw-border);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
303
|
+
.${PREFIX}-question-critical{border-color:var(--aiw-danger-text);box-shadow:0 0 0 1px color-mix(in srgb,var(--aiw-danger-text) 25%,transparent)}
|
|
304
|
+
.${PREFIX}-question-title{font-size:var(--aiw-font-md);font-weight:600;margin-bottom:4px;color:var(--aiw-text)}
|
|
305
|
+
.${PREFIX}-question-ctx{font-size:var(--aiw-font-xs);color:var(--aiw-muted);margin-bottom:8px;line-height:1.45}
|
|
306
|
+
.${PREFIX}-question-opts{display:flex;flex-direction:column;gap:6px}
|
|
307
|
+
.${PREFIX}-question-opt{display:flex;flex-direction:column;gap:2px;text-align:left;padding:9px 11px;border-radius:var(--aiw-radius-md);border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text);cursor:pointer;font:inherit;transition:border-color .14s ease,background .14s ease}
|
|
308
|
+
.${PREFIX}-question-opt:hover{border-color:var(--aiw-accent);background:var(--aiw-surface-raised)}
|
|
309
|
+
.${PREFIX}-question-opt-on{border-color:var(--aiw-accent);background:var(--aiw-surface-raised)}
|
|
310
|
+
.${PREFIX}-question-opt-label{font-size:var(--aiw-font-sm);font-weight:600}
|
|
311
|
+
.${PREFIX}-question-opt-desc{font-size:var(--aiw-font-xs);color:var(--aiw-muted);line-height:1.4}
|
|
312
|
+
.${PREFIX}-question-free{display:flex;gap:6px;margin-top:2px}
|
|
313
|
+
.${PREFIX}-question-input{flex:1 1 auto;min-width:0;padding:9px 11px;border-radius:var(--aiw-radius-md);border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text);font:inherit;font-size:var(--aiw-font-sm)}
|
|
314
|
+
.${PREFIX}-question-send{padding:9px 14px;border-radius:var(--aiw-radius-md);border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);font:inherit;font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer;margin-top:6px}
|
|
315
|
+
/* A multi-select confirm with nothing picked has nothing to send — it says so
|
|
316
|
+
rather than looking clickable and doing nothing. */
|
|
317
|
+
.${PREFIX}-question-send:disabled{opacity:.5;cursor:not-allowed}
|
|
318
|
+
.${PREFIX}-question-err{margin-top:8px;font-size:var(--aiw-font-xs);line-height:1.45;color:var(--aiw-danger-text)}
|
|
319
|
+
/* Answered: collapsed to the decision, so the transcript reads as a
|
|
320
|
+
conversation rather than a dead form. */
|
|
321
|
+
.${PREFIX}-question-done{border-style:dashed;opacity:.75}
|
|
322
|
+
.${PREFIX}-question-answer{font-size:var(--aiw-font-sm);color:var(--aiw-text-2)}
|
|
323
|
+
.${PREFIX}-question-answer::before{content:"✓ ";color:var(--aiw-accent-ink)}
|
|
324
|
+
.${PREFIX}-draggable{cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}
|
|
325
|
+
.${PREFIX}-draggable:active{cursor:grabbing}
|
|
326
|
+
/* Controls inside the header keep normal behaviour — the drag handler ignores
|
|
327
|
+
them, and this keeps the cursor honest about that. */
|
|
328
|
+
.${PREFIX}-draggable button,.${PREFIX}-draggable a{cursor:pointer;touch-action:auto}
|
|
329
|
+
/* Advanced view is a fixed edge-to-edge layout — dragging is disabled there
|
|
330
|
+
(the pointerdown handler bails), so drop the grab affordance too. */
|
|
331
|
+
.${PREFIX}-advanced .${PREFIX}-draggable{cursor:default}
|
|
332
|
+
/* Cream disc on the navy header — the ink half of the inversion. The scrim it
|
|
333
|
+
used to carry is gone; see the header rule. */
|
|
334
|
+
.${PREFIX}-avatar{position:relative;width:38px;height:38px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;border-radius:50%;background:#FCF7E3}
|
|
335
|
+
.${PREFIX}-avatar .${PREFIX}-ayca{width:34px;height:34px}
|
|
336
|
+
.${PREFIX}-hname{display:flex;flex-direction:column;line-height:1.15;min-width:0;flex:1 1 auto}
|
|
337
|
+
.${PREFIX}-title{font-weight:700;font-size:var(--aiw-font-lg);letter-spacing:.04em}
|
|
338
|
+
.${PREFIX}-sub{font-size:var(--aiw-font-2xs);opacity:.85}
|
|
339
|
+
.${PREFIX}-metachip{align-self:flex-start;margin-top:3px;font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:1px 6px;border-radius:var(--aiw-radius-sm);background:rgba(255,255,255,.22);color:var(--aiw-accent-contrast);white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
|
|
340
|
+
.${PREFIX}-close{background:color-mix(in srgb,var(--aiw-header-fg) 15%,transparent);border:none;color:var(--aiw-header-fg);font-size:var(--aiw-font-2xl);line-height:1;width:26px;height:26px;border-radius:var(--aiw-radius-md);cursor:pointer;flex:0 0 auto}
|
|
341
|
+
.${PREFIX}-log{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:14px;display:flex;flex-direction:column;gap:10px;background:var(--aiw-bg);scrollbar-width:thin}
|
|
342
|
+
.${PREFIX}-log:focus-visible{outline:none}
|
|
343
|
+
.${PREFIX}-log::-webkit-scrollbar{width:8px}
|
|
344
|
+
.${PREFIX}-log::-webkit-scrollbar-thumb{background:rgba(0,0,0,.18);border-radius:var(--aiw-radius-md)}
|
|
345
|
+
@keyframes ${PREFIX}-caret{0%,55%{opacity:.85}55.01%,100%{opacity:0}}
|
|
346
|
+
.${PREFIX}-streaming::after{content:"";display:inline-block;width:2px;height:1.05em;margin-left:1px;border-radius:var(--aiw-radius-2xs);background:var(--aiw-accent);vertical-align:-2px;animation:${PREFIX}-caret 1.1s steps(1) infinite}
|
|
347
|
+
.${PREFIX}-msg{max-width:85%;padding:9px 12px;border-radius:var(--aiw-radius-2xl);font-size:var(--aiw-font-md);line-height:1.45;white-space:pre-wrap;word-break:break-word;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
348
|
+
.${PREFIX}-msg.${PREFIX}-user{align-self:flex-end;background:var(--aiw-user-bg,color-mix(in srgb,var(--aiw-accent) 76%,${USER_BUBBLE_INK}));color:var(--aiw-user-contrast,#fff);border-bottom-right-radius:4px}
|
|
349
|
+
.${PREFIX}-assistant{align-self:flex-start;background:var(--aiw-surface-raised);color:var(--aiw-text);border:1px solid var(--aiw-border);border-bottom-left-radius:4px}
|
|
350
|
+
.${PREFIX}-assistant p{margin:0 0 8px}.${PREFIX}-assistant>:last-child{margin-bottom:0}
|
|
351
|
+
.${PREFIX}-assistant h1,.${PREFIX}-assistant h2,.${PREFIX}-assistant h3,.${PREFIX}-assistant h4{margin:10px 0 6px;font-weight:700;line-height:1.25}
|
|
352
|
+
.${PREFIX}-assistant h1{font-size:var(--aiw-font-xl)}.${PREFIX}-assistant h2{font-size:var(--aiw-font-xl)}.${PREFIX}-assistant h3{font-size:var(--aiw-font-md)}.${PREFIX}-assistant h4{font-size:var(--aiw-font-sm)}
|
|
353
|
+
.${PREFIX}-assistant ul,.${PREFIX}-assistant ol{margin:6px 0;padding-left:20px}
|
|
354
|
+
.${PREFIX}-assistant li{line-height:1.45;margin:2px 0}
|
|
355
|
+
.${PREFIX}-assistant a{color:var(--aiw-accent-ink);text-decoration:underline;text-underline-offset:2px}
|
|
356
|
+
.${PREFIX}-assistant code{background:rgba(0,0,0,.06);border-radius:var(--aiw-radius-sm);padding:1px 5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:var(--aiw-font-xs)}
|
|
357
|
+
.${PREFIX}-assistant pre.md-pre{background:#0d1117;color:#e6edf3;border-radius:var(--aiw-radius-lg);padding:10px 12px;overflow:auto;margin:8px 0}
|
|
358
|
+
.${PREFIX}-assistant pre.md-pre code{background:none;padding:0;color:inherit;font-size:var(--aiw-font-xs);white-space:pre}
|
|
359
|
+
.${PREFIX}-assistant blockquote{margin:8px 0;padding:2px 12px;border-left:3px solid color-mix(in srgb,var(--aiw-accent) 40%,transparent);color:var(--aiw-text-2)}
|
|
360
|
+
.${PREFIX}-assistant hr{border:none;border-top:1px solid var(--aiw-border);margin:10px 0}
|
|
361
|
+
.${PREFIX}-assistant table.md-table{width:100%;border-collapse:collapse;font-size:var(--aiw-font-xs);margin:8px 0}
|
|
362
|
+
.${PREFIX}-assistant table.md-table th{text-align:left;font-weight:700;color:var(--aiw-text-2);border-bottom:1px solid var(--aiw-border);padding:5px 8px}
|
|
363
|
+
.${PREFIX}-assistant table.md-table td{border-bottom:1px solid var(--aiw-border-soft);padding:5px 8px}
|
|
364
|
+
.${PREFIX}-assistant strong{font-weight:700}.${PREFIX}-assistant del{opacity:.7}
|
|
365
|
+
.${PREFIX}-widget-host{padding:8px}
|
|
366
|
+
.${PREFIX}-typing{align-self:flex-start;display:flex;gap:4px;padding:10px 12px}
|
|
367
|
+
.${PREFIX}-typing span{width:7px;height:7px;border-radius:50%;background:var(--aiw-accent);animation:${PREFIX}-blink 1.2s infinite}
|
|
368
|
+
.${PREFIX}-typing span:nth-child(2){animation-delay:.2s}
|
|
369
|
+
.${PREFIX}-typing span:nth-child(3){animation-delay:.4s}
|
|
370
|
+
.${PREFIX}-error{align-self:stretch;border:1px solid var(--aiw-danger-border);background:var(--aiw-danger-bg);border-radius:var(--aiw-radius-2xl);padding:11px 12px;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
371
|
+
.${PREFIX}-error-text{font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-danger-text)}
|
|
372
|
+
.${PREFIX}-error-detail{font-size:var(--aiw-font-2xs);color:var(--aiw-danger-text-2);margin-top:3px;word-break:break-word}
|
|
373
|
+
.${PREFIX}-error-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
|
|
374
|
+
.${PREFIX}-error-btn{border:1px solid var(--aiw-danger-border);background:var(--aiw-surface);color:var(--aiw-danger-text);border-radius:var(--aiw-radius-md);padding:6px 12px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}
|
|
375
|
+
.${PREFIX}-error-retry{background:var(--aiw-accent);border-color:var(--aiw-accent);color:var(--aiw-accent-contrast)}
|
|
376
|
+
.${PREFIX}-error-btn:disabled{opacity:.6;cursor:default}
|
|
377
|
+
/* Quality prompt (#299) — the widget asking, rather than waiting to be told.
|
|
378
|
+
Reads as a quiet aside, not an alert: it must never look like the error card,
|
|
379
|
+
because on a slow-but-good answer nothing has gone wrong. */
|
|
380
|
+
.${PREFIX}-quality{align-self:stretch;border:1px solid var(--aiw-border);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-2xl);padding:11px 12px;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
381
|
+
.${PREFIX}-quality-title{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-text);line-height:1.35}
|
|
382
|
+
.${PREFIX}-quality-row{display:flex;align-items:center;gap:8px;margin-top:9px;flex-wrap:wrap}
|
|
383
|
+
.${PREFIX}-quality-reason{flex:1 1 160px;min-width:0;border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text);border-radius:var(--aiw-radius-md);padding:7px 10px;font:inherit;font-size:var(--aiw-font-xs)}
|
|
384
|
+
.${PREFIX}-quality-reason::placeholder{color:var(--aiw-text-3)}
|
|
385
|
+
.${PREFIX}-quality-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
|
|
386
|
+
.${PREFIX}-quality-send{border:1px solid var(--aiw-accent);background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-md);padding:6px 12px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}
|
|
387
|
+
.${PREFIX}-quality-send:disabled{opacity:.6;cursor:default}
|
|
388
|
+
.${PREFIX}-quality-dismiss{border:1px solid var(--aiw-border);background:var(--aiw-surface);color:var(--aiw-text-2);border-radius:var(--aiw-radius-md);padding:6px 12px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}
|
|
389
|
+
/* Answered state: the card collapses to a single line of thanks. */
|
|
390
|
+
.${PREFIX}-quality-done{font-size:var(--aiw-font-xs);color:var(--aiw-text-2);font-weight:500}
|
|
391
|
+
.${PREFIX}-suggestions{display:flex;flex-wrap:wrap;gap:6px;padding:8px 10px 0;background:var(--aiw-surface)}
|
|
392
|
+
.${PREFIX}-suggestion{border:1px solid color-mix(in srgb,var(--aiw-accent) 20%,transparent);background:color-mix(in srgb,var(--aiw-accent) 5%,transparent);color:var(--aiw-accent-ink);border-radius:var(--aiw-radius-pill);padding:6px 11px;font-size:var(--aiw-font-xs);font-weight:500;line-height:1.2;cursor:pointer;transition:background .15s ease,border-color .15s ease;text-align:left}
|
|
393
|
+
.${PREFIX}-suggestion:hover{background:color-mix(in srgb,var(--aiw-accent) 10%,transparent);border-color:color-mix(in srgb,var(--aiw-accent) 40%,transparent)}
|
|
394
|
+
.${PREFIX}-form{display:flex;gap:8px;padding:10px;border-top:1px solid var(--aiw-border);background:var(--aiw-surface)}
|
|
395
|
+
.${PREFIX}-input{flex:1;background:var(--aiw-surface);color:var(--aiw-text);border:1px solid var(--aiw-border-strong);border-radius:var(--aiw-radius-lg);padding:10px 12px;font-size:var(--aiw-font-md);outline:none}
|
|
396
|
+
.${PREFIX}-meter{padding:7px 12px 0;background:var(--aiw-surface)}
|
|
397
|
+
.${PREFIX}-meter-bar{height:4px;border-radius:var(--aiw-radius-pill);background:var(--aiw-surface-2);overflow:hidden}
|
|
398
|
+
.${PREFIX}-meter-bar>span{display:block;height:100%;width:100%;transform-origin:left;border-radius:var(--aiw-radius-pill);background:linear-gradient(90deg,var(--aiw-accent),#FBAA34);transition:transform var(--duration-base) var(--ease-out)}
|
|
399
|
+
.${PREFIX}-meter-row{display:flex;justify-content:space-between;gap:8px;margin-top:3px;font-size:var(--aiw-font-3xs);color:var(--aiw-muted)}
|
|
400
|
+
.${PREFIX}-status{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 12px;font-size:var(--aiw-font-2xs);background:var(--aiw-surface);border-top:1px solid var(--aiw-border-soft)}
|
|
401
|
+
.${PREFIX}-status-role{display:inline-flex;align-items:center;padding:2px 8px;border-radius:var(--aiw-radius-pill);font-weight:600;color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 10%,transparent)}
|
|
402
|
+
.${PREFIX}-status-credits{font-weight:600;color:var(--aiw-text-2);font-variant-numeric:tabular-nums}
|
|
403
|
+
.${PREFIX}-status-credits-val{display:inline-block;transition:color .2s ease}
|
|
404
|
+
.${PREFIX}-credits-live{color:var(--aiw-accent-ink)}
|
|
405
|
+
.${PREFIX}-cta{display:flex;justify-content:center;padding:6px 0 2px}
|
|
406
|
+
.${PREFIX}-cta-btn{display:inline-flex;align-items:center;justify-content:center;border-radius:var(--aiw-radius-pill);padding:9px 18px;font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-accent-contrast);text-decoration:none;background:linear-gradient(90deg,var(--aiw-accent),#FBAA34);box-shadow:0 4px 14px color-mix(in srgb,var(--aiw-accent) 25%,transparent)}
|
|
407
|
+
.${PREFIX}-lead{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);padding:11px 12px;background:var(--aiw-surface-raised);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
408
|
+
.${PREFIX}-form-title{font-size:var(--aiw-font-sm);font-weight:600;margin-bottom:8px}
|
|
409
|
+
.${PREFIX}-lead-form{display:flex;flex-direction:column;gap:8px}
|
|
410
|
+
/* ── Form controls ───────────────────────────────────────────────────────
|
|
411
|
+
Everything the chat can draw as an input goes through ONE token layer
|
|
412
|
+
(--aiw-field-*) instead of its own hexes and radii, and that layer is
|
|
413
|
+
remapped below to the platform's semantic control variables when the host
|
|
414
|
+
page has them. The geometry (40px tall, 12px inline padding, 14px text, a
|
|
415
|
+
recessed inner shadow, a 2px focus ring) MIRRORS the host design system's
|
|
416
|
+
input.tsx — the widget is vanilla DOM in a detached root and cannot import
|
|
417
|
+
the React primitive, so the numbers are copied deliberately; that file is the
|
|
418
|
+
source of truth if they ever move. */
|
|
419
|
+
.${PREFIX}-bubble,.${PREFIX}-panel{--aiw-field-bg:var(--aiw-surface);--aiw-field-fg:var(--aiw-text);--aiw-field-border:var(--aiw-border-strong);--aiw-field-placeholder:var(--aiw-muted);--aiw-field-ring:var(--aiw-accent);--aiw-field-ring-fg:var(--aiw-accent-contrast);--aiw-field-radius:10px}
|
|
420
|
+
/* Host-token mode. Added at mount only when the page really defines our
|
|
421
|
+
variables, so a customer site that happens to own an --input cannot repaint
|
|
422
|
+
the chat's controls. Values stay as var() references, never snapshots, so the
|
|
423
|
+
app's dark-mode class keeps driving them after mount. */
|
|
424
|
+
.${PREFIX}-bubble.${PREFIX}-host-tokens,.${PREFIX}-panel.${PREFIX}-host-tokens{--aiw-field-bg:hsl(var(--card));--aiw-field-fg:hsl(var(--foreground));--aiw-field-border:hsl(var(--input));--aiw-field-placeholder:hsl(var(--muted-foreground));--aiw-field-ring:hsl(var(--ring));--aiw-field-ring-fg:hsl(var(--primary-foreground));--aiw-field-radius:calc(var(--radius) - 2px)}
|
|
425
|
+
.${PREFIX}-field{width:100%;box-sizing:border-box;min-height:40px;padding:8px 12px;font-family:inherit;font-size:var(--aiw-font-md);line-height:1.45;color:var(--aiw-field-fg);background:var(--aiw-field-bg);border:1px solid var(--aiw-field-border);border-radius:var(--aiw-field-radius);outline:none;box-shadow:inset 0 1px 2px 0 color-mix(in srgb,var(--aiw-field-fg) 5%,transparent);transition:border-color .15s ease,box-shadow .15s ease}
|
|
426
|
+
.${PREFIX}-field::placeholder{color:var(--aiw-field-placeholder);opacity:1}
|
|
427
|
+
.${PREFIX}-field:hover:not(:disabled){border-color:color-mix(in srgb,var(--aiw-field-border) 70%,var(--aiw-field-fg))}
|
|
428
|
+
.${PREFIX}-field:focus{border-color:var(--aiw-field-ring);box-shadow:0 0 0 2px var(--aiw-field-ring)}
|
|
429
|
+
.${PREFIX}-field:disabled{opacity:.5;cursor:not-allowed}
|
|
430
|
+
textarea.${PREFIX}-field{min-height:76px;resize:vertical}
|
|
431
|
+
/* The native select arrow is drawn by the OS and ignores our palette — in dark
|
|
432
|
+
mode it renders a black-on-dark wedge. Draw our own instead. */
|
|
433
|
+
select.${PREFIX}-field{appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:32px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239aa0a6' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;background-size:15px}
|
|
434
|
+
/* Options are drawn by the OS popup, which does NOT inherit the panel — without
|
|
435
|
+
this a dark-mode select opens as black text on a black list. */
|
|
436
|
+
.${PREFIX}-field option{background:var(--aiw-field-bg);color:var(--aiw-field-fg)}
|
|
437
|
+
.${PREFIX}-field-group{display:flex;flex-direction:column;gap:6px}
|
|
438
|
+
/* "This is the field that's holding you up." An outline rather than a border so
|
|
439
|
+
it lands the same on a bare input, a checkbox row and a radio GROUP — the
|
|
440
|
+
three shapes buildField can hand back. */
|
|
441
|
+
.${PREFIX}-field-invalid{outline:1px solid var(--aiw-danger-text);outline-offset:2px;border-radius:var(--aiw-field-radius)}
|
|
442
|
+
.${PREFIX}-form-err,.${PREFIX}-proposal-err{font-size:var(--aiw-font-xs);line-height:1.45;color:var(--aiw-danger-text)}
|
|
443
|
+
.${PREFIX}-field-check{display:flex;align-items:center;gap:8px;font-size:var(--aiw-font-sm);line-height:1.4;color:var(--aiw-field-fg);cursor:pointer}
|
|
444
|
+
.${PREFIX}-field-check-label{min-width:0}
|
|
445
|
+
.${PREFIX}-check{appearance:none;-webkit-appearance:none;position:relative;flex:0 0 auto;width:16px;height:16px;margin:0;box-sizing:border-box;background:var(--aiw-field-bg);border:1px solid var(--aiw-field-border);border-radius:var(--aiw-radius-xs);cursor:pointer;outline:none;transition:background .15s ease,border-color .15s ease,box-shadow .15s ease}
|
|
446
|
+
.${PREFIX}-check[type=radio]{border-radius:50%}
|
|
447
|
+
.${PREFIX}-check:hover:not(:disabled){border-color:color-mix(in srgb,var(--aiw-field-border) 70%,var(--aiw-field-fg))}
|
|
448
|
+
.${PREFIX}-check:focus-visible{border-color:var(--aiw-field-ring);box-shadow:0 0 0 2px var(--aiw-field-ring)}
|
|
449
|
+
.${PREFIX}-check:checked{background:var(--aiw-field-ring);border-color:var(--aiw-field-ring)}
|
|
450
|
+
/* Tick + dot are drawn from borders rather than a glyph so they scale with the
|
|
451
|
+
box and take the accent's contrast colour in both schemes. */
|
|
452
|
+
.${PREFIX}-check[type=checkbox]:checked::after{content:"";position:absolute;left:4.5px;top:1.5px;width:4px;height:8px;border:solid var(--aiw-field-ring-fg);border-width:0 2px 2px 0;transform:rotate(45deg)}
|
|
453
|
+
.${PREFIX}-check[type=radio]:checked::after{content:"";position:absolute;left:3.5px;top:3.5px;width:7px;height:7px;border-radius:50%;background:var(--aiw-field-ring-fg)}
|
|
454
|
+
.${PREFIX}-check:disabled{opacity:.5;cursor:not-allowed}
|
|
455
|
+
.${PREFIX}-lead-btn{border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-lg);padding:10px 16px;font-size:var(--aiw-font-md);font-weight:600;cursor:pointer}
|
|
456
|
+
.${PREFIX}-lead-btn:disabled{opacity:.6;cursor:default}
|
|
457
|
+
.${PREFIX}-lead-ok{font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-accent-ink)}
|
|
458
|
+
.${PREFIX}-input:focus{border-color:var(--aiw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--aiw-accent) 13%,transparent)}
|
|
459
|
+
.${PREFIX}-send{border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-lg);padding:0 16px;font-size:var(--aiw-font-md);font-weight:600;cursor:pointer}
|
|
460
|
+
.${PREFIX}-send:disabled{opacity:.5;cursor:default}
|
|
461
|
+
.${PREFIX}-attach{flex:0 0 auto;display:flex;align-items:center;justify-content:center;border:1px solid var(--aiw-border-strong);background:var(--aiw-surface);border-radius:var(--aiw-radius-lg);width:38px;line-height:1;cursor:pointer;color:var(--aiw-text-2);transition:border-color .12s,color .12s,background .12s}
|
|
462
|
+
.${PREFIX}-attach:hover{border-color:var(--aiw-accent);color:var(--aiw-accent-ink)}
|
|
463
|
+
.${PREFIX}-attach:disabled{opacity:.5;cursor:default}
|
|
464
|
+
.${PREFIX}-attbar{display:flex;flex-wrap:wrap;gap:6px;padding:8px 10px 0;background:var(--aiw-surface)}
|
|
465
|
+
.${PREFIX}-artifacts{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding:6px 10px 0;background:var(--aiw-surface)}
|
|
466
|
+
.${PREFIX}-artifacts-title{font-size:var(--aiw-font-2xs);font-weight:600;color:var(--aiw-muted)}
|
|
467
|
+
.${PREFIX}-artifact{display:inline-flex;align-items:center;gap:6px;max-width:200px;border:1px solid var(--aiw-border);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-md);padding:3px 8px;font-size:var(--aiw-font-2xs);color:var(--aiw-text-2);white-space:nowrap}
|
|
468
|
+
.${PREFIX}-artifact-name{overflow:hidden;text-overflow:ellipsis;max-width:130px}
|
|
469
|
+
.${PREFIX}-artifact-save{border:none;background:transparent;color:#0b6cff;font-size:var(--aiw-font-2xs);font-weight:600;cursor:pointer;padding:0}
|
|
470
|
+
.${PREFIX}-artifact-save:disabled{color:var(--aiw-muted);cursor:default}
|
|
471
|
+
.${PREFIX}-att{display:inline-flex;align-items:center;gap:5px;max-width:180px;border:1px solid var(--aiw-border);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-md);padding:3px 8px;font-size:var(--aiw-font-xs);color:var(--aiw-text-2);white-space:nowrap}
|
|
472
|
+
.${PREFIX}-att>span{overflow:hidden;text-overflow:ellipsis}
|
|
473
|
+
.${PREFIX}-atts{display:flex;flex-wrap:wrap;gap:6px;max-width:92%}
|
|
474
|
+
.${PREFIX}-atts.${PREFIX}-user{align-self:flex-end;justify-content:flex-end}
|
|
475
|
+
.${PREFIX}-att-x{border:none;background:transparent;color:var(--aiw-muted);font-size:var(--aiw-font-lg);line-height:1;cursor:pointer;padding:0 0 0 2px}
|
|
476
|
+
.${PREFIX}-att-x:hover{color:#e11}
|
|
477
|
+
.${PREFIX}-att-err{border-color:var(--aiw-danger-border);background:var(--aiw-danger-bg);color:var(--aiw-danger-text)}
|
|
478
|
+
.${PREFIX}-msgactions{display:inline-flex;align-items:center;gap:6px;margin-top:3px;color:var(--aiw-muted);opacity:.55;transition:opacity .15s ease}
|
|
479
|
+
.${PREFIX}-msgactions:hover,.${PREFIX}-msgactions:focus-within{opacity:1}
|
|
480
|
+
/* User edit sits at the message's bottom-RIGHT, hugging the bubble edge, and is
|
|
481
|
+
HIDDEN until the user hovers their message (or the row/button itself) — an
|
|
482
|
+
always-visible bordered pencil floating under every user turn read as a stray
|
|
483
|
+
box. Its row is the immediate next sibling of the user bubble, so a hover-plus
|
|
484
|
+
sibling selector reveals it; the row's own :hover/:focus-within keeps it up
|
|
485
|
+
while you reach the
|
|
486
|
+
button across the small gap. Higher specificity than the base .msgactions:hover
|
|
487
|
+
rule so it wins the cascade. */
|
|
488
|
+
.${PREFIX}-msgactions.${PREFIX}-user{align-self:flex-end;justify-content:flex-end;padding-right:2px;opacity:0}
|
|
489
|
+
.${PREFIX}-msg.${PREFIX}-user:hover + .${PREFIX}-msgactions.${PREFIX}-user,.${PREFIX}-msgactions.${PREFIX}-user:hover,.${PREFIX}-msgactions.${PREFIX}-user:focus-within{opacity:1}
|
|
490
|
+
/* HOVER IS NOT A UNIVERSAL INPUT. On a touch device there is no hover state
|
|
491
|
+
to enter, so opacity 0 revealed only on hover meant Edit and Copy did not
|
|
492
|
+
EXIST on a phone — the widget's mobile layout is a full-screen sheet, i.e.
|
|
493
|
+
the case where they matter most. hover:none is the honest test (it asks
|
|
494
|
+
what the primary pointer can do, not how wide the screen is), and the
|
|
495
|
+
answer there is: always visible, just quiet.
|
|
496
|
+
NOTE: this whole sheet is a JS template literal, so no backticks. */
|
|
497
|
+
@media (hover:none){
|
|
498
|
+
.${PREFIX}-msgactions.${PREFIX}-user{opacity:.75}
|
|
499
|
+
}
|
|
500
|
+
.${PREFIX}-msgactions.${PREFIX}-assistant{align-self:flex-start}
|
|
501
|
+
.${PREFIX}-msgact{display:inline-flex;align-items:center;gap:4px;border:none;background:transparent;color:inherit;border-radius:var(--aiw-radius-sm);padding:2px 5px;font-size:var(--aiw-font-2xs);font-weight:500;cursor:pointer;line-height:1}
|
|
502
|
+
.${PREFIX}-msgact:hover{color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 7%,transparent)}
|
|
503
|
+
/* Icon-only variant (edit) — a small, quiet chip (subtle surface + border) that
|
|
504
|
+
is revealed on message hover, then tints ACCENT (not a hard white-on-accent
|
|
505
|
+
fill) on its own hover so it reads as an affordance, not a loud button. */
|
|
506
|
+
.${PREFIX}-msgact-icon{padding:5px;border-radius:var(--aiw-radius-md);color:var(--aiw-muted);background:color-mix(in srgb,var(--aiw-surface) 92%,transparent);border:1px solid var(--aiw-border);transition:color .15s ease,background .15s ease,border-color .15s ease}
|
|
507
|
+
.${PREFIX}-msgact-icon:hover{color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 12%,transparent);border-color:color-mix(in srgb,var(--aiw-accent) 45%,transparent)}
|
|
508
|
+
.${PREFIX}-msgact-icon svg{width:15px;height:15px}
|
|
509
|
+
/* Per-message vote (thumbs) — light icon buttons; the chosen vote stays lit. */
|
|
510
|
+
/* ── Accessibility floor (#308) ──────────────────────────────────────────────
|
|
511
|
+
ONE focus rule for all 40 buttons, on a base class every one of them carries.
|
|
512
|
+
Nothing in this sheet ever REMOVED a button's ring — there is no outline:none
|
|
513
|
+
on one — so this was never a keyboard trap. It is that the ring was never
|
|
514
|
+
designed and never measured against a surface the host is explicitly allowed
|
|
515
|
+
to repaint (the theme option), and "whatever the UA draws" is not an answer we can
|
|
516
|
+
ship to a third party once the widget is public (#306).
|
|
517
|
+
|
|
518
|
+
Two-toned so it holds on ANY ground: the accent fills the offset gap and a
|
|
519
|
+
fixed dark ring sits outside it, so the pair cannot vanish into either a
|
|
520
|
+
light or a dark panel. Same construction the design house prescribes for a
|
|
521
|
+
brand ring that fails on one theme. */
|
|
522
|
+
.${PREFIX}-btn:focus-visible{outline:2px solid var(--aiw-accent);outline-offset:2px;box-shadow:0 0 0 4px #151D2F}
|
|
523
|
+
/* A hit area of at least 24x24 (WCAG 2.2 AA) WITHOUT changing the drawn size —
|
|
524
|
+
a transparent ::before costs nothing visually, so a 20px vote button and a
|
|
525
|
+
6px carousel dot stay exactly as designed.
|
|
526
|
+
|
|
527
|
+
The GAPS below grow with them, and that is not cosmetic. WCAG's spacing
|
|
528
|
+
exception only holds when a 24px circle centred on each target does not
|
|
529
|
+
intersect its neighbour's, so the gap has to put the CENTRES 24px apart.
|
|
530
|
+
Enlarging a hit area without doing that just makes two controls fight over
|
|
531
|
+
the same pixels, which is worse than the small target was. */
|
|
532
|
+
.${PREFIX}-tap::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px}
|
|
533
|
+
/* 20px drawn + 4px gap = centres 24px apart. */
|
|
534
|
+
.${PREFIX}-votes{display:inline-flex;align-items:center;gap:4px}
|
|
535
|
+
/* 20x20 drawn (14px icon + 3px padding); the .-tap ::before takes the TARGET
|
|
536
|
+
to 24x24. The chat-quality control (#299) being the smallest thing in the
|
|
537
|
+
widget was the sharpest finding in the audit. */
|
|
538
|
+
.${PREFIX}-vote{position:relative;padding:3px;border-radius:var(--aiw-radius-sm);color:var(--aiw-muted);background:transparent}
|
|
539
|
+
.${PREFIX}-vote::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px}
|
|
540
|
+
.${PREFIX}-vote:hover{color:var(--aiw-accent-ink);background:color-mix(in srgb,var(--aiw-accent) 10%,transparent)}
|
|
541
|
+
.${PREFIX}-vote-on{color:var(--aiw-accent-ink)}
|
|
542
|
+
.${PREFIX}-vote svg{width:14px;height:14px;display:block}
|
|
543
|
+
.${PREFIX}-branchnav{display:inline-flex;align-items:center;gap:2px;color:inherit;font-variant-numeric:tabular-nums}
|
|
544
|
+
.${PREFIX}-branch-btn{display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:inherit;border-radius:var(--aiw-radius-sm);padding:2px;cursor:pointer;line-height:1}
|
|
545
|
+
.${PREFIX}-branch-btn:hover:not(:disabled){color:var(--aiw-accent-ink)}
|
|
546
|
+
.${PREFIX}-branch-btn:disabled{opacity:.35;cursor:default}
|
|
547
|
+
.${PREFIX}-branch-count{font-size:var(--aiw-font-2xs);padding:0 2px}
|
|
548
|
+
.${PREFIX}-edit{display:flex;flex-direction:column;gap:6px;max-width:94%;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
549
|
+
.${PREFIX}-edit.${PREFIX}-user{align-self:stretch}
|
|
550
|
+
/* Edit mode keeps the USER BUBBLE look (same fill + radius), so clicking edit
|
|
551
|
+
doesn't swap the message for a different-looking box — just a subtle focus
|
|
552
|
+
ring, matching the inline-edit request. */
|
|
553
|
+
.${PREFIX}-edit-input{width:100%;background:color-mix(in srgb,var(--aiw-accent) 76%,#04191b);color:#fff;caret-color:#fff;box-sizing:border-box;border:1.5px solid transparent;border-radius:var(--aiw-radius-2xl);padding:9px 12px;font-size:var(--aiw-font-md);line-height:1.45;font-family:inherit;resize:none;outline:none;transition:border-color .15s ease,box-shadow .15s ease}
|
|
554
|
+
.${PREFIX}-edit-input::placeholder{color:rgba(255,255,255,.6)}
|
|
555
|
+
.${PREFIX}-edit-input:focus{border-color:rgba(255,255,255,.55);box-shadow:0 0 0 3px color-mix(in srgb,var(--aiw-accent) 28%,transparent)}
|
|
556
|
+
.${PREFIX}-edit-actions{display:flex;justify-content:flex-end;gap:6px}
|
|
557
|
+
.${PREFIX}-edit-cancel{border:none;background:transparent;color:var(--aiw-muted);border-radius:var(--aiw-radius-pill);padding:5px 11px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}
|
|
558
|
+
.${PREFIX}-edit-cancel:hover{background:var(--aiw-surface-2)}
|
|
559
|
+
.${PREFIX}-edit-save{border:none;background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-radius:var(--aiw-radius-pill);padding:5px 13px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer}
|
|
560
|
+
.${PREFIX}-edit-save:hover{opacity:.92}
|
|
561
|
+
.${PREFIX}-hactions{display:flex;align-items:center;gap:4px;flex:0 0 auto}
|
|
562
|
+
.${PREFIX}-icon{background:color-mix(in srgb,var(--aiw-header-fg) 15%,transparent);border:none;color:var(--aiw-header-fg);width:26px;height:26px;border-radius:var(--aiw-radius-md);cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}
|
|
563
|
+
.${PREFIX}-icon:hover{background:color-mix(in srgb,var(--aiw-header-fg) 28%,transparent)}
|
|
564
|
+
.${PREFIX}-icon-on{background:var(--aiw-header-fg);color:var(--aiw-header-bg)}
|
|
565
|
+
.${PREFIX}-icon-on:hover{background:var(--aiw-header-fg)}
|
|
566
|
+
.${PREFIX}-morewrap{position:relative;display:inline-flex}
|
|
567
|
+
.${PREFIX}-menu{position:absolute;top:calc(100% + 8px);right:0;z-index:6;display:flex;flex-direction:column;min-width:212px;padding:6px;background:var(--aiw-surface-raised);color:var(--aiw-text);border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-xl);box-shadow:0 12px 34px rgba(15,23,42,.18);
|
|
568
|
+
/* A TRANSITION, not a keyframe, and anchored at the trigger (#309). It was
|
|
569
|
+
an entrance KEYFRAME with no transform-origin, so it grew from its own centre
|
|
570
|
+
instead of from the button that opened it — and because keyframes RESTART
|
|
571
|
+
while transitions RETARGET, a fast open-close-open stuttered. It stays in the
|
|
572
|
+
DOM (display was toggled), which is exactly the rapidly-retriggered case
|
|
573
|
+
house law names.
|
|
574
|
+
visibility rather than display because display is not animatable; it
|
|
575
|
+
flips discretely at the end of the transition, which is what keeps a closed
|
|
576
|
+
menu out of the tab order. */
|
|
577
|
+
transform-origin:top right;transform:translateY(-4px) scale(.98);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--duration-fast) var(--ease-out),transform var(--duration-fast) var(--ease-out),visibility 0s linear var(--duration-fast)}
|
|
578
|
+
.${PREFIX}-menu-open{transform:none;opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s}
|
|
579
|
+
.${PREFIX}-menu::before{content:"";position:absolute;top:-5px;right:14px;width:10px;height:10px;background:var(--aiw-surface-raised);border-left:1px solid var(--aiw-border);border-top:1px solid var(--aiw-border);transform:rotate(45deg)}
|
|
580
|
+
.${PREFIX}-menu-item{display:flex;align-items:center;gap:10px;width:100%;text-align:left;border:none;background:transparent;color:var(--aiw-text-2);border-radius:var(--aiw-radius-md);padding:9px 10px;font-size:var(--aiw-font-sm);font-weight:500;cursor:pointer;line-height:1.2}
|
|
581
|
+
.${PREFIX}-menu-item:hover{background:color-mix(in srgb,var(--aiw-accent) 7%,transparent);color:var(--aiw-accent-ink)}
|
|
582
|
+
.${PREFIX}-menu-item:disabled{opacity:.5;cursor:default}
|
|
583
|
+
.${PREFIX}-menu-ico{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:18px;height:18px;color:var(--aiw-text-2)}
|
|
584
|
+
.${PREFIX}-menu-item:hover .${PREFIX}-menu-ico,.${PREFIX}-menu-item-on .${PREFIX}-menu-ico{color:var(--aiw-accent-ink)}
|
|
585
|
+
.${PREFIX}-menu-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
586
|
+
.${PREFIX}-menu-state{flex:0 0 auto;font-size:var(--aiw-font-3xs);font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--aiw-muted);background:var(--aiw-surface-2);border-radius:var(--aiw-radius-pill);padding:2px 7px}
|
|
587
|
+
.${PREFIX}-menu-item-on .${PREFIX}-menu-state{color:var(--aiw-accent-contrast);background:var(--aiw-accent)}
|
|
588
|
+
.${PREFIX}-autonav{align-self:flex-start;display:inline-flex;align-items:center;gap:7px;border:1px solid color-mix(in srgb,var(--aiw-accent) 20%,transparent);background:color-mix(in srgb,var(--aiw-accent) 6%,transparent);color:var(--aiw-accent-ink);border-radius:var(--aiw-radius-lg);padding:8px 12px;font-size:var(--aiw-font-sm);font-weight:600;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
589
|
+
.${PREFIX}-expanded{width:min(760px,calc(100vw - 32px));height:calc(100vh - 40px)}
|
|
590
|
+
.${PREFIX}-expanded .${PREFIX}-msg{max-width:min(75%,62ch)}
|
|
591
|
+
/* Advanced view — chat column + drivable app pane. The chat column always wraps
|
|
592
|
+
the chat (fills the panel in normal mode); the pane only shows in advanced. */
|
|
593
|
+
.${PREFIX}-chatcol{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;min-width:0;height:100%}
|
|
594
|
+
.${PREFIX}-pane{display:none;flex-direction:column;min-width:0;min-height:0;background:var(--aiw-bg)}
|
|
595
|
+
.${PREFIX}-advbtn-on{background:rgba(255,255,255,.34)}
|
|
596
|
+
/* Advanced view IS the wide view (owns the screen), so the plain "wide" toggle
|
|
597
|
+
is redundant here — hide it and leave advanced/exit + close. */
|
|
598
|
+
.${PREFIX}-advanced .${PREFIX}-expand{display:none}
|
|
599
|
+
/* Fullscreen toggle only makes sense in advanced view — hidden otherwise. */
|
|
600
|
+
.${PREFIX}-fullbtn{display:none}
|
|
601
|
+
.${PREFIX}-advanced .${PREFIX}-fullbtn{display:flex}
|
|
602
|
+
.${PREFIX}-advanced{width:calc(100vw - 40px);max-width:1240px;height:calc(100vh - 40px);flex-direction:row;align-items:stretch}
|
|
603
|
+
/* Fullscreen: drop the inset margins, width cap and radius so advanced view
|
|
604
|
+
fills the whole viewport edge-to-edge. Two classes → wins over .advanced. */
|
|
605
|
+
.${PREFIX}-advanced.${PREFIX}-advanced-full{inset:0;width:100vw;height:100vh;height:100dvh;max-width:none;max-height:none;border-radius:0}
|
|
606
|
+
.${PREFIX}-advanced .${PREFIX}-chatcol{flex:0 0 var(--aiw-chatw,400px);min-width:320px;border-right:1px solid var(--aiw-border)}
|
|
607
|
+
.${PREFIX}-advanced .${PREFIX}-pane{display:flex;flex:1 1 auto}
|
|
608
|
+
.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-chatcol{flex:1 1 auto;min-width:0;max-width:none;border-right:0}
|
|
609
|
+
/* Draggable divider between chat + app pane. Hidden unless advanced & expanded.
|
|
610
|
+
A thin bar with a wider invisible hit-area; brightens on hover/drag. */
|
|
611
|
+
.${PREFIX}-resizer{display:none}
|
|
612
|
+
.${PREFIX}-advanced .${PREFIX}-resizer{display:block;flex:0 0 6px;align-self:stretch;cursor:col-resize;background:var(--aiw-border);touch-action:none;position:relative;transition:background .15s}
|
|
613
|
+
.${PREFIX}-advanced .${PREFIX}-resizer::after{content:"";position:absolute;top:0;bottom:0;left:-4px;right:-4px}
|
|
614
|
+
.${PREFIX}-advanced .${PREFIX}-resizer:hover,.${PREFIX}-resizing .${PREFIX}-resizer{background:var(--aiw-accent,#6366f1)}
|
|
615
|
+
.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-resizer{display:none}
|
|
616
|
+
/* While dragging: kill text selection + let pointer events pass over the iframe
|
|
617
|
+
(an iframe would otherwise swallow the drag and drop the pointer). */
|
|
618
|
+
.${PREFIX}-resizing{user-select:none}
|
|
619
|
+
.${PREFIX}-resizing .${PREFIX}-pane-frame{pointer-events:none}
|
|
620
|
+
/* Collapsed: the pane shrinks to a thin strip that still shows the toggle (so it
|
|
621
|
+
can be re-opened), and its url + framed body hide. */
|
|
622
|
+
.${PREFIX}-advanced.${PREFIX}-pane-collapsed .${PREFIX}-pane{flex:0 0 40px;min-height:0}
|
|
623
|
+
.${PREFIX}-pane-collapsed .${PREFIX}-pane-body{display:none}
|
|
624
|
+
.${PREFIX}-pane-collapsed .${PREFIX}-pane-url{display:none}
|
|
625
|
+
.${PREFIX}-pane-collapsed .${PREFIX}-pane-bar{padding:6px 5px}
|
|
626
|
+
.${PREFIX}-pane-collapsed .${PREFIX}-pane-collapse{transform:rotate(180deg)}
|
|
627
|
+
.${PREFIX}-pane-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--aiw-border);background:var(--aiw-surface-2);flex:0 0 auto}
|
|
628
|
+
.${PREFIX}-pane-url{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
629
|
+
/* Padded gutter around the driven app so it reads as an inset "screen". */
|
|
630
|
+
.${PREFIX}-pane-body{flex:1 1 auto;min-width:0;min-height:0;display:flex;padding:12px}
|
|
631
|
+
.${PREFIX}-pane-frame{flex:1 1 auto;width:100%;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-xl);background:var(--aiw-surface);min-height:0;box-shadow:0 1px 4px rgba(15,23,42,.07)}
|
|
632
|
+
/* Narrow: stack the app pane on top and the chat (with composer) below. */
|
|
633
|
+
@media (max-width:820px){
|
|
634
|
+
.${PREFIX}-advanced{flex-direction:column-reverse}
|
|
635
|
+
.${PREFIX}-advanced .${PREFIX}-chatcol{flex:1 1 auto;min-width:0;max-width:none;border-right:0;border-top:1px solid var(--aiw-border)}
|
|
636
|
+
.${PREFIX}-advanced .${PREFIX}-pane{flex:1 1 auto;min-height:38%}
|
|
637
|
+
/* Stacked layout: the horizontal divider doesn't apply. */
|
|
638
|
+
.${PREFIX}-advanced .${PREFIX}-resizer{display:none}
|
|
639
|
+
.${PREFIX}-pane-body{padding:8px}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.${PREFIX}-history{position:absolute;inset:0;background:var(--aiw-surface);display:flex;flex-direction:column;z-index:5;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
643
|
+
.${PREFIX}-history-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid var(--aiw-border);font-weight:600;font-size:var(--aiw-font-md)}
|
|
644
|
+
.${PREFIX}-history-back{border:1px solid var(--aiw-border-strong);background:var(--aiw-surface-raised);border-radius:var(--aiw-radius-md);padding:5px 11px;font-size:var(--aiw-font-xs);font-weight:600;cursor:pointer;color:var(--aiw-text-2)}
|
|
645
|
+
.${PREFIX}-history-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:4px;font-size:var(--aiw-font-sm);color:var(--aiw-text-2)}
|
|
646
|
+
.${PREFIX}-history-sep{padding:8px 4px 2px;font-size:var(--aiw-font-3xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--aiw-muted)}
|
|
647
|
+
.${PREFIX}-history-item{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;border:1px solid var(--aiw-border);background:var(--aiw-surface-raised);border-radius:var(--aiw-radius-lg);padding:10px 12px;cursor:pointer;width:100%}
|
|
648
|
+
.${PREFIX}-history-item:hover{border-color:var(--aiw-accent);background:color-mix(in srgb,var(--aiw-accent) 4%,transparent)}
|
|
649
|
+
.${PREFIX}-history-title{font-weight:600;color:var(--aiw-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
650
|
+
.${PREFIX}-history-date{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);flex:0 0 auto}
|
|
651
|
+
.${PREFIX}-history-star{font-size:var(--aiw-font-lg);line-height:1;color:#cbcbcb;flex:0 0 auto;padding:0 2px;cursor:pointer}
|
|
652
|
+
.${PREFIX}-history-star:hover{color:#f59e0b}
|
|
653
|
+
.${PREFIX}-widget{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);padding:12px;background:var(--aiw-surface-raised);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
654
|
+
.${PREFIX}-widget-title{font-size:var(--aiw-font-xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--aiw-muted);margin-bottom:8px}
|
|
655
|
+
.${PREFIX}-widget-stat{font-size:var(--aiw-font-3xl);font-weight:800;line-height:1.1;color:var(--aiw-text)}
|
|
656
|
+
.${PREFIX}-widget-cap{font-size:var(--aiw-font-sm);color:var(--aiw-text-2);margin-top:2px}
|
|
657
|
+
.${PREFIX}-widget-delta{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-accent-ink);margin-top:4px}
|
|
658
|
+
.${PREFIX}-widget-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(90px,1fr));gap:8px}
|
|
659
|
+
/* Composed UI cards ([[ui:…]]) — tiles the assistant lays out itself. */
|
|
660
|
+
.${PREFIX}-ui{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);padding:12px;background:var(--aiw-surface-raised);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
661
|
+
.${PREFIX}-ui-title{font-size:var(--aiw-font-xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--aiw-muted);margin-bottom:6px}
|
|
662
|
+
.${PREFIX}-ui-cap{font-size:var(--aiw-font-sm);color:var(--aiw-text-2);margin-bottom:10px}
|
|
663
|
+
/* A strip SCROLLS rather than shrinking its tiles: squeezing eight scenes into
|
|
664
|
+
a 340px panel makes every one of them too small to judge, which defeats the
|
|
665
|
+
point of showing them. Snap points keep the scroll landing on whole tiles. */
|
|
666
|
+
.${PREFIX}-ui-strip{display:flex;gap:8px;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:4px;-webkit-overflow-scrolling:touch}
|
|
667
|
+
.${PREFIX}-ui-strip>.${PREFIX}-ui-tile{flex:0 0 132px;scroll-snap-align:start}
|
|
668
|
+
/* Carousel: one item at a time. The track is the same tile row, translated. */
|
|
669
|
+
/* ONE number governs the carousel's geometry, because the controls have to be
|
|
670
|
+
anchored to the MEDIA and nothing else knows how tall that is.
|
|
671
|
+
Two real defects came from not having it: a 9:16 scene box computed from the
|
|
672
|
+
panel width was 558px tall in a 368px panel — one empty scene taller than the
|
|
673
|
+
whole visible chat, with the paging pushed off screen — and the dots, pinned
|
|
674
|
+
to the card's BOTTOM, painted over the caption instead of over the picture. */
|
|
675
|
+
.${PREFIX}-ui-carousel{position:relative;--aiw-cmedia:min(38vh,300px)}
|
|
676
|
+
/* Height leads, width follows from the aspect — so a portrait scene stays
|
|
677
|
+
portrait instead of being letterboxed or cropped to fit a cap. */
|
|
678
|
+
.${PREFIX}-ui-carousel .${PREFIX}-ui-media{height:var(--aiw-cmedia);width:auto;max-width:100%;margin:0 auto}
|
|
679
|
+
.${PREFIX}-ui-carousel-stage{position:relative;overflow:hidden;border-radius:var(--aiw-radius-lg)}
|
|
680
|
+
.${PREFIX}-ui-carousel .${PREFIX}-ui-items{display:flex;gap:0;transition:transform .25s ease;overflow:visible}
|
|
681
|
+
.${PREFIX}-ui-carousel .${PREFIX}-ui-tile{flex:0 0 100%;min-width:100%}
|
|
682
|
+
.${PREFIX}-ui-arrow{position:absolute;top:calc(var(--aiw-cmedia) / 2 - 15px);z-index:2;display:flex;height:30px;width:30px;align-items:center;justify-content:center;border:0;border-radius:var(--aiw-radius-pill);background:rgba(0,0,0,.5);color:#fff;font-size:var(--aiw-font-lg);line-height:1;cursor:pointer}
|
|
683
|
+
.${PREFIX}-ui-arrow:hover{background:rgba(0,0,0,.7)}
|
|
684
|
+
.${PREFIX}-ui-arrow-left{left:6px}
|
|
685
|
+
.${PREFIX}-ui-arrow-right{right:6px}
|
|
686
|
+
.${PREFIX}-ui-counter{position:absolute;right:8px;top:8px;z-index:2;padding:2px 8px;border-radius:var(--aiw-radius-pill);background:rgba(0,0,0,.55);color:#fff;font-size:var(--aiw-font-3xs);font-weight:700}
|
|
687
|
+
/* gap 5px -> 18px: 6px dots with 18px between them put the centres 24px apart,
|
|
688
|
+
which is what the WCAG 2.2 spacing exception actually requires (#308). */
|
|
689
|
+
.${PREFIX}-ui-dots{position:absolute;left:0;right:0;top:calc(var(--aiw-cmedia) - 14px);bottom:auto;z-index:2;display:flex;justify-content:center;gap:18px}
|
|
690
|
+
/* 6x6 drawn, real <button>s. Same treatment: the dot stays 6px, the target is 24. */
|
|
691
|
+
.${PREFIX}-ui-dot{position:relative;height:6px;width:6px;padding:0;border:0;border-radius:var(--aiw-radius-pill);background:rgba(255,255,255,.55);cursor:pointer}
|
|
692
|
+
.${PREFIX}-ui-dot::before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:24px;height:24px}
|
|
693
|
+
/* The active dot widens to a pill. NO transition on that width (#309): house
|
|
694
|
+
law allows transform and opacity, and scaleX on a 6px circle distorts the
|
|
695
|
+
round cap into an ellipse — so the honest answer is to change instantly
|
|
696
|
+
rather than animate the wrong property or fake it with the right one. */
|
|
697
|
+
.${PREFIX}-ui-dot-on{width:16px;background:#fff}
|
|
698
|
+
.${PREFIX}-ui-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px}
|
|
699
|
+
.${PREFIX}-ui-list{display:flex;flex-direction:column;gap:8px}
|
|
700
|
+
.${PREFIX}-ui-list>.${PREFIX}-ui-tile{display:grid;grid-template-columns:64px 1fr;gap:10px;align-items:start}
|
|
701
|
+
.${PREFIX}-ui-list .${PREFIX}-ui-media{grid-row:span 3}
|
|
702
|
+
.${PREFIX}-ui-media{position:relative;aspect-ratio:1;border-radius:var(--aiw-radius-lg);overflow:hidden;background:var(--aiw-surface-2);border:1px solid var(--aiw-border-soft);display:flex;align-items:center;justify-content:center}
|
|
703
|
+
.${PREFIX}-ui-a-portrait .${PREFIX}-ui-media{aspect-ratio:4/5}
|
|
704
|
+
.${PREFIX}-ui-a-story .${PREFIX}-ui-media{aspect-ratio:9/16}
|
|
705
|
+
.${PREFIX}-ui-a-landscape .${PREFIX}-ui-media,.${PREFIX}-ui-a-wide .${PREFIX}-ui-media{aspect-ratio:16/9}
|
|
706
|
+
.${PREFIX}-ui-media-el{width:100%;height:100%;object-fit:cover;display:block}
|
|
707
|
+
/* An audio tile has nothing to LOOK at, so the box stops pretending to be a
|
|
708
|
+
picture: no forced aspect ratio, and the player sits at its natural height
|
|
709
|
+
instead of being stretched to fill a square. */
|
|
710
|
+
.${PREFIX}-ui-media:has(> audio.${PREFIX}-ui-media-el){aspect-ratio:auto;padding:8px}
|
|
711
|
+
audio.${PREFIX}-ui-media-el{height:auto;object-fit:unset}
|
|
712
|
+
.${PREFIX}-ui-media-ph{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);text-align:center;padding:0 6px}
|
|
713
|
+
.${PREFIX}-ui-badge{position:absolute;left:6px;top:6px;padding:2px 7px;border-radius:var(--aiw-radius-pill);font-size:var(--aiw-font-3xs);font-weight:700;letter-spacing:.02em;background:rgba(0,0,0,.55);color:#fff;backdrop-filter:blur(4px)}
|
|
714
|
+
.${PREFIX}/* The two FILLED badges stay literal on purpose, and are not the bypass the
|
|
715
|
+
audit counted. They are opaque chips carrying the base rule's white text
|
|
716
|
+
(5.43:1 and 7.54:1), so they neither flip with the scheme nor need to:
|
|
717
|
+
--aiw-ok-bg / --aiw-danger-bg are PALE SURFACES for cards, and pouring one
|
|
718
|
+
into a filled pill would put pale text on a pale fill. Same reasoning as the
|
|
719
|
+
preview chrome's traffic-light dots. */
|
|
720
|
+
-ui-badge-ready{background:#16794a}
|
|
721
|
+
.${PREFIX}-ui-badge-running{background:var(--aiw-accent);color:var(--aiw-accent-contrast)}
|
|
722
|
+
.${PREFIX}-ui-badge-failed{background:#a32020}
|
|
723
|
+
.${PREFIX}-ui-tile-title{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-text);margin-top:6px;line-height:1.25}
|
|
724
|
+
.${PREFIX}-ui-tile-cap{font-size:var(--aiw-font-2xs);color:var(--aiw-text-2);margin-top:2px;line-height:1.3}
|
|
725
|
+
.${PREFIX}-ui-tile-actions,.${PREFIX}-ui-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
|
726
|
+
.${PREFIX}-ui-actions{margin-top:10px}
|
|
727
|
+
.${PREFIX}-ui-act{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}
|
|
728
|
+
.${PREFIX}-ui-btn{padding:5px 10px;border-radius:var(--aiw-radius-md);border:1px solid var(--aiw-border-strong);background:var(--aiw-bg);color:var(--aiw-text);font-size:var(--aiw-font-2xs);font-weight:600;cursor:pointer}
|
|
729
|
+
.${PREFIX}-ui-btn:hover{background:var(--aiw-surface-2)}
|
|
730
|
+
.${PREFIX}-ui-btn:disabled{opacity:.5;cursor:default}
|
|
731
|
+
.${PREFIX}-ui-btn-primary{background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-color:transparent}
|
|
732
|
+
.${PREFIX}-ui-btn-danger{color:var(--aiw-danger-text);border-color:color-mix(in srgb,var(--aiw-danger-text) 40%,transparent)}
|
|
733
|
+
.${PREFIX}-ui-btn-ghost{padding:5px 8px;border-radius:var(--aiw-radius-md);border:1px solid transparent;background:transparent;color:var(--aiw-muted);font-size:var(--aiw-font-2xs);font-weight:600;cursor:pointer}
|
|
734
|
+
.${PREFIX}-ui-confirm-q{font-size:var(--aiw-font-2xs);color:var(--aiw-text-2)}
|
|
735
|
+
.${PREFIX}-preview{align-self:stretch;border:1px solid var(--aiw-border);border-radius:var(--aiw-radius-2xl);overflow:hidden;background:var(--aiw-surface);animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
736
|
+
.${PREFIX}-preview-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--aiw-border-soft)}
|
|
737
|
+
.${PREFIX}-preview-dots{display:inline-flex;gap:4px}
|
|
738
|
+
.${PREFIX}-preview-dots i{width:9px;height:9px;border-radius:50%;background:#e2e2e2}
|
|
739
|
+
.${PREFIX}-preview-dots i:nth-child(1){background:#ff5f57}
|
|
740
|
+
.${PREFIX}-preview-dots i:nth-child(2){background:#febc2e}
|
|
741
|
+
.${PREFIX}-preview-dots i:nth-child(3){background:#28c840}
|
|
742
|
+
.${PREFIX}-preview-title{font-size:var(--aiw-font-xs);font-weight:600;color:var(--aiw-muted)}
|
|
743
|
+
.${PREFIX}-preview-frame{display:block;width:100%;height:340px;border:0;background:var(--aiw-surface)}
|
|
744
|
+
.${PREFIX}-proposal-media{display:block;width:100%;max-height:200px;object-fit:cover;border-radius:var(--aiw-radius-xl);border:1px solid var(--aiw-border);background:var(--aiw-surface-2)}
|
|
745
|
+
.${PREFIX}-proposal-media-holder{position:relative}
|
|
746
|
+
/* NOT --aiw-accent-contrast: this badge sits on 60% black over an image, not on
|
|
747
|
+
the accent, so it must stay light whatever the accent resolves to. Reading the
|
|
748
|
+
accent token here would have turned it navy-on-black the moment #307 made the
|
|
749
|
+
contrast derived. */
|
|
750
|
+
.${PREFIX}-proposal-media-badge{position:absolute;left:8px;bottom:8px;padding:2px 8px;border-radius:var(--aiw-radius-pill);background:rgba(0,0,0,.6);color:#FCF7E3;font-size:var(--aiw-font-2xs);font-weight:600}
|
|
751
|
+
.${PREFIX}-chips{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}
|
|
752
|
+
.${PREFIX}-chip{padding:8px 14px;border-radius:var(--aiw-radius-pill);border:1px solid color-mix(in srgb,var(--aiw-accent) 35%,transparent);background:color-mix(in srgb,var(--aiw-accent) 7%,transparent);color:var(--aiw-accent-ink);font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer;transition:background .12s,color .12s,border-color .12s}
|
|
753
|
+
.${PREFIX}-chip:hover{background:color-mix(in srgb,var(--aiw-accent) 14%,transparent)}
|
|
754
|
+
.${PREFIX}-chip-on,.${PREFIX}-chip-send{background:var(--aiw-accent);color:var(--aiw-accent-contrast);border-color:transparent}
|
|
755
|
+
.${PREFIX}-chip-send:hover{background:var(--aiw-accent)}
|
|
756
|
+
.${PREFIX}-chip-other{border-style:dashed;background:transparent;color:var(--aiw-muted);border-color:var(--aiw-border-strong)}
|
|
757
|
+
.${PREFIX}-chip:disabled{opacity:.45;cursor:default}
|
|
758
|
+
.${PREFIX}-kpi{border:1px solid var(--aiw-border-soft);border-radius:var(--aiw-radius-lg);padding:8px 10px;background:var(--aiw-bg)}
|
|
759
|
+
.${PREFIX}-kpi-v{font-size:var(--aiw-font-2xl);font-weight:700;color:var(--aiw-text)}
|
|
760
|
+
.${PREFIX}-kpi-l{font-size:var(--aiw-font-2xs);color:var(--aiw-muted);margin-top:1px}
|
|
761
|
+
.${PREFIX}-kpi-d{font-size:var(--aiw-font-2xs);font-weight:600;color:var(--aiw-accent-ink);margin-top:2px}
|
|
762
|
+
.${PREFIX}-widget-table{width:100%;border-collapse:collapse;font-size:var(--aiw-font-xs)}
|
|
763
|
+
.${PREFIX}-widget-table th{text-align:left;font-weight:700;color:var(--aiw-text-2);border-bottom:1px solid var(--aiw-border);padding:6px 8px}
|
|
764
|
+
.${PREFIX}-widget-table td{border-bottom:1px solid var(--aiw-border-soft);padding:6px 8px;color:var(--aiw-text)}
|
|
765
|
+
.${PREFIX}-widget-list{margin:0;padding-left:18px;font-size:var(--aiw-font-sm);color:var(--aiw-text);display:flex;flex-direction:column;gap:3px}
|
|
766
|
+
.${PREFIX}-nav{align-self:flex-start;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
767
|
+
.${PREFIX}-nav-btn{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--aiw-accent);background:color-mix(in srgb,var(--aiw-accent) 6%,transparent);color:var(--aiw-accent-ink);border-radius:var(--aiw-radius-lg);padding:9px 14px;font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer}
|
|
768
|
+
.${PREFIX}-nav-btn:hover{background:color-mix(in srgb,var(--aiw-accent) 11%,transparent)}
|
|
769
|
+
.${PREFIX}-nav-btn:disabled{opacity:.7;cursor:default}
|
|
770
|
+
.${PREFIX}-proposal{align-self:stretch;border:1px solid color-mix(in srgb,var(--aiw-accent) 20%,transparent);background:color-mix(in srgb,var(--aiw-accent) 4%,transparent);border-radius:var(--aiw-radius-2xl);padding:12px;display:flex;flex-direction:column;gap:8px;animation:${PREFIX}-rise var(--duration-fast) var(--ease-out)}
|
|
771
|
+
.${PREFIX}-proposal-title{font-size:var(--aiw-font-sm);font-weight:700}
|
|
772
|
+
.${PREFIX}-proposal-edit{display:flex;flex-direction:column;gap:4px}
|
|
773
|
+
.${PREFIX}-proposal-edit-label{font-size:var(--aiw-font-2xs);font-weight:600;color:var(--aiw-text-2)}
|
|
774
|
+
.${PREFIX}-proposal-summary{font-size:var(--aiw-font-xs);color:var(--aiw-text-2);white-space:pre-wrap;line-height:1.45}
|
|
775
|
+
/* Storyboard plan inside a confirm card: one row per shot, so the thing the
|
|
776
|
+
renders are bought from is READ rather than skimmed as a paragraph. */
|
|
777
|
+
/* Two lines of the prompt: enough to judge the shot, not so much that four of
|
|
778
|
+
them bury the buttons under the fold. */
|
|
779
|
+
.${PREFIX}-proposal-ok{font-size:var(--aiw-font-sm);font-weight:600;color:var(--aiw-ok-text);display:inline-flex;align-items:center;gap:6px}
|
|
780
|
+
.${PREFIX}-confirm-q{font-size:var(--aiw-font-sm);color:var(--aiw-text-2);margin-bottom:8px}
|
|
781
|
+
.${PREFIX}-confirm-row{display:flex;gap:8px}
|
|
782
|
+
.${PREFIX}-confirm-no{border:1px solid var(--aiw-border-strong);background:var(--aiw-surface-raised);color:var(--aiw-text-2);border-radius:var(--aiw-radius-lg);padding:9px 14px;font-size:var(--aiw-font-sm);font-weight:600;cursor:pointer}
|
|
783
|
+
${darkWhenHostSaysSo}
|
|
784
|
+
@media (prefers-color-scheme:dark){${darkWhenNobodySaid}}
|
|
785
|
+
|
|
786
|
+
@keyframes ${PREFIX}-sheetup{from{transform:translateY(100%)}to{transform:translateY(0)}}
|
|
787
|
+
/* On phones the panel becomes a full-width bottom sheet (slides up from the
|
|
788
|
+
bottom edge, ~90% of the dynamic viewport, rounded top, grab handle) so the
|
|
789
|
+
chat is comfortably usable instead of a cramped corner card. */
|
|
790
|
+
/* A phone in LANDSCAPE is about 844x390 — wider than 640, so the desktop
|
|
791
|
+
corner card applied and max-height:calc(100vh - 40px) clamped it to 350px
|
|
792
|
+
tall. Header and composer take ~120 of that, leaving ~230px of message log
|
|
793
|
+
in the exact posture where the soft keyboard also appears (#309).
|
|
794
|
+
Short viewport AND coarse pointer, not short alone: a short desktop window
|
|
795
|
+
is not a phone, and a full-screen sheet there would be wrong. */
|
|
796
|
+
@media (max-width:640px),(max-height:520px) and (pointer:coarse){
|
|
797
|
+
/* z-index: the desktop 48 sits deliberately BELOW the app's Radix modals
|
|
798
|
+
(z-50) so a dialog covers the chat. That rule did not anticipate page
|
|
799
|
+
CHROME at the same level: the marketing site's header is
|
|
800
|
+
"fixed inset-x-0 top-0 z-50", so it painted over the top 66px of the
|
|
801
|
+
full-screen sheet — its logo, locale switcher and hamburger landing on
|
|
802
|
+
top of the widget's own avatar and buttons (reported 2026-08-07).
|
|
803
|
+
A full-screen sheet covers the viewport and IS the modal surface while
|
|
804
|
+
it is open, so here — and only here — it outranks host chrome. */
|
|
805
|
+
.${PREFIX}-panel{inset:0;z-index:51;width:100%;max-width:100%;height:100vh;height:100dvh;max-height:none;border-radius:0;animation:${PREFIX}-sheetup var(--duration-base) var(--ease-out);transition:none}
|
|
806
|
+
.${PREFIX}-expanded{width:100%;max-width:100%;height:100vh;height:100dvh}
|
|
807
|
+
.${PREFIX}-expanded .${PREFIX}-msg{max-width:86%}
|
|
808
|
+
/* Give the title its own full-width row: avatar + actions share the top row,
|
|
809
|
+
the name/subtitle drop to a dedicated line below so the title never gets
|
|
810
|
+
squeezed by the action buttons on narrow phones. */
|
|
811
|
+
.${PREFIX}-header{position:relative;padding-top:calc(14px + env(safe-area-inset-top));display:grid;grid-template-columns:auto 1fr;grid-template-areas:"avatar actions" "name name";align-items:center;gap:8px 10px;touch-action:none}
|
|
812
|
+
.${PREFIX}-header::before{content:"";position:absolute;top:calc(5px + env(safe-area-inset-top));left:50%;transform:translateX(-50%);width:38px;height:4px;border-radius:var(--aiw-radius-xs);background:color-mix(in srgb,var(--aiw-header-fg) 55%,transparent)}
|
|
813
|
+
.${PREFIX}-avatar{grid-area:avatar}
|
|
814
|
+
.${PREFIX}-hactions{grid-area:actions;justify-self:end}
|
|
815
|
+
.${PREFIX}-hname{grid-area:name}
|
|
816
|
+
.${PREFIX}-title{font-size:var(--aiw-font-xl)}
|
|
817
|
+
.${PREFIX}-log{padding-bottom:calc(14px + env(safe-area-inset-bottom))}
|
|
818
|
+
.${PREFIX}-form{padding-bottom:calc(10px + env(safe-area-inset-bottom))}
|
|
819
|
+
/* iOS auto-zooms the page when a focused field is under 16px, and the zoom
|
|
820
|
+
is what then makes the sheet scroll sideways. 16px is the threshold, not
|
|
821
|
+
a preference — do not lower it. text-size-adjust stops iOS inflating the
|
|
822
|
+
rest of the text to compensate. */
|
|
823
|
+
.${PREFIX}-input,.${PREFIX}-edit-input,.${PREFIX}-question-input,
|
|
824
|
+
.${PREFIX}-lead-form input,.${PREFIX}-lead-form textarea{font-size:var(--aiw-font-xl)}
|
|
825
|
+
.${PREFIX}-panel{-webkit-text-size-adjust:100%;text-size-adjust:100%}
|
|
826
|
+
/* While the soft keyboard is up it covers the home indicator, but iOS keeps
|
|
827
|
+
reporting safe-area-inset-bottom as if it were still there — leaving a
|
|
828
|
+
dead band under the composer. The keyboard handler sets this flag. */
|
|
829
|
+
.${PREFIX}-panel.${PREFIX}-kb .${PREFIX}-form{padding-bottom:10px}
|
|
830
|
+
.${PREFIX}-panel.${PREFIX}-kb .${PREFIX}-log{padding-bottom:14px}
|
|
831
|
+
.${PREFIX}-bubble{bottom:16px;${side}:16px}
|
|
832
|
+
/* Expand/restore is meaningless once the sheet is full-screen — hide it. */
|
|
833
|
+
.${PREFIX}-expand{display:none}
|
|
834
|
+
}
|
|
835
|
+
/* ── Reduced motion, gated at the KEYFRAME (#308) ────────────────────────────
|
|
836
|
+
This used to name eleven selectors. Twenty-two were animated, four of the
|
|
837
|
+
eleven carried no animation at all, and the list could only ever fall further
|
|
838
|
+
behind — the shared rise keyframe alone has sixteen consumers.
|
|
839
|
+
|
|
840
|
+
Redefining the keyframes reaches every consumer, including ones added later
|
|
841
|
+
by someone who never reads this block. That is the difference between a rule
|
|
842
|
+
and a list.
|
|
843
|
+
|
|
844
|
+
NOT all the way to zero, per house law: entrances keep their fade so spatial
|
|
845
|
+
continuity survives, and lose only the movement. The spinner keeps turning
|
|
846
|
+
because it reports STATE — a still spinner says "stuck", which is a worse
|
|
847
|
+
answer than a moving one. Purely decorative loops stop entirely.
|
|
848
|
+
|
|
849
|
+
One thing CSS cannot reach: the launcher blob is SMIL animate elements, and
|
|
850
|
+
animation:none has no authority over SMIL. #305 removes it by drawing a
|
|
851
|
+
static mark; noted so the two issues do not both try. */
|
|
852
|
+
@media (prefers-reduced-motion:reduce){
|
|
853
|
+
@keyframes ${PREFIX}-rise{from{opacity:0}to{opacity:1}}
|
|
854
|
+
@keyframes ${PREFIX}-richin{from{opacity:0}to{opacity:1}}
|
|
855
|
+
@keyframes ${PREFIX}-tokin{from{opacity:0}to{opacity:1}}
|
|
856
|
+
@keyframes ${PREFIX}-sheetup{from{opacity:0}to{opacity:1}}
|
|
857
|
+
@keyframes ${PREFIX}-float{0%,100%{transform:none}}
|
|
858
|
+
@keyframes ${PREFIX}-pulse{0%,100%{opacity:1}}
|
|
859
|
+
@keyframes ${PREFIX}-blink{0%,100%{opacity:1}}
|
|
860
|
+
@keyframes ${PREFIX}-blink2{0%,100%{opacity:1}}
|
|
861
|
+
@keyframes ${PREFIX}-caret{0%,100%{opacity:1}}
|
|
862
|
+
/* -breathe is the launcher's working state (#305). It is a STATUS signal, like
|
|
863
|
+
the spinner, so stillness would say 'idle' when a turn is running — but it is
|
|
864
|
+
also a loop in the corner of every page, which the spinner is not. Slowed and
|
|
865
|
+
shallowed rather than stopped: it still reads as alive, at a fraction of the
|
|
866
|
+
amplitude. */
|
|
867
|
+
@keyframes ${PREFIX}-breathe{0%,100%{opacity:1}50%{opacity:.88}}
|
|
868
|
+
.${PREFIX}-log{scroll-behavior:auto}
|
|
869
|
+
}
|
|
870
|
+
`;
|
|
871
|
+
const style = document.createElement("style");
|
|
872
|
+
style.setAttribute("data-sgiant-aiw", "");
|
|
873
|
+
style.textContent = css;
|
|
874
|
+
document.head.appendChild(style);
|
|
875
|
+
}
|
|
876
|
+
//# sourceMappingURL=styles.js.map
|