@funnycaptcha/math 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/index.js +26 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -28,11 +28,12 @@ function verifyAnswer(c, userAnswer) {
|
|
|
28
28
|
// src/render.ts
|
|
29
29
|
import { hashProof } from "@funnycaptcha/core";
|
|
30
30
|
var STR = {
|
|
31
|
-
zh: { title: "\u8BF7\u8BA1\u7B97", placeholder: "\u8F93\u5165\u7B54\u6848", submit: "\u9A8C\u8BC1", fail: "\u7B54\u9519\u4E86\uFF0C\u6362\u4E00\u9898" },
|
|
32
|
-
en: { title: "Solve", placeholder: "Enter answer", submit: "Verify", fail: "Wrong, try again" }
|
|
31
|
+
zh: { title: "\u8BF7\u8BA1\u7B97", placeholder: "\u8F93\u5165\u7B54\u6848", submit: "\u9A8C\u8BC1", fail: "\u7B54\u9519\u4E86\uFF0C\u6362\u4E00\u9898", refresh: "\u5237\u65B0" },
|
|
32
|
+
en: { title: "Solve", placeholder: "Enter answer", submit: "Verify", fail: "Wrong, try again", refresh: "Refresh" }
|
|
33
33
|
};
|
|
34
34
|
function createMathInstance(container, config) {
|
|
35
35
|
const t = STR[config.locale];
|
|
36
|
+
const theme = config.theme ?? "light";
|
|
36
37
|
let current;
|
|
37
38
|
let listeners = [];
|
|
38
39
|
let startTime = Date.now();
|
|
@@ -40,16 +41,36 @@ function createMathInstance(container, config) {
|
|
|
40
41
|
current = generateChallenge();
|
|
41
42
|
startTime = Date.now();
|
|
42
43
|
container.innerHTML = `
|
|
43
|
-
<
|
|
44
|
+
<style>
|
|
45
|
+
.fc-math{font-family:-apple-system,system-ui,sans-serif;max-width:360px;width:100%;padding:16px;box-sizing:border-box}
|
|
46
|
+
.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}
|
|
47
|
+
.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}
|
|
48
|
+
.fc-math{background:var(--fc-bg);border:1px solid var(--fc-border);border-radius:10px;color:var(--fc-text)}
|
|
49
|
+
.fc-math-q{display:block;font-size:14px;font-weight:600;color:var(--fc-text);margin-bottom:12px}
|
|
50
|
+
.fc-math-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
51
|
+
.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}
|
|
52
|
+
.fc-math-input:focus{border-color:var(--fc-accent);box-shadow:0 0 0 3px var(--fc-accent-soft)}
|
|
53
|
+
.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}
|
|
54
|
+
.fc-math-btn:hover{opacity:.9}
|
|
55
|
+
.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}
|
|
56
|
+
.fc-math-refresh:hover{border-color:var(--fc-accent);color:var(--fc-accent)}
|
|
57
|
+
.fc-math-msg{font-size:13px;min-height:18px;margin-top:10px;color:var(--fc-danger)}
|
|
58
|
+
</style>
|
|
59
|
+
<div class="fc-math" data-theme="${theme}">
|
|
44
60
|
<label class="fc-math-q">${t.title}: ${current.question}</label>
|
|
45
|
-
<
|
|
46
|
-
|
|
61
|
+
<div class="fc-math-row">
|
|
62
|
+
<input class="fc-math-input" type="text" inputmode="numeric" placeholder="${t.placeholder}" />
|
|
63
|
+
<button class="fc-math-btn">${t.submit}</button>
|
|
64
|
+
<button class="fc-math-refresh">${t.refresh}</button>
|
|
65
|
+
</div>
|
|
47
66
|
<div class="fc-math-msg"></div>
|
|
48
67
|
</div>
|
|
49
68
|
`;
|
|
50
69
|
const btn = container.querySelector(".fc-math-btn");
|
|
51
70
|
const input = container.querySelector(".fc-math-input");
|
|
52
71
|
const msg = container.querySelector(".fc-math-msg");
|
|
72
|
+
const refreshBtn = container.querySelector(".fc-math-refresh");
|
|
73
|
+
refreshBtn.addEventListener("click", render);
|
|
53
74
|
btn.addEventListener("click", async () => {
|
|
54
75
|
const val = Number(input.value);
|
|
55
76
|
const success = verifyAnswer(current, val);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/challenge.ts","../src/render.ts","../src/verifier.ts"],"sourcesContent":["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
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/challenge.ts","../src/render.ts","../src/verifier.ts"],"sourcesContent":["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"],"mappings":"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","names":["hashProof","hashProof"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnycaptcha/math",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Arithmetic captcha — FunnyChapter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@funnycaptcha/core": "0.
|
|
18
|
+
"@funnycaptcha/core": "0.6.0"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public",
|