@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,224 @@
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
+
9
+ import { cleanSeoSlug } from "./slug-engine";
10
+ import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
11
+
12
+ export interface SerpSimulatorResult {
13
+ title: string;
14
+ truncatedTitle: string;
15
+ titlePixelWidth: number;
16
+ isTitleTruncated: boolean;
17
+ description: string;
18
+ truncatedDescription: string;
19
+ isDescriptionTruncated: boolean;
20
+ urlPreview: string;
21
+ estimatedCtrScore: number; // 0 to 100
22
+ recommendations: string[];
23
+ }
24
+
25
+ export interface RoiCalculatorResult {
26
+ hoursSavedWeekly: number;
27
+ hoursSavedMonthly: number;
28
+ monthlyCostSavings: number;
29
+ annualCostSavings: number;
30
+ roiPercentage: number;
31
+ breakEvenDays: number;
32
+ }
33
+
34
+ export interface DensityAnalysisResult {
35
+ totalWords: number;
36
+ characterCount: number;
37
+ readingTimeMinutes: number;
38
+ topKeywords: { word: string; count: number; densityPercent: number }[];
39
+ seoReadabilityScore: number; // 0 to 100
40
+ }
41
+
42
+ export class FreePublicToolsEngine {
43
+ /**
44
+ * 1. Google SERP Preview & CTR Optimizer Simulator (Ahrefs / Moz Parity)
45
+ */
46
+ static simulateSerpSnippet(input: {
47
+ title: string;
48
+ description: string;
49
+ url: string;
50
+ targetKeyword?: string;
51
+ }): SerpSimulatorResult {
52
+ const title = (input.title || "").trim();
53
+ const description = (input.description || "").trim();
54
+ const url = (input.url || "https://example.com").trim();
55
+ const kw = (input.targetKeyword || "").toLowerCase().trim();
56
+
57
+ // Average pixel width estimation (approx 10px per char on Arial 20px)
58
+ const titlePixelWidth = title.length * 9.5;
59
+ const maxTitlePixels = 580; // Google Desktop breakpoint
60
+ const maxDescChars = 158;
61
+
62
+ const isTitleTruncated = titlePixelWidth > maxTitlePixels;
63
+ const truncatedTitle = isTitleTruncated ? title.slice(0, 56) + "..." : title;
64
+
65
+ const isDescriptionTruncated = description.length > maxDescChars;
66
+ const truncatedDescription = isDescriptionTruncated ? description.slice(0, 155) + "..." : description;
67
+
68
+ let ctrScore = 50;
69
+ const recommendations: string[] = [];
70
+
71
+ // Title checks
72
+ if (title.length >= 40 && title.length <= 60) {
73
+ ctrScore += 20;
74
+ } else if (title.length < 30) {
75
+ recommendations.push("Le titre est trop court (< 30 car.). Ajoutez des précisions pour booster le clic.");
76
+ } else if (isTitleTruncated) {
77
+ ctrScore -= 10;
78
+ recommendations.push("Le titre dépasse 580px et sera tronqué sur Google Desktop.");
79
+ }
80
+
81
+ // Keyword presence
82
+ if (kw && title.toLowerCase().includes(kw)) {
83
+ ctrScore += 15;
84
+ } else if (kw) {
85
+ recommendations.push(`Le mot-clé principal "${kw}" est absent du titre.`);
86
+ }
87
+
88
+ // Description checks
89
+ if (description.length >= 120 && description.length <= 158) {
90
+ ctrScore += 15;
91
+ } else if (description.length < 80) {
92
+ recommendations.push("La méta-description est trop courte (< 80 car.).");
93
+ }
94
+
95
+ return {
96
+ title,
97
+ truncatedTitle,
98
+ titlePixelWidth: Math.round(titlePixelWidth),
99
+ isTitleTruncated,
100
+ description,
101
+ truncatedDescription,
102
+ isDescriptionTruncated,
103
+ urlPreview: url,
104
+ estimatedCtrScore: Math.min(Math.max(ctrScore, 10), 100),
105
+ recommendations,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * 2. Free ROI & Time-Saved Automation Calculator
111
+ */
112
+ static calculateRoi(input: {
113
+ hoursSpentPerWeek: number;
114
+ hourlyRateOrSalary: number;
115
+ teamMembersCount?: number;
116
+ softwareMonthlyPrice?: number;
117
+ }): RoiCalculatorResult {
118
+ const hours = Math.max(input.hoursSpentPerWeek || 5, 1);
119
+ const rate = Math.max(input.hourlyRateOrSalary || 35, 1);
120
+ const team = Math.max(input.teamMembersCount || 1, 1);
121
+ const softwareCost = input.softwareMonthlyPrice ?? 49;
122
+
123
+ // Assuming automation saves ~75% of manual operational time
124
+ const hoursSavedWeekly = Math.round(hours * 0.75 * team * 10) / 10;
125
+ const hoursSavedMonthly = Math.round(hoursSavedWeekly * 4.33);
126
+
127
+ const monthlyGrossSavings = hoursSavedMonthly * rate;
128
+ const monthlyNetSavings = Math.max(monthlyGrossSavings - softwareCost, 0);
129
+ const annualCostSavings = monthlyNetSavings * 12;
130
+
131
+ const roiPercentage = softwareCost > 0 ? Math.round((monthlyNetSavings / softwareCost) * 100) : 1000;
132
+ const breakEvenDays = monthlyGrossSavings > 0 ? Math.max(Math.round((softwareCost / (monthlyGrossSavings / 30))), 1) : 30;
133
+
134
+ return {
135
+ hoursSavedWeekly,
136
+ hoursSavedMonthly,
137
+ monthlyCostSavings: Math.round(monthlyNetSavings),
138
+ annualCostSavings: Math.round(annualCostSavings),
139
+ roiPercentage,
140
+ breakEvenDays,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * 3. Instant Keyword Density & Readability Analyzer
146
+ */
147
+ static analyzeTextDensity(text: string, language: string = "fr"): DensityAnalysisResult {
148
+ if (!text || typeof text !== "string") {
149
+ return { totalWords: 0, characterCount: 0, readingTimeMinutes: 0, topKeywords: [], seoReadabilityScore: 0 };
150
+ }
151
+
152
+ const clean = text.toLowerCase().replace(/[^a-z0-9à-ÿ\s]/g, " ");
153
+ const words = clean.split(/\s+/).filter((w) => w.length > 2);
154
+ const totalWords = words.length;
155
+ const characterCount = text.length;
156
+ const readingTimeMinutes = Math.max(Math.round((totalWords / 200) * 10) / 10, 0.5);
157
+
158
+ const stopWords = new Set(["pour", "dans", "avec", "sur", "les", "des", "une", "que", "qui", "est", "par", "the", "and", "for", "with", "this", "that"]);
159
+ const frequencyMap = new Map<string, number>();
160
+
161
+ for (const w of words) {
162
+ if (!stopWords.has(w)) {
163
+ frequencyMap.set(w, (frequencyMap.get(w) || 0) + 1);
164
+ }
165
+ }
166
+
167
+ const sorted = Array.from(frequencyMap.entries()).sort((a, b) => b[1] - a[1]);
168
+ const topKeywords = sorted.slice(0, 8).map(([word, count]) => ({
169
+ word,
170
+ count,
171
+ densityPercent: totalWords > 0 ? Math.round((count / totalWords) * 1000) / 10 : 0,
172
+ }));
173
+
174
+ let seoReadabilityScore = 70;
175
+ if (totalWords >= 600) seoReadabilityScore += 20;
176
+ else if (totalWords < 300) seoReadabilityScore -= 25;
177
+
178
+ return {
179
+ totalWords,
180
+ characterCount,
181
+ readingTimeMinutes,
182
+ topKeywords,
183
+ seoReadabilityScore: Math.min(Math.max(seoReadabilityScore, 10), 100),
184
+ };
185
+ }
186
+
187
+ /**
188
+ * 4. Free SEO Slug Cleanser Helper
189
+ */
190
+ static cleanSlugTool(input: string, lang: string = "fr"): { original: string; cleanSlug: string; charSavings: number } {
191
+ const clean = cleanSeoSlug(input, { language: lang });
192
+ return {
193
+ original: input,
194
+ cleanSlug: clean,
195
+ charSavings: Math.max(input.length - clean.length, 0),
196
+ };
197
+ }
198
+
199
+ /**
200
+ * 5. Free Schema.org Generator Helper
201
+ */
202
+ static generateSampleSchema(type: "localBusiness" | "softwareApp" | "faq", name: string, url: string): Record<string, unknown> {
203
+ if (type === "localBusiness") {
204
+ return ExtendedSchemaGraphBuilder.buildLocalBusiness({
205
+ name,
206
+ url,
207
+ city: "Paris",
208
+ country: "France",
209
+ });
210
+ }
211
+ if (type === "faq") {
212
+ return ExtendedSchemaGraphBuilder.buildFAQPage([
213
+ { question: `Comment fonctionne ${name} ?`, answer: `${name} automatise vos opérations en quelques clics.` },
214
+ { question: `Quel est le tarif de ${name} ?`, answer: `Un essai gratuit de 14 jours est disponible sans carte bancaire.` },
215
+ ]);
216
+ }
217
+ return ExtendedSchemaGraphBuilder.buildSoftwareApplication({
218
+ name,
219
+ description: `Solution logicielle professionnelle ${name}`,
220
+ url,
221
+ priceMonthly: 29,
222
+ });
223
+ }
224
+ }
package/src/index.ts CHANGED
@@ -17,6 +17,9 @@ 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";
22
+ export * from "./free-public-tools";
20
23
  export * from "./urlytics-engine";
21
24
  export * from "./keyword-permutator";
22
25
  export * from "./llm-prompt";
package/src/llm-prompt.ts CHANGED
@@ -275,22 +275,34 @@ 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. **🛠️ Free Public Interactive Tools & Lead Magnets (\`FreePublicToolsEngine\`):**
287
+ - Ahrefs/Semrush-style high-intent public calculators: Google SERP Preview Simulator, Automation ROI Calculator, Keyword Density Analyzer, and Schema.org Generator.
288
+ - Boosts dwell time, organic backlinks, and visitor-to-customer conversion.
289
+
290
+ 8. **🏛️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
279
291
  - \`buildAggregateRating\` (Google Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
280
292
 
281
- 6. **🔍 URL Decomposition & Analysis (\`UrlyticsEngine\`):**
293
+ 9. **🔍 URL Decomposition & Analysis (\`UrlyticsEngine\`):**
282
294
  - Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
283
295
 
284
- 7. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
296
+ 10. **⚡ SEM Keyword Combinator (\`KeywordPermutatorEngine\`):**
285
297
  - Combinatorial matrix generation: Products × Modifiers × Locations with match types (\`[exact]\`, \`"phrase"\`, \`+broad\`) and intent classification.
286
298
 
287
- 8. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
299
+ 11. **⚖️ Legal & Editorial Compliance (\`LegalDisclaimerEngine\`):**
288
300
  - 7 SeedRank-grade disclaimer templates, 90-day staleness verification, and multi-language anti-disparagement guardian.
289
301
 
290
- 9. **📊 Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
302
+ 12. **📊 Telemetry & Cloud Metering (\`matrixEngine.getTelemetryPayload\` & \`LynxAnalyticsClient\`):**
291
303
  - Synchronizes total programmatic pages, indexed hubs, and AI bot crawl hits (GPTBot, ClaudeBot, PerplexityBot) with the LynxSEO Studio dashboard.
292
304
 
293
- 10. **🧹 URL Slug Engine & Schema.org Graphs:**
305
+ 13. **🧹 URL Slug Engine & Schema.org Graphs:**
294
306
  - \`cleanSeoSlug(text, { language })\`: Strips accents, stop words across 10+ languages, and non-alphanumeric noise.
295
307
  - Outputs Google-validated Schema.org JSON-LD graphs with 0 syntax errors.
296
308
  `.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"
@@ -124,6 +125,7 @@ export interface PseoCalculator {
124
125
  title: string;
125
126
  formulaDescription: string;
126
127
  savingsMetric: string;
128
+ inputs?: string[];
127
129
  }
128
130
 
129
131
  export interface SchemaOrgGraph {
@@ -141,6 +143,7 @@ export interface GeneratedPageMeta {
141
143
  robots: "index, follow" | "noindex, follow";
142
144
  schemaGraph: SchemaOrgGraph;
143
145
  disclaimerText?: string;
146
+ ogImageUrl?: string;
144
147
  neighboringLinks?: { name: string; url: string }[];
145
148
  service?: PseoService;
146
149
  location?: PseoLocation;
@@ -160,6 +163,7 @@ export interface MatrixOptions {
160
163
  countries?: string[] | string; // e.g. ["france", "spain"] or "europe" or "international"
161
164
  territories?: string[] | string; // Alias for countries
162
165
  cleanDirectRoutes?: boolean; // When true (default), eliminates parasite words (/solutions/, etc.) for direct /{service}/{city}
166
+ enableFreeTools?: boolean; // When true (default), auto-generates 5 high-converting interactive tools like Ahrefs
163
167
  minPopulationToIndex?: number;
164
168
  defaultCurrency?: string;
165
169
  defaultCurrencySymbol?: string;
@@ -822,6 +826,44 @@ export class PseoMatrixEngine {
822
826
  }
823
827
  if (data.calculators) {
824
828
  allPages.push(...this.generateCalculatorMatrix(domain, data.calculators, options));
829
+ } else if (options.enableFreeTools !== false) {
830
+ const defaultCalculators: PseoCalculator[] = [
831
+ {
832
+ slug: "serp-preview-simulator",
833
+ title: "Google SERP Snippet Preview & CTR Simulator",
834
+ formulaDescription: "Simulate desktop and mobile Google search appearance and optimize CTR",
835
+ savingsMetric: "Search Click-Through Rate",
836
+ inputs: ["pageTitle", "metaDescription", "targetKeyword"],
837
+ },
838
+ {
839
+ slug: "roi-calculator",
840
+ title: "Automation ROI & Time-Saved Calculator",
841
+ formulaDescription: "Calculate weekly operational hours recovered and monthly budget saved",
842
+ savingsMetric: "Monthly Net Financial Savings",
843
+ inputs: ["hoursSpentWeekly", "hourlyRate", "teamMembers"],
844
+ },
845
+ {
846
+ slug: "seo-slug-generator",
847
+ title: "SEO URL Slug Cleaner & Optimizer",
848
+ formulaDescription: "Strip stop words, dates, and noise characters for high-CTR clean URLs",
849
+ savingsMetric: "URL Cleanliness & Readability",
850
+ inputs: ["rawTitle", "language"],
851
+ },
852
+ {
853
+ slug: "keyword-density-checker",
854
+ title: "Live Keyword Density & Readability Analyzer",
855
+ formulaDescription: "Analyze n-gram frequency, reading time, and content depth",
856
+ savingsMetric: "Content Optimization Score",
857
+ inputs: ["articleText"],
858
+ },
859
+ ];
860
+ allPages.push(...this.generateCalculatorMatrix(domain, defaultCalculators, options));
861
+ }
862
+
863
+ for (const p of allPages) {
864
+ if (!p.ogImageUrl) {
865
+ p.ogImageUrl = OgImageGenerator.generateOgImageUrl(domain, p, options.brandName);
866
+ }
825
867
  }
826
868
 
827
869
  return allPages;
@@ -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
+ }