Dermelia

SEO Dashboard

dashboard Dashboard description Pages link Liens image Medias account_tree Sitemap settings Parametres
Pages totales—
Score moyen—
P1 avec HTML—
' + '' + // ===== SECTION 2: SEO Score ===== '
' + '

Score SEO

' + '
' + '
' + // Large SVG gauge '
' + renderLargeGauge(computedScore) + '
' + // Criteria grid '
' + '
' + checks.map(function(ch, i) { return '
' + '' + (ch.pass ? 'check_circle' : 'cancel') + '' + '
' + '
' + ch.label + '
' + '
' + esc(ch.tip || '') + '
' + '
' + '+' + ch.pts + '' + '
'; }).join('') + '
' + '
' + '
' + '
' + '
' + // ===== SECTION 3: Brief SEO ===== '
' + '

Brief SEO Complet

' + '
' + // Title tag '
' + '
' + 'Title Tag' + charCountBadge(p.titleTag, 50, 60) + '
' + copyBox(p.titleTag || '\u2014', 'title-' + slug) + '
' + // H1 '
' + 'H1' + copyBox(p.h1 || '\u2014', 'h1-' + slug) + '
' + '
' + // Meta description (full width) '
' + '
' + 'Meta Description' + charCountBadge(p.metaDescription, 120, 160) + '
' + copyBox(p.metaDescription || '\u2014', 'meta-' + slug) + '
' + // Heading structure '
' + '

Structure des titres

' + '
' + (p.h2s||[]).map(function(h2, i) { var relatedH3s = getH3sForH2(p, i); return '
' + '
' + 'H2' + '' + esc(h2) + '' + '
' + relatedH3s.map(function(h3) { return '
' + 'H3' + '' + esc(h3) + '' + '
'; }).join('') + '
'; }).join('') + '
' + '
' + // Secondary keywords (moved to keyword hero block above) // FAQ (p.faq && p.faq.length ? '
' + '

Questions FAQ (' + p.faq.length + ')

' + '
' + p.faq.map(function(q, i) { return '
' + '
' + 'help' + '' + esc(q) + '' + 'expand_more' + '
' + '
' + '

Reponse a rediger dans le contenu de la page.

' + '
' + '
'; }).join('') + '
' + '
' : '') + '
' + // ===== SECTION 4: Internal Links ===== '
' + '

Maillage & Liens

' + '
' + // Suggested internal links '
' + '

' + 'link' + 'Liens internes suggeres (' + suggestedLinks.length + ')' + '

' + (suggestedLinks.length ? '
' + suggestedLinks.map(function(l) { return '
' + 'subdirectory_arrow_right' + '
' + '
' + l.url + '
' + '
' + esc(l.keyword) + '
' + '
' + priorityBadge(l.priority) + '
'; }).join('') + '
' : '

Aucune suggestion de lien interne.

') + '
' + // Existing internal links '
' + '

' + 'inventory_2' + 'Liens internes existants' + '

' + '
' + 'link_off' + '

Disponible apres integration du contenu HTML

' + '
' + '
' + '
' + '
' + // External links '
' + '

' + 'open_in_new' + 'Liens externes suggeres' + '

' + '
' + 'travel_explore' + '

Sources d\'autorite a definir selon le sujet

' + '
' + '
' + // Backlinks '
' + '

' + 'arrow_back' + 'Backlinks recenses' + '

' + '
' + 'search' + '

Connecter Google Search Console pour les backlinks reels

' + '
' + '
' + '
' + '
' + // ===== SECTION 5: Media ===== '
' + '

Medias / Images

' + '
' + (p.htmlExists ? '

Images detectees dans le fichier HTML :

' + '
' + '
image

Chargement des images...

' + '
' : '
' + 'add_photo_alternate' + '

Aucune image ajoutee.
Les images seront ajoutees lors de l\'integration Astro.

' + '
' ) + '
' + '
' + // ===== SECTION 6: Content ===== '
' + '

Contenu actuel

' + '
' + (p.htmlExists && p.htmlFile ? '
' + '
' + 'code' + 'Fichier HTML : ' + esc(p.htmlFile) + '' + '
' + '' + 'visibility' + 'Previsualiser' + '' + '
' + '
' + '' + '
' : '
' + 'edit_note' + '

Contenu en attente de redaction

' + (p.briefFile ? 'Voir le brief SEO →' : '') + '
' ) + '
' + '
' + 'description' + 'Brief : ' + (p.briefFile ? '' + esc(p.briefFile) + '' : 'Non defini') + '' + '
' + '
' + 'schedule' + 'Genere : ' + (p.generatedDate || '\u2014') + '' + '
' + '
' + '
' + '
' + ''; // end pageDetailScroll mc.className = 'main-inner view-enter'; // Animate gauge after render requestAnimationFrame(function() { var gaugeCircle = document.getElementById('gaugeCircleAnim'); if (gaugeCircle) { var r = 70, c = 2*Math.PI*r, target = c - (computedScore/100)*c; gaugeCircle.style.strokeDashoffset = target; } // Setup section scroll observer setupSectionObserver(); // Load images if HTML exists if (p.htmlExists && p.htmlFile) { loadPageImages(p); } }); } // ===== HELPER: Large gauge ===== function renderLargeGauge(score) { var r = 70, c = 2*Math.PI*r; var color = scoreColor(score); return '
' + '' + '' + '' + '' + '
' + score + '
' + '
'; } // ===== HELPER: SEO checks ===== function computeSeoChecks(p, kw) { return [ { label: 'Title contient le mot-cle', pts: 15, pass: p.titleTag && p.titleTag.toLowerCase().indexOf(kw) >= 0, tip: p.titleTag ? (p.titleTag.toLowerCase().indexOf(kw) >= 0 ? 'Mot-cle present dans le title' : 'Ajouter "' + p.keyword + '" dans le title tag') : 'Title tag manquant' }, { label: 'Meta description presente et optimisee', pts: 10, pass: p.metaDescription && p.metaDescription.length >= 80, tip: p.metaDescription ? (p.metaDescription.length + ' caracteres') : 'Meta description manquante' }, { label: 'H1 contient le mot-cle', pts: 15, pass: p.h1 && p.h1.toLowerCase().indexOf(kw) >= 0, tip: p.h1 ? (p.h1.toLowerCase().indexOf(kw) >= 0 ? 'Mot-cle present dans le H1' : 'Ajouter "' + p.keyword + '" dans le H1') : 'H1 manquant' }, { label: 'Structure H2 conforme au brief', pts: 10, pass: p.h2s && p.h2s.length >= 3, tip: (p.h2s ? p.h2s.length : 0) + ' balises H2 definies' }, { label: 'FAQ presentes', pts: 10, pass: p.faq && p.faq.length > 0, tip: (p.faq ? p.faq.length : 0) + ' questions FAQ' }, { label: 'Mots-cles secondaires definis', pts: 10, pass: p.secondaryKeywords && p.secondaryKeywords.length > 0, tip: cleanSecondaryKws(p.secondaryKeywords).length + ' mots-cles secondaires' }, { label: 'Page HTML creee', pts: 15, pass: p.htmlExists, tip: p.htmlExists ? 'Fichier ' + (p.htmlFile||'') + ' present' : 'Page HTML a creer' }, { label: 'Alt texts images', pts: 5, pass: false, tip: 'Verifier apres integration' }, { label: 'Liens internes >= 3', pts: 5, pass: false, tip: 'A verifier dans le contenu final' }, { label: 'Liens externes >= 1', pts: 5, pass: false, tip: 'Ajouter au moins 1 lien externe d\'autorite' }, ]; } // ===== HELPER: Internal links ===== function computeInternalLinks(p) { return DATA.pages.filter(function(other) { if (other.slug === p.slug) return false; var otherKw = other.keyword.toLowerCase(); return (p.secondaryKeywords||[]).some(function(sk) { var skl = sk.toLowerCase().replace(/\*+/g,'').trim(); return skl.length > 5 && (skl.indexOf(otherKw) >= 0 || otherKw.indexOf(skl) >= 0); }); }).slice(0, 10); } // ===== HELPER: Copy box ===== function copyBox(text, id) { return '
' + '' + esc(text) + '' + '' + '
'; } function copyToClipboard(id, btn) { var el = document.getElementById('copyText-' + id); if (!el) return; var text = el.textContent; navigator.clipboard.writeText(text).then(function() { btn.classList.add('copied'); btn.querySelector('.material-symbols-outlined').textContent = 'check'; setTimeout(function() { btn.classList.remove('copied'); btn.querySelector('.material-symbols-outlined').textContent = 'content_copy'; }, 2000); }); } // ===== HELPER: Character count badge ===== function charCountBadge(text, min, max) { if (!text) return 'Manquant'; var len = text.length; var cls = len >= min && len <= max ? 'char-ok' : (len < min ? 'char-warn' : 'char-over'); return '' + len + '/' + max + ' car.'; } // ===== HELPER: KPI card ===== function kpiCard(label, value, color) { return '
' + '' + label + '' + '' + value + '' + '
'; } // ===== HELPER: Priority color ===== function priColorMap(pri) { var m = { P1:'#dc2626', P2:'#ea580c', P3:'#ca8a04', P4:'#2563eb', P5:'#9ca3af', Blog:'#7c3aed' }; return m[pri] || '#45474c'; } // ===== HELPER: Clean secondary keywords ===== function cleanSecondaryKws(kws) { if (!kws) return []; return kws.filter(function(k) { return k.indexOf('**') < 0 && k.indexOf('H2 :') < 0 && k.indexOf('H3 :') < 0 && k.indexOf('*') !== 0 && k.length < 60 && k.length > 3; }); } // ===== HELPER: Get H3s for H2 index ===== function getH3sForH2(p, h2Index) { if (!p.h3s || !p.h2s) return []; // Heuristic: distribute H3s among H2s var h3PerH2 = Math.ceil(p.h3s.length / p.h2s.length); var start = h2Index * h3PerH2; return p.h3s.slice(start, start + h3PerH2); } // ===== HELPER: Search from chip ===== function searchFromChip(kw) { filters.search = kw; navigateTo('#pages'); } // ===== HELPER: Section scroll observer ===== function setupSectionObserver() { var sections = document.querySelectorAll('section[id^="sec-"]'); var navLinks = document.querySelectorAll('#sectionNav a'); if (!sections.length || !navLinks.length) return; var observer = new IntersectionObserver(function(entries) { entries.forEach(function(entry) { if (entry.isIntersecting) { navLinks.forEach(function(a) { a.classList.remove('active-section'); }); var link = document.querySelector('#sectionNav a[data-section="' + entry.target.id + '"]'); if (link) link.classList.add('active-section'); } }); }, { rootMargin: '-100px 0px -70% 0px' }); sections.forEach(function(s) { observer.observe(s); }); // Smooth scroll for section nav clicks navLinks.forEach(function(a) { a.addEventListener('click', function(e) { e.preventDefault(); var target = document.getElementById(a.dataset.section); if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }); } // ===== HELPER: Load images from HTML page ===== function loadPageImages(p) { if (!p.htmlFile) return; fetch('./' + p.htmlFile) .then(function(r) { return r.text(); }) .then(function(html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, 'text/html'); var imgs = doc.querySelectorAll('img'); var grid = document.getElementById('mediaGrid'); if (!grid) return; if (imgs.length === 0) { grid.innerHTML = '
image_not_supported

Aucune image trouvee dans le HTML

'; return; } var cards = ''; imgs.forEach(function(img, i) { var src = img.getAttribute('src') || ''; var alt = img.getAttribute('alt') || ''; var savedAlt = localStorage.getItem('alt-' + p.slug + '-' + i); if (savedAlt !== null) alt = savedAlt; cards += '
' + '
' + '' + esc(alt) + '' + '
' + '
' + '' + '' + '
' + '' + esc(src.split('/').pop()) + '' + '
' + '
' + '
'; }); grid.innerHTML = cards; }) .catch(function() { var grid = document.getElementById('mediaGrid'); if (grid) grid.innerHTML = '
Impossible de charger le fichier HTML
'; }); } // ========== SHARED HELPERS ========== function scoreColor(score) { if (score >= 80) return '#16a34a'; if (score >= 60) return '#ca8a04'; if (score >= 40) return '#ea580c'; return '#dc2626'; } function posColor(pos) { if (pos === null || pos === undefined) return '#9ca3af'; if (pos < 3) return '#16a34a'; if (pos < 10) return '#ca8a04'; if (pos < 20) return '#ea580c'; return '#dc2626'; } function positionBadge(pos) { if (pos === null || pos === undefined) return '\u2014'; var cls = 'pos-red'; if (pos < 3) cls = 'pos-green'; else if (pos < 10) cls = 'pos-yellow'; else if (pos < 20) cls = 'pos-orange'; return '' + pos + ''; } function priorityBadge(pri) { return '' + pri + ''; } function scoreBar(score) { var color = scoreColor(score); return '
' + '
' + '' + score + '' + '
'; } function pipelineBadges(status) { var steps = [ { key: 'brief', label: 'B' }, { key: 'content', label: 'C' }, { key: 'html', label: 'H' }, { key: 'published', label: 'P' }, ]; return steps.map(function(s) { var done = status[s.key]; return '' + (done ? 'check_circle' : 'radio_button_unchecked') + ''; }).join(' '); } function sortArrow(key) { if (currentSort.key !== key) return '\u2195'; return currentSort.dir === 'asc' ? '\u2191' : '\u2193'; } function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // SEO API base URL var SEO_API = 'https://mc.brunel-digital.fr/seo-api'; async function loadKeywordResearch(keyword) { var container = document.getElementById('kwResearchContainer'); if (!container) return; container.innerHTML = '
Analyse SERP en cours pour ' + esc(keyword) + '...
'; try { var resp = await fetch(SEO_API + '/api/analysis?q=' + encodeURIComponent(keyword)); var data = await resp.json(); if (data.error) throw new Error(data.error); renderKeywordResults(container, data, keyword); } catch(e) { container.innerHTML = '
error

' + esc(e.message) + '

'; } } function renderKeywordResults(container, data, keyword) { var s = data.summary || {}; var serp = data.serp || {}; var gsc = data.gsc || {}; var gscRows = gsc.rows || []; var html = ''; // ====== ROW 1: Position Overview (glassmorphism) ====== html += '
'; html += '
'; html += '
'; // Search position var orgPos = s.dermeliaOrganicPos; html += '
'; html += '
languageSearch
'; html += orgPos ? '
#' + orgPos + '
' : '
Non classe
'; html += '
'; // Maps position var locPos = s.dermeliaLocalPos; html += '
'; html += '
pin_dropMaps
'; html += locPos ? '
#' + locPos + '
' : '
Absent
'; html += '
'; // GSC Clicks var gscMain = gscRows.length > 0 ? gscRows[0] : null; html += '
'; html += '
ads_clickClics
'; html += '
' + (gscMain ? gscMain.clicks : '0') + '
'; if (gscMain) html += '
' + gscMain.impressions + ' impr · ' + gscMain.ctr + '%
'; html += '
'; // Difficulty / Total results html += '
'; html += '
speedResultats
'; var totalR = (serp.searchInfo && serp.searchInfo.totalResults) ? serp.searchInfo.totalResults : 0; html += '
' + (totalR ? totalR.toLocaleString('fr') : '?') + '
'; html += '
'; html += '
'; // ====== ROW 2: GSC All Queries for this keyword (data table) ====== if (gscRows.length > 0) { html += '
'; html += '

bar_chartDonnees GSC (30j) — Requetes liees

'; html += '
'; html += ''; gscRows.forEach(function(r) { html += ''; }); html += '
RequeteClicsImpr.CTRPos.
' + esc(r.query) + '' + r.clicks + '' + r.impressions + '' + r.ctr + '%#' + r.position + '
'; } // ====== ROW 3: SERP Analysis — Organic + Local Pack side by side ====== html += '
'; // Organic top 10 html += '
'; html += '

languageTop 10 Organique

'; (serp.organic || []).slice(0, 10).forEach(function(r, i) { var isDerm = r.isDermelia; var bg = isDerm ? 'background:rgba(0,104,119,0.08);border-left:3px solid #006877' : (i % 2 === 0 ? 'background:rgba(0,0,0,0.015)' : ''); html += '
'; html += '' + r.position + ''; if (isDerm) html += 'star'; html += '
'; html += '
' + esc(r.title || '').substring(0, 55) + '
'; html += '
' + esc((r.link || '').replace(/^https?:\/\//, '').substring(0, 45)) + '
'; html += '
'; }); html += '
'; // Local Pack html += '
'; html += '

pin_dropLocal Pack (Maps)

'; if ((serp.localPack || []).length > 0) { serp.localPack.forEach(function(r, i) { var isDerm = r.isDermelia; var bg = isDerm ? 'background:rgba(0,104,119,0.08);border-left:3px solid #006877' : (i % 2 === 0 ? 'background:rgba(0,0,0,0.015)' : ''); html += '
'; html += '' + r.position + ''; if (isDerm) html += 'star'; html += '
'; html += '
' + esc(r.title || '') + '
'; if (r.rating) html += '
' + '★'.repeat(Math.round(r.rating)) + ' ' + r.rating + ' (' + (r.reviews || 0) + ' avis)
'; html += '
'; }); } else { html += '

Pas de Local Pack

'; } html += '
'; html += '
'; // ====== ROW 4: Related + Suggestions + PAA ====== html += '
'; // Related searches html += '
'; html += '

manage_searchRecherches associees

'; if (s.relatedQueries && s.relatedQueries.length) { html += '
'; s.relatedQueries.forEach(function(q) { html += '' + esc(q) + ''; }); html += '
'; } else { html += '

Aucune

'; } html += '
'; // Google Suggest html += '
'; html += '

auto_awesomeGoogle Suggest

'; if (data.suggestions && data.suggestions.length) { html += '
'; data.suggestions.forEach(function(q) { html += '' + esc(q) + ''; }); html += '
'; } else { html += '

Aucune

'; } html += '
'; html += '
'; // PAA (full width if present) if (s.peopleAlsoAsk && s.peopleAlsoAsk.length) { html += '
'; html += '

quizPeople Also Ask (opportunites FAQ / GEO)

'; html += '
'; s.peopleAlsoAsk.forEach(function(q) { html += '
help_outline' + esc(q) + '
'; }); html += '
'; } // ====== FOOTER: Explore other keyword + timestamp ====== html += '
'; html += '
search
'; html += ''; html += ''; html += '
'; html += '
' + new Date(data.timestamp).toLocaleString('fr-FR') + (data.serp && data.serp._cached ? ' · cache 24h' : ' · live') + '
'; container.innerHTML = html; } // Keyboard shortcut document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && currentPageSlug) goBack(); }); // Click handlers for sidebar links document.querySelectorAll('.sidebar-nav a').forEach(function(a) { a.addEventListener('click', function(e) { e.preventDefault(); navigateTo('#' + a.dataset.view); }); });