Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Start Suite - Login</title> | |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"> | |
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='icon.png') }}"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Audiowide&family=Lexend:wght@100..900&display=swap'); | |
* { | |
margin: 0; | |
padding: 0; | |
font-family: "Lexend", sans-serif; | |
} | |
body { | |
font-family: 'Roboto', sans-serif; | |
background: #4285f4; | |
min-height: 100vh; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: #333; | |
overflow: hidden; | |
position: relative; | |
} | |
/* Rain Effect Styles */ | |
.rain-container { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
z-index: 1; | |
} | |
.rain-drop { | |
position: absolute; | |
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.2)); | |
border-radius: 50px; | |
animation: fall linear infinite; | |
opacity: 0.7; | |
} | |
@keyframes fall { | |
0% { | |
transform: translateY(-20px); | |
opacity: 1; | |
} | |
100% { | |
transform: translateY(100vh); | |
opacity: 0.3; | |
} | |
} | |
.auth-container { | |
background: rgba(255, 255, 255, 0.95); | |
border-radius: 20px; | |
padding: 40px; | |
width: 100%; | |
max-width: 400px; | |
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
backdrop-filter: blur(10px); | |
position: relative; | |
z-index: 2; | |
} | |
.auth-header { | |
text-align: center; | |
margin-bottom: 30px; | |
} | |
.auth-title { | |
font-size: 2rem; | |
font-weight: 700; | |
color: #333; | |
margin-bottom: 10px; | |
} | |
.auth-subtitle { | |
color: #666; | |
font-size: 1rem; | |
} | |
.auth-form { | |
display: flex; | |
flex-direction: column; | |
gap: 20px; | |
} | |
.form-group { | |
position: relative; | |
} | |
.form-input { | |
width: calc(100% - 42px); | |
padding: 15px 20px; | |
border: 2px solid #e1e5e9; | |
border-radius: 10px; | |
font-size: 1rem; | |
transition: all 0.3s ease; | |
background: white; | |
} | |
.form-input:focus { | |
outline: none; | |
border-color: #667eea; | |
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
} | |
.form-input::placeholder { | |
color: #999; | |
} | |
.checkbox-group { | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
margin-top: 5px; | |
} | |
.checkbox-input { | |
width: 18px; | |
height: 18px; | |
accent-color: #667eea; | |
cursor: pointer; | |
} | |
.checkbox-label { | |
font-size: 0.9rem; | |
color: #666; | |
cursor: pointer; | |
user-select: none; | |
} | |
.auth-btn { | |
padding: 15px 30px; | |
border: none; | |
border-radius: 10px; | |
font-size: 1rem; | |
font-weight: 500; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
background: #667eea; | |
color: white; | |
} | |
.auth-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3); | |
} | |
.auth-btn:active { | |
transform: translateY(0); | |
} | |
.auth-toggle { | |
text-align: center; | |
margin-top: 20px; | |
} | |
.auth-toggle a { | |
color: #667eea; | |
text-decoration: none; | |
font-weight: 500; | |
cursor: pointer; | |
} | |
.auth-toggle a:hover { | |
text-decoration: underline; | |
} | |
.error-message { | |
color: #e74c3c; | |
font-size: 0.9rem; | |
margin-top: 10px; | |
text-align: center; | |
} | |
.success-message { | |
color: #27ae60; | |
font-size: 0.9rem; | |
margin-top: 10px; | |
text-align: center; | |
} | |
.hidden { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="rain-container" id="rainContainer"></div> | |
<div class="auth-container"> | |
<div class="auth-header"> | |
<h1 class="auth-title">Start Suite</h1> | |
<p class="auth-subtitle">Your personalized dashboard</p> | |
</div> | |
<form class="auth-form" id="loginForm"> | |
<div class="form-group"> | |
<input type="text" class="form-input" id="username" placeholder="Username" required> | |
</div> | |
<div class="form-group"> | |
<input type="password" class="form-input" id="password" placeholder="Password" required> | |
</div> | |
<div class="form-group" id="rememberMeGroup"> | |
<div class="checkbox-group"> | |
<input type="checkbox" class="checkbox-input" id="rememberMe" checked> | |
<label for="rememberMe" class="checkbox-label">Remember me for 30 days</label> | |
</div> | |
</div> | |
<button type="submit" class="auth-btn" id="authBtn">Login</button> | |
<div class="error-message hidden" id="errorMessage"></div> | |
<div class="success-message hidden" id="successMessage"></div> | |
</form> | |
<div class="auth-toggle"> | |
<span id="toggleText">Don't have an account? </span> | |
<a href="#" id="toggleLink">Sign up</a> | |
</div> | |
</div> | |
<script> | |
let isLoginMode = true; | |
const form = document.getElementById('loginForm'); | |
const authBtn = document.getElementById('authBtn'); | |
const toggleLink = document.getElementById('toggleLink'); | |
const toggleText = document.getElementById('toggleText'); | |
const errorMessage = document.getElementById('errorMessage'); | |
const successMessage = document.getElementById('successMessage'); | |
const rememberMeGroup = document.getElementById('rememberMeGroup'); | |
const rememberMeCheckbox = document.getElementById('rememberMe'); | |
function showError(message) { | |
errorMessage.textContent = message; | |
errorMessage.classList.remove('hidden'); | |
successMessage.classList.add('hidden'); | |
} | |
function showSuccess(message) { | |
successMessage.textContent = message; | |
successMessage.classList.remove('hidden'); | |
errorMessage.classList.add('hidden'); | |
} | |
function hideMessages() { | |
errorMessage.classList.add('hidden'); | |
successMessage.classList.add('hidden'); | |
} | |
toggleLink.addEventListener('click', (e) => { | |
e.preventDefault(); | |
isLoginMode = !isLoginMode; | |
if (isLoginMode) { | |
authBtn.textContent = 'Login'; | |
toggleText.textContent = "Don't have an account? "; | |
toggleLink.textContent = 'Sign up'; | |
rememberMeGroup.style.display = 'block'; // Show remember me for login | |
} else { | |
authBtn.textContent = 'Sign Up'; | |
toggleText.textContent = 'Already have an account? '; | |
toggleLink.textContent = 'Login'; | |
rememberMeGroup.style.display = 'none'; // Hide remember me for signup | |
} | |
hideMessages(); | |
}); | |
form.addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
const username = document.getElementById('username').value; | |
const password = document.getElementById('password').value; | |
const rememberMe = rememberMeCheckbox.checked; | |
if (!username || !password) { | |
showError('Please fill in all fields'); | |
return; | |
} | |
const endpoint = isLoginMode ? '/login' : '/register'; | |
const requestBody = { username, password }; | |
// Add rememberMe flag for login requests | |
if (isLoginMode) { | |
requestBody.rememberMe = rememberMe; | |
} | |
try { | |
const response = await fetch(endpoint, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify(requestBody) | |
}); | |
const data = await response.json(); | |
if (response.ok) { | |
showSuccess(data.message); | |
setTimeout(() => { | |
window.location.href = '/'; | |
}, 1000); | |
} else { | |
showError(data.error); | |
} | |
} catch (error) { | |
showError('An error occurred. Please try again.'); | |
} | |
}); | |
// Rain Effect | |
class RainEffect { | |
constructor() { | |
this.rainContainer = document.getElementById('rainContainer'); | |
this.rainDrops = []; | |
this.maxDrops = 100; | |
this.init(); | |
} | |
init() { | |
this.createRain(); | |
this.animateRain(); | |
} | |
createRain() { | |
for (let i = 0; i < this.maxDrops; i++) { | |
this.createRainDrop(); | |
} | |
} | |
createRainDrop() { | |
const drop = document.createElement('div'); | |
drop.className = 'rain-drop'; | |
// Random horizontal position | |
const x = Math.random() * window.innerWidth; | |
// Random size variation | |
const size = Math.random() * 0.8 + 0.2; | |
drop.style.width = `${2 * size}px`; | |
drop.style.height = `${20 * size}px`; | |
// Random speed (duration) | |
const duration = Math.random() * 2 + 1; // 1-3 seconds | |
drop.style.animationDuration = `${duration}s`; | |
// Random delay | |
const delay = Math.random() * 2; | |
drop.style.animationDelay = `${delay}s`; | |
// Position the drop | |
drop.style.left = `${x}px`; | |
drop.style.top = '-20px'; | |
this.rainContainer.appendChild(drop); | |
this.rainDrops.push(drop); | |
} | |
animateRain() { | |
// Clean up and recreate drops periodically | |
setInterval(() => { | |
this.rainDrops.forEach(drop => { | |
const rect = drop.getBoundingClientRect(); | |
if (rect.top > window.innerHeight) { | |
// Reset the drop to the top with new random position | |
drop.style.left = `${Math.random() * window.innerWidth}px`; | |
drop.style.top = '-20px'; | |
// Randomize properties again | |
const size = Math.random() * 0.8 + 0.2; | |
drop.style.width = `${2 * size}px`; | |
drop.style.height = `${20 * size}px`; | |
const duration = Math.random() * 2 + 1; | |
drop.style.animationDuration = `${duration}s`; | |
} | |
}); | |
}, 100); | |
} | |
} | |
// Initialize rain effect when the page loads | |
new RainEffect(); | |
</script> | |
</body> | |
</html> |