diff --git a/public/style.css b/public/style.css index e1e806d..422256e 100644 --- a/public/style.css +++ b/public/style.css @@ -759,3 +759,31 @@ body.nav-mini .ngrp span { display: none; } .vunw { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 14px; } .vunw input { width: 18px; height: 18px; } .vmark { position: absolute; right: 16px; bottom: 96px; z-index: 6; background: rgba(0, 0, 0, .7); color: #fff; border: 1px solid rgba(255, 255, 255, .5); } .vdeckc .vcard.wide { width: 250px; } + +/* Movies & TV polish */ +.vimg { box-shadow: 0 2px 8px rgba(0, 0, 0, .18); transition: transform .18s ease, box-shadow .18s ease; } +.vimg img { animation: vfade .35s ease both; } +@keyframes vfade { from { opacity: 0; } to { opacity: 1; } } +@media (hover: hover) { + .vcard:hover .vimg, .vcard:focus-visible .vimg { transform: translateY(-3px) scale(1.025); box-shadow: 0 10px 24px rgba(0, 0, 0, .32); } + .vcard.wide .vimg::after, .vcard .vimg::after { content: ''; position: absolute; inset: 0; background: linear-gradient(transparent 55%, rgba(0, 0, 0, .45)); opacity: 0; transition: opacity .18s; pointer-events: none; } + .vcard:hover .vimg::after { opacity: 1; } + .vx { opacity: 0; transition: opacity .15s; } .vdeckc:hover .vx, .vx:focus-visible { opacity: .95; } +} +.vrow { scroll-snap-type: x proximity; padding: 4px 2px 10px; margin: 0 -2px; } +.vrow > * { scroll-snap-align: start; } +.vcard b { font-weight: 650; letter-spacing: -.005em; } +.vprog { height: 5px; border-radius: 0 3px 3px 0; overflow: hidden; } .vprog b { border-radius: 0 3px 3px 0; box-shadow: 0 0 6px var(--accent); } +#view h2 { letter-spacing: -.01em; } +.vclear { vertical-align: middle; font-size: 13px; } +.vfilt select { border-radius: 10px; border: 1px solid var(--line); background: var(--panel); padding: 0 10px; color: var(--text); } +.vsk { aspect-ratio: 2/3; border-radius: 10px; background: linear-gradient(100deg, var(--panel2) 30%, var(--panel) 50%, var(--panel2) 70%); background-size: 300% 100%; animation: vshim 1.3s linear infinite; } +@keyframes vshim { from { background-position: 100% 0; } to { background-position: 0 0; } } +.vhero .vbg { opacity: .3; height: 300px; filter: saturate(1.2); } +.vpost { box-shadow: 0 10px 30px rgba(0, 0, 0, .35); border-radius: 12px; } +.vsum { font-size: 15.5px; } +.vmark { border-radius: 10px; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); animation: vfade .25s ease both; font-weight: 650; } +/* Lyrics polish */ +.ll { text-shadow: 0 1px 12px color-mix(in srgb, var(--bg) 70%, transparent); } +.ll.on { text-shadow: 0 0 28px color-mix(in srgb, var(--accent) 35%, transparent); } +.lyrart { box-shadow: 0 4px 14px rgba(0, 0, 0, .3); } diff --git a/public/sw.js b/public/sw.js index 1abb2c2..8062d84 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ // App-shell cache only; Seafile/API traffic always goes to the network. -const CACHE = 'hub-v17'; +const CACHE = 'hub-v18'; const SHARE = 'hub-share'; // files shared into the app from other apps, held until the person picks a folder const SHELL = ['/icon.svg', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/apple-touch-icon.png']; diff --git a/public/video.js b/public/video.js index 58915c4..5ac275a 100644 --- a/public/video.js +++ b/public/video.js @@ -95,12 +95,12 @@ async function videoHome(st, libKey) { const load = async () => { const my = gen, mc = await px.get(`/library/sections/${cur.key}/all?type=${type}&sort=${vdEnc($('#vsort').value)}${$('#vunwc').checked ? '&unwatched=1' : ''}${genre ? '&genre=' + vdEnc(genre) : ''}&X-Plex-Container-Start=${start}&X-Plex-Container-Size=${size}`); if (!$('#vgrid') || my !== gen) return; total = mc.totalSize || mc.size || 0; const items = mc.Metadata || []; start += items.length; all = all.concat(items); - $('#vgrid').insertAdjacentHTML('beforeend', items.map(m => vdCard(m)).join('')); if (start === items.length) $('#vgrid .empty')?.remove(); + $('#vgrid').insertAdjacentHTML('beforeend', items.map(m => vdCard(m)).join('')); if (start === items.length) $('#vgrid .vsk') && $('#vgrid').querySelectorAll('.vsk').forEach(x => x.remove()); $('#vmore').innerHTML = start < total ? `
` : ''; if ($('#vmb')) $('#vmb').onclick = () => load().catch(e => toast(e.message)); if (!all.length) $('#vgrid').innerHTML = 'Nothing matches.
'; }; - const reload = () => { gen++; start = 0; total = 0; all = []; $('#vgrid').innerHTML = 'Loading…
'; $('#vmore').innerHTML = ''; $('#vgrid').innerHTML = ''; load().catch(e => { if ($('#vgrid')) $('#vgrid').innerHTML = `${esc(e.message)}
`; }); }; + const reload = () => { gen++; start = 0; total = 0; all = []; $('#vmore').innerHTML = ''; $('#vgrid').innerHTML = ''.repeat(12); load().catch(e => { if ($('#vgrid')) $('#vgrid').innerHTML = `${esc(e.message)}
`; }); }; $('#vsort').onchange = () => { store.set('video_sort', $('#vsort').value); reload(); }; $('#vunwc').onchange = () => { store.set('video_unw', $('#vunwc').checked ? '1' : '0'); reload(); }; $('#vgen').onchange = () => { genre = $('#vgen').value; reload(); }; reload(); // press and hold (or right-click) a card: quick actions