Initial Commit
This commit is contained in:
179
app/globals.css
179
app/globals.css
@@ -1,26 +1,183 @@
|
||||
@import "tailwindcss";
|
||||
@import "katex/dist/katex.min.css";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #f4f8ff;
|
||||
--foreground: #10223f;
|
||||
--muted: #4f6588;
|
||||
--border: #cbdaef;
|
||||
--surface: #FFFFFF;
|
||||
--surface-raised: #FFFFFF;
|
||||
|
||||
--unit-1: #2563eb;
|
||||
--unit-1-light: #e8f1ff;
|
||||
--unit-1-dark: #1d4ed8;
|
||||
|
||||
--unit-2: #0ea5a4;
|
||||
--unit-2-light: #e6fffb;
|
||||
--unit-2-dark: #0f766e;
|
||||
|
||||
--unit-3: #f97316;
|
||||
--unit-3-light: #fff1e7;
|
||||
--unit-3-dark: #c2410c;
|
||||
|
||||
--unit-4: #e11d48;
|
||||
--unit-4-light: #ffe8ef;
|
||||
--unit-4-dark: #be123c;
|
||||
|
||||
--correct: #16a34a;
|
||||
--correct-light: #dcfce7;
|
||||
--incorrect: #dc2626;
|
||||
--incorrect-light: #fee2e2;
|
||||
--hint: #d97706;
|
||||
--hint-light: #ffedd5;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgb(16 34 63 / 0.06), 0 2px 8px rgb(37 99 235 / 0.04);
|
||||
--shadow-md: 0 8px 18px rgb(16 34 63 / 0.09), 0 2px 8px rgb(37 99 235 / 0.08);
|
||||
--shadow-lg: 0 16px 30px rgb(16 34 63 / 0.11), 0 6px 20px rgb(37 99 235 / 0.1);
|
||||
--shadow-xl: 0 24px 44px rgb(16 34 63 / 0.14), 0 12px 28px rgb(37 99 235 / 0.12);
|
||||
--shadow-glow: 0 0 24px rgb(37 99 235 / 0.18);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-muted: var(--muted);
|
||||
--color-border: var(--border);
|
||||
--color-surface: var(--surface);
|
||||
--color-surface-raised: var(--surface-raised);
|
||||
|
||||
--color-unit-1: var(--unit-1);
|
||||
--color-unit-1-light: var(--unit-1-light);
|
||||
--color-unit-1-dark: var(--unit-1-dark);
|
||||
--color-unit-2: var(--unit-2);
|
||||
--color-unit-2-light: var(--unit-2-light);
|
||||
--color-unit-2-dark: var(--unit-2-dark);
|
||||
--color-unit-3: var(--unit-3);
|
||||
--color-unit-3-light: var(--unit-3-light);
|
||||
--color-unit-3-dark: var(--unit-3-dark);
|
||||
--color-unit-4: var(--unit-4);
|
||||
--color-unit-4-light: var(--unit-4-light);
|
||||
--color-unit-4-dark: var(--unit-4-dark);
|
||||
|
||||
--color-correct: var(--correct);
|
||||
--color-correct-light: var(--correct-light);
|
||||
--color-incorrect: var(--incorrect);
|
||||
--color-incorrect-light: var(--incorrect-light);
|
||||
--color-hint: var(--hint);
|
||||
--color-hint-light: var(--hint-light);
|
||||
|
||||
--font-sans: "Nunito", "Trebuchet MS", "Segoe UI", sans-serif;
|
||||
--font-display: "Avenir Next", "Poppins", "Trebuchet MS", sans-serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
background:
|
||||
radial-gradient(circle at 15% 0%, color-mix(in srgb, var(--unit-1) 9%, transparent), transparent 46%),
|
||||
radial-gradient(circle at 90% 8%, color-mix(in srgb, var(--unit-3) 9%, transparent), transparent 34%),
|
||||
radial-gradient(circle at 85% 88%, color-mix(in srgb, var(--unit-2) 7%, transparent), transparent 40%),
|
||||
linear-gradient(180deg, #f7fbff 0%, var(--background) 100%);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-family: var(--font-display);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: color-mix(in srgb, var(--unit-1) 20%, transparent);
|
||||
}
|
||||
|
||||
/* Subtle gradient background pattern */
|
||||
.hero-gradient {
|
||||
background:
|
||||
radial-gradient(ellipse 100% 70% at 55% -15%, color-mix(in srgb, var(--unit-1) 14%, transparent), transparent),
|
||||
radial-gradient(ellipse 70% 40% at 95% 45%, color-mix(in srgb, var(--unit-3) 12%, transparent), transparent),
|
||||
radial-gradient(ellipse 70% 50% at 10% 75%, color-mix(in srgb, var(--unit-2) 10%, transparent), transparent),
|
||||
linear-gradient(180deg, rgb(255 255 255 / 0.94), rgb(255 255 255 / 0.82));
|
||||
}
|
||||
|
||||
/* Dot pattern overlay */
|
||||
.dot-pattern {
|
||||
background-image:
|
||||
linear-gradient(to right, rgb(203 218 239 / 0.45) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgb(203 218 239 / 0.45) 1px, transparent 1px);
|
||||
background-size: 22px 22px;
|
||||
}
|
||||
|
||||
@keyframes float-card {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
.float-card {
|
||||
animation: float-card 3.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.playground-bg {
|
||||
background: linear-gradient(180deg, #2f65e8 0%, #2a5fdc 100%);
|
||||
}
|
||||
|
||||
.playground-frame {
|
||||
border-left: 4px solid #1e4fc6;
|
||||
border-right: 4px solid #1e4fc6;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 16px 40px rgb(15 35 86 / 0.22);
|
||||
}
|
||||
|
||||
.section-ribbon {
|
||||
border: 2px solid #1f4fbe;
|
||||
background: linear-gradient(180deg, #2f65e8 0%, #2051c3 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.game-thumb-grid {
|
||||
background-image:
|
||||
linear-gradient(90deg, rgb(255 255 255 / 0.22) 1px, transparent 1px),
|
||||
linear-gradient(0deg, rgb(255 255 255 / 0.22) 1px, transparent 1px);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
|
||||
/* Smooth scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
/* Focus ring utility */
|
||||
.focus-ring {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.focus-ring:focus-visible {
|
||||
outline-color: var(--unit-1);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Cabrits Math Lab",
|
||||
description:
|
||||
"Build confidence in mathematics with interactive lessons and practice for secondary school students.",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -24,9 +14,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<body className="antialiased">
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
28
app/lessons/layout.tsx
Normal file
28
app/lessons/layout.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { MobileNav } from "@/components/layout/mobile-nav";
|
||||
|
||||
export default function LessonsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="playground-bg flex min-h-screen flex-col">
|
||||
<Header />
|
||||
<div className="playground-frame relative mx-auto flex w-full max-w-6xl flex-1">
|
||||
<Sidebar />
|
||||
<main className="flex-1 bg-white">
|
||||
<div className="relative p-4 lg:hidden">
|
||||
<MobileNav />
|
||||
</div>
|
||||
<div className="mx-auto max-w-4xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
42
app/lessons/page.tsx
Normal file
42
app/lessons/page.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import Link from "next/link";
|
||||
import { curriculum } from "@/lib/curriculum";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export default function LessonsOverview() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="mb-2 text-3xl font-bold tracking-tight">All Topics</h1>
|
||||
<p className="mb-10 text-muted">Form 1, Term 2 — Select a topic to explore</p>
|
||||
|
||||
{curriculum.map((unit) => (
|
||||
<section key={unit.slug} className="mb-12">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<Badge variant={unit.color}>Unit {unit.number}</Badge>
|
||||
<h2 className="text-xl font-semibold">{unit.title}</h2>
|
||||
<span className="hidden text-sm text-muted sm:inline">{unit.weeks}</span>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{unit.topics.map((topic) => (
|
||||
<Link key={topic.slug} href={`/lessons/${unit.slug}/${topic.slug}`}>
|
||||
<Card accent={unit.color} hover className="group h-full">
|
||||
<h3 className="mb-1 font-semibold">{topic.title}</h3>
|
||||
<p className="mb-3 text-sm leading-relaxed text-muted">{topic.description}</p>
|
||||
<div className="flex items-center justify-between text-xs text-muted">
|
||||
<span>Week {topic.week}</span>
|
||||
<span className="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
Explore
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-1-fractions/add-subtract/page.tsx
Normal file
22
app/lessons/unit-1-fractions/add-subtract/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { FractionOperationExplorer } from "@/components/explorers/fraction-operation-explorer";
|
||||
|
||||
export default function AddSubtractPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 1: Fractions", href: "/lessons/unit-1-fractions" },
|
||||
{ label: "Add & Subtract" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Add and Subtract Fractions</h1>
|
||||
|
||||
<FractionOperationExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-1-fractions/divide/page.tsx
Normal file
22
app/lessons/unit-1-fractions/divide/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { FractionOperationExplorer } from "@/components/explorers/fraction-operation-explorer";
|
||||
|
||||
export default function DividePage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 1: Fractions", href: "/lessons/unit-1-fractions" },
|
||||
{ label: "Divide" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Divide Fractions</h1>
|
||||
|
||||
<FractionOperationExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-1-fractions/fraction-of-quantity/page.tsx
Normal file
22
app/lessons/unit-1-fractions/fraction-of-quantity/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { FractionQuantityExplorer } from "@/components/explorers/fraction-quantity-explorer";
|
||||
|
||||
export default function FractionOfQuantityPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 1: Fractions", href: "/lessons/unit-1-fractions" },
|
||||
{ label: "Of a Quantity" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Fraction of a Quantity</h1>
|
||||
|
||||
<FractionQuantityExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-1-fractions/mixed-operations/page.tsx
Normal file
22
app/lessons/unit-1-fractions/mixed-operations/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { BODMASExplorer } from "@/components/explorers/bodmas-explorer";
|
||||
|
||||
export default function MixedOperationsPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 1: Fractions", href: "/lessons/unit-1-fractions" },
|
||||
{ label: "BODMAS" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Mixed Operations (BODMAS)</h1>
|
||||
|
||||
<BODMASExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-1-fractions/multiply/page.tsx
Normal file
22
app/lessons/unit-1-fractions/multiply/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { FractionOperationExplorer } from "@/components/explorers/fraction-operation-explorer";
|
||||
|
||||
export default function MultiplyPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 1: Fractions", href: "/lessons/unit-1-fractions" },
|
||||
{ label: "Multiply" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Multiply Fractions</h1>
|
||||
|
||||
<FractionOperationExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
app/lessons/unit-1-fractions/page.tsx
Normal file
41
app/lessons/unit-1-fractions/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link";
|
||||
import { curriculum } from "@/lib/curriculum";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
|
||||
export default function Unit1Overview() {
|
||||
const unit = curriculum[0];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: `Unit 1: ${unit.title}` },
|
||||
]}
|
||||
/>
|
||||
<div className="mb-10">
|
||||
<Badge variant="unit-1" className="mb-3">Unit 1 — {unit.weeks}</Badge>
|
||||
<h1 className="mb-2 text-3xl font-bold tracking-tight">{unit.title}</h1>
|
||||
<p className="text-muted leading-relaxed">{unit.description}</p>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{unit.topics.map((topic, i) => (
|
||||
<Link key={topic.slug} href={`/lessons/${unit.slug}/${topic.slug}`}>
|
||||
<Card accent="unit-1" hover className="group h-full">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-unit-1-light text-xs font-bold text-unit-1-dark shadow-[var(--shadow-sm)]">
|
||||
{i + 1}
|
||||
</span>
|
||||
<span className="text-xs text-muted">Week {topic.week}</span>
|
||||
</div>
|
||||
<h3 className="mb-1 font-semibold">{topic.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-muted">{topic.description}</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-1-fractions/whole-from-fractions/page.tsx
Normal file
22
app/lessons/unit-1-fractions/whole-from-fractions/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { FractionQuantityExplorer } from "@/components/explorers/fraction-quantity-explorer";
|
||||
|
||||
export default function WholeFromFractionsPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 1: Fractions", href: "/lessons/unit-1-fractions" },
|
||||
{ label: "Find the Whole" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Calculate the Whole from Fractions</h1>
|
||||
|
||||
<FractionQuantityExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-2-decimals/approximate/page.tsx
Normal file
22
app/lessons/unit-2-decimals/approximate/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { RoundingExplorer } from "@/components/explorers/rounding-explorer";
|
||||
|
||||
export default function ApproximatePage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 2: Decimals", href: "/lessons/unit-2-decimals" },
|
||||
{ label: "Approximate" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Approximate Decimals</h1>
|
||||
|
||||
<RoundingExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-2-decimals/compare-order/page.tsx
Normal file
22
app/lessons/unit-2-decimals/compare-order/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { DecimalOrderExplorer } from "@/components/explorers/decimal-order-explorer";
|
||||
|
||||
export default function CompareOrderPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 2: Decimals", href: "/lessons/unit-2-decimals" },
|
||||
{ label: "Compare & Order" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Compare and Order Decimals</h1>
|
||||
|
||||
<DecimalOrderExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
app/lessons/unit-2-decimals/page.tsx
Normal file
41
app/lessons/unit-2-decimals/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link";
|
||||
import { curriculum } from "@/lib/curriculum";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
|
||||
export default function Unit2Overview() {
|
||||
const unit = curriculum[1];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: `Unit 2: ${unit.title}` },
|
||||
]}
|
||||
/>
|
||||
<div className="mb-10">
|
||||
<Badge variant="unit-2" className="mb-3">Unit 2 — {unit.weeks}</Badge>
|
||||
<h1 className="mb-2 text-3xl font-bold tracking-tight">{unit.title}</h1>
|
||||
<p className="text-muted leading-relaxed">{unit.description}</p>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{unit.topics.map((topic, i) => (
|
||||
<Link key={topic.slug} href={`/lessons/${unit.slug}/${topic.slug}`}>
|
||||
<Card accent="unit-2" hover className="group h-full">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-unit-2-light text-xs font-bold text-unit-2-dark shadow-[var(--shadow-sm)]">
|
||||
{i + 7}
|
||||
</span>
|
||||
<span className="text-xs text-muted">Week {topic.week}</span>
|
||||
</div>
|
||||
<h3 className="mb-1 font-semibold">{topic.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-muted">{topic.description}</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-2-decimals/standard-form/page.tsx
Normal file
22
app/lessons/unit-2-decimals/standard-form/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { StandardFormExplorer } from "@/components/explorers/standard-form-explorer";
|
||||
|
||||
export default function StandardFormPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 2: Decimals", href: "/lessons/unit-2-decimals" },
|
||||
{ label: "Standard Form" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Standard Form (Scientific Notation)</h1>
|
||||
|
||||
<StandardFormExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-3-decimal-operations/add-subtract/page.tsx
Normal file
22
app/lessons/unit-3-decimal-operations/add-subtract/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { DecimalArithmeticExplorer } from "@/components/explorers/decimal-arithmetic-explorer";
|
||||
|
||||
export default function AddSubtractPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 3: Decimal Operations", href: "/lessons/unit-3-decimal-operations" },
|
||||
{ label: "Add & Subtract" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Add and Subtract Decimals</h1>
|
||||
|
||||
<DecimalArithmeticExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-3-decimal-operations/convert/page.tsx
Normal file
22
app/lessons/unit-3-decimal-operations/convert/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { ConversionExplorer } from "@/components/explorers/conversion-explorer";
|
||||
|
||||
export default function ConvertPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 3: Decimal Operations", href: "/lessons/unit-3-decimal-operations" },
|
||||
{ label: "Convert" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Convert Decimals and Fractions</h1>
|
||||
|
||||
<ConversionExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { DecimalArithmeticExplorer } from "@/components/explorers/decimal-arithmetic-explorer";
|
||||
|
||||
export default function MultiplyDividePage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 3: Decimal Operations", href: "/lessons/unit-3-decimal-operations" },
|
||||
{ label: "Multiply & Divide" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Multiply and Divide Decimals</h1>
|
||||
|
||||
<DecimalArithmeticExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
app/lessons/unit-3-decimal-operations/page.tsx
Normal file
41
app/lessons/unit-3-decimal-operations/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link";
|
||||
import { curriculum } from "@/lib/curriculum";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
|
||||
export default function Unit3Overview() {
|
||||
const unit = curriculum[2];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: `Unit 3: ${unit.title}` },
|
||||
]}
|
||||
/>
|
||||
<div className="mb-10">
|
||||
<Badge variant="unit-3" className="mb-3">Unit 3 — {unit.weeks}</Badge>
|
||||
<h1 className="mb-2 text-3xl font-bold tracking-tight">{unit.title}</h1>
|
||||
<p className="text-muted leading-relaxed">{unit.description}</p>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{unit.topics.map((topic, i) => (
|
||||
<Link key={topic.slug} href={`/lessons/${unit.slug}/${topic.slug}`}>
|
||||
<Card accent="unit-3" hover className="group h-full">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-unit-3-light text-xs font-bold text-unit-3-dark shadow-[var(--shadow-sm)]">
|
||||
{i + 10}
|
||||
</span>
|
||||
<span className="text-xs text-muted">Week {topic.week}</span>
|
||||
</div>
|
||||
<h3 className="mb-1 font-semibold">{topic.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-muted">{topic.description}</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-4-ratio-proportion/define-ratio/page.tsx
Normal file
22
app/lessons/unit-4-ratio-proportion/define-ratio/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { RatioExplorer } from "@/components/explorers/ratio-explorer";
|
||||
|
||||
export default function DefineRatioPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 4: Ratio & Proportion", href: "/lessons/unit-4-ratio-proportion" },
|
||||
{ label: "Define Ratio" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Define a Ratio</h1>
|
||||
|
||||
<RatioExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-4-ratio-proportion/divide-in-ratio/page.tsx
Normal file
22
app/lessons/unit-4-ratio-proportion/divide-in-ratio/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { RatioExplorer } from "@/components/explorers/ratio-explorer";
|
||||
|
||||
export default function DivideInRatioPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 4: Ratio & Proportion", href: "/lessons/unit-4-ratio-proportion" },
|
||||
{ label: "Divide in Ratio" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Divide a Quantity in a Given Ratio</h1>
|
||||
|
||||
<RatioExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { RatioExplorer } from "@/components/explorers/ratio-explorer";
|
||||
|
||||
export default function FractionsAndRatiosPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 4: Ratio & Proportion", href: "/lessons/unit-4-ratio-proportion" },
|
||||
{ label: "Fractions & Ratios" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Fractions and Ratios</h1>
|
||||
|
||||
<RatioExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
app/lessons/unit-4-ratio-proportion/page.tsx
Normal file
41
app/lessons/unit-4-ratio-proportion/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link";
|
||||
import { curriculum } from "@/lib/curriculum";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
|
||||
export default function Unit4Overview() {
|
||||
const unit = curriculum[3];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: `Unit 4: ${unit.title}` },
|
||||
]}
|
||||
/>
|
||||
<div className="mb-10">
|
||||
<Badge variant="unit-4" className="mb-3">Unit 4 — {unit.weeks}</Badge>
|
||||
<h1 className="mb-2 text-3xl font-bold tracking-tight">{unit.title}</h1>
|
||||
<p className="text-muted leading-relaxed">{unit.description}</p>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{unit.topics.map((topic, i) => (
|
||||
<Link key={topic.slug} href={`/lessons/${unit.slug}/${topic.slug}`}>
|
||||
<Card accent="unit-4" hover className="group h-full">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-unit-4-light text-xs font-bold text-unit-4-dark shadow-[var(--shadow-sm)]">
|
||||
{String.fromCharCode(97 + i)}
|
||||
</span>
|
||||
<span className="text-xs text-muted">Week {topic.week}</span>
|
||||
</div>
|
||||
<h3 className="mb-1 font-semibold">{topic.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-muted">{topic.description}</p>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-4-ratio-proportion/simplify-ratios/page.tsx
Normal file
22
app/lessons/unit-4-ratio-proportion/simplify-ratios/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { RatioExplorer } from "@/components/explorers/ratio-explorer";
|
||||
|
||||
export default function SimplifyRatiosPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 4: Ratio & Proportion", href: "/lessons/unit-4-ratio-proportion" },
|
||||
{ label: "Simplify Ratios" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Simplify Ratios</h1>
|
||||
|
||||
<RatioExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
app/lessons/unit-4-ratio-proportion/word-problems/page.tsx
Normal file
22
app/lessons/unit-4-ratio-proportion/word-problems/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
||||
import { RatioExplorer } from "@/components/explorers/ratio-explorer";
|
||||
|
||||
export default function WordProblemsPage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: "Lessons", href: "/lessons" },
|
||||
{ label: "Unit 4: Ratio & Proportion", href: "/lessons/unit-4-ratio-proportion" },
|
||||
{ label: "Word Problems" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<h1 className="text-3xl font-bold tracking-tight">Proportional Parts Word Problems</h1>
|
||||
|
||||
<RatioExplorer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
app/not-found.tsx
Normal file
37
app/not-found.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import Link from "next/link";
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="playground-bg flex min-h-screen flex-col">
|
||||
<Header />
|
||||
<main className="playground-frame hero-gradient relative mx-auto flex w-full max-w-6xl flex-1 items-center justify-center">
|
||||
<div className="dot-pattern absolute inset-0 opacity-25" />
|
||||
<div className="relative text-center">
|
||||
<p className="mb-2 text-sm font-semibold uppercase tracking-widest text-muted">
|
||||
Page not found
|
||||
</p>
|
||||
<h1 className="mb-4 text-8xl font-bold tracking-tighter">
|
||||
<span className="bg-gradient-to-r from-unit-1 via-unit-4 to-unit-2 bg-clip-text text-transparent">
|
||||
404
|
||||
</span>
|
||||
</h1>
|
||||
<p className="mb-8 text-lg text-muted">
|
||||
This page doesn't exist or has been moved.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center gap-2 rounded-xl bg-foreground px-6 py-3 font-medium text-background shadow-[var(--shadow-md)] transition-all duration-200 hover:shadow-[var(--shadow-lg)] hover:-translate-y-0.5"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
182
app/page.tsx
182
app/page.tsx
@@ -1,65 +1,133 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
import { curriculum } from "@/lib/curriculum";
|
||||
|
||||
const unitStyles = {
|
||||
"unit-1": {
|
||||
tile: "from-[#4e95ff] to-[#1f63ea]",
|
||||
unitCard: "border-[#8cb2ff] bg-[#eaf2ff]",
|
||||
chip: "bg-[#2059cc]",
|
||||
},
|
||||
"unit-2": {
|
||||
tile: "from-[#29c6c5] to-[#0f8d8c]",
|
||||
unitCard: "border-[#89e5dd] bg-[#eafffb]",
|
||||
chip: "bg-[#0f8d8c]",
|
||||
},
|
||||
"unit-3": {
|
||||
tile: "from-[#ffab55] to-[#e97617]",
|
||||
unitCard: "border-[#ffd2a3] bg-[#fff4e8]",
|
||||
chip: "bg-[#d26a17]",
|
||||
},
|
||||
"unit-4": {
|
||||
tile: "from-[#ff5f85] to-[#d11b56]",
|
||||
unitCard: "border-[#ffb2c7] bg-[#fff0f4]",
|
||||
chip: "bg-[#c6174e]",
|
||||
},
|
||||
};
|
||||
|
||||
const topicTiles = [
|
||||
"from-[#4e95ff] via-[#357fe9] to-[#1f63ea]",
|
||||
"from-[#2ac3be] via-[#1da8a3] to-[#0f8d8c]",
|
||||
"from-[#ffb067] via-[#f2913a] to-[#d86e16]",
|
||||
"from-[#ff6f98] via-[#ef3d72] to-[#cb1c54]",
|
||||
"from-[#7d95ff] via-[#6078f2] to-[#3f56d9]",
|
||||
"from-[#4ccf7b] via-[#2fb45f] to-[#1f944b]",
|
||||
"from-[#ff8f8f] via-[#f26b6b] to-[#d94545]",
|
||||
"from-[#5eb7ff] via-[#3f99ef] to-[#2a7fda]",
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
const allTopics = curriculum.flatMap((unit) =>
|
||||
unit.topics.map((topic) => ({
|
||||
unit,
|
||||
topic,
|
||||
})),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
<div className="playground-bg flex min-h-screen flex-col">
|
||||
<Header />
|
||||
<main className="playground-frame mx-auto w-full max-w-6xl flex-1 px-3 pb-10 sm:px-5">
|
||||
<section className="mt-4 rounded-md border-2 border-[#1f50bf] bg-[#f1f6ff] p-4 sm:p-6">
|
||||
<p className="text-center text-sm font-bold text-[#26438b]">
|
||||
Interactive maths practice for secondary school students
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
<h1 className="mt-2 text-center text-3xl font-extrabold text-[#17367d] sm:text-5xl">
|
||||
Math Topics Students Love to Explore
|
||||
</h1>
|
||||
<p className="mx-auto mt-3 max-w-3xl text-center text-sm font-semibold text-[#33508e] sm:text-base">
|
||||
Explore each topic with visual examples, short explanations, and guided lessons.
|
||||
Pick a unit, jump into a topic, and start solving.
|
||||
</p>
|
||||
<div className="mt-5 flex flex-wrap items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/lessons"
|
||||
className="rounded-sm bg-[#e6503e] px-5 py-2.5 text-sm font-extrabold text-white shadow-md transition-colors hover:bg-[#cc4231]"
|
||||
>
|
||||
Browse Lessons
|
||||
</Link>
|
||||
<Link
|
||||
href="/practice"
|
||||
className="rounded-sm bg-[#0e9d50] px-5 py-2.5 text-sm font-extrabold text-white shadow-md transition-colors hover:bg-[#0c7f41]"
|
||||
>
|
||||
Start Practice
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-5">
|
||||
<h2 className="section-ribbon rounded-sm px-3 py-2 text-lg font-extrabold sm:text-xl">
|
||||
Unit Worlds
|
||||
</h2>
|
||||
<div className="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{curriculum.map((unit) => (
|
||||
<Link
|
||||
key={unit.slug}
|
||||
href={`/lessons/${unit.slug}`}
|
||||
className={`rounded-md border-2 p-3 transition-transform hover:-translate-y-0.5 ${unitStyles[unit.color].unitCard}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h3 className="text-lg font-extrabold text-[#142f75]">{unit.title}</h3>
|
||||
<span className={`rounded-full px-2 py-1 text-xs font-extrabold text-white ${unitStyles[unit.color].chip}`}>
|
||||
Unit {unit.number}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-sm font-semibold text-[#35508b]">{unit.description}</p>
|
||||
<p className="mt-2 text-xs font-bold uppercase tracking-wide text-[#4262aa]">{unit.weeks}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-6">
|
||||
<h2 className="section-ribbon rounded-sm px-3 py-2 text-lg font-extrabold sm:text-xl">
|
||||
Topic Explorer
|
||||
</h2>
|
||||
<div className="mt-3 grid grid-cols-2 gap-2.5 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{allTopics.map(({ unit, topic }, index) => (
|
||||
<Link
|
||||
key={`${unit.slug}-${topic.slug}`}
|
||||
href={`/lessons/${unit.slug}/${topic.slug}`}
|
||||
className="rounded-sm border border-[#8ea9df] bg-white p-2 transition-transform hover:-translate-y-0.5 hover:shadow-md"
|
||||
>
|
||||
<div className={`game-thumb-grid mb-2 flex aspect-[4/3] flex-col justify-between rounded-sm bg-gradient-to-br p-2 text-white ${topicTiles[index % topicTiles.length]}`}>
|
||||
<span className="w-fit rounded-sm bg-black/25 px-1.5 py-0.5 text-[10px] font-extrabold uppercase tracking-wide">
|
||||
Week {topic.week}
|
||||
</span>
|
||||
<span className="text-center text-xs font-extrabold leading-tight">
|
||||
{topic.shortTitle}
|
||||
</span>
|
||||
</div>
|
||||
<p className="min-h-[2.5rem] text-xs font-extrabold text-[#193981]">
|
||||
{topic.title}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
160
app/practice/page.tsx
Normal file
160
app/practice/page.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
import { PracticeSection } from "@/components/practice/practice-section";
|
||||
import type { Difficulty, MathProblem } from "@/lib/problems/types";
|
||||
|
||||
import { generateFractionAddSubtract, generateFractionMultiply, generateFractionDivide, generateFractionOfQuantity, generateWholeFromFraction } from "@/lib/problems/generators/fraction-problems";
|
||||
import { generateDecimalCompareOrder, generateDecimalRounding, generateStandardForm, generateDecimalAddSubtract, generateDecimalMultiplyDivide, generateDecimalConversion } from "@/lib/problems/generators/decimal-problems";
|
||||
import { generateSimplifyRatio, generateDivideInRatio, generateRatioWordProblem } from "@/lib/problems/generators/ratio-problems";
|
||||
|
||||
interface TopicGenerator {
|
||||
unitSlug: string;
|
||||
topicSlug: string;
|
||||
label: string;
|
||||
generator: (difficulty: Difficulty) => MathProblem;
|
||||
unitColor: "unit-1" | "unit-2" | "unit-3" | "unit-4";
|
||||
}
|
||||
|
||||
type UnitColor = TopicGenerator["unitColor"];
|
||||
|
||||
const TOPIC_GENERATORS: TopicGenerator[] = [
|
||||
{ unitSlug: "unit-1-fractions", topicSlug: "add-subtract", label: "Add & Subtract Fractions", generator: generateFractionAddSubtract, unitColor: "unit-1" },
|
||||
{ unitSlug: "unit-1-fractions", topicSlug: "multiply", label: "Multiply Fractions", generator: generateFractionMultiply, unitColor: "unit-1" },
|
||||
{ unitSlug: "unit-1-fractions", topicSlug: "divide", label: "Divide Fractions", generator: generateFractionDivide, unitColor: "unit-1" },
|
||||
{ unitSlug: "unit-1-fractions", topicSlug: "fraction-of-quantity", label: "Fraction of a Quantity", generator: generateFractionOfQuantity, unitColor: "unit-1" },
|
||||
{ unitSlug: "unit-1-fractions", topicSlug: "whole-from-fractions", label: "Find the Whole", generator: generateWholeFromFraction, unitColor: "unit-1" },
|
||||
{ unitSlug: "unit-2-decimals", topicSlug: "compare-order", label: "Compare & Order Decimals", generator: generateDecimalCompareOrder, unitColor: "unit-2" },
|
||||
{ unitSlug: "unit-2-decimals", topicSlug: "approximate", label: "Approximate Decimals", generator: generateDecimalRounding, unitColor: "unit-2" },
|
||||
{ unitSlug: "unit-2-decimals", topicSlug: "standard-form", label: "Standard Form", generator: generateStandardForm, unitColor: "unit-2" },
|
||||
{ unitSlug: "unit-3-decimal-operations", topicSlug: "convert", label: "Convert Decimals & Fractions", generator: generateDecimalConversion, unitColor: "unit-3" },
|
||||
{ unitSlug: "unit-3-decimal-operations", topicSlug: "add-subtract", label: "Add & Subtract Decimals", generator: generateDecimalAddSubtract, unitColor: "unit-3" },
|
||||
{ unitSlug: "unit-3-decimal-operations", topicSlug: "multiply-divide", label: "Multiply & Divide Decimals", generator: generateDecimalMultiplyDivide, unitColor: "unit-3" },
|
||||
{ unitSlug: "unit-4-ratio-proportion", topicSlug: "simplify-ratios", label: "Simplify Ratios", generator: generateSimplifyRatio, unitColor: "unit-4" },
|
||||
{ unitSlug: "unit-4-ratio-proportion", topicSlug: "divide-in-ratio", label: "Divide in a Ratio", generator: generateDivideInRatio, unitColor: "unit-4" },
|
||||
{ unitSlug: "unit-4-ratio-proportion", topicSlug: "word-problems", label: "Ratio Word Problems", generator: generateRatioWordProblem, unitColor: "unit-4" },
|
||||
];
|
||||
|
||||
const unitColors: Record<UnitColor, { activeFilter: string; inactiveFilter: string; label: string }> = {
|
||||
"unit-1": {
|
||||
activeFilter: "border-unit-1 bg-unit-1 text-white shadow-[var(--shadow-sm)]",
|
||||
inactiveFilter: "border-unit-1/40 text-unit-1-dark hover:bg-unit-1-light",
|
||||
label: "Fractions",
|
||||
},
|
||||
"unit-2": {
|
||||
activeFilter: "border-unit-2 bg-unit-2 text-white shadow-[var(--shadow-sm)]",
|
||||
inactiveFilter: "border-unit-2/40 text-unit-2-dark hover:bg-unit-2-light",
|
||||
label: "Decimals",
|
||||
},
|
||||
"unit-3": {
|
||||
activeFilter: "border-unit-3 bg-unit-3 text-white shadow-[var(--shadow-sm)]",
|
||||
inactiveFilter: "border-unit-3/40 text-unit-3-dark hover:bg-unit-3-light",
|
||||
label: "Decimal Operations",
|
||||
},
|
||||
"unit-4": {
|
||||
activeFilter: "border-unit-4 bg-unit-4 text-white shadow-[var(--shadow-sm)]",
|
||||
inactiveFilter: "border-unit-4/40 text-unit-4-dark hover:bg-unit-4-light",
|
||||
label: "Ratio & Proportion",
|
||||
},
|
||||
};
|
||||
|
||||
export default function PracticePage() {
|
||||
const [selectedTopic, setSelectedTopic] = useState<TopicGenerator | null>(null);
|
||||
const [filterUnit, setFilterUnit] = useState<UnitColor | null>(null);
|
||||
|
||||
const filtered = filterUnit
|
||||
? TOPIC_GENERATORS.filter((t) => t.unitColor === filterUnit)
|
||||
: TOPIC_GENERATORS;
|
||||
|
||||
return (
|
||||
<div className="playground-bg flex min-h-screen flex-col">
|
||||
<Header />
|
||||
<main className="playground-frame mx-auto w-full max-w-6xl flex-1 bg-white">
|
||||
<div className="mx-auto max-w-4xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<h1 className="mb-2 text-3xl font-extrabold tracking-tight">Practice</h1>
|
||||
<p className="mb-8 text-muted">
|
||||
Pick a topic and build your confidence with fresh questions each round.
|
||||
</p>
|
||||
|
||||
{/* Unit filters */}
|
||||
<div className="mb-6 flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => setFilterUnit(null)}
|
||||
className={`rounded-full border-2 px-4 py-1.5 text-sm font-extrabold transition-all duration-200 ${
|
||||
filterUnit === null
|
||||
? "border-foreground bg-foreground text-background shadow-[var(--shadow-sm)]"
|
||||
: "border-border text-muted hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
All Topics
|
||||
</button>
|
||||
{(["unit-1", "unit-2", "unit-3", "unit-4"] as const).map((uc) => (
|
||||
<button
|
||||
key={uc}
|
||||
onClick={() => setFilterUnit(filterUnit === uc ? null : uc)}
|
||||
className={`rounded-full border-2 px-4 py-1.5 text-sm font-extrabold transition-all duration-200 ${
|
||||
filterUnit === uc
|
||||
? unitColors[uc].activeFilter
|
||||
: unitColors[uc].inactiveFilter
|
||||
}`}
|
||||
>
|
||||
{unitColors[uc].label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Topic grid */}
|
||||
{!selectedTopic && (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{filtered.map((topic) => (
|
||||
<Card
|
||||
key={`${topic.unitSlug}-${topic.topicSlug}`}
|
||||
hover
|
||||
className="cursor-pointer"
|
||||
accent={topic.unitColor}
|
||||
onClick={() => setSelectedTopic(topic)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-bold">{topic.label}</span>
|
||||
<Badge variant={topic.unitColor}>
|
||||
{unitColors[topic.unitColor].label}
|
||||
</Badge>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Practice area */}
|
||||
{selectedTopic && (
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => setSelectedTopic(null)}
|
||||
className="mb-6"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
Back to Topics
|
||||
</Button>
|
||||
|
||||
<PracticeSection
|
||||
title={`Practice: ${selectedTopic.label}`}
|
||||
generator={selectedTopic.generator}
|
||||
unitColor={selectedTopic.unitColor}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user