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

Lyrics

- + @@ -143,11 +146,7 @@

Poster

- - + \ No newline at end of file diff --git a/web_app/static/script.js b/web_app/static/script.js index 441a4ed..d06131c 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,8 @@ function handleLyricLineClick(lineNumber) { async function generatePoster() { if (!currentMetadata) return; - loadingOverlay.style.display = 'flex'; + generateBtn.disabled = true; + generateBtn.classList.add('loading'); const indexingToggle = document.getElementById('indexingToggle'); const accentToggle = document.getElementById('accentToggle'); @@ -635,8 +635,9 @@ 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"); + generateBtn.disabled = false; + generateBtn.classList.remove('loading'); return; } @@ -669,13 +670,20 @@ async function generatePoster() { posterContainer.innerHTML = ''; posterContainer.appendChild(img); showDownloadButton(imageUrl, data.filename); - loadingOverlay.style.display = 'none'; + generateBtn.disabled = false; + generateBtn.classList.remove('loading'); + }; + img.onerror = () => { + showToast('Failed to load generated poster.', 'error'); + generateBtn.disabled = false; + generateBtn.classList.remove('loading'); }; } catch (error) { console.error("Generation failed", error); showToast(`Error: ${error.message}`, "error"); - loadingOverlay.style.display = 'none'; + generateBtn.disabled = false; + generateBtn.classList.remove('loading'); } } diff --git a/web_app/static/style.css b/web_app/static/style.css index 468eb76..5af01bf 100644 --- a/web_app/static/style.css +++ b/web_app/static/style.css @@ -645,8 +645,11 @@ input:focus { } @keyframes spin { + from { + transform: translate(-50%, -50%) rotate(0deg); + } to { - transform: rotate(360deg); + transform: translate(-50%, -50%) rotate(360deg); } } @@ -908,4 +911,38 @@ input:focus { .result-action svg { pointer-events: none; -} \ No newline at end of file +} +/* --- Button Loading State --- */ +.primary-btn { + position: relative; /* Anchor for spinner */ +} + +.primary-btn:disabled { + opacity: 0.65; + cursor: not-allowed; + transform: none; /* Cancel hover effect */ + background-color: var(--accent-color); +} + +.primary-btn.loading .btn-text { + visibility: hidden; + opacity: 0; +} + +.btn-spinner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 24px; + height: 24px; + border: 3px solid rgba(26, 26, 31, 0.2); /* Use a darker transparent for contrast on the accent bg */ + border-top-color: #1a1a1f; /* Match button text color */ + border-radius: 50%; + animation: spin 0.8s linear infinite; + display: none; /* Hidden by default */ +} + +.primary-btn.loading .btn-spinner { + display: block; +}