Build interactive geometry education apps with Next.js 15, TypeScript, Tailwind CSS, and Framer Motion animations
Expert guide for building interactive geometry education applications using Next.js 15 App Router, TypeScript, Tailwind CSS, Framer Motion, and Zustand state management.
This skill helps you build educational geometry applications with:
```
geometry-tutor/
├── public/images/ # Static assets
├── src/
│ ├── app/
│ │ ├── (auth)/ # Auth routes (login, register)
│ │ ├── (dashboard)/ # Protected dashboard routes
│ │ │ ├── layout.tsx # Dashboard layout wrapper
│ │ │ ├── page.tsx # Dashboard home
│ │ │ ├── progress/ # Progress tracking
│ │ │ ├── settings/ # User settings
│ │ │ └── modules/[moduleId]/ # Dynamic module routes
│ │ │ ├── lesson/
│ │ │ ├── demonstration/
│ │ │ ├── quiz/
│ │ │ └── review/
│ │ ├── layout.tsx # Root layout
│ │ └── globals.css
│ ├── components/
│ │ ├── ui/ # Reusable UI primitives
│ │ ├── modules/ # Module-specific components
│ │ ├── layout/ # Layout components
│ │ └── shared/ # Shared utility components
│ ├── lib/
│ │ ├── constants.ts # Module data & config
│ │ ├── types.ts # TypeScript definitions
│ │ └── utils.ts # Utility functions
│ ├── hooks/
│ │ └── use-module-progress.ts
│ └── store/
│ └── user-progress-store.ts # Zustand state
```
```bash
npx create-next-app@latest geometry-tutor --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd geometry-tutor
```
```bash
npm install framer-motion zustand @radix-ui/react-dialog @radix-ui/react-progress @radix-ui/react-tabs
npm install -D @tailwindcss/typography
```
```bash
mkdir -p src/app/\(auth\)/{login,register} \
src/app/\(dashboard\)/{progress,settings,modules} \
src/components/{ui,modules,layout,shared} \
src/{lib,hooks,store} \
public/images
```
Update `tailwind.config.js` with custom theme:
```js
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
darkMode: 'class',
theme: {
extend: {
colors: {
primary: { /* custom palette */ },
secondary: { /* custom palette */ },
},
animation: {
'fade-in': 'fadeIn 0.3s ease-in',
'slide-up': 'slideUp 0.4s ease-out',
},
},
},
plugins: [require('@tailwindcss/typography')],
};
```
Create `src/lib/types.ts`:
```typescript
export interface Module {
id: string;
title: string;
description: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
estimatedTime: string;
sections: Section[];
}
export interface Section {
type: 'lesson' | 'demonstration' | 'quiz' | 'review';
title: string;
completed: boolean;
}
export interface UserProgress {
moduleId: string;
completedSections: string[];
score?: number;
lastAccessed: Date;
}
```
Create `src/store/user-progress-store.ts`:
```typescript
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface ProgressState {
progress: Record<string, UserProgress>;
updateProgress: (moduleId: string, section: string) => void;
getModuleProgress: (moduleId: string) => UserProgress | undefined;
}
export const useProgressStore = create<ProgressState>()(
persist(
(set, get) => ({
progress: {},
updateProgress: (moduleId, section) => {
set((state) => ({
progress: {
...state.progress,
[moduleId]: {
...state.progress[moduleId],
completedSections: [
...(state.progress[moduleId]?.completedSections || []),
section,
],
lastAccessed: new Date(),
},
},
}));
},
getModuleProgress: (moduleId) => get().progress[moduleId],
}),
{ name: 'geometry-tutor-progress' }
)
);
```
Create `src/lib/constants.ts` with module definitions:
```typescript
export const MODULES: Module[] = [
{
id: 'pythagorean-theorem',
title: 'Pythagorean Theorem',
description: 'Master the fundamental relationship in right triangles',
difficulty: 'beginner',
estimatedTime: '30 min',
sections: [
{ type: 'lesson', title: 'Understanding the Theorem', completed: false },
{ type: 'demonstration', title: 'Interactive Proof', completed: false },
{ type: 'quiz', title: 'Test Your Knowledge', completed: false },
{ type: 'review', title: 'Summary & Practice', completed: false },
],
},
// Add more modules...
];
```
Create `src/components/modules/pythagorean-demo.tsx` with Framer Motion:
```typescript
'use client';
import { motion } from 'framer-motion';
import { useState } from 'react';
export function PythagoreanDemo() {
const [a, setA] = useState(3);
const [b, setB] = useState(4);
const c = Math.sqrt(a * a + b * b);
return (
<div className="space-y-6">
<motion.svg
width="400"
height="400"
viewBox="0 0 400 400"
className="mx-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{/* Animated triangle visualization */}
<motion.line
x1="50" y1="300" x2={50 + a * 40} y2="300"
stroke="currentColor"
strokeWidth="3"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.8 }}
/>
{/* Add more SVG elements */}
</motion.svg>
<div className="flex gap-4">
<label>
Side A: <input type="range" min="1" max="10" value={a} onChange={(e) => setA(+e.target.value)} />
</label>
<label>
Side B: <input type="range" min="1" max="10" value={b} onChange={(e) => setB(+e.target.value)} />
</label>
</div>
<div className="text-center text-lg">
{a}² + {b}² = {c.toFixed(2)}²
</div>
</div>
);
}
```
Create `src/components/modules/quiz-component.tsx`:
```typescript
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface Question {
id: string;
question: string;
options: string[];
correctAnswer: number;
explanation: string;
}
export function QuizComponent({ questions }: { questions: Question[] }) {
const [currentQuestion, setCurrentQuestion] = useState(0);
const [selectedAnswer, setSelectedAnswer] = useState<number | null>(null);
const [showExplanation, setShowExplanation] = useState(false);
const handleAnswer = (index: number) => {
setSelectedAnswer(index);
setShowExplanation(true);
};
return (
<div className="max-w-2xl mx-auto">
<AnimatePresence mode="wait">
<motion.div
key={currentQuestion}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-6"
>
<h3 className="text-xl font-semibold">
{questions[currentQuestion].question}
</h3>
<div className="space-y-3">
{questions[currentQuestion].options.map((option, index) => (
<button
key={index}
onClick={() => handleAnswer(index)}
className={`w-full p-4 text-left rounded-lg border-2 transition ${
selectedAnswer === index
? index === questions[currentQuestion].correctAnswer
? 'border-green-500 bg-green-50'
: 'border-red-500 bg-red-50'
: 'border-gray-200 hover:border-gray-300'
}`}
disabled={showExplanation}
>
{option}
</button>
))}
</div>
{showExplanation && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="p-4 bg-blue-50 rounded-lg"
>
<p className="font-semibold">Explanation:</p>
<p>{questions[currentQuestion].explanation}</p>
</motion.div>
)}
</motion.div>
</AnimatePresence>
</div>
);
}
```
Create `src/app/(dashboard)/layout.tsx`:
```typescript
import { Sidebar } from '@/components/layout/sidebar';
import { Header } from '@/components/layout/header';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex h-screen">
<Sidebar />
<div className="flex-1 flex flex-col">
<Header />
<main className="flex-1 overflow-y-auto p-6 bg-gray-50">
{children}
</main>
</div>
</div>
);
}
```
Create `src/app/(dashboard)/modules/[moduleId]/page.tsx`:
```typescript
import { MODULES } from '@/lib/constants';
import { notFound } from 'next/navigation';
import Link from 'next/link';
export default function ModulePage({ params }: { params: { moduleId: string } }) {
const module = MODULES.find((m) => m.id === params.moduleId);
if (!module) notFound();
return (
<div className="max-w-4xl mx-auto space-y-8">
<div>
<h1 className="text-4xl font-bold">{module.title}</h1>
<p className="text-gray-600 mt-2">{module.description}</p>
</div>
<div className="grid gap-4">
{module.sections.map((section, index) => (
<Link
key={index}
href={`/modules/${module.id}/${section.type}`}
className="p-6 border rounded-lg hover:shadow-md transition"
>
<h3 className="text-xl font-semibold">{section.title}</h3>
<p className="text-sm text-gray-500 mt-1">{section.type}</p>
</Link>
))}
</div>
</div>
);
}
```
1. **Run development server**: `npm run dev`
2. **Test on multiple devices**: Use browser dev tools + real devices
3. **Check TypeScript errors**: `npm run type-check`
4. **Lint code**: `npm run lint`
5. **Build for production**: `npm run build`
1. Push code to GitHub repository
2. Connect repository to Vercel
3. Configure environment variables (if any)
4. Deploy with automatic builds on push
1. Add module definition to `src/lib/constants.ts`
2. Create module directory: `src/app/(dashboard)/modules/[new-module-id]/`
3. Add lesson/demonstration/quiz/review pages
4. Create module-specific components in `src/components/modules/`
1. Use SVG for geometric shapes
2. Animate with Framer Motion's `motion.svg` and child elements
3. Add interactive controls (sliders, buttons) to manipulate parameters
4. Display real-time calculations based on user input
1. Define question data with correct answers and explanations
2. Use `QuizComponent` or create custom variant
3. Track answers in component state
4. Update global progress store on completion
5. Provide immediate feedback with animations
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/geometry-tutor-development-guide/raw