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 <title>Login | Lux Meter Pro - Professional Light Measurement App</title> 7 <meta name="description" content="Sign in to your Lux Meter Pro account to access premium features, sync your measurements, and manage your professional lighting assessments."> 8 <meta name="keywords" content="lux meter pro login, sign in, professional lighting app, light measurement account"> 9 <meta name="robots" content="noindex, nofollow"> 10 <link rel="icon" type="image/x-icon" href="favicon.ico"> 11 <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"> 12 <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"> 13 <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"> 14 <link rel="manifest" href="site.webmanifest"> 15 16 <style> 17 * { 18 margin: 0; 19 padding: 0; 20 box-sizing: border-box; 21 } 22 23 body { 24 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; 25 line-height: 1.6; 26 color: #333; 27 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 28 min-height: 100vh; 29 display: flex; 30 align-items: center; 31 justify-content: center; 32 padding: 20px; 33 } 34 35 .login-container { 36 background: white; 37 border-radius: 20px; 38 box-shadow: 0 20px 60px rgba(0,0,0,0.3); 39 width: 100%; 40 max-width: 400px; 41 padding: 3rem 2rem; 42 position: relative; 43 overflow: hidden; 44 } 45 46 .login-container::before { 47 content: ''; 48 position: absolute; 49 top: 0; 50 left: 0; 51 right: 0; 52 height: 4px; 53 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 54 } 55 56 .logo { 57 text-align: center; 58 margin-bottom: 2rem; 59 } 60 61 .logo h1 { 62 font-size: 2rem; 63 color: #2c3e50; 64 margin-bottom: 0.5rem; 65 font-weight: 700; 66 } 67 68 .logo-icon { 69 font-size: 3rem; 70 margin-bottom: 1rem; 71 } 72 73 .logo p { 74 color: #6c757d; 75 font-size: 0.9rem; 76 } 77 78 .login-header { 79 text-align: center; 80 margin-bottom: 2rem; 81 } 82 83 .login-header h2 { 84 font-size: 1.8rem; 85 color: #2c3e50; 86 margin-bottom: 0.5rem; 87 font-weight: 600; 88 } 89 90 .login-header p { 91 color: #6c757d; 92 font-size: 0.95rem; 93 } 94 95 .form-group { 96 margin-bottom: 1.5rem; 97 } 98 99 .form-group label { 100 display: block; 101 margin-bottom: 0.5rem; 102 font-weight: 600; 103 color: #2c3e50; 104 font-size: 0.9rem; 105 } 106 107 .form-group input { 108 width: 100%; 109 padding: 1rem; 110 border: 2px solid #e9ecef; 111 border-radius: 10px; 112 font-size: 1rem; 113 transition: all 0.3s ease; 114 font-family: inherit; 115 } 116 117 .form-group input:focus { 118 outline: none; 119 border-color: #667eea; 120 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 121 } 122 123 .form-group input.error { 124 border-color: #dc3545; 125 box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1); 126 } 127 128 .form-group input.success { 129 border-color: #28a745; 130 box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.1); 131 } 132 133 .password-toggle { 134 position: relative; 135 } 136 137 .password-toggle-btn { 138 position: absolute; 139 right: 12px; 140 top: 50%; 141 transform: translateY(-50%); 142 background: none; 143 border: none; 144 color: #6c757d; 145 cursor: pointer; 146 font-size: 1.1rem; 147 padding: 0.5rem; 148 border-radius: 4px; 149 transition: all 0.3s ease; 150 } 151 152 .password-toggle-btn:hover { 153 color: #2c3e50; 154 background: rgba(0,0,0,0.05); 155 } 156 157 .remember-forgot { 158 display: flex; 159 justify-content: space-between; 160 align-items: center; 161 margin-bottom: 2rem; 162 font-size: 0.9rem; 163 } 164 165 .remember-me { 166 display: flex; 167 align-items: center; 168 gap: 0.5rem; 169 } 170 171 .remember-me input[type="checkbox"] { 172 width: auto; 173 } 174 175 .forgot-password { 176 color: #667eea; 177 text-decoration: none; 178 transition: color 0.3s ease; 179 } 180 181 .forgot-password:hover { 182 color: #5a6fd8; 183 text-decoration: underline; 184 } 185 186 .login-btn { 187 width: 100%; 188 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 189 color: white; 190 padding: 1rem; 191 border: none; 192 border-radius: 10px; 193 font-size: 1.1rem; 194 font-weight: 600; 195 cursor: pointer; 196 transition: all 0.3s ease; 197 margin-bottom: 1.5rem; 198 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); 199 } 200 201 .login-btn:hover:not(:disabled) { 202 transform: translateY(-2px); 203 box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); 204 } 205 206 .login-btn:disabled { 207 opacity: 0.7;
208 cursor: not-allowed; 209 transform: none !important; 210 } 211 212 .divider { 213 text-align: center; 214 margin: 2rem 0; 215 position: relative; 216 color: #6c757d; 217 font-size: 0.9rem; 218 } 219 220 .divider::before { 221 content: ''; 222 position: absolute; 223 top: 50%; 224 left: 0; 225 right: 0; 226 height: 1px; 227 background: #e9ecef; 228 z-index: 1; 229 } 230 231 .divider span { 232 background: white; 233 padding: 0 1rem; 234 position: relative; 235 z-index: 2; 236 } 237 238 .social-login { 239 display: flex; 240 flex-direction: column; 241 gap: 1rem; 242 margin-bottom: 2rem; 243 } 244 245 .social-btn { 246 display: flex; 247 align-items: center; 248 justify-content: center; 249 gap: 0.5rem; 250 padding: 1rem; 251 border: 2px solid #e9ecef; 252 border-radius: 10px; 253 background: white; 254 color: #2c3e50; 255 text-decoration: none; 256 font-weight: 600; 257 transition: all 0.3s ease; 258 font-size: 0.95rem; 259 } 260 261 .social-btn:hover { 262 border-color: #667eea; 263 background: #f8f9fa; 264 transform: translateY(-1px); 265 } 266 267 .google-btn { 268 border-color: #db4437; 269 color: #db4437; 270 } 271 272 .google-btn:hover { 273 background: rgba(219, 68, 55, 0.1); 274 border-color: #db4437; 275 } 276 277 .apple-btn { 278 border-color: #000; 279 color: #000; 280 } 281 282 .apple-btn:hover { 283 background: rgba(0, 0, 0, 0.05); 284 border-color: #000; 285 } 286 287 .signup-link { 288 text-align: center; 289 color: #6c757d; 290 font-size: 0.9rem; 291 } 292 293 .signup-link a { 294 color: #667eea; 295 text-decoration: none; 296 font-weight: 600; 297 } 298 299 .signup-link a:hover { 300 text-decoration: underline; 301 } 302 303 .error-message { 304 background: #f8d7da; 305 color: #721c24; 306 padding: 0.75rem; 307 border-radius: 8px; 308 margin-bottom: 1rem; 309 font-size: 0.9rem; 310 border: 1px solid #f5c6cb; 311 display: none; 312 } 313 314 .success-message { 315 background: #d4edda; 316 color: #155724; 317 padding: 0.75rem; 318 border-radius: 8px; 319 margin-bottom: 1rem; 320 font-size: 0.9rem; 321 border: 1px solid #c3e6cb; 322 display: none; 323 } 324 325 .loading-spinner { 326 display: none; 327 width: 20px; 328 height: 20px; 329 border: 2px solid #ffffff; 330 border-top: 2px solid transparent; 331 border-radius: 50%; 332 animation: spin 1s linear infinite; 333 margin-right: 0.5rem; 334 } 335 336 @keyframes spin { 337 0% { transform: rotate(0deg); } 338 100% { transform: rotate(360deg); } 339 } 340 341 .back-to-home { 342 position: absolute; 343 top: 20px; 344 left: 20px; 345 background: rgba(255,255,255,0.9); 346 color: #2c3e50; 347 padding: 0.5rem 1rem; 348 border-radius: 25px; 349 text-decoration: none; 350 font-weight: 500; 351 transition: all 0.3s ease; 352 backdrop-filter: blur(10px); 353 box-shadow: 0 4px 15px rgba(0,0,0,0.1); 354 } 355 356 .back-to-home:hover { 357 background: white; 358 transform: translateY(-2px); 359 box-shadow: 0 8px 25px rgba(0,0,0,0.15); 360 } 361 362 /* Responsive Design */ 363 @media (max-width: 768px) { 364 .login-container { 365 padding: 2rem 1.5rem; 366 margin: 1rem; 367 } 368 369 .logo h1 { 370 font-size: 1.8rem; 371 } 372 373 .login-header h2 { 374 font-size: 1.6rem; 375 } 376 377 .remember-forgot { 378 flex-direction: column; 379 gap: 1rem; 380 align-items: flex-start; 381 } 382 } 383 384 @media (max-width: 414px) { 385 body { 386 padding: 10px; 387 } 388 389 .login-container { 390 padding: 1.5rem 1rem; 391 } 392 393 .back-to-home { 394 top: 10px; 395 left: 10px; 396 padding: 0.4rem 0.8rem; 397 font-size: 0.9rem; 398 } 399 } 400 </style> 401</head> 402<body> 403 <!-- Back to Home Link --> 404 <a href="index.html" class="back-to-home">â Back to Home</a> 405 406 <div class="login-container"> 407 <!-- Logo Section --> 408 <div class="logo"> 409 <div class="logo-icon">ð</div> 410 <h1>Lux Meter Pro</h1> 411 <p>Professional Light Measurement</p> 412 </div> 413 414 <!-- Login Header --> 415 <div class="login-header"> 416 <h2>Welcome Back</h2> 417 <p>Sign in to access your lighting measurements and reports</p> 418 </div> 419 420 <!-- Error/Success Messages --> 421 <div id="errorMessage" class="error-message"></div> 422 <div id="successMessage" class="success-message"></div> 423 424 <!-- Login Form --> 425 <form id="loginForm"> 426 <div class="form-group"> 427 <label for="email">Email Address</label> 428 <input type="email" id="email" name="email" placeholder="[email protected]" required> 429 </div> 430 431 <div class="form-group"> 432 <label for="password">Password</label> 433 <div class="password-toggle"> 434 <input type="password" id="password" name="password" placeholder="Enter your password" required> 435 <button type="button" class="password-toggle-btn" id="passwordToggle">ðï¸</button> 436 </div> 437 </div> 438 439 <div class="remember-forgot"> 440 <label class="remember-me"> 441 <input type="checkbox" id="rememberMe" name="rememberMe"> 442 Remember me 443 </label> 444 <a href="#" class="forgot-password" id="forgotPassword">Forgot Password?</a> 445 </div> 446 447 <button type="submit" class="login-btn" id="loginBtn">
448 <span class="loading-spinner" id="loadingSpinner"></span> 449 <span id="loginBtnText">Sign In</span> 450 </button> 451 </form> 452 453 <!-- Signup Link --> 454 <div class="signup-link"> 455 Don't have an account? <a href="signup.html">Sign up here</a> 456 </div> 457 </div> 458 459 <!-- Firebase SDK --> 460
460<script type="module"> 461 // Import Firebase modules 462 import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js'; 463 import { 464 getAuth, 465 signInWithEmailAndPassword, 466 signInWithPopup, 467 GoogleAuthProvider, 468 OAuthProvider, 469 sendPasswordResetEmail, 470 setPersistence, 471 browserLocalPersistence, 472 browserSessionPersistence, 473 onAuthStateChanged 474 } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js'; 475 476 // Firebase configuration 477 const firebaseConfig = { 478 apiKey: "AIzaSyDfdlgiOySnAXHppoart3ud_aVo2j--9sE", 479 authDomain: "luxmeter-fba54.firebaseapp.com", 480 projectId: "luxmeter-fba54", 481 storageBucket: "luxmeter-fba54.firebasestorage.app", 482 messagingSenderId: "233688623963", 483 appId: "1:233688623963:web:45f0e6517fa5d5f9ea087e", 484 measurementId: "G-PGP657TTYD" 485 }; 486 487 // Initialize Firebase 488 const app = initializeApp(firebaseConfig); 489 const auth = getAuth(app); 490 491 // Providers 492 const googleProvider = new GoogleAuthProvider(); 493 const appleProvider = new OAuthProvider('apple.com'); 494 495 // DOM Elements 496 const loginForm = document.getElementById('loginForm'); 497 const emailInput = document.getElementById('email'); 498 const passwordInput = document.getElementById('password'); 499 const rememberMeInput = document.getElementById('rememberMe'); 500 const loginBtn = document.getElementById('loginBtn'); 501 const loginBtnText = document.getElementById('loginBtnText'); 502 const loadingSpinner = document.getElementById('loadingSpinner'); 503 const errorMessage = document.getElementById('errorMessage'); 504 const successMessage = document.getElementById('successMessage'); 505 const passwordToggle = document.getElementById('passwordToggle'); 506 const forgotPasswordLink = document.getElementById('forgotPassword'); 507 const googleLoginBtn = document.getElementById('googleLogin'); 508 const appleLoginBtn = document.getElementById('appleLogin'); 509 510 // Check if user is already logged in 511 onAuthStateChanged(auth, (user) => { 512 if (user) { 513 // User is signed in, redirect to dashboard 514 showSuccess('Already signed in! Redirecting to dashboard...'); 515 setTimeout(() => { 516 window.location.href = 'accounts/my-dash.html'; 517 }, 1500); 518 } 519 }); 520 521 // Utility Functions 522 function showError(message) { 523 errorMessage.textContent = message; 524 errorMessage.style.display = 'block'; 525 successMessage.style.display = 'none'; 526 setTimeout(() => { 527 errorMessage.style.display = 'none'; 528 }, 5000); 529 } 530 531 function showSuccess(message) { 532 successMessage.textContent = message; 533 successMessage.style.display = 'block'; 534 errorMessage.style.display = 'none'; 535 setTimeout(() => { 536 successMessage.style.display = 'none'; 537 }, 5000); 538 } 539 540 function setLoading(loading) { 541 if (loading) { 542 loginBtn.disabled = true; 543 loadingSpinner.style.display = 'inline-block'; 544 loginBtnText.textContent = 'Signing In...'; 545 } else { 546 loginBtn.disabled = false; 547 loadingSpinner.style.display = 'none'; 548 loginBtnText.textContent = 'Sign In'; 549 } 550 } 551 552 function validateEmail(email) { 553 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 554 return emailRegex.test(email); 555 } 556 557 function validateForm() { 558 let isValid = true; 559 560 // Reset styles 561 emailInput.classList.remove('error', 'success'); 562 passwordInput.classList.remove('error', 'success'); 563 564 // Validate email 565 if (!emailInput.value.trim()) { 566 emailInput.classList.add('error'); 567 isValid = false;
568 } else if (!validateEmail(emailInput.value)) { 569 emailInput.classList.add('error'); 570 showError('Please enter a valid email address'); 571 isValid = false; 572 } else { 573 emailInput.classList.add('success'); 574 } 575 576 // Validate password 577 if (!passwordInput.value.trim()) { 578 passwordInput.classList.add('error'); 579 isValid = false; 580 } else if (passwordInput.value.length < 6) { 581 passwordInput.classList.add('error'); 582 showError('Password must be at least 6 characters long'); 583 isValid = false; 584 } else { 585 passwordInput.classList.add('success'); 586 } 587 588 return isValid; 589 } 590 591 // Password Toggle 592 passwordToggle.addEventListener('click', function() { 593 const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password'; 594 passwordInput.setAttribute('type', type); 595 this.textContent = type === 'password' ? 'ðï¸' : 'ð'; 596 }); 597 598 // Form Submission 599 loginForm.addEventListener('submit', async function(e) { 600 e.preventDefault(); 601 602 if (!validateForm()) { 603 return; 604 } 605 606 setLoading(true); 607 608 try { 609 // Set persistence based on remember me checkbox 610 const persistence = rememberMeInput.checked ? browserLocalPersistence : browserSessionPersistence; 611 await setPersistence(auth, persistence); 612 613 // Sign in with email and password 614 const userCredential = await signInWithEmailAndPassword(auth, emailInput.value, passwordInput.value); 615 const user = userCredential.user; 616 617 showSuccess('Login successful! Redirecting to dashboard...'); 618 619 // Store user info in localStorage if remember me is checked 620 if (rememberMeInput.checked) { 621 localStorage.setItem('luxmeter_user', JSON.stringify({ 622 uid: user.uid, 623 email: user.email, 624 displayName: user.displayName, 625 loginTime: new Date().toISOString() 626 })); 627 } 628 629 // Redirect to dashboard after short delay 630 setTimeout(() => { 631 window.location.href = 'accounts/my-dash.html'; 632 }, 1500); 633 634 } catch (error) { 635 console.error('Login error:', error); 636 637 let errorMsg = 'Login failed. Please try again.'; 638 639 switch (error.code) { 640 case 'auth/user-not-found': 641 errorMsg = 'No account found with this email address.'; 642 break; 643 case 'auth/wrong-password': 644 errorMsg = 'Incorrect password. Please try again.'; 645 break; 646 case 'auth/invalid-email': 647 errorMsg = 'Invalid email address format.'; 648 break; 649 case 'auth/user-disabled': 650 errorMsg = 'This account has been disabled.'; 651 break; 652 case 'auth/too-many-requests': 653 errorMsg = 'Too many failed attempts. Please try again later.'; 654 break; 655 case 'auth/network-request-failed': 656 errorMsg = 'Network error. Please check your connection.'; 657 break; 658 } 659 660 showError(errorMsg); 661 } finally { 662 setLoading(false); 663 } 664 }); 665 666 // Google Sign In 667 googleLoginBtn.addEventListener('click', async function() { 668 try { 669 const result = await signInWithPopup(auth, googleProvider); 670 const user = result.user; 671 672 showSuccess('Google sign-in successful! Redirecting...'); 673 674 // Store user info 675 localStorage.setItem('luxmeter_user', JSON.stringify({ 676 uid: user.uid, 677 email: user.email, 678 displayName: user.displayName, 679 photoURL: user.photoURL, 680 loginTime: new Date().toISOString(), 681 provider: 'google' 682 })); 683 684 setTimeout(() => { 685 window.location.href = 'accounts/my-dash.html'; 686 }, 1500); 687 688 } catch (error) { 689 console.error('Google sign-in error:', error); 690 showError('Google sign-in failed. Please try again.'); 691 } 692 }); 693 694 // Apple Sign In 695 appleLoginBtn.addEventListener('click', async function() { 696 try { 697 const result = await signInWithPopup(auth, appleProvider); 698 const user = result.user; 699 700 showSuccess('Apple sign-in successful! Redirecting...'); 701 702 // Store user info 703 localStorage.setItem('luxmeter_user', JSON.stringify({ 704 uid: user.uid, 705 email: user.email, 706 displayName: user.displayName, 707 loginTime: new Date().toISOString(), 708 provider: 'apple' 709 })); 710 711 setTimeout(() => { 712 window.location.href = 'accounts/my-dash.html'; 713 }, 1500); 714 715 } catch (error) { 716 console.error('Apple sign-in error:', error); 717 showError('Apple sign-in failed. Please try again.'); 718 } 719 }); 720 721 // Forgot Password 722 forgotPasswordLink.addEventListener('click', async function(e) { 723 e.preventDefault(); 724 725 if (!emailInput.value.trim()) { 726 showError('Please enter your email address first'); 727 emailInput.focus(); 728 return; 729 } 730 731 if (!validateEmail(emailInput.value)) { 732 showError('Please enter a valid email address'); 733 emailInput.focus(); 734 return; 735 } 736 737 try { 738 await sendPasswordResetEmail(auth, emailInput.value); 739 showSuccess('Password reset email sent! Check your inbox.'); 740 } catch (error) { 741 console.error('Password reset error:', error); 742 743 let errorMsg = 'Failed to send password reset email.'; 744 if (error.code === 'auth/user-not-found') { 745 errorMsg = 'No account found with this email address.'; 746 } 747 showError(errorMsg); 748 } 749 }); 750
751 // Input validation on blur 752 emailInput.addEventListener('blur', function() { 753 if (this.value.trim() && validateEmail(this.value)) { 754 this.classList.remove('error'); 755 this.classList.add('success'); 756 } else if (this.value.trim()) { 757 this.classList.remove('success'); 758 this.classList.add('error'); 759 } 760 }); 761 762 passwordInput.addEventListener('blur', function() { 763 if (this.value.trim() && this.value.length >= 6) { 764 this.classList.remove('error'); 765 this.classList.add('success'); 766 } else if (this.value.trim()) { 767 this.classList.remove('success'); 768 this.classList.add('error'); 769 } 770 }); 771 772 // Clear validation on input 773 emailInput.addEventListener('input', function() { 774 this.classList.remove('error', 'success'); 775 }); 776 777 passwordInput.addEventListener('input', function() { 778 this.classList.remove('error', 'success'); 779 }); 780 </script>
780 781</body> 782</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.