@arcblock/did-connect-core 4.1.53 → 4.1.55

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.
@@ -381,12 +381,19 @@ var messages = {
381
381
  emailExplanation: "We will send you a verification code. No password needed.",
382
382
  otherMethods: "Other ways to sign in",
383
383
  passkeyCreateAccount: "Create an account with Passkey",
384
- lastUsed: "Last used: {method}",
384
+ lastUsed: "Last used",
385
385
  passkeyExplanation: "Create a new account using your fingerprint, face, or device unlock. Your device will ask you to save a Passkey. Already have an account? Go back to sign in.",
386
386
  continueWith: "Continue with {provider}",
387
387
  oauthWaiting: "Continue in the {provider} window",
388
388
  oauthIncomplete: "Sign-in is not complete. Reopen the window or choose another method.",
389
389
  oauthReopen: "Reopen window",
390
+ oauthUnconfirmed: "We could not confirm sign-in. Reload this page to check your session before trying again.",
391
+ oauthCheckSession: "Reload and check sign-in",
392
+ oauthCancelled: "Stopped waiting for sign-in. If the provider window is still open, close it before retrying.",
393
+ oauthDenied: "Authorization was declined. Try again or choose another sign-in method.",
394
+ oauthBlocked: "Your browser blocked the sign-in window. Reopen it or continue on this page.",
395
+ oauthTimedOut: "Sign-in timed out. Reopen the window to start again.",
396
+ oauthFailed: "We could not complete sign-in. Try again or choose another method.",
390
397
  oauthFullPage: "Continue on this page",
391
398
  emailEdit: "Change email",
392
399
  emailCooldown: "Resend in {seconds}s",
@@ -447,12 +454,19 @@ var messages = {
447
454
  emailExplanation: "\u6211\u4EEC\u4F1A\u53D1\u9001\u9A8C\u8BC1\u7801\uFF0C\u65E0\u9700\u5BC6\u7801\u3002",
448
455
  otherMethods: "\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F",
449
456
  passkeyCreateAccount: "\u4F7F\u7528 Passkey \u521B\u5EFA\u8D26\u6237",
450
- lastUsed: "\u4E0A\u6B21\u4F7F\u7528\uFF1A{method}",
457
+ lastUsed: "\u4E0A\u6B21\u4F7F\u7528",
451
458
  passkeyExplanation: "\u4F7F\u7528\u6307\u7EB9\u3001\u9762\u5BB9\u6216\u8BBE\u5907\u89E3\u9501\u65B9\u5F0F\u521B\u5EFA\u65B0\u8D26\u6237\uFF0C\u65E0\u9700\u5BC6\u7801\u3002\u8BBE\u5907\u4F1A\u63D0\u793A\u4FDD\u5B58 Passkey\u3002\u5DF2\u6709\u8D26\u6237\uFF1F\u8BF7\u8FD4\u56DE\u767B\u5F55\u3002",
452
459
  continueWith: "\u4F7F\u7528 {provider} \u7EE7\u7EED",
453
460
  oauthWaiting: "\u8BF7\u5728 {provider} \u7A97\u53E3\u4E2D\u7EE7\u7EED",
454
461
  oauthIncomplete: "\u767B\u5F55\u5C1A\u672A\u5B8C\u6210\u3002\u60A8\u53EF\u4EE5\u91CD\u65B0\u6253\u5F00\u7A97\u53E3\u6216\u9009\u62E9\u5176\u4ED6\u65B9\u5F0F\u3002",
455
462
  oauthReopen: "\u91CD\u65B0\u6253\u5F00\u7A97\u53E3",
463
+ oauthUnconfirmed: "\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u767B\u5F55\u7ED3\u679C\u3002\u8BF7\u5148\u91CD\u65B0\u52A0\u8F7D\u9875\u9762\u68C0\u67E5\u767B\u5F55\u72B6\u6001\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u91CD\u8BD5\u3002",
464
+ oauthCheckSession: "\u91CD\u65B0\u52A0\u8F7D\u5E76\u68C0\u67E5\u767B\u5F55\u72B6\u6001",
465
+ oauthCancelled: "\u5DF2\u505C\u6B62\u7B49\u5F85\u767B\u5F55\u3002\u82E5\u6388\u6743\u7A97\u53E3\u4ECD\u7136\u6253\u5F00\uFF0C\u8BF7\u5173\u95ED\u540E\u518D\u91CD\u8BD5\u3002",
466
+ oauthDenied: "\u60A8\u672A\u540C\u610F\u6388\u6743\u3002\u53EF\u4EE5\u91CD\u8BD5\u6216\u9009\u62E9\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F\u3002",
467
+ oauthBlocked: "\u6D4F\u89C8\u5668\u62E6\u622A\u4E86\u767B\u5F55\u7A97\u53E3\u3002\u8BF7\u91CD\u65B0\u6253\u5F00\uFF0C\u6216\u5728\u5F53\u524D\u9875\u9762\u7EE7\u7EED\u3002",
468
+ oauthTimedOut: "\u767B\u5F55\u7B49\u5F85\u5DF2\u8D85\u65F6\u3002\u8BF7\u91CD\u65B0\u6253\u5F00\u7A97\u53E3\u518D\u8BD5\u3002",
469
+ oauthFailed: "\u672A\u80FD\u5B8C\u6210\u767B\u5F55\u3002\u8BF7\u91CD\u8BD5\u6216\u9009\u62E9\u5176\u4ED6\u65B9\u5F0F\u3002",
456
470
  oauthFullPage: "\u5728\u5F53\u524D\u9875\u9762\u7EE7\u7EED",
457
471
  emailEdit: "\u4FEE\u6539\u90AE\u7BB1",
458
472
  emailCooldown: "{seconds} \u79D2\u540E\u91CD\u53D1",
@@ -513,12 +527,19 @@ var messages = {
513
527
  emailExplanation: "\u6211\u5011\u6703\u50B3\u9001\u9A57\u8B49\u78BC\uFF0C\u7121\u9700\u5BC6\u78BC\u3002",
514
528
  otherMethods: "\u5176\u4ED6\u767B\u5165\u65B9\u5F0F",
515
529
  passkeyCreateAccount: "\u4F7F\u7528 Passkey \u5EFA\u7ACB\u5E33\u6236",
516
- lastUsed: "\u4E0A\u6B21\u4F7F\u7528\uFF1A{method}",
530
+ lastUsed: "\u4E0A\u6B21\u4F7F\u7528",
517
531
  passkeyExplanation: "\u4F7F\u7528\u6307\u7D0B\u3001\u9762\u5BB9\u6216\u88DD\u7F6E\u89E3\u9396\u65B9\u5F0F\u5EFA\u7ACB\u65B0\u5E33\u6236\uFF0C\u7121\u9700\u5BC6\u78BC\u3002\u88DD\u7F6E\u6703\u63D0\u793A\u5132\u5B58 Passkey\u3002\u5DF2\u6709\u5E33\u6236\uFF1F\u8ACB\u8FD4\u56DE\u767B\u5165\u3002",
518
532
  continueWith: "\u4F7F\u7528 {provider} \u7E7C\u7E8C",
519
533
  oauthWaiting: "\u8ACB\u5728 {provider} \u8996\u7A97\u4E2D\u7E7C\u7E8C",
520
534
  oauthIncomplete: "\u767B\u5165\u5C1A\u672A\u5B8C\u6210\u3002\u60A8\u53EF\u4EE5\u91CD\u65B0\u958B\u555F\u8996\u7A97\u6216\u9078\u64C7\u5176\u4ED6\u65B9\u5F0F\u3002",
521
535
  oauthReopen: "\u91CD\u65B0\u958B\u555F\u8996\u7A97",
536
+ oauthUnconfirmed: "\u66AB\u6642\u7121\u6CD5\u78BA\u8A8D\u767B\u5165\u7D50\u679C\u3002\u8ACB\u5148\u91CD\u65B0\u8F09\u5165\u9801\u9762\u6AA2\u67E5\u767B\u5165\u72C0\u614B\uFF0C\u518D\u6C7A\u5B9A\u662F\u5426\u91CD\u8A66\u3002",
537
+ oauthCheckSession: "\u91CD\u65B0\u8F09\u5165\u4E26\u6AA2\u67E5\u767B\u5165\u72C0\u614B",
538
+ oauthCancelled: "\u5DF2\u505C\u6B62\u7B49\u5F85\u767B\u5165\u3002\u82E5\u6388\u6B0A\u8996\u7A97\u4ECD\u7136\u958B\u555F\uFF0C\u8ACB\u95DC\u9589\u5F8C\u518D\u91CD\u8A66\u3002",
539
+ oauthDenied: "\u60A8\u672A\u540C\u610F\u6388\u6B0A\u3002\u53EF\u4EE5\u91CD\u8A66\u6216\u9078\u64C7\u5176\u4ED6\u767B\u5165\u65B9\u5F0F\u3002",
540
+ oauthBlocked: "\u700F\u89BD\u5668\u5C01\u9396\u4E86\u767B\u5165\u8996\u7A97\u3002\u8ACB\u91CD\u65B0\u958B\u555F\uFF0C\u6216\u5728\u76EE\u524D\u9801\u9762\u7E7C\u7E8C\u3002",
541
+ oauthTimedOut: "\u767B\u5165\u7B49\u5F85\u5DF2\u903E\u6642\u3002\u8ACB\u91CD\u65B0\u958B\u555F\u8996\u7A97\u518D\u8A66\u3002",
542
+ oauthFailed: "\u672A\u80FD\u5B8C\u6210\u767B\u5165\u3002\u8ACB\u91CD\u8A66\u6216\u9078\u64C7\u5176\u4ED6\u65B9\u5F0F\u3002",
522
543
  oauthFullPage: "\u5728\u76EE\u524D\u9801\u9762\u7E7C\u7E8C",
523
544
  emailEdit: "\u4FEE\u6539\u96FB\u5B50\u90F5\u4EF6",
524
545
  emailCooldown: "{seconds} \u79D2\u5F8C\u91CD\u65B0\u50B3\u9001",
@@ -579,12 +600,19 @@ var messages = {
579
600
  emailExplanation: "\u78BA\u8A8D\u30B3\u30FC\u30C9\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u4E0D\u8981\u3067\u3059\u3002",
580
601
  otherMethods: "\u4ED6\u306E\u30ED\u30B0\u30A4\u30F3\u65B9\u6CD5",
581
602
  passkeyCreateAccount: "Passkey \u3067\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
582
- lastUsed: "\u524D\u56DE\u306E\u65B9\u6CD5\uFF1A{method}",
603
+ lastUsed: "\u524D\u56DE\u4F7F\u7528",
583
604
  passkeyExplanation: "\u6307\u7D0B\u3001\u9854\u8A8D\u8A3C\u3001\u307E\u305F\u306F\u30C7\u30D0\u30A4\u30B9\u306E\u30ED\u30C3\u30AF\u89E3\u9664\u3067\u65B0\u3057\u3044\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210\u3057\u307E\u3059\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u4E0D\u8981\u3067\u3059\u3002\u30C7\u30D0\u30A4\u30B9\u304C Passkey \u306E\u4FDD\u5B58\u3092\u6C42\u3081\u307E\u3059\u3002\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u304A\u6301\u3061\u3067\u3059\u304B\uFF1F\u30ED\u30B0\u30A4\u30F3\u306B\u623B\u3063\u3066\u304F\u3060\u3055\u3044\u3002",
584
605
  continueWith: "{provider} \u3067\u7D9A\u3051\u308B",
585
606
  oauthWaiting: "{provider} \u306E\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u7D9A\u3051\u3066\u304F\u3060\u3055\u3044",
586
607
  oauthIncomplete: "\u30ED\u30B0\u30A4\u30F3\u306F\u307E\u3060\u5B8C\u4E86\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3059\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
587
608
  oauthReopen: "\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3059",
609
+ oauthUnconfirmed: "\u30ED\u30B0\u30A4\u30F3\u7D50\u679C\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u306E\u524D\u306B\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
610
+ oauthCheckSession: "\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u30ED\u30B0\u30A4\u30F3\u3092\u78BA\u8A8D",
611
+ oauthCancelled: "\u30ED\u30B0\u30A4\u30F3\u306E\u5F85\u6A5F\u3092\u7D42\u4E86\u3057\u307E\u3057\u305F\u3002\u8A8D\u8A3C\u30A6\u30A3\u30F3\u30C9\u30A6\u304C\u958B\u3044\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u9589\u3058\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
612
+ oauthDenied: "\u8A8D\u8A3C\u304C\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
613
+ oauthBlocked: "\u30D6\u30E9\u30A6\u30B6\u304C\u30ED\u30B0\u30A4\u30F3\u753B\u9762\u3092\u30D6\u30ED\u30C3\u30AF\u3057\u307E\u3057\u305F\u3002\u958B\u304D\u76F4\u3059\u304B\u3001\u3053\u306E\u30DA\u30FC\u30B8\u3067\u7D9A\u3051\u3066\u304F\u3060\u3055\u3044\u3002",
614
+ oauthTimedOut: "\u30ED\u30B0\u30A4\u30F3\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\u3002\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
615
+ oauthFailed: "\u30ED\u30B0\u30A4\u30F3\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
588
616
  oauthFullPage: "\u3053\u306E\u30DA\u30FC\u30B8\u3067\u7D9A\u3051\u308B",
589
617
  emailEdit: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5909\u66F4",
590
618
  emailCooldown: "{seconds} \u79D2\u5F8C\u306B\u518D\u9001\u4FE1",
@@ -2612,7 +2640,9 @@ h1 {
2612
2640
  }
2613
2641
  @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
2614
2642
 
2615
- /* QR timeout overlay */
2643
+ /* QR timeout overlay. It sits on the code, not across the card. Dark keeps a
2644
+ dim scrim so the expired plate stays readable as "inactive". */
2645
+ .qr-plate { position: relative; }
2616
2646
  .qr-timeout-overlay {
2617
2647
  position: absolute;
2618
2648
  inset: 0;
@@ -2621,14 +2651,28 @@ h1 {
2621
2651
  align-items: center;
2622
2652
  justify-content: center;
2623
2653
  gap: 12px;
2654
+ padding: 16px;
2655
+ text-align: center;
2624
2656
  background: rgba(0, 0, 0, 0.65);
2625
- border-radius: var(--radius);
2657
+ border-radius: inherit;
2626
2658
  backdrop-filter: blur(2px);
2627
2659
  }
2628
2660
  .qr-timeout-overlay .timeout-text {
2629
2661
  font-size: 13px;
2662
+ line-height: 1.4;
2630
2663
  color: #fff;
2631
2664
  }
2665
+ [data-theme="light"] .qr-timeout-overlay,
2666
+ :host([data-theme="light"]) .qr-timeout-overlay {
2667
+ background: rgba(255, 255, 255, 0.94);
2668
+ border: 1px solid var(--border);
2669
+ box-shadow: var(--shadow-sm);
2670
+ backdrop-filter: blur(8px);
2671
+ }
2672
+ [data-theme="light"] .qr-timeout-overlay .timeout-text,
2673
+ :host([data-theme="light"]) .qr-timeout-overlay .timeout-text {
2674
+ color: var(--text-secondary);
2675
+ }
2632
2676
  .qr-timeout-overlay .refresh-btn {
2633
2677
  display: inline-flex;
2634
2678
  align-items: center;
@@ -2980,28 +3024,41 @@ h1 {
2980
3024
  border-color: var(--border-strong);
2981
3025
  }
2982
3026
  .locale-trigger svg { width: 14px; height: 14px; flex-shrink: 0; }
3027
+ /* One compact menu surface; rows are choices, not nested pill buttons. */
2983
3028
  .locale-menu {
2984
- display: none;
2985
- flex-direction: column;
2986
- align-items: stretch;
2987
- gap: 4px;
2988
- margin-top: 4px;
3029
+ display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: 4;
3030
+ min-width: 144px; width: max-content; max-width: min(240px,calc(100vw - 64px));
3031
+ padding: 4px; background: var(--bg-card); border: 1px solid var(--border);
3032
+ border-radius: 10px; box-shadow: var(--shadow-lg);
2989
3033
  }
2990
- .locale-switcher.open .locale-menu {
2991
- display: flex;
2992
- }
2993
- .locale-btn[aria-checked="true"] {
2994
- color: var(--text);
2995
- border-color: var(--border-strong);
3034
+ .locale-switcher.open .locale-menu { display: flex; flex-direction: column; }
3035
+ .locale-menu .locale-btn {
3036
+ width: 100%; min-height: 36px; height: auto; padding: 7px 10px; gap: 8px;
3037
+ border: 0; border-radius: 6px; background: transparent; text-align: left;
3038
+ font-size: 13px; line-height: 1.4; overflow-wrap: anywhere;
2996
3039
  }
2997
- .locale-check { font-size: 11px; line-height: 1; }
3040
+ .locale-menu .locale-btn:hover, .locale-menu .locale-btn:focus-visible { background: var(--bg-hover); color: var(--text); }
3041
+ .locale-menu .locale-btn[aria-checked="true"] { color: var(--text); font-weight: 600; }
3042
+ .locale-check { width: 14px; flex: 0 0 14px; font-size: 12px; line-height: 1; }
3043
+ @media (pointer: coarse) { .locale-menu .locale-btn { min-height: 44px; } }
3044
+ .has-last-used { flex-wrap: wrap; }
3045
+ .last-used { display: inline-block; font-size: 11px; font-weight: 400; line-height: 1.4;
3046
+ padding: 2px 6px; border: 1px solid currentColor; border-radius: 4px; opacity: .72; white-space: nowrap; }
2998
3047
  .card { position: relative; }
2999
3048
 
3000
3049
  /* Long localized method labels remain readable on narrow screens. */
3001
3050
  .methods-grid { grid-template-columns: 1fr; }
3002
3051
  .method-btn { min-height: 48px; white-space: normal; }
3003
3052
  .passkey-alt { min-height: 44px; font-weight: 600; }
3004
- .oauth-recovery { font-size: 14px; line-height: 1.5; margin: 12px 0; }
3053
+ .oauth-recovery { font-size: 14px; line-height: 1.5; margin: 12px 0; padding: 12px;
3054
+ border: 1px solid var(--border); border-radius: var(--radius); color: var(--text-secondary); text-align: left; }
3055
+ .oauth-recovery p { margin: 0 0 8px; overflow-wrap: anywhere; }
3056
+ .oauth-recovery-actions { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: center; }
3057
+ .oauth-recovery-actions button, .oauth-recovery-actions a { font: inherit; font-size: 13px; min-height: 44px;
3058
+ display: inline-flex; align-items: center; padding: 4px 0; color: var(--text); border: 0; background: none; cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
3059
+ .oauth-recovery-actions[hidden], .oauth-recovery-actions [hidden] { display: none; }
3060
+ .oauth-recovery-actions :focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
3061
+
3005
3062
  @media (max-width: 480px) { .card { padding: 24px 20px; width: 100%; box-sizing: border-box; } h1 { font-size: 24px; } }
3006
3063
 
3007
3064
  .passkey-create-entry { display: flex; justify-content: center; align-items: center; width: 100%; box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-surface); color: var(--text); padding: 10px 14px; }
@@ -3014,11 +3071,7 @@ h1 {
3014
3071
  .card-modal .locale-switcher { min-width: 0; max-width: 100%; }
3015
3072
  .card-modal .locale-trigger { max-width: 100%; height: 36px; }
3016
3073
  .locale-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3017
- .card-modal .locale-menu { position: absolute; top: calc(100% + 8px); left: 0; z-index: 4; margin-top: 0;
3018
- width: max-content; max-width: min(260px,calc(100vw - 80px)); padding: 6px;
3019
- background: var(--bg-card); border: 1px solid var(--border-strong); border-radius: 10px; box-shadow: var(--shadow-lg); }
3020
- .card-modal .locale-menu .locale-btn { min-height: 40px; height: auto; text-align: left; border-radius: 6px; }
3021
- .card-modal .locale-menu .locale-btn span { overflow-wrap: anywhere; }
3074
+ .card-modal .locale-menu { left: 0; right: auto; }
3022
3075
 
3023
3076
  .card { padding: 52px 24px 24px; box-sizing: border-box; }
3024
3077
  .card-modal { width: 100%; }
@@ -3027,7 +3080,13 @@ h1 {
3027
3080
  .card.card-modal, :host([data-theme="light"]) .card.card-modal { box-shadow: none; }
3028
3081
  /* A translateY entrance expands an auto scroller by 4px until it finishes. */
3029
3082
  .card-modal[data-view] { animation: none; }
3030
- .card-modal .back-btn { position: static; display: flex; width: max-content; max-width: 100%; margin: 0 0 16px; }
3083
+ /* Sub-step navigation shares the close button's center line in both modes. */
3084
+ .back-btn { top: 8px; left: 24px; height: 44px; max-width: calc(100% - 160px); line-height: 1.4; text-align: left; }
3085
+ .back-btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 4px; }
3086
+ .card-modal .back-btn { position: absolute; top: 8px; left: 24px; margin: 0; }
3087
+ .card-modal:has(.back-btn) .card-tools { left: auto; right: 64px; max-width: calc(100% - 160px); }
3088
+ .passkey-creating > .home-link { display: none; }
3089
+ @media (max-width:480px) { .back-btn, .card-modal .back-btn { left: 18px; } }
3031
3090
  .card-modal h1 { display: block; overflow: visible; -webkit-line-clamp: unset; line-clamp: unset; line-height: 1.4; padding-block: 2px; }
3032
3091
  .card-modal h1[tabindex="-1"]:focus { outline: none; }
3033
3092
  .card-modal h1, .card-modal h2, .card-modal .subtitle, .card-modal .status { overflow-wrap: anywhere; }
@@ -3474,6 +3533,8 @@ var LoginPage = class {
3474
3533
  }
3475
3534
  /** Locale sits on the card's top-right row, level with Back / Cancel. */
3476
3535
  renderCardTools(card) {
3536
+ if (!(this.options.showLocaleSelector ?? this.options.mode !== "dialog"))
3537
+ return;
3477
3538
  const tools = document.createElement("div");
3478
3539
  tools.className = "card-tools";
3479
3540
  this.renderLocaleSwitcher(tools);
@@ -3531,6 +3592,8 @@ var LoginPage = class {
3531
3592
  trigger.addEventListener("click", (event) => {
3532
3593
  event.stopPropagation();
3533
3594
  setOpen(menu.hidden);
3595
+ if (!menu.hidden)
3596
+ menu.querySelector('[aria-checked="true"]')?.focus();
3534
3597
  });
3535
3598
  for (const item of locales) {
3536
3599
  const checked = item.code === current.code;
@@ -3540,12 +3603,13 @@ var LoginPage = class {
3540
3603
  btn.setAttribute("role", "menuitemradio");
3541
3604
  btn.setAttribute("aria-checked", checked ? "true" : "false");
3542
3605
  btn.dataset.locale = item.code;
3606
+ btn.tabIndex = -1;
3543
3607
  btn.title = item.label;
3544
- if (checked) {
3608
+ {
3545
3609
  const mark = document.createElement("span");
3546
3610
  mark.className = "locale-check";
3547
3611
  mark.setAttribute("aria-hidden", "true");
3548
- mark.textContent = "\u2713";
3612
+ mark.textContent = checked ? "\u2713" : "";
3549
3613
  btn.appendChild(mark);
3550
3614
  }
3551
3615
  const label = document.createElement("span");
@@ -3557,6 +3621,24 @@ var LoginPage = class {
3557
3621
  });
3558
3622
  menu.appendChild(btn);
3559
3623
  }
3624
+ wrapper.addEventListener("keydown", (event) => {
3625
+ const items = [...menu.querySelectorAll(".locale-btn")];
3626
+ const active = this.shadow?.activeElement ?? document.activeElement;
3627
+ const index = items.indexOf(active);
3628
+ if (event.key === "Escape") {
3629
+ event.preventDefault();
3630
+ event.stopPropagation();
3631
+ setOpen(false);
3632
+ trigger.focus();
3633
+ } else if (event.key === "Tab")
3634
+ setOpen(false);
3635
+ else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
3636
+ event.preventDefault();
3637
+ setOpen(true);
3638
+ const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : index < 0 ? event.key === "ArrowUp" ? items.length - 1 : 0 : (index + (event.key === "ArrowUp" ? -1 : 1) + items.length) % items.length;
3639
+ items[next]?.focus();
3640
+ }
3641
+ });
3560
3642
  const onOutsideClick = (event) => {
3561
3643
  const path = event.composedPath();
3562
3644
  if (path.includes(wrapper))
@@ -3607,25 +3689,25 @@ var LoginPage = class {
3607
3689
  break;
3608
3690
  }
3609
3691
  }
3692
+ /** Keep recognition next to the action, without a second page subtitle. */
3693
+ markLastUsed(button, method, locale) {
3694
+ if (!this.options.preferenceKey)
3695
+ return;
3696
+ try {
3697
+ if (localStorage.getItem(`did-login-method:${this.options.preferenceKey}`) !== method)
3698
+ return;
3699
+ const hint = document.createElement("span");
3700
+ hint.className = "last-used";
3701
+ hint.textContent = t("lastUsed", locale);
3702
+ button.classList.add("has-last-used");
3703
+ button.appendChild(hint);
3704
+ } catch {
3705
+ }
3706
+ }
3610
3707
  renderMethodsView(card, locale, methods) {
3611
3708
  if (this.options.mode !== "dialog")
3612
3709
  this.renderHomeLink(card, locale);
3613
3710
  this.renderBrandHeader(card, locale);
3614
- if (this.options.preferenceKey) {
3615
- try {
3616
- const last = localStorage.getItem(`did-login-method:${this.options.preferenceKey}`);
3617
- const labels = { passkey: "Passkey", oauth: "OAuth", email: t("emailButton", locale), "did-connect": "DID Wallet" };
3618
- const provider = last?.startsWith("oauth:") ? this.options.oauthProviders?.find((p) => p.enabled && `oauth:${p.id}` === last) : void 0;
3619
- const label = provider?.name ?? (last ? labels[last] : void 0);
3620
- if (last && label && (methods.includes(last) || provider && methods.includes("oauth"))) {
3621
- const hint = document.createElement("p");
3622
- hint.className = "subtitle";
3623
- hint.textContent = t("lastUsed", locale, { method: label });
3624
- card.appendChild(hint);
3625
- }
3626
- } catch {
3627
- }
3628
- }
3629
3711
  const requestedPasskey = this.options.methods.includes("passkey");
3630
3712
  if (requestedPasskey && !methods.includes("passkey")) {
3631
3713
  const warn = document.createElement("div");
@@ -3895,11 +3977,12 @@ var LoginPage = class {
3895
3977
  }
3896
3978
  });
3897
3979
  }
3898
- renderBackButton(parent, locale) {
3980
+ renderBackButton(parent, locale, onBack = () => this.switchView("methods")) {
3899
3981
  const btn = document.createElement("button");
3982
+ btn.type = "button";
3900
3983
  btn.className = "back-btn";
3901
3984
  btn.innerHTML = `${ICON_BACK_ARROW} ${escapeHtml(t("back", locale))}`;
3902
- btn.addEventListener("click", () => this.switchView("methods"));
3985
+ btn.addEventListener("click", onBack);
3903
3986
  parent.appendChild(btn);
3904
3987
  }
3905
3988
  /**
@@ -4006,12 +4089,6 @@ var LoginPage = class {
4006
4089
  }
4007
4090
  footer.appendChild(links);
4008
4091
  }
4009
- if (this.currentView !== "methods" && this.options.mode !== "dialog") {
4010
- this.renderHomeLink(footer, locale);
4011
- const cancel = footer.querySelector(".home-link");
4012
- if (cancel)
4013
- cancel.style.cssText = "position:static;display:inline-flex;margin-top:12px";
4014
- }
4015
4092
  parent.appendChild(footer);
4016
4093
  }
4017
4094
  renderDivider(parent, locale) {
@@ -4107,11 +4184,8 @@ var LoginPage = class {
4107
4184
  createBtn.id = "passkey-btn";
4108
4185
  createBtn.innerHTML = `${ICON_PASSKEY} ${escapeHtml(t("passkeyCreate", locale))}`;
4109
4186
  section.appendChild(createBtn);
4110
- const backLink = document.createElement("button");
4111
- backLink.type = "button";
4112
- backLink.className = "passkey-alt";
4113
- backLink.textContent = t("passkeyBackToSignIn", locale);
4114
- section.appendChild(backLink);
4187
+ parent.querySelector(".back-btn")?.remove();
4188
+ this.renderBackButton(parent, locale, () => renderSignInState());
4115
4189
  const doCreate = async () => {
4116
4190
  if (createBtn.disabled)
4117
4191
  return;
@@ -4141,7 +4215,6 @@ var LoginPage = class {
4141
4215
  if (e.key === "Enter")
4142
4216
  doCreate();
4143
4217
  });
4144
- backLink.addEventListener("click", () => renderSignInState());
4145
4218
  createBtn.focus();
4146
4219
  };
4147
4220
  const doSignIn = async () => {
@@ -4176,6 +4249,7 @@ var LoginPage = class {
4176
4249
  };
4177
4250
  const renderSignInState = () => {
4178
4251
  this.cardEl?.classList.remove("passkey-creating");
4252
+ parent.querySelector(".back-btn")?.remove();
4179
4253
  this.abort?.abort();
4180
4254
  this.generation++;
4181
4255
  const heading = this.cardEl?.querySelector("h1");
@@ -4187,6 +4261,7 @@ var LoginPage = class {
4187
4261
  signInBtn.className = this.cardEl?.classList.contains("social-first") ? "btn btn-secondary" : "btn";
4188
4262
  signInBtn.id = "passkey-btn";
4189
4263
  signInBtn.innerHTML = `${ICON_PASSKEY} ${escapeHtml(t("passkeySignIn", locale))}`;
4264
+ this.markLastUsed(signInBtn, "passkey", locale);
4190
4265
  section.appendChild(signInBtn);
4191
4266
  signInBtn.addEventListener("click", () => doSignIn());
4192
4267
  const createLink = document.createElement("button");
@@ -4208,159 +4283,240 @@ var LoginPage = class {
4208
4283
  startOAuthPopup(providerId, btn) {
4209
4284
  this.oauthCleanup?.();
4210
4285
  const epoch = this.generation;
4286
+ const locale = this.options.locale;
4211
4287
  const servicePrefix = this.config.servicePrefix ?? "/.well-known/service";
4212
4288
  const oauthBase = this.options.masterOAuthOrigin ? `${this.options.masterOAuthOrigin}${servicePrefix}` : servicePrefix;
4213
4289
  const oauthOrigin = new URL(oauthBase, window.location.href).origin;
4214
- const loginUrl = `${oauthBase}/api/oauth/${encodeURIComponent(providerId)}/login?returnUrl=${encodeURIComponent(window.location.href)}`;
4215
- const width = 500;
4216
- const height = 620;
4217
- const left = window.screenX + (window.innerWidth - width) / 2;
4218
- const top = window.screenY + (window.innerHeight - height) / 2;
4219
- const popup = window.open(loginUrl, "oauth-login:popup", `left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1,popup`);
4220
- if (!popup) {
4221
- this.options.onBeforeRedirect?.();
4222
- window.location.href = loginUrl;
4223
- return;
4290
+ let returnUrl = window.location.href;
4291
+ if (this.options.mode !== "dialog" && window.location.pathname === `${servicePrefix}/login`) {
4292
+ const requested = new URLSearchParams(window.location.search).get("return_to");
4293
+ try {
4294
+ const target = new URL(requested || "/", window.location.origin);
4295
+ returnUrl = target.origin === window.location.origin && !/^\/[/\\]/.test(target.pathname) ? target.href : `${window.location.origin}/`;
4296
+ } catch {
4297
+ returnUrl = `${window.location.origin}/`;
4298
+ }
4224
4299
  }
4300
+ const loginUrl = `${oauthBase}/api/oauth/${encodeURIComponent(providerId)}/login?returnUrl=${encodeURIComponent(returnUrl)}`;
4301
+ const provider = this.options.oauthProviders?.find((p) => p.id === providerId)?.name ?? providerId;
4225
4302
  const originalHTML = btn.innerHTML;
4226
- btn.textContent = t("oauthWaiting", this.options.locale, { provider: this.options.oauthProviders?.find((p) => p.id === providerId)?.name ?? providerId });
4227
- btn.disabled = true;
4228
- btn.classList.add("oauth-btn-loading");
4303
+ let popup = null;
4304
+ let settled = false;
4305
+ let exchanging = false;
4306
+ let probing = false;
4307
+ let pollTimer;
4308
+ let sessionTimer;
4309
+ let abandonTimer;
4310
+ const current = () => !settled && this.mounted && epoch === this.generation;
4311
+ const locked = [];
4312
+ const lockMethods = () => {
4313
+ for (const button of this.cardEl?.querySelectorAll(".method-btn, .passkey-create-entry, #passkey-btn, .locale-trigger, .locale-btn") ?? []) {
4314
+ locked.push({ button, disabled: button.disabled });
4315
+ button.disabled = true;
4316
+ }
4317
+ };
4229
4318
  const recovery = document.createElement("div");
4230
4319
  recovery.className = "oauth-recovery";
4231
4320
  recovery.setAttribute("role", "status");
4232
4321
  const hint = document.createElement("p");
4233
- hint.textContent = t("oauthIncomplete", this.options.locale);
4322
+ const actions = document.createElement("div");
4323
+ actions.className = "oauth-recovery-actions";
4324
+ const reopen = document.createElement("button");
4325
+ reopen.type = "button";
4326
+ reopen.className = "oauth-retry";
4327
+ reopen.textContent = t("oauthReopen", locale);
4328
+ reopen.hidden = true;
4329
+ reopen.onclick = () => {
4330
+ this.select("oauth");
4331
+ this.activeProvider = providerId;
4332
+ this.startOAuthPopup(providerId, btn);
4333
+ };
4234
4334
  const fullPage = document.createElement("a");
4235
4335
  fullPage.href = loginUrl;
4236
- fullPage.textContent = t("oauthFullPage", this.options.locale);
4237
- fullPage.addEventListener("click", () => {
4336
+ fullPage.textContent = t("oauthFullPage", locale);
4337
+ fullPage.addEventListener("click", (event) => {
4338
+ const reconcile = recovery.dataset.state === "oauthUnconfirmed";
4339
+ if (exchanging && !settled) {
4340
+ event.preventDefault();
4341
+ return;
4342
+ }
4343
+ if (reconcile)
4344
+ event.preventDefault();
4238
4345
  this.options.onBeforeRedirect?.();
4239
4346
  this.oauthCleanup?.();
4347
+ if (reconcile)
4348
+ window.location.reload();
4240
4349
  });
4241
- recovery.append(hint, fullPage);
4242
- recovery.hidden = true;
4350
+ const cancel = document.createElement("button");
4351
+ cancel.type = "button";
4352
+ cancel.className = "oauth-cancel";
4353
+ cancel.textContent = t("cancel", locale);
4354
+ actions.append(reopen, fullPage, cancel);
4355
+ recovery.append(hint, actions);
4243
4356
  btn.parentElement?.after(recovery);
4244
- const restore = () => {
4245
- recovery.hidden = false;
4246
- this.emit("incomplete");
4357
+ const restoreButton = () => {
4247
4358
  btn.innerHTML = originalHTML;
4248
4359
  btn.disabled = false;
4249
4360
  btn.classList.remove("oauth-btn-loading");
4250
4361
  };
4251
- let settled = false;
4252
- let sessionPollTimer;
4362
+ const closePopup = () => {
4363
+ try {
4364
+ popup?.close();
4365
+ } catch {
4366
+ }
4367
+ };
4368
+ const cleanup = () => {
4369
+ settled = true;
4370
+ clearInterval(pollTimer);
4371
+ clearInterval(sessionTimer);
4372
+ clearTimeout(abandonTimer);
4373
+ window.removeEventListener("message", onMessage);
4374
+ for (const item of locked.splice(0))
4375
+ item.button.disabled = item.disabled;
4376
+ restoreButton();
4377
+ };
4378
+ const showRecovery = (key) => {
4379
+ hint.textContent = t(key, locale, { provider });
4380
+ recovery.dataset.state = key;
4381
+ actions.hidden = false;
4382
+ reopen.hidden = false;
4383
+ cancel.hidden = true;
4384
+ restoreButton();
4385
+ };
4386
+ const fail = (key) => {
4387
+ if (!current())
4388
+ return;
4389
+ cleanup();
4390
+ closePopup();
4391
+ showRecovery(key);
4392
+ this.emit("incomplete");
4393
+ };
4253
4394
  const succeed = () => {
4254
- if (settled || !this.mounted || epoch !== this.generation)
4395
+ if (!current())
4255
4396
  return;
4256
- settled = true;
4257
4397
  cleanup();
4258
- btn.textContent = t("success", this.options.locale);
4398
+ closePopup();
4399
+ recovery.remove();
4400
+ btn.textContent = t("success", locale);
4401
+ btn.disabled = true;
4259
4402
  this.handleSuccess();
4260
4403
  };
4261
- const onMessage = async (event) => {
4262
- if (event.origin !== oauthOrigin || event.source !== popup)
4404
+ cancel.onclick = () => {
4405
+ if (exchanging)
4263
4406
  return;
4264
- if (!event.data || event.data.type !== "authorization_response")
4407
+ fail("oauthCancelled");
4408
+ btn.focus();
4409
+ };
4410
+ const onMessage = async (event) => {
4411
+ if (event.origin !== oauthOrigin || event.source !== popup || !popup)
4265
4412
  return;
4266
- if (settled || !this.mounted || epoch !== this.generation)
4413
+ if (!event.data || event.data.type !== "authorization_response" || !current() || exchanging)
4267
4414
  return;
4268
- if (pollTimer !== void 0) {
4269
- clearInterval(pollTimer);
4270
- pollTimer = void 0;
4271
- }
4272
- try {
4273
- popup.close();
4274
- } catch {
4275
- }
4415
+ clearInterval(pollTimer);
4416
+ clearInterval(sessionTimer);
4276
4417
  if (event.data.error) {
4277
- restore();
4278
- cleanup();
4418
+ fail(event.data.error === "access_denied" ? "oauthDenied" : event.data.error === "cancelled" ? "oauthCancelled" : "oauthFailed");
4279
4419
  return;
4280
4420
  }
4281
4421
  const { code, state } = event.data.response ?? event.data;
4282
- if (!code) {
4283
- restore();
4284
- cleanup();
4422
+ if (typeof code !== "string" || !code || typeof state !== "string" || !state) {
4423
+ fail("oauthFailed");
4285
4424
  return;
4286
4425
  }
4287
- btn.textContent = t("verifying", this.options.locale);
4426
+ exchanging = true;
4427
+ lockMethods();
4428
+ actions.hidden = true;
4429
+ closePopup();
4430
+ hint.textContent = t("verifying", locale);
4431
+ btn.textContent = t("verifying", locale);
4288
4432
  btn.disabled = true;
4289
4433
  try {
4290
4434
  const oauthBody = { provider: providerId, code, state };
4291
4435
  if (this.options.invitationId)
4292
4436
  oauthBody.invitationId = this.options.invitationId;
4293
- await this.http.post(`${servicePrefix}/api/oauth/login`, oauthBody);
4437
+ await this.http.post(`${servicePrefix}/api/oauth/login`, oauthBody, { signal: this.abort?.signal });
4294
4438
  succeed();
4295
- } catch (oauthErr) {
4296
- if (settled || !this.mounted || epoch !== this.generation)
4439
+ } catch (err) {
4440
+ if (!current())
4297
4441
  return;
4298
- const errMsg = oauthErr instanceof Error ? oauthErr.message : "";
4299
- const errCode = oauthErr instanceof Error && "code" in oauthErr ? oauthErr.code : "";
4300
- if (errCode === "FORBIDDEN" || errMsg === "registration_closed" || errCode === "registration_closed") {
4301
- settled = true;
4442
+ const code2 = err instanceof Error && "code" in err ? String(err.code) : "";
4443
+ const message = err instanceof Error ? err.message : "";
4444
+ if (code2 === "FORBIDDEN" || code2 === "registration_closed" || message === "registration_closed") {
4302
4445
  cleanup();
4303
- this.showFullError(friendlyError(oauthErr, this.options.locale));
4304
- } else {
4305
- restore();
4306
- }
4446
+ recovery.remove();
4447
+ this.showFullError(friendlyError(err, locale));
4448
+ } else
4449
+ fail("oauthFailed");
4450
+ }
4451
+ };
4452
+ this.oauthCleanup = () => {
4453
+ cleanup();
4454
+ recovery.remove();
4455
+ closePopup();
4456
+ };
4457
+ window.addEventListener("message", onMessage);
4458
+ try {
4459
+ popup = window.open(loginUrl, "oauth-login:popup", `left=${window.screenX + (window.innerWidth - 500) / 2},top=${window.screenY + (window.innerHeight - 620) / 2},width=500,height=620,resizable,scrollbars=yes,status=1,popup`);
4460
+ } catch {
4461
+ }
4462
+ if (!popup) {
4463
+ fail("oauthBlocked");
4464
+ return;
4465
+ }
4466
+ hint.textContent = t("oauthWaiting", locale, { provider });
4467
+ btn.textContent = t("oauthWaiting", locale, { provider });
4468
+ btn.disabled = true;
4469
+ btn.classList.add("oauth-btn-loading");
4470
+ const checkSession = async () => {
4471
+ if (!current() || exchanging || probing)
4472
+ return;
4473
+ probing = true;
4474
+ try {
4475
+ const session = await this.http.get(`${servicePrefix}/api/did/session`);
4476
+ if (current() && !exchanging && session?.authenticated === true)
4477
+ succeed();
4478
+ } catch {
4479
+ } finally {
4480
+ probing = false;
4307
4481
  }
4308
4482
  };
4309
- let pollTimer = setInterval(() => {
4483
+ pollTimer = setInterval(() => {
4310
4484
  let closed = false;
4311
4485
  try {
4312
- closed = popup.closed;
4486
+ closed = !!popup?.closed;
4313
4487
  } catch {
4314
4488
  return;
4315
4489
  }
4316
- if (!closed)
4490
+ if (!closed || !current())
4317
4491
  return;
4318
- if (pollTimer !== void 0) {
4319
- clearInterval(pollTimer);
4320
- pollTimer = void 0;
4321
- }
4322
- restore();
4323
- const checkSession = () => {
4324
- void this.http.get(`${servicePrefix}/api/did/session`).then((session) => {
4325
- if (session?.authenticated)
4326
- succeed();
4327
- }).catch(() => {
4328
- });
4329
- };
4330
- checkSession();
4331
- sessionPollTimer = setInterval(checkSession, 500);
4492
+ clearInterval(pollTimer);
4493
+ showRecovery("oauthIncomplete");
4494
+ cancel.hidden = false;
4495
+ void checkSession();
4496
+ sessionTimer = setInterval(() => {
4497
+ void checkSession();
4498
+ }, 2e3);
4332
4499
  }, 500);
4333
- const abandonTimer = setTimeout(() => {
4334
- if (settled)
4500
+ abandonTimer = setTimeout(() => {
4501
+ if (!current())
4502
+ return;
4503
+ if (!exchanging) {
4504
+ fail("oauthTimedOut");
4335
4505
  return;
4336
- cleanup();
4337
- restore();
4338
- }, 5 * 60 * 1e3);
4339
- const cleanup = () => {
4340
- settled = true;
4341
- btn.innerHTML = originalHTML;
4342
- btn.disabled = false;
4343
- btn.classList.remove("oauth-btn-loading");
4344
- if (pollTimer !== void 0) {
4345
- clearInterval(pollTimer);
4346
- pollTimer = void 0;
4347
- }
4348
- if (sessionPollTimer !== void 0) {
4349
- clearInterval(sessionPollTimer);
4350
- sessionPollTimer = void 0;
4351
4506
  }
4352
- clearTimeout(abandonTimer);
4353
- window.removeEventListener("message", onMessage);
4354
- };
4355
- this.oauthCleanup = () => {
4507
+ this.abort?.abort();
4356
4508
  cleanup();
4357
- recovery.remove();
4358
- try {
4359
- popup.close();
4360
- } catch {
4361
- }
4362
- };
4363
- window.addEventListener("message", onMessage);
4509
+ closePopup();
4510
+ lockMethods();
4511
+ hint.textContent = t("oauthUnconfirmed", locale);
4512
+ recovery.dataset.state = "oauthUnconfirmed";
4513
+ actions.hidden = false;
4514
+ reopen.hidden = true;
4515
+ cancel.hidden = true;
4516
+ fullPage.href = window.location.href;
4517
+ fullPage.textContent = t("oauthCheckSession", locale);
4518
+ this.emit("incomplete");
4519
+ }, 5 * 60 * 1e3);
4364
4520
  }
4365
4521
  // (removed dead code: renderEmailButton — email now renders through renderSecondaryGrid)
4366
4522
  /**
@@ -4383,6 +4539,7 @@ var LoginPage = class {
4383
4539
  btn.dataset.provider = provider.id;
4384
4540
  const icon = getOAuthIcon(provider.id);
4385
4541
  btn.innerHTML = `${icon} ${escapeHtml(t("continueWith", locale, { provider: provider.name }))}`;
4542
+ this.markLastUsed(btn, `oauth:${provider.id}`, locale);
4386
4543
  grid.appendChild(btn);
4387
4544
  btn.addEventListener("click", () => {
4388
4545
  this.select("oauth");
@@ -4396,6 +4553,7 @@ var LoginPage = class {
4396
4553
  btn.className = "method-btn";
4397
4554
  btn.id = "email-btn";
4398
4555
  btn.innerHTML = `${ICON_EMAIL} ${escapeHtml(t("emailButton", locale))}`;
4556
+ this.markLastUsed(btn, "email", locale);
4399
4557
  grid.appendChild(btn);
4400
4558
  btn.addEventListener("click", () => {
4401
4559
  this.select("email");
@@ -4408,6 +4566,7 @@ var LoginPage = class {
4408
4566
  btn.id = "did-wallet-btn";
4409
4567
  const walletLabel = grid.children.length > 0 ? t("didWalletButtonShort", locale) : t("didWalletButton", locale);
4410
4568
  btn.innerHTML = `${ICON_DID_WALLET} ${escapeHtml(walletLabel)}`;
4569
+ this.markLastUsed(btn, "did-connect", locale);
4411
4570
  grid.appendChild(btn);
4412
4571
  btn.addEventListener("click", () => {
4413
4572
  this.select("did-connect");
@@ -4444,7 +4603,7 @@ var LoginPage = class {
4444
4603
  }
4445
4604
  });
4446
4605
  this.tokenSession.on("timeout", () => {
4447
- statusEl.textContent = t("timeout", locale);
4606
+ statusEl.textContent = "";
4448
4607
  const overlay = document.createElement("div");
4449
4608
  overlay.className = "qr-timeout-overlay";
4450
4609
  overlay.innerHTML = `<span class="timeout-text">${escapeHtml(t("timeout", locale))}</span>`;
@@ -4459,7 +4618,8 @@ var LoginPage = class {
4459
4618
  this.startDIDConnectInView(statusEl, qrContainer, walletBtn, locale);
4460
4619
  });
4461
4620
  overlay.appendChild(refreshBtn);
4462
- qrContainer.appendChild(overlay);
4621
+ const plate = qrContainer.querySelector(".qr-plate");
4622
+ (plate ?? qrContainer).appendChild(overlay);
4463
4623
  });
4464
4624
  this.tokenSession.on("error", (err) => {
4465
4625
  this.showConnectResult(qrContainer, walletBtn, statusEl, friendlyError(err, locale), true);
@@ -4710,6 +4870,9 @@ var LoginDialog = class {
4710
4870
  mount() {
4711
4871
  if (this.dialog)
4712
4872
  return;
4873
+ const hostLocale = document.documentElement.lang;
4874
+ if (hostLocale)
4875
+ this.options.locale = hostLocale;
4713
4876
  let focused = document.activeElement;
4714
4877
  while (focused?.shadowRoot?.activeElement)
4715
4878
  focused = focused.shadowRoot.activeElement;