Comment concevoir un Désastre ?

Vue d’ensemble

Les désastres sont un système d’effets visuels et sonores qui se déclenchent automatiquement sur le site selon des règles définies. Ils créent des expériences interactives surprenantes et ludiques pour les visiteurs.

Le système repose sur trois composants principaux :

  • Règles : Conditions de déclenchement basées sur le contenu (titre, artiste, etc.)

  • Recettes : Configuration des effets à appliquer

  • Désastres : Implémentations JavaScript/CSS des effets visuels

Architecture

Structure des fichiers

src/
├── apps/frontend/config/desastres/
│   ├── desastres.yml              # Configuration principale avec imports
│   ├── regles/                    # Règles de déclenchement
│   │   ├── colors.yml
│   │   ├── misc.yml
│   │   └── ...
│   ├── recettes/                  # Configuration des effets
│   │   ├── mangelettres.yml
│   │   ├── tts.yml
│   │   ├── splitouine.yml
│   │   └── ...
│   └── schemas/                   # Schémas de validation JSON
│       ├── regles.schema.json
│       ├── recettes.schema.json
│       └── desastres-main.schema.json
└── web/desastres/                 # Implémentations des désastres
    ├── mangelettres/
    ├── splitouine/
    ├── fish/
    ├── light/
    ├── tts/
    └── ...

Flux de fonctionnement

Diagram

Types de désastres

Désastres visuels

mangelettres

Fait disparaître progressivement certaines lettres du texte.

Technologie : GSAP SplitText

Options :

  • rate : Taux de suppression (0.0 à 1.0)

  • letters : Lettres à supprimer (ex: "aeiou")

  • selector : Sélecteur CSS de l’élément cible

Exemple :

recettes:
  mange_voyelles:
    enabled: true
    desastre: mangelettres
    options:
      rate: 0.5
      letters: "aeiou"
      selector: "section.content"

splitouine

Animation de texte avec effets de division et de mouvement.

Technologie : GSAP SplitText

Caractéristiques :

  • Division du texte en caractères individuels

  • Animations fluides et personnalisables

  • Effets de rotation, translation, opacité

fish (démo)

Affiche un poisson animé qui suit le curseur de la souris.

Technologie : Lottie

Comportement :

  • Suivi du curseur avec décalage vertical

  • Animation Lottie en boucle

  • Superposition en z-index élevé

light (démo)

Crée des rayons lumineux traversant la page.

Technologie : CSS + Web Animations API

Effet :

  • Trois rayons lumineux animés

  • Déplacement horizontal de gauche à droite

  • Rotation à 45°

musique (démo)

Retire les lettres "M" et les fait défiler sur une portée musicale SVG.

Technologie : anime.js + SVG

Particularités :

  • Extraction des lettres "M" et "m"

  • Génération dynamique d’une portée SVG

  • Animation sinusoïdale des lettres

noir / bleu

Change la couleur de fond de la page.

Implémentation : CSS simple

Options :

  • noir : Fond noir

  • bleu : Fond bleu

Désastres sonores

tts (Text-to-Speech) (démo)

Synthèse vocale du contenu de la page.

Technologie : Web Speech API

Options configurables :

  • rate : Vitesse de lecture (0.1 à 10, ou plage [min, max])

  • pitch : Hauteur de la voix (0 à 2, ou plage [min, max])

  • volume : Volume (0 à 1, ou plage [min, max])

  • lang : Code langue (ex: "fr-FR", "en-US")

  • voices : Liste de noms de voix préférées

  • texts : Textes à lire (si non fourni, lit le contenu de la page)

Exemple :

recettes:
  tts_robot:
    enabled: true
    desastre: tts
    options:
      rate: [0.8, 1.2]    # Vitesse aléatoire entre 0.8 et 1.2
      pitch: [0.5, 0.7]   # Voix grave
      volume: 0.8
      lang: "fr-FR"
      voices: ["Google français", "Microsoft Hortense"]

Désastres sur le son

bande-usee

Le premier désastre qui touche le morceau et non la page. Le site s’appelle Musique Approximative ; celui-ci prend ce nom au mot et fait flotter la hauteur du son, comme une bande magnétique passée trop de fois.

Une ligne à retard modulée dans un AudioWorklet — un retard qui varie est une hauteur qui varie. Deux oscillations superposées : le wow lent, le flutter rapide.

Trois couches, une seule modulation. L’oreille l’entend, l’œil la voit dériver sur le titre, la main la sent dans la réponse de la page au pointeur. Les contrepoints partagent le signal du processeur, ils ne l’imitent pas : c’est ce qui fait comprendre que l’effet est voulu. Un son qui flotte seul se lit comme une panne, et un visiteur qui croit le site cassé s’en va.

Le curseur n’est pas ralenti — aucune API ne le permet — c’est la page qui met du temps à le remarquer. Aucune propriété de mise en page n’est transitionnée : les clics restent exacts.

Deux sorties. prefers-reduced-motion: reduce retire les deux contrepoints en laissant le son ; ?sans-desastre retire tout. La seconde existe parce qu’aucun réglage standard ne permet de refuser une altération sonore — sans elle, ce serait le seul désastre auquel on ne peut pas échapper.

L’usure suit l’âge du morceau. Le catalogue va de juin 2008 à aujourd’hui — 6 643 jours, 8 216 morceaux — et l’ancienneté n’avait jamais rien déclenché ni modulé. Elle module désormais l’intensité :

morceau de intensité amplitude

2026

0,15

4,4 cents

2016

0,45

13,1 cents

2011

0,74

21,6 cents

2008

1,00

28,6 cents

La courbe — plancher 0,15 puis exposant 2 — a été choisie sur les morceaux réels. Une courbe linéaire placerait 43 % du catalogue dans la bande la plus altérée, ce qui ferait de l’usure l’état normal plutôt que la marque d’un âge. Le plancher existe parce que sans lui, 29 % des morceaux recevraient une altération inaudible alors que l’en-tête X-Desastre annonce le désastre.

La référence de dix-huit ans est une constante, et non l’étendue du catalogue : celle-ci grandit chaque jour, et l’usure d’un morceau donné dériverait sans que personne ne l’ait décidé.

La date de publication est lue dans la page, où elle ne figurait jusqu’alors nulle part — notable pour une archive de dix-huit ans. Elle n’est pas réservée au désastre.

Détail d’arborescence qui a sa raison : le processeur vit sous worklet/ et non javascript/, que le gestionnaire charge automatiquement en script classique — où AudioWorkletProcessor n’existe pas.

Voir src/web/desastres/bande-usee/README.adoc.

Désastres interactifs

redirect

Redirige vers une URL externe.

Options :

  • url : URL de destination

Exemple :

recettes:
  redirect_kraftwerk:
    enabled: true
    desastre: redirect
    options:
      url: "https://www.kraftwerk.com"

mamie

Affiche des images aléatoires en superposition.

Options :

  • imageFolder : Dossier contenant les images

  • imageBaseName : Préfixe des noms de fichiers

  • imageExtension : Extension des fichiers

  • imageCount : Nombre d’images disponibles

Configuration

Définir une règle

Les règles déterminent quand un désastre se déclenche.

Structure :

# yaml-language-server: $schema=../schemas/regles.schema.json

regles:
  - query: "query.title ~ /.*bleu.*/i"
    recettes: [bleu]
    probability: 0.7
    trigger: debug_bleu

Propriétés :

  • query (requis) : Expression de condition

    • Syntaxe : query.FIELD ~ /REGEX/FLAGS

    • Champs disponibles : title, artist, album, etc.

  • recettes (requis) : Liste des recettes à appliquer

  • probability : Probabilité de déclenchement (0.0 à 1.0, défaut: 1.0)

  • trigger : Paramètre URL pour forcer le déclenchement (ex: ?debug_bleu)

Unicité :

Une règle n’est évaluée qu’une fois, même si elle est déclarée dans plusieurs des fichiers importés par desastres.yml. Deux déclarations sont la même règle lorsque leur query, leur probability, leurs recettes et leur trigger sont identiques — c’est la copie d’un bloc d’un fichier à l’autre. Sans ce dédoublonnage, une règle à probability: 0.7 déclarée deux fois se déclencherait dans 91 % des cas au lieu de 70 %.

Deux règles qui ne diffèrent que par leur trigger restent en revanche deux règles : les fusionner reviendrait à supprimer un déclencheur, et un désastre ne doit jamais être observable seulement par tirage.

Une recette désignée par plusieurs règles satisfaites n’est appliquée qu’une fois, au rang de la première règle qui la désigne. C’est le cas courant lorsque deux conditions se recouvrent.

Exemples de conditions :

# Titre contient "fish" ou "poisson"
query: "query.title ~ /.*(fish|poisson).*/i"

# Artiste exact
query: "query.artist == 'Kraftwerk'"

# Titre commence par "La"
query: "query.title ~ /^La .*/i"

Créer une recette

Les recettes définissent comment le désastre se comporte.

Structure :

# yaml-language-server: $schema=../schemas/recettes.schema.json

recettes:
  nom_recette:
    enabled: true
    desastre: type_desastre
    scripts:
      - "/desastres/shared/gsap.min.js"
      - "/desastres/mangelettres/javascript/mangelettres.js"
    options:
      # Options spécifiques au désastre
      rate: 0.5
      selector: "section.content"

Propriétés :

  • enabled (requis) : Active/désactive la recette

  • desastre (requis) : Type de désastre (correspond au dossier dans /desastres/)

  • scripts : Scripts JavaScript à charger

  • options : Configuration spécifique au désastre

Validation avec JSON Schema

Le système utilise des schémas JSON pour valider la configuration.

Avantages :

  • ✅ Autocomplétion dans VSCode, IntelliJ

  • ✅ Validation en temps réel

  • ✅ Documentation intégrée

  • ✅ Prévention d’erreurs

Installation dans VSCode :

  1. Installer l’extension YAML (Red Hat)

  2. Ajouter la directive # yaml-language-server en haut des fichiers

  3. Profiter de l’autocomplétion ! 🎉

Validation en ligne de commande :

npm install -g ajv-cli

# Valider un fichier de règles
ajv validate -s schemas/regles.schema.json -d regles/colors.yml

# Valider un fichier de recettes
ajv validate -s schemas/recettes.schema.json -d recettes/tts.yml

Développement

Créer un nouveau désastre

  1. Créer le dossier dans src/web/desastres/mon-desastre/

    mkdir -p src/web/desastres/mon-desastre/javascript
    mkdir -p src/web/desastres/mon-desastre/css
  2. Créer le fichier JavaScript javascript/mon-desastre.js

    /**
     * Désastre Mon Désastre
     */
    (function() {
      console.log('🎭 Désastre "mon-desastre" activé');
    
      // Récupérer les options depuis window.desastreOptions
      const options = window.desastreOptions || {};
    
      // Implémenter l'effet
      function appliquerDesastre() {
        // Votre code ici
      }
    
      // Initialiser
      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', appliquerDesastre);
      } else {
        appliquerDesastre();
      }
    })();
  3. Créer le fichier CSS (optionnel) css/mon-desastre.css

  4. Créer la recette dans src/apps/frontend/config/desastres/recettes/mon-desastre.yml

    # yaml-language-server: $schema=../schemas/recettes.schema.json
    
    recettes:
      mon_desastre:
        enabled: true
        desastre: mon-desastre
        scripts:
          - "/desastres/mon-desastre/javascript/mon-desastre.js"
        options:
          # Vos options personnalisées
          intensity: 0.8
  5. Créer la règle dans src/apps/frontend/config/desastres/regles/mon-desastre.yml

    # yaml-language-server: $schema=../schemas/regles.schema.json
    
    regles:
      - query: "query.title ~ /.*test.*/i"
        recettes: [mon_desastre]
        probability: 1.0
        trigger: debug_mon_desastre
  6. Importer dans la configuration principale desastres.yml

    imports:
      - { resource: regles/mon-desastre.yml }
      - { resource: recettes/mon-desastre.yml }
  7. Documenter : Créer src/web/desastres/mon-desastre/README.adoc

Bibliothèques disponibles

Le système utilise plusieurs bibliothèques JavaScript :

  • GSAP : Animations avancées

    • Core : gsap.min.js

    • SplitText : SplitText.min.js

  • anime.js : Animations légères

  • Lottie : Animations vectorielles

  • Web Speech API : Synthèse vocale (natif)

Chemin des bibliothèques partagées :

/desastres/shared/
├── gsap.min.js
├── SplitText.min.js
└── ...

Débogage

Forcer un désastre avec trigger

Ajoutez le paramètre trigger dans l’URL :

https://musiqueapproximative.net/post/123?debug_bleu

Logs console

Tous les désastres loguent leur activation :

console.log('🎭 Désastre "mangelettres" activé');
console.log('Options:', options);

Inspecter la configuration

La configuration est disponible dans window.desastreOptions :

console.log(window.desastreOptions);

Liste des désastres disponibles

Nom Description Technologie

amour

Effet visuel d’amour

CSS/JS

bleu

Fond bleu

CSS

danse

Animation de danse

JS

fish

Poisson qui suit le curseur

Lottie

kraftwerk

Effet Kraftwerk

JS

light

Rayons lumineux

Web Animations API

mamie

Images aléatoires

JS

mangelettres

Suppression de lettres

GSAP SplitText

musique

Portée musicale avec lettres M

anime.js + SVG

noir

Fond noir

CSS

postillons

Effet de postillons

JS

redirect

Redirection URL

JS

robot

Effet robotique

JS

sale

Effet sale/glitch

JS

splitouine

Animation de texte

GSAP SplitText

tts

Synthèse vocale

Web Speech API

Ce que coûte un désastre, et pourquoi il n’y a pas de générateur

Le coût, mesuré

Un désastre complet demande aujourd’hui cinq fichiers dans quatre répertoires :

config/desastres/recettes/<nom>.yml

la recette — ce que le désastre fait

config/desastres/regles/<nom>.yml

la règle — quand il se déclenche

config/desastres.yml

deux lignes d’import à déclarer, une par fichier

web/desastres/<nom>/javascript/*.js

le comportement

web/desastres/<nom>/stylesheets/*.css

l’apparence

Plus deux schémas JSON à respecter, validables avec ajv — voir « Validation avec JSON Schema » ci-dessus.

La fréquence, mesurée

Les dix-neuf recettes existantes sont apparues sur deux jours, les 9 et 10 novembre 2025. Pas par un import massif : par pull requests distinctes et nommées — SPOOOOOKYYYYY (#18), Desastres-plomberie (#20), Consonnards et Voyellistes (#23), Plomberie (#27). Elles ont bien été écrites une à une, en rafale.

Depuis : 27 commits sur les désastres en novembre 2025, 3 en janvier 2026, 4 en août 2026. Aucun désastre neuf en neuf mois.

La décision

Il n’y a pas de générateur, et il n’y en aura pas tant que la mesure ressemblera à celle-ci. Outiller une tâche qui n’a pas été faite une seule fois depuis neuf mois coûterait plus qu’elle ne rendrait — l’outil serait à écrire, à comprendre, à maintenir, et à corriger à chaque évolution du format.

À quelle condition cette décision se révise. Si une rafale d’écriture reprend — disons trois désastres neufs en un mois — la question mérite d’être reposée, avec la mesure refaite. Le raisonnement ci-dessus ne vaut que par ses chiffres, et ils sont datés du 2026-08-19.

Ce qui ne serait pas une raison de la réviser : trouver l’écriture fastidieuse une fois. Le coût est de cinq fichiers ; ce qui coûtait réellement était de ne pas savoir où ils vont, et c’est cette page qui y répond désormais.

Bonnes pratiques

Performance

  • Lazy loading : Les scripts ne sont chargés que si le désastre est déclenché

  • Probabilités : Utilisez probability pour éviter la lassitude

  • Cleanup : Nettoyez les événements et éléments DOM créés

Accessibilité

  • Respect du prefers-reduced-motion :

    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (prefersReducedMotion) {
      // Désactiver ou réduire les animations
      return;
    }
  • Contrôles : Permettre de désactiver les effets

  • Audio : Respecter les préférences de volume

Maintenance

  • Documentation : Chaque désastre doit avoir un README.adoc

  • Versioning : Documenter les changements dans les recettes

  • Tests : Tester avec différents contenus et navigateurs

Troubleshooting

Le désastre ne se déclenche pas

  1. Vérifier la règle : La condition est-elle remplie ?

    console.log('Titre:', document.title);
  2. Vérifier la probabilité : Essayer avec probability: 1.0

  3. Utiliser le trigger : Forcer avec ?debug_nom_desastre

  4. Vérifier les scripts : Ouvrir la console pour voir les erreurs

Erreur de chargement de script

  • Vérifier le chemin dans scripts

  • S’assurer que le fichier existe

  • Vérifier les permissions

Conflit entre désastres

  • Certains désastres peuvent interférer (ex: deux désastres modifiant le même texte)

  • Utiliser des sélecteurs CSS spécifiques

  • Tester les combinaisons

Performance dégradée

  • Réduire le nombre d’éléments animés

  • Utiliser will-change CSS pour optimiser

  • Débouncer les événements (scroll, mousemove)