diff --git a/web_app/static/index.html b/web_app/static/index.html
index 0d56371..6ad94e8 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..be4fcb0 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.classList.add('loading');
+ generateBtn.disabled = true;
const indexingToggle = document.getElementById('indexingToggle');
const accentToggle = document.getElementById('accentToggle');
@@ -635,7 +635,8 @@ async function generatePoster() {
const end = endVal;
if (isNaN(start) || isNaN(end) || start >= end) {
- loadingOverlay.style.display = 'none';
+ generateBtn.classList.remove('loading');
+ generateBtn.disabled = false;
showToast("Please enter a valid range (Start must be less than End).", "error");
return;
}
@@ -669,13 +670,20 @@ async function generatePoster() {
posterContainer.innerHTML = '';
posterContainer.appendChild(img);
showDownloadButton(imageUrl, data.filename);
- loadingOverlay.style.display = 'none';
+ generateBtn.classList.remove('loading');
+ generateBtn.disabled = false;
+ };
+ img.onerror = () => {
+ showToast("Error loading generated image.", "error");
+ generateBtn.classList.remove('loading');
+ generateBtn.disabled = false;
};
} catch (error) {
console.error("Generation failed", error);
showToast(`Error: ${error.message}`, "error");
- loadingOverlay.style.display = 'none';
+ generateBtn.classList.remove('loading');
+ generateBtn.disabled = false;
}
}
diff --git a/web_app/static/style.css b/web_app/static/style.css
index 468eb76..906cfb0 100644
--- a/web_app/static/style.css
+++ b/web_app/static/style.css
@@ -908,4 +908,53 @@ input:focus {
.result-action svg {
pointer-events: none;
-}
\ No newline at end of file
+}
+
+/* --- Button Loading State --- */
+.primary-btn {
+ position: relative; /* Needed for spinner positioning */
+}
+
+.btn-spinner {
+ display: none;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 20px;
+ height: 20px;
+ border: 2px solid rgba(0, 0, 0, 0.2);
+ border-top-color: #1a1a1f; /* Spinner color matching button text */
+ border-radius: 50%;
+ animation: btn-spin 0.8s linear infinite;
+}
+
+.btn-text {
+ transition: opacity 0.2s ease, visibility 0.2s ease;
+}
+
+/* Loading state */
+.primary-btn.loading .btn-text {
+ visibility: hidden;
+ opacity: 0;
+}
+
+.primary-btn.loading .btn-spinner {
+ display: block;
+}
+
+.primary-btn.loading {
+ cursor: wait;
+ background: var(--accent-hover);
+ pointer-events: none; /* Disables clicks while loading */
+}
+
+
+@keyframes btn-spin {
+ from {
+ transform: translate(-50%, -50%) rotate(0deg);
+ }
+ to {
+ transform: translate(-50%, -50%) rotate(360deg);
+ }
+}