From 832068a61f5019119758c007a890a14c7ba51bd7 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Wed, 25 Mar 2026 21:04:12 +0100 Subject: [PATCH] refactor: centralize common styles and utilities in pmocontrol.css - Move protocol badge styles to global pmocontrol.css - Extract .section-title, .icon-btn, and scrollbar utilities into pmocontrol.css - Add --color-primary-rgb variable for rgba() usage - Deprecate duplicate .protocol-* and @keyframes spin definitions across components - Update version to 0.3.29 --- Cargo.lock | 2 +- PMOMusic/Cargo.toml | 2 +- pmoapp/webapp/src/assets/styles/drawers.css | 2 + .../webapp/src/assets/styles/pmocontrol.css | 119 ++++++++++++++++++ pmoapp/webapp/src/assets/styles/variables.css | 2 + .../components/pmocontrol/MediaBrowser.vue | 9 +- .../components/pmocontrol/RendererCard.vue | 18 +-- .../src/components/unified/BottomTabBar.vue | 35 +----- .../src/components/unified/EmptyState.vue | 9 +- .../src/components/unified/RendererDrawer.vue | 35 +----- .../src/components/unified/ServerDrawer.vue | 6 +- .../src/components/upnp/VariablesList.vue | 3 - version.txt | 2 +- 13 files changed, 132 insertions(+), 112 deletions(-) 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 index e442aaa3..977c3b58 100644 --- a/pmoapp/webapp/src/assets/styles/drawers.css +++ b/pmoapp/webapp/src/assets/styles/drawers.css @@ -153,6 +153,7 @@ CONTENU ======================================== */ +/* Add class="scrollbar-thin" on elements needing thin scrollbar (see pmocontrol.css) */ .drawer-content { flex: 1; overflow-y: auto; @@ -163,6 +164,7 @@ 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; 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/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 31abd54f..48d6383e 100644 --- a/pmoapp/webapp/src/components/unified/BottomTabBar.vue +++ b/pmoapp/webapp/src/components/unified/BottomTabBar.vue @@ -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 82a4e443..acff5f53 100644 --- a/pmoapp/webapp/src/components/unified/RendererDrawer.vue +++ b/pmoapp/webapp/src/components/unified/RendererDrawer.vue @@ -594,40 +594,7 @@ async function handleTransferQueue(event: Event, targetRendererId: string) { flex-shrink: 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 */ /* Transport button */ .transport-btn { diff --git a/pmoapp/webapp/src/components/unified/ServerDrawer.vue b/pmoapp/webapp/src/components/unified/ServerDrawer.vue index d6da423a..b400cfbb 100644 --- a/pmoapp/webapp/src/components/unified/ServerDrawer.vue +++ b/pmoapp/webapp/src/components/unified/ServerDrawer.vue @@ -1149,11 +1149,7 @@ function handleSettingsClick() { animation: spin 0.8s linear infinite; } -@keyframes spin { - to { - transform: rotate(360deg); - } -} +/* @keyframes spin is now global in pmocontrol.css */ .loading-state p { margin: 0; diff --git a/pmoapp/webapp/src/components/upnp/VariablesList.vue b/pmoapp/webapp/src/components/upnp/VariablesList.vue index ff11a443..d85384f0 100644 --- a/pmoapp/webapp/src/components/upnp/VariablesList.vue +++ b/pmoapp/webapp/src/components/upnp/VariablesList.vue @@ -215,9 +215,6 @@ watch(editingVar, (newVar) => { margin-bottom: 1rem; } -@keyframes spin { - to { transform: rotate(360deg); } -} /* Error state */ .error-state { diff --git a/version.txt b/version.txt index 9578e940..a6c8cf78 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.28 +0.3.29