@swetrix/captcha 2.0.2 → 2.3.0
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/README.md +72 -5
- package/dist/captcha-loader.cjs +333 -0
- package/dist/captcha-loader.cjs.map +1 -0
- package/dist/captcha-loader.esm.js +327 -0
- package/dist/captcha-loader.esm.js.map +1 -0
- package/dist/captcha-loader.js +1 -1
- package/dist/captcha-loader.js.map +1 -1
- package/dist/captcha.js +1 -1
- package/dist/captcha.js.map +1 -1
- package/dist/esnext/captcha-loader.d.ts +10 -0
- package/dist/esnext/captcha-loader.js +315 -0
- package/dist/esnext/captcha-loader.js.map +1 -0
- package/dist/esnext/captcha.d.ts +10 -0
- package/dist/esnext/captcha.js +491 -0
- package/dist/esnext/captcha.js.map +1 -0
- package/dist/esnext/i18n.d.ts +22 -0
- package/dist/esnext/i18n.js +110 -0
- package/dist/esnext/i18n.js.map +1 -0
- package/dist/esnext/index.d.ts +7 -0
- package/dist/esnext/index.js +7 -0
- package/dist/esnext/index.js.map +1 -0
- package/dist/esnext/logger.d.ts +6 -0
- package/dist/esnext/logger.js +8 -0
- package/dist/esnext/logger.js.map +1 -0
- package/dist/esnext/pow-worker.d.ts +1 -0
- package/dist/esnext/pow-worker.js +127 -0
- package/dist/esnext/pow-worker.js.map +1 -0
- package/dist/pages/dark.html +461 -276
- package/dist/pages/light.html +463 -278
- package/dist/pages/test.html +26 -27
- package/package.json +58 -32
- package/.nvmrc +0 -1
- package/.prettierrc.js +0 -13
- package/dist/assets/logo_blue.png +0 -0
- package/dist/assets/logo_white.png +0 -0
- package/rollup.config.mjs +0 -83
- package/src/assets/logo_blue.png +0 -0
- package/src/assets/logo_white.png +0 -0
- package/src/captcha-loader.ts +0 -212
- package/src/captcha.ts +0 -358
- package/src/pages/dark.html +0 -284
- package/src/pages/light.html +0 -286
- package/src/pages/test.html +0 -33
- package/src/pow-worker.ts +0 -178
- package/tsconfig.esnext.json +0 -15
- package/tsconfig.json +0 -14
package/src/captcha.ts
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
export {}
|
|
2
|
-
|
|
3
|
-
// @ts-ignore
|
|
4
|
-
const isDevelopment = window.__SWETRIX_CAPTCHA_DEV || false
|
|
5
|
-
|
|
6
|
-
const API_URL = isDevelopment ? 'http://localhost:5005/v1/captcha' : 'https://api.swetrix.com/v1/captcha'
|
|
7
|
-
const WORKER_URL = isDevelopment ? './pow-worker.js' : 'https://cap.swetrix.com/pow-worker.js'
|
|
8
|
-
const MSG_IDENTIFIER = 'swetrix-captcha'
|
|
9
|
-
const CAPTCHA_TOKEN_LIFETIME = 300 // seconds (5 minutes).
|
|
10
|
-
|
|
11
|
-
// Main-thread fallback limits (same as worker)
|
|
12
|
-
const MAX_ITERATIONS = 100_000_000 // 100 million attempts
|
|
13
|
-
const TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes
|
|
14
|
-
|
|
15
|
-
const ENDPOINTS = {
|
|
16
|
-
GENERATE: '/generate',
|
|
17
|
-
VERIFY: '/verify',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
enum IFRAME_MESSAGE_TYPES {
|
|
21
|
-
SUCCESS = 'success',
|
|
22
|
-
FAILURE = 'failure',
|
|
23
|
-
TOKEN_EXPIRED = 'tokenExpired',
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
enum ACTION {
|
|
27
|
-
checkbox = 'checkbox',
|
|
28
|
-
failure = 'failure',
|
|
29
|
-
completed = 'completed',
|
|
30
|
-
loading = 'loading',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface PowChallenge {
|
|
34
|
-
challenge: string
|
|
35
|
-
difficulty: number
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface PowResult {
|
|
39
|
-
type: 'result'
|
|
40
|
-
nonce: number
|
|
41
|
-
solution: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface PowProgress {
|
|
45
|
-
type: 'progress'
|
|
46
|
-
attempts: number
|
|
47
|
-
hashRate: number
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let activeAction: ACTION = ACTION.checkbox
|
|
51
|
-
let powWorker: Worker | null = null
|
|
52
|
-
|
|
53
|
-
const sendMessageToLoader = (event: IFRAME_MESSAGE_TYPES, data = {}) => {
|
|
54
|
-
window.parent.postMessage(
|
|
55
|
-
{
|
|
56
|
-
event,
|
|
57
|
-
type: MSG_IDENTIFIER,
|
|
58
|
-
// @ts-ignore
|
|
59
|
-
cid: window.__SWETRIX_CAPTCHA_ID,
|
|
60
|
-
...data,
|
|
61
|
-
},
|
|
62
|
-
'*',
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Sets the provided action visible and the rest hidden with smooth transitions
|
|
68
|
-
* @param {*} action checkbox | failure | completed | loading
|
|
69
|
-
*/
|
|
70
|
-
const activateAction = (action: ACTION) => {
|
|
71
|
-
activeAction = action
|
|
72
|
-
|
|
73
|
-
const statusDefault = document.querySelector('#status-default')
|
|
74
|
-
const statusFailure = document.querySelector('#status-failure')
|
|
75
|
-
const statusComputing = document.querySelector('#status-computing')
|
|
76
|
-
|
|
77
|
-
const actions = {
|
|
78
|
-
checkbox: document.querySelector('#checkbox'),
|
|
79
|
-
failure: document.querySelector('#failure'),
|
|
80
|
-
completed: document.querySelector('#completed'),
|
|
81
|
-
loading: document.querySelector('#loading'),
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Hide all action elements with transitions
|
|
85
|
-
// Checkbox uses fade-out class for smooth transition
|
|
86
|
-
if (action !== ACTION.checkbox) {
|
|
87
|
-
actions.checkbox?.classList.add('fade-out')
|
|
88
|
-
} else {
|
|
89
|
-
actions.checkbox?.classList.remove('fade-out')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Loading, failure, completed use .show class for visibility
|
|
93
|
-
actions.loading?.classList.remove('show')
|
|
94
|
-
actions.failure?.classList.remove('show')
|
|
95
|
-
actions.completed?.classList.remove('show')
|
|
96
|
-
|
|
97
|
-
// Change the status text
|
|
98
|
-
statusDefault?.classList.add('hidden')
|
|
99
|
-
statusFailure?.classList.add('hidden')
|
|
100
|
-
statusComputing?.classList.add('hidden')
|
|
101
|
-
|
|
102
|
-
if (action === 'failure') {
|
|
103
|
-
statusFailure?.classList.remove('hidden')
|
|
104
|
-
} else if (action === 'loading') {
|
|
105
|
-
statusComputing?.classList.remove('hidden')
|
|
106
|
-
} else {
|
|
107
|
-
statusDefault?.classList.remove('hidden')
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Show the active action element with animation
|
|
111
|
-
if (action !== ACTION.checkbox) {
|
|
112
|
-
// Small delay to ensure CSS transitions work properly
|
|
113
|
-
requestAnimationFrame(() => {
|
|
114
|
-
actions[action]?.classList.add('show')
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const setLifetimeTimeout = () => {
|
|
120
|
-
setTimeout(() => {
|
|
121
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.TOKEN_EXPIRED)
|
|
122
|
-
activateAction(ACTION.checkbox)
|
|
123
|
-
}, CAPTCHA_TOKEN_LIFETIME * 1000)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const generateChallenge = async (): Promise<PowChallenge | null> => {
|
|
127
|
-
try {
|
|
128
|
-
const response = await fetch(`${API_URL}${ENDPOINTS.GENERATE}`, {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: {
|
|
131
|
-
'Content-Type': 'application/json',
|
|
132
|
-
},
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
// @ts-ignore
|
|
135
|
-
pid: window.__SWETRIX_PROJECT_ID,
|
|
136
|
-
}),
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
throw new Error('Failed to generate challenge')
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return await response.json()
|
|
144
|
-
} catch (e) {
|
|
145
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
146
|
-
activateAction(ACTION.failure)
|
|
147
|
-
return null
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const verifySolution = async (challenge: string, nonce: number, solution: string): Promise<string | null> => {
|
|
152
|
-
try {
|
|
153
|
-
const response = await fetch(`${API_URL}${ENDPOINTS.VERIFY}`, {
|
|
154
|
-
method: 'POST',
|
|
155
|
-
headers: {
|
|
156
|
-
'Content-Type': 'application/json',
|
|
157
|
-
},
|
|
158
|
-
body: JSON.stringify({
|
|
159
|
-
challenge,
|
|
160
|
-
nonce,
|
|
161
|
-
solution,
|
|
162
|
-
// @ts-ignore
|
|
163
|
-
pid: window.__SWETRIX_PROJECT_ID,
|
|
164
|
-
}),
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
if (!response.ok) {
|
|
168
|
-
throw new Error('Verification failed')
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const data = await response.json()
|
|
172
|
-
|
|
173
|
-
if (!data.success) {
|
|
174
|
-
throw new Error('Verification failed')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return data.token
|
|
178
|
-
} catch (e) {
|
|
179
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
180
|
-
activateAction(ACTION.failure)
|
|
181
|
-
return null
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const solveChallenge = async (challenge: PowChallenge): Promise<void> => {
|
|
186
|
-
return new Promise((resolve, reject) => {
|
|
187
|
-
// Terminate any existing worker
|
|
188
|
-
if (powWorker) {
|
|
189
|
-
powWorker.terminate()
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
powWorker = new Worker(WORKER_URL)
|
|
194
|
-
} catch (e) {
|
|
195
|
-
// Fallback: solve in main thread if worker fails
|
|
196
|
-
solveInMainThread(challenge).then(resolve).catch(reject)
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
powWorker.onmessage = async (
|
|
201
|
-
event: MessageEvent<
|
|
202
|
-
PowResult | PowProgress | { type: 'timeout'; reason: string } | { type: 'error'; message?: string }
|
|
203
|
-
>,
|
|
204
|
-
) => {
|
|
205
|
-
const data = event.data
|
|
206
|
-
|
|
207
|
-
if (data.type === 'progress') {
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (data.type === 'timeout') {
|
|
212
|
-
// Worker timed out or hit max iterations
|
|
213
|
-
console.error('PoW worker timeout:', (data as { type: 'timeout'; reason: string }).reason)
|
|
214
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
215
|
-
activateAction(ACTION.failure)
|
|
216
|
-
powWorker?.terminate()
|
|
217
|
-
powWorker = null
|
|
218
|
-
resolve()
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (data.type === 'result') {
|
|
223
|
-
// Worker found the solution
|
|
224
|
-
const token = await verifySolution(challenge.challenge, (data as PowResult).nonce, (data as PowResult).solution)
|
|
225
|
-
|
|
226
|
-
if (token) {
|
|
227
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.SUCCESS, { token })
|
|
228
|
-
setLifetimeTimeout()
|
|
229
|
-
activateAction(ACTION.completed)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
powWorker?.terminate()
|
|
233
|
-
powWorker = null
|
|
234
|
-
resolve()
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Handle error message from worker
|
|
239
|
-
if (data.type === 'error') {
|
|
240
|
-
const errorData = data as { type: 'error'; message?: string }
|
|
241
|
-
console.error('PoW worker error message:', errorData.message || 'Unknown error')
|
|
242
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
243
|
-
activateAction(ACTION.failure)
|
|
244
|
-
powWorker?.terminate()
|
|
245
|
-
powWorker = null
|
|
246
|
-
resolve()
|
|
247
|
-
return
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Fallback for unexpected message types
|
|
251
|
-
console.warn('PoW worker received unexpected message type:', (data as { type?: unknown }).type, 'Raw data:', data)
|
|
252
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
253
|
-
activateAction(ACTION.failure)
|
|
254
|
-
powWorker?.terminate()
|
|
255
|
-
powWorker = null
|
|
256
|
-
resolve()
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
powWorker.onerror = (error) => {
|
|
260
|
-
console.error('PoW worker error:', error)
|
|
261
|
-
powWorker?.terminate()
|
|
262
|
-
powWorker = null
|
|
263
|
-
|
|
264
|
-
// Fallback to main thread
|
|
265
|
-
solveInMainThread(challenge).then(resolve).catch(reject)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Start the worker
|
|
269
|
-
powWorker.postMessage({
|
|
270
|
-
challenge: challenge.challenge,
|
|
271
|
-
difficulty: challenge.difficulty,
|
|
272
|
-
})
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Fallback solution for environments where workers don't work
|
|
277
|
-
const solveInMainThread = async (challenge: PowChallenge): Promise<void> => {
|
|
278
|
-
const { challenge: challengeStr, difficulty } = challenge
|
|
279
|
-
let nonce = 0
|
|
280
|
-
const startTime = Date.now()
|
|
281
|
-
|
|
282
|
-
const sha256 = async (message: string): Promise<string> => {
|
|
283
|
-
const encoder = new TextEncoder()
|
|
284
|
-
const data = encoder.encode(message)
|
|
285
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
|
286
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
|
287
|
-
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const hasValidPrefix = (hash: string, diff: number): boolean => {
|
|
291
|
-
for (let i = 0; i < diff; i++) {
|
|
292
|
-
if (hash[i] !== '0') return false
|
|
293
|
-
}
|
|
294
|
-
return true
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
while (nonce < MAX_ITERATIONS) {
|
|
298
|
-
// Check overall timeout
|
|
299
|
-
const elapsedMs = Date.now() - startTime
|
|
300
|
-
if (elapsedMs >= TIMEOUT_MS) {
|
|
301
|
-
console.error(`PoW main-thread timeout: ${TIMEOUT_MS}ms elapsed after ${nonce} attempts`)
|
|
302
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
303
|
-
activateAction(ACTION.failure)
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const input = `${challengeStr}:${nonce}`
|
|
308
|
-
const hash = await sha256(input)
|
|
309
|
-
|
|
310
|
-
if (hasValidPrefix(hash, difficulty)) {
|
|
311
|
-
const token = await verifySolution(challengeStr, nonce, hash)
|
|
312
|
-
|
|
313
|
-
if (token) {
|
|
314
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.SUCCESS, { token })
|
|
315
|
-
setLifetimeTimeout()
|
|
316
|
-
activateAction(ACTION.completed)
|
|
317
|
-
}
|
|
318
|
-
return
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
nonce++
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Max iterations reached without finding solution
|
|
325
|
-
console.error(`PoW main-thread max iterations reached: ${MAX_ITERATIONS} attempts`)
|
|
326
|
-
sendMessageToLoader(IFRAME_MESSAGE_TYPES.FAILURE)
|
|
327
|
-
activateAction(ACTION.failure)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
331
|
-
const captchaComponent = document.querySelector('#swetrix-captcha')
|
|
332
|
-
const branding = document.querySelector('#branding')
|
|
333
|
-
|
|
334
|
-
branding?.addEventListener('click', (e: Event) => {
|
|
335
|
-
e.stopPropagation()
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
captchaComponent?.addEventListener('click', async () => {
|
|
339
|
-
if (activeAction === ACTION.loading || activeAction === ACTION.completed) {
|
|
340
|
-
return
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (activeAction === ACTION.failure) {
|
|
344
|
-
activateAction(ACTION.checkbox)
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
activateAction(ACTION.loading)
|
|
349
|
-
|
|
350
|
-
const challenge = await generateChallenge()
|
|
351
|
-
|
|
352
|
-
if (!challenge) {
|
|
353
|
-
return
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
await solveChallenge(challenge)
|
|
357
|
-
})
|
|
358
|
-
})
|
package/src/pages/dark.html
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="utf-8">
|
|
6
|
-
<title>Swetrix Captcha</title>
|
|
7
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
8
|
-
|
|
9
|
-
<style>
|
|
10
|
-
html {
|
|
11
|
-
font-family: '-apple-system', 'system-ui', 'BlinkMacSystemFont', 'Inter', 'Cantarell', 'Helvetica Neue', 'Roboto', 'Oxygen', 'Ubuntu', sans-serif;
|
|
12
|
-
-webkit-font-smoothing: antialiased;
|
|
13
|
-
-moz-osx-font-smoothing: grayscale;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
body {
|
|
17
|
-
margin: 0;
|
|
18
|
-
padding: 0;
|
|
19
|
-
height: auto;
|
|
20
|
-
overflow: auto;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#swetrix-captcha {
|
|
24
|
-
display: flex;
|
|
25
|
-
justify-content: center;
|
|
26
|
-
align-items: center;
|
|
27
|
-
position: relative;
|
|
28
|
-
|
|
29
|
-
/* 300 - 20px (padding) */
|
|
30
|
-
width: 280px;
|
|
31
|
-
|
|
32
|
-
background-color: #0f172a;
|
|
33
|
-
border: 1px solid #1e293b;
|
|
34
|
-
height: 64px;
|
|
35
|
-
-webkit-user-select: none;
|
|
36
|
-
user-select: none;
|
|
37
|
-
padding-left: 10px;
|
|
38
|
-
padding-right: 10px;
|
|
39
|
-
|
|
40
|
-
cursor: pointer;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#swetrix-captcha:hover {
|
|
44
|
-
background-color: #1e293b;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
#challenge {
|
|
48
|
-
display: flex;
|
|
49
|
-
align-items: center;
|
|
50
|
-
cursor: pointer;
|
|
51
|
-
flex: 1;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#action {
|
|
55
|
-
margin-right: 10px;
|
|
56
|
-
position: relative;
|
|
57
|
-
width: 28px;
|
|
58
|
-
height: 28px;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
#checkbox {
|
|
62
|
-
background-color: #111827;
|
|
63
|
-
border: 1px solid #374151;
|
|
64
|
-
height: 25px;
|
|
65
|
-
width: 25px;
|
|
66
|
-
border-radius: 3px;
|
|
67
|
-
position: absolute;
|
|
68
|
-
top: 50%;
|
|
69
|
-
left: 50%;
|
|
70
|
-
transform: translate(-50%, -50%);
|
|
71
|
-
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
#checkbox:hover {
|
|
75
|
-
border-color: #4b5563;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#status {
|
|
79
|
-
font-size: 14px;
|
|
80
|
-
color: #f9fafb;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#status span {
|
|
84
|
-
transition: opacity 0.2s ease;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
#status-computing {
|
|
88
|
-
display: flex;
|
|
89
|
-
flex-direction: column;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.hidden {
|
|
93
|
-
display: none !important;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* Fade out animation for hiding elements */
|
|
97
|
-
.fade-out {
|
|
98
|
-
opacity: 0 !important;
|
|
99
|
-
transform: translate(-50%, -50%) scale(0.8) !important;
|
|
100
|
-
pointer-events: none;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/* Fade in animation for showing elements */
|
|
104
|
-
.fade-in {
|
|
105
|
-
opacity: 1;
|
|
106
|
-
transform: translate(-50%, -50%) scale(1);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
#branding {
|
|
110
|
-
position: absolute;
|
|
111
|
-
bottom: 4px;
|
|
112
|
-
right: 10px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#branding a {
|
|
116
|
-
font-size: 9px;
|
|
117
|
-
color: #6b7280;
|
|
118
|
-
text-decoration: none;
|
|
119
|
-
transition: color 0.2s ease;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#branding a:hover {
|
|
123
|
-
color: #9ca3af;
|
|
124
|
-
text-decoration: underline;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
#action svg {
|
|
128
|
-
width: 28px;
|
|
129
|
-
height: 28px;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
#failure > svg {
|
|
133
|
-
/* bg-red-500 */
|
|
134
|
-
color: #d6292a;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
#completed > svg {
|
|
138
|
-
/* bg-green-600 */
|
|
139
|
-
color: #16a24c;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#completed,
|
|
143
|
-
#failure {
|
|
144
|
-
display: flex;
|
|
145
|
-
align-items: center;
|
|
146
|
-
justify-content: center;
|
|
147
|
-
position: absolute;
|
|
148
|
-
top: 50%;
|
|
149
|
-
left: 50%;
|
|
150
|
-
transform: translate(-50%, -50%) scale(0.8);
|
|
151
|
-
opacity: 0;
|
|
152
|
-
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
#completed.show,
|
|
156
|
-
#failure.show {
|
|
157
|
-
opacity: 1;
|
|
158
|
-
transform: translate(-50%, -50%) scale(1);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/* Checkmark draw animation */
|
|
162
|
-
#completed.show svg path {
|
|
163
|
-
stroke-dasharray: 24;
|
|
164
|
-
stroke-dashoffset: 24;
|
|
165
|
-
animation: drawCheck 0.4s ease forwards 0.1s;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@keyframes drawCheck {
|
|
169
|
-
to {
|
|
170
|
-
stroke-dashoffset: 0;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/* Failure shake animation */
|
|
175
|
-
#failure.show {
|
|
176
|
-
animation: shake 0.4s ease;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@keyframes shake {
|
|
180
|
-
0%, 100% { transform: translate(-50%, -50%) scale(1) rotate(0deg); }
|
|
181
|
-
25% { transform: translate(-50%, -50%) scale(1) rotate(-5deg); }
|
|
182
|
-
75% { transform: translate(-50%, -50%) scale(1) rotate(5deg); }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* Loading indicator - Material Design style spinner */
|
|
186
|
-
#loading {
|
|
187
|
-
position: absolute;
|
|
188
|
-
top: 50%;
|
|
189
|
-
left: 50%;
|
|
190
|
-
transform: translate(-50%, -50%);
|
|
191
|
-
width: 24px;
|
|
192
|
-
height: 24px;
|
|
193
|
-
opacity: 0;
|
|
194
|
-
transition: opacity 0.25s ease;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
#loading.show {
|
|
198
|
-
opacity: 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
#loading svg {
|
|
202
|
-
width: 24px;
|
|
203
|
-
height: 24px;
|
|
204
|
-
animation: rotate 1.4s linear infinite;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
#loading svg circle {
|
|
208
|
-
stroke: #60a5fa;
|
|
209
|
-
stroke-linecap: round;
|
|
210
|
-
animation: dash 1.4s ease-in-out infinite;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
@keyframes rotate {
|
|
214
|
-
100% {
|
|
215
|
-
transform: rotate(360deg);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
@keyframes dash {
|
|
220
|
-
0% {
|
|
221
|
-
stroke-dasharray: 1, 150;
|
|
222
|
-
stroke-dashoffset: 0;
|
|
223
|
-
}
|
|
224
|
-
50% {
|
|
225
|
-
stroke-dasharray: 90, 150;
|
|
226
|
-
stroke-dashoffset: -35;
|
|
227
|
-
}
|
|
228
|
-
100% {
|
|
229
|
-
stroke-dasharray: 90, 150;
|
|
230
|
-
stroke-dashoffset: -124;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
</style>
|
|
234
|
-
<script>
|
|
235
|
-
window.__SWETRIX_CAPTCHA_THEME = 'dark'
|
|
236
|
-
const urlParams = new URLSearchParams(window.location.search)
|
|
237
|
-
const pid = urlParams.get('pid')
|
|
238
|
-
const cid = urlParams.get('cid')
|
|
239
|
-
|
|
240
|
-
window.__SWETRIX_CAPTCHA_ID = cid
|
|
241
|
-
window.__SWETRIX_PROJECT_ID = pid
|
|
242
|
-
</script>
|
|
243
|
-
<script src="../captcha.js" defer></script>
|
|
244
|
-
</head>
|
|
245
|
-
|
|
246
|
-
<body>
|
|
247
|
-
<div id="swetrix-captcha">
|
|
248
|
-
<div id="challenge">
|
|
249
|
-
<div id="action">
|
|
250
|
-
<!-- Can contain a checkbox itself / a red cross (with a retry action) / a green check mark -->
|
|
251
|
-
<div id="checkbox"></div>
|
|
252
|
-
<div id="failure">
|
|
253
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
254
|
-
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/>
|
|
255
|
-
<path d="M12 9v4"/>
|
|
256
|
-
<path d="M12 17h.01"/>
|
|
257
|
-
</svg>
|
|
258
|
-
</div>
|
|
259
|
-
<div id="completed">
|
|
260
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
261
|
-
<path d="M20 6 9 17l-5-5"/>
|
|
262
|
-
</svg>
|
|
263
|
-
</div>
|
|
264
|
-
<div id="loading">
|
|
265
|
-
<svg viewBox="0 0 50 50">
|
|
266
|
-
<circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
|
|
267
|
-
</svg>
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
<div id="status">
|
|
271
|
-
<span id="status-default">I am human</span>
|
|
272
|
-
<span id="status-failure" class="hidden">Failure, please retry</span>
|
|
273
|
-
<span id="status-computing" class="hidden">
|
|
274
|
-
<span>Verifying...</span>
|
|
275
|
-
</span>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
<div id="branding">
|
|
279
|
-
<a href="https://swetrix.com/captcha" target="_blank" rel="noopener noreferrer">Swetrix Captcha</a>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
</body>
|
|
283
|
-
|
|
284
|
-
</html>
|