diff --git a/build.py b/build.py new file mode 100644 index 0000000..c69cf25 --- /dev/null +++ b/build.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +""" +Pages d'erreur du portfolio (style yb2026), générées depuis un seul modèle. + - out/apache/.html : servies par Apache (ErrorDocument), polices liées au thème + - out/npm/.html : servies par NPM quand Apache est éteint (502/503/504), polices intégrées en base64 + - out/template.php : même page en PHP pour wp_die() et wp-content/php-error.php +Relancer après modification : python3 ~/yb-erreurs/build.py && sudo ~/yb-erreurs/install.sh +""" +import base64, html, os +ROOT = os.path.dirname(os.path.abspath(__file__)) +FONTS = '/home/yass/wordpress/wp-content/themes/yb2026/assets/fonts' + +CODES = { + 400: ("Requête invalide", "Le navigateur a envoyé une requête que le serveur n'a pas su lire. Recharger la page suffit souvent."), + 401: ("Authentification requise", "Cette zone demande des identifiants. Si tu n'en as pas, le reste du site est ouvert à tous."), + 403: ("Accès interdit", "Cette zone est privée : le serveur refuse de l'afficher. Le reste du site, lui, est grand ouvert."), + 404: ("Page introuvable", "Cette adresse ne mène nulle part. Elle a peut-être changé, ou il y a une faute de frappe."), + 405: ("Méthode non autorisée", "Cette adresse n'accepte pas ce type de requête."), + 408: ("Délai dépassé", "La requête a mis trop de temps à arriver. Vérifie ta connexion et réessaie."), + 410: ("Page supprimée", "Ce contenu a été retiré définitivement."), + 413: ("Envoi trop lourd", "Ce que tu as envoyé dépasse la taille autorisée par le serveur."), + 414: ("Adresse trop longue", "L'adresse demandée est trop longue pour être traitée."), + 429: ("Trop de requêtes", "Doucement : trop de demandes en peu de temps. Patiente un instant puis réessaie."), + 500: ("Erreur interne", "Quelque chose a planté côté serveur. Ce n'est pas de ton fait, et c'est sûrement déjà en cours de correction."), + 501: ("Non pris en charge", "Le serveur ne sait pas traiter cette demande."), + 502: ("Le site ne répond pas", "Le serveur du site ne répond pas correctement pour le moment. Réessaie dans une minute."), + 503: ("Maintenance en cours", "Le site redémarre ou est en maintenance. Il revient très vite, réessaie dans une minute."), + 504: ("Délai dépassé", "Le serveur du site a mis trop de temps à répondre. Réessaie dans un instant."), +} +NPM_CODES = (502, 503, 504) + +def font_css(inline): + faces = [("Archivo", "Archivo-100-900-latin.woff2", "100 900", "62% 125%"), + ("JetBrains Mono", "JetBrainsMono-400-latin.woff2", "400 700", None)] + out = [] + for fam, f, w, st in faces: + src = ("data:font/woff2;base64," + base64.b64encode(open(os.path.join(FONTS, f), 'rb').read()).decode()) if inline \ + else "/wp-content/themes/yb2026/assets/fonts/" + f + out.append("@font-face{font-family:'%s';font-weight:%s;%sfont-display:swap;src:url(%s) format('woff2')}" % (fam, w, ("font-stretch:%s;" % st) if st else "", src)) + return "\n".join(out) + +CSS = r""" +:root{--bg:#0a0a0a;--ink:#f5f5f0;--lime:#d3fd50;--ink-rgb:245,245,240;--lime-rgb:211,253,80;--mut:rgba(245,245,240,.55);--b:rgba(245,245,240,.12);color-scheme:dark} +@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){--bg:#f3f2ed;--ink:#111;--lime:#4a7300;--ink-rgb:17,17,17;--lime-rgb:74,115,0;--mut:rgba(17,17,17,.55);--b:rgba(17,17,17,.12);color-scheme:light}} +:root[data-theme="light"]{--bg:#f3f2ed;--ink:#111;--lime:#4a7300;--ink-rgb:17,17,17;--lime-rgb:74,115,0;--mut:rgba(17,17,17,.55);--b:rgba(17,17,17,.12);color-scheme:light} +*{box-sizing:border-box;margin:0;padding:0} +html,body{min-height:100%;background:var(--bg);overflow-x:clip} +body{color:var(--ink);font:400 16px/1.6 'Archivo',system-ui,sans-serif;display:flex;flex-direction:column;min-height:100vh;padding:0 clamp(20px,4vw,44px);overflow-x:hidden} +::selection{background:var(--lime);color:var(--bg)} +a{color:inherit;text-decoration:none} +.grain{position:fixed;inset:0;pointer-events:none;opacity:.05;z-index:5;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E")} +.top{display:flex;justify-content:space-between;align-items:center;padding:22px 0;border-bottom:1px solid var(--b);font:700 14px 'JetBrains Mono',monospace;letter-spacing:.05em} +.top b{color:var(--lime);font-weight:700} +.top span{font-weight:500;font-size:11.5px;letter-spacing:.18em;color:var(--mut)} +main{flex:1;display:flex;flex-direction:column;justify-content:center;gap:22px;padding:48px 0;position:relative;min-width:0} +.ring{position:absolute;right:-6vw;top:50%;width:min(56vw,620px);aspect-ratio:1;transform:translateY(-50%);border-radius:50%;pointer-events:none; + background:radial-gradient(circle,rgba(var(--lime-rgb),.10),transparent 62%);border:1px dashed rgba(var(--lime-rgb),.22);animation:spin 60s linear infinite} +.ring::after{content:'';position:absolute;inset:16%;border-radius:50%;border:1px solid rgba(var(--ink-rgb),.08)} +@keyframes spin{to{transform:translateY(-50%) rotate(360deg)}} +.kick{font:700 12px 'JetBrains Mono',monospace;letter-spacing:.22em;color:var(--lime)} +.code{font:900 clamp(110px,26vw,300px)/.82 'Archivo',sans-serif;font-stretch:118%;letter-spacing:-.05em;display:flex;gap:.04em;user-select:none} +.code span:nth-child(odd){color:var(--ink)} +.code span:nth-child(even){color:transparent;-webkit-text-stroke:2px var(--lime)} +h1{font:800 clamp(28px,4.4vw,52px)/1.05 'Archivo',sans-serif;letter-spacing:-.025em;text-wrap:balance;max-width:16ch} +p.msg{max-width:52ch;color:rgba(var(--ink-rgb),.78);font-size:17px} +.req{font:500 12.5px 'JetBrains Mono',monospace;color:var(--mut);display:flex;gap:10px;flex-wrap:wrap;align-items:center;max-width:100%} +.req code{font:inherit;color:var(--ink);background:rgba(var(--ink-rgb),.06);border:1px solid var(--b);padding:3px 8px;border-radius:6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.req em{font-style:normal;color:var(--lime)} +.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:6px} +.btn{font:700 12.5px 'JetBrains Mono',monospace;letter-spacing:.14em;padding:14px 20px;border-radius:99px;border:1px solid var(--b);transition:transform .25s cubic-bezier(.3,1.5,.5,1),border-color .2s,color .2s,background .2s;cursor:pointer;background:none;color:var(--ink)} +.btn:hover{border-color:var(--lime);color:var(--lime);transform:translateY(-2px)} +.btn.main{background:var(--lime);border-color:var(--lime);color:var(--bg)} +.btn.main:hover{color:var(--bg)} +.btn:focus-visible{outline:2px solid var(--lime);outline-offset:3px} +footer{padding:20px 0 26px;border-top:1px solid var(--b);font:500 11px 'JetBrains Mono',monospace;letter-spacing:.16em;color:var(--mut);display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap} +@media (max-width:640px){.ring{right:-30vw;width:90vw}.top span{display:none}} +""" + +def page(code, title, msg, inline_fonts, php=False): + digits = "".join("%s" % d for d in str(code)) + e = (lambda s: s) if php else html.escape + head_title = "%s · %s" % (code, title) if not php else "" + t = title if not php else "" + m = e(msg) if not php else "" + dg = digits if not php else "' . esc_html($d) . ''; ?>" + cd = str(code) if not php else "" + return """ + + + + + + +%(head)s · Yasser Boulayoune + + + + + +
YB©2026YASSER BOULAYOUNE · PORTFOLIO
+
+ +

ERREUR %(cd)s

+ +

%(title)s

+

%(msg)s

+

REQUÊTE/→ %(cd)s

+
+ ACCUEIL → + + CONTACT +
+
+
© 2026 YASSER BOULAYOUNERÉGION LYONNAISE
+ + + +""" % {"head": head_title if php else html.escape(head_title), "fonts": font_css(inline_fonts), "css": CSS, "cd": cd, "digits": dg, "title": t if php else html.escape(t), "msg": m} + +HUB_CSS = r""" +.hub{flex:1;padding:44px 0 60px;display:flex;flex-direction:column;gap:38px} +.hub h1{max-width:none} +.hub .lead{max-width:62ch;color:rgba(var(--ink-rgb),.75)} +.grp h2{font:700 11.5px 'JetBrains Mono',monospace;letter-spacing:.22em;color:var(--mut);padding-bottom:10px;border-bottom:1px solid var(--b);display:flex;gap:10px} +.grp h2 b{color:var(--lime)} +.grp p.note{margin-top:10px;font-size:14px;color:var(--mut);max-width:70ch} +.row{display:grid;grid-template-columns:96px 1fr auto;gap:18px;align-items:center;padding:16px 0;border-bottom:1px solid var(--b)} +.row .n{font:900 40px/1 'Archivo',sans-serif;font-stretch:115%;color:transparent;-webkit-text-stroke:1.5px var(--lime)} +.row .t{min-width:0} +.row .t strong{display:block;font:700 18px 'Archivo',sans-serif} +.row .t span{display:block;font:500 12px 'JetBrains Mono',monospace;color:var(--mut);margin-top:3px;overflow-wrap:anywhere} +.row form{margin:0} +.row .btn{white-space:nowrap} +@media (max-width:620px){.row{grid-template-columns:64px 1fr;}.row .n{font-size:30px}.row .go{grid-column:1/-1}} +""" + +def hub(inline_fonts): + real = [ + (403, "Dossier privé (Apache)", "GET /wp-content/uploads/ · listage de dossier interdit", 'DÉCLENCHER →'), + (403, "Fichier sensible (Apache)", "GET /wp-config.php · fichier protégé", 'DÉCLENCHER →'), + (403, "Dossier système (Apache)", "GET /wp-includes/", 'DÉCLENCHER →'), + (404, "Page introuvable (thème WordPress)", "GET /cette-page-nexiste-pas/ · c'est le 404 du thème, pas ces pages-ci", 'DÉCLENCHER →'), + (403, "Refus WordPress (wp_die)", "POST /wp-comments-post.php · commentaire sur un article aux commentaires fermés", + '
'), + ] + prev = [(c, t, "Page Apache · /yb-erreur/%d.html" % c) for c, (t, m) in CODES.items()] + rows = lambda items: "".join('
%s
%s%s
%s
' % i for i in items) + prev_rows = rows([(str(c), html.escape(t), html.escape(d), 'APERÇU →' % c) for c, t, d in prev] + + [("500", "Erreur critique (plantage PHP)", "Drop-in wp-content/php-error.php", 'APERÇU →')]) + return """ + + +Pages d'erreur · test + + + +
YB©2026BANC D'ESSAI · PAGES D'ERREUR
+
+

TERRAIN DE TEST

Toutes les pages d'erreur

+

Les liens du haut provoquent de vraies erreurs sur le site, avec le vrai code HTTP. Ceux du bas ouvrent chaque page en aperçu, y compris celles qu'on ne peut pas déclencher sans casser le site.

+

%02d ERREURS RÉELLES

%s
+

%02d APERÇUS

%s +

Les 502, 503 et 504 s'affichent aussi quand le site est complètement éteint : c'est alors le proxy (NPM) qui les sert, avec le même design. Les aperçus renvoient un code 200, c'est normal.

+
+
© 2026 YASSER BOULAYOUNEPAGE NON INDEXÉE
+ +""" % (font_css(inline_fonts), CSS, HUB_CSS, len(real), rows(real), len(prev) + 1, prev_rows) + +os.makedirs(os.path.join(ROOT, "out/apache"), exist_ok=True) +os.makedirs(os.path.join(ROOT, "out/npm"), exist_ok=True) +for c, (t, m) in CODES.items(): + open(os.path.join(ROOT, "out/apache/%d.html" % c), "w").write(page(c, t, m, False)) + if c in NPM_CODES: + open(os.path.join(ROOT, "out/npm/%d.html" % c), "w").write(page(c, t, m, True)) +open(os.path.join(ROOT, "out/apache/critique.html"), "w").write(page(500, "Erreur critique", "Le site a rencontré un problème technique. Ce n'est pas de ton fait et c'est sûrement déjà en cours de correction : réessaie dans quelques minutes.", False)) +open(os.path.join(ROOT, "out/apache/index.html"), "w").write(hub(False)) +open(os.path.join(ROOT, "out/template.php"), "w").write("" + page(0, "", "", False, php=True)) +print("ok", len(CODES), "pages apache,", len(NPM_CODES), "pages npm, 1 modèle php")