Standardize page wrappers, add showSocialShare prop, and add Cmd+K search

- Standardize all page wrappers to use consistent min-h-screen bg-background pattern
- Remove Layout wrapper usage from pages that had it
- Remove page-level SocialShare and headers, move into components
- Add showSocialShare prop to all interactive components for consistent control
- Add Cmd+K / Ctrl+K command palette search across all interactives
- Export allInteractives from InteractiveIndex for reuse

Co-Authored-By: Neeldhara Misra <mail@neeldhara.com>
This commit is contained in:
Devin AI 2026-02-18 20:07:30 +00:00
parent dc8300f358
commit 8f2a086df6
35 changed files with 259 additions and 357 deletions

View file

@ -3,6 +3,7 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ErrorBoundary from "@/components/ErrorBoundary";
import CommandSearch from "@/components/CommandSearch";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import InteractiveIndex from "./components/InteractiveIndex";
@ -78,6 +79,7 @@ const App = () => (
<Toaster />
<Sonner />
<BrowserRouter>
<CommandSearch />
<Routes>
<Route path="/" element={<Index />} />
<Route path="/index" element={<InteractiveIndex />} />

View file

@ -66,7 +66,11 @@ const getOddPowers = (heaps: number[]): Set<number> => {
return oddPowers;
};
const AssistedNimGame: React.FC = () => {
interface AssistedNimGameProps {
showSocialShare?: boolean;
}
const AssistedNimGame: React.FC<AssistedNimGameProps> = ({ showSocialShare = true }) => {
const [mode, setMode] = useState<Mode | null>(null);
const [showSetup, setShowSetup] = useState(false);
const [userHeapCount, setUserHeapCount] = useState(3);
@ -384,12 +388,12 @@ const AssistedNimGame: React.FC = () => {
</CardContent>
</Card>
{/* Social Share */}
<SocialShare
title="Assisted Game of Nim - XOR Strategy Visualization"
description="Learn the mathematical strategy behind Nim with visual power-of-2 breakdown!"
url={window.location.href}
/>
{showSocialShare && (
<SocialShare
title="Assisted Game of Nim - XOR Strategy Visualization"
description="Learn the mathematical strategy behind Nim with visual power-of-2 breakdown!"
/>
)}
</div>
);
};

View file

@ -0,0 +1,65 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { allInteractives } from "@/components/InteractiveIndex";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Badge } from "@/components/ui/badge";
const CommandSearch = () => {
const [open, setOpen] = useState(false);
const navigate = useNavigate();
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((prev) => !prev);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
const handleSelect = (path: string) => {
setOpen(false);
navigate(path);
};
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Search interactives by title or description..." />
<CommandList>
<CommandEmpty>No interactives found.</CommandEmpty>
<CommandGroup heading="Interactives">
{allInteractives.map((interactive) => (
<CommandItem
key={interactive.id}
value={`${interactive.title} ${interactive.description}`}
onSelect={() => handleSelect(interactive.path)}
>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span className="font-medium">{interactive.title}</span>
<Badge variant="outline" className="text-xs">
{interactive.theme}
</Badge>
</div>
<span className="text-xs text-muted-foreground line-clamp-1">
{interactive.description}
</span>
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
);
};
export default CommandSearch;

View file

@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Shield, Target, RotateCcw, Info } from "lucide-react";
import SocialShare from '@/components/SocialShare';
// Extended good rectangle: 10 columns × 12 rows (following Fig. 10)
// Border: columns 0 and 9, rows 0/1 (top) and 10/11 (bottom)
@ -349,7 +350,11 @@ const defendAttack = (
};
const EternalDominationGame: React.FC = () => {
interface EternalDominationGameProps {
showSocialShare?: boolean;
}
const EternalDominationGame: React.FC<EternalDominationGameProps> = ({ showSocialShare = true }) => {
const [guards, setGuards] = useState<Set<string>>(generateInitialGuards);
const [attackHistory, setAttackHistory] = useState<Position[]>([]);
const [message, setMessage] = useState<string>("Click any unguarded cell to attack.");
@ -679,6 +684,13 @@ const EternalDominationGame: React.FC = () => {
)}
</CardContent>
</Card>
{showSocialShare && (
<SocialShare
title="Eternal Domination on a Grid"
description="Explore the m-eternal domination problem - can you attack in a way that breaks the defense?"
/>
)}
</div>
);
};

View file

@ -4,8 +4,13 @@ import { Button } from "@/components/ui/button";
import { Slider } from "@/components/ui/slider";
import { Input } from "@/components/ui/input";
import { toast } from "sonner";
import SocialShare from '@/components/SocialShare';
const FerrersRogersRamanujan = () => {
interface FerrersRogersRamanujanProps {
showSocialShare?: boolean;
}
const FerrersRogersRamanujan = ({ showSocialShare = true }: FerrersRogersRamanujanProps) => {
const [n, setN] = useState(14);
const [partitionSliders, setPartitionSliders] = useState<number[]>([]);
const [committedPartition, setCommittedPartition] = useState<number[]>([]);
@ -330,6 +335,13 @@ const FerrersRogersRamanujan = () => {
The hook sizes form a new partition where parts differ by at least 2.
</p>
</div>
{showSocialShare && (
<SocialShare
title="Ferrers Diagram & Rogers-Ramanujan Partition"
description="Explore integer partitions through visual Ferrers diagrams and discover the elegant Rogers-Ramanujan transformation."
/>
)}
</div>
</Card>
);

View file

@ -18,7 +18,11 @@ interface GameState {
moveHistory: string[];
}
const GoldCoinGame: React.FC = () => {
interface GoldCoinGameProps {
showSocialShare?: boolean;
}
const GoldCoinGame: React.FC<GoldCoinGameProps> = ({ showSocialShare = true }) => {
const [gameState, setGameState] = useState<GameState>({
track: Array(15).fill('empty'),
currentPlayer: 0,
@ -421,11 +425,12 @@ const GoldCoinGame: React.FC = () => {
</DialogContent>
</Dialog>
<SocialShare
title="The Gold Coin Game"
description="A strategic two-player game where you move coins to acquire the gold coin!"
url={window.location.href}
/>
{showSocialShare && (
<SocialShare
title="The Gold Coin Game"
description="A strategic two-player game where you move coins to acquire the gold coin!"
/>
)}
</div>
);
};

View file

@ -18,7 +18,7 @@ interface Interactive {
}
// This would ideally be generated from the actual components, but for now we'll hardcode
const allInteractives: Interactive[] = [
export const allInteractives: Interactive[] = [
{
id: 'binary-number-game',
title: 'Binary Number Representation',

View file

@ -27,7 +27,11 @@ const heapColors = [
'#f87171', '#a78bfa', '#fbbf24', '#60a5fa', '#34d399', '#f472b6', '#f87171', '#a78bfa', '#facc15', '#38bdf8',
];
const NimGame: React.FC = () => {
interface NimGameProps {
showSocialShare?: boolean;
}
const NimGame: React.FC<NimGameProps> = ({ showSocialShare = true }) => {
const [mode, setMode] = useState<Mode | null>(null);
const [showSetup, setShowSetup] = useState(false);
const [userHeapCount, setUserHeapCount] = useState(3);
@ -275,14 +279,12 @@ const NimGame: React.FC = () => {
</CardContent>
</Card>
{/* Social Share */}
<div className="flex justify-center">
{showSocialShare && (
<SocialShare
title="Game of Nim"
description="Play the classic two-player game of Nim! Take turns removing stones from heaps. The player to take the last stone wins!"
url={`${window.location.origin}/games/nim`}
/>
</div>
)}
</div>
);
};

View file

@ -11,6 +11,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { RefreshCw, User, UserRound, Users, UserCheck } from "lucide-react";
import SocialShare from '@/components/SocialShare';
const AGENTS = ["Alice", "Bob", "Charlie", "Dana"];
const AGENT_ICONS = [User, UserRound, Users, UserCheck];
@ -23,9 +24,10 @@ const ROOM_COLORS = [
interface RentDivisionPuzzleProps {
onComplete?: (completed: boolean) => void;
showSocialShare?: boolean;
}
const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
const RentDivisionPuzzle = ({ onComplete, showSocialShare = true }: RentDivisionPuzzleProps) => {
// V-matrix: valuations[agent][room]
const [valuations, setValuations] = useState<number[][]>(() =>
Array.from({ length: 4 }, () =>
@ -469,6 +471,12 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
</p>
</CardContent>
</Card>
{showSocialShare && (
<SocialShare
title="Envy-Free Rent Division"
description="Explore fair division by assigning rooms and setting rents to achieve envy-free allocations."
/>
)}
</div>
);
};

View file

@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useToast } from "@/hooks/use-toast";
import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import SocialShare from '@/components/SocialShare';
// Rules of Inference Playground (Example 1: Resolution)
// Layout: 3 columns (Premises | Notepad lines with rule margin | Rules)
@ -196,7 +197,7 @@ function DropZone({ children, onDrop, className }: { children?: React.ReactNode;
);
}
export default function RulesOfInferencePlayground({ onComplete }: { onComplete?: (completed: boolean) => void }) {
export default function RulesOfInferencePlayground({ onComplete, showSocialShare = true }: { onComplete?: (completed: boolean) => void; showSocialShare?: boolean }) {
const { toast } = useToast();
const [exampleId, setExampleId] = useState(EXAMPLES[0].id);
const example = useMemo(() => EXAMPLES.find((e) => e.id === exampleId)!, [exampleId]);
@ -579,6 +580,13 @@ export default function RulesOfInferencePlayground({ onComplete }: { onComplete?
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{showSocialShare && (
<SocialShare
title="Rules of Inference Playground"
description="Practice applying resolution and equivalence rules to derive conclusions step by step in this interactive logic playground."
/>
)}
</div>
);
}

View file

@ -1,13 +1,12 @@
import Layout from "@/components/Layout";
import AscDescGridPuzzle from "@/components/AscDescGridPuzzle";
const AscDescGridPuzzlePage = () => {
return (
<Layout>
<div className="py-8">
<AscDescGridPuzzle />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<AscDescGridPuzzle showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,10 +1,11 @@
import React from 'react';
import AssistedNimGame from '@/components/AssistedNimGame';
const AssistedNimGamePage = () => {
return (
<div className="container mx-auto px-4 py-8">
<AssistedNimGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<AssistedNimGame showSocialShare={true} />
</div>
</div>
);
};

View file

@ -1,13 +1,12 @@
import Layout from "@/components/Layout";
import BagchalGame from "@/components/BagchalGame";
const BagchalGamePage = () => {
return (
<Layout>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<BagchalGame showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -2,9 +2,9 @@ import BalancedTernaryGame from '@/components/BalancedTernaryGame';
const BalancedTernaryGamePage = () => {
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-4xl mx-auto">
<BalancedTernaryGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<BalancedTernaryGame showSocialShare={true} />
</div>
</div>
);

View file

@ -2,9 +2,9 @@ import BinaryNumberGame from '@/components/BinaryNumberGame';
const BinaryNumberGamePage = () => {
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-4xl mx-auto">
<BinaryNumberGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<BinaryNumberGame showSocialShare={true} />
</div>
</div>
);

View file

@ -1,29 +1,13 @@
import BulgarianSolitaire from "@/components/BulgarianSolitaire";
import { useSearchParams } from "react-router-dom";
const BulgarianSolitairePage = () => {
const [searchParams] = useSearchParams();
const from = searchParams.get('from');
if (from === 'puzzles') {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8 space-y-6">
<div className="flex items-center gap-4">
<button
onClick={() => window.location.href = '/themes/puzzles'}
className="text-primary hover:text-primary/80 font-medium"
>
Back to Puzzles
</button>
</div>
<BulgarianSolitaire showSocialShare={true} />
</div>
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<BulgarianSolitaire showSocialShare={true} />
</div>
);
}
return <BulgarianSolitaire showSocialShare={true} />;
</div>
);
};
export default BulgarianSolitairePage;

View file

@ -1,13 +1,12 @@
import Layout from '@/components/Layout';
import BurnsidesLemma from '@/components/BurnsidesLemma';
const BurnsidesLemmaPage = () => {
return (
<Layout>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<BurnsidesLemma showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,16 +1,12 @@
import Layout from '@/components/Layout';
import CubeColoring from '@/components/CubeColoring';
const CubeColoringPage = () => {
return (
<Layout>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<CubeColoring
showSocialShare={true}
shareUrl={window.location.href}
/>
<CubeColoring showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,27 +1,12 @@
import Layout from '@/components/Layout';
import DominoRetilingPuzzle from '@/components/DominoRetilingPuzzle';
import { useLocation, Link } from 'react-router-dom';
import { ArrowLeft } from 'lucide-react';
const DominoRetilingPuzzlePage = () => {
const location = useLocation();
const fromPuzzles = location.search.includes('from=puzzles');
return (
<Layout>
{fromPuzzles && (
<div className="max-w-4xl mx-auto px-6 pt-4">
<Link
to="/themes/puzzles"
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<ArrowLeft className="w-4 h-4 mr-1" />
Back to Puzzles
</Link>
</div>
)}
<DominoRetilingPuzzle />
</Layout>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<DominoRetilingPuzzle showSocialShare={true} />
</div>
</div>
);
};

View file

@ -1,15 +1,12 @@
import Layout from "@/components/Layout";
import ErdosDiscrepancyPuzzle from "@/components/ErdosDiscrepancyPuzzle";
const ErdosDiscrepancyPuzzlePage = () => {
return (
<Layout>
<div className="py-8 px-4">
<div className="max-w-6xl mx-auto">
<ErdosDiscrepancyPuzzle showSocialShare={true} />
</div>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<ErdosDiscrepancyPuzzle showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,87 +1,12 @@
import Layout from "@/components/Layout";
import EternalDominationGame from "@/components/EternalDominationGame";
import SocialShare from "@/components/SocialShare";
import { Badge } from "@/components/ui/badge";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
const EternalDominationGamePage = () => {
return (
<Layout>
<div className="py-8 px-4">
<div className="max-w-4xl mx-auto space-y-6">
<div className="text-center space-y-4">
<Badge variant="outline" className="mb-2">Miscellany</Badge>
<h1 className="text-3xl font-bold">Eternal Domination on a Grid</h1>
<p className="text-muted-foreground max-w-2xl mx-auto">
Explore the m-eternal domination problem on a 12×10 grid. Guards must maintain
a dominating set while defending against arbitrary attack sequences.
</p>
</div>
<EternalDominationGame />
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="background">
<AccordionTrigger>Mathematical Background</AccordionTrigger>
<AccordionContent className="space-y-3 text-sm">
<p>
<strong>Dominating Set:</strong> A subset S of vertices in a graph G such that
every vertex not in S has a neighbor in S. Think of guards placed at certain
vertices that can "watch" their neighbors.
</p>
<p>
<strong>m-Eternal Domination:</strong> A two-player game where the defender
places guards on vertices, and the attacker repeatedly attacks unguarded vertices.
The defender responds by moving guards (all can move simultaneously, but only to
adjacent vertices) such that one guard ends on the attacked vertex and the guards
still form a dominating set.
</p>
<p>
<strong>The Challenge:</strong> The defender wins if they can maintain a dominating
set forever against any attack sequence. The m-eternal domination number γ(G) is
the minimum number of guards needed to win.
</p>
</AccordionContent>
</AccordionItem>
<AccordionItem value="strategy">
<AccordionTrigger>Defense Strategy</AccordionTrigger>
<AccordionContent className="space-y-3 text-sm">
<p>
This implementation uses the strategy from research on finite grids:
</p>
<ul className="list-disc pl-5 space-y-1">
<li><strong>Border guards:</strong> All vertices on the border (rows 0, 1, 8, 9 and
columns 0, 11) are always guarded, forming a protective cycle C.</li>
<li><strong>Interior guards:</strong> Placed in a pattern that ensures every interior
vertex is dominated by exactly one guard.</li>
<li><strong>Defense mechanism:</strong> When attacked, interior guards shift toward
the attack, potentially pushing a guard to the border. Border guards shift along
"complementary paths" to fill the resulting gaps.</li>
</ul>
</AccordionContent>
</AccordionItem>
<AccordionItem value="reference">
<AccordionTrigger>Reference</AccordionTrigger>
<AccordionContent className="text-sm">
<p>
Based on: "m-Eternal Domination and Variants on Some Classes of Finite and
Infinite Graphs" by Calamoneri et al. (CIAC 2025), which establishes bounds
and strategies for eternal domination on various grid types including square,
hexagonal, and triangular grids.
</p>
</AccordionContent>
</AccordionItem>
</Accordion>
<SocialShare
title="Eternal Domination on a Grid"
description="Explore the m-eternal domination problem - can you attack in a way that breaks the defense?"
/>
</div>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<EternalDominationGame showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,27 +1,12 @@
import Layout from "@/components/Layout";
import FerrersRogersRamanujan from "@/components/FerrersRogersRamanujan";
import SocialShare from "@/components/SocialShare";
const FerrersRogersRamanujanPage = () => {
return (
<Layout>
<div className="container mx-auto py-8 px-4 space-y-8">
<div className="space-y-2">
<h1 className="text-4xl font-bold">Ferrers Diagram & Rogers-Ramanujan Partition</h1>
<p className="text-muted-foreground text-lg">
Explore integer partitions through visual Ferrers diagrams and discover the elegant Rogers-Ramanujan transformation.
</p>
</div>
<FerrersRogersRamanujan />
<SocialShare
title="Ferrers Diagram & Rogers-Ramanujan Partition"
description="Explore integer partitions through visual Ferrers diagrams and discover the elegant Rogers-Ramanujan transformation."
url="https://lovable.dev/themes/discrete-math/counting/ferrers-rogers-ramanujan"
/>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<FerrersRogersRamanujan showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,10 +1,11 @@
import React from 'react';
import GoldCoinGame from '@/components/GoldCoinGame';
const GoldCoinGamePage = () => {
return (
<div className="container mx-auto px-4 py-8">
<GoldCoinGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<GoldCoinGame showSocialShare={true} />
</div>
</div>
);
};

View file

@ -1,29 +1,12 @@
import Layout from '@/components/Layout';
import LadybugClockPuzzle from '@/components/LadybugClockPuzzle';
import { useLocation, Link } from 'react-router-dom';
import { ArrowLeft } from 'lucide-react';
const LadybugClockPuzzlePage = () => {
const location = useLocation();
const fromPuzzles = location.search.includes('from=puzzles');
return (
<Layout>
{fromPuzzles && (
<div className="max-w-6xl mx-auto px-4 pt-4">
<Link
to="/themes/puzzles"
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<ArrowLeft className="w-4 h-4 mr-1" />
Back to Puzzles
</Link>
</div>
)}
<div className="py-8">
<LadybugClockPuzzle />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<LadybugClockPuzzle showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,10 +1,11 @@
import React from 'react';
import NimGame from '@/components/NimGame';
const NimGamePage = () => {
return (
<div className="container mx-auto px-4 py-8">
<NimGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<NimGame showSocialShare={true} />
</div>
</div>
);
};

View file

@ -1,11 +1,12 @@
import Layout from "@/components/Layout";
import ParityBitsGame from "@/components/ParityBitsGame";
const ParityBitsGamePage = () => {
return (
<Layout>
<ParityBitsGame />
</Layout>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<ParityBitsGame showSocialShare={true} />
</div>
</div>
);
};

View file

@ -1,29 +1,13 @@
import PebblePlacementGame from "@/components/PebblePlacementGame";
import { useSearchParams } from "react-router-dom";
const PebblePlacementGamePage = () => {
const [searchParams] = useSearchParams();
const from = searchParams.get('from');
if (from === 'puzzles') {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8 space-y-6">
<div className="flex items-center gap-4">
<button
onClick={() => window.location.href = '/themes/puzzles'}
className="text-primary hover:text-primary/80 font-medium"
>
Back to Puzzles
</button>
</div>
<PebblePlacementGame showSocialShare={true} />
</div>
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<PebblePlacementGame showSocialShare={true} />
</div>
);
}
return <PebblePlacementGame showSocialShare={true} />;
</div>
);
};
export default PebblePlacementGamePage;

View file

@ -1,13 +1,12 @@
import Layout from '@/components/Layout';
import PresentsPuzzle from "@/components/PresentsPuzzle";
const PresentsPuzzlePage = () => {
return (
<Layout>
<div className="max-w-6xl mx-auto px-4 py-8">
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<PresentsPuzzle showSocialShare={true} />
</div>
</Layout>
</div>
);
};

View file

@ -1,34 +1,10 @@
import { useState } from "react";
import RentDivisionPuzzle from "@/components/RentDivisionPuzzle";
import SocialShare from "@/components/SocialShare";
const RentDivisionPuzzlePage = () => {
const [isCompleted, setIsCompleted] = useState(false);
return (
<div
className={`min-h-screen transition-colors duration-500 p-6 ${
isCompleted ? "bg-green-50/50 dark:bg-green-950/20" : "bg-background"
}`}
>
<div className="max-w-6xl mx-auto space-y-6">
<div className="space-y-2">
<h1 className="text-3xl font-bold text-foreground">
Envy-Free Rent Division
</h1>
<p className="text-muted-foreground">
Explore fair division by assigning rooms and setting rents. Can you find an
allocation where no one envies another?
</p>
</div>
<RentDivisionPuzzle onComplete={setIsCompleted} />
<SocialShare
title="Envy-Free Rent Division - Interactive Fair Division"
description="Explore fair division by assigning rooms and setting rents to achieve envy-free allocations."
url="https://lovable.dev/themes/social-choice/rent-division"
/>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<RentDivisionPuzzle showSocialShare={true} />
</div>
</div>
);

View file

@ -1,31 +1,10 @@
import { useState } from "react";
import RulesOfInferencePlayground from "@/components/RulesOfInferencePlayground";
import SocialShare from "@/components/SocialShare";
const RulesOfInferencePlaygroundPage = () => {
const [isCompleted, setIsCompleted] = useState(false);
return (
<div
className={`min-h-screen transition-colors duration-500 p-6 ${
isCompleted ? 'bg-green-50/50 dark:bg-green-950/20' : 'bg-background'
}`}
>
<div className="max-w-4xl mx-auto space-y-6">
<div className="space-y-2">
<h1 className="text-3xl font-bold text-foreground">Rules of Inference Playground</h1>
<p className="text-muted-foreground">
Practice applying resolution and equivalence rules to derive conclusions step by step.
</p>
</div>
<RulesOfInferencePlayground onComplete={setIsCompleted} />
<SocialShare
title="Rules of Inference Playground - Interactive Logic Learning"
description="Practice applying resolution and equivalence rules to derive conclusions step by step in this interactive logic playground."
url="https://lovable.dev/discrete-math/foundations/rules-of-inference"
/>
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<RulesOfInferencePlayground showSocialShare={true} />
</div>
</div>
);

View file

@ -1,29 +1,13 @@
import StackingBlocks from "@/components/StackingBlocks";
import { useSearchParams } from "react-router-dom";
const StackingBlocksPage = () => {
const [searchParams] = useSearchParams();
const from = searchParams.get('from');
if (from === 'puzzles') {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8 space-y-6">
<div className="flex items-center gap-4">
<button
onClick={() => window.location.href = '/themes/puzzles'}
className="text-primary hover:text-primary/80 font-medium"
>
Back to Puzzles
</button>
</div>
<StackingBlocks showSocialShare={true} />
</div>
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<StackingBlocks showSocialShare={true} />
</div>
);
}
return <StackingBlocks showSocialShare={true} />;
</div>
);
};
export default StackingBlocksPage;

View file

@ -1,7 +1,13 @@
import SubtractionGame from '@/components/SubtractionGame';
const SubtractionGamePage = () => {
return <SubtractionGame />;
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<SubtractionGame showSocialShare={true} />
</div>
</div>
);
};
export default SubtractionGamePage;

View file

@ -2,9 +2,9 @@ import TernaryNumberGame from '@/components/TernaryNumberGame';
const TernaryNumberGamePage = () => {
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-4xl mx-auto">
<TernaryNumberGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<TernaryNumberGame showSocialShare={true} />
</div>
</div>
);

View file

@ -2,9 +2,9 @@ import ZeckendorfGame from '@/components/ZeckendorfGame';
const ZeckendorfGamePage = () => {
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-4xl mx-auto">
<ZeckendorfGame />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<ZeckendorfGame showSocialShare={true} />
</div>
</div>
);

View file

@ -2,9 +2,9 @@ import ZeckendorfSearchTrick from '@/components/ZeckendorfSearchTrick';
const ZeckendorfSearchTrickPage = () => {
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-6xl mx-auto">
<ZeckendorfSearchTrick />
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<ZeckendorfSearchTrick showSocialShare={true} />
</div>
</div>
);