← Work

Signal Flags

An interactive signal translator — type any text and hoist it as International Code of Signals flags, flip it into Morse, semaphore figures or the spoken alphabet. Every symbol drawn in code from its formal definition, zero image assets.

Live 2026 AstroSVGVanilla JS

The idea

Before radio, ships talked by stringing colored cloth on a halyard. The International Code of Signals assigns a flag to every letter A–Z, and each one carries its own meaning when flown alone — Oscar means man overboard, Uniform means you are running into danger, and Quebec is how a ship says I’m healthy, let me in. I wanted to type any text and see it dressed as a hoist, so I built it into this site. Then it grew: the same message now also renders as Morse, semaphore figures and the spoken alphabet, and the translation runs both ways.

Try it

The full alphabet

Every letter flag with its single-flag meaning at sea. Click any tile to add it to your message — building text out of flags is the reverse translation.

Numeral pennants

Digits don't get rectangles — they hoist as long tapered pennants, which is how ships spell coordinates, times and positions.

Semaphore

The same alphabet, spelled with a flag in each hand. Each arm points to one of eight positions around the body; a few letters bring a flag across the chest. Click to add.

How it works

There are no images anywhere in this project. Every flag is an SVG generated at build time from geometry, and the geometry comes from each flag’s heraldic blazon — the formal description heralds have used since before ships had radios.

"bravo zulu" ──▶ upper() ──▶ tokens ──▶ tiles ──▶ word groups
                              B,R,A,V,O     ▢▢▢▢▢ ▢▢▢▢
                              Z,U,L         gap = space
                                        unknown chars get a
                                        dashed tile, never dropped
  • Swallowtails (Alfa, Bravo) are a single path with the notch cut 36% into the fly.
  • Yankee is “bendy sinister of ten or and gules”: ten diagonal bands running upper-fly to lower-hoist. That’s computed by parameterizing stripe boundaries along t = x + y and intersecting each band with the flag rectangle.
  • Numerals are pennants, not rectangles. Digits 0–9 hoist as long tapers, so every internal band, roundel and saltire has to follow the slope: a band from x1 to x2 is a polygon whose height shrinks by 35·(x/100) per end. No clip paths anywhere — all geometry is computed.
  • Semaphore arms point to eight 45° positions around the body, and a few letters bring a flag across the chest. The angle table was cross-checked against the reference figures rather than trusted from memory.
  • Zulu is four triangles meeting at center per saltire; Oscar splits on the diagonal with yellow in the lower-hoist triangle. Getting these orientations right meant pulling the reference SVGs and reading the actual paths instead of trusting memory or random chart images.

Engineering notes

  • Build-time generation. All 36 flags render from one data table of blazons during the Astro build. The client receives finished SVG strings as JSON — no runtime geometry math, no libraries.
  • Reverse translation. Morse and the spoken alphabet auto-detect their own input: paste ... --- ... or alfa bravo and the tool decodes instead of encodes. For flags, the chart tiles append letters and any hoisted tile can be clicked off — flags back into text, one click each.
  • The translator is the only JavaScript. It re-renders on every keystroke by cloning prebuilt SVG strings from a JSON island. The reference charts are fully static HTML, and ?q= links prefill the message — /work/nautical-flags/?q=bravo zulu is a shareable hoist.
  • Spaces become gaps, not missing flags. A real hoist separates words with distance on the halyard, so the renderer groups tiles into words instead of collapsing whitespace.
  • Unknown characters fail visibly. Punctuation gets a dashed placeholder tile showing the original character. Silent dropping is how you end up flying a message you didn’t mean.
  • Signal colors ignore dark mode. The page chrome follows the theme, but the flags stay fixed-color — a red Bravo at sea doesn’t dim because you prefer dark interfaces. White fields get a hairline outline so they survive light backgrounds.