@lynxflow/seo-engine 1.6.1 → 1.6.3

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/dist/index.mjs CHANGED
@@ -3231,6 +3231,87 @@ function resolveBuiltInLocations(territories) {
3231
3231
  return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
3232
3232
  }
3233
3233
 
3234
+ // src/og-image-generator.ts
3235
+ class OgImageGenerator {
3236
+ static generateOgImageUrl(domain, pageMeta, brandName) {
3237
+ const cleanDomain = domain.replace(/\/+$/, "");
3238
+ const params = new URLSearchParams({
3239
+ title: pageMeta.h1 || pageMeta.title,
3240
+ badge: pageMeta.matrixFamily.toUpperCase(),
3241
+ desc: pageMeta.description ? pageMeta.description.slice(0, 120) : "",
3242
+ brand: brandName
3243
+ });
3244
+ if (pageMeta.location) {
3245
+ params.set("loc", `${pageMeta.location.name}, ${pageMeta.location.country}`);
3246
+ }
3247
+ return `${cleanDomain}/api/og?${params.toString()}`;
3248
+ }
3249
+ static renderDynamicOgSvg(opts) {
3250
+ const brand = opts.brandName || "LynxSEO";
3251
+ const badge = opts.badgeText || "OFFICIAL";
3252
+ const title = opts.title.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3253
+ const loc = opts.locationName ? `\uD83D\uDCCD ${opts.locationName}` : "⚡ Instant Cloud Setup";
3254
+ const rating = opts.ratingValue ?? 4.9;
3255
+ const reviews = opts.reviewCount ?? 1280;
3256
+ const accent = opts.brandColor || "#6366F1";
3257
+ return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
3258
+ <defs>
3259
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
3260
+ <stop offset="0%" stop-color="#090D16"/>
3261
+ <stop offset="50%" stop-color="#0F172A"/>
3262
+ <stop offset="100%" stop-color="#020617"/>
3263
+ </linearGradient>
3264
+ <linearGradient id="textGrad" x1="0%" y1="0%" x2="100%" y2="0%">
3265
+ <stop offset="0%" stop-color="#FFFFFF"/>
3266
+ <stop offset="100%" stop-color="#CBD5E1"/>
3267
+ </linearGradient>
3268
+ <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
3269
+ <feGaussianBlur stdDeviation="80" result="blur" />
3270
+ </filter>
3271
+ </defs>
3272
+
3273
+ <!-- Background -->
3274
+ <rect width="1200" height="630" fill="url(#bgGrad)"/>
3275
+
3276
+ <!-- Subtle Ambient Glow -->
3277
+ <circle cx="200" cy="150" r="220" fill="${accent}" opacity="0.15" filter="url(#glow)"/>
3278
+ <circle cx="1000" cy="480" r="260" fill="${accent}" opacity="0.12" filter="url(#glow)"/>
3279
+
3280
+ <!-- Top Glassmorphism Badge -->
3281
+ <g transform="translate(80, 80)">
3282
+ <rect width="220" height="42" rx="21" fill="${accent}" fill-opacity="0.15" stroke="${accent}" stroke-opacity="0.4" stroke-width="1.5"/>
3283
+ <text x="110" y="26" fill="#FFFFFF" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="14" font-weight="700" text-anchor="middle" letter-spacing="1.5">${badge}</text>
3284
+ </g>
3285
+
3286
+ <!-- Location / Scope Pill -->
3287
+ <g transform="translate(320, 80)">
3288
+ <rect width="260" height="42" rx="21" fill="#1E293B" fill-opacity="0.6" stroke="#334155" stroke-width="1"/>
3289
+ <text x="130" y="26" fill="#94A3B8" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="14" font-weight="600" text-anchor="middle">${loc}</text>
3290
+ </g>
3291
+
3292
+ <!-- Giant Title -->
3293
+ <text x="80" y="250" fill="url(#textGrad)" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="52" font-weight="900" letter-spacing="-1.5">
3294
+ <tspan x="80" dy="0">${title.slice(0, 38)}</tspan>
3295
+ ${title.length > 38 ? `<tspan x="80" dy="68">${title.slice(38, 80)}</tspan>` : ""}
3296
+ </text>
3297
+
3298
+ <!-- Trust Stars & Social Proof -->
3299
+ <g transform="translate(80, 470)">
3300
+ <text x="0" y="32" fill="#FBBF24" font-size="24">★★★★★</text>
3301
+ <text x="140" y="30" fill="#F8FAFC" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="20" font-weight="800">${rating} / 5</text>
3302
+ <text x="210" y="30" fill="#64748B" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="16" font-weight="500">(${reviews.toLocaleString()} verified reviews)</text>
3303
+ </g>
3304
+
3305
+ <!-- Footer Brand -->
3306
+ <g transform="translate(80, 540)">
3307
+ <line x1="0" y1="0" x2="1040" y2="0" stroke="#1E293B" stroke-width="1.5"/>
3308
+ <text x="0" y="42" fill="#FFFFFF" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="20" font-weight="800">${brand}</text>
3309
+ <text x="1040" y="42" fill="#64748B" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="16" font-weight="500" text-anchor="end">Engineered with @lynxflow/seo-engine</text>
3310
+ </g>
3311
+ </svg>`;
3312
+ }
3313
+ }
3314
+
3234
3315
  // src/matrix-engine.ts
3235
3316
  class PseoMatrixEngine {
3236
3317
  legalEngine;
@@ -3734,6 +3815,43 @@ class PseoMatrixEngine {
3734
3815
  }
3735
3816
  if (data.calculators) {
3736
3817
  allPages.push(...this.generateCalculatorMatrix(domain, data.calculators, options));
3818
+ } else if (options.enableFreeTools !== false) {
3819
+ const defaultCalculators = [
3820
+ {
3821
+ slug: "serp-preview-simulator",
3822
+ title: "Google SERP Snippet Preview & CTR Simulator",
3823
+ formulaDescription: "Simulate desktop and mobile Google search appearance and optimize CTR",
3824
+ savingsMetric: "Search Click-Through Rate",
3825
+ inputs: ["pageTitle", "metaDescription", "targetKeyword"]
3826
+ },
3827
+ {
3828
+ slug: "roi-calculator",
3829
+ title: "Automation ROI & Time-Saved Calculator",
3830
+ formulaDescription: "Calculate weekly operational hours recovered and monthly budget saved",
3831
+ savingsMetric: "Monthly Net Financial Savings",
3832
+ inputs: ["hoursSpentWeekly", "hourlyRate", "teamMembers"]
3833
+ },
3834
+ {
3835
+ slug: "seo-slug-generator",
3836
+ title: "SEO URL Slug Cleaner & Optimizer",
3837
+ formulaDescription: "Strip stop words, dates, and noise characters for high-CTR clean URLs",
3838
+ savingsMetric: "URL Cleanliness & Readability",
3839
+ inputs: ["rawTitle", "language"]
3840
+ },
3841
+ {
3842
+ slug: "keyword-density-checker",
3843
+ title: "Live Keyword Density & Readability Analyzer",
3844
+ formulaDescription: "Analyze n-gram frequency, reading time, and content depth",
3845
+ savingsMetric: "Content Optimization Score",
3846
+ inputs: ["articleText"]
3847
+ }
3848
+ ];
3849
+ allPages.push(...this.generateCalculatorMatrix(domain, defaultCalculators, options));
3850
+ }
3851
+ for (const p of allPages) {
3852
+ if (!p.ogImageUrl) {
3853
+ p.ogImageUrl = OgImageGenerator.generateOgImageUrl(domain, p, options.brandName);
3854
+ }
3737
3855
  }
3738
3856
  return allPages;
3739
3857
  }
@@ -3978,6 +4096,253 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
3978
4096
  return "";
3979
4097
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
3980
4098
  }
4099
+ // src/ui-icons.ts
4100
+ var UI_ICONS = {
4101
+ zap: {
4102
+ name: "Zap / Lightning",
4103
+ viewBox: "0 0 24 24",
4104
+ svgPath: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4105
+ },
4106
+ shield: {
4107
+ name: "Shield / Security",
4108
+ viewBox: "0 0 24 24",
4109
+ svgPath: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4110
+ },
4111
+ star: {
4112
+ name: "Star / Rating",
4113
+ viewBox: "0 0 24 24",
4114
+ svgPath: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" fill="currentColor"/>'
4115
+ },
4116
+ chart: {
4117
+ name: "Chart / Analytics",
4118
+ viewBox: "0 0 24 24",
4119
+ svgPath: '<line x1="18" y1="20" x2="18" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="12" y1="20" x2="12" y2="4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="6" y1="20" x2="6" y2="14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>'
4120
+ },
4121
+ calendar: {
4122
+ name: "Calendar / Scheduling",
4123
+ viewBox: "0 0 24 24",
4124
+ svgPath: '<rect x="3" y="4" width="18" height="18" rx="2" ry="2" fill="none" stroke="currentColor" stroke-width="2"/><line x1="16" y1="2" x2="16" y2="6" stroke="currentColor" stroke-width="2"/><line x1="8" y1="2" x2="8" y2="6" stroke="currentColor" stroke-width="2"/><line x1="3" y1="10" x2="21" y2="10" stroke="currentColor" stroke-width="2"/>'
4125
+ },
4126
+ users: {
4127
+ name: "Users / Team",
4128
+ viewBox: "0 0 24 24",
4129
+ svgPath: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="9" cy="7" r="4" fill="none" stroke="currentColor" stroke-width="2"/><path d="M23 21v-2a4 4 0 0 0-3-3.87" fill="none" stroke="currentColor" stroke-width="2"/><path d="M16 3.13a4 4 0 0 1 0 7.75" fill="none" stroke="currentColor" stroke-width="2"/>'
4130
+ },
4131
+ rocket: {
4132
+ name: "Rocket / Speed",
4133
+ viewBox: "0 0 24 24",
4134
+ svgPath: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" fill="none" stroke="currentColor" stroke-width="2"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" fill="none" stroke="currentColor" stroke-width="2"/>'
4135
+ },
4136
+ sparkles: {
4137
+ name: "Sparkles / AI",
4138
+ viewBox: "0 0 24 24",
4139
+ svgPath: '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3L12 3z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>'
4140
+ },
4141
+ check: {
4142
+ name: "Check / Success",
4143
+ viewBox: "0 0 24 24",
4144
+ svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4145
+ },
4146
+ lock: {
4147
+ name: "Lock / Privacy",
4148
+ viewBox: "0 0 24 24",
4149
+ svgPath: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4" fill="none" stroke="currentColor" stroke-width="2"/>'
4150
+ },
4151
+ clock: {
4152
+ name: "Clock / 24-7",
4153
+ viewBox: "0 0 24 24",
4154
+ svgPath: '<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/><polyline points="12 6 12 12 16 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>'
4155
+ },
4156
+ phone: {
4157
+ name: "Phone / Contact",
4158
+ viewBox: "0 0 24 24",
4159
+ svgPath: '<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" fill="none" stroke="currentColor" stroke-width="2"/>'
4160
+ },
4161
+ mapPin: {
4162
+ name: "Map Pin / Local",
4163
+ viewBox: "0 0 24 24",
4164
+ svgPath: '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="10" r="3" fill="none" stroke="currentColor" stroke-width="2"/>'
4165
+ },
4166
+ euro: {
4167
+ name: "Euro / Pricing",
4168
+ viewBox: "0 0 24 24",
4169
+ svgPath: '<path d="M4 10h12M4 14h9M19 6a7.7 7.7 0 0 0-5.2-2A7.9 7.9 0 0 0 6 12a7.9 7.9 0 0 0 7.8 8 7.7 7.7 0 0 0 5.2-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>'
4170
+ },
4171
+ arrowRight: {
4172
+ name: "Arrow Right",
4173
+ viewBox: "0 0 24 24",
4174
+ svgPath: '<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><polyline points="12 5 19 12 12 19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4175
+ }
4176
+ };
4177
+ function renderUiIconSvg(iconName, className = "w-5 h-5 inline-block") {
4178
+ const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
4179
+ return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
4180
+ }
4181
+ function resolveFeatureIcon(featureText) {
4182
+ const lower = featureText.toLowerCase();
4183
+ if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
4184
+ return "shield";
4185
+ }
4186
+ if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
4187
+ return "zap";
4188
+ }
4189
+ if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
4190
+ return "sparkles";
4191
+ }
4192
+ if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
4193
+ return "chart";
4194
+ }
4195
+ if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
4196
+ return "calendar";
4197
+ }
4198
+ if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
4199
+ return "users";
4200
+ }
4201
+ if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
4202
+ return "euro";
4203
+ }
4204
+ if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
4205
+ return "mapPin";
4206
+ }
4207
+ if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
4208
+ return "clock";
4209
+ }
4210
+ return "zap";
4211
+ }
4212
+ // src/free-public-tools.ts
4213
+ class FreePublicToolsEngine {
4214
+ static simulateSerpSnippet(input) {
4215
+ const title = (input.title || "").trim();
4216
+ const description = (input.description || "").trim();
4217
+ const url = (input.url || "https://example.com").trim();
4218
+ const kw = (input.targetKeyword || "").toLowerCase().trim();
4219
+ const titlePixelWidth = title.length * 9.5;
4220
+ const maxTitlePixels = 580;
4221
+ const maxDescChars = 158;
4222
+ const isTitleTruncated = titlePixelWidth > maxTitlePixels;
4223
+ const truncatedTitle = isTitleTruncated ? title.slice(0, 56) + "..." : title;
4224
+ const isDescriptionTruncated = description.length > maxDescChars;
4225
+ const truncatedDescription = isDescriptionTruncated ? description.slice(0, 155) + "..." : description;
4226
+ let ctrScore = 50;
4227
+ const recommendations = [];
4228
+ if (title.length >= 40 && title.length <= 60) {
4229
+ ctrScore += 20;
4230
+ } else if (title.length < 30) {
4231
+ recommendations.push("Le titre est trop court (< 30 car.). Ajoutez des précisions pour booster le clic.");
4232
+ } else if (isTitleTruncated) {
4233
+ ctrScore -= 10;
4234
+ recommendations.push("Le titre dépasse 580px et sera tronqué sur Google Desktop.");
4235
+ }
4236
+ if (kw && title.toLowerCase().includes(kw)) {
4237
+ ctrScore += 15;
4238
+ } else if (kw) {
4239
+ recommendations.push(`Le mot-clé principal "${kw}" est absent du titre.`);
4240
+ }
4241
+ if (description.length >= 120 && description.length <= 158) {
4242
+ ctrScore += 15;
4243
+ } else if (description.length < 80) {
4244
+ recommendations.push("La méta-description est trop courte (< 80 car.).");
4245
+ }
4246
+ return {
4247
+ title,
4248
+ truncatedTitle,
4249
+ titlePixelWidth: Math.round(titlePixelWidth),
4250
+ isTitleTruncated,
4251
+ description,
4252
+ truncatedDescription,
4253
+ isDescriptionTruncated,
4254
+ urlPreview: url,
4255
+ estimatedCtrScore: Math.min(Math.max(ctrScore, 10), 100),
4256
+ recommendations
4257
+ };
4258
+ }
4259
+ static calculateRoi(input) {
4260
+ const hours = Math.max(input.hoursSpentPerWeek || 5, 1);
4261
+ const rate = Math.max(input.hourlyRateOrSalary || 35, 1);
4262
+ const team = Math.max(input.teamMembersCount || 1, 1);
4263
+ const softwareCost = input.softwareMonthlyPrice ?? 49;
4264
+ const hoursSavedWeekly = Math.round(hours * 0.75 * team * 10) / 10;
4265
+ const hoursSavedMonthly = Math.round(hoursSavedWeekly * 4.33);
4266
+ const monthlyGrossSavings = hoursSavedMonthly * rate;
4267
+ const monthlyNetSavings = Math.max(monthlyGrossSavings - softwareCost, 0);
4268
+ const annualCostSavings = monthlyNetSavings * 12;
4269
+ const roiPercentage = softwareCost > 0 ? Math.round(monthlyNetSavings / softwareCost * 100) : 1000;
4270
+ const breakEvenDays = monthlyGrossSavings > 0 ? Math.max(Math.round(softwareCost / (monthlyGrossSavings / 30)), 1) : 30;
4271
+ return {
4272
+ hoursSavedWeekly,
4273
+ hoursSavedMonthly,
4274
+ monthlyCostSavings: Math.round(monthlyNetSavings),
4275
+ annualCostSavings: Math.round(annualCostSavings),
4276
+ roiPercentage,
4277
+ breakEvenDays
4278
+ };
4279
+ }
4280
+ static analyzeTextDensity(text, language = "fr") {
4281
+ if (!text || typeof text !== "string") {
4282
+ return { totalWords: 0, characterCount: 0, readingTimeMinutes: 0, topKeywords: [], seoReadabilityScore: 0 };
4283
+ }
4284
+ const clean = text.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, " ");
4285
+ const words = clean.split(/\s+/).filter((w) => w.length > 2);
4286
+ const totalWords = words.length;
4287
+ const characterCount = text.length;
4288
+ const readingTimeMinutes = Math.max(Math.round(totalWords / 200 * 10) / 10, 0.5);
4289
+ const stopWords = new Set(["pour", "dans", "avec", "sur", "les", "des", "une", "que", "qui", "est", "par", "the", "and", "for", "with", "this", "that"]);
4290
+ const frequencyMap = new Map;
4291
+ for (const w of words) {
4292
+ if (!stopWords.has(w)) {
4293
+ frequencyMap.set(w, (frequencyMap.get(w) || 0) + 1);
4294
+ }
4295
+ }
4296
+ const sorted = Array.from(frequencyMap.entries()).sort((a, b) => b[1] - a[1]);
4297
+ const topKeywords = sorted.slice(0, 8).map(([word, count]) => ({
4298
+ word,
4299
+ count,
4300
+ densityPercent: totalWords > 0 ? Math.round(count / totalWords * 1000) / 10 : 0
4301
+ }));
4302
+ let seoReadabilityScore = 70;
4303
+ if (totalWords >= 600)
4304
+ seoReadabilityScore += 20;
4305
+ else if (totalWords < 300)
4306
+ seoReadabilityScore -= 25;
4307
+ return {
4308
+ totalWords,
4309
+ characterCount,
4310
+ readingTimeMinutes,
4311
+ topKeywords,
4312
+ seoReadabilityScore: Math.min(Math.max(seoReadabilityScore, 10), 100)
4313
+ };
4314
+ }
4315
+ static cleanSlugTool(input, lang = "fr") {
4316
+ const clean = cleanSeoSlug(input, { language: lang });
4317
+ return {
4318
+ original: input,
4319
+ cleanSlug: clean,
4320
+ charSavings: Math.max(input.length - clean.length, 0)
4321
+ };
4322
+ }
4323
+ static generateSampleSchema(type, name, url) {
4324
+ if (type === "localBusiness") {
4325
+ return ExtendedSchemaGraphBuilder.buildLocalBusiness({
4326
+ name,
4327
+ url,
4328
+ city: "Paris",
4329
+ country: "France"
4330
+ });
4331
+ }
4332
+ if (type === "faq") {
4333
+ return ExtendedSchemaGraphBuilder.buildFAQPage([
4334
+ { question: `Comment fonctionne ${name} ?`, answer: `${name} automatise vos opérations en quelques clics.` },
4335
+ { question: `Quel est le tarif de ${name} ?`, answer: `Un essai gratuit de 14 jours est disponible sans carte bancaire.` }
4336
+ ]);
4337
+ }
4338
+ return ExtendedSchemaGraphBuilder.buildSoftwareApplication({
4339
+ name,
4340
+ description: `Solution logicielle professionnelle ${name}`,
4341
+ url,
4342
+ priceMonthly: 29
4343
+ });
4344
+ }
4345
+ }
3981
4346
  // src/urlytics-engine.ts
3982
4347
  class UrlyticsEngine {
3983
4348
  static parseUrl(rawUrl) {
@@ -4388,22 +4753,34 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
4388
4753
  4. **\uD83C\uDFA8 Vector Brand & Social Icons (\`renderBrandIconSvg\`):**
4389
4754
  - Zero-dependency official SVG vectors for 30+ brands (Facebook, Instagram, LinkedIn, Shopify, Slack, WhatsApp, Google, GitHub, TikTok, YouTube).
4390
4755
 
4391
- 5. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4756
+ 5. **✨ Standard UI & Feature Icons (\`renderUiIconSvg\`, \`resolveFeatureIcon\`):**
4757
+ - Zero-dependency Lucide-style vector icons (shield, zap, chart, calendar, users, rocket, sparkles, check, lock, clock, phone, mapPin, euro).
4758
+ - Semantic keyword auto-resolver: Automatically maps feature copy to the most relevant icon.
4759
+
4760
+ 6. **\uD83D\uDDBC️ Dynamic OpenGraph (OG) Image Generator (\`OgImageGenerator\`):**
4761
+ - \`renderDynamicOgSvg(opts)\`: Generates high-converting 1200x630 SVG social cards with glassmorphism badges, star ratings, and gradient backdrops.
4762
+ - \`generateOgImageUrl(domain, pageMeta, brandName)\`: Edge-ready query URL for \`app/api/og/route.ts\`.
4763
+
4764
+ 7. **\uD83D\uDEE0️ Free Public Interactive Tools & Lead Magnets (\`FreePublicToolsEngine\`):**
4765
+ - Ahrefs/Semrush-style high-intent public calculators: Google SERP Preview Simulator, Automation ROI Calculator, Keyword Density Analyzer, and Schema.org Generator.
4766
+ - Boosts dwell time, organic backlinks, and visitor-to-customer conversion.
4767
+
4768
+ 8. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4392
4769
  - \`buildAggregateRating\` (Google Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
4393
4770
 
4394
- 6. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4771
+ 9. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4395
4772
  - Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
4396
4773
 
4397
- 7. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4774
+ 10. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4398
4775
  - Combinatorial matrix generation: Products × Modifiers × Locations with match types (\`[exact]\`, \`"phrase"\`, \`+broad\`) and intent classification.
4399
4776
 
4400
- 8. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4777
+ 11. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4401
4778
  - 7 SeedRank-grade disclaimer templates, 90-day staleness verification, and multi-language anti-disparagement guardian.
4402
4779
 
4403
- 9. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4780
+ 12. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4404
4781
  - Synchronizes total programmatic pages, indexed hubs, and AI bot crawl hits (GPTBot, ClaudeBot, PerplexityBot) with the LynxSEO Studio dashboard.
4405
4782
 
4406
- 10. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4783
+ 13. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4407
4784
  - \`cleanSeoSlug(text, { language })\`: Strips accents, stop words across 10+ languages, and non-alphanumeric noise.
4408
4785
  - Outputs Google-validated Schema.org JSON-LD graphs with 0 syntax errors.
4409
4786
  `.trim();
@@ -6552,8 +6929,10 @@ var LynxSeo = {
6552
6929
  var src_default = LynxSeo;
6553
6930
  export {
6554
6931
  validateSeoSlug,
6932
+ resolveFeatureIcon,
6555
6933
  resolveBuiltInLocations,
6556
6934
  resolveBrandIcon,
6935
+ renderUiIconSvg,
6557
6936
  renderBrandIconSvg,
6558
6937
  getSeoAgentPrompt,
6559
6938
  src_default as default,
@@ -6561,6 +6940,7 @@ export {
6561
6940
  cleanSeoSlug,
6562
6941
  YoastParityEngine,
6563
6942
  UrlyticsEngine,
6943
+ UI_ICONS,
6564
6944
  TokenQuotaManager,
6565
6945
  TechnicalRulesAuditor,
6566
6946
  TeamRbacEngine,
@@ -6580,6 +6960,7 @@ export {
6580
6960
  RankMathParityEngine,
6581
6961
  PseoMatrixEngine,
6582
6962
  PSEO_AGENT_SYSTEM_PROMPT,
6963
+ OgImageGenerator,
6583
6964
  NGramDensityAnalyzer,
6584
6965
  McpSeoServerHub,
6585
6966
  MasterMarketingEngine,
@@ -6600,6 +6981,7 @@ export {
6600
6981
  I18nDetector,
6601
6982
  HyperswitchGateway,
6602
6983
  GeoMeshLinkingEngine,
6984
+ FreePublicToolsEngine,
6603
6985
  ExtendedSchemaGraphBuilder,
6604
6986
  EmbeddableSeoWidgetGenerator,
6605
6987
  DeepCrawlerAuditor,
@@ -95,6 +95,7 @@ export interface PseoCalculator {
95
95
  title: string;
96
96
  formulaDescription: string;
97
97
  savingsMetric: string;
98
+ inputs?: string[];
98
99
  }
99
100
  export interface SchemaOrgGraph {
100
101
  "@context": "https://schema.org";
@@ -110,6 +111,7 @@ export interface GeneratedPageMeta {
110
111
  robots: "index, follow" | "noindex, follow";
111
112
  schemaGraph: SchemaOrgGraph;
112
113
  disclaimerText?: string;
114
+ ogImageUrl?: string;
113
115
  neighboringLinks?: {
114
116
  name: string;
115
117
  url: string;
@@ -131,6 +133,7 @@ export interface MatrixOptions {
131
133
  countries?: string[] | string;
132
134
  territories?: string[] | string;
133
135
  cleanDirectRoutes?: boolean;
136
+ enableFreeTools?: boolean;
134
137
  minPopulationToIndex?: number;
135
138
  defaultCurrency?: string;
136
139
  defaultCurrencySymbol?: string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 🖼️ Dynamic OpenGraph (OG) & Social Card Image Generator
3
+ * Generates high-converting 1200x630 SVG social preview cards and URL queries
4
+ * with zero heavy headless browser dependencies (Puppeteer/Playwright free).
5
+ */
6
+ import { GeneratedPageMeta } from "./matrix-engine";
7
+ export interface OgImageOptions {
8
+ brandName: string;
9
+ title: string;
10
+ badgeText?: string;
11
+ subtitle?: string;
12
+ locationName?: string;
13
+ ratingValue?: number;
14
+ reviewCount?: number;
15
+ brandColor?: string;
16
+ }
17
+ export declare class OgImageGenerator {
18
+ /**
19
+ * Generates a fully formatted URL for dynamic Edge / API OG image generation.
20
+ * e.g. /api/og?title=...&badge=...&city=...
21
+ */
22
+ static generateOgImageUrl(domain: string, pageMeta: GeneratedPageMeta, brandName: string): string;
23
+ /**
24
+ * Generates an ultra-crisp 1200x630 pure SVG image string for social previews.
25
+ */
26
+ static renderDynamicOgSvg(opts: OgImageOptions): string;
27
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 🎨 Universal Zero-Dependency UI & Feature SVG Icons
3
+ * Provides crisp, modern Lucide-style vector icons for features, trust badges, and UI components.
4
+ * Includes automatic semantic keyword-to-icon detection for feature descriptions.
5
+ */
6
+ export interface UiIcon {
7
+ name: string;
8
+ viewBox: string;
9
+ svgPath: string;
10
+ }
11
+ export declare const UI_ICONS: Record<string, UiIcon>;
12
+ /**
13
+ * Returns raw inline SVG markup for a standard UI icon.
14
+ */
15
+ export declare function renderUiIconSvg(iconName: string, className?: string): string;
16
+ /**
17
+ * Semantically resolves the most relevant UI icon based on feature text keywords.
18
+ */
19
+ export declare function resolveFeatureIcon(featureText: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "High-Performance Multilingual Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -8,6 +8,9 @@ import { UrlyticsEngine } from "./urlytics-engine";
8
8
  import { KeywordPermutatorEngine } from "./keyword-permutator";
9
9
  import { renderBrandIconSvg } from "./brand-icons";
10
10
  import { I18nDetector } from "./i18n-detector";
11
+ import { renderUiIconSvg, resolveFeatureIcon } from "./ui-icons";
12
+ import { OgImageGenerator } from "./og-image-generator";
13
+ import { FreePublicToolsEngine } from "./free-public-tools";
11
14
 
12
15
  describe("Multilingual SEO Slug Engine (10+ Languages)", () => {
13
16
  it("English: Strips stop words and years", () => {
@@ -265,4 +268,73 @@ describe("Audited Reference Engines (Advertools, Santifer, Seonaut)", () => {
265
268
  expect(telemetry.brandName).toBe("Acme");
266
269
  expect(telemetry.servicesCount).toBe(1);
267
270
  });
271
+
272
+ it("UI Icons: Renders standard Lucide-style SVG and resolves semantic feature icons", () => {
273
+ const shieldSvg = renderUiIconSvg("shield");
274
+ expect(shieldSvg).toContain("<svg");
275
+ expect(shieldSvg).toContain("viewBox");
276
+
277
+ expect(resolveFeatureIcon("Bank-grade RGPD Security")).toBe("shield");
278
+ expect(resolveFeatureIcon("Real-time automated sync")).toBe("zap");
279
+ expect(resolveFeatureIcon("Analytics dashboard & KPI tracker")).toBe("chart");
280
+ expect(resolveFeatureIcon("Smart AI content generator")).toBe("sparkles");
281
+ });
282
+
283
+ it("OgImageGenerator: Generates dynamic 1200x630 SVG and Edge URL query", () => {
284
+ const ogSvg = OgImageGenerator.renderDynamicOgSvg({
285
+ brandName: "Acme Cloud",
286
+ title: "Autopost Facebook & Instagram in Paris",
287
+ badgeText: "SOCIAL MEDIA",
288
+ locationName: "Paris, France",
289
+ ratingValue: 4.9,
290
+ reviewCount: 1280,
291
+ });
292
+ expect(ogSvg).toContain("<svg width=\"1200\" height=\"630\"");
293
+ expect(ogSvg).toContain("Autopost Facebook");
294
+ expect(ogSvg).toContain("4.9 / 5");
295
+ expect(ogSvg).toContain("Paris, France");
296
+
297
+ const engine = new PseoMatrixEngine();
298
+ const page = engine.resolvePage(
299
+ ["autopost-facebook", "paris"],
300
+ "https://acme.com",
301
+ {
302
+ services: [{ slug: "autopost-facebook", name: "Autopost Facebook", category: "Social" }],
303
+ },
304
+ { brandName: "Acme", countries: ["france"] },
305
+ );
306
+ expect(page?.ogImageUrl).toBeDefined();
307
+ expect(page?.ogImageUrl).toContain("/api/og?");
308
+ expect(page?.ogImageUrl).toContain("brand=Acme");
309
+ });
310
+
311
+ it("FreePublicToolsEngine: Simulates SERP, calculates ROI, and analyzes density", () => {
312
+ const serp = FreePublicToolsEngine.simulateSerpSnippet({
313
+ title: "Autopost Facebook & Instagram Tool 2026",
314
+ description: "Automate your social media scheduling across all major networks with AI copilot and analytics.",
315
+ url: "https://acme.com/autopost-facebook",
316
+ targetKeyword: "autopost",
317
+ });
318
+ expect(serp.estimatedCtrScore).toBeGreaterThan(50);
319
+ expect(serp.titlePixelWidth).toBeGreaterThan(0);
320
+
321
+ const roi = FreePublicToolsEngine.calculateRoi({
322
+ hoursSpentPerWeek: 10,
323
+ hourlyRateOrSalary: 50,
324
+ teamMembersCount: 2,
325
+ softwareMonthlyPrice: 79,
326
+ });
327
+ expect(roi.monthlyCostSavings).toBeGreaterThan(1000);
328
+ expect(roi.roiPercentage).toBeGreaterThan(100);
329
+
330
+ const density = FreePublicToolsEngine.analyzeTextDensity(
331
+ "Autopost Facebook automatise vos réseaux sociaux. Notre outil autopost vous fait gagner du temps avec autopost.",
332
+ "fr",
333
+ );
334
+ expect(density.totalWords).toBeGreaterThan(5);
335
+ expect(density.topKeywords[0].word).toBe("autopost");
336
+
337
+ const schema = FreePublicToolsEngine.generateSampleSchema("softwareApp", "Acme", "https://acme.com");
338
+ expect(schema["@type"]).toBe("SoftwareApplication");
339
+ });
268
340
  });