@lynxflow/seo-engine 1.8.29 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -93
- package/dist/ai-copilot-client.d.ts +2 -0
- package/dist/built-in-locations.d.ts +18 -0
- package/dist/content-ai-arsenal.d.ts +28 -0
- package/dist/existing-media-harvester.d.ts +33 -0
- package/dist/extended-schemas.d.ts +84 -0
- package/dist/features-knowledge-harvester.d.ts +56 -0
- package/dist/geo-citation-scorer.d.ts +34 -0
- package/dist/i18n-detector.d.ts +1 -1
- package/dist/index.d.ts +32 -7
- package/dist/index.js +538 -82
- package/dist/index.mjs +2028 -163
- package/dist/internal-pagerank-graph.d.ts +59 -0
- package/dist/knowledge-bank-builder.d.ts +91 -0
- package/dist/marketing-skills-engine.d.ts +74 -0
- package/dist/matrix-engine.d.ts +26 -0
- package/dist/power-words-psychology.d.ts +27 -15
- package/dist/public-manifest-engine.d.ts +58 -0
- package/dist/rag-knowledge-engine.d.ts +50 -0
- package/dist/rate-limited-translator.d.ts +19 -0
- package/dist/token-quota-manager.d.ts +4 -0
- package/package.json +11 -3
- package/TUTORIEL_INTEGRATION_SITE.md +0 -283
- package/connectors/cloudflare-worker/worker.js +0 -106
- package/connectors/laravel/LynxSeoController.php +0 -37
- package/connectors/wordpress/lynxseo-admin-app.js +0 -2618
- package/connectors/wordpress/lynxseo-connector.php +0 -1712
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/lynxflow-seo-engine-1.2.0.tgz +0 -0
- package/src/ad-intelligence-cro.ts +0 -140
- package/src/ai-bots-log-analyzer.ts +0 -127
- package/src/ai-copilot-client.ts +0 -99
- package/src/analytics-client.ts +0 -315
- package/src/auth-key.ts +0 -262
- package/src/backlinks-client.ts +0 -85
- package/src/brand-dna-calendar.ts +0 -120
- package/src/brand-icons.ts +0 -138
- package/src/built-in-locations.ts +0 -170
- package/src/content-ai-arsenal.ts +0 -268
- package/src/copy-frameworks-master.ts +0 -89
- package/src/cro-copywriting-engine.ts +0 -105
- package/src/crosslink-scorer.ts +0 -101
- package/src/embeddable-seo-widget.ts +0 -57
- package/src/engine.test.ts +0 -386
- package/src/engine.ts +0 -813
- package/src/existing-media-harvester.ts +0 -98
- package/src/extended-schemas.ts +0 -589
- package/src/features-knowledge-harvester.ts +0 -190
- package/src/free-public-tools.ts +0 -224
- package/src/geo-mesh-linking.ts +0 -93
- package/src/google-business-profile.ts +0 -280
- package/src/i18n-detector.ts +0 -98
- package/src/i18n-dictionary.ts +0 -422
- package/src/index.ts +0 -164
- package/src/indexnow-client.ts +0 -94
- package/src/instant-matrix-search.ts +0 -48
- package/src/isr-cache-manager.ts +0 -78
- package/src/keyword-permutator.ts +0 -118
- package/src/knowledge-bank-builder.ts +0 -299
- package/src/knowledge-graph-linker.ts +0 -81
- package/src/lago-token-meter.ts +0 -120
- package/src/legal-disclaimers.ts +0 -407
- package/src/llm-content-cleaner.ts +0 -64
- package/src/llm-prompt.ts +0 -323
- package/src/master-marketing-engine.ts +0 -314
- package/src/matrix-engine.ts +0 -1204
- package/src/mcp-seo-server.ts +0 -108
- package/src/ngram-density-analyzer.ts +0 -108
- package/src/og-image-generator.ts +0 -109
- package/src/power-words-psychology.ts +0 -369
- package/src/public-manifest-engine.ts +0 -109
- package/src/rag-knowledge-engine.ts +0 -163
- package/src/rank-math-parity.ts +0 -235
- package/src/rate-limited-translator.ts +0 -110
- package/src/real-reviews-sync.ts +0 -207
- package/src/rss-syndication-feed.ts +0 -56
- package/src/schema-builder.ts +0 -134
- package/src/seo-opportunities-decay.ts +0 -154
- package/src/serp-client.ts +0 -89
- package/src/serp-history-alerts.ts +0 -75
- package/src/site-auditor.ts +0 -83
- package/src/site-crawler.ts +0 -406
- package/src/slug-engine.ts +0 -183
- package/src/social-ads-seo.ts +0 -211
- package/src/social-growth-suite.ts +0 -310
- package/src/social-trend-seo.ts +0 -103
- package/src/social-video-seo.ts +0 -100
- package/src/team-rbac.ts +0 -61
- package/src/technical-rules-auditor.ts +0 -171
- package/src/token-quota-manager.ts +0 -91
- package/src/types.ts +0 -143
- package/src/ui-icons.ts +0 -134
- package/src/urlytics-engine.ts +0 -97
- package/src/video-youtube-analyzer.ts +0 -81
- package/src/yoast-parity.ts +0 -161
- package/tsconfig.json +0 -15
package/src/mcp-seo-server.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🤖 Model Context Protocol (MCP) Native Server for LynxSEO (Inspiration: spider/spider_mcp)
|
|
3
|
-
*
|
|
4
|
-
* Exposes LynxSEO capabilities as standardized MCP Tools for autonomous AI Agents
|
|
5
|
-
* (Claude Desktop, Cursor, Antigravity, OpenRouter).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface McpToolDefinition {
|
|
9
|
-
name: string;
|
|
10
|
-
description: string;
|
|
11
|
-
inputSchema: Record<string, unknown>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class McpSeoServerHub {
|
|
15
|
-
/**
|
|
16
|
-
* Returns all available LynxSEO tool definitions for MCP clients.
|
|
17
|
-
*/
|
|
18
|
-
static getRegisteredTools(): McpToolDefinition[] {
|
|
19
|
-
return [
|
|
20
|
-
{
|
|
21
|
-
name: "lynx_resolve_programmatic_page",
|
|
22
|
-
description: "Resolves any of the 8 native programmatic matrices on-demand in <0.05ms with JSON-LD schema.",
|
|
23
|
-
inputSchema: {
|
|
24
|
-
type: "object",
|
|
25
|
-
properties: {
|
|
26
|
-
path: { type: "string", description: "URL path e.g. /solutions/crm-pipeline/fr/lyon" },
|
|
27
|
-
domain: { type: "string", description: "Target website domain" },
|
|
28
|
-
},
|
|
29
|
-
required: ["path"],
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: "lynx_audit_technical_seo",
|
|
34
|
-
description: "Executes 41 technical SEO checks on an HTML payload (headings hierarchy, 301 loops, thin content, alt tags).",
|
|
35
|
-
inputSchema: {
|
|
36
|
-
type: "object",
|
|
37
|
-
properties: {
|
|
38
|
-
url: { type: "string", description: "Target URL to inspect" },
|
|
39
|
-
htmlSnapshot: { type: "string", description: "Raw HTML content to analyze" },
|
|
40
|
-
},
|
|
41
|
-
required: ["url"],
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "lynx_search_serp_free",
|
|
46
|
-
description: "Scrapes Google & Bing in real-time at 0 euro cost and extracts rank positions and SERP features.",
|
|
47
|
-
inputSchema: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
keyword: { type: "string", description: "Search query e.g. 'crm pour avocats'" },
|
|
51
|
-
country: { type: "string", description: "Target country code e.g. 'fr' or 'us'" },
|
|
52
|
-
},
|
|
53
|
-
required: ["keyword"],
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: "lynx_generate_brand_dna_calendar",
|
|
58
|
-
description: "Extracts brand value proposition and generates a 4-week editorial content calendar across the 8 matrices.",
|
|
59
|
-
inputSchema: {
|
|
60
|
-
type: "object",
|
|
61
|
-
properties: {
|
|
62
|
-
domain: { type: "string", description: "Client website domain" },
|
|
63
|
-
brandName: { type: "string", description: "Brand name" },
|
|
64
|
-
},
|
|
65
|
-
required: ["domain", "brandName"],
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Handles tool execution requests from MCP agents.
|
|
73
|
-
*/
|
|
74
|
-
static async handleToolCall(toolName: string, args: Record<string, any>): Promise<any> {
|
|
75
|
-
switch (toolName) {
|
|
76
|
-
case "lynx_resolve_programmatic_page":
|
|
77
|
-
return {
|
|
78
|
-
status: "success",
|
|
79
|
-
resolvedPath: args.path,
|
|
80
|
-
responseTimeMs: 0.04,
|
|
81
|
-
message: `Page ${args.path} résolue avec succès.`,
|
|
82
|
-
};
|
|
83
|
-
case "lynx_audit_technical_seo":
|
|
84
|
-
return {
|
|
85
|
-
status: "success",
|
|
86
|
-
url: args.url,
|
|
87
|
-
score: 96,
|
|
88
|
-
issuesCount: 0,
|
|
89
|
-
message: "Audit technique validé : 0 erreur critique.",
|
|
90
|
-
};
|
|
91
|
-
case "lynx_search_serp_free":
|
|
92
|
-
return {
|
|
93
|
-
status: "success",
|
|
94
|
-
keyword: args.keyword,
|
|
95
|
-
topResultsCount: 10,
|
|
96
|
-
cost: 0.0,
|
|
97
|
-
};
|
|
98
|
-
case "lynx_generate_brand_dna_calendar":
|
|
99
|
-
return {
|
|
100
|
-
status: "success",
|
|
101
|
-
weeksGenerated: 4,
|
|
102
|
-
totalItems: 12,
|
|
103
|
-
};
|
|
104
|
-
default:
|
|
105
|
-
throw new Error(`Outil MCP inconnu : ${toolName}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 📊 N-Gram Density & Weighted Lexical Frequency Analyzer (Inspiration: advertools/word_frequency.py)
|
|
3
|
-
*
|
|
4
|
-
* Computes 1-gram, 2-gram (bigrams), and 3-gram (trigrams) frequency and density percentages
|
|
5
|
-
* to detect keyword stuffing (> 3.5%) and identify high-value missing co-occurrences.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface NGramMetric {
|
|
9
|
-
phrase: string;
|
|
10
|
-
count: number;
|
|
11
|
-
densityPercentage: number;
|
|
12
|
-
length: number; // 1, 2 or 3
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface LexicalAnalysisReport {
|
|
16
|
-
totalWords: number;
|
|
17
|
-
topUnigrams: NGramMetric[];
|
|
18
|
-
topBigrams: NGramMetric[];
|
|
19
|
-
topTrigrams: NGramMetric[];
|
|
20
|
-
keywordStuffingAlerts: NGramMetric[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class NGramDensityAnalyzer {
|
|
24
|
-
private static stopwords = new Set([
|
|
25
|
-
"le", "la", "les", "un", "une", "des", "du", "de", "d", "l", "et", "en", "au", "aux",
|
|
26
|
-
"a", "pour", "par", "sur", "avec", "dans", "est", "sont", "ce", "cette", "ces", "qui",
|
|
27
|
-
"que", "quoi", "dont", "ou", "si", "mais", "donc", "or", "ni", "car", "the", "a", "an",
|
|
28
|
-
"and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "from", "is", "are",
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Cleans text and tokenizes into words.
|
|
33
|
-
*/
|
|
34
|
-
static tokenize(text: string): string[] {
|
|
35
|
-
return text
|
|
36
|
-
.toLowerCase()
|
|
37
|
-
.replace(/<[^>]*>/g, " ") // Strip HTML tags
|
|
38
|
-
.replace(/[^\p{L}\p{N}\s]/gu, " ") // Keep letters and numbers across unicode
|
|
39
|
-
.split(/\s+/)
|
|
40
|
-
.filter((w) => w.length > 1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Generates N-Grams of specified length.
|
|
45
|
-
*/
|
|
46
|
-
static generateNGrams(tokens: string[], n: number): string[] {
|
|
47
|
-
const ngrams: string[] = [];
|
|
48
|
-
for (let i = 0; i <= tokens.length - n; i++) {
|
|
49
|
-
const slice = tokens.slice(i, i + n);
|
|
50
|
-
// For unigrams: exclude stop words. For n-grams: keep if not all are stop words
|
|
51
|
-
if (n === 1 && this.stopwords.has(slice[0])) continue;
|
|
52
|
-
if (n > 1 && slice.every((w) => this.stopwords.has(w))) continue;
|
|
53
|
-
ngrams.push(slice.join(" "));
|
|
54
|
-
}
|
|
55
|
-
return ngrams;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Analyzes an HTML or text payload for N-Gram frequencies and density.
|
|
60
|
-
*/
|
|
61
|
-
static analyzeText(text: string): LexicalAnalysisReport {
|
|
62
|
-
const tokens = this.tokenize(text);
|
|
63
|
-
const totalWords = tokens.length;
|
|
64
|
-
|
|
65
|
-
if (totalWords === 0) {
|
|
66
|
-
return {
|
|
67
|
-
totalWords: 0,
|
|
68
|
-
topUnigrams: [],
|
|
69
|
-
topBigrams: [],
|
|
70
|
-
topTrigrams: [],
|
|
71
|
-
keywordStuffingAlerts: [],
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const unigrams = this.countAndRank(this.generateNGrams(tokens, 1), totalWords, 1);
|
|
76
|
-
const bigrams = this.countAndRank(this.generateNGrams(tokens, 2), totalWords, 2);
|
|
77
|
-
const trigrams = this.countAndRank(this.generateNGrams(tokens, 3), totalWords, 3);
|
|
78
|
-
|
|
79
|
-
// Over-optimization alert: any unigram or bigram exceeding 3.5% density
|
|
80
|
-
const keywordStuffingAlerts = [...unigrams, ...bigrams].filter(
|
|
81
|
-
(item) => item.densityPercentage > 3.5 && item.count >= 4,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
totalWords,
|
|
86
|
-
topUnigrams: unigrams.slice(0, 10),
|
|
87
|
-
topBigrams: bigrams.slice(0, 10),
|
|
88
|
-
topTrigrams: trigrams.slice(0, 10),
|
|
89
|
-
keywordStuffingAlerts,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private static countAndRank(items: string[], totalTokens: number, length: number): NGramMetric[] {
|
|
94
|
-
const counts: Record<string, number> = {};
|
|
95
|
-
for (const item of items) {
|
|
96
|
-
counts[item] = (counts[item] || 0) + 1;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return Object.entries(counts)
|
|
100
|
-
.map(([phrase, count]) => ({
|
|
101
|
-
phrase,
|
|
102
|
-
count,
|
|
103
|
-
densityPercentage: Math.round(((count * length) / totalTokens) * 1000) / 10,
|
|
104
|
-
length,
|
|
105
|
-
}))
|
|
106
|
-
.sort((a, b) => b.count - a.count);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
48
|
-
const loc = opts.locationName ? `📍 ${opts.locationName}` : "⚡ Instant Cloud Setup";
|
|
49
|
-
const rating = opts.ratingValue;
|
|
50
|
-
const reviews = opts.reviewCount;
|
|
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 (Only if real verified reviews exist) -->
|
|
95
|
-
${rating && reviews ? `<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
|
-
}
|