548 lines
20 KiB
TypeScript
548 lines
20 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { useAuthStore } from "../../stores/authStore";
|
||
import { CheckCircle, Play, Search } from "lucide-react";
|
||
import { api } from "../../utils/api";
|
||
import type { PracticeSheet } from "../../types/sheet";
|
||
import { formatStatus } from "../../lib/utils";
|
||
import { useNavigate } from "react-router-dom";
|
||
import { SearchOverlay } from "../../components/SearchOverlay";
|
||
import { InfoHeader } from "../../components/InfoHeader";
|
||
|
||
// ─── Shared blob/dot background (same as break/results screens) ────────────────
|
||
const DOTS = [
|
||
{ size: 12, color: "#f97316", top: "8%", left: "6%", delay: "0s" },
|
||
{ size: 8, color: "#a855f7", top: "22%", left: "2%", delay: "1s" },
|
||
{ size: 10, color: "#22c55e", top: "55%", left: "4%", delay: "0.5s" },
|
||
{ size: 14, color: "#3b82f6", top: "10%", right: "5%", delay: "1.5s" },
|
||
{ size: 8, color: "#f43f5e", top: "40%", right: "3%", delay: "0.8s" },
|
||
{ size: 10, color: "#eab308", top: "70%", right: "7%", delay: "0.3s" },
|
||
];
|
||
|
||
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; }
|
||
|
||
.home-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) {
|
||
.home-screen {
|
||
padding-left: calc(17rem + 1.25rem);
|
||
}
|
||
}
|
||
|
||
/* ── Blobs ── */
|
||
.h-blob { position: fixed; pointer-events: none; z-index: 0; filter: blur(48px); opacity: 0.35; }
|
||
.h-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:hWobble1 14s ease-in-out infinite; }
|
||
.h-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:hWobble2 16s ease-in-out infinite; }
|
||
.h-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:hWobble1 18s ease-in-out infinite reverse; }
|
||
.h-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:hWobble2 12s ease-in-out infinite; }
|
||
|
||
@keyframes hWobble1 {
|
||
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 hWobble2 {
|
||
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);}
|
||
}
|
||
|
||
/* ── Floating dots ── */
|
||
.h-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:hFloat 6s ease-in-out infinite; }
|
||
@keyframes hFloat {
|
||
0%,100%{transform:translateY(0) rotate(0deg);}
|
||
50%{transform:translateY(-14px) rotate(180deg);}
|
||
}
|
||
|
||
/* ── Inner scroll container ── */
|
||
.home-inner {
|
||
position: relative;
|
||
z-index: 1;
|
||
max-width: 580px;
|
||
margin: 0 auto;
|
||
padding: 2rem 1.25rem 4rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1.75rem;
|
||
}
|
||
|
||
/* ── Section titles ── */
|
||
.h-section-title {
|
||
font-size: 1.2rem; font-weight: 900; color: #1e1b4b;
|
||
letter-spacing: -0.01em; margin-bottom: 0.75rem;
|
||
}
|
||
|
||
/* ── Search bar ── */
|
||
.h-search-wrap {
|
||
position: relative;
|
||
animation: hPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) 0.05s both;
|
||
}
|
||
.h-search-input {
|
||
width: 100%; box-sizing: border-box;
|
||
padding: 0.85rem 1rem 0.85rem 2.8rem;
|
||
background: white; border: 2.5px solid #f3f4f6;
|
||
border-radius: 18px;
|
||
font-family: 'Nunito', sans-serif;
|
||
font-size: 0.9rem; font-weight: 700; color: #9ca3af;
|
||
box-shadow: 0 4px 14px rgba(0,0,0,0.05);
|
||
cursor: pointer; outline: none;
|
||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
.h-search-input:focus { border-color:#c084fc; box-shadow:0 4px 20px rgba(192,132,252,0.15); }
|
||
.h-search-icon {
|
||
position: absolute; left: 0.9rem; top: 50%;
|
||
transform: translateY(-50%); pointer-events:none;
|
||
}
|
||
|
||
/* ── In-progress card ── */
|
||
.h-inprogress-card {
|
||
background: white;
|
||
border: 2.5px solid #c4b5fd;
|
||
border-radius: 22px;
|
||
padding: 1.1rem 1.25rem;
|
||
box-shadow: 0 4px 16px rgba(167,139,250,0.12);
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: 1rem; cursor: pointer;
|
||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
.h-inprogress-card:hover { transform:translateY(-2px);box-shadow:0 8px 24px rgba(167,139,250,0.2); }
|
||
.h-inprogress-info { display:flex;flex-direction:column;gap:0.25rem;flex:1;min-width:0; }
|
||
.h-inprogress-title {
|
||
font-size: 0.95rem; font-weight: 900; color: #1e1b4b;
|
||
white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
|
||
}
|
||
.h-inprogress-badge {
|
||
font-size: 0.65rem; font-weight: 800; letter-spacing:0.1em;
|
||
text-transform:uppercase; color:#a855f7;
|
||
background:#f3e8ff; border-radius:100px; padding:0.2rem 0.6rem;
|
||
width: fit-content;
|
||
}
|
||
.h-play-btn {
|
||
width: 44px; height: 44px; border-radius: 50%; border: none; cursor: pointer;
|
||
background: linear-gradient(135deg, #a855f7, #7c3aed);
|
||
display:flex;align-items:center;justify-content:center;
|
||
box-shadow: 0 4px 0 #5b21b6aa;
|
||
transition: transform 0.1s ease, box-shadow 0.1s ease;
|
||
flex-shrink:0;
|
||
}
|
||
.h-play-btn:hover { transform:translateY(-2px);box-shadow:0 6px 0 #5b21b6aa; }
|
||
.h-play-btn:active { transform:translateY(2px);box-shadow:0 2px 0 #5b21b6aa; }
|
||
|
||
/* ── Empty state ── */
|
||
.h-empty {
|
||
background:white; border:2.5px dashed #e5e7eb; border-radius:22px;
|
||
padding: 1.75rem; text-align:center;
|
||
font-size:0.9rem; font-weight:700; color:#9ca3af;
|
||
}
|
||
.h-empty-emoji { font-size:2rem; display:block; margin-bottom:0.5rem; }
|
||
|
||
/* ── Tabs ── */
|
||
.h-tabs-list {
|
||
display:flex; border-bottom: 2px solid #f3f4f6;
|
||
gap: 0; margin-bottom:1rem;
|
||
}
|
||
.h-tab-btn {
|
||
flex:1; padding:0.65rem 0; text-align:center;
|
||
font-family:'Nunito',sans-serif; font-size:0.82rem; font-weight:800;
|
||
color:#9ca3af; background:transparent; border:none; border-bottom: 3px solid transparent;
|
||
margin-bottom:-2px; cursor:pointer;
|
||
transition: color 0.2s ease, border-color 0.2s ease;
|
||
}
|
||
.h-tab-btn.active { color:#1e1b4b; border-bottom-color:#a855f7; }
|
||
|
||
/* ── Practice sheet ── */
|
||
.h-sheet-grid {
|
||
display:grid; gap:0.85rem;
|
||
grid-template-columns: 1fr;
|
||
}
|
||
@media(min-width:520px){ .h-sheet-grid { grid-template-columns:1fr 1fr; } }
|
||
|
||
.h-sheet-card {
|
||
background:white; border:2.5px solid #f3f4f6; border-radius:22px;
|
||
padding:1.1rem; box-shadow:0 4px 14px rgba(0,0,0,0.04);
|
||
display:flex; flex-direction:column; gap:0.6rem;
|
||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
.h-sheet-card:hover { transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,0.07); }
|
||
|
||
.h-sheet-title { font-size:0.95rem;font-weight:900;color:#1e1b4b;line-height:1.2; }
|
||
.h-sheet-desc { font-size:0.78rem;font-weight:600;color:#9ca3af;font-family:'Nunito Sans',sans-serif; }
|
||
|
||
.h-sheet-meta {
|
||
display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.4rem;
|
||
}
|
||
.h-status-pill {
|
||
font-size:0.65rem;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;
|
||
border-radius:100px;padding:0.25rem 0.65rem;
|
||
}
|
||
.h-status-pill.inprogress { background:#f3e8ff;color:#9333ea; }
|
||
.h-status-pill.notstarted { background:#f3f4f6;color:#6b7280; }
|
||
.h-status-pill.completed { background:#dcfce7;color:#16a34a; }
|
||
|
||
.h-modules-badge {
|
||
font-size:0.65rem;font-weight:800;
|
||
background:#ede9fe;color:#7c3aed;border-radius:100px;padding:0.25rem 0.65rem;
|
||
}
|
||
.h-time-badge {
|
||
font-size:0.72rem;font-weight:700;color:#9ca3af;display:flex;align-items:center;gap:0.3rem;
|
||
}
|
||
|
||
.h-start-btn {
|
||
width:100%;margin-top:auto;
|
||
background:#f97316;color:white;border:none;border-radius:100px;
|
||
padding:0.75rem;font-family:'Nunito',sans-serif;
|
||
font-size:0.9rem;font-weight:800;cursor:pointer;
|
||
box-shadow:0 4px 0 #c2560e,0 6px 16px rgba(249,115,22,0.2);
|
||
transition:transform 0.1s ease,box-shadow 0.1s ease;
|
||
}
|
||
.h-start-btn:hover { transform:translateY(-2px);box-shadow:0 6px 0 #c2560e,0 10px 20px rgba(249,115,22,0.25); }
|
||
.h-start-btn:active { transform:translateY(2px); box-shadow:0 2px 0 #c2560e,0 3px 8px rgba(249,115,22,0.15); }
|
||
|
||
/* ── Tips section ── */
|
||
.h-tips-list { display:flex;flex-direction:column;gap:0.6rem; }
|
||
.h-tip-row {
|
||
display:flex;align-items:flex-start;gap:0.65rem;
|
||
background:white;border:2.5px solid #f3f4f6;border-radius:16px;
|
||
padding:0.75rem 1rem;box-shadow:0 2px 8px rgba(0,0,0,0.03);
|
||
}
|
||
.h-tip-icon { flex-shrink:0;margin-top:1px; }
|
||
.h-tip-text { font-size:0.85rem;font-weight:700;color:#374151;line-height:1.4; }
|
||
|
||
|
||
/* ── Load more ── */
|
||
.h-load-more-btn {
|
||
width: 100%; margin-top: 0.25rem;
|
||
padding: 0.75rem;
|
||
background: white; border: 2.5px solid #f3f4f6;
|
||
border-radius: 100px; cursor: pointer;
|
||
font-family: 'Nunito', sans-serif;
|
||
font-size: 0.82rem; font-weight: 800; color: #9ca3af;
|
||
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
|
||
box-shadow: 0 3px 10px rgba(0,0,0,0.04);
|
||
transition: all 0.2s ease;
|
||
}
|
||
.h-load-more-btn:hover { border-color: #c4b5fd; color: #a855f7; background: #fdf4ff; transform: translateY(-1px); box-shadow: 0 6px 14px rgba(0,0,0,0.06); }
|
||
.h-load-more-btn:active { transform: translateY(1px); }
|
||
|
||
.h-sheet-count {
|
||
text-align: center;
|
||
font-family: 'Nunito Sans', sans-serif;
|
||
font-size: 0.72rem; font-weight: 600; color: #d1d5db;
|
||
margin-top: 0.5rem;
|
||
}
|
||
.h-sheet-count span { font-weight: 800; color: #9ca3af; }
|
||
|
||
@keyframes hPopIn {
|
||
from{opacity:0;transform:scale(0.92) translateY(10px);}
|
||
to{opacity:1;transform:scale(1) translateY(0);}
|
||
}
|
||
.h-anim { animation: hPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; }
|
||
.h-anim-1 { animation-delay:0.05s; }
|
||
.h-anim-2 { animation-delay:0.1s; }
|
||
.h-anim-3 { animation-delay:0.15s; }
|
||
.h-anim-4 { animation-delay:0.2s; }
|
||
.h-anim-5 { animation-delay:0.25s; }
|
||
|
||
/* Desktop / wide tweaks */
|
||
@media (min-width: 900px) {
|
||
.home-inner { max-width: var(--content-max); padding: 3rem 1.5rem 6rem; }
|
||
.h-sheet-grid { grid-template-columns: repeat(3, 1fr); gap: 1rem; }
|
||
|
||
/* nudge blobs so they align visually with the centered container */
|
||
.h-blob-1 { left: calc((100vw - var(--content-max)) / 2 - 120px); top: -120px; width: 300px; height: 300px; }
|
||
.h-blob-2 { left: calc((100vw - var(--content-max)) / 2 + 20px); bottom: -80px; width: 220px; height: 220px; }
|
||
.h-blob-3 { right: calc((100vw - var(--content-max)) / 2 - 40px); top: 10%; width: 260px; height: 260px; }
|
||
.h-blob-4 { right: calc((100vw - var(--content-max)) / 2 + 10px); bottom: 6%; width: 180px; height: 180px; }
|
||
}
|
||
`;
|
||
|
||
// ─── Sheet card ───────────────────────────────────────────────────────────────
|
||
const SheetCard = ({
|
||
sheet,
|
||
onStart,
|
||
}: {
|
||
sheet: PracticeSheet;
|
||
onStart: (id: string) => void;
|
||
}) => {
|
||
const statusClass =
|
||
sheet.user_status === "IN_PROGRESS"
|
||
? "inprogress"
|
||
: sheet.user_status === "COMPLETED"
|
||
? "completed"
|
||
: "notstarted";
|
||
|
||
return (
|
||
<div className="h-sheet-card">
|
||
<p className="h-sheet-title">{sheet.title}</p>
|
||
{sheet.description && <p className="h-sheet-desc">{sheet.description}</p>}
|
||
<div className="h-sheet-meta">
|
||
<span className={`h-status-pill ${statusClass}`}>
|
||
{formatStatus(sheet.user_status)}
|
||
</span>
|
||
<span className="h-modules-badge">
|
||
📚 {sheet.modules_count} modules
|
||
</span>
|
||
</div>
|
||
<p className="h-time-badge">⏱️ {sheet.time_limit} min</p>
|
||
<button className="h-start-btn" onClick={() => onStart(sheet.id)}>
|
||
{sheet.user_status === "COMPLETED" ? "Retry →" : "Start →"}
|
||
</button>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// ─── Tips data ────────────────────────────────────────────────────────────────
|
||
const TIPS = [
|
||
"Practice regularly with official SAT materials",
|
||
"Review your mistakes and learn from them",
|
||
"Focus on your weak areas first",
|
||
"Take full-length practice tests",
|
||
"Get plenty of rest before test day",
|
||
];
|
||
|
||
// ─── Main component ───────────────────────────────────────────────────────────
|
||
const PAGE_SIZE = 6;
|
||
|
||
export const Home = () => {
|
||
const user = useAuthStore((state) => state.user);
|
||
const navigate = useNavigate();
|
||
|
||
const [practiceSheets, setPracticeSheets] = useState<PracticeSheet[]>([]);
|
||
const [notStartedSheets, setNotStartedSheets] = useState<PracticeSheet[]>([]);
|
||
const [inProgressSheets, setInProgressSheets] = useState<PracticeSheet[]>([]);
|
||
const [completedSheets, setCompletedSheets] = useState<PracticeSheet[]>([]);
|
||
const [activeTab, setActiveTab] = useState<
|
||
"all" | "NOT_STARTED" | "COMPLETED"
|
||
>("all");
|
||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
||
|
||
useEffect(() => {
|
||
const sort = (sheets: PracticeSheet[]) => {
|
||
setNotStartedSheets(
|
||
sheets.filter((s) => s.user_status === "NOT_STARTED"),
|
||
);
|
||
setInProgressSheets(
|
||
sheets.filter((s) => s.user_status === "IN_PROGRESS"),
|
||
);
|
||
setCompletedSheets(sheets.filter((s) => s.user_status === "COMPLETED"));
|
||
};
|
||
|
||
const fetch = async () => {
|
||
if (!user) return;
|
||
try {
|
||
const authStorage = localStorage.getItem("auth-storage");
|
||
if (!authStorage) return;
|
||
const {
|
||
state: { token },
|
||
} = JSON.parse(authStorage);
|
||
if (!token) return;
|
||
const sheets = await api.getPracticeSheets(token, 1, 10);
|
||
setPracticeSheets(sheets.data);
|
||
sort(sheets.data);
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
};
|
||
fetch();
|
||
}, [user]);
|
||
|
||
const handleStart = (id: string) => navigate(`/student/practice/${id}`);
|
||
|
||
const allTabSheets =
|
||
activeTab === "all"
|
||
? practiceSheets
|
||
: activeTab === "NOT_STARTED"
|
||
? notStartedSheets
|
||
: completedSheets;
|
||
|
||
const tabSheets = allTabSheets.slice(0, visibleCount);
|
||
const hasMore = visibleCount < allTabSheets.length;
|
||
const remaining = allTabSheets.length - visibleCount;
|
||
|
||
const handleTabChange = (tab: "all" | "NOT_STARTED" | "COMPLETED") => {
|
||
setActiveTab(tab);
|
||
setVisibleCount(PAGE_SIZE);
|
||
};
|
||
|
||
return (
|
||
<div className="home-screen">
|
||
<style>{STYLES}</style>
|
||
|
||
{/* Blobs */}
|
||
<div className="h-blob h-blob-1" />
|
||
<div className="h-blob h-blob-2" />
|
||
<div className="h-blob h-blob-3" />
|
||
<div className="h-blob h-blob-4" />
|
||
|
||
{/* Dots */}
|
||
{DOTS.map((d, i) => (
|
||
<div
|
||
key={i}
|
||
className="h-dot"
|
||
style={
|
||
{
|
||
width: d.size,
|
||
height: d.size,
|
||
background: d.color,
|
||
top: d.top,
|
||
left: d.left,
|
||
right: d.right,
|
||
animationDelay: d.delay,
|
||
animationDuration: `${3.5 + i * 0.4}s`,
|
||
} as React.CSSProperties
|
||
}
|
||
/>
|
||
))}
|
||
|
||
<div className="home-inner">
|
||
{/* ── Header ── */}
|
||
<InfoHeader
|
||
mode="DEFAULT"
|
||
onViewAll={() => navigate("/student/quests")}
|
||
/>
|
||
|
||
{/* ── Search ── */}
|
||
<div className="h-search-wrap h-anim h-anim-1">
|
||
<span className="h-search-icon">
|
||
<Search size={18} color="#9ca3af" />
|
||
</span>
|
||
<input
|
||
className="h-search-input"
|
||
placeholder="Search practice sheets..."
|
||
readOnly
|
||
onFocus={() => setIsSearchOpen(true)}
|
||
/>
|
||
</div>
|
||
|
||
{/* ── In progress ── */}
|
||
<section className="h-anim h-anim-2">
|
||
<p className="h-section-title">📌 Pick up where you left off</p>
|
||
{inProgressSheets.length > 0 ? (
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: "0.65rem",
|
||
}}
|
||
>
|
||
{inProgressSheets.map((sheet) => (
|
||
<div
|
||
key={sheet.id}
|
||
className="h-inprogress-card"
|
||
onClick={() => handleStart(sheet.id)}
|
||
>
|
||
<div className="h-inprogress-info">
|
||
<p className="h-inprogress-title">{sheet.title}</p>
|
||
<span className="h-inprogress-badge">In Progress</span>
|
||
</div>
|
||
<button
|
||
className="h-play-btn"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
handleStart(sheet.id);
|
||
}}
|
||
>
|
||
<Play size={18} color="white" fill="white" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="h-empty">
|
||
<span className="h-empty-emoji">🎯</span>
|
||
No sheets in progress — start one below!
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{/* ── All sheets with tabs ── */}
|
||
<section className="h-anim h-anim-3">
|
||
<p className="h-section-title">📋 Practice Sheets</p>
|
||
|
||
{/* Tab buttons */}
|
||
<div className="h-tabs-list">
|
||
{(["all", "NOT_STARTED", "COMPLETED"] as const).map((tab) => (
|
||
<button
|
||
key={tab}
|
||
className={`h-tab-btn${activeTab === tab ? " active" : ""}`}
|
||
onClick={() => handleTabChange(tab)}
|
||
>
|
||
{tab === "all"
|
||
? "All"
|
||
: tab === "NOT_STARTED"
|
||
? "Not Started"
|
||
: "Completed"}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{allTabSheets.length > 0 ? (
|
||
<>
|
||
<div className="h-sheet-grid">
|
||
{tabSheets.map((sheet) => (
|
||
<SheetCard
|
||
key={sheet.id}
|
||
sheet={sheet}
|
||
onStart={handleStart}
|
||
/>
|
||
))}
|
||
</div>
|
||
{hasMore ? (
|
||
<button
|
||
className="h-load-more-btn"
|
||
onClick={() => setVisibleCount((c) => c + PAGE_SIZE)}
|
||
>
|
||
↓ Show {Math.min(remaining, PAGE_SIZE)} more
|
||
</button>
|
||
) : allTabSheets.length > PAGE_SIZE ? (
|
||
<p className="h-sheet-count">
|
||
Showing all <span>{allTabSheets.length}</span> sheets
|
||
</p>
|
||
) : null}
|
||
</>
|
||
) : (
|
||
<div className="h-empty">
|
||
<span className="h-empty-emoji">🔍</span>
|
||
Nothing here yet!
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{/* ── Tips ── */}
|
||
<section className="h-anim h-anim-4">
|
||
<p className="h-section-title">💡 SAT Prep Tips</p>
|
||
<div className="h-tips-list">
|
||
{TIPS.map((tip, i) => (
|
||
<div key={i} className="h-tip-row">
|
||
<CheckCircle size={18} color="#a855f7" className="h-tip-icon" />
|
||
<p className="h-tip-text">{tip}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
{isSearchOpen && (
|
||
<SearchOverlay
|
||
sheets={practiceSheets}
|
||
onClose={() => {
|
||
setIsSearchOpen(false);
|
||
setSearchQuery("");
|
||
}}
|
||
searchQuery={searchQuery}
|
||
setSearchQuery={setSearchQuery}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|