diff --git a/web_app/static/index.html b/web_app/static/index.html index 0d56371..b3084d0 100644 --- a/web_app/static/index.html +++ b/web_app/static/index.html @@ -116,7 +116,10 @@

Lyrics

- + @@ -143,10 +146,6 @@

Poster

- diff --git a/web_app/static/script.js b/web_app/static/script.js index 441a4ed..ff0be7a 100644 --- a/web_app/static/script.js +++ b/web_app/static/script.js @@ -20,7 +20,6 @@ const startLineInput = document.getElementById('startLine'); const endLineInput = document.getElementById('endLine'); const themeInput = document.getElementById('themeInput'); const fontInput = document.getElementById('fontInput'); -const loadingOverlay = document.getElementById('loadingOverlay'); let currentMetadata = null; let searchDebounceTimer = null; @@ -613,7 +612,15 @@ function handleLyricLineClick(lineNumber) { async function generatePoster() { if (!currentMetadata) return; - loadingOverlay.style.display = 'flex'; + const btnText = generateBtn.querySelector('.btn-text'); + if (!btnText) return; + + // --- New Loading State Logic --- + const originalText = btnText.textContent; + generateBtn.classList.add('loading'); + generateBtn.disabled = true; + btnText.textContent = 'Generating...'; + generateBtn.setAttribute('aria-label', 'Generating poster, please wait.'); const indexingToggle = document.getElementById('indexingToggle'); const accentToggle = document.getElementById('accentToggle'); @@ -635,8 +642,10 @@ async function generatePoster() { const end = endVal; if (isNaN(start) || isNaN(end) || start >= end) { - loadingOverlay.style.display = 'none'; showToast("Please enter a valid range (Start must be less than End).", "error"); + // --- Reset Loading State --- + generateBtn.classList.remove('loading'); + generateBtn.disabled = false; return; } @@ -669,13 +678,17 @@ 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 { + // --- Reset Loading State --- + generateBtn.classList.remove('loading'); + generateBtn.disabled = false; + btnText.textContent = originalText; + generateBtn.removeAttribute('aria-label'); } } diff --git a/web_app/static/style.css b/web_app/static/style.css index 468eb76..940d28d 100644 --- a/web_app/static/style.css +++ b/web_app/static/style.css @@ -159,6 +159,39 @@ header p { background: var(--accent-color); color: #1a1a1f; width: 100%; + position: relative; /* For spinner placement */ +} + +.primary-btn .btn-text { + transition: opacity 0.2s ease-out; +} + +/* Spinner is inside the button, inheriting from the main .spinner keyframes */ +.primary-btn .spinner { + position: absolute; + width: 20px; + height: 20px; + border-width: 2px; + border-color: rgba(0, 0, 0, 0.2); + border-top-color: #1a1a1f; /* Match button text color */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: none; /* Hidden by default */ +} + +.primary-btn.loading { + cursor: not-allowed; + pointer-events: none; +} + +.primary-btn.loading .btn-text { + opacity: 0; + visibility: hidden; /* Hide from screen readers */ +} + +.primary-btn.loading .spinner { + display: block; } .primary-btn:hover {