@geenius/seo 0.1.0 → 0.3.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.
Files changed (126) hide show
  1. package/package.json +16 -3
  2. package/packages/convex/dist/index.d.ts +56 -0
  3. package/packages/convex/dist/index.js +133 -0
  4. package/packages/convex/dist/index.js.map +1 -0
  5. package/packages/react/README.md +1 -1
  6. package/packages/react/dist/index.d.ts +156 -0
  7. package/packages/react/dist/index.js +567 -0
  8. package/packages/react/dist/index.js.map +1 -0
  9. package/packages/react-css/README.md +1 -1
  10. package/packages/react-css/dist/index.cjs +571 -0
  11. package/packages/react-css/dist/index.cjs.map +1 -0
  12. package/packages/react-css/{src/seo.css → dist/index.css} +7 -153
  13. package/packages/react-css/dist/index.css.map +1 -0
  14. package/packages/react-css/dist/index.d.cts +53 -0
  15. package/packages/react-css/dist/index.d.ts +53 -0
  16. package/packages/react-css/dist/index.js +539 -0
  17. package/packages/react-css/dist/index.js.map +1 -0
  18. package/packages/shared/README.md +1 -1
  19. package/packages/shared/dist/index.d.ts +262 -0
  20. package/packages/shared/dist/index.js +381 -0
  21. package/packages/shared/dist/index.js.map +1 -0
  22. package/packages/solidjs/README.md +1 -1
  23. package/packages/solidjs/dist/index.d.ts +133 -0
  24. package/packages/solidjs/dist/index.js +416 -0
  25. package/packages/solidjs/dist/index.js.map +1 -0
  26. package/packages/solidjs-css/README.md +1 -1
  27. package/packages/solidjs-css/dist/index.cjs +399 -0
  28. package/packages/solidjs-css/dist/index.cjs.map +1 -0
  29. package/packages/solidjs-css/{src/seo.css → dist/index.css} +7 -153
  30. package/packages/solidjs-css/dist/index.css.map +1 -0
  31. package/packages/solidjs-css/dist/index.d.cts +53 -0
  32. package/packages/solidjs-css/dist/index.d.ts +53 -0
  33. package/packages/solidjs-css/dist/index.js +367 -0
  34. package/packages/solidjs-css/dist/index.js.map +1 -0
  35. package/.changeset/config.json +0 -11
  36. package/.github/CODEOWNERS +0 -1
  37. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  38. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  39. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  40. package/.github/dependabot.yml +0 -11
  41. package/.github/workflows/ci.yml +0 -23
  42. package/.github/workflows/release.yml +0 -29
  43. package/.nvmrc +0 -1
  44. package/.project/ACCOUNT.yaml +0 -4
  45. package/.project/IDEAS.yaml +0 -7
  46. package/.project/PROJECT.yaml +0 -11
  47. package/.project/ROADMAP.yaml +0 -15
  48. package/CODE_OF_CONDUCT.md +0 -16
  49. package/CONTRIBUTING.md +0 -26
  50. package/SECURITY.md +0 -15
  51. package/SUPPORT.md +0 -8
  52. package/packages/convex/package.json +0 -42
  53. package/packages/convex/src/functions.ts +0 -5
  54. package/packages/convex/src/mutations.ts +0 -83
  55. package/packages/convex/src/queries.ts +0 -57
  56. package/packages/convex/src/schema.ts +0 -23
  57. package/packages/convex/tsconfig.json +0 -19
  58. package/packages/convex/tsup.config.ts +0 -18
  59. package/packages/react/package.json +0 -49
  60. package/packages/react/src/components/ArticleJsonLd.tsx +0 -42
  61. package/packages/react/src/components/BreadcrumbsJsonLd.tsx +0 -24
  62. package/packages/react/src/components/MetaEditor.tsx +0 -147
  63. package/packages/react/src/components/SEOHead.tsx +0 -107
  64. package/packages/react/src/components/SEOPreview.tsx +0 -42
  65. package/packages/react/src/components/SEOScoreCard.tsx +0 -51
  66. package/packages/react/src/components/SitemapViewer.tsx +0 -36
  67. package/packages/react/src/components/index.ts +0 -7
  68. package/packages/react/src/hooks/index.ts +0 -4
  69. package/packages/react/src/hooks/useSEO.ts +0 -27
  70. package/packages/react/src/hooks/useSEOAdmin.ts +0 -42
  71. package/packages/react/src/hooks/useSEOScore.ts +0 -7
  72. package/packages/react/src/hooks/useSitemap.ts +0 -8
  73. package/packages/react/src/index.ts +0 -51
  74. package/packages/react/src/index.tsx +0 -11
  75. package/packages/react/src/pages/SEOAdminPage.tsx +0 -101
  76. package/packages/react/src/pages/SEOAnalyticsPage.tsx +0 -96
  77. package/packages/react/src/pages/index.ts +0 -2
  78. package/packages/react/tsconfig.json +0 -19
  79. package/packages/react/tsup.config.ts +0 -12
  80. package/packages/react-css/package.json +0 -36
  81. package/packages/react-css/src/components/ArticleJsonLd.tsx +0 -42
  82. package/packages/react-css/src/components/BreadcrumbsJsonLd.tsx +0 -24
  83. package/packages/react-css/src/components/MetaEditor.tsx +0 -147
  84. package/packages/react-css/src/components/SEOHead.tsx +0 -95
  85. package/packages/react-css/src/components/SEOPreview.tsx +0 -42
  86. package/packages/react-css/src/components/SEOScoreCard.tsx +0 -42
  87. package/packages/react-css/src/components/SitemapViewer.tsx +0 -36
  88. package/packages/react-css/src/components/index.ts +0 -7
  89. package/packages/react-css/src/index.ts +0 -9
  90. package/packages/react-css/src/pages/SEOAdminPage.tsx +0 -88
  91. package/packages/react-css/src/pages/SEOAnalyticsPage.tsx +0 -82
  92. package/packages/react-css/src/pages/index.ts +0 -2
  93. package/packages/react-css/tsup.config.ts +0 -2
  94. package/packages/shared/package.json +0 -42
  95. package/packages/shared/src/__tests__/seo.test.ts +0 -70
  96. package/packages/shared/src/config.ts +0 -297
  97. package/packages/shared/src/index.ts +0 -207
  98. package/packages/shared/tsconfig.json +0 -18
  99. package/packages/shared/tsup.config.ts +0 -11
  100. package/packages/shared/vitest.config.ts +0 -4
  101. package/packages/solidjs/package.json +0 -45
  102. package/packages/solidjs/src/components/ArticleJsonLd.tsx +0 -35
  103. package/packages/solidjs/src/components/BreadcrumbsJsonLd.tsx +0 -24
  104. package/packages/solidjs/src/components/MetaEditor.tsx +0 -155
  105. package/packages/solidjs/src/components/SEOHead.tsx +0 -109
  106. package/packages/solidjs/src/components/SEOPreview.tsx +0 -42
  107. package/packages/solidjs/src/components/SEOScoreCard.tsx +0 -57
  108. package/packages/solidjs/src/components/SitemapViewer.tsx +0 -44
  109. package/packages/solidjs/src/components/index.ts +0 -7
  110. package/packages/solidjs/src/index.ts +0 -11
  111. package/packages/solidjs/src/pages/SEOAdminPage.tsx +0 -104
  112. package/packages/solidjs/src/pages/SEOAnalyticsPage.tsx +0 -102
  113. package/packages/solidjs/src/pages/index.ts +0 -2
  114. package/packages/solidjs/src/primitives/index.ts +0 -4
  115. package/packages/solidjs/src/primitives/useSEO.ts +0 -27
  116. package/packages/solidjs/src/primitives/useSEOAdmin.ts +0 -42
  117. package/packages/solidjs/src/primitives/useSEOScore.ts +0 -7
  118. package/packages/solidjs/src/primitives/useSitemap.ts +0 -8
  119. package/packages/solidjs/tsconfig.json +0 -20
  120. package/packages/solidjs/tsup.config.ts +0 -12
  121. package/packages/solidjs-css/package.json +0 -35
  122. package/packages/solidjs-css/src/index.ts +0 -5
  123. package/packages/solidjs-css/src/primitives/index.ts +0 -1
  124. package/packages/solidjs-css/tsup.config.ts +0 -2
  125. package/pnpm-workspace.yaml +0 -2
  126. package/tsconfig.json +0 -23
@@ -0,0 +1,381 @@
1
+ // src/config.ts
2
+ var SEOConfigBuilder = class {
3
+ config = {
4
+ siteName: "",
5
+ siteUrl: ""
6
+ };
7
+ /**
8
+ * Sets the site name
9
+ */
10
+ withSiteName(name) {
11
+ this.config.siteName = name;
12
+ return this;
13
+ }
14
+ /**
15
+ * Sets the site URL
16
+ */
17
+ withSiteUrl(url) {
18
+ this.config.siteUrl = url;
19
+ return this;
20
+ }
21
+ /**
22
+ * Sets default OG image
23
+ */
24
+ withDefaultImage(url) {
25
+ this.config.defaultImage = url;
26
+ return this;
27
+ }
28
+ /**
29
+ * Sets Twitter handle
30
+ */
31
+ withTwitterHandle(handle) {
32
+ this.config.twitterHandle = handle;
33
+ return this;
34
+ }
35
+ /**
36
+ * Sets title template (e.g., '%s — My Site')
37
+ */
38
+ withTitleTemplate(template) {
39
+ this.config.titleTemplate = template;
40
+ return this;
41
+ }
42
+ /**
43
+ * Sets default locale
44
+ */
45
+ withLocale(locale) {
46
+ this.config.locale = locale;
47
+ return this;
48
+ }
49
+ /**
50
+ * Sets Google Analytics ID
51
+ */
52
+ withGoogleAnalyticsId(id) {
53
+ this.config.googleAnalyticsId = id;
54
+ return this;
55
+ }
56
+ /**
57
+ * Sets Google Tag Manager ID
58
+ */
59
+ withGoogleTagManagerId(id) {
60
+ this.config.googleTagManagerId = id;
61
+ return this;
62
+ }
63
+ /**
64
+ * Builds the configuration
65
+ */
66
+ build() {
67
+ if (!this.config.siteName || !this.config.siteUrl) {
68
+ throw new Error("SEO config requires siteName and siteUrl");
69
+ }
70
+ return this.config;
71
+ }
72
+ };
73
+ function createSEOConfig() {
74
+ return new SEOConfigBuilder();
75
+ }
76
+ function configureSEO(config) {
77
+ return {
78
+ siteName: config.siteName,
79
+ siteUrl: config.siteUrl,
80
+ titleTemplate: config.titleTemplate,
81
+ defaultImage: config.defaultImage,
82
+ twitterHandle: config.twitterHandle,
83
+ locale: config.locale ?? "en_US",
84
+ googleAnalyticsId: config.googleAnalyticsId,
85
+ googleTagManagerId: config.googleTagManagerId
86
+ };
87
+ }
88
+ var PageMetaBuilder = class {
89
+ constructor(config, title, description) {
90
+ this.config = config;
91
+ this.meta.title = title;
92
+ this.meta.description = description;
93
+ this.meta.og = {
94
+ title,
95
+ description,
96
+ type: "website",
97
+ url: config.siteUrl,
98
+ siteName: config.siteName,
99
+ image: config.defaultImage
100
+ };
101
+ this.meta.twitter = {
102
+ card: "summary_large_image",
103
+ title,
104
+ description,
105
+ image: config.defaultImage,
106
+ creator: config.twitterHandle
107
+ };
108
+ }
109
+ meta = {
110
+ og: {},
111
+ twitter: {}
112
+ };
113
+ /**
114
+ * Adds keywords
115
+ */
116
+ withKeywords(keywords) {
117
+ this.meta.keywords = keywords;
118
+ return this;
119
+ }
120
+ /**
121
+ * Sets canonical URL
122
+ */
123
+ withCanonical(url) {
124
+ this.meta.canonical = url;
125
+ return this;
126
+ }
127
+ /**
128
+ * Sets OG image
129
+ */
130
+ withImage(url, alt) {
131
+ if (this.meta.og) {
132
+ this.meta.og.image = url;
133
+ if (alt) this.meta.og.imageAlt = alt;
134
+ }
135
+ if (this.meta.twitter) {
136
+ this.meta.twitter.image = url;
137
+ }
138
+ return this;
139
+ }
140
+ /**
141
+ * Sets OG type
142
+ */
143
+ withType(type) {
144
+ if (this.meta.og) {
145
+ this.meta.og.type = type;
146
+ }
147
+ return this;
148
+ }
149
+ /**
150
+ * Adds JSON-LD schema
151
+ */
152
+ withJsonLd(schema) {
153
+ if (!this.meta.jsonLd) {
154
+ this.meta.jsonLd = [];
155
+ }
156
+ this.meta.jsonLd.push(schema);
157
+ return this;
158
+ }
159
+ /**
160
+ * Sets robots meta
161
+ */
162
+ withRobots(robots) {
163
+ this.meta.robots = robots;
164
+ return this;
165
+ }
166
+ /**
167
+ * Adds alternate language links
168
+ */
169
+ withAlternates(alternates) {
170
+ this.meta.alternates = alternates;
171
+ return this;
172
+ }
173
+ /**
174
+ * Builds the meta tags
175
+ */
176
+ build() {
177
+ return {
178
+ title: this.meta.title || "",
179
+ description: this.meta.description || "",
180
+ keywords: this.meta.keywords || [],
181
+ canonical: this.meta.canonical,
182
+ og: this.meta.og || { type: "website", title: "", description: "", url: "" },
183
+ twitter: this.meta.twitter || { card: "summary", title: "", description: "" },
184
+ robots: this.meta.robots,
185
+ alternates: this.meta.alternates,
186
+ jsonLd: this.meta.jsonLd
187
+ };
188
+ }
189
+ };
190
+ function createPageMeta(config, title, description) {
191
+ return new PageMetaBuilder(config, title, description);
192
+ }
193
+ function validateSEOMeta(meta) {
194
+ const errors = [];
195
+ if (!meta.title || meta.title.length === 0) {
196
+ errors.push("Title is required");
197
+ }
198
+ if (meta.title && meta.title.length > 60) {
199
+ errors.push("Title should be under 60 characters");
200
+ }
201
+ if (!meta.description || meta.description.length === 0) {
202
+ errors.push("Description is required");
203
+ }
204
+ if (meta.description && meta.description.length > 160) {
205
+ errors.push("Description should be under 160 characters");
206
+ }
207
+ if (!meta.og?.image && !meta.twitter?.image) {
208
+ errors.push("At least one image (OG or Twitter) is recommended");
209
+ }
210
+ if (!meta.keywords || meta.keywords.length === 0) {
211
+ errors.push("At least one keyword is recommended");
212
+ }
213
+ return {
214
+ valid: errors.length === 0,
215
+ errors
216
+ };
217
+ }
218
+ var seoPresets = {
219
+ /**
220
+ * Blog article preset
221
+ */
222
+ article: (_config) => ({
223
+ og: { type: "article", title: "", description: "", url: "" },
224
+ robots: "index, follow"
225
+ }),
226
+ /**
227
+ * E-commerce product preset
228
+ */
229
+ product: (_config) => ({
230
+ og: { type: "product", title: "", description: "", url: "" },
231
+ robots: "index, follow"
232
+ }),
233
+ /**
234
+ * Service/company page preset
235
+ */
236
+ business: (_config) => ({
237
+ robots: "index, follow"
238
+ }),
239
+ /**
240
+ * Private/unlisted page preset
241
+ */
242
+ private: () => ({
243
+ robots: "noindex, nofollow"
244
+ })
245
+ };
246
+
247
+ // src/index.ts
248
+ function buildTitle(pageTitle, siteName, sep = " | ") {
249
+ return `${pageTitle}${sep}${siteName}`;
250
+ }
251
+ function applyTitleTemplate(pageTitle, template) {
252
+ if (!template) return pageTitle;
253
+ return template.replace("%s", pageTitle);
254
+ }
255
+ function truncateDescription(desc, max = 160) {
256
+ if (desc.length <= max) return desc;
257
+ return desc.slice(0, max - 3) + "...";
258
+ }
259
+ function articleSchema(data) {
260
+ return {
261
+ "@context": "https://schema.org",
262
+ "@type": "Article",
263
+ ...data
264
+ };
265
+ }
266
+ function productSchema(data) {
267
+ return {
268
+ "@context": "https://schema.org",
269
+ "@type": "Product",
270
+ name: data.name,
271
+ description: data.description,
272
+ url: data.url,
273
+ image: data.image,
274
+ offers: {
275
+ "@type": "Offer",
276
+ price: data.price,
277
+ priceCurrency: data.currency
278
+ }
279
+ };
280
+ }
281
+ function orgSchema(data) {
282
+ return {
283
+ "@context": "https://schema.org",
284
+ "@type": "Organization",
285
+ ...data
286
+ };
287
+ }
288
+ function breadcrumbSchema(items) {
289
+ return {
290
+ "@context": "https://schema.org",
291
+ "@type": "BreadcrumbList",
292
+ itemListElement: items.map((item, i) => ({
293
+ "@type": "ListItem",
294
+ position: i + 1,
295
+ name: item.name,
296
+ item: item.url
297
+ }))
298
+ };
299
+ }
300
+ function faqSchema(items) {
301
+ return {
302
+ "@context": "https://schema.org",
303
+ "@type": "FAQPage",
304
+ mainEntity: items.map((q) => ({
305
+ "@type": "Question",
306
+ name: q.question,
307
+ acceptedAnswer: {
308
+ "@type": "Answer",
309
+ text: q.answer
310
+ }
311
+ }))
312
+ };
313
+ }
314
+ function webApplicationSchema(data) {
315
+ const schema = {
316
+ "@context": "https://schema.org",
317
+ "@type": "WebApplication",
318
+ name: data.name,
319
+ url: data.url,
320
+ description: data.description
321
+ };
322
+ if (data.applicationCategory) schema.applicationCategory = data.applicationCategory;
323
+ if (data.operatingSystem) schema.operatingSystem = data.operatingSystem;
324
+ if (data.offers) {
325
+ schema.offers = {
326
+ "@type": "Offer",
327
+ price: data.offers.price,
328
+ priceCurrency: data.offers.currency
329
+ };
330
+ }
331
+ return schema;
332
+ }
333
+ function buildCanonical(path, baseUrl) {
334
+ return `${baseUrl.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
335
+ }
336
+ function buildAlternates(locales, baseUrl, path) {
337
+ return Object.fromEntries(locales.map((l) => [l, `${baseUrl}/${l}${path}`]));
338
+ }
339
+ function generateRobotsTxt(config) {
340
+ const lines = ["User-agent: *"];
341
+ config.allow.forEach((p) => lines.push(`Allow: ${p}`));
342
+ config.disallow.forEach((p) => lines.push(`Disallow: ${p}`));
343
+ if (config.sitemap) lines.push(`Sitemap: ${config.sitemap}`);
344
+ return lines.join("\n");
345
+ }
346
+ function generateSitemapXml(entries) {
347
+ const urls = entries.map(
348
+ (e) => ` <url>
349
+ <loc>${e.url}</loc>${e.lastmod ? `
350
+ <lastmod>${e.lastmod}</lastmod>` : ""}${e.changefreq ? `
351
+ <changefreq>${e.changefreq}</changefreq>` : ""}${e.priority !== void 0 ? `
352
+ <priority>${e.priority}</priority>` : ""}
353
+ </url>`
354
+ ).join("\n");
355
+ return `<?xml version="1.0" encoding="UTF-8"?>
356
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
357
+ ${urls}
358
+ </urlset>`;
359
+ }
360
+ function estimateReadingTime(content) {
361
+ return Math.ceil(content.split(/\s+/).length / 200);
362
+ }
363
+ function calcSEOScore(meta) {
364
+ const issues = [];
365
+ if (!meta.title || meta.title.length < 10) issues.push("Title too short (< 10 chars)");
366
+ if (meta.title && meta.title.length > 60) issues.push("Title too long (> 60 chars)");
367
+ if (!meta.description || meta.description.length < 50)
368
+ issues.push("Description too short (< 50 chars)");
369
+ if (meta.description && meta.description.length > 160)
370
+ issues.push("Description too long (> 160 chars)");
371
+ if (!meta.keywords || meta.keywords.length === 0) issues.push("No keywords specified");
372
+ if (!meta.canonical) issues.push("No canonical URL");
373
+ if (!meta.og.image) issues.push("No OG image");
374
+ if (!meta.twitter.image) issues.push("No Twitter image");
375
+ const score = Math.max(0, 100 - issues.length * 12);
376
+ return { score, issues };
377
+ }
378
+
379
+ export { PageMetaBuilder, SEOConfigBuilder, applyTitleTemplate, articleSchema, breadcrumbSchema, buildAlternates, buildCanonical, buildTitle, calcSEOScore, configureSEO, createPageMeta, createSEOConfig, estimateReadingTime, faqSchema, generateRobotsTxt, generateSitemapXml, orgSchema, productSchema, seoPresets, truncateDescription, validateSEOMeta, webApplicationSchema };
380
+ //# sourceMappingURL=index.js.map
381
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/index.ts"],"names":[],"mappings":";AAkBO,IAAM,mBAAN,MAAuB;AAAA,EACpB,MAAA,GAAoB;AAAA,IAC1B,QAAA,EAAU,EAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACX;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,IAAA,EAAoB;AAC/B,IAAA,IAAA,CAAK,OAAO,QAAA,GAAW,IAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,GAAA,EAAmB;AAC7B,IAAA,IAAA,CAAK,OAAO,OAAA,GAAU,GAAA;AACtB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,GAAA,EAAmB;AAClC,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,GAAA;AAC3B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,MAAA,EAAsB;AACtC,IAAA,IAAA,CAAK,OAAO,aAAA,GAAgB,MAAA;AAC5B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,QAAA,EAAwB;AACxC,IAAA,IAAA,CAAK,OAAO,aAAA,GAAgB,QAAA;AAC5B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAA,EAAsB;AAC/B,IAAA,IAAA,CAAK,OAAO,MAAA,GAAS,MAAA;AACrB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsB,EAAA,EAAkB;AACtC,IAAA,IAAA,CAAK,OAAO,iBAAA,GAAoB,EAAA;AAChC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB,EAAA,EAAkB;AACvC,IAAA,IAAA,CAAK,OAAO,kBAAA,GAAqB,EAAA;AACjC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAmB;AACjB,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,IAAA,CAAK,OAAO,OAAA,EAAS;AACjD,MAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,IAC5D;AACA,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF;AAKO,SAAS,eAAA,GAAoC;AAClD,EAAA,OAAO,IAAI,gBAAA,EAAiB;AAC9B;AAeO,SAAS,aAAa,MAAA,EAA+E;AAC1G,EAAA,OAAO;AAAA,IACL,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,SAAS,MAAA,CAAO,OAAA;AAAA,IAChB,eAAe,MAAA,CAAO,aAAA;AAAA,IACtB,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,eAAe,MAAA,CAAO,aAAA;AAAA,IACtB,MAAA,EAAQ,OAAO,MAAA,IAAU,OAAA;AAAA,IACzB,mBAAmB,MAAA,CAAO,iBAAA;AAAA,IAC1B,oBAAoB,MAAA,CAAO;AAAA,GAC7B;AACF;AAeO,IAAM,kBAAN,MAAsB;AAAA,EAM3B,WAAA,CAAoB,MAAA,EAAmB,KAAA,EAAe,WAAA,EAAqB;AAAvD,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAClB,IAAA,IAAA,CAAK,KAAK,KAAA,GAAQ,KAAA;AAClB,IAAA,IAAA,CAAK,KAAK,WAAA,GAAc,WAAA;AACxB,IAAA,IAAA,CAAK,KAAK,EAAA,GAAK;AAAA,MACb,KAAA;AAAA,MACA,WAAA;AAAA,MACA,IAAA,EAAM,SAAA;AAAA,MACN,KAAK,MAAA,CAAO,OAAA;AAAA,MACZ,UAAU,MAAA,CAAO,QAAA;AAAA,MACjB,OAAO,MAAA,CAAO;AAAA,KAChB;AACA,IAAA,IAAA,CAAK,KAAK,OAAA,GAAU;AAAA,MAClB,IAAA,EAAM,qBAAA;AAAA,MACN,KAAA;AAAA,MACA,WAAA;AAAA,MACA,OAAO,MAAA,CAAO,YAAA;AAAA,MACd,SAAS,MAAA,CAAO;AAAA,KAClB;AAAA,EACF;AAAA,EAvBQ,IAAA,GAAyB;AAAA,IAC/B,IAAI,EAAC;AAAA,IACL,SAAS;AAAC,GACZ;AAAA;AAAA;AAAA;AAAA,EAyBA,aAAa,QAAA,EAA0B;AACrC,IAAA,IAAA,CAAK,KAAK,QAAA,GAAW,QAAA;AACrB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,GAAA,EAAmB;AAC/B,IAAA,IAAA,CAAK,KAAK,SAAA,GAAY,GAAA;AACtB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,CAAU,KAAa,GAAA,EAAoB;AACzC,IAAA,IAAI,IAAA,CAAK,KAAK,EAAA,EAAI;AAChB,MAAA,IAAA,CAAK,IAAA,CAAK,GAAG,KAAA,GAAQ,GAAA;AACrB,MAAA,IAAI,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,QAAA,GAAW,GAAA;AAAA,IACnC;AACA,IAAA,IAAI,IAAA,CAAK,KAAK,OAAA,EAAS;AACrB,MAAA,IAAA,CAAK,IAAA,CAAK,QAAQ,KAAA,GAAQ,GAAA;AAAA,IAC5B;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAA,EAA+C;AACtD,IAAA,IAAI,IAAA,CAAK,KAAK,EAAA,EAAI;AAChB,MAAA,IAAA,CAAK,IAAA,CAAK,GAAG,IAAA,GAAO,IAAA;AAAA,IACtB;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAA,EAAuC;AAChD,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,MAAA,EAAQ;AACrB,MAAA,IAAA,CAAK,IAAA,CAAK,SAAS,EAAC;AAAA,IACtB;AACA,IAAA,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA;AAC5B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAA,EAAsB;AAC/B,IAAA,IAAA,CAAK,KAAK,MAAA,GAAS,MAAA;AACnB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAAA,EAA0C;AACvD,IAAA,IAAA,CAAK,KAAK,UAAA,GAAa,UAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAiB;AACf,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,IAAA,CAAK,IAAA,CAAK,KAAA,IAAS,EAAA;AAAA,MAC1B,WAAA,EAAa,IAAA,CAAK,IAAA,CAAK,WAAA,IAAe,EAAA;AAAA,MACtC,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,QAAA,IAAY,EAAC;AAAA,MACjC,SAAA,EAAW,KAAK,IAAA,CAAK,SAAA;AAAA,MACrB,EAAA,EAAI,IAAA,CAAK,IAAA,CAAK,EAAA,IAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,EAAA,EAAI,WAAA,EAAa,EAAA,EAAI,GAAA,EAAK,EAAA,EAAG;AAAA,MAC5E,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,OAAA,IAAY,EAAE,MAAM,SAAA,EAAW,KAAA,EAAO,EAAA,EAAI,WAAA,EAAa,EAAA,EAAG;AAAA,MAC7E,MAAA,EAAQ,KAAK,IAAA,CAAK,MAAA;AAAA,MAClB,UAAA,EAAY,KAAK,IAAA,CAAK,UAAA;AAAA,MACtB,MAAA,EAAQ,KAAK,IAAA,CAAK;AAAA,KACpB;AAAA,EACF;AACF;AAKO,SAAS,cAAA,CAAe,MAAA,EAAmB,KAAA,EAAe,WAAA,EAAsC;AACrG,EAAA,OAAO,IAAI,eAAA,CAAgB,MAAA,EAAQ,KAAA,EAAO,WAAW,CAAA;AACvD;AAKO,SAAS,gBAAgB,IAAA,EAAqD;AACnF,EAAA,MAAM,SAAmB,EAAC;AAE1B,EAAA,IAAI,CAAC,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA,EAAG;AAC1C,IAAA,MAAA,CAAO,KAAK,mBAAmB,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,KAAA,CAAM,SAAS,EAAA,EAAI;AACxC,IAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAAA,EACnD;AAEA,EAAA,IAAI,CAAC,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,WAAA,CAAY,WAAW,CAAA,EAAG;AACtD,IAAA,MAAA,CAAO,KAAK,yBAAyB,CAAA;AAAA,EACvC;AACA,EAAA,IAAI,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,WAAA,CAAY,SAAS,GAAA,EAAK;AACrD,IAAA,MAAA,CAAO,KAAK,4CAA4C,CAAA;AAAA,EAC1D;AAEA,EAAA,IAAI,CAAC,IAAA,CAAK,EAAA,EAAI,SAAS,CAAC,IAAA,CAAK,SAAS,KAAA,EAAO;AAC3C,IAAA,MAAA,CAAO,KAAK,mDAAmD,CAAA;AAAA,EACjE;AAEA,EAAA,IAAI,CAAC,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,WAAW,CAAA,EAAG;AAChD,IAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAAA,EACnD;AAEA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAO,MAAA,KAAW,CAAA;AAAA,IACzB;AAAA,GACF;AACF;AAKO,IAAM,UAAA,GAAa;AAAA;AAAA;AAAA;AAAA,EAIxB,OAAA,EAAS,CAAC,OAAA,MAA0C;AAAA,IAClD,EAAA,EAAI,EAAE,IAAA,EAAM,SAAA,EAAW,OAAO,EAAA,EAAI,WAAA,EAAa,EAAA,EAAI,GAAA,EAAK,EAAA,EAAG;AAAA,IAC3D,MAAA,EAAQ;AAAA,GACV,CAAA;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,EAAS,CAAC,OAAA,MAA0C;AAAA,IAClD,EAAA,EAAI,EAAE,IAAA,EAAM,SAAA,EAAW,OAAO,EAAA,EAAI,WAAA,EAAa,EAAA,EAAI,GAAA,EAAK,EAAA,EAAG;AAAA,IAC3D,MAAA,EAAQ;AAAA,GACV,CAAA;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,EAAU,CAAC,OAAA,MAA0C;AAAA,IACnD,MAAA,EAAQ;AAAA,GACV,CAAA;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAyB;AAAA,IAChC,MAAA,EAAQ;AAAA,GACV;AACF;;;ACrQO,SAAS,UAAA,CAAW,SAAA,EAAmB,QAAA,EAAkB,GAAA,GAAM,KAAA,EAAe;AACnF,EAAA,OAAO,CAAA,EAAG,SAAS,CAAA,EAAG,GAAG,GAAG,QAAQ,CAAA,CAAA;AACtC;AAEO,SAAS,kBAAA,CAAmB,WAAmB,QAAA,EAA2B;AAC/E,EAAA,IAAI,CAAC,UAAU,OAAO,SAAA;AACtB,EAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,IAAA,EAAM,SAAS,CAAA;AACzC;AAEO,SAAS,mBAAA,CAAoB,IAAA,EAAc,GAAA,GAAM,GAAA,EAAa;AACnE,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,GAAA,EAAK,OAAO,IAAA;AAC/B,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAA,GAAM,CAAC,CAAA,GAAI,KAAA;AAClC;AAIO,SAAS,cAAc,IAAA,EAOF;AAC1B,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,oBAAA;AAAA,IACZ,OAAA,EAAS,SAAA;AAAA,IACT,GAAG;AAAA,GACL;AACF;AAEO,SAAS,cAAc,IAAA,EAOF;AAC1B,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,oBAAA;AAAA,IACZ,OAAA,EAAS,SAAA;AAAA,IACT,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,KAAK,IAAA,CAAK,GAAA;AAAA,IACV,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,MAAA,EAAQ;AAAA,MACN,OAAA,EAAS,OAAA;AAAA,MACT,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,eAAe,IAAA,CAAK;AAAA;AACtB,GACF;AACF;AAEO,SAAS,UAAU,IAAA,EAKE;AAC1B,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,oBAAA;AAAA,IACZ,OAAA,EAAS,cAAA;AAAA,IACT,GAAG;AAAA,GACL;AACF;AAEO,SAAS,iBAAiB,KAAA,EAGH;AAC5B,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,oBAAA;AAAA,IACZ,OAAA,EAAS,gBAAA;AAAA,IACT,eAAA,EAAiB,KAAA,CAAM,GAAA,CAAI,CAAC,MAAM,CAAA,MAAO;AAAA,MACvC,OAAA,EAAS,UAAA;AAAA,MACT,UAAU,CAAA,GAAI,CAAA;AAAA,MACd,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,MAAM,IAAA,CAAK;AAAA,KACb,CAAE;AAAA,GACJ;AACF;AAEO,SAAS,UAAU,KAAA,EAGI;AAC5B,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,oBAAA;AAAA,IACZ,OAAA,EAAS,SAAA;AAAA,IACT,UAAA,EAAY,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MAC5B,OAAA,EAAS,UAAA;AAAA,MACT,MAAM,CAAA,CAAE,QAAA;AAAA,MACR,cAAA,EAAgB;AAAA,QACd,OAAA,EAAS,QAAA;AAAA,QACT,MAAM,CAAA,CAAE;AAAA;AACV,KACF,CAAE;AAAA,GACJ;AACF;AAEO,SAAS,qBAAqB,IAAA,EAOT;AAC1B,EAAA,MAAM,MAAA,GAAkC;AAAA,IACtC,UAAA,EAAY,oBAAA;AAAA,IACZ,OAAA,EAAS,gBAAA;AAAA,IACT,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,KAAK,IAAA,CAAK,GAAA;AAAA,IACV,aAAa,IAAA,CAAK;AAAA,GACpB;AACA,EAAA,IAAI,IAAA,CAAK,mBAAA,EAAqB,MAAA,CAAO,mBAAA,GAAsB,IAAA,CAAK,mBAAA;AAChE,EAAA,IAAI,IAAA,CAAK,eAAA,EAAiB,MAAA,CAAO,eAAA,GAAkB,IAAA,CAAK,eAAA;AACxD,EAAA,IAAI,KAAK,MAAA,EAAQ;AACf,IAAA,MAAA,CAAO,MAAA,GAAS;AAAA,MACd,OAAA,EAAS,OAAA;AAAA,MACT,KAAA,EAAO,KAAK,MAAA,CAAO,KAAA;AAAA,MACnB,aAAA,EAAe,KAAK,MAAA,CAAO;AAAA,KAC7B;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,cAAA,CAAe,MAAc,OAAA,EAAyB;AACpE,EAAA,OAAO,CAAA,EAAG,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,CAAA;AACjE;AAEO,SAAS,eAAA,CACd,OAAA,EACA,OAAA,EACA,IAAA,EACwB;AACxB,EAAA,OAAO,OAAO,WAAA,CAAY,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAM,CAAC,CAAA,EAAG,CAAA,EAAG,OAAO,IAAI,CAAC,CAAA,EAAG,IAAI,CAAA,CAAE,CAAC,CAAC,CAAA;AAC7E;AAEO,SAAS,kBAAkB,MAAA,EAA2B;AAC3D,EAAA,MAAM,KAAA,GAAQ,CAAC,eAAe,CAAA;AAC9B,EAAA,MAAA,CAAO,KAAA,CAAM,QAAQ,CAAC,CAAA,KAAM,MAAM,IAAA,CAAK,CAAA,OAAA,EAAU,CAAC,CAAA,CAAE,CAAC,CAAA;AACrD,EAAA,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAC,CAAA,KAAM,MAAM,IAAA,CAAK,CAAA,UAAA,EAAa,CAAC,CAAA,CAAE,CAAC,CAAA;AAC3D,EAAA,IAAI,OAAO,OAAA,EAAS,KAAA,CAAM,KAAK,CAAA,SAAA,EAAY,MAAA,CAAO,OAAO,CAAA,CAAE,CAAA;AAC3D,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB;AAEO,SAAS,mBAAmB,OAAA,EAAiC;AAClE,EAAA,MAAM,OAAO,OAAA,CACV,GAAA;AAAA,IACC,CAAC,CAAA,KACC,CAAA;AAAA,SAAA,EAAqB,CAAA,CAAE,GAAG,CAAA,MAAA,EAAS,CAAA,CAAE,OAAA,GAAU;AAAA,aAAA,EAAkB,EAAE,OAAO,CAAA,UAAA,CAAA,GAAe,EAAE,CAAA,EAAG,EAAE,UAAA,GAAa;AAAA,gBAAA,EAAqB,EAAE,UAAU,CAAA,aAAA,CAAA,GAAkB,EAAE,CAAA,EAAG,CAAA,CAAE,aAAa,MAAA,GAAY;AAAA,cAAA,EAAmB,CAAA,CAAE,QAAQ,CAAA,WAAA,CAAA,GAAgB,EAAE;AAAA,QAAA;AAAA,GACnP,CACC,KAAK,IAAI,CAAA;AACZ,EAAA,OAAO,CAAA;AAAA;AAAA,EAAyG,IAAI;AAAA,SAAA,CAAA;AACtH;AAEO,SAAS,oBAAoB,OAAA,EAAyB;AAC3D,EAAA,OAAO,KAAK,IAAA,CAAK,OAAA,CAAQ,MAAM,KAAK,CAAA,CAAE,SAAS,GAAG,CAAA;AACpD;AAEO,SAAS,aAAa,IAAA,EAG3B;AACA,EAAA,MAAM,SAAmB,EAAC;AAE1B,EAAA,IAAI,CAAC,KAAK,KAAA,IAAS,IAAA,CAAK,MAAM,MAAA,GAAS,EAAA,EAAI,MAAA,CAAO,IAAA,CAAK,8BAA8B,CAAA;AACrF,EAAA,IAAI,IAAA,CAAK,SAAS,IAAA,CAAK,KAAA,CAAM,SAAS,EAAA,EAAI,MAAA,CAAO,KAAK,6BAA6B,CAAA;AACnF,EAAA,IAAI,CAAC,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,YAAY,MAAA,GAAS,EAAA;AACjD,IAAA,MAAA,CAAO,KAAK,oCAAoC,CAAA;AAClD,EAAA,IAAI,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,WAAA,CAAY,MAAA,GAAS,GAAA;AAChD,IAAA,MAAA,CAAO,KAAK,oCAAoC,CAAA;AAClD,EAAA,IAAI,CAAC,KAAK,QAAA,IAAY,IAAA,CAAK,SAAS,MAAA,KAAW,CAAA,EAAG,MAAA,CAAO,IAAA,CAAK,uBAAuB,CAAA;AACrF,EAAA,IAAI,CAAC,IAAA,CAAK,SAAA,EAAW,MAAA,CAAO,KAAK,kBAAkB,CAAA;AACnD,EAAA,IAAI,CAAC,IAAA,CAAK,EAAA,CAAG,KAAA,EAAO,MAAA,CAAO,KAAK,aAAa,CAAA;AAC7C,EAAA,IAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,MAAA,CAAO,KAAK,kBAAkB,CAAA;AAEvD,EAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,GAAA,GAAM,MAAA,CAAO,SAAS,EAAE,CAAA;AAClD,EAAA,OAAO,EAAE,OAAO,MAAA,EAAO;AACzB","file":"index.js","sourcesContent":["/**\n * SEO configuration and utilities\n */\n\nimport type { SEOConfig, SEOMeta, OGMeta, TwitterMeta } from './index'\n\n/**\n * SEO configuration builder for type-safe setup\n *\n * @example\n * ```ts\n * const seoConfig = createSEOConfig()\n * .withSiteName('My Site')\n * .withSiteUrl('https://example.com')\n * .withDefaultImage('https://example.com/og-image.jpg')\n * .withTwitterHandle('@mysite')\n * ```\n */\nexport class SEOConfigBuilder {\n private config: SEOConfig = {\n siteName: '',\n siteUrl: '',\n }\n\n /**\n * Sets the site name\n */\n withSiteName(name: string): this {\n this.config.siteName = name\n return this\n }\n\n /**\n * Sets the site URL\n */\n withSiteUrl(url: string): this {\n this.config.siteUrl = url\n return this\n }\n\n /**\n * Sets default OG image\n */\n withDefaultImage(url: string): this {\n this.config.defaultImage = url\n return this\n }\n\n /**\n * Sets Twitter handle\n */\n withTwitterHandle(handle: string): this {\n this.config.twitterHandle = handle\n return this\n }\n\n /**\n * Sets title template (e.g., '%s — My Site')\n */\n withTitleTemplate(template: string): this {\n this.config.titleTemplate = template\n return this\n }\n\n /**\n * Sets default locale\n */\n withLocale(locale: string): this {\n this.config.locale = locale\n return this\n }\n\n /**\n * Sets Google Analytics ID\n */\n withGoogleAnalyticsId(id: string): this {\n this.config.googleAnalyticsId = id\n return this\n }\n\n /**\n * Sets Google Tag Manager ID\n */\n withGoogleTagManagerId(id: string): this {\n this.config.googleTagManagerId = id\n return this\n }\n\n /**\n * Builds the configuration\n */\n build(): SEOConfig {\n if (!this.config.siteName || !this.config.siteUrl) {\n throw new Error('SEO config requires siteName and siteUrl')\n }\n return this.config\n }\n}\n\n/**\n * Creates a new SEO configuration builder\n */\nexport function createSEOConfig(): SEOConfigBuilder {\n return new SEOConfigBuilder()\n}\n\n/**\n * Creates a validated SEO configuration from a partial config object.\n * Requires siteName and siteUrl at minimum.\n *\n * @example\n * ```ts\n * const config = configureSEO({\n * siteName: 'My Site',\n * siteUrl: 'https://example.com',\n * titleTemplate: '%s — My Site',\n * })\n * ```\n */\nexport function configureSEO(config: Partial<SEOConfig> & { siteName: string; siteUrl: string }): SEOConfig {\n return {\n siteName: config.siteName,\n siteUrl: config.siteUrl,\n titleTemplate: config.titleTemplate,\n defaultImage: config.defaultImage,\n twitterHandle: config.twitterHandle,\n locale: config.locale ?? 'en_US',\n googleAnalyticsId: config.googleAnalyticsId,\n googleTagManagerId: config.googleTagManagerId,\n }\n}\n\n/**\n * Meta tags builder for individual pages\n *\n * @example\n * ```ts\n * const meta = createPageMeta({\n * title: 'Home',\n * description: 'Welcome to my site'\n * })\n * .withKeywords(['web', 'development'])\n * .withImage('https://example.com/image.jpg')\n * ```\n */\nexport class PageMetaBuilder {\n private meta: Partial<SEOMeta> = {\n og: {} as OGMeta,\n twitter: {} as TwitterMeta,\n }\n\n constructor(private config: SEOConfig, title: string, description: string) {\n this.meta.title = title\n this.meta.description = description\n this.meta.og = {\n title,\n description,\n type: 'website',\n url: config.siteUrl,\n siteName: config.siteName,\n image: config.defaultImage,\n }\n this.meta.twitter = {\n card: 'summary_large_image',\n title,\n description,\n image: config.defaultImage,\n creator: config.twitterHandle,\n }\n }\n\n /**\n * Adds keywords\n */\n withKeywords(keywords: string[]): this {\n this.meta.keywords = keywords\n return this\n }\n\n /**\n * Sets canonical URL\n */\n withCanonical(url: string): this {\n this.meta.canonical = url\n return this\n }\n\n /**\n * Sets OG image\n */\n withImage(url: string, alt?: string): this {\n if (this.meta.og) {\n this.meta.og.image = url\n if (alt) this.meta.og.imageAlt = alt\n }\n if (this.meta.twitter) {\n this.meta.twitter.image = url\n }\n return this\n }\n\n /**\n * Sets OG type\n */\n withType(type: 'website' | 'article' | 'product'): this {\n if (this.meta.og) {\n this.meta.og.type = type\n }\n return this\n }\n\n /**\n * Adds JSON-LD schema\n */\n withJsonLd(schema: Record<string, unknown>): this {\n if (!this.meta.jsonLd) {\n this.meta.jsonLd = []\n }\n this.meta.jsonLd.push(schema)\n return this\n }\n\n /**\n * Sets robots meta\n */\n withRobots(robots: string): this {\n this.meta.robots = robots\n return this\n }\n\n /**\n * Adds alternate language links\n */\n withAlternates(alternates: Record<string, string>): this {\n this.meta.alternates = alternates\n return this\n }\n\n /**\n * Builds the meta tags\n */\n build(): SEOMeta {\n return {\n title: this.meta.title || '',\n description: this.meta.description || '',\n keywords: this.meta.keywords || [],\n canonical: this.meta.canonical,\n og: this.meta.og || ({ type: 'website', title: '', description: '', url: '' } as OGMeta),\n twitter: this.meta.twitter || ({ card: 'summary', title: '', description: '' } as TwitterMeta),\n robots: this.meta.robots,\n alternates: this.meta.alternates,\n jsonLd: this.meta.jsonLd,\n }\n }\n}\n\n/**\n * Creates page-specific meta tags\n */\nexport function createPageMeta(config: SEOConfig, title: string, description: string): PageMetaBuilder {\n return new PageMetaBuilder(config, title, description)\n}\n\n/**\n * Validates SEO meta configuration\n */\nexport function validateSEOMeta(meta: SEOMeta): { valid: boolean; errors: string[] } {\n const errors: string[] = []\n\n if (!meta.title || meta.title.length === 0) {\n errors.push('Title is required')\n }\n if (meta.title && meta.title.length > 60) {\n errors.push('Title should be under 60 characters')\n }\n\n if (!meta.description || meta.description.length === 0) {\n errors.push('Description is required')\n }\n if (meta.description && meta.description.length > 160) {\n errors.push('Description should be under 160 characters')\n }\n\n if (!meta.og?.image && !meta.twitter?.image) {\n errors.push('At least one image (OG or Twitter) is recommended')\n }\n\n if (!meta.keywords || meta.keywords.length === 0) {\n errors.push('At least one keyword is recommended')\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\n/**\n * Presets for common SEO configurations\n */\nexport const seoPresets = {\n /**\n * Blog article preset\n */\n article: (_config: SEOConfig): Partial<SEOMeta> => ({\n og: { type: 'article', title: '', description: '', url: '' },\n robots: 'index, follow',\n }),\n\n /**\n * E-commerce product preset\n */\n product: (_config: SEOConfig): Partial<SEOMeta> => ({\n og: { type: 'product', title: '', description: '', url: '' },\n robots: 'index, follow',\n }),\n\n /**\n * Service/company page preset\n */\n business: (_config: SEOConfig): Partial<SEOMeta> => ({\n robots: 'index, follow',\n }),\n\n /**\n * Private/unlisted page preset\n */\n private: (): Partial<SEOMeta> => ({\n robots: 'noindex, nofollow',\n }),\n}\n","// ─── Config Builders & Utilities ──────────────────────────────────\nexport {\n SEOConfigBuilder,\n createSEOConfig,\n configureSEO,\n PageMetaBuilder,\n createPageMeta,\n validateSEOMeta,\n seoPresets,\n} from './config'\n\n// ─── SEO Metadata Interfaces ─────────────────────────────────────\n\nexport interface OGMeta {\n title: string\n description: string\n image?: string\n imageAlt?: string\n type: 'website' | 'article' | 'product'\n url: string\n siteName?: string\n}\n\nexport interface TwitterMeta {\n card: 'summary' | 'summary_large_image'\n title: string\n description: string\n image?: string\n creator?: string\n}\n\nexport interface SEOMeta {\n title: string\n description: string\n keywords: string[]\n canonical?: string\n og: OGMeta\n twitter: TwitterMeta\n robots?: string\n alternates?: Record<string, string>\n jsonLd?: Record<string, unknown>[]\n}\n\nexport interface SitemapEntry {\n url: string\n lastmod?: string\n changefreq?: string\n priority?: number\n}\n\nexport interface RobotsTxt {\n allow: string[]\n disallow: string[]\n sitemap?: string\n}\n\nexport interface SEOConfig {\n siteName: string\n siteUrl: string\n titleTemplate?: string\n defaultImage?: string\n twitterHandle?: string\n locale?: string\n googleAnalyticsId?: string\n googleTagManagerId?: string\n}\n\n// ─── Utility Functions ──────────────────────────────────────────\n\nexport function buildTitle(pageTitle: string, siteName: string, sep = ' | '): string {\n return `${pageTitle}${sep}${siteName}`\n}\n\nexport function applyTitleTemplate(pageTitle: string, template?: string): string {\n if (!template) return pageTitle\n return template.replace('%s', pageTitle)\n}\n\nexport function truncateDescription(desc: string, max = 160): string {\n if (desc.length <= max) return desc\n return desc.slice(0, max - 3) + '...'\n}\n\n// ─── JSON-LD Schema Builders ────────────────────────────────────\n\nexport function articleSchema(data: {\n title: string\n description: string\n author: string\n datePublished: string\n url: string\n image?: string\n}): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n ...data,\n }\n}\n\nexport function productSchema(data: {\n name: string\n description: string\n price: number\n currency: string\n url: string\n image?: string\n}): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: data.name,\n description: data.description,\n url: data.url,\n image: data.image,\n offers: {\n '@type': 'Offer',\n price: data.price,\n priceCurrency: data.currency,\n },\n }\n}\n\nexport function orgSchema(data: {\n name: string\n url: string\n logo?: string\n sameAs?: string[]\n}): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n ...data,\n }\n}\n\nexport function breadcrumbSchema(items: {\n name: string\n url: string\n}[]): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items.map((item, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: item.name,\n item: item.url,\n })),\n }\n}\n\nexport function faqSchema(items: {\n question: string\n answer: string\n}[]): Record<string, unknown> {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((q) => ({\n '@type': 'Question',\n name: q.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: q.answer,\n },\n })),\n }\n}\n\nexport function webApplicationSchema(data: {\n name: string\n url: string\n description: string\n applicationCategory?: string\n operatingSystem?: string\n offers?: { price: number; currency: string }\n}): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'WebApplication',\n name: data.name,\n url: data.url,\n description: data.description,\n }\n if (data.applicationCategory) schema.applicationCategory = data.applicationCategory\n if (data.operatingSystem) schema.operatingSystem = data.operatingSystem\n if (data.offers) {\n schema.offers = {\n '@type': 'Offer',\n price: data.offers.price,\n priceCurrency: data.offers.currency,\n }\n }\n return schema\n}\n\nexport function buildCanonical(path: string, baseUrl: string): string {\n return `${baseUrl.replace(/\\/$/, '')}/${path.replace(/^\\//, '')}`\n}\n\nexport function buildAlternates(\n locales: string[],\n baseUrl: string,\n path: string,\n): Record<string, string> {\n return Object.fromEntries(locales.map((l) => [l, `${baseUrl}/${l}${path}`]))\n}\n\nexport function generateRobotsTxt(config: RobotsTxt): string {\n const lines = ['User-agent: *']\n config.allow.forEach((p) => lines.push(`Allow: ${p}`))\n config.disallow.forEach((p) => lines.push(`Disallow: ${p}`))\n if (config.sitemap) lines.push(`Sitemap: ${config.sitemap}`)\n return lines.join('\\n')\n}\n\nexport function generateSitemapXml(entries: SitemapEntry[]): string {\n const urls = entries\n .map(\n (e) =>\n ` <url>\\n <loc>${e.url}</loc>${e.lastmod ? `\\n <lastmod>${e.lastmod}</lastmod>` : ''}${e.changefreq ? `\\n <changefreq>${e.changefreq}</changefreq>` : ''}${e.priority !== undefined ? `\\n <priority>${e.priority}</priority>` : ''}\\n </url>`,\n )\n .join('\\n')\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>`\n}\n\nexport function estimateReadingTime(content: string): number {\n return Math.ceil(content.split(/\\s+/).length / 200)\n}\n\nexport function calcSEOScore(meta: SEOMeta): {\n score: number\n issues: string[]\n} {\n const issues: string[] = []\n\n if (!meta.title || meta.title.length < 10) issues.push('Title too short (< 10 chars)')\n if (meta.title && meta.title.length > 60) issues.push('Title too long (> 60 chars)')\n if (!meta.description || meta.description.length < 50)\n issues.push('Description too short (< 50 chars)')\n if (meta.description && meta.description.length > 160)\n issues.push('Description too long (> 160 chars)')\n if (!meta.keywords || meta.keywords.length === 0) issues.push('No keywords specified')\n if (!meta.canonical) issues.push('No canonical URL')\n if (!meta.og.image) issues.push('No OG image')\n if (!meta.twitter.image) issues.push('No Twitter image')\n\n const score = Math.max(0, 100 - issues.length * 12)\n return { score, issues }\n}\n"]}
@@ -1 +1 @@
1
- # ✦ @geenius-seo/solidjs\n\n> Geenius Seo — SolidJS components & primitives\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-seo/solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-seo/solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
1
+ # ✦ @geenius/seo-solidjs\n\n> Geenius Seo — SolidJS components & primitives\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius/seo-solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius/seo-solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
@@ -0,0 +1,133 @@
1
+ import { SEOConfig, SEOMeta, SitemapEntry } from '@geenius/seo-shared';
2
+ export * from '@geenius/seo-shared';
3
+ import * as solid_js from 'solid-js';
4
+ import { JSX, Accessor } from 'solid-js';
5
+
6
+ interface SeoContextValue {
7
+ config: SEOConfig;
8
+ }
9
+ interface SeoProviderProps {
10
+ config: SEOConfig;
11
+ children: JSX.Element;
12
+ }
13
+ declare function SeoProvider(props: SeoProviderProps): JSX.Element;
14
+ declare function useSEOContext(): SeoContextValue;
15
+
16
+ interface CreateSEOOptions {
17
+ path?: string;
18
+ }
19
+ declare function createSEO(options?: CreateSEOOptions): {
20
+ meta: solid_js.Accessor<SEOMeta | null>;
21
+ updateMeta: (newMeta: SEOMeta) => void;
22
+ isLoading: solid_js.Accessor<boolean>;
23
+ };
24
+ /** @deprecated Use `createSEO` instead */
25
+ declare const useSEO: typeof createSEO;
26
+
27
+ interface PageEntry {
28
+ path: string;
29
+ meta: SEOMeta;
30
+ }
31
+ interface CreateSEOAdminOptions {
32
+ /** Pre-loaded pages (e.g. from Convex createQuery) */
33
+ initialPages?: Accessor<PageEntry[] | undefined>;
34
+ /** Callback when a page is upserted */
35
+ onUpsert?: (path: string, meta: SEOMeta) => Promise<void>;
36
+ /** Callback when a page is deleted */
37
+ onDelete?: (path: string) => Promise<void>;
38
+ }
39
+ /**
40
+ * Primitive to manage multiple page SEO metadata entries.
41
+ * Supports optional Convex backend via callback props.
42
+ */
43
+ declare function createSEOAdmin(options?: CreateSEOAdminOptions): {
44
+ pages: Accessor<PageEntry[]>;
45
+ upsertMeta: (path: string, meta: SEOMeta) => Promise<void>;
46
+ deleteMeta: (path: string) => Promise<void>;
47
+ isLoading: Accessor<boolean>;
48
+ };
49
+ /** @deprecated Use `createSEOAdmin` instead */
50
+ declare const useSEOAdmin: typeof createSEOAdmin;
51
+
52
+ declare function createSEOScore(meta: () => SEOMeta): solid_js.Accessor<{
53
+ score: number;
54
+ issues: string[];
55
+ }>;
56
+ /** @deprecated Use `createSEOScore` instead */
57
+ declare const useSEOScore: typeof createSEOScore;
58
+
59
+ declare function createSitemap(entries: () => SitemapEntry[]): {
60
+ xml: solid_js.Accessor<string>;
61
+ };
62
+ /** @deprecated Use `createSitemap` instead */
63
+ declare const useSitemap: typeof createSitemap;
64
+
65
+ /**
66
+ * Returns a canonical URL for the given path using the site URL from SeoProvider.
67
+ */
68
+ declare function createCanonical(path?: Accessor<string | undefined>): Accessor<string>;
69
+
70
+ /**
71
+ * Injects JSON-LD structured data into the document head.
72
+ * Cleans up on disposal or when data changes.
73
+ */
74
+ declare function createStructuredData(data: Accessor<Record<string, unknown> | Record<string, unknown>[] | undefined>): void;
75
+
76
+ interface SEOHeadProps {
77
+ meta: SEOMeta;
78
+ }
79
+ declare function SEOHead(props: SEOHeadProps): null;
80
+
81
+ interface SEOPreviewProps {
82
+ meta: SEOMeta;
83
+ }
84
+ declare function SEOPreview(props: SEOPreviewProps): solid_js.JSX.Element;
85
+
86
+ interface SEOScoreCardProps {
87
+ meta: SEOMeta;
88
+ }
89
+ declare function SEOScoreCard(props: SEOScoreCardProps): solid_js.JSX.Element;
90
+
91
+ interface MetaEditorProps {
92
+ meta: SEOMeta;
93
+ onChange: (meta: SEOMeta) => void;
94
+ }
95
+ declare function MetaEditor(props: MetaEditorProps): solid_js.JSX.Element;
96
+
97
+ interface SitemapViewerProps {
98
+ entries: SitemapEntry[];
99
+ }
100
+ declare function SitemapViewer(props: SitemapViewerProps): solid_js.JSX.Element;
101
+
102
+ interface BreadcrumbsJsonLdProps {
103
+ items: {
104
+ name: string;
105
+ url: string;
106
+ }[];
107
+ }
108
+ declare function BreadcrumbsJsonLd(props: BreadcrumbsJsonLdProps): null;
109
+
110
+ interface ArticleJsonLdProps {
111
+ title: string;
112
+ description: string;
113
+ author: string;
114
+ datePublished: string;
115
+ url: string;
116
+ image?: string;
117
+ }
118
+ declare function ArticleJsonLd(props: ArticleJsonLdProps): null;
119
+
120
+ interface JsonLdProps {
121
+ type: string;
122
+ data: Record<string, unknown>;
123
+ }
124
+ /**
125
+ * Renders a JSON-LD structured data script in the document head.
126
+ */
127
+ declare function JsonLd(props: JsonLdProps): null;
128
+
129
+ declare function SEOAdminPage(): solid_js.JSX.Element;
130
+
131
+ declare function SEOAnalyticsPage(): solid_js.JSX.Element;
132
+
133
+ export { ArticleJsonLd, BreadcrumbsJsonLd, type CreateSEOAdminOptions, JsonLd, type JsonLdProps, MetaEditor, SEOAdminPage, SEOAnalyticsPage, SEOHead, SEOPreview, SEOScoreCard, SeoProvider, type SeoProviderProps, SitemapViewer, createCanonical, createSEO, createSEOAdmin, createSEOScore, createSitemap, createStructuredData, useSEO, useSEOAdmin, useSEOContext, useSEOScore, useSitemap };