{"_id":"@bihibar/blip","name":"@bihibar/blip","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@bihibar/blip","version":"1.0.0","description":"Blip — a fluid, zero-dependency animated mascot for React with three shapes, ten expressions, and a config you can copy from the Lab.","packageManager":"bun@1.3.14","type":"module","main":"./dist-package/index.js","module":"./dist-package/index.js","types":"./dist-package/index.d.ts","exports":{".":{"types":"./dist-package/index.d.ts","import":"./dist-package/index.js"}},"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"dev":"vite","build":"tsc --noEmit && vite build","build:package":"vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","check":"bun run typecheck && bun run test && bun run build && bun run build:package","prepublishOnly":"bun run check","preview":"vite preview"},"keywords":["blip","mascot","react","animation","svg","character","expressions"],"author":{"name":"bihibar"},"license":"ISC","peerDependencies":{"react":">=18.2.0","react-dom":">=18.2.0"},"devDependencies":{"@tailwindcss/vite":"^4.3.3","@testing-library/react":"^16.3.3","@types/node":"^26.0.0","@types/react":"^19.2.18","@types/react-dom":"^19.2.5","@vitejs/plugin-react":"^6.1.1","dialkit":"^1.4.3","happy-dom":"^20.13.2","motion":"^13.2.0","react":"^19.2.8","react-dom":"^19.2.8","tailwindcss":"^4.3.3","typescript":"^6.0.3","vite":"^8.2.2","vitest":"^4.1.11"},"_id":"@bihibar/blip@1.0.0","_nodeVersion":"26.4.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-qogI1DMbGo41R+NE6ftgcRM6ujnwa8fkkogFZZ0aRxNO3ROL+Ul9zKXnoKtaPN3YNG051yeTUBl+zJgEegkzVg==","shasum":"927841db17fed85dc06ab040bbcdba2050f0a621","tarball":"https://registry.npmjs.org/@bihibar/blip/-/blip-1.0.0.tgz","fileCount":31,"unpackedSize":391737,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDt326+9b/YJWm52Pndq/7cd/87cVoU72U/CW6pPAjzBgIhAN2t8DVoFVQlWTaF0wN3IL3Ct9J8JwZmC/FO6GGwVeth"}]},"_npmUser":{"name":"bihibar","email":"bihib4r@gmail.com"},"directories":{},"maintainers":[{"name":"bihibar","email":"bihib4r@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/blip_1.0.0_1788426332555_0.6786644940679407"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-03T09:05:31.836Z","1.0.0":"2026-09-03T09:05:32.751Z","modified":"2026-09-03T09:05:33.176Z"},"maintainers":[{"name":"bihibar","email":"bihib4r@gmail.com"}],"description":"Blip — a fluid, zero-dependency animated mascot for React with three shapes, ten expressions, and a config you can copy from the Lab.","keywords":["blip","mascot","react","animation","svg","character","expressions"],"author":{"name":"bihibar"},"license":"ISC","readme":"# @bihibar/blip\n\nBlip is an animated SVG mascot for React. It ships three body shapes, ten\nexpressions, and a small engine that morphs every drawn feature (body, eyes,\nbrows, mouth) between them. One `requestAnimationFrame` loop per mascot writes\nattributes straight to the DOM; React renders once. The package has no runtime\ndependencies besides React.\n\n## Install\n\n```bash\nnpm install @bihibar/blip\n# or\nbun add @bihibar/blip\n```\n\nReact 18.2 or newer is required (peer dependency). The package is ESM only and\nships its own type declarations.\n\n## Render\n\n```tsx\nimport { Blip } from '@bihibar/blip';\n\nexport function WelcomeBlip() {\n  return <Blip state=\"happy\" shape=\"fin\" size={240} />;\n}\n```\n\n`Blip` renders a square wrapper `<div>` containing an\n`<svg viewBox=\"0 0 400 400\" role=\"img\">`. Change `state` or `shape` at any time;\nthe mascot morphs from whatever is currently on screen, so rapid changes never\njump.\n\n### Props\n\n| Prop | Type | Default | Meaning |\n| --- | --- | --- | --- |\n| `state` | `BlipState` | `'neutral'` | Expression to show. |\n| `shape` | `BlipShape` | `config.shape` | Body silhouette. Overrides `config.shape`. |\n| `config` | `DeepPartial<BlipConfig>` | `{}` | Overrides merged onto `DEFAULT_BLIP_CONFIG`. |\n| `size` | `number \\| string` | `280` | Width and height. Numbers are pixels; strings are any CSS length. |\n| `className` | `string` | | Applied to the wrapper `<div>`. |\n| `style` | `React.CSSProperties` | | Applied to the wrapper `<div>`. |\n| `frozenAt` | `number` | | Render one frame at this scene time (seconds) and run no animation loop. |\n| `seed` | `number` | hash of `useId()` | Seeds the blink and glance schedules so several mascots on a page do not blink in unison. |\n| `onFrame` | `(t: number) => void` | | Called once per frame with the scene clock in seconds. |\n| `aria-label` | `string` | `\"Blip feeling <state>\"` | Accessible name of the SVG. |\n\n## Shapes\n\n| id | Label | Color |\n| --- | --- | --- |\n| `bunny` | Bunny 🐰 | `#1000FF` |\n| `hash` | Hash #️⃣ | `#9E97FB` |\n| `fin` | Fin 🌿 | `#7DF799` |\n\n`bunny` is the default. Every shape is normalized into the same 400 × 400 frame\nand carries its own face anchors (eye centers, eye size and tilt, mouth position\nand width). Switching shapes morphs the silhouette and the face over\n`transition.shapeDuration`.\n\n```tsx\n<Blip shape=\"hash\" />\n<Blip config={{ shape: 'fin' }} />\n```\n\n`BLIP_SHAPES` is the ordered list of ids; `SHAPES[id]` / `getShape(id)` return\nthe `ShapeDef` (label, emoji, color, outline, face anchors, bounds).\n\n## States\n\n| State | Emoji | Face | Body / extras |\n| --- | --- | --- | --- |\n| `neutral` | 😐 | oval eyes, straight mouth | gentle float |\n| `happy` | 😊 | soft smile, blush | livelier float |\n| `excited` | 😄 | open `D` mouth, bigger eyes | bouncing with squash, sparkles |\n| `joy` | 😆 | `> <` chevron eyes, `ω` mouth | wiggle |\n| `sad` | 😢 | drooping eyes, brows, frown | slumps and breathes slowly |\n| `crying` | 😭 | chevron eyes, brows, wailing mouth | tremble, tears |\n| `angry` | 😠 | slanted eyes, brows, tight frown | shake bursts, 💢 mark |\n| `sleepy` | 😴 | half-closed eyes, small `o` mouth | tilts, slow breathing, zzz |\n| `wink` | 😉 | one eye arched, offset smile | slight tilt |\n| `surprised` | 😮 | wide eyes, raised brows, `O` mouth | lifts up |\n\n`BLIP_STATES` is the ordered list of ids. `EXPRESSION_META[state]` gives the\n`{ label, emoji }` pair for building state pickers.\n\n## Configure\n\n`config` is a deep partial: pass only the keys you want to change. Objects are\nmerged recursively, arrays are replaced, `undefined` is ignored.\n\n```tsx\n<Blip\n  state=\"excited\"\n  config={{\n    shape: 'hash',\n    body: { tintByState: true },\n    face: { eyeSize: 1.15, blush: 0.8 },\n    motion: { eyesFollowCursor: false },\n  }}\n/>\n```\n\n### Config reference\n\nTop level:\n\n| Key | Type | Default | Meaning |\n| --- | --- | --- | --- |\n| `shape` | `BlipShape` | `'bunny'` | Body silhouette. The `shape` prop overrides it. |\n| `statePalettes` | `Partial<Record<BlipState, BlipStatePalette>>` | `undefined` | Absolute color overrides per state (`{ body?, face?, blush? }`). Wins over `body.tintByState`. |\n\n`body`:\n\n| Key | Type | Default | Meaning |\n| --- | --- | --- | --- |\n| `body.color` | `hex \\| 'auto'` | `'auto'` | Body fill. `'auto'` uses the shape's own color. |\n| `body.tintByState` | `boolean` | `false` | Mix the body color toward a per-state tint (angry → red, sad → blue, …). |\n| `body.shade` | `number` | `0.18` | 0–1. Strength of the soft radial shading toward the silhouette edge. |\n| `body.shadeColor` | `hex \\| 'auto'` | `'auto'` | Edge color of the shading. `'auto'` derives a darker version of the body. |\n| `body.squash` | `number` | `0.6` | 0–1. Squash-and-stretch amount applied to bounces and landings. |\n\n`face`:\n\n| Key | Type | Default | Meaning |\n| --- | --- | --- | --- |\n| `face.color` | `hex` | `'#FFFFFF'` | Eye, mouth and brow color. Ignored when `cutout` is on. |\n| `face.cutout` | `boolean` | `false` | Render the face as holes cut through the body (the page shows through). |\n| `face.scale` | `number` | `1` | Uniform scale of the whole face around its center. |\n| `face.offsetX` | `number` | `0` | Horizontal face offset in viewBox units (the viewBox is 400 × 400). |\n| `face.offsetY` | `number` | `0` | Vertical face offset in viewBox units. |\n| `face.eyeSize` | `number` | `1` | Eye size multiplier (≈0.75 for dots, ≈1.25 for big ovals). |\n| `face.eyeSpacing` | `number` | `1` | Eye spacing multiplier. |\n| `face.mouthScale` | `number` | `1` | Mouth size multiplier. |\n| `face.mouthOffsetY` | `number` | `0` | Extra vertical offset of the mouth in viewBox units. |\n| `face.strokeWidth` | `number` | `1` | Stroke thickness multiplier for mouth lines and brows. |\n| `face.blush` | `number` | `0.6` | 0–1. Blush intensity, multiplied by each expression's own blush factor. |\n| `face.blushColor` | `hex` | `'#FF7EB3'` | Blush color. |\n| `face.brows` | `boolean` | `true` | Show brows on the expressions that use them (sad, crying, angry, surprised). |\n| `face.mouth` | `boolean` | `false` | Draw the mouth. Off by default: every expression is carried by the eyes alone. |\n| `face.interiorColor` | `hex \\| 'auto'` | `'auto'` | Interior of open mouths. `'auto'` uses the body color (outline look). |\n\n`motion`:\n\n| Key | Type | Default | Meaning |\n| --- | --- | --- | --- |\n| `motion.idleSpeed` | `number` | `1` | Global idle speed multiplier. |\n| `motion.breathe` | `number` | `0.6` | 0–1. Breathing amplitude. |\n| `motion.bob` | `number` | `6` | Vertical float amplitude in viewBox units. |\n| `motion.sway` | `number` | `2` | Sway rotation amplitude in degrees. |\n| `motion.blink` | `boolean` | `true` | Blink at all. |\n| `motion.blinkInterval` | `number` | `3.6` | Average seconds between blinks. |\n| `motion.eyesFollowCursor` | `boolean` | `true` | Eyes (and slightly the head) follow the pointer. |\n| `motion.eyeWander` | `boolean` | `true` | Eyes drift and glance around when nothing is being followed. |\n| `motion.gazeRange` | `number` | `0.35` | How far the eyes can slide, as a fraction of the eye width. |\n| `motion.headFollow` | `number` | `0.6` | 0–1. How much the head turns and the face shifts toward the gaze target. |\n| `motion.gazeStiffness` | `number` | `120` | Spring stiffness of gaze motion. |\n| `motion.gazeDamping` | `number` | `14` | Spring damping of gaze motion. |\n| `motion.extras` | `boolean` | `true` | Tears, sparkles, zzz and the anger mark. |\n| `motion.reactToState` | `boolean` | `true` | Per-state body behaviour: bouncing when excited, shaking when angry, drooping when sad, … |\n\n`transition`:\n\n| Key | Type | Default | Meaning |\n| --- | --- | --- | --- |\n| `transition.duration` | `number` | `0.42` | Seconds for an expression change. |\n| `transition.shapeDuration` | `number` | `0.6` | Seconds for a shape change. |\n| `transition.overshoot` | `number` | `0.15` | 0–1. Mixes a little overshoot into expression changes (0 = pure ease-out). |\n\nSee [`docs/mascot/config.md`](docs/mascot/config.md) for how colors resolve and\nthe per-state tint table.\n\n### State palettes\n\n`statePalettes` sets absolute colors for individual states. Each entry is\n`{ body?, face?, blush? }`; missing fields fall back to `body.color` /\n`face.color` / `face.blushColor`. Palette colors tween over the transition like\neverything else.\n\n```tsx\n<Blip\n  config={{\n    statePalettes: {\n      angry: { body: '#E5484D', face: '#FFF7F7' },\n      sleepy: { body: '#5B5F97', blush: '#C7B8FF' },\n    },\n  }}\n/>\n```\n\n## BlipScene\n\n`BlipScene` centers a `Blip` over a soft radial-gradient background derived\nfrom the body color of the current state, so the backdrop follows palette and\ntint changes. It takes the same props as `Blip` plus `background` (default\n`true`). `className` and `style` apply to the scene wrapper; give it a size and\nthe mascot is centered inside at `size`.\n\n```tsx\nimport { BlipScene } from '@bihibar/blip';\n\n<BlipScene state=\"sleepy\" size={280} style={{ width: '100%', height: 420 }} />;\n<BlipScene state=\"joy\" background={false} />;\n```\n\n## Static frames with `frozenAt`\n\n`frozenAt` renders exactly one frame at the given scene time and starts no\nanimation loop or event listeners. Use it for thumbnails, state pickers, tests\nand server-rendered illustrations. Pair it with `seed` for byte-identical\noutput across renders.\n\n```tsx\n<Blip state=\"joy\" frozenAt={1.2} seed={7} size={88} />\n```\n\nWithout `frozenAt`, the first paint is still rendered synchronously from the\nengine at `t = 0` (so SSR output is a correct, complete mascot) and the loop\nstarts after mount.\n\n## Presets with `defineBlipConfig`\n\nDeclare a reusable preset once and pass small overrides only where a placement\nneeds to differ. `defineBlipConfig` keeps the literal types of what you wrote;\n`createBlipConfig` resolves layers into a complete `BlipConfig` starting from\nthe defaults.\n\n```tsx\nimport { Blip, createBlipConfig, defineBlipConfig } from '@bihibar/blip';\n\nexport const brandBlip = defineBlipConfig({\n  shape: 'hash',\n  body: { color: '#635BFF', shade: 0.25 },\n  face: { color: '#FFFFFF', eyeSize: 1.1 },\n  motion: { eyesFollowCursor: false },\n  statePalettes: {\n    angry: { body: '#E5484D' },\n  },\n});\n\n<Blip state=\"happy\" config={brandBlip} />;\n<Blip state=\"happy\" config={createBlipConfig(brandBlip, { motion: { bob: 3 } })} />;\n```\n\n`mergeBlipConfig(base, override)` merges one partial onto a complete config;\n`diffBlipConfig(config)` returns the minimal partial that reproduces `config`\nfrom the defaults (this is what the Lab's \"Copy JSON\" emits).\n\n## The Lab\n\nThe Blip Lab is a Vite dashboard for tuning every config key, viewing all ten\nstates on all three shapes, and copying the result as JSON or a React snippet.\n\n```bash\nbun install\nbun run dev\n```\n\nOpen `http://localhost:5173`. See\n[`docs/setup/getting-started.md`](docs/setup/getting-started.md).\n\n## Accessibility and reduced motion\n\n- The SVG has `role=\"img\"` and an `aria-label` of `\"Blip feeling <state>\"`\n  that updates with the state. Override it with the `aria-label` prop.\n- `prefers-reduced-motion` is honored live via `matchMedia`. With it on, the\n  mascot keeps blinking and still changes expression (in 0.25 s, no overshoot)\n  but does not breathe, bob, sway, shake, drift or follow the pointer.\n- For an entirely static mascot use `frozenAt`.\n- Pointer following ignores touch pointers and releases when the pointer leaves\n  the document. The loop pauses while the tab is hidden.\n\n## Advanced: `MascotEngine`\n\nThe React component is a thin wrapper around `MascotEngine`, which is\nframework-free: give it a config and a clock in seconds, and `sample(now)`\nreturns a plain `Frame` of path data, transforms and colors.\n\n```ts\nimport { MascotEngine, createBlipConfig } from '@bihibar/blip';\n\nconst engine = new MascotEngine({ config: createBlipConfig(), state: 'happy', seed: 1 });\nengine.setState('surprised', 2.0);\nconst frame = engine.sample(2.2); // frame.body.d, frame.eyes[0].transform, frame.label, …\n```\n\nSee [`docs/mascot/engine.md`](docs/mascot/engine.md) for the morph model and the\n`Frame` shape.\n\n## Coming from @bihibar/bloop\n\nBlip is the successor to [`@bihibar/bloop`](https://www.npmjs.com/package/@bihibar/bloop)\n(0.1.x), rebuilt from scratch: three new shapes, expressions read from the eyes\nalone, a purpose-built engine and no runtime dependencies. It ships as a\nseparate package, so both can be installed side by side while you switch.\n\nEvery public name follows the rename: `Bloop` → `Blip`, `BloopScene` →\n`BlipScene`, `createBloopConfig` → `createBlipConfig`, `BloopConfig` →\n`BlipConfig`, `BLOOP_STATES` → `BLIP_STATES`, and so on. The config schema\nalso changed:\n\n**Removed**\n\n- `BloopBackground` and `BloopBackgroundProps`; the `background` config group\n  and `BloopBackgroundConfig`. `BlipScene` now draws a plain radial gradient\n  and takes `background?: boolean` instead of `config.background.enabled`.\n- The `motion` and `@paper-design/shaders-react` dependencies. The package has\n  no runtime dependencies besides React.\n- `body.shape` (`'blob' | 'original'`), `body.lobes`, `lobeDepth`, `spinSpeed`,\n  `shaderFill`, `colorInner`, `colorOuter`, `tintByEmotion`, `gradientX`,\n  `gradientY`, `gradientSize`, `wobble`, `wobbleSpeed`.\n- `motion.lookAround`, `lookAmount`, `lookVertical`, `lookInterval`.\n- `transition.stiffness` / `transition.damping` (transitions are eased by\n  duration now).\n\n**Renamed / replaced**\n\n| `@bihibar/bloop` | `@bihibar/blip` |\n| --- | --- |\n| `body.shape: 'blob' \\| 'original'` | top-level `shape: 'bunny' \\| 'hash' \\| 'fin'` |\n| `body.colorInner` / `body.colorOuter` | `body.color` (`hex \\| 'auto'`), plus `body.shade` / `body.shadeColor` for the edge shading |\n| `body.tintByEmotion` | `body.tintByState` |\n| `face.faceOffsetX` / `face.faceOffsetY` | `face.offsetX` / `face.offsetY` |\n| `face.blushAmount` | `face.blush` |\n| `motion.breatheAmount` / `bobAmount` / `swayAmount` | `motion.breathe` / `bob` / `sway` |\n| `motion.look*` | `motion.eyeWander` + `motion.gazeRange`, `headFollow`, `gazeStiffness`, `gazeDamping` |\n| `transition.stiffness` / `damping` | `transition.duration`, `shapeDuration`, `overshoot` |\n| `statePalettes[state]: { bodyInner, bodyOuter, background }` | `statePalettes[state]: { body, face, blush }` |\n\n**Added**\n\n- States `joy`, `crying`, `wink`, `surprised` (the six bloop states remain;\n  `BLIP_STATES` is now ordered `neutral, happy, excited, joy, sad, crying, angry, sleepy, wink, surprised`).\n- Config keys `body.squash`, `face.mouthOffsetY`, `face.strokeWidth`,\n  `face.brows`, `face.interiorColor`, `motion.extras`, `motion.reactToState`.\n- Props `shape`, `frozenAt`, `seed`, `onFrame`, `aria-label`; `size` accepts a\n  CSS string.\n- Exports `MascotEngine`, `diffBlipConfig`, `BLIP_SHAPES`, `SHAPES`,\n  `getShape`, `EXPRESSION_META` and the `Frame`, `ShapeDef`, `FaceAnchor`,\n  `LookTarget`, `MascotEngineOptions`, `BlipShape` types.\n\nUnchanged: `Blip`, `BlipScene`, `DEFAULT_BLIP_CONFIG`, `createBlipConfig`,\n`defineBlipConfig`, `mergeBlipConfig`, `BLIP_STATES`, `face.color`,\n`face.cutout`, `face.scale`, `face.eyeSize`, `face.eyeSpacing`,\n`face.mouthScale`, `face.blushColor`, `motion.idleSpeed`, `motion.blink`,\n`motion.blinkInterval`, `motion.eyesFollowCursor`, `motion.eyeWander`.\n\nFull notes in [`CHANGELOG.md`](CHANGELOG.md).\n\n## Documentation\n\n- [`docs/mascot/overview.md`](docs/mascot/overview.md) — components, props, exports\n- [`docs/mascot/config.md`](docs/mascot/config.md) — full config reference and color resolution\n- [`docs/mascot/engine.md`](docs/mascot/engine.md) — how the morphing engine works (contributors)\n- [`docs/integration/platform-integration.md`](docs/integration/platform-integration.md) — shared presets and state wiring\n- [`docs/integration/publishing.md`](docs/integration/publishing.md) — release process\n- [`docs/setup/getting-started.md`](docs/setup/getting-started.md) — run the Lab locally\n\n## License\n\nISC\n","readmeFilename":"README.md","_rev":"1-8328e17b7758dd987f456164f1facf05"}