diff options
| author | Adam Soltys <[email protected]> | 2026-06-24 20:13:25 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-24 20:13:25 -0700 |
| commit | 3db3ba8e134363a3f11081f99a23950f1e6af9e2 (patch) | |
| tree | 7d8a1f5df1cd1e9c009f2358d2a0e781f4fd511a /src/app.js | |
| parent | 9e2737e6736f9dd582d083c6d6fd669a28a99316 (diff) | |
| download | coinosv3-3db3ba8e134363a3f11081f99a23950f1e6af9e2.tar.gz coinosv3-3db3ba8e134363a3f11081f99a23950f1e6af9e2.zip | |
Fix dark-mode contrast and stop install-link jitter
- warn-box, addr-box, summary, toast and green badge/notice borders used
hardcoded light colors that washed out or vanished in dark mode; route
them through theme variables (new --toast-bg/-fg and --green-line pairs).
- Drive the footer "Install app" link from a persisted localStorage flag
instead of the live beforeinstallprompt event so it's present on first
paint after a refresh, eliminating the layout shift.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/app.js')
| -rw-r--r-- | src/app.js | 37 |
1 files changed, 31 insertions, 6 deletions
@@ -119,8 +119,10 @@ function footer() { { style: 'margin-top:4px' }, h('button', { class: 'linklike', style: 'font-weight:400', onClick: openHowItWorks }, t('howItWorks')), // Chrome no longer prompts to install on its own โ surface our own link - // once it reports the app is installable (beforeinstallprompt fired). - installPrompt + // once it reports the app is installable. We render from a persisted flag + // (not the live event) so the link is present on the first paint after a + // refresh, avoiding a layout shift when beforeinstallprompt fires late. + installable() ? h('span', {}, h('span', { class: 'faint' }, ' ยท '), h('button', { class: 'linklike', style: 'font-weight:400', onClick: triggerInstall }, t('installApp'))) : null, @@ -151,25 +153,48 @@ function toggleTheme() { // PWA install. Chrome fires beforeinstallprompt when the app qualifies; we stash // the event and reveal an "Install app" link, then replay it on a user tap (the -// browser requires a gesture). beforeinstallprompt only fires when not already -// installed, so the link naturally hides once installed. +// browser requires a gesture). The link's visibility is driven by a persisted +// flag rather than the live event so it's present on the first paint after a +// refresh (no layout shift); the event only supplies the prompt to replay. +const INSTALLABLE_KEY = 'btc-wallet-installable'; let installPrompt = null; +function isStandalone() { + try { + return matchMedia('(display-mode: standalone)').matches || navigator.standalone === true; + } catch { return false; } +} +function installable() { + if (isStandalone()) return false; + try { return localStorage.getItem(INSTALLABLE_KEY) === '1'; } catch { return false; } +} +function setInstallable(v) { + try { + if (v) localStorage.setItem(INSTALLABLE_KEY, '1'); + else localStorage.removeItem(INSTALLABLE_KEY); + } catch {} +} if (typeof window !== 'undefined') { window.addEventListener('beforeinstallprompt', (e) => { e.preventDefault(); installPrompt = e; - render(); + // Only re-render if this changes what's on screen; on a refresh the link is + // already shown from the persisted flag, so nothing moves. + const wasShown = installable(); + setInstallable(true); + if (!wasShown) render(); }); window.addEventListener('appinstalled', () => { installPrompt = null; + setInstallable(false); render(); }); } async function triggerInstall() { const e = installPrompt; + // The prompt event may not have fired yet this load even though the link is + // shown from the persisted flag; bail quietly if so. if (!e) return; installPrompt = null; - render(); e.prompt(); try { await e.userChoice; |
