@enbox/browser 0.3.44 → 0.3.46
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 +172 -82
- 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/relay-connect-runner.js +1 -0
- package/dist/esm/relay-connect-runner.js.map +1 -1
- package/dist/esm/ui/connect-modal.js +318 -124
- 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/relay-connect-runner.d.ts +6 -0
- package/dist/types/relay-connect-runner.d.ts.map +1 -1
- package/dist/types/ui/connect-modal.d.ts +56 -3
- package/dist/types/ui/connect-modal.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/browser-connect-handler.ts +11 -1
- package/src/index.ts +1 -1
- package/src/relay-connect-runner.ts +8 -0
- package/src/ui/connect-modal.ts +393 -126
|
@@ -13,9 +13,18 @@
|
|
|
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 alternate-method link and the wallet
|
|
17
|
-
* disclosure (
|
|
18
|
-
* demoted to an option).
|
|
16
|
+
* 3. **Footer** — one row holding the alternate-method link and the wallet
|
|
17
|
+
* switcher disclosure (a tile grid with search + custom URL — the
|
|
18
|
+
* entire legacy selector, demoted to an option).
|
|
19
|
+
*
|
|
20
|
+
* Theming: the stylesheet is static and reads `--ec-*` design tokens.
|
|
21
|
+
* Light/dark follows the visitor's system via `prefers-color-scheme` and
|
|
22
|
+
* updates live; the embedding app can force an appearance or override
|
|
23
|
+
* palette tokens through {@link ConnectModalOptions.theme}. Overrides are
|
|
24
|
+
* applied as inline custom properties — never interpolated into the
|
|
25
|
+
* stylesheet — so a hostile string can't escape its declaration. The QR
|
|
26
|
+
* card alone is theme-invariant (dark modules on white scan most
|
|
27
|
+
* reliably).
|
|
19
28
|
*
|
|
20
29
|
* The phone path drives the `@enbox/connect` relay transport via
|
|
21
30
|
* {@link runRelayConnect}; the browser path drives the popup transport via
|
|
@@ -48,6 +57,20 @@ const REMINT_SAFETY_MS = 30000;
|
|
|
48
57
|
const REMINT_MIN_MS = 10000;
|
|
49
58
|
/** Pairing code length (matches the wallet's generated PIN). */
|
|
50
59
|
const PIN_LENGTH = 4;
|
|
60
|
+
/** Catalog size beyond which the wallet switcher shows its search bar. */
|
|
61
|
+
const WALLET_SEARCH_THRESHOLD = 4; // one full row of tiles
|
|
62
|
+
/** Design-token custom property behind each palette key. */
|
|
63
|
+
const PALETTE_TOKENS = {
|
|
64
|
+
background: '--ec-bg',
|
|
65
|
+
surface: '--ec-surface',
|
|
66
|
+
text: '--ec-text',
|
|
67
|
+
textMuted: '--ec-muted',
|
|
68
|
+
border: '--ec-border',
|
|
69
|
+
accent: '--ec-accent',
|
|
70
|
+
accentContrast: '--ec-accent-contrast',
|
|
71
|
+
danger: '--ec-danger',
|
|
72
|
+
success: '--ec-success',
|
|
73
|
+
};
|
|
51
74
|
/**
|
|
52
75
|
* Resolves a wallet origin to its relay `connectServerUrl` via the wallet's
|
|
53
76
|
* `/.well-known/enbox-connect` document ({@link fetchWalletWellKnown} — the
|
|
@@ -158,14 +181,13 @@ export function runConnectModal(options) {
|
|
|
158
181
|
// Method resolution order: explicit option → remembered → phone.
|
|
159
182
|
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
183
|
return new Promise((resolve, reject) => {
|
|
161
|
-
var _a, _b;
|
|
184
|
+
var _a, _b, _c, _d;
|
|
162
185
|
// ── Host + shadow root ─────────────────────────────────────
|
|
163
186
|
const host = document.createElement('div');
|
|
164
187
|
host.id = 'enbox-connect-modal';
|
|
165
188
|
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
189
|
const style = document.createElement('style');
|
|
168
|
-
style.textContent =
|
|
190
|
+
style.textContent = MODAL_STYLES;
|
|
169
191
|
shadow.appendChild(style);
|
|
170
192
|
const overlay = document.createElement('div');
|
|
171
193
|
overlay.className = 'overlay';
|
|
@@ -174,6 +196,42 @@ export function runConnectModal(options) {
|
|
|
174
196
|
modal.setAttribute('role', 'dialog');
|
|
175
197
|
modal.setAttribute('aria-modal', 'true');
|
|
176
198
|
modal.setAttribute('aria-label', `Connect to ${appName}`);
|
|
199
|
+
// ── Theme ──────────────────────────────────────────────────
|
|
200
|
+
// System light/dark is pure CSS (`prefers-color-scheme`), so it tracks
|
|
201
|
+
// the visitor live. A forced appearance pins the token block instead.
|
|
202
|
+
const theme = (_a = options.theme) !== null && _a !== void 0 ? _a : {};
|
|
203
|
+
const appearance = (_b = theme.appearance) !== null && _b !== void 0 ? _b : 'auto';
|
|
204
|
+
if (appearance !== 'auto') {
|
|
205
|
+
modal.setAttribute('data-appearance', appearance);
|
|
206
|
+
}
|
|
207
|
+
// Dapp palette overrides land as inline custom properties: setProperty
|
|
208
|
+
// parses each value as a lone declaration, so a hostile string cannot
|
|
209
|
+
// escape into the stylesheet.
|
|
210
|
+
const systemDark = (_c = window.matchMedia) === null || _c === void 0 ? void 0 : _c.call(window, '(prefers-color-scheme: dark)');
|
|
211
|
+
const hasPaletteOverrides = theme.accent !== undefined || theme.accentContrast !== undefined
|
|
212
|
+
|| theme.light !== undefined || theme.dark !== undefined;
|
|
213
|
+
const applyPaletteTokens = () => {
|
|
214
|
+
const scheme = appearance === 'auto'
|
|
215
|
+
? ((systemDark === null || systemDark === void 0 ? void 0 : systemDark.matches) === true ? 'dark' : 'light')
|
|
216
|
+
: appearance;
|
|
217
|
+
const palette = Object.assign({ accent: theme.accent, accentContrast: theme.accentContrast }, theme[scheme]);
|
|
218
|
+
for (const key of Object.keys(PALETTE_TOKENS)) {
|
|
219
|
+
const value = palette[key];
|
|
220
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
221
|
+
modal.style.setProperty(PALETTE_TOKENS[key], value);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
modal.style.removeProperty(PALETTE_TOKENS[key]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const onSchemeChange = () => applyPaletteTokens();
|
|
229
|
+
if (hasPaletteOverrides) {
|
|
230
|
+
applyPaletteTokens();
|
|
231
|
+
if (appearance === 'auto') {
|
|
232
|
+
(_d = systemDark === null || systemDark === void 0 ? void 0 : systemDark.addEventListener) === null || _d === void 0 ? void 0 : _d.call(systemDark, 'change', onSchemeChange);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
177
235
|
// ── Session state ──────────────────────────────────────────
|
|
178
236
|
let settled = false;
|
|
179
237
|
let relaySession;
|
|
@@ -182,13 +240,15 @@ export function runConnectModal(options) {
|
|
|
182
240
|
let pinResolve;
|
|
183
241
|
const discoveryCache = new Map();
|
|
184
242
|
const cleanup = () => {
|
|
243
|
+
var _a;
|
|
185
244
|
relaySession === null || relaySession === void 0 ? void 0 : relaySession.cancel();
|
|
186
245
|
clearTimeout(remintTimer);
|
|
187
246
|
document.removeEventListener('keydown', onKeydown);
|
|
247
|
+
(_a = systemDark === null || systemDark === void 0 ? void 0 : systemDark.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(systemDark, 'change', onSchemeChange);
|
|
188
248
|
try {
|
|
189
249
|
document.body.removeChild(host);
|
|
190
250
|
}
|
|
191
|
-
catch ( /* best effort */
|
|
251
|
+
catch ( /* best effort */_b) { /* best effort */ }
|
|
192
252
|
};
|
|
193
253
|
const settle = (settler) => {
|
|
194
254
|
if (settled) {
|
|
@@ -237,12 +297,19 @@ export function runConnectModal(options) {
|
|
|
237
297
|
}
|
|
238
298
|
const qrBox = document.createElement('div');
|
|
239
299
|
qrBox.className = 'qr-box';
|
|
300
|
+
// Fixed colors on the white card: dark-on-light QRs scan reliably in
|
|
301
|
+
// both appearances (inverted codes trip up some camera apps).
|
|
240
302
|
qrBox.replaceChildren(qrToSvg(encodeQr(handoff.walletUri), {
|
|
241
|
-
dark:
|
|
303
|
+
dark: '#14141f',
|
|
242
304
|
light: 'transparent',
|
|
243
305
|
quietZone: 2,
|
|
244
306
|
}));
|
|
245
|
-
setStage(qrBox, el('p', 'stage-caption', 'Scan with your phone’s camera'), el('p', 'stage-subline', 'Your wallet stays on your phone
|
|
307
|
+
setStage(qrBox, el('p', 'stage-caption', 'Scan with your phone’s camera'), el('p', 'stage-subline', 'Your wallet stays on your phone.'));
|
|
308
|
+
};
|
|
309
|
+
const renderClaimed = () => {
|
|
310
|
+
const pulse = document.createElement('div');
|
|
311
|
+
pulse.className = 'claimed-pulse';
|
|
312
|
+
setStage(pulse, el('p', 'stage-caption', 'Phone connected — finish there'), el('p', 'stage-subline', 'Approve the request on your phone, then come back here.'));
|
|
246
313
|
};
|
|
247
314
|
const renderPin = (attempt, previousError) => {
|
|
248
315
|
return new Promise((resolvePin) => {
|
|
@@ -388,6 +455,14 @@ export function runConnectModal(options) {
|
|
|
388
455
|
scheduleRemint(handoff.expiresIn);
|
|
389
456
|
}
|
|
390
457
|
},
|
|
458
|
+
onClaimed: () => {
|
|
459
|
+
if (session.active && !settled) {
|
|
460
|
+
// The phone has the request — stop re-minting (a new pointer
|
|
461
|
+
// would orphan the approval in progress) and show progress.
|
|
462
|
+
clearTimeout(remintTimer);
|
|
463
|
+
renderClaimed();
|
|
464
|
+
}
|
|
465
|
+
},
|
|
391
466
|
requestPin: (attempt, previousError) => {
|
|
392
467
|
clearTimeout(remintTimer);
|
|
393
468
|
return renderPin(attempt, previousError);
|
|
@@ -486,59 +561,69 @@ export function runConnectModal(options) {
|
|
|
486
561
|
method = next;
|
|
487
562
|
yield startMethod();
|
|
488
563
|
});
|
|
489
|
-
// ── Footer: method link + wallet
|
|
564
|
+
// ── Footer: one row (method link + wallet toggle), panel below ──
|
|
490
565
|
const renderFooter = () => {
|
|
491
566
|
footer.replaceChildren();
|
|
567
|
+
const row = document.createElement('div');
|
|
568
|
+
row.className = 'footer-row';
|
|
492
569
|
if (method === 'phone') {
|
|
493
570
|
const alt = document.createElement('button');
|
|
494
571
|
alt.className = 'footer-link method-link';
|
|
495
|
-
alt.textContent = 'No phone
|
|
572
|
+
alt.textContent = 'No phone? Use this browser →';
|
|
496
573
|
// startPopup runs synchronously in this click handler.
|
|
497
574
|
alt.addEventListener('click', () => { startPopup(); });
|
|
498
|
-
|
|
575
|
+
row.appendChild(alt);
|
|
499
576
|
}
|
|
500
577
|
else if (!isMobile) {
|
|
501
578
|
const alt = document.createElement('button');
|
|
502
579
|
alt.className = 'footer-link method-link';
|
|
503
580
|
alt.textContent = 'Use your phone instead →';
|
|
504
581
|
alt.addEventListener('click', () => { void switchMethod('phone'); });
|
|
505
|
-
|
|
582
|
+
row.appendChild(alt);
|
|
506
583
|
}
|
|
584
|
+
let panel;
|
|
507
585
|
if (!lockedWallet && wallets.length > 0) {
|
|
508
|
-
|
|
586
|
+
const switcher = buildWalletSwitcher();
|
|
587
|
+
row.appendChild(switcher.toggle);
|
|
588
|
+
panel = switcher.panel;
|
|
589
|
+
}
|
|
590
|
+
if (row.childElementCount > 0) {
|
|
591
|
+
footer.appendChild(row);
|
|
592
|
+
}
|
|
593
|
+
if (panel !== undefined) {
|
|
594
|
+
footer.appendChild(panel);
|
|
509
595
|
}
|
|
510
596
|
};
|
|
511
597
|
const buildWalletSwitcher = () => {
|
|
512
598
|
var _a, _b;
|
|
513
|
-
const wrap = document.createElement('div');
|
|
514
|
-
wrap.className = 'wallet-switcher';
|
|
515
599
|
const current = wallets.find((wallet) => wallet.url === walletUrl);
|
|
516
600
|
const toggle = document.createElement('button');
|
|
517
601
|
toggle.className = 'footer-link wallet-toggle';
|
|
518
602
|
toggle.setAttribute('aria-expanded', 'false');
|
|
519
|
-
toggle.textContent = `Wallet: ${(_b = (_a = current === null || current === void 0 ? void 0 : current.name) !== null && _a !== void 0 ? _a : walletUrl) !== null && _b !== void 0 ? _b : 'Choose'} ▾`;
|
|
603
|
+
toggle.textContent = `Wallet: ${(_b = (_a = current === null || current === void 0 ? void 0 : current.name) !== null && _a !== void 0 ? _a : hostnameOf(walletUrl)) !== null && _b !== void 0 ? _b : 'Choose'} ▾`;
|
|
520
604
|
const panel = document.createElement('div');
|
|
521
605
|
panel.className = 'wallet-panel';
|
|
522
606
|
panel.hidden = true;
|
|
523
|
-
// Search filter —
|
|
524
|
-
const
|
|
607
|
+
// Search filter — appears once the catalog outgrows one grid row.
|
|
608
|
+
const tiles = [];
|
|
525
609
|
const empty = document.createElement('div');
|
|
526
610
|
empty.className = 'wallet-empty';
|
|
527
611
|
empty.textContent = 'No wallets match your search.';
|
|
528
612
|
empty.hidden = true;
|
|
529
|
-
if (wallets.length >
|
|
613
|
+
if (wallets.length > WALLET_SEARCH_THRESHOLD) {
|
|
530
614
|
const search = document.createElement('input');
|
|
531
615
|
search.type = 'search';
|
|
532
616
|
search.className = 'wallet-search';
|
|
533
617
|
search.placeholder = 'Search wallets…';
|
|
618
|
+
search.setAttribute('aria-label', 'Search wallets');
|
|
534
619
|
search.addEventListener('input', () => {
|
|
535
620
|
const query = search.value.trim().toLowerCase();
|
|
536
621
|
let visible = 0;
|
|
537
|
-
for (const { wallet, el } of
|
|
622
|
+
for (const { wallet, el: tile } of tiles) {
|
|
538
623
|
const match = query === ''
|
|
539
624
|
|| wallet.name.toLowerCase().includes(query)
|
|
540
625
|
|| wallet.url.toLowerCase().includes(query);
|
|
541
|
-
|
|
626
|
+
tile.hidden = !match;
|
|
542
627
|
if (match) {
|
|
543
628
|
visible += 1;
|
|
544
629
|
}
|
|
@@ -547,26 +632,36 @@ export function runConnectModal(options) {
|
|
|
547
632
|
});
|
|
548
633
|
panel.appendChild(search);
|
|
549
634
|
}
|
|
635
|
+
// Square tiles, three per row; past two rows the grid scrolls in
|
|
636
|
+
// place so a large catalog never stretches the modal.
|
|
637
|
+
const scroll = document.createElement('div');
|
|
638
|
+
scroll.className = 'wallet-scroll';
|
|
639
|
+
const grid = document.createElement('div');
|
|
640
|
+
grid.className = 'wallet-grid';
|
|
550
641
|
for (const wallet of wallets) {
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
642
|
+
const tile = document.createElement('button');
|
|
643
|
+
tile.className = 'wallet-tile';
|
|
644
|
+
tile.title = wallet.name;
|
|
645
|
+
tile.appendChild(buildWalletIcon(wallet));
|
|
646
|
+
const tileName = document.createElement('span');
|
|
647
|
+
tileName.className = 'wallet-tile-name';
|
|
648
|
+
tileName.textContent = wallet.name;
|
|
649
|
+
tile.appendChild(tileName);
|
|
558
650
|
if (wallet.url === walletUrl) {
|
|
559
|
-
|
|
651
|
+
tile.classList.add('selected');
|
|
652
|
+
tile.setAttribute('aria-pressed', 'true');
|
|
560
653
|
}
|
|
561
|
-
|
|
654
|
+
tile.addEventListener('click', () => {
|
|
562
655
|
walletUrl = wallet.url;
|
|
563
656
|
renderFooter();
|
|
564
657
|
void startMethod();
|
|
565
658
|
});
|
|
566
|
-
|
|
567
|
-
|
|
659
|
+
tiles.push({ wallet, el: tile });
|
|
660
|
+
grid.appendChild(tile);
|
|
568
661
|
}
|
|
569
|
-
|
|
662
|
+
scroll.appendChild(grid);
|
|
663
|
+
scroll.appendChild(empty);
|
|
664
|
+
panel.appendChild(scroll);
|
|
570
665
|
// Custom wallet URL (the power-user path, demoted to the disclosure).
|
|
571
666
|
const custom = document.createElement('div');
|
|
572
667
|
custom.className = 'wallet-custom';
|
|
@@ -642,9 +737,7 @@ export function runConnectModal(options) {
|
|
|
642
737
|
panel.hidden = !panel.hidden;
|
|
643
738
|
toggle.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
|
|
644
739
|
});
|
|
645
|
-
|
|
646
|
-
wrap.appendChild(panel);
|
|
647
|
-
return wrap;
|
|
740
|
+
return { toggle, panel };
|
|
648
741
|
};
|
|
649
742
|
// ── Global interactions ────────────────────────────────────
|
|
650
743
|
overlay.addEventListener('click', (event) => {
|
|
@@ -673,6 +766,18 @@ export function runConnectModal(options) {
|
|
|
673
766
|
function walletInCatalog(wallets, url) {
|
|
674
767
|
return wallets.some((wallet) => wallet.url === url);
|
|
675
768
|
}
|
|
769
|
+
/** Compact display form of a wallet URL for the switcher toggle. */
|
|
770
|
+
function hostnameOf(url) {
|
|
771
|
+
if (url === undefined) {
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
return new URL(url).hostname;
|
|
776
|
+
}
|
|
777
|
+
catch (_a) {
|
|
778
|
+
return url;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
676
781
|
function el(tag, className, text) {
|
|
677
782
|
const node = document.createElement(tag);
|
|
678
783
|
node.className = className;
|
|
@@ -740,19 +845,42 @@ function trapFocus(shadow, event) {
|
|
|
740
845
|
}
|
|
741
846
|
}
|
|
742
847
|
// ─── Styles ──────────────────────────────────────────────────────
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
848
|
+
//
|
|
849
|
+
// One static sheet; every color reads a `--ec-*` token. Light values sit on
|
|
850
|
+
// `.modal`, dark values apply under `prefers-color-scheme` (so the system
|
|
851
|
+
// appearance tracks live) and under a forced `data-appearance`. Dapp
|
|
852
|
+
// palette overrides land as inline custom properties, which outrank all of
|
|
853
|
+
// these — nothing user-provided is ever concatenated into this string.
|
|
854
|
+
/** Light-appearance design tokens. */
|
|
855
|
+
const LIGHT_TOKENS = `
|
|
856
|
+
--ec-bg: #ffffff;
|
|
857
|
+
--ec-surface: #f5f6f8;
|
|
858
|
+
--ec-text: #17171f;
|
|
859
|
+
--ec-muted: #63636e;
|
|
860
|
+
--ec-border: #e3e4e9;
|
|
861
|
+
--ec-accent: #0a62d0;
|
|
862
|
+
--ec-accent-contrast: #ffffff;
|
|
863
|
+
--ec-danger: #c0392b;
|
|
864
|
+
--ec-success: #1e9e5a;`;
|
|
865
|
+
/** Dark-appearance design tokens. */
|
|
866
|
+
const DARK_TOKENS = `
|
|
867
|
+
--ec-bg: #181822;
|
|
868
|
+
--ec-surface: #23232f;
|
|
869
|
+
--ec-text: #e9e9f0;
|
|
870
|
+
--ec-muted: #9a9aa6;
|
|
871
|
+
--ec-border: #33333f;
|
|
872
|
+
--ec-accent: #58a6ff;
|
|
873
|
+
--ec-accent-contrast: #0b1526;
|
|
874
|
+
--ec-danger: #ff7a7a;
|
|
875
|
+
--ec-success: #43d17c;`;
|
|
876
|
+
const MODAL_STYLES = `
|
|
754
877
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
755
878
|
|
|
879
|
+
/* Keep the hidden attribute authoritative: author display values (e.g.
|
|
880
|
+
the panel's flex) would otherwise override the UA's [hidden] rule and
|
|
881
|
+
leave "collapsed" sections permanently expanded. */
|
|
882
|
+
[hidden] { display: none !important; }
|
|
883
|
+
|
|
756
884
|
.overlay {
|
|
757
885
|
position: fixed;
|
|
758
886
|
inset: 0;
|
|
@@ -760,6 +888,7 @@ function buildStyles(isDark) {
|
|
|
760
888
|
display: flex;
|
|
761
889
|
align-items: center;
|
|
762
890
|
justify-content: center;
|
|
891
|
+
padding: 16px;
|
|
763
892
|
background: rgba(0, 0, 0, 0.5);
|
|
764
893
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
765
894
|
animation: fadeIn 0.15s ease-out;
|
|
@@ -774,66 +903,96 @@ function buildStyles(isDark) {
|
|
|
774
903
|
40%, 80% { transform: translateX(6px); }
|
|
775
904
|
}
|
|
776
905
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
906
|
+
@keyframes claimedPulse {
|
|
907
|
+
0%, 100% { transform: scale(0.85); opacity: 0.7; }
|
|
908
|
+
50% { transform: scale(1); opacity: 1; }
|
|
909
|
+
}
|
|
777
910
|
|
|
778
|
-
.modal {
|
|
779
|
-
|
|
780
|
-
|
|
911
|
+
.modal {${LIGHT_TOKENS}
|
|
912
|
+
--ec-accent-soft: color-mix(in srgb, var(--ec-accent) 14%, transparent);
|
|
913
|
+
background: var(--ec-bg);
|
|
914
|
+
color: var(--ec-text);
|
|
781
915
|
border-radius: 16px;
|
|
782
916
|
width: 400px;
|
|
783
|
-
max-width:
|
|
784
|
-
max-height:
|
|
917
|
+
max-width: 100%;
|
|
918
|
+
max-height: calc(100vh - 32px);
|
|
785
919
|
overflow-y: auto;
|
|
786
|
-
padding: 20px
|
|
787
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.
|
|
920
|
+
padding: 16px 20px 12px;
|
|
921
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
|
|
788
922
|
animation: slideUp 0.2s ease-out;
|
|
789
923
|
display: flex;
|
|
790
924
|
flex-direction: column;
|
|
791
925
|
}
|
|
926
|
+
@media (prefers-color-scheme: dark) {
|
|
927
|
+
.modal {${DARK_TOKENS}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
.modal[data-appearance='light'] {${LIGHT_TOKENS}
|
|
931
|
+
}
|
|
932
|
+
.modal[data-appearance='dark'] {${DARK_TOKENS}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
button, input { font-family: inherit; }
|
|
936
|
+
button:focus-visible, a:focus-visible {
|
|
937
|
+
outline: 2px solid var(--ec-accent);
|
|
938
|
+
outline-offset: 2px;
|
|
939
|
+
}
|
|
792
940
|
|
|
793
|
-
.header { display: flex; justify-content: space-between; align-items: center; }
|
|
941
|
+
.header { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
794
942
|
.header-identity { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
795
|
-
.header-icon { border-radius: 6px; }
|
|
943
|
+
.header-icon { border-radius: 6px; flex-shrink: 0; }
|
|
796
944
|
h2 { font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
797
945
|
|
|
798
946
|
.close-btn {
|
|
799
947
|
background: none; border: none; font-size: 24px; cursor: pointer;
|
|
800
|
-
color:
|
|
948
|
+
color: var(--ec-muted); padding: 4px 8px; border-radius: 8px; line-height: 1;
|
|
949
|
+
flex-shrink: 0;
|
|
801
950
|
}
|
|
802
|
-
.close-btn:hover { color:
|
|
951
|
+
.close-btn:hover { color: var(--ec-text); background: var(--ec-surface); }
|
|
803
952
|
|
|
804
953
|
.stage {
|
|
805
|
-
min-height:
|
|
954
|
+
min-height: 240px;
|
|
806
955
|
display: flex;
|
|
807
956
|
flex-direction: column;
|
|
808
957
|
align-items: center;
|
|
809
958
|
justify-content: center;
|
|
810
|
-
gap:
|
|
959
|
+
gap: 10px;
|
|
811
960
|
text-align: center;
|
|
812
|
-
padding:
|
|
961
|
+
padding: 12px 0 10px;
|
|
813
962
|
}
|
|
814
963
|
.stage.fade-in > * { animation: stageIn 0.15s ease-out; }
|
|
815
964
|
|
|
816
965
|
.qr-box {
|
|
817
|
-
width:
|
|
818
|
-
height:
|
|
819
|
-
padding:
|
|
966
|
+
width: 200px;
|
|
967
|
+
height: 200px;
|
|
968
|
+
padding: 10px;
|
|
820
969
|
border-radius: 12px;
|
|
821
|
-
background:
|
|
822
|
-
border: 1px solid
|
|
970
|
+
background: #ffffff;
|
|
971
|
+
border: 1px solid #e3e4e9;
|
|
823
972
|
}
|
|
824
973
|
.qr-box svg { width: 100%; height: 100%; display: block; }
|
|
825
974
|
|
|
975
|
+
.claimed-pulse {
|
|
976
|
+
width: 64px; height: 64px; border-radius: 50%;
|
|
977
|
+
background: var(--ec-accent-soft); position: relative;
|
|
978
|
+
}
|
|
979
|
+
.claimed-pulse::after {
|
|
980
|
+
content: ''; position: absolute; inset: 18px; border-radius: 50%;
|
|
981
|
+
background: var(--ec-accent);
|
|
982
|
+
animation: claimedPulse 1.6s ease-in-out infinite;
|
|
983
|
+
}
|
|
984
|
+
|
|
826
985
|
.stage-caption { font-size: 15px; font-weight: 600; }
|
|
827
|
-
.stage-subline { font-size: 13px; color:
|
|
828
|
-
.stage-error { font-size: 13px; color:
|
|
986
|
+
.stage-subline { font-size: 13px; color: var(--ec-muted); max-width: 300px; line-height: 1.45; }
|
|
987
|
+
.stage-error { font-size: 13px; color: var(--ec-danger); }
|
|
829
988
|
|
|
830
989
|
.deep-link {
|
|
831
990
|
display: block;
|
|
832
991
|
width: 100%;
|
|
833
|
-
padding:
|
|
992
|
+
padding: 13px 16px;
|
|
834
993
|
border-radius: 12px;
|
|
835
|
-
background:
|
|
836
|
-
color:
|
|
994
|
+
background: var(--ec-accent);
|
|
995
|
+
color: var(--ec-accent-contrast);
|
|
837
996
|
font-weight: 600;
|
|
838
997
|
font-size: 15px;
|
|
839
998
|
text-decoration: none;
|
|
@@ -842,31 +1001,31 @@ function buildStyles(isDark) {
|
|
|
842
1001
|
.pin-row { display: flex; gap: 10px; }
|
|
843
1002
|
.pin-row.shake { animation: shake 0.4s ease-in-out; }
|
|
844
1003
|
.pin-input {
|
|
845
|
-
width:
|
|
1004
|
+
width: 46px; height: 54px;
|
|
846
1005
|
text-align: center;
|
|
847
1006
|
font-size: 24px; font-weight: 600;
|
|
848
|
-
color:
|
|
849
|
-
background:
|
|
850
|
-
border: 1px solid
|
|
1007
|
+
color: var(--ec-text);
|
|
1008
|
+
background: var(--ec-surface);
|
|
1009
|
+
border: 1px solid var(--ec-border);
|
|
851
1010
|
border-radius: 10px;
|
|
852
1011
|
outline: none;
|
|
853
1012
|
}
|
|
854
|
-
.pin-input:focus { border-color:
|
|
1013
|
+
.pin-input:focus { border-color: var(--ec-accent); }
|
|
855
1014
|
|
|
856
1015
|
.check {
|
|
857
1016
|
width: 56px; height: 56px;
|
|
858
1017
|
display: flex; align-items: center; justify-content: center;
|
|
859
1018
|
border-radius: 50%;
|
|
860
|
-
background:
|
|
861
|
-
color: #
|
|
1019
|
+
background: var(--ec-success);
|
|
1020
|
+
color: #ffffff;
|
|
862
1021
|
font-size: 28px;
|
|
863
1022
|
}
|
|
864
1023
|
|
|
865
1024
|
.spinner {
|
|
866
1025
|
width: 32px; height: 32px;
|
|
867
1026
|
border-radius: 50%;
|
|
868
|
-
border: 3px solid
|
|
869
|
-
border-top-color:
|
|
1027
|
+
border: 3px solid var(--ec-border);
|
|
1028
|
+
border-top-color: var(--ec-accent);
|
|
870
1029
|
animation: spin 0.8s linear infinite;
|
|
871
1030
|
}
|
|
872
1031
|
|
|
@@ -874,88 +1033,123 @@ function buildStyles(isDark) {
|
|
|
874
1033
|
padding: 10px 20px;
|
|
875
1034
|
border: none;
|
|
876
1035
|
border-radius: 10px;
|
|
877
|
-
background:
|
|
878
|
-
color:
|
|
1036
|
+
background: var(--ec-accent);
|
|
1037
|
+
color: var(--ec-accent-contrast);
|
|
879
1038
|
font-size: 14px;
|
|
880
1039
|
font-weight: 500;
|
|
881
1040
|
cursor: pointer;
|
|
882
1041
|
}
|
|
883
|
-
.stage-btn:hover { filter: brightness(1.
|
|
1042
|
+
.stage-btn:hover { filter: brightness(1.08); }
|
|
884
1043
|
|
|
885
1044
|
.footer {
|
|
886
|
-
border-top: 1px solid
|
|
887
|
-
padding-top:
|
|
1045
|
+
border-top: 1px solid var(--ec-border);
|
|
1046
|
+
padding-top: 8px;
|
|
888
1047
|
display: flex;
|
|
889
1048
|
flex-direction: column;
|
|
890
1049
|
gap: 6px;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.footer-row {
|
|
1053
|
+
display: flex;
|
|
891
1054
|
align-items: center;
|
|
1055
|
+
justify-content: space-between;
|
|
1056
|
+
flex-wrap: wrap;
|
|
1057
|
+
gap: 2px 8px;
|
|
892
1058
|
}
|
|
1059
|
+
.footer-row > * { min-width: 0; }
|
|
1060
|
+
.footer-row > :only-child { margin-inline: auto; }
|
|
893
1061
|
|
|
894
1062
|
.footer-link {
|
|
895
1063
|
background: none;
|
|
896
1064
|
border: none;
|
|
897
|
-
color:
|
|
1065
|
+
color: var(--ec-muted);
|
|
898
1066
|
font-size: 13px;
|
|
899
1067
|
cursor: pointer;
|
|
900
|
-
padding:
|
|
1068
|
+
padding: 5px 8px;
|
|
901
1069
|
border-radius: 8px;
|
|
902
1070
|
}
|
|
903
|
-
.footer-link:hover { color:
|
|
904
|
-
.method-link { color:
|
|
1071
|
+
.footer-link:hover { color: var(--ec-text); background: var(--ec-surface); }
|
|
1072
|
+
.method-link { color: var(--ec-accent); }
|
|
1073
|
+
.wallet-toggle { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
905
1074
|
|
|
906
|
-
.wallet-
|
|
907
|
-
.wallet-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1075
|
+
.wallet-panel { width: 100%; padding: 4px 0 2px; display: flex; flex-direction: column; gap: 8px; }
|
|
1076
|
+
.wallet-search {
|
|
1077
|
+
width: 100%; padding: 8px 12px;
|
|
1078
|
+
border-radius: 10px; border: 1px solid var(--ec-border); background: transparent;
|
|
1079
|
+
color: inherit; font: inherit; font-size: 13px;
|
|
1080
|
+
}
|
|
1081
|
+
.wallet-search:focus { outline: none; border-color: var(--ec-accent); }
|
|
1082
|
+
|
|
1083
|
+
.wallet-scroll { max-height: 260px; overflow-y: auto; }
|
|
1084
|
+
.wallet-grid {
|
|
1085
|
+
display: grid;
|
|
1086
|
+
grid-template-columns: repeat(3, 1fr);
|
|
1087
|
+
gap: 8px;
|
|
1088
|
+
}
|
|
1089
|
+
.wallet-tile {
|
|
1090
|
+
aspect-ratio: 1;
|
|
1091
|
+
display: flex;
|
|
1092
|
+
flex-direction: column;
|
|
1093
|
+
align-items: center;
|
|
1094
|
+
justify-content: center;
|
|
1095
|
+
gap: 8px;
|
|
1096
|
+
padding: 8px;
|
|
1097
|
+
min-width: 0;
|
|
1098
|
+
background: var(--ec-surface);
|
|
1099
|
+
border: 1px solid var(--ec-border);
|
|
1100
|
+
border-radius: 12px;
|
|
1101
|
+
color: var(--ec-text);
|
|
1102
|
+
font-size: 12px;
|
|
917
1103
|
cursor: pointer;
|
|
918
1104
|
}
|
|
919
|
-
.wallet-
|
|
1105
|
+
.wallet-tile:hover { border-color: var(--ec-accent); }
|
|
1106
|
+
.wallet-tile.selected {
|
|
1107
|
+
border-color: var(--ec-accent);
|
|
1108
|
+
background: var(--ec-accent-soft);
|
|
1109
|
+
box-shadow: inset 0 0 0 1px var(--ec-accent);
|
|
1110
|
+
}
|
|
1111
|
+
.wallet-tile-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
920
1112
|
|
|
921
1113
|
.wallet-icon { position: relative; width: 28px; height: 28px; flex-shrink: 0; display: inline-flex; }
|
|
1114
|
+
.wallet-tile .wallet-icon { width: 40px; height: 40px; }
|
|
922
1115
|
.wallet-badge {
|
|
923
|
-
width:
|
|
1116
|
+
width: 100%; height: 100%; border-radius: 8px; display: inline-flex;
|
|
924
1117
|
align-items: center; justify-content: center; font-size: 13px;
|
|
925
|
-
font-weight: 600; background:
|
|
1118
|
+
font-weight: 600; background: var(--ec-accent-soft); color: var(--ec-accent);
|
|
926
1119
|
}
|
|
927
|
-
.wallet-
|
|
928
|
-
.wallet-
|
|
929
|
-
.wallet-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
.wallet-search:focus { outline: none; border-color: ${accent}; }
|
|
935
|
-
.wallet-empty { padding: 8px 10px; font-size: 12px; opacity: 0.7; }
|
|
936
|
-
.url-error { padding: 4px 2px 0; font-size: 12px; color: ${danger}; }
|
|
937
|
-
.wallet-custom { display: flex; gap: 6px; margin-top: 4px; }
|
|
1120
|
+
.wallet-tile .wallet-badge { border-radius: 10px; font-size: 17px; }
|
|
1121
|
+
.wallet-img { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 8px; object-fit: cover; }
|
|
1122
|
+
.wallet-tile .wallet-img { border-radius: 10px; }
|
|
1123
|
+
|
|
1124
|
+
.wallet-empty { padding: 10px 4px; font-size: 12px; color: var(--ec-muted); text-align: center; }
|
|
1125
|
+
|
|
1126
|
+
.wallet-custom { display: flex; gap: 6px; }
|
|
938
1127
|
.url-input {
|
|
939
1128
|
flex: 1;
|
|
940
1129
|
padding: 8px 12px;
|
|
941
|
-
border: 1px solid
|
|
1130
|
+
border: 1px solid var(--ec-border);
|
|
942
1131
|
border-radius: 10px;
|
|
943
1132
|
font-size: 13px;
|
|
944
|
-
background:
|
|
945
|
-
color:
|
|
1133
|
+
background: var(--ec-surface);
|
|
1134
|
+
color: var(--ec-text);
|
|
946
1135
|
outline: none;
|
|
947
1136
|
}
|
|
948
|
-
.url-input:focus { border-color:
|
|
949
|
-
.url-input.invalid { border-color:
|
|
1137
|
+
.url-input:focus { border-color: var(--ec-accent); }
|
|
1138
|
+
.url-input.invalid { border-color: var(--ec-danger); }
|
|
950
1139
|
.url-go {
|
|
951
1140
|
padding: 8px 14px;
|
|
952
1141
|
border: none;
|
|
953
1142
|
border-radius: 10px;
|
|
954
|
-
background:
|
|
955
|
-
color:
|
|
1143
|
+
background: var(--ec-accent);
|
|
1144
|
+
color: var(--ec-accent-contrast);
|
|
956
1145
|
font-size: 13px;
|
|
957
1146
|
cursor: pointer;
|
|
958
1147
|
}
|
|
959
|
-
|
|
960
|
-
|
|
1148
|
+
.url-error { font-size: 12px; color: var(--ec-danger); }
|
|
1149
|
+
|
|
1150
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1151
|
+
.overlay, .modal, .stage.fade-in > *, .pin-row.shake { animation: none; }
|
|
1152
|
+
.claimed-pulse::after { animation: none; }
|
|
1153
|
+
}
|
|
1154
|
+
`;
|
|
961
1155
|
//# sourceMappingURL=connect-modal.js.map
|