Construire le WordPress : ce qu'on garde du prototype, comment on l'assemble

Mis à jour le 06/10Le plan suit le prototype du 06/10 (une seule feuille de style, version mobile, bande de position, moments clés lus dans la frise). À lire en premier : la section 0 (ce qui a changé depuis le 01/10) et la section 19 (dans quel ordre on construit le thème). Rien n'est construit dans WordPress : Emmanuel tranche d'abord les points de la section 14.

> 08/10 (soir) : pour la construction, ce plan est remplacé par la spécification docs/plans/2026-10-08-specification-wordpress.md (sections 1 à 13, 15 et 19 ; les étudiants n'interviennent plus). Ses sections 14 (décisions) et 16 (vérifications) restent la mémoire des choix. Le WordPress local est construit d'après la spécification.

Plan du passage à WordPress, la prochaine étape du projet. Écrit le 1er octobre 2026 pour la séance du 5 octobre, mis à jour le 6 octobre au soir, puis le 7 au soir après l'audit de Ryan et la revue complète du prototype (docs/revue-2026-10-07/RAPPORT.md) : la séance du 5 a porté sur le prototype, pas sur WordPress, et le prototype a beaucoup changé depuis (section 0). Rien n'est encore construit : Emmanuel tranche les points de la section 14, la session prépare le socle, les étudiants construisent les pages en séance (date à fixer). L'ordre de construction est en section 19. Source de cette page : docs/plans/2026-10-05-construire-wordpress.md ; elle se régénère par python3 docs/outils/pages-organisation.py.

Base de l'analyse : le prototype de la branche main (commit 1cfa8e1, en ligne le 06/10, puis corrigé le 07/10 d'après l'audit de Ryan et la revue ; la première version de ce plan partait de 13979fe, 01/10), le WordPress local (7.1.2, ACF gratuit 6.8.10 depuis le 01/10, modèle du 24/09, inchangé depuis le 01/10), la maquette Figma « FINALE » relue par le connecteur Figma, et le code de WordPress lui-même pour chaque mécanisme dont le plan dépend (section 16).

0. Où on en est le 07/10 : ce qui a changé depuis la première version

Côté WordPress, rien n'a bougé depuis le 01/10. Le thème marais n'existe pas (thème actif : twentytwentyfive), la base ne contient ni événement ni thématique, le type presse et la page Presse ne sont pas créés, la page activites n'est pas renommée. Les gabarits préparés le 01/10 attendent dans docs/en-reserve/wordpress-gabarits/ (à reprendre, section 9).

Côté prototype, beaucoup a changé du 05 au 07/10. Décisions d'Emmanuel, et ce qu'elles changent pour le thème :

DateDécisionPour le thème
01/10ACF gratuit, six sections fixes pour les thématiques, coordonnées sur la page Contactsections 5 et 13 (inchangé)
06/10Une seule feuille de style, assets/css/site.css : 17 sections dans l'ordre de la cascade (la 17e, l'échelle de texte sur téléphone, depuis le 07/10), sommaire en tête ; les anciennes feuilles (tokens, base, composants, 39 feuilles de blocs, une par page) et les <style> des pages n'existent plus ; les étudiants ne touchent plus aux CSSfunctions.php la charge une fois, minifiée en production ; plus aucune feuille par page ni par bloc (sections 3, 4, 7, 8)
06/10Frise sur ordinateur : une seule échelle, --px-h (1 px de la maquette à l'échelle de la hauteur de l'écran), pour toutes les mesuresrien à coder ; toute règle nouvelle de la frise s'écrit calc(N * var(--px-h))
06/10Version mobile d'après la FINALE (cadre « Mobile », 487:1095) : frise verticale sous 768 px, barre des chapitres en bas, menu plein écran, mesures en --px-mle même HTML sert les deux : rien à coder si le HTML et les classes sont repris exactement (section 7)
06/10Bande de position : cinq parts égales depuis le 07/10 (audit de Ryan ; proportionnelles à la longueur des chapitres le 06/10), barre qui arrive sur le nom quand un chapitre commence, donc plus rapide dans un chapitre court, noms bruns ou crèmes selon ce qui passe derrière (frise.js)HTML exact de la bande (section 7) ; images de la frise servies par le même domaine que la page
06/10Moments clés de la page Alliance = événements de la rubrique Aujourd'hui (chapitre 05 de la frise)requête par rubrique, plus par la case Alliance (sections 2 et 9)
06/10Pied de page : la grille de Daria sur ordinateur, le pied de la frise sur mobile, sur toutes les pages qui en ont un ; la fiche d'un moment clé n'a pas de pied de page, donc celle d'une action à venir non plus (un seul gabarit, section 6) ; la branche daria n'est pas reprisefooter.php (section 3) ; point 7 de la section 14 réglé
06/10Aperçus de partage (Open Graph) sur toutes les pagessection 17
06/10Lien Itinéraire des cartes d'action laissé tel quel dans le prototype, corrigé dans WordPresssections 5, 14 (point 19) et 18
06/10Une seule couleur de bordure (--bordure) ; flèche de Contact = celle de Presse ; Vision alternative de Veronika, photos et noms 30 % plus grands sous 600 px ; titres en escalier calés sur la grille (accueil)rien de plus que copier site.css et les scripts
06/10Photos de la frise recadrées de leur bord clair à la source (champ recadrage de docs/photos-frise.json, script teinter-photos.py)copier les fichiers du prototype tels quels
07/10Audit de Ryan : une durée et une courbe pour toutes les transitions (--duree, --courbe, section 1 de site.css), survols réservés à la souris (@media (hover: hover) and (pointer: fine)), navigation en dvh, Contact et Crédits refaits d'après la FINALE (Crédits sans pied de page, Contact avec le pied commun sur mobile), échelle de texte sur téléphone (section 17)rien de plus que copier site.css ; les pieds de page de Contact et Crédits (section 3)
07/10Logo : le triton dans la navigation ; l'icône de l'onglet (favicon) garde le « M » de l'ancien logo (docs/outils/icones.py)header.php ; l'icône du site dans WordPress (section 14, point 26)
07/10Photos des moments clés en brun, comme la maquette ; teinte à l'envoi décidée pour les photos ajoutées par l'Alliance (section 14, point 12)la carte prend la copie brune de son image mise en avant, faite à l'envoi (section 8, bloc 8) : l'import envoie la photo d'origine en couleurs (section 12, point 4) ; les variantes -brun.webp ne servent qu'au prototype
07/10Revue du prototype (docs/revue-2026-10-07/RAPPORT.md) et ses corrections : .home renommé .accueil, Presse refaite d'après la FINALE, images en WebP à leur taille d'affichage (accueil 8,9 Mo devenu 1,7 Mo), lien d'évitement, polices préchargées (template-parts/tete.php), frise au clavier et à la molette, pied de page sans bande vide, étiquettes du formulaire de contactsections 3, 8 et 19 ; les points encore à trancher sont en section 14, points 22 à 31

Points d'attention nouveaux, relevés en relisant le prototype à travers WordPress, à traiter en construisant :

Cas d'école pour la séance : comment on fait un choix technique

Emmanuel, le 01/10 : « C'est un bel exemple de comment on doit faire les choix techniques. » À discuter avec les étudiants à la première séance WordPress, dans la partie sur les choix techniques. La question de départ était simple : faut-il ACF Pro ? La réponse a demandé huit gestes, dans cet ordre. Chacun se transpose à n'importe quel choix d'outil.

  1. Remonter à l'origine du choix. ACF Pro avait été pris parce qu'Emmanuel l'avait demandé le 24/09, pas parce qu'un besoin l'exigeait. Un outil choisi par habitude ou par confort se réexamine dès qu'il coûte quelque chose : ici, une licence à vérifier pour 13 postes, le point qui bloquait la séance.
  2. Vérifier ce que l'outil fait vraiment, dans le code, pas de mémoire. Ce qui est payant vit dans le dossier pro/ du plugin : on le liste. Puis une commande liste les champs Pro réellement utilisés par notre modèle. Résultat : trois fonctions seulement (répéteur, galerie, page d'options), et tout le reste est gratuit, y compris la création des types de contenu.
  3. Partir du besoin, pas de l'outil. Pour chaque champ Pro, la question n'est pas « comment garder le répéteur ? » mais « de quoi la page a-t-elle besoin ? ». Trois réseaux sociaux fixes n'ont pas besoin d'un répéteur ; une page de crédits n'a pas besoin de champs du tout.
  4. Regarder les données réelles pour dimensionner. Les sept textes de la Vision alternative dans le SPIP ont de zéro à cinq intertitres. Cinq sections fixes suffisent donc : le répéteur et son nombre illimité ne servent à rien ici.
  5. Voir la page avant d'en parler. Emmanuel n'avait pas compris la phrase « sections typées » : on a ouvert la page « Eau » dans le navigateur. Et la page a corrigé l'explication elle-même : la photo est sous le texte, pas à côté comme dit d'abord.
  6. Chercher les options cachées. La première réponse opposait deux voies (répéteur payant, ou texte libre qui perd la mise en page). En creusant, cinq façons sont apparues, dont deux gratuites qui gardent la mise en page exacte (sections fixes, ou découpe du texte par le gabarit). Une alternative à deux options est presque toujours incomplète.
  7. Comparer sur les mêmes critères, dans un tableau. Pour chaque option : ce que voit le client, la quantité de code, ce qu'on garde de la maquette, ce qu'on perd (ici, la garantie que la structure sera respectée), le coût (licence, temps). Puis une recommandation, et la décision reste à celui qui en porte les conséquences.
  8. Le même raisonnement, sur une autre question le même jour. Lien « Itinéraire » : faut-il une API pour transformer l'adresse en carte OpenStreetMap ? On a lu le code source d'OpenStreetMap : sa page d'itinéraire accepte l'adresse en texte dans le lien et la cherche elle-même. Aucune API à appeler de notre côté. Sans la lecture du code, on aurait écrit une intégration inutile.

Le détail de chaque étape est plus bas : section 13, « ACF Pro est-il nécessaire ? » et « Et les bordures ? » ; section 5, « Le lien Itinéraire ». Le récit est dans le carnet de bord, 01/10.

1. En bref

2. La logique Alliance : cohérente, à six conditions

La demande du 01/10 est le modèle du 24/09 (un seul type « événement », trois vues sans doublon) ramené à une page : la page Alliance réunit l'ancienne vue « Activités » (les actions cochées) et l'ancienne vue « Agenda » (le futur). La maquette FINALE le montre telle quelle : trois cartes « Rejoignez nos prochaines actions » en haut, six cartes « Moments clés de la mobilisation » en bas (échantillons répétés). Depuis le 06/10, les cartes du bas sont les événements du chapitre 05 « Aujourd'hui » de la frise (blocs 35 à 39 dans le prototype, template-parts/moments-cles.php), cochés ou non : la case action_alliance ne sert plus qu'à la liste du haut, au bandeau de l'accueil et au bouton Fermer des fiches.

  1. La date qui fait foi est date_tri (champ date, enregistré AAAAMMJJ, obligatoire). « Aujourd'hui » se calcule par current_time('Ymd'), jamais par date() : WordPress force PHP en UTC, et entre minuit et deux heures date() donne encore la veille.
  2. Une règle unique pour « à venir » : fin = date_fin si elle est remplie, sinon date_tri ; à venir si fin >= aujourd'hui. Un événement du jour reste en haut jusqu'à minuit ; un événement sur plusieurs jours (une exposition) reste en haut jusqu'à sa date de fin. La même règle sert à la page Alliance, à la fiche et au bandeau de l'accueil.
  3. La frise exclut le futur : elle montre les événements dont date_tri <= aujourd'hui. C'est ce que dit la demande du 01/10 (« quand la date sera passée, il apparaîtra dans la ligne du temps ») et ça remplace « la frise montre tout » du 24/09. À dater dans les décisions.
  4. Une action de l'Alliance prend la rubrique « Aujourd'hui ». Le champ rubrique est obligatoire et un champ taxonomie ACF n'a pas de valeur par défaut (vérifié) : l'instruction du champ le dit en toutes lettres. L'action passée entre alors dans le chapitre 05, après les blocs 35 à 38. La disposition a déjà « image et texte » par défaut.
  5. Un bloc générique affiche tout événement sans fichier de bloc (créé par l'Alliance dans l'admin) : même contrat de section, image, titre, date, carte avec le texte court et « Lire la suite ». Sans lui, la frise sauterait l'événement.
  6. Les ordres : page Alliance, haut = du plus proche au plus lointain ; bas = l'ordre de la frise (date_tri croissante), comme le prototype depuis le 06/10 (l'ordre inverse reste possible : section 14, point 18) ; tout est affiché (les trois et six cartes de la maquette sont des échantillons répétés) ; frise = chapitre dans l'ordre de l'admin, puis date_tri croissante.

Deux conséquences. Le bloc 39 « Aujourd'hui : la lutte continue » ne resterait pas dernier : les deux « Samedi de Tritoni-e » (05/09 et 19/09/2026) le dépasseraient. Recommandé (section 14, points 2 et 17) : il devient une clôture fixe, rendue après le dernier chapitre, son « Lire la suite » menant à la page Alliance. Depuis le 06/10, cocher un bloc historique ne le met plus dans les moments clés : ce sont les événements de la rubrique Aujourd'hui. Mais le bloc 39 est aussi une carte des moments clés dans le prototype : s'il devient une clôture hors base, il en sort ; s'il reste un événement, il y reste (section 14, point 17).

Deux cas limites acceptés : le jour même, un événement est à la fois en haut de la page Alliance et dans la frise ; une exposition commencée hier est en haut de la page Alliance et déjà dans la frise. Dans la maquette, la même photo est verte tant que l'action est à venir, sépia (brune) une fois passée. Réglé le 07/10 (point 12 de la section 14) : l'Alliance envoie une seule photo, en couleurs d'origine, et WordPress en fabrique lui-même une version par teinte à l'envoi ; le gabarit affiche la verte ou la brune selon la date. Toujours aucun filtre dans le navigateur.

3. Ce qu'on récupère du prototype

Le thème s'appelle marais, dans wordpress/wp-content/themes/marais/. Son arborescence assets/ est celle du prototype : les url() de site.css (../fonts/, ../img/) restent valables tant que la feuille, ou sa version minifiée, reste dans assets/css/. C'est l'unique exception à « jamais de ../ » : un chemin dans une CSS est relatif au fichier CSS, pas à la page.

PrototypeThèmeTel quel ?Ce qui change
assets/css/site.css (la seule feuille, 17 sections)même cheminouiles règles du bloc générique s'y ajoutent (section 3 de la feuille), en --px-h sur ordinateur et en --px-m sur mobile ; celles des cinq ouvertures y sont déjà (section 4) ; servie minifiée en production (site.min.css, section 8) ; le bloc « Anciens noms » de la section 1 reste (33 usages)
assets/fonts/ (Bugrino, Geist)idemouila police d'essai Neue Haas n'est jamais copiée (elle n'est plus dans le dépôt depuis le 07/10, seulement sur le disque du professeur)
assets/img/ (hors timeline), assets/video/idemouien WebP à leur taille d'affichage depuis le 07/10 ; img/Presse sert aussi au menu (flèche de Contact) : le renommer en img/presse partout à la fois, ou le garder ; placeholder.svg écarté (les anciens posters et alliance-moment.png, inutilisés, sont retirés du prototype depuis le 07/10) ; les photos des cartes de Vision et des fiches de thème vont en médiathèque, pas dans le thème (images des thématiques), leurs originaux sont dans docs/sources-images/ ; les icônes (img/icones/) seulement si le point 26 de la section 14 les garde dans le thème
assets/img/timeline/ (33 WebP teintés et recadrés ; les 5 variantes brunes -brun.webp des moments clés ne servent qu'au prototype)idemouiles 33 WebP copiés tels quels, jamais de filtre CSS (décision du 30/09) ; les 7 JPG du bloc 21 ne sont utilisés par aucun bloc (le 21 n'a pas d'image dans la frise) : ils vont en médiathèque (images de l'événement 21, quatre au plus), pas dans le thème ; une régénération passe par docs/outils/teinter-photos.py et docs/photos-frise.json, jamais par les originaux de _Preparation/
assets/img/og/ (10 images de partage)idemouisection 17
template-parts/timeline/slug.php (34 blocs ; sans numéro depuis le 08/10, point 3)34 copiéspresquedeux remplacements par script : src="assets/..." devient un echo get_theme_file_uri(), href="single.php" devient the_permalink() ; leurs règles sont déjà dans la section 4 de site.css
blocs 01, 03, 14, 27, 34 (ouvertures)template-parts/timeline/ouverture.phpnonun seul fichier, lu sur le terme rubrique : titre, numéro, sous-menu ; il garde les ids et classes du prototype (evt-01-rubrique-contexte...), que site.css et la bande de position attendent (section 7)
template-parts/timeline/a-faire.phptemplate-parts/timeline/generique.phpnonla boîte grise devient le bloc d'un événement sans fichier
template-parts/menu-chapitres.phpidempresquelit la liste construite par la frise au lieu de $evenements global
template-parts/pied-frise.phpidempresquecoordonnées lues sur la page Contact (groupe Coordonnées), liens vers les pages légales
template-parts/moments-cles.phpla boucle du bas de page-alliance.phpnonrequête sur la rubrique Aujourd'hui (section 9) au lieu de lire data/evenements.php et les fichiers de blocs ; la photo de la carte est en brun : la copie teintée à l'envoi de son image mise en avant (section 8, bloc 8 ; le prototype, lui, prend une variante -brun.webp) ; plus, si l'option C du point 17 est retenue, la carte du bloc 39 écrite après la boucle
template-parts/og.php, data/og.phpune fonction du thème sur wp_headnonsection 17
histoire-contexte.phppage-histoire-contexte.phpnondata/evenements.php remplacé par une requête ; include remplacé par locate_template à trois replis ; même boucle
data/evenements.php, outils/generer.phpdisparaissentla liste vient de la base ; l'import remplace le générateur
assets/js/frise.js, nav.js, hero-video.js, bandeau.js, constellation.jsidemouichargés par wp_enqueue_script (hero-video.js et bandeau.js sur l'accueil seulement) ; frise.js ne change pas si le HTML de la frise et de la bande est repris exactement (quatre conditions, section 7) ; GSAP depuis cdnjs ou copié dans assets/js/vendor/ (section 14, point 13)
template-parts/nav.php et template-parts/tete.phpheader.phppresquereçoit le <head> (wp_head()), les deux polices préchargées et les icônes de tete.php (ou l'icône du site de WordPress, point 26), le lien d'évitement « Aller au contenu » en tête du body, <html class="no-js"> et son script d'une ligne sur la frise, body_class( isset( $args['body'] ) ? $args['body'] : '' ), $page_courante calculé par is_page() et is_singular() (presse comprise) ; ne charge plus nav.js lui-même (mis en file par functions.php) ; flèche de Contact par get_theme_file_uri() ; les six entrées restent écrites dans le fichier (pas de wp_nav_menu, qui exigerait un walker pour les classes nav-m__*)
template-parts/footer.php (Daria)footer.phppresquecoordonnées et réseaux lus sur la page Contact, wp_footer() ; HTML de la grille de Daria ; sa CSS est la section 15 de site.css (ordinateur : la grille ; mobile : le pied de la frise, décision du 06/10)
footer-nu.phpnouveautrois lignes (wp_footer(), fermeture du body) pour les gabarits sans pied de page : la frise (son pied est dans la piste), la fiche d'un événement, la fiche d'une thématique, Vision alternative, Crédits (sans pied depuis le 07/10) ; appelé par get_footer( 'nu' )
index.phpfront-page.phppresquebandeau de trois actualités par requête ; hero, vidéo et trois sections en dur au départ ; les cartes mènent aux pages ; .home renommé .accueil (fait le 07/10) ; bandeau.js remet le bandeau à zéro après une visite au clavier
alliance.phppage-alliance.phppresquela boucle for des actions et l'include des moments clés deviennent deux requêtes (section 9)
presse.phppage-presse.phpnonrefaite d'après la FINALE le 07/10 : en-tête comme Alliance (photo en <img>, titre highlight-title), cartes « press-card » dans un main.presse ; boucle sur le type presse au lieu du tableau $articles, carte liée à l'URL de l'article dans un nouvel onglet ; la classe de body page-presse n'est plus exigée ; sa CSS est dans site.css (section 10)
ressources.phppage-ressources.phpnontableau remplacé par get_posts, format déduit du premier champ rempli, échappement ; CSS dans site.css (section 11)
contact.phppage-contact.phppresquecoordonnées lues dans les champs de la page elle-même (groupe Coordonnées), texte depuis l'éditeur, formulaire HTML sans traitement au départ (étiquettes cachées et autocomplete depuis le 07/10 ; champ e-mail : section 14, point 27) ; get_footer() : le pied commun, visible sur mobile seulement (site.css le cache au-dessus de 768 px par la règle .contact + .footer-grid : footer.php ne doit rien écrire entre </main> et la grille)
credits.phppage-credits.phpnouveau gabaritles colonnes de rôles (listes dl) ne sortent pas d'un éditeur : structure écrite dans le gabarit, ou trois champs texte, un par colonne (la section 13 du plan propose l'éditeur : à trancher, section 14, point 25) ; get_footer( 'nu' ) : pas de pied depuis le 07/10 (audit de Ryan, D-09) ; CSS dans site.css (section 13)
vision-alternative.php (version de Veronika, 05/10)page-vision-alternative.phppresqueles huit thèmes viennent de la base (positions dans quatre champs ACF) ; les traits restent deux tableaux PHP, 14 sur ordinateur et 13 sur mobile, d'après la FINALE (07/10) ; classe de body page-vision
single.php (fiche de Veronika)single-evenement.phppresquetitre, date, photo, lieu, texte lus en base ; classe --a-venir ou --passe ; classe de body page-single ; pas de pied de page (décision du 06/10) ; le pied intérieur « Restez informé / Contact » reste
single-page-alternative.phpsingle-thematique.phppresque$theme remplacé par le titre, l'image mise en avant et six sections fixes section_1 à section_6 (décidé le 01/10, une section vide n'est pas affichée) ; classe de body single-page-alternative ; navigation sur mobile seulement, ni pied de page ni « Restez informé » (version de Veronika, 05/10), sauf décision contraire d'Emmanuel
template-parts/pied-fiche.phpnouveau« Restez informé » et « Contact » de la fiche d'un événement, lus sur la page Contact
style.css, index.php, page.php, functions.php, acf-json/nouveauxen-tête du thème, repli, pages simples (Mentions légales, Confidentialité), huit blocs (section 8), structure ACF versionnée

En chiffres : 33 événements importés (le 39 devient une clôture, sauf décision contraire : section 14, point 17), 5 ouvertures générées, 34 fichiers de bloc copiés (dont la clôture), aucune CSS de bloc à copier (section 4 de site.css), 2 événements sans fichier dès le premier jour (les Tritoni-e, rendus par le générique), 22 fichiers PHP et CSS à écrire ou adapter (la colonne « Thème » ci-dessus), plus acf-json/.

Les trois défauts du prototype relevés le 01/10 (footer.html inclus par Presse et Ressources, $page_courante absent de Presse) sont corrigés et en ligne depuis le 06/10. Reste : la fiche du prototype (single.php, ligne 51) pointe encore vers Google Maps.

4. Les cinq gestes pour convertir une page

Un gabarit WordPress est une page du prototype à laquelle on applique cinq gestes, toujours les mêmes. Il n'y a plus de geste « charger sa feuille de style » : depuis le 06/10, tout le style est dans site.css, chargée une fois pour tout le site. Exemple sur ressources.php (vingt minutes, en direct, étape 3 de la séance).

  1. Le haut et le bas : get_header() remplace tout le <head> et l'include de la navigation ; get_footer() remplace l'include du pied de page (get_footer( 'nu' ) pour une page sans pied). Trois pages passent leur classe de body : get_header( null, array( 'body' => 'page-vision' ) ) (section 6).
  2. Les données : le tableau écrit en dur devient $ressources = get_posts( array( 'post_type' => 'ressource', 'numberposts' => -1 ) );.
  3. Les valeurs : $r[0] devient $r->post_title ; les champs ACF se lisent par get_field( 'date_publication', $r->ID ) ; un terme par get_field( 'type_ressource', $r->ID )->name.
  4. L'échappement : htmlspecialchars() devient esc_html() dans le texte, esc_attr() dans un attribut, esc_url() dans un href ou un src.
  5. Les chemins : assets/img/x.svg devient <?php echo esc_url( get_theme_file_uri( 'assets/img/x.svg' ) ); ?> ; un lien vers une page devient home_url( '/presse/' ) ; un lien vers un contenu devient the_permalink().

Une règle qui va avec : aucune feuille, aucun <style>, aucune police externe dans un gabarit. Une règle CSS nouvelle va dans la section de sa page de site.css, ou dans la section 2 si elle sert partout ; dans la frise, ses mesures s'écrivent en --px-h (ordinateur) et --px-m (mobile).

Avant, dans ressources.php :

<?php foreach ($ressources as $r) { ?>
    <li class="ressources__item">
        <a class="ressources__lien" href="<?php echo $r[4]; ?>" target="_blank">
            <span class="ressources__nom"><?php echo $r[0]; ?></span>
            <span class="ressources__infos">
                <span class="ressources__type"><?php echo $r[1]; ?></span>
                <span class="ressources__annee"><?php echo $r[2]; ?></span>
                <span class="ressources__format"><?php echo $r[3]; ?></span>
            </span>
            <img class="ressources__fleche" src="assets/img/resource-item__arrow.svg" alt="flèche">
        </a>
    </li>
<?php } ?>

Après, dans page-ressources.php :

<?php foreach ( $ressources as $r ) : ?>
<?php $lien = (string) get_field( 'lien_externe', $r->ID ); ?>
    <li class="ressources__item">
        <a class="ressources__lien" href="<?php echo esc_url( $lien ); ?>" target="_blank" rel="noopener">
            <span class="ressources__nom"><?php echo esc_html( $r->post_title ); ?></span>
            <span class="ressources__infos">
                <span class="ressources__type"><?php echo esc_html( get_field( 'type_ressource', $r->ID )->name ); ?></span>
                <span class="ressources__annee"><?php echo esc_html( substr( (string) get_field( 'date_publication', $r->ID ), 0, 4 ) ); ?></span>
                <span class="ressources__format"><?php echo esc_html( marais_format( $r->ID ) ); ?></span>
            </span>
            <img class="ressources__fleche" src="<?php echo esc_url( get_theme_file_uri( 'assets/img/resource-item__arrow.svg' ) ); ?>" alt="">
        </a>
    </li>
<?php endforeach; ?>

(marais_format() : le premier champ rempli parmi fichier_pdf, fichier_audio, video, lien_externe, rendu « PDF », « Audio », « Vidéo » ou « Lien » ; quatre lignes en tête du gabarit. La flèche est décorative : alt="".)

5. Le modèle de contenu : ce qui existe, ce qui change

Tout se fait dans l'admin (ACF > Types de publication, Taxonomies, Groupes de champs ; il n'y a plus de pages d'options avec ACF gratuit). Les clés sont celles du 24/09 (docs/MODELE-WORDPRESS.md). Au 06/10, rien de la colonne « Ce qui change » n'est fait. Les modifications se font sur le poste d'Emmanuel avant la séance WordPress, jamais en séance sur les postes des étudiants : ACF nomme chaque objet par une clé unique, treize postes qui refont les mêmes clics donneraient treize structures différentes.

TypeExisteFicheCe qui change avant la séance WordPress
Événement (evenement)ouisingle-evenement.phpchamp adresse (texte) après lieu, si le point 19 de la section 14 le retient ; libellé et instruction de action_alliance réécrits (ils citent la page Activités) ; instruction de rubrique : « Pour une action de l'Alliance : Aujourd'hui » ; date_tri renommée « Date » ; message « Identifiant du bloc » corrigé (NN-slug.php) ; fait le 01/10 (ACF gratuit) : la galerie devient un groupe images (image_1 à image_4), le répéteur documents un groupe de trois emplacements document_1 à document_3 (intitule, fichier)
Presse (presse)nonaucune (la carte ouvre l'article)type à créer : titre seul, non interrogeable publiquement, permalien « Aucun », icône document ; champs url (obligatoire), media (BX1, RTBF, Bruzz...), mention (« Reportage de... »), date_publication (obligatoire, sert au tri), langue
Ressource (ressource)ouiaucune (la ligne ouvre le fichier ou le lien)passer « publiquement interrogeable » à non et permalien à « Aucun » (le slug ressources est aussi celui de la page) ; types : garder Publication et Dossier, ajouter Étude, Avis officiel, Plan et permis, Réclamation et recours, Texte de loi, supprimer Revue de presse ; langue facultatif
Thématique (thematique)ouisingle-thematique.phpfait le 01/10 : six sections fixes section_1 à section_6 (champs groupe gratuits : intitulé, texte, image), un message en tête de l'écran qui explique le choix, onglets Constellation et Section 1 à 6 ; champ galerie retiré (payant, inutilisé par la maquette) ; reste à ajouter quatre nombres position_x, position_y, position_mx, position_my (place de la carte dans la constellation, écran puis mobile)
Rubrique (rubrique, 5 termes)ouiaucune (taxonomie non publique)rien ; l'ordre vient de Simple Custom Post Order ; image_ouverture et introduction restent inutilisés par la FINALE
Coordonnées (groupe sur la page Contact)fait le 01/10remplace la page d'options « Infos du site » (fonction payante) : email_contact, telephone, adresse, reseau_instagram, reseau_facebook, reseau_mastodon ; seule source des coordonnées : pied de page, pied de frise, pied des fiches, page Contact ; retrouvée par get_page_by_path( 'contact' )
Pagesouifront-page.php, page-<slug>.php, page.php« Activités » renommée « Alliance » (slug alliance) ; page « Presse » créée ; Agenda, À propos, Agir, Cookies en brouillon (absentes de la navigation et de la maquette) ; Mentions légales et Confidentialité gardées sur page.php ; Crédits sur page-credits.php (section 3) ; fait le 01/10 : le groupe Crédits (répéteur payant) est supprimé, les crédits s'écrivent dans l'éditeur de la page ou dans trois champs texte, selon le choix de la section 3 ; l'accueil reçoit trois blocs fixes calqués sur la maquette (marais, alliance, ecologies) au lieu d'un répéteur

Ce que lit chaque gabarit, champ par champ :

GabaritNatifsChamps ACF
carte « action à venir » (page Alliance)titre, image mise en avant, liendate_tri (jour, date), horaires, lieu, adresse (lien Itinéraire)
carte « moment clé » (page Alliance) : événement passé de la rubrique Aujourd'huititre, image mise en avant, liendate_affichee seule (pas de date si elle est vide, comme le prototype), première phrase de resume coupée à 160 caractères
bandeau de l'accueiltitre, image mise en avant, liendate_tri, date_fin, action_alliance
bloc générique de la frisetitre, liendate_affichee sinon date_tri, resume, images (image_1) sinon image mise en avant, disposition
fiche d'un événementtitre, image mise en avant, texte longdate_tri, date_fin, date_affichee, horaires, lieu, adresse, action_alliance et rubrique (cible du bouton Fermer)
carte pressetitreurl, media, mention, date_publication, langue
ligne ressourcetitretype_ressource, date_publication (année), puis le premier rempli de fichier_pdf, fichier_audio, video, lien_externe
carte de la constellationtitre, slug, ordreimage_menu, position_x, position_y, position_mx, position_my
fiche d'une thématiquetitre, image mise en avantsection_1 à section_6 (intitule, texte, image) ; une section vide est sautée
pieds de page et page Contactsur la page Contact : email_contact, telephone, adresse, reseau_instagram, reseau_facebook, reseau_mastodon (un réseau vide n'est pas affiché)

Versionnement : un dossier acf-json/ dans le thème. Dès qu'il existe et que le thème est actif, ACF y écrit un fichier par type, taxonomie et groupe à chaque enregistrement. Ce dossier remplace docs/wordpress-acf.json, qui devient une archive.

Le lien « Itinéraire » : OpenStreetMap, pas Google Maps (question d'Emmanuel, 01/10 fin d'après-midi)

La maquette demande OpenStreetMap : commentaire d'Imane sur la page Alliance de la FINALE (« lien openstreetMap »), confirmé par Emmanuel. Le prototype de la fiche (prototype/single.php, ligne 51) pointe encore vers Google Maps ; celui de la page Alliance pointe depuis le 05/10 vers OpenStreetMap (search?query=, Elliot), avec une adresse qui n'est pas celle de la carte (décision du 06/10 : corrigé dans WordPress, section 18). Le plan et le code en réserve passent à OpenStreetMap.

Question d'Emmanuel : soit les éditeurs encodent l'adresse et on construit le lien (avec une API ?), soit ils vont chercher le bon lien eux-mêmes et le collent dans un champ URL.

Ce que fait OpenStreetMap, lu dans son code source (openstreetmap-website, fichiers app/assets/javascripts/index_modules/directions.js lignes 205 à 212, directions/endpoint.js et search.js lignes 75 à 77) : la page d'itinéraire lit les paramètres from et to de l'adresse et remplit ses deux champs ; un texte qui n'est pas une paire de coordonnées est géolocalisé par OpenStreetMap lui-même. La page de recherche lit le paramètre query. Une adresse qui répond 200 prouve seulement que la page s'ouvre : il faut regarder où OpenStreetMap pose la destination. « Rue de Scheutveld, 1070 Anderlecht », l'exemple de la première version, mène rue Van Soust (Moortebeek), à 1,8 km : la rue s'appelle « Rue Scheutveld » (vérifié le 06/10).

https://www.openstreetmap.org/directions?to=Rue%20Scheutveld%2C%201070%20Anderlecht
https://www.openstreetmap.org/search?query=Rue%20Scheutveld%2C%201070%20Anderlecht
A. L'éditeur tape l'adresse, le site construit le lienB. L'éditeur colle un lien OpenStreetMap
Pour l'éditeuril écrit l'adresse, rien d'autreil ouvre openstreetmap.org, trouve le lieu, clique « Partager », copie le lien, le colle
Codeune ligne dans le gabarit : 'https://www.openstreetmap.org/directions?to=' . rawurlencode( $adresse )aucun : esc_url( get_field( 'lien_carte' ) )
APIaucune de notre côté : c'est OpenStreetMap qui cherche l'adresse quand le visiteur cliqueaucune
Précisiondépend de l'adresse : un carrefour (« Coin Vander Bruggen et Scheutveld »), un point dans le marais ou une adresse mal écrite peuvent ne pas être trouvésexacte : l'éditeur choisit le point lui-même, même sans adresse
Risqueitinéraire vide si l'adresse n'est pas reconnuelien d'un autre site (Google) collé par erreur ; à dire dans l'instruction du champ

Une API de géolocalisation appelée par notre site (Nominatim, qui transforme une adresse en coordonnées) n'est pas utile : elle demanderait du code à l'enregistrement (functions.php), le respect de ses règles d'usage (une requête par seconde, identification), pour un gain nul par rapport à A, puisque le lien de A fait faire la même recherche par OpenStreetMap.

Variante : les deux champs. L'adresse (toujours, elle s'affiche) et un lien OpenStreetMap facultatif ; le gabarit prend le lien s'il est rempli, sinon il construit le lien depuis l'adresse. Le cas courant reste « je tape l'adresse », le cas difficile (carrefour, marais) a sa solution. Trois lignes de gabarit, un champ URL de plus.

Recommandation provisoire, décision d'Emmanuel en attente : la variante à deux champs. Code en réserve passé de Google Maps à directions?to= (option A) le 01/10. Trois choix restent à faire en une fois (section 14, point 19) : champ lieu existant ou nouveau champ adresse ; lien collé facultatif ou non ; directions?to= ou search?query= (celui du prototype). La section 9 et le code en réserve s'aligneront sur la décision.

6. Les deux fiches : WordPress choisit par le type

Pour un contenu seul, WordPress cherche un gabarit dans cet ordre : single-<type>-<slug>.php, single-<type>.php, single.php, singular.php, index.php (fichier wp-includes/template.php, fonction get_single_template). Il suffit donc de nommer les fichiers par le type : single-evenement.php sert tous les événements (/evenements/<slug>/), single-thematique.php toutes les thématiques (/vision-alternative/<slug>/). Aucun aiguillage, aucun champ : chaque contenu renvoie vers sa fiche parce que the_permalink() mène à son adresse et que WordPress ouvre le gabarit de son type.

Démonstration en classe (deux minutes) : créer single-evenement-demo.php avec une phrase, ouvrir un événement, le renommer single-evenement.php, ouvrir une thématique qui tombe sur index.php. Puis supprimer le fichier de démonstration.

7. La frise dans WordPress

La page page-histoire-contexte.php fait deux passes, comme le prototype.

Passe 1 : la liste. Le tableau $evenements que fournissait data/evenements.php est reconstruit depuis WordPress, avec les mêmes clés (fichier, rubrique, numero, titre, date) plus type et post (et chapitre pour une ouverture). Pour chaque rubrique dans l'ordre de l'admin : une entrée « ouverture », puis les événements du chapitre dont date_tri <= aujourd'hui, triés par date_tri. À la fin, la clôture 39. Le nom du fichier d'un événement vient de marais_id_bloc() : 21-naissance-de-l-alliance si ce fichier existe, sinon le slug seul.

Passe 2 : la boucle. La même qu'aujourd'hui. Une ouverture charge ouverture.php ; un événement charge le premier fichier qui existe parmi son fichier de bloc, generique-<disposition>.php et generique.php ; la clôture charge le bloc 39. Bande de position et sous-menu des chapitres sont générés depuis la même liste. Les ouvertures gardent les ids et classes du prototype (evt-01-rubrique-contexte, evt--01-rubrique-contexte, ... evt-34-rubrique-aujourdhui) : site.css (section 4) et la bande de position les attendent. frise.js ne change pas d'une ligne, à quatre conditions (plus bas).

<?php
/* page-histoire-contexte.php : la frise. Même boucle que le prototype, la liste vient de WordPress. */
$aujourdhui = current_time( 'Ymd' );
$rubriques  = get_terms( array( 'taxonomy' => 'rubrique', 'hide_empty' => false ) );
if ( is_wp_error( $rubriques ) ) {
    $rubriques = array();
}

// Les ids des ouvertures sont ceux du prototype, que site.css (section 4) et la bande de position attendent.
// Une table fixe : un numéro calculé changerait dès qu'un événement s'ajoute dans un chapitre précédent.
$ids_ouvertures = array(
    'contexte'     => '01-rubrique-contexte',
    'histoire'     => '03-rubrique-histoire',
    'contestation' => '14-rubrique-contestation',
    'quartier'     => '27-rubrique-quartier',
    'aujourdhui'   => '34-rubrique-aujourdhui',
);

// Passe 1 : la liste des blocs, dans l'ordre de la frise, et celle des chapitres pour la bande
$evenements = array();
$ouvertures = array();
$chapitre   = 0;
foreach ( $rubriques as $rubrique ) {
    $chapitre++;
    $fichier = isset( $ids_ouvertures[ $rubrique->slug ] ) ? $ids_ouvertures[ $rubrique->slug ] : 'rubrique-' . $rubrique->slug;
    $evenements[] = array( 'type' => 'ouverture', 'fichier' => $fichier, 'rubrique' => $rubrique->slug,
        'numero' => substr( $fichier, 0, 2 ), 'chapitre' => sprintf( '%02d', $chapitre ),
        'titre' => $rubrique->name, 'date' => '', 'post' => null );
    // Libellé court de la bande : la description du terme si elle est remplie (« Quartier »), sinon son nom (section 14, point 21)
    $ouvertures[] = array( 'fichier' => $fichier, 'rubrique' => $rubrique->slug,
        'libelle_court' => '' !== $rubrique->description ? $rubrique->description : $rubrique->name );
    $posts = get_posts( array(
        'post_type'   => 'evenement',
        'numberposts' => -1,
        'tax_query'   => array( array( 'taxonomy' => 'rubrique', 'field' => 'term_id', 'terms' => $rubrique->term_id ) ),
        'meta_query'  => array( 'date_clause' => array( 'key' => 'date_tri', 'value' => $aujourdhui, 'compare' => '<=', 'type' => 'NUMERIC' ) ),
        'orderby'     => 'date_clause',
        'order'       => 'ASC',
    ) );
    foreach ( $posts as $evt ) {
        $evenements[] = array( 'type' => 'evenement', 'fichier' => marais_id_bloc( $evt->post_name ), 'rubrique' => $rubrique->slug,
            'numero' => '', 'titre' => $evt->post_title, 'date' => (string) get_field( 'date_affichee', $evt->ID ), 'post' => $evt );
    }
}
// La clôture « Aujourd'hui : la lutte continue » : un bloc fixe après le dernier chapitre
$evenements[] = array( 'type' => 'cloture', 'fichier' => '39-la-lutte-continue', 'rubrique' => 'aujourdhui',
    'numero' => '', 'titre' => 'Aujourd’hui : la lutte continue', 'date' => 'Aujourd’hui', 'post' => null );

// Les scripts de la frise (la feuille, site.css, est chargée pour tout le site par functions.php)
wp_enqueue_script( 'gsap', 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/gsap.min.js', array(), '3.13.0', true );
wp_enqueue_script( 'gsap-scrolltrigger', 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/ScrollTrigger.min.js', array( 'gsap' ), '3.13.0', true );
wp_enqueue_script( 'marais-frise', get_theme_file_uri( 'assets/js/frise.js' ), array( 'gsap-scrolltrigger' ), marais_version( 'assets/js/frise.js' ), true );

// L'empreinte des deux fichiers de GSAP, publiée par cdnjs (la même que le prototype, histoire-contexte.php) :
// un fichier modifié sur le CDN est refusé par le navigateur, et la frise passe en défilement simple.
// Inutile si GSAP est copié dans le thème (section 14, point 13).
add_filter( 'script_loader_tag', function ( $balise, $handle ) {
    $empreintes = array(
        'gsap'               => 'sha512-NcZdtrT77bJr4STcmsGAESr06BYGE8woZdSdEgqnpyqac7sugNO+Tr4bGwGF3MsnEkGKhU2KL2xh6Ec+BqsaHA==',
        'gsap-scrolltrigger' => 'sha512-P2IDYZfqSwjcSjX0BKeNhwRUH8zRPGlgcWl5n6gBLzdi4Y5/0O4zaXrtO4K9TZK6Hn1BenYpKowuCavNandERg==',
    );
    if ( isset( $empreintes[ $handle ] ) ) {
        $balise = str_replace( ' src=', ' integrity="' . $empreintes[ $handle ] . '" crossorigin="anonymous" referrerpolicy="no-referrer" src=', $balise );
    }
    return $balise;
}, 10, 2 );

get_header();
?>
<h1 class="visuellement-cache"><?php echo esc_html( get_the_title() ); ?></h1>
<main id="contenu" class="frise">
    <div class="frise__piste">
<?php /* Passe 2 : l'ouverture, le fichier de l'étudiant (sinon le générique), ou la clôture */ ?>
<?php foreach ( $evenements as $evenement ) : ?>
<?php if ( 'ouverture' === $evenement['type'] ) : ?>
<?php get_template_part( 'template-parts/timeline/ouverture', null, array( 'evenement' => $evenement, 'evenements' => $evenements ) ); ?>
<?php elseif ( 'cloture' === $evenement['type'] ) : ?>
<?php get_template_part( 'template-parts/timeline/' . $evenement['fichier'] ); ?>
<?php else : ?>
<?php $post = $evenement['post']; setup_postdata( $post ); ?>
<?php locate_template( array(
    'template-parts/timeline/' . $evenement['fichier'] . '.php',
    'template-parts/timeline/generique-' . (string) get_field( 'disposition' ) . '.php',
    'template-parts/timeline/generique.php',
), true, false, array( 'evenement' => $evenement ) ); ?>
<?php endif; ?>
<?php endforeach; ?>
<?php wp_reset_postdata(); ?>
<?php get_template_part( 'template-parts/pied-frise' ); ?>
    </div>
</main>
<?php /* La bande de position : le HTML exact du prototype (histoire-contexte.php). Le lien vise l'id de l'ouverture
         du chapitre, le numéro sert à la barre des chapitres sur mobile, le curseur est APRÈS la bande. */ ?>
<nav class="position position--proportionnelle" aria-label="Position dans la frise">
<?php foreach ( $ouvertures as $i => $ouverture ) : ?>
    <a href="#evt-<?php echo esc_attr( $ouverture['fichier'] ); ?>" data-rubrique="<?php echo esc_attr( $ouverture['rubrique'] ); ?>"<?php echo 0 === $i ? ' class="is-active"' : ''; ?>><span class="position__numero" aria-hidden="true"><?php echo esc_html( sprintf( '%02d', $i + 1 ) ); ?></span><?php echo esc_html( $ouverture['libelle_court'] ); ?></a>
<?php endforeach; ?>
</nav>
<span class="position__curseur" aria-hidden="true"></span>
<?php get_footer( 'nu' ); ?>

Ce que ça demande, et ce que ça laisse tel quel :

8. functions.php : huit blocs, et ce qui n'y est pas

<?php
/*
 * Thème Marais Biestebroeck : le minimum. Le modèle de contenu vit dans ACF (admin), pas ici.
 * Version du 07/10 au soir : celle du plan, section 8 (six blocs), plus les blocs 7 et 8 ajoutés après la revue
 * du prototype (tailles d'images, teinte des photos à l'envoi). Le bloc 6 (aperçus de partage) attend la section 17.
 */

// 1. Réglages du thème (aucun réglage de l'admin ne les remplace)
function marais_reglages() {
    add_theme_support( 'title-tag' );
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'html5', array( 'script', 'style' ) );
}
add_action( 'after_setup_theme', 'marais_reglages' );

// 2. Les textes tels qu'ils sont écrits : sans cela, WordPress change le « - » des titres de page en tiret
//    demi-cadratin et réécrit les guillemets et apostrophes des textes saisis
add_filter( 'run_wptexturize', '__return_false' );

// 3. Numéro de version d'un fichier du thème : sa date de modification (le navigateur recharge une feuille modifiée)
function marais_version( $fichier ) {
    $chemin = get_theme_file_path( $fichier );
    return file_exists( $chemin ) ? (string) filemtime( $chemin ) : '1';
}

// 4. Le socle de toutes les pages : la feuille unique (minifiée en production seulement) et nav.js
function marais_socle() {
    $minifiee = 'production' === wp_get_environment_type() && file_exists( get_theme_file_path( 'assets/css/site.min.css' ) );
    $feuille  = $minifiee ? 'assets/css/site.min.css' : 'assets/css/site.css';
    wp_enqueue_style( 'marais', get_theme_file_uri( $feuille ), array(), marais_version( $feuille ) );
    wp_enqueue_script( 'marais-nav', get_theme_file_uri( 'assets/js/nav.js' ), array(), marais_version( 'assets/js/nav.js' ), true );
}
add_action( 'wp_enqueue_scripts', 'marais_socle' );

// 5. Le nom du fichier de bloc d'un événement : son slug, « naissance-de-l-alliance » (sans numéro depuis le 08/10) ;
//    l'ancienne forme numérotée « 21-naissance-de-l-alliance » reste acceptée
function marais_id_bloc( $slug ) {
    $dossier = get_theme_file_path( 'template-parts/timeline/' );
    if ( file_exists( $dossier . $slug . '.php' ) ) {
        return $slug;
    }
    $trouves = glob( $dossier . '[0-9][0-9]-' . $slug . '.php' );
    if ( is_array( $trouves ) && count( $trouves ) > 0 ) {
        return basename( $trouves[0], '.php' );
    }
    return $slug;
}

// 6. Les aperçus de partage (Open Graph) : plan, section 17 (à écrire)

// 7. Les tailles d'images du site (revue du 07/10, I9) : chaque image servie à sa taille d'affichage, comme le prototype.
//    Les gabarits les demandent par wp_get_attachment_image( $id, 'carte' ) : WordPress écrit srcset, width, height
//    et loading="lazy". Une taille ajoutée ne s'applique qu'aux images envoyées ensuite : pour les autres,
//    wp --path=wordpress media regenerate --yes
function marais_tailles_images() {
    add_image_size( 'vignette', 450, 300, true ); // bandeau de l'accueil (200 x 150 à l'écran, le double)
    add_image_size( 'carte', 640, 0 );            // cartes de Vision alternative (250 px au plus à l'écran)
    add_image_size( 'moment', 960, 0 );           // cartes des moments clés (479 px à l'écran)
    add_image_size( 'partage', 1200, 630, true ); // aperçus de partage (plan, section 17)
}
add_action( 'after_setup_theme', 'marais_tailles_images' );

// 8. La teinte des photos à l'envoi (plan, section 14, point 12, réglé le 07/10) : une copie jaune et une copie brune
//    de chaque photo envoyée, rangées dans uploads/marais-teintes/ ; marais_image_teinte_url( $id, 'brun' ) en donne l'adresse
require_once get_theme_file_path( 'inc/teinte-envoi.php' );

Pourquoi chaque bloc : sans post-thumbnails, la boîte « Image mise en avant » n'apparaît sur aucun type malgré le réglage du type (WordPress exige les deux) ; sans title-tag, aucun <title>. run_wptexturize est désactivé parce que le projet interdit le tiret demi-cadratin, que WordPress met dans chaque titre de page. La version par date de fichier évite le piège classique de la CSS en cache après une modification (le prototype fait la même chose avec site.css?v=). Une seule feuille pour tout le site (décision du 06/10) : aucun gabarit n'en charge, chaque page a sa section dans site.css. site.min.css est produite au déploiement, dans la copie qui part en ligne (npx lightningcss-cli --minify assets/css/site.css -o assets/css/site.min.css : le paquet lightningcss seul n'a pas de commande), n'est jamais modifiée à la main ni versionnée (.gitignore) ; sans elle, le thème sert site.css. La condition production protège les postes : une minification lancée par erreur en local ne masquerait pas les modifications de site.css. WordPress vaut production par défaut : le wp-config.php local et celui du paquet des 13 postes déclarent define( 'WP_ENVIRONMENT_TYPE', 'local' ); (absent aujourd'hui du poste d'Emmanuel). Le cinquième bloc permet de garder les noms NN-slug.php et les ids evt-NN-slug sans renommer 34 PHP ; il accepte aussi slug.php, donc un renommage plus tard ne touche pas la boucle.

Les blocs 7 et 8 s'ajoutent le 07/10 (le même code est dans docs/en-reserve/wordpress-gabarits/functions.php, et la teinte dans teinte-envoi.php, à poser dans inc/ du thème) :

Ce qui n'y est pas, et où ça vit : types, taxonomies, champs (ACF gratuit, versionnés par acf-json/) ; coordonnées (champs de la page Contact) ; ordre des rubriques et des thématiques (Simple Custom Post Order) ; page d'accueil statique et permaliens (Réglages) ; tailles d'images de WordPress (Réglages > Médias ; celles du thème : bloc 7 ci-dessus, et la taille « partage » de la section 17) ; éditeur classique (extension, si retenue) ; formulaire de contact (extension, phase suivante) ; menus (les six entrées sont figées par la maquette dans header.php) ; aucun pre_get_posts. Seule exception à « aucun traitement à l'enregistrement » : l'image de partage composée des fiches d'événements (section 17), si elle est retenue. Deux réglages restent à décider : masquer la barre d'administration sur le site (add_filter( 'show_admin_bar', '__return_false' ), section 14, point 20), et retirer les styles que WordPress ajoute (bibliothèque de blocs, styles globaux, emoji) si les captures avant / après de la section 19 montrent qu'ils changent le rendu. Tout ajout futur se justifie ici, sinon il va dans un gabarit.

9. La page Alliance, l'accueil, la presse : les requêtes

Page Alliance, deux requêtes sur le même squelette. À venir : coché Alliance, et la date ou la date de fin n'est pas passée, du plus proche au plus lointain.

$aujourdhui = current_time( 'Ymd' ); // fuseau du site (Europe/Brussels), jamais date() qui est en UTC
$communs    = array( 'post_type' => 'evenement', 'posts_per_page' => -1, 'no_found_rows' => true, 'meta_key' => 'date_tri', 'orderby' => 'meta_value_num' );
$a_venir = new WP_Query( $communs + array(
    'order'      => 'ASC',
    'meta_query' => array(
        'relation' => 'AND',
        array( 'key' => 'action_alliance', 'value' => '1' ),
        array(
            'relation' => 'OR',
            array( 'key' => 'date_tri', 'value' => $aujourdhui, 'compare' => '>=', 'type' => 'NUMERIC' ),
            array( 'key' => 'date_fin', 'value' => $aujourdhui, 'compare' => '>=', 'type' => 'NUMERIC' ),
        ),
    ),
) );

Moments clés (décision du 06/10) : les événements de la rubrique Aujourd'hui, passés, dans l'ordre de la frise, cochés ou non.

$moments = new WP_Query( $communs + array(
    'order'      => 'ASC', // l'ordre de la frise, comme le prototype (section 14, point 18)
    'tax_query'  => array( array( 'taxonomy' => 'rubrique', 'field' => 'slug', 'terms' => 'aujourdhui' ) ),
    'meta_query' => array( array( 'key' => 'date_tri', 'value' => $aujourdhui, 'compare' => '<=', 'type' => 'NUMERIC' ) ),
) );

Carte d'un moment clé : image mise en avant (sinon image_1 du groupe images), en brun : la copie teintée à l'envoi, marais_image_teinte_url( get_post_thumbnail_id(), 'brun' ) (section 8, bloc 8), titre, date_affichee seule (pas de date si elle est vide), première phrase de resume coupée à 160 caractères, the_permalink(). C'est ce que fait le prototype (template-parts/moments-cles.php) en lisant les fichiers des blocs : il y prend la première img.image. Pour garder ces photos, l'import pose l'image mise en avant des événements 35 à 38 depuis la première img.image de leur bloc (section 12, point 4) : la photo d'origine en couleurs, que WordPress teint à l'envoi (une photo déjà teintée en jaune ne donnerait qu'un brun délavé) ; le 38, non coché, n'en aurait sinon aucune. Si l'option C du point 17 est retenue, la carte du bloc 39 s'écrit après la boucle. Les cartes d'actions reprennent le HTML de alliance.php : image par the_post_thumbnail(), titre par get_the_title(), lien par the_permalink(), jour et date par wp_date( 'l', ... ) et wp_date( 'j F', ... ) avec ucfirst() (WordPress écrit « dimanche » en minuscule), horaires tels quels, lieu lu brut par get_field( 'lieu', false, false ) puis nl2br( esc_html() ) (sans les deux false, ACF renvoie déjà des <br> qu'esc_html() afficherait en toutes lettres), Itinéraire construit sur adresse avec rawurlencode() (ou le lien collé : selon le point 19 de la section 14). Sans action à venir : « Aucune action programmée pour le moment ».

Fiche d'un événement : en tête du gabarit, $fin = date_fin sinon date_tri et $a_venir = ( fin >= aujourd'hui ) ; deux chaînes AAAAMMJJ de même longueur se comparent comme des nombres. La classe du panneau, le format de la date, les horaires et le libellé de la section en dépendent.

Bandeau de l'accueil (trois cartes) : les prochaines actions dans l'ordre, complétées par les dernières passées s'il en manque, par la même règle « fin >= aujourd'hui ». Rendu deux fois comme dans index.php (la seconde série aria-hidden). Point 8 de la section 14 pour les deux autres options.

Revue de presse : tous les contenus du type presse, triés par date_publication décroissante ; chaque carte est un lien vers url, badge media, titre, mention, date par wp_date( 'j F Y' ). Un article sans date disparaît de la liste (la jointure l'exige), d'où le champ obligatoire.

Le code complet de ces gabarits (page Alliance, fiche, pied des fiches, bandeau, presse, frise, ouverture, sous-menu, générique, functions.php) est linté et rangé dans docs/en-reserve/wordpress-gabarits/, avec la liste des retouches restantes, pour être posé dans le thème quand Emmanuel aura tranché. Il date du 01/10 et est à reprendre avant usage : il charge encore les anciennes feuilles (base, footer, composants, timeline/*.css, une par page) ; sa bande de position est l'ancienne (curseur dans la bande, sans position--proportionnelle ni numéros) ; ouverture.php écrit evt-rubrique-<slug> au lieu des ids du prototype ; page-alliance.php choisit les moments clés par la case Alliance ; page-presse.php part de l'ancienne page (la Presse du prototype est refaite le 07/10 : en-tête highlight-title dans main.presse, mêmes classes de cartes) ; les deux fiches appellent get_footer() et single-thematique.php ajoute un pied que la fiche n'a plus ; single-evenement.php n'envoie pas la classe page-single (get_header( null, array( 'body' => 'page-single' ) )) ; page-histoire-contexte.php appelle get_footer( 'frise' ) (un footer-frise.php prévu dans son LISEZMOI.md, devenu footer-nu.php : passer à get_footer( 'nu' )) ; son functions.php n'a que quatre blocs (manquent run_wptexturize et les aperçus de partage).

10. Ce qui ne sera pas encore modifiable dans l'admin au départ

Le modèle de contenu l'est entièrement. Dans le code, pour l'instant :

ÉlémentOùDevient modifiable
les six entrées de navigationheader.phpsi on passe à wp_nav_menu avec un walker (phase suivante, pas prioritaire : la maquette les fige)
titres surlignés des hero (Alliance, Presse, Contact) et titres de sectionsgabaritschamps ACF sur les pages, phase suivante
les trois sections de l'accueil (textes du SPIP)front-page.phples groupes ACF marais, alliance et ecologies existent depuis le 01/10 (section 5) ; front-page.php les lira en phase suivante
textes, dates et images des 34 blocsHTML des blocsbloc par bloc, à la demande (section 7)
la clôture 39bloc 39reste un bloc fixe
les quatorze traits de la constellationpage-vision-alternative.phptableau PHP ; un champ relation si le client doit les changer
libellés « Infos pratiques » et « Ce qui s'est passé »single-evenement.phplibellés du gabarit
formulaire de contactHTML sans traitementextension de formulaire, phase suivante

11. La première séance WordPress : déroulé proposé

Écrit pour le 5/10, la séance n'a pas eu lieu ce jour-là (le 5/10 a porté sur le prototype). Le déroulé reste valable pour la première séance WordPress, date à fixer. Mises à jour du 06/10 : aucun étudiant n'a plus de feuille de style à lui (tout est dans site.css) ; « Infos du site » est remplacé par la page Contact (groupe Coordonnées) ; le pied de page est décidé ; dans l'exercice de 15h55, « Naissance de l'Alliance » est en rubrique Contestation et n'irait pas dans les moments clés : l'exercice se fait sur la « Balade nature » (rubrique Aujourd'hui, datée d'hier : elle entre dans la frise et dans les moments clés).

HeureDuréeQuoiQui
13h0020 minLa frise tourne déjà dans WordPress. Ouvrir /wordpress/histoire-contexte/ : les cinq ouvertures, les 33 blocs, la clôture, les deux Tritoni-e rendus par le générique. Puis l'admin : Événements (ouvrir le bloc 21), Rubriques, Presse, Ressources, Thématiques, Pages, la page Contact et son groupe Coordonnées ; ACF > Groupes de champs, d'où viennent ces écrans. Annoncer les décisions prises (section 14), projeter le tableau qui fait quoi. Dire à Romain ce qu'il fait aujourd'huiEmmanuel projette
13h2030 minInstaller le WordPress du Marais sur chaque poste (section 13) : une étape à la fois au tableau, validation par un voisin avant la suivante. Vérifiable : la frise s'ouvre avec ses images, la page Alliance affiche ses deux listeschaque étudiant, Emmanuel circule
13h5030 minAnatomie du thème, hiérarchie en direct, une page convertie. Dix minutes : style.css, index.php, functions.php (huit blocs), header.php, footer.php, et la règle page-<slug>.php, single-<type>.php, front-page.php. Deux minutes : la démonstration de la section 6. Vingt minutes : ressources.php devient page-ressources.php par les cinq gestes (section 4)Emmanuel au clavier, Nuria à côté, la classe suit dans VS Code
14h2080 minChaque groupe convertit sa page, l'équipe admin saisit les contenus (tableau ci-dessous). Toute question qui touche le modèle se règle dans ACF sur le poste d'Emmanuel, pas dans le code. Contrôle à 15h40 : chaque page s'affiche avec la navigation, et le pied de page pour celles qui en ont un (accueil, Alliance, Presse, Ressources ; Contact sur mobile seulement ; Crédits n'en a plus depuis le 07/10), sans Warning PHPcinq groupes et l'équipe admin, Emmanuel circule
15h4015 minPause et point d'étape : qui a sa page qui s'affiche, qui bloque ; les postes dont l'installation a échoué la refonttous
15h5535 minLa boucle de la frise et la logique Alliance. Quinze minutes de lecture guidée de page-histoire-contexte.php en partant du prototype (« $evenements venait de data/evenements.php, il vient de WordPress ; la boucle est la même »). Cinq minutes : les deux requêtes de la page Alliance (à venir = case Alliance cochée et date de fin non passée ; moments clés = rubrique Aujourd'hui et date passée ; toutes deux du plus ancien au plus récent). Dix minutes, chacun sur son poste : créer « Balade nature » dimanche prochain, cochée, rubrique Aujourd'hui (en haut de la page Alliance, absente de la frise) ; la dater d'hier (elle quitte le haut, entre dans les moments clés et dans la frise par le générique, avant la clôture) ; ouvrir sa fiche, le bouton Fermer ramène à la page Alliance. Cinq minutes : chacun vérifie son bloc dans la frise WordPress, images et « Lire la suite »Emmanuel projette, Romain montre l'ouverture, puis chaque étudiant
16h3020 minTour des pages : accueil, Alliance, Presse, Ressources, Contact, Vision alternative, une fiche de thème, une fiche d'événement, le pied de page. Critère : la page lit la base (créer un contenu dans l'admin, le voir apparaître), aucun Warning, aucun chemin assets/ relatif. Tableau de ce qui reste (section 15)un étudiant par groupe projette
16h5010 minCommit, push, décisions notées. Chaque groupe commite ses fichiers du thème (jamais functions.php ni header.php sans l'avoir dit) et pousse sa branche ; Emmanuel fusionne dans main après la séance (fichiers disjoints). Les décisions du jour sont dictées et datéeschaque groupe, Emmanuel

Qui fait quoi de 14h20 à 15h40 :

GroupeÉtudiantFichierPoint de départ
fou du busRayan Bermudezfront-page.phpson index.php ; squelette avec la requête du bandeau fourni
fou du busElliotpage-alliance.php, puis relecture de header.php (entrée active sur chaque page)son alliance.php ; squelette avec les deux requêtes fourni
groupe CNuriapage-ressources.php (finition après l'étape de 13h50)sa page convertie en direct
groupe CMariapage-presse.php (sa CSS est déjà dans site.css, section 10)son presse.php
groupe CMaelipage-contact.phpson contact.php
groupe DRayan Thamerssingle-thematique.php (CSS : site.css, section 8)sa single-page-alternative.php ; six sections fixes, gabarit en réserve (docs/en-reserve/wordpress-gabarits/single-thematique.php)
D visionVeronikapage-vision-alternative.php, puis single-evenement.php et pied-fiche.phpses vision-alternative.php et single.php ; code d'en-tête fourni
Dariafooter.php et pied-frise.phpson footer.php ; coordonnées lues sur la page Contact
Romainrelecture de ouverture.php et menu-chapitres.php avec Emmanuel, test du sous-menu, puis generique-galerie.phpses ouvertures (remplacées le 29/09 par la frise refaite : à lui dire avant la séance)
équipe adminImanemédiathèque : photos des pages verticales en WebP, textes alternatifs, crédits, images mises en avant des événements cochés, images du bloc 21 (quatre au plus, groupe images)le prototype (alt repris des <img>)
équipe adminMoiseles 13 références de pressetable préparée depuis le SPIP
équipe adminNicolasles 8 thématiques (textes du SPIP dans les sections 1 à 6, un intertitre du SPIP = une section ; image du menu, positions)docs/spip-articles.json, vision-alternative.php
équipe adminRyan Torresles 3 ressources réelles, les types, les coordonnées (page Contact), le texte de la page Crédits, relecture des événementsSPIP et prototype

L'équipe admin ne touche pas au PHP : page.php et page-credits.php sont préparés avant la séance. Quatre étudiants sans PHP ce jour-là est un choix à confirmer (point 9 de la section 14). Le contrôle de chaque page se fait aux deux formats, ordinateur et mobile (402 de large).

12. Avant la séance WordPress : ce qu'il faut préparer

État au 06/10 : rien n'est fait, sauf le point 8 (fait) et une partie du point 7. Indispensable pour que la séance démarre à 13h :

  1. Emmanuel tranche les points de la section 14 ; sans le point 1 (les 13 postes) la séance ne peut pas démarrer, sans les points 2, 3 et 9 rien ne peut être préparé.
  2. Admin WordPress (une demi-heure) : renommer la page Activités en Alliance (slug alliance), créer la page Presse, mettre Agenda, À propos, Agir et Cookies en brouillon ; créer le type presse et son groupe de champs ; corriger les types de ressource ; ajouter adresse sur l'événement (selon le point 19) et position_x, position_y, position_mx, position_my sur la thématique (telephone et les six sections existent depuis le 01/10) ; réécrire les libellés et instructions (section 5) ; régler ressource en non interrogeable et sans permalien ; vider les règles de réécriture ; installer Classic Editor si retenu.
  3. Le socle du thème : les fichiers des étapes 4 et 5 de la section 19 (et les gabarits clés du point 5 ci-dessous si le point 9 retient A), les assets copiés, .home renommé .accueil dans le prototype puis copié, les règles du générique dans site.css (celles des ouvertures y sont déjà), acf-json/ créé, le script de portage lancé sur les 34 blocs, la ligne 46 du .gitignore (# !wordpress/wp-content/themes/<theme-du-projet>/, un modèle) remplacée par !wordpress/wp-content/themes/marais/, et la ligne wordpress/ retirée de .git/info/exclude du poste d'Emmanuel (elle garderait le thème ignoré malgré le .gitignore, vérifié le 07/10 dans un dépôt jetable), le thème activé, chaque objet ACF ré-enregistré une fois pour remplir acf-json/.
  4. L'import des contenus par un script lancé avec wp eval-file : les 33 événements depuis docs/blocs-frise.json et docs/blocs-contenus.json (slug imposé, rubrique, date_tri, date affichée, texte court repris de la carte du bloc, disposition, case Alliance selon le point 8, image mise en avant des événements 35 à 38 = la photo d'origine de la première img.image de leur bloc (champ source de docs/photos-frise.json, dans _Preparation/ sur le poste du professeur), importée en médiathèque par wp media import une fois la teinte à l'envoi posée (section 8, bloc 8), puis set_post_thumbnail()) ; les deux Tritoni-e, cochés, rubrique Aujourd'hui, en brouillon jusqu'au contrôle ; les 13 références de presse depuis la table spip_syndic de la base marais_spip ; les 8 thématiques (titre, slug, ordre, positions). Corriger d'abord date_tri du bloc 35 (point 11 : avec sa date actuelle, le tri donne 36, 37, 38 puis 35). Contrôle : l'ordre produit par WordPress reproduit 02 à 38 ; la frise au pixel contre le prototype, Tritoni-e encore en brouillon (ils n'existent pas dans le prototype et allongent le chapitre 05, donc toutes les parts de la bande) ; puis publication des Tritoni-e et contrôle visuel du générique et de la bande ; aucune ligne dans debug.log.
  5. Les gabarits clés (page Alliance, fiche d'événement, pied des fiches, pied de page, pied de frise) : posés complets avant la séance si Emmanuel retient le point 9, sinon livrés en squelettes ; dans les deux cas, repris du code en réserve mis à jour (section 9).
  6. Le paquet pour les 13 postes (section 13), testé sur un Mac avec MAMP et un PC avec XAMPP.
  7. Le dépôt de la classe : wordpress/wp-content/themes/marais ajouté à livraison/liste.txt ; les règles wordpress/* du .gitignore du projet copiées dans livraison/racine/.gitignore (sinon un git add . d'étudiant pousse WordPress, ACF Pro et wp-config.php sur le dépôt de la classe) ; toutes les pages du prototype livrées (fait : liste.txt livre toutes les pages depuis le 05/10, et site.css depuis le 06/10) ; livraison sur accord ; main fusionnée dans les branches des étudiants, ou fusion par chacun à 13h20.
  8. Le prototype en ligne : fait (les trois corrections sont en ligne depuis le 06/10).

Souhaitable :

13. Les 13 postes : comment chacun obtient le WordPress

Rien n'est écrit sur ce point et il conditionne tout : livraison/liste.txt exclut wordpress/, et les étudiants travaillent sous MAMP (port 8888, base localhost:8889, root / root) ou XAMPP (port 80, base localhost, root sans mot de passe).

Option A, recommandée : un paquet hors git et une base exportée. Un wordpress-marais.zip (core 7.1.2, ACF gratuit, Simple Custom Post Order, Classic Editor si retenu, wp-config.php dont WP_HOME et WP_SITEURL se calculent sur $_SERVER['HTTP_HOST'] avec un repli pour la ligne de commande, et trois lignes d'accès à la base commentées pour MAMP et XAMPP) à dézipper à la racine du clone, sans écraser themes/marais/ qui arrive par git ; un .htaccess dont RewriteBase vaut /marais/wordpress/ (celui du poste d'Emmanuel vaut /wordpress/ et donnerait des 404) ; un marais_wp.sql exporté après création d'un compte etudiant (rôle à fixer) et suppression du compte d'Emmanuel avec réattribution de ses contenus. Installation en trente minutes à l'étape 2. Depuis le passage à ACF gratuit (01/10), aucune condition de licence : toutes les extensions viennent de wordpress.org.

Option B, en repli : un seul WordPress sur le sous-domaine, le thème déployé par git puis FTP, chacun regarde en ligne. Simple à installer, mais un gabarit cassé par un groupe casse la page de tous, et personne ne travaille hors ligne.

Option C, à éviter : chaque étudiant installe WordPress et ACF depuis zéro. Une heure de séance perdue, treize configurations différentes.

Recommandation : A, testée avant la séance sur un Mac et un PC ; B pour un poste qui échoue.

ACF Pro est-il nécessaire ? (question d'Emmanuel, 01/10 après-midi)

Récit du 01/10, gardé pour la séance (cas d'école, plus haut) ; les décisions sont plus bas dans cette section et en section 5.

Question : « Pourquoi as-tu besoin d'ACF Pro, à cause du répéteur ? » Réponse : ACF Pro n'est pas indispensable. Il a été utilisé parce qu'Emmanuel l'avait demandé le 24/09 (« Utilise bien le plugin ACF Pro »), pas par nécessité technique. Décidé le soir même : ACF gratuit (plus bas).

Ce que la version gratuite fait déjà. Les types de contenu et les taxonomies créés dans l'admin sont dans le cœur d'ACF, gratuit depuis la 6.1 : dans le plugin installé, includes/post-types/class-acf-post-type.php et class-acf-taxonomy.php sont hors du dossier pro/. Les champs groupe, lien, fichier, image, éditeur, liste, vrai ou faux, date sont gratuits aussi (includes/fields/). Le dossier pro/ ne contient que le répéteur, la galerie, le contenu flexible, le clone (pro/fields/) et les pages d'options (pro/post-types/acf-ui-options-page.php). Relevé par :

ls wordpress/wp-content/plugins/advanced-custom-fields-pro/pro/fields/ wordpress/wp-content/plugins/advanced-custom-fields-pro/pro/post-types/
cd wordpress && wp eval 'foreach (acf_get_field_groups() as $g) foreach (acf_get_fields($g) as $f) if (in_array($f["type"], array("repeater","gallery","flexible_content","clone"))) echo $g["title"], " : ", $f["name"], " (", $f["type"], ")\n";'

Les champs Pro du modèle, et les remplacements envisagés le 01/10 après-midi (retenus : tableau « Avant / Après » plus bas) :

Champ ProOùRemplacement gratuit
répéteur reseauxInfos du sitetrois champs URL fixes (Instagram, Facebook, Mastodon, comme la maquette)
page d'optionsInfos du siteune page « Infos du site » avec ses champs, lue par son ID
répéteur documentsÉvénementdeux champs fichier fixes, ou des liens dans le texte
répéteur creditspage Créditsle texte de la page dans l'éditeur
répéteur sectionsAccueilaucun : l'accueil reste en dur au départ
galerie galerieÉvénement, Thématiquel'image mise en avant, et des images dans le texte ; le bloc générique n'utilise que la première image
répéteur sections (proposé le 01/10, abandonné)Thématiquesix sections fixes, en place depuis le 01/10 (ci-dessous)

Ce que la version gratuite résoudrait : le point 1 de la section 14 (la licence pour 13 postes) disparaît ; ACF gratuit s'installe depuis wordpress.org, par chacun, sans clé. Ce qu'on perdrait : surtout la fiche d'une thématique, ci-dessous.

La fiche d'une thématique, expliquée (explication pas à pas avec Emmanuel, 01/10). Dans le prototype, la page « Eau » de Rayan Thamers (prototype/single-page-alternative.php, lignes 12 à 56) est une liste de quatre sections, et chaque section a un type :

Sa boucle PHP lit ce type et donne à chaque section sa propre mise en page (la classe theme-detail__section--question ou --links). Trois façons de garder ça dans WordPress :

  1. ACF Pro, un répéteur sections. Chaque ligne du répéteur est une section : intitulé, type (texte, question, liens), texte, image, liens. Le client remplit des cases, ajoute ou retire des lignes. La boucle de Rayan reste la même : foreach ( (array) get_field( 'sections' ) as $section ). La mise en page de la maquette est garantie : le client ne peut pas la casser.
  2. ACF gratuit, des sections fixes. Pas de répéteur, mais des champs groupe numérotés : section_1 à section_5 (intitulé, texte, image), plus un champ question et trois champs lien. La boucle devient for ( $i = 1; $i <= 5; $i++ ), on saute une section vide. Même rendu que le prototype, mais un maximum fixe, et cinq groupes affichés même quand la thématique n'en utilise que deux.
  3. ACF gratuit, le texte de la page. Pas de champ du tout : le client écrit dans l'éditeur de WordPress, avec des intertitres, des paragraphes, des images et des liens, et le gabarit affiche the_content(). La CSS met en forme les intertitres, paragraphes et images. Plus simple pour le client, mais rien n'oblige à respecter la maquette : « Question » n'est plus un type de section, juste un intertitre que le client écrit ou non ; une photo est sous le bon texte seulement si le client la place au bon endroit.

Ce que demandent les sept textes du SPIP qui alimenteront ces fiches (docs/spip-articles.json) :

ThématiqueIntertitresImagesLiens
Eau4 (dont « Question » et « Lien pour en savoir plus »)22
Terre134
Air210
Énergie011
Sanctuaire et corridors324
Droit à l'habitat030
Symbolique534

La page se voit en local sur https://maraisbiestebroeck.test/prototype/single-page-alternative.php (ouverte avec Emmanuel le 01/10) : titre « Eau », grande photo, puis les quatre sections séparées par un trait, chaque intitulé en gris au-dessus de son texte. Seule « Eau » a une section « Question ». Cinq sections suffiraient pour la variante 2. Correction de la réponse donnée en séance de discussion : elle disait que la version gratuite transforme les sections en texte libre ; la variante 2 (sections fixes) montre que ce n'est pas la seule possibilité.

Et les bordures ? (question d'Emmanuel, 01/10 fin d'après-midi) Après avoir vu la page « Eau », Emmanuel convient que l'absence de répéteur pose problème et demande : si on met tout dans l'éditeur, perd-on les traits durs entre les sections ? Les traits de la page sont trois bordures de la CSS de Rayan (single-page-alternative.php, CSS lignes 55 à 98) : à gauche et à droite du panneau (.theme-detail__panel), sous le titre (.theme-detail__header), et sous chaque section (.theme-detail__section, border-bottom), qui a aussi ses marges intérieures (64 px au-dessus, 32 px sur les côtés). Les deux premières tiennent au gabarit, pas au contenu : elles restent quoi qu'il arrive. Seuls les traits entre les sections dépendent de la façon dont le contenu est saisi :

Façon de saisirTraits entre sections« Question » et « Liens »Pour le clientCode
1. ACF Pro, répéteuridentiques au prototypeun type par ligne, choisi dans une listedes cases à remplir, nombre librela boucle de Rayan telle quelle
2. ACF gratuit, sections fixes section_1 à section_5identiques au prototypeun champ question et des champs lien dédiésdes cases à remplir, cinq sections au plusla boucle de Rayan, for au lieu de foreach
3. Éditeur, le gabarit découpe le texte à chaque intertitreidentiques : chaque morceau est enveloppé dans la <section> de Rayan« Question » en citation (bouton citation de l'éditeur), liens en listeécrire librement, mais chaque section commence par un intertitre « Titre 2 »six lignes : découper get_the_content() avant chaque <h2> et entourer chaque morceau
4. Éditeur, la CSS dessine un trait au-dessus de chaque intertitrepresque : le trait part de l'intertitre, il faut des marges négatives pour qu'il touche les bords du panneauidem 3idem 3aucun PHP, une dizaine de lignes de CSS
5. Éditeur de blocs, une composition « Section » (bloc Groupe) créée dans l'adminidentiques si la CSS cible le groupeune composition par typeinsérer la composition, la rempliraucun PHP ; exige l'éditeur de blocs (contraire à la recommandation Classic Editor, point 6)

Ce que l'éditeur fait perdre, dans 3, 4 et 5 : la garantie. Un intertitre oublié ou remplacé par du gras, et deux sections n'en font qu'une ; une photo glissée au mauvais endroit change de section. Les formulaires (1 et 2) n'ont pas ce risque.

Recommandation donnée ce jour-là, avant la décision : ACF gratuit, avec la variante 2 pour la fiche d'une thématique. Elle garde la boucle et le rendu de Rayan, et supprime la question de licence.

Décision d'Emmanuel (01/10, fin d'après-midi) et mise en place. « Mettons six sections. Si elle est vide, on n'affiche pas les bords haut et bas. Et on explique dans l'interface WordPress qu'on a fait comme ça pour éviter un plugin payant. » Fait le même jour dans le WordPress local :

Décision d'Emmanuel (01/10, soir) : tout le modèle passe à ACF gratuit. Question posée : « Passe-t-on aussi ces champs à la version gratuite, pour se passer complètement d'ACF Pro ? » Réponse : « oui ». Fait le même jour dans le WordPress local, dans cet ordre :

  1. Sauvegardes : base (wp db export) et structure ACF complète (wp acf json export), dans le dossier de travail de la session.
  2. Vérifier qu'aucune donnée n'est perdue : aucune valeur n'était enregistrée dans les champs retirés (requête sur wp_postmeta et wp_options).
  3. Convertir, ACF Pro encore actif, chaque fonction payante en emplacements fixes gratuits, chacun expliqué dans l'écran d'édition :
Avant (payant)Après (gratuit)Explication donnée au client dans l'écran
page d'options « Infos du site » et son groupegroupe « Coordonnées » sur la page Contact : email_contact, telephone, adresse, reseau_instagram, reseau_facebook, reseau_mastodonmessage : ces coordonnées s'affichent sur la page Contact, dans tous les pieds de page et au bas des fiches, elles se modifient ici seulement ; le menu « Infos du site » était une fonction payante
répéteur reseauxtrois champs URL fixes (les trois réseaux de la maquette)un réseau vide n'apparaît pas
répéteur documents (événement)trois emplacements document_1 à document_3 (intitulé, fichier)« trois au plus » et la raison (version gratuite)
galerie galerie (événement)groupe images, image_1 à image_4 (quatre images au plus, le maximum d'un bloc de la frise)idem
galerie galerie (thématique)retirée : la maquette ne l'utilise pas
répéteur sections (accueil)trois blocs fixes calqués sur la maquette FINALE (28:304) : marais (titre, deux images, deux cartes avec lien), alliance (titre, image, une carte avec lien), ecologies (titre, image, trois cartes libellé, texte, lien), en ongletsmessage : les trois blocs dans l'ordre de la maquette ; la vidéo est dans le thème, le bandeau d'actualités se remplit seul
répéteur credits (page Crédits)supprimé : les crédits s'écrivent dans l'éditeur de la page
  1. Supprimer les groupes « Coordonnées » de la page d'options et « Crédits », et la page d'options elle-même ; contrôle : plus aucun champ répéteur, galerie, contenu flexible ni clone.
  2. Changer d'extension : ACF Pro désactivé (laissé installé, réactivable), ACF gratuit 6.8.10 installé depuis wordpress.org et activé.
  3. Vérifier sous ACF gratuit : les trois types et les deux taxonomies sont enregistrés ; l'ordre des rubriques tient ; écriture et relecture par le circuit ACF de chaque nouveau champ (rubrique, date, case Alliance, document, section d'une thématique, coordonnées sur la page Contact, carte de l'accueil) ; neuf écrans d'administration en 200 sans erreur PHP ; l'ancien menu « Infos du site » n'existe plus ; contenus, valeurs et session de test supprimés ; structure réexportée dans docs/wordpress-acf.json (12 objets).
  4. Gabarits en réserve adaptés : pied-fiche.php lit les coordonnées sur la page Contact (get_page_by_path( 'contact' )), generique.php lit image_1 du groupe images.

Conséquence pour la séance : le point 1 de la section 14 (licence pour 13 postes) n'existe plus ; le paquet d'installation contient ACF gratuit.

14. Points à trancher (Emmanuel)

Règle d'Emmanuel du 08/10, qui prime sur tout ce qui suit : le WordPress est la copie conforme du prototype actuel. Mêmes pages, mêmes noms, rien en plus, rien en moins ; seuls les contenus deviennent dynamiques (type « événement » et champs). Toute évolution passe d'abord par le prototype, puis le thème le recopie. Exemple : les deux fiches du prototype (single.php, single-page-alternative.php) montrent toujours le même contenu ; dans WordPress, chaque fiche affiche celui de son événement ou de sa thématique. Les étudiants ne travaillent plus sur le WordPress (point 1).

  1. Les 13 postes : décidé le 08/10 par Emmanuel : B, et plus encore : les étudiants ne travaillent plus sur le WordPress. Un seul WordPress, construit par Emmanuel (avec la session) d'abord en local sur son poste (https://maraisbiestebroeck.test/wordpress/), puis mis en ligne sur le sous-domaine, sur accord ; ni paquet, ni séance WordPress des étudiants. Les sections 11 (déroulé de la séance), 13 (paquet) et 19 (étapes du paquet et de la séance) sont à réécrire ; les points 9 et 10 deviennent sans objet. Avant la décision : paquet hors git et base exportée (A), WordPress unique sur le sous-domaine (B), installation par chacun (C). Recommandation : A, B en repli. La question de licence est réglée : ACF gratuit depuis le 01/10 (section 13).
  2. La frise exclut-elle le futur, et le bloc 39 : décidé le 08/10 par Emmanuel : B (la frise montre tout, sauf les actions cochées « Action de l'Alliance » encore à venir ; avec le point 17 en C, le 39 est une clôture fixe hors base : sa date 31/12/9999 devient inutile, et 33 événements sont importés). Avant la décision : frise = date_tri <= aujourd'hui et le 39 en clôture fixe, 33 événements importés (A) ; frise = tout sauf les actions cochées à venir et le 39 daté 31/12/9999 (B) ; la frise montre tout, futur compris, décision du 24/09 (C). Recommandation : A.
  3. Nom des fichiers de bloc : décidé le 08/10 par Emmanuel : B (renommer en slug.php, ids evt-slug et sélecteurs .evt--slug de la section 4 de site.css, comme décidé le 24/09 ; à faire dans le prototype avant le portage). Avant la décision : garder NN-slug.php et les ids evt-NN-slug, aucun renommage, le helper accepte les deux formes (A) ; renommer en slug.php : 34 PHP, leurs ids et les sélecteurs de la section 4 de site.css (B, décision du 24/09). Recommandation : A au départ, B possible plus tard sans toucher la boucle.
  4. Les 34 blocs : décidé le 08/10 par Emmanuel (rappel des décisions du 24/09) : les textes viennent du type de contenu « événement » (titre, dates, texte), importé du SPIP pour la plus grande part et des contenus déjà relevés pour le reste (docs/blocs-contenus.json) ; le fichier de bloc ne porte que la disposition. Avant la décision : figés en HTML avec les deux échos posés par script, dynamisés bloc par bloc ensuite (A) ; chaque bloc lit get_field() dès la première séance, un fichier par étudiant (B). Recommandation : A.
  5. Les pages : réglé le 08/10 par la règle d'Emmanuel : exactement les pages du prototype, sous leurs noms (Alliance à /alliance/, Presse à /presse/) ; Agenda, À propos, Agir et Cookies, absentes du prototype, sont supprimées. Avant : Activités renommée Alliance, Presse créée au premier niveau, Agenda, À propos, Agir et Cookies en brouillon (A) ; garder activites et supprimer les quatre (B). Recommandation : A, réversible.
  6. L'éditeur pour le client, attendu depuis le 24/09 : choix de l'agent du 08/10, à valider (sans effet sur le site visible) : l'écran classique partout, par une ligne dans functions.php (D). Avant : extension Classic Editor, champs ACF sous le texte (A) ; éditeur de blocs avec les champs repliés en bas (B) ; show_in_rest décoché dans ACF, écran classique sans extension, pour les types d'ACF seulement (C ; le MCP Adapter 0.5.0 n'expose de toute façon aucun contenu : l'argument « le MCP ne lit plus ces types » de la première version était inexact) ; option oubliée : une ligne dans functions.php, add_filter( 'use_block_editor_for_post_type', '__return_false' ) (D). Recommandation : A.
  7. Le pied de page : réglé le 06/10 : la grille de Daria sur ordinateur, le pied de la frise sur mobile, sur toutes les pages qui en ont un ; la fiche d'un moment clé n'a pas de pied de page, donc celle d'une action à venir non plus (un seul gabarit, single-evenement.php) ; la branche daria n'est pas reprise.
  8. Le bandeau de l'accueil et les blocs cochés : décidé le 08/10 par Emmanuel : bandeau A (automatique : prochaines actions cochées, complétées par les dernières passées ; date_affichee quand elle existe) ; cochés : tous les événements de 21 à 38 (le 39 est hors base, point 17), et les samedis de Tritoni-e. Conséquence : le bouton Fermer suit la règle de la section 6 (page Alliance seulement si l'événement y figure), plus la case. Avant la décision : bandeau = prochaines actions puis dernières passées (A), les trois cochées les plus récentes (B), sélection manuelle par un champ relation (C) ; blocs cochés Alliance = 21, 23, 35, 36, 37 (A, le 24 est un acte du promoteur) ou tous de 21 à 39 (B) ; et 22, 25, 26 ? Recommandation : A et A, liste à valider avec Matthias avant l'import. Depuis le 06/10, cocher ne change plus les moments clés (rubrique Aujourd'hui) : la case sert au bandeau, à la liste « à venir » et au bouton Fermer. Conséquence à trancher : 21 et 23 (rubrique Contestation) ne sont plus sur la page Alliance ; cochés, ils ne servent plus qu'au bandeau, faut-il encore les cocher ?
  9. Qui écrit les gabarits clés avant la séance : sans objet depuis le 08/10 (les étudiants ne travaillent plus sur le WordPress, point 1). Avant : Emmanuel ou la session pose les versions complètes de page-alliance.php, single-evenement.php, pied-fiche.php, footer.php, pied-frise.php, et Elliot, Veronika et Daria relisent, testent les cas limites et finissent (A, la frise, la page Alliance et une fiche tournent à 13h) ; ces fichiers sont écrits par les étudiants en séance à partir de squelettes (B, aucune vérification de ces pages avant la séance). Même question pour les quatre étudiants de l'équipe admin : sans PHP ce jour-là ? Recommandation : A.
  10. Les branches : sans objet depuis le 08/10 pour le WordPress (point 1) ; les branches des étudiants restent celles du prototype. Avant : Emmanuel fusionne main dans les branches des étudiants avant la séance (A) ou chaque étudiant fait git merge origin/main à 13h20, dix minutes de plus (B). Recommandation : A.
  11. Quatre détails de contenu : décidés le 08/10 par Emmanuel : a3 (bloc 35 au 01/03/2025, affiché « Printemps 2025 ») ; b2 (aucun petit titre au-dessus du texte d'une fiche passée, « Infos pratiques » sur les actions à venir) ; c2 (« Stop au saccage » fusionné avec le bloc 24) ; d1 (galerie et crédit photo sous le texte de la fiche, seulement s'ils sont remplis). Avant la décision : date_tri du bloc 35 (le fichier dit 18/01/2026, l'article BX1 est du 22/12/2025 : provisoire au 15/06/2025 pour garder l'ordre du prototype, ou 22/12/2025 et le bloc passe après le 38) ; libellé de la section de texte d'un moment clé passé (« Ce qui s'est passé », rien, « Le récit ») ; « Stop au saccage » de l'agenda SPIP (2024-01-24) : fusion avec le bloc 23, avec le 24, ou événement distinct ; réintroduire sous le texte de la fiche la galerie et le crédit photo de la version du 25/09 (commit 800dcf8), si remplis.
  12. La teinte vert puis sépia d'une même photo sur la page Alliance et les fiches : réglé le 07/10 par Emmanuel. Refusé : tout ce qui obligerait l'Alliance à envoyer deux versions d'une photo. Retenu : WordPress teint à l'envoi. L'Alliance envoie la photo d'origine ; le thème, sur le crochet wp_generate_attachment_metadata (celui où WordPress fabrique déjà les miniatures), en enregistre une version par teinte (-jaune, -brun...) avec les courbes de docs/outils/courbes-teintes.json, et le gabarit choisit la version (verte si l'action est à venir, brune une fois passée ; brune pour les moments clés). Mesuré le 07/10 en PHP avec GD seul, sans extension : 0,26 s pour une photo de 1600 px, rendu identique à la maquette. Une courbe changée demande de refaire les versions (wp media regenerate). Écarté : le filtre SVG dans le navigateur (rendu au défilement non mesuré, photos deux fois plus lourdes, aperçus de partage en couleurs d'origine). Dans le prototype, docs/outils/teinter-photos.py joue ce rôle (champ variantes de docs/photos-frise.json : les cinq photos des moments clés en brun).
  13. GSAP depuis cdnjs : le réseau de l'école l'a-t-il laissé passer les 28 et 29/09 ? Sinon on copie les deux fichiers dans le thème avant la séance (le 06/10, un navigateur de test a vu cdnjs répondre lentement : la frise reste utilisable, elle passe en défilement simple).
  14. Mise à jour de Simple Custom Post Order (2.8.4) avant la séance, après export de la base (ACF gratuit est à jour).
  15. Déploiements sur accord : réglé (WORDPRESS.html et les trois corrections du prototype sont en ligne).
  16. Multilingue (questions client 8.1 à 8.3) : décidé le 08/10 par Emmanuel : le site est uniquement en français, il ne sera jamais traduit (aucune extension multilingue ; adresses et modèle en français). Avant la décision : on reste francophone pour la première version, ou on tranche avant de figer slugs et modèle ?
  17. Le bloc 39 « Aujourd'hui : la lutte continue » (nouveau, 06/10) : décidé le 08/10 par Emmanuel : C (clôture fixe de la frise, et sa carte écrite en dur après la requête des moments clés, son lien vers #evt-39-la-lutte-continue). Avant la décision : clôture fixe hors base, qui sort alors des moments clés de la page Alliance (A) ; événement importé, daté, qui reste dans les moments clés et dans la frise (B, mais les Tritoni-e le dépasseraient) ; clôture fixe et carte écrite en dur après la requête des moments clés, son lien menant à la frise (#evt-39-la-lutte-continue) (C ; dans le prototype, la carte mène à single.php). Recommandation : C, qui garde les deux rendus du prototype. Limite de C : l'ancre n'amène au bloc 39 qu'une fois la lecture de location.hash ajoutée à frise.js (section 15) ; d'ici là, le lien ouvre la frise à son début.
  18. L'ordre des moments clés (nouveau) : décidé le 08/10 par Emmanuel : A (du plus ancien au plus récent, l'ordre de la frise, comme le prototype ; la carte du 39 en dernier). Avant la décision : ordre de la frise, du plus ancien au plus récent, comme le prototype (A) ; du plus récent au plus ancien, comme le prévoyait ce plan le 01/10 (B). Prototype et WordPress suivront le même. Recommandation : A.
  19. Le lien Itinéraire (nouveau, voir section 5) : décidé le 08/10 par Emmanuel : B (nouveau champ « Adresse pour l'itinéraire », jamais affiché, et un lien OpenStreetMap collé, facultatif, prioritaire s'il est rempli ; page d'itinéraire directions?to=). Avant la décision : champ lieu existant ou nouveau champ adresse ; lien OpenStreetMap collé facultatif ou non ; directions?to= ou search?query=. Une seule décision, puis on aligne sections 5, 9, 18 et le code en réserve. Recommandation : nouveau champ adresse (le lieu est un libellé, « Coin Vander Bruggen et Scheutveld », pas une adresse), lien collé facultatif, directions?to=.
  20. La barre d'administration (nouveau) : choix de l'agent du 08/10, à valider (sans effet pour le visiteur) : masquée sur le site (A). Avant : masquée sur le site (A, la frise et la navigation restent au pixel) ; gardée et compensée en CSS (B : 32 px, et 46 px à 782 px de large et moins). Recommandation : A.
  21. Le libellé du chapitre 4 (nouveau) : décidé le 08/10 par Emmanuel : A (deux noms, comme la maquette, par un champ « Libellé court » sur la rubrique ; vide, la bande prend le nom complet). Avant la décision : « Quartier » dans la bande et « Le Quartier » dans le sous-menu, comme le prototype, par un libellé court en description du terme (A) ; « Le Quartier » partout, après vérification de la largeur dans la bande (B). Recommandation : A.

Points ouverts par l'audit de Ryan et la revue du 07/10 (docs/revue-2026-10-07/RAPPORT.md, numéros du rapport entre parenthèses ; page de décision illustrée, avec simulations : organisation/DECISIONS-REVUE.html). Ce sont des choix de rendu, pas de code : le prototype garde la maquette tant qu'ils ne sont pas tranchés.

  1. La frise sur tablette en portrait et sur écran 4:3 (I2, M12) : décidé le 08/10 par Emmanuel : C (frise verticale en portrait ou sur écran bas, échelle plafonnée à 1300 en paysage presque carré). Avant la décision : l'échelle suit la hauteur de l'écran ; en portrait (820 x 1180, 1024 x 1366), titres, cartes et pied dépassent la largeur (19 à 25 éléments jamais visibles en entier), à 1024 x 768 le pied de la frise est rogné à gauche ; en téléphone paysage (844 x 390), le texte tombe à 7 px. Options : frise verticale (celle du mobile) dès que l'écran est en portrait ou bas (A) ; échelle plafonnée par la largeur, min(100vh / 982, (100vw - 76px) / 1300) (B ; 1100, proposé d'abord, laisse le pied coupé de 65 px à 1024 x 768 et de 164 px à 1000 x 900, mesuré le 07/10 au soir ; 1300 fait tout tenir, sans effet à 1280 x 800 et au-delà) ; A et B ensemble (C) ; rien (D). Recommandation : C. La bascule de A est la même condition dans site.css (97 requêtes média) et dans frise.js ; frise.js mesure les blocs, B n'y change rien.
  2. Le contraste de l'ouverture du chapitre 01 (I4) : décidé le 08/10 par Emmanuel : A (texte brun sur le fond du chapitre 01) et le rouge --rouge à #d92e56 pour le menu de Contestation. Avant la décision : crème sur brun clair, 1,82:1 pour le grand titre et le menu (il faut 3:1 pour le titre, 4,5:1 pour le menu) ; menu de Contestation à 4,11:1. Options : texte brun sur ce fond (A, plus de 7:1) ; fond plus foncé (B) ; la maquette telle quelle (C). Recommandation : A pour Contexte ; pour le menu de Contestation, le rouge --rouge à #d92e56 (4,58:1) : la graisse seule ne suffit pas, un texte gras n'est « grand » qu'à partir de 18,7 px.
  3. Le texte secondaire à 50 % de brun (I5) : décidé le 08/10 par Emmanuel : A (un seul jeton de texte secondaire à 65 %, #827161 ; le 50 % pour les traits). Avant la décision : adresses, Itinéraire, textes des cartes, intitulés de Crédits, métadonnées de Presse, types de Ressources, coordonnées de Contact, intitulés de la fiche thème : 2,97 à 3,42:1 pour un seuil de 4,5:1. La même intention est écrite de six façons dans site.css. Options : un seul jeton de texte secondaire à 65 % de brun au moins (#827161, 4,57:1), le 50 % gardé pour les traits (A) ; brun plein (B) ; la maquette telle quelle (C). Recommandation : A.
  4. La page Crédits dans WordPress : décidé le 08/10 par Emmanuel : A (structure et noms dans page-credits.php, le tableau PHP du prototype ; la page existe dans l'admin pour son adresse et son titre). Avant la décision : la section 3 de ce plan prévoit la structure dans page-credits.php, la section 13 « le texte de la page dans l'éditeur » (répéteur supprimé). Les colonnes de rôles et de noms ne sortent pas d'un éditeur. Options : structure dans le gabarit, noms en dur (A) ; structure dans le gabarit, un champ texte par rôle (B) ; l'éditeur, sans les colonnes (C). Recommandation : A (les crédits changent rarement).
  5. L'icône du site : décidé le 08/10 par Emmanuel : A (le réglage « Icône du site » de WordPress, PNG carré de 512 px à ajouter à docs/outils/icones.py ; header.php n'écrit aucune balise d'icône). Avant la décision : le prototype a son SVG, un ICO et une icône de 180 px pour l'iPhone (assets/img/icones/, le « M »). Options : le réglage « Icône du site » de WordPress, qui demande un carré d'au moins 512 px (à ajouter à docs/outils/icones.py) et écrit lui-même ses balises, modifiable par l'Alliance (A) ; les fichiers du thème, écrits dans header.php comme le prototype (B). Recommandation : A.
  6. Le formulaire de contact (I13) : décidé le 08/10 par Emmanuel : A (un champ e-mail obligatoire sous le prénom ; une page Confidentialité devient nécessaire). Avant la décision : la maquette n'a ni champ e-mail ni case de consentement ; sans e-mail, l'Alliance ne peut pas répondre. Options : un champ e-mail obligatoire (A, 51 px de plus, un écart à la maquette) ; la maquette telle quelle (B). Dans les deux cas, une page Confidentialité devient nécessaire dès que le formulaire collecte des données (N18), et la question 2.6 du client reste ouverte. Recommandation : A.
  7. Le pied de page sur tablette (I11) : décidé le 08/10 par Emmanuel : A (le pied empilé du téléphone sous 1000 px). Avant la décision : la réduction proportionnelle n'a pas de plancher, le texte tombe à 9,7 px à 820 px de large (liens de 8 px de haut). Options : le pied empilé du mobile en dessous d'environ 1000 px (A) ; un plancher de 15 px (B, la grille déborde alors) ; rien (C). Recommandation : A.
  8. Arrêter ce qui bouge tout seul (M43, N7) : décidé le 08/10 par Emmanuel : A pour le bandeau (le bouton Pause de la vidéo l'arrête aussi), rien pour la constellation (seul le mouvement réduit l'arrête, comme aujourd'hui). Avant la décision : le bandeau d'actualités ne s'arrête qu'au survol de la souris ou au clavier (rien au toucher) ; la constellation dérive sans fin, sans aucun arrêt ; WCAG 2.2.2 (que le projet applique déjà à la vidéo). Options : un bouton Pause pour chacun, comme la vidéo (A) ; arrêt au toucher (B) ; le mouvement réduit seulement (C, aujourd'hui). Recommandation : A pour le bandeau, le bouton Pause de la vidéo arrêtant aussi le bandeau (rien de nouveau à l'écran) ; pour la constellation, B au minimum, A si Ryan accepte un bouton.
  9. Les archives par date (N14) : décidé le 08/10 par Emmanuel : A (archives par date coupées : is_date() mène à la 404, un bloc de plus dans functions.php). Avant la décision : WordPress pose la classe date sur le <body> d'une archive par date, et .date est une classe de la frise (position: absolute). Options : couper les archives par date, rien ne les utilise (A, quelques lignes) ; préfixer les classes génériques de la frise (B, des centaines de règles). Recommandation : A.
  10. Petits choix de mise en page : décidés le 08/10 par Emmanuel : a, le soulignement du lien de Crédits est gardé ; b, la date du bloc 02 remonte de 26 px de maquette ; c, toute la carte d'un bloc est cliquable (.carte__lien::after) ; d, la case vide des moments clés reste ; e, le fil d'Ariane est abandonné (la FINALE n'en a pas) ; f, une règle de focus commune (contour brun de 2 px, décalé de 2 px) ; g, les vidéos de l'intro 3D ne sont pas copiées dans le thème. Avant la décision : le soulignement du lien « Techniques infographiques Web » sur Crédits, que la maquette ne souligne pas (M9) ; le nom « Aujourd'hui » de la bande sur la fin de la date du bloc 02 au premier écran (M10) ; la carte d'un bloc de la frise cliquable en entier ou seulement par sa flèche de 21 x 19 px (M11) ; la rangée incomplète des moments clés, cinq cartes sur trois colonnes (N1) ; le fil d'Ariane décidé le 22/09, absent de la maquette et du prototype (N19) ; un style de focus commun au site (M50) ; la place de l'intro 3D, dont les 5 Mo partiraient dans le thème avec assets/ (N26).

Ces points sont expliqués pour la séance du 07/10, avec captures, options et recommandations revues, dans organisation/DECISIONS-WORDPRESS.html. Quatre recommandations y diffèrent de celles du plan : point 6 (une ligne dans functions.php plutôt que l'extension), point 8 (cocher 35, 36, 37 et les Tritoni-e, pas 21 et 23), point 9 (mixte : Alliance et fiche prêtes, Daria branche les pieds de page), point 18 (deux avis, la relecture préfère le plus récent d'abord).

Relevé du 06/10 au soir : inexactitudes du plan, à corriger avec les décisions

Relevées en revérifiant chaque point dans le prototype, la base locale, l'ancien site et la maquette (quatre lectures en parallèle). Corrigé le 07/10 dans les sections : l'exemple d'adresse et la preuve « répond 200 » (section 5), la lecture brute du champ lieu (section 9), l'argument MCP du point 6. Le reste attend les décisions de la section 14.

15. Phase suivante, après la première séance WordPress

16. Ce qui a été vérifié, et où

Chaque mécanisme dont le plan dépend a été lu dans le code de WordPress 7.1.2, d'ACF Pro 6.8.6 (jusqu'au 01/10) puis d'ACF gratuit 6.8.10, ou de Simple Custom Post Order 2.8.4 installés en local, et testé en lecture seule par wp eval. Ce qui n'a pas pu l'être : le thème lui-même, qui n'existe pas encore ; chaque test de bout en bout se fera après sa création.

MécanismePreuve
un fichier single-<type>.php est choisi par le type ; repli single.php, singular.php, index.php ; vrai pour un thème classique seulement (Twenty Twenty-Five, thème de blocs, replierait sur son templates/single.html)wp-includes/template.php, get_single_template ; template-loader.php
locate_template( array, true, false, $args ) charge le premier existant, expose $args, renvoie une chaîne vide sans avertissement ; ne cherche que dans le thème (jamais dans un plugin) ; false en troisième force require (pas require_once), indispensable pour un générique inclus plusieurs foiswp-includes/template.php, locate_template et load_template ; wp eval
meta_query de type NUMERIC : CAST(meta_value AS SIGNED) dans le WHERE et le ORDER BY, une jointure par clause ; 15500101 avant 20240501wp-includes/class-wp-meta-query.php ; SQL produit par get_posts, lu par wp eval
get_terms sans orderby suit l'ordre du glisser-déposer : deux filtres de Simple Custom Post Order (SQL sur le site, tri PHP dans l'admin), à condition que la taxonomie soit cochée dans ses réglages (fait pour rubrique et type_ressource) ; extension désactivée, l'ordre devient alphabétique sans erreursimple-custom-post-order.php, filtres get_terms_orderby et get_terms ; wp eval avec et sans l'extension
current_time( 'Ymd' ) suit Europe/Brussels, date() est en UTC ; wp_date( 'l j F' ) donne « dimanche 18 octobre » en minusculewp-settings.php (date_default_timezone_set( 'UTC' )), wp-includes/functions.php ; wp eval
un script mis en file en tête d'un gabarit, avant get_header() (ceux de la frise), sort avant </body> avec true ; la feuille unique, mise en file par functions.php sur wp_enqueue_scripts, sort dans le <head> ; condition : header.php appelle wp_head(), footer.php appelle wp_footer()wp-includes/default-filters.php (wp_print_styles sur wp_head), functions.wp-styles.php
ACF écrit ses JSON dans <thème actif>/acf-json/ dès que le dossier existe et est inscriptible ; aujourd'hui il viserait twentytwentyfive/acf-json, inexistantadvanced-custom-fields/includes/local-json.php
un type ACF réglé « permalien aucun » n'enregistre aucune règle de réécriture ; « non interrogeable » seul n'en retire aucune : les deux réglages sont nécessairesincludes/post-types/class-acf-post-type.php ; wp rewrite list
un champ taxonomie ACF n'a pas de valeur par défaut (29 réglages du champ, aucun default_value)includes/fields/class-acf-field-taxonomy.php ; wp eval
le script de portage des blocs : 34 copiés, aucun src="assets restant, syntaxe PHP valide sur les 34lancé sur une copie, hors du projet

Le 07/10, le prototype a été relu en entier avant le passage à WordPress (docs/revue-2026-10-07/RAPPORT.md) : neuf lectures en parallèle (pages sur ordinateur, frise, Vision alternative, mobile, code, CSS, contenus et liens, accessibilité et performance, préparation de WordPress), chaque constat reproduit ou réfuté par un second relecteur (154 constats, 19 réfutés, 100 retenus après fusion). Les corrections du même soir ont été vérifiées par un banc de captures (122 captures, 8 formats, comparaison au pixel contre une copie du prototype d'avant), qui sert de modèle à la vérification de la section 19.

Méthode de l'analyse du 01/10 : sept lectures en parallèle (pages verticales, frise, fiches, documentation et décisions, WordPress local, branches des étudiants, maquette Figma), trois plans indépendants (séance d'abord, client d'abord, risques d'abord), deux juges, une synthèse, puis chaque affirmation technique soumise à deux réfutations et le tout passé à une critique de complétude. Rien n'a été modifié dans le prototype, la base ni les branches.

17. Les aperçus de partage (Open Graph) dans WordPress (question d'Emmanuel, 06/10)

Dans le prototype (06/10) : template-parts/og.php écrit les balises, data/og.php donne les textes, docs/outils/og-images.py compose les 10 images (1200 x 630, sous 300 Ko : accueil, les cinq entrées du menu, Contact, Crédits, une fiche d'événement, une fiche de thème). L'adresse partagée (og:url) est l'adresse canonique de la page, sans les paramètres ajoutés par les plateformes : dans WordPress, wp_get_canonical_url(), la même que la balise canonical que WordPress écrit déjà. Passage à WordPress proposé :

QuoiDans WordPressPourquoi
Les balises og: et twitter:cardUne fonction du thème accrochée à wp_head (functions.php), qui reprend template-parts/og.php : titre de la page, extrait ou champ ACF « phrase de partage », image de partage, og:url = wp_get_canonical_url() (la même adresse que la balise canonical)Pas d'extension SEO à installer ni à expliquer ; une extension (Yoast SEO, Rank Math) le ferait aussi, avec un champ « image Facebook » par page, si le client veut plus tard gérer le référencement
Images des pages fixes (accueil, les cinq entrées du menu, Contact, Crédits)Les images actuelles, importées une fois dans la médiathèque (wp --path=wordpress media import prototype/assets/img/og/*.jpg, depuis la racine du dépôt), choisies dans un champ ACF image « Image de partage » sur chaque pageElles changent rarement ; si une page change, on relance le script et on remplace l'image
Images des fiches d'événements (33 fiches, et celles que le client ajoutera) ; les 8 fiches de thème prennent la même mécanique ou leur image à la uneComposées par le thème à l'enregistrement de la fiche (save_post) : la photo de la fiche, son titre en escalier en Bugrino et ses couleurs, écrites dans uploads/og/<slug>.jpg avec la bibliothèque GD de PHPLe client publie une fiche, l'aperçu suit tout seul, sans passer par nous
RepliL'image à la une recadrée en 1200 x 630 (add_image_size( 'partage', 1200, 630, true )), puis wp --path=wordpress media regenerate --image_size=partage --yes pour les images envoyées avant (une taille déclarée ne s'applique qu'aux envois suivants)Une fiche sans image composée a quand même un aperçu

Points à vérifier avant d'écrire le code : GD lit les polices TTF ou OTF, pas le WOFF2 servi au navigateur (il faudra Bugrino-Bold.ttf, licence à vérifier) ; GD et FreeType présents chez l'hébergeur (php -m | grep gd en SSH) ; le débogueur de Facebook pour tester un aperçu et vider son cache.

18. À corriger au passage dans WordPress (décisions d'Emmanuel)

19. Prochaine étape : construire le thème, dans l'ordre

Chaque étape se vérifie avant la suivante. La méthode de vérification est celle du 06/10 pour la feuille unique, outillée le 07/10 : captures des pages du prototype et du thème, aux mêmes formats, comparées au pixel par script (PIL.ImageChops.difference), en mouvement réduit, cache du navigateur coupé (Chrome sans fenêtre, port 9340). Formats : 1512 x 982 (la maquette), 1440 x 900, 1920 x 1080 et 402 de large (le mobile), plus 1024 x 768, 820 x 1180 et 844 x 390 (tablette, téléphone en paysage). La compatibilité PHP 7.4 (les postes de l'école) se vérifie sans PHP 7.4, par PHP_CodeSniffer et la règle PHPCompatibility (phpcs --standard=PHPCompatibility --runtime-set testVersion 7.4- <dossier>) : le 07/10, aucune syntaxe ni fonction postérieure dans le prototype.

  1. Trancher (Emmanuel) : section 14, points 1 à 6, 8, 9, 11, 16, les points 17 à 21 (06/10) et les points 22 à 31 de la revue du 07/10 (le 5 règle les pages de l'étape 3, le 8 et le 11 l'import de l'étape 5, le 16 les slugs ; les points 22 à 24 et 28 changent site.css avant qu'elle soit copiée).
  2. Préparer le prototype : fait le 07/10 (.home renommé .accueil, vérifié au pixel ; corrections de la revue), à mettre en ligne sur accord d'Emmanuel ; restent les corrections qui suivront les points 22 à 31.
  3. L'admin WordPress : section 12, point 2 (pages, type presse, champs, libellés, permaliens), après export de la base ; define( 'WP_ENVIRONMENT_TYPE', 'local' ); dans le wp-config.php local (section 8).
  4. Le socle du thème : style.css, functions.php (section 8, avec les blocs 7 et 8 du 07/10 : tailles d'images, teinte à l'envoi), header.php (polices préchargées et icônes de tete.php ou icône du site selon le point 26, lien d'évitement, <html class="no-js"> et son script pour la frise, navigation, classes de body, $page_courante), defined( 'ABSPATH' ) || exit; en tête de chaque morceau, footer.php (complet si le point 9 retient A ; sinon réduit à wp_footer() et à la fermeture du body, Daria écrit la grille en séance), footer-nu.php, index.php, page.php ; copie de assets/ (une seule feuille, scripts, polices, images, vidéos) ; le thème activé, acf-json/ rempli. Contrôle : une page vide du thème montre la navigation et le pied, au pixel du prototype.
  5. La frise : page-histoire-contexte.php (section 7), ouverture.php avec les ids du prototype, menu-chapitres.php, generique.php, template-parts/pied-frise.php (copié du prototype ; coordonnées lues sur la page Contact avant la séance si le point 9 retient A, sinon par Daria en séance), le script de portage des 34 blocs, puis l'import des contenus (section 12, point 4). Contrôle : la frise WordPress au pixel contre le prototype aux quatre formats, bande de position comprise (parts, barre, couleur des noms), les deux Tritoni-e encore en brouillon ; puis publication des Tritoni-e et contrôle visuel du générique et de la bande.
  6. Les gabarits préparés avant la séance : page-credits.php dans tous les cas (l'équipe admin ne fait pas de PHP ce jour-là) ; si le point 9 retient A, page-alliance.php (deux requêtes, section 9), single-evenement.php, pied-fiche.php et le footer.php complet. Chaque gabarit applique les cinq gestes (section 4), repart du code en réserve mis à jour (section 9) et se vérifie aux quatre formats.
  7. Le paquet des 13 postes (section 13) et la livraison du thème sur le dépôt de la classe (section 12, points 6 et 7), sur accord.
  8. La première séance WordPress (section 11) : les étudiants convertissent les autres pages (front-page.php, page-presse.php, page-ressources.php, page-contact.php, page-vision-alternative.php, single-thematique.php, et les gabarits clés si le point 9 retient B), mêmes gestes, mêmes formats.
  9. Après la séance : fusion des branches, finitions, les aperçus de partage (section 17), puis la mise en ligne du WordPress sur le sous-domaine, sur accord.

Les étapes 3 à 7 précèdent la première séance WordPress : la classe démarre sur une frise qui tourne déjà et sur un poste installé (section 11).