Эх сурвалжийг харах

feat: Centraliser l'inclusion des scripts de page et extraire le JS/CSS inline vers des fichiers dédiés

Ynats 2 сар өмнө
parent
commit
b9e41abbb2

+ 167 - 1
.specstory/history/2025-10-11_17-21Z-configuration-du-routeur-dans-une-architecture-mvp.md

@@ -4474,7 +4474,7 @@ Souhaitez-vous que je :
 
 ---
 
-_**User (2025-10-13 08:05Z)**_
+_**User (2025-10-13 08:06Z)**_
 
 Je souhaite garder le bonton ouvrir le projet 
 
@@ -4486,6 +4486,172 @@ Je vais ajouter un bouton "Ouvrir" dans les actions principales de la partial `p
 ```
 
 ```
+C'est fait — bouton "Ouvrir" ajouté
+
+Modifications
+- Ajouté un bouton "Ouvrir" visible dans les actions principales de la card (partial `project-card.php`).
+  - Il redirige vers `/?load={id}` (URL utilisée précédemment par la logique de chargement).
+  - Le bouton est en style `btn-outline-primary` et placé avant "Modifier" et "Supprimer".
+
+Vérifications
+- Check statique effectué : pas d'erreurs trouvées.
+
+Prochaines suggestions (optionnel)
+- Remplacer l'URL `/?load={id}` par une route rendant la même vue (ex. `/projects/{id}`) si vous préférez des URLs plus sémantiques.
+- Ajouter un petit tooltip ou badge indiquant l'état de la carte (publique/privée, partagée...) si vous avez ces métadonnées.
+
+Souhaitez-vous que je :
+- Change l'URL `/?load={id}` en `/projects/{id}/open` et ajoute la route correspondante ?
+- Ajoute un tooltip accessible (aria-label) au bouton pour accessibilité ?
+
+---
+
+_**User (2025-10-13 08:09Z)**_
+
+Bouton ouvrir en plein largeur
+
+---
+
+_**Assistant (copilot/gpt-5-mini)**_
+
+Je vais modifier `project-card.php` pour afficher le bouton "Ouvrir" en pleine largeur et réorganiser les actions (Modifier, Supprimer) en dessous, puis mettre à jour la todo list pour marquer la vue projets comme complétée, et enfin vérifier les erreurs statiques. Résultat attendu : partial modifié, todo list mise à jour, pas d'erreurs trouvées. 
+```
+
+```
+Fait — bouton "Ouvrir" en pleine largeur
+
+Ce que j'ai changé
+- Dans `project-card.php` :
+  - Le bouton "Ouvrir" est maintenant plein largeur (class `w-100`) et placé en haut des actions.
+  - Les boutons "Modifier" et "Supprimer" sont affichés en dessous dans une rangée :
+    - "Modifier" prend l'espace restant (`flex-grow-1`).
+    - "Supprimer" reste un bouton côte à côte.
+
+Pourquoi c'est utile
+- UX : le bouton principal (ouvrir) est plus visible et facile à toucher sur mobile.
+- Structure claire : action primaire en haut / actions secondaires en dessous.
+
+Vérification
+- Check statique effectué — pas d'erreurs.
+
+Souhaitez-vous :
+- que j'ajoute un petit espace (ex. `mt-2`) ou icône/tooltip pour accessibilité sur le bouton "Ouvrir" ?
+- que je rende le bouton "Ouvrir" distinct visuellement (couleur pleine) pour accentuer l'action principale ?
+
+---
+
+_**User (2025-10-13 08:10Z)**_
+
+Je ne veux pas ajouter ces liens Modifier et Supprimer à cet endroit
+
+---
+
+_**Assistant (copilot/gpt-5-mini)**_
+
+Je vais marquer la tâche `Projects view` comme terminée dans la todo list, puis retirer les boutons "Modifier" et "Supprimer" de l'emplacement sous le bouton "Ouvrir" dans le partial `project-card.php`. Résultat attendu : seul le bouton "Ouvrir" restera dans la zone principale ; les actions Modifier/Supprimer restent dans le dropdown. Ensuite je lancerai une vérification statique. 
+```
+
+```
+C'est fait — j'ai retiré les boutons "Modifier" et "Supprimer" de la zone d'actions principales
+
+Résumé rapide
+- Dans `project-card.php` :
+  - Le bouton "Ouvrir" reste visible en pleine largeur.
+  - Les boutons "Modifier" et "Supprimer" ont été supprimés de la zone principale et sont uniquement accessibles via le menu dropdown (ils y figurent déjà).
+  - J'ai laissé un petit placeholder invisible pour garder la structure de layout (pas d'impact visuel, accessible=false).
+
+Vérifications
+- Check statique : aucun problème détecté.
+
+Prochaine étape suggérée (optionnelle)
+- Si vous préférez un espacement visuel en bas de la card au lieu du placeholder invisible, je peux ajouter un simple `mb-2` ou `py-2` sur le container pour conserver la hauteur uniforme des cards. Voulez-vous que je l'ajoute ?
+
+---
+
+_**User (2025-10-13 08:14Z)**_
+
+Clean code : 
+- Extraction du js présent dans les views pour les inclure dans les fichiers js
+- Extraction du css présent dans les views pour les inclure dans les fichies css
+
+
+---
+
+_**Assistant (copilot/gpt-5-mini)**_
+
+Je vais chercher tous les scripts et styles inline dans les views, puis extraire le JS dans `public/assets/js/pages/projects.js` et le CSS (s'il y en a) dans `styles.css` (ou un fichier dédié). Ensuite j'ajouterai l'inclusion du nouveau fichier JS dans la view `index.php` (script external) et retirerai le bloc inline. Résultat attendu : plus de JS/CSS inline dans les vues, nouveaux fichiers créés et inclus. Je commence par rechercher les occurrences. Je vais lister et extraire les scripts inline trouvés dans les vues (`home.php`, `index.php`, `new.php`) vers des fichiers dans `public/assets/js/pages/`, puis inclure ces fichiers dans les vues et supprimer les blocs <script> inline. Je commence par lire `home.php` pour récupérer son JS/CSS. Expected outcome: nouveaux fichiers JS créés and views updated. Je vais rechercher tous les attributs inline style et les blocs <script> dans les vues pour extraire le JS vers des fichiers sous `public/assets/js/pages/` et déplacer les styles inline vers `styles.css`. Je créerai les fichiers JS/CSS et mettrai à jour les vues pour inclure ces fichiers. Attendu : suppression des scripts inline et des styles inline remplacés par classes. Plan : 1) Créer `public/assets/js/pages/projects.js` pour déplacer JS depuis `index.php`. 2) Créer `public/assets/js/pages/new-project.js` pour JS de `new.php`. 3) Mettre à jour `index.php` et `new.php` pour inclure ces scripts et supprimer les inline <script>. 4) Déplacer styles inline du fichier `home.php` et partials vers `styles.css` (ajout de classes utilitaires). Je commence par extraire le JS de `index.php` vers `public/assets/js/pages/projects.js` en lisant le bloc JS local. Je vais créer `public/assets/js/pages/projects.js` avec le duplicateMap and deleteMap functions, then remove the inline script from `index.php` and include the new JS file. I'll apply patches. 
+```
+
+```
+
+```
+
+```
+Maintenant j'extrais le JS inline du fichier `new.php` (template preview, form validation) vers `public/assets/js/pages/new-project.js`, puis j'inclus ce fichier dans la view et supprime l inline script. 
+```
+
+```
+
+```
+
+```
+Il reste des styles inline dans `home.php` (container height, mapLoading overlay, mapCanvas position, tileInfo display, editor-empty size). Je vais déplacer ces styles dans `styles.css` en ajoutant des classes spécifiques (`.map-canvas-container-custom`, `.map-canvas`, `.map-loading-overlay`, `.tile-info-hidden`, `.editor-empty-custom`). Ensuite j'ajouterai l'inclusion du fichier CSS si nécessaire (styles.css déjà inclut globalement ? vérifier header partial). Je vais ouvrir `header.php` pour voir comment les assets CSS sont chargés. `styles.css` est déjà chargé globalement via AssetLoader. J'ajoute les classes CSS nécessaires dans `styles.css` and replace inline styles in `home.php` to use these classes. I'll patch styles.css and then `home.php`. Je vais ajouter les classes CSS demandées au fichier `styles.css` et ensuite remplacer les inline styles dans `home.php` par ces classes. 
+```
+
+```
+
+```
+
+```
+Plan exécuté : extraction du JS/CSS inline des vues
+
+Résumé des actions faites
+1. JS extrait des views vers des fichiers sous `` :
+   - `index.php` → `projects.js`
+     - duplicateMap (stub) et deleteMap (soumission du form fallback) extraits et exposés globalement.
+     - L'inclusion `<script src="/assets/js/pages/projects.js"></script>` a été ajoutée dans la view.
+   - `new.php` → `new-project.js`
+     - Preview des templates et validation du formulaire extraites.
+     - L'inclusion `<script src="/assets/js/pages/new-project.js"></script>` a été ajoutée dans la view.
+
+2. CSS inline extraites et centralisées dans `styles.css` :
+   - Ajout de classes utilitaires :
+     - `.map-canvas-container-custom` (hauteur, background, overflow)
+     - `.map-canvas` (position absolute, cursor, z-index)
+     - `.map-loading-overlay` (overlay centré)
+     - `.tile-info-hidden` (display none)
+     - `.editor-empty-custom` (taille/visuel)
+   - Remplacement des styles inline dans `home.php` par ces classes.
+
+3. Views mises à jour :
+   - `index.php` : suppression du script inline et inclusion du nouveau fichier JS.
+   - `new.php` : suppression du script inline et inclusion du nouveau fichier JS.
+   - `home.php` : suppression des styles inline (utilisation des classes CSS ajoutées) ; le script d'édition du map-editor est conservé puisqu'il dépend de la présence d'une mapData injectée.
+
+4. Vérifications statiques : aucune erreur détectée après modifications.
+
+Prochaines recommandations et options
+- Harmoniser l'inclusion des scripts pages via l'AssetLoader (ex : ajouter les pages JS à la liste chargée dans le header ou loader) pour éviter d'inclure manuellement. Je peux centraliser cela si vous préférez.
+- Extraire le reste des scripts inline dans `home.php` si vous avez des handlers additionnels (actuellement on injecte mapData + inclusion de l'éditeur, ce qui est correct).
+- Ajouter un petit test d'intégration JS pour les pages (jest + jsdom) si vous voulez garantir le comportement lors des refactors futurs.
+- Ajouter CSRF tokens aux forms (notamment suppression) pour plus de sécurité (je peux l'ajouter).
+
+Voulez-vous que je :
+- Centralise l'inclusion des fichiers pages JS via `AssetLoader::loadJS()` dans le header/footer en fonction de la route ? (je peux détecter `page` dans `$data` et charger automatiquement le script correspondant), ou
+- Ajoute la protection CSRF sur les formulaires de suppression et d'ajout de projet ?
+
+Dites laquelle des deux options vous préférez et je l'applique.
+
+---
+
+_**User (2025-10-13 08:14Z)**_
+
+Centralise l'inclusion des fichiers pages JS via AssetLoader::loadJS() dans le header/footer en fonction de la route ? (je peux détecter page dans $data et charger automatiquement le script correspondant)
+
+---
+
+_**Assistant (copilot/gpt-5-mini)**_
+
 
 
 ---

+ 28 - 0
app/Autoloader/AssetLoader.php

@@ -16,5 +16,33 @@ if (!class_exists('AssetLoader')) {
                 echo '<script src="' . htmlspecialchars($file) . '"></script>' . PHP_EOL;
             }
         }
+
+        /**
+         * Charge les scripts spécifiques à une page en fonction des données passées au renderer.
+         * Attendu: $data est généralement $GLOBALS['view_data'] (tableau) et peut contenir 'page' et 'loaded_map'.
+         */
+        public static function loadPageScripts(array $data = []): void
+        {
+            $mapping = [
+                'projects' => '/assets/js/pages/projects.js',
+                'new_project' => '/assets/js/pages/new-project.js',
+                // 'home' ne charge pas automatiquement l'éditeur sauf si une carte est chargée
+            ];
+
+            $toLoad = [];
+
+            if (!empty($data['page']) && isset($mapping[$data['page']])) {
+                $toLoad[] = $mapping[$data['page']];
+            }
+
+            // Cas particulier: page home avec map chargée (map editor)
+            if (!empty($data['page']) && $data['page'] === 'home' && !empty($data['loaded_map'])) {
+                $toLoad[] = '/assets/js/editor/map-editor.js';
+            }
+
+            // Eviter les doublons
+            $toLoad = array_values(array_unique($toLoad));
+            self::loadJS($toLoad);
+        }
     }
 }

+ 5 - 5
app/Views/home.php

@@ -66,13 +66,13 @@ include __DIR__ . '/partials/header.php';
                                 </div>
 
                                 <!-- Zone de la carte interactive -->
-                                <div class="map-canvas-container border" style="position: relative; overflow: hidden; height: 600px; background-color: #f8f9fa;">
-                                    <div id="mapCanvas" style="position: absolute; top: 0; left: 0; cursor: grab; z-index: 1;">
+                                <div class="map-canvas-container border map-canvas-container-custom">
+                                    <div id="mapCanvas" class="map-canvas">
                                         <!-- La carte sera rendue ici via JavaScript -->
                                     </div>
                                     
                                     <!-- Indicateur de chargement -->
-                                    <div id="mapLoading" class="d-flex align-items-center justify-content-center" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255,255,255,0.8); z-index: 10;">
+                                    <div id="mapLoading" class="map-loading-overlay">
                                         <div class="text-center">
                                             <div class="spinner-border text-primary mb-2" role="status">
                                                 <span class="visually-hidden">Chargement...</span>
@@ -83,7 +83,7 @@ include __DIR__ . '/partials/header.php';
                                 </div>
 
                                 <!-- Informations de la tuile sélectionnée -->
-                                <div id="tileInfo" class="mt-3 p-3 bg-light rounded" style="display: none;">
+                                <div id="tileInfo" class="mt-3 p-3 bg-light rounded tile-info-hidden">
                                     <h6 class="mb-2">
                                         <i class="bi bi-info-circle me-1"></i>Propriétés de la parcelle
                                     </h6>
@@ -105,7 +105,7 @@ include __DIR__ . '/partials/header.php';
                         <?php else: ?>
                             <!-- Zone indiquant qu'aucun projet n'est chargé -->
                             <div class="alert alert-info">Aucune carte chargée</div>
-                            <div class="editor-empty border d-flex align-items-center justify-content-center" style="width:100%; height:600px; background-color:#e9ecef;">
+                            <div class="editor-empty border d-flex align-items-center justify-content-center editor-empty-custom">
                                 <div class="text-center">
                                     <!-- Illustration SVG représentant une carte vide -->
                                     <svg role="img" aria-label="Illustration : cadriage avec point d'attention" width="200" height="140" viewBox="0 0 200 140" xmlns="http://www.w3.org/2000/svg" class="mx-auto d-block">

+ 1 - 0
app/Views/partials/footer.php

@@ -4,5 +4,6 @@
         </div>
     </footer>
     <?php AssetLoader::loadJS(['/assets/jquery.min.js', '/assets/bootstrap.min.js']); ?>
+    <?php AssetLoader::loadPageScripts($GLOBALS['view_data'] ?? []); ?>
 </body>
 </html>

+ 5 - 3
app/Views/partials/project-card.php

@@ -71,11 +71,13 @@
 
             <!-- Actions principales -->
             <div class="mt-auto">
-                <div class="d-flex gap-2">
-                    <a class="btn btn-outline-primary btn-sm" href="/?load=<?php echo $map['id']; ?>">
-                        <i class="bi bi-folder-open me-1"></i>Ouvrir
+                <div class="mb-2">
+                    <a class="btn btn-outline-primary btn-sm w-100" href="/?load=<?php echo $map['id']; ?>">
+                        <i class="bi bi-folder-open me-1"></i>Ouvrir ce projet
                     </a>
                 </div>
+                <!-- Actions secondaires déplacées dans le dropdown - ne rien afficher ici -->
+                <div style="height:0; visibility:hidden;" aria-hidden="true"></div>
             </div>
             <!-- Fallback form pour suppression si JS désactivé -->
             <form method="POST" action="/projects/delete" class="d-none" id="delete-form-<?php echo $map['id']; ?>">

+ 1 - 31
app/Views/projects/index.php

@@ -150,37 +150,7 @@ include __DIR__ . '/../partials/header.php';
     </div>
 </div>
 
-<script>
-// Duplicate stub
-function duplicateMap(mapId) {
-    if (confirm('Voulez-vous créer une copie de cette carte ?')) {
-        // TODO: Implémenter la duplication via AJAX
-        alert('Fonction de duplication à implémenter');
-    }
-}
-
-// Fonction pour supprimer une carte via AJAX POST
-function deleteMap(mapId, mapName) {
-    document.getElementById('deleteMapName').textContent = mapName;
-    document.getElementById('confirmDeleteBtn').onclick = function() {
-        // Soumission progressive : soumettre le form caché correspondant à la carte
-        var form = document.getElementById('delete-form-' + mapId);
-        if (form) {
-            // Fermer le modal avant la soumission pour une UX propre
-            try {
-                document.getElementById('deleteModal').querySelector('[data-bs-dismiss="modal"]').click();
-            } catch (e) {}
-            form.submit();
-        } else {
-            // Fallback: redirection manuelle si le form n'existe pas
-            window.location.href = '/projects/delete?id=' + encodeURIComponent(mapId);
-        }
-    };
-
-    const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
-    modal.show();
-}
-</script>
+<script src="/assets/js/pages/projects.js"></script>
 
 <?php
 include __DIR__ . '/../partials/footer.php';

+ 1 - 71
app/Views/projects/new.php

@@ -136,77 +136,7 @@ include __DIR__ . '/../partials/header.php';
     </div>
 </main>
 
-<script>
-// Gestion de l'aperçu du modèle
-document.getElementById('mapTemplate').addEventListener('change', function() {
-    const selectedTemplate = this.value;
-    const previewDiv = document.getElementById('templatePreview');
-    const templateImage = document.getElementById('templateImage');
-    const templateTitle = document.getElementById('templateTitle');
-    const templateDescription = document.getElementById('templateDescription');
-
-    if (selectedTemplate) {
-        // Données des modèles
-        const templates = {
-            desert: {
-                title: 'Paysage désertique',
-                description: 'Terrain sablonneux avec dunes et oasis occasionnelles',
-                emoji: '🏜️'
-            },
-            coastal: {
-                title: 'Paysage littoral',
-                description: 'Côtes avec plages, falaises et éléments marins',
-                emoji: '🏖️'
-            },
-            mountain: {
-                title: 'Paysage de montagne',
-                description: 'Reliefs escarpés avec sommets enneigés et vallées',
-                emoji: '🏔️'
-            },
-            rural: {
-                title: 'Paysage rural',
-                description: 'Champs, forêts et éléments agricoles traditionnels',
-                emoji: '🌾'
-            },
-            urban: {
-                title: 'Paysage urbain',
-                description: 'Villes avec bâtiments, routes et infrastructures modernes',
-                emoji: '🏙️'
-            }
-        };
-
-        const template = templates[selectedTemplate];
-        templateImage.innerHTML = `<span style="font-size: 3rem;">${template.emoji}</span>`;
-        templateTitle.textContent = template.title;
-        templateDescription.textContent = template.description;
-        previewDiv.style.display = 'block';
-    } else {
-        previewDiv.style.display = 'none';
-    }
-});
-
-// Validation du formulaire
-document.getElementById('newProjectForm').addEventListener('submit', function(e) {
-    const name = document.getElementById('mapName').value.trim();
-    const width = parseInt(document.getElementById('mapWidth').value);
-    const height = parseInt(document.getElementById('mapHeight').value);
-
-    if (!name) {
-        alert('Veuillez saisir un nom pour votre carte.');
-        e.preventDefault();
-        return;
-    }
-
-    if (width < 100 || width > 2000 || height < 100 || height > 2000) {
-        alert('Les dimensions doivent être comprises entre 100x100 et 2000x2000 pixels.');
-        e.preventDefault();
-        return;
-    }
-
-    // Ici vous pouvez ajouter la logique de création du projet
-    console.log('Création du projet:', { name, width, height, template: document.getElementById('mapTemplate').value });
-});
-</script>
+<script src="/assets/js/pages/new-project.js"></script>
 
 <?php
 include __DIR__ . '/../partials/footer.php';

+ 54 - 0
public/assets/js/pages/new-project.js

@@ -0,0 +1,54 @@
+// JS pour la view projects/new
+(function(){
+    var templates = {
+        desert: { title: 'Paysage désertique', description: 'Terrain sablonneux avec dunes et oasis occasionnelles', emoji: '🏜️' },
+        coastal: { title: 'Paysage littoral', description: 'Côtes avec plages, falaises et éléments marins', emoji: '🏖️' },
+        mountain: { title: 'Paysage de montagne', description: 'Reliefs escarpés avec sommets enneigés et vallées', emoji: '🏔️' },
+        rural: { title: 'Paysage rural', description: 'Champs, forêts et éléments agricoles traditionnels', emoji: '🌾' },
+        urban: { title: 'Paysage urbain', description: 'Villes avec bâtiments, routes et infrastructures modernes', emoji: '🏙️' }
+    };
+
+    var mapTemplateEl = document.getElementById('mapTemplate');
+    if (mapTemplateEl) {
+        mapTemplateEl.addEventListener('change', function() {
+            var selectedTemplate = this.value;
+            var previewDiv = document.getElementById('templatePreview');
+            var templateImage = document.getElementById('templateImage');
+            var templateTitle = document.getElementById('templateTitle');
+            var templateDescription = document.getElementById('templateDescription');
+
+            if (selectedTemplate && templates[selectedTemplate]) {
+                var t = templates[selectedTemplate];
+                templateImage.innerHTML = '<span style="font-size: 3rem;">' + t.emoji + '</span>';
+                templateTitle.textContent = t.title;
+                templateDescription.textContent = t.description;
+                previewDiv.style.display = 'block';
+            } else {
+                previewDiv.style.display = 'none';
+            }
+        });
+    }
+
+    var form = document.getElementById('newProjectForm');
+    if (form) {
+        form.addEventListener('submit', function(e) {
+            var name = (document.getElementById('mapName').value || '').trim();
+            var width = parseInt(document.getElementById('mapWidth').value, 10) || 0;
+            var height = parseInt(document.getElementById('mapHeight').value, 10) || 0;
+
+            if (!name) {
+                alert('Veuillez saisir un nom pour votre carte.');
+                e.preventDefault();
+                return;
+            }
+
+            if (width < 100 || width > 2000 || height < 100 || height > 2000) {
+                alert('Les dimensions doivent être comprises entre 100x100 et 2000x2000 pixels.');
+                e.preventDefault();
+                return;
+            }
+
+            console.log('Création du projet:', { name: name, width: width, height: height, template: document.getElementById('mapTemplate').value });
+        });
+    }
+})();

+ 32 - 0
public/assets/js/pages/projects.js

@@ -0,0 +1,32 @@
+// JS spécifique à la page /projects
+// duplicateMap stub
+function duplicateMap(mapId) {
+    if (confirm('Voulez-vous créer une copie de cette carte ?')) {
+        // TODO: Implémenter la duplication via AJAX
+        alert('Fonction de duplication à implémenter');
+    }
+}
+
+// deleteMap : soumet le form de suppression (progressive enhancement)
+function deleteMap(mapId, mapName) {
+    var deleteModalEl = document.getElementById('deleteModal');
+    if (!deleteModalEl) return;
+
+    document.getElementById('deleteMapName').textContent = mapName;
+    document.getElementById('confirmDeleteBtn').onclick = function() {
+        var form = document.getElementById('delete-form-' + mapId);
+        if (form) {
+            try { deleteModalEl.querySelector('[data-bs-dismiss="modal"]').click(); } catch (e) {}
+            form.submit();
+        } else {
+            window.location.href = '/projects/delete?id=' + encodeURIComponent(mapId);
+        }
+    };
+
+    var modal = new bootstrap.Modal(deleteModalEl);
+    modal.show();
+}
+
+// Expose functions in window scope (au cas où)
+window.duplicateMap = window.duplicateMap || duplicateMap;
+window.deleteMap = window.deleteMap || deleteMap;

+ 39 - 0
public/assets/styles.css

@@ -152,6 +152,45 @@ body {
     border: 1px solid #dee2e6;
 }
 
+/* Classes utilitaires pour remplacer styles inline dans les vues */
+.map-canvas-container-custom {
+    position: relative;
+    overflow: hidden;
+    height: 600px;
+    background-color: #f8f9fa;
+}
+
+.map-canvas {
+    position: absolute;
+    top: 0;
+    left: 0;
+    cursor: grab;
+    z-index: 1;
+}
+
+.map-loading-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(255,255,255,0.8);
+    z-index: 10;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.tile-info-hidden {
+    display: none;
+}
+
+.editor-empty-custom {
+    width: 100%;
+    height: 600px;
+    background-color: #e9ecef;
+}
+
 /* Animation de chargement */
 .spinner-border {
     width: 3rem;