aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/ark.js59
-rw-r--r--src/i18n.js9
-rw-r--r--src/style.css22
3 files changed, 49 insertions, 41 deletions
diff --git a/src/features/ark.js b/src/features/ark.js
index fe1fe90..fd4b175 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -2104,37 +2104,36 @@ export function arkFeature(ctx) {
// header row and no horizontal budget at all. `.coin` brings the
// on-chain coin-control look — hairlines between rows, big accent
// checkboxes — so the two coin pages read as siblings.
- h('div', { class: 'col', style: 'margin-top:4px' },
- h('label', { class: 'coin' },
- tick(sel.size === ids.size, (e) => {
- ui.arkCoinsSel = e.target.checked ? new Set(ids) : new Set();
- render();
- }, t('arkCoinsSelectAll')),
- h('span', { class: 'small faint' }, t('arkCoinsSelectAll'))),
- ...spend.map((v) => {
- const f = feeNowOf(v);
- const xf = exitFeeOf(v);
- return h('label', { class: 'coin' },
- tick(sel.has(v.id), (e) => {
- e.target.checked ? sel.add(v.id) : sel.delete(v.id);
+ // One coin per row in a headed table — the labels live in the header
+ // once instead of on every row as chips. It scrolls sideways on a
+ // narrow screen (the wrapper, not the page) rather than wrapping.
+ // A tap anywhere on a row toggles it.
+ h('div', { class: 'coin-scroll' },
+ h('table', { class: 'coin-table' },
+ h('thead', {}, h('tr', {},
+ h('th', {}, tick(sel.size === ids.size, (e) => {
+ ui.arkCoinsSel = e.target.checked ? new Set(ids) : new Set();
render();
- }),
- h('div', { class: 'col grow', style: 'gap:3px;min-width:0' },
- h('span', {}, fmtAmount(v.amountSat),
- h('span', { class: 'small faint' }, ' ' + unitLabel())),
- // little labeled chips instead of a dot-separated line: each
- // fact wears its own pill, wrapping freely on narrow screens —
- // the renewal price rides here too, a chip like its siblings
- h('div', { class: 'coin-chips' },
- h('span', {
- class: 'coin-chip' + (v.expiryHeight && tip && v.expiryHeight - tip <= 0 ? ' warn' : ''),
- title: t('arkCoinsChipExpTitle'),
- }, '⏳ ' + t('arkCoinsChipExp', { d: expiresOf(v) })),
- h('span', { class: 'coin-chip', title: t('arkCoinsChipExitTitle') },
- t('arkCoinsChipExit', { fee: xf == null ? '—' : fmtAmount(xf) + ' ' + unitLabel() })),
- h('span', { class: 'coin-chip', title: t('arkCoinsChipRenewTitle') },
- t('arkCoinsChipRenew', { fee: renewChip(f) })))));
- })),
+ }, t('arkCoinsSelectAll'))),
+ h('th', { class: 'num' }, t('arkCoinsColAmount')),
+ h('th', { class: 'num', title: t('arkCoinsChipExpTitle') }, t('arkCoinsColExpiry')),
+ h('th', { class: 'num', title: t('arkCoinsChipExitTitle') }, t('arkCoinsColExit')),
+ h('th', { class: 'num', title: t('arkCoinsChipRenewTitle') }, t('arkCoinsColRenew')))),
+ h('tbody', {}, ...spend.map((v) => {
+ const f = feeNowOf(v);
+ const xf = exitFeeOf(v);
+ const due = !!(v.expiryHeight && tip && v.expiryHeight - tip <= 0);
+ const toggle = () => { sel.has(v.id) ? sel.delete(v.id) : sel.add(v.id); render(); };
+ return h('tr', {
+ class: 'clickable',
+ onClick: (e) => { if (e.target.tagName !== 'INPUT') toggle(); },
+ },
+ h('td', {}, tick(sel.has(v.id), toggle)),
+ h('td', { class: 'num' }, fmtAmount(v.amountSat), h('span', { class: 'small faint' }, ' ' + unitLabel())),
+ h('td', { class: 'num' + (due ? ' warn' : '') }, expiresOf(v)),
+ h('td', { class: 'num' }, xf == null ? '—' : fmtAmount(xf), xf == null ? null : h('span', { class: 'small faint' }, ' ' + unitLabel())),
+ h('td', { class: 'num' }, renewChip(f)));
+ })))),
h('p', { class: 'small faint', style: 'margin:4px 0 0' }, t('arkCoinsExpiryNote'))),
h('div', { class: 'card col', style: 'gap:8px' },
h('h4', { style: 'margin:0' }, t('arkCoinsExitTitle')),
diff --git a/src/i18n.js b/src/i18n.js
index bfd6ff8..2d4e8c6 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -307,11 +307,12 @@ const STR = {
arkCoinsTitle: 'Spending coins',
arkCoinsIntro: 'Your Spending balance lives in {n} coins totalling {total}. Each can be pulled on-chain without the server’s help — at a cost that grows with the coin’s history.',
arkCoinsIntroOne: 'Your Spending balance lives in a single coin of {total}. It can be pulled on-chain without the server’s help — at a cost that grows with the coin’s history.',
- arkCoinsChipExp: 'expiry {d}',
- arkCoinsChipExit: 'exit cost {fee}',
- arkCoinsChipRenew: 'renewal cost {fee}',
+ arkCoinsColAmount: 'Amount',
+ arkCoinsColExpiry: 'Expiry',
+ arkCoinsColExit: 'Exit cost',
+ arkCoinsColRenew: 'Renewal',
arkCoinsChipRenewTitle: 'This coin’s share of the renewal fee — the fee is priced on all the coins together and rounded up once, so tiny shares add almost nothing',
- arkCoinsChipRenewTiny: 'under 1 sat',
+ arkCoinsChipRenewTiny: '< 1 sat',
arkCoinsChipExpTitle: 'Time until this coin needs renewal',
arkCoinsChipExitTitle: 'What pulling this coin on-chain by itself would cost',
arkFeeChain: 'On-chain fee',
diff --git a/src/style.css b/src/style.css
index bcec4a5..dd90ec1 100644
--- a/src/style.css
+++ b/src/style.css
@@ -284,14 +284,22 @@ textarea { resize: vertical; min-height: 84px; font-family: var(--mono); font-si
.coin { display: flex; align-items: center; gap: 11px; padding: 11px 2px; border-top: 1px solid var(--line); }
.coin:first-child { border-top: 0; }
.coin input[type=checkbox] { width: 18px; height: 18px; accent-color: var(--accent); }
-/* per-coin fact pills: each datum wears its own little labeled chip */
-.coin-chips { display: flex; gap: 6px; flex-wrap: wrap; }
-.coin-chip {
- display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px;
- border: 1px solid var(--line); border-radius: 999px;
- font-size: 11.5px; color: var(--muted); white-space: nowrap;
+/* the Spending coins table: labels once in the header, numbers right-
+ aligned in tabular figures; on a narrow screen the WRAPPER scrolls
+ sideways so the page itself never does */
+.coin-scroll { overflow-x: auto; margin: 4px -2px 0; -webkit-overflow-scrolling: touch; }
+.coin-table { border-collapse: collapse; width: 100%; font-size: 13.5px; white-space: nowrap; }
+.coin-table th {
+ text-align: left; font-weight: 600; font-size: 12px; color: var(--muted);
+ padding: 4px 5px 8px; border-bottom: 1px solid var(--line-strong);
}
-.coin-chip.warn { color: var(--accent-ink); border-color: var(--accent); }
+.coin-table td { padding: 10px 5px; border-top: 1px solid var(--line); vertical-align: middle; }
+.coin-table tbody tr:first-child td { border-top: 0; }
+.coin-table th:first-child, .coin-table td:first-child { width: 22px; padding-left: 2px; padding-right: 2px; }
+.coin-table .num { text-align: right; font-variant-numeric: tabular-nums; }
+.coin-table td.warn { color: var(--accent-ink); font-weight: 600; }
+.coin-table tr.clickable { cursor: pointer; }
+.coin-table input[type=checkbox] { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; display: block; }
/* ---- review / summary ---- */
.summary { background: var(--surface2); border: 1px solid var(--line); border-radius: 12px; padding: 14px; }