J’ai divisé mes stats visiteurs par 3500. Mais c’était voulu.

4 min de lecture

La semaine dernière sur Thesparktwin.com je me suis pris une petachié de robots.

14 000 visiteurs alors que le site en fait en général 3 à 5 vrais visiteurs par jour.

Des bots bien sous tous rapports, IP résidentielle de tous les pays du monde, vrais navigateurs.
J’ai dû faire sortir tout le catalogue des drapeaux du monde à Clicky je pense.

Les seuls trucs qui les trahissent, c’est l’absence de référé, et le pays qui ne matche pas avec la langue consultée (IP guatemaltaise sur une page japonaise, uzbeque sur une page grecque, turque sur une chinoise, …).

Les pages du site sont générées en statique puis hébergées dans les caches de Cloudflare. Donc mon hébergement n’est pas trop dérangé.

Mais ce qui me dérange, c’est que mes stats sur Clicky sont devenues illisibles.

Chose à savoir, sur ces sites je suis en mode « Anti-adblock », c’est-à-dire que c’est mon serveur qui héberge des trackeurs et que je remonte les informations à Clicky de serveur à serveur.

Donc avec l’impossibilité de faire le tri des visiteurs sur leurs caractéristiques propres, je me suis dit que j’allais ne déclencher le script Clicky qu’après un vrai geste humain :
Un clic, un touchscreen ou un mouvement de souris (120 pixels cumulés, 800 millisecondes au minimum).

<script>
(function () {
  var parti = false;
  var opt = { passive: true, capture: true };
  var forts = ['pointerdown', 'touchstart', 'keydown'];   // gestes "intentionnels" : clic / touche / doigt
  var n = 0, dist = 0, t0 = 0, lx = null, ly = null;      // mesure du mouvement de souris

  function stop() {
    forts.forEach(function (g) { removeEventListener(g, humain, opt); });
    removeEventListener('mousemove', bouge, opt);
    removeEventListener('pointermove', bouge, opt);
  }
  function declencher() {
    if (parti) return; parti = true;
    stop();
    humainDetecte();     // <-- VOTRE action ici (charger le mouchard, etc.) } // Clic / touche / doigt = geste fort -> on part tout de suite.
  function humain(e) { if (!parti && !(e && e.isTrusted === false)) declencher(); }
  // Souris : on exige un VRAI mouvement soutenu, pas un saut unique de robot.
  function bouge(e) {
    if (parti || e.isTrusted === false) return;            // ignore les événements synthétiques
    var x = e.clientX, y = e.clientY;
    if (lx !== null) dist += Math.abs(x - lx) + Math.abs(y - ly);
    lx = x; ly = y;
    var now = e.timeStamp || Date.now();
    if (!t0) t0 = now;
    if (++n >= 10 && dist >= 120 && (now - t0) >= 800) declencher();   // ≥10 mouvements, ≥120 px, ≥800 ms
  }
  forts.forEach(function (g) { addEventListener(g, humain, opt); });
  addEventListener('mousemove', bouge, opt);
  addEventListener('pointermove', bouge, opt);

  function humainDetecte() {
    // TODO : ce que vous déclenchez quand un humain est confirmé.
    // ex. charger Clicky, envoyer un ping, etc.
  }
})();
</script>

YOUPI !!
Je suis passé de 14 000 « visiteurs » à 8 000.

Putain, les salauds de bots interagissent comme des humains sur le site.

C’est là que Claude (notre cher ami à tous), m’a proposé Turnstile de Cloudflare.
Leur captcha sans intervention.
Je pensais qu’il fallait qu’il soit visible. Mais visiblement non.

Et là : Magie.
4 visiteurs

Les bots on quand même une ethique.
Ils respectent les Captcha (je vous ai parlé de la fois où je n’arrivais pas à remplir un Captcha et qu’après parlementation avec Claude il a accepté de me lire ce que je n’arrivais pas ?).

Mais pour combien de temps ?

Si vous utilisez Clicky en mode classique, vous pouvez quand même activer Turnstile.

<script>
(function () {
// ---- À RENSEIGNER ----
var SITEKEY = 'VOTRE_SITEKEY_TURNSTILE'; // clé publique (dashboard Cloudflare > Turnstile)
var CLICKY_SITE_ID = 12345678; // votre Site ID Clicky (nombre, sans guillemets)
// ----------------------

var parti = false, demande = false, wid = null;
var opt = { passive: true, capture: true };
var forts = ['pointerdown', 'touchstart', 'keydown']; // clic / touche / doigt
var n = 0, dist = 0, t0 = 0, lx = null, ly = null; // mesure du mouvement souris

// 1) FILTRE MOUVEMENT HUMAIN ------------------------------------------------
function stop() {
forts.forEach(function (g) { removeEventListener(g, humain, opt); });
removeEventListener('mousemove', bouge, opt);
removeEventListener('pointermove', bouge, opt);
}
function partir() { if (!demande) { demande = true; stop(); chargerTS(); } }
function humain(e) { if (!parti && !(e && e.isTrusted === false)) partir(); }
function bouge(e) {
if (parti || demande || e.isTrusted === false) return; // ignore les événements synthétiques
var x = e.clientX, y = e.clientY;
if (lx !== null) dist += Math.abs(x - lx) + Math.abs(y - ly);
lx = x; ly = y;
var now = e.timeStamp || Date.now();
if (!t0) t0 = now;
if (++n >= 10 && dist >= 120 && (now - t0) >= 800) partir(); // ≥10 mvts, ≥120 px, ≥800 ms
}
forts.forEach(function (g) { addEventListener(g, humain, opt); });
addEventListener('mousemove', bouge, opt);
addEventListener('pointermove', bouge, opt);

// 2) TURNSTILE INVISIBLE ----------------------------------------------------
function rendre() {
if (wid !== null || !(window.turnstile && turnstile.render)) return;
var box = document.createElement('div');
box.style.cssText = 'position:fixed;left:-9999px;top:0;width:1px;height:1px;overflow:hidden';
document.body.appendChild(box); // hors-écran, PAS display:none (sinon l'iframe ne s'initialise pas)
wid = turnstile.render(box, {
sitekey: SITEKEY,
callback: chargerClicky, // humain validé -> on charge Clicky
'error-callback': function () { return true; } // robot -> pas de jeton, on ne charge rien
});
}
function chargerTS() {
if (window.turnstile && turnstile.render) { rendre(); return; }
if (document.getElementById('cf-ts-api')) return;
window.onloadTurnstileCallback = rendre; // appelé quand Turnstile est prêt (fiable avec defer)
var a = document.createElement('script');
a.id = 'cf-ts-api';
a.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onloadTurnstileCallback';
a.defer = true;
document.head.appendChild(a);
}

// 3) CHARGEMENT DU CLICKY OFFICIEL (seulement si humain + Turnstile OK) ------
function chargerClicky(token) {
if (parti) return; parti = true; // une seule fois ; (token non revérifié côté serveur en embed classique)
window.clicky_site_ids = window.clicky_site_ids || [];
window.clicky_site_ids.push(CLICKY_SITE_ID);
var s = document.createElement('script');
s.async = true;
s.src = '//static.getclicky.com/js';
document.head.appendChild(s);
}
})();
</script>

Il ne me reste plus qu’à revenir aux 14 000 visiteurs.
Mais réels cette fois-ci.

J’ai mis 20 ans à finir un devoir de BTS

3 min de lecture

En 2007, je passe mon BTS.
Il me manque un truc tout bête : un moyen simple de dessiner une zone de chalandise. (Pour les non-initiés : la zone d’où viennent vos clients. En gros, qui habite assez près pour pousser votre porte.)
Je m’y étais pris avec Gimp, pour gribouiller sur des fonds de carte GoogleMap.

Un an plus tard, en 2007, je tombe sur une technique pour en tracer une visuellement, en quelques clics.
Exactement ce qui m’avait manqué l’année d’avant.
Alors je la partage. Gratuitement, sur un blog.

Et là, surprise : ça marche vachement bien. Les stats (Clicky déjà pour moi) s’emballent.
En 2011, j’ai décidé d’en faire un site dédié : creer-zone-de-chalandise.fr.

Pendant des années, ça tourne. Des milliers de visiteurs mensuels.
Près d’une centaine de commentaires : des étudiants, des enseignants, des commerçants.
Cité par Wikipedia, des manuels scolaires, des sites de l’Éducation nationale.

Puis tout a cassé

Mes techniques reposaient sur des outils tiers.
Google Maps, Owlapps, Cartoo, Google Docs…
Ils ont disparu les uns après les autres.

Le web, c’est ça aussi : des outils sur lesquels on compte, et qui ferment sans prévenir.

Résultat : mes tutoriels ne marchaient plus.
Frustrant pour tout le monde.

En 2021, je refais le site avec uniquement du contenu.
Plus de technique, puisqu’il n’y avait plus d’outil derrière.

Un pansement, pas une solution.

Ça devait être temporaire, j’avais envie de proposer mon outil.

Alors (enfin) en 2026, j’ai fait l’outil moi-même !

Puisque les outils d’avant n’existent plus, j’en ai codé un.
Et tant qu’à faire, je l’ai mis gratuit.

Pour toujours.

Sans e-mail demandé.

Je ne vais pas demander un ou deux euros à des étudiants. Surtout maintenant.

Voilà l’outil. Un assistant en 4 étapes, la carte juste en dessous :

  1. L’adresse du point de vente.
  2. Le mode de calcul : à vol d’oiseau (des cercles) ou en temps de trajet (isochrones, en voiture, à pied ou à vélo).
  3. Les trois zones : primaire, secondaire, tertiaire.
  4. L’export.

Sous le capot, que des données publiques françaises : fond de carte Plan IGN, adresses géocodées par l’IGN, population des communes par l’INSEE, isochrones calculées par openrouteservice.

Vous pouvez même importer votre fichier clients en CSV : les adresses se placent toutes seules sur la carte.
Et à la fin, export en PNG ou PDF, plus un lien de partage — pratique pour rendre un dossier de BTS.

Au passage, j’ai refait tout le reste du site

Sortie de WordPress : une petite application PHP sur mesure, mêmes adresses qu’avant (les 301 et 410 s’occupent du reste).

Tout le contenu a été réécrit pour les étudiants post-bac – BTS MCO, NDRC, bac pro.
Le tout tient en  21 pages : les définitions (zone isométrique, zone isochrone), les méthodes (magasin existant, point de vente futur, chiffre d’affaires potentiel, modèles de Reilly et de Huff), une page BTS MCO/NDRC, une fiche de révision, un glossaire, des tutoriels, une FAQ.

Un bouton, sur chaque page, télécharge tout le site comme un cours complet en PDF (47 pages).
Les commentaires sont rouverts et modérés. Et j’ai même redistribué les anciens commentaires pertinents sur les nouvelles pages, selon la question posée.

J’ai aussi tout balisé pour que ChatGPT, Perplexity ou Copilot recommandent l’outil aux étudiants qui leur demandent comment faire. Parce que c’est là qu’ils cherchent maintenant.

Bref

20 ans après, le devoir que je n’arrivais pas à rendre est enfin terminé.
Et il est à tous les étudiants en commerce, gratuitement.

Si vous préparez un dossier, ou si vous enseignez la chose : servez-vous.

Bon courage pour vos dossiers.

Qyu.re Mon alternative personnelle à Codeanywhere

4 min de lecture

Codeanywhere, c’était mon éditeur de code que j’aimais d’amour.
Celui qui me laissait ouvrir un fichier sur un serveur FTP, le corriger, enregistrer, et voir le site bouger dans la foulée.

Je l’utilisais depuis 2013. J’en parlais déjà ici : Avec quoi je code ?
Treize ans de lune de miel.

Puis le support de la version que j’utilisais s’est arrêté.
Ça faisait déjà quelques années que le support me renouvelait mon compte manuellement, en me prévenant que, si ça se trouve, ça ne tiendrait que quelques mois.
Ça a tenu plusieurs années.

Mais au milieu de l’été, ça s’est arrêté pour de vrai.
En plein développement de Ets-Allio.fr.

La chiasse.

J’ai cherché, j’ai cherché, j’ai trouvé des trucs surement mieux, mais plus compliqués. Rien qui ne correspondait à mon utilisation.

Aujourd’hui, tout passe par des conteneurs, des repos GitHub, des pipelines, des environnements qui se montent et se démontent surement. Je n’utilise pas les notices de meubles Ikea.
C’est surement propre. Surement carré. Surement pro.

Mais c’est chiant. Et ce n’est pas ma façon de coder.

Tout ce que je veux c’est l’inverse.
DU SIMPLE.
Ouvrir un fichier, le modifier, enregistrer. Fin de la partie.
Ctrl + O, tiptiptip, Ctrl + S, Alt + F4. Mic drop.

Et tout ça,… directement en prod !

Pas de build, pas de branche à merger pour corriger une virgule, pas d’usine à gaz.

Rajouter ce putain de point virgule oublié ne mérite pas un pipeline de prod.

Le cahier des charges tenait en 2 mots.

Refaire Codeanywhere.

Avec toutes les fonctions que j’utilisais déjà au quotidien.
Plus celles que j’aurais aimé y trouver.

C’est tout.
En réfléchissant, c’est déjà beaucoup.

J’avais le NDD qyu.re qui dormait dans ma collection de noms de domaine.
(Oui, j’en collectionne. Non, je ne me soigne pas.)
Autant lui donner un vrai boulot plutôt que de le laisser prendre la poussière.

J’avais aussi en candidat partir.la, mais ça aurait été con de le cramer là-dessus.
Il mérite peut-être mieux qu’une arborescence de fichiers et du texte en monotype.

Ce que fait l’outil, la liste complète (?)

Connexion et comptes

  • Coffre de serveurs FTP / FTPS, mots de passe chiffrés.
  • Multi-utilisateurs, avec partage d’accès révocable (lecture seule ou écriture).
    Ça, c’est une fonction qui manquait sur Codeanywhere. Ici j’ajoute un serveur, et je peux le partager avec Lionel, ou Laure, ou n’importe qui qui bosse avec moi sur un projet, sans avoir à partager les identifiants.
  • Relais HTTPS pour les serveurs qu’on ne peut pas joindre en direct, et un diagnostic de connexion intégré (Qyu.re est hébergé sur O2switch qui ne veut pas communiquer en FTP avec OVH).

Éditer

  • Arborescence, onglets multi-fichiers, plusieurs serveurs ouverts en même temps.
    Les onglets, c’est la vie. #team+50
  • Coloration syntaxique (HTML, CSS, JS, PHP, SQL, logs) et autocomplétion.
  • Recherche / remplacement dans les fichiers ouverts ou sur tout un dossier, arborescence de dossier ou tout le serveur.
  • Beautify / Minify.
  • Validation de la syntaxe avant d’enregistrer.
    J’aimais Codeanywhere justement car tu pouvais rajouter ton point-virgule qui manquait dès la première erreur 500.
    Maintenant, je n’ai même plus l’occasion d’oublier ce point-virgule ou cette accolade fermante imbriquée dans une cascade de 9.
  • Conversion d’encodage et de fins de ligne, édition des fichiers non-UTF-8.
  • Thème clair / sombre, panneaux redimensionnables, position du curseur mémorisée.

Gérer les fichiers

  • Uploader, télécharger, dupliquer, déplacer, zipper, dézipper, corbeille.
    La corbeille. Sur ton serveur de prod, … Que celui qui n’en a jamais rêvé… comprenne qu’il est surement tout à fait normal comparé à moi.
    Et oui, tout ça pour un fichier ou un dossier (et ses sous-dossiers).
  • chmod (une case à cocher à la FileZilla, pas un chiffre à retenir), usage disque, propriétés.
  • Lecture de la fin des gros logs et suivi en direct (tail -f).

Voir

  • Aperçu des images + zoom + compression.
    Même plus besoin de télécharger une image, de la passer sur TinyPNG et de la réuploader. Un bouton et le panda vient directement te faire ça à domicile sur ton serveur.
  • CSV en mode texte ou tableur.
    Pareil, tu bosses sur ton CSV en prod, on est pas là pour faire semblant.
  • SVG éditable en code avec visualisation des modifs en direct.
    Qui d’autre édite ses SVG en code par ici ?

Travailler à plusieurs

  • Révisions : chaque enregistrement est archivé, avec diff et restauration.
    Ça, je l’utilisais pas mal sur Codeanywhere. On pouvait d’ailleurs s’en servir comme une « poubelle », si tu supprimais un fichier, tu en recréais un au même endroit avec le même nom et tu pouvais retrouver son contenu via la révision.
  • Présence en temps réel et journal d’activité : qui édite quoi, en ce moment.
    Ça évite qu’on écrase un fichier qu’un autre est en train d’éditer.

Les trucs plus récents

Chaud comme je suis, j’ai encore rajouté des trucs suivant mes besoins et je vais continuer à le faire.

  • Serveur MCP.
    Mon IA peut éditer les fichiers à ma place, avec des droits bornés : un seul serveur, voire un seul dossier, en lecture seule si je veux.
  • Upload de photo en masse.
    J’ai eu besoin de scanner un livre. Long. Chiant.
    Donc j’ai rajouté une fonction dans Qyu.re pour le faire.
    Je mitraille avec le téléphone, les photos partent direct sur le serveur, sans jamais être enregistrées dans la pellicule, métadonnées EXIF supprimées, et redimensionnées au passage.

Et le plus beau dans tout ça :

Je n’ai pas écrit une ligne de code

Tout est vibecodé.
Avec Claude.

Je dis ce que je veux.
Claude fait.
Je teste, je râle, il refait, je valide.

Voilà

Ce n’est pas à vendre, c’est juste l’outil dont j’ai besoin, et qui me sert tous les jours.

Si vous en avez besoin, je vous crée un compte, ou vous file le code.

Codeanywhere je l’aimais d’amour.
Qyu.re Faisons des enfants.