{"_id":"vctrfx","_rev":"4-37af08c9a61781574bee77b21dc2d24d","name":"vctrfx","dist-tags":{"latest":"0.10.1"},"versions":{"0.6.0":{"name":"vctrfx","version":"0.6.0","keywords":["svg","postprocessing","post-processing","effects","filter","glitch","scanlines","bloom","halftone","duotone","design"],"author":{"name":"Tal Hayut"},"license":"MIT","_id":"vctrfx@0.6.0","maintainers":[{"name":"tool3","email":"talhayut.dev@gmail.com"}],"homepage":"https://github.com/tool3/pstfx#readme","bugs":{"url":"https://github.com/tool3/pstfx/issues"},"dist":{"shasum":"49330eaa4accfae16f0c6c0902e8f2aa22893e2a","tarball":"https://registry.npmjs.org/vctrfx/-/vctrfx-0.6.0.tgz","fileCount":285,"integrity":"sha512-7nqe2qOYS60K+1zIDvndBHyNo+F0j+i4jIeEByuZAWk7XmyLPIKSbtnEeLM178Sk5nOLKgVUvRivyjzRDPzC6Q==","signatures":[{"sig":"MEUCIEDoK6rJIMzmCouyVyQPII8OU8nqTMD7gQiSko+tjE0SAiEA8YMCb149unwhF3xdqGO3bDsbxsshbFwprNXPF85UeTs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":562992},"main":"./dist/cjs/index.js","type":"module","types":"./dist/esm/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./package.json":"./package.json"},"gitHead":"6a270f8fdd29c498db1c9da91b007774a74495c0","scripts":{"test":"node --test \"test/**/*.test.ts\"","build":"npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/finalize.ts","clean":"node scripts/clean.ts","gallery":"node scripts/gallery.ts","examples":"node examples/run-all.ts","typecheck":"tsc -p tsconfig.json","test:watch":"node --test --watch \"test/**/*.test.ts\"","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"tool3","email":"talhayut.dev@gmail.com"},"repository":{"url":"git+https://github.com/tool3/pstfx.git","type":"git"},"_npmVersion":"11.17.0","description":"Post-processing effects for any SVG. Scanlines, bloom, glitch, halftone and more — in, out, still an SVG.","directories":{},"sideEffects":false,"_nodeVersion":"26.4.0","dependencies":{"grfti":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"shellfie":"^2.4.4","typescript":"^7.0.2","@types/node":"^26.4.1"},"_npmOperationalInternal":{"tmp":"tmp/vctrfx_0.6.0_1789408124826_0.873246102062923","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"vctrfx","version":"0.8.0","keywords":["svg","postprocessing","post-processing","effects","filter","glitch","scanlines","bloom","halftone","duotone","design"],"author":{"name":"Tal Hayut"},"license":"MIT","_id":"vctrfx@0.8.0","maintainers":[{"name":"tool3","email":"talhayut.dev@gmail.com"}],"homepage":"https://github.com/tool3/vctrfx#readme","bugs":{"url":"https://github.com/tool3/vctrfx/issues"},"dist":{"shasum":"90967421c58f9c5cc2fb998d20d35e469d3faff8","tarball":"https://registry.npmjs.org/vctrfx/-/vctrfx-0.8.0.tgz","fileCount":285,"integrity":"sha512-etoVrHVEBuPL/bhzbDdgB/1r5kJXLlwIj22newLYxl84+8YId6FXqKHEo5BGQyxE5UImVrvtSIaBzTtgHMiGGQ==","signatures":[{"sig":"MEYCIQC7/Ho3MtimX/LL3Lr58pCUebdThkVH/RdKakoGliz2CwIhAOwMgrsijje1inucWEFJlzVWmLR/bHR61CPxK7gXOZHR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/vctrfx@0.8.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":562989},"main":"./dist/cjs/index.js","type":"module","types":"./dist/esm/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./package.json":"./package.json"},"gitHead":"6f051d4514547e1cc0897a9e6b8b3a4af52a1c71","scripts":{"test":"node --test \"test/**/*.test.ts\"","build":"npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/finalize.ts","clean":"node scripts/clean.ts","gallery":"node scripts/gallery.ts","examples":"node examples/run-all.ts","typecheck":"tsc -p tsconfig.json","test:watch":"node --test --watch \"test/**/*.test.ts\"","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:5268dfd3-165c-40c7-ab77-d99355e7909b"}},"repository":{"url":"git+https://github.com/tool3/vctrfx.git","type":"git"},"_npmVersion":"11.19.0","description":"Post-processing effects for any SVG. Scanlines, bloom, glitch, halftone and more — in, out, still an SVG.","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"grfti":"^0.5.0"},"_hasShrinkwrap":false,"devDependencies":{"shellfie":"^2.4.4","typescript":"^7.0.2","@types/node":"^26.4.1"},"_npmOperationalInternal":{"tmp":"tmp/vctrfx_0.8.0_1789412496145_0.340087780875884","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"vctrfx","version":"0.10.0","keywords":["svg","postprocessing","post-processing","effects","filter","glitch","scanlines","bloom","halftone","duotone","design"],"author":{"name":"Tal Hayut"},"license":"MIT","_id":"vctrfx@0.10.0","maintainers":[{"name":"tool3","email":"talhayut.dev@gmail.com"}],"homepage":"https://github.com/tool3/vctrfx#readme","bugs":{"url":"https://github.com/tool3/vctrfx/issues"},"dist":{"shasum":"58f8a9472abb15844b7ab6b37816d9ecaf1325b1","tarball":"https://registry.npmjs.org/vctrfx/-/vctrfx-0.10.0.tgz","fileCount":47,"integrity":"sha512-9tva/vWjJN8rdYw6f9vQURf4pYS5h/BJpEmdsuEMYUX3nHTVNqD5URBUhBgK5MWkoSJVwE0iu7La4ciABVCSFw==","signatures":[{"sig":"MEQCIEAGXyKt4Pl64eVHdZcLd4y32mj2YTSf2VoT9kYtFD9WAiBl0x0k3LGp0Ef+PqlD3OgolwRsorFFqALLou+2o9dnNQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIBkEFa0SEuT1yFBsQnG8UYjDNINT51YCHXIfuwo4wcvlAiBTdGwKWealOmOI0hXuMfCrQ4d+BQ0Od+ZRN2oR0wk+fg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/vctrfx@0.10.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":611139},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"584cc659d1dc292e9e98a9ab3d3941760a03dd43","scripts":{"test":"node --test \"test/**/*.test.ts\"","build":"tsdown","gallery":"node scripts/gallery.ts","examples":"node examples/run-all.ts","typecheck":"tsc -p tsconfig.json","test:watch":"node --test --watch \"test/**/*.test.ts\"","prepublishOnly":"npm run typecheck && npm test && npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:5268dfd3-165c-40c7-ab77-d99355e7909b"}},"repository":{"url":"git+https://github.com/tool3/vctrfx.git","type":"git"},"_npmVersion":"11.19.0","description":"Post-processing effects for any SVG. Scanlines, bloom, glitch, halftone and more — in, out, still an SVG.","directories":{},"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"grfti":"^0.5.0"},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.23.0","shellfie":"^2.4.4","typescript":"^7.0.2","@types/node":"^26.4.1"},"_npmOperationalInternal":{"tmp":"tmp/vctrfx_0.10.0_1789749964246_0.30366165021800784","host":"s3://npm-registry-packages-npm-production"}},"0.10.1":{"_id":"vctrfx@0.10.1","bugs":{"url":"https://github.com/tool3/vctrfx/issues"},"dist":{"shasum":"3f118d9677be88809a40236716a6ecc667ea6dc7","tarball":"https://registry.npmjs.org/vctrfx/-/vctrfx-0.10.1.tgz","fileCount":47,"integrity":"sha512-Rz2n3+GdjfNmsE5iwCDuUapWsj9rY3jz+HeGF5F3MM5T63PGOVng/UTY10EA3RHjkY1tIpDBR3zMX8uQFs7tUA==","signatures":[{"sig":"MEQCIBlbuHJ5t+bgIpLueP6GzqbuoMwJ1Z4IyoM66FIAVXPsAiB9v93g4xPSjxyv8CcPdSLJO83iJ169G2YRwKkY3LhXVw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDwnQsjSRcSF0747quVzHizISwUC/gegkH55AVkN6NAJwIgLHHb3i2XSiRzdDRHnxtlla0QN9zV3h6dePjpAEFx5eo="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/vctrfx@0.10.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":611139},"main":"./dist/index.cjs","name":"vctrfx","type":"module","types":"./dist/index.d.ts","author":{"name":"Tal Hayut"},"module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"ebac2992fc94ad5100a12ecad89d01e7c7071da7","license":"MIT","scripts":{"test":"node --test \"test/**/*.test.ts\"","build":"tsdown","gallery":"node scripts/gallery.ts","examples":"node examples/run-all.ts","typecheck":"tsc -p tsconfig.json","test:watch":"node --test --watch \"test/**/*.test.ts\"","prepublishOnly":"npm run typecheck && npm test && npm run build"},"version":"0.10.1","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:5268dfd3-165c-40c7-ab77-d99355e7909b"}},"homepage":"https://github.com/tool3/vctrfx#readme","keywords":["svg","postprocessing","post-processing","effects","filter","glitch","scanlines","bloom","halftone","duotone","design"],"repository":{"url":"git+https://github.com/tool3/vctrfx.git","type":"git"},"_npmVersion":"11.19.0","description":"Post-processing effects for any SVG. Scanlines, bloom, glitch, halftone and more — in, out, still an SVG.","directories":{},"maintainers":[{"name":"tool3","email":"talhayut.dev@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{"grfti":"^0.5.0"},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.23.0","shellfie":"^2.4.4","typescript":"^7.0.2","@types/node":"^26.4.1"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vctrfx_0.10.1_1790105525800_0.7408102078396612"}}},"time":{"created":"2026-09-14T17:48:44.730Z","modified":"2026-09-22T19:32:06.247Z","0.6.0":"2026-09-14T17:48:44.998Z","0.8.0":"2026-09-14T19:01:36.286Z","0.10.0":"2026-09-18T16:46:04.344Z","0.10.1":"2026-09-22T19:32:05.925Z"},"bugs":{"url":"https://github.com/tool3/vctrfx/issues"},"author":{"name":"Tal Hayut"},"license":"MIT","homepage":"https://github.com/tool3/vctrfx#readme","keywords":["svg","postprocessing","post-processing","effects","filter","glitch","scanlines","bloom","halftone","duotone","design"],"repository":{"url":"git+https://github.com/tool3/vctrfx.git","type":"git"},"description":"Post-processing effects for any SVG. Scanlines, bloom, glitch, halftone and more — in, out, still an SVG.","maintainers":[{"name":"tool3","email":"talhayut.dev@gmail.com"}],"readme":"<div align=\"center\">\n\n# vctrfx\n\n`vctrfx(logo, [crt()])`\n\n<!-- <img src=\"https://shellfied.vercel.app/s/4nvQFSK.svg\" width=\"420\" alt=\"crt preset\"> -->\n<img src=\"https://shellfied.vercel.app/s/eTkpuhP.svg\" width=\"420\" alt=\"crt preset\">\n\n### Post-processing effects for any SVG.\n\nGive it an SVG, get back an SVG — now with scanlines, bloom, glitch, halftone or a\ndozen other looks baked in. Still vector, still editable, no rasterizing, no DOM.\n\n[![npm](https://img.shields.io/npm/v/vctrfx)](https://www.npmjs.com/package/vctrfx)\n[![license](https://img.shields.io/badge/license-MIT-orange)](./LICENSE)\n\n</div>\n\n---\n\n## Highlights\n\n- ✅ **Vector in, vector out** — no canvas, no rasterizing, no `<image>` payloads. The result still scales and still has your paths in it.\n- ✅ **Runs anywhere** — zero runtime dependencies, no DOM. Node, Bun, Deno, edge functions, build scripts, browsers.\n- ✅ **27 effects + 8 presets** — every one tuned to look right with no arguments.\n- ✅ **Follows your artwork's shape** — overlays clip flush to a rounded frame or any silhouette, never over it.\n- ✅ **Deterministic** — same input and settings give byte-identical output, so it drops into a build pipeline and a snapshot test.\n- ✅ **14 KB gzipped**, fully tree-shakeable. Import one effect, ship one effect.\n- ✅ **Self-contained motion** — opt into `animate` and the output loops on its own inside an `<img>`, no JavaScript attached.\n- ✅ **Keeps existing animation** — an SVG that already animates keeps animating.\n\n---\n\n## Table of contents\n\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [API](#api)\n- [Effects](#effects)\n- [Examples](#examples)\n- [Shape](#the-output-keeps-your-artworks-shape)\n- [Motion](#motion)\n- [Naming effects instead of importing them](#naming-effects-instead-of-importing-them)\n- [Writing your own effect](#writing-your-own-effect)\n- [Recipes](#recipes)\n- [Notes](#notes)\n\n---\n\n## Installation\n\n```bash\nnpm install vctrfx\n```\n\n## Quick start\n\n```ts\nimport { readFileSync, writeFileSync } from 'node:fs'\nimport { vctrfx, crt } from 'vctrfx'\n\nconst source = readFileSync('logo.svg', 'utf8')\n\nwriteFileSync('logo-crt.svg', vctrfx(source, [crt()]))\n```\n\nEffects are applied in order, exactly as you would stack them in an editor:\n\n```ts\nimport { vctrfx, duotone, halftone, grain } from 'vctrfx'\n\nconst poster = vctrfx(source, [\n  duotone({ shadow: '#111d4a', highlight: '#ff6b6b' }),\n  halftone({ size: 5, angle: 15 }),\n  grain({ amount: 0.4 }),\n])\n```\n\n## API\n\n### `vctrfx(source, effects?, settings?)`\n\nTakes an SVG string, returns an SVG string.\n\n```ts\nconst output = vctrfx(source, [bloom()], { seed: 'hero', format: 'pretty' })\n```\n\n### `createPipeline(effects, settings?)`\n\nA reusable, named stack of effects. Build it once, apply it to many files.\n\n```ts\nimport { createPipeline, duotone, grain } from 'vctrfx'\n\nconst brand = createPipeline([duotone({ shadow: '#001427' }), grain()], { seed: 'brand' })\n\nicons.map(brand.apply)\n```\n\n### `compose(name, effects)`\n\nCollapses several effects into one, so a house style becomes a single effect that can\nbe passed around, exported, and stacked with others.\n\n```ts\nexport const houseStyle = compose('house-style', [contrast({ amount: 1.1 }), grain(), vignette()])\n```\n\n### `resolveEffects(input)` and `applyEffects(source, input, settings?)`\n\nThe same stack, written as data rather than imports — for config files, CLI flags and\nanything else that has to travel as a string. See\n[Naming effects instead of importing them](#naming-effects-instead-of-importing-them).\n\n```ts\nimport { applyEffects } from 'vctrfx'\n\napplyEffects(source, 'scanlines(gap:3,opacity:.35) grain(amount:.4)')\n```\n\n### `toDataUri(svg, options?)`\n\n```ts\nelement.style.backgroundImage = `url(\"${toDataUri(vctrfx(source, [crt()]))}\")`\n```\n\nURI-encoded by default (smaller and readable); pass `{ base64: true }` if you need it.\n\n### Settings\n\n| Setting   | Default      | What it does |\n| --------- | ------------ | ------------ |\n| `seed`    | `'vctrfx'`   | Seeds every random decision. Change it to reroll a glitch or a grain field; keep it to get the same output forever. |\n| `prefix`  | `'vctrfx'`   | Prefix for every generated id and class. |\n| `scope`   | auto         | Id namespace. Derived from the source, seed and effects so several vctrfx outputs can be inlined in one page without colliding. Set it yourself for stable, readable ids. |\n| `clip`    | `'shape'`    | Trim the finished result to the artwork's own frame, so the output silhouette matches the input exactly. `'none'` lets effects spill past it. |\n| `animate` | `true`       | Global switch for motion. `false` strips animation from every effect that asked for it — useful for print, PDF or snapshot tests. |\n| `format`  | `'preserve'` | `'preserve'` keeps the input's whitespace, `'pretty'` re-indents, `'minify'` drops comments and layout whitespace. |\n\n## Effects\n\n### Light\n\n| Effect | Options |\n| ------ | ------- |\n| `blur` | `radius` `3`, `axis` `'both' \\| 'horizontal' \\| 'vertical'` |\n| `bloom` | `radius` `8`, `threshold` `0.55`, `intensity` `1.1` |\n| `glow` | `color` `'#ffffff'`, `radius` `6`, `spread` `0`, `intensity` `1` |\n| `shadow` | `x` `3`, `y` `4`, `blur` `4`, `color` `'#000000'`, `opacity` `0.4` |\n\n### Colour\n\n| Effect | Options |\n| ------ | ------- |\n| `grayscale` | `amount` `1` |\n| `saturate` | `amount` `1.4` |\n| `hueRotate` | `angle` `90` |\n| `invert` | `amount` `1` |\n| `brightness` | `amount` `1.15` |\n| `contrast` | `amount` `1.25` |\n| `sepia` | `amount` `1` |\n| `fade` | `amount` `0.7` |\n| `posterize` | `steps` `5`, `includeAlpha` `false` |\n| `threshold` | `level` `0.5`, `dark` `'#000000'`, `light` `'#ffffff'` |\n| `duotone` | `shadow` `'#12263a'`, `highlight` `'#f4d35e'`, `mix` `1` |\n| `tint` | `color` `'#ff2d55'`, `amount` `0.45` |\n\n### Texture and distortion\n\n| Effect | Options |\n| ------ | ------- |\n| `grain` | `amount` `0.32`, `size` `0.8`, `monochrome` `true`, `blend` `'overlay'`, `animate` `false`, `speed` `12` |\n| `scanlines` | `gap` `4`, `thickness` `1.5`, `opacity` `0.28`, `color` `'#000000'`, `angle` `0`, `blend` `'multiply'`, `animate` `false`, `speed` `6`, `clip` `'shape'` |\n| `chromaticAberration` | `offset` `2`, `angle` `0` |\n| `glitch` | `intensity` `0.5`, `slices` `7`, `colorShift` `true`, `animate` `false`, `speed` `1` |\n| `pixelate` | `size` `8` |\n| `halftone` | `size` `6`, `angle` `45`, `levels` `4`, `color` `'#111111'`, `background` `'#ffffff'`, `keepSource` `false`, `clip` `'shape'` |\n| `vignette` | `amount` `0.65`, `radius` `0.6`, `softness` `0.7`, `color` `'#000000'`, `clip` `'shape'` |\n| `outline` | `width` `2`, `color` `'#000000'`, `position` `'outside' \\| 'inside'` |\n| `wave` | `amplitude` `12`, `frequency` `0.02`, `octaves` `2`, `animate` `false`, `speed` `0.15` |\n| `emboss` | `depth` `1`, `angle` `135`, `desaturate` `true` |\n| `sharpen` | `amount` `1` |\n\nEvery option is optional. `bloom()` on its own is tuned to look right.\n\n### Presets\n\nFinished looks, each one a `compose` of the effects above.\n\n```ts\nimport { crt, vhs, riso, xerox, neon, film, newsprint, cyberpunk } from 'vctrfx'\n\nvctrfx(source, [crt({ animate: true })])\nvctrfx(source, [riso({ shadow: '#2b3a67', highlight: '#ff5a5f' })])\nvctrfx(source, [neon({ color: '#4cc9f0' })])\n```\n\n| Preset | Look |\n| ------ | ---- |\n| `crt` | Phosphor glow, scanlines, colour fringing, vignette |\n| `vhs` | Tape wobble, heavy fringing, rolling lines, noise |\n| `riso` | Two-colour risograph with paper grain |\n| `xerox` | Blown-out photocopy |\n| `neon` | Saturated sign glow |\n| `film` | Halation, grain and a soft vignette |\n| `newsprint` | Halftone dots on off-white stock |\n| `cyberpunk` | Sliced, shifted, bloomed, scanned |\n\n### Tuning a preset\n\nA preset is a stack of effects, and every one of them is reachable by name. Override the\neffect you want to move and the rest of the recipe stays put:\n\n```ts\nvctrfx(source, [film({ grain: { amount: 0.5 } })])\n```\n\nOverrides merge over the preset's values rather than replacing the effect, so changing\none knob keeps the others. `film` runs bloom at `radius: 6, threshold: 0.68` — this moves\nthe radius and keeps the threshold:\n\n```ts\nvctrfx(source, [film({ bloom: { radius: 14 } })])\n```\n\nEach preset's options type lists exactly the effects it contains, so editors autocomplete\nthem and `film({ scanlines: ... })` is a type error — film has no scanlines in it.\n\n```ts\ncrt({\n  animate: true,\n  scanlines: { gap: 6, blend: 'overlay' },\n  vignette: { amount: 0.9 },\n})\n```\n\nThe shorthands stay: `animate` on `crt`, `vhs` and `cyberpunk`, `shadow`/`highlight` on\n`riso`, `color` on `neon`. A per-effect override wins when both are given.\n\nEvery preset's own values are exported, so you can read what you are overriding, build a\nvariant from them, or drive a UI off them:\n\n```ts\nimport { film, FILM_DEFAULTS } from 'vctrfx'\n\nFILM_DEFAULTS.bloom              // { radius: 6, threshold: 0.68 }\nfilm({ bloom: { ...FILM_DEFAULTS.bloom, radius: 20 } })\n```\n\n`CRT_DEFAULTS`, `VHS_DEFAULTS`, `RISO_DEFAULTS`, `XEROX_DEFAULTS`, `NEON_DEFAULTS`,\n`FILM_DEFAULTS`, `NEWSPRINT_DEFAULTS` and `CYBERPUNK_DEFAULTS` are all exported.\n\n## Examples\n\nEvery effect and preset ships as a runnable TypeScript script under\n[`examples/`](https://github.com/tool3/vctrfx/tree/master/examples) — the linked name opens the code, which shows the exact\noptions used. Each script writes its results into `examples/svgs`, and runs as-is with no\nbuild step.\n\n```bash\nnpm run examples                    # render all 39\nnode examples/effects/halftone.ts   # or just one\nnpm run gallery                     # side-by-side overview page\n```\n\nEvery example runs twice: on still artwork, and on a spinning 3D solid — so you can see\neach effect hold up on something that moves. The gallery page plays both.\n\n### Effect examples\n\n| Example | Before | After |\n| ------- | ------ | ----- |\n| [`blur`](https://github.com/tool3/vctrfx/blob/master/examples/effects/blur.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/blur.before.svg\" width=\"180\" alt=\"blur before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/blur.after.svg\" width=\"180\" alt=\"blur after\"> |\n| [`bloom`](https://github.com/tool3/vctrfx/blob/master/examples/effects/bloom.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/bloom.before.svg\" width=\"180\" alt=\"bloom before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/bloom.after.svg\" width=\"180\" alt=\"bloom after\"> |\n| [`glow`](https://github.com/tool3/vctrfx/blob/master/examples/effects/glow.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/glow.before.svg\" width=\"180\" alt=\"glow before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/glow.after.svg\" width=\"180\" alt=\"glow after\"> |\n| [`shadow`](https://github.com/tool3/vctrfx/blob/master/examples/effects/shadow.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/shadow.before.svg\" width=\"180\" alt=\"shadow before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/shadow.after.svg\" width=\"180\" alt=\"shadow after\"> |\n| [`grayscale`](https://github.com/tool3/vctrfx/blob/master/examples/effects/grayscale.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/grayscale.before.svg\" width=\"180\" alt=\"grayscale before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/grayscale.after.svg\" width=\"180\" alt=\"grayscale after\"> |\n| [`saturate`](https://github.com/tool3/vctrfx/blob/master/examples/effects/saturate.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/saturate.before.svg\" width=\"180\" alt=\"saturate before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/saturate.after.svg\" width=\"180\" alt=\"saturate after\"> |\n| [`hue-rotate`](https://github.com/tool3/vctrfx/blob/master/examples/effects/hue-rotate.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/hue-rotate.before.svg\" width=\"180\" alt=\"hue-rotate before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/hue-rotate.after.svg\" width=\"180\" alt=\"hue-rotate after\"> |\n| [`invert`](https://github.com/tool3/vctrfx/blob/master/examples/effects/invert.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/invert.before.svg\" width=\"180\" alt=\"invert before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/invert.after.svg\" width=\"180\" alt=\"invert after\"> |\n| [`brightness`](https://github.com/tool3/vctrfx/blob/master/examples/effects/brightness.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/brightness.before.svg\" width=\"180\" alt=\"brightness before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/brightness.after.svg\" width=\"180\" alt=\"brightness after\"> |\n| [`contrast`](https://github.com/tool3/vctrfx/blob/master/examples/effects/contrast.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/contrast.before.svg\" width=\"180\" alt=\"contrast before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/contrast.after.svg\" width=\"180\" alt=\"contrast after\"> |\n| [`sepia`](https://github.com/tool3/vctrfx/blob/master/examples/effects/sepia.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/sepia.before.svg\" width=\"180\" alt=\"sepia before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/sepia.after.svg\" width=\"180\" alt=\"sepia after\"> |\n| [`fade`](https://github.com/tool3/vctrfx/blob/master/examples/effects/fade.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/fade.before.svg\" width=\"180\" alt=\"fade before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/fade.after.svg\" width=\"180\" alt=\"fade after\"> |\n| [`posterize`](https://github.com/tool3/vctrfx/blob/master/examples/effects/posterize.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/posterize.before.svg\" width=\"180\" alt=\"posterize before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/posterize.after.svg\" width=\"180\" alt=\"posterize after\"> |\n| [`threshold`](https://github.com/tool3/vctrfx/blob/master/examples/effects/threshold.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/threshold.before.svg\" width=\"180\" alt=\"threshold before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/threshold.after.svg\" width=\"180\" alt=\"threshold after\"> |\n| [`duotone`](https://github.com/tool3/vctrfx/blob/master/examples/effects/duotone.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/duotone.before.svg\" width=\"180\" alt=\"duotone before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/duotone.after.svg\" width=\"180\" alt=\"duotone after\"> |\n| [`tint`](https://github.com/tool3/vctrfx/blob/master/examples/effects/tint.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/tint.before.svg\" width=\"180\" alt=\"tint before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/tint.after.svg\" width=\"180\" alt=\"tint after\"> |\n| [`grain`](https://github.com/tool3/vctrfx/blob/master/examples/effects/grain.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/grain.before.svg\" width=\"180\" alt=\"grain before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/grain.after.svg\" width=\"180\" alt=\"grain after\"> |\n| [`scanlines`](https://github.com/tool3/vctrfx/blob/master/examples/effects/scanlines.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/scanlines.before.svg\" width=\"180\" alt=\"scanlines before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/scanlines.after.svg\" width=\"180\" alt=\"scanlines after\"> |\n| [`chromatic-aberration`](https://github.com/tool3/vctrfx/blob/master/examples/effects/chromatic-aberration.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/chromatic-aberration.before.svg\" width=\"180\" alt=\"chromatic-aberration before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/chromatic-aberration.after.svg\" width=\"180\" alt=\"chromatic-aberration after\"> |\n| [`glitch`](https://github.com/tool3/vctrfx/blob/master/examples/effects/glitch.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/glitch.before.svg\" width=\"180\" alt=\"glitch before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/glitch.after.svg\" width=\"180\" alt=\"glitch after\"> |\n| [`pixelate`](https://github.com/tool3/vctrfx/blob/master/examples/effects/pixelate.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/pixelate.before.svg\" width=\"180\" alt=\"pixelate before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/pixelate.after.svg\" width=\"180\" alt=\"pixelate after\"> |\n| [`halftone`](https://github.com/tool3/vctrfx/blob/master/examples/effects/halftone.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/halftone.before.svg\" width=\"180\" alt=\"halftone before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/halftone.after.svg\" width=\"180\" alt=\"halftone after\"> |\n| [`vignette`](https://github.com/tool3/vctrfx/blob/master/examples/effects/vignette.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/vignette.before.svg\" width=\"180\" alt=\"vignette before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/vignette.after.svg\" width=\"180\" alt=\"vignette after\"> |\n| [`outline`](https://github.com/tool3/vctrfx/blob/master/examples/effects/outline.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/outline.before.svg\" width=\"180\" alt=\"outline before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/outline.after.svg\" width=\"180\" alt=\"outline after\"> |\n| [`wave`](https://github.com/tool3/vctrfx/blob/master/examples/effects/wave.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/wave.before.svg\" width=\"180\" alt=\"wave before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/wave.after.svg\" width=\"180\" alt=\"wave after\"> |\n| [`emboss`](https://github.com/tool3/vctrfx/blob/master/examples/effects/emboss.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/emboss.before.svg\" width=\"180\" alt=\"emboss before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/emboss.after.svg\" width=\"180\" alt=\"emboss after\"> |\n| [`sharpen`](https://github.com/tool3/vctrfx/blob/master/examples/effects/sharpen.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/sharpen.before.svg\" width=\"180\" alt=\"sharpen before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/sharpen.after.svg\" width=\"180\" alt=\"sharpen after\"> |\n\n### Preset examples\n\n| Example | Before | After |\n| ------- | ------ | ----- |\n| [`crt`](https://github.com/tool3/vctrfx/blob/master/examples/presets/crt.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/crt.before.svg\" width=\"180\" alt=\"crt before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/crt.after.svg\" width=\"180\" alt=\"crt after\"> |\n| [`cyberpunk`](https://github.com/tool3/vctrfx/blob/master/examples/presets/cyberpunk.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/cyberpunk.before.svg\" width=\"180\" alt=\"cyberpunk before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/cyberpunk.after.svg\" width=\"180\" alt=\"cyberpunk after\"> |\n| [`film-3d`](https://github.com/tool3/vctrfx/blob/master/examples/presets/film-3d.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/film-3d.before.svg\" width=\"180\" alt=\"film-3d before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/film-3d.after.svg\" width=\"180\" alt=\"film-3d after\"> |\n| [`film`](https://github.com/tool3/vctrfx/blob/master/examples/presets/film.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/film.before.svg\" width=\"180\" alt=\"film before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/film.after.svg\" width=\"180\" alt=\"film after\"> |\n| [`neon`](https://github.com/tool3/vctrfx/blob/master/examples/presets/neon.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/neon.before.svg\" width=\"180\" alt=\"neon before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/neon.after.svg\" width=\"180\" alt=\"neon after\"> |\n| [`newsprint`](https://github.com/tool3/vctrfx/blob/master/examples/presets/newsprint.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/newsprint.before.svg\" width=\"180\" alt=\"newsprint before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/newsprint.after.svg\" width=\"180\" alt=\"newsprint after\"> |\n| [`riso`](https://github.com/tool3/vctrfx/blob/master/examples/presets/riso.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/riso.before.svg\" width=\"180\" alt=\"riso before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/riso.after.svg\" width=\"180\" alt=\"riso after\"> |\n| [`vhs`](https://github.com/tool3/vctrfx/blob/master/examples/presets/vhs.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/vhs.before.svg\" width=\"180\" alt=\"vhs before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/vhs.after.svg\" width=\"180\" alt=\"vhs after\"> |\n| [`xerox`](https://github.com/tool3/vctrfx/blob/master/examples/presets/xerox.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/xerox.before.svg\" width=\"180\" alt=\"xerox before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/xerox.after.svg\" width=\"180\" alt=\"xerox after\"> |\n\n### Animated examples\n\nApplied to `examples/sources/motion.svg`, which loops on its own in plain SMIL. Both\ncolumns are playing: the source keeps its motion, and the second adds vctrfx's on top.\n\n| Example | Before | After |\n| ------- | ------ | ----- |\n| [`motion-crt`](https://github.com/tool3/vctrfx/blob/master/examples/animated/preset-over-motion.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/motion-crt.before.svg\" width=\"240\" alt=\"motion-crt before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/motion-crt.after.svg\" width=\"240\" alt=\"motion-crt after\"> |\n| [`motion-layered`](https://github.com/tool3/vctrfx/blob/master/examples/animated/layered-motion.ts) | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/motion-layered.before.svg\" width=\"240\" alt=\"motion-layered before\"> | <img src=\"https://raw.githubusercontent.com/tool3/vctrfx/master/examples/svgs/motion-layered.after.svg\" width=\"240\" alt=\"motion-layered after\"> |\n\n## The output keeps your artwork's shape\n\nAn SVG with rounded corners should come back with the same rounded corners. Two things\nwould otherwise break that, and vctrfx handles both.\n\n**Overlays.** `scanlines`, `vignette` and `halftone` lay something *over* the drawing,\nand a naive overlay is a rectangle that paints straight over your corners.\n\n**Filters.** `bloom`, `glow`, `chromaticAberration` and friends bleed *outward* by\ndesign. On a rounded card that bleed lands outside the corner arc as a soft halo and\ncolour fringing, so the silhouette stops being yours.\n\nSo vctrfx works out the shape your artwork actually has, and holds everything to it:\n\n1. **A frame it can measure.** If your artwork sits on a full-bleed backdrop — a\n   `<rect>` filling the viewBox, rounded or not, however deeply nested in groups — that\n   rect's geometry becomes a `clipPath`, corner radius included. Overlays clip to it,\n   and so does the finished result, so filter bleed is trimmed at the same arc.\n2. **A clip you already declared.** A `clip-path` on the root `<svg>` is reused as-is.\n3. **Anything else.** For artwork with no frame, overlays fall back to a mask built from\n   the drawing's own alpha, and nothing is clipped globally — a glow on a logo still\n   glows outward, which is the point of it.\n\nMeasured on a rounded terminal card with the `crt` preset: without this, 1853 pixels of\nthe silhouette differ from the input. With it, 2 — both antialiasing on the arc.\n\n```ts\nvctrfx(roundedCard, [crt()])\n```\n\nTwo escape hatches, at different levels:\n\n```ts\nvctrfx(source, [scanlines({ clip: 'viewport' })])   // this overlay fills the frame\nvctrfx(source, [glow()], { clip: 'none' })          // let everything spill past the frame\n```\n\nThe measured-frame path is free — one `clipPath`, shared by every effect that needs it.\nThe alpha fallback costs one extra render of the artwork per overlay effect.\n\n## Motion\n\nEffects with an `animate` option emit their own `<animate>` elements and CSS\nkeyframes into the output. Nothing is attached at runtime, so the file animates on its\nown — as an `<img src>`, a CSS `background-image`, or inlined in a page.\n\n```ts\nconst banner = vctrfx(source, [scanlines({ animate: true, speed: 4 }), grain({ animate: true })])\n```\n\nTurn all of it off in one place when you need a still frame:\n\n```ts\nvctrfx(source, [crt({ animate: true })], { animate: false })\n```\n\n### Animated SVGs go in, animated SVGs come out\n\nAn input that already animates keeps animating. `<animate>`, `<animateTransform>`,\n`<set>` and CSS keyframes in the source are carried through untouched — the artwork is\nwrapped, never rewritten — and the effect is applied to every frame as it plays.\n\n```ts\nconst styled = vctrfx(spinner, [crt()])\n```\n\nYour motion and vctrfx's motion compose, in one self-contained file:\n\n```ts\nconst banner = vctrfx(spinner, [\n  bloom({ radius: 6, threshold: 0.45 }),\n  glitch({ intensity: 0.6, animate: true }),\n  scanlines({ gap: 3, animate: true }),\n  grain({ amount: 0.35, animate: true }),\n])\n```\n\nBoth are in the [animated examples](#animated-examples) above, rendered from a looping source.\n\n## Naming effects instead of importing them\n\nEvery effect and preset is reachable by name, so a stack can be written as a plain\nstring or a JSON-safe object and still produce exactly what the imports would.\n\n```ts\nimport { applyEffects, resolveEffects, vctrfx } from 'vctrfx'\n\napplyEffects(source, 'crt')\napplyEffects(source, 'scanlines(gap:3,opacity:.35) grain(amount:.4)')\napplyEffects(source, [{ scanlines: { gap: 3 } }, 'grain', crt()])\napplyEffects(source, { use: 'crt', seed: 'hero', animate: false })\n```\n\n`resolveEffects` returns `{ effects, settings }` if you would rather hand the result to\n`vctrfx()` or `createPipeline()` yourself.\n\n**The syntax**\n\n| Written | Means |\n| ------- | ----- |\n| `crt` | The effect or preset by that name, with its own defaults |\n| `scanlines(gap: 3)` | Arguments as `key: value`, comma separated. `=` also works |\n| `grain(monochrome)` | A bare key is `true` |\n| `halftone(background: none)` | `none` and `null` are `null`; `#1c1c1c` stays a colour |\n| `crt(scanlines.gap: 3)` | A dotted key reaches inside a preset, leaving the rest of the recipe alone |\n| `scanlines grain` | A stack, applied left to right. `,`, `+` and `\\|` separate too |\n\nNames are matched loosely — `chromatic-aberration`, `chromaticAberration` and\n`chromatic` all land on the same effect — and a typo is an error with a suggestion\nrather than a silently missing effect:\n\n```\nVctrfxError: Unknown effect \"scanline\". Did you mean \"scanlines\"?\n```\n\n`EFFECT_NAMES` and `PRESET_NAMES` list what is available, which is what the CLIs\nbuilt on this print for `--list-effects`.\n\n## Writing your own effect\n\nAn effect is a name plus a list of stages, and there are two kinds of stage. A\n**filter stage** contributes SVG filter primitives; consecutive filter stages are merged\ninto a single `<filter>` element automatically. A **layer stage** restructures the\nartwork — wrapping it, duplicating it, or laying something over it.\n\n```ts\nimport { defineEffect, filterStage, series } from 'vctrfx'\n\nexport const solarize = ({ level = 0.5 } = {}) =>\n  defineEffect('solarize', [\n    filterStage(\n      (io) => ({\n        primitives: series(io, [\n          { name: 'feComponentTransfer', children: [/* ... */] },\n          { name: 'feColorMatrix', attributes: { type: 'saturate', values: 1.4 } },\n        ]),\n      }),\n      { margin: 10 },\n    ),\n  ])\n```\n\n`series` wires `in` and `result` down the chain for you. Reach for `io.input` and\n`io.output` directly when an effect needs to branch — `bloom` and `chromaticAberration`\nare worked examples.\n\nA layer stage receives the current artwork and returns the new artwork, plus anything\nit needs in `<defs>` or in a `<style>` block:\n\n```ts\nimport { cover, defineEffect, group, layerStage } from 'vctrfx'\n\nexport const wash = ({ color = '#ff2d55' } = {}) =>\n  defineEffect('wash', [\n    layerStage((content, context) => ({\n      content: group([content, cover(context.viewport, { fill: color, opacity: 0.2 })]),\n    })),\n  ])\n```\n\nThe `context` handed to every stage carries the resolved `viewport`, a seeded\n`random(key)` and `range(min, max, key)`, and `uid(hint)` for collision-free ids.\nRandomness is keyed rather than sequential, which is what keeps output byte-stable.\n\n## Recipes\n\n**React**\n\n```tsx\nimport { useMemo } from 'react'\nimport { vctrfx, crt } from 'vctrfx'\n\nconst Poster = ({ svg }: { svg: string }) => {\n  const html = useMemo(() => vctrfx(svg, [crt()]), [svg])\n  return <div dangerouslySetInnerHTML={{ __html: html }} />\n}\n```\n\n**Build step**\n\n```js\nimport { readdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { createPipeline, duotone, grain } from 'vctrfx'\n\nconst brand = createPipeline([duotone(), grain()], { format: 'minify' })\n\nreaddirSync('icons').forEach((file) => {\n  writeFileSync(`dist/${file}`, brand.apply(readFileSync(`icons/${file}`, 'utf8')))\n})\n```\n\n**In the browser**\n\n```ts\nconst styled = vctrfx(await fetch('/logo.svg').then((response) => response.text()), [neon()])\ndocument.querySelector('#logo')!.innerHTML = styled\n```\n\n## Notes\n\n- **Browser support.** Everything is standard SVG 1.1 filters plus `mix-blend-mode`,\n  which covers every current browser. `pixelate` leans on `feTile` and renders slightly\n  differently in Safari.\n- **Resolution.** Browsers rasterize filter regions at the SVG's own coordinate scale.\n  If a filtered SVG is displayed much larger than its `viewBox`, the filtered parts can\n  soften. Author at the size you intend to display, or scale the `viewBox` up.\n- **Ids.** Generated ids are namespaced per document, so several vctrfx outputs can live\n  in one page. Pass `scope` if you want to name that namespace yourself.\n- **Structure.** Non-rendering nodes — `<title>`, `<desc>`, `<defs>`, `<style>`,\n  `<metadata>` — are left where they are. Only the drawn content is wrapped.\n\n## Development\n\n```bash\nnpm test           # 126 tests, no test framework needed\nnpm run typecheck\nnpm run build      # ESM + CJS + types\nnpm run examples   # renders examples/svgs\nnpm run gallery    # renders examples/gallery.html\n```\n\nThe repo is TypeScript end to end — source, tests, examples and build scripts. Nothing\nis written in JavaScript, and Node runs all of it directly.\n\n## License\n\nMIT © Tal Hayut\n\n\n<!-- figlet fonts to use -->\n<!-- Slant Relief -->\n<!-- Terrace -->\n<!-- ANSI Compact -->\n<!-- ANSI Regular -->\n<!-- ANSI Shadow -->\n<!-- Coder Mini -->\n<!-- Rebel -->\n<!-- Broadway -->\n<!-- DOS Rebel -->\n<!-- Georgia11 -->\n<!-- Big Money-ne -->\n<!-- Shaded Blocky -->\n<!-- Sub-Zero -->","readmeFilename":"README.md"}