@mushi-mushi/web 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +1 -1
- package/README.md +25 -2
- package/SECURITY.md +1 -1
- package/dist/index.cjs +760 -229
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +760 -229
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -216,294 +216,607 @@ function getAvailableLocales() {
|
|
|
216
216
|
// src/styles.ts
|
|
217
217
|
function getWidgetStyles(theme) {
|
|
218
218
|
const isDark = theme === "dark";
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
219
|
+
const paper = isDark ? "#0F0E0C" : "#F8F4ED";
|
|
220
|
+
const ink = isDark ? "#F2EBDD" : "#0E0D0B";
|
|
221
|
+
const inkMuted = isDark ? "#928B7E" : "#5C5852";
|
|
222
|
+
const inkFaint = isDark ? "#5A5650" : "#9A9489";
|
|
223
|
+
const rule = isDark ? "rgba(242,235,221,0.10)" : "rgba(14,13,11,0.10)";
|
|
224
|
+
const ruleStrong = isDark ? "rgba(242,235,221,0.18)" : "rgba(14,13,11,0.16)";
|
|
225
|
+
const vermillion = isDark ? "#FF5A47" : "#E03C2C";
|
|
226
|
+
const vermillionWash = isDark ? "rgba(255,90,71,0.12)" : "rgba(224,60,44,0.08)";
|
|
227
|
+
const vermillionInk = isDark ? "#FFE5E0" : "#7A1F15";
|
|
228
|
+
const fontDisplay = `'Iowan Old Style', 'Palatino Linotype', 'Palatino', 'Book Antiqua', 'Cambria', Georgia, 'Times New Roman', serif`;
|
|
229
|
+
const fontBody = `system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Display', 'Segoe UI', sans-serif`;
|
|
230
|
+
const fontMono = `ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, 'Liberation Mono', monospace`;
|
|
231
|
+
const easeStamp = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
227
232
|
return `
|
|
228
233
|
:host {
|
|
229
234
|
all: initial;
|
|
230
|
-
font-family:
|
|
235
|
+
font-family: ${fontBody};
|
|
231
236
|
font-size: 14px;
|
|
232
|
-
line-height: 1.
|
|
233
|
-
color: ${
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
237
|
+
line-height: 1.55;
|
|
238
|
+
color: ${ink};
|
|
239
|
+
-webkit-font-smoothing: antialiased;
|
|
240
|
+
-moz-osx-font-smoothing: grayscale;
|
|
241
|
+
font-feature-settings: 'ss01', 'cv11'; /* nicer system-ui glyphs where supported */
|
|
242
|
+
}
|
|
243
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
244
|
+
button { font-family: inherit; }
|
|
241
245
|
|
|
242
|
-
/* Trigger
|
|
246
|
+
/* \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
247
|
+
A small "stamp card" \u2014 soft rounded square (4px radius), paper
|
|
248
|
+
background, vermillion bottom edge that reads as the inked face
|
|
249
|
+
of a real \u5370\u9451. A pulsing dot in the top-right hints there's a
|
|
250
|
+
channel here without needing a notification badge. */
|
|
243
251
|
.mushi-trigger {
|
|
244
252
|
position: fixed;
|
|
245
|
-
width:
|
|
246
|
-
height:
|
|
247
|
-
border
|
|
248
|
-
border:
|
|
253
|
+
width: 52px;
|
|
254
|
+
height: 52px;
|
|
255
|
+
border: 1px solid ${ruleStrong};
|
|
256
|
+
border-radius: 4px;
|
|
257
|
+
background: ${paper};
|
|
258
|
+
color: ${ink};
|
|
249
259
|
cursor: pointer;
|
|
250
260
|
display: flex;
|
|
251
261
|
align-items: center;
|
|
252
262
|
justify-content: center;
|
|
263
|
+
font-family: ${fontDisplay};
|
|
253
264
|
font-size: 22px;
|
|
254
|
-
|
|
255
|
-
box-shadow:
|
|
256
|
-
|
|
265
|
+
line-height: 1;
|
|
266
|
+
box-shadow:
|
|
267
|
+
0 1px 0 ${rule},
|
|
268
|
+
0 6px 14px -8px rgba(14,13,11,0.35),
|
|
269
|
+
inset 0 -3px 0 ${vermillion};
|
|
270
|
+
transition: transform 200ms ${easeStamp}, box-shadow 200ms ${easeStamp};
|
|
271
|
+
overflow: visible;
|
|
272
|
+
isolation: isolate;
|
|
273
|
+
}
|
|
274
|
+
.mushi-trigger::after {
|
|
275
|
+
content: '';
|
|
276
|
+
position: absolute;
|
|
277
|
+
top: 6px;
|
|
278
|
+
right: 6px;
|
|
279
|
+
width: 6px;
|
|
280
|
+
height: 6px;
|
|
281
|
+
border-radius: 50%;
|
|
282
|
+
background: ${vermillion};
|
|
283
|
+
box-shadow: 0 0 0 0 ${vermillion};
|
|
284
|
+
animation: mushi-pulse 2.4s ${easeStamp} infinite;
|
|
257
285
|
}
|
|
258
286
|
.mushi-trigger:hover {
|
|
259
|
-
transform:
|
|
260
|
-
box-shadow:
|
|
287
|
+
transform: translateY(-2px) rotate(-1.5deg);
|
|
288
|
+
box-shadow:
|
|
289
|
+
0 1px 0 ${rule},
|
|
290
|
+
0 14px 24px -10px rgba(14,13,11,0.45),
|
|
291
|
+
inset 0 -3px 0 ${vermillion};
|
|
292
|
+
}
|
|
293
|
+
.mushi-trigger:active {
|
|
294
|
+
transform: translateY(0) rotate(0);
|
|
295
|
+
box-shadow:
|
|
296
|
+
0 1px 0 ${rule},
|
|
297
|
+
0 2px 4px -2px rgba(14,13,11,0.35),
|
|
298
|
+
inset 0 -2px 0 ${vermillion};
|
|
299
|
+
}
|
|
300
|
+
.mushi-trigger:focus-visible {
|
|
301
|
+
outline: 2px solid ${vermillion};
|
|
302
|
+
outline-offset: 3px;
|
|
303
|
+
}
|
|
304
|
+
.mushi-trigger.bottom-right { bottom: 24px; right: 24px; }
|
|
305
|
+
.mushi-trigger.bottom-left { bottom: 24px; left: 24px; }
|
|
306
|
+
.mushi-trigger.top-right { top: 24px; right: 24px; }
|
|
307
|
+
.mushi-trigger.top-left { top: 24px; left: 24px; }
|
|
308
|
+
|
|
309
|
+
@keyframes mushi-pulse {
|
|
310
|
+
0% { box-shadow: 0 0 0 0 ${vermillion}; opacity: 1; }
|
|
311
|
+
70% { box-shadow: 0 0 0 8px rgba(224,60,44,0); opacity: 0.5; }
|
|
312
|
+
100% { box-shadow: 0 0 0 0 rgba(224,60,44,0); opacity: 1; }
|
|
261
313
|
}
|
|
262
|
-
.mushi-trigger.bottom-right { bottom: 20px; right: 20px; }
|
|
263
|
-
.mushi-trigger.bottom-left { bottom: 20px; left: 20px; }
|
|
264
|
-
.mushi-trigger.top-right { top: 20px; right: 20px; }
|
|
265
|
-
.mushi-trigger.top-left { top: 20px; left: 20px; }
|
|
266
314
|
|
|
267
|
-
/* Panel
|
|
315
|
+
/* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
316
|
+
Paper-card. Sharper corners (6px) than typical SaaS modals
|
|
317
|
+
(which default to 12-16px and read as plastic). Two-layer shadow:
|
|
318
|
+
one hairline that sells the paper edge, one diffuse that lifts
|
|
319
|
+
the panel off the underlying app. No backdrop-filter \u2014 we want
|
|
320
|
+
the widget to feel like it sits ON the page, not blur INTO it. */
|
|
268
321
|
.mushi-panel {
|
|
269
322
|
position: fixed;
|
|
270
|
-
width:
|
|
271
|
-
max-
|
|
272
|
-
|
|
273
|
-
background: ${
|
|
274
|
-
|
|
275
|
-
border:
|
|
323
|
+
width: 384px;
|
|
324
|
+
max-width: calc(100vw - 32px);
|
|
325
|
+
max-height: min(640px, calc(100vh - 120px));
|
|
326
|
+
background: ${paper};
|
|
327
|
+
border: 1px solid ${ruleStrong};
|
|
328
|
+
border-radius: 6px;
|
|
329
|
+
box-shadow:
|
|
330
|
+
0 1px 0 ${rule},
|
|
331
|
+
0 24px 56px -20px rgba(14,13,11,0.30),
|
|
332
|
+
0 8px 16px -8px rgba(14,13,11,0.20);
|
|
276
333
|
overflow: hidden;
|
|
277
334
|
display: flex;
|
|
278
335
|
flex-direction: column;
|
|
336
|
+
transform-origin: var(--mushi-origin, bottom right);
|
|
279
337
|
}
|
|
280
|
-
.mushi-panel.open {
|
|
281
|
-
animation: mushi-slide-in 0.25s ease forwards;
|
|
282
|
-
}
|
|
338
|
+
.mushi-panel.open { animation: mushi-stamp-in 320ms ${easeStamp} both; }
|
|
283
339
|
.mushi-panel.closed { display: none; }
|
|
284
|
-
.mushi-panel.bottom-right { bottom:
|
|
285
|
-
.mushi-panel.bottom-left { bottom:
|
|
286
|
-
.mushi-panel.top-right { top:
|
|
287
|
-
.mushi-panel.top-left { top:
|
|
340
|
+
.mushi-panel.bottom-right { bottom: 88px; right: 24px; --mushi-origin: bottom right; }
|
|
341
|
+
.mushi-panel.bottom-left { bottom: 88px; left: 24px; --mushi-origin: bottom left; }
|
|
342
|
+
.mushi-panel.top-right { top: 88px; right: 24px; --mushi-origin: top right; }
|
|
343
|
+
.mushi-panel.top-left { top: 88px; left: 24px; --mushi-origin: top left; }
|
|
288
344
|
|
|
289
|
-
@keyframes mushi-
|
|
290
|
-
|
|
291
|
-
|
|
345
|
+
@keyframes mushi-stamp-in {
|
|
346
|
+
0% { opacity: 0; transform: scale(0.94) translateY(6px); }
|
|
347
|
+
60% { opacity: 1; }
|
|
348
|
+
100% { opacity: 1; transform: scale(1) translateY(0); }
|
|
292
349
|
}
|
|
293
350
|
|
|
294
|
-
/* Header
|
|
351
|
+
/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
352
|
+
Editorial masthead: small mono eyebrow ("MUSHI / REPORT") on top,
|
|
353
|
+
serif display headline below, mono step counter on the far right.
|
|
354
|
+
A single hairline separates header from body \u2014 no card stacking. */
|
|
295
355
|
.mushi-header {
|
|
296
|
-
padding: 14px
|
|
297
|
-
border-bottom: 1px solid ${
|
|
298
|
-
display:
|
|
356
|
+
padding: 18px 20px 14px;
|
|
357
|
+
border-bottom: 1px solid ${rule};
|
|
358
|
+
display: grid;
|
|
359
|
+
grid-template-columns: auto 1fr auto;
|
|
360
|
+
align-items: end;
|
|
361
|
+
gap: 12px;
|
|
362
|
+
}
|
|
363
|
+
.mushi-header-mark {
|
|
364
|
+
display: inline-flex;
|
|
299
365
|
align-items: center;
|
|
300
|
-
|
|
366
|
+
justify-content: center;
|
|
367
|
+
width: 22px;
|
|
368
|
+
height: 22px;
|
|
369
|
+
border-radius: 3px;
|
|
370
|
+
background: ${vermillion};
|
|
371
|
+
color: #FAF7F0;
|
|
372
|
+
font-family: ${fontDisplay};
|
|
373
|
+
font-size: 14px;
|
|
374
|
+
font-weight: 600;
|
|
375
|
+
line-height: 1;
|
|
376
|
+
letter-spacing: -0.02em;
|
|
377
|
+
transform: rotate(-3deg);
|
|
378
|
+
flex-shrink: 0;
|
|
379
|
+
}
|
|
380
|
+
.mushi-header-titles {
|
|
381
|
+
min-width: 0;
|
|
382
|
+
display: flex;
|
|
383
|
+
flex-direction: column;
|
|
384
|
+
gap: 2px;
|
|
385
|
+
}
|
|
386
|
+
.mushi-header-eyebrow {
|
|
387
|
+
font-family: ${fontMono};
|
|
388
|
+
font-size: 10px;
|
|
389
|
+
letter-spacing: 0.18em;
|
|
390
|
+
text-transform: uppercase;
|
|
391
|
+
color: ${inkMuted};
|
|
301
392
|
}
|
|
302
393
|
.mushi-header h3 {
|
|
303
|
-
font-
|
|
394
|
+
font-family: ${fontDisplay};
|
|
395
|
+
font-size: 19px;
|
|
396
|
+
font-weight: 500;
|
|
397
|
+
line-height: 1.15;
|
|
398
|
+
letter-spacing: -0.01em;
|
|
399
|
+
color: ${ink};
|
|
400
|
+
}
|
|
401
|
+
.mushi-header-meta {
|
|
402
|
+
align-self: start;
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 6px;
|
|
406
|
+
}
|
|
407
|
+
.mushi-step-counter {
|
|
408
|
+
font-family: ${fontMono};
|
|
409
|
+
font-size: 11px;
|
|
410
|
+
color: ${inkMuted};
|
|
411
|
+
letter-spacing: 0.06em;
|
|
412
|
+
tab-size: 2ch;
|
|
413
|
+
padding-top: 2px;
|
|
414
|
+
}
|
|
415
|
+
.mushi-step-counter b {
|
|
304
416
|
font-weight: 600;
|
|
305
|
-
|
|
417
|
+
color: ${ink};
|
|
306
418
|
}
|
|
307
419
|
.mushi-close, .mushi-back {
|
|
308
420
|
background: none;
|
|
309
421
|
border: none;
|
|
310
422
|
cursor: pointer;
|
|
311
|
-
font-size: 18px;
|
|
312
|
-
color: ${textMuted};
|
|
313
423
|
padding: 4px;
|
|
314
|
-
|
|
424
|
+
color: ${inkMuted};
|
|
425
|
+
font-family: ${fontBody};
|
|
426
|
+
font-size: 14px;
|
|
315
427
|
line-height: 1;
|
|
428
|
+
border-radius: 3px;
|
|
429
|
+
transition: color 150ms ${easeStamp};
|
|
316
430
|
}
|
|
317
|
-
.mushi-close:hover, .mushi-back:hover {
|
|
318
|
-
|
|
431
|
+
.mushi-close:hover, .mushi-back:hover { color: ${vermillion}; }
|
|
432
|
+
.mushi-close:focus-visible, .mushi-back:focus-visible {
|
|
433
|
+
outline: 1.5px solid ${vermillion};
|
|
434
|
+
outline-offset: 2px;
|
|
319
435
|
}
|
|
320
436
|
|
|
321
|
-
/* Body
|
|
437
|
+
/* \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
438
|
+
Generous left/right padding (22px) so type breathes. Vertical
|
|
439
|
+
padding tighter at top because the header rule already creates
|
|
440
|
+
breathing room. */
|
|
322
441
|
.mushi-body {
|
|
323
|
-
padding:
|
|
442
|
+
padding: 8px 22px 16px;
|
|
324
443
|
overflow-y: auto;
|
|
325
444
|
flex: 1;
|
|
445
|
+
scrollbar-width: thin;
|
|
446
|
+
scrollbar-color: ${inkFaint} transparent;
|
|
326
447
|
}
|
|
448
|
+
.mushi-body::-webkit-scrollbar { width: 6px; }
|
|
449
|
+
.mushi-body::-webkit-scrollbar-thumb { background: ${inkFaint}; border-radius: 3px; }
|
|
327
450
|
|
|
328
|
-
/* Step 1:
|
|
451
|
+
/* \u2500\u2500 Step 1: Categories as a contents-page list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
452
|
+
No boxes. Hairline rules between rows. Hovering a row pulls a
|
|
453
|
+
vermillion arrow in from the right and tints the row label \u2014
|
|
454
|
+
reads like flipping through an index card. */
|
|
329
455
|
.mushi-option-btn {
|
|
330
|
-
display:
|
|
456
|
+
display: grid;
|
|
457
|
+
grid-template-columns: auto 1fr auto;
|
|
331
458
|
align-items: center;
|
|
332
|
-
gap:
|
|
459
|
+
gap: 14px;
|
|
333
460
|
width: 100%;
|
|
334
|
-
padding:
|
|
335
|
-
|
|
336
|
-
border-
|
|
337
|
-
|
|
338
|
-
background: ${bgMuted};
|
|
461
|
+
padding: 14px 0;
|
|
462
|
+
border: none;
|
|
463
|
+
border-bottom: 1px solid ${rule};
|
|
464
|
+
background: transparent;
|
|
339
465
|
cursor: pointer;
|
|
340
|
-
font-size: 14px;
|
|
341
466
|
color: inherit;
|
|
342
467
|
text-align: left;
|
|
343
|
-
transition:
|
|
344
|
-
|
|
345
|
-
.mushi-option-btn:hover {
|
|
346
|
-
border-color: ${isDark ? "#a78bfa" : accent};
|
|
347
|
-
background: ${accentBgLight};
|
|
348
|
-
transform: translateX(2px);
|
|
468
|
+
transition: padding 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
469
|
+
position: relative;
|
|
349
470
|
}
|
|
471
|
+
.mushi-option-btn:last-child { border-bottom: none; }
|
|
472
|
+
.mushi-option-btn:hover { padding-left: 6px; color: ${vermillion}; }
|
|
473
|
+
.mushi-option-btn:hover .mushi-option-arrow { opacity: 1; transform: translateX(0); color: ${vermillion}; }
|
|
350
474
|
.mushi-option-btn:focus-visible {
|
|
351
|
-
outline:
|
|
352
|
-
|
|
475
|
+
outline: none;
|
|
476
|
+
padding-left: 6px;
|
|
477
|
+
box-shadow: inset 2px 0 0 ${vermillion};
|
|
478
|
+
}
|
|
479
|
+
.mushi-option-icon {
|
|
480
|
+
font-size: 18px;
|
|
481
|
+
line-height: 1;
|
|
482
|
+
flex-shrink: 0;
|
|
483
|
+
filter: ${isDark ? "none" : "grayscale(0.15)"};
|
|
484
|
+
}
|
|
485
|
+
.mushi-option-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
486
|
+
.mushi-option-label {
|
|
487
|
+
font-family: ${fontDisplay};
|
|
488
|
+
font-size: 16px;
|
|
489
|
+
font-weight: 500;
|
|
490
|
+
letter-spacing: -0.005em;
|
|
491
|
+
line-height: 1.2;
|
|
492
|
+
}
|
|
493
|
+
.mushi-option-desc {
|
|
494
|
+
font-size: 12px;
|
|
495
|
+
color: ${inkMuted};
|
|
496
|
+
letter-spacing: 0.005em;
|
|
497
|
+
}
|
|
498
|
+
.mushi-option-arrow {
|
|
499
|
+
font-family: ${fontMono};
|
|
500
|
+
font-size: 14px;
|
|
501
|
+
color: ${inkFaint};
|
|
502
|
+
opacity: 0;
|
|
503
|
+
transform: translateX(-4px);
|
|
504
|
+
transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
353
505
|
}
|
|
354
|
-
.mushi-option-icon { font-size: 20px; flex-shrink: 0; }
|
|
355
|
-
.mushi-option-text { display: flex; flex-direction: column; gap: 2px; }
|
|
356
|
-
.mushi-option-label { font-weight: 500; }
|
|
357
|
-
.mushi-option-desc { font-size: 12px; color: ${textMuted}; }
|
|
358
506
|
|
|
359
|
-
/* Step 2:
|
|
507
|
+
/* \u2500\u2500 Step 2: Selected-category breadcrumb + intent text-buttons \u2500
|
|
508
|
+
Breadcrumb is a thin chip with the kanji-stamp aesthetic carried
|
|
509
|
+
over (vermillion left rule). Intents are inline TEXT buttons
|
|
510
|
+
with vermillion underlines on hover \u2014 not pill-shaped chips,
|
|
511
|
+
which is the SaaS default and not what we are. */
|
|
360
512
|
.mushi-selected-category {
|
|
361
|
-
display: flex;
|
|
513
|
+
display: inline-flex;
|
|
362
514
|
align-items: center;
|
|
363
515
|
gap: 8px;
|
|
364
|
-
padding:
|
|
365
|
-
border-
|
|
366
|
-
background: ${
|
|
367
|
-
|
|
368
|
-
font-
|
|
369
|
-
|
|
370
|
-
|
|
516
|
+
padding: 6px 10px 6px 12px;
|
|
517
|
+
border-left: 2px solid ${vermillion};
|
|
518
|
+
background: ${vermillionWash};
|
|
519
|
+
color: ${vermillionInk};
|
|
520
|
+
font-family: ${fontMono};
|
|
521
|
+
font-size: 11px;
|
|
522
|
+
letter-spacing: 0.12em;
|
|
523
|
+
text-transform: uppercase;
|
|
524
|
+
margin: 4px 0 14px;
|
|
525
|
+
border-radius: 0 3px 3px 0;
|
|
526
|
+
}
|
|
527
|
+
.mushi-selected-category span:first-child { font-size: 14px; }
|
|
371
528
|
.mushi-intents {
|
|
372
529
|
display: flex;
|
|
373
|
-
flex-
|
|
374
|
-
gap:
|
|
530
|
+
flex-direction: column;
|
|
531
|
+
gap: 2px;
|
|
375
532
|
}
|
|
376
533
|
.mushi-intent-btn {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
534
|
+
display: flex;
|
|
535
|
+
align-items: center;
|
|
536
|
+
justify-content: space-between;
|
|
537
|
+
gap: 12px;
|
|
538
|
+
padding: 12px 0;
|
|
539
|
+
border: none;
|
|
540
|
+
border-bottom: 1px solid ${rule};
|
|
541
|
+
background: transparent;
|
|
381
542
|
cursor: pointer;
|
|
382
|
-
font-size: 13px;
|
|
383
543
|
color: inherit;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
background: ${accentBgLight};
|
|
544
|
+
text-align: left;
|
|
545
|
+
font-family: ${fontDisplay};
|
|
546
|
+
font-size: 15px;
|
|
547
|
+
transition: padding 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
389
548
|
}
|
|
549
|
+
.mushi-intent-btn::after {
|
|
550
|
+
content: '\u2192';
|
|
551
|
+
font-family: ${fontMono};
|
|
552
|
+
font-size: 13px;
|
|
553
|
+
color: ${inkFaint};
|
|
554
|
+
opacity: 0;
|
|
555
|
+
transform: translateX(-4px);
|
|
556
|
+
transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp};
|
|
557
|
+
}
|
|
558
|
+
.mushi-intent-btn:last-child { border-bottom: none; }
|
|
559
|
+
.mushi-intent-btn:hover { padding-left: 6px; color: ${vermillion}; }
|
|
560
|
+
.mushi-intent-btn:hover::after { opacity: 1; transform: translateX(0); color: ${vermillion}; }
|
|
390
561
|
.mushi-intent-btn:focus-visible {
|
|
391
|
-
outline:
|
|
392
|
-
|
|
562
|
+
outline: none;
|
|
563
|
+
padding-left: 6px;
|
|
564
|
+
box-shadow: inset 2px 0 0 ${vermillion};
|
|
393
565
|
}
|
|
394
566
|
|
|
395
|
-
/* Step 3:
|
|
567
|
+
/* \u2500\u2500 Step 3: Borderless textarea + minimal attach pills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
568
|
+
The textarea has no box around it \u2014 just a hairline underline
|
|
569
|
+
that turns vermillion on focus. Encourages writing rather than
|
|
570
|
+
form-filling. */
|
|
396
571
|
.mushi-textarea {
|
|
397
572
|
width: 100%;
|
|
398
|
-
min-height:
|
|
399
|
-
padding: 10px
|
|
400
|
-
border
|
|
401
|
-
border: 1px solid ${
|
|
402
|
-
background:
|
|
403
|
-
color:
|
|
404
|
-
font-family:
|
|
573
|
+
min-height: 96px;
|
|
574
|
+
padding: 8px 0 10px;
|
|
575
|
+
border: none;
|
|
576
|
+
border-bottom: 1px solid ${ruleStrong};
|
|
577
|
+
background: transparent;
|
|
578
|
+
color: ${ink};
|
|
579
|
+
font-family: ${fontBody};
|
|
405
580
|
font-size: 14px;
|
|
581
|
+
line-height: 1.5;
|
|
406
582
|
resize: vertical;
|
|
407
583
|
outline: none;
|
|
408
|
-
transition: border-color
|
|
584
|
+
transition: border-color 200ms ${easeStamp};
|
|
409
585
|
}
|
|
410
|
-
.mushi-textarea
|
|
411
|
-
|
|
412
|
-
|
|
586
|
+
.mushi-textarea::placeholder {
|
|
587
|
+
color: ${inkFaint};
|
|
588
|
+
font-style: italic;
|
|
413
589
|
}
|
|
590
|
+
.mushi-textarea:focus { border-bottom-color: ${vermillion}; }
|
|
414
591
|
|
|
415
592
|
.mushi-attachments {
|
|
416
593
|
display: flex;
|
|
417
|
-
|
|
418
|
-
|
|
594
|
+
flex-wrap: wrap;
|
|
595
|
+
gap: 6px;
|
|
596
|
+
margin-top: 12px;
|
|
419
597
|
}
|
|
420
598
|
.mushi-attach-btn {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
599
|
+
display: inline-flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
gap: 6px;
|
|
602
|
+
padding: 5px 10px;
|
|
603
|
+
border: 1px solid ${ruleStrong};
|
|
604
|
+
border-radius: 3px;
|
|
605
|
+
background: transparent;
|
|
606
|
+
color: ${inkMuted};
|
|
607
|
+
font-family: ${fontMono};
|
|
608
|
+
font-size: 11px;
|
|
609
|
+
letter-spacing: 0.04em;
|
|
610
|
+
text-transform: uppercase;
|
|
425
611
|
cursor: pointer;
|
|
426
|
-
|
|
427
|
-
color: ${textMuted};
|
|
428
|
-
transition: border-color 0.15s, color 0.15s;
|
|
612
|
+
transition: color 180ms ${easeStamp}, border-color 180ms ${easeStamp}, background 180ms ${easeStamp};
|
|
429
613
|
}
|
|
430
614
|
.mushi-attach-btn:hover {
|
|
431
|
-
|
|
432
|
-
color:
|
|
615
|
+
color: ${ink};
|
|
616
|
+
border-color: ${ink};
|
|
433
617
|
}
|
|
434
618
|
.mushi-attach-btn.active {
|
|
435
|
-
|
|
436
|
-
color: ${
|
|
437
|
-
background: ${
|
|
619
|
+
color: ${vermillion};
|
|
620
|
+
border-color: ${vermillion};
|
|
621
|
+
background: ${vermillionWash};
|
|
622
|
+
}
|
|
623
|
+
.mushi-attach-btn:focus-visible {
|
|
624
|
+
outline: 2px solid ${vermillion};
|
|
625
|
+
outline-offset: 2px;
|
|
438
626
|
}
|
|
439
627
|
|
|
440
|
-
/* Footer
|
|
628
|
+
/* \u2500\u2500 Footer + submit (vermillion stamp) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
629
|
+
Submit button is the heaviest visual moment in the widget \u2014
|
|
630
|
+
vermillion fill, mono-caps label, send arrow. Holds an ink-
|
|
631
|
+
bloom pseudo-element that animates outward when pressed. */
|
|
441
632
|
.mushi-footer {
|
|
442
|
-
padding:
|
|
443
|
-
border-top: 1px solid ${
|
|
633
|
+
padding: 14px 22px 16px;
|
|
634
|
+
border-top: 1px solid ${rule};
|
|
444
635
|
display: flex;
|
|
445
636
|
align-items: center;
|
|
446
|
-
justify-content:
|
|
637
|
+
justify-content: space-between;
|
|
638
|
+
gap: 12px;
|
|
639
|
+
}
|
|
640
|
+
.mushi-footer-hint {
|
|
641
|
+
font-family: ${fontMono};
|
|
642
|
+
font-size: 10px;
|
|
643
|
+
letter-spacing: 0.10em;
|
|
644
|
+
text-transform: uppercase;
|
|
645
|
+
color: ${inkFaint};
|
|
447
646
|
}
|
|
448
647
|
.mushi-submit {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
648
|
+
position: relative;
|
|
649
|
+
display: inline-flex;
|
|
650
|
+
align-items: center;
|
|
651
|
+
gap: 8px;
|
|
652
|
+
padding: 10px 18px;
|
|
653
|
+
border: 1px solid ${vermillion};
|
|
654
|
+
border-radius: 3px;
|
|
655
|
+
background: ${vermillion};
|
|
656
|
+
color: #FAF7F0;
|
|
657
|
+
font-family: ${fontMono};
|
|
658
|
+
font-size: 11px;
|
|
659
|
+
letter-spacing: 0.16em;
|
|
660
|
+
text-transform: uppercase;
|
|
456
661
|
cursor: pointer;
|
|
457
|
-
|
|
662
|
+
overflow: hidden;
|
|
663
|
+
transition: transform 180ms ${easeStamp}, box-shadow 180ms ${easeStamp};
|
|
664
|
+
box-shadow: 0 2px 0 ${isDark ? "#7A1F15" : "#9A2A1E"};
|
|
665
|
+
}
|
|
666
|
+
.mushi-submit::after {
|
|
667
|
+
content: '';
|
|
668
|
+
position: absolute;
|
|
669
|
+
inset: 0;
|
|
670
|
+
background: radial-gradient(circle at center, rgba(255,255,255,0.35) 0%, transparent 60%);
|
|
671
|
+
opacity: 0;
|
|
672
|
+
transform: scale(0.4);
|
|
673
|
+
transition: opacity 280ms ${easeStamp}, transform 380ms ${easeStamp};
|
|
674
|
+
pointer-events: none;
|
|
675
|
+
}
|
|
676
|
+
.mushi-submit:hover {
|
|
677
|
+
transform: translateY(-1px);
|
|
678
|
+
box-shadow: 0 3px 0 ${isDark ? "#7A1F15" : "#9A2A1E"};
|
|
679
|
+
}
|
|
680
|
+
.mushi-submit:hover::after { opacity: 1; transform: scale(1.4); }
|
|
681
|
+
.mushi-submit:active { transform: translateY(1px); box-shadow: 0 1px 0 ${isDark ? "#7A1F15" : "#9A2A1E"}; }
|
|
682
|
+
.mushi-submit:disabled {
|
|
683
|
+
cursor: wait;
|
|
684
|
+
opacity: 0.7;
|
|
458
685
|
}
|
|
459
|
-
.mushi-submit:hover { background: ${accentHover}; }
|
|
460
|
-
.mushi-submit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
461
686
|
.mushi-submit:focus-visible {
|
|
462
|
-
outline: 2px solid ${
|
|
463
|
-
outline-offset:
|
|
687
|
+
outline: 2px solid ${vermillion};
|
|
688
|
+
outline-offset: 3px;
|
|
464
689
|
}
|
|
690
|
+
.mushi-submit-arrow {
|
|
691
|
+
display: inline-block;
|
|
692
|
+
transition: transform 220ms ${easeStamp};
|
|
693
|
+
}
|
|
694
|
+
.mushi-submit:hover .mushi-submit-arrow { transform: translateX(3px); }
|
|
465
695
|
|
|
466
|
-
/* Step indicator
|
|
696
|
+
/* \u2500\u2500 Step indicator (numeral ledger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
697
|
+
Replaces the generic three-dots with a typographic series:
|
|
698
|
+
"01 \u2014 02 \u2014 03". The active step uses serif numerals, the
|
|
699
|
+
others use mono so the active one literally reads heavier. */
|
|
467
700
|
.mushi-step-indicator {
|
|
468
701
|
display: flex;
|
|
702
|
+
align-items: center;
|
|
469
703
|
justify-content: center;
|
|
470
|
-
gap:
|
|
471
|
-
padding: 10px;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
.mushi-
|
|
485
|
-
|
|
704
|
+
gap: 8px;
|
|
705
|
+
padding: 10px 22px 14px;
|
|
706
|
+
color: ${inkFaint};
|
|
707
|
+
font-family: ${fontMono};
|
|
708
|
+
font-size: 11px;
|
|
709
|
+
letter-spacing: 0.10em;
|
|
710
|
+
}
|
|
711
|
+
.mushi-step-num {
|
|
712
|
+
display: inline-flex;
|
|
713
|
+
align-items: baseline;
|
|
714
|
+
gap: 4px;
|
|
715
|
+
transition: color 200ms ${easeStamp};
|
|
716
|
+
}
|
|
717
|
+
.mushi-step-num.done { color: ${inkMuted}; text-decoration: line-through; text-decoration-color: ${inkFaint}; }
|
|
718
|
+
.mushi-step-num.active {
|
|
719
|
+
color: ${vermillion};
|
|
720
|
+
font-family: ${fontDisplay};
|
|
721
|
+
font-size: 14px;
|
|
722
|
+
font-weight: 600;
|
|
723
|
+
letter-spacing: 0;
|
|
486
724
|
}
|
|
725
|
+
.mushi-step-sep { width: 14px; height: 1px; background: ${rule}; }
|
|
487
726
|
|
|
488
|
-
/* Success
|
|
727
|
+
/* \u2500\u2500 Success: \u6731\u5370 stamp animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
728
|
+
The success state is the signature moment. A vermillion ring
|
|
729
|
+
scribes itself, then a "RECEIVED" mono-caps label fades in at
|
|
730
|
+
the centre, evoking a hanko being pressed onto the form. */
|
|
489
731
|
.mushi-success {
|
|
490
732
|
text-align: center;
|
|
491
|
-
padding:
|
|
733
|
+
padding: 28px 16px 20px;
|
|
734
|
+
}
|
|
735
|
+
.mushi-success-stamp {
|
|
736
|
+
position: relative;
|
|
737
|
+
width: 96px;
|
|
738
|
+
height: 96px;
|
|
739
|
+
margin: 0 auto 16px;
|
|
740
|
+
display: inline-flex;
|
|
741
|
+
align-items: center;
|
|
742
|
+
justify-content: center;
|
|
743
|
+
}
|
|
744
|
+
.mushi-success-stamp svg {
|
|
745
|
+
position: absolute;
|
|
746
|
+
inset: 0;
|
|
747
|
+
width: 100%;
|
|
748
|
+
height: 100%;
|
|
749
|
+
}
|
|
750
|
+
.mushi-success-stamp circle {
|
|
751
|
+
fill: none;
|
|
752
|
+
stroke: ${vermillion};
|
|
753
|
+
stroke-width: 3;
|
|
754
|
+
stroke-dasharray: 280;
|
|
755
|
+
stroke-dashoffset: 280;
|
|
756
|
+
transform: rotate(-90deg);
|
|
757
|
+
transform-origin: center;
|
|
758
|
+
animation: mushi-stamp-ring 700ms ${easeStamp} 80ms forwards;
|
|
759
|
+
}
|
|
760
|
+
.mushi-success-stamp-label {
|
|
761
|
+
font-family: ${fontDisplay};
|
|
762
|
+
font-size: 18px;
|
|
763
|
+
font-weight: 600;
|
|
764
|
+
color: ${vermillion};
|
|
765
|
+
letter-spacing: 0.04em;
|
|
766
|
+
transform: rotate(-6deg);
|
|
767
|
+
opacity: 0;
|
|
768
|
+
animation: mushi-stamp-press 360ms ${easeStamp} 600ms forwards;
|
|
769
|
+
}
|
|
770
|
+
.mushi-success-headline {
|
|
771
|
+
font-family: ${fontDisplay};
|
|
772
|
+
font-size: 18px;
|
|
773
|
+
font-weight: 500;
|
|
774
|
+
color: ${ink};
|
|
775
|
+
margin-bottom: 4px;
|
|
492
776
|
}
|
|
493
|
-
.mushi-success-
|
|
494
|
-
font-
|
|
495
|
-
|
|
777
|
+
.mushi-success-meta {
|
|
778
|
+
font-family: ${fontMono};
|
|
779
|
+
font-size: 11px;
|
|
780
|
+
letter-spacing: 0.10em;
|
|
781
|
+
text-transform: uppercase;
|
|
782
|
+
color: ${inkMuted};
|
|
496
783
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
784
|
+
|
|
785
|
+
@keyframes mushi-stamp-ring {
|
|
786
|
+
to { stroke-dashoffset: 0; }
|
|
787
|
+
}
|
|
788
|
+
@keyframes mushi-stamp-press {
|
|
789
|
+
0% { opacity: 0; transform: rotate(-6deg) scale(1.3); }
|
|
790
|
+
60% { opacity: 1; transform: rotate(-6deg) scale(0.94); }
|
|
791
|
+
100% { opacity: 1; transform: rotate(-6deg) scale(1); }
|
|
500
792
|
}
|
|
501
793
|
|
|
502
|
-
/* Error
|
|
794
|
+
/* \u2500\u2500 Error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
795
|
+
Inline editorial note rather than a red box. Vermillion left
|
|
796
|
+
rule keeps the same accent language. */
|
|
503
797
|
.mushi-error {
|
|
504
|
-
|
|
798
|
+
margin-top: 10px;
|
|
799
|
+
padding: 8px 0 8px 10px;
|
|
800
|
+
border-left: 2px solid ${vermillion};
|
|
801
|
+
color: ${vermillion};
|
|
505
802
|
font-size: 12px;
|
|
506
|
-
|
|
803
|
+
font-family: ${fontMono};
|
|
804
|
+
letter-spacing: 0.02em;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* \u2500\u2500 Reduced motion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
808
|
+
Honour the OS preference: kill every transition + animation
|
|
809
|
+
except the focus underline (which is critical feedback). */
|
|
810
|
+
@media (prefers-reduced-motion: reduce) {
|
|
811
|
+
*,
|
|
812
|
+
*::before,
|
|
813
|
+
*::after {
|
|
814
|
+
animation-duration: 0.001ms !important;
|
|
815
|
+
animation-iteration-count: 1 !important;
|
|
816
|
+
transition-duration: 0.001ms !important;
|
|
817
|
+
}
|
|
818
|
+
.mushi-success-stamp circle { stroke-dashoffset: 0; }
|
|
819
|
+
.mushi-success-stamp-label { opacity: 1; }
|
|
507
820
|
}
|
|
508
821
|
`;
|
|
509
822
|
}
|
|
@@ -516,6 +829,18 @@ var CATEGORY_ICONS = {
|
|
|
516
829
|
confusing: "\u{1F615}",
|
|
517
830
|
other: "\u{1F4DD}"
|
|
518
831
|
};
|
|
832
|
+
function pad2(n) {
|
|
833
|
+
return n < 10 ? `0${n}` : String(n);
|
|
834
|
+
}
|
|
835
|
+
var TOTAL_STEPS = 3;
|
|
836
|
+
var STEP_NUMBER = {
|
|
837
|
+
category: 1,
|
|
838
|
+
intent: 2,
|
|
839
|
+
details: 3
|
|
840
|
+
};
|
|
841
|
+
function isSubmitShortcut(e) {
|
|
842
|
+
return (e.metaKey || e.ctrlKey) && e.key === "Enter";
|
|
843
|
+
}
|
|
519
844
|
var MushiWidget = class {
|
|
520
845
|
host;
|
|
521
846
|
shadow;
|
|
@@ -529,11 +854,28 @@ var MushiWidget = class {
|
|
|
529
854
|
screenshotAttached = false;
|
|
530
855
|
elementSelected = false;
|
|
531
856
|
submitting = false;
|
|
857
|
+
/** Captured at the moment of submit so the success ledger metadata
|
|
858
|
+
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
859
|
+
* is on screen. */
|
|
860
|
+
submittedAt = null;
|
|
861
|
+
/** Pending success-state + auto-close timers. Tracked so destroy()
|
|
862
|
+
* can clear them — otherwise a host that unmounts mid-submit leaks
|
|
863
|
+
* this MushiWidget reference (and re-renders into a detached shadow
|
|
864
|
+
* root) for up to ~3.3s after destroy. */
|
|
865
|
+
successTimer = null;
|
|
866
|
+
autoCloseTimer = null;
|
|
532
867
|
constructor(config = {}, callbacks) {
|
|
533
868
|
this.config = {
|
|
534
869
|
position: config.position ?? "bottom-right",
|
|
535
870
|
theme: config.theme ?? "auto",
|
|
536
|
-
|
|
871
|
+
// Falsy-OR (NOT `??`) on purpose: `triggerText: ''` is semantically
|
|
872
|
+
// nonsense — it would render a labelless, glyphless trigger button
|
|
873
|
+
// that users can't see or aim at. Treat empty string the same as
|
|
874
|
+
// omitted so any caller that wires this to a cleared form input or
|
|
875
|
+
// pastes a legacy snippet that emitted `triggerText: ""` (see
|
|
876
|
+
// apps/admin/src/lib/sdkSnippets.ts widgetLines history) still gets
|
|
877
|
+
// the default 🐛 and a visible button.
|
|
878
|
+
triggerText: config.triggerText || "\u{1F41B}",
|
|
537
879
|
expandedTitle: config.expandedTitle ?? "",
|
|
538
880
|
mode: config.mode ?? "conversational",
|
|
539
881
|
locale: config.locale ?? "auto",
|
|
@@ -549,12 +891,27 @@ var MushiWidget = class {
|
|
|
549
891
|
document.body.appendChild(this.host);
|
|
550
892
|
this.render();
|
|
551
893
|
}
|
|
894
|
+
updateConfig(config = {}) {
|
|
895
|
+
this.config = {
|
|
896
|
+
...this.config,
|
|
897
|
+
...config.position ? { position: config.position } : {},
|
|
898
|
+
...config.theme ? { theme: config.theme } : {},
|
|
899
|
+
...config.triggerText !== void 0 ? { triggerText: config.triggerText || "\u{1F41B}" } : {},
|
|
900
|
+
...config.expandedTitle !== void 0 ? { expandedTitle: config.expandedTitle } : {},
|
|
901
|
+
...config.mode ? { mode: config.mode } : {},
|
|
902
|
+
...config.locale ? { locale: config.locale } : {},
|
|
903
|
+
...config.zIndex !== void 0 ? { zIndex: config.zIndex } : {}
|
|
904
|
+
};
|
|
905
|
+
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
906
|
+
this.render();
|
|
907
|
+
}
|
|
552
908
|
open(options) {
|
|
553
909
|
if (this.isOpen) return;
|
|
554
910
|
this.isOpen = true;
|
|
555
911
|
this.screenshotAttached = false;
|
|
556
912
|
this.elementSelected = false;
|
|
557
913
|
this.submitting = false;
|
|
914
|
+
this.submittedAt = null;
|
|
558
915
|
if (options?.category) {
|
|
559
916
|
this.selectedCategory = options.category;
|
|
560
917
|
this.selectedIntent = null;
|
|
@@ -585,6 +942,14 @@ var MushiWidget = class {
|
|
|
585
942
|
if (this.isOpen) this.render();
|
|
586
943
|
}
|
|
587
944
|
destroy() {
|
|
945
|
+
if (this.successTimer !== null) {
|
|
946
|
+
clearTimeout(this.successTimer);
|
|
947
|
+
this.successTimer = null;
|
|
948
|
+
}
|
|
949
|
+
if (this.autoCloseTimer !== null) {
|
|
950
|
+
clearTimeout(this.autoCloseTimer);
|
|
951
|
+
this.autoCloseTimer = null;
|
|
952
|
+
}
|
|
588
953
|
this.host.remove();
|
|
589
954
|
}
|
|
590
955
|
getTheme() {
|
|
@@ -606,6 +971,8 @@ var MushiWidget = class {
|
|
|
606
971
|
trigger.className = `mushi-trigger ${pos}`;
|
|
607
972
|
trigger.textContent = this.config.triggerText;
|
|
608
973
|
trigger.setAttribute("aria-label", t.widget.trigger);
|
|
974
|
+
trigger.setAttribute("aria-haspopup", "dialog");
|
|
975
|
+
trigger.setAttribute("aria-expanded", String(this.isOpen));
|
|
609
976
|
trigger.style.zIndex = String(this.config.zIndex);
|
|
610
977
|
trigger.addEventListener("click", () => {
|
|
611
978
|
if (this.isOpen) this.close();
|
|
@@ -615,6 +982,7 @@ var MushiWidget = class {
|
|
|
615
982
|
const panel = document.createElement("div");
|
|
616
983
|
panel.className = `mushi-panel ${pos}${this.isOpen ? " open" : " closed"}`;
|
|
617
984
|
panel.setAttribute("role", "dialog");
|
|
985
|
+
panel.setAttribute("aria-modal", "true");
|
|
618
986
|
panel.setAttribute("aria-label", t.widget.title);
|
|
619
987
|
panel.style.zIndex = String(this.config.zIndex + 1);
|
|
620
988
|
if (this.isOpen) {
|
|
@@ -636,37 +1004,67 @@ var MushiWidget = class {
|
|
|
636
1004
|
return this.renderSuccessStep();
|
|
637
1005
|
}
|
|
638
1006
|
}
|
|
639
|
-
|
|
1007
|
+
/**
|
|
1008
|
+
* Editorial masthead. Always carries:
|
|
1009
|
+
* • the brand mark (虫 kanji on vermillion, "MUSHI" in mono above)
|
|
1010
|
+
* • the page title (serif display)
|
|
1011
|
+
* • the close affordance
|
|
1012
|
+
*
|
|
1013
|
+
* On sub-steps it additionally renders a back button (replacing the
|
|
1014
|
+
* "MUSHI" eyebrow with a "← BACK" mono link) and a step counter
|
|
1015
|
+
* ledger ("02 / 03") on the far right.
|
|
1016
|
+
*/
|
|
1017
|
+
renderHeader(opts) {
|
|
640
1018
|
const t = this.locale;
|
|
1019
|
+
const { title, showBack = false, step, eyebrow } = opts;
|
|
1020
|
+
const eyebrowHtml = showBack ? `<button type="button" class="mushi-back" data-action="back" aria-label="${t.widget.back}">\u2190 ${t.widget.back}</button>` : `<span class="mushi-header-eyebrow">${eyebrow ?? "Mushi \xB7 Report"}</span>`;
|
|
1021
|
+
const counterHtml = step ? `<span class="mushi-step-counter" aria-label="Step ${step} of ${TOTAL_STEPS}"><b>${pad2(step)}</b> / ${pad2(TOTAL_STEPS)}</span>` : "";
|
|
641
1022
|
return `
|
|
642
1023
|
<div class="mushi-header">
|
|
643
|
-
|
|
644
|
-
<
|
|
645
|
-
|
|
1024
|
+
<div class="mushi-header-mark" aria-hidden="true">\u866B</div>
|
|
1025
|
+
<div class="mushi-header-titles">
|
|
1026
|
+
${eyebrowHtml}
|
|
1027
|
+
<h3>${title}</h3>
|
|
1028
|
+
</div>
|
|
1029
|
+
<div class="mushi-header-meta">
|
|
1030
|
+
${counterHtml}
|
|
1031
|
+
<button type="button" class="mushi-close" data-action="close" aria-label="${t.widget.close}">\u2715</button>
|
|
1032
|
+
</div>
|
|
646
1033
|
</div>
|
|
647
1034
|
`;
|
|
648
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Numeral step indicator: "01 — 02 — 03", with the active step in
|
|
1038
|
+
* vermillion serif and completed steps struck through in mono.
|
|
1039
|
+
* Replaces the original three-dot indicator (a generic SaaS pattern).
|
|
1040
|
+
*/
|
|
1041
|
+
renderStepIndicator(currentStep) {
|
|
1042
|
+
const segments = [];
|
|
1043
|
+
for (let i = 1; i <= TOTAL_STEPS; i++) {
|
|
1044
|
+
const cls = i < currentStep ? "mushi-step-num done" : i === currentStep ? "mushi-step-num active" : "mushi-step-num";
|
|
1045
|
+
segments.push(`<span class="${cls}">${pad2(i)}</span>`);
|
|
1046
|
+
if (i < TOTAL_STEPS) segments.push('<span class="mushi-step-sep" aria-hidden="true"></span>');
|
|
1047
|
+
}
|
|
1048
|
+
return `<div class="mushi-step-indicator" aria-hidden="true">${segments.join("")}</div>`;
|
|
1049
|
+
}
|
|
649
1050
|
renderCategoryStep() {
|
|
650
1051
|
const t = this.locale;
|
|
651
1052
|
const categories = ["bug", "slow", "visual", "confusing", "other"].map((id) => `
|
|
652
|
-
<button class="mushi-option-btn" data-category="${id}" role="radio" aria-checked="false">
|
|
653
|
-
<span class="mushi-option-icon">${CATEGORY_ICONS[id]}</span>
|
|
1053
|
+
<button type="button" class="mushi-option-btn" data-category="${id}" role="radio" aria-checked="false">
|
|
1054
|
+
<span class="mushi-option-icon" aria-hidden="true">${CATEGORY_ICONS[id]}</span>
|
|
654
1055
|
<div class="mushi-option-text">
|
|
655
1056
|
<span class="mushi-option-label">${t.step1.categories[id]}</span>
|
|
656
1057
|
<span class="mushi-option-desc">${t.step1.categoryDescriptions[id]}</span>
|
|
657
1058
|
</div>
|
|
1059
|
+
<span class="mushi-option-arrow" aria-hidden="true">\u2192</span>
|
|
658
1060
|
</button>
|
|
659
1061
|
`).join("");
|
|
660
1062
|
return `
|
|
661
|
-
${this.renderHeader(t.step1.heading)}
|
|
1063
|
+
${this.renderHeader({ title: t.step1.heading, step: STEP_NUMBER.category })}
|
|
662
1064
|
<div class="mushi-body" role="radiogroup" aria-label="${t.step1.heading}">
|
|
663
1065
|
${categories}
|
|
664
1066
|
</div>
|
|
665
|
-
|
|
666
|
-
<span class="mushi-dot active"></span>
|
|
667
|
-
<span class="mushi-dot"></span>
|
|
668
|
-
<span class="mushi-dot"></span>
|
|
669
|
-
</div>
|
|
1067
|
+
${this.renderStepIndicator(STEP_NUMBER.category)}
|
|
670
1068
|
`;
|
|
671
1069
|
}
|
|
672
1070
|
renderIntentStep() {
|
|
@@ -674,32 +1072,28 @@ var MushiWidget = class {
|
|
|
674
1072
|
const cat = this.selectedCategory;
|
|
675
1073
|
const intents = t.step2.intents[cat] || [];
|
|
676
1074
|
const options = intents.map((intent) => `
|
|
677
|
-
<button class="mushi-intent-btn" data-intent="${intent}">
|
|
1075
|
+
<button type="button" class="mushi-intent-btn" data-intent="${intent}">
|
|
678
1076
|
${intent}
|
|
679
1077
|
</button>
|
|
680
1078
|
`).join("");
|
|
681
1079
|
return `
|
|
682
|
-
${this.renderHeader(t.step2.heading, true)}
|
|
1080
|
+
${this.renderHeader({ title: t.step2.heading, showBack: true, step: STEP_NUMBER.intent })}
|
|
683
1081
|
<div class="mushi-body">
|
|
684
1082
|
<div class="mushi-selected-category">
|
|
685
|
-
<span>${CATEGORY_ICONS[cat]}</span>
|
|
1083
|
+
<span aria-hidden="true">${CATEGORY_ICONS[cat]}</span>
|
|
686
1084
|
<span>${t.step1.categories[cat]}</span>
|
|
687
1085
|
</div>
|
|
688
1086
|
<div class="mushi-intents">
|
|
689
1087
|
${options}
|
|
690
1088
|
</div>
|
|
691
1089
|
</div>
|
|
692
|
-
|
|
693
|
-
<span class="mushi-dot done"></span>
|
|
694
|
-
<span class="mushi-dot active"></span>
|
|
695
|
-
<span class="mushi-dot"></span>
|
|
696
|
-
</div>
|
|
1090
|
+
${this.renderStepIndicator(STEP_NUMBER.intent)}
|
|
697
1091
|
`;
|
|
698
1092
|
}
|
|
699
1093
|
renderDetailsStep() {
|
|
700
1094
|
const t = this.locale;
|
|
701
1095
|
return `
|
|
702
|
-
${this.renderHeader(t.step3.heading, true)}
|
|
1096
|
+
${this.renderHeader({ title: t.step3.heading, showBack: true, step: STEP_NUMBER.details })}
|
|
703
1097
|
<div class="mushi-body">
|
|
704
1098
|
<textarea
|
|
705
1099
|
class="mushi-textarea"
|
|
@@ -709,35 +1103,47 @@ var MushiWidget = class {
|
|
|
709
1103
|
autofocus
|
|
710
1104
|
></textarea>
|
|
711
1105
|
<div class="mushi-attachments">
|
|
712
|
-
<button class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
1106
|
+
<button type="button" class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
713
1107
|
\u{1F4F8} ${this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton}
|
|
714
1108
|
</button>
|
|
715
|
-
<button class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
1109
|
+
<button type="button" class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
716
1110
|
\u{1F3AF} ${this.elementSelected ? t.step3.elementSelected : t.step3.elementButton}
|
|
717
1111
|
</button>
|
|
718
1112
|
</div>
|
|
719
1113
|
<div class="mushi-error" style="display:none" role="alert"></div>
|
|
720
1114
|
</div>
|
|
721
1115
|
<div class="mushi-footer">
|
|
722
|
-
<
|
|
723
|
-
|
|
1116
|
+
<span class="mushi-footer-hint" aria-hidden="true">\u2318 + ENTER \u2192 send</span>
|
|
1117
|
+
<button type="button" class="mushi-submit" data-action="submit"${this.submitting ? " disabled" : ""}>
|
|
1118
|
+
<span>${this.submitting ? t.widget.submitting : t.widget.submit}</span>
|
|
1119
|
+
<span class="mushi-submit-arrow" aria-hidden="true">\u2192</span>
|
|
724
1120
|
</button>
|
|
725
1121
|
</div>
|
|
726
|
-
|
|
727
|
-
<span class="mushi-dot done"></span>
|
|
728
|
-
<span class="mushi-dot done"></span>
|
|
729
|
-
<span class="mushi-dot active"></span>
|
|
730
|
-
</div>
|
|
1122
|
+
${this.renderStepIndicator(STEP_NUMBER.details)}
|
|
731
1123
|
`;
|
|
732
1124
|
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Editorial success state: 朱印-style red stamp ring with the kanji
|
|
1127
|
+
* 受 ("received") at its centre, the localised "thank you" string
|
|
1128
|
+
* in serif below, and a mono ledger receipt ("REPORT · HH:MM:SS").
|
|
1129
|
+
* The ring + label animations are defined in styles.ts so this stays
|
|
1130
|
+
* pure markup and `prefers-reduced-motion` flips them to the final
|
|
1131
|
+
* frame instantly.
|
|
1132
|
+
*/
|
|
733
1133
|
renderSuccessStep() {
|
|
734
1134
|
const t = this.locale;
|
|
1135
|
+
const stamp = this.submittedAt ?? /* @__PURE__ */ new Date();
|
|
1136
|
+
const time = stamp.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
|
|
735
1137
|
return `
|
|
736
|
-
${this.renderHeader(t.widget.title)}
|
|
1138
|
+
${this.renderHeader({ title: t.widget.title, eyebrow: "Mushi \xB7 Receipt" })}
|
|
737
1139
|
<div class="mushi-body">
|
|
738
1140
|
<div class="mushi-success">
|
|
739
|
-
<div class="mushi-success-
|
|
740
|
-
|
|
1141
|
+
<div class="mushi-success-stamp" aria-hidden="true">
|
|
1142
|
+
<svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"><circle cx="50" cy="50" r="44"/></svg>
|
|
1143
|
+
<span class="mushi-success-stamp-label">\u53D7</span>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div class="mushi-success-headline">${t.widget.submitted}</div>
|
|
1146
|
+
<div class="mushi-success-meta">REPORT \xB7 ${time}</div>
|
|
741
1147
|
</div>
|
|
742
1148
|
</div>
|
|
743
1149
|
`;
|
|
@@ -775,7 +1181,7 @@ var MushiWidget = class {
|
|
|
775
1181
|
panel.querySelector('[data-action="element"]')?.addEventListener("click", () => {
|
|
776
1182
|
this.callbacks.onElementSelectorRequest?.();
|
|
777
1183
|
});
|
|
778
|
-
|
|
1184
|
+
const submitReport = () => {
|
|
779
1185
|
const textarea = panel.querySelector(".mushi-textarea");
|
|
780
1186
|
const description = textarea?.value?.trim() ?? "";
|
|
781
1187
|
const errorEl = panel.querySelector(".mushi-error");
|
|
@@ -788,27 +1194,43 @@ var MushiWidget = class {
|
|
|
788
1194
|
return;
|
|
789
1195
|
}
|
|
790
1196
|
this.submitting = true;
|
|
1197
|
+
this.submittedAt = /* @__PURE__ */ new Date();
|
|
791
1198
|
this.render();
|
|
792
1199
|
this.callbacks.onSubmit({
|
|
793
1200
|
category: this.selectedCategory,
|
|
794
1201
|
description,
|
|
795
1202
|
intent: this.selectedIntent ?? void 0
|
|
796
1203
|
});
|
|
797
|
-
setTimeout(() => {
|
|
1204
|
+
this.successTimer = setTimeout(() => {
|
|
1205
|
+
this.successTimer = null;
|
|
798
1206
|
this.submitting = false;
|
|
799
1207
|
this.step = "success";
|
|
800
1208
|
this.render();
|
|
801
|
-
setTimeout(() => {
|
|
1209
|
+
this.autoCloseTimer = setTimeout(() => {
|
|
1210
|
+
this.autoCloseTimer = null;
|
|
802
1211
|
if (this.step === "success") this.close();
|
|
803
|
-
},
|
|
1212
|
+
}, 2800);
|
|
804
1213
|
}, 500);
|
|
805
|
-
}
|
|
1214
|
+
};
|
|
1215
|
+
panel.querySelector('[data-action="submit"]')?.addEventListener("click", submitReport);
|
|
806
1216
|
panel.addEventListener("keydown", (e) => {
|
|
807
|
-
if (e.key === "Escape")
|
|
1217
|
+
if (e.key === "Escape") {
|
|
1218
|
+
this.close();
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
if (this.step === "details" && isSubmitShortcut(e)) {
|
|
1222
|
+
e.preventDefault();
|
|
1223
|
+
submitReport();
|
|
1224
|
+
}
|
|
808
1225
|
});
|
|
809
1226
|
}
|
|
810
1227
|
trapFocus(panel) {
|
|
811
1228
|
requestAnimationFrame(() => {
|
|
1229
|
+
const textarea = panel.querySelector("textarea");
|
|
1230
|
+
if (textarea) {
|
|
1231
|
+
textarea.focus();
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
812
1234
|
const focusable = panel.querySelectorAll("button, textarea, [tabindex]");
|
|
813
1235
|
if (focusable.length > 0) focusable[0].focus();
|
|
814
1236
|
});
|
|
@@ -1396,6 +1818,8 @@ var Mushi = class {
|
|
|
1396
1818
|
}
|
|
1397
1819
|
};
|
|
1398
1820
|
function createInstance(config) {
|
|
1821
|
+
const bootstrapConfig = config;
|
|
1822
|
+
let activeConfig = config;
|
|
1399
1823
|
const log = config.debug ?? false ? createLogger({ scope: "mushi", level: "debug", format: "pretty" }) : noopLogger;
|
|
1400
1824
|
const apiClient = createApiClient({
|
|
1401
1825
|
projectId: config.projectId,
|
|
@@ -1406,20 +1830,50 @@ function createInstance(config) {
|
|
|
1406
1830
|
const offlineQueue = createOfflineQueue(config.offline);
|
|
1407
1831
|
const rateLimiter = createRateLimiter({ maxBurst: 10, refillRate: 1, refillIntervalMs: 5e3 });
|
|
1408
1832
|
const piiScrubber = createPiiScrubber();
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1833
|
+
let consoleCap = null;
|
|
1834
|
+
let networkCap = null;
|
|
1835
|
+
let perfCap = null;
|
|
1836
|
+
let screenshotCap = null;
|
|
1837
|
+
let elementSelector = null;
|
|
1838
|
+
function syncCaptureModules() {
|
|
1839
|
+
if (activeConfig.capture?.console !== false) {
|
|
1840
|
+
consoleCap ??= createConsoleCapture();
|
|
1841
|
+
} else {
|
|
1842
|
+
consoleCap?.destroy();
|
|
1843
|
+
consoleCap = null;
|
|
1844
|
+
}
|
|
1845
|
+
if (activeConfig.capture?.network !== false) {
|
|
1846
|
+
networkCap ??= createNetworkCapture();
|
|
1847
|
+
} else {
|
|
1848
|
+
networkCap?.destroy();
|
|
1849
|
+
networkCap = null;
|
|
1850
|
+
}
|
|
1851
|
+
if (activeConfig.capture?.performance !== false) {
|
|
1852
|
+
perfCap ??= createPerformanceCapture();
|
|
1853
|
+
} else {
|
|
1854
|
+
perfCap?.destroy();
|
|
1855
|
+
perfCap = null;
|
|
1856
|
+
}
|
|
1857
|
+
screenshotCap = activeConfig.capture?.screenshot !== "off" ? screenshotCap ?? createScreenshotCapture() : null;
|
|
1858
|
+
if (!screenshotCap) pendingScreenshot = null;
|
|
1859
|
+
if (activeConfig.capture?.elementSelector !== false) {
|
|
1860
|
+
elementSelector ??= createElementSelector();
|
|
1861
|
+
} else {
|
|
1862
|
+
elementSelector?.deactivate();
|
|
1863
|
+
elementSelector = null;
|
|
1864
|
+
pendingElement = null;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1414
1867
|
const listeners = /* @__PURE__ */ new Map();
|
|
1415
1868
|
function emit(type, data) {
|
|
1416
1869
|
listeners.get(type)?.forEach((handler) => handler({ type, data }));
|
|
1417
1870
|
}
|
|
1418
|
-
let userInfo = null;
|
|
1419
|
-
const customMetadata = {};
|
|
1420
1871
|
let pendingScreenshot = null;
|
|
1421
1872
|
let pendingElement = null;
|
|
1422
1873
|
let pendingProactiveTrigger = null;
|
|
1874
|
+
let userInfo = null;
|
|
1875
|
+
const customMetadata = {};
|
|
1876
|
+
syncCaptureModules();
|
|
1423
1877
|
const widget = new MushiWidget(config.widget, {
|
|
1424
1878
|
onSubmit: async ({ category, description, intent }) => {
|
|
1425
1879
|
log.info("Report submitted", { category, intent });
|
|
@@ -1442,13 +1896,13 @@ function createInstance(config) {
|
|
|
1442
1896
|
emit("widget:closed");
|
|
1443
1897
|
},
|
|
1444
1898
|
onScreenshotRequest: async () => {
|
|
1445
|
-
if (!screenshotCap) return;
|
|
1899
|
+
if (!screenshotCap || activeConfig.capture?.screenshot === "off") return;
|
|
1446
1900
|
log.debug("Taking screenshot");
|
|
1447
1901
|
pendingScreenshot = await screenshotCap.take();
|
|
1448
1902
|
widget.setScreenshotAttached(pendingScreenshot !== null);
|
|
1449
1903
|
},
|
|
1450
1904
|
onElementSelectorRequest: async () => {
|
|
1451
|
-
if (!elementSelector) return;
|
|
1905
|
+
if (!elementSelector || activeConfig.capture?.elementSelector === false) return;
|
|
1452
1906
|
log.debug("Element selector activated");
|
|
1453
1907
|
const el = await elementSelector.activate();
|
|
1454
1908
|
if (el) {
|
|
@@ -1502,6 +1956,34 @@ function createInstance(config) {
|
|
|
1502
1956
|
offlineQueue.flush(apiClient).then((result) => {
|
|
1503
1957
|
if (result.sent > 0) log.info("Synced offline reports", { sent: result.sent });
|
|
1504
1958
|
});
|
|
1959
|
+
function applyRuntimeConfig(runtime) {
|
|
1960
|
+
if (runtime.enabled === false) {
|
|
1961
|
+
activeConfig = bootstrapConfig;
|
|
1962
|
+
clearCachedRuntimeConfig(config.projectId);
|
|
1963
|
+
syncCaptureModules();
|
|
1964
|
+
widget.updateConfig(activeConfig.widget);
|
|
1965
|
+
log.debug("Runtime SDK config disabled; using bootstrap config", { version: runtime.version });
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
activeConfig = mergeRuntimeConfig(activeConfig, runtime);
|
|
1969
|
+
syncCaptureModules();
|
|
1970
|
+
if (runtime.widget) widget.updateConfig(activeConfig.widget);
|
|
1971
|
+
log.debug("Applied runtime SDK config", { version: runtime.version });
|
|
1972
|
+
}
|
|
1973
|
+
if (config.runtimeConfig !== false) {
|
|
1974
|
+
const cached = readCachedRuntimeConfig(config.projectId);
|
|
1975
|
+
if (cached) applyRuntimeConfig(cached);
|
|
1976
|
+
apiClient.getSdkConfig().then((result) => {
|
|
1977
|
+
if (result.ok && result.data) {
|
|
1978
|
+
cacheRuntimeConfig(config.projectId, result.data);
|
|
1979
|
+
applyRuntimeConfig(result.data);
|
|
1980
|
+
} else if (result.error) {
|
|
1981
|
+
log.debug("Runtime SDK config unavailable", result.error);
|
|
1982
|
+
}
|
|
1983
|
+
}).catch((err) => {
|
|
1984
|
+
log.debug("Runtime SDK config fetch failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1505
1987
|
log.info("Initialized", { projectId: config.projectId });
|
|
1506
1988
|
async function submitReport(category, description, intent) {
|
|
1507
1989
|
const filterResult = preFilter.check(description);
|
|
@@ -1551,9 +2033,9 @@ function createInstance(config) {
|
|
|
1551
2033
|
description: scrubbedDescription,
|
|
1552
2034
|
userIntent: intent,
|
|
1553
2035
|
environment: captureEnvironment(),
|
|
1554
|
-
consoleLogs: consoleCap?.getEntries(),
|
|
1555
|
-
networkLogs: networkCap?.getEntries(),
|
|
1556
|
-
performanceMetrics: perfCap?.getMetrics(),
|
|
2036
|
+
consoleLogs: activeConfig.capture?.console === false ? void 0 : consoleCap?.getEntries(),
|
|
2037
|
+
networkLogs: activeConfig.capture?.network === false ? void 0 : networkCap?.getEntries(),
|
|
2038
|
+
performanceMetrics: activeConfig.capture?.performance === false ? void 0 : perfCap?.getMetrics(),
|
|
1557
2039
|
screenshotDataUrl: pendingScreenshot ?? void 0,
|
|
1558
2040
|
selectedElement: pendingElement ?? void 0,
|
|
1559
2041
|
metadata: {
|
|
@@ -1628,6 +2110,9 @@ function createInstance(config) {
|
|
|
1628
2110
|
close() {
|
|
1629
2111
|
widget.close();
|
|
1630
2112
|
},
|
|
2113
|
+
updateConfig(runtimeConfig) {
|
|
2114
|
+
applyRuntimeConfig(runtimeConfig);
|
|
2115
|
+
},
|
|
1631
2116
|
destroy() {
|
|
1632
2117
|
proactiveTriggers?.destroy();
|
|
1633
2118
|
proactiveManager?.reset();
|
|
@@ -1696,6 +2181,50 @@ function createInstance(config) {
|
|
|
1696
2181
|
};
|
|
1697
2182
|
return sdk;
|
|
1698
2183
|
}
|
|
2184
|
+
function mergeRuntimeConfig(config, runtime) {
|
|
2185
|
+
return {
|
|
2186
|
+
...config,
|
|
2187
|
+
widget: {
|
|
2188
|
+
...config.widget,
|
|
2189
|
+
...runtime.widget
|
|
2190
|
+
},
|
|
2191
|
+
capture: {
|
|
2192
|
+
...config.capture,
|
|
2193
|
+
...runtime.capture
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
function runtimeConfigCacheKey(projectId) {
|
|
2198
|
+
return `mushi:sdk-config:${projectId}`;
|
|
2199
|
+
}
|
|
2200
|
+
function readCachedRuntimeConfig(projectId) {
|
|
2201
|
+
if (typeof localStorage === "undefined") return null;
|
|
2202
|
+
try {
|
|
2203
|
+
const raw = localStorage.getItem(runtimeConfigCacheKey(projectId));
|
|
2204
|
+
if (!raw) return null;
|
|
2205
|
+
const parsed = JSON.parse(raw);
|
|
2206
|
+
return parsed.config ?? null;
|
|
2207
|
+
} catch {
|
|
2208
|
+
return null;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
function cacheRuntimeConfig(projectId, config) {
|
|
2212
|
+
if (typeof localStorage === "undefined") return;
|
|
2213
|
+
try {
|
|
2214
|
+
localStorage.setItem(runtimeConfigCacheKey(projectId), JSON.stringify({
|
|
2215
|
+
cachedAt: Date.now(),
|
|
2216
|
+
config
|
|
2217
|
+
}));
|
|
2218
|
+
} catch {
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
function clearCachedRuntimeConfig(projectId) {
|
|
2222
|
+
if (typeof localStorage === "undefined") return;
|
|
2223
|
+
try {
|
|
2224
|
+
localStorage.removeItem(runtimeConfigCacheKey(projectId));
|
|
2225
|
+
} catch {
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
1699
2228
|
function createNoopInstance() {
|
|
1700
2229
|
return {
|
|
1701
2230
|
report: () => {
|
|
@@ -1711,6 +2240,8 @@ function createNoopInstance() {
|
|
|
1711
2240
|
},
|
|
1712
2241
|
close: () => {
|
|
1713
2242
|
},
|
|
2243
|
+
updateConfig: () => {
|
|
2244
|
+
},
|
|
1714
2245
|
destroy: () => {
|
|
1715
2246
|
instance = null;
|
|
1716
2247
|
},
|