@lynxflow/seo-engine 1.6.1 → 1.6.2

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.d.ts CHANGED
@@ -16,6 +16,8 @@ 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";
19
21
  export * from "./urlytics-engine";
20
22
  export * from "./keyword-permutator";
21
23
  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,
@@ -3338,6 +3342,87 @@ function resolveBuiltInLocations(territories) {
3338
3342
  return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
3339
3343
  }
3340
3344
 
3345
+ // src/og-image-generator.ts
3346
+ class OgImageGenerator {
3347
+ static generateOgImageUrl(domain, pageMeta, brandName) {
3348
+ const cleanDomain = domain.replace(/\/+$/, "");
3349
+ const params = new URLSearchParams({
3350
+ title: pageMeta.h1 || pageMeta.title,
3351
+ badge: pageMeta.matrixFamily.toUpperCase(),
3352
+ desc: pageMeta.description ? pageMeta.description.slice(0, 120) : "",
3353
+ brand: brandName
3354
+ });
3355
+ if (pageMeta.location) {
3356
+ params.set("loc", `${pageMeta.location.name}, ${pageMeta.location.country}`);
3357
+ }
3358
+ return `${cleanDomain}/api/og?${params.toString()}`;
3359
+ }
3360
+ static renderDynamicOgSvg(opts) {
3361
+ const brand = opts.brandName || "LynxSEO";
3362
+ const badge = opts.badgeText || "OFFICIAL";
3363
+ const title = opts.title.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3364
+ const loc = opts.locationName ? `\uD83D\uDCCD ${opts.locationName}` : "⚡ Instant Cloud Setup";
3365
+ const rating = opts.ratingValue ?? 4.9;
3366
+ const reviews = opts.reviewCount ?? 1280;
3367
+ const accent = opts.brandColor || "#6366F1";
3368
+ return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
3369
+ <defs>
3370
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
3371
+ <stop offset="0%" stop-color="#090D16"/>
3372
+ <stop offset="50%" stop-color="#0F172A"/>
3373
+ <stop offset="100%" stop-color="#020617"/>
3374
+ </linearGradient>
3375
+ <linearGradient id="textGrad" x1="0%" y1="0%" x2="100%" y2="0%">
3376
+ <stop offset="0%" stop-color="#FFFFFF"/>
3377
+ <stop offset="100%" stop-color="#CBD5E1"/>
3378
+ </linearGradient>
3379
+ <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
3380
+ <feGaussianBlur stdDeviation="80" result="blur" />
3381
+ </filter>
3382
+ </defs>
3383
+
3384
+ <!-- Background -->
3385
+ <rect width="1200" height="630" fill="url(#bgGrad)"/>
3386
+
3387
+ <!-- Subtle Ambient Glow -->
3388
+ <circle cx="200" cy="150" r="220" fill="${accent}" opacity="0.15" filter="url(#glow)"/>
3389
+ <circle cx="1000" cy="480" r="260" fill="${accent}" opacity="0.12" filter="url(#glow)"/>
3390
+
3391
+ <!-- Top Glassmorphism Badge -->
3392
+ <g transform="translate(80, 80)">
3393
+ <rect width="220" height="42" rx="21" fill="${accent}" fill-opacity="0.15" stroke="${accent}" stroke-opacity="0.4" stroke-width="1.5"/>
3394
+ <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>
3395
+ </g>
3396
+
3397
+ <!-- Location / Scope Pill -->
3398
+ <g transform="translate(320, 80)">
3399
+ <rect width="260" height="42" rx="21" fill="#1E293B" fill-opacity="0.6" stroke="#334155" stroke-width="1"/>
3400
+ <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>
3401
+ </g>
3402
+
3403
+ <!-- Giant Title -->
3404
+ <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">
3405
+ <tspan x="80" dy="0">${title.slice(0, 38)}</tspan>
3406
+ ${title.length > 38 ? `<tspan x="80" dy="68">${title.slice(38, 80)}</tspan>` : ""}
3407
+ </text>
3408
+
3409
+ <!-- Trust Stars & Social Proof -->
3410
+ <g transform="translate(80, 470)">
3411
+ <text x="0" y="32" fill="#FBBF24" font-size="24">★★★★★</text>
3412
+ <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>
3413
+ <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>
3414
+ </g>
3415
+
3416
+ <!-- Footer Brand -->
3417
+ <g transform="translate(80, 540)">
3418
+ <line x1="0" y1="0" x2="1040" y2="0" stroke="#1E293B" stroke-width="1.5"/>
3419
+ <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>
3420
+ <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>
3421
+ </g>
3422
+ </svg>`;
3423
+ }
3424
+ }
3425
+
3341
3426
  // src/matrix-engine.ts
3342
3427
  class PseoMatrixEngine {
3343
3428
  legalEngine;
@@ -3842,6 +3927,11 @@ class PseoMatrixEngine {
3842
3927
  if (data.calculators) {
3843
3928
  allPages.push(...this.generateCalculatorMatrix(domain, data.calculators, options));
3844
3929
  }
3930
+ for (const p of allPages) {
3931
+ if (!p.ogImageUrl) {
3932
+ p.ogImageUrl = OgImageGenerator.generateOgImageUrl(domain, p, options.brandName);
3933
+ }
3934
+ }
3845
3935
  return allPages;
3846
3936
  }
3847
3937
  resolvePage(slugOrPath, domain, data, options) {
@@ -4085,6 +4175,119 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
4085
4175
  return "";
4086
4176
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
4087
4177
  }
4178
+ // src/ui-icons.ts
4179
+ var UI_ICONS = {
4180
+ zap: {
4181
+ name: "Zap / Lightning",
4182
+ viewBox: "0 0 24 24",
4183
+ 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"/>'
4184
+ },
4185
+ shield: {
4186
+ name: "Shield / Security",
4187
+ viewBox: "0 0 24 24",
4188
+ 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"/>'
4189
+ },
4190
+ star: {
4191
+ name: "Star / Rating",
4192
+ viewBox: "0 0 24 24",
4193
+ 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"/>'
4194
+ },
4195
+ chart: {
4196
+ name: "Chart / Analytics",
4197
+ viewBox: "0 0 24 24",
4198
+ 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"/>'
4199
+ },
4200
+ calendar: {
4201
+ name: "Calendar / Scheduling",
4202
+ viewBox: "0 0 24 24",
4203
+ 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"/>'
4204
+ },
4205
+ users: {
4206
+ name: "Users / Team",
4207
+ viewBox: "0 0 24 24",
4208
+ 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"/>'
4209
+ },
4210
+ rocket: {
4211
+ name: "Rocket / Speed",
4212
+ viewBox: "0 0 24 24",
4213
+ 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"/>'
4214
+ },
4215
+ sparkles: {
4216
+ name: "Sparkles / AI",
4217
+ viewBox: "0 0 24 24",
4218
+ 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"/>'
4219
+ },
4220
+ check: {
4221
+ name: "Check / Success",
4222
+ viewBox: "0 0 24 24",
4223
+ svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4224
+ },
4225
+ lock: {
4226
+ name: "Lock / Privacy",
4227
+ viewBox: "0 0 24 24",
4228
+ 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"/>'
4229
+ },
4230
+ clock: {
4231
+ name: "Clock / 24-7",
4232
+ viewBox: "0 0 24 24",
4233
+ 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"/>'
4234
+ },
4235
+ phone: {
4236
+ name: "Phone / Contact",
4237
+ viewBox: "0 0 24 24",
4238
+ 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"/>'
4239
+ },
4240
+ mapPin: {
4241
+ name: "Map Pin / Local",
4242
+ viewBox: "0 0 24 24",
4243
+ 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"/>'
4244
+ },
4245
+ euro: {
4246
+ name: "Euro / Pricing",
4247
+ viewBox: "0 0 24 24",
4248
+ 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"/>'
4249
+ },
4250
+ arrowRight: {
4251
+ name: "Arrow Right",
4252
+ viewBox: "0 0 24 24",
4253
+ 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"/>'
4254
+ }
4255
+ };
4256
+ function renderUiIconSvg(iconName, className = "w-5 h-5 inline-block") {
4257
+ const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
4258
+ return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
4259
+ }
4260
+ function resolveFeatureIcon(featureText) {
4261
+ const lower = featureText.toLowerCase();
4262
+ if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
4263
+ return "shield";
4264
+ }
4265
+ if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
4266
+ return "zap";
4267
+ }
4268
+ if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
4269
+ return "sparkles";
4270
+ }
4271
+ if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
4272
+ return "chart";
4273
+ }
4274
+ if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
4275
+ return "calendar";
4276
+ }
4277
+ if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
4278
+ return "users";
4279
+ }
4280
+ if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
4281
+ return "euro";
4282
+ }
4283
+ if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
4284
+ return "mapPin";
4285
+ }
4286
+ if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
4287
+ return "clock";
4288
+ }
4289
+ return "zap";
4290
+ }
4088
4291
  // src/urlytics-engine.ts
4089
4292
  class UrlyticsEngine {
4090
4293
  static parseUrl(rawUrl) {
@@ -4495,22 +4698,30 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
4495
4698
  4. **\uD83C\uDFA8 Vector Brand & Social Icons (\`renderBrandIconSvg\`):**
4496
4699
  - Zero-dependency official SVG vectors for 30+ brands (Facebook, Instagram, LinkedIn, Shopify, Slack, WhatsApp, Google, GitHub, TikTok, YouTube).
4497
4700
 
4498
- 5. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4701
+ 5. **✨ Standard UI & Feature Icons (\`renderUiIconSvg\`, \`resolveFeatureIcon\`):**
4702
+ - Zero-dependency Lucide-style vector icons (shield, zap, chart, calendar, users, rocket, sparkles, check, lock, clock, phone, mapPin, euro).
4703
+ - Semantic keyword auto-resolver: Automatically maps feature copy to the most relevant icon.
4704
+
4705
+ 6. **\uD83D\uDDBC️ Dynamic OpenGraph (OG) Image Generator (\`OgImageGenerator\`):**
4706
+ - \`renderDynamicOgSvg(opts)\`: Generates high-converting 1200x630 SVG social cards with glassmorphism badges, star ratings, and gradient backdrops.
4707
+ - \`generateOgImageUrl(domain, pageMeta, brandName)\`: Edge-ready query URL for \`app/api/og/route.ts\`.
4708
+
4709
+ 7. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4499
4710
  - \`buildAggregateRating\` (Google Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
4500
4711
 
4501
- 6. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4712
+ 8. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4502
4713
  - Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
4503
4714
 
4504
- 7. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4715
+ 9. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4505
4716
  - Combinatorial matrix generation: Products × Modifiers × Locations with match types (\`[exact]\`, \`"phrase"\`, \`+broad\`) and intent classification.
4506
4717
 
4507
- 8. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4718
+ 10. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4508
4719
  - 7 SeedRank-grade disclaimer templates, 90-day staleness verification, and multi-language anti-disparagement guardian.
4509
4720
 
4510
- 9. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4721
+ 11. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4511
4722
  - Synchronizes total programmatic pages, indexed hubs, and AI bot crawl hits (GPTBot, ClaudeBot, PerplexityBot) with the LynxSEO Studio dashboard.
4512
4723
 
4513
- 10. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4724
+ 12. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4514
4725
  - \`cleanSeoSlug(text, { language })\`: Strips accents, stop words across 10+ languages, and non-alphanumeric noise.
4515
4726
  - Outputs Google-validated Schema.org JSON-LD graphs with 0 syntax errors.
4516
4727
  `.trim();
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;
@@ -3735,6 +3816,11 @@ class PseoMatrixEngine {
3735
3816
  if (data.calculators) {
3736
3817
  allPages.push(...this.generateCalculatorMatrix(domain, data.calculators, options));
3737
3818
  }
3819
+ for (const p of allPages) {
3820
+ if (!p.ogImageUrl) {
3821
+ p.ogImageUrl = OgImageGenerator.generateOgImageUrl(domain, p, options.brandName);
3822
+ }
3823
+ }
3738
3824
  return allPages;
3739
3825
  }
3740
3826
  resolvePage(slugOrPath, domain, data, options) {
@@ -3978,6 +4064,119 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
3978
4064
  return "";
3979
4065
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
3980
4066
  }
4067
+ // src/ui-icons.ts
4068
+ var UI_ICONS = {
4069
+ zap: {
4070
+ name: "Zap / Lightning",
4071
+ viewBox: "0 0 24 24",
4072
+ 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"/>'
4073
+ },
4074
+ shield: {
4075
+ name: "Shield / Security",
4076
+ viewBox: "0 0 24 24",
4077
+ 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"/>'
4078
+ },
4079
+ star: {
4080
+ name: "Star / Rating",
4081
+ viewBox: "0 0 24 24",
4082
+ 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"/>'
4083
+ },
4084
+ chart: {
4085
+ name: "Chart / Analytics",
4086
+ viewBox: "0 0 24 24",
4087
+ 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"/>'
4088
+ },
4089
+ calendar: {
4090
+ name: "Calendar / Scheduling",
4091
+ viewBox: "0 0 24 24",
4092
+ 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"/>'
4093
+ },
4094
+ users: {
4095
+ name: "Users / Team",
4096
+ viewBox: "0 0 24 24",
4097
+ 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"/>'
4098
+ },
4099
+ rocket: {
4100
+ name: "Rocket / Speed",
4101
+ viewBox: "0 0 24 24",
4102
+ 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"/>'
4103
+ },
4104
+ sparkles: {
4105
+ name: "Sparkles / AI",
4106
+ viewBox: "0 0 24 24",
4107
+ 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"/>'
4108
+ },
4109
+ check: {
4110
+ name: "Check / Success",
4111
+ viewBox: "0 0 24 24",
4112
+ svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
4113
+ },
4114
+ lock: {
4115
+ name: "Lock / Privacy",
4116
+ viewBox: "0 0 24 24",
4117
+ 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"/>'
4118
+ },
4119
+ clock: {
4120
+ name: "Clock / 24-7",
4121
+ viewBox: "0 0 24 24",
4122
+ 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"/>'
4123
+ },
4124
+ phone: {
4125
+ name: "Phone / Contact",
4126
+ viewBox: "0 0 24 24",
4127
+ 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"/>'
4128
+ },
4129
+ mapPin: {
4130
+ name: "Map Pin / Local",
4131
+ viewBox: "0 0 24 24",
4132
+ 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"/>'
4133
+ },
4134
+ euro: {
4135
+ name: "Euro / Pricing",
4136
+ viewBox: "0 0 24 24",
4137
+ 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"/>'
4138
+ },
4139
+ arrowRight: {
4140
+ name: "Arrow Right",
4141
+ viewBox: "0 0 24 24",
4142
+ 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"/>'
4143
+ }
4144
+ };
4145
+ function renderUiIconSvg(iconName, className = "w-5 h-5 inline-block") {
4146
+ const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
4147
+ return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
4148
+ }
4149
+ function resolveFeatureIcon(featureText) {
4150
+ const lower = featureText.toLowerCase();
4151
+ if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
4152
+ return "shield";
4153
+ }
4154
+ if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
4155
+ return "zap";
4156
+ }
4157
+ if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
4158
+ return "sparkles";
4159
+ }
4160
+ if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
4161
+ return "chart";
4162
+ }
4163
+ if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
4164
+ return "calendar";
4165
+ }
4166
+ if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
4167
+ return "users";
4168
+ }
4169
+ if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
4170
+ return "euro";
4171
+ }
4172
+ if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
4173
+ return "mapPin";
4174
+ }
4175
+ if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
4176
+ return "clock";
4177
+ }
4178
+ return "zap";
4179
+ }
3981
4180
  // src/urlytics-engine.ts
3982
4181
  class UrlyticsEngine {
3983
4182
  static parseUrl(rawUrl) {
@@ -4388,22 +4587,30 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
4388
4587
  4. **\uD83C\uDFA8 Vector Brand & Social Icons (\`renderBrandIconSvg\`):**
4389
4588
  - Zero-dependency official SVG vectors for 30+ brands (Facebook, Instagram, LinkedIn, Shopify, Slack, WhatsApp, Google, GitHub, TikTok, YouTube).
4390
4589
 
4391
- 5. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4590
+ 5. **✨ Standard UI & Feature Icons (\`renderUiIconSvg\`, \`resolveFeatureIcon\`):**
4591
+ - Zero-dependency Lucide-style vector icons (shield, zap, chart, calendar, users, rocket, sparkles, check, lock, clock, phone, mapPin, euro).
4592
+ - Semantic keyword auto-resolver: Automatically maps feature copy to the most relevant icon.
4593
+
4594
+ 6. **\uD83D\uDDBC️ Dynamic OpenGraph (OG) Image Generator (\`OgImageGenerator\`):**
4595
+ - \`renderDynamicOgSvg(opts)\`: Generates high-converting 1200x630 SVG social cards with glassmorphism badges, star ratings, and gradient backdrops.
4596
+ - \`generateOgImageUrl(domain, pageMeta, brandName)\`: Edge-ready query URL for \`app/api/og/route.ts\`.
4597
+
4598
+ 7. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
4392
4599
  - \`buildAggregateRating\` (Google Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
4393
4600
 
4394
- 6. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4601
+ 8. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
4395
4602
  - Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
4396
4603
 
4397
- 7. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4604
+ 9. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
4398
4605
  - Combinatorial matrix generation: Products × Modifiers × Locations with match types (\`[exact]\`, \`"phrase"\`, \`+broad\`) and intent classification.
4399
4606
 
4400
- 8. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4607
+ 10. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
4401
4608
  - 7 SeedRank-grade disclaimer templates, 90-day staleness verification, and multi-language anti-disparagement guardian.
4402
4609
 
4403
- 9. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4610
+ 11. **\uD83D\uDCCA Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
4404
4611
  - Synchronizes total programmatic pages, indexed hubs, and AI bot crawl hits (GPTBot, ClaudeBot, PerplexityBot) with the LynxSEO Studio dashboard.
4405
4612
 
4406
- 10. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4613
+ 12. **\uD83E\uDDF9 URL Slug Engine & Schema.org Graphs:**
4407
4614
  - \`cleanSeoSlug(text, { language })\`: Strips accents, stop words across 10+ languages, and non-alphanumeric noise.
4408
4615
  - Outputs Google-validated Schema.org JSON-LD graphs with 0 syntax errors.
4409
4616
  `.trim();
@@ -6552,8 +6759,10 @@ var LynxSeo = {
6552
6759
  var src_default = LynxSeo;
6553
6760
  export {
6554
6761
  validateSeoSlug,
6762
+ resolveFeatureIcon,
6555
6763
  resolveBuiltInLocations,
6556
6764
  resolveBrandIcon,
6765
+ renderUiIconSvg,
6557
6766
  renderBrandIconSvg,
6558
6767
  getSeoAgentPrompt,
6559
6768
  src_default as default,
@@ -6561,6 +6770,7 @@ export {
6561
6770
  cleanSeoSlug,
6562
6771
  YoastParityEngine,
6563
6772
  UrlyticsEngine,
6773
+ UI_ICONS,
6564
6774
  TokenQuotaManager,
6565
6775
  TechnicalRulesAuditor,
6566
6776
  TeamRbacEngine,
@@ -6580,6 +6790,7 @@ export {
6580
6790
  RankMathParityEngine,
6581
6791
  PseoMatrixEngine,
6582
6792
  PSEO_AGENT_SYSTEM_PROMPT,
6793
+ OgImageGenerator,
6583
6794
  NGramDensityAnalyzer,
6584
6795
  McpSeoServerHub,
6585
6796
  MasterMarketingEngine,
@@ -110,6 +110,7 @@ export interface GeneratedPageMeta {
110
110
  robots: "index, follow" | "noindex, follow";
111
111
  schemaGraph: SchemaOrgGraph;
112
112
  disclaimerText?: string;
113
+ ogImageUrl?: string;
113
114
  neighboringLinks?: {
114
115
  name: string;
115
116
  url: 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.2",
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,8 @@ 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";
11
13
 
12
14
  describe("Multilingual SEO Slug Engine (10+ Languages)", () => {
13
15
  it("English: Strips stop words and years", () => {
@@ -265,4 +267,43 @@ describe("Audited Reference Engines (Advertools, Santifer, Seonaut)", () => {
265
267
  expect(telemetry.brandName).toBe("Acme");
266
268
  expect(telemetry.servicesCount).toBe(1);
267
269
  });
270
+
271
+ it("UI Icons: Renders standard Lucide-style SVG and resolves semantic feature icons", () => {
272
+ const shieldSvg = renderUiIconSvg("shield");
273
+ expect(shieldSvg).toContain("<svg");
274
+ expect(shieldSvg).toContain("viewBox");
275
+
276
+ expect(resolveFeatureIcon("Bank-grade RGPD Security")).toBe("shield");
277
+ expect(resolveFeatureIcon("Real-time automated sync")).toBe("zap");
278
+ expect(resolveFeatureIcon("Analytics dashboard & KPI tracker")).toBe("chart");
279
+ expect(resolveFeatureIcon("Smart AI content generator")).toBe("sparkles");
280
+ });
281
+
282
+ it("OgImageGenerator: Generates dynamic 1200x630 SVG and Edge URL query", () => {
283
+ const ogSvg = OgImageGenerator.renderDynamicOgSvg({
284
+ brandName: "Acme Cloud",
285
+ title: "Autopost Facebook & Instagram in Paris",
286
+ badgeText: "SOCIAL MEDIA",
287
+ locationName: "Paris, France",
288
+ ratingValue: 4.9,
289
+ reviewCount: 1280,
290
+ });
291
+ expect(ogSvg).toContain("<svg width=\"1200\" height=\"630\"");
292
+ expect(ogSvg).toContain("Autopost Facebook");
293
+ expect(ogSvg).toContain("4.9 / 5");
294
+ expect(ogSvg).toContain("Paris, France");
295
+
296
+ const engine = new PseoMatrixEngine();
297
+ const page = engine.resolvePage(
298
+ ["autopost-facebook", "paris"],
299
+ "https://acme.com",
300
+ {
301
+ services: [{ slug: "autopost-facebook", name: "Autopost Facebook", category: "Social" }],
302
+ },
303
+ { brandName: "Acme", countries: ["france"] },
304
+ );
305
+ expect(page?.ogImageUrl).toBeDefined();
306
+ expect(page?.ogImageUrl).toContain("/api/og?");
307
+ expect(page?.ogImageUrl).toContain("brand=Acme");
308
+ });
268
309
  });
package/src/index.ts CHANGED
@@ -17,6 +17,8 @@ export * from "./matrix-engine";
17
17
  export * from "./built-in-locations";
18
18
  export * from "./i18n-detector";
19
19
  export * from "./brand-icons";
20
+ export * from "./ui-icons";
21
+ export * from "./og-image-generator";
20
22
  export * from "./urlytics-engine";
21
23
  export * from "./keyword-permutator";
22
24
  export * from "./llm-prompt";
package/src/llm-prompt.ts CHANGED
@@ -275,22 +275,30 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
275
275
  4. **🎨 Vector Brand & Social Icons (\`renderBrandIconSvg\`):**
276
276
  - Zero-dependency official SVG vectors for 30+ brands (Facebook, Instagram, LinkedIn, Shopify, Slack, WhatsApp, Google, GitHub, TikTok, YouTube).
277
277
 
278
- 5. **🏛️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
278
+ 5. **✨ Standard UI & Feature Icons (\`renderUiIconSvg\`, \`resolveFeatureIcon\`):**
279
+ - Zero-dependency Lucide-style vector icons (shield, zap, chart, calendar, users, rocket, sparkles, check, lock, clock, phone, mapPin, euro).
280
+ - Semantic keyword auto-resolver: Automatically maps feature copy to the most relevant icon.
281
+
282
+ 6. **🖼️ Dynamic OpenGraph (OG) Image Generator (\`OgImageGenerator\`):**
283
+ - \`renderDynamicOgSvg(opts)\`: Generates high-converting 1200x630 SVG social cards with glassmorphism badges, star ratings, and gradient backdrops.
284
+ - \`generateOgImageUrl(domain, pageMeta, brandName)\`: Edge-ready query URL for \`app/api/og/route.ts\`.
285
+
286
+ 7. **🏛️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
279
287
  - \`buildAggregateRating\` (Google Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
280
288
 
281
- 6. **🔍 URL Decomposition & Analysis (\`UrlyticsEngine\`):**
289
+ 8. **🔍 URL Decomposition & Analysis (\`UrlyticsEngine\`):**
282
290
  - Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
283
291
 
284
- 7. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
292
+ 9. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
285
293
  - Combinatorial matrix generation: Products × Modifiers × Locations with match types (\`[exact]\`, \`"phrase"\`, \`+broad\`) and intent classification.
286
294
 
287
- 8. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
295
+ 10. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
288
296
  - 7 SeedRank-grade disclaimer templates, 90-day staleness verification, and multi-language anti-disparagement guardian.
289
297
 
290
- 9. **📊 Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
298
+ 11. **📊 Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
291
299
  - Synchronizes total programmatic pages, indexed hubs, and AI bot crawl hits (GPTBot, ClaudeBot, PerplexityBot) with the LynxSEO Studio dashboard.
292
300
 
293
- 10. **🧹 URL Slug Engine & Schema.org Graphs:**
301
+ 12. **🧹 URL Slug Engine & Schema.org Graphs:**
294
302
  - \`cleanSeoSlug(text, { language })\`: Strips accents, stop words across 10+ languages, and non-alphanumeric noise.
295
303
  - Outputs Google-validated Schema.org JSON-LD graphs with 0 syntax errors.
296
304
  `.trim();
@@ -14,6 +14,7 @@ import { cleanSeoSlug } from "./slug-engine";
14
14
  import { LegalDisclaimerEngine } from "./legal-disclaimers";
15
15
  import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
16
16
  import { resolveBuiltInLocations } from "./built-in-locations";
17
+ import { OgImageGenerator } from "./og-image-generator";
17
18
 
18
19
  export type MatrixFamily =
19
20
  | "local-geo"
@@ -141,6 +142,7 @@ export interface GeneratedPageMeta {
141
142
  robots: "index, follow" | "noindex, follow";
142
143
  schemaGraph: SchemaOrgGraph;
143
144
  disclaimerText?: string;
145
+ ogImageUrl?: string;
144
146
  neighboringLinks?: { name: string; url: string }[];
145
147
  service?: PseoService;
146
148
  location?: PseoLocation;
@@ -824,6 +826,12 @@ export class PseoMatrixEngine {
824
826
  allPages.push(...this.generateCalculatorMatrix(domain, data.calculators, options));
825
827
  }
826
828
 
829
+ for (const p of allPages) {
830
+ if (!p.ogImageUrl) {
831
+ p.ogImageUrl = OgImageGenerator.generateOgImageUrl(domain, p, options.brandName);
832
+ }
833
+ }
834
+
827
835
  return allPages;
828
836
  }
829
837
 
@@ -0,0 +1,109 @@
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
+
7
+ import { GeneratedPageMeta } from "./matrix-engine";
8
+
9
+ export interface OgImageOptions {
10
+ brandName: string;
11
+ title: string;
12
+ badgeText?: string;
13
+ subtitle?: string;
14
+ locationName?: string;
15
+ ratingValue?: number;
16
+ reviewCount?: number;
17
+ brandColor?: string;
18
+ }
19
+
20
+ export class OgImageGenerator {
21
+ /**
22
+ * Generates a fully formatted URL for dynamic Edge / API OG image generation.
23
+ * e.g. /api/og?title=...&badge=...&city=...
24
+ */
25
+ static generateOgImageUrl(domain: string, pageMeta: GeneratedPageMeta, brandName: string): string {
26
+ const cleanDomain = domain.replace(/\/+$/, "");
27
+ const params = new URLSearchParams({
28
+ title: pageMeta.h1 || pageMeta.title,
29
+ badge: pageMeta.matrixFamily.toUpperCase(),
30
+ desc: pageMeta.description ? pageMeta.description.slice(0, 120) : "",
31
+ brand: brandName,
32
+ });
33
+
34
+ if (pageMeta.location) {
35
+ params.set("loc", `${pageMeta.location.name}, ${pageMeta.location.country}`);
36
+ }
37
+
38
+ return `${cleanDomain}/api/og?${params.toString()}`;
39
+ }
40
+
41
+ /**
42
+ * Generates an ultra-crisp 1200x630 pure SVG image string for social previews.
43
+ */
44
+ static renderDynamicOgSvg(opts: OgImageOptions): string {
45
+ const brand = opts.brandName || "LynxSEO";
46
+ const badge = opts.badgeText || "OFFICIAL";
47
+ const title = opts.title.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
48
+ const loc = opts.locationName ? `📍 ${opts.locationName}` : "⚡ Instant Cloud Setup";
49
+ const rating = opts.ratingValue ?? 4.9;
50
+ const reviews = opts.reviewCount ?? 1280;
51
+ const accent = opts.brandColor || "#6366F1";
52
+
53
+ return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
54
+ <defs>
55
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
56
+ <stop offset="0%" stop-color="#090D16"/>
57
+ <stop offset="50%" stop-color="#0F172A"/>
58
+ <stop offset="100%" stop-color="#020617"/>
59
+ </linearGradient>
60
+ <linearGradient id="textGrad" x1="0%" y1="0%" x2="100%" y2="0%">
61
+ <stop offset="0%" stop-color="#FFFFFF"/>
62
+ <stop offset="100%" stop-color="#CBD5E1"/>
63
+ </linearGradient>
64
+ <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
65
+ <feGaussianBlur stdDeviation="80" result="blur" />
66
+ </filter>
67
+ </defs>
68
+
69
+ <!-- Background -->
70
+ <rect width="1200" height="630" fill="url(#bgGrad)"/>
71
+
72
+ <!-- Subtle Ambient Glow -->
73
+ <circle cx="200" cy="150" r="220" fill="${accent}" opacity="0.15" filter="url(#glow)"/>
74
+ <circle cx="1000" cy="480" r="260" fill="${accent}" opacity="0.12" filter="url(#glow)"/>
75
+
76
+ <!-- Top Glassmorphism Badge -->
77
+ <g transform="translate(80, 80)">
78
+ <rect width="220" height="42" rx="21" fill="${accent}" fill-opacity="0.15" stroke="${accent}" stroke-opacity="0.4" stroke-width="1.5"/>
79
+ <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>
80
+ </g>
81
+
82
+ <!-- Location / Scope Pill -->
83
+ <g transform="translate(320, 80)">
84
+ <rect width="260" height="42" rx="21" fill="#1E293B" fill-opacity="0.6" stroke="#334155" stroke-width="1"/>
85
+ <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>
86
+ </g>
87
+
88
+ <!-- Giant Title -->
89
+ <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">
90
+ <tspan x="80" dy="0">${title.slice(0, 38)}</tspan>
91
+ ${title.length > 38 ? `<tspan x="80" dy="68">${title.slice(38, 80)}</tspan>` : ""}
92
+ </text>
93
+
94
+ <!-- Trust Stars & Social Proof -->
95
+ <g transform="translate(80, 470)">
96
+ <text x="0" y="32" fill="#FBBF24" font-size="24">★★★★★</text>
97
+ <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>
98
+ <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>
99
+ </g>
100
+
101
+ <!-- Footer Brand -->
102
+ <g transform="translate(80, 540)">
103
+ <line x1="0" y1="0" x2="1040" y2="0" stroke="#1E293B" stroke-width="1.5"/>
104
+ <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>
105
+ <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>
106
+ </g>
107
+ </svg>`;
108
+ }
109
+ }
@@ -0,0 +1,134 @@
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
+
7
+ export interface UiIcon {
8
+ name: string;
9
+ viewBox: string;
10
+ svgPath: string;
11
+ }
12
+
13
+ export const UI_ICONS: Record<string, UiIcon> = {
14
+ zap: {
15
+ name: "Zap / Lightning",
16
+ viewBox: "0 0 24 24",
17
+ 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"/>',
18
+ },
19
+ shield: {
20
+ name: "Shield / Security",
21
+ viewBox: "0 0 24 24",
22
+ 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"/>',
23
+ },
24
+ star: {
25
+ name: "Star / Rating",
26
+ viewBox: "0 0 24 24",
27
+ 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"/>',
28
+ },
29
+ chart: {
30
+ name: "Chart / Analytics",
31
+ viewBox: "0 0 24 24",
32
+ 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"/>',
33
+ },
34
+ calendar: {
35
+ name: "Calendar / Scheduling",
36
+ viewBox: "0 0 24 24",
37
+ 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"/>',
38
+ },
39
+ users: {
40
+ name: "Users / Team",
41
+ viewBox: "0 0 24 24",
42
+ 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"/>',
43
+ },
44
+ rocket: {
45
+ name: "Rocket / Speed",
46
+ viewBox: "0 0 24 24",
47
+ 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"/>',
48
+ },
49
+ sparkles: {
50
+ name: "Sparkles / AI",
51
+ viewBox: "0 0 24 24",
52
+ 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"/>',
53
+ },
54
+ check: {
55
+ name: "Check / Success",
56
+ viewBox: "0 0 24 24",
57
+ svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
58
+ },
59
+ lock: {
60
+ name: "Lock / Privacy",
61
+ viewBox: "0 0 24 24",
62
+ 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"/>',
63
+ },
64
+ clock: {
65
+ name: "Clock / 24-7",
66
+ viewBox: "0 0 24 24",
67
+ 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"/>',
68
+ },
69
+ phone: {
70
+ name: "Phone / Contact",
71
+ viewBox: "0 0 24 24",
72
+ 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"/>',
73
+ },
74
+ mapPin: {
75
+ name: "Map Pin / Local",
76
+ viewBox: "0 0 24 24",
77
+ 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"/>',
78
+ },
79
+ euro: {
80
+ name: "Euro / Pricing",
81
+ viewBox: "0 0 24 24",
82
+ 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"/>',
83
+ },
84
+ arrowRight: {
85
+ name: "Arrow Right",
86
+ viewBox: "0 0 24 24",
87
+ 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"/>',
88
+ },
89
+ };
90
+
91
+ /**
92
+ * Returns raw inline SVG markup for a standard UI icon.
93
+ */
94
+ export function renderUiIconSvg(iconName: string, className: string = "w-5 h-5 inline-block"): string {
95
+ const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
96
+ return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
97
+ }
98
+
99
+ /**
100
+ * Semantically resolves the most relevant UI icon based on feature text keywords.
101
+ */
102
+ export function resolveFeatureIcon(featureText: string): string {
103
+ const lower = featureText.toLowerCase();
104
+
105
+ if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
106
+ return "shield";
107
+ }
108
+ if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
109
+ return "zap";
110
+ }
111
+ if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
112
+ return "sparkles";
113
+ }
114
+ if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
115
+ return "chart";
116
+ }
117
+ if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
118
+ return "calendar";
119
+ }
120
+ if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
121
+ return "users";
122
+ }
123
+ if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
124
+ return "euro";
125
+ }
126
+ if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
127
+ return "mapPin";
128
+ }
129
+ if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
130
+ return "clock";
131
+ }
132
+
133
+ return "zap";
134
+ }