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, a ๐ค share button and a
๐งณ toggle (below).
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 layout, a theme, a shape, and optionally a photo; then download it or hand it to the share sheet, with the post text already written. Every choice lives in the link, so the picture you are looking at, the file you download and the words that go with it are the same card at the same moment โ the page pins its clock into all three.
A bare Share page opens on your โ default preset, or on the built-in Daily starter (the Dense layout) when you have none. A link anybody made before 0.14 still means what it says and draws the card it drew โ the same pixels outside an event, save where 0.13 cut a value short or the card's moment fell inside a lock that ended later; during an event the stamp is added.
On a phone the page is built for one hand: the preview and the ๐ค Share button stay pinned at the top while the panels โ Stats, Post, Look, Photo โ sit underneath, and a tap on a stat, a theme or a hashtag changes the card in place, without reloading and without the screen jumping. The address bar follows along, so a reload or a bookmark lands on the same card. With JavaScript off every control is still a plain link that lands back on its own panel.
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.
Layouts
Three layouts, and they differ in how much room the clock gets โ never in what is true.
| Layout | What it is | Story | Square | Wide |
|---|---|---|---|---|
| Poster | the big timer โ what every card looked like before 0.14 | 6 stats (3 with a photo) | 6 (3) | 3 |
| Dense | a small timer and the room given to the numbers | 12 (9 beside a photo) | 9 (6) | 6 |
| Check-in | the photo fills the card; a glass panel holds the stats | 12 | 8 | โ |
Check-in needs a right now card with a photo, in story or square. Ask for it anywhere else and the card quietly draws as Dense, and the page says so. The Check-in starter (the dashboard's ๐ค button uses it) picks today's photo for you.
A Check-in card has one clock: the shutter. Every number on it โ the timer, the streak, the event day โ is as it stood when the photo was taken (clamped to when it reached the box, so a camera with a fast clock cannot date a picture into the future), and that moment is printed on the photo itself. Drawn again next week, it is the same card, byte for byte.
Stats
How many fit depends on the layout and the shape (the table above); the page says fits N for the card on screen, from the same arithmetic that draws it. The Stats panel shows twelve slots in the card's own order: tap a slot, then move it earlier or later, or remove it; tap a stat in the catalogue below โ each one shows its value right now โ to add or drop it. โบ default list goes back to the list in Settings โ Stamps & recap; clear means no stats at all. The choice travels in the link and changes no setting; a preset keeps it.
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. A value too long for its cell takes two cells instead of being cut short โ "LOCKTOBER ยท DAY 2/31" reads in full. A slot drawn dashed won't fit on this card; a dim one is already on the card โ on Dense and Check-in the elapsed time is the big number, the level and rank are in the footer, and a stamp on the card already names the event and the day โ so none of those is drawn twice.
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.
A Check-in card is the exception that proves the rule: it has one moment, the shutter, and prints it on the photo. Nothing on it is from now, so there is nothing to keep apart โ which is the only reason it is allowed to fill the card with the picture.
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.
The event stamp
When you are in an event โ Locktober, or any playset with an [event] โ the card carries a
drawn postage stamp in the event's own colour: a perforated rim, a padlock, the event's name,
the day in big numerals with /31 beside it, and the year. Nothing to set up and nothing
for the playset to ship: it is drawn from the event's name, dates and accent, so every event
playset gets one.
It sits where its moment is. On a framed photo it is on the photo, and dated by the shutter โ a photo from the 2nd says DAY 2 forever, however late you share it. On a Check-in card it is in the header (the shutter is that card's only clock); on a card without a photo it is in the header, as of the card's own moment. No event at that moment means no stamp โ never a fallback to "the event running now".
Turn it off for one card under Look โ Event stamp, or with seal=0 in the link.
The stamp reads the playset as it is now
A card re-drawn later asks the playset what the event was. Switch the playset off, remove it, or install a version with different dates, and old cards drawn again lose their stamp or change it. A picture you already shared is a file and does not change.
Post text
Under the card, the Post panel has the words ready: built here, on the box, from the same card โ the same moment, and the stats the card actually drew, in its order. No model writes it and nothing is sent anywhere.
Three styles. Pup (the default) has three phrasings that take turns by event day โ one of them quotes the playset's own line, so on Locktober's second day it opens Day 2 of 31. It's Locktober, pup. โ Stats is the numbers, and Minimal is just the day and the tags. Custom โ opens the template, which you can edit:
- a field in braces fills in from the card:
{name},{sign},{day},{days},{left},{year},{line},{locked},{stats}(up to four of the card's own stats),{tags},{photo_note},{badge}โ and any stat id from the catalogue, like{streak_current}; - a field with nothing to say drops its own segment: a line is split at
ยทand only the pieces whose field is empty go, so๐ Locked {locked} ยท {stats}on an unlocked pup is just the stats, never "Locked None"; a line that empties is dropped; - it never prints what a hidden timer hides โ the time to go and the sentence are simply not there โ and never a location.
Hashtags. An event gives two by default โ #Locktober #Locktober2026 โ and offers more
(#LocktoberDay2, #chastity, #locked, #keyholder, #chastitycage, #pupplay, #petplay)
that are never on until you tap them. Add your own; {day}, {days} and {year} inside one
fill in from the event. A playset's catalogue tags are never turned into hashtags.
Alt text describes the card for a screen reader (it is written for you and cannot be
edited into saying something the picture does not). Content warning is
chastity ยท Locktober day 2/31 by default; set your own in the panel. The counters under the
text track X (โ280, emoji count double), Bluesky (300) and Mastodon (500, counting the CW).
Sharing it. On an iPhone, ๐ค Share puts the post text on your clipboard and opens the share sheet with the picture, in one tap โ pick the app, then paste. (iOS drops the picture if text rides along in the share sheet, which is why the text goes by clipboard.) The picture is prepared before you tap; if you tap before it is ready the page tells you to wait a moment rather than downloading. โง Copy text, Copy alt and Copy CW copy one thing each.
Presets
A preset is a whole look with a name: the stats, the layout, the theme, the shape, the event stamp, the post style, the hashtags, the template and the CW. The row of chips under the preview holds them.
- ๏ผ Save keeps what the page shows under a name.
- A chip applies that preset to the photo you are on โ a preset never stores a photo or a location, which are chosen per card.
- Change anything and the active chip shows โ: tap it to update the preset or save as new.
- Look โ Manage presets renames, stars and deletes them. The โ default is what a bare Share page and the dashboard's ๐ค button open on. An update or a delete can be undone from the message that confirms it.
Two built-in starters, Daily and Check-in, sit after your own presets, with a dashed outline; they cannot be renamed, starred or deleted. Presets live in this box's database and ride the full backup. Saving one needs JavaScript; applying one is a link.
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.
Carrying photos to a new box
๐งณ means travels with my pup to my next box โ not shown to any pack. It is off by default,
and it is about one thing only: which photos go into your pup.json
when you arrive on your own new box. What a pack sees is a different choice, made on
Consent item by item; ticking ๐งณ shows a photo to nobody.
- Toggle it on a gallery tile, or all in one place under Pup ID โ What travels.
- A photo that travels is re-encoded: rotated upright, at most 1600 px, JPEG, with no EXIF, no colour profile and no comment โ no location, no camera details. Its caption and its place stay home. So do voice memos, always.
- At most 60 photos go in one
pup.json, newest first; a photo past that says stays โ over the 60-photo limit. A photo whose check-in or task you undid stays home and says stays โ undone. The chip and the count on the Pup ID page say exactly what the file will carry (a re-encoded photo over 1 MB, or past 24 MB in all, is also left out when the file is made). - A photo that arrived that way is a carried photo on the new box: it shows on its kit item and under What travels, it is counted by no statistic and no card (it was not taken there), and it can travel again.
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.
On an iPhone the share sheet gets the picture only, and the share card's post text is
already on your clipboard โ paste it into the post. (The file in Save to Files is named for
the event day, like locktober-2026-day02-20261002-1205-story.jpg.)
For 12 hours after a photo check-in, the dashboard shows ๐ค Share Day N right under the big buttons: one tap opens the Share page with that photo, the Check-in card, the stamp and the text ready. The gallery's ๐ card link does the same for any photo.
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.