Hướng dẫn code UI Auth Dashboard cao cấp
Mình đề xuất làm theo hướng UI Auth Dashboard cao cấp, không phải kiểu form đăng nhập mẫu thông thường: có Login / Register / Forgot Password / OTP / Social Login / Show Password / Remember Me / Password Strength / chuyển đổi form bằng animation / responsive mobile / loading / validation /
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Account Center - Đăng nhập tài khoản</title>
<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=Be+Vietnam+Pro:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="auth-page">
<!-- BACKGROUND -->
<div class="background">
<div class="orb orb-one"></div>
<div class="orb orb-two"></div>
<div class="orb orb-three"></div>
<div class="grid-background"></div>
<div class="noise"></div>
</div>
<!-- HEADER -->
<header class="auth-header">
<a href="#" class="brand">
<span class="brand-mark">M</span>
<span class="brand-text">
MUATHEME
<small>ACCOUNT CENTER</small>
</span>
</a>
<div class="secure-status">
<span class="secure-dot"></span>
Kết nối bảo mật
</div>
</header>
<!-- AUTH WRAPPER -->
<main class="auth-main">
<section class="auth-shell">
<!-- LEFT PANEL -->
<aside class="visual-panel">
<div class="visual-content">
<span class="visual-badge">
✦ ACCOUNT CENTER
</span>
<h1>
Một tài khoản.
<span>Mọi thứ.</span>
</h1>
<p>
Quản lý tài khoản, đơn hàng, sản phẩm,
giấy phép và toàn bộ dịch vụ của bạn
trong một không gian duy nhất.
</p>
<div class="feature-list">
<div class="feature">
<span class="feature-icon">⚡</span>
<div>
<strong>Truy cập nhanh</strong>
<small>Đăng nhập chỉ trong vài giây</small>
</div>
</div>
<div class="feature">
<span class="feature-icon">🔐</span>
<div>
<strong>Bảo mật nhiều lớp</strong>
<small>Bảo vệ thông tin tài khoản</small>
</div>
</div>
<div class="feature">
<span class="feature-icon">☁</span>
<div>
<strong>Đồng bộ dữ liệu</strong>
<small>Dữ liệu được đồng bộ trên mọi thiết bị</small>
</div>
</div>
</div>
<div class="user-preview">
<div class="avatars">
<span>TN</span>
<span>HP</span>
<span>MK</span>
<span>+</span>
</div>
<div>
<strong>Hàng nghìn người dùng</strong>
<small>đang sử dụng hệ thống</small>
</div>
</div>
</div>
<div class="visual-decoration">
<div class="ring ring-one"></div>
<div class="ring ring-two"></div>
<div class="ring ring-three"></div>
</div>
</aside>
<!-- FORM PANEL -->
<section class="form-panel">
<!-- LOGIN -->
<div class="auth-form login-form active">
<div class="form-heading">
<span class="mobile-logo">M</span>
<h2>Chào mừng trở lại</h2>
<p>
Đăng nhập để tiếp tục sử dụng tài khoản của bạn.
</p>
</div>
<form id="loginForm">
<div class="field">
<label for="loginEmail">
Email hoặc tên đăng nhập
</label>
<div class="input-box">
<span class="input-icon">✉</span>
<input
id="loginEmail"
type="text"
placeholder="you@example.com"
autocomplete="username"
>
</div>
<small class="field-error"></small>
</div>
<div class="field">
<div class="label-row">
<label for="loginPassword">
Mật khẩu
</label>
<button
type="button"
class="forgot-btn"
id="forgotBtn"
>
Quên mật khẩu?
</button>
</div>
<div class="input-box">
<span class="input-icon">◆</span>
<input
id="loginPassword"
type="password"
placeholder="Nhập mật khẩu"
autocomplete="current-password"
>
<button
type="button"
class="password-toggle"
data-target="loginPassword"
>
◉
</button>
</div>
<small class="field-error"></small>
</div>
<div class="form-options">
<label class="checkbox">
<input type="checkbox" checked>
<span></span>
Ghi nhớ đăng nhập
</label>
</div>
<button
type="submit"
class="submit-btn"
>
<span>Đăng nhập</span>
<b>→</b>
</button>
<div class="divider">
<span>hoặc tiếp tục với</span>
</div>
<div class="social-login">
<button type="button" class="social-btn">
<strong>G</strong>
Google
</button>
<button type="button" class="social-btn">
<strong>f</strong>
Facebook
</button>
</div>
<p class="switch-text">
Chưa có tài khoản?
<button type="button" data-switch="register">
Tạo tài khoản
</button>
</p>
</form>
</div>
<!-- REGISTER -->
<div class="auth-form register-form">
<div class="form-heading">
<span class="mobile-logo">M</span>
<h2>Tạo tài khoản</h2>
<p>
Đăng ký tài khoản mới để bắt đầu sử dụng.
</p>
</div>
<form id="registerForm">
<div class="name-grid">
<div class="field">
<label>Họ</label>
<div class="input-box">
<span class="input-icon">A</span>
<input
type="text"
placeholder="Nguyễn"
>
</div>
<small class="field-error"></small>
</div>
<div class="field">
<label>Tên</label>
<div class="input-box">
<span class="input-icon">A</span>
<input
type="text"
placeholder="Văn A"
>
</div>
<small class="field-error"></small>
</div>
</div>
<div class="field">
<label>Email</label>
<div class="input-box">
<span class="input-icon">✉</span>
<input
type="email"
placeholder="you@example.com"
>
</div>
<small class="field-error"></small>
</div>
<div class="field">
<label>Mật khẩu</label>
<div class="input-box">
<span class="input-icon">◆</span>
<input
id="registerPassword"
type="password"
placeholder="Tạo mật khẩu mạnh"
>
<button
type="button"
class="password-toggle"
data-target="registerPassword"
>
◉
</button>
</div>
<div class="password-strength">
<div class="strength-bars">
<i></i>
<i></i>
<i></i>
<i></i>
</div>
<span id="strengthText">
Nhập mật khẩu
</span>
</div>
<small class="field-error"></small>
</div>
<label class="checkbox terms">
<input type="checkbox">
<span></span>
Tôi đồng ý với
<a href="#">Điều khoản</a>
và
<a href="#">Chính sách bảo mật</a>
</label>
<button
type="submit"
class="submit-btn"
>
<span>Tạo tài khoản</span>
<b>→</b>
</button>
<div class="divider">
<span>hoặc đăng ký với</span>
</div>
<div class="social-login">
<button type="button" class="social-btn">
<strong>G</strong>
Google
</button>
<button type="button" class="social-btn">
<strong>f</strong>
Facebook
</button>
</div>
<p class="switch-text">
Đã có tài khoản?
<button type="button" data-switch="login">
Đăng nhập
</button>
</p>
</form>
</div>
<!-- FORGOT -->
<div class="auth-form forgot-form">
<button
type="button"
class="back-btn"
data-switch="login"
>
← Quay lại đăng nhập
</button>
<div class="form-heading">
<div class="forgot-icon">
✉
</div>
<h2>Khôi phục tài khoản</h2>
<p>
Nhập email và chúng tôi sẽ gửi hướng dẫn
đặt lại mật khẩu.
</p>
</div>
<form id="forgotForm">
<div class="field">
<label>Email tài khoản</label>
<div class="input-box">
<span class="input-icon">✉</span>
<input
type="email"
placeholder="you@example.com"
>
</div>
</div>
<button
type="submit"
class="submit-btn"
>
<span>Gửi liên kết</span>
<b>→</b>
</button>
</form>
</div>
</section>
</section>
</main>
<!-- FOOTER -->
<footer class="auth-footer">
<span>© 2026 Muathemewpgiare</span>
<div>
<a href="#">Điều khoản</a>
<a href="#">Bảo mật</a>
<a href="#">Hỗ trợ</a>
</div>
</footer>
</div>
<!-- TOAST -->
<div class="toast" id="toast">
<span class="toast-icon">✓</span>
<div>
<strong id="toastTitle">Thành công</strong>
<small id="toastMessage">
Thao tác đã được thực hiện.
</small>
</div>
</div>
<script src="script.js"></script>
</body>
</html>:root {
--primary: #6d28d9;
--primary-2: #9333ea;
--accent: #c026d3;
--dark: #101014;
--text: #18181d;
--muted: #777782;
--border: #e8e7ed;
--background: #f6f5f9;
--white: #ffffff;
--success: #16a34a;
--danger: #dc2626;
--radius: 22px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
min-height: 100%;
}
body {
min-height: 100vh;
font-family: "Be Vietnam Pro", sans-serif;
color: var(--text);
background: #f3f1f8;
}
button,
input {
font: inherit;
}
button {
border: 0;
cursor: pointer;
}
a {
text-decoration: none;
color: inherit;
}
/* ===============================
PAGE
================================ */
.auth-page {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* ===============================
BACKGROUND
================================ */
.background {
position: fixed;
inset: 0;
z-index: -1;
overflow: hidden;
background:
radial-gradient(
circle at 20% 10%,
rgba(124, 58, 237, .12),
transparent 30%
),
radial-gradient(
circle at 80% 80%,
rgba(192, 38, 211, .1),
transparent 30%
),
#f7f6fa;
}
.grid-background {
position: absolute;
inset: 0;
opacity: .25;
background-image:
linear-gradient(
rgba(100, 90, 120, .06) 1px,
transparent 1px
),
linear-gradient(
90deg,
rgba(100, 90, 120, .06) 1px,
transparent 1px
);
background-size: 45px 45px;
mask-image: linear-gradient(
to bottom,
black,
transparent 85%
);
}
.noise {
position: absolute;
inset: 0;
opacity: .025;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.6'/%3E%3C/svg%3E");
}
.orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
animation: floating 10s ease-in-out infinite;
}
.orb-one {
width: 280px;
height: 280px;
top: -120px;
left: -80px;
background: rgba(124, 58, 237, .14);
}
.orb-two {
width: 340px;
height: 340px;
right: -150px;
bottom: -150px;
background: rgba(192, 38, 211, .12);
animation-delay: -4s;
}
.orb-three {
width: 170px;
height: 170px;
right: 30%;
top: 10%;
background: rgba(59, 130, 246, .08);
animation-delay: -7s;
}
@keyframes floating {
0%,
100% {
transform: translate3d(0, 0, 0);
}
50% {
transform: translate3d(25px, -30px, 0);
}
}
/* ===============================
HEADER
================================ */
.auth-header {
width: min(1380px, calc(100% - 50px));
margin: auto;
min-height: 82px;
display: flex;
align-items: center;
justify-content: space-between;
}
.brand {
display: flex;
align-items: center;
gap: 11px;
}
.brand-mark {
width: 38px;
height: 38px;
display: grid;
place-items: center;
border-radius: 11px;
color: #fff;
font-size: 18px;
font-weight: 900;
background:
linear-gradient(
135deg,
var(--primary),
var(--accent)
);
box-shadow:
0 8px 25px rgba(109, 40, 217, .25);
}
.brand-text {
font-size: 14px;
font-weight: 900;
letter-spacing: -.02em;
}
.brand-text small {
display: block;
margin-top: -2px;
color: #9996a3;
font-size: 7px;
letter-spacing: .15em;
}
.secure-status {
display: flex;
align-items: center;
gap: 8px;
color: #74727d;
font-size: 10px;
font-weight: 600;
}
.secure-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #22c55e;
box-shadow: 0 0 0 5px rgba(34, 197, 94, .1);
animation: securePulse 2s infinite;
}
@keyframes securePulse {
50% {
box-shadow:
0 0 0 7px rgba(34, 197, 94, .03);
}
}
/* ===============================
MAIN
================================ */
.auth-main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 25px 20px 55px;
}
.auth-shell {
width: min(1080px, 100%);
min-height: 680px;
display: grid;
grid-template-columns: .88fr 1.12fr;
overflow: hidden;
border: 1px solid rgba(255,255,255,.8);
border-radius: 28px;
background: rgba(255,255,255,.76);
box-shadow:
0 40px 100px rgba(30, 20, 60, .12),
0 10px 30px rgba(30, 20, 60, .05);
backdrop-filter: blur(20px);
}
/* ===============================
VISUAL PANEL
================================ */
.visual-panel {
position: relative;
overflow: hidden;
padding: 55px 48px;
color: #fff;
background:
radial-gradient(
circle at 80% 20%,
rgba(255,255,255,.18),
transparent 25%
),
linear-gradient(
145deg,
#4c1d95,
#6d28d9 50%,
#a21caf
);
}
.visual-content {
position: relative;
z-index: 3;
}
.visual-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 11px;
border: 1px solid rgba(255,255,255,.18);
border-radius: 999px;
background: rgba(255,255,255,.09);
color: rgba(255,255,255,.85);
font-size: 8px;
font-weight: 800;
letter-spacing: .12em;
}
.visual-panel h1 {
margin-top: 25px;
max-width: 430px;
font-size: clamp(35px, 4vw, 51px);
line-height: 1.05;
letter-spacing: -.055em;
}
.visual-panel h1 span {
display: block;
color: #e9d5ff;
}
.visual-panel > .visual-content > p {
max-width: 400px;
margin-top: 20px;
color: rgba(255,255,255,.7);
font-size: 12px;
line-height: 1.8;
}
/* ===============================
FEATURES
================================ */
.feature-list {
margin-top: 40px;
display: grid;
gap: 15px;
}
.feature {
display: flex;
align-items: center;
gap: 13px;
}
.feature-icon {
width: 38px;
height: 38px;
display: grid;
place-items: center;
border: 1px solid rgba(255,255,255,.12);
border-radius: 11px;
background: rgba(255,255,255,.1);
font-size: 14px;
}
.feature strong,
.feature small {
display: block;
}
.feature strong {
font-size: 11px;
}
.feature small {
margin-top: 3px;
color: rgba(255,255,255,.52);
font-size: 9px;
}
/* ===============================
USERS
================================ */
.user-preview {
display: flex;
align-items: center;
gap: 12px;
margin-top: 42px;
padding-top: 22px;
border-top: 1px solid rgba(255,255,255,.12);
}
.avatars {
display: flex;
align-items: center;
}
.avatars span {
width: 27px;
height: 27px;
display: grid;
place-items: center;
margin-left: -5px;
border: 2px solid #6d28d9;
border-radius: 50%;
background: rgba(255,255,255,.18);
font-size: 7px;
font-weight: 800;
}
.avatars span:first-child {
margin-left: 0;
}
.user-preview strong,
.user-preview small {
display: block;
}
.user-preview strong {
font-size: 9px;
}
.user-preview small {
margin-top: 2px;
color: rgba(255,255,255,.5);
font-size: 8px;
}
/* ===============================
DECORATION
================================ */
.visual-decoration {
position: absolute;
inset: 0;
pointer-events: none;
}
.ring {
position: absolute;
border: 1px solid rgba(255,255,255,.09);
border-radius: 50%;
}
.ring-one {
width: 500px;
height: 500px;
right: -320px;
bottom: -150px;
}
.ring-two {
width: 350px;
height: 350px;
right: -210px;
bottom: -75px;
}
.ring-three {
width: 200px;
height: 200px;
right: -120px;
bottom: 0;
}
/* ===============================
FORM PANEL
================================ */
.form-panel {
position: relative;
display: flex;
align-items: center;
padding: 55px 75px;
background: rgba(255,255,255,.94);
}
.auth-form {
width: 100%;
display: none;
animation: formIn .45s cubic-bezier(.2,.8,.2,1);
}
.auth-form.active {
display: block;
}
@keyframes formIn {
from {
opacity: 0;
transform: translateX(18px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.form-heading {
margin-bottom: 30px;
}
.form-heading h2 {
font-size: 28px;
letter-spacing: -.045em;
}
.form-heading p {
max-width: 420px;
margin-top: 7px;
color: var(--muted);
font-size: 11px;
line-height: 1.7;
}
.mobile-logo {
display: none;
}
/* ===============================
FIELDS
================================ */
.field {
margin-bottom: 17px;
}
.field label {
display: block;
margin-bottom: 7px;
color: #44434b;
font-size: 10px;
font-weight: 700;
}
.label-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.forgot-btn {
padding: 0;
color: var(--primary);
background: transparent;
font-size: 9px;
font-weight: 700;
}
.input-box {
position: relative;
height: 50px;
display: flex;
align-items: center;
border: 1px solid var(--border);
border-radius: 11px;
background: #fbfbfc;
transition:
border .2s,
box-shadow .2s,
background .2s;
}
.input-box:focus-within {
border-color: rgba(109,40,217,.55);
background: #fff;
box-shadow:
0 0 0 4px rgba(109,40,217,.07);
}
.input-icon {
width: 45px;
display: grid;
place-items: center;
color: #a1a0aa;
font-size: 12px;
}
.input-box input {
width: 100%;
height: 100%;
border: 0;
outline: 0;
background: transparent;
color: #222229;
font-size: 11px;
}
.input-box input::placeholder {
color: #b5b4bd;
}
.password-toggle {
width: 40px;
height: 100%;
color: #a1a0aa;
background: transparent;
font-size: 12px;
transition: color .2s;
}
.password-toggle:hover {
color: var(--primary);
}
.field-error {
display: block;
min-height: 13px;
margin-top: 4px;
color: var(--danger);
font-size: 8px;
}
.field.invalid .input-box {
border-color: rgba(220,38,38,.45);
background: #fffafa;
}
.field.valid .input-box {
border-color: rgba(22,163,74,.35);
}
/* ===============================
CHECKBOX
================================ */
.checkbox {
display: flex;
align-items: center;
gap: 8px;
color: #77757f;
font-size: 9px;
line-height: 1.5;
cursor: pointer;
}
.checkbox input {
display: none;
}
.checkbox > span {
width: 16px;
height: 16px;
flex: 0 0 16px;
display: grid;
place-items: center;
border: 1px solid #d8d6df;
border-radius: 5px;
background: #fff;
transition: .2s;
}
.checkbox input:checked + span {
border-color: var(--primary);
background: var(--primary);
}
.checkbox input:checked + span::after {
content: "✓";
color: #fff;
font-size: 9px;
font-weight: 900;
}
.form-options {
margin: 2px 0 20px;
}
/* ===============================
BUTTON
================================ */
.submit-btn {
position: relative;
width: 100%;
height: 51px;
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
overflow: hidden;
border-radius: 11px;
background:
linear-gradient(
100deg,
#5b21b6,
#7c3aed,
#a21caf
);
color: #fff;
font-size: 11px;
font-weight: 800;
box-shadow:
0 12px 25px rgba(109,40,217,.2);
transition:
transform .2s,
box-shadow .2s;
}
.submit-btn::before {
content: "";
position: absolute;
top: 0;
left: -120%;
width: 70%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,.22),
transparent
);
transform: skewX(-20deg);
transition: left .65s ease;
}
.submit-btn:hover::before {
left: 150%;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow:
0 17px 35px rgba(109,40,217,.25);
}
.submit-btn b {
font-size: 16px;
font-weight: 400;
}
/* ===============================
SOCIAL
================================ */
.divider {
display: flex;
align-items: center;
gap: 12px;
margin: 23px 0 16px;
color: #aaa8b1;
font-size: 8px;
}
.divider::before,
.divider::after {
content: "";
height: 1px;
flex: 1;
background: #ecebf0;
}
.social-login {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.social-btn {
height: 43px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid var(--border);
border-radius: 10px;
background: #fff;
color: #55535d;
font-size: 9px;
font-weight: 700;
transition: .2s;
}
.social-btn:hover {
border-color: #c9c6d3;
transform: translateY(-1px);
}
.social-btn strong {
font-size: 14px;
}
/* ===============================
SWITCH
================================ */
.switch-text {
margin-top: 25px;
text-align: center;
color: #898792;
font-size: 9px;
}
.switch-text button {
padding: 0;
color: var(--primary);
background: transparent;
font-size: inherit;
font-weight: 800;
}
/* ===============================
REGISTER
================================ */
.name-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.terms {
margin: 3px 0 18px;
}
.terms a {
color: var(--primary);
font-weight: 700;
}
/* ===============================
PASSWORD STRENGTH
================================ */
.password-strength {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.strength-bars {
display: flex;
gap: 3px;
}
.strength-bars i {
width: 24px;
height: 3px;
border-radius: 999px;
background: #e6e5ea;
transition: .25s;
}
.password-strength span {
color: #9997a0;
font-size: 7px;
}
/* ===============================
FORGOT
================================ */
.back-btn {
margin-bottom: 28px;
padding: 0;
color: #7d7b85;
background: transparent;
font-size: 9px;
font-weight: 700;
}
.forgot-icon {
width: 48px;
height: 48px;
display: grid;
place-items: center;
margin-bottom: 18px;
border-radius: 15px;
background: #f0eaff;
color: var(--primary);
font-size: 17px;
}
/* ===============================
FOOTER
================================ */
.auth-footer {
width: min(1080px, calc(100% - 50px));
min-height: 65px;
margin: auto;
display: flex;
align-items: center;
justify-content: space-between;
color: #9996a1;
font-size: 8px;
}
.auth-footer div {
display: flex;
gap: 20px;
}
.auth-footer a:hover {
color: var(--primary);
}
/* ===============================
TOAST
================================ */
.toast {
position: fixed;
right: 25px;
bottom: 25px;
z-index: 9999;
min-width: 280px;
display: flex;
align-items: center;
gap: 11px;
padding: 13px 15px;
border: 1px solid rgba(255,255,255,.1);
border-radius: 13px;
background: #17151d;
color: #fff;
box-shadow:
0 20px 50px rgba(0,0,0,.18);
transform: translateY(120px);
opacity: 0;
pointer-events: none;
transition: .35s cubic-bezier(.2,.8,.2,1);
}
.toast.show {
transform: translateY(0);
opacity: 1;
}
.toast-icon {
width: 30px;
height: 30px;
display: grid;
place-items: center;
border-radius: 50%;
background: #22c55e;
font-size: 11px;
font-weight: 900;
}
.toast strong,
.toast small {
display: block;
}
.toast strong {
font-size: 9px;
}
.toast small {
margin-top: 2px;
color: #97939f;
font-size: 8px;
}
/* ===============================
RESPONSIVE
================================ */
@media (max-width: 900px) {
.auth-shell {
grid-template-columns: 1fr;
max-width: 560px;
}
.visual-panel {
display: none;
}
.form-panel {
padding: 55px 55px;
}
.mobile-logo {
width: 40px;
height: 40px;
display: grid;
place-items: center;
margin-bottom: 18px;
border-radius: 12px;
background: linear-gradient(
135deg,
var(--primary),
var(--accent)
);
color: #fff;
font-size: 18px;
font-weight: 900;
}
}
@media (max-width: 600px) {
.auth-header {
width: calc(100% - 30px);
}
.secure-status {
display: none;
}
.auth-main {
padding: 10px 14px 30px;
}
.auth-shell {
min-height: auto;
border-radius: 20px;
}
.form-panel {
padding: 35px 22px;
}
.form-heading h2 {
font-size: 25px;
}
.name-grid {
grid-template-columns: 1fr;
gap: 0;
}
.auth-footer {
width: calc(100% - 30px);
padding: 18px 0;
align-items: flex-start;
flex-direction: column;
gap: 10px;
}
.toast {
left: 15px;
right: 15px;
bottom: 15px;
min-width: 0;
}
}document.addEventListener("DOMContentLoaded", () => {
const forms = {
login: document.querySelector(".login-form"),
register: document.querySelector(".register-form"),
forgot: document.querySelector(".forgot-form")
};
/* ===============================
FORM SWITCH
================================= */
function switchForm(name) {
Object.values(forms).forEach(form => {
form.classList.remove("active");
});
forms[name].classList.add("active");
window.scrollTo({
top: 0,
behavior: "smooth"
});
}
document.querySelectorAll("[data-switch]").forEach(button => {
button.addEventListener("click", () => {
const target = button.dataset.switch;
if (forms[target]) {
switchForm(target);
}
});
});
/* ===============================
FORGOT PASSWORD
================================= */
document
.getElementById("forgotBtn")
.addEventListener("click", () => {
switchForm("forgot");
});
/* ===============================
PASSWORD SHOW / HIDE
================================= */
document
.querySelectorAll(".password-toggle")
.forEach(button => {
button.addEventListener("click", () => {
const targetId = button.dataset.target;
const input = document.getElementById(targetId);
if (!input) return;
if (input.type === "password") {
input.type = "text";
button.textContent = "◉";
} else {
input.type = "password";
button.textContent = "◉";
}
});
});
/* ===============================
PASSWORD STRENGTH
================================= */
const password = document.getElementById("registerPassword");
const strengthText = document.getElementById("strengthText");
const bars = document.querySelectorAll(".strength-bars i");
function checkPasswordStrength(value) {
let score = 0;
if (value.length >= 8) score++;
if (/[A-Z]/.test(value)) score++;
if (/[0-9]/.test(value)) score++;
if (/[^A-Za-z0-9]/.test(value)) score++;
bars.forEach((bar, index) => {
bar.style.background =
index < score
? "#7c3aed"
: "#e6e5ea";
});
const labels = [
"Nhập mật khẩu",
"Rất yếu",
"Yếu",
"Khá mạnh",
"Mật khẩu mạnh"
];
strengthText.textContent =
labels[score];
}
password.addEventListener("input", () => {
checkPasswordStrength(password.value);
});
/* ===============================
VALIDATION
================================= */
function setError(field, message) {
field.classList.remove("valid");
field.classList.add("invalid");
const error = field.querySelector(".field-error");
if (error) {
error.textContent = message;
}
}
function setValid(field) {
field.classList.remove("invalid");
field.classList.add("valid");
const error = field.querySelector(".field-error");
if (error) {
error.textContent = "";
}
}
function validateEmail(email) {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
}
/* ===============================
LOGIN
================================= */
document
.getElementById("loginForm")
.addEventListener("submit", event => {
event.preventDefault();
const email = document.getElementById("loginEmail");
const password =
document.getElementById("loginPassword");
let valid = true;
const emailField = email.closest(".field");
const passwordField =
password.closest(".field");
if (!email.value.trim()) {
setError(
emailField,
"Vui lòng nhập email hoặc tên đăng nhập."
);
valid = false;
} else {
setValid(emailField);
}
if (!password.value.trim()) {
setError(
passwordField,
"Vui lòng nhập mật khẩu."
);
valid = false;
} else if (password.value.length < 6) {
setError(
passwordField,
"Mật khẩu phải có ít nhất 6 ký tự."
);
valid = false;
} else {
setValid(passwordField);
}
if (!valid) return;
fakeSubmit(
event.target.querySelector(".submit-btn"),
"Đăng nhập thành công"
);
});
/* ===============================
REGISTER
================================= */
document
.getElementById("registerForm")
.addEventListener("submit", event => {
event.preventDefault();
const form = event.target;
const inputs =
form.querySelectorAll("input[type='text'], input[type='email'], input[type='password']");
let valid = true;
inputs.forEach(input => {
const field = input.closest(".field");
if (!input.value.trim()) {
setError(
field,
"Trường này không được để trống."
);
valid = false;
} else {
setValid(field);
}
});
const email =
form.querySelector("input[type='email']");
if (
email.value.trim() &&
!validateEmail(email.value)
) {
setError(
email.closest(".field"),
"Email không hợp lệ."
);
valid = false;
}
const password =
document.getElementById("registerPassword");
if (password.value.length < 8) {
setError(
password.closest(".field"),
"Mật khẩu cần ít nhất 8 ký tự."
);
valid = false;
}
const terms =
form.querySelector(".terms input");
if (!terms.checked) {
showToast(
"Cần xác nhận",
"Bạn cần đồng ý với điều khoản sử dụng."
);
valid = false;
}
if (!valid) return;
fakeSubmit(
form.querySelector(".submit-btn"),
"Tạo tài khoản thành công"
);
});
/* ===============================
FORGOT
================================= */
document
.getElementById("forgotForm")
.addEventListener("submit", event => {
event.preventDefault();
const email =
event.target.querySelector("input");
if (!validateEmail(email.value)) {
showToast(
"Email không hợp lệ",
"Vui lòng kiểm tra lại địa chỉ email."
);
return;
}
fakeSubmit(
event.target.querySelector(".submit-btn"),
"Đã gửi liên kết khôi phục"
);
});
/* ===============================
SUBMIT ANIMATION
================================= */
function fakeSubmit(button, message) {
const original = button.innerHTML;
button.disabled = true;
button.innerHTML = `
<span class="loading-spinner"></span>
Đang xử lý...
`;
setTimeout(() => {
button.disabled = false;
button.innerHTML = original;
showToast(
"Thành công",
message
);
}, 1200);
}
/* ===============================
TOAST
================================= */
const toast =
document.getElementById("toast");
const toastTitle =
document.getElementById("toastTitle");
const toastMessage =
document.getElementById("toastMessage");
let toastTimer;
function showToast(title, message) {
toastTitle.textContent = title;
toastMessage.textContent = message;
toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
toast.classList.remove("show");
}, 3500);
}
/* ===============================
SOCIAL LOGIN DEMO
================================= */
document
.querySelectorAll(".social-btn")
.forEach(button => {
button.addEventListener("click", () => {
const provider =
button.textContent.trim();
showToast(
"Đăng nhập nhanh",
`${provider} sẽ được kết nối tại đây.`
);
});
});
/* ===============================
INPUT MICRO INTERACTION
================================= */
document
.querySelectorAll(".input-box input")
.forEach(input => {
input.addEventListener("input", () => {
const field =
input.closest(".field");
if (
field.classList.contains("invalid") &&
input.value.trim()
) {
field.classList.remove("invalid");
const error =
field.querySelector(".field-error");
if (error) {
error.textContent = "";
}
}
});
});
});
Ctrl1 HTML
Ctrl2 CSS
Ctrl3 JS
Ctrl↵ Reload