SPUR / MAKING-OF
← back to the press

02 / RISOGRAPH RIOT / BUILD NOTES

The print
is the
picture.

SPUR needed to feel like a tiny printed thing, not a website wearing a colourful costume. The whole interface borrows the useful friction of a riso run: uneven screens, a little drift between inks, cardboard-stiff rules and captions that read like notes in the margin.

The
concept.

The fictional press is based above a locksmith in Grønland: enough of a real place to give it scuffed edges, not so much backstory that it becomes brand cosplay. Its copy is written as a working printer talking to fellow paper people — precise about the monthly mailing, excited about contributors, and a little suspicious of clean edges.

The layout runs like a stack of printed matter. A full-bleed machine poster makes the first impression, a crooked rack makes the archive tangible, an almost-black membership panel changes the pace, and the final open call is literally pinned to a blue notice board.

01 / paper

Newsprint beige is the surface. Black rules do the work that grey UI borders would usually do.

02 / drift

Pink and blue never quite agree. That tiny error makes every scene feel printed rather than composited.

03 / pace

The ticker and registration jolt move with stepped timing so their motion has a machine-like stutter.

The
signature.

Two separately printed canvases

There are no image files in the site. Every illustration is sampled by a small scene function, then printed as individual circles onto two stacked transparent canvases. The two canvases use different angled sampling grids, different dot sizes, mix-blend-mode: multiply, and a 1–2px registration error. The paper grain above them is an SVG feTurbulence filter — not a bitmap texture. The registration bench later in the page keeps the same ticket drawing and changes only the plate offsets, so the visitor can see that tiny physical error make a new image.

/* The two ink plates are real, independent canvas layers. */
.riso-layer { position: absolute; inset: 0; mix-blend-mode: multiply; }
.riso-layer[data-ink="pink"] { transform: translate(-2px, 1px); }
.riso-layer[data-ink="blue"] { transform: translate(2px, -1px); }

const renderedCanvases = new WeakSet();
function drawHalftone(canvas) {
  const rect = canvas.getBoundingClientRect();
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  const width = Math.max(1, Math.round(rect.width * dpr));
  const height = Math.max(1, Math.round(rect.height * dpr));
  if (renderedCanvases.has(canvas) &&
      canvas.width === width && canvas.height === height) return;
  canvas.width = width; canvas.height = height;
  const ctx = canvas.getContext('2d');
  if (!ctx) return;
  const scene = canvas.dataset.scene;
  const ink = canvas.dataset.ink;
  const isPink = ink === 'pink';
  const spacing = Math.max(5.25, 6 * dpr);
  const slant = isPink ? .22 : -.18;
  ctx.fillStyle = isPink ? PINK : BLUE;

  for (let y = -spacing; y < height + spacing; y += spacing) {
    for (let x = -spacing; x < width + spacing; x += spacing) {
      const sampleX = (x + y * slant) / width;
      const sampleY = y / height;
      const tone = sceneField(scene, sampleX, sampleY, ink);
      if (tone < .08) continue;
      const grit = .82 + .18 * Math.sin(x * .37 + y * .91 + (isPink ? 3 : 8));
      const radius = Math.min(spacing * .48, spacing * tone * .53 * grit);
      ctx.beginPath();
      ctx.arc(x, y, radius, 0, Math.PI * 2);
      ctx.fill();
    }
  }
  renderedCanvases.add(canvas);
}

Palette
& type.

PINK#FF48B0
BLUE#0078BF
NEWSPRINT#F2EDE4
INK#1A1A1A
Archivo Black, Impact, sans-serif / headings
FOLD IT
LOUD.
Special Elite, Courier New, monospace / captions
Issue #32 leaves 06 August. Please do not laminate the little ferry ticket.

Iteration
log.

Pass 1 — correctness

  • Made the font stylesheet non-blocking, then drew the hero’s two canvas plates immediately, so the riso effect no longer waits for the load event; other plates now render only as they approach the viewport, with an IntersectionObserver fallback.
  • Kept the backing resolution capped at 2× and made resize work redraw only visible plates, plus the two hero plates needed for the first impression. The looping ticker now pauses while its section is off-screen, the tab is hidden, or reduced motion is requested.
  • Raised small navigation and control text, gave every link/button a 44px target, and changed focus outlines to ink black. The critical white/blue and ink/pink pairings measure 4.73:1 and 5.64:1 respectively.
  • Made the notice-board sheet narrower on small screens, constrained the code block to horizontal scrolling inside itself, and added grid shrink guards so the 390px layout does not force page-width overflow.
  • Added null/feature guards around canvas, observer, and control setup. There are no external image assets or JavaScript CDN dependencies to fail.

Pass 2 — design depth

  • Weak moment 01 — the archive was a neat three-card grid. Recast it as a real stack of printed matter: one oversized lead issue breaks upward through the section, while a pink receipt-sized fold and a blue tower booklet cross its rhythm at different scales and angles. Two misregistered CSS dot plates now bleed behind the rack, extending the riso treatment into the layout itself.
  • Weak moment 02 — the club section read as a flat information split. Turned the next dispatch into a physical paper object that overlaps the dark club panel, with offset pink/blue shadows, a crooked circulation stamp, more decisive display scale, and a blue screen of dot texture pressing in from the edge.
  • Weak moment 03 — the open call sat too politely in the middle of its board. Pulled the taped notice across the pink/blue seam, enlarged and tightened its headline, and let a giant offset FOLD overprint and blue dot plate make the board feel like an active print surface rather than a safe two-column layout.

Pass 3 — complexify

  • New interaction — the page needed a second thing to discover after the hero jolt. Added a below-the-fold registration bench with three keyboard-operable proof states: tight, SPUR’s usual sweet slip, and deliberately runaway. It does not swap in a canned alternate image; it moves the same independent pink and blue ticket plates in stepped increments, then reports exactly what changed.
  • Made the proof feel like a press artefact, not a control panel. The new ferry-dog ticket has a perforated edge, stamp, route rule, barcode, wave marks, and collar overprint sampled by the halftone algorithm. Its paper card thumps once on a state change while the background reads like an overprinted stack of proof sheets.
  • Finished the handoff. Added matching Open Graph and social metadata to both pages, tightened the final copy (including changing the club price to kr 120), and preserved the inline critical CSS, lazy lower-plate drawing, focus rings, capped canvas resolution, and reduced-motion override from the first pass.