Compare commits

..

No commits in common. "4053334f7fb72f05ab2068c15f04a28e47982b27" and "54fbd58293b6248689d78731a0ac5aed415d0b1e" have entirely different histories.

8 changed files with 377 additions and 1104 deletions

View file

@ -337,19 +337,6 @@ class RefreshAllTests(TestCase):
'keep_sorted': '', 'auto_download_default': False, 'keep_sorted': '', 'auto_download_default': False,
**PLAYBACK_DEFAULTS}) **PLAYBACK_DEFAULTS})
def test_state_counts_the_whole_inbox(self):
# The list is capped (inbox_limit); inbox_total counts every new episode of this user.
eps = [PodcastEpisode.objects.create(feed=self.a, guid=f'n{i}', title=f'N{i}', audio_url=f'https://example.org/{i}.mp3',
pub_date=datetime.now(timezone.utc) - timedelta(days=i)) for i in range(5)]
EpisodeProgress.objects.create(user=self.user, episode=eps[0], played=True)
EpisodeProgress.objects.create(user=self.user, episode=eps[1], dismissed=True)
EpisodeProgress.objects.create(user=self.user, episode=eps[2], position_seconds=30)
PodcastEpisode.objects.create(feed=self.bobs, guid='b', title='Bobs', audio_url='https://example.org/b.mp3')
data = self.client.get('/podcasts/state/?inbox_limit=2').json()
self.assertEqual([e['id'] for e in data['inbox']], [eps[2].id, eps[3].id])
self.assertEqual((data['inbox_total'], data['inbox_limit']), (3, 2))
self.assertEqual(self.client.get('/podcasts/state/').json()['inbox_total'], 3)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Shownotes, chapters, transcripts # Shownotes, chapters, transcripts

View file

@ -1569,9 +1569,8 @@ def refresh_all_feeds(request):
@csrf_exempt @csrf_exempt
def podcast_state(request): def podcast_state(request):
"""Everything a client shows first, in one response: feeds, queue, the """Everything a client shows first, in one response: feeds, queue, the
first page of the inbox (inbox_limit of inbox_total new episodes), the first page of the inbox, the settings and how many AntennaPod downloads
settings and how many AntennaPod downloads wait to be offered wait to be offered (import-downloads/)."""
(import-downloads/)."""
if not request.user.is_authenticated: if not request.user.is_authenticated:
return JsonResponse({'error': 'authentication required'}, status=401) return JsonResponse({'error': 'authentication required'}, status=401)
try: try:
@ -1584,8 +1583,6 @@ def podcast_state(request):
'feeds': _feeds_data(request.user), 'feeds': _feeds_data(request.user),
'queue': _queue_data(request.user), 'queue': _queue_data(request.user),
'inbox': _inbox_data(request.user, 0, limit, with_description=False), 'inbox': _inbox_data(request.user, 0, limit, with_description=False),
'inbox_total': _inbox_episodes(request.user).count(),
'inbox_limit': limit,
'settings': _settings_data(s), 'settings': _settings_data(s),
'import_downloads': {'count': len(downloads), 'bytes': sum(e['size'] for e in downloads)}, 'import_downloads': {'count': len(downloads), 'bytes': sum(e['size'] for e in downloads)},
'server_time': timezone.now().isoformat(), 'server_time': timezone.now().isoformat(),
@ -1987,19 +1984,20 @@ def _add_user_state(user, episodes):
return episodes return episodes
def _inbox_episodes(user):
"""The inbox: the user's episodes neither played nor dismissed."""
hidden = EpisodeProgress.objects.filter(user=user).filter(
Q(played=True) | Q(dismissed=True)
).values('episode_id')
return PodcastEpisode.objects.filter(feed__user=user).exclude(id__in=hidden)
def _inbox_data(user, offset, limit, with_description=True): def _inbox_data(user, offset, limit, with_description=True):
"""Inbox episodes. Shownotes can be large (up to 50 KB each); the apps' """Inbox episodes. Shownotes can be large (up to 50 KB each); the apps'
state/ leaves them out and loads one episode's at a time.""" state/ leaves them out and loads one episode's at a time."""
hidden_ids = set(
EpisodeProgress.objects.filter(
user=user,
).filter(
Q(played=True) | Q(dismissed=True)
).values_list('episode_id', flat=True)
)
episodes = list( episodes = list(
_inbox_episodes(user) PodcastEpisode.objects.filter(feed__user=user)
.exclude(id__in=hidden_ids)
.select_related('feed') .select_related('feed')
.order_by('-pub_date') .order_by('-pub_date')
.values(*_inbox_fields(with_description))[offset:offset + limit] .values(*_inbox_fields(with_description))[offset:offset + limit]

View file

@ -4064,32 +4064,13 @@ function jumpToTocEntry(href) {
// Reader Settings // Reader Settings
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Typography defaults. Phones keep the long-standing ones; a wide screen
// (desktop browser, diora desktop) starts bigger and wider.
const _READER_TYPE_DEFAULTS_NARROW = { fontSize: 16, lineHeight: 1.8, maxWidth: 65, fontWeight: null };
const _READER_TYPE_DEFAULTS_WIDE = { fontSize: 20, lineHeight: 1.6, maxWidth: 90, fontWeight: 375 };
const _READER_WIDTH_MAX = 140; // ch, the width slider's upper end ("Full" stays separate)
function _readerTypeDefaults() {
return window.matchMedia('(min-width: 900px)').matches
? _READER_TYPE_DEFAULTS_WIDE : _READER_TYPE_DEFAULTS_NARROW;
}
function loadReaderSettings(bookId) { function loadReaderSettings(bookId) {
// Reset to defaults, then apply per-book overrides // Reset to defaults, then apply per-book overrides
Object.assign(readerSettings, { theme: 'dark', fontFamily: 'serif', noBold: false, Object.assign(readerSettings, { fontSize: 16, lineHeight: 1.8, maxWidth: 65, theme: 'dark',
pdfZoom: 100, pdfInverted: false, pdfPaginated: false, pdfSpread: false }, fontFamily: 'serif', noBold: false, fontWeight: null,
_readerTypeDefaults()); pdfZoom: 100, pdfInverted: false, pdfPaginated: false, pdfSpread: false });
try { try {
const saved = JSON.parse(localStorage.getItem(`diora_reader_settings_${bookId}`) || '{}'); const saved = JSON.parse(localStorage.getItem(`diora_reader_settings_${bookId}`) || '{}');
// Settings saved before v2 hold every value, changed or not. A value equal
// to the old default was never chosen, so it gives way to today's default.
if (!saved.v) {
for (const [key, old] of Object.entries(_READER_TYPE_DEFAULTS_NARROW)) {
if (saved[key] === old) delete saved[key];
}
}
delete saved.v;
Object.assign(readerSettings, saved); Object.assign(readerSettings, saved);
if (saved.pdfPaginated === undefined) { if (saved.pdfPaginated === undefined) {
readerSettings.pdfPaginated = window.innerWidth < 768; readerSettings.pdfPaginated = window.innerWidth < 768;
@ -4099,7 +4080,7 @@ function loadReaderSettings(bookId) {
function saveReaderSettings() { function saveReaderSettings() {
if (currentBookId) { if (currentBookId) {
localStorage.setItem(`diora_reader_settings_${currentBookId}`, JSON.stringify({ ...readerSettings, v: 2 })); localStorage.setItem(`diora_reader_settings_${currentBookId}`, JSON.stringify(readerSettings));
} }
} }
@ -4189,7 +4170,7 @@ function toggleSettingsPanel() {
panel.innerHTML = ` panel.innerHTML = `
<label>Font <button class="btn btn-sm" id="rs-font-minus">−</button> <input type="range" id="rs-font" min="12" max="24" step="1" value="${readerSettings.fontSize}"> <button class="btn btn-sm" id="rs-font-plus">+</button> <span id="rs-font-val">${readerSettings.fontSize}px</span></label> <label>Font <button class="btn btn-sm" id="rs-font-minus">−</button> <input type="range" id="rs-font" min="12" max="24" step="1" value="${readerSettings.fontSize}"> <button class="btn btn-sm" id="rs-font-plus">+</button> <span id="rs-font-val">${readerSettings.fontSize}px</span></label>
<label>Line <button class="btn btn-sm" id="rs-line-minus">−</button> <input type="range" id="rs-line" min="12" max="30" step="1" value="${Math.round(readerSettings.lineHeight * 10)}"> <button class="btn btn-sm" id="rs-line-plus">+</button> <span id="rs-line-val">${readerSettings.lineHeight}</span></label> <label>Line <button class="btn btn-sm" id="rs-line-minus">−</button> <input type="range" id="rs-line" min="12" max="30" step="1" value="${Math.round(readerSettings.lineHeight * 10)}"> <button class="btn btn-sm" id="rs-line-plus">+</button> <span id="rs-line-val">${readerSettings.lineHeight}</span></label>
<label>Width <button class="btn btn-sm" id="rs-width-minus">−</button> <input type="range" id="rs-width" min="40" max="${_READER_WIDTH_MAX}" step="5" value="${Math.min(readerSettings.maxWidth, _READER_WIDTH_MAX)}"> <button class="btn btn-sm" id="rs-width-plus">+</button> <span id="rs-width-val">${readerSettings.maxWidth}ch</span></label> <label>Width <button class="btn btn-sm" id="rs-width-minus">−</button> <input type="range" id="rs-width" min="40" max="90" step="5" value="${readerSettings.maxWidth}"> <button class="btn btn-sm" id="rs-width-plus">+</button> <span id="rs-width-val">${readerSettings.maxWidth}ch</span></label>
<button class="btn btn-sm" id="rs-width-full">Full</button> <button class="btn btn-sm" id="rs-width-full">Full</button>
<label>Weight <button class="btn btn-sm" id="rs-weight-minus">−</button> <input type="range" id="rs-weight" min="200" max="800" step="${_readerWeightStep()}" value="${_readerWeight()}"> <button class="btn btn-sm" id="rs-weight-plus">+</button> <span id="rs-weight-val">${_readerWeight()}</span></label> <label>Weight <button class="btn btn-sm" id="rs-weight-minus">−</button> <input type="range" id="rs-weight" min="200" max="800" step="${_readerWeightStep()}" value="${_readerWeight()}"> <button class="btn btn-sm" id="rs-weight-plus">+</button> <span id="rs-weight-val">${_readerWeight()}</span></label>
<button class="btn btn-sm ${readerSettings.fontFamily === 'serif' ? 'active' : ''}" data-rs-font="serif">Serif</button> <button class="btn btn-sm ${readerSettings.fontFamily === 'serif' ? 'active' : ''}" data-rs-font="serif">Serif</button>

View file

@ -1684,18 +1684,6 @@
// The shared player: what plays on the other devices, and from now on every change. // The shared player: what plays on the other devices, and from now on every change.
startSync(); startSync();
// The start page chosen in the podcasts' settings (nui_start_page, this browser; none: the
// last used tab, the web's way). Opening diora shows it; reloading and back/forward keep the tab.
(function openStartPage() {
let start = '';
try { start = localStorage.getItem('nui_start_page') || ''; } catch (e) {}
if (!start || typeof showTab !== 'function') return;
const nav = performance.getEntriesByType ? performance.getEntriesByType('navigation')[0] : null;
if (nav && nav.type !== 'navigate') return;
const tab = start === 'queue' || start === 'inbox' ? 'podcasts' : start === 'radio' ? 'radio' : 'books';
if (typeof _currentTopTab === 'undefined' || _currentTopTab !== tab) showTab(tab);
})();
// The page (copied from the app) comes last, once all of the above is in place. // The page (copied from the app) comes last, once all of the above is in place.
const script = document.createElement('script'); const script = document.createElement('script');
script.src = UI_SCRIPT; script.src = UI_SCRIPT;

View file

@ -2,7 +2,7 @@
* diora service worker — caches the app shell for offline use. * diora service worker — caches the app shell for offline use.
*/ */
const CACHE = 'diora-v66'; const CACHE = 'diora-v65';
const SHELL = [ const SHELL = [
'/static/css/app.css', '/static/css/app.css',
'/static/js/app.js', '/static/js/app.js',

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,9 @@
/* Copied from diora-android 339de8f 2026-10-02: the podcast parts of web-native/static/native.css /* Copied from diora-android 99528f2 2026-10-02: the podcast parts of web-native/static/native.css
(bottom sheet, podcasts) by tools/sync_podcast_ui.py. Do not edit here. (bottom sheet, podcasts) by tools/sync_podcast_ui.py. Do not edit here.
The web's own additions are in podcasts-web.css. */ The web's own additions are in podcasts-web.css. */
/* ---- bottom sheet ---------------------------------------------------------------- */ /* ---- bottom sheet ---------------------------------------------------------------- */
/* Monospace stays the brand's (headings, tabs, numbers); reading text — titles, episode
lines, show notes, descriptions, hints — is the system's sans (no font download). */
:root {
--nui-sans: system-ui, -apple-system, 'Segoe UI', Roboto, 'Noto Sans', 'Helvetica Neue', Arial, sans-serif;
}
#nui-sheet { #nui-sheet {
position: fixed; position: fixed;
inset: 0; inset: 0;
@ -87,18 +81,13 @@
} }
.nui-row-hint { .nui-row-hint {
font-family: var(--nui-sans); font-size: 12px;
font-size: 13px;
font-weight: 400; font-weight: 400;
letter-spacing: normal;
line-height: 1.35;
color: var(--nui-muted); color: var(--nui-muted);
} }
/* Red only where it's meant: the destructive choice in a confirmation. What is set is bright and bold. */
.nui-danger { color: var(--accent); } .nui-danger { color: var(--accent); }
.nui-active .nui-row-label { color: var(--fg, #fff); font-weight: 700; } .nui-active .nui-row-label { color: var(--accent); }
.nui-active .nui-row-hint { color: var(--fg, #fff); }
.nui-row-split { .nui-row-split {
padding: 0 16px 0 0; padding: 0 16px 0 0;
@ -161,103 +150,84 @@ html.nui-one-player #nui-mini { display: none !important; }
scrollbar-width: none; scrollbar-width: none;
} }
.nui-pod-tab { .nui-pod-tab {
flex: 1 1 auto; flex: 1 0 auto;
min-width: 0;
min-height: var(--nui-tap); min-height: var(--nui-tap);
padding: 0 5px; padding: 0 10px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 4px; gap: 6px;
border: none; border: none;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
background: none; background: none;
color: var(--nui-muted); color: var(--nui-muted);
font: inherit; font: inherit;
font-size: 13px; font-size: 14px;
letter-spacing: 0;
white-space: nowrap; white-space: nowrap;
} }
.nui-pod-tab.active { color: var(--fg, #fff); font-weight: 700; border-bottom-color: var(--fg, #fff); } .nui-pod-tab.active { color: inherit; font-weight: 700; border-bottom-color: var(--accent); }
.nui-pod-count { .nui-pod-count {
min-width: 18px; min-width: 20px;
padding: 1px 5px; padding: 1px 6px;
border-radius: 9px; border-radius: 10px;
background: #2a2a2a; background: #2a2a2a;
color: #ddd; font-size: 11px;
font-size: 10.5px;
font-weight: 700; font-weight: 700;
letter-spacing: 0;
text-align: center; text-align: center;
font-variant-numeric: tabular-nums;
} }
.nui-pod-count-queue { background: var(--accent); color: #fff; } .nui-pod-tab.active .nui-pod-count { background: var(--accent); color: #fff; }
.nui-pod-summary { padding: 6px 2px 10px; font-size: 12px; color: var(--nui-muted); } .nui-pod-summary { padding: 6px 2px 10px; font-size: 12px; color: var(--nui-muted); }
.nui-pod-empty { padding: 32px 12px; color: var(--nui-muted); text-align: center; line-height: 1.5; font-family: var(--nui-sans); letter-spacing: normal; font-weight: 400; font-size: 14px; } .nui-pod-empty { padding: 32px 12px; color: var(--nui-muted); text-align: center; line-height: 1.5; }
.nui-pod-list { display: flex; flex-direction: column; } .nui-pod-list { display: flex; flex-direction: column; }
.nui-pod-row { .nui-pod-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 12px;
min-height: 58px; min-height: 72px;
padding: 5px 0; padding: 8px 0;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
background: var(--bg); background: var(--bg);
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
.nui-pod-row:active { background: #141414; } .nui-pod-row:active { background: #141414; }
.nui-pod-row.nui-pod-played .nui-pod-title { color: var(--nui-muted); font-weight: 400; } .nui-pod-row.nui-pod-played .nui-pod-title { color: var(--nui-muted); font-weight: 400; }
.nui-pod-row.nui-pod-gone { display: none; }
.nui-pod-row.nui-pod-current .nui-pod-title { color: var(--accent); } .nui-pod-row.nui-pod-current .nui-pod-title { color: var(--accent); }
.nui-pod-row.nui-pod-current .nui-pod-title::before { content: '▶ '; } .nui-pod-row.nui-pod-current .nui-pod-title::before { content: '▶ '; }
.nui-pod-art { .nui-pod-art {
flex: none; flex: none;
width: 46px; width: 52px;
height: 46px; height: 52px;
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
background: #1c1c1c; background: #1c1c1c;
display: flex;
align-items: center;
justify-content: center;
} }
/* No cover anywhere: the podcast's initial on a neutral tile. */
.nui-pod-initial { font-size: 20px; font-weight: 700; color: #6a6a6a; line-height: 1; }
.nui-pod-art-big .nui-pod-initial { font-size: 30px; }
.nui-pod-art-player .nui-pod-initial { font-size: 96px; }
.nui-pod-art img { width: 100%; height: 100%; object-fit: cover; display: block; } .nui-pod-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nui-pod-art-big { width: 64px; height: 64px; } .nui-pod-art-big { width: 72px; height: 72px; }
.nui-pod-art-mini { width: 44px; height: 44px; margin-left: 10px; } .nui-pod-art-mini { width: 44px; height: 44px; margin-left: 10px; }
.nui-pod-art-player { width: min(72vw, 320px); height: min(72vw, 320px); border-radius: 8px; align-self: center; } .nui-pod-art-player { width: min(72vw, 320px); height: min(72vw, 320px); border-radius: 8px; align-self: center; }
.nui-pod-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; } .nui-pod-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.nui-pod-title { .nui-pod-title {
font-family: var(--nui-sans); font-size: 15px;
font-size: 14px; font-weight: 700;
font-weight: 600; line-height: 1.3;
letter-spacing: normal;
line-height: 1.22;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
overflow-wrap: anywhere;
} }
.nui-pod-meta { .nui-pod-meta {
font-family: var(--nui-sans);
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
letter-spacing: normal;
font-variant-numeric: tabular-nums;
color: var(--nui-muted); color: var(--nui-muted);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.nui-pod-progress { height: 3px; margin-top: 2px; border-radius: 2px; background: #2a2a2a; overflow: hidden; max-width: 160px; } .nui-pod-progress { height: 3px; margin-top: 4px; border-radius: 2px; background: #2a2a2a; overflow: hidden; max-width: 160px; }
.nui-pod-progress > span { display: block; height: 100%; background: var(--accent); } .nui-pod-progress > span { display: block; height: 100%; background: var(--accent); }
.nui-pod-badge { .nui-pod-badge {
flex: none; flex: none;
@ -290,10 +260,18 @@ html.nui-one-player #nui-mini { display: none !important; }
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6);
} }
.nui-pod-feedhead { display: flex; align-items: center; gap: 10px; margin: 2px 0 6px; } .nui-pod-feedhead { display: flex; align-items: center; gap: 10px; margin: 4px 0 10px; }
.nui-pod-feedhead > .nui-icon:first-child { min-width: 32px; margin-left: -6px; } .nui-pod-switch {
.nui-pod-feedhead .nui-pod-title { font-size: 16px; } display: grid;
.nui-pod-feedhead .nui-pod-infobtn { min-width: 38px; } grid-template-columns: auto 1fr;
column-gap: 10px;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid var(--border);
font-size: 14px;
}
.nui-pod-switch input { width: 20px; height: 20px; accent-color: var(--accent); }
.nui-pod-switch .nui-pod-meta { grid-column: 2; }
.nui-pod-search { display: flex; gap: 8px; margin: 4px 0 10px; } .nui-pod-search { display: flex; gap: 8px; margin: 4px 0 10px; }
.nui-pod-search input { flex: 1; min-width: 0; min-height: var(--nui-tap); font-size: 16px; } .nui-pod-search input { flex: 1; min-width: 0; min-height: var(--nui-tap); font-size: 16px; }
@ -338,8 +316,8 @@ html.nui-one-player #nui-mini { display: none !important; }
} }
.nui-pod-player-top { display: flex; justify-content: center; } .nui-pod-player-top { display: flex; justify-content: center; }
.nui-pod-player-top .nui-icon { font-size: 26px; color: var(--nui-muted); } .nui-pod-player-top .nui-icon { font-size: 26px; color: var(--nui-muted); }
.nui-pod-player-title { font-family: var(--nui-sans); font-size: 18px; font-weight: 600; letter-spacing: normal; line-height: 1.3; text-align: center; } .nui-pod-player-title { font-size: 18px; font-weight: 700; line-height: 1.3; text-align: center; }
.nui-pod-player-feed { margin-top: -6px; font-family: var(--nui-sans); font-size: 13.5px; font-weight: 400; letter-spacing: normal; color: var(--nui-muted); text-align: center; } .nui-pod-player-feed { margin-top: -6px; font-size: 13px; color: var(--nui-muted); text-align: center; }
.nui-pod-seek { width: 100%; accent-color: var(--accent); height: 28px; } .nui-pod-seek { width: 100%; accent-color: var(--accent); height: 28px; }
.nui-pod-times { display: flex; justify-content: space-between; margin-top: -8px; font-size: 12px; color: var(--nui-muted); font-variant-numeric: tabular-nums; } .nui-pod-times { display: flex; justify-content: space-between; margin-top: -8px; font-size: 12px; color: var(--nui-muted); font-variant-numeric: tabular-nums; }
.nui-pod-controls { display: flex; align-items: center; justify-content: center; gap: 28px; } .nui-pod-controls { display: flex; align-items: center; justify-content: center; gap: 28px; }
@ -388,7 +366,7 @@ body.eink .nui-pod-art { background: #eee; }
.nui-pod-head .nui-pod-summary { padding: 6px 2px; } .nui-pod-head .nui-pod-summary { padding: 6px 2px; }
.nui-pod-tools { display: flex; align-items: center; margin-right: -6px; } .nui-pod-tools { display: flex; align-items: center; margin-right: -6px; }
.nui-pod-tools .nui-icon { font-size: 18px; color: var(--nui-muted); } .nui-pod-tools .nui-icon { font-size: 18px; color: var(--nui-muted); }
.nui-pod-lock.on { color: var(--fg, #fff) !important; } .nui-pod-lock.on { color: var(--accent) !important; }
.nui-pod-selbar { .nui-pod-selbar {
position: sticky; position: sticky;
top: 0; top: 0;
@ -407,13 +385,14 @@ body.eink .nui-pod-art { background: #eee; }
.nui-pod-selactions .btn { min-height: 38px; flex: 1 0 auto; } .nui-pod-selactions .btn { min-height: 38px; flex: 1 0 auto; }
.nui-pod-selactions .btn:disabled { opacity: 0.4; } .nui-pod-selactions .btn:disabled { opacity: 0.4; }
.nui-pod-check { flex: none; width: 28px; font-size: 22px; color: var(--nui-muted); text-align: center; } .nui-pod-check { flex: none; width: 28px; font-size: 22px; color: var(--nui-muted); text-align: center; }
.nui-pod-check.on { color: var(--fg, #fff); } .nui-pod-check.on { color: var(--accent); }
.nui-pod-row.nui-pod-selecting { user-select: none; -webkit-user-select: none; } .nui-pod-row.nui-pod-selecting { user-select: none; -webkit-user-select: none; }
body.eink .nui-pod-check.on, body.eink .nui-pod-check.on,
body.eink .nui-pod-lock.on { color: #000 !important; } body.eink .nui-pod-lock.on { color: #000 !important; }
body.eink .nui-pod-selbar { background: #fff; } body.eink .nui-pod-selbar { background: #fff; }
/* podcast chips (the list filters), sleep timer */ /* podcast playback: per-podcast chips, sleep timer */
.nui-pod-feedprefs { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.nui-pod-chip { .nui-pod-chip {
display: inline-flex; display: inline-flex;
align-items: baseline; align-items: baseline;
@ -428,9 +407,28 @@ body.eink .nui-pod-selbar { background: #fff; }
font-size: 13px; font-size: 13px;
} }
.nui-pod-chip strong { color: inherit; font-weight: 700; } .nui-pod-chip strong { color: inherit; font-weight: 700; }
.nui-pod-chip.on { border-color: #8a8a8a; color: var(--fg, #fff); font-weight: 700; } .nui-pod-chip.on { border-color: var(--accent); color: inherit; }
.nui-pod-chip.on strong { color: var(--accent); }
.nui-pod-sleep { font-size: 15px !important; white-space: nowrap; } .nui-pod-sleep { font-size: 15px !important; white-space: nowrap; }
.nui-pod-sleep.on { color: var(--accent) !important; font-weight: 700; } .nui-pod-sleep.on { color: var(--accent) !important; font-weight: 700; }
/* unsubscribing: a quiet link, asked once more in place */
.nui-pod-unsub { display: flex; justify-content: flex-end; padding: 6px 0; border-bottom: 1px solid var(--border); }
.nui-pod-unsub-btn {
min-height: var(--nui-tap, 44px);
padding: 0 4px;
border: none;
background: none;
color: var(--nui-muted);
font: inherit;
font-size: 13px;
}
.nui-pod-unsub-ask { display: block; padding: 12px 0; }
.nui-pod-unsub-ask .nui-pod-title { display: block; overflow: visible; }
.nui-pod-unsub-ask .nui-pod-meta { margin-top: 4px; line-height: 1.4; white-space: normal; overflow: visible; }
.nui-pod-unsub-actions { display: flex; gap: 8px; margin-top: 10px; }
.nui-pod-unsub-actions .btn { min-height: var(--nui-tap, 44px); }
.nui-pod-unsub-yes { color: var(--accent); border-color: var(--accent); font-weight: 700; }
body.eink .nui-pod-unsub-yes { color: #000; border-color: #000; }
body.eink .nui-pod-chip.on { border-color: #000; } body.eink .nui-pod-chip.on { border-color: #000; }
body.eink .nui-pod-chip.on strong, body.eink .nui-pod-chip.on strong,
body.eink .nui-pod-sleep.on { color: #000 !important; } body.eink .nui-pod-sleep.on { color: #000 !important; }
@ -448,7 +446,7 @@ body.eink .nui-pod-sleep.on { color: #000 !important; }
font: inherit; font: inherit;
font-size: 14px; font-size: 14px;
} }
.nui-pod-subtab.active { color: inherit; font-weight: 700; border-bottom-color: var(--fg, #fff); } .nui-pod-subtab.active { color: inherit; font-weight: 700; border-bottom-color: var(--accent); }
.nui-pod-chapters { list-style: none; margin: 0; padding: 0; } .nui-pod-chapters { list-style: none; margin: 0; padding: 0; }
.nui-pod-chapter { .nui-pod-chapter {
display: grid; display: grid;
@ -460,14 +458,12 @@ body.eink .nui-pod-sleep.on { color: #000 !important; }
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
} }
.nui-pod-chapter-title { font-family: var(--nui-sans); letter-spacing: normal; font-weight: 400; font-size: 14.5px; }
.nui-pod-chapter.current .nui-pod-chapter-title { font-weight: 600; }
.nui-pod-chapter-t { color: var(--nui-muted); font-size: 12px; font-variant-numeric: tabular-nums; } .nui-pod-chapter-t { color: var(--nui-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.nui-pod-chapter.current { color: var(--accent); font-weight: 700; } .nui-pod-chapter.current { color: var(--accent); font-weight: 700; }
.nui-pod-chapter.current .nui-pod-chapter-t { color: var(--accent); } .nui-pod-chapter.current .nui-pod-chapter-t { color: var(--accent); }
.nui-pod-notes { font-family: var(--nui-sans); font-size: 14.5px; font-weight: 400; letter-spacing: normal; line-height: 1.55; overflow-wrap: anywhere; } .nui-pod-notes { font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
.nui-pod-notes img { max-width: 100%; height: auto; } .nui-pod-notes img { max-width: 100%; height: auto; }
.nui-pod-notes a { color: inherit; text-decoration: underline; text-decoration-color: #777; } .nui-pod-notes a { color: var(--accent); }
.nui-pod-notes .nui-pod-stamp { font-weight: 700; text-decoration: none; border-bottom: 1px dotted currentColor; } .nui-pod-notes .nui-pod-stamp { font-weight: 700; text-decoration: none; border-bottom: 1px dotted currentColor; }
.nui-pod-notes table { display: block; overflow-x: auto; } .nui-pod-notes table { display: block; overflow-x: auto; }
.nui-pod-notes-sheet .nui-pod-player-panel { max-height: 88%; } .nui-pod-notes-sheet .nui-pod-player-panel { max-height: 88%; }
@ -510,7 +506,7 @@ body.eink .nui-pod-subtab.active { border-bottom-color: #000; color: #000; }
color: var(--nui-muted); color: var(--nui-muted);
background: background:
radial-gradient(closest-side, var(--bg) 78%, transparent 80% 100%), radial-gradient(closest-side, var(--bg) 78%, transparent 80% 100%),
conic-gradient(#d0d0d0 var(--p, 0%), #2a2a2a 0); conic-gradient(var(--accent) var(--p, 0%), #2a2a2a 0);
} }
body.eink .nui-pod-dl-done { color: #000; } body.eink .nui-pod-dl-done { color: #000; }
body.eink .nui-pod-dl-done::after { border-color: #000; } body.eink .nui-pod-dl-done::after { border-color: #000; }
@ -522,7 +518,7 @@ body.eink .nui-pod-dl-ring > span {
} }
/* the top bar's "↓ 3/68" and the downloads overview (openDownloads) */ /* the top bar's "↓ 3/68" and the downloads overview (openDownloads) */
.nui-pod-dlind { min-width: 0 !important; padding: 0 8px; font-size: 13px !important; font-weight: 700; color: var(--fg, #fff); white-space: nowrap; font-variant-numeric: tabular-nums; } .nui-pod-dlind { min-width: 0 !important; padding: 0 8px; font-size: 13px !important; font-weight: 700; color: var(--accent); white-space: nowrap; font-variant-numeric: tabular-nums; }
.nui-pod-dlind[hidden] { display: none !important; } .nui-pod-dlind[hidden] { display: none !important; }
.nui-pod-dls .nui-pod-player-panel { min-height: 50%; } .nui-pod-dls .nui-pod-player-panel { min-height: 50%; }
.nui-pod-dls-head { display: flex; flex-direction: column; gap: 2px; } .nui-pod-dls-head { display: flex; flex-direction: column; gap: 2px; }
@ -562,11 +558,13 @@ body.eink .nui-pod-star, body.eink .nui-pod-fav.on { color: #000 !important; }
/* feed errors, chapter navigation, transcript */ /* feed errors, chapter navigation, transcript */
.nui-pod-feederr { font-size: 12px; color: var(--yellow); } .nui-pod-feederr { font-size: 12px; color: var(--yellow); }
.nui-pod-offer { padding: 10px 12px; margin: 8px 0; border: 1px solid var(--nui-muted); border-radius: 6px; line-height: 1.45; font-size: 14px; font-family: var(--nui-sans); letter-spacing: normal; font-weight: 400; } .nui-pod-offer { padding: 10px 12px; margin: 8px 0; border: 1px solid var(--nui-muted); border-radius: 6px; line-height: 1.45; font-size: 14px; }
.nui-pod-offer-row { display: flex; gap: 8px; margin-top: 8px; } .nui-pod-offer-row { display: flex; gap: 8px; margin-top: 8px; }
.nui-pod-feederr-box { padding: 8px 10px; margin: 4px 0 8px; border: 1px solid var(--yellow); border-radius: 6px; line-height: 1.4; } .nui-pod-feederr-box { padding: 8px 10px; margin: 4px 0 8px; border: 1px solid var(--yellow); border-radius: 6px; line-height: 1.4; }
.nui-pod-chapnav { display: flex; align-items: center; gap: 4px; margin-bottom: 4px; }
.nui-pod-chapnow { flex: 1; min-width: 0; text-align: center; font-size: 13px; color: var(--nui-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nui-pod-transcript { max-height: 60vh; overflow-y: auto; padding-right: 4px; } .nui-pod-transcript { max-height: 60vh; overflow-y: auto; padding-right: 4px; }
.nui-pod-seg { margin: 0; padding: 6px 4px; font-family: var(--nui-sans); letter-spacing: normal; font-weight: 400; font-size: 14.5px; line-height: 1.5; color: var(--nui-muted); border-radius: 4px; cursor: pointer; } .nui-pod-seg { margin: 0; padding: 6px 4px; font-size: 14px; line-height: 1.5; color: var(--nui-muted); border-radius: 4px; cursor: pointer; }
.nui-pod-seg.current { color: inherit; background: #1a1a1a; } .nui-pod-seg.current { color: inherit; background: #1a1a1a; }
.nui-pod-speaker { color: inherit; } .nui-pod-speaker { color: inherit; }
body.eink .nui-pod-feederr { color: #000; } body.eink .nui-pod-feederr { color: #000; }
@ -581,164 +579,13 @@ body.eink .nui-pod-seg.current { color: #000; background: #e6e6e6; }
.nui-pod-sec-note { padding: 10px 2px; font-size: 13px; color: var(--nui-muted); } .nui-pod-sec-note { padding: 10px 2px; font-size: 13px; color: var(--nui-muted); }
.nui-pod-feedcount { flex: none; } .nui-pod-feedcount { flex: none; }
.nui-pod-infobtn { flex: none; font-size: 20px; color: var(--nui-muted); } .nui-pod-infobtn { flex: none; font-size: 20px; color: var(--nui-muted); }
.nui-pod-info-desc { font-family: var(--nui-sans); letter-spacing: normal; font-size: 14.5px; font-weight: 400; line-height: 1.5; white-space: pre-line; overflow-wrap: anywhere; padding: 4px 0 10px; border-bottom: 1px solid var(--border); } .nui-pod-info-desc { font-size: 14px; font-weight: 400; line-height: 1.5; white-space: pre-line; overflow-wrap: anywhere; padding: 4px 0 10px; border-bottom: 1px solid var(--border); }
.nui-pod-facts { font-weight: 400; display: grid; grid-template-columns: max-content 1fr; gap: 8px 14px; margin: 10px 0 4px; font-size: 14px; } .nui-pod-facts { font-weight: 400; display: grid; grid-template-columns: max-content 1fr; gap: 8px 14px; margin: 10px 0 4px; font-size: 14px; }
.nui-pod-facts dt { color: var(--nui-muted); } .nui-pod-facts dt { color: var(--nui-muted); }
.nui-pod-facts dd { margin: 0; min-width: 0; font-weight: 400; overflow-wrap: anywhere; } .nui-pod-facts dd { margin: 0; min-width: 0; font-weight: 400; overflow-wrap: anywhere; }
.nui-pod-facts a { color: inherit; text-decoration: underline; text-decoration-color: #777; } .nui-pod-facts a { color: var(--accent); }
.nui-pod-info-url { display: block; font-size: 12px; color: var(--nui-muted); } .nui-pod-info-url { display: block; font-size: 12px; color: var(--nui-muted); }
.nui-pod-info-actions { display: flex; gap: 8px; margin-top: 6px; } .nui-pod-info-actions { display: flex; gap: 8px; margin-top: 6px; }
.nui-pod-info-actions .btn { min-height: 36px; padding: 0 14px; font-size: 14px; } .nui-pod-info-actions .btn { min-height: 36px; padding: 0 14px; font-size: 14px; }
body.eink .nui-pod-sec { color: #000; border-bottom-color: #000; } body.eink .nui-pod-sec { color: #000; border-bottom-color: #000; }
body.eink .nui-pod-facts a { color: #000; text-decoration: underline; } body.eink .nui-pod-facts a { color: #000; text-decoration: underline; }
/* ---- UI round: rows, swipes, the feed page's line, the player's chapters ------------------ */
/* the row's one action, its menu, the download mark */
.nui-pod-row > .nui-icon { min-width: 40px; }
.nui-pod-row > .nui-pod-menu { min-width: 34px; font-size: 18px; color: var(--nui-muted); margin-right: -4px; }
.nui-pod-playbtn { font-size: 17px !important; color: var(--accent); }
.nui-pod-row > .nui-pod-dl { min-width: 40px !important; margin-left: 0; }
.nui-pod-dlmark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
margin-right: 5px;
vertical-align: -2px;
border-radius: 50%;
border: 1px solid #8a8a8a;
font-size: 9px;
font-weight: 700;
line-height: 1;
color: #cfcfcf;
}
.nui-pod-dlmark-done { background: #3a3a3a; border-color: #3a3a3a; color: #fff; }
.nui-pod-dlmark-failed { border-color: var(--yellow); color: var(--yellow); }
.nui-pod-dlmark-queued,
.nui-pod-dlmark-downloading {
border: none;
background:
radial-gradient(closest-side, var(--bg) 62%, transparent 66% 100%),
conic-gradient(#d0d0d0 var(--p, 0%), #3a3a3a 0);
}
.nui-pod-queued { font-size: 18px !important; }
/* swiping a row: it moves, what the swipe does shows underneath */
.nui-pod-list { position: relative; }
.nui-pod-list > .nui-pod-row { touch-action: pan-y; }
.nui-pod-row.nui-pod-swiping { position: relative; z-index: 1; will-change: transform; }
.nui-pod-swipebg {
position: absolute;
left: 0;
right: 0;
z-index: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 18px;
background: #161616;
color: var(--nui-muted);
font-size: 14px;
white-space: nowrap;
pointer-events: none;
}
.nui-pod-swipebg.armed { background: #2c2c2c; color: var(--fg, #fff); font-weight: 700; }
#nui-pod-undo {
position: fixed;
left: 50%;
bottom: calc(var(--nui-dock-h, 124px) + 12px + env(safe-area-inset-bottom, 0px));
transform: translateX(-50%);
z-index: 950;
display: flex;
align-items: center;
gap: 14px;
max-width: calc(100% - 24px);
padding: 4px 6px 4px 16px;
border-radius: 8px;
background: #262626;
color: #fff;
font-size: 14px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.18s;
}
#nui-pod-undo.nui-show { opacity: 1; pointer-events: auto; }
.nui-pod-undo-text { font-family: var(--nui-sans); letter-spacing: normal; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nui-pod-undo-btn {
min-height: 40px;
padding: 0 10px;
border: none;
background: none;
color: #fff;
font: inherit;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
cursor: pointer;
}
/* the top bar's search */
.nui-pod-searchbtn svg { display: block; }
.nui-pod-searchbtn.on { color: var(--fg, #fff); }
/* the feed page: one quiet line instead of the settings, the ⚙ sheet's parts */
.nui-pod-feedsum {
display: block;
width: 100%;
margin: -2px 0 6px;
padding: 2px 0 8px;
border: none;
border-bottom: 1px solid var(--border);
background: none;
color: var(--nui-muted);
font-family: var(--nui-sans);
font-size: 13px;
font-weight: 400;
letter-spacing: normal;
line-height: 1.4;
text-align: left;
cursor: pointer;
}
.nui-pod-gear { font-size: 22px !important; }
.nui-pod-sheetsep { height: 1px; margin: 6px 20px; background: var(--border); }
.nui-pod-sheetnote { padding: 2px 20px 10px; white-space: normal; line-height: 1.45; font-size: 13px; }
/* the player's chapters, right under the title */
.nui-pod-chapbox { margin-top: -4px; border: 1px solid var(--border); border-radius: 8px; padding: 2px 4px 2px 10px; }
.nui-pod-chapbox[hidden] { display: none; }
.nui-pod-chaphead { display: flex; align-items: center; gap: 2px; min-height: 40px; }
.nui-pod-chapcount { flex: 1; min-width: 0; font-size: 12px; color: var(--nui-muted); font-variant-numeric: tabular-nums; }
.nui-pod-chapbtn { min-width: 40px !important; min-height: 40px !important; font-size: 16px !important; }
.nui-pod-chaplist { position: relative; max-height: 168px; overflow-y: auto; margin: 0 -4px 4px -10px; padding: 0 4px 0 10px; border-top: 1px solid var(--border); }
.nui-pod-chaplist .nui-pod-chapter { padding: 8px 4px; }
.nui-pod-chaplist .nui-pod-chapter:last-child { border-bottom: none; }
.nui-pod-art-player { width: min(64vw, 300px); height: min(64vw, 300px); }
.nui-pod-withchapters .nui-pod-art-player { width: min(46vw, 220px); height: min(46vw, 220px); }
/* the downloads overview: the finished ones as a list */
.nui-pod-dls-donerow { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.nui-pod-dls-show { min-height: 36px; padding: 0 12px; font-size: 14px !important; }
body.eink .nui-pod-count-queue { background: #000; color: #fff; }
body.eink .nui-pod-tab.active .nui-pod-count { background: #000; color: #fff; }
body.eink .nui-pod-playbtn { color: #000; }
body.eink .nui-pod-dlmark { border-color: #000; color: #000; }
body.eink .nui-pod-dlmark-done { background: #000; color: #fff; }
body.eink .nui-pod-dlmark-queued,
body.eink .nui-pod-dlmark-downloading {
background:
radial-gradient(closest-side, #fff 62%, transparent 66% 100%),
conic-gradient(#000 var(--p, 0%), #ccc 0);
}
body.eink .nui-pod-swipebg { background: #eee; color: #444; }
body.eink .nui-pod-swipebg.armed { background: #000; color: #fff; }
body.eink #nui-pod-undo { background: #000; box-shadow: none; }
body.eink .nui-pod-initial { color: #555; }
body.eink .nui-pod-feedsum { color: #444; border-bottom-color: #000; }
body.eink .nui-pod-chapbox { border-color: #000; }
body.eink .nui-pod-chip.on { border-color: #000; color: #000; }
body.eink .nui-pod-notes a,
body.eink .nui-pod-facts a { text-decoration-color: #000; }

View file

@ -161,9 +161,6 @@ html.dpw .nui-mini-toggle { font-size: 22px; color: var(--accent); }
transition: opacity 0.2s; transition: opacity 0.2s;
} }
#nui-radio-toast.nui-show { opacity: 1; } #nui-radio-toast.nui-show { opacity: 1; }
/* The swipe's „Rückgängig“ bar: above the radio bar, like the toast. */
html.dpw #nui-pod-undo { bottom: calc(var(--dpw-bar-h, 72px) + 80px); }
html.dpw.nui-pod-mini-open:not(.dpw-in-bar) #nui-pod-undo { bottom: calc(var(--dpw-bar-h, 72px) + 64px + 16px); }
/* ---- a mouse: hover states the phone doesn't need --------------------------------- */ /* ---- a mouse: hover states the phone doesn't need --------------------------------- */
@ -234,11 +231,5 @@ html.dpw body.bright-bg .nui-pod-art { background: #e4e4e4; }
html.dpw body.bright-bg .nui-pod-count, html.dpw body.bright-bg .nui-pod-count,
html.dpw body.bright-bg .nui-pod-progress, html.dpw body.bright-bg .nui-pod-progress,
html.dpw body.bright-bg .nui-pod-mini-bar { background: #d4d4d4; } html.dpw body.bright-bg .nui-pod-mini-bar { background: #d4d4d4; }
html.dpw body.bright-bg .nui-pod-count { color: var(--fg); } html.dpw body.bright-bg .nui-pod-tab.active .nui-pod-count { background: var(--accent); color: #fff; }
html.dpw body.bright-bg .nui-pod-count-queue { background: var(--accent); color: #fff; }
html.dpw body.bright-bg .nui-pod-swipebg { background: #ececec; }
html.dpw body.bright-bg .nui-pod-swipebg.armed { background: #d6d6d6; }
html.dpw body.bright-bg .nui-pod-dlmark-done { background: #d4d4d4; border-color: #d4d4d4; color: #000; }
html.dpw body.bright-bg .nui-pod-initial { color: #888; }
html.dpw body.bright-bg .nui-pod-chip.on { border-color: #555; }
html.dpw body.bright-bg #nui-pod-mini { box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.12); } html.dpw body.bright-bg #nui-pod-mini { box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.12); }