mirror of
https://github.com/HananoshikaYomaru/openfreebooks.git
synced 2026-11-12 20:45:52 +00:00
Enhance math content and widget responsiveness: Add detailed explanations for calculating remainders in written calculation chapters, including examples in _index.md and core.html. Update SKILL.md to include mobile-first demo responsiveness guidelines for all widgets. Revise styles in chapter.scss to improve mobile layout and introduce a mobile toggle for the vertical arithmetic workbench. Update feed.xml and sitemap.xml to reflect recent content additions and modifications.
This commit is contained in:
parent
30b4f4cf53
commit
61963350e7
9 changed files with 231 additions and 44 deletions
|
|
@ -52,6 +52,7 @@ bun run build # production: JS + Zola + pagefind → public/
|
||||||
| Graph | `graph.edges` must be **acyclic**; builder throws on cycles |
|
| Graph | `graph.edges` must be **acyclic**; builder throws on cycles |
|
||||||
| Chart axes (all widgets) | Every coordinate chart must render **x/y axis labels, positive-direction arrowheads, and visible graduations (ticks + scale labels)**. Apply this consistently across all subjects/widgets. |
|
| Chart axes (all widgets) | Every coordinate chart must render **x/y axis labels, positive-direction arrowheads, and visible graduations (ticks + scale labels)**. Apply this consistently across all subjects/widgets. |
|
||||||
| Accessibility | Avoid decorative characters that may render as emoji on mobile. Prefer broadly supported symbols/text so desktop and mobile stay visually consistent. |
|
| Accessibility | Avoid decorative characters that may render as emoji on mobile. Prefer broadly supported symbols/text so desktop and mobile stay visually consistent. |
|
||||||
|
| Demo responsiveness (all widgets) | Mobile-first: keep demo viewport short, reserve stage/canvas height up front to avoid CLS, and never let status/controls overlap or block the interactive area. Use collapsible/toggle controls on small screens when needed. |
|
||||||
|
|
||||||
## Common tasks → where to edit
|
## Common tasks → where to edit
|
||||||
|
|
||||||
|
|
@ -139,6 +140,11 @@ Live chapters live under `content/{subject}/{slug}/`:
|
||||||
|
|
||||||
**Dialog/modals in widgets:** when adding a `<dialog>` in chapter widgets, use the shared modal pattern and classes from `themes/openfreebooks/sass/_contributors.scss` (`contributor-dialog`, `contributor-dialog-panel`, `contributor-dialog-close`) as the base style, keep the dialog centered in modal mode, and only layer minimal widget-specific overrides on top.
|
**Dialog/modals in widgets:** when adding a `<dialog>` in chapter widgets, use the shared modal pattern and classes from `themes/openfreebooks/sass/_contributors.scss` (`contributor-dialog`, `contributor-dialog-panel`, `contributor-dialog-close`) as the base style, keep the dialog centered in modal mode, and only layer minimal widget-specific overrides on top.
|
||||||
|
|
||||||
|
**Widget layout standards (all subjects):**
|
||||||
|
- Keep the primary demo stage visible without long pre-scroll on mobile.
|
||||||
|
- Reserve stage space from first render (`min-height`/fixed aspect) so hydration or assets do not shift nearby content.
|
||||||
|
- Keep feedback UI (step badges, status, toolbars) outside the canvas interaction area, or anchored so they never occlude controls/content.
|
||||||
|
|
||||||
### Interactive demo technology choices
|
### Interactive demo technology choices
|
||||||
|
|
||||||
Interactive demos can use different technologies based on the learning goal and interaction type:
|
Interactive demos can use different technologies based on the learning goal and interaction type:
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,27 @@ strand = "Number & Algebra"
|
||||||
dividend? Write that digit in the quotient, multiply, subtract, then bring down the next digit. If
|
dividend? Write that digit in the quotient, multiply, subtract, then bring down the next digit. If
|
||||||
the division is not exact, the amount left is the <strong>remainder</strong>.
|
the division is not exact, the amount left is the <strong>remainder</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
To calculate and check a remainder, always use
|
||||||
|
\( \text{dividend} = \text{divisor} \times \text{quotient} + \text{remainder} \), with
|
||||||
|
\(0 \le \text{remainder} < \text{divisor}\). For example:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
\[
|
||||||
|
\begin{aligned}
|
||||||
|
157 \div 12 &= 13 \text{ remainder } 1 \\
|
||||||
|
157 &= 12 \times 13 + 1
|
||||||
|
\end{aligned}
|
||||||
|
\]
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The remainder can also be written as a fraction of the divisor:
|
||||||
|
\[
|
||||||
|
\begin{aligned}
|
||||||
|
157 \div 12 &= 13 + \frac{1}{12}
|
||||||
|
\end{aligned}
|
||||||
|
\]
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 id="terminology" class="book-prose__heading">Names of the parts</h2>
|
<h2 id="terminology" class="book-prose__heading">Names of the parts</h2>
|
||||||
<div class="book-table-wrap">
|
<div class="book-table-wrap">
|
||||||
|
|
@ -190,7 +211,6 @@ strand = "Number & Algebra"
|
||||||
Subtraction is the inverse of addition: \(c = a - b\) means the same as \(c + b = a\). Borrowing
|
Subtraction is the inverse of addition: \(c = a - b\) means the same as \(c + b = a\). Borrowing
|
||||||
moves one ten from the left so the subtraction in the current column matches that story.
|
moves one ten from the left so the subtraction in the current column matches that story.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="checkpoints" class="book-prose__heading">Checkpoints</h2>
|
<h2 id="checkpoints" class="book-prose__heading">Checkpoints</h2>
|
||||||
<div class="book-callout" role="note">
|
<div class="book-callout" role="note">
|
||||||
<p class="book-callout__label">Checkpoint</p>
|
<p class="book-callout__label">Checkpoint</p>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
.va-workbench__shell {
|
.va-workbench__shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 28rem;
|
min-height: 24rem;
|
||||||
border: 1px solid var(--va-panel-border);
|
border: 1px solid var(--va-panel-border);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -54,6 +54,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.va-workbench__mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: color-mix(in srgb, var(--paper) 50%, var(--card-bg));
|
||||||
|
color: var(--ink);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.va-workbench__sidebar {
|
.va-workbench__sidebar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -294,37 +308,38 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 16rem;
|
min-height: 18rem;
|
||||||
background: var(--va-stage-bg);
|
background: var(--va-stage-bg);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.va-workbench__message-box {
|
.va-workbench__message-box {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 1rem;
|
z-index: 1;
|
||||||
left: 50%;
|
width: 100%;
|
||||||
transform: translateX(-50%);
|
padding: 0.75rem 0.9rem;
|
||||||
z-index: 2;
|
border-bottom: 1px solid var(--line);
|
||||||
width: min(92%, 32rem);
|
background: color-mix(in srgb, var(--paper) 80%, var(--card-bg));
|
||||||
padding: 0.85rem 1.1rem;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
background: color-mix(in srgb, var(--card-bg) 92%, transparent);
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
box-shadow: 0 8px 24px var(--shadow-soft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.va-workbench__message-box--error .va-workbench__message {
|
.va-workbench__message-box--error .va-workbench__message {
|
||||||
color: var(--va-error);
|
color: var(--va-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.va-workbench__message-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.va-workbench__message {
|
.va-workbench__message {
|
||||||
font-size: 1rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
margin: 0 0 0.5rem;
|
margin: 0;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.va-workbench__progress-track {
|
.va-workbench__progress-track {
|
||||||
|
|
@ -342,9 +357,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.va-workbench__step-counter {
|
.va-workbench__step-counter {
|
||||||
font-size: 0.75rem;
|
font-size: 0.72rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
margin: 0.35rem 0 0;
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.va-workbench__canvas-wrap {
|
.va-workbench__canvas-wrap {
|
||||||
|
|
@ -356,6 +372,51 @@
|
||||||
background-size: 20px 20px;
|
background-size: 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.va-workbench {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__mobile-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__sidebar {
|
||||||
|
gap: 0;
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.24s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__sidebar--open {
|
||||||
|
gap: 0.85rem;
|
||||||
|
max-height: 42rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__stage {
|
||||||
|
min-height: 14rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__message-head {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__message {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.va-workbench__canvas-wrap {
|
||||||
|
min-height: 12rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.va-workbench__canvas {
|
.va-workbench__canvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,27 @@
|
||||||
dividend? Write that digit in the quotient, multiply, subtract, then bring down the next digit. If
|
dividend? Write that digit in the quotient, multiply, subtract, then bring down the next digit. If
|
||||||
the division is not exact, the amount left is the <strong>remainder</strong>.
|
the division is not exact, the amount left is the <strong>remainder</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
To calculate and check a remainder, always use
|
||||||
|
\( \text{dividend} = \text{divisor} \times \text{quotient} + \text{remainder} \), with
|
||||||
|
\(0 \le \text{remainder} < \text{divisor}\). For example:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
\[
|
||||||
|
\begin{aligned}
|
||||||
|
157 \div 12 &= 13 \text{ remainder } 1 \\
|
||||||
|
157 &= 12 \times 13 + 1
|
||||||
|
\end{aligned}
|
||||||
|
\]
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The remainder can also be written as a fraction of the divisor:
|
||||||
|
\[
|
||||||
|
\begin{aligned}
|
||||||
|
157 \div 12 &= 13 + \frac{1}{12}
|
||||||
|
\end{aligned}
|
||||||
|
\]
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2 id="terminology" class="book-prose__heading">Names of the parts</h2>
|
<h2 id="terminology" class="book-prose__heading">Names of the parts</h2>
|
||||||
<div class="book-table-wrap">
|
<div class="book-table-wrap">
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
Subtraction is the inverse of addition: \(c = a - b\) means the same as \(c + b = a\). Borrowing
|
Subtraction is the inverse of addition: \(c = a - b\) means the same as \(c + b = a\). Borrowing
|
||||||
moves one ten from the left so the subtraction in the current column matches that story.
|
moves one ten from the left so the subtraction in the current column matches that story.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 id="checkpoints" class="book-prose__heading">Checkpoints</h2>
|
<h2 id="checkpoints" class="book-prose__heading">Checkpoints</h2>
|
||||||
<div class="book-callout" role="note">
|
<div class="book-callout" role="note">
|
||||||
<p class="book-callout__label">Checkpoint</p>
|
<p class="book-callout__label">Checkpoint</p>
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,9 @@ export class MathVisualizer {
|
||||||
this.generateAddition(lines);
|
this.generateAddition(lines);
|
||||||
} else {
|
} else {
|
||||||
let current = lines[0];
|
let current = lines[0];
|
||||||
|
const initialValue = BigInt(current);
|
||||||
|
const divisionDivisors: bigint[] = [];
|
||||||
|
const divisionRemainders: bigint[] = [];
|
||||||
const finalStepFrames: Frame[] = [];
|
const finalStepFrames: Frame[] = [];
|
||||||
|
|
||||||
for (let i = 1; i < lines.length; i++) {
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
|
@ -159,14 +162,29 @@ export class MathVisualizer {
|
||||||
this.generateMultiplication(current, next);
|
this.generateMultiplication(current, next);
|
||||||
current = (BigInt(current) * BigInt(next)).toString();
|
current = (BigInt(current) * BigInt(next)).toString();
|
||||||
} else if (this.operation === "/") {
|
} else if (this.operation === "/") {
|
||||||
if (BigInt(next) === 0n) throw new Error("Cannot divide by zero.");
|
const nextDivisor = BigInt(next);
|
||||||
|
if (nextDivisor === 0n) throw new Error("Cannot divide by zero.");
|
||||||
|
const currentValue = BigInt(current);
|
||||||
|
divisionDivisors.push(nextDivisor);
|
||||||
|
divisionRemainders.push(currentValue % nextDivisor);
|
||||||
this.generateDivision(current, next);
|
this.generateDivision(current, next);
|
||||||
current = (BigInt(current) / BigInt(next)).toString();
|
current = (currentValue / nextDivisor).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
finalStepFrames.push(this.cloneGrid(this.frames[this.frames.length - 1]));
|
finalStepFrames.push(this.cloneGrid(this.frames[this.frames.length - 1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.operation === "/" && divisionDivisors.length > 0 && this.frames.length > 0) {
|
||||||
|
const resultFrame = this.cloneGrid(this.frames[this.frames.length - 1]);
|
||||||
|
resultFrame.message = this.buildDivisionResultMessage(
|
||||||
|
initialValue,
|
||||||
|
BigInt(current),
|
||||||
|
divisionDivisors,
|
||||||
|
divisionRemainders
|
||||||
|
);
|
||||||
|
this.frames.push(resultFrame);
|
||||||
|
}
|
||||||
|
|
||||||
if (finalStepFrames.length > 1) {
|
if (finalStepFrames.length > 1) {
|
||||||
this.frames.push(this.mergeFramesToSummary(finalStepFrames));
|
this.frames.push(this.mergeFramesToSummary(finalStepFrames));
|
||||||
}
|
}
|
||||||
|
|
@ -202,6 +220,49 @@ export class MathVisualizer {
|
||||||
return frame.cells[`${x},${y}`] ?? null;
|
return frame.cells[`${x},${y}`] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private rationalToDecimalString(numerator: bigint, denominator: bigint, maxDigits = 9) {
|
||||||
|
const integerPart = numerator / denominator;
|
||||||
|
let remainder = numerator % denominator;
|
||||||
|
if (remainder === 0n) return integerPart.toString();
|
||||||
|
|
||||||
|
let fractional = "";
|
||||||
|
for (let i = 0; i < maxDigits && remainder !== 0n; i++) {
|
||||||
|
remainder *= 10n;
|
||||||
|
fractional += (remainder / denominator).toString();
|
||||||
|
remainder %= denominator;
|
||||||
|
}
|
||||||
|
if (remainder !== 0n) fractional += "...";
|
||||||
|
return `${integerPart.toString()}.${fractional}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildDivisionResultMessage(
|
||||||
|
originalDividend: bigint,
|
||||||
|
finalQuotient: bigint,
|
||||||
|
divisors: bigint[],
|
||||||
|
remainders: bigint[]
|
||||||
|
) {
|
||||||
|
const totalDivisor = divisors.reduce((acc, d) => acc * d, 1n);
|
||||||
|
const decimalValue = this.rationalToDecimalString(originalDividend, totalDivisor);
|
||||||
|
|
||||||
|
if (remainders.every((r) => r === 0n)) {
|
||||||
|
return `Result: ${finalQuotient.toString()} (exact division).`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const terms: string[] = [];
|
||||||
|
for (let i = remainders.length - 1; i >= 0; i--) {
|
||||||
|
const remainder = remainders[i];
|
||||||
|
const denomFactors = divisors
|
||||||
|
.slice(i)
|
||||||
|
.map((d) => d.toString())
|
||||||
|
.reverse();
|
||||||
|
const denominatorText =
|
||||||
|
denomFactors.length === 1 ? denomFactors[0] : denomFactors.join(" x ");
|
||||||
|
terms.push(`${remainder.toString()}/${denominatorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Result: ${finalQuotient.toString()} + ${terms.join(" + ")} = ${decimalValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
private mergeFramesToSummary(finalFrames: Frame[]): Frame {
|
private mergeFramesToSummary(finalFrames: Frame[]): Frame {
|
||||||
const summaryFrame = this.createEmptyFrame();
|
const summaryFrame = this.createEmptyFrame();
|
||||||
summaryFrame.message = "Final Summary of All Steps";
|
summaryFrame.message = "Final Summary of All Steps";
|
||||||
|
|
@ -661,7 +722,7 @@ export class MathVisualizer {
|
||||||
qFrame.message = `${div} goes into ${currentVal} exactly ${qDigit} times.`;
|
qFrame.message = `${div} goes into ${currentVal} exactly ${qDigit} times.`;
|
||||||
this.frames.push(qFrame);
|
this.frames.push(qFrame);
|
||||||
|
|
||||||
if (qDigit > 0 || i === dividendArr.length - 1) {
|
if (qDigit > 0) {
|
||||||
const subVal = qDigit * div;
|
const subVal = qDigit * div;
|
||||||
const subStr = subVal.toString();
|
const subStr = subVal.toString();
|
||||||
const subFrame = this.cloneGrid(this.frames[this.frames.length - 1]);
|
const subFrame = this.cloneGrid(this.frames[this.frames.length - 1]);
|
||||||
|
|
@ -695,9 +756,14 @@ export class MathVisualizer {
|
||||||
}
|
}
|
||||||
resFrame.message = `Remainder is ${currentVal}.`;
|
resFrame.message = `Remainder is ${currentVal}.`;
|
||||||
this.frames.push(resFrame);
|
this.frames.push(resFrame);
|
||||||
|
} else if (i === dividendArr.length - 1) {
|
||||||
|
const zeroFrame = this.cloneGrid(this.frames[this.frames.length - 1]);
|
||||||
|
zeroFrame.message = `${div} does not fit into ${currentVal}. Quotient digit is 0; remainder stays ${currentVal}.`;
|
||||||
|
this.frames.push(zeroFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ function VerticalArithmeticWorkbench() {
|
||||||
const [playing, setPlaying] = createSignal(false);
|
const [playing, setPlaying] = createSignal(false);
|
||||||
const [speed, setSpeed] = createSignal(800);
|
const [speed, setSpeed] = createSignal(800);
|
||||||
const [soundMuted, setSoundMuted] = createSignal(true);
|
const [soundMuted, setSoundMuted] = createSignal(true);
|
||||||
|
const [controlsOpen, setControlsOpen] = createSignal(false);
|
||||||
|
|
||||||
const sound = new WorkbenchSound();
|
const sound = new WorkbenchSound();
|
||||||
let lastStep = 0;
|
let lastStep = 0;
|
||||||
|
|
@ -178,7 +179,21 @@ function VerticalArithmeticWorkbench() {
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="va-workbench__shell">
|
<div class="va-workbench__shell">
|
||||||
<aside class="va-workbench__sidebar">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="va-workbench__mobile-toggle"
|
||||||
|
aria-expanded={controlsOpen()}
|
||||||
|
aria-controls="va-workbench-controls"
|
||||||
|
onClick={() => setControlsOpen((open) => !open)}
|
||||||
|
>
|
||||||
|
{controlsOpen() ? "Hide controls" : "Show controls"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<aside
|
||||||
|
id="va-workbench-controls"
|
||||||
|
class="va-workbench__sidebar"
|
||||||
|
classList={{ "va-workbench__sidebar--open": controlsOpen() }}
|
||||||
|
>
|
||||||
<div class="va-workbench__brand">
|
<div class="va-workbench__brand">
|
||||||
<span class="va-workbench__brand-icon" aria-hidden="true">
|
<span class="va-workbench__brand-icon" aria-hidden="true">
|
||||||
±
|
±
|
||||||
|
|
@ -309,22 +324,21 @@ function VerticalArithmeticWorkbench() {
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="va-workbench__stage">
|
<div class="va-workbench__stage">
|
||||||
<div
|
<div class="va-workbench__message-box" classList={{ "va-workbench__message-box--error": isError() }}>
|
||||||
class="va-workbench__message-box"
|
<div class="va-workbench__message-head">
|
||||||
classList={{ "va-workbench__message-box--error": isError() }}
|
<p class="va-workbench__message" aria-live="polite">
|
||||||
>
|
{message()}
|
||||||
<p class="va-workbench__message" aria-live="polite">
|
</p>
|
||||||
{message()}
|
<p class="va-workbench__step-counter">
|
||||||
</p>
|
Step {stepCurrent()} / {stepTotal()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="va-workbench__progress-track">
|
<div class="va-workbench__progress-track">
|
||||||
<div
|
<div
|
||||||
class="va-workbench__progress-fill"
|
class="va-workbench__progress-fill"
|
||||||
style={{ width: `${progress()}%` }}
|
style={{ width: `${progress()}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="va-workbench__step-counter">
|
|
||||||
Step {stepCurrent()} / {stepTotal()}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={canvasWrapRef} class="va-workbench__canvas-wrap">
|
<div ref={canvasWrapRef} class="va-workbench__canvas-wrap">
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@
|
||||||
<title>Open Free Books</title>
|
<title>Open Free Books</title>
|
||||||
<link>https://openfreebooks.org/</link>
|
<link>https://openfreebooks.org/</link>
|
||||||
<description>Recent updates to Open Free Books pages and live chapters.</description>
|
<description>Recent updates to Open Free Books pages and live chapters.</description>
|
||||||
<lastBuildDate>Fri, 22 May 2026 06:25:20 GMT</lastBuildDate>
|
<lastBuildDate>Fri, 22 May 2026 06:55:33 GMT</lastBuildDate>
|
||||||
|
<item>
|
||||||
|
<title>Pythagoras' theorem</title>
|
||||||
|
<link>https://openfreebooks.org/math/pythagoras-theorem/</link>
|
||||||
|
<guid isPermaLink="true">https://openfreebooks.org/math/pythagoras-theorem/</guid>
|
||||||
|
<pubDate>Fri, 22 May 2026 06:55:33 GMT</pubDate>
|
||||||
|
<description>Use Pythagoras' theorem and its converse in 2D problems and coordinate settings.</description>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>Algebraic fractions & formulas</title>
|
<title>Algebraic fractions & formulas</title>
|
||||||
<link>https://openfreebooks.org/math/algebraic-fractions-formulas/</link>
|
<link>https://openfreebooks.org/math/algebraic-fractions-formulas/</link>
|
||||||
|
|
@ -26,13 +33,6 @@
|
||||||
<pubDate>Fri, 22 May 2026 05:57:59 GMT</pubDate>
|
<pubDate>Fri, 22 May 2026 05:57:59 GMT</pubDate>
|
||||||
<description>0 is nothing; 1 is one. Learn to tell them apart — the same skill as telling false from true — before counting larger amounts.</description>
|
<description>0 is nothing; 1 is one. Learn to tell them apart — the same skill as telling false from true — before counting larger amounts.</description>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<title>Pythagoras' theorem</title>
|
|
||||||
<link>https://openfreebooks.org/math/pythagoras-theorem/</link>
|
|
||||||
<guid isPermaLink="true">https://openfreebooks.org/math/pythagoras-theorem/</guid>
|
|
||||||
<pubDate>Fri, 22 May 2026 05:22:44 GMT</pubDate>
|
|
||||||
<description>Use Pythagoras' theorem and its converse in 2D problems and coordinate settings.</description>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<title>Coordinate geometry foundations</title>
|
<title>Coordinate geometry foundations</title>
|
||||||
<link>https://openfreebooks.org/math/coordinate-geometry-foundations/</link>
|
<link>https://openfreebooks.org/math/coordinate-geometry-foundations/</link>
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://openfreebooks.org/math/pythagoras-theorem/</loc>
|
<loc>https://openfreebooks.org/math/pythagoras-theorem/</loc>
|
||||||
<lastmod>2026-05-22T05:22:44.000Z</lastmod>
|
<lastmod>2026-05-22T06:55:33.000Z</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://openfreebooks.org/math/quadratic-equations/</loc>
|
<loc>https://openfreebooks.org/math/quadratic-equations/</loc>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue