diff --git a/app/lessons/unit-1-fractions/divide/page.tsx b/app/lessons/unit-1-fractions/divide/page.tsx index 836d530..0d24fe5 100644 --- a/app/lessons/unit-1-fractions/divide/page.tsx +++ b/app/lessons/unit-1-fractions/divide/page.tsx @@ -16,7 +16,7 @@ export default function DividePage() {

Divide Fractions

- + ); } diff --git a/app/lessons/unit-1-fractions/multiply/page.tsx b/app/lessons/unit-1-fractions/multiply/page.tsx index 9e49ac2..1373226 100644 --- a/app/lessons/unit-1-fractions/multiply/page.tsx +++ b/app/lessons/unit-1-fractions/multiply/page.tsx @@ -16,7 +16,7 @@ export default function MultiplyPage() {

Multiply Fractions

- + ); } diff --git a/app/lessons/unit-1-fractions/whole-from-fractions/page.tsx b/app/lessons/unit-1-fractions/whole-from-fractions/page.tsx index 7d363b8..0910073 100644 --- a/app/lessons/unit-1-fractions/whole-from-fractions/page.tsx +++ b/app/lessons/unit-1-fractions/whole-from-fractions/page.tsx @@ -16,7 +16,7 @@ export default function WholeFromFractionsPage() {

Calculate the Whole from Fractions

- + ); } diff --git a/app/lessons/unit-3-decimal-operations/multiply-divide/page.tsx b/app/lessons/unit-3-decimal-operations/multiply-divide/page.tsx index 47b4172..31f9a98 100644 --- a/app/lessons/unit-3-decimal-operations/multiply-divide/page.tsx +++ b/app/lessons/unit-3-decimal-operations/multiply-divide/page.tsx @@ -16,7 +16,7 @@ export default function MultiplyDividePage() {

Multiply and Divide Decimals

- + ); } diff --git a/app/lessons/unit-4-ratio-proportion/simplify-ratios/page.tsx b/app/lessons/unit-4-ratio-proportion/simplify-ratios/page.tsx index bb8fc56..7166c28 100644 --- a/app/lessons/unit-4-ratio-proportion/simplify-ratios/page.tsx +++ b/app/lessons/unit-4-ratio-proportion/simplify-ratios/page.tsx @@ -16,7 +16,7 @@ export default function SimplifyRatiosPage() {

Simplify Ratios

- + ); } diff --git a/app/practice/page.tsx b/app/practice/page.tsx index 8364b14..3faa066 100644 --- a/app/practice/page.tsx +++ b/app/practice/page.tsx @@ -117,7 +117,15 @@ export default function PracticePage() { hover className="cursor-pointer" accent={topic.unitColor} + tabIndex={0} + role="button" onClick={() => setSelectedTopic(topic)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setSelectedTopic(topic); + } + }} >
{topic.label} diff --git a/components/explorers/conversion-explorer.tsx b/components/explorers/conversion-explorer.tsx index 4923a65..62bd957 100644 --- a/components/explorers/conversion-explorer.tsx +++ b/components/explorers/conversion-explorer.tsx @@ -5,6 +5,7 @@ import { Card } from "@/components/ui/card"; import { StepControls } from "./step-controls"; import { MathDisplay } from "@/components/math/math-display"; import { simplify, toKatex, gcd } from "@/lib/math/fractions"; +import { parseStrictDecimal, parseStrictInt } from "@/lib/math/validation"; type ConvertMode = "decToFrac" | "fracToDec"; @@ -161,16 +162,16 @@ export function ConversionExplorer() { try { let s: Step[]; if (mode === "decToFrac") { - const val = parseFloat(decInput); - if (isNaN(val) || val < 0 || val >= 10) { + const val = parseStrictDecimal(decInput); + if (val === null || val < 0 || val >= 10) { setError("Enter a valid decimal between 0 and 10."); return; } s = buildDecToFracSteps(decInput.trim()); } else { - const n = parseInt(numInput); - const d = parseInt(denInput); - if (isNaN(n) || isNaN(d) || d === 0) { + const n = parseStrictInt(numInput); + const d = parseStrictInt(denInput); + if (n === null || d === null || d === 0) { setError("Enter a valid fraction (denominator ≠ 0)."); return; } diff --git a/components/explorers/decimal-arithmetic-explorer.tsx b/components/explorers/decimal-arithmetic-explorer.tsx index 2efcc82..d7c6629 100644 --- a/components/explorers/decimal-arithmetic-explorer.tsx +++ b/components/explorers/decimal-arithmetic-explorer.tsx @@ -227,8 +227,12 @@ function ColumnArithmetic({ ); } -export function DecimalArithmeticExplorer() { - const [op, setOp] = useState("add"); +export function DecimalArithmeticExplorer({ + initialOperation = "add", +}: { + initialOperation?: DecOp; +} = {}) { + const [op, setOp] = useState(initialOperation); const [aInput, setAInput] = useState("12.45"); const [bInput, setBInput] = useState("3.7"); const [error, setError] = useState(""); diff --git a/components/explorers/fraction-operation-explorer.tsx b/components/explorers/fraction-operation-explorer.tsx index 4c71fad..5217ca1 100644 --- a/components/explorers/fraction-operation-explorer.tsx +++ b/components/explorers/fraction-operation-explorer.tsx @@ -174,8 +174,12 @@ function FractionBar({ ); } -export function FractionOperationExplorer() { - const [op, setOp] = useState("add"); +export function FractionOperationExplorer({ + initialOperation = "add", +}: { + initialOperation?: Operation; +} = {}) { + const [op, setOp] = useState(initialOperation); const [n1, setN1] = useState("1"); const [d1, setD1] = useState("3"); const [n2, setN2] = useState("1"); diff --git a/components/explorers/fraction-quantity-explorer.tsx b/components/explorers/fraction-quantity-explorer.tsx index b2e80b9..0391779 100644 --- a/components/explorers/fraction-quantity-explorer.tsx +++ b/components/explorers/fraction-quantity-explorer.tsx @@ -121,8 +121,12 @@ function QuantityBar({ filled, label }: { filled: number; label?: string }) { ); } -export function FractionQuantityExplorer() { - const [mode, setMode] = useState("fractionOf"); +export function FractionQuantityExplorer({ + initialMode = "fractionOf", +}: { + initialMode?: FQMode; +} = {}) { + const [mode, setMode] = useState(initialMode); const [num, setNum] = useState("3"); const [den, setDen] = useState("4"); const [quantity, setQuantity] = useState("80"); diff --git a/components/explorers/ratio-explorer.tsx b/components/explorers/ratio-explorer.tsx index e0717f2..ce13cd1 100644 --- a/components/explorers/ratio-explorer.tsx +++ b/components/explorers/ratio-explorer.tsx @@ -182,8 +182,12 @@ function RatioBar({ ); } -export function RatioExplorer() { - const [mode, setMode] = useState("divide"); +export function RatioExplorer({ + initialMode = "divide", +}: { + initialMode?: RatioMode; +} = {}) { + const [mode, setMode] = useState(initialMode); const [partA, setPartA] = useState("2"); const [partB, setPartB] = useState("3"); const [partC, setPartC] = useState(""); diff --git a/components/explorers/rounding-explorer.tsx b/components/explorers/rounding-explorer.tsx index 165b06d..437ec74 100644 --- a/components/explorers/rounding-explorer.tsx +++ b/components/explorers/rounding-explorer.tsx @@ -3,6 +3,7 @@ import { useState, useCallback } from "react"; import { Card } from "@/components/ui/card"; import { StepControls } from "./step-controls"; +import { parseStrictDecimal } from "@/lib/math/validation"; type RoundTarget = "whole" | "1dp" | "2dp" | "1sf" | "2sf" | "3sf"; @@ -384,9 +385,9 @@ export function RoundingExplorer() { function handleGo() { setError(""); const trimmed = input.trim(); - const val = parseFloat(trimmed); - if (isNaN(val)) { - setError("Enter a valid number."); + const val = parseStrictDecimal(trimmed); + if (val === null) { + setError("Enter a valid number (digits and decimal point only)."); return; } if (val < 0) { diff --git a/components/explorers/standard-form-explorer.tsx b/components/explorers/standard-form-explorer.tsx index cbacb77..84c1282 100644 --- a/components/explorers/standard-form-explorer.tsx +++ b/components/explorers/standard-form-explorer.tsx @@ -337,10 +337,11 @@ export function StandardFormExplorer() {

{totalSteps === 0 ? "Number is already in standard form position — power = 0" - : `Step ${currentStep} of ${totalSteps}`} + : `Step ${currentStep + 1} of ${totalSteps}`}

{/* Digit row */} +
{display.digits.map((ch, i) => { const isLeadingZero = i < fz && fz !== -1; @@ -372,6 +373,7 @@ export function StandardFormExplorer() { }} />
+
{/* Direction label */} {dirText && ( @@ -403,7 +405,7 @@ export function StandardFormExplorer() { {/* Controls */} { - const tag = (e.target as HTMLElement).tagName; + const el = e.target as HTMLElement; + const tag = el.tagName; if (["INPUT", "TEXTAREA", "SELECT"].includes(tag)) return; + if (el.closest("button, [role='button'], a")) return; if (e.key === "ArrowRight" || e.key === " ") { e.preventDefault(); diff --git a/lib/math/validation.ts b/lib/math/validation.ts index df868f3..bf6a3c4 100644 --- a/lib/math/validation.ts +++ b/lib/math/validation.ts @@ -1,6 +1,20 @@ import { gcd } from "./fractions"; import { simplifyRatio } from "./ratios"; +/** Parse a strict decimal string (digits and optional single dot). Rejects scientific notation, trailing letters, etc. */ +export function parseStrictDecimal(input: string): number | null { + const s = input.trim(); + if (!/^\d+(\.\d+)?$/.test(s)) return null; + return parseFloat(s); +} + +/** Parse a strict integer string (digits only, no dot or letters). */ +export function parseStrictInt(input: string): number | null { + const s = input.trim(); + if (!/^\d+$/.test(s)) return null; + return parseInt(s, 10); +} + export type AnswerResult = | { correct: true; simplified: boolean } | { correct: false; message: string };