earwigmoth commited on
Commit
67eb5fb
Β·
verified Β·
1 Parent(s): fbcba92
Files changed (3) hide show
  1. signup.css +80 -0
  2. signup.html +107 -0
  3. 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
+ });