← One Line  ·  field notes

How to walk seven miles
without lifting the pen

The working notes behind One Line — one SVG path element, art-directed point by point, that draws itself as you scroll and becomes every scene in the story.

I · The concept

One Line is a scroll-told short story about an old mapmaker drawing his coastline for the last time. The conceit: the entire illustration layer is a single, unbroken <path>. The same stroke that underlines the title becomes the roofline of his house, a coiled mooring rope, three gulls, the cliff strata, the lighthouse, a squall, and finally the flat blue horizon — where its tip comes to rest as the period of the last sentence.

The aesthetic school is ink-on-paper book illustration — continuous-line drawing (think Picasso's one-line animals, Saul Steinberg's wandering nib) crossed with literary print design: warm cream, a serif with real italics, drop cap, roman-numeral chapters, one restrained accent color for the sea.

II · One path, many scenes

There is no trick that generates this. The path was budgeted like a book: a tall viewBox="0 0 1000 11600", a scene ledger, then coordinates sketched by hand so each scene lives in the left band (x ≤ 500) while text sits to the right. Between scenes the line simply walks — loose S-curves down a rail near x = 200 that read as the path underfoot.

y rangescenedrawn with
348Title underlinethe path's first segment, under the SVG <text> title
1310–1690Houseground → wall → roof slope interrupted by a chimney → eave → wall
2560–2960Rope coilten concentric semicircular arcs, tail crossing out
3720–3820Gullspaired cubic "m" strokes at staggered heights
4670–5480Cliffsorthogonal h/v ledges stepping down to a beach
6500–7180Lighthouseup one tapered side, gallery, lantern dome, down the other
8120–8790Squallcubics whose control points cross, so crests loop like breaking waves
9760–11014Horizon & periodripples decay into a flat run, inked sea-blue, ending at a full stop

The coil: semicircles with a rule

A hand-drawn spiral sounds hard until you notice that alternating semicircular arcs around a shared center are a spiral. Each arc's endpoints must be exactly 2r apart on the same horizontal line, and every arc keeps sweep=1 so the pen keeps circling the same way:

<!-- outer to inner: 172, 150, 128, 108, 90, 74, 60, 48, 38, 30 -->
A 172 172 0 0 1  98 2790   bottom half:  442 → 98   (344 = 2×172)
A 150 150 0 0 1 398 2790   top half:      98 → 398  (300 = 2×150)
A 128 128 0 0 1 142 2790   bottom:       398 → 142  (256 = 2×128)
… and so on, tightening toward the center
Every arc pair shares one baseline (dashed). Alternate ends, shrink the radius, keep the sweep — a nautilus appears.

The gulls: the honest "m"

Each gull is two short cubics — a steep wing up-and-over, a cusp, and a second wing — connected to its neighbors by long shallow glides. The trick that makes them read as birds instead of waves: keep the glide sag much flatter than the wing peaks, and stagger the gulls' baselines so they occupy sky, not sea.

c 12 -30 26 -34 37 -9    wing one (sharp peak)
c 11 -27 27 -23 39  3    wing two (cusp between)
c 30  26 72  16 102 -30  long shallow glide to the next, higher bird
Two cubics, one cusp. The dashed lines are the control handles doing the flying.

The squall: loops from crossed handles

A breaking wave is a cubic whose control points cross over each other — the curve is forced to loop back through itself like a curling crest. Four cubics make one wave module: climb, curl back, sweep under the curl (this crossing draws the loop), then run along the trough.

III · Drawing on scroll

Dash-offset as a pen

The classic line-drawing technique: set stroke-dasharray to the path's total length so the whole line is one giant dash, then slide stroke-dashoffset from L to 0. Whatever fraction has slid past is "inked."

const L = path.getTotalLength();
path.style.strokeDasharray  = L + ' ' + L;
path.style.strokeDashoffset = L;              // blank page
// each frame:
path.style.strokeDashoffset = L - drawnLength;

One landmine worth knowing: don't combine this with vector-effect: non-scaling-stroke. Chrome computes dash metrics for non-scaling strokes in screen space, so your user-unit dashoffset silently desyncs from getPointAtLength. One Line scales stroke-width in JS instead, to hold a constant ~2.5px nib at every viewport.

Inverting scroll into length

Mapping scroll to length linearly fails here, because the line's length is not evenly distributed down the page — the coil packs ~2,800 units of ink into 400 vertical units. So the page samples the path once, records each sample's y, and forces the sequence monotonic with a minimum slope. Binary-searching that "anchor" array turns the y the reader is looking at into how much line should exist — and dense scenes (coil, lighthouse) draw themselves in a quick flourish while the scroll passes them.

let ay = 0;
for (let i = 0; i <= N; i++){
  const p = path.getPointAtLength(L * i / N);
  ay = Math.max(ay + 0.5, p.y);   // monotonic, min slope 0.5/sample
  anchors[i] = ay; lens[i] = L * i / N;
}
// scroll → tipY → binary search anchors → lerp into lens[]

The living nib

The drawn length lags its target through a frame-rate-independent lerp, so the pen accelerates and settles like a hand rather than a scrollbar. A small circle rides the tip via getPointAtLength — and because the path's final coordinate sits just after the last sentence (an SVG <text> with text-anchor="end" pinned to it), the resting nib is the story's full stop.

const k = 1 - Math.pow(0.94, dt / 16.7);      // ~6%/frame at 60fps
cur += (target - cur) * k;
const p = path.getPointAtLength(cur);
nib.setAttribute('cx', p.x); nib.setAttribute('cy', p.y);

One accent, one gradient

The rule said one path element — so the sea-blue ending is not a second path. It's a vertical linearGradient in userSpaceOnUse units applied to the stroke: pure ink until y ≈ 9,160, blending to sea-blue as the storm dies into the horizon.

IV · How it was made

Built by Claude (Fable 5) writing vanilla HTML, CSS and JavaScript by hand — no frameworks, no build step, no drawing software; every coordinate in the path was reasoned out on graph-paper logic and corrected against screenshots. It is one of 25 sites in the Fable Showcase, each built in a wildly different visual language. The whole set lives at fable-25-dhb.pages.dev.

Respecting prefers-reduced-motion: the line renders fully drawn, every passage is visible, and the nib rests at the period — the finished map instead of the walk.

V · Steal this

  1. Budget a long path like a book. Give your viewBox a chapter ledger (y-ranges per scene) before writing a single coordinate. Scenes stay in one horizontal band; text gets the other. Composition problems disappear when the grid is decided first.
  2. Spirals are just semicircles with a rule. Alternating A r r 0 0 1 arcs whose endpoints sit 2r apart on a shared baseline coil perfectly — no math beyond subtraction.
  3. Never pair dash-drawing with non-scaling-stroke. Dashes go screen-space in Chromium and your pen tip drifts off the ink. Scale stroke-width in JS instead.
  4. Invert scroll through a monotonic anchor array. Sampling getPointAtLength once and binary-searching a min-slope y-envelope lets a wandering, backtracking path still track the reader's viewport.
  5. Let the lerp be the personality. cur += (target - cur) * k with a frame-normalized k is one line of code, but it's the difference between a scrollbar and a hand that hurries, hesitates, and catches up.