From cc27c5db48666666f0a088137fa929cebc4cd972 Mon Sep 17 00:00:00 2001 From: "Troll (Hermes Agent)" Date: Mon, 31 Aug 2026 03:17:14 +0000 Subject: [PATCH] Design system refactor: shared CSS tokens, cleaner customer/admin pages, kiosk name privacy, v0.9.1 --- README.md | 2 +- VERSION | 2 +- app.py | 25 +- static/css/booth.css | 1123 ++++++++++++++++++++++++++++++++ templates/admin/dashboard.html | 217 ++---- templates/admin/request.html | 467 +++---------- templates/admin/settings.html | 229 +------ templates/kiosk.html | 191 +----- templates/player.html | 168 ++--- templates/request.html | 290 +++------ 10 files changed, 1490 insertions(+), 1224 deletions(-) create mode 100644 static/css/booth.css diff --git a/README.md b/README.md index 77c32a2..b265cae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Theme Song Booth (MusicGPT Edition) -![Version](https://img.shields.io/badge/version-v0.9.0-blue) +![Version](https://img.shields.io/badge/version-v0.9.1-blue) A Flask web application for running a convention booth where visitors request a custom AI-generated theme song. Operators manage the queue from an admin dashboard, generate prompts via Hermes, queue generations through the MusicGPT API, and deliver final songs by email. diff --git a/VERSION b/VERSION index ac39a10..f374f66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0 +0.9.1 diff --git a/app.py b/app.py index b8c361e..8c97daf 100644 --- a/app.py +++ b/app.py @@ -1069,11 +1069,10 @@ def kiosk(): queue = [] for r in raw_queue: if r.get('status') in active_statuses: - email = r.get('email') or '' - name = email.split('@')[0] if '@' in email else email + name = (r.get('name') or '').strip() queue.append({ 'id': r['id'], - 'name': name or 'Guest', + 'name': name or f"Guest #{r['id']}", 'status': KIOSK_STATUS_MAP[r['status']], 'raw_status': r['status'], }) @@ -1482,6 +1481,23 @@ def admin_request(rid): steps, current_step = get_request_steps(req) musicgpt_status = req.get('musicgpt_status') + # MusicGPT progress stepper data + mg_steps = [ + ('IN_QUEUE', 'Queued'), + ('IN_PROGRESS', 'Generating'), + ('COMPLETED', 'Completed') + ] + mg_index = -1 + mg_completed = False + if musicgpt_status: + for i, (key, _) in enumerate(mg_steps): + if key == musicgpt_status: + mg_index = i + if musicgpt_status in ('COMPLETED', 'FINISHED'): + mg_completed = True + if musicgpt_status in ('FAILED', 'ERROR', 'CANCELLED'): + mg_index = -2 + return render_template( 'admin/request.html', req=req, @@ -1501,6 +1517,9 @@ def admin_request(rid): steps=steps, current_step=current_step, musicgpt_status=musicgpt_status, + mg_steps=mg_steps, + mg_index=mg_index, + mg_completed=mg_completed, ) diff --git a/static/css/booth.css b/static/css/booth.css new file mode 100644 index 0000000..c0abd9d --- /dev/null +++ b/static/css/booth.css @@ -0,0 +1,1123 @@ +:root { + /* Brand colors sampled from Trollgorithm_booth.jpg */ + --booth-gold: #e0a040; + --booth-gold-light: #f2c46d; + --booth-amber: #805030; + --booth-amber-dark: #5a3520; + + /* Surfaces */ + --bg-base: #0b0f17; + --bg-page: #111827; + --bg-panel: #1f2937; + --bg-input: #111827; + --bg-hover: #2d3748; + + /* Text */ + --text-primary: #f3f4f6; + --text-secondary: #9ca3af; + --text-muted: #6b7280; + --text-link: #93c5fd; + --text-link-hover: #bfdbfe; + + /* Accents */ + --accent: var(--booth-gold); + --accent-hover: var(--booth-gold-light); + --accent-text: #0b0f17; + --accent-subtle: rgba(224, 160, 64, 0.12); + + /* Semantic */ + --success: #10b981; + --success-bg: rgba(16, 185, 129, 0.12); + --warning: #f59e0b; + --warning-bg: rgba(245, 158, 11, 0.12); + --danger: #ef4444; + --danger-bg: rgba(239, 68, 68, 0.12); + --info: #60a5fa; + --info-bg: rgba(96, 165, 250, 0.12); + + /* Borders */ + --border: #374151; + --border-subtle: rgba(55, 65, 81, 0.6); + + /* Spacing */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-full: 9999px; + + /* Typography */ + --font-display: "Outfit", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-body: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.25); + --shadow-lg: 0 10px 32px rgba(0, 0, 0, 0.35); + + /* Transitions */ + --transition-fast: 120ms ease; + --transition-base: 200ms ease; +} + +/* --------------------------------------------- + Base reset (scope to booth pages) + --------------------------------------------- */ +.booth-page { + margin: 0; + padding: 0; + font-family: var(--font-body); + background: var(--bg-base); + color: var(--text-primary); + line-height: 1.5; + min-height: 100vh; +} + +.booth-page *, +.booth-page *::before, +.booth-page *::after { + box-sizing: border-box; +} + +.booth-page img { + max-width: 100%; + display: block; +} + +.booth-page a { + color: var(--text-link); + text-decoration: none; +} + +.booth-page a:hover { + color: var(--text-link-hover); + text-decoration: underline; +} + +/* --------------------------------------------- + Typography + --------------------------------------------- */ +.booth-page h1, +.booth-page h2, +.booth-page h3 { + font-family: var(--font-display); + color: var(--accent); + margin-top: 0; + line-height: 1.2; +} + +.booth-page h1 { font-size: 1.75rem; } +.booth-page h2 { font-size: 1.35rem; } +.booth-page h3 { font-size: 1.1rem; } + +.booth-page .subtitle { + color: var(--text-secondary); + text-align: center; + margin-bottom: var(--space-6); +} + +/* --------------------------------------------- + Layout containers + --------------------------------------------- */ +.booth-page .container { + max-width: 720px; + margin: 0 auto; + padding: var(--space-6); +} + +.booth-page .container-wide { + max-width: 1200px; +} + +.booth-page .card { + background: var(--bg-panel); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + padding: var(--space-6); + box-shadow: var(--shadow-lg); +} + +/* --------------------------------------------- + Banner + --------------------------------------------- */ +.booth-page .banner { + width: 100%; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + margin-bottom: var(--space-6); +} + +/* --------------------------------------------- + Forms + --------------------------------------------- */ +.booth-page label { + display: block; + margin-top: var(--space-4); + font-weight: 600; + color: var(--text-link); +} + +.booth-page input[type="text"], +.booth-page input[type="email"], +.booth-page input[type="password"], +.booth-page input[type="number"], +.booth-page input[type="url"], +.booth-page input[type="file"], +.booth-page select, +.booth-page textarea { + width: 100%; + padding: var(--space-3); + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--bg-input); + color: var(--text-primary); + font: inherit; + margin-top: var(--space-1); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.booth-page input:focus, +.booth-page select:focus, +.booth-page textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-subtle); +} + +.booth-page textarea { + min-height: 90px; + resize: vertical; +} + +.booth-page input[type="checkbox"], +.booth-page input[type="radio"] { + width: auto; + margin: 0; +} + +.booth-page .checkbox-label { + display: flex; + align-items: center; + gap: var(--space-2); + margin-top: var(--space-4); + cursor: pointer; + font-weight: 400; + color: var(--text-primary); +} + +.booth-page .checkbox-label input { + flex-shrink: 0; +} + +.booth-page .required { + color: var(--danger); +} + +/* --------------------------------------------- + Buttons + --------------------------------------------- */ +.booth-page button, +.booth-page .button, +.booth-page .button-link { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-5); + border: none; + border-radius: var(--radius-md); + background: var(--accent); + color: var(--accent-text); + font-weight: 700; + font-size: 1rem; + cursor: pointer; + transition: transform var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast); +} + +.booth-page button:hover, +.booth-page .button:hover, +.booth-page .button-link:hover { + background: var(--accent-hover); + transform: translateY(-1px); + text-decoration: none; +} + +.booth-page button:disabled, +.booth-page .button:disabled { + background: var(--border); + color: var(--text-muted); + cursor: not-allowed; + transform: none; +} + +.booth-page .button-secondary { + background: var(--border); + color: var(--text-primary); +} + +.booth-page .button-secondary:hover { + background: var(--bg-hover); +} + +.booth-page .button-success { + background: var(--success); + color: var(--accent-text); +} + +.booth-page .button-success:hover { + background: #34d399; +} + +.booth-page .button-danger { + background: var(--danger); + color: #fff; +} + +.booth-page .button-danger:hover { + background: #f87171; +} + +.booth-page .button-block { + width: 100%; + margin-top: var(--space-6); +} + +/* --------------------------------------------- + Flash / status messages + --------------------------------------------- */ +.booth-page .flash { + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + margin-bottom: var(--space-4); + background: var(--success-bg); + color: var(--text-primary); + border: 1px solid var(--success); +} + +.booth-page .flash.error { + background: var(--danger-bg); + border-color: var(--danger); +} + +.booth-page .flash.warning { + background: var(--warning-bg); + border-color: var(--warning); +} + +.booth-page .flash.info { + background: var(--info-bg); + border-color: var(--info); +} + +/* --------------------------------------------- + Status badges + --------------------------------------------- */ +.booth-page .status-badge { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.03em; + background: var(--border); + color: var(--text-primary); + white-space: nowrap; +} + +.booth-page .status-badge.ok, +.booth-page .status-badge.delivered, +.booth-page .status-badge.paid { + background: var(--success); + color: var(--accent-text); +} + +.booth-page .status-badge.pending, +.booth-page .status-badge.prompt_ready { + background: var(--info); + color: var(--accent-text); +} + +.booth-page .status-badge.songs_uploaded { + background: #a78bfa; + color: var(--accent-text); +} + +.booth-page .status-badge.awaiting_payment { + background: var(--warning); + color: var(--accent-text); +} + +.booth-page .status-badge.revisions_requested, +.booth-page .status-badge.missing, +.booth-page .status-badge.cancelled { + background: var(--danger); + color: #fff; +} + +.booth-page .status-badge.sent { + background: var(--info); + color: var(--accent-text); +} + +/* --------------------------------------------- + Admin dashboard + --------------------------------------------- */ +.booth-page .topbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-4); + margin-bottom: var(--space-6); + flex-wrap: wrap; +} + +.booth-page .topbar .nav { + display: flex; + gap: var(--space-4); + align-items: center; +} + +.booth-page .filters { + display: flex; + gap: var(--space-2); + flex-wrap: wrap; + align-items: center; + margin-bottom: var(--space-4); +} + +.booth-page .filters a, +.booth-page .filters .filter-pill { + color: var(--text-link); + text-decoration: none; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + border: 1px solid transparent; + font-size: 0.9rem; +} + +.booth-page .filters a:hover { + background: var(--bg-panel); +} + +.booth-page .filters a.active { + font-weight: 700; + color: var(--accent-text); + background: var(--accent); +} + +.booth-page .table-wrap { + overflow-x: auto; + border-radius: var(--radius-lg); + border: 1px solid var(--border); +} + +.booth-page table { + width: 100%; + border-collapse: collapse; + background: var(--bg-panel); + font-size: 0.92rem; +} + +.booth-page th, +.booth-page td { + padding: var(--space-2) var(--space-3); + text-align: left; + border-bottom: 1px solid var(--border); + vertical-align: middle; +} + +.booth-page th { + background: var(--bg-page); + color: var(--text-secondary); + font-weight: 600; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.booth-page tr:hover { + background: var(--bg-hover); +} + +.booth-page .table-actions { + display: flex; + gap: var(--space-2); + align-items: center; + flex-wrap: wrap; +} + +.booth-page .table-actions a, +.booth-page .table-actions button { + padding: var(--space-1) var(--space-2); + font-size: 0.8rem; +} + +.booth-page .refresh-hint { + color: var(--text-muted); + font-size: 0.85rem; + margin-left: auto; +} + +/* --------------------------------------------- + Admin request detail page + --------------------------------------------- */ +.booth-page .section { + background: var(--bg-panel); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: var(--space-5); + margin-bottom: var(--space-4); +} + +.booth-page .section h2 { + margin-bottom: var(--space-3); +} + +.booth-page .section h3 { + color: var(--accent); + font-size: 1rem; + margin-bottom: var(--space-2); +} + +.booth-page .copy-hint { + font-size: 0.85rem; + color: var(--text-secondary); + margin-top: var(--space-1); +} + +.booth-page .actions { + display: flex; + gap: var(--space-2); + flex-wrap: wrap; + margin-top: var(--space-3); +} + +.booth-page .actions button, +.booth-page .actions .button, +.booth-page .actions .button-link { + margin-top: 0; +} + +.booth-page .two-col { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); +} + +@media (max-width: 900px) { + .booth-page .two-col { + grid-template-columns: 1fr; + } +} + +.booth-page .revision-note { + background: var(--danger-bg); + border: 1px solid var(--danger); + border-radius: var(--radius-md); + padding: var(--space-4); + margin-top: var(--space-4); +} + +.booth-page .revision-note h3 { + color: var(--danger); + margin-top: 0; +} + +.booth-page .song-row { + display: flex; + align-items: flex-start; + gap: var(--space-3); + margin: var(--space-2) 0; + padding: var(--space-2) 0; +} + +.booth-page .song-row input[type="checkbox"], +.booth-page .song-row input[type="radio"] { + margin-top: 0.2rem; +} + +.booth-page .song-row label { + margin: 0; + font-weight: 400; + line-height: 1.4; + flex: 1; +} + +.booth-page .file-select { + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: var(--space-2) var(--space-3); + margin: var(--space-2) 0; +} + +.booth-page .file-select input { + width: auto; + margin-right: var(--space-2); +} + +.booth-page .file-select label { + display: inline; + margin: 0; + font-weight: 400; +} + +.booth-page .old-rev { + font-size: 0.8rem; + color: var(--text-muted); + margin-left: 1.6rem; +} + +.booth-page .approved-box { + font-size: 1.1rem; + font-weight: 800; + color: var(--accent); +} + +.booth-page .steps { + display: flex; + list-style: none; + margin: 0; + padding: 0; + gap: var(--space-1); + align-items: flex-start; +} + +.booth-page .step-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + flex: 1; + min-width: 70px; + position: relative; +} + +.booth-page .step-item:not(:last-child)::after { + content: ""; + position: absolute; + top: 0.875rem; + left: 50%; + width: 100%; + height: 2px; + background: var(--border); + z-index: 0; +} + +.booth-page .step-item.completed:not(:last-child)::after { + background: var(--success); +} + +.booth-page .step-indicator { + width: 1.75rem; + height: 1.75rem; + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8rem; + font-weight: 700; + background: var(--border); + color: var(--text-primary); + position: relative; + z-index: 1; +} + +.booth-page .step-item.completed .step-indicator { + background: var(--success); + color: var(--accent-text); +} + +.booth-page .step-item.current .step-indicator { + background: var(--accent); + color: var(--accent-text); + box-shadow: 0 0 0 3px var(--accent-subtle); +} + +.booth-page .step-item.upcoming .step-indicator { + background: var(--border); + color: var(--text-muted); +} + +.booth-page .step-label { + font-size: 0.7rem; + margin-top: var(--space-1); + color: var(--text-secondary); +} + +.booth-page .step-item.upcoming .step-label { + color: var(--text-muted); +} + +.booth-page .history-list { + margin: 0; + padding: 0; + list-style: none; +} + +.booth-page .history-list li { + border-bottom: 1px solid var(--border); + padding: var(--space-3) 0; +} + +.booth-page .history-list li:last-child { + border-bottom: none; +} + +.booth-page .history-list .meta { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.booth-page .history-list .note { + margin: var(--space-1) 0 0 0; +} + +.booth-page .media-status { + display: flex; + align-items: flex-start; + gap: var(--space-4); + flex-wrap: wrap; +} + +.booth-page .cover-col { + flex-shrink: 0; +} + +.booth-page .cover-col img { + max-width: 160px; + border-radius: var(--radius-md); +} + +.booth-page .status-col { + flex: 1; + min-width: 220px; +} + +.booth-page .cover-hint { + font-size: 0.75rem; + color: var(--text-muted); + margin: var(--space-1) 0 0 0; + max-width: 160px; +} + +.booth-page .section-nav { + display: flex; + gap: var(--space-1); + flex-wrap: wrap; + margin-bottom: var(--space-4); +} + +.booth-page .section-nav a { + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + background: var(--bg-panel); + border: 1px solid var(--border); + font-size: 0.85rem; + font-weight: 600; + color: var(--text-secondary); +} + +.booth-page .section-nav a:hover { + background: var(--bg-hover); + color: var(--text-primary); + text-decoration: none; +} + +/* --------------------------------------------- + Player page + --------------------------------------------- */ +.booth-page .player { + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--space-4); + margin: var(--space-4) 0; +} + +.booth-page .player audio { + width: 100%; + margin-top: var(--space-2); +} + +.booth-page .lyrics { + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--space-5); + margin: var(--space-4) 0; + line-height: 1.35; +} + +.booth-page .lyrics h2 { + margin-top: 0; + margin-bottom: var(--space-4); + color: var(--accent); + font-size: 1.25rem; +} + +.booth-page .lyrics .verse { + margin-bottom: var(--space-4); +} + +.booth-page .lyrics .verse:last-child { + margin-bottom: 0; +} + +.booth-page .lyrics .lyric-line { + display: block; + margin: 0; + padding: 0; +} + +.booth-page .locked { + margin-top: var(--space-4); + padding: var(--space-5); + background: var(--bg-input); + border-radius: var(--radius-lg); + border: 1px solid var(--border); +} + +.booth-page .locked h2 { + margin-top: 0; + color: var(--accent); +} + +/* --------------------------------------------- + Kiosk + --------------------------------------------- */ +.booth-page.kiosk-page, +.booth-page.kiosk-page body { + height: 100%; + overflow: hidden; +} + +.booth-page.kiosk-page body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 2vh 3vw; + text-align: center; +} + +.booth-page .kiosk { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +} + +.booth-page .kiosk .header { + width: 100%; +} + +.booth-page .kiosk-banner { + width: 100%; + max-height: 22vh; + object-fit: contain; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); +} + +.booth-page .status-pill { + display: inline-block; + margin-top: 1.5vh; + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-full); + font-weight: 700; + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.booth-page .status-pill.open { + background: var(--success); + color: var(--accent-text); +} + +.booth-page .status-pill.closed { + background: var(--danger); + color: #fff; +} + +.booth-page .stage { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + max-width: 900px; + margin: 2vh 0; +} + +.booth-page .slide { + width: 100%; + animation: fadeIn 0.8s ease; +} + +.booth-page .slide.hidden { + display: none; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.booth-page .kiosk h1 { + margin: 0 0 1.5vh 0; + font-size: clamp(2rem, 5vw, 3.5rem); +} + +.booth-page .qr-code { + max-height: 44vh; + max-width: 44vh; + width: auto; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + background: #fff; + padding: var(--space-2); +} + +.booth-page .qr-caption { + margin-top: 1.5vh; + font-size: clamp(1.1rem, 2.5vw, 1.6rem); + color: var(--text-link); +} + +.booth-page .price-card, +.booth-page .queue-card { + background: var(--bg-panel); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + padding: 3vh 4vw; + box-shadow: var(--shadow-lg); + width: 100%; +} + +.booth-page .queue-card { + max-height: 62vh; + display: flex; + flex-direction: column; +} + +.booth-page .price-card h2, +.booth-page .queue-card h2 { + margin: 0 0 2vh 0; + font-size: clamp(1.6rem, 4vw, 2.6rem); +} + +.booth-page .price-list, +.booth-page .queue-list { + list-style: none; + margin: 0; + padding: 0; + text-align: left; +} + +.booth-page .price-list li, +.booth-page .queue-list li { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border-subtle); + padding: 1.2vh 0; + font-size: clamp(1.2rem, 3vw, 1.8rem); +} + +.booth-page .price-list li:last-child, +.booth-page .queue-list li:last-child { + border-bottom: none; +} + +.booth-page .price-list .label, +.booth-page .queue-list .customer { + color: var(--text-primary); +} + +.booth-page .price-list .amount, +.booth-page .queue-list .status { + font-weight: 700; + color: var(--accent); +} + +.booth-page .price-empty, +.booth-page .queue-empty { + font-size: 1.3rem; + color: var(--text-secondary); + text-align: center; +} + +.booth-page .queue-note { + font-size: 1rem; + color: var(--text-secondary); + margin-top: var(--space-3); + text-align: center; +} + +.booth-page .kiosk .footer { + font-size: 1rem; + color: var(--text-muted); +} + +.booth-page .dots { + display: flex; + gap: var(--space-2); + justify-content: center; + margin-top: 1vh; +} + +.booth-page .dot { + width: 12px; + height: 12px; + border-radius: var(--radius-full); + background: var(--border); + transition: background 0.3s; +} + +.booth-page .dot.active { + background: var(--accent); +} + +/* --------------------------------------------- + Settings page + --------------------------------------------- */ +.booth-page .grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-5); +} + +@media (max-width: 900px) { + .booth-page .grid { + grid-template-columns: 1fr; + } +} + +.booth-page .danger-zone { + grid-column: 1 / -1; + background: var(--danger-bg); + border: 1px solid var(--danger); + border-radius: var(--radius-lg); + padding: var(--space-5); +} + +.booth-page .danger-zone h2 { + color: var(--danger); +} + +.booth-page .danger-zone button { + background: var(--danger); +} + +.booth-page .danger-zone button:hover { + background: #f87171; +} + +.booth-page .meta-table { + width: 100%; + border-collapse: collapse; + margin-top: var(--space-2); +} + +.booth-page .meta-table td { + padding: var(--space-2); + vertical-align: top; + border-bottom: 1px solid var(--border); +} + +.booth-page .meta-table td:first-child { + width: 30%; + font-size: 0.9rem; + color: var(--text-secondary); + padding-left: 0; +} + +.booth-page .meta-table td:last-child { + padding-right: 0; +} + +.booth-page .meta-table tr:last-child td { + border-bottom: none; +} + +.booth-page .meta-table input, +.booth-page .meta-table textarea, +.booth-page .meta-table select { + margin-top: 0; +} + +.booth-page .path { + word-break: break-all; + font-family: var(--font-mono); + font-size: 0.85rem; + color: var(--text-secondary); +} + +.booth-page .version-tag { + display: inline-block; + background: var(--accent); + color: var(--accent-text); + font-size: 0.85rem; + font-weight: 700; + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-md); + margin-left: var(--space-2); + vertical-align: middle; +} + +.booth-page .status-ok { + color: var(--success); + font-weight: 600; +} + +.booth-page .status-bad { + color: var(--warning); + font-weight: 600; +} + +/* --------------------------------------------- + Utility + --------------------------------------------- */ +.booth-page .text-muted { color: var(--text-muted); } +.booth-page .text-secondary { color: var(--text-secondary); } +.booth-page .text-accent { color: var(--accent); } +.booth-page .text-danger { color: var(--danger); } +.booth-page .text-success { color: var(--success); } + +.booth-page .mb-0 { margin-bottom: 0; } +.booth-page .mt-0 { margin-top: 0; } +.booth-page .mb-2 { margin-bottom: var(--space-2); } +.booth-page .mt-2 { margin-top: var(--space-2); } +.booth-page .mb-4 { margin-bottom: var(--space-4); } +.booth-page .mt-4 { margin-top: var(--space-4); } +.booth-page .mb-6 { margin-bottom: var(--space-6); } +.booth-page .mt-6 { margin-top: var(--space-6); } + +.booth-page .text-center { text-align: center; } +.booth-page .font-mono { font-family: var(--font-mono); } + +.booth-page hr.divider { + border: none; + border-top: 1px solid var(--border); + margin: var(--space-4) 0; +} diff --git a/templates/admin/dashboard.html b/templates/admin/dashboard.html index ce57a0a..09a932c 100644 --- a/templates/admin/dashboard.html +++ b/templates/admin/dashboard.html @@ -7,127 +7,35 @@ {% if refresh_seconds and refresh_seconds > 0 %} {% endif %} + + + + - -
- + +
- Kiosk - Pricing - Sales - Settings - Log out +

Theme Song Booth — Admin Dashboard

+
-

Theme Song Booth — Admin Dashboard

- {% with messages = get_flashed_messages(with_categories=true) %} {% for category, message in messages %}
{{ message }}
{% endfor %} {% endwith %} -
All Pending @@ -135,7 +43,7 @@ Awaiting Payment Delivered
- +
{% if refresh_seconds and refresh_seconds > 0 %} @@ -146,41 +54,60 @@
- - - - - - - - - - - - - - - {% for r in requests %} - - - - - - - - - - {% endfor %} - {% if not requests %} - - {% endif %} - -
IDNameEmailGenreStatusApprovedActions
#{{ r.id }}{{ r.name }}{{ r.email }}{{ r.style_genre or '-' }}{{ statuses[r.status] }}{% if r.customer_approved and r.customer_approved != 'none' %}{{ (r.customer_approved or 'none').upper() }}{% else %}-{% endif %} - Open -
- -
-
No requests found.
+
+ + + + + + + + + + + + + + {% for r in requests %} + + + + + + + + + + {% endfor %} + {% if not requests %} + + {% endif %} + +
IDNameEmailGenreStatusApprovedActions
#{{ r.id }}{{ r.name }}{{ r.email }}{{ r.style_genre or '-' }}{{ statuses[r.status] }}{% if r.customer_approved and r.customer_approved != 'none' %}{{ (r.customer_approved or 'none').upper() }}{% else %}-<{% endif %} + Open + {% if r.status in ('pending', 'revisions_requested') %} + Prompt + {% endif %} + {% if r.status == 'prompt_ready' and not r.musicgpt_status %} +
+ + +
+ {% endif %} + {% if r.status == 'songs_uploaded' and r.customer_approved == 'none' %} +
+ + +
+ {% endif %} + {% if r.status == 'awaiting_payment' %} + Deliver + {% endif %} +
+ +
+
No requests found.
+
diff --git a/templates/admin/request.html b/templates/admin/request.html index 2e0aec7..ccbaf9a 100644 --- a/templates/admin/request.html +++ b/templates/admin/request.html @@ -4,255 +4,40 @@ Request #{{ req.id }} — Admin - + + + + - -
-

← Dashboard

+ +
+

← Dashboard

Request #{{ req.id }} — {{ req.name }}

- -
+ + + +
{% if req.album_cover_url %}
- Album cover + Album cover {% if not deliver_album_cover %} -

Album cover is hidden from the customer preview/delivery because delivery is disabled in settings.

+

Album cover is hidden from the customer preview/delivery because delivery is disabled in settings.

{% endif %}
{% endif %}
-

Request Status

+

Request Status

{% if current_step == -1 %} -

Cancelled

+

Cancelled

{% else %}
    {% for i in range(steps|length) %} @@ -261,9 +46,7 @@ {% set is_completed = i < current_step or (is_last and current_step == i) %} {% set is_current = i == current_step and not is_last %}
  1. -
    - {% if is_completed %}✓{% else %}{{ i + 1 }}{% endif %} -
    +
    {% if is_completed %}✓{% else %}{{ i + 1 }}{% endif %}
    {{ label }}
  2. {% endfor %} @@ -271,47 +54,33 @@ {% endif %} {% if req.musicgpt_status %} -

    Recording Studio Status

    +

    Recording Studio Status

      - {% set mg_steps = [ - ('IN_QUEUE', 'Queued'), - ('IN_PROGRESS', 'Generating'), - ('COMPLETED', 'Completed') - ] %} - {% set current_mg = req.musicgpt_status %} - {% set ns = namespace(mg_index=-1) %} {% for j in range(mg_steps|length) %} - {% if mg_steps[j][0] == current_mg %}{% set ns.mg_index = j %}{% endif %} - {% endfor %} - {% if current_mg in ('FAILED', 'ERROR', 'CANCELLED') %}{% set ns.mg_index = -2 %}{% endif %} - {% set mg_completed_step = req.musicgpt_status in ('COMPLETED', 'FINISHED') %} - {% for j in range(mg_steps|length) %} - {% set mg_key, mg_label = mg_steps[j] %} - {% set mg_completed = j < ns.mg_index or mg_completed_step %} - {% set mg_current = j == ns.mg_index and not mg_completed_step %} -
    1. -
      - {% if mg_completed %}✓{% else %}{{ j + 1 }}{% endif %} -
      -
      {{ mg_label }}
      -
    2. + {% set mg_key, mg_label = mg_steps[j] %} +
    3. +
      {% if mg_index > j or mg_completed %}✓{% else %}{{ j + 1 }}{% endif %}
      +
      {{ mg_label }}
      +
    4. {% endfor %}
    + {% set total_cost = (req.musicgpt_cost or 0) + (req.stems_cost or 0) %} {% if total_cost > 0 %} -

    LLM Cost: {{ format_musicgpt_cost(total_cost) }} - {% if req.stems_cost %} - (songs: {{ format_musicgpt_cost(req.musicgpt_cost) }} + stems: {{ format_musicgpt_cost(req.stems_cost) }}) - {% endif %} +

    LLM Cost: {{ format_musicgpt_cost(total_cost) }} + {% if req.stems_cost %} + (songs: {{ format_musicgpt_cost(req.musicgpt_cost) }} + stems: {{ format_musicgpt_cost(req.stems_cost) }}) + {% endif %}

    {% elif req.musicgpt_cost is not none %} -

    LLM Cost: {{ format_musicgpt_cost(req.musicgpt_cost) }}

    +

    LLM Cost: {{ format_musicgpt_cost(req.musicgpt_cost) }}

    {% endif %} {% endif %} + {% if req.status != 'cancelled' %} -
    + - +
    {% endif %}
@@ -327,8 +96,7 @@
- -
+

Customer Info

Operators can correct customer details here. Click Save when done.

@@ -340,7 +108,7 @@ - + - + - + -

Stored style: {{ req.style_genre or '—' }}

+

Stored style: {{ req.style_genre or '—' }}

-
- -
+

Revision History

{% if revision_history %}
    @@ -437,8 +204,7 @@ {% endif %}
- -
+

Operator Notes

Internal notes for the booth team. Customers cannot see this.

@@ -446,13 +212,12 @@
- +
- -
+

Stems / Extras Link

Auto-generated from Gokapi when stems are completed. You can also paste a custom share link here — it will appear in the delivery email.

@@ -460,9 +225,9 @@
- + {% if req.stems_url and not req.stems_link %} - + {% endif %}
@@ -471,12 +236,11 @@
- -
+

1. Generate & Save Music Prompt

- +
- +

Paste Hermes' Title, Style, and Lyrics directly into the fields below, then save or generate.

@@ -493,14 +257,14 @@
- - - + + +
-
+
@@ -515,8 +279,8 @@ {% endfor %} -
- -
+

2. Songs

- +
- +
- +
-
+
@@ -584,44 +345,37 @@
-
+
-
-

Select source MP3 for stems extraction:

+

Select source MP3 for stems extraction:

{% set a_ready = req.song_a_path and file_exists(req.song_a_path) %} {% set b_ready = req.song_b_path and file_exists(req.song_b_path) %}
- -
- -

Vocal + instrumental extraction: ~$0.018–$0.084 USD per song on Pro plan.

-

+

{% if req.stems_status %} {{ req.stems_status }} {% if req.stems_cost is not none %}({{ format_musicgpt_cost(req.stems_cost) }}){% endif %} @@ -634,11 +388,11 @@ {% endif %} {% if req.stems_link %}

{% elif req.stems_url %} {% endif %} @@ -649,35 +403,33 @@
- -
+

3. Notify Customer

-

+

Preview email: {% if req.preview_sent_at %} - ✅ Sent {{ req.preview_sent_at|regina_dt }} + Sent {{ req.preview_sent_at|regina_dt }} {% else %} - ❌ Not sent yet + Not sent yet {% endif %}

-

Both songs must be uploaded first.

+

Both songs must be uploaded first.

{% if req.song_a_path and req.song_b_path %} -

+

Operator preview link: Open customer player in new tab →

{% endif %}
- -
+

4. Payment & Delivery

-

+

Customer approved: {% if req.customer_approved == 'none' %} Nothing yet @@ -688,34 +440,33 @@

Delivery email: {% if req.delivery_sent_at %} - ✅ Sent {{ req.delivery_sent_at|regina_dt }} + Sent {{ req.delivery_sent_at|regina_dt }} {% else %} - ❌ Not sent yet + Not sent yet {% endif %}

{% if req.square_payment_ref %}

Square Payment Reference: {{ req.square_payment_ref }}

{% endif %}
-
- +
-
+
-

Delivery options:

-