Style guide
Every lesson follows one blueprint and every figure follows one visual language. That sameness is deliberate: a learner who has read one lesson knows how to read all of them, and a contributor who has written one knows how to write the next.
This guide is version 0.9, the pilot version. It is frozen as 1.0 at the end of the pilot.
The lesson blueprint
A lesson takes about 25 minutes and runs 1,500 to 2,500 words. It has a metadata block and eleven sections, always in this order. The section ids are fixed, so links and checks can rely on them.
| # | Section heading | id | What it holds | Illustration duty |
|---|---|---|---|---|
| 1 | (header, built from metadata) | Code, level, time, prerequisites as links, three to five measurable objectives | None | |
| 2 | The question | hook |
One concrete question the lesson will answer, in a .hook block |
None |
| 3 | Intuition | intuition |
The idea in plain words, tied to the anchor picture | Figure 1: the anchor diagram |
| 4 | Build it up | build |
The idea in small steps. Each step adds one thing to the picture or one line to the maths | Step frames |
| 5 | The formal statement | formal |
Definitions, the result, and the full derivation with every step explained | Colour-matched equation |
| 6 | Worked example | worked |
A small case solved by hand with real numbers | Figure 2: the worked-example trace |
| 7 | Code | code |
The method from scratch, then the library version. Cells assert the stated values | Output of the code |
| 8 | Explore | explore |
The module’s interactive, or a pointer to the lesson that holds it | Interactive |
| 9 | Pitfalls | pitfalls |
The common mistakes and why they are wrong, in .pitfall blocks |
Figure 3: wrong beside right |
| 10 | Check yourself | check |
Five recall questions with answers. Three graded exercises with solutions. One proof or challenge at L3 and above | None |
| 11 | Summary card | summary |
One picture and the key formulas on a single card | The anchor figure again |
| 12 | Sources and next steps | sources |
Sources that were opened and checked, and the lessons this one unlocks | None |
Three rules make the lessons deeper than most courses.
- No skipped steps. Every derivation shows every line. “It can be shown that” fails review.
- Three depth layers. The core path is always visible. Full proofs go in
.deeperblocks and research context in.frontierblocks, both collapsed, so a beginner and an expert can read the same page. - Questions come back. From the second lesson of a module on, a
.comebackblock after the hook repeats two recall questions from the lesson before.
Metadata
---
title: "Rotation and shear as matrices"
description: "One or two sentences for search results and link previews, 80 to 320 characters."
image: figures/M02.4.3-F1-card.png # the link-preview picture, made from the anchor figure
jupyter: python3 # run the code in the project's own environment
id: M02.4.3
level: L1
minutes: 25
requires: [M02.4.1, M02.4.2] # lesson, module or course codes
unlocks: [M02.4.4]
objectives: # three to five, each starting with a verb you can test
- "Quote any objective that contains a colon: like this one."
status: in review # draft, in review, or published
reviewers: [] # people who signed a check, never tools
last_verified: 2026-11-11
sources:
- "Author, Title, section"
---Writing
- Write for a reader who is clever and new to the topic. Use short sentences and plain words.
- Introduce a term in bold once, where it is defined, and use the same term from then on.
- Put the picture before the equation, and name the parts before describing the process.
- Tie every lesson to why machine learning needs it, in the hook or in a pitfall.
- Use real numbers in worked examples, small enough to check by hand.
- British spelling in prose (“colour”, “maths”); code identifiers stay American where libraries are.
Blocks
Lessons are plain Quarto Markdown plus a few fenced blocks. site/lua/lesson.lua turns them into components. Keep these blocks at the top level of the page or directly inside another block, never inside a list item.
::: {.hook}
The question.
:::
::: {.aimfig fig="M02.4.1-F1" alt="What the picture shows, for someone who cannot see it."}
Caption. It may contain $maths$.
:::
::: {.interactive x="M02.4-X1" title="Matrix playground" fallback="M02.4.2-F1" fallback-alt="What the static picture shows."}
Two sentences on what to try.
:::
::: {.deeper title="why the definition needs both rules"}
A full proof or a subtle point. Collapsed by default.
:::
::: {.frontier title="linear maps inside a transformer"}
Where this leads in current research. Collapsed by default.
:::
::: {.pitfall}
**"The mistaken belief, in the learner's words."**
Why it is wrong, and what is right.
:::
::: {.comeback}
**From the last lesson**
**1.** A question from the lesson before.
::: {.answer}
Its answer.
:::
:::
::: {.recall}
**1.** A question.
::: {.answer}
Its answer.
:::
:::
::: {.exercises}
**1.** [★]{.stars} An exercise.
::: {.answer summary="Show solution"}
The full solution.
:::
:::
::: {.summary-card}
**The idea in one line.**
::: {.aimfig fig="M02.4.1-F1" alt="The anchor figure again, described."}
A five-word caption.
:::
- Key formula one.
:::Modifiers on a figure: add .narrow for a small diagram or .wide for a wide one, as in {.aimfig .narrow fig=... alt=...}.
Code
- Build the method from scratch first, in plain Python or NumPy. Then show the library call.
- Every value the text states is asserted in a cell. Put the assertions in a cell with
#| echo: falseright after the cell that prints the values. - Cells run top to bottom in a fresh process. Set every random seed. Keep the whole lesson under ten seconds.
- Lesson code must run on Python 3.12 and every later version, because learners will not all have the newest. Do not use a feature, module or function name that is newer than 3.12 (write
math.comb, notmath.integer.comb). The site is built with Python 3.14. - Do not draw plots in lesson cells. A plot is a figure, and figures are scripts (next section).
The illustration standard
Minimums
| Unit | Minimum illustration |
|---|---|
| Lesson | 3 original figures: the anchor diagram, the worked-example trace, and wrong beside right |
| Module | 1 interactive explorable |
| Course | 1 course map and 1 one-page summary poster |
| Algorithm | 1 step-by-step trace |
| Theorem | 1 geometric picture, wherever one exists |
Rules, and the evidence behind them
- Picture before equation. In Mayer’s review, people who learned from words and graphics beat those given words alone across 13 experimental comparisons, with a median effect size of 1.35.
- Static by default. Use motion only when motion is the idea. Use an interactive only when the learner must change something to see the point.
- Labels sit on the parts. No distant legends.
- One idea per figure. A complex picture becomes a sequence of step frames.
- Name the parts before the process.
- Nothing decorative. No stock art, no mascots, no background textures. Every mark teaches.
- Text always fits. Words never overflow the box they sit in, never run off the edge of a panel and never sit on top of other words. The figure builder checks this on every figure and stops the build if it fails (
aimstyle.audit_text). The fix is to shorten the words, move them, or enlarge the box, in that order. The same holds on the web pages, at phone width too.
One colour language
A colour means the same thing in every figure, interactive and equation. The colours are the Okabe-Ito palette, which stays distinct under the common forms of colour-vision deficiency.
| Role | Meaning | Colour | Hex | In maths |
|---|---|---|---|---|
data |
Data, observations, inputs | Blue | #0072B2 |
\cdata{x} |
model |
Model output, predictions, images under a map | Vermillion | #D55E00 |
\cmodel{\hat y} |
truth |
Ground truth and targets | Black (white on dark) | #000000 |
\ctruth{y} |
grad |
Gradients, updates, motion, “correct” | Bluish green | #009E73 |
\cgrad{\nabla L} |
loss |
Loss, error, residuals | Reddish purple | #CC79A7 |
\closs{L} |
param |
Parameters and weights | Orange | #E69F00 |
\cparam{w} |
second |
A second class or second dataset | Sky blue | #56B4E9 |
\csecond{x'} |
highlight |
The current step, as a fill only | Yellow | #F0E442 |
Two rulings for cases the table does not name:
- The result of an operation takes the model colour. Inputs are data (blue); what is computed from them (a sum, a scaled copy, a projection, an image under a map) is model (vermillion). A scalar that the reader controls, such as a weight or a scale factor, is param (orange).
- Three sets of examples. Training examples are filled circles in the data colour, validation examples are diamonds in the second colour, and test examples are hollow squares in ink. The shapes differ as well as the colours.
style/aimstyle/palette.py is the single source of these values. Lines and fills use the colours above. Text uses darker (or, on the dark theme, lighter) variants of the same hues that reach a contrast of 4.5 to 1; tests/test_style.py checks that on every commit. Colour never carries meaning alone: a direct label, a shape or a pattern always repeats it.
Type follows the same discipline: one sans-serif face (Atkinson Hyperlegible, chosen for legibility), the Computer Modern maths face for symbols, three text sizes (SMALL, LABEL, TITLE) and two line weights (THIN, THICK).
Figures are code
Each figure is a script named by its address, in the figures/ folder of its module:
"""M02.4.1-F1 (anchor): a linear map moves the whole plane and keeps the grid a grid."""
from aimstyle import figure, components as c, SMALL, LABEL, THIN, THICK
@figure("M02.4.1-F1", width=6.6, height=3.5)
def draw(fig, t): # t is the active theme: t.data, t.model, t.ink, t.on("data") ...
ax = fig.add_subplot()
c.plane(ax, t, lim=3)
c.vector(ax, (2, 1), t, t.data, label=r"$\mathbf{u}$")python tools/run_figures.pyregenerates every figure. Each script draws the figure in the light and in the dark theme and writes one small SVG that serves both: its colours are CSS variables that switch with the page theme. The SVGs are build output and are not committed.- A script never names a hex colour. It asks the theme:
t.datafor lines and fills,t.on("data")for text,t.soft("data")for a quiet fill,t.ink,t.muted,t.grid. aimstyle.componentsdraws the recurring parts:plane,vector,warped_grid,shape,label,pointer,dot,badge,box,flow,panel_titleandverdict. Parts for one field live beside it (aimstyle/rl.py,aimstyle/ml.py). Reuse before you draw.- The standard width is 6.4 to 6.6 inches. Text must stay readable when the figure is shown 360 pixels wide, so prefer two panels to four, and stack before you shrink.
- Helper modules shared by a module’s figures start with an underscore (
figures/_common.py). - Screenshots and third-party images are banned. That keeps the style whole and removes copyright risk.
- Set
AIM_PREVIEW_DIR=/some/folderwhen running the tool to also get PNG previews for review. - A label that is meant to cross a shape can opt out of the text-fit check with
text.set_gid("free"). Use it rarely, and say why in a comment. components.box(..., text=...)shrinks its text to fit, down to 8 points. Below that the build fails, because text that small cannot be read on a phone.
A figure passes review when it teaches one idea, reads at phone width, survives greyscale and colour-vision simulation, has accurate alt text, and regenerates from its script.
Interactives
One interactive per module, written as a single dependency-free ES module in the module’s interactive/ folder. It loads instantly, works on a phone and needs no server.
// M02.4-X1: Matrix playground
import { mount, onTheme, el, svgEl, slider, button, fmt } from "../../../../../site/js/aim.js";
mount("M02.4-X1", (stage) => {
const svg = svgEl("svg", { viewBox: "0 0 400 400", role: "img", "aria-label": "What it shows" });
const controls = el("div", { class: "x-controls" });
stage.append(svg, controls);
// build controls with slider({...}) and button(...), keep state in plain variables,
// and redraw from state inside onTheme((c) => { ...use c.data, c.on.data, c.grid... })
});- Colours come from
colors()oronTheme(), never from literals, so both themes work. - Every control is a native
<input>or<button>: the keyboard and screen readers work for free. Anything draggable also has a slider or button that does the same job. - Motion is optional. Honour
reducedMotion()and always offer a step button beside play. - State what the reader should notice in a read-out (
class="x-readout"), in words and numbers. - The lesson supplies a static fallback figure. It stays in the page if the script fails.
- Layout classes:
x-controls,x-row,x-buttons,x-readout,x-full(span both columns),x-handle(draggable: give this class to every drag target;aim.jsthen keeps a touch on it from scrolling the page, while a swipe elsewhere on the drawing still scrolls),fieldset.x-groupwith alegendfor a named group of controls,button.primary, andaria-pressedfor toggles.
Accessibility
- Every figure has alt text written by its author, describing what the picture shows.
- Text contrast is at least 4.5 to 1, and 3 to 1 for large text.
- Every animation has a still state and respects the reduced-motion setting.
- Every interactive works by keyboard and has a static fallback figure.
- Every figure has a light and a dark rendering, carried in one file.
Checks on every commit
- Every code cell runs and its outputs match the stated values (
tools/run_lesson_code.py, and again in the site build). - Every figure regenerates from its script (
tools/run_figures.py). - The prerequisite graph has no cycles and no missing lessons (
tools/check_lessons.py). - Every lesson has objectives, at least five figures (three for lessons written before 14 November 2026), five recall questions and sources (
tools/check_lessons.py). - Every figure has alt text, the palette passes the contrast check (
tests/test_style.py), and no text overflows its box or overlaps other text, in figures (tools/run_figures.py) or on pages (tools/audit_overflow.mjs). - Every internal link resolves (
tools/check_links.py). External links are checked weekly. - Symbols match NOTATION.md (
tools/check_lessons.py).
In addition, tests/ holds a numeric test for every derivation: a formula in a lesson is checked against a direct computation before anyone is asked to read it.
Review before “published”
A lesson is marked in review until a named person has signed each of five checks.
| Check | What is verified | How |
|---|---|---|
| Sources | Every factual claim and definition | Traced to a primary source that the reviewer opens and reads |
| Maths | Every derivation and formula | Re-derived by a second person, and tested numerically in the build |
| Code | Every cell and printed output | Run in the automated build with pinned library versions |
| Figures | Every figure and interactive | The review points above |
| Learning | Clarity at the stated level | Learners at the target level work through it |
Drafting tools may help write text, code and figure scripts. They cannot approve anything. Every equation in a draft is tested with numbers, every citation is opened, and a citation that cannot be opened is removed.