aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-23 08:21:58 -0700
committerAdam Soltys <[email protected]>2026-06-23 08:21:58 -0700
commit43acfb9e9e9205e02c39c398c18e3325211f40f1 (patch)
tree68dffae1150033f6dea65a7c0a0eb071053f1575 /src
parent7c542410dc78711bcf017dd3d23d560ed6349669 (diff)
downloadcoinosv3-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.js35
-rw-r--r--src/i18n.js1
2 files changed, 35 insertions, 1 deletions
diff --git a/src/app.js b/src/app.js
index 8343290..89da6d0 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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.',