"use client";
import { useState, useCallback } from "react";
import { Card } from "@/components/ui/card";
import { StepControls } from "./step-controls";
import { MathDisplay } from "@/components/math/math-display";
type Tab = "multiply" | "divide" | "thermometer";
interface Step {
label: string;
math: string;
signVisual?: { signA: "+" | "-"; signB: "+" | "-"; resultSign: "+" | "-" };
thermometer?: { startTemp: number; change: number; endTemp: number };
}
function getSign(n: number): "+" | "-" {
return n >= 0 ? "+" : "-";
}
function buildMultiplySteps(a: number, b: number): Step[] {
const steps: Step[] = [];
const result = a * b;
const signA = getSign(a);
const signB = getSign(b);
const signR = getSign(result);
steps.push({
label: "Start with the multiplication",
math: `${a < 0 ? `(${a})` : a} \\times ${b < 0 ? `(${b})` : b}`,
});
// Step: Multiply the absolute values
steps.push({
label: `First, multiply the absolute values: ${Math.abs(a)} × ${Math.abs(b)}`,
math: `|${a}| \\times |${b}| = ${Math.abs(a)} \\times ${Math.abs(b)} = ${Math.abs(result)}`,
});
// Step: Determine the sign
const sameSign = (a >= 0 && b >= 0) || (a < 0 && b < 0);
if (sameSign) {
steps.push({
label: `Same signs (${signA} × ${signB}): the result is POSITIVE`,
math: `(${signA}) \\times (${signB}) = (+)`,
signVisual: { signA, signB, resultSign: "+" },
});
} else {
steps.push({
label: `Different signs (${signA} × ${signB}): the result is NEGATIVE`,
math: `(${signA}) \\times (${signB}) = (-)`,
signVisual: { signA, signB, resultSign: "-" },
});
}
// Step: Combine
steps.push({
label: "Combine the magnitude and sign",
math: `${a < 0 ? `(${a})` : a} \\times ${b < 0 ? `(${b})` : b} = ${result}`,
signVisual: { signA, signB, resultSign: signR },
});
// Story time for negative × negative
if (a < 0 && b < 0) {
steps.push({
label: "Why does negative × negative = positive? Think of it like a double-negative in English!",
math: `\\text{\"Do NOT not eat!\"} = \\text{\"Eat!\"} \\quad (-) \\times (-) = (+)`,
signVisual: { signA: "-", signB: "-", resultSign: "+" },
});
}
return steps;
}
function buildDivideSteps(a: number, b: number): Step[] {
const steps: Step[] = [];
const result = a / b;
const signA = getSign(a);
const signB = getSign(b);
const signR = getSign(result);
const isWhole = Number.isInteger(result);
const displayResult = isWhole ? result.toString() : result.toFixed(2);
steps.push({
label: "Start with the division",
math: `${a < 0 ? `(${a})` : a} \\div ${b < 0 ? `(${b})` : b}`,
});
steps.push({
label: `First, divide the absolute values: ${Math.abs(a)} ÷ ${Math.abs(b)}`,
math: `|${a}| \\div |${b}| = ${Math.abs(a)} \\div ${Math.abs(b)} = ${isWhole ? Math.abs(result) : Math.abs(result).toFixed(2)}`,
});
const sameSign = (a >= 0 && b >= 0) || (a < 0 && b < 0);
if (sameSign) {
steps.push({
label: `Same signs (${signA} ÷ ${signB}): the result is POSITIVE`,
math: `(${signA}) \\div (${signB}) = (+)`,
signVisual: { signA, signB, resultSign: "+" },
});
} else {
steps.push({
label: `Different signs (${signA} ÷ ${signB}): the result is NEGATIVE`,
math: `(${signA}) \\div (${signB}) = (-)`,
signVisual: { signA, signB, resultSign: "-" },
});
}
steps.push({
label: "Combine the magnitude and sign",
math: `${a < 0 ? `(${a})` : a} \\div ${b < 0 ? `(${b})` : b} = ${displayResult}`,
signVisual: { signA, signB, resultSign: signR },
});
return steps;
}
function buildThermometerSteps(startTemp: number, change: number): Step[] {
const steps: Step[] = [];
const endTemp = startTemp + change;
const direction = change >= 0 ? "rose" : "dropped";
const absChange = Math.abs(change);
steps.push({
label: "Read the starting temperature on the thermometer",
math: `\\text{Starting temperature: } ${startTemp}°C`,
thermometer: { startTemp, change: 0, endTemp: startTemp },
});
steps.push({
label: `The temperature ${direction} by ${absChange}°C`,
math: change >= 0
? `${startTemp}°C + ${absChange}°C`
: `${startTemp}°C - ${absChange}°C`,
thermometer: { startTemp, change: 0, endTemp: startTemp },
});
steps.push({
label: change >= 0 ? "Rising temperature means we ADD" : "Dropping temperature means we SUBTRACT",
math: `${startTemp} + (${change}) = ${endTemp}`,
thermometer: { startTemp, change, endTemp },
});
steps.push({
label: `The new temperature is ${endTemp}°C`,
math: `\\text{New temperature: } ${endTemp}°C`,
thermometer: { startTemp, change, endTemp },
});
return steps;
}
function SignRuleGrid({ visual, operation }: { visual?: Step["signVisual"]; operation: "×" | "÷" }) {
const rules = [
{ a: "+", b: "+", result: "+", example: operation === "×" ? "3 × 2 = 6" : "6 ÷ 2 = 3" },
{ a: "-", b: "-", result: "+", example: operation === "×" ? "(-3) × (-2) = 6" : "(-6) ÷ (-2) = 3" },
{ a: "+", b: "-", result: "-", example: operation === "×" ? "3 × (-2) = -6" : "6 ÷ (-2) = -3" },
{ a: "-", b: "+", result: "-", example: operation === "×" ? "(-3) × 2 = -6" : "(-6) ÷ 2 = -3" },
];
return (
{rules.map((r) => {
const isActive = visual && visual.signA === r.a && visual.signB === r.b;
return (
({r.a}) {operation} ({r.b}) = ({r.result})
{r.example}
);
})}
);
}
function Thermometer({ data }: { data?: Step["thermometer"] }) {
const minTemp = -20;
const maxTemp = 30;
const range = maxTemp - minTemp;
const startTemp = data?.startTemp ?? 0;
const endTemp = data?.endTemp ?? 0;
const startY = ((startTemp - minTemp) / range) * 100;
const endY = ((endTemp - minTemp) / range) * 100;
const fillY = ((endTemp - minTemp) / range) * 100;
const ticks = [];
for (let t = minTemp; t <= maxTemp; t += 5) {
ticks.push(t);
}
return (
{/* Thermometer */}
{/* Mercury fill */}
= 0
? `linear-gradient(to top, #dc2626, #f97316)`
: `linear-gradient(to top, #2563eb, #60a5fa)`,
}}
/>
{/* Tick marks */}
{ticks.map((t) => {
const y = ((t - minTemp) / range) * 100;
return (
);
})}
{/* Bulb */}
°C
{/* Scale labels */}
{ticks.map((t) => {
const y = ((t - minTemp) / range) * 100;
const isStart = t === startTemp;
const isEnd = data && data.change !== 0 && t === endTemp;
return (
{t}°C
{isStart && (
START
)}
{isEnd && (
END
)}
);
})}
);
}
function ThermometerPractice() {
const scenarios = [
{ city: "London", startTemp: -3, change: 7, question: "The temperature rose by 7°C. What is the new temperature?" },
{ city: "Moscow", startTemp: -12, change: -5, question: "The temperature dropped by 5°C. What is the new temperature?" },
{ city: "Kingston", startTemp: 28, change: -10, question: "The temperature dropped by 10°C. What is the new temperature?" },
{ city: "Ottawa", startTemp: -8, change: 15, question: "The temperature rose by 15°C. What is the new temperature?" },
{ city: "Reykjavik", startTemp: -1, change: -6, question: "The temperature dropped by 6°C. What is the new temperature?" },
];
const [scenarioIdx, setScenarioIdx] = useState(0);
const [userAnswer, setUserAnswer] = useState("");
const [feedback, setFeedback] = useState<"correct" | "incorrect" | null>(null);
const scenario = scenarios[scenarioIdx];
const correctAnswer = scenario.startTemp + scenario.change;
function check() {
const parsed = parseInt(userAnswer);
if (isNaN(parsed)) return;
setFeedback(parsed === correctAnswer ? "correct" : "incorrect");
}
function next() {
setScenarioIdx((i) => (i + 1) % scenarios.length);
setUserAnswer("");
setFeedback(null);
}
return (
Real-World Challenge
In {scenario.city}, the temperature is {scenario.startTemp}°C.
{scenario.question}
{
setUserAnswer(e.target.value);
setFeedback(null);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (feedback !== null) next();
else check();
}
}}
className={`w-24 rounded-lg border-2 px-3 py-2 text-center text-lg font-bold outline-none transition-colors ${
feedback === "correct"
? "border-correct bg-correct-light"
: feedback === "incorrect"
? "border-incorrect bg-incorrect-light"
: "border-border focus:border-unit-5"
}`}
placeholder="°C"
aria-label="Temperature answer"
/>
°C
{feedback === null ? (
Check
) : (
Next City →
)}
{feedback === "correct" && (
Correct! {scenario.startTemp} + ({scenario.change}) = {correctAnswer}°C
)}
{feedback === "incorrect" && (
Not quite. {scenario.startTemp} + ({scenario.change}) = {correctAnswer}°C
)}
);
}
function MultiplyDividePractice() {
const [problem, setProblem] = useState(() => generateProblem());
const [userAnswer, setUserAnswer] = useState("");
const [feedback, setFeedback] = useState<"correct" | "incorrect" | null>(null);
const [score, setScore] = useState({ correct: 0, total: 0 });
function generateProblem() {
const ops = ["×", "÷"] as const;
const op = ops[Math.floor(Math.random() * ops.length)];
if (op === "×") {
const a = Math.floor(Math.random() * 25) - 12;
const b = Math.floor(Math.random() * 25) - 12;
return { a, b, op, answer: a * b };
} else {
// Generate division with clean integer results
const b = Math.floor(Math.random() * 11) - 5;
const nonZeroB = b === 0 ? 3 : b;
const answer = Math.floor(Math.random() * 21) - 10;
const a = answer * nonZeroB;
return { a, b: nonZeroB, op, answer };
}
}
function check() {
const parsed = parseInt(userAnswer);
if (isNaN(parsed)) return;
const isCorrect = parsed === problem.answer;
setFeedback(isCorrect ? "correct" : "incorrect");
setScore((prev) => ({
correct: prev.correct + (isCorrect ? 1 : 0),
total: prev.total + 1,
}));
}
function next() {
setProblem(generateProblem());
setUserAnswer("");
setFeedback(null);
}
const displayA = problem.a < 0 ? `(${problem.a})` : `${problem.a}`;
const displayB = problem.b < 0 ? `(${problem.b})` : `${problem.b}`;
return (
Quick Practice
{score.correct}/{score.total}
{displayA} {problem.op} {displayB} =
{
setUserAnswer(e.target.value);
setFeedback(null);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (feedback !== null) next();
else check();
}
}}
className={`w-20 rounded-lg border-2 px-3 py-2 text-center text-xl font-bold outline-none transition-colors ${
feedback === "correct"
? "border-correct bg-correct-light"
: feedback === "incorrect"
? "border-incorrect bg-incorrect-light"
: "border-border focus:border-unit-5"
}`}
placeholder="?"
aria-label="Your answer"
/>
{feedback === null ? (
Check
) : (
Next →
)}
{feedback === "correct" && (
Correct!
)}
{feedback === "incorrect" && (
Not quite. The answer is {problem.answer}.
)}
);
}
export function IntegerMultiplyDivideExplorer() {
const [tab, setTab] = useState
("multiply");
const [inputA, setInputA] = useState("-3");
const [inputB, setInputB] = useState("5");
const [startTemp, setStartTemp] = useState("-3");
const [tempChange, setTempChange] = useState("7");
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;
function handleGo() {
setError("");
if (tab === "thermometer") {
const st = parseInt(startTemp);
const ch = parseInt(tempChange);
if (isNaN(st) || isNaN(ch)) {
setError("Enter valid integers.");
return;
}
if (Math.abs(st) > 20 || Math.abs(ch) > 30) {
setError("Keep temperatures reasonable (-20 to 30).");
return;
}
const s = buildThermometerSteps(st, ch);
setSteps(s);
setCurrentStep(0);
setIsPlaying(false);
return;
}
const a = parseInt(inputA);
const b = parseInt(inputB);
if (isNaN(a) || isNaN(b)) {
setError("Enter valid integers.");
return;
}
if (tab === "divide" && b === 0) {
setError("Cannot divide by zero!");
return;
}
if (Math.abs(a) > 99 || Math.abs(b) > 99) {
setError("Keep numbers under 100.");
return;
}
if (tab === "divide" && !Number.isInteger(a / b)) {
setError("Choose numbers that divide evenly for now.");
return;
}
try {
const s = tab === "multiply" ? buildMultiplySteps(a, b) : buildDivideSteps(a, b);
setSteps(s);
setCurrentStep(0);
setIsPlaying(false);
} catch {
setError("Could not compute. Check your inputs.");
}
}
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 reset = useCallback(() => {
setSteps(null);
setCurrentStep(0);
setIsPlaying(false);
}, []);
const step = steps ? steps[currentStep] : null;
return (
{/* Tab selector */}
{(["multiply", "divide", "thermometer"] as Tab[]).map((t) => (
{
setTab(t);
reset();
}}
className={`rounded-full border-2 px-4 py-2 text-sm font-semibold transition-colors ${
tab === t
? "border-unit-5 bg-unit-5 text-white"
: "border-unit-5/40 text-unit-5 hover:bg-unit-5-light"
}`}
>
{t === "multiply"
? "Multiply (×)"
: t === "divide"
? "Divide (÷)"
: "Thermometer"}
))}
{/* Input Card */}
{tab === "thermometer" ? "Enter temperature scenario" : "Enter two integers"}
{tab === "thermometer" ? (
) : (
setInputA(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-5"
aria-label="First integer"
/>
{tab === "multiply" ? "×" : "÷"}
setInputB(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-5"
aria-label="Second integer"
/>
Go →
)}
{error && {error}
}
{/* Sign Rules Grid (for multiply/divide) */}
{tab !== "thermometer" && (
)}
{/* Display Card */}
{!step ? (
Enter values above and click Go
) : (
<>
{step.label}
{step.thermometer && }
>
)}
{/* Controls */}
0}
/>
{/* Result */}
{!done || !steps ? (
Result will appear here when steps are complete
) : (
<>
Answer
>
)}
{/* Practice sections */}
{tab === "thermometer" ?
:
}
);
}