@r3dacted42/shape-puzzle-captcha
v0.0.6
Published
Web component for a shape puzzle based captcha UI, based on the square hole meme. Drag and drop shapes into the correct holes to pass!
Readme
shape-puzzle-captcha
An interactive 3D Web Component captcha UI inspired by the (in)famous "Square Hole" meme. Users must drag and drop 3D shapes into their ~~in~~correct holes to pass the verification. Built with Lit, Three.js, and CSG (Constructive Solid Geometry).
[!WARNING]
This component provides the UI for a puzzle. Because the verification happens in the browser, it can be bypassed via the developer console. Do not use this as your sole line of defense against bot attacks on sensitive endpoints.
Features
- Built as a standard Web Component using Lit, it just works anywhere.
- Uses Three.js for real-time rendering and physics interactions.
- Native dark mode support, customizable colors using CSS variables and attributes.
- Portal based popup for the puzzle, no messing around with the z-index.
Register the component
Using a Package Manager (Recommended)
- Install the package and its dependencies via npm, yarn, or pnpm.
npm install @r3dacted42/shape-puzzle-captcha - Import it once in the file containing your application's main entry point (e.g., main.js, app.js, or index.js) to register the Web Component:
import "@r3dacted42/shape-puzzle-captcha";
Support for SSR depends on whether an integratio for Lit is available. If you're using Astro or something similar, consider using the CDN method below.
Using a CDN (Vanilla HTML)
If you aren't using a bundler, you can use the component directly in the browser. Because modern ES modules are used, you must include an import map to resolve the dependencies.
<script type="importmap">
{
"imports": {
"lit": "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js",
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three-bvh-csg": "https://unpkg.com/[email protected]/build/index.module.js",
"three-mesh-bvh": "https://unpkg.com/[email protected]/build/index.module.js"
}
}
</script>
<script
type="module"
src="https://unpkg.com/@r3dacted42/shape-puzzle-captcha@latest/dist/shape-puzzle-captcha.js"
></script>Alternatively, if you can't or don't want to use import maps, you can use the bundle version:
<script
type="module"
src="https://unpkg.com/@r3dacted42/shape-puzzle-captcha@latest/dist/bundle/shape-puzzle-captcha.js"
></script>Usage
Once registered, use the <shape-puzzle-captcha> tag anywhere in your HTML or JSX templates, and listen for events.
<shape-puzzle-captcha auto-dark> </shape-puzzle-captcha>
<button id="submit-btn" disabled>Humans Only Please!</button>
<script>
const submitBtn = document.getElementById("submit-btn");
document.addEventListener("shapepuzzlecaptcha:solved", (e) => {
// A backend is essential for a truly secure captcha!
// if (someBackendCall(clientInfo).isVerified)
submitBtn.disabled = false;
});
</script>API Reference
Attributes / Properties
| Attribute | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| event-key | String | "shapepuzzlecaptcha" | The namespace for the custom events emitted by the component. |
| shape-color | Number (Hex) | 0xa83232 | Hex color code for the default, unselected shapes. |
| selected-shape-color | Number (Hex) | 0xc27502 | Hex color code for the shape currently being dragged. |
| disable-audio | Boolean | false | Set true to hide the audio button |
| auto-dark | Boolean | "data" | false | Set it true to follow the browser's color scheme. |
[!TIP] Use
class="dark"ordata-darkon<shape-puzzle-captcha>to use the dark color scheme. See Advanced Theming for details. Theauto-darkattribute usesclass="dark"by default. Set it to"data"to use thedata-darkattribute for dark theme.
Methods
| Method | Returns | Description |
| ------ | ------- | ----------- |
| reset() | undefined | Reset the 3D scene and returns the component to the "unsolved" state. Also emits the shapepuzzlecaptcha:reset event. |
Events
The component emits global events that bubble up to the document/window.
| Event Name | Description |
| ---------- | ----------- |
| shapepuzzlecaptcha:solved | User clicks "Verify" and all shapes are in the correct holes. |
| shapepuzzlecaptcha:failed | User clicks "Verify" but the shapes are placed incorrectly. |
| shapepuzzlecaptcha:reset | User clicks the reset button or the reset() function is called. |
| shapepuzzlecaptcha:audio | User clicks the audio button. |
| shapepuzzlecaptcha:info | User clicks the info button. |
[!TIP] The event names are dynamically prefixed by your
event-key, so if you haveevent-key="meow"on the component, then you'll get events likemeow:solved,meow:failed,meow:reset, etc.
Advanced Theming
The component uses CSS variables for easy styling. You can override these variables on the <shape-puzzle-captcha> element to match your brand. The internal popup component will automatically inherit these styles.
shape-puzzle-captcha {
--font-family: system-ui, -apple-system, sans-serif;
--bg-color: #ffffff;
--canvas-bg-color: #f0f0f0;
--text-color: #000;
--primary-color: #1a73e9;
--on-primary-color: #ffffff;
--primary-hover-color: #1669c1;
--border-color: #cccccc;
--image-btn-color: #737373;
}
shape-puzzle-captcha(.dark),
shape-puzzle-captcha([data-dark]) {
--bg-color: #1f1f1f;
--canvas-bg-color: #292929;
--text-color: #ffffff;
--primary-color: #611c99;
--on-primary-color: #ffffff;
--primary-hover-color: #6e16c1;
--border-color: #505050;
--image-btn-color: #8d8d8d;
}MIT License © 2026 r3dacted42
