sign up
Browse files- signup.css +80 -0
- signup.html +107 -0
- signup.js +167 -0
signup.css
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ============================================================
|
| 2 |
+
SpeakFlow AI β signup.css (shared base with login.css)
|
| 3 |
+
============================================================ */
|
| 4 |
+
:root {
|
| 5 |
+
--blue:#2563EB;--blue-light:#3B82F6;--blue-dark:#1D4ED8;
|
| 6 |
+
--teal:#14B8A6;--purple:#7C3AED;--green:#10B981;--red:#EF4444;
|
| 7 |
+
--bg:#0F172A;--surface:rgba(30,41,59,0.85);--surface-2:#1E293B;
|
| 8 |
+
--border:rgba(148,163,184,0.2);
|
| 9 |
+
--text:#F1F5F9;--text-muted:#94A3B8;--text-light:#64748B;
|
| 10 |
+
--radius:16px;--radius-lg:24px;
|
| 11 |
+
--shadow-lg:0 24px 64px rgba(0,0,0,0.5);
|
| 12 |
+
--font-display:'Space Grotesk',sans-serif;
|
| 13 |
+
--font-body:'Inter',sans-serif;
|
| 14 |
+
--transition:.25s ease;
|
| 15 |
+
}
|
| 16 |
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
| 17 |
+
html{scroll-behavior:smooth;}
|
| 18 |
+
body{font-family:var(--font-body);background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;overflow-x:hidden;}
|
| 19 |
+
|
| 20 |
+
/* ββ Animated Background βββββββββββββββββββββββββββββββββββ */
|
| 21 |
+
.auth-bg{position:fixed;inset:0;z-index:0;overflow:hidden;}
|
| 22 |
+
.auth-orb{position:absolute;border-radius:50%;filter:blur(80px);opacity:.35;animation:orbFloat 8s ease-in-out infinite;}
|
| 23 |
+
.orb-1{width:600px;height:600px;background:radial-gradient(circle,#2563EB,transparent);top:-200px;left:-200px;animation-delay:0s;}
|
| 24 |
+
.orb-2{width:500px;height:500px;background:radial-gradient(circle,#7C3AED,transparent);bottom:-150px;right:-150px;animation-delay:3s;}
|
| 25 |
+
.orb-3{width:400px;height:400px;background:radial-gradient(circle,#14B8A6,transparent);top:50%;left:50%;transform:translate(-50%,-50%);animation-delay:6s;}
|
| 26 |
+
@keyframes orbFloat{0%,100%{transform:translateY(0) scale(1);}50%{transform:translateY(-30px) scale(1.05);}}
|
| 27 |
+
.auth-particles{position:absolute;inset:0;}
|
| 28 |
+
|
| 29 |
+
/* ββ Auth Wrapper ββββββββββββββββββββββββββββββββββββββββββ */
|
| 30 |
+
.auth-wrapper{position:relative;z-index:10;width:100%;max-width:500px;padding:20px;}
|
| 31 |
+
.auth-card{background:rgba(15,23,42,0.75);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-lg);padding:40px;box-shadow:var(--shadow-lg);}
|
| 32 |
+
|
| 33 |
+
/* ββ Logo ββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 34 |
+
.auth-logo{display:flex;align-items:center;gap:14px;margin-bottom:28px;}
|
| 35 |
+
.auth-logo-icon{width:52px;height:52px;background:linear-gradient(135deg,var(--blue),var(--teal));border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:1.4rem;color:#fff;flex-shrink:0;}
|
| 36 |
+
.auth-logo-text{font-family:var(--font-display);font-size:1.3rem;font-weight:700;display:block;}
|
| 37 |
+
.logo-ai{background:linear-gradient(135deg,var(--blue-light),var(--teal));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;}
|
| 38 |
+
.auth-logo-sub{font-size:0.78rem;color:var(--text-muted);margin-top:2px;}
|
| 39 |
+
.auth-title{font-family:var(--font-display);font-size:1.7rem;font-weight:700;margin-bottom:6px;}
|
| 40 |
+
.auth-subtitle{color:var(--text-muted);font-size:0.9rem;margin-bottom:28px;line-height:1.5;}
|
| 41 |
+
|
| 42 |
+
/* ββ Form ββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 43 |
+
.form-group{margin-bottom:18px;}
|
| 44 |
+
.form-label{display:flex;align-items:center;gap:7px;font-size:0.85rem;font-weight:600;color:var(--text-muted);margin-bottom:8px;}
|
| 45 |
+
.input-wrap{position:relative;}
|
| 46 |
+
.form-input{width:100%;padding:13px 16px;background:rgba(30,41,59,0.8);border:1px solid var(--border);border-radius:12px;color:var(--text);font-size:0.95rem;font-family:var(--font-body);outline:none;transition:border var(--transition),box-shadow var(--transition);}
|
| 47 |
+
.form-input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(37,99,235,0.15);}
|
| 48 |
+
.form-input.input-error{border-color:var(--red);}
|
| 49 |
+
.form-input.input-ok{border-color:var(--green);}
|
| 50 |
+
.toggle-pass{position:absolute;right:14px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:1rem;padding:4px;}
|
| 51 |
+
.toggle-pass:hover{color:var(--blue);}
|
| 52 |
+
.form-error{display:block;font-size:0.8rem;color:var(--red);margin-top:5px;min-height:16px;}
|
| 53 |
+
.form-success{display:block;font-size:0.85rem;color:var(--green);margin-top:5px;}
|
| 54 |
+
.text-center{text-align:center;}
|
| 55 |
+
.form-group.has-error .form-input{border-color:var(--red);}
|
| 56 |
+
.form-group.has-ok .form-input{border-color:var(--green);}
|
| 57 |
+
|
| 58 |
+
/* ββ Strength Meter ββββββββββββββββββββββββββββββββββββββββ */
|
| 59 |
+
.strength-meter{margin-top:8px;display:flex;align-items:center;gap:10px;}
|
| 60 |
+
.strength-bar{flex:1;height:5px;background:rgba(255,255,255,0.1);border-radius:10px;overflow:hidden;}
|
| 61 |
+
.strength-fill{height:100%;width:0%;border-radius:10px;transition:width .4s,background .4s;}
|
| 62 |
+
.strength-label{font-size:0.78rem;font-weight:600;color:var(--text-muted);white-space:nowrap;}
|
| 63 |
+
|
| 64 |
+
/* ββ Buttons βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 65 |
+
.btn-auth{width:100%;padding:14px;background:linear-gradient(135deg,var(--blue),var(--blue-dark));color:#fff;border:none;border-radius:12px;font-size:1rem;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:9px;margin-top:20px;transition:all var(--transition);box-shadow:0 4px 20px rgba(37,99,235,0.35);}
|
| 66 |
+
.btn-auth:hover{transform:translateY(-2px);box-shadow:0 8px 28px rgba(37,99,235,0.45);}
|
| 67 |
+
.btn-auth:active{transform:translateY(0);}
|
| 68 |
+
.btn-auth-outline{width:100%;padding:13px;background:transparent;color:var(--text-muted);border:1px solid var(--border);border-radius:12px;font-size:0.95rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:9px;margin-top:12px;transition:all var(--transition);text-decoration:none;}
|
| 69 |
+
.btn-auth-outline:hover{border-color:var(--blue);color:var(--blue);background:rgba(37,99,235,0.07);}
|
| 70 |
+
|
| 71 |
+
/* ββ Divider βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 72 |
+
.auth-divider{display:flex;align-items:center;gap:12px;margin:20px 0 0;color:var(--text-light);font-size:0.82rem;}
|
| 73 |
+
.auth-divider::before,.auth-divider::after{content:'';flex:1;height:1px;background:var(--border);}
|
| 74 |
+
|
| 75 |
+
/* ββ Particles βββββββββββββββββββββββββββββββββββββββββββββ */
|
| 76 |
+
.particle{position:absolute;background:rgba(99,179,237,.5);border-radius:50%;animation:floatUp linear infinite;pointer-events:none;}
|
| 77 |
+
@keyframes floatUp{0%{transform:translateY(0) rotate(0deg);opacity:.6;}100%{transform:translateY(-100vh) rotate(360deg);opacity:0;}}
|
| 78 |
+
|
| 79 |
+
/* ββ Responsive ββββββββββββββββββββββββββββββββββββββββββββ */
|
| 80 |
+
@media(max-width:520px){.auth-card{padding:28px 20px;}.auth-wrapper{padding:12px;}}
|
signup.html
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" data-theme="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Sign Up β SpeakFlow AI</title>
|
| 7 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
| 9 |
+
<link rel="stylesheet" href="signup.css" />
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
|
| 13 |
+
<!-- Animated Background -->
|
| 14 |
+
<div class="auth-bg">
|
| 15 |
+
<div class="auth-orb orb-1"></div>
|
| 16 |
+
<div class="auth-orb orb-2"></div>
|
| 17 |
+
<div class="auth-orb orb-3"></div>
|
| 18 |
+
<div class="auth-particles" id="authParticles"></div>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<!-- Card -->
|
| 22 |
+
<div class="auth-wrapper">
|
| 23 |
+
<div class="auth-card glass-card">
|
| 24 |
+
|
| 25 |
+
<!-- Logo -->
|
| 26 |
+
<div class="auth-logo">
|
| 27 |
+
<div class="auth-logo-icon"><i class="fas fa-wave-square"></i></div>
|
| 28 |
+
<div>
|
| 29 |
+
<span class="auth-logo-text">SpeakFlow <span class="logo-ai">AI</span></span>
|
| 30 |
+
<p class="auth-logo-sub">AI-Powered Speech Analysis</p>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<h1 class="auth-title">Create Account</h1>
|
| 35 |
+
<p class="auth-subtitle">Join SpeakFlow AI and start analyzing speech patterns today.</p>
|
| 36 |
+
|
| 37 |
+
<form id="signupForm" novalidate autocomplete="off">
|
| 38 |
+
|
| 39 |
+
<!-- Full Name -->
|
| 40 |
+
<div class="form-group" id="fg-name">
|
| 41 |
+
<label class="form-label"><i class="fas fa-user"></i> Full Name</label>
|
| 42 |
+
<div class="input-wrap">
|
| 43 |
+
<input type="text" id="signupName" class="form-input" placeholder="Your full name" autocomplete="name" />
|
| 44 |
+
</div>
|
| 45 |
+
<span class="form-error" id="nameErr"></span>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<!-- Email -->
|
| 49 |
+
<div class="form-group" id="fg-email">
|
| 50 |
+
<label class="form-label"><i class="fas fa-envelope"></i> Email Address</label>
|
| 51 |
+
<div class="input-wrap">
|
| 52 |
+
<input type="email" id="signupEmail" class="form-input" placeholder="you@example.com" autocomplete="email" />
|
| 53 |
+
</div>
|
| 54 |
+
<span class="form-error" id="emailErr"></span>
|
| 55 |
+
</div>
|
| 56 |
+
|
| 57 |
+
<!-- Password -->
|
| 58 |
+
<div class="form-group" id="fg-pass">
|
| 59 |
+
<label class="form-label"><i class="fas fa-lock"></i> Password</label>
|
| 60 |
+
<div class="input-wrap">
|
| 61 |
+
<input type="password" id="signupPass" class="form-input" placeholder="Create a strong password" autocomplete="new-password" />
|
| 62 |
+
<button type="button" class="toggle-pass" id="togglePass1" tabindex="-1">
|
| 63 |
+
<i class="fas fa-eye"></i>
|
| 64 |
+
</button>
|
| 65 |
+
</div>
|
| 66 |
+
<!-- Strength Meter -->
|
| 67 |
+
<div class="strength-meter" id="strengthMeter">
|
| 68 |
+
<div class="strength-bar">
|
| 69 |
+
<div class="strength-fill" id="strengthFill"></div>
|
| 70 |
+
</div>
|
| 71 |
+
<span class="strength-label" id="strengthLabel">Enter password</span>
|
| 72 |
+
</div>
|
| 73 |
+
<span class="form-error" id="passErr"></span>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Confirm Password -->
|
| 77 |
+
<div class="form-group" id="fg-confirm">
|
| 78 |
+
<label class="form-label"><i class="fas fa-shield-alt"></i> Confirm Password</label>
|
| 79 |
+
<div class="input-wrap">
|
| 80 |
+
<input type="password" id="signupConfirm" class="form-input" placeholder="Re-enter your password" autocomplete="new-password" />
|
| 81 |
+
<button type="button" class="toggle-pass" id="togglePass2" tabindex="-1">
|
| 82 |
+
<i class="fas fa-eye"></i>
|
| 83 |
+
</button>
|
| 84 |
+
</div>
|
| 85 |
+
<span class="form-error" id="confirmErr"></span>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<p class="form-error text-center" id="globalErr"></p>
|
| 89 |
+
<p class="form-success text-center" id="globalSuccess" style="display:none;"></p>
|
| 90 |
+
|
| 91 |
+
<button type="submit" class="btn-auth" id="signupBtn">
|
| 92 |
+
<span id="signupBtnText"><i class="fas fa-user-plus"></i> Create Account</span>
|
| 93 |
+
<span id="signupBtnLoader" style="display:none;"><i class="fas fa-spinner fa-spin"></i> Creating...</span>
|
| 94 |
+
</button>
|
| 95 |
+
</form>
|
| 96 |
+
|
| 97 |
+
<div class="auth-divider"><span>Already have an account?</span></div>
|
| 98 |
+
<a href="login.html" class="btn-auth-outline">
|
| 99 |
+
<i class="fas fa-sign-in-alt"></i> Go to Login
|
| 100 |
+
</a>
|
| 101 |
+
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<script src="signup.js"></script>
|
| 106 |
+
</body>
|
| 107 |
+
</html>
|
signup.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ============================================================
|
| 2 |
+
SpeakFlow AI β signup.js
|
| 3 |
+
============================================================ */
|
| 4 |
+
'use strict';
|
| 5 |
+
|
| 6 |
+
// Redirect if already logged in
|
| 7 |
+
(function(){
|
| 8 |
+
const s = JSON.parse(localStorage.getItem('sf_session')||'null');
|
| 9 |
+
if(s && s.loggedIn) window.location.href = 'index.html';
|
| 10 |
+
})();
|
| 11 |
+
|
| 12 |
+
/* ββ Particles βββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 13 |
+
(function(){
|
| 14 |
+
const c = document.getElementById('authParticles');
|
| 15 |
+
for(let i=0;i<20;i++){
|
| 16 |
+
const p=document.createElement('div');p.className='particle';
|
| 17 |
+
const sz=Math.random()*5+2;
|
| 18 |
+
Object.assign(p.style,{width:sz+'px',height:sz+'px',left:Math.random()*100+'%',bottom:'-10px',animationDuration:(Math.random()*20+15)+'s',animationDelay:(Math.random()*10)+'s',opacity:Math.random()*.4+.1});
|
| 19 |
+
c.appendChild(p);
|
| 20 |
+
}
|
| 21 |
+
})();
|
| 22 |
+
|
| 23 |
+
/* ββ DOM refs ββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 24 |
+
const form = document.getElementById('signupForm');
|
| 25 |
+
const nameInp = document.getElementById('signupName');
|
| 26 |
+
const emailInp = document.getElementById('signupEmail');
|
| 27 |
+
const passInp = document.getElementById('signupPass');
|
| 28 |
+
const confirmInp = document.getElementById('signupConfirm');
|
| 29 |
+
const nameErr = document.getElementById('nameErr');
|
| 30 |
+
const emailErr = document.getElementById('emailErr');
|
| 31 |
+
const passErr = document.getElementById('passErr');
|
| 32 |
+
const confirmErr = document.getElementById('confirmErr');
|
| 33 |
+
const globalErr = document.getElementById('globalErr');
|
| 34 |
+
const globalOk = document.getElementById('globalSuccess');
|
| 35 |
+
const btnText = document.getElementById('signupBtnText');
|
| 36 |
+
const btnLoader = document.getElementById('signupBtnLoader');
|
| 37 |
+
const strengthFill = document.getElementById('strengthFill');
|
| 38 |
+
const strengthLabel = document.getElementById('strengthLabel');
|
| 39 |
+
|
| 40 |
+
/* ββ Show/Hide Password ββββββββββββββββββββββββββββββββββββββ */
|
| 41 |
+
function toggleVis(inputEl, btn){
|
| 42 |
+
btn.addEventListener('click',()=>{
|
| 43 |
+
const show = inputEl.type==='password';
|
| 44 |
+
inputEl.type = show?'text':'password';
|
| 45 |
+
btn.querySelector('i').className = show?'fas fa-eye-slash':'fas fa-eye';
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
toggleVis(passInp, document.getElementById('togglePass1'));
|
| 49 |
+
toggleVis(confirmInp, document.getElementById('togglePass2'));
|
| 50 |
+
|
| 51 |
+
/* ββ Password Strength βββββββββββββββββββββββββββββββββββββββ */
|
| 52 |
+
passInp.addEventListener('input',()=>{
|
| 53 |
+
const v = passInp.value;
|
| 54 |
+
let score = 0;
|
| 55 |
+
if(v.length>=8) score++;
|
| 56 |
+
if(/[A-Z]/.test(v)) score++;
|
| 57 |
+
if(/[0-9]/.test(v)) score++;
|
| 58 |
+
if(/[^A-Za-z0-9]/.test(v)) score++;
|
| 59 |
+
const levels = [
|
| 60 |
+
{pct:0, color:'transparent', label:'Enter password'},
|
| 61 |
+
{pct:25, color:'#EF4444', label:'Weak'},
|
| 62 |
+
{pct:50, color:'#F59E0B', label:'Fair'},
|
| 63 |
+
{pct:75, color:'#3B82F6', label:'Good'},
|
| 64 |
+
{pct:100, color:'#10B981', label:'Strong'},
|
| 65 |
+
];
|
| 66 |
+
const l = v.length===0 ? levels[0] : levels[Math.max(1,score)];
|
| 67 |
+
strengthFill.style.width = l.pct+'%';
|
| 68 |
+
strengthFill.style.background = l.color;
|
| 69 |
+
strengthLabel.textContent = l.label;
|
| 70 |
+
strengthLabel.style.color = l.color;
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
/* ββ Validation helpers ββββββββββββββββββββββββββββββββββββββ */
|
| 74 |
+
function setErr(el,fgId,msg){
|
| 75 |
+
el.textContent=msg;
|
| 76 |
+
const fg=document.getElementById(fgId);
|
| 77 |
+
fg.classList.add('has-error');fg.classList.remove('has-ok');
|
| 78 |
+
}
|
| 79 |
+
function setOk(el,fgId){
|
| 80 |
+
el.textContent='';
|
| 81 |
+
const fg=document.getElementById(fgId);
|
| 82 |
+
fg.classList.remove('has-error');fg.classList.add('has-ok');
|
| 83 |
+
}
|
| 84 |
+
function clearState(el,fgId){
|
| 85 |
+
el.textContent='';
|
| 86 |
+
const fg=document.getElementById(fgId);
|
| 87 |
+
fg.classList.remove('has-error','has-ok');
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
function validateEmail(e){return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);}
|
| 91 |
+
|
| 92 |
+
/* ββ Submit ββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 93 |
+
form.addEventListener('submit',function(e){
|
| 94 |
+
e.preventDefault();
|
| 95 |
+
globalErr.textContent=''; globalOk.style.display='none';
|
| 96 |
+
let valid=true;
|
| 97 |
+
|
| 98 |
+
const name = nameInp.value.trim();
|
| 99 |
+
const email = emailInp.value.trim().toLowerCase();
|
| 100 |
+
const pass = passInp.value;
|
| 101 |
+
const confirm = confirmInp.value;
|
| 102 |
+
|
| 103 |
+
// Name
|
| 104 |
+
if(!name){setErr(nameErr,'fg-name','Full name is required.');valid=false;}
|
| 105 |
+
else if(name.length<2){setErr(nameErr,'fg-name','Name must be at least 2 characters.');valid=false;}
|
| 106 |
+
else setOk(nameErr,'fg-name');
|
| 107 |
+
|
| 108 |
+
// Email
|
| 109 |
+
if(!email){setErr(emailErr,'fg-email','Email is required.');valid=false;}
|
| 110 |
+
else if(!validateEmail(email)){setErr(emailErr,'fg-email','Enter a valid email address.');valid=false;}
|
| 111 |
+
else setOk(emailErr,'fg-email');
|
| 112 |
+
|
| 113 |
+
// Password
|
| 114 |
+
if(!pass){setErr(passErr,'fg-pass','Password is required.');valid=false;}
|
| 115 |
+
else if(pass.length<6){setErr(passErr,'fg-pass','Password must be at least 6 characters.');valid=false;}
|
| 116 |
+
else setOk(passErr,'fg-pass');
|
| 117 |
+
|
| 118 |
+
// Confirm
|
| 119 |
+
if(!confirm){setErr(confirmErr,'fg-confirm','Please confirm your password.');valid=false;}
|
| 120 |
+
else if(confirm!==pass){setErr(confirmErr,'fg-confirm','Passwords do not match.');valid=false;}
|
| 121 |
+
else setOk(confirmErr,'fg-confirm');
|
| 122 |
+
|
| 123 |
+
if(!valid) return;
|
| 124 |
+
|
| 125 |
+
// Prevent admin email
|
| 126 |
+
if(email==='admin@speakflow.ai'){globalErr.textContent='β This email is reserved.';return;}
|
| 127 |
+
|
| 128 |
+
// Check duplicate
|
| 129 |
+
const users = JSON.parse(localStorage.getItem('sf_users')||'[]');
|
| 130 |
+
if(users.find(u=>u.email===email)){globalErr.textContent='β An account with this email already exists.';return;}
|
| 131 |
+
|
| 132 |
+
// Show loader
|
| 133 |
+
btnText.style.display='none'; btnLoader.style.display='flex';
|
| 134 |
+
|
| 135 |
+
setTimeout(()=>{
|
| 136 |
+
const newUser = {
|
| 137 |
+
id: 'usr_'+Date.now(),
|
| 138 |
+
name, email, password:pass,
|
| 139 |
+
createdAt: new Date().toISOString()
|
| 140 |
+
};
|
| 141 |
+
users.push(newUser);
|
| 142 |
+
localStorage.setItem('sf_users',JSON.stringify(users));
|
| 143 |
+
|
| 144 |
+
// Auto-login after signup
|
| 145 |
+
const session = {loggedIn:true, name, email, id:newUser.id};
|
| 146 |
+
localStorage.setItem('sf_session',JSON.stringify(session));
|
| 147 |
+
|
| 148 |
+
globalOk.textContent='β
Account created! Redirecting...';
|
| 149 |
+
globalOk.style.display='block';
|
| 150 |
+
btnText.style.display='flex'; btnLoader.style.display='none';
|
| 151 |
+
|
| 152 |
+
setTimeout(()=>{ window.location.href='index.html'; },1000);
|
| 153 |
+
},900);
|
| 154 |
+
});
|
| 155 |
+
|
| 156 |
+
/* ββ Live validation on blur βββββββββββββββββββββββββββββββββ */
|
| 157 |
+
nameInp.addEventListener('blur',()=>{ if(!nameInp.value.trim()) setErr(nameErr,'fg-name','Full name is required.'); });
|
| 158 |
+
emailInp.addEventListener('blur',()=>{
|
| 159 |
+
const v=emailInp.value.trim();
|
| 160 |
+
if(!v) setErr(emailErr,'fg-email','Email is required.');
|
| 161 |
+
else if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) setErr(emailErr,'fg-email','Enter a valid email address.');
|
| 162 |
+
});
|
| 163 |
+
passInp.addEventListener('blur',()=>{ if(!passInp.value) setErr(passErr,'fg-pass','Password is required.'); });
|
| 164 |
+
confirmInp.addEventListener('blur',()=>{
|
| 165 |
+
if(confirmInp.value && confirmInp.value!==passInp.value) setErr(confirmErr,'fg-confirm','Passwords do not match.');
|
| 166 |
+
else if(!confirmInp.value) setErr(confirmErr,'fg-confirm','Please confirm your password.');
|
| 167 |
+
});
|