diff --git a/web_app/static/index.html b/web_app/static/index.html
index 0d56371..d57b35e 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..f480a56 100644
--- a/web_app/static/script.js
+++ b/web_app/static/script.js
@@ -20,7 +20,7 @@ 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');
+const loadingOverlay = document.getElementById('loadingOverlay'); // Keep for now, might be used elsewhere
let currentMetadata = null;
let searchDebounceTimer = null;
@@ -611,9 +611,17 @@ function handleLyricLineClick(lineNumber) {
}
async function generatePoster() {
- if (!currentMetadata) return;
+ if (!currentMetadata || generateBtn.classList.contains('loading')) return;
- loadingOverlay.style.display = 'flex';
+ const btnText = generateBtn.querySelector('.btn-text');
+ const spinner = generateBtn.querySelector('.spinner');
+
+ // --- 1. Set Loading State ---
+ generateBtn.classList.add('loading');
+ generateBtn.disabled = true;
+ btnText.textContent = 'Generating...'; // Update text for visual consistency
+ generateBtn.setAttribute('aria-label', 'Generating poster, please wait'); // Set accessible name
+ spinner.style.display = 'block';
const indexingToggle = document.getElementById('indexingToggle');
const accentToggle = document.getElementById('accentToggle');
@@ -635,8 +643,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");
+ // No loading overlay to hide, just return
return;
}
@@ -669,13 +677,20 @@ async function generatePoster() {
posterContainer.innerHTML = '';
posterContainer.appendChild(img);
showDownloadButton(imageUrl, data.filename);
- loadingOverlay.style.display = 'none';
+ // No loading overlay to hide
};
} catch (error) {
console.error("Generation failed", error);
showToast(`Error: ${error.message}`, "error");
- loadingOverlay.style.display = 'none';
+ // No loading overlay to hide
+ } finally {
+ // --- 2. Reset Button State ---
+ generateBtn.classList.remove('loading');
+ generateBtn.disabled = false;
+ btnText.textContent = 'Generate Poster';
+ generateBtn.removeAttribute('aria-label'); // Remove accessible name
+ spinner.style.display = 'none';
}
}
diff --git a/web_app/static/style.css b/web_app/static/style.css
index 468eb76..eacd76a 100644
--- a/web_app/static/style.css
+++ b/web_app/static/style.css
@@ -178,6 +178,28 @@ header p {
border-color: var(--text-muted);
}
+.primary-btn.loading {
+ cursor: not-allowed;
+ background: var(--accent-hover);
+}
+
+.primary-btn.loading .btn-text {
+ display: none;
+}
+
+.primary-btn.loading .spinner {
+ display: block;
+ margin-bottom: 0; /* Override default margin */
+}
+
+/* Scoped spinner for button */
+.primary-btn .spinner {
+ width: 20px;
+ height: 20px;
+ border-width: 2px;
+ border-top-color: #1a1a1f; /* Dark color for contrast */
+}
+
/* Inputs */
input[type="text"],
input[type="number"] {