Que dit Google sur le SEO ? /

Declaration officielle

Il est recommandé de toujours rendre les meta tags cohérents avant l'intervention de JavaScript, ou au minimum de les omettre si la version serveur et la version JavaScript-rendue ne peuvent pas être cohérentes.
15:27
🎥 Vidéo source

Extrait d'une vidéo Google Search Central

⏱ 18:24 💬 EN 📅 10/12/2020 ✂ 12 déclarations
Voir sur YouTube (15:27) →
Autres déclarations de cette vidéo 11
  1. 1:01 Faut-il vraiment contacter l'équipe AdSense pour résoudre vos problèmes de performance PageSpeed ?
  2. 1:01 Faut-il vraiment retarder le JavaScript AdSense pour booster votre SEO ?
  3. 2:35 Pourquoi Google refuse-t-il de communiquer les dimensions du viewport de Googlebot ?
  4. 3:07 Comment Googlebot gère-t-il réellement le contenu en bas de page ?
  5. 3:38 Faut-il abandonner l'infinite scroll pour être correctement indexé par Google ?
  6. 4:08 L'Intersection Observer est-il vraiment crawlé par Googlebot ?
  7. 6:24 Pourquoi Googlebot utilise-t-il un viewport de 10 000 pixels ?
  8. 9:23 Pourquoi Google refuse-t-il d'indexer le contenu qui dépend du viewport ?
  9. 10:11 Pourquoi Google fixe-t-il la largeur du viewport de son crawler à 1024 pixels ?
  10. 12:38 Les meta tags no-archive en JavaScript fonctionnent-ils vraiment ?
  11. 14:24 Google analyse-t-il vraiment les meta tags avant ET après le rendu JavaScript ?
📅
Declaration officielle du (il y a 5 ans)
TL;DR

Google recommande de servir des meta tags cohérents avant l'exécution de JavaScript, ou de les omettre totalement si vous ne pouvez pas garantir cette cohérence entre les deux versions. En clair : si votre SPA ou framework JS modifie les balises title, description ou canonical après le rendu initial, vous créez une zone de friction pour Googlebot. L'alternative la plus sûre reste le Server-Side Rendering ou l'hydratation côté serveur, pas le bricolage côté client.

Ce qu'il faut comprendre

Pourquoi Google insiste-t-il sur la cohérence des meta tags avant JavaScript ?

Googlebot crawle votre page en deux temps : d'abord le HTML brut renvoyé par le serveur, puis — après avoir ajouté la page à une file d'attente de rendu — il exécute JavaScript et récupère le DOM final. Cette architecture à deux phases crée un décalage temporel qui peut atteindre plusieurs secondes, voire minutes dans certains cas.

Si vos meta tags (title, description, canonical, robots) changent entre ces deux passes, Google doit choisir quelle version indexer. Martin Splitt ne précise pas quel signal prime, et c'est justement là que ça coince : vous introduisez une incertitude que vous ne maîtrisez pas.

Quels meta tags sont concernés par cette recommandation ?

Tous ceux qui influencent l'indexation et l'affichage dans les SERPs : balise title, meta description, meta robots (noindex, nofollow), canonical, hreflang, Open Graph et Twitter Cards si vous visez le partage social. En revanche, les balises purement techniques comme viewport ou charset ne posent aucun problème si elles sont injectées par JS — Google s'en fiche.

Le vrai enjeu concerne les frameworks React, Vue, Angular, Svelte qui montent l'interface côté client et modifient le après coup. Si vous utilisez des librairies comme react-helmet ou vue-meta sans SSR, vous êtes pile dans le cas visé par cette déclaration.

Que se passe-t-il concrètement si les meta tags diffèrent entre HTML initial et version JS ?

Google doit arbitrer. Dans certains cas observés sur le terrain, c'est la version serveur qui est indexée ; dans d'autres, la version JS prend le dessus. Aucune documentation officielle ne tranche ce point — c'est opaque, et l'opacité en SEO, c'est du risque pur.

Autre problème : les crawlers tiers (Facebook, LinkedIn, Twitter, Pinterest) n'exécutent généralement pas JavaScript. Si vos Open Graph tags ne sont présents qu'après rendu JS, vos snippets sociaux seront vides ou cassés. Google n'est pas le seul acteur à considérer.

  • Googlebot crawle en deux phases distinctes, avec un décalage temporel potentiellement long entre HTML brut et rendu JS
  • Les meta tags critiques (title, description, canonical, robots, hreflang) doivent être identiques dans les deux versions — ou absents du HTML initial
  • Les crawlers sociaux (Facebook, LinkedIn, Twitter) n'exécutent pas JavaScript : vos Open Graph tags doivent être côté serveur
  • Omettre totalement un meta tag côté serveur est préférable à servir une valeur différente qui sera écrasée par JS
  • Les frameworks SPA modernes (React, Vue, Angular) créent ce problème par défaut sans SSR ou pré-rendering activé

Avis d'un expert SEO

Cette recommandation est-elle cohérente avec les observations terrain ?

Oui, mais avec une nuance majeure : Google arrive à crawler et indexer des SPA full-JS depuis des années. Des milliers de sites React ou Vue sans SSR rankent correctement. Alors pourquoi cette déclaration maintenant ?

Parce que la capacité de Google à gérer JavaScript ne signifie pas que c'est optimal. Le rendu JS consomme du crawl budget, introduit de la latence, et augmente le risque d'erreurs — timeout, JS mal formé, ressources bloquées. Splitt pousse une recommandation d'architecture, pas une règle technique absolue.

Quelles zones grises subsistent dans cette déclaration ?

Splitt ne dit pas quel signal l'emporte quand les deux versions divergent. Il ne précise pas non plus si Google stocke les deux versions ou fusionne les signaux. [A vérifier] : aucune donnée officielle ne documente ce comportement d'arbitrage — on navigue à vue.

Autre point flou : qu'entend-on par "cohérent" ? Une balise title de 55 caractères côté serveur et 58 caractères après JS est-elle incohérente ? Quid d'une meta description identique mais reformatée avec des espaces différents ? Google ne fixe pas de seuil de tolérance, ce qui laisse place à l'interprétation.

Dans quels cas cette règle peut-elle être contournée sans risque ?

Si vous avez mis en place du Dynamic Rendering (servir une version pré-rendue à Googlebot et la version JS aux utilisateurs), vous pouvez techniquement ignorer cette recommandation pour les humains. Mais attention : Google a officiellement découragé cette pratique comme workaround permanent.

Autre exception : les meta tags non critiques pour l'indexation (Open Graph, Twitter Cards) peuvent être injectés par JS si vous vous fichez du partage social ou utilisez un service de pré-rendering tiers pour les crawlers sociaux. Mais franchement, autant tout uniformiser côté serveur et éviter la complexité.

Attention : Si vous travaillez sur un site e-commerce avec des milliers de pages produits générées côté client, cette recommandation devient critique. Une incohérence title/canonical entre HTML initial et JS peut fragmenter votre indexation et diluer votre ranking. Ne prenez pas ce risque sur des pages stratégiques.

Impact pratique et recommandations

Comment vérifier que vos meta tags sont cohérents entre HTML serveur et version JS ?

Première étape : désactivez JavaScript dans Chrome DevTools (Settings → Debugger → Disable JavaScript) et rechargez votre page. Inspectez le : si vos meta tags critiques sont absents ou diffèrent de la version JS activée, vous avez un problème.

Deuxième approche : utilisez curl ou Screaming Frog en mode "Render JavaScript" pour comparer les deux versions. Screaming Frog vous montre côte à côte le HTML brut et le DOM rendu — c'est un gain de temps colossal sur des audits de sites larges.

Quelles solutions techniques permettent de garantir cette cohérence ?

La solution de référence, c'est le Server-Side Rendering (SSR) : Next.js pour React, Nuxt.js pour Vue, Angular Universal pour Angular. Ces frameworks génèrent le HTML final côté serveur avec tous les meta tags en place avant envoi au client. Google reçoit directement la version complète.

Alternative crédible : le Static Site Generation (SSG) avec pré-rendering à la build (Gatsby, Astro, Eleventy). Chaque page est compilée en HTML statique avec meta tags figés. Pas de JS nécessaire pour l'indexation, mais moins flexible pour du contenu dynamique.

Dernier recours si vous restez en SPA full-client : injectez des meta tags génériques côté serveur (titre et description de fallback) et laissez JS affiner ensuite. Google indexera la version serveur, ce qui est sous-optimal mais évite l'incohérence. Cela dit, pourquoi se compliquer la vie ?

Quelles erreurs éviter absolument dans la mise en œuvre ?

Ne servez jamais un title vide côté serveur en pensant que JS le remplira à temps — Google peut indexer la version vide. Ne dupliquez pas non plus les balises : si vous avez un serveur et que JS en injecte un second, le navigateur garde le premier (comportement DOM standard), mais vous créez du bruit pour Googlebot.</p><p>Évitez également de croire que le Dynamic Rendering est une solution pérenne. Google l'a explicitement qualifié de <strong>workaround temporaire</strong>, pas d'architecture cible. Si vous l'utilisez, planifiez dès maintenant une migration vers SSR ou SSG — c'est une dette technique qui finira par vous coûter en crawl budget et en ranking.</p><ul class="checklist"><li>Auditez votre site avec JavaScript désactivé pour identifier les meta tags absents ou différents</li><li>Comparez HTML brut vs rendu JS avec Screaming Frog en mode "Render JavaScript"</li><li>Migrez vers Next.js, Nuxt.js ou Angular Universal si vous utilisez un framework SPA moderne</li><li>Si migration impossible à court terme, injectez des meta tags génériques côté serveur comme fallback</li><li>Testez vos Open Graph tags avec le Facebook Debugger et le Twitter Card Validator — ils n'exécutent pas JS</li><li>Documentez vos choix d'architecture pour les équipes dev et SEO — la cohérence multi-équipe est clé</li></ul><div class="summary">Garantir la cohérence des meta tags entre HTML serveur et version JavaScript n'est pas un détail technique — c'est une <strong>décision d'architecture</strong> qui impacte indexation, crawl budget et ranking. Le Server-Side Rendering reste la solution la plus fiable, mais elle implique une refonte technique parfois lourde. Si vous manquez de ressources internes ou si l'arbitrage entre SSR, SSG et SPA vous semble flou, faire appel à une agence SEO spécialisée en architecture web peut accélérer la transition tout en sécurisant vos gains organiques — surtout sur des sites e-commerce ou médias à fort volume de pages.</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 indexe-t-il la version serveur ou la version JavaScript des meta tags en cas de différence ?</summary> <div class="faq-answer">Google ne documente pas officiellement quel signal prime. Les observations terrain montrent des résultats variables selon les cas, ce qui rend la pratique risquée. La seule certitude : éviter cette incohérence élimine le risque d'arbitrage imprévisible.</div> </details> <details class="faq-item"> <summary class="faq-question">Puis-je utiliser react-helmet ou vue-meta sans Server-Side Rendering ?</summary> <div class="faq-answer">Techniquement oui, mais vous créez exactement le problème visé par cette déclaration : vos meta tags seront absents du HTML initial et injectés après coup par JS. Pour des pages stratégiques, c'est déconseillé.</div> </details> <details class="faq-item"> <summary class="faq-question">Les Open Graph tags doivent-ils absolument être côté serveur ?</summary> <div class="faq-answer">Oui si vous visez le partage social. Facebook, LinkedIn, Twitter et Pinterest n'exécutent pas JavaScript — si vos OG tags sont injectés par JS, vos snippets sociaux seront vides ou cassés.</div> </details> <details class="faq-item"> <summary class="faq-question">Le Dynamic Rendering est-il une solution viable à long terme ?</summary> <div class="faq-answer">Non. Google l'a qualifié de workaround temporaire, pas d'architecture cible. Si vous l'utilisez, planifiez une migration vers SSR ou SSG pour éviter de dépendre d'une pratique officiellement découragée.</div> </details> <details class="faq-item"> <summary class="faq-question">Comment tester rapidement si mes meta tags sont cohérents entre serveur et JS ?</summary> <div class="faq-answer">Désactivez JavaScript dans Chrome DevTools, rechargez la page et inspectez le <head>. Comparez avec la version JS activée. Pour un audit exhaustif, utilisez Screaming Frog en mode "Render JavaScript" qui affiche les deux versions côte à côte.</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=meta+tags" class="seo-tag-pill">meta tags</a> <a href="/?q=JavaScript+SEO" class="seo-tag-pill">JavaScript SEO</a> <a href="/?q=SSR" class="seo-tag-pill">SSR</a> <a href="/?q=rendu+serveur" class="seo-tag-pill">rendu serveur</a> <a href="/?q=indexation" class="seo-tag-pill">indexation</a> <a href="/?q=crawl+budget" class="seo-tag-pill">crawl budget</a> <a href="/?q=SPA" class="seo-tag-pill">SPA</a> <a href="/?q=Open+Graph" class="seo-tag-pill">Open Graph</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">11</span> </h2> <p class="yt-same-intro"> Autres enseignements SEO extraits de cette même vidéo Google Search Central · durée 18 min · publiée le 10/12/2020 </p> <div class="related-grid yt-same-grid"> <a href="/d/adsense-et-performance-contacter-l-equipe-adsense" class="related-item yt-same-item"> <div class="title">Faut-il vraiment contacter l'équipe AdSense pour résoudre vos problèmes de performance PageSpeed ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 1:01</span> </div> </a> <a href="/d/retarder-javascript-adsense-peut-ameliorer-l-experience-utilisateur" class="related-item yt-same-item"> <div class="title">Faut-il vraiment retarder le JavaScript AdSense pour booster votre SEO ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 1:01</span> </div> </a> <a href="/d/viewport-googlebot-detail-d-implementation-non-garanti" class="related-item yt-same-item"> <div class="title">Pourquoi Google refuse-t-il de communiquer les dimensions du viewport de Googlebot ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 2:35</span> </div> </a> <a href="/d/googlebot-etend-le-viewport-verticalement-selon-le-contenu" class="related-item yt-same-item"> <div class="title">Comment Googlebot gère-t-il réellement le contenu en bas de page ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 3:07</span> </div> </a> <a href="/d/infinite-scroll-recommandations-alternatives" class="related-item yt-same-item"> <div class="title">Faut-il abandonner l'infinite scroll pour être correctement indexé par Google ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 3:38</span> </div> </a> <a href="/d/intersection-observer-fonctionne-avec-googlebot" class="related-item yt-same-item"> <div class="title">L'Intersection Observer est-il vraiment crawlé par Googlebot ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 4:08</span> </div> </a> <a href="/d/viewport-initial-probablement-10-000-pixels" class="related-item yt-same-item"> <div class="title">Pourquoi Googlebot utilise-t-il un viewport de 10 000 pixels ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 6:24</span> </div> </a> <a href="/d/contenu-important-doit-etre-accessible-sans-dependre-du-viewport" class="related-item yt-same-item"> <div class="title">Pourquoi Google refuse-t-il d'indexer le contenu qui dépend du viewport ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 9:23</span> </div> </a> <a href="/d/viewport-largeur-fixee-probablement-a-1024-pixels" class="related-item yt-same-item"> <div class="title">Pourquoi Google fixe-t-il la largeur du viewport de son crawler à 1024 pixels ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 10:11</span> </div> </a> <a href="/d/meta-tags-no-archive-via-javascript-comportement-incertain" class="related-item yt-same-item"> <div class="title">Les meta tags no-archive en JavaScript fonctionnent-ils vraiment ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 12:38</span> </div> </a> <a href="/d/meta-tags-analyses-avant-et-apres-le-rendu" class="related-item yt-same-item"> <div class="title">Google analyse-t-il vraiment les meta tags avant ET après le rendu JavaScript ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 14:24</span> </div> </a> </div> <a href="https://www.youtube.com/watch?v=Z-x9l3ayBYU" 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/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/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/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/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/disparite-de-contenu-entre-versions-mobile-et-desktop" class="related-item"> <div class="title">Pourquoi la disparité mobile/desktop tue-t-elle votre référencement en indexation mobile-first ?</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/meta-tags-analyses-avant-et-apres-le-rendu"> <div class="nav-label">« Precedent</div> <div class="nav-title">Meta tags analysés avant et après le rendu...</div> </a> <a href="/d/le-rendering-utilise-chrome-evergreen-mis-a-jour-regulierement" style="text-align: right;"> <div class="nav-label">Suivant »</div> <div class="nav-title">Le rendering utilise Chrome evergreen mis à jour r...</div> </a> </div> <div class="back-link"> <a href="/" class="btn">« Retour aux resultats</a> </div> <!-- Boutons de partage sociaux (bas de page) --> <div class="share-block" data-entity-type="declaration" data-entity-id="6767" data-share-lang="fr"> <div class="share-block-label"> <span class="share-icon" aria-hidden="true">🔗</span> <span>Partager cet article</span> </div> <div class="share-buttons"> <a class="share-btn share-btn-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fseoclaims.com%2Fd%2Frecommandation-meta-tags-coherents-sans-javascript" target="_blank" rel="noopener nofollow" data-network="facebook" aria-label="Partager sur Facebook" title="Partager sur Facebook"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M22 12.06C22 6.5 17.52 2 12 2S2 6.5 2 12.06c0 5 3.66 9.13 8.44 9.88v-6.99H7.9v-2.89h2.54V9.85c0-2.51 1.49-3.89 3.78-3.89 1.09 0 2.24.2 2.24.2v2.46h-1.26c-1.24 0-1.63.77-1.63 1.56v1.88h2.78l-.45 2.89h-2.33v6.99C18.34 21.19 22 17.06 22 12.06z"/></svg> <span class="share-btn-text">Facebook</span> </a> <a class="share-btn share-btn-twitter" href="https://twitter.com/intent/tweet?text=Selon%20Google%20%22Faut-il%20rendre%20les%20meta%20tags%20c%C3%B4t%C3%A9%20serveur%20ou%20accepter%20qu%27ils%20soient%20modifi%C3%A9s%20par%20JavaScript%20%3F%22%0A%0Ahttps%3A%2F%2Fseoclaims.com%2Fd%2Frecommandation-meta-tags-coherents-sans-javascript%0A%0ART%20ce%20post%20%21" target="_blank" rel="noopener nofollow" data-network="twitter" aria-label="Partager sur X" title="Partager sur X"> <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg> <span class="share-btn-text">X</span> </a> <a class="share-btn share-btn-linkedin" href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fseoclaims.com%2Fd%2Frecommandation-meta-tags-coherents-sans-javascript" target="_blank" rel="noopener nofollow" data-network="linkedin" aria-label="Partager sur LinkedIn" title="Partager sur LinkedIn"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M20.45 20.45h-3.55v-5.57c0-1.33-.03-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.36V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.38-1.85 3.61 0 4.28 2.38 4.28 5.47v6.27zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zm1.78 13.02H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z"/></svg> <span class="share-btn-text">LinkedIn</span> </a> <a class="share-btn share-btn-email" href="mailto:?subject=Selon%20Google%20%22Faut-il%20rendre%20les%20meta%20tags%20c%C3%B4t%C3%A9%20serveur%20ou%20accepter%20qu%27ils%20soient%20modifi%C3%A9s%20par%20JavaScript%20%3F%22&body=Selon%20Google%20%22Faut-il%20rendre%20les%20meta%20tags%20c%C3%B4t%C3%A9%20serveur%20ou%20accepter%20qu%27ils%20soient%20modifi%C3%A9s%20par%20JavaScript%20%3F%22%0A%0AGoogle%20recommande%20de%20servir%20des%20meta%20tags%20coh%C3%A9rents%20avant%20l%27ex%C3%A9cution%20de%20JavaScript%2C%20ou%20de%20les%20omettre%20totalement%20si%20vous%20ne%20pouvez%20pas%20garantir%20cette%20coh%C3%A9rence%0A%0Ahttps%3A%2F%2Fseoclaims.com%2Fd%2Frecommandation-meta-tags-coherents-sans-javascript%0A%0ART%20ce%20post%20%21" data-network="email" aria-label="Partager par email" title="Partager par email"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M2 4h20c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H2c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zm10 7L2.4 6h19.2L12 11zm0 2L2 7v11h20V7l-10 6z"/></svg> <span class="share-btn-text">Email</span> </a> <button type="button" class="share-btn share-btn-copy" data-network="copy" data-copy-url="https://seoclaims.com/d/recommandation-meta-tags-coherents-sans-javascript" data-label-copy="Copier le lien" data-label-copied="Lien copie !" aria-label="Copier le lien" title="Copier le lien"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg> <span class="share-btn-text">Copier le lien</span> </button> </div> </div> <script> (function(){ var blocks = document.querySelectorAll('.share-block:not([data-share-init])'); blocks.forEach(function(block){ block.setAttribute('data-share-init', '1'); var entityType = block.dataset.entityType; var entityId = block.dataset.entityId; var shareLang = block.dataset.shareLang || 'fr'; function track(network){ try { var fd = new FormData(); fd.append('entity_type', entityType); fd.append('entity_id', entityId); fd.append('network', network); fd.append('lang', shareLang); if (navigator.sendBeacon) { navigator.sendBeacon('https://seoclaims.com/api/share-track.php', fd); } else { fetch('https://seoclaims.com/api/share-track.php', { method:'POST', body: fd, keepalive: true }); } } catch(e){} } block.querySelectorAll('.share-btn').forEach(function(btn){ var network = btn.dataset.network; if (network === 'copy') { btn.addEventListener('click', function(){ var url = btn.dataset.copyUrl; var done = function(){ var txt = btn.querySelector('.share-btn-text'); var orig = btn.dataset.labelCopy; if (txt) txt.textContent = btn.dataset.labelCopied; btn.classList.add('share-btn-success'); setTimeout(function(){ if (txt) txt.textContent = orig; btn.classList.remove('share-btn-success'); }, 1800); }; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(url).then(done).catch(function(){ window.prompt(btn.dataset.labelCopy, url); }); } else { var ta = document.createElement('textarea'); ta.value = url; ta.style.position='fixed'; ta.style.left='-9999px'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); done(); } catch(e){ window.prompt(btn.dataset.labelCopy, url); } document.body.removeChild(ta); } track('copy'); }); return; } btn.addEventListener('click', function(e){ track(network); if (network === 'facebook' || network === 'twitter' || network === 'linkedin') { e.preventDefault(); window.open(btn.href, 'shareWindow', 'width=620,height=620,resizable=yes,scrollbars=yes'); } // email : laisse le mailto: par defaut }); }); }); })(); </script> </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="6767"> <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>