{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grain",
  "type": "registry:ui",
  "title": "Grain",
  "description": "Film-grain overlay from a cached SVG-noise tile, with blend modes and an optional compositor-only flicker — texture for gradients and heroes.",
  "dependencies": [],
  "registryDependencies": [
    "https://kuro.abstergo.fyi/r/kura-theme.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/kura/grain/grain.tsx",
      "type": "registry:ui",
      "target": "components/kura/grain/grain.tsx",
      "content": "import type * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type GrainBlend = \"overlay\" | \"soft-light\" | \"multiply\" | \"screen\" | \"normal\";\n\nconst CSS = `\n@keyframes kura-grain {\n  0%, 100% { transform: translate(0, 0); }\n  12.5% { transform: translate(-7%, 4%); }\n  25% { transform: translate(5%, -9%); }\n  37.5% { transform: translate(-11%, -3%); }\n  50% { transform: translate(8%, 7%); }\n  62.5% { transform: translate(-3%, 11%); }\n  75% { transform: translate(10%, -5%); }\n  87.5% { transform: translate(-6%, -10%); }\n}\n`;\n\nconst TILE = 180;\n\n/** A tiling, high-contrast monochrome noise tile (SVG feTurbulence) as a data URI. */\nfunction noiseTile(scale: number): string {\n  const freq = (0.85 / Math.max(0.25, scale)).toFixed(3);\n  const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${TILE}' height='${TILE}'><filter id='n' x='0' y='0'><feTurbulence type='fractalNoise' baseFrequency='${freq}' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/><feComponentTransfer><feFuncR type='linear' slope='2.4' intercept='-0.7'/><feFuncG type='linear' slope='2.4' intercept='-0.7'/><feFuncB type='linear' slope='2.4' intercept='-0.7'/></feComponentTransfer></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>`;\n  return `url(\"data:image/svg+xml,${svg.replace(/\"/g, \"'\").replace(/</g, \"%3C\").replace(/>/g, \"%3E\")}\")`;\n}\n\nexport interface GrainProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n  /** Strength of the grain, 0–1. Default 0.12. */\n  opacity?: number;\n  /** How the grain mixes with what is underneath. Default \"overlay\". */\n  blend?: GrainBlend;\n  /** Grain size multiplier; 1 is fine film grain, 2 is coarse. Default 1. */\n  scale?: number;\n  /** Flicker the grain like projected film. Off by default — stillness is the Kura default. */\n  animated?: boolean;\n  /** Flicker rate in frames per second when `animated`. Default 12. */\n  fps?: number;\n}\n\n/**\n * A film-grain overlay for gradients, heroes and imagery. One cached SVG-noise tile, composited\n * with a blend mode; the optional flicker only moves a transform in `steps()`, so it stays on the\n * compositor and costs next to nothing. Pure CSS (server-safe), decorative, reduced-motion aware.\n */\nexport function Grain({\n  opacity = 0.12,\n  blend = \"overlay\",\n  scale = 1,\n  animated = false,\n  fps = 12,\n  className,\n  style,\n  ...props\n}: GrainProps) {\n  const frames = 8;\n  return (\n    <div\n      aria-hidden\n      data-slot=\"grain\"\n      data-animated={animated}\n      className={cn(\"pointer-events-none absolute inset-0 overflow-hidden\", className)}\n      style={{\n        opacity: Math.min(1, Math.max(0, opacity)),\n        mixBlendMode: blend,\n        ...style,\n      }}\n      {...props}\n    >\n      {animated && (\n        <style href=\"kura-grain\" precedence=\"kura\">\n          {CSS}\n        </style>\n      )}\n      <div\n        data-slot=\"grain-layer\"\n        className={cn(\n          \"absolute -inset-1/2\",\n          animated &&\n            \"[animation:var(--kura-grain-animation)] will-change-transform motion-reduce:animate-none\",\n        )}\n        style={{\n          backgroundImage: noiseTile(scale),\n          backgroundSize: `${TILE}px ${TILE}px`,\n          [\"--kura-grain-animation\" as string]: `kura-grain ${(frames / Math.max(1, fps)).toFixed(3)}s steps(1, end) infinite`,\n        }}\n      />\n    </div>\n  );\n}\n"
    },
    {
      "path": "registry/kura/grain/index.ts",
      "type": "registry:ui",
      "target": "components/kura/grain/index.ts",
      "content": "export { Grain, type GrainProps } from \"./grain\";\n"
    }
  ],
  "categories": [
    "effects",
    "grain",
    "noise",
    "texture",
    "film"
  ],
  "docs": "Docs: https://kuro.abstergo.fyi/components/grain · Markdown for agents: https://kuro.abstergo.fyi/components/grain.md",
  "meta": {
    "kura": {
      "status": "beta",
      "whenToUse": [
        "Adding a print-like finish on top of a mesh-gradient, aurora or photo.",
        "Hiding colour banding on large, low-contrast gradients.",
        "Giving a flat hero or empty-state panel a subtle tactile texture."
      ],
      "whenNotToUse": [
        "As the background itself — combine it with mesh-gradient or aurora.",
        "Over body text or dense UI: noise reduces legibility.",
        "Signalling activity — grain is texture; use border-beam or thinking-indicator."
      ]
    }
  }
}
