summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/css/layout.css39
-rw-r--r--web/css/player.css20
-rw-r--r--web/index.html7
-rw-r--r--web/js/app.js3
-rw-r--r--web/js/player.js23
5 files changed, 84 insertions, 8 deletions
diff --git a/web/css/layout.css b/web/css/layout.css
index 8860f77..7dd70a4 100644
--- a/web/css/layout.css
+++ b/web/css/layout.css
@@ -257,6 +257,19 @@ body {
/* Responsive */
@media (max-width: 860px) {
+ :root {
+ --header-height: 3rem;
+ --sidebar-width: 0;
+ }
+ .site-header {
+ gap: 0.35rem;
+ padding: 0 0.5rem;
+ }
+ .search-bar {
+ max-width: none;
+ min-width: 0;
+ flex: 1 1 auto;
+ }
.sidebar {
position: fixed;
inset: var(--header-height) auto auto 0;
@@ -269,11 +282,35 @@ body {
}
.sidebar.open { display: flex; }
.page { padding-left: 0; }
- .media-grid { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); }
+ .player { inset: auto 0 0 0 !important; }
+ .toolbar {
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ }
+ .toolbar .input-sm {
+ min-width: 4.5rem;
+ max-width: none;
+ flex: 1 1 4.5rem;
+ }
+ .site-header .logo {
+ font-size: 0.9rem;
+ }
+ #theme-toggle,
+ #user-name {
+ display: none;
+ }
+ #logout-btn {
+ padding: 0.3rem 0.5rem;
+ font-size: 0.75rem;
+ }
+ .media-grid { grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); }
}
@media (max-width: 520px) {
.media-grid { grid-template-columns: repeat(2, 1fr); }
+ .toolbar .input-sm {
+ min-width: 3.5rem;
+ }
}
/* Utilities */
diff --git a/web/css/player.css b/web/css/player.css
index 3221042..706667d 100644
--- a/web/css/player.css
+++ b/web/css/player.css
@@ -97,7 +97,25 @@
transform: translate(-50%, -50%) scale(1);
}
-/* Volume */
+/* Touch-friendly progress bar */
+.progress-track {
+ touch-action: none;
+}
+
+@media (max-width: 520px) {
+ .player .controls {
+ gap: 0.35rem;
+ padding: 0.4rem 0.5rem;
+ }
+ .volume-slider {
+ width: 3.5rem;
+ }
+ .big-play .play-icon {
+ width: 2.8rem;
+ height: 2.8rem;
+ font-size: 1rem;
+ }
+}
.volume-control {
display: flex;
align-items: center;
diff --git a/web/index.html b/web/index.html
index 4b63f13..1fc583e 100644
--- a/web/index.html
+++ b/web/index.html
@@ -43,13 +43,16 @@
<option value="audio">Audio</option>
</select>
<button id="filter-favorites" class="btn btn-ghost btn-sm">❤ Favorites</button>
+ <div class="spacer"></div>
+ <button id="filter-toggle" class="btn btn-ghost btn-sm" title="More filters">Filters ▾</button>
+ <span id="result-count" class="text-muted text-xs"></span>
+ </div>
+ <div id="filter-advanced" class="toolbar hidden">
<input id="filter-tags" type="text" placeholder="Tags (comma)" aria-label="Filter by tags" class="input-sm">
<input id="filter-min-duration" type="number" min="0" placeholder="Min s" aria-label="Min duration seconds" class="input-sm">
<input id="filter-max-duration" type="number" min="0" placeholder="Max s" aria-label="Max duration seconds" class="input-sm">
<input id="filter-min-filesize" type="number" min="0" placeholder="Min MB" aria-label="Min file size MB" class="input-sm">
<input id="filter-max-filesize" type="number" min="0" placeholder="Max MB" aria-label="Max file size MB" class="input-sm">
- <div class="spacer"></div>
- <span id="result-count" class="text-muted text-xs"></span>
</div>
<div id="media-grid" class="media-grid" tabindex="0" aria-label="Media grid"></div>
</div>
diff --git a/web/js/app.js b/web/js/app.js
index ea1021c..e72b58e 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -117,6 +117,9 @@ async function initApp() {
document.getElementById('filter-max-duration')?.addEventListener('change', (e) => { state.filters.maxDuration = e.target.value; loadMedia(); });
document.getElementById('filter-min-filesize')?.addEventListener('change', (e) => { state.filters.minFilesizeMB = e.target.value; loadMedia(); });
document.getElementById('filter-max-filesize')?.addEventListener('change', (e) => { state.filters.maxFilesizeMB = e.target.value; loadMedia(); });
+ document.getElementById('filter-toggle')?.addEventListener('click', () => {
+ document.getElementById('filter-advanced')?.classList.toggle('hidden');
+ });
// Menu toggle
document.getElementById('menu-btn')?.addEventListener('click', () => {
diff --git a/web/js/player.js b/web/js/player.js
index ed3e8bf..b61be08 100644
--- a/web/js/player.js
+++ b/web/js/player.js
@@ -68,13 +68,28 @@ export function initPlayer() {
});
let seeking = false;
- e.track?.addEventListener('click', (ev) => {
+ const seekToFraction = (frac) => {
const m = currentMediaElement();
if (!m || !m.duration) return;
- const r = e.track.getBoundingClientRect();
- const frac = (ev.clientX - r.left) / r.width;
m.currentTime = Math.max(0, Math.min(1, frac)) * m.duration;
- });
+ };
+ const handlePointer = (ev) => {
+ const r = e.track.getBoundingClientRect();
+ const clientX = ev.touches ? ev.touches[0].clientX : ev.clientX;
+ return (clientX - r.left) / r.width;
+ };
+ e.track?.addEventListener('click', (ev) => seekToFraction(handlePointer(ev)));
+ e.track?.addEventListener('touchstart', (ev) => {
+ seeking = true;
+ ev.preventDefault();
+ seekToFraction(handlePointer(ev));
+ }, { passive: false });
+ e.track?.addEventListener('touchmove', (ev) => {
+ if (!seeking) return;
+ ev.preventDefault();
+ seekToFraction(handlePointer(ev));
+ }, { passive: false });
+ e.track?.addEventListener('touchend', () => { seeking = false; });
e.track?.addEventListener('keydown', (ev) => {
const m = currentMediaElement();
if (!m || !m.duration) return;