sataruz-captcha 0.1.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/LICENSE +21 -0
- package/README.md +58 -0
- package/assets/sataruz/cao_yanbing.png +0 -0
- package/assets/sataruz/frok.png +0 -0
- package/assets/sataruz/irelia.png +0 -0
- package/assets/sataruz/jormungandr.png +0 -0
- package/assets/sataruz/netherworld.png +0 -0
- package/assets/sataruz/nine.png +0 -0
- package/assets/sataruz/qingluan.png +0 -0
- package/assets/sataruz/ratu_salju.png +0 -0
- package/assets/sataruz/samael.png +0 -0
- package/assets/sataruz/titan.png +0 -0
- package/assets/sataruz/uriel.png +0 -0
- package/assets/sataruz/zhuqing.png +0 -0
- package/assets/sataruz-captcha.svg +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +1043 -0
- package/dist/sataruz-captcha.css +1131 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1043 @@
|
|
|
1
|
+
import { useState, useMemo, useRef, useEffect } from 'react';
|
|
2
|
+
import { useReducedMotion, MotionConfig, motion, AnimatePresence } from 'motion/react';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/SataruzCaptcha.tsx
|
|
6
|
+
|
|
7
|
+
// src/toys.ts
|
|
8
|
+
var TOY_META = {
|
|
9
|
+
cao_yanbing: { label: "Cao Yanbing", accent: "#C4A055" },
|
|
10
|
+
irelia: { label: "Irelia", accent: "#7B68EE" },
|
|
11
|
+
titan: { label: "Titan", accent: "#A9A9A9" },
|
|
12
|
+
frok: { label: "Frok", accent: "#32CD32" },
|
|
13
|
+
uriel: { label: "Uriel", accent: "#FF6347" },
|
|
14
|
+
jormungandr: { label: "Jormungandr", accent: "#228B22" },
|
|
15
|
+
netherworld: { label: "Netherworld", accent: "#2F4F4F" },
|
|
16
|
+
zhuqing: { label: "Zhuqing", accent: "#FFD700" },
|
|
17
|
+
nine: { label: "9", accent: "#4169E1" },
|
|
18
|
+
samael: { label: "Samael", accent: "#DC143C" },
|
|
19
|
+
ratu_salju: { label: "Ratu Salju", accent: "#B0E0E6" },
|
|
20
|
+
qingluan: { label: "Qingluan", accent: "#FF69B4" }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/clawArt.ts
|
|
24
|
+
var CLAW_PIVOT = { x: 20.6, y: 22.7 };
|
|
25
|
+
var CLAW_BODY = [{ "fill": "#666D85", "d": "M20.031,24.324c0.086,2.939,0.263,5.874,0.531,8.802c0.016,0.175,0.073,0.395,0.247,0.42\r\n c0.218,0.03,0.311-0.267,0.318-0.487c0.104-2.988,0.207-5.975,0.311-8.963C21.048,24.142,20.657,24.188,20.031,24.324z" }, { "fill": "#3D4459", "d": "M20.222,11.816c0.023,2.291,0.046,4.583,0.069,6.874c0.356,0.036,0.717,0.036,1.073,0\r\n c-0.01-2.339-0.019-4.679-0.028-7.018C20.987,11.656,20.636,11.679,20.222,11.816z" }, { "fill": "#666D85", "d": "M18.924,10.588c0.013,0.395,0.025,0.789,0.038,1.184c0.003,0.101,0.009,0.209,0.073,0.288\r\n c0.084,0.103,0.234,0.116,0.367,0.12c1.017,0.034,2.044,0.068,3.047-0.106c0.054-0.009,0.111-0.021,0.15-0.059\r\n c0.047-0.047,0.054-0.12,0.058-0.187c0.022-0.45,0.009-0.901-0.039-1.349c-0.004-0.04-0.011-0.083-0.04-0.11\r\n c-0.031-0.03-0.079-0.031-0.122-0.031C21.186,10.343,19.916,10.39,18.924,10.588z" }, { "fill": "#DE2121", "d": "M18.282,9.052c-0.062,0-0.13,0.002-0.179,0.04c-0.059,0.045-0.074,0.126-0.084,0.199\r\n c-0.055,0.411-0.06,0.83-0.015,1.242c0.008,0.074,0.02,0.153,0.071,0.208c0.066,0.07,0.172,0.077,0.268,0.079\r\n c1.573,0.031,3.146,0.006,4.717-0.076c0.064-0.003,0.132-0.008,0.185-0.045c0.096-0.066,0.102-0.204,0.098-0.321\r\n c-0.015-0.438-0.031-0.875-0.046-1.313c-0.001-0.039-0.004-0.081-0.031-0.109c-0.029-0.03-0.076-0.032-0.119-0.032\r\n C22.905,8.925,19.701,9.046,18.282,9.052z" }, { "fill": "#666D85", "d": "M19.815,20.079c-0.001-0.526-0.003-1.052-0.004-1.577c0-0.036,0-0.074,0.017-0.105\r\n c0.033-0.061,0.112-0.077,0.18-0.085c0.483-0.058,0.97-0.086,1.456-0.083c0.099,0.001,0.21,0.008,0.274,0.083\r\n c0.053,0.062,0.055,0.151,0.055,0.233c0,0.475-0.001,0.95-0.001,1.425C21.155,19.978,20.517,19.972,19.815,20.079z" }, { "fill": "#B4B9C4", "d": "M19.458,20.771c-0.269,0.791-0.539,1.583-0.808,2.374c-0.016,0.048-0.033,0.098-0.022,0.147\r\n c0.01,0.047,0.045,0.085,0.077,0.121l0.799,0.87c0.083,0.091,0.169,0.184,0.28,0.238c0.134,0.066,0.289,0.068,0.438,0.069\r\n c0.471,0.003,0.942,0.006,1.413,0.01c0.048,0,0.098,0,0.141-0.021c0.052-0.026,0.085-0.078,0.115-0.127\r\n c0.244-0.399,0.487-0.797,0.731-1.196c0.047-0.078,0.096-0.159,0.106-0.249c0.011-0.101-0.027-0.199-0.065-0.293\r\n c-0.231-0.58-0.463-1.159-0.695-1.739c-0.029-0.072-0.061-0.149-0.124-0.194c-0.074-0.053-0.172-0.051-0.262-0.047\r\n C20.836,20.765,20.09,20.808,19.458,20.771z" }, { "fill": "#3D4459", "d": "M21.101,22.267c-0.117-0.169-0.332-0.237-0.531-0.186c-0.182,0.047-0.359,0.12-0.459,0.274\r\n c-0.076,0.116-0.095,0.259-0.101,0.397c-0.004,0.095-0.002,0.192,0.034,0.28c0.061,0.149,0.214,0.245,0.372,0.274\r\n c0.254,0.047,0.53-0.066,0.678-0.278c0.147-0.212,0.158-0.51,0.025-0.732C21.114,22.286,21.107,22.276,21.101,22.267z" }, { "fill": "#DE2121", "d": "M22.585,19.777c-1.368-0.115-2.751-0.055-4.105,0.179c-0.127,0.022-0.272,0.059-0.327,0.176\r\n c-0.028,0.06-0.025,0.129-0.022,0.195c0.009,0.173,0.017,0.345,0.026,0.518c0.003,0.058,0.007,0.118,0.037,0.167\r\n c0.053,0.084,0.164,0.106,0.262,0.118c1.395,0.173,2.808,0.083,4.21-0.007c0.351-0.023,0.703-0.045,1.047-0.121\r\n c0.053-0.012,0.109-0.026,0.145-0.066c0.044-0.048,0.049-0.119,0.05-0.184c0.003-0.209-0.01-0.418-0.039-0.625\r\n c-0.01-0.07-0.025-0.145-0.076-0.193c-0.048-0.045-0.118-0.055-0.183-0.064C23.273,19.826,22.935,19.806,22.585,19.777z" }, { "fill": "#851313", "d": "M20.275,21.326c-0.53,0-1.06-0.021-1.589-0.064c-0.113-0.009-0.267-0.021-0.391-0.104\r\n c-0.281-0.187-0.251-0.584-0.204-0.866c0.007-0.041,0.046-0.068,0.086-0.061c0.041,0.007,0.068,0.045,0.062,0.086\r\n c-0.064,0.383-0.02,0.611,0.139,0.716c0.085,0.056,0.195,0.069,0.32,0.079c1.626,0.13,3.266,0.058,4.875-0.214\r\n c0.041-0.007,0.079,0.021,0.086,0.061c0.007,0.041-0.021,0.08-0.061,0.087C22.501,21.233,21.388,21.326,20.275,21.326z" }, { "fill": "#1F2229", "d": "M20.376,17.8c-0.04,0-0.074-0.032-0.075-0.073l-0.152-5.054c-0.001-0.041,0.031-0.076,0.073-0.077\r\n c0.042,0,0.076,0.031,0.077,0.073l0.152,5.054c0.001,0.042-0.031,0.076-0.073,0.077C20.377,17.8,20.376,17.8,20.376,17.8z" }, { "fill": "#851313", "d": "M17.993,10.472c-0.041,0-0.075-0.033-0.075-0.075l-0.001-0.949c0-0.109,0-0.258,0.087-0.372\r\n c0.126-0.164,0.364-0.165,0.492-0.166l4.541-0.022c0.088-0.001,0.179,0.004,0.25,0.068c0.089,0.081,0.087,0.211,0.086,0.282\r\n l-0.018,1.146c-0.001,0.042-0.033,0.074-0.076,0.074c-0.042-0.001-0.075-0.035-0.074-0.076l0.018-1.146\r\n c0.001-0.064-0.001-0.136-0.037-0.168c-0.031-0.028-0.087-0.03-0.148-0.03l-4.541,0.022c-0.141,0.001-0.298,0.01-0.374,0.108\r\n c-0.052,0.068-0.056,0.167-0.056,0.28l0.001,0.949C18.068,10.438,18.035,10.472,17.993,10.472\r\n C17.993,10.472,17.993,10.472,17.993,10.472z" }];
|
|
26
|
+
var CLAW_ARM_L = [{ "fill": "#808596", "d": "M18.965,20.803c-1.243,0.927-2.486,1.854-3.729,2.781c-0.176,0.131-0.363,0.279-0.417,0.491\r\n c-0.049,0.192,0.023,0.391,0.089,0.577c0.681,1.927,0.824,3.999,1.323,5.981c0.055,0.217,0.118,0.441,0.261,0.613\r\n c0.142,0.17,0.348,0.272,0.542,0.38c0.641,0.359,1.208,0.848,1.658,1.429c0.141,0.182,0.283,0.383,0.499,0.463\r\n c0.216,0.08,0.52-0.043,0.533-0.249c-0.459-0.883-1.167-1.635-2.02-2.147c-0.152-0.091-0.313-0.179-0.413-0.325\r\n c-0.081-0.119-0.113-0.263-0.142-0.404c-0.348-1.641-0.697-3.281-1.045-4.922c-0.102-0.478,0.135-0.964,0.574-1.178\r\n c0.616-0.3,1.225-0.615,1.827-0.943c0.325-0.177,0.656-0.367,0.882-0.66c0.274-0.355,0.358-0.817,0.433-1.259\r\n c0.029-0.17,0.057-0.35-0.007-0.51c-0.096-0.238-0.378-0.358-0.635-0.345C18.921,20.591,18.683,20.709,18.965,20.803z" }, { "fill": "#3D4459", "d": "M19.335,32.718c-0.024,0-0.047-0.011-0.062-0.032c-0.347-0.498-0.802-0.934-1.314-1.26\r\n c-0.033-0.021-0.067-0.042-0.101-0.063c-0.213-0.132-0.432-0.268-0.574-0.485c-0.11-0.169-0.158-0.367-0.2-0.543\r\n c-0.391-1.627-0.752-3.283-1.073-4.922c-0.032-0.163-0.076-0.387,0.009-0.583c0.093-0.216,0.315-0.338,0.477-0.427l1.805-0.99\r\n c0.036-0.02,0.082-0.007,0.102,0.03c0.02,0.036,0.007,0.082-0.03,0.102l-1.805,0.99c-0.185,0.102-0.344,0.199-0.412,0.355\r\n c-0.061,0.141-0.036,0.307,0.001,0.494c0.321,1.638,0.681,3.292,1.072,4.916c0.039,0.163,0.084,0.349,0.18,0.496\r\n c0.123,0.188,0.328,0.316,0.527,0.439c0.034,0.021,0.068,0.042,0.102,0.064c0.529,0.336,0.998,0.786,1.357,1.3\r\n c0.024,0.034,0.015,0.081-0.019,0.104C19.365,32.714,19.35,32.718,19.335,32.718z" }];
|
|
27
|
+
var CLAW_ARM_R = [{ "fill": "#808596", "d": "M22.64,20.803c1.243,0.927,2.486,1.854,3.729,2.781c0.176,0.131,0.363,0.279,0.417,0.491\r\n c0.049,0.192-0.023,0.391-0.089,0.577c-0.681,1.927-0.824,3.999-1.323,5.981c-0.055,0.217-0.118,0.441-0.261,0.613\r\n c-0.142,0.17-0.348,0.272-0.542,0.38c-0.641,0.359-1.208,0.848-1.658,1.429c-0.141,0.182-0.284,0.383-0.499,0.463\r\n c-0.216,0.08-0.52-0.043-0.533-0.249c0.459-0.883,1.167-1.635,2.02-2.147c0.152-0.091,0.313-0.179,0.413-0.325\r\n c0.081-0.119,0.113-0.263,0.142-0.404c0.348-1.641,0.697-3.281,1.045-4.922c0.101-0.478-0.135-0.964-0.574-1.178\r\n c-0.616-0.3-1.225-0.615-1.827-0.943c-0.324-0.177-0.656-0.367-0.882-0.66c-0.274-0.355-0.358-0.817-0.433-1.259\r\n c-0.029-0.17-0.057-0.35,0.007-0.51c0.096-0.238,0.378-0.358,0.635-0.345C22.684,20.591,22.922,20.709,22.64,20.803z" }, { "fill": "#3D4459", "d": "M24.41,30.372c-0.007,0-0.014-0.001-0.021-0.003c-0.04-0.012-0.063-0.053-0.051-0.093\r\n c0.464-1.586,0.83-3.218,1.089-4.85c0.007-0.041,0.045-0.069,0.086-0.062c0.041,0.007,0.069,0.045,0.062,0.086\r\n c-0.26,1.639-0.627,3.277-1.093,4.868C24.472,30.351,24.442,30.372,24.41,30.372z" }, { "fill": "#3D4459", "d": "M22.613,33.409c-0.017,0-0.034-0.006-0.048-0.017c-0.032-0.027-0.036-0.074-0.009-0.106\r\n c0.565-0.675,1.234-1.247,1.99-1.698l0.055-0.033c0.18-0.107,0.365-0.217,0.488-0.377c0.142-0.186,0.197-0.436,0.244-0.656\r\n l1.14-5.256c0.009-0.04,0.049-0.066,0.089-0.057c0.041,0.009,0.066,0.049,0.057,0.089l-1.14,5.256\r\n c-0.051,0.236-0.109,0.503-0.272,0.715c-0.141,0.184-0.339,0.301-0.531,0.415l-0.055,0.032c-0.741,0.443-1.398,1.003-1.952,1.666\r\n C22.656,33.399,22.635,33.409,22.613,33.409z" }];
|
|
28
|
+
var GW = 380;
|
|
29
|
+
var GH = 320;
|
|
30
|
+
var RAIL_Y = 14;
|
|
31
|
+
var HOME_Y = 64;
|
|
32
|
+
var DROP_Y = 198;
|
|
33
|
+
var CLAW_MIN = 46;
|
|
34
|
+
var CLAW_MAX = 334;
|
|
35
|
+
var COIL_LEN = 50;
|
|
36
|
+
var GRAB_RADIUS = 38;
|
|
37
|
+
var GRIP_OFFSET = 46;
|
|
38
|
+
var TRAY = { cx: 232, cy: GH + 56, min: 150, max: 320 };
|
|
39
|
+
var TOY_SET = [
|
|
40
|
+
{ toy: "cao_yanbing", w: 90 },
|
|
41
|
+
{ toy: "irelia", w: 88 },
|
|
42
|
+
{ toy: "titan", w: 94 },
|
|
43
|
+
{ toy: "frok", w: 82 },
|
|
44
|
+
{ toy: "uriel", w: 92 },
|
|
45
|
+
{ toy: "jormungandr", w: 98 },
|
|
46
|
+
{ toy: "netherworld", w: 85 },
|
|
47
|
+
{ toy: "zhuqing", w: 86 },
|
|
48
|
+
{ toy: "nine", w: 76 },
|
|
49
|
+
{ toy: "samael", w: 89 },
|
|
50
|
+
{ toy: "ratu_salju", w: 84 },
|
|
51
|
+
{ toy: "qingluan", w: 80 }
|
|
52
|
+
];
|
|
53
|
+
var rand = (a, b) => a + Math.random() * (b - a);
|
|
54
|
+
var CONFETTI = [
|
|
55
|
+
{ dx: -44, dy: -54, dr: -150, c: "#34c759", d: 0 },
|
|
56
|
+
{ dx: -30, dy: -66, dr: 120, c: "#ffd60a", d: 0.05 },
|
|
57
|
+
{ dx: -14, dy: -76, dr: -80, c: "#5cd679", d: 0.02 },
|
|
58
|
+
{ dx: 2, dy: -80, dr: 60, c: "#5a93c9", d: 0.07 },
|
|
59
|
+
{ dx: 16, dy: -74, dr: -130, c: "#ffb340", d: 0.03 },
|
|
60
|
+
{ dx: 30, dy: -64, dr: 100, c: "#a8e6b8", d: 0.06 },
|
|
61
|
+
{ dx: 44, dy: -52, dr: -110, c: "#34c759", d: 0.01 },
|
|
62
|
+
{ dx: -54, dy: -36, dr: 90, c: "#e58ab0", d: 0.09 },
|
|
63
|
+
{ dx: 54, dy: -34, dr: -70, c: "#5a93c9", d: 0.08 }
|
|
64
|
+
];
|
|
65
|
+
var easeInQuad = (p) => p * p;
|
|
66
|
+
var easeOutCubic = (p) => 1 - Math.pow(1 - p, 3);
|
|
67
|
+
var easeInOutCubic = (p) => p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2;
|
|
68
|
+
var clamp01 = (p) => Math.min(1, Math.max(0, p));
|
|
69
|
+
var T = {
|
|
70
|
+
antic: 0.16,
|
|
71
|
+
// tiny upward anticipation before the dive
|
|
72
|
+
down: 0.78,
|
|
73
|
+
// cable pays out, accelerating
|
|
74
|
+
dwell1: 0.18,
|
|
75
|
+
// momentum carries the claw a touch past the stop, then settles
|
|
76
|
+
close: 0.45,
|
|
77
|
+
// fingers close, decelerating
|
|
78
|
+
dwell2: 0.26,
|
|
79
|
+
// grip settles before the lift
|
|
80
|
+
load: 0.24,
|
|
81
|
+
// the cable takes the toy's weight: a visible strain dip
|
|
82
|
+
up: 0.95,
|
|
83
|
+
// slow ease-in-out retract
|
|
84
|
+
open: 0.4
|
|
85
|
+
// fingers release over the tray
|
|
86
|
+
};
|
|
87
|
+
var ANTIC_RISE = 8;
|
|
88
|
+
var DROP_G = 1150;
|
|
89
|
+
var ENTRANCE_G = 1500;
|
|
90
|
+
var shuffle = (arr) => {
|
|
91
|
+
const a = [...arr];
|
|
92
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
93
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
94
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
95
|
+
}
|
|
96
|
+
return a;
|
|
97
|
+
};
|
|
98
|
+
function scatterPile(target) {
|
|
99
|
+
const order = shuffle(TOY_SET);
|
|
100
|
+
const rest = order.filter((t) => t.toy !== target);
|
|
101
|
+
const tgt = order.find((t) => t.toy === target);
|
|
102
|
+
const nB = 8;
|
|
103
|
+
const nTop = order.length - nB;
|
|
104
|
+
const bottomIdx = 2 + Math.floor(Math.random() * 4);
|
|
105
|
+
const slots = new Array(order.length);
|
|
106
|
+
let r = 0;
|
|
107
|
+
const frontW = [];
|
|
108
|
+
const frontToy = [];
|
|
109
|
+
for (let i = 0; i < nB; i++) {
|
|
110
|
+
const isTarget = i === bottomIdx;
|
|
111
|
+
const t = isTarget ? tgt : rest[r++];
|
|
112
|
+
frontToy.push(t);
|
|
113
|
+
frontW.push(t.w * (isTarget ? rand(1, 1.05) : rand(0.76, 0.86)));
|
|
114
|
+
}
|
|
115
|
+
const xs = [0];
|
|
116
|
+
for (let i = 1; i < nB; i++) {
|
|
117
|
+
xs.push(xs[i - 1] + (frontW[i - 1] + frontW[i]) / 2 * rand(0.62, 0.68));
|
|
118
|
+
}
|
|
119
|
+
const span = xs[nB - 1];
|
|
120
|
+
const fit = Math.min(1, (GW - 80) / span);
|
|
121
|
+
for (let i = 0; i < nB; i++) frontW[i] *= fit;
|
|
122
|
+
const offset = (GW - span * fit) / 2;
|
|
123
|
+
const centers = [];
|
|
124
|
+
for (let i = 0; i < nB; i++) {
|
|
125
|
+
const cx = offset + xs[i] * fit + rand(-3, 3);
|
|
126
|
+
centers.push(cx);
|
|
127
|
+
const isTarget = i === bottomIdx;
|
|
128
|
+
slots[i] = {
|
|
129
|
+
toy: frontToy[i].toy,
|
|
130
|
+
w: frontW[i],
|
|
131
|
+
x: Math.min(GW - 26, Math.max(26, cx)),
|
|
132
|
+
b: rand(0, 2),
|
|
133
|
+
// planted on the floor, not hovering over it
|
|
134
|
+
z: isTarget ? 4 : 2,
|
|
135
|
+
// target drawn in front of its neighbours
|
|
136
|
+
rot: rand(-7, 7),
|
|
137
|
+
dropFrom: -rand(340, 440),
|
|
138
|
+
delay: i * 0.05 + rand(0, 0.1)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const gaps = [];
|
|
142
|
+
for (let g = 0; g < nB - 1; g++) {
|
|
143
|
+
if (g === bottomIdx - 1 || g === bottomIdx) continue;
|
|
144
|
+
gaps.push(g);
|
|
145
|
+
}
|
|
146
|
+
const useGaps = shuffle(gaps).slice(0, nTop);
|
|
147
|
+
let ti = 0;
|
|
148
|
+
for (const g of useGaps) {
|
|
149
|
+
const t = rest[r++];
|
|
150
|
+
const cx = (centers[g] + centers[g + 1]) / 2 + rand(-3, 3);
|
|
151
|
+
slots[nB + ti] = {
|
|
152
|
+
toy: t.toy,
|
|
153
|
+
w: t.w * rand(0.7, 0.8),
|
|
154
|
+
x: Math.min(GW - 26, Math.max(26, cx)),
|
|
155
|
+
b: rand(6, 16),
|
|
156
|
+
// low: peeking over shoulders, base out of sight
|
|
157
|
+
z: 1,
|
|
158
|
+
// BEHIND the floor row
|
|
159
|
+
rot: rand(-8, 8),
|
|
160
|
+
dropFrom: -rand(360, 470),
|
|
161
|
+
delay: 0.45 + ti * 0.08 + rand(0, 0.1)
|
|
162
|
+
// settle in after the floor row
|
|
163
|
+
};
|
|
164
|
+
ti++;
|
|
165
|
+
}
|
|
166
|
+
const tip = slots[nB + Math.floor(Math.random() * nTop)];
|
|
167
|
+
tip.rot = rand(10, 16) * (Math.random() < 0.5 ? -1 : 1);
|
|
168
|
+
return slots;
|
|
169
|
+
}
|
|
170
|
+
function SataruzCaptcha({
|
|
171
|
+
target: targetProp,
|
|
172
|
+
onVerify,
|
|
173
|
+
title = "Verify you're human",
|
|
174
|
+
assetBase = "/sataruz/",
|
|
175
|
+
className
|
|
176
|
+
}) {
|
|
177
|
+
const reduce = useReducedMotion();
|
|
178
|
+
const [autoTarget] = useState(() => TOY_SET[Math.floor(Math.random() * TOY_SET.length)].toy);
|
|
179
|
+
const target = targetProp ?? autoTarget;
|
|
180
|
+
const [phase, setPhase] = useState("idle");
|
|
181
|
+
const [infoOpen, setInfoOpen] = useState(false);
|
|
182
|
+
const [verified, setVerified] = useState(false);
|
|
183
|
+
const [message, setMessage] = useState(null);
|
|
184
|
+
const [overTray, setOverTray] = useState(false);
|
|
185
|
+
const [trayMode, setTrayMode] = useState("");
|
|
186
|
+
const pile = useMemo(() => scatterPile(target), [target]);
|
|
187
|
+
const rigEl = useRef(null);
|
|
188
|
+
const clawEl = useRef(null);
|
|
189
|
+
const coilEl = useRef(null);
|
|
190
|
+
const fingerL = useRef(null);
|
|
191
|
+
const fingerR = useRef(null);
|
|
192
|
+
const carriedEl = useRef(null);
|
|
193
|
+
const stickEl = useRef(null);
|
|
194
|
+
const trolleyEl = useRef(null);
|
|
195
|
+
const shadowEl = useRef(null);
|
|
196
|
+
const machineEl = useRef(null);
|
|
197
|
+
const trayEl = useRef(null);
|
|
198
|
+
const pileEls = useRef([]);
|
|
199
|
+
const dir = useRef(0);
|
|
200
|
+
const phaseRef = useRef("idle");
|
|
201
|
+
const onVerifyRef = useRef(onVerify);
|
|
202
|
+
onVerifyRef.current = onVerify;
|
|
203
|
+
const sim = useRef({
|
|
204
|
+
x: GW / 2,
|
|
205
|
+
y: HOME_Y,
|
|
206
|
+
vx: 0,
|
|
207
|
+
drive: 0,
|
|
208
|
+
// smoothed steering input (eases abrupt key/stick changes)
|
|
209
|
+
sway: 0,
|
|
210
|
+
swayV: 0,
|
|
211
|
+
breeze: 0,
|
|
212
|
+
// sub-degree ambient sway so the rig never freezes solid
|
|
213
|
+
close: 0,
|
|
214
|
+
carried: -1,
|
|
215
|
+
carry: { x: 0, y: 0 },
|
|
216
|
+
// scripted-sequence bookkeeping
|
|
217
|
+
stage: "",
|
|
218
|
+
st: 0,
|
|
219
|
+
// seconds inside the current stage
|
|
220
|
+
depthY: DROP_Y,
|
|
221
|
+
// how far the cable pays out THIS grab (reaches the toy's head)
|
|
222
|
+
fallV: 0,
|
|
223
|
+
stretch: 0,
|
|
224
|
+
// 0..~0.09 vertical stretch while falling fast (squash-and-stretch)
|
|
225
|
+
xrot: 0,
|
|
226
|
+
// extra rotation on the carried toy (pickup tilt / dangle lag / impact)
|
|
227
|
+
swallow: 0,
|
|
228
|
+
// 0..1 shrink+fade as a WRONG toy is dismissed off the lid
|
|
229
|
+
released: false,
|
|
230
|
+
// the claw has let go this drop (one-shot)
|
|
231
|
+
mouthY: 353
|
|
232
|
+
// the hatch rim line in machine space — measured at release time
|
|
233
|
+
});
|
|
234
|
+
const softRef = useRef(null);
|
|
235
|
+
if (softRef.current === null) {
|
|
236
|
+
softRef.current = pile.map((s) => ({
|
|
237
|
+
dx: 0,
|
|
238
|
+
dy: 0,
|
|
239
|
+
rot: 0,
|
|
240
|
+
sq: 0,
|
|
241
|
+
vdx: 0,
|
|
242
|
+
vdy: 0,
|
|
243
|
+
vrot: 0,
|
|
244
|
+
vsq: 0,
|
|
245
|
+
ey: s.dropFrom,
|
|
246
|
+
evy: 0,
|
|
247
|
+
delay: s.delay,
|
|
248
|
+
landed: false
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
const setPhaseBoth = (p) => {
|
|
252
|
+
phaseRef.current = p;
|
|
253
|
+
setPhase(p);
|
|
254
|
+
};
|
|
255
|
+
const api = useRef({ setPhaseBoth, setMessage, setVerified, setOverTray, setTrayMode });
|
|
256
|
+
api.current = { setPhaseBoth, setMessage, setVerified, setOverTray, setTrayMode };
|
|
257
|
+
const targetIdx = useMemo(() => pile.findIndex((p) => p.toy === target), [pile, target]);
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
const s = sim.current;
|
|
260
|
+
const soft = softRef.current;
|
|
261
|
+
let raf = 0;
|
|
262
|
+
let wasOverTray = false;
|
|
263
|
+
let prevNow = 0;
|
|
264
|
+
const speedMul = reduce ? 2.4 : 1;
|
|
265
|
+
if (reduce) {
|
|
266
|
+
soft.forEach((b) => {
|
|
267
|
+
b.ey = 0;
|
|
268
|
+
b.landed = true;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const toyCenter = (i) => {
|
|
272
|
+
const p = pile[i];
|
|
273
|
+
return { x: p.x, y: GH - p.b - p.w / 2 * 0.92 };
|
|
274
|
+
};
|
|
275
|
+
const ripple = (x, power, except = -1) => {
|
|
276
|
+
pile.forEach((p, i) => {
|
|
277
|
+
if (i === except || i === s.carried) return;
|
|
278
|
+
const d = Math.abs(p.x - x);
|
|
279
|
+
if (d < 80) {
|
|
280
|
+
const f = (1 - d / 80) * power;
|
|
281
|
+
const side = p.x < x ? -1 : 1;
|
|
282
|
+
const b = soft[i];
|
|
283
|
+
b.vdx += side * f * 1.6;
|
|
284
|
+
b.vdy -= f * 1.1;
|
|
285
|
+
b.vrot += side * f * 2;
|
|
286
|
+
b.vsq += f * 0.02;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
const pend = () => {
|
|
291
|
+
const len = Math.max(2, s.y - RAIL_Y);
|
|
292
|
+
const rad = reduce ? 0 : (s.sway + s.breeze) * Math.PI / 180;
|
|
293
|
+
return { ex: s.x + Math.sin(rad) * len, ey: RAIL_Y + Math.cos(rad) * len };
|
|
294
|
+
};
|
|
295
|
+
const gripY = (ey) => ey + GRIP_OFFSET + (s.carried >= 0 ? pile[s.carried].w : 80) / 2;
|
|
296
|
+
const candidateAt = (x) => {
|
|
297
|
+
let best = -1;
|
|
298
|
+
let bestScore = -Infinity;
|
|
299
|
+
pile.forEach((q, i) => {
|
|
300
|
+
const d = Math.abs(q.x - x);
|
|
301
|
+
if (d < GRAB_RADIUS) {
|
|
302
|
+
const score = q.z * 100 - d;
|
|
303
|
+
if (score > bestScore) {
|
|
304
|
+
bestScore = score;
|
|
305
|
+
best = i;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return best;
|
|
310
|
+
};
|
|
311
|
+
const render = () => {
|
|
312
|
+
const sway = reduce ? 0 : s.sway + s.breeze;
|
|
313
|
+
const len = Math.max(2, s.y - RAIL_Y);
|
|
314
|
+
if (trolleyEl.current) trolleyEl.current.style.transform = `translateX(${(s.x - 14).toFixed(2)}px)`;
|
|
315
|
+
if (shadowEl.current) {
|
|
316
|
+
const rad = sway * Math.PI / 180;
|
|
317
|
+
const ex = s.x + Math.sin(rad) * len;
|
|
318
|
+
const bottomY = s.carried >= 0 ? s.carry.y + pile[s.carried].w / 2 : s.y + 58;
|
|
319
|
+
const t2 = clamp01(1 - (GH - bottomY) / 210);
|
|
320
|
+
shadowEl.current.style.transform = `translateX(${(ex - 45).toFixed(2)}px) scaleX(${(1.25 - 0.5 * t2).toFixed(3)})`;
|
|
321
|
+
shadowEl.current.style.opacity = (0.1 + 0.3 * t2).toFixed(3);
|
|
322
|
+
}
|
|
323
|
+
if (rigEl.current) {
|
|
324
|
+
const totalH = len + 70;
|
|
325
|
+
rigEl.current.setAttribute("viewBox", `0 0 36 ${totalH.toFixed(1)}`);
|
|
326
|
+
rigEl.current.setAttribute("height", totalH.toFixed(1));
|
|
327
|
+
rigEl.current.style.transform = `translateX(${s.x.toFixed(2)}px) rotate(${sway.toFixed(2)}deg)`;
|
|
328
|
+
}
|
|
329
|
+
coilEl.current?.setAttribute("transform", `translate(9 0) scale(1 ${(len / 100).toFixed(4)})`);
|
|
330
|
+
clawEl.current?.setAttribute("transform", `translate(18 ${len.toFixed(2)}) scale(2.5) translate(-20.6 -8.9)`);
|
|
331
|
+
const pinch = 15 * s.close;
|
|
332
|
+
fingerL.current?.setAttribute("transform", `rotate(${pinch.toFixed(2)} ${CLAW_PIVOT.x} ${CLAW_PIVOT.y})`);
|
|
333
|
+
fingerR.current?.setAttribute("transform", `rotate(${(-pinch).toFixed(2)} ${CLAW_PIVOT.x} ${CLAW_PIVOT.y})`);
|
|
334
|
+
for (let i = 0; i < pile.length; i++) {
|
|
335
|
+
const el = pileEls.current[i];
|
|
336
|
+
if (!el) continue;
|
|
337
|
+
const b = soft[i];
|
|
338
|
+
el.style.transform = `translate(${b.dx.toFixed(2)}px, ${(b.dy + b.ey).toFixed(2)}px) rotate(${(pile[i].rot + b.rot).toFixed(2)}deg) scale(${(1 - b.sq * 0.6).toFixed(3)}, ${(1 + b.sq).toFixed(3)})`;
|
|
339
|
+
}
|
|
340
|
+
if (s.carried >= 0 && carriedEl.current) {
|
|
341
|
+
const w = pile[s.carried].w;
|
|
342
|
+
const sc = 1 - s.swallow * 0.78;
|
|
343
|
+
const sx = (sc * (1 - s.stretch * 0.55)).toFixed(3);
|
|
344
|
+
const sy = (sc * (1 + s.stretch)).toFixed(3);
|
|
345
|
+
carriedEl.current.style.transform = `translate(${s.carry.x - w / 2}px, ${s.carry.y - w / 2}px) rotate(${(sway + s.xrot).toFixed(2)}deg) scale(${sx}, ${sy})`;
|
|
346
|
+
if (s.swallow > 0) carriedEl.current.style.opacity = (1 - s.swallow).toFixed(2);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
const stageP = (dur, dt) => {
|
|
350
|
+
s.st += dt;
|
|
351
|
+
return clamp01(s.st / dur);
|
|
352
|
+
};
|
|
353
|
+
const nextStage = (st) => {
|
|
354
|
+
s.stage = st;
|
|
355
|
+
s.st = 0;
|
|
356
|
+
};
|
|
357
|
+
const step = (now) => {
|
|
358
|
+
const dtRaw = prevNow ? Math.min(0.04, Math.max(4e-3, (now - prevNow) / 1e3)) : 1 / 60;
|
|
359
|
+
prevNow = now;
|
|
360
|
+
const dt = dtRaw * speedMul;
|
|
361
|
+
const f = dt * 60;
|
|
362
|
+
const ph = phaseRef.current;
|
|
363
|
+
const a = api.current;
|
|
364
|
+
if (!reduce) {
|
|
365
|
+
const lean = ph === "idle" || ph === "carry" ? -s.vx * 0.042 : 0;
|
|
366
|
+
s.swayV += (lean - s.sway) * 0.05 * f;
|
|
367
|
+
s.swayV *= Math.pow(0.93, f);
|
|
368
|
+
s.sway += s.swayV * f;
|
|
369
|
+
s.breeze = Math.sin(now / 1500) * 0.45 + Math.sin(now / 521) * 0.12;
|
|
370
|
+
}
|
|
371
|
+
for (let i = 0; i < soft.length; i++) {
|
|
372
|
+
const b = soft[i];
|
|
373
|
+
if (!b.landed) {
|
|
374
|
+
if (b.delay > 0) {
|
|
375
|
+
b.delay -= dt;
|
|
376
|
+
} else {
|
|
377
|
+
b.evy += ENTRANCE_G * dt;
|
|
378
|
+
b.ey += b.evy * dt;
|
|
379
|
+
if (b.ey >= 0) {
|
|
380
|
+
b.ey = 0;
|
|
381
|
+
b.landed = true;
|
|
382
|
+
b.vsq += Math.min(0.055, b.evy * 6e-5);
|
|
383
|
+
b.vrot += rand(-0.8, 0.8);
|
|
384
|
+
ripple(pile[i].x, Math.min(0.22, b.evy * 2e-4), i);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
b.vdx += -b.dx * 0.055 * f;
|
|
389
|
+
b.vdy += -b.dy * 0.055 * f;
|
|
390
|
+
b.vrot += -b.rot * 0.05 * f;
|
|
391
|
+
b.vsq += -b.sq * 0.13 * f;
|
|
392
|
+
const damp = Math.pow(0.9, f);
|
|
393
|
+
b.vdx *= damp;
|
|
394
|
+
b.vdy *= damp;
|
|
395
|
+
b.vrot *= Math.pow(0.91, f);
|
|
396
|
+
b.vsq *= Math.pow(0.84, f);
|
|
397
|
+
b.dx += b.vdx * f;
|
|
398
|
+
b.dy += b.vdy * f;
|
|
399
|
+
b.rot += b.vrot * f;
|
|
400
|
+
b.sq += b.vsq * f;
|
|
401
|
+
}
|
|
402
|
+
if (ph === "idle" || ph === "carry") {
|
|
403
|
+
s.drive += (dir.current - s.drive) * (1 - Math.exp(-13 * dt));
|
|
404
|
+
s.vx += s.drive * 720 * dt;
|
|
405
|
+
s.vx *= Math.exp(-5.5 * dt);
|
|
406
|
+
s.x = Math.min(CLAW_MAX, Math.max(CLAW_MIN, s.x + s.vx * dt));
|
|
407
|
+
if (ph === "carry") {
|
|
408
|
+
s.xrot += (s.sway * 0.5 - s.xrot) * (1 - Math.exp(-6 * dt));
|
|
409
|
+
const { ex, ey } = pend();
|
|
410
|
+
s.carry.x = ex;
|
|
411
|
+
s.carry.y = gripY(ey);
|
|
412
|
+
const over = s.x >= TRAY.min && s.x <= TRAY.max;
|
|
413
|
+
if (over !== wasOverTray) {
|
|
414
|
+
wasOverTray = over;
|
|
415
|
+
a.setOverTray(over);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
} else if (ph === "seq") {
|
|
419
|
+
if (s.stage === "antic") {
|
|
420
|
+
const p = stageP(T.antic, dt);
|
|
421
|
+
s.y = HOME_Y - ANTIC_RISE * easeOutCubic(p);
|
|
422
|
+
s.close = -0.55 * easeOutCubic(p);
|
|
423
|
+
if (p >= 1) {
|
|
424
|
+
const cand = candidateAt(s.x);
|
|
425
|
+
if (cand >= 0) {
|
|
426
|
+
const c = toyCenter(cand);
|
|
427
|
+
s.depthY = Math.min(GH - 46, Math.max(HOME_Y + 50, c.y - GRIP_OFFSET - pile[cand].w / 2));
|
|
428
|
+
} else {
|
|
429
|
+
s.depthY = DROP_Y;
|
|
430
|
+
}
|
|
431
|
+
nextStage("down");
|
|
432
|
+
}
|
|
433
|
+
} else if (s.stage === "down") {
|
|
434
|
+
const p = stageP(T.down, dt);
|
|
435
|
+
s.y = HOME_Y - ANTIC_RISE + (s.depthY - HOME_Y + ANTIC_RISE) * easeInQuad(p);
|
|
436
|
+
if (s.y > 130 && !reduce) {
|
|
437
|
+
pile.forEach((q, i) => {
|
|
438
|
+
const d = Math.abs(q.x - s.x);
|
|
439
|
+
if (d < 56) {
|
|
440
|
+
const push = (1 - d / 56) * 7 * dt;
|
|
441
|
+
const side = q.x < s.x ? -1 : 1;
|
|
442
|
+
soft[i].vdx += side * push;
|
|
443
|
+
soft[i].vsq += push * 0.012;
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (p >= 1) nextStage("dwell1");
|
|
448
|
+
} else if (s.stage === "dwell1") {
|
|
449
|
+
const p = stageP(T.dwell1, dt);
|
|
450
|
+
s.y = s.depthY + (reduce ? 0 : 3.5 * Math.sin(Math.PI * p));
|
|
451
|
+
if (p >= 1) {
|
|
452
|
+
s.y = s.depthY;
|
|
453
|
+
nextStage("close");
|
|
454
|
+
}
|
|
455
|
+
} else if (s.stage === "close") {
|
|
456
|
+
const p = stageP(T.close, dt);
|
|
457
|
+
s.close = -0.55 + 1.55 * easeOutCubic(p);
|
|
458
|
+
if (p >= 1) {
|
|
459
|
+
const best = candidateAt(s.x);
|
|
460
|
+
s.carried = best;
|
|
461
|
+
if (best >= 0) {
|
|
462
|
+
s.carry = { ...toyCenter(best) };
|
|
463
|
+
s.xrot = pile[best].rot + soft[best].rot;
|
|
464
|
+
const el = pileEls.current[best];
|
|
465
|
+
if (el) el.style.visibility = "hidden";
|
|
466
|
+
ripple(pile[best].x, 0.35, best);
|
|
467
|
+
if (carriedEl.current) {
|
|
468
|
+
carriedEl.current.src = el?.src ?? carriedEl.current.src;
|
|
469
|
+
carriedEl.current.style.width = `${pile[best].w}px`;
|
|
470
|
+
carriedEl.current.style.visibility = "visible";
|
|
471
|
+
carriedEl.current.style.opacity = "";
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
ripple(s.x, 0.2);
|
|
475
|
+
}
|
|
476
|
+
nextStage("dwell2");
|
|
477
|
+
}
|
|
478
|
+
} else if (s.stage === "dwell2") {
|
|
479
|
+
if (stageP(T.dwell2, dt) >= 1) nextStage(s.carried >= 0 && !reduce ? "load" : "up");
|
|
480
|
+
if (s.carried >= 0) {
|
|
481
|
+
s.xrot += -s.xrot * (1 - Math.exp(-3.5 * dt));
|
|
482
|
+
const { ex, ey } = pend();
|
|
483
|
+
s.carry.x = ex;
|
|
484
|
+
s.carry.y = gripY(ey);
|
|
485
|
+
}
|
|
486
|
+
} else if (s.stage === "load") {
|
|
487
|
+
const p = stageP(T.load, dt);
|
|
488
|
+
const bell = Math.sin(Math.PI * p);
|
|
489
|
+
s.y = s.depthY + 6 * bell;
|
|
490
|
+
s.close = 1 + 0.12 * bell;
|
|
491
|
+
s.xrot += -s.xrot * (1 - Math.exp(-3.5 * dt));
|
|
492
|
+
const { ex, ey } = pend();
|
|
493
|
+
s.carry.x = ex;
|
|
494
|
+
s.carry.y = gripY(ey);
|
|
495
|
+
if (p >= 1) {
|
|
496
|
+
s.y = s.depthY;
|
|
497
|
+
nextStage("up");
|
|
498
|
+
}
|
|
499
|
+
} else if (s.stage === "up") {
|
|
500
|
+
const p = stageP(T.up, dt);
|
|
501
|
+
s.y = s.depthY + (HOME_Y - s.depthY) * easeInOutCubic(p);
|
|
502
|
+
if (s.carried >= 0) {
|
|
503
|
+
s.xrot += -s.xrot * (1 - Math.exp(-3.5 * dt));
|
|
504
|
+
const { ex, ey } = pend();
|
|
505
|
+
s.carry.x = ex;
|
|
506
|
+
s.carry.y = gripY(ey);
|
|
507
|
+
}
|
|
508
|
+
if (p >= 1) {
|
|
509
|
+
if (s.carried >= 0) {
|
|
510
|
+
a.setPhaseBoth("carry");
|
|
511
|
+
a.setMessage(null);
|
|
512
|
+
} else {
|
|
513
|
+
a.setPhaseBoth("idle");
|
|
514
|
+
a.setMessage("Came up empty. Try again.");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} else if (ph === "toTray") {
|
|
519
|
+
const right = s.carried === targetIdx;
|
|
520
|
+
if (s.stage === "open") {
|
|
521
|
+
const p = stageP(T.open, dt);
|
|
522
|
+
s.close = 1 - easeOutCubic(p);
|
|
523
|
+
if (s.st > 0.12 && !s.released) {
|
|
524
|
+
s.released = true;
|
|
525
|
+
if (right) {
|
|
526
|
+
a.setTrayMode("open");
|
|
527
|
+
if (reduce) {
|
|
528
|
+
if (carriedEl.current) carriedEl.current.style.visibility = "hidden";
|
|
529
|
+
s.carried = -1;
|
|
530
|
+
a.setOverTray(false);
|
|
531
|
+
a.setTrayMode("win");
|
|
532
|
+
nextStage("beat");
|
|
533
|
+
a.setPhaseBoth("celebrate");
|
|
534
|
+
} else {
|
|
535
|
+
const m = machineEl.current?.getBoundingClientRect();
|
|
536
|
+
const tr = trayEl.current?.getBoundingClientRect();
|
|
537
|
+
if (m && tr) s.mouthY = tr.top - m.top + 2;
|
|
538
|
+
s.fallV = 30;
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
s.fallV = 40;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (right && s.released && s.carried >= 0) {
|
|
546
|
+
s.fallV = Math.min(s.fallV + DROP_G * dt, 460);
|
|
547
|
+
s.carry.y += s.fallV * dt;
|
|
548
|
+
s.carry.x += (TRAY.cx - s.carry.x) * (1 - Math.exp(-2.2 * dt));
|
|
549
|
+
s.xrot += -s.xrot * (1 - Math.exp(-4 * dt));
|
|
550
|
+
s.stretch = Math.abs(s.fallV) / 460 * 0.09;
|
|
551
|
+
const w = pile[s.carried].w;
|
|
552
|
+
const sunk = s.carry.y + w / 2 - s.mouthY;
|
|
553
|
+
if (sunk > 0 && carriedEl.current) {
|
|
554
|
+
carriedEl.current.style.clipPath = `inset(0 0 ${sunk.toFixed(1)}px 0)`;
|
|
555
|
+
carriedEl.current.style.filter = `brightness(${Math.max(0.4, 1 - sunk / w * 0.75).toFixed(3)})`;
|
|
556
|
+
}
|
|
557
|
+
if (sunk >= w + 4) {
|
|
558
|
+
if (carriedEl.current) {
|
|
559
|
+
carriedEl.current.style.visibility = "hidden";
|
|
560
|
+
carriedEl.current.style.clipPath = "";
|
|
561
|
+
carriedEl.current.style.filter = "";
|
|
562
|
+
}
|
|
563
|
+
s.carried = -1;
|
|
564
|
+
a.setOverTray(false);
|
|
565
|
+
a.setTrayMode("win");
|
|
566
|
+
nextStage("beat");
|
|
567
|
+
a.setPhaseBoth("celebrate");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!right && s.fallV !== 0) {
|
|
571
|
+
s.fallV = Math.min(s.fallV + DROP_G * dt, 360);
|
|
572
|
+
s.carry.y += s.fallV * dt;
|
|
573
|
+
s.carry.x += (TRAY.cx - s.carry.x) * (1 - Math.exp(-4 * dt));
|
|
574
|
+
s.xrot += -s.xrot * (1 - Math.exp(-3 * dt));
|
|
575
|
+
s.stretch = Math.abs(s.fallV) / 460 * 0.09;
|
|
576
|
+
if (s.fallV > 0 && s.carry.y >= TRAY.cy) {
|
|
577
|
+
if (s.fallV > 200) {
|
|
578
|
+
s.carry.y = TRAY.cy;
|
|
579
|
+
s.fallV = -s.fallV * 0.28;
|
|
580
|
+
s.xrot += rand(-7, 7);
|
|
581
|
+
} else {
|
|
582
|
+
s.carry.y = TRAY.cy;
|
|
583
|
+
s.fallV = 0;
|
|
584
|
+
s.stretch = 0;
|
|
585
|
+
a.setOverTray(false);
|
|
586
|
+
a.setTrayMode("no");
|
|
587
|
+
a.setMessage(
|
|
588
|
+
`That\u2019s the ${TOY_META[pile[s.carried].toy].label}! Find the ${TOY_META[target].label}.`
|
|
589
|
+
);
|
|
590
|
+
nextStage("beat");
|
|
591
|
+
a.setPhaseBoth("deny");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
} else if (ph === "celebrate") {
|
|
596
|
+
if (s.stage === "beat") {
|
|
597
|
+
if (stageP(0.28, dt) >= 1) {
|
|
598
|
+
nextStage("shine");
|
|
599
|
+
api.current.setVerified(true);
|
|
600
|
+
onVerifyRef.current?.();
|
|
601
|
+
}
|
|
602
|
+
} else if (s.stage === "shine") {
|
|
603
|
+
if (stageP(0.7, dt) >= 1) api.current.setPhaseBoth("done");
|
|
604
|
+
}
|
|
605
|
+
} else if (ph === "deny") {
|
|
606
|
+
if (s.stage === "beat") {
|
|
607
|
+
const p = stageP(0.46, dt);
|
|
608
|
+
s.swallow = easeOutCubic(p);
|
|
609
|
+
s.carry.y -= 46 * dt;
|
|
610
|
+
if (p >= 1) {
|
|
611
|
+
const idx = s.carried;
|
|
612
|
+
api.current.setTrayMode("");
|
|
613
|
+
const el = pileEls.current[idx];
|
|
614
|
+
if (el) el.style.visibility = "";
|
|
615
|
+
if (carriedEl.current) {
|
|
616
|
+
carriedEl.current.style.visibility = "hidden";
|
|
617
|
+
carriedEl.current.style.opacity = "";
|
|
618
|
+
carriedEl.current.style.clipPath = "";
|
|
619
|
+
carriedEl.current.style.filter = "";
|
|
620
|
+
}
|
|
621
|
+
s.swallow = 0;
|
|
622
|
+
const b = soft[idx];
|
|
623
|
+
b.vsq += 0.05;
|
|
624
|
+
b.vrot += rand(-1, 1);
|
|
625
|
+
ripple(pile[idx].x, 0.25, idx);
|
|
626
|
+
s.carried = -1;
|
|
627
|
+
a.setPhaseBoth("idle");
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
render();
|
|
632
|
+
raf = requestAnimationFrame(step);
|
|
633
|
+
};
|
|
634
|
+
render();
|
|
635
|
+
raf = requestAnimationFrame(step);
|
|
636
|
+
return () => cancelAnimationFrame(raf);
|
|
637
|
+
}, [reduce, targetIdx, target, pile]);
|
|
638
|
+
const action = () => {
|
|
639
|
+
const s = sim.current;
|
|
640
|
+
if (verified) return;
|
|
641
|
+
if (phaseRef.current === "idle") {
|
|
642
|
+
setMessage(null);
|
|
643
|
+
s.close = 0;
|
|
644
|
+
s.stage = "antic";
|
|
645
|
+
s.st = 0;
|
|
646
|
+
setPhaseBoth("seq");
|
|
647
|
+
} else if (phaseRef.current === "carry") {
|
|
648
|
+
if (s.x >= TRAY.min && s.x <= TRAY.max) {
|
|
649
|
+
if (carriedEl.current) {
|
|
650
|
+
carriedEl.current.style.visibility = "visible";
|
|
651
|
+
carriedEl.current.style.opacity = "";
|
|
652
|
+
carriedEl.current.style.clipPath = "";
|
|
653
|
+
carriedEl.current.style.filter = "";
|
|
654
|
+
}
|
|
655
|
+
s.stage = "open";
|
|
656
|
+
s.st = 0;
|
|
657
|
+
s.fallV = 0;
|
|
658
|
+
s.swallow = 0;
|
|
659
|
+
s.stretch = 0;
|
|
660
|
+
s.released = false;
|
|
661
|
+
setOverTray(false);
|
|
662
|
+
setPhaseBoth("toTray");
|
|
663
|
+
} else {
|
|
664
|
+
setMessage("Move the toy over the drop zone first.");
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
const stickDrag = useRef(null);
|
|
669
|
+
const onStickDown = (e) => {
|
|
670
|
+
if (verified) return;
|
|
671
|
+
e.target.setPointerCapture(e.pointerId);
|
|
672
|
+
stickDrag.current = { id: e.pointerId, startX: e.clientX };
|
|
673
|
+
if (stickEl.current) stickEl.current.style.transition = "none";
|
|
674
|
+
};
|
|
675
|
+
const onStickMove = (e) => {
|
|
676
|
+
const d = stickDrag.current;
|
|
677
|
+
if (!d || e.pointerId !== d.id) return;
|
|
678
|
+
const dx = Math.max(-26, Math.min(26, e.clientX - d.startX));
|
|
679
|
+
dir.current = dx / 26;
|
|
680
|
+
if (stickEl.current) stickEl.current.style.transform = `rotate(${(dx * 1.05).toFixed(1)}deg)`;
|
|
681
|
+
};
|
|
682
|
+
const onStickUp = (e) => {
|
|
683
|
+
if (stickDrag.current?.id !== e.pointerId) return;
|
|
684
|
+
stickDrag.current = null;
|
|
685
|
+
dir.current = 0;
|
|
686
|
+
if (stickEl.current) {
|
|
687
|
+
stickEl.current.style.transition = "transform 0.25s cubic-bezier(0.2, 1.6, 0.4, 1)";
|
|
688
|
+
stickEl.current.style.transform = "";
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const onKeyDown = (e) => {
|
|
692
|
+
if (infoOpen) {
|
|
693
|
+
if (e.key === "Escape") setInfoOpen(false);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (verified) return;
|
|
697
|
+
if (e.key === "ArrowLeft") {
|
|
698
|
+
e.preventDefault();
|
|
699
|
+
dir.current = -1;
|
|
700
|
+
} else if (e.key === "ArrowRight") {
|
|
701
|
+
e.preventDefault();
|
|
702
|
+
dir.current = 1;
|
|
703
|
+
} else if ((e.key === " " || e.key === "Enter") && !e.repeat) {
|
|
704
|
+
e.preventDefault();
|
|
705
|
+
action();
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const onKeyUp = (e) => {
|
|
709
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowRight") dir.current = 0;
|
|
710
|
+
};
|
|
711
|
+
const t = TOY_META[target];
|
|
712
|
+
const busy = phase !== "idle" && phase !== "carry";
|
|
713
|
+
const stepNo = verified || phase === "carry" || phase === "toTray" || phase === "celebrate" ? 3 : phase === "seq" ? 2 : 1;
|
|
714
|
+
const carried = sim.current.carried;
|
|
715
|
+
const carriedW = carried >= 0 ? pile[carried].w : 80;
|
|
716
|
+
return (
|
|
717
|
+
// app-wide reduced-motion safety net: under prefers-reduced-motion, Motion
|
|
718
|
+
// drops transform/position animation and keeps opacity — meaningful state
|
|
719
|
+
// still reads, nothing slides.
|
|
720
|
+
/* @__PURE__ */ jsx(MotionConfig, { reducedMotion: "user", children: /* @__PURE__ */ jsxs(
|
|
721
|
+
motion.div,
|
|
722
|
+
{
|
|
723
|
+
className: className ? `clawcap ${className}` : "clawcap",
|
|
724
|
+
role: "group",
|
|
725
|
+
"aria-label": "Claw machine verification",
|
|
726
|
+
tabIndex: 0,
|
|
727
|
+
onKeyDown,
|
|
728
|
+
onKeyUp,
|
|
729
|
+
initial: reduce ? false : { opacity: 0, y: 16, scale: 0.985 },
|
|
730
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
731
|
+
transition: { duration: 0.55, ease: [0.2, 0.8, 0.2, 1] },
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsxs("header", { className: "clawcap-top", children: [
|
|
734
|
+
/* @__PURE__ */ jsx(
|
|
735
|
+
motion.span,
|
|
736
|
+
{
|
|
737
|
+
className: verified ? "clawcap-shield clawcap-shield--ok" : "clawcap-shield",
|
|
738
|
+
"aria-hidden": "true",
|
|
739
|
+
initial: verified ? { scale: 0.55 } : false,
|
|
740
|
+
animate: { scale: 1 },
|
|
741
|
+
transition: { type: "spring", stiffness: 420, damping: 20 },
|
|
742
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 20", width: "14", height: "14", children: [
|
|
743
|
+
/* @__PURE__ */ jsx(
|
|
744
|
+
"path",
|
|
745
|
+
{
|
|
746
|
+
d: "M10 2.2 4 4.6v4.6c0 4 2.6 6.7 6 8.2 3.4-1.5 6-4.2 6-8.2V4.6Z",
|
|
747
|
+
fill: "none",
|
|
748
|
+
stroke: "currentColor",
|
|
749
|
+
strokeWidth: "1.5",
|
|
750
|
+
strokeLinejoin: "round"
|
|
751
|
+
}
|
|
752
|
+
),
|
|
753
|
+
verified && /* @__PURE__ */ jsx("path", { d: "m7 9.8 2.2 2.2L13.4 7.6", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
754
|
+
] })
|
|
755
|
+
},
|
|
756
|
+
verified ? "ok" : "idle"
|
|
757
|
+
),
|
|
758
|
+
/* @__PURE__ */ jsx(
|
|
759
|
+
"button",
|
|
760
|
+
{
|
|
761
|
+
type: "button",
|
|
762
|
+
className: "clawcap-help",
|
|
763
|
+
"aria-label": "About PlayCaptcha",
|
|
764
|
+
"aria-haspopup": "dialog",
|
|
765
|
+
onClick: () => setInfoOpen(true),
|
|
766
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 20", width: "14", height: "14", "aria-hidden": "true", children: [
|
|
767
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "7.4", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
768
|
+
/* @__PURE__ */ jsx("path", { d: "M8 8.2c.2-1.2 1-1.9 2.1-1.9 1.2 0 2 .8 2 1.8 0 1.6-2.1 1.7-2.1 3.2", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }),
|
|
769
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "13.9", r: "0.9", fill: "currentColor" })
|
|
770
|
+
] })
|
|
771
|
+
}
|
|
772
|
+
)
|
|
773
|
+
] }),
|
|
774
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: infoOpen && /* @__PURE__ */ jsx(
|
|
775
|
+
motion.div,
|
|
776
|
+
{
|
|
777
|
+
className: "clawcap-info",
|
|
778
|
+
role: "dialog",
|
|
779
|
+
"aria-modal": "true",
|
|
780
|
+
"aria-label": "About PlayCaptcha",
|
|
781
|
+
onClick: () => setInfoOpen(false),
|
|
782
|
+
initial: { opacity: 0 },
|
|
783
|
+
animate: { opacity: 1 },
|
|
784
|
+
exit: { opacity: 0 },
|
|
785
|
+
transition: { duration: 0.18 },
|
|
786
|
+
children: /* @__PURE__ */ jsxs(
|
|
787
|
+
motion.div,
|
|
788
|
+
{
|
|
789
|
+
className: "clawcap-info-card",
|
|
790
|
+
onClick: (e) => e.stopPropagation(),
|
|
791
|
+
initial: reduce ? false : { opacity: 0, scale: 0.92, y: 10 },
|
|
792
|
+
animate: { opacity: 1, scale: 1, y: 0 },
|
|
793
|
+
exit: { opacity: 0, scale: 0.97, transition: { duration: 0.13, ease: "easeOut" } },
|
|
794
|
+
transition: { type: "spring", stiffness: 360, damping: 28 },
|
|
795
|
+
children: [
|
|
796
|
+
/* @__PURE__ */ jsx(
|
|
797
|
+
"button",
|
|
798
|
+
{
|
|
799
|
+
type: "button",
|
|
800
|
+
className: "clawcap-info-x",
|
|
801
|
+
"aria-label": "Close",
|
|
802
|
+
onClick: () => setInfoOpen(false),
|
|
803
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M5 5l10 10M15 5 5 15", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) })
|
|
804
|
+
}
|
|
805
|
+
),
|
|
806
|
+
/* @__PURE__ */ jsxs("div", { className: "clawcap-info-head", children: [
|
|
807
|
+
/* @__PURE__ */ jsx("span", { className: "clawcap-info-tile", children: /* @__PURE__ */ jsx("img", { src: "/sataruz-captcha.svg", alt: "", "aria-hidden": "true" }) }),
|
|
808
|
+
/* @__PURE__ */ jsxs("h4", { className: "clawcap-info-title", children: [
|
|
809
|
+
"SataruzCaptcha ",
|
|
810
|
+
/* @__PURE__ */ jsx("span", { className: "clawcap-info-ver", children: "v1" })
|
|
811
|
+
] }),
|
|
812
|
+
/* @__PURE__ */ jsx("p", { className: "clawcap-info-tag", children: "Catch the right toy to prove you\u2019re human." })
|
|
813
|
+
] }),
|
|
814
|
+
/* @__PURE__ */ jsx("ol", { className: "clawcap-info-list", children: [
|
|
815
|
+
["Move", /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
816
|
+
"Line the claw up right over your prize \u2014 joystick or ",
|
|
817
|
+
/* @__PURE__ */ jsx("kbd", { children: "\u2190" }),
|
|
818
|
+
" ",
|
|
819
|
+
/* @__PURE__ */ jsx("kbd", { children: "\u2192" })
|
|
820
|
+
] })],
|
|
821
|
+
["Grab", /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
822
|
+
"Commit. The claw dives, bites and hauls it up \u2014 red button or ",
|
|
823
|
+
/* @__PURE__ */ jsx("kbd", { children: "Space" })
|
|
824
|
+
] })],
|
|
825
|
+
["Drop", /* @__PURE__ */ jsx(Fragment, { children: "Ferry it to the hatch and let go. Wrong toy? Straight back on the pile" })]
|
|
826
|
+
].map(([label, desc], i) => /* @__PURE__ */ jsxs("li", { children: [
|
|
827
|
+
/* @__PURE__ */ jsx("span", { className: "clawcap-info-n", children: i + 1 }),
|
|
828
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
829
|
+
/* @__PURE__ */ jsx("strong", { children: label }),
|
|
830
|
+
/* @__PURE__ */ jsx("span", { className: "clawcap-info-d", children: desc })
|
|
831
|
+
] })
|
|
832
|
+
] }, label)) }),
|
|
833
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "clawcap-info-done", onClick: () => setInfoOpen(false), children: "Got it" })
|
|
834
|
+
]
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
}
|
|
838
|
+
) }),
|
|
839
|
+
/* @__PURE__ */ jsx("h3", { className: "clawcap-title", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx(
|
|
840
|
+
motion.span,
|
|
841
|
+
{
|
|
842
|
+
style: { display: "inline-block" },
|
|
843
|
+
initial: { opacity: 0, y: 5 },
|
|
844
|
+
animate: { opacity: 1, y: 0 },
|
|
845
|
+
exit: { opacity: 0, y: -5 },
|
|
846
|
+
transition: { duration: 0.24, ease: "easeOut" },
|
|
847
|
+
children: verified ? "Verified" : title
|
|
848
|
+
},
|
|
849
|
+
verified ? "verified" : "title"
|
|
850
|
+
) }) }),
|
|
851
|
+
/* @__PURE__ */ jsx("p", { className: "clawcap-sub", "aria-live": "polite", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx(
|
|
852
|
+
motion.span,
|
|
853
|
+
{
|
|
854
|
+
style: { display: "inline-block" },
|
|
855
|
+
initial: { opacity: 0, y: 4 },
|
|
856
|
+
animate: { opacity: 1, y: 0 },
|
|
857
|
+
exit: { opacity: 0, y: -4 },
|
|
858
|
+
transition: { duration: 0.2, ease: "easeOut" },
|
|
859
|
+
children: verified ? "You\u2019re human. Nice catch." : message ? message : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
860
|
+
"Use the claw to pick up the",
|
|
861
|
+
" ",
|
|
862
|
+
/* @__PURE__ */ jsxs("em", { style: { color: t.accent }, children: [
|
|
863
|
+
/* @__PURE__ */ jsx("img", { className: "clawcap-sub-toy", src: `${assetBase}${target}.png`, alt: "", draggable: false }),
|
|
864
|
+
t.label
|
|
865
|
+
] })
|
|
866
|
+
] })
|
|
867
|
+
},
|
|
868
|
+
verified ? "done" : message ?? "challenge"
|
|
869
|
+
) }) }),
|
|
870
|
+
/* @__PURE__ */ jsxs("ol", { className: "clawcap-steps", "aria-hidden": "true", children: [
|
|
871
|
+
/* @__PURE__ */ jsx("span", { className: "clawcap-steps-pill", style: { transform: `translateX(${(stepNo - 1) * 100}%)` } }),
|
|
872
|
+
["Move", "Grab", "Drop"].map((label, i) => /* @__PURE__ */ jsxs("li", { className: stepNo === i + 1 ? "is-active" : void 0, children: [
|
|
873
|
+
/* @__PURE__ */ jsx("span", { className: "clawcap-step-n", children: i + 1 }),
|
|
874
|
+
" ",
|
|
875
|
+
label
|
|
876
|
+
] }, label))
|
|
877
|
+
] }),
|
|
878
|
+
/* @__PURE__ */ jsxs("div", { ref: machineEl, className: "clawcap-machine", children: [
|
|
879
|
+
/* @__PURE__ */ jsxs("div", { className: "clawcap-case", children: [
|
|
880
|
+
/* @__PURE__ */ jsxs("div", { className: verified ? "clawcap-glass clawcap-glass--dim" : "clawcap-glass", children: [
|
|
881
|
+
/* @__PURE__ */ jsx("div", { className: "cc-rail" }),
|
|
882
|
+
/* @__PURE__ */ jsx("div", { ref: trolleyEl, className: "cc-trolley", "aria-hidden": "true" }),
|
|
883
|
+
/* @__PURE__ */ jsx("div", { ref: shadowEl, className: "cc-claw-shadow", "aria-hidden": "true" }),
|
|
884
|
+
pile.map((p, i) => /* @__PURE__ */ jsx(
|
|
885
|
+
"img",
|
|
886
|
+
{
|
|
887
|
+
ref: (el) => {
|
|
888
|
+
pileEls.current[i] = el;
|
|
889
|
+
},
|
|
890
|
+
className: "cc-toy",
|
|
891
|
+
src: `${assetBase}${p.toy}.png`,
|
|
892
|
+
alt: "",
|
|
893
|
+
draggable: false,
|
|
894
|
+
style: {
|
|
895
|
+
left: p.x - p.w / 2,
|
|
896
|
+
bottom: p.b,
|
|
897
|
+
width: p.w,
|
|
898
|
+
zIndex: p.z,
|
|
899
|
+
transform: `translateY(${p.dropFrom}px)`,
|
|
900
|
+
transformOrigin: "50% 100%"
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
p.toy
|
|
904
|
+
)),
|
|
905
|
+
/* @__PURE__ */ jsx("div", { className: "cc-pile-shadow" }),
|
|
906
|
+
/* @__PURE__ */ jsxs("svg", { ref: rigEl, className: "cc-rig", width: "36", height: COIL_LEN + 70, viewBox: `0 0 36 ${COIL_LEN + 70}`, "aria-hidden": "true", children: [
|
|
907
|
+
/* @__PURE__ */ jsx("g", { ref: coilEl, transform: `translate(9 0) scale(1 ${COIL_LEN / 100})`, children: /* @__PURE__ */ jsx(
|
|
908
|
+
"path",
|
|
909
|
+
{
|
|
910
|
+
d: "M9 0 L9 5 C 15 7.5 15 9.5 9 12 C 3 14.5 3 16.5 9 19 C 15 21.5 15 23.5 9 26 C 3 28.5 3 30.5 9 33 C 15 35.5 15 37.5 9 40 C 3 42.5 3 44.5 9 47 C 15 49.5 15 51.5 9 54 C 3 56.5 3 58.5 9 61 C 15 63.5 15 65.5 9 68 C 3 70.5 3 72.5 9 75 C 15 77.5 15 79.5 9 82 C 3 84.5 3 86.5 9 89 C 15 91.5 15 93.5 9 95 L 9 100",
|
|
911
|
+
fill: "none",
|
|
912
|
+
stroke: "#9A9FA8",
|
|
913
|
+
strokeWidth: "2.2",
|
|
914
|
+
strokeLinecap: "round",
|
|
915
|
+
strokeLinejoin: "round",
|
|
916
|
+
vectorEffect: "non-scaling-stroke"
|
|
917
|
+
}
|
|
918
|
+
) }),
|
|
919
|
+
/* @__PURE__ */ jsxs("g", { ref: clawEl, transform: `translate(18 ${COIL_LEN}) scale(2.5) translate(-20.6 -8.9)`, children: [
|
|
920
|
+
/* @__PURE__ */ jsx("g", { ref: fingerL, children: CLAW_ARM_L.map((p, i) => /* @__PURE__ */ jsx("path", { fill: p.fill, d: p.d }, i)) }),
|
|
921
|
+
/* @__PURE__ */ jsx("g", { ref: fingerR, children: CLAW_ARM_R.map((p, i) => /* @__PURE__ */ jsx("path", { fill: p.fill, d: p.d }, i)) }),
|
|
922
|
+
CLAW_BODY.map((p, i) => /* @__PURE__ */ jsx("path", { fill: p.fill, d: p.d }, i))
|
|
923
|
+
] })
|
|
924
|
+
] }),
|
|
925
|
+
/* @__PURE__ */ jsx("div", { className: "cc-glass-shine" })
|
|
926
|
+
] }),
|
|
927
|
+
/* @__PURE__ */ jsxs("div", { className: "clawcap-panel", children: [
|
|
928
|
+
/* @__PURE__ */ jsxs(
|
|
929
|
+
"div",
|
|
930
|
+
{
|
|
931
|
+
className: "cc-joy",
|
|
932
|
+
role: "slider",
|
|
933
|
+
"aria-label": "Move the claw",
|
|
934
|
+
"aria-valuemin": 0,
|
|
935
|
+
"aria-valuemax": 100,
|
|
936
|
+
"aria-valuenow": Math.round((sim.current.x - CLAW_MIN) / (CLAW_MAX - CLAW_MIN) * 100),
|
|
937
|
+
onPointerDown: onStickDown,
|
|
938
|
+
onPointerMove: onStickMove,
|
|
939
|
+
onPointerUp: onStickUp,
|
|
940
|
+
onPointerCancel: onStickUp,
|
|
941
|
+
children: [
|
|
942
|
+
/* @__PURE__ */ jsx("div", { className: "cc-joy-base" }),
|
|
943
|
+
/* @__PURE__ */ jsxs("div", { ref: stickEl, className: "cc-joy-stick", children: [
|
|
944
|
+
/* @__PURE__ */ jsx("div", { className: "cc-joy-shaft" }),
|
|
945
|
+
/* @__PURE__ */ jsx("div", { className: "cc-joy-ball" })
|
|
946
|
+
] })
|
|
947
|
+
]
|
|
948
|
+
}
|
|
949
|
+
),
|
|
950
|
+
/* @__PURE__ */ jsxs(
|
|
951
|
+
"div",
|
|
952
|
+
{
|
|
953
|
+
ref: trayEl,
|
|
954
|
+
className: "cc-tray" + (trayMode === "open" ? " cc-tray--open" : trayMode === "win" ? " cc-tray--win" : trayMode === "no" ? " cc-tray--no" : overTray ? " cc-tray--hot" : ""),
|
|
955
|
+
children: [
|
|
956
|
+
/* @__PURE__ */ jsxs("span", { className: "cc-tray-hatch", "aria-hidden": "true", children: [
|
|
957
|
+
/* @__PURE__ */ jsx("span", { className: "cc-tray-mouth" }),
|
|
958
|
+
/* @__PURE__ */ jsx("span", { className: "cc-tray-door cc-tray-door--l" }),
|
|
959
|
+
/* @__PURE__ */ jsx("span", { className: "cc-tray-door cc-tray-door--r" }),
|
|
960
|
+
/* @__PURE__ */ jsx("span", { className: "cc-tray-skin" })
|
|
961
|
+
] }),
|
|
962
|
+
trayMode === "win" && !reduce && /* @__PURE__ */ jsx("span", { className: "cc-confetti", "aria-hidden": "true", children: CONFETTI.map((p, i) => /* @__PURE__ */ jsx(
|
|
963
|
+
"i",
|
|
964
|
+
{
|
|
965
|
+
style: {
|
|
966
|
+
background: p.c,
|
|
967
|
+
animationDelay: `${p.d}s`,
|
|
968
|
+
"--dx": `${p.dx}px`,
|
|
969
|
+
"--dy": `${p.dy}px`,
|
|
970
|
+
"--dr": `${p.dr}deg`
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
i
|
|
974
|
+
)) }),
|
|
975
|
+
/* @__PURE__ */ jsxs("span", { className: "cc-tray-label", children: [
|
|
976
|
+
trayMode === "win" ? (
|
|
977
|
+
// a clean check — the catch is in
|
|
978
|
+
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "m3.6 8.6 2.9 2.9 6-6.8", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
979
|
+
) : trayMode === "no" ? (
|
|
980
|
+
// try-again loop — it goes back to the pile
|
|
981
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: [
|
|
982
|
+
/* @__PURE__ */ jsx("path", { d: "M13.2 8A5.2 5.2 0 1 1 11.6 4.25", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
|
|
983
|
+
/* @__PURE__ */ jsx("path", { d: "M11.7 1.5v2.9h2.9", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
984
|
+
] })
|
|
985
|
+
) : (
|
|
986
|
+
// a toy over the parted slot — this is where the catch goes in
|
|
987
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: [
|
|
988
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "4.9", r: "2.7", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
989
|
+
/* @__PURE__ */ jsx("path", { d: "M2.4 12.1h3.7M9.9 12.1h3.7", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
990
|
+
] })
|
|
991
|
+
),
|
|
992
|
+
/* @__PURE__ */ jsx("span", { children: trayMode === "win" ? "Nice catch!" : trayMode === "no" ? "Hmm, wrong toy" : overTray ? "Release!" : "Drop here" })
|
|
993
|
+
] })
|
|
994
|
+
]
|
|
995
|
+
}
|
|
996
|
+
),
|
|
997
|
+
/* @__PURE__ */ jsx(
|
|
998
|
+
"button",
|
|
999
|
+
{
|
|
1000
|
+
type: "button",
|
|
1001
|
+
className: phase === "carry" && overTray ? "cc-action cc-action--ready" : "cc-action",
|
|
1002
|
+
onClick: action,
|
|
1003
|
+
disabled: busy || verified,
|
|
1004
|
+
"aria-label": phase === "carry" ? "Drop the toy" : "Grab",
|
|
1005
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx(
|
|
1006
|
+
motion.span,
|
|
1007
|
+
{
|
|
1008
|
+
style: { display: "inline-block" },
|
|
1009
|
+
initial: { opacity: 0, y: 6 },
|
|
1010
|
+
animate: { opacity: 1, y: 0 },
|
|
1011
|
+
exit: { opacity: 0, y: -6 },
|
|
1012
|
+
transition: { duration: 0.16, ease: "easeOut" },
|
|
1013
|
+
children: phase === "carry" ? "Drop" : "Grab"
|
|
1014
|
+
},
|
|
1015
|
+
phase === "carry" ? "drop" : "grab"
|
|
1016
|
+
) })
|
|
1017
|
+
}
|
|
1018
|
+
)
|
|
1019
|
+
] })
|
|
1020
|
+
] }),
|
|
1021
|
+
/* @__PURE__ */ jsx(
|
|
1022
|
+
"img",
|
|
1023
|
+
{
|
|
1024
|
+
ref: carriedEl,
|
|
1025
|
+
className: "cc-carried",
|
|
1026
|
+
src: carried >= 0 ? `${assetBase}${pile[carried].toy}.png` : `${assetBase}${target}.png`,
|
|
1027
|
+
alt: "",
|
|
1028
|
+
draggable: false,
|
|
1029
|
+
style: {
|
|
1030
|
+
width: carriedW,
|
|
1031
|
+
visibility: carried >= 0 && phase !== "idle" ? "visible" : "hidden"
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
)
|
|
1035
|
+
] }),
|
|
1036
|
+
/* @__PURE__ */ jsx("p", { className: "clawcap-hint", children: "Joystick or \u2190 \u2192 to move \xB7 Space to grab & drop" })
|
|
1037
|
+
]
|
|
1038
|
+
}
|
|
1039
|
+
) })
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
export { SataruzCaptcha, TOY_META };
|