diff options
| author | Adam Soltys <[email protected]> | 2026-09-06 21:08:09 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-06 21:08:09 -0700 |
| commit | 3d9faca20b7375e949546ac6b701307d4f7795ac (patch) | |
| tree | a915487eb1b3413e9a5ed73d089da6259f663394 | |
| parent | ad496c0fb53706f221241dd48440c68d3333937d (diff) | |
| download | coinosv3-3d9faca20b7375e949546ac6b701307d4f7795ac.tar.gz coinosv3-3d9faca20b7375e949546ac6b701307d4f7795ac.zip | |
Auto-pay: no "Paying" screen — the badge animates spinner → green check into Paid.
The armed auto-pay flow flashed a "Paying" title/spinner card that then swapped title, badge and footer for the "Paid" card — too much text change and layout shift for something that completes in a moment. Now the in-flight and success states are ONE structurally identical card (autopayCard): recipient name and amount sit in their final positions the whole time, and the step to Paid is just a class flip. The badge starts as a spinner ring (the ⚡ hidden) and, on success, settles into the green success check with a pop while the ⚡ fades in; the "tap to proceed" footer occupies reserved height so nothing jumps. No "Paying" wording at all — the badge carries the state. Respects prefers-reduced-motion. Manual (non-auto) pays keep their normal Paid screen.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
| -rw-r--r-- | src/features/ark.js | 30 | ||||
| -rw-r--r-- | src/style.css | 17 |
2 files changed, 36 insertions, 11 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index fa08289..6e68200 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -960,7 +960,7 @@ export function arkFeature(ctx) { }; const settle = (a) => { if (a.step === 'done') { - ui.arkLnPaid = { amountSat: a.amountSat, meta: p.meta }; + ui.arkLnPaid = { amountSat: a.amountSat, meta: p.meta, autopaid: !!(p._autopaid || p.autopaying) }; if (p.meta && p.meta.pk) noteZap('inv:' + p.invoice, p.meta.pk); // the user opted this recipient into auto-pay: remember the chosen // budget so future fixed charges from them clear without a tap @@ -1016,10 +1016,26 @@ export function arkFeature(ctx) { } const amountFill = (sat) => (ctx.getUnit() === 'sats' ? String(sat) : (sat / 1e8).toFixed(8)); + // Auto-pay progress + success as ONE structurally identical card, so the + // paying → paid step is a class flip (spinner ring settles into the green + // check, amount and recipient never move) instead of a text/layout swap. No + // "Paying" wording at all — the badge animation carries the state. + const autopayCard = (paid, meta, amountSat) => + h('div', { + class: 'card col autopay-card' + (paid ? ' paid' : ''), + style: 'align-items:center;text-align:center;gap:14px;padding:48px 20px' + (paid ? ';cursor:pointer' : ''), + onClick: paid ? () => { ui.arkLnPaid = null; ui.send = blankSend(); render(); } : undefined, + }, + h('div', { class: 'check-badge autopay-badge' }, '⚡'), + meta && meta.name ? h('div', { class: 'small muted' }, meta.name) : null, + h('div', { class: 'amount-neg', style: 'font-size:18px' }, '-' + fmtAmount(amountSat) + ' ' + unitLabel()), + h('div', { class: 'small muted autopay-foot' }, paid ? t('tapToProceed') : '')); + function arkLnPayView() { const u = ' ' + unitLabel(); if (ui.arkLnPaid) { const zap = ui.arkLnPaid.meta; + if (ui.arkLnPaid.autopaid) return autopayCard(true, zap, ui.arkLnPaid.amountSat); return h('div', { class: 'card col', style: 'align-items:center;text-align:center;gap:14px;cursor:pointer;padding:48px 20px', @@ -1034,16 +1050,8 @@ export function arkFeature(ctx) { const p = ui.arkLnPay; if (!p) return null; const zap = p.meta; - // Auto-pay: show only a paying screen (matching the paid screen's layout, so - // it morphs into it) instead of flashing the review card before it pays. - if (p.autopaying) { - return h('div', { class: 'card col', style: 'align-items:center;text-align:center;gap:14px;padding:48px 20px' }, - h('div', { class: 'check-badge' }, '⚡'), - h('h2', { style: 'margin:0' }, t('arkLnPaying')), - zap && zap.name ? h('div', { class: 'small muted' }, zap.name) : null, - h('div', { class: 'amount-neg', style: 'font-size:18px' }, '-' + fmtAmount(p.amountSat) + u), - h('span', { class: 'spinner' })); - } + // Auto-pay in flight: the progress half of the same card (see autopayCard). + if (p.autopaying) return autopayCard(false, p.meta, p.amountSat); const row = (label, sats, bold) => h('div', { class: 'row between' + (bold ? '' : ''), style: bold ? 'font-weight:600' : '' }, h('span', { class: bold ? '' : 'small muted' }, label), h('span', {}, fmtAmount(sats) + u)); const total = (p.amountSat || 0) + (p.feeSat || 0); diff --git a/src/style.css b/src/style.css index 6779579..95c0935 100644 --- a/src/style.css +++ b/src/style.css @@ -677,6 +677,23 @@ hr.divider { margin: 16px 0; } @keyframes check-ring { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 45%, transparent); } to { box-shadow: 0 0 0 30px transparent; } } .anim-page .check-badge { animation: check-pop .55s cubic-bezier(.34, 1.56, .64, 1) both, check-ring 1.1s ease-out .25s both; } +/* Auto-pay card: the badge is a spinner ring while paying, then flips to the + green success check with a pop — one node, so the transition is smooth and + nothing around it shifts. */ +.autopay-badge { position: relative; transition: background .3s ease, color .3s ease; } +.autopay-card:not(.paid) .autopay-badge { background: var(--accent-soft); color: transparent; animation: none; } +.autopay-card:not(.paid) .autopay-badge::after { + content: ''; position: absolute; inset: -7px; border-radius: 50%; + border: 2px solid var(--line-strong); border-top-color: var(--accent); + animation: spin .7s linear infinite; +} +.autopay-card.paid .autopay-badge { animation: check-pop .5s cubic-bezier(.34, 1.56, .64, 1) both, check-ring 1.1s ease-out .2s both; } +.autopay-foot { min-height: 1.15em; } +@media (prefers-reduced-motion: reduce) { + .autopay-card:not(.paid) .autopay-badge::after { animation: none; } + .autopay-card.paid .autopay-badge { animation: none; } +} + /* micro-interactions */ button, .btn { transition: filter .12s, background .15s, border-color .15s, box-shadow .15s, transform .1s; } .btn-primary:hover { filter: brightness(1.15); } |
