{"_id":"@ayo0la/grain-canvas","_rev":"2-8c6ffc447e3e8f7480ca0628178d95b3","name":"@ayo0la/grain-canvas","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@ayo0la/grain-canvas","version":"0.1.0","license":"MIT","_id":"@ayo0la/grain-canvas@0.1.0","maintainers":[{"name":"ayo0la","email":"aymorakinyo1@gmail.com"}],"dist":{"shasum":"ead1aada4b1c4ea9b23b19ca62ba6a3e8e6fb6ad","tarball":"https://registry.npmjs.org/@ayo0la/grain-canvas/-/grain-canvas-0.1.0.tgz","fileCount":6,"integrity":"sha512-buAJRT3kbagpvr3250dTvWv9rsc2KtiEQTLd9jIk2ZrVMEkdJhQWcnWN/c0EbvSEV5H+RzLeHQKQ/SQHFzHenQ==","signatures":[{"sig":"MEQCIAeqcOnB5q7BCq+/KolvRf18Teb72ikt/YzaJCWrOJg1AiApmh31sxBbhx1yr4AQsS3fF+ghVnwbAgHVNX/9VRKoSw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23708},"main":"./dist/index.cjs.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.esm.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.esm.js","require":"./dist/index.cjs.js"}},"gitHead":"0ac450170a0330bbce62f1372b78d5d027043b9e","scripts":{"dev":"tsup --watch","demo":"npx serve .","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayo0la","email":"aymorakinyo1@gmail.com"},"_npmVersion":"10.9.3","description":"Animated grain overlay + particle system canvas background. Zero dependencies, fully configurable.","directories":{},"_nodeVersion":"22.19.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/grain-canvas_0.1.0_1774095310771_0.6559756418454017","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ayo0la/grain-canvas","version":"0.1.1","description":"Animated grain overlay + particle system canvas background. Zero dependencies, fully configurable.","license":"MIT","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.esm.js","require":"./dist/index.cjs.js"}},"main":"./dist/index.cjs.js","module":"./dist/index.esm.js","types":"./dist/index.d.ts","scripts":{"build":"tsup","dev":"tsup --watch","demo":"npx serve .","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"devDependencies":{"tsup":"^8.0.0","typescript":"^5.0.0"},"_id":"@ayo0la/grain-canvas@0.1.1","gitHead":"b63e12b189f3f2cdb25a5afb6abce5817d4bc8e2","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-m4vXlaWl4nH20ku/t+wB7opBMt6xFpuWQITCY/YpU47povgf2HEWCpG5EUjiyHk3ubwMuhUfmygpMwwfdLvblg==","shasum":"7a36dc7352dcd6fe1909d5e2650e18b23f4744a8","tarball":"https://registry.npmjs.org/@ayo0la/grain-canvas/-/grain-canvas-0.1.1.tgz","fileCount":6,"unpackedSize":23728,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC1ux5UVEzWK7sxmIMKT4iJvQX3OvFtTAXvw9Uzo2EwcAIgQhMcWpcHXXRCGCq/BAyg2/+6zh6CbY9Ml51kq3+9VlY="}]},"_npmUser":{"name":"ayo0la","email":"aymorakinyo1@gmail.com"},"directories":{},"maintainers":[{"name":"ayo0la","email":"aymorakinyo1@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/grain-canvas_0.1.1_1775232881474_0.6423307471122452"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-21T12:15:10.694Z","modified":"2026-04-03T16:14:41.787Z","0.1.0":"2026-03-21T12:15:10.908Z","0.1.1":"2026-04-03T16:14:41.665Z"},"license":"MIT","description":"Animated grain overlay + particle system canvas background. Zero dependencies, fully configurable.","maintainers":[{"name":"ayo0la","email":"aymorakinyo1@gmail.com"}],"readme":"# @ayo0la/grain-canvas\n\nAnimated film grain + floating particle system as a canvas background. Zero dependencies. Fully configurable. Works anywhere.\n\n**[Live demo](https://ayoola-morakinyo.vercel.app)** — seen on my portfolio site.\n\n---\n\n## Install\n\n```bash\nnpm install @ayo0la/grain-canvas\n```\n\n## Usage\n\n```html\n<canvas id=\"bg\"></canvas>\n```\n\n```css\n#bg {\n  position: fixed;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n}\n```\n\n```js\nimport { init } from '@ayo0la/grain-canvas'\n\nconst destroy = init(document.getElementById('bg'))\n\n// Later, to clean up:\ndestroy()\n```\n\nThe canvas must have non-zero CSS dimensions before calling `init`. If it's 0×0, a console warning is emitted and the effect will start once the canvas is resized.\n\n---\n\n## Config\n\nAll options are optional. The defaults below produce the effect shown in the demo.\n\n```js\nimport { init, defaults } from '@ayo0la/grain-canvas'\n\nconst destroy = init(canvas, {\n  accentRatio: 0.4,\n  backgroundColor: '#050505',\n})\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `backgroundColor` | `string` | `'#080808'` | Canvas fill color. Use near-black for grain to look right. |\n| `particleCount` | `number` | `90` | Number of particles. Clamped 1–500. |\n| `particleSizeMin` | `number` | `0.8` | Minimum particle radius (px). |\n| `particleSizeMax` | `number` | `2.8` | Maximum particle radius (px). |\n| `particleBaseColor` | `string` | `'rgba(255,255,255,0.55)'` | Color of standard particles. |\n| `particleAccentColor` | `string` | `'rgba(255,215,0,0.75)'` | Color of accent particles. |\n| `accentRatio` | `number` | `0.25` | Fraction of particles using accent color. Clamped 0–1. |\n| `mouseAttractionRadius` | `number` | `200` | Cursor attraction radius (px). |\n| `mouseAttractionStrength` | `number` | `0.0008` | Attraction force multiplier. |\n| `connectionDistance` | `number` | `110` | Max distance (px) to draw lines between particles. |\n| `connectionOpacity` | `number` | `0.25` | Max opacity of connection lines. |\n| `glowRadius` | `number` | `140` | Radius of cursor/tap glow (px). |\n| `glowColor` | `string` | `'rgba(255,215,0,0.07)'` | Color of cursor/tap glow. |\n| `grainOpacity` | `number` | `0.35` | Grain overlay opacity. Clamped 0–1. |\n| `grainSwapInterval` | `number` | `50` | Milliseconds between grain frame swaps. Minimum 33ms. |\n| `vignetteOpacity` | `number` | `0.6` | Edge vignette darkness. Clamped 0–1. |\n\nColor strings are passed directly to the Canvas 2D API — invalid values render as transparent.\n\n---\n\n## Mobile\n\nMobile behaviour is automatic and not configurable:\n\n- Grain renders at half resolution with 3 frames instead of 6 for performance\n- Tapping attracts particles (same as cursor on desktop)\n- Releasing your finger resets the attraction point to the canvas center\n- The animation loop pauses automatically when the tab is hidden\n\n---\n\n## Known limitations\n\n- **Grain uses `screen` blend mode** — designed for dark backgrounds. Light `backgroundColor` values will look washed out.\n- **Color strings are not validated** — invalid CSS colors render as transparent, matching browser canvas behavior.\n- **`0.x.y` is unstable** — per semver convention, minor versions may include breaking changes until `1.0.0`.\n\n---\n\n## React\n\n```jsx\nimport { useEffect, useRef } from 'react'\nimport { init } from '@ayo0la/grain-canvas'\n\nexport function GrainBackground({ options }) {\n  const canvasRef = useRef(null)\n\n  useEffect(() => {\n    if (!canvasRef.current) return\n    const destroy = init(canvasRef.current, options)\n    return destroy\n  }, [])\n\n  return (\n    <canvas\n      ref={canvasRef}\n      style={{ position: 'fixed', inset: 0, width: '100%', height: '100%' }}\n    />\n  )\n}\n```\n\n---\n\n## Cleanup\n\n`init` returns a `destroy` function. Call it to cancel the animation loop and remove all event listeners. Safe to call more than once. The canvas is not cleared — the last painted frame stays visible.\n\n```js\nconst destroy = init(canvas)\n// ...\ndestroy() // stops the effect\ndestroy() // no-op, safe\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}