updates: the app checks for a new version on every return to the foreground and shows a NEW VERSION · RELOAD pill; RELOAD is also on the seed screen for the installed app
shots / shots (push) Canceled after 0s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
This commit is contained in:
teahubxyzandClaude Fable 5.1 committed 2026-10-21 03:40:36 +00:00
1 parent e699de83aa
commit b4809dbbe1
1 file changed
+16 -2
+16 -2
View File
@@ -82,6 +82,7 @@ input,textarea{font:inherit;color:inherit}
#stationSheet,#picker,#tapePick{z-index:6}
#stationSheet{position:fixed;left:50%;bottom:0;transform:translateX(-50%);width:min(480px,100%);background:var(--card);border:1px solid var(--line);border-bottom:0;border-radius:18px 18px 0 0;padding:14px 14px calc(14px + env(safe-area-inset-bottom));z-index:6;box-shadow:0 -20px 60px rgba(0,0,0,.6)}
#stationSheet h4{font:600 10px var(--font);letter-spacing:.2em;color:var(--dim);margin:0 0 10px}
#upd{position:fixed;top:calc(10px + env(safe-area-inset-top));left:50%;transform:translateX(-50%);z-index:9;padding:9px 16px;border-radius:999px;background:var(--ink);color:var(--bg);font:600 11px var(--font);letter-spacing:.16em;box-shadow:0 10px 30px -10px rgba(0,0,0,.6)}
#stationSheet .x{position:absolute;top:8px;right:8px;width:36px;height:36px;border-radius:50%;display:grid;place-items:center;color:var(--dim);font:300 22px var(--sans);line-height:1}
#stationSheet .x:hover{color:var(--ink);background:var(--bg)}
#stationSheet label{display:block;color:var(--dim);font-size:12px;margin:8px 0 4px}
@@ -282,7 +283,7 @@ input,textarea{font:inherit;color:inherit}
<div class="hint" id="seedHint">a mood, a line, a place, a word</div>
</form>
<div id="stations" class="hidden"><h3>YOUR STATIONS</h3><div class="row" id="stationRow"></div></div>
<div id="keyRow"><button class="nav" id="libBtn2" type="button">LIBRARY</button><button class="nav" id="tasteBtn2" type="button">TASTE</button><button class="nav theme" type="button" title="light / dark">LIGHT</button><button id="keyBtn" type="button" class="hidden">shared budget · add a venice key</button></div>
<div id="keyRow"><button class="nav" id="libBtn2" type="button">LIBRARY</button><button class="nav" id="tasteBtn2" type="button">TASTE</button><button class="nav theme" type="button" title="light / dark">LIGHT</button><button class="nav reload" type="button" title="reload the app">RELOAD</button><button id="keyBtn" type="button" class="hidden">shared budget · add a venice key</button></div>
<div id="keyBox" class="hidden">
<p>Optional. Without a key, gandora runs on a shared budget through inference.yolopreneur.lol (a few tracks a day per person). With your own Venice key it goes straight to api.venice.ai from this browser and the key goes nowhere else.</p>
<input id="keyIn" placeholder="sk-…" spellcheck="false">
@@ -362,6 +363,7 @@ input,textarea{font:inherit;color:inherit}
<div class="empty hidden" id="tapeEmpty">Empty tape. Add tracks from the library with the + on a tile.</div>
</section>
<div id="tapePick" class="hidden"></div>
<button id="upd" class="hidden" type="button">NEW VERSION · RELOAD</button>
<div id="stationSheet" class="hidden"></div>
<div id="scrim" class="hidden"></div>
@@ -1343,7 +1345,19 @@ if ("mediaSession" in navigator) {
state.audio.addEventListener("play", () => { ms.playbackState = "playing"; });
state.audio.addEventListener("pause", () => { ms.playbackState = "paused"; });
}
if ("serviceWorker" in navigator && location.protocol === "https:") navigator.serviceWorker.register("sw.js").catch(() => {});
/* updates: an installed app has no address bar to refresh from, so the page checks for a new worker whenever it
comes back to the foreground (and every ten minutes) and offers a reload when one has taken over */
function showUpdate() { const u = $("#upd"); if (u) u.classList.remove("hidden"); }
if ("serviceWorker" in navigator && location.protocol === "https:") {
navigator.serviceWorker.register("sw.js").then((reg) => {
const check = () => reg.update().catch(() => {});
setInterval(check, 10 * 60 * 1000);
document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") check(); });
reg.addEventListener("updatefound", () => { const nw = reg.installing; if (!nw) return; nw.addEventListener("statechange", () => { if (nw.state === "activated" && navigator.serviceWorker.controller) showUpdate(); }); });
}).catch(() => {});
}
$("#upd").addEventListener("click", () => location.reload());
for (const b of document.querySelectorAll("button.reload")) b.addEventListener("click", () => location.reload());
/* light / dark: follows the system until the toggle is used, then remembered */
const sysDark = window.matchMedia("(prefers-color-scheme: dark)");
function applyTheme() {