An open-source PureRef for the browser. An infinite wall you drop images on, arrange, crop, rotate, pack tight, annotate, and keep. Nothing leaves your computer: boards live in the browser's IndexedDB, the app works offline once it has loaded, and there is no account and no server.
Live: https://projects.patrickreinbold.com/tack/ — installable as a PWA.
Source: https://github.com/st-patrick/tack — MIT.
Landing page: landing.html.
Support: if Tack saved you a licence fee, you can put something back.
- Images in by drag and drop (from the desktop or another browser tab), by paste (a screenshot on the clipboard, an image URL, or plain text becomes a note), or through the file picker. Original bytes are kept — nothing is re-encoded.
- Arrange: move with edge/centre snapping to neighbours, scale about the
opposite corner (or the centre with
Alt), rotate with a knob (Shiftsnaps to 15°), crop by dragging edges, flip, greyscale, opacity, z-order, duplicate, nudge with the arrow keys. Multi-select by marquee orShift-click; group scale and rotate. - Pack (
Ctrl P) tidies everything — or the selection — into a tight rectangle shaped like your window;Ctrl Shift Pfirst normalises heights. - Notes: double-click empty space, type. They wrap, scale and rotate like images.
- Boards: any number of them, switch from the toolbar. A board can be saved
as one
.tackfile with the images inside, and opened again anywhere. Export the board or a selection as a PNG at full resolution. - Undo/redo for everything, autosave 400 ms after the last change, and on tab switch.
- Light and dark, keyboard-first, touch pinch/pan on tablets.
It is static files. Serve the folder with anything:
npx serve . # or python -m http.server, or open index.html over http
There is no build step and no dependency. The source is ES modules:
src/scene.js the board: items, camera, selection, undo (pure data)
src/render.js canvas drawing, handle geometry, PNG export
src/input.js pointer + keyboard gestures, snapping, pinch
src/store.js IndexedDB (boards + original image blobs)
src/images.js decode cache with a ≤1024px mip for zoomed-out drawing
src/pack.js shelf packing ("Optimize")
src/files.js drops, pastes, URLs, .tack export/import
src/app.js wiring: boards, autosave, menus, note editing
sw.js offline shell cache
test/walk.mjs drives the app in headless Chromium: drops synthetic images,
packs, drags, scales, rotates, crops, flips, marquee-selects, group scales,
writes a note, reloads to check persistence, saves and reopens a .tack file,
exports a PNG, toggles theme, and checks that nothing logged an error.
Playwright is the only (dev) dependency, and it lives in test/ so the app
itself stays dependency-free.
cd test && npm install
npm run serve # serves the repo root on :8460
npm run walk # in a second terminal; screenshots land in test/shots/
.tack is JSON: { app: "tack", v: 1, name, cam, items[], blobs{ id: dataURL } }.
Item fields: kind (image|note), x y (centre, world px), scale, rot
(radians), flipX flipY gray opacity z, crop {l t r b} as fractions of the
natural size, nw nh natural pixels, blobId; notes carry text w.
MIT.
