# obsidianui
Dithered canvas effects for React, with a playground, a showcase and a shadcn registry. Each effect renders on a small canvas, writes one `ImageData` per frame, and is scaled up with `image-rendering: pixelated`.
obsidianui is a rebrand and extension of [shad-fx](https://github.com/sekeidesign/shad-fx) by Piergiorgio Gonni. See `LICENSE` and `NOTICE.md`.
## Deploy to Vercel
1. Push this repository to GitHub (or GitLab or Bitbucket).
2. In Vercel, choose **Add New > Project** and import the repository. Leave every setting at its default: `vercel.json` already sets the framework, the install command and the build command.
3. Deploy.
Or from a terminal, with the [Vercel CLI](https://vercel.com/docs/cli):
```bash
npx vercel # preview deployment
npx vercel --prod # production deployment
```
No environment variables are required. On Vercel the build derives them:
| Variable | Default on Vercel | Used for |
| --- | --- | --- |
| `SITE_URL` | Production domain on production builds, the deployment URL on previews | Install commands on the page, URLs inside the registry, page metadata |
| `SKILLS_SOURCE` | `owner/repo` of a GitHub-connected project | The `npx skills add` line on the page. Hidden when unknown |
Set `SITE_URL` when you add a custom domain, so the install commands use it (for example `https://fx.example.com`), then redeploy. See `.env.example`.
Preview deployments are behind Vercel Authentication by default, so `shadcn add` against a preview URL is refused. Use the production URL, or turn off protection for previews.
### Hosting elsewhere
It is a standard Next.js app. Run `pnpm build` with `SITE_URL` set to the public origin, then `pnpm start`, or deploy it to any host that runs Next.js.
## Develop
```bash
pnpm install
pnpm dev # http://localhost:3000
pnpm lint
pnpm typecheck
pnpm build && pnpm start
```
Node 24 and pnpm 12 (pinned in `package.json`).
## The registry
`registry.json` is the source of truth. `scripts/build-registry.mjs` runs before every `dev` and `build`. It checks that every listed file exists and that every effect is registered and exported, then writes `public/r/{name}.json` with the file contents inlined. Dependencies between items become absolute URLs on the current deployment. `public/r` is generated, so it is not committed.
Once deployed, anyone can install from it:
```bash
npx shadcn@latest add https://<your-domain>/r/obsidianui.json
```
## Layout
- `components/obsidianui/engine.ts`: frame loop, colour helpers, `Dynamic<T>`, `live()` and anchors.
- `components/obsidianui/dither/`: the `ObsidianCanvas` renderer and its painter.
- `components/obsidianui/effects/`: fire, bolt, rings, fluid, beam, rain, snow, aurora, warp, fireworks, matrix, plasma, smoke and orbit.
- `hooks/use-prefers-reduced-motion.ts`: the reduced-motion preference.
- `skills/`: agent skills for using obsidianui and writing new effects (`npx skills add <owner>/<repo>`).
- `app/`: the site. `app/_components/catalog.ts` drives the playground and the reference tables.
## Usage
```tsx
import { useMemo } from "react";
import { ObsidianCanvas, fire } from "@/components/obsidianui";
export function Card() {
const effect = useMemo(() => fire({ height: 0.5 }), []);
return (
<div className="relative">
<ObsidianCanvas effect={effect} />
<p className="relative">Content sits above the canvas.</p>
</div>
);
}
```