Skip to content

HTML with answers (no backend)

Write the exercise with its answers, add one script, and the page grades itself in the browser.

Trade-off: the answers are in the page source. Use this mode for self-practice, homework pages, blogs, prototypes and offline use. For tests and anything with stakes, use HTML + backend.

  1. Load the bundle (it registers <astl-player>, the built-in components and the in-browser compiler):

    <script type="module" src="https://cdn.jsdelivr.net/npm/astrolabe@0.1/dist/cdn/astrolabe.js"></script>
    <style>astl-player:not(:defined) { visibility: hidden; }</style>

    Optional: dist/astrolabe.css holds the same --astl-* tokens for styling the page around the player.

  2. Put an <astl-doc> inside <astl-player>. Use the content format:

    <astl-player locale="en" persist>
    <astl-doc lang="en">
    <astl-page id="p1" label="Animals">
    <p>An elephant is <astl-gap id="g1" answer="grey"><astl-alt>gray</astl-alt></astl-gap>, and a trout is a <astl-gap id="g2" answer="fish"></astl-gap>.</p>
    <astl-choice id="q1" multiple scoring="per-option">
    <astl-prompt>Which animals are mammals?</astl-prompt>
    <astl-option id="a" correct>Otter</astl-option>
    <astl-option id="b">Trout</astl-option>
    <astl-option id="c" correct>Mole</astl-option>
    </astl-choice>
    </astl-page>
    <astl-page id="p2" label="Numbers">
    <p>Half of one is <astl-gap id="g3" type="number" answer="0.5"></astl-gap>.</p>
    <astl-choice id="q2">
    <astl-prompt>How many legs does a spider have?</astl-prompt>
    <astl-option id="a">Six</astl-option>
    <astl-option id="b" correct>Eight</astl-option>
    <astl-option id="c">Ten</astl-option>
    </astl-choice>
    </astl-page>
    </astl-doc>
    </astl-player>
  3. Open the page over http: or https: (localhost works; file: does not, because browsers load module scripts and WebCrypto only from real origins). The player compiles the source in the browser, hides it, and shows the exercise with "Check" (practice mode). Nothing is sent over the network.

  • astrolabe.js registers a compileSource hook: the engine compiles your source into a view and an answer key in memory and grades with localChecker. It uses a fixed public secret and the page path as salt, so ids stay stable across reloads (the answers are in the page anyway, so the secret protects nothing here).
  • If the source has errors (a gap without an answer, a choice without a correct option), the player shows them instead of the exercise: a short notice, then each error in the player's locale (also logged to the browser console). Run astl validate page.html to see them in the terminal (it checks every <astl-doc> of the page).
  • locale sets the UI language (buttons, messages); lang on <astl-doc> is the content language used for grading (Polish decimal comma, diacritics).
Attribute on <astl-player> Meaning
locale UI language, e.g. pl, en. Default: the browser's language.
theme light, dark, hc (high contrast) or auto (default, follows the system).
persist Save progress in the browser (localStorage) and restore it after a reload.

Listen to events if you want to store progress yourself (astl-snapshot, astl-result, astl-page). Every attribute, property, method and event: Player reference.

The bundle is a thin wrapper. With a bundler:

import { createEngine, localChecker } from '@astrolabe/engine';
import { definePlayer } from '@astrolabe/player';
import { builtInDefinitions } from '@astrolabe/components/definitions';
import { builtInElements } from '@astrolabe/components/elements';
// Answers are public in this mode, so a fixed secret is fine and keeps ids stable across reloads.
const engine = createEngine({ components: builtInDefinitions, secret: 'astrolabe-in-browser-key' });
definePlayer({
elements: builtInElements,
async compileSource(source) {
const result = await engine.compile(source, { salt: location.pathname });
if (!result.ok) {
const errors = result.issues.filter((i) => i.severity === 'error');
throw new Error(errors.map((i) => engine.describe(i, 'en')).join('\n'));
}
return { view: result.view, checker: localChecker(engine, result.key) };
},
});

Runnable example: examples/static-html in the repository (pnpm dev:static serves it).