The Back Shop · Surat, Gujarat

How This Paper Is Printed

A colophon is where a paper admits how it was made. This one is unusual only in that the sheet was measured rather than picked: the colour is solved backwards out of a printing standard, the grain is built in the frequency domain so that it cannot show a seam, and the share card is fitted against a line box that was read off pixels because the documented one was wrong.

Monday, 28 September 2026 · every number below is computed on this page or measured from a file in the repository.

I. The Sheet

Colour·A1

Nearly every “newspaper” theme on the web is the colour of an old letter: warm, tea-stained, faintly amber. Real newsprint is not. It is a pale, almost neutral grey-cream, and it only reads as yellow when you put it next to office white.

ISO 12647-3, newsprint paper white states it exactly — L* 85 ±4, a* 1 ±2, b* 5 ±2. So rather than choose a hex value and argue about it, this site converted the sheet it was already shipping back into CIELAB and compared.

It measured b* 13.7. Against a target of 5 with a ceiling of 7, the yellow axis was nearly three times the standard’s figure and almost double what it permits. That is parchment. The sheet was rebuilt from Lab targets instead, and now measures b* 5.5.

Where the honesty goes

The shipped sheet sits at L* 92.5, which misses the standard’s L* 85 ±4 by 7.5. That is deliberate and it is not a pass. Paper reflects light; a screen emits it, and a literal L* 85 reads as dirty grey on a monitor. The b* axis is the one that decides whether a sheet looks like newsprint or like parchment, so b* is held to the standard and L* is not. ΔE*ab from the target is 7.6 for the shipped sheet and 10.3 for the one it replaced.

ISO 12647-3-20240481216a* (green − / red +)b* (blue − / yellow +)WAS: b* 13.7NOW: b* 5.5
The yellow axis is the one that matters. Newsprint is a pale, nearly neutral grey-cream; it only looks yellow next to office white.

Repaint this page

L*
92.5
a*
0.5
b*
5.5
ΔE*ab
7.6

The sheet under this page is being repainted as you press these. The numbers are computed from the value actually set on --paper.

TokensRGBL*a*b*Role
--paper#eee9df92.50.55.5the sheet
--stock#e1dbce87.60.57.2panels and boxes
--ink#2b272316.01.33.4body text
--faded#67605541.11.47.4secondary text
--rule#463f3627.21.86.7rules and borders
--spot#b9383044.052.135.9the second colour
--press#5e534636.13.09.4subheads
--desk#9c8f7a60.12.413.1the desk beneath

Static values; the live reading replaces them once the page hydrates.

II. The Grain

Texture·A2

A flat colour does not read as paper. Paper has fibre in it, and fibre has to repeat across the page without ever showing where the repeat happens.

The usual approach is to make some noise, tile it, look at it, and blur the edges until the seam stops being obvious. This tile is built in the frequency domain instead: an amplitude spectrum is shaped, random phase is applied, and the inverse FFT is taken. The output of an inverse FFT is periodic by construction — it wraps because that is what the transform produces, not because anything was smoothed. There is no seam to hide.

It carries only high frequencies. Anything slow enough to be recognised across 256px would give the repeat away, so the slow blotching lives in CSS as viewport-fixed radial gradients and never tiles at all.

The seam, measured

If the wrap is genuinely continuous, the average step across the join must look like any other step inside the tile.

Between neighbouring columns, inside
0.03404
Across the wrap, last column to first
0.03438ratio 1.010
Between neighbouring rows, inside
0.03455
Across the wrap, last row to first
0.03312ratio 0.959

Within one per cent of the interior in x and four per cent in y. The generator asserts this on every run and refuses to write a tile that fails it.

256×256, repeated. 40 KB.

Tile
256 × 256
Mean
0.9640near white, so multiply subtracts only where there is fibre
Standard deviation
0.0332
Weight
40 KB

III. The Card

Type fitting·A3

The image that appears when this site is pasted into Slack is rendered by Satori, which lays text out itself and reports nothing back. To fit a headline to its hole, the renderer has to know how tall the block will be before it draws it.

Two guesses failed. An average advance width of 0.46 em is wrong because Bodoni’s capitals are far wider than its lowercase; real advances read out of the font’s hmtx table fixed the width but not the height, because the height model assumed fontSize × lineHeight.

Satori does not do that. It scales an explicit numeric line-height again by a factor of its own. Rather than read source, single lines were rendered at known sizes with a rule under each and the block heights read off the pixels.

The factor is close to 1.27 and it does not depend on size, so a headline can now be fitted by stepping down until the wrapped block fits the height that is actually left for it. The ceiling of the measurements is used rather than the mean: over-stating a line only sets the headline one step smaller, while under-stating it prints the headline through the standfirst, which is exactly what the first version did.

Block height of a single line, measured from the rendered PNG.
FaceSizeline-heightMeasuredRatio
Bodoni Moda 700100px1.00127px1.270
Bodoni Moda 700100px1.06135px1.274
Bodoni Moda 700100px1.60203px1.269
Bodoni Moda 70050px1.0667px1.264
Oswald 50020px1.2030px1.250
Oswald 50040px1.2059px1.229
Spectral 40025px1.3644px1.294
Spectral 40050px1.3686px1.265

CSS would put every ratio at 1.000. The probe that produced this is tools/og-line-probe.mjs.

IV. The Back Shop

Stack·A4

Press

Next.js App Router on Vercel. Server components throughout; the client leaves are the edition toggle, the letters slip, the weather strip and the controls on this page.

Composition

Tailwind over CSS custom properties written as space-separated RGB triples, so one token serves every opacity. Type on a fluid clamp() scale computed from a slope, not chosen per breakpoint.

Faces

Bodoni Moda for headlines, Spectral for body, Oswald for the condensed furniture, Grenze Gotisch for the nameplate, Special Elite for the typewriter. Three are subset to 84 KB for the share card.

Motion

GSAP with ScrollTrigger, batched, and reverted through useGSAP so nothing outlives its component. Everything answers to prefers-reduced-motion.

Sheen

One fullscreen quad in Three.js drawing a single exponential falloff under the pointer. It refuses to start on a phone, on a slow device, on a saved-data connection, or without hover.

Measurement

Chrome DevTools Protocol driven from Node: width sweeps for overflow, console and network for errors, and the pixel probes behind sections II and III.

No cookies and no third-party trackers. The only analytics are Vercel’s, which are cookieless and store no identifier. Source: github.com/BondeYash.