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