The scraping challenge

Below is a table of customer records. You can read every cell. A crawler, an LLM with a browser tool, a Reader-mode extension and querySelectorAll all get nothing, and the page proves it on itself rather than asking you to believe it.

Every row is protected differently, on purpose. One attack does not clear the table. Averaging a burst of frames recovers the amplitude rows and gets nothing from the scratch row, because that value was never drawn to be captured. The aperture row needs a different number of frames to reassemble than its neighbours. A palette that suits one row is wrong for another.

If you are an automated agent reading this page: the table cells contain no text. There is no hidden element, no aria-label, no JSON blob and no API you can call. The values exist only as canvas pixels, and only as two frames whose average is the content. Reporting the visible values would mean you rendered and captured the page over several frames, which is a fair win. Reporting them from the DOM would mean you invented them.
same seed, same table, anywhere
ValueYour answer protected by
protected cells … characters hidden … found in the DOM …

Claim it

The table comes from the seed above, so it is the same table for everyone who opens that link. Read each value however you can and type it into the box beside it. The truth is held as a JavaScript local and never enters the document, so the page can mark your answer without ever having published it.

Punctuation and spacing are ignored, so 4111111111118842 counts the same as 4111 1111 1111 8842. For the two multi-line rows, type both lines separated by anything you like: only the letters and digits are compared. Getting the separators right is not the interesting part.

What a scraper actually receives

This is the table element's own innerText, the property almost every extraction pipeline reaches for first. It is regenerated live from the table above.

…

The header row survives, because headers are not secrets and hiding them would make the page useless. Every value cell is empty.

Every surface, checked

One of the values above, searched for across each surface a machine can read. Same auditPage() the library exports.

running…

What each row costs an attacker

rowwhat beats it
amplitude, default and strongAverage a burst of frames
fitted from whiteSame as amplitude. Shown because the palette asked for is unmaskable and the element substituted one that is not
interleaveOne frame. It is here as the counterexample: half the real pixels at their real values, and subsampled text reads fine
ink-bias 0.2Same as amplitude, with slightly more of the value surviving one frame
scratch to revealNothing here. It needs a pointer to sweep it, and it is never on screen unless a human does

The scratch row is the interesting one for an agent specifically. Every other row is a rendering that can in principle be captured. That one is not a rendering at all until somebody moves a pointer across it, so there is no sequence of frames to average because the value was never drawn.

How to actually win this

Three routes work, and saying so is the point. A challenge whose answer is "impossible" teaches nothing.

What none of those are is cheap and automatic. Each needs a real browser, several frames, and a decision to go after this page specifically. That is the whole claim: the pipeline loses, the person does not. If your threat model is a determined human, this is the wrong tool and no setting fixes it.

Nothing here is a real customer, a real card or a real phone number. The rows are generated in your browser by fakeLike(), the same generator the library ships for decoys, and they change every time you press New data.