"use client"; import { useState, useCallback } from "react"; import { Card } from "@/components/ui/card"; import { ClientOnly } from "@/components/ui/client-only"; import { StepControls } from "./step-controls"; import { MathDisplay } from "@/components/math/math-display"; import { CartesianGrid, type GridPoint, type GridSegment, type GridArrow } from "./cartesian-grid"; type Mode = "plot" | "shape"; interface Step { label: string; math: string; points: GridPoint[]; arrows?: GridArrow[]; segments?: GridSegment[]; highlight?: { x: number; y: number }; } // ── Plot a single point ────────────────────────────────────────────────────── function buildPlotSteps(x: number, y: number): Step[] { const steps: Step[] = [ { label: "Start at the origin (0, 0) — where the two axes cross.", math: `(0,\\ 0)`, points: [{ x: 0, y: 0, label: "start", tone: "muted" }], }, ]; // Move along x if (x === 0) { steps.push({ label: "The x-coordinate is 0, so stay on the y-axis — don't move left or right.", math: `x = 0`, points: [{ x: 0, y: 0, tone: "muted" }], }); } else { steps.push({ label: `Move ALONG the x-axis: ${Math.abs(x)} to the ${x > 0 ? "RIGHT" : "LEFT"}.`, math: `x = ${x}`, points: [{ x: 0, y: 0, tone: "muted" }], arrows: [{ from: { x: 0, y: 0 }, to: { x, y: 0 } }], }); } // Move along y if (y === 0) { steps.push({ label: "The y-coordinate is 0, so stay on the x-axis — don't move up or down.", math: `y = 0`, points: [{ x, y: 0, tone: "muted" }], arrows: x === 0 ? [] : [{ from: { x: 0, y: 0 }, to: { x, y: 0 } }], }); } else { steps.push({ label: `Now move parallel to the y-axis: ${Math.abs(y)} ${y > 0 ? "UP" : "DOWN"}.`, math: `y = ${y}`, points: [{ x, y: 0, tone: "muted" }], arrows: [ ...(x === 0 ? [] : [{ from: { x: 0, y: 0 }, to: { x, y: 0 } }]), { from: { x, y: 0 }, to: { x, y } }, ], highlight: { x, y }, }); } steps.push({ label: `Mark the point (${x}, ${y}). Remember: the x-coordinate always comes first!`, math: `(${x},\\ ${y})`, points: [{ x, y, label: `(${x}, ${y})`, tone: "solid" }], highlight: { x, y }, }); return steps; } // ── Shape reveal presets ───────────────────────────────────────────────────── interface Shape { name: string; points: { label: string; x: number; y: number }[]; } const SHAPES: Record = { mystery: { name: "Mystery Shape", points: [ { label: "A", x: -4, y: -8 }, { label: "B", x: -3, y: -9 }, { label: "C", x: -2, y: -8 }, { label: "D", x: -2, y: 0 }, { label: "E", x: -5, y: 0 }, { label: "F", x: -6, y: 4 }, { label: "G", x: -2, y: 5 }, { label: "H", x: 2, y: 4 }, { label: "I", x: 5, y: 0 }, ], }, square: { name: "Square", points: [ { label: "A", x: 2, y: 2 }, { label: "B", x: 7, y: 2 }, { label: "C", x: 7, y: 7 }, { label: "D", x: 2, y: 7 }, ], }, house: { name: "House", points: [ { label: "A", x: -5, y: -4 }, { label: "B", x: 5, y: -4 }, { label: "C", x: 5, y: 3 }, { label: "D", x: 0, y: 7 }, { label: "E", x: -5, y: 3 }, ], }, }; function buildShapeSteps(shape: Shape): Step[] { const pts = shape.points; const steps: Step[] = []; for (let i = 0; i < pts.length; i++) { const plotted = pts.slice(0, i + 1); const segments: GridSegment[] = []; for (let j = 1; j <= i; j++) { segments.push({ from: pts[j - 1], to: pts[j] }); } const p = pts[i]; steps.push({ label: i === 0 ? `Plot the first point ${p.label} at (${p.x}, ${p.y}).` : `Plot ${p.label} at (${p.x}, ${p.y}) and join it to ${pts[i - 1].label}.`, math: `${p.label} = (${p.x},\\ ${p.y})`, points: plotted.map((q) => ({ x: q.x, y: q.y, label: q.label, tone: "solid" as const })), segments, highlight: { x: p.x, y: p.y }, }); } // Closing segment const allSegments: GridSegment[] = []; for (let j = 1; j < pts.length; j++) allSegments.push({ from: pts[j - 1], to: pts[j] }); allSegments.push({ from: pts[pts.length - 1], to: pts[0] }); steps.push({ label: `Finally, join ${pts[pts.length - 1].label} back to ${pts[0].label} to close the shape. What is it?`, math: `${pts[pts.length - 1].label} \\to ${pts[0].label}`, points: pts.map((q) => ({ x: q.x, y: q.y, label: q.label, tone: "solid" as const })), segments: allSegments, }); return steps; } // ── Quick practice: click the point ────────────────────────────────────────── function randInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } function ClickPractice() { const [target, setTarget] = useState(() => ({ x: randInt(-8, 8), y: randInt(-8, 8) })); const [clicked, setClicked] = useState<{ x: number; y: number } | null>(null); const [score, setScore] = useState({ correct: 0, total: 0 }); const isCorrect = clicked && clicked.x === target.x && clicked.y === target.y; function handleClick(x: number, y: number) { if (clicked) return; const correct = x === target.x && y === target.y; setClicked({ x, y }); setScore((s) => ({ correct: s.correct + (correct ? 1 : 0), total: s.total + 1 })); } function next() { setTarget({ x: randInt(-8, 8), y: randInt(-8, 8) }); setClicked(null); } const points: GridPoint[] = []; if (clicked) { if (isCorrect) { points.push({ x: target.x, y: target.y, label: `(${target.x}, ${target.y})`, tone: "target" }); } else { points.push({ x: clicked.x, y: clicked.y, label: "you", tone: "wrong" }); points.push({ x: target.x, y: target.y, label: `(${target.x}, ${target.y})`, tone: "target" }); } } return (

Quick Practice

{score.correct}/{score.total}

Click where ({target.x}, {target.y}) belongs

{clicked && (
{isCorrect ? (

Spot on! That is ({target.x}, {target.y}).

) : (

You clicked ({clicked.x}, {clicked.y}). The target ({target.x}, {target.y}) is shown in green.

)}
)}
); } // ── Main explorer ──────────────────────────────────────────────────────────── export function PlotPointsExplorer() { const [mode, setMode] = useState("plot"); const [inputX, setInputX] = useState("4"); const [inputY, setInputY] = useState("3"); const [shapeKey, setShapeKey] = useState("mystery"); const [error, setError] = useState(""); const [steps, setSteps] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const done = steps ? currentStep >= steps.length - 1 : false; const reset = useCallback(() => { setSteps(null); setCurrentStep(0); setIsPlaying(false); }, []); function switchMode(m: Mode) { setMode(m); setError(""); reset(); } function handlePlot() { setError(""); const x = parseInt(inputX, 10); const y = parseInt(inputY, 10); if (isNaN(x) || isNaN(y)) return setError("Enter whole numbers for x and y."); if ([x, y].some((v) => v < -10 || v > 10)) return setError("Keep both coordinates between -10 and 10."); setSteps(buildPlotSteps(x, y)); setCurrentStep(0); setIsPlaying(false); } function handleShape() { setError(""); setSteps(buildShapeSteps(SHAPES[shapeKey])); setCurrentStep(0); setIsPlaying(false); } const stepForward = useCallback(() => { if (!steps || currentStep >= steps.length - 1) return; setCurrentStep((s) => s + 1); if (currentStep + 1 >= steps.length - 1) setIsPlaying(false); }, [steps, currentStep]); const stepBack = useCallback(() => { if (currentStep <= 0) return; setCurrentStep((s) => s - 1); }, [currentStep]); const togglePlay = useCallback(() => setIsPlaying((p) => !p), []); const step = steps ? steps[currentStep] : null; return (
{/* Mode toggle */}
{(["plot", "shape"] as Mode[]).map((m) => ( ))}
{/* Input card */} {mode === "plot" ? ( <>

Enter the coordinates to plot

( setInputX(e.target.value)} className="w-20 rounded-lg border-2 border-border bg-surface px-3 py-2 text-center text-lg font-bold outline-none focus:border-unit-7" aria-label="x coordinate" /> , setInputY(e.target.value)} className="w-20 rounded-lg border-2 border-border bg-surface px-3 py-2 text-center text-lg font-bold outline-none focus:border-unit-7" aria-label="y coordinate" /> )
) : ( <>

Pick a shape, then step through the points to reveal it

)} {error &&

{error}

}
{/* Display card */} {!step ? (

{mode === "plot" ? ( <>Enter coordinates above and click Plot ) : ( <>Pick a shape and click Start )}

) : ( <>

{step.label}

)}
{/* Controls */} 0} /> {/* Quick practice */} }>
); }