"use client"; import { useState, useCallback } from "react"; import { Card } from "@/components/ui/card"; import { StepControls } from "./step-controls"; import { gcd, lcm, simplify, toKatex } from "@/lib/math/fractions"; import { MathDisplay } from "@/components/math/math-display"; type Operation = "add" | "subtract" | "multiply" | "divide"; interface Step { label: string; math: string; barA: { filled: number; total: number }; barB: { filled: number; total: number }; barResult?: { filled: number; total: number }; } function buildSteps( n1: number, d1: number, n2: number, d2: number, op: Operation, ): Step[] { const steps: Step[] = []; // Step 0: Show the original fractions const opSymbol = { add: "+", subtract: "-", multiply: "\\times", divide: "\\div" }[op]; steps.push({ label: "Start with the two fractions", math: `${toKatex(n1, d1)} ${opSymbol} ${toKatex(n2, d2)}`, barA: { filled: n1, total: d1 }, barB: { filled: n2, total: d2 }, }); if (op === "add" || op === "subtract") { // Step 1: Find LCD const lcd = lcm(d1, d2); const mult1 = lcd / d1; const mult2 = lcd / d2; const cn1 = n1 * mult1; const cn2 = n2 * mult2; if (d1 !== d2) { steps.push({ label: `Find the LCD of ${d1} and ${d2}`, math: `\\text{LCD}(${d1}, ${d2}) = ${lcd}`, barA: { filled: n1, total: d1 }, barB: { filled: n2, total: d2 }, }); // Step 2: Convert both fractions steps.push({ label: "Convert to equivalent fractions with the LCD", math: `${toKatex(n1, d1)} = ${toKatex(cn1, lcd)} \\quad ${toKatex(n2, d2)} = ${toKatex(cn2, lcd)}`, barA: { filled: cn1, total: lcd }, barB: { filled: cn2, total: lcd }, }); } // Step 3: Perform the operation const resultNum = op === "add" ? cn1 + cn2 : cn1 - cn2; steps.push({ label: op === "add" ? "Add the numerators" : "Subtract the numerators", math: `${toKatex(cn1, lcd)} ${opSymbol} ${toKatex(cn2, lcd)} = ${toKatex(resultNum, lcd)}`, barA: { filled: cn1, total: lcd }, barB: { filled: cn2, total: lcd }, barResult: { filled: Math.abs(resultNum), total: lcd }, }); // Step 4: Simplify if needed const [sn, sd] = simplify(resultNum, lcd); if (Math.abs(sn) !== Math.abs(resultNum) || sd !== lcd) { const g = gcd(Math.abs(resultNum), lcd); steps.push({ label: `Simplify by dividing by GCD = ${g}`, math: `${toKatex(resultNum, lcd)} = ${toKatex(sn, sd)}`, barA: { filled: cn1, total: lcd }, barB: { filled: cn2, total: lcd }, barResult: { filled: Math.abs(sn), total: sd }, }); } } else if (op === "multiply") { // Step 1: Multiply numerators and denominators const rn = n1 * n2; const rd = d1 * d2; steps.push({ label: "Multiply numerators and denominators", math: `\\frac{${n1} \\times ${n2}}{${d1} \\times ${d2}} = ${toKatex(rn, rd)}`, barA: { filled: n1, total: d1 }, barB: { filled: n2, total: d2 }, barResult: { filled: rn, total: rd }, }); // Step 2: Simplify const [sn, sd] = simplify(rn, rd); if (sn !== rn || sd !== rd) { const g = gcd(Math.abs(rn), rd); steps.push({ label: `Simplify by dividing by GCD = ${g}`, math: `${toKatex(rn, rd)} = ${toKatex(sn, sd)}`, barA: { filled: n1, total: d1 }, barB: { filled: n2, total: d2 }, barResult: { filled: Math.abs(sn), total: sd }, }); } } else { // divide: invert and multiply steps.push({ label: "Invert the second fraction (reciprocal)", math: `${toKatex(n1, d1)} \\times ${toKatex(d2, n2)}`, barA: { filled: n1, total: d1 }, barB: { filled: d2, total: n2 }, }); const rn = n1 * d2; const rd = d1 * n2; steps.push({ label: "Multiply numerators and denominators", math: `\\frac{${n1} \\times ${d2}}{${d1} \\times ${n2}} = ${toKatex(rn, rd)}`, barA: { filled: n1, total: d1 }, barB: { filled: d2, total: n2 }, barResult: { filled: rn, total: rd }, }); const [sn, sd] = simplify(rn, rd); if (sn !== rn || sd !== rd) { const g = gcd(Math.abs(rn), rd); steps.push({ label: `Simplify by dividing by GCD = ${g}`, math: `${toKatex(rn, rd)} = ${toKatex(sn, sd)}`, barA: { filled: n1, total: d1 }, barB: { filled: d2, total: n2 }, barResult: { filled: Math.abs(sn), total: sd }, }); } } return steps; } function FractionBar({ filled, total, color, label, }: { filled: number; total: number; color: string; label?: string; }) { const maxSegments = Math.min(total, 24); const fillCount = Math.min(filled, maxSegments); return (
{label && {label}}
{Array.from({ length: maxSegments }, (_, i) => (
))}
{filled}/{total}
); } export function FractionOperationExplorer() { const [op, setOp] = useState("add"); const [n1, setN1] = useState("1"); const [d1, setD1] = useState("3"); const [n2, setN2] = useState("1"); const [d2, setD2] = useState("4"); 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(""); const nums = [parseInt(n1), parseInt(d1), parseInt(n2), parseInt(d2)]; if (nums.some(isNaN) || nums.some((n) => n === 0)) { setError("Enter valid non-zero numbers."); return; } if (nums[1] < 0 || nums[3] < 0) { setError("Denominators must be positive."); return; } if (nums.some((n) => Math.abs(n) > 99)) { setError("Keep numbers under 100."); return; } try { const s = buildSteps(nums[0], nums[1], nums[2], nums[3], op); 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; const opLabels: Record = { add: "Add (+)", subtract: "Subtract (−)", multiply: "Multiply (×)", divide: "Divide (÷)", }; return (
{/* Operation tabs */}
{(["add", "subtract", "multiply", "divide"] as Operation[]).map((o) => ( ))}
{/* Input Card */}

Enter two fractions

setN1(e.target.value)} className="w-14 rounded-lg border-2 border-border bg-surface px-2 py-1.5 text-center text-lg font-bold outline-none focus:border-unit-1" aria-label="Numerator 1" />
setD1(e.target.value)} className="w-14 rounded-lg border-2 border-border bg-surface px-2 py-1.5 text-center text-lg font-bold outline-none focus:border-unit-1" aria-label="Denominator 1" />
{{ add: "+", subtract: "−", multiply: "×", divide: "÷" }[op]}
setN2(e.target.value)} className="w-14 rounded-lg border-2 border-border bg-surface px-2 py-1.5 text-center text-lg font-bold outline-none focus:border-unit-1" aria-label="Numerator 2" />
setD2(e.target.value)} className="w-14 rounded-lg border-2 border-border bg-surface px-2 py-1.5 text-center text-lg font-bold outline-none focus:border-unit-1" aria-label="Denominator 2" />
{error &&

{error}

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

Enter fractions above and click Go

) : ( <>

{step.label}

{step.barResult && ( )}
)}
{/* Controls */} 0} /> {/* Result */} {!done || !steps ? (

Result will appear here when steps are complete

) : ( <>

Answer

)}
); }