@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.js
CHANGED
|
@@ -216,294 +216,607 @@ function getAvailableLocales() {
|
|
|
216
216
|
// src/styles.ts
|
|
217
217
|
function getWidgetStyles(theme) {
|
|
218
218
|
const isDark = theme === "dark";
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
219
|
+
const paper = isDark ? "#0F0E0C" : "#F8F4ED";
|
|
220
|
+
const ink = isDark ? "#F2EBDD" : "#0E0D0B";
|
|
221
|
+
const inkMuted = isDark ? "#928B7E" : "#5C5852";
|
|
222
|
+
const inkFaint = isDark ? "#5A5650" : "#9A9489";
|
|
223
|
+
const rule = isDark ? "rgba(242,235,221,0.10)" : "rgba(14,13,11,0.10)";
|
|
224
|
+
const ruleStrong = isDark ? "rgba(242,235,221,0.18)" : "rgba(14,13,11,0.16)";
|
|
225
|
+
const vermillion = isDark ? "#FF5A47" : "#E03C2C";
|
|
226
|
+
const vermillionWash = isDark ? "rgba(255,90,71,0.12)" : "rgba(224,60,44,0.08)";
|
|
227
|
+
const vermillionInk = isDark ? "#FFE5E0" : "#7A1F15";
|
|
228
|
+
const fontDisplay = `'Iowan Old Style', 'Palatino Linotype', 'Palatino', 'Book Antiqua', 'Cambria', Georgia, 'Times New Roman', serif`;
|
|
229
|
+
const fontBody = `system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Display', 'Segoe UI', sans-serif`;
|
|
230
|
+
const fontMono = `ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, 'Liberation Mono', monospace`;
|
|
231
|
+
const easeStamp = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
227
232
|
return `
|
|
228
233
|
:host {
|
|
229
234
|
all: initial;
|
|
230
|
-
font-family:
|
|
235
|
+
font-family: ${fontBody};
|
|
231
236
|
font-size: 14px;
|
|
232
|
-
line-height: 1.
|
|
233
|
-
color: ${
|
|
234
|
-
|
|
237
|
+
line-height: 1.55;
|
|
238
|
+
color: ${ink};
|
|
239
|
+
-webkit-font-smoothing: antialiased;
|
|
240
|
+
-moz-osx-font-smoothing: grayscale;
|
|
241
|
+
font-feature-settings: 'ss01', 'cv11'; /* nicer system-ui glyphs where supported */
|
|
242
|
+
}
|
|
243
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
244
|
+
button { font-family: inherit; }
|
|
235
245
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
/* Trigger button */
|
|
246
|
+
/* \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
247
|
+
A small "stamp card" \u2014 soft rounded square (4px radius), paper
|
|
248
|
+
background, vermillion bottom edge that reads as the inked face
|
|
249
|
+
of a real \u5370\u9451. A pulsing dot in the top-right hints there's a
|
|
250
|
+
channel here without needing a notification badge. */
|
|
243
251
|
.mushi-trigger {
|
|
244
252
|
position: fixed;
|
|
245
|
-
width:
|
|
246
|
-
height:
|
|
247
|
-
border
|
|
248
|
-
border:
|
|
253
|
+
width: 52px;
|
|
254
|
+
height: 52px;
|
|
255
|
+
border: 1px solid ${ruleStrong};
|
|
256
|
+
border-radius: 4px;
|
|
257
|
+
background: ${paper};
|
|
258
|
+
color: ${ink};
|
|
249
259
|
cursor: pointer;
|
|
250
260
|
display: flex;
|
|
251
261
|
align-items: center;
|
|
252
262
|
justify-content: center;
|
|
263
|
+
font-family: ${fontDisplay};
|
|
253
264
|
font-size: 22px;
|
|
254
|
-
|
|
255
|
-
box-shadow:
|
|
256
|
-
|
|
265
|
+
line-height: 1;
|
|
266
|
+
box-shadow:
|
|
267
|
+
0 1px 0 ${rule},
|
|
268
|
+
0 6px 14px -8px rgba(14,13,11,0.35),
|
|
269
|
+
inset 0 -3px 0 ${vermillion};
|
|
270
|
+
transition: transform 200ms ${easeStamp}, box-shadow 200ms ${easeStamp};
|
|
271
|
+
overflow: visible;
|
|
272
|
+
isolation: isolate;
|
|
273
|
+
}
|
|
274
|
+
.mushi-trigger::after {
|
|
275
|
+
content: '';
|
|
276
|
+
position: absolute;
|
|
277
|
+
top: 6px;
|
|
278
|
+
right: 6px;
|
|
279
|
+
width: 6px;
|
|
280
|
+
height: 6px;
|
|
281
|
+
border-radius: 50%;
|
|
282
|
+
background: ${vermillion};
|
|
283
|
+
box-shadow: 0 0 0 0 ${vermillion};
|
|
284
|
+
animation: mushi-pulse 2.4s ${easeStamp} infinite;
|
|
257
285
|
}
|
|
258
286
|
.mushi-trigger:hover {
|
|
259
|
-
transform:
|
|
260
|
-
box-shadow:
|
|
287
|
+
transform: translateY(-2px) rotate(-1.5deg);
|
|
288
|
+
box-shadow:
|
|
289
|
+
0 1px 0 ${rule},
|
|
290
|
+
0 14px 24px -10px rgba(14,13,11,0.45),
|
|
291
|
+
inset 0 -3px 0 ${vermillion};
|
|
292
|
+
}
|
|
293
|
+
.mushi-trigger:active {
|
|
294
|
+
transform: translateY(0) rotate(0);
|
|
295
|
+
box-shadow:
|
|
296
|
+
0 1px 0 ${rule},
|
|
297
|
+
0 2px 4px -2px rgba(14,13,11,0.35),
|
|
298
|
+
inset 0 -2px 0 ${vermillion};
|
|
299
|
+
}
|
|
300
|
+
.mushi-trigger:focus-visible {
|
|
301
|
+
outline: 2px solid ${vermillion};
|
|
302
|
+
outline-offset: 3px;
|
|
303
|
+
}
|
|
304
|
+
.mushi-trigger.bottom-right { bottom: 24px; right: 24px; }
|
|
305
|
+
.mushi-trigger.bottom-left { bottom: 24px; left: 24px; }
|
|
306
|
+
.mushi-trigger.top-right { top: 24px; right: 24px; }
|
|
307
|
+
.mushi-trigger.top-left { top: 24px; left: 24px; }
|
|
308
|
+
|
|
309
|
+
@keyframes mushi-pulse {
|
|
310
|
+
0% { box-shadow: 0 0 0 0 ${vermillion}; opacity: 1; }
|
|
311
|
+
70% { box-shadow: 0 0 0 8px rgba(224,60,44,0); opacity: 0.5; }
|
|
312
|
+
100% { box-shadow: 0 0 0 0 rgba(224,60,44,0); opacity: 1; }
|
|
261
313
|
}
|
|
262
|
-
.mushi-trigger.bottom-right { bottom: 20px; right: 20px; }
|
|
263
|
-
.mushi-trigger.bottom-left { bottom: 20px; left: 20px; }
|
|
264
|
-
.mushi-trigger.top-right { top: 20px; right: 20px; }
|
|
265
|
-
.mushi-trigger.top-left { top: 20px; left: 20px; }
|
|
266
314
|
|
|
267
|
-
/* Panel
|
|
315
|
+
/* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
316
|
+
Paper-card. Sharper corners (6px) than typical SaaS modals
|
|
317
|
+
(which default to 12-16px and read as plastic). Two-layer shadow:
|
|
318
|
+
one hairline that sells the paper edge, one diffuse that lifts
|
|
319
|
+
the panel off the underlying app. No backdrop-filter \u2014 we want
|
|
320
|
+
the widget to feel like it sits ON the page, not blur INTO it. */
|
|
268
321
|
.mushi-panel {
|
|
269
322
|
position: fixed;
|
|
270
|
-
width:
|
|
271
|
-
max-
|
|
272
|
-
|
|
273
|
-
background: ${
|
|
274
|
-
|
|
275
|
-
border:
|
|
323
|
+
width: 384px;
|
|
324
|
+
max-width: calc(100vw - 32px);
|
|
325
|
+
max-height: min(640px, calc(100vh - 120px));
|
|
326
|
+
background: ${paper};
|
|
327
|
+
border: 1px solid ${ruleStrong};
|
|
328
|
+
border-radius: 6px;
|
|
329
|
+
box-shadow:
|
|
330
|
+
0 1px 0 ${rule},
|
|
331
|
+
0 24px 56px -20px rgba(14,13,11,0.30),
|
|
332
|
+
0 8px 16px -8px rgba(14,13,11,0.20);
|
|
276
333
|
overflow: hidden;
|
|
277
334
|
display: flex;
|
|
278
335
|
flex-direction: column;
|
|
336
|
+
transform-origin: var(--mushi-origin, bottom right);
|
|
279
337
|
}
|
|
280
|
-
.mushi-panel.open {
|
|
281
|
-
animation: mushi-slide-in 0.25s ease forwards;
|
|
282
|
-
}
|
|
338
|
+
.mushi-panel.open { animation: mushi-stamp-in 320ms ${easeStamp} both; }
|
|
283
339
|
.mushi-panel.closed { display: none; }
|
|
284
|
-
.mushi-panel.bottom-right { bottom:
|
|
285
|
-
.mushi-panel.bottom-left { bottom:
|
|
286
|
-
.mushi-panel.top-right { top:
|
|
287
|
-
.mushi-panel.top-left { top:
|
|
340
|
+
.mushi-panel.bottom-right { bottom: 88px; right: 24px; --mushi-origin: bottom right; }
|
|
341
|
+
.mushi-panel.bottom-left { bottom: 88px; left: 24px; --mushi-origin: bottom left; }
|
|
342
|
+
.mushi-panel.top-right { top: 88px; right: 24px; --mushi-origin: top right; }
|
|
343
|
+
.mushi-panel.top-left { top: 88px; left: 24px; --mushi-origin: top left; }
|
|
288
344
|
|
|
289
|
-
@keyframes mushi-
|
|
290
|
-
|
|
291
|
-
|
|
345
|
+
@keyframes mushi-stamp-in {
|
|
346
|
+
0% { opacity: 0; transform: scale(0.94) translateY(6px); }
|
|
347
|
+
60% { opacity: 1; }
|
|
348
|
+
100% { opacity: 1; transform: scale(1) translateY(0); }
|
|
292
349
|
}
|
|
293
350
|
|
|
294
|
-
/* Header
|
|
351
|
+
/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
352
|
+
Editorial masthead: small mono eyebrow ("MUSHI / REPORT") on top,
|
|
353
|
+
serif display headline below, mono step counter on the far right.
|
|
354
|
+
A single hairline separates header from body \u2014 no card stacking. */
|
|
295
355
|
.mushi-header {
|
|
296
|
-
padding: 14px
|
|
297
|
-
border-bottom: 1px solid ${
|
|
298
|
-
display:
|
|
356
|
+
padding: 18px 20px 14px;
|
|
357
|
+
border-bottom: 1px solid ${rule};
|
|
358
|
+
display: grid;
|
|
359
|
+
grid-template-columns: auto 1fr auto;
|
|
360
|
+
align-items: end;
|
|
361
|
+
gap: 12px;
|
|
362
|
+
}
|
|
363
|
+
.mushi-header-mark {
|
|
364
|
+
display: inline-flex;
|
|
299
365
|
align-items: center;
|
|
300
|
-
|
|
366
|
+
justify-content: center;
|
|
367
|
+
width: 22px;
|
|
368
|
+
height: 22px;
|
|
369
|
+
border-radius: 3px;
|
|
370
|
+
background: ${vermillion};
|
|
371
|
+
color: #FAF7F0;
|
|
372
|
+
font-family: ${fontDisplay};
|
|
373
|
+
font-size: 14px;
|
|
374
|
+
font-weight: 600;
|
|
375
|
+
line-height: 1;
|
|
376
|
+
letter-spacing: -0.02em;
|
|
377
|
+
transform: rotate(-3deg);
|
|
378
|
+
flex-shrink: 0;
|
|
379
|
+
}
|
|
380
|
+
.mushi-header-titles {
|
|
381
|
+
min-width: 0;
|
|
382
|
+
display: flex;
|
|
383
|
+
flex-direction: column;
|
|
384
|
+
gap: 2px;
|
|
385
|
+
}
|
|
386
|
+
.mushi-header-eyebrow {
|
|
387
|
+
font-family: ${fontMono};
|
|
388
|
+
font-size: 10px;
|
|
389
|
+
letter-spacing: 0.18em;
|
|
390
|
+
text-transform: uppercase;
|
|
391
|
+
color: ${inkMuted};
|
|
301
392
|
}
|
|
302
393
|
.mushi-header h3 {
|
|
303
|
-
font-
|
|
394
|
+
font-family: ${fontDisplay};
|
|
395
|
+
font-size: 19px;
|
|
396
|
+
font-weight: 500;
|
|
397
|
+
line-height: 1.15;
|
|
398
|
+
letter-spacing: -0.01em;
|
|
399
|
+
color: ${ink};
|
|
400
|
+
}
|
|
401
|
+
.mushi-header-meta {
|
|
402
|
+
align-self: start;
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 6px;
|
|
406
|
+
}
|
|
407
|
+
.mushi-step-counter {
|
|
408
|
+
font-family: ${fontMono};
|
|
409
|
+
font-size: 11px;
|
|
410
|
+
color: ${inkMuted};
|
|
411
|
+
letter-spacing: 0.06em;
|
|
412
|
+
tab-size: 2ch;
|
|
413
|
+
padding-top: 2px;
|
|
414
|
+
}
|
|
415
|
+
.mushi-step-counter b {
|
|
304
416
|
font-weight: 600;
|
|
305
|
-
|
|
417
|
+
color: ${ink};
|
|
306
418
|
}
|
|
307
419
|
.mushi-close, .mushi-back {
|
|
308
420
|
background: none;
|
|
309
421
|
border: none;
|
|
310
422
|
cursor: pointer;
|
|
311
|
-
font-size: 18px;
|
|
312
|
-
color: ${textMuted};
|
|
313
423
|
padding: 4px;
|
|
314
|
-
|
|
424
|
+
color: ${inkMuted};
|
|
425
|
+
font-family: ${fontBody};
|
|
426
|
+
font-size: 14px;
|
|
315
427
|
line-height: 1;
|
|
428
|
+
border-radius: 3px;
|
|
429
|
+
transition: color 150ms ${easeStamp};
|
|
316
430
|
}
|
|
317
|
-
.mushi-close:hover, .mushi-back:hover {
|
|
318
|
-
|
|
431
|
+
.mushi-close:hover, .mushi-back:hover { color: ${vermillion}; }
|
|
432
|
+
.mushi-close:focus-visible, .mushi-back:focus-visible {
|
|
433
|
+
outline: 1.5px solid ${vermillion};
|
|
434
|
+
outline-offset: 2px;
|
|
319
435
|
}
|
|
320
436
|
|
|
321
|
-
/* Body
|
|
437
|
+
/* \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
438
|
+
Generous left/right padding (22px) so type breathes. Vertical
|
|
439
|
+
padding tighter at top because the header rule already creates
|
|
440
|
+
breathing room. */
|
|
322
441
|
.mushi-body {
|
|
323
|
-
padding:
|
|
442
|
+
padding: 8px 22px 16px;
|
|
324
443
|
overflow-y: auto;
|
|
325
444
|
flex: 1;
|
|
445
|
+
scrollbar-width: thin;
|
|
446
|
+
scrollbar-color: ${inkFaint} transparent;
|
|
326
447
|
}
|
|
448
|
+
.mushi-body::-webkit-scrollbar { width: 6px; }
|
|
449
|
+
.mushi-body::-webkit-scrollbar-thumb { background: ${inkFaint}; border-radius: 3px; }
|
|
327
450
|
|
|
328
|
-
/* Step 1:
|
|
451
|
+
/* \u2500\u2500 Step 1: Categories as a contents-page list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
452
|
+
No boxes. Hairline rules between rows. Hovering a row pulls a
|
|
453
|
+
vermillion arrow in from the right and tints the row label \u2014
|
|
454
|
+
reads like flipping through an index card. */
|
|
329
455
|
.mushi-option-btn {
|
|
330
|
-
display:
|
|
456
|
+
display: grid;
|
|
457
|
+
grid-template-columns: auto 1fr auto;
|
|
331
458
|
align-items: center;
|
|
332
|
-
gap:
|
|
459
|
+
gap: 14px;
|
|
333
460
|
width: 100%;
|
|
334
|
-
padding:
|
|
335
|
-
|
|
336
|
-
border-
|
|
337
|
-
|
|
338
|
-
background: ${bgMuted};
|
|
461
|
+
padding: 14px 0;
|
|
462
|
+
border: none;
|
|
463
|
+
border-bottom: 1px solid ${rule};
|
|
464
|
+
background: transparent;
|
|
339
465
|
cursor: pointer;
|
|
340
|
-
font-size: 14px;
|
|
341
466
|
color: inherit;
|
|
342
467
|
text-align: left;
|
|
343
|
-
transition:
|
|
344
|
-
|
|
345
|
-
.mushi-option-btn:hover {
|
|
346
|
-
border-color: ${isDark ? "#a78bfa" : accent};
|
|
347
|
-
background: ${accentBgLight};
|
|
348
|
-
transform: translateX(2px);
|
|
468
|
+
transition: padding 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
469
|
+
position: relative;
|
|
349
470
|
}
|
|
471
|
+
.mushi-option-btn:last-child { border-bottom: none; }
|
|
472
|
+
.mushi-option-btn:hover { padding-left: 6px; color: ${vermillion}; }
|
|
473
|
+
.mushi-option-btn:hover .mushi-option-arrow { opacity: 1; transform: translateX(0); color: ${vermillion}; }
|
|
350
474
|
.mushi-option-btn:focus-visible {
|
|
351
|
-
outline:
|
|
352
|
-
|
|
475
|
+
outline: none;
|
|
476
|
+
padding-left: 6px;
|
|
477
|
+
box-shadow: inset 2px 0 0 ${vermillion};
|
|
478
|
+
}
|
|
479
|
+
.mushi-option-icon {
|
|
480
|
+
font-size: 18px;
|
|
481
|
+
line-height: 1;
|
|
482
|
+
flex-shrink: 0;
|
|
483
|
+
filter: ${isDark ? "none" : "grayscale(0.15)"};
|
|
484
|
+
}
|
|
485
|
+
.mushi-option-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
486
|
+
.mushi-option-label {
|
|
487
|
+
font-family: ${fontDisplay};
|
|
488
|
+
font-size: 16px;
|
|
489
|
+
font-weight: 500;
|
|
490
|
+
letter-spacing: -0.005em;
|
|
491
|
+
line-height: 1.2;
|
|
492
|
+
}
|
|
493
|
+
.mushi-option-desc {
|
|
494
|
+
font-size: 12px;
|
|
495
|
+
color: ${inkMuted};
|
|
496
|
+
letter-spacing: 0.005em;
|
|
497
|
+
}
|
|
498
|
+
.mushi-option-arrow {
|
|
499
|
+
font-family: ${fontMono};
|
|
500
|
+
font-size: 14px;
|
|
501
|
+
color: ${inkFaint};
|
|
502
|
+
opacity: 0;
|
|
503
|
+
transform: translateX(-4px);
|
|
504
|
+
transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
353
505
|
}
|
|
354
|
-
.mushi-option-icon { font-size: 20px; flex-shrink: 0; }
|
|
355
|
-
.mushi-option-text { display: flex; flex-direction: column; gap: 2px; }
|
|
356
|
-
.mushi-option-label { font-weight: 500; }
|
|
357
|
-
.mushi-option-desc { font-size: 12px; color: ${textMuted}; }
|
|
358
506
|
|
|
359
|
-
/* Step 2:
|
|
507
|
+
/* \u2500\u2500 Step 2: Selected-category breadcrumb + intent text-buttons \u2500
|
|
508
|
+
Breadcrumb is a thin chip with the kanji-stamp aesthetic carried
|
|
509
|
+
over (vermillion left rule). Intents are inline TEXT buttons
|
|
510
|
+
with vermillion underlines on hover \u2014 not pill-shaped chips,
|
|
511
|
+
which is the SaaS default and not what we are. */
|
|
360
512
|
.mushi-selected-category {
|
|
361
|
-
display: flex;
|
|
513
|
+
display: inline-flex;
|
|
362
514
|
align-items: center;
|
|
363
515
|
gap: 8px;
|
|
364
|
-
padding:
|
|
365
|
-
border-
|
|
366
|
-
background: ${
|
|
367
|
-
|
|
368
|
-
font-
|
|
369
|
-
|
|
370
|
-
|
|
516
|
+
padding: 6px 10px 6px 12px;
|
|
517
|
+
border-left: 2px solid ${vermillion};
|
|
518
|
+
background: ${vermillionWash};
|
|
519
|
+
color: ${vermillionInk};
|
|
520
|
+
font-family: ${fontMono};
|
|
521
|
+
font-size: 11px;
|
|
522
|
+
letter-spacing: 0.12em;
|
|
523
|
+
text-transform: uppercase;
|
|
524
|
+
margin: 4px 0 14px;
|
|
525
|
+
border-radius: 0 3px 3px 0;
|
|
526
|
+
}
|
|
527
|
+
.mushi-selected-category span:first-child { font-size: 14px; }
|
|
371
528
|
.mushi-intents {
|
|
372
529
|
display: flex;
|
|
373
|
-
flex-
|
|
374
|
-
gap:
|
|
530
|
+
flex-direction: column;
|
|
531
|
+
gap: 2px;
|
|
375
532
|
}
|
|
376
533
|
.mushi-intent-btn {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
534
|
+
display: flex;
|
|
535
|
+
align-items: center;
|
|
536
|
+
justify-content: space-between;
|
|
537
|
+
gap: 12px;
|
|
538
|
+
padding: 12px 0;
|
|
539
|
+
border: none;
|
|
540
|
+
border-bottom: 1px solid ${rule};
|
|
541
|
+
background: transparent;
|
|
381
542
|
cursor: pointer;
|
|
382
|
-
font-size: 13px;
|
|
383
543
|
color: inherit;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
background: ${accentBgLight};
|
|
544
|
+
text-align: left;
|
|
545
|
+
font-family: ${fontDisplay};
|
|
546
|
+
font-size: 15px;
|
|
547
|
+
transition: padding 220ms ${easeStamp}, color 220ms ${easeStamp};
|
|
389
548
|
}
|
|
549
|
+
.mushi-intent-btn::after {
|
|
550
|
+
content: '\u2192';
|
|
551
|
+
font-family: ${fontMono};
|
|
552
|
+
font-size: 13px;
|
|
553
|
+
color: ${inkFaint};
|
|
554
|
+
opacity: 0;
|
|
555
|
+
transform: translateX(-4px);
|
|
556
|
+
transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp};
|
|
557
|
+
}
|
|
558
|
+
.mushi-intent-btn:last-child { border-bottom: none; }
|
|
559
|
+
.mushi-intent-btn:hover { padding-left: 6px; color: ${vermillion}; }
|
|
560
|
+
.mushi-intent-btn:hover::after { opacity: 1; transform: translateX(0); color: ${vermillion}; }
|
|
390
561
|
.mushi-intent-btn:focus-visible {
|
|
391
|
-
outline:
|
|
392
|
-
|
|
562
|
+
outline: none;
|
|
563
|
+
padding-left: 6px;
|
|
564
|
+
box-shadow: inset 2px 0 0 ${vermillion};
|
|
393
565
|
}
|
|
394
566
|
|
|
395
|
-
/* Step 3:
|
|
567
|
+
/* \u2500\u2500 Step 3: Borderless textarea + minimal attach pills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
568
|
+
The textarea has no box around it \u2014 just a hairline underline
|
|
569
|
+
that turns vermillion on focus. Encourages writing rather than
|
|
570
|
+
form-filling. */
|
|
396
571
|
.mushi-textarea {
|
|
397
572
|
width: 100%;
|
|
398
|
-
min-height:
|
|
399
|
-
padding: 10px
|
|
400
|
-
border
|
|
401
|
-
border: 1px solid ${
|
|
402
|
-
background:
|
|
403
|
-
color:
|
|
404
|
-
font-family:
|
|
573
|
+
min-height: 96px;
|
|
574
|
+
padding: 8px 0 10px;
|
|
575
|
+
border: none;
|
|
576
|
+
border-bottom: 1px solid ${ruleStrong};
|
|
577
|
+
background: transparent;
|
|
578
|
+
color: ${ink};
|
|
579
|
+
font-family: ${fontBody};
|
|
405
580
|
font-size: 14px;
|
|
581
|
+
line-height: 1.5;
|
|
406
582
|
resize: vertical;
|
|
407
583
|
outline: none;
|
|
408
|
-
transition: border-color
|
|
584
|
+
transition: border-color 200ms ${easeStamp};
|
|
409
585
|
}
|
|
410
|
-
.mushi-textarea
|
|
411
|
-
|
|
412
|
-
|
|
586
|
+
.mushi-textarea::placeholder {
|
|
587
|
+
color: ${inkFaint};
|
|
588
|
+
font-style: italic;
|
|
413
589
|
}
|
|
590
|
+
.mushi-textarea:focus { border-bottom-color: ${vermillion}; }
|
|
414
591
|
|
|
415
592
|
.mushi-attachments {
|
|
416
593
|
display: flex;
|
|
417
|
-
|
|
418
|
-
|
|
594
|
+
flex-wrap: wrap;
|
|
595
|
+
gap: 6px;
|
|
596
|
+
margin-top: 12px;
|
|
419
597
|
}
|
|
420
598
|
.mushi-attach-btn {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
599
|
+
display: inline-flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
gap: 6px;
|
|
602
|
+
padding: 5px 10px;
|
|
603
|
+
border: 1px solid ${ruleStrong};
|
|
604
|
+
border-radius: 3px;
|
|
605
|
+
background: transparent;
|
|
606
|
+
color: ${inkMuted};
|
|
607
|
+
font-family: ${fontMono};
|
|
608
|
+
font-size: 11px;
|
|
609
|
+
letter-spacing: 0.04em;
|
|
610
|
+
text-transform: uppercase;
|
|
425
611
|
cursor: pointer;
|
|
426
|
-
|
|
427
|
-
color: ${textMuted};
|
|
428
|
-
transition: border-color 0.15s, color 0.15s;
|
|
612
|
+
transition: color 180ms ${easeStamp}, border-color 180ms ${easeStamp}, background 180ms ${easeStamp};
|
|
429
613
|
}
|
|
430
614
|
.mushi-attach-btn:hover {
|
|
431
|
-
|
|
432
|
-
color:
|
|
615
|
+
color: ${ink};
|
|
616
|
+
border-color: ${ink};
|
|
433
617
|
}
|
|
434
618
|
.mushi-attach-btn.active {
|
|
435
|
-
|
|
436
|
-
color: ${
|
|
437
|
-
background: ${
|
|
619
|
+
color: ${vermillion};
|
|
620
|
+
border-color: ${vermillion};
|
|
621
|
+
background: ${vermillionWash};
|
|
622
|
+
}
|
|
623
|
+
.mushi-attach-btn:focus-visible {
|
|
624
|
+
outline: 2px solid ${vermillion};
|
|
625
|
+
outline-offset: 2px;
|
|
438
626
|
}
|
|
439
627
|
|
|
440
|
-
/* Footer
|
|
628
|
+
/* \u2500\u2500 Footer + submit (vermillion stamp) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
629
|
+
Submit button is the heaviest visual moment in the widget \u2014
|
|
630
|
+
vermillion fill, mono-caps label, send arrow. Holds an ink-
|
|
631
|
+
bloom pseudo-element that animates outward when pressed. */
|
|
441
632
|
.mushi-footer {
|
|
442
|
-
padding:
|
|
443
|
-
border-top: 1px solid ${
|
|
633
|
+
padding: 14px 22px 16px;
|
|
634
|
+
border-top: 1px solid ${rule};
|
|
444
635
|
display: flex;
|
|
445
636
|
align-items: center;
|
|
446
|
-
justify-content:
|
|
637
|
+
justify-content: space-between;
|
|
638
|
+
gap: 12px;
|
|
639
|
+
}
|
|
640
|
+
.mushi-footer-hint {
|
|
641
|
+
font-family: ${fontMono};
|
|
642
|
+
font-size: 10px;
|
|
643
|
+
letter-spacing: 0.10em;
|
|
644
|
+
text-transform: uppercase;
|
|
645
|
+
color: ${inkFaint};
|
|
447
646
|
}
|
|
448
647
|
.mushi-submit {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
648
|
+
position: relative;
|
|
649
|
+
display: inline-flex;
|
|
650
|
+
align-items: center;
|
|
651
|
+
gap: 8px;
|
|
652
|
+
padding: 10px 18px;
|
|
653
|
+
border: 1px solid ${vermillion};
|
|
654
|
+
border-radius: 3px;
|
|
655
|
+
background: ${vermillion};
|
|
656
|
+
color: #FAF7F0;
|
|
657
|
+
font-family: ${fontMono};
|
|
658
|
+
font-size: 11px;
|
|
659
|
+
letter-spacing: 0.16em;
|
|
660
|
+
text-transform: uppercase;
|
|
456
661
|
cursor: pointer;
|
|
457
|
-
|
|
662
|
+
overflow: hidden;
|
|
663
|
+
transition: transform 180ms ${easeStamp}, box-shadow 180ms ${easeStamp};
|
|
664
|
+
box-shadow: 0 2px 0 ${isDark ? "#7A1F15" : "#9A2A1E"};
|
|
665
|
+
}
|
|
666
|
+
.mushi-submit::after {
|
|
667
|
+
content: '';
|
|
668
|
+
position: absolute;
|
|
669
|
+
inset: 0;
|
|
670
|
+
background: radial-gradient(circle at center, rgba(255,255,255,0.35) 0%, transparent 60%);
|
|
671
|
+
opacity: 0;
|
|
672
|
+
transform: scale(0.4);
|
|
673
|
+
transition: opacity 280ms ${easeStamp}, transform 380ms ${easeStamp};
|
|
674
|
+
pointer-events: none;
|
|
675
|
+
}
|
|
676
|
+
.mushi-submit:hover {
|
|
677
|
+
transform: translateY(-1px);
|
|
678
|
+
box-shadow: 0 3px 0 ${isDark ? "#7A1F15" : "#9A2A1E"};
|
|
679
|
+
}
|
|
680
|
+
.mushi-submit:hover::after { opacity: 1; transform: scale(1.4); }
|
|
681
|
+
.mushi-submit:active { transform: translateY(1px); box-shadow: 0 1px 0 ${isDark ? "#7A1F15" : "#9A2A1E"}; }
|
|
682
|
+
.mushi-submit:disabled {
|
|
683
|
+
cursor: wait;
|
|
684
|
+
opacity: 0.7;
|
|
458
685
|
}
|
|
459
|
-
.mushi-submit:hover { background: ${accentHover}; }
|
|
460
|
-
.mushi-submit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
461
686
|
.mushi-submit:focus-visible {
|
|
462
|
-
outline: 2px solid ${
|
|
463
|
-
outline-offset:
|
|
687
|
+
outline: 2px solid ${vermillion};
|
|
688
|
+
outline-offset: 3px;
|
|
689
|
+
}
|
|
690
|
+
.mushi-submit-arrow {
|
|
691
|
+
display: inline-block;
|
|
692
|
+
transition: transform 220ms ${easeStamp};
|
|
464
693
|
}
|
|
694
|
+
.mushi-submit:hover .mushi-submit-arrow { transform: translateX(3px); }
|
|
465
695
|
|
|
466
|
-
/* Step indicator
|
|
696
|
+
/* \u2500\u2500 Step indicator (numeral ledger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
697
|
+
Replaces the generic three-dots with a typographic series:
|
|
698
|
+
"01 \u2014 02 \u2014 03". The active step uses serif numerals, the
|
|
699
|
+
others use mono so the active one literally reads heavier. */
|
|
467
700
|
.mushi-step-indicator {
|
|
468
701
|
display: flex;
|
|
702
|
+
align-items: center;
|
|
469
703
|
justify-content: center;
|
|
470
|
-
gap:
|
|
471
|
-
padding: 10px;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
.mushi-
|
|
485
|
-
|
|
704
|
+
gap: 8px;
|
|
705
|
+
padding: 10px 22px 14px;
|
|
706
|
+
color: ${inkFaint};
|
|
707
|
+
font-family: ${fontMono};
|
|
708
|
+
font-size: 11px;
|
|
709
|
+
letter-spacing: 0.10em;
|
|
710
|
+
}
|
|
711
|
+
.mushi-step-num {
|
|
712
|
+
display: inline-flex;
|
|
713
|
+
align-items: baseline;
|
|
714
|
+
gap: 4px;
|
|
715
|
+
transition: color 200ms ${easeStamp};
|
|
716
|
+
}
|
|
717
|
+
.mushi-step-num.done { color: ${inkMuted}; text-decoration: line-through; text-decoration-color: ${inkFaint}; }
|
|
718
|
+
.mushi-step-num.active {
|
|
719
|
+
color: ${vermillion};
|
|
720
|
+
font-family: ${fontDisplay};
|
|
721
|
+
font-size: 14px;
|
|
722
|
+
font-weight: 600;
|
|
723
|
+
letter-spacing: 0;
|
|
486
724
|
}
|
|
725
|
+
.mushi-step-sep { width: 14px; height: 1px; background: ${rule}; }
|
|
487
726
|
|
|
488
|
-
/* Success
|
|
727
|
+
/* \u2500\u2500 Success: \u6731\u5370 stamp animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
728
|
+
The success state is the signature moment. A vermillion ring
|
|
729
|
+
scribes itself, then a "RECEIVED" mono-caps label fades in at
|
|
730
|
+
the centre, evoking a hanko being pressed onto the form. */
|
|
489
731
|
.mushi-success {
|
|
490
732
|
text-align: center;
|
|
491
|
-
padding:
|
|
733
|
+
padding: 28px 16px 20px;
|
|
734
|
+
}
|
|
735
|
+
.mushi-success-stamp {
|
|
736
|
+
position: relative;
|
|
737
|
+
width: 96px;
|
|
738
|
+
height: 96px;
|
|
739
|
+
margin: 0 auto 16px;
|
|
740
|
+
display: inline-flex;
|
|
741
|
+
align-items: center;
|
|
742
|
+
justify-content: center;
|
|
743
|
+
}
|
|
744
|
+
.mushi-success-stamp svg {
|
|
745
|
+
position: absolute;
|
|
746
|
+
inset: 0;
|
|
747
|
+
width: 100%;
|
|
748
|
+
height: 100%;
|
|
749
|
+
}
|
|
750
|
+
.mushi-success-stamp circle {
|
|
751
|
+
fill: none;
|
|
752
|
+
stroke: ${vermillion};
|
|
753
|
+
stroke-width: 3;
|
|
754
|
+
stroke-dasharray: 280;
|
|
755
|
+
stroke-dashoffset: 280;
|
|
756
|
+
transform: rotate(-90deg);
|
|
757
|
+
transform-origin: center;
|
|
758
|
+
animation: mushi-stamp-ring 700ms ${easeStamp} 80ms forwards;
|
|
759
|
+
}
|
|
760
|
+
.mushi-success-stamp-label {
|
|
761
|
+
font-family: ${fontDisplay};
|
|
762
|
+
font-size: 18px;
|
|
763
|
+
font-weight: 600;
|
|
764
|
+
color: ${vermillion};
|
|
765
|
+
letter-spacing: 0.04em;
|
|
766
|
+
transform: rotate(-6deg);
|
|
767
|
+
opacity: 0;
|
|
768
|
+
animation: mushi-stamp-press 360ms ${easeStamp} 600ms forwards;
|
|
769
|
+
}
|
|
770
|
+
.mushi-success-headline {
|
|
771
|
+
font-family: ${fontDisplay};
|
|
772
|
+
font-size: 18px;
|
|
773
|
+
font-weight: 500;
|
|
774
|
+
color: ${ink};
|
|
775
|
+
margin-bottom: 4px;
|
|
492
776
|
}
|
|
493
|
-
.mushi-success-
|
|
494
|
-
font-
|
|
495
|
-
|
|
777
|
+
.mushi-success-meta {
|
|
778
|
+
font-family: ${fontMono};
|
|
779
|
+
font-size: 11px;
|
|
780
|
+
letter-spacing: 0.10em;
|
|
781
|
+
text-transform: uppercase;
|
|
782
|
+
color: ${inkMuted};
|
|
496
783
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
784
|
+
|
|
785
|
+
@keyframes mushi-stamp-ring {
|
|
786
|
+
to { stroke-dashoffset: 0; }
|
|
787
|
+
}
|
|
788
|
+
@keyframes mushi-stamp-press {
|
|
789
|
+
0% { opacity: 0; transform: rotate(-6deg) scale(1.3); }
|
|
790
|
+
60% { opacity: 1; transform: rotate(-6deg) scale(0.94); }
|
|
791
|
+
100% { opacity: 1; transform: rotate(-6deg) scale(1); }
|
|
500
792
|
}
|
|
501
793
|
|
|
502
|
-
/* Error
|
|
794
|
+
/* \u2500\u2500 Error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
795
|
+
Inline editorial note rather than a red box. Vermillion left
|
|
796
|
+
rule keeps the same accent language. */
|
|
503
797
|
.mushi-error {
|
|
504
|
-
|
|
798
|
+
margin-top: 10px;
|
|
799
|
+
padding: 8px 0 8px 10px;
|
|
800
|
+
border-left: 2px solid ${vermillion};
|
|
801
|
+
color: ${vermillion};
|
|
505
802
|
font-size: 12px;
|
|
506
|
-
|
|
803
|
+
font-family: ${fontMono};
|
|
804
|
+
letter-spacing: 0.02em;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* \u2500\u2500 Reduced motion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
808
|
+
Honour the OS preference: kill every transition + animation
|
|
809
|
+
except the focus underline (which is critical feedback). */
|
|
810
|
+
@media (prefers-reduced-motion: reduce) {
|
|
811
|
+
*,
|
|
812
|
+
*::before,
|
|
813
|
+
*::after {
|
|
814
|
+
animation-duration: 0.001ms !important;
|
|
815
|
+
animation-iteration-count: 1 !important;
|
|
816
|
+
transition-duration: 0.001ms !important;
|
|
817
|
+
}
|
|
818
|
+
.mushi-success-stamp circle { stroke-dashoffset: 0; }
|
|
819
|
+
.mushi-success-stamp-label { opacity: 1; }
|
|
507
820
|
}
|
|
508
821
|
`;
|
|
509
822
|
}
|
|
@@ -516,6 +829,18 @@ var CATEGORY_ICONS = {
|
|
|
516
829
|
confusing: "\u{1F615}",
|
|
517
830
|
other: "\u{1F4DD}"
|
|
518
831
|
};
|
|
832
|
+
function pad2(n) {
|
|
833
|
+
return n < 10 ? `0${n}` : String(n);
|
|
834
|
+
}
|
|
835
|
+
var TOTAL_STEPS = 3;
|
|
836
|
+
var STEP_NUMBER = {
|
|
837
|
+
category: 1,
|
|
838
|
+
intent: 2,
|
|
839
|
+
details: 3
|
|
840
|
+
};
|
|
841
|
+
function isSubmitShortcut(e) {
|
|
842
|
+
return (e.metaKey || e.ctrlKey) && e.key === "Enter";
|
|
843
|
+
}
|
|
519
844
|
var MushiWidget = class {
|
|
520
845
|
host;
|
|
521
846
|
shadow;
|
|
@@ -529,11 +854,28 @@ var MushiWidget = class {
|
|
|
529
854
|
screenshotAttached = false;
|
|
530
855
|
elementSelected = false;
|
|
531
856
|
submitting = false;
|
|
857
|
+
/** Captured at the moment of submit so the success ledger metadata
|
|
858
|
+
* ("REPORT · 14:23:07 JST") doesn't drift while the success step
|
|
859
|
+
* is on screen. */
|
|
860
|
+
submittedAt = null;
|
|
861
|
+
/** Pending success-state + auto-close timers. Tracked so destroy()
|
|
862
|
+
* can clear them — otherwise a host that unmounts mid-submit leaks
|
|
863
|
+
* this MushiWidget reference (and re-renders into a detached shadow
|
|
864
|
+
* root) for up to ~3.3s after destroy. */
|
|
865
|
+
successTimer = null;
|
|
866
|
+
autoCloseTimer = null;
|
|
532
867
|
constructor(config = {}, callbacks) {
|
|
533
868
|
this.config = {
|
|
534
869
|
position: config.position ?? "bottom-right",
|
|
535
870
|
theme: config.theme ?? "auto",
|
|
536
|
-
|
|
871
|
+
// Falsy-OR (NOT `??`) on purpose: `triggerText: ''` is semantically
|
|
872
|
+
// nonsense — it would render a labelless, glyphless trigger button
|
|
873
|
+
// that users can't see or aim at. Treat empty string the same as
|
|
874
|
+
// omitted so any caller that wires this to a cleared form input or
|
|
875
|
+
// pastes a legacy snippet that emitted `triggerText: ""` (see
|
|
876
|
+
// apps/admin/src/lib/sdkSnippets.ts widgetLines history) still gets
|
|
877
|
+
// the default 🐛 and a visible button.
|
|
878
|
+
triggerText: config.triggerText || "\u{1F41B}",
|
|
537
879
|
expandedTitle: config.expandedTitle ?? "",
|
|
538
880
|
mode: config.mode ?? "conversational",
|
|
539
881
|
locale: config.locale ?? "auto",
|
|
@@ -555,6 +897,7 @@ var MushiWidget = class {
|
|
|
555
897
|
this.screenshotAttached = false;
|
|
556
898
|
this.elementSelected = false;
|
|
557
899
|
this.submitting = false;
|
|
900
|
+
this.submittedAt = null;
|
|
558
901
|
if (options?.category) {
|
|
559
902
|
this.selectedCategory = options.category;
|
|
560
903
|
this.selectedIntent = null;
|
|
@@ -585,6 +928,14 @@ var MushiWidget = class {
|
|
|
585
928
|
if (this.isOpen) this.render();
|
|
586
929
|
}
|
|
587
930
|
destroy() {
|
|
931
|
+
if (this.successTimer !== null) {
|
|
932
|
+
clearTimeout(this.successTimer);
|
|
933
|
+
this.successTimer = null;
|
|
934
|
+
}
|
|
935
|
+
if (this.autoCloseTimer !== null) {
|
|
936
|
+
clearTimeout(this.autoCloseTimer);
|
|
937
|
+
this.autoCloseTimer = null;
|
|
938
|
+
}
|
|
588
939
|
this.host.remove();
|
|
589
940
|
}
|
|
590
941
|
getTheme() {
|
|
@@ -606,6 +957,8 @@ var MushiWidget = class {
|
|
|
606
957
|
trigger.className = `mushi-trigger ${pos}`;
|
|
607
958
|
trigger.textContent = this.config.triggerText;
|
|
608
959
|
trigger.setAttribute("aria-label", t.widget.trigger);
|
|
960
|
+
trigger.setAttribute("aria-haspopup", "dialog");
|
|
961
|
+
trigger.setAttribute("aria-expanded", String(this.isOpen));
|
|
609
962
|
trigger.style.zIndex = String(this.config.zIndex);
|
|
610
963
|
trigger.addEventListener("click", () => {
|
|
611
964
|
if (this.isOpen) this.close();
|
|
@@ -615,6 +968,7 @@ var MushiWidget = class {
|
|
|
615
968
|
const panel = document.createElement("div");
|
|
616
969
|
panel.className = `mushi-panel ${pos}${this.isOpen ? " open" : " closed"}`;
|
|
617
970
|
panel.setAttribute("role", "dialog");
|
|
971
|
+
panel.setAttribute("aria-modal", "true");
|
|
618
972
|
panel.setAttribute("aria-label", t.widget.title);
|
|
619
973
|
panel.style.zIndex = String(this.config.zIndex + 1);
|
|
620
974
|
if (this.isOpen) {
|
|
@@ -636,37 +990,67 @@ var MushiWidget = class {
|
|
|
636
990
|
return this.renderSuccessStep();
|
|
637
991
|
}
|
|
638
992
|
}
|
|
639
|
-
|
|
993
|
+
/**
|
|
994
|
+
* Editorial masthead. Always carries:
|
|
995
|
+
* • the brand mark (虫 kanji on vermillion, "MUSHI" in mono above)
|
|
996
|
+
* • the page title (serif display)
|
|
997
|
+
* • the close affordance
|
|
998
|
+
*
|
|
999
|
+
* On sub-steps it additionally renders a back button (replacing the
|
|
1000
|
+
* "MUSHI" eyebrow with a "← BACK" mono link) and a step counter
|
|
1001
|
+
* ledger ("02 / 03") on the far right.
|
|
1002
|
+
*/
|
|
1003
|
+
renderHeader(opts) {
|
|
640
1004
|
const t = this.locale;
|
|
1005
|
+
const { title, showBack = false, step, eyebrow } = opts;
|
|
1006
|
+
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>`;
|
|
1007
|
+
const counterHtml = step ? `<span class="mushi-step-counter" aria-label="Step ${step} of ${TOTAL_STEPS}"><b>${pad2(step)}</b> / ${pad2(TOTAL_STEPS)}</span>` : "";
|
|
641
1008
|
return `
|
|
642
1009
|
<div class="mushi-header">
|
|
643
|
-
|
|
644
|
-
<
|
|
645
|
-
|
|
1010
|
+
<div class="mushi-header-mark" aria-hidden="true">\u866B</div>
|
|
1011
|
+
<div class="mushi-header-titles">
|
|
1012
|
+
${eyebrowHtml}
|
|
1013
|
+
<h3>${title}</h3>
|
|
1014
|
+
</div>
|
|
1015
|
+
<div class="mushi-header-meta">
|
|
1016
|
+
${counterHtml}
|
|
1017
|
+
<button type="button" class="mushi-close" data-action="close" aria-label="${t.widget.close}">\u2715</button>
|
|
1018
|
+
</div>
|
|
646
1019
|
</div>
|
|
647
1020
|
`;
|
|
648
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Numeral step indicator: "01 — 02 — 03", with the active step in
|
|
1024
|
+
* vermillion serif and completed steps struck through in mono.
|
|
1025
|
+
* Replaces the original three-dot indicator (a generic SaaS pattern).
|
|
1026
|
+
*/
|
|
1027
|
+
renderStepIndicator(currentStep) {
|
|
1028
|
+
const segments = [];
|
|
1029
|
+
for (let i = 1; i <= TOTAL_STEPS; i++) {
|
|
1030
|
+
const cls = i < currentStep ? "mushi-step-num done" : i === currentStep ? "mushi-step-num active" : "mushi-step-num";
|
|
1031
|
+
segments.push(`<span class="${cls}">${pad2(i)}</span>`);
|
|
1032
|
+
if (i < TOTAL_STEPS) segments.push('<span class="mushi-step-sep" aria-hidden="true"></span>');
|
|
1033
|
+
}
|
|
1034
|
+
return `<div class="mushi-step-indicator" aria-hidden="true">${segments.join("")}</div>`;
|
|
1035
|
+
}
|
|
649
1036
|
renderCategoryStep() {
|
|
650
1037
|
const t = this.locale;
|
|
651
1038
|
const categories = ["bug", "slow", "visual", "confusing", "other"].map((id) => `
|
|
652
|
-
<button class="mushi-option-btn" data-category="${id}" role="radio" aria-checked="false">
|
|
653
|
-
<span class="mushi-option-icon">${CATEGORY_ICONS[id]}</span>
|
|
1039
|
+
<button type="button" class="mushi-option-btn" data-category="${id}" role="radio" aria-checked="false">
|
|
1040
|
+
<span class="mushi-option-icon" aria-hidden="true">${CATEGORY_ICONS[id]}</span>
|
|
654
1041
|
<div class="mushi-option-text">
|
|
655
1042
|
<span class="mushi-option-label">${t.step1.categories[id]}</span>
|
|
656
1043
|
<span class="mushi-option-desc">${t.step1.categoryDescriptions[id]}</span>
|
|
657
1044
|
</div>
|
|
1045
|
+
<span class="mushi-option-arrow" aria-hidden="true">\u2192</span>
|
|
658
1046
|
</button>
|
|
659
1047
|
`).join("");
|
|
660
1048
|
return `
|
|
661
|
-
${this.renderHeader(t.step1.heading)}
|
|
1049
|
+
${this.renderHeader({ title: t.step1.heading, step: STEP_NUMBER.category })}
|
|
662
1050
|
<div class="mushi-body" role="radiogroup" aria-label="${t.step1.heading}">
|
|
663
1051
|
${categories}
|
|
664
1052
|
</div>
|
|
665
|
-
|
|
666
|
-
<span class="mushi-dot active"></span>
|
|
667
|
-
<span class="mushi-dot"></span>
|
|
668
|
-
<span class="mushi-dot"></span>
|
|
669
|
-
</div>
|
|
1053
|
+
${this.renderStepIndicator(STEP_NUMBER.category)}
|
|
670
1054
|
`;
|
|
671
1055
|
}
|
|
672
1056
|
renderIntentStep() {
|
|
@@ -674,32 +1058,28 @@ var MushiWidget = class {
|
|
|
674
1058
|
const cat = this.selectedCategory;
|
|
675
1059
|
const intents = t.step2.intents[cat] || [];
|
|
676
1060
|
const options = intents.map((intent) => `
|
|
677
|
-
<button class="mushi-intent-btn" data-intent="${intent}">
|
|
1061
|
+
<button type="button" class="mushi-intent-btn" data-intent="${intent}">
|
|
678
1062
|
${intent}
|
|
679
1063
|
</button>
|
|
680
1064
|
`).join("");
|
|
681
1065
|
return `
|
|
682
|
-
${this.renderHeader(t.step2.heading, true)}
|
|
1066
|
+
${this.renderHeader({ title: t.step2.heading, showBack: true, step: STEP_NUMBER.intent })}
|
|
683
1067
|
<div class="mushi-body">
|
|
684
1068
|
<div class="mushi-selected-category">
|
|
685
|
-
<span>${CATEGORY_ICONS[cat]}</span>
|
|
1069
|
+
<span aria-hidden="true">${CATEGORY_ICONS[cat]}</span>
|
|
686
1070
|
<span>${t.step1.categories[cat]}</span>
|
|
687
1071
|
</div>
|
|
688
1072
|
<div class="mushi-intents">
|
|
689
1073
|
${options}
|
|
690
1074
|
</div>
|
|
691
1075
|
</div>
|
|
692
|
-
|
|
693
|
-
<span class="mushi-dot done"></span>
|
|
694
|
-
<span class="mushi-dot active"></span>
|
|
695
|
-
<span class="mushi-dot"></span>
|
|
696
|
-
</div>
|
|
1076
|
+
${this.renderStepIndicator(STEP_NUMBER.intent)}
|
|
697
1077
|
`;
|
|
698
1078
|
}
|
|
699
1079
|
renderDetailsStep() {
|
|
700
1080
|
const t = this.locale;
|
|
701
1081
|
return `
|
|
702
|
-
${this.renderHeader(t.step3.heading, true)}
|
|
1082
|
+
${this.renderHeader({ title: t.step3.heading, showBack: true, step: STEP_NUMBER.details })}
|
|
703
1083
|
<div class="mushi-body">
|
|
704
1084
|
<textarea
|
|
705
1085
|
class="mushi-textarea"
|
|
@@ -709,35 +1089,47 @@ var MushiWidget = class {
|
|
|
709
1089
|
autofocus
|
|
710
1090
|
></textarea>
|
|
711
1091
|
<div class="mushi-attachments">
|
|
712
|
-
<button class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
1092
|
+
<button type="button" class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
713
1093
|
\u{1F4F8} ${this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton}
|
|
714
1094
|
</button>
|
|
715
|
-
<button class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
1095
|
+
<button type="button" class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
716
1096
|
\u{1F3AF} ${this.elementSelected ? t.step3.elementSelected : t.step3.elementButton}
|
|
717
1097
|
</button>
|
|
718
1098
|
</div>
|
|
719
1099
|
<div class="mushi-error" style="display:none" role="alert"></div>
|
|
720
1100
|
</div>
|
|
721
1101
|
<div class="mushi-footer">
|
|
722
|
-
<
|
|
723
|
-
|
|
1102
|
+
<span class="mushi-footer-hint" aria-hidden="true">\u2318 + ENTER \u2192 send</span>
|
|
1103
|
+
<button type="button" class="mushi-submit" data-action="submit"${this.submitting ? " disabled" : ""}>
|
|
1104
|
+
<span>${this.submitting ? t.widget.submitting : t.widget.submit}</span>
|
|
1105
|
+
<span class="mushi-submit-arrow" aria-hidden="true">\u2192</span>
|
|
724
1106
|
</button>
|
|
725
1107
|
</div>
|
|
726
|
-
|
|
727
|
-
<span class="mushi-dot done"></span>
|
|
728
|
-
<span class="mushi-dot done"></span>
|
|
729
|
-
<span class="mushi-dot active"></span>
|
|
730
|
-
</div>
|
|
1108
|
+
${this.renderStepIndicator(STEP_NUMBER.details)}
|
|
731
1109
|
`;
|
|
732
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Editorial success state: 朱印-style red stamp ring with the kanji
|
|
1113
|
+
* 受 ("received") at its centre, the localised "thank you" string
|
|
1114
|
+
* in serif below, and a mono ledger receipt ("REPORT · HH:MM:SS").
|
|
1115
|
+
* The ring + label animations are defined in styles.ts so this stays
|
|
1116
|
+
* pure markup and `prefers-reduced-motion` flips them to the final
|
|
1117
|
+
* frame instantly.
|
|
1118
|
+
*/
|
|
733
1119
|
renderSuccessStep() {
|
|
734
1120
|
const t = this.locale;
|
|
1121
|
+
const stamp = this.submittedAt ?? /* @__PURE__ */ new Date();
|
|
1122
|
+
const time = stamp.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
|
|
735
1123
|
return `
|
|
736
|
-
${this.renderHeader(t.widget.title)}
|
|
1124
|
+
${this.renderHeader({ title: t.widget.title, eyebrow: "Mushi \xB7 Receipt" })}
|
|
737
1125
|
<div class="mushi-body">
|
|
738
1126
|
<div class="mushi-success">
|
|
739
|
-
<div class="mushi-success-
|
|
740
|
-
|
|
1127
|
+
<div class="mushi-success-stamp" aria-hidden="true">
|
|
1128
|
+
<svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"><circle cx="50" cy="50" r="44"/></svg>
|
|
1129
|
+
<span class="mushi-success-stamp-label">\u53D7</span>
|
|
1130
|
+
</div>
|
|
1131
|
+
<div class="mushi-success-headline">${t.widget.submitted}</div>
|
|
1132
|
+
<div class="mushi-success-meta">REPORT \xB7 ${time}</div>
|
|
741
1133
|
</div>
|
|
742
1134
|
</div>
|
|
743
1135
|
`;
|
|
@@ -775,7 +1167,7 @@ var MushiWidget = class {
|
|
|
775
1167
|
panel.querySelector('[data-action="element"]')?.addEventListener("click", () => {
|
|
776
1168
|
this.callbacks.onElementSelectorRequest?.();
|
|
777
1169
|
});
|
|
778
|
-
|
|
1170
|
+
const submitReport = () => {
|
|
779
1171
|
const textarea = panel.querySelector(".mushi-textarea");
|
|
780
1172
|
const description = textarea?.value?.trim() ?? "";
|
|
781
1173
|
const errorEl = panel.querySelector(".mushi-error");
|
|
@@ -788,27 +1180,43 @@ var MushiWidget = class {
|
|
|
788
1180
|
return;
|
|
789
1181
|
}
|
|
790
1182
|
this.submitting = true;
|
|
1183
|
+
this.submittedAt = /* @__PURE__ */ new Date();
|
|
791
1184
|
this.render();
|
|
792
1185
|
this.callbacks.onSubmit({
|
|
793
1186
|
category: this.selectedCategory,
|
|
794
1187
|
description,
|
|
795
1188
|
intent: this.selectedIntent ?? void 0
|
|
796
1189
|
});
|
|
797
|
-
setTimeout(() => {
|
|
1190
|
+
this.successTimer = setTimeout(() => {
|
|
1191
|
+
this.successTimer = null;
|
|
798
1192
|
this.submitting = false;
|
|
799
1193
|
this.step = "success";
|
|
800
1194
|
this.render();
|
|
801
|
-
setTimeout(() => {
|
|
1195
|
+
this.autoCloseTimer = setTimeout(() => {
|
|
1196
|
+
this.autoCloseTimer = null;
|
|
802
1197
|
if (this.step === "success") this.close();
|
|
803
|
-
},
|
|
1198
|
+
}, 2800);
|
|
804
1199
|
}, 500);
|
|
805
|
-
}
|
|
1200
|
+
};
|
|
1201
|
+
panel.querySelector('[data-action="submit"]')?.addEventListener("click", submitReport);
|
|
806
1202
|
panel.addEventListener("keydown", (e) => {
|
|
807
|
-
if (e.key === "Escape")
|
|
1203
|
+
if (e.key === "Escape") {
|
|
1204
|
+
this.close();
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (this.step === "details" && isSubmitShortcut(e)) {
|
|
1208
|
+
e.preventDefault();
|
|
1209
|
+
submitReport();
|
|
1210
|
+
}
|
|
808
1211
|
});
|
|
809
1212
|
}
|
|
810
1213
|
trapFocus(panel) {
|
|
811
1214
|
requestAnimationFrame(() => {
|
|
1215
|
+
const textarea = panel.querySelector("textarea");
|
|
1216
|
+
if (textarea) {
|
|
1217
|
+
textarea.focus();
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
812
1220
|
const focusable = panel.querySelectorAll("button, textarea, [tabindex]");
|
|
813
1221
|
if (focusable.length > 0) focusable[0].focus();
|
|
814
1222
|
});
|