基於義務教育階段數學的網頁年齡驗證機制。This is a JavaScript age verification system that requires visitors to solve a math problem from the K–12 curriculum before accessing contents.

喔呀?
你因爲
數學不好
被傳送到了
這個頁面?
沒事沒事。
放心,所有答案都在這裡了。
起因 #
2026年6月,在網頁加入數學題用於驗證訪客年齡是否成年。
答對了有獎勵,答錯了有懲罰(畏怖)
在色色的頁面插入數學題驗證訪客年齡。例如這個頁面:個人推薦10部實用的裏番



事由:最近很多國家的法律紛紛要求加強網站驗證使用者年齡,於是我就在想,不侵犯使用者隱私的年齡驗證方法…就是加入數學題!驗證你高中有沒有畢業。讓我看看你發育正不正常啊!
用JavaScript在色色NSFW的頁面插入題目。重新整理頁面可換題目。爲了防止直接右鍵Google算式,使用CSS禁止選取文字。爲了簡化設計我沒有使用LaTeX渲染公式。
數學很好!是宇宙語言,外國人不用翻譯也看得懂。
透過一系列跨冊跨科目的隨機題目檢測訪客的年齡。內容包含冪次、開根號、三角函數、對數、插值法、微積分的簡單運算。微積分因爲一些高中文組沒有教所以把抽到的機率設很低。這些題目的程度應該是臺灣、中國大陸、美國高中生都能解出來的簡單題目。
破解這個年齡驗證JavaScript的方法很簡單,因爲大體沿用Hugo年齡驗證程式碼,就是在瀏覽器針對ivonblog.com放一塊「登dua郎」臺羅拼音的「餅乾」,或是直接用uBlock Origin封鎖元素。但是知道怎麼繞過去的人,應該不會連這些題目都答不出來吧。
題目處理邏輯如下。嗯,就是這麼暴力簡單。我原本的設計還想把題目存在Cloudflare Workers然後交換金鑰解密後才會回傳答案,避免script kiddie直接看到答案作弊。
<script>
(function () {
const cookieName = "tng-toa-lang"; // tńg-tōa-lâng. Are you adult?
const cookieValue = "yes";
const cookieMaxAge = 86400;
const rickrollUrl = "https://ivonblog.com/posts/hugo-age-verification-with-math-questions/";
const YOU_SEE_THIS_NOW_YOU_ARE_THE_CHEATER = "";
const normalQuestions = [
{ question: "2^6 + √100 = ?", encodedAnswers: ["NzQ="] },
{ question: "√144 + 3^2 = ?", encodedAnswers: ["MjE="] },
{ question: "2 × sin(0°) = ?", encodedAnswers: ["MA=="] },
{ question: "cos(90°) x 999 = ?", encodedAnswers: ["MA=="] },
{ question: "tan(45°) x 5 = ?", encodedAnswers: ["NQ=="] },
{ question: "cos(90°) + sin(0°) = ?", encodedAnswers: ["MA=="] },
{ question: "log10(1000) = ?", encodedAnswers: ["Mw=="] },
{ question: "log10(100) = ?", encodedAnswers: ["Mg=="] },
{ question: "log10(10) = ?", encodedAnswers: ["MQ=="] },
{ question: "log10(1) = ?", encodedAnswers: ["MA=="] },
{ question: "x + 7 = 13, x = ?", encodedAnswers: ["Ng=="] },
{ question: "3x = 21, x = ?", encodedAnswers: ["Nw=="] },
{ question: "3² + 4² = c². c = ?", encodedAnswers: ["NQ=="] },
{ question: "6² + 8² = c². c = ?", encodedAnswers: ["MTA="] },
{ question: "| -9 | = ?", encodedAnswers: ["OQ=="] },
{ question: "| -7 | = ?", encodedAnswers: ["Nw=="] },
{ question: "| -8 | = ?", encodedAnswers: ["OA=="] },
{ question: "| (-9) + 8 | = ?", encodedAnswers: ["MQ=="] },
{ question: "| (-731) + 731| = ?", encodedAnswers: ["MA=="] },
{ question: "| (-228) - 228 | = ?", encodedAnswers: ["NDU2"] },
{ question: "| -67 | = ?", encodedAnswers: ["Njc="] },
{ question: "| -1 - (1) | = ?", encodedAnswers: ["Mg=="] },
{ question: "| 666 - (-333) | = ?", encodedAnswers: ["OTk5"] },
{ question: "P = 1, Q = 0. P ∧ Q = ?", encodedAnswers: ["MA=="] },
{ question: "P = 1, Q = 0. P ∨ Q = ?", encodedAnswers: ["MQ=="] },
{ question: "∫_0^1 2x dx = ?", encodedAnswers: ["MQ=="] },
{ question: "∫_0^2 2x dx = ?", encodedAnswers: ["NA=="] },
{ question: "∫_0^3 2x dx = ?", encodedAnswers: ["OQ=="] },
{ question: "∫_0^4 2x dx = ?", encodedAnswers: ["MTY="] }
];
const luckyQuestions = [
{ question: "f'(c) = (f(3)-f(1))/(3-1), f(3)=7, f(1)=3, f'(c)=?", encodedAnswers: ["Mg=="] },
{ question: "∫_0^5 2x dx = ?", encodedAnswers: ["MjU="] },
{ question: "Σ(k=1..10) k + Σ(k=1..10)(11-k) - 10 = ?", encodedAnswers: ["MTAw"] }
];
const luckyRate = 0.2;
const overlay = document.getElementById("content-warning-overlay");
const closeTarget = document.getElementById("content-warning-accepted");
if (!overlay) return;
const hasAccepted = document.cookie
.split("; ")
.some((cookie) => cookie === cookieName + "=" + cookieValue);
const unlockPage = function () {
document.documentElement.classList.remove("content-warning-locked");
};
const closeWarning = function () {
overlay.remove();
if (closeTarget) closeTarget.remove();
unlockPage();
};
if (hasAccepted) {
closeWarning();
return;
}
document.documentElement.classList.add("content-warning-locked");
const intro = overlay.querySelector("[data-content-warning-intro]");
const startButton = overlay.querySelector("[data-content-warning-start]");
const questionForm = overlay.querySelector("[data-content-warning-question]");
const questionText = overlay.querySelector("[data-content-warning-question-text]");
const answerInput = overlay.querySelector("[data-content-warning-answer]");
const feedback = overlay.querySelector("[data-content-warning-feedback]");
const declineButtons = overlay.querySelectorAll("[data-content-warning-decline]");
let currentQuestion = null;
const pickQuestion = function () {
const pool = Math.random() < luckyRate
? luckyQuestions
: normalQuestions;
return pool[Math.floor(Math.random() * pool.length)];
};
const normalizeAnswer = function (value) {
return String(value)
.trim()
.toLowerCase()
.replace(/\s+/g, "")
.replace(/ /g, "")
.replace(/×/g, "*")
.replace(/−/g, "-")
.replace(/,/g, ",")
.replace(/。/g, ".")
.replace(/²/g, "^2")
.replace(/³/g, "^3");
};
const decodeAnswer = function (value) {
return atob(value);
};
const isCorrectAnswer = function (input, question) {
const normalizedInput = normalizeAnswer(input);
const answers = Array.isArray(question.encodedAnswers) ? question.encodedAnswers.map(decodeAnswer) : question.answers;
return answers.some(function (answer) {
return normalizeAnswer(answer) === normalizedInput;
});
};
const setAcceptedCookie = function () {
const secure = window.location.protocol === "https:" ? "; Secure" : "";
document.cookie =
cookieName +
"=" +
cookieValue +
"; path=/; max-age=" +
cookieMaxAge +
"; SameSite=Lax" +
secure;
};
const showQuestion = function () {
currentQuestion = pickQuestion();
if (intro) intro.hidden = true;
if (questionForm) questionForm.hidden = false;
if (questionText) questionText.textContent = currentQuestion.question;
if (feedback) feedback.textContent = "";
if (answerInput) {
answerInput.value = "";
window.setTimeout(function () {
answerInput.focus();
}, 0);
}
};
const passGate = function () {
setAcceptedCookie();
closeWarning();
};
const failGate = function () {
window.location.href = rickrollUrl;
};
if (startButton) {
startButton.addEventListener("click", function () {
showQuestion();
});
}
if (questionForm) {
questionForm.addEventListener("submit", function (event) {
event.preventDefault();
if (!currentQuestion) {
showQuestion();
return;
}
if (isCorrectAnswer(answerInput ? answerInput.value : "", currentQuestion)) {
passGate();
} else {
failGate();
}
});
}
declineButtons.forEach(function (button) {
button.addEventListener("click", function (event) {
event.preventDefault();
failGate();
});
});
})();
</script>(題目滾動式更新,請勿洩題。)
本實驗爲期一個月,將搭配Google Analytics觀察跳出率變化,看看數學究竟能夠擊退多少訪客。
一個月後再回來看看成果。
結果 #
一個月前,我做了一個實驗,在查看網站NSFW內容之前需要通過數學題的年齡驗證,用來看看你發育正不正常。看看這會對網站流量造成多大影響。
題目大概高中數學程度,最難的題目也就簡單微積分以及拉格朗日多項式而已的說。

結論是:好像沒差?從Google Analytics後臺資料來看,網站的幾個熱門NSFW網頁流量還是很高,參與度(87%)和跳出率(12%)都沒有顯著變化。一堆人成功通過了考驗,看來大家的文化水準都很高。也有可能AI解題真的太方便了。
好的,所以這個年齡驗證機制我就決定保留下來了!我會酬情增加送分題(判斷絕對值)的比例的。
刪除 #
幾天後。
因爲急用,我快爆炸了,卻在看自己文章的時候忘記數學題答案被擋在外面5分鐘,感覺龜懶趴火,所以我決定把blog年齡驗證數學題的機制刪除了。實驗一個月我蒐集到的資料也滿意了。
啊啊~我終究覺得年齡驗證這個框框會阻礙資訊自由流通,成爲變相paywall(儘管你只要付出腦力就能通過)。還有NoScript和RSS用戶表示他們關不掉數學題驗證,影響閱讀,所以我決定還是刪除了,現在各位可以自由色色啦。
但形式上的年齡驗證提示框還是要有,目的是爲了防止被iWIN之類的機構找碴,故需要在進入網站前設立R18防護機制。年齡驗證提示本身是用HTML與CSS實現的,在沒有JavaScript的環境也能關掉。