From 9e2737e6736f9dd582d083c6d6fd669a28a99316 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 24 Jun 2026 19:39:50 -0700 Subject: Add dark mode; gift amount unit toggle; multi-input gifts - Dark mode: theme variables + a footer "Dark mode/Light mode" toggle, persisted and applied pre-paint via a small head script (defaults to the OS preference). Hardcoded surfaces moved to CSS variables. - Gift amount: the unit label is now the clickable sats/BTC toggle, and the amount is parsed in the selected unit. - Gifts can now span multiple coins: createGift selects several confirmed coins (first input SIGHASH_SINGLE to lock the change, the rest SIGHASH_NONE), so a gift larger than any single coin works. Claim/preview sum all inputs and size the fee by input count. Verified end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'src/app.js') diff --git a/src/app.js b/src/app.js index 466524d..76474f5 100644 --- a/src/app.js +++ b/src/app.js @@ -123,12 +123,32 @@ function footer() { installPrompt ? h('span', {}, h('span', { class: 'faint' }, ' · '), h('button', { class: 'linklike', style: 'font-weight:400', onClick: triggerInstall }, t('installApp'))) - : null + : null, + h('span', { class: 'faint' }, ' · '), + h('button', { class: 'linklike', style: 'font-weight:400', onClick: toggleTheme }, resolvedTheme() === 'dark' ? t('lightMode') : t('darkMode')) ), h('div', { style: 'margin-top:8px' }, languagePicker()) ); } +const THEME_KEY = 'btc-wallet-theme'; +function resolvedTheme() { + try { + const s = localStorage.getItem(THEME_KEY); + if (s === 'dark' || s === 'light') return s; + } catch {} + try { return matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } catch {} + return 'light'; +} +function applyTheme() { + try { document.documentElement.dataset.theme = resolvedTheme(); } catch {} +} +function toggleTheme() { + try { localStorage.setItem(THEME_KEY, resolvedTheme() === 'dark' ? 'light' : 'dark'); } catch {} + applyTheme(); + render(); +} + // 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 @@ -958,7 +978,7 @@ function giftRate() { function createGiftLink() { const rate = giftRate(); const min = giftMinimum(rate); - const amt = Math.round(Number(ui.giftAmount)); + const amt = parseAmount(ui.giftAmount, unit); // entered in the current display unit if (!amt || amt < min) { ui.giftError = t('giftAmountInvalid', { n: fmtAmount(min) + ' ' + unitLabel() }); render(); return; } try { ui.giftCode = wallet.createGift(amt, rate).code; @@ -987,9 +1007,9 @@ function giftCard() { ) : h('div', { class: 'col gap6' }, h('div', { class: 'input-group' }, - h('input', { type: 'number', min: String(giftMinimum(giftRate())), inputmode: 'numeric', placeholder: t('giftAmountLabel'), + h('input', { type: 'number', step: unit === 'sats' ? '1' : '0.00000001', min: '0', inputmode: 'decimal', placeholder: t('giftAmountLabel'), value: ui.giftAmount, onInput: (e) => (ui.giftAmount = e.target.value) }), - h('span', { class: 'small muted', style: 'align-self:center' }, unitLabel()) + h('div', { style: 'display:flex;align-items:center' }, unitTag()) ), h('div', { class: 'small faint' }, t('giftMinNote', { n: fmtAmount(giftMinimum(giftRate())) + ' ' + unitLabel() })), ui.giftError && h('div', { class: 'notice err' }, ui.giftError), -- cgit v1.2.3