diff --git a/static/js/sw.js b/static/js/sw.js index 97ced2b..2113fd9 100644 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -2,7 +2,7 @@ * diora service worker — caches the app shell for offline use. */ -const CACHE = 'diora-v100'; +const CACHE = 'diora-v101'; const SHELL = [ '/static/css/app.css', '/static/js/app.js', diff --git a/static/podcasts/native-podcast.js b/static/podcasts/native-podcast.js index 3a2ca2e..24ebfac 100644 --- a/static/podcasts/native-podcast.js +++ b/static/podcasts/native-podcast.js @@ -1,4 +1,4 @@ -// Copied from diora-android 7dcc0c6 2026-10-04: web-native/static/native-podcast.js +// Copied from diora-android 5821dfa 2026-10-12: web-native/static/native-podcast.js // by tools/sync_podcast_ui.py. Do not edit here: change it in diora-android // and copy again. The browser host is static/js/podcasts-web.js. @@ -4405,6 +4405,22 @@ if (epScreen && (before.episodeId !== player.episodeId || !!before.playing !== !!player.playing)) episodeScreenParts(); }; + // What plays, for the screensaver (native-saver.js): the player's report and the chapter at its place. + const nowAsked = new Set(); + window.__dioraPlayerNow = function () { + if (!player || player.kind !== 'podcast') return player; + const d = details.get(player.episodeId); + if (!d && player.episodeId && !nowAsked.has(player.episodeId)) { + nowAsked.add(player.episodeId); + Promise.resolve(loadDetails(player.episodeId)).catch(() => {}); + } + let chapter = ''; + const chapters = (d && d.chapters) || []; + const t = (player.pos || 0) / 1000; + for (const c of chapters) if ((c.start || 0) <= t + 0.5) chapter = c.title || ''; + return Object.assign({}, player, {chapter: plain(chapter)}); + }; + // Ambient starts (app.js): an episode playing here pauses. window.__dioraPausePlayer = function () { if (player.kind !== 'podcast' || !player.playing || player.remote) return; diff --git a/static/podcasts/podcast-ui.css b/static/podcasts/podcast-ui.css index 7422bc7..39a02cf 100644 --- a/static/podcasts/podcast-ui.css +++ b/static/podcasts/podcast-ui.css @@ -1,4 +1,4 @@ -/* Copied from diora-android 67a915e 2026-10-03: the podcast parts of web-native/static/native.css +/* Copied from diora-android 5821dfa 2026-10-12: the podcast parts of web-native/static/native.css (bottom sheet, podcasts) by tools/sync_podcast_ui.py. Do not edit here. The web's own additions are in podcasts-web.css. */ @@ -796,3 +796,185 @@ body.eink .nui-pod-facts a { text-decoration-color: #000; } background: rgba(0, 0, 0, 0.55); color: #ddd; } + +/* diora Reader (books only, native.js): no read-aloud, no cloud import. */ +.nui-reader-app #reader-tts-btn, +.nui-reader-app #reader-tts-lang, +.nui-reader-app button[onclick*="toggleCloudImport"], +.nui-reader-app #cloud-browser { display: none !important; } + +/* Datenverbrauch (native-ui.js showDataUsage) */ +.nui-usage { padding: 4px 16px 12px; } +.nui-usage-chips { display: flex; gap: 6px; margin-bottom: 12px; } +.nui-usage-chip { padding: 6px 12px; border: 1px solid var(--border, #2a2a2a); border-radius: 16px; background: none; color: inherit; font: inherit; font-size: 13px; } +.nui-usage-chip.on { border-color: var(--accent, #e63946); color: var(--accent, #e63946); } +.nui-usage-total { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; margin-bottom: 10px; } +.nui-usage-total b { color: var(--accent, #e63946); } +.nui-usage-table { width: 100%; border-collapse: collapse; font-size: 13px; } +.nui-usage-table th { text-align: right; font-weight: 400; color: var(--nui-muted, #9a9a9a); padding: 2px 0 6px; } +.nui-usage-table td { padding: 7px 0; border-top: 1px solid var(--border, #2a2a2a); vertical-align: top; } +.nui-usage-table td + td { text-align: right; white-space: nowrap; padding-left: 10px; } +.nui-usage-bar { display: block; height: 3px; margin-top: 4px; background: #2a2a2a; border-radius: 2px; overflow: hidden; } +.nui-usage-bar > span { display: block; height: 100%; background: var(--accent, #e63946); } +.nui-usage-note { font-size: 12px; color: var(--nui-muted, #9a9a9a); margin: 10px 0 0; } +body.eink .nui-usage-bar > span { background: #000; } +body.eink .nui-usage-total b { color: #000; } + +/* ---- sidebar layout (html.nui-sidebar, native-radio.js): the menu as a rail on the left, + the player bars at the bottom of the page. Always on a TV, a choice in the app menu elsewhere. ---- */ + +.nui-rail-brand, .nui-rail-menu { display: none; } + +html.nui-sidebar { --nui-rail-w: 200px; } +html.nui-sidebar body { padding-left: var(--nui-rail-w); } +html.nui-sidebar.nui-reading body { padding-left: 0; } +html.nui-sidebar #nui-dock { left: var(--nui-rail-w); } +html.nui-sidebar #nui-tabbar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--nui-rail-w); + flex-direction: column; + padding: 18px 0 12px; + background: #0b0b0b; + border-right: 1px solid var(--border); + overflow-y: auto; +} +html.nui-sidebar #nui-tabbar button { + flex: 0 0 auto; + height: 46px; + flex-direction: row; + justify-content: flex-start; + gap: 14px; + padding: 0 22px; + font-size: 15px; + text-align: left; +} +html.nui-sidebar #nui-tabbar .nui-tab-icon { width: 22px; text-align: center; font-size: 19px; } +html.nui-sidebar #nui-tabbar button.nui-active { background: #181818; box-shadow: inset 3px 0 0 var(--accent); } +html.nui-sidebar .nui-rail-brand { + display: block; + padding: 0 22px 18px; + color: var(--accent); + font-weight: 700; + font-size: 22px; + letter-spacing: 0.08em; +} +html.nui-sidebar .nui-rail-menu { display: flex; margin-top: auto; } +/* Home's own wordmark: the rail has it already. */ +html.nui-sidebar #tab-home .home-brand { display: none; } +body.eink #nui-tabbar { background: #fff; } +/* A narrow screen (a phone upright): the rail shows its signs only. */ +@media (max-width: 640px) { + html.nui-sidebar { --nui-rail-w: 60px; } + html.nui-sidebar #nui-tabbar button { padding: 0; justify-content: center; font-size: 0; gap: 0; } + html.nui-sidebar #nui-tabbar .nui-tab-icon { font-size: 21px; } + html.nui-sidebar .nui-rail-brand { padding: 0 0 14px; text-align: center; font-size: 16px; letter-spacing: 0; overflow: hidden; } +} + +/* ---- television (html.nui-tv, native-tv.js): a focus that the remote moves ---- */ + +html.nui-tv { scroll-padding: 24px 0 calc(var(--nui-dock-h, 0px) + 24px); } +html.nui-tv .nui-tv-focus { + outline: 3px solid var(--accent) !important; + outline-offset: 2px; + border-radius: 6px; +} +html.nui-tv #nui-tabbar button.nui-tv-focus { outline-offset: -3px; background: #222; } +html.nui-tv :focus:not(.nui-tv-focus) { outline: none; } + +/* Books and feeds hidden on the TV (unless switched on in the app menu); Home's + sections of them: native-tv.js (:has, which the e-readers' old WebView lacks). */ +html.nui-noread #nui-tabbar button[data-tab="books"], +html.nui-noread #nui-tabbar button[data-tab="feeds"] { display: none; } + +/* ---- screensaver (native-saver.js): what plays, large, drifting slowly ---- */ + +#nui-saver { + position: fixed; + inset: 0; + z-index: 3000; + display: none; + overflow: hidden; + background: #000; + color: #fff; + cursor: none; +} +#nui-saver.nui-on { display: block; animation: nui-saver-in 1.2s ease-out; } +@keyframes nui-saver-in { from { opacity: 0; } to { opacity: 1; } } +.nui-saver-bg { + position: absolute; + inset: -10%; + background: center / cover no-repeat; + filter: blur(60px) brightness(0.32) saturate(1.3); +} +.nui-saver-stage { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 5vw; + padding: 8vh 10vw; + transition: transform 44s ease-in-out; +} +.nui-saver-art { + flex: 0 0 auto; + width: 34vmin; + height: 34vmin; + border-radius: 3vmin; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: #161616; + box-shadow: 0 2vmin 8vmin rgba(0, 0, 0, 0.6); +} +.nui-saver-art img { width: 100%; height: 100%; object-fit: cover; } +.nui-saver-art span { font-size: 16vmin; color: var(--accent); line-height: 1; } +.nui-saver-text { flex: 0 1 52vw; min-width: 0; display: flex; flex-direction: column; gap: 1.6vmin; } +.nui-saver-kicker, .nui-saver-sub, .nui-saver-chapter, .nui-saver-left { font-family: var(--nui-sans); letter-spacing: normal; } +.nui-saver-kicker { color: var(--accent); font-size: 2.6vmin; font-weight: 600; text-transform: uppercase; } +.nui-saver-title { + font-size: 5.4vmin; + font-weight: 700; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +.nui-saver-sub { font-size: 3.4vmin; color: #ddd; line-height: 1.3; } +.nui-saver-chapter { font-size: 3vmin; color: var(--accent); } +.nui-saver-progress { height: 0.6vmin; border-radius: 1vmin; background: rgba(255, 255, 255, 0.18); overflow: hidden; margin-top: 1vmin; } +.nui-saver-progress span { display: block; height: 100%; background: var(--accent); transition: width 4s linear; } +.nui-saver-left { font-size: 2.6vmin; color: #bbb; } +#nui-saver [hidden] { display: none; } +.nui-saver-clock { + position: absolute; + right: 5vw; + bottom: 6vh; + font-size: 7vmin; + font-weight: 200; + color: rgba(255, 255, 255, 0.75); + font-variant-numeric: tabular-nums; + transition: transform 44s ease-in-out; +} +/* Ambient: its own picture full screen; the saver only adds the clock and the sound world. */ +html.nui-saver-ambient #tab-ambient { display: block !important; } +html.nui-saver-ambient #ambient-frame { + position: fixed !important; + inset: 0; + width: 100vw !important; + height: 100vh !important; + z-index: 2999; + border: 0; +} +html.nui-saver-ambient #nui-saver { background: transparent; } +html.nui-saver-ambient .nui-saver-bg, +html.nui-saver-ambient .nui-saver-art, +html.nui-saver-ambient .nui-saver-progress, +html.nui-saver-ambient .nui-saver-left { display: none; } +html.nui-saver-ambient .nui-saver-stage { align-items: flex-start; justify-content: flex-start; } +html.nui-saver-ambient .nui-saver-title { font-size: 3.4vmin; font-weight: 300; letter-spacing: 0.1em; }