Files
edbridge-scholars/src/pages/student/hard-test-modules/page.tsx

467 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
DecimalsArrowRight,
Languages,
Percent,
Pilcrow,
Superscript,
WholeWord,
Trophy,
} from "lucide-react";
import { useState } from "react";
import { useAuthStore } from "../../../stores/authStore";
import { useNavigate } from "react-router-dom";
import { useExamConfigStore } from "../../../stores/useExamConfigStore";
type Module = "EBRW" | "MATH" | null;
const DOTS = [
{ size: 10, color: "#f97316", top: "6%", left: "4%", delay: "0s" },
{ size: 7, color: "#a855f7", top: "28%", left: "2%", delay: "1.2s" },
{ size: 9, color: "#22c55e", top: "62%", left: "3%", delay: "0.6s" },
{ size: 12, color: "#3b82f6", top: "10%", right: "4%", delay: "1.8s" },
{ size: 7, color: "#f43f5e", top: "48%", right: "2%", delay: "0.9s" },
{ size: 9, color: "#eab308", top: "76%", right: "5%", delay: "0.4s" },
];
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');
:root { --content-max: 1100px; }
.htm-screen {
min-height: 100vh;
background: #fffbf4;
font-family: 'Nunito', sans-serif;
position: relative;
overflow-x: hidden;
}
/* On desktop, account for sidebar */
@media (min-width: 768px) {
.htm-screen {
padding-left: calc(17rem + 1.25rem);
}
}
.htm-blob { position:fixed;pointer-events:none;z-index:0;filter:blur(48px);opacity:0.35; }
.htm-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:htmWobble1 14s ease-in-out infinite; }
.htm-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:htmWobble2 16s ease-in-out infinite; }
.htm-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:htmWobble1 18s ease-in-out infinite reverse; }
.htm-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:htmWobble2 12s ease-in-out infinite; }
@keyframes htmWobble1 {
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(12px,16px) rotate(8deg);}
}
@keyframes htmWobble2 {
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(-10px,12px) rotate(-6deg);}
}
.htm-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:htmFloat 7s ease-in-out infinite; }
@keyframes htmFloat {
0%,100%{transform:translateY(0) rotate(0deg);}
50%{transform:translateY(-12px) rotate(180deg);}
}
.htm-inner {
position: relative; z-index: 1;
max-width: 560px; margin: 0 auto;
padding: 2rem 1.25rem 8rem;
display: flex; flex-direction: column; gap: 1.5rem;
}
@keyframes htmPopIn {
from { opacity:0; transform:scale(0.92) translateY(12px); }
to { opacity:1; transform:scale(1) translateY(0); }
}
.htm-anim { animation: htmPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; }
.htm-anim-1 { animation-delay: 0.05s; }
.htm-anim-2 { animation-delay: 0.12s; }
.htm-anim-3 { animation-delay: 0.19s; }
/* Header */
.htm-eyebrow {
font-size: 0.62rem; font-weight: 800; letter-spacing: 0.16em;
text-transform: uppercase; color: #84cc16;
display: flex; align-items: center; gap: 0.35rem;
}
.htm-title {
font-size: 1.75rem; font-weight: 900; color: #1e1b4b;
letter-spacing: -0.02em; line-height: 1.15;
}
.htm-sub {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.82rem; font-weight: 600; color: #9ca3af; line-height: 1.5;
margin-top: 0.2rem;
}
/* Module cards */
.htm-card {
border-radius: 28px;
position: relative; overflow: hidden;
cursor: pointer;
border: 3px solid transparent;
transition: transform 0.2s cubic-bezier(0.34,1.56,0.64,1),
box-shadow 0.2s ease,
border-color 0.2s ease;
min-height: 200px;
display: flex; flex-direction: column; justify-content: flex-end;
}
.htm-card:hover { transform: translateY(-4px); }
.htm-card:active { transform: translateY(2px) scale(0.98); }
.htm-card.ebrw {
background: linear-gradient(145deg, #3b82f6 0%, #1d4ed8 60%, #1e40af 100%);
box-shadow: 0 10px 0 #1e3a8a66, 0 14px 32px rgba(29,78,216,0.3);
}
.htm-card.ebrw.selected {
border-color: #93c5fd;
box-shadow: 0 10px 0 #1e3a8a88, 0 16px 40px rgba(29,78,216,0.45);
transform: translateY(-4px) scale(1.01);
}
.htm-card.math {
background: linear-gradient(145deg, #f43f5e 0%, #e11d48 60%, #be123c 100%);
box-shadow: 0 10px 0 #9f123666, 0 14px 32px rgba(225,29,72,0.3);
}
.htm-card.math.selected {
border-color: #fda4af;
box-shadow: 0 10px 0 #9f123688, 0 16px 40px rgba(225,29,72,0.45);
transform: translateY(-4px) scale(1.01);
}
/* Decorative icons */
.htm-card-icons {
position: absolute; inset: 0; pointer-events: none;
}
/* Card body (text + chips) */
.htm-card-body {
position: relative; z-index: 2;
padding: 1.5rem 1.5rem 1.75rem;
display: flex; flex-direction: column; gap: 0.5rem;
}
.htm-card-tag {
font-size: 0.6rem; font-weight: 800; letter-spacing: 0.18em;
text-transform: uppercase; color: rgba(255,255,255,0.6);
}
.htm-card-name {
font-size: 1.6rem; font-weight: 900; color: white;
letter-spacing: -0.02em; line-height: 1.1;
}
.htm-card-desc {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.78rem; font-weight: 600; color: rgba(255,255,255,0.7);
line-height: 1.4; margin-top: 0.1rem;
}
/* Stat pills row */
.htm-stat-row {
display: flex; gap: 0.4rem; margin-top: 0.5rem; flex-wrap: wrap;
}
.htm-stat-pill {
background: rgba(255,255,255,0.15);
border: 1.5px solid rgba(255,255,255,0.25);
border-radius: 100px; padding: 0.25rem 0.65rem;
font-size: 0.65rem; font-weight: 800; color: white;
display: flex; align-items: center; gap: 0.3rem;
backdrop-filter: blur(4px);
}
/* Selected check badge */
.htm-check-badge {
position: absolute; top: 1rem; right: 1rem; z-index: 3;
width: 32px; height: 32px; border-radius: 50%;
background: rgba(255,255,255,0.2);
border: 2px solid rgba(255,255,255,0.4);
display: flex; align-items: center; justify-content: center;
transition: all 0.25s cubic-bezier(0.34,1.56,0.64,1);
opacity: 0; transform: scale(0.7);
}
.htm-card.selected .htm-check-badge {
opacity: 1; transform: scale(1);
background: rgba(255,255,255,0.3);
border-color: rgba(255,255,255,0.7);
}
/* CTA bar */
.htm-cta-bar {
position: fixed;
bottom: 96px;
left: 0;
right: 0;
z-index: 10;
padding: 0.85rem 1.25rem calc(0.85rem + env(safe-area-inset-bottom));
transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1), opacity 0.25s ease;
}
.htm-cta-bar.hidden {
transform: translateY(100%); opacity: 0; pointer-events: none;
}
.htm-cta-inner {
max-width: 560px;
margin: 0 auto;
}
@media (min-width: 900px) {
.htm-inner { max-width: var(--content-max); padding: 3rem 1.5rem 10rem; }
.htm-cta-bar { left: var(--sidebar-width); right: 0; }
.htm-cta-inner { max-width: var(--content-max); margin: 0 auto; }
/* align blobs to centered content */
.htm-blob-3 { right: calc((100vw - var(--content-max)) / 2 - 48px); }
.htm-blob-1 { left: calc((100vw - var(--content-max)) / 2 - 56px); }
.htm-blob-2 { left: calc((100vw - var(--content-max)) / 2 + 12px); }
.htm-blob-4 { right: calc((100vw - var(--content-max)) / 2 + 12px); }
/* make module cards slightly wider on desktop */
.htm-card { min-height: 220px; }
}
.htm-start-btn {
width: 100%; padding: 0.95rem;
background: linear-gradient(135deg, #84cc16, #65a30d);
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 #3f6212, 0 8px 20px rgba(101,163,13,0.3);
transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.htm-start-btn:hover { transform:translateY(-2px); box-shadow:0 8px 0 #3f6212,0 12px 24px rgba(101,163,13,0.35); }
.htm-start-btn:active { transform:translateY(3px); box-shadow:0 3px 0 #3f6212; }
.htm-start-label {
font-size: 0.7rem; font-weight: 700; color: #9ca3af;
text-align: center; margin-top: 0.5rem;
font-family: 'Nunito Sans', sans-serif;
}
.htm-start-label span { font-weight: 800; color: #6b7280; }
`;
export const HardTestModules = () => {
const user = useAuthStore((state) => state.user);
const navigate = useNavigate();
const [selected, setSelected] = useState<Module>(null);
const { setMode, storeDuration, setSection } = useExamConfigStore();
function handleStartModule() {
if (!user || !selected) return;
setMode("MODULE");
storeDuration(7);
setSection(selected);
navigate(`/student/practice/${selected}/test`, { replace: true });
}
const toggle = (mod: "EBRW" | "MATH") =>
setSelected((prev) => (prev === mod ? null : mod));
return (
<div className="htm-screen">
<style>{STYLES}</style>
{/* Blobs */}
<div className="htm-blob htm-blob-1" />
<div className="htm-blob htm-blob-2" />
<div className="htm-blob htm-blob-3" />
<div className="htm-blob htm-blob-4" />
{/* Dots */}
{DOTS.map((d, i) => (
<div
key={i}
className="htm-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 + i * 0.5}s`,
} as React.CSSProperties
}
/>
))}
<div className="htm-inner">
{/* Header */}
<header className="htm-anim">
<p className="htm-eyebrow">
<Trophy size={11} /> Hard Modules
</p>
<h1 className="htm-title">Pick your challenge</h1>
<p className="htm-sub">
Tackle the hardest SAT questions. Select a section to begin.
</p>
</header>
{/* EBRW Card */}
<div
className={`htm-card ebrw htm-anim htm-anim-1${selected === "EBRW" ? " selected" : ""}`}
onClick={() => toggle("EBRW")}
>
{/* Background icons */}
<div className="htm-card-icons">
<Languages
size={220}
style={{
position: "absolute",
top: -20,
right: -30,
transform: "rotate(-20deg)",
color: "white",
opacity: 0.12,
}}
/>
<WholeWord
size={130}
style={{
position: "absolute",
top: -15,
left: -10,
transform: "rotate(15deg)",
color: "white",
opacity: 0.1,
}}
/>
<Pilcrow
size={110}
style={{
position: "absolute",
bottom: -20,
left: 20,
transform: "rotate(-18deg)",
color: "white",
opacity: 0.1,
}}
/>
</div>
{/* Check */}
<div className="htm-check-badge">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path
d="M2.5 7L5.5 10L11.5 4"
stroke="white"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
{/* Body */}
<div className="htm-card-body">
<span className="htm-card-tag">Section 1</span>
<h2 className="htm-card-name">
Reading &amp;
<br />
Writing
</h2>
<p className="htm-card-desc">
Grammar, vocabulary, comprehension &amp; evidence-based analysis
</p>
<div className="htm-stat-row">
<span className="htm-stat-pill">📖 27 Questions</span>
<span className="htm-stat-pill"> 32 min</span>
<span className="htm-stat-pill">🔥 Hard tier</span>
</div>
</div>
</div>
{/* MATH Card */}
<div
className={`htm-card math htm-anim htm-anim-2${selected === "MATH" ? " selected" : ""}`}
onClick={() => toggle("MATH")}
>
{/* Background icons */}
<div className="htm-card-icons">
<DecimalsArrowRight
size={220}
style={{
position: "absolute",
top: -20,
right: -30,
transform: "rotate(-20deg)",
color: "white",
opacity: 0.12,
}}
/>
<Superscript
size={130}
style={{
position: "absolute",
top: -15,
left: -10,
transform: "rotate(15deg)",
color: "white",
opacity: 0.1,
}}
/>
<Percent
size={110}
style={{
position: "absolute",
bottom: -15,
left: 20,
transform: "rotate(-10deg)",
color: "white",
opacity: 0.1,
}}
/>
</div>
{/* Check */}
<div className="htm-check-badge">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path
d="M2.5 7L5.5 10L11.5 4"
stroke="white"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
{/* Body */}
<div className="htm-card-body">
<span className="htm-card-tag">Section 2</span>
<h2 className="htm-card-name">Mathematics</h2>
<p className="htm-card-desc">
Algebra, advanced math, geometry &amp; data analysis under
pressure
</p>
<div className="htm-stat-row">
<span className="htm-stat-pill">🔢 22 Questions</span>
<span className="htm-stat-pill"> 35 min</span>
<span className="htm-stat-pill">🔥 Hard tier</span>
</div>
</div>
</div>
</div>
{/* CTA bar */}
<div className={`htm-cta-bar${!selected ? " hidden" : ""}`}>
<div className="htm-cta-inner">
<button className="htm-start-btn" onClick={handleStartModule}>
🏆 Start{" "}
{selected === "EBRW"
? "Reading & Writing"
: selected === "MATH"
? "Mathematics"
: ""}{" "}
Module
</button>
<p className="htm-start-label">
Tap again to <span>deselect</span>
</p>
</div>
</div>
</div>
);
};