Que dit Google sur le SEO ? /
Quiz SEO Express

Testez vos connaissances SEO en 3 questions

Moins de 30 secondes. Decouvrez ce que vous savez vraiment sur le referencement Google.

🕒 ~30s 🎯 3 questions 📚 SEO Google

Declaration officielle

Les données du Web Almanac montrant des différences entre HTML brut et HTML rendu sont des observations factuelles, pas des jugements de valeur. Ces différences ne constituent pas un problème en soi pour Google Search, qui fonctionne bien avec JavaScript.
🎥 Vidéo source

Extrait d'une vidéo Google Search Central

💬 EN 📅 26/04/2021 ✂ 26 déclarations
Voir sur YouTube →
Autres déclarations de cette vidéo 25
  1. Les liens JavaScript retardent-ils vraiment la découverte par Google ?
  2. Pourquoi Google ignore-t-il vos balises canoniques quand le HTML brut contredit le rendu ?
  3. Le noindex en HTML brut empêche-t-il définitivement le rendu JavaScript par Google ?
  4. JavaScript et SEO : peut-on vraiment modifier title, meta et liens côté client sans risque ?
  5. Le JavaScript côté client est-il vraiment un frein pour vos performances SEO ?
  6. Google AdSense pénalise-t-il vraiment la vitesse de votre site comme n'importe quel script tiers ?
  7. Faut-il s'inquiéter des erreurs 'other error' sur les images dans la Search Console ?
  8. User agent ou viewport : quelle détection privilégier pour vos versions mobiles séparées ?
  9. Les liens de navigation JavaScript affectent-ils vraiment le référencement de votre site ?
  10. Peut-on vraiment perdre le contrôle de sa canonical en laissant l'attribut href vide au chargement ?
  11. Quel crawler Google utilise vraiment ses outils de test SEO ?
  12. Les données structurées de votre version mobile s'appliquent-elles aussi au desktop ?
  13. Faut-il vraiment arrêter de craindre le JavaScript pour le SEO ?
  14. Les liens JavaScript retardent-ils vraiment la découverte par Google ?
  15. Pourquoi une balise canonical différente entre HTML brut et rendu peut-elle ruiner votre stratégie de canonicalisation ?
  16. Peut-on vraiment retirer un noindex via JavaScript sans risquer la désindexation ?
  17. Peut-on vraiment modifier les balises meta et les liens en JavaScript sans risque SEO ?
  18. Les produits Google bénéficient-ils d'un avantage SEO caché dans les résultats de recherche ?
  19. Faut-il s'inquiéter des erreurs 'other' dans l'outil d'inspection d'URL ?
  20. Google ignore-t-il vraiment vos images lors du rendu pour la recherche web ?
  21. User agent ou viewport : Google fait-il vraiment la différence pour l'indexation mobile ?
  22. Les liens générés en JavaScript transmettent-ils vraiment les signaux de ranking comme les liens HTML classiques ?
  23. Une balise canonical vide en HTML peut-elle forcer Google à auto-canonicaliser votre page par erreur ?
  24. Le Mobile-Friendly Test peut-il remplacer l'URL Inspection Tool pour auditer le crawl mobile ?
  25. Pourquoi Google ignore-t-il vos données structurées desktop après le mobile-first indexing ?
📅
Declaration officielle du (il y a 5 ans)
TL;DR

Martin Splitt rappelle que les écarts entre HTML brut et rendu ne sont que des observations factuelles, pas des signaux d'alarme. Google Search gère JavaScript sans souci majeur selon lui. Reste à vérifier si cette déclaration tient face aux audits terrain où les sites JS lourds peinent souvent à indexer correctement.

Ce qu'il faut comprendre

D'où vient cette clarification de Google ?

Le Web Almanac — projet HTTP Archive qui cartographie l'état du web — publie chaque année des données comparant le HTML initial (ce que reçoit le crawler en première requête) et le HTML rendu (ce qui apparaît après exécution JavaScript). Les écarts peuvent être massifs : titres absents, contenus vides, structures modifiées.

Face à ces chiffres, certains SEO ont interprété ces différences comme un signal d'alerte. Martin Splitt intervient pour recadrer : ces observations sont factuelles, elles ne portent aucun jugement de valeur. Google ne sanctionne pas un site parce qu'il génère du contenu côté client.

Que signifie concrètement « observation sans jugement » ?

Splitt distingue constat technique et problème SEO. Oui, il existe des différences. Non, elles ne constituent pas automatiquement un handicap pour le référencement. Google affirme que son moteur de rendu fonctionne bien avec JavaScript — ce qui ne veut pas dire qu'il fonctionne parfaitement, ni instantanément.

Le problème c'est que cette formulation reste floue. « Fonctionne bien » ne dit rien sur les délais d'indexation, la consommation de crawl budget, ou la capacité à interpréter des frameworks complexes. C'est là que ça coince : entre « techniquement capable » et « optimal pour le ranking », il y a un fossé.

Pourquoi cette déclaration maintenant ?

Google observe probablement une confusion croissante entre les SEO qui extrapolent les données du Web Almanac. Si les chiffres montrent que 30% des sites modifient leur via JS, certains en déduisent que c'est problématique. Splitt coupe court : ce n'est pas un bug, c'est un choix d'implémentation.</p><p>Mais attention — dire que ce n'est pas un problème <em>en soi</em> n'équivaut pas à dire que c'est une <strong>bonne pratique</strong>. Google peut indexer JavaScript tout en préférant le HTML statique pour des raisons de vitesse, de fiabilité, de cohérence.</p><ul><li>Les différences HTML brut/rendu sont des <strong>observations factuelles</strong>, pas des défauts à corriger systématiquement</li><li>Google affirme gérer JavaScript correctement — sans garantir que c'est optimal pour le SEO</li><li>« Pas un problème pour Google Search » ne signifie pas « recommandé pour performer en SERP »</li><li>Cette déclaration vise à <strong>désamorcer la panique</strong> autour des données du Web Almanac</li><li>Reste à distinguer ce qui est techniquement possible de ce qui est stratégiquement judicieux</li></ul></div> </div> <div class="section-card ai-section"> <h2>Avis d'un expert SEO</h2> <div class="content" data-field="ai_expert_opinion"><h3>Cette rassurance tient-elle face aux observations terrain ?</h3><p>Soyons honnêtes — si Google gère si bien JavaScript, pourquoi voit-on encore régulièrement des contenus <strong>générés côté client non indexés</strong> ? Pourquoi des sites en frameworks JS mettent-ils des semaines à indexer des pages que Googlebot a déjà crawlées ? La réalité c'est qu'il existe un <strong>delta temporel</strong> incompressible entre le crawl et le rendu.</p><p>Splitt a techniquement raison : ce n'est pas un bug, c'est un processus. Mais pour un SEO, un délai d'indexation de 3 semaines sur une catégorie e-commerce, c'est un <strong>problème business</strong> réel. Dire « ça fonctionne bien » occulte les nuances de performance, de priorité de crawl, de ressources serveur allouées au rendering.</p><h3>Où se situent les vrais risques avec le JavaScript ?</h3><p>Le risque n'est pas que Google <em>ne puisse pas</em> indexer — il peut. Le risque c'est qu'il le fasse <strong>plus lentement</strong>, avec moins de garanties, ou en consommant trop de crawl budget sur des sites à forte volumétrie. Les frameworks qui modifient les <title>, les meta descriptions ou le contenu principal après coup introduisent une <strong>latence structurelle</strong>.</p><p>Autre point rarement évoqué : les erreurs JS silencieuses. Une exception non catchée, une dépendance qui échoue, un script tiers qui bloque — et tout le contenu disparaît du DOM rendu. Google n'indexe alors <strong>rien</strong>. Ce n'est pas un jugement de valeur, c'est un échec technique que le HTML statique évite par conception.</p><p><strong>[A vérifier]</strong> : Google affirme que « ça fonctionne bien », mais aucune métrique publique ne définit ce seuil. Qu'est-ce qu'un délai acceptable ? 24h ? 7 jours ? 30 jours ? Sans SLA officiel, on navigue à vue.</p><h3>Faut-il en conclure que le HTML statique n'a plus d'avantage ?</h3><p>Non. Le HTML statique reste la <strong>méthode la plus fiable</strong> pour garantir indexation immédiate, cohérence entre crawl et rendu, et maîtrise totale du contenu servi. Les sites à forte volumétrie (actualité, e-commerce, petites annonces) ont tout intérêt à privilégier le SSR ou la génération statique.</p><p>Le vrai message de Splitt c'est : « Ne paniquez pas si vous utilisez JS, on sait gérer. » Mais ça ne signifie pas « Migrez tout en client-side rendering sans conséquence. » La nuance est capitale. Entre « techniquement possible » et « stratégiquement optimal », il faut choisir en connaissance de cause.</p><div class="alert">Si votre site génère massivement du contenu via JavaScript (SPA, React sans SSR, frameworks client-only) et que vous constatez des délais d'indexation anormaux, le problème n'est pas une sanction Google — c'est une contrainte architecturale. Pas de jugement de valeur, juste une limite technique à anticiper.</div></div> </div> <div class="section-card ai-section"> <h2>Impact pratique et recommandations</h2> <div class="content" data-field="ai_practical_impact"><h3>Comment vérifier si mon site subit un écart HTML brut/rendu problématique ?</h3><p>Commence par comparer ce que Googlebot reçoit initialement avec ce qu'il indexe après rendering. Utilise l'<strong>outil d'inspection d'URL</strong> dans Search Console : onglet « HTML » pour le brut, onglet « Capture d'écran » et « Autres infos » pour le rendu. Si ton <title>, tes headings ou ton contenu principal apparaissent uniquement après JS, tu es en territoire à risque.</p><p>Ensuite, mesure les <strong>délais d'indexation</strong>. Publie une page, soumets-la via Search Console, et chronomètre le temps jusqu'à apparition dans l'index. Si ça dépasse 48-72h alors que le contenu est crawlé, c'est un symptôme de rendering en file d'attente. Pas catastrophique, mais sous-optimal.</p><h3>Quelles actions concrètes si j'utilise JavaScript pour le contenu critique ?</h3><p>Si tu es en <strong>SPA</strong> (React, Vue, Angular sans SSR), passe au <strong>server-side rendering</strong> ou à la génération statique (Next.js, Nuxt, etc.). Le delta de performance SEO est mesurable : indexation quasi instantanée, cohérence garantie, zéro dépendance aux ressources de rendering Google.</p><p>Si la migration complète est impossible, applique un <strong>rendering hybride</strong> : HTML statique pour le contenu critique (title, headings, premier paragraphe), JavaScript pour les interactions secondaires (filtres, animations, widgets). Google indexe le socle immédiatement, le reste peut attendre.</p><h3>Quand faut-il vraiment s'inquiéter des différences brut/rendu ?</h3><p>Les cas à surveiller : <strong>e-commerce</strong> avec catalogues générés côté client (risque de non-indexation des fiches produit), <strong>actualité</strong> où la fraîcheur compte (un article invisible 48h perd son trafic), <strong>sites à forte volumétrie</strong> où chaque page en attente de rendering consomme du crawl budget.</p><p>En revanche, si tu gères un site vitrine de 20 pages avec quelques animations JS, l'écart brut/rendu n'impactera probablement rien. Le risque est <strong>proportionnel au volume</strong> et à la criticité temporelle du contenu.</p><ul class="checklist"><li>Auditer les écarts HTML brut/rendu via l'outil d'inspection Search Console</li><li>Mesurer les délais d'indexation réels sur des pages fraîchement publiées</li><li>Privilégier SSR ou génération statique pour le contenu critique (title, headings, body)</li><li>Tester la résilience : désactiver JavaScript et vérifier que le contenu essentiel reste accessible</li><li>Surveiller les logs serveur pour identifier les requêtes de Googlebot bloquées par des erreurs JS</li><li>Documenter les choix techniques pour arbitrer entre rapidité de développement et performance SEO</li></ul><div class="summary">Google gère JavaScript — personne ne dit le contraire. Mais gérer ne signifie pas optimiser. Si ton site repose massivement sur du rendering client-side, tu introduis une latence structurelle que le HTML statique évite. Ces optimisations techniques peuvent vite devenir complexes à orchestrer, surtout sur des architectures existantes. Si tu veux arbitrer sereinement entre impératifs dev et exigences SEO, l'accompagnement d'une agence SEO spécialisée peut clarifier les priorités et sécuriser les migrations.</div></div> </div> <!-- FAQ --> <div class="section-card"> <h2>❓ Questions frequentes</h2> <div class="faq-list"> <details class="faq-item"> <summary class="faq-question">Google pénalise-t-il les sites qui génèrent du contenu via JavaScript ?</summary> <div class="faq-answer">Non, il n'y a pas de pénalité. Google peut indexer du contenu généré côté client, mais avec un délai potentiel lié au rendering. Ce n'est pas une sanction, c'est une contrainte technique.</div> </details> <details class="faq-item"> <summary class="faq-question">Un écart entre HTML brut et rendu nuit-il au référencement ?</summary> <div class="faq-answer">Pas systématiquement. Si le contenu critique (title, headings, texte principal) apparaît uniquement après JS, l'indexation peut être retardée. Sur des sites à forte volumétrie ou actualité, ce délai peut impacter le trafic.</div> </details> <details class="faq-item"> <summary class="faq-question">Faut-il abandonner les frameworks JavaScript pour le SEO ?</summary> <div class="faq-answer">Non, mais privilégie le server-side rendering (SSR) ou la génération statique pour le contenu critique. Les SPA sans SSR introduisent une latence d'indexation évitable.</div> </details> <details class="faq-item"> <summary class="faq-question">Comment vérifier que Googlebot voit le même contenu que mes utilisateurs ?</summary> <div class="faq-answer">Utilise l'outil d'inspection d'URL dans Search Console. Compare l'onglet HTML (brut) et la capture d'écran (rendu). Si des éléments clés manquent dans le HTML brut, c'est un signal à surveiller.</div> </details> <details class="faq-item"> <summary class="faq-question">Le crawl budget est-il affecté par le rendering JavaScript ?</summary> <div class="faq-answer">Potentiellement oui. Googlebot doit crawler la page, puis la mettre en file d'attente pour rendering. Sur des sites volumétriques, cela peut ralentir la découverte de nouvelles URLs et consommer plus de ressources.</div> </details> </div> </div> <!-- Tags SEO --> <div class="seo-tags-block"> <span class="seo-tags-label">🏷 Sujets associes</span> <div class="seo-tags"> <a href="/?q=JavaScript+SEO" class="seo-tag-pill">JavaScript SEO</a> <a href="/?q=HTML+rendu" class="seo-tag-pill">HTML rendu</a> <a href="/?q=crawl+budget" class="seo-tag-pill">crawl budget</a> <a href="/?q=indexation" class="seo-tag-pill">indexation</a> <a href="/?q=server-side+rendering" class="seo-tag-pill">server-side rendering</a> <a href="/?q=Googlebot" class="seo-tag-pill">Googlebot</a> <a href="/?q=frameworks+JS" class="seo-tag-pill">frameworks JS</a> <a href="/?q=rendering" class="seo-tag-pill">rendering</a> </div> </div> <!-- Categories --> <div class="declaration-tags"> <a href="/c/javascript-technique" class="tag" style="background: #eab30820; color: #eab308;">JavaScript & Technique</a> </div> <!-- Syntheses thematiques liees --> <!-- Autres declarations de la meme video YouTube --> <div class="section-card yt-same-video"> <h2> 🎥 De la même vidéo <span class="yt-same-count">25</span> </h2> <p class="yt-same-intro"> Autres enseignements SEO extraits de cette même vidéo Google Search Central · publiée le 26/04/2021 </p> <div class="related-grid yt-same-grid"> <a href="/d/javascript-et-decouverte-des-liens-delai-possible-mais-pas-bloquant" class="related-item yt-same-item"> <div class="title">Les liens JavaScript retardent-ils vraiment la découverte par Google ?</div> <div class="meta"> </div> </a> <a href="/d/balises-canoniques-contradictoires-entre-html-brut-et-rendu-signal-affaibli" class="related-item yt-same-item"> <div class="title">Pourquoi Google ignore-t-il vos balises canoniques quand le HTML brut contredit le rendu ?</div> <div class="meta"> </div> </a> <a href="/d/meta-robots-noindex-en-html-brut-bloque-le-rendu-javascript" class="related-item yt-same-item"> <div class="title">Le noindex en HTML brut empêche-t-il définitivement le rendu JavaScript par Google ?</div> <div class="meta"> </div> </a> <a href="/d/modification-de-title-meta-description-et-liens-via-javascript-acceptable" class="related-item yt-same-item"> <div class="title">JavaScript et SEO : peut-on vraiment modifier title, meta et liens côté client sans risque ?</div> <div class="meta"> </div> </a> <a href="/d/javascript-cote-client-impact-sur-les-performances-a-considerer" class="related-item yt-same-item"> <div class="title">Le JavaScript côté client est-il vraiment un frein pour vos performances SEO ?</div> <div class="meta"> </div> </a> <a href="/d/produits-google-soumis-aux-memes-criteres-seo-que-les-autres" class="related-item yt-same-item"> <div class="title">Google AdSense pénalise-t-il vraiment la vitesse de votre site comme n'importe quel script tiers ?</div> <div class="meta"> </div> </a> <a href="/d/erreurs-other-error-pour-images-et-ressources-non-essentielles-normal" class="related-item yt-same-item"> <div class="title">Faut-il s'inquiéter des erreurs 'other error' sur les images dans la Search Console ?</div> <div class="meta"> </div> </a> <a href="/d/user-agent-vs-viewport-pour-versions-mobiles-separees" class="related-item yt-same-item"> <div class="title">User agent ou viewport : quelle détection privilégier pour vos versions mobiles séparées ?</div> <div class="meta"> </div> </a> <a href="/d/liens-de-navigation-principale-generes-en-javascript-aucun-probleme-de-ranking" class="related-item yt-same-item"> <div class="title">Les liens de navigation JavaScript affectent-ils vraiment le référencement de votre site ?</div> <div class="meta"> </div> </a> <a href="/d/balise-canonical-vide-puis-remplie-via-javascript-risque-d-auto-canonicalisation" class="related-item yt-same-item"> <div class="title">Peut-on vraiment perdre le contrôle de sa canonical en laissant l'attribut href vide au chargement ?</div> <div class="meta"> </div> </a> <a href="/d/mobile-friendly-test-utilise-toujours-le-crawler-mobile" class="related-item yt-same-item"> <div class="title">Quel crawler Google utilise vraiment ses outils de test SEO ?</div> <div class="meta"> </div> </a> <a href="/d/structured-data-interpretee-selon-le-crawler-utilise-mobile-ou-desktop" class="related-item yt-same-item"> <div class="title">Les données structurées de votre version mobile s'appliquent-elles aussi au desktop ?</div> <div class="meta"> </div> </a> <a href="/d/javascript-et-changement-de-contenu-pas-de-probleme-general" class="related-item yt-same-item"> <div class="title">Faut-il vraiment arrêter de craindre le JavaScript pour le SEO ?</div> <div class="meta"> </div> </a> <a href="/d/liens-en-javascript-decouverte-legerement-retardee" class="related-item yt-same-item"> <div class="title">Les liens JavaScript retardent-ils vraiment la découverte par Google ?</div> <div class="meta"> </div> </a> <a href="/d/canonical-contradictoires-entre-html-brut-et-rendu-signal-affaibli" class="related-item yt-same-item"> <div class="title">Pourquoi une balise canonical différente entre HTML brut et rendu peut-elle ruiner votre stratégie de canonicalisation ?</div> <div class="meta"> </div> </a> <a href="/d/meta-robots-noindex-en-javascript-sens-de-modification-important" class="related-item yt-same-item"> <div class="title">Peut-on vraiment retirer un noindex via JavaScript sans risquer la désindexation ?</div> <div class="meta"> </div> </a> <a href="/d/modification-de-meta-tags-par-javascript-generalement-acceptable" class="related-item yt-same-item"> <div class="title">Peut-on vraiment modifier les balises meta et les liens en JavaScript sans risque SEO ?</div> <div class="meta"> </div> </a> <a href="/d/produits-google-sans-traitement-preferentiel-pour-le-seo" class="related-item yt-same-item"> <div class="title">Les produits Google bénéficient-ils d'un avantage SEO caché dans les résultats de recherche ?</div> <div class="meta"> </div> </a> <a href="/d/erreurs-other-sur-les-ressources-souvent-sans-importance" class="related-item yt-same-item"> <div class="title">Faut-il s'inquiéter des erreurs 'other' dans l'outil d'inspection d'URL ?</div> <div class="meta"> </div> </a> <a href="/d/images-non-recuperees-lors-du-rendu-principal" class="related-item yt-same-item"> <div class="title">Google ignore-t-il vraiment vos images lors du rendu pour la recherche web ?</div> <div class="meta"> </div> </a> <a href="/d/user-agent-vs-viewport-pour-servir-mobile-desktop" class="related-item yt-same-item"> <div class="title">User agent ou viewport : Google fait-il vraiment la différence pour l'indexation mobile ?</div> <div class="meta"> </div> </a> <a href="/d/liens-ajoutes-en-javascript-aucun-impact-sur-le-passage-de-signaux" class="related-item yt-same-item"> <div class="title">Les liens générés en JavaScript transmettent-ils vraiment les signaux de ranking comme les liens HTML classiques ?</div> <div class="meta"> </div> </a> <a href="/d/canonical-tag-vide-risque-d-auto-canonicalisation" class="related-item yt-same-item"> <div class="title">Une balise canonical vide en HTML peut-elle forcer Google à auto-canonicaliser votre page par erreur ?</div> <div class="meta"> </div> </a> <a href="/d/mobile-friendly-test-pour-verifier-le-crawl-mobile" class="related-item yt-same-item"> <div class="title">Le Mobile-Friendly Test peut-il remplacer l'URL Inspection Tool pour auditer le crawl mobile ?</div> <div class="meta"> </div> </a> <a href="/d/structured-data-basee-sur-la-version-mobile-apres-mfi" class="related-item yt-same-item"> <div class="title">Pourquoi Google ignore-t-il vos données structurées desktop après le mobile-first indexing ?</div> <div class="meta"> </div> </a> </div> <a href="https://www.youtube.com/watch?v=0rfKsusOwj0" target="_blank" rel="noopener" class="yt-same-source-link"> 🎥 Voir la vidéo complète sur YouTube → </a> </div> <!-- Declarations similaires --> <div class="section-card"> <h2>Declarations similaires</h2> <div class="related-grid"> <a href="/d/usage-de-custom-javascript-metrics" class="related-item"> <div class="title">Google utilise-t-il des scripts JavaScript personnalisés pour évaluer vos pages ?</div> <div class="meta"> Martin Splitt · 23/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/importancia-d-http-archive-pour-le-seo" class="related-item"> <div class="title">HTTP Archive : Google révèle-t-il enfin comment il analyse vraiment vos pages ?</div> <div class="meta"> Gary Illyes · 23/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/attention-aux-domaines-dont-les-noms-sont-trop-proches" class="related-item"> <div class="title">Un nom de domaine proche d'un concurrent peut-il nuire à votre référencement ?</div> <div class="meta"> John Mueller · 21/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/google-peut-ignorer-les-liens-depuis-les-sites-qui-spamment" class="related-item"> <div class="title">Google peut-il vraiment ignorer tous les liens d'un site spammeur ?</div> <div class="meta"> John Mueller · 21/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/les-versions-markdown-ne-servent-pas-a-grand-chose-d-un-point-de-vue-seo" class="related-item"> <div class="title">Faut-il proposer des versions Markdown de vos contenus pour booster votre visibilité dans les résultats IA ?</div> <div class="meta"> John Mueller · 21/04/2026 · <span class="stars">★★</span> </div> </a> <a href="/d/impact-de-la-vitesse-sur-la-conversion" class="related-item"> <div class="title">La vitesse d'un site impacte-t-elle vraiment la conversion ?</div> <div class="meta"> Martin Splitt · 30/03/2026 · <span class="stars">★★</span> </div> </a> </div> </div> <!-- Prev/Next --> <div class="prev-next"> <a href="/d/structured-data-basee-sur-la-version-mobile-apres-mfi"> <div class="nav-label">« Precedent</div> <div class="nav-title">Structured data basée sur la version mobile après ...</div> </a> <a href="/d/balise" style="text-align: right;"> <div class="nav-label">Suivant »</div> <div class="nav-title">Balise...</div> </a> </div> <div class="back-link"> <a href="/" class="btn">« Retour aux resultats</a> </div> </div> <style> .comments-section { max-width: 860px; margin: 40px auto; padding: 0 20px; } .comments-title { font-size: 1.3rem; font-weight: 700; color: #1e293b; margin: 0 0 20px; padding-bottom: 12px; border-bottom: 2px solid #f1f5f9; } .comments-count { font-weight: 400; font-size: .9rem; color: #94a3b8; } .comments-empty { color: #94a3b8; font-style: italic; margin-bottom: 24px; } .comments-list { margin-bottom: 28px; } .comment-item { padding: 16px 0; border-bottom: 1px solid #f1f5f9; } .comment-item:last-child { border-bottom: none; } .comment-pending { background: #fffbeb; border-left: 3px solid #f59e0b; padding-left: 14px; border-radius: 6px; margin: 4px 0; } .comment-pending-badge { display: inline-block; font-size: .7rem; font-weight: 600; color: #92400e; background: #fef3c7; padding: 1px 8px; border-radius: 10px; margin-left: 8px; } .comment-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } .comment-author { font-weight: 600; font-size: .95rem; color: #1e293b; } .comment-date { font-size: .8rem; color: #94a3b8; } .comment-body { font-size: .93rem; line-height: 1.6; color: #475569; } .comment-form { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 24px; position: relative; } .comment-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; } .comment-form-field { margin-bottom: 12px; } .comment-form-field label { display: block; font-size: .85rem; font-weight: 600; color: #475569; margin-bottom: 4px; } .comment-form-field input, .comment-form-field textarea { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; font-size: .9rem; font-family: inherit; background: #fff; transition: border-color .2s; box-sizing: border-box; } .comment-form-field input:focus, .comment-form-field textarea:focus { outline: none; border-color: #1a73e8; box-shadow: 0 0 0 3px rgba(26, 115, 232, .1); } .comment-form-field textarea { resize: vertical; min-height: 100px; } .comment-form-charcount { font-size: .75rem; color: #94a3b8; text-align: right; margin-top: 4px; } .comment-form-footer { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; } .comment-submit-btn { padding: 10px 24px; background: #1a73e8; color: #fff; border: none; border-radius: 8px; font-size: .9rem; font-weight: 600; cursor: pointer; transition: opacity .2s; } .comment-submit-btn:hover { opacity: .88; } .comment-submit-btn:disabled { opacity: .5; cursor: default; } .comment-form-notice { font-size: .8rem; color: #94a3b8; font-style: italic; } .comment-feedback { margin-top: 12px; padding: 10px 14px; border-radius: 8px; font-size: .9rem; font-weight: 500; } .comment-feedback-success { background: #d1fae5; color: #065f46; } .comment-feedback-error { background: #fee2e2; color: #991b1b; } @media (max-width: 768px) { .comment-form-row { grid-template-columns: 1fr; gap: 0; } .comments-section { padding: 0 16px; } } </style> <section class="comments-section" id="comments"> <h3 class="comments-title">💬 Commentaires <span class="comments-count">(0)</span></h3> <div class="comments-list" id="comments-list"> <p class="comments-empty" id="comments-empty">Soyez le premier à commenter.</p> </div> <form class="comment-form" id="comment-form" novalidate> <input type="hidden" name="page_type" value="declaration"> <input type="hidden" name="page_id" value="5366"> <input type="hidden" name="lang" value="fr"> <!-- Honeypot anti-spam --> <div style="position:absolute;left:-9999px;" aria-hidden="true"> <label for="website">Do not fill this field</label> <input type="text" id="website" name="website" tabindex="-1" autocomplete="off"> </div> <div class="comment-form-row"> <div class="comment-form-field"> <label for="comment-name">Nom ou pseudo *</label> <input type="text" id="comment-name" name="author_name" required maxlength="100" autocomplete="name"> </div> <div class="comment-form-field"> <label for="comment-email">Email (facultatif, non publié)</label> <input type="email" id="comment-email" name="author_email" maxlength="255" autocomplete="email"> </div> </div> <div class="comment-form-field"> <label for="comment-content">Votre commentaire *</label> <textarea id="comment-content" name="content" required maxlength="2000" rows="4"></textarea> <div class="comment-form-charcount"><span id="comment-chars">2000</span> caractères restants</div> </div> <div class="comment-form-footer"> <button type="submit" class="comment-submit-btn">Publier le commentaire</button> <span class="comment-form-notice">Les commentaires sont modérés avant publication.</span> </div> <div id="comment-feedback" class="comment-feedback" style="display:none;"></div> </form> </section> <script> (function() { var form = document.getElementById('comment-form'); if (!form) return; var textarea = document.getElementById('comment-content'); var charsEl = document.getElementById('comment-chars'); var feedback = document.getElementById('comment-feedback'); var API_URL = 'https://seoclaims.com/api/comments.php'; // Compteur de caracteres textarea.addEventListener('input', function() { var remaining = 2000 - this.value.length; charsEl.textContent = remaining; charsEl.style.color = remaining < 100 ? '#dc2626' : ''; }); form.addEventListener('submit', function(e) { e.preventDefault(); var btn = form.querySelector('.comment-submit-btn'); btn.disabled = true; var origText = btn.textContent; btn.textContent = '...'; feedback.style.display = 'none'; var data = { action: 'submit', page_type: form.querySelector('[name="page_type"]').value, page_id: parseInt(form.querySelector('[name="page_id"]').value), author_name: form.querySelector('[name="author_name"]').value.trim(), author_email: form.querySelector('[name="author_email"]').value.trim(), content: form.querySelector('[name="content"]').value.trim(), lang: form.querySelector('[name="lang"]').value, website: form.querySelector('[name="website"]').value }; fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) .then(function(r) { return r.json(); }) .then(function(res) { if (res.success) { feedback.className = 'comment-feedback comment-feedback-success'; feedback.textContent = res.message; feedback.style.display = 'block'; // Inserer le commentaire en ligne (en attente de moderation) var list = document.getElementById('comments-list'); var empty = document.getElementById('comments-empty'); if (empty) empty.remove(); var pendingLabel = 'En attente de moderation'; var now = new Date(); var day = String(now.getDate()).padStart(2,'0'); var month = String(now.getMonth()+1).padStart(2,'0'); var dateStr = day + '/' + month + '/' + now.getFullYear(); var div = document.createElement('div'); div.className = 'comment-item comment-pending'; div.innerHTML = '<div class="comment-meta">' + '<span class="comment-author">' + data.author_name.replace(/</g,'<') + '</span>' + '<span class="comment-date">' + dateStr + '</span>' + '<span class="comment-pending-badge">' + pendingLabel + '</span>' + '</div>' + '<div class="comment-body">' + data.content.replace(/</g,'<').replace(/\n/g,'<br>') + '</div>'; list.appendChild(div); form.querySelector('[name="content"]').value = ''; form.querySelector('[name="author_name"]').value = ''; form.querySelector('[name="author_email"]').value = ''; charsEl.textContent = '2000'; } else { feedback.className = 'comment-feedback comment-feedback-error'; feedback.textContent = res.error; feedback.style.display = 'block'; btn.disabled = false; btn.textContent = origText; } }) .catch(function() { feedback.className = 'comment-feedback comment-feedback-error'; feedback.textContent = 'Erreur réseau'; feedback.style.display = 'block'; btn.disabled = false; btn.textContent = origText; }); }); })(); </script> <section class="nl-inline-section"> <div class="nl-inline-inner"> <div class="nl-inline-icon">🔔</div> <div class="nl-inline-text"> <h2 class="nl-inline-headline">Recevez une analyse complète en temps réel des dernières déclarations de Google</h2> <p class="nl-inline-sub">Soyez alerté à chaque nouvelle déclaration officielle Google SEO — avec l'analyse complète incluse.</p> </div> <div class="nl-inline-action"> <form class="nl-inline-form" id="nl-inline-form" novalidate> <input type="email" name="email" placeholder="Votre adresse email" required autocomplete="email"> <button type="submit">S'inscrire gratuitement</button> </form> <div class="nl-inline-privacy">Aucun spam. Désinscription en 1 clic.</div> </div> </div> </section> <style> .nl-inline-section { background: linear-gradient(135deg, #0f172a 0%, #1e3a5f 50%, #0f172a 100%); padding: 52px 24px; margin: 0; } .nl-inline-inner { max-width: 960px; margin: 0 auto; display: grid; grid-template-columns: auto 1fr auto; gap: 24px 32px; align-items: center; } .nl-inline-icon { font-size: 40px; line-height: 1; } .nl-inline-headline { font-size: clamp(16px, 2vw, 20px); font-weight: 700; color: #f0f9ff; margin: 0 0 6px; line-height: 1.35; } .nl-inline-sub { font-size: 14px; color: #7fb8d8; margin: 0; line-height: 1.55; } .nl-inline-action { min-width: 300px; } .nl-inline-form { display: flex; gap: 8px; } .nl-inline-form input[type="email"] { flex: 1; padding: 11px 14px; border: 1px solid rgba(14,165,233,.4); border-radius: 8px; background: rgba(255,255,255,.07); color: #f0f9ff; font-size: 14px; outline: none; min-width: 0; transition: border-color .2s, background .2s; } .nl-inline-form input[type="email"]::placeholder { color: #5a91b0; } .nl-inline-form input[type="email"]:focus { border-color: #0ea5e9; background: rgba(255,255,255,.12); } .nl-inline-form button { padding: 11px 18px; background: linear-gradient(135deg, #0ea5e9, #7c3aed); color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: opacity .2s; } .nl-inline-form button:hover { opacity: .88; } .nl-inline-form button:disabled { opacity: .55; cursor: default; } .nl-inline-privacy { font-size: 12px; color: #4a7a96; margin-top: 7px; text-align: center; } @media (max-width: 768px) { .nl-inline-inner { grid-template-columns: auto 1fr; grid-template-rows: auto auto; } .nl-inline-action { grid-column: 1 / -1; min-width: 0; } .nl-inline-form { flex-wrap: wrap; } .nl-inline-form input[type="email"] { min-width: 200px; } } </style> <script> (function() { var form = document.getElementById('nl-inline-form'); if (!form) return; var LANG = 'fr'; var API_URL = 'https://seoclaims.com/api/subscribe'; var COOKIE = 'nl_subscribed'; var MSG = { success: 'Parfait ! Vous recevrez les prochaines analyses.', already: 'Vous êtes déjà inscrit(e).', error: 'Une erreur est survenue, veuillez réessayer.', }; function getCookie(n) { return document.cookie.split(';').some(function(c) { return c.trim().startsWith(n + '='); }); } function setCookie(n, days) { var d = new Date(); d.setDate(d.getDate() + days); document.cookie = n + '=1;expires=' + d.toUTCString() + ';path=/;SameSite=Lax'; } // Masquer si deja inscrit if (getCookie(COOKIE)) { var section = form.closest('.nl-inline-section'); if (section) section.style.display = 'none'; return; } form.addEventListener('submit', function(e) { e.preventDefault(); var email = form.querySelector('input[type="email"]').value.trim(); var btn = form.querySelector('button'); btn.disabled = true; var orig = btn.textContent; btn.textContent = '...'; fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, lang: LANG }) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { setCookie(COOKIE, 365); form.innerHTML = '<div style="color:#0d9488;font-weight:600;font-size:15px;padding:10px 0;">✓ ' + (data.already ? MSG.already : MSG.success) + '</div>'; // Masquer aussi banniere footer et popup si presents var banner = document.getElementById('nl-banner'); var popup = document.getElementById('nl-popup'); if (banner) banner.style.display = 'none'; if (popup) popup.style.display = 'none'; } else { btn.disabled = false; btn.textContent = orig; var err = form.querySelector('.nl-inline-err'); if (!err) { err = document.createElement('div'); err.className = 'nl-inline-err'; err.style.cssText = 'color:#dc2626;font-size:13px;margin-top:6px;'; form.parentNode.insertBefore(err, form.nextSibling); } err.textContent = data.error || MSG.error; } }) .catch(function() { btn.disabled = false; btn.textContent = orig; }); }); })(); </script> <footer class="site-footer"> <div class="footer-inner"> <!-- ====== Row 1 : About + Navigation + Resources ====== --> <div class="footer-top"> <div> <div class="footer-brand"> <picture> <source srcset="https://seoclaims.com/assets/logo.webp" type="image/webp"> <img src="https://seoclaims.com/assets/logo.png" alt="SEO Declarations" class="footer-logo" loading="lazy" width="132" height="72"> </picture> </div> <p>SEO Claims recense, analyse et traduit les <a href="/declarations">declarations officielles de Google</a> sur le referencement naturel, issues des <a href="/articles">articles publies</a> et des <a href="/videos">videos YouTube</a> de Google Search Central. Chaque declaration est enrichie par une <a href="/declarations">analyse IA</a>, classee par <a href="/declarations">categorie SEO</a> et attribuee a son <a href="/speaker/john-mueller">auteur</a>. Un outil indispensable pour les professionnels du SEO qui veulent savoir exactement ce que Google recommande.</p> </div> <div> <div class="footer-heading">Navigation</div> <nav class="footer-nav"> <a href="/declarations" class="footer-link">Declarations</a> <a href="/labs/" class="footer-link">Labs SEO</a> <a href="/speaker/john-mueller" class="footer-link">Auteurs</a> <a href="/sitemap-declarations" class="footer-link">Plan du site</a> <a href="/comparatif-agences-seo" class="footer-link">Comparatif Agences SEO</a> <a href="/mentions-legales" class="footer-link">Mentions legales</a> </nav> </div> <div> <div class="footer-heading">Ressources</div> <nav class="footer-nav"> <a href="https://search.google.com/search-console" target="_blank" rel="noopener" class="footer-link">Google Search Console</a> <a href="https://pagespeed.web.dev/" target="_blank" rel="noopener" class="footer-link">PageSpeed Insights</a> <a href="https://search.google.com/test/rich-results" target="_blank" rel="noopener" class="footer-link">Rich Results Test</a> <a href="https://developer.chrome.com/docs/lighthouse/" target="_blank" rel="noopener" class="footer-link">Lighthouse</a> <a href="https://developers.google.com/search/docs" target="_blank" rel="noopener" class="footer-link">Google Search Guidelines</a> <a href="/outils-google" class="footer-link" style="color: #60a5fa; font-weight: 600;">Tous les outils Google →</a> </nav> </div> </div> <!-- ====== Row 2 : 3 piliers SEO — Top 5 categories par vues ====== --> <div class="footer-pillars"> <!-- Semantique --> <details class="footer-pillar-details" open> <summary class="footer-pillar-title"> <span style="display:flex;align-items:center;gap:8px;"> <span class="footer-pillar-dot" style="background: #60a5fa;"></span> <span class="footer-pillar-label" style="color: #60a5fa;">Semantique</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/c/ia-seo" class="footer-pillar-link"> <span>IA & SEO</span> <span class="footer-pillar-count">9673</span> </a> <a href="/c/contenu" class="footer-pillar-link"> <span>Contenu</span> <span class="footer-pillar-count">5585</span> </a> <a href="/c/nom-domaine" class="footer-pillar-link"> <span>Nom de domaine</span> <span class="footer-pillar-count">1943</span> </a> <a href="/c/pdf-fichiers" class="footer-pillar-link"> <span>PDF & Fichiers</span> <span class="footer-pillar-count">497</span> </a> <a href="/c/discover-actualites" class="footer-pillar-link"> <span>Discover & Actualites</span> <span class="footer-pillar-count">343</span> </a> </nav> </details> <!-- Technique --> <details class="footer-pillar-details" open> <summary class="footer-pillar-title"> <span style="display:flex;align-items:center;gap:8px;"> <span class="footer-pillar-dot" style="background: #34d399;"></span> <span class="footer-pillar-label" style="color: #34d399;">Technique</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/c/anciennete-historique" class="footer-pillar-link"> <span>Anciennete & Historique</span> <span class="footer-pillar-count">6840</span> </a> <a href="/c/crawl-indexation" class="footer-pillar-link"> <span>Crawl & Indexation</span> <span class="footer-pillar-count">3560</span> </a> <a href="/c/javascript-technique" class="footer-pillar-link"> <span>JavaScript & Technique</span> <span class="footer-pillar-count">2358</span> </a> <a href="/c/search-console" class="footer-pillar-link"> <span>Search Console</span> <span class="footer-pillar-count">1848</span> </a> <a href="/c/performance-web" class="footer-pillar-link"> <span>Performance Web</span> <span class="footer-pillar-count">105</span> </a> </nav> </details> <!-- Autorite --> <details class="footer-pillar-details" open> <summary class="footer-pillar-title"> <span style="display:flex;align-items:center;gap:8px;"> <span class="footer-pillar-dot" style="background: #fbbf24;"></span> <span class="footer-pillar-label" style="color: #fbbf24;">Autorite</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/c/liens-backlinks" class="footer-pillar-link"> <span>Liens & Backlinks</span> <span class="footer-pillar-count">2076</span> </a> <a href="/c/reseaux-sociaux" class="footer-pillar-link"> <span>Reseaux sociaux</span> <span class="footer-pillar-count">541</span> </a> <a href="/c/penalites-spam" class="footer-pillar-link"> <span>Penalites & Spam</span> <span class="footer-pillar-count">515</span> </a> <a href="/c/algorithmes" class="footer-pillar-link"> <span>Algorithmes</span> <span class="footer-pillar-count">416</span> </a> <a href="/c/recherche-locale" class="footer-pillar-link"> <span>Recherche locale</span> <span class="footer-pillar-count">116</span> </a> </nav> </details> </div> <!-- ====== Row 3 : Dernieres prises de parole ====== --> <div class="footer-popular"> <div class="footer-popular-title">Dernieres prises de parole Google sur le SEO</div> <div class="footer-popular-grid"> <a href="/d/celui-qui-dit-tout-savoir-sur-le-seo-est-un-imposteur" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">Pourquoi personne ne peut vraiment maîtriser le SEO à 100% ?</span> </a> <a href="/d/le-seo-est-complexe-multiforme-et-resilient" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">Peut-on vraiment se permettre de faire n'importe quoi en SEO sans conséq…</span> </a> <a href="/d/usage-de-custom-javascript-metrics" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">Google utilise-t-il des scripts JavaScript personnalisés pour évaluer vo…</span> </a> <a href="/d/requete-sql-dans-bigquery-pour-l-analyse-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Gary Illyes</span> </span> <span class="footer-popular-text">Faut-il vraiment maîtriser SQL et BigQuery pour faire du SEO en 2025 ?</span> </a> <a href="/d/recommandations-sur-la-taille-des-fichiers-robots-txt" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">Faut-il vraiment respecter la limite de 100KB pour votre fichier robots.…</span> </a> <a href="/d/importancia-d-http-archive-pour-le-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Gary Illyes</span> </span> <span class="footer-popular-text">HTTP Archive : Google révèle-t-il enfin comment il analyse vraiment vos …</span> </a> <a href="/d/utilisation-de-bigquery-pour-analyser-les-sites-web" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">BigQuery est-il vraiment indispensable pour analyser vos données SEO à g…</span> </a> <a href="/d/nouvelle-collecte-de-donnees-robots-txt-avec-http-archive" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Gary Illyes</span> </span> <span class="footer-popular-text">Pourquoi Google publie-t-il soudainement des données massives sur l'usag…</span> </a> </div> </div> <!-- ====== Row 4 : Copyright ====== --> <div class="footer-bottom"> <span>© 2026 SEO Declarations. Tous droits reserves.</span> <span>Ce site n'est pas affilie a Google. Les declarations presentees sont issues de communications publiques de Google.</span> </div> </div> </footer> <!-- ====== Popup Newsletter (une fois par session) ====== --> <div class="nl-popup-overlay" id="nl-popup" style="display:none;" role="dialog" aria-modal="true" aria-label="Recevez une analyse complète en temps réel des dernières déclarations de Google"> <div class="nl-popup-box"> <button class="nl-popup-close" id="nl-popup-close" aria-label="Fermer">×</button> <div class="nl-popup-header"> <div class="nl-popup-badge">Restez informé</div> <h3 class="nl-popup-title">Recevez une analyse complète en temps réel des dernières déclarations de Google</h3> <p class="nl-popup-sub">Soyez le premier informé à chaque nouvelle déclaration officielle Google SEO, avec l'analyse complète incluse.</p> </div> <form class="nl-form nl-popup-form" data-lang="fr" id="nl-popup-form"> <div class="nl-popup-input-group"> <input type="email" name="email" placeholder="Votre adresse email" required autocomplete="email"> <button type="submit">S'inscrire gratuitement</button> </div> </form> <div class="nl-popup-trust"> <span class="nl-popup-trust-icon">🔒</span> <span>Aucun spam. Désinscription en 1 clic.</span> </div> </div> </div> <style> /* === Newsletter Popup === */ .nl-popup-overlay { position: fixed; inset: 0; background: rgba(15,23,42,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 20px; animation: nlFadeIn .3s ease; } .nl-popup-box { background: linear-gradient(165deg, #ffffff 0%, #f8fafc 100%); border-radius: 20px; padding: 40px 36px 32px; max-width: 460px; width: 100%; position: relative; box-shadow: 0 25px 80px rgba(0,0,0,.2), 0 0 0 1px rgba(0,0,0,.05); animation: nlSlideUp .4s cubic-bezier(.16,1,.3,1); text-align: center; } .nl-popup-close { position: absolute; top: 12px; right: 14px; background: none; border: none; font-size: 22px; color: #94a3b8; cursor: pointer; line-height: 1; padding: 6px 10px; border-radius: 8px; transition: color .2s, background .2s; } .nl-popup-close:hover { color: #374151; background: #f1f5f9; } .nl-popup-header { margin-bottom: 24px; } .nl-popup-badge { display: inline-block; background: linear-gradient(135deg, #0ea5e9, #7c3aed); color: #fff; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; padding: 5px 14px; border-radius: 20px; margin-bottom: 16px; } .nl-popup-title { font-size: 22px; font-weight: 800; color: #0f172a; line-height: 1.3; margin: 0 0 10px; } .nl-popup-sub { font-size: 14px; color: #64748b; line-height: 1.6; margin: 0; } .nl-popup-input-group { display: flex; gap: 0; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,.08), 0 0 0 1px rgba(0,0,0,.06); } .nl-popup-form input[type="email"] { flex: 1; padding: 14px 16px; background: #fff; color: #1e293b; border: none; font-size: 15px; outline: none; min-width: 0; } .nl-popup-form input[type="email"]::placeholder { color: #94a3b8; } .nl-popup-form button { padding: 14px 22px; background: linear-gradient(135deg, #0ea5e9, #6d28d9); color: #fff; border: none; font-size: 14px; font-weight: 700; cursor: pointer; white-space: nowrap; transition: opacity .2s; } .nl-popup-form button:hover { opacity: .9; } .nl-popup-form button:disabled { opacity: .5; cursor: default; } .nl-popup-trust { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 14px; font-size: 12px; color: #94a3b8; } .nl-popup-trust-icon { font-size: 13px; } .nl-popup-msg { padding: 16px; border-radius: 12px; font-weight: 600; font-size: 15px; margin-top: 4px; } .nl-popup-msg.success { background: #ecfdf5; color: #059669; } .nl-popup-msg.error { background: #fef2f2; color: #dc2626; } @media (max-width: 480px) { .nl-popup-box { padding: 32px 20px 24px; } .nl-popup-input-group { flex-direction: column; border-radius: 12px; } .nl-popup-form input[type="email"] { border-bottom: 1px solid #e2e8f0; } .nl-popup-form button { padding: 14px; border-radius: 0 0 12px 12px; } .nl-popup-title { font-size: 19px; } } @keyframes nlFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes nlSlideUp { from { transform: translateY(30px) scale(.97); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } } </style> <script> (function() { var LANG = 'fr'; var API_URL = 'https://seoclaims.com/api/subscribe'; var COOKIE = 'nl_subscribed'; var SESSION_KEY = 'nl_popup_shown'; var MSG = { success: 'Parfait ! Vous êtes inscrit(e).', already: 'Vous êtes déjà inscrit(e).', error: 'Une erreur est survenue, veuillez réessayer.', }; function getCookie(n) { return document.cookie.split(';').some(c => c.trim().startsWith(n + '=')); } function setCookie(n, days) { var d = new Date(); d.setDate(d.getDate() + days); document.cookie = n + '=1;expires=' + d.toUTCString() + ';path=/;SameSite=Lax'; } function closePopup(popup) { popup.style.opacity = '0'; setTimeout(function() { popup.style.display = 'none'; }, 300); sessionStorage.setItem(SESSION_KEY, '1'); } function showMsg(container, text, type) { var existing = container.querySelector('.nl-popup-msg'); if (existing) existing.remove(); var el = document.createElement('div'); el.className = 'nl-popup-msg ' + type; el.textContent = text; container.appendChild(el); } document.addEventListener('DOMContentLoaded', function() { var popup = document.getElementById('nl-popup'); var popupForm = document.getElementById('nl-popup-form'); var closeBtn = document.getElementById('nl-popup-close'); if (!popup || !popupForm) return; // Ne pas afficher si : deja inscrit OU deja vu cette session if (getCookie(COOKIE) || sessionStorage.getItem(SESSION_KEY)) return; // Afficher apres 30% scroll OU 20 secondes var nlShown = false; function showNlPopup() { if (nlShown) return; nlShown = true; popup.style.display = 'flex'; sessionStorage.setItem(SESSION_KEY, '1'); } var scrollHandler = function() { var pct = window.scrollY / (document.body.scrollHeight - window.innerHeight); if (pct > 0.3) { showNlPopup(); window.removeEventListener('scroll', scrollHandler); } }; window.addEventListener('scroll', scrollHandler, {passive: true}); setTimeout(showNlPopup, 20000); closeBtn.addEventListener('click', function() { closePopup(popup); }); popup.addEventListener('click', function(e) { if (e.target === popup) closePopup(popup); }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && popup.style.display === 'flex') closePopup(popup); }); popupForm.addEventListener('submit', function(e) { e.preventDefault(); var email = popupForm.querySelector('input[type="email"]').value.trim(); var btn = popupForm.querySelector('button[type="submit"]'); btn.disabled = true; var origText = btn.textContent; btn.textContent = '...'; fetch(API_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({email: email, lang: LANG}) }) .then(function(r) { return r.json(); }) .then(function(data) { btn.disabled = false; btn.textContent = origText; if (data.success) { setCookie(COOKIE, 365); popupForm.style.display = 'none'; showMsg(popup.querySelector('.nl-popup-box'), data.already ? MSG.already : MSG.success, 'success'); // Masquer aussi le bloc inline si present var inline = document.querySelector('.nl-inline-section'); if (inline) inline.style.display = 'none'; setTimeout(function() { closePopup(popup); }, 2500); } else { showMsg(popup.querySelector('.nl-popup-box'), data.error || MSG.error, 'error'); } }) .catch(function() { btn.disabled = false; btn.textContent = origText; showMsg(popup.querySelector('.nl-popup-box'), MSG.error, 'error'); }); }); }); })(); </script> <!-- WebSite + Organization Schema --> <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "WebSite", "@id": "https://seoclaims.com/#website", "name": "SEO Declarations", "url": "https://seoclaims.com", "inLanguage": [ "fr-FR", "en" ], "publisher": { "@id": "https://seoclaims.com/#organization" }, "potentialAction": { "@type": "SearchAction", "target": { "@type": "EntryPoint", "urlTemplate": "https://seoclaims.com/?q={search_term_string}" }, "query-input": "required name=search_term_string" } }, { "@type": "Organization", "@id": "https://seoclaims.com/#organization", "name": "SEO Declarations", "url": "https://seoclaims.com", "logo": { "@type": "ImageObject", "url": "https://seoclaims.com/assets/logo.png", "width": 512, "height": 512 }, "description": "Moteur de recherche des declarations officielles de Google sur le SEO, avec analyse bilingue enrichie par IA.", "knowsAbout": [ "Search Engine Optimization", "Google Search", "Core Web Vitals", "Technical SEO", "Content SEO", "Link building", "Structured data", "AI search optimization" ], "sameAs": [] } ] }</script> <!-- ==================== CHATBOT WIDGET ==================== --> <button id="cbw-launcher" type="button" aria-label="Poser une question a l'assistant SEO" title="Poser une question a l'assistant SEO"> <svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> </svg> <span class="cbw-launcher-label">Assistant SEO</span> <span class="cbw-launcher-pulse" aria-hidden="true"></span> </button> <div id="cbw-panel" role="dialog" aria-label="Assistant SEO" aria-hidden="true"> <div class="cbw-head"> <div class="cbw-head-txt"> <strong>Assistant SEO</strong> <span>Base sur les declarations officielles de Google</span> </div> <div class="cbw-head-actions"> <a href="https://seoclaims.com/chatbot" class="cbw-icon-btn" title="Ouvrir la version complete" aria-label="Ouvrir la version complete"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"></path></svg> </a> <button type="button" id="cbw-close" class="cbw-icon-btn" title="Fermer" aria-label="Fermer"> <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18"></path><path d="M6 6l12 12"></path></svg> </button> </div> </div> <div class="cbw-body" id="cbw-messages" aria-live="polite"> <div class="cbw-msg assistant cbw-welcome">Bonjour ! Posez-moi une question sur le SEO et Google — je reponds en citant les sources officielles.</div> </div> <form class="cbw-form" id="cbw-form" autocomplete="off"> <textarea id="cbw-input" rows="1" placeholder="Posez votre question..." maxlength="500" required></textarea> <button type="submit" id="cbw-send" aria-label="Envoyer"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> </button> </form> </div> <style> #cbw-launcher { position: fixed; bottom: 24px; right: 24px; z-index: 9998; display: flex; align-items: center; gap: 8px; padding: 14px 20px 14px 16px; background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%); color: #fff; border: none; border-radius: 999px; box-shadow: 0 8px 24px rgba(124,58,237,.35); font-family: inherit; font-size: .93rem; font-weight: 600; cursor: pointer; transition: transform .15s ease, box-shadow .15s ease; } #cbw-launcher:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(124,58,237,.45); } #cbw-launcher svg { flex-shrink: 0; } .cbw-launcher-label { white-space: nowrap; } .cbw-launcher-pulse { position: absolute; top: -2px; right: -2px; width: 12px; height: 12px; background: #10b981; border: 2px solid #fff; border-radius: 50%; animation: cbwPulse 2s infinite; } @keyframes cbwPulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.3); opacity: .7; } } #cbw-panel { position: fixed; bottom: 24px; right: 24px; z-index: 9999; width: 400px; max-width: calc(100vw - 32px); height: 560px; max-height: calc(100vh - 48px); background: #fff; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,.2); display: grid; grid-template-rows: auto 1fr auto; overflow: hidden; transform: translateY(20px) scale(.96); opacity: 0; pointer-events: none; transition: transform .22s ease, opacity .22s ease; font-family: inherit; } #cbw-panel[aria-hidden="false"] { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; } #cbw-panel[aria-hidden="false"] ~ #cbw-launcher, body.cbw-open #cbw-launcher { opacity: 0; pointer-events: none; transform: scale(.8); } .cbw-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; background: linear-gradient(135deg, #7c3aed, #5b21b6); color: #fff; } .cbw-head-txt strong { display: block; font-size: .97rem; line-height: 1.2; } .cbw-head-txt span { display: block; font-size: .75rem; opacity: .85; margin-top: 2px; } .cbw-head-actions { display: flex; gap: 6px; } .cbw-icon-btn { width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; background: rgba(255,255,255,.12); color: #fff; border: none; border-radius: 8px; cursor: pointer; text-decoration: none; transition: background .15s; } .cbw-icon-btn:hover { background: rgba(255,255,255,.22); } .cbw-body { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; background: #fafafa; } .cbw-msg { max-width: 92%; padding: 10px 14px; border-radius: 12px; font-size: .9rem; line-height: 1.55; animation: cbwIn .2s ease-out; } @keyframes cbwIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .cbw-msg.user { align-self: flex-end; background: linear-gradient(135deg, #1a73e8, #1557b0); color: #fff; border-bottom-right-radius: 3px; } .cbw-msg.assistant { align-self: flex-start; background: #fff; border: 1px solid #e2e8f0; border-bottom-left-radius: 3px; color: #1e293b; } .cbw-msg.assistant.error { background: #fef2f2; border-color: #fecaca; color: #991b1b; } .cbw-msg.cbw-welcome { background: linear-gradient(135deg, #f3e8ff, #ede9fe); border-color: #c4b5fd; color: #5b21b6; } .cbw-citation { display: inline-block; background: #ede9fe; color: #5b21b6; font-size: .75rem; font-weight: 700; padding: 0 6px; border-radius: 8px; margin: 0 2px; text-decoration: none; vertical-align: 1px; } .cbw-hint { display: inline-block; background: #fef3c7; color: #92400e; font-style: normal; font-size: .82rem; padding: 1px 7px; border-radius: 6px; margin-left: 4px; font-weight: 500; } .cbw-citation:hover { background: #ddd6fe; } .cbw-sources { margin-top: 10px; padding-top: 9px; border-top: 1px dashed #cbd5e1; } .cbw-sources-title { font-size: .7rem; text-transform: uppercase; letter-spacing: .4px; font-weight: 700; color: #64748b; margin-bottom: 6px; } .cbw-src { display: block; padding: 8px 10px; background: #fff; border: 1px solid #e2e8f0; border-left: 3px solid #7c3aed; border-radius: 6px; margin-bottom: 5px; font-size: .78rem; color: inherit; text-decoration: none; transition: transform .12s; } .cbw-src:hover { transform: translateX(2px); border-color: #7c3aed; } .cbw-src strong { display: block; color: #1e293b; margin-bottom: 1px; font-size: .8rem; line-height: 1.3; } .cbw-src .cbw-src-meta { color: #64748b; font-size: .7rem; } .cbw-src .cbw-src-id { display: inline-block; background: #ede9fe; color: #5b21b6; font-family: monospace; font-size: .68rem; font-weight: 700; padding: 0 5px; border-radius: 4px; margin-right: 4px; } .cbw-typing { display: flex; gap: 4px; padding: 10px 14px; } .cbw-typing span { width: 7px; height: 7px; border-radius: 50%; background: #7c3aed; opacity: .5; animation: cbwTyping 1.2s infinite; } .cbw-typing span:nth-child(2) { animation-delay: .15s; } .cbw-typing span:nth-child(3) { animation-delay: .3s; } @keyframes cbwTyping { 0%,80%,100% { transform: scale(.7); opacity: .4; } 40% { transform: scale(1); opacity: 1; } } .cbw-form { display: flex; gap: 8px; padding: 12px; background: #fff; border-top: 1px solid #e2e8f0; } .cbw-form textarea { flex: 1; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 10px; font-size: .9rem; font-family: inherit; resize: none; min-height: 40px; max-height: 110px; outline: none; transition: border-color .15s; } .cbw-form textarea:focus { border-color: #7c3aed; box-shadow: 0 0 0 3px rgba(124,58,237,.12); } .cbw-form button { width: 42px; height: 42px; display: inline-flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #7c3aed, #5b21b6); color: #fff; border: none; border-radius: 10px; cursor: pointer; flex-shrink: 0; } .cbw-form button:disabled { opacity: .5; cursor: not-allowed; } @media (max-width: 640px) { #cbw-launcher { bottom: 16px; right: 16px; padding: 12px 16px 12px 14px; font-size: .85rem; } .cbw-launcher-label { display: none; } #cbw-launcher { padding: 14px; } #cbw-panel { top: 0; bottom: 0; left: 0; right: 0; width: 100vw; max-width: 100vw; /* dvh = dynamic viewport height : se reduit quand le clavier mobile s'ouvre */ /* fallback vh pour navigateurs tres anciens */ height: 100vh; height: 100dvh; max-height: 100vh; max-height: 100dvh; border-radius: 0; } /* Laisse le clavier rogner l'espace du body, pas de la frame entiere */ .cbw-body { overscroll-behavior: contain; } } @media print { #cbw-launcher, #cbw-panel { display: none !important; } } </style> <script> (function() { const LANG = "fr"; const T = {"sending":"...","send":"Envoyer","sources":"Sources","rate_limit":"Limite de 10 questions par heure atteinte.","error":"Erreur. Merci de reessayer.","too_short":"Question trop courte."}; const launcher = document.getElementById('cbw-launcher'); const panel = document.getElementById('cbw-panel'); const closeBtn = document.getElementById('cbw-close'); const msgsEl = document.getElementById('cbw-messages'); const formEl = document.getElementById('cbw-form'); const inputEl = document.getElementById('cbw-input'); const sendBtn = document.getElementById('cbw-send'); // Sync panel height to the visual viewport (keyboard-aware) sur mobile // Necessaire car Chrome Android n'honore pas toujours 100dvh correctement // quand le clavier virtuel s'ouvre : la panel reste a 100vh et le contenu // du haut est pousse hors ecran. function syncViewport() { if (!window.visualViewport) return; if (panel.getAttribute('aria-hidden') !== 'false') return; if (window.innerWidth > 640) { panel.style.height = ''; return; } panel.style.height = window.visualViewport.height + 'px'; } function open() { panel.setAttribute('aria-hidden', 'false'); document.body.classList.add('cbw-open'); syncViewport(); if (window.visualViewport) { window.visualViewport.addEventListener('resize', syncViewport); window.visualViewport.addEventListener('scroll', syncViewport); } // Afficher le message de bienvenue au chargement msgsEl.scrollTop = 0; setTimeout(() => inputEl.focus(), 200); } function close() { panel.setAttribute('aria-hidden', 'true'); document.body.classList.remove('cbw-open'); panel.style.height = ''; if (window.visualViewport) { window.visualViewport.removeEventListener('resize', syncViewport); window.visualViewport.removeEventListener('scroll', syncViewport); } } launcher.addEventListener('click', open); closeBtn.addEventListener('click', close); document.addEventListener('keydown', e => { if (e.key === 'Escape' && panel.getAttribute('aria-hidden') === 'false') close(); }); function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function renderAnswer(answer, sources, queryId) { const byId = {}; (sources || []).forEach(s => { byId[s.id] = s; }); let html = esc(answer).replace(/\[#(\d+)\]/g, (m, id) => { const s = byId[id]; if (!s) return ''; return '<a class="cbw-citation" href="' + esc(s.url) + '" target="_blank" rel="noopener" data-qid="' + (queryId||0) + '" data-sid="' + id + '" data-kind="citation">#' + id + '</a>'; }); // Markdown italique *texte* -> <em>texte</em> (non greedy, non multiligne) html = html.replace(/\*([^*\n]+?)\*/g, '<em class="cbw-hint">$1</em>'); return html.replace(/\n/g, '<br>'); } function renderSources(sources, queryId) { if (!sources || !sources.length) return ''; let out = '<div class="cbw-sources"><div class="cbw-sources-title">' + esc(T.sources) + '</div>'; sources.forEach(s => { const meta = [s.speaker, s.date].filter(Boolean).join(' · '); out += '<a class="cbw-src" href="' + esc(s.url) + '" target="_blank" rel="noopener" data-qid="' + (queryId||0) + '" data-sid="' + s.id + '" data-kind="source_card">' + '<strong><span class="cbw-src-id">#' + s.id + '</span>' + esc(s.title || '') + '</strong>' + '<div class="cbw-src-meta">' + meta + '</div></a>'; }); return out + '</div>'; } // Beacon de clic : non bloquant, n'empeche pas la navigation function logClick(qid, sid, kind) { if (!qid || !sid) return; const payload = JSON.stringify({ query_id: qid, source_id: sid, kind }); try { if (navigator.sendBeacon) { navigator.sendBeacon('/api/chatbot-click', new Blob([payload], {type: 'application/json'})); } else { fetch('/api/chatbot-click', { method: 'POST', headers: {'Content-Type':'application/json'}, body: payload, keepalive: true }).catch(() => {}); } } catch (e) {} } // Delegation d'evenement sur le conteneur messages msgsEl.addEventListener('click', function(e) { const a = e.target.closest('a[data-qid][data-sid]'); if (!a) return; logClick(parseInt(a.dataset.qid, 10), parseInt(a.dataset.sid, 10), a.dataset.kind || 'source_card'); }); function addUserMsg(txt) { const d = document.createElement('div'); d.className = 'cbw-msg user'; d.innerHTML = esc(txt).replace(/\n/g, '<br>'); msgsEl.appendChild(d); scroll(); } function addBot(data, isError) { const d = document.createElement('div'); d.className = 'cbw-msg assistant' + (isError ? ' error' : ''); if (typeof data === 'string') d.innerHTML = esc(data); else { const qid = data.query_id || 0; d.innerHTML = renderAnswer(data.answer || '', data.sources || [], qid) + renderSources(data.sources || [], qid); } msgsEl.appendChild(d); // Pas de scroll : on laisse l'utilisateur sur sa question, il scrolle lui-meme pour lire la reponse } function showTyping() { const d = document.createElement('div'); d.className = 'cbw-msg assistant'; d.id = 'cbw-typing'; d.innerHTML = '<div class="cbw-typing"><span></span><span></span><span></span></div>'; msgsEl.appendChild(d); // Pas de scroll sur l'indicateur de frappe non plus } function hideTyping() { const t = document.getElementById('cbw-typing'); if (t) t.remove(); } function scroll() { msgsEl.scrollTop = msgsEl.scrollHeight; } async function ask(question, origin) { origin = origin || 'widget'; addUserMsg(question); inputEl.value = ''; inputEl.style.height = 'auto'; sendBtn.disabled = true; showTyping(); try { const resp = await fetch('/api/chatbot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, lang: LANG, referer: window.location.pathname, origin }) }); hideTyping(); const data = await resp.json(); if (!resp.ok) { addBot(data.error === 'rate_limit' ? T.rate_limit : T.error, true); } else { addBot(data, false); } } catch (e) { hideTyping(); addBot(T.error, true); } finally { sendBtn.disabled = false; inputEl.focus(); } } // API publique pour declencher le chatbot depuis d'autres pages // Usage : window.ChatbotSEO.ask("Ma question", "search_fallback") window.ChatbotSEO = { open, close, ask: function(question, origin) { if (!question || question.length < 3) return; open(); // Laisser l'animation d'ouverture se lancer puis envoyer setTimeout(() => ask(question, origin || 'search_fallback'), 350); } }; inputEl.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 110) + 'px'; }); inputEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); formEl.requestSubmit(); } }); formEl.addEventListener('submit', function(e) { e.preventDefault(); const q = inputEl.value.trim(); if (q.length < 3) { addBot(T.too_short, true); return; } ask(q); }); })(); </script> <nav class="mobile-nav" aria-label="Navigation mobile"> <a href="/" class="mobile-nav-item"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg> <span>Recherche</span> </a> <a href="/declarations" class="mobile-nav-item"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg> <span>Catégories</span> </a> <a href="/?browse=1&sort=date_desc" class="mobile-nav-item"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> <span>Récentes</span> </a> <a href="/en/" class="mobile-nav-item mobile-nav-lang"> <svg width="22" height="16" viewBox="0 0 640 480"> <rect width="213" height="480" fill="#00247d"/><rect x="213" width="214" height="480" fill="#fff"/><rect x="427" width="213" height="480" fill="#cf142b"/> </svg> <span>EN</span> </a> </nav> <!-- Lightbox (self-contained) --> <div id="lightbox" style="display:none;position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.9);align-items:center;justify-content:center;padding:16px;cursor:zoom-out;" onclick="closeLightbox()"> <button onclick="closeLightbox();event.stopPropagation();" style="position:absolute;top:12px;right:16px;z-index:100000;width:44px;height:44px;border:none;background:rgba(255,255,255,.2);color:#fff;font-size:1.6rem;border-radius:50%;cursor:pointer;" aria-label="Fermer">×</button> <img id="lightbox-img" src="" alt="" style="max-width:95%;max-height:90vh;border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,.5);" onclick="event.stopPropagation()"> </div> <script> function openLightbox(src){ var lb=document.getElementById('lightbox'); document.getElementById('lightbox-img').src=src; lb.style.display='flex'; document.body.style.overflow='hidden'; } function closeLightbox(){ document.getElementById('lightbox').style.display='none'; document.body.style.overflow=''; } document.addEventListener('keydown',function(e){if(e.key==='Escape')closeLightbox();}); function ytPlay(card) { var vid = card.dataset.vid; var ts = parseInt(card.dataset.ts) || 0; card.classList.add('playing'); card.innerHTML = '<iframe src="https://www.youtube-nocookie.com/embed/' + vid + '?autoplay=1&rel=0&modestbranding=1' + (ts > 0 ? '&start=' + ts : '') + '" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'; } </script> </body> </html>