@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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 🛠️ Free Public Interactive Tools & Lead Magnets Suite
3
+ * Inspired by Ahrefs, Semrush, Moz, and WordStream free tool ecosystems.
4
+ *
5
+ * Provides 6 zero-dependency interactive calculators and simulators that can be embedded
6
+ * on public programmatic pages to maximize dwell time, engagement, backlink acquisition, and user conversion.
7
+ */
8
+ export interface SerpSimulatorResult {
9
+ title: string;
10
+ truncatedTitle: string;
11
+ titlePixelWidth: number;
12
+ isTitleTruncated: boolean;
13
+ description: string;
14
+ truncatedDescription: string;
15
+ isDescriptionTruncated: boolean;
16
+ urlPreview: string;
17
+ estimatedCtrScore: number;
18
+ recommendations: string[];
19
+ }
20
+ export interface RoiCalculatorResult {
21
+ hoursSavedWeekly: number;
22
+ hoursSavedMonthly: number;
23
+ monthlyCostSavings: number;
24
+ annualCostSavings: number;
25
+ roiPercentage: number;
26
+ breakEvenDays: number;
27
+ }
28
+ export interface DensityAnalysisResult {
29
+ totalWords: number;
30
+ characterCount: number;
31
+ readingTimeMinutes: number;
32
+ topKeywords: {
33
+ word: string;
34
+ count: number;
35
+ densityPercent: number;
36
+ }[];
37
+ seoReadabilityScore: number;
38
+ }
39
+ export declare class FreePublicToolsEngine {
40
+ /**
41
+ * 1. Google SERP Preview & CTR Optimizer Simulator (Ahrefs / Moz Parity)
42
+ */
43
+ static simulateSerpSnippet(input: {
44
+ title: string;
45
+ description: string;
46
+ url: string;
47
+ targetKeyword?: string;
48
+ }): SerpSimulatorResult;
49
+ /**
50
+ * 2. Free ROI & Time-Saved Automation Calculator
51
+ */
52
+ static calculateRoi(input: {
53
+ hoursSpentPerWeek: number;
54
+ hourlyRateOrSalary: number;
55
+ teamMembersCount?: number;
56
+ softwareMonthlyPrice?: number;
57
+ }): RoiCalculatorResult;
58
+ /**
59
+ * 3. Instant Keyword Density & Readability Analyzer
60
+ */
61
+ static analyzeTextDensity(text: string, language?: string): DensityAnalysisResult;
62
+ /**
63
+ * 4. Free SEO Slug Cleanser Helper
64
+ */
65
+ static cleanSlugTool(input: string, lang?: string): {
66
+ original: string;
67
+ cleanSlug: string;
68
+ charSavings: number;
69
+ };
70
+ /**
71
+ * 5. Free Schema.org Generator Helper
72
+ */
73
+ static generateSampleSchema(type: "localBusiness" | "softwareApp" | "faq", name: string, url: string): Record<string, unknown>;
74
+ }
package/dist/index.d.ts CHANGED
@@ -16,6 +16,9 @@ export * from "./matrix-engine";
16
16
  export * from "./built-in-locations";
17
17
  export * from "./i18n-detector";
18
18
  export * from "./brand-icons";
19
+ export * from "./ui-icons";
20
+ export * from "./og-image-generator";
21
+ export * from "./free-public-tools";
19
22
  export * from "./urlytics-engine";
20
23
  export * from "./keyword-permutator";
21
24
  export * from "./llm-prompt";
package/dist/index.js CHANGED
@@ -40,8 +40,10 @@ var __export = (target, all) => {
40
40
  var exports_src = {};
41
41
  __export(exports_src, {
42
42
  validateSeoSlug: () => validateSeoSlug,
43
+ resolveFeatureIcon: () => resolveFeatureIcon,
43
44
  resolveBuiltInLocations: () => resolveBuiltInLocations,
44
45
  resolveBrandIcon: () => resolveBrandIcon,
46
+ renderUiIconSvg: () => renderUiIconSvg,
45
47
  renderBrandIconSvg: () => renderBrandIconSvg,
46
48
  getSeoAgentPrompt: () => getSeoAgentPrompt,
47
49
  default: () => src_default,
@@ -49,6 +51,7 @@ __export(exports_src, {
49
51
  cleanSeoSlug: () => cleanSeoSlug,
50
52
  YoastParityEngine: () => YoastParityEngine,
51
53
  UrlyticsEngine: () => UrlyticsEngine,
54
+ UI_ICONS: () => UI_ICONS,
52
55
  TokenQuotaManager: () => TokenQuotaManager,
53
56
  TechnicalRulesAuditor: () => TechnicalRulesAuditor,
54
57
  TeamRbacEngine: () => TeamRbacEngine,
@@ -68,6 +71,7 @@ __export(exports_src, {
68
71
  RankMathParityEngine: () => RankMathParityEngine,
69
72
  PseoMatrixEngine: () => PseoMatrixEngine,
70
73
  PSEO_AGENT_SYSTEM_PROMPT: () => PSEO_AGENT_SYSTEM_PROMPT,
74
+ OgImageGenerator: () => OgImageGenerator,
71
75
  NGramDensityAnalyzer: () => NGramDensityAnalyzer,
72
76
  McpSeoServerHub: () => McpSeoServerHub,
73
77
  MasterMarketingEngine: () => MasterMarketingEngine,
@@ -88,6 +92,7 @@ __export(exports_src, {
88
92
  I18nDetector: () => I18nDetector,
89
93
  HyperswitchGateway: () => HyperswitchGateway,
90
94
  GeoMeshLinkingEngine: () => GeoMeshLinkingEngine,
95
+ FreePublicToolsEngine: () => FreePublicToolsEngine,
91
96
  ExtendedSchemaGraphBuilder: () => ExtendedSchemaGraphBuilder,
92
97
  EmbeddableSeoWidgetGenerator: () => EmbeddableSeoWidgetGenerator,
93
98
  DeepCrawlerAuditor: () => DeepCrawlerAuditor,
@@ -3338,6 +3343,87 @@ function resolveBuiltInLocations(territories) {
3338
3343
  return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
3339
3344
  }
3340
3345
 
3346
+ // src/og-image-generator.ts
3347
+ class OgImageGenerator {
3348
+ static generateOgImageUrl(domain, pageMeta, brandName) {
3349
+ const cleanDomain = domain.replace(/\/+$/, "");
3350
+ const params = new URLSearchParams({
3351
+ title: pageMeta.h1 || pageMeta.title,
3352
+ badge: pageMeta.matrixFamily.toUpperCase(),
3353
+ desc: pageMeta.description ? pageMeta.description.slice(0, 120) : "",
3354
+ brand: brandName
3355
+ });
3356
+ if (pageMeta.location) {
3357
+ params.set("loc", `${pageMeta.location.name}, ${pageMeta.location.country}`);
3358
+ }
3359
+ return `${cleanDomain}/api/og?${params.toString()}`;
3360
+ }
3361
+ static renderDynamicOgSvg(opts) {
3362
+ const brand = opts.brandName || "LynxSEO";
3363
+ const badge = opts.badgeText || "OFFICIAL";
3364
+ const title = opts.title.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3365
+ const loc = opts.locationName ? `\uD83D\uDCCD ${opts.locationName}` : "⚡ Instant Cloud Setup";
3366
+ const rating = opts.ratingValue ?? 4.9;
3367
+ const reviews = opts.reviewCount ?? 1280;
3368
+ const accent = opts.brandColor || "#6366F1";
3369
+ return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
3370
+ <defs>
3371
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
3372
+ <stop offset="0%" stop-color="#090D16"/>
3373
+ <stop offset="50%" stop-color="#0F172A"/>
3374
+ <stop offset="100%" stop-color="#020617"/>
3375
+ </linearGradient>
3376
+ <linearGradient id="textGrad" x1="0%" y1="0%" x2="100%" y2="0%">
3377
+ <stop offset="0%" stop-color="#FFFFFF"/>
3378
+ <stop offset="100%" stop-color="#CBD5E1"/>
3379
+ </linearGradient>
3380
+ <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
3381
+ <feGaussianBlur stdDeviation="80" result="blur" />
3382
+ </filter>
3383
+ </defs>
3384
+
3385
+ <!-- Background -->
3386
+ <rect width="1200" height="630" fill="url(#bgGrad)"/>
3387
+
3388
+ <!-- Subtle Ambient Glow -->
3389
+ <circle cx="200" cy="150" r="220" fill="${accent}" opacity="0.15" filter="url(#glow)"/>
3390
+ <circle cx="1000" cy="480" r="260" fill="${accent}" opacity="0.12" filter="url(#glow)"/>
3391
+
3392
+ <!-- Top Glassmorphism Badge -->
3393
+ <g transform="translate(80, 80)">
3394
+ <rect width="220" height="42" rx="21" fill="${accent}" fill-opacity="0.15" stroke="${accent}" stroke-opacity="0.4" stroke-width="1.5"/>
3395
+ <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>
3396
+ </g>
3397
+
3398
+ <!-- Location / Scope Pill -->
3399
+ <g transform="translate(320, 80)">
3400
+ <rect width="260" height="42" rx="21" fill="#1E293B" fill-opacity="0.6" stroke="#334155" stroke-width="1"/>
3401
+ <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>
3402
+ </g>
3403
+
3404
+ <!-- Giant Title -->
3405
+ <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">
3406
+ <tspan x="80" dy="0">${title.slice(0, 38)}</tspan>
3407
+ ${title.length > 38 ? `<tspan x="80" dy="68">${title.slice(38, 80)}</tspan>` : ""}
3408
+ </text>
3409
+
3410
+ <!-- Trust Stars & Social Proof -->
3411
+ <g transform="translate(80, 470)">
3412
+ <text x="0" y="32" fill="#FBBF24" font-size="24">★★★★★</text>
3413
+ <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>
3414
+ <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>
3415
+ </g>
3416
+
3417
+ <!-- Footer Brand -->
3418
+ <g transform="translate(80, 540)">
3419
+ <line x1="0" y1="0" x2="1040" y2="0" stroke="#1E293B" stroke-width="1.5"/>
3420
+ <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>
3421
+ <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>
3422
+ </g>
3423
+ </svg>`;
3424
+ }
3425
+ }
3426
+
3341
3427
  // src/matrix-engine.ts
3342
3428
  class PseoMatrixEngine {
3343
3429
  legalEngine;
@@ -3841,6 +3927,43 @@ class PseoMatrixEngine {
3841
3927
  }
3842
3928
  if (data.calculators) {
3843
3929
  allPages.push(...this.generateCalculatorMatrix(domain, data.calculators, options));
3930
+ } else if (options.enableFreeTools !== false) {
3931
+ const defaultCalculators = [
3932
+ {
3933
+ slug: "serp-preview-simulator",
3934
+ title: "Google SERP Snippet Preview & CTR Simulator",
3935
+ formulaDescription: "Simulate desktop and mobile Google search appearance and optimize CTR",
3936
+ savingsMetric: "Search Click-Through Rate",
3937
+ inputs: ["pageTitle", "metaDescription", "targetKeyword"]
3938
+ },
3939
+ {
3940
+ slug: "roi-calculator",
3941
+ title: "Automation ROI & Time-Saved Calculator",
3942
+ formulaDescription: "Calculate weekly operational hours recovered and monthly budget saved",
3943
+ savingsMetric: "Monthly Net Financial Savings",
3944
+ inputs: ["hoursSpentWeekly", "hourlyRate", "teamMembers"]
3945
+ },
3946
+ {
3947
+ slug: "seo-slug-generator",
3948
+ title: "SEO URL Slug Cleaner & Optimizer",
3949
+ formulaDescription: "Strip stop words, dates, and noise characters for high-CTR clean URLs",
3950
+ savingsMetric: "URL Cleanliness & Readability",
3951
+ inputs: ["rawTitle", "language"]
3952
+ },
3953
+ {
3954
+ slug: "keyword-density-checker",
3955
+ title: "Live Keyword Density & Readability Analyzer",
3956
+ formulaDescription: "Analyze n-gram frequency, reading time, and content depth",
3957
+ savingsMetric: "Content Optimization Score",
3958
+ inputs: ["articleText"]
3959
+ }
3960
+ ];
3961
+ allPages.push(...this.generateCalculatorMatrix(domain, defaultCalculators, options));
3962
+ }
3963
+ for (const p of allPages) {
3964
+ if (!p.ogImageUrl) {
3965
+ p.ogImageUrl = OgImageGenerator.generateOgImageUrl(domain, p, options.brandName);
3966
+ }
3844
3967
  }
3845
3968
  return allPages;
3846
3969
  }
@@ -4085,6 +4208,253 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
4085
4208
  return "";
4086
4209
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
4087
4210
  }
4211
+ // src/ui-icons.ts
4212
+ var UI_ICONS = {
4213
+ zap: {
4214
+ name: "Zap / Lightning",
4215
+ viewBox: "0 0 24 24",
4216
+ 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"/>'
4217
+ },
4218
+ shield: {
4219
+ name: "Shield / Security",
4220
+ viewBox: "0 0 24 24",
4221
+ 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"/>'
4222
+ },
4223
+ star: {
4224
+ name: "Star / Rating",
4225
+ viewBox: "0 0 24 24",
4226
+ 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"/>'
4227
+ },
4228
+ chart: {
4229
+ name: "Chart / Analytics",
4230
+ viewBox: "0 0 24 24",
4231
+ 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"/>'
4232
+ },
4233
+ calendar: {
4234
+ name: "Calendar / Scheduling",
4235
+ viewBox: "0 0 24 24",
4236
+ 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"/>'
4237
+ },
4238
+ users: {
4239
+ name: "Users / Team",
4240
+ viewBox: "0 0 24 24",
4241
+ 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"/>'
4242
+ },
4243
+ rocket: {
4244
+ name: "Rocket / Speed",
4245
+ viewBox: "0 0 24 24",
4246
+ 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"/>'
4247
+ },
4248
+ sparkles: {
4249
+ name: "Sparkles / AI",
4250
+ viewBox: "0 0 24 24",
4251
+ 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"/>'
4252
+ },
4253
+ check: {
4254
+ name: "Check / Success",
4255
+ viewBox: "0 0 24 24",
4256
+ svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4257
+ },
4258
+ lock: {
4259
+ name: "Lock / Privacy",
4260
+ viewBox: "0 0 24 24",
4261
+ 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"/>'
4262
+ },
4263
+ clock: {
4264
+ name: "Clock / 24-7",
4265
+ viewBox: "0 0 24 24",
4266
+ 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"/>'
4267
+ },
4268
+ phone: {
4269
+ name: "Phone / Contact",
4270
+ viewBox: "0 0 24 24",
4271
+ 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"/>'
4272
+ },
4273
+ mapPin: {
4274
+ name: "Map Pin / Local",
4275
+ viewBox: "0 0 24 24",
4276
+ 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"/>'
4277
+ },
4278
+ euro: {
4279
+ name: "Euro / Pricing",
4280
+ viewBox: "0 0 24 24",
4281
+ 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"/>'
4282
+ },
4283
+ arrowRight: {
4284
+ name: "Arrow Right",
4285
+ viewBox: "0 0 24 24",
4286
+ 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"/>'
4287
+ }
4288
+ };
4289
+ function renderUiIconSvg(iconName, className = "w-5 h-5 inline-block") {
4290
+ const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
4291
+ return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
4292
+ }
4293
+ function resolveFeatureIcon(featureText) {
4294
+ const lower = featureText.toLowerCase();
4295
+ if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
4296
+ return "shield";
4297
+ }
4298
+ if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
4299
+ return "zap";
4300
+ }
4301
+ if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
4302
+ return "sparkles";
4303
+ }
4304
+ if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
4305
+ return "chart";
4306
+ }
4307
+ if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
4308
+ return "calendar";
4309
+ }
4310
+ if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
4311
+ return "users";
4312
+ }
4313
+ if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
4314
+ return "euro";
4315
+ }
4316
+ if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
4317
+ return "mapPin";
4318
+ }
4319
+ if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
4320
+ return "clock";
4321
+ }
4322
+ return "zap";
4323
+ }
4324
+ // src/free-public-tools.ts
4325
+ class FreePublicToolsEngine {
4326
+ static simulateSerpSnippet(input) {
4327
+ const title = (input.title || "").trim();
4328
+ const description = (input.description || "").trim();
4329
+ const url = (input.url || "https://example.com").trim();
4330
+ const kw = (input.targetKeyword || "").toLowerCase().trim();
4331
+ const titlePixelWidth = title.length * 9.5;
4332
+ const maxTitlePixels = 580;
4333
+ const maxDescChars = 158;
4334
+ const isTitleTruncated = titlePixelWidth > maxTitlePixels;
4335
+ const truncatedTitle = isTitleTruncated ? title.slice(0, 56) + "..." : title;
4336
+ const isDescriptionTruncated = description.length > maxDescChars;
4337
+ const truncatedDescription = isDescriptionTruncated ? description.slice(0, 155) + "..." : description;
4338
+ let ctrScore = 50;
4339
+ const recommendations = [];
4340
+ if (title.length >= 40 && title.length <= 60) {
4341
+ ctrScore += 20;
4342
+ } else if (title.length < 30) {
4343
+ recommendations.push("Le titre est trop court (< 30 car.). Ajoutez des précisions pour booster le clic.");
4344
+ } else if (isTitleTruncated) {
4345
+ ctrScore -= 10;
4346
+ recommendations.push("Le titre dépasse 580px et sera tronqué sur Google Desktop.");
4347
+ }
4348
+ if (kw && title.toLowerCase().includes(kw)) {
4349
+ ctrScore += 15;
4350
+ } else if (kw) {
4351
+ recommendations.push(`Le mot-clé principal "${kw}" est absent du titre.`);
4352
+ }
4353
+ if (description.length >= 120 && description.length <= 158) {
4354
+ ctrScore += 15;
4355
+ } else if (description.length < 80) {
4356
+ recommendations.push("La méta-description est trop courte (< 80 car.).");
4357
+ }
4358
+ return {
4359
+ title,
4360
+ truncatedTitle,
4361
+ titlePixelWidth: Math.round(titlePixelWidth),
4362
+ isTitleTruncated,
4363
+ description,
4364
+ truncatedDescription,
4365
+ isDescriptionTruncated,
4366
+ urlPreview: url,
4367
+ estimatedCtrScore: Math.min(Math.max(ctrScore, 10), 100),
4368
+ recommendations
4369
+ };
4370
+ }
4371
+ static calculateRoi(input) {
4372
+ const hours = Math.max(input.hoursSpentPerWeek || 5, 1);
4373
+ const rate = Math.max(input.hourlyRateOrSalary || 35, 1);
4374
+ const team = Math.max(input.teamMembersCount || 1, 1);
4375
+ const softwareCost = input.softwareMonthlyPrice ?? 49;
4376
+ const hoursSavedWeekly = Math.round(hours * 0.75 * team * 10) / 10;
4377
+ const hoursSavedMonthly = Math.round(hoursSavedWeekly * 4.33);
4378
+ const monthlyGrossSavings = hoursSavedMonthly * rate;
4379
+ const monthlyNetSavings = Math.max(monthlyGrossSavings - softwareCost, 0);
4380
+ const annualCostSavings = monthlyNetSavings * 12;
4381
+ const roiPercentage = softwareCost > 0 ? Math.round(monthlyNetSavings / softwareCost * 100) : 1000;
4382
+ const breakEvenDays = monthlyGrossSavings > 0 ? Math.max(Math.round(softwareCost / (monthlyGrossSavings / 30)), 1) : 30;
4383
+ return {
4384
+ hoursSavedWeekly,
4385
+ hoursSavedMonthly,
4386
+ monthlyCostSavings: Math.round(monthlyNetSavings),
4387
+ annualCostSavings: Math.round(annualCostSavings),
4388
+ roiPercentage,
4389
+ breakEvenDays
4390
+ };
4391
+ }
4392
+ static analyzeTextDensity(text, language = "fr") {
4393
+ if (!text || typeof text !== "string") {
4394
+ return { totalWords: 0, characterCount: 0, readingTimeMinutes: 0, topKeywords: [], seoReadabilityScore: 0 };
4395
+ }
4396
+ const clean = text.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, " ");
4397
+ const words = clean.split(/\s+/).filter((w) => w.length > 2);
4398
+ const totalWords = words.length;
4399
+ const characterCount = text.length;
4400
+ const readingTimeMinutes = Math.max(Math.round(totalWords / 200 * 10) / 10, 0.5);
4401
+ const stopWords = new Set(["pour", "dans", "avec", "sur", "les", "des", "une", "que", "qui", "est", "par", "the", "and", "for", "with", "this", "that"]);
4402
+ const frequencyMap = new Map;
4403
+ for (const w of words) {
4404
+ if (!stopWords.has(w)) {
4405
+ frequencyMap.set(w, (frequencyMap.get(w) || 0) + 1);
4406
+ }
4407
+ }
4408
+ const sorted = Array.from(frequencyMap.entries()).sort((a, b) => b[1] - a[1]);
4409
+ const topKeywords = sorted.slice(0, 8).map(([word, count]) => ({
4410
+ word,
4411
+ count,
4412
+ densityPercent: totalWords > 0 ? Math.round(count / totalWords * 1000) / 10 : 0
4413
+ }));
4414
+ let seoReadabilityScore = 70;
4415
+ if (totalWords >= 600)
4416
+ seoReadabilityScore += 20;
4417
+ else if (totalWords < 300)
4418
+ seoReadabilityScore -= 25;
4419
+ return {
4420
+ totalWords,
4421
+ characterCount,
4422
+ readingTimeMinutes,
4423
+ topKeywords,
4424
+ seoReadabilityScore: Math.min(Math.max(seoReadabilityScore, 10), 100)
4425
+ };
4426
+ }
4427
+ static cleanSlugTool(input, lang = "fr") {
4428
+ const clean = cleanSeoSlug(input, { language: lang });
4429
+ return {
4430
+ original: input,
4431
+ cleanSlug: clean,
4432
+ charSavings: Math.max(input.length - clean.length, 0)
4433
+ };
4434
+ }
4435
+ static generateSampleSchema(type, name, url) {
4436
+ if (type === "localBusiness") {
4437
+ return ExtendedSchemaGraphBuilder.buildLocalBusiness({
4438
+ name,
4439
+ url,
4440
+ city: "Paris",
4441
+ country: "France"
4442
+ });
4443
+ }
4444
+ if (type === "faq") {
4445
+ return ExtendedSchemaGraphBuilder.buildFAQPage([
4446
+ { question: `Comment fonctionne ${name} ?`, answer: `${name} automatise vos opérations en quelques clics.` },
4447
+ { question: `Quel est le tarif de ${name} ?`, answer: `Un essai gratuit de 14 jours est disponible sans carte bancaire.` }
4448
+ ]);
4449
+ }
4450
+ return ExtendedSchemaGraphBuilder.buildSoftwareApplication({
4451
+ name,
4452
+ description: `Solution logicielle professionnelle ${name}`,
4453
+ url,
4454
+ priceMonthly: 29
4455
+ });
4456
+ }
4457
+ }
4088
4458
  // src/urlytics-engine.ts
4089
4459
  class UrlyticsEngine {
4090
4460
  static parseUrl(rawUrl) {
@@ -4495,22 +4865,34 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
4495
4865
  4. **\uD83C\uDFA8 Vector Brand & Social Icons (\`renderBrandIconSvg\`):**
4496
4866
  - Zero-dependency official SVG vectors for 30+ brands (Facebook, Instagram, LinkedIn, Shopify, Slack, WhatsApp, Google, GitHub, TikTok, YouTube).
4497
4867
 
4498
- 5. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4868
+ 5. **✨ Standard UI & Feature Icons (\`renderUiIconSvg\`, \`resolveFeatureIcon\`):**
4869
+ - Zero-dependency Lucide-style vector icons (shield, zap, chart, calendar, users, rocket, sparkles, check, lock, clock, phone, mapPin, euro).
4870
+ - Semantic keyword auto-resolver: Automatically maps feature copy to the most relevant icon.
4871
+
4872
+ 6. **\uD83D\uDDBC️ Dynamic OpenGraph (OG) Image Generator (\`OgImageGenerator\`):**
4873
+ - \`renderDynamicOgSvg(opts)\`: Generates high-converting 1200x630 SVG social cards with glassmorphism badges, star ratings, and gradient backdrops.
4874
+ - \`generateOgImageUrl(domain, pageMeta, brandName)\`: Edge-ready query URL for \`app/api/og/route.ts\`.
4875
+
4876
+ 7. **\uD83D\uDEE0️ Free Public Interactive Tools & Lead Magnets (\`FreePublicToolsEngine\`):**
4877
+ - Ahrefs/Semrush-style high-intent public calculators: Google SERP Preview Simulator, Automation ROI Calculator, Keyword Density Analyzer, and Schema.org Generator.
4878
+ - Boosts dwell time, organic backlinks, and visitor-to-customer conversion.
4879
+
4880
+ 8. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4499
4881
  - \`buildAggregateRating\` (Google Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
4500
4882
 
4501
- 6. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4883
+ 9. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4502
4884
  - Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
4503
4885
 
4504
- 7. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4886
+ 10. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4505
4887
  - Combinatorial matrix generation: Products × Modifiers × Locations with match types (\`[exact]\`, \`"phrase"\`, \`+broad\`) and intent classification.
4506
4888
 
4507
- 8. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4889
+ 11. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4508
4890
  - 7 SeedRank-grade disclaimer templates, 90-day staleness verification, and multi-language anti-disparagement guardian.
4509
4891
 
4510
- 9. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4892
+ 12. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4511
4893
  - Synchronizes total programmatic pages, indexed hubs, and AI bot crawl hits (GPTBot, ClaudeBot, PerplexityBot) with the LynxSEO Studio dashboard.
4512
4894
 
4513
- 10. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4895
+ 13. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4514
4896
  - \`cleanSeoSlug(text, { language })\`: Strips accents, stop words across 10+ languages, and non-alphanumeric noise.
4515
4897
  - Outputs Google-validated Schema.org JSON-LD graphs with 0 syntax errors.
4516
4898
  `.trim();