feat: Implement login functionality with captcha verification and associated UI.

This commit is contained in:
Wartana
2026-03-10 15:54:17 +08:00
parent 03269c9430
commit 5714ef4877
3 changed files with 259 additions and 1 deletions

103
index.css
View File

@@ -311,4 +311,107 @@ footer {
color: var(--text-muted);
}
/* Login Overlay Styles */
.login-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
transition: opacity 0.5s ease;
}
.login-card {
background: var(--card-bg);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 24px;
padding: 32px;
width: 90%;
max-width: 400px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3);
animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.login-header {
text-align: center;
margin-bottom: 24px;
}
.login-header p {
color: var(--text-muted);
font-size: 0.9rem;
}
.full-width {
width: 100%;
}
.captcha-container {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
background: rgba(255, 255, 255, 0.5);
padding: 8px;
border-radius: 12px;
border: 1px solid var(--border);
}
#captchaCanvas {
background: #fff;
border-radius: 8px;
flex-grow: 1;
}
.icon-btn {
background: #f3f4f6;
color: var(--text);
padding: 8px;
border-radius: 8px;
transition: background 0.2s;
}
.icon-btn:hover {
background: #e5e7eb;
}
.error-message {
background: rgba(239, 68, 68, 0.1);
color: var(--danger);
padding: 10px;
border-radius: 8px;
font-size: 0.85rem;
margin-bottom: 16px;
text-align: center;
border: 1px solid rgba(239, 68, 68, 0.2);
animation: shake 0.4s ease-in-out;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
50% {
transform: translateX(5px);
}
75% {
transform: translateX(-5px);
}
}
/* Base CSS End */

View File

@@ -12,9 +12,51 @@
</head>
<body>
<div id="loginOverlay" class="login-overlay">
<div class="login-card">
<header class="login-header">
<div class="icon-container">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</div>
<h1>Login Access</h1>
<p>Please enter your credentials to continue</p>
</header>
<form id="loginForm">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" placeholder="Type admin" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" placeholder="Type admin" required>
</div>
<div class="form-group">
<label>Security Verification</label>
<div class="captcha-container">
<canvas id="captchaCanvas" width="150" height="50"></canvas>
<button type="button" id="refreshCaptcha" class="icon-btn" title="Refresh Captcha">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path>
<path d="M21 3v5h-5"></path>
</svg>
</button>
</div>
<input type="text" id="captchaInput" placeholder="Enter characters above" required>
</div>
<div id="loginError" class="error-message" style="display: none;"></div>
<button type="submit" class="primary-btn full-width">Sign In</button>
</form>
</div>
</div>
<div class="app-background"></div>
<div class="container">
<div class="container" id="appContainer" style="display: none;">
<header class="app-header">
<div class="icon-container">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
@@ -99,6 +141,7 @@
</footer>
</div>
<script src="login.js"></script>
<script src="app.js"></script>
</body>

112
login.js Normal file
View File

@@ -0,0 +1,112 @@
// Login and Captcha Logic for btlabel
document.addEventListener('DOMContentLoaded', () => {
const loginForm = document.getElementById('loginForm');
const loginOverlay = document.getElementById('loginOverlay');
const appContainer = document.getElementById('appContainer');
const loginError = document.getElementById('loginError');
const captchaCanvas = document.getElementById('captchaCanvas');
const refreshCaptchaBtn = document.getElementById('refreshCaptcha');
const captchaInput = document.getElementById('captchaInput');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
let currentCaptcha = '';
// Generate random captcha string
const generateCaptcha = () => {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude similar looking chars like 0, O, I, 1
let result = '';
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
// Draw captcha on canvas
const drawCaptcha = (text) => {
const ctx = captchaCanvas.getContext('2d');
ctx.clearRect(0, 0, captchaCanvas.width, captchaCanvas.height);
// Background noise - lines
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.3)`;
ctx.beginPath();
ctx.moveTo(Math.random() * captchaCanvas.width, Math.random() * captchaCanvas.height);
ctx.lineTo(Math.random() * captchaCanvas.width, Math.random() * captchaCanvas.height);
ctx.stroke();
}
// Background noise - dots
for (let i = 0; i < 30; i++) {
ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.3)`;
ctx.beginPath();
ctx.arc(Math.random() * captchaCanvas.width, Math.random() * captchaCanvas.height, 1, 0, Math.PI * 2);
ctx.fill();
}
// Draw text
ctx.font = 'bold 30px Arial';
ctx.textBaseline = 'middle';
const spacing = captchaCanvas.width / (text.length + 1);
for (let i = 0; i < text.length; i++) {
ctx.fillStyle = `rgb(${Math.random() * 100}, ${Math.random() * 100}, ${Math.random() * 100})`;
const x = spacing * (i + 1);
const y = captchaCanvas.height / 2 + (Math.random() * 10 - 5);
const angle = (Math.random() * 0.4) - 0.2;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillText(text[i], -10, 10);
ctx.restore();
}
};
const refreshCaptcha = () => {
currentCaptcha = generateCaptcha();
drawCaptcha(currentCaptcha);
captchaInput.value = '';
};
// Initial captcha
refreshCaptcha();
refreshCaptchaBtn.addEventListener('click', refreshCaptcha);
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
const username = usernameInput.value;
const password = passwordInput.value;
const captcha = captchaInput.value.toUpperCase();
loginError.style.display = 'none';
if (captcha !== currentCaptcha) {
loginError.textContent = 'Invalid security code. Please try again.';
loginError.style.display = 'block';
refreshCaptcha();
return;
}
// Placeholder credentials
if (username === 'admin' && password === 'admin') {
// Success
loginOverlay.style.opacity = '0';
setTimeout(() => {
loginOverlay.style.display = 'none';
appContainer.style.display = 'block';
// Trigger any initializations needed for the app
if (typeof updatePreview === 'function') {
updatePreview();
}
}, 500);
} else {
loginError.textContent = 'Invalid username or password.';
loginError.style.display = 'block';
refreshCaptcha();
}
});
});