Accessibility
What this site delivers, and what it does not. The rule it follows is the one I apply to documents: never declare structure you do not provide.
what is delivered
- The terminal appearance is decoration. Prompts, box rules and the ASCII banner are generated by stylesheets or hidden from assistive technology; the document underneath is ordinary headings, lists, tables and links. The banner announces as "Erik Nijenhuis, heading level 1", never as a run of block characters.
- Every bar chart is accompanied by its number, and the bar itself is hidden from screen readers. There is no way in this codebase to render a bar without the figure it represents.
- Body text meets WCAG AAA contrast (7:1) and every other colour pair meets AA (4.5:1) or the non-text threshold (3:1). This is verified at build time against the stylesheet, in both light and dark themes, and the build fails on a shortfall.
- Motion is opt-in. Animations only run once the page has confirmed that reduced motion was not requested; with scripting disabled nothing moves at all. Recorded terminal sessions have a still image showing the finished output, and a text transcript.
- The site works without JavaScript. The skills search becomes an ordinary form that loads the complete table, and every skill has its own page.
- Light and dark themes both exist and are both maintained. The default follows your operating system.
decisions worth stating
The search results are a table, not a listbox
The obvious implementation would mark the search box as an ARIA combobox with a listbox of options. It is not done here, deliberately. That pattern promises an interaction contract — option selection, a committed value, typeahead semantics — which a filtered table does not honour. Declaring it would be claiming a behaviour that is not implemented, which is worse for someone relying on it than plain table semantics. Instead: a real table, a polite live region announcing how many rows match, and links you can reach with the arrow keys.
Sheet music is a drawing, and the recording is the alternative
The scores on ~/music are engraved by LilyPond, which draws every notehead, clef and digit as a path — and the lyrics under the staff along with them, so even a song's words are a picture there. There is no text inside an engraving at all, and no alt text that could honestly stand in for a page of music. So the notation is marked as decoration rather than dressed up as an image with a description, and what carries the piece is beside it: a caption naming it with its key, metre and instrument; a recording, which conveys the music rather than the notation; the count of systems, so the shape of the page is at least stated; and, for a piece with words, those words printed as ordinary text — in the language they are sung in, with a translation. A piece is only published once it has a recording.
A score is engraved as several systems because a line of music has to break somewhere. That is an artefact of the page, not several different pictures, so the set carries one name between them — rather than the same label announced once per system with nothing to tell them apart.
The audio player is built here, and what that costs
The controls under a score are this site's rather than the browser's. That is a decision with a price: the browser's player arrives with its keyboard behaviour already correct, and replacing it moves that responsibility here — on the one control that exists for accessibility in the first place.
So it is kept by using the elements that already implement it rather than by
reimplementing them. The seek bar is an <input type="range">,
so arrow keys, Home, End and the Page keys move it exactly as they do
anywhere else, and a screen reader knows it as a slider without being told.
The play control is a <button>, so Enter and Space work
and it takes focus in order. Whether it is playing is carried by that
button's own name — it reads "Play …" or "Pause …" — rather than by a live
region, and the elapsed time is deliberately not announced at all: it
changes several times a second and would talk over everything else on the
page. The slider announces its position as a time, "0:32 of 1:24", instead
of the step count underneath it.
None of that is reached by hiding anything until a script runs. The page ships the browser's own player, and the script replaces it. With scripting off, or if the script fails, what remains is a working native player rather than an empty box.
Recordings keep their own colours
Terminal recordings are shown with the colours they were recorded in, on a dark frame, in both themes. A recording is a photograph of a terminal; repainting it to match the page would misrepresent what was on screen.
what is not delivered
- This site has not been tested with a screen reader by anyone who uses one daily. It has been checked with keyboard-only navigation and against the automated rules described above, which is not the same thing.
- That applies to the audio player in particular, and it is the piece I would most like to hear about. Its markup is verified on every build — a real browser loads the page, and the tests assert that each control has a name, that the seek bar is a range input and not something wearing the word "slider", and that the native player is still there when the script is not. What no test of mine can tell you is how it behaves under the thing it is for.
- No formal WCAG conformance level is claimed. The specific measures above are what I can demonstrate; a claim of full conformance would be an assertion I have not audited.
If something here does not work for you, mail erik@xerdi.com and describe what happened — that is more useful to me than a conformance badge.