Article SEO SEO Technique

Comment créer un thème WordPress : guide complet étape par étape

Sommaire de l'article

Introduction

WordPress est une plateforme de CMS (Content Management System) extrêmement populaire pour créer et gérer des sites web de tous types : blogs, sites vitrines, boutiques en ligne, portails d’actualités, portfolios, etc. L’un des principaux atouts de WordPress est sa grande flexibilité, notamment grâce à son système de thèmes. Un thème WordPress détermine l’apparence visuelle, la mise en page et une partie du fonctionnement de votre site web.

Créer un thème WordPress personnalisé peut paraître intimidant au premier abord, mais en suivant une méthodologie claire et en comprenant les fichiers essentiels, cette tâche devient accessible, même pour un développeur débutant ayant des bases en HTML, CSS et PHP. Un thème bien conçu permet d’améliorer les performances, l’expérience utilisateur, le référencement naturel et la maintenabilité du site.

Dans cet article, nous allons vous guider pas à pas pour créer un thème WordPress à partir de zéro, puis voir comment le personnaliser et l’optimiser. Nous aborderons :

  • les concepts clés et la structure d’un thème WordPress ;
  • les fichiers indispensables et leur rôle ;
  • la création d’un thème enfant (child theme) pour personnaliser un thème existant ;
  • les bonnes pratiques de performance, d’accessibilité et de sécurité ;
  • des conseils concrets pour améliorer l’UX et le SEO on-page de votre site.

Pré-requis pour créer un thème WordPress

Avant de commencer, il est important de disposer de quelques éléments et compétences de base :

  • Une installation WordPress fonctionnelle en local (via des outils comme Local, MAMP, WampServer ou XAMPP) ou sur un serveur d’hébergement.
  • Un éditeur de code adapté au développement (VS Code, PhpStorm, Sublime Text, etc.).
  • Des notions de base en HTML5, CSS3 et PHP. Des connaissances en JavaScript sont un plus, notamment pour les fonctionnalités interactives.
  • Des permissions pour accéder au répertoire /wp-content/themes/ de votre installation WordPress (via FTP, SFTP ou un gestionnaire de fichiers de votre hébergeur).

Concepts clés

Pour créer un thème WordPress propre et évolutif, il est essentiel de maîtriser quelques concepts fondamentaux.

Qu’est-ce qu’un thème WordPress ?

Un thème WordPress est un ensemble de fichiers (PHP, CSS, JavaScript, images, templates, etc.) qui contrôle :

  • l’apparence du site (typographie, couleurs, mise en page) ;
  • la structure des pages (en-tête, contenu, barre latérale, pied de page) ;
  • la manière dont les contenus (articles, pages, archives, résultats de recherche) sont affichés.

WordPress se base sur une hiérarchie de templates pour déterminer quel fichier utiliser selon le type de contenu demandé (page d’accueil, article, catégorie, page 404, etc.).

Structure d’un thème WordPress

Un thème WordPress classique est stocké dans un dossier situé dans /wp-content/themes/. Ce dossier contient au minimum deux fichiers obligatoires pour qu’un thème soit reconnu par WordPress :

  • style.css : fichier principal de styles, mais aussi fichier qui contient les métadonnées du thème (nom, auteur, description, version, etc.) dans un commentaire en haut du fichier.
  • index.php : fichier modèle principal. S’il n’existe aucun autre template plus spécifique pour un type de contenu donné, c’est ce fichier qui sera utilisé par WordPress pour générer la page.

En pratique, un thème complet inclut généralement d’autres fichiers importants :

  • header.php : contient le code de l’en-tête du site, notamment la balise , le logo, le menu principal, l’ouverture de la balise et souvent l’ouverture d’un conteneur principal.
  • footer.php : contient le code du pied de page du site, les informations légales, des liens secondaires, les scripts chargés en bas de page et la fermeture des balises et .
  • functions.php : permet de déclarer les fonctionnalités du thème (menus, widgets, images à la une, scripts et feuilles de style, support des fonctionnalités de WordPress, etc.).
  • sidebar.php : gère l’affichage de la ou des barres latérales (widgets, menus secondaires, formulaires de recherche, etc.).
  • single.php : définit la mise en page d’un article de blog individuel.
  • page.php : définit l’affichage d’une page statique.
  • archive.php : gère l’affichage des listes d’articles par catégorie, étiquette, auteur ou date.
  • search.php : affiche les résultats de recherche internes du site.
  • 404.php : affiche la page d’erreur lorsque le contenu demandé n’est pas trouvé.

La hiérarchie des templates

WordPress utilise une hiérarchie précise pour choisir quel fichier utiliser. Par exemple :

  • Pour un article de blog, WordPress va chercher successivement single-{post-type}.php, puis single.php, puis index.php si aucun fichier plus spécifique n’est présent.
  • Pour une page, il utilisera un éventuel template personnalisé (fichier avec un commentaire Template Name), sinon page.php, puis index.php.
  • Pour les archives de catégories, il cherchera category-{slug}.php, puis category.php, puis archive.php, puis index.php.

Comprendre cette hiérarchie vous permet de créer des modèles spécifiques pour les types de contenus les plus importants de votre site, tout en gardant index.php comme solution de repli.

La boucle WordPress (The Loop)

Les fichiers de templates utilisent la boucle WordPress, un ensemble de fonctions PHP qui permettent de parcourir et d’afficher les contenus dynamiques. Un exemple typique de boucle est :

  

Aucun contenu disponible pour le moment.

Cette structure est fondamentale : elle permet à WordPress d’afficher les articles, les pages ou les résultats de recherche en fonction du contexte.

Créer un thème WordPress à partir de zéro

Étape 1 : Créer le dossier du thème

Pour créer un nouveau thème, commencez par :

  1. Accéder au répertoire /wp-content/themes/ de votre installation WordPress.
  2. Créer un nouveau dossier, par exemple mon-theme-personnalise. Évitez les espaces et les caractères spéciaux ; utilisez plutôt des tirets.

Étape 2 : Créer le fichier style.css

À l’intérieur de ce dossier, créez un fichier nommé style.css. En haut de ce fichier, ajoutez un commentaire contenant les métadonnées du thème :

/*
Theme Name: Mon Thème Personnalisé
Theme URI: https://exemple.com/mon-theme
Author: Votre Nom
Author URI: https://exemple.com
Description: Thème WordPress personnalisé, léger et optimisé.
Version: 1.0.0
Text Domain: mon-theme-personnalise
*/

Ce bloc de commentaires est indispensable pour que WordPress reconnaisse et affiche votre thème dans le tableau de bord, dans le menu Apparence > Thèmes.

En dessous de ce commentaire, vous pouvez commencer à ajouter vos styles CSS. Par exemple :

body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; padding: 0;
}

Étape 3 : Créer le fichier index.php

Créez ensuite un fichier index.php dans le même dossier. Ce fichier sera le template principal de votre thème. Commencez par un contenu minimal utilisant les fonctions get_header et get_footer :

 
>

Aucun article trouvé.

Pour séparer la structure de l’en-tête et du pied de page, créez deux fichiers : header.php et footer.php.

Exemple minimal de header.php :


>
   <?php wp_title('|', true, 'right'); bloginfo('name'); ?> 

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
>  

Exemple minimal de footer.php :

 

© - . Tous droits réservés.

Étape 5 : Déclarer les fonctionnalités dans functions.php

Le fichier functions.php est essentiel pour configurer les fonctionnalités de base de votre thème : prise en charge des images mises en avant, menus de navigation, chargement des scripts et feuilles de style, etc. Créez ce fichier et ajoutez :

 gérée par WordPress add_theme_support( 'title-tag' ); // Support des images à la une add_theme_support( 'post-thumbnails' ); // Menus de navigation register_nav_menus( array( 'primary' => 'Menu principal', 'footer' => 'Menu pied de page', ) ); // Support du HTML5 pour plusieurs éléments add_theme_support( 'html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', ) );
}
add_action( 'after_setup_theme', 'mon_theme_setup' ); function mon_theme_scripts { // Feuille de style principale wp_enqueue_style( 'mon-theme-style', get_stylesheet_uri, array, wp_get_theme->get( 'Version' ) );
}
add_action( 'wp_enqueue_scripts', 'mon_theme_scripts' );

Évitez d’utiliser @import dans les fichiers CSS pour charger des feuilles de style supplémentaires, car cela dégrade les performances. Préférez toujours wp_enqueue_style et wp_enqueue_script dans functions.php.

Créer un thème enfant (child theme)

Pourquoi un thème enfant ?

Un child theme permet de personnaliser un thème existant (thème parent) sans modifier directement ses fichiers. Ainsi, lorsque le thème parent est mis à jour, vos modifications restent intactes. C’est la méthode recommandée pour personnaliser un thème téléchargé depuis le répertoire officiel ou acheté sur une marketplace.

Étapes pour créer un thème enfant

Pour créer un thème enfant WordPress moderne, procédez comme suit :

  1. Dans /wp-content/themes/, créez un nouveau dossier, par exemple mon-theme-enfant.
  2. Dans ce dossier, créez un fichier style.css contenant au minimum :
/*
Theme Name: Mon Thème Enfant
Template: nom-du-theme-parent
Description: Thème enfant du thème parent "Nom du Thème Parent".
Author: Votre Nom
Version: 1.0.0
Text Domain: mon-theme-enfant
*/

La valeur de Template doit correspondre exactement au nom du dossier du thème parent. Depuis les versions récentes de WordPress, il est déconseillé d’utiliser @import dans style.css pour charger la feuille de style du thème parent. À la place, utilisez functions.php du thème enfant pour gérer les feuilles de style.

  1. Dans le même dossier, créez un fichier functions.php avec par exemple :
get( 'Version' ) );
}
add_action( 'wp_enqueue_scripts', 'mon_theme_enfant_styles' );

Ensuite, vous pouvez :

  • copier un fichier du parent vers le thème enfant (par exemple single.php) et le modifier pour surcharger son comportement ;
  • ajouter du CSS personnalisé dans le style.css du thème enfant ;
  • ajouter des fonctions spécifiques dans le functions.php du thème enfant.

Modèles et templates essentiels

Les principaux fichiers de templates

En plus de index.php, voici quelques fichiers de modèles très utiles pour structurer efficacement votre thème :

  • front-page.php : utilisé pour la page d’accueil lorsqu’une page statique est définie comme page d’accueil dans les réglages de lecture.
  • home.php : utilisé pour la liste des articles du blog lorsque la page d’accueil est configurée pour afficher les derniers articles.
  • single.php : pour l’affichage d’un article individuel.
  • page.php : pour l’affichage des pages statiques.
  • archive.php : pour les archives (catégories, étiquettes, auteurs, dates).
  • category.php et tag.php : pour personnaliser les archives de catégories et d’étiquettes.
  • search.php : pour les résultats de recherche.
  • 404.php : pour la page d’erreur 404 personnalisée.

Créer un modèle de page personnalisé

Vous pouvez créer des modèles de page spécifiques (pour une landing page, une page d’accueil spéciale, une page de contact, etc.). Pour cela, créez un fichier PHP, par exemple template-landing.php, et ajoutez en haut du fichier :


 

Ce template sera ensuite sélectionnable depuis l’éditeur de page WordPress dans la colonne de droite (bloc « Modèle »).

Bonnes pratiques pour un thème WordPress efficace

Optimiser les performances et le contenu

Un thème performant améliore le temps de chargement, l’expérience utilisateur et le référencement naturel. Voici quelques recommandations clés :

  • Réduire la taille des images : utilisez des formats adaptés (JPEG pour les photos, PNG ou SVG pour les icônes et logos) et des outils de compression. Vous pouvez également tirer parti du format WebP si votre hébergement le permet.
  • Charger les fichiers CSS et JavaScript de manière optimisée : utilisez wp_enqueue_style et wp_enqueue_script dans functions.php, évitez de lier directement des fichiers dans le HTML. Placez les scripts non critiques en pied de page et activez le chargement différé lorsque c’est possible.
  • Mettre en cache les ressources : utilisez des en-têtes de cache côté serveur et, si besoin, un plugin de cache pour générer des pages statiques. Le thème doit rester léger pour que ces outils soient efficaces.
  • Limiter le nombre de polices externes : évitez de charger trop de familles de polices et de variantes. Privilégiez 1 à 2 familles de polices avec quelques graisses seulement.
  • Désactiver les plugins inutiles : un thème bien conçu n’a pas besoin de plugins pour des fonctionnalités de base comme le menu ou la mise en page standard. Plus il y a de plugins, plus le risque de conflit et de lenteur augmente.
  • Utiliser un CDN si nécessaire : un réseau de diffusion de contenu peut accélérer le chargement des images, fichiers CSS et JS pour les visiteurs éloignés de votre serveur.

Améliorer la structure et l’architecture du thème

Une architecture claire facilite la maintenance, les mises à jour et l’évolution du thème.

  • Séparer le code logique du design : laissez la logique métier (fonctions PHP, requêtes, hooks) dans functions.php et les fichiers de templates, tandis que la présentation doit rester dans les fichiers CSS. Évitez de mélanger de longs blocs de styles en ligne dans les templates.
  • Utiliser des parties de template : lorsque vous avez des blocs répétés (par exemple l’affichage d’un article dans une liste), créez des fichiers partiels comme content.php ou content-single.php et incluez-les avec get_template_part.
  • Respecter les standards de codage WordPress : suivez les conventions de nommage, d’indentation et les recommandations de sécurité (échappement des données, vérification des capacités, etc.).
  • Rendre le thème traduisible : utilisez les fonctions __ et _e avec un text domain défini dans style.css pour toutes les chaînes de texte visibles côté utilisateur.

Accessibilité et expérience utilisateur

Un bon thème doit être utilisable par le plus grand nombre, y compris les personnes utilisant des lecteurs d’écran ou naviguant uniquement au clavier.

  • Utilisez une structure de titres logique (

    ,

    , etc.) pour aider les moteurs de recherche et les lecteurs d’écran à comprendre la hiérarchie du contenu.

  • Assurez un contraste suffisant entre le texte et l’arrière-plan.
  • Ajoutez des attributs alt pertinents aux images importantes pour le contenu.
  • Rendez les éléments interactifs accessibles au clavier (menus, boutons, formulaires).
  • Évitez les animations agressives ou difficiles à arrêter.

Sécurité de base dans un thème

Même si la sécurité repose beaucoup sur WordPress lui-même et les plugins, un thème doit respecter certains principes :

  • Échapper toutes les données affichées dans les templates (par exemple esc_html, esc_attr, esc_url) lorsqu’elles proviennent de l’utilisateur ou de la base de données.
  • Vérifier les droits de l’utilisateur (current_user_can) avant d’exécuter des actions sensibles dans l’interface d’administration.
  • Ne jamais stocker d’identifiants, de mots de passe ou de clés API en clair dans les fichiers du thème.

Conseils SEO on-page pour votre thème WordPress

Le thème joue un rôle important dans le référencement naturel WordPress, même si le contenu reste l’élément le plus déterminant.

  • Balises de titre optimisées : laissez WordPress (ou un plugin SEO) gérer la balise </code> via la prise en charge de <code>title-tag</code>. Évitez de la coder en dur.</li> <li><strong>Balises Hn structurées</strong> : un seul <code><h1></code> par page, puis des <code><h2 id="logiques-pour-les-sous-titres-ne-mettez-pas-le-logo-en-sur-toutes-les-pages-si-le-titre-du-contenu-d"></code>, <code><h3></code> logiques pour les sous-titres. Ne mettez pas le logo en <code><h1></code> sur toutes les pages si le titre du contenu doit être le <code><h1></code>.</li> <li><strong>Liens internes clairs</strong> : les menus, les breadcrumbs (fil d’Ariane) et les sections de contenus connexes doivent être bien intégrés dans les templates.</li> <li><strong>Balises méta et données structurées</strong> : laissez la possibilité d’ajouter des metas personnalisées et des données structurées via un plugin SEO sans les bloquer par des surcharges de templates trop rigides.</li> <li><strong>Performances et mobile</strong> : un thème responsive, rapide et léger est mieux classé et offre une meilleure expérience utilisateur.</li> </ul> <h2>Tester et activer votre thème</h2> <h3 id="activer-le-theme-dans-le-tableau-de-bord">Activer le thème dans le tableau de bord</h3> <p>Une fois vos fichiers de base créés (<code>style.css</code>, <code>index.php</code>, <code>header.php</code>, <code>footer.php</code> et <code>functions.php</code>), rendez-vous dans <strong>Apparence > Thèmes</strong> dans l’administration WordPress. Votre thème doit apparaître dans la liste. Cliquez sur « Activer » pour l’utiliser sur votre site.</p> <h3 id="verifier-l-affichage-des-principaux-types-de-contenu">Vérifier l’affichage des principaux types de contenu</h3> <p>Testez les points suivants :</p> <ul> <li>la page d’accueil (articles récents ou page statique selon votre configuration) ;</li> <li>les articles de blog individuels ;</li> <li>les pages statiques ;</li> <li>les archives (catégories, étiquettes) ;</li> <li>les résultats de recherche ;</li> <li>la page 404 ;</li> <li>le menu principal et, le cas échéant, les menus secondaires et le pied de page.</li> </ul> <p>Corrigez progressivement les problèmes d’affichage en ajustant la structure HTML, les classes CSS et les boucles WordPress.</p> <h2 id="aller-plus-loin-blocs-constructeurs-et-fse">Aller plus loin : blocs, constructeurs et FSE</h2> <p>Avec les versions récentes de WordPress, vous pouvez aussi créer des thèmes basés sur les blocs (Full Site Editing), en utilisant des fichiers <code>block-templates</code> et un fichier <code>theme.json</code>. Ces thèmes permettent de modifier l’entière mise en page du site directement dans l’éditeur de site. Vous pouvez également utiliser des constructeurs de thèmes visuels (Elementor, SeedProd, Beaver Themer, etc.) pour générer une grande partie de la structure sans écrire de code, tout en gardant la possibilité de créer ou d’ajuster un thème classique lorsque vous avez besoin d’un contrôle plus fin et de meilleures performances.</p> <h2 id="conclusion">Conclusion</h2> <p>Créer un <strong>thème WordPress</strong> efficace, rapide et personnalisable repose sur une bonne compréhension de la structure des fichiers, de la hiérarchie des templates et des bonnes pratiques de développement. En partant d’un thème classique minimal (avec <code>style.css</code>, <code>index.php</code>, <code>header.php</code>, <code>footer.php</code> et <code>functions.php</code>), puis en ajoutant progressivement des templates spécifiques, un thème enfant pour vos personnalisations et des optimisations de performance, vous obtenez un environnement parfaitement adapté à vos besoins et prêt pour un excellent référencement naturel.</p> </div> <!-- CTA Section --> <div class="mt-12 pt-8 border-t border-gray-200"> <div class="bg-gradient-to-r from-purple-600 to-blue-600 rounded-xl p-8 text-center text-white"> <h3 class="text-2xl font-bold mb-4">Besoin d'aide avec votre SEO ?</h3> <p class="mb-6 text-purple-100">Notre équipe d'experts peut vous aider à optimiser votre site e-commerce</p> <div class="flex flex-col sm:flex-row gap-4 justify-center"> <a href="/seo-ecommerce" class="bg-white text-purple-600 px-8 py-3 rounded-lg font-semibold hover:bg-purple-50 transition inline-block"> Découvrir nos services SEO </a> <a href="/#contact" class="bg-purple-800 text-white px-8 py-3 rounded-lg font-semibold hover:bg-purple-900 transition inline-block"> Nous contacter </a> </div> </div> </div> </article> <!-- Section Commentaires --> <div class="mt-12 max-w-4xl mx-auto bg-white rounded-2xl shadow-xl p-8 md:p-12"> <h2 class="text-3xl font-bold text-gray-900 mb-6 flex items-center gap-3"> <i class="fas fa-comments text-purple-600"></i> Commentaires </h2> <!-- Liste des commentaires approuvés --> <div id="comments-list" class="mb-8 space-y-6"> <!-- Les commentaires approuvés seront chargés ici via JavaScript --> </div> <!-- Formulaire de commentaire --> <div class="border-t border-gray-200 pt-8"> <h3 class="text-xl font-semibold text-gray-900 mb-4">Laisser un commentaire</h3> <form id="comment-form" class="space-y-4"> <input type="hidden" id="article-slug" value="comment-creer-un-theme-wordpress-guide-complet-etape-par-etape"> <div class="grid md:grid-cols-2 gap-4"> <div> <label for="comment-name" class="block text-sm font-medium text-gray-700 mb-2">Nom *</label> <input type="text" id="comment-name" name="name" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent"> </div> <div> <label for="comment-email" class="block text-sm font-medium text-gray-700 mb-2">Email *</label> <input type="email" id="comment-email" name="email" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent"> </div> </div> <div> <label for="comment-message" class="block text-sm font-medium text-gray-700 mb-2">Message *</label> <textarea id="comment-message" name="message" rows="5" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent"></textarea> </div> <div class="text-sm text-gray-600"> <i class="fas fa-info-circle text-purple-600"></i> Votre commentaire sera soumis à modération avant publication. </div> <button type="submit" class="bg-gradient-to-r from-purple-600 to-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:from-purple-700 hover:to-blue-700 transition inline-flex items-center gap-2"> <i class="fas fa-paper-plane"></i> Publier le commentaire </button> </form> <div id="comment-status" class="mt-4 hidden"></div> </div> </div> <!-- Navigation Articles --> <div class="mt-12 max-w-4xl mx-auto"> <a href="/blog" class="inline-flex items-center gap-2 text-purple-600 hover:text-purple-800 transition font-semibold"> <i class="fas fa-arrow-left"></i> Retour au blog </a> </div> </div> </section> <!-- Script pour les commentaires --> <script> const articleSlug = 'comment-creer-un-theme-wordpress-guide-complet-etape-par-etape'; // Charger les commentaires approuvés async function loadComments() { try { const response = await fetch(`/gestion-commentaires/get-comments.php?slug=${articleSlug}`); const data = await response.json(); const commentsList = document.getElementById('comments-list'); if (data.success && data.comments.length > 0) { commentsList.innerHTML = data.comments.map(comment => ` <div class="border-l-4 border-purple-600 pl-4 py-2"> <div class="flex items-center gap-2 mb-2"> <strong class="text-gray-900">${comment.name}</strong> <span class="text-sm text-gray-500">•</span> <span class="text-sm text-gray-500">${new Date(comment.date).toLocaleDateString('fr-FR')}</span> </div> <p class="text-gray-700">${comment.message}</p> </div> `).join(''); } else { commentsList.innerHTML = '<p class="text-gray-500 italic">Aucun commentaire pour le moment. Soyez le premier à commenter !</p>'; } } catch (error) { console.error('Erreur lors du chargement des commentaires:', error); } } // Gérer la soumission du formulaire document.getElementById('comment-form').addEventListener('submit', async function(e) { e.preventDefault(); const formData = { slug: articleSlug, name: document.getElementById('comment-name').value, email: document.getElementById('comment-email').value, message: document.getElementById('comment-message').value }; const submitBtn = this.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Envoi en cours...'; try { const response = await fetch('/gestion-commentaires/submit-comment.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const result = await response.json(); const messageDiv = document.getElementById('comment-status'); if (result.success) { messageDiv.className = 'mt-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg'; messageDiv.textContent = 'Merci ! Votre commentaire a été soumis et sera publié après modération.'; messageDiv.classList.remove('hidden'); this.reset(); } else { messageDiv.className = 'mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg'; messageDiv.textContent = result.message || 'Une erreur est survenue. Veuillez réessayer.'; messageDiv.classList.remove('hidden'); } } catch (error) { const messageDiv = document.getElementById('comment-status'); messageDiv.className = 'mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg'; messageDiv.textContent = 'Erreur de connexion. Veuillez réessayer plus tard.'; messageDiv.classList.remove('hidden'); } finally { submitBtn.disabled = false; submitBtn.innerHTML = originalText; } }); // Charger les commentaires au chargement de la page loadComments(); </script> <footer class="bg-gray-950 text-gray-400 py-12"> <div class="container mx-auto px-6"> <div class="flex flex-col md:flex-row justify-between items-center"> <div class="mb-6 md:mb-0 flex items-start gap-4"> <img src="/images/logo.png" alt="Logo VRAIVEX" class="h-32 w-32 md:h-40 md:w-40 object-contain"> <div> <a href="#" class="text-2xl font-bold block"> <span class="gradient-text">VRAIVEX</span> </a> <p class="mt-2 text-sm">Automatisation, IA et SEO au service de la performance e-commerce</p> </div> </div> <div class="flex flex-col items-center md:items-end"> <div class="grid grid-cols-2 md:flex md:flex-wrap gap-3 md:space-x-6 mb-4 text-center md:text-right"> <a href="/#about" class="hover:text-white transition text-sm">À propos</a> <a href="/#services" class="hover:text-white transition text-sm">Services</a> <a href="/#prestations" class="hover:text-white transition text-sm">Prestations</a> <a href="/#bestsellers" class="hover:text-white transition text-sm">Best Sellers</a> <a href="/#brands" class="hover:text-white transition text-sm">Nos marques</a> <a href="/creation-site-ecommerce" class="hover:text-white transition text-sm">Création Sites</a> <a href="/seo-ecommerce" class="hover:text-white transition text-sm">SEO E-commerce</a> <a href="/partenaires" class="hover:text-white transition text-sm">Partenaires</a> <a href="/#contact" class="hover:text-white transition text-sm">Contact</a> </div> <p class="text-sm text-center md:text-right">© 2025 VRAIVEX. Tous droits réservés.</p> </div> </div> </div> </footer> <!-- Back to Top Button --> <button id="backToTop" class="fixed bottom-8 right-8 bg-gradient-to-r from-purple-600 to-blue-600 text-white p-4 rounded-full shadow-lg hover:shadow-xl transform hover:scale-110 transition-all duration-300 z-50 hidden"> <i class="fas fa-arrow-up text-xl"></i> </button> <script> // Header scroll effect window.addEventListener('scroll', function() { const header = document.getElementById('header'); if (window.scrollY > 100) { header.classList.add('header-scrolled'); } else { header.classList.remove('header-scrolled'); } }); // Smooth scrolling for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); document.querySelector(this.getAttribute('href')).scrollIntoView({ behavior: 'smooth' }); }); }); // Mobile menu toggle const mobileMenuButton = document.getElementById('mobileMenuButton'); const mobileMenu = document.getElementById('mobileMenu'); const menuIcon = document.getElementById('menuIcon'); if (mobileMenuButton && mobileMenu) { mobileMenuButton.addEventListener('click', function() { mobileMenu.classList.toggle('hidden'); // Toggle icon between bars and times if (mobileMenu.classList.contains('hidden')) { menuIcon.classList.remove('fa-times'); menuIcon.classList.add('fa-bars'); } else { menuIcon.classList.remove('fa-bars'); menuIcon.classList.add('fa-times'); } }); // Close menu when clicking on a link const mobileLinks = mobileMenu.querySelectorAll('a'); mobileLinks.forEach(link => { link.addEventListener('click', function() { mobileMenu.classList.add('hidden'); menuIcon.classList.remove('fa-times'); menuIcon.classList.add('fa-bars'); }); }); } // Brands Carousel const brandsCarousel = document.getElementById('brandsCarousel'); const brandsContainer = document.getElementById('brandsContainer'); const brandsPrevBtn = document.getElementById('brandsPrevBtn'); const brandsNextBtn = document.getElementById('brandsNextBtn'); const brandsPrevBtnMobile = document.getElementById('brandsPrevBtnMobile'); const brandsNextBtnMobile = document.getElementById('brandsNextBtnMobile'); if (brandsContainer && brandsCarousel) { let currentIndex = 0; const cards = brandsContainer.querySelectorAll('.brand-card'); const cardsPerView = { mobile: 1, tablet: 2, desktop: 3, large: 4 }; function getCardsPerView() { const width = window.innerWidth; if (width >= 1280) return cardsPerView.large; if (width >= 1024) return cardsPerView.desktop; if (width >= 768) return cardsPerView.tablet; return cardsPerView.mobile; } function updateCarousel() { const cardsPerViewCount = getCardsPerView(); const containerWidth = brandsCarousel.offsetWidth; const cardWidth = containerWidth / cardsPerViewCount; const maxIndex = Math.max(0, cards.length - cardsPerViewCount); currentIndex = Math.min(currentIndex, maxIndex); brandsContainer.style.transform = `translateX(-${currentIndex * cardWidth}px)`; // Update button states const isAtStart = currentIndex === 0; const isAtEnd = currentIndex >= maxIndex; if (brandsPrevBtn) { brandsPrevBtn.style.opacity = isAtStart ? '0.5' : '1'; brandsPrevBtn.style.cursor = isAtStart ? 'not-allowed' : 'pointer'; } if (brandsNextBtn) { brandsNextBtn.style.opacity = isAtEnd ? '0.5' : '1'; brandsNextBtn.style.cursor = isAtEnd ? 'not-allowed' : 'pointer'; } if (brandsPrevBtnMobile) { brandsPrevBtnMobile.style.opacity = isAtStart ? '0.5' : '1'; brandsPrevBtnMobile.style.cursor = isAtStart ? 'not-allowed' : 'pointer'; } if (brandsNextBtnMobile) { brandsNextBtnMobile.style.opacity = isAtEnd ? '0.5' : '1'; brandsNextBtnMobile.style.cursor = isAtEnd ? 'not-allowed' : 'pointer'; } } function nextSlide() { const cardsPerViewCount = getCardsPerView(); const maxIndex = Math.max(0, cards.length - cardsPerViewCount); if (currentIndex < maxIndex) { currentIndex++; updateCarousel(); } } function prevSlide() { if (currentIndex > 0) { currentIndex--; updateCarousel(); } } // Event listeners if (brandsNextBtn) brandsNextBtn.addEventListener('click', nextSlide); if (brandsPrevBtn) brandsPrevBtn.addEventListener('click', prevSlide); if (brandsNextBtnMobile) brandsNextBtnMobile.addEventListener('click', nextSlide); if (brandsPrevBtnMobile) brandsPrevBtnMobile.addEventListener('click', prevSlide); // Set responsive width for cards function setCardWidths() { const cardsPerViewCount = getCardsPerView(); const containerWidth = brandsCarousel.offsetWidth; const gap = 24; // 24px gap const cardWidth = (containerWidth - (gap * (cardsPerViewCount - 1))) / cardsPerViewCount; cards.forEach(card => { card.style.width = `${cardWidth}px`; card.style.flexShrink = '0'; }); } // Initialize setCardWidths(); updateCarousel(); // Update on resize let resizeTimeout; window.addEventListener('resize', function() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(function() { setCardWidths(); currentIndex = 0; updateCarousel(); }, 250); }); } </script> <script> // Header scroll effect window.addEventListener('scroll', function() { const header = document.getElementById('header'); if (window.scrollY > 50) { header.classList.add('header-scrolled'); } else { header.classList.remove('header-scrolled'); } }); // Scroll animations const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, observerOptions); // Observe all fade-in-up elements document.querySelectorAll('.fade-in-up').forEach(el => { observer.observe(el); }); // Smooth scroll for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // Counter animation for stats function animateCounter(element, target, duration = 2000) { let start = 0; const increment = target / (duration / 16); const timer = setInterval(() => { start += increment; if (start >= target) { element.textContent = target + (element.textContent.includes('+') ? '+' : '') + (element.textContent.includes('K') ? 'K€' : ''); clearInterval(timer); } else { element.textContent = Math.floor(start) + (element.textContent.includes('+') ? '+' : '') + (element.textContent.includes('K') ? 'K€' : ''); } }, 16); } // Observe stats section const statsObserver = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting && !entry.target.classList.contains('animated')) { entry.target.classList.add('animated'); const statsCards = entry.target.querySelectorAll('.stats-card'); statsCards.forEach((card, index) => { setTimeout(() => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; setTimeout(() => { card.style.transition = 'all 0.6s ease'; card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }, 100); }, index * 100); }); } }); }, { threshold: 0.3 }); const statsSection = document.querySelector('section.bg-gradient-to-r'); if (statsSection) { statsObserver.observe(statsSection); } </script> <script> // Gestion du formulaire de contact const contactForm = document.getElementById('contact-form'); const formMessage = document.getElementById('form-message'); const submitBtn = document.getElementById('submit-btn'); if (contactForm) { contactForm.addEventListener('submit', async function(e) { e.preventDefault(); // Désactiver le bouton pendant l'envoi submitBtn.disabled = true; submitBtn.textContent = 'Envoi en cours...'; // Récupérer les données du formulaire const formData = new FormData(contactForm); try { // Vérifier que les données sont bien dans le FormData const formDataObj = { name: formData.get('name'), email: formData.get('email'), subject: formData.get('subject'), message: formData.get('message') }; console.log('Données du formulaire:', formDataObj); // Vérifier que tous les champs sont remplis if (!formDataObj.name || !formDataObj.email || !formDataObj.subject || !formDataObj.message) { formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; formMessage.textContent = 'Veuillez remplir tous les champs du formulaire.'; submitBtn.disabled = false; submitBtn.textContent = 'Envoyer le message'; return; } // Essayer d'abord avec JSON (plus fiable) // Si ça ne fonctionne pas, on essaiera avec FormData let response; try { // Méthode 1 : Envoyer en JSON (plus fiable selon les forums) response = await fetch('gestion-formulaire-contact/send-email-json.php', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formDataObj) }); } catch (jsonError) { console.warn('Erreur avec JSON, essai avec FormData:', jsonError); // Méthode 2 : Fallback avec FormData response = await fetch('send-email.php', { method: 'POST', body: formData }); } // Lire le texte de la réponse d'abord pour déboguer const responseText = await response.text(); console.log('Réponse serveur:', responseText.substring(0, 500)); // Vérifier si la réponse est OK if (!response.ok) { // Essayer de parser le JSON d'erreur try { const errorResult = JSON.parse(responseText); // Afficher le message d'erreur du serveur directement à l'utilisateur formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; formMessage.textContent = errorResult.message || `Erreur ${response.status}: ${response.statusText}`; submitBtn.disabled = false; submitBtn.textContent = 'Envoyer le message'; return; // Sortir de la fonction pour ne pas continuer } catch (e) { throw new Error(`Erreur HTTP ${response.status}: ${response.statusText}. Réponse: ${responseText.substring(0, 200)}`); } } // Vérifier si PHP n'est pas exécuté (le serveur renvoie le code PHP brut) if (responseText.trim().startsWith('<?php') || responseText.includes('<?php')) { console.error('ERREUR: PHP n\'est pas exécuté par le serveur. Le code PHP est renvoyé brut.'); console.error('Le serveur web n\'est pas configuré pour exécuter PHP.'); // Utiliser la solution de secours : sauvegarder dans localStorage const messageData = { name: formData.get('name'), email: formData.get('email'), subject: formData.get('subject'), message: formData.get('message'), timestamp: new Date().toISOString() }; // Sauvegarder dans localStorage comme solution de secours const savedMessages = JSON.parse(localStorage.getItem('vraivex_messages') || '[]'); savedMessages.push(messageData); localStorage.setItem('vraivex_messages', JSON.stringify(savedMessages)); // Afficher un message spécial formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-yellow-600 text-white'; formMessage.innerHTML = '⚠️ PHP n\'est pas configuré sur le serveur. Votre message a été sauvegardé localement. <br>Veuillez nous contacter directement à <strong>contact@vraivex.fr</strong> ou consulter les messages sauvegardés dans la console du navigateur.'; // Afficher les messages sauvegardés dans la console console.log('Messages sauvegardés localement:', savedMessages); console.log('Pour consulter les messages, tapez dans la console: JSON.parse(localStorage.getItem("vraivex_messages"))'); return; // Sortir de la fonction } // Essayer de parser le JSON let result; try { result = JSON.parse(responseText); } catch (parseError) { console.error('Erreur de parsing JSON:', parseError); console.error('Réponse reçue:', responseText.substring(0, 500)); throw new Error('Le serveur a renvoyé une réponse invalide. Vérifiez la console pour plus de détails.'); } // Afficher le message de résultat formMessage.classList.remove('hidden'); if (result.success) { formMessage.className = 'mb-6 p-4 rounded-lg bg-green-600 text-white'; formMessage.textContent = 'Message envoyé avec succès ! Nous vous répondrons dans les plus brefs délais.'; contactForm.reset(); } else { formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; let errorMsg = result.message || 'Une erreur est survenue. Veuillez réessayer.'; // Afficher le message de debug en développement (à retirer en production) if (result.debug) { console.error('Erreur détaillée:', result.debug); } formMessage.textContent = errorMsg; } } catch (error) { formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; // Message d'erreur plus détaillé pour le débogage let errorMsg = 'Une erreur est survenue lors de la communication avec le serveur. '; errorMsg += 'Veuillez réessayer plus tard ou nous contacter directement à contact@vraivex.fr'; // En mode développement, afficher plus de détails if (error.message) { console.error('Erreur détaillée:', error); console.error('Message:', error.message); console.error('Stack:', error.stack); } formMessage.textContent = errorMsg; } finally { // Réactiver le bouton submitBtn.disabled = false; submitBtn.textContent = 'Envoyer le message'; // Masquer le message après 5 secondes setTimeout(() => { formMessage.classList.add('hidden'); }, 5000); } }); } // Back to Top Button functionality const backToTopButton = document.getElementById('backToTop'); // Show/hide button based on scroll position window.addEventListener('scroll', () => { if (window.pageYOffset > 300) { backToTopButton.classList.remove('hidden'); } else { backToTopButton.classList.add('hidden'); } }); // Smooth scroll to top when clicked backToTopButton.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); </script> </body> </html>