aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.js111
-rw-r--r--src/style.css11
2 files changed, 65 insertions, 57 deletions
diff --git a/src/app.js b/src/app.js
index a77eae6..4adf45c 100644
--- a/src/app.js
+++ b/src/app.js
@@ -3313,58 +3313,10 @@ function balanceCard() {
h('div', {}, h('div', { class: 'k' }, l.label), h('div', { class: 'v' }, fmtAmount(l.sat), ' ', unitTag()))))
: null);
};
- const carouselMode = hasSpending && !kindLocked;
- let heroEl;
- if (carouselMode) {
- const ORDER2 = ['spending', 'savings'];
- heroEl = h('div', {
- class: 'bal-carousel',
- onScroll: (e) => {
- const el = e.target;
- el._lastScroll = Date.now();
- clearTimeout(el._settle);
- el._settle = setTimeout(() => {
- const mid = el.scrollLeft + el.clientWidth / 2;
- let best = 0, bestD = Infinity;
- [...el.children].forEach((k, i) => {
- const d = Math.abs(k.offsetLeft + k.offsetWidth / 2 - mid);
- if (d < bestD) { bestD = d; best = i; }
- });
- const view = ORDER2[best];
- if (view && view !== accountSel()) {
- ui.account = view;
- try { localStorage.setItem(ACCOUNT_KEY, view); } catch {}
- render();
- }
- }, 120);
- },
- }, ORDER2.map((v) => h('div', { class: 'bal-slide' }, faceFor(v))));
- // Align the carousel to the selected face after this render lands — but
- // never fight a finger or live momentum.
- setTimeout(() => {
- const el = document.querySelector('.bal-carousel');
- if (!el || Date.now() - (el._lastScroll || 0) < 350) return;
- const k = el.children[ORDER2.indexOf(sel)];
- if (!k) return;
- const target = k.offsetLeft + k.offsetWidth / 2 - el.clientWidth / 2;
- if (Math.abs(el.scrollLeft - target) > 6) el.scrollTo({ left: target, behavior: el._inited ? 'smooth' : 'auto' });
- el._inited = true;
- }, 0);
- } else {
- const dtRaw = animWindow('acct', sel, 300);
- // Decide AT the change whether this face switch animates: only deliberate
- // switches (tap/swipe set _accDir) slide — a background flip (arkReady
- // arriving and revealing Spending right after sign-in) must not.
- if (dtRaw >= 0 && dtRaw < 16.7) _accAnim = !!_accDir && !ui.navAnimSkip;
- const dtAcc = _accAnim ? dtRaw : -1;
- heroEl = faceFor(sel);
- applyAnim(heroEl, 'anim-tab-' + (_accDir || 'left'), dtAcc);
- }
- _accDir = null;
- return h(
- 'div',
- { class: 'card balance' },
- heroEl,
+ // Everything below the balance figure — the move button, top-up offer,
+ // spend-setup offer and the inline unfold panel — rides on whichever card
+ // is ACTIVE, so the peeking neighbor stays a clean balance preview.
+ const cardExtras = () => [
// "Move money" and friends: the only door between the two balances —
// pointless (and confusing) while there's only one.
...(hasSpending ? featureAll('balanceActions') : []).map((a2) =>
@@ -3403,8 +3355,59 @@ function balanceCard() {
const w = h('div', {}, extra);
applyAnim(w, 'anim-unfold', dt);
return w;
- })()
- );
+ })(),
+ ];
+ const carouselMode = hasSpending && !kindLocked;
+ if (carouselMode) {
+ // Two SEPARATE cards side by side — the neighbor card peeks past the
+ // active one's edge and drags/snaps into place, BlueWallet-style.
+ const ORDER2 = ['spending', 'savings'];
+ // Align the carousel to the selected card after this render lands — but
+ // never fight a finger or live momentum.
+ setTimeout(() => {
+ const el = document.querySelector('.bal-carousel');
+ if (!el || Date.now() - (el._lastScroll || 0) < 350) return;
+ const k = el.children[ORDER2.indexOf(sel)];
+ if (!k) return;
+ const target = k.offsetLeft + k.offsetWidth / 2 - el.clientWidth / 2;
+ if (Math.abs(el.scrollLeft - target) > 6) el.scrollTo({ left: target, behavior: el._inited ? 'smooth' : 'auto' });
+ el._inited = true;
+ }, 0);
+ _accDir = null; // the drag is the animation here
+ return h('div', {
+ class: 'bal-carousel',
+ onScroll: (e) => {
+ const el = e.target;
+ el._lastScroll = Date.now();
+ clearTimeout(el._settle);
+ el._settle = setTimeout(() => {
+ const mid = el.scrollLeft + el.clientWidth / 2;
+ let best = 0, bestD = Infinity;
+ [...el.children].forEach((k, i) => {
+ const d = Math.abs(k.offsetLeft + k.offsetWidth / 2 - mid);
+ if (d < bestD) { bestD = d; best = i; }
+ });
+ const view = ORDER2[best];
+ if (view && view !== accountSel()) {
+ ui.account = view;
+ try { localStorage.setItem(ACCOUNT_KEY, view); } catch {}
+ render();
+ }
+ }, 120);
+ },
+ }, ORDER2.map((v) => h('div', { class: 'card balance bal-slide' },
+ faceFor(v), ...(v === sel ? cardExtras() : []))));
+ }
+ const dtRaw = animWindow('acct', sel, 300);
+ // Decide AT the change whether this face switch animates: only deliberate
+ // switches (tap/swipe set _accDir) slide — a background flip (arkReady
+ // arriving and revealing Spending right after sign-in) must not.
+ if (dtRaw >= 0 && dtRaw < 16.7) _accAnim = !!_accDir && !ui.navAnimSkip;
+ const dtAcc = _accAnim ? dtRaw : -1;
+ const heroEl = faceFor(sel);
+ applyAnim(heroEl, 'anim-tab-' + (_accDir || 'left'), dtAcc);
+ _accDir = null;
+ return h('div', { class: 'card balance' }, heroEl, ...cardExtras());
}
diff --git a/src/style.css b/src/style.css
index 933fa69..e21835b 100644
--- a/src/style.css
+++ b/src/style.css
@@ -444,13 +444,18 @@ hr.divider { margin: 16px 0; }
/* balance carousel: the other face peeks at the card's edge and drags into
place — native scroll-snap does the physics. Full-bleed inside the card
so the peeking neighbor touches the card wall. */
+/* A page-level strip of whole balance CARDS: full-bleed past the app column
+ so the neighbor card peeks in from the screen edge. The negative margins +
+ matching padding enlarge the clip box so card shadows aren't cropped. */
.bal-carousel {
- display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory;
- margin: -20px; padding: 20px; scrollbar-width: none; -ms-overflow-style: none;
+ position: relative; /* offsetParent for the slides: offsetLeft math stays container-relative */
+ display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
+ margin: -16px; padding: 16px; scrollbar-width: none; -ms-overflow-style: none;
touch-action: pan-x pan-y;
}
.bal-carousel::-webkit-scrollbar { display: none; }
-.bal-slide { flex: 0 0 calc(100% - 34px); scroll-snap-align: center; min-width: 0; }
+.bal-slide { flex: 0 0 calc(100% - 40px); scroll-snap-align: center; min-width: 0; }
+.bal-carousel .card + .card { margin-top: 0; }
.balance-move .seg { display: flex; width: 100%; }
.balance-move .seg button { flex: 1; }
.send-suggest { max-height: 260px; overflow-y: auto; }