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.

What you see
Screenshot
+ denoise

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

ThreatResult
Print Screen, Win+Shift+S, Cmd+Shift+4, single-frame OCRBlocked
DOM-reading AI agents, LLM scrapers, View Source, Select AllBlocked absolutely
A blur or denoise pass over one captured frameBlocked at block ≥ stroke width
The security check and the scraping challenge run these claims for real rather than asking you to take the table on trust, and they are also where the method's limits are laid out; everything else in the sidebar measures a claim on a live element.