Fix mobile header, merge author name variants, clearer playback errors
build / check (push) Successful in 10s
build / image (push) Successful in 27s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-14 16:02:39 -04:00
1 parent f977f3294c
commit 1df566d85d
1 file changed
+18 -3
+18 -3
View File
@@ -100,7 +100,9 @@ a.pill{text-decoration:none;color:var(--fg);display:inline-block}
.chlist .on{color:var(--acc);font-weight:700}
#toast{position:fixed;left:50%;bottom:110px;transform:translateX(-50%);background:var(--fg);color:var(--bg);padding:10px 16px;border-radius:99px;font-weight:600;z-index:30;opacity:0;pointer-events:none;transition:opacity .2s}
#toast.on{opacity:1}
@media(max-width:560px){.logo span{display:none}.hd{padding:calc(12px + env(safe-area-inset-top)) 14px 12px}main{padding:4px 14px}.grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}}
.brk{display:none}
@media(max-width:560px){.logo span{display:inline}.logo{margin-right:auto}.hd{flex-wrap:wrap;gap:10px;padding:calc(12px + env(safe-area-inset-top)) 14px 12px}
#adminlink,#who{order:1}.brk{display:block;order:2;flex:0 0 100%;height:0}.search{order:3;margin-left:0;min-width:0}#sort{order:3;max-width:48%}main{padding:4px 14px}.grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}}
</style>
</head>
<body>
@@ -116,6 +118,7 @@ a.pill{text-decoration:none;color:var(--fg);display:inline-block}
<header><div class="hd">
<div class="logo"><i><svg><use href="#i-book"/></svg></i><span>Audiobooks</span></div>
<div class="brk"></div>
<div class="search"><svg><use href="#i-search"/></svg><input id="q" type="search" placeholder="Search title, author, series"></div>
<a class="pill hide" id="adminlink" href="/admin" style="padding:8px 12px">Admin</a>
<button class="pill" id="who" title="Sign out" style="padding:8px 12px"></button>
@@ -222,7 +225,15 @@ function render() {
$('count').textContent = books.length ? `${list.length} book${list.length === 1 ? '' : 's'}` : '';
if (sort === 'author') {
const groups = new Map();
for (const b of list) { const a = author(b) || 'Unknown author'; if (!groups.has(a)) groups.set(a, []); groups.get(a).push(b); }
const labels = new Map();
for (const b of list) {
const a = author(b) || 'Unknown author', k = a.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
if (!groups.has(k)) { groups.set(k, []); labels.set(k, a); }
groups.get(k).push(b);
}
for (const [k, bs] of groups) groups.set(k, { label: labels.get(k), bs });
const sorted = [...groups.values()].sort((x, y) => x.label.localeCompare(y.label));
groups.clear(); sorted.forEach(g => groups.set(g.label, g.bs));
$('grid').className = '';
$('grid').replaceChildren(...[...groups].map(([a, bs]) => {
const s = document.createElement('section');
@@ -312,7 +323,11 @@ $('fwd').onclick = () => skip(30); $('pch').onclick = () => jump(-1); $('nch').o
audio.onplay = () => { ico('play', 'pause'); ico('mplay', 'pause'); };
audio.onpause = () => { ico('play', 'play'); ico('mplay', 'play'); saveProgress(); };
audio.onended = () => { if (current) setProg(bookId(current), { pos: 0, dur: audio.duration, t: Date.now() }); render(); };
audio.onerror = () => toast("Playback failed (this browser can't play that format)");
audio.onerror = () => {
const c = audio.error && audio.error.code, why = { 1: 'aborted', 2: 'network error', 3: 'could not decode', 4: 'format or stream not supported' }[c] || 'unknown';
console.error('audio error', c, audio.error && audio.error.message, audio.src);
toast(`Playback failed: ${why}${current ? ' · ' + (audio.src.split('.').pop().slice(0, 5)) : ''}`);
};
$('seek').oninput = e => { audio.currentTime = e.target.value; };
// ---- cross-device sync (progress is kept by server.py; newest timestamp wins) ----