ape-claw 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.
Files changed (114) hide show
  1. package/.cursor/skills/ape-claw/SKILL.md +322 -0
  2. package/LICENSE +21 -0
  3. package/README.md +826 -0
  4. package/allowlists/opensea-slug-overrides.json +13 -0
  5. package/allowlists/recommended.apechain.json +322 -0
  6. package/config/clawbots.example.json +3 -0
  7. package/config/policy.example.json +27 -0
  8. package/data/starter-pack-bundle.json +1 -0
  9. package/data/starter-pack.json +495 -0
  10. package/docs/ACP_BOUNTIES.md +108 -0
  11. package/docs/APECLAW_V2_ALPHA.md +206 -0
  12. package/docs/AUTONOMY_AND_SUBSTRATE.md +69 -0
  13. package/docs/CLAWBOTS_AND_INVITES.md +102 -0
  14. package/docs/CLI_GUIDE.md +124 -0
  15. package/docs/CONTRIBUTING.md +130 -0
  16. package/docs/DASHBOARD_GUIDE.md +108 -0
  17. package/docs/GLOBAL_BACKEND.md +145 -0
  18. package/docs/ONCHAIN_V2_GUIDE.md +140 -0
  19. package/docs/PRODUCT_OVERVIEW.md +127 -0
  20. package/docs/README.md +40 -0
  21. package/docs/SKILLCARDS_AND_IMPORTER.md +147 -0
  22. package/docs/STARTER_PACK.md +297 -0
  23. package/docs/SUPPORTED_NETWORKS.md +58 -0
  24. package/docs/TELEMETRY_AND_EVENTS.md +103 -0
  25. package/docs/THE_POD_RUNNER.md +198 -0
  26. package/docs/V1_WORKFLOWS.md +108 -0
  27. package/docs/V2_ONCHAIN_SKILLS.md +157 -0
  28. package/docs/WEB4_PLAN_STATUS.md +95 -0
  29. package/docs/WEB4_SWARM_MODEL.md +104 -0
  30. package/docs/archive/AUTONOMY_AND_SUBSTRATE.md +66 -0
  31. package/docs/archive/WEB4_PLAN_STATUS.md +93 -0
  32. package/docs/archive/WEB4_SWARM_MODEL.md +98 -0
  33. package/docs/developer/01-architecture.md +345 -0
  34. package/docs/developer/02-contracts.md +1034 -0
  35. package/docs/developer/03-writing-modules.md +513 -0
  36. package/docs/developer/04-skillcard-spec.md +336 -0
  37. package/docs/developer/05-backend-api.md +1079 -0
  38. package/docs/developer/06-telemetry.md +798 -0
  39. package/docs/developer/07-testing.md +546 -0
  40. package/docs/developer/08-contributing.md +211 -0
  41. package/docs/operator/01-quickstart.md +49 -0
  42. package/docs/operator/02-dashboard.md +174 -0
  43. package/docs/operator/03-cli-reference.md +818 -0
  44. package/docs/operator/04-skills-library.md +169 -0
  45. package/docs/operator/05-pod-operations.md +314 -0
  46. package/docs/operator/06-deployment.md +299 -0
  47. package/docs/operator/07-safety-and-policy.md +311 -0
  48. package/docs/operator/08-troubleshooting.md +457 -0
  49. package/docs/operator/09-env-reference.md +238 -0
  50. package/docs/social/STARTER_PACK_THREAD.md +209 -0
  51. package/package.json +77 -0
  52. package/skillcards/import-sources.json +93 -0
  53. package/skillcards/seed/acp-bounty-poll.v1.json +38 -0
  54. package/skillcards/seed/acp-bounty-post.v1.json +55 -0
  55. package/skillcards/seed/acp-browse.v1.json +41 -0
  56. package/skillcards/seed/acp-fulfill-and-route.v1.json +56 -0
  57. package/skillcards/seed/apeclaw-bridge-relay.v1.json +46 -0
  58. package/skillcards/seed/apeclaw-nft-autobuy.v1.json +60 -0
  59. package/skillcards/seed/apeclaw-receipt-recorder.v1.json +64 -0
  60. package/skillcards/seed/humanizer.v1.json +74 -0
  61. package/skillcards/seed/otherside-navigator.v1.json +116 -0
  62. package/skillcards/seed/stonkbrokers-launcher.v1.json +280 -0
  63. package/skillcards/seed/walkie-p2p.v1.json +66 -0
  64. package/src/cli/index.mjs +8 -0
  65. package/src/cli.mjs +1929 -0
  66. package/src/lib/bridge-relay.mjs +294 -0
  67. package/src/lib/clawbots.mjs +94 -0
  68. package/src/lib/io.mjs +36 -0
  69. package/src/lib/market.mjs +233 -0
  70. package/src/lib/nft-opensea.mjs +159 -0
  71. package/src/lib/paths.mjs +17 -0
  72. package/src/lib/pod-init.mjs +40 -0
  73. package/src/lib/policy.mjs +112 -0
  74. package/src/lib/rpc.mjs +49 -0
  75. package/src/lib/telemetry.mjs +92 -0
  76. package/src/lib/v2-onchain-abi.mjs +294 -0
  77. package/src/lib/v2-skillcard.mjs +27 -0
  78. package/src/server/index.mjs +169 -0
  79. package/src/server/logger.mjs +21 -0
  80. package/src/server/middleware/auth.mjs +90 -0
  81. package/src/server/middleware/body-limit.mjs +35 -0
  82. package/src/server/middleware/cors.mjs +33 -0
  83. package/src/server/middleware/rate-limit.mjs +44 -0
  84. package/src/server/routes/chat.mjs +178 -0
  85. package/src/server/routes/clawbots.mjs +182 -0
  86. package/src/server/routes/events.mjs +95 -0
  87. package/src/server/routes/health.mjs +72 -0
  88. package/src/server/routes/pod.mjs +64 -0
  89. package/src/server/routes/quotes.mjs +161 -0
  90. package/src/server/routes/skills.mjs +239 -0
  91. package/src/server/routes/static.mjs +161 -0
  92. package/src/server/routes/v2.mjs +48 -0
  93. package/src/server/sse.mjs +73 -0
  94. package/src/server/storage/file-backend.mjs +295 -0
  95. package/src/server/storage/index.mjs +37 -0
  96. package/src/server/storage/sqlite-backend.mjs +380 -0
  97. package/src/telemetry-server.mjs +1604 -0
  98. package/ui/css/dashboard.css +792 -0
  99. package/ui/css/skills.css +689 -0
  100. package/ui/docs.html +840 -0
  101. package/ui/favicon-180.png +0 -0
  102. package/ui/favicon-192.png +0 -0
  103. package/ui/favicon-32.png +0 -0
  104. package/ui/favicon-lobster.png +0 -0
  105. package/ui/favicon.svg +10 -0
  106. package/ui/index.html +2957 -0
  107. package/ui/js/dashboard.js +1766 -0
  108. package/ui/js/skills.js +1621 -0
  109. package/ui/pod.html +909 -0
  110. package/ui/shared/motion.css +286 -0
  111. package/ui/shared/motion.js +170 -0
  112. package/ui/shared/sidebar-nav.css +379 -0
  113. package/ui/shared/sidebar-nav.js +137 -0
  114. package/ui/skills.html +2879 -0
@@ -0,0 +1,286 @@
1
+ /* ══════════════════════════════════════════════════
2
+ ApeClaw motion primitives
3
+ ══════════════════════════════════════════════════ */
4
+
5
+ /* Section reveal — slides up and fades in */
6
+ @keyframes acRevealUp {
7
+ from { opacity: 0; transform: translateY(40px); }
8
+ to { opacity: 1; transform: translateY(0); }
9
+ }
10
+
11
+ /* Counter number "pop" on arrival */
12
+ @keyframes acCounterPop {
13
+ 0% { transform: scale(0.8); opacity: 0; }
14
+ 60% { transform: scale(1.05); }
15
+ 100% { transform: scale(1); opacity: 1; }
16
+ }
17
+
18
+ /* Horizontal divider sweep */
19
+ @keyframes acLineSweep {
20
+ from { transform: scaleX(0); }
21
+ to { transform: scaleX(1); }
22
+ }
23
+
24
+ /* Progress bar shimmer sweep */
25
+ @keyframes acShimmer {
26
+ 0% { background-position: -200% center; }
27
+ 100% { background-position: 200% center; }
28
+ }
29
+
30
+ /* Scale in from center */
31
+ @keyframes acScaleIn {
32
+ from { transform: scale(0.85); opacity: 0; }
33
+ to { transform: scale(1); opacity: 1; }
34
+ }
35
+
36
+ /* Gentle bounce for scroll hints */
37
+ @keyframes acBounce {
38
+ 0%, 100% { transform: translateY(0); }
39
+ 50% { transform: translateY(6px); }
40
+ }
41
+
42
+ /* CTA glow pulse */
43
+ @keyframes acGlowPulse {
44
+ 0%, 100% { box-shadow: 0 0 12px rgba(207,255,4,0.10); }
45
+ 50% { box-shadow: 0 0 24px rgba(207,255,4,0.20), 0 0 48px rgba(207,255,4,0.06); }
46
+ }
47
+
48
+ /* Magnetic tilt for cards */
49
+ @keyframes acTiltReset {
50
+ to { transform: perspective(600px) rotateX(0) rotateY(0) scale(1); }
51
+ }
52
+
53
+ /* Particle float */
54
+ @keyframes acFloat {
55
+ 0%, 100% { transform: translateY(0) translateX(0); opacity: 0.7; }
56
+ 25% { transform: translateY(-8px) translateX(3px); opacity: 1; }
57
+ 75% { transform: translateY(4px) translateX(-2px); opacity: 0.5; }
58
+ }
59
+
60
+ /* Gradient border spin */
61
+ @keyframes acBorderSpin {
62
+ to { --ac-border-angle: 360deg; }
63
+ }
64
+
65
+ /* Typing cursor blink */
66
+ @keyframes acCursorBlink {
67
+ 0%, 49% { border-color: currentColor; }
68
+ 50%, 100% { border-color: transparent; }
69
+ }
70
+
71
+ /* Ripple expand */
72
+ @keyframes acRipple {
73
+ from { transform: scale(0); opacity: 0.4; }
74
+ to { transform: scale(4); opacity: 0; }
75
+ }
76
+
77
+ /* ── States ─────────────────────────────── */
78
+
79
+ .ac-observe {
80
+ opacity: 0;
81
+ transform: translateY(40px);
82
+ transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
83
+ transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
84
+ }
85
+ .ac-observe.ac-visible {
86
+ opacity: 1;
87
+ transform: translateY(0);
88
+ }
89
+
90
+ /* Slide in from left variant */
91
+ .ac-observe.ac-from-left {
92
+ transform: translateX(-40px);
93
+ }
94
+ .ac-observe.ac-from-left.ac-visible {
95
+ transform: translateX(0);
96
+ }
97
+
98
+ /* Slide in from right variant */
99
+ .ac-observe.ac-from-right {
100
+ transform: translateX(40px);
101
+ }
102
+ .ac-observe.ac-from-right.ac-visible {
103
+ transform: translateX(0);
104
+ }
105
+
106
+ /* Scale-up variant */
107
+ .ac-observe.ac-scale {
108
+ transform: scale(0.9);
109
+ }
110
+ .ac-observe.ac-scale.ac-visible {
111
+ transform: scale(1);
112
+ }
113
+
114
+ /* Stagger children inside a revealed container */
115
+ .ac-stagger > .ac-observe {
116
+ transition-delay: calc(var(--ac-i, 0) * 80ms);
117
+ }
118
+
119
+ /* Faster stagger for grids */
120
+ .ac-stagger-fast > .ac-observe {
121
+ transition-delay: calc(var(--ac-i, 0) * 45ms);
122
+ }
123
+
124
+ /* Counter pop class */
125
+ .ac-counter-pop {
126
+ animation: acCounterPop 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
127
+ }
128
+
129
+ /* Section divider line that sweeps in */
130
+ .ac-divider {
131
+ height: 1px;
132
+ background: linear-gradient(90deg, transparent, rgba(207,255,4,0.25), transparent);
133
+ transform: scaleX(0);
134
+ opacity: 1 !important;
135
+ transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
136
+ }
137
+ .ac-visible > .ac-divider,
138
+ .ac-divider.ac-visible {
139
+ transform: scaleX(1);
140
+ }
141
+
142
+ /* Progress bar shimmer overlay */
143
+ .ac-shimmer {
144
+ position: relative;
145
+ overflow: hidden;
146
+ }
147
+ .ac-shimmer::after {
148
+ content: "";
149
+ position: absolute;
150
+ inset: 0;
151
+ background-image: linear-gradient(
152
+ 90deg,
153
+ transparent 0%,
154
+ rgba(255,255,255,0.12) 50%,
155
+ transparent 100%
156
+ );
157
+ background-size: 200% 100%;
158
+ animation: acShimmer 2s ease-in-out infinite;
159
+ }
160
+
161
+ /* Stat shimmer bar */
162
+ .ac-stat-shimmer {
163
+ height: 3px;
164
+ margin-top: 6px;
165
+ border-radius: 2px;
166
+ background: linear-gradient(
167
+ 90deg,
168
+ rgba(207,255,4,0.0) 0%,
169
+ rgba(207,255,4,0.25) 50%,
170
+ rgba(207,255,4,0.0) 100%
171
+ );
172
+ background-size: 200% 100%;
173
+ animation: acShimmer 1.8s ease-in-out infinite;
174
+ opacity: 0;
175
+ transition: opacity 0.3s;
176
+ }
177
+ .ac-stat-shimmer.ac-active {
178
+ opacity: 1;
179
+ }
180
+
181
+ /* Scroll hint bounce */
182
+ .ac-bounce {
183
+ animation: acBounce 2s ease-in-out infinite;
184
+ }
185
+
186
+ /* CTA glow pulse */
187
+ .ac-glow-pulse {
188
+ animation: acGlowPulse 3s ease-in-out infinite;
189
+ }
190
+
191
+ /* ── Magnetic tilt on hover ────────────── */
192
+ .ac-tilt {
193
+ transition: transform 0.4s cubic-bezier(0.22, 0.68, 0, 1.2);
194
+ will-change: transform;
195
+ }
196
+ .ac-tilt:hover {
197
+ transform: perspective(600px) rotateX(var(--ac-ry, 0deg)) rotateY(var(--ac-rx, 0deg));
198
+ }
199
+
200
+ /* ── Interactive ripple on click ─────── */
201
+ .ac-ripple {
202
+ position: relative;
203
+ overflow: hidden;
204
+ }
205
+ .ac-ripple .ac-ripple-circle {
206
+ position: absolute;
207
+ width: 20px;
208
+ height: 20px;
209
+ border-radius: 50%;
210
+ background: rgba(207,255,4,0.15);
211
+ pointer-events: none;
212
+ animation: acRipple 0.6s ease-out forwards;
213
+ }
214
+
215
+ /* ── Skeleton loading placeholder ──── */
216
+ .ac-skeleton {
217
+ position: relative;
218
+ overflow: hidden;
219
+ background: rgba(255,255,255,0.03);
220
+ border-radius: 4px;
221
+ }
222
+ .ac-skeleton::after {
223
+ content: '';
224
+ position: absolute;
225
+ inset: 0;
226
+ background: linear-gradient(90deg,
227
+ transparent 0%,
228
+ rgba(255,255,255,0.04) 40%,
229
+ rgba(255,255,255,0.06) 50%,
230
+ rgba(255,255,255,0.04) 60%,
231
+ transparent 100%
232
+ );
233
+ background-size: 200% 100%;
234
+ animation: acShimmer 1.5s ease-in-out infinite;
235
+ }
236
+
237
+ /* ── Focus ring ────────────────────────── */
238
+ .ac-focus-ring:focus-visible {
239
+ outline: 2px solid rgba(207,255,4,0.6);
240
+ outline-offset: 3px;
241
+ box-shadow: 0 0 0 4px rgba(207,255,4,0.08);
242
+ }
243
+
244
+ /* ── Tooltip ───────────────────────────── */
245
+ [data-ac-tip] {
246
+ position: relative;
247
+ }
248
+ [data-ac-tip]::after {
249
+ content: attr(data-ac-tip);
250
+ position: absolute;
251
+ bottom: calc(100% + 8px);
252
+ left: 50%;
253
+ transform: translateX(-50%) translateY(4px);
254
+ padding: 5px 10px;
255
+ background: rgba(9,13,18,0.95);
256
+ border: 1px solid rgba(207,255,4,0.2);
257
+ border-radius: 4px;
258
+ color: #e6e6e6;
259
+ font-size: 11px;
260
+ font-weight: 600;
261
+ letter-spacing: 0.03em;
262
+ white-space: nowrap;
263
+ z-index: 100;
264
+ pointer-events: none;
265
+ opacity: 0;
266
+ transition: opacity 0.2s, transform 0.2s;
267
+ box-shadow: 0 4px 16px rgba(0,0,0,0.4);
268
+ }
269
+ [data-ac-tip]:hover::after {
270
+ opacity: 1;
271
+ transform: translateX(-50%) translateY(0);
272
+ }
273
+
274
+ /* ── Reduced motion ────────────────────── */
275
+ @media (prefers-reduced-motion: reduce) {
276
+ .ac-observe { transition-duration: 0.01ms !important; }
277
+ .ac-counter-pop { animation-duration: 0.01ms !important; }
278
+ .ac-shimmer::after { animation: none !important; }
279
+ .ac-stat-shimmer { animation: none !important; }
280
+ .ac-bounce { animation: none !important; }
281
+ .ac-glow-pulse { animation: none !important; }
282
+ .ac-divider { transition-duration: 0.01ms !important; }
283
+ .ac-skeleton::after { animation: none !important; }
284
+ .ac-tilt { transition: none !important; }
285
+ .ac-tilt:hover { transform: none !important; }
286
+ }
@@ -0,0 +1,170 @@
1
+ (function () {
2
+ "use strict";
3
+ if (typeof IntersectionObserver === "undefined") return;
4
+
5
+ /* ── Scroll reveal ────────────────────────────
6
+ Any element with class "ac-observe" is hidden by CSS (opacity 0,
7
+ translateY 40px). When it enters the viewport, we add "ac-visible"
8
+ which triggers the CSS transition. Each element fires once. */
9
+ var THRESHOLD = 0.12;
10
+ var revealObs = new IntersectionObserver(function (entries) {
11
+ for (var i = 0; i < entries.length; i++) {
12
+ if (!entries[i].isIntersecting) continue;
13
+ entries[i].target.classList.add("ac-visible");
14
+ revealObs.unobserve(entries[i].target);
15
+ }
16
+ }, { threshold: THRESHOLD });
17
+
18
+ function observeAll() {
19
+ var els = document.querySelectorAll(".ac-observe:not(.ac-visible)");
20
+ for (var i = 0; i < els.length; i++) revealObs.observe(els[i]);
21
+ var dividers = document.querySelectorAll(".ac-divider:not(.ac-visible)");
22
+ for (var d = 0; d < dividers.length; d++) revealObs.observe(dividers[d]);
23
+ }
24
+ observeAll();
25
+ if (typeof MutationObserver !== "undefined") {
26
+ new MutationObserver(observeAll).observe(document.body, { childList: true, subtree: true });
27
+ }
28
+
29
+ /* ── Stagger index ────────────────────────────
30
+ Inside a .ac-stagger container, each child .ac-observe gets a
31
+ CSS custom property --ac-i so the delay cascades. */
32
+ function assignStaggerIndex() {
33
+ var containers = document.querySelectorAll(".ac-stagger");
34
+ for (var c = 0; c < containers.length; c++) {
35
+ var kids = containers[c].querySelectorAll(":scope > .ac-observe, :scope > * > .ac-observe");
36
+ for (var k = 0; k < kids.length; k++) {
37
+ kids[k].style.setProperty("--ac-i", String(k));
38
+ }
39
+ }
40
+ }
41
+ assignStaggerIndex();
42
+
43
+ /* ── Animated count-up ────────────────────────
44
+ Elements with [data-countup] will count from 0 to the target number
45
+ using an ease-out cubic curve when they enter the viewport.
46
+ Usage: <span data-countup="1234" data-countup-suffix="+" data-countup-duration="1400">0</span>
47
+ The element's textContent is replaced as the count progresses. */
48
+ var COUNTUP_DEFAULT_DURATION = 1400;
49
+
50
+ function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
51
+
52
+ function formatNumber(n) {
53
+ if (typeof Intl !== "undefined" && Intl.NumberFormat) {
54
+ return new Intl.NumberFormat().format(n);
55
+ }
56
+ return String(n);
57
+ }
58
+
59
+ function startCountUp(el) {
60
+ if (el.getAttribute("data-countup-started")) return;
61
+ el.setAttribute("data-countup-started", "1");
62
+
63
+ var target = parseFloat(String(el.getAttribute("data-countup") || "0").replace(/,/g, ""));
64
+ if (!isFinite(target) || target <= 0) return;
65
+ var duration = parseInt(el.getAttribute("data-countup-duration") || String(COUNTUP_DEFAULT_DURATION), 10);
66
+ var suffix = el.getAttribute("data-countup-suffix") || "";
67
+ var prefix = el.getAttribute("data-countup-prefix") || "";
68
+ var isFloat = String(el.getAttribute("data-countup") || "").indexOf(".") !== -1;
69
+ var decimals = isFloat ? (String(el.getAttribute("data-countup")).split(".")[1] || "").length : 0;
70
+
71
+ el.classList.add("ac-counter-pop");
72
+
73
+ var shimmer = el.parentElement ? el.parentElement.querySelector(".ac-stat-shimmer") : null;
74
+ if (shimmer) shimmer.classList.add("ac-active");
75
+
76
+ var t0 = performance.now();
77
+ function tick(now) {
78
+ var p = Math.min((now - t0) / duration, 1);
79
+ var eased = easeOutCubic(p);
80
+ var current = isFloat
81
+ ? (target * eased).toFixed(decimals)
82
+ : formatNumber(Math.round(target * eased));
83
+ el.textContent = prefix + current + suffix;
84
+ if (p < 1) {
85
+ requestAnimationFrame(tick);
86
+ } else {
87
+ if (shimmer) shimmer.classList.remove("ac-active");
88
+ }
89
+ }
90
+ requestAnimationFrame(tick);
91
+ }
92
+
93
+ var countObs = new IntersectionObserver(function (entries) {
94
+ for (var i = 0; i < entries.length; i++) {
95
+ if (!entries[i].isIntersecting) continue;
96
+ startCountUp(entries[i].target);
97
+ countObs.unobserve(entries[i].target);
98
+ }
99
+ }, { threshold: 0.3 });
100
+
101
+ var countEls = document.querySelectorAll("[data-countup]");
102
+ for (var i = 0; i < countEls.length; i++) countObs.observe(countEls[i]);
103
+
104
+ /* Re-scan for countup elements added dynamically (e.g. after API data loads) */
105
+ if (typeof MutationObserver !== "undefined") {
106
+ new MutationObserver(function () {
107
+ var els = document.querySelectorAll("[data-countup]:not([data-countup-started])");
108
+ for (var j = 0; j < els.length; j++) countObs.observe(els[j]);
109
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-countup"] });
110
+ }
111
+
112
+ /* Expose a manual trigger for pages that populate stats asynchronously */
113
+ window.acTriggerCountUp = function (el) {
114
+ if (!el || el.getAttribute("data-countup-started")) return;
115
+ countObs.observe(el);
116
+ };
117
+ window.acRescanCountUp = function () {
118
+ var els = document.querySelectorAll("[data-countup]:not([data-countup-started])");
119
+ for (var j = 0; j < els.length; j++) countObs.observe(els[j]);
120
+ };
121
+
122
+ /* ── Ripple click effect ──────────────────────
123
+ Any element with class "ac-ripple" gets a ripple on click. */
124
+ document.addEventListener("click", function (e) {
125
+ var target = e.target.closest(".ac-ripple");
126
+ if (!target) return;
127
+ var rect = target.getBoundingClientRect();
128
+ var circle = document.createElement("span");
129
+ circle.className = "ac-ripple-circle";
130
+ circle.style.left = (e.clientX - rect.left - 10) + "px";
131
+ circle.style.top = (e.clientY - rect.top - 10) + "px";
132
+ target.appendChild(circle);
133
+ setTimeout(function () { circle.remove(); }, 700);
134
+ });
135
+
136
+ /* ── Magnetic tilt on hover ────────────────────
137
+ Elements with .ac-tilt get subtle 3D rotation tracking the cursor. */
138
+ document.addEventListener("mousemove", function (e) {
139
+ var tilts = document.querySelectorAll(".ac-tilt:hover");
140
+ for (var t = 0; t < tilts.length; t++) {
141
+ var el = tilts[t];
142
+ var rect = el.getBoundingClientRect();
143
+ var cx = rect.left + rect.width / 2;
144
+ var cy = rect.top + rect.height / 2;
145
+ var dx = (e.clientX - cx) / (rect.width / 2);
146
+ var dy = (e.clientY - cy) / (rect.height / 2);
147
+ el.style.setProperty("--ac-rx", (dx * 3).toFixed(1) + "deg");
148
+ el.style.setProperty("--ac-ry", (dy * -3).toFixed(1) + "deg");
149
+ }
150
+ });
151
+ document.addEventListener("mouseleave", function () {
152
+ var tilts = document.querySelectorAll(".ac-tilt");
153
+ for (var t = 0; t < tilts.length; t++) {
154
+ tilts[t].style.removeProperty("--ac-rx");
155
+ tilts[t].style.removeProperty("--ac-ry");
156
+ }
157
+ }, true);
158
+
159
+ /* ── Stagger fast variant ──────────────────── */
160
+ function assignFastStagger() {
161
+ var containers = document.querySelectorAll(".ac-stagger-fast");
162
+ for (var c = 0; c < containers.length; c++) {
163
+ var kids = containers[c].querySelectorAll(":scope > .ac-observe, :scope > * > .ac-observe");
164
+ for (var k = 0; k < kids.length; k++) {
165
+ kids[k].style.setProperty("--ac-i", String(k));
166
+ }
167
+ }
168
+ }
169
+ assignFastStagger();
170
+ })();