diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 49 | ||||
| -rw-r--r-- | src/i18n.js | 7 | ||||
| -rw-r--r-- | src/style.css | 25 |
3 files changed, 57 insertions, 24 deletions
@@ -510,6 +510,25 @@ function pasteBtn(apply) { }); } +// Paste, for a textarea. Beside a one-line input the button belongs in the +// input-group (see Send); above a textarea it floats next to the label as a +// box heavier than the label itself. So put it inside the field — and only +// while the field is empty, which is the only time it's wanted and the only +// time it can't cover what you typed. +function pasteInto(ta, apply) { + const btn = pasteBtn((text) => { apply(text); sync(); }); + if (!btn) return ta; + btn.classList.add('paste-in'); + btn.append(h('span', {}, t('paste'))); + const wrap = h('div', { class: 'paste-wrap' }, ta, btn); + // Toggled on the node rather than through render(): re-rendering a textarea + // on every keystroke rebuilds it under the caret. + const sync = () => wrap.classList.toggle('has-text', !!ta.value); + ta.addEventListener('input', sync); + sync(); + return wrap; +} + // ---------------------------------------------------------------- display unit // Global BTC/sats preference, persisted in localStorage across refreshes and // logouts. Every unit label on the site is clickable to toggle it. @@ -766,11 +785,8 @@ function importPane() { h( 'label', { class: 'field' }, - h('div', { class: 'row between' }, - h('span', { class: 'lab' }, t('importLabel')), - pasteBtn((text) => { ta.value = text; ui.importText = text; }) - ), - ta + h('span', { class: 'lab' }, t('importLabel')), + pasteInto(ta, (text) => { ta.value = text; ui.importText = text; }) ), optionsPanel(), h('button', { class: 'btn-primary btn-block', onClick: () => openWallet(ui.importText) }, t('openWallet')) @@ -2121,7 +2137,7 @@ function onboardScreen() { const o = ui.onb; // steps that were waiting on something advance the moment it exists if ((o.step === 'welcome' || o.step === 'signin') && ui.screen === 'wallet' && activeAccount()) { - o.step = 'legacy'; + o.step = 'username'; o.enterAddr = featureHook('namesAddress') || null; } if (o.step === 'username') { @@ -2163,10 +2179,8 @@ function onboardScreen() { disabled: !!o.legacyBusy, onClick: goLegacy, }, o.legacyBusy ? h('span', { class: 'spinner sm' }) : t('onbLegacyGo')), h('button', { class: 'btn-block', style: 'padding:14px', onClick: () => { o.step = 'username'; render(); } }, - o.wentToLegacy ? t('onbLegacyDone') : t('onbLegacyNo')), + o.wentToLegacy ? t('onbLegacyDone') : t('back')), ui.onbError ? h('div', { class: 'notice err' }, ui.onbError) : null, - // No Back: the wallet exists by now, so the nostr question behind us - // would just bounce straight back here. Both choices lead forward. ]); } if (o.step === 'welcome') { @@ -2207,10 +2221,12 @@ function onboardScreen() { title(t('onbNameTitle')), h('p', { class: 'muted', style: 'margin:0' }, t('onbNameBody')), featureHook('namesClaimForm') || h('div', { class: 'row gap6', style: 'align-items:center' }, h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('onbNameWait'))), - addr - ? h('button', { class: 'btn-block', onClick: () => { o.step = 'avatar'; render(); } }, - t('onbKeepPre'), h('span', { class: 'onb-addr' }, addr), t('onbKeepPost')) - : null, + // A way past without naming yourself, but not one that shows the + // npub-shaped default address — nobody wants to be offered that. + addr ? h('button', { class: 'linklike small', onClick: () => { o.step = 'avatar'; render(); } }, t('onbSkipName')) : null, + // Most people are new. The ones who aren't know it, and can say so here + // rather than everyone being asked first. + h('button', { class: 'linklike small', onClick: () => { ui.onbError = ''; o.step = 'legacy'; render(); } }, t('onbHaveCoinos')), ]); } if (o.step === 'avatar') { @@ -2607,12 +2623,9 @@ function loadSeedCard() { return h( 'div', { class: 'card col' }, - h('div', { class: 'row between' }, - h('h3', { style: 'margin:0' }, t('loadSeedTitle')), - pasteBtn((text) => { seedTa.value = text; ls.value = text; }) - ), + h('h3', { style: 'margin:0' }, t('loadSeedTitle')), h('p', { class: 'small muted', style: 'margin:0' }, t('loadSeedDesc')), - seedTa, + pasteInto(seedTa, (text) => { seedTa.value = text; ls.value = text; }), h('input', { type: 'password', class: 'mono-input', placeholder: t('bip39PassphraseOpt'), autocapitalize: 'none', autocomplete: 'off', value: ls.passphrase, diff --git a/src/i18n.js b/src/i18n.js index 9b65a77..a395518 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -525,20 +525,19 @@ const STR = { onbStart: 'Get started', onbHaveWallet: 'I already have a wallet', onbSigninTitle: 'Sign in with Nostr', - onbLegacyTitle: 'Do you have an account on coinos.io?', + onbLegacyTitle: 'Move your coinos.io account', onbLegacyBody: 'The original coinos. We can move your balance over and point your username here — your old account keeps working, it just stops receiving payments.', onbLegacyGo: 'Move my coinos.io account', onbLegacyNoAddr: "Your payment address isn't ready yet — try again in a moment.", onbLegacyDone: "I'm done — continue", - onbLegacyNo: "No, I'm new here", + onbHaveCoinos: 'I already have a coinos.io account', migrateClaimed: 'Welcome back — {name} is yours here now.', migrateClaimFailed: "Your balance moved, but {name} isn't free yet. Try claiming it in a minute.", migrateWorking: 'Claiming {name}…', onbNameTitle: 'Claim your username', onbNameBody: 'Your payment address — people can send you bitcoin with just this.', onbNameWait: 'Getting things ready…', - onbKeepPre: 'Use ', - onbKeepPost: ' for now', + onbSkipName: 'Skip for now', onbAvatarTitle: 'Choose your look', onbAvatarBody: 'Pick a punk or upload your own picture. You can change it anytime.', onbUpload: 'Upload a photo', diff --git a/src/style.css b/src/style.css index 5064fae..975f490 100644 --- a/src/style.css +++ b/src/style.css @@ -80,9 +80,12 @@ html, body { margin: 0 auto; padding: 24px 16px 24px; /* A column the height of the window, so the footer sits at the bottom of - short screens instead of floating under the content. */ + short screens instead of floating under the content. svh, not dvh: dvh + can resolve to the tall viewport (URL bar hidden) at first paint, which + puts the footer just below the fold until you scroll — the phone browser + showing its address bar is the case that has to fit. */ min-height: 100vh; - min-height: 100dvh; + min-height: 100svh; display: flex; flex-direction: column; } @@ -578,3 +581,21 @@ body::after { .chan-pick { width: auto; min-width: 0; padding: 6px 32px 6px 10px; font-size: 13px; font-weight: 600; border-radius: 9px; background-position: right 10px center; } .or-split { display: flex; align-items: center; gap: 10px; color: var(--faint); font-size: 13px; } .or-split::before, .or-split::after { content: ''; flex: 1; height: 1px; background: var(--line); } +.paste-wrap { position: relative; display: block; } +.paste-wrap textarea { display: block; } +.paste-in { + position: absolute; right: 10px; bottom: 12px; display: flex; align-items: center; gap: 6px; + padding: 5px 9px; font-size: 12px; font-weight: 600; border-radius: 8px; + color: var(--muted); background: var(--paper); border: 1px solid var(--line-strong); +} +.paste-in:hover { color: var(--ink); } +.paste-wrap.has-text .paste-in { display: none; } + +/* Short screens (a small phone with its address bar showing) — buy back the + vertical room the footer needs by tightening the frame, not the content. */ +@media (max-height: 620px) { + #app { padding-top: 12px; padding-bottom: 12px; } + .card { padding: 16px; } + .footer { padding-top: 14px; padding-bottom: 0; } + .tabs { margin: 10px 0; } +} |
