feat(lessons): add new lessons for english section

This commit is contained in:
shafin-r
2026-03-09 16:41:06 +06:00
parent b5edb3554f
commit 59e601052f
38 changed files with 9086 additions and 1205 deletions

View File

@ -2,191 +2,363 @@ import { useState, useEffect } from "react";
import type { FormEvent } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { useAuthStore } from "../../stores/authStore";
import { Loader2, Mail, Lock } from "lucide-react";
import { Loader2, Mail, Lock, Target, Clock, BarChart2 } from "lucide-react";
interface LocationState {
from?: { pathname: string };
}
const DOTS = [
{ size: 12, color: "#f97316", top: "8%", left: "6%", delay: "0s" },
{ size: 7, color: "#a855f7", top: "22%", left: "3%", delay: "1.2s" },
{ size: 9, color: "#22c55e", top: "65%", left: "5%", delay: "0.6s" },
{ size: 8, color: "#f43f5e", top: "80%", left: "8%", delay: "2.1s" },
{ size: 12, color: "#3b82f6", top: "10%", right: "6%", delay: "1.8s" },
{ size: 7, color: "#eab308", top: "40%", right: "3%", delay: "0.9s" },
{ size: 10, color: "#a855f7", top: "72%", right: "5%", delay: "0.4s" },
{ size: 8, color: "#f97316", top: "55%", right: "8%", delay: "1.5s" },
];
const STYLES = `
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
.lg-screen {
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
.lg-root {
min-height: 100vh;
background: #fffbf4;
display: flex;
font-family: 'Nunito', sans-serif;
background: #fffbf4;
}
/* ─── LEFT PANEL ─── */
.lg-left {
position: relative;
width: 50%;
min-height: 100vh;
background: linear-gradient(160deg, #060d1f 0%, #0f2044 40%, #0e3476 75%, #1a56c4 100%);
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 3rem 3.5rem;
overflow: hidden;
flex-shrink: 0;
}
/* Animated grid */
.lg-grid {
position: absolute; inset: 0; pointer-events: none;
background-image:
linear-gradient(rgba(99,179,255,0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(99,179,255,0.06) 1px, transparent 1px);
background-size: 52px 52px;
animation: gridScroll 25s linear infinite;
}
@keyframes gridScroll {
from { background-position: 0 0; }
to { background-position: 52px 52px; }
}
/* Radial glow spots */
.lg-glow {
position: absolute; pointer-events: none; border-radius: 50%;
filter: blur(60px);
}
.lg-glow-1 { width: 380px; height: 380px; background: #1d4ed8; opacity: 0.35; top: -120px; right: -80px; animation: glowPulse 8s ease-in-out infinite; }
.lg-glow-2 { width: 280px; height: 280px; background: #0ea5e9; opacity: 0.2; bottom: -60px; left: -60px; animation: glowPulse 10s ease-in-out infinite 2s; }
.lg-glow-3 { width: 200px; height: 200px; background: #f97316; opacity: 0.12; top: 55%; left: 55%; animation: glowPulse 12s ease-in-out infinite 1s; }
@keyframes glowPulse {
0%,100% { transform: scale(1); opacity: 0.2; }
50% { transform: scale(1.2); opacity: 0.35; }
}
/* ── Floating score card ── */
.lg-score-card {
position: absolute;
top: 9%; right: 6%;
width: 162px;
background: linear-gradient(135deg, rgba(255,255,255,0.11), rgba(255,255,255,0.05));
border: 1px solid rgba(255,255,255,0.18);
border-radius: 20px;
padding: 1.1rem 1.2rem 1rem;
backdrop-filter: blur(16px);
box-shadow: 0 8px 32px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.15);
animation: floatA 6s ease-in-out infinite;
z-index: 2;
}
@keyframes floatA {
0%,100% { transform: translateY(0) rotate(-1.5deg); }
50% { transform: translateY(-14px) rotate(0.5deg); }
}
.lg-score-tag {
font-size: 0.58rem; font-weight: 800; letter-spacing: 0.14em;
text-transform: uppercase; color: #7dd3fc; margin-bottom: 0.5rem;
display: flex; align-items: center; gap: 0.3rem;
}
.lg-score-tag::before { content:''; width:6px;height:6px;border-radius:50%;background:#34d399;display:inline-block;box-shadow:0 0 6px #34d399; }
.lg-score-num {
font-size: 2.2rem; font-weight: 900; color: white; line-height: 1; margin-bottom: 0.2rem;
text-shadow: 0 2px 12px rgba(99,179,255,0.4);
}
.lg-score-delta {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.7rem; font-weight: 700; color: #4ade80;
display: flex; align-items: center; gap: 0.2rem; margin-bottom: 0.75rem;
}
.lg-bars {
display: flex; align-items: flex-end; gap: 3px; height: 40px;
}
.lg-bar {
flex: 1; border-radius: 3px 3px 0 0;
animation: barGrow 1s cubic-bezier(0.34,1.56,0.64,1) both;
transform-origin: bottom;
}
@keyframes barGrow {
from { transform: scaleY(0); }
to { transform: scaleY(1); }
}
/* ── Streak pill ── */
.lg-streak {
position: absolute;
bottom: 12%; left: 5%;
background: linear-gradient(135deg, rgba(249,115,22,0.18), rgba(239,68,68,0.1));
border: 1px solid rgba(249,115,22,0.4);
border-radius: 100px;
padding: 0.65rem 1.2rem;
backdrop-filter: blur(14px);
box-shadow: 0 4px 20px rgba(249,115,22,0.2), inset 0 1px 0 rgba(255,255,255,0.1);
display: flex; align-items: center; gap: 0.65rem;
animation: floatB 7s ease-in-out infinite 1s;
z-index: 2;
}
@keyframes floatB {
0%,100% { transform: translateY(0) rotate(1deg); }
50% { transform: translateY(-10px) rotate(-0.5deg); }
}
.lg-streak-fire { font-size: 1.5rem; filter: drop-shadow(0 0 8px #f97316); }
.lg-streak-text strong { display:block; font-size:0.85rem; font-weight:900; color:white; }
.lg-streak-text span { font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:600; color:#fed7aa; }
/* ── Questions badge ── */
.lg-q-badge {
position: absolute;
bottom: 28%; right: 5%;
background: linear-gradient(135deg, rgba(139,92,246,0.18), rgba(99,102,241,0.1));
border: 1px solid rgba(139,92,246,0.4);
border-radius: 16px;
padding: 0.7rem 1.05rem;
backdrop-filter: blur(14px);
box-shadow: 0 4px 20px rgba(139,92,246,0.2), inset 0 1px 0 rgba(255,255,255,0.1);
animation: floatA 9s ease-in-out infinite 0.5s;
z-index: 2;
}
.lg-q-badge p { font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:700; color:#c4b5fd; margin-bottom:0.15rem; }
.lg-q-badge strong { font-size:1.35rem; font-weight:900; color:white; display:block; text-shadow:0 0 20px rgba(167,139,250,0.5); }
/* ── Accuracy ring ── */
.lg-ring-wrap {
position: absolute;
top: 52%; left: 6%;
width: 80px; height: 80px;
animation: floatB 10s ease-in-out infinite 0.8s;
z-index: 2;
}
.lg-ring-svg { width: 80px; height: 80px; transform: rotate(-90deg); }
.lg-ring-bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 5; }
.lg-ring-fill { fill: none; stroke: #34d399; stroke-width: 5; stroke-linecap: round;
stroke-dasharray: 188; stroke-dashoffset: 18;
animation: ringFill 1.8s ease both 0.3s; }
@keyframes ringFill {
from { stroke-dashoffset: 188; }
to { stroke-dashoffset: 18; }
}
.lg-ring-label {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.lg-ring-label strong { font-size: 0.95rem; font-weight: 900; color: white; line-height: 1; }
.lg-ring-label span { font-family:'Nunito Sans',sans-serif; font-size: 0.52rem; font-weight: 700; color: #7dd3fc; text-transform: uppercase; letter-spacing: 0.05em; }
/* Scattered glitter dots */
.lg-glitter { position: absolute; border-radius: 50%; pointer-events: none; animation: glitterFloat 8s ease-in-out infinite; }
@keyframes glitterFloat {
0%,100% { transform: translateY(0) scale(1); opacity: 0.5; }
50% { transform: translateY(-16px) scale(1.3); opacity: 0.9; }
}
/* Stars */
.lg-star { position: absolute; pointer-events: none; animation: starTwinkle 2.5s ease-in-out infinite; color: #fde68a; }
@keyframes starTwinkle {
0%,100% { opacity: 0.4; transform: scale(0.9) rotate(0deg); }
50% { opacity: 1; transform: scale(1.4) rotate(20deg); }
}
/* Thin decorative rings */
.lg-deco-ring {
position: absolute; border-radius: 50%; pointer-events: none;
border: 1.5px solid rgba(255,255,255,0.07);
animation: decoSpin 40s linear infinite;
}
@keyframes decoSpin { to { transform: rotate(360deg); } }
/* Panel content */
.lg-panel-content {
position: relative; z-index: 3;
display: flex; flex-direction: column;
align-items: flex-start; gap: 2rem;
width: 100%;
}
.lg-panel-logo { display: flex; align-items: center; gap: 0.75rem; }
.lg-panel-logo-badge {
width: 46px; height: 46px; border-radius: 13px;
background: linear-gradient(135deg, #f97316, #ef4444);
display: flex; align-items: center; justify-content: center;
padding: 2rem 1.25rem;
box-shadow: 0 5px 0 rgba(0,0,0,0.3), 0 8px 20px rgba(249,115,22,0.45);
font-size: 1.3rem;
}
.lg-panel-logo-text { font-size: 1.35rem; font-weight: 900; color: white; letter-spacing: -0.02em; }
.lg-panel-headline { display: flex; flex-direction: column; gap: 0.6rem; }
.lg-panel-headline h2 {
font-size: 2.6rem; font-weight: 900; line-height: 1.1;
color: white; letter-spacing: -0.035em;
text-shadow: 0 4px 24px rgba(0,0,0,0.3);
}
.lg-panel-headline h2 span {
background: linear-gradient(90deg, #fbbf24 0%, #f97316 60%, #ef4444 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
filter: drop-shadow(0 0 16px rgba(249,115,22,0.4));
}
.lg-panel-headline p {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.92rem; font-weight: 600; color: #93c5fd; line-height: 1.65;
}
/* Blobs */
.lg-blob { position:fixed;pointer-events:none;z-index:0; }
.lg-blob-1 { width:280px;height:280px;background:#fde68a;top:-100px;left:-100px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:lgWobble1 14s ease-in-out infinite; }
.lg-blob-2 { width:220px;height:220px;background:#a5f3c0;bottom:-60px;left:4%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:lgWobble2 16s ease-in-out infinite; }
.lg-blob-3 { width:250px;height:250px;background:#fbcfe8;top:10%;right:-70px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:lgWobble1 18s ease-in-out infinite reverse; }
.lg-blob-4 { width:180px;height:180px;background:#bfdbfe;bottom:8%;right:0;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:lgWobble2 12s ease-in-out infinite; }
@keyframes lgWobble1 {
0%,100%{border-radius:60% 40% 70% 30%/50% 60% 40% 50%;transform:translate(0,0) rotate(0deg);}
50%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(14px,18px) rotate(8deg);}
/* Stats row */
.lg-stats { display: flex; gap: 0.75rem; width: 100%; }
.lg-stat {
flex: 1;
background: linear-gradient(135deg, rgba(255,255,255,0.09), rgba(255,255,255,0.04));
border: 1px solid rgba(255,255,255,0.13);
border-radius: 18px; padding: 0.9rem 0.85rem;
backdrop-filter: blur(12px);
box-shadow: 0 4px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.12);
display: flex; flex-direction: column; gap: 0.2rem;
animation: statSlide 0.5s ease both;
}
@keyframes lgWobble2 {
0%,100%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(0,0) rotate(0deg);}
50%{border-radius:60% 40% 70% 30%/40% 60% 40% 60%;transform:translate(-12px,14px) rotate(-6deg);}
.lg-stat:nth-child(1) { animation-delay: 0.1s; }
.lg-stat:nth-child(2) { animation-delay: 0.2s; }
.lg-stat:nth-child(3) { animation-delay: 0.3s; }
@keyframes statSlide {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.lg-stat-icon { width: 28px; height: 28px; border-radius: 8px; display:flex;align-items:center;justify-content:center; margin-bottom:0.3rem; }
.lg-stat strong { font-size: 1.3rem; font-weight: 900; color: white; line-height: 1; }
.lg-stat span { font-family:'Nunito Sans',sans-serif; font-size:0.64rem; font-weight:600; color:#93c5fd; }
.lg-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.28;animation:lgFloat 7s ease-in-out infinite; }
@keyframes lgFloat {
0%,100%{transform:translateY(0) rotate(0deg);}
50%{transform:translateY(-14px) rotate(180deg);}
/* Social proof */
.lg-social { display: flex; align-items: center; gap: 0.75rem; }
.lg-avs { display: flex; }
.lg-av {
width: 30px; height: 30px; border-radius: 50%;
border: 2px solid #0f2044; margin-left: -8px;
display: flex; align-items: center; justify-content: center;
font-size: 0.6rem; font-weight: 800; color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.lg-av:first-child { margin-left: 0; }
.lg-social p { font-family:'Nunito Sans',sans-serif; font-size:0.75rem; font-weight:700; color:#93c5fd; }
.lg-social p strong { color:#fbbf24; }
/* Card */
.lg-card {
/* ─── RIGHT PANEL ─── */
.lg-right {
flex: 1;
display: flex; align-items: center; justify-content: center;
padding: 3rem 4rem;
position: relative; overflow: hidden;
}
.lg-bg-dot { position:absolute;border-radius:50%;pointer-events:none;opacity:0.09;animation:bgFloat 10s ease-in-out infinite; }
@keyframes bgFloat { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-14px);} }
.lg-form-wrap {
position: relative; z-index: 1;
width: 100%; max-width: 400px;
background: white; border: 2.5px solid #f3f4f6;
border-radius: 28px;
box-shadow: 0 12px 40px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.04);
padding: 2.25rem 2rem 2rem;
display: flex; flex-direction: column; gap: 1.75rem;
animation: lgPopIn 0.5s cubic-bezier(0.34,1.56,0.64,1) both;
display: flex; flex-direction: column; gap: 2rem;
animation: formPop 0.55s cubic-bezier(0.34,1.56,0.64,1) both;
}
@keyframes lgPopIn {
from { opacity:0; transform:scale(0.9) translateY(20px); }
to { opacity:1; transform:scale(1) translateY(0); }
@keyframes formPop {
from { opacity:0; transform:translateY(22px) scale(0.97); }
to { opacity:1; transform:translateY(0) scale(1); }
}
/* Logo area */
.lg-logo-wrap {
display: flex; flex-direction: column; align-items: center; gap: 0.85rem;
}
.lg-logo-badge {
width: 64px; height: 64px; border-radius: 20px;
background: linear-gradient(135deg, #a855f7, #7c3aed);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 6px 0 #5b21b655, 0 10px 24px rgba(124,58,237,0.25);
font-size: 1.75rem;
animation: lgPopIn 0.5s cubic-bezier(0.34,1.56,0.64,1) 0.1s both;
}
.lg-title {
font-size: 1.5rem; font-weight: 900; color: #1e1b4b;
letter-spacing: -0.02em; text-align: center;
}
.lg-sub {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.82rem; font-weight: 600; color: #9ca3af;
text-align: center; margin-top: -0.25rem;
}
.lg-form-header { display:flex;flex-direction:column;gap:0.4rem; }
.lg-form-header h1 { font-size:2rem;font-weight:900;color:#1e1b4b;letter-spacing:-0.03em;line-height:1.2; }
.lg-form-header p { font-family:'Nunito Sans',sans-serif;font-size:0.88rem;font-weight:600;color:#9ca3af; }
/* Form fields */
.lg-fields { display: flex; flex-direction: column; gap: 1rem; }
.lg-field { display: flex; flex-direction: column; gap: 0.4rem; }
.lg-label {
font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em;
text-transform: uppercase; color: #6b7280;
padding-left: 0.25rem;
}
.lg-input-wrap { position: relative; }
.lg-input-icon {
position: absolute; left: 0.85rem; top: 50%;
transform: translateY(-50%); pointer-events: none; color: #9ca3af;
transition: color 0.2s ease;
}
.lg-fields { display:flex;flex-direction:column;gap:1.1rem; }
.lg-field { display:flex;flex-direction:column;gap:0.4rem; }
.lg-label { font-size:0.7rem;font-weight:800;letter-spacing:0.1em;text-transform:uppercase;color:#6b7280;padding-left:0.2rem; }
.lg-input-wrap { position:relative; }
.lg-input-icon { position:absolute;left:0.9rem;top:50%;transform:translateY(-50%);pointer-events:none;color:#9ca3af;transition:color 0.2s; }
.lg-input {
width: 100%; padding: 0.8rem 1rem 0.8rem 2.6rem;
background: #f9fafb; border: 2.5px solid #f3f4f6;
border-radius: 14px;
font-family: 'Nunito Sans', sans-serif;
font-size: 0.88rem; font-weight: 600; color: #1e1b4b;
outline: none; transition: all 0.2s ease;
box-sizing: border-box;
width:100%;padding:0.9rem 1rem 0.9rem 2.65rem;
background:#f9fafb;border:2.5px solid #f3f4f6;border-radius:14px;
font-family:'Nunito Sans',sans-serif;font-size:0.9rem;font-weight:600;color:#1e1b4b;
outline:none;transition:all 0.2s;box-sizing:border-box;
}
.lg-input:focus {
background: white; border-color: #c4b5fd;
box-shadow: 0 0 0 3px rgba(168,85,247,0.1);
}
.lg-input:focus ~ .lg-input-icon { color: #a855f7; }
.lg-input:disabled { opacity: 0.5; cursor: not-allowed; }
.lg-input::placeholder { color: #d1d5db; }
.lg-input:focus { background:white;border-color:#93c5fd;box-shadow:0 0 0 3.5px rgba(59,130,246,0.1); }
.lg-input:disabled { opacity:0.5;cursor:not-allowed; }
.lg-input::placeholder { color:#d1d5db; }
/* Remember me */
.lg-remember {
display: flex; align-items: center; gap: 0.5rem;
padding: 0 0.1rem;
}
.lg-checkbox {
width: 18px; height: 18px; border-radius: 6px;
accent-color: #a855f7; cursor: pointer; flex-shrink: 0;
}
.lg-remember-label {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.8rem; font-weight: 600; color: #6b7280;
cursor: pointer;
}
.lg-remember { display:flex;align-items:center;gap:0.5rem;padding:0 0.1rem; }
.lg-checkbox { width:17px;height:17px;border-radius:5px;accent-color:#3b82f6;cursor:pointer;flex-shrink:0; }
.lg-remember-label { font-family:'Nunito Sans',sans-serif;font-size:0.8rem;font-weight:600;color:#6b7280;cursor:pointer; }
/* Error */
.lg-error {
background: #fff1f2; border: 2px solid #fecdd3;
border-radius: 14px; padding: 0.75rem 1rem;
font-family: 'Nunito Sans', sans-serif;
font-size: 0.82rem; font-weight: 700; color: #e11d48;
display: flex; align-items: center; gap: 0.5rem;
background:#fff1f2;border:2px solid #fecdd3;border-radius:14px;padding:0.8rem 1rem;
font-family:'Nunito Sans',sans-serif;font-size:0.82rem;font-weight:700;color:#e11d48;
display:flex;align-items:center;gap:0.5rem;
}
/* Submit button */
.lg-btn {
width: 100%; padding: 0.95rem;
background: #f97316; color: white; border: none;
border-radius: 100px; cursor: pointer;
font-family: 'Nunito', sans-serif; font-size: 0.95rem; font-weight: 900;
display: flex; align-items: center; justify-content: center; gap: 0.5rem;
box-shadow: 0 6px 0 #c2560e, 0 8px 20px rgba(249,115,22,0.25);
transition: transform 0.1s ease, box-shadow 0.1s ease;
width:100%;padding:1rem;background:#f97316;color:white;border:none;border-radius:100px;cursor:pointer;
font-family:'Nunito',sans-serif;font-size:1rem;font-weight:900;
display:flex;align-items:center;justify-content:center;gap:0.5rem;
box-shadow:0 6px 0 #c2560e,0 10px 24px rgba(249,115,22,0.28);
transition:transform 0.1s,box-shadow 0.1s;
}
.lg-btn:hover { transform:translateY(-2px); box-shadow:0 8px 0 #c2560e,0 12px 24px rgba(249,115,22,0.3); }
.lg-btn:active { transform:translateY(3px); box-shadow:0 3px 0 #c2560e; }
.lg-btn:disabled {
background: #e5e7eb; color: #9ca3af;
cursor: not-allowed; box-shadow: 0 4px 0 #d1d5db;
}
.lg-btn:disabled:hover { transform: none; box-shadow: 0 4px 0 #d1d5db; }
.lg-btn:hover { transform:translateY(-2px);box-shadow:0 8px 0 #c2560e,0 14px 28px rgba(249,115,22,0.32); }
.lg-btn:active { transform:translateY(3px);box-shadow:0 3px 0 #c2560e; }
.lg-btn:disabled { background:#e5e7eb;color:#9ca3af;cursor:not-allowed;box-shadow:0 4px 0 #d1d5db; }
.lg-btn:disabled:hover { transform:none;box-shadow:0 4px 0 #d1d5db; }
.lg-spinner { animation: lgSpin 0.8s linear infinite; }
@keyframes lgSpin { to { transform: rotate(360deg); } }
.lg-spinner { animation:spin 0.8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* Footer hint */
.lg-footer {
text-align: center;
font-family: 'Nunito Sans', sans-serif;
font-size: 0.75rem; font-weight: 600; color: #9ca3af;
.lg-footer { text-align:center;font-family:'Nunito Sans',sans-serif;font-size:0.75rem;font-weight:600;color:#9ca3af; }
.lg-signup-footer { text-align:center;font-family:'Nunito Sans',sans-serif;font-size:0.8rem;font-weight:600;color:#9ca3af; }
.lg-link { color:#f97316;font-weight:800;text-decoration:none;cursor:pointer; }
.lg-link:hover { color:#ea6c00; }
@media (max-width: 860px) {
.lg-left { display:none; }
.lg-right { padding:2rem 1.5rem; }
}
.rg-footer {
text-align: center;
font-family: 'Nunito Sans', sans-serif;
font-size: 0.78rem; font-weight: 600; color: #9ca3af;
}
.rg-link {
color: #a855f7; font-weight: 800; text-decoration: none;
transition: color 0.2s ease;
}
.rg-link:hover { color: #7c3aed; }
`;
const BAR_HEIGHTS = [30, 50, 42, 75, 55, 88, 65];
const BAR_COLORS = [
"#60a5fa",
"#60a5fa",
"#7dd3fc",
"#38bdf8",
"#60a5fa",
"#7dd3fc",
"#38bdf8",
];
const AV_COLORS = [
"linear-gradient(135deg,#3b82f6,#1d4ed8)",
"linear-gradient(135deg,#f97316,#ef4444)",
"linear-gradient(135deg,#22c55e,#15803d)",
"linear-gradient(135deg,#a855f7,#7c3aed)",
"linear-gradient(135deg,#eab308,#ca8a04)",
];
const AV_INITIALS = ["SK", "NR", "TM", "AB", "PL"];
export const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@ -195,13 +367,12 @@ export const Login = () => {
const { login, isAuthenticated, isLoading, error, clearError } =
useAuthStore();
const from = (location.state as LocationState)?.from?.pathname || "/student";
const from =
(location.state as LocationState)?.from?.pathname || "/student/home";
useEffect(() => {
if (isAuthenticated) navigate("/student/home", { replace: true });
}, [isAuthenticated, navigate]);
useEffect(() => {
return () => clearError();
}, [clearError]);
@ -216,138 +387,323 @@ export const Login = () => {
if (isAuthenticated) return null;
return (
<div className="lg-screen">
<div className="lg-root">
<style>{STYLES}</style>
{/* Blobs */}
<div className="lg-blob lg-blob-1" />
<div className="lg-blob lg-blob-2" />
<div className="lg-blob lg-blob-3" />
<div className="lg-blob lg-blob-4" />
{/* ── LEFT PANEL ── */}
<div className="lg-left">
{/* Background layers */}
<div className="lg-grid" />
<div className="lg-glow lg-glow-1" />
<div className="lg-glow lg-glow-2" />
<div className="lg-glow lg-glow-3" />
{/* Dots */}
{DOTS.map((d, i) => (
<div
key={i}
className="lg-dot"
style={
{
width: d.size,
height: d.size,
background: d.color,
top: d.top,
left: d.left,
right: d.right,
animationDelay: d.delay,
animationDuration: `${5.5 + i * 0.4}s`,
} as React.CSSProperties
}
/>
))}
<div className="lg-card">
{/* Logo + heading */}
<div className="lg-logo-wrap space-y-5">
<img
src="src/assets/ed_logo.png"
alt="EdBridge"
style={{
width: 600,
height: 70,
objectFit: "contain",
borderRadius: 8,
}}
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
{/* Decorative spinning rings */}
{[
{ s: 260, t: "38%", l: "58%", mt: -130, ml: -130 },
{
s: 380,
t: "42%",
l: "54%",
mt: -190,
ml: -190,
dir: "reverse" as const,
},
].map((r, i) => (
<div
key={i}
className="lg-deco-ring"
style={
{
width: r.s,
height: r.s,
top: r.t,
left: r.l,
marginTop: r.mt,
marginLeft: r.ml,
animationDirection: r.dir ?? "normal",
animationDuration: `${35 + i * 10}s`,
} as React.CSSProperties
}
/>
))}
<div>
<h1 className="lg-title">Welcome back 👋</h1>
<p className="lg-sub">Sign in to continue your SAT prep</p>
{/* Floating score card */}
<div className="lg-score-card">
<p className="lg-score-tag">SAT Score</p>
<p className="lg-score-num">1480</p>
<p className="lg-score-delta"> +120 pts this month</p>
<div className="lg-bars">
{BAR_HEIGHTS.map((h, i) => (
<div
key={i}
className="lg-bar"
style={{
height: `${h}%`,
background: BAR_COLORS[i],
animationDelay: `${i * 0.08}s`,
}}
/>
))}
</div>
</div>
{/* Fields */}
<div className="lg-fields">
{/* Email */}
<div className="lg-field">
<label className="lg-label" htmlFor="email">
Email
</label>
<div className="lg-input-wrap">
<Mail size={15} className="lg-input-icon" />
<input
id="email"
type="email"
className="lg-input"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
/>
</div>
{/* Accuracy ring */}
<div className="lg-ring-wrap">
<svg className="lg-ring-svg" viewBox="0 0 80 80">
<circle className="lg-ring-bg" cx="40" cy="40" r="30" />
<circle className="lg-ring-fill" cx="40" cy="40" r="30" />
</svg>
<div className="lg-ring-label">
<strong>94%</strong>
<span>Accuracy</span>
</div>
</div>
{/* Password */}
<div className="lg-field">
<label className="lg-label" htmlFor="password">
Password
</label>
<div className="lg-input-wrap">
<Lock size={15} className="lg-input-icon" />
<input
id="password"
type="password"
className="lg-input"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
{/* Streak pill */}
<div className="lg-streak">
<span className="lg-streak-fire">🔥</span>
<div className="lg-streak-text">
<strong>14-day streak!</strong>
<span>Keep it going</span>
</div>
</div>
{/* Remember me */}
<div className="lg-remember">
<input id="rememberMe" type="checkbox" className="lg-checkbox" />
<label htmlFor="rememberMe" className="lg-remember-label">
Keep me signed in
</label>
</div>
{/* Questions badge */}
<div className="lg-q-badge">
<p>Questions solved</p>
<strong>2,847</strong>
</div>
{/* Error */}
{error && (
<div className="lg-error">
<span></span> {error}
</div>
)}
{/* Glitter dots */}
{[
{ s: 10, c: "#60a5fa", t: "13%", l: "58%", d: "0s", dur: "9s" },
{ s: 7, c: "#fbbf24", t: "70%", l: "70%", d: "1s", dur: "11s" },
{ s: 12, c: "#34d399", t: "38%", l: "7%", d: "0.4s", dur: "8s" },
{ s: 5, c: "#f472b6", t: "82%", l: "35%", d: "1.8s", dur: "13s" },
{ s: 8, c: "#a78bfa", t: "20%", l: "36%", d: "0.9s", dur: "10s" },
{ s: 6, c: "#fb923c", t: "62%", l: "80%", d: "1.3s", dur: "7s" },
].map((d, i) => (
<div
key={i}
className="lg-glitter"
style={
{
width: d.s,
height: d.s,
background: d.c,
top: d.t,
left: d.l,
animationDelay: d.d,
animationDuration: d.dur,
} as React.CSSProperties
}
/>
))}
{/* Submit */}
<button
className="lg-btn"
onClick={handleSubmit}
disabled={isLoading || !email || !password}
{/* Stars */}
{[
{ t: "8%", l: "16%", s: "1.1rem", d: "0s" },
{ t: "22%", l: "74%", s: "0.85rem", d: "0.7s" },
{ t: "55%", l: "20%", s: "0.95rem", d: "1.4s" },
{ t: "86%", l: "58%", s: "0.75rem", d: "0.3s" },
{ t: "44%", l: "88%", s: "1rem", d: "1.0s" },
].map((s, i) => (
<span
key={i}
className="lg-star"
style={
{
top: s.t,
left: s.l,
fontSize: s.s,
animationDelay: s.d,
} as React.CSSProperties
}
>
{isLoading ? (
<>
<Loader2 size={18} className="lg-spinner" /> Signing in...
</>
) : (
"Sign In →"
)}
</button>
</div>
</span>
))}
<p className="lg-footer">
By signing in you agree to Edbridge's Terms & Privacy Policy.
</p>
<p className="rg-footer">
Don't have an account?{" "}
<a href="/register" className="rg-link">
Sign up
</a>
</p>
{/* Panel content */}
<div className="lg-panel-content">
<div className="lg-panel-logo">
<div className="lg-panel-logo-badge">📚</div>
<span className="lg-panel-logo-text">EdBridge</span>
</div>
<div className="lg-panel-headline">
<h2>
Welcome
<br />
back,
<br />
<span>champion.</span>
</h2>
<p>
Your SAT goals are waiting.
<br />
Pick up right where you left off.
</p>
</div>
<div className="lg-stats">
{[
{
icon: <Target size={14} color="#fff" />,
bg: "linear-gradient(135deg,#3b82f6,#1d4ed8)",
val: "94%",
label: "Accuracy",
},
{
icon: <Clock size={14} color="#fff" />,
bg: "linear-gradient(135deg,#f97316,#ef4444)",
val: "47m",
label: "Daily study",
},
{
icon: <BarChart2 size={14} color="#fff" />,
bg: "linear-gradient(135deg,#22c55e,#15803d)",
val: "+180",
label: "Score gain",
},
].map((s, i) => (
<div className="lg-stat" key={i}>
<div className="lg-stat-icon" style={{ background: s.bg }}>
{s.icon}
</div>
<strong>{s.val}</strong>
<span>{s.label}</span>
</div>
))}
</div>
<div className="lg-social">
<div className="lg-avs">
{AV_INITIALS.map((init, i) => (
<div
key={i}
className="lg-av"
style={{ background: AV_COLORS[i] }}
>
{init}
</div>
))}
</div>
<p>
<strong>2,400+</strong> students improved their scores
</p>
</div>
</div>
</div>
{/* ── RIGHT PANEL ── */}
<div className="lg-right">
{[
{ s: 200, c: "#f97316", t: "4%", r: "4%", d: "0s", dur: "12s" },
{ s: 120, c: "#3b82f6", b: "8%", l: "2%", d: "1.5s", dur: "10s" },
{ s: 70, c: "#22c55e", t: "52%", r: "2%", d: "0.8s", dur: "8s" },
].map((d, i) => (
<div
key={i}
className="lg-bg-dot"
style={
{
width: d.s,
height: d.s,
background: d.c,
top: (d as any).t,
right: (d as any).r,
bottom: (d as any).b,
left: (d as any).l,
animationDelay: d.d,
animationDuration: d.dur,
} as React.CSSProperties
}
/>
))}
<div className="lg-form-wrap">
<div className="lg-form-header">
<h1>Welcome back 👋</h1>
<p>Sign in to continue your SAT prep</p>
</div>
<div className="lg-fields">
<div className="lg-field">
<label className="lg-label" htmlFor="email">
Email
</label>
<div className="lg-input-wrap">
<Mail size={15} className="lg-input-icon" />
<input
id="email"
type="email"
className="lg-input"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="lg-field">
<label className="lg-label" htmlFor="password">
Password
</label>
<div className="lg-input-wrap">
<Lock size={15} className="lg-input-icon" />
<input
id="password"
type="password"
className="lg-input"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="lg-remember">
<input id="rememberMe" type="checkbox" className="lg-checkbox" />
<label htmlFor="rememberMe" className="lg-remember-label">
Keep me signed in
</label>
</div>
{error && (
<div className="lg-error">
<span></span> {error}
</div>
)}
<button
className="lg-btn"
onClick={handleSubmit}
disabled={isLoading || !email || !password}
>
{isLoading ? (
<>
<Loader2 size={18} className="lg-spinner" /> Signing in...
</>
) : (
"Sign In →"
)}
</button>
</div>
<p className="lg-footer">
By signing in you agree to EdBridge's Terms & Privacy Policy.
</p>
<p className="lg-signup-footer">
Don't have an account?{" "}
<span className="lg-link" onClick={() => navigate("/register")}>
Sign up
</span>
</p>
</div>
</div>
</div>
);