atelier

Catalog / Technique

Generative mark

Use when a page wants a drawing made by a program rather than by hand or by shader: an emblem per project, a divider, a figure beside a heading, a share image. Covers making it at build time and shipping an SVG, seeding from the slug, making marks differ in kind, writing the smallest path, where a mark may sit, and drawing it on once.

Loads as
technique-generative-mark
Written for
An agent, to follow

Generative mark

A drawing made by a small program: lines that follow a field, the contour lines of a made-up terrain. The program runs once, in the build, and what ships is an SVG. On the page it is a picture: no script, no frames, no GPU, sharp at any size, printable. It is the one creative-coding family whose rest state is the whole thing.

When to use

  • An emblem per project or per track, seeded from the page’s slug, so every page has a mark of its own and both languages share it.
  • A section divider or a quiet figure beside a heading, where the task is scanning and the page can afford a few kilobytes of picture.
  • The still that stands in for a shader where there is no GPU.
  • A share image, rendered to a bitmap at build time.

Do not use it for:

  • A ground under body text. Thin lines at the spacing of small type destroy legibility. Tastes is firm and right.
  • Anywhere a real image of the project exists and would say more.
  • Beside a real chart or map, where fake cartography reads as data.
  • Generating in the browser on a reader’s page. It costs the script, the main-thread time and determinism across engines, and buys nothing a build cannot give. A lab page, where the reader turns the dials, is the exception.

Rules

  1. Build time first. The generator is called from a component script or a build step and is stripped from the page. What reaches the browser is the SVG.
  2. Seed from the route slug. Not the title, not the locale. A slug with Turkish letters hashes differently from its ASCII spelling, and both language versions of a page must get one mark.
  3. The seed chooses structure, not only noise. Eight marks that differ only in their noise seed read as eight prints of one thumb (seen, 2026-10-06). The seed must reach the dials that change the silhouette: frequency, turns, density, the kind of field. Better still, let a true fact choose the kind, on the consumer’s side: the track picks the field, the year picks the density. Then the mark encodes something and can be said to.
  4. Simplify, relative coordinates, one decimal, one path. The same drawing went from 40 kB to 5 kB gzipped by those four (measured). Simplification is the largest saving and SVGO does not do it. One path with many subpaths also removes dozens of nameless graphics elements from the accessibility tree and lets the whole mark draw on with one animation.
  5. fill="none", stroke="currentColor", aria-hidden="true". The mark takes the colour of the text around it and says nothing to a reader. For a second colour, a class on the root and a rule in the skin; the generator stays ignorant of tokens.
  6. Generate for the display size. A stroke scales with the drawing; a mark designed at 520 px and shown at 150 px washed out, and with non-scaling-stroke it became a solid mass (seen). One unit is one CSS pixel at the size the mark is shown, the spacing is chosen in those pixels, and a spacing under about 6 stops reading as lines.
  7. A mark has its own box. A mark in a box beside a heading is a drawing. The same lines bleeding off every edge are wallpaper. A shape, a margin, a place in the layout.
  8. Inline when small and alone, a file when large or many. Up to about 3 kB gzipped per mark and 8 kB per page inline; above that, or a listing with an emblem per row, a file through img with alt="", themed as a mask-image over currentColor if it must follow the text colour.
  9. Pin one mark’s hash in a test, or commit the output. The noise and the tracing use Math.sin, Math.cos and Math.atan2, whose last bits are not promised across engines. A Node upgrade can reroute a line. Pinned, the change is noticed; committed, the build does not depend on the generator at all.
  10. Clamp every dial, and cap the walk. A spacing of nearly zero or a box of a million units must return, not hang a build.
  11. If it draws on: once, linear, nothing hidden outside the animation, not at all under reduced motion. The dash exists only inside the animation, with a backwards fill for the delay; before and after it, the mark is as styled. One mark a page at most; a listing of drawn-on marks is a dozen animations at once.
  12. Do not claim a mark encodes data unless it does. A mark whose kind follows the track is fine unexplained, since nothing is lost by missing it. A mark presented as data needs the data as text beside it. Information is never hidden in something aria-hidden.
  13. What reads as craft. One stroke weight, round ends. Even spacing that comes from the method. Paper showing: tone from line density, never from opacity or fill. A visible rule the reader can sense. A frame. A reason. One ink, perhaps one line in the accent.
  14. What reads as stock. Noise-angle lines from random starts, crossing and clumping. Contour lines at low opacity behind a headline. Gradients, glow, blur, several colours, varying stroke. A loop: a plotter drawing is finished. The same mark on every page. Wobble filters and paper textures: ink that skips and pools is what a screen cannot fake, so do not try.

Decisions

Question Choose Because
Where a generator lives One recipe, two ways in: generate for a build, a thin mount for the lab Tuning by eye is the work and the catalog is the tool; no check or stage code changes
What generate returns A string It is what a build inlines
Committed, or made on every build Made on every build, one hash pinned Committing adds generated files to review; pinning notices a change
Does the seed choose structure Yes, a toggle, on Seed-only marks are indistinguishable
Line or fill Line Fills are larger, need a palette, and are a different taste
Still, or drawn on Still; drawing on is a separate recipe for one mark a page Drawn-on marks in a listing are a dozen animations
Pen speed One speed: every line at the same rate, short ones finish first It is what a pen does and it needs one element
Index contours Off, a toggle Varying stroke leaves the taste; it is real cartographic convention
Inline or file Inline for one mark on a page; files for a listing Tens of kB in a listing’s HTML otherwise

Tuning

Dial Start Range One end The other
Spacing, in CSS px at display size 10 6 to 24 A texture; over 10 kB A few stray lines; reads as unfinished
Test distance, as a share of spacing 0.5 0.3 to 0.9 Long lines that pinch together Short broken lines, gaps
Field frequency, cycles across the width 1.2 0.5 to 3 One broad sweep Busy curls, many short lines, a bigger file
Turns 1 0.35 to 2.5 Nearly parallel, combed Tight whorls, lines crowd into sinks
Octaves 2 1 to 3 Very smooth Wobbling lines
Minimum extent, in spacings 2 1 to 6 Stubble Holes where short lines were
Contour levels 12 3 to 30 A few rings A dense map
Stroke width, in CSS px 1 0.75 to 1.5 Disappears on a 1x screen Stops reading as a pen
Simplify tolerance, in units 0.25 0.1 to 0.6 A larger file Visible corners unless smoothed
Draw-on duration (ms) 1,600 600 to 4,000 A flick The reader waits for a picture

Ranges are estimates from stills; halving the spacing doubles the lines and the bytes.

Rest state

The mark. The same without script, under reduced motion, without WebGL, in print, and after a draw-on has ended. A .svg file that fails to load is an empty box with alt="", announcing nothing.

Cost

  • To build: the algorithms took an hour each; every later minute was dials. Tuning is the work.
  • To run: in the browser, parsing and one paint. On the page, 1.5 to 6 kB gzipped a mark at the densities that look right, which is a small image. In the build, 5 to 30 ms a mark on a laptop. A draw-on repaints the path on the main thread each frame for its duration, which is small for one mark and unmeasured.
  • To the reader: nothing, unless it sits under their text.

Checklist

  • The budget check passes, and the recipe’s own test pins one mark’s hash and holds the default mark under a stated size.
  • npm test: the same seed gives the same bytes; absurd dials return in bounded time.
  • npm run check:browser: each box on the stage holds one svg with one path, sized to its box, hidden from readers; a changed seed changes every mark; a draw-on held mid-way is half drawn, and finished leaves no animation and no style.
  • Looked at on the stage: every version, three seeds each, at the emblem, the divider and the figure; and once at phone size.
  • The mark is beside the text on the fixture, never under it.
  • Run once in Safari and once in Firefox before it is marked passed.

Recipes

  • flow-mark: evenly spaced streamlines of a seeded field, as one path; the seed chooses the kind.
  • contour-mark: the contour lines of a seeded height field, joined without comparing coordinates.
  • draw-on: the strokes of any inline SVG drawn in once, on first view, with nothing left behind.

Sources

  • tyler-hobbs-flow-fields: the method, and the warning that noise mapped to angle is overdone
  • jobard-lefer-streamlines: evenly spaced streamlines, the two distances, the collision grid, seeding from what is drawn
  • anvaka-streamlines: a JavaScript implementation of the same method, read for its parameters, not copied
  • wikipedia-marching-squares: contour extraction, the sixteen cases, the ambiguous pair
  • wikipedia-ramer-douglas-peucker: polyline simplification
  • bridson-poisson-disk: even random points, for a stipple not built here
  • bryc-prngs: small seedable generators and seeding from a string hash
  • mattdesl-pen-plotter: the plotter as a taste, and what physical ink adds
  • github-identicons: the production precedent for a mark from a hash of an identifier
  • wai-decorative-images: a decorative image takes an empty alt
  • roselli-dont-split-words: dozens of nameless graphics elements are the split-text hazard in another material
  • mdn-element-animate: the draw-on’s one call
  • mdn-prefers-reduced-motion: decided in script, in one place
  • astro-docs-components: a component script runs at build time and is stripped from the page
  • bcd-svg-path: path everywhere; read for pathLength, which it does not list
  • bcd-svg-global-attributes: stroke-dasharray and stroke-dashoffset everywhere
  • bcd-mask-image: theming a file through a mask