SVG, presets & building blocks

SVG without React

import { writeFileSync } from "node:fs";
import { toSvg } from "linescape";

const svg = toSvg(
  [{ path: { through: "center", angle: -12 }, width: 220, paint: "#ffe3d3" }],
  { width: 1440, height: 900 },
);
writeFileSync("hero-lines.svg", svg);

One <svg>, decorative (aria-hidden), with coordinates rounded to two decimals: the same lines at the same size always give the same bytes. Animations are included as CSS and SMIL inside the SVG.

OptionDefaultMeaning
width, heightrequiredThe element's size, in px.
idPrefix"linescape"Prefix for ids, class names and keyframes. Give each SVG its own when inlining several into one page.
precision2Decimal places for coordinates.
motiontruefalse leaves out undulation (SMIL), which CSS can't switch off for reduced motion.

render() takes the same arguments and returns the SVG as a plain tree of nodes ({ tag, attrs, children }), which serialize() turns into a string. <Linescape> renders the same tree as React elements, so both always draw the same thing.

Presets

Hand-made starting points. Spread one and change what you like:

import { presets } from "linescape";

<Linescape line={{ ...presets.swoosh, paint: "#0a7" }} />

band, swoosh, underline, ridgeline, swirl, stripes, ribbon, coil, zigzag, brush, frame, orbit, parallel, current, scribble. See them all on the home page.

Suggestions

import { suggest } from "linescape";

suggest({ seed: "hero" });                                   // any kind
suggest({ seed: 7, kind: "swirl" });                         // band, sweep, squiggle, loops, swirl,
                                                             // ridges, stripes, brush, coil, frame
suggest({ seed: 8, keep: { path: myLine.path } });           // keep what you've settled on

suggest() generates a complete line from a seed: a starting point rather than a blank page. The result is a plain, static spec; print it, paste it and change it. Nothing is random when a line renders. The same seed always gives the same suggestion. The playground's Shuffle button is built on it, with locks for the parts to keep.

resolve()

resolve(line) fills every default and validates, returning the complete line as plain data. It's what the renderer works from, and what the playground edits. Invalid specs throw an error starting linescape: that names the field.

Building blocks

For drawing lines your own way: buildBackbone() (the path, measured by arc length), buildCurve() (with patterns and features), emit() (fitted path data), resolvePoint() and resolveLength() (positions), and parsePath().

Stability

The same spec and element size always produce the same output, on the server and in the browser. From 1.0, the same spec, size and major version produce byte-identical output: any change to generated output, presets and suggest() included, is a major release. Before 1.0, a minor version may still change output; patch versions never do.