cap-widget 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "cap-widget",
3
+ "version": "0.1.46",
4
+ "description": "Client-side widget for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.",
5
+ "keywords": [
6
+ "algorithm",
7
+ "anti-abuse",
8
+ "anti-automation",
9
+ "anti-bot",
10
+ "anti-ddos",
11
+ "anti-dos",
12
+ "anti-exploitation",
13
+ "anti-scraping",
14
+ "anti-spam",
15
+ "attack-mitigation",
16
+ "bots",
17
+ "captcha-alternative",
18
+ "client-server",
19
+ "complexity",
20
+ "computational-puzzle",
21
+ "crypto",
22
+ "cryptographic",
23
+ "cybersecurity",
24
+ "ddos",
25
+ "defense",
26
+ "detection",
27
+ "filtering",
28
+ "hashcash",
29
+ "hcaptcha",
30
+ "invisible",
31
+ "pow",
32
+ "prevention",
33
+ "proof-of-work",
34
+ "protection",
35
+ "protocol",
36
+ "puzzle",
37
+ "recaptcha",
38
+ "security",
39
+ "spam",
40
+ "turing-test",
41
+ "verification"
42
+ ],
43
+ "homepage": "https://trycap.dev/?utm_source=npm&utm_medium=referral&utm_campaign=homepage",
44
+ "bugs": {
45
+ "url": "https://github.com/tiagozip/cap/issues"
46
+ },
47
+ "license": "Apache-2.0",
48
+ "author": "Tiago",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/tiagozip/cap.git"
52
+ },
53
+ "type": "commonjs",
54
+ "main": "cap.min.js",
55
+ "types": "cap.d.ts"
56
+ }
@@ -0,0 +1,116 @@
1
+ (() => {
2
+ const showWidget = (element, capWidget) => {
3
+ const offset = parseInt(element.getAttribute("data-cap-floating-offset")) || 8;
4
+ const position = element.getAttribute("data-cap-floating-position") || "top";
5
+
6
+ Object.assign(capWidget.style, {
7
+ display: "block",
8
+ position: "absolute",
9
+ zIndex: "99999",
10
+ opacity: "0",
11
+ transform: "scale(0.98)",
12
+ marginTop: "-4px",
13
+ boxShadow: "rgba(0, 0, 0, 0.05) 0px 6px 24px 0px",
14
+ borderRadius: "14px",
15
+ transition: "opacity 0.15s, margin-top 0.2s, transform 0.2s",
16
+ });
17
+
18
+ setTimeout(() => {
19
+ capWidget.style.transform = "scale(1)";
20
+ capWidget.style.opacity = "1";
21
+ capWidget.style.marginTop = "0";
22
+ }, 5);
23
+
24
+ // `position: absolute` is resolved against the nearest positioned ancestor (capWidget.offsetParent),
25
+ // but getBoundingClientRect() returns viewport-relative coords, convert to container-relative coords.
26
+ const triggerRect = element.getBoundingClientRect();
27
+ const containerRect = (capWidget.offsetParent ?? document.documentElement).getBoundingClientRect();
28
+
29
+ const centeredLeft = triggerRect.left + (triggerRect.width - capWidget.offsetWidth) / 2;
30
+ const clampedLeft = Math.max(2, Math.min(centeredLeft, window.innerWidth - capWidget.offsetWidth));
31
+ capWidget.style.left = `${clampedLeft - containerRect.left}px`;
32
+
33
+ if (position === "top") {
34
+ const idealTop = triggerRect.top - capWidget.offsetHeight - offset;
35
+ const clampedTop = Math.max(0, idealTop);
36
+ capWidget.style.top = `${clampedTop - containerRect.top}px`;
37
+ } else {
38
+ const idealTop = triggerRect.bottom + offset;
39
+ const clampedTop = Math.min(idealTop, window.innerHeight - capWidget.offsetHeight);
40
+ capWidget.style.top = `${clampedTop - containerRect.top}px`;
41
+ }
42
+
43
+ capWidget.solve();
44
+ };
45
+
46
+ const hideWidget = (capWidget) => {
47
+ setTimeout(() => {
48
+ capWidget.style.transform = "scale(0.98)";
49
+ capWidget.style.opacity = "0";
50
+ capWidget.style.marginTop = "-4px";
51
+ }, 500);
52
+
53
+ setTimeout(() => {
54
+ capWidget.style.display = "none";
55
+ }, 700);
56
+ };
57
+
58
+ const setupElement = (element) => {
59
+ const capWidgetSelector = element.getAttribute("data-cap-floating");
60
+ if (!capWidgetSelector) return;
61
+
62
+ const capWidget = document.querySelector(capWidgetSelector);
63
+ if (!document.contains(capWidget) && !capWidget.solve) {
64
+ throw new Error(`[cap floating] "${capWidgetSelector}" doesn't exist or isn't a Cap widget`);
65
+ }
66
+
67
+ capWidget.style.display = "none";
68
+ let bypass = false;
69
+
70
+ element.addEventListener(
71
+ "click",
72
+ (e) => {
73
+ if (bypass) {
74
+ bypass = false;
75
+ return;
76
+ }
77
+
78
+ e.stopImmediatePropagation();
79
+ e.preventDefault();
80
+
81
+ showWidget(element, capWidget);
82
+
83
+ const onSolve = ({ detail }) => {
84
+ capWidget.removeEventListener("solve", onSolve);
85
+ element.setAttribute("data-cap-token", detail.token);
86
+ element.setAttribute("data-cap-progress", "done");
87
+
88
+ setTimeout(() => {
89
+ bypass = true;
90
+ element.click();
91
+ }, 500);
92
+
93
+ hideWidget(capWidget);
94
+ };
95
+
96
+ capWidget.addEventListener("solve", onSolve);
97
+ },
98
+ true,
99
+ );
100
+ };
101
+
102
+ const init = (root) => {
103
+ setupElement(root);
104
+ root.querySelectorAll("[data-cap-floating]").forEach(setupElement);
105
+ };
106
+
107
+ init(document.body);
108
+
109
+ new MutationObserver((mutations) =>
110
+ mutations.forEach((mutation) =>
111
+ mutation.addedNodes.forEach((node) => {
112
+ if (node.nodeType === Node.ELEMENT_NODE) init(node);
113
+ }),
114
+ ),
115
+ ).observe(document.body, { childList: true, subtree: true });
116
+ })();
package/src/cap.css ADDED
@@ -0,0 +1,268 @@
1
+ @media (prefers-reduced-motion: reduce) {
2
+ .captcha,
3
+ .captcha * {
4
+ transition: none !important;
5
+ animation: none !important;
6
+ }
7
+
8
+ .label {
9
+ transition: none !important;
10
+ transform: none !important;
11
+ filter: none !important;
12
+ opacity: 1 !important;
13
+ }
14
+
15
+ .label:not(.active) {
16
+ display: none !important;
17
+ }
18
+
19
+ .label.exit {
20
+ display: none !important;
21
+ }
22
+ }
23
+
24
+ .captcha,
25
+ .captcha * {
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ .captcha {
30
+ background-color: var(--cap-background, #fdfdfd);
31
+ border: 1px solid var(--cap-border-color, #dddddd8f);
32
+ border-radius: var(--cap-border-radius, 14px);
33
+ user-select: none;
34
+ height: var(--cap-widget-height, 58px);
35
+ width: var(--cap-widget-width, 260px);
36
+ display: flex;
37
+ align-items: center;
38
+ padding: var(--cap-widget-padding, 14px);
39
+ gap: var(--cap-gap, 15px);
40
+ cursor: pointer;
41
+ transition:
42
+ filter 0.2s,
43
+ transform 0.2s;
44
+ position: relative;
45
+ -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
46
+ overflow: hidden;
47
+ color: var(--cap-color, #212121);
48
+ }
49
+
50
+ .captcha:not([data-state]):active {
51
+ transform: scale(0.97);
52
+ }
53
+
54
+ .captcha:hover {
55
+ filter: brightness(98%);
56
+ }
57
+
58
+ .captcha * {
59
+ font-family: var(
60
+ --cap-font,
61
+ system,
62
+ -apple-system,
63
+ "BlinkMacSystemFont",
64
+ ".SFNSText-Regular",
65
+ "San Francisco",
66
+ "Roboto",
67
+ "Segoe UI",
68
+ "Helvetica Neue",
69
+ "Lucida Grande",
70
+ "Ubuntu",
71
+ "arial",
72
+ sans-serif
73
+ );
74
+ }
75
+
76
+ .checkbox {
77
+ width: var(--cap-checkbox-size, 25px);
78
+ height: var(--cap-checkbox-size, 25px);
79
+ border: var(--cap-checkbox-border, 1px solid #aaaaaad1);
80
+ border-radius: var(--cap-checkbox-border-radius, 6px);
81
+ background-color: var(--cap-checkbox-background, #fafafa91);
82
+ transition: opacity 0.2s;
83
+ margin-top: var(--cap-checkbox-margin, 2px);
84
+ margin-bottom: var(--cap-checkbox-margin, 2px);
85
+ }
86
+
87
+ .captcha p {
88
+ margin: 0;
89
+ font-weight: 500;
90
+ font-size: 15px;
91
+ user-select: none;
92
+ }
93
+
94
+ .label-wrapper {
95
+ position: relative;
96
+ height: 2em;
97
+ overflow: hidden;
98
+ flex: 1;
99
+ min-width: 0;
100
+ display: flex;
101
+ align-items: center;
102
+ }
103
+
104
+ .label-wrapper::before,
105
+ .label-wrapper::after {
106
+ content: "";
107
+ pointer-events: none;
108
+ position: absolute;
109
+ left: 0;
110
+ width: 100%;
111
+ height: 0.6em;
112
+ z-index: 1;
113
+ }
114
+
115
+ .label-wrapper::before {
116
+ top: 0;
117
+ background: linear-gradient(to bottom, var(--cap-background, #fdfdfd), transparent);
118
+ }
119
+
120
+ .label-wrapper::after {
121
+ bottom: 0;
122
+ background: linear-gradient(to top, var(--cap-background, #fdfdfd), transparent);
123
+ }
124
+
125
+ .label {
126
+ position: absolute;
127
+ white-space: nowrap;
128
+ opacity: 0;
129
+ transform: translateY(100%);
130
+ transition:
131
+ transform 0.5s cubic-bezier(0.25, 1, 0.5, 1),
132
+ opacity 0.5s ease,
133
+ filter 0.5s ease;
134
+ filter: blur(2px);
135
+ }
136
+
137
+ .label.active {
138
+ opacity: 1;
139
+ transform: translateY(0);
140
+ filter: none;
141
+ }
142
+
143
+ .label.exit {
144
+ opacity: 0;
145
+ transform: translateY(-100%);
146
+ filter: blur(2px);
147
+ }
148
+
149
+ .checkbox .progress-ring {
150
+ display: none;
151
+ width: 100%;
152
+ height: 100%;
153
+ transform: rotate(-90deg);
154
+ }
155
+
156
+ .checkbox .progress-ring-bg {
157
+ fill: none;
158
+ stroke: var(--cap-spinner-background-color, #eee);
159
+ stroke-width: var(--cap-spinner-thickness, 3);
160
+ }
161
+
162
+ .checkbox .progress-ring-circle {
163
+ fill: none;
164
+ stroke: var(--cap-spinner-color, #000);
165
+ stroke-width: var(--cap-spinner-thickness, 3);
166
+ stroke-linecap: round;
167
+ stroke-dasharray: 87.96;
168
+ stroke-dashoffset: 87.96;
169
+ transition: stroke-dashoffset 0.3s ease;
170
+ }
171
+
172
+ .captcha[data-state="verifying"] .checkbox {
173
+ background: none;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ transform: scale(1.1);
178
+ border: none;
179
+ border-radius: 50%;
180
+ background-color: transparent;
181
+ }
182
+
183
+ .captcha[data-state="verifying"] .checkbox .progress-ring {
184
+ display: block;
185
+ }
186
+
187
+ .captcha[data-state="done"] .checkbox {
188
+ border: 1px solid transparent;
189
+ background-image: var(
190
+ --cap-checkmark,
191
+ url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E")
192
+ );
193
+ background-size: cover;
194
+ }
195
+
196
+ .captcha[data-state="done"] .checkbox .progress-ring {
197
+ display: none;
198
+ }
199
+
200
+ .captcha[data-state="error"] .checkbox {
201
+ border: 1px solid transparent;
202
+ background-image: var(
203
+ --cap-error-cross,
204
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 24 24'%3E%3Cpath fill='%23f55b50' d='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8'/%3E%3C%2Fsvg%3E")
205
+ );
206
+ background-size: cover;
207
+ }
208
+
209
+ .captcha[data-state="error"] .checkbox .progress-ring {
210
+ display: none;
211
+ }
212
+
213
+ .captcha.invalid {
214
+ animation: cap-shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97);
215
+ border-color: var(--cap-invalid-border-color, #f55b50);
216
+ box-shadow: 0 0 0 3px var(--cap-invalid-ring-color, rgba(245, 91, 80, 0.2));
217
+ }
218
+
219
+ @keyframes cap-shake {
220
+ 10%, 90% { transform: translateX(-1px); }
221
+ 20%, 80% { transform: translateX(2px); }
222
+ 30%, 50%, 70% { transform: translateX(-4px); }
223
+ 40%, 60% { transform: translateX(4px); }
224
+ }
225
+
226
+ @media (prefers-reduced-motion: reduce) {
227
+ .captcha.invalid {
228
+ animation: none !important;
229
+ }
230
+ }
231
+
232
+ .captcha[disabled] {
233
+ cursor: not-allowed;
234
+ }
235
+
236
+ .captcha[disabled][data-state="verifying"] {
237
+ cursor: progress;
238
+ }
239
+
240
+ .captcha[disabled][data-state="done"] {
241
+ cursor: default;
242
+ }
243
+
244
+ .captcha .credits {
245
+ position: absolute;
246
+ bottom: 10px;
247
+ right: 10px;
248
+ font-size: 12px !important;
249
+ color: var(--cap-color, #212121);
250
+ opacity: 0.8 !important;
251
+ text-underline-offset: 0.18em;
252
+ text-decoration-thickness: 0.08em;
253
+ z-index: 6;
254
+ display: block !important;
255
+ }
256
+
257
+ .cap-troubleshoot-link {
258
+ color: #0066cc;
259
+ text-decoration: underline;
260
+ text-underline-offset: 0.15em;
261
+ text-decoration-thickness: 0.08em;
262
+ cursor: pointer;
263
+ font-weight: 500;
264
+ font-size: inherit;
265
+ }
266
+ .cap-troubleshoot-link:hover {
267
+ opacity: 0.8;
268
+ }