diff --git a/website_next/index.html b/website_next/index.html index 8c8aef6c2..48fd4a920 100644 --- a/website_next/index.html +++ b/website_next/index.html @@ -116,6 +116,7 @@ + diff --git a/website_next/wallets/add/index.js b/website_next/wallets/add/index.js index 52611fbab..f45bfd82d 100644 --- a/website_next/wallets/add/index.js +++ b/website_next/wallets/add/index.js @@ -20,8 +20,8 @@ function createSourceInput() { const input = document.createElement("input"); input.name = "source"; - input.type = redaction.isHidden() ? "password" : "text"; - input.setAttribute("data-wallets-private-input", ""); + input.type = "text"; + redaction.setInput(input); input.autocomplete = "off"; input.placeholder = "xpub or descriptor..."; input.required = true; @@ -38,10 +38,10 @@ export function createAddForm(options) { const title = document.createElement("h2"); const name = document.createElement("input"); const source = createSourceInput(); - const actions = document.createElement("div"); + const actions = document.createElement("footer"); const cancel = document.createElement("button"); const submit = document.createElement("button"); - const status = document.createElement("p"); + const status = document.createElement("output"); const fields = [ createField("name", name), createField("xpub or descriptor", source), @@ -55,8 +55,8 @@ export function createAddForm(options) { cancel.type = "button"; cancel.append("Cancel"); submit.type = "submit"; + submit.classList.add("primary"); submit.append("Add"); - status.setAttribute("role", "status"); actions.append(cancel, submit); form.append( title, diff --git a/website_next/wallets/add/style.css b/website_next/wallets/add/style.css index 4afa740fd..41a8547d9 100644 --- a/website_next/wallets/add/style.css +++ b/website_next/wallets/add/style.css @@ -4,17 +4,11 @@ main.wallets { display: grid; gap: 0.75rem; - > div { + > footer { display: flex; gap: 0.5rem; justify-content: end; } - - button[type="submit"] { - border-color: var(--orange); - color: var(--black); - background: var(--orange); - } } } } diff --git a/website_next/wallets/amount/index.js b/website_next/wallets/amount/index.js index 9193b9c76..f789b5aa0 100644 --- a/website_next/wallets/amount/index.js +++ b/website_next/wallets/amount/index.js @@ -3,10 +3,19 @@ import { redaction } from "../redaction/index.js"; const SATS_PER_BTC = 100_000_000; const FRACTION_DIGITS = 8; const FIXED_PRIVATE_TEXT = "*****"; +const amounts = /** @type {BtcAmountRecord[]} */ ([]); /** * @typedef {Object} BtcAmountOptions * @property {boolean} [signed] + * + * @typedef {Object} BtcAmount + * @property {number} sats + * @property {boolean} signed + * + * @typedef {Object} BtcAmountRecord + * @property {HTMLElement} element + * @property {BtcAmount} amount */ /** @@ -126,30 +135,21 @@ function getBtcParts(sats, options = {}) { return parts; } -/** - * @param {number} sats - * @param {BtcAmountOptions} [options] - */ -export function formatBtc(sats, options = {}) { - return getBtcParts(sats, options).map((part) => part.text).join(""); -} - /** * @param {HTMLElement} element - * @param {number} sats - * @param {BtcAmountOptions} [options] + * @param {BtcAmount} amount */ -function renderBtcAmount(element, sats, options = {}) { +function renderBtcAmount(element, amount) { if (redaction.isHidden()) { element.textContent = FIXED_PRIVATE_TEXT; return; } - element.replaceChildren(...getBtcParts(sats, options).map((part) => { + element.replaceChildren(...getBtcParts(amount.sats, amount).map((part) => { const span = document.createElement("span"); if (part.muted) { - span.setAttribute("data-wallets-btc-muted", ""); + span.classList.add("muted"); } span.append(part.text); @@ -165,28 +165,26 @@ function renderBtcAmount(element, sats, options = {}) { */ export function createBtcAmount(tag, sats, options = {}) { const element = document.createElement(tag); + const amount = { + sats, + signed: options.signed === true, + }; - element.setAttribute("data-wallets-btc-amount", String(sats)); - element.setAttribute( - "data-wallets-btc-signed", - options.signed ? "true" : "false", - ); - renderBtcAmount(element, sats, options); + element.classList.add("wallets__amount"); + amounts.push({ element, amount }); + renderBtcAmount(element, amount); return element; } -/** - * @param {HTMLElement} root - */ -export function syncBtcAmounts(root) { - const amounts = root.querySelectorAll("[data-wallets-btc-amount]"); +export function syncBtcAmounts() { + for (let index = amounts.length - 1; index >= 0; index -= 1) { + const { element, amount } = amounts[index]; - for (const amount of amounts) { - const element = /** @type {HTMLElement} */ (amount); - const sats = Number(element.getAttribute("data-wallets-btc-amount")); - const signed = element.getAttribute("data-wallets-btc-signed") === "true"; - - renderBtcAmount(element, sats, { signed }); + if (!element.isConnected) { + amounts.splice(index, 1); + } else { + renderBtcAmount(element, amount); + } } } diff --git a/website_next/wallets/amount/style.css b/website_next/wallets/amount/style.css new file mode 100644 index 000000000..9add21f4f --- /dev/null +++ b/website_next/wallets/amount/style.css @@ -0,0 +1,7 @@ +main.wallets { + .wallets__amount { + .muted { + color: color-mix(in oklch, currentColor 45%, transparent); + } + } +} diff --git a/website_next/wallets/empty/index.js b/website_next/wallets/empty/index.js index a6d0ed649..8552bb0a9 100644 --- a/website_next/wallets/empty/index.js +++ b/website_next/wallets/empty/index.js @@ -15,6 +15,7 @@ export function createEmpty(options) { text.append("No wallet imported yet"); button.type = "button"; + button.classList.add("primary"); button.append("Add wallet"); button.addEventListener("click", options.onAdd); empty.append(text, button); diff --git a/website_next/wallets/empty/style.css b/website_next/wallets/empty/style.css index ab1fd0b7b..5ff46b79a 100644 --- a/website_next/wallets/empty/style.css +++ b/website_next/wallets/empty/style.css @@ -3,17 +3,11 @@ main.wallets { display: grid; gap: 1rem; place-content: center; - min-height: 16rem; + min-height: calc(100dvh - 2 * var(--offset)); text-align: center; p { margin: 0; } - - > button { - border-color: var(--orange); - color: var(--black); - background: var(--orange); - } } } diff --git a/website_next/wallets/index.js b/website_next/wallets/index.js index dae28b35a..7687530de 100644 --- a/website_next/wallets/index.js +++ b/website_next/wallets/index.js @@ -1,5 +1,4 @@ import { brk } from "../utils/client.js"; -import { createGroupedAddress } from "./wallet/address/index.js"; import { setStatus, withBusy, @@ -81,8 +80,8 @@ export function createWalletsPage() { } privacyButton.addEventListener("click", () => { - redaction.toggle(main, privacyButton, createGroupedAddress); - syncBtcAmounts(main); + redaction.toggle(privacyButton); + syncBtcAmounts(); }); lockButton.addEventListener("click", () => { @@ -209,8 +208,6 @@ export function createWalletsPage() { const current = vault.current(); const empty = !needsSetup && !locked && !current; - main.toggleAttribute("data-wallets-page-locked", locked || needsSetup); - main.toggleAttribute("data-wallets-page-empty", empty); header.hidden = locked || needsSetup || empty; selectorElement.hidden = locked || needsSetup || empty; lockButton.hidden = locked || needsSetup || !vault.hasPassword; diff --git a/website_next/wallets/layout/index.js b/website_next/wallets/layout/index.js index 350ee0144..a7d467bfb 100644 --- a/website_next/wallets/layout/index.js +++ b/website_next/wallets/layout/index.js @@ -19,19 +19,22 @@ import { createElement } from "../dom.js"; export function createLayout() { const main = createElement("main", "wallets"); const header = document.createElement("header"); - const actions = document.createElement("div"); + const actions = document.createElement("menu"); const addButton = document.createElement("button"); const privacyButton = document.createElement("button"); const lockButton = document.createElement("button"); const selector = createElement("section", "wallets__selector"); - const walletList = document.createElement("div"); - const content = document.createElement("section"); + const walletList = document.createElement("nav"); + const content = document.createElement("article"); const addDialog = createElement("dialog", "wallets__dialog"); addButton.type = "button"; + addButton.classList.add("primary"); addButton.append("Add watch-only wallet"); privacyButton.type = "button"; + privacyButton.classList.add("primary"); lockButton.type = "button"; + lockButton.classList.add("primary"); lockButton.append("Lock"); content.setAttribute("aria-live", "polite"); walletList.setAttribute("tabindex", "0"); diff --git a/website_next/wallets/layout/style.css b/website_next/wallets/layout/style.css index 0dd63ff3f..d7427d444 100644 --- a/website_next/wallets/layout/style.css +++ b/website_next/wallets/layout/style.css @@ -9,25 +9,22 @@ main.wallets { justify-content: start; } - > div { + > menu { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: end; + margin: 0; + padding: 0; + list-style: none; @media (max-width: 34rem) { justify-content: start; } - - > button { - border-color: var(--orange); - color: var(--black); - background: var(--orange); - } } } - > section[aria-live] { + > article { display: grid; gap: 1.5rem; } diff --git a/website_next/wallets/lock/index.js b/website_next/wallets/lock/index.js index 5a4311101..04c28a193 100644 --- a/website_next/wallets/lock/index.js +++ b/website_next/wallets/lock/index.js @@ -21,16 +21,16 @@ function bindResetHold(button, onReset) { clearTimeout(timer); timer = undefined; - button.removeAttribute("data-wallets-holding"); + button.classList.remove("holding"); } function start() { if (timer !== undefined) return; - button.setAttribute("data-wallets-holding", ""); + button.classList.add("holding"); timer = window.setTimeout(() => { timer = undefined; - button.removeAttribute("data-wallets-holding"); + button.classList.remove("holding"); onReset(); }, RESET_HOLD_MS); } @@ -68,7 +68,7 @@ export function createLock(options) { const password = document.createElement("input"); const button = document.createElement("button"); const reset = document.createElement("button"); - const status = document.createElement("p"); + const status = document.createElement("output"); title.append("Unlock vault"); password.name = "password"; @@ -78,10 +78,10 @@ export function createLock(options) { password.placeholder = "Password"; password.required = true; button.type = "submit"; + button.classList.add("primary"); button.append("Unlock"); reset.type = "button"; reset.append("Reset vault"); - status.setAttribute("role", "status"); form.append(password, button); form.addEventListener("submit", (event) => { event.preventDefault(); diff --git a/website_next/wallets/lock/style.css b/website_next/wallets/lock/style.css index 84b61ac2e..51651976f 100644 --- a/website_next/wallets/lock/style.css +++ b/website_next/wallets/lock/style.css @@ -3,7 +3,9 @@ main.wallets { display: grid; gap: 1rem; place-content: center; - min-height: 16rem; + width: min(100%, 28rem); + min-height: calc(100dvh - 2 * var(--offset)); + margin-inline: auto; text-align: center; > h1 { @@ -15,20 +17,13 @@ main.wallets { > form { display: grid; - grid-template-columns: minmax(12rem, 18rem) auto; + grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem; align-items: end; - justify-content: center; @media (max-width: 34rem) { grid-template-columns: 1fr; } - - > button { - border-color: var(--orange); - color: var(--black); - background: var(--orange); - } } > button { @@ -49,7 +44,7 @@ main.wallets { transform-origin: left; } - &[data-wallets-holding]::before { + &.holding::before { transform: scaleX(1); transition: transform 2s linear; } diff --git a/website_next/wallets/lookup/hash.js b/website_next/wallets/lookup/hash.js index df3f238f1..8cb007913 100644 --- a/website_next/wallets/lookup/hash.js +++ b/website_next/wallets/lookup/hash.js @@ -83,13 +83,6 @@ function rapidHashV3(bytes) { let a = readU64(bytes, length - 16) ^ BigInt(length); let b = readU64(bytes, length - 8); - if (length > 32) { - seed = rapidMix( - readU64(bytes, 16) ^ DEFAULT_SECRETS[2], - readU64(bytes, 24) ^ seed, - ); - } - a ^= DEFAULT_SECRETS[1]; b ^= seed; diff --git a/website_next/wallets/redaction/index.js b/website_next/wallets/redaction/index.js index 5b38b3378..a340bd994 100644 --- a/website_next/wallets/redaction/index.js +++ b/website_next/wallets/redaction/index.js @@ -1,6 +1,15 @@ const FIXED_PRIVATE_TEXT = "*****"; let hidden = false; +const effects = /** @type {RedactionEffect[]} */ ([]); + +/** + * @typedef {"exact" | "fixed"} RedactionMode + * + * @typedef {Object} RedactionEffect + * @property {HTMLElement} element + * @property {() => void} sync + */ function isHidden() { return hidden; @@ -17,23 +26,30 @@ function createText(value) { /** * @param {string} value - * @param {string | null} mode + * @param {RedactionMode} mode */ function mask(value, mode) { return mode === "fixed" ? FIXED_PRIVATE_TEXT : createText(value); } +/** + * @param {HTMLElement} element + * @param {() => void} sync + */ +function addEffect(element, sync) { + effects.push({ element, sync }); + sync(); +} + /** * @param {HTMLElement} element * @param {string} value - * @param {"exact" | "fixed"} [mode] + * @param {RedactionMode} [mode] */ function setValue(element, value, mode = "exact") { - element.setAttribute("data-wallets-private-value", value); - element.setAttribute("data-wallets-private-mode", mode); - element.textContent = hidden - ? mask(value, mode) - : value; + addEffect(element, () => { + element.textContent = hidden ? mask(value, mode) : value; + }); } /** @@ -41,15 +57,36 @@ function setValue(element, value, mode = "exact") { * @param {string} value */ function setTitle(element, value) { - element.setAttribute("data-wallets-private-title", value); - element.title = hidden ? createText(value) : value; + addEffect(element, () => { + element.title = hidden ? createText(value) : value; + }); +} + +/** + * @param {HTMLElement} element + * @param {string} value + * @param {(text: string) => void} render + */ +function setAddress(element, value, render) { + addEffect(element, () => { + render(hidden ? createText(value) : value); + }); +} + +/** + * @param {HTMLInputElement} input + */ +function setInput(input) { + addEffect(input, () => { + input.type = hidden ? "password" : "text"; + }); } /** * @template {keyof HTMLElementTagNameMap} Tag * @param {Tag} tag * @param {string} value - * @param {"exact" | "fixed"} [mode] + * @param {RedactionMode} [mode] */ function createValue(tag, value, mode = "exact") { const element = document.createElement(tag); @@ -59,44 +96,14 @@ function createValue(tag, value, mode = "exact") { return element; } -/** - * @param {HTMLElement} root - * @param {(text: string) => HTMLElement} createAddress - */ -function sync(root, createAddress) { - const values = root.querySelectorAll("[data-wallets-private-value]"); - const titles = root.querySelectorAll("[data-wallets-private-title]"); - const addresses = root.querySelectorAll("[data-wallets-private-address]"); - const inputs = root.querySelectorAll("[data-wallets-private-input]"); +function sync() { + for (let index = effects.length - 1; index >= 0; index -= 1) { + const effect = effects[index]; - for (const value of values) { - const text = value.getAttribute("data-wallets-private-value") ?? ""; - const mode = value.getAttribute("data-wallets-private-mode"); - - value.textContent = hidden - ? mask(text, mode) - : text; - } - - for (const element of titles) { - const title = /** @type {HTMLElement} */ (element); - const text = title.getAttribute("data-wallets-private-title") ?? ""; - - title.title = hidden - ? createText(text) - : text; - } - - for (const address of addresses) { - const text = address.getAttribute("data-wallets-private-address") ?? ""; - const next = hidden ? createText(text) : text; - - address.replaceChildren(...createAddress(next).childNodes); - } - - for (const input of inputs) { - if (input instanceof HTMLInputElement) { - input.type = hidden ? "password" : "text"; + if (!effect.element.isConnected) { + effects.splice(index, 1); + } else { + effect.sync(); } } } @@ -110,13 +117,11 @@ function syncButton(button) { } /** - * @param {HTMLElement} root * @param {HTMLButtonElement} button - * @param {(text: string) => HTMLElement} createAddress */ -function toggle(root, button, createAddress) { +function toggle(button) { hidden = !hidden; - sync(root, createAddress); + sync(); syncButton(button); } @@ -125,6 +130,8 @@ export const redaction = /** @type {const} */ ({ createText, setValue, setTitle, + setAddress, + setInput, createValue, syncButton, toggle, diff --git a/website_next/wallets/selector/index.js b/website_next/wallets/selector/index.js index 9460f4a20..5d847bf43 100644 --- a/website_next/wallets/selector/index.js +++ b/website_next/wallets/selector/index.js @@ -8,14 +8,21 @@ * @typedef {Object} WalletSelectorOptions * @property {() => string} getSelectedId * @property {(walletId: string) => void} onSelect + * + * @typedef {Object} WalletSelectorButton + * @property {HTMLButtonElement} button + * @property {string} id */ /** * @param {HTMLElement} walletList * @param {StoredWallet[]} wallets * @param {WalletSelectorOptions} options + * @returns {WalletSelectorButton[]} */ function renderButtons(walletList, wallets, options) { + const buttons = /** @type {WalletSelectorButton[]} */ ([]); + walletList.replaceChildren(); for (const wallet of wallets) { @@ -24,13 +31,15 @@ function renderButtons(walletList, wallets, options) { button.type = "button"; button.setAttribute("aria-pressed", selected ? "true" : "false"); - button.setAttribute("data-wallet-id", wallet.id); button.append(wallet.name); button.addEventListener("click", () => { options.onSelect(wallet.id); }); + buttons.push({ button, id: wallet.id }); walletList.append(button); } + + return buttons; } /** @@ -38,29 +47,29 @@ function renderButtons(walletList, wallets, options) { * @param {WalletSelectorOptions} options */ export function createSelector(walletList, options) { - function selectSnappedWallet() { - const buttons = [...walletList.querySelectorAll("button")]; + /** @type {WalletSelectorButton[]} */ + let buttons = []; + function selectSnappedWallet() { if (buttons.length === 0) return; const listRect = walletList.getBoundingClientRect(); const listCenter = listRect.left + listRect.width / 2; - const closest = buttons.reduce((best, button) => { - const rect = button.getBoundingClientRect(); + const closest = buttons.reduce((best, item) => { + const rect = item.button.getBoundingClientRect(); const center = rect.left + rect.width / 2; const distance = Math.abs(center - listCenter); return distance < best.distance - ? { button, distance } + ? { item, distance } : best; }, { - button: buttons[0], + item: buttons[0], distance: Number.POSITIVE_INFINITY, }); - const id = closest.button.getAttribute("data-wallet-id"); - if (id && id !== options.getSelectedId()) { - options.onSelect(id); + if (closest.item.id !== options.getSelectedId()) { + options.onSelect(closest.item.id); } } @@ -90,12 +99,13 @@ export function createSelector(walletList, options) { return { clear() { walletList.replaceChildren(); + buttons = []; }, /** * @param {StoredWallet[]} wallets */ render(wallets) { - renderButtons(walletList, wallets, options); + buttons = renderButtons(walletList, wallets, options); }, }; } diff --git a/website_next/wallets/selector/style.css b/website_next/wallets/selector/style.css index 88505fdaa..28fcf58ab 100644 --- a/website_next/wallets/selector/style.css +++ b/website_next/wallets/selector/style.css @@ -2,7 +2,7 @@ main.wallets { .wallets__selector { min-width: 0; - > div { + > nav { display: flex; gap: 1rem; min-width: 0; diff --git a/website_next/wallets/setup/index.js b/website_next/wallets/setup/index.js index 73e7db466..17b6f4502 100644 --- a/website_next/wallets/setup/index.js +++ b/website_next/wallets/setup/index.js @@ -22,11 +22,11 @@ function createDescriptionText(text) { export function createSetup(options) { const section = createElement("section", "wallets__setup"); const title = document.createElement("h1"); - const description = document.createElement("div"); + const description = document.createElement("article"); const form = document.createElement("form"); const password = document.createElement("input"); const button = document.createElement("button"); - const status = document.createElement("p"); + const status = document.createElement("output"); title.append("Wallets"); description.append( @@ -49,8 +49,8 @@ export function createSetup(options) { password.placeholder = "Set password"; password.required = true; button.type = "submit"; + button.classList.add("primary"); button.append("Continue"); - status.setAttribute("role", "status"); form.append(password, button); form.addEventListener("submit", (event) => { event.preventDefault(); diff --git a/website_next/wallets/setup/style.css b/website_next/wallets/setup/style.css index 2460f77e2..2dcd0bd0c 100644 --- a/website_next/wallets/setup/style.css +++ b/website_next/wallets/setup/style.css @@ -3,8 +3,8 @@ main.wallets { display: grid; gap: 1rem; place-content: center; - max-width: 36rem; - min-height: 16rem; + width: min(100%, 36rem); + min-height: calc(100dvh - 2 * var(--offset)); margin-inline: auto; h1 { @@ -27,7 +27,7 @@ main.wallets { margin: 0; } - > div { + > article { display: grid; gap: 0.75rem; color: var(--gray); @@ -41,18 +41,11 @@ main.wallets { grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem; align-items: end; - justify-content: center; width: 100%; @media (max-width: 34rem) { grid-template-columns: 1fr; } - - > button { - border-color: var(--orange); - color: var(--black); - background: var(--orange); - } } } } diff --git a/website_next/wallets/style.css b/website_next/wallets/style.css index 2220c9852..1d7d07128 100644 --- a/website_next/wallets/style.css +++ b/website_next/wallets/style.css @@ -11,12 +11,8 @@ main.wallets { padding: var(--offset) var(--page-x); scroll-padding-top: var(--offset); - &:is([data-wallets-page-empty], [data-wallets-page-locked]) { - min-height: 100dvh; - align-content: center; - } - - [role="status"] { + output { + display: block; min-height: var(--line-height-sm); margin: 0; color: var(--gray); @@ -24,10 +20,6 @@ main.wallets { line-height: var(--line-height-sm); } - [data-wallets-btc-muted] { - color: color-mix(in oklch, currentColor 45%, transparent); - } - :is(input, select, button) { min-width: 0; height: var(--control-height); @@ -49,6 +41,12 @@ main.wallets { outline-offset: 2px; } + button.primary { + border-color: var(--orange); + color: var(--black); + background: var(--orange); + } + input::placeholder { color: color-mix(in oklch, var(--gray) 70%, transparent); } diff --git a/website_next/wallets/wallet/actions/style.css b/website_next/wallets/wallet/actions/style.css index b1a36da48..132b126a5 100644 --- a/website_next/wallets/wallet/actions/style.css +++ b/website_next/wallets/wallet/actions/style.css @@ -9,18 +9,11 @@ main.wallets { @media (max-width: 34rem) { justify-content: start; } - - > button { - border-color: var(--orange); - color: var(--black); - background: var(--orange); - - &:disabled { - border-color: color-mix(in oklch, var(--gray) 35%, transparent); - color: var(--gray); - background: transparent; - cursor: default; - } + > button:disabled { + border-color: color-mix(in oklch, var(--gray) 35%, transparent); + color: var(--gray); + background: transparent; + cursor: default; } } } diff --git a/website_next/wallets/wallet/address/index.js b/website_next/wallets/wallet/address/index.js index 375bfc52a..c447065d1 100644 --- a/website_next/wallets/wallet/address/index.js +++ b/website_next/wallets/wallet/address/index.js @@ -17,15 +17,14 @@ export function createGroupedAddress(text) { const group = document.createElement("span"); for (const character of groups[groupIndex]) { - const span = document.createElement("span"); + if (Number.isNaN(Number(character))) { + group.append(character); + } else { + const number = document.createElement("var"); - span.setAttribute( - "data-wallets-address-character", - Number.isNaN(Number(character)) ? "letter" : "number", - ); - - span.append(character); - group.append(span); + number.append(character); + group.append(number); + } } element.append(group); @@ -41,12 +40,11 @@ export function createGroupedAddress(text) { * @param {string} address */ function createPrivateAddress(address) { - const hidden = redaction.createText(address); - const element = redaction.isHidden() - ? createGroupedAddress(hidden) - : createGroupedAddress(address); + const element = createGroupedAddress(address); - element.setAttribute("data-wallets-private-address", address); + redaction.setAddress(element, address, (text) => { + element.replaceChildren(...createGroupedAddress(text).childNodes); + }); return element; } @@ -55,10 +53,9 @@ function createPrivateAddress(address) { * @param {WalletAddress} row */ function createAddressBadge(row) { - const badge = document.createElement("span"); + const badge = document.createElement("b"); const label = row.branchLabel?.toLowerCase() ?? "address"; - badge.setAttribute("data-wallets-address-branch", label); badge.append(label, ` #${formatNumber(row.index)}`); return badge; @@ -69,7 +66,7 @@ function createAddressBadge(row) { */ export function createAddressCellContent(row) { const element = createElement("div", "wallets__address-cell"); - const anonSet = document.createElement("span"); + const anonSet = document.createElement("small"); anonSet.append(`anon set: ${formatNumber(row.historyBucketSize)}`); element.append( diff --git a/website_next/wallets/wallet/address/style.css b/website_next/wallets/wallet/address/style.css index fa336fadf..4197d0530 100644 --- a/website_next/wallets/wallet/address/style.css +++ b/website_next/wallets/wallet/address/style.css @@ -3,7 +3,7 @@ main.wallets { display: grid; gap: 0.25rem; - > span:first-child { + > b { display: inline-flex; align-items: center; justify-self: start; @@ -12,10 +12,11 @@ main.wallets { border-radius: 0.25rem; padding: 0 0.25rem; color: color-mix(in oklch, var(--white) 76%, var(--gray)); + font-weight: 400; line-height: 1; } - > span:last-child { + > small { color: var(--gray); font-size: var(--font-size-xs); line-height: var(--line-height-xs); @@ -29,15 +30,13 @@ main.wallets { max-width: 40rem; > span { - white-space: nowrap; - } - - [data-wallets-address-character="letter"] { color: var(--white); - } + white-space: nowrap; - [data-wallets-address-character="number"] { - color: color-mix(in oklch, var(--white) 50%, var(--gray)); + > var { + color: color-mix(in oklch, var(--white) 50%, var(--gray)); + font-style: normal; + } } } } diff --git a/website_next/wallets/wallet/index.js b/website_next/wallets/wallet/index.js index 2901a16f0..d1648da3b 100644 --- a/website_next/wallets/wallet/index.js +++ b/website_next/wallets/wallet/index.js @@ -22,11 +22,10 @@ import { renderTransactions } from "./transactions/index.js"; export function createWalletPanel() { const actions = createElement("section", "wallets__wallet-actions"); const summary = createElement("section", "wallets__summary"); - const status = document.createElement("p"); + const status = document.createElement("output"); const results = createElement("section", "wallets__results"); actions.setAttribute("aria-label", "Wallet actions"); - status.setAttribute("role", "status"); summary.setAttribute("aria-label", "Wallets summary"); results.setAttribute("aria-label", "Wallets results"); diff --git a/website_next/wallets/wallet/receive/index.js b/website_next/wallets/wallet/receive/index.js index 01ff2ea74..fa220f43e 100644 --- a/website_next/wallets/wallet/receive/index.js +++ b/website_next/wallets/wallet/receive/index.js @@ -56,7 +56,7 @@ function createReceiveQr(receiveAddress) { * @param {ReceiveAddress} receiveAddress */ function createReceiveAddress(receiveAddress) { - const element = document.createElement("div"); + const element = document.createElement("p"); element.append(createGroupedAddress(receiveAddress.address)); @@ -73,24 +73,28 @@ async function copyReceiveAddress(receiveAddress, copy) { } /** + * @param {HTMLElement} host * @param {ReceiveAddress} receiveAddress */ -function openReceiveDialog(receiveAddress) { - const main = document.querySelector("main.wallets") ?? document.body; +function openReceiveDialog(host, receiveAddress) { const dialog = createElement( "dialog", "wallets__dialog wallets__receive-dialog", ); - const content = document.createElement("div"); - const actions = document.createElement("div"); + const content = document.createElement("article"); + const actions = document.createElement("footer"); const copy = document.createElement("button"); + const closeForm = document.createElement("form"); const close = document.createElement("button"); copy.type = "button"; + copy.classList.add("primary"); copy.append("Copy"); - close.type = "button"; + closeForm.method = "dialog"; + close.type = "submit"; close.append("Close"); - actions.append(copy, close); + closeForm.append(close); + actions.append(copy, closeForm); content.append( createReceiveTitle(receiveAddress), createReceiveQr(receiveAddress), @@ -98,16 +102,13 @@ function openReceiveDialog(receiveAddress) { actions, ); dialog.append(content); - main.append(dialog); + host.append(dialog); copy.addEventListener("click", () => { void copyReceiveAddress(receiveAddress, copy).catch(() => { copy.textContent = "Copy failed"; }); }); - close.addEventListener("click", () => { - dialog.close(); - }); dialog.addEventListener("close", () => { dialog.remove(); }); @@ -128,10 +129,11 @@ export function renderReceiveButton(element, receiveAddress) { button.type = "button"; button.disabled = !receiveAddress; + button.classList.add("primary"); button.append("Receive"); button.addEventListener("click", () => { if (receiveAddress) { - openReceiveDialog(receiveAddress); + openReceiveDialog(element, receiveAddress); } }); element.append(button); diff --git a/website_next/wallets/wallet/receive/style.css b/website_next/wallets/wallet/receive/style.css index 1beb16b43..eb2ad337d 100644 --- a/website_next/wallets/wallet/receive/style.css +++ b/website_next/wallets/wallet/receive/style.css @@ -2,11 +2,11 @@ main.wallets { .wallets__receive-dialog { width: min(100% - 2rem, 32rem); - > div { + > article { display: grid; gap: 1rem; - h2 { + > h2 { margin: 0; font-size: var(--font-size-lg); font-weight: 400; @@ -22,22 +22,20 @@ main.wallets { image-rendering: pixelated; } - > div:first-of-type { + > p { + margin: 0; color: var(--white); font-size: var(--font-size-sm); line-height: var(--line-height-sm); } - > div:last-of-type { + > footer { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: end; - - > button:first-child { - border-color: var(--orange); - color: var(--black); - background: var(--orange); + > form { + margin: 0; } } } diff --git a/website_next/wallets/wallet/transactions/history.js b/website_next/wallets/wallet/transactions/history.js index 26fb63fe2..6a6c91e5d 100644 --- a/website_next/wallets/wallet/transactions/history.js +++ b/website_next/wallets/wallet/transactions/history.js @@ -18,8 +18,6 @@ const historyByBucketKey = /** * @typedef {Object} AddressHistory * @property {unknown[]} transactions - * @property {number} fetchedAddressCount - * @property {number} bucketSize */ /** @@ -29,17 +27,6 @@ function createBucketKey(addresses) { return [...addresses].sort().join("\n"); } -/** - * @param {WalletAddress} address - */ -function assertHistoryIsReasonable(address) { - if (address.txCount > MAX_SELECTED_ADDRESS_TXS) { - throw new Error( - `History disabled for addresses over ${MAX_SELECTED_ADDRESS_TXS} transactions`, - ); - } -} - /** * @param {AddressHistoryClient} client * @param {readonly string[]} addresses @@ -67,13 +54,12 @@ async function fetchBucketHistory(client, addresses) { * @returns {Promise} */ async function load(client, address) { - assertHistoryIsReasonable(address); - - if (address.historyAddresses.length === 0) { + if ( + address.txCount > MAX_SELECTED_ADDRESS_TXS || + address.historyAddresses.length === 0 + ) { return { transactions: [], - fetchedAddressCount: 0, - bucketSize: address.historyBucketSize, }; } @@ -94,8 +80,6 @@ async function load(client, address) { return { transactions: bucketHistory.get(address.address) ?? [], - fetchedAddressCount: address.historyAddresses.length, - bucketSize: address.historyBucketSize, }; } diff --git a/website_next/wallets/wallet/transactions/index.js b/website_next/wallets/wallet/transactions/index.js index 7a22192f5..3fad84b60 100644 --- a/website_next/wallets/wallet/transactions/index.js +++ b/website_next/wallets/wallet/transactions/index.js @@ -47,10 +47,10 @@ function appendTransactionDetail(element, transaction) { * @param {WalletTransaction} transaction */ function createTransactionDetails(transaction) { - const content = document.createElement("div"); + const content = document.createElement("section"); const txid = document.createElement("code"); const meta = document.createElement("p"); - const list = document.createElement("div"); + const list = document.createElement("ul"); redaction.setTitle(txid, transaction.txid); redaction.setValue(txid, transaction.txid); @@ -62,7 +62,10 @@ function createTransactionDetails(transaction) { createBtcAmount("span", transaction.fee), ); for (const address of transaction.addresses) { - list.append(createAddressCellContent(address.walletAddress)); + const item = document.createElement("li"); + + item.append(createAddressCellContent(address.walletAddress)); + list.append(item); } content.append(txid, meta, list); @@ -74,7 +77,7 @@ function createTransactionDetails(transaction) { */ function createTransactionRow(transaction) { const row = document.createElement("li"); - const main = document.createElement("div"); + const main = document.createElement("header"); const label = document.createElement("strong"); const amount = createBtcAmount( "span", @@ -87,8 +90,12 @@ function createTransactionRow(transaction) { const summary = document.createElement("summary"); label.append(typeLabels[transaction.type]); - amount.dataset.walletsTxAmount = - transaction.amount >= 0 ? "positive" : "negative"; + if (transaction.amount > 0) { + amount.classList.add("positive"); + } + if (transaction.amount < 0) { + amount.classList.add("negative"); + } redaction.setTitle(txid, transaction.txid); redaction.setValue(txid, formatTxid(transaction.txid)); summary.append("Details"); diff --git a/website_next/wallets/wallet/transactions/style.css b/website_next/wallets/wallet/transactions/style.css index c7a69c87b..ac2970e83 100644 --- a/website_next/wallets/wallet/transactions/style.css +++ b/website_next/wallets/wallet/transactions/style.css @@ -55,7 +55,7 @@ main.wallets { grid-template-columns: 1fr; } - > div:first-child { + > header { display: flex; gap: 1rem; align-items: baseline; @@ -71,11 +71,11 @@ main.wallets { color: var(--white); white-space: nowrap; - &[data-wallets-tx-amount="positive"] { + &.positive { color: var(--green); } - &[data-wallets-tx-amount="negative"] { + &.negative { color: var(--red); } } @@ -135,7 +135,7 @@ main.wallets { } } - > div { + > section { display: grid; gap: 1rem; @@ -145,9 +145,12 @@ main.wallets { font-family: inherit; } - > div { + > ul { display: grid; gap: 0.75rem; + margin: 0; + padding: 0; + list-style: none; } } }