{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "riso-print",
  "title": "Riso Print",
  "description": "A two-ink risograph print: the accent plate slides in off-register, wobbles and settles a hair out of alignment over grainy paper.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/riso-print/index.tsx",
      "content": "\"use client\";\n\nimport { type ReactNode, useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  random,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type RisoPrintProps = {\n  /** Subject printed in both inks. Dark tones go to the key plate, warm tones to the accent plate. */\n  children?: ReactNode;\n  /** [key ink, accent ink]. */\n  inks?: [string, string];\n  /** Key ink. Overrides `inks[0]` when set. */\n  keyInk?: string;\n  /** Accent ink. Overrides `inks[1]` when set. */\n  accentInk?: string;\n  paper?: string;\n  /** Final misregistration of the accent plate, in px at 720p. */\n  offset?: [number, number];\n  /** Ink speckle and paper tooth, 0 to 1. */\n  grain?: number;\n  /** Stop-motion wobble of the accent plate after it settles, in px at 720p. 0 disables. */\n  jitter?: number;\n  /** Print registration marks in the corners. */\n  marks?: boolean;\n  speed?: number;\n};\n\nexport const risoPrintDefaults = {\n  inks: [\"#111111\", \"#E5322D\"] as [string, string],\n  paper: \"#F2F0EA\",\n  offset: [7, -5] as [number, number],\n  grain: 0.5,\n  jitter: 1.2,\n  marks: true,\n  speed: 1,\n};\n\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\n// Coverage = A - channel: the key plate takes everything without red (blacks,\n// blues), the accent plate everything without green (reds, blacks). Black\n// therefore prints in both plates, like a rich black on a real riso.\nconst plateMatrix = (channel: 0 | 1) => {\n  const k = 1.4;\n  const r = channel === 0 ? -k : 0;\n  const g = channel === 1 ? -k : 0;\n  return `0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  ${r} ${g} 0 ${k} -0.2`;\n};\n\nfunction PosterPlate({\n  plate,\n  width,\n  height,\n}: {\n  plate: 0 | 1;\n  width: number;\n  height: number;\n}) {\n  const s = Math.min(width, height);\n  const cx = width / 2;\n  const cy = height / 2;\n  return (\n    <svg\n      width={width}\n      height={height}\n      viewBox={`0 0 ${width} ${height}`}\n      style={{ position: \"absolute\", inset: 0 }}\n      role=\"img\"\n      aria-label=\"Geometric poster\"\n    >\n      {plate === 0 ? (\n        <g fill=\"#000\">\n          <rect\n            x={cx - 0.42 * s}\n            y={cy - 0.1 * s}\n            width={0.36 * s}\n            height={0.36 * s}\n          />\n          {[0, 1, 2].map((i) => (\n            <rect\n              key={i}\n              x={cx + 0.2 * s}\n              y={cy + 0.2 * s + i * 0.045 * s}\n              width={(0.3 - i * 0.07) * s}\n              height={0.018 * s}\n            />\n          ))}\n        </g>\n      ) : (\n        <g fill=\"#000\">\n          <circle cx={cx + 0.08 * s} cy={cy - 0.04 * s} r={0.26 * s} />\n          <rect\n            x={cx - 0.46 * s}\n            y={cy - 0.34 * s}\n            width={0.07 * s}\n            height={0.07 * s}\n          />\n        </g>\n      )}\n    </svg>\n  );\n}\n\nfunction Marks({ width, height }: { width: number; height: number }) {\n  const s = Math.min(width, height);\n  const m = 0.06 * s;\n  const r = 0.018 * s;\n  const points = [\n    [m, m],\n    [width - m, m],\n    [m, height - m],\n    [width - m, height - m],\n  ];\n  return (\n    <svg\n      width={width}\n      height={height}\n      style={{ position: \"absolute\", inset: 0 }}\n      role=\"img\"\n      aria-label=\"Registration marks\"\n    >\n      {points.map(([x, y]) => (\n        <g key={`${x}-${y}`} stroke=\"#000\" strokeWidth={0.0035 * s} fill=\"none\">\n          <circle cx={x} cy={y} r={r} />\n          <line x1={x - r * 1.7} y1={y} x2={x + r * 1.7} y2={y} />\n          <line x1={x} y1={y - r * 1.7} x2={x} y2={y + r * 1.7} />\n        </g>\n      ))}\n    </svg>\n  );\n}\n\nexport function RisoPrint({\n  children,\n  inks: inksProp = risoPrintDefaults.inks,\n  keyInk,\n  accentInk,\n  paper = risoPrintDefaults.paper,\n  offset = risoPrintDefaults.offset,\n  grain = risoPrintDefaults.grain,\n  jitter = risoPrintDefaults.jitter,\n  marks = risoPrintDefaults.marks,\n  speed = risoPrintDefaults.speed,\n}: RisoPrintProps) {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const id = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const inks: [string, string] = [\n    keyInk ?? inksProp[0],\n    accentInk ?? inksProp[1],\n  ];\n  const unit = Math.min(width, height) / 720;\n  const t = (frame / fps) * 30 * speed;\n  const g = Math.max(0, Math.min(1, grain));\n\n  // Key plate: the ink hits the paper.\n  const press = interpolate(t, [0, 8], [0, 1], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n\n  // Accent plate: slides in off-register, wobbles, settles a hair off.\n  const slide = spring({\n    frame: t - 4,\n    fps: 30,\n    config: { damping: 9, stiffness: 120, mass: 0.8 },\n  });\n  const step = Math.floor(t / 4);\n  const jitterIn = interpolate(t, [26, 34], [0, 1], clamp);\n  const jx = (random(`riso-jx-${step}`) - 0.5) * 2 * jitter * unit * jitterIn;\n  const jy = (random(`riso-jy-${step}`) - 0.5) * 2 * jitter * unit * jitterIn;\n  const ax = interpolate(slide, [0, 1], [-0.22 * width, offset[0] * unit]) + jx;\n  const ay = interpolate(slide, [0, 1], [0.08 * height, offset[1] * unit]) + jy;\n  const arot = interpolate(slide, [0, 1], [-4, 0.35]);\n  const aopacity = interpolate(t, [4, 8], [0, 1], clamp);\n\n  const speckle = `0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  ${-5 * g} 0 0 0 ${1 + 5 * g * 0.56}`;\n  const plates = [\n    {\n      ink: inks[0],\n      transform: `scale(${interpolate(press, [0, 1], [1.035, 1])})`,\n      opacity: press * 0.94,\n    },\n    {\n      ink: inks[1],\n      transform: `translate(${ax}px, ${ay}px) rotate(${arot}deg)`,\n      opacity: aopacity * 0.9,\n    },\n  ];\n\n  return (\n    <AbsoluteFill style={{ backgroundColor: paper, overflow: \"hidden\" }}>\n      <svg width={0} height={0} style={{ position: \"absolute\" }} aria-hidden>\n        <defs>\n          {plates.map((plate, i) => (\n            <filter\n              key={plate.ink + i}\n              id={`${id}-plate-${i}`}\n              x=\"0\"\n              y=\"0\"\n              width=\"1\"\n              height=\"1\"\n              colorInterpolationFilters=\"sRGB\"\n            >\n              <feColorMatrix\n                in=\"SourceGraphic\"\n                type=\"matrix\"\n                values={plateMatrix(i as 0 | 1)}\n                result=\"coverage\"\n              />\n              <feTurbulence\n                type=\"fractalNoise\"\n                baseFrequency={0.9 / unit}\n                numOctaves={2}\n                seed={7 + i * 13}\n                result=\"noise\"\n              />\n              <feColorMatrix\n                in=\"noise\"\n                type=\"matrix\"\n                values={speckle}\n                result=\"mask\"\n              />\n              <feComposite\n                in=\"coverage\"\n                in2=\"mask\"\n                operator=\"in\"\n                result=\"inked\"\n              />\n              <feFlood floodColor={plate.ink} result=\"ink\" />\n              <feComposite in=\"ink\" in2=\"inked\" operator=\"in\" />\n            </filter>\n          ))}\n          <filter\n            id={`${id}-paper`}\n            x=\"0\"\n            y=\"0\"\n            width=\"1\"\n            height=\"1\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feTurbulence\n              type=\"fractalNoise\"\n              baseFrequency={0.65 / unit}\n              numOctaves={3}\n              seed={3}\n            />\n            <feColorMatrix\n              type=\"matrix\"\n              values={`0 0 0 0 0.25  0 0 0 0 0.2  0 0 0 0 0.12  ${0.5 * g} 0 0 0 ${-0.18 * g}`}\n            />\n          </filter>\n        </defs>\n      </svg>\n      {plates.map((plate, i) => (\n        <AbsoluteFill\n          key={plate.ink + i}\n          style={{\n            filter: `url(#${id}-plate-${i})`,\n            mixBlendMode: \"multiply\",\n            opacity: plate.opacity,\n            transform: plate.transform,\n          }}\n        >\n          {children ? (\n            <AbsoluteFill\n              style={{ alignItems: \"center\", justifyContent: \"center\" }}\n            >\n              {children}\n            </AbsoluteFill>\n          ) : (\n            <PosterPlate plate={i as 0 | 1} width={width} height={height} />\n          )}\n          {marks ? <Marks width={width} height={height} /> : null}\n        </AbsoluteFill>\n      ))}\n      <svg\n        width={width}\n        height={height}\n        style={{ position: \"absolute\", inset: 0, mixBlendMode: \"multiply\" }}\n        aria-hidden\n      >\n        <rect\n          width={width}\n          height={height}\n          fill=\"#fff\"\n          filter={`url(#${id}-paper)`}\n        />\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/riso-print.tsx"
    }
  ],
  "type": "registry:component"
}
