feat(desktop): honest, unified dictation popups with Esc to cancel

Wave 3 of the 2026-09-28 design overhaul.

- Esc (no modifiers) cancels an in-progress dictation, recording or
  processing: VoiceModeService.requestEscape() queues the dead 'escape'
  action. Clicking the capsule cancels only while recording, so a stray
  click during processing no longer throws the text away.
- Errors show in the user's language: bootstrap now passes error.code to
  the tip (the translated branch never ran), and one core classifier
  (voice-error-message) feeds both the tip and the main-window alert.
- Capsule: bars stay flat without input (no fake "alive" wobble before the
  mic opens), an indeterminate bar replaces the made-up percentage, the
  partial transcript stays visible while processing, an Esc hint, and a
  brief "Inserted" state so success is visible.
- All six popups share popups/_shared (Pretendard, one panel radius,
  selection, kbd hints, reduced motion). History/command popups get
  titles, listbox roles and window sizes from popup-list-geometry (empty
  states were clipped); the command popup's "0" key is registered;
  result popup explains why it appeared and can be closed; captions keep
  a theme-independent dark backing on purpose.
- Popup themes follow the OS when set to system and the document lang
  follows the app language.
- Site hero demo mirrors the new capsule (done state, indeterminate bar,
  Esc hint; 10 locales).
This commit is contained in:
Yun Chan 2026-09-28 20:46:24 +09:00
parent f064c93197
commit 7b1210b5d1
41 changed files with 1565 additions and 1143 deletions

View file

@ -147,28 +147,23 @@
transform: none;
}
/* ── 녹음 캡슐 — 앱 recording-tip/style.css 와 같은 값 ─── */
/* ── 녹음 캡슐 — 앱 popups/recording-tip/style.css(v4, 2026-09-28)와 같은 값 ───
* 판 모양(radius 10)·단색 막대·입력 없으면 막대 정지·처리 중 무한 막대·완료 "입력함".
* 색은 앱 다크 테마 원시값(packages/ui/src/theme-raw.ts)과 같다 — 값이 바뀌면 함께 고친다. */
.capsule {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 16px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(15, 17, 22, 0.88);
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.12);
backdrop-filter: blur(28px);
-webkit-backdrop-filter: blur(28px);
}
.capsule:has(.capsule-partial) {
border-radius: 18px;
}
.capsule-recording {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 160px;
max-width: 320px;
min-width: 168px;
max-width: 300px;
padding: 8px 12px;
border-radius: 10px;
border: 1px solid rgba(148, 180, 255, 0.12);
background: #111a30;
box-shadow: 0 12px 32px rgba(3, 7, 18, 0.55);
}
.capsule.is-listening {
border-color: rgba(148, 180, 255, 0.2);
}
.capsule-row {
display: flex;
@ -179,66 +174,88 @@
display: flex;
align-items: center;
gap: 3px;
height: 28px;
height: 22px;
}
.capsule-bar {
display: block;
width: 3px;
min-height: 3px;
min-height: 2px;
height: 2px;
border-radius: 999px;
border-radius: 2px;
background: #3b82f6;
transition: height 80ms ease-out;
}
.capsule-bar:nth-child(1) { background: #93c5fd; opacity: 0.85; }
.capsule-bar:nth-child(2) { background: #60a5fa; opacity: 0.9; }
.capsule-bar:nth-child(3) { background: #4f8dfa; opacity: 0.95; }
.capsule-bar:nth-child(4) { background: #3b82f6; }
.capsule-bar:nth-child(5) { background: #3b82f6; }
.capsule-bar:nth-child(6) { background: #5b75f7; }
.capsule-bar:nth-child(7) { background: #6e71f7; opacity: 0.95; }
.capsule-bar:nth-child(8) { background: #818cf8; opacity: 0.9; }
.capsule-bar:nth-child(9) { background: #a78bfa; opacity: 0.85; }
.capsule.is-thinking .capsule-bar {
background: #7e8eb0;
}
.capsule-duration {
min-width: 34px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
min-width: 32px;
font-size: 13px;
font-weight: 600;
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
color: #f4f4f5;
}
.capsule-partial {
display: -webkit-box;
max-height: 38px;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
border-top: 1px solid rgba(255, 255, 255, 0.08);
padding-top: 6px;
font-size: 12px;
line-height: 1.45;
color: #f4f4f5;
}
.capsule-progress {
display: block;
width: 120px;
height: 4px;
overflow: hidden;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
}
.capsule-progress-bar {
display: block;
width: 0;
height: 100%;
border-radius: 999px;
background: #3b82f6;
transition: width 100ms linear;
color: #eef2fb;
}
.capsule-thinking {
flex: 1;
font-size: 12px;
font-weight: 500;
color: #a1a1aa;
color: #93a4c8;
white-space: nowrap;
}
.capsule-cancel {
margin-left: auto;
display: inline-flex;
align-items: center;
height: 18px;
padding: 0 5px;
border: 1px solid rgba(148, 180, 255, 0.2);
border-radius: 4px;
font-size: 11px;
line-height: 1;
color: #7e8eb0;
white-space: nowrap;
}
.capsule-progress {
position: relative;
display: block;
height: 2px;
overflow: hidden;
border-radius: 1px;
background: rgba(148, 180, 255, 0.12);
}
.capsule-progress-bar {
position: absolute;
top: 0;
bottom: 0;
width: 36%;
border-radius: 1px;
background: #3b82f6;
animation: capsule-indeterminate 1.1s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
@keyframes capsule-indeterminate {
from { left: -36%; }
to { left: 100%; }
}
.capsule-partial {
display: flex;
flex-direction: column;
justify-content: flex-end;
max-height: 38px;
overflow: hidden;
border-top: 1px solid rgba(148, 180, 255, 0.07);
padding-top: 6px;
font-size: 12px;
line-height: 1.55;
color: #eef2fb;
}
.capsule.is-thinking .capsule-partial {
color: #7e8eb0;
}
.capsule-done {
font-size: 12px;
color: #eef2fb;
}
.capsule-done-icon {
color: #34d399;
}
/* ── Grid Background Pattern (히어로) ─────────────────── */