Playing
Photos, stamps and recap cards
The gallery, the five stamped-overlay layouts, the session recap card, what the overlay says (and when it says it), and what is stripped on the way out.
On this page
Every photo you put into Latch โ a check-in, a verification shot for training โ lands in one content-addressed store under your data folder and shows in the Gallery. Voice memos live in the same store but are not gallery tiles: audio has no pixels.
Gallery
/gallery shows every photo, newest first, with the task it verified if it was one. Each tile
has a stamp-layout picker, a download of the stamped version, and a ๐ค share button.
Stamped photos
A stamped photo is your picture with an overlay of stats, re-encoded as a fresh JPEG. Five layouts, plus auto, which picks by aspect ratio:
| Layout | Name in the app | Shape |
|---|---|---|
bar |
Hairline | one thin line of facts along an edge |
hero |
Collar | a bold band with the headline number |
badge |
Tag | a compact corner tag |
dossier |
Case File | a grid of labelled values |
rail |
Strip | a footer strip, suits wide photos |
Sizes: 1080, 1600 (default) or 2048 px on the long edge. The default layout and which stats appear are yours to set โ see Customise โ Stamps for the full token list (streak, day N, time locked, tasks per week, rank, and about forty others).
The overlay is as-of the shutter
Every value on a stamped photo is computed from the log at the photo's timestamp: the clock as it stood, the streak as it was, the training count up to that moment. A photo from three weeks ago does not wear today's streak. One deliberate exception: an event undone after the photo was taken still appears on that photo's overlay โ otherwise every undo would silently recaption every picture already in your gallery.
EXIF and GPS are stripped by construction. The stamped file is rendered from pixels and saved as a new JPEG; there is no metadata to copy. The original stays in your store untouched.
Hidden timer: while a hidden lock is running, no overlay prints the sentence or the time remaining โ either one plus the start time would give the end away. Once the lock ends, they return.
Recap cards
Every finished (or in-progress) session has a recap card at /session/<id>/recap.jpg, linked
from the Last lock card and the Sessions list. Two shapes: story (1080 ร 1920, for a
phone screen) and square. The card shows the verdict (clean / early / in progress),
the headline time, a grid of stats, your rank and level, and a strip of the session's photos.
Which stats sit in the grid is a token list (Settings โ Stamps โ Session recap card). The
session's private note is not printed unless you add ?note=1 to the URL โ private prose on
a share object has to be opt-in.
Share cards
A recap card is about one finished lock. A share card is whatever you point it at โ at
Share in the nav, or /share.
Pick a kind, a theme, a shape, and optionally a photo; then download it or hand it to the share sheet. Every choice lives in the link, so the picture you are looking at and the file you download are the same URL.
Three kinds
| Is about | Its clock | |
|---|---|---|
| Right now | the live state โ locked or not, how long, the streak | this moment |
| The last stretch | a span: 7, 14, 30 or 90 days, and what happened in it | the two ends of the span |
| One badge | one thing you earned, and where things stood then | the badge's own moment |
The last stretch prints the span's actual dates rather than calling itself "this week". That is deliberate: a card headed THIS WEEK on a Wednesday is a claim about three days wearing the name of a week, which is the same thing badges refuse to do when they wait for a month to end before deciding its winner.
One badge is a record, not a snapshot with a trophy on it. Every number on it is as it stood when the badge was earned โ so a card about something you did in August shows August's streak, not today's. The date in the corner is that moment, and the whole card agrees with it. It appears as an option once you have earned a badge.
Themes
Seven palettes, and six of them are the badge metals: Steel, Moss, Sapphire, Amethyst and Gold are the five rarities the badge art uses, and Plate is the cream plate a badge sits on. Midnight is the app's own colours โ what every card looked like before themes existed, and still the default.
Badges drawn on a card wear their own metal, so a legendary badge is gold on any theme.
Stats
The grid holds six (three if the card carries a photo, three on the wide shape). The Share page can add, remove and reorder them for one card without changing any setting โ the choice travels in the link. To change what a card starts with, edit the token lists in Settings โ Stamps & recap.
A stat with nothing to say at this moment is left out rather than printed as a zero, so the card lays out what resolved, never what you asked for.
What fills the lower half
Never nothing. In order: the photo if you picked one, then the badges โ the ones earned in the span on a week card, the rest of your collection on a milestone card, or all of them on a right now card if you ask โ and otherwise the events that moved the clock most, which is the same log the numbers above it come from.
A badge's icon is never printed, because the icons are emoji and the card's font has no emoji in it. A badge is drawn instead โ the same hexagon-and-metal-ring shape the badge art uses.
The card has two moments in it, and says so
This is the one thing worth understanding about a share card that carries a photograph. The stats describe the card's own moment. The photograph describes whenever its shutter fired โ possibly weeks ago. So the card keeps them visibly apart rather than blending them:
- the card's own date and time sit in the top-right corner;
- the photo's own date and time are burned into the bottom of the picture itself.
The caption is not optional, and it lives inside the image rather than in the card's margin โ a card gets cropped, re-shared and screenshotted, and a date that lives in the surrounding chrome survives none of that.
Why a milestone card takes no photograph
Its numbers are already as-of the day the badge was earned. Adding a picture would put a third moment on one card โ the badge's, the shutter's, and the reader's assumption that a card they were just handed is about today. Two is already the most anyone can keep straight, so that kind refuses a photo and says so on the page.
Location on photos
Latch can record roughly where a photo was taken and print it on a card or a stamped export. Two separate switches, because they are two different decisions:
| Setting | Default | |
|---|---|---|
| Record a coarse location with new photos | photo.place_capture |
on |
| Print it on an image | nothing โ every export asks explicitly | off |
The rounding happens when the photo is saved, not when it is printed. The exact coordinate is never written to disk, so it is not in the database, not in your backups and not in the export bundle โ and a card cannot be sharpened back later, by you or by anyone who gets a copy of your data.
How coarse is photo.place_dp:
| Grid | Reads as | |
|---|---|---|
0 |
~111 km | region |
1 |
~11 km | city |
2 |
~1.1 km | neighbourhood โ the default |
3 |
~110 m | street |
Where it comes from, in order:
- The photo's own EXIF โ what the camera recorded, so it stays correct however late you log the photo.
- Your browser, but only for a photo you are logging now. A photo backdated to last Tuesday will not take today's position: that would put where you are standing onto a picture taken somewhere else.
- A label you type ("the park", "hotel, Chicago"). A typed label replaces the coordinate entirely rather than appearing next to it.
A photo whose camera recorded nothing simply has no location, and the Share page says so instead of offering a switch that would do nothing.
What rounding does and does not buy you
A 1 km cell is a neighbourhood, not a doorway. But the grid is fixed, so every photo taken at home lands in the same cell โ and over a long log, the cell you appear in most is home, to within a kilometre. Coarsening limits the blast radius of one image; it is not a disguise. If that matters for a particular card, use a typed label, or leave the location off.
Sharing
The ๐ค button uses the phone's share sheet (the Web Share API). It needs a secure origin โ
https://, through your reverse proxy โ and a browser that supports it. Anywhere else it
downloads the JPEG instead. Nothing in Latch posts anywhere; sharing is you handing a file to
another app.
Limits and formats
- Photos: JPEG, PNG, WebP, up to 40 MB. The check is applied to the upload stream, so an oversize file is refused before it costs memory.
- HEIC is converted by your browser on the way in โ automatically, on a phone. The server still has no HEIF decoder, so a desktop browser that cannot decode it sends the file as-is and gets a refusal explaining the fix. See Troubleshooting โ HEIC for what conversion costs you.
- Only unreadable formats are converted. A JPEG, PNG, WebP or GIF is uploaded byte for byte โ a canvas round-trip would strip the EXIF, and EXIF is where the capture time and the location come from.
- Video is not stored, but picking one offers a use a frame from it button โ a still pulled out of the clip, one tap. Offered, not automatic: a still is not the clip.
- Voice memos: up to 12 MB / ten minutes; the recorder stops itself at ten.
- Thumbnails are generated on upload and rebuilt on demand if one goes missing after a restore.
Picking one
Two doors, deliberately different:
- ๐ธ Photo check-in on the dashboard is the fast one โ one tap, camera, logged. It goes straight to the camera and submits itself, so there is nothing to confirm. If it catches the ceiling, undo it from the Log.
- Everywhere else โ the fold under the check-in button, and the verification photo on Training โ opens your normal picker, so the camera roll is available and a photo you already took is fine. You then get a thumbnail of the file that is actually going to be uploaded, its size, and โป replace.
That the thumbnail is of the uploaded file matters more than it sounds: a HEIC is converted and a clip becomes a single frame before anything is sent, and the preview shows the result of that, not the thing you tapped. If a preview appears with no picture in it, the file is being sent as-is because this browser could not decode it โ the line underneath says so, and the server's refusal will name the real format.
Where the bytes are
<data dir>/media/YYYY/MM/<sha256>.<ext> with thumbnails under media/thumbs/. The export
bundle carries originals and thumbnails (Export). Media is not in the SQLite
file, so a database-only backup is not a full backup.
This page also ships inside the app, at /guide โ so your own instance always serves the guide for the version you are running, with the internet unplugged. Get Latch ยท Something wrong here? Tell me.