-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
273 lines (248 loc) · 18.9 KB
/
index.html
File metadata and controls
273 lines (248 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Form Mockup</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Simple transition for view switching */
.view {
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
will-change: opacity, transform;
}
.view.hidden {
opacity: 0;
transform: scale(0.98);
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
/* Padding is now handled by the container to ensure consistency */
}
/* Adjust for card padding */
.card-container {
min-height: 480px; /* Set a min-height to prevent layout shifts */
}
/* OTP input styling */
.otp-input {
width: 3rem;
height: 3.5rem;
text-align: center;
font-size: 1.25rem;
border-radius: 0.5rem;
border: 1px solid;
transition: all 0.2s;
}
</style>
</head>
<body class="bg-slate-100 dark:bg-slate-900 flex items-center justify-center min-h-screen">
<div class="w-full max-w-md p-4">
<!-- Login Card -->
<div class="relative bg-white dark:bg-slate-800 rounded-2xl shadow-xl card-container">
<!-- This container now handles the padding for all views inside it -->
<div class="relative h-full p-8">
<!-- Initial View -->
<div id="initial-view" class="view">
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-slate-800 dark:text-white">Sign In</h1>
<p class="text-slate-500 dark:text-slate-400 mt-2">Welcome back! Choose your sign-in method.</p>
</div>
<button class="w-full flex items-center justify-center gap-3 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg px-4 py-3 text-slate-700 dark:text-slate-200 font-medium hover:bg-slate-50 dark:hover:bg-slate-600 transition-colors duration-200">
<svg class="w-5 h-5" viewBox="0 0 48 48"><path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"></path><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"></path><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"></path><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571l6.19,5.238C42.012,35.846,44,30.138,44,24C44,22.659,43.862,21.35,43.611,20.083z"></path></svg>
Sign in with Google
</button>
<div class="flex items-center my-6"><hr class="w-full border-slate-300 dark:border-slate-600"><span class="px-3 text-slate-500 dark:text-slate-400 text-sm font-medium">OR</span><hr class="w-full border-slate-300 dark:border-slate-600"></div>
<form id="email-form">
<div class="mb-4">
<label for="email" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Email Address</label>
<input type="email" id="email" name="email" required placeholder="[email protected]" class="w-full px-4 py-2.5 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-slate-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-shadow">
</div>
<button type="submit" class="w-full bg-blue-600 text-white font-semibold rounded-lg px-4 py-3 hover:bg-blue-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-slate-800">Continue with Email</button>
</form>
</div>
<!-- Email Options View -->
<div id="email-options-view" class="view hidden">
<div class="flex items-center mb-8"><button class="back-button p-2 rounded-full hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors" data-target="initial-view"><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-500 dark:text-slate-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" /></svg></button><div class="text-center flex-grow"><h1 class="text-2xl font-bold text-slate-800 dark:text-white">Choose a method</h1><p id="user-email-display" class="text-slate-500 dark:text-slate-400 mt-2 truncate"></p></div><div class="w-5"></div></div>
<div class="space-y-4">
<button id="show-email-otp" class="w-full flex items-center text-left gap-4 p-4 border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors duration-200"><div class="bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400 p-2.5 rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg></div><div><p class="font-semibold text-slate-800 dark:text-slate-100">Email me a code</p><p class="text-sm text-slate-500 dark:text-slate-400">We'll send an OTP to your inbox.</p></div></button>
<button id="show-auth-app" class="w-full flex items-center text-left gap-4 p-4 border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors duration-200"><div class="bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 p-2.5 rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 20.944a11.955 11.955 0 0118-8.618z" /></svg></div><div><p class="font-semibold text-slate-800 dark:text-slate-100">Use authenticator app</p><p class="text-sm text-slate-500 dark:text-slate-400">Use a code from an authenticator app.</p></div></button>
<button id="show-password" class="w-full flex items-center text-left gap-4 p-4 border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors duration-200"><div class="bg-purple-100 dark:bg-purple-900/50 text-purple-600 dark:text-purple-400 p-2.5 rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg></div><div><p class="font-semibold text-slate-800 dark:text-slate-100">Sign in with password</p><p class="text-sm text-slate-500 dark:text-slate-400">Use your account password.</p></div></button>
<button id="show-sms-otp" class="w-full flex items-center text-left gap-4 p-4 border border-slate-300 dark:border-slate-600 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors duration-200"><div class="bg-sky-100 dark:bg-sky-900/50 text-sky-600 dark:text-sky-400 p-2.5 rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg></div><div><p class="font-semibold text-slate-800 dark:text-slate-100">Text me a code (SMS)</p><p class="text-sm text-slate-500 dark:text-slate-400">We'll text an OTP to your phone.</p></div></button>
</div>
</div>
<!-- Inner Step Views -->
<div id="email-otp-view" class="view hidden"></div>
<div id="auth-app-view" class="view hidden"></div>
<div id="password-view" class="view hidden"></div>
<div id="sms-otp-view" class="view hidden"></div>
</div>
</div>
<footer class="text-center mt-6 text-sm text-slate-500 dark:text-slate-400">
<p>Don't have an account? <a href="#" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Sign up</a></p>
</footer>
</div>
<script>
// --- DOM Element References ---
const views = document.querySelectorAll('.view');
const emailForm = document.getElementById('email-form');
const emailInput = document.getElementById('email');
const userEmailDisplay = document.getElementById('user-email-display');
// --- View Content Templates ---
const viewTemplates = {
'email-otp-view': {
title: 'Check your email',
content: `<p class="text-slate-500 dark:text-slate-400 mt-2 mb-6">We sent a 6-digit code to <b class="user-email-display text-slate-600 dark:text-slate-300"></b>. The code expires shortly, so please enter it soon.</p>
<div class="flex justify-center gap-2 otp-container">${Array(6).fill(0).map((_, i) => `<input type="text" maxlength="1" data-index="${i}" class="otp-input bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">`).join('')}</div>
<p class="text-center mt-6 text-sm text-slate-500 dark:text-slate-400">Didn't get a code? <a href="#" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Resend</a></p>`
},
'auth-app-view': {
title: 'Authenticator code',
content: `<p class="text-slate-500 dark:text-slate-400 mt-2 mb-6">Open your authenticator app (e.g., Google, Microsoft) and enter the 6-digit code.</p>
<div class="flex justify-center gap-2 otp-container">${Array(6).fill(0).map((_, i) => `<input type="text" maxlength="1" data-index="${i}" class="otp-input bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">`).join('')}</div>`
},
'password-view': {
title: 'Enter your password',
content: `<p class="text-slate-500 dark:text-slate-400 mt-2 mb-6">Signing in as <b class="user-email-display text-slate-600 dark:text-slate-300"></b>.</p>
<div class="relative">
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" required placeholder="Your password" class="w-full px-4 py-2.5 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-slate-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-shadow">
</div>
<button type="submit" class="mt-6 w-full bg-blue-600 text-white font-semibold rounded-lg px-4 py-3 hover:bg-blue-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-slate-800">Sign In</button>
<p class="text-center mt-4 text-sm"><a href="#" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Forgot password?</a></p>`
},
'sms-otp-view': {
title: 'Check your phone',
content: `<p class="text-slate-500 dark:text-slate-400 mt-2 mb-6">We sent a 6-digit code to your phone number ending in <b>••82</b>.</p>
<div class="flex justify-center gap-2 otp-container">${Array(6).fill(0).map((_, i) => `<input type="text" maxlength="1" data-index="${i}" class="otp-input bg-white dark:bg-slate-700 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">`).join('')}</div>
<p class="text-center mt-6 text-sm text-slate-500 dark:text-slate-400">Didn't get it? <a href="#" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Resend code</a></p>`
}
};
// --- Functions ---
/**
* Manages the transition between all views.
* @param {string} viewId The ID of the view to make visible.
*/
function switchToView(viewId) {
const allViews = document.querySelectorAll('.view');
allViews.forEach(view => {
// We add 'hidden' to all, then remove it from the one we want to show
if (view.id !== viewId) {
view.classList.add('hidden');
}
});
const viewToShow = document.getElementById(viewId);
if (viewToShow) {
viewToShow.classList.remove('hidden');
// For OTP views, focus the first input
const firstOtpInput = viewToShow.querySelector('.otp-input');
if (firstOtpInput) {
firstOtpInput.focus();
}
}
}
/**
* Populates a view with its title and content from the template.
* @param {string} viewId The ID of the view to populate.
*/
function populateView(viewId) {
const view = document.getElementById(viewId);
const template = viewTemplates[viewId];
if (!view || !template) return;
// The content is now placed inside a container that handles padding
view.innerHTML = `
<div class="flex items-center mb-8">
<button class="back-button p-2 -ml-2 rounded-full hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors" data-target="email-options-view">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-500 dark:text-slate-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" /></svg>
</button>
<div class="text-center flex-grow">
<h1 class="text-2xl font-bold text-slate-800 dark:text-white">${template.title}</h1>
</div>
<div class="w-5"></div>
</div>
${template.content}
`;
}
/**
* Handles OTP input logic: auto-focus next, backspace, and paste.
* @param {Event} e The input or keydown event.
*/
function handleOtpInput(e) {
const input = e.target;
if (!input.matches('.otp-input')) return;
const value = input.value;
const index = parseInt(input.dataset.index);
if (e.type === 'paste') {
e.preventDefault();
const paste = (e.clipboardData || window.clipboardData).getData('text');
const inputs = input.closest('.otp-container').querySelectorAll('.otp-input');
for (let i = 0; i < paste.length; i++) {
if (inputs[i]) {
inputs[i].value = paste[i] || '';
}
}
const focusIndex = Math.min(paste.length, inputs.length - 1);
if(inputs[focusIndex]) inputs[focusIndex].focus();
return;
}
// Move to next input
if (value && index < 5) {
input.nextElementSibling.focus();
}
// Handle backspace
if (e.key === 'Backspace' && !value && index > 0) {
input.previousElementSibling.focus();
}
}
// --- Event Listeners Setup ---
// Initial email form submission
emailForm.addEventListener('submit', (event) => {
event.preventDefault();
const email = emailInput.value;
if (email) {
document.querySelectorAll('.user-email-display').forEach(el => el.textContent = email);
userEmailDisplay.textContent = email; // For the options view header
switchToView('email-options-view');
}
});
// Delegate clicks for view switching from the main container
document.querySelector('.card-container').addEventListener('click', (e) => {
const button = e.target.closest('button');
if (!button) return;
// Back buttons
if (button.classList.contains('back-button')) {
const targetView = button.dataset.target;
if (targetView) switchToView(targetView);
return;
}
// Option buttons
const viewMap = {
'show-email-otp': 'email-otp-view',
'show-auth-app': 'auth-app-view',
'show-password': 'password-view',
'show-sms-otp': 'sms-otp-view'
};
const targetViewId = viewMap[button.id];
if (targetViewId) {
switchToView(targetViewId);
}
});
// Delegate events for OTP inputs
document.querySelector('.card-container').addEventListener('input', handleOtpInput);
document.querySelector('.card-container').addEventListener('keydown', handleOtpInput);
document.querySelector('.card-container').addEventListener('paste', (e) => {
if(e.target.matches('.otp-input')) handleOtpInput(e);
});
// --- Initial State ---
Object.keys(viewTemplates).forEach(populateView);
switchToView('initial-view');
</script>
</body>
</html>