467 lines
15 KiB
TypeScript
467 lines
15 KiB
TypeScript
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 &
|
||
<br />
|
||
Writing
|
||
</h2>
|
||
<p className="htm-card-desc">
|
||
Grammar, vocabulary, comprehension & 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 & 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>
|
||
);
|
||
};
|