01Palette
Every colour is a dye that Shetland actually used. Nothing decorative was invented.
- Undyed wool
#EDE6D8— the ground everything sits on; the warp, the page.
- Indigo
#27418C— vat-dyed working blue; interaction and links.
- Madder
#A63A2B— root-dyed red kept scarce: eyebrows, hints, overshot floats.
- Peat
#332B24— near-black brown for text and the darkest fleece.
02Type
handsel
Fraunces (variable). The optical-size axis goes soft and wonky at display sizes —
the hero sets alternating letters with WONK 1 in indigo, a nod to hand-spun irregularity.
Counters and stats use it at text optical sizes.
Throw the shuttle
Karla. A plain-spoken grotesque for instructions, labels and controls — workshop signage rather than gallery captions. Wide tracking in uppercase for rail labels.
03Techniques
- Canvas 2D weaving engine — the signature. No libraries.
- Seeded sprite atlas for thread rendering (mulberry32 PRNG, cached per wool/orientation/variant).
- Double-buffered cloth: each pick scrolls the drawdown one row and stamps the new pick on top.
- Pointer-capture shuttle drag with a commit threshold; a state machine (fly → beat → shed) choreographs the pick.
- Procedural woven divider bands and draft previews rendered by the same engine.
- All-CSS entrance choreography gated on
document.fonts.readywith a timer fallback.
The loom is real in the textile sense: a draft is stored as a threading (which of four shafts carries each warp end) and a liftplan (which shafts rise on each pick). For every cell the engine asks one question — is this end's shaft in the current lift set? — and layers a warp sprite over a weft sprite or vice versa. Twill's diagonal, herringbone's chevron and Monk's Belt's floats are never drawn; they fall out of that single rule, exactly as they do on wood. Houndstooth proves the point by changing nothing at the shafts — only the colour order of warp and weft. Each committed pick also lands in a liftplan chart on the right and winds onto a cloth beam whose radius grows with your weaving.
04Iteration log
Pass 1 — Craft
- Found the four draft previews and the three woven divider bands rendering blank: the static
renderer called
drawRowwith a missing argument, so every cell painted at zero height. Fixed — the bands now weave houndstooth, twill and overshot between sections. - The hero's
paddingshorthand was silently zeroing the wrap's side gutters — at 375px the wordmark's h sat clipped against the viewport edge. Split into longhand top/bottom. - Canvas labels ("Threading", "Liftplan") ignored the bench's padding and collided with the warp; repositioned, and Liftplan set vertical along the chart it names.
- Simplified the draft-change shed transition to reuse the existing state machine.
Pass 2 — Depth
- Bare warp now renders through the unwoven drawdown, so a freshly cut loom shows a naked warp under tension instead of a void — and "cut off the cloth" reads honestly.
- Two quiet chevrons sit beside the shuttle until your first pick — the only tutorial the page has.
- Second-read detail: cut off a cloth of a hundred picks or more and the loom tells you what handsel means. Milestone messages surface at 24, 60, 100, 160, 240 and 360 picks.
- Cursor commits to
grabbingfor the length of a drag.
Pass 3 — Hardening
- Headless QA at 1440 / 768 / 375: zero horizontal overflow, zero console errors; drag threshold verified (a short pull returns the shuttle without committing a pick).
- Reduced motion: entrance and shed animations collapse, throws commit instantly to a settled frame; the loop pauses when the tab is hidden; DPR capped at 2 with debounced re-layout.
- Chanel rule: removed the third canvas tag ("Shed") — the open shed explains itself.