aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.js76
-rw-r--r--src/i18n.js2
-rw-r--r--src/style.css8
3 files changed, 59 insertions, 27 deletions
diff --git a/src/app.js b/src/app.js
index 64d6f0f..9830800 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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); }