Pixel-art swap cards that travel by QR code. Draw or import a small picture, put your handle on the back, sign it, and hand it over: the other phone scans it, checks the signature, and the card joins their collection with its history — who drew it, who passed it on, which copy of how many.
No server, no accounts, no tracking. Everything lives on the device; a card
moves only as the person sends it: a QR code on the screen, or a link, a
.pips file or a picture of the card, each carrying the same signed
envelope.
Status: early. The core is complete and tested; the app works end to end but its design is still a first draft. Formats may change before 1.0.
- Identity: an Ed25519 key, restored from 24 words or a paper QR code.
- Cards up to 64×64 px in the app (the format holds 128), 32 colours, animation; 33 built-in palettes; a pixel editor with layers, frames that last as long as they need, and an undo tree.
- New cards from pictures: PNG and Aseprite files as they are (layers, frames, upscaled exports), photos and larger pictures (JPEG, WebP — whatever the browser opens) through a square crop; semi-transparent pixels are made solid, and the app says how many; a picture of a Pips card opens as the card itself.
- Several cards at once: as one
.pipsfile or as a picture of each; several files received at once. - Giving and receiving by QR — one code, or a fountain-coded carousel for large cards; every card and envelope signature is checked. QR codes are read by the browser where it can, by our own reader everywhere else.
- Numbered copies: the artist scans the recipient's code and issues "№3 of 50", signed and bound to that person.
- Collection by weeks and months, a feed and single cards; archive, bin, personal notes and tags.
- Encrypted backups that open only with the same key (its words or paper QR); restoring brings back the key and the collection in one step.
- Titles and names in any script, the same length in each (32 and 24 characters), emoji included; nothing invisible or text-reversing — the same rule in the core and in what the app lets be typed.
- A card from a newer version of Pips is said to be one, not called damaged.
- Russian and English; installable, works offline.
| Path | What |
|---|---|
crates/pips-base45 |
Base45 (RFC 9285), the text inside QR codes |
crates/pips-palette |
built-in palettes, .hex / .gpl import |
crates/pips-format |
the card format: strict codec, canonical form, digest |
crates/pips-sign |
keys, signatures, edition certificates, envelopes, recovery words |
crates/pips-qr |
single QR codes and the carousel |
crates/pips-raster |
PNG / Aseprite import |
crates/pips-store |
backups |
crates/pips-wasm |
one facade over all of the above for the web app |
crates/pips-scan |
QR reading for browsers without BarcodeDetector, loaded on demand |
web/ |
the app: plain ES modules, no build step, strict CSP |
web-next/ |
the app written again, layer by layer, not published yet (docs/REWRITE.md) |
vendor/rqrr |
the QR reader pips-qr uses, with fixes of ours (PIPS.md) |
Each crate does one thing and is no_std where it can be; most come with a
small command-line tool (pips-card, pips-sign, pips-qr, pips-raster,
pips-backup…) that reads standard input and writes standard output.
Every parser is strict: it refuses rather than repairs, and checks sizes
before allocating. Design decisions and their reasons are in
DECISIONS.md (Russian).
Needs Rust (stable, with the wasm32-unknown-unknown target), the matching
wasm-bindgen-cli, and Python 3 for the local server. On NixOS, nix-shell
provides all of it.
cargo test --workspace # the core
scripts/build-wasm.sh # the core for the browser, into web/pkg/ (and web-next/pkg/)
scripts/serve.sh # http://localhost:8000Browser tests drive the app in Chromium (and, in CI, Firefox) through its
real journeys — import, exchange by QR, link, file and picture, refusals,
numbered copies, backups, restoring, the editor, the published site with
no network and its next release — and keep each screen's layout at three
sizes (and in English at the narrowest) and the collection's zoom from
laying the page out on every frame; unit tests (tests/unit, plain node) cover the app's logic:
node --test tests/unit/*.test.mjs
cd tests/e2e && npm ci && npx playwright install chromium && npm testThe new app's tests (tests/next) run the same way, in Chromium, Firefox
and WebKit alike:
cd tests/next && npm ci && npm test # BROWSER=firefox, BROWSER=webkitWhat the tests reach of the app's code can be measured (Chromium and node), with the functions no test runs and the files no test loads listed:
cd tests/e2e && COVERAGE=/tmp/cov npm test && cd ../..
NODE_V8_COVERAGE=/tmp/cov node --test tests/unit/*.test.mjs
node tests/coverage-report.mjs /tmp/covEvery parser of outside input has a fuzz target (fuzz/, nightly Rust):
cargo install cargo-fuzz
cd fuzz && cargo +nightly fuzz run card corpus/card seeds/cardscripts/build-site.sh assembles what GitHub Pages publishes. CI runs
formatting, clippy, tests, the minimum Rust version, a RustSec check of the
dependencies, a type check of the app and the browser tests (Chromium and
Firefox, a seeded "monkey" among them), and deploys main — on each push
to main and each pull request; a second workflow fuzzes every target on
the same and for longer once a week. A third, run by hand
(Actions → Repeat browser tests), runs the browser tests many rounds over in
both browsers, to find any that pass only sometimes.
Start with docs/GUIDE.md (Russian): how the project is put together, where things live, how to fix a bug properly and what not to touch. First project ever? docs/FIRST-STEPS.md. AI assistants: AGENTS.md. Plans for an Android app: docs/ANDROID.md (Russian).
Code: Apache-2.0. vendor/rqrr: rqrr 0.11.0 with one fix of
ours (see its PIPS.md), MIT or Apache-2.0, and ISC. Fonts in web/fonts: SIL Open Font Licence
(see the files there). Test pictures in crates/pips-raster/tests/aseprite:
MIT, from the asefile project. web/dev/qrcode.js (development only, not
published): MIT, Kazuhiko Arase.
По-русски. Pips — пиксельные карточки для обмена через QR-код, без сервера и аккаунтов. Проект на ранней стадии: ядро готово и покрыто тестами, приложение работает целиком, дизайн — первый черновик. Решения и их причины — в DECISIONS.md; как устроен проект и как в нём работать — в docs/GUIDE.md, первый шаг для новичков — docs/FIRST-STEPS.md, для ИИ — AGENTS.md.