diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 76 | ||||
| -rw-r--r-- | src/i18n.js | 2 | ||||
| -rw-r--r-- | src/style.css | 8 |
3 files changed, 59 insertions, 27 deletions
@@ -2254,7 +2254,10 @@ function accountSel() { } return ui.account; } -function setAccountSel(a) { +let _accDir = null; // which way the balance face should slide in +function setAccountSel(a, dir) { + if (ui.account === a) return; + _accDir = dir || (a === 'savings' ? 'left' : 'right'); ui.account = a; try { localStorage.setItem(ACCOUNT_KEY, a); } catch {} render(); @@ -2276,36 +2279,39 @@ function balanceCard() { const saving = wallet.spendable; const hasArk = !!featureHook('arkReady'); const sel = hasArk ? accountSel() : 'savings'; - return h( - 'div', - { class: 'card balance' }, - // The account toggle IS the headline label: you're always "in" Spending - // or Savings, and everything (receive, gifts) follows. No total — the two - // are different kinds of money, and adding them up says nothing useful. - hasArk - ? h('div', { class: 'seg balance-seg' }, - h('button', { class: sel === 'spending' ? 'active' : '', onClick: () => setAccountSel('spending') }, t('spendingLabel')), - h('button', { class: sel === 'savings' ? 'active' : '', onClick: () => setAccountSel('savings') }, t('savingLabel'))) - : h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.05em' }, t('balance')), + const isSpending = sel === 'spending'; + // One balance at a time, full size: the card IS the account you're in, and + // everything (receive, gifts) follows it. Swipe or tap the dots to flip. + const dtAcc = animWindow('acct', sel, 300); + const face = h('div', { class: 'balance-face' }, + h('div', { class: 'row between', style: 'align-items:center' }, + h('div', { class: 'small faint', style: 'text-transform:uppercase;letter-spacing:.06em' }, + hasArk ? (isSpending ? t('spendingLabel') : t('savingLabel')) : t('balance')), + hasArk + ? h('div', { class: 'balance-dots' }, + h('button', { class: 'bdot' + (isSpending ? ' on' : ''), title: t('spendingLabel'), onClick: () => setAccountSel('spending') }), + h('button', { class: 'bdot' + (!isSpending ? ' on' : ''), title: t('savingLabel'), onClick: () => setAccountSel('savings') })) + : null), h('div', { class: 'amt', style: firstLoad ? 'opacity:.3' : '' }, firstLoad ? h('span', { class: 'spinner sm', style: 'margin-right:8px' }) : null, - animatedAmount('bal:' + sel, sel === 'spending' ? spending : saving), ' ', unitTag('unit')), - hasArk || pending > 0 || featLines.length - ? h( - 'div', - { class: 'split' }, - hasArk - ? h('div', {}, - h('div', { class: 'k' }, sel === 'spending' ? t('savingLabel') : t('spendingLabel')), - h('div', { class: 'v' }, animatedAmount('bal2:' + sel, sel === 'spending' ? saving : spending), ' ', unitTag())) - : null, + animatedAmount('bal:' + sel, isSpending ? spending : saving), ' ', unitTag('unit')), + hasArk + ? h('div', { class: 'small muted balance-note' }, isSpending ? t('balanceNoteSpending') : t('balanceNoteSavings')) + : null, + pending > 0 || featLines.length + ? h('div', { class: 'split' }, pending > 0 ? h('div', {}, h('div', { class: 'k' }, t('pending')), h('div', { class: 'v pending' }, fmtAmount(pending), ' ', unitTag())) : null, ...featLines.map((l) => - h('div', {}, h('div', { class: 'k' }, l.label), h('div', { class: 'v' }, fmtAmount(l.sat), ' ', unitTag()))) - ) - : null, + h('div', {}, h('div', { class: 'k' }, l.label), h('div', { class: 'v' }, fmtAmount(l.sat), ' ', unitTag())))) + : null); + applyAnim(face, 'anim-tab-' + (_accDir || 'left'), dtAcc); + _accDir = null; + return h( + 'div', + { class: 'card balance' }, + face, // "Move money" and friends: the only door between the two balances. ...featureAll('balanceActions').map((a2) => h('button', { class: 'btn-sm', style: 'margin-top:10px', onClick: a2.onClick }, a2.label)), @@ -3387,11 +3393,18 @@ applyDir(); // inside chat, profiles, takeovers, or the (offline-forced) settings tab. (() => { const ORDER = ['receive', 'send', 'history']; - let sx = 0, sy = 0, t0 = 0, live = false; + let sx = 0, sy = 0, t0 = 0, live = false, onBalance = false, swallowClick = false; + window.addEventListener('click', (e) => { + if (!swallowClick) return; + swallowClick = false; + e.stopPropagation(); + e.preventDefault(); + }, true); window.addEventListener('touchstart', (e) => { live = e.touches.length === 1 && ui.screen === 'wallet' && !ui.chatOpen && !ui.profilePk && !ui.arkExitPage && ORDER.includes(ui.tab); if (!live) return; + onBalance = !!(e.target.closest && e.target.closest('.balance')); sx = e.touches[0].clientX; sy = e.touches[0].clientY; t0 = Date.now(); @@ -3403,6 +3416,17 @@ applyDir(); const t = e.changedTouches[0]; const dx = t.clientX - sx, dy = t.clientY - sy; if (Date.now() - t0 > 600 || Math.abs(dx) < 60 || Math.abs(dx) < 1.8 * Math.abs(dy)) return; + // A touch sequence still fires a compatibility click at the release + // point; after a swipe that would ALSO press whatever is under the + // finger. Swallow exactly one click. + swallowClick = true; + setTimeout(() => { swallowClick = false; }, 400); + // a swipe that began on the balance card flips the account instead + if (onBalance) { + const want = dx < 0 ? 'savings' : 'spending'; + if (featureHook('arkReady')) setAccountSel(want, dx < 0 ? 'left' : 'right'); + return; + } const next = ORDER[ORDER.indexOf(ui.tab) + (dx < 0 ? 1 : -1)]; if (!next) return; _swipeDir = dx < 0 ? 'left' : 'right'; diff --git a/src/i18n.js b/src/i18n.js index 9f43888..37b59af 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -140,6 +140,8 @@ const STR = { moveToSavingBtn: 'Move to Savings', spendingLabel: 'Spending', savingLabel: 'Savings', + balanceNoteSpending: 'Instant · free to send', + balanceNoteSavings: 'On-chain · mining fees apply', movingLabel: 'Moving…', balance: 'Balance', pending: 'Pending', diff --git a/src/style.css b/src/style.css index 3a5ab97..0131556 100644 --- a/src/style.css +++ b/src/style.css @@ -387,7 +387,6 @@ hr.divider { margin: 16px 0; } .balance-move { gap: 10px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); text-align: left; } .balance-move .seg { display: flex; width: 100%; } .balance-move .seg button { flex: 1; } -.balance-seg { display: inline-flex; margin-bottom: 6px; } .send-suggest { max-height: 260px; overflow-y: auto; } .clickable { cursor: pointer; } .profile-avatar { width: 64px; height: 64px; font-size: 18px; } @@ -513,3 +512,10 @@ body::after { @keyframes mark-float { 50% { transform: translateY(-6px); } } @media (prefers-reduced-motion: reduce) { .onb-mark { animation: none; } } .onb-addr { color: var(--muted); font-family: var(--mono); font-size: 13px; } +/* balance card: one account at a time, dots to flip */ +.balance-face { min-height: 118px; } +.balance-note { margin-top: 2px; } +.balance-dots { display: flex; gap: 7px; align-items: center; } +.bdot { width: 8px; height: 8px; padding: 0; border-radius: 50%; border: 0; background: var(--line-strong); cursor: pointer; transition: background .18s, transform .18s; } +.bdot:hover { transform: scale(1.25); background: var(--muted); } +.bdot.on { background: var(--accent); transform: scale(1.15); } |
