"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";
interface Column {
exponent: number;
placeValue: number;
digit: string;
contrib: number;
}
interface Step {
label: string;
math: string;
activeCol: number | null;
revealed: number; // how many columns show their contribution
}
const PLACE_NAMES = ["ones", "tens", "hundreds", "thousands", "ten thousands", "hundred thousands"];
function columnsFor(n: number): Column[] {
const str = `${n}`;
const len = str.length;
return str.split("").map((d, i) => {
const exponent = len - 1 - i;
return {
exponent,
placeValue: Math.pow(10, exponent),
digit: d,
contrib: parseInt(d, 10) * Math.pow(10, exponent),
};
});
}
function buildSteps(n: number): Step[] {
const cols = columnsFor(n);
const len = cols.length;
const steps: Step[] = [
{
label: "In the denary (base 10) system, each digit has a place value that is a power of 10.",
math: `${n}`,
activeCol: null,
revealed: 0,
},
];
cols.forEach((c, i) => {
steps.push({
label: `The ${PLACE_NAMES[c.exponent]} digit is ${c.digit}. Its place value is 10^${c.exponent} = ${c.placeValue.toLocaleString("en-US")}.`,
math: `${c.digit} \\times 10^{${c.exponent}} = ${c.contrib.toLocaleString("en-US")}`,
activeCol: i,
revealed: i + 1,
});
});
const expanded = cols.map((c) => `${c.digit} \\times 10^{${c.exponent}}`).join(" + ");
steps.push({
label: "Put it all together — this is the number in expanded form.",
math: `${n} = ${expanded}`,
activeCol: null,
revealed: len,
});
const sumParts = cols.map((c) => c.contrib.toLocaleString("en-US")).join(" + ");
steps.push({
label: "Add every part back up and you return to the original number.",
math: `${sumParts} = ${n.toLocaleString("en-US")}`,
activeCol: null,
revealed: len,
});
return steps;
}
function PlaceValueChart({ columns, step }: { columns: Column[]; step: Step }) {
return (
{columns.map((c, i) => (
|
10{c.exponent}
|
))}
{columns.map((c, i) => (
|
{c.placeValue.toLocaleString("en-US")}
|
))}
{columns.map((c, i) => (
|
{c.digit}
|
))}
{columns.map((c, i) => (
|
{i < step.revealed ? c.contrib.toLocaleString("en-US") : "_"}
|
))}
);
}
// ── Quick practice ───────────────────────────────────────────────────────────
interface PracticeQ {
prompt: string;
answer: number;
}
function randInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function makeQuestion(): PracticeQ {
if (Math.random() < 0.5) {
// value of a digit
const n = randInt(1000, 99999);
const cols = columnsFor(n);
const nonZero = cols.filter((c) => c.digit !== "0");
const pick = nonZero[Math.floor(Math.random() * nonZero.length)];
return {
prompt: `In ${n.toLocaleString("en-US")}, what is the VALUE of the digit ${pick.digit}?`,
answer: pick.contrib,
};
}
// exponent of a named column
const exp = randInt(0, 5);
return {
prompt: `Which power of 10 is the ${PLACE_NAMES[exp]} column? Enter the exponent.`,
answer: exp,
};
}
function QuickPractice() {
const [q, setQ] = useState(() => makeQuestion());
const [val, setVal] = useState("");
const [feedback, setFeedback] = useState<"correct" | "incorrect" | null>(null);
const [score, setScore] = useState({ correct: 0, total: 0 });
function check() {
const parsed = parseInt(val, 10);
if (isNaN(parsed)) return;
const correct = parsed === q.answer;
setFeedback(correct ? "correct" : "incorrect");
setScore((s) => ({ correct: s.correct + (correct ? 1 : 0), total: s.total + 1 }));
}
function next() {
setQ(makeQuestion());
setVal("");
setFeedback(null);
}
return (
Quick Practice
{score.correct}/{score.total}
{q.prompt}
{
setVal(e.target.value);
setFeedback(null);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (feedback !== null) next();
else check();
}
}}
className={`w-32 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-6"
}`}
placeholder="?"
aria-label="Your answer"
/>
{feedback === null ? (
) : (
)}
{feedback === "correct" && Correct!
}
{feedback === "incorrect" && (
Not quite — the answer is {q.answer.toLocaleString("en-US")}.
)}
);
}
// ── Main explorer ────────────────────────────────────────────────────────────
export function PlaceValueExplorer() {
const [input, setInput] = useState("983275");
const [error, setError] = useState("");
const [columns, setColumns] = useState(null);
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);
setColumns(null);
setCurrentStep(0);
setIsPlaying(false);
}, []);
function handleGo() {
setError("");
const cleaned = input.replace(/[,\s]/g, "");
const n = parseInt(cleaned, 10);
if (isNaN(n) || !/^\d+$/.test(cleaned)) return setError("Enter a whole number (digits only).");
if (n < 1 || n > 999999) return setError("Use a whole number between 1 and 999,999.");
setColumns(columnsFor(n));
setSteps(buildSteps(n));
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 (
{/* Input card */}
Enter a whole number (up to 999,999)
setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleGo();
}}
className="w-44 rounded-lg border-2 border-unit-6 bg-surface px-3 py-2 text-center font-mono text-2xl font-bold text-unit-6 outline-none focus:border-unit-6-dark"
aria-label="Number input"
/>
{error && {error}
}
{/* Display card */}
{!step || !columns ? (
Enter a number above and click Break Down
) : (
<>
{step.label}
>
)}
{/* Controls */}
0}
/>
{/* Quick practice */}
}>
);
}