tts-voice-creator-clone-and.../static/css/voice-design.css
mARTin-B78 c91aaabe3a
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
fix(mobile): retry failed script loads; fix 8 phone-width layout bugs
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>
2026-09-13 17:09:24 +02:00

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; } }