{"_id":"@carlwhittick/squiggle","name":"@carlwhittick/squiggle","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@carlwhittick/squiggle","version":"1.0.0","description":"Canvas-drawn animated squiggle underlines with ripple effects on hover/click","type":"module","main":"./dist/squiggle.umd.cjs","module":"./dist/squiggle.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/squiggle.js","require":"./dist/squiggle.umd.cjs","types":"./dist/index.d.ts"}},"scripts":{"dev":"vite build --watch","build":"tsc --noEmit && vite build","prepublishOnly":"npm run build"},"keywords":["squiggle","underline","canvas","ripple","animation","text-decoration"],"author":{"name":"Carl Whittick"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/carlwhittick/squiggle.git"},"devDependencies":{"typescript":"^5.0.0","vite":"^8.0.0","vite-plugin-dts":"^4.0.0"},"gitHead":"5f6c43581d472ba3a003baeae88927a3b89c3eb4","_id":"@carlwhittick/squiggle@1.0.0","bugs":{"url":"https://github.com/carlwhittick/squiggle/issues"},"homepage":"https://github.com/carlwhittick/squiggle#readme","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-CYZIawhk6ubrGtN6zvDQ45r3hP69tbTseZtsjEG7klyCY8C4FqdF/UpChqVnB5am2bI+cU8z1pW2p1x1UIxcTA==","shasum":"1c98d7976c28d16903f23daa8e4476cb1bd8cd1f","tarball":"https://registry.npmjs.org/@carlwhittick/squiggle/-/squiggle-1.0.0.tgz","fileCount":11,"unpackedSize":28547,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAnrzS5zkD7zsvBYSxpAlxpfBsTk2eWszq7kjFOVzOHOAiBtg2yJnb0OQle0qD+KBgDLuBbsZHhbtbCS2WX2RBIhUA=="}]},"_npmUser":{"name":"carlwhittick","email":"carl.whittick@gmail.com"},"directories":{},"maintainers":[{"name":"carlwhittick","email":"carl.whittick@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/squiggle_1.0.0_1782190884045_0.08666909043322191"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-23T05:01:23.837Z","1.0.0":"2026-06-23T05:01:24.524Z","modified":"2026-06-23T05:01:24.765Z"},"maintainers":[{"name":"carlwhittick","email":"carl.whittick@gmail.com"}],"description":"Canvas-drawn animated squiggle underlines with ripple effects on hover/click","homepage":"https://github.com/carlwhittick/squiggle#readme","keywords":["squiggle","underline","canvas","ripple","animation","text-decoration"],"repository":{"type":"git","url":"git+https://github.com/carlwhittick/squiggle.git"},"author":{"name":"Carl Whittick"},"bugs":{"url":"https://github.com/carlwhittick/squiggle/issues"},"license":"MIT","readme":"# @carlwhittick/squiggle\n\nCanvas-drawn animated squiggle underlines for links and text. Ripples propagate outward from the hover/click origin and dampen over time. Skips ink around glyphs like native `text-decoration`.\n\n## Install\n\n```bash\nnpm install @carlwhittick/squiggle\n```\n\n## Usage\n\n```ts\nimport { squiggleUnderline } from '@carlwhittick/squiggle';\n\nconst instance = squiggleUnderline('a, .link');\n\n// Clean up (e.g. on SPA route change)\ninstance.destroy();\n```\n\n## Options\n\n```ts\nsquiggleUnderline(selector, options?)\n```\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `amplitudeEm` | `number` | `0.2` | Wave half-amplitude as em multiplier |\n| `wavelengthBase` | `number` | `16` | Base wavelength in px |\n| `wavelengthEm` | `number` | `0.5` | Wavelength em contribution |\n| `frequency` | `number` | `4` | Oscillation frequency (Hz) |\n| `propagationSpeed` | `number` | `300` | Ripple travel speed (px/s) |\n| `spatialDamping` | `number` | `0.012` | Amplitude decay with distance |\n| `timeDamping` | `number` | `2.5` | Amplitude decay over time |\n| `thicknessEm` | `number` | `0.075` | Stroke thickness as em multiplier |\n| `offsetYEm` | `number` | `-0.05` | Vertical offset from text baseline as em multiplier |\n| `skipInkGapEm` | `number` | `0.1` | Gap around glyphs as em multiplier |\n\n## How it works\n\nFor each matching element:\n\n1. CSS `text-decoration` is suppressed and replaced with an absolutely-positioned `<canvas>`.\n2. Text is rendered offscreen to build a pixel mask; the canvas line skips columns where glyph pixels are detected (skip-ink).\n3. On `mouseenter` / `click` / `focus`, a ripple origin is recorded. Each animation frame sums wave displacement from all active ripples using a damped travelling-wave equation.\n4. A `ResizeObserver` keeps the canvas in sync with element layout changes.\n\n## Returns\n\n```ts\n{ destroy: () => void }\n```\n\nCall `destroy()` to cancel animation frames, remove canvases, and unbind all listeners.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-a689195b0d4f0deaa6274f091f196892"}