Treasure Hunt QR Code
A birthday, a rainy Saturday, a proposal that makes them work for it: hide a chain of codes where each scan reveals the riddle pointing to the next. A card from the chain builder below carries its clue twice: in the code, which the builder encodes as a reveal link to this site and which needs a connection when scanned, and as printed words beneath the artwork, which need none — so a card can be read by eye wherever the signal is not. In the generator a framed card prints the clue under the code only when you leave the caption empty; a caption you type takes its place. An offline note writes the clue into the code itself instead, with the display caveat set out below. Gold ring frames and numbered captions make the cards feel like the treasure they lead to.
Keepsake print checklist
- Keep the white margin around the code — it is the quiet zone scanners need, not empty space to crop.
- Size for the moment: 2 × 2 cm of code is the floor for cards and tags; photo-woven codes reward 4 cm or more; distant reveals want one tenth of the scanning distance.
- Matte paper over gloss — glare is the most common keepsake scan failure.
- Scan a test print with a real phone before the moment that matters.
Build the whole hunt at once
One clue per line, in the order they will be found. Each card is numbered so a hunt found out of order still makes sense, and the clue is printed on the card as well as encoded — a hunt should not end because a phone died.
A worked example, rendered by the real engine
Rendered when this page was built, by the exact engine the tool above runs — gold ring frame, "Clue No. 4" caption. Scan it with a scanner that displays bare text and the clue appears:
Clue 4 of 6: I keep winter warm and summer waiting. Look inside me. (Hint: hallway)
Version 8 at 49×49 modules, error correction H — the level every module-redrawing style on this site encodes at.
What the treasure-hunt preset encodes
Guide: the chain module's rules — how a pasted draft becomes clue texts, how clue texts become numbered cards, the refusals at either end of the clue count, and a fictional chain measured as offline notes against reveal links — are set out in full in Treasure-hunt chains. This page applies the engine's rules to one preset and the chain builder above, with every number computed from them.
The treasure-hunt preset starts on the Ring frame style with a message as its content. Every preset in the occasion registry fixes the style a page opens on, the payload builder it uses, its field placeholders, and the example rendered above. For a treasure hunt that is a ring frame around an untouched code, a message payload, and the fictional clue shown in the worked example — the same registry entry drives the generator, this page and the test suite, so none of them can disagree. The example's caption, Clue No. 4, is a typed caption from that entry, which is why the card above carries that caption and not the clue: on a framed keepsake with the caption left empty, the message is printed under the code as its caption, and a caption you type takes its place. The chain builder does not read the registry at all: it fixes the ring template itself and sets every card in the frame's default palette, passing no preset colour. Built that way from the same inner code and caption, its composite is byte-identical to the gold one above — two defaults that happen to agree, not one shared entry.
As an offline note, a message payload is passed through exactly as typed. The text builder checks only that something was entered; it does not trim, rewrite or wrap the words, so the code carries the clue character for character, and the bytes that verification later compares against are those exact characters. The reveal-link path is one step less literal: the fragment encoder trims surrounding whitespace from the clue before base32-encoding it, and the chain builder's parser trims every line it reads, so a clue's leading or trailing spaces never reach a reveal link. What a message is not is a link. The methodology lists the payload types Apple's Camera and Google's ML Kit recognise — links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins — and bare text is not among them. That one fact is why this preset has two message modes, set out below, where a link preset needs none.
The example clue, Clue 4 of 6: I keep winter warm and summer waiting. Look inside me. (Hint: hallway), is 83 characters long. Carried as
an offline note at error correction H, the encoder chooses version
8: a 49×49-module symbol,
57×57 once the 4-module quiet zone is drawn on
every side. Version 8 places its alignment-pattern centres at modules
6, 24, 42; the three finder corners carry none, so this symbol has
6 alignment patterns — the smaller inner squares a scanner uses to orient a dense
code — and every one of them is rendered as a crisp classic square, because restyling alignment
patterns broke decoding outright in testing.
What a phone does with a clue, and the two modes
A QR code containing bare text carries no scheme, so a phone has nothing to act on. Apple's Camera and Google's ML Kit both recognise links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins — and no plain-text type. Faced with bare text, an iPhone offers to copy or search it and Google Lens simply runs a web search, so the message is the one thing the reader never sees. Samsung phones and dedicated scanner apps do display it, but a keepsake cannot depend on which phone opens it.
So a clue, like any message keepsake here, has two honest modes. A reveal link
encodes a normal URL to the reveal page at /m/ on this site, with the clue
carried in the URL's fragment — the part after the #. Per RFC 3986 a fragment is
stripped before a request is made, so the clue never reaches a server, a CDN or a log; it is
decoded entirely on the reader's device. An offline note writes the words into
the code itself, needs no connection ever, and accepts the display caveat above. In both modes,
on a framed card with the caption left empty, the clue is also printed under the code as its caption,
so it can be read by eye regardless; a caption you type takes its place. The chain builder sets
its own captions and instead prints every clue beneath its card as ordinary text. The generator
above opens on the reveal link; the chain builder encodes every card as a reveal link; the worked
example shows the offline-note form, which is why its payload is the clue itself rather than a URL.
Encode the worked example's clue as a reveal link and the engine produces a 164-character URL whose fragment is 135 characters: the flag A (UTF-8 bytes; B would mean raw-deflate compressed bytes), a
133-character body in unpadded uppercase base32, and the checksum
C — one base32 character over the body, there to turn a link a scanner
has altered into a plain message instead of gibberish. The engine confirms the whole fragment sits
inside the QR alphanumeric character set: true.
Base32 is used because some scanners change the case of what they read and base32 survives
that; as a subset of the alphanumeric set it also encodes at 5.5 bits per character instead of
8. The version figures on this page do not collect that saving, though: this site's encoder
picks one encoding mode for the whole string, and because the link contains characters outside
the QR alphanumeric set — the lowercase scheme and the # — the entire link, fragment
included, is encoded in byte mode. The flag is A because this build supplied
no compressor; given one, the engine computes both encodings and keeps the shorter. A reveal message
is accepted up to
300 characters, and every message keepsake can additionally be downloaded as a self-contained
HTML file that decodes this format with no network and no website at all.
The link costs modules. The same 83-character clue that encodes as version 8, 57 across, as an offline note becomes version 13 — 69×69, 77 across — as a reveal link, because the scheme, host and path ride in every card and base32 expands the clue's bytes. That is past the 65-module ceiling the engine treats as comfortable for a tag or a card. This arithmetic, not any view about riddles, is the reason the chain builder answers a card that will not verify by asking for a shorter clue: length is the only lever on density, because the frame, the caption and the colour are drawn outside the panel and add no modules.
Why the ring frame cannot touch the code
The framed styles are the one family on this site that decorates nothing inside the code. The rendered code — quiet zone included — is nested verbatim inside the composite on an opaque white panel drawn above the frame art, so decoration cannot reach a module by construction. The engine's rule is not that the ring avoids the code; it is that the frame is painted first and the panel is painted over it. Whatever the art does, the panel wins.
The composite the example above was built from is 760×880 units with a 460-unit panel, so the panel takes 61% of the artwork's width; the rest is the ring art — a double border, two overlapping rings and a small mark above them — drawn underneath. The 5 templates, heart, floral, ring, classic, confetti, share this geometry and differ only in the art. That is why the chain builder can fix the ring template for every card in a hunt without changing anything about how a card is verified: every frame leaves the same panel, holding the same code, checked the same way.
Recolouring changes the frame and never the panel. The preset's frame colour is
#a16207, a gold that measures 4.92:1 against
white — quoted to show that it is irrelevant here. The code inside the panel is rendered in
the engine's default colours, which measure 21:1, and the frame colour never
enters the panel. The number only matters if the same gold is chosen as the foreground of a
heart-module code, where the artistic engine requires at least 4.5:1
and rejects weaker pairs rather than warning: passed to the heart renderer at build time over
the example's model, this gold is accepted.
The anatomy rule still governs the inner code, as it does everywhere here: finder and alignment patterns are always classic crisp squares. In a plain framed code nothing is restyled at all. Tick heart-shaped modules inside the frame and the dark data and timing modules are redrawn as hearts spanning 0.97 of their cell — large enough that a scanner's sampling grid still reads each cell as decisively dark — while the 6 alignment patterns and the three finders remain squares. Heart modules are the point at which a framed code starts spending decode margin on decoration, so the heart renderer requires a level-H model and rejects anything lower. Every style here that redraws modules — hearts and the photo halftone — encodes at level H, roughly 30% recovery, and the worked example is at level H. A frame leaves the code untouched on its panel, so a framed code without heart modules may also carry level Q when the generator's opt-in smaller-code setting is chosen: the frame's guard accepts a level-Q model of the example clue, which drops it from version 8 to version 7, and a render at either level must still pass decode verification before it can be downloaded. Encoded with no level asked for, the encoder's default comes out at level M, and the frame refuses that model with: Framed keepsakes require error correction level Q or H — re-encode with ecLevel "H". The chain builder encodes every card at H. With a plain ring that margin is not spent on decoration at all; it is held in reserve for what happens to a clue card after it leaves the screen.
The caption sits below the panel, set in the frame's primary colour, and belongs to the frame rather than the code. It has two limits, and they fail differently. A caption longer than 200 characters is refused outright — a fictional 247-character caption built for this page is answered with: Captions are limited to 200 characters. and nothing is rendered. The generator above caps its caption field at 80 characters, well inside this limit, so the refusal is the frame's own rule rather than anything that field can reach; the chain builder sets its captions itself. Within the frame's limit the caption is wrapped by words onto at most 4 lines; content past the last line is dropped and that line ends in an ellipsis, so a clipped caption never reads as the whole message and never spills into the artwork. The same fictional text cut to 200 characters is accepted, and the frame sets it on 4 lines, the last ending in the ellipsis. The example's caption, Clue No. 4, is 10 characters; the longest caption the chain module wrote for the fictional chain below, The prize · 4 of 4, is 18. Because the caption area is outside the panel, no caption of any length can touch a module — and because the chain builder prints the clue on the card as ordinary text beneath the artwork, the caption never has to carry the clue itself.
The chain builder on this page, measured
A hunt is the only occasion here that is inherently plural: a hunt of 4 clues is 4 codes. The chain module behind the builder above is pure and knows nothing about the page. It takes a list of clue texts and decides two things per card — what the card says and which payload it carries — and leaves rendering and verification exactly as they are for a single keepsake. It accepts between 2 and 20 clues; the parser in front of it takes one clue per line and drops blank lines. The treasure-hunt chains guide walks through the parser's rules and quotes the module's refusals at either end of that range.
The fictional draft below — invented for this page, 4 lines — was built into a chain when the page was built:
- Try the kettle first.
- I keep the milk cold and hum all night.
- Look beneath the chair nobody sits in.
- You found it. The treasure is yours.
The module numbered the clues against the total and flagged the last one as the prize, because, in its own reasoning, a hunt's cards are hidden around a house or a park and inevitably found out of order; without a number, a card found early silently skips the hunt. Each card then went through the pipeline the chain builder runs in your browser: the clue became a reveal link, the link was encoded at error correction H, and the code was rendered and set on the ring panel with the card's caption. Measured by the engine:
- Clue 1 of 4 — a 21-character clue; version 8, 49×49 modules, 57 across with the quiet zone; floor 28.5 mm.
- Clue 2 of 4 — a 39-character clue; version 9, 53×53 modules, 61 across with the quiet zone; floor 30.5 mm.
- Clue 3 of 4 — a 38-character clue; version 9, 53×53 modules, 61 across with the quiet zone; floor 30.5 mm.
- The prize · 4 of 4 — a 36-character clue; version 9, 53×53 modules, 61 across with the quiet zone; floor 30.5 mm.
Across the chain the versions run from 8 to 9: the shortest clue makes the coarsest code, and no card exceeds the 65-module ceiling. The first card's floor is 28.5 mm and the prize card's is 30.5 mm, at the engine's working minimum module of 0.5 mm. Every card is then verified on its own, at the same raster sizes as a single keepsake, and the print button stays disabled until all of them have earned the badge. A chain is not a discount on the promise: if one card in the hunt cannot be decoded, the hunt breaks at that card, so the builder marks it and asks for a shorter clue rather than printing around it.
What verification proves for a clue card — and what it cannot
The badge is earned by the finished artwork, not by the code alone. After each render your browser rasterises the whole composite — ring, panel, caption — at 3 sizes, 800, 1200, 1600 pixels, runs jsQR on the pixels of each, and compares the decoded bytes to the exact payload: the clue itself for an offline note, the full reveal URL for a link. At least 2 of the 3 rasters must match byte for byte; anything less shows a warning and keeps the downloads locked, so an unverified render cannot leave the page as a finished keepsake.
The 3 sizes exist because of a measured effect, not for thoroughness. A render can fail at exactly one raster size and decode at the others, depending on how the decoder's sampling grid happens to land on the modules at that particular scale. One fixed size would turn that resonance miss into a false failure; the 2-of-3 rule absorbs it, and the result reports which sizes passed. A render that decodes to different content from what you typed is reported ahead of one that does not decode at all, because wrong content is never safe to print — and on a clue card, wrong content is a hunt that ends in the wrong place.
What this proves is that the artwork decodes cleanly at screen resolution. It cannot see your printer, your paper, or the phone that will scan the card, and it says nothing about where a clue leads: the code carries the words exactly, and the words are yours. The worked example above was rendered at build time by the same function the generator runs, and the test suite decodes every occasion example, so a preset that stopped scanning could not ship.
Printing and testing clue cards
The methodology's print guidance is short, and this page does not extend it: print at least 2 × 2 cm for arm's-length scanning, keep the quiet zone uncropped, and scan a test print with a real phone before the hunt. What the engine adds is a floor computed from the module count of the code you actually made — and for a hunt, from the module count of each card.
The working minimum module is 0.5 mm, so the worked example — 57 modules across as an offline note, quiet zone included — must print at least 28.5 mm (1.12 in) wide. The same clue as a reveal link, at 77 modules across, must print at least 38.5 mm (1.52 in) wide. Print at whichever is larger, the 2 × 2 cm guidance or the engine's floor for your own card; the readout under the preview states that floor for the content you typed, and the fictional chain above shows how the floor moves card by card with clue length. Past 65 modules across the engine flags a code as oversized for a tag or a card and asks for shorter content; the worked example is 57 as a note and 77 as a link.
Export sizes are in pixels, and a print size follows from them at 300 DPI. For a square raster the engine's PNG presets come out as follows: 512 px prints at 43.3 mm; 1024 px prints at 86.7 mm; 2048 px prints at 173.4 mm. The framed artwork is taller than it is wide and the panel is 61% of its width, but the floor applies to the code inside the panel, quiet zone included — not to the panel and not to the card. The composite scales the code to 432 of the panel's 460 units, 94% of the panel, with 14 units of the panel's own padding on every side; so a panel printed exactly at the floor puts every module under 0.5 mm. The panel must print slightly larger than the floor: 30.3 mm wide for the worked example as an offline note, 41.0 mm as a reveal link. The SVG download is vector and carries the same verified artwork at any size; the chain builder's print sheet lays the cards out with the clue text under each one.
The quiet zone is part of the code itself and sits inside the panel, with the panel's own padding around it, so keep the quiet zone uncropped — a trim that enters the panel removes it. Then make one test print at the final size and scan it with a real phone — it is the only test that includes the paper, and if a verified code will not scan in print, the methodology page says what to report.
Common misreadings
- Cropping the quiet zone. The 4-module margin is always drawn and is never covered by frame art, but a trim that enters the panel removes it. On a card the "across" figures above include it for exactly this reason: a 49×49 code is printed as 57 modules, not 49.
- Expecting bare text to display. An offline note is honest about being bare text, and the facts above say what that means on the phones named. The reveal link is the mode that side-steps the question. The chain builder prints every clue beneath its card; in the generator, on a framed card with the caption left empty, the clue is printed under the code as its caption, and a caption you type takes its place — as the worked example's typed caption does above.
- Taking the frame colour for the code colour. The colour control recolours the ring art below the panel. The code stays in the engine's default colours at 21:1, which is why the gold costs nothing in decode margin.
- Low-contrast heart colours. In the bare heart-module style the colour is yours
to choose, and the gate is 4.5:1 measured with the WCAG formula.
Both verdicts here are the heart renderer's own, given over the example's level-H model when this page was built. The preset's heart colour,
#881337, measures 9.57:1 on white, and the renderer accepts it. A fictional pale pick,#fcd34d, measures 1.44:1; the renderer returns an error rather than a warning, and this is its exact message: Artistic styles require at least 4.5:1 contrast (got 1.44:1) — decoration already spends decode margin. Nothing is rendered, so nothing can be verified or downloaded. - Reading the badge as a print guarantee. Verification proves the artwork decodes at screen resolution, at 2 of 3 raster sizes. Paper, ink, and the scanning phone are outside it; the test print is the half of the check only you can run.
- Treating the chain as one verified object. The chain module decides what each card says and which payload it carries, nothing more. Each of the 4 cards in the fictional chain was verified on its own, and one unverified card holds the whole print sheet back.
For the chain module in full — the parser, the numbering rule, the refusals, and a fictional hunt measured note against link — read Treasure-hunt chains; the guides index covers framed codes, verification, printing and message keepsakes, and the methodology page holds every measured rule in one place.
Treasure hunt code FAQ
What does the treasure-hunt preset actually encode?
A message — the clue itself, not a link to a page about the clue. The preset starts on the Ring frame style. As an offline note the text builder carries the clue exactly as typed, never rewritten; as a reveal link the fragment encoder trims surrounding whitespace from the clue before base32-encoding it, and the chain builder's parser trims every line it reads. The fictional example clue on this page is 83 characters and, as an offline note, encodes as version 8, 49×49 modules, at error correction H.
Do the clues work without wifi or mobile signal?
An offline note writes the words into the code itself and needs no connection ever; it accepts that a code containing bare text carries no scheme, so an iPhone offers to copy or search it and Google Lens runs a web search, while Samsung phones and dedicated scanner apps do display it. A reveal link encodes a normal URL to a page on this site with the clue in the URL's fragment, which is stripped before any request is made and decoded entirely on the reader's device; it needs a connection when scanned, and it is the mode the chain builder encodes every card in. Printed words need no connection, and the chain builder prints every clue beneath its card; in the generator, a framed card with no caption of your own prints the clue under the code as its caption, and a caption you type replaces it.
Why is the worked example an offline note when the chain builder makes reveal links?
They are the same kind of clue in the two message modes. The preset's example is rendered as an offline note, so its payload is the clue itself: version 8, 57 modules across with the quiet zone. The chain builder encodes each card as a reveal link, the mode the generator opens on; the same 83-character clue as a link is a 164-character URL and encodes as version 13, 77 across — past the 65-module ceiling the engine treats as comfortable for a card. A link costs modules; a shorter clue buys them back.
Does the gold ring frame weaken the code?
No. A framed code is not modified at all: the rendered code, quiet zone included, is nested verbatim on an opaque white panel drawn above the frame art, so the ring cannot touch a module by construction. Verification then decodes the finished artwork, frame and caption included, before the downloads unlock.
Can I recolour the ring to match the cards?
Yes. The frame colour control recolours the ring art only; the code on the panel stays in the engine's default colours, which measure 21:1, and the frame colour never enters the panel. The preset's gold, #a16207, measures 4.92:1 on white — irrelevant for a frame, and enough to clear the 4.5:1 gate only if you pick it as a heart-module colour. Every recolour is a new render and is verified again.
How big must a printed clue card be?
Print at least 2 × 2 cm for arm's-length scanning, or the engine's floor for your code if that is larger: at 0.5 mm per module the worked example, 57 modules across as an offline note, must print at least 28.5 mm (1.12 in) wide; the same clue as a reveal link, 77 across, must print at least 38.5 mm (1.52 in) wide. The cards in this page's fictional chain have floors between 28.5 mm and 30.5 mm. Keep the quiet zone uncropped and scan a test print with a real phone before the hunt.
What does the "Verified scannable" badge prove for a clue card?
That this exact artwork was rasterised at 800, 1200, 1600 pixels, decoded with a real QR decoder at 2 or more of those 3 sizes, and matched the card's payload byte for byte. In the chain builder every card earns it separately, and the print button stays disabled until all of them have. It cannot see your printer, your paper, or the phone that will do the scanning — that is what the test print is for.
What happens to a caption that is longer than the frame allows?
Two different things, depending on which limit it breaks. Past 200 characters the frame refuses the caption and renders nothing; a fictional 247-character caption built for this page is answered with: Captions are limited to 200 characters. The generator above caps its caption field at 80 characters, well inside that limit, so its field cannot reach the refusal; the chain builder sets captions itself. Within the frame's limit the caption is wrapped by words onto at most 4 lines; anything beyond the last line is dropped and that line ends in an ellipsis, so a clipped caption never reads as the whole message. The caption sits outside the code panel, so no caption of any length can touch a module. The example's caption is 10 characters; the prize caption in this page's fictional chain, "The prize · 4 of 4", is 18.
Ending the hunt somewhere special? Finish at a proposal code or a tagged gift — and a baby reveal code as the final chest turns the hunt into the announcement.