diff --git a/media_server/static/css/styles.css b/media_server/static/css/styles.css index 53e7fc0..7ad7668 100644 --- a/media_server/static/css/styles.css +++ b/media_server/static/css/styles.css @@ -2719,7 +2719,7 @@ footer .separator { .browser-container { background: var(--bg-secondary); border-radius: 12px; - padding: 1rem; + padding: 1.25rem; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); } @@ -2740,14 +2740,20 @@ footer .separator { .breadcrumb { display: flex; align-items: center; - gap: 0.5rem; + gap: 0.25rem; margin-bottom: 1rem; - padding: 0.75rem; + padding: 0.5rem 0.75rem; background: var(--bg-tertiary); - border-radius: 6px; - font-size: 0.875rem; + border-radius: 8px; + font-size: 0.813rem; overflow-x: auto; white-space: nowrap; + scrollbar-width: none; + border: 1px solid var(--border); +} + +.breadcrumb::-webkit-scrollbar { + display: none; } .breadcrumb:empty { @@ -2755,28 +2761,44 @@ footer .separator { } .breadcrumb-item { - color: var(--accent); + color: var(--text-secondary); cursor: pointer; - transition: color 0.2s; + transition: all 0.2s; + padding: 0.2rem 0.5rem; + border-radius: 4px; + font-weight: 500; } .breadcrumb-item:hover { - color: var(--accent-hover); - text-decoration: underline; + color: var(--accent); + background: rgba(29, 185, 84, 0.08); + text-decoration: none; +} + +.breadcrumb-item:last-child { + color: var(--text-primary); + font-weight: 600; + cursor: default; + pointer-events: none; } .breadcrumb-home { display: flex; align-items: center; + padding: 0.25rem; + color: var(--text-muted); } .breadcrumb-home:hover { text-decoration: none; + color: var(--accent); } .breadcrumb-separator { color: var(--text-muted); - margin: 0 0.25rem; + margin: 0; + opacity: 0.5; + font-size: 0.75rem; } /* Browser Toolbar */ @@ -2947,13 +2969,19 @@ footer .separator { /* Browser Grid */ .browser-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.75rem; margin-bottom: 1.5rem; min-height: 200px; align-items: stretch; } +/* Root folder grid — wider cards */ +.browser-grid.browser-root-grid { + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1rem; +} + /* Compact Grid */ .browser-grid.browser-grid-compact { grid-template-columns: repeat(auto-fill, minmax(80px, 100px)); @@ -2990,41 +3018,66 @@ footer .separator { .browser-list { display: flex; flex-direction: column; - gap: 2px; + gap: 1px; margin-bottom: 1.5rem; min-height: 200px; } +/* List view column header */ +.browser-list-header { + display: grid; + grid-template-columns: 40px 1fr auto auto auto auto; + align-items: center; + gap: 0.75rem; + padding: 0.4rem 0.75rem; + font-size: 0.688rem; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid var(--border); + margin-bottom: 0.25rem; + user-select: none; +} + +.browser-list-header span:nth-child(n+3) { + text-align: right; +} + .browser-list-item { display: grid; grid-template-columns: 40px 1fr auto auto auto auto; align-items: center; gap: 0.75rem; padding: 0.5rem 0.75rem; - background: var(--bg-tertiary); + background: transparent; border: 1px solid transparent; - border-radius: 4px; + border-radius: 6px; cursor: pointer; transition: all 0.15s; animation: itemFadeIn 0.3s ease-out backwards; - animation-delay: calc(var(--item-index, 0) * 20ms); + animation-delay: calc(var(--item-index, 0) * 15ms); } .browser-list-item:hover { + background: var(--bg-tertiary); + border-color: var(--border); +} + +.browser-list-item:active { background: var(--border); - border-color: var(--accent); } .browser-list-icon { - width: 32px; - height: 32px; + width: 36px; + height: 36px; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; - border-radius: 4px; - background: var(--bg-primary); + border-radius: 6px; + background: var(--bg-tertiary); flex-shrink: 0; overflow: hidden; position: relative; @@ -3036,8 +3089,8 @@ footer .separator { display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.5); - border-radius: 4px; + background: rgba(0, 0, 0, 0.55); + border-radius: 6px; opacity: 0; transition: opacity 0.15s; pointer-events: none; @@ -3054,10 +3107,10 @@ footer .separator { } .browser-list-thumbnail { - width: 32px; - height: 32px; + width: 36px; + height: 36px; object-fit: cover; - border-radius: 4px; + border-radius: 6px; } .browser-list-thumbnail.loading { @@ -3084,6 +3137,7 @@ footer .separator { white-space: nowrap; min-width: 55px; text-align: right; + font-variant-numeric: tabular-nums; } .browser-list-duration { @@ -3101,6 +3155,7 @@ footer .separator { white-space: nowrap; min-width: 60px; text-align: right; + font-variant-numeric: tabular-nums; } .browser-loading { @@ -3125,85 +3180,114 @@ footer .separator { .browser-item { background: var(--bg-tertiary); - border: 1px solid var(--border); - border-radius: 8px; + border: 1px solid transparent; + border-radius: 10px; padding: 0.6rem; cursor: pointer; - transition: all 0.2s; + transition: all 0.2s ease; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; position: relative; animation: itemFadeIn 0.3s ease-out backwards; - animation-delay: calc(var(--item-index, 0) * 30ms); + animation-delay: calc(var(--item-index, 0) * 25ms); } @keyframes itemFadeIn { - from { opacity: 0; transform: translateY(8px) scale(0.97); } - to { opacity: 1; transform: translateY(0) scale(1); } + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } } .browser-item:hover { - background: var(--border); - border-color: var(--accent); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + border-color: var(--border); + transform: translateY(-3px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); } +.browser-item:active { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + +/* Root Folder Cards — distinctive hero style */ +.browser-item.browser-root-folder { + padding: 1.25rem 1rem; + gap: 0.75rem; + border: 1px solid var(--border); + background: linear-gradient(135deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%); + min-height: 120px; + justify-content: center; +} + +.browser-item.browser-root-folder .browser-thumb-wrapper { + width: auto; + height: auto; +} + +.browser-item.browser-root-folder .browser-icon { + width: 56px; + height: 56px; + font-size: 1.75rem; + border-radius: 14px; + background: rgba(29, 185, 84, 0.1); + border: 1px solid rgba(29, 185, 84, 0.15); + transition: all 0.25s; +} + +.browser-item.browser-root-folder:hover .browser-icon { + background: rgba(29, 185, 84, 0.18); + border-color: rgba(29, 185, 84, 0.3); + transform: scale(1.05); +} + +.browser-item.browser-root-folder .browser-item-name { + font-size: 0.875rem; + font-weight: 600; +} + +/* Unavailable root folder overlay */ +.browser-item.browser-root-folder.unavailable .browser-icon { + background: rgba(231, 76, 60, 0.08); + border-color: rgba(231, 76, 60, 0.12); + opacity: 0.6; +} /* Thumbnail Display */ .browser-thumbnail { - width: 90px; - height: 90px; + width: 100%; + aspect-ratio: 1; object-fit: cover; - border-radius: 6px; + border-radius: 8px; background: var(--bg-primary); display: block; } .browser-thumbnail.loading { background: linear-gradient( - 90deg, - var(--bg-primary) 25%, + 110deg, + var(--bg-primary) 30%, var(--bg-tertiary) 50%, - var(--bg-primary) 75% + var(--bg-primary) 70% ); background-size: 200% 100%; - animation: loading 1.5s infinite; - position: relative; - opacity: 0; -} - -.browser-thumbnail.loading::after { - content: '⏳'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 2rem; - opacity: 0.6; - animation: pulse 1.5s infinite; + animation: shimmer 1.8s ease-in-out infinite; + opacity: 1; } .browser-thumbnail.loaded { - animation: fadeIn 0.5s ease-out forwards; + animation: fadeIn 0.4s ease-out forwards; } -@keyframes loading { +@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } -@keyframes pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 0.8; } -} - @keyframes fadeIn { from { opacity: 0; - transform: scale(0.95); + transform: scale(0.97); } to { opacity: 1; @@ -3213,13 +3297,13 @@ footer .separator { /* File/Folder Icons */ .browser-icon { - width: 90px; - height: 90px; + width: 100%; + aspect-ratio: 1; display: flex; align-items: center; justify-content: center; - font-size: 3rem; - border-radius: 6px; + font-size: 2.5rem; + border-radius: 8px; background: var(--bg-primary); } @@ -3227,10 +3311,11 @@ footer .separator { width: 100%; text-align: center; margin-top: auto; + padding: 0 0.15rem; } .browser-item-name { - font-size: 0.813rem; + font-size: 0.75rem; font-weight: 500; color: var(--text-primary); word-break: break-word; @@ -3239,12 +3324,14 @@ footer .separator { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; + line-height: 1.3; } .browser-item-meta { - font-size: 0.75rem; - color: var(--text-secondary); - margin-top: 0.25rem; + font-size: 0.688rem; + color: var(--text-muted); + margin-top: 0.2rem; + line-height: 1.3; } .browser-item-type { @@ -3260,6 +3347,11 @@ footer .separator { display: flex; align-items: center; justify-content: center; + opacity: 0; + transition: opacity 0.2s; +} + +.browser-item:hover .browser-item-type { opacity: 0.85; } @@ -3274,9 +3366,11 @@ footer .separator { /* Thumbnail Wrapper & Play Overlay */ .browser-thumb-wrapper { position: relative; - width: 90px; - height: 90px; + width: 100%; + aspect-ratio: 1; flex-shrink: 0; + border-radius: 8px; + overflow: hidden; } .browser-thumb-wrapper .browser-thumbnail, @@ -3291,24 +3385,29 @@ footer .separator { display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.45); - border-radius: 6px; + background: rgba(0, 0, 0, 0.5); + border-radius: 8px; opacity: 0; transition: opacity 0.2s; pointer-events: none; } .browser-play-overlay svg { - width: 40px; - height: 40px; + width: 36px; + height: 36px; color: #fff; - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4)); + filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5)); + transition: transform 0.15s; } .browser-item:hover .browser-play-overlay { opacity: 1; } +.browser-item:hover .browser-play-overlay svg { + transform: scale(1.1); +} + /* Compact grid overrides */ .browser-grid-compact .browser-thumb-wrapper { width: 100%; @@ -3325,7 +3424,7 @@ footer .separator { background: transparent; border: none; border-radius: 4px; - padding: 0.2rem; + padding: 0.25rem; color: var(--text-muted); cursor: pointer; transition: color 0.15s; @@ -3335,6 +3434,11 @@ footer .separator { justify-content: center; width: auto; height: auto; + opacity: 0; +} + +.browser-list-item:hover .browser-list-download { + opacity: 1; } .browser-list-download:hover { @@ -3346,7 +3450,7 @@ footer .separator { /* Pagination */ .pagination { display: flex; - justify-content: center; + justify-content: space-between; align-items: center; gap: 1rem; padding-top: 1rem; @@ -3354,13 +3458,13 @@ footer .separator { } .pagination button { - padding: 0.5rem 1.5rem; + padding: 0.4rem 1.25rem; border-radius: 6px; background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text-primary); cursor: pointer; - font-size: 0.875rem; + font-size: 0.813rem; font-weight: 600; transition: all 0.2s; width: auto; @@ -3383,19 +3487,25 @@ footer .separator { display: flex; align-items: center; gap: 0.5rem; - font-size: 0.875rem; + font-size: 0.813rem; color: var(--text-secondary); } +.pagination-showing { + font-size: 0.75rem; + color: var(--text-muted); + white-space: nowrap; +} + .page-input { - width: 3.5rem; - padding: 0.3rem 0.4rem; + width: 3rem; + padding: 0.25rem 0.35rem; text-align: center; background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 4px; color: var(--text-primary); - font-size: 0.875rem; + font-size: 0.813rem; -moz-appearance: textfield; } @@ -3412,8 +3522,17 @@ footer .separator { /* Responsive Design */ @media (max-width: 600px) { + .browser-container { + padding: 0.75rem; + } + .browser-grid { - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 0.5rem; + } + + .browser-grid.browser-root-grid { + grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 0.75rem; } @@ -3421,17 +3540,13 @@ footer .separator { grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); } - .browser-thumb-wrapper { - width: 100px; - height: 100px; - } - - .browser-icon { - font-size: 2.5rem; - } - .browser-item { - padding: 0.75rem; + padding: 0.5rem; + } + + .browser-item.browser-root-folder { + padding: 1rem 0.75rem; + min-height: 100px; } .browser-header-section { @@ -3467,12 +3582,27 @@ footer .separator { display: none; } + .browser-list-header { + grid-template-columns: 32px 1fr auto auto; + gap: 0.5rem; + padding: 0.35rem 0.5rem; + } + + .browser-list-header span:nth-child(n+3):nth-child(-n+4) { + display: none; + } + .browser-list-item { grid-template-columns: 32px 1fr auto auto; gap: 0.5rem; padding: 0.4rem 0.5rem; } + .browser-list-icon { + width: 32px; + height: 32px; + } + .browser-list-duration { display: none; } @@ -3485,6 +3615,17 @@ footer .separator { display: none; } + .pagination { + flex-wrap: wrap; + gap: 0.5rem; + } + + .pagination-showing { + flex-basis: 100%; + text-align: center; + order: -1; + } + .album-art-glow { width: 250px; height: 250px; @@ -3523,6 +3664,14 @@ footer .separator { display: none; } + .browser-list-header { + grid-template-columns: 40px 1fr auto auto auto; + } + + .browser-list-header span:nth-child(3) { + display: none; + } + .browser-list-item { grid-template-columns: 40px 1fr auto auto auto; } diff --git a/media_server/static/index.html b/media_server/static/index.html index c2c1962..a1bd75b 100644 --- a/media_server/static/index.html +++ b/media_server/static/index.html @@ -290,6 +290,7 @@ / 1 + diff --git a/media_server/static/js/browser.js b/media_server/static/js/browser.js index 5b06045..8eb4f19 100644 --- a/media_server/static/js/browser.js +++ b/media_server/static/js/browser.js @@ -83,13 +83,13 @@ function showRootFolders() { revokeBlobUrls(container); if (viewMode === 'list') { container.className = 'browser-list'; - } else if (viewMode === 'compact') { - container.className = 'browser-grid browser-grid-compact'; } else { - container.className = 'browser-grid'; + container.className = 'browser-grid browser-root-grid'; } container.innerHTML = ''; + const folderSvg = ''; + Object.entries(mediaFolders).forEach(([id, folder]) => { if (!folder.enabled) return; const unavailable = folder.available === false; @@ -105,13 +105,13 @@ function showRootFolders() { }; } row.innerHTML = ` -
+