Sailwing: wind on the map, sails in your hands
Sailwing is a sailing-trim and race-navigation tool that runs in the browser. The line on the repository is the pitch: wind on the map, sails in your hands. Source is matusso/sailwing. The site is static. Wind, land and depth are fetched in the browser.

Map and wind
The map is on the left and the trim panel on the right. It opens on the north basin of Lake Garda. Click the water and the boat moves there. Live wind for that point comes from Open-Meteo: the current 10 m wind speed and the direction it blows from, in knots, no API key, gust included in the toast. A compass dial and a slider from 0 to 35 kn set the wind by hand. Until that request returns, the page uses 12 kn from the west.
Arrow keys change heading by 2°, or by 10° with Shift. T tacks or gybes. A runs auto-trim.
Trim
The model boat is a 7 m keelboat, Sailwing 24 in the code, with 16 m² of main and 10 m² of jib. Sheets run out to 90° from the centreline. The main stops at 2°, the jib at 9°.
The panel shows speed, true and apparent wind, VMG along the wind axis, and heel. Point of sail runs from close-hauled to running, or “in irons” when you are pointed too high to move. Each sail is judged against the fastest sheet for that heading: Luffing, Trim in, Trimmed or Ease. Auto-trim searches both angles for boat speed. Keep trimmed holds the result as you steer, and GPS mode turns it on. The polar is that search from 0° to 180°, in 2° steps, with the best upwind and downwind VMG underneath.
Planning a leg
Plan takes a bearing, or you switch the map to Race track and drop marks. Drag moves one, right-click removes it, and the course stays in the browser. The first mark is the start, the last the finish.
The planner compares sailing straight with two boards whose sideways drift cancels, on the polar for the wind you have. Beat · tack is both boards upwind, Run · gybe both downwind, and Two boards splits across a beam reach. A tack costs 10 seconds and a gybe 6, for slowing, turning and getting going again. On a short leg that can erase the gain, so the split stays only when it arrives sooner. A bearing with no distance asks for at least 3% and 0.05 kn. You get a heading, a point of sail, sheet angles and a share of the time. Marks add distance, an ETA and the tack count.
Simulate sails the course with the same navigator.
GPS and the race navigator
GPS needs HTTPS: the deployed site, or npm run dev:phone. The boat follows the fix. Speed and course come from the phone when it has them, and from the track after you move further than the noise (at least 8 m) when it does not. The first fix turns on auto-trim, loads wind, and refreshes it every 10 minutes. A wake lock keeps the screen on for GPS and for a live race.
With a course set, Race calls the next mark. Sail direct when it is fetchable, or hold your tack to the layline, with a countdown and a distance. Prepare shows inside 30 seconds. Tack now or Gybe now shows inside 8, and the phone vibrates. Within 3° you are on target. Out to 45° the hint is head up or bear away, and beyond that a heading. Target speed sits beside yours, or beside speed over ground, with laylines and ETAs to the mark and the finish.
A mark is rounded within 30 m, widened to the GPS accuracy when the fix is coarser. A detour waypoint counts within 20 m.
Land and depth
Routes stay off land and out of water shallower than draft plus 0.5 m, plus a margin off the shore and off shoals. Draft defaults to 1.5 m, so the floor is 2.0 m of water, and the margin defaults to 40 m. Both sit under Plan → Safety.
Land is OpenStreetMap water polygons via OpenFreeMap vector tiles: worldwide, lakes included, piers counted as land. Depth is EMODnet mean bathymetry, about 115 m cells, European seas only. Lakes, other seas, and a depth request that fails leave the shore margin on its own. Red on the map is too shallow. Amber is inside the margin.
Routing searches a grid and costs each step by sailing time, then pulls the path tight. Tacks go on the side with water, and a channel finer than the grid is short-tacked and flagged. While racing, the navigator steers for the next detour waypoint, re-plans if you stray, and vibrates when land or shallows are within 90 seconds, or 150 m if you are slow.
The model
src/physics.ts is a velocity-prediction model of that 7 m boat. Speed is where drive equals resistance. The curves are tuned for plausible numbers, and they describe this model boat only.
Sails use lift and drag. Below 4° angle of attack the sail flogs and lift is zero. Flow stalls around 20°, then separates. Peak lift coefficient is 1.5 on the main and 1.6 on the jib. Past 135° of apparent wind the main blankets the jib, down to 35% of its drive dead downwind.
The hull has viscous drag on 11 m² of wetted surface, wave drag that climbs with the seventh power of speed over hull speed (1.34√LWL, LWL in feet), and induced drag from a keel of 1.6 m effective span. Leeway drag is capped, because at low speed the boat slips sideways. Heel is the heeling moment against 8500 N·m, centre of effort 4 m up, and drive falls with the square of the cosine of heel.
Auto-trim walks the two sheets by coordinate descent, and that polar is what gets sailed.
How it is built
Vite and TypeScript, Leaflet for the map. npm test is Vitest, covering physics, the navigator and routing. npm run build typechecks and writes dist/.
Cloudflare serves that build as Workers static assets. wrangler.toml attaches sailwing.burso.eu as a custom domain, which needs the burso.eu zone on the same account. Pushes to main are built and deployed by Cloudflare’s Git integration (npm run build, then npx wrangler deploy). Other branches get previews. GitHub Actions runs the tests and the build. Hashed files under /assets are cached for a year from public/_headers. The same build is at sailwing.burso.workers.dev.
npm install
npm run dev # http://localhost:5173
npm test
npm run buildTry it at sailwing.burso.eu. The model is src/physics.ts.