@sphoro/bugcatch-web-sdk 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/dist/bugcatch-call.es.js +201 -200
- package/dist/bugcatch-call.es.js.map +1 -1
- package/dist/bugcatch-call.umd.js +1 -1
- package/dist/bugcatch-call.umd.js.map +1 -1
- package/dist/bugcatch-core.es.js +1085 -1007
- package/dist/bugcatch-core.es.js.map +1 -1
- package/dist/bugcatch-panel.es.js +1179 -1050
- package/dist/bugcatch-panel.es.js.map +1 -1
- package/dist/bugcatch-panel.umd.js +397 -326
- package/dist/bugcatch-panel.umd.js.map +1 -1
- package/dist/bugcatch.standalone.umd.js +520 -437
- package/dist/bugcatch.standalone.umd.js.map +1 -1
- package/dist/bugcatch.umd.js +132 -120
- package/dist/bugcatch.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/accent.js +38 -0
- package/src/incoming-call.js +17 -15
- package/src/index.d.ts +43 -2
- package/src/index.js +26 -7
- package/src/panel/icons.js +2 -0
- package/src/panel/index.js +17 -3
- package/src/panel/messages.js +2 -2
- package/src/panel/picks.js +6 -2
- package/src/panel/refs.js +1 -1
- package/src/panel/render.js +11 -3
- package/src/panel/styles.js +242 -186
- package/src/panel/thread.js +117 -7
- package/src/updates.js +77 -27
- package/src/widget.js +160 -141
package/src/widget.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import * as ICONS from './icons.js';
|
|
16
16
|
import { loadPanel, loadCall } from './lazy.js';
|
|
17
17
|
import { noticeNode } from './notice.js';
|
|
18
|
+
import { readableOn } from './accent.js';
|
|
18
19
|
|
|
19
20
|
const POSITIONS = {
|
|
20
21
|
'bottom-right': 'bottom:20px;right:20px;',
|
|
@@ -50,86 +51,119 @@ function writeStoredTheme(value) {
|
|
|
50
51
|
} catch { /* storage denied — the choice just won't outlive the page */ }
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// The dark palette, said once and applied twice: when the site or the visitor
|
|
55
|
+
// picks dark, and when 'auto' meets a system set to dark.
|
|
56
|
+
const DARK = `
|
|
57
|
+
--bc-bg: #151517;
|
|
58
|
+
--bc-elevated: #1c1c1f;
|
|
59
|
+
--bc-fg: #f3f2ef;
|
|
60
|
+
--bc-muted: #b3b0aa;
|
|
61
|
+
--bc-faint: #8f8c86;
|
|
62
|
+
--bc-border: #2c2c30;
|
|
63
|
+
--bc-subtle: #1f1f22;
|
|
64
|
+
--bc-stone: #26262a;
|
|
65
|
+
--bc-raised: #303035;
|
|
66
|
+
--bc-danger: #ff6b81;
|
|
67
|
+
--bc-ok: #4fd19a;
|
|
68
|
+
--bc-warn: #f2a54a;
|
|
69
|
+
--bc-info: #79a8ff;
|
|
70
|
+
--bc-brand-ink: #b3b0aa;
|
|
71
|
+
--bc-brand-mark: #a59cff;
|
|
72
|
+
--bc-shadow: 0 0 0 1px rgba(255,255,255,.08), 0 30px 70px -20px rgba(0,0,0,.8), 0 8px 20px -8px rgba(0,0,0,.5);
|
|
73
|
+
--bc-shadow-sm: 0 0 0 1px rgba(255,255,255,.07), 0 1px 2px rgba(0,0,0,.4);
|
|
74
|
+
--bc-accent-soft: color-mix(in srgb, var(--bc-accent) 14%, var(--bc-bg));`;
|
|
75
|
+
|
|
53
76
|
// The launcher's stylesheet: the theme, the base rules, the dock. The panel's
|
|
54
77
|
// arrives with the panel (panel/styles.js) and is added after this one.
|
|
78
|
+
//
|
|
79
|
+
// `accent` is the host's colour, or null for ink (accent.js). Ink is drawn
|
|
80
|
+
// from the theme's own foreground, so it turns white on dark by itself; a
|
|
81
|
+
// configured colour stays that colour in both.
|
|
55
82
|
const css = (accent) => `
|
|
56
83
|
:host { all: initial; }
|
|
57
84
|
:host {
|
|
58
|
-
--bc-accent: ${accent};
|
|
59
|
-
--bc-on-accent:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
--bc-
|
|
63
|
-
--bc-
|
|
64
|
-
--bc-
|
|
65
|
-
--bc-
|
|
66
|
-
--bc-
|
|
67
|
-
--bc-
|
|
68
|
-
--bc-
|
|
69
|
-
--bc-
|
|
70
|
-
--bc-
|
|
85
|
+
--bc-accent: ${accent || 'var(--bc-fg)'};
|
|
86
|
+
--bc-on-accent: ${accent ? readableOn(accent) : 'var(--bc-bg)'};
|
|
87
|
+
/* The assistant's orb: BugCatch's own three colours, or the host's accent
|
|
88
|
+
with two lighter cuts of it, so the orb on their site is in their colour. */
|
|
89
|
+
--bc-o1: ${accent ? `color-mix(in srgb, ${accent} 32%, #fff)` : '#ffb3c9'};
|
|
90
|
+
--bc-o2: ${accent || '#6a5cff'};
|
|
91
|
+
--bc-o3: ${accent ? `color-mix(in srgb, ${accent} 22%, #ffe7d1)` : '#ffd6a0'};
|
|
92
|
+
--bc-bg: #ffffff;
|
|
93
|
+
--bc-elevated: #ffffff;
|
|
94
|
+
--bc-fg: #0e0e10;
|
|
95
|
+
--bc-muted: #57544e;
|
|
96
|
+
--bc-faint: #75726c;
|
|
97
|
+
--bc-border: #e9e7e3;
|
|
98
|
+
--bc-subtle: #f6f5f3;
|
|
99
|
+
/* Warm stone: fields, chips and the selected track. */
|
|
100
|
+
--bc-stone: #f0efec;
|
|
101
|
+
--bc-raised: #ffffff;
|
|
102
|
+
--bc-danger: #c8233d;
|
|
103
|
+
--bc-ok: #0b7a55;
|
|
104
|
+
--bc-warn: #a4570a;
|
|
105
|
+
--bc-info: #2459d1;
|
|
71
106
|
/* The lockup's own two colours, kept off --bc-accent on purpose: that one is
|
|
72
107
|
the customer's brand, and this mark is ours. Values are the light cut of
|
|
73
108
|
brand/sphoro-bugcatch-primary.svg, pulled up from near-black so the mark
|
|
74
109
|
identifies itself without out-shouting the host's page. */
|
|
75
|
-
--bc-brand-ink: #
|
|
110
|
+
--bc-brand-ink: #57544e;
|
|
76
111
|
--bc-brand-mark: #5647ee;
|
|
77
|
-
--bc-shadow: 0
|
|
78
|
-
--bc-shadow-sm: 0 1px
|
|
79
|
-
--bc-dur:
|
|
112
|
+
--bc-shadow: 0 0 0 1px rgba(20,18,14,.06), 0 30px 70px -24px rgba(20,18,14,.30), 0 8px 20px -8px rgba(20,18,14,.10);
|
|
113
|
+
--bc-shadow-sm: 0 0 0 1px rgba(20,18,14,.06), 0 1px 2px rgba(20,18,14,.05);
|
|
114
|
+
--bc-dur: 220ms;
|
|
80
115
|
--bc-ease: cubic-bezier(.22,1,.36,1);
|
|
116
|
+
/* Film grain over the orb, so it reads as light rather than a gradient. */
|
|
117
|
+
--bc-grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .6 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
|
|
81
118
|
/* The system everything below is drawn from. Derived tokens are written in
|
|
82
119
|
terms of the ones above, so a host that overrides --bc-accent or the
|
|
83
120
|
palette gets a coherent result without touching these. */
|
|
84
|
-
--bc-accent-soft: color-mix(in srgb, var(--bc-accent)
|
|
85
|
-
--bc-accent-line: color-mix(in srgb, var(--bc-accent)
|
|
86
|
-
--bc-ring: 0 0 0 3px color-mix(in srgb, var(--bc-accent)
|
|
121
|
+
--bc-accent-soft: color-mix(in srgb, var(--bc-accent) 7%, var(--bc-bg));
|
|
122
|
+
--bc-accent-line: color-mix(in srgb, var(--bc-accent) 30%, var(--bc-border));
|
|
123
|
+
--bc-ring: 0 0 0 3px color-mix(in srgb, var(--bc-accent) 16%, transparent);
|
|
87
124
|
--bc-r-sm: 8px;
|
|
88
125
|
--bc-r-md: 12px;
|
|
89
|
-
--bc-r-lg:
|
|
90
|
-
--bc-r-xl:
|
|
126
|
+
--bc-r-lg: 16px;
|
|
127
|
+
--bc-r-xl: 20px;
|
|
91
128
|
--bc-fs-xs: 11.5px;
|
|
92
129
|
--bc-fs-sm: 12.5px;
|
|
93
130
|
--bc-fs-md: 14px;
|
|
94
|
-
--bc-fs-lg:
|
|
131
|
+
--bc-fs-lg: 15px;
|
|
95
132
|
--bc-pad: 18px;
|
|
96
133
|
}
|
|
97
|
-
:host(.bc-dark) {
|
|
98
|
-
--bc-bg: #14161c;
|
|
99
|
-
--bc-elevated: #1c1f27;
|
|
100
|
-
--bc-fg: #eef0f4;
|
|
101
|
-
--bc-muted: #a3aab6;
|
|
102
|
-
--bc-faint: #6f7682;
|
|
103
|
-
--bc-border: #2a2e38;
|
|
104
|
-
--bc-subtle: #1f222a;
|
|
105
|
-
--bc-raised: #363d4b;
|
|
106
|
-
--bc-warn: #f59e0b;
|
|
107
|
-
--bc-brand-ink: #9aa1ad;
|
|
108
|
-
--bc-brand-mark: #9b8fff;
|
|
109
|
-
--bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);
|
|
110
|
-
--bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);
|
|
111
|
-
--bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));
|
|
134
|
+
:host(.bc-dark) {${DARK}
|
|
112
135
|
}
|
|
113
136
|
@media (prefers-color-scheme: dark) {
|
|
114
|
-
:host(.bc-auto) {
|
|
115
|
-
--bc-bg: #14161c;
|
|
116
|
-
--bc-elevated: #1c1f27;
|
|
117
|
-
--bc-fg: #eef0f4;
|
|
118
|
-
--bc-muted: #a3aab6;
|
|
119
|
-
--bc-faint: #6f7682;
|
|
120
|
-
--bc-border: #2a2e38;
|
|
121
|
-
--bc-subtle: #1f222a;
|
|
122
|
-
--bc-raised: #363d4b;
|
|
123
|
-
--bc-warn: #f59e0b;
|
|
124
|
-
--bc-brand-ink: #9aa1ad;
|
|
125
|
-
--bc-brand-mark: #9b8fff;
|
|
126
|
-
--bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);
|
|
127
|
-
--bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);
|
|
128
|
-
--bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));
|
|
137
|
+
:host(.bc-auto) {${DARK}
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif; }
|
|
142
|
+
|
|
143
|
+
/* ---------- the orb ----------
|
|
144
|
+
The assistant's face, on the launcher and at the top of every screen: three
|
|
145
|
+
blurred colour blobs turning slowly under a highlight and grain. Hue from
|
|
146
|
+
--bc-o1..3. Sized by whoever places it. */
|
|
147
|
+
.orb {
|
|
148
|
+
position: relative; display: block; flex: none; aspect-ratio: 1; border-radius: 50%;
|
|
149
|
+
background: var(--bc-o2); overflow: hidden; isolation: isolate;
|
|
150
|
+
box-shadow: inset -3px -5px 10px rgba(0,0,0,.22), inset 2px 3px 7px rgba(255,255,255,.35);
|
|
151
|
+
}
|
|
152
|
+
.orb::before {
|
|
153
|
+
content: ''; position: absolute; inset: -20%; border-radius: 50%; z-index: -1;
|
|
154
|
+
background:
|
|
155
|
+
radial-gradient(circle at 70% 72%, var(--bc-o3) 0%, transparent 44%),
|
|
156
|
+
radial-gradient(circle at 26% 66%, var(--bc-o1) 0%, transparent 48%),
|
|
157
|
+
radial-gradient(circle at 58% 30%, var(--bc-o2) 0%, transparent 62%);
|
|
158
|
+
filter: blur(3px);
|
|
159
|
+
animation: bc-orb-turn 18s linear infinite;
|
|
160
|
+
}
|
|
161
|
+
.orb::after {
|
|
162
|
+
content: ''; position: absolute; inset: 0; border-radius: 50%;
|
|
163
|
+
background: radial-gradient(circle at 34% 26%, rgba(255,255,255,.85) 0 5%, rgba(255,255,255,0) 30%), var(--bc-grain);
|
|
164
|
+
mix-blend-mode: soft-light;
|
|
165
|
+
}
|
|
166
|
+
@keyframes bc-orb-turn { to { transform: rotate(360deg); } }
|
|
133
167
|
svg { width: 1em; height: 1em; display: block; }
|
|
134
168
|
/* The hidden attribute has to actually hide. Nearly every element in here is given a
|
|
135
169
|
display by a rule below, and any of them beats the attribute's own UA style
|
|
@@ -160,65 +194,50 @@ button:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-vi
|
|
|
160
194
|
@keyframes bc-launcher-in { from { opacity: 0; transform: translateY(12px) scale(.86) } to { opacity: 1; transform: none } }
|
|
161
195
|
@keyframes bc-item-in { from { opacity: 0; transform: translateY(8px) scale(.96) } to { opacity: 1; transform: none } }
|
|
162
196
|
|
|
163
|
-
/* ---------- dock:
|
|
197
|
+
/* ---------- dock: the launcher, and what is said beside it ---------- */
|
|
164
198
|
.dock {
|
|
165
199
|
position: fixed; z-index: 2147483000;
|
|
166
200
|
display: flex; flex-direction: column; gap: 10px;
|
|
167
201
|
}
|
|
168
202
|
.dock[hidden] { display: none; }
|
|
169
|
-
/* While the panel is open the
|
|
170
|
-
|
|
203
|
+
/* While the panel is open the launcher rises above the scrim, so the button
|
|
204
|
+
that opened it is the button that closes it. */
|
|
171
205
|
.dock.over { z-index: 2147483002; }
|
|
172
206
|
.dock.at-right { align-items: flex-end; }
|
|
173
207
|
.dock.at-left { align-items: flex-start; }
|
|
174
|
-
/* Anchored to the top, "above the launcher" would be off-screen, so
|
|
175
|
-
|
|
208
|
+
/* Anchored to the top, "above the launcher" would be off-screen, so cards go
|
|
209
|
+
under it and the launcher stays the outermost thing. */
|
|
176
210
|
.dock.at-top { flex-direction: column-reverse; }
|
|
177
211
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
box-shadow: 0 6px 18px rgba(10,12,16,.14), 0 1px 3px rgba(10,12,16,.08);
|
|
183
|
-
animation: bc-launcher-in 420ms var(--bc-ease) both;
|
|
184
|
-
}
|
|
185
|
-
.themer button {
|
|
186
|
-
display: grid; place-items: center;
|
|
187
|
-
width: 28px; height: 28px; padding: 0;
|
|
188
|
-
border: none; border-radius: 999px; background: transparent;
|
|
189
|
-
color: var(--bc-muted); cursor: pointer;
|
|
190
|
-
transition: background var(--bc-dur) var(--bc-ease), color var(--bc-dur) var(--bc-ease), transform 140ms var(--bc-ease);
|
|
191
|
-
}
|
|
192
|
-
.themer button svg { font-size: 15px; }
|
|
193
|
-
.themer button:hover { background: var(--bc-subtle); color: var(--bc-fg); }
|
|
194
|
-
.themer button:active { transform: scale(.92); }
|
|
195
|
-
.themer button[aria-pressed="true"] { background: var(--bc-accent); color: var(--bc-on-accent); }
|
|
196
|
-
.themer button[aria-pressed="true"]:hover { background: var(--bc-accent); filter: brightness(1.06); }
|
|
197
|
-
|
|
198
|
-
/* ---------- launcher ---------- */
|
|
212
|
+
/* ---------- launcher ----------
|
|
213
|
+
A pill with the assistant's orb in it: ink by default, the host's colour when
|
|
214
|
+
they set one. The shadow is neutral, never tinted, so a bright brand colour
|
|
215
|
+
does not glow on somebody's checkout. */
|
|
199
216
|
.launcher {
|
|
200
217
|
position: relative;
|
|
201
|
-
display: inline-flex; align-items: center; gap:
|
|
202
|
-
height:
|
|
218
|
+
display: inline-flex; align-items: center; gap: 10px;
|
|
219
|
+
height: 46px; padding: 0 18px 0 7px; border: none; border-radius: 999px;
|
|
203
220
|
background: var(--bc-accent); color: var(--bc-on-accent);
|
|
204
|
-
font-size: var(--bc-fs-md); font-weight:
|
|
221
|
+
font-size: var(--bc-fs-md); font-weight: 560; line-height: 1; letter-spacing: -.01em;
|
|
205
222
|
cursor: pointer;
|
|
206
|
-
box-shadow: 0
|
|
207
|
-
transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear
|
|
223
|
+
box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 10px 26px -8px rgba(0,0,0,.36);
|
|
224
|
+
transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear;
|
|
208
225
|
animation: bc-launcher-in 420ms var(--bc-ease) both;
|
|
209
226
|
}
|
|
210
|
-
/* Two
|
|
227
|
+
/* Two faces in one circle: the orb when closed, a chevron when open, turned
|
|
211
228
|
into each other so the button reads as the panel's own handle. */
|
|
212
|
-
.launcher .ico { position: relative; width:
|
|
229
|
+
.launcher .ico { position: relative; width: 32px; height: 32px; flex: none; }
|
|
213
230
|
.launcher .ico > span {
|
|
214
|
-
position: absolute; inset: 0;
|
|
231
|
+
position: absolute; inset: 0; display: grid; place-items: center; border-radius: 50%;
|
|
215
232
|
transition: transform 320ms var(--bc-ease), opacity 200ms ease;
|
|
216
233
|
}
|
|
217
|
-
.launcher .ico
|
|
234
|
+
.launcher .ico .orb { width: 32px; }
|
|
235
|
+
.launcher .ico .when-open { background: color-mix(in srgb, var(--bc-on-accent) 14%, transparent); }
|
|
236
|
+
.launcher .ico svg { width: 18px; height: 18px; }
|
|
218
237
|
.launcher .ico .when-open { opacity: 0; transform: rotate(-90deg) scale(.6); }
|
|
219
238
|
.launcher[aria-expanded="true"] .ico .when-closed { opacity: 0; transform: rotate(90deg) scale(.6); }
|
|
220
239
|
.launcher[aria-expanded="true"] .ico .when-open { opacity: 1; transform: none; }
|
|
221
|
-
.launcher:hover { transform: translateY(-
|
|
240
|
+
.launcher:hover { transform: translateY(-1px); box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 14px 30px -8px rgba(0,0,0,.42); }
|
|
222
241
|
.launcher:active { transform: translateY(0) scale(.97); transition-duration: 90ms; }
|
|
223
242
|
.launcher:focus-visible { outline-offset: 3px; }
|
|
224
243
|
.launcher[hidden] { display: none; }
|
|
@@ -241,11 +260,11 @@ button:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-vi
|
|
|
241
260
|
In this sheet because the card beside the button exists before the panel's
|
|
242
261
|
does. The tone is a colour and an icon; the site's accent is the info one. */
|
|
243
262
|
.notice {
|
|
244
|
-
--tone: var(--bc-
|
|
263
|
+
--tone: var(--bc-info);
|
|
245
264
|
display: flex; align-items: flex-start; gap: 10px;
|
|
246
|
-
padding:
|
|
247
|
-
background:
|
|
248
|
-
border: 1px solid
|
|
265
|
+
padding: 11px 10px 11px 12px; border-radius: var(--bc-r-md);
|
|
266
|
+
background: var(--bc-subtle);
|
|
267
|
+
border: 1px solid var(--bc-border);
|
|
249
268
|
color: var(--bc-fg);
|
|
250
269
|
font-size: var(--bc-fs-sm); line-height: 1.45; text-align: left;
|
|
251
270
|
animation: bc-item-in 240ms var(--bc-ease) both;
|
|
@@ -253,33 +272,48 @@ button:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-vi
|
|
|
253
272
|
.notice.tone-success { --tone: var(--bc-ok); }
|
|
254
273
|
.notice.tone-warning { --tone: var(--bc-warn); }
|
|
255
274
|
.notice.tone-critical { --tone: var(--bc-danger); }
|
|
256
|
-
|
|
275
|
+
/* The tone is the icon's colour and a wash behind it — enough to tell a delay
|
|
276
|
+
from a fix at a glance, without a coloured card shouting over the form. */
|
|
277
|
+
.notice-ico {
|
|
278
|
+
flex: none; display: grid; place-items: center; width: 22px; height: 22px; margin-top: -1px;
|
|
279
|
+
border-radius: 7px; font-size: 14px;
|
|
280
|
+
color: var(--tone); background: color-mix(in srgb, var(--tone) 13%, transparent);
|
|
281
|
+
}
|
|
257
282
|
.notice-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
|
258
|
-
.notice-text strong { display: block; font-weight:
|
|
283
|
+
.notice-text strong { display: block; font-weight: 600; letter-spacing: -.005em; }
|
|
259
284
|
.notice-text p { margin: 2px 0 0; color: var(--bc-muted); white-space: pre-line; }
|
|
260
285
|
.notice-cta {
|
|
261
|
-
display: inline-
|
|
262
|
-
color: var(--
|
|
286
|
+
display: inline-flex; align-items: center; gap: 4px; margin-top: 6px;
|
|
287
|
+
color: var(--bc-fg); font-weight: 600; text-decoration: underline;
|
|
288
|
+
text-decoration-color: color-mix(in srgb, var(--bc-fg) 30%, transparent); text-underline-offset: 3px;
|
|
263
289
|
}
|
|
264
|
-
.notice-cta:hover { text-decoration
|
|
290
|
+
.notice-cta:hover { text-decoration-color: currentColor; }
|
|
265
291
|
.notice-x {
|
|
266
292
|
flex: none; display: grid; place-items: center;
|
|
267
|
-
width:
|
|
293
|
+
width: 24px; height: 24px; margin: -2px -2px 0 0; padding: 0;
|
|
268
294
|
border: 0; border-radius: 999px; background: transparent;
|
|
269
|
-
color: var(--bc-
|
|
295
|
+
color: var(--bc-faint); cursor: pointer;
|
|
270
296
|
}
|
|
271
|
-
.notice-x:hover { background: color-mix(in srgb, var(--
|
|
272
|
-
.notice-x svg { font-size:
|
|
297
|
+
.notice-x:hover { background: color-mix(in srgb, var(--bc-fg) 7%, transparent); color: var(--bc-fg); }
|
|
298
|
+
.notice-x svg { font-size: 13px; }
|
|
273
299
|
/* Beside the button it floats over somebody else's page: opaque, lifted, and
|
|
274
300
|
never taller than a glance. The whole text is in the panel. */
|
|
275
301
|
.notice.at-launcher {
|
|
276
302
|
width: min(320px, calc(100vw - 40px));
|
|
277
|
-
|
|
303
|
+
background: var(--bc-elevated); border-color: transparent;
|
|
304
|
+
box-shadow: var(--bc-shadow);
|
|
305
|
+
padding: 13px 12px 13px 13px;
|
|
278
306
|
}
|
|
279
307
|
.notice.at-launcher .notice-text p {
|
|
280
308
|
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
|
|
281
309
|
}
|
|
282
|
-
|
|
310
|
+
/* In the panel they are a stack of short lines above the form, not a wall:
|
|
311
|
+
the body is held to two lines, and the whole of it was beside the button. */
|
|
312
|
+
.notice.at-panel { margin-bottom: 8px; padding: 9px 8px 9px 10px; }
|
|
313
|
+
.notice.at-panel .notice-text p {
|
|
314
|
+
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
|
|
315
|
+
}
|
|
316
|
+
.notice.at-panel .notice-cta { margin-top: 3px; }
|
|
283
317
|
|
|
284
318
|
/* The dock sits in a corner of a page the widget does not own. On a notched
|
|
285
319
|
phone that corner is under the home indicator, and in landscape it is under
|
|
@@ -299,25 +333,12 @@ button:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-vi
|
|
|
299
333
|
.dock.over { display: none; }
|
|
300
334
|
}
|
|
301
335
|
@media (pointer: coarse) {
|
|
302
|
-
/* The theme switch's buttons are small glyphs whose boxes cannot grow without
|
|
303
|
-
moving the launcher under them, so each keeps its size and gets an
|
|
304
|
-
invisible target drawn around it instead. */
|
|
305
|
-
.themer button { position: relative; }
|
|
306
|
-
.themer button::after {
|
|
307
|
-
content: '';
|
|
308
|
-
position: absolute;
|
|
309
|
-
inset: -8px;
|
|
310
|
-
}
|
|
311
|
-
/* The themer's three buttons sit side by side, so their expanders would
|
|
312
|
-
overlap each other without a gap to grow into. */
|
|
313
|
-
.themer { gap: 4px; }
|
|
314
336
|
.notice-x { width: 36px; height: 36px; margin: -9px -8px -6px 0; }
|
|
315
337
|
}
|
|
316
338
|
/* Hover is a lie on a touchscreen: it latches on tap and stays. The launcher's
|
|
317
339
|
lift in particular was left applied after the panel closed. */
|
|
318
340
|
@media (hover: none) {
|
|
319
341
|
.launcher:hover { transform: none; filter: none; }
|
|
320
|
-
.themer button:hover { transform: none; }
|
|
321
342
|
}
|
|
322
343
|
|
|
323
344
|
/* ---------- while the panel's code is on its way ----------
|
|
@@ -356,7 +377,7 @@ export function mountWidget(client) {
|
|
|
356
377
|
|
|
357
378
|
const root = host.attachShadow({ mode: 'open' });
|
|
358
379
|
const style = document.createElement('style');
|
|
359
|
-
style.textContent = css(client.config.accent ||
|
|
380
|
+
style.textContent = css(client.config.accent || null);
|
|
360
381
|
root.appendChild(style);
|
|
361
382
|
|
|
362
383
|
const position = POSITIONS[client.config.widgetPosition] ? client.config.widgetPosition : 'bottom-right';
|
|
@@ -365,30 +386,27 @@ export function mountWidget(client) {
|
|
|
365
386
|
+ (position.startsWith('top') ? ' at-top' : '');
|
|
366
387
|
dock.style.cssText = POSITIONS[position];
|
|
367
388
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
b.setAttribute('aria-pressed', String(b.dataset.theme === theme));
|
|
385
|
-
});
|
|
386
|
-
});
|
|
389
|
+
// The theme is chosen inside the panel, from its header (panel/render.js),
|
|
390
|
+
// not from a switch parked beside the button on every page of the host's
|
|
391
|
+
// site. Light, dark, then the system's own choice, and round again.
|
|
392
|
+
const themeControls = {
|
|
393
|
+
current: () => theme,
|
|
394
|
+
label: () => THEMES.find((t) => t.value === theme).label,
|
|
395
|
+
cycle() {
|
|
396
|
+
const next = THEMES[(THEMES.findIndex((t) => t.value === theme) + 1) % THEMES.length];
|
|
397
|
+
theme = next.value;
|
|
398
|
+
// The panel lives in the same shadow root, so an open form re-themes with
|
|
399
|
+
// it rather than waiting for the next open.
|
|
400
|
+
host.className = themeClass(theme);
|
|
401
|
+
writeStoredTheme(theme);
|
|
402
|
+
return next;
|
|
403
|
+
},
|
|
404
|
+
};
|
|
387
405
|
|
|
388
406
|
const launcher = document.createElement('button');
|
|
389
407
|
launcher.className = 'launcher';
|
|
390
408
|
launcher.type = 'button';
|
|
391
|
-
launcher.innerHTML = `<span class="ico" aria-hidden="true"><span class="when-closed"
|
|
409
|
+
launcher.innerHTML = `<span class="ico" aria-hidden="true"><span class="when-closed"><span class="orb"></span></span>`
|
|
392
410
|
+ `<span class="when-open">${ICONS.chevronDown}</span></span><span>Report a bug</span>`;
|
|
393
411
|
launcher.setAttribute('aria-haspopup', 'dialog');
|
|
394
412
|
launcher.setAttribute('aria-expanded', 'false');
|
|
@@ -433,6 +451,7 @@ export function mountWidget(client) {
|
|
|
433
451
|
dock,
|
|
434
452
|
client,
|
|
435
453
|
preloadCall,
|
|
454
|
+
themeControls,
|
|
436
455
|
onClose: () => {
|
|
437
456
|
open = null;
|
|
438
457
|
dock.classList.remove('over');
|
|
@@ -484,8 +503,8 @@ export function mountWidget(client) {
|
|
|
484
503
|
launcher.focus();
|
|
485
504
|
hideLoadFailure();
|
|
486
505
|
});
|
|
487
|
-
// First in the dock, so it sits furthest from the corner: above the
|
|
488
|
-
//
|
|
506
|
+
// First in the dock, so it sits furthest from the corner: above the
|
|
507
|
+
// launcher in a bottom dock, below it in a top one.
|
|
489
508
|
dock.prepend(failure);
|
|
490
509
|
}
|
|
491
510
|
|
|
@@ -532,7 +551,7 @@ export function mountWidget(client) {
|
|
|
532
551
|
.catch(() => { /* no card is the answer to every failure */ });
|
|
533
552
|
});
|
|
534
553
|
|
|
535
|
-
dock.append(
|
|
554
|
+
dock.append(launcher);
|
|
536
555
|
root.appendChild(dock);
|
|
537
556
|
|
|
538
557
|
document.body.appendChild(host);
|