@mushi-mushi/web 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +1 -1
- package/README.md +25 -2
- package/SECURITY.md +1 -1
- package/dist/index.cjs +625 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +625 -217
- 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
|
-
|
|
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; }
|
|
237
247
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
/* Trigger button */
|
|
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;
|
|
691
|
+
}
|
|
692
|
+
.mushi-submit-arrow {
|
|
693
|
+
display: inline-block;
|
|
694
|
+
transition: transform 220ms ${easeStamp};
|
|
466
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",
|
|
@@ -557,6 +899,7 @@ var MushiWidget = class {
|
|
|
557
899
|
this.screenshotAttached = false;
|
|
558
900
|
this.elementSelected = false;
|
|
559
901
|
this.submitting = false;
|
|
902
|
+
this.submittedAt = null;
|
|
560
903
|
if (options?.category) {
|
|
561
904
|
this.selectedCategory = options.category;
|
|
562
905
|
this.selectedIntent = null;
|
|
@@ -587,6 +930,14 @@ var MushiWidget = class {
|
|
|
587
930
|
if (this.isOpen) this.render();
|
|
588
931
|
}
|
|
589
932
|
destroy() {
|
|
933
|
+
if (this.successTimer !== null) {
|
|
934
|
+
clearTimeout(this.successTimer);
|
|
935
|
+
this.successTimer = null;
|
|
936
|
+
}
|
|
937
|
+
if (this.autoCloseTimer !== null) {
|
|
938
|
+
clearTimeout(this.autoCloseTimer);
|
|
939
|
+
this.autoCloseTimer = null;
|
|
940
|
+
}
|
|
590
941
|
this.host.remove();
|
|
591
942
|
}
|
|
592
943
|
getTheme() {
|
|
@@ -608,6 +959,8 @@ var MushiWidget = class {
|
|
|
608
959
|
trigger.className = `mushi-trigger ${pos}`;
|
|
609
960
|
trigger.textContent = this.config.triggerText;
|
|
610
961
|
trigger.setAttribute("aria-label", t.widget.trigger);
|
|
962
|
+
trigger.setAttribute("aria-haspopup", "dialog");
|
|
963
|
+
trigger.setAttribute("aria-expanded", String(this.isOpen));
|
|
611
964
|
trigger.style.zIndex = String(this.config.zIndex);
|
|
612
965
|
trigger.addEventListener("click", () => {
|
|
613
966
|
if (this.isOpen) this.close();
|
|
@@ -617,6 +970,7 @@ var MushiWidget = class {
|
|
|
617
970
|
const panel = document.createElement("div");
|
|
618
971
|
panel.className = `mushi-panel ${pos}${this.isOpen ? " open" : " closed"}`;
|
|
619
972
|
panel.setAttribute("role", "dialog");
|
|
973
|
+
panel.setAttribute("aria-modal", "true");
|
|
620
974
|
panel.setAttribute("aria-label", t.widget.title);
|
|
621
975
|
panel.style.zIndex = String(this.config.zIndex + 1);
|
|
622
976
|
if (this.isOpen) {
|
|
@@ -638,37 +992,67 @@ var MushiWidget = class {
|
|
|
638
992
|
return this.renderSuccessStep();
|
|
639
993
|
}
|
|
640
994
|
}
|
|
641
|
-
|
|
995
|
+
/**
|
|
996
|
+
* Editorial masthead. Always carries:
|
|
997
|
+
* • the brand mark (虫 kanji on vermillion, "MUSHI" in mono above)
|
|
998
|
+
* • the page title (serif display)
|
|
999
|
+
* • the close affordance
|
|
1000
|
+
*
|
|
1001
|
+
* On sub-steps it additionally renders a back button (replacing the
|
|
1002
|
+
* "MUSHI" eyebrow with a "← BACK" mono link) and a step counter
|
|
1003
|
+
* ledger ("02 / 03") on the far right.
|
|
1004
|
+
*/
|
|
1005
|
+
renderHeader(opts) {
|
|
642
1006
|
const t = this.locale;
|
|
1007
|
+
const { title, showBack = false, step, eyebrow } = opts;
|
|
1008
|
+
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>`;
|
|
1009
|
+
const counterHtml = step ? `<span class="mushi-step-counter" aria-label="Step ${step} of ${TOTAL_STEPS}"><b>${pad2(step)}</b> / ${pad2(TOTAL_STEPS)}</span>` : "";
|
|
643
1010
|
return `
|
|
644
1011
|
<div class="mushi-header">
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
1012
|
+
<div class="mushi-header-mark" aria-hidden="true">\u866B</div>
|
|
1013
|
+
<div class="mushi-header-titles">
|
|
1014
|
+
${eyebrowHtml}
|
|
1015
|
+
<h3>${title}</h3>
|
|
1016
|
+
</div>
|
|
1017
|
+
<div class="mushi-header-meta">
|
|
1018
|
+
${counterHtml}
|
|
1019
|
+
<button type="button" class="mushi-close" data-action="close" aria-label="${t.widget.close}">\u2715</button>
|
|
1020
|
+
</div>
|
|
648
1021
|
</div>
|
|
649
1022
|
`;
|
|
650
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Numeral step indicator: "01 — 02 — 03", with the active step in
|
|
1026
|
+
* vermillion serif and completed steps struck through in mono.
|
|
1027
|
+
* Replaces the original three-dot indicator (a generic SaaS pattern).
|
|
1028
|
+
*/
|
|
1029
|
+
renderStepIndicator(currentStep) {
|
|
1030
|
+
const segments = [];
|
|
1031
|
+
for (let i = 1; i <= TOTAL_STEPS; i++) {
|
|
1032
|
+
const cls = i < currentStep ? "mushi-step-num done" : i === currentStep ? "mushi-step-num active" : "mushi-step-num";
|
|
1033
|
+
segments.push(`<span class="${cls}">${pad2(i)}</span>`);
|
|
1034
|
+
if (i < TOTAL_STEPS) segments.push('<span class="mushi-step-sep" aria-hidden="true"></span>');
|
|
1035
|
+
}
|
|
1036
|
+
return `<div class="mushi-step-indicator" aria-hidden="true">${segments.join("")}</div>`;
|
|
1037
|
+
}
|
|
651
1038
|
renderCategoryStep() {
|
|
652
1039
|
const t = this.locale;
|
|
653
1040
|
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>
|
|
1041
|
+
<button type="button" class="mushi-option-btn" data-category="${id}" role="radio" aria-checked="false">
|
|
1042
|
+
<span class="mushi-option-icon" aria-hidden="true">${CATEGORY_ICONS[id]}</span>
|
|
656
1043
|
<div class="mushi-option-text">
|
|
657
1044
|
<span class="mushi-option-label">${t.step1.categories[id]}</span>
|
|
658
1045
|
<span class="mushi-option-desc">${t.step1.categoryDescriptions[id]}</span>
|
|
659
1046
|
</div>
|
|
1047
|
+
<span class="mushi-option-arrow" aria-hidden="true">\u2192</span>
|
|
660
1048
|
</button>
|
|
661
1049
|
`).join("");
|
|
662
1050
|
return `
|
|
663
|
-
${this.renderHeader(t.step1.heading)}
|
|
1051
|
+
${this.renderHeader({ title: t.step1.heading, step: STEP_NUMBER.category })}
|
|
664
1052
|
<div class="mushi-body" role="radiogroup" aria-label="${t.step1.heading}">
|
|
665
1053
|
${categories}
|
|
666
1054
|
</div>
|
|
667
|
-
|
|
668
|
-
<span class="mushi-dot active"></span>
|
|
669
|
-
<span class="mushi-dot"></span>
|
|
670
|
-
<span class="mushi-dot"></span>
|
|
671
|
-
</div>
|
|
1055
|
+
${this.renderStepIndicator(STEP_NUMBER.category)}
|
|
672
1056
|
`;
|
|
673
1057
|
}
|
|
674
1058
|
renderIntentStep() {
|
|
@@ -676,32 +1060,28 @@ var MushiWidget = class {
|
|
|
676
1060
|
const cat = this.selectedCategory;
|
|
677
1061
|
const intents = t.step2.intents[cat] || [];
|
|
678
1062
|
const options = intents.map((intent) => `
|
|
679
|
-
<button class="mushi-intent-btn" data-intent="${intent}">
|
|
1063
|
+
<button type="button" class="mushi-intent-btn" data-intent="${intent}">
|
|
680
1064
|
${intent}
|
|
681
1065
|
</button>
|
|
682
1066
|
`).join("");
|
|
683
1067
|
return `
|
|
684
|
-
${this.renderHeader(t.step2.heading, true)}
|
|
1068
|
+
${this.renderHeader({ title: t.step2.heading, showBack: true, step: STEP_NUMBER.intent })}
|
|
685
1069
|
<div class="mushi-body">
|
|
686
1070
|
<div class="mushi-selected-category">
|
|
687
|
-
<span>${CATEGORY_ICONS[cat]}</span>
|
|
1071
|
+
<span aria-hidden="true">${CATEGORY_ICONS[cat]}</span>
|
|
688
1072
|
<span>${t.step1.categories[cat]}</span>
|
|
689
1073
|
</div>
|
|
690
1074
|
<div class="mushi-intents">
|
|
691
1075
|
${options}
|
|
692
1076
|
</div>
|
|
693
1077
|
</div>
|
|
694
|
-
|
|
695
|
-
<span class="mushi-dot done"></span>
|
|
696
|
-
<span class="mushi-dot active"></span>
|
|
697
|
-
<span class="mushi-dot"></span>
|
|
698
|
-
</div>
|
|
1078
|
+
${this.renderStepIndicator(STEP_NUMBER.intent)}
|
|
699
1079
|
`;
|
|
700
1080
|
}
|
|
701
1081
|
renderDetailsStep() {
|
|
702
1082
|
const t = this.locale;
|
|
703
1083
|
return `
|
|
704
|
-
${this.renderHeader(t.step3.heading, true)}
|
|
1084
|
+
${this.renderHeader({ title: t.step3.heading, showBack: true, step: STEP_NUMBER.details })}
|
|
705
1085
|
<div class="mushi-body">
|
|
706
1086
|
<textarea
|
|
707
1087
|
class="mushi-textarea"
|
|
@@ -711,35 +1091,47 @@ var MushiWidget = class {
|
|
|
711
1091
|
autofocus
|
|
712
1092
|
></textarea>
|
|
713
1093
|
<div class="mushi-attachments">
|
|
714
|
-
<button class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
1094
|
+
<button type="button" class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
715
1095
|
\u{1F4F8} ${this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton}
|
|
716
1096
|
</button>
|
|
717
|
-
<button class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
1097
|
+
<button type="button" class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
718
1098
|
\u{1F3AF} ${this.elementSelected ? t.step3.elementSelected : t.step3.elementButton}
|
|
719
1099
|
</button>
|
|
720
1100
|
</div>
|
|
721
1101
|
<div class="mushi-error" style="display:none" role="alert"></div>
|
|
722
1102
|
</div>
|
|
723
1103
|
<div class="mushi-footer">
|
|
724
|
-
<
|
|
725
|
-
|
|
1104
|
+
<span class="mushi-footer-hint" aria-hidden="true">\u2318 + ENTER \u2192 send</span>
|
|
1105
|
+
<button type="button" class="mushi-submit" data-action="submit"${this.submitting ? " disabled" : ""}>
|
|
1106
|
+
<span>${this.submitting ? t.widget.submitting : t.widget.submit}</span>
|
|
1107
|
+
<span class="mushi-submit-arrow" aria-hidden="true">\u2192</span>
|
|
726
1108
|
</button>
|
|
727
1109
|
</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>
|
|
1110
|
+
${this.renderStepIndicator(STEP_NUMBER.details)}
|
|
733
1111
|
`;
|
|
734
1112
|
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Editorial success state: 朱印-style red stamp ring with the kanji
|
|
1115
|
+
* 受 ("received") at its centre, the localised "thank you" string
|
|
1116
|
+
* in serif below, and a mono ledger receipt ("REPORT · HH:MM:SS").
|
|
1117
|
+
* The ring + label animations are defined in styles.ts so this stays
|
|
1118
|
+
* pure markup and `prefers-reduced-motion` flips them to the final
|
|
1119
|
+
* frame instantly.
|
|
1120
|
+
*/
|
|
735
1121
|
renderSuccessStep() {
|
|
736
1122
|
const t = this.locale;
|
|
1123
|
+
const stamp = this.submittedAt ?? /* @__PURE__ */ new Date();
|
|
1124
|
+
const time = stamp.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
|
|
737
1125
|
return `
|
|
738
|
-
${this.renderHeader(t.widget.title)}
|
|
1126
|
+
${this.renderHeader({ title: t.widget.title, eyebrow: "Mushi \xB7 Receipt" })}
|
|
739
1127
|
<div class="mushi-body">
|
|
740
1128
|
<div class="mushi-success">
|
|
741
|
-
<div class="mushi-success-
|
|
742
|
-
|
|
1129
|
+
<div class="mushi-success-stamp" aria-hidden="true">
|
|
1130
|
+
<svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"><circle cx="50" cy="50" r="44"/></svg>
|
|
1131
|
+
<span class="mushi-success-stamp-label">\u53D7</span>
|
|
1132
|
+
</div>
|
|
1133
|
+
<div class="mushi-success-headline">${t.widget.submitted}</div>
|
|
1134
|
+
<div class="mushi-success-meta">REPORT \xB7 ${time}</div>
|
|
743
1135
|
</div>
|
|
744
1136
|
</div>
|
|
745
1137
|
`;
|
|
@@ -777,7 +1169,7 @@ var MushiWidget = class {
|
|
|
777
1169
|
panel.querySelector('[data-action="element"]')?.addEventListener("click", () => {
|
|
778
1170
|
this.callbacks.onElementSelectorRequest?.();
|
|
779
1171
|
});
|
|
780
|
-
|
|
1172
|
+
const submitReport = () => {
|
|
781
1173
|
const textarea = panel.querySelector(".mushi-textarea");
|
|
782
1174
|
const description = textarea?.value?.trim() ?? "";
|
|
783
1175
|
const errorEl = panel.querySelector(".mushi-error");
|
|
@@ -790,27 +1182,43 @@ var MushiWidget = class {
|
|
|
790
1182
|
return;
|
|
791
1183
|
}
|
|
792
1184
|
this.submitting = true;
|
|
1185
|
+
this.submittedAt = /* @__PURE__ */ new Date();
|
|
793
1186
|
this.render();
|
|
794
1187
|
this.callbacks.onSubmit({
|
|
795
1188
|
category: this.selectedCategory,
|
|
796
1189
|
description,
|
|
797
1190
|
intent: this.selectedIntent ?? void 0
|
|
798
1191
|
});
|
|
799
|
-
setTimeout(() => {
|
|
1192
|
+
this.successTimer = setTimeout(() => {
|
|
1193
|
+
this.successTimer = null;
|
|
800
1194
|
this.submitting = false;
|
|
801
1195
|
this.step = "success";
|
|
802
1196
|
this.render();
|
|
803
|
-
setTimeout(() => {
|
|
1197
|
+
this.autoCloseTimer = setTimeout(() => {
|
|
1198
|
+
this.autoCloseTimer = null;
|
|
804
1199
|
if (this.step === "success") this.close();
|
|
805
|
-
},
|
|
1200
|
+
}, 2800);
|
|
806
1201
|
}, 500);
|
|
807
|
-
}
|
|
1202
|
+
};
|
|
1203
|
+
panel.querySelector('[data-action="submit"]')?.addEventListener("click", submitReport);
|
|
808
1204
|
panel.addEventListener("keydown", (e) => {
|
|
809
|
-
if (e.key === "Escape")
|
|
1205
|
+
if (e.key === "Escape") {
|
|
1206
|
+
this.close();
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (this.step === "details" && isSubmitShortcut(e)) {
|
|
1210
|
+
e.preventDefault();
|
|
1211
|
+
submitReport();
|
|
1212
|
+
}
|
|
810
1213
|
});
|
|
811
1214
|
}
|
|
812
1215
|
trapFocus(panel) {
|
|
813
1216
|
requestAnimationFrame(() => {
|
|
1217
|
+
const textarea = panel.querySelector("textarea");
|
|
1218
|
+
if (textarea) {
|
|
1219
|
+
textarea.focus();
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
814
1222
|
const focusable = panel.querySelectorAll("button, textarea, [tabindex]");
|
|
815
1223
|
if (focusable.length > 0) focusable[0].focus();
|
|
816
1224
|
});
|