@enbox/browser 0.3.45 → 0.3.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.mjs +245 -94
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/browser-connect-handler.js +2 -1
- package/dist/esm/browser-connect-handler.js.map +1 -1
- package/dist/esm/ui/connect-modal.js +469 -159
- package/dist/esm/ui/connect-modal.js.map +1 -1
- package/dist/types/browser-connect-handler.d.ts +8 -1
- package/dist/types/browser-connect-handler.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/ui/connect-modal.d.ts +57 -3
- package/dist/types/ui/connect-modal.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/browser-connect-handler.ts +11 -1
- package/src/index.ts +1 -1
- package/src/ui/connect-modal.ts +549 -162
|
@@ -13,9 +13,19 @@
|
|
|
13
13
|
* 2. **Stage** — a height-stable region where states crossfade: the QR
|
|
14
14
|
* (or deep link on phones), the pairing-code input, progress, success,
|
|
15
15
|
* and error states.
|
|
16
|
-
* 3. **Footer** — the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* 3. **Footer** — the wallet identity row ("Connecting with" + the selected
|
|
17
|
+
* wallet and the next catalog wallets as tiles, capped so the same wallet
|
|
18
|
+
* never appears twice), a More tile that expands the full catalog grid
|
|
19
|
+
* with search + custom URL in place, and the alternate-method link.
|
|
20
|
+
*
|
|
21
|
+
* Theming: the stylesheet is static and reads `--ec-*` design tokens.
|
|
22
|
+
* Light/dark follows the visitor's system via `prefers-color-scheme` and
|
|
23
|
+
* updates live; the embedding app can force an appearance or override
|
|
24
|
+
* palette tokens through {@link ConnectModalOptions.theme}. Overrides are
|
|
25
|
+
* applied as inline custom properties — never interpolated into the
|
|
26
|
+
* stylesheet — so a hostile string can't escape its declaration. The QR
|
|
27
|
+
* card alone is theme-invariant (dark modules on white scan most
|
|
28
|
+
* reliably).
|
|
19
29
|
*
|
|
20
30
|
* The phone path drives the `@enbox/connect` relay transport via
|
|
21
31
|
* {@link runRelayConnect}; the browser path drives the popup transport via
|
|
@@ -48,6 +58,22 @@ const REMINT_SAFETY_MS = 30000;
|
|
|
48
58
|
const REMINT_MIN_MS = 10000;
|
|
49
59
|
/** Pairing code length (matches the wallet's generated PIN). */
|
|
50
60
|
const PIN_LENGTH = 4;
|
|
61
|
+
/** Catalog size beyond which the wallet switcher shows its search bar. */
|
|
62
|
+
const WALLET_SEARCH_THRESHOLD = 4; // one full row of tiles
|
|
63
|
+
/** Wallet tiles shown in the collapsed identity row (plus the More tile). */
|
|
64
|
+
const WALLET_ROW_SIZE = 3;
|
|
65
|
+
/** Design-token custom property behind each palette key. */
|
|
66
|
+
const PALETTE_TOKENS = {
|
|
67
|
+
background: '--ec-bg',
|
|
68
|
+
surface: '--ec-surface',
|
|
69
|
+
text: '--ec-text',
|
|
70
|
+
textMuted: '--ec-muted',
|
|
71
|
+
border: '--ec-border',
|
|
72
|
+
accent: '--ec-accent',
|
|
73
|
+
accentContrast: '--ec-accent-contrast',
|
|
74
|
+
danger: '--ec-danger',
|
|
75
|
+
success: '--ec-success',
|
|
76
|
+
};
|
|
51
77
|
/**
|
|
52
78
|
* Resolves a wallet origin to its relay `connectServerUrl` via the wallet's
|
|
53
79
|
* `/.well-known/enbox-connect` document ({@link fetchWalletWellKnown} — the
|
|
@@ -146,7 +172,7 @@ export function runConnectModal(options) {
|
|
|
146
172
|
throw new Error('[@enbox/browser] The connect modal is only available in browser environments.');
|
|
147
173
|
}
|
|
148
174
|
const deps = Object.assign(Object.assign({}, defaultDeps()), options.deps);
|
|
149
|
-
const wallets = (_a = options.wallets) !== null && _a !== void 0 ? _a : [];
|
|
175
|
+
const wallets = dedupeWalletsByUrl((_a = options.wallets) !== null && _a !== void 0 ? _a : []);
|
|
150
176
|
const rememberChoice = (_b = options.rememberChoice) !== null && _b !== void 0 ? _b : true;
|
|
151
177
|
const appName = (_c = options.appName) !== null && _c !== void 0 ? _c : window.location.host;
|
|
152
178
|
const relayWalletPath = (_d = options.relayWalletPath) !== null && _d !== void 0 ? _d : '/connect/app';
|
|
@@ -158,14 +184,13 @@ export function runConnectModal(options) {
|
|
|
158
184
|
// Method resolution order: explicit option → remembered → phone.
|
|
159
185
|
let method = (_j = (_h = options.preferredMethod) !== null && _h !== void 0 ? _h : lastChoice === null || lastChoice === void 0 ? void 0 : lastChoice.method) !== null && _j !== void 0 ? _j : 'phone';
|
|
160
186
|
return new Promise((resolve, reject) => {
|
|
161
|
-
var _a, _b;
|
|
187
|
+
var _a, _b, _c, _d;
|
|
162
188
|
// ── Host + shadow root ─────────────────────────────────────
|
|
163
189
|
const host = document.createElement('div');
|
|
164
190
|
host.id = 'enbox-connect-modal';
|
|
165
191
|
const shadow = host.attachShadow({ mode: 'open' });
|
|
166
|
-
const isDark = (_b = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-color-scheme: dark)').matches) !== null && _b !== void 0 ? _b : false;
|
|
167
192
|
const style = document.createElement('style');
|
|
168
|
-
style.textContent =
|
|
193
|
+
style.textContent = MODAL_STYLES;
|
|
169
194
|
shadow.appendChild(style);
|
|
170
195
|
const overlay = document.createElement('div');
|
|
171
196
|
overlay.className = 'overlay';
|
|
@@ -174,6 +199,42 @@ export function runConnectModal(options) {
|
|
|
174
199
|
modal.setAttribute('role', 'dialog');
|
|
175
200
|
modal.setAttribute('aria-modal', 'true');
|
|
176
201
|
modal.setAttribute('aria-label', `Connect to ${appName}`);
|
|
202
|
+
// ── Theme ──────────────────────────────────────────────────
|
|
203
|
+
// System light/dark is pure CSS (`prefers-color-scheme`), so it tracks
|
|
204
|
+
// the visitor live. A forced appearance pins the token block instead.
|
|
205
|
+
const theme = (_a = options.theme) !== null && _a !== void 0 ? _a : {};
|
|
206
|
+
const appearance = (_b = theme.appearance) !== null && _b !== void 0 ? _b : 'auto';
|
|
207
|
+
if (appearance !== 'auto') {
|
|
208
|
+
modal.setAttribute('data-appearance', appearance);
|
|
209
|
+
}
|
|
210
|
+
// Dapp palette overrides land as inline custom properties: setProperty
|
|
211
|
+
// parses each value as a lone declaration, so a hostile string cannot
|
|
212
|
+
// escape into the stylesheet.
|
|
213
|
+
const systemDark = (_c = window.matchMedia) === null || _c === void 0 ? void 0 : _c.call(window, '(prefers-color-scheme: dark)');
|
|
214
|
+
const hasPaletteOverrides = theme.accent !== undefined || theme.accentContrast !== undefined
|
|
215
|
+
|| theme.light !== undefined || theme.dark !== undefined;
|
|
216
|
+
const applyPaletteTokens = () => {
|
|
217
|
+
const scheme = appearance === 'auto'
|
|
218
|
+
? ((systemDark === null || systemDark === void 0 ? void 0 : systemDark.matches) === true ? 'dark' : 'light')
|
|
219
|
+
: appearance;
|
|
220
|
+
const palette = Object.assign({ accent: theme.accent, accentContrast: theme.accentContrast }, theme[scheme]);
|
|
221
|
+
for (const key of Object.keys(PALETTE_TOKENS)) {
|
|
222
|
+
const value = palette[key];
|
|
223
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
224
|
+
modal.style.setProperty(PALETTE_TOKENS[key], value);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
modal.style.removeProperty(PALETTE_TOKENS[key]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const onSchemeChange = () => applyPaletteTokens();
|
|
232
|
+
if (hasPaletteOverrides) {
|
|
233
|
+
applyPaletteTokens();
|
|
234
|
+
if (appearance === 'auto') {
|
|
235
|
+
(_d = systemDark === null || systemDark === void 0 ? void 0 : systemDark.addEventListener) === null || _d === void 0 ? void 0 : _d.call(systemDark, 'change', onSchemeChange);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
177
238
|
// ── Session state ──────────────────────────────────────────
|
|
178
239
|
let settled = false;
|
|
179
240
|
let relaySession;
|
|
@@ -181,14 +242,40 @@ export function runConnectModal(options) {
|
|
|
181
242
|
let popupBusy = false;
|
|
182
243
|
let pinResolve;
|
|
183
244
|
const discoveryCache = new Map();
|
|
245
|
+
// Collapsed identity row: the selected wallet plus the next catalog
|
|
246
|
+
// wallets, never repeating one. Slots stay stable across in-row
|
|
247
|
+
// switches (only the highlight moves); the row recomposes only when a
|
|
248
|
+
// wallet from outside it — grid pick or custom URL — becomes selected,
|
|
249
|
+
// which puts that wallet in the first slot.
|
|
250
|
+
let rowWallets = [];
|
|
251
|
+
/** The catalog entry for `url`, or a synthesized option for custom URLs. */
|
|
252
|
+
const walletByUrl = (url) => { var _a, _b; return (_a = wallets.find((wallet) => wallet.url === url)) !== null && _a !== void 0 ? _a : { name: (_b = hostnameOf(url)) !== null && _b !== void 0 ? _b : url, url }; };
|
|
253
|
+
const composeWalletRow = () => {
|
|
254
|
+
if (walletUrl === undefined) {
|
|
255
|
+
rowWallets = wallets.slice(0, WALLET_ROW_SIZE);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (rowWallets.some((wallet) => wallet.url === walletUrl)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const selected = walletByUrl(walletUrl);
|
|
262
|
+
rowWallets = [selected, ...wallets.filter((wallet) => wallet.url !== selected.url)].slice(0, WALLET_ROW_SIZE);
|
|
263
|
+
};
|
|
264
|
+
const selectWallet = (url) => {
|
|
265
|
+
walletUrl = url;
|
|
266
|
+
renderFooter();
|
|
267
|
+
void startMethod();
|
|
268
|
+
};
|
|
184
269
|
const cleanup = () => {
|
|
270
|
+
var _a;
|
|
185
271
|
relaySession === null || relaySession === void 0 ? void 0 : relaySession.cancel();
|
|
186
272
|
clearTimeout(remintTimer);
|
|
187
273
|
document.removeEventListener('keydown', onKeydown);
|
|
274
|
+
(_a = systemDark === null || systemDark === void 0 ? void 0 : systemDark.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(systemDark, 'change', onSchemeChange);
|
|
188
275
|
try {
|
|
189
276
|
document.body.removeChild(host);
|
|
190
277
|
}
|
|
191
|
-
catch ( /* best effort */
|
|
278
|
+
catch ( /* best effort */_b) { /* best effort */ }
|
|
192
279
|
};
|
|
193
280
|
const settle = (settler) => {
|
|
194
281
|
if (settled) {
|
|
@@ -227,22 +314,39 @@ export function runConnectModal(options) {
|
|
|
227
314
|
setStage(el('div', 'spinner'), el('p', 'stage-caption', message));
|
|
228
315
|
};
|
|
229
316
|
const renderQr = (handoff) => {
|
|
317
|
+
var _a;
|
|
318
|
+
const wallet = walletUrl !== undefined ? walletByUrl(walletUrl) : undefined;
|
|
319
|
+
const walletName = (_a = wallet === null || wallet === void 0 ? void 0 : wallet.name) !== null && _a !== void 0 ? _a : 'your wallet';
|
|
230
320
|
if (isMobile) {
|
|
231
321
|
const link = document.createElement('a');
|
|
232
322
|
link.className = 'deep-link';
|
|
233
323
|
link.href = handoff.walletUri;
|
|
234
|
-
|
|
235
|
-
|
|
324
|
+
if (wallet !== undefined) {
|
|
325
|
+
link.appendChild(buildWalletIcon(wallet));
|
|
326
|
+
}
|
|
327
|
+
link.appendChild(el('span', 'deep-link-label', `Continue in ${walletName}`));
|
|
328
|
+
setStage(link, el('p', 'stage-caption', `${walletName} opens to approve this connection.`), el('p', 'stage-subline', 'Come back here when you’re done.'));
|
|
236
329
|
return;
|
|
237
330
|
}
|
|
238
331
|
const qrBox = document.createElement('div');
|
|
239
332
|
qrBox.className = 'qr-box';
|
|
333
|
+
// Fixed colors on the white card: dark-on-light QRs scan reliably in
|
|
334
|
+
// both appearances (inverted codes trip up some camera apps).
|
|
240
335
|
qrBox.replaceChildren(qrToSvg(encodeQr(handoff.walletUri), {
|
|
241
|
-
dark:
|
|
336
|
+
dark: '#14141f',
|
|
242
337
|
light: 'transparent',
|
|
243
338
|
quietZone: 2,
|
|
244
339
|
}));
|
|
245
|
-
|
|
340
|
+
// The selected wallet's mark sits at the QR centre — the "this is the
|
|
341
|
+
// wallet you're connecting to" cue. The 44px plate over the ~180px
|
|
342
|
+
// symbol occludes ~5% of modules, well inside ECC level M's 15%
|
|
343
|
+
// recovery budget.
|
|
344
|
+
if (wallet !== undefined) {
|
|
345
|
+
const logo = el('div', 'qr-logo');
|
|
346
|
+
logo.appendChild(buildWalletIcon(wallet));
|
|
347
|
+
qrBox.appendChild(logo);
|
|
348
|
+
}
|
|
349
|
+
setStage(qrBox, el('p', 'stage-caption', 'Scan with your phone’s camera'), el('p', 'stage-subline', `${walletName} stays on your phone.`));
|
|
246
350
|
};
|
|
247
351
|
const renderClaimed = () => {
|
|
248
352
|
const pulse = document.createElement('div');
|
|
@@ -499,59 +603,102 @@ export function runConnectModal(options) {
|
|
|
499
603
|
method = next;
|
|
500
604
|
yield startMethod();
|
|
501
605
|
});
|
|
502
|
-
// ── Footer:
|
|
606
|
+
// ── Footer: identity row (+ catalog panel), method link below ──
|
|
503
607
|
const renderFooter = () => {
|
|
504
608
|
footer.replaceChildren();
|
|
609
|
+
if (!lockedWallet) {
|
|
610
|
+
composeWalletRow();
|
|
611
|
+
const switcher = buildWalletSwitcher();
|
|
612
|
+
if (switcher.label !== undefined) {
|
|
613
|
+
footer.appendChild(switcher.label);
|
|
614
|
+
}
|
|
615
|
+
footer.appendChild(switcher.row);
|
|
616
|
+
footer.appendChild(switcher.panel);
|
|
617
|
+
}
|
|
618
|
+
const row = document.createElement('div');
|
|
619
|
+
row.className = 'footer-row';
|
|
505
620
|
if (method === 'phone') {
|
|
506
621
|
const alt = document.createElement('button');
|
|
507
622
|
alt.className = 'footer-link method-link';
|
|
508
|
-
alt.textContent = 'No phone
|
|
623
|
+
alt.textContent = 'No phone? Use this browser →';
|
|
509
624
|
// startPopup runs synchronously in this click handler.
|
|
510
625
|
alt.addEventListener('click', () => { startPopup(); });
|
|
511
|
-
|
|
626
|
+
row.appendChild(alt);
|
|
512
627
|
}
|
|
513
628
|
else if (!isMobile) {
|
|
514
629
|
const alt = document.createElement('button');
|
|
515
630
|
alt.className = 'footer-link method-link';
|
|
516
631
|
alt.textContent = 'Use your phone instead →';
|
|
517
632
|
alt.addEventListener('click', () => { void switchMethod('phone'); });
|
|
518
|
-
|
|
633
|
+
row.appendChild(alt);
|
|
519
634
|
}
|
|
520
|
-
if (
|
|
521
|
-
footer.appendChild(
|
|
635
|
+
if (row.childElementCount > 0) {
|
|
636
|
+
footer.appendChild(row);
|
|
522
637
|
}
|
|
523
638
|
};
|
|
524
639
|
const buildWalletSwitcher = () => {
|
|
525
|
-
var _a
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
640
|
+
var _a;
|
|
641
|
+
// Identity row: selected wallet + the next catalog wallets, then the
|
|
642
|
+
// More tile expanding the full catalog in place.
|
|
643
|
+
const row = document.createElement('div');
|
|
644
|
+
row.className = 'wallet-row';
|
|
645
|
+
row.setAttribute('role', 'radiogroup');
|
|
646
|
+
row.setAttribute('aria-label', 'Wallet');
|
|
647
|
+
for (const wallet of rowWallets) {
|
|
648
|
+
const tile = document.createElement('button');
|
|
649
|
+
tile.className = 'row-tile';
|
|
650
|
+
tile.title = (_a = wallet.description) !== null && _a !== void 0 ? _a : wallet.name;
|
|
651
|
+
tile.setAttribute('role', 'radio');
|
|
652
|
+
const isSelected = wallet.url === walletUrl;
|
|
653
|
+
tile.setAttribute('aria-checked', isSelected ? 'true' : 'false');
|
|
654
|
+
if (isSelected) {
|
|
655
|
+
tile.classList.add('selected');
|
|
656
|
+
}
|
|
657
|
+
tile.appendChild(buildWalletIcon(wallet));
|
|
658
|
+
tile.appendChild(el('span', 'row-tile-name', wallet.name));
|
|
659
|
+
tile.addEventListener('click', () => {
|
|
660
|
+
if (wallet.url !== walletUrl) {
|
|
661
|
+
selectWallet(wallet.url);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
row.appendChild(tile);
|
|
665
|
+
}
|
|
533
666
|
const panel = document.createElement('div');
|
|
534
667
|
panel.className = 'wallet-panel';
|
|
535
668
|
panel.hidden = true;
|
|
536
|
-
|
|
537
|
-
const
|
|
669
|
+
const hiddenCount = wallets.filter((wallet) => !rowWallets.some((shown) => shown.url === wallet.url)).length;
|
|
670
|
+
const more = document.createElement('button');
|
|
671
|
+
more.className = 'row-tile more-tile';
|
|
672
|
+
more.title = 'All wallets';
|
|
673
|
+
more.setAttribute('aria-expanded', 'false');
|
|
674
|
+
more.appendChild(el('span', 'more-count', hiddenCount > 0 ? `+${hiddenCount}` : '⋯'));
|
|
675
|
+
more.appendChild(el('span', 'row-tile-name', 'More'));
|
|
676
|
+
more.addEventListener('click', () => {
|
|
677
|
+
panel.hidden = !panel.hidden;
|
|
678
|
+
more.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
|
|
679
|
+
});
|
|
680
|
+
row.appendChild(more);
|
|
681
|
+
const label = rowWallets.length > 0 ? el('div', 'wallet-row-label', 'Connecting with') : undefined;
|
|
682
|
+
// Search filter — appears once the catalog outgrows one grid row.
|
|
683
|
+
const tiles = [];
|
|
538
684
|
const empty = document.createElement('div');
|
|
539
685
|
empty.className = 'wallet-empty';
|
|
540
686
|
empty.textContent = 'No wallets match your search.';
|
|
541
687
|
empty.hidden = true;
|
|
542
|
-
if (wallets.length >
|
|
688
|
+
if (wallets.length > WALLET_SEARCH_THRESHOLD) {
|
|
543
689
|
const search = document.createElement('input');
|
|
544
690
|
search.type = 'search';
|
|
545
691
|
search.className = 'wallet-search';
|
|
546
692
|
search.placeholder = 'Search wallets…';
|
|
693
|
+
search.setAttribute('aria-label', 'Search wallets');
|
|
547
694
|
search.addEventListener('input', () => {
|
|
548
695
|
const query = search.value.trim().toLowerCase();
|
|
549
696
|
let visible = 0;
|
|
550
|
-
for (const { wallet, el } of
|
|
697
|
+
for (const { wallet, el: tile } of tiles) {
|
|
551
698
|
const match = query === ''
|
|
552
699
|
|| wallet.name.toLowerCase().includes(query)
|
|
553
700
|
|| wallet.url.toLowerCase().includes(query);
|
|
554
|
-
|
|
701
|
+
tile.hidden = !match;
|
|
555
702
|
if (match) {
|
|
556
703
|
visible += 1;
|
|
557
704
|
}
|
|
@@ -560,26 +707,34 @@ export function runConnectModal(options) {
|
|
|
560
707
|
});
|
|
561
708
|
panel.appendChild(search);
|
|
562
709
|
}
|
|
710
|
+
// Square tiles, three per row; past two rows the grid scrolls in
|
|
711
|
+
// place so a large catalog never stretches the modal.
|
|
712
|
+
const scroll = document.createElement('div');
|
|
713
|
+
scroll.className = 'wallet-scroll';
|
|
714
|
+
const grid = document.createElement('div');
|
|
715
|
+
grid.className = 'wallet-grid';
|
|
563
716
|
for (const wallet of wallets) {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
717
|
+
const tile = document.createElement('button');
|
|
718
|
+
tile.className = 'wallet-tile';
|
|
719
|
+
tile.title = wallet.name;
|
|
720
|
+
tile.appendChild(buildWalletIcon(wallet));
|
|
721
|
+
const tileName = document.createElement('span');
|
|
722
|
+
tileName.className = 'wallet-tile-name';
|
|
723
|
+
tileName.textContent = wallet.name;
|
|
724
|
+
tile.appendChild(tileName);
|
|
571
725
|
if (wallet.url === walletUrl) {
|
|
572
|
-
|
|
726
|
+
tile.classList.add('selected');
|
|
727
|
+
tile.setAttribute('aria-pressed', 'true');
|
|
573
728
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
renderFooter();
|
|
577
|
-
void startMethod();
|
|
729
|
+
tile.addEventListener('click', () => {
|
|
730
|
+
selectWallet(wallet.url);
|
|
578
731
|
});
|
|
579
|
-
|
|
580
|
-
|
|
732
|
+
tiles.push({ wallet, el: tile });
|
|
733
|
+
grid.appendChild(tile);
|
|
581
734
|
}
|
|
582
|
-
|
|
735
|
+
scroll.appendChild(grid);
|
|
736
|
+
scroll.appendChild(empty);
|
|
737
|
+
panel.appendChild(scroll);
|
|
583
738
|
// Custom wallet URL (the power-user path, demoted to the disclosure).
|
|
584
739
|
const custom = document.createElement('div');
|
|
585
740
|
custom.className = 'wallet-custom';
|
|
@@ -597,11 +752,6 @@ export function runConnectModal(options) {
|
|
|
597
752
|
// handshake is the ultimate gate — a valid wallet behind strict CORS
|
|
598
753
|
// should not be permanently blocked.
|
|
599
754
|
let allowUnverified = false;
|
|
600
|
-
const useWallet = (origin) => {
|
|
601
|
-
walletUrl = origin;
|
|
602
|
-
renderFooter();
|
|
603
|
-
void startMethod();
|
|
604
|
-
};
|
|
605
755
|
const submitCustom = () => __awaiter(this, void 0, void 0, function* () {
|
|
606
756
|
const value = input.value.trim();
|
|
607
757
|
if (value === '') {
|
|
@@ -616,7 +766,7 @@ export function runConnectModal(options) {
|
|
|
616
766
|
return;
|
|
617
767
|
}
|
|
618
768
|
if (allowUnverified) {
|
|
619
|
-
|
|
769
|
+
selectWallet(origin);
|
|
620
770
|
return;
|
|
621
771
|
}
|
|
622
772
|
go.disabled = true;
|
|
@@ -626,7 +776,7 @@ export function runConnectModal(options) {
|
|
|
626
776
|
go.disabled = false;
|
|
627
777
|
if (valid) {
|
|
628
778
|
go.textContent = 'Use';
|
|
629
|
-
|
|
779
|
+
selectWallet(origin);
|
|
630
780
|
return;
|
|
631
781
|
}
|
|
632
782
|
allowUnverified = true;
|
|
@@ -651,13 +801,7 @@ export function runConnectModal(options) {
|
|
|
651
801
|
custom.appendChild(go);
|
|
652
802
|
panel.appendChild(custom);
|
|
653
803
|
panel.appendChild(urlError);
|
|
654
|
-
|
|
655
|
-
panel.hidden = !panel.hidden;
|
|
656
|
-
toggle.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
|
|
657
|
-
});
|
|
658
|
-
wrap.appendChild(toggle);
|
|
659
|
-
wrap.appendChild(panel);
|
|
660
|
-
return wrap;
|
|
804
|
+
return { label, row, panel };
|
|
661
805
|
};
|
|
662
806
|
// ── Global interactions ────────────────────────────────────
|
|
663
807
|
overlay.addEventListener('click', (event) => {
|
|
@@ -686,6 +830,22 @@ export function runConnectModal(options) {
|
|
|
686
830
|
function walletInCatalog(wallets, url) {
|
|
687
831
|
return wallets.some((wallet) => wallet.url === url);
|
|
688
832
|
}
|
|
833
|
+
/** Drops catalog entries that repeat an earlier wallet's URL. */
|
|
834
|
+
function dedupeWalletsByUrl(wallets) {
|
|
835
|
+
return wallets.filter((wallet, index) => wallets.findIndex((candidate) => candidate.url === wallet.url) === index);
|
|
836
|
+
}
|
|
837
|
+
/** Compact display form of a wallet URL for the switcher toggle. */
|
|
838
|
+
function hostnameOf(url) {
|
|
839
|
+
if (url === undefined) {
|
|
840
|
+
return undefined;
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
return new URL(url).hostname;
|
|
844
|
+
}
|
|
845
|
+
catch (_a) {
|
|
846
|
+
return url;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
689
849
|
function el(tag, className, text) {
|
|
690
850
|
const node = document.createElement(tag);
|
|
691
851
|
node.className = className;
|
|
@@ -753,19 +913,42 @@ function trapFocus(shadow, event) {
|
|
|
753
913
|
}
|
|
754
914
|
}
|
|
755
915
|
// ─── Styles ──────────────────────────────────────────────────────
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
916
|
+
//
|
|
917
|
+
// One static sheet; every color reads a `--ec-*` token. Light values sit on
|
|
918
|
+
// `.modal`, dark values apply under `prefers-color-scheme` (so the system
|
|
919
|
+
// appearance tracks live) and under a forced `data-appearance`. Dapp
|
|
920
|
+
// palette overrides land as inline custom properties, which outrank all of
|
|
921
|
+
// these — nothing user-provided is ever concatenated into this string.
|
|
922
|
+
/** Light-appearance design tokens. */
|
|
923
|
+
const LIGHT_TOKENS = `
|
|
924
|
+
--ec-bg: #ffffff;
|
|
925
|
+
--ec-surface: #f5f6f8;
|
|
926
|
+
--ec-text: #17171f;
|
|
927
|
+
--ec-muted: #63636e;
|
|
928
|
+
--ec-border: #e3e4e9;
|
|
929
|
+
--ec-accent: #0a62d0;
|
|
930
|
+
--ec-accent-contrast: #ffffff;
|
|
931
|
+
--ec-danger: #c0392b;
|
|
932
|
+
--ec-success: #1e9e5a;`;
|
|
933
|
+
/** Dark-appearance design tokens. */
|
|
934
|
+
const DARK_TOKENS = `
|
|
935
|
+
--ec-bg: #181822;
|
|
936
|
+
--ec-surface: #23232f;
|
|
937
|
+
--ec-text: #e9e9f0;
|
|
938
|
+
--ec-muted: #9a9aa6;
|
|
939
|
+
--ec-border: #33333f;
|
|
940
|
+
--ec-accent: #58a6ff;
|
|
941
|
+
--ec-accent-contrast: #0b1526;
|
|
942
|
+
--ec-danger: #ff7a7a;
|
|
943
|
+
--ec-success: #43d17c;`;
|
|
944
|
+
const MODAL_STYLES = `
|
|
767
945
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
768
946
|
|
|
947
|
+
/* Keep the hidden attribute authoritative: author display values (e.g.
|
|
948
|
+
the panel's flex) would otherwise override the UA's [hidden] rule and
|
|
949
|
+
leave "collapsed" sections permanently expanded. */
|
|
950
|
+
[hidden] { display: none !important; }
|
|
951
|
+
|
|
769
952
|
.overlay {
|
|
770
953
|
position: fixed;
|
|
771
954
|
inset: 0;
|
|
@@ -773,6 +956,7 @@ function buildStyles(isDark) {
|
|
|
773
956
|
display: flex;
|
|
774
957
|
align-items: center;
|
|
775
958
|
justify-content: center;
|
|
959
|
+
padding: 16px;
|
|
776
960
|
background: rgba(0, 0, 0, 0.5);
|
|
777
961
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
778
962
|
animation: fadeIn 0.15s ease-out;
|
|
@@ -787,115 +971,159 @@ function buildStyles(isDark) {
|
|
|
787
971
|
40%, 80% { transform: translateX(6px); }
|
|
788
972
|
}
|
|
789
973
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
974
|
+
@keyframes claimedPulse {
|
|
975
|
+
0%, 100% { transform: scale(0.85); opacity: 0.7; }
|
|
976
|
+
50% { transform: scale(1); opacity: 1; }
|
|
977
|
+
}
|
|
790
978
|
|
|
791
|
-
.modal {
|
|
792
|
-
|
|
793
|
-
|
|
979
|
+
.modal {${LIGHT_TOKENS}
|
|
980
|
+
--ec-accent-soft: color-mix(in srgb, var(--ec-accent) 14%, transparent);
|
|
981
|
+
background: var(--ec-bg);
|
|
982
|
+
color: var(--ec-text);
|
|
794
983
|
border-radius: 16px;
|
|
795
984
|
width: 400px;
|
|
796
|
-
max-width:
|
|
797
|
-
max-height:
|
|
985
|
+
max-width: 100%;
|
|
986
|
+
max-height: calc(100vh - 32px);
|
|
798
987
|
overflow-y: auto;
|
|
799
|
-
padding: 20px
|
|
800
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.
|
|
988
|
+
padding: 16px 20px 12px;
|
|
989
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
|
|
801
990
|
animation: slideUp 0.2s ease-out;
|
|
802
991
|
display: flex;
|
|
803
992
|
flex-direction: column;
|
|
804
993
|
}
|
|
994
|
+
@media (prefers-color-scheme: dark) {
|
|
995
|
+
.modal {${DARK_TOKENS}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
.modal[data-appearance='light'] {${LIGHT_TOKENS}
|
|
999
|
+
}
|
|
1000
|
+
.modal[data-appearance='dark'] {${DARK_TOKENS}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
button, input { font-family: inherit; }
|
|
1004
|
+
button:focus-visible, a:focus-visible {
|
|
1005
|
+
outline: 2px solid var(--ec-accent);
|
|
1006
|
+
outline-offset: 2px;
|
|
1007
|
+
}
|
|
805
1008
|
|
|
806
|
-
.header { display: flex; justify-content: space-between; align-items: center; }
|
|
1009
|
+
.header { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
807
1010
|
.header-identity { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
808
|
-
.header-icon { border-radius: 6px; }
|
|
1011
|
+
.header-icon { border-radius: 6px; flex-shrink: 0; }
|
|
809
1012
|
h2 { font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
810
1013
|
|
|
811
1014
|
.close-btn {
|
|
812
1015
|
background: none; border: none; font-size: 24px; cursor: pointer;
|
|
813
|
-
color:
|
|
1016
|
+
color: var(--ec-muted); padding: 4px 8px; border-radius: 8px; line-height: 1;
|
|
1017
|
+
flex-shrink: 0;
|
|
814
1018
|
}
|
|
815
|
-
.close-btn:hover { color:
|
|
1019
|
+
.close-btn:hover { color: var(--ec-text); background: var(--ec-surface); }
|
|
816
1020
|
|
|
817
1021
|
.stage {
|
|
818
|
-
min-height:
|
|
1022
|
+
min-height: 240px;
|
|
819
1023
|
display: flex;
|
|
820
1024
|
flex-direction: column;
|
|
821
1025
|
align-items: center;
|
|
822
1026
|
justify-content: center;
|
|
823
|
-
gap:
|
|
1027
|
+
gap: 10px;
|
|
824
1028
|
text-align: center;
|
|
825
|
-
padding:
|
|
1029
|
+
padding: 12px 0 10px;
|
|
826
1030
|
}
|
|
827
1031
|
.stage.fade-in > * { animation: stageIn 0.15s ease-out; }
|
|
828
1032
|
|
|
829
1033
|
.qr-box {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1034
|
+
position: relative;
|
|
1035
|
+
width: 200px;
|
|
1036
|
+
height: 200px;
|
|
1037
|
+
padding: 10px;
|
|
833
1038
|
border-radius: 12px;
|
|
834
|
-
background:
|
|
1039
|
+
background: #ffffff;
|
|
1040
|
+
border: 1px solid #e3e4e9;
|
|
1041
|
+
}
|
|
1042
|
+
.qr-box svg { width: 100%; height: 100%; display: block; }
|
|
1043
|
+
|
|
1044
|
+
/* Selected wallet's mark on the QR centre. Like the card, the plate is
|
|
1045
|
+
theme-invariant white so the logo sits on a scannable ground. */
|
|
1046
|
+
.qr-logo {
|
|
1047
|
+
position: absolute;
|
|
1048
|
+
top: 50%;
|
|
1049
|
+
left: 50%;
|
|
1050
|
+
transform: translate(-50%, -50%);
|
|
1051
|
+
width: 44px;
|
|
1052
|
+
height: 44px;
|
|
1053
|
+
border-radius: 10px;
|
|
1054
|
+
background: #ffffff;
|
|
1055
|
+
border: 1px solid #e3e4e9;
|
|
1056
|
+
display: flex;
|
|
1057
|
+
align-items: center;
|
|
1058
|
+
justify-content: center;
|
|
1059
|
+
}
|
|
1060
|
+
.qr-logo .wallet-icon { width: 30px; height: 30px; }
|
|
1061
|
+
.qr-logo .wallet-badge { border-radius: 8px; font-size: 15px; }
|
|
1062
|
+
|
|
835
1063
|
.claimed-pulse {
|
|
836
1064
|
width: 64px; height: 64px; border-radius: 50%;
|
|
837
|
-
background:
|
|
1065
|
+
background: var(--ec-accent-soft); position: relative;
|
|
838
1066
|
}
|
|
839
1067
|
.claimed-pulse::after {
|
|
840
1068
|
content: ''; position: absolute; inset: 18px; border-radius: 50%;
|
|
841
|
-
background:
|
|
842
|
-
animation:
|
|
1069
|
+
background: var(--ec-accent);
|
|
1070
|
+
animation: claimedPulse 1.6s ease-in-out infinite;
|
|
843
1071
|
}
|
|
844
|
-
@keyframes enbox-claimed-pulse {
|
|
845
|
-
0%, 100% { transform: scale(0.85); opacity: 0.7; }
|
|
846
|
-
50% { transform: scale(1); opacity: 1; }
|
|
847
|
-
}
|
|
848
|
-
@media (prefers-reduced-motion: reduce) {
|
|
849
|
-
.claimed-pulse::after { animation: none; }
|
|
850
|
-
};
|
|
851
|
-
border: 1px solid ${border};
|
|
852
|
-
}
|
|
853
|
-
.qr-box svg { width: 100%; height: 100%; display: block; }
|
|
854
1072
|
|
|
855
1073
|
.stage-caption { font-size: 15px; font-weight: 600; }
|
|
856
|
-
.stage-subline { font-size: 13px; color:
|
|
857
|
-
.stage-error { font-size: 13px; color:
|
|
1074
|
+
.stage-subline { font-size: 13px; color: var(--ec-muted); max-width: 300px; line-height: 1.45; }
|
|
1075
|
+
.stage-error { font-size: 13px; color: var(--ec-danger); }
|
|
858
1076
|
|
|
859
1077
|
.deep-link {
|
|
860
|
-
display:
|
|
1078
|
+
display: flex;
|
|
1079
|
+
align-items: center;
|
|
1080
|
+
justify-content: center;
|
|
1081
|
+
gap: 10px;
|
|
861
1082
|
width: 100%;
|
|
862
|
-
padding:
|
|
1083
|
+
padding: 13px 16px;
|
|
863
1084
|
border-radius: 12px;
|
|
864
|
-
background:
|
|
865
|
-
color:
|
|
1085
|
+
background: var(--ec-accent);
|
|
1086
|
+
color: var(--ec-accent-contrast);
|
|
866
1087
|
font-weight: 600;
|
|
867
1088
|
font-size: 15px;
|
|
868
1089
|
text-decoration: none;
|
|
869
1090
|
}
|
|
1091
|
+
.deep-link .wallet-icon { width: 24px; height: 24px; }
|
|
1092
|
+
.deep-link .wallet-badge {
|
|
1093
|
+
border-radius: 6px;
|
|
1094
|
+
font-size: 12px;
|
|
1095
|
+
background: var(--ec-accent-contrast);
|
|
1096
|
+
}
|
|
1097
|
+
.deep-link .wallet-img { border-radius: 6px; }
|
|
870
1098
|
|
|
871
1099
|
.pin-row { display: flex; gap: 10px; }
|
|
872
1100
|
.pin-row.shake { animation: shake 0.4s ease-in-out; }
|
|
873
1101
|
.pin-input {
|
|
874
|
-
width:
|
|
1102
|
+
width: 46px; height: 54px;
|
|
875
1103
|
text-align: center;
|
|
876
1104
|
font-size: 24px; font-weight: 600;
|
|
877
|
-
color:
|
|
878
|
-
background:
|
|
879
|
-
border: 1px solid
|
|
1105
|
+
color: var(--ec-text);
|
|
1106
|
+
background: var(--ec-surface);
|
|
1107
|
+
border: 1px solid var(--ec-border);
|
|
880
1108
|
border-radius: 10px;
|
|
881
1109
|
outline: none;
|
|
882
1110
|
}
|
|
883
|
-
.pin-input:focus { border-color:
|
|
1111
|
+
.pin-input:focus { border-color: var(--ec-accent); }
|
|
884
1112
|
|
|
885
1113
|
.check {
|
|
886
1114
|
width: 56px; height: 56px;
|
|
887
1115
|
display: flex; align-items: center; justify-content: center;
|
|
888
1116
|
border-radius: 50%;
|
|
889
|
-
background:
|
|
890
|
-
color: #
|
|
1117
|
+
background: var(--ec-success);
|
|
1118
|
+
color: #ffffff;
|
|
891
1119
|
font-size: 28px;
|
|
892
1120
|
}
|
|
893
1121
|
|
|
894
1122
|
.spinner {
|
|
895
1123
|
width: 32px; height: 32px;
|
|
896
1124
|
border-radius: 50%;
|
|
897
|
-
border: 3px solid
|
|
898
|
-
border-top-color:
|
|
1125
|
+
border: 3px solid var(--ec-border);
|
|
1126
|
+
border-top-color: var(--ec-accent);
|
|
899
1127
|
animation: spin 0.8s linear infinite;
|
|
900
1128
|
}
|
|
901
1129
|
|
|
@@ -903,88 +1131,170 @@ function buildStyles(isDark) {
|
|
|
903
1131
|
padding: 10px 20px;
|
|
904
1132
|
border: none;
|
|
905
1133
|
border-radius: 10px;
|
|
906
|
-
background:
|
|
907
|
-
color:
|
|
1134
|
+
background: var(--ec-accent);
|
|
1135
|
+
color: var(--ec-accent-contrast);
|
|
908
1136
|
font-size: 14px;
|
|
909
1137
|
font-weight: 500;
|
|
910
1138
|
cursor: pointer;
|
|
911
1139
|
}
|
|
912
|
-
.stage-btn:hover { filter: brightness(1.
|
|
1140
|
+
.stage-btn:hover { filter: brightness(1.08); }
|
|
913
1141
|
|
|
914
1142
|
.footer {
|
|
915
|
-
border-top: 1px solid
|
|
916
|
-
padding-top:
|
|
1143
|
+
border-top: 1px solid var(--ec-border);
|
|
1144
|
+
padding-top: 8px;
|
|
917
1145
|
display: flex;
|
|
918
1146
|
flex-direction: column;
|
|
919
1147
|
gap: 6px;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.footer-row {
|
|
1151
|
+
display: flex;
|
|
920
1152
|
align-items: center;
|
|
1153
|
+
justify-content: space-between;
|
|
1154
|
+
flex-wrap: wrap;
|
|
1155
|
+
gap: 2px 8px;
|
|
921
1156
|
}
|
|
1157
|
+
.footer-row > * { min-width: 0; }
|
|
1158
|
+
.footer-row > :only-child { margin-inline: auto; }
|
|
922
1159
|
|
|
923
1160
|
.footer-link {
|
|
924
1161
|
background: none;
|
|
925
1162
|
border: none;
|
|
926
|
-
color:
|
|
1163
|
+
color: var(--ec-muted);
|
|
927
1164
|
font-size: 13px;
|
|
928
1165
|
cursor: pointer;
|
|
929
|
-
padding:
|
|
1166
|
+
padding: 5px 8px;
|
|
930
1167
|
border-radius: 8px;
|
|
931
1168
|
}
|
|
932
|
-
.footer-link:hover { color:
|
|
933
|
-
.method-link { color:
|
|
1169
|
+
.footer-link:hover { color: var(--ec-text); background: var(--ec-surface); }
|
|
1170
|
+
.method-link { color: var(--ec-accent); }
|
|
1171
|
+
|
|
1172
|
+
.wallet-row-label { font-size: 12px; color: var(--ec-muted); padding: 2px 2px 0; }
|
|
934
1173
|
|
|
935
|
-
.wallet-switcher { width: 100%; display: flex; flex-direction: column; align-items: center; }
|
|
936
|
-
.wallet-panel { width: 100%; padding: 6px 0; display: flex; flex-direction: column; gap: 4px; }
|
|
937
1174
|
.wallet-row {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1175
|
+
display: grid;
|
|
1176
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1177
|
+
gap: 8px;
|
|
1178
|
+
}
|
|
1179
|
+
.row-tile {
|
|
1180
|
+
display: flex;
|
|
1181
|
+
flex-direction: column;
|
|
1182
|
+
align-items: center;
|
|
1183
|
+
justify-content: center;
|
|
1184
|
+
gap: 6px;
|
|
1185
|
+
padding: 10px 6px;
|
|
1186
|
+
min-width: 0;
|
|
1187
|
+
background: var(--ec-surface);
|
|
1188
|
+
border: 1px solid var(--ec-border);
|
|
1189
|
+
border-radius: 12px;
|
|
1190
|
+
color: var(--ec-text);
|
|
1191
|
+
font-size: 11px;
|
|
946
1192
|
cursor: pointer;
|
|
947
1193
|
}
|
|
948
|
-
.
|
|
1194
|
+
.row-tile:hover { border-color: var(--ec-accent); }
|
|
1195
|
+
.row-tile.selected {
|
|
1196
|
+
border-color: var(--ec-accent);
|
|
1197
|
+
background: var(--ec-accent-soft);
|
|
1198
|
+
box-shadow: inset 0 0 0 1px var(--ec-accent);
|
|
1199
|
+
}
|
|
1200
|
+
.row-tile.selected .row-tile-name { font-weight: 600; }
|
|
1201
|
+
.row-tile .wallet-icon { width: 28px; height: 28px; }
|
|
1202
|
+
.row-tile-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1203
|
+
|
|
1204
|
+
.more-tile { color: var(--ec-muted); }
|
|
1205
|
+
.more-tile:hover { color: var(--ec-text); }
|
|
1206
|
+
.more-tile[aria-expanded='true'] { border-color: var(--ec-accent); }
|
|
1207
|
+
.more-count {
|
|
1208
|
+
width: 28px;
|
|
1209
|
+
height: 28px;
|
|
1210
|
+
border-radius: 8px;
|
|
1211
|
+
display: inline-flex;
|
|
1212
|
+
align-items: center;
|
|
1213
|
+
justify-content: center;
|
|
1214
|
+
font-size: 12px;
|
|
1215
|
+
font-weight: 600;
|
|
1216
|
+
background: var(--ec-accent-soft);
|
|
1217
|
+
color: var(--ec-accent);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.wallet-panel { width: 100%; padding: 4px 0 2px; display: flex; flex-direction: column; gap: 8px; }
|
|
1221
|
+
.wallet-search {
|
|
1222
|
+
width: 100%; padding: 8px 12px;
|
|
1223
|
+
border-radius: 10px; border: 1px solid var(--ec-border); background: transparent;
|
|
1224
|
+
color: inherit; font: inherit; font-size: 13px;
|
|
1225
|
+
}
|
|
1226
|
+
.wallet-search:focus { outline: none; border-color: var(--ec-accent); }
|
|
1227
|
+
|
|
1228
|
+
.wallet-scroll { max-height: 260px; overflow-y: auto; }
|
|
1229
|
+
.wallet-grid {
|
|
1230
|
+
display: grid;
|
|
1231
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1232
|
+
gap: 8px;
|
|
1233
|
+
}
|
|
1234
|
+
.wallet-tile {
|
|
1235
|
+
aspect-ratio: 1;
|
|
1236
|
+
display: flex;
|
|
1237
|
+
flex-direction: column;
|
|
1238
|
+
align-items: center;
|
|
1239
|
+
justify-content: center;
|
|
1240
|
+
gap: 8px;
|
|
1241
|
+
padding: 8px;
|
|
1242
|
+
min-width: 0;
|
|
1243
|
+
background: var(--ec-surface);
|
|
1244
|
+
border: 1px solid var(--ec-border);
|
|
1245
|
+
border-radius: 12px;
|
|
1246
|
+
color: var(--ec-text);
|
|
1247
|
+
font-size: 12px;
|
|
1248
|
+
cursor: pointer;
|
|
1249
|
+
}
|
|
1250
|
+
.wallet-tile:hover { border-color: var(--ec-accent); }
|
|
1251
|
+
.wallet-tile.selected {
|
|
1252
|
+
border-color: var(--ec-accent);
|
|
1253
|
+
background: var(--ec-accent-soft);
|
|
1254
|
+
box-shadow: inset 0 0 0 1px var(--ec-accent);
|
|
1255
|
+
}
|
|
1256
|
+
.wallet-tile-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
949
1257
|
|
|
950
1258
|
.wallet-icon { position: relative; width: 28px; height: 28px; flex-shrink: 0; display: inline-flex; }
|
|
1259
|
+
.wallet-tile .wallet-icon { width: 40px; height: 40px; }
|
|
951
1260
|
.wallet-badge {
|
|
952
|
-
width:
|
|
1261
|
+
width: 100%; height: 100%; border-radius: 8px; display: inline-flex;
|
|
953
1262
|
align-items: center; justify-content: center; font-size: 13px;
|
|
954
|
-
font-weight: 600; background:
|
|
1263
|
+
font-weight: 600; background: var(--ec-accent-soft); color: var(--ec-accent);
|
|
955
1264
|
}
|
|
956
|
-
.wallet-
|
|
957
|
-
.wallet-
|
|
958
|
-
.wallet-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
}
|
|
963
|
-
.wallet-search:focus { outline: none; border-color: ${accent}; }
|
|
964
|
-
.wallet-empty { padding: 8px 10px; font-size: 12px; opacity: 0.7; }
|
|
965
|
-
.url-error { padding: 4px 2px 0; font-size: 12px; color: ${danger}; }
|
|
966
|
-
.wallet-custom { display: flex; gap: 6px; margin-top: 4px; }
|
|
1265
|
+
.wallet-tile .wallet-badge { border-radius: 10px; font-size: 17px; }
|
|
1266
|
+
.wallet-img { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 8px; object-fit: cover; }
|
|
1267
|
+
.wallet-tile .wallet-img { border-radius: 10px; }
|
|
1268
|
+
|
|
1269
|
+
.wallet-empty { padding: 10px 4px; font-size: 12px; color: var(--ec-muted); text-align: center; }
|
|
1270
|
+
|
|
1271
|
+
.wallet-custom { display: flex; gap: 6px; }
|
|
967
1272
|
.url-input {
|
|
968
1273
|
flex: 1;
|
|
969
1274
|
padding: 8px 12px;
|
|
970
|
-
border: 1px solid
|
|
1275
|
+
border: 1px solid var(--ec-border);
|
|
971
1276
|
border-radius: 10px;
|
|
972
1277
|
font-size: 13px;
|
|
973
|
-
background:
|
|
974
|
-
color:
|
|
1278
|
+
background: var(--ec-surface);
|
|
1279
|
+
color: var(--ec-text);
|
|
975
1280
|
outline: none;
|
|
976
1281
|
}
|
|
977
|
-
.url-input:focus { border-color:
|
|
978
|
-
.url-input.invalid { border-color:
|
|
1282
|
+
.url-input:focus { border-color: var(--ec-accent); }
|
|
1283
|
+
.url-input.invalid { border-color: var(--ec-danger); }
|
|
979
1284
|
.url-go {
|
|
980
1285
|
padding: 8px 14px;
|
|
981
1286
|
border: none;
|
|
982
1287
|
border-radius: 10px;
|
|
983
|
-
background:
|
|
984
|
-
color:
|
|
1288
|
+
background: var(--ec-accent);
|
|
1289
|
+
color: var(--ec-accent-contrast);
|
|
985
1290
|
font-size: 13px;
|
|
986
1291
|
cursor: pointer;
|
|
987
1292
|
}
|
|
988
|
-
|
|
989
|
-
|
|
1293
|
+
.url-error { font-size: 12px; color: var(--ec-danger); }
|
|
1294
|
+
|
|
1295
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1296
|
+
.overlay, .modal, .stage.fade-in > *, .pin-row.shake { animation: none; }
|
|
1297
|
+
.claimed-pulse::after { animation: none; }
|
|
1298
|
+
}
|
|
1299
|
+
`;
|
|
990
1300
|
//# sourceMappingURL=connect-modal.js.map
|