Riso Print

Print a subject in two risograph inks, with the accent plate sliding in off-register and settling a hair out of alignment over grainy paper

Installation
$ pnpm dlx shadcn@latest add @remocn/riso-print

Usage

RisoPrint is a full-frame scene. It renders its subject twice, once per ink plate, and multiplies both plates onto a paper ground. The key plate presses onto the paper first. The accent plate slides in from the lower left on a loose spring, overshoots, and settles a few pixels off register. Both plates are speckled with seeded noise where the ink failed to take, and a paper tooth texture sits over everything.

import { RisoPrint } from "@/components/remocn/riso-print";
 
export const Poster = () => <RisoPrint />;

Without children it prints a built-in geometric poster. Pass children to print your own subject. Children are centered in the frame and rendered once per plate.

Separating your subject into plates

Each plate picks up ink by color channel, like a separation:

Color in your subjectKey plateAccent plate
BlackYesYes, overprinting into a rich black
Red, #FF0000NoYes
Cyan or green, #00FFFF, #00FF00YesNo
White or transparentNoNo

The key plate takes everything without red, and the accent plate takes everything without green. So paint the subject in these flat separation colors, not in the final inks. The final inks come from keyInk and accentInk (or the inks tuple).

import { RisoPrint } from "@/components/remocn/riso-print";
 
export const ZineCover = () => (
  <RisoPrint keyInk="#1F3A93" accentInk="#FF4F64" paper="#F4EFE4">
    <div style={{ display: "flex", alignItems: "center", gap: 56 }}>
      <div
        style={{
          width: 300,
          height: 300,
          borderRadius: "50%",
          background: "#FF0000",
        }}
      />
      <span style={{ fontSize: 180, fontWeight: 900, color: "#00FFFF" }}>
        ZINE
      </span>
    </div>
  </RisoPrint>
);

Here the circle prints only in pink and the word only in blue. Black text would print in both plates and show the accent plate's offset as a pink fringe.

Timing

At 30 fps and speed={1}:

FramesMotion
0–8The key plate presses in: it fades in and shrinks from 103.5% to 100%
4–8The accent plate fades in while it starts to slide
4–33The accent plate springs from the lower left, rotated −4°, overshoots and settles at offset with a 0.35° skew
26–34The stop-motion wobble fades in
34+The print holds. The accent plate jumps to a new seeded position every 4 frames, within jitter pixels

The wobble never stops, so the hold stays alive. Set jitter={0} for a still hold. speed scales the whole timeline. Pixel props are pixels at 720p and scale with the composition height.

Notes

Both plates and the paper run SVG filters with fractal noise over the full frame, so this look renders slower than a plain scene. The noise is seeded, so every frame is deterministic.

Props
PropTypeDefaultDescription
children
React.ReactNode—Subject to print, centered and rendered once per plate. Without it a geometric poster is printed
keyInk
string"#111111"Key ink. Overrides inks[0] when set
accentInk
string"#E5322D"Accent ink. Overrides inks[1] when set
inks
[string, string]["#111111", "#E5322D"]Key ink and accent ink as a tuple; keyInk and accentInk take precedence
paper
string"#F2F0EA"Paper color behind the inks
offset
[number, number][7, -5]Final misregistration of the accent plate in px at 720p
grain
number0.5Ink speckle and paper tooth, clamped to 0–1
jitter
number1.2Stop-motion wobble of the accent plate after it settles, in px at 720p; 0 disables it
marks
booleantruePrints registration marks in the four corners on both plates
speed
number1Playback multiplier