Files
lidify/frontend/app/globals.css
Your Name cc8d0f6969 Release v1.3.0: Multi-source downloads, audio analyzer resilience, mobile improvements
Major Features:
- Multi-source download system (Soulseek/Lidarr with fallback)
- Configurable enrichment speed control (1-5x)
- Mobile touch drag support for seek sliders
- iOS PWA media controls (Control Center, Lock Screen)
- Artist name alias resolution via Last.fm
- Circuit breaker pattern for audio analysis

Critical Fixes:
- Audio analyzer stability (non-ASCII, BrokenProcessPool, OOM)
- Discovery system race conditions and import failures
- Radio decade categorization using originalYear
- LastFM API response normalization
- Mood bucket infinite loop prevention

Security:
- Bull Board admin authentication
- Lidarr webhook signature verification
- JWT token expiration and refresh
- Encryption key validation on startup

Closes #2, #6, #9, #13, #21, #26, #31, #34, #35, #37, #40, #43
2026-01-06 20:07:33 -06:00

564 lines
12 KiB
CSS

@import "tailwindcss";
:root {
/* Lidify Design System - Dark Theme Only */
/* Background Layers */
--bg-primary: #0a0a0a; /* Primary canvas */
--bg-secondary: #0f0f0f; /* Cards, panels */
--bg-tertiary: #141414; /* Nested content, wells */
--bg-hover: #1a1a1a; /* Modals, dropdowns, hover */
--bg-active: #1c1c1c; /* Active/selected states */
/* Borders */
--border-subtle: #1c1c1c; /* Subtle borders (default) */
--border-interactive: #262626; /* Interactive elements */
--border-focus: #333333; /* Focus, selected states */
/* Text Colors */
--text-primary: #ffffff; /* Primary headers */
--text-body: #e5e5e5; /* Body text */
--text-secondary: #a3a3a3; /* Secondary info */
--text-muted: #737373; /* Timestamps, meta */
--text-disabled: #525252; /* Disabled */
/* Actions */
--color-primary: #eab308; /* PRIMARY CTA ONLY - Yellow */
--color-ai: #a855f7; /* AI features - Purple */
--color-ai-dark: #9333ea; /* AI selected */
/* Status */
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
}
* {
box-sizing: border-box;
}
body {
background: var(--bg-primary);
color: var(--text-body);
font-family: var(--font-montserrat), -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
min-height: 100vh;
}
/* Make all buttons have pointer cursor */
button {
cursor: pointer;
}
button:disabled {
cursor: not-allowed;
}
/* Hide scrollbar utility - for horizontal scroll areas */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
/* Scrollbar Styling - Desktop only */
@media (min-width: 769px) {
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: var(--bg-primary);
}
::-webkit-scrollbar-thumb {
background: var(--bg-hover);
border-radius: 6px;
border: 2px solid var(--bg-primary);
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-focus);
}
}
/* Hide scrollbars on mobile and tablet */
@media (max-width: 768px) {
::-webkit-scrollbar {
display: none;
}
* {
scrollbar-width: none;
}
}
/* Animated Gradient Background */
@keyframes gradient-shift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.animate-gradient-shift {
background-size: 200% 200%;
animation: gradient-shift 15s ease infinite;
}
/* Fade in animation for login page artist info */
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fade-in 0.6s ease-out forwards;
}
/* Shake animation for error messages */
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-4px);
}
20%,
40%,
60%,
80% {
transform: translateX(4px);
}
}
.animate-shake {
animation: shake 0.5s ease-in-out;
}
/* Galaxy Background Animations */
@keyframes galaxyFloat {
0%,
100% {
transform: translateY(0px) translateX(0px);
opacity: 0.4;
}
25% {
transform: translateY(-25px) translateX(15px);
opacity: 0.8;
}
50% {
transform: translateY(-15px) translateX(-15px);
opacity: 0.5;
}
75% {
transform: translateY(-20px) translateX(8px);
opacity: 0.7;
}
}
@keyframes galaxyTwinkle {
0%,
100% {
opacity: 0.2;
transform: scale(1);
}
50% {
opacity: 0.8;
transform: scale(1.2);
}
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
/* =====================================================
Accessibility - Reduced Motion Support
===================================================== */
/* Respect user's motion preferences */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* =====================================================
Android TV - Navigation Bar & Now Playing Only
===================================================== */
/* TV mode base setup */
html.tv-mode,
body.tv-mode {
background: linear-gradient(180deg, #1a1020 0%, #0a0a0a 100%) !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
width: 100vw !important;
height: 100vh !important;
}
body.tv-mode {
display: flex !important;
flex-direction: column !important;
}
body.tv-mode ::-webkit-scrollbar {
display: none;
}
body.tv-mode * {
scrollbar-width: none;
}
/* TV Content Area */
.tv-content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
/* ===== TV NAVIGATION BAR ===== */
.tv-nav {
height: 56px;
padding: 0 40px;
display: flex;
align-items: center;
gap: 20px;
flex-shrink: 0;
background: transparent;
}
.tv-logo {
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.tv-logo span {
font-size: 18px;
font-weight: 700;
color: #fff;
}
.tv-nav-links {
display: flex;
gap: 4px;
}
.tv-nav-link {
padding: 6px 14px;
font-size: 14px;
color: #777;
text-decoration: none;
border-radius: 4px;
transition: color 0.15s, background 0.15s;
}
.tv-nav-link.active {
color: #fff;
}
.tv-nav-link.focused {
color: #ecb200;
background: rgba(236, 178, 0, 0.1);
}
.tv-sync-btn {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
color: #fff;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s;
}
.tv-sync-btn:hover,
.tv-sync-btn:focus {
background: rgba(255, 255, 255, 0.2);
outline: 2px solid #ecb200;
}
.tv-sync-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ===== TV NOW PLAYING BAR ===== */
.tv-now-playing-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 40px;
background: rgba(255, 255, 255, 0.03);
flex-shrink: 0;
}
.tv-np-cover {
width: 48px;
height: 48px;
border-radius: 6px;
object-fit: cover;
}
.tv-np-info {
flex: 1;
min-width: 0;
}
.tv-np-title {
font-size: 14px;
font-weight: 600;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tv-np-artist {
font-size: 12px;
color: #888;
}
.tv-np-time {
font-size: 12px;
color: #888;
margin-right: 16px;
font-variant-numeric: tabular-nums;
}
/* Play/Pause button - main circular button */
.tv-np-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: #ecb200;
color: #000;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.15s, background 0.15s;
}
.tv-np-btn svg {
width: 16px !important;
height: 16px !important;
}
.tv-np-btn:hover {
background: #ffc107;
transform: scale(1.05);
}
/* Control buttons (shuffle, prev, next, repeat) */
.tv-np-ctrl {
width: 32px;
height: 32px;
border-radius: 50%;
background: transparent;
color: #888;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: color 0.15s, background 0.15s;
position: relative;
}
.tv-np-ctrl:hover,
.tv-np-ctrl:focus {
color: #fff;
background: rgba(255, 255, 255, 0.1);
}
.tv-np-ctrl.active {
color: #ecb200;
}
.tv-np-ctrl.active:hover {
color: #ffc107;
}
/* Repeat "1" indicator */
.tv-np-repeat-one {
position: absolute;
bottom: 2px;
right: 2px;
font-size: 8px;
font-weight: bold;
color: #ecb200;
}
/* ===== TV FOCUS STATES ===== */
/* Card focus styling - visible ring around focused cards */
body.tv-mode [data-tv-card]:focus {
outline: none;
box-shadow: 0 0 0 3px #ecb200, 0 0 20px rgba(236, 178, 0, 0.4);
transform: scale(1.02);
transition: box-shadow 0.15s ease, transform 0.15s ease;
z-index: 10;
position: relative;
}
body.tv-mode [data-tv-card]:focus-visible {
outline: none;
}
/* Buttons in TV mode */
body.tv-mode button:focus,
body.tv-mode a:focus {
outline: 2px solid #ecb200;
outline-offset: 2px;
}
/* Remove default focus outlines for cards since we use box-shadow */
body.tv-mode [data-tv-card] {
outline: none;
transition: box-shadow 0.15s ease, transform 0.15s ease;
}
/* Filter buttons / tabs focus */
body.tv-mode [data-tv-filter]:focus {
outline: none;
background: rgba(236, 178, 0, 0.2);
color: #ecb200;
box-shadow: 0 0 0 2px #ecb200;
}
/* Track list items focus */
body.tv-mode [data-tv-track]:focus {
outline: none;
background: rgba(236, 178, 0, 0.15);
box-shadow: inset 0 0 0 2px #ecb200;
}
/* Action buttons in action bars */
body.tv-mode [data-tv-action]:focus {
outline: none;
box-shadow: 0 0 0 2px #ecb200;
transform: scale(1.05);
}
/* Smooth scrolling for TV */
body.tv-mode .tv-content {
scroll-behavior: smooth;
}
/* Hide scrollbar in TV mode */
body.tv-mode ::-webkit-scrollbar {
display: none;
}
body.tv-mode * {
scrollbar-width: none;
}
/* ===== QR Scanner Mode ===== */
/* When QR scanner is active, hide the WebView content to show camera */
body.scanner-active {
background: transparent !important;
}
body.scanner-active > * {
visibility: hidden;
}
body.scanner-active .scanner-overlay {
visibility: visible;
}
/* ===== PWA Standalone Mode Styles ===== */
/* Safe area insets for notched devices and PWA window controls */
:root {
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-left: env(safe-area-inset-left, 0px);
--safe-area-right: env(safe-area-inset-right, 0px);
/* Window Controls Overlay (Desktop PWA title bar) */
--titlebar-height: env(titlebar-area-height, 0px);
--titlebar-width: env(titlebar-area-width, 100%);
--titlebar-x: env(titlebar-area-x, 0px);
--titlebar-y: env(titlebar-area-y, 0px);
}
/* When running as installed PWA */
@media all and (display-mode: standalone) {
/* Ensure body fills viewport properly */
html,
body {
height: 100%;
overflow: hidden;
}
/* Add padding for safe areas on iOS */
body {
padding-top: var(--safe-area-top);
padding-bottom: var(--safe-area-bottom);
padding-left: var(--safe-area-left);
padding-right: var(--safe-area-right);
}
}
/* Window Controls Overlay mode (Desktop PWA with custom title bar) */
@media all and (display-mode: window-controls-overlay) {
/* The top bar extends into the title bar area */
body {
padding-top: 0;
}
/* Make the top bar draggable like a native title bar */
.pwa-titlebar-drag {
-webkit-app-region: drag;
app-region: drag;
}
/* Buttons and interactive elements should not be draggable */
.pwa-titlebar-drag button,
.pwa-titlebar-drag a,
.pwa-titlebar-drag input {
-webkit-app-region: no-drag;
app-region: no-drag;
}
}