From head to sheet

Nine to thirty heads on one surface, each in its place, each at the same size – and any one of them large in the middle at any moment. That is less administration than it sounds: the sheet is a grid, a scale, an order and a number.

The grid

The column count jumps at four fixed thresholds – 560, 900, 1300, 1800 pixels. Not a formula but a table: a formula would eventually hit a width at which it proposes two columns or eleven, and neither is a sheet. The rows then follow from a single condition – the cells should stay roughly square.

When in doubt, one column more rather than large heads with air around them. A head standing alone in a flat tile on a wide screen looks lost; six small heads in a row look like a sheet.

The seed of a cell is blattSeed + r · spalten + c. Neighbouring cells therefore have neighbouring seeds – exactly the case for which the generator in the chapter Randomness throws away eight draws. Without that warm-up there would be rows of relatives here.

Space requirement against platzIn

The style says what a head needs (raum(dna)), the cell says what it can offer (platzIn). Both compute in head units, so that they are comparable at all.

Downwards nothing is measured: the name stands there, and no hairdo reaches that far. Upwards and sideways it does – the afro is round and distinctly wider than the skull, the knot of a bun sits on top, and a hat brim juts out past every head.

If a hairdo demands more than its cell can offer, it is not that one head that gets smaller but the whole sheet. Nine heads at nine scales look like a fault; a slightly smaller sheet looks like intent.

Usually the result is 1. The cell is dimensioned so that the usual hairdos fit; the factor only takes effect for the largest afro spheres – and then for everyone.

The drawing order is part of the correctness

A head is not assembled but stacked. There is no occlusion computation, no depth buffer and no masks between the strokes – every stroke may paint over what lies beneath it. The order is therefore not a sequence but a statement about what lies in front of what. (The one exception stays inside a stroke: the cheek arc clips the eye it belongs to, see Features.)

For a long time it was written down nowhere. It was the order of seventeen calls in zeichneGesicht, and whoever moved one of them two lines up moved an occlusion – with nothing telling them they had just touched a contract. Today every stroke names its layer, and the layers stand as a list:

The strokes are collected, sorted stably by layer and then drawn. Stable means: within a layer whoever was collected first still comes first – otherwise the contour would lie under its own surface and be gone.

LayerWhy there
halsRight at the back. The head sits on it and covers the join.
hinter-dem-kopfBraids, the afro sphere. They are drawn before the head surface and half covered by it – that is the whole trick.
ohrenThe far ear sits at the side of the skull, so under its surface.
kopfThe lid over everything at the back – and the contour above it, or the edge of the surface would eat into the line.
zugewandtes-ohrThe near ear lies in front of the cheek: when the head turns, its anchor travels behind the silhouette, and the head surface bit off its root. Which ear lands here is said by its normal – not by its side in the picture.
hautCheeks, eye rings, hatching, forehead lines: colour and pale secondary lines on the skin, under all the strokes.
bartUnder the nose, in the order too. Whatever overlaps the nose, it covers.
fernes-augeThe eye on the far side. Which one that is is said by its depth – not by the sign of the rotation.
naseIt has volume and stands free – so it covers the eye behind it.
nahes-augeDrawn in front of the nose, it would erase this one too.
mundOver the beard, under the rest.
frisurHair and headwear: the shell lies on the skull, not under it.
aufgesetztEarring, plaster – on the head, but not part of it.
sternaugenThe pure star eyes (funkeln) and nothing else: not an eye shape but a mark on the head – bigger than any eye, in its own colour. Under a hairdo it would sit there clipped. The brow stays down with the face and is covered by the star.
davorIn front of the face and therefore above everything – including the cap, which sits on the skull and not in front of it.
The nose that erases the eye

Symptom: With a turned head a piece is missing from the near eye, as if it had been cut into. Cause: The nose is not a line but a shape with a surface. Drawn in front of both eyes it covers not only the far one but the near one too. Fix: Three layers interlock: far eye, nose, near eye. Which of the two is the far one is decided by the depth of the eye fields, not by the sign of the rotation – and a test holds it fast: over four hundred seeds the eye drawn last must be the one with the larger z.

The close-up is a number

No second element, no modal, no second canvas. fokus.t runs between 0 (the head sits in its cell) and 1 (it stands in the middle), and the location is computed from it.

Because every frame is redrawn anyway – the heads follow the pointer – this is the simpler way and incidentally the smoother one: t approaches its target by a fixed share of the remaining distance each frame. That is "fast away, soft in" in both directions, and it can be reversed mid-movement without anything jumping.

The veil over the grid is paper-coloured, not grey. The sheet steps back instead of a curtain falling in front of it – and at the end it covers completely, so that no grid shimmers through behind the large head.

The workshop: one head, sixteen dice

There is a way out of the large view. On /werkstatt.html?kopf=118317 the same person stands alone on the paper, with one die per feature beside them: the eyes without the nose, the hairdo without the collar.

That this works at all is not a new computation but an old decision: zeichneKopf takes a DNA and not a seed, and the style's head-data cache hangs off the DNA object, not off its seed. A hand-built DNA with a changed feature therefore gets its own shell, its own palette and its own space requirement.

What is new is what an edited head IS. On the sheet a seed is the address of a person; re-rolling one feature leaves that address. A Wurf (a throw) is therefore the seed plus the little that deviates from it – and together they fit into one line again:

?kopf=118317&auge=stern&haar=igel&farben=2

Two kinds of deviation, written down differently. Features have a vocabulary, so for them the value itself is written. That is readable, short and stable: a rolled feature is never recomputed and therefore cannot change behind your back when another die falls next to it. Skull shape, proportions, pose and colours have none – eight decimal numbers in an address would be unreadable and broken at the first rounding. For them the address says which throw counts; the value comes from strom(seed, 'kopfform#3').

A feature, though, is more than its word. For every word the style rolls a handwriting – how many spikes the hedgehog has, which brim the hat, how full the bob lies – and that should fall anew with every throw. If it hung off the word alone, this head would own exactly ONE hat, and the die would show it again and again: not a die, a switch. So behind the word, when needed, stands the edition: haar=igel.3 is the hedgehog from the third throw on the hairdo. The first edition carries no counter, so every link from before shows the same head as before.

One die, one feature. What stands next to it stays – including the style's handwriting. That is why the eye die rolls shape, pupil, size difference and star spikes together (see streams), and why fresh proportions are discarded and drawn again until their layout fits the features already chosen.

There is one exception, and it stands here because it is one: the tone of the pure star eyes. It comes from a scale of its own and sits in none of the five colour genes. It therefore listens to TWO dice – the eyes', because it belongs to the star, and the colours', because on such a head it is the loudest colour in the picture. Once the colours have been rolled, that tone survives a new star shape.

The challenge is not the computation but the AMOUNT. A head consists of more than forty numbers and twelve features; as a list of sliders that would be a cockpit. Three decisions keep it small.

DecisionEffect
Sixteen groups instead of forty values Rolling happens on the level a person thinks in: "eyes", not "eye shape, pupil size, jitter, star spikes"
The picture is the menu Six places on the head roll their feature: eyes, nose, mouth, cheeks, hairdo, collar. The anchors come from the layout and run through the same view as the drawing – a guessed second geometry would be wrong on every other rotation
The list is the second, full hand Every group, every value in plain words, and the path for keyboard and screen reader – for both of them the canvas is an empty rectangle

Not on the head are brows, marks, glasses, beard and trinkets: a hit zone for the brows NEXT TO one for the eyes would be a guessing game, since a tenth of a head lies between them. The headwear is missing for the same reason – it sits where the hairdo sits.

A die with nothing to offer is switched off and says why: over a hair mass every headwear except "nothing" is blocked. The catalogue supplies the reason (both directions), the page supplies the sentence. The draw happens WITHOUT the value that currently stands there – a die that shows the same thing again looks broken – unless that value has a visible handwriting: a star, a hat, a hairdo may come back, because in a new edition they are a different star, a different hat. Which values those are is a list in core/wurf.ts. And it draws FLAT: the catalogue's weights are made for the sheet, where one head in nine should wear a hat and "none" weighs 8 for glasses, 12 for beard and 9 for headwear. Whoever presses the beard die in the workshop wants to see a beard; with the sheet's weights three throws in four would be a throw into the void. Here every value counts the same; only the fit to the face's lay stays preferred.

The way back leads to the sheet you came from: the link from the sheet carries its seed along (&blatt=…), the workshop keeps it in its address and hands it back with the head – /?blatt=…&kopf=… builds exactly that sheet and opens the head large right away. The switch between the two pages is a cross-fade (@view-transition), and so that it fades onto a finished picture, both pages load their script render-blocking in the head.

And the language? The docs carry it in the PATH – /docs/ is German, /docs/en/ English – so that a link arrives at its recipient the way it was sent. A document has an edition. The workshop is not one: its address describes a HEAD, and that head is the same in every language. So here the browser decides – German for whoever has German set, English otherwise. To pin it down, append ?sprache=de or ?sprache=en; then it stands in the address and travels with it.

The sheet: a piece of paper you can take with you

On the grid the paper is the WINDOW. Nine heads spread across whatever area happens to be there; a narrow window gives a different grid than a wide one. That is right for something you leaf through.

In the workshop a single picture comes into being, and one that leaves the browser. A picture whose format comes from the window is a screenshot once downloaded: tall on a phone, wide on a desk, and never what you saw. So the sheet has a format – DIN, portrait, height = width · √2 – and units of its own. It is 600 units wide no matter how large it appears; the caller sets the scale on the surface before drawing.

One function for screen and file. The screen calls it with the scale of the display, the saved file with a larger one – 1500 px wide, so f = 2.5. Two drawing paths would drift apart sooner or later, and precisely where nobody looks: in the downloaded picture.

Inside it the head still hangs on the style's space requirement – a hat needs more room than a bare skull – but no longer on the window size. The scale is therefore computed once per throw and not once per frame.

The name stands in the bottom margin, small, like a signature. It hangs on the SHEET, not on the chin: a name that travels with the hairdo sits lower under a hat than under a bare skull.

Edge and shadow are drawn – with the same pen as the chin and the same press that also sets the skin beside its own contour. A rectangle with an exact corner would be the only exact thing on a page made of hand-drawn lines, and a soft CSS shadow the only piece of photography. The shadow is therefore a second plate that does not register: the same outline, shifted a little, in a darker tone. The paper is clipped to that outline, otherwise the drawn edge would sit on an exact rectangle of paper and you would see both.

Four strokes, not one rectangle: the corners are sharp, and every stroke runs a little past them – the way a hand draws a box. It does not stop in the corner, it goes over it. A rounded outline looked like a stack of cards, and that is exactly what a sheet is not.

And the sheet does not BOIL. The lines of a face start afresh eight times a second because a face is alive; a piece of paper is not, and a trembling frame around a calm picture would be the frame you end up looking at.

Belongs to the tableBelongs in the picture
Edge and shadow of the sheet Paper, grain, vignette
The movement – gaze, blink, breath The rest position: the pose of the DNA, open eyes, no glance aside
List, arrows, bar The head and its name

The second row is the more important one. The head in the workshop is alive – it follows the pointer, blinks, breathes. A picture taken mid-movement would give a different image for the same address on every click, and that an address denotes a head is the promise everything here hangs on. The file name is that same address in a spelling a file system tolerates: ?kopf=118317&auge=stern becomes tomu-118317-auge-stern.png.

And on a phone? There a single line stands instead of the sixteen, and two arrows page through them. A list longer than the device is tall is not an overview but a shaft: the head you are rolling would be out of frame while you scroll. WHICH line is meant is decided by the computation; WHETHER only one is visible is decided by the style sheet – the computation should not have to know how wide the window is. Tapping a feature on the picture leaves the line below already on that feature.

The names are not part of the picture

They lie as <span> over the canvas, in exactly the place where the canvas would otherwise have painted them – the app computes that location anyway, for the zoom.

Painted text

Symptom: A name cannot be selected, cannot be copied, cannot be searched, and a screen reader finds nothing on the sheet but an empty rectangle. Cause: Painted text is a picture. Fix: Text stays text. The canvas carries an aria-label, the names stand above it in the DOM.

The gain is not only the copying. It also gives the name in the close-up a movement of its own: it comes up a touch from below once the head has already settled, and it goes faster than it came. A name that took as long as the head would compete with it for attention.

On the sheet it is the other way round, and for the same reason: that picture leaves the browser. A name that lives only in the DOM would not arrive at its recipient – a head without an address. There the name is painted, and the screen reader hears it in the canvas's aria-label.

Two names, not one: wunsch is what should stand there, gezeigt is what stands there right now. The app asks afresh every frame – the same wish sixty times a second. Without that separation a new fade-out would start in every frame, and the fade-in would never get its turn; the name would stay half there forever.

Checking without a screen

The development build puts four handles on window.presse: wuerfle(seed) sets a particular sheet, schaue(x, y, s) advances the springs without a frame loop, zellen() says which head sits where and what it wears, and abzug(name) sends the sheet at full resolution via POST /__abzug to the server, which files it under abzuege/.

The invisible page stands still

Symptom: An automated check sees heads staring straight ahead – whether they follow the pointer can only be claimed. Cause: Without a visible page the browser stops requestAnimationFrame. There is nobody to paint the next frame. Fix: schaue() advances the springs itself in steps of 1/60 second and draws once. For the print the names are painted into the canvas once – the next frame wipes them away again.

The whole branch exists only under import.meta.env.DEV; in the bundle it is removed. A handle that ships with the release is no longer a testing aid but an interface.