Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Two reports from the same mobile session tied together: a voice-library
crash ("fmtDbfs is not defined") and a save button reporting "No audio
ready" despite a file being correctly loaded and trimmed. Root cause
was the same in both -- the module loader fetched each JS file once
with no retry, so a single dropped request on a flaky connection left
that module's functions/variables undefined while every other module
kept executing anyway, producing confusing symptoms nowhere near the
real cause.
loader.js: _load() now retries up to 3 times with backoff before
rejecting. If a module still can't load after that, the app says so
plainly via a toast (or a plain DOM banner if even utils.js failed to
load) instead of silently revealing a partially-broken page. Wired into
both the core-module fallback path and ensureFeatureModules' lazy path.
Loading every section of the app at a 375px viewport and checking for
elements running past the viewport edge (all now 0) found eight real,
independent layout bugs, each with a distinct root cause:
- .section-head's mobile wrap rule targeted .section-header, a class
nothing in the app uses -- dead since it was written. Every section's
icon/title/action row (e.g. "+ New voice") ran off-screen unreachable.
- The synth-mode toggle and Clone-a-Voice source tabs' icon-only mobile
rules matched a <span> wrapper the markup didn't have. Added it, plus
aria-labels so hiding the text doesn't erase the accessible name.
- voice-design.css loads after style.css and re-declares
.design-sample-grid unscoped -- style.css's mobile override always
lost the cascade regardless of viewport. Moved the override to the
file that actually wins.
- Three separate minmax(380-440px,1fr) engine-card grids couldn't fit
one column under ~460px. Collapsed to 1fr with min-width:0 on the
items -- the column fix alone wasn't enough, since a grid item's
content-based min-width is still a floor even inside a 1fr track.
- The routing test row (450px+ minimum) and the conversation input bar
(a single non-wrapping flex row for text field + mic + gate + three
toggles) now wrap under 767px.
- Try It Out's EQ sliders shrank to fit beside their toggle instead of
ever wrapping below it, overflowing their own squeezed container.
Forced onto their own full-width line.
Verified with a Playwright sweep of all 16 sections (0 overflowing
elements, 0 console errors) plus the existing 225-test suite and the
frontend syntax gate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
51 lines
5.3 KiB
CSS
51 lines
5.3 KiB
CSS
/* ── Voice Design feature ─────────────────────────────────────────────────── */
|
|
.design-hints { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 12px; color: var(--subtext); }
|
|
.design-hints th, .design-hints td { text-align: left; vertical-align: top; padding: 7px 9px; border-bottom: 1px solid var(--border); }
|
|
.design-hints th { color: var(--text); font-size: 14px; text-transform: uppercase; letter-spacing: .06em; }
|
|
.design-samples-details summary { cursor: pointer; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; color: var(--subtext); user-select: none; list-style: none; }
|
|
.design-samples-details summary::-webkit-details-marker { display: none; }
|
|
.design-samples-details summary::before { content: '\25B6'; color: var(--accent); font-size: 14px; }
|
|
.design-samples-details[open] summary::before { content: '\25BC'; }
|
|
.design-samples-details summary span:first-child { font-size: 12px; font-weight: 700; color: var(--subtext); text-transform: uppercase; letter-spacing: .09em; }
|
|
.design-samples-details .design-sample-header { margin-top: 12px; }
|
|
.preset-row { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-top: 10px; }
|
|
.preset-row input, .preset-row select { background: var(--panel); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 8px 12px; font-size: 14px; }
|
|
.design-sample-grid { display: grid; grid-template-columns: minmax(150px,1fr) 52px 86px minmax(220px,1.4fr) minmax(220px,1.4fr) 176px; gap: 8px; align-items: center; }
|
|
/* This file loads after style.css, so its own (unscoped) rule above always
|
|
won on specificity+order regardless of viewport -- style.css's mobile
|
|
override of the same selector was silently dead code at every width. */
|
|
@media (max-width: 640px) {
|
|
.design-sample-grid { grid-template-columns: 1fr; gap: 4px; }
|
|
.qwen-sample .sample-desc, .qwen-sample .sample-text {
|
|
white-space: normal; overflow: visible; text-overflow: clip;
|
|
}
|
|
}
|
|
.design-sample-header { padding: 8px 10px 10px; border-bottom: 1px solid var(--border); }
|
|
.design-sample-header div { white-space: nowrap; overflow: hidden; font-size: 14px; font-weight: 700; color: var(--subtext); text-transform: uppercase; letter-spacing: .07em; }
|
|
.qwen-sample { border: 1px solid var(--border); border-radius: 6px; background: var(--panel); padding: 8px 10px; transition: border-color .15s; }
|
|
.qwen-sample:hover { border-color: var(--accent); }
|
|
.qwen-sample strong { color: var(--accent); font-size: 13px; }
|
|
.qwen-sample .sample-sex, .qwen-sample .sample-language { color: var(--subtext); font-size: 12px; font-family: monospace; }
|
|
.qwen-sample .sample-desc, .qwen-sample .sample-text { color: var(--subtext); font-size: 12px; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.qwen-sample audio { display: none; grid-column: 1 / -1; margin-top: 6px; }
|
|
.qwen-sample-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; justify-content: flex-end; }
|
|
.lang-select { background: var(--panel); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 8px 12px; font-size: 14px; }
|
|
.d-naming-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
|
|
.d-naming-row .field { flex: 0 0 auto; }
|
|
.d-naming-row .field.flex1 { flex: 1 1 140px; }
|
|
#d-voice-id { font-family: monospace; font-weight: 600; font-size: 15px; color: var(--accent); width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 12px; }
|
|
#d-transcript { min-height: 70px; width: 100%; background: var(--panel); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 10px 14px; font-size: 14px; font-family: inherit; resize: vertical; }
|
|
#d-lang, #d-gender, #d-name { background: var(--bg); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 8px 12px; font-size: 14px; }
|
|
#design-save-result { display: flex; flex-direction: column; gap: 8px; }
|
|
#tts-voice-select { background: var(--panel); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); padding: 8px 12px; font-size: 14px; min-width: 200px; }
|
|
.preview-match-panel { margin-top: 12px; border: 1px solid var(--border); border-radius: 6px; background: rgba(37,99,235,.04); padding: 12px; display: grid; grid-template-columns: minmax(220px,.75fr) minmax(260px,1fr); gap: 12px; }
|
|
.preview-match-panel[hidden] { display: none; }
|
|
.preview-match-meta { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
|
.preview-match-title { font-weight: 700; color: var(--text); }
|
|
.preview-match-detail { color: var(--subtext); font-size: 12px; line-height: 1.45; }
|
|
.preview-match-warning { color: var(--yellow); font-size: 12px; line-height: 1.45; }
|
|
.preview-match-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
.preview-match-panel audio { width: 100%; min-width: 0; }
|
|
.preview-match-transcript { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; color: var(--subtext); font-family: monospace; font-size: 12px; line-height: 1.4; max-height: 88px; overflow: auto; white-space: pre-wrap; }
|
|
@media (max-width: 900px) { .preview-match-panel { grid-template-columns: 1fr; } }
|