From ad3e1794479e3b3ad9b1f9bb961eae75426f41c8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:35:49 +0000 Subject: [PATCH] feat(ux): add loading state to generate button Adds a loading spinner and disabled state to the "Generate Poster" button to provide immediate feedback during an asynchronous operation. This change improves the user experience by: - Replacing a disruptive full-screen overlay with a localized spinner. - Preventing multiple clicks by disabling the button during generation. - Improving accessibility by adding an `aria-label` to inform screen reader users of the loading state. --- web_app/static/index.html | 5 ++++- web_app/static/script.js | 19 ++++++++++++++++--- web_app/static/style.css | 13 +++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/web_app/static/index.html b/web_app/static/index.html index 0d56371..69569a5 100644 --- a/web_app/static/index.html +++ b/web_app/static/index.html @@ -116,7 +116,10 @@

Lyrics

- + diff --git a/web_app/static/script.js b/web_app/static/script.js index 441a4ed..3ec88d2 100644 --- a/web_app/static/script.js +++ b/web_app/static/script.js @@ -613,7 +613,15 @@ function handleLyricLineClick(lineNumber) { async function generatePoster() { if (!currentMetadata) return; - loadingOverlay.style.display = 'flex'; + // -- UX Enhancement: Button Loading State -- + const btnText = generateBtn.querySelector('.btn-text'); + const btnSpinner = generateBtn.querySelector('.btn-spinner'); + + generateBtn.disabled = true; + btnText.style.display = 'none'; + btnSpinner.style.display = 'block'; + generateBtn.setAttribute('aria-label', 'Generating poster, please wait.'); + // -- End UX Enhancement -- const indexingToggle = document.getElementById('indexingToggle'); const accentToggle = document.getElementById('accentToggle'); @@ -669,13 +677,18 @@ async function generatePoster() { posterContainer.innerHTML = ''; posterContainer.appendChild(img); showDownloadButton(imageUrl, data.filename); - loadingOverlay.style.display = 'none'; }; } catch (error) { console.error("Generation failed", error); showToast(`Error: ${error.message}`, "error"); - loadingOverlay.style.display = 'none'; + } finally { + // -- UX Enhancement: Reset Button State -- + generateBtn.disabled = false; + btnText.style.display = 'block'; + btnSpinner.style.display = 'none'; + generateBtn.removeAttribute('aria-label'); + // -- End UX Enhancement -- } } diff --git a/web_app/static/style.css b/web_app/static/style.css index 468eb76..30b84d0 100644 --- a/web_app/static/style.css +++ b/web_app/static/style.css @@ -166,6 +166,19 @@ header p { transform: translateY(-1px); } +.primary-btn:disabled { + background: var(--accent-hover); + cursor: not-allowed; + opacity: 0.7; +} + +.btn-spinner { + width: 20px; + height: 20px; + border-width: 2px; + margin: 0; +} + .secondary-btn { background: transparent; border-color: var(--border-color);