website

source code for the ttmaths.uk website
Log | Files | Refs | README

commit 16752f42e1aa7bbf5938e7e3b119d273c9f89942
Author: hrryt <harry@mayesfield.uk>
Date:   Tue, 28 Jul 2026 13:32:30 +0100

Initial commit

Diffstat:
A.gitignore | 4++++
Abuild.mjs | 15+++++++++++++++
Apackage.json | 13+++++++++++++
Asrc/components/Equation.jsx | 12++++++++++++
Asrc/components/QItem.jsx | 21+++++++++++++++++++++
Asrc/components/QList.jsx | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/components/QuadraticEquationQItem.jsx | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/components/QuadraticEquationQList.jsx | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/components/QuadraticEquationQuestionSet.jsx | 14++++++++++++++
Asrc/scripts/auto-maths.js | 8++++++++
Asrc/scripts/contexts.js | 5+++++
Asrc/scripts/index.js | 2++
Asrc/scripts/react-roots.js | 12++++++++++++
Asrc/styles/index.css | 43+++++++++++++++++++++++++++++++++++++++++++
Awww/index.html | 24++++++++++++++++++++++++
15 files changed, 298 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json +www/js +www/css diff --git a/build.mjs b/build.mjs @@ -0,0 +1,15 @@ +import * as esbuild from 'esbuild' + +await esbuild.build({ + entryPoints: ['src/scripts/index.js'], + bundle: true, + minify: true, + sourcemap: true, + outdir: 'www/js', +}) + +await esbuild.build({ + entryPoints: ['src/styles/index.css'], + bundle: true, + outdir: 'www/css', +}) diff --git a/package.json b/package.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "kern-typ": "^0.2.0", + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "esbuild": "0.28.1" + }, + "allowScripts": { + "esbuild": false + } +} diff --git a/src/components/Equation.jsx b/src/components/Equation.jsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { render } from 'kern-typ'; + +export default function Equation({ equation, displayMode = false }) { + const elementRef = React.useRef(); + + React.useEffect(() => { + render(equation, elementRef.current, { displayMode: displayMode }); + }, [equation]); + + return <div className="Equation" ref={elementRef} />; +} diff --git a/src/components/QItem.jsx b/src/components/QItem.jsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import Equation from './Equation.jsx'; +import { ShowAnswerContext, QListContext } from '../scripts/contexts.js'; + +export default function QItem({ question, answer }) { + const showAnswer = React.useContext(ShowAnswerContext); + const inQList = React.useContext(QListContext); + + const item = ( + <div className="QItem"> + <div className="Question"> + <Equation equation={question} /> + </div> + <div className={"Answer" + (showAnswer ? "" : " AnswerInvisible")}> + <Equation equation={answer} /> + </div> + </div> + ); + + return inQList ? <li>{item}</li> : item; +} diff --git a/src/components/QList.jsx b/src/components/QList.jsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { QListContext, ShowAnswerContext, RefreshContext } from '../scripts/contexts.js'; + +function QuestionSet({ children }) { + const [showAnswers, setShowAnswers] = React.useState(false); + const [refresh, setRefresh] = React.useState(false); + function revealAnswers() { setShowAnswers(!showAnswers) }; + function triggerRefresh() { setRefresh(!refresh) }; + + return ( + <div className="QuestionSet"> + <div className="ButtonPanel"> + <button className="reveal-button" onClick={revealAnswers}> + {showAnswers ? "Hide" : "Show"} Answers + </button> + <button className="refresh-button" onClick={triggerRefresh}> + Refresh Questions + </button> + </div> + <ShowAnswerContext value={showAnswers}> + <RefreshContext value={refresh}> + {children} + </RefreshContext> + </ShowAnswerContext> + </div> + ); +} + +export default function QList({ children }) { + const inQList = React.useContext(QListContext); + + const list = ( + <ol className={"QList" + (inQList ? " QListInner" : " QListOuter")}> + <QListContext value="true"> + {children} + </QListContext> + </ol> + ); + + return inQList ? <li>{list}</li> : <QuestionSet>{list}</QuestionSet>; +} diff --git a/src/components/QuadraticEquationQItem.jsx b/src/components/QuadraticEquationQItem.jsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import QItem from './QItem.jsx'; + +function formatNumber(x, showPlus, showOne, showZero, suffix='') { + if (!showZero && x == 0) { return ''; } + plus = showPlus ? '+' : ''; + sign = x < 0 ? '-' : plus; + x = Math.abs(x); + showOne || x == 1 && (x = ''); + return sign + x + suffix; +} + +function formatQuadraticEquation(a, b, c) { + const fa = formatNumber(a, showPlus=false, showOne=false, showZero=false, suffix='x^2'); + const fb = formatNumber(b, showPlus=true, showOne=false, showZero=false, suffix='x'); + const fc = formatNumber(c, showPlus=true, showOne=true, showZero=false); + return `${fa} ${fb} ${fc} = 0`; +} + +function formatRoots(root1, root2) { + const froot2 = formatNumber(root2, showPlus=false, showOne=true, showZero=true); + if (root1 == root2) { return `x = ${froot2}`; } + if (Math.abs(root1) == root2) { return `x = plus.minus ${root2}`; } + const froot1 = formatNumber(root1, showPlus=false, showOne=true, showZero=true); + return `x = ${froot1} thick "or" thick x = ${froot2}`; +} + +function getQuadraticEquation(root1, root2, a) { + const b = a * -(root1 + root2); + const c = a * root1 * root2; + + const equation = formatQuadraticEquation(a, b, c); + const roots = formatRoots(root1, root2); + + return [equation, roots]; +} + +export default function QuadraticEquationQItem({ roots, a = 1 }) { + const [question, answer] = getQuadraticEquation(roots[0], roots[1], a); + return <QItem question={question} answer={answer} />; +} diff --git a/src/components/QuadraticEquationQList.jsx b/src/components/QuadraticEquationQList.jsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import QList from './QList.jsx'; +import QuadraticEquationQItem from './QuadraticEquationQItem.jsx'; +import { RefreshContext } from '../scripts/contexts.js'; + +function getRandomInt(min, range) { + return min + Math.floor(range * Math.random()); +} + +function getRandomRoots() { + return [getRandomInt(-9, 19), getRandomInt(-9, 19)].sort((a, b) => a - b); +} + +function getUniqueRoots(n) { + const listRoots = Array(); + + for (let i = 0; i < n; i++) { + roots = getRandomRoots(); + while (listRoots.some((v, i) => v[0] == roots[0] && v[1] == roots[1])) { + roots = getRandomRoots(); + } + listRoots.push(roots); + } + + return listRoots; +} + +export default function QuadraticEquationQList({ n, a = 1 }) { + const [listRoots, setListRoots] = React.useState(getUniqueRoots(10)); + const refresh = React.useContext(RefreshContext); + + React.useEffect(() => { + setListRoots(getUniqueRoots(10)); + }, [refresh]); + + return ( + <QList> + {Array.from({ length: n }).map((v, i) => { + return <QuadraticEquationQItem roots={listRoots[i]} a={a} />; + })} + </QList> + ); +} diff --git a/src/components/QuadraticEquationQuestionSet.jsx b/src/components/QuadraticEquationQuestionSet.jsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import QList from './QList.jsx'; +import QuadraticEquationQList from './QuadraticEquationQList.jsx'; + +export default function QuadraticEquationQuestionSet() { + return ( + <QList> + <QuadraticEquationQList n="5" /> + <QuadraticEquationQList n="6" a="2" /> + <QuadraticEquationQList n="7" a="3" /> + <QuadraticEquationQList n="5" a="4" /> + </QList> + ); +} diff --git a/src/scripts/auto-maths.js b/src/scripts/auto-maths.js @@ -0,0 +1,8 @@ +import renderMathInElement from 'kern-typ/auto-render'; + +renderMathInElement(document.body, { + delimiters: [ + { left: '$$', right: '$$', display: true }, + { left: '$', right: '$', display: false }, + ], +}); diff --git a/src/scripts/contexts.js b/src/scripts/contexts.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const QListContext = createContext(false); +export const RefreshContext = createContext(false); +export const ShowAnswerContext = createContext(false); diff --git a/src/scripts/index.js b/src/scripts/index.js @@ -0,0 +1,2 @@ +import './auto-maths.js'; +import './react-roots.js'; diff --git a/src/scripts/react-roots.js b/src/scripts/react-roots.js @@ -0,0 +1,12 @@ +import { createElement } from 'react'; +import { createRoot } from 'react-dom/client'; + +import QuadraticEquationQuestionSet from '../components/QuadraticEquationQuestionSet.jsx'; + +function renderComponent(id, component) { + node = document.getElementById(id); + const root = createRoot(node); + root.render(createElement(component)); +} + +renderComponent('quadratic-equation-question-set', QuadraticEquationQuestionSet); diff --git a/src/styles/index.css b/src/styles/index.css @@ -0,0 +1,43 @@ +@import 'kern-typ/kern.css'; + +html { + display: table; + margin: 0 auto; + background-color: green; +} + +body { + display: table-cell; + vertical-align: middle; + width: 100vw; + max-width: 800px; + background-color: white; + padding: 0 2em 0 1em; +} + +.QListOuter { + column-width: 200px; + column-gap: 4em; +} + +li { + break-inside: avoid-column; +} + +.QListInner { + list-style-type: lower-latin; +} + +.ButtonPanel { + margin-bottom: 1em; +} + +.Answer { + display: flex; + justify-content: flex-end; + color: green; +} + +.AnswerInvisible { + visibility: hidden; +} diff --git a/www/index.html b/www/index.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <link rel="stylesheet" href="css/index.css" /> + </head> + <body> + <h1>Hello world!</h1> + <section> + <h2>Introduction</h2> + <p>$ x^2 + 5x + 6 $ is an expression.</p> + $$ x^2 + 5x + 6 = 0 $$ + $$ x = -3 "or" x = -2 $$ + </section> + <section> + <h2>Random questions</h2> + <div id="quadratic-equation-question-set"></div> + </section> + <footer> + <p>Fin</p> + </footer> + <script src="js/index.js"></script> + </body> +</html>