"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"; import { simplifyRatio } from "@/lib/math/ratios"; import { gcd } from "@/lib/math/fractions"; type RatioMode = "divide" | "simplify" | "equivalent"; interface Step { label: string; math: string; barSegments: { value: number; filled: boolean; label?: string }[]; } const COLORS = ["var(--unit-4)", "var(--unit-1)", "var(--unit-3)", "var(--hint)"]; function buildDivideSteps(total: number, parts: number[]): Step[] { const steps: Step[] = []; const sum = parts.reduce((a, b) => a + b, 0); // Step 0: Show the ratio and total steps.push({ label: `Divide ${total} in the ratio ${parts.join(" : ")}`, math: `${total} \\div (${parts.join(" : ")})`, barSegments: parts.map((p) => ({ value: p, filled: false })), }); // Step 1: Sum the parts steps.push({ label: `Add the ratio parts: ${parts.join(" + ")} = ${sum}`, math: `${parts.join(" + ")} = ${sum} \\text{ total parts}`, barSegments: parts.map((p) => ({ value: p, filled: false })), }); // Step 2: Find one part const onePart = total / sum; steps.push({ label: `Find one part: ${total} ÷ ${sum} = ${onePart}`, math: `\\text{One part} = ${total} \\div ${sum} = ${Number.isInteger(onePart) ? onePart : onePart.toFixed(2)}`, barSegments: parts.map((p) => ({ value: p, filled: false })), }); // Steps 3+: Calculate each share const shares: number[] = []; parts.forEach((p, i) => { const share = p * onePart; shares.push(share); steps.push({ label: `Part ${i + 1}: ${p} × ${Number.isInteger(onePart) ? onePart : onePart.toFixed(2)} = ${Number.isInteger(share) ? share : share.toFixed(2)}`, math: `${p} \\times ${Number.isInteger(onePart) ? onePart : onePart.toFixed(2)} = ${Number.isInteger(share) ? share : share.toFixed(2)}`, barSegments: parts.map((pp, j) => ({ value: pp, filled: j <= i, label: j <= i ? `${Number.isInteger(shares[j]) ? shares[j] : shares[j].toFixed(2)}` : undefined, })), }); }); // Final step: Show all shares const shareStrs = shares.map((s) => (Number.isInteger(s) ? s.toString() : s.toFixed(2))); steps.push({ label: `Result: ${shareStrs.join(", ")}`, math: `${total} = ${shareStrs.join(" + ")}`, barSegments: parts.map((pp, j) => ({ value: pp, filled: true, label: shareStrs[j], })), }); return steps; } function buildSimplifySteps(parts: number[]): Step[] { const steps: Step[] = []; steps.push({ label: `Simplify the ratio ${parts.join(" : ")}`, math: parts.join(" : "), barSegments: parts.map((p) => ({ value: p, filled: true })), }); // Find GCD let g = parts[0]; for (let i = 1; i < parts.length; i++) g = gcd(g, parts[i]); if (g === 1) { steps.push({ label: "The ratio is already in its simplest form (GCD = 1)", math: `\\text{GCD} = 1`, barSegments: parts.map((p) => ({ value: p, filled: true })), }); } else { steps.push({ label: `Find the GCD of all parts: ${g}`, math: `\\text{GCD}(${parts.join(", ")}) = ${g}`, barSegments: parts.map((p) => ({ value: p, filled: true })), }); const simplified = simplifyRatio(parts); steps.push({ label: `Divide each part by ${g}`, math: parts.map((p) => `${p} \\div ${g} = ${p / g}`).join(", \\quad "), barSegments: simplified.map((p) => ({ value: p, filled: true })), }); steps.push({ label: `Simplified ratio: ${simplified.join(" : ")}`, math: `${parts.join(" : ")} = ${simplified.join(" : ")}`, barSegments: simplified.map((p) => ({ value: p, filled: true })), }); } return steps; } function buildEquivalentSteps(parts: number[], multiplier: number): Step[] { const steps: Step[] = []; steps.push({ label: `Find an equivalent ratio: ${parts.join(" : ")} × ${multiplier}`, math: `(${parts.join(" : ")}) \\times ${multiplier}`, barSegments: parts.map((p) => ({ value: p, filled: true })), }); const result = parts.map((p) => p * multiplier); steps.push({ label: `Multiply each part by ${multiplier}`, math: parts.map((p) => `${p} \\times ${multiplier} = ${p * multiplier}`).join(", \\quad "), barSegments: result.map((p) => ({ value: p, filled: true })), }); steps.push({ label: `Equivalent ratio: ${result.join(" : ")}`, math: `${parts.join(" : ")} = ${result.join(" : ")}`, barSegments: result.map((p) => ({ value: p, filled: true })), }); return steps; } function RatioBar({ segments, }: { segments: { value: number; filled: boolean; label?: string }[]; }) { const total = segments.reduce((s, seg) => s + seg.value, 0); if (total === 0) return null; return (
{segments.map((seg, i) => (
{seg.label || seg.value}
))}
{segments.map((seg, i) => (
{seg.label && `(${seg.value} parts)`}
))}
); } export function RatioExplorer() { const [mode, setMode] = useState("divide"); const [partA, setPartA] = useState("2"); const [partB, setPartB] = useState("3"); const [partC, setPartC] = useState(""); const [total, setTotal] = useState("60"); const [multiplier, setMultiplier] = 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 getParts(): number[] { const parts = [parseInt(partA), parseInt(partB)]; if (partC.trim()) parts.push(parseInt(partC)); return parts; } function handleGo() { setError(""); const parts = getParts(); if (parts.some(isNaN) || parts.some((p) => p <= 0)) { setError("Enter valid positive ratio parts."); return; } try { let s: Step[]; if (mode === "divide") { const t = parseFloat(total); if (isNaN(t) || t <= 0) { setError("Enter a valid positive total."); return; } s = buildDivideSteps(t, parts); } else if (mode === "simplify") { s = buildSimplifySteps(parts); } else { const m = parseInt(multiplier); if (isNaN(m) || m <= 0) { setError("Enter a valid positive multiplier."); return; } s = buildEquivalentSteps(parts, m); } 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 modeLabels: Record = { divide: "Divide in Ratio", simplify: "Simplify Ratio", equivalent: "Equivalent Ratio", }; return (
{/* Mode tabs */}
{(["divide", "simplify", "equivalent"] as RatioMode[]).map((m) => ( ))}
{/* Input */}

{mode === "divide" ? "Enter ratio parts and total" : mode === "simplify" ? "Enter ratio parts" : "Enter ratio parts and multiplier"}

setPartA(e.target.value)} className="w-16 rounded-lg border-2 border-border bg-surface px-2 py-2.5 text-center text-lg font-bold outline-none focus:border-unit-4" aria-label="Part A" /> : setPartB(e.target.value)} className="w-16 rounded-lg border-2 border-border bg-surface px-2 py-2.5 text-center text-lg font-bold outline-none focus:border-unit-4" aria-label="Part B" /> : setPartC(e.target.value)} placeholder="?" className="w-16 rounded-lg border-2 border-border bg-surface px-2 py-2.5 text-center text-lg font-bold outline-none focus:border-unit-4" aria-label="Part C (optional)" /> {mode === "divide" && ( <> Total: setTotal(e.target.value)} className="w-20 rounded-lg border-2 border-border bg-surface px-2 py-2.5 text-center text-lg font-bold outline-none focus:border-unit-4" aria-label="Total" /> )} {mode === "equivalent" && ( <> × setMultiplier(e.target.value)} className="w-16 rounded-lg border-2 border-border bg-surface px-2 py-2.5 text-center text-lg font-bold outline-none focus:border-unit-4" aria-label="Multiplier" /> )}
{error &&

{error}

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

Enter values above and click Go

) : ( <>

{step.label}

)}
{/* Controls */} 0} /> {/* Result */} {!done || !steps ? (

Result will appear here when steps are complete

) : ( <>

Answer

{steps[steps.length - 1].label.replace("Result: ", "").replace("Simplified ratio: ", "").replace("Equivalent ratio: ", "")}

)}
); }