Ici, on parle d’habitude de Home Assistant, de Raspberry Pi et de YAML. Mais il arrive qu’un projet du quotidien s’invite sur l’établi. Cette fois, c’est le loto de SoBad, mon club de badminton à Saint-Orens.
Un loto associatif, c’est une soirée conviviale… et une petite logistique : un animateur qui tire et annonce les boules, une salle qui doit voir les numéros sortis depuis le fond, des lots à suivre partie par partie, des cartons à vérifier quand quelqu’un crie « Quine ! ». Des solutions existent, et je me suis d’ailleurs inspiré de Lotofocus pour l’ergonomie. Mais j’avais envie d’un outil à nous, aux couleurs du club, que n’importe quel bénévole puisse lancer sans rien installer.
Comme pour le thème TechDevBlog de ce blog, je l’ai développé avec l’aide de Claude. Et comme pour le thème, la règle était la même : rester simple.

Le cahier des charges
Avant d’écrire une ligne, j’ai posé ce que je voulais vraiment :
- Deux écrans : une régie pour l’animateur (sur le PC portable) et un affichage plein écran pour la salle (projecteur ou TV).
- Aucune installation : pas de serveur, pas de compte, pas de dépendance. La salle des fêtes n’a pas forcément de Wi-Fi fiable, et le PC du jour n’est pas toujours le même.
- Les vraies règles du loto : quine, double quine, carton plein, et parfois plusieurs lots sur une même étape.
- Des lots préparés à l’avance, idéalement dans Excel, parce que c’est là que les membres du bureau font leur liste.
- Ne rien perdre si quelqu’un ferme la fenêtre par erreur en pleine partie.
- La charte du club : le logo et le rouge, noir, blanc.
Un seul fichier, et c’est tout
Le dépôt tient en trois fichiers, dont un README et un modèle CSV. Toute l’application est dans Dashboard_Loto.html : HTML, CSS, JavaScript et même le logo, encodé en WebP base64 directement dans le code. Environ 87 Ko au total.
On double-clique sur le fichier, il s’ouvre dans Chrome, Edge ou Firefox, et c’est parti. Internet ne sert qu’à charger les polices Google Fonts ; sans connexion, la page fonctionne quand même avec les polices de repli.
C’est le même esprit que pour le thème du blog, développé sans outil de build : moins il y a de pièces, moins il y a de choses qui cassent le soir J.
Côté régie : ce que voit l’animateur
La régie regroupe tout ce dont l’animateur a besoin pendant la soirée :
- Le tirage : un bouton « Tirer une boule » (ou la touche Espace), l’annulation de la dernière boule et la remise à zéro.
- Le mode boulier physique : si les boules sortent d’un vrai boulier, on saisit simplement le numéro annoncé. Le tableau des 90 boules est aussi cliquable pour ajouter ou retirer un numéro.
- La partie en cours : quelle partie, quelle étape est en jeu, quel lot est à gagner et lequel vient ensuite.
- Les gagnants : on déclare le gagnant, on passe à l’étape suivante, et tout est historisé dans l’onglet Gagnants.
- La vérification d’un carton : on saisit les 15 numéros du carton annoncé, ceux déjà sortis passent au vert et le verdict s’affiche. Depuis le dernier commit, les numéros saisis en double sont refusés, histoire d’éviter un faux « carton validé ».
- Le message salle : un bandeau à afficher sur l’écran de la salle, du genre « Pause buvette 15 min » ou « Vérification en cours ».

L’écran salle : deux fenêtres qui se parlent
C’est la partie que je trouve la plus intéressante techniquement. L’écran salle, c’est le même fichier ouvert dans une deuxième fenêtre du même navigateur. On la glisse sur le projecteur, on clique sur « Utiliser cette fenêtre comme écran salle » (ou on ouvre directement l’adresse terminée par #ecran), on passe en plein écran, et elle suit la régie toute seule : boule géante, dernières boules sorties, tableau des 90 numéros, partie et lot en jeu.

Pour faire communiquer les deux fenêtres sans serveur, j’utilise deux canaux en parallèle :
BroadcastChannel, l’API faite pour ça : un canal nommé sur lequel toutes les fenêtres d’une même origine peuvent publier et écouter.- L’événement
storage: quand une fenêtre écrit une clé dans lelocalStorage, les autres fenêtres reçoivent un événement. C’est le filet de sécurité quandBroadcastChanneln’est pas disponible, ce qui peut arriver selon la façon dont la page est ouverte.
Comme un même message peut arriver par les deux canaux, chacun porte un identifiant unique et la fenêtre ignore ceux qu’elle a déjà traités :
|
1 2 3 4 5 6 7 8 9 10 11 |
let bc = null; try { bc = new BroadcastChannel('loto-regie') } catch (e) {} function send(msg) { const m = { ...msg, id: Math.random().toString(36).slice(2) + Date.now() }; try { bc && bc.postMessage(m) } catch (e) {} // Sur le canal storage, l'état complet est déjà sauvegardé : // on n'envoie que le signal const light = m.type === 'state' ? { type: 'state', extra: m.extra || null, id: m.id } : m; try { localStorage.setItem(MSG_KEY, JSON.stringify(light)) } catch (e) {} } |
Petit détail qui rassure le soir J : les deux fenêtres s’envoient un « ping » toutes les deux secondes. Si la régie n’a pas de nouvelles de l’écran salle depuis six secondes, l’indicateur passe de « Écran salle connecté » à « non connecté ». On sait tout de suite si le projecteur a décroché.
L’annonce vocale, comme au vrai loto
Pour soulager la voix de l’animateur (ou pour doubler l’annonce), la régie peut lire les numéros à voix haute grâce à la synthèse vocale du navigateur, la Web Speech API. Pas de fichier audio, pas de service externe.
Et j’ai repris la tradition des lotos : on annonce le nombre, puis ses deux chiffres. Les numéros inférieurs à 10 ont droit au fameux « tout seul » :
|
1 2 3 4 5 6 7 |
const u = new SpeechSynthesisUtterance( n < 10 ? `le ${fr(n)}, tout seul` : `le ${fr(n)}. ${Math.floor(n / 10)}, ${n % 10}` ); u.lang = 'fr-FR'; u.rate = .9; speechSynthesis.speak(u); |
L’option se désactive d’une case à cocher si l’animateur préfère garder le micro pour lui.
Les lots préparés dans Excel
La liste des lots se construit souvent des semaines à l’avance, au fil des dons des partenaires. Plutôt que de tout ressaisir dans l’appli, on peut l’importer depuis un CSV : une ligne par lot, trois colonnes séparées par des points-virgules (le séparateur qu’Excel utilise par défaut en français).
|
1 2 3 4 |
Partie;Étape;Lot Partie 1;Quine;Coffret shampoing Partie 1;Double quine;Panier garni Partie 1;Carton plein;Raquette de badminton |
L’étape vaut Quine, Double quine ou Carton plein. Si une étape contient plusieurs lignes, le gagnant de l’étape remporte tous les lots correspondants. Un modèle est fourni dans le dépôt, la page permet de le télécharger, d’importer le fichier par glisser-déposer et d’exporter les lots actuels.
Ne rien perdre en cours de soirée
Tout l’état du loto (parties, boules tirées, gagnants) est enregistré automatiquement dans le localStorage du navigateur à chaque action. Fermer l’onglet par erreur ou recharger la page ne fait rien perdre, tant qu’on rouvre le fichier dans le même navigateur.
Au chargement, l’état sauvegardé passe par une petite fonction migrate() qui complète les champs manquants. C’est ce qui m’a permis de faire évoluer la structure des données (par exemple passer d’un lot unique à plusieurs lots par étape) sans casser une sauvegarde existante.
Les limites assumées
Le choix « un seul fichier, zéro serveur » a ses contreparties, et autant les dire :
- Régie et écran salle doivent tourner sur la même machine, dans le même navigateur. C’est le cas classique (un PC portable relié au projecteur), mais impossible de piloter l’écran depuis une tablette.
- La sauvegarde reste locale : changer de navigateur ou de PC, c’est repartir de zéro (d’où l’export des lots).
- La vérification des cartons reste manuelle : on saisit les 15 numéros. Rien de dramatique, c’est ce que fait déjà l’arbitre avec le carton papier.
Essayer chez vous
Le projet est public sur GitHub : HappyManuDev/Tableau-de-bord-loto-facile.
- Téléchargez
Dashboard_Loto.html(et le modèle CSV si besoin). - Ouvrez-le dans un navigateur récent et renseignez le nom de votre événement.
- Préparez vos parties et vos lots, à la main ou par import CSV.
- Ouvrez une deuxième fenêtre pour l’écran salle, et lancez le tirage.
Le logo et les couleurs sont ceux du SoBad, mais rien ne vous empêche de les remplacer par ceux de votre association. Si vous l’utilisez pour votre propre loto, je serais ravi d’avoir vos retours dans les commentaires… et bonne chance pour le carton plein !