{"_id":"@norbert_bodziony/bloub","_rev":"3-2859ddb0667908c1a57d924849e72b4f","name":"@norbert_bodziony/bloub","dist-tags":{"latest":"0.3.0"},"versions":{"0.2.0":{"name":"@norbert_bodziony/bloub","version":"0.2.0","keywords":["svg","animation","avatar","solid-js","morphing"],"author":{"name":"Jérémy Perret"},"license":"MIT","_id":"@norbert_bodziony/bloub@0.2.0","maintainers":[{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"}],"homepage":"https://bloub-app.pages.dev","bugs":{"url":"https://github.com/NorbertBodziony/bloub/issues"},"dist":{"shasum":"404ac53153b0742d3544496aa32b67f3abe0a563","tarball":"https://registry.npmjs.org/@norbert_bodziony/bloub/-/bloub-0.2.0.tgz","fileCount":20,"integrity":"sha512-Z4tkQouT0ULczET96LK4CCUW8BgNhLWJtEx5cDPu9vtn1PXmj8xuo/HTt4BG29Xfu/d2b8KhiHsRfHX+3s7EGA==","signatures":[{"sig":"MEUCICddFqKB6ilqCIKYeimWaKO40FyruPn/aoKwIpdmLbZcAiEAnylgFMd8NdSFFRj/jwy+wkJDLhC5WLbUuR8bYT8d6wM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":133297},"type":"module","types":"./dist/package/types/index.d.ts","exports":{".":{"types":"./dist/package/types/index.d.ts","import":"./dist/package/server.js","browser":"./dist/package/index.js","default":"./dist/package/server.js"}},"gitHead":"1802de609747bce01ce4b7871a262f48ebbb8104","private":false,"scripts":{"dev":"vite","test":"vitest run","build":"bun run typecheck && bun run build:app && bun run build:package","preview":"vite preview","build:app":"vite build","typecheck":"tsc --noEmit","pack:check":"bun run test:package && bun run test:tarball","test:watch":"vitest","test:package":"bun run build:package && tsc -p tsconfig.package-test.json && bun tests/package-runtime.ts","test:tarball":"bun tools/test-package-tarball.mjs","build:package":"vite build --config vite.lib.config.ts && vite build --config vite.lib.ssr.config.ts && tsc -p tsconfig.lib.json","prepublishOnly":"bun run test && bun run typecheck && bun run pack:check"},"_npmUser":{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"},"repository":{"url":"git+https://github.com/NorbertBodziony/bloub.git","type":"git"},"_npmVersion":"11.6.1","description":"SVG recreation of the x.ai bot avatar. One shape morphing through 14 states, measured off the reference video frame by frame.","directories":{},"sideEffects":false,"_nodeVersion":"24.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"bun@1.4.0","devDependencies":{"vite":"^8.2.1","vitest":"^4.1.10","solid-js":"^1.9.15","happy-dom":"^20.11.2","mediabunny":"^1.54.0","typescript":"^5.9.3","@types/node":"^26.0.0","tailwindcss":"^4.3.3","@tailwindcss/vite":"^4.3.3","vite-plugin-solid":"^2.11.14"},"peerDependencies":{"solid-js":"^1.9.15"},"_npmOperationalInternal":{"tmp":"tmp/bloub_0.2.0_1787225726426_0.09376803167203085","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@norbert_bodziony/bloub","version":"0.2.1","keywords":["svg","animation","avatar","solid-js","morphing"],"author":{"name":"Jérémy Perret"},"license":"MIT","_id":"@norbert_bodziony/bloub@0.2.1","maintainers":[{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"}],"homepage":"https://bloub-app.pages.dev","bugs":{"url":"https://github.com/NorbertBodziony/bloub/issues"},"dist":{"shasum":"0a0f7c96d394213fd6ebb7e931abc204cdcf49a1","tarball":"https://registry.npmjs.org/@norbert_bodziony/bloub/-/bloub-0.2.1.tgz","fileCount":20,"integrity":"sha512-HyCvKreqOPlpCfgvHN4CtmNYI+K8Fe9ZYh3Eoe9rc9oWesp5IXq+c6pQMyA+rRUR3/GKNjU6lO50bAaiTUcm/g==","signatures":[{"sig":"MEUCIQDFZTU+Zw2aSASNpXsus1wBYrXHRbehsdZ9bTdYy8hCvgIgP+9Nst1VVBkwppI2mXQC1HIL7KmdqsS1o5X06Nf37aY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":133629},"type":"module","types":"./dist/package/types/index.d.ts","exports":{".":{"types":"./dist/package/types/index.d.ts","import":"./dist/package/server.js","browser":"./dist/package/index.js","default":"./dist/package/server.js"}},"gitHead":"1802de609747bce01ce4b7871a262f48ebbb8104","private":false,"scripts":{"dev":"vite","test":"vitest run","build":"bun run typecheck && bun run build:app && bun run build:package","preview":"vite preview","build:app":"vite build","typecheck":"tsc --noEmit","pack:check":"bun run test:package && bun run test:tarball","test:watch":"vitest","test:package":"bun run build:package && tsc -p tsconfig.package-test.json && bun tests/package-runtime.ts","test:tarball":"bun tools/test-package-tarball.mjs","build:package":"vite build --config vite.lib.config.ts && vite build --config vite.lib.ssr.config.ts && tsc -p tsconfig.lib.json","prepublishOnly":"bun run test && bun run typecheck && bun run pack:check"},"_npmUser":{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"},"repository":{"url":"git+https://github.com/NorbertBodziony/bloub.git","type":"git"},"_npmVersion":"11.6.1","description":"SVG recreation of the x.ai bot avatar. One shape morphing through 14 states, measured off the reference video frame by frame.","directories":{},"sideEffects":false,"_nodeVersion":"24.10.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"bun@1.4.0","devDependencies":{"vite":"^8.2.1","vitest":"^4.1.10","solid-js":"2.0.0-rc.0","happy-dom":"^20.11.2","mediabunny":"^1.54.0","typescript":"^5.9.3","@types/node":"^26.0.0","tailwindcss":"^4.3.3","@solidjs/web":"2.0.0-rc.0","@tailwindcss/vite":"^4.3.3","@solidjs/vite-plugin":"3.0.0-next.30"},"peerDependencies":{"solid-js":"^2.0.0-rc.0","@solidjs/web":"^2.0.0-rc.0"},"_npmOperationalInternal":{"tmp":"tmp/bloub_0.2.1_1787227302478_0.14274002655122575","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@norbert_bodziony/bloub","private":false,"version":"0.3.0","type":"module","description":"SVG recreation of the x.ai bot avatar. One shape morphing through 14 states, measured off the reference video frame by frame.","license":"MIT","author":{"name":"Jérémy Perret"},"repository":{"type":"git","url":"git+https://github.com/NorbertBodziony/bloub.git"},"homepage":"https://bloub-app.pages.dev","publishConfig":{"access":"public"},"keywords":["svg","animation","avatar","solid-js","morphing"],"packageManager":"bun@1.4.0","types":"./dist/package/types/index.d.ts","exports":{".":{"types":"./dist/package/types/index.d.ts","browser":"./dist/package/index.js","import":"./dist/package/server.js","default":"./dist/package/server.js"}},"sideEffects":false,"scripts":{"dev":"vite","typecheck":"tsc --noEmit","build":"bun run typecheck && bun run build:app && bun run build:package","build:app":"vite build","build:package":"vite build --config vite.lib.config.ts && vite build --config vite.lib.ssr.config.ts && tsc -p tsconfig.lib.json","preview":"vite preview","test":"vitest run","test:watch":"vitest","test:package":"bun run build:package && tsc -p tsconfig.package-test.json && bun tests/package-runtime.ts","test:tarball":"bun tools/test-package-tarball.mjs","pack:check":"bun run test:package && bun run test:tarball","prepublishOnly":"bun run test && bun run typecheck && bun run pack:check"},"peerDependencies":{"@solidjs/web":"^2.0.0-rc.0","solid-js":"^2.0.0-rc.0"},"devDependencies":{"@tailwindcss/vite":"^4.3.3","@types/node":"^26.0.0","@solidjs/vite-plugin":"3.0.0-next.30","@solidjs/web":"2.0.0-rc.0","happy-dom":"^20.11.2","mediabunny":"^1.54.0","solid-js":"2.0.0-rc.0","tailwindcss":"^4.3.3","typescript":"^5.9.3","vite":"^8.2.1","vitest":"^4.1.10"},"gitHead":"da7a942f4638dcc8cd9776ec5c5390fd2a4165f9","_id":"@norbert_bodziony/bloub@0.3.0","bugs":{"url":"https://github.com/NorbertBodziony/bloub/issues"},"_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-nWqtQsX9dQ4fFipi3x0YvygFXXe3i3vcBtJovLTX5UdKK2XIGK1oAWQahXJqpOLjeVNiAGutB5SpaBf6IJoDAQ==","shasum":"ae906233e830ad733d188dd6df2c112ebead3cda","tarball":"https://registry.npmjs.org/@norbert_bodziony/bloub/-/bloub-0.3.0.tgz","fileCount":20,"unpackedSize":136657,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCQSWLS9qL9uhffI93mHhhQiWitM/ejTJKNZ186px0JzwIgY0B8LsszCgl7GmyK3XD7YGhYi6sSP2lCDfHImlldaok="}]},"_npmUser":{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"},"directories":{},"maintainers":[{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bloub_0.3.0_1788542546572_0.675155132247292"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-20T11:35:26.231Z","modified":"2026-09-04T17:22:26.896Z","0.2.0":"2026-08-20T11:35:26.635Z","0.2.1":"2026-08-20T12:01:42.632Z","0.3.0":"2026-09-04T17:22:26.738Z"},"bugs":{"url":"https://github.com/NorbertBodziony/bloub/issues"},"author":{"name":"Jérémy Perret"},"license":"MIT","homepage":"https://bloub-app.pages.dev","keywords":["svg","animation","avatar","solid-js","morphing"],"repository":{"type":"git","url":"git+https://github.com/NorbertBodziony/bloub.git"},"description":"SVG recreation of the x.ai bot avatar. One shape morphing through 14 states, measured off the reference video frame by frame.","maintainers":[{"name":"norbert_bodziony","email":"norbertbodziony@gmail.com"}],"readme":"# bloub\n\n[Live demo](https://bloub-app.pages.dev) ·\n[![CI](https://github.com/NorbertBodziony/bloub/actions/workflows/ci.yml/badge.svg)](https://github.com/NorbertBodziony/bloub/actions/workflows/ci.yml)\n\nAn SVG recreation of the x.ai bot avatar: **one filled black shape** that morphs\nbetween 14 states, **two white shapes** for the eyes that morph independently, on\na plain background. No animation library.\n\n![The avatar going through idle, wink, orbit and burst](docs/demo.gif)\n\n## Running it\n\n```bash\nbun install\nbun run dev\n```\n\nThen open http://localhost:5190.\n\n```bash\nbun run test     # Vitest\nbun run build    # strict type check, demo build, and package build\n```\n\nSolidJS, Vite, TypeScript, Tailwind 4, and Bun. No ESLint and no Prettier:\nstrict TypeScript is the source gate, so run `bun run build` before you call\nsomething done.\n\n## What's in it\n\nThe rail on the left switches between three views. **Customise** offers 8 body\nshapes, 12 colours and 16 rest expressions, kept between visits. **Animations** is\na small editor: arrange states into a timeline, set how long each is held, save the\nresult. **Settings** holds the language (French, English or Chinese) and the\ncredits.\n\nAnything on screen can be exported: the avatar as SVG, PNG or an animated GIF, and\na whole timeline as GIF or MP4. The still formats need no library at all, and the\nvideo encoder is only fetched the first time you ask for one.\n\nTwo URLs are worth knowing:\n\n- `#planche`: the 14 states side by side, frozen. Quick visual check.\n- `#etat=orbit&stop`: opens one state directly, playback paused.\n\n![The 14 states, frozen side by side](docs/states.png)\n\n## Why the numbers look arbitrary\n\nThey're measured, not chosen. The reference video was cut at 10 fps and each\nstate measured off the frames: silhouettes by sub-pixel ray casting, eyes by\ncapsule fitting, colours and stroke widths by direct sampling.\n\nSo the constants in the code are **measurements**, and rounding them to friendlier\nvalues breaks the resemblance, which is the only thing this project is trying to\nget right. A few are counter-intuitive enough to be worth knowing before you\ncorrect anything:\n\n| What you'd assume | What the video shows |\n|---|---|\n| The eyes lean `//` | They lean `\\\\`, around 26° off vertical |\n| The body is a squircle | It's a perfect circle, radial deviation under 0.7% |\n| Transitions are springs | Exponential ease-outs; the body never overshoots |\n| The comet crosses the screen | The dot stays put, the trail orbits it |\n| The avatar floats at rest | It doesn't. The life is gaze drift and blinking |\n\n[docs/measurements.md](docs/measurements.md) has the rest, including how to\nregenerate the extracted profiles.\n\n## How it's put together\n\n`src/bot/` is framework-free and clock-free: `engine.sample(t)` is a pure\nfunction of time. Pausing, resuming, jumping to an arbitrary date and running\ntests all produce the same image, which is what makes the frozen state board and\nthe DOM-less test suite possible.\n\n| | |\n|---|---|\n| [docs/architecture.md](docs/architecture.md) | The engine, radial-profile morphing, eyes as mask holes |\n| [docs/measurements.md](docs/measurements.md) | What was measured, and regenerating `profiles.ts` |\n| [docs/intro.md](docs/intro.md) | The arrival sequence, and why it only plays one state |\n| [docs/interface.md](docs/interface.md) | Layout, the three-column scene, CSS traps |\n| [docs/export.md](docs/export.md) | Exporting to SVG, PNG, GIF and MP4 |\n| [docs/i18n.md](docs/i18n.md) | The hand-rolled translation layer |\n\n## Using the package\n\n```bash\nnpm install @norbert_bodziony/bloub solid-js@2.0.0-rc.0 @solidjs/web@2.0.0-rc.0\n# or: bun add @norbert_bodziony/bloub solid-js@2.0.0-rc.0 @solidjs/web@2.0.0-rc.0\n```\n\n```tsx\nimport { createSignal } from 'solid-js'\nimport { BloubBot, defaultCycle, type BloubBotRef } from '@norbert_bodziony/bloub'\n\nconst [playing, setPlaying] = createSignal(false)\nlet bot: BloubBotRef | undefined\n\n<BloubBot\n  ref={(value) => (bot = value)}\n  cycle={defaultCycle().blocks}\n  playing={playing()}\n  onPlayingChange={setPlaying}\n/>\n```\n\n`block` is the playback cursor. A montage can play the same state twice, so the\nindex identifies the current item. `state` follows it as an output. Pass\n`frozenAt` to render one exact frame with no animation loop. The component also\nexports a ref with `svg`, `seek(index, offset?)`, and `renderAt(seconds)`.\n\nThe package contains the complete avatar engine, all measured state transitions,\nshapes, colours, expressions, cycles, and gaze helpers. It does not contain the\neditor, Tailwind, export dialogs, video encoder, or application translations.\n`solid-js` and `@solidjs/web` stay peer dependencies, so an application keeps one\nSolid runtime. The package supports SolidJS 2, starting with `2.0.0-rc.0`.\n\nProps: `size`, `shape`, `color`, `expression`, `paper`, `frozenAt`, `cycle`,\n`follow`, `gaze`, `ariaLabel`, `block`, `state`, `playing`, `elapsed`, `fps`, and\n`autoPause`. Each playback value has a matching `on...Change` callback. See\n[BloubBot.tsx](src/components/BloubBot.tsx) for the details.\n\nAn animated bot costs a style recalculation, a layout and a paint per drawn\nframe, so its price is set by how often it draws and by how many of them are on\nscreen. `fps` caps the rate — a 40 px avatar in a list is indistinguishable at 30\nfrom the screen's 120 — and `autoPause`, on by default, stops the loop when the\nbot is off screen or its tab is in the background. Neither changes what the bot\nlooks like while someone is watching it.\n\n## Changes\n\n[CHANGELOG.md](CHANGELOG.md), one entry per release — which is how you tell whether the\ncopy you have carries a given fix.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\nNot affiliated with, endorsed by or connected to x.ai. It recreates the visual\nbehaviour of their bot avatar as an exercise; \"Grok\" and \"x.ai\" belong to their\nowners. The MIT licence covers the code in this repository, not the design it\nimitates.\n","readmeFilename":"README.md"}