diff options
| author | Adam Soltys <[email protected]> | 2026-06-23 08:21:58 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-23 08:21:58 -0700 |
| commit | 43acfb9e9e9205e02c39c398c18e3325211f40f1 (patch) | |
| tree | 68dffae1150033f6dea65a7c0a0eb071053f1575 /src | |
| parent | 7c542410dc78711bcf017dd3d23d560ed6349669 (diff) | |
| download | coinosv3-43acfb9e9e9205e02c39c398c18e3325211f40f1.tar.gz coinosv3-43acfb9e9e9205e02c39c398c18e3325211f40f1.zip | |
Add PWA install affordance; serve manifest as manifest+json
Chrome no longer shows an automatic install prompt, so catch
beforeinstallprompt and surface an "Install app" link in the footer
that replays the prompt on a user tap (hides once installed).
(nginx on the host now serves manifest.webmanifest as
application/manifest+json instead of octet-stream.)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 35 | ||||
| -rw-r--r-- | src/i18n.js | 1 |
2 files changed, 35 insertions, 1 deletions
@@ -93,11 +93,44 @@ function footer() { h( 'div', { style: 'margin-top:4px' }, - h('button', { class: 'linklike', style: 'font-weight:400', onClick: openHowItWorks }, t('howItWorks')) + 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 + ? h('span', {}, h('span', { class: 'faint' }, ' · '), + h('button', { class: 'linklike', style: 'font-weight:400', onClick: triggerInstall }, t('installApp'))) + : null ) ); } +// 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. +let installPrompt = null; +if (typeof window !== 'undefined') { + window.addEventListener('beforeinstallprompt', (e) => { + e.preventDefault(); + installPrompt = e; + render(); + }); + window.addEventListener('appinstalled', () => { + installPrompt = null; + render(); + }); +} +async function triggerInstall() { + const e = installPrompt; + if (!e) return; + installPrompt = null; + render(); + e.prompt(); + try { + await e.userChoice; + } catch {} +} + // Open the How it works page, remembering where to return to. function openHowItWorks() { if (ui.screen === 'howItWorks') return; diff --git a/src/i18n.js b/src/i18n.js index 90cb2f1..ba85819 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -60,6 +60,7 @@ const STR = { invalidSeed: 'Invalid seed phrase — check the spelling and word order.', keysNote: 'Keys never leave your browser. Works fully offline. Save this page and open it from your filesystem.', howItWorks: 'How it works', + installApp: 'Install app', hiwBasicsTitle: 'The basics', hiwBasics1: 'When you make a wallet, the page generates twelve random words. Those words are the key to your wallet. Anyone who has them can spend the coins, so write them down on paper and keep them somewhere only you can get to.', hiwBasics2: 'Everything happens on your device. There’s no server keeping your balance and nobody holding your coins for you. The words stay in your browser, and the page still works when you’re offline.', |
