1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <link rel="icon" href="/static/birdfy-favicon.ico"> 7 <title>Log in with Birdfy</title> 8 <link rel="stylesheet" href="/static/birdfy-styles.css?v=20260308d" /> 9 <style> 10 :root { 11 --primary: #00D4CD; 12 --bg-start: #eef5f8; 13 --bg-end: #f3f6fb; 14 --border: #d9e0ea; 15 } 16 17 body { 18 perspective: 1400px; 19 } 20 21 .card.intro-spin { 22 transform-style: preserve-3d; 23 transform-origin: center center; 24 will-change: transform; 25 animation: introSpinY 0.75s cubic-bezier(0.2, 0.8, 0.2, 1) 1; 26 } 27 28 @keyframes introSpinY { 29 0% { 30 transform: rotateY(0deg); 31 } 32 100% { 33 transform: rotateY(360deg); 34 } 35 } 36 37 @media (prefers-reduced-motion: reduce) { 38 .card.intro-spin { 39 animation: none; 40 } 41 } 42 43 .submit-btn { 44 display: inline-flex; 45 align-items: center; 46 justify-content: center; 47 gap: 8px; 48 min-height: 42px; 49 } 50 51 .submit-btn:disabled { 52 opacity: 0.75; 53 cursor: not-allowed; 54 } 55 56 .signup-btn { 57 width: 100%; 58 min-height: 42px; 59 background: #ffffff; 60 color: var(--primary); 61 border: 1px solid var(--primary); 62 margin-top: 2px; 63 } 64 65 .signup-btn:hover { 66 background: rgba(0, 212, 205, 0.08); 67 } 68 69 .success-banner { 70 background: #eaf8f1; 71 color: #1e8e5a; 72 border: 1px solid #b9e2cc; 73 border-radius: 8px; 74 padding: 8px 10px; 75 margin-bottom: 12px; 76 } 77 78 .login-success-modal[hidden] { 79 display: none; 80 } 81 82 .login-success-modal { 83 position: fixed; 84 inset: 0; 85 z-index: 20; 86 display: flex; 87 align-items: center; 88 justify-content: center; 89 padding: 24px; 90 background: rgba(9, 20, 31, 0.42); 91 } 92 93 .login-success-dialog { 94 width: min(420px, 100%); 95 background: #fff; 96 border: 1px solid var(--border); 97 border-radius: 14px; 98 box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18); 99 padding: 22px; 100 } 101 102 .login-success-dialog h2 { 103 margin: 0 0 10px; 104 font-size: 22px; 105 } 106 107 .login-success-dialog p { 108 margin: 0 0 18px; 109 color: #5f6f82; 110 line-height: 1.5; 111 } 112 113 .login-success-dialog .submit-btn { 114 width: 100%; 115 } 116 117 .forgot-btn { 118 align-self: flex-end; 119 min-height: 24px; 120 margin: -2px 0 2px; 121 padding: 0; 122 border: none; 123 background: transparent; 124 color: #5f6f82; 125 font-size: 13px; 126 font-weight: 500; 127 line-height: 1.2; 128 text-decoration: none; 129 } 130 131 .forgot-btn:hover { 132 color: #3f4f62; 133 text-decoration: underline; 134 text-decoration-thickness: 1px; 135 text-underline-offset: 2px; 136 background: transparent; 137 } 138 139 .credential-panel[hidden] { 140 display: none; 141 } 142 143 .credential-panel { 144 display: flex; 145 flex-direction: column; 146 gap: 8px; 147 } 148 149 .credential-panel > label { 150 display: block; 151 } 152 153 .field-row { 154 display: grid; 155 grid-template-columns: minmax(0, 1fr) auto; 156 gap: 10px; 157 align-items: center; 158 } 159 160 .send-code-btn { 161 min-width: 122px; 162 min-height: 42px; 163 padding: 0 14px; 164 border: 1px solid var(--primary); 165 border-radius: 10px; 166 background: #ffffff; 167 color: var(--primary); 168 font-weight: 600; 169 white-space: nowrap; 170 } 171 172 .send-code-btn:disabled { 173 opacity: 0.72; 174 cursor: not-allowed; 175 } 176 177 .send-code-btn:hover { 178 background: rgba(0, 212, 205, 0.08); 179 } 180 181 .login-utility-row { 182 display: flex; 183 align-items: center; 184 justify-content: space-between; 185 gap: 12px; 186 margin-top: 8px; 187 } 188 189 .marketing-opt-in { 190 display: flex; 191 align-items: flex-start; 192 gap: 10px; 193 margin-top: 10px; 194 color: #4f6072; 195 font-size: 14px; 196 line-height: 1.4; 197 cursor: pointer; 198 } 199 200 .marketing-opt-in input { 201 width: 16px; 202 height: 16px; 203 margin: 2px 0 0; 204 accent-color: var(--primary); 205 flex: 0 0 auto; 206 } 207 208 .marketing-opt-in span { 209 flex: 1 1 auto; 210 } 211 212 .alt-toggle { 213 width: auto; 214 min-height: 30px; 215 padding: 2px 0; 216 margin-top: 0; 217 border: none; 218 background: transparent; 219 color: #6a7787; 220 font-size: 14px; 221 font-weight: 500; 222 display: inline-flex; 223 align-items: center; 224 justify-content: flex-start; 225 gap: 6px; 226 } 227 228 .alt-toggle:hover { 229 color: #445265; 230 background: transparent; 231 } 232 233 .alt-toggle-icon { 234 font-size: 12px; 235 line-height: 1; 236 transition: transform 0.18s ease; 237 } 238 239 .alt-toggle[aria-expanded="true"] .alt-toggle-icon { 240 transform: rotate(180deg); 241 } 242 243 .mode-switch-btn { 244 min-height: 30px; 245 padding: 2px 0; 246 border: none; 247 background: transparent; 248 color: #5d6d81; 249 font-size: 14px; 250 font-weight: 600; 251 display: inline-flex; 252 align-items: center; 253 justify-content: flex-end; 254 gap: 6px; 255 margin-left: auto; 256 } 257 258 .mode-switch-btn:hover { 259 color: #21384d; 260 background: transparent; 261 } 262 263 .social-options { 264 max-height: 0; 265 opacity: 0; 266 overflow: hidden; 267 transform: translateY(-4px); 268 transition: max-height 0.24s ease, opacity 0.2s ease, transform 0.2s ease; 269 } 270 271 .social-options.open { 272 max-height: 240px; 273 opacity: 1; 274 transform: translateY(0); 275 } 276 277 .alt-divider { 278 display: flex; 279 align-items: center; 280 margin: 8px 0 10px; 281 color: #6a7787; 282 font-size: 13px; 283 line-height: 1.2; 284 } 285 286 .alt-divider::before, 287 .alt-divider::after { 288 content: ""; 289 flex: 1; 290 border-top: 1px solid #d9e0ea; 291 } 292 293 .alt-divider span { 294 padding: 0 10px; 295 text-align: center; 296 } 297 298 .social-btn { 299 width: 100%; 300 min-height: 42px; 301 display: inline-flex; 302 align-items: center; 303 justify-content: center; 304 gap: 10px; 305 border: 1px solid transparent; 306 margin-top: 0; 307 } 308 309 .social-btn:disabled { 310 opacity: 0.72;
311 cursor: not-allowed; 312 } 313 314 .social-options .facebook-btn { 315 margin-top: 10px; 316 } 317 318 .social-options .apple-btn { 319 margin-top: 14px; 320 } 321 322 .social-btn img { 323 width: 18px; 324 height: 18px; 325 display: block; 326 } 327 328 .facebook-btn { 329 background: #1877f2; 330 color: #ffffff; 331 } 332 333 .facebook-btn:hover { 334 background: #166fe5; 335 } 336 337 .apple-btn { 338 background: #000000; 339 color: #ffffff; 340 } 341 342 .apple-btn:hover { 343 background: #161616; 344 } 345 346 .btn-spinner { 347 width: 14px; 348 height: 14px; 349 border: 2px solid rgba(255, 255, 255, 0.4); 350 border-top-color: #ffffff; 351 border-radius: 9999px; 352 display: none; 353 animation: spin 0.8s linear infinite; 354 } 355 356 .submit-btn.is-loading .btn-spinner { 357 display: inline-block; 358 } 359 360 .auth-shell { 361 width: 100%; 362 max-width: 420px; 363 max-height: 100%; 364 display: flex; 365 flex-direction: column; 366 align-items: stretch; 367 } 368 369 .auth-shell .card { 370 max-width: none; 371 max-height: none; 372 overflow-y: visible; 373 } 374 375 .page-footer { 376 margin-top: 10px; 377 text-align: center; 378 font-size: 12px; 379 color: #7a8593; 380 line-height: 1.3; 381 flex: 0 0 auto; 382 user-select: none; 383 } 384 385 @media (max-height: 900px) { 386 .auth-shell .card { 387 max-height: calc(100dvh - 74px); 388 overflow-y: auto; 389 } 390 } 391 392 @keyframes spin { 393 to { 394 transform: rotate(360deg); 395 } 396 } 397 </style> 398</head> 399<body> 400 <div class="auth-shell"> 401 <main class="card intro-spin"> 402 <div class="brand-logo-wrap"> 403 <img src="/static/logos/birdfy-logo.svg" alt="Birdfy logo" class="brand-logo" /> 404 </div> 405 <h1>Log in with Birdfy</h1> 406 <p class="subtitle"> 407 408 409 410 Use your Birdfy Account to log in to <strong>Birdfy.com</strong>. 411 412 </p> 413 414 <div id="login-success" class="success-banner" hidden></div> 415 <div id="login-error" class="error" hidden></div> 416 417 <form method="post" action="/login" id="login-form"> 418 <input type="hidden" name="next" value="https://birdfy-openid-auth.birdfy.com/authorize?scope=openid+email&client_id=birdfy-shopify&response_type=code&redirect_uri=https%3A%2F%2Fshopify.com%2Fauthentication%2F78293467416%2Flogin%2Fexternal%2Fcallback&state=hWNHCLOpaEkoLie8FAi6zR6g&nonce=c79a1e59-ce2e-4c73-bdd7-6e1525f690c0&ui_locales=en&ui_country_code=US" /> 419 <input type="hidden" name="locale" value="en" /> 420 <input type="hidden" name="login_method" id="login-method-input" value="code" /> 421 422 <label for="username">Email</label> 423 <input id="username" name="username" type="email" inputmode="email" autocomplete="username email" required /> 424 425 <div id="login-code-panel" class="credential-panel"> 426 <label for="code">Verification Code</label> 427 <div class="field-row"> 428 <input id="code" name="code" type="text" inputmode="numeric" maxlength="6" autocomplete="one-time-code" /> 429 <button type="button" id="send-code-btn" class="send-code-btn"> 430 <span class="btn-label">Send Code</span> 431 </button> 432 </div> 433 </div> 434 435 <div id="login-password-panel" class="credential-panel" hidden> 436 <label for="password">Password</label> 437 <input id="password" name="password" type="password" autocomplete="current-password" /> 438 <button type="button" id="forgot-password-btn" class="forgot-btn">Forgot password?</button> 439 </div> 440 441 <label class="marketing-opt-in" for="news-and-offers-opt-in"> 442 <input 443 id="news-and-offers-opt-in" 444 name="news_and_offers_opt_in" 445 type="checkbox" 446 value="true" 447 checked 448 /> 449 <span>Email me with news and offers</span> 450 </label> 451 452 <button type="submit" id="login-submit" class="submit-btn"> 453 <span class="btn-spinner" aria-hidden="true"></span>
454 <span class="btn-label">Continue</span> 455 </button> 456 <button type="button" id="signup-btn" class="signup-btn">Sign up</button> 457 458 <div class="login-utility-row"> 459 <button 460 type="button" 461 class="alt-toggle" 462 id="social-toggle" 463 aria-expanded="false" 464 aria-controls="social-options" 465 > 466 <span>Other log-in options</span> 467 <span class="alt-toggle-icon" aria-hidden="true">â¾</span> 468 </button> 469 470 <button 471 type="button" 472 class="mode-switch-btn" 473 id="login-mode-switch" 474 aria-label="Login method" 475 > 476 Password 477 </button> 478 </div> 479 480 <div id="social-options" class="social-options" aria-hidden="true"> 481 <div class="alt-divider"> 482 <span>Or continue with</span> 483 </div> 484 485 <button type="button" id="facebook-login-btn" class="social-btn facebook-btn"> 486 <img src="/static/logos/facebook.svg" alt="Facebook" /> 487 <span class="social-label">Continue with Facebook</span> 488 </button> 489 490 <button type="button" id="apple-login-btn" class="social-btn apple-btn"> 491 <img src="/static/logos/apple.svg" alt="Apple" /> 492 <span class="social-label">Continue with Apple</span> 493 </button> 494 </div> 495 </form> 496 <form method="post" action="https://birdfy-openid-auth.birdfy.com/facebook-login" id="facebook-login-form" hidden> 497 <input type="hidden" name="next" value="https://birdfy-openid-auth.birdfy.com/authorize?scope=openid+email&client_id=birdfy-shopify&response_type=code&redirect_uri=https%3A%2F%2Fshopify.com%2Fauthentication%2F78293467416%2Flogin%2Fexternal%2Fcallback&state=hWNHCLOpaEkoLie8FAi6zR6g&nonce=c79a1e59-ce2e-4c73-bdd7-6e1525f690c0&ui_locales=en&ui_country_code=US" /> 498 <input type="hidden" name="locale" value="en" /> 499 <input type="hidden" name="token" id="facebook-login-token" value="" /> 500 <input 501 type="hidden" 502 name="news_and_offers_opt_in" 503 id="facebook-news-and-offers-opt-in" 504 value="true" 505 /> 506 </form> 507 </main> 508 <div class="page-footer">Copyright © 2026 Netvue Inc.</div> 509 </div> 510 <div id="login-success-modal" class="login-success-modal" hidden> 511 <div class="login-success-dialog" role="dialog" aria-modal="true" aria-labelledby="login-success-title"> 512 <h2 id="login-success-title">Check your email</h2> 513 <p id="login-success-modal-message"></p> 514 <button type="button" id="login-success-modal-close" class="submit-btn">OK</button> 515 </div> 516 </div> 517
517<script> 518 (() => { 519 const form = document.getElementById("login-form"); 520 const submitButton = document.getElementById("login-submit"); 521 const label = submitButton ? submitButton.querySelector(".btn-label") : null; 522 const errorBox = document.getElementById("login-error"); 523 const successBox = document.getElementById("login-success"); 524 const loginMethodInput = document.getElementById("login-method-input"); 525 const loginModeSwitch = document.getElementById("login-mode-switch"); 526 const loginCodePanel = document.getElementById("login-code-panel"); 527 const loginPasswordPanel = document.getElementById("login-password-panel"); 528 const usernameInput = document.getElementById("username"); 529 const passwordInput = document.getElementById("password"); 530 const codeInput = document.getElementById("code"); 531 const sendCodeButton = document.getElementById("send-code-btn"); 532 const sendCodeLabel = sendCodeButton ? sendCodeButton.querySelector(".btn-label") : null; 533 const forgotButton = document.getElementById("forgot-password-btn"); 534 const signupButton = document.getElementById("signup-btn"); 535 const facebookLoginButton = document.getElementById("facebook-login-btn"); 536 const facebookLoginForm = document.getElementById("facebook-login-form"); 537 const facebookLoginToken = document.getElementById("facebook-login-token"); 538 const facebookNewsAndOffersOptIn = document.getElementById("facebook-news-and-offers-opt-in"); 539 const appleLoginButton = document.getElementById("apple-login-btn"); 540 const newsAndOffersOptIn = document.getElementById("news-and-offers-opt-in"); 541 const forgotPasswordUrl = "https://birdfy-openid-auth.birdfy.com/forgot-password?next=https%3A%2F%2Fbirdfy-openid-auth.birdfy.com%2Fauthorize%3Fscope%3Dopenid%2Bemail%26client_id%3Dbirdfy-shopify%26response_type%3Dcode%26redirect_uri%3Dhttps%253A%252F%252Fshopify.com%252Fauthentication%252F78293467416%252Flogin%252Fexternal%252Fcallback%26state%3DhWNHCLOpaEkoLie8FAi6zR6g%26nonce%3Dc79a1e59-ce2e-4c73-bdd7-6e1525f690c0%26ui_locales%3Den%26ui_country_code%3DUS\u0026ui_locales=en"; 542 const socialToggle = document.getElementById("social-toggle"); 543 const socialOptions = document.getElementById("social-options"); 544 const signupUrl = "https://birdfy-openid-auth.birdfy.com/signup?next=https%3A%2F%2Fbirdfy-openid-auth.birdfy.com%2Fauthorize%3Fscope%3Dopenid%2Bemail%26client_id%3Dbirdfy-shopify%26response_type%3Dcode%26redirect_uri%3Dhttps%253A%252F%252Fshopify.com%252Fauthentication%252F78293467416%252Flogin%252Fexternal%252Fcallback%26state%3DhWNHCLOpaEkoLie8FAi6zR6g%26nonce%3Dc79a1e59-ce2e-4c73-bdd7-6e1525f690c0%26ui_locales%3Den%26ui_country_code%3DUS\u0026ui_locales=en"; 545 const appleLoginUrl = "https://birdfy-openid-auth.birdfy.com/apple-login?next=https%3A%2F%2Fbirdfy-openid-auth.birdfy.com%2Fauthorize%3Fscope%3Dopenid%2Bemail%26client_id%3Dbirdfy-shopify%26response_type%3Dcode%26redirect_uri%3Dhttps%253A%252F%252Fshopify.com%252Fauthentication%252F78293467416%252Flogin%252Fexternal%252Fcallback%26state%3DhWNHCLOpaEkoLie8FAi6zR6g%26nonce%3Dc79a1e59-ce2e-4c73-bdd7-6e1525f690c0%26ui_locales%3Den%26ui_country_code%3DUS\u0026ui_locales=en"; 546 const loginSendCodeUrl = "https://birdfy-openid-auth.birdfy.com/login/send-code"; 547 const initialLoginMethod = "code"; 548 const successModal = document.getElementById("login-success-modal"); 549 const successModalMessage = document.getElementById("login-success-modal-message"); 550 const successModalClose = document.getElementById("login-success-modal-close"); 551 const loadingText = "Logging in..."; 552 const sendCodeText = "Send Code"; 553 const resendWaitPrefix = "Resend in"; 554 const sendingCodeText = "Sending code..."; 555 const useCodeLoginText = "Verification Code"; 556 const usePasswordLoginText = "Password"; 557 const facebookLoadingText = "Connecting to Facebook..."; 558 const appleLoadingText = "Redirecting to Apple..."; 559 const facebookSdkLoadFailedText = "Facebook sign-in is currently unavailable on this device or browser."; 560 const requiredEmailMessage = "Please enter your email address."; 561 const invalidEmailMessage = "Please enter a valid email address."; 562 const invalidCodeMessage = "Please enter a valid 6-digit verification code."; 563 const sendCodeFailedText = "Unable to send the verification code right now. Please try again later."; 564 const facebookAppId = "6748322615250899"; 565 const facebookSdkSrcUrl = "https://connect.facebook.net/en_US/sdk.js"; 566 const facebookSdkVersion = "v19.0"; 567 const forgotPasswordSuccessCode = "forgot_password_email_sent"; 568 const emailRegex = /^[a-z0-9]+([._-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+\.){1,63}[a-z0-9]+$/; 569 let facebookSdkPromise = null; 570 let sendCodeCooldownSeconds = 0; 571 let sendCodeCountdownTimer = null; 572 573 const showInlineError = (message) => { 574 if (!errorBox || !message) { 575 return; 576 } 577 errorBox.hidden = false;
578 errorBox.textContent = message; 579 }; 580 581 const showInlineSuccess = (message) => { 582 if (!successBox || !message) { 583 return; 584 } 585 successBox.hidden = false; 586 successBox.textContent = message; 587 }; 588 589 const setSocialButtonLoading = (button, text) => { 590 if (!button) { 591 return; 592 } 593 button.disabled = true; 594 const labelNode = button.querySelector(".social-label"); 595 if (labelNode && text) { 596 labelNode.textContent = text; 597 } 598 }; 599 600 const restoreSocialButtonLabel = (button, text) => { 601 if (!button) { 602 return; 603 } 604 button.disabled = false; 605 const labelNode = button.querySelector(".social-label"); 606 if (labelNode && text) { 607 labelNode.textContent = text; 608 } 609 }; 610 611 const loadFacebookSdk = () => { 612 if (window.FB && typeof window.FB.init === "function") { 613 return Promise.resolve(window.FB); 614 } 615 if (facebookSdkPromise) { 616 return facebookSdkPromise; 617 } 618 facebookSdkPromise = new Promise((resolve, reject) => { 619 const existing = document.getElementById("facebook-jssdk"); 620 if (existing) { 621 const waitForSdk = () => { 622 if (window.FB && typeof window.FB.init === "function") { 623 resolve(window.FB); 624 return; 625 } 626 window.setTimeout(waitForSdk, 50); 627 }; 628 waitForSdk(); 629 return; 630 } 631 632 const script = document.createElement("script"); 633 script.id = "facebook-jssdk"; 634 script.async = true; 635 script.defer = true; 636 script.crossOrigin = "anonymous"; 637 script.src = facebookSdkSrcUrl; 638 script.onload = () => { 639 if (!window.FB || typeof window.FB.init !== "function") { 640 reject(new Error("facebook_sdk_missing")); 641 return; 642 } 643 window.FB.init({ 644 appId: facebookAppId, 645 autoLogAppEvents: true, 646 xfbml: true, 647 version: facebookSdkVersion, 648 }); 649 resolve(window.FB); 650 }; 651 script.onerror = () => reject(new Error("facebook_sdk_load_failed")); 652 document.body.appendChild(script); 653 }).catch((error) => { 654 facebookSdkPromise = null; 655 throw error; 656 }); 657 return facebookSdkPromise; 658 }; 659 660 const clearMessages = () => { 661 if (errorBox) { 662 errorBox.hidden = true; 663 errorBox.textContent = ""; 664 } 665 if (successBox) { 666 successBox.hidden = true; 667 successBox.textContent = ""; 668 } 669 if (successModal) { 670 successModal.hidden = true; 671 } 672 if (window.location.hash) { 673 history.replaceState(null, "", window.location.pathname + window.location.search); 674 } 675 }; 676 677 const stopSendCodeCountdown = () => { 678 if (sendCodeCountdownTimer) { 679 window.clearInterval(sendCodeCountdownTimer); 680 sendCodeCountdownTimer = null; 681 } 682 }; 683 684 const renderSendCodeCountdown = () => { 685 if (!sendCodeButton || !sendCodeLabel) { 686 return; 687 } 688 if (sendCodeCooldownSeconds > 0) { 689 sendCodeButton.disabled = true; 690 sendCodeLabel.textContent = `${resendWaitPrefix} ${sendCodeCooldownSeconds}s`; 691 sendCodeCooldownSeconds -= 1; 692 return; 693 } 694 sendCodeButton.disabled = false; 695 sendCodeLabel.textContent = sendCodeText; 696 stopSendCodeCountdown(); 697 }; 698 699 const startSendCodeCountdown = (seconds) => { 700 sendCodeCooldownSeconds = Math.max(0, Number(seconds) || 0); 701 stopSendCodeCountdown(); 702 renderSendCodeCountdown(); 703 if (sendCodeCooldownSeconds > 0) { 704 sendCodeCountdownTimer = window.setInterval(renderSendCodeCountdown, 1000); 705 } 706 }; 707 708 const validateEmail = () => { 709 const email = (usernameInput?.value || "").trim().toLowerCase(); 710 if (!email) { 711 showInlineError(requiredEmailMessage); 712 usernameInput?.focus(); 713 return ""; 714 } 715 if (!emailRegex.test(email)) { 716 showInlineError(invalidEmailMessage); 717 usernameInput?.focus(); 718 return ""; 719 } 720 return email; 721 }; 722 723 const isNewsAndOffersOptedIn = () => { 724 return Boolean(newsAndOffersOptIn?.checked); 725 }; 726 727 const getActiveLoginMethod = () => { 728 return (loginMethodInput?.value || "code").trim() === "password" ? "password" : "code"; 729 }; 730 731 const applyLoginMethod = (method) => { 732 const resolvedMethod = method === "password" ? "password" : "code"; 733 if (loginMethodInput) { 734 loginMethodInput.value = resolvedMethod; 735 } 736 if (loginCodePanel) { 737 loginCodePanel.hidden = resolvedMethod !== "code"; 738 } 739 if (loginPasswordPanel) { 740 loginPasswordPanel.hidden = resolvedMethod !== "password"; 741 } 742 if (passwordInput) { 743 passwordInput.disabled = resolvedMethod !== "password"; 744 } 745 if (codeInput) { 746 codeInput.disabled = resolvedMethod !== "code"; 747 } 748 if (forgotButton) { 749 forgotButton.hidden = resolvedMethod !== "password"; 750 } 751 if (loginModeSwitch) { 752 loginModeSwitch.textContent = resolvedMethod === "password" ? useCodeLoginText : usePasswordLoginText; 753 } 754 }; 755 756 const showSuccessModal = (message) => { 757 if (!successModal || !successModalMessage) { 758 return; 759 } 760 successModalMessage.textContent = message; 761 successModal.hidden = false;
762 }; 763 764 const decodeBase64Utf8 = (value) => { 765 try { 766 const binary = atob(value); 767 const bytes = Uint8Array.from(binary, (ch) => ch.charCodeAt(0)); 768 return new TextDecoder().decode(bytes); 769 } catch { 770 return ""; 771 } 772 }; 773 774 const renderLoginErrorFromHash = () => { 775 if (!errorBox && !successBox) { 776 return; 777 } 778 const params = new URLSearchParams(window.location.hash.replace(/^#/, "")); 779 const successCode = (params.get("success") || "").trim(); 780 const encodedSuccessMessage = (params.get("success_b64") || "").trim(); 781 const successMessage = decodeBase64Utf8(encodedSuccessMessage); 782 if (successMessage) { 783 if (successCode === forgotPasswordSuccessCode) { 784 showSuccessModal(successMessage); 785 } else if (successBox) { 786 successBox.hidden = false; 787 successBox.textContent = successMessage; 788 } 789 } 790 const encodedMessage = (params.get("error_b64") || "").trim(); 791 const message = decodeBase64Utf8(encodedMessage); 792 if (message && errorBox) { 793 errorBox.hidden = false; 794 errorBox.textContent = message; 795 } 796 }; 797 798 renderLoginErrorFromHash(); 799 applyLoginMethod(initialLoginMethod); 800 801 if (loginModeSwitch) { 802 loginModeSwitch.addEventListener("click", () => { 803 clearMessages(); 804 applyLoginMethod(getActiveLoginMethod() === "password" ? "code" : "password"); 805 }); 806 } 807 808 if (forgotButton) { 809 forgotButton.addEventListener("click", () => { 810 clearMessages(); 811 window.location.href = forgotPasswordUrl; 812 }); 813 } 814 if (signupButton) { 815 signupButton.addEventListener("click", () => { 816 clearMessages(); 817 window.location.href = signupUrl; 818 }); 819 } 820 if (codeInput) { 821 codeInput.addEventListener("input", () => { 822 codeInput.value = (codeInput.value || "").replace(/\D/g, "").slice(0, 6); 823 }); 824 } 825 if (sendCodeButton) { 826 sendCodeButton.addEventListener("click", async () => { 827 if (sendCodeButton.disabled) { 828 return; 829 } 830 clearMessages(); 831 const email = validateEmail(); 832 if (!email) { 833 return; 834 } 835 sendCodeButton.disabled = true; 836 if (sendCodeLabel) { 837 sendCodeLabel.textContent = sendingCodeText; 838 } 839 const formData = new FormData(); 840 formData.set("username", email); 841 formData.set("next", document.querySelector('input[name="next"]')?.value || ""); 842 formData.set("locale", document.querySelector('input[name="locale"]')?.value || ""); 843 try { 844 const response = await fetch(loginSendCodeUrl, { 845 method: "POST", 846 body: formData, 847 }); 848 let payload = {}; 849 try { 850 payload = await response.json(); 851 } catch { 852 payload = {}; 853 } 854 const message = (payload?.message || "").trim(); 855 const cooldownSeconds = Number(payload?.cooldown_seconds) || 0; 856 if (!response.ok) { 857 if (cooldownSeconds > 0) { 858 startSendCodeCountdown(cooldownSeconds); 859 } else { 860 sendCodeButton.disabled = false; 861 if (sendCodeLabel) { 862 sendCodeLabel.textContent = sendCodeText; 863 } 864 } 865 showInlineError(message || sendCodeFailedText); 866 return; 867 } 868 showInlineSuccess(message); 869 startSendCodeCountdown(cooldownSeconds); 870 } catch { 871 sendCodeButton.disabled = false; 872 if (sendCodeLabel) { 873 sendCodeLabel.textContent = sendCodeText; 874 } 875 showInlineError(sendCodeFailedText); 876 } 877 }); 878 } 879 if (facebookLoginButton && facebookLoginForm && facebookLoginToken) { 880 const initialFacebookText = facebookLoginButton.querySelector(".social-label")?.textContent || ""; 881 facebookLoginButton.addEventListener("click", async () => { 882 if (facebookLoginButton.disabled) { 883 return; 884 } 885 clearMessages(); 886 setSocialButtonLoading(facebookLoginButton, facebookLoadingText); 887 try { 888 const FB = await loadFacebookSdk(); 889 FB.login((response) => { 890 const accessToken = response?.authResponse?.accessToken; 891 if (response?.status === "connected" && accessToken) { 892 facebookLoginToken.value = accessToken; 893 if (facebookNewsAndOffersOptIn) { 894 facebookNewsAndOffersOptIn.value = isNewsAndOffersOptedIn() ? "true" : "false"; 895 } 896 requestAnimationFrame(() => facebookLoginForm.submit()); 897 return; 898 } 899 restoreSocialButtonLabel(facebookLoginButton, initialFacebookText); 900 showInlineError("Facebook sign-in failed. Please try again or use another login method."); 901 }, { 902 auth_type: "reauthenticate", 903 }); 904 } catch { 905 restoreSocialButtonLabel(facebookLoginButton, initialFacebookText); 906 showInlineError(facebookSdkLoadFailedText); 907 } 908 }); 909 } 910 if (appleLoginButton) { 911 const initialAppleText = appleLoginButton.querySelector(".social-label")?.textContent || ""; 912 appleLoginButton.addEventListener("click", () => { 913 if (appleLoginButton.disabled) { 914 return; 915 } 916 clearMessages(); 917 setSocialButtonLoading(appleLoginButton, appleLoadingText); 918 const target = new URL(appleLoginUrl, window.location.origin); 919 target.searchParams.set("news_and_offers_opt_in", isNewsAndOffersOptedIn() ? "true" : "false"); 920 window.location.href = target.toString(); 921 }); 922 } 923 if (socialToggle && socialOptions) { 924 socialToggle.addEventListener("click", () => { 925 const expanded = socialToggle.getAttribute("aria-expanded") === "true"; 926 const next = !expanded; 927 socialToggle.setAttribute("aria-expanded", String(next)); 928 socialOptions.classList.toggle("open", next); 929 socialOptions.setAttribute("aria-hidden", String(!next)); 930 }); 931 } 932 933 if (successModal) { 934 successModal.addEventListener("click", (event) => { 935 if (event.target === successModal) { 936 clearMessages(); 937 } 938 }); 939 } 940 941 if (successModalClose) { 942 successModalClose.addEventListener("click", clearMessages); 943 } 944 945 if (!form || !submitButton || !label) { 946 return; 947 } 948 949 form.addEventListener("submit", (event) => { 950 if (submitButton.disabled) { 951 event.preventDefault(); 952 return; 953 } 954 clearMessages(); 955 if (getActiveLoginMethod() === "code") { 956 const email = validateEmail(); 957 if (!email) { 958 event.preventDefault(); 959 return; 960 } 961 if (!codeInput || !/^\d{6}$/.test((codeInput.value || "").trim())) { 962 event.preventDefault(); 963 showInlineError(invalidCodeMessage); 964 codeInput?.focus(); 965 return; 966 } 967 } 968 event.preventDefault(); 969 submitButton.disabled = true; 970 submitButton.classList.add("is-loading"); 971 submitButton.setAttribute("aria-busy", "true"); 972 label.textContent = loadingText; 973 requestAnimationFrame(() => form.submit()); 974 }); 975 })(); 976 </script>
976 977</body> 978</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.