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
$ pnpm dlx shadcn@latest add @remocn/riso-printUsage
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 subject | Key plate | Accent plate |
|---|---|---|
| Black | Yes | Yes, overprinting into a rich black |
Red, #FF0000 | No | Yes |
Cyan or green, #00FFFF, #00FF00 | Yes | No |
| White or transparent | No | No |
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}:
| Frames | Motion |
|---|---|
| 0–8 | The key plate presses in: it fades in and shrinks from 103.5% to 100% |
| 4–8 | The accent plate fades in while it starts to slide |
| 4–33 | The accent plate springs from the lower left, rotated −4°, overshoots and settles at offset with a 0.35° skew |
| 26–34 | The 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.
| Prop | Type | Default | Description |
|---|---|---|---|
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 | number | 0.5 | Ink speckle and paper tooth, clamped to 0–1 |
jitter | number | 1.2 | Stop-motion wobble of the accent plate after it settles, in px at 720p; 0 disables it |
marks | boolean | true | Prints registration marks in the four corners on both plates |
speed | number | 1 | Playback multiplier |