diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..e9a45b4
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+.git
+.github
+.forgejo
+dist
+node_modules
+npm-debug.log*
+Dockerfile*
+README.md
diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml
new file mode 100644
index 0000000..40a10b9
--- /dev/null
+++ b/.forgejo/workflows/ci.yml
@@ -0,0 +1,41 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ forgejo.ref }}
+ cancel-in-progress: true
+
+jobs:
+ verify:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Lint
+ run: npm run lint
+
+ - name: Build
+ run: npm run build
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..ff67f4c
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM node:22-alpine AS build
+
+WORKDIR /app
+
+COPY package.json package-lock.json ./
+RUN npm ci
+
+COPY . .
+RUN npm run build
+
+FROM nginx:1.27-alpine AS runtime
+
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+COPY --from=build /app/dist /usr/share/nginx/html
+
+EXPOSE 80
+
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD wget -q --spider http://127.0.0.1/ || exit 1
diff --git a/README.md b/README.md
index e0eb78a..672b6b0 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,12 @@ This project is built with:
Simply open [Lovable](https://lovable.dev/projects/5ee9d1e7-af25-47e9-ab4c-bdbca4554e32) and click on Share -> Publish.
+### Forgejo and Dokploy
+
+Pushes and pull requests targeting `main` are verified by the Forgejo Actions workflow in `.forgejo/workflows/ci.yml`. The production container is built from `Dockerfile` and serves the Vite output with NGINX, including React Router fallback handling.
+
+Dokploy is connected to the `Websites/interactives` Forgejo repository with autodeploy enabled. A successful push to `main` triggers a fresh Dockerfile build and replaces the running service only after the image has built successfully.
+
## Can I connect a custom domain to my Lovable project?
Yes, you can!
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..aca9d71
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,22 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location = /index.html {
+ add_header Cache-Control "no-cache";
+ }
+
+ location ~* \.(?:css|js|mjs|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2)$ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ try_files $uri =404;
+ }
+}
diff --git a/src/App.tsx b/src/App.tsx
index 55ab15d..f94e757 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -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";
@@ -67,6 +68,7 @@ import BagchalGamePage from './pages/BagchalGamePage';
import AscDescGridPuzzlePage from './pages/AscDescGridPuzzlePage';
import EternalDominationGamePage from './pages/EternalDominationGamePage';
import LadybugClockPuzzlePage from './pages/LadybugClockPuzzlePage';
+import ErdosDiscrepancyPuzzlePage from './pages/ErdosDiscrepancyPuzzlePage';
const queryClient = new QueryClient();
@@ -77,6 +79,7 @@ const App = () => (
+
} />
} />
@@ -144,6 +147,7 @@ const App = () => (
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/components/AssistedNimGame.tsx b/src/components/AssistedNimGame.tsx
index ee3fa28..d04028c 100644
--- a/src/components/AssistedNimGame.tsx
+++ b/src/components/AssistedNimGame.tsx
@@ -66,7 +66,11 @@ const getOddPowers = (heaps: number[]): Set => {
return oddPowers;
};
-const AssistedNimGame: React.FC = () => {
+interface AssistedNimGameProps {
+ showSocialShare?: boolean;
+}
+
+const AssistedNimGame: React.FC = ({ showSocialShare = true }) => {
const [mode, setMode] = useState(null);
const [showSetup, setShowSetup] = useState(false);
const [userHeapCount, setUserHeapCount] = useState(3);
@@ -384,14 +388,14 @@ const AssistedNimGame: React.FC = () => {
- {/* Social Share */}
-
+ {showSocialShare && (
+
+ )}
);
};
-export default AssistedNimGame;
\ No newline at end of file
+export default AssistedNimGame;
\ No newline at end of file
diff --git a/src/components/CommandSearch.tsx b/src/components/CommandSearch.tsx
new file mode 100644
index 0000000..1b5a6fb
--- /dev/null
+++ b/src/components/CommandSearch.tsx
@@ -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 (
+
+
+
+ No interactives found.
+
+ {allInteractives.map((interactive) => (
+ handleSelect(interactive.path)}
+ >
+
+
+ {interactive.title}
+
+ {interactive.theme}
+
+
+
+ {interactive.description}
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default CommandSearch;
diff --git a/src/components/ErdosDiscrepancyPuzzle.tsx b/src/components/ErdosDiscrepancyPuzzle.tsx
new file mode 100644
index 0000000..8046d40
--- /dev/null
+++ b/src/components/ErdosDiscrepancyPuzzle.tsx
@@ -0,0 +1,902 @@
+import React, { useState, useEffect, useCallback, useMemo } from 'react';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Badge } from '@/components/ui/badge';
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+import { Slider } from '@/components/ui/slider';
+import {
+ Play, RotateCcw, Info, ChevronDown, ChevronUp,
+ ArrowLeft, ArrowRight, Skull, AlertTriangle,
+ BookOpen, Zap, Eye, Grid3X3, TrendingUp, Pause,
+ Volume2, VolumeX, Target, Trophy
+} from 'lucide-react';
+import SocialShare from '@/components/SocialShare';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+
+interface ErdosDiscrepancyPuzzleProps {
+ showSocialShare?: boolean;
+}
+
+type GamePhase = 'intro' | 'writing' | 'captor-reveal' | 'walking' | 'fallen' | 'survived';
+type Direction = 1 | -1 | null;
+
+interface DiscrepancyResult {
+ d: number;
+ k: number;
+ sum: number;
+ positions: number[];
+}
+
+const ErdosDiscrepancyPuzzle: React.FC = ({ showSocialShare = false }) => {
+ // Core game state
+ const [sequence, setSequence] = useState([]);
+ const [discrepancyBound, setDiscrepancyBound] = useState(1);
+ const [gamePhase, setGamePhase] = useState('intro');
+ const [showRules, setShowRules] = useState(false);
+
+ // Walk visualization state
+ const [selectedD, setSelectedD] = useState(1);
+ const [walkPosition, setWalkPosition] = useState(0);
+ const [walkStep, setWalkStep] = useState(0);
+ const [isWalking, setIsWalking] = useState(false);
+ const [walkSpeed, setWalkSpeed] = useState(500);
+
+ // Prisoner's Walk story mode
+ const [captorChosenD, setCaptorChosenD] = useState(null);
+ const [prisonerPosition, setPrisonerPosition] = useState(0);
+ const [prisonerStep, setPrisonerStep] = useState(0);
+
+ // Heatmap state
+ const [hoveredCell, setHoveredCell] = useState<{d: number, k: number} | null>(null);
+ const [highlightedSubsequence, setHighlightedSubsequence] = useState([]);
+
+ // Best score tracking
+ const [bestLength, setBestLength] = useState(null);
+
+ // Load best score from localStorage
+ useEffect(() => {
+ const saved = localStorage.getItem(`erdos-best-length-c${discrepancyBound}`);
+ if (saved) {
+ setBestLength(parseInt(saved));
+ }
+ }, [discrepancyBound]);
+
+ // Calculate all discrepancies for current sequence
+ const allDiscrepancies = useMemo((): DiscrepancyResult[] => {
+ if (sequence.length === 0) return [];
+
+ const results: DiscrepancyResult[] = [];
+ const n = sequence.length;
+
+ for (let d = 1; d <= n; d++) {
+ let sum = 0;
+ const positions: number[] = [];
+
+ for (let k = 1; k * d <= n; k++) {
+ const pos = k * d - 1; // 0-indexed
+ if (sequence[pos] !== null) {
+ sum += sequence[pos]!;
+ positions.push(k * d);
+ results.push({
+ d,
+ k,
+ sum,
+ positions: [...positions]
+ });
+ }
+ }
+ }
+
+ return results;
+ }, [sequence]);
+
+ // Find maximum discrepancy
+ const maxDiscrepancy = useMemo(() => {
+ if (allDiscrepancies.length === 0) return { value: 0, result: null };
+
+ let maxAbs = 0;
+ let maxResult: DiscrepancyResult | null = null;
+
+ for (const result of allDiscrepancies) {
+ if (Math.abs(result.sum) > maxAbs) {
+ maxAbs = Math.abs(result.sum);
+ maxResult = result;
+ }
+ }
+
+ return { value: maxAbs, result: maxResult };
+ }, [allDiscrepancies]);
+
+ // Check if current sequence violates bound
+ const hasViolation = maxDiscrepancy.value > discrepancyBound;
+
+ // Find worst step size for captor
+ const findWorstD = useCallback((): number => {
+ let worstD = 1;
+ let worstDiscrepancy = 0;
+
+ for (const result of allDiscrepancies) {
+ if (Math.abs(result.sum) > worstDiscrepancy) {
+ worstDiscrepancy = Math.abs(result.sum);
+ worstD = result.d;
+ }
+ }
+
+ return worstD;
+ }, [allDiscrepancies]);
+
+ // Get discrepancy for specific d,k
+ const getDiscrepancy = (d: number, k: number): number | null => {
+ const result = allDiscrepancies.find(r => r.d === d && r.k === k);
+ return result ? result.sum : null;
+ };
+
+ // Toggle sequence value
+ const togglePosition = (index: number) => {
+ if (gamePhase !== 'writing') return;
+
+ setSequence(prev => {
+ const newSeq = [...prev];
+ if (newSeq[index] === null) {
+ newSeq[index] = 1;
+ } else if (newSeq[index] === 1) {
+ newSeq[index] = -1;
+ } else {
+ newSeq[index] = 1;
+ }
+ return newSeq;
+ });
+ };
+
+ // Add new position to sequence
+ const addPosition = () => {
+ if (gamePhase !== 'writing') return;
+ setSequence(prev => [...prev, null]);
+ };
+
+ // Start the game
+ const startGame = () => {
+ setSequence([null, null, null, null, null]);
+ setGamePhase('writing');
+ setWalkPosition(0);
+ setWalkStep(0);
+ setPrisonerPosition(0);
+ setPrisonerStep(0);
+ setCaptorChosenD(null);
+ setHighlightedSubsequence([]);
+ };
+
+ // Reset the game
+ const resetGame = () => {
+ setSequence([]);
+ setGamePhase('intro');
+ setWalkPosition(0);
+ setWalkStep(0);
+ setIsWalking(false);
+ setPrisonerPosition(0);
+ setPrisonerStep(0);
+ setCaptorChosenD(null);
+ setHighlightedSubsequence([]);
+ };
+
+ // Handle captor reveal (Prisoner's Walk mode)
+ const revealCaptor = () => {
+ const worstD = findWorstD();
+ setCaptorChosenD(worstD);
+ setGamePhase('captor-reveal');
+ setPrisonerPosition(0);
+ setPrisonerStep(0);
+ };
+
+ // Start prisoner walking
+ const startPrisonerWalk = () => {
+ if (captorChosenD === null) return;
+ setGamePhase('walking');
+ setPrisonerPosition(0);
+ setPrisonerStep(0);
+ };
+
+ // Prisoner walk animation
+ useEffect(() => {
+ if (gamePhase !== 'walking' || captorChosenD === null) return;
+
+ const interval = setInterval(() => {
+ setPrisonerStep(prev => {
+ const nextStep = prev + 1;
+ const posIndex = nextStep * captorChosenD - 1;
+
+ if (posIndex >= sequence.length || sequence[posIndex] === null) {
+ // Sequence ended without falling
+ setGamePhase('survived');
+ clearInterval(interval);
+
+ // Save best score
+ const validLength = sequence.filter(s => s !== null).length;
+ if (!bestLength || validLength > bestLength) {
+ setBestLength(validLength);
+ localStorage.setItem(`erdos-best-length-c${discrepancyBound}`, validLength.toString());
+ }
+
+ return prev;
+ }
+
+ const newPosition = prisonerPosition + sequence[posIndex]!;
+ setPrisonerPosition(newPosition);
+
+ if (Math.abs(newPosition) > discrepancyBound) {
+ setGamePhase('fallen');
+ clearInterval(interval);
+ return nextStep;
+ }
+
+ return nextStep;
+ });
+ }, walkSpeed);
+
+ return () => clearInterval(interval);
+ }, [gamePhase, captorChosenD, sequence, prisonerPosition, discrepancyBound, walkSpeed, bestLength]);
+
+ // Manual walk control
+ const walkSubsequence = () => {
+ if (isWalking) {
+ setIsWalking(false);
+ return;
+ }
+
+ setWalkPosition(0);
+ setWalkStep(0);
+ setIsWalking(true);
+ };
+
+ // Walk animation effect
+ useEffect(() => {
+ if (!isWalking) return;
+
+ const interval = setInterval(() => {
+ setWalkStep(prev => {
+ const nextStep = prev + 1;
+ const posIndex = nextStep * selectedD - 1;
+
+ if (posIndex >= sequence.length || sequence[posIndex] === null) {
+ setIsWalking(false);
+ return prev;
+ }
+
+ setWalkPosition(current => current + sequence[posIndex]!);
+ return nextStep;
+ });
+ }, walkSpeed);
+
+ return () => clearInterval(interval);
+ }, [isWalking, selectedD, sequence, walkSpeed]);
+
+ // Get color for heatmap cell
+ const getHeatmapColor = (discrepancy: number | null): string => {
+ if (discrepancy === null) return 'bg-muted/30';
+
+ const absVal = Math.abs(discrepancy);
+ if (absVal > discrepancyBound) {
+ return 'bg-destructive/80 text-destructive-foreground';
+ }
+ if (absVal === discrepancyBound) {
+ return 'bg-amber-500/70 text-white';
+ }
+ if (absVal === 0) {
+ return 'bg-emerald-500/50 text-emerald-900 dark:text-emerald-100';
+ }
+
+ const intensity = absVal / discrepancyBound;
+ if (intensity < 0.5) {
+ return 'bg-emerald-400/40 text-emerald-900 dark:text-emerald-100';
+ }
+ return 'bg-amber-400/50 text-amber-900 dark:text-amber-100';
+ };
+
+ // Max valid k for given d
+ const maxK = (d: number) => Math.floor(sequence.length / d);
+
+ // Highlighted positions for selected d
+ const selectedPositions = useMemo(() => {
+ const positions: number[] = [];
+ for (let k = 1; k * selectedD <= sequence.length; k++) {
+ positions.push(k * selectedD);
+ }
+ return positions;
+ }, [selectedD, sequence.length]);
+
+ // Get fill status of sequence
+ const filledCount = sequence.filter(s => s !== null).length;
+ const allFilled = filledCount === sequence.length && sequence.length > 0;
+
+ // Known optimal sequences
+ const optimalSequences: Record = {
+ 1: [1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1], // One of the optimal 11-length sequences for C=1
+ };
+
+ const loadOptimalSequence = () => {
+ const optimal = optimalSequences[discrepancyBound];
+ if (optimal) {
+ setSequence([...optimal]);
+ setGamePhase('writing');
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+ The Erdős Discrepancy Problem
+
+
+ Can you write instructions to survive the tunnel forever?
+
+
+
+ setShowRules(!showRules)}
+ >
+ {showRules ? : }
+ {showRules ? 'Hide' : 'Rules'}
+
+
+
+
+ {/* Rules Panel */}
+ {showRules && (
+
+
+
+
+
+ The Prisoner's Dilemma
+
+
+ You're trapped in a tunnel with a deadly cliff {discrepancyBound} step{discrepancyBound > 1 ? 's' : ''} to your LEFT and
+ a pit of vipers {discrepancyBound} step{discrepancyBound > 1 ? 's' : ''} to your RIGHT .
+
+
+ Write a list of instructions: each is either LEFT or RIGHT .
+
+
+ The evil captor then picks a step size d and you follow every d-th instruction.
+
+
+
+
+ The Impossible Task
+
+
+ C = 1: Maximum possible sequence length is 11
+
+
+ C = 2: Maximum possible sequence length is 1,160
+
+
+ Terence Tao proved in 2015: No infinite sequence can survive!
+
+
+
+
+
+ )}
+
+ {/* Game Phase: Intro */}
+ {gamePhase === 'intro' && (
+
+
+
+
+
The Prisoner's Walk
+
+ You wake up in a dark tunnel. There's a cliff to your left and vipers to your right.
+ Your captor demands you write a sequence of LEFT/RIGHT instructions...
+ but they get to choose which instructions you follow.
+
+
+
+
+ Discrepancy Bound (C): {discrepancyBound}
+
+
+ 1
+ setDiscrepancyBound(v[0])}
+ min={1}
+ max={3}
+ step={1}
+ className="flex-1"
+ />
+ 3
+
+
+ C=1: Max 11 steps | C=2: Max 1,160 steps | C=3: Unknown (very large)
+
+
+
+
+
+
+ Start Writing
+
+ {discrepancyBound === 1 && (
+
+
+ Load Optimal (11)
+
+ )}
+
+
+ {bestLength && (
+
+ Your best: {bestLength} steps with C={discrepancyBound}
+
+ )}
+
+
+
+ )}
+
+ {/* Main Game UI */}
+ {gamePhase !== 'intro' && (
+
+ {/* Status Bar */}
+
+
+ Length: {sequence.length}
+
+
+ Filled: {filledCount}/{sequence.length}
+
+
+ Max Discrepancy: {maxDiscrepancy.value}
+ {maxDiscrepancy.result && ` (d=${maxDiscrepancy.result.d}, k=${maxDiscrepancy.result.k})`}
+
+
+ Bound: C = {discrepancyBound}
+
+ {bestLength && (
+
+
+ Best: {bestLength}
+
+ )}
+
+
+ {/* Violation Alert */}
+ {hasViolation && gamePhase === 'writing' && (
+
+
+ Bound Exceeded!
+
+ The subsequence with d={maxDiscrepancy.result?.d}, k={maxDiscrepancy.result?.k} has
+ discrepancy {maxDiscrepancy.value}, which exceeds your bound of {discrepancyBound}.
+
+
+ )}
+
+
+
+
+
+ Sequence
+
+
+
+ Walk
+
+
+
+ Heatmap
+
+
+
+ {/* Sequence Builder Tab */}
+
+
+
+
+ Write Your Instructions
+ {gamePhase === 'writing' && (
+ Click to toggle
+ )}
+
+
+
+ {/* Sequence Grid */}
+
+
+ {sequence.map((val, idx) => {
+ const pos = idx + 1;
+ const isHighlighted = highlightedSubsequence.includes(pos) || selectedPositions.includes(pos);
+ const isViolationPart = maxDiscrepancy.result?.positions.includes(pos) && hasViolation;
+
+ return (
+
togglePosition(idx)}
+ disabled={gamePhase !== 'writing'}
+ className={`
+ relative w-10 h-14 rounded-lg border-2 flex flex-col items-center justify-center
+ transition-all duration-200
+ ${val === 1 ? 'bg-blue-500/20 border-blue-500 text-blue-600 dark:text-blue-400' : ''}
+ ${val === -1 ? 'bg-rose-500/20 border-rose-500 text-rose-600 dark:text-rose-400' : ''}
+ ${val === null ? 'bg-muted/50 border-muted-foreground/30 text-muted-foreground' : ''}
+ ${isHighlighted ? 'ring-2 ring-amber-400 ring-offset-1' : ''}
+ ${isViolationPart ? 'ring-2 ring-destructive ring-offset-1' : ''}
+ ${gamePhase === 'writing' ? 'hover:scale-105 cursor-pointer' : 'cursor-default'}
+ `}
+ >
+ {pos}
+ {val === 1 && }
+ {val === -1 && }
+ {val === null && ? }
+
+ );
+ })}
+
+ {gamePhase === 'writing' && (
+
+ +
+
+ )}
+
+
+
+ {/* Controls */}
+
+ {gamePhase === 'writing' && (
+ <>
+
+
+ Face the Captor
+
+
+
+ Reset
+
+ >
+ )}
+
+
+
+
+
+ {/* Walk Visualization Tab */}
+
+
+
+ Walk Visualization
+
+
+ {/* Step size selector */}
+
+
+
Step size (d):
+
+ {Array.from({ length: Math.min(sequence.length, 8) }, (_, i) => i + 1).map(d => (
+ {
+ setSelectedD(d);
+ setWalkPosition(0);
+ setWalkStep(0);
+ setIsWalking(false);
+ }}
+ className="w-8 h-8 p-0"
+ >
+ {d}
+
+ ))}
+
+
+
+
+ Speed:
+ setWalkSpeed(1000 - v[0])}
+ min={0}
+ max={900}
+ step={100}
+ className="w-24"
+ />
+
+
+
+ {/* Number line visualization */}
+
+ {/* Danger zones */}
+
+ CLIFF
+
+
+ VIPERS
+
+
+ {/* Safe zone markers */}
+
+ {Array.from({ length: discrepancyBound * 2 + 1 }, (_, i) => i - discrepancyBound).map(pos => (
+
+ {pos}
+
+ ))}
+
+
+ {/* Walker position */}
+
+
+
+ {/* Walk info */}
+
+ Position: {walkPosition}
+ Steps taken: {walkStep}
+ Following: positions {selectedPositions.slice(0, 5).join(', ')}{selectedPositions.length > 5 ? '...' : ''}
+
+
+ {/* Walk controls */}
+
+
+ {isWalking ? : }
+ {isWalking ? 'Pause' : 'Walk d=' + selectedD}
+
+
{
+ setWalkPosition(0);
+ setWalkStep(0);
+ setIsWalking(false);
+ }}
+ >
+
+
+
+
+
+
+
+ {/* Heatmap Tab */}
+
+ {/* This is actually handled in the walk tab above */}
+
+
+
+
+
+ Discrepancy Heatmap
+
+
+
+ Each cell shows the sum of values at positions d, 2d, 3d, ..., kd.
+ Red cells exceed the bound.
+
+
+
+
+
+
+ d \ k
+ {Array.from({ length: Math.min(12, sequence.length) }, (_, i) => i + 1).map(k => (
+ {k}
+ ))}
+
+
+
+ {Array.from({ length: Math.min(8, sequence.length) }, (_, i) => i + 1).map(d => (
+
+ {d}
+ {Array.from({ length: Math.min(12, sequence.length) }, (_, i) => i + 1).map(k => {
+ const discrepancy = getDiscrepancy(d, k);
+ const isValid = d * k <= sequence.length;
+
+ return (
+ {
+ if (isValid) {
+ setHoveredCell({ d, k });
+ const result = allDiscrepancies.find(r => r.d === d && r.k === k);
+ if (result) {
+ setHighlightedSubsequence(result.positions);
+ }
+ }
+ }}
+ onMouseLeave={() => {
+ setHoveredCell(null);
+ setHighlightedSubsequence([]);
+ }}
+ >
+ {isValid && discrepancy !== null ? discrepancy : ''}
+
+ );
+ })}
+
+ ))}
+
+
+
+
+ {hoveredCell && (
+
+ d={hoveredCell.d}, k={hoveredCell.k}: Looking at positions{' '}
+ {Array.from({ length: hoveredCell.k }, (_, i) => (i + 1) * hoveredCell.d).join(', ')}
+
+ )}
+
+
+
+
+
+ {/* Captor Reveal Phase */}
+ {gamePhase === 'captor-reveal' && captorChosenD !== null && (
+
+
+
+ The Captor Speaks...
+
+ "Interesting sequence... Let me see... I choose d = {captorChosenD} !"
+
+
+ You will follow every {captorChosenD === 1 ? '' : captorChosenD === 2 ? '2nd' : captorChosenD === 3 ? '3rd' : `${captorChosenD}th`} instruction:
+ positions {Array.from({ length: Math.floor(sequence.length / captorChosenD) }, (_, i) => (i + 1) * captorChosenD).slice(0, 8).join(', ')}...
+
+
+
+ Begin Walking
+
+
+
+ )}
+
+ {/* Walking Phase */}
+ {gamePhase === 'walking' && captorChosenD !== null && (
+
+
+
+
Walking with d = {captorChosenD}...
+
+ Step {prisonerStep} | Position: {prisonerPosition}
+
+
+
+ {/* Tunnel visualization */}
+
+
+ 🏔️
+
+
+ 🐍
+
+
+
+ 🚶
+
+
+
+
+ )}
+
+ {/* Fallen Phase */}
+ {gamePhase === 'fallen' && (
+
+
+ You Fell!
+
+ After {prisonerStep} steps with d={captorChosenD}, you ended up at position {prisonerPosition}.
+ The captor found your weakness! Your sequence lasted {sequence.length} instructions.
+
+
+ Try Again
+
+
+
+ Reset
+
+
+
+
+ )}
+
+ {/* Survived Phase (temporary - sequence ended) */}
+ {gamePhase === 'survived' && (
+
+
+ Sequence Complete!
+
+ Your {sequence.length}-instruction sequence survived d={captorChosenD}!
+ But remember: no sequence can survive forever. Try making it longer!
+
+ setGamePhase('writing')} variant="outline" size="sm">
+ Extend Sequence
+
+
+
+ New Game
+
+
+
+
+ )}
+
+ )}
+
+ {/* Acknowledgement */}
+
+
+ Credits & History
+
+ This puzzle is based on the Erdős Discrepancy Problem, posed by Paul Erdős in the 1930s
+ (with a $500 prize!). The "Prisoner's Walk" framing comes from various mathematical expositions,
+ popularized by James Grime's singingbanana video.
+
+
+ 2014: Boris Konev & Alexei Lisitsa proved C=2 has maximum length 1,160 using SAT solvers
+ (generating a 13GB proof!). 2015: Terence Tao proved that NO infinite sequence
+ can have bounded discrepancy, settling the problem completely.
+
+
+
+
+ {/* Social Share */}
+ {showSocialShare !== false && (
+
+ )}
+
+ );
+};
+
+export default ErdosDiscrepancyPuzzle;
diff --git a/src/components/EternalDominationGame.tsx b/src/components/EternalDominationGame.tsx
index 4720bf1..fd2e461 100644
--- a/src/components/EternalDominationGame.tsx
+++ b/src/components/EternalDominationGame.tsx
@@ -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 = ({ showSocialShare = true }) => {
const [guards, setGuards] = useState>(generateInitialGuards);
const [attackHistory, setAttackHistory] = useState([]);
const [message, setMessage] = useState("Click any unguarded cell to attack.");
@@ -679,6 +684,13 @@ const EternalDominationGame: React.FC = () => {
)}
+
+ {showSocialShare && (
+
+ )}
);
};
diff --git a/src/components/FerrersRogersRamanujan.tsx b/src/components/FerrersRogersRamanujan.tsx
index 1da4500..a5c95ac 100644
--- a/src/components/FerrersRogersRamanujan.tsx
+++ b/src/components/FerrersRogersRamanujan.tsx
@@ -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([]);
const [committedPartition, setCommittedPartition] = useState([]);
@@ -330,6 +335,13 @@ const FerrersRogersRamanujan = () => {
The hook sizes form a new partition where parts differ by at least 2.
+
+ {showSocialShare && (
+
+ )}
);
diff --git a/src/components/GoldCoinGame.tsx b/src/components/GoldCoinGame.tsx
index f2dd211..456dc17 100644
--- a/src/components/GoldCoinGame.tsx
+++ b/src/components/GoldCoinGame.tsx
@@ -18,7 +18,11 @@ interface GameState {
moveHistory: string[];
}
-const GoldCoinGame: React.FC = () => {
+interface GoldCoinGameProps {
+ showSocialShare?: boolean;
+}
+
+const GoldCoinGame: React.FC = ({ showSocialShare = true }) => {
const [gameState, setGameState] = useState({
track: Array(15).fill('empty'),
currentPlayer: 0,
@@ -421,13 +425,14 @@ const GoldCoinGame: React.FC = () => {
-
+ {showSocialShare && (
+
+ )}
);
};
-export default GoldCoinGame;
\ No newline at end of file
+export default GoldCoinGame;
\ No newline at end of file
diff --git a/src/components/InteractiveGallery.tsx b/src/components/InteractiveGallery.tsx
index 1086712..24a3a03 100644
--- a/src/components/InteractiveGallery.tsx
+++ b/src/components/InteractiveGallery.tsx
@@ -67,7 +67,7 @@ const InteractiveGallery = () => {
Data Structures and Algorithms
-
Interactive explorations of elementary data structures and algorithms.
+
Interactive explorations of elementary data structures and algorithms.
{/* Search bar */}
@@ -103,4 +103,4 @@ const InteractiveGallery = () => {
);
};
-export default InteractiveGallery;
\ No newline at end of file
+export default InteractiveGallery;
diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx
index a9ce43c..4223abe 100644
--- a/src/components/InteractiveIndex.tsx
+++ b/src/components/InteractiveIndex.tsx
@@ -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',
@@ -288,6 +288,15 @@ const allInteractives: Interactive[] = [
path: '/puzzles/ladybug-clock',
theme: 'Puzzles',
dateAdded: '2025-01-21'
+ },
+ {
+ id: 'erdos-discrepancy',
+ title: 'The Erdős Discrepancy Problem',
+ description: 'Can you write instructions to survive the tunnel forever? Explore this famous mathematical puzzle through the Prisoner\'s Walk game.',
+ tags: ['erdos', 'discrepancy', 'sequences', 'game-theory', 'impossibility', 'puzzle', 'number-theory'],
+ path: '/puzzles/erdos-discrepancy',
+ theme: 'Puzzles',
+ dateAdded: '2025-01-22'
}
];
@@ -314,7 +323,7 @@ const InteractiveIndex = () => {
// Filter and sort interactives
const filteredAndSortedInteractives = useMemo(() => {
- let filtered = allInteractives.filter(interactive => {
+ const 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()));
@@ -540,4 +549,4 @@ const InteractiveIndex = () => {
);
};
-export default InteractiveIndex;
\ No newline at end of file
+export default InteractiveIndex;
diff --git a/src/components/LadybugClockPuzzle.tsx b/src/components/LadybugClockPuzzle.tsx
index 4c6de70..7de133f 100644
--- a/src/components/LadybugClockPuzzle.tsx
+++ b/src/components/LadybugClockPuzzle.tsx
@@ -8,11 +8,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Play, Pause, RotateCcw, SkipForward, FastForward, Info, ExternalLink, BarChart3 } from 'lucide-react';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, Cell } from 'recharts';
import SocialShare from '@/components/SocialShare';
-
interface LadybugClockPuzzleProps {
showSocialShare?: boolean;
}
-
interface SimulationState {
position: number; // 0-11 (0 = 12 o'clock, 1 = 1 o'clock, etc.)
visited: Set
;
@@ -21,10 +19,10 @@ interface SimulationState {
isComplete: boolean;
moveCount: number;
}
-
const CLOCK_NUMBERS = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
-
-const LadybugClockPuzzle: React.FC = ({ showSocialShare = true }) => {
+const LadybugClockPuzzle: React.FC = ({
+ showSocialShare = true
+}) => {
// Simulation state
const [simulation, setSimulation] = useState({
position: 0,
@@ -34,33 +32,32 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
isComplete: false,
moveCount: 0
});
-
+
// Controls
const [isPlaying, setIsPlaying] = useState(false);
const [speed, setSpeed] = useState(50); // 1-100, maps to delay
const [numPositions, setNumPositions] = useState(12);
-
+
// Statistics
const [lastPaintedCounts, setLastPaintedCounts] = useState>({});
const [totalSimulations, setTotalSimulations] = useState(0);
const [averageMoves, setAverageMoves] = useState(0);
const [totalMoves, setTotalMoves] = useState(0);
-
+
// User prediction
const [userPrediction, setUserPrediction] = useState(null);
const [showPredictionResult, setShowPredictionResult] = useState(false);
-
+
// Refs
const animationRef = useRef(null);
const isPlayingRef = useRef(isPlaying);
-
useEffect(() => {
isPlayingRef.current = isPlaying;
}, [isPlaying]);
// Get delay from speed (inverse relationship)
const getDelay = useCallback(() => {
- return Math.max(50, 1000 - (speed * 9));
+ return Math.max(50, 1000 - speed * 9);
}, [speed]);
// Reset simulation
@@ -83,18 +80,15 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
const step = useCallback(() => {
setSimulation(prev => {
if (prev.isComplete) return prev;
-
+
// Random move: clockwise or counterclockwise
const direction = Math.random() < 0.5 ? 1 : -1;
const newPosition = ((prev.position + direction) % numPositions + numPositions) % numPositions;
-
const newVisited = new Set(prev.visited);
const wasNewlyPainted = !newVisited.has(newPosition);
newVisited.add(newPosition);
-
const isNowComplete = newVisited.size === numPositions;
const lastPainted = isNowComplete && wasNewlyPainted ? newPosition : prev.lastPainted;
-
return {
position: newPosition,
visited: newVisited,
@@ -111,13 +105,11 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
if (!isPlaying || simulation.isComplete) {
return;
}
-
const timeoutId = setTimeout(() => {
if (isPlayingRef.current && !simulation.isComplete) {
step();
}
}, getDelay());
-
return () => clearTimeout(timeoutId);
}, [isPlaying, simulation, step, getDelay]);
@@ -134,44 +126,40 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
const visited = new Set([0]);
let moveCount = 0;
let lastPainted = 0;
-
while (visited.size < numPositions) {
const direction = Math.random() < 0.5 ? 1 : -1;
position = ((position + direction) % numPositions + numPositions) % numPositions;
-
if (!visited.has(position)) {
lastPainted = position;
visited.add(position);
}
moveCount++;
}
-
- return { lastPainted, moveCount };
+ return {
+ lastPainted,
+ moveCount
+ };
}, [numPositions]);
// Run to completion
const runToCompletion = useCallback(() => {
setIsPlaying(false);
-
- let currentSim = simulation;
+ const currentSim = simulation;
let position = currentSim.position;
const visited = new Set(currentSim.visited);
let moveCount = currentSim.moveCount;
let lastPainted = currentSim.lastPainted;
const path = [...currentSim.path];
-
while (visited.size < numPositions) {
const direction = Math.random() < 0.5 ? 1 : -1;
position = ((position + direction) % numPositions + numPositions) % numPositions;
path.push(position);
-
if (!visited.has(position)) {
lastPainted = position;
visited.add(position);
}
moveCount++;
}
-
setSimulation({
position,
visited,
@@ -184,15 +172,15 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
// Run batch simulations
const runBatchSimulations = useCallback((count: number) => {
- const newCounts = { ...lastPaintedCounts };
+ const newCounts = {
+ ...lastPaintedCounts
+ };
let batchMoves = 0;
-
for (let i = 0; i < count; i++) {
const result = runInstantSimulation();
newCounts[result.lastPainted] = (newCounts[result.lastPainted] || 0) + 1;
batchMoves += result.moveCount;
}
-
setLastPaintedCounts(newCounts);
setTotalSimulations(prev => prev + count);
setTotalMoves(prev => prev + batchMoves);
@@ -225,7 +213,7 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
// Calculate clock position for a given index
const getClockPosition = (index: number, radius: number) => {
- const angle = (index / numPositions) * 2 * Math.PI - Math.PI / 2;
+ const angle = index / numPositions * 2 * Math.PI - Math.PI / 2;
return {
x: Math.cos(angle) * radius,
y: Math.sin(angle) * radius
@@ -233,10 +221,12 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
};
// Prepare chart data
- const chartData = Array.from({ length: numPositions - 1 }, (_, i) => {
+ const chartData = Array.from({
+ length: numPositions - 1
+ }, (_, i) => {
const pos = i + 1; // 1 to n-1 (0/12 can't be last)
const count = lastPaintedCounts[pos] || 0;
- const percentage = totalSimulations > 0 ? (count / totalSimulations) * 100 : 0;
+ const percentage = totalSimulations > 0 ? count / totalSimulations * 100 : 0;
return {
position: positionToNumber(pos),
count,
@@ -244,15 +234,9 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
label: `${positionToNumber(pos)}`
};
});
-
const theoreticalProbability = 100 / (numPositions - 1);
-
- const shareUrl = typeof window !== 'undefined'
- ? `${window.location.origin}/puzzles/ladybug-clock`
- : '';
-
- return (
-
+ const shareUrl = typeof window !== 'undefined' ? `${window.location.origin}/puzzles/ladybug-clock` : '';
+ return
{/* Header */}
The Ladybug Clock Puzzle
@@ -272,71 +256,34 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
{/* Hour markers and numbers */}
- {Array.from({ length: numPositions }, (_, i) => {
- const pos = getClockPosition(i, 100);
- const isPainted = simulation.visited.has(i);
- const isLast = simulation.isComplete && simulation.lastPainted === i;
- const isCurrent = simulation.position === i;
-
- return (
-
+ {Array.from({
+ length: numPositions
+ }, (_, i) => {
+ const pos = getClockPosition(i, 100);
+ const isPainted = simulation.visited.has(i);
+ const isLast = simulation.isComplete && simulation.lastPainted === i;
+ const isCurrent = simulation.position === i;
+ return
{/* Tick mark */}
-
+
{/* Number circle background */}
-
+
{/* Glow effect for newly painted or last */}
- {(isPainted || isLast) && (
-
- )}
+ {(isPainted || isLast) && }
{/* Number text */}
-
+
{positionToNumber(i)}
-
- );
- })}
+ ;
+ })}
{/* Ladybug */}
{(() => {
- const ladybugPos = getClockPosition(simulation.position, 100);
- return (
-
+ const ladybugPos = getClockPosition(simulation.position, 100);
+ return
{/* Ladybug body */}
{/* Head */}
@@ -351,9 +298,8 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
{/* Antennae */}
-
- );
- })()}
+ ;
+ })()}
{/* Center decoration */}
@@ -374,13 +320,11 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
- {simulation.isComplete && simulation.lastPainted !== null && (
-
+ {simulation.isComplete && simulation.lastPainted !== null &&
🎉 Complete! Last painted: {positionToNumber(simulation.lastPainted)}
-
- )}
+
}
@@ -394,32 +338,17 @@ const LadybugClockPuzzle: React.FC
= ({ showSocialShare
-
setIsPlaying(!isPlaying)}
- disabled={simulation.isComplete}
- variant={isPlaying ? "destructive" : "default"}
- size="sm"
- >
+ setIsPlaying(!isPlaying)} disabled={simulation.isComplete} variant={isPlaying ? "destructive" : "default"} size="sm">
{isPlaying ? : }
{isPlaying ? 'Pause' : 'Play'}
-
+
Step
-
+
Complete
@@ -432,26 +361,12 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
Animation Speed
- setSpeed(v[0])}
- min={1}
- max={100}
- step={1}
- className="w-full"
- />
+ setSpeed(v[0])} min={1} max={100} step={1} className="w-full" />
Number of Positions: {numPositions}
- setNumPositions(v[0])}
- min={4}
- max={24}
- step={2}
- className="w-full"
- />
+ setNumPositions(v[0])} min={4} max={24} step={2} className="w-full" />
@@ -466,16 +381,9 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
- {[100, 1000, 10000].map(count => (
-
runBatchSimulations(count)}
- variant="outline"
- size="sm"
- >
+ {[100, 1000, 10000].map(count => runBatchSimulations(count)} variant="outline" size="sm">
Run {count.toLocaleString()}
-
- ))}
+ )}
Clear Stats
@@ -485,18 +393,15 @@ const LadybugClockPuzzle: React.FC
= ({ showSocialShare
Total simulations: {totalSimulations.toLocaleString()}
- {totalSimulations > 0 && (
-
+ {totalSimulations > 0 &&
Average moves to complete: {averageMoves.toFixed(1)}
-
- )}
+ }
{/* User Prediction */}
- {totalSimulations === 0 && (
-
+ {totalSimulations === 0 &&
Make a Prediction! 🤔
@@ -504,33 +409,23 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
Which number do you think is most likely to be painted last?
-
- {Array.from({ length: numPositions - 1 }, (_, i) => i + 1).map(pos => (
-
setUserPrediction(pos)}
- variant={userPrediction === pos ? "default" : "outline"}
- size="sm"
- className="w-10 h-10"
- >
+
+ {Array.from({
+ length: numPositions - 1
+ }, (_, i) => i + 1).map(pos => setUserPrediction(pos)} variant={userPrediction === pos ? "default" : "outline"} size="sm" className="w-8 h-8 text-xs p-0">
{positionToNumber(pos)}
-
- ))}
+ )}
- {userPrediction !== null && (
-
+ {userPrediction !== null &&
You predicted: {positionToNumber(userPrediction)} . Run some simulations to see!
-
- )}
+ }
-
- )}
+ }
{/* Statistics Chart */}
- {totalSimulations > 0 && (
-
+ {totalSimulations > 0 &&
Statistics: Which number was painted last?
@@ -540,52 +435,42 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
-
+
- `${v.toFixed(1)}%`}
- domain={[0, Math.max(theoreticalProbability * 1.5, 15)]}
- />
- [`${value.toFixed(2)}%`, 'Probability']}
- />
-
+ `${v.toFixed(1)}%`} domain={[0, Math.max(theoreticalProbability * 1.5, 15)]} />
+ [`${value.toFixed(2)}%`, 'Probability']} />
+
- {chartData.map((entry, index) => (
- |
- ))}
+ {chartData.map((entry, index) => | )}
- {userPrediction !== null && (
-
+ {userPrediction !== null &&
Your prediction ({positionToNumber(userPrediction)}): {' '}
{((lastPaintedCounts[userPrediction] || 0) / totalSimulations * 100).toFixed(2)}%
{' '}— Theory predicts all numbers have equal probability of{' '}
{theoreticalProbability.toFixed(2)}% !
-
- )}
+
}
@@ -594,34 +479,21 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
-
- )}
+ }
{/* Path History */}
- {simulation.path.length > 1 && (
-
+ {simulation.path.length > 1 &&
Path History
- {simulation.path.map((pos, i) => (
-
+ {simulation.path.map((pos, i) =>
{positionToNumber(pos)}
-
- ))}
+ )}
-
- )}
+ }
{/* Educational Info */}
@@ -641,19 +513,14 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
The simulation ends when all numbers have been visited at least once
- The Surprising Result
-
- Intuitively, you might expect the number 6 (opposite to 12) to be the most likely to be painted last,
- since it's the farthest from the starting point. However, the probability is exactly 1/{numPositions - 1} for
- every number from 1 to {numPositions - 1}! This is because once all but one number is painted, the random walk
- will eventually reach that last number, regardless of where it is.
-
- Mathematical Connections
+
+
+
- Random Walks on Cycles: This is an example of a random walk on a cycle graph
- Cover Time: The number of moves to visit all vertices is called the "cover time"
- Gambler's Ruin: Related to the classic probability problem of a gambler with a finite amount
+
+
+
@@ -670,21 +537,12 @@ const LadybugClockPuzzle: React.FC = ({ showSocialShare
{' '}and demonstrated in{' '}
this YouTube short
-
- . It illustrates a beautiful result about random walks on cycle graphs: every position (except the start) has equal probability of being visited last.
+ .
- {showSocialShare && (
-
- )}
-
- );
+ {showSocialShare && }
+ ;
};
-
export default LadybugClockPuzzle;
diff --git a/src/components/NimGame.tsx b/src/components/NimGame.tsx
index 924a523..5fe070c 100644
--- a/src/components/NimGame.tsx
+++ b/src/components/NimGame.tsx
@@ -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 = ({ showSocialShare = true }) => {
const [mode, setMode] = useState(null);
const [showSetup, setShowSetup] = useState(false);
const [userHeapCount, setUserHeapCount] = useState(3);
@@ -275,16 +279,14 @@ const NimGame: React.FC = () => {
- {/* Social Share */}
-
+ {showSocialShare && (
-
+ )}
);
};
-export default NimGame;
\ No newline at end of file
+export default NimGame;
\ No newline at end of file
diff --git a/src/components/RentDivisionPuzzle.tsx b/src/components/RentDivisionPuzzle.tsx
index b06a5b1..51f9951 100644
--- a/src/components/RentDivisionPuzzle.tsx
+++ b/src/components/RentDivisionPuzzle.tsx
@@ -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(() =>
Array.from({ length: 4 }, () =>
@@ -469,6 +471,12 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
+ {showSocialShare && (
+
+ )}
);
};
diff --git a/src/components/RulesOfInferencePlayground.tsx b/src/components/RulesOfInferencePlayground.tsx
index 1f226f8..54d85b9 100644
--- a/src/components/RulesOfInferencePlayground.tsx
+++ b/src/components/RulesOfInferencePlayground.tsx
@@ -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?
+
+ {showSocialShare && (
+
+ )}
);
-}
\ No newline at end of file
+}
diff --git a/src/components/ZeckendorfGame.tsx b/src/components/ZeckendorfGame.tsx
index 439b0d0..1655e46 100644
--- a/src/components/ZeckendorfGame.tsx
+++ b/src/components/ZeckendorfGame.tsx
@@ -34,7 +34,7 @@ const ZeckendorfGame: React.FC = ({ showSocialShare = true
// Generate a random combination of Fibonacci numbers
const numTerms = Math.floor(Math.random() * 4) + 2; // 2-5 terms
let target = 0;
- let usedIndices = new Set();
+ const usedIndices = new Set();
for (let i = 0; i < numTerms; i++) {
let index;
@@ -235,4 +235,4 @@ const ZeckendorfGame: React.FC = ({ showSocialShare = true
);
};
-export default ZeckendorfGame;
\ No newline at end of file
+export default ZeckendorfGame;
diff --git a/src/components/guessing-game/QuestionGuessingMode.tsx b/src/components/guessing-game/QuestionGuessingMode.tsx
index d30855a..d76a11a 100644
--- a/src/components/guessing-game/QuestionGuessingMode.tsx
+++ b/src/components/guessing-game/QuestionGuessingMode.tsx
@@ -217,7 +217,7 @@ const QuestionGuessingMode: React.FC = ({
case 'odd':
baseCondition = (num % 2 !== 0);
break;
- case 'prime':
+ case 'prime': {
const isPrimeNum = (n: number): boolean => {
if (n < 2) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
@@ -227,6 +227,7 @@ const QuestionGuessingMode: React.FC = ({
};
baseCondition = isPrimeNum(num);
break;
+ }
case 'perfectSquare':
baseCondition = Math.sqrt(num) % 1 === 0;
break;
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
index 56a0979..69d63a4 100644
--- a/src/components/ui/command.tsx
+++ b/src/components/ui/command.tsx
@@ -21,7 +21,7 @@ const Command = React.forwardRef<
))
Command.displayName = CommandPrimitive.displayName
-interface CommandDialogProps extends DialogProps {}
+type CommandDialogProps = DialogProps
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
index 9f9a6dc..12c9136 100644
--- a/src/components/ui/textarea.tsx
+++ b/src/components/ui/textarea.tsx
@@ -2,8 +2,7 @@ import * as React from "react"
import { cn } from "@/lib/utils"
-export interface TextareaProps
- extends React.TextareaHTMLAttributes {}
+export type TextareaProps = React.TextareaHTMLAttributes
const Textarea = React.forwardRef(
({ className, ...props }, ref) => {
diff --git a/src/pages/AscDescGridPuzzlePage.tsx b/src/pages/AscDescGridPuzzlePage.tsx
index 7963768..da7dde4 100644
--- a/src/pages/AscDescGridPuzzlePage.tsx
+++ b/src/pages/AscDescGridPuzzlePage.tsx
@@ -1,13 +1,12 @@
-import Layout from "@/components/Layout";
import AscDescGridPuzzle from "@/components/AscDescGridPuzzle";
const AscDescGridPuzzlePage = () => {
return (
-
-
-
+
);
};
diff --git a/src/pages/AssistedNimGamePage.tsx b/src/pages/AssistedNimGamePage.tsx
index 233302a..61b9527 100644
--- a/src/pages/AssistedNimGamePage.tsx
+++ b/src/pages/AssistedNimGamePage.tsx
@@ -1,12 +1,13 @@
-import React from 'react';
import AssistedNimGame from '@/components/AssistedNimGame';
const AssistedNimGamePage = () => {
return (
-
-
+
);
};
-export default AssistedNimGamePage;
\ No newline at end of file
+export default AssistedNimGamePage;
\ No newline at end of file
diff --git a/src/pages/BagchalGamePage.tsx b/src/pages/BagchalGamePage.tsx
index 2e1ff48..3d65664 100644
--- a/src/pages/BagchalGamePage.tsx
+++ b/src/pages/BagchalGamePage.tsx
@@ -1,13 +1,12 @@
-import Layout from "@/components/Layout";
import BagchalGame from "@/components/BagchalGame";
const BagchalGamePage = () => {
return (
-
+
);
};
diff --git a/src/pages/BalancedTernaryGamePage.tsx b/src/pages/BalancedTernaryGamePage.tsx
index 61f4057..bd70437 100644
--- a/src/pages/BalancedTernaryGamePage.tsx
+++ b/src/pages/BalancedTernaryGamePage.tsx
@@ -2,12 +2,12 @@ import BalancedTernaryGame from '@/components/BalancedTernaryGame';
const BalancedTernaryGamePage = () => {
return (
-
-
-
+
);
};
-export default BalancedTernaryGamePage;
\ No newline at end of file
+export default BalancedTernaryGamePage;
diff --git a/src/pages/BinaryNumberGamePage.tsx b/src/pages/BinaryNumberGamePage.tsx
index d12b6c1..15b8a5c 100644
--- a/src/pages/BinaryNumberGamePage.tsx
+++ b/src/pages/BinaryNumberGamePage.tsx
@@ -2,12 +2,12 @@ import BinaryNumberGame from '@/components/BinaryNumberGame';
const BinaryNumberGamePage = () => {
return (
-
-
-
+
);
};
-export default BinaryNumberGamePage;
\ No newline at end of file
+export default BinaryNumberGamePage;
diff --git a/src/pages/BulgarianSolitairePage.tsx b/src/pages/BulgarianSolitairePage.tsx
index 2a9dfc6..0f6651d 100644
--- a/src/pages/BulgarianSolitairePage.tsx
+++ b/src/pages/BulgarianSolitairePage.tsx
@@ -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 (
-
-
-
- window.location.href = '/themes/puzzles'}
- className="text-primary hover:text-primary/80 font-medium"
- >
- ← Back to Puzzles
-
-
-
-
+ return (
+
+
+
- );
- }
-
- return
;
+
+ );
};
-export default BulgarianSolitairePage;
\ No newline at end of file
+export default BulgarianSolitairePage;
diff --git a/src/pages/BurnsidesLemmaPage.tsx b/src/pages/BurnsidesLemmaPage.tsx
index c3b7303..713c547 100644
--- a/src/pages/BurnsidesLemmaPage.tsx
+++ b/src/pages/BurnsidesLemmaPage.tsx
@@ -1,13 +1,12 @@
-import Layout from '@/components/Layout';
import BurnsidesLemma from '@/components/BurnsidesLemma';
const BurnsidesLemmaPage = () => {
return (
-
+
);
};
diff --git a/src/pages/CubeColoringPage.tsx b/src/pages/CubeColoringPage.tsx
index 30ddb00..1d8d941 100644
--- a/src/pages/CubeColoringPage.tsx
+++ b/src/pages/CubeColoringPage.tsx
@@ -1,16 +1,12 @@
-import Layout from '@/components/Layout';
import CubeColoring from '@/components/CubeColoring';
const CubeColoringPage = () => {
return (
-
+
);
};
diff --git a/src/pages/DominoRetilingPuzzlePage.tsx b/src/pages/DominoRetilingPuzzlePage.tsx
index e3f7af7..8170ba1 100644
--- a/src/pages/DominoRetilingPuzzlePage.tsx
+++ b/src/pages/DominoRetilingPuzzlePage.tsx
@@ -1,28 +1,13 @@
-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 (
-
- {fromPuzzles && (
-
-
-
- Back to Puzzles
-
-
- )}
-
-
+
);
};
-export default DominoRetilingPuzzlePage;
\ No newline at end of file
+export default DominoRetilingPuzzlePage;
diff --git a/src/pages/ErdosDiscrepancyPuzzlePage.tsx b/src/pages/ErdosDiscrepancyPuzzlePage.tsx
new file mode 100644
index 0000000..2578ef0
--- /dev/null
+++ b/src/pages/ErdosDiscrepancyPuzzlePage.tsx
@@ -0,0 +1,13 @@
+import ErdosDiscrepancyPuzzle from "@/components/ErdosDiscrepancyPuzzle";
+
+const ErdosDiscrepancyPuzzlePage = () => {
+ return (
+
+ );
+};
+
+export default ErdosDiscrepancyPuzzlePage;
diff --git a/src/pages/EternalDominationGamePage.tsx b/src/pages/EternalDominationGamePage.tsx
index bd81bb9..54506fe 100644
--- a/src/pages/EternalDominationGamePage.tsx
+++ b/src/pages/EternalDominationGamePage.tsx
@@ -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 (
-
-
-
-
-
Miscellany
-
Eternal Domination on a Grid
-
- Explore the m-eternal domination problem on a 12×10 grid. Guards must maintain
- a dominating set while defending against arbitrary attack sequences.
-
-
-
-
-
-
-
- Mathematical Background
-
-
- Dominating Set: 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.
-
-
- m-Eternal Domination: 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.
-
-
- The Challenge: 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.
-
-
-
-
-
- Defense Strategy
-
-
- This implementation uses the strategy from research on finite grids:
-
-
- Border guards: All vertices on the border (rows 0, 1, 8, 9 and
- columns 0, 11) are always guarded, forming a protective cycle C.
- Interior guards: Placed in a pattern that ensures every interior
- vertex is dominated by exactly one guard.
- Defense mechanism: 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.
-
-
-
-
-
- Reference
-
-
- 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.
-
-
-
-
-
-
-
+
);
};
diff --git a/src/pages/FerrersRogersRamanujanPage.tsx b/src/pages/FerrersRogersRamanujanPage.tsx
index c343f8e..35253d1 100644
--- a/src/pages/FerrersRogersRamanujanPage.tsx
+++ b/src/pages/FerrersRogersRamanujanPage.tsx
@@ -1,27 +1,12 @@
-import Layout from "@/components/Layout";
import FerrersRogersRamanujan from "@/components/FerrersRogersRamanujan";
-import SocialShare from "@/components/SocialShare";
const FerrersRogersRamanujanPage = () => {
return (
-
-
-
-
Ferrers Diagram & Rogers-Ramanujan Partition
-
- Explore integer partitions through visual Ferrers diagrams and discover the elegant Rogers-Ramanujan transformation.
-
-
-
-
-
-
+
);
};
diff --git a/src/pages/GoldCoinGamePage.tsx b/src/pages/GoldCoinGamePage.tsx
index d838ae9..0ef4b78 100644
--- a/src/pages/GoldCoinGamePage.tsx
+++ b/src/pages/GoldCoinGamePage.tsx
@@ -1,12 +1,13 @@
-import React from 'react';
import GoldCoinGame from '@/components/GoldCoinGame';
const GoldCoinGamePage = () => {
return (
-
-
+
);
};
-export default GoldCoinGamePage;
\ No newline at end of file
+export default GoldCoinGamePage;
\ No newline at end of file
diff --git a/src/pages/LadybugClockPuzzlePage.tsx b/src/pages/LadybugClockPuzzlePage.tsx
index df6554a..71bdf85 100644
--- a/src/pages/LadybugClockPuzzlePage.tsx
+++ b/src/pages/LadybugClockPuzzlePage.tsx
@@ -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 (
-
- {fromPuzzles && (
-
-
-
- Back to Puzzles
-
-
- )}
-
-
+
);
};
diff --git a/src/pages/NimGamePage.tsx b/src/pages/NimGamePage.tsx
index aa202f6..f34463a 100644
--- a/src/pages/NimGamePage.tsx
+++ b/src/pages/NimGamePage.tsx
@@ -1,12 +1,13 @@
-import React from 'react';
import NimGame from '@/components/NimGame';
const NimGamePage = () => {
return (
-
-
+
);
};
-export default NimGamePage;
\ No newline at end of file
+export default NimGamePage;
\ No newline at end of file
diff --git a/src/pages/ParityBitsGamePage.tsx b/src/pages/ParityBitsGamePage.tsx
index 6ba5a0b..58949d2 100644
--- a/src/pages/ParityBitsGamePage.tsx
+++ b/src/pages/ParityBitsGamePage.tsx
@@ -1,12 +1,13 @@
-import Layout from "@/components/Layout";
import ParityBitsGame from "@/components/ParityBitsGame";
const ParityBitsGamePage = () => {
return (
-
-
-
+
);
};
-export default ParityBitsGamePage;
\ No newline at end of file
+export default ParityBitsGamePage;
diff --git a/src/pages/PebblePlacementGamePage.tsx b/src/pages/PebblePlacementGamePage.tsx
index e6013d1..c359304 100644
--- a/src/pages/PebblePlacementGamePage.tsx
+++ b/src/pages/PebblePlacementGamePage.tsx
@@ -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 (
-
-
-
- window.location.href = '/themes/puzzles'}
- className="text-primary hover:text-primary/80 font-medium"
- >
- ← Back to Puzzles
-
-
-
-
+ return (
+
+
- );
- }
-
- return
;
+
+ );
};
-export default PebblePlacementGamePage;
\ No newline at end of file
+export default PebblePlacementGamePage;
diff --git a/src/pages/PresentsPuzzlePage.tsx b/src/pages/PresentsPuzzlePage.tsx
index cc51817..edb710a 100644
--- a/src/pages/PresentsPuzzlePage.tsx
+++ b/src/pages/PresentsPuzzlePage.tsx
@@ -1,13 +1,12 @@
-import Layout from '@/components/Layout';
import PresentsPuzzle from "@/components/PresentsPuzzle";
const PresentsPuzzlePage = () => {
return (
-
-
+
);
};
diff --git a/src/pages/RentDivisionPuzzlePage.tsx b/src/pages/RentDivisionPuzzlePage.tsx
index 63dad7f..154033f 100644
--- a/src/pages/RentDivisionPuzzlePage.tsx
+++ b/src/pages/RentDivisionPuzzlePage.tsx
@@ -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 (
-
-
-
-
- Envy-Free Rent Division
-
-
- Explore fair division by assigning rooms and setting rents. Can you find an
- allocation where no one envies another?
-
-
-
-
-
-
+
);
diff --git a/src/pages/RulesOfInferencePlaygroundPage.tsx b/src/pages/RulesOfInferencePlaygroundPage.tsx
index 8c9a2f8..34a2fd3 100644
--- a/src/pages/RulesOfInferencePlaygroundPage.tsx
+++ b/src/pages/RulesOfInferencePlaygroundPage.tsx
@@ -1,34 +1,13 @@
-import { useState } from "react";
import RulesOfInferencePlayground from "@/components/RulesOfInferencePlayground";
-import SocialShare from "@/components/SocialShare";
const RulesOfInferencePlaygroundPage = () => {
- const [isCompleted, setIsCompleted] = useState(false);
-
return (
-
-
-
-
Rules of Inference Playground
-
- Practice applying resolution and equivalence rules to derive conclusions step by step.
-
-
-
-
-
-
+
);
};
-export default RulesOfInferencePlaygroundPage;
\ No newline at end of file
+export default RulesOfInferencePlaygroundPage;
diff --git a/src/pages/StackingBlocksPage.tsx b/src/pages/StackingBlocksPage.tsx
index 7239dfc..ccb4f78 100644
--- a/src/pages/StackingBlocksPage.tsx
+++ b/src/pages/StackingBlocksPage.tsx
@@ -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 (
-
-
-
- window.location.href = '/themes/puzzles'}
- className="text-primary hover:text-primary/80 font-medium"
- >
- ← Back to Puzzles
-
-
-
-
+ return (
+
+
+
- );
- }
-
- return
;
+
+ );
};
-export default StackingBlocksPage;
\ No newline at end of file
+export default StackingBlocksPage;
diff --git a/src/pages/SubtractionGamePage.tsx b/src/pages/SubtractionGamePage.tsx
index 302ca9c..75c3d78 100644
--- a/src/pages/SubtractionGamePage.tsx
+++ b/src/pages/SubtractionGamePage.tsx
@@ -1,7 +1,13 @@
import SubtractionGame from '@/components/SubtractionGame';
const SubtractionGamePage = () => {
- return
;
+ return (
+
+ );
};
-export default SubtractionGamePage;
\ No newline at end of file
+export default SubtractionGamePage;
diff --git a/src/pages/TernaryNumberGamePage.tsx b/src/pages/TernaryNumberGamePage.tsx
index 79ba882..a85620d 100644
--- a/src/pages/TernaryNumberGamePage.tsx
+++ b/src/pages/TernaryNumberGamePage.tsx
@@ -2,12 +2,12 @@ import TernaryNumberGame from '@/components/TernaryNumberGame';
const TernaryNumberGamePage = () => {
return (
-
-
-
+
);
};
-export default TernaryNumberGamePage;
\ No newline at end of file
+export default TernaryNumberGamePage;
diff --git a/src/pages/ZeckendorfGamePage.tsx b/src/pages/ZeckendorfGamePage.tsx
index 130ecfa..c585236 100644
--- a/src/pages/ZeckendorfGamePage.tsx
+++ b/src/pages/ZeckendorfGamePage.tsx
@@ -2,12 +2,12 @@ import ZeckendorfGame from '@/components/ZeckendorfGame';
const ZeckendorfGamePage = () => {
return (
-
-
-
+
);
};
-export default ZeckendorfGamePage;
\ No newline at end of file
+export default ZeckendorfGamePage;
\ No newline at end of file
diff --git a/src/pages/ZeckendorfSearchTrickPage.tsx b/src/pages/ZeckendorfSearchTrickPage.tsx
index 59e977f..f5cc437 100644
--- a/src/pages/ZeckendorfSearchTrickPage.tsx
+++ b/src/pages/ZeckendorfSearchTrickPage.tsx
@@ -2,12 +2,12 @@ import ZeckendorfSearchTrick from '@/components/ZeckendorfSearchTrick';
const ZeckendorfSearchTrickPage = () => {
return (
-
-
-
+
);
};
-export default ZeckendorfSearchTrickPage;
\ No newline at end of file
+export default ZeckendorfSearchTrickPage;
\ No newline at end of file
diff --git a/src/pages/theme-pages/Puzzles.tsx b/src/pages/theme-pages/Puzzles.tsx
index 323270e..5e6b9b1 100644
--- a/src/pages/theme-pages/Puzzles.tsx
+++ b/src/pages/theme-pages/Puzzles.tsx
@@ -115,6 +115,16 @@ const Puzzles = () => {
difficulty: "Intermediate" as const,
duration: "5-15 min",
participants: "1 player"
+ },
+ {
+ id: "erdos-discrepancy",
+ title: "The Erdős Discrepancy Problem",
+ description: "Can you write instructions to survive the prisoner's tunnel? Explore this famous mathematical puzzle and discover why no sequence can keep you safe forever!",
+ path: "/puzzles/erdos-discrepancy",
+ tags: ["Number Theory", "Sequences", "Impossibility", "Game Theory"],
+ difficulty: "Advanced" as const,
+ duration: "10-30 min",
+ participants: "1 player"
}
];
diff --git a/src/pages/theme-pages/discrete-math/Foundations.tsx b/src/pages/theme-pages/discrete-math/Foundations.tsx
index c4eba6c..c8eec1b 100644
--- a/src/pages/theme-pages/discrete-math/Foundations.tsx
+++ b/src/pages/theme-pages/discrete-math/Foundations.tsx
@@ -3,7 +3,6 @@ import { Link } from "react-router-dom";
import { Search } from "lucide-react";
import Layout from "@/components/Layout";
import InteractiveCard from "@/components/InteractiveCard";
-import SocialShare from "@/components/SocialShare";
import BinaryNumberGame from "@/components/BinaryNumberGame";
import TernaryNumberGame from "@/components/TernaryNumberGame";
import BalancedTernaryGame from "@/components/BalancedTernaryGame";
@@ -80,12 +79,6 @@ const Foundations = () => {
-
-
;
}
@@ -117,4 +110,4 @@ const Foundations = () => {
;
};
-export default Foundations;
\ No newline at end of file
+export default Foundations;
diff --git a/tailwind.config.ts b/tailwind.config.ts
index a4b00a3..a934352 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,4 +1,5 @@
import type { Config } from "tailwindcss";
+import tailwindcssAnimate from "tailwindcss-animate";
export default {
darkMode: ["class"],
@@ -122,5 +123,5 @@ export default {
}
}
},
- plugins: [require("tailwindcss-animate")],
+ plugins: [tailwindcssAnimate],
} satisfies Config;