@consilioweb/payload-seo-analyzer 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -8
- package/dist/client.cjs +1179 -193
- package/dist/client.js +1179 -193
- package/dist/index.cjs +1378 -133
- package/dist/index.d.cts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +1379 -134
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -18,7 +18,7 @@ var MIN_WORDS_GENERIC = 300;
|
|
|
18
18
|
var MIN_WORDS_THIN = 100;
|
|
19
19
|
var MIN_WORDS_QUALITY_FAIL = 50;
|
|
20
20
|
var MIN_WORDS_QUALITY_WARN = 200;
|
|
21
|
-
var CORNERSTONE_MIN_WORDS =
|
|
21
|
+
var CORNERSTONE_MIN_WORDS = 600;
|
|
22
22
|
var THIN_AGING_MIN_WORDS = 500;
|
|
23
23
|
var KEYWORD_DENSITY_MAX = 3;
|
|
24
24
|
var KEYWORD_DENSITY_WARN = 2.5;
|
|
@@ -1452,6 +1452,9 @@ var fr = {
|
|
|
1452
1452
|
groupTechnical: "Technique",
|
|
1453
1453
|
groupAccessibility: "Accessibilit\xE9",
|
|
1454
1454
|
groupEcommerce: "E-commerce",
|
|
1455
|
+
groupEeat: "E-E-A-T",
|
|
1456
|
+
groupGeo: "GEO (IA)",
|
|
1457
|
+
groupHreflang: "Hreflang",
|
|
1455
1458
|
levelExcellent: "Excellent",
|
|
1456
1459
|
levelGood: "Bon",
|
|
1457
1460
|
levelFair: "Acceptable",
|
|
@@ -1460,6 +1463,8 @@ var fr = {
|
|
|
1460
1463
|
categoryImportant: "Important",
|
|
1461
1464
|
categoryBonus: "Bonus",
|
|
1462
1465
|
seoScore: "Score SEO",
|
|
1466
|
+
aiReadiness: "IA",
|
|
1467
|
+
aiReadinessTooltip: "Pr\xEAt pour l'IA \u2014 qualit\xE9 de structuration pour \xEAtre cit\xE9 par les moteurs g\xE9n\xE9ratifs (AI Overviews, ChatGPT, Perplexity). Distinct du score SEO.",
|
|
1463
1468
|
outOf100: "/ 100",
|
|
1464
1469
|
cornerstoneLabel: "PILIER",
|
|
1465
1470
|
checksPassed: "crit\xE8res valid\xE9s",
|
|
@@ -2025,6 +2030,9 @@ var en = {
|
|
|
2025
2030
|
groupTechnical: "Technical",
|
|
2026
2031
|
groupAccessibility: "Accessibility",
|
|
2027
2032
|
groupEcommerce: "E-commerce",
|
|
2033
|
+
groupEeat: "E-E-A-T",
|
|
2034
|
+
groupGeo: "GEO (AI)",
|
|
2035
|
+
groupHreflang: "Hreflang",
|
|
2028
2036
|
levelExcellent: "Excellent",
|
|
2029
2037
|
levelGood: "Good",
|
|
2030
2038
|
levelFair: "Fair",
|
|
@@ -2033,6 +2041,8 @@ var en = {
|
|
|
2033
2041
|
categoryImportant: "Important",
|
|
2034
2042
|
categoryBonus: "Bonus",
|
|
2035
2043
|
seoScore: "SEO Score",
|
|
2044
|
+
aiReadiness: "AI",
|
|
2045
|
+
aiReadinessTooltip: "AI-readiness \u2014 how well the page is structured to be cited by generative engines (AI Overviews, ChatGPT, Perplexity). Separate from the SEO score.",
|
|
2036
2046
|
outOf100: "/ 100",
|
|
2037
2047
|
cornerstoneLabel: "CORNERSTONE",
|
|
2038
2048
|
checksPassed: "checks passed",
|
|
@@ -2182,8 +2192,11 @@ var rulesFr = {
|
|
|
2182
2192
|
powerWordsPass: (count, words) => `Le title contient ${count} mot(s) puissant(s) : ${words}`,
|
|
2183
2193
|
powerWordsFail: 'Le title ne contient aucun mot puissant \u2014 Ajoutez un mot comme "gratuit", "guide", "complet" pour booster le CTR.',
|
|
2184
2194
|
powerWordsTip: "Les mots puissants (gratuit, exclusif, guide, complet, essentiel...) attirent l'attention dans les resultats de recherche.",
|
|
2195
|
+
pixelWidthLabel: "Largeur du title (pixels)",
|
|
2196
|
+
pixelWidthPass: (px) => `Largeur estimee ${px}px \u2014 sous le seuil de troncature SERP (~600px).`,
|
|
2197
|
+
pixelWidthWarn: (px) => `Largeur estimee ${px}px \u2014 risque de troncature dans Google (~600px). Informatif.`,
|
|
2185
2198
|
hasNumberLabel: "Nombre dans le title",
|
|
2186
|
-
hasNumberPass: "Le title contient un nombre \u2014
|
|
2199
|
+
hasNumberPass: "Le title contient un nombre \u2014 format type liste, souvent plus engageant.",
|
|
2187
2200
|
hasNumberFail: 'Aucun nombre dans le title \u2014 Les titres avec chiffres (ex: "5 astuces", "Top 10") attirent plus de clics.',
|
|
2188
2201
|
hasNumberTip: 'Ajoutez un nombre pour creer un titre de type liste (ex: "7 conseils pour...", "Les 3 erreurs a eviter").',
|
|
2189
2202
|
isQuestionLabel: "Title interrogatif",
|
|
@@ -2267,12 +2280,12 @@ var rulesFr = {
|
|
|
2267
2280
|
keywordIntroFail: (kw) => `Ajoutez le mot-cle "${kw}" dans les premieres phrases du contenu.`,
|
|
2268
2281
|
densityLabel: "Densite du mot-cle",
|
|
2269
2282
|
densityOverstuffed: (density) => `Densite du mot-cle : ${density}% \u2014 Trop eleve (>3%), risque de suroptimisation (keyword stuffing).`,
|
|
2270
|
-
densityHigh: (density) => `Densite du mot-cle : ${density}% \u2014 Legerement elevee
|
|
2271
|
-
densityPass: (density) => `Densite du mot-cle : ${density}% \u2014
|
|
2283
|
+
densityHigh: (density) => `Densite du mot-cle : ${density}% \u2014 Legerement elevee, restez naturel pour eviter la sur-optimisation.`,
|
|
2284
|
+
densityPass: (density) => `Densite du mot-cle : ${density}% \u2014 Usage naturel, aucun bourrage detecte.`,
|
|
2272
2285
|
densityPassWordLevel: (density) => `Les composants du mot-cle sont presents dans le contenu (densite estimee : ${density}%).`,
|
|
2273
2286
|
densityLowWordLevel: (density) => `Les composants du mot-cle sont presents mais peu frequents (densite estimee : ${density}%). Renforcez leur presence.`,
|
|
2274
2287
|
densityLow: (density) => `Densite du mot-cle : ${density}% \u2014 Trop faible. Visez 0,5% a 2,5%.`,
|
|
2275
|
-
densityMissing: (kw) => `Le mot-cle "${kw}" n'apparait
|
|
2288
|
+
densityMissing: (kw) => `Le mot-cle "${kw}" n'apparait pas dans le corps \u2014 privilegiez une couverture naturelle du sujet plutot que la repetition.`,
|
|
2276
2289
|
placeholderLabel: "Contenu placeholder",
|
|
2277
2290
|
placeholderFail: "Du contenu placeholder a ete detecte (lorem ipsum, TODO, etc.) \u2014 Remplacez par du vrai contenu.",
|
|
2278
2291
|
placeholderPass: "Aucun contenu placeholder detecte.",
|
|
@@ -2358,7 +2371,7 @@ var rulesFr = {
|
|
|
2358
2371
|
cornerstone: {
|
|
2359
2372
|
wordcountLabel: "Longueur du contenu pilier",
|
|
2360
2373
|
wordcountPass: (count) => `${count} mots \u2014 Le contenu pilier est suffisamment complet.`,
|
|
2361
|
-
wordcountFail: (count) => `${count} mots \u2014
|
|
2374
|
+
wordcountFail: (count) => `${count} mots \u2014 Contenu pilier trop leger ; developpez la couverture du sujet (sans viser un nombre de mots arbitraire).`,
|
|
2362
2375
|
internalLinksLabel: "Maillage interne du contenu pilier",
|
|
2363
2376
|
internalLinksPass: (count) => `${count} liens internes \u2014 Bon maillage pour un contenu pilier.`,
|
|
2364
2377
|
internalLinksFail: (count) => `${count} lien(s) interne(s) \u2014 Un contenu pilier devrait avoir au moins 5 liens internes vers du contenu associe.`,
|
|
@@ -2412,12 +2425,23 @@ var rulesFr = {
|
|
|
2412
2425
|
yearRefWarn: (oldest, current, last) => `Le contenu mentionne l'annee ${oldest} sans reference a ${current} ou ${last} \u2014 Contenu potentiellement obsolete.`,
|
|
2413
2426
|
yearRefPass: (year) => `Le contenu fait reference a l'annee en cours (${year}).`,
|
|
2414
2427
|
thinAgingLabel: "Contenu leger et ancien",
|
|
2415
|
-
thinAgingFail: (words, days) => `Seulement ${words} mots et non mis a jour depuis ${days} jours \u2014 Un contenu leger ancien perd rapidement en pertinence
|
|
2428
|
+
thinAgingFail: (words, days) => `Seulement ${words} mots et non mis a jour depuis ${days} jours \u2014 Un contenu leger ancien perd rapidement en pertinence.`,
|
|
2429
|
+
fakeRefreshLabel: "Faux rafraichissement",
|
|
2430
|
+
fakeRefreshWarn: (displayedDays, updatedDays) => `La date affichee (il y a ${displayedDays} j) est plus recente que la derniere modification reelle (il y a ${updatedDays} j) \u2014 evitez de rajeunir la date sans vraie mise a jour du contenu.`,
|
|
2431
|
+
fakeRefreshTip: "Google detecte la vraie date de modification. Mettez a jour le fond du contenu, pas seulement la date affichee."
|
|
2416
2432
|
},
|
|
2417
2433
|
schema: {
|
|
2418
2434
|
readinessLabel: "Donnees structurees",
|
|
2419
2435
|
readinessPass: "La page a suffisamment de metadonnees pour generer du JSON-LD (title, description, image).",
|
|
2420
|
-
readinessFail: "Completez le title, la description et ajoutez une image pour exploiter pleinement les donnees structurees."
|
|
2436
|
+
readinessFail: "Completez le title, la description et ajoutez une image pour exploiter pleinement les donnees structurees.",
|
|
2437
|
+
coverageLabel: "Couverture des donnees structurees",
|
|
2438
|
+
coverageOptional: "Les donnees structurees sont optionnelles pour ce type de page.",
|
|
2439
|
+
coveragePass: (type) => `Donnees CMS suffisantes pour generer un schema ${type} valide.`,
|
|
2440
|
+
coverageMissing: (type, fields) => `Schema ${type} attendu pour cette page \u2014 champ(s) requis manquant(s) dans le CMS : ${fields}.`,
|
|
2441
|
+
coverageMissingTip: "Completez ces champs, ou assurez-vous que le JSON-LD correspondant est injecte au rendu (frontend).",
|
|
2442
|
+
coverageRemind: (type, fields) => `Schema ${type} : champs CMS requis presents. Verifiez aussi ces champs requis non detectables automatiquement : ${fields}.`,
|
|
2443
|
+
faqNoRichResultLabel: "FAQ / donnees structurees",
|
|
2444
|
+
faqNoRichResult: "FAQPage detecte \u2014 markup valide et toujours lu par les moteurs et l'IA, mais Google ne genere plus de rich result FAQ (2026). Conservez le markup, n'attendez pas d'extrait enrichi en SERP."
|
|
2421
2445
|
},
|
|
2422
2446
|
technical: {
|
|
2423
2447
|
canonicalMissingLabel: "URL canonique",
|
|
@@ -2426,6 +2450,8 @@ var rulesFr = {
|
|
|
2426
2450
|
canonicalInvalidMessage: (url) => `URL canonique "${url}" invalide \u2014 Utilisez une URL absolue (https://...).`,
|
|
2427
2451
|
canonicalExternalLabel: "URL canonique",
|
|
2428
2452
|
canonicalExternalMessage: "URL canonique pointe vers un domaine externe \u2014 Verifiez que c'est intentionnel.",
|
|
2453
|
+
canonicalCrossLabel: "URL canonique",
|
|
2454
|
+
canonicalCrossMessage: (target) => `URL canonique pointe vers une AUTRE page (${target}) \u2014 cette page se desindexe au profit de cette URL. Verifiez que c'est intentionnel.`,
|
|
2429
2455
|
canonicalOkLabel: "URL canonique",
|
|
2430
2456
|
canonicalOkMessage: "URL canonique correctement definie.",
|
|
2431
2457
|
robotsNoindexLabel: "Robots noindex",
|
|
@@ -2550,8 +2576,11 @@ var rulesEn = {
|
|
|
2550
2576
|
powerWordsPass: (count, words) => `The title contains ${count} power word(s): ${words}`,
|
|
2551
2577
|
powerWordsFail: 'The title contains no power words \u2014 Add a word like "free", "guide", "ultimate" to boost CTR.',
|
|
2552
2578
|
powerWordsTip: "Power words (free, exclusive, guide, ultimate, essential...) attract attention in search results.",
|
|
2579
|
+
pixelWidthLabel: "Title pixel width",
|
|
2580
|
+
pixelWidthPass: (px) => `Estimated width ${px}px \u2014 under the SERP truncation threshold (~600px).`,
|
|
2581
|
+
pixelWidthWarn: (px) => `Estimated width ${px}px \u2014 may be truncated in Google (~600px). Informational.`,
|
|
2553
2582
|
hasNumberLabel: "Number in title",
|
|
2554
|
-
hasNumberPass: "The title contains a number \u2014
|
|
2583
|
+
hasNumberPass: "The title contains a number \u2014 list-style format, often more engaging.",
|
|
2555
2584
|
hasNumberFail: 'No number in the title \u2014 Titles with numbers (e.g. "5 tips", "Top 10") attract more clicks.',
|
|
2556
2585
|
hasNumberTip: 'Add a number to create a list-type title (e.g. "7 tips for...", "The 3 mistakes to avoid").',
|
|
2557
2586
|
isQuestionLabel: "Question title",
|
|
@@ -2635,12 +2664,12 @@ var rulesEn = {
|
|
|
2635
2664
|
keywordIntroFail: (kw) => `Add the keyword "${kw}" in the first sentences of the content.`,
|
|
2636
2665
|
densityLabel: "Keyword density",
|
|
2637
2666
|
densityOverstuffed: (density) => `Keyword density: ${density}% \u2014 Too high (>3%), risk of keyword stuffing.`,
|
|
2638
|
-
densityHigh: (density) => `Keyword density: ${density}% \u2014 Slightly high
|
|
2639
|
-
densityPass: (density) => `Keyword density: ${density}% \u2014
|
|
2667
|
+
densityHigh: (density) => `Keyword density: ${density}% \u2014 Slightly high; keep it natural to avoid over-optimisation.`,
|
|
2668
|
+
densityPass: (density) => `Keyword density: ${density}% \u2014 Natural usage, no stuffing detected.`,
|
|
2640
2669
|
densityPassWordLevel: (density) => `Keyword components are present in the content (estimated density: ${density}%).`,
|
|
2641
2670
|
densityLowWordLevel: (density) => `Keyword components are present but infrequent (estimated density: ${density}%). Strengthen their presence.`,
|
|
2642
2671
|
densityLow: (density) => `Keyword density: ${density}% \u2014 Too low. Aim for 0.5% to 2.5%.`,
|
|
2643
|
-
densityMissing: (kw) => `The keyword "${kw}"
|
|
2672
|
+
densityMissing: (kw) => `The keyword "${kw}" does not appear in the body \u2014 focus on covering the topic naturally rather than repeating it.`,
|
|
2644
2673
|
placeholderLabel: "Placeholder content",
|
|
2645
2674
|
placeholderFail: "Placeholder content detected (lorem ipsum, TODO, etc.) \u2014 Replace with real content.",
|
|
2646
2675
|
placeholderPass: "No placeholder content detected.",
|
|
@@ -2726,7 +2755,7 @@ var rulesEn = {
|
|
|
2726
2755
|
cornerstone: {
|
|
2727
2756
|
wordcountLabel: "Pillar content length",
|
|
2728
2757
|
wordcountPass: (count) => `${count} words \u2014 The pillar content is comprehensive enough.`,
|
|
2729
|
-
wordcountFail: (count) => `${count} words \u2014 Pillar content
|
|
2758
|
+
wordcountFail: (count) => `${count} words \u2014 Pillar content seems thin; expand topical coverage (without targeting an arbitrary word count).`,
|
|
2730
2759
|
internalLinksLabel: "Pillar content internal linking",
|
|
2731
2760
|
internalLinksPass: (count) => `${count} internal links \u2014 Good linking for pillar content.`,
|
|
2732
2761
|
internalLinksFail: (count) => `${count} internal link(s) \u2014 Pillar content should have at least 5 internal links to related content.`,
|
|
@@ -2780,12 +2809,23 @@ var rulesEn = {
|
|
|
2780
2809
|
yearRefWarn: (oldest, current, last) => `The content mentions year ${oldest} without reference to ${current} or ${last} \u2014 Potentially outdated.`,
|
|
2781
2810
|
yearRefPass: (year) => `The content references the current year (${year}).`,
|
|
2782
2811
|
thinAgingLabel: "Thin and old content",
|
|
2783
|
-
thinAgingFail: (words, days) => `Only ${words} words and not updated for ${days} days \u2014 Thin old content loses relevance quickly
|
|
2812
|
+
thinAgingFail: (words, days) => `Only ${words} words and not updated for ${days} days \u2014 Thin old content loses relevance quickly.`,
|
|
2813
|
+
fakeRefreshLabel: "Fake refresh",
|
|
2814
|
+
fakeRefreshWarn: (displayedDays, updatedDays) => `The displayed date (${displayedDays} days ago) is newer than the last real modification (${updatedDays} days ago) \u2014 avoid bumping the date without a real content update.`,
|
|
2815
|
+
fakeRefreshTip: "Google detects the real modification date. Update the actual content, not just the displayed date."
|
|
2784
2816
|
},
|
|
2785
2817
|
schema: {
|
|
2786
2818
|
readinessLabel: "Structured data",
|
|
2787
2819
|
readinessPass: "The page has sufficient metadata to generate JSON-LD (title, description, image).",
|
|
2788
|
-
readinessFail: "Complete the title, description and add an image to fully leverage structured data."
|
|
2820
|
+
readinessFail: "Complete the title, description and add an image to fully leverage structured data.",
|
|
2821
|
+
coverageLabel: "Structured data coverage",
|
|
2822
|
+
coverageOptional: "Structured data is optional for this page type.",
|
|
2823
|
+
coveragePass: (type) => `Enough CMS data to generate a valid ${type} schema.`,
|
|
2824
|
+
coverageMissing: (type, fields) => `${type} schema expected for this page \u2014 required field(s) missing in the CMS: ${fields}.`,
|
|
2825
|
+
coverageMissingTip: "Complete these fields, or make sure the matching JSON-LD is injected at render time (frontend).",
|
|
2826
|
+
coverageRemind: (type, fields) => `${type} schema: required CMS fields are present. Also confirm these required fields the analyzer cannot detect: ${fields}.`,
|
|
2827
|
+
faqNoRichResultLabel: "FAQ / structured data",
|
|
2828
|
+
faqNoRichResult: "FAQPage detected \u2014 markup is valid and still read by search/AI engines, but Google no longer renders FAQ rich results (2026). Keep the markup; do not expect an enhanced SERP snippet."
|
|
2789
2829
|
},
|
|
2790
2830
|
technical: {
|
|
2791
2831
|
canonicalMissingLabel: "Canonical URL",
|
|
@@ -2794,6 +2834,8 @@ var rulesEn = {
|
|
|
2794
2834
|
canonicalInvalidMessage: (url) => `Canonical URL "${url}" is invalid \u2014 Use an absolute URL (https://...).`,
|
|
2795
2835
|
canonicalExternalLabel: "Canonical URL",
|
|
2796
2836
|
canonicalExternalMessage: "Canonical URL points to an external domain \u2014 Verify this is intentional.",
|
|
2837
|
+
canonicalCrossLabel: "Canonical URL",
|
|
2838
|
+
canonicalCrossMessage: (target) => `Canonical URL points to a DIFFERENT page (${target}) \u2014 this page de-indexes itself in favor of that URL. Verify this is intentional.`,
|
|
2797
2839
|
canonicalOkLabel: "Canonical URL",
|
|
2798
2840
|
canonicalOkMessage: "Canonical URL is correctly defined.",
|
|
2799
2841
|
robotsNoindexLabel: "Robots noindex",
|
|
@@ -3146,6 +3188,21 @@ function getTranslations(locale) {
|
|
|
3146
3188
|
}
|
|
3147
3189
|
|
|
3148
3190
|
// src/rules/title.ts
|
|
3191
|
+
var TITLE_PIXEL_MAX = 600;
|
|
3192
|
+
var NARROW_CHARS = new Set("iIl.,:;|!'`jft()[]{}/\\".split(""));
|
|
3193
|
+
var WIDE_CHARS = new Set("mwMW@%".split(""));
|
|
3194
|
+
function estimateTitlePixelWidth(title) {
|
|
3195
|
+
let px = 0;
|
|
3196
|
+
for (const ch of title) {
|
|
3197
|
+
if (ch === " ") px += 5;
|
|
3198
|
+
else if (NARROW_CHARS.has(ch)) px += 5;
|
|
3199
|
+
else if (WIDE_CHARS.has(ch)) px += 15;
|
|
3200
|
+
else if (ch >= "A" && ch <= "Z") px += 12;
|
|
3201
|
+
else if (ch >= "0" && ch <= "9") px += 10;
|
|
3202
|
+
else px += 9;
|
|
3203
|
+
}
|
|
3204
|
+
return Math.round(px);
|
|
3205
|
+
}
|
|
3149
3206
|
function checkTitle(input, ctx) {
|
|
3150
3207
|
const checks = [];
|
|
3151
3208
|
const r = getTranslations(ctx.locale).rules.title;
|
|
@@ -3171,8 +3228,10 @@ function checkTitle(input, ctx) {
|
|
|
3171
3228
|
label: r.lengthLabel,
|
|
3172
3229
|
status: "warning",
|
|
3173
3230
|
message: r.lengthShort(titleLen),
|
|
3174
|
-
|
|
3175
|
-
weight
|
|
3231
|
+
// SEO desintox: title length is a SERP-display hint, NOT a ranking factor
|
|
3232
|
+
// (Google rewrites 60%+ of titles). Informational weight, never critical.
|
|
3233
|
+
category: "bonus",
|
|
3234
|
+
weight: 1,
|
|
3176
3235
|
group: "title",
|
|
3177
3236
|
tip: r.lengthShortTip
|
|
3178
3237
|
});
|
|
@@ -3182,8 +3241,8 @@ function checkTitle(input, ctx) {
|
|
|
3182
3241
|
label: r.lengthLabel,
|
|
3183
3242
|
status: "warning",
|
|
3184
3243
|
message: r.lengthLong(titleLen),
|
|
3185
|
-
category: "
|
|
3186
|
-
weight:
|
|
3244
|
+
category: "bonus",
|
|
3245
|
+
weight: 1,
|
|
3187
3246
|
group: "title",
|
|
3188
3247
|
tip: r.lengthLongTip
|
|
3189
3248
|
});
|
|
@@ -3193,11 +3252,21 @@ function checkTitle(input, ctx) {
|
|
|
3193
3252
|
label: r.lengthLabel,
|
|
3194
3253
|
status: "pass",
|
|
3195
3254
|
message: r.lengthPass(titleLen),
|
|
3196
|
-
category: "
|
|
3197
|
-
weight:
|
|
3255
|
+
category: "bonus",
|
|
3256
|
+
weight: 1,
|
|
3198
3257
|
group: "title"
|
|
3199
3258
|
});
|
|
3200
3259
|
}
|
|
3260
|
+
const titlePx = estimateTitlePixelWidth(title);
|
|
3261
|
+
checks.push({
|
|
3262
|
+
id: "title-pixel-width",
|
|
3263
|
+
label: r.pixelWidthLabel,
|
|
3264
|
+
status: titlePx > TITLE_PIXEL_MAX ? "warning" : "pass",
|
|
3265
|
+
message: titlePx > TITLE_PIXEL_MAX ? r.pixelWidthWarn(titlePx) : r.pixelWidthPass(titlePx),
|
|
3266
|
+
category: "bonus",
|
|
3267
|
+
weight: 0,
|
|
3268
|
+
group: "title"
|
|
3269
|
+
});
|
|
3201
3270
|
if (kw) {
|
|
3202
3271
|
const titleNorm = normalizeForComparison(title);
|
|
3203
3272
|
const kwPresent = keywordMatchesText(kw, titleNorm);
|
|
@@ -3733,44 +3802,15 @@ function checkContent(input, ctx) {
|
|
|
3733
3802
|
weight: 2,
|
|
3734
3803
|
group: "content"
|
|
3735
3804
|
});
|
|
3736
|
-
} else if (density >= KEYWORD_DENSITY_MIN) {
|
|
3737
|
-
checks.push({
|
|
3738
|
-
id: "content-keyword-density",
|
|
3739
|
-
label: r.densityLabel,
|
|
3740
|
-
status: "pass",
|
|
3741
|
-
message: exactCount > 0 ? r.densityPass(density.toFixed(1)) : r.densityPassWordLevel(density.toFixed(1)),
|
|
3742
|
-
category: "important",
|
|
3743
|
-
weight: 2,
|
|
3744
|
-
group: "content"
|
|
3745
|
-
});
|
|
3746
|
-
} else if (wordLevelMatch) {
|
|
3747
|
-
checks.push({
|
|
3748
|
-
id: "content-keyword-density",
|
|
3749
|
-
label: r.densityLabel,
|
|
3750
|
-
status: "warning",
|
|
3751
|
-
message: r.densityLowWordLevel(density.toFixed(1)),
|
|
3752
|
-
category: "important",
|
|
3753
|
-
weight: 2,
|
|
3754
|
-
group: "content"
|
|
3755
|
-
});
|
|
3756
|
-
} else if (exactCount > 0) {
|
|
3757
|
-
checks.push({
|
|
3758
|
-
id: "content-keyword-density",
|
|
3759
|
-
label: r.densityLabel,
|
|
3760
|
-
status: "warning",
|
|
3761
|
-
message: r.densityLow(density.toFixed(1)),
|
|
3762
|
-
category: "important",
|
|
3763
|
-
weight: 2,
|
|
3764
|
-
group: "content"
|
|
3765
|
-
});
|
|
3766
3805
|
} else {
|
|
3806
|
+
const present = exactCount > 0 || wordLevelMatch;
|
|
3767
3807
|
checks.push({
|
|
3768
3808
|
id: "content-keyword-density",
|
|
3769
3809
|
label: r.densityLabel,
|
|
3770
|
-
status: "
|
|
3771
|
-
message: r.densityMissing(input.focusKeyword || normalizedKeyword),
|
|
3772
|
-
category: "
|
|
3773
|
-
weight:
|
|
3810
|
+
status: "pass",
|
|
3811
|
+
message: present ? r.densityPass(density.toFixed(1)) : r.densityMissing(input.focusKeyword || normalizedKeyword),
|
|
3812
|
+
category: "bonus",
|
|
3813
|
+
weight: 0,
|
|
3774
3814
|
group: "content"
|
|
3775
3815
|
});
|
|
3776
3816
|
}
|
|
@@ -3817,39 +3857,17 @@ function checkContent(input, ctx) {
|
|
|
3817
3857
|
const tiersWithKw = [tier1, tier2, tier3].filter(
|
|
3818
3858
|
(t) => keywordMatchesText(normalizedKeyword, t)
|
|
3819
3859
|
).length;
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
}
|
|
3830
|
-
}
|
|
3831
|
-
checks.push({
|
|
3832
|
-
id: "content-keyword-distribution",
|
|
3833
|
-
label: r.distributionLabel,
|
|
3834
|
-
status: "warning",
|
|
3835
|
-
message: r.distributionWarn,
|
|
3836
|
-
category: "important",
|
|
3837
|
-
weight: 2,
|
|
3838
|
-
group: "content",
|
|
3839
|
-
tip: r.distributionWarnTip
|
|
3840
|
-
});
|
|
3841
|
-
} else {
|
|
3842
|
-
checks.push({
|
|
3843
|
-
id: "content-keyword-distribution",
|
|
3844
|
-
label: r.distributionLabel,
|
|
3845
|
-
status: "fail",
|
|
3846
|
-
message: r.distributionFail,
|
|
3847
|
-
category: "important",
|
|
3848
|
-
weight: 2,
|
|
3849
|
-
group: "content",
|
|
3850
|
-
tip: r.distributionFailTip
|
|
3851
|
-
});
|
|
3852
|
-
}
|
|
3860
|
+
const wellDistributed = tiersWithKw >= 2;
|
|
3861
|
+
checks.push({
|
|
3862
|
+
id: "content-keyword-distribution",
|
|
3863
|
+
label: r.distributionLabel,
|
|
3864
|
+
status: wellDistributed ? "pass" : "warning",
|
|
3865
|
+
message: wellDistributed ? r.distributionPass(tiersWithKw) : r.distributionWarn,
|
|
3866
|
+
category: "bonus",
|
|
3867
|
+
weight: 0,
|
|
3868
|
+
group: "content",
|
|
3869
|
+
...wellDistributed ? {} : { tip: r.distributionWarnTip }
|
|
3870
|
+
});
|
|
3853
3871
|
}
|
|
3854
3872
|
if (wordCount > 500) {
|
|
3855
3873
|
const allLists = [];
|
|
@@ -4341,23 +4359,161 @@ function checkSocial(input, ctx) {
|
|
|
4341
4359
|
return checks;
|
|
4342
4360
|
}
|
|
4343
4361
|
|
|
4362
|
+
// src/rules/schema-requirements.ts
|
|
4363
|
+
var SCHEMA_REQUIREMENTS = {
|
|
4364
|
+
Article: {
|
|
4365
|
+
required: ["headline", "image"],
|
|
4366
|
+
recommended: ["author", "datePublished", "dateModified", "publisher"],
|
|
4367
|
+
richResult: true
|
|
4368
|
+
},
|
|
4369
|
+
Product: {
|
|
4370
|
+
// Google needs name + at least one of offers / review / aggregateRating
|
|
4371
|
+
required: ["name", "offers"],
|
|
4372
|
+
recommended: ["image", "brand", "aggregateRating", "review", "sku"],
|
|
4373
|
+
richResult: true
|
|
4374
|
+
},
|
|
4375
|
+
LocalBusiness: {
|
|
4376
|
+
required: ["name", "address"],
|
|
4377
|
+
recommended: ["telephone", "openingHours", "geo", "priceRange"],
|
|
4378
|
+
richResult: true
|
|
4379
|
+
},
|
|
4380
|
+
BreadcrumbList: {
|
|
4381
|
+
required: ["itemListElement"],
|
|
4382
|
+
recommended: [],
|
|
4383
|
+
richResult: true
|
|
4384
|
+
},
|
|
4385
|
+
Organization: {
|
|
4386
|
+
required: ["name", "url"],
|
|
4387
|
+
recommended: ["logo", "sameAs"],
|
|
4388
|
+
richResult: false
|
|
4389
|
+
},
|
|
4390
|
+
Person: {
|
|
4391
|
+
required: ["name"],
|
|
4392
|
+
recommended: ["sameAs", "jobTitle", "image", "url"],
|
|
4393
|
+
richResult: false
|
|
4394
|
+
},
|
|
4395
|
+
FAQPage: {
|
|
4396
|
+
required: ["mainEntity"],
|
|
4397
|
+
recommended: [],
|
|
4398
|
+
// FAQ rich results removed by Google (May 2026). Markup still useful for AI/search understanding.
|
|
4399
|
+
richResult: false
|
|
4400
|
+
},
|
|
4401
|
+
Event: {
|
|
4402
|
+
required: ["name", "startDate", "location"],
|
|
4403
|
+
recommended: ["endDate", "offers", "image", "performer"],
|
|
4404
|
+
richResult: true
|
|
4405
|
+
},
|
|
4406
|
+
Recipe: {
|
|
4407
|
+
required: ["name", "image", "recipeIngredient", "recipeInstructions"],
|
|
4408
|
+
recommended: ["nutrition", "aggregateRating", "totalTime", "recipeYield"],
|
|
4409
|
+
richResult: true
|
|
4410
|
+
},
|
|
4411
|
+
Video: {
|
|
4412
|
+
required: ["name", "thumbnailUrl", "uploadDate"],
|
|
4413
|
+
recommended: ["duration", "contentUrl", "description"],
|
|
4414
|
+
richResult: true
|
|
4415
|
+
}
|
|
4416
|
+
};
|
|
4417
|
+
var CMS_VERIFIABLE_SCHEMA_FIELDS = /* @__PURE__ */ new Set([
|
|
4418
|
+
"headline",
|
|
4419
|
+
"name",
|
|
4420
|
+
"image",
|
|
4421
|
+
"url",
|
|
4422
|
+
"itemListElement",
|
|
4423
|
+
"mainEntity"
|
|
4424
|
+
]);
|
|
4425
|
+
|
|
4344
4426
|
// src/rules/schema.ts
|
|
4427
|
+
var FAQ_BLOCK_TYPES = /* @__PURE__ */ new Set(["faq", "FAQ", "faqBlock", "faqs"]);
|
|
4428
|
+
function hasFaqBlock(blocks) {
|
|
4429
|
+
if (!Array.isArray(blocks)) return false;
|
|
4430
|
+
return blocks.some((block) => {
|
|
4431
|
+
if (!block || typeof block !== "object") return false;
|
|
4432
|
+
const t = block.blockType;
|
|
4433
|
+
return typeof t === "string" && FAQ_BLOCK_TYPES.has(t);
|
|
4434
|
+
});
|
|
4435
|
+
}
|
|
4436
|
+
function detectExpectedSchemaType(input, ctx) {
|
|
4437
|
+
if (input.isProduct) return "Product";
|
|
4438
|
+
if (input.isPost || ctx.pageType === "blog") return "Article";
|
|
4439
|
+
if (ctx.pageType === "local-seo") return "LocalBusiness";
|
|
4440
|
+
if (ctx.pageType === "agency") return "Organization";
|
|
4441
|
+
if (ctx.pageType === "legal" || ctx.pageType === "contact" || ctx.pageType === "form") return null;
|
|
4442
|
+
return "Article";
|
|
4443
|
+
}
|
|
4345
4444
|
function checkSchema(input, ctx) {
|
|
4346
4445
|
const checks = [];
|
|
4347
4446
|
const r = getTranslations(ctx.locale).rules.schema;
|
|
4348
|
-
const
|
|
4349
|
-
const
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4447
|
+
const faqPresent = hasFaqBlock(input.blocks);
|
|
4448
|
+
const present = {
|
|
4449
|
+
headline: !!(input.metaTitle || input.heroTitle) || ctx.allHeadings.some((h) => h.tag === "h1"),
|
|
4450
|
+
name: !!(input.metaTitle || input.heroTitle),
|
|
4451
|
+
image: ctx.imageStats.total > 0 || !!input.metaImage,
|
|
4452
|
+
url: true,
|
|
4453
|
+
itemListElement: !!input.slug,
|
|
4454
|
+
mainEntity: faqPresent
|
|
4455
|
+
};
|
|
4456
|
+
const expected = detectExpectedSchemaType(input, ctx);
|
|
4457
|
+
if (expected === null) {
|
|
4458
|
+
checks.push({
|
|
4459
|
+
id: "schema-coverage",
|
|
4460
|
+
label: r.coverageLabel,
|
|
4461
|
+
status: "pass",
|
|
4462
|
+
message: r.coverageOptional,
|
|
4463
|
+
category: "bonus",
|
|
4464
|
+
weight: 0,
|
|
4465
|
+
group: "schema"
|
|
4466
|
+
});
|
|
4467
|
+
} else {
|
|
4468
|
+
const reqDef = SCHEMA_REQUIREMENTS[expected];
|
|
4469
|
+
const knownMissing = reqDef.required.filter(
|
|
4470
|
+
(f) => CMS_VERIFIABLE_SCHEMA_FIELDS.has(f) && !present[f]
|
|
4471
|
+
);
|
|
4472
|
+
const unverifiable = reqDef.required.filter((f) => !CMS_VERIFIABLE_SCHEMA_FIELDS.has(f));
|
|
4473
|
+
if (knownMissing.length > 0) {
|
|
4474
|
+
checks.push({
|
|
4475
|
+
id: "schema-coverage",
|
|
4476
|
+
label: r.coverageLabel,
|
|
4477
|
+
status: "warning",
|
|
4478
|
+
message: r.coverageMissing(expected, knownMissing.join(", ")),
|
|
4479
|
+
category: "bonus",
|
|
4480
|
+
weight: 1,
|
|
4481
|
+
group: "schema",
|
|
4482
|
+
tip: r.coverageMissingTip
|
|
4483
|
+
});
|
|
4484
|
+
} else if (unverifiable.length > 0) {
|
|
4485
|
+
checks.push({
|
|
4486
|
+
id: "schema-coverage",
|
|
4487
|
+
label: r.coverageLabel,
|
|
4488
|
+
status: "pass",
|
|
4489
|
+
message: r.coverageRemind(expected, unverifiable.join(", ")),
|
|
4490
|
+
category: "bonus",
|
|
4491
|
+
weight: 0,
|
|
4492
|
+
group: "schema"
|
|
4493
|
+
});
|
|
4494
|
+
} else {
|
|
4495
|
+
checks.push({
|
|
4496
|
+
id: "schema-coverage",
|
|
4497
|
+
label: r.coverageLabel,
|
|
4498
|
+
status: "pass",
|
|
4499
|
+
message: r.coveragePass(expected),
|
|
4500
|
+
category: "bonus",
|
|
4501
|
+
weight: 1,
|
|
4502
|
+
group: "schema"
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
if (faqPresent && true) {
|
|
4507
|
+
checks.push({
|
|
4508
|
+
id: "schema-faq-no-rich-result",
|
|
4509
|
+
label: r.faqNoRichResultLabel,
|
|
4510
|
+
status: "pass",
|
|
4511
|
+
message: r.faqNoRichResult,
|
|
4512
|
+
category: "bonus",
|
|
4513
|
+
weight: 0,
|
|
4514
|
+
group: "schema"
|
|
4515
|
+
});
|
|
4516
|
+
}
|
|
4361
4517
|
return checks;
|
|
4362
4518
|
}
|
|
4363
4519
|
|
|
@@ -4479,8 +4635,8 @@ function checkReadability(input, ctx) {
|
|
|
4479
4635
|
label: r.passiveLabelFail,
|
|
4480
4636
|
status: "warning",
|
|
4481
4637
|
message: r.passiveFail(passiveSentences.length, sentences.length, Math.round(passiveRatio * 100)),
|
|
4482
|
-
category: "
|
|
4483
|
-
weight:
|
|
4638
|
+
category: "bonus",
|
|
4639
|
+
weight: 0,
|
|
4484
4640
|
group: "readability"
|
|
4485
4641
|
});
|
|
4486
4642
|
} else {
|
|
@@ -4489,8 +4645,8 @@ function checkReadability(input, ctx) {
|
|
|
4489
4645
|
label: r.passiveLabelPass,
|
|
4490
4646
|
status: "pass",
|
|
4491
4647
|
message: r.passivePass(Math.round(passiveRatio * 100)),
|
|
4492
|
-
category: "
|
|
4493
|
-
weight:
|
|
4648
|
+
category: "bonus",
|
|
4649
|
+
weight: 0,
|
|
4494
4650
|
group: "readability"
|
|
4495
4651
|
});
|
|
4496
4652
|
}
|
|
@@ -4505,7 +4661,7 @@ function checkReadability(input, ctx) {
|
|
|
4505
4661
|
status: "warning",
|
|
4506
4662
|
message: r.transitionsFail(Math.round(transitionRatio * 100)),
|
|
4507
4663
|
category: "bonus",
|
|
4508
|
-
weight:
|
|
4664
|
+
weight: 0,
|
|
4509
4665
|
group: "readability"
|
|
4510
4666
|
});
|
|
4511
4667
|
} else {
|
|
@@ -4515,7 +4671,7 @@ function checkReadability(input, ctx) {
|
|
|
4515
4671
|
status: "pass",
|
|
4516
4672
|
message: r.transitionsPass(Math.round(transitionRatio * 100)),
|
|
4517
4673
|
category: "bonus",
|
|
4518
|
-
weight:
|
|
4674
|
+
weight: 0,
|
|
4519
4675
|
group: "readability"
|
|
4520
4676
|
});
|
|
4521
4677
|
}
|
|
@@ -4963,10 +5119,34 @@ function checkFreshness(input, ctx) {
|
|
|
4963
5119
|
group: "freshness"
|
|
4964
5120
|
});
|
|
4965
5121
|
}
|
|
5122
|
+
if (input.displayedDate && input.updatedAt) {
|
|
5123
|
+
const displayedDays = daysSince(input.displayedDate);
|
|
5124
|
+
const updatedDays = daysSince(input.updatedAt);
|
|
5125
|
+
if (displayedDays !== Infinity && updatedDays !== Infinity && updatedDays - displayedDays > 60) {
|
|
5126
|
+
checks.push({
|
|
5127
|
+
id: "freshness-fake-refresh",
|
|
5128
|
+
label: r.fakeRefreshLabel,
|
|
5129
|
+
status: "warning",
|
|
5130
|
+
message: r.fakeRefreshWarn(displayedDays, updatedDays),
|
|
5131
|
+
category: "bonus",
|
|
5132
|
+
weight: 0,
|
|
5133
|
+
group: "freshness",
|
|
5134
|
+
tip: r.fakeRefreshTip
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
4966
5138
|
return checks;
|
|
4967
5139
|
}
|
|
4968
5140
|
|
|
4969
5141
|
// src/rules/technical.ts
|
|
5142
|
+
function isCrossCanonical(canonical, siteUrl, slug) {
|
|
5143
|
+
const norm = (p) => p.replace(/[?#].*$/, "").replace(/^\/+/, "").replace(/\/+$/, "").toLowerCase();
|
|
5144
|
+
const canonicalPath = norm(canonical.slice(siteUrl.length));
|
|
5145
|
+
const selfPath = norm(slug);
|
|
5146
|
+
const selfIsHome = selfPath === "" || selfPath === "home";
|
|
5147
|
+
if (selfIsHome) return canonicalPath !== "" && canonicalPath !== "home";
|
|
5148
|
+
return canonicalPath !== selfPath;
|
|
5149
|
+
}
|
|
4970
5150
|
function checkTechnical(input, ctx) {
|
|
4971
5151
|
const checks = [];
|
|
4972
5152
|
const r = getTranslations(ctx.locale).rules.technical;
|
|
@@ -5004,6 +5184,16 @@ function checkTechnical(input, ctx) {
|
|
|
5004
5184
|
weight: 2,
|
|
5005
5185
|
group: "technical"
|
|
5006
5186
|
});
|
|
5187
|
+
} else if (siteUrl && input.slug && !input.isGlobal && isCrossCanonical(canonical, siteUrl, input.slug)) {
|
|
5188
|
+
checks.push({
|
|
5189
|
+
id: "canonical-cross",
|
|
5190
|
+
label: r.canonicalCrossLabel,
|
|
5191
|
+
status: "warning",
|
|
5192
|
+
message: r.canonicalCrossMessage(canonical),
|
|
5193
|
+
category: "important",
|
|
5194
|
+
weight: 2,
|
|
5195
|
+
group: "technical"
|
|
5196
|
+
});
|
|
5007
5197
|
} else {
|
|
5008
5198
|
checks.push({
|
|
5009
5199
|
id: "canonical-ok",
|
|
@@ -5296,6 +5486,336 @@ function checkEcommerce(input, ctx) {
|
|
|
5296
5486
|
return checks;
|
|
5297
5487
|
}
|
|
5298
5488
|
|
|
5489
|
+
// src/rules/eeat.ts
|
|
5490
|
+
var STRINGS = {
|
|
5491
|
+
fr: {
|
|
5492
|
+
authorLabel: "Auteur attribu\xE9 (E-E-A-T)",
|
|
5493
|
+
authorPass: "Un auteur est attribu\xE9 \u2014 bon signal de transparence E-E-A-T.",
|
|
5494
|
+
authorFail: "Aucun auteur identifi\xE9 \u2014 attribuez un auteur r\xE9el pour renforcer la confiance (E-E-A-T).",
|
|
5495
|
+
authorTip: "Ajoutez un auteur avec une courte bio et un lien vers son profil (Person schema + sameAs).",
|
|
5496
|
+
authorEntityLabel: "Entit\xE9 auteur (profil / sameAs)",
|
|
5497
|
+
authorEntityPass: "L'auteur a un lien de profil \u2014 renforce l'entit\xE9 (sameAs) pour les moteurs et l'IA.",
|
|
5498
|
+
authorEntityFail: "L'auteur n'a pas de lien de profil \u2014 ajoutez une URL (LinkedIn, page auteur) comme sameAs.",
|
|
5499
|
+
datesLabel: "Dates de publication / mise \xE0 jour",
|
|
5500
|
+
datesPass: "Dates de publication et de mise \xE0 jour disponibles \u2014 bon pour la fra\xEEcheur et la confiance.",
|
|
5501
|
+
datesFail: "Date de publication ou de mise \xE0 jour manquante \u2014 exposez datePublished et dateModified.",
|
|
5502
|
+
sourcesLabel: "Sources externes cit\xE9es",
|
|
5503
|
+
sourcesPass: (n) => `${n} lien(s) vers des sources externes \u2014 renforce la cr\xE9dibilit\xE9 et l'E-E-A-T.`,
|
|
5504
|
+
sourcesFail: "Aucune source externe cit\xE9e \u2014 liez des sources fiables pour appuyer vos affirmations.",
|
|
5505
|
+
sourcesTip: "Citez des \xE9tudes, donn\xE9es officielles ou r\xE9f\xE9rences sectorielles avec des liens sortants.",
|
|
5506
|
+
dataLabel: "Donn\xE9es originales / chiffr\xE9es",
|
|
5507
|
+
dataPass: "Le contenu pr\xE9sente des donn\xE9es chiffr\xE9es \u2014 signal d'expertise et de contenu original.",
|
|
5508
|
+
dataFail: "Peu de donn\xE9es chiffr\xE9es d\xE9tect\xE9es \u2014 ajoutez des chiffres, statistiques ou r\xE9sultats concrets.",
|
|
5509
|
+
dataTip: "Le contenu d\xE9montrant une exp\xE9rience de premi\xE8re main (donn\xE9es, chiffres, exemples v\xE9cus) est le levier de mont\xE9e n\xB01 en 2026."
|
|
5510
|
+
},
|
|
5511
|
+
en: {
|
|
5512
|
+
authorLabel: "Attributed author (E-E-A-T)",
|
|
5513
|
+
authorPass: "An author is attributed \u2014 good E-E-A-T transparency signal.",
|
|
5514
|
+
authorFail: "No identified author \u2014 attribute a real author to strengthen trust (E-E-A-T).",
|
|
5515
|
+
authorTip: "Add an author with a short bio and a link to their profile (Person schema + sameAs).",
|
|
5516
|
+
authorEntityLabel: "Author entity (profile / sameAs)",
|
|
5517
|
+
authorEntityPass: "The author has a profile link \u2014 strengthens the entity (sameAs) for search and AI.",
|
|
5518
|
+
authorEntityFail: "The author has no profile link \u2014 add a URL (LinkedIn, author page) as sameAs.",
|
|
5519
|
+
datesLabel: "Published / updated dates",
|
|
5520
|
+
datesPass: "Published and modified dates available \u2014 good for freshness and trust.",
|
|
5521
|
+
datesFail: "Published or modified date missing \u2014 expose datePublished and dateModified.",
|
|
5522
|
+
sourcesLabel: "External sources cited",
|
|
5523
|
+
sourcesPass: (n) => `${n} link(s) to external sources \u2014 strengthens credibility and E-E-A-T.`,
|
|
5524
|
+
sourcesFail: "No external source cited \u2014 link reliable sources to back your claims.",
|
|
5525
|
+
sourcesTip: "Cite studies, official data or industry references with outbound links.",
|
|
5526
|
+
dataLabel: "Original / quantitative data",
|
|
5527
|
+
dataPass: "The content includes quantitative data \u2014 a signal of expertise and original content.",
|
|
5528
|
+
dataFail: "Little quantitative data detected \u2014 add figures, statistics or concrete results.",
|
|
5529
|
+
dataTip: "First-hand-experience content (data, figures, lived examples) is the #1 visibility lever in 2026."
|
|
5530
|
+
}
|
|
5531
|
+
};
|
|
5532
|
+
var EEAT_SKIP_PAGE_TYPES = /* @__PURE__ */ new Set(["legal", "contact", "form", "home"]);
|
|
5533
|
+
function checkEeat(input, ctx) {
|
|
5534
|
+
const checks = [];
|
|
5535
|
+
if (EEAT_SKIP_PAGE_TYPES.has(ctx.pageType) || ctx.wordCount < 100) return checks;
|
|
5536
|
+
const s = STRINGS[ctx.locale] ?? STRINGS.fr;
|
|
5537
|
+
const hasAuthor = !!(input.author && input.author.trim());
|
|
5538
|
+
checks.push({
|
|
5539
|
+
id: "eeat-author",
|
|
5540
|
+
label: s.authorLabel,
|
|
5541
|
+
status: hasAuthor ? "pass" : "warning",
|
|
5542
|
+
message: hasAuthor ? s.authorPass : s.authorFail,
|
|
5543
|
+
category: "important",
|
|
5544
|
+
weight: 0,
|
|
5545
|
+
group: "eeat",
|
|
5546
|
+
...hasAuthor ? {} : { tip: s.authorTip }
|
|
5547
|
+
});
|
|
5548
|
+
if (hasAuthor) {
|
|
5549
|
+
const hasLink = !!(input.authorUrl && input.authorUrl.trim());
|
|
5550
|
+
checks.push({
|
|
5551
|
+
id: "eeat-author-entity",
|
|
5552
|
+
label: s.authorEntityLabel,
|
|
5553
|
+
status: hasLink ? "pass" : "warning",
|
|
5554
|
+
message: hasLink ? s.authorEntityPass : s.authorEntityFail,
|
|
5555
|
+
category: "bonus",
|
|
5556
|
+
weight: 0,
|
|
5557
|
+
group: "eeat"
|
|
5558
|
+
});
|
|
5559
|
+
}
|
|
5560
|
+
const datesOk = !!input.publishedAt && !!input.updatedAt;
|
|
5561
|
+
checks.push({
|
|
5562
|
+
id: "eeat-dates",
|
|
5563
|
+
label: s.datesLabel,
|
|
5564
|
+
status: datesOk ? "pass" : "warning",
|
|
5565
|
+
message: datesOk ? s.datesPass : s.datesFail,
|
|
5566
|
+
category: "bonus",
|
|
5567
|
+
weight: 0,
|
|
5568
|
+
group: "eeat"
|
|
5569
|
+
});
|
|
5570
|
+
const externalLinks = ctx.allLinks.filter((l) => /^https?:\/\//i.test(l.url));
|
|
5571
|
+
const hasSources = externalLinks.length > 0;
|
|
5572
|
+
checks.push({
|
|
5573
|
+
id: "eeat-sources",
|
|
5574
|
+
label: s.sourcesLabel,
|
|
5575
|
+
status: hasSources ? "pass" : "warning",
|
|
5576
|
+
message: hasSources ? s.sourcesPass(externalLinks.length) : s.sourcesFail,
|
|
5577
|
+
category: "bonus",
|
|
5578
|
+
weight: 0,
|
|
5579
|
+
group: "eeat",
|
|
5580
|
+
...hasSources ? {} : { tip: s.sourcesTip }
|
|
5581
|
+
});
|
|
5582
|
+
const hasPercent = /\d+([.,]\d+)?\s?%/.test(ctx.fullText);
|
|
5583
|
+
const numberCount = (ctx.fullText.match(/\b\d{2,}\b/g) || []).length;
|
|
5584
|
+
const hasData = hasPercent || numberCount >= 3;
|
|
5585
|
+
checks.push({
|
|
5586
|
+
id: "eeat-original-data",
|
|
5587
|
+
label: s.dataLabel,
|
|
5588
|
+
status: hasData ? "pass" : "warning",
|
|
5589
|
+
message: hasData ? s.dataPass : s.dataFail,
|
|
5590
|
+
category: "bonus",
|
|
5591
|
+
weight: 0,
|
|
5592
|
+
group: "eeat",
|
|
5593
|
+
...hasData ? {} : { tip: s.dataTip }
|
|
5594
|
+
});
|
|
5595
|
+
return checks;
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5598
|
+
// src/rules/geo.ts
|
|
5599
|
+
var QUESTION_WORDS = {
|
|
5600
|
+
fr: ["comment", "pourquoi", "quand", "quel", "quelle", "quels", "quelles", "combien", "ou", "o\xF9", "qui", "que", "quoi", "est-ce"],
|
|
5601
|
+
en: ["how", "why", "when", "what", "which", "where", "who", "can", "do", "does", "is", "are", "should"]
|
|
5602
|
+
};
|
|
5603
|
+
var STRINGS2 = {
|
|
5604
|
+
fr: {
|
|
5605
|
+
answerLabel: "R\xE9ponse en t\xEAte (answer-first)",
|
|
5606
|
+
answerPass: "Le contenu d\xE9marre par une accroche concise \u2014 favorise l'extraction par l'IA.",
|
|
5607
|
+
answerFail: "Le contenu ne d\xE9marre pas par une r\xE9ponse concise \u2014 placez une r\xE9ponse directe en t\xEAte de page/section.",
|
|
5608
|
+
answerTip: "Format BLUF (Bottom Line Up Front) : r\xE9pondez \xE0 l'intention en 1-2 phrases avant de d\xE9velopper.",
|
|
5609
|
+
questionsLabel: "Titres en question",
|
|
5610
|
+
questionsPass: (n) => `${n} sous-titre(s) formul\xE9(s) en question \u2014 structure Q\u2192R id\xE9ale pour l'IA.`,
|
|
5611
|
+
questionsFail: "Aucun titre en question \u2014 formulez certains H2/H3 en questions (les moteurs IA citent les paires question/r\xE9ponse).",
|
|
5612
|
+
structureLabel: "Contenu extractible (listes / tableaux)",
|
|
5613
|
+
structurePass: "Listes ou tableaux d\xE9tect\xE9s \u2014 unit\xE9s facilement extraites et cit\xE9es par l'IA.",
|
|
5614
|
+
structureFail: "Aucune liste ni tableau \u2014 structurez les \xE9num\xE9rations et comparaisons en listes/tableaux.",
|
|
5615
|
+
chunkLabel: "Contenu d\xE9coup\xE9 (scannable)",
|
|
5616
|
+
chunkPass: "Contenu bien d\xE9coup\xE9 en sections \u2014 facilite l'extraction de passages.",
|
|
5617
|
+
chunkFail: "Contenu peu d\xE9coup\xE9 \u2014 ajoutez des sous-titres pour cr\xE9er des passages auto-suffisants."
|
|
5618
|
+
},
|
|
5619
|
+
en: {
|
|
5620
|
+
answerLabel: "Answer-first lead",
|
|
5621
|
+
answerPass: "Content opens with a concise lead \u2014 helps AI extraction.",
|
|
5622
|
+
answerFail: "Content does not open with a concise answer \u2014 put a direct answer at the top of the page/section.",
|
|
5623
|
+
answerTip: "BLUF (Bottom Line Up Front): answer the intent in 1-2 sentences before expanding.",
|
|
5624
|
+
questionsLabel: "Question-style headings",
|
|
5625
|
+
questionsPass: (n) => `${n} heading(s) phrased as questions \u2014 ideal Q\u2192A structure for AI.`,
|
|
5626
|
+
questionsFail: "No question headings \u2014 phrase some H2/H3 as questions (AI engines cite question/answer pairs).",
|
|
5627
|
+
structureLabel: "Extractable content (lists / tables)",
|
|
5628
|
+
structurePass: "Lists or tables detected \u2014 units easily extracted and cited by AI.",
|
|
5629
|
+
structureFail: "No list or table \u2014 structure enumerations and comparisons as lists/tables.",
|
|
5630
|
+
chunkLabel: "Chunked content (scannable)",
|
|
5631
|
+
chunkPass: "Content is well chunked into sections \u2014 helps passage extraction.",
|
|
5632
|
+
chunkFail: "Content is barely chunked \u2014 add subheadings to create self-contained passages."
|
|
5633
|
+
}
|
|
5634
|
+
};
|
|
5635
|
+
var GEO_SKIP_PAGE_TYPES = /* @__PURE__ */ new Set(["legal", "contact", "form", "home"]);
|
|
5636
|
+
function isQuestionHeading(text, locale) {
|
|
5637
|
+
const t = text.trim().toLowerCase();
|
|
5638
|
+
if (t.endsWith("?")) return true;
|
|
5639
|
+
const words = QUESTION_WORDS[locale] ?? QUESTION_WORDS.fr;
|
|
5640
|
+
return words.some((w) => t.startsWith(w + " ") || t.startsWith(w + "-"));
|
|
5641
|
+
}
|
|
5642
|
+
function collectLexicalSources(input) {
|
|
5643
|
+
const sources = [];
|
|
5644
|
+
if (input.heroRichText) sources.push(input.heroRichText);
|
|
5645
|
+
if (input.content) sources.push(input.content);
|
|
5646
|
+
if (Array.isArray(input.blocks)) {
|
|
5647
|
+
for (const b of input.blocks) {
|
|
5648
|
+
if (!b || typeof b !== "object") continue;
|
|
5649
|
+
const blk = b;
|
|
5650
|
+
if (blk.richText) sources.push(blk.richText);
|
|
5651
|
+
if (Array.isArray(blk.columns)) {
|
|
5652
|
+
for (const c of blk.columns) {
|
|
5653
|
+
if (c && typeof c === "object" && c.richText) {
|
|
5654
|
+
sources.push(c.richText);
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
return sources;
|
|
5661
|
+
}
|
|
5662
|
+
function containsLexicalType(node, type, depth = 0) {
|
|
5663
|
+
if (depth > 50 || !node || typeof node !== "object") return false;
|
|
5664
|
+
const n = node;
|
|
5665
|
+
if (n.type === type) return true;
|
|
5666
|
+
const root = n.root || n;
|
|
5667
|
+
const children = root.children || n.children;
|
|
5668
|
+
if (Array.isArray(children)) {
|
|
5669
|
+
for (const c of children) {
|
|
5670
|
+
if (containsLexicalType(c, type, depth + 1)) return true;
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5673
|
+
return false;
|
|
5674
|
+
}
|
|
5675
|
+
function checkGeo(input, ctx) {
|
|
5676
|
+
const checks = [];
|
|
5677
|
+
if (GEO_SKIP_PAGE_TYPES.has(ctx.pageType) || ctx.wordCount < 150) return checks;
|
|
5678
|
+
const s = STRINGS2[ctx.locale] ?? STRINGS2.fr;
|
|
5679
|
+
const locale = ctx.locale;
|
|
5680
|
+
const firstSentence = (ctx.sentences[0] || "").trim();
|
|
5681
|
+
const firstSentenceWords = firstSentence ? firstSentence.split(/\s+/).length : 0;
|
|
5682
|
+
const answerFirst = firstSentenceWords > 0 && firstSentenceWords <= 30;
|
|
5683
|
+
checks.push({
|
|
5684
|
+
id: "geo-answer-first",
|
|
5685
|
+
label: s.answerLabel,
|
|
5686
|
+
status: answerFirst ? "pass" : "warning",
|
|
5687
|
+
message: answerFirst ? s.answerPass : s.answerFail,
|
|
5688
|
+
category: "bonus",
|
|
5689
|
+
weight: 0,
|
|
5690
|
+
group: "geo",
|
|
5691
|
+
...answerFirst ? {} : { tip: s.answerTip }
|
|
5692
|
+
});
|
|
5693
|
+
const questionHeadings = ctx.allHeadings.filter(
|
|
5694
|
+
(h) => h.tag !== "h1" && isQuestionHeading(h.text, locale)
|
|
5695
|
+
).length;
|
|
5696
|
+
checks.push({
|
|
5697
|
+
id: "geo-question-headings",
|
|
5698
|
+
label: s.questionsLabel,
|
|
5699
|
+
status: questionHeadings > 0 ? "pass" : "warning",
|
|
5700
|
+
message: questionHeadings > 0 ? s.questionsPass(questionHeadings) : s.questionsFail,
|
|
5701
|
+
category: "bonus",
|
|
5702
|
+
weight: 0,
|
|
5703
|
+
group: "geo"
|
|
5704
|
+
});
|
|
5705
|
+
const sources = collectLexicalSources(input);
|
|
5706
|
+
const hasList = sources.some((src) => extractListsFromLexical(src).length > 0);
|
|
5707
|
+
const hasTable = sources.some((src) => containsLexicalType(src, "table"));
|
|
5708
|
+
const structured = hasList || hasTable;
|
|
5709
|
+
checks.push({
|
|
5710
|
+
id: "geo-extractable-structure",
|
|
5711
|
+
label: s.structureLabel,
|
|
5712
|
+
status: structured ? "pass" : "warning",
|
|
5713
|
+
message: structured ? s.structurePass : s.structureFail,
|
|
5714
|
+
category: "bonus",
|
|
5715
|
+
weight: 0,
|
|
5716
|
+
group: "geo"
|
|
5717
|
+
});
|
|
5718
|
+
const subheadings = ctx.allHeadings.filter((h) => h.tag !== "h1").length;
|
|
5719
|
+
const expected = Math.max(1, Math.floor(ctx.wordCount / 300));
|
|
5720
|
+
const chunked = subheadings >= expected;
|
|
5721
|
+
checks.push({
|
|
5722
|
+
id: "geo-chunked",
|
|
5723
|
+
label: s.chunkLabel,
|
|
5724
|
+
status: chunked ? "pass" : "warning",
|
|
5725
|
+
message: chunked ? s.chunkPass : s.chunkFail,
|
|
5726
|
+
category: "bonus",
|
|
5727
|
+
weight: 0,
|
|
5728
|
+
group: "geo"
|
|
5729
|
+
});
|
|
5730
|
+
return checks;
|
|
5731
|
+
}
|
|
5732
|
+
|
|
5733
|
+
// src/rules/hreflang.ts
|
|
5734
|
+
var HREFLANG_RE = /^[a-z]{2,3}(-[a-z]{2,4})?$/i;
|
|
5735
|
+
var STRINGS3 = {
|
|
5736
|
+
fr: {
|
|
5737
|
+
codesLabel: "Codes hreflang valides",
|
|
5738
|
+
codesPass: "Tous les codes hreflang sont au format valide (langue ISO 639-1, r\xE9gion ISO 3166-1 optionnelle).",
|
|
5739
|
+
codesFail: (bad) => `Code(s) hreflang invalide(s) : ${bad} \u2014 un seul code erron\xE9 fait ignorer tout le cluster par Google.`,
|
|
5740
|
+
dupLabel: "Doublons hreflang",
|
|
5741
|
+
dupPass: "Aucun doublon de code hreflang.",
|
|
5742
|
+
dupFail: (dup) => `Code(s) hreflang en double : ${dup} \u2014 chaque locale doit \xEAtre d\xE9clar\xE9e une seule fois.`,
|
|
5743
|
+
absLabel: "URLs hreflang absolues",
|
|
5744
|
+
absPass: "Toutes les URLs hreflang sont absolues.",
|
|
5745
|
+
absFail: "Certaines URLs hreflang ne sont pas absolues \u2014 utilisez des URLs compl\xE8tes (https://...).",
|
|
5746
|
+
xdefLabel: "hreflang x-default",
|
|
5747
|
+
xdefPass: "Un x-default est d\xE9fini \u2014 bonne pratique pour les visiteurs hors locales cibl\xE9es.",
|
|
5748
|
+
xdefFail: 'Aucun x-default \u2014 ajoutez un hreflang="x-default" pour les locales non couvertes.'
|
|
5749
|
+
},
|
|
5750
|
+
en: {
|
|
5751
|
+
codesLabel: "Valid hreflang codes",
|
|
5752
|
+
codesPass: "All hreflang codes are well-formed (ISO 639-1 language, optional ISO 3166-1 region).",
|
|
5753
|
+
codesFail: (bad) => `Invalid hreflang code(s): ${bad} \u2014 a single bad code makes Google ignore the whole cluster.`,
|
|
5754
|
+
dupLabel: "Duplicate hreflang",
|
|
5755
|
+
dupPass: "No duplicate hreflang code.",
|
|
5756
|
+
dupFail: (dup) => `Duplicate hreflang code(s): ${dup} \u2014 each locale must be declared once.`,
|
|
5757
|
+
absLabel: "Absolute hreflang URLs",
|
|
5758
|
+
absPass: "All hreflang URLs are absolute.",
|
|
5759
|
+
absFail: "Some hreflang URLs are not absolute \u2014 use full URLs (https://...).",
|
|
5760
|
+
xdefLabel: "hreflang x-default",
|
|
5761
|
+
xdefPass: "An x-default is defined \u2014 good practice for visitors outside targeted locales.",
|
|
5762
|
+
xdefFail: 'No x-default \u2014 add hreflang="x-default" for locales you do not cover.'
|
|
5763
|
+
}
|
|
5764
|
+
};
|
|
5765
|
+
function checkHreflang(input, ctx) {
|
|
5766
|
+
const alts = input.localeAlternates;
|
|
5767
|
+
if (!Array.isArray(alts) || alts.length === 0) return [];
|
|
5768
|
+
const s = STRINGS3[ctx.locale] ?? STRINGS3.fr;
|
|
5769
|
+
const checks = [];
|
|
5770
|
+
const codes = alts.map((a) => (a.hreflang || "").trim()).filter(Boolean);
|
|
5771
|
+
const invalid = codes.filter((c) => c.toLowerCase() !== "x-default" && !HREFLANG_RE.test(c));
|
|
5772
|
+
checks.push({
|
|
5773
|
+
id: "hreflang-codes",
|
|
5774
|
+
label: s.codesLabel,
|
|
5775
|
+
status: invalid.length === 0 ? "pass" : "fail",
|
|
5776
|
+
message: invalid.length === 0 ? s.codesPass : s.codesFail(invalid.join(", ")),
|
|
5777
|
+
category: "important",
|
|
5778
|
+
weight: 2,
|
|
5779
|
+
group: "hreflang"
|
|
5780
|
+
});
|
|
5781
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5782
|
+
const dups = /* @__PURE__ */ new Set();
|
|
5783
|
+
for (const c of codes.map((c2) => c2.toLowerCase())) {
|
|
5784
|
+
if (seen.has(c)) dups.add(c);
|
|
5785
|
+
seen.add(c);
|
|
5786
|
+
}
|
|
5787
|
+
checks.push({
|
|
5788
|
+
id: "hreflang-duplicates",
|
|
5789
|
+
label: s.dupLabel,
|
|
5790
|
+
status: dups.size === 0 ? "pass" : "warning",
|
|
5791
|
+
message: dups.size === 0 ? s.dupPass : s.dupFail([...dups].join(", ")),
|
|
5792
|
+
category: "important",
|
|
5793
|
+
weight: 1,
|
|
5794
|
+
group: "hreflang"
|
|
5795
|
+
});
|
|
5796
|
+
const allAbsolute = alts.every((a) => /^https?:\/\//i.test((a.href || "").trim()));
|
|
5797
|
+
checks.push({
|
|
5798
|
+
id: "hreflang-absolute",
|
|
5799
|
+
label: s.absLabel,
|
|
5800
|
+
status: allAbsolute ? "pass" : "warning",
|
|
5801
|
+
message: allAbsolute ? s.absPass : s.absFail,
|
|
5802
|
+
category: "important",
|
|
5803
|
+
weight: 1,
|
|
5804
|
+
group: "hreflang"
|
|
5805
|
+
});
|
|
5806
|
+
const hasXDefault = codes.some((c) => c.toLowerCase() === "x-default");
|
|
5807
|
+
checks.push({
|
|
5808
|
+
id: "hreflang-x-default",
|
|
5809
|
+
label: s.xdefLabel,
|
|
5810
|
+
status: hasXDefault ? "pass" : "warning",
|
|
5811
|
+
message: hasXDefault ? s.xdefPass : s.xdefFail,
|
|
5812
|
+
category: "bonus",
|
|
5813
|
+
weight: 1,
|
|
5814
|
+
group: "hreflang"
|
|
5815
|
+
});
|
|
5816
|
+
return checks;
|
|
5817
|
+
}
|
|
5818
|
+
|
|
5299
5819
|
// src/index.ts
|
|
5300
5820
|
function buildContext(data, config) {
|
|
5301
5821
|
const {
|
|
@@ -5500,6 +6020,9 @@ function analyzeSeo(data, config) {
|
|
|
5500
6020
|
{ group: "freshness", fn: checkFreshness },
|
|
5501
6021
|
{ group: "technical", fn: checkTechnical },
|
|
5502
6022
|
{ group: "accessibility", fn: checkAccessibility },
|
|
6023
|
+
{ group: "eeat", fn: checkEeat },
|
|
6024
|
+
{ group: "geo", fn: checkGeo },
|
|
6025
|
+
{ group: "hreflang", fn: checkHreflang },
|
|
5503
6026
|
// E-commerce rules only run for product pages
|
|
5504
6027
|
...data.isProduct ? [{ group: "ecommerce", fn: checkEcommerce }] : []
|
|
5505
6028
|
];
|
|
@@ -5531,7 +6054,24 @@ function analyzeSeo(data, config) {
|
|
|
5531
6054
|
if (score >= SCORE_EXCELLENT) level = "excellent";
|
|
5532
6055
|
else if (score >= SCORE_GOOD) level = "good";
|
|
5533
6056
|
else if (score >= SCORE_OK) level = "ok";
|
|
5534
|
-
|
|
6057
|
+
const aiChecks = checks.filter(
|
|
6058
|
+
(c) => c.group === "geo" || c.group === "eeat" || c.id === "schema-coverage"
|
|
6059
|
+
);
|
|
6060
|
+
let aiReadiness;
|
|
6061
|
+
if (aiChecks.length > 0) {
|
|
6062
|
+
let aiEarned = 0;
|
|
6063
|
+
for (const c of aiChecks) {
|
|
6064
|
+
if (c.status === "pass") aiEarned += 1;
|
|
6065
|
+
else if (c.status === "warning") aiEarned += WARNING_MULTIPLIER;
|
|
6066
|
+
}
|
|
6067
|
+
const aiScore = Math.round(aiEarned / aiChecks.length * 100);
|
|
6068
|
+
let aiLevel = "poor";
|
|
6069
|
+
if (aiScore >= SCORE_EXCELLENT) aiLevel = "excellent";
|
|
6070
|
+
else if (aiScore >= SCORE_GOOD) aiLevel = "good";
|
|
6071
|
+
else if (aiScore >= SCORE_OK) aiLevel = "ok";
|
|
6072
|
+
aiReadiness = { score: aiScore, level: aiLevel, checkCount: aiChecks.length };
|
|
6073
|
+
}
|
|
6074
|
+
return { score, level, checks, ...aiReadiness ? { aiReadiness } : {} };
|
|
5535
6075
|
}
|
|
5536
6076
|
function useSeoLocale() {
|
|
5537
6077
|
const locale = useLocale();
|
|
@@ -6059,7 +6599,10 @@ function getGroupLabels(t) {
|
|
|
6059
6599
|
"freshness": t.seoAnalyzer.groupFreshness,
|
|
6060
6600
|
"technical": t.seoAnalyzer.groupTechnical,
|
|
6061
6601
|
"accessibility": t.seoAnalyzer.groupAccessibility,
|
|
6062
|
-
"ecommerce": t.seoAnalyzer.groupEcommerce
|
|
6602
|
+
"ecommerce": t.seoAnalyzer.groupEcommerce,
|
|
6603
|
+
"eeat": t.seoAnalyzer.groupEeat,
|
|
6604
|
+
"geo": t.seoAnalyzer.groupGeo,
|
|
6605
|
+
"hreflang": t.seoAnalyzer.groupHreflang
|
|
6063
6606
|
};
|
|
6064
6607
|
}
|
|
6065
6608
|
var GROUP_ORDER = [
|
|
@@ -6079,6 +6622,9 @@ var GROUP_ORDER = [
|
|
|
6079
6622
|
"freshness",
|
|
6080
6623
|
"technical",
|
|
6081
6624
|
"accessibility",
|
|
6625
|
+
"eeat",
|
|
6626
|
+
"geo",
|
|
6627
|
+
"hreflang",
|
|
6082
6628
|
"ecommerce"
|
|
6083
6629
|
];
|
|
6084
6630
|
function getLevelColor(level) {
|
|
@@ -6866,7 +7412,34 @@ var SeoAnalyzer = () => {
|
|
|
6866
7412
|
border: `2px solid ${C2.border}`,
|
|
6867
7413
|
textTransform: "uppercase",
|
|
6868
7414
|
letterSpacing: "0.04em"
|
|
6869
|
-
}, children: t.seoAnalyzer.cornerstoneLabel })
|
|
7415
|
+
}, children: t.seoAnalyzer.cornerstoneLabel }),
|
|
7416
|
+
analysis.aiReadiness && /* @__PURE__ */ jsxs(
|
|
7417
|
+
"div",
|
|
7418
|
+
{
|
|
7419
|
+
title: t.seoAnalyzer.aiReadinessTooltip,
|
|
7420
|
+
style: {
|
|
7421
|
+
display: "inline-flex",
|
|
7422
|
+
alignItems: "center",
|
|
7423
|
+
gap: 4,
|
|
7424
|
+
padding: "4px 10px",
|
|
7425
|
+
borderRadius: 8,
|
|
7426
|
+
fontSize: 11,
|
|
7427
|
+
fontWeight: 900,
|
|
7428
|
+
backgroundColor: getLevelColor(analysis.aiReadiness.level),
|
|
7429
|
+
color: getLevelColor(analysis.aiReadiness.level) === C2.yellow ? C2.black : C2.white,
|
|
7430
|
+
border: `2px solid ${C2.border}`,
|
|
7431
|
+
textTransform: "uppercase",
|
|
7432
|
+
letterSpacing: "0.04em"
|
|
7433
|
+
},
|
|
7434
|
+
children: [
|
|
7435
|
+
"\u2728",
|
|
7436
|
+
" ",
|
|
7437
|
+
t.seoAnalyzer.aiReadiness,
|
|
7438
|
+
" ",
|
|
7439
|
+
analysis.aiReadiness.score
|
|
7440
|
+
]
|
|
7441
|
+
}
|
|
7442
|
+
)
|
|
6870
7443
|
] }),
|
|
6871
7444
|
/* @__PURE__ */ jsxs(
|
|
6872
7445
|
"div",
|
|
@@ -8292,6 +8865,23 @@ function TableRow({
|
|
|
8292
8865
|
},
|
|
8293
8866
|
children: item.score > item.previousScore ? "\u2191" : "\u2193"
|
|
8294
8867
|
}
|
|
8868
|
+
),
|
|
8869
|
+
item.aiReadiness != null && /* @__PURE__ */ jsxs(
|
|
8870
|
+
"span",
|
|
8871
|
+
{
|
|
8872
|
+
title: t.seoAnalyzer.aiReadinessTooltip,
|
|
8873
|
+
style: {
|
|
8874
|
+
fontSize: 10,
|
|
8875
|
+
fontWeight: 800,
|
|
8876
|
+
color: getScoreColor(item.aiReadiness),
|
|
8877
|
+
lineHeight: 1,
|
|
8878
|
+
whiteSpace: "nowrap"
|
|
8879
|
+
},
|
|
8880
|
+
children: [
|
|
8881
|
+
"\u2728",
|
|
8882
|
+
item.aiReadiness
|
|
8883
|
+
]
|
|
8884
|
+
}
|
|
8295
8885
|
)
|
|
8296
8886
|
] }),
|
|
8297
8887
|
/* @__PURE__ */ jsxs(
|
|
@@ -15752,6 +16342,400 @@ ${jsonString}
|
|
|
15752
16342
|
}
|
|
15753
16343
|
);
|
|
15754
16344
|
}
|
|
16345
|
+
var C4 = {
|
|
16346
|
+
text: "var(--theme-text, #1a1a1a)",
|
|
16347
|
+
sub: "var(--theme-elevation-600, #6b7280)",
|
|
16348
|
+
card: "var(--theme-elevation-50, #f9fafb)",
|
|
16349
|
+
bg: "var(--theme-elevation-0, #fff)",
|
|
16350
|
+
border: "var(--theme-elevation-200, #e5e7eb)",
|
|
16351
|
+
green: "#22c55e",
|
|
16352
|
+
yellow: "#f59e0b",
|
|
16353
|
+
red: "#ef4444",
|
|
16354
|
+
blue: "#3b82f6"
|
|
16355
|
+
};
|
|
16356
|
+
var S = {
|
|
16357
|
+
fr: {
|
|
16358
|
+
title: "Core Web Vitals",
|
|
16359
|
+
subtitle: "LCP / INP / CLS r\xE9els via PageSpeed Insights \u2014 informationnel, hors du score SEO (tie-breaker).",
|
|
16360
|
+
urlPlaceholder: "https://votre-site.fr/page-a-tester",
|
|
16361
|
+
test: "Tester",
|
|
16362
|
+
testing: "Analyse en cours\u2026",
|
|
16363
|
+
mobile: "Mobile",
|
|
16364
|
+
desktop: "Desktop",
|
|
16365
|
+
sourceField: "Donn\xE9es terrain (utilisateurs r\xE9els, CrUX)",
|
|
16366
|
+
sourceLab: "Donn\xE9es labo (Lighthouse)",
|
|
16367
|
+
noInp: "INP n\xE9cessite des donn\xE9es terrain r\xE9elles (indisponible en labo).",
|
|
16368
|
+
noKey: "Astuce : d\xE9finissez PAGESPEED_API_KEY c\xF4t\xE9 serveur pour augmenter le quota.",
|
|
16369
|
+
good: "Bon",
|
|
16370
|
+
ni: "\xC0 am\xE9liorer",
|
|
16371
|
+
poor: "Mauvais",
|
|
16372
|
+
na: "\u2014"
|
|
16373
|
+
},
|
|
16374
|
+
en: {
|
|
16375
|
+
title: "Core Web Vitals",
|
|
16376
|
+
subtitle: "Real LCP / INP / CLS via PageSpeed Insights \u2014 informational, outside the SEO score (tie-breaker).",
|
|
16377
|
+
urlPlaceholder: "https://your-site.com/page-to-test",
|
|
16378
|
+
test: "Test",
|
|
16379
|
+
testing: "Analyzing\u2026",
|
|
16380
|
+
mobile: "Mobile",
|
|
16381
|
+
desktop: "Desktop",
|
|
16382
|
+
sourceField: "Field data (real users, CrUX)",
|
|
16383
|
+
sourceLab: "Lab data (Lighthouse)",
|
|
16384
|
+
noInp: "INP needs real-user field data (not available in lab).",
|
|
16385
|
+
noKey: "Tip: set PAGESPEED_API_KEY on the server to raise the quota.",
|
|
16386
|
+
good: "Good",
|
|
16387
|
+
ni: "Needs improvement",
|
|
16388
|
+
poor: "Poor",
|
|
16389
|
+
na: "\u2014"
|
|
16390
|
+
}
|
|
16391
|
+
};
|
|
16392
|
+
function ratingColor(r) {
|
|
16393
|
+
if (r === "good") return C4.green;
|
|
16394
|
+
if (r === "needs-improvement") return C4.yellow;
|
|
16395
|
+
if (r === "poor") return C4.red;
|
|
16396
|
+
return C4.sub;
|
|
16397
|
+
}
|
|
16398
|
+
function formatValue(m, key) {
|
|
16399
|
+
if (m.value === null || Number.isNaN(m.value)) return "\u2014";
|
|
16400
|
+
if (key === "cls") return m.value.toFixed(2);
|
|
16401
|
+
if (m.unit === "ms") return m.value >= 1e3 ? `${(m.value / 1e3).toFixed(2)} s` : `${Math.round(m.value)} ms`;
|
|
16402
|
+
return String(Math.round(m.value));
|
|
16403
|
+
}
|
|
16404
|
+
function CoreWebVitalsPanel({ locale }) {
|
|
16405
|
+
const s = S[locale] ?? S.fr;
|
|
16406
|
+
const [url, setUrl] = useState(typeof window !== "undefined" ? window.location.origin : "");
|
|
16407
|
+
const [strategy, setStrategy] = useState("mobile");
|
|
16408
|
+
const [loading, setLoading] = useState(false);
|
|
16409
|
+
const [error, setError] = useState(null);
|
|
16410
|
+
const [data, setData] = useState(null);
|
|
16411
|
+
const run = async () => {
|
|
16412
|
+
if (!url) return;
|
|
16413
|
+
setLoading(true);
|
|
16414
|
+
setError(null);
|
|
16415
|
+
setData(null);
|
|
16416
|
+
try {
|
|
16417
|
+
const res = await fetch(
|
|
16418
|
+
`/api/seo-plugin/core-web-vitals?url=${encodeURIComponent(url)}&strategy=${strategy}`,
|
|
16419
|
+
{ credentials: "include" }
|
|
16420
|
+
);
|
|
16421
|
+
const json = await res.json();
|
|
16422
|
+
if (!res.ok) setError(json.error || `Error ${res.status}`);
|
|
16423
|
+
else setData(json);
|
|
16424
|
+
} catch (e) {
|
|
16425
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
16426
|
+
} finally {
|
|
16427
|
+
setLoading(false);
|
|
16428
|
+
}
|
|
16429
|
+
};
|
|
16430
|
+
const labelByKey = { lcp: "LCP", inp: "INP", cls: "CLS" };
|
|
16431
|
+
const metricCard = (key, m) => /* @__PURE__ */ jsxs(
|
|
16432
|
+
"div",
|
|
16433
|
+
{
|
|
16434
|
+
style: {
|
|
16435
|
+
flex: 1,
|
|
16436
|
+
minWidth: 120,
|
|
16437
|
+
padding: 14,
|
|
16438
|
+
borderRadius: 10,
|
|
16439
|
+
border: `1px solid ${C4.border}`,
|
|
16440
|
+
backgroundColor: C4.bg
|
|
16441
|
+
},
|
|
16442
|
+
children: [
|
|
16443
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, color: C4.sub, textTransform: "uppercase", letterSpacing: "0.04em" }, children: labelByKey[key] }),
|
|
16444
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 24, fontWeight: 800, color: ratingColor(m.rating), lineHeight: 1.2, marginTop: 4 }, children: formatValue(m, key) }),
|
|
16445
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, color: ratingColor(m.rating) }, children: m.rating === "good" ? s.good : m.rating === "needs-improvement" ? s.ni : m.rating === "poor" ? s.poor : s.na }),
|
|
16446
|
+
m.note && /* @__PURE__ */ jsx("div", { style: { fontSize: 10, color: C4.sub, marginTop: 4 }, children: s.noInp })
|
|
16447
|
+
]
|
|
16448
|
+
},
|
|
16449
|
+
key
|
|
16450
|
+
);
|
|
16451
|
+
const inputStyle4 = {
|
|
16452
|
+
flex: 1,
|
|
16453
|
+
minWidth: 200,
|
|
16454
|
+
padding: "8px 10px",
|
|
16455
|
+
borderRadius: 8,
|
|
16456
|
+
border: `1px solid ${C4.border}`,
|
|
16457
|
+
backgroundColor: C4.bg,
|
|
16458
|
+
color: C4.text,
|
|
16459
|
+
fontSize: 13
|
|
16460
|
+
};
|
|
16461
|
+
const btnStyle = (active) => ({
|
|
16462
|
+
padding: "8px 12px",
|
|
16463
|
+
borderRadius: 8,
|
|
16464
|
+
border: `1px solid ${active ? C4.blue : C4.border}`,
|
|
16465
|
+
backgroundColor: active ? C4.blue : C4.bg,
|
|
16466
|
+
color: active ? "#fff" : C4.text,
|
|
16467
|
+
fontSize: 12,
|
|
16468
|
+
fontWeight: 700,
|
|
16469
|
+
cursor: "pointer"
|
|
16470
|
+
});
|
|
16471
|
+
return /* @__PURE__ */ jsxs(
|
|
16472
|
+
"div",
|
|
16473
|
+
{
|
|
16474
|
+
style: {
|
|
16475
|
+
padding: 16,
|
|
16476
|
+
borderRadius: 12,
|
|
16477
|
+
border: `1px solid ${C4.border}`,
|
|
16478
|
+
backgroundColor: C4.card,
|
|
16479
|
+
marginBottom: 20
|
|
16480
|
+
},
|
|
16481
|
+
children: [
|
|
16482
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 800, color: C4.text }, children: s.title }),
|
|
16483
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: C4.sub, marginTop: 2, marginBottom: 12 }, children: s.subtitle }),
|
|
16484
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", marginBottom: 12 }, children: [
|
|
16485
|
+
/* @__PURE__ */ jsx(
|
|
16486
|
+
"input",
|
|
16487
|
+
{
|
|
16488
|
+
type: "url",
|
|
16489
|
+
value: url,
|
|
16490
|
+
onChange: (e) => setUrl(e.target.value),
|
|
16491
|
+
placeholder: s.urlPlaceholder,
|
|
16492
|
+
style: inputStyle4
|
|
16493
|
+
}
|
|
16494
|
+
),
|
|
16495
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setStrategy("mobile"), style: btnStyle(strategy === "mobile"), children: s.mobile }),
|
|
16496
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setStrategy("desktop"), style: btnStyle(strategy === "desktop"), children: s.desktop }),
|
|
16497
|
+
/* @__PURE__ */ jsx(
|
|
16498
|
+
"button",
|
|
16499
|
+
{
|
|
16500
|
+
type: "button",
|
|
16501
|
+
onClick: run,
|
|
16502
|
+
disabled: loading || !url,
|
|
16503
|
+
style: { ...btnStyle(true), opacity: loading || !url ? 0.6 : 1 },
|
|
16504
|
+
children: loading ? s.testing : s.test
|
|
16505
|
+
}
|
|
16506
|
+
)
|
|
16507
|
+
] }),
|
|
16508
|
+
error && /* @__PURE__ */ jsx("div", { style: { color: C4.red, fontSize: 13, fontWeight: 600 }, children: error }),
|
|
16509
|
+
data && /* @__PURE__ */ jsxs("div", { children: [
|
|
16510
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 10 }, children: [
|
|
16511
|
+
metricCard("lcp", data.metrics.lcp),
|
|
16512
|
+
metricCard("inp", data.metrics.inp),
|
|
16513
|
+
metricCard("cls", data.metrics.cls)
|
|
16514
|
+
] }),
|
|
16515
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: C4.sub, marginTop: 8 }, children: [
|
|
16516
|
+
data.source === "field" ? s.sourceField : s.sourceLab,
|
|
16517
|
+
!data.keyConfigured && ` \xB7 ${s.noKey}`
|
|
16518
|
+
] })
|
|
16519
|
+
] })
|
|
16520
|
+
]
|
|
16521
|
+
}
|
|
16522
|
+
);
|
|
16523
|
+
}
|
|
16524
|
+
var C5 = {
|
|
16525
|
+
text: "var(--theme-text, #1a1a1a)",
|
|
16526
|
+
sub: "var(--theme-elevation-600, #6b7280)",
|
|
16527
|
+
card: "var(--theme-elevation-50, #f9fafb)",
|
|
16528
|
+
bg: "var(--theme-elevation-0, #fff)",
|
|
16529
|
+
border: "var(--theme-elevation-200, #e5e7eb)",
|
|
16530
|
+
green: "#22c55e",
|
|
16531
|
+
red: "#ef4444",
|
|
16532
|
+
blue: "#3b82f6"
|
|
16533
|
+
};
|
|
16534
|
+
var S2 = {
|
|
16535
|
+
fr: {
|
|
16536
|
+
title: "Google Search Console",
|
|
16537
|
+
subtitle: "Connexion OAuth pour importer automatiquement clics, impressions et positions r\xE9elles.",
|
|
16538
|
+
notConfigured: "Int\xE9gration non configur\xE9e. Activez features.gscApi et d\xE9finissez GSC_OAUTH_CLIENT_ID / GSC_OAUTH_CLIENT_SECRET c\xF4t\xE9 serveur, puis enregistrez l'URI de redirection dans Google Cloud.",
|
|
16539
|
+
redirectHint: "URI de redirection \xE0 enregistrer :",
|
|
16540
|
+
connect: "Connecter Google Search Console",
|
|
16541
|
+
disconnect: "D\xE9connecter",
|
|
16542
|
+
connectedAs: "Connect\xE9",
|
|
16543
|
+
fetch: "R\xE9cup\xE9rer les donn\xE9es (28 j)",
|
|
16544
|
+
fetching: "Chargement\u2026",
|
|
16545
|
+
byQuery: "Par requ\xEAte",
|
|
16546
|
+
byPage: "Par page",
|
|
16547
|
+
query: "Requ\xEAte",
|
|
16548
|
+
page: "Page",
|
|
16549
|
+
clicks: "Clics",
|
|
16550
|
+
impressions: "Impressions",
|
|
16551
|
+
ctr: "CTR",
|
|
16552
|
+
position: "Position",
|
|
16553
|
+
noData: "Aucune donn\xE9e sur la p\xE9riode.",
|
|
16554
|
+
refreshStatus: "Rafra\xEEchir le statut",
|
|
16555
|
+
connectHint: "Une fen\xEAtre Google va s'ouvrir. Apr\xE8s autorisation, revenez ici et rafra\xEEchissez le statut."
|
|
16556
|
+
},
|
|
16557
|
+
en: {
|
|
16558
|
+
title: "Google Search Console",
|
|
16559
|
+
subtitle: "OAuth connection to automatically import real clicks, impressions and positions.",
|
|
16560
|
+
notConfigured: "Integration not configured. Enable features.gscApi and set GSC_OAUTH_CLIENT_ID / GSC_OAUTH_CLIENT_SECRET on the server, then register the redirect URI in Google Cloud.",
|
|
16561
|
+
redirectHint: "Redirect URI to register:",
|
|
16562
|
+
connect: "Connect Google Search Console",
|
|
16563
|
+
disconnect: "Disconnect",
|
|
16564
|
+
connectedAs: "Connected",
|
|
16565
|
+
fetch: "Fetch data (28 d)",
|
|
16566
|
+
fetching: "Loading\u2026",
|
|
16567
|
+
byQuery: "By query",
|
|
16568
|
+
byPage: "By page",
|
|
16569
|
+
query: "Query",
|
|
16570
|
+
page: "Page",
|
|
16571
|
+
clicks: "Clicks",
|
|
16572
|
+
impressions: "Impressions",
|
|
16573
|
+
ctr: "CTR",
|
|
16574
|
+
position: "Position",
|
|
16575
|
+
noData: "No data for the period.",
|
|
16576
|
+
refreshStatus: "Refresh status",
|
|
16577
|
+
connectHint: "A Google window will open. After authorizing, come back here and refresh the status."
|
|
16578
|
+
}
|
|
16579
|
+
};
|
|
16580
|
+
function GscPanel({ locale }) {
|
|
16581
|
+
const s = S2[locale] ?? S2.fr;
|
|
16582
|
+
const [status, setStatus] = useState(null);
|
|
16583
|
+
const [busy, setBusy] = useState(false);
|
|
16584
|
+
const [error, setError] = useState(null);
|
|
16585
|
+
const [rows, setRows] = useState(null);
|
|
16586
|
+
const [dimension, setDimension] = useState("query");
|
|
16587
|
+
const [dataLoading, setDataLoading] = useState(false);
|
|
16588
|
+
const loadStatus = useCallback(async () => {
|
|
16589
|
+
setError(null);
|
|
16590
|
+
try {
|
|
16591
|
+
const res = await fetch("/api/seo-plugin/gsc/status", { credentials: "include" });
|
|
16592
|
+
const json = await res.json();
|
|
16593
|
+
if (!res.ok) setError(json.error || `Error ${res.status}`);
|
|
16594
|
+
else setStatus(json);
|
|
16595
|
+
} catch (e) {
|
|
16596
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
16597
|
+
}
|
|
16598
|
+
}, []);
|
|
16599
|
+
useEffect(() => {
|
|
16600
|
+
void loadStatus();
|
|
16601
|
+
}, [loadStatus]);
|
|
16602
|
+
const connect = async () => {
|
|
16603
|
+
setBusy(true);
|
|
16604
|
+
setError(null);
|
|
16605
|
+
try {
|
|
16606
|
+
const res = await fetch("/api/seo-plugin/gsc/auth", { credentials: "include" });
|
|
16607
|
+
const json = await res.json();
|
|
16608
|
+
if (!res.ok) setError(json.error || `Error ${res.status}`);
|
|
16609
|
+
else if (json.authUrl) window.open(json.authUrl, "_blank", "noopener,noreferrer");
|
|
16610
|
+
} catch (e) {
|
|
16611
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
16612
|
+
} finally {
|
|
16613
|
+
setBusy(false);
|
|
16614
|
+
}
|
|
16615
|
+
};
|
|
16616
|
+
const disconnect = async () => {
|
|
16617
|
+
setBusy(true);
|
|
16618
|
+
setError(null);
|
|
16619
|
+
try {
|
|
16620
|
+
await fetch("/api/seo-plugin/gsc/disconnect", { method: "POST", credentials: "include" });
|
|
16621
|
+
setRows(null);
|
|
16622
|
+
await loadStatus();
|
|
16623
|
+
} catch (e) {
|
|
16624
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
16625
|
+
} finally {
|
|
16626
|
+
setBusy(false);
|
|
16627
|
+
}
|
|
16628
|
+
};
|
|
16629
|
+
const fetchData = async (dim) => {
|
|
16630
|
+
setDimension(dim);
|
|
16631
|
+
setDataLoading(true);
|
|
16632
|
+
setError(null);
|
|
16633
|
+
setRows(null);
|
|
16634
|
+
try {
|
|
16635
|
+
const res = await fetch(`/api/seo-plugin/gsc/data?dimension=${dim}&rowLimit=50`, { credentials: "include" });
|
|
16636
|
+
const json = await res.json();
|
|
16637
|
+
if (!res.ok) setError(json.error || `Error ${res.status}`);
|
|
16638
|
+
else setRows(json.rows || []);
|
|
16639
|
+
} catch (e) {
|
|
16640
|
+
setError(e instanceof Error ? e.message : "Network error");
|
|
16641
|
+
} finally {
|
|
16642
|
+
setDataLoading(false);
|
|
16643
|
+
}
|
|
16644
|
+
};
|
|
16645
|
+
const btn = (primary) => ({
|
|
16646
|
+
padding: "8px 12px",
|
|
16647
|
+
borderRadius: 8,
|
|
16648
|
+
border: `1px solid ${primary ? C5.blue : C5.border}`,
|
|
16649
|
+
backgroundColor: primary ? C5.blue : C5.bg,
|
|
16650
|
+
color: primary ? "#fff" : C5.text,
|
|
16651
|
+
fontSize: 12,
|
|
16652
|
+
fontWeight: 700,
|
|
16653
|
+
cursor: "pointer",
|
|
16654
|
+
opacity: busy ? 0.6 : 1
|
|
16655
|
+
});
|
|
16656
|
+
const card = {
|
|
16657
|
+
padding: 16,
|
|
16658
|
+
borderRadius: 12,
|
|
16659
|
+
border: `1px solid ${C5.border}`,
|
|
16660
|
+
backgroundColor: C5.card,
|
|
16661
|
+
marginBottom: 20
|
|
16662
|
+
};
|
|
16663
|
+
if (!status) {
|
|
16664
|
+
return /* @__PURE__ */ jsxs("div", { style: card, children: [
|
|
16665
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 800, color: C5.text }, children: s.title }),
|
|
16666
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: C5.sub, marginTop: 6 }, children: "\u2026" })
|
|
16667
|
+
] });
|
|
16668
|
+
}
|
|
16669
|
+
return /* @__PURE__ */ jsxs("div", { style: card, children: [
|
|
16670
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }, children: [
|
|
16671
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
16672
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 800, color: C5.text }, children: s.title }),
|
|
16673
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: C5.sub, marginTop: 2 }, children: s.subtitle })
|
|
16674
|
+
] }),
|
|
16675
|
+
/* @__PURE__ */ jsx(
|
|
16676
|
+
"span",
|
|
16677
|
+
{
|
|
16678
|
+
style: {
|
|
16679
|
+
fontSize: 11,
|
|
16680
|
+
fontWeight: 800,
|
|
16681
|
+
padding: "4px 10px",
|
|
16682
|
+
borderRadius: 999,
|
|
16683
|
+
color: "#fff",
|
|
16684
|
+
backgroundColor: status.connected ? C5.green : C5.sub
|
|
16685
|
+
},
|
|
16686
|
+
children: status.connected ? "\u25CF " + s.connectedAs : "\u25CB"
|
|
16687
|
+
}
|
|
16688
|
+
)
|
|
16689
|
+
] }),
|
|
16690
|
+
error && /* @__PURE__ */ jsx("div", { style: { color: C5.red, fontSize: 13, fontWeight: 600, marginTop: 10 }, children: error }),
|
|
16691
|
+
!status.configured && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12, fontSize: 13, color: C5.sub, lineHeight: 1.5 }, children: [
|
|
16692
|
+
s.notConfigured,
|
|
16693
|
+
status.redirectUri && /* @__PURE__ */ jsxs("div", { style: { marginTop: 8 }, children: [
|
|
16694
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 700 }, children: s.redirectHint }),
|
|
16695
|
+
" ",
|
|
16696
|
+
/* @__PURE__ */ jsx("code", { style: { fontSize: 12 }, children: status.redirectUri })
|
|
16697
|
+
] })
|
|
16698
|
+
] }),
|
|
16699
|
+
status.configured && !status.connected && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
|
|
16700
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: connect, disabled: busy, style: btn(true), children: s.connect }),
|
|
16701
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void loadStatus(), disabled: busy, style: { ...btn(false), marginLeft: 8 }, children: s.refreshStatus }),
|
|
16702
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: C5.sub, marginTop: 8 }, children: s.connectHint })
|
|
16703
|
+
] }),
|
|
16704
|
+
status.configured && status.connected && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
|
|
16705
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: C5.sub, marginBottom: 10 }, children: [
|
|
16706
|
+
s.connectedAs,
|
|
16707
|
+
status.connectedEmail ? ` \xB7 ${status.connectedEmail}` : "",
|
|
16708
|
+
status.propertyUrl ? ` \xB7 ${status.propertyUrl}` : ""
|
|
16709
|
+
] }),
|
|
16710
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 12 }, children: [
|
|
16711
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void fetchData("query"), disabled: dataLoading, style: btn(dimension === "query"), children: s.byQuery }),
|
|
16712
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void fetchData("page"), disabled: dataLoading, style: btn(dimension === "page"), children: s.byPage }),
|
|
16713
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: disconnect, disabled: busy, style: btn(false), children: s.disconnect })
|
|
16714
|
+
] }),
|
|
16715
|
+
dataLoading && /* @__PURE__ */ jsx("div", { style: { fontSize: 13, color: C5.sub }, children: s.fetching }),
|
|
16716
|
+
rows && rows.length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 13, color: C5.sub }, children: s.noData }),
|
|
16717
|
+
rows && rows.length > 0 && /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 12 }, children: [
|
|
16718
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { style: { textAlign: "left", color: C5.sub }, children: [
|
|
16719
|
+
/* @__PURE__ */ jsx("th", { style: { padding: "6px 8px" }, children: dimension === "query" ? s.query : s.page }),
|
|
16720
|
+
/* @__PURE__ */ jsx("th", { style: { padding: "6px 8px", textAlign: "right" }, children: s.clicks }),
|
|
16721
|
+
/* @__PURE__ */ jsx("th", { style: { padding: "6px 8px", textAlign: "right" }, children: s.impressions }),
|
|
16722
|
+
/* @__PURE__ */ jsx("th", { style: { padding: "6px 8px", textAlign: "right" }, children: s.ctr }),
|
|
16723
|
+
/* @__PURE__ */ jsx("th", { style: { padding: "6px 8px", textAlign: "right" }, children: s.position })
|
|
16724
|
+
] }) }),
|
|
16725
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsxs("tr", { style: { borderTop: `1px solid ${C5.border}`, color: C5.text }, children: [
|
|
16726
|
+
/* @__PURE__ */ jsx("td", { style: { padding: "6px 8px", maxWidth: 320, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: r.keys?.[0] || "\u2014" }),
|
|
16727
|
+
/* @__PURE__ */ jsx("td", { style: { padding: "6px 8px", textAlign: "right", fontWeight: 700 }, children: r.clicks }),
|
|
16728
|
+
/* @__PURE__ */ jsx("td", { style: { padding: "6px 8px", textAlign: "right" }, children: r.impressions }),
|
|
16729
|
+
/* @__PURE__ */ jsxs("td", { style: { padding: "6px 8px", textAlign: "right" }, children: [
|
|
16730
|
+
(r.ctr * 100).toFixed(1),
|
|
16731
|
+
"%"
|
|
16732
|
+
] }),
|
|
16733
|
+
/* @__PURE__ */ jsx("td", { style: { padding: "6px 8px", textAlign: "right" }, children: r.position.toFixed(1) })
|
|
16734
|
+
] }, i)) })
|
|
16735
|
+
] }) })
|
|
16736
|
+
] })
|
|
16737
|
+
] });
|
|
16738
|
+
}
|
|
15755
16739
|
var V8 = {
|
|
15756
16740
|
text: "var(--theme-text, #1a1a1a)",
|
|
15757
16741
|
textSecondary: "var(--theme-elevation-600, #6b7280)",
|
|
@@ -16164,6 +17148,8 @@ function PerformanceView() {
|
|
|
16164
17148
|
]
|
|
16165
17149
|
}
|
|
16166
17150
|
),
|
|
17151
|
+
/* @__PURE__ */ jsx(CoreWebVitalsPanel, { locale }),
|
|
17152
|
+
/* @__PURE__ */ jsx(GscPanel, { locale }),
|
|
16167
17153
|
showImport && /* @__PURE__ */ jsxs(
|
|
16168
17154
|
"div",
|
|
16169
17155
|
{
|
|
@@ -17823,7 +18809,7 @@ var controlBtnStyle = {
|
|
|
17823
18809
|
cursor: "pointer",
|
|
17824
18810
|
lineHeight: 1.4
|
|
17825
18811
|
};
|
|
17826
|
-
var
|
|
18812
|
+
var C6 = {
|
|
17827
18813
|
cyan: "#00E5FF",
|
|
17828
18814
|
black: "#000",
|
|
17829
18815
|
green: "#22c55e",
|
|
@@ -17838,10 +18824,10 @@ var C4 = {
|
|
|
17838
18824
|
var TITLE_MIN = 30;
|
|
17839
18825
|
var TITLE_MAX = 60;
|
|
17840
18826
|
function getCharColor(len) {
|
|
17841
|
-
if (len === 0) return
|
|
17842
|
-
if (len >= TITLE_MIN && len <= TITLE_MAX) return
|
|
17843
|
-
if (len > 0 && len < TITLE_MIN) return
|
|
17844
|
-
return
|
|
18827
|
+
if (len === 0) return C6.textSecondary;
|
|
18828
|
+
if (len >= TITLE_MIN && len <= TITLE_MAX) return C6.green;
|
|
18829
|
+
if (len > 0 && len < TITLE_MIN) return C6.orange;
|
|
18830
|
+
return C6.red;
|
|
17845
18831
|
}
|
|
17846
18832
|
function getProgressPercent(len) {
|
|
17847
18833
|
if (len === 0) return 0;
|
|
@@ -17849,9 +18835,9 @@ function getProgressPercent(len) {
|
|
|
17849
18835
|
}
|
|
17850
18836
|
function getProgressColor(len) {
|
|
17851
18837
|
if (len === 0) return "var(--theme-elevation-200, #e5e7eb)";
|
|
17852
|
-
if (len >= TITLE_MIN && len <= TITLE_MAX) return
|
|
17853
|
-
if (len < TITLE_MIN) return
|
|
17854
|
-
return
|
|
18838
|
+
if (len >= TITLE_MIN && len <= TITLE_MAX) return C6.green;
|
|
18839
|
+
if (len < TITLE_MIN) return C6.orange;
|
|
18840
|
+
return C6.red;
|
|
17855
18841
|
}
|
|
17856
18842
|
function MetaTitleField({
|
|
17857
18843
|
path,
|
|
@@ -17922,7 +18908,7 @@ function MetaTitleField({
|
|
|
17922
18908
|
style: {
|
|
17923
18909
|
fontSize: 13,
|
|
17924
18910
|
fontWeight: 700,
|
|
17925
|
-
color:
|
|
18911
|
+
color: C6.textPrimary
|
|
17926
18912
|
},
|
|
17927
18913
|
children: t.metaTitle.label
|
|
17928
18914
|
}
|
|
@@ -17962,9 +18948,9 @@ function MetaTitleField({
|
|
|
17962
18948
|
fontSize: 14,
|
|
17963
18949
|
fontFamily: "inherit",
|
|
17964
18950
|
borderRadius: 8,
|
|
17965
|
-
border: `2px solid ${
|
|
17966
|
-
backgroundColor:
|
|
17967
|
-
color:
|
|
18951
|
+
border: `2px solid ${C6.border}`,
|
|
18952
|
+
backgroundColor: C6.surfaceBg,
|
|
18953
|
+
color: C6.textPrimary,
|
|
17968
18954
|
outline: "none",
|
|
17969
18955
|
boxShadow: "2px 2px 0 0 var(--theme-border-color, rgba(0,0,0,1))"
|
|
17970
18956
|
}
|
|
@@ -17982,9 +18968,9 @@ function MetaTitleField({
|
|
|
17982
18968
|
gap: 5,
|
|
17983
18969
|
padding: "8px 14px",
|
|
17984
18970
|
borderRadius: 8,
|
|
17985
|
-
border: `2px solid ${
|
|
17986
|
-
backgroundColor: loading ?
|
|
17987
|
-
color: loading ?
|
|
18971
|
+
border: `2px solid ${C6.border}`,
|
|
18972
|
+
backgroundColor: loading ? C6.surface50 : C6.cyan,
|
|
18973
|
+
color: loading ? C6.textSecondary : C6.black,
|
|
17988
18974
|
fontWeight: 800,
|
|
17989
18975
|
fontSize: 11,
|
|
17990
18976
|
textTransform: "uppercase",
|
|
@@ -18031,7 +19017,7 @@ function MetaTitleField({
|
|
|
18031
19017
|
justifyContent: "space-between",
|
|
18032
19018
|
marginTop: 4,
|
|
18033
19019
|
fontSize: 10,
|
|
18034
|
-
color:
|
|
19020
|
+
color: C6.textSecondary
|
|
18035
19021
|
},
|
|
18036
19022
|
children: [
|
|
18037
19023
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -18065,9 +19051,9 @@ function MetaTitleField({
|
|
|
18065
19051
|
borderRadius: 6,
|
|
18066
19052
|
fontSize: 11,
|
|
18067
19053
|
fontWeight: 600,
|
|
18068
|
-
color:
|
|
19054
|
+
color: C6.red,
|
|
18069
19055
|
backgroundColor: "rgba(239,68,68,0.08)",
|
|
18070
|
-
border: `1px solid ${
|
|
19056
|
+
border: `1px solid ${C6.red}`
|
|
18071
19057
|
},
|
|
18072
19058
|
children: error
|
|
18073
19059
|
}
|
|
@@ -18076,7 +19062,7 @@ function MetaTitleField({
|
|
|
18076
19062
|
}
|
|
18077
19063
|
);
|
|
18078
19064
|
}
|
|
18079
|
-
var
|
|
19065
|
+
var C7 = {
|
|
18080
19066
|
cyan: "#00E5FF",
|
|
18081
19067
|
black: "#000",
|
|
18082
19068
|
green: "#22c55e",
|
|
@@ -18091,10 +19077,10 @@ var C5 = {
|
|
|
18091
19077
|
var DESC_MIN = 120;
|
|
18092
19078
|
var DESC_MAX = 160;
|
|
18093
19079
|
function getCharColor2(len) {
|
|
18094
|
-
if (len === 0) return
|
|
18095
|
-
if (len >= DESC_MIN && len <= DESC_MAX) return
|
|
18096
|
-
if (len > 0 && len < DESC_MIN) return
|
|
18097
|
-
return
|
|
19080
|
+
if (len === 0) return C7.textSecondary;
|
|
19081
|
+
if (len >= DESC_MIN && len <= DESC_MAX) return C7.green;
|
|
19082
|
+
if (len > 0 && len < DESC_MIN) return C7.orange;
|
|
19083
|
+
return C7.red;
|
|
18098
19084
|
}
|
|
18099
19085
|
function getProgressPercent2(len) {
|
|
18100
19086
|
if (len === 0) return 0;
|
|
@@ -18102,9 +19088,9 @@ function getProgressPercent2(len) {
|
|
|
18102
19088
|
}
|
|
18103
19089
|
function getProgressColor2(len) {
|
|
18104
19090
|
if (len === 0) return "var(--theme-elevation-200, #e5e7eb)";
|
|
18105
|
-
if (len >= DESC_MIN && len <= DESC_MAX) return
|
|
18106
|
-
if (len < DESC_MIN) return
|
|
18107
|
-
return
|
|
19091
|
+
if (len >= DESC_MIN && len <= DESC_MAX) return C7.green;
|
|
19092
|
+
if (len < DESC_MIN) return C7.orange;
|
|
19093
|
+
return C7.red;
|
|
18108
19094
|
}
|
|
18109
19095
|
function MetaDescriptionField({
|
|
18110
19096
|
path,
|
|
@@ -18175,7 +19161,7 @@ function MetaDescriptionField({
|
|
|
18175
19161
|
style: {
|
|
18176
19162
|
fontSize: 13,
|
|
18177
19163
|
fontWeight: 700,
|
|
18178
|
-
color:
|
|
19164
|
+
color: C7.textPrimary
|
|
18179
19165
|
},
|
|
18180
19166
|
children: t.metaDescription.label
|
|
18181
19167
|
}
|
|
@@ -18215,9 +19201,9 @@ function MetaDescriptionField({
|
|
|
18215
19201
|
fontSize: 14,
|
|
18216
19202
|
fontFamily: "inherit",
|
|
18217
19203
|
borderRadius: 8,
|
|
18218
|
-
border: `2px solid ${
|
|
18219
|
-
backgroundColor:
|
|
18220
|
-
color:
|
|
19204
|
+
border: `2px solid ${C7.border}`,
|
|
19205
|
+
backgroundColor: C7.surfaceBg,
|
|
19206
|
+
color: C7.textPrimary,
|
|
18221
19207
|
outline: "none",
|
|
18222
19208
|
resize: "vertical",
|
|
18223
19209
|
lineHeight: 1.5,
|
|
@@ -18237,9 +19223,9 @@ function MetaDescriptionField({
|
|
|
18237
19223
|
gap: 5,
|
|
18238
19224
|
padding: "8px 14px",
|
|
18239
19225
|
borderRadius: 8,
|
|
18240
|
-
border: `2px solid ${
|
|
18241
|
-
backgroundColor: loading ?
|
|
18242
|
-
color: loading ?
|
|
19226
|
+
border: `2px solid ${C7.border}`,
|
|
19227
|
+
backgroundColor: loading ? C7.surface50 : C7.cyan,
|
|
19228
|
+
color: loading ? C7.textSecondary : C7.black,
|
|
18243
19229
|
fontWeight: 800,
|
|
18244
19230
|
fontSize: 11,
|
|
18245
19231
|
textTransform: "uppercase",
|
|
@@ -18287,7 +19273,7 @@ function MetaDescriptionField({
|
|
|
18287
19273
|
justifyContent: "space-between",
|
|
18288
19274
|
marginTop: 4,
|
|
18289
19275
|
fontSize: 10,
|
|
18290
|
-
color:
|
|
19276
|
+
color: C7.textSecondary
|
|
18291
19277
|
},
|
|
18292
19278
|
children: [
|
|
18293
19279
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -18321,9 +19307,9 @@ function MetaDescriptionField({
|
|
|
18321
19307
|
borderRadius: 6,
|
|
18322
19308
|
fontSize: 11,
|
|
18323
19309
|
fontWeight: 600,
|
|
18324
|
-
color:
|
|
19310
|
+
color: C7.red,
|
|
18325
19311
|
backgroundColor: "rgba(239,68,68,0.08)",
|
|
18326
|
-
border: `1px solid ${
|
|
19312
|
+
border: `1px solid ${C7.red}`
|
|
18327
19313
|
},
|
|
18328
19314
|
children: error
|
|
18329
19315
|
}
|
|
@@ -18332,7 +19318,7 @@ function MetaDescriptionField({
|
|
|
18332
19318
|
}
|
|
18333
19319
|
);
|
|
18334
19320
|
}
|
|
18335
|
-
var
|
|
19321
|
+
var C8 = {
|
|
18336
19322
|
cyan: "#00E5FF",
|
|
18337
19323
|
black: "#000",
|
|
18338
19324
|
white: "#fff",
|
|
@@ -18409,8 +19395,8 @@ function MetaImageField({
|
|
|
18409
19395
|
gap: 10,
|
|
18410
19396
|
padding: "10px 14px",
|
|
18411
19397
|
borderRadius: 8,
|
|
18412
|
-
border: `2px solid ${
|
|
18413
|
-
backgroundColor:
|
|
19398
|
+
border: `2px solid ${C8.border}`,
|
|
19399
|
+
backgroundColor: C8.surfaceBg,
|
|
18414
19400
|
boxShadow: "2px 2px 0 0 var(--theme-border-color, rgba(0,0,0,1))"
|
|
18415
19401
|
},
|
|
18416
19402
|
children: [
|
|
@@ -18429,7 +19415,7 @@ function MetaImageField({
|
|
|
18429
19415
|
fontWeight: 900,
|
|
18430
19416
|
backgroundColor: hasImage ? "rgba(34,197,94,0.15)" : "rgba(255,138,0,0.15)",
|
|
18431
19417
|
color: hasImage ? "#16a34a" : "#d97706",
|
|
18432
|
-
border: `1px solid ${hasImage ?
|
|
19418
|
+
border: `1px solid ${hasImage ? C8.green : C8.orange}`
|
|
18433
19419
|
},
|
|
18434
19420
|
children: hasImage ? "\u2713" : "!"
|
|
18435
19421
|
}
|
|
@@ -18441,7 +19427,7 @@ function MetaImageField({
|
|
|
18441
19427
|
style: {
|
|
18442
19428
|
fontSize: 12,
|
|
18443
19429
|
fontWeight: 700,
|
|
18444
|
-
color:
|
|
19430
|
+
color: C8.textPrimary
|
|
18445
19431
|
},
|
|
18446
19432
|
children: t.metaImage.label
|
|
18447
19433
|
}
|
|
@@ -18451,7 +19437,7 @@ function MetaImageField({
|
|
|
18451
19437
|
{
|
|
18452
19438
|
style: {
|
|
18453
19439
|
fontSize: 10,
|
|
18454
|
-
color:
|
|
19440
|
+
color: C8.textSecondary,
|
|
18455
19441
|
lineHeight: 1.4
|
|
18456
19442
|
},
|
|
18457
19443
|
children: hasImage ? t.metaImage.imageSet : t.metaImage.noImage
|
|
@@ -18471,9 +19457,9 @@ function MetaImageField({
|
|
|
18471
19457
|
gap: 5,
|
|
18472
19458
|
padding: "8px 14px",
|
|
18473
19459
|
borderRadius: 8,
|
|
18474
|
-
border: `2px solid ${
|
|
18475
|
-
backgroundColor: loading ?
|
|
18476
|
-
color: loading ?
|
|
19460
|
+
border: `2px solid ${C8.border}`,
|
|
19461
|
+
backgroundColor: loading ? C8.surface50 : success ? C8.green : C8.cyan,
|
|
19462
|
+
color: loading ? C8.textSecondary : success ? C8.white : C8.black,
|
|
18477
19463
|
fontWeight: 800,
|
|
18478
19464
|
fontSize: 11,
|
|
18479
19465
|
textTransform: "uppercase",
|
|
@@ -18500,9 +19486,9 @@ function MetaImageField({
|
|
|
18500
19486
|
borderRadius: 6,
|
|
18501
19487
|
fontSize: 11,
|
|
18502
19488
|
fontWeight: 600,
|
|
18503
|
-
color:
|
|
19489
|
+
color: C8.red,
|
|
18504
19490
|
backgroundColor: "rgba(239,68,68,0.08)",
|
|
18505
|
-
border: `1px solid ${
|
|
19491
|
+
border: `1px solid ${C8.red}`
|
|
18506
19492
|
},
|
|
18507
19493
|
children: error
|
|
18508
19494
|
}
|
|
@@ -18511,7 +19497,7 @@ function MetaImageField({
|
|
|
18511
19497
|
}
|
|
18512
19498
|
);
|
|
18513
19499
|
}
|
|
18514
|
-
var
|
|
19500
|
+
var C9 = {
|
|
18515
19501
|
black: "#000",
|
|
18516
19502
|
white: "#fff",
|
|
18517
19503
|
green: "#22c55e",
|
|
@@ -18525,15 +19511,15 @@ var C7 = {
|
|
|
18525
19511
|
function getCompletenessColor(count) {
|
|
18526
19512
|
switch (count) {
|
|
18527
19513
|
case 0:
|
|
18528
|
-
return
|
|
19514
|
+
return C9.red;
|
|
18529
19515
|
case 1:
|
|
18530
|
-
return
|
|
19516
|
+
return C9.orange;
|
|
18531
19517
|
case 2:
|
|
18532
|
-
return
|
|
19518
|
+
return C9.yellow;
|
|
18533
19519
|
case 3:
|
|
18534
|
-
return
|
|
19520
|
+
return C9.green;
|
|
18535
19521
|
default:
|
|
18536
|
-
return
|
|
19522
|
+
return C9.textSecondary;
|
|
18537
19523
|
}
|
|
18538
19524
|
}
|
|
18539
19525
|
function getCompletenessLabel(count, ov) {
|
|
@@ -18591,8 +19577,8 @@ function OverviewField({
|
|
|
18591
19577
|
fontFamily: "var(--font-body, Inter, system-ui, sans-serif)",
|
|
18592
19578
|
padding: "12px 14px",
|
|
18593
19579
|
borderRadius: 10,
|
|
18594
|
-
border: `2px solid ${
|
|
18595
|
-
backgroundColor:
|
|
19580
|
+
border: `2px solid ${C9.border}`,
|
|
19581
|
+
backgroundColor: C9.surfaceBg,
|
|
18596
19582
|
boxShadow: "3px 3px 0 0 var(--theme-border-color, rgba(0,0,0,1))",
|
|
18597
19583
|
marginBottom: 12
|
|
18598
19584
|
},
|
|
@@ -18615,7 +19601,7 @@ function OverviewField({
|
|
|
18615
19601
|
fontWeight: 800,
|
|
18616
19602
|
textTransform: "uppercase",
|
|
18617
19603
|
letterSpacing: "0.04em",
|
|
18618
|
-
color:
|
|
19604
|
+
color: C9.textPrimary
|
|
18619
19605
|
},
|
|
18620
19606
|
children: t.overview.metaCompleteness
|
|
18621
19607
|
}
|
|
@@ -18630,8 +19616,8 @@ function OverviewField({
|
|
|
18630
19616
|
fontSize: 11,
|
|
18631
19617
|
fontWeight: 800,
|
|
18632
19618
|
backgroundColor: completenessColor,
|
|
18633
|
-
color: completenessColor ===
|
|
18634
|
-
border: `2px solid ${
|
|
19619
|
+
color: completenessColor === C9.yellow ? C9.black : C9.white,
|
|
19620
|
+
border: `2px solid ${C9.border}`,
|
|
18635
19621
|
textTransform: "uppercase",
|
|
18636
19622
|
letterSpacing: "0.03em"
|
|
18637
19623
|
},
|
|
@@ -18729,7 +19715,7 @@ function OverviewField({
|
|
|
18729
19715
|
style: {
|
|
18730
19716
|
fontSize: 12,
|
|
18731
19717
|
fontWeight: 600,
|
|
18732
|
-
color: item.filled ?
|
|
19718
|
+
color: item.filled ? C9.textPrimary : C9.textSecondary
|
|
18733
19719
|
},
|
|
18734
19720
|
children: item.label
|
|
18735
19721
|
}
|
|
@@ -18741,7 +19727,7 @@ function OverviewField({
|
|
|
18741
19727
|
marginLeft: "auto",
|
|
18742
19728
|
fontSize: 10,
|
|
18743
19729
|
fontWeight: 700,
|
|
18744
|
-
color: item.filled ?
|
|
19730
|
+
color: item.filled ? C9.green : C9.red,
|
|
18745
19731
|
textTransform: "uppercase",
|
|
18746
19732
|
letterSpacing: "0.03em"
|
|
18747
19733
|
},
|
|
@@ -18758,7 +19744,7 @@ function OverviewField({
|
|
|
18758
19744
|
}
|
|
18759
19745
|
);
|
|
18760
19746
|
}
|
|
18761
|
-
var
|
|
19747
|
+
var C10 = {
|
|
18762
19748
|
cyan: "#00E5FF",
|
|
18763
19749
|
black: "#000",
|
|
18764
19750
|
white: "#fff",
|
|
@@ -18777,10 +19763,10 @@ var G = {
|
|
|
18777
19763
|
descGrey: "#4d5156",
|
|
18778
19764
|
faviconBg: "#e8eaed"};
|
|
18779
19765
|
function charCountColor2(len, min, max) {
|
|
18780
|
-
if (len >= min && len <= max) return
|
|
18781
|
-
if (len > 0 && len < min) return
|
|
18782
|
-
if (len > max) return
|
|
18783
|
-
return
|
|
19766
|
+
if (len >= min && len <= max) return C10.green;
|
|
19767
|
+
if (len > 0 && len < min) return C10.orange;
|
|
19768
|
+
if (len > max) return C10.red;
|
|
19769
|
+
return C10.textSecondary;
|
|
18784
19770
|
}
|
|
18785
19771
|
function truncateText(text, maxChars) {
|
|
18786
19772
|
if (text.length <= maxChars) return text;
|
|
@@ -18835,8 +19821,8 @@ function SerpPreview({
|
|
|
18835
19821
|
padding: "10px 12px",
|
|
18836
19822
|
cursor: "pointer",
|
|
18837
19823
|
borderRadius: 8,
|
|
18838
|
-
border: `2px solid ${
|
|
18839
|
-
backgroundColor:
|
|
19824
|
+
border: `2px solid ${C10.border}`,
|
|
19825
|
+
backgroundColor: C10.surface50,
|
|
18840
19826
|
userSelect: "none"
|
|
18841
19827
|
},
|
|
18842
19828
|
children: [
|
|
@@ -18851,7 +19837,7 @@ function SerpPreview({
|
|
|
18851
19837
|
fontWeight: 800,
|
|
18852
19838
|
textTransform: "uppercase",
|
|
18853
19839
|
letterSpacing: "0.04em",
|
|
18854
|
-
color:
|
|
19840
|
+
color: C10.textPrimary
|
|
18855
19841
|
},
|
|
18856
19842
|
children: [
|
|
18857
19843
|
/* @__PURE__ */ jsxs(
|
|
@@ -18883,7 +19869,7 @@ function SerpPreview({
|
|
|
18883
19869
|
transition: "transform 0.2s",
|
|
18884
19870
|
display: "inline-block",
|
|
18885
19871
|
transform: open ? "rotate(90deg)" : "none",
|
|
18886
|
-
color:
|
|
19872
|
+
color: C10.textSecondary
|
|
18887
19873
|
},
|
|
18888
19874
|
children: "\u25B6"
|
|
18889
19875
|
}
|
|
@@ -18916,10 +19902,10 @@ function SerpPreview({
|
|
|
18916
19902
|
"div",
|
|
18917
19903
|
{
|
|
18918
19904
|
style: {
|
|
18919
|
-
backgroundColor:
|
|
18920
|
-
border: `2px solid ${
|
|
19905
|
+
backgroundColor: C10.white,
|
|
19906
|
+
border: `2px solid ${C10.border}`,
|
|
18921
19907
|
borderRadius: 12,
|
|
18922
|
-
boxShadow: `3px 3px 0 0 ${
|
|
19908
|
+
boxShadow: `3px 3px 0 0 ${C10.border}`,
|
|
18923
19909
|
padding: isDesktop ? 20 : 14,
|
|
18924
19910
|
maxWidth: isDesktop ? 650 : 380,
|
|
18925
19911
|
overflow: "hidden"
|
|
@@ -18982,7 +19968,7 @@ function SerpPreview({
|
|
|
18982
19968
|
style: {
|
|
18983
19969
|
fontSize: 14,
|
|
18984
19970
|
fontWeight: 400,
|
|
18985
|
-
color:
|
|
19971
|
+
color: C10.black,
|
|
18986
19972
|
lineHeight: 1.3,
|
|
18987
19973
|
whiteSpace: "nowrap",
|
|
18988
19974
|
overflow: "hidden",
|
|
@@ -19046,7 +20032,7 @@ function SerpPreview({
|
|
|
19046
20032
|
"span",
|
|
19047
20033
|
{
|
|
19048
20034
|
style: {
|
|
19049
|
-
color:
|
|
20035
|
+
color: C10.textSecondary,
|
|
19050
20036
|
fontStyle: "italic",
|
|
19051
20037
|
fontSize: titleFontSize - 2
|
|
19052
20038
|
},
|
|
@@ -19075,7 +20061,7 @@ function SerpPreview({
|
|
|
19075
20061
|
"span",
|
|
19076
20062
|
{
|
|
19077
20063
|
style: {
|
|
19078
|
-
color:
|
|
20064
|
+
color: C10.textSecondary,
|
|
19079
20065
|
fontStyle: "italic",
|
|
19080
20066
|
fontSize: descFontSize - 1
|
|
19081
20067
|
},
|
|
@@ -19108,7 +20094,7 @@ function SerpPreview({
|
|
|
19108
20094
|
fontSize: 11
|
|
19109
20095
|
},
|
|
19110
20096
|
children: [
|
|
19111
|
-
/* @__PURE__ */ jsx("span", { style: { color:
|
|
20097
|
+
/* @__PURE__ */ jsx("span", { style: { color: C10.textSecondary, fontWeight: 600 }, children: t.serpPreview.previewTitle }),
|
|
19112
20098
|
/* @__PURE__ */ jsxs(
|
|
19113
20099
|
"span",
|
|
19114
20100
|
{
|
|
@@ -19137,7 +20123,7 @@ function SerpPreview({
|
|
|
19137
20123
|
fontSize: 11
|
|
19138
20124
|
},
|
|
19139
20125
|
children: [
|
|
19140
|
-
/* @__PURE__ */ jsx("span", { style: { color:
|
|
20126
|
+
/* @__PURE__ */ jsx("span", { style: { color: C10.textSecondary, fontWeight: 600 }, children: t.serpPreview.previewDescription }),
|
|
19141
20127
|
/* @__PURE__ */ jsxs(
|
|
19142
20128
|
"span",
|
|
19143
20129
|
{
|
|
@@ -19166,14 +20152,14 @@ function SerpPreview({
|
|
|
19166
20152
|
fontSize: 11
|
|
19167
20153
|
},
|
|
19168
20154
|
children: [
|
|
19169
|
-
/* @__PURE__ */ jsx("span", { style: { color:
|
|
20155
|
+
/* @__PURE__ */ jsx("span", { style: { color: C10.textSecondary, fontWeight: 600 }, children: t.serpPreview.url }),
|
|
19170
20156
|
/* @__PURE__ */ jsxs(
|
|
19171
20157
|
"span",
|
|
19172
20158
|
{
|
|
19173
20159
|
style: {
|
|
19174
20160
|
fontWeight: 700,
|
|
19175
20161
|
fontVariantNumeric: "tabular-nums",
|
|
19176
|
-
color: fullUrl.length <= 75 ?
|
|
20162
|
+
color: fullUrl.length <= 75 ? C10.green : C10.red
|
|
19177
20163
|
},
|
|
19178
20164
|
children: [
|
|
19179
20165
|
fullUrl.length,
|
|
@@ -19208,13 +20194,13 @@ function DeviceButton({
|
|
|
19208
20194
|
gap: 5,
|
|
19209
20195
|
padding: "4px 12px",
|
|
19210
20196
|
borderRadius: 6,
|
|
19211
|
-
border: `2px solid ${
|
|
20197
|
+
border: `2px solid ${C10.border}`,
|
|
19212
20198
|
fontSize: 11,
|
|
19213
20199
|
fontWeight: 700,
|
|
19214
20200
|
cursor: "pointer",
|
|
19215
|
-
backgroundColor: active ?
|
|
19216
|
-
color: active ?
|
|
19217
|
-
boxShadow: active ? `2px 2px 0 0 ${
|
|
20201
|
+
backgroundColor: active ? C10.cyan : C10.surfaceBg,
|
|
20202
|
+
color: active ? C10.black : C10.textPrimary,
|
|
20203
|
+
boxShadow: active ? `2px 2px 0 0 ${C10.border}` : "none",
|
|
19218
20204
|
transition: "background-color 0.15s"
|
|
19219
20205
|
},
|
|
19220
20206
|
children: [
|