diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..8e09a36 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,6 @@ +This journal is for Palette's critical UX/accessibility learnings only. + +**Format:** +`## YYYY-MM-DD - [Title]` +`**Learning:** [UX/a11y insight]` +`**Action:** [How to apply next time]` diff --git a/web_app/static/index.html b/web_app/static/index.html index 0d56371..0c737c4 100644 --- a/web_app/static/index.html +++ b/web_app/static/index.html @@ -143,10 +143,6 @@

Poster

- diff --git a/web_app/static/script.js b/web_app/static/script.js index 441a4ed..669b729 100644 --- a/web_app/static/script.js +++ b/web_app/static/script.js @@ -613,7 +613,16 @@ function handleLyricLineClick(lineNumber) { async function generatePoster() { if (!currentMetadata) return; - loadingOverlay.style.display = 'flex'; + const originalButtonHTML = generateBtn.innerHTML; + generateBtn.disabled = true; + generateBtn.classList.add('loading'); + generateBtn.innerHTML = `Generating...`; + + const restoreButton = () => { + generateBtn.disabled = false; + generateBtn.classList.remove('loading'); + generateBtn.innerHTML = originalButtonHTML; + }; const indexingToggle = document.getElementById('indexingToggle'); const accentToggle = document.getElementById('accentToggle'); @@ -635,8 +644,8 @@ 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"); + restoreButton(); return; } @@ -669,13 +678,17 @@ async function generatePoster() { posterContainer.innerHTML = ''; posterContainer.appendChild(img); showDownloadButton(imageUrl, data.filename); - loadingOverlay.style.display = 'none'; + restoreButton(); + }; + img.onerror = () => { + showToast("Error: Failed to load the generated poster image.", "error"); + restoreButton(); }; } catch (error) { console.error("Generation failed", error); showToast(`Error: ${error.message}`, "error"); - loadingOverlay.style.display = 'none'; + restoreButton(); } } diff --git a/web_app/static/style.css b/web_app/static/style.css index 468eb76..3859a80 100644 --- a/web_app/static/style.css +++ b/web_app/static/style.css @@ -908,4 +908,26 @@ input:focus { .result-action svg { pointer-events: none; +} + +/* --- Button Loading State --- */ +.primary-btn.loading { + cursor: wait; + opacity: 0.8; + pointer-events: none; +} + +.primary-btn.loading .spinner-small { + display: inline-block; + margin-right: 8px; /* space between spinner and text */ +} + +.spinner-small { + display: none; /* hidden by default */ + width: 20px; + height: 20px; + border: 2px solid rgba(0, 0, 0, 0.3); + border-top-color: #1a1a1f; + border-radius: 50%; + animation: spin 0.8s linear infinite; } \ No newline at end of file