Send
This screen becomes the transmitter.
- payload
- —
- blocks
- —
- session
- —
- estimate
- —
Fills the screen. Tap or press Escape to stop.
Hold the phone about a hand's width away and let it focus.
Photonic Zero
Point a phone camera at this screen. The file crosses the gap on nothing but photons — no wifi, no bluetooth, no pairing, no upload.
That single fact defines the whole design space. Show a sequence of frames to a camera and it will miss some — autofocus hunts, a hand shakes, the operating system schedules something else. There is no back channel. Nothing can ask for frame 47 again.
The obvious answer is to loop forever and hope. That is the coupon collector problem: slow, and unbounded in the worst case.
Every frame is a droplet: the XOR of a pseudo-randomly chosen subset of the message's blocks. The receiver does not care which droplets it catches, only how many. Collect a little over the minimum and the message falls out.
Frames 0..K go out systematically, so a clean capture finishes at zero
coding overhead and only pays for the fountain when frames are genuinely lost.
The counters below are a real transfer running in this tab right now, losing one frame in four at random. Nothing is retransmitted and nothing restarts. Losing a quarter of the frames simply costs a quarter more of them.
Measured on this build, not estimated. The bottleneck is the display refresh rate, not the decoder — which leaves plenty of headroom on a phone.
| Profile | Grid | Per frame | Frames / MB | Best case |
|---|---|---|---|---|
| mono | 97² | 776 B | 1,352 | 45 s |
| robust | 33² | 34 B | 30,840 | 17 min |
| balanced | 49² | 479 B | 2,190 | 73 s |
| resilient | 65² | 640 B | 1,639 | 55 s |
| fast | 97² | 2,739 B | 383 | 13 s |
fast is the default for short, pre-encrypted or signed payloads. It needs a close, steady, well-lit colour capture. Choose mono when capture reliability matters more than speed; the decoder calibrates to whatever dark ink you pick.
For the fastest transfer, choose fast and 20 frames per second.
That is 2,739 bytes per frame instead of 776 — about six times quicker than the
default, at the cost of needing colour and a steadier hand.
Files are gzipped before transmission, which is worth roughly 2.5x on text, source and most binaries. The 1 MB sample here is random bytes, so it compresses by nothing at all — that is the honest worst case, and what the timings above assume.
Open this page on a second device to receive. Nothing is uploaded — the file is read, encoded and decoded entirely inside the two browsers.
This screen becomes the transmitter.
Fills the screen. Tap or press Escape to stop.
Hold the phone about a hand's width away and let it focus.
This device becomes the camera.
0 B recovered, checksum verified.
Save itThere is no backend. This page is static files on a CDN; your file is read into memory, encoded by a 100 kB WebAssembly module, and drawn to a canvas. It is never sent anywhere, because sending it somewhere would defeat the entire point of an air-gap protocol.
The decoded result lives in an object URL that is revoked the moment you leave, start another transfer, or hide the tab. Close this page and it is gone.
Rust core with zero external dependencies, no_std, plus bindings for
JavaScript, Python and C.
npm install photonic-zero
pip install photonic-zero
cargo add pz-core
PZ is a transport, not a security protocol. It provides no confidentiality and no authenticity — anything with a view of the screen can read the frames. Encrypt before you transmit if that matters. v0.1.0, wire format unfrozen, not independently audited.