Refactor site structure and add comprehensive interactive index

- Move Puzzles and Miscellany under Themes
- Add Contest Problems as new theme
- Create About page with comprehensive site information
- Create InteractiveIndex component with advanced filtering and sorting
- Update navigation to include Index, Themes, and About
- Add pagination (10 items per page) with search and tag filtering
- Update routing structure for new theme organization
- Remove standalone Puzzles and Miscellany pages
- Add new theme pages: /themes/puzzles, /themes/miscellany, /themes/contest-problems
- Update homepage to point to new interactive index
- Maintain consistent layout and styling across all new pages
This commit is contained in:
Neeldhara Misra 2025-07-20 10:07:04 +05:30
parent 85a8298352
commit 3f04e8c7d7
9 changed files with 443 additions and 11 deletions

View file

@ -4,15 +4,18 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index"; import Index from "./pages/Index";
import InteractiveIndex from "./components/InteractiveIndex";
import Themes from "./pages/Themes"; import Themes from "./pages/Themes";
import Puzzles from "./pages/Puzzles"; import About from "./pages/About";
import Miscellany from "./pages/Miscellany";
import DiscreteMath from "./pages/theme-pages/DiscreteMath"; import DiscreteMath from "./pages/theme-pages/DiscreteMath";
import SocialChoice from "./pages/theme-pages/SocialChoice"; import SocialChoice from "./pages/theme-pages/SocialChoice";
import AdvancedAlgorithms from "./pages/theme-pages/AdvancedAlgorithms"; import AdvancedAlgorithms from "./pages/theme-pages/AdvancedAlgorithms";
import DataStructures from "./pages/theme-pages/DataStructures"; import DataStructures from "./pages/theme-pages/DataStructures";
import Games from "./pages/theme-pages/Games"; import Games from "./pages/theme-pages/Games";
import CardsMath from "./pages/theme-pages/CardsMath"; import CardsMath from "./pages/theme-pages/CardsMath";
import Puzzles from "./pages/theme-pages/Puzzles";
import Miscellany from "./pages/theme-pages/Miscellany";
import ContestProblems from "./pages/theme-pages/ContestProblems";
import BinaryNumberGamePage from "./pages/BinaryNumberGamePage"; import BinaryNumberGamePage from "./pages/BinaryNumberGamePage";
import BinaryNumberGameGreenScreen from "./pages/BinaryNumberGameGreenScreen"; import BinaryNumberGameGreenScreen from "./pages/BinaryNumberGameGreenScreen";
import BinarySearchTrickPage from "./pages/BinarySearchTrickPage"; import BinarySearchTrickPage from "./pages/BinarySearchTrickPage";
@ -38,9 +41,9 @@ const App = () => (
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
<Route path="/index" element={<InteractiveIndex />} />
<Route path="/themes" element={<Themes />} /> <Route path="/themes" element={<Themes />} />
<Route path="/puzzles" element={<Puzzles />} /> <Route path="/about" element={<About />} />
<Route path="/miscellany" element={<Miscellany />} />
{/* Theme-specific routes */} {/* Theme-specific routes */}
<Route path="/themes/discrete-math" element={<DiscreteMath />} /> <Route path="/themes/discrete-math" element={<DiscreteMath />} />
@ -56,6 +59,9 @@ const App = () => (
<Route path="/themes/data-structures" element={<DataStructures />} /> <Route path="/themes/data-structures" element={<DataStructures />} />
<Route path="/themes/games" element={<Games />} /> <Route path="/themes/games" element={<Games />} />
<Route path="/themes/cards-math" element={<CardsMath />} /> <Route path="/themes/cards-math" element={<CardsMath />} />
<Route path="/themes/puzzles" element={<Puzzles />} />
<Route path="/themes/miscellany" element={<Miscellany />} />
<Route path="/themes/contest-problems" element={<ContestProblems />} />
{/* Direct interactive routes */} {/* Direct interactive routes */}
<Route path="/binary-number-game" element={<BinaryNumberGamePage />} /> <Route path="/binary-number-game" element={<BinaryNumberGamePage />} />

View file

@ -0,0 +1,292 @@
import React, { useState, useMemo } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Search, SortAsc, SortDesc } from 'lucide-react';
import Layout from '@/components/Layout';
interface Interactive {
id: string;
title: string;
description: string;
tags: string[];
path: string;
theme: string;
dateAdded?: string; // For recency sorting
}
// This would ideally be generated from the actual components, but for now we'll hardcode
const allInteractives: Interactive[] = [
{
id: 'binary-number-game',
title: 'Binary Number Representation',
description: 'Learn how numbers are represented in binary (base-2) format through an interactive guessing game.',
tags: ['binary', 'numbers', 'conversion', 'representation'],
path: '/binary-number-game',
theme: 'Discrete Math',
dateAdded: '2024-01-15'
},
{
id: 'binary-search-trick',
title: 'Binary Search Magic Trick',
description: 'Perform an amazing magic trick that reveals how binary numbers work. Think of a number and watch the magic happen!',
tags: ['binary', 'magic', 'numbers', 'trick', 'data-structures'],
path: '/binary-search-trick',
theme: 'Data Structures',
dateAdded: '2024-02-20'
},
{
id: 'game-of-sim',
title: 'Game of Sim',
description: 'A strategic two-player game where you take turns coloring edges between vertices, trying to avoid creating a triangle of your own color.',
tags: ['strategy', 'graph-theory', 'two-player', 'logic', 'game-theory'],
path: '/game-of-sim',
theme: 'Games',
dateAdded: '2024-03-10'
}
];
type SortField = 'title' | 'dateAdded';
type SortDirection = 'asc' | 'desc';
const InteractiveIndex = () => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedTag, setSelectedTag] = useState<string>('');
const [sortField, setSortField] = useState<SortField>('title');
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10;
// Get all unique tags
const allTags = useMemo(() => {
const tags = new Set<string>();
allInteractives.forEach(interactive => {
interactive.tags.forEach(tag => tags.add(tag));
});
return Array.from(tags).sort();
}, []);
// Filter and sort interactives
const filteredAndSortedInteractives = useMemo(() => {
let filtered = allInteractives.filter(interactive => {
const matchesSearch = interactive.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
interactive.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
interactive.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesTag = !selectedTag || interactive.tags.includes(selectedTag);
return matchesSearch && matchesTag;
});
// Sort
filtered.sort((a, b) => {
let aValue = a[sortField];
let bValue = b[sortField];
if (sortField === 'title') {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
}
if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1;
if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1;
return 0;
});
return filtered;
}, [searchTerm, selectedTag, sortField, sortDirection]);
// Pagination
const totalPages = Math.ceil(filteredAndSortedInteractives.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const paginatedInteractives = filteredAndSortedInteractives.slice(startIndex, startIndex + itemsPerPage);
const handleTagClick = (tag: string) => {
setSelectedTag(selectedTag === tag ? '' : tag);
setCurrentPage(1);
};
const handleSortChange = (field: SortField) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
setCurrentPage(1);
};
return (
<Layout>
<div className="py-12 px-4">
<div className="max-w-6xl mx-auto">
<h1 className="text-4xl font-bold text-foreground mb-8">All Interactives</h1>
{/* Search and Sort Controls */}
<div className="flex flex-col sm:flex-row gap-4 mb-8">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
<Input
placeholder="Search interactives..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
className="pl-10"
/>
</div>
<div className="flex gap-2">
<Button
variant="outline"
onClick={() => handleSortChange('title')}
className="flex items-center gap-2"
>
{sortField === 'title' ? (
sortDirection === 'asc' ? <SortAsc className="w-4 h-4" /> : <SortDesc className="w-4 h-4" />
) : (
<SortAsc className="w-4 h-4" />
)}
Title
</Button>
<Button
variant="outline"
onClick={() => handleSortChange('dateAdded')}
className="flex items-center gap-2"
>
{sortField === 'dateAdded' ? (
sortDirection === 'asc' ? <SortAsc className="w-4 h-4" /> : <SortDesc className="w-4 h-4" />
) : (
<SortAsc className="w-4 h-4" />
)}
Recency
</Button>
</div>
</div>
{/* Tag Filter */}
{selectedTag && (
<div className="mb-6">
<span className="text-sm text-muted-foreground mr-2">Filtered by:</span>
<Badge
variant="secondary"
className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
onClick={() => setSelectedTag('')}
>
{selectedTag} ×
</Badge>
</div>
)}
{/* Tags */}
<div className="mb-8">
<h3 className="text-lg font-semibold text-foreground mb-3">Filter by Tags</h3>
<div className="flex flex-wrap gap-2">
{allTags.map(tag => (
<Badge
key={tag}
variant={selectedTag === tag ? "default" : "outline"}
className="cursor-pointer hover:bg-primary hover:text-primary-foreground"
onClick={() => handleTagClick(tag)}
>
{tag}
</Badge>
))}
</div>
</div>
{/* Results Count */}
<div className="mb-6">
<p className="text-muted-foreground">
Showing {filteredAndSortedInteractives.length} of {allInteractives.length} interactives
</p>
</div>
{/* Interactive List */}
<div className="space-y-4 mb-8">
{paginatedInteractives.map(interactive => (
<Card key={interactive.id} className="hover:shadow-md transition-shadow">
<CardHeader>
<CardTitle className="text-xl">
<a
href={interactive.path}
className="text-foreground hover:text-primary transition-colors"
>
{interactive.title}
</a>
</CardTitle>
<div className="flex flex-wrap gap-2 mt-2">
{interactive.tags.map(tag => (
<Badge
key={tag}
variant="secondary"
className="cursor-pointer hover:bg-primary hover:text-primary-foreground"
onClick={() => handleTagClick(tag)}
>
{tag}
</Badge>
))}
</div>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{interactive.description}</p>
<div className="mt-3">
<Badge variant="outline" className="text-xs">
{interactive.theme}
</Badge>
</div>
</CardContent>
</Card>
))}
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="flex justify-center gap-2">
<Button
variant="outline"
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
>
Previous
</Button>
<div className="flex items-center gap-2">
{Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
<Button
key={page}
variant={currentPage === page ? "default" : "outline"}
size="sm"
onClick={() => setCurrentPage(page)}
>
{page}
</Button>
))}
</div>
<Button
variant="outline"
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
>
Next
</Button>
</div>
)}
{paginatedInteractives.length === 0 && (
<div className="text-center py-12">
<p className="text-muted-foreground text-lg">No interactives found matching your criteria.</p>
</div>
)}
</div>
</div>
</Layout>
);
};
export default InteractiveIndex;

View file

@ -5,9 +5,9 @@ const Navigation = () => {
const location = useLocation(); const location = useLocation();
const navItems = [ const navItems = [
{ name: "Index", path: "/index" },
{ name: "Themes", path: "/themes" }, { name: "Themes", path: "/themes" },
{ name: "Puzzles", path: "/puzzles" }, { name: "About", path: "/about" }
{ name: "Miscellany", path: "/miscellany" }
]; ];
return ( return (

56
src/pages/About.tsx Normal file
View file

@ -0,0 +1,56 @@
import Layout from "@/components/Layout";
const About = () => {
return (
<Layout>
<div className="py-12 px-4">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold text-foreground mb-8">About Interactives</h1>
<div className="prose prose-lg max-w-none">
<p className="text-lg text-muted-foreground mb-6">
Welcome to Interactives, a comprehensive educational platform designed to enhance learning through interactive experiences. Our mission is to make complex concepts accessible and engaging through hands-on exploration.
</p>
<h2 className="text-2xl font-semibold text-foreground mt-8 mb-4">Our Mission</h2>
<p className="text-muted-foreground mb-6">
We believe that learning is most effective when it's interactive, visual, and engaging. Our platform provides a collection of carefully crafted interactive tools that supplement traditional educational methods, helping students and educators alike to better understand and explore complex topics.
</p>
<h2 className="text-2xl font-semibold text-foreground mt-8 mb-4">What We Offer</h2>
<ul className="list-disc list-inside text-muted-foreground mb-6 space-y-2">
<li><strong>Interactive Demonstrations:</strong> Visual representations of complex concepts</li>
<li><strong>Educational Games:</strong> Learning through play and problem-solving</li>
<li><strong>Algorithm Visualizations:</strong> Step-by-step breakdowns of computational processes</li>
<li><strong>Mathematical Explorations:</strong> Hands-on discovery of mathematical principles</li>
<li><strong>Social Choice Simulations:</strong> Understanding collective decision-making</li>
</ul>
<h2 className="text-2xl font-semibold text-foreground mt-8 mb-4">Our Approach</h2>
<p className="text-muted-foreground mb-6">
Each interactive is designed with careful attention to pedagogy, ensuring that users not only learn the concepts but also develop intuition and deeper understanding. We focus on creating experiences that are both educational and enjoyable, making learning a rewarding journey rather than a chore.
</p>
<h2 className="text-2xl font-semibold text-foreground mt-8 mb-4">For Educators</h2>
<p className="text-muted-foreground mb-6">
Our platform serves as a valuable supplement to classroom instruction, providing tools that can be used to illustrate concepts, engage students, and provide hands-on practice. Many of our interactives are designed to work well in both individual and group learning environments.
</p>
<h2 className="text-2xl font-semibold text-foreground mt-8 mb-4">For Students</h2>
<p className="text-muted-foreground mb-6">
Whether you're studying computer science, mathematics, or related fields, our interactives provide a unique way to explore and understand complex topics. Use them to supplement your coursework, prepare for exams, or simply satisfy your curiosity about how things work.
</p>
<div className="bg-muted p-6 rounded-lg mt-8">
<p className="text-sm text-muted-foreground italic">
Interactives is an ongoing project, with new content and features being added regularly. We welcome feedback and suggestions for new topics or improvements to existing interactives.
</p>
</div>
</div>
</div>
</div>
</Layout>
);
};
export default About;

View file

@ -23,14 +23,14 @@ const Index = () => {
<div className="flex justify-center gap-4 mb-16"> <div className="flex justify-center gap-4 mb-16">
<Button asChild size="lg" className="bg-accent hover:bg-accent/90"> <Button asChild size="lg" className="bg-accent hover:bg-accent/90">
<Link to="/themes" className="inline-flex items-center"> <Link to="/index" className="inline-flex items-center">
Explore Themes Browse All Interactives
<ArrowRight className="ml-2 w-4 h-4" /> <ArrowRight className="ml-2 w-4 h-4" />
</Link> </Link>
</Button> </Button>
<Button asChild variant="outline" size="lg"> <Button asChild variant="outline" size="lg">
<Link to="/puzzles"> <Link to="/themes">
Try Puzzles Explore Themes
</Link> </Link>
</Button> </Button>
</div> </div>

View file

@ -6,7 +6,10 @@ import {
Database, Database,
Gamepad2, Gamepad2,
CreditCard, CreditCard,
Calculator Calculator,
Puzzle,
Sparkles,
Trophy
} from "lucide-react"; } from "lucide-react";
import Layout from "@/components/Layout"; import Layout from "@/components/Layout";
import ThemeCard from "@/components/ThemeCard"; import ThemeCard from "@/components/ThemeCard";
@ -48,6 +51,24 @@ const Themes = () => {
description: "Discover mathematical principles and probability theory through card games and interactive demonstrations.", description: "Discover mathematical principles and probability theory through card games and interactive demonstrations.",
path: "/themes/cards-math", path: "/themes/cards-math",
icon: <CreditCard className="w-6 h-6" /> icon: <CreditCard className="w-6 h-6" />
},
{
title: "Puzzles",
description: "Get ready for an engaging collection of educational puzzles designed to challenge your problem-solving skills and reinforce key concepts across multiple disciplines.",
path: "/themes/puzzles",
icon: <Puzzle className="w-6 h-6" />
},
{
title: "Miscellany",
description: "Discover unique educational experiments, creative tools, and innovative interactive content that doesn't fit into traditional categories but sparks curiosity and enhances learning.",
path: "/themes/miscellany",
icon: <Sparkles className="w-6 h-6" />
},
{
title: "Contest Problems",
description: "Practice with problems from programming competitions, mathematical olympiads, and algorithmic challenges with interactive solutions and explanations.",
path: "/themes/contest-problems",
icon: <Trophy className="w-6 h-6" />
} }
]; ];

View file

@ -0,0 +1,19 @@
import ComingSoon from "@/components/ComingSoon";
import Layout from "@/components/Layout";
const ContestProblems = () => {
return (
<Layout>
<div className="py-12 px-4">
<div className="max-w-6xl mx-auto">
<ComingSoon
title="Contest Problems"
description="Practice with problems from programming competitions, mathematical olympiads, and algorithmic challenges with interactive solutions and explanations."
/>
</div>
</div>
</Layout>
);
};
export default ContestProblems;

View file

@ -0,0 +1,19 @@
import ComingSoon from "@/components/ComingSoon";
import Layout from "@/components/Layout";
const Miscellany = () => {
return (
<Layout>
<div className="py-12 px-4">
<div className="max-w-6xl mx-auto">
<ComingSoon
title="Miscellany"
description="Discover unique educational experiments, creative tools, and innovative interactive content that doesn't fit into traditional categories but sparks curiosity and enhances learning."
/>
</div>
</div>
</Layout>
);
};
export default Miscellany;

View file

@ -0,0 +1,19 @@
import ComingSoon from "@/components/ComingSoon";
import Layout from "@/components/Layout";
const Puzzles = () => {
return (
<Layout>
<div className="py-12 px-4">
<div className="max-w-6xl mx-auto">
<ComingSoon
title="Interactive Puzzles"
description="Get ready for an engaging collection of educational puzzles designed to challenge your problem-solving skills and reinforce key concepts across multiple disciplines."
/>
</div>
</div>
</Layout>
);
};
export default Puzzles;