feat: easter egg théière 418 + Konami obfusqué via global.js centralisé
- /418/ (et /fr|en/418) : vraie page HTTP 418 'Je suis une théière', SVG
animé (vapeur), boutons café (refus RFC 2324) / thé (toast), noindex.
- Nouveau public/js/global.js chargé sur toutes les pages (statiques,
dynamiques articles/blog, page d'erreur) qui :
* écoute le code Konami ↑↑↓↓←→←→BA → redirige vers /418/ ;
* charge ses clés par codes de caractères (aucune chaîne révélatrice en
clair : ni 'ArrowUp', ni '/418/', ni 'b/a' dans la source) ;
* sert de dispatcher centralisé : injecte par page (components/discord/
zfprobe) via <body data-page=...> ou règles de chemin, sans doublon.
- easter.js supprimé au profit de global.js unique sur 22 pages statiques.
This commit is contained in:
parent
a95a287327
commit
1482c6aaf0
24 changed files with 217 additions and 0 deletions
|
|
@ -47,6 +47,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@
|
||||||
<link rel="me" href="https://piaille.fr/@zektyc" />
|
<link rel="me" href="https://piaille.fr/@zektyc" />
|
||||||
<link rel="stylesheet" href="/css/style.css?v=8" />
|
<link rel="stylesheet" href="/css/style.css?v=8" />
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
<script src="/js/i18n.js?v=3" defer></script>
|
<script src="/js/i18n.js?v=3" defer></script>
|
||||||
<script src="/js/discord.js?v=1" defer></script>
|
<script src="/js/discord.js?v=1" defer></script>
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/css/style.css?v=8" />
|
<link rel="stylesheet" href="/css/style.css?v=8" />
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/i18n.js" defer></script>
|
<script src="/js/i18n.js" defer></script>
|
||||||
<script>document.addEventListener("click",function(e){var t=e.target.closest?e.target.closest(".site-nav"):null;if(t){var n=document.getElementById("nav-toggle");if(n)n.checked=false}})</script>
|
<script>document.addEventListener("click",function(e){var t=e.target.closest?e.target.closest(".site-nav"):null;if(t){var n=document.getElementById("nav-toggle");if(n)n.checked=false}})</script>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
<script src="/js/zfprobe.js?v=15" defer></script>
|
<script src="/js/zfprobe.js?v=15" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
<script src="/js/zfprobe.js?v=15" defer></script>
|
<script src="/js/zfprobe.js?v=15" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
<script src="/js/i18n.js?v=3" defer></script>
|
<script src="/js/i18n.js?v=3" defer></script>
|
||||||
<script src="/js/discord.js?v=1" defer></script>
|
<script src="/js/discord.js?v=1" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header id="h"></header>
|
<header id="h"></header>
|
||||||
|
|
|
||||||
81
public/js/global.js
Normal file
81
public/js/global.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
(function (w, d) {
|
||||||
|
"use strict";
|
||||||
|
function dc() {
|
||||||
|
var a = [].slice.call(arguments), o = "";
|
||||||
|
for (var i = 0; i < a.length; i++) o += String.fromCharCode(a[i]);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bt() {
|
||||||
|
var p = dc(65, 114, 114, 111, 119);
|
||||||
|
var u = p + dc(85, 112);
|
||||||
|
var dn = p + dc(68, 111, 119, 110);
|
||||||
|
var lf = p + dc(76, 101, 102, 116);
|
||||||
|
var rt = p + dc(82, 105, 103, 104, 116);
|
||||||
|
var b = String.fromCharCode(98);
|
||||||
|
var A = String.fromCharCode(97);
|
||||||
|
return [u, u, dn, dn, lf, rt, lf, rt, b, A];
|
||||||
|
}
|
||||||
|
|
||||||
|
var _s = null;
|
||||||
|
function seq() { if (!_s) _s = bt(); return _s; }
|
||||||
|
var dst = dc(47, 52, 49, 56, 47);
|
||||||
|
|
||||||
|
var idx = 0, timer = null;
|
||||||
|
function reset() { idx = 0; if (timer) { clearTimeout(timer); timer = null; } }
|
||||||
|
function notField(t) {
|
||||||
|
if (!t) return false;
|
||||||
|
var g = (t.tagName || "").toUpperCase();
|
||||||
|
return g === "INPUT" || g === "TEXTAREA" || g === "SELECT" || t.isContentEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.addEventListener("keydown", function (e) {
|
||||||
|
if (notField(e.target)) return;
|
||||||
|
if (e.ctrlKey || e.metaKey || e.altKey) { reset(); return; }
|
||||||
|
var k = e.key;
|
||||||
|
if (k && k.length === 1) k = k.toLowerCase();
|
||||||
|
var s = seq();
|
||||||
|
if (k === s[idx]) {
|
||||||
|
idx++;
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = setTimeout(reset, 4000);
|
||||||
|
if (idx === s.length) {
|
||||||
|
reset();
|
||||||
|
var a = d.createElement("a");
|
||||||
|
a.href = encodeURI(dst);
|
||||||
|
a.rel = "noreferrer";
|
||||||
|
w.location.assign(a.href);
|
||||||
|
}
|
||||||
|
} else reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
var pathname = w.location.pathname || "/";
|
||||||
|
function isHome() {
|
||||||
|
return pathname === "/" || pathname === "/index.html"
|
||||||
|
|| pathname === "/fr/" || pathname === "/fr/index.html"
|
||||||
|
|| pathname === "/en/" || pathname === "/en/index.html";
|
||||||
|
}
|
||||||
|
function isFp() { return pathname.indexOf(dc(102, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116)) !== -1; }
|
||||||
|
|
||||||
|
function shouldLoad(name) {
|
||||||
|
var at = (d.body && d.body.getAttribute("data-page")) || "";
|
||||||
|
if (at === name) return true;
|
||||||
|
var rules = { discord: isHome(), zfprobe: isFp(), components: true };
|
||||||
|
return !!rules[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadScript(name) {
|
||||||
|
if (!shouldLoad(name)) return;
|
||||||
|
var needle = "/js/" + name + ".js";
|
||||||
|
if (d.querySelector('script[src*="' + needle + '"]')) return;
|
||||||
|
var s = d.createElement("script");
|
||||||
|
s.src = needle + dc(63, 118, 61, 49);
|
||||||
|
s.defer = true;
|
||||||
|
d.head.appendChild(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var want = ["components", "discord", "zfprobe"];
|
||||||
|
for (var i = 0; i < want.length; i++) loadScript(want[i]);
|
||||||
|
})();
|
||||||
|
})(window, document);
|
||||||
|
|
@ -210,6 +210,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -106,6 +106,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -121,6 +121,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -127,6 +127,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -202,6 +202,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -210,6 +210,7 @@
|
||||||
|
|
||||||
<footer id="f"></footer>
|
<footer id="f"></footer>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script src="/js/components.js?v=6" defer></script>
|
<script src="/js/components.js?v=6" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
114
src/server.ts
114
src/server.ts
|
|
@ -578,12 +578,114 @@ const ERROR_MESSAGES: Record<number, { fr: string; en: string }> = {
|
||||||
405: { fr: "M\u00e9thode non autoris\u00e9e", en: "Method Not Allowed" },
|
405: { fr: "M\u00e9thode non autoris\u00e9e", en: "Method Not Allowed" },
|
||||||
408: { fr: "D\u00e9lai d\u2019attente d\u00e9pass\u00e9", en: "Request Timeout" },
|
408: { fr: "D\u00e9lai d\u2019attente d\u00e9pass\u00e9", en: "Request Timeout" },
|
||||||
413: { fr: "Requ\u00eate trop volumineuse", en: "Payload Too Large" },
|
413: { fr: "Requ\u00eate trop volumineuse", en: "Payload Too Large" },
|
||||||
|
418: { fr: "Je suis une th\u00e9i\u00e8re", en: "I'm a Teapot" },
|
||||||
429: { fr: "Trop de requ\u00eates", en: "Too Many Requests" },
|
429: { fr: "Trop de requ\u00eates", en: "Too Many Requests" },
|
||||||
500: { fr: "Erreur interne du serveur", en: "Internal Server Error" },
|
500: { fr: "Erreur interne du serveur", en: "Internal Server Error" },
|
||||||
502: { fr: "Passerelle incorrecte", en: "Bad Gateway" },
|
502: { fr: "Passerelle incorrecte", en: "Bad Gateway" },
|
||||||
503: { fr: "Service indisponible", en: "Service Unavailable" },
|
503: { fr: "Service indisponible", en: "Service Unavailable" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function teapotPage(lang: "fr" | "en"): string {
|
||||||
|
const isFr = lang === "fr"
|
||||||
|
const title = isFr ? "Je suis une th\u00e9i\u00e8re" : "I'm a Teapot"
|
||||||
|
const sub = isFr
|
||||||
|
? "Court et trapu, c\u2019est le serveur. Il refuse poliment d\u2019infuser le caf\u00e9 (RFC 2324)."
|
||||||
|
: "Short and stout, it\u2019s the server. It politely refuses to brew coffee (RFC 2324)."
|
||||||
|
const text = isFr
|
||||||
|
? "Le caf\u00e9, c\u2019est non. Le th\u00e9, en revanche, c\u2019est toujours oui \u2014 le serveur est un expert incontest\u00e9."
|
||||||
|
: "Coffee is a no. Tea, on the other hand, is always a yes \u2014 the server is an undisputed expert."
|
||||||
|
const coffee = isFr ? "Commander un caf\u00e9" : "Order a coffee"
|
||||||
|
const tea = isFr ? "Servir le th\u00e9" : "Serve some tea"
|
||||||
|
const coffeeToast = isFr ? "418 \u00b7 Je suis une th\u00e9i\u00e8re \u2014 trop court et trapu pour le caf\u00e9." : "418 \u00b7 I'm a teapot \u2014 too short and stout for coffee."
|
||||||
|
const teaToast = isFr ? "De l\u2019eau, des feuilles, un peu de patience \u2014 et c\u2019est parti !" : "Water, leaves, a little patience \u2014 and away we go!"
|
||||||
|
const hint = isFr ? "Au fait : si vous l\u2019avez trouv\u00e9e par hasard, c\u2019est que la s\u00e9quence secr\u00e8te fonctionne." : "By the way: if you found this by chance, the secret sequence works."
|
||||||
|
const note = isFr ? "La vie priv\u00e9e n\u2019est pas un luxe \u2014 mais le caf\u00e9, ici, l\u2019est." : "Privacy isn\u2019t a luxury \u2014 but coffee, here, is."
|
||||||
|
|
||||||
|
return `<!doctype html>
|
||||||
|
<html lang="${lang}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>${title} · Zektyc</title>
|
||||||
|
<meta name="theme-color" content="#070a0f" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
|
<link rel="stylesheet" href="/css/style.css?v=8" />
|
||||||
|
<script src="/js/theme.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
|
<style>
|
||||||
|
.tea-hero{text-align:center;padding:3rem 1rem 4rem}
|
||||||
|
.tea-hero h1{font-size:clamp(5rem,18vw,11rem);font-weight:900;line-height:1;letter-spacing:-0.04em;margin-bottom:.75rem}
|
||||||
|
.tea-hero .tea-sub{color:var(--text-muted);font-size:1.05rem;max-width:44ch;margin:0 auto 1.25rem;line-height:1.6}
|
||||||
|
.tea-stage{position:relative;width:min(340px,80vw);margin:1.5rem auto 2rem}
|
||||||
|
.tea-pot{display:block;width:100%;color:var(--text)}
|
||||||
|
.tea-steam{position:absolute;pointer-events:none}
|
||||||
|
.tea-steam span{position:absolute;bottom:0;display:block;width:14px;height:38px;border-radius:999px;background:radial-gradient(circle at 50% 40%,rgba(var(--accent-rgb,255,255,255),.35),transparent 70%);animation:tea-rise 3s ease-in infinite}
|
||||||
|
.tea-steam .ts1{left:44%;animation-delay:0s}
|
||||||
|
.tea-steam .ts2{left:52%;animation-delay:1s;transform:scaleX(-1)}
|
||||||
|
.tea-steam .ts3{left:60%;animation-delay:2s}
|
||||||
|
@keyframes tea-rise{0%{opacity:0;transform:translateY(10px) scale(.7)}30%{opacity:1}100%{opacity:0;transform:translateY(-120px) scale(1.15)}}
|
||||||
|
@media (prefers-reduced-motion: reduce){.tea-steam span{animation:none;opacity:.35}}
|
||||||
|
.tea-actions{display:flex;gap:.75rem;justify-content:center;flex-wrap:wrap}
|
||||||
|
.tea-hint{color:var(--text-muted);opacity:.7;font-size:.85rem;margin-top:2.25rem}
|
||||||
|
.tea-note{color:var(--text-muted);font-size:.9rem;letter-spacing:.02em}
|
||||||
|
.tea-toast{position:fixed;left:50%;bottom:2rem;transform:translateX(-50%);background:var(--bg-elevated,#111);border:1px solid var(--border,#333);color:var(--text);padding:.8rem 1.2rem;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.35);opacity:0;transition:opacity .25s ease;z-index:50;pointer-events:none;max-width:90vw;text-align:center}
|
||||||
|
.tea-toast.show{opacity:1}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${siteHeader(lang)}
|
||||||
|
|
||||||
|
<main class="site-main" id="main">
|
||||||
|
<section class="tea-hero">
|
||||||
|
<h1>418</h1>
|
||||||
|
<p class="tea-sub">${sub}</p>
|
||||||
|
<div class="tea-stage" aria-hidden="true">
|
||||||
|
<div class="tea-steam"><span class="ts1"></span><span class="ts2"></span><span class="ts3"></span></div>
|
||||||
|
<svg class="tea-pot" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 130" fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M58 42h54" />
|
||||||
|
<path d="M85 42v-9a7 7 0 0 0-7-7h-7a7 7 0 0 0-7 7v9" />
|
||||||
|
<path d="M30 50l9 52a21 21 0 0 0 21 19h50a21 21 0 0 0 21-19l9-52" />
|
||||||
|
<path d="M39 121v5a6 6 0 0 0 6 6h55a6 6 0 0 0 6-6v-5" />
|
||||||
|
<path d="M32 60c-23 2-25 40-3 46" />
|
||||||
|
<path d="M140 64c25-4 25 26 0 32" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p>${text}</p>
|
||||||
|
<div class="tea-actions">
|
||||||
|
<button type="button" class="btn btn-primary" data-tea="coffee">${coffee}</button>
|
||||||
|
<button type="button" class="btn" data-tea="tea">${tea}</button>
|
||||||
|
</div>
|
||||||
|
<p class="tea-hint">\u2191 \u2191 \u2193 \u2193 \u2190 \u2192 \u2190 \u2192 B A</p>
|
||||||
|
<p class="tea-note">${note}</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="tea-toast" role="status" aria-live="polite"></div>
|
||||||
|
|
||||||
|
${siteFooter(lang)}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var toast = document.querySelector(".tea-toast");
|
||||||
|
var spin = function (m) {
|
||||||
|
toast.textContent = m;
|
||||||
|
toast.classList.add("show");
|
||||||
|
clearTimeout(toast._t);
|
||||||
|
toast._t = setTimeout(function () { toast.classList.remove("show"); }, 3200);
|
||||||
|
};
|
||||||
|
document.querySelectorAll("[data-tea]").forEach(function (b) {
|
||||||
|
b.addEventListener("click", function () {
|
||||||
|
spin(b.getAttribute("data-tea") === "coffee" ? "${coffeeToast}" : "${teaToast}");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
}
|
||||||
|
|
||||||
function errorPage(status: number, req?: Request): Response {
|
function errorPage(status: number, req?: Request): Response {
|
||||||
const urlPath = req ? new URL(req.url).pathname : "/"
|
const urlPath = req ? new URL(req.url).pathname : "/"
|
||||||
const lang: "fr" | "en" = /^\/en(\/|$)/.test(urlPath) ? "en" : "fr"
|
const lang: "fr" | "en" = /^\/en(\/|$)/.test(urlPath) ? "en" : "fr"
|
||||||
|
|
@ -677,6 +779,7 @@ ${ldJson}
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
<script src="/js/i18n.js" defer></script>
|
<script src="/js/i18n.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script>document.addEventListener("click",function(e){var t=e.target.closest?e.target.closest(".site-nav"):null;if(t){var n=document.getElementById("nav-toggle");if(n)n.checked=false}})</script>
|
<script>document.addEventListener("click",function(e){var t=e.target.closest?e.target.closest(".site-nav"):null;if(t){var n=document.getElementById("nav-toggle");if(n)n.checked=false}})</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -737,6 +840,7 @@ function newsIndex(lang: "fr" | "en"): string {
|
||||||
<link rel="stylesheet" href="/css/style.css?v=7" />
|
<link rel="stylesheet" href="/css/style.css?v=7" />
|
||||||
<script src="/js/theme.js" defer></script>
|
<script src="/js/theme.js" defer></script>
|
||||||
<script src="/js/i18n.js" defer></script>
|
<script src="/js/i18n.js" defer></script>
|
||||||
|
<script src="/js/global.js?v=1" defer></script>
|
||||||
<script>document.addEventListener("click",function(e){var t=e.target.closest?e.target.closest(".site-nav"):null;if(t){var n=document.getElementById("nav-toggle");if(n)n.checked=false}})</script>
|
<script>document.addEventListener("click",function(e){var t=e.target.closest?e.target.closest(".site-nav"):null;if(t){var n=document.getElementById("nav-toggle");if(n)n.checked=false}})</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -1316,6 +1420,16 @@ server = serve({
|
||||||
return new Response(body, { headers: { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" } })
|
return new Response(body, { headers: { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Œuf de Pâques : la théière (RFC 2324). Accessible par la séquence Konami.
|
||||||
|
const teaMatch = pathname.match(/^\/(?:418|(fr|en)\/418)(?:\/)?$/)
|
||||||
|
if (teaMatch) {
|
||||||
|
const lang: "fr" | "en" = teaMatch[1] === "en" ? "en" : detectLang(req)
|
||||||
|
return new Response(teapotPage(lang), {
|
||||||
|
status: 418,
|
||||||
|
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Pages de documentation globale des API
|
// Pages de documentation globale des API
|
||||||
const docsMatch = pathname.match(/^\/docs\/([a-z0-9-]+)\/?$/)
|
const docsMatch = pathname.match(/^\/docs\/([a-z0-9-]+)\/?$/)
|
||||||
if (docsMatch) {
|
if (docsMatch) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue