nocap
Content is split into frames that alternate at your display's refresh rate.
Each frame is noise, their mean is the content, and your visual system does the
averaging. So you read it and a single capture does not. The text never
enters the DOM, so scrapers and page-reading agents get nothing at all. It can
even hand a capture a plausible wrong value instead of
noise.
Thirty seconds says it better than this paragraph:
watch the promo reel, live, and screenshot it
while it plays.
Live. The middle panel is a real capture of the left one, re-grabbed each second. The right is that capture through a box blur, the cheapest attack on this.
Install
npm install nocap-js
import 'nocap-js'; // registers <nocap-secret>
const el = document.querySelector('nocap-secret');
el.secret = await fetchAccountNumber(); // write-only, never enters the DOM
Every attribute and export is documented in the API reference, with integration notes in the README.
React, Vue, Svelte, Angular
<nocap-secret> is a standard custom element, so every
framework can render it with no wrapper package. One rule: the value
goes in through the secret property, never a template
attribute. An attribute would write the plaintext into the DOM --
the exact surface this library exists to keep it off -- and the element
deliberately does not read one. TypeScript already knows the tag: the element
class is registered in HTMLElementTagNameMap, so refs and
querySelector('nocap-secret') come back typed.
React
import 'nocap-js';
import { useEffect, useRef } from 'react';
function AccountNumber({ value }) {
const el = useRef(null);
// A ref, not a JSX attribute: secret is a write-only DOM property.
useEffect(() => { el.current.secret = value; }, [value]);
return <nocap-secret ref={el} strength="medium"></nocap-secret>;
}
Vue 3
// main.js -- the tag is a custom element, not a Vue component app.config.compilerOptions.isCustomElement = (tag) => tag === 'nocap-secret';
<script setup> import 'nocap-js'; defineProps(['account']); </script> <template> <!-- .prop forces a property binding, so the value stays out of the DOM --> <nocap-secret :secret.prop="account" strength="medium"></nocap-secret> </template>
Angular
import 'nocap-js';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// [secret] is a property binding in Angular, so this is already safe.
template: `<nocap-secret [secret]="account" strength="medium"></nocap-secret>`,
})
export class AccountCard { account = ''; }
Svelte
Svelte sets properties on custom elements directly, so
<nocap-secret secret={value}> just works; the
README section has the
full example.
Server-side rendering is safe to import under -- Next, Astro and Remix evaluate module top-level on the server, so registration is guarded and the element only paints in a browser. Set the value on the client, where it came from an authenticated fetch; a value serialized into SSR HTML would be in the page source, which is the thing being avoided.
What this defeats
| Threat | Result |
|---|---|
| Print Screen, Win+Shift+S, Cmd+Shift+4, single-frame OCR | Blocked |
| DOM-reading AI agents, LLM scrapers, View Source, Select All | Blocked absolutely |
| A blur or denoise pass over one captured frame | Blocked at block ≥ stroke width |