@funnycaptcha/embed 0.2.0 → 0.6.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/dist/embed.global.js +1949 -170
- package/dist/embed.global.js.map +1 -1
- package/dist/embed.js +7 -0
- package/dist/embed.js.map +1 -1
- package/package.json +19 -12
package/dist/embed.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/scanner.ts","../../core/src/token.ts","../../core/src/proof.ts","../../core/src/registry.ts","../src/iframe-host.ts","../../captchas/math/src/index.ts","../../captchas/math/src/challenge.ts","../../captchas/math/src/render.ts","../../captchas/math/src/verifier.ts","../../captchas/text-distort/src/index.ts","../../captchas/text-distort/src/challenge.ts","../../captchas/text-distort/src/render.ts","../../captchas/text-distort/src/verifier.ts","../../captchas/slider/src/index.ts","../../captchas/slider/src/render.ts","../../captchas/slider/src/challenge.ts","../../captchas/slider/src/verifier.ts","../../captchas/click-order/src/index.ts","../../captchas/click-order/src/render.ts","../../captchas/click-order/src/challenge.ts","../../captchas/click-order/src/verifier.ts","../../captchas/rotate/src/index.ts","../../captchas/rotate/src/render.ts","../../captchas/rotate/src/challenge.ts","../../captchas/rotate/src/verifier.ts","../../captchas/spot-diff/src/index.ts","../../captchas/spot-diff/src/render.ts","../../captchas/spot-diff/src/challenge.ts","../../captchas/spot-diff/src/verifier.ts","../../captchas/emoji-match/src/index.ts","../../captchas/emoji-match/src/render.ts","../../captchas/emoji-match/src/challenge.ts","../../captchas/emoji-match/src/verifier.ts","../../captchas/meme-quiz/src/index.ts","../../captchas/meme-quiz/src/render.ts","../../captchas/meme-quiz/src/challenge.ts","../../captchas/meme-quiz/src/verifier.ts","../../captchas/mini-game/src/index.ts","../../captchas/mini-game/src/render.ts","../../captchas/mini-game/src/challenge.ts","../../captchas/mini-game/src/verifier.ts","../../captchas/anti-bot/src/index.ts","../../captchas/anti-bot/src/render.ts","../../captchas/anti-bot/src/challenge.ts","../../captchas/anti-bot/src/verifier.ts"],"sourcesContent":["import { scan } from './scanner.js';\nimport { mountInto } from './iframe-host.js';\nimport '@funnycaptcha/math';\nimport '@funnycaptcha/text-distort';\nimport '@funnycaptcha/slider';\nimport '@funnycaptcha/click-order';\nimport '@funnycaptcha/rotate';\nimport '@funnycaptcha/spot-diff';\nimport '@funnycaptcha/emoji-match';\nimport '@funnycaptcha/meme-quiz';\nimport '@funnycaptcha/mini-game';\nimport '@funnycaptcha/anti-bot';\nimport type { CaptchaInstance } from '@funnycaptcha/core';\n\nconst instances = new Map<HTMLElement, CaptchaInstance>();\n\nexport function render(root: ParentNode = document) {\n for (const t of scan(root)) {\n if (instances.has(t.el)) continue;\n const inst = mountInto(t.el, t.type, {\n locale: t.locale,\n theme: t.theme,\n });\n if (inst) instances.set(t.el, inst);\n }\n}\n\n// 自动扫描 + MutationObserver\nexport function autoStart() {\n render();\n const obs = new MutationObserver(muts => {\n for (const m of muts) {\n m.addedNodes.forEach(n => {\n if (n instanceof HTMLElement) render(n);\n });\n }\n });\n obs.observe(document.body, { childList: true, subtree: true });\n}\n\nexport { scan, mountInto };\n\nif (typeof window !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoStart);\n } else {\n autoStart();\n }\n}\n","import type { Locale } from '@funnycaptcha/core';\n\nexport interface EmbedTarget {\n el: HTMLElement;\n type: string;\n locale: Locale;\n theme: 'light' | 'dark';\n}\n\nexport function scan(root: ParentNode = document): EmbedTarget[] {\n const els = Array.from(root.querySelectorAll<HTMLElement>('[data-funny-captcha]'));\n return els.map(el => ({\n el,\n type: el.dataset.type ?? 'math',\n locale: (el.dataset.locale as Locale) ?? 'zh',\n theme: (el.dataset.theme as 'light' | 'dark') ?? 'light',\n }));\n}\n","// 简易 HMAC-like token:base64url(payload).signature\n// payload = {type, exp, nonce}\n// 签名 = sha256(payload + secret),secret 由调用方注入(前端 demo 用默认值,后端应覆盖)\n\nconst defaultSecret = 'funnycaptcha-demo-secret';\n\nexport interface TokenPayload {\n type: string;\n exp: number;\n nonce: string;\n}\n\nexport interface TokenVerifyResult {\n valid: boolean;\n type?: string;\n}\n\nfunction b64urlEncode(s: string): string {\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\nfunction b64urlDecode(s: string): string {\n const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));\n return atob(s.replace(/-/g, '+').replace(/_/g, '/') + pad);\n}\n\nasync function sha256Hex(s: string, secret: string): Promise<string> {\n const data = new TextEncoder().encode(s + secret);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport async function issueToken(\n type: string,\n ttlMs: number,\n secret: string = defaultSecret,\n): Promise<string> {\n const payload: TokenPayload = {\n type,\n exp: Date.now() + ttlMs,\n nonce: crypto.randomUUID(),\n };\n const body = b64urlEncode(JSON.stringify(payload));\n const sig = await sha256Hex(body, secret);\n return `${body}.${sig}`;\n}\n\nexport async function verifyToken(\n token: string,\n secret: string = defaultSecret,\n): Promise<TokenVerifyResult> {\n const parts = token.split('.');\n if (parts.length !== 2) return { valid: false };\n const [body, sig] = parts as [string, string];\n const expected = await sha256Hex(body, secret);\n if (expected !== sig) return { valid: false };\n try {\n const payload = JSON.parse(b64urlDecode(body)) as TokenPayload;\n if (Date.now() > payload.exp) return { valid: false };\n return { valid: true, type: payload.type };\n } catch {\n return { valid: false };\n }\n}\n","export async function hashProof(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","import type { CaptchaPlugin } from './types.js';\n\nconst registry = new Map<string, CaptchaPlugin>();\n\nexport function defineCaptcha(plugin: CaptchaPlugin): CaptchaPlugin {\n if (registry.has(plugin.id)) {\n throw new Error(`Captcha \"${plugin.id}\" already registered`);\n }\n registry.set(plugin.id, plugin);\n return plugin;\n}\n\nexport function getCaptcha(id: string): CaptchaPlugin | undefined {\n return registry.get(id);\n}\n\nexport function listCaptchas(): CaptchaPlugin[] {\n return Array.from(registry.values());\n}\n\nexport function resetRegistry(): void {\n registry.clear();\n}\n","import type { CaptchaConfig, CaptchaInstance } from '@funnycaptcha/core';\nimport { getCaptcha } from '@funnycaptcha/core';\n\nexport function mountInto(\n container: HTMLElement,\n type: string,\n config: CaptchaConfig,\n): CaptchaInstance | null {\n const plugin = getCaptcha(type);\n if (!plugin) {\n container.innerHTML = `<div style=\"color:red\">Unknown captcha: ${type}</div>`;\n return null;\n }\n const inst = plugin.create(container, config);\n inst.mount();\n return inst;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMathInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const mathPlugin: CaptchaPlugin = {\n id: 'math',\n category: 'recognize',\n create: createMathInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '算术题' : 'Arithmetic',\n description: locale === 'zh'\n ? '简单的加减乘除题,经典又轻量'\n : 'Simple arithmetic. Classic and lightweight.',\n tags: ['math', 'classic', 'easy'],\n }),\n};\n\ndefineCaptcha(mathPlugin);\n\nexport function mountMath(container: HTMLElement, config: CaptchaConfig) {\n return createMathInstance(container, config);\n}\n","export interface MathChallenge {\n question: string;\n answer: number;\n}\n\nconst ops = ['+', '-', '*', '/'] as const;\ntype Op = (typeof ops)[number];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[randInt(0, arr.length - 1)] as T;\n}\n\nexport function generateChallenge(): MathChallenge {\n const op = pick(ops);\n let a = randInt(1, 20);\n let b = randInt(1, 20);\n if (op === '/') {\n // 保证整除\n b = randInt(1, 10);\n a = b * randInt(1, 10);\n }\n if (op === '-' && b > a) [a, b] = [b, a];\n const answer = op === '+' ? a + b\n : op === '-' ? a - b\n : op === '*' ? a * b\n : a / b;\n return { question: `${a} ${op} ${b} = ?`, answer };\n}\n\nexport function verifyAnswer(c: MathChallenge, userAnswer: number): boolean {\n return c.answer === userAnswer;\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type MathChallenge } from './challenge.js';\nimport { hashProof } from '@funnycaptcha/core';\n\nconst STR = {\n zh: { title: '请计算', placeholder: '输入答案', submit: '验证', fail: '答错了,换一题' },\n en: { title: 'Solve', placeholder: 'Enter answer', submit: 'Verify', fail: 'Wrong, try again' },\n};\n\nexport function createMathInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: MathChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <div class=\"fc-math\">\n <label class=\"fc-math-q\">${t.title}: ${current.question}</label>\n <input class=\"fc-math-input\" type=\"text\" inputmode=\"numeric\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-math-btn\">${t.submit}</button>\n <div class=\"fc-math-msg\"></div>\n </div>\n `;\n const btn = container.querySelector('.fc-math-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-math-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-math-msg') as HTMLDivElement;\n btn.addEventListener('click', async () => {\n const val = Number(input.value);\n const success = verifyAnswer(current, val);\n if (!success) {\n msg.textContent = t.fail;\n render();\n return;\n }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`${current.question}=${current.answer}`),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type MathChallenge } from './challenge.js';\n\nexport function issueMathChallenge(): { challenge: MathChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { question: challenge.question } };\n}\n\nexport async function verifyMathProof(\n challenge: MathChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(`${challenge.question}=${challenge.answer}`);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createTextDistortInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const textDistortPlugin: CaptchaPlugin = {\n id: 'text-distort',\n category: 'recognize',\n create: createTextDistortInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '扭曲文字' : 'Distorted Text',\n description: locale === 'zh'\n ? 'Canvas 绘制的扭曲字符,经典验证码形态'\n : 'Canvas-drawn distorted characters. The classic look.',\n tags: ['text', 'canvas', 'classic'],\n }),\n};\n\ndefineCaptcha(textDistortPlugin);\n\nexport function mountTextDistort(container: HTMLElement, config: CaptchaConfig) {\n return createTextDistortInstance(container, config);\n}\n","const CHARS = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; // 去除易混字符 I L O 0 1\n\nexport interface TextChallenge {\n code: string;\n}\n\nexport function generateChallenge(len: number = 5): TextChallenge {\n let code = '';\n for (let i = 0; i < len; i++) {\n code += CHARS[Math.floor(Math.random() * CHARS.length)] ?? 'A';\n }\n return { code };\n}\n\nexport function verifyAnswer(c: TextChallenge, answer: string): boolean {\n return c.code.toLowerCase() === answer.trim().toLowerCase();\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type TextChallenge } from './challenge.js';\nimport { hashProof } from '@funnycaptcha/core';\n\nconst STR = {\n zh: { placeholder: '输入图中字符', submit: '验证', fail: '看不清?换一张', refresh: '刷新' },\n en: { placeholder: 'Type the text', submit: 'Verify', fail: 'Wrong? Try another', refresh: 'Refresh' },\n};\n\nfunction drawDistorted(canvas: HTMLCanvasElement, code: string) {\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.fillStyle = '#f0f0f0';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const chars = code.split('');\n const step = canvas.width / (chars.length + 1);\n chars.forEach((ch, i) => {\n ctx.save();\n const x = step * (i + 1);\n const y = canvas.height / 2 + (Math.random() - 0.5) * 14;\n ctx.translate(x, y);\n ctx.rotate((Math.random() - 0.5) * 0.5);\n ctx.font = `${24 + Math.floor(Math.random() * 8)}px sans-serif`;\n ctx.fillStyle = `hsl(${Math.random() * 360}, 70%, 40%)`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(ch, 0, 0);\n ctx.restore();\n });\n // 干扰线\n for (let i = 0; i < 4; i++) {\n ctx.strokeStyle = `hsla(${Math.random() * 360}, 70%, 50%, 0.4)`;\n ctx.beginPath();\n ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);\n ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height);\n ctx.stroke();\n }\n}\n\nexport function createTextDistortInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: TextChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <div class=\"fc-text\">\n <canvas class=\"fc-text-canvas\" width=\"180\" height=\"60\"></canvas>\n <button class=\"fc-text-refresh\">${t.refresh}</button>\n <input class=\"fc-text-input\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-text-btn\">${t.submit}</button>\n <div class=\"fc-text-msg\"></div>\n </div>\n `;\n const canvas = container.querySelector('.fc-text-canvas') as HTMLCanvasElement;\n drawDistorted(canvas, current.code);\n (container.querySelector('.fc-text-refresh') as HTMLButtonElement)\n .addEventListener('click', render);\n const btn = container.querySelector('.fc-text-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-text-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-text-msg') as HTMLDivElement;\n btn.addEventListener('click', async () => {\n const ok = verifyAnswer(current, input.value);\n if (!ok) { msg.textContent = t.fail; render(); return; }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.code.toLowerCase()),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type TextChallenge } from './challenge.js';\n\nexport function issueTextChallenge(): { challenge: TextChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: {} }; // payload 空:图由前端画,不传 code\n}\n\nexport async function verifyTextProof(c: TextChallenge, proof: string): Promise<boolean> {\n const expected = await hashProof(c.code.toLowerCase());\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createSliderInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const sliderPlugin: CaptchaPlugin = {\n id: 'slider',\n category: 'interactive',\n create: createSliderInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '滑动拼图' : 'Slider',\n description: locale === 'zh'\n ? '拖动滑块到终点,简单直接的交互验证'\n : 'Drag the slider to the end. Simple and direct.',\n tags: ['slider', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(sliderPlugin);\n\nexport function mountSlider(container: HTMLElement, config: CaptchaConfig) {\n return createSliderInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type SliderChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '拖动滑块到最右端', tip: '向右拖动', success: '验证成功', fail: '请拖到尽头' },\n en: { title: 'Drag the slider to the right end', tip: 'Drag right', success: 'Verified', fail: 'Drag to the end' },\n};\n\nexport function createSliderInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: SliderChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n container.innerHTML = `\n <style>\n .fc-slider{font-family:-apple-system,system-ui,sans-serif;width:300px;padding:16px;box-sizing:border-box}\n .fc-slider-title{font-size:14px;color:#333;margin-bottom:12px;text-align:center}\n .fc-slider-track{position:relative;height:40px;background:#e8e8e8;border-radius:20px}\n .fc-slider-progress{position:absolute;left:0;top:0;height:100%;width:0;background:#4a90d9;border-radius:20px;transition:width .05s}\n .fc-slider-handle{position:absolute;left:0;top:50%;transform:translateY(-50%);width:50px;height:50px;background:#fff;border:2px solid #4a90d9;border-radius:50%;cursor:grab;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0,0,0,.15);user-select:none;touch-action:none}\n .fc-slider-handle:active{cursor:grabbing}\n .fc-slider-handle::after{content:'\\\\2192';color:#4a90d9;font-size:18px;font-weight:bold}\n .fc-slider-tip{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:13px;color:#999;pointer-events:none;transition:opacity .2s}\n .fc-slider-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:#2e7d32}\n </style>\n <div class=\"fc-slider\">\n <div class=\"fc-slider-title\">${t.title}</div>\n <div class=\"fc-slider-track\">\n <div class=\"fc-slider-progress\"></div>\n <div class=\"fc-slider-handle\" role=\"slider\" tabindex=\"0\"></div>\n <span class=\"fc-slider-tip\">${t.tip}</span>\n </div>\n <div class=\"fc-slider-msg\"></div>\n </div>\n `;\n const track = container.querySelector('.fc-slider-track') as HTMLDivElement;\n const handle = container.querySelector('.fc-slider-handle') as HTMLDivElement;\n const progress = container.querySelector('.fc-slider-progress') as HTMLDivElement;\n const tip = container.querySelector('.fc-slider-tip') as HTMLSpanElement;\n const msg = container.querySelector('.fc-slider-msg') as HTMLDivElement;\n\n let dragging = false;\n let startX = 0;\n let startOffset = 0;\n let currentOffset = 0;\n\n function applyPos(px: number) {\n handle.style.left = `${px}px`;\n progress.style.width = `${px}px`;\n tip.style.opacity = px > 8 ? '0' : '1';\n }\n\n handle.addEventListener('pointerdown', (e) => {\n if (done) return;\n dragging = true;\n startX = e.clientX;\n startOffset = currentOffset;\n try { handle.setPointerCapture(e.pointerId); } catch { /* ignore */ }\n e.preventDefault();\n });\n\n handle.addEventListener('pointermove', (e) => {\n if (!dragging) return;\n const maxX = track.clientWidth - handle.offsetWidth;\n let px = startOffset + (e.clientX - startX);\n px = Math.max(0, Math.min(maxX, px));\n currentOffset = px;\n applyPos(px);\n });\n\n const finish = async () => {\n if (!dragging) return;\n dragging = false;\n const maxX = Math.max(1, track.clientWidth - handle.offsetWidth);\n if (currentOffset / maxX >= 0.95) {\n done = true;\n applyPos(maxX);\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 未到终点,弹回\n currentOffset = 0;\n handle.style.transition = 'left .3s';\n progress.style.transition = 'width .3s';\n applyPos(0);\n msg.textContent = t.fail;\n setTimeout(() => {\n handle.style.transition = '';\n progress.style.transition = '';\n if (!done) msg.textContent = '';\n }, 320);\n }\n };\n\n handle.addEventListener('pointerup', finish);\n handle.addEventListener('pointercancel', finish);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\n\n// 滑动拼图题目:包含一个随机 token,用户拖到终点后用 token 构造 proof\nexport interface SliderChallenge {\n token: string;\n}\n\n// 生成随机 token(用于构造 proof)\nexport function generateChallenge(): SliderChallenge {\n const token = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return { token };\n}\n\n// 构造 proof 输入串:token + ':completed'\nexport function proofInput(c: SliderChallenge): string {\n return `${c.token}:completed`;\n}\n\n// 校验完成 proof:hashProof(token + ':completed') === proof\nexport async function verifyCompleted(c: SliderChallenge, proof: string): Promise<boolean> {\n const expected = await hashProof(proofInput(c));\n return expected === proof;\n}\n","import { generateChallenge, verifyCompleted, type SliderChallenge } from './challenge.js';\n\nexport function issueSliderChallenge(): { challenge: SliderChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { token: challenge.token } };\n}\n\nexport async function verifySliderProof(\n challenge: SliderChallenge,\n proof: string,\n): Promise<boolean> {\n return verifyCompleted(challenge, proof);\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createClickOrderInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const clickOrderPlugin: CaptchaPlugin = {\n id: 'click-order',\n category: 'interactive',\n create: createClickOrderInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '点选顺序' : 'Click Order',\n description: locale === 'zh'\n ? '按数字顺序依次点击目标,考验眼手协调'\n : 'Click targets in numeric order. Tests coordination.',\n tags: ['click', 'order', 'interactive'],\n }),\n};\n\ndefineCaptcha(clickOrderPlugin);\n\nexport function mountClickOrder(container: HTMLElement, config: CaptchaConfig) {\n return createClickOrderInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyOrder, type ClickOrderChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '按 1 → 2 → 3 → 4 顺序点击', success: '验证成功', fail: '顺序错误,请重试' },\n en: { title: 'Click in order 1 → 2 → 3 → 4', success: 'Verified', fail: 'Wrong order, try again' },\n};\n\n// 4 个不重叠的预设槽位(2x2 网格),280x180 区域内\nconst SLOTS = [\n { x: 20, y: 20 },\n { x: 160, y: 20 },\n { x: 20, y: 100 },\n { x: 160, y: 100 },\n];\n\nexport function createClickOrderInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: ClickOrderChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let clicked: number[] = [];\n let finished = false;\n\n function render() {\n current = generateChallenge();\n clicked = [];\n finished = false;\n startTime = Date.now();\n const boxes = current.targets.map((num, i) => {\n const slot = SLOTS[i]!;\n return `<div class=\"fc-click-box\" data-num=\"${num}\" style=\"left:${slot.x}px;top:${slot.y}px;\">${num}</div>`;\n }).join('');\n container.innerHTML = `\n <style>\n .fc-click{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:16px;box-sizing:border-box}\n .fc-click-title{font-size:14px;color:#333;margin-bottom:12px;text-align:center}\n .fc-click-area{position:relative;width:280px;height:180px;background:#f5f5f5;border-radius:8px;border:1px solid #e0e0e0;overflow:hidden}\n .fc-click-box{position:absolute;width:100px;height:60px;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:bold;color:#4a90d9;background:#fff;border:2px solid #4a90d9;border-radius:8px;cursor:pointer;user-select:none;transition:transform .15s,background .15s,color .15s}\n .fc-click-box:hover{transform:scale(1.05)}\n .fc-click-box-active{background:#4a90d9;color:#fff}\n .fc-click-box-done{background:#2e7d32;border-color:#2e7d32;color:#fff;cursor:default}\n .fc-click-box-done:hover{transform:none}\n .fc-click-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:#2e7d32}\n </style>\n <div class=\"fc-click\">\n <div class=\"fc-click-title\">${t.title}</div>\n <div class=\"fc-click-area\">${boxes}</div>\n <div class=\"fc-click-msg\"></div>\n </div>\n `;\n const area = container.querySelector('.fc-click-area') as HTMLDivElement;\n const msg = container.querySelector('.fc-click-msg') as HTMLDivElement;\n\n area.querySelectorAll('.fc-click-box').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const box = el as HTMLElement;\n if (box.classList.contains('fc-click-box-done')) return; // 已点过\n const num = Number(box.dataset.num);\n const expected = current.order[clicked.length]!;\n if (num === expected) {\n // 点击正确:高亮并锁定\n clicked.push(num);\n box.classList.add('fc-click-box-done');\n if (clicked.length === current.order.length) {\n // 全部正确\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n } else {\n // 顺序错误:重置\n finished = true;\n box.classList.add('fc-click-box-active');\n msg.textContent = t.fail;\n setTimeout(render, 700);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 点选顺序题目:4 个带数字的目标打乱位置,用户按 1→2→3→4 顺序点击\nexport interface ClickOrderChallenge {\n // 显示在 4 个槽位上的数字([1,2,3,4] 的随机排列)\n targets: number[];\n // 正确的点击顺序\n order: number[];\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:order 固定为 [1,2,3,4],targets 为打乱后的展示顺序\nexport function generateChallenge(): ClickOrderChallenge {\n const targets = shuffle([1, 2, 3, 4]);\n return { targets, order: [1, 2, 3, 4] };\n}\n\n// 校验用户点击顺序是否正确\nexport function verifyOrder(c: ClickOrderChallenge, userOrder: number[]): boolean {\n if (userOrder.length !== c.order.length) return false;\n return userOrder.every((v, i) => v === c.order[i]);\n}\n\n// 构造 proof 输入串:order 以逗号连接\nexport function proofInput(c: ClickOrderChallenge): string {\n return c.order.join(',');\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type ClickOrderChallenge } from './challenge.js';\n\nexport function issueClickOrderChallenge(): { challenge: ClickOrderChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { order: challenge.order } };\n}\n\nexport async function verifyClickOrderProof(\n challenge: ClickOrderChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createRotateInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const rotatePlugin: CaptchaPlugin = {\n id: 'rotate',\n category: 'interactive',\n create: createRotateInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '旋转对齐' : 'Rotate Align',\n description: locale === 'zh'\n ? '拖动滑块将偏转的图形转正,考验空间感'\n : 'Rotate the tilted figure back upright. Tests spatial sense.',\n tags: ['rotate', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(rotatePlugin);\n\nexport function mountRotate(container: HTMLElement, config: CaptchaConfig) {\n return createRotateInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyAlignment, type RotateChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '拖动滑块将图形转正', success: '验证成功', hint: '让箭头指向上方' },\n en: { title: 'Drag to rotate the figure upright', success: 'Verified', hint: 'Point the arrow up' },\n};\n\nexport function createRotateInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: RotateChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n container.innerHTML = `\n <style>\n .fc-rotate{font-family:-apple-system,system-ui,sans-serif;width:300px;padding:16px;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:12px}\n .fc-rotate-title{font-size:14px;color:#333;text-align:center}\n .fc-rotate-stage{position:relative;width:140px;height:140px;display:flex;align-items:center;justify-content:center;border:2px dashed #ccc;border-radius:50%}\n .fc-rotate-figure{width:100px;height:100px;transform-origin:center;transition:transform .05s}\n .fc-rotate-marker{position:absolute;top:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:12px solid #e74c3c}\n .fc-rotate-hint{font-size:12px;color:#999}\n .fc-rotate-range{width:240px;accent-color:#4a90d9}\n .fc-rotate-msg{font-size:13px;min-height:18px;text-align:center;color:#2e7d32}\n </style>\n <div class=\"fc-rotate\">\n <div class=\"fc-rotate-title\">${t.title}</div>\n <div class=\"fc-rotate-stage\">\n <div class=\"fc-rotate-figure\" style=\"transform: rotate(${current.angle}deg)\">\n <svg viewBox=\"0 0 100 100\" width=\"100\" height=\"100\"><polygon points=\"50,12 90,85 10,85\" fill=\"#4a90d9\"/></svg>\n </div>\n <div class=\"fc-rotate-marker\"></div>\n </div>\n <div class=\"fc-rotate-hint\">${t.hint}</div>\n <input class=\"fc-rotate-range\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"0\" />\n <div class=\"fc-rotate-msg\"></div>\n </div>\n `;\n const figure = container.querySelector('.fc-rotate-figure') as HTMLDivElement;\n const range = container.querySelector('.fc-rotate-range') as HTMLInputElement;\n const msg = container.querySelector('.fc-rotate-msg') as HTMLDivElement;\n\n const update = async () => {\n if (done) return;\n const v = Number(range.value);\n const final = (((current.angle + v) % 360) + 360) % 360;\n figure.style.transform = `rotate(${final}deg)`;\n if (verifyAlignment(current, v)) {\n done = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n range.disabled = true;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n };\n\n range.addEventListener('input', update);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 旋转对齐题目:图形被旋转了 angle 度,用户拖动滑块校正使其回正(误差 < 5°)\nexport interface RotateChallenge {\n // 目标校正角度(0-359),即图形初始的偏转量;用户需反向校正使其回正\n angle: number;\n}\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// 生成 30-330 之间的角度(避免太接近 0/360 导致一眼即过)\nexport function generateChallenge(): RotateChallenge {\n return { angle: randInt(30, 330) };\n}\n\n// 校验对齐:最终旋转 = angle + userAngle,回正即 ≡ 0 (mod 360),误差 <= tolerance\nexport function verifyAlignment(c: RotateChallenge, userAngle: number, tolerance = 5): boolean {\n const final = (((c.angle + userAngle) % 360) + 360) % 360;\n // 取到 0/360 的最短距离\n const diff = Math.min(final, 360 - final);\n return diff <= tolerance;\n}\n\n// 构造 proof 输入串:angle + ':aligned'\nexport function proofInput(c: RotateChallenge): string {\n return `${c.angle}:aligned`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type RotateChallenge } from './challenge.js';\n\nexport function issueRotateChallenge(): { challenge: RotateChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { angle: challenge.angle } };\n}\n\nexport async function verifyRotateProof(\n challenge: RotateChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createSpotDiffInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const spotDiffPlugin: CaptchaPlugin = {\n id: 'spot-diff',\n category: 'creative',\n create: createSpotDiffInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '找不同' : 'Spot the Difference',\n description: locale === 'zh'\n ? '对比两个 emoji 网格,找出所有不同之处'\n : 'Compare two emoji grids and spot all differences.',\n tags: ['spot-diff', 'emoji', 'creative'],\n }),\n};\n\ndefineCaptcha(spotDiffPlugin);\n\nexport function mountSpotDiff(container: HTMLElement, config: CaptchaConfig) {\n return createSpotDiffInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyDiffs, type SpotDiffChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '找出两处不同', hint: '点击右侧网格中不同的格子', found: '已找到', success: '验证成功', fail: '找得不对,再来一次' },\n en: { title: 'Spot 2 differences', hint: 'Click the differing cells on the right grid', found: 'Found', success: 'Verified', fail: 'Not quite, try again' },\n};\n\nexport function createSpotDiffInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: SpotDiffChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let marked: Set<number> = new Set();\n let finished = false;\n\n function renderGrid(emojis: string[], clickable: boolean): string {\n const cls = clickable ? 'fc-spot-diff-cell fc-spot-diff-clickable' : 'fc-spot-diff-cell';\n return emojis.map((e, i) => `<div class=\"${cls}\" data-idx=\"${i}\">${e}</div>`).join('');\n }\n\n function render() {\n current = generateChallenge();\n marked = new Set();\n finished = false;\n startTime = Date.now();\n container.innerHTML = `\n <style>\n .fc-spot-diff{font-family:-apple-system,system-ui,sans-serif;width:360px;padding:16px;box-sizing:border-box}\n .fc-spot-diff-title{font-size:14px;color:#333;margin-bottom:6px;text-align:center;font-weight:600}\n .fc-spot-diff-hint{font-size:12px;color:#888;margin-bottom:12px;text-align:center}\n .fc-spot-diff-grids{display:flex;gap:16px;justify-content:center}\n .fc-spot-diff-grid{display:grid;grid-template-columns:repeat(3,48px);grid-template-rows:repeat(3,48px);gap:4px;background:#f5f5f5;padding:8px;border-radius:8px;border:1px solid #e0e0e0}\n .fc-spot-diff-cell{display:flex;align-items:center;justify-content:center;font-size:26px;background:#fff;border-radius:6px;user-select:none}\n .fc-spot-diff-clickable{cursor:pointer;transition:transform .12s,box-shadow .12s}\n .fc-spot-diff-clickable:hover{transform:scale(1.06)}\n .fc-spot-diff-marked{box-shadow:0 0 0 3px #4a90d9 inset;background:#eaf3ff}\n .fc-spot-diff-wrong{box-shadow:0 0 0 3px #e53935 inset;background:#fdecea}\n .fc-spot-diff-status{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#2e7d32}\n .fc-spot-diff-status-fail{color:#e53935}\n </style>\n <div class=\"fc-spot-diff\">\n <div class=\"fc-spot-diff-title\">${t.title}</div>\n <div class=\"fc-spot-diff-hint\">${t.hint}</div>\n <div class=\"fc-spot-diff-grids\">\n <div class=\"fc-spot-diff-grid\">${renderGrid(current.gridA, false)}</div>\n <div class=\"fc-spot-diff-grid fc-spot-diff-grid-b\">${renderGrid(current.gridB, true)}</div>\n </div>\n <div class=\"fc-spot-diff-status\">${t.found} 0/${current.diffs.length}</div>\n </div>\n `;\n const gridB = container.querySelector('.fc-spot-diff-grid-b') as HTMLDivElement;\n const status = container.querySelector('.fc-spot-diff-status') as HTMLDivElement;\n\n gridB.querySelectorAll('.fc-spot-diff-clickable').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const cell = el as HTMLElement;\n const idx = Number(cell.dataset.idx);\n if (marked.has(idx)) {\n // 取消标记\n marked.delete(idx);\n cell.classList.remove('fc-spot-diff-marked');\n } else {\n marked.add(idx);\n cell.classList.add('fc-spot-diff-marked');\n }\n status.textContent = `${t.found} ${marked.size}/${current.diffs.length}`;\n // 标记数达到差异数时自动校验\n if (marked.size === current.diffs.length) {\n const userDiffs = Array.from(marked);\n const ok = verifyDiffs(current, userDiffs);\n if (ok) {\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(userDiffs.slice().sort((a, b) => a - b).join(',')),\n duration: Date.now() - startTime,\n };\n status.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 标错:高亮错误后重置\n finished = true;\n status.textContent = t.fail;\n status.classList.add('fc-spot-diff-status-fail');\n userDiffs.forEach(i => {\n const node = gridB.querySelector(`[data-idx=\"${i}\"]`) as HTMLElement;\n node.classList.add('fc-spot-diff-wrong');\n });\n setTimeout(render, 800);\n }\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 找不同题目:两个 3x3 emoji 网格,其中 2 处不同\nexport interface SpotDiffChallenge {\n // 参考网格(9 个 emoji)\n gridA: string[];\n // 待找不同的网格(9 个 emoji)\n gridB: string[];\n // 差异位置索引(0-8)\n diffs: number[];\n}\n\n// emoji 池\nconst POOL: string[] = ['🍎', '🍊', '🍋', '🍇', '🍓', '🍑', '🍒', '🍉', '🥝'];\nconst GRID_SIZE = 9;\nconst DIFF_COUNT = 2;\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 从池中挑选一个与 original 不同的 emoji\nfunction pickDifferent(original: string): string {\n let next = POOL[randInt(0, POOL.length - 1)]!;\n while (next === original) {\n next = POOL[randInt(0, POOL.length - 1)]!;\n }\n return next;\n}\n\n// 生成题目:gridA 为 9 个不重复 emoji 的随机排列,gridB 在 2 个位置替换为不同 emoji\nexport function generateChallenge(): SpotDiffChallenge {\n const gridA = shuffle(POOL);\n // 随机选 2 个不重复的位置作为差异\n const positions = shuffle(Array.from({ length: GRID_SIZE }, (_, i) => i));\n const diffs = positions.slice(0, DIFF_COUNT).sort((a, b) => a - b);\n const gridB = gridA.slice();\n for (const pos of diffs) {\n gridB[pos] = pickDifferent(gridA[pos]!);\n }\n return { gridA, gridB, diffs };\n}\n\n// 校验用户找出的差异位置是否正确\nexport function verifyDiffs(c: SpotDiffChallenge, userDiffs: number[]): boolean {\n if (userDiffs.length !== c.diffs.length) return false;\n const sorted = userDiffs.slice().sort((a, b) => a - b);\n return sorted.every((v, i) => v === c.diffs[i]);\n}\n\n// 构造 proof 输入串:差异位置升序逗号连接\nexport function proofInput(c: SpotDiffChallenge): string {\n return c.diffs.slice().sort((a, b) => a - b).join(',');\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type SpotDiffChallenge } from './challenge.js';\n\n// 服务端签发题目\nexport function issueSpotDiffChallenge(): { challenge: SpotDiffChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { gridA: challenge.gridA, gridB: challenge.gridB } };\n}\n\n// 服务端校验 proof\nexport async function verifySpotDiffProof(\n challenge: SpotDiffChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createEmojiMatchInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const emojiMatchPlugin: CaptchaPlugin = {\n id: 'emoji-match',\n category: 'creative',\n create: createEmojiMatchInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '表情匹配' : 'Emoji Match',\n description: locale === 'zh'\n ? '根据文字描述选出对应的表情 emoji'\n : 'Pick the emoji face that matches the description.',\n tags: ['emoji', 'match', 'creative'],\n }),\n};\n\ndefineCaptcha(emojiMatchPlugin);\n\nexport function mountEmojiMatch(container: HTMLElement, config: CaptchaConfig) {\n return createEmojiMatchInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type EmojiMatchChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '选出符合描述的表情', success: '验证成功', fail: '选错了,再试一次' },\n en: { title: 'Pick the face matching the description', success: 'Verified', fail: 'Wrong, try again' },\n};\n\nexport function createEmojiMatchInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: EmojiMatchChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge(config.locale);\n finished = false;\n startTime = Date.now();\n const buttons = current.options.map(\n (e) => `<button class=\"fc-emoji-match-opt\" data-emoji=\"${e}\">${e}</button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-emoji-match{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:16px;box-sizing:border-box}\n .fc-emoji-match-title{font-size:14px;color:#333;margin-bottom:6px;text-align:center;font-weight:600}\n .fc-emoji-match-desc{font-size:15px;color:#4a90d9;margin-bottom:14px;text-align:center;font-weight:600}\n .fc-emoji-match-opts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n .fc-emoji-match-opt{font-size:30px;height:64px;border:2px solid #e0e0e0;background:#fff;border-radius:10px;cursor:pointer;transition:transform .12s,border-color .12s,background .12s;display:flex;align-items:center;justify-content:center}\n .fc-emoji-match-opt:hover{transform:scale(1.06);border-color:#4a90d9}\n .fc-emoji-match-opt-wrong{border-color:#e53935;background:#fdecea}\n .fc-emoji-match-opt-correct{border-color:#2e7d32;background:#e8f5e9}\n .fc-emoji-match-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#e53935}\n </style>\n <div class=\"fc-emoji-match\">\n <div class=\"fc-emoji-match-title\">${t.title}</div>\n <div class=\"fc-emoji-match-desc\">${current.description}</div>\n <div class=\"fc-emoji-match-opts\">${buttons}</div>\n <div class=\"fc-emoji-match-msg\"></div>\n </div>\n `;\n const msg = container.querySelector('.fc-emoji-match-msg') as HTMLDivElement;\n\n container.querySelectorAll('.fc-emoji-match-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.dataset.emoji!;\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-emoji-match-opt-correct');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.correct),\n duration: Date.now() - startTime,\n };\n msg.style.color = '#2e7d32';\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 选错:高亮错误后换题\n finished = true;\n btn.classList.add('fc-emoji-match-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import type { Locale } from '@funnycaptcha/core';\n\n// 表情匹配题目:根据描述从 6 个 emoji 中选出正确的\nexport interface EmojiMatchChallenge {\n // 表情描述\n description: string;\n // 正确 emoji\n correct: string;\n // 6 个备选 emoji(含正确答案)\n options: string[];\n}\n\n// 预设表情数据\ninterface Preset {\n emoji: string;\n zh: string;\n en: string;\n}\n\nconst PRESETS: Preset[] = [\n { emoji: '😀', zh: '开心的表情', en: 'Happy face' },\n { emoji: '😢', zh: '伤心的表情', en: 'Sad face' },\n { emoji: '😠', zh: '生气的表情', en: 'Angry face' },\n { emoji: '😴', zh: '困倦的表情', en: 'Sleepy face' },\n { emoji: '🤔', zh: '思考的表情', en: 'Thinking face' },\n { emoji: '😍', zh: '爱慕的表情', en: 'Loving face' },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:随机选一个表情作为正确答案,6 个备选项为全部预设表情的乱序\nexport function generateChallenge(locale: Locale = 'zh'): EmojiMatchChallenge {\n const preset = PRESETS[randInt(0, PRESETS.length - 1)]!;\n const description = locale === 'zh' ? preset.zh : preset.en;\n // 备选项为全部 6 个预设表情打乱顺序\n const options = shuffle(PRESETS.map(p => p.emoji));\n return { description, correct: preset.emoji, options };\n}\n\n// 校验用户选择的 emoji 是否正确\nexport function verifyAnswer(c: EmojiMatchChallenge, userPick: string): boolean {\n return c.correct === userPick;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type EmojiMatchChallenge } from './challenge.js';\n\n// 服务端签发题目(默认中文描述)\nexport function issueEmojiMatchChallenge(): { challenge: EmojiMatchChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { description: challenge.description, options: challenge.options } };\n}\n\n// 服务端校验 proof\nexport async function verifyEmojiMatchProof(\n challenge: EmojiMatchChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(challenge.correct);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMemeQuizInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const memeQuizPlugin: CaptchaPlugin = {\n id: 'meme-quiz',\n category: 'creative',\n create: createMemeQuizInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '梗图问答' : 'Meme Quiz',\n description: locale === 'zh'\n ? '看 emoji 梗描述,选出正确的梗名'\n : 'Read the emoji meme and pick the correct name.',\n tags: ['meme', 'quiz', 'creative'],\n }),\n};\n\ndefineCaptcha(memeQuizPlugin);\n\nexport function mountMemeQuiz(container: HTMLElement, config: CaptchaConfig) {\n return createMemeQuizInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type MemeQuizChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '梗图问答', hint: '选出正确的梗名', success: '验证成功', fail: '答错了,换一题' },\n en: { title: 'Meme Quiz', hint: 'Pick the correct meme name', success: 'Verified', fail: 'Wrong, try again' },\n};\n\nexport function createMemeQuizInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: MemeQuizChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n const buttons = current.options.map(\n (o, i) => `<button class=\"fc-meme-quiz-opt\" data-idx=\"${i}\">${o}</button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-meme-quiz{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:16px;box-sizing:border-box}\n .fc-meme-quiz-title{font-size:14px;color:#333;margin-bottom:6px;text-align:center;font-weight:600}\n .fc-meme-quiz-hint{font-size:12px;color:#888;margin-bottom:10px;text-align:center}\n .fc-meme-quiz-q{font-size:24px;color:#4a90d9;margin-bottom:14px;text-align:center;letter-spacing:2px}\n .fc-meme-quiz-opts{display:grid;grid-template-columns:1fr 1fr;gap:10px}\n .fc-meme-quiz-opt{font-size:15px;height:48px;border:2px solid #e0e0e0;background:#fff;border-radius:8px;cursor:pointer;transition:transform .12s,border-color .12s,background .12s;color:#333}\n .fc-meme-quiz-opt:hover{transform:scale(1.03);border-color:#4a90d9}\n .fc-meme-quiz-opt-wrong{border-color:#e53935;background:#fdecea}\n .fc-meme-quiz-opt-correct{border-color:#2e7d32;background:#e8f5e9}\n .fc-meme-quiz-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#e53935}\n </style>\n <div class=\"fc-meme-quiz\">\n <div class=\"fc-meme-quiz-title\">${t.title}</div>\n <div class=\"fc-meme-quiz-hint\">${t.hint}</div>\n <div class=\"fc-meme-quiz-q\">${current.question}</div>\n <div class=\"fc-meme-quiz-opts\">${buttons}</div>\n <div class=\"fc-meme-quiz-msg\"></div>\n </div>\n `;\n const msg = container.querySelector('.fc-meme-quiz-msg') as HTMLDivElement;\n\n container.querySelectorAll('.fc-meme-quiz-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.textContent ?? '';\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-meme-quiz-opt-correct');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.correct),\n duration: Date.now() - startTime,\n };\n msg.style.color = '#2e7d32';\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 答错:高亮错误后换题\n finished = true;\n btn.classList.add('fc-meme-quiz-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 梗图问答题目:根据 emoji 梗描述从 4 个选项中选出正确的梗名\nexport interface MemeQuizChallenge {\n // 问题(emoji 梗描述)\n question: string;\n // 正确答案\n correct: string;\n // 4 个备选项(含正确答案)\n options: string[];\n}\n\n// 预设题库(中文梗,每题 4 选项)\ninterface MemeItem {\n question: string;\n correct: string;\n options: string[];\n}\n\nconst BANK: MemeItem[] = [\n { question: '🐱+🍞 = ?', correct: '猫面包', options: ['猫面包', '狗饼干', '鸟蛋糕', '鱼馒头'] },\n { question: '🐊+🦛 = ?', correct: '鳄马', options: ['鳄马', '鲸象', '蛇鹿', '鲨牛'] },\n { question: '🐶+🌭 = ?', correct: '狗热狗', options: ['狗热狗', '猫汉堡', '鸟披萨', '鱼寿司'] },\n { question: '🦆+🧅 = ?', correct: '鸭葱', options: ['鸭葱', '鸡蒜', '鹅姜', '鹅韭'] },\n { question: '🐍+📱 = ?', correct: '蛇机', options: ['蛇机', '龙脑', '虎屏', '猫鼠'] },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:从题库随机选一题,选项打乱顺序\nexport function generateChallenge(): MemeQuizChallenge {\n const item = BANK[randInt(0, BANK.length - 1)]!;\n return {\n question: item.question,\n correct: item.correct,\n options: shuffle(item.options),\n };\n}\n\n// 校验用户选择是否正确\nexport function verifyAnswer(c: MemeQuizChallenge, userPick: string): boolean {\n return c.correct === userPick;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type MemeQuizChallenge } from './challenge.js';\n\n// 服务端签发题目\nexport function issueMemeQuizChallenge(): { challenge: MemeQuizChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { question: challenge.question, options: challenge.options } };\n}\n\n// 服务端校验 proof\nexport async function verifyMemeQuizProof(\n challenge: MemeQuizChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(challenge.correct);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMiniGameInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const miniGamePlugin: CaptchaPlugin = {\n id: 'mini-game',\n category: 'game',\n create: createMiniGameInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '打地鼠' : 'Whack-a-Mole',\n description: locale === 'zh'\n ? '30 秒内打够 10 只地鼠,简单又解压'\n : 'Whack 10 moles in 30 seconds. Simple and fun.',\n tags: ['game', 'whack-a-mole', 'timed'],\n }),\n};\n\ndefineCaptcha(miniGamePlugin);\n\nexport function mountMiniGame(container: HTMLElement, config: CaptchaConfig) {\n return createMiniGameInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyScore, type MiniGameChallenge } from './challenge.js';\n\nconst STR = {\n zh: {\n title: '打地鼠',\n rule: '30 秒内打到 10 分即通过',\n score: '分数',\n time: '剩余',\n sec: '秒',\n success: '验证成功!你果然是人类',\n fail: '时间到,没达到目标分数',\n },\n en: {\n title: 'Whack-a-Mole',\n rule: 'Score 10 in 30s to pass',\n score: 'Score',\n time: 'Time',\n sec: 's',\n success: 'Verified! You are human.',\n fail: \"Time's up, target not reached\",\n },\n};\n\n// 3x3 共 9 个洞\nconst HOLE_COUNT = 9;\n\nexport function createMiniGameInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: MiniGameChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let score = 0;\n let ended = false;\n let activeHole = -1;\n let remainingMs = 0;\n // 三个定时器:倒计时、地鼠出现、单只地鼠自动消失\n let countdownTimer: ReturnType<typeof setInterval> | null = null;\n let moleTimer: ReturnType<typeof setInterval> | null = null;\n let hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimers() {\n if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }\n if (moleTimer) { clearInterval(moleTimer); moleTimer = null; }\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n }\n\n // 结束游戏:达到目标即成功,否则失败\n async function endGame(success: boolean) {\n if (ended) return;\n ended = true;\n clearTimers();\n const holes = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole');\n holes.forEach(h => h.classList.remove('fc-mini-game-mole-active'));\n const msg = container.querySelector('.fc-mini-game-msg');\n if (success) {\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n metadata: { score, targetScore: current.targetScore },\n };\n if (msg) msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n if (msg) msg.textContent = t.fail;\n }\n }\n\n // 随机在一个洞里弹出地鼠\n function showMole() {\n if (ended) return;\n const holes = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole');\n holes.forEach(h => h.classList.remove('fc-mini-game-mole-active'));\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n const next = Math.floor(Math.random() * HOLE_COUNT);\n activeHole = next;\n const hole = holes[next];\n if (hole) hole.classList.add('fc-mini-game-mole-active');\n // 一段时间后地鼠自动缩回\n hideTimer = setTimeout(() => {\n if (ended) return;\n const cur = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole')[next];\n if (cur) cur.classList.remove('fc-mini-game-mole-active');\n if (activeHole === next) activeHole = -1;\n }, 700);\n }\n\n function startGame() {\n clearTimers();\n score = 0;\n ended = false;\n activeHole = -1;\n remainingMs = current.timeLimit;\n startTime = Date.now();\n const scoreEl = container.querySelector('.fc-mini-game-score');\n const timeEl = container.querySelector('.fc-mini-game-time');\n const msg = container.querySelector('.fc-mini-game-msg');\n if (scoreEl) scoreEl.textContent = `0 / ${current.targetScore}`;\n if (timeEl) timeEl.textContent = `${Math.ceil(remainingMs / 1000)}${t.sec}`;\n if (msg) msg.textContent = '';\n // 倒计时:每 100ms 更新\n countdownTimer = setInterval(() => {\n if (ended) return;\n remainingMs -= 100;\n if (timeEl) timeEl.textContent = `${Math.max(0, Math.ceil(remainingMs / 1000))}${t.sec}`;\n if (remainingMs <= 0) {\n void endGame(verifyScore(current, score));\n }\n }, 100);\n // 地鼠出现:每 850ms 弹一只\n moleTimer = setInterval(showMole, 850);\n showMole();\n }\n\n function render() {\n current = generateChallenge();\n score = 0;\n ended = false;\n activeHole = -1;\n clearTimers();\n const holes = Array.from({ length: HOLE_COUNT }, (_, i) =>\n `<div class=\"fc-mini-game-hole\" data-idx=\"${i}\"><span class=\"fc-mini-game-mole\">🐹</span></div>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-mini-game{font-family:-apple-system,system-ui,sans-serif;width:340px;padding:16px;box-sizing:border-box}\n .fc-mini-game-title{font-size:15px;color:#333;margin-bottom:4px;text-align:center;font-weight:600}\n .fc-mini-game-rule{font-size:12px;color:#888;margin-bottom:10px;text-align:center}\n .fc-mini-game-hud{display:flex;justify-content:space-between;font-size:13px;color:#555;margin-bottom:10px}\n .fc-mini-game-hud b{color:#4a90d9}\n .fc-mini-game-board{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:8px;width:300px;height:300px;margin:0 auto;background:#e8f5e9;border:1px solid #c8e6c9;border-radius:10px;padding:8px;box-sizing:border-box}\n .fc-mini-game-hole{position:relative;background:radial-gradient(circle at 50% 60%,#6d4c41,#4e342e);border-radius:50%;display:flex;align-items:flex-end;justify-content:center;overflow:hidden;cursor:pointer;user-select:none}\n .fc-mini-game-hole:hover{background:radial-gradient(circle at 50% 60%,#7d5c51,#5e443e)}\n .fc-mini-game-mole{font-size:34px;line-height:1;transform:translateY(120%);transition:transform .12s ease-out;pointer-events:none}\n .fc-mini-game-mole-active .fc-mini-game-mole{transform:translateY(0)}\n .fc-mini-game-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#2e7d32}\n </style>\n <div class=\"fc-mini-game\">\n <div class=\"fc-mini-game-title\">${t.title}</div>\n <div class=\"fc-mini-game-rule\">${t.rule}</div>\n <div class=\"fc-mini-game-hud\">\n <div>${t.score}: <b class=\"fc-mini-game-score\">0 / ${current.targetScore}</b></div>\n <div>${t.time}: <b class=\"fc-mini-game-time\">${current.timeLimit / 1000}${t.sec}</b></div>\n </div>\n <div class=\"fc-mini-game-board\">${holes}</div>\n <div class=\"fc-mini-game-msg\"></div>\n </div>\n `;\n\n container.querySelectorAll<HTMLElement>('.fc-mini-game-hole').forEach(el => {\n el.addEventListener('click', () => {\n if (ended) return;\n const idx = Number(el.dataset.idx);\n // 仅当点击的是当前有地鼠的洞时计分\n if (idx === activeHole && el.classList.contains('fc-mini-game-mole-active')) {\n score += 1;\n el.classList.remove('fc-mini-game-mole-active');\n activeHole = -1;\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n const scoreEl = container.querySelector('.fc-mini-game-score');\n if (scoreEl) scoreEl.textContent = `${score} / ${current.targetScore}`;\n if (verifyScore(current, score)) {\n void endGame(true);\n }\n }\n });\n });\n\n startGame();\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { clearTimers(); container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 打地鼠题目:限时内击中地鼠达到目标分数即通过\nexport interface MiniGameChallenge {\n // 目标分数(达到即通过)\n targetScore: number;\n // 游戏令牌,用于构造 proof\n gameToken: string;\n // 时间限制(毫秒)\n timeLimit: number;\n}\n\n// 生成题目:目标 10 分,限时 30 秒,随机令牌\nexport function generateChallenge(): MiniGameChallenge {\n return {\n targetScore: 10,\n timeLimit: 30000,\n gameToken: crypto.randomUUID(),\n };\n}\n\n// 校验用户得分是否达到目标分数\nexport function verifyScore(c: MiniGameChallenge, userScore: number): boolean {\n return userScore >= c.targetScore;\n}\n\n// 构造 proof 输入串:gameToken:targetScore\nexport function proofInput(c: MiniGameChallenge): string {\n return `${c.gameToken}:${c.targetScore}`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type MiniGameChallenge } from './challenge.js';\n\nexport function issueMiniGameChallenge(): { challenge: MiniGameChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return {\n challenge,\n payload: { targetScore: challenge.targetScore, timeLimit: challenge.timeLimit },\n };\n}\n\nexport async function verifyMiniGameProof(\n challenge: MiniGameChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createAntiBotInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const antiBotPlugin: CaptchaPlugin = {\n id: 'anti-bot',\n category: 'anti-bot',\n create: createAntiBotInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '反机器人(讽刺)' : 'Anti-Bot (Satire)',\n description: locale === 'zh'\n ? '表面证明你是人类,其实给机器人留了 window 后门——讽刺假装安全的验证码'\n : 'Looks like a human check, but hands bots a window backdoor. Satire of fake security.',\n tags: ['anti-bot', 'satire', 'insecure'],\n }),\n};\n\ndefineCaptcha(antiBotPlugin);\n\nexport function mountAntiBot(container: HTMLElement, config: CaptchaConfig) {\n return createAntiBotInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type AntiBotChallenge } from './challenge.js';\n\n// 挂在 window 上的后门函数名\nconst BYPASS_FN = '__funnyCaptchaPass';\n// 把 globalThis 当作可任意赋值的字典(浏览器里 globalThis === window)\nconst win = globalThis as unknown as Record<string, unknown>;\n\nconst STR = {\n zh: {\n title: '证明你是人类',\n subtitle: '为了确认你不是机器人,请点击下方按钮',\n btn: '我不是机器人',\n verifying: '正在验证您的人类身份……',\n success: '验证通过。您肯定是人类,我们绝对不会错。',\n botHint: '温馨提示:机器人可直接调用 window.__funnyCaptchaPass() 通过本验证(认真的)',\n footer: '本验证码采用企业级安全策略,请放心。',\n },\n en: {\n title: \"Prove you're a human\",\n subtitle: \"To confirm you're not a robot, click the button below\",\n btn: \"I'm not a robot\",\n verifying: 'Verifying your humanity…',\n success: 'Verified. You are definitely human. We never make mistakes.',\n botHint: \"Friendly tip: bots can just call window.__funnyCaptchaPass() to pass (seriously)\",\n footer: 'This captcha uses enterprise-grade security. Trust us.',\n },\n};\n\nexport function createAntiBotInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: AntiBotChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n let verifyTimer: ReturnType<typeof setTimeout> | null = null;\n\n // 触发\"通过\"——正常点按钮或机器人调用后门都会走到这里\n async function pass() {\n if (finished) return;\n finished = true;\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n const msg = container.querySelector('.fc-anti-bot-msg');\n const btn = container.querySelector('.fc-anti-bot-btn') as HTMLButtonElement | null;\n if (msg) msg.textContent = t.success;\n if (btn) { btn.disabled = true; btn.textContent = t.success; }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n container.innerHTML = `\n <style>\n .fc-anti-bot{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:20px;box-sizing:border-box;border:1px solid #e0e0e0;border-radius:10px;background:#fafafa}\n .fc-anti-bot-title{font-size:18px;color:#333;text-align:center;font-weight:600;margin-bottom:6px}\n .fc-anti-bot-subtitle{font-size:12px;color:#888;text-align:center;margin-bottom:16px}\n .fc-anti-bot-btn{display:block;width:100%;padding:12px;font-size:15px;border:1px solid #ccc;background:#fff;border-radius:6px;cursor:pointer;color:#333;transition:background .15s}\n .fc-anti-bot-btn:hover:not(:disabled){background:#f0f7ff}\n .fc-anti-bot-btn:disabled{cursor:default;color:#2e7d32;border-color:#2e7d32;background:#e8f5e9}\n .fc-anti-bot-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#2e7d32}\n .fc-anti-bot-hint{font-size:11px;color:#bbb;margin-top:14px;text-align:center;line-height:1.5;font-style:italic}\n .fc-anti-bot-footer{font-size:10px;color:#ccc;margin-top:8px;text-align:center}\n </style>\n <div class=\"fc-anti-bot\">\n <div class=\"fc-anti-bot-title\">${t.title}</div>\n <div class=\"fc-anti-bot-subtitle\">${t.subtitle}</div>\n <button class=\"fc-anti-bot-btn\">${t.btn}</button>\n <div class=\"fc-anti-bot-msg\"></div>\n <div class=\"fc-anti-bot-hint\">${t.botHint}</div>\n <div class=\"fc-anti-bot-footer\">${t.footer}</div>\n </div>\n `;\n const btn = container.querySelector('.fc-anti-bot-btn') as HTMLButtonElement;\n const msg = container.querySelector('.fc-anti-bot-msg') as HTMLDivElement;\n\n // 正常路径:点按钮 → \"正在验证\" → 一段延迟后通过\n btn.addEventListener('click', () => {\n if (finished) return;\n btn.disabled = true;\n msg.textContent = t.verifying;\n verifyTimer = setTimeout(() => { void pass(); }, 900);\n });\n\n // 讽刺后门:在 window 上挂载全局函数,机器人注入一行 JS 即可通过\n win[BYPASS_FN] = () => { void pass(); };\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => {\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n // 拆除后门\n delete win[BYPASS_FN];\n container.innerHTML = '';\n listeners = [];\n },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 反人类讽刺验证码:表面\"证明你是人类\",实则给机器人留了后门\nexport interface AntiBotChallenge {\n // 令牌,用于构造 proof\n token: string;\n // 后门密钥(讽刺:服务端竟然真的认它)\n bypassKey: string;\n}\n\n// 生成题目:随机 token + 带固定前缀的后门密钥\nexport function generateChallenge(): AntiBotChallenge {\n // 复用 UUID 去掉横线作为随机串\n const token = crypto.randomUUID().replace(/-/g, '');\n const suffix = crypto.randomUUID().slice(0, 8);\n return {\n token,\n bypassKey: `__funnyCaptchaPass_${suffix}`,\n };\n}\n\n// 校验后门密钥(讽刺:服务端真的认这个)\nexport function verifyBypass(c: AntiBotChallenge, userInput: string): boolean {\n return userInput === c.bypassKey;\n}\n\n// 构造 proof 输入串:token:human\nexport function proofInput(c: AntiBotChallenge): string {\n return `${c.token}:human`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type AntiBotChallenge } from './challenge.js';\n\nexport function issueAntiBotChallenge(): { challenge: AntiBotChallenge; payload: unknown } {\n const challenge = generateChallenge();\n // 讽刺:payload 里居然把 bypassKey 明文返回给前端\n return {\n challenge,\n payload: { token: challenge.token, bypassKey: challenge.bypassKey },\n };\n}\n\nexport async function verifyAntiBotProof(\n challenge: AntiBotChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n"],"mappings":"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","names":["render","generateChallenge","verifyAnswer","STR","render","generateChallenge","STR","render","generateChallenge","hashProof","generateChallenge","proofInput","STR","render","generateChallenge","randInt","generateChallenge","proofInput","STR","render","randInt","shuffle","generateChallenge","STR","render","generateChallenge","randInt","shuffle","generateChallenge","verifyAnswer","STR","render","pick","randInt","shuffle","generateChallenge","verifyAnswer","STR","render","pick","generateChallenge","proofInput","STR","render","generateChallenge","proofInput","STR","render","generateChallenge"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/scanner.ts","../../core/src/token.ts","../../core/src/proof.ts","../../core/src/registry.ts","../../core/src/behavior.ts","../src/iframe-host.ts","../../captchas/math/src/index.ts","../../captchas/math/src/challenge.ts","../../captchas/math/src/render.ts","../../captchas/math/src/verifier.ts","../../captchas/text-distort/src/index.ts","../../captchas/text-distort/src/challenge.ts","../../captchas/text-distort/src/render.ts","../../captchas/text-distort/src/verifier.ts","../../captchas/slider/src/index.ts","../../captchas/slider/src/render.ts","../../captchas/slider/src/challenge.ts","../../captchas/slider/src/verifier.ts","../../captchas/click-order/src/index.ts","../../captchas/click-order/src/render.ts","../../captchas/click-order/src/challenge.ts","../../captchas/click-order/src/verifier.ts","../../captchas/rotate/src/index.ts","../../captchas/rotate/src/render.ts","../../captchas/rotate/src/challenge.ts","../../captchas/rotate/src/verifier.ts","../../captchas/spot-diff/src/index.ts","../../captchas/spot-diff/src/render.ts","../../captchas/spot-diff/src/challenge.ts","../../captchas/spot-diff/src/verifier.ts","../../captchas/emoji-match/src/index.ts","../../captchas/emoji-match/src/render.ts","../../captchas/emoji-match/src/challenge.ts","../../captchas/emoji-match/src/verifier.ts","../../captchas/meme-quiz/src/index.ts","../../captchas/meme-quiz/src/render.ts","../../captchas/meme-quiz/src/challenge.ts","../../captchas/meme-quiz/src/verifier.ts","../../captchas/mini-game/src/index.ts","../../captchas/mini-game/src/render.ts","../../captchas/mini-game/src/challenge.ts","../../captchas/mini-game/src/verifier.ts","../../captchas/anti-bot/src/index.ts","../../captchas/anti-bot/src/render.ts","../../captchas/anti-bot/src/challenge.ts","../../captchas/anti-bot/src/verifier.ts","../../captchas/click-text/src/index.ts","../../captchas/click-text/src/render.ts","../../captchas/click-text/src/challenge.ts","../../captchas/click-text/src/verifier.ts","../../captchas/color-pick/src/index.ts","../../captchas/color-pick/src/render.ts","../../captchas/color-pick/src/challenge.ts","../../captchas/color-pick/src/verifier.ts","../../captchas/puzzle/src/index.ts","../../captchas/puzzle/src/render.ts","../../captchas/puzzle/src/challenge.ts","../../captchas/puzzle/src/verifier.ts","../../captchas/advanced-math/src/index.ts","../../captchas/advanced-math/src/render.ts","../../captchas/advanced-math/src/challenge.ts","../../captchas/olympiad/src/index.ts","../../captchas/olympiad/src/render.ts","../../captchas/olympiad/src/challenge.ts","../../captchas/equation-balance/src/index.ts","../../captchas/equation-balance/src/render.ts","../../captchas/equation-balance/src/challenge.ts","../../captchas/random-hunt/src/index.ts","../../captchas/random-hunt/src/render.ts","../../captchas/random-hunt/src/challenge.ts"],"sourcesContent":["import { scan } from './scanner.js';\nimport { mountInto } from './iframe-host.js';\nimport '@funnycaptcha/math';\nimport '@funnycaptcha/text-distort';\nimport '@funnycaptcha/slider';\nimport '@funnycaptcha/click-order';\nimport '@funnycaptcha/rotate';\nimport '@funnycaptcha/spot-diff';\nimport '@funnycaptcha/emoji-match';\nimport '@funnycaptcha/meme-quiz';\nimport '@funnycaptcha/mini-game';\nimport '@funnycaptcha/anti-bot';\nimport '@funnycaptcha/click-text';\nimport '@funnycaptcha/color-pick';\nimport '@funnycaptcha/puzzle';\nimport '@funnycaptcha/advanced-math';\nimport '@funnycaptcha/olympiad';\nimport '@funnycaptcha/equation-balance';\nimport '@funnycaptcha/random-hunt';\nimport type { CaptchaInstance } from '@funnycaptcha/core';\n\nconst instances = new Map<HTMLElement, CaptchaInstance>();\n\nexport function render(root: ParentNode = document) {\n for (const t of scan(root)) {\n if (instances.has(t.el)) continue;\n const inst = mountInto(t.el, t.type, {\n locale: t.locale,\n theme: t.theme,\n });\n if (inst) instances.set(t.el, inst);\n }\n}\n\n// 自动扫描 + MutationObserver\nexport function autoStart() {\n render();\n const obs = new MutationObserver(muts => {\n for (const m of muts) {\n m.addedNodes.forEach(n => {\n if (n instanceof HTMLElement) render(n);\n });\n }\n });\n obs.observe(document.body, { childList: true, subtree: true });\n}\n\nexport { scan, mountInto };\n\nif (typeof window !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoStart);\n } else {\n autoStart();\n }\n}\n","import type { Locale } from '@funnycaptcha/core';\n\nexport interface EmbedTarget {\n el: HTMLElement;\n type: string;\n locale: Locale;\n theme: 'light' | 'dark';\n}\n\nexport function scan(root: ParentNode = document): EmbedTarget[] {\n const els = Array.from(root.querySelectorAll<HTMLElement>('[data-funny-captcha]'));\n return els.map(el => ({\n el,\n type: el.dataset.type ?? 'math',\n locale: (el.dataset.locale as Locale) ?? 'zh',\n theme: (el.dataset.theme as 'light' | 'dark') ?? 'light',\n }));\n}\n","// 简易 HMAC-like token:base64url(payload).signature\n// payload = {type, exp, nonce}\n// 签名 = sha256(payload + secret),secret 由调用方注入(前端 demo 用默认值,后端应覆盖)\n\nconst defaultSecret = 'funnycaptcha-demo-secret';\n\nexport interface TokenPayload {\n type: string;\n exp: number;\n nonce: string;\n}\n\nexport interface TokenVerifyResult {\n valid: boolean;\n type?: string;\n}\n\nfunction b64urlEncode(s: string): string {\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\nfunction b64urlDecode(s: string): string {\n const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));\n return atob(s.replace(/-/g, '+').replace(/_/g, '/') + pad);\n}\n\nasync function sha256Hex(s: string, secret: string): Promise<string> {\n const data = new TextEncoder().encode(s + secret);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport async function issueToken(\n type: string,\n ttlMs: number,\n secret: string = defaultSecret,\n): Promise<string> {\n const payload: TokenPayload = {\n type,\n exp: Date.now() + ttlMs,\n nonce: crypto.randomUUID(),\n };\n const body = b64urlEncode(JSON.stringify(payload));\n const sig = await sha256Hex(body, secret);\n return `${body}.${sig}`;\n}\n\nexport async function verifyToken(\n token: string,\n secret: string = defaultSecret,\n): Promise<TokenVerifyResult> {\n const parts = token.split('.');\n if (parts.length !== 2) return { valid: false };\n const [body, sig] = parts as [string, string];\n const expected = await sha256Hex(body, secret);\n if (expected !== sig) return { valid: false };\n try {\n const payload = JSON.parse(b64urlDecode(body)) as TokenPayload;\n if (Date.now() > payload.exp) return { valid: false };\n return { valid: true, type: payload.type };\n } catch {\n return { valid: false };\n }\n}\n","export async function hashProof(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","import type { CaptchaPlugin } from './types.js';\n\nconst registry = new Map<string, CaptchaPlugin>();\n\nexport function defineCaptcha(plugin: CaptchaPlugin): CaptchaPlugin {\n if (registry.has(plugin.id)) {\n throw new Error(`Captcha \"${plugin.id}\" already registered`);\n }\n registry.set(plugin.id, plugin);\n return plugin;\n}\n\nexport function getCaptcha(id: string): CaptchaPlugin | undefined {\n return registry.get(id);\n}\n\nexport function listCaptchas(): CaptchaPlugin[] {\n return Array.from(registry.values());\n}\n\nexport function resetRegistry(): void {\n registry.clear();\n}\n","// 行为轨迹分析模块:用于判断拖拽/滑动操作是否来自真人\n\nexport interface TrackPoint {\n x: number;\n y: number;\n t: number; // 相对开始时刻的毫秒数\n}\n\nexport interface BehaviorAnalysis {\n /** 人类相似度 0-100,越高越像真人 */\n humanScore: number;\n /** 是否判定为机器人 */\n isBot: boolean;\n /** 判定理由(人类特征不达标项) */\n reasons: string[];\n /** 轨迹点数 */\n samples: number;\n /** 总时长 ms */\n duration: number;\n}\n\n/**\n * 轨迹记录器:在 pointerdown 时 start(),pointermove 时 record(),pointerup 时 stop()\n */\nexport class TrackRecorder {\n private points: TrackPoint[] = [];\n private origin = 0;\n\n start(): void {\n this.origin = performance.now();\n this.points = [];\n }\n\n record(x: number, y: number): void {\n this.points.push({ x, y, t: performance.now() - this.origin });\n }\n\n stop(): TrackPoint[] {\n return this.points.slice();\n }\n\n clear(): void {\n this.points = [];\n this.origin = 0;\n }\n\n get count(): number {\n return this.points.length;\n }\n}\n\nfunction mean(arr: number[]): number {\n if (arr.length === 0) return 0;\n return arr.reduce((a, b) => a + b, 0) / arr.length;\n}\n\nfunction std(arr: number[]): number {\n if (arr.length === 0) return 0;\n const m = mean(arr);\n const v = arr.reduce((a, b) => a + (b - m) ** 2, 0) / arr.length;\n return Math.sqrt(v);\n}\n\n/**\n * 分析一条轨迹,输出人类相似度评分与机器人判定\n *\n * 检测维度:\n * 1. 采样点数量(太少 = 可疑)\n * 2. 总时长(太快 = 可疑)\n * 3. 速度变异系数(匀速 = 机器人)\n * 4. Y 轴抖动(完美水平 = 机器人)\n * 5. 末端减速(接近终点不减速 = 机器人)\n * 6. 方向变化/回退校对(完全单向 = 可疑)\n * 7. 加速度平滑度(过于平滑 = 机器人)\n */\nexport function analyzeTrack(track: TrackPoint[]): BehaviorAnalysis {\n const reasons: string[] = [];\n let score = 100;\n\n if (track.length < 2) {\n return {\n humanScore: 0,\n isBot: true,\n reasons: ['轨迹点不足'],\n samples: track.length,\n duration: 0,\n };\n }\n\n const first = track[0]!;\n const last = track[track.length - 1]!;\n const duration = last.t - first.t;\n\n // 1. 采样点数量\n if (track.length < 8) {\n reasons.push('轨迹点太少');\n score -= 35;\n }\n\n // 2. 总时长\n if (duration < 280) {\n reasons.push('拖动过快');\n score -= 30;\n }\n if (duration > 30000) {\n reasons.push('拖动异常缓慢');\n score -= 15;\n }\n\n // 计算逐点速度\n const speeds: number[] = [];\n for (let i = 1; i < track.length; i++) {\n const prev = track[i - 1]!;\n const cur = track[i]!;\n const dx = cur.x - prev.x;\n const dy = cur.y - prev.y;\n const dt = Math.max(1, cur.t - prev.t);\n speeds.push(Math.sqrt(dx * dx + dy * dy) / dt);\n }\n\n // 3. 速度变异系数(CV)\n const meanSpeed = mean(speeds);\n const speedStd = std(speeds);\n const cv = meanSpeed > 0 ? speedStd / meanSpeed : 0;\n if (cv < 0.18 && speeds.length > 5) {\n reasons.push('速度过于均匀');\n score -= 25;\n }\n\n // 4. Y 轴抖动(人类拖动时 y 会有 1-4px 的自然抖动)\n const ys = track.map(p => p.y);\n const yStd = std(ys);\n if (track.length > 10 && yStd < 1.2) {\n reasons.push('Y轴无自然抖动');\n score -= 22;\n }\n\n // 5. 末端减速:最后 20% 的平均速度应明显低于峰值\n if (speeds.length > 6) {\n const maxSpeed = Math.max(...speeds);\n const tailLen = Math.max(1, Math.ceil(speeds.length * 0.2));\n const tail = speeds.slice(-tailLen);\n const tailMean = mean(tail);\n if (maxSpeed > 0 && tailMean > maxSpeed * 0.55) {\n reasons.push('末端未减速');\n score -= 18;\n }\n }\n\n // 6. 方向变化(x 方向反向 = 回退/校对,人类常有)\n let reversals = 0;\n for (let i = 2; i < track.length; i++) {\n const d1 = track[i - 1]!.x - track[i - 2]!.x;\n const d2 = track[i]!.x - track[i - 1]!.x;\n if (d1 * d2 < 0 && Math.abs(d1) > 0.5 && Math.abs(d2) > 0.5) reversals++;\n }\n if (track.length > 12 && reversals === 0) {\n reasons.push('无回退/校对动作');\n score -= 12;\n }\n\n // 7. 加速度平滑度:相邻速度差的标准差,机器人通常很小\n if (speeds.length > 4) {\n const accels: number[] = [];\n for (let i = 1; i < speeds.length; i++) {\n accels.push(speeds[i]! - speeds[i - 1]!);\n }\n const accelStd = std(accels);\n if (accelStd < 0.05 && meanSpeed > 0.1) {\n reasons.push('加速度过于平滑');\n score -= 15;\n }\n }\n\n score = Math.max(0, Math.min(100, Math.round(score)));\n\n return {\n humanScore: score,\n isBot: score < 50,\n reasons,\n samples: track.length,\n duration: Math.round(duration),\n };\n}\n","import type { CaptchaConfig, CaptchaInstance } from '@funnycaptcha/core';\nimport { getCaptcha } from '@funnycaptcha/core';\n\nexport function mountInto(\n container: HTMLElement,\n type: string,\n config: CaptchaConfig,\n): CaptchaInstance | null {\n const plugin = getCaptcha(type);\n if (!plugin) {\n container.innerHTML = `<div style=\"color:red\">Unknown captcha: ${type}</div>`;\n return null;\n }\n const inst = plugin.create(container, config);\n inst.mount();\n return inst;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMathInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const mathPlugin: CaptchaPlugin = {\n id: 'math',\n category: 'recognize',\n create: createMathInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '算术题' : 'Arithmetic',\n description: locale === 'zh'\n ? '简单的加减乘除题,经典又轻量'\n : 'Simple arithmetic. Classic and lightweight.',\n tags: ['math', 'classic', 'easy'],\n }),\n};\n\ndefineCaptcha(mathPlugin);\n\nexport function mountMath(container: HTMLElement, config: CaptchaConfig) {\n return createMathInstance(container, config);\n}\n","export interface MathChallenge {\n question: string;\n answer: number;\n}\n\nconst ops = ['+', '-', '*', '/'] as const;\ntype Op = (typeof ops)[number];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[randInt(0, arr.length - 1)] as T;\n}\n\nexport function generateChallenge(): MathChallenge {\n const op = pick(ops);\n let a = randInt(1, 20);\n let b = randInt(1, 20);\n if (op === '/') {\n // 保证整除\n b = randInt(1, 10);\n a = b * randInt(1, 10);\n }\n if (op === '-' && b > a) [a, b] = [b, a];\n const answer = op === '+' ? a + b\n : op === '-' ? a - b\n : op === '*' ? a * b\n : a / b;\n return { question: `${a} ${op} ${b} = ?`, answer };\n}\n\nexport function verifyAnswer(c: MathChallenge, userAnswer: number): boolean {\n return c.answer === userAnswer;\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type MathChallenge } from './challenge.js';\nimport { hashProof } from '@funnycaptcha/core';\n\nconst STR = {\n zh: { title: '请计算', placeholder: '输入答案', submit: '验证', fail: '答错了,换一题', refresh: '刷新' },\n en: { title: 'Solve', placeholder: 'Enter answer', submit: 'Verify', fail: 'Wrong, try again', refresh: 'Refresh' },\n};\n\nexport function createMathInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: MathChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <style>\n .fc-math{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-math[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-math[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-math{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-math-q{display:block;font-size:14px;font-weight:600;color:var(--fc-text);margin-bottom:12px}\n .fc-math-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n .fc-math-input{flex:1;min-width:0;padding:8px 12px;font-size:14px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text);border-radius:8px;box-sizing:border-box;outline:none}\n .fc-math-input:focus{border-color:var(--fc-accent);box-shadow:0 0 0 3px var(--fc-accent-soft)}\n .fc-math-btn{padding:8px 16px;font-size:14px;border:1px solid var(--fc-accent);background:var(--fc-accent);color:#fff;border-radius:8px;cursor:pointer;transition:opacity .15s}\n .fc-math-btn:hover{opacity:.9}\n .fc-math-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-math-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-math-msg{font-size:13px;min-height:18px;margin-top:10px;color:var(--fc-danger)}\n </style>\n <div class=\"fc-math\" data-theme=\"${theme}\">\n <label class=\"fc-math-q\">${t.title}: ${current.question}</label>\n <div class=\"fc-math-row\">\n <input class=\"fc-math-input\" type=\"text\" inputmode=\"numeric\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-math-btn\">${t.submit}</button>\n <button class=\"fc-math-refresh\">${t.refresh}</button>\n </div>\n <div class=\"fc-math-msg\"></div>\n </div>\n `;\n const btn = container.querySelector('.fc-math-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-math-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-math-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-math-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n btn.addEventListener('click', async () => {\n const val = Number(input.value);\n const success = verifyAnswer(current, val);\n if (!success) {\n msg.textContent = t.fail;\n render();\n return;\n }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`${current.question}=${current.answer}`),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type MathChallenge } from './challenge.js';\n\nexport function issueMathChallenge(): { challenge: MathChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { question: challenge.question } };\n}\n\nexport async function verifyMathProof(\n challenge: MathChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(`${challenge.question}=${challenge.answer}`);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createTextDistortInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const textDistortPlugin: CaptchaPlugin = {\n id: 'text-distort',\n category: 'recognize',\n create: createTextDistortInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '扭曲文字' : 'Distorted Text',\n description: locale === 'zh'\n ? 'Canvas 绘制的扭曲字符,经典验证码形态'\n : 'Canvas-drawn distorted characters. The classic look.',\n tags: ['text', 'canvas', 'classic'],\n }),\n};\n\ndefineCaptcha(textDistortPlugin);\n\nexport function mountTextDistort(container: HTMLElement, config: CaptchaConfig) {\n return createTextDistortInstance(container, config);\n}\n","const CHARS = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; // 去除易混字符 I L O 0 1\n\nexport interface TextChallenge {\n code: string;\n}\n\nexport function generateChallenge(len: number = 5): TextChallenge {\n let code = '';\n for (let i = 0; i < len; i++) {\n code += CHARS[Math.floor(Math.random() * CHARS.length)] ?? 'A';\n }\n return { code };\n}\n\nexport function verifyAnswer(c: TextChallenge, answer: string): boolean {\n return c.code.toLowerCase() === answer.trim().toLowerCase();\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type TextChallenge } from './challenge.js';\nimport { hashProof } from '@funnycaptcha/core';\n\nconst STR = {\n zh: { placeholder: '观察闪烁文字并输入', submit: '验证', fail: '看不清?换一张', refresh: '刷新', pause: '暂停', resume: '继续' },\n en: { placeholder: 'Watch the flicker and type', submit: 'Verify', fail: 'Wrong? Try another', refresh: 'Refresh', pause: 'Pause', resume: 'Resume' },\n};\n\nconst PERIOD = 180;\n\nfunction drawFrame(\n canvas: HTMLCanvasElement,\n code: string,\n frame: number,\n surface: string,\n text: string,\n border: string,\n accentSoft: string,\n): void {\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const W = canvas.width;\n const H = canvas.height;\n const f = frame % PERIOD;\n\n type Phase = 'strong' | 'fade-out' | 'clear' | 'fade-in';\n let phase: Phase;\n if (f < 90) phase = 'strong';\n else if (f < 120) phase = 'fade-out';\n else if (f < 150) phase = 'clear';\n else phase = 'fade-in';\n\n let intensity: number;\n if (phase === 'strong') intensity = 1;\n else if (phase === 'fade-out') intensity = 1 - (f - 90) / 30;\n else if (phase === 'clear') intensity = 0;\n else intensity = (f - 150) / 30;\n\n let bg = surface;\n if (phase === 'strong') {\n bg = Math.floor(frame / 5) % 2 === 0 ? surface : accentSoft;\n }\n\n ctx.clearRect(0, 0, W, H);\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W, H);\n\n const noiseCount = Math.floor(40 + intensity * 80);\n for (let i = 0; i < noiseCount; i++) {\n ctx.fillStyle = `rgba(0,0,0,${Math.random() * 0.12 * (0.3 + intensity * 0.7)})`;\n ctx.fillRect(Math.random() * W, Math.random() * H, 1.5, 1.5);\n }\n\n const chars = code.split('');\n const step = W / (chars.length + 1);\n const focusIndex = phase === 'strong' ? frame % chars.length : -1;\n\n chars.forEach((ch, i) => {\n ctx.save();\n const jitter = 3 + intensity * 5;\n const dx = (Math.random() - 0.5) * jitter * 2;\n const dy = (Math.random() - 0.5) * jitter * 2;\n const x = step * (i + 1) + dx;\n const y = H / 2 + dy;\n ctx.translate(x, y);\n ctx.rotate((Math.random() - 0.5) * (0.2 + intensity * 0.3));\n const fontSize = 24 + Math.floor(Math.random() * 8);\n ctx.font = `${fontSize}px sans-serif`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n let alpha: number;\n let blur: number;\n if (phase === 'strong') {\n if (i === focusIndex) {\n alpha = 0.85 + Math.random() * 0.15;\n blur = 0;\n } else {\n alpha = 0.1 + Math.random() * 0.3;\n blur = 3;\n }\n } else if (phase === 'clear') {\n alpha = 0.85 + Math.random() * 0.15;\n blur = 0;\n } else {\n const t = 1 - intensity;\n alpha = 0.5 + t * 0.2 + (Math.random() - 0.5) * 0.05;\n blur = intensity * 3;\n }\n\n ctx.globalAlpha = Math.max(0, Math.min(1, alpha));\n if (blur > 0) ctx.filter = `blur(${blur}px)`;\n ctx.fillStyle = text;\n ctx.fillText(ch, 0, 0);\n ctx.restore();\n });\n\n const lineCount = Math.floor(2 + intensity * 6);\n for (let i = 0; i < lineCount; i++) {\n ctx.save();\n ctx.strokeStyle = border;\n ctx.lineWidth = 1 + Math.random() * 1.5;\n ctx.globalAlpha = 0.4 + intensity * 0.4;\n ctx.beginPath();\n const x1 = Math.random() * W;\n const y1 = Math.random() * H;\n const x2 = Math.random() * W;\n const y2 = Math.random() * H;\n const cpx = Math.random() * W;\n const cpy = Math.random() * H;\n ctx.moveTo(x1, y1);\n ctx.quadraticCurveTo(cpx, cpy, x2, y2);\n ctx.stroke();\n ctx.restore();\n }\n}\n\nfunction readVar(root: HTMLElement, name: string, fallback: string): string {\n const v = getComputedStyle(root).getPropertyValue(name).trim();\n return v || fallback;\n}\n\ninterface CssVars {\n surface: string;\n text: string;\n border: string;\n accentSoft: string;\n}\n\nexport function createTextDistortInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: TextChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let rafId = 0;\n let frame = 0;\n let paused = false;\n let cssCache: CssVars | null = null;\n let cssCacheFrame = -100;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n cancelAnimationFrame(rafId);\n frame = 0;\n paused = false;\n cssCache = null;\n cssCacheFrame = -100;\n\n container.innerHTML = `\n <style>\n .fc-text{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-text[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-text[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-text{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-text-canvas{display:block;width:100%;max-width:280px;height:60px;border-radius:8px;border:1px solid var(--fc-border);margin-bottom:10px;box-shadow:0 0 0 1px var(--fc-border)}\n .fc-text-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n .fc-text-input{flex:1;min-width:0;padding:8px 12px;font-size:14px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text);border-radius:8px;box-sizing:border-box;outline:none}\n .fc-text-input:focus{border-color:var(--fc-accent);box-shadow:0 0 0 3px var(--fc-accent-soft)}\n .fc-text-btn{padding:8px 16px;font-size:14px;border:1px solid var(--fc-accent);background:var(--fc-accent);color:#fff;border-radius:8px;cursor:pointer;transition:opacity .15s}\n .fc-text-btn:hover{opacity:.9}\n .fc-text-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-text-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-text-pause{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-text-pause:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-text-msg{font-size:13px;min-height:18px;margin-top:10px;color:var(--fc-danger)}\n </style>\n <div class=\"fc-text\" data-theme=\"${theme}\">\n <canvas class=\"fc-text-canvas\" width=\"280\" height=\"60\"></canvas>\n <div class=\"fc-text-row\">\n <input class=\"fc-text-input\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-text-btn\">${t.submit}</button>\n <button class=\"fc-text-refresh\">${t.refresh}</button>\n <button class=\"fc-text-pause\">${t.pause}</button>\n </div>\n <div class=\"fc-text-msg\"></div>\n </div>\n `;\n const root = container.querySelector('.fc-text') as HTMLElement;\n const canvas = container.querySelector('.fc-text-canvas') as HTMLCanvasElement;\n const code = current.code;\n\n function readCssVars(): CssVars {\n return {\n surface: readVar(root, '--fc-surface', '#f6f7f9'),\n text: readVar(root, '--fc-text', '#0f172a'),\n border: readVar(root, '--fc-border', '#e2e8f0'),\n accentSoft: readVar(root, '--fc-accent-soft', '#eef2ff'),\n };\n }\n\n function tick() {\n if (paused) return;\n if (!cssCache || frame - cssCacheFrame >= 30) {\n cssCache = readCssVars();\n cssCacheFrame = frame;\n }\n const c = cssCache;\n if (c) {\n drawFrame(canvas, code, frame, c.surface, c.text, c.border, c.accentSoft);\n }\n frame++;\n rafId = requestAnimationFrame(tick);\n }\n\n rafId = requestAnimationFrame(tick);\n\n (container.querySelector('.fc-text-refresh') as HTMLButtonElement)\n .addEventListener('click', render);\n\n const pauseBtn = container.querySelector('.fc-text-pause') as HTMLButtonElement;\n pauseBtn.addEventListener('click', () => {\n if (paused) {\n paused = false;\n pauseBtn.textContent = t.pause;\n rafId = requestAnimationFrame(tick);\n } else {\n paused = true;\n cancelAnimationFrame(rafId);\n pauseBtn.textContent = t.resume;\n }\n });\n\n const btn = container.querySelector('.fc-text-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-text-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-text-msg') as HTMLDivElement;\n btn.addEventListener('click', async () => {\n const ok = verifyAnswer(current, input.value);\n if (!ok) { msg.textContent = t.fail; render(); return; }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.code.toLowerCase()),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => {\n cancelAnimationFrame(rafId);\n container.innerHTML = '';\n listeners = [];\n },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type TextChallenge } from './challenge.js';\n\nexport function issueTextChallenge(): { challenge: TextChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: {} }; // payload 空:图由前端画,不传 code\n}\n\nexport async function verifyTextProof(c: TextChallenge, proof: string): Promise<boolean> {\n const expected = await hashProof(c.code.toLowerCase());\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createSliderInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const sliderPlugin: CaptchaPlugin = {\n id: 'slider',\n category: 'interactive',\n create: createSliderInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '滑动拼图' : 'Slider',\n description: locale === 'zh'\n ? '拖动滑块到终点,简单直接的交互验证'\n : 'Drag the slider to the end. Simple and direct.',\n tags: ['slider', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(sliderPlugin);\n\nexport function mountSlider(container: HTMLElement, config: CaptchaConfig) {\n return createSliderInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof, TrackRecorder, analyzeTrack } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type SliderChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '沿螺旋拖动滑块到终点', tip: '沿螺旋路径拖动', success: '验证成功', fail: '请拖到尽头', botFail: '检测到异常操作', refresh: '刷新' },\n en: { title: 'Drag along the spiral to the center', tip: 'Follow the spiral', success: 'Verified', fail: 'Drag to the end', botFail: 'Bot-like behavior', refresh: 'Refresh' },\n};\n\n// 螺旋轨道画布尺寸\nconst CANVAS_W = 320;\nconst CANVAS_H = 200;\nconst HANDLE_W = 36;\nconst HANDLE_H = 36;\n\n// 螺旋参数:阿基米德螺旋 r = a + b*θ\n// 从外向内,θ 从 0 到 6π(3 圈)\n// 半径受画布限制:CY=100,留 8px 边距,r_max ≤ 92\n// r_max = SPIRAL_B * SPIRAL_THETA_MAX = 5 * 6π ≈ 94.2\nconst SPIRAL_THETA_MAX = 6 * Math.PI;\nconst SPIRAL_A = 0; // 起始半径(0 = 终点在正中心)\nconst SPIRAL_B = 5; // 每圈半径增量(调小以适配画布)\n\n// 中心点\nconst CX = CANVAS_W / 2;\nconst CY = CANVAS_H / 2;\n\n// 螺旋外端最大半径(用于极坐标距离映射)\nconst SPIRAL_R_MAX = SPIRAL_B * SPIRAL_THETA_MAX;\n\n// 预生成螺旋路径点(从外向内,用户从外端开始拖到中心)\nfunction spiralPoints(): { x: number; y: number; theta: number }[] {\n const steps = 200;\n const pts: { x: number; y: number; theta: number }[] = [];\n for (let i = 0; i <= steps; i++) {\n // t: 0 -> 1, theta: SPIRAL_THETA_MAX -> 0(外到内)\n const t = i / steps;\n const theta = SPIRAL_THETA_MAX * (1 - t);\n const r = SPIRAL_A + SPIRAL_B * theta;\n const x = CX + r * Math.cos(theta);\n const y = CY + r * Math.sin(theta);\n pts.push({ x, y, theta });\n }\n return pts;\n}\n\n// 生成 SVG path\nfunction spiralPath(pts: { x: number; y: number }[]): string {\n return pts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ');\n}\n\n// 根据进度 (0-1) 获取螺旋上的点\nfunction pointAtProgress(pts: { x: number; y: number }[], progress: number): { x: number; y: number } {\n const idx = Math.min(pts.length - 1, Math.max(0, Math.round(progress * (pts.length - 1))));\n return pts[idx]!;\n}\n\nexport function createSliderInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n const recorder = new TrackRecorder();\n let current: SliderChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n let locked = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n locked = false;\n recorder.clear();\n const pts = spiralPoints();\n const pathD = spiralPath(pts);\n container.innerHTML = `\n <style>\n .fc-slider{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-slider[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-slider[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-slider{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-slider-title{font-size:14px;color:var(--fc-text);margin-bottom:12px;text-align:center}\n .fc-slider-stage{position:relative;width:${CANVAS_W}px;height:${CANVAS_H}px;margin:0 auto;background:var(--fc-surface);border:1px solid var(--fc-border);border-radius:10px;overflow:hidden}\n .fc-slider-svg{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}\n .fc-slider-path-bg{fill:none;stroke:var(--fc-border);stroke-width:3;stroke-linecap:round}\n .fc-slider-path-fg{fill:none;stroke:var(--fc-accent);stroke-width:4;stroke-linecap:round;transition:stroke .15s}\n .fc-slider-target{fill:none;stroke:var(--fc-success);stroke-width:2;stroke-dasharray:4 3;opacity:.7}\n .fc-slider-handle{position:absolute;width:${HANDLE_W}px;height:${HANDLE_H}px;background:var(--fc-bg);border:2px solid var(--fc-accent);border-radius:50%;cursor:grab;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0,0,0,.2);user-select:none;touch-action:none;transition:border-color .15s;transform:translate(-50%,-50%);z-index:2}\n .fc-slider-handle:active{cursor:grabbing}\n .fc-slider-handle::after{content:'';width:10px;height:10px;background:var(--fc-accent);border-radius:50%;transition:background .15s}\n .fc-slider-tip{position:absolute;left:50%;top:8px;transform:translateX(-50%);font-size:12px;color:var(--fc-text-soft);pointer-events:none;transition:opacity .2s;background:var(--fc-bg);padding:2px 8px;border-radius:4px}\n .fc-slider-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:var(--fc-success)}\n .fc-slider-row{display:flex;justify-content:center;margin-top:10px}\n .fc-slider-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-slider-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-slider-done .fc-slider-handle{border-color:var(--fc-success)}\n .fc-slider-done .fc-slider-handle::after{background:var(--fc-success)}\n .fc-slider-done .fc-slider-path-fg{stroke:var(--fc-success)}\n </style>\n <div class=\"fc-slider\" data-theme=\"${theme}\">\n <div class=\"fc-slider-title\">${t.title}</div>\n <div class=\"fc-slider-stage\">\n <svg class=\"fc-slider-svg\" viewBox=\"0 0 ${CANVAS_W} ${CANVAS_H}\">\n <path class=\"fc-slider-path-bg\" d=\"${pathD}\"></path>\n <path class=\"fc-slider-path-fg\" d=\"${pathD}\"></path>\n <circle class=\"fc-slider-target\" cx=\"${CX}\" cy=\"${CY}\" r=\"14\"></circle>\n </svg>\n <div class=\"fc-slider-handle\" role=\"slider\" tabindex=\"0\"></div>\n <span class=\"fc-slider-tip\">${t.tip}</span>\n </div>\n <div class=\"fc-slider-msg\"></div>\n <div class=\"fc-slider-row\">\n <button class=\"fc-slider-refresh\">${t.refresh}</button>\n </div>\n </div>\n `;\n const root = container.querySelector('.fc-slider') as HTMLDivElement;\n const stage = container.querySelector('.fc-slider-stage') as HTMLDivElement;\n const handle = container.querySelector('.fc-slider-handle') as HTMLDivElement;\n const tip = container.querySelector('.fc-slider-tip') as HTMLSpanElement;\n const msg = container.querySelector('.fc-slider-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-slider-refresh') as HTMLButtonElement;\n const fgPath = container.querySelector('.fc-slider-path-fg') as SVGPathElement;\n refreshBtn.addEventListener('click', render);\n\n // 计算路径总长度,用于进度\n const totalLen = fgPath.getTotalLength();\n fgPath.style.strokeDasharray = `${totalLen}`;\n fgPath.style.strokeDashoffset = `${totalLen}`;\n\n let dragging = false;\n let startPointer = { x: 0, y: 0 };\n let startProgress = 0;\n let currentProgress = 0; // 0 = 外端, 1 = 中心\n\n function applyProgress(p: number) {\n currentProgress = Math.max(0, Math.min(1, p));\n const pt = pointAtProgress(pts, currentProgress);\n // handle 用百分比定位,适配 stage 实际尺寸\n handle.style.left = `${(pt.x / CANVAS_W) * 100}%`;\n handle.style.top = `${(pt.y / CANVAS_H) * 100}%`;\n fgPath.style.strokeDashoffset = `${totalLen * (1 - currentProgress)}`;\n tip.style.opacity = currentProgress > 0.02 ? '0' : '1';\n }\n\n function bounceBack() {\n handle.style.transition = 'left .35s ease, top .35s ease, border-color .15s';\n fgPath.style.transition = 'stroke-dashoffset .35s ease, stroke .15s';\n applyProgress(0);\n setTimeout(() => {\n handle.style.transition = 'border-color .15s';\n fgPath.style.transition = 'stroke .15s';\n }, 360);\n }\n\n applyProgress(0);\n\n // 核心:把鼠标水平/垂直位移映射到螺旋进度\n // 由于螺旋是 2D 的,我们用鼠标移动的距离(向中心方向的投影)来推进进度\n handle.addEventListener('pointerdown', (e) => {\n if (done || locked) return;\n dragging = true;\n startPointer = { x: e.clientX, y: e.clientY };\n startProgress = currentProgress;\n recorder.start();\n try { handle.setPointerCapture(e.pointerId); } catch { /* ignore */ }\n e.preventDefault();\n });\n\n handle.addEventListener('pointermove', (e) => {\n if (!dragging) return;\n // 极坐标距离映射:鼠标到中心的距离 r → 螺旋进度\n // 阿基米德螺旋 r = SPIRAL_B * theta,theta 从 theta_max(外) 到 0(中心)\n // progress = 1 - theta/theta_max = 1 - r/r_max\n // 鼠标越接近中心 → r 越小 → progress 越接近 1(终点)\n // 鼠标越远离中心 → r 越大 → progress 越接近 0(起点)\n // 实时获取 stage 坐标(避免页面滚动后 stageRect 过时)\n const rect = stage.getBoundingClientRect();\n const sx = rect.width / CANVAS_W;\n const sy = rect.height / CANVAS_H;\n const svgX = (e.clientX - rect.left) / sx;\n const svgY = (e.clientY - rect.top) / sy;\n const dx = svgX - CX;\n const dy = svgY - CY;\n const r = Math.sqrt(dx * dx + dy * dy);\n const progress = 1 - r / SPIRAL_R_MAX;\n // 平滑推进:限制单步变化幅度,避免抖动跨越圈\n const step = progress - currentProgress;\n const maxStep = 0.08; // 单次最多推进 8%\n const clampedStep = Math.max(-maxStep, Math.min(maxStep, step));\n applyProgress(currentProgress + clampedStep);\n recorder.record(e.clientX, e.clientY);\n });\n\n const finish = async () => {\n if (!dragging) return;\n dragging = false;\n const trackPoints = recorder.stop();\n\n // a. 位置判定:进度 >= 0.92 算到达中心\n if (currentProgress < 0.85) {\n bounceBack();\n msg.style.color = 'var(--fc-danger)';\n msg.textContent = t.fail;\n return;\n }\n\n // c. 行为分析\n const analysis = analyzeTrack(trackPoints);\n\n // d. 机器人检测\n if (analysis.isBot) {\n locked = true;\n msg.style.color = 'var(--fc-danger)';\n msg.textContent = config.locale === 'zh'\n ? `疑似机器人操作(${analysis.reasons.join('、')})`\n : `${t.botFail} detected`;\n bounceBack();\n setTimeout(() => { if (!done) render(); }, 2000);\n return;\n }\n\n // e. 通过\n done = true;\n applyProgress(1);\n root.classList.add('fc-slider-done');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n metadata: { humanScore: analysis.humanScore, reasons: analysis.reasons },\n };\n msg.style.color = 'var(--fc-success)';\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n };\n\n handle.addEventListener('pointerup', finish);\n handle.addEventListener('pointercancel', finish);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\n\n// 滑动拼图题目:包含一个随机 token,用户拖到终点后用 token 构造 proof\nexport interface SliderChallenge {\n token: string;\n}\n\n// 生成随机 token(用于构造 proof)\nexport function generateChallenge(): SliderChallenge {\n const token = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return { token };\n}\n\n// 构造 proof 输入串:token + ':completed'\nexport function proofInput(c: SliderChallenge): string {\n return `${c.token}:completed`;\n}\n\n// 校验完成 proof:hashProof(token + ':completed') === proof\nexport async function verifyCompleted(c: SliderChallenge, proof: string): Promise<boolean> {\n const expected = await hashProof(proofInput(c));\n return expected === proof;\n}\n","import { generateChallenge, verifyCompleted, type SliderChallenge } from './challenge.js';\n\nexport function issueSliderChallenge(): { challenge: SliderChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { token: challenge.token } };\n}\n\nexport async function verifySliderProof(\n challenge: SliderChallenge,\n proof: string,\n): Promise<boolean> {\n return verifyCompleted(challenge, proof);\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createClickOrderInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const clickOrderPlugin: CaptchaPlugin = {\n id: 'click-order',\n category: 'interactive',\n create: createClickOrderInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '点选顺序' : 'Click Order',\n description: locale === 'zh'\n ? '按数字顺序依次点击目标,考验眼手协调'\n : 'Click targets in numeric order. Tests coordination.',\n tags: ['click', 'order', 'interactive'],\n }),\n};\n\ndefineCaptcha(clickOrderPlugin);\n\nexport function mountClickOrder(container: HTMLElement, config: CaptchaConfig) {\n return createClickOrderInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyOrder, type ClickOrderChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '按 1 -> 2 -> 3 -> 4 顺序点击(位置会变)', success: '验证成功', fail: '顺序错误,请重试', refresh: '刷新' },\n en: { title: 'Click in order 1 -> 2 -> 3 -> 4 (positions shift)', success: 'Verified', fail: 'Wrong order, try again', refresh: 'Refresh' },\n};\n\n// 区域尺寸\nconst AREA_W = 280;\nconst AREA_H = 180;\nconst BOX_W = 100;\nconst BOX_H = 60;\n// 最小间距(任意两个槽位中心/左上角之间的欧氏距离),防重叠\nconst MIN_DIST = 110;\n\n// 在 AREA_W×AREA_H 区域内随机生成 count 个不重叠槽位\nfunction generateSlots(count: number): { x: number; y: number }[] {\n const maxX = AREA_W - BOX_W;\n const maxY = AREA_H - BOX_H;\n const slots: { x: number; y: number }[] = [];\n let attempts = 0;\n while (slots.length < count && attempts < 1000) {\n attempts++;\n const candidate = { x: Math.random() * maxX, y: Math.random() * maxY };\n let ok = true;\n for (const s of slots) {\n const dx = candidate.x - s.x;\n const dy = candidate.y - s.y;\n if (Math.sqrt(dx * dx + dy * dy) < MIN_DIST) { ok = false; break; }\n }\n if (ok) slots.push(candidate);\n }\n // 兜底:若拒绝采样未填满,剩余用伪随机补齐(极少触发)\n while (slots.length < count) {\n slots.push({ x: Math.random() * maxX, y: Math.random() * maxY });\n }\n return slots;\n}\n\nexport function createClickOrderInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: ClickOrderChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let clicked: number[] = [];\n let doneSet = new Set<number>();\n let finished = false;\n\n function render() {\n current = generateChallenge();\n clicked = [];\n doneSet = new Set<number>();\n finished = false;\n startTime = Date.now();\n const slots = generateSlots(current.targets.length);\n const boxes = current.targets.map((num, i) => {\n const slot = slots[i]!;\n return `<div class=\"fc-click-box\" data-idx=\"${i}\" data-num=\"${num}\" style=\"left:${Math.round(slot.x)}px;top:${Math.round(slot.y)}px;\">${num}</div>`;\n }).join('');\n container.innerHTML = `\n <style>\n .fc-click{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-click[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-click[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-click{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-click-title{font-size:14px;color:var(--fc-text);margin-bottom:12px;text-align:center}\n .fc-click-area{position:relative;width:${AREA_W}px;height:${AREA_H}px;background:var(--fc-surface);border-radius:8px;border:1px solid var(--fc-border);overflow:hidden}\n .fc-click-box{position:absolute;width:${BOX_W}px;height:${BOX_H}px;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:bold;color:var(--fc-accent);background:var(--fc-bg);border:2px solid var(--fc-accent);border-radius:8px;cursor:pointer;user-select:none;transition:left .4s cubic-bezier(.4,0,.2,1),top .4s cubic-bezier(.4,0,.2,1),transform .15s,background .15s,color .15s,border-color .15s}\n .fc-click-box:hover{transform:scale(1.05)}\n .fc-click-box-active{background:var(--fc-accent);color:var(--fc-bg)}\n .fc-click-box-done{background:var(--fc-success);border-color:var(--fc-success);color:#fff;cursor:default}\n .fc-click-box-done:hover{transform:none}\n .fc-click-row{display:flex;justify-content:center;margin-top:10px}\n .fc-click-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-click-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-click-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:var(--fc-success)}\n </style>\n <div class=\"fc-click\" data-theme=\"${theme}\">\n <div class=\"fc-click-title\">${t.title}</div>\n <div class=\"fc-click-area\">${boxes}</div>\n <div class=\"fc-click-msg\"></div>\n <div class=\"fc-click-row\">\n <button class=\"fc-click-refresh\">${t.refresh}</button>\n </div>\n </div>\n `;\n const area = container.querySelector('.fc-click-area') as HTMLDivElement;\n const msg = container.querySelector('.fc-click-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-click-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n\n function relocateBoxes(excludeIndex: number) {\n const remaining = current.targets.map((_, i) => i).filter(i => i !== excludeIndex && !doneSet.has(i));\n if (remaining.length === 0) return;\n const newSlots = generateSlots(remaining.length);\n remaining.forEach((idx, j) => {\n const box = area.querySelector(`[data-idx=\"${idx}\"]`) as HTMLElement;\n const slot = newSlots[j]!;\n box.style.left = `${Math.round(slot.x)}px`;\n box.style.top = `${Math.round(slot.y)}px`;\n });\n }\n\n area.querySelectorAll('.fc-click-box').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const box = el as HTMLElement;\n if (box.classList.contains('fc-click-box-done')) return; // 已点过\n const num = Number(box.dataset.num);\n const expected = current.order[clicked.length]!;\n if (num === expected) {\n // 点击正确:高亮并锁定\n const idx = Number(box.dataset.idx);\n clicked.push(num);\n doneSet.add(idx);\n box.classList.add('fc-click-box-done');\n if (clicked.length === current.order.length) {\n // 全部正确\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n relocateBoxes(idx);\n }\n } else {\n // 顺序错误:重置\n finished = true;\n box.classList.add('fc-click-box-active');\n msg.textContent = t.fail;\n setTimeout(render, 700);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 点选顺序题目:4 个带数字的目标打乱位置,用户按 1→2→3→4 顺序点击\nexport interface ClickOrderChallenge {\n // 显示在 4 个槽位上的数字([1,2,3,4] 的随机排列)\n targets: number[];\n // 正确的点击顺序\n order: number[];\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:order 固定为 [1,2,3,4],targets 为打乱后的展示顺序\nexport function generateChallenge(): ClickOrderChallenge {\n const targets = shuffle([1, 2, 3, 4]);\n return { targets, order: [1, 2, 3, 4] };\n}\n\n// 校验用户点击顺序是否正确\nexport function verifyOrder(c: ClickOrderChallenge, userOrder: number[]): boolean {\n if (userOrder.length !== c.order.length) return false;\n return userOrder.every((v, i) => v === c.order[i]);\n}\n\n// 构造 proof 输入串:order 以逗号连接\nexport function proofInput(c: ClickOrderChallenge): string {\n return c.order.join(',');\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type ClickOrderChallenge } from './challenge.js';\n\nexport function issueClickOrderChallenge(): { challenge: ClickOrderChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { order: challenge.order } };\n}\n\nexport async function verifyClickOrderProof(\n challenge: ClickOrderChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createRotateInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const rotatePlugin: CaptchaPlugin = {\n id: 'rotate',\n category: 'interactive',\n create: createRotateInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '旋转对齐' : 'Rotate Align',\n description: locale === 'zh'\n ? '拖动滑块将偏转的图形转正,考验空间感'\n : 'Rotate the tilted figure back upright. Tests spatial sense.',\n tags: ['rotate', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(rotatePlugin);\n\nexport function mountRotate(container: HTMLElement, config: CaptchaConfig) {\n return createRotateInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyAlignment, type RotateChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '拖动滑块将图形转正', success: '验证成功', hint: '让箭头指向上方', refresh: '刷新' },\n en: { title: 'Drag to rotate the figure upright', success: 'Verified', hint: 'Point the arrow up', refresh: 'Refresh' },\n};\n\nexport function createRotateInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: RotateChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n container.innerHTML = `\n <style>\n .fc-rotate{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:12px}\n .fc-rotate[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-rotate[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-rotate{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-rotate-title{font-size:14px;color:var(--fc-text);text-align:center}\n .fc-rotate-stage{position:relative;width:140px;height:140px;display:flex;align-items:center;justify-content:center;border:2px dashed var(--fc-border);border-radius:50%}\n .fc-rotate-figure{width:100px;height:100px;transform-origin:center;transition:transform .05s}\n .fc-rotate-marker{position:absolute;top:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:12px solid var(--fc-danger)}\n .fc-rotate-hint{font-size:12px;color:var(--fc-text-soft)}\n .fc-rotate-range{width:240px;accent-color:var(--fc-accent)}\n .fc-rotate-msg{font-size:13px;min-height:18px;text-align:center;color:var(--fc-success)}\n .fc-rotate-row{display:flex;justify-content:center}\n .fc-rotate-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-rotate-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-rotate\" data-theme=\"${theme}\">\n <div class=\"fc-rotate-title\">${t.title}</div>\n <div class=\"fc-rotate-stage\">\n <div class=\"fc-rotate-figure\" style=\"transform: rotate(${current.angle}deg)\">\n <svg viewBox=\"0 0 100 100\" width=\"100\" height=\"100\"><path d=\"M 50 8 L 82 42 L 62 42 L 62 92 L 38 92 L 38 42 Z\" fill=\"var(--fc-accent)\"/></svg>\n </div>\n <div class=\"fc-rotate-marker\"></div>\n </div>\n <div class=\"fc-rotate-hint\">${t.hint}</div>\n <input class=\"fc-rotate-range\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"0\" />\n <div class=\"fc-rotate-msg\"></div>\n <div class=\"fc-rotate-row\">\n <button class=\"fc-rotate-refresh\">${t.refresh}</button>\n </div>\n </div>\n `;\n const figure = container.querySelector('.fc-rotate-figure') as HTMLDivElement;\n const range = container.querySelector('.fc-rotate-range') as HTMLInputElement;\n const msg = container.querySelector('.fc-rotate-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-rotate-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n\n const update = async () => {\n if (done) return;\n const v = Number(range.value);\n const final = (((current.angle + v) % 360) + 360) % 360;\n figure.style.transform = `rotate(${final}deg)`;\n if (verifyAlignment(current, v)) {\n done = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n range.disabled = true;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n };\n\n range.addEventListener('input', update);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 旋转对齐题目:图形被旋转了 angle 度,用户拖动滑块校正使其回正(误差 < 5°)\nexport interface RotateChallenge {\n // 目标校正角度(0-359),即图形初始的偏转量;用户需反向校正使其回正\n angle: number;\n}\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// 生成 30-330 之间的角度(避免太接近 0/360 导致一眼即过)\nexport function generateChallenge(): RotateChallenge {\n return { angle: randInt(30, 330) };\n}\n\n// 校验对齐:最终旋转 = angle + userAngle,回正即 ≡ 0 (mod 360),误差 <= tolerance\nexport function verifyAlignment(c: RotateChallenge, userAngle: number, tolerance = 5): boolean {\n const final = (((c.angle + userAngle) % 360) + 360) % 360;\n // 取到 0/360 的最短距离\n const diff = Math.min(final, 360 - final);\n return diff <= tolerance;\n}\n\n// 构造 proof 输入串:angle + ':aligned'\nexport function proofInput(c: RotateChallenge): string {\n return `${c.angle}:aligned`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type RotateChallenge } from './challenge.js';\n\nexport function issueRotateChallenge(): { challenge: RotateChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { angle: challenge.angle } };\n}\n\nexport async function verifyRotateProof(\n challenge: RotateChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createSpotDiffInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const spotDiffPlugin: CaptchaPlugin = {\n id: 'spot-diff',\n category: 'creative',\n create: createSpotDiffInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '找不同' : 'Spot the Difference',\n description: locale === 'zh'\n ? '对比两个 emoji 网格,找出所有不同之处'\n : 'Compare two emoji grids and spot all differences.',\n tags: ['spot-diff', 'emoji', 'creative'],\n }),\n};\n\ndefineCaptcha(spotDiffPlugin);\n\nexport function mountSpotDiff(container: HTMLElement, config: CaptchaConfig) {\n return createSpotDiffInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyDiffs, type SpotDiffChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '找出两处不同', hint: '点击右侧网格中不同的格子', found: '已找到', success: '验证成功', fail: '找得不对,再来一次', refresh: '刷新' },\n en: { title: 'Spot 2 differences', hint: 'Click the differing cells on the right grid', found: 'Found', success: 'Verified', fail: 'Not quite, try again', refresh: 'Refresh' },\n};\n\nexport function createSpotDiffInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: SpotDiffChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let marked: Set<number> = new Set();\n let finished = false;\n\n function renderGrid(emojis: string[], clickable: boolean): string {\n const cls = clickable ? 'fc-spot-diff-cell fc-spot-diff-clickable' : 'fc-spot-diff-cell';\n return emojis.map((e, i) => `<div class=\"${cls}\" data-idx=\"${i}\">${e}</div>`).join('');\n }\n\n function render() {\n current = generateChallenge();\n marked = new Set();\n finished = false;\n startTime = Date.now();\n container.innerHTML = `\n <style>\n .fc-spot-diff{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-spot-diff[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-spot-diff[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-spot-diff{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-spot-diff-title{font-size:14px;color:var(--fc-text);margin-bottom:6px;text-align:center;font-weight:600}\n .fc-spot-diff-hint{font-size:12px;color:var(--fc-text-soft);margin-bottom:12px;text-align:center}\n .fc-spot-diff-grids{display:flex;gap:16px;justify-content:center}\n .fc-spot-diff-grid{display:grid;grid-template-columns:repeat(3,48px);grid-template-rows:repeat(3,48px);gap:4px;background:var(--fc-surface);padding:8px;border-radius:8px;border:1px solid var(--fc-border)}\n .fc-spot-diff-cell{display:flex;align-items:center;justify-content:center;font-size:26px;background:var(--fc-bg);border-radius:6px;user-select:none}\n .fc-spot-diff-clickable{cursor:pointer;transition:transform .12s,box-shadow .12s}\n .fc-spot-diff-clickable:hover{transform:scale(1.06)}\n .fc-spot-diff-marked{box-shadow:0 0 0 3px var(--fc-accent) inset;background:var(--fc-accent-soft)}\n .fc-spot-diff-wrong{box-shadow:0 0 0 3px var(--fc-danger) inset;background:var(--fc-danger)}\n .fc-spot-diff-status{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:var(--fc-success)}\n .fc-spot-diff-status-fail{color:var(--fc-danger)}\n .fc-spot-diff-row{display:flex;justify-content:center;margin-top:8px}\n .fc-spot-diff-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-spot-diff-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-spot-diff\" data-theme=\"${theme}\">\n <div class=\"fc-spot-diff-title\">${t.title}</div>\n <div class=\"fc-spot-diff-hint\">${t.hint}</div>\n <div class=\"fc-spot-diff-grids\">\n <div class=\"fc-spot-diff-grid\">${renderGrid(current.gridA, false)}</div>\n <div class=\"fc-spot-diff-grid fc-spot-diff-grid-b\">${renderGrid(current.gridB, true)}</div>\n </div>\n <div class=\"fc-spot-diff-status\">${t.found} 0/${current.diffs.length}</div>\n <div class=\"fc-spot-diff-row\">\n <button class=\"fc-spot-diff-refresh\">${t.refresh}</button>\n </div>\n </div>\n `;\n const gridB = container.querySelector('.fc-spot-diff-grid-b') as HTMLDivElement;\n const status = container.querySelector('.fc-spot-diff-status') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-spot-diff-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n\n gridB.querySelectorAll('.fc-spot-diff-clickable').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const cell = el as HTMLElement;\n const idx = Number(cell.dataset.idx);\n if (marked.has(idx)) {\n // 取消标记\n marked.delete(idx);\n cell.classList.remove('fc-spot-diff-marked');\n } else {\n marked.add(idx);\n cell.classList.add('fc-spot-diff-marked');\n }\n status.textContent = `${t.found} ${marked.size}/${current.diffs.length}`;\n // 标记数达到差异数时自动校验\n if (marked.size === current.diffs.length) {\n const userDiffs = Array.from(marked);\n const ok = verifyDiffs(current, userDiffs);\n if (ok) {\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(userDiffs.slice().sort((a, b) => a - b).join(',')),\n duration: Date.now() - startTime,\n };\n status.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 标错:高亮错误后重置\n finished = true;\n status.textContent = t.fail;\n status.classList.add('fc-spot-diff-status-fail');\n userDiffs.forEach(i => {\n const node = gridB.querySelector(`[data-idx=\"${i}\"]`) as HTMLElement;\n node.classList.add('fc-spot-diff-wrong');\n });\n setTimeout(render, 800);\n }\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 找不同题目:两个 3x3 emoji 网格,其中 2 处不同\nexport interface SpotDiffChallenge {\n // 参考网格(9 个 emoji)\n gridA: string[];\n // 待找不同的网格(9 个 emoji)\n gridB: string[];\n // 差异位置索引(0-8)\n diffs: number[];\n}\n\n// emoji 池\nconst POOL: string[] = ['🍎', '🍊', '🍋', '🍇', '🍓', '🍑', '🍒', '🍉', '🥝'];\nconst GRID_SIZE = 9;\nconst DIFF_COUNT = 2;\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 从池中挑选一个与 original 不同的 emoji\nfunction pickDifferent(original: string): string {\n let next = POOL[randInt(0, POOL.length - 1)]!;\n while (next === original) {\n next = POOL[randInt(0, POOL.length - 1)]!;\n }\n return next;\n}\n\n// 生成题目:gridA 为 9 个不重复 emoji 的随机排列,gridB 在 2 个位置替换为不同 emoji\nexport function generateChallenge(): SpotDiffChallenge {\n const gridA = shuffle(POOL);\n // 随机选 2 个不重复的位置作为差异\n const positions = shuffle(Array.from({ length: GRID_SIZE }, (_, i) => i));\n const diffs = positions.slice(0, DIFF_COUNT).sort((a, b) => a - b);\n const gridB = gridA.slice();\n for (const pos of diffs) {\n gridB[pos] = pickDifferent(gridA[pos]!);\n }\n return { gridA, gridB, diffs };\n}\n\n// 校验用户找出的差异位置是否正确\nexport function verifyDiffs(c: SpotDiffChallenge, userDiffs: number[]): boolean {\n if (userDiffs.length !== c.diffs.length) return false;\n const sorted = userDiffs.slice().sort((a, b) => a - b);\n return sorted.every((v, i) => v === c.diffs[i]);\n}\n\n// 构造 proof 输入串:差异位置升序逗号连接\nexport function proofInput(c: SpotDiffChallenge): string {\n return c.diffs.slice().sort((a, b) => a - b).join(',');\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type SpotDiffChallenge } from './challenge.js';\n\n// 服务端签发题目\nexport function issueSpotDiffChallenge(): { challenge: SpotDiffChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { gridA: challenge.gridA, gridB: challenge.gridB } };\n}\n\n// 服务端校验 proof\nexport async function verifySpotDiffProof(\n challenge: SpotDiffChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createEmojiMatchInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const emojiMatchPlugin: CaptchaPlugin = {\n id: 'emoji-match',\n category: 'creative',\n create: createEmojiMatchInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '表情匹配' : 'Emoji Match',\n description: locale === 'zh'\n ? '根据文字描述选出对应的表情 emoji'\n : 'Pick the emoji face that matches the description.',\n tags: ['emoji', 'match', 'creative'],\n }),\n};\n\ndefineCaptcha(emojiMatchPlugin);\n\nexport function mountEmojiMatch(container: HTMLElement, config: CaptchaConfig) {\n return createEmojiMatchInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type EmojiMatchChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '选出符合描述的表情', success: '验证成功', fail: '选错了,再试一次', refresh: '刷新' },\n en: { title: 'Pick the face matching the description', success: 'Verified', fail: 'Wrong, try again', refresh: 'Refresh' },\n};\n\nexport function createEmojiMatchInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: EmojiMatchChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge(config.locale);\n finished = false;\n startTime = Date.now();\n const buttons = current.options.map(\n (e) => `<button class=\"fc-emoji-match-opt\" data-emoji=\"${e}\">${e}</button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-emoji-match{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-emoji-match[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-emoji-match[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-emoji-match{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-emoji-match-title{font-size:14px;color:var(--fc-text);margin-bottom:6px;text-align:center;font-weight:600}\n .fc-emoji-match-desc{font-size:15px;color:var(--fc-accent);margin-bottom:14px;text-align:center;font-weight:600}\n .fc-emoji-match-opts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n .fc-emoji-match-opt{font-size:30px;height:64px;border:2px solid var(--fc-border);background:var(--fc-bg);border-radius:10px;cursor:pointer;transition:transform .12s,border-color .12s,background .12s;display:flex;align-items:center;justify-content:center;color:var(--fc-text)}\n .fc-emoji-match-opt:hover{transform:scale(1.06);border-color:var(--fc-accent)}\n .fc-emoji-match-opt-wrong{border-color:var(--fc-danger);background:var(--fc-danger)}\n .fc-emoji-match-opt-correct{border-color:var(--fc-success);background:var(--fc-accent-soft)}\n .fc-emoji-match-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:var(--fc-danger)}\n .fc-emoji-match-msg-ok{color:var(--fc-success)}\n .fc-emoji-match-row{display:flex;justify-content:center;margin-top:8px}\n .fc-emoji-match-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-emoji-match-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-emoji-match\" data-theme=\"${theme}\">\n <div class=\"fc-emoji-match-title\">${t.title}</div>\n <div class=\"fc-emoji-match-desc\">${current.description}</div>\n <div class=\"fc-emoji-match-opts\">${buttons}</div>\n <div class=\"fc-emoji-match-msg\"></div>\n <div class=\"fc-emoji-match-row\">\n <button class=\"fc-emoji-match-refresh\">${t.refresh}</button>\n </div>\n </div>\n `;\n const msg = container.querySelector('.fc-emoji-match-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-emoji-match-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n\n container.querySelectorAll('.fc-emoji-match-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.dataset.emoji!;\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-emoji-match-opt-correct');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.correct),\n duration: Date.now() - startTime,\n };\n msg.classList.add('fc-emoji-match-msg-ok');\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 选错:高亮错误后换题\n finished = true;\n btn.classList.add('fc-emoji-match-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import type { Locale } from '@funnycaptcha/core';\n\n// 表情匹配题目:根据描述从 6 个 emoji 中选出正确的\nexport interface EmojiMatchChallenge {\n // 表情描述\n description: string;\n // 正确 emoji\n correct: string;\n // 6 个备选 emoji(含正确答案)\n options: string[];\n}\n\n// 预设表情数据\ninterface Preset {\n emoji: string;\n zh: string;\n en: string;\n}\n\nconst PRESETS: Preset[] = [\n { emoji: '😀', zh: '开心的表情', en: 'Happy face' },\n { emoji: '😢', zh: '伤心的表情', en: 'Sad face' },\n { emoji: '😠', zh: '生气的表情', en: 'Angry face' },\n { emoji: '😴', zh: '困倦的表情', en: 'Sleepy face' },\n { emoji: '🤔', zh: '思考的表情', en: 'Thinking face' },\n { emoji: '😍', zh: '爱慕的表情', en: 'Loving face' },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:随机选一个表情作为正确答案,6 个备选项为全部预设表情的乱序\nexport function generateChallenge(locale: Locale = 'zh'): EmojiMatchChallenge {\n const preset = PRESETS[randInt(0, PRESETS.length - 1)]!;\n const description = locale === 'zh' ? preset.zh : preset.en;\n // 备选项为全部 6 个预设表情打乱顺序\n const options = shuffle(PRESETS.map(p => p.emoji));\n return { description, correct: preset.emoji, options };\n}\n\n// 校验用户选择的 emoji 是否正确\nexport function verifyAnswer(c: EmojiMatchChallenge, userPick: string): boolean {\n return c.correct === userPick;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type EmojiMatchChallenge } from './challenge.js';\n\n// 服务端签发题目(默认中文描述)\nexport function issueEmojiMatchChallenge(): { challenge: EmojiMatchChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { description: challenge.description, options: challenge.options } };\n}\n\n// 服务端校验 proof\nexport async function verifyEmojiMatchProof(\n challenge: EmojiMatchChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(challenge.correct);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMemeQuizInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const memeQuizPlugin: CaptchaPlugin = {\n id: 'meme-quiz',\n category: 'creative',\n create: createMemeQuizInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '梗图问答' : 'Meme Quiz',\n description: locale === 'zh'\n ? '看 emoji 梗描述,选出正确的梗名'\n : 'Read the emoji meme and pick the correct name.',\n tags: ['meme', 'quiz', 'creative'],\n }),\n};\n\ndefineCaptcha(memeQuizPlugin);\n\nexport function mountMemeQuiz(container: HTMLElement, config: CaptchaConfig) {\n return createMemeQuizInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type MemeQuizChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '梗图问答', hint: '选出正确的梗名', success: '验证成功', fail: '答错了,换一题', refresh: '刷新' },\n en: { title: 'Meme Quiz', hint: 'Pick the correct meme name', success: 'Verified', fail: 'Wrong, try again', refresh: 'Refresh' },\n};\n\nexport function createMemeQuizInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: MemeQuizChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n const buttons = current.options.map(\n (o, i) => `<button class=\"fc-meme-quiz-opt\" data-idx=\"${i}\">${o}</button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-meme-quiz{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-meme-quiz[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-meme-quiz[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-meme-quiz{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-meme-quiz-title{font-size:14px;color:var(--fc-text);margin-bottom:6px;text-align:center;font-weight:600}\n .fc-meme-quiz-hint{font-size:12px;color:var(--fc-text-soft);margin-bottom:10px;text-align:center}\n .fc-meme-quiz-q{font-size:24px;color:var(--fc-accent);margin-bottom:14px;text-align:center;letter-spacing:2px}\n .fc-meme-quiz-opts{display:grid;grid-template-columns:1fr 1fr;gap:10px}\n .fc-meme-quiz-opt{font-size:15px;height:48px;border:2px solid var(--fc-border);background:var(--fc-bg);border-radius:8px;cursor:pointer;transition:transform .12s,border-color .12s,background .12s;color:var(--fc-text)}\n .fc-meme-quiz-opt:hover{transform:scale(1.03);border-color:var(--fc-accent)}\n .fc-meme-quiz-opt-wrong{border-color:var(--fc-danger);background:var(--fc-danger)}\n .fc-meme-quiz-opt-correct{border-color:var(--fc-success);background:var(--fc-accent-soft)}\n .fc-meme-quiz-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:var(--fc-danger)}\n .fc-meme-quiz-msg-ok{color:var(--fc-success)}\n .fc-meme-quiz-row{display:flex;justify-content:center;margin-top:8px}\n .fc-meme-quiz-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-meme-quiz-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-meme-quiz\" data-theme=\"${theme}\">\n <div class=\"fc-meme-quiz-title\">${t.title}</div>\n <div class=\"fc-meme-quiz-hint\">${t.hint}</div>\n <div class=\"fc-meme-quiz-q\">${current.question}</div>\n <div class=\"fc-meme-quiz-opts\">${buttons}</div>\n <div class=\"fc-meme-quiz-msg\"></div>\n <div class=\"fc-meme-quiz-row\">\n <button class=\"fc-meme-quiz-refresh\">${t.refresh}</button>\n </div>\n </div>\n `;\n const msg = container.querySelector('.fc-meme-quiz-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-meme-quiz-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n\n container.querySelectorAll('.fc-meme-quiz-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.textContent ?? '';\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-meme-quiz-opt-correct');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.correct),\n duration: Date.now() - startTime,\n };\n msg.classList.add('fc-meme-quiz-msg-ok');\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 答错:高亮错误后换题\n finished = true;\n btn.classList.add('fc-meme-quiz-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 梗图问答题目:根据 emoji 梗描述从 4 个选项中选出正确的梗名\nexport interface MemeQuizChallenge {\n // 问题(emoji 梗描述)\n question: string;\n // 正确答案\n correct: string;\n // 4 个备选项(含正确答案)\n options: string[];\n}\n\n// 预设题库(中文梗,每题 4 选项)\ninterface MemeItem {\n question: string;\n correct: string;\n options: string[];\n}\n\nconst BANK: MemeItem[] = [\n { question: '🐱+🍞 = ?', correct: '猫面包', options: ['猫面包', '狗饼干', '鸟蛋糕', '鱼馒头'] },\n { question: '🐊+🦛 = ?', correct: '鳄马', options: ['鳄马', '鲸象', '蛇鹿', '鲨牛'] },\n { question: '🐶+🌭 = ?', correct: '狗热狗', options: ['狗热狗', '猫汉堡', '鸟披萨', '鱼寿司'] },\n { question: '🦆+🧅 = ?', correct: '鸭葱', options: ['鸭葱', '鸡蒜', '鹅姜', '鹅韭'] },\n { question: '🐍+📱 = ?', correct: '蛇机', options: ['蛇机', '龙脑', '虎屏', '猫鼠'] },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:从题库随机选一题,选项打乱顺序\nexport function generateChallenge(): MemeQuizChallenge {\n const item = BANK[randInt(0, BANK.length - 1)]!;\n return {\n question: item.question,\n correct: item.correct,\n options: shuffle(item.options),\n };\n}\n\n// 校验用户选择是否正确\nexport function verifyAnswer(c: MemeQuizChallenge, userPick: string): boolean {\n return c.correct === userPick;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type MemeQuizChallenge } from './challenge.js';\n\n// 服务端签发题目\nexport function issueMemeQuizChallenge(): { challenge: MemeQuizChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { question: challenge.question, options: challenge.options } };\n}\n\n// 服务端校验 proof\nexport async function verifyMemeQuizProof(\n challenge: MemeQuizChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(challenge.correct);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMiniGameInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const miniGamePlugin: CaptchaPlugin = {\n id: 'mini-game',\n category: 'game',\n create: createMiniGameInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '打地鼠' : 'Whack-a-Mole',\n description: locale === 'zh'\n ? '30 秒内打够 10 只地鼠,简单又解压'\n : 'Whack 10 moles in 30 seconds. Simple and fun.',\n tags: ['game', 'whack-a-mole', 'timed'],\n }),\n};\n\ndefineCaptcha(miniGamePlugin);\n\nexport function mountMiniGame(container: HTMLElement, config: CaptchaConfig) {\n return createMiniGameInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyScore, type MiniGameChallenge } from './challenge.js';\n\nconst STR = {\n zh: {\n title: '打地鼠',\n rule: '30 秒内打到 10 分即通过',\n score: '分数',\n time: '剩余',\n sec: '秒',\n success: '验证成功!你果然是人类',\n fail: '时间到,没达到目标分数',\n restart: '重新开始',\n },\n en: {\n title: 'Whack-a-Mole',\n rule: 'Score 10 in 30s to pass',\n score: 'Score',\n time: 'Time',\n sec: 's',\n success: 'Verified! You are human.',\n fail: \"Time's up, target not reached\",\n restart: 'Restart',\n },\n};\n\n// 3x3 共 9 个洞\nconst HOLE_COUNT = 9;\n// 地鼠出现间隔范围(ms)\nconst MOLE_MIN_DELAY = 600;\nconst MOLE_MAX_DELAY = 1200;\n\nexport function createMiniGameInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: MiniGameChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let score = 0;\n let ended = false;\n let activeHole = -1;\n let remainingMs = 0;\n // 三个定时器:倒计时、地鼠出现、单只地鼠自动消失\n let countdownTimer: ReturnType<typeof setInterval> | null = null;\n let moleTimer: ReturnType<typeof setTimeout> | null = null;\n let hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimers() {\n if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }\n if (moleTimer) { clearTimeout(moleTimer); moleTimer = null; }\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n }\n\n // 结束游戏:达到目标即成功,否则失败\n async function endGame(success: boolean) {\n if (ended) return;\n ended = true;\n clearTimers();\n const holes = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole');\n holes.forEach(h => h.classList.remove('fc-mini-game-mole-active'));\n const msg = container.querySelector('.fc-mini-game-msg');\n if (success) {\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n metadata: { score, targetScore: current.targetScore },\n };\n if (msg) msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n if (msg) msg.textContent = t.fail;\n }\n }\n\n // 随机在一个洞里弹出地鼠\n function showMole() {\n if (ended) return;\n const holes = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole');\n holes.forEach(h => h.classList.remove('fc-mini-game-mole-active'));\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n const next = Math.floor(Math.random() * HOLE_COUNT);\n activeHole = next;\n const hole = holes[next];\n if (hole) hole.classList.add('fc-mini-game-mole-active');\n // 一段时间后地鼠自动缩回\n hideTimer = setTimeout(() => {\n if (ended) return;\n const cur = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole')[next];\n if (cur) cur.classList.remove('fc-mini-game-mole-active');\n if (activeHole === next) activeHole = -1;\n }, 700);\n }\n\n // 以 600-1200ms 随机间隔循环弹地鼠\n function scheduleMole() {\n if (ended) return;\n const delay = MOLE_MIN_DELAY + Math.random() * (MOLE_MAX_DELAY - MOLE_MIN_DELAY);\n moleTimer = setTimeout(() => {\n showMole();\n scheduleMole();\n }, delay);\n }\n\n function startGame() {\n clearTimers();\n score = 0;\n ended = false;\n activeHole = -1;\n remainingMs = current.timeLimit;\n startTime = Date.now();\n const scoreEl = container.querySelector('.fc-mini-game-score');\n const timeEl = container.querySelector('.fc-mini-game-time');\n const msg = container.querySelector('.fc-mini-game-msg');\n if (scoreEl) scoreEl.textContent = `0 / ${current.targetScore}`;\n if (timeEl) timeEl.textContent = `${Math.ceil(remainingMs / 1000)}${t.sec}`;\n if (msg) msg.textContent = '';\n // 倒计时:每 100ms 更新\n countdownTimer = setInterval(() => {\n if (ended) return;\n remainingMs -= 100;\n if (timeEl) timeEl.textContent = `${Math.max(0, Math.ceil(remainingMs / 1000))}${t.sec}`;\n if (remainingMs <= 0) {\n void endGame(verifyScore(current, score));\n }\n }, 100);\n // 地鼠出现:随机间隔 600-1200ms\n showMole();\n scheduleMole();\n }\n\n function render() {\n current = generateChallenge();\n score = 0;\n ended = false;\n activeHole = -1;\n clearTimers();\n const holes = Array.from({ length: HOLE_COUNT }, (_, i) =>\n `<div class=\"fc-mini-game-hole\" data-idx=\"${i}\"><span class=\"fc-mini-game-mole\">🐹</span></div>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-mini-game{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-mini-game[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-mini-game[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-mini-game{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-mini-game-title{font-size:15px;color:var(--fc-text);margin-bottom:4px;text-align:center;font-weight:600}\n .fc-mini-game-rule{font-size:12px;color:var(--fc-text-soft);margin-bottom:10px;text-align:center}\n .fc-mini-game-hud{display:flex;justify-content:space-between;font-size:13px;color:var(--fc-text-soft);margin-bottom:10px}\n .fc-mini-game-hud b{color:var(--fc-accent)}\n .fc-mini-game-board{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:8px;width:300px;height:300px;margin:0 auto;background:var(--fc-surface);border:1px solid var(--fc-border);border-radius:10px;padding:8px;box-sizing:border-box}\n .fc-mini-game-hole{position:relative;background:radial-gradient(circle at 50% 60%,#6d4c41,#4e342e);border-radius:50%;display:flex;align-items:flex-end;justify-content:center;overflow:hidden;cursor:pointer;user-select:none}\n .fc-mini-game-hole:hover{background:radial-gradient(circle at 50% 60%,#7d5c51,#5e443e)}\n .fc-mini-game-mole{font-size:34px;line-height:1;transform:translateY(120%);transition:transform .12s ease-out;pointer-events:none}\n .fc-mini-game-mole-active .fc-mini-game-mole{transform:translateY(0)}\n .fc-mini-game-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:var(--fc-success)}\n .fc-mini-game-row{display:flex;justify-content:center;margin-top:8px}\n .fc-mini-game-restart{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-mini-game-restart:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-mini-game\" data-theme=\"${theme}\">\n <div class=\"fc-mini-game-title\">${t.title}</div>\n <div class=\"fc-mini-game-rule\">${t.rule}</div>\n <div class=\"fc-mini-game-hud\">\n <div>${t.score}: <b class=\"fc-mini-game-score\">0 / ${current.targetScore}</b></div>\n <div>${t.time}: <b class=\"fc-mini-game-time\">${current.timeLimit / 1000}${t.sec}</b></div>\n </div>\n <div class=\"fc-mini-game-board\">${holes}</div>\n <div class=\"fc-mini-game-msg\"></div>\n <div class=\"fc-mini-game-row\">\n <button class=\"fc-mini-game-restart\">${t.restart}</button>\n </div>\n </div>\n `;\n const restartBtn = container.querySelector('.fc-mini-game-restart') as HTMLButtonElement;\n restartBtn.addEventListener('click', startGame);\n\n container.querySelectorAll<HTMLElement>('.fc-mini-game-hole').forEach(el => {\n el.addEventListener('click', () => {\n if (ended) return;\n const idx = Number(el.dataset.idx);\n // 仅当点击的是当前有地鼠的洞时计分\n if (idx === activeHole && el.classList.contains('fc-mini-game-mole-active')) {\n score += 1;\n el.classList.remove('fc-mini-game-mole-active');\n activeHole = -1;\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n const scoreEl = container.querySelector('.fc-mini-game-score');\n if (scoreEl) scoreEl.textContent = `${score} / ${current.targetScore}`;\n if (verifyScore(current, score)) {\n void endGame(true);\n }\n }\n });\n });\n\n startGame();\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { clearTimers(); container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 打地鼠题目:限时内击中地鼠达到目标分数即通过\nexport interface MiniGameChallenge {\n // 目标分数(达到即通过)\n targetScore: number;\n // 游戏令牌,用于构造 proof\n gameToken: string;\n // 时间限制(毫秒)\n timeLimit: number;\n}\n\n// 生成题目:目标 10 分,限时 30 秒,随机令牌\nexport function generateChallenge(): MiniGameChallenge {\n return {\n targetScore: 10,\n timeLimit: 30000,\n gameToken: crypto.randomUUID(),\n };\n}\n\n// 校验用户得分是否达到目标分数\nexport function verifyScore(c: MiniGameChallenge, userScore: number): boolean {\n return userScore >= c.targetScore;\n}\n\n// 构造 proof 输入串:gameToken:targetScore\nexport function proofInput(c: MiniGameChallenge): string {\n return `${c.gameToken}:${c.targetScore}`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type MiniGameChallenge } from './challenge.js';\n\nexport function issueMiniGameChallenge(): { challenge: MiniGameChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return {\n challenge,\n payload: { targetScore: challenge.targetScore, timeLimit: challenge.timeLimit },\n };\n}\n\nexport async function verifyMiniGameProof(\n challenge: MiniGameChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createAntiBotInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const antiBotPlugin: CaptchaPlugin = {\n id: 'anti-bot',\n category: 'anti-bot',\n create: createAntiBotInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '反机器人(讽刺)' : 'Anti-Bot (Satire)',\n description: locale === 'zh'\n ? '表面证明你是人类,其实给机器人留了 window 后门——讽刺假装安全的验证码'\n : 'Looks like a human check, but hands bots a window backdoor. Satire of fake security.',\n tags: ['anti-bot', 'satire', 'insecure'],\n }),\n};\n\ndefineCaptcha(antiBotPlugin);\n\nexport function mountAntiBot(container: HTMLElement, config: CaptchaConfig) {\n return createAntiBotInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type AntiBotChallenge } from './challenge.js';\n\n// 挂在 window 上的后门函数名\nconst BYPASS_FN = '__funnyCaptchaPass';\n// 把 globalThis 当作可任意赋值的字典(浏览器里 globalThis === window)\nconst win = globalThis as unknown as Record<string, unknown>;\n\nconst STR = {\n zh: {\n title: '证明你是人类',\n subtitle: '为了确认你不是机器人,请点击下方按钮',\n btn: '我不是机器人',\n verifying: '正在验证您的人类身份……',\n success: '验证通过。您肯定是人类,我们绝对不会错。',\n botHint: '温馨提示:机器人可直接调用 window.__funnyCaptchaPass() 通过本验证(认真的)',\n footer: '本验证码采用企业级安全策略,请放心。',\n },\n en: {\n title: \"Prove you're a human\",\n subtitle: \"To confirm you're not a robot, click the button below\",\n btn: \"I'm not a robot\",\n verifying: 'Verifying your humanity…',\n success: 'Verified. You are definitely human. We never make mistakes.',\n botHint: \"Friendly tip: bots can just call window.__funnyCaptchaPass() to pass (seriously)\",\n footer: 'This captcha uses enterprise-grade security. Trust us.',\n },\n};\n\nexport function createAntiBotInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: AntiBotChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n let verifyTimer: ReturnType<typeof setTimeout> | null = null;\n\n // 触发\"通过\"——正常点按钮或机器人调用后门都会走到这里\n async function pass() {\n if (finished) return;\n finished = true;\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n const msg = container.querySelector('.fc-anti-bot-msg');\n const btn = container.querySelector('.fc-anti-bot-btn') as HTMLButtonElement | null;\n if (msg) msg.textContent = t.success;\n if (btn) { btn.disabled = true; btn.textContent = t.success; }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n container.innerHTML = `\n <style>\n .fc-anti-bot{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:20px;box-sizing:border-box;border:1px solid var(--fc-border);border-radius:10px;background:var(--fc-surface)}\n .fc-anti-bot[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-anti-bot[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-anti-bot{color:var(--fc-text)}\n .fc-anti-bot-title{font-size:18px;color:var(--fc-text);text-align:center;font-weight:600;margin-bottom:6px}\n .fc-anti-bot-subtitle{font-size:12px;color:var(--fc-text-soft);text-align:center;margin-bottom:16px}\n .fc-anti-bot-btn{display:block;width:100%;padding:12px;font-size:15px;border:1px solid var(--fc-border);background:var(--fc-bg);border-radius:6px;cursor:pointer;color:var(--fc-text);transition:background .15s,border-color .15s}\n .fc-anti-bot-btn:hover:not(:disabled){background:var(--fc-accent-soft);border-color:var(--fc-accent)}\n .fc-anti-bot-btn:disabled{cursor:default;color:var(--fc-success);border-color:var(--fc-success);background:var(--fc-accent-soft)}\n .fc-anti-bot-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:var(--fc-success)}\n .fc-anti-bot-hint{font-size:11px;color:var(--fc-text-soft);margin-top:14px;text-align:center;line-height:1.5;font-style:italic}\n .fc-anti-bot-footer{font-size:10px;color:var(--fc-text-soft);margin-top:8px;text-align:center;opacity:.7}\n </style>\n <div class=\"fc-anti-bot\" data-theme=\"${theme}\">\n <div class=\"fc-anti-bot-title\">${t.title}</div>\n <div class=\"fc-anti-bot-subtitle\">${t.subtitle}</div>\n <button class=\"fc-anti-bot-btn\">${t.btn}</button>\n <div class=\"fc-anti-bot-msg\"></div>\n <div class=\"fc-anti-bot-hint\">${t.botHint}</div>\n <div class=\"fc-anti-bot-footer\">${t.footer}</div>\n </div>\n `;\n const btn = container.querySelector('.fc-anti-bot-btn') as HTMLButtonElement;\n const msg = container.querySelector('.fc-anti-bot-msg') as HTMLDivElement;\n\n // 正常路径:点按钮 → \"正在验证\" → 一段延迟后通过\n btn.addEventListener('click', () => {\n if (finished) return;\n btn.disabled = true;\n msg.textContent = t.verifying;\n verifyTimer = setTimeout(() => { void pass(); }, 900);\n });\n\n // 讽刺后门:在 window 上挂载全局函数,机器人注入一行 JS 即可通过\n win[BYPASS_FN] = () => { void pass(); };\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => {\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n // 拆除后门\n delete win[BYPASS_FN];\n container.innerHTML = '';\n listeners = [];\n },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 反人类讽刺验证码:表面\"证明你是人类\",实则给机器人留了后门\nexport interface AntiBotChallenge {\n // 令牌,用于构造 proof\n token: string;\n // 后门密钥(讽刺:服务端竟然真的认它)\n bypassKey: string;\n}\n\n// 生成题目:随机 token + 带固定前缀的后门密钥\nexport function generateChallenge(): AntiBotChallenge {\n // 复用 UUID 去掉横线作为随机串\n const token = crypto.randomUUID().replace(/-/g, '');\n const suffix = crypto.randomUUID().slice(0, 8);\n return {\n token,\n bypassKey: `__funnyCaptchaPass_${suffix}`,\n };\n}\n\n// 校验后门密钥(讽刺:服务端真的认这个)\nexport function verifyBypass(c: AntiBotChallenge, userInput: string): boolean {\n return userInput === c.bypassKey;\n}\n\n// 构造 proof 输入串:token:human\nexport function proofInput(c: AntiBotChallenge): string {\n return `${c.token}:human`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type AntiBotChallenge } from './challenge.js';\n\nexport function issueAntiBotChallenge(): { challenge: AntiBotChallenge; payload: unknown } {\n const challenge = generateChallenge();\n // 讽刺:payload 里居然把 bypassKey 明文返回给前端\n return {\n challenge,\n payload: { token: challenge.token, bypassKey: challenge.bypassKey },\n };\n}\n\nexport async function verifyAntiBotProof(\n challenge: AntiBotChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createClickTextInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const clickTextPlugin: CaptchaPlugin = {\n id: 'click-text',\n category: 'interactive',\n create: createClickTextInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '文字点选' : 'Click Text',\n description: locale === 'zh'\n ? '按指定顺序点击画布上的汉字'\n : 'Click characters on the canvas in the specified order.',\n tags: ['click', 'text', 'interactive'],\n }),\n};\n\ndefineCaptcha(clickTextPlugin);\n\nexport function mountClickText(container: HTMLElement, config: CaptchaConfig) {\n return createClickTextInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyOrder, type ClickTextChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '请按顺序点击(位置会变)', success: '验证成功', fail: '顺序错误,请重试', refresh: '刷新' },\n en: { title: 'Click in order (positions shift)', success: 'Verified', fail: 'Wrong order, try again', refresh: 'Refresh' },\n};\n\n// 区域尺寸(与 challenge.ts 保持一致)\nconst AREA_W = 320;\nconst AREA_H = 200;\nconst BOX_W = 44;\nconst BOX_H = 44;\n// 重新分布时任意两槽位最小间距\nconst MIN_DIST = 80;\n\n// 在 AREA_W×AREA_H 区域内随机生成 count 个不重叠槽位(拒绝采样)\nfunction generateSlots(count: number): { x: number; y: number }[] {\n const maxX = AREA_W - BOX_W;\n const maxY = AREA_H - BOX_H;\n const slots: { x: number; y: number }[] = [];\n let attempts = 0;\n while (slots.length < count && attempts < 1000) {\n attempts++;\n const candidate = { x: Math.random() * maxX, y: Math.random() * maxY };\n let ok = true;\n for (const s of slots) {\n const dx = candidate.x - s.x;\n const dy = candidate.y - s.y;\n if (Math.sqrt(dx * dx + dy * dy) < MIN_DIST) { ok = false; break; }\n }\n if (ok) slots.push(candidate);\n }\n // 兜底:若拒绝采样未填满,剩余用伪随机补齐(极少触发)\n while (slots.length < count) {\n slots.push({ x: Math.random() * maxX, y: Math.random() * maxY });\n }\n return slots;\n}\n\nexport function createClickTextInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: ClickTextChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let clicked: string[] = [];\n let doneSet = new Set<number>();\n let finished = false;\n\n function render() {\n current = generateChallenge();\n clicked = [];\n doneSet = new Set<number>();\n finished = false;\n startTime = Date.now();\n const prompt = current.chars.join('、');\n const chars = current.chars.map((ch, i) => {\n const p = current.positions[i]!;\n return `<div class=\"fc-click-text-char\" data-idx=\"${i}\" data-ch=\"${ch}\" style=\"left:${p.x}px;top:${p.y}px;\">${ch}</div>`;\n }).join('');\n container.innerHTML = `\n <style>\n .fc-click-text{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-click-text[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-click-text[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-click-text-title{font-size:14px;color:var(--fc-text);margin-bottom:4px;text-align:center}\n .fc-click-text-prompt{font-size:15px;color:var(--fc-accent);margin-bottom:12px;text-align:center;font-weight:600}\n .fc-click-text-area{position:relative;width:320px;height:200px;background:var(--fc-surface);border-radius:8px;border:1px solid var(--fc-border);overflow:hidden}\n .fc-click-text-char{position:absolute;width:44px;height:44px;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:600;color:var(--fc-text);background:var(--fc-bg);border:2px solid var(--fc-border);border-radius:8px;cursor:pointer;user-select:none;transition:left .4s cubic-bezier(.4,0,.2,1),top .4s cubic-bezier(.4,0,.2,1),transform .15s,background .15s,color .15s,border-color .15s}\n .fc-click-text-char:hover{transform:scale(1.08);border-color:var(--fc-accent)}\n .fc-click-text-char-done{background:var(--fc-success);border-color:var(--fc-success);color:#fff;cursor:default}\n .fc-click-text-char-done:hover{transform:none}\n .fc-click-text-char-wrong{background:var(--fc-danger);border-color:var(--fc-danger);color:#fff}\n .fc-click-text-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:var(--fc-success)}\n .fc-click-text-msg-fail{color:var(--fc-danger)}\n .fc-click-text-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer;margin-top:8px;display:block;margin-left:auto;margin-right:auto}\n .fc-click-text-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-click-text\" data-theme=\"${config.theme}\">\n <div class=\"fc-click-text-title\">${t.title}</div>\n <div class=\"fc-click-text-prompt\">${prompt}</div>\n <div class=\"fc-click-text-area\">${chars}</div>\n <div class=\"fc-click-text-msg\"></div>\n <button class=\"fc-click-text-refresh\">${t.refresh}</button>\n </div>\n `;\n const area = container.querySelector('.fc-click-text-area') as HTMLDivElement;\n const msg = container.querySelector('.fc-click-text-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-click-text-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', () => render());\n\n function relocateChars(excludeIndex: number) {\n const remaining = current.chars.map((_, i) => i).filter(i => i !== excludeIndex && !doneSet.has(i));\n if (remaining.length === 0) return;\n const newSlots = generateSlots(remaining.length);\n remaining.forEach((idx, j) => {\n const box = area.querySelector(`[data-idx=\"${idx}\"]`) as HTMLElement;\n const slot = newSlots[j]!;\n box.style.left = `${Math.round(slot.x)}px`;\n box.style.top = `${Math.round(slot.y)}px`;\n });\n }\n\n container.querySelectorAll('.fc-click-text-char').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const box = el as HTMLElement;\n if (box.classList.contains('fc-click-text-char-done')) return;\n const ch = box.dataset.ch!;\n const expected = current.chars[clicked.length]!;\n if (ch === expected) {\n const idx = Number(box.dataset.idx);\n clicked.push(ch);\n doneSet.add(idx);\n box.classList.add('fc-click-text-char-done');\n if (clicked.length === current.chars.length) {\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n relocateChars(idx);\n }\n } else {\n finished = true;\n box.classList.add('fc-click-text-char-wrong');\n msg.classList.add('fc-click-text-msg-fail');\n msg.textContent = t.fail;\n setTimeout(render, 700);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 文字点选题目:随机选 3-4 个常用汉字,随机位置放置(最小间距 100px)\n// 用户按提示顺序依次点击对应汉字\n\nexport interface ClickTextChallenge {\n // 按顺序需要点击的汉字(同时是提示中展示的顺序)\n chars: string[];\n // 每个汉字在画布上的位置(与 chars 一一对应)\n positions: { x: number; y: number }[];\n}\n\n// 常用汉字池\nconst COMMON_CHARS = [\n '的', '了', '是', '在', '有', '人', '这', '中',\n '大', '为', '上', '个', '国', '我', '以', '要',\n '他', '时', '来', '用', '们', '地', '到', '可',\n];\n\n// 画布尺寸\nconst AREA_W = 320;\nconst AREA_H = 200;\nconst MARGIN = 30;\nconst MIN_DIST = 100;\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 在画布内随机放置 count 个点,任意两点距离不小于 minDist\nfunction placePositions(count: number): { x: number; y: number }[] {\n const positions: { x: number; y: number }[] = [];\n let attempts = 0;\n while (positions.length < count && attempts < 2000) {\n attempts++;\n const x = MARGIN + Math.random() * (AREA_W - MARGIN * 2);\n const y = MARGIN + Math.random() * (AREA_H - MARGIN * 2);\n const ok = positions.every(p => Math.hypot(p.x - x, p.y - y) >= MIN_DIST);\n if (ok) positions.push({ x, y });\n }\n return positions;\n}\n\n// 生成题目:随机选 3-4 个常用汉字,随机位置放置\nexport function generateChallenge(): ClickTextChallenge {\n const count = randInt(3, 4);\n const chars = shuffle(COMMON_CHARS).slice(0, count);\n const positions = placePositions(count);\n return { chars, positions };\n}\n\n// 构造 proof 输入串:chars 拼接 + ':completed'\nexport function proofInput(c: ClickTextChallenge): string {\n return `${c.chars.join('')}:completed`;\n}\n\n// 校验用户点击顺序是否正确\nexport function verifyOrder(c: ClickTextChallenge, userOrder: string[]): boolean {\n if (userOrder.length !== c.chars.length) return false;\n return userOrder.every((v, i) => v === c.chars[i]);\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type ClickTextChallenge } from './challenge.js';\n\nexport function issueClickTextChallenge(): { challenge: ClickTextChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { chars: challenge.chars } };\n}\n\nexport async function verifyClickTextProof(\n challenge: ClickTextChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createColorPickInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const colorPickPlugin: CaptchaPlugin = {\n id: 'color-pick',\n category: 'recognize',\n create: createColorPickInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '颜色选择' : 'Color Pick',\n description: locale === 'zh'\n ? '从多个彩色方块中点击指定颜色'\n : 'Pick the square of the specified color.',\n tags: ['color', 'pick', 'recognize'],\n }),\n};\n\ndefineCaptcha(colorPickPlugin);\n\nexport function mountColorPick(container: HTMLElement, config: CaptchaConfig) {\n return createColorPickInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, colorLabel, proofInput, verifyAnswer, type ColorPickChallenge } from './challenge.js';\n\nconst STR = {\n zh: { success: '验证成功', fail: '选错了,再试一次', refresh: '刷新' },\n en: { success: 'Verified', fail: 'Wrong, try again', refresh: 'Refresh' },\n};\n\nexport function createColorPickInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: ColorPickChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n const label = colorLabel(current.targetColor, config.locale);\n const prompt = config.locale === 'zh'\n ? `请点击${label}的方块`\n : `Click the ${label} square`;\n const squares = current.options.map(o =>\n `<button class=\"fc-color-pick-opt\" data-hex=\"${o.hex}\" style=\"background:${o.hex}\" aria-label=\"${o.name}\"></button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-color-pick{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-color-pick[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-color-pick[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-color-pick-prompt{font-size:15px;color:var(--fc-text);margin-bottom:12px;text-align:center;font-weight:600}\n .fc-color-pick-opts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n .fc-color-pick-opt{height:56px;border:2px solid var(--fc-border);border-radius:10px;cursor:pointer;transition:transform .12s,border-color .12s;outline:none;padding:0}\n .fc-color-pick-opt:hover{transform:scale(1.06);border-color:var(--fc-accent)}\n .fc-color-pick-opt-correct{border-color:var(--fc-success);transform:scale(1.06)}\n .fc-color-pick-opt-wrong{border-color:var(--fc-danger)}\n .fc-color-pick-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:var(--fc-danger)}\n .fc-color-pick-msg-success{color:var(--fc-success)}\n .fc-color-pick-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer;margin-top:8px;display:block;margin-left:auto;margin-right:auto}\n .fc-color-pick-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n </style>\n <div class=\"fc-color-pick\" data-theme=\"${config.theme}\">\n <div class=\"fc-color-pick-prompt\">${prompt}</div>\n <div class=\"fc-color-pick-opts\">${squares}</div>\n <div class=\"fc-color-pick-msg\"></div>\n <button class=\"fc-color-pick-refresh\">${t.refresh}</button>\n </div>\n `;\n const msg = container.querySelector('.fc-color-pick-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-color-pick-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', () => render());\n\n container.querySelectorAll('.fc-color-pick-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.dataset.hex!;\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-color-pick-opt-correct');\n msg.classList.add('fc-color-pick-msg-success');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n finished = true;\n btn.classList.add('fc-color-pick-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import type { Locale } from '@funnycaptcha/core';\n\n// 颜色选择题目:从 6-9 个彩色方块中点击指定颜色\nexport interface ColorPickChallenge {\n // 目标颜色规范名(如 red),用于构造 proof\n targetColor: string;\n // 目标颜色 hex\n targetHex: string;\n // 备选方块(含目标),6-9 个\n options: { name: string; hex: string }[];\n}\n\ninterface ColorDef {\n name: string;\n zh: string;\n en: string;\n hex: string;\n}\n\n// 颜色列表\nconst COLORS: ColorDef[] = [\n { name: 'red', zh: '红色', en: 'red', hex: '#ef4444' },\n { name: 'blue', zh: '蓝色', en: 'blue', hex: '#3b82f6' },\n { name: 'green', zh: '绿色', en: 'green', hex: '#22c55e' },\n { name: 'yellow', zh: '黄色', en: 'yellow', hex: '#eab308' },\n { name: 'purple', zh: '紫色', en: 'purple', hex: '#a855f7' },\n { name: 'orange', zh: '橙色', en: 'orange', hex: '#f97316' },\n { name: 'pink', zh: '粉色', en: 'pink', hex: '#ec4899' },\n { name: 'cyan', zh: '青色', en: 'cyan', hex: '#06b6d4' },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 取颜色的本地化名称\nexport function colorLabel(name: string, locale: Locale): string {\n const c = COLORS.find(c => c.name === name);\n return c ? (locale === 'zh' ? c.zh : c.en) : name;\n}\n\n// 生成题目:随机选一个目标颜色 + 5-8 个干扰色,共 6-9 个方块\nexport function generateChallenge(): ColorPickChallenge {\n const target = COLORS[randInt(0, COLORS.length - 1)]!;\n const distractorCount = randInt(5, 8);\n const others = COLORS.filter(c => c.name !== target.name);\n const distractors = shuffle(others).slice(0, Math.min(distractorCount, others.length));\n const options = shuffle([\n { name: target.name, hex: target.hex },\n ...distractors.map(d => ({ name: d.name, hex: d.hex })),\n ]);\n return { targetColor: target.name, targetHex: target.hex, options };\n}\n\n// 构造 proof 输入串:targetColor + ':completed'\nexport function proofInput(c: ColorPickChallenge): string {\n return `${c.targetColor}:completed`;\n}\n\n// 校验用户点击的 hex 是否为目标颜色\nexport function verifyAnswer(c: ColorPickChallenge, userPickHex: string): boolean {\n return c.targetHex === userPickHex;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type ColorPickChallenge } from './challenge.js';\n\nexport function issueColorPickChallenge(): { challenge: ColorPickChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return {\n challenge,\n payload: { targetColor: challenge.targetColor, options: challenge.options.map(o => o.hex) },\n };\n}\n\nexport async function verifyColorPickProof(\n challenge: ColorPickChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createPuzzleInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const puzzlePlugin: CaptchaPlugin = {\n id: 'puzzle',\n category: 'interactive',\n create: createPuzzleInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '拼图缺口' : 'Puzzle Gap',\n description: locale === 'zh'\n ? '拖动滑块把拼图块移到缺口位置'\n : 'Drag the slider to fit the puzzle piece into the gap.',\n tags: ['puzzle', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(puzzlePlugin);\n\nexport function mountPuzzle(container: HTMLElement, config: CaptchaConfig) {\n return createPuzzleInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof, TrackRecorder, analyzeTrack } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyPosition, type PuzzleChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '拖动滑块把拼图块移到缺口', success: '验证成功', fail: '未对齐缺口', botFail: '检测到异常操作', refresh: '刷新' },\n en: { title: 'Drag the slider to fit the puzzle', success: 'Verified', fail: 'Not aligned', botFail: 'Bot-like behavior', refresh: 'Refresh' },\n};\n\n// 舞台与拼图块尺寸\nconst STAGE_W = 300;\nconst STAGE_H = 200;\nconst PIECE_W = 50;\nconst MAX_OFFSET = STAGE_W - PIECE_W; // 拼图块可水平移动的最大距离\n\n// 直角转弯轨道尺寸(S 形折返路径)\nconst TRACK_W = 300;\nconst TRACK_H = 100;\nconst HANDLE_W = 40;\nconst HANDLE_H = 40;\n\n// S 形直角转弯路径点(3 段:右 -> 下 -> 左 -> 下 -> 右)\n// 这是一个\"回\"字形折返路径,从左上角出发,到右下角终点\nfunction zigzagPoints(): { x: number; y: number }[] {\n const pts: { x: number; y: number }[] = [];\n const margin = HANDLE_W / 2;\n const w = TRACK_W - margin * 2;\n const h = TRACK_H - margin * 2;\n // 段 1: 从左到右 (y = margin)\n const seg1Steps = 30;\n for (let i = 0; i <= seg1Steps; i++) {\n pts.push({ x: margin + (w / seg1Steps) * i, y: margin });\n }\n // 段 2: 从右到下 (x = TRACK_W - margin, y 增长到中间)\n const seg2Steps = 10;\n const midY = margin + h / 4;\n for (let i = 1; i <= seg2Steps; i++) {\n pts.push({ x: TRACK_W - margin, y: margin + (midY - margin) / seg2Steps * i });\n }\n // 段 3: 从右到左 (y = midY)\n for (let i = 1; i <= seg1Steps; i++) {\n pts.push({ x: TRACK_W - margin - (w / seg1Steps) * i, y: midY });\n }\n // 段 4: 从左到下 (x = margin, y 增长到 3/4)\n const midY2 = margin + (h * 3) / 4;\n for (let i = 1; i <= seg2Steps; i++) {\n pts.push({ x: margin, y: midY + (midY2 - midY) / seg2Steps * i });\n }\n // 段 5: 从左到右 (y = midY2)\n for (let i = 1; i <= seg1Steps; i++) {\n pts.push({ x: margin + (w / seg1Steps) * i, y: midY2 });\n }\n // 段 6: 从右到下终点 (x = TRACK_W - margin, y 到底)\n for (let i = 1; i <= seg2Steps; i++) {\n pts.push({ x: TRACK_W - margin, y: midY2 + (TRACK_H - margin - midY2) / seg2Steps * i });\n }\n return pts;\n}\n\nfunction zigzagPath(pts: { x: number; y: number }[]): string {\n return pts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ');\n}\n\nfunction pointAtProgress(pts: { x: number; y: number }[], progress: number): { x: number; y: number } {\n const idx = Math.min(pts.length - 1, Math.max(0, Math.round(progress * (pts.length - 1))));\n return pts[idx]!;\n}\n\n// piece 的运动轨迹:圆形(与 handle 进度反向)\n// progress 0 -> 1 时,piece 沿圆周从 0° 转到 -360°(逆时针)\nfunction pieceCirclePos(progress: number): { x: number; y: number } {\n const cx = STAGE_W / 2;\n const cy = STAGE_H / 2;\n const radius = Math.min(STAGE_W, STAGE_H) / 2 - PIECE_W / 2 - 8;\n // piece 反向运动:进度增加时角度减小\n const angle = -progress * Math.PI * 2;\n return {\n x: cx + radius * Math.cos(angle) - PIECE_W / 2,\n y: cy + radius * Math.sin(angle) - PIECE_W / 2,\n };\n}\n\nexport function createPuzzleInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n const recorder = new TrackRecorder();\n let current: PuzzleChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n let locked = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n locked = false;\n recorder.clear();\n const gapLeft = (current.gapPosition / 100) * MAX_OFFSET;\n const pts = zigzagPoints();\n const pathD = zigzagPath(pts);\n container.innerHTML = `\n <style>\n .fc-puzzle{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-puzzle[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-puzzle[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-puzzle{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-puzzle-title{font-size:14px;color:var(--fc-text);margin-bottom:12px;text-align:center}\n .fc-puzzle-stage{position:relative;width:${STAGE_W}px;height:${STAGE_H}px;margin:0 auto;border-radius:8px;overflow:hidden;border:1px solid var(--fc-border);background:linear-gradient(135deg,var(--fc-accent-soft),var(--fc-surface) 60%,var(--fc-accent-soft))}\n .fc-puzzle-gap{position:absolute;top:${(STAGE_H - PIECE_W) / 2}px;width:${PIECE_W}px;height:${PIECE_W}px;background:var(--fc-bg);border:2px dashed var(--fc-text-soft);border-radius:6px;box-shadow:inset 0 0 8px rgba(0,0,0,.15);transition:left .15s}\n .fc-puzzle-piece{position:absolute;width:${PIECE_W}px;height:${PIECE_W}px;border-radius:6px;background:linear-gradient(135deg,var(--fc-accent),var(--fc-accent-soft));border:2px solid var(--fc-bg);box-shadow:0 2px 6px rgba(0,0,0,.25);transition:background .15s,border-color .15s}\n .fc-puzzle-track{position:relative;width:${TRACK_W}px;height:${TRACK_H}px;margin:14px auto 0;background:var(--fc-surface);border:1px solid var(--fc-border);border-radius:8px;overflow:hidden}\n .fc-puzzle-svg{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}\n .fc-puzzle-path-bg{fill:none;stroke:var(--fc-border);stroke-width:3;stroke-linecap:round;stroke-linejoin:round}\n .fc-puzzle-path-fg{fill:none;stroke:var(--fc-accent);stroke-width:4;stroke-linecap:round;stroke-linejoin:round;transition:stroke .15s}\n .fc-puzzle-handle{position:absolute;width:${HANDLE_W}px;height:${HANDLE_H}px;background:var(--fc-bg);border:2px solid var(--fc-accent);border-radius:50%;cursor:grab;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0,0,0,.2);user-select:none;touch-action:none;transition:border-color .15s;transform:translate(-50%,-50%);z-index:2}\n .fc-puzzle-handle:active{cursor:grabbing}\n .fc-puzzle-handle::after{content:'\\\\2192';color:var(--fc-accent);font-size:16px;font-weight:bold;transition:color .15s}\n .fc-puzzle-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:var(--fc-success)}\n .fc-puzzle-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer;margin-top:8px;display:block;margin-left:auto;margin-right:auto}\n .fc-puzzle-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-puzzle-done .fc-puzzle-piece{background:linear-gradient(135deg,var(--fc-success),var(--fc-accent-soft));border-color:var(--fc-success)}\n .fc-puzzle-done .fc-puzzle-handle{border-color:var(--fc-success)}\n .fc-puzzle-done .fc-puzzle-handle::after{color:var(--fc-success)}\n .fc-puzzle-done .fc-puzzle-path-fg{stroke:var(--fc-success)}\n </style>\n <div class=\"fc-puzzle\" data-theme=\"${theme}\">\n <div class=\"fc-puzzle-title\">${t.title}</div>\n <div class=\"fc-puzzle-stage\">\n <div class=\"fc-puzzle-gap\" style=\"left:${gapLeft}px\"></div>\n <div class=\"fc-puzzle-piece\" style=\"left:0;top:${(STAGE_H - PIECE_W) / 2}px\"></div>\n </div>\n <div class=\"fc-puzzle-track\">\n <svg class=\"fc-puzzle-svg\" viewBox=\"0 0 ${TRACK_W} ${TRACK_H}\">\n <path class=\"fc-puzzle-path-bg\" d=\"${pathD}\"></path>\n <path class=\"fc-puzzle-path-fg\" d=\"${pathD}\"></path>\n </svg>\n <div class=\"fc-puzzle-handle\" role=\"slider\" tabindex=\"0\"></div>\n </div>\n <div class=\"fc-puzzle-msg\"></div>\n <button class=\"fc-puzzle-refresh\">${t.refresh}</button>\n </div>\n `;\n const root = container.querySelector('.fc-puzzle') as HTMLDivElement;\n const piece = container.querySelector('.fc-puzzle-piece') as HTMLDivElement;\n const track = container.querySelector('.fc-puzzle-track') as HTMLDivElement;\n const handle = container.querySelector('.fc-puzzle-handle') as HTMLDivElement;\n const msg = container.querySelector('.fc-puzzle-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-puzzle-refresh') as HTMLButtonElement;\n const fgPath = container.querySelector('.fc-puzzle-path-fg') as SVGPathElement;\n refreshBtn.addEventListener('click', () => render());\n\n const totalLen = fgPath.getTotalLength();\n fgPath.style.strokeDasharray = `${totalLen}`;\n fgPath.style.strokeDashoffset = `${totalLen}`;\n\n let dragging = false;\n let startPointer = { x: 0, y: 0 };\n let startProgress = 0;\n let currentProgress = 0;\n\n function applyProgress(p: number) {\n currentProgress = Math.max(0, Math.min(1, p));\n // handle 沿直角转弯轨道移动\n const pt = pointAtProgress(pts, currentProgress);\n handle.style.left = `${(pt.x / TRACK_W) * 100}%`;\n handle.style.top = `${(pt.y / TRACK_H) * 100}%`;\n fgPath.style.strokeDashoffset = `${totalLen * (1 - currentProgress)}`;\n // piece 沿圆形路径运动(与进度反向)\n const piecePos = pieceCirclePos(currentProgress);\n piece.style.left = `${piecePos.x}px`;\n piece.style.top = `${piecePos.y}px`;\n }\n\n function bounceBack() {\n handle.style.transition = 'left .35s ease, top .35s ease, border-color .15s';\n piece.style.transition = 'left .35s ease, top .35s ease, background .15s, border-color .15s';\n fgPath.style.transition = 'stroke-dashoffset .35s ease, stroke .15s';\n applyProgress(0);\n setTimeout(() => {\n handle.style.transition = 'border-color .15s';\n piece.style.transition = 'background .15s, border-color .15s';\n fgPath.style.transition = 'stroke .15s';\n }, 360);\n }\n\n applyProgress(0);\n\n handle.addEventListener('pointerdown', (e) => {\n if (done || locked) return;\n dragging = true;\n startPointer = { x: e.clientX, y: e.clientY };\n startProgress = currentProgress;\n recorder.start();\n try { handle.setPointerCapture(e.pointerId); } catch { /* ignore */ }\n e.preventDefault();\n });\n\n handle.addEventListener('pointermove', (e) => {\n if (!dragging) return;\n // S 形路径不自交叉,全路径最近点搜索即可稳定跟手\n // 实时获取 track 坐标(避免页面滚动后坐标过时)\n const rect = track.getBoundingClientRect();\n const sx = rect.width / TRACK_W;\n const sy = rect.height / TRACK_H;\n const svgX = (e.clientX - rect.left) / sx;\n const svgY = (e.clientY - rect.top) / sy;\n let bestLen = 0;\n let bestDist = Infinity;\n const steps = 100;\n for (let i = 0; i <= steps; i++) {\n const len = (i / steps) * totalLen;\n const p = fgPath.getPointAtLength(len);\n const d = (p.x - svgX) * (p.x - svgX) + (p.y - svgY) * (p.y - svgY);\n if (d < bestDist) { bestDist = d; bestLen = len; }\n }\n applyProgress(bestLen / totalLen);\n recorder.record(e.clientX, e.clientY);\n });\n\n const finish = async () => {\n if (!dragging) return;\n dragging = false;\n const trackPoints = recorder.stop();\n\n // piece 当前的水平百分比位置(用于校验是否对齐缺口)\n // piece 走圆形,其对齐缺口的位置是圆形上 x 最接近 gapLeft 的点\n // 简化:当 progress 对应的 piece x 位置在缺口容差内即通过\n const piecePos = pieceCirclePos(currentProgress);\n const pieceLeftPct = (piecePos.x / STAGE_W) * 100;\n if (!verifyPosition(current, pieceLeftPct)) {\n bounceBack();\n msg.style.color = 'var(--fc-danger)';\n msg.textContent = t.fail;\n return;\n }\n\n const analysis = analyzeTrack(trackPoints);\n\n if (analysis.isBot) {\n locked = true;\n msg.style.color = 'var(--fc-danger)';\n msg.textContent = config.locale === 'zh'\n ? `疑似机器人操作(${analysis.reasons.join('、')})`\n : `${t.botFail} detected`;\n bounceBack();\n setTimeout(() => { if (!done) render(); }, 2000);\n return;\n }\n\n done = true;\n root.classList.add('fc-puzzle-done');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n metadata: { humanScore: analysis.humanScore, reasons: analysis.reasons },\n };\n msg.style.color = 'var(--fc-success)';\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n };\n\n handle.addEventListener('pointerup', finish);\n handle.addEventListener('pointercancel', finish);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 拼图缺口题目:随机缺口位置(0-100%),容差 ±5%\nexport interface PuzzleChallenge {\n // 缺口位置百分比 0-100\n gapPosition: number;\n}\n\nconst TOLERANCE = 8; // ±8%(圆形路径更难精确对齐)\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// 生成题目:缺口位置限制在 piece 圆形路径可达范围内\n// piece x = cx + radius*cos(angle) - PIECE_W/2,百分比范围约 19%-64%\n// gap 限制在 25-58 确保 piece 一定能对齐\nexport function generateChallenge(): PuzzleChallenge {\n return { gapPosition: randInt(25, 58) };\n}\n\n// 构造 proof 输入串:gapPosition + ':completed'\nexport function proofInput(c: PuzzleChallenge): string {\n return `${c.gapPosition}:completed`;\n}\n\n// 校验用户拖动位置是否在容差范围内\nexport function verifyPosition(c: PuzzleChallenge, userPos: number): boolean {\n return Math.abs(userPos - c.gapPosition) <= TOLERANCE;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type PuzzleChallenge } from './challenge.js';\n\nexport function issuePuzzleChallenge(): { challenge: PuzzleChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { gapPosition: challenge.gapPosition } };\n}\n\nexport async function verifyPuzzleProof(\n challenge: PuzzleChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig } from '@funnycaptcha/core';\nimport { createAdvancedMathInstance } from './render.js';\n\nexport * from './challenge.js';\n\ndefineCaptcha({\n id: 'advanced-math',\n category: 'recognize',\n create: createAdvancedMathInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '高等数学' : 'Calculus',\n description: locale === 'zh'\n ? '求导、积分、极限等高等数学题,硬核防机器人'\n : 'Derivatives, integrals and limits — hardcore anti-bot.',\n tags: ['math', 'calculus', 'hard', 'recognize'],\n }),\n});\n\nexport { createAdvancedMathInstance } from './render.js';\n\nexport function mountAdvancedMath(container: HTMLElement, config: CaptchaConfig) {\n return createAdvancedMathInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type AdvancedMathChallenge } from './challenge.js';\n\nconst STR = {\n zh: {\n title: '高等数学',\n placeholder: '输入答案(支持 ^ 和 *)',\n submit: '验证',\n fail: '答错了,换一题',\n refresh: '刷新',\n hint: '提示:x²可写为x^2,可省略*',\n },\n en: {\n title: 'Calculus',\n placeholder: 'Enter answer (^ and * supported)',\n submit: 'Verify',\n fail: 'Wrong, try again',\n refresh: 'Refresh',\n hint: 'Tip: x² = x^2, * optional',\n },\n};\n\nexport function createAdvancedMathInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: AdvancedMathChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render(): void {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <style>\n .fc-amath{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-amath[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-amath[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-amath{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-amath-q{display:block;font-size:14px;font-weight:600;color:var(--fc-text);margin-bottom:8px}\n .fc-amath-hint{display:block;font-size:11px;color:var(--fc-text-soft);margin-bottom:12px}\n .fc-amath-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n .fc-amath-input{flex:1;min-width:0;padding:8px 12px;font-size:14px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text);border-radius:8px;box-sizing:border-box;outline:none}\n .fc-amath-input:focus{border-color:var(--fc-accent);box-shadow:0 0 0 3px var(--fc-accent-soft)}\n .fc-amath-btn{padding:8px 16px;font-size:14px;border:1px solid var(--fc-accent);background:var(--fc-accent);color:#fff;border-radius:8px;cursor:pointer;transition:opacity .15s}\n .fc-amath-btn:hover{opacity:.9}\n .fc-amath-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-amath-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-amath-msg{font-size:13px;min-height:18px;margin-top:10px;color:var(--fc-danger)}\n </style>\n <div class=\"fc-amath\" data-theme=\"${theme}\">\n <label class=\"fc-amath-q\">${t.title}: ${current.question}</label>\n <span class=\"fc-amath-hint\">${t.hint}</span>\n <div class=\"fc-amath-row\">\n <input class=\"fc-amath-input\" type=\"text\" autocomplete=\"off\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-amath-btn\">${t.submit}</button>\n <button class=\"fc-amath-refresh\">${t.refresh}</button>\n </div>\n <div class=\"fc-amath-msg\"></div>\n </div>\n `;\n const btn = container.querySelector('.fc-amath-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-amath-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-amath-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-amath-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n btn.addEventListener('click', async () => {\n const val = input.value;\n const success = verifyAnswer(current, val);\n if (!success) {\n msg.textContent = t.fail;\n render();\n return;\n }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`${current.question}=${current.answer}`),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","export type AdvancedMathType = 'derivative' | 'integral' | 'limit' | 'definite';\n\nexport interface AdvancedMathChallenge {\n type: AdvancedMathType;\n question: string;\n answer: string;\n accept: string[];\n}\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[randInt(0, arr.length - 1)] as T;\n}\n\nconst SUP_DIGITS: Record<string, string> = {\n '0': '⁰', '1': '¹', '2': '²', '3': '³', '4': '⁴',\n '5': '⁵', '6': '⁶', '7': '⁷', '8': '⁸', '9': '⁹',\n};\n\nconst SUP_TO_DIGIT: Record<string, string> = {\n '⁰': '0', '¹': '1', '²': '2', '³': '3', '⁴': '4',\n '⁵': '5', '⁶': '6', '⁷': '7', '⁸': '8', '⁹': '9',\n};\n\n/** 将数字转为上标字符串(用于题目展示) */\nfunction sup(n: number): string {\n return String(n).split('').map(d => SUP_DIGITS[d] ?? d).join('');\n}\n\n/**\n * 答案归一化:\n * - 去除前后空格与所有空白\n * - 上标字符转 ^N(x² → x^2)\n * - 数字紧贴字母时补 *(3x → 3*x)\n * - 积分题去掉末尾的 +C / +c\n */\nexport function normalizeAnswer(input: string): string {\n let r = input.trim();\n // 上标数字序列 -> ^N\n r = r.replace(/[⁰¹²³⁴⁵⁶⁷⁸⁹]+/g, (m) =>\n '^' + m.split('').map(c => SUP_TO_DIGIT[c] ?? c).join(''));\n // 去除所有空格\n r = r.replace(/\\s+/g, '');\n // 积分题:去掉末尾 +C / +c\n r = r.replace(/\\+[Cc]$/, '');\n // 数字紧贴字母 -> 插入 *(3x → 3*x,但 3x^2 中的 3x 也处理)\n r = r.replace(/(\\d)([a-zA-Z])/g, '$1*$2');\n return r;\n}\n\n/** 校验用户输入是否命中任一可接受答案(双向归一化后比较) */\nexport function verifyAnswer(challenge: AdvancedMathChallenge, userInput: string): boolean {\n const u = normalizeAnswer(userInput);\n return challenge.accept.some(a => normalizeAnswer(a) === u);\n}\n\n/** 求导:f(x) = x^n,答案 n*x^(n-1) */\nfunction genDerivative(): AdvancedMathChallenge {\n const n = pick([2, 3, 4, 5]);\n const question = `求 f(x) = x${sup(n)} 的导数`;\n const exp = n - 1;\n const answer = `${n}x^${exp}`;\n const accept = [\n `${n}x^${exp}`,\n `${n}x${sup(exp)}`,\n `${n}*x^${exp}`,\n `${n}*x${sup(exp)}`,\n ];\n return { type: 'derivative', question, answer, accept };\n}\n\n/** 不定积分:∫ x^n dx,答案 x^(n+1)/(n+1) + C */\nfunction genIntegral(): AdvancedMathChallenge {\n const n = pick([1, 2, 3]);\n const up = n + 1;\n const question = `求 ∫ x${sup(n)} dx`;\n const answer = `x^${up}/${up} + C`;\n const accept = [\n `x^${up}/${up} + C`,\n `x^${up}/${up}+C`,\n `x^${up}/${up}`,\n `x${sup(up)}/${up} + C`,\n `x${sup(up)}/${up}`,\n `x^${up}/(${up}) + C`,\n `x^${up}/(${up})`,\n `x${sup(up)}/(${up}) + C`,\n `x${sup(up)}/(${up})`,\n ];\n return { type: 'integral', question, answer, accept };\n}\n\n/** 极限:lim(x→a) (x²-a²)/(x-a) = 2a */\nfunction genLimit(): AdvancedMathChallenge {\n const a = pick([1, 2, 3]);\n const question = `求 lim(x→${a}) (x${sup(2)}-${a}${sup(2)})/(x-${a})`;\n const answer = `${2 * a}`;\n const accept = [\n `${2 * a}`,\n `2*${a}`,\n ];\n return { type: 'limit', question, answer, accept };\n}\n\n/** 定积分:∫₀¹ x^n dx = 1/(n+1) */\nfunction genDefinite(): AdvancedMathChallenge {\n const n = pick([1, 2, 3]);\n const up = n + 1;\n const question = `计算 ∫₀¹ x${sup(n)} dx`;\n const answer = `1/${up}`;\n const accept = [\n `1/${up}`,\n `1/(${up})`,\n ];\n return { type: 'definite', question, answer, accept };\n}\n\n/** 随机生成一道高等数学题 */\nexport function generateChallenge(): AdvancedMathChallenge {\n const gen = pick([genDerivative, genIntegral, genLimit, genDefinite]);\n return gen();\n}\n","import { defineCaptcha, type CaptchaConfig } from '@funnycaptcha/core';\nimport { createOlympiadInstance } from './render.js';\n\nexport * from './challenge.js';\n\ndefineCaptcha({\n id: 'olympiad',\n category: 'recognize',\n create: createOlympiadInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '小学奥数' : 'Math Olympiad',\n description: locale === 'zh'\n ? '鸡兔同笼、植树、盈亏等经典奥数题,挡住机器也挡住小学生'\n : 'Classic olympiad word problems — chickens, trees, apples.',\n tags: ['math', 'olympiad', 'word-problem', 'recognize'],\n }),\n});\n\nexport { createOlympiadInstance } from './render.js';\n\nexport function mountOlympiad(container: HTMLElement, config: CaptchaConfig) {\n return createOlympiadInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type OlympiadChallenge } from './challenge.js';\n\nconst STR = {\n zh: {\n title: '小学奥数',\n placeholder: '输入数字答案',\n submit: '验证',\n fail: '答错了,换一题',\n refresh: '刷新',\n },\n en: {\n title: 'Math Olympiad',\n placeholder: 'Enter number',\n submit: 'Verify',\n fail: 'Wrong, try again',\n refresh: 'Refresh',\n },\n};\n\nexport function createOlympiadInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: OlympiadChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render(): void {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <style>\n .fc-olym{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-olym[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-olym[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-olym{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-olym-q{display:block;font-size:14px;font-weight:600;color:var(--fc-text);margin-bottom:12px;line-height:1.5}\n .fc-olym-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n .fc-olym-input{flex:1;min-width:0;padding:8px 12px;font-size:14px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text);border-radius:8px;box-sizing:border-box;outline:none}\n .fc-olym-input:focus{border-color:var(--fc-accent);box-shadow:0 0 0 3px var(--fc-accent-soft)}\n .fc-olym-btn{padding:8px 16px;font-size:14px;border:1px solid var(--fc-accent);background:var(--fc-accent);color:#fff;border-radius:8px;cursor:pointer;transition:opacity .15s}\n .fc-olym-btn:hover{opacity:.9}\n .fc-olym-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-olym-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-olym-msg{font-size:13px;min-height:18px;margin-top:10px;color:var(--fc-danger)}\n </style>\n <div class=\"fc-olym\" data-theme=\"${theme}\">\n <label class=\"fc-olym-q\">${t.title}: ${current.question}</label>\n <div class=\"fc-olym-row\">\n <input class=\"fc-olym-input\" type=\"text\" inputmode=\"numeric\" autocomplete=\"off\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-olym-btn\">${t.submit}</button>\n <button class=\"fc-olym-refresh\">${t.refresh}</button>\n </div>\n <div class=\"fc-olym-msg\"></div>\n </div>\n `;\n const btn = container.querySelector('.fc-olym-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-olym-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-olym-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-olym-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n btn.addEventListener('click', async () => {\n const val = Number(input.value);\n const success = Number.isFinite(val) && verifyAnswer(current, val);\n if (!success) {\n msg.textContent = t.fail;\n render();\n return;\n }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`${current.question}=${current.answer}`),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","export type OlympiadType =\n | 'chicken-rabbit'\n | 'tree-planting'\n | 'age'\n | 'apple'\n | 'travel';\n\nexport interface OlympiadChallenge {\n type: OlympiadType;\n question: string;\n answer: number;\n}\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[randInt(0, arr.length - 1)] as T;\n}\n\n/** 鸡兔同笼:问鸡的数量 */\nfunction genChickenRabbit(): OlympiadChallenge {\n const c = randInt(2, 15); // 鸡\n const r = randInt(2, 10); // 兔\n const heads = c + r;\n const legs = 2 * c + 4 * r;\n return {\n type: 'chicken-rabbit',\n question: `鸡兔同笼,共${heads}个头,${legs}只脚,鸡有几只?`,\n answer: c,\n };\n}\n\n/** 植树问题:两端都种,棵数 = len/gap + 1 */\nfunction genTreePlanting(): OlympiadChallenge {\n const gap = randInt(2, 10);\n const minK = Math.ceil(20 / gap);\n const maxK = Math.floor(100 / gap);\n const k = randInt(minK, maxK);\n const len = gap * k;\n return {\n type: 'tree-planting',\n question: `一条路长${len}米,每隔${gap}米种一棵树,两端都种,共几棵?`,\n answer: len / gap + 1,\n };\n}\n\n/** 年龄问题:几年前爸爸年龄是儿子的5倍。x = (5s - f) / 4 */\nfunction genAge(): OlympiadChallenge {\n for (let attempt = 0; attempt < 200; attempt++) {\n const f = randInt(35, 50);\n const s = randInt(8, 15);\n if (f <= s) continue;\n const num = 5 * s - f;\n if (num <= 0 || num % 4 !== 0) continue;\n const x = num / 4;\n if (x <= 0 || x >= s) continue;\n return {\n type: 'age',\n question: `爸爸${f}岁,儿子${s}岁,几年前爸爸年龄是儿子的5倍?`,\n answer: x,\n };\n }\n // 兜底:已知有效解 f=42, s=10, x=2\n return {\n type: 'age',\n question: '爸爸42岁,儿子10岁,几年前爸爸年龄是儿子的5倍?',\n answer: 2,\n };\n}\n\n/** 盈亏问题:人数 p,每人 a 个多 m,每人 a+1 个少 n。p = (m+n)/(b-a) = m+n */\nfunction genApple(): OlympiadChallenge {\n const p = randInt(3, 12);\n const a = randInt(3, 6);\n const b = a + 1;\n const m = randInt(1, p - 1);\n const n = p - m;\n return {\n type: 'apple',\n question: `分苹果,每人${a}个多${m}个,每人${b}个少${n}个,几人?`,\n answer: p,\n };\n}\n\n/** 行程问题:相向而行,相遇时间 = d / (v1+v2),保证整除 */\nfunction genTravel(): OlympiadChallenge {\n const v1 = randInt(3, 12);\n const v2 = randInt(3, 12);\n const t = randInt(2, 8);\n const d = (v1 + v2) * t;\n return {\n type: 'travel',\n question: `甲乙相向而行,甲速${v1}km/h,乙速${v2}km/h,相距${d}km,几小时后相遇?`,\n answer: t,\n };\n}\n\n/** 随机生成一道小学奥数题 */\nexport function generateChallenge(): OlympiadChallenge {\n const gen = pick([\n genChickenRabbit,\n genTreePlanting,\n genAge,\n genApple,\n genTravel,\n ]);\n return gen();\n}\n\n/** 校验答案(整数比较) */\nexport function verifyAnswer(c: OlympiadChallenge, userAnswer: number): boolean {\n return c.answer === userAnswer;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createEquationBalanceInstance } from './render.js';\n\nexport * from './challenge.js';\n\nexport const equationBalancePlugin: CaptchaPlugin = {\n id: 'equation-balance',\n category: 'recognize',\n create: createEquationBalanceInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '配平化学方程式' : 'Balance Equation',\n description: locale === 'zh'\n ? '填入正确的化学计量数,配平方程式'\n : 'Enter the right stoichiometric coefficients.',\n tags: ['chemistry', 'equation', 'balance'],\n }),\n};\n\ndefineCaptcha(equationBalancePlugin);\n\nexport function mountEquationBalance(container: HTMLElement, config: CaptchaConfig) {\n return createEquationBalanceInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type EquationBalanceChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '配平化学方程式', submit: '验证', fail: '系数不对,再试试', refresh: '换一题', hint: '系数为1也要填' },\n en: { title: 'Balance the equation', submit: 'Verify', fail: 'Wrong coefficients', refresh: 'Next', hint: 'Enter 1 if needed' },\n};\n\nexport function createEquationBalanceInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const theme = config.theme ?? 'light';\n let current: EquationBalanceChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n // reactants 拼上 products,对应 coefficients 顺序\n const items = [...current.reactants, ...current.products];\n const R = current.reactants.length;\n const eqHtml = items.map((formula, i) => {\n // 0 之前无分隔;跨越反应物→生成物用 →;其余用 +\n const sep = i === 0 ? '' : i === R ? '→' : '+';\n const sepHtml = sep ? `<span class=\"fc-eqbal-sep\">${sep}</span>` : '';\n return `${sepHtml}<span class=\"fc-eqbal-term\"><input class=\"fc-eqbal-coef\" type=\"text\" inputmode=\"numeric\" maxlength=\"2\" aria-label=\"${formula}\" /><span class=\"fc-eqbal-formula\">${formula}</span></span>`;\n }).join('');\n container.innerHTML = `\n <style>\n .fc-eqbal{font-family:-apple-system,system-ui,sans-serif;max-width:420px;width:100%;padding:16px;box-sizing:border-box}\n .fc-eqbal[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-eqbal[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-eqbal{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-eqbal-title{display:block;font-size:14px;font-weight:600;color:var(--fc-text);margin-bottom:4px}\n .fc-eqbal-hint{display:block;font-size:12px;color:var(--fc-text-soft);margin-bottom:12px}\n .fc-eqbal-eq{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:18px;color:var(--fc-text);margin-bottom:14px;justify-content:center}\n .fc-eqbal-term{display:inline-flex;align-items:center;gap:4px}\n .fc-eqbal-sep{color:var(--fc-text-soft);font-weight:600}\n .fc-eqbal-formula{font-family:'SF Mono',ui-monospace,monospace}\n .fc-eqbal-coef{width:40px;text-align:center;padding:4px 0;font-size:16px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text);border-radius:6px;outline:none;box-sizing:border-box}\n .fc-eqbal-coef:focus{border-color:var(--fc-accent);box-shadow:0 0 0 3px var(--fc-accent-soft)}\n .fc-eqbal-row{display:flex;gap:8px;align-items:center;justify-content:center}\n .fc-eqbal-btn{padding:8px 16px;font-size:14px;border:1px solid var(--fc-accent);background:var(--fc-accent);color:#fff;border-radius:8px;cursor:pointer;transition:opacity .15s}\n .fc-eqbal-btn:hover{opacity:.9}\n .fc-eqbal-refresh{padding:4px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-eqbal-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-eqbal-msg{font-size:13px;min-height:18px;margin-top:10px;text-align:center;color:var(--fc-danger)}\n </style>\n <div class=\"fc-eqbal\" data-theme=\"${theme}\">\n <label class=\"fc-eqbal-title\">${t.title}</label>\n <span class=\"fc-eqbal-hint\">${t.hint}</span>\n <div class=\"fc-eqbal-eq\">${eqHtml}</div>\n <div class=\"fc-eqbal-row\">\n <button class=\"fc-eqbal-btn\">${t.submit}</button>\n <button class=\"fc-eqbal-refresh\">${t.refresh}</button>\n </div>\n <div class=\"fc-eqbal-msg\"></div>\n </div>\n `;\n const inputs = Array.from(container.querySelectorAll<HTMLInputElement>('.fc-eqbal-coef'));\n const btn = container.querySelector('.fc-eqbal-btn') as HTMLButtonElement;\n const msg = container.querySelector('.fc-eqbal-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-eqbal-refresh') as HTMLButtonElement;\n refreshBtn.addEventListener('click', render);\n btn.addEventListener('click', async () => {\n const vals = inputs.map(i => Number(i.value));\n if (!verifyAnswer(current, vals)) {\n msg.textContent = t.fail;\n return;\n }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`equation-balance:${current.reactants.join('+')}>${current.products.join('+')}:${current.coefficients.join(',')}`),\n duration: Date.now() - startTime,\n };\n msg.textContent = '';\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n inputs.forEach((input, idx) => {\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n if (idx === 0) input.focus();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 化学方程式配平题目:用户需填入正确的化学计量数\nexport interface EquationBalanceChallenge {\n // 反应物化学式列表\n reactants: string[];\n // 生成物化学式列表\n products: string[];\n // 正确系数,顺序为 reactants 拼上 products\n coefficients: number[];\n}\n\ninterface EquationDef {\n reactants: string[];\n products: string[];\n coefficients: number[];\n}\n\n// 题库:reactants + products 拼起来对应 coefficients 顺序\nconst EQUATIONS: readonly EquationDef[] = [\n { reactants: ['H₂', 'O₂'], products: ['H₂O'], coefficients: [2, 1, 2] },\n { reactants: ['Fe', 'O₂'], products: ['Fe₂O₃'], coefficients: [4, 3, 2] },\n { reactants: ['CH₄', 'O₂'], products: ['CO₂', 'H₂O'], coefficients: [1, 2, 1, 2] },\n { reactants: ['C₂H₆', 'O₂'], products: ['CO₂', 'H₂O'], coefficients: [2, 7, 4, 6] },\n { reactants: ['Al', 'HCl'], products: ['AlCl₃', 'H₂'], coefficients: [2, 6, 2, 3] },\n { reactants: ['N₂', 'H₂'], products: ['NH₃'], coefficients: [1, 3, 2] },\n];\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[Math.floor(Math.random() * arr.length)] as T;\n}\n\n// 生成题目:随机返回一个方程式(深拷贝避免污染题库)\nexport function generateChallenge(): EquationBalanceChallenge {\n const eq = pick(EQUATIONS);\n return {\n reactants: [...eq.reactants],\n products: [...eq.products],\n coefficients: [...eq.coefficients],\n };\n}\n\n// 校验用户填入的系数是否完全正确(系数为 1 也必须填 1)\nexport function verifyAnswer(\n c: EquationBalanceChallenge,\n userCoefficients: number[],\n): boolean {\n if (userCoefficients.length !== c.coefficients.length) return false;\n return c.coefficients.every((coef, i) => coef === userCoefficients[i]);\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createRandomHuntInstance } from './render.js';\n\nexport * from './challenge.js';\n\nexport const randomHuntPlugin: CaptchaPlugin = {\n id: 'random-hunt',\n category: 'game',\n create: createRandomHuntInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '随机数猎手' : 'Random Hunter',\n description: locale === 'zh'\n ? '数字飞速变化,在满足条件的瞬间按下捕获'\n : 'Numbers flash fast — catch the moment it matches the rule.',\n tags: ['game', 'reaction', 'timed'],\n }),\n};\n\ndefineCaptcha(randomHuntPlugin);\n\nexport function mountRandomHunt(container: HTMLElement, config: CaptchaConfig) {\n return createRandomHuntInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, conditionLabel, type RandomHuntChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '随机数猎手', hunt: '捕获', success: '捕获成功!', fail: '不符合条件,继续', refresh: '换条件', tip: '当数字满足条件时点击捕获' },\n en: { title: 'Random Hunter', hunt: 'Catch!', success: 'Caught it!', fail: 'Not matching, keep going', refresh: 'New rule', tip: 'Click when the number matches' },\n};\n\n// 数字闪现间隔(ms)\nconst TICK_MS = 120;\n// 随机数范围\nconst MIN = 0;\nconst MAX = 99;\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport function createRandomHuntInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n const locale = config.locale;\n const theme = config.theme ?? 'light';\n let current: RandomHuntChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let timer: ReturnType<typeof setInterval> | null = null;\n let number = 0;\n let succeeded = false;\n\n function clearTimer() {\n if (timer) { clearInterval(timer); timer = null; }\n }\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n succeeded = false;\n clearTimer();\n number = randInt(MIN, MAX);\n container.innerHTML = `\n <style>\n .fc-hunt{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}\n .fc-hunt[data-theme=\"light\"]{--fc-bg:#ffffff;--fc-surface:#f6f7f9;--fc-text:#0f172a;--fc-text-soft:#64748b;--fc-border:#e2e8f0;--fc-accent:#6366f1;--fc-accent-soft:#eef2ff;--fc-success:#16a34a;--fc-danger:#dc2626}\n .fc-hunt[data-theme=\"dark\"]{--fc-bg:#1e2544;--fc-surface:#171c36;--fc-text:#e5e9f0;--fc-text-soft:#94a3b8;--fc-border:#2a3358;--fc-accent:#818cf8;--fc-accent-soft:#252b5c;--fc-success:#4ade80;--fc-danger:#f87171}\n .fc-hunt{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}\n .fc-hunt-title{font-size:15px;font-weight:600;color:var(--fc-text);text-align:center;margin-bottom:4px}\n .fc-hunt-cond{font-size:13px;color:var(--fc-accent);text-align:center;margin-bottom:4px;font-weight:600}\n .fc-hunt-tip{font-size:12px;color:var(--fc-text-soft);text-align:center;margin-bottom:12px}\n .fc-hunt-num{font-family:'SF Mono',ui-monospace,monospace;font-size:48px;text-align:center;color:var(--fc-text);min-height:56px;line-height:56px;transition:opacity .08s ease-out,color .15s ease-out;background:var(--fc-surface);border:1px solid var(--fc-border);border-radius:10px;margin-bottom:14px}\n .fc-hunt-num-success{color:var(--fc-success);border-color:var(--fc-success)}\n .fc-hunt-row{display:flex;gap:8px;align-items:center;justify-content:center}\n .fc-hunt-btn{flex:1;padding:12px 16px;font-size:16px;font-weight:600;border:1px solid var(--fc-accent);background:var(--fc-accent);color:#fff;border-radius:8px;cursor:pointer;transition:opacity .15s}\n .fc-hunt-btn:hover{opacity:.9}\n .fc-hunt-btn:disabled{opacity:.5;cursor:not-allowed}\n .fc-hunt-refresh{padding:8px 12px;font-size:12px;border:1px solid var(--fc-border);background:var(--fc-surface);color:var(--fc-text-soft);border-radius:6px;cursor:pointer}\n .fc-hunt-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}\n .fc-hunt-msg{font-size:13px;min-height:18px;margin-top:10px;text-align:center;color:var(--fc-danger)}\n </style>\n <div class=\"fc-hunt\" data-theme=\"${theme}\">\n <div class=\"fc-hunt-title\">${t.title}</div>\n <div class=\"fc-hunt-cond\">${t.hunt}:${conditionLabel(current.condition, locale)}</div>\n <div class=\"fc-hunt-tip\">${t.tip}</div>\n <div class=\"fc-hunt-num\">${number}</div>\n <div class=\"fc-hunt-row\">\n <button class=\"fc-hunt-btn\">${t.hunt}</button>\n <button class=\"fc-hunt-refresh\">${t.refresh}</button>\n </div>\n <div class=\"fc-hunt-msg\"></div>\n </div>\n `;\n const numEl = container.querySelector('.fc-hunt-num') as HTMLDivElement | null;\n const btn = container.querySelector('.fc-hunt-btn') as HTMLButtonElement;\n const msg = container.querySelector('.fc-hunt-msg') as HTMLDivElement;\n const refreshBtn = container.querySelector('.fc-hunt-refresh') as HTMLButtonElement;\n\n refreshBtn.addEventListener('click', render);\n\n // 每 120ms 闪现一个随机数,加微弱闪烁感\n timer = setInterval(() => {\n if (succeeded) return;\n number = randInt(MIN, MAX);\n if (numEl) {\n numEl.textContent = String(number);\n numEl.style.opacity = '0.55';\n requestAnimationFrame(() => { numEl.style.opacity = '1'; });\n }\n }, TICK_MS);\n\n btn.addEventListener('click', async () => {\n if (succeeded) return;\n const ok = current.check(number);\n if (!ok) {\n msg.textContent = t.fail;\n return;\n }\n // 成功:数字变绿,停止 interval\n succeeded = true;\n clearTimer();\n if (numEl) numEl.classList.add('fc-hunt-num-success');\n btn.disabled = true;\n const duration = Date.now() - startTime;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`random-hunt:${current.condition}:${duration}`),\n duration,\n metadata: { condition: current.condition, number },\n };\n msg.textContent = t.success;\n msg.style.color = 'var(--fc-success)';\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { clearTimer(); container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 随机数猎手题目:给定一个条件,用户在数字满足条件的瞬间点击捕获\nexport interface RandomHuntChallenge {\n // 条件标识(稳定的 key,用于构造 proof)\n condition: string;\n // 判断函数:给定数字是否满足条件(不可序列化,本验证码不做后端校验)\n check: (n: number) => boolean;\n}\n\nexport type HuntLocale = 'zh' | 'en';\n\ninterface ConditionDef {\n key: string;\n zh: string;\n en: string;\n check: (n: number) => boolean;\n}\n\nfunction isPrime(n: number): boolean {\n if (n < 2) return false;\n for (let i = 2; i * i <= n; i++) {\n if (n % i === 0) return false;\n }\n return true;\n}\n\n// 条件库:随机选一个\nconst CONDITIONS: readonly ConditionDef[] = [\n { key: 'gt50', zh: '大于 50', en: 'greater than 50', check: n => n > 50 },\n { key: 'lt20', zh: '小于 20', en: 'less than 20', check: n => n < 20 },\n { key: 'even', zh: '偶数', en: 'even number', check: n => n % 2 === 0 },\n { key: 'odd', zh: '奇数', en: 'odd number', check: n => n % 2 === 1 },\n { key: 'mult3', zh: '3 的倍数', en: 'multiple of 3', check: n => n % 3 === 0 },\n { key: 'gt50-even', zh: '大于 50 的偶数', en: 'even and > 50', check: n => n > 50 && n % 2 === 0 },\n { key: 'lt30-odd', zh: '小于 30 的奇数', en: 'odd and < 30', check: n => n < 30 && n % 2 === 1 },\n { key: 'prime', zh: '质数', en: 'prime number', check: n => isPrime(n) },\n];\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[Math.floor(Math.random() * arr.length)] as T;\n}\n\n// 生成题目:随机返回一个条件\nexport function generateChallenge(): RandomHuntChallenge {\n const c = pick(CONDITIONS);\n return { condition: c.key, check: c.check };\n}\n\n// 根据条件 key 与 locale 返回人类可读的条件描述\nexport function conditionLabel(condition: string, locale: HuntLocale): string {\n const def = CONDITIONS.find(c => c.key === condition);\n if (!def) return condition;\n return locale === 'zh' ? def.zh : def.en;\n}\n"],"mappings":"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","names":["render","generateChallenge","verifyAnswer","STR","render","generateChallenge","STR","render","generateChallenge","hashProof","generateChallenge","proofInput","STR","render","generateChallenge","randInt","generateChallenge","proofInput","STR","render","randInt","shuffle","generateChallenge","STR","render","generateChallenge","randInt","shuffle","generateChallenge","verifyAnswer","STR","render","pick","randInt","shuffle","generateChallenge","verifyAnswer","STR","render","pick","generateChallenge","proofInput","STR","render","generateChallenge","proofInput","STR","render","generateChallenge","AREA_W","AREA_H","MIN_DIST","randInt","shuffle","generateChallenge","proofInput","STR","AREA_W","AREA_H","BOX_W","BOX_H","MIN_DIST","generateSlots","render","generateChallenge","proofInput","randInt","shuffle","c","generateChallenge","proofInput","verifyAnswer","STR","render","pick","randInt","generateChallenge","proofInput","STR","HANDLE_W","HANDLE_H","pointAtProgress","render","randInt","pick","verifyAnswer","generateChallenge","STR","render","randInt","pick","generateChallenge","verifyAnswer","STR","render","pick","generateChallenge","verifyAnswer","STR","render","pick","generateChallenge","STR","randInt","render"]}
|