diff --git a/Cargo.lock b/Cargo.lock index 86e8b4a3..5b716822 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.28" +version = "0.3.29" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index bd17d142..e254a3fe 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.28" +version = "0.3.29" edition = "2024" [dependencies] diff --git a/pmoapp/webapp/src/assets/styles/drawers.css b/pmoapp/webapp/src/assets/styles/drawers.css new file mode 100644 index 00000000..977c3b58 --- /dev/null +++ b/pmoapp/webapp/src/assets/styles/drawers.css @@ -0,0 +1,272 @@ +/** + * Styles communs pour les drawers (ServerDrawer, RendererDrawer) + * + * Chaque drawer ajoute uniquement ce qui le différencie : + * - position (left/right) + * - border (border-right / border-left) + * - box-shadow (direction) + * - backdrop spécifique (position) + */ + +/* ======================================== + BACKDROP + ======================================== */ + +.drawer-backdrop { + position: fixed; + top: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.35); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + z-index: 200; +} + +@media (max-width: 768px) and (orientation: portrait) { + .drawer-backdrop { + display: none; + } +} + +/* ======================================== + PANNEAU DRAWER + ======================================== */ + +.app-drawer { + position: fixed; + top: 0; + bottom: 0; + width: 50vw; + /* Fond opaque — évite la transparence empilée sur d'autres éléments glass */ + background: rgba(22, 22, 32, 0.96); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + z-index: 201; + display: flex; + flex-direction: column; + overflow: hidden; +} + +@media (prefers-color-scheme: light) { + .app-drawer { + background: rgba(245, 245, 250, 0.97); + } +} + +@media (max-width: 768px) and (orientation: portrait) { + .app-drawer { + width: 100vw; + } +} + +/* ======================================== + HEADER + ======================================== */ + +.drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-sm); + padding: var(--spacing-lg); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + flex-shrink: 0; +} + +@media (prefers-color-scheme: light) { + .drawer-header { + border-bottom-color: rgba(0, 0, 0, 0.1); + } +} + +.drawer-title-section { + display: flex; + align-items: center; + gap: var(--spacing-sm); + color: var(--color-text); + flex: 1; + min-width: 0; +} + +.drawer-title { + font-size: var(--text-xl); + font-weight: 700; + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.drawer-title.small { + font-size: var(--text-base); +} + +/* Bouton fermer */ +.drawer-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; + padding: 0; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; + cursor: pointer; + transition: all var(--transition-fast) ease; + color: var(--color-text); +} + +.drawer-close-btn:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +.drawer-close-btn:active { + transform: scale(0.95); +} + +/* Bouton retour (ServerDrawer navigation) */ +.drawer-back-btn { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + flex-shrink: 0; + padding: 0; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; + cursor: pointer; + transition: all var(--transition-fast) ease; + color: var(--color-text); +} + +.drawer-back-btn:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.05); +} + +/* ======================================== + CONTENU + ======================================== */ + +/* Add class="scrollbar-thin" on elements needing thin scrollbar (see pmocontrol.css) */ +.drawer-content { + flex: 1; + overflow-y: auto; + padding: var(--spacing-md); +} + +.drawer-section { + margin-bottom: var(--spacing-lg); +} + +/* TODO: remplacer par la classe globale .section-title de pmocontrol.css */ +.drawer-section-title { + font-size: var(--text-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--color-text-secondary); + margin: 0 0 var(--spacing-sm) 0; + padding: 0 var(--spacing-sm); +} + +/* Liste d'items générique */ +.drawer-item-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; +} + +.drawer-item { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-md); + border-radius: 12px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + transition: all var(--transition-fast) ease; +} + +.drawer-item.clickable { + cursor: pointer; +} + +.drawer-item.clickable:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.2); +} + +.drawer-item.clickable:active { + background: rgba(255, 255, 255, 0.08); +} + +.drawer-item.offline { + opacity: var(--opacity-disabled, 0.5); + cursor: not-allowed; +} + +/* Icône dans un item */ +.drawer-item-icon { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; + border-radius: 8px; + background: rgba(255, 255, 255, 0.1); + color: var(--color-text-secondary); +} + +/* Info texte dans un item */ +.drawer-item-info { + flex: 1; + min-width: 0; +} + +.drawer-item-name { + font-size: var(--text-base); + font-weight: 600; + color: var(--color-text); + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.drawer-item-sub { + font-size: var(--text-sm); + color: var(--color-text-secondary); + margin: 2px 0 0 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ======================================== + FOOTER + ======================================== */ + +.drawer-footer { + padding: var(--spacing-md); + border-top: 1px solid rgba(255, 255, 255, 0.1); + flex-shrink: 0; +} + +@media (prefers-color-scheme: light) { + .drawer-footer { + border-top-color: rgba(0, 0, 0, 0.1); + } +} + +/* Les animations d'entrée/sortie sont définies dans chaque composant (scoped) + car elles diffèrent selon la direction (gauche/droite/bas) */ diff --git a/pmoapp/webapp/src/assets/styles/pmocontrol.css b/pmoapp/webapp/src/assets/styles/pmocontrol.css index 00cd4dbb..dbd85493 100644 --- a/pmoapp/webapp/src/assets/styles/pmocontrol.css +++ b/pmoapp/webapp/src/assets/styles/pmocontrol.css @@ -434,3 +434,122 @@ input[type="range"]::-moz-range-thumb:hover { white-space: nowrap; border-width: 0; } + +/* ======================================== + UTILITIES + ======================================== */ + +/* Section title (used in drawers and panels) */ +.section-title { + font-size: var(--text-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--color-text-secondary); + margin: 0 0 var(--spacing-sm) 0; +} + +/* Icon-only circular button */ +.icon-btn { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; + cursor: pointer; + color: var(--color-text); + transition: background var(--transition-fast) ease, transform var(--transition-fast) ease; +} + +.icon-btn:hover { + background: rgba(255, 255, 255, 0.2); +} + +.icon-btn:active { + transform: scale(0.92); +} + +@media (prefers-color-scheme: light) { + .icon-btn { + background: rgba(0, 0, 0, 0.06); + border-color: rgba(0, 0, 0, 0.12); + } + .icon-btn:hover { + background: rgba(0, 0, 0, 0.12); + } +} + +/* ======================================== + PROTOCOL BADGES (global, used in BottomTabBar, RendererDrawer, RendererCard) + ======================================== */ + +.protocol-badge { + display: inline-flex; + align-items: center; + padding: 0.2rem 0.5rem; + border-radius: var(--radius-full); + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border: 1px solid transparent; +} + +.protocol-upnp { + background-color: rgba(59, 130, 246, 0.15); + color: #3b82f6; + border-color: rgba(59, 130, 246, 0.3); +} + +.protocol-openhome { + background-color: rgba(139, 92, 246, 0.15); + color: #8b5cf6; + border-color: rgba(139, 92, 246, 0.3); +} + +.protocol-hybrid { + background-color: rgba(16, 185, 129, 0.15); + color: #10b981; + border-color: rgba(16, 185, 129, 0.3); +} + +.protocol-chromecast { + background-color: rgba(245, 158, 11, 0.15); + color: #f59e0b; + border-color: rgba(245, 158, 11, 0.3); +} + +/* ======================================== + SCROLLBAR (webkit) + ======================================== */ + +.scrollbar-thin::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.scrollbar-thin::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 3px; +} + +.scrollbar-thin::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; +} + +.scrollbar-thin::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* ======================================== + ANIMATIONS + ======================================== */ + +@keyframes pulse-opacity { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} diff --git a/pmoapp/webapp/src/assets/styles/variables.css b/pmoapp/webapp/src/assets/styles/variables.css index e3569395..f9c0c982 100644 --- a/pmoapp/webapp/src/assets/styles/variables.css +++ b/pmoapp/webapp/src/assets/styles/variables.css @@ -78,6 +78,8 @@ /* Primary color (brand) */ --color-primary: #667eea; --color-primary-hover: #5568d3; + /* Primary color RGB components (for rgba() usage) */ + --color-primary-rgb: 102, 126, 234; /* ======================================== Transitions diff --git a/pmoapp/webapp/src/components/PlayListManager.vue b/pmoapp/webapp/src/components/PlayListManager.vue index d4385c6f..7b2debe3 100644 --- a/pmoapp/webapp/src/components/PlayListManager.vue +++ b/pmoapp/webapp/src/components/PlayListManager.vue @@ -1412,17 +1412,17 @@ onMounted(() => { -.detail-header-main { display: flex; flex-direction: column; gap: 0.25rem; } diff --git a/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue b/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue index c665b17a..9c88bda0 100644 --- a/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue +++ b/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue @@ -257,14 +257,7 @@ async function handleQueueItem(itemId: string, rendererId: string) { animation: spin 1s linear infinite; } -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} +/* @keyframes spin is now global in pmocontrol.css */ /* Error */ .browser-error { diff --git a/pmoapp/webapp/src/components/pmocontrol/RendererCard.vue b/pmoapp/webapp/src/components/pmocontrol/RendererCard.vue index d3d38072..3dfdf581 100644 --- a/pmoapp/webapp/src/components/pmocontrol/RendererCard.vue +++ b/pmoapp/webapp/src/components/pmocontrol/RendererCard.vue @@ -253,23 +253,7 @@ function goToRenderer() { letter-spacing: 0.05em; } -.protocol-upnp { - background-color: rgba(59, 130, 246, 0.1); - color: #3b82f6; - border: 1px solid #3b82f6; -} - -.protocol-openhome { - background-color: rgba(139, 92, 246, 0.1); - color: #8b5cf6; - border: 1px solid #8b5cf6; -} - -.protocol-hybrid { - background-color: rgba(16, 185, 129, 0.1); - color: #10b981; - border: 1px solid #10b981; -} +/* Protocol badge colors (.protocol-upnp, .protocol-openhome, etc.) → pmocontrol.css */ /* Cover */ .card-cover { diff --git a/pmoapp/webapp/src/components/unified/BottomTabBar.vue b/pmoapp/webapp/src/components/unified/BottomTabBar.vue index 67d616a2..48d6383e 100644 --- a/pmoapp/webapp/src/components/unified/BottomTabBar.vue +++ b/pmoapp/webapp/src/components/unified/BottomTabBar.vue @@ -148,18 +148,18 @@ function handleRendererDrawerClick() { gap: var(--spacing-md); height: 72px; padding: 0 var(--spacing-md); - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(30px) saturate(180%); - -webkit-backdrop-filter: blur(30px) saturate(180%); - border-top: 1px solid rgba(255, 255, 255, 0.2); - box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.1); + background: rgba(22, 22, 32, 0.96); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + border-top: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.3); z-index: 100; } -@media (prefers-color-scheme: dark) { +@media (prefers-color-scheme: light) { .bottom-bar { - background: rgba(0, 0, 0, 0.25); - border-top: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(245, 245, 250, 0.97); + border-top-color: rgba(0, 0, 0, 0.1); } } @@ -293,40 +293,7 @@ function handleRendererDrawerClick() { margin: 0; } -/* Protocol badge */ -.protocol-badge { - padding: 2px 8px; - border-radius: 4px; - font-size: 10px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - flex-shrink: 0; -} - -.protocol-upnp { - background-color: rgba(59, 130, 246, 0.15); - color: #3b82f6; - border: 1px solid rgba(59, 130, 246, 0.3); -} - -.protocol-openhome { - background-color: rgba(139, 92, 246, 0.15); - color: #8b5cf6; - border: 1px solid rgba(139, 92, 246, 0.3); -} - -.protocol-hybrid { - background-color: rgba(16, 185, 129, 0.15); - color: #10b981; - border: 1px solid rgba(16, 185, 129, 0.3); -} - -.protocol-chromecast { - background-color: rgba(244, 114, 182, 0.15); - color: #f472b6; - border: 1px solid rgba(244, 114, 182, 0.3); -} +/* Protocol badge classes (.protocol-badge, .protocol-upnp, etc.) are now global in pmocontrol.css */ .status-badge { flex-shrink: 0; diff --git a/pmoapp/webapp/src/components/unified/EmptyState.vue b/pmoapp/webapp/src/components/unified/EmptyState.vue index 7904367c..36ef09b8 100644 --- a/pmoapp/webapp/src/components/unified/EmptyState.vue +++ b/pmoapp/webapp/src/components/unified/EmptyState.vue @@ -153,14 +153,7 @@ async function handleRefresh() { animation: spin 1s linear infinite; } -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} +/* @keyframes spin is now global in pmocontrol.css */ /* Responsive mobile */ @media (max-width: 768px) { diff --git a/pmoapp/webapp/src/components/unified/RendererDrawer.vue b/pmoapp/webapp/src/components/unified/RendererDrawer.vue index 8eb0a19b..acff5f53 100644 --- a/pmoapp/webapp/src/components/unified/RendererDrawer.vue +++ b/pmoapp/webapp/src/components/unified/RendererDrawer.vue @@ -138,7 +138,7 @@ function handleRendererClick(renderer: RendererSummary) { } function handleSettingsClick() { - router.push("/debug/api-dashboard"); + router.push("/debug"); close(); } @@ -210,7 +210,7 @@ async function handleTransferQueue(event: Event, targetRendererId: string) { -