TLcube

QR reads well.
It has just never been pretty

Posters, packaging, business cards — QR is everywhere, and dropping a black-and-white square onto a design you spent weeks on has always stung. But QR looks the way it does for a reason: it is what you build when a 1994 camera and 1994 silicon have to read it at all.

Those constraints have largely lifted. Phone cameras and compute have room to spare now. So instead of chasing recognition alone, we spent that headroom on being worth looking at. This is not a replacement for QR — it is a seat next to it.

Why now

QR was the right answer for its moment. Two values, square modules, big finder patterns — every one of those choices existed so a weak camera could still read it. Under those constraints, giving up on looks was rational.

That premise changed. Phone cameras today are nothing like those, and real-time video processing runs inside a browser. So the headroom could go into density — or it could go into how it looks. That is why this exists.

Density was the trade. Rhombic cells are less area-efficient than square modules. This format does not compete on density. If you need to carry a lot, QR is the right tool.

Three types

Pick by where it will sit. All three share one data contract and differ only in silhouette. Every code below encodes https://tl.estre.so.

Type Y — single cube

Type Y — single cube

Three n×n faces. A single solid mark, good for signage and merch.

n = 13 / 21 / 25 · net payload 31 / 98 / 141 B
Type O — hex field

Type O — hex field

The base form. Rhombic cells tile outward from a central finder.

k = 6 / 8 / 10 · net payload 18 / 39 / 65 B
Type A — triangular silhouette

Type A — triangular silhouette

A hexagonal core plus corner patches, forming an equilateral triangle.

k = 6 / 8 / 10 · net payload 31 / 62 / 101 B

Net payload is at ECC-M. All three types can carry a fallback QR alongside, so there is still a path where a TL code cannot be read.

How it works

1. Cell = three rhombi

A rhombille tiling splits each hexagonal cell into T, L and R faces.

2. Order = symbol

Ranking the three luminances gives 3! = 6 outcomes — one base-6 digit.

3. 3 digits = 1 symbol

Reed–Solomon over the prime field GF(211) corrects errors.

What this frees up

Invariant under monotonic transforms. Global lighting shifts, gamma, and printer tone mapping are monotonic — they cannot change an ordering. If the order survives, the value survives.

The renderer stays free. The data contract is only "the order between faces, plus a minimum separation". Inside that, colour, texture, per-face gradients and animation are all open. That freedom is the point of the format.

It does not make anything pretty on its own. What opens up is the contract, not the outcome. What you draw inside it is still your work.

Scanner status

The decoder is under development. The numbers below come from photos taken on real phones — not synthetic test renders.

TypeReal photos decodedDecode timeLive scanning
Type Y — single cube3 / 3~0.4 susable
Type O — hex field3 / 3~1.1 simproving
Type A — triangular silhouette2 / 3~0.8 simproving
Centre-QR variant (all three types)8 / 9improving

Decode time dominates how live scanning feels. The scanner looks at a frame roughly every 0.3 s, so when one read takes several seconds the frames in between are dropped — and regardless of accuracy it feels like it “only reads once in a while”. Speed is the top priority right now.

Shooting conditions matter too. Measured, 9 pixels per cell was the decode floor, and at the same distance an ultra-wide lens frames the code smaller and drops below that line (ultra-wide 7.6 px failed / wide 9.1 px succeeded). That is why the scanner offers a lens picker.

In the centre-QR variant a QR block takes the place of the central finder. It was unreadable for a while because there was no entry point; a path that uses the QR’s own finder patterns as reference now handles it.

Measured 2026-08-11 · sample of 9 photos across three phone sensors (ultra-wide, wide, telephoto) · short side 1440 px. The sample is small — read this as a current state, not a success rate.

Spec and implementation

The format spec and reference implementation are published under the Apache License 2.0. Vanilla JavaScript, no build toolchain, zero runtime dependencies — it runs as a single HTML file.

Implementing only the decoder counts as a conforming implementation. Adoption starts with the reading side.

SiteRoleState
tlcube.estre.soGeneratorlive
tlscan.estre.soScannerin development — status ›
tl.estre.soOverview hubyou are here