Tidewatch design system · icons v1.0
Organic, mechanical and interface forms drawn on one 24 px grid, at one stroke weight, with one set of terminals — so a swell, an anemometer and an export arrow can sit in the same 24 px row without one of them looking borrowed.
The only test that matters. Same optical weight across organic, mechanical and abstract; same terminals; nothing heavier or lighter than its neighbour; every icon centred on the same rule. The same sprite is doing all three rows — one drawing, two polarities, two sizes.
Stated, not implied. Anything that looks wrong when it is mathematically right gets corrected optically, and the correction is written down.
Water and sky are drawn with curves off the shared wave and one circle radius; instruments are drawn with a mast, a plumb line and graduation ticks; interface marks are drawn with the same chevron head and the same corner radii. Nothing in the third group is allowed a weight the first group cannot match.
One drawing per icon at every size — the stroke scales as a ratio of the artboard, so 96 px is a 6 px stroke and 16 px is a 1 px stroke. Two icons outrun 16 px and ship a second form; the rest do not need one.
The anemometer’s hub-to-cup gaps (1.4 units) and the rainfall’s three-drop rhythm (3.5 units) are the only places the set closes up below 20 px. The 16 px forms drop the hub and one drop, widen what is left, and keep the same skeleton so the swap is invisible in a row.
Icons earn their keep at 18–24 px next to type, in buttons, in a warning band and in a metadata strip — where optical weight against 13 px text matters more than how the icon looks alone on a plate.
Swell above threshold. 2.5 m exceeded 13:00–16:00 — mooring inspection advised.
Fifteen decorative instances, two icon-only controls named on the <button>, one named graphic. The favourite toggle is the same symbol filled by CSS — click it.
<!-- 1 · paste the sprite once, first thing in <body> -->
<svg id="sprite" aria-hidden="true">
<defs><path id="tw-wave" d="M3 0c1.5-1.67 3-1.67 4.5 0s3 1.67…"/></defs>
<symbol id="tw-swell" viewBox="0 0 24 24">
<title>Swell / sea state</title>
<use href="#tw-wave" y="6.5"/><use href="#tw-wave" y="12"/>
<use href="#tw-wave" y="17.5"/>
</symbol>
</svg>
/* 2 · one rule styles the whole set */
.i { width:var(--s,24px); height:var(--s,24px); fill:none;
stroke:currentColor; stroke-width:var(--sw,1.5);
stroke-linecap:round; stroke-linejoin:round }
.i--fill { fill:currentColor } /* filled state, same symbol */
.tools .i { --s:18px; --sw:1.5 } /* size + weight from CSS */
<!-- 3 · decorative: text already carries the label -->
<svg class="i" aria-hidden="true"><use href="#tw-swell"/></svg> 1.8 m
<!-- 4 · meaningful: name the graphic -->
<svg class="i" role="img" aria-label="Warning"><use href="#tw-alert"/></svg>
<!-- 5 · icon-only control: name the button, hide the graphic -->
<button aria-label="Favourite this station" aria-pressed="true">
<svg class="i i--fill" aria-hidden="true"><use href="#tw-favourite"/></svg>
</button>