mesh-avatar-studio
Turn one illustration into an animated 2D mesh avatar with a coding agent and a local editor
libelli — turn spreadsheet rows into print-ready cards, entirely in your browser.
DesignSvelte · TypeScriptGitHub @Arty2OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Turn spreadsheet rows into print-ready cards, entirely in the browser.
• Project page — https://heracl.es/libelli
• Live instance — https://libelli.vercel.app
• Source — https://github.com/Arty2/libelli
Libelli is the plural of libellus, a little book — about the size of the
thing coming out of your printer. The dragonfly in the mark is the same word
taking another turn: a libellula is one of those.Read the full README ↗libelli is a free, open-source web app that turns the rows of a spreadsheet into print-ready cards. It runs entirely in the browser: there is no server and no account, nothing is uploaded, and it keeps working offline.
Libelli is Latin, the plural of libellus: "little book", a diminutive of liber, "book". The dragonfly in the logo is a nod to Libellula, a genus of dragonflies whose name is thought to come from the same word.
A user pastes or imports a table, designs one card, and links each area on the card to a column. libelli then produces one card per row. Typical uses are labels, recipe cards, exhibition captions, revision cards, programmes and small booklets.
The card is laid out on a page measured in millimetres. An area can take its words from a column, hold fixed text, or show an image, a drawing or a barcode. Areas can follow one another down the page, grow or shrink to fit their content, and disappear when their column is empty, so a card without a subtitle has no gap where the subtitle would be. Layout can also read the columns and propose a first draft of the card. The editor and the printed page share one layout, so what is on screen is what is printed.
Every control in the app has a tooltip: hover over it, or press and hold it on a touchscreen. This page covers what a tooltip cannot.
The interface's accent — pressed buttons, the selection, focus rings, ticked boxes — is the colour your system uses for its own controls, in browsers that share it with a page, such as Firefox and Safari. Chrome, Edge and other Chromium browsers do not, so there it is a fixed blue.
Open libelli in a second tab and it asks what to do, because only one tab edits at a time: Open Here moves the editing to this tab, Read Only shows the card being edited in the other tab, updating as it changes and following the row it is on, and Close Tab closes this tab. A read-only tab saves nothing; the eye beside the card brings the choice back, and its theme button changes that tab alone, until it is closed. Put a read-only tab in full screen and it shows the card alone, fitted to the screen, on black and in the colours it prints in, whatever the theme — a display; leaving full screen brings the rest back, as do Escape and a double-click. A read-only tab always fits the card to its own window; it does not follow the editing tab's zoom. Its logo asks for full screen, and brings the choice back when the browser says no or it is full screen already.
Full screen with the card alone is a presentation: the arrow keys, Page Up and Page Down (what most clickers send), Space, Home and End page the cards, as does a swipe sideways. Paging in the editing tab moves any read-only tabs along with it; a read-only tab pages on its own until the editing tab moves.
In the editing tab, the logo hides everything but the card — the same view, without giving up the editing — and asks the browser for full screen, to show the card alone; if the browser says no, it stays read only in the window. Pressing the logo again leaves full screen too, and the logo, the eye or Escape brings the editor back. Keys that would change anything do nothing meanwhile.
A table can be pasted straight from Excel, Google Sheets, Coda or any other spreadsheet, or imported as a CSV or TSV file. Quoted fields and line breaks inside a cell are kept. When pasting into a table that already has columns, no header row is needed: the cells fill the existing columns from left to right.
A row whose card runs off the page is marked with a warning sign under its number. That is a Grow area whose words grow it past the page margin or the edge, or an area anchored under one that it pushes there; an area placed in the margin on purpose is marked only once it grows past the edge. Hover or hold the sign to see which areas.
Whitespace, beside the row height under the table, shows the spaces, tabs
and line ends in the cell you are typing in and in the full-size editor: a dot
for a space (a ring where a line wraps at it), → for a tab, a square for a
no-break space, ¶ for the end of a line. In the full-size editor, %%placeholders%%
are always shown in the accent colour. Pressing it leaves the caret where it was. It is for
finding the double space or the trailing one that sets a line askew.
Cell content is always treated as text. A cell containing <b>x</b> prints
those characters, not bold type.
Column names contain no spaces: spaces become hyphens, and other punctuation is removed. This keeps every name usable as a placeholder (below).
Several tables and templates can be kept side by side, and they are independent of each other: one design can print any table, and one table can be printed with any design. A table exports as CSV; a template as JSON.
A CSV, TSV or template JSON can be dragged onto the app from your files, and the table menu's Import opens a CSV or TSV the same way. Each opens as a new table or a new template, and the ones you had open are saved first, so nothing is overwritten; Ctrl/Cmd+Z goes back to them. A file already in your library, unchanged, opens that copy rather than making a second one. To fill the open table instead, use Paste. A file that is not one of those, or that does not read, is named in the status bar with the reason. A picture is dropped on the page or on an area instead.
Text between double percent signs is replaced on each card. Placeholders work in an area's own text and inside cells, and Markdown around them still applies.
| Placeholder | Prints |
|---|---|
%%title%% | The title column of this card's row. Case is ignored. |
%%title:find:replace%% | The column, with every find replaced. The replacement may be empty; a literal colon is written \:. |
%%today%% | Today's date. %%today:YYYY-MM-DD%% sets the format, with YYYY, YY, MM, DD, MMMM, MMM, dddd and ddd. |
%%today:countdown:2026-12-25%% | The time to a moment: "4 days", then "5 hours", then "40 minutes", rounded down; once it has passed, "3 days ago". The moment is YYYY-MM-DD or YYYY-MM-DDTHH:MM in this browser's time, or a column holding one, for a deadline per row. A unit on the end fixes it: %%today:countdown:2026-12-25:hours%%. On screen it moves on by itself. Its words come from CSS, so a template can translate them — see Language. |
%%lookup:3:price%% | The price column of row 3. Row numbers follow their rows when the table is sorted or rearranged. |
%%lookup:next:title%% | The same column in the next row; previous for the one before. |
%%page:current%%, %%page:total%% | The card's page number, and how many pages there are. |
%%toc:title%% | A table of contents: each other page's title with its page number. |
%%% | A tab: what follows is pushed to the right edge, as in Espresso %%% 2.20. The area's Leader fills the gap with dots, dashes or a line. |
A placeholder that matches nothing is underlined in wavy red in the editor.
Typing %% in any text field offers what can go there: the built-in ones
first — today, today:countdown:…, page:current, page:total, lookup
and toc — then, under a rule, the table's columns. One that needs more typed
after it leaves the caret inside it. A single % is ordinary
text. today, lookup, page and toc are reserved words; a column with one
of those names has to be renamed, or reached with lookup.
Areas set to Markdown support a small subset: #, ## and ### headings,
bulleted lists (one level of nesting), numbered lists, **bold**, *italic*,
~~strikethrough~~, ==highlight==, `code`, [links](https://…),
paragraphs separated by a blank line, and --- for a rule. Anything else is
shown as written.
[some words]{red} colours just those words. Hex values, rgb(), hsl() and
the standard CSS colour names are accepted.
Each template can carry its own stylesheet. Its rules apply only inside the
card, and @import and external url() references are removed.
#Job-Title.
.area matches every area..content-field,
.content-static, .content-image) and mode (.mode-plain,
.mode-markdown, .mode-image, .mode-color, .mode-qr), and .columns
when set in
columns.#page-3, #cover, #back-cover,
#inside-cover and #inside-back-cover, and as .recto and .verso when
facing pages are on: #cover .area { … }.--page-w, --page-h,
--margin-top (and the other three edges), --bleed, --text-font,
--text-size, --text-leading and --text-color.Starter, in the CSS editor, writes out a sheet describing the current template — its page, fonts and every named area — which can be edited directly or handed to an AI model to style.
Export opens a preview of every card before anything is printed; untick a card to leave it out. From there the run can be printed, or saved as 300 dpi PNG files, bundled into one ZIP when there are several.
For the print to match the screen, set these in the browser's print dialog:
Print Settings can place 2, 4, 6 or 8 cards on one sheet, with bleed and crop marks. Its Order setting can also arrange the pages for folding:
A booklet is padded with blank pages to a multiple of four (eight for the mini zine). With Recto / Verso on, left-hand pages mirror the layout of the right-hand ones across the fold.
Language in the Page bar says what language the cards are in. It sets the
page's lang, which picks the dictionary justified text hyphenates with, and
which the template's CSS can test with :lang(). Words the app adds itself
come from CSS for this reason, in English unless the template says otherwise:
the page number's separator, and a countdown's units and "ago".
:lang(el) .countdown-unit.days::before { content: ' ημέρες'; }
:lang(el) .countdown-unit.day::before { content: ' ημέρα'; }
:lang(el) .countdown.past::after { content: ''; }
:lang(el) .countdown.past::before { content: 'πριν από '; }
:lang(el) .page-number .of::before { content: ' από '; }
The units are .minute, .minutes, .hour, .hours, .day and .days, one
for a count of one and the other for every other count; a language with more
plural forms than that gets the two.
| Keys | Action |
|---|---|
| Ctrl/⌘ z | Undo |
| Ctrl/⌘ y | Redo |
| Ctrl/⌘ ⇧ z | Toggle the last change off and on, to compare |
| Enter | Type into the selected area |
| Esc | Stop typing, leave Select Multiple, deselect, or close what is open |
| Arrows | Nudge the selection 1 mm; with ⇧ 5 mm, with Alt ⇧ 10 mm |
| Arrows, PageUp / PageDown | Move between cards, with nothing selected |
| ⇧ / Ctrl / ⌘ + click | Add an area to the selection, or remove it |
| Ctrl/⌘ a | Select every area |
| Ctrl/⌘ d | Duplicate the selected areas |
| Ctrl/⌘ l | Lock or unlock the selected areas |
| Ctrl/⌘ ⇧ l | Lock or unlock the design |
| Delete / Backspace | Delete the selected areas |
| Ctrl/⌘ c / v | Copy an area's words; paste text as a new area |
| Ctrl/⌘ ⇧ c / v | Copy an area's style; paste it onto the selection |
| Ctrl/⌘ ⇧ + arrows | Change the alignment |
| Ctrl/⌘ ⇧ + scroll | Change the type size of the area under the pointer |
| Ctrl/⌘ + scroll, or pinch | Zoom the page; off the page, the interface's text size |
| Ctrl/⌘ + / − | With areas selected, make their type a point larger or smaller |
| Ctrl/⌘ + / − / 0 | With nothing selected, zoom the page in, out, or fit (⇧ 0 for 100%) |
| Ctrl/⌘ h | Show or hide area outlines |
| Ctrl/⌘ ; or | | Cycle the guides |
| Ctrl/⌘ ' or # | Show or hide the grid |
| Ctrl/⌘ s | Save the drawing, while drawing |
| Ctrl/⌘ p | Export; press again to print |
| Ctrl/⌘ ⇧ p or s | Export |
| ? or / | Help |
Holding Undo or Redo beside the page, or right-clicking either, lists every step it could take, so several can be skipped at once.
While typing in a field, undo and Delete act on the text, as usual. On a touchscreen, a pinch on the page zooms it; zoom and pan (under + Area) lets one finger scroll the page and adds an on-screen pad for nudging; and a long press on an area opens its menu.
Everything is saved automatically in the browser. Images and fonts are stored by name, so a template stays small and can be shared as a JSON file; whoever opens it is asked for any image or font they do not have. Drawings made in the app are the exception: they are saved inside the table cell, so they travel with the data. In Chromium-based browsers, images can be kept in a folder on disk instead.
Undo history is kept in memory, so it lasts until the page is reloaded; an update waits for the user to accept it for the same reason.
The app keeps a copy of itself and works offline after the first visit. Where the browser supports it, it can be installed as an app. Only one tab edits at a time; a second tab offers to take over.
Nothing that is typed, drawn or imported is ever uploaded. There is no backend and no analytics. Three kinds of request do reach the network, each because something was asked for by name:
fonts.googleapis.com. A template from someone else can
therefore trigger this request when opened. Only a valid font name is
accepted. Uploading a font file avoids the request entirely.http and
https addresses are allowed.A template's CSS cannot add to this list, and neither can a cell: a stored
image is written local:name, which a browser never treats as an address.
libelli is a static SvelteKit site with no runtime dependencies; the Markdown renderer, the CSV parser and the barcode encoders are written by hand.
npm install
npm run dev # http://localhost:5173
npm run verify # project rules, lint, unit tests and type checks
npm run build # static output in ./build, for any static host
Versions are numbered 0.minor.patch, and the leading zero never changes:
| Change | Bumps | Example |
|---|---|---|
| A fix | the patch | 0.29.6 → 0.29.7 |
| A feature | the minor | 0.29.6 → 0.30.0 |
AGENTS.md explains the codebase and its rules,
docs/decisions.md the reasoning behind each module, and
CHANGELOG.md what each version added.
Dialectic Acheiropoieton of Heracles Papatheodorou and Claude
Released under the MIT License.