A single-file, offline maritime instrument that resolves a ship's observed relative (apparent) wind into the true wind and its Beaufort force, and draws the vector diagram behind the answer.
A portfolio piece. The interesting parts are the constraint — one HTML file, no build step, no dependencies, no network — and the verification, which is recorded in SELF-CHECK.md.
Not for operational use. This is a demonstration. It is not certified or approved for navigation, and must not be used as the source for an official record.
A ship's anemometer cannot measure the real wind while the ship is moving. What it reads is the apparent wind — the true wind plus the ship's own motion vector. An officer needs the true wind, so the ship's motion has to be subtracted back out:
true wind velocity = apparent wind velocity + ship velocity
Each term is a vector pointing the way the air or the ship actually travels, which is the opposite of the meteorological convention where a wind is named by the direction it blows from. Getting that inversion right, consistently, in both the arithmetic and the diagram, is most of the work.
Four inputs — course, speed, relative wind direction, relative wind speed — and the result updates as you type:
TRUE WIND
DIRECTION SPEED BEAUFORT
245 °T 22.4 kn 6
WSW Strong breeze
Relative wind direction can be entered either as a bearing from the bow (000 ahead, 090
starboard beam, 180 astern, 270 port beam) or in the Red/Green convention (Red 45 = 45° to
port). Both routes produce byte-identical output — that is asserted in the tests.
Two view controls, both remembered between sessions:
- North-up / Head-up — north-up fixes the compass rose so the true bearing can be read straight off it; head-up turns the ship upright to match the view from the bridge.
- Day / Night — night is dark and red-dominant, the way bridge instrument lighting works, so it does not destroy dark adaptation.
One file, no build. TrueWind.html is edited directly and opens by double-click. No
bundler, no package manager, no framework, no fonts or scripts fetched from anywhere. The
page issues zero network requests, which is asserted rather than assumed.
(The index.html in the repo root is not the application — it is a few-line redirect so the
bare GitHub Pages URL resolves. .nojekyll tells Pages to serve the files byte-for-byte
rather than running them through Jekyll.)
The maths cannot drift out of sync with its tests. All the arithmetic lives in a single
DOM-free <script id="wind-math"> block. tests.html fetches TrueWind.html, extracts that
exact block, evaluates it and asserts against it — so there is no second copy of the formulas
to fall behind, and what the tests exercise is literally what ships. If the block goes
missing, the runner fails loudly instead of quietly passing.
85 assertions, and not only fixed input/output pairs. The suite includes properties that must hold across the whole input space:
- a round-trip — subtracting the ship's velocity back out of the answer must reproduce the observed apparent wind
- a stopped-ship invariant — with no way on, true and apparent must be identical, checked across many headings
- a bound invariant — the true wind can never differ from the apparent by more than the ship's own speed, checked over 240 course/bearing combinations
Verification is measured, not eyeballed. Geometry is swept across all 360° of result bearing in both orientations; contrast is computed against WCAG thresholds in both themes; layout is checked under substituted fallback fonts and down to a 320px viewport. SELF-CHECK.md records what was checked, the defects that were found, and the limitations that were deliberately accepted.
The visual identity comes from the instrument's own world rather than generic nautical decoration. Admiralty charts print compass roses and cautionary notes in magenta, so magenta is the accent here — reserved strictly for the true wind, the one figure that gets transcribed. Day is the chart table: charted-shallow-water blue, chart paper, engraving ink, a faint ruled graticule. Night commits to red instrument illumination.
Display type is Bahnschrift, the DIN-derived grotesque of technical and transport signage. The layout is a bridge console: the answer sits in a repeater strip across the top, dark in both themes the way a real repeater reads the same in daylight and dark, rather than buried in a side column.
Two things the tool deliberately does not decide for you.
Course is used as both heading and track. The anemometer measures its bearing from the fore-and-aft line — the ship's heading — while the velocity vector properly wants course over ground. With a single course field these are treated as equal. That is correct when leeway and set are negligible, and off by roughly the drift angle when they are not.
The speed you enter decides what "true" means. Enter speed over ground and you get true wind over the ground, which is what a weather report wants. Enter log speed and you get true wind relative to the water.
Calm is reported, not invented. If the ship's motion exactly cancels the wind — steaming
at 10 knots into a 10 knot dead-ahead apparent wind — the real wind is zero and therefore has
no direction. The tool reports CALM — direction variable rather than producing an
arbitrary bearing from floating-point noise.
tests.html is a development tool, not part of the deliverable. It has to be served over
http rather than opened from disk, because it fetches the file under test:
python -m http.server 8765Then open http://localhost:8765/tests.html. The summary line reads RESULT: 85 passed, 0 failed.
Note that browsers cache TrueWind.html aggressively — append a query string or hard-reload
after editing, or you will be testing the previous version. tests.html is immune; it fetches
with cache: 'no-store'.
Current release: 1.0.0.
Semantic versioning, but read for an application rather than a library. There is no API here and nothing depends on this, so "breaking" has to be defined by the thing that actually matters for a tool whose output gets written into a record:
- Patch — the same inputs give a more correct or better-presented result.
- Minor — new capability; existing results are unchanged.
- Major — an old and a new build would disagree on the same inputs.
Two people with different builds getting different answers from identical observations is the one change that genuinely warrants a major bump.
The running version is shown in the app's own footer, not only in git — the deliverable is a single file that gets copied around, and otherwise carries no way to identify which build it is.
This was written with Claude Code, which the commit trailers already record. The verification in SELF-CHECK.md is the substance of the claim: every figure there was measured in a live browser, and it documents real defects that the review caught — labels colliding on the diagram, a rim label three pixels from clipping, a Beaufort force that could disagree with the speed printed beside it, and contrast failures in the night theme.
MIT — see LICENSE.