Skip to main content

Card designer

The designer at /design is how a card gets made and bought. An artist arrives with cover art and leaves with an order; everything between is derived from that one image.

This page describes current code. If it disagrees with the repo, the repo wins.


The five steps

StepWhat the artist doesWhat the system does
1 ArtworkDrops in cover art, or picks a stock colourSamples the image in all five moods and offers them as real swatches
2 LayoutPicks one of fourteen designsRenders every thumbnail with their art and their colours
3 WordsTypes name, title and any optional linesAuto-fits the title, checks length and contrast, shows their slug URL
4 MusicUploads tracks, or skipsCreates the workspace and release, writes the player theme, themes the preview
5 OrderPicks quantity of Custom SlappcardsExports both print faces, saves the design, hands off to Stripe

The card is on screen the whole time, front and back, in either orientation. The draft saves itself to cardDesigns after every change, keyed by an opaque token in localStorage, so someone can design before they have an account and still have the work when they come back.

flowchart TD
art[Cover art] --> sample[extractPalette OKLab]
sample --> colourway[Card colourway]
sample --> theme[releases.playerTheme]
colourway --> card[CardFace]
layout[Template] --> card
words[Content slots] --> card
slug[artists.slug] --> qr[QR - slappcard.com/slug]
qr --> card
card --> export[300dpi front and back to R2]
export --> batch[cardBatches print files]
theme --> listen[Fan listen page]

The engine

apps/web/src/lib/card/ is the v5.1 designer bundle, ported as authored.

FileWhat it holds
templates.tsSPEC, COLORWAYS, SLOTS, the 14 templates, the 10 house backs, the decoration library
qr.tsModule shapes, finder eyes, plate and logo knockout, on top of uqr for the encoding
colorway.tsBridge from a sampled ExtractedPalette to a paintable CardColorway
design.tsStored config to renderable data, plus the production check
print.ts300 dpi geometry and rasterisation
artwork.tsUpload ingest — a working copy and a small stored preview

Everything is authored in millimetres and mapped through one --mm property, so the thumbnail, the live preview and the print export come from a single set of numbers. Only --zoom changes.

Format. 88.9 × 50.8 mm trim, 3.175 mm bleed and safe area, 3 mm corner radius. At 300 dpi that is 1050 × 600 px trim, 1125 × 675 with bleed. This is a US business card, not the CR80 the placeholder system used.

The templates emit HTML strings rather than JSX. That is deliberate: rewriting 28 authored layouts as components would forfeit the decode and legibility verification they arrived with. Copy is escaped by the engine (esc, including quotes) and image sources are scheme-checked in CardFace before they reach an img tag.

Layouts

split Two-Track · bleed Blackout · band Top Billing · sleeve The Sleeve · type All Caps · frame The Classic · ticket The Stub · stack Flyer · deck Boombox · stamp Certified · spine J-Card · meter In the Red · tile Clean Cut · strip Reel to Reel

Each is separately authored in landscape and portrait — portrait is not the landscape layout squeezed into a column. Each declares which of the ten house backs it uses, so the two faces read as one object, and each carries its own example copy so the picker shows a finished card rather than a blank one.


The scan code

One code per artist, encoding https://slappcard.com/\{slug\}.

Every card in a pack carries it, so a reprint never invalidates cards already in someone's hands, and the artist can put the same link on socials. The catch-all route resolves the slug — legacy Wix slugs first, then live artist handles — into that artist's shared card, and redirects to /c/\{code\} so entitlements, gating and scan analytics all run through the one existing path.

A batch therefore mints one shared card row, not one per physical card; cardBatches.quantity remains the number being printed and shipped. Shared cards are excluded from the abuse ratio for the same reason legacy ones are: that ratio assumes one card per recipient.

The asset lives at qr/artist/\{artistId\}.svg, generated by qrActions.generateArtistQr.

Keep slugs short. The block is 20 mm and a module must stay at or above 0.5 mm. slappcard.com/riz is comfortable; a very long handle pushes the code denser and costs acquisition distance. The designer refuses to check out if the link will not scan.


Print

The designer exports both faces at 300 dpi with bleed and the code already composited, uploads them to R2, and records them on the design as frontArtR2Key / backArtR2Key. Because every card in the run is identical, printPipelineActions.generateBatchPrintFiles no longer renders anything: it confirms both objects exist and marks the batch ready_to_print.

Bleed is produced by scaling the face up by the bleed ratio and centre-cropping, which runs type about 7% larger than the authored millimetres. The alternative — a trim-sized card centred on a bleed canvas — leaves a border of face colour around every full-bleed layout.

Corners export square. The printer die-cuts the 3 mm radius.

A batch still waits on tracklist: cards that scan to an empty page are not worth printing.


Data

cardDesigns.config is v2:

{
version: 2,
template: "split" |, // 14
orientation: "landscape" | "portrait",
colorway:
| { source: "stock", id: "og" | "blk" | "red" | "pur" | "lim" | "pnk" }
| { source: "artwork", mood, accent?, resolved: { face, ink, accent, dark, swatches } },
qr: { url, ec, shape, eye, dark, light, logo },
content: { title, artist, subtitle, meta, badge, cta, url },
}

Sampled palettes cache their resolved colours so ops and the print pipeline never re-run the sampler on an image they may not be able to read.

Pre-v2 rows are not migrated; the stored validator is a union and convex/lib/cardDesign.ts lifts them to v2 on read, mapping retired faces onto the closest layout and rebuilding an artwork colourway from the palette v1 kept in custom.artFace/artInk/artAccent.

Alongside the config: artworkPreview (a downscaled data URL, cheap enough to store on the document) and artworkR2Key (the full-resolution original).


Checkout

cardDesignId travels from the designer into createCheckoutSession, onto the Stripe session metadata, and onto the pending purchases row. When payment lands, mintCardBatch binds the design to the batch it just created and moves it to ordered, then the asset check runs. Nobody has to chase artwork after the fact — which is what used to leave batches sitting in needs_assets.

Checkout still requires an account, but the design does not: the workspace is created from the name already typed in the wizard rather than sending the artist away to make one first.


Where else the engine renders

CardFace is the only card renderer in the app. Marketing pages use MarketingCard, a container-fitting wrapper around it, so the card on the home page is the card that prints. Print Studio (/studio) reviews submitted designs and pulls the two faces down for the print house.