@seo-console/package 1.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/dist/index.mjs ADDED
@@ -0,0 +1,4316 @@
1
+ // src/lib/supabase/server.ts
2
+ import { createServerClient } from "@supabase/ssr";
3
+ import { cookies } from "next/headers";
4
+ async function createClient() {
5
+ const cookieStore = await cookies();
6
+ return createServerClient(
7
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
8
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
9
+ {
10
+ cookies: {
11
+ getAll() {
12
+ return cookieStore.getAll();
13
+ },
14
+ setAll(cookiesToSet) {
15
+ try {
16
+ cookiesToSet.forEach(
17
+ ({ name, value, options }) => cookieStore.set(name, value, options)
18
+ );
19
+ } catch {
20
+ }
21
+ }
22
+ }
23
+ }
24
+ );
25
+ }
26
+
27
+ // src/lib/database/seo-records.ts
28
+ function transformRowToSEORecord(row) {
29
+ return {
30
+ id: row.id,
31
+ userId: row.user_id,
32
+ routePath: row.route_path,
33
+ title: row.title ?? void 0,
34
+ description: row.description ?? void 0,
35
+ keywords: row.keywords ?? void 0,
36
+ ogTitle: row.og_title ?? void 0,
37
+ ogDescription: row.og_description ?? void 0,
38
+ ogImageUrl: row.og_image_url ?? void 0,
39
+ ogImageWidth: row.og_image_width ?? void 0,
40
+ ogImageHeight: row.og_image_height ?? void 0,
41
+ ogType: row.og_type ?? void 0,
42
+ ogUrl: row.og_url ?? void 0,
43
+ ogSiteName: row.og_site_name ?? void 0,
44
+ twitterCard: row.twitter_card ?? void 0,
45
+ twitterTitle: row.twitter_title ?? void 0,
46
+ twitterDescription: row.twitter_description ?? void 0,
47
+ twitterImageUrl: row.twitter_image_url ?? void 0,
48
+ twitterSite: row.twitter_site ?? void 0,
49
+ twitterCreator: row.twitter_creator ?? void 0,
50
+ canonicalUrl: row.canonical_url ?? void 0,
51
+ robots: row.robots ?? void 0,
52
+ author: row.author ?? void 0,
53
+ publishedTime: row.published_time ? new Date(row.published_time) : void 0,
54
+ modifiedTime: row.modified_time ? new Date(row.modified_time) : void 0,
55
+ structuredData: row.structured_data ? row.structured_data : void 0,
56
+ validationStatus: row.validation_status ?? void 0,
57
+ lastValidatedAt: row.last_validated_at ? new Date(row.last_validated_at) : void 0,
58
+ validationErrors: row.validation_errors ? row.validation_errors : void 0,
59
+ createdAt: new Date(row.created_at),
60
+ updatedAt: new Date(row.updated_at)
61
+ };
62
+ }
63
+ function transformToInsert(record) {
64
+ return {
65
+ user_id: record.userId,
66
+ route_path: record.routePath,
67
+ title: record.title ?? null,
68
+ description: record.description ?? null,
69
+ keywords: record.keywords ?? null,
70
+ og_title: record.ogTitle ?? null,
71
+ og_description: record.ogDescription ?? null,
72
+ og_image_url: record.ogImageUrl ?? null,
73
+ og_image_width: record.ogImageWidth ?? null,
74
+ og_image_height: record.ogImageHeight ?? null,
75
+ og_type: record.ogType ?? null,
76
+ og_url: record.ogUrl ?? null,
77
+ og_site_name: record.ogSiteName ?? null,
78
+ twitter_card: record.twitterCard ?? null,
79
+ twitter_title: record.twitterTitle ?? null,
80
+ twitter_description: record.twitterDescription ?? null,
81
+ twitter_image_url: record.twitterImageUrl ?? null,
82
+ twitter_site: record.twitterSite ?? null,
83
+ twitter_creator: record.twitterCreator ?? null,
84
+ canonical_url: record.canonicalUrl ?? null,
85
+ robots: record.robots ?? null,
86
+ author: record.author ?? null,
87
+ published_time: record.publishedTime?.toISOString() ?? null,
88
+ modified_time: record.modifiedTime?.toISOString() ?? null,
89
+ structured_data: record.structuredData ?? null
90
+ };
91
+ }
92
+ function transformToUpdate(record) {
93
+ const update = {};
94
+ if (record.routePath !== void 0) update.route_path = record.routePath;
95
+ if (record.title !== void 0) update.title = record.title ?? null;
96
+ if (record.description !== void 0)
97
+ update.description = record.description ?? null;
98
+ if (record.keywords !== void 0) update.keywords = record.keywords ?? null;
99
+ if (record.ogTitle !== void 0) update.og_title = record.ogTitle ?? null;
100
+ if (record.ogDescription !== void 0)
101
+ update.og_description = record.ogDescription ?? null;
102
+ if (record.ogImageUrl !== void 0)
103
+ update.og_image_url = record.ogImageUrl ?? null;
104
+ if (record.ogImageWidth !== void 0)
105
+ update.og_image_width = record.ogImageWidth ?? null;
106
+ if (record.ogImageHeight !== void 0)
107
+ update.og_image_height = record.ogImageHeight ?? null;
108
+ if (record.ogType !== void 0) update.og_type = record.ogType ?? null;
109
+ if (record.ogUrl !== void 0) update.og_url = record.ogUrl ?? null;
110
+ if (record.ogSiteName !== void 0)
111
+ update.og_site_name = record.ogSiteName ?? null;
112
+ if (record.twitterCard !== void 0)
113
+ update.twitter_card = record.twitterCard ?? null;
114
+ if (record.twitterTitle !== void 0)
115
+ update.twitter_title = record.twitterTitle ?? null;
116
+ if (record.twitterDescription !== void 0)
117
+ update.twitter_description = record.twitterDescription ?? null;
118
+ if (record.twitterImageUrl !== void 0)
119
+ update.twitter_image_url = record.twitterImageUrl ?? null;
120
+ if (record.twitterSite !== void 0)
121
+ update.twitter_site = record.twitterSite ?? null;
122
+ if (record.twitterCreator !== void 0)
123
+ update.twitter_creator = record.twitterCreator ?? null;
124
+ if (record.canonicalUrl !== void 0)
125
+ update.canonical_url = record.canonicalUrl ?? null;
126
+ if (record.robots !== void 0) update.robots = record.robots ?? null;
127
+ if (record.author !== void 0) update.author = record.author ?? null;
128
+ if (record.publishedTime !== void 0)
129
+ update.published_time = record.publishedTime?.toISOString() ?? null;
130
+ if (record.modifiedTime !== void 0)
131
+ update.modified_time = record.modifiedTime?.toISOString() ?? null;
132
+ if (record.structuredData !== void 0)
133
+ update.structured_data = record.structuredData ?? null;
134
+ if (record.validationStatus !== void 0)
135
+ update.validation_status = record.validationStatus ?? null;
136
+ if (record.lastValidatedAt !== void 0)
137
+ update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;
138
+ if (record.validationErrors !== void 0)
139
+ update.validation_errors = record.validationErrors ?? null;
140
+ return update;
141
+ }
142
+ async function getSEORecords() {
143
+ try {
144
+ const supabase = await createClient();
145
+ const {
146
+ data: { user }
147
+ } = await supabase.auth.getUser();
148
+ if (!user) {
149
+ return {
150
+ success: false,
151
+ error: new Error("User not authenticated")
152
+ };
153
+ }
154
+ const { data, error } = await supabase.from("seo_records").select("*").eq("user_id", user.id).order("created_at", { ascending: false });
155
+ if (error) {
156
+ return { success: false, error };
157
+ }
158
+ const records = (data || []).map(transformRowToSEORecord);
159
+ return { success: true, data: records };
160
+ } catch (error) {
161
+ return {
162
+ success: false,
163
+ error: error instanceof Error ? error : new Error("Unknown error")
164
+ };
165
+ }
166
+ }
167
+ async function getSEORecordById(id) {
168
+ try {
169
+ const supabase = await createClient();
170
+ const {
171
+ data: { user }
172
+ } = await supabase.auth.getUser();
173
+ if (!user) {
174
+ return {
175
+ success: false,
176
+ error: new Error("User not authenticated")
177
+ };
178
+ }
179
+ const { data, error } = await supabase.from("seo_records").select("*").eq("id", id).eq("user_id", user.id).single();
180
+ if (error) {
181
+ return { success: false, error };
182
+ }
183
+ if (!data) {
184
+ return {
185
+ success: false,
186
+ error: new Error("SEO record not found")
187
+ };
188
+ }
189
+ return { success: true, data: transformRowToSEORecord(data) };
190
+ } catch (error) {
191
+ return {
192
+ success: false,
193
+ error: error instanceof Error ? error : new Error("Unknown error")
194
+ };
195
+ }
196
+ }
197
+ async function getSEORecordByRoute(routePath) {
198
+ try {
199
+ const supabase = await createClient();
200
+ const {
201
+ data: { user }
202
+ } = await supabase.auth.getUser();
203
+ if (!user) {
204
+ return {
205
+ success: false,
206
+ error: new Error("User not authenticated")
207
+ };
208
+ }
209
+ const { data, error } = await supabase.from("seo_records").select("*").eq("route_path", routePath).eq("user_id", user.id).maybeSingle();
210
+ if (error) {
211
+ return { success: false, error };
212
+ }
213
+ if (!data) {
214
+ return { success: true, data: null };
215
+ }
216
+ return { success: true, data: transformRowToSEORecord(data) };
217
+ } catch (error) {
218
+ return {
219
+ success: false,
220
+ error: error instanceof Error ? error : new Error("Unknown error")
221
+ };
222
+ }
223
+ }
224
+ async function createSEORecord(record) {
225
+ try {
226
+ const supabase = await createClient();
227
+ const {
228
+ data: { user }
229
+ } = await supabase.auth.getUser();
230
+ if (!user) {
231
+ return {
232
+ success: false,
233
+ error: new Error("User not authenticated")
234
+ };
235
+ }
236
+ const insertData = transformToInsert({ ...record, userId: user.id });
237
+ const { data, error } = await supabase.from("seo_records").insert(insertData).select().single();
238
+ if (error) {
239
+ return { success: false, error };
240
+ }
241
+ return { success: true, data: transformRowToSEORecord(data) };
242
+ } catch (error) {
243
+ return {
244
+ success: false,
245
+ error: error instanceof Error ? error : new Error("Unknown error")
246
+ };
247
+ }
248
+ }
249
+ async function updateSEORecord(record) {
250
+ try {
251
+ const supabase = await createClient();
252
+ const {
253
+ data: { user }
254
+ } = await supabase.auth.getUser();
255
+ if (!user) {
256
+ return {
257
+ success: false,
258
+ error: new Error("User not authenticated")
259
+ };
260
+ }
261
+ const { id, ...updateData } = record;
262
+ const transformedUpdate = transformToUpdate(updateData);
263
+ const { data, error } = await supabase.from("seo_records").update(transformedUpdate).eq("id", id).eq("user_id", user.id).select().single();
264
+ if (error) {
265
+ return { success: false, error };
266
+ }
267
+ if (!data) {
268
+ return {
269
+ success: false,
270
+ error: new Error("SEO record not found")
271
+ };
272
+ }
273
+ return { success: true, data: transformRowToSEORecord(data) };
274
+ } catch (error) {
275
+ return {
276
+ success: false,
277
+ error: error instanceof Error ? error : new Error("Unknown error")
278
+ };
279
+ }
280
+ }
281
+ async function deleteSEORecord(id) {
282
+ try {
283
+ const supabase = await createClient();
284
+ const {
285
+ data: { user }
286
+ } = await supabase.auth.getUser();
287
+ if (!user) {
288
+ return {
289
+ success: false,
290
+ error: new Error("User not authenticated")
291
+ };
292
+ }
293
+ const { error } = await supabase.from("seo_records").delete().eq("id", id).eq("user_id", user.id);
294
+ if (error) {
295
+ return { success: false, error };
296
+ }
297
+ return { success: true, data: void 0 };
298
+ } catch (error) {
299
+ return {
300
+ success: false,
301
+ error: error instanceof Error ? error : new Error("Unknown error")
302
+ };
303
+ }
304
+ }
305
+
306
+ // src/hooks/useGenerateMetadata.ts
307
+ async function useGenerateMetadata(options = {}) {
308
+ const { routePath, fallback = {} } = options;
309
+ if (!routePath) {
310
+ return {
311
+ title: fallback.title,
312
+ description: fallback.description,
313
+ ...fallback
314
+ };
315
+ }
316
+ const result = await getSEORecordByRoute(routePath);
317
+ if (!result.success || !result.data) {
318
+ return {
319
+ title: fallback.title,
320
+ description: fallback.description,
321
+ ...fallback
322
+ };
323
+ }
324
+ const record = result.data;
325
+ const metadata = {};
326
+ if (record.title) {
327
+ metadata.title = record.title;
328
+ }
329
+ if (record.description) {
330
+ metadata.description = record.description;
331
+ }
332
+ if (record.keywords && record.keywords.length > 0) {
333
+ metadata.keywords = record.keywords;
334
+ }
335
+ if (record.author) {
336
+ metadata.authors = [{ name: record.author }];
337
+ }
338
+ if (record.ogTitle || record.ogDescription || record.ogImageUrl || record.ogType) {
339
+ const supportedOGTypes = ["website", "article", "book", "profile"];
340
+ const ogType = record.ogType && supportedOGTypes.includes(record.ogType) ? record.ogType : "website";
341
+ const openGraph = {
342
+ type: ogType,
343
+ title: record.ogTitle || record.title || void 0,
344
+ description: record.ogDescription || record.description || void 0,
345
+ url: record.ogUrl || void 0,
346
+ siteName: record.ogSiteName || void 0
347
+ };
348
+ if (record.ogImageUrl) {
349
+ openGraph.images = [
350
+ {
351
+ url: record.ogImageUrl,
352
+ width: record.ogImageWidth || void 0,
353
+ height: record.ogImageHeight || void 0,
354
+ alt: record.ogTitle || record.title || void 0
355
+ }
356
+ ];
357
+ }
358
+ if (ogType === "article") {
359
+ const articleOpenGraph = {
360
+ ...openGraph,
361
+ ...record.publishedTime && {
362
+ publishedTime: record.publishedTime.toISOString()
363
+ },
364
+ ...record.modifiedTime && {
365
+ modifiedTime: record.modifiedTime.toISOString()
366
+ }
367
+ };
368
+ metadata.openGraph = articleOpenGraph;
369
+ } else {
370
+ metadata.openGraph = openGraph;
371
+ }
372
+ }
373
+ if (record.twitterCard || record.twitterTitle || record.twitterDescription || record.twitterImageUrl) {
374
+ metadata.twitter = {
375
+ card: record.twitterCard || "summary",
376
+ title: record.twitterTitle || record.ogTitle || record.title || void 0,
377
+ description: record.twitterDescription || record.ogDescription || record.description || void 0,
378
+ images: record.twitterImageUrl ? [record.twitterImageUrl] : void 0,
379
+ site: record.twitterSite || void 0,
380
+ creator: record.twitterCreator || void 0
381
+ };
382
+ }
383
+ if (record.canonicalUrl) {
384
+ metadata.alternates = {
385
+ canonical: record.canonicalUrl
386
+ };
387
+ }
388
+ if (record.robots) {
389
+ metadata.robots = record.robots;
390
+ }
391
+ return {
392
+ ...fallback,
393
+ ...metadata,
394
+ // Ensure title and description from record override fallback if present
395
+ title: record.title || fallback.title,
396
+ description: record.description || fallback.description,
397
+ // Merge openGraph if both exist
398
+ openGraph: fallback.openGraph ? { ...metadata.openGraph, ...fallback.openGraph } : metadata.openGraph,
399
+ // Merge twitter if both exist
400
+ twitter: fallback.twitter ? { ...metadata.twitter, ...fallback.twitter } : metadata.twitter
401
+ };
402
+ }
403
+ function getRoutePathFromParams(params, pattern) {
404
+ let routePath = pattern;
405
+ for (const [key, value] of Object.entries(params)) {
406
+ const paramValue = Array.isArray(value) ? value.join("/") : value;
407
+ routePath = routePath.replace(`[${key}]`, paramValue);
408
+ routePath = routePath.replace(`[...${key}]`, paramValue);
409
+ }
410
+ return routePath;
411
+ }
412
+
413
+ // src/components/seo/SEORecordList.tsx
414
+ import { useState as useState3, useEffect as useEffect2 } from "react";
415
+
416
+ // src/components/ui/button.tsx
417
+ import * as React from "react";
418
+
419
+ // src/lib/utils.ts
420
+ import { clsx } from "clsx";
421
+
422
+ // ../../node_modules/tailwind-merge/dist/bundle-mjs.mjs
423
+ var CLASS_PART_SEPARATOR = "-";
424
+ var createClassGroupUtils = (config) => {
425
+ const classMap = createClassMap(config);
426
+ const {
427
+ conflictingClassGroups,
428
+ conflictingClassGroupModifiers
429
+ } = config;
430
+ const getClassGroupId = (className) => {
431
+ const classParts = className.split(CLASS_PART_SEPARATOR);
432
+ if (classParts[0] === "" && classParts.length !== 1) {
433
+ classParts.shift();
434
+ }
435
+ return getGroupRecursive(classParts, classMap) || getGroupIdForArbitraryProperty(className);
436
+ };
437
+ const getConflictingClassGroupIds = (classGroupId, hasPostfixModifier) => {
438
+ const conflicts = conflictingClassGroups[classGroupId] || [];
439
+ if (hasPostfixModifier && conflictingClassGroupModifiers[classGroupId]) {
440
+ return [...conflicts, ...conflictingClassGroupModifiers[classGroupId]];
441
+ }
442
+ return conflicts;
443
+ };
444
+ return {
445
+ getClassGroupId,
446
+ getConflictingClassGroupIds
447
+ };
448
+ };
449
+ var getGroupRecursive = (classParts, classPartObject) => {
450
+ if (classParts.length === 0) {
451
+ return classPartObject.classGroupId;
452
+ }
453
+ const currentClassPart = classParts[0];
454
+ const nextClassPartObject = classPartObject.nextPart.get(currentClassPart);
455
+ const classGroupFromNextClassPart = nextClassPartObject ? getGroupRecursive(classParts.slice(1), nextClassPartObject) : void 0;
456
+ if (classGroupFromNextClassPart) {
457
+ return classGroupFromNextClassPart;
458
+ }
459
+ if (classPartObject.validators.length === 0) {
460
+ return void 0;
461
+ }
462
+ const classRest = classParts.join(CLASS_PART_SEPARATOR);
463
+ return classPartObject.validators.find(({
464
+ validator
465
+ }) => validator(classRest))?.classGroupId;
466
+ };
467
+ var arbitraryPropertyRegex = /^\[(.+)\]$/;
468
+ var getGroupIdForArbitraryProperty = (className) => {
469
+ if (arbitraryPropertyRegex.test(className)) {
470
+ const arbitraryPropertyClassName = arbitraryPropertyRegex.exec(className)[1];
471
+ const property = arbitraryPropertyClassName?.substring(0, arbitraryPropertyClassName.indexOf(":"));
472
+ if (property) {
473
+ return "arbitrary.." + property;
474
+ }
475
+ }
476
+ };
477
+ var createClassMap = (config) => {
478
+ const {
479
+ theme,
480
+ prefix
481
+ } = config;
482
+ const classMap = {
483
+ nextPart: /* @__PURE__ */ new Map(),
484
+ validators: []
485
+ };
486
+ const prefixedClassGroupEntries = getPrefixedClassGroupEntries(Object.entries(config.classGroups), prefix);
487
+ prefixedClassGroupEntries.forEach(([classGroupId, classGroup]) => {
488
+ processClassesRecursively(classGroup, classMap, classGroupId, theme);
489
+ });
490
+ return classMap;
491
+ };
492
+ var processClassesRecursively = (classGroup, classPartObject, classGroupId, theme) => {
493
+ classGroup.forEach((classDefinition) => {
494
+ if (typeof classDefinition === "string") {
495
+ const classPartObjectToEdit = classDefinition === "" ? classPartObject : getPart(classPartObject, classDefinition);
496
+ classPartObjectToEdit.classGroupId = classGroupId;
497
+ return;
498
+ }
499
+ if (typeof classDefinition === "function") {
500
+ if (isThemeGetter(classDefinition)) {
501
+ processClassesRecursively(classDefinition(theme), classPartObject, classGroupId, theme);
502
+ return;
503
+ }
504
+ classPartObject.validators.push({
505
+ validator: classDefinition,
506
+ classGroupId
507
+ });
508
+ return;
509
+ }
510
+ Object.entries(classDefinition).forEach(([key, classGroup2]) => {
511
+ processClassesRecursively(classGroup2, getPart(classPartObject, key), classGroupId, theme);
512
+ });
513
+ });
514
+ };
515
+ var getPart = (classPartObject, path) => {
516
+ let currentClassPartObject = classPartObject;
517
+ path.split(CLASS_PART_SEPARATOR).forEach((pathPart) => {
518
+ if (!currentClassPartObject.nextPart.has(pathPart)) {
519
+ currentClassPartObject.nextPart.set(pathPart, {
520
+ nextPart: /* @__PURE__ */ new Map(),
521
+ validators: []
522
+ });
523
+ }
524
+ currentClassPartObject = currentClassPartObject.nextPart.get(pathPart);
525
+ });
526
+ return currentClassPartObject;
527
+ };
528
+ var isThemeGetter = (func) => func.isThemeGetter;
529
+ var getPrefixedClassGroupEntries = (classGroupEntries, prefix) => {
530
+ if (!prefix) {
531
+ return classGroupEntries;
532
+ }
533
+ return classGroupEntries.map(([classGroupId, classGroup]) => {
534
+ const prefixedClassGroup = classGroup.map((classDefinition) => {
535
+ if (typeof classDefinition === "string") {
536
+ return prefix + classDefinition;
537
+ }
538
+ if (typeof classDefinition === "object") {
539
+ return Object.fromEntries(Object.entries(classDefinition).map(([key, value]) => [prefix + key, value]));
540
+ }
541
+ return classDefinition;
542
+ });
543
+ return [classGroupId, prefixedClassGroup];
544
+ });
545
+ };
546
+ var createLruCache = (maxCacheSize) => {
547
+ if (maxCacheSize < 1) {
548
+ return {
549
+ get: () => void 0,
550
+ set: () => {
551
+ }
552
+ };
553
+ }
554
+ let cacheSize = 0;
555
+ let cache = /* @__PURE__ */ new Map();
556
+ let previousCache = /* @__PURE__ */ new Map();
557
+ const update = (key, value) => {
558
+ cache.set(key, value);
559
+ cacheSize++;
560
+ if (cacheSize > maxCacheSize) {
561
+ cacheSize = 0;
562
+ previousCache = cache;
563
+ cache = /* @__PURE__ */ new Map();
564
+ }
565
+ };
566
+ return {
567
+ get(key) {
568
+ let value = cache.get(key);
569
+ if (value !== void 0) {
570
+ return value;
571
+ }
572
+ if ((value = previousCache.get(key)) !== void 0) {
573
+ update(key, value);
574
+ return value;
575
+ }
576
+ },
577
+ set(key, value) {
578
+ if (cache.has(key)) {
579
+ cache.set(key, value);
580
+ } else {
581
+ update(key, value);
582
+ }
583
+ }
584
+ };
585
+ };
586
+ var IMPORTANT_MODIFIER = "!";
587
+ var createParseClassName = (config) => {
588
+ const {
589
+ separator,
590
+ experimentalParseClassName
591
+ } = config;
592
+ const isSeparatorSingleCharacter = separator.length === 1;
593
+ const firstSeparatorCharacter = separator[0];
594
+ const separatorLength = separator.length;
595
+ const parseClassName = (className) => {
596
+ const modifiers = [];
597
+ let bracketDepth = 0;
598
+ let modifierStart = 0;
599
+ let postfixModifierPosition;
600
+ for (let index = 0; index < className.length; index++) {
601
+ let currentCharacter = className[index];
602
+ if (bracketDepth === 0) {
603
+ if (currentCharacter === firstSeparatorCharacter && (isSeparatorSingleCharacter || className.slice(index, index + separatorLength) === separator)) {
604
+ modifiers.push(className.slice(modifierStart, index));
605
+ modifierStart = index + separatorLength;
606
+ continue;
607
+ }
608
+ if (currentCharacter === "/") {
609
+ postfixModifierPosition = index;
610
+ continue;
611
+ }
612
+ }
613
+ if (currentCharacter === "[") {
614
+ bracketDepth++;
615
+ } else if (currentCharacter === "]") {
616
+ bracketDepth--;
617
+ }
618
+ }
619
+ const baseClassNameWithImportantModifier = modifiers.length === 0 ? className : className.substring(modifierStart);
620
+ const hasImportantModifier = baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER);
621
+ const baseClassName = hasImportantModifier ? baseClassNameWithImportantModifier.substring(1) : baseClassNameWithImportantModifier;
622
+ const maybePostfixModifierPosition = postfixModifierPosition && postfixModifierPosition > modifierStart ? postfixModifierPosition - modifierStart : void 0;
623
+ return {
624
+ modifiers,
625
+ hasImportantModifier,
626
+ baseClassName,
627
+ maybePostfixModifierPosition
628
+ };
629
+ };
630
+ if (experimentalParseClassName) {
631
+ return (className) => experimentalParseClassName({
632
+ className,
633
+ parseClassName
634
+ });
635
+ }
636
+ return parseClassName;
637
+ };
638
+ var sortModifiers = (modifiers) => {
639
+ if (modifiers.length <= 1) {
640
+ return modifiers;
641
+ }
642
+ const sortedModifiers = [];
643
+ let unsortedModifiers = [];
644
+ modifiers.forEach((modifier) => {
645
+ const isArbitraryVariant = modifier[0] === "[";
646
+ if (isArbitraryVariant) {
647
+ sortedModifiers.push(...unsortedModifiers.sort(), modifier);
648
+ unsortedModifiers = [];
649
+ } else {
650
+ unsortedModifiers.push(modifier);
651
+ }
652
+ });
653
+ sortedModifiers.push(...unsortedModifiers.sort());
654
+ return sortedModifiers;
655
+ };
656
+ var createConfigUtils = (config) => ({
657
+ cache: createLruCache(config.cacheSize),
658
+ parseClassName: createParseClassName(config),
659
+ ...createClassGroupUtils(config)
660
+ });
661
+ var SPLIT_CLASSES_REGEX = /\s+/;
662
+ var mergeClassList = (classList, configUtils) => {
663
+ const {
664
+ parseClassName,
665
+ getClassGroupId,
666
+ getConflictingClassGroupIds
667
+ } = configUtils;
668
+ const classGroupsInConflict = [];
669
+ const classNames = classList.trim().split(SPLIT_CLASSES_REGEX);
670
+ let result = "";
671
+ for (let index = classNames.length - 1; index >= 0; index -= 1) {
672
+ const originalClassName = classNames[index];
673
+ const {
674
+ modifiers,
675
+ hasImportantModifier,
676
+ baseClassName,
677
+ maybePostfixModifierPosition
678
+ } = parseClassName(originalClassName);
679
+ let hasPostfixModifier = Boolean(maybePostfixModifierPosition);
680
+ let classGroupId = getClassGroupId(hasPostfixModifier ? baseClassName.substring(0, maybePostfixModifierPosition) : baseClassName);
681
+ if (!classGroupId) {
682
+ if (!hasPostfixModifier) {
683
+ result = originalClassName + (result.length > 0 ? " " + result : result);
684
+ continue;
685
+ }
686
+ classGroupId = getClassGroupId(baseClassName);
687
+ if (!classGroupId) {
688
+ result = originalClassName + (result.length > 0 ? " " + result : result);
689
+ continue;
690
+ }
691
+ hasPostfixModifier = false;
692
+ }
693
+ const variantModifier = sortModifiers(modifiers).join(":");
694
+ const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier;
695
+ const classId = modifierId + classGroupId;
696
+ if (classGroupsInConflict.includes(classId)) {
697
+ continue;
698
+ }
699
+ classGroupsInConflict.push(classId);
700
+ const conflictGroups = getConflictingClassGroupIds(classGroupId, hasPostfixModifier);
701
+ for (let i = 0; i < conflictGroups.length; ++i) {
702
+ const group = conflictGroups[i];
703
+ classGroupsInConflict.push(modifierId + group);
704
+ }
705
+ result = originalClassName + (result.length > 0 ? " " + result : result);
706
+ }
707
+ return result;
708
+ };
709
+ function twJoin() {
710
+ let index = 0;
711
+ let argument;
712
+ let resolvedValue;
713
+ let string = "";
714
+ while (index < arguments.length) {
715
+ if (argument = arguments[index++]) {
716
+ if (resolvedValue = toValue(argument)) {
717
+ string && (string += " ");
718
+ string += resolvedValue;
719
+ }
720
+ }
721
+ }
722
+ return string;
723
+ }
724
+ var toValue = (mix) => {
725
+ if (typeof mix === "string") {
726
+ return mix;
727
+ }
728
+ let resolvedValue;
729
+ let string = "";
730
+ for (let k = 0; k < mix.length; k++) {
731
+ if (mix[k]) {
732
+ if (resolvedValue = toValue(mix[k])) {
733
+ string && (string += " ");
734
+ string += resolvedValue;
735
+ }
736
+ }
737
+ }
738
+ return string;
739
+ };
740
+ function createTailwindMerge(createConfigFirst, ...createConfigRest) {
741
+ let configUtils;
742
+ let cacheGet;
743
+ let cacheSet;
744
+ let functionToCall = initTailwindMerge;
745
+ function initTailwindMerge(classList) {
746
+ const config = createConfigRest.reduce((previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig), createConfigFirst());
747
+ configUtils = createConfigUtils(config);
748
+ cacheGet = configUtils.cache.get;
749
+ cacheSet = configUtils.cache.set;
750
+ functionToCall = tailwindMerge;
751
+ return tailwindMerge(classList);
752
+ }
753
+ function tailwindMerge(classList) {
754
+ const cachedResult = cacheGet(classList);
755
+ if (cachedResult) {
756
+ return cachedResult;
757
+ }
758
+ const result = mergeClassList(classList, configUtils);
759
+ cacheSet(classList, result);
760
+ return result;
761
+ }
762
+ return function callTailwindMerge() {
763
+ return functionToCall(twJoin.apply(null, arguments));
764
+ };
765
+ }
766
+ var fromTheme = (key) => {
767
+ const themeGetter = (theme) => theme[key] || [];
768
+ themeGetter.isThemeGetter = true;
769
+ return themeGetter;
770
+ };
771
+ var arbitraryValueRegex = /^\[(?:([a-z-]+):)?(.+)\]$/i;
772
+ var fractionRegex = /^\d+\/\d+$/;
773
+ var stringLengths = /* @__PURE__ */ new Set(["px", "full", "screen"]);
774
+ var tshirtUnitRegex = /^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/;
775
+ var lengthUnitRegex = /\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/;
776
+ var colorFunctionRegex = /^(rgba?|hsla?|hwb|(ok)?(lab|lch))\(.+\)$/;
777
+ var shadowRegex = /^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/;
778
+ var imageRegex = /^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/;
779
+ var isLength = (value) => isNumber(value) || stringLengths.has(value) || fractionRegex.test(value);
780
+ var isArbitraryLength = (value) => getIsArbitraryValue(value, "length", isLengthOnly);
781
+ var isNumber = (value) => Boolean(value) && !Number.isNaN(Number(value));
782
+ var isArbitraryNumber = (value) => getIsArbitraryValue(value, "number", isNumber);
783
+ var isInteger = (value) => Boolean(value) && Number.isInteger(Number(value));
784
+ var isPercent = (value) => value.endsWith("%") && isNumber(value.slice(0, -1));
785
+ var isArbitraryValue = (value) => arbitraryValueRegex.test(value);
786
+ var isTshirtSize = (value) => tshirtUnitRegex.test(value);
787
+ var sizeLabels = /* @__PURE__ */ new Set(["length", "size", "percentage"]);
788
+ var isArbitrarySize = (value) => getIsArbitraryValue(value, sizeLabels, isNever);
789
+ var isArbitraryPosition = (value) => getIsArbitraryValue(value, "position", isNever);
790
+ var imageLabels = /* @__PURE__ */ new Set(["image", "url"]);
791
+ var isArbitraryImage = (value) => getIsArbitraryValue(value, imageLabels, isImage);
792
+ var isArbitraryShadow = (value) => getIsArbitraryValue(value, "", isShadow);
793
+ var isAny = () => true;
794
+ var getIsArbitraryValue = (value, label, testValue) => {
795
+ const result = arbitraryValueRegex.exec(value);
796
+ if (result) {
797
+ if (result[1]) {
798
+ return typeof label === "string" ? result[1] === label : label.has(result[1]);
799
+ }
800
+ return testValue(result[2]);
801
+ }
802
+ return false;
803
+ };
804
+ var isLengthOnly = (value) => (
805
+ // `colorFunctionRegex` check is necessary because color functions can have percentages in them which which would be incorrectly classified as lengths.
806
+ // For example, `hsl(0 0% 0%)` would be classified as a length without this check.
807
+ // I could also use lookbehind assertion in `lengthUnitRegex` but that isn't supported widely enough.
808
+ lengthUnitRegex.test(value) && !colorFunctionRegex.test(value)
809
+ );
810
+ var isNever = () => false;
811
+ var isShadow = (value) => shadowRegex.test(value);
812
+ var isImage = (value) => imageRegex.test(value);
813
+ var getDefaultConfig = () => {
814
+ const colors = fromTheme("colors");
815
+ const spacing = fromTheme("spacing");
816
+ const blur = fromTheme("blur");
817
+ const brightness = fromTheme("brightness");
818
+ const borderColor = fromTheme("borderColor");
819
+ const borderRadius = fromTheme("borderRadius");
820
+ const borderSpacing = fromTheme("borderSpacing");
821
+ const borderWidth = fromTheme("borderWidth");
822
+ const contrast = fromTheme("contrast");
823
+ const grayscale = fromTheme("grayscale");
824
+ const hueRotate = fromTheme("hueRotate");
825
+ const invert = fromTheme("invert");
826
+ const gap = fromTheme("gap");
827
+ const gradientColorStops = fromTheme("gradientColorStops");
828
+ const gradientColorStopPositions = fromTheme("gradientColorStopPositions");
829
+ const inset = fromTheme("inset");
830
+ const margin = fromTheme("margin");
831
+ const opacity = fromTheme("opacity");
832
+ const padding = fromTheme("padding");
833
+ const saturate = fromTheme("saturate");
834
+ const scale = fromTheme("scale");
835
+ const sepia = fromTheme("sepia");
836
+ const skew = fromTheme("skew");
837
+ const space = fromTheme("space");
838
+ const translate = fromTheme("translate");
839
+ const getOverscroll = () => ["auto", "contain", "none"];
840
+ const getOverflow = () => ["auto", "hidden", "clip", "visible", "scroll"];
841
+ const getSpacingWithAutoAndArbitrary = () => ["auto", isArbitraryValue, spacing];
842
+ const getSpacingWithArbitrary = () => [isArbitraryValue, spacing];
843
+ const getLengthWithEmptyAndArbitrary = () => ["", isLength, isArbitraryLength];
844
+ const getNumberWithAutoAndArbitrary = () => ["auto", isNumber, isArbitraryValue];
845
+ const getPositions = () => ["bottom", "center", "left", "left-bottom", "left-top", "right", "right-bottom", "right-top", "top"];
846
+ const getLineStyles = () => ["solid", "dashed", "dotted", "double", "none"];
847
+ const getBlendModes = () => ["normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"];
848
+ const getAlign = () => ["start", "end", "center", "between", "around", "evenly", "stretch"];
849
+ const getZeroAndEmpty = () => ["", "0", isArbitraryValue];
850
+ const getBreaks = () => ["auto", "avoid", "all", "avoid-page", "page", "left", "right", "column"];
851
+ const getNumberAndArbitrary = () => [isNumber, isArbitraryValue];
852
+ return {
853
+ cacheSize: 500,
854
+ separator: ":",
855
+ theme: {
856
+ colors: [isAny],
857
+ spacing: [isLength, isArbitraryLength],
858
+ blur: ["none", "", isTshirtSize, isArbitraryValue],
859
+ brightness: getNumberAndArbitrary(),
860
+ borderColor: [colors],
861
+ borderRadius: ["none", "", "full", isTshirtSize, isArbitraryValue],
862
+ borderSpacing: getSpacingWithArbitrary(),
863
+ borderWidth: getLengthWithEmptyAndArbitrary(),
864
+ contrast: getNumberAndArbitrary(),
865
+ grayscale: getZeroAndEmpty(),
866
+ hueRotate: getNumberAndArbitrary(),
867
+ invert: getZeroAndEmpty(),
868
+ gap: getSpacingWithArbitrary(),
869
+ gradientColorStops: [colors],
870
+ gradientColorStopPositions: [isPercent, isArbitraryLength],
871
+ inset: getSpacingWithAutoAndArbitrary(),
872
+ margin: getSpacingWithAutoAndArbitrary(),
873
+ opacity: getNumberAndArbitrary(),
874
+ padding: getSpacingWithArbitrary(),
875
+ saturate: getNumberAndArbitrary(),
876
+ scale: getNumberAndArbitrary(),
877
+ sepia: getZeroAndEmpty(),
878
+ skew: getNumberAndArbitrary(),
879
+ space: getSpacingWithArbitrary(),
880
+ translate: getSpacingWithArbitrary()
881
+ },
882
+ classGroups: {
883
+ // Layout
884
+ /**
885
+ * Aspect Ratio
886
+ * @see https://tailwindcss.com/docs/aspect-ratio
887
+ */
888
+ aspect: [{
889
+ aspect: ["auto", "square", "video", isArbitraryValue]
890
+ }],
891
+ /**
892
+ * Container
893
+ * @see https://tailwindcss.com/docs/container
894
+ */
895
+ container: ["container"],
896
+ /**
897
+ * Columns
898
+ * @see https://tailwindcss.com/docs/columns
899
+ */
900
+ columns: [{
901
+ columns: [isTshirtSize]
902
+ }],
903
+ /**
904
+ * Break After
905
+ * @see https://tailwindcss.com/docs/break-after
906
+ */
907
+ "break-after": [{
908
+ "break-after": getBreaks()
909
+ }],
910
+ /**
911
+ * Break Before
912
+ * @see https://tailwindcss.com/docs/break-before
913
+ */
914
+ "break-before": [{
915
+ "break-before": getBreaks()
916
+ }],
917
+ /**
918
+ * Break Inside
919
+ * @see https://tailwindcss.com/docs/break-inside
920
+ */
921
+ "break-inside": [{
922
+ "break-inside": ["auto", "avoid", "avoid-page", "avoid-column"]
923
+ }],
924
+ /**
925
+ * Box Decoration Break
926
+ * @see https://tailwindcss.com/docs/box-decoration-break
927
+ */
928
+ "box-decoration": [{
929
+ "box-decoration": ["slice", "clone"]
930
+ }],
931
+ /**
932
+ * Box Sizing
933
+ * @see https://tailwindcss.com/docs/box-sizing
934
+ */
935
+ box: [{
936
+ box: ["border", "content"]
937
+ }],
938
+ /**
939
+ * Display
940
+ * @see https://tailwindcss.com/docs/display
941
+ */
942
+ display: ["block", "inline-block", "inline", "flex", "inline-flex", "table", "inline-table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row-group", "table-row", "flow-root", "grid", "inline-grid", "contents", "list-item", "hidden"],
943
+ /**
944
+ * Floats
945
+ * @see https://tailwindcss.com/docs/float
946
+ */
947
+ float: [{
948
+ float: ["right", "left", "none", "start", "end"]
949
+ }],
950
+ /**
951
+ * Clear
952
+ * @see https://tailwindcss.com/docs/clear
953
+ */
954
+ clear: [{
955
+ clear: ["left", "right", "both", "none", "start", "end"]
956
+ }],
957
+ /**
958
+ * Isolation
959
+ * @see https://tailwindcss.com/docs/isolation
960
+ */
961
+ isolation: ["isolate", "isolation-auto"],
962
+ /**
963
+ * Object Fit
964
+ * @see https://tailwindcss.com/docs/object-fit
965
+ */
966
+ "object-fit": [{
967
+ object: ["contain", "cover", "fill", "none", "scale-down"]
968
+ }],
969
+ /**
970
+ * Object Position
971
+ * @see https://tailwindcss.com/docs/object-position
972
+ */
973
+ "object-position": [{
974
+ object: [...getPositions(), isArbitraryValue]
975
+ }],
976
+ /**
977
+ * Overflow
978
+ * @see https://tailwindcss.com/docs/overflow
979
+ */
980
+ overflow: [{
981
+ overflow: getOverflow()
982
+ }],
983
+ /**
984
+ * Overflow X
985
+ * @see https://tailwindcss.com/docs/overflow
986
+ */
987
+ "overflow-x": [{
988
+ "overflow-x": getOverflow()
989
+ }],
990
+ /**
991
+ * Overflow Y
992
+ * @see https://tailwindcss.com/docs/overflow
993
+ */
994
+ "overflow-y": [{
995
+ "overflow-y": getOverflow()
996
+ }],
997
+ /**
998
+ * Overscroll Behavior
999
+ * @see https://tailwindcss.com/docs/overscroll-behavior
1000
+ */
1001
+ overscroll: [{
1002
+ overscroll: getOverscroll()
1003
+ }],
1004
+ /**
1005
+ * Overscroll Behavior X
1006
+ * @see https://tailwindcss.com/docs/overscroll-behavior
1007
+ */
1008
+ "overscroll-x": [{
1009
+ "overscroll-x": getOverscroll()
1010
+ }],
1011
+ /**
1012
+ * Overscroll Behavior Y
1013
+ * @see https://tailwindcss.com/docs/overscroll-behavior
1014
+ */
1015
+ "overscroll-y": [{
1016
+ "overscroll-y": getOverscroll()
1017
+ }],
1018
+ /**
1019
+ * Position
1020
+ * @see https://tailwindcss.com/docs/position
1021
+ */
1022
+ position: ["static", "fixed", "absolute", "relative", "sticky"],
1023
+ /**
1024
+ * Top / Right / Bottom / Left
1025
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1026
+ */
1027
+ inset: [{
1028
+ inset: [inset]
1029
+ }],
1030
+ /**
1031
+ * Right / Left
1032
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1033
+ */
1034
+ "inset-x": [{
1035
+ "inset-x": [inset]
1036
+ }],
1037
+ /**
1038
+ * Top / Bottom
1039
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1040
+ */
1041
+ "inset-y": [{
1042
+ "inset-y": [inset]
1043
+ }],
1044
+ /**
1045
+ * Start
1046
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1047
+ */
1048
+ start: [{
1049
+ start: [inset]
1050
+ }],
1051
+ /**
1052
+ * End
1053
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1054
+ */
1055
+ end: [{
1056
+ end: [inset]
1057
+ }],
1058
+ /**
1059
+ * Top
1060
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1061
+ */
1062
+ top: [{
1063
+ top: [inset]
1064
+ }],
1065
+ /**
1066
+ * Right
1067
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1068
+ */
1069
+ right: [{
1070
+ right: [inset]
1071
+ }],
1072
+ /**
1073
+ * Bottom
1074
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1075
+ */
1076
+ bottom: [{
1077
+ bottom: [inset]
1078
+ }],
1079
+ /**
1080
+ * Left
1081
+ * @see https://tailwindcss.com/docs/top-right-bottom-left
1082
+ */
1083
+ left: [{
1084
+ left: [inset]
1085
+ }],
1086
+ /**
1087
+ * Visibility
1088
+ * @see https://tailwindcss.com/docs/visibility
1089
+ */
1090
+ visibility: ["visible", "invisible", "collapse"],
1091
+ /**
1092
+ * Z-Index
1093
+ * @see https://tailwindcss.com/docs/z-index
1094
+ */
1095
+ z: [{
1096
+ z: ["auto", isInteger, isArbitraryValue]
1097
+ }],
1098
+ // Flexbox and Grid
1099
+ /**
1100
+ * Flex Basis
1101
+ * @see https://tailwindcss.com/docs/flex-basis
1102
+ */
1103
+ basis: [{
1104
+ basis: getSpacingWithAutoAndArbitrary()
1105
+ }],
1106
+ /**
1107
+ * Flex Direction
1108
+ * @see https://tailwindcss.com/docs/flex-direction
1109
+ */
1110
+ "flex-direction": [{
1111
+ flex: ["row", "row-reverse", "col", "col-reverse"]
1112
+ }],
1113
+ /**
1114
+ * Flex Wrap
1115
+ * @see https://tailwindcss.com/docs/flex-wrap
1116
+ */
1117
+ "flex-wrap": [{
1118
+ flex: ["wrap", "wrap-reverse", "nowrap"]
1119
+ }],
1120
+ /**
1121
+ * Flex
1122
+ * @see https://tailwindcss.com/docs/flex
1123
+ */
1124
+ flex: [{
1125
+ flex: ["1", "auto", "initial", "none", isArbitraryValue]
1126
+ }],
1127
+ /**
1128
+ * Flex Grow
1129
+ * @see https://tailwindcss.com/docs/flex-grow
1130
+ */
1131
+ grow: [{
1132
+ grow: getZeroAndEmpty()
1133
+ }],
1134
+ /**
1135
+ * Flex Shrink
1136
+ * @see https://tailwindcss.com/docs/flex-shrink
1137
+ */
1138
+ shrink: [{
1139
+ shrink: getZeroAndEmpty()
1140
+ }],
1141
+ /**
1142
+ * Order
1143
+ * @see https://tailwindcss.com/docs/order
1144
+ */
1145
+ order: [{
1146
+ order: ["first", "last", "none", isInteger, isArbitraryValue]
1147
+ }],
1148
+ /**
1149
+ * Grid Template Columns
1150
+ * @see https://tailwindcss.com/docs/grid-template-columns
1151
+ */
1152
+ "grid-cols": [{
1153
+ "grid-cols": [isAny]
1154
+ }],
1155
+ /**
1156
+ * Grid Column Start / End
1157
+ * @see https://tailwindcss.com/docs/grid-column
1158
+ */
1159
+ "col-start-end": [{
1160
+ col: ["auto", {
1161
+ span: ["full", isInteger, isArbitraryValue]
1162
+ }, isArbitraryValue]
1163
+ }],
1164
+ /**
1165
+ * Grid Column Start
1166
+ * @see https://tailwindcss.com/docs/grid-column
1167
+ */
1168
+ "col-start": [{
1169
+ "col-start": getNumberWithAutoAndArbitrary()
1170
+ }],
1171
+ /**
1172
+ * Grid Column End
1173
+ * @see https://tailwindcss.com/docs/grid-column
1174
+ */
1175
+ "col-end": [{
1176
+ "col-end": getNumberWithAutoAndArbitrary()
1177
+ }],
1178
+ /**
1179
+ * Grid Template Rows
1180
+ * @see https://tailwindcss.com/docs/grid-template-rows
1181
+ */
1182
+ "grid-rows": [{
1183
+ "grid-rows": [isAny]
1184
+ }],
1185
+ /**
1186
+ * Grid Row Start / End
1187
+ * @see https://tailwindcss.com/docs/grid-row
1188
+ */
1189
+ "row-start-end": [{
1190
+ row: ["auto", {
1191
+ span: [isInteger, isArbitraryValue]
1192
+ }, isArbitraryValue]
1193
+ }],
1194
+ /**
1195
+ * Grid Row Start
1196
+ * @see https://tailwindcss.com/docs/grid-row
1197
+ */
1198
+ "row-start": [{
1199
+ "row-start": getNumberWithAutoAndArbitrary()
1200
+ }],
1201
+ /**
1202
+ * Grid Row End
1203
+ * @see https://tailwindcss.com/docs/grid-row
1204
+ */
1205
+ "row-end": [{
1206
+ "row-end": getNumberWithAutoAndArbitrary()
1207
+ }],
1208
+ /**
1209
+ * Grid Auto Flow
1210
+ * @see https://tailwindcss.com/docs/grid-auto-flow
1211
+ */
1212
+ "grid-flow": [{
1213
+ "grid-flow": ["row", "col", "dense", "row-dense", "col-dense"]
1214
+ }],
1215
+ /**
1216
+ * Grid Auto Columns
1217
+ * @see https://tailwindcss.com/docs/grid-auto-columns
1218
+ */
1219
+ "auto-cols": [{
1220
+ "auto-cols": ["auto", "min", "max", "fr", isArbitraryValue]
1221
+ }],
1222
+ /**
1223
+ * Grid Auto Rows
1224
+ * @see https://tailwindcss.com/docs/grid-auto-rows
1225
+ */
1226
+ "auto-rows": [{
1227
+ "auto-rows": ["auto", "min", "max", "fr", isArbitraryValue]
1228
+ }],
1229
+ /**
1230
+ * Gap
1231
+ * @see https://tailwindcss.com/docs/gap
1232
+ */
1233
+ gap: [{
1234
+ gap: [gap]
1235
+ }],
1236
+ /**
1237
+ * Gap X
1238
+ * @see https://tailwindcss.com/docs/gap
1239
+ */
1240
+ "gap-x": [{
1241
+ "gap-x": [gap]
1242
+ }],
1243
+ /**
1244
+ * Gap Y
1245
+ * @see https://tailwindcss.com/docs/gap
1246
+ */
1247
+ "gap-y": [{
1248
+ "gap-y": [gap]
1249
+ }],
1250
+ /**
1251
+ * Justify Content
1252
+ * @see https://tailwindcss.com/docs/justify-content
1253
+ */
1254
+ "justify-content": [{
1255
+ justify: ["normal", ...getAlign()]
1256
+ }],
1257
+ /**
1258
+ * Justify Items
1259
+ * @see https://tailwindcss.com/docs/justify-items
1260
+ */
1261
+ "justify-items": [{
1262
+ "justify-items": ["start", "end", "center", "stretch"]
1263
+ }],
1264
+ /**
1265
+ * Justify Self
1266
+ * @see https://tailwindcss.com/docs/justify-self
1267
+ */
1268
+ "justify-self": [{
1269
+ "justify-self": ["auto", "start", "end", "center", "stretch"]
1270
+ }],
1271
+ /**
1272
+ * Align Content
1273
+ * @see https://tailwindcss.com/docs/align-content
1274
+ */
1275
+ "align-content": [{
1276
+ content: ["normal", ...getAlign(), "baseline"]
1277
+ }],
1278
+ /**
1279
+ * Align Items
1280
+ * @see https://tailwindcss.com/docs/align-items
1281
+ */
1282
+ "align-items": [{
1283
+ items: ["start", "end", "center", "baseline", "stretch"]
1284
+ }],
1285
+ /**
1286
+ * Align Self
1287
+ * @see https://tailwindcss.com/docs/align-self
1288
+ */
1289
+ "align-self": [{
1290
+ self: ["auto", "start", "end", "center", "stretch", "baseline"]
1291
+ }],
1292
+ /**
1293
+ * Place Content
1294
+ * @see https://tailwindcss.com/docs/place-content
1295
+ */
1296
+ "place-content": [{
1297
+ "place-content": [...getAlign(), "baseline"]
1298
+ }],
1299
+ /**
1300
+ * Place Items
1301
+ * @see https://tailwindcss.com/docs/place-items
1302
+ */
1303
+ "place-items": [{
1304
+ "place-items": ["start", "end", "center", "baseline", "stretch"]
1305
+ }],
1306
+ /**
1307
+ * Place Self
1308
+ * @see https://tailwindcss.com/docs/place-self
1309
+ */
1310
+ "place-self": [{
1311
+ "place-self": ["auto", "start", "end", "center", "stretch"]
1312
+ }],
1313
+ // Spacing
1314
+ /**
1315
+ * Padding
1316
+ * @see https://tailwindcss.com/docs/padding
1317
+ */
1318
+ p: [{
1319
+ p: [padding]
1320
+ }],
1321
+ /**
1322
+ * Padding X
1323
+ * @see https://tailwindcss.com/docs/padding
1324
+ */
1325
+ px: [{
1326
+ px: [padding]
1327
+ }],
1328
+ /**
1329
+ * Padding Y
1330
+ * @see https://tailwindcss.com/docs/padding
1331
+ */
1332
+ py: [{
1333
+ py: [padding]
1334
+ }],
1335
+ /**
1336
+ * Padding Start
1337
+ * @see https://tailwindcss.com/docs/padding
1338
+ */
1339
+ ps: [{
1340
+ ps: [padding]
1341
+ }],
1342
+ /**
1343
+ * Padding End
1344
+ * @see https://tailwindcss.com/docs/padding
1345
+ */
1346
+ pe: [{
1347
+ pe: [padding]
1348
+ }],
1349
+ /**
1350
+ * Padding Top
1351
+ * @see https://tailwindcss.com/docs/padding
1352
+ */
1353
+ pt: [{
1354
+ pt: [padding]
1355
+ }],
1356
+ /**
1357
+ * Padding Right
1358
+ * @see https://tailwindcss.com/docs/padding
1359
+ */
1360
+ pr: [{
1361
+ pr: [padding]
1362
+ }],
1363
+ /**
1364
+ * Padding Bottom
1365
+ * @see https://tailwindcss.com/docs/padding
1366
+ */
1367
+ pb: [{
1368
+ pb: [padding]
1369
+ }],
1370
+ /**
1371
+ * Padding Left
1372
+ * @see https://tailwindcss.com/docs/padding
1373
+ */
1374
+ pl: [{
1375
+ pl: [padding]
1376
+ }],
1377
+ /**
1378
+ * Margin
1379
+ * @see https://tailwindcss.com/docs/margin
1380
+ */
1381
+ m: [{
1382
+ m: [margin]
1383
+ }],
1384
+ /**
1385
+ * Margin X
1386
+ * @see https://tailwindcss.com/docs/margin
1387
+ */
1388
+ mx: [{
1389
+ mx: [margin]
1390
+ }],
1391
+ /**
1392
+ * Margin Y
1393
+ * @see https://tailwindcss.com/docs/margin
1394
+ */
1395
+ my: [{
1396
+ my: [margin]
1397
+ }],
1398
+ /**
1399
+ * Margin Start
1400
+ * @see https://tailwindcss.com/docs/margin
1401
+ */
1402
+ ms: [{
1403
+ ms: [margin]
1404
+ }],
1405
+ /**
1406
+ * Margin End
1407
+ * @see https://tailwindcss.com/docs/margin
1408
+ */
1409
+ me: [{
1410
+ me: [margin]
1411
+ }],
1412
+ /**
1413
+ * Margin Top
1414
+ * @see https://tailwindcss.com/docs/margin
1415
+ */
1416
+ mt: [{
1417
+ mt: [margin]
1418
+ }],
1419
+ /**
1420
+ * Margin Right
1421
+ * @see https://tailwindcss.com/docs/margin
1422
+ */
1423
+ mr: [{
1424
+ mr: [margin]
1425
+ }],
1426
+ /**
1427
+ * Margin Bottom
1428
+ * @see https://tailwindcss.com/docs/margin
1429
+ */
1430
+ mb: [{
1431
+ mb: [margin]
1432
+ }],
1433
+ /**
1434
+ * Margin Left
1435
+ * @see https://tailwindcss.com/docs/margin
1436
+ */
1437
+ ml: [{
1438
+ ml: [margin]
1439
+ }],
1440
+ /**
1441
+ * Space Between X
1442
+ * @see https://tailwindcss.com/docs/space
1443
+ */
1444
+ "space-x": [{
1445
+ "space-x": [space]
1446
+ }],
1447
+ /**
1448
+ * Space Between X Reverse
1449
+ * @see https://tailwindcss.com/docs/space
1450
+ */
1451
+ "space-x-reverse": ["space-x-reverse"],
1452
+ /**
1453
+ * Space Between Y
1454
+ * @see https://tailwindcss.com/docs/space
1455
+ */
1456
+ "space-y": [{
1457
+ "space-y": [space]
1458
+ }],
1459
+ /**
1460
+ * Space Between Y Reverse
1461
+ * @see https://tailwindcss.com/docs/space
1462
+ */
1463
+ "space-y-reverse": ["space-y-reverse"],
1464
+ // Sizing
1465
+ /**
1466
+ * Width
1467
+ * @see https://tailwindcss.com/docs/width
1468
+ */
1469
+ w: [{
1470
+ w: ["auto", "min", "max", "fit", "svw", "lvw", "dvw", isArbitraryValue, spacing]
1471
+ }],
1472
+ /**
1473
+ * Min-Width
1474
+ * @see https://tailwindcss.com/docs/min-width
1475
+ */
1476
+ "min-w": [{
1477
+ "min-w": [isArbitraryValue, spacing, "min", "max", "fit"]
1478
+ }],
1479
+ /**
1480
+ * Max-Width
1481
+ * @see https://tailwindcss.com/docs/max-width
1482
+ */
1483
+ "max-w": [{
1484
+ "max-w": [isArbitraryValue, spacing, "none", "full", "min", "max", "fit", "prose", {
1485
+ screen: [isTshirtSize]
1486
+ }, isTshirtSize]
1487
+ }],
1488
+ /**
1489
+ * Height
1490
+ * @see https://tailwindcss.com/docs/height
1491
+ */
1492
+ h: [{
1493
+ h: [isArbitraryValue, spacing, "auto", "min", "max", "fit", "svh", "lvh", "dvh"]
1494
+ }],
1495
+ /**
1496
+ * Min-Height
1497
+ * @see https://tailwindcss.com/docs/min-height
1498
+ */
1499
+ "min-h": [{
1500
+ "min-h": [isArbitraryValue, spacing, "min", "max", "fit", "svh", "lvh", "dvh"]
1501
+ }],
1502
+ /**
1503
+ * Max-Height
1504
+ * @see https://tailwindcss.com/docs/max-height
1505
+ */
1506
+ "max-h": [{
1507
+ "max-h": [isArbitraryValue, spacing, "min", "max", "fit", "svh", "lvh", "dvh"]
1508
+ }],
1509
+ /**
1510
+ * Size
1511
+ * @see https://tailwindcss.com/docs/size
1512
+ */
1513
+ size: [{
1514
+ size: [isArbitraryValue, spacing, "auto", "min", "max", "fit"]
1515
+ }],
1516
+ // Typography
1517
+ /**
1518
+ * Font Size
1519
+ * @see https://tailwindcss.com/docs/font-size
1520
+ */
1521
+ "font-size": [{
1522
+ text: ["base", isTshirtSize, isArbitraryLength]
1523
+ }],
1524
+ /**
1525
+ * Font Smoothing
1526
+ * @see https://tailwindcss.com/docs/font-smoothing
1527
+ */
1528
+ "font-smoothing": ["antialiased", "subpixel-antialiased"],
1529
+ /**
1530
+ * Font Style
1531
+ * @see https://tailwindcss.com/docs/font-style
1532
+ */
1533
+ "font-style": ["italic", "not-italic"],
1534
+ /**
1535
+ * Font Weight
1536
+ * @see https://tailwindcss.com/docs/font-weight
1537
+ */
1538
+ "font-weight": [{
1539
+ font: ["thin", "extralight", "light", "normal", "medium", "semibold", "bold", "extrabold", "black", isArbitraryNumber]
1540
+ }],
1541
+ /**
1542
+ * Font Family
1543
+ * @see https://tailwindcss.com/docs/font-family
1544
+ */
1545
+ "font-family": [{
1546
+ font: [isAny]
1547
+ }],
1548
+ /**
1549
+ * Font Variant Numeric
1550
+ * @see https://tailwindcss.com/docs/font-variant-numeric
1551
+ */
1552
+ "fvn-normal": ["normal-nums"],
1553
+ /**
1554
+ * Font Variant Numeric
1555
+ * @see https://tailwindcss.com/docs/font-variant-numeric
1556
+ */
1557
+ "fvn-ordinal": ["ordinal"],
1558
+ /**
1559
+ * Font Variant Numeric
1560
+ * @see https://tailwindcss.com/docs/font-variant-numeric
1561
+ */
1562
+ "fvn-slashed-zero": ["slashed-zero"],
1563
+ /**
1564
+ * Font Variant Numeric
1565
+ * @see https://tailwindcss.com/docs/font-variant-numeric
1566
+ */
1567
+ "fvn-figure": ["lining-nums", "oldstyle-nums"],
1568
+ /**
1569
+ * Font Variant Numeric
1570
+ * @see https://tailwindcss.com/docs/font-variant-numeric
1571
+ */
1572
+ "fvn-spacing": ["proportional-nums", "tabular-nums"],
1573
+ /**
1574
+ * Font Variant Numeric
1575
+ * @see https://tailwindcss.com/docs/font-variant-numeric
1576
+ */
1577
+ "fvn-fraction": ["diagonal-fractions", "stacked-fractions"],
1578
+ /**
1579
+ * Letter Spacing
1580
+ * @see https://tailwindcss.com/docs/letter-spacing
1581
+ */
1582
+ tracking: [{
1583
+ tracking: ["tighter", "tight", "normal", "wide", "wider", "widest", isArbitraryValue]
1584
+ }],
1585
+ /**
1586
+ * Line Clamp
1587
+ * @see https://tailwindcss.com/docs/line-clamp
1588
+ */
1589
+ "line-clamp": [{
1590
+ "line-clamp": ["none", isNumber, isArbitraryNumber]
1591
+ }],
1592
+ /**
1593
+ * Line Height
1594
+ * @see https://tailwindcss.com/docs/line-height
1595
+ */
1596
+ leading: [{
1597
+ leading: ["none", "tight", "snug", "normal", "relaxed", "loose", isLength, isArbitraryValue]
1598
+ }],
1599
+ /**
1600
+ * List Style Image
1601
+ * @see https://tailwindcss.com/docs/list-style-image
1602
+ */
1603
+ "list-image": [{
1604
+ "list-image": ["none", isArbitraryValue]
1605
+ }],
1606
+ /**
1607
+ * List Style Type
1608
+ * @see https://tailwindcss.com/docs/list-style-type
1609
+ */
1610
+ "list-style-type": [{
1611
+ list: ["none", "disc", "decimal", isArbitraryValue]
1612
+ }],
1613
+ /**
1614
+ * List Style Position
1615
+ * @see https://tailwindcss.com/docs/list-style-position
1616
+ */
1617
+ "list-style-position": [{
1618
+ list: ["inside", "outside"]
1619
+ }],
1620
+ /**
1621
+ * Placeholder Color
1622
+ * @deprecated since Tailwind CSS v3.0.0
1623
+ * @see https://tailwindcss.com/docs/placeholder-color
1624
+ */
1625
+ "placeholder-color": [{
1626
+ placeholder: [colors]
1627
+ }],
1628
+ /**
1629
+ * Placeholder Opacity
1630
+ * @see https://tailwindcss.com/docs/placeholder-opacity
1631
+ */
1632
+ "placeholder-opacity": [{
1633
+ "placeholder-opacity": [opacity]
1634
+ }],
1635
+ /**
1636
+ * Text Alignment
1637
+ * @see https://tailwindcss.com/docs/text-align
1638
+ */
1639
+ "text-alignment": [{
1640
+ text: ["left", "center", "right", "justify", "start", "end"]
1641
+ }],
1642
+ /**
1643
+ * Text Color
1644
+ * @see https://tailwindcss.com/docs/text-color
1645
+ */
1646
+ "text-color": [{
1647
+ text: [colors]
1648
+ }],
1649
+ /**
1650
+ * Text Opacity
1651
+ * @see https://tailwindcss.com/docs/text-opacity
1652
+ */
1653
+ "text-opacity": [{
1654
+ "text-opacity": [opacity]
1655
+ }],
1656
+ /**
1657
+ * Text Decoration
1658
+ * @see https://tailwindcss.com/docs/text-decoration
1659
+ */
1660
+ "text-decoration": ["underline", "overline", "line-through", "no-underline"],
1661
+ /**
1662
+ * Text Decoration Style
1663
+ * @see https://tailwindcss.com/docs/text-decoration-style
1664
+ */
1665
+ "text-decoration-style": [{
1666
+ decoration: [...getLineStyles(), "wavy"]
1667
+ }],
1668
+ /**
1669
+ * Text Decoration Thickness
1670
+ * @see https://tailwindcss.com/docs/text-decoration-thickness
1671
+ */
1672
+ "text-decoration-thickness": [{
1673
+ decoration: ["auto", "from-font", isLength, isArbitraryLength]
1674
+ }],
1675
+ /**
1676
+ * Text Underline Offset
1677
+ * @see https://tailwindcss.com/docs/text-underline-offset
1678
+ */
1679
+ "underline-offset": [{
1680
+ "underline-offset": ["auto", isLength, isArbitraryValue]
1681
+ }],
1682
+ /**
1683
+ * Text Decoration Color
1684
+ * @see https://tailwindcss.com/docs/text-decoration-color
1685
+ */
1686
+ "text-decoration-color": [{
1687
+ decoration: [colors]
1688
+ }],
1689
+ /**
1690
+ * Text Transform
1691
+ * @see https://tailwindcss.com/docs/text-transform
1692
+ */
1693
+ "text-transform": ["uppercase", "lowercase", "capitalize", "normal-case"],
1694
+ /**
1695
+ * Text Overflow
1696
+ * @see https://tailwindcss.com/docs/text-overflow
1697
+ */
1698
+ "text-overflow": ["truncate", "text-ellipsis", "text-clip"],
1699
+ /**
1700
+ * Text Wrap
1701
+ * @see https://tailwindcss.com/docs/text-wrap
1702
+ */
1703
+ "text-wrap": [{
1704
+ text: ["wrap", "nowrap", "balance", "pretty"]
1705
+ }],
1706
+ /**
1707
+ * Text Indent
1708
+ * @see https://tailwindcss.com/docs/text-indent
1709
+ */
1710
+ indent: [{
1711
+ indent: getSpacingWithArbitrary()
1712
+ }],
1713
+ /**
1714
+ * Vertical Alignment
1715
+ * @see https://tailwindcss.com/docs/vertical-align
1716
+ */
1717
+ "vertical-align": [{
1718
+ align: ["baseline", "top", "middle", "bottom", "text-top", "text-bottom", "sub", "super", isArbitraryValue]
1719
+ }],
1720
+ /**
1721
+ * Whitespace
1722
+ * @see https://tailwindcss.com/docs/whitespace
1723
+ */
1724
+ whitespace: [{
1725
+ whitespace: ["normal", "nowrap", "pre", "pre-line", "pre-wrap", "break-spaces"]
1726
+ }],
1727
+ /**
1728
+ * Word Break
1729
+ * @see https://tailwindcss.com/docs/word-break
1730
+ */
1731
+ break: [{
1732
+ break: ["normal", "words", "all", "keep"]
1733
+ }],
1734
+ /**
1735
+ * Hyphens
1736
+ * @see https://tailwindcss.com/docs/hyphens
1737
+ */
1738
+ hyphens: [{
1739
+ hyphens: ["none", "manual", "auto"]
1740
+ }],
1741
+ /**
1742
+ * Content
1743
+ * @see https://tailwindcss.com/docs/content
1744
+ */
1745
+ content: [{
1746
+ content: ["none", isArbitraryValue]
1747
+ }],
1748
+ // Backgrounds
1749
+ /**
1750
+ * Background Attachment
1751
+ * @see https://tailwindcss.com/docs/background-attachment
1752
+ */
1753
+ "bg-attachment": [{
1754
+ bg: ["fixed", "local", "scroll"]
1755
+ }],
1756
+ /**
1757
+ * Background Clip
1758
+ * @see https://tailwindcss.com/docs/background-clip
1759
+ */
1760
+ "bg-clip": [{
1761
+ "bg-clip": ["border", "padding", "content", "text"]
1762
+ }],
1763
+ /**
1764
+ * Background Opacity
1765
+ * @deprecated since Tailwind CSS v3.0.0
1766
+ * @see https://tailwindcss.com/docs/background-opacity
1767
+ */
1768
+ "bg-opacity": [{
1769
+ "bg-opacity": [opacity]
1770
+ }],
1771
+ /**
1772
+ * Background Origin
1773
+ * @see https://tailwindcss.com/docs/background-origin
1774
+ */
1775
+ "bg-origin": [{
1776
+ "bg-origin": ["border", "padding", "content"]
1777
+ }],
1778
+ /**
1779
+ * Background Position
1780
+ * @see https://tailwindcss.com/docs/background-position
1781
+ */
1782
+ "bg-position": [{
1783
+ bg: [...getPositions(), isArbitraryPosition]
1784
+ }],
1785
+ /**
1786
+ * Background Repeat
1787
+ * @see https://tailwindcss.com/docs/background-repeat
1788
+ */
1789
+ "bg-repeat": [{
1790
+ bg: ["no-repeat", {
1791
+ repeat: ["", "x", "y", "round", "space"]
1792
+ }]
1793
+ }],
1794
+ /**
1795
+ * Background Size
1796
+ * @see https://tailwindcss.com/docs/background-size
1797
+ */
1798
+ "bg-size": [{
1799
+ bg: ["auto", "cover", "contain", isArbitrarySize]
1800
+ }],
1801
+ /**
1802
+ * Background Image
1803
+ * @see https://tailwindcss.com/docs/background-image
1804
+ */
1805
+ "bg-image": [{
1806
+ bg: ["none", {
1807
+ "gradient-to": ["t", "tr", "r", "br", "b", "bl", "l", "tl"]
1808
+ }, isArbitraryImage]
1809
+ }],
1810
+ /**
1811
+ * Background Color
1812
+ * @see https://tailwindcss.com/docs/background-color
1813
+ */
1814
+ "bg-color": [{
1815
+ bg: [colors]
1816
+ }],
1817
+ /**
1818
+ * Gradient Color Stops From Position
1819
+ * @see https://tailwindcss.com/docs/gradient-color-stops
1820
+ */
1821
+ "gradient-from-pos": [{
1822
+ from: [gradientColorStopPositions]
1823
+ }],
1824
+ /**
1825
+ * Gradient Color Stops Via Position
1826
+ * @see https://tailwindcss.com/docs/gradient-color-stops
1827
+ */
1828
+ "gradient-via-pos": [{
1829
+ via: [gradientColorStopPositions]
1830
+ }],
1831
+ /**
1832
+ * Gradient Color Stops To Position
1833
+ * @see https://tailwindcss.com/docs/gradient-color-stops
1834
+ */
1835
+ "gradient-to-pos": [{
1836
+ to: [gradientColorStopPositions]
1837
+ }],
1838
+ /**
1839
+ * Gradient Color Stops From
1840
+ * @see https://tailwindcss.com/docs/gradient-color-stops
1841
+ */
1842
+ "gradient-from": [{
1843
+ from: [gradientColorStops]
1844
+ }],
1845
+ /**
1846
+ * Gradient Color Stops Via
1847
+ * @see https://tailwindcss.com/docs/gradient-color-stops
1848
+ */
1849
+ "gradient-via": [{
1850
+ via: [gradientColorStops]
1851
+ }],
1852
+ /**
1853
+ * Gradient Color Stops To
1854
+ * @see https://tailwindcss.com/docs/gradient-color-stops
1855
+ */
1856
+ "gradient-to": [{
1857
+ to: [gradientColorStops]
1858
+ }],
1859
+ // Borders
1860
+ /**
1861
+ * Border Radius
1862
+ * @see https://tailwindcss.com/docs/border-radius
1863
+ */
1864
+ rounded: [{
1865
+ rounded: [borderRadius]
1866
+ }],
1867
+ /**
1868
+ * Border Radius Start
1869
+ * @see https://tailwindcss.com/docs/border-radius
1870
+ */
1871
+ "rounded-s": [{
1872
+ "rounded-s": [borderRadius]
1873
+ }],
1874
+ /**
1875
+ * Border Radius End
1876
+ * @see https://tailwindcss.com/docs/border-radius
1877
+ */
1878
+ "rounded-e": [{
1879
+ "rounded-e": [borderRadius]
1880
+ }],
1881
+ /**
1882
+ * Border Radius Top
1883
+ * @see https://tailwindcss.com/docs/border-radius
1884
+ */
1885
+ "rounded-t": [{
1886
+ "rounded-t": [borderRadius]
1887
+ }],
1888
+ /**
1889
+ * Border Radius Right
1890
+ * @see https://tailwindcss.com/docs/border-radius
1891
+ */
1892
+ "rounded-r": [{
1893
+ "rounded-r": [borderRadius]
1894
+ }],
1895
+ /**
1896
+ * Border Radius Bottom
1897
+ * @see https://tailwindcss.com/docs/border-radius
1898
+ */
1899
+ "rounded-b": [{
1900
+ "rounded-b": [borderRadius]
1901
+ }],
1902
+ /**
1903
+ * Border Radius Left
1904
+ * @see https://tailwindcss.com/docs/border-radius
1905
+ */
1906
+ "rounded-l": [{
1907
+ "rounded-l": [borderRadius]
1908
+ }],
1909
+ /**
1910
+ * Border Radius Start Start
1911
+ * @see https://tailwindcss.com/docs/border-radius
1912
+ */
1913
+ "rounded-ss": [{
1914
+ "rounded-ss": [borderRadius]
1915
+ }],
1916
+ /**
1917
+ * Border Radius Start End
1918
+ * @see https://tailwindcss.com/docs/border-radius
1919
+ */
1920
+ "rounded-se": [{
1921
+ "rounded-se": [borderRadius]
1922
+ }],
1923
+ /**
1924
+ * Border Radius End End
1925
+ * @see https://tailwindcss.com/docs/border-radius
1926
+ */
1927
+ "rounded-ee": [{
1928
+ "rounded-ee": [borderRadius]
1929
+ }],
1930
+ /**
1931
+ * Border Radius End Start
1932
+ * @see https://tailwindcss.com/docs/border-radius
1933
+ */
1934
+ "rounded-es": [{
1935
+ "rounded-es": [borderRadius]
1936
+ }],
1937
+ /**
1938
+ * Border Radius Top Left
1939
+ * @see https://tailwindcss.com/docs/border-radius
1940
+ */
1941
+ "rounded-tl": [{
1942
+ "rounded-tl": [borderRadius]
1943
+ }],
1944
+ /**
1945
+ * Border Radius Top Right
1946
+ * @see https://tailwindcss.com/docs/border-radius
1947
+ */
1948
+ "rounded-tr": [{
1949
+ "rounded-tr": [borderRadius]
1950
+ }],
1951
+ /**
1952
+ * Border Radius Bottom Right
1953
+ * @see https://tailwindcss.com/docs/border-radius
1954
+ */
1955
+ "rounded-br": [{
1956
+ "rounded-br": [borderRadius]
1957
+ }],
1958
+ /**
1959
+ * Border Radius Bottom Left
1960
+ * @see https://tailwindcss.com/docs/border-radius
1961
+ */
1962
+ "rounded-bl": [{
1963
+ "rounded-bl": [borderRadius]
1964
+ }],
1965
+ /**
1966
+ * Border Width
1967
+ * @see https://tailwindcss.com/docs/border-width
1968
+ */
1969
+ "border-w": [{
1970
+ border: [borderWidth]
1971
+ }],
1972
+ /**
1973
+ * Border Width X
1974
+ * @see https://tailwindcss.com/docs/border-width
1975
+ */
1976
+ "border-w-x": [{
1977
+ "border-x": [borderWidth]
1978
+ }],
1979
+ /**
1980
+ * Border Width Y
1981
+ * @see https://tailwindcss.com/docs/border-width
1982
+ */
1983
+ "border-w-y": [{
1984
+ "border-y": [borderWidth]
1985
+ }],
1986
+ /**
1987
+ * Border Width Start
1988
+ * @see https://tailwindcss.com/docs/border-width
1989
+ */
1990
+ "border-w-s": [{
1991
+ "border-s": [borderWidth]
1992
+ }],
1993
+ /**
1994
+ * Border Width End
1995
+ * @see https://tailwindcss.com/docs/border-width
1996
+ */
1997
+ "border-w-e": [{
1998
+ "border-e": [borderWidth]
1999
+ }],
2000
+ /**
2001
+ * Border Width Top
2002
+ * @see https://tailwindcss.com/docs/border-width
2003
+ */
2004
+ "border-w-t": [{
2005
+ "border-t": [borderWidth]
2006
+ }],
2007
+ /**
2008
+ * Border Width Right
2009
+ * @see https://tailwindcss.com/docs/border-width
2010
+ */
2011
+ "border-w-r": [{
2012
+ "border-r": [borderWidth]
2013
+ }],
2014
+ /**
2015
+ * Border Width Bottom
2016
+ * @see https://tailwindcss.com/docs/border-width
2017
+ */
2018
+ "border-w-b": [{
2019
+ "border-b": [borderWidth]
2020
+ }],
2021
+ /**
2022
+ * Border Width Left
2023
+ * @see https://tailwindcss.com/docs/border-width
2024
+ */
2025
+ "border-w-l": [{
2026
+ "border-l": [borderWidth]
2027
+ }],
2028
+ /**
2029
+ * Border Opacity
2030
+ * @see https://tailwindcss.com/docs/border-opacity
2031
+ */
2032
+ "border-opacity": [{
2033
+ "border-opacity": [opacity]
2034
+ }],
2035
+ /**
2036
+ * Border Style
2037
+ * @see https://tailwindcss.com/docs/border-style
2038
+ */
2039
+ "border-style": [{
2040
+ border: [...getLineStyles(), "hidden"]
2041
+ }],
2042
+ /**
2043
+ * Divide Width X
2044
+ * @see https://tailwindcss.com/docs/divide-width
2045
+ */
2046
+ "divide-x": [{
2047
+ "divide-x": [borderWidth]
2048
+ }],
2049
+ /**
2050
+ * Divide Width X Reverse
2051
+ * @see https://tailwindcss.com/docs/divide-width
2052
+ */
2053
+ "divide-x-reverse": ["divide-x-reverse"],
2054
+ /**
2055
+ * Divide Width Y
2056
+ * @see https://tailwindcss.com/docs/divide-width
2057
+ */
2058
+ "divide-y": [{
2059
+ "divide-y": [borderWidth]
2060
+ }],
2061
+ /**
2062
+ * Divide Width Y Reverse
2063
+ * @see https://tailwindcss.com/docs/divide-width
2064
+ */
2065
+ "divide-y-reverse": ["divide-y-reverse"],
2066
+ /**
2067
+ * Divide Opacity
2068
+ * @see https://tailwindcss.com/docs/divide-opacity
2069
+ */
2070
+ "divide-opacity": [{
2071
+ "divide-opacity": [opacity]
2072
+ }],
2073
+ /**
2074
+ * Divide Style
2075
+ * @see https://tailwindcss.com/docs/divide-style
2076
+ */
2077
+ "divide-style": [{
2078
+ divide: getLineStyles()
2079
+ }],
2080
+ /**
2081
+ * Border Color
2082
+ * @see https://tailwindcss.com/docs/border-color
2083
+ */
2084
+ "border-color": [{
2085
+ border: [borderColor]
2086
+ }],
2087
+ /**
2088
+ * Border Color X
2089
+ * @see https://tailwindcss.com/docs/border-color
2090
+ */
2091
+ "border-color-x": [{
2092
+ "border-x": [borderColor]
2093
+ }],
2094
+ /**
2095
+ * Border Color Y
2096
+ * @see https://tailwindcss.com/docs/border-color
2097
+ */
2098
+ "border-color-y": [{
2099
+ "border-y": [borderColor]
2100
+ }],
2101
+ /**
2102
+ * Border Color S
2103
+ * @see https://tailwindcss.com/docs/border-color
2104
+ */
2105
+ "border-color-s": [{
2106
+ "border-s": [borderColor]
2107
+ }],
2108
+ /**
2109
+ * Border Color E
2110
+ * @see https://tailwindcss.com/docs/border-color
2111
+ */
2112
+ "border-color-e": [{
2113
+ "border-e": [borderColor]
2114
+ }],
2115
+ /**
2116
+ * Border Color Top
2117
+ * @see https://tailwindcss.com/docs/border-color
2118
+ */
2119
+ "border-color-t": [{
2120
+ "border-t": [borderColor]
2121
+ }],
2122
+ /**
2123
+ * Border Color Right
2124
+ * @see https://tailwindcss.com/docs/border-color
2125
+ */
2126
+ "border-color-r": [{
2127
+ "border-r": [borderColor]
2128
+ }],
2129
+ /**
2130
+ * Border Color Bottom
2131
+ * @see https://tailwindcss.com/docs/border-color
2132
+ */
2133
+ "border-color-b": [{
2134
+ "border-b": [borderColor]
2135
+ }],
2136
+ /**
2137
+ * Border Color Left
2138
+ * @see https://tailwindcss.com/docs/border-color
2139
+ */
2140
+ "border-color-l": [{
2141
+ "border-l": [borderColor]
2142
+ }],
2143
+ /**
2144
+ * Divide Color
2145
+ * @see https://tailwindcss.com/docs/divide-color
2146
+ */
2147
+ "divide-color": [{
2148
+ divide: [borderColor]
2149
+ }],
2150
+ /**
2151
+ * Outline Style
2152
+ * @see https://tailwindcss.com/docs/outline-style
2153
+ */
2154
+ "outline-style": [{
2155
+ outline: ["", ...getLineStyles()]
2156
+ }],
2157
+ /**
2158
+ * Outline Offset
2159
+ * @see https://tailwindcss.com/docs/outline-offset
2160
+ */
2161
+ "outline-offset": [{
2162
+ "outline-offset": [isLength, isArbitraryValue]
2163
+ }],
2164
+ /**
2165
+ * Outline Width
2166
+ * @see https://tailwindcss.com/docs/outline-width
2167
+ */
2168
+ "outline-w": [{
2169
+ outline: [isLength, isArbitraryLength]
2170
+ }],
2171
+ /**
2172
+ * Outline Color
2173
+ * @see https://tailwindcss.com/docs/outline-color
2174
+ */
2175
+ "outline-color": [{
2176
+ outline: [colors]
2177
+ }],
2178
+ /**
2179
+ * Ring Width
2180
+ * @see https://tailwindcss.com/docs/ring-width
2181
+ */
2182
+ "ring-w": [{
2183
+ ring: getLengthWithEmptyAndArbitrary()
2184
+ }],
2185
+ /**
2186
+ * Ring Width Inset
2187
+ * @see https://tailwindcss.com/docs/ring-width
2188
+ */
2189
+ "ring-w-inset": ["ring-inset"],
2190
+ /**
2191
+ * Ring Color
2192
+ * @see https://tailwindcss.com/docs/ring-color
2193
+ */
2194
+ "ring-color": [{
2195
+ ring: [colors]
2196
+ }],
2197
+ /**
2198
+ * Ring Opacity
2199
+ * @see https://tailwindcss.com/docs/ring-opacity
2200
+ */
2201
+ "ring-opacity": [{
2202
+ "ring-opacity": [opacity]
2203
+ }],
2204
+ /**
2205
+ * Ring Offset Width
2206
+ * @see https://tailwindcss.com/docs/ring-offset-width
2207
+ */
2208
+ "ring-offset-w": [{
2209
+ "ring-offset": [isLength, isArbitraryLength]
2210
+ }],
2211
+ /**
2212
+ * Ring Offset Color
2213
+ * @see https://tailwindcss.com/docs/ring-offset-color
2214
+ */
2215
+ "ring-offset-color": [{
2216
+ "ring-offset": [colors]
2217
+ }],
2218
+ // Effects
2219
+ /**
2220
+ * Box Shadow
2221
+ * @see https://tailwindcss.com/docs/box-shadow
2222
+ */
2223
+ shadow: [{
2224
+ shadow: ["", "inner", "none", isTshirtSize, isArbitraryShadow]
2225
+ }],
2226
+ /**
2227
+ * Box Shadow Color
2228
+ * @see https://tailwindcss.com/docs/box-shadow-color
2229
+ */
2230
+ "shadow-color": [{
2231
+ shadow: [isAny]
2232
+ }],
2233
+ /**
2234
+ * Opacity
2235
+ * @see https://tailwindcss.com/docs/opacity
2236
+ */
2237
+ opacity: [{
2238
+ opacity: [opacity]
2239
+ }],
2240
+ /**
2241
+ * Mix Blend Mode
2242
+ * @see https://tailwindcss.com/docs/mix-blend-mode
2243
+ */
2244
+ "mix-blend": [{
2245
+ "mix-blend": [...getBlendModes(), "plus-lighter", "plus-darker"]
2246
+ }],
2247
+ /**
2248
+ * Background Blend Mode
2249
+ * @see https://tailwindcss.com/docs/background-blend-mode
2250
+ */
2251
+ "bg-blend": [{
2252
+ "bg-blend": getBlendModes()
2253
+ }],
2254
+ // Filters
2255
+ /**
2256
+ * Filter
2257
+ * @deprecated since Tailwind CSS v3.0.0
2258
+ * @see https://tailwindcss.com/docs/filter
2259
+ */
2260
+ filter: [{
2261
+ filter: ["", "none"]
2262
+ }],
2263
+ /**
2264
+ * Blur
2265
+ * @see https://tailwindcss.com/docs/blur
2266
+ */
2267
+ blur: [{
2268
+ blur: [blur]
2269
+ }],
2270
+ /**
2271
+ * Brightness
2272
+ * @see https://tailwindcss.com/docs/brightness
2273
+ */
2274
+ brightness: [{
2275
+ brightness: [brightness]
2276
+ }],
2277
+ /**
2278
+ * Contrast
2279
+ * @see https://tailwindcss.com/docs/contrast
2280
+ */
2281
+ contrast: [{
2282
+ contrast: [contrast]
2283
+ }],
2284
+ /**
2285
+ * Drop Shadow
2286
+ * @see https://tailwindcss.com/docs/drop-shadow
2287
+ */
2288
+ "drop-shadow": [{
2289
+ "drop-shadow": ["", "none", isTshirtSize, isArbitraryValue]
2290
+ }],
2291
+ /**
2292
+ * Grayscale
2293
+ * @see https://tailwindcss.com/docs/grayscale
2294
+ */
2295
+ grayscale: [{
2296
+ grayscale: [grayscale]
2297
+ }],
2298
+ /**
2299
+ * Hue Rotate
2300
+ * @see https://tailwindcss.com/docs/hue-rotate
2301
+ */
2302
+ "hue-rotate": [{
2303
+ "hue-rotate": [hueRotate]
2304
+ }],
2305
+ /**
2306
+ * Invert
2307
+ * @see https://tailwindcss.com/docs/invert
2308
+ */
2309
+ invert: [{
2310
+ invert: [invert]
2311
+ }],
2312
+ /**
2313
+ * Saturate
2314
+ * @see https://tailwindcss.com/docs/saturate
2315
+ */
2316
+ saturate: [{
2317
+ saturate: [saturate]
2318
+ }],
2319
+ /**
2320
+ * Sepia
2321
+ * @see https://tailwindcss.com/docs/sepia
2322
+ */
2323
+ sepia: [{
2324
+ sepia: [sepia]
2325
+ }],
2326
+ /**
2327
+ * Backdrop Filter
2328
+ * @deprecated since Tailwind CSS v3.0.0
2329
+ * @see https://tailwindcss.com/docs/backdrop-filter
2330
+ */
2331
+ "backdrop-filter": [{
2332
+ "backdrop-filter": ["", "none"]
2333
+ }],
2334
+ /**
2335
+ * Backdrop Blur
2336
+ * @see https://tailwindcss.com/docs/backdrop-blur
2337
+ */
2338
+ "backdrop-blur": [{
2339
+ "backdrop-blur": [blur]
2340
+ }],
2341
+ /**
2342
+ * Backdrop Brightness
2343
+ * @see https://tailwindcss.com/docs/backdrop-brightness
2344
+ */
2345
+ "backdrop-brightness": [{
2346
+ "backdrop-brightness": [brightness]
2347
+ }],
2348
+ /**
2349
+ * Backdrop Contrast
2350
+ * @see https://tailwindcss.com/docs/backdrop-contrast
2351
+ */
2352
+ "backdrop-contrast": [{
2353
+ "backdrop-contrast": [contrast]
2354
+ }],
2355
+ /**
2356
+ * Backdrop Grayscale
2357
+ * @see https://tailwindcss.com/docs/backdrop-grayscale
2358
+ */
2359
+ "backdrop-grayscale": [{
2360
+ "backdrop-grayscale": [grayscale]
2361
+ }],
2362
+ /**
2363
+ * Backdrop Hue Rotate
2364
+ * @see https://tailwindcss.com/docs/backdrop-hue-rotate
2365
+ */
2366
+ "backdrop-hue-rotate": [{
2367
+ "backdrop-hue-rotate": [hueRotate]
2368
+ }],
2369
+ /**
2370
+ * Backdrop Invert
2371
+ * @see https://tailwindcss.com/docs/backdrop-invert
2372
+ */
2373
+ "backdrop-invert": [{
2374
+ "backdrop-invert": [invert]
2375
+ }],
2376
+ /**
2377
+ * Backdrop Opacity
2378
+ * @see https://tailwindcss.com/docs/backdrop-opacity
2379
+ */
2380
+ "backdrop-opacity": [{
2381
+ "backdrop-opacity": [opacity]
2382
+ }],
2383
+ /**
2384
+ * Backdrop Saturate
2385
+ * @see https://tailwindcss.com/docs/backdrop-saturate
2386
+ */
2387
+ "backdrop-saturate": [{
2388
+ "backdrop-saturate": [saturate]
2389
+ }],
2390
+ /**
2391
+ * Backdrop Sepia
2392
+ * @see https://tailwindcss.com/docs/backdrop-sepia
2393
+ */
2394
+ "backdrop-sepia": [{
2395
+ "backdrop-sepia": [sepia]
2396
+ }],
2397
+ // Tables
2398
+ /**
2399
+ * Border Collapse
2400
+ * @see https://tailwindcss.com/docs/border-collapse
2401
+ */
2402
+ "border-collapse": [{
2403
+ border: ["collapse", "separate"]
2404
+ }],
2405
+ /**
2406
+ * Border Spacing
2407
+ * @see https://tailwindcss.com/docs/border-spacing
2408
+ */
2409
+ "border-spacing": [{
2410
+ "border-spacing": [borderSpacing]
2411
+ }],
2412
+ /**
2413
+ * Border Spacing X
2414
+ * @see https://tailwindcss.com/docs/border-spacing
2415
+ */
2416
+ "border-spacing-x": [{
2417
+ "border-spacing-x": [borderSpacing]
2418
+ }],
2419
+ /**
2420
+ * Border Spacing Y
2421
+ * @see https://tailwindcss.com/docs/border-spacing
2422
+ */
2423
+ "border-spacing-y": [{
2424
+ "border-spacing-y": [borderSpacing]
2425
+ }],
2426
+ /**
2427
+ * Table Layout
2428
+ * @see https://tailwindcss.com/docs/table-layout
2429
+ */
2430
+ "table-layout": [{
2431
+ table: ["auto", "fixed"]
2432
+ }],
2433
+ /**
2434
+ * Caption Side
2435
+ * @see https://tailwindcss.com/docs/caption-side
2436
+ */
2437
+ caption: [{
2438
+ caption: ["top", "bottom"]
2439
+ }],
2440
+ // Transitions and Animation
2441
+ /**
2442
+ * Tranisition Property
2443
+ * @see https://tailwindcss.com/docs/transition-property
2444
+ */
2445
+ transition: [{
2446
+ transition: ["none", "all", "", "colors", "opacity", "shadow", "transform", isArbitraryValue]
2447
+ }],
2448
+ /**
2449
+ * Transition Duration
2450
+ * @see https://tailwindcss.com/docs/transition-duration
2451
+ */
2452
+ duration: [{
2453
+ duration: getNumberAndArbitrary()
2454
+ }],
2455
+ /**
2456
+ * Transition Timing Function
2457
+ * @see https://tailwindcss.com/docs/transition-timing-function
2458
+ */
2459
+ ease: [{
2460
+ ease: ["linear", "in", "out", "in-out", isArbitraryValue]
2461
+ }],
2462
+ /**
2463
+ * Transition Delay
2464
+ * @see https://tailwindcss.com/docs/transition-delay
2465
+ */
2466
+ delay: [{
2467
+ delay: getNumberAndArbitrary()
2468
+ }],
2469
+ /**
2470
+ * Animation
2471
+ * @see https://tailwindcss.com/docs/animation
2472
+ */
2473
+ animate: [{
2474
+ animate: ["none", "spin", "ping", "pulse", "bounce", isArbitraryValue]
2475
+ }],
2476
+ // Transforms
2477
+ /**
2478
+ * Transform
2479
+ * @see https://tailwindcss.com/docs/transform
2480
+ */
2481
+ transform: [{
2482
+ transform: ["", "gpu", "none"]
2483
+ }],
2484
+ /**
2485
+ * Scale
2486
+ * @see https://tailwindcss.com/docs/scale
2487
+ */
2488
+ scale: [{
2489
+ scale: [scale]
2490
+ }],
2491
+ /**
2492
+ * Scale X
2493
+ * @see https://tailwindcss.com/docs/scale
2494
+ */
2495
+ "scale-x": [{
2496
+ "scale-x": [scale]
2497
+ }],
2498
+ /**
2499
+ * Scale Y
2500
+ * @see https://tailwindcss.com/docs/scale
2501
+ */
2502
+ "scale-y": [{
2503
+ "scale-y": [scale]
2504
+ }],
2505
+ /**
2506
+ * Rotate
2507
+ * @see https://tailwindcss.com/docs/rotate
2508
+ */
2509
+ rotate: [{
2510
+ rotate: [isInteger, isArbitraryValue]
2511
+ }],
2512
+ /**
2513
+ * Translate X
2514
+ * @see https://tailwindcss.com/docs/translate
2515
+ */
2516
+ "translate-x": [{
2517
+ "translate-x": [translate]
2518
+ }],
2519
+ /**
2520
+ * Translate Y
2521
+ * @see https://tailwindcss.com/docs/translate
2522
+ */
2523
+ "translate-y": [{
2524
+ "translate-y": [translate]
2525
+ }],
2526
+ /**
2527
+ * Skew X
2528
+ * @see https://tailwindcss.com/docs/skew
2529
+ */
2530
+ "skew-x": [{
2531
+ "skew-x": [skew]
2532
+ }],
2533
+ /**
2534
+ * Skew Y
2535
+ * @see https://tailwindcss.com/docs/skew
2536
+ */
2537
+ "skew-y": [{
2538
+ "skew-y": [skew]
2539
+ }],
2540
+ /**
2541
+ * Transform Origin
2542
+ * @see https://tailwindcss.com/docs/transform-origin
2543
+ */
2544
+ "transform-origin": [{
2545
+ origin: ["center", "top", "top-right", "right", "bottom-right", "bottom", "bottom-left", "left", "top-left", isArbitraryValue]
2546
+ }],
2547
+ // Interactivity
2548
+ /**
2549
+ * Accent Color
2550
+ * @see https://tailwindcss.com/docs/accent-color
2551
+ */
2552
+ accent: [{
2553
+ accent: ["auto", colors]
2554
+ }],
2555
+ /**
2556
+ * Appearance
2557
+ * @see https://tailwindcss.com/docs/appearance
2558
+ */
2559
+ appearance: [{
2560
+ appearance: ["none", "auto"]
2561
+ }],
2562
+ /**
2563
+ * Cursor
2564
+ * @see https://tailwindcss.com/docs/cursor
2565
+ */
2566
+ cursor: [{
2567
+ cursor: ["auto", "default", "pointer", "wait", "text", "move", "help", "not-allowed", "none", "context-menu", "progress", "cell", "crosshair", "vertical-text", "alias", "copy", "no-drop", "grab", "grabbing", "all-scroll", "col-resize", "row-resize", "n-resize", "e-resize", "s-resize", "w-resize", "ne-resize", "nw-resize", "se-resize", "sw-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "zoom-in", "zoom-out", isArbitraryValue]
2568
+ }],
2569
+ /**
2570
+ * Caret Color
2571
+ * @see https://tailwindcss.com/docs/just-in-time-mode#caret-color-utilities
2572
+ */
2573
+ "caret-color": [{
2574
+ caret: [colors]
2575
+ }],
2576
+ /**
2577
+ * Pointer Events
2578
+ * @see https://tailwindcss.com/docs/pointer-events
2579
+ */
2580
+ "pointer-events": [{
2581
+ "pointer-events": ["none", "auto"]
2582
+ }],
2583
+ /**
2584
+ * Resize
2585
+ * @see https://tailwindcss.com/docs/resize
2586
+ */
2587
+ resize: [{
2588
+ resize: ["none", "y", "x", ""]
2589
+ }],
2590
+ /**
2591
+ * Scroll Behavior
2592
+ * @see https://tailwindcss.com/docs/scroll-behavior
2593
+ */
2594
+ "scroll-behavior": [{
2595
+ scroll: ["auto", "smooth"]
2596
+ }],
2597
+ /**
2598
+ * Scroll Margin
2599
+ * @see https://tailwindcss.com/docs/scroll-margin
2600
+ */
2601
+ "scroll-m": [{
2602
+ "scroll-m": getSpacingWithArbitrary()
2603
+ }],
2604
+ /**
2605
+ * Scroll Margin X
2606
+ * @see https://tailwindcss.com/docs/scroll-margin
2607
+ */
2608
+ "scroll-mx": [{
2609
+ "scroll-mx": getSpacingWithArbitrary()
2610
+ }],
2611
+ /**
2612
+ * Scroll Margin Y
2613
+ * @see https://tailwindcss.com/docs/scroll-margin
2614
+ */
2615
+ "scroll-my": [{
2616
+ "scroll-my": getSpacingWithArbitrary()
2617
+ }],
2618
+ /**
2619
+ * Scroll Margin Start
2620
+ * @see https://tailwindcss.com/docs/scroll-margin
2621
+ */
2622
+ "scroll-ms": [{
2623
+ "scroll-ms": getSpacingWithArbitrary()
2624
+ }],
2625
+ /**
2626
+ * Scroll Margin End
2627
+ * @see https://tailwindcss.com/docs/scroll-margin
2628
+ */
2629
+ "scroll-me": [{
2630
+ "scroll-me": getSpacingWithArbitrary()
2631
+ }],
2632
+ /**
2633
+ * Scroll Margin Top
2634
+ * @see https://tailwindcss.com/docs/scroll-margin
2635
+ */
2636
+ "scroll-mt": [{
2637
+ "scroll-mt": getSpacingWithArbitrary()
2638
+ }],
2639
+ /**
2640
+ * Scroll Margin Right
2641
+ * @see https://tailwindcss.com/docs/scroll-margin
2642
+ */
2643
+ "scroll-mr": [{
2644
+ "scroll-mr": getSpacingWithArbitrary()
2645
+ }],
2646
+ /**
2647
+ * Scroll Margin Bottom
2648
+ * @see https://tailwindcss.com/docs/scroll-margin
2649
+ */
2650
+ "scroll-mb": [{
2651
+ "scroll-mb": getSpacingWithArbitrary()
2652
+ }],
2653
+ /**
2654
+ * Scroll Margin Left
2655
+ * @see https://tailwindcss.com/docs/scroll-margin
2656
+ */
2657
+ "scroll-ml": [{
2658
+ "scroll-ml": getSpacingWithArbitrary()
2659
+ }],
2660
+ /**
2661
+ * Scroll Padding
2662
+ * @see https://tailwindcss.com/docs/scroll-padding
2663
+ */
2664
+ "scroll-p": [{
2665
+ "scroll-p": getSpacingWithArbitrary()
2666
+ }],
2667
+ /**
2668
+ * Scroll Padding X
2669
+ * @see https://tailwindcss.com/docs/scroll-padding
2670
+ */
2671
+ "scroll-px": [{
2672
+ "scroll-px": getSpacingWithArbitrary()
2673
+ }],
2674
+ /**
2675
+ * Scroll Padding Y
2676
+ * @see https://tailwindcss.com/docs/scroll-padding
2677
+ */
2678
+ "scroll-py": [{
2679
+ "scroll-py": getSpacingWithArbitrary()
2680
+ }],
2681
+ /**
2682
+ * Scroll Padding Start
2683
+ * @see https://tailwindcss.com/docs/scroll-padding
2684
+ */
2685
+ "scroll-ps": [{
2686
+ "scroll-ps": getSpacingWithArbitrary()
2687
+ }],
2688
+ /**
2689
+ * Scroll Padding End
2690
+ * @see https://tailwindcss.com/docs/scroll-padding
2691
+ */
2692
+ "scroll-pe": [{
2693
+ "scroll-pe": getSpacingWithArbitrary()
2694
+ }],
2695
+ /**
2696
+ * Scroll Padding Top
2697
+ * @see https://tailwindcss.com/docs/scroll-padding
2698
+ */
2699
+ "scroll-pt": [{
2700
+ "scroll-pt": getSpacingWithArbitrary()
2701
+ }],
2702
+ /**
2703
+ * Scroll Padding Right
2704
+ * @see https://tailwindcss.com/docs/scroll-padding
2705
+ */
2706
+ "scroll-pr": [{
2707
+ "scroll-pr": getSpacingWithArbitrary()
2708
+ }],
2709
+ /**
2710
+ * Scroll Padding Bottom
2711
+ * @see https://tailwindcss.com/docs/scroll-padding
2712
+ */
2713
+ "scroll-pb": [{
2714
+ "scroll-pb": getSpacingWithArbitrary()
2715
+ }],
2716
+ /**
2717
+ * Scroll Padding Left
2718
+ * @see https://tailwindcss.com/docs/scroll-padding
2719
+ */
2720
+ "scroll-pl": [{
2721
+ "scroll-pl": getSpacingWithArbitrary()
2722
+ }],
2723
+ /**
2724
+ * Scroll Snap Align
2725
+ * @see https://tailwindcss.com/docs/scroll-snap-align
2726
+ */
2727
+ "snap-align": [{
2728
+ snap: ["start", "end", "center", "align-none"]
2729
+ }],
2730
+ /**
2731
+ * Scroll Snap Stop
2732
+ * @see https://tailwindcss.com/docs/scroll-snap-stop
2733
+ */
2734
+ "snap-stop": [{
2735
+ snap: ["normal", "always"]
2736
+ }],
2737
+ /**
2738
+ * Scroll Snap Type
2739
+ * @see https://tailwindcss.com/docs/scroll-snap-type
2740
+ */
2741
+ "snap-type": [{
2742
+ snap: ["none", "x", "y", "both"]
2743
+ }],
2744
+ /**
2745
+ * Scroll Snap Type Strictness
2746
+ * @see https://tailwindcss.com/docs/scroll-snap-type
2747
+ */
2748
+ "snap-strictness": [{
2749
+ snap: ["mandatory", "proximity"]
2750
+ }],
2751
+ /**
2752
+ * Touch Action
2753
+ * @see https://tailwindcss.com/docs/touch-action
2754
+ */
2755
+ touch: [{
2756
+ touch: ["auto", "none", "manipulation"]
2757
+ }],
2758
+ /**
2759
+ * Touch Action X
2760
+ * @see https://tailwindcss.com/docs/touch-action
2761
+ */
2762
+ "touch-x": [{
2763
+ "touch-pan": ["x", "left", "right"]
2764
+ }],
2765
+ /**
2766
+ * Touch Action Y
2767
+ * @see https://tailwindcss.com/docs/touch-action
2768
+ */
2769
+ "touch-y": [{
2770
+ "touch-pan": ["y", "up", "down"]
2771
+ }],
2772
+ /**
2773
+ * Touch Action Pinch Zoom
2774
+ * @see https://tailwindcss.com/docs/touch-action
2775
+ */
2776
+ "touch-pz": ["touch-pinch-zoom"],
2777
+ /**
2778
+ * User Select
2779
+ * @see https://tailwindcss.com/docs/user-select
2780
+ */
2781
+ select: [{
2782
+ select: ["none", "text", "all", "auto"]
2783
+ }],
2784
+ /**
2785
+ * Will Change
2786
+ * @see https://tailwindcss.com/docs/will-change
2787
+ */
2788
+ "will-change": [{
2789
+ "will-change": ["auto", "scroll", "contents", "transform", isArbitraryValue]
2790
+ }],
2791
+ // SVG
2792
+ /**
2793
+ * Fill
2794
+ * @see https://tailwindcss.com/docs/fill
2795
+ */
2796
+ fill: [{
2797
+ fill: [colors, "none"]
2798
+ }],
2799
+ /**
2800
+ * Stroke Width
2801
+ * @see https://tailwindcss.com/docs/stroke-width
2802
+ */
2803
+ "stroke-w": [{
2804
+ stroke: [isLength, isArbitraryLength, isArbitraryNumber]
2805
+ }],
2806
+ /**
2807
+ * Stroke
2808
+ * @see https://tailwindcss.com/docs/stroke
2809
+ */
2810
+ stroke: [{
2811
+ stroke: [colors, "none"]
2812
+ }],
2813
+ // Accessibility
2814
+ /**
2815
+ * Screen Readers
2816
+ * @see https://tailwindcss.com/docs/screen-readers
2817
+ */
2818
+ sr: ["sr-only", "not-sr-only"],
2819
+ /**
2820
+ * Forced Color Adjust
2821
+ * @see https://tailwindcss.com/docs/forced-color-adjust
2822
+ */
2823
+ "forced-color-adjust": [{
2824
+ "forced-color-adjust": ["auto", "none"]
2825
+ }]
2826
+ },
2827
+ conflictingClassGroups: {
2828
+ overflow: ["overflow-x", "overflow-y"],
2829
+ overscroll: ["overscroll-x", "overscroll-y"],
2830
+ inset: ["inset-x", "inset-y", "start", "end", "top", "right", "bottom", "left"],
2831
+ "inset-x": ["right", "left"],
2832
+ "inset-y": ["top", "bottom"],
2833
+ flex: ["basis", "grow", "shrink"],
2834
+ gap: ["gap-x", "gap-y"],
2835
+ p: ["px", "py", "ps", "pe", "pt", "pr", "pb", "pl"],
2836
+ px: ["pr", "pl"],
2837
+ py: ["pt", "pb"],
2838
+ m: ["mx", "my", "ms", "me", "mt", "mr", "mb", "ml"],
2839
+ mx: ["mr", "ml"],
2840
+ my: ["mt", "mb"],
2841
+ size: ["w", "h"],
2842
+ "font-size": ["leading"],
2843
+ "fvn-normal": ["fvn-ordinal", "fvn-slashed-zero", "fvn-figure", "fvn-spacing", "fvn-fraction"],
2844
+ "fvn-ordinal": ["fvn-normal"],
2845
+ "fvn-slashed-zero": ["fvn-normal"],
2846
+ "fvn-figure": ["fvn-normal"],
2847
+ "fvn-spacing": ["fvn-normal"],
2848
+ "fvn-fraction": ["fvn-normal"],
2849
+ "line-clamp": ["display", "overflow"],
2850
+ rounded: ["rounded-s", "rounded-e", "rounded-t", "rounded-r", "rounded-b", "rounded-l", "rounded-ss", "rounded-se", "rounded-ee", "rounded-es", "rounded-tl", "rounded-tr", "rounded-br", "rounded-bl"],
2851
+ "rounded-s": ["rounded-ss", "rounded-es"],
2852
+ "rounded-e": ["rounded-se", "rounded-ee"],
2853
+ "rounded-t": ["rounded-tl", "rounded-tr"],
2854
+ "rounded-r": ["rounded-tr", "rounded-br"],
2855
+ "rounded-b": ["rounded-br", "rounded-bl"],
2856
+ "rounded-l": ["rounded-tl", "rounded-bl"],
2857
+ "border-spacing": ["border-spacing-x", "border-spacing-y"],
2858
+ "border-w": ["border-w-s", "border-w-e", "border-w-t", "border-w-r", "border-w-b", "border-w-l"],
2859
+ "border-w-x": ["border-w-r", "border-w-l"],
2860
+ "border-w-y": ["border-w-t", "border-w-b"],
2861
+ "border-color": ["border-color-s", "border-color-e", "border-color-t", "border-color-r", "border-color-b", "border-color-l"],
2862
+ "border-color-x": ["border-color-r", "border-color-l"],
2863
+ "border-color-y": ["border-color-t", "border-color-b"],
2864
+ "scroll-m": ["scroll-mx", "scroll-my", "scroll-ms", "scroll-me", "scroll-mt", "scroll-mr", "scroll-mb", "scroll-ml"],
2865
+ "scroll-mx": ["scroll-mr", "scroll-ml"],
2866
+ "scroll-my": ["scroll-mt", "scroll-mb"],
2867
+ "scroll-p": ["scroll-px", "scroll-py", "scroll-ps", "scroll-pe", "scroll-pt", "scroll-pr", "scroll-pb", "scroll-pl"],
2868
+ "scroll-px": ["scroll-pr", "scroll-pl"],
2869
+ "scroll-py": ["scroll-pt", "scroll-pb"],
2870
+ touch: ["touch-x", "touch-y", "touch-pz"],
2871
+ "touch-x": ["touch"],
2872
+ "touch-y": ["touch"],
2873
+ "touch-pz": ["touch"]
2874
+ },
2875
+ conflictingClassGroupModifiers: {
2876
+ "font-size": ["leading"]
2877
+ }
2878
+ };
2879
+ };
2880
+ var twMerge = /* @__PURE__ */ createTailwindMerge(getDefaultConfig);
2881
+
2882
+ // src/lib/utils.ts
2883
+ function cn(...inputs) {
2884
+ return twMerge(clsx(inputs));
2885
+ }
2886
+
2887
+ // src/components/ui/button.tsx
2888
+ import { jsx } from "react/jsx-runtime";
2889
+ var Button = React.forwardRef(
2890
+ ({ className, variant = "default", size = "default", ...props }, ref) => {
2891
+ return /* @__PURE__ */ jsx(
2892
+ "button",
2893
+ {
2894
+ className: cn(
2895
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
2896
+ {
2897
+ "bg-primary text-primary-foreground hover:bg-primary/90": variant === "default",
2898
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90": variant === "destructive",
2899
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground": variant === "outline",
2900
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80": variant === "secondary",
2901
+ "hover:bg-accent hover:text-accent-foreground": variant === "ghost",
2902
+ "text-primary underline-offset-4 hover:underline": variant === "link"
2903
+ },
2904
+ {
2905
+ "h-10 px-4 py-2": size === "default",
2906
+ "h-9 rounded-md px-3": size === "sm",
2907
+ "h-11 rounded-md px-8": size === "lg",
2908
+ "h-10 w-10": size === "icon"
2909
+ },
2910
+ className
2911
+ ),
2912
+ ref,
2913
+ ...props
2914
+ }
2915
+ );
2916
+ }
2917
+ );
2918
+ Button.displayName = "Button";
2919
+
2920
+ // src/components/ui/card.tsx
2921
+ import * as React2 from "react";
2922
+ import { jsx as jsx2 } from "react/jsx-runtime";
2923
+ var Card = React2.forwardRef(
2924
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
2925
+ "div",
2926
+ {
2927
+ ref,
2928
+ className: cn(
2929
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
2930
+ className
2931
+ ),
2932
+ ...props
2933
+ }
2934
+ )
2935
+ );
2936
+ Card.displayName = "Card";
2937
+ var CardHeader = React2.forwardRef(
2938
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx2("div", { ref, className: cn("flex flex-col space-y-1.5 p-6", className), ...props })
2939
+ );
2940
+ CardHeader.displayName = "CardHeader";
2941
+ var CardTitle = React2.forwardRef(
2942
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx2("h3", { ref, className: cn("text-2xl font-semibold leading-none tracking-tight", className), ...props })
2943
+ );
2944
+ CardTitle.displayName = "CardTitle";
2945
+ var CardDescription = React2.forwardRef(
2946
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx2("p", { ref, className: cn("text-sm text-muted-foreground", className), ...props })
2947
+ );
2948
+ CardDescription.displayName = "CardDescription";
2949
+ var CardContent = React2.forwardRef(
2950
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx2("div", { ref, className: cn("p-6 pt-0", className), ...props })
2951
+ );
2952
+ CardContent.displayName = "CardContent";
2953
+ var CardFooter = React2.forwardRef(
2954
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx2("div", { ref, className: cn("flex items-center p-6 pt-0", className), ...props })
2955
+ );
2956
+ CardFooter.displayName = "CardFooter";
2957
+
2958
+ // src/components/seo/SEORecordForm.tsx
2959
+ import { useState as useState2 } from "react";
2960
+ import { useForm } from "react-hook-form";
2961
+ import { zodResolver } from "@hookform/resolvers/zod";
2962
+
2963
+ // src/lib/validation/seo-schema.ts
2964
+ import { z } from "zod";
2965
+ var ogTypeSchema = z.enum([
2966
+ "website",
2967
+ "article",
2968
+ "product",
2969
+ "book",
2970
+ "profile",
2971
+ "music",
2972
+ "video"
2973
+ ]);
2974
+ var twitterCardSchema = z.enum([
2975
+ "summary",
2976
+ "summary_large_image",
2977
+ "app",
2978
+ "player"
2979
+ ]);
2980
+ var validationStatusSchema = z.enum([
2981
+ "pending",
2982
+ "valid",
2983
+ "invalid",
2984
+ "warning"
2985
+ ]);
2986
+ var seoMetadataSchema = z.object({
2987
+ // Basic metadata
2988
+ title: z.string().max(60, "Title must be 60 characters or less").optional(),
2989
+ description: z.string().max(160, "Description must be 160 characters or less").optional(),
2990
+ keywords: z.array(z.string()).optional(),
2991
+ // Open Graph
2992
+ ogTitle: z.string().max(60).optional(),
2993
+ ogDescription: z.string().max(200).optional(),
2994
+ ogImageUrl: z.string().url("Must be a valid URL").optional(),
2995
+ ogImageWidth: z.number().int().positive().max(1200).optional(),
2996
+ ogImageHeight: z.number().int().positive().max(1200).optional(),
2997
+ ogType: ogTypeSchema.optional(),
2998
+ ogUrl: z.string().url("Must be a valid URL").optional(),
2999
+ ogSiteName: z.string().optional(),
3000
+ // Twitter Card
3001
+ twitterCard: twitterCardSchema.optional(),
3002
+ twitterTitle: z.string().max(70).optional(),
3003
+ twitterDescription: z.string().max(200).optional(),
3004
+ twitterImageUrl: z.string().url("Must be a valid URL").optional(),
3005
+ twitterSite: z.string().optional(),
3006
+ twitterCreator: z.string().optional(),
3007
+ // Additional metadata
3008
+ canonicalUrl: z.string().url("Must be a valid URL").optional(),
3009
+ robots: z.string().optional(),
3010
+ author: z.string().optional(),
3011
+ publishedTime: z.coerce.date().optional(),
3012
+ modifiedTime: z.coerce.date().optional(),
3013
+ // Structured data
3014
+ structuredData: z.record(z.unknown()).optional()
3015
+ });
3016
+ var seoRecordSchema = seoMetadataSchema.extend({
3017
+ id: z.string().uuid().optional(),
3018
+ userId: z.string().uuid(),
3019
+ routePath: z.string().min(1, "Route path is required").regex(/^\/.*/, "Route path must start with /"),
3020
+ validationStatus: validationStatusSchema.optional(),
3021
+ lastValidatedAt: z.coerce.date().optional(),
3022
+ validationErrors: z.record(z.unknown()).optional(),
3023
+ createdAt: z.coerce.date().optional(),
3024
+ updatedAt: z.coerce.date().optional()
3025
+ });
3026
+ var createSEORecordSchema = seoRecordSchema.omit({
3027
+ id: true,
3028
+ validationStatus: true,
3029
+ lastValidatedAt: true,
3030
+ validationErrors: true,
3031
+ createdAt: true,
3032
+ updatedAt: true
3033
+ });
3034
+ var updateSEORecordSchema = seoRecordSchema.partial().required({ id: true }).omit({
3035
+ userId: true,
3036
+ createdAt: true
3037
+ });
3038
+
3039
+ // src/components/ui/input.tsx
3040
+ import * as React3 from "react";
3041
+ import { jsx as jsx3 } from "react/jsx-runtime";
3042
+ var Input = React3.forwardRef(
3043
+ ({ className, type, ...props }, ref) => {
3044
+ return /* @__PURE__ */ jsx3(
3045
+ "input",
3046
+ {
3047
+ type,
3048
+ className: cn(
3049
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
3050
+ className
3051
+ ),
3052
+ ref,
3053
+ ...props
3054
+ }
3055
+ );
3056
+ }
3057
+ );
3058
+ Input.displayName = "Input";
3059
+
3060
+ // src/components/seo/OGImagePreview.tsx
3061
+ import { useState, useEffect } from "react";
3062
+
3063
+ // src/components/ui/spinner.tsx
3064
+ import { jsx as jsx4 } from "react/jsx-runtime";
3065
+ function Spinner({ className, size = "default" }) {
3066
+ return /* @__PURE__ */ jsx4(
3067
+ "div",
3068
+ {
3069
+ className: cn(
3070
+ "animate-spin rounded-full border-2 border-current border-t-transparent",
3071
+ {
3072
+ "h-4 w-4": size === "sm",
3073
+ "h-6 w-6": size === "default",
3074
+ "h-8 w-8": size === "lg"
3075
+ },
3076
+ className
3077
+ )
3078
+ }
3079
+ );
3080
+ }
3081
+
3082
+ // src/components/seo/OGImagePreview.tsx
3083
+ import { jsx as jsx5, jsxs } from "react/jsx-runtime";
3084
+ function OGImagePreview({
3085
+ imageUrl,
3086
+ expectedWidth,
3087
+ expectedHeight,
3088
+ title,
3089
+ description
3090
+ }) {
3091
+ const [selectedPlatform, setSelectedPlatform] = useState("facebook");
3092
+ const [imageError, setImageError] = useState(false);
3093
+ const [validationResult, setValidationResult] = useState(null);
3094
+ const [validating, setValidating] = useState(false);
3095
+ useEffect(() => {
3096
+ if (imageUrl) {
3097
+ validateImage();
3098
+ }
3099
+ }, [imageUrl, expectedWidth, expectedHeight]);
3100
+ const validateImage = async () => {
3101
+ if (!imageUrl) return;
3102
+ setValidating(true);
3103
+ try {
3104
+ const response = await fetch("/api/validate-image", {
3105
+ method: "POST",
3106
+ headers: { "Content-Type": "application/json" },
3107
+ body: JSON.stringify({
3108
+ imageUrl,
3109
+ expectedWidth,
3110
+ expectedHeight
3111
+ })
3112
+ });
3113
+ if (response.ok) {
3114
+ const result = await response.json();
3115
+ setValidationResult(result);
3116
+ }
3117
+ } catch {
3118
+ } finally {
3119
+ setValidating(false);
3120
+ }
3121
+ };
3122
+ const platformSpecs = {
3123
+ facebook: {
3124
+ name: "Facebook",
3125
+ width: 1200,
3126
+ height: 630,
3127
+ aspectRatio: "1.91:1",
3128
+ color: "bg-blue-50 border-blue-200"
3129
+ },
3130
+ twitter: {
3131
+ name: "Twitter",
3132
+ width: 1200,
3133
+ height: 675,
3134
+ aspectRatio: "16:9",
3135
+ color: "bg-sky-50 border-sky-200"
3136
+ },
3137
+ linkedin: {
3138
+ name: "LinkedIn",
3139
+ width: 1200,
3140
+ height: 627,
3141
+ aspectRatio: "1.91:1",
3142
+ color: "bg-blue-50 border-blue-200"
3143
+ }
3144
+ };
3145
+ const spec = platformSpecs[selectedPlatform];
3146
+ return /* @__PURE__ */ jsxs(Card, { children: [
3147
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
3148
+ /* @__PURE__ */ jsx5(CardTitle, { children: "OG Image Preview" }),
3149
+ /* @__PURE__ */ jsx5(CardDescription, { children: "Preview how your image appears on social platforms" })
3150
+ ] }),
3151
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
3152
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-2", children: ["facebook", "twitter", "linkedin"].map((platform) => /* @__PURE__ */ jsx5(
3153
+ "button",
3154
+ {
3155
+ onClick: () => setSelectedPlatform(platform),
3156
+ className: `px-4 py-2 rounded-lg text-sm font-medium transition-colors ${selectedPlatform === platform ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}`,
3157
+ "aria-pressed": selectedPlatform === platform,
3158
+ children: platformSpecs[platform].name
3159
+ },
3160
+ platform
3161
+ )) }),
3162
+ /* @__PURE__ */ jsx5("div", { className: `p-3 rounded-lg border ${spec.color}`, children: /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
3163
+ /* @__PURE__ */ jsxs("div", { className: "font-medium mb-1", children: [
3164
+ spec.name,
3165
+ " Recommended Size"
3166
+ ] }),
3167
+ /* @__PURE__ */ jsxs("div", { className: "text-gray-600", children: [
3168
+ spec.width,
3169
+ " \xD7 ",
3170
+ spec.height,
3171
+ "px (",
3172
+ spec.aspectRatio,
3173
+ ")"
3174
+ ] })
3175
+ ] }) }),
3176
+ /* @__PURE__ */ jsx5("div", { className: "relative", children: /* @__PURE__ */ jsxs(
3177
+ "div",
3178
+ {
3179
+ className: "relative border-2 border-gray-300 rounded-lg overflow-hidden bg-gray-100",
3180
+ style: {
3181
+ aspectRatio: `${spec.width} / ${spec.height}`,
3182
+ maxWidth: "100%"
3183
+ },
3184
+ children: [
3185
+ imageUrl && !imageError ? /* @__PURE__ */ jsx5(
3186
+ "img",
3187
+ {
3188
+ src: imageUrl,
3189
+ alt: title || "OG Image",
3190
+ className: "w-full h-full object-cover",
3191
+ onError: () => setImageError(true),
3192
+ onLoad: () => setImageError(false)
3193
+ }
3194
+ ) : /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center h-full text-gray-400", children: imageError ? /* @__PURE__ */ jsxs("div", { className: "text-center p-4", children: [
3195
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium", children: "Image failed to load" }),
3196
+ /* @__PURE__ */ jsx5("div", { className: "text-xs mt-1", children: "Check the URL is accessible" })
3197
+ ] }) : /* @__PURE__ */ jsx5(Spinner, {}) }),
3198
+ title && !imageError && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-4 text-white", children: [
3199
+ title && /* @__PURE__ */ jsx5("div", { className: "font-semibold text-sm mb-1 line-clamp-2", children: title }),
3200
+ description && /* @__PURE__ */ jsx5("div", { className: "text-xs opacity-90 line-clamp-2", children: description })
3201
+ ] })
3202
+ ]
3203
+ }
3204
+ ) }),
3205
+ validating && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600", children: [
3206
+ /* @__PURE__ */ jsx5(Spinner, { size: "sm" }),
3207
+ /* @__PURE__ */ jsx5("span", { children: "Validating image..." })
3208
+ ] }),
3209
+ validationResult && !validating && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3210
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3211
+ /* @__PURE__ */ jsx5("span", { className: "text-sm font-medium", children: "Validation Status" }),
3212
+ /* @__PURE__ */ jsx5(
3213
+ "span",
3214
+ {
3215
+ className: `text-xs px-2 py-1 rounded ${validationResult.isValid ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"}`,
3216
+ children: validationResult.isValid ? "Valid" : "Issues Found"
3217
+ }
3218
+ )
3219
+ ] }),
3220
+ validationResult.metadata && /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-600 space-y-1", children: [
3221
+ /* @__PURE__ */ jsxs("div", { children: [
3222
+ "Dimensions: ",
3223
+ validationResult.metadata.width,
3224
+ " \xD7 ",
3225
+ validationResult.metadata.height,
3226
+ "px"
3227
+ ] }),
3228
+ /* @__PURE__ */ jsxs("div", { children: [
3229
+ "Format: ",
3230
+ validationResult.metadata.format.toUpperCase()
3231
+ ] }),
3232
+ validationResult.metadata.size && /* @__PURE__ */ jsxs("div", { children: [
3233
+ "Size: ",
3234
+ (validationResult.metadata.size / 1024).toFixed(1),
3235
+ " KB"
3236
+ ] })
3237
+ ] }),
3238
+ validationResult.issues.length > 0 && /* @__PURE__ */ jsx5("div", { className: "space-y-2 mt-3", children: validationResult.issues.map((issue, idx) => /* @__PURE__ */ jsxs(
3239
+ "div",
3240
+ {
3241
+ className: `p-2 rounded text-xs border ${issue.severity === "critical" ? "bg-red-50 border-red-200 text-red-800" : issue.severity === "warning" ? "bg-yellow-50 border-yellow-200 text-yellow-800" : "bg-blue-50 border-blue-200 text-blue-800"}`,
3242
+ children: [
3243
+ /* @__PURE__ */ jsx5("div", { className: "font-medium", children: issue.field }),
3244
+ /* @__PURE__ */ jsx5("div", { className: "mt-1", children: issue.message }),
3245
+ issue.expected && /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
3246
+ "Expected: ",
3247
+ /* @__PURE__ */ jsx5("code", { className: "bg-white/50 px-1 rounded", children: issue.expected })
3248
+ ] }),
3249
+ issue.actual && /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
3250
+ "Actual: ",
3251
+ /* @__PURE__ */ jsx5("code", { className: "bg-white/50 px-1 rounded", children: issue.actual })
3252
+ ] })
3253
+ ]
3254
+ },
3255
+ idx
3256
+ )) })
3257
+ ] }),
3258
+ /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t", children: [
3259
+ /* @__PURE__ */ jsx5("div", { className: "text-xs text-gray-500 mb-1", children: "Image URL" }),
3260
+ /* @__PURE__ */ jsx5("div", { className: "text-xs font-mono text-gray-700 break-all", children: imageUrl || "No image URL provided" })
3261
+ ] })
3262
+ ] })
3263
+ ] });
3264
+ }
3265
+
3266
+ // src/components/seo/SEORecordForm.tsx
3267
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
3268
+ function SEORecordForm({ record, onSuccess, onCancel }) {
3269
+ const [loading, setLoading] = useState2(false);
3270
+ const [error, setError] = useState2(null);
3271
+ const isEditing = !!record;
3272
+ const schema = isEditing ? updateSEORecordSchema : createSEORecordSchema;
3273
+ const {
3274
+ register,
3275
+ handleSubmit,
3276
+ formState: { errors },
3277
+ reset,
3278
+ watch
3279
+ } = useForm({
3280
+ resolver: zodResolver(schema),
3281
+ defaultValues: record ? {
3282
+ id: record.id,
3283
+ routePath: record.routePath,
3284
+ title: record.title,
3285
+ description: record.description,
3286
+ keywords: record.keywords,
3287
+ ogTitle: record.ogTitle,
3288
+ ogDescription: record.ogDescription,
3289
+ ogImageUrl: record.ogImageUrl,
3290
+ ogImageWidth: record.ogImageWidth,
3291
+ ogImageHeight: record.ogImageHeight,
3292
+ ogType: record.ogType,
3293
+ ogUrl: record.ogUrl,
3294
+ ogSiteName: record.ogSiteName,
3295
+ twitterCard: record.twitterCard,
3296
+ twitterTitle: record.twitterTitle,
3297
+ twitterDescription: record.twitterDescription,
3298
+ twitterImageUrl: record.twitterImageUrl,
3299
+ twitterSite: record.twitterSite,
3300
+ twitterCreator: record.twitterCreator,
3301
+ canonicalUrl: record.canonicalUrl,
3302
+ robots: record.robots,
3303
+ author: record.author,
3304
+ publishedTime: record.publishedTime,
3305
+ modifiedTime: record.modifiedTime
3306
+ } : {
3307
+ routePath: ""
3308
+ }
3309
+ });
3310
+ const titleValue = watch("title");
3311
+ const descriptionValue = watch("description");
3312
+ const ogTitleValue = watch("ogTitle");
3313
+ const ogDescriptionValue = watch("ogDescription");
3314
+ const onSubmit = async (data) => {
3315
+ setLoading(true);
3316
+ setError(null);
3317
+ try {
3318
+ const url = isEditing ? `/api/seo-records/${data.id}` : "/api/seo-records";
3319
+ const method = isEditing ? "PATCH" : "POST";
3320
+ const response = await fetch(url, {
3321
+ method,
3322
+ headers: { "Content-Type": "application/json" },
3323
+ body: JSON.stringify(data)
3324
+ });
3325
+ if (!response.ok) {
3326
+ const errorData = await response.json();
3327
+ throw new Error(errorData.error || "Failed to save SEO record");
3328
+ }
3329
+ onSuccess?.();
3330
+ if (!isEditing) {
3331
+ reset();
3332
+ }
3333
+ } catch (err) {
3334
+ setError(err instanceof Error ? err.message : "An error occurred");
3335
+ } finally {
3336
+ setLoading(false);
3337
+ }
3338
+ };
3339
+ return /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-6", children: [
3340
+ error && /* @__PURE__ */ jsx6(
3341
+ "div",
3342
+ {
3343
+ className: "bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg text-sm",
3344
+ role: "alert",
3345
+ "aria-live": "polite",
3346
+ children: error
3347
+ }
3348
+ ),
3349
+ /* @__PURE__ */ jsxs2(Card, { children: [
3350
+ /* @__PURE__ */ jsxs2(CardHeader, { children: [
3351
+ /* @__PURE__ */ jsx6(CardTitle, { children: "Basic Information" }),
3352
+ /* @__PURE__ */ jsx6(CardDescription, { children: "Core SEO metadata" })
3353
+ ] }),
3354
+ /* @__PURE__ */ jsxs2(CardContent, { className: "space-y-4", children: [
3355
+ /* @__PURE__ */ jsxs2("div", { children: [
3356
+ /* @__PURE__ */ jsxs2("label", { htmlFor: "routePath", className: "block text-sm font-medium text-gray-700 mb-1", children: [
3357
+ "Route Path ",
3358
+ /* @__PURE__ */ jsx6("span", { className: "text-red-500", children: "*" })
3359
+ ] }),
3360
+ /* @__PURE__ */ jsx6(
3361
+ Input,
3362
+ {
3363
+ id: "routePath",
3364
+ ...register("routePath"),
3365
+ placeholder: "/about",
3366
+ "aria-describedby": errors.routePath ? "routePath-error" : void 0,
3367
+ "aria-invalid": errors.routePath ? "true" : "false"
3368
+ }
3369
+ ),
3370
+ errors.routePath && /* @__PURE__ */ jsx6("p", { id: "routePath-error", className: "mt-1 text-sm text-red-600", role: "alert", children: errors.routePath.message })
3371
+ ] }),
3372
+ /* @__PURE__ */ jsxs2("div", { children: [
3373
+ /* @__PURE__ */ jsx6("label", { htmlFor: "title", className: "block text-sm font-medium text-gray-700 mb-1", children: "Title" }),
3374
+ /* @__PURE__ */ jsx6(
3375
+ Input,
3376
+ {
3377
+ id: "title",
3378
+ ...register("title"),
3379
+ placeholder: "Page Title",
3380
+ maxLength: 60,
3381
+ "aria-describedby": "title-help title-count"
3382
+ }
3383
+ ),
3384
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between mt-1", children: [
3385
+ /* @__PURE__ */ jsx6("p", { id: "title-help", className: "text-xs text-gray-500", children: "Recommended: 50-60 characters" }),
3386
+ /* @__PURE__ */ jsxs2("span", { id: "title-count", className: "text-xs text-gray-500", "aria-live": "polite", children: [
3387
+ titleValue?.length || 0,
3388
+ "/60"
3389
+ ] })
3390
+ ] }),
3391
+ errors.title && /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-red-600", role: "alert", children: errors.title.message })
3392
+ ] }),
3393
+ /* @__PURE__ */ jsxs2("div", { children: [
3394
+ /* @__PURE__ */ jsx6("label", { htmlFor: "description", className: "block text-sm font-medium text-gray-700 mb-1", children: "Description" }),
3395
+ /* @__PURE__ */ jsx6(
3396
+ "textarea",
3397
+ {
3398
+ id: "description",
3399
+ ...register("description"),
3400
+ rows: 3,
3401
+ placeholder: "Page description",
3402
+ maxLength: 160,
3403
+ className: "w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500",
3404
+ "aria-describedby": "description-help description-count"
3405
+ }
3406
+ ),
3407
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between mt-1", children: [
3408
+ /* @__PURE__ */ jsx6("p", { id: "description-help", className: "text-xs text-gray-500", children: "Recommended: 150-160 characters" }),
3409
+ /* @__PURE__ */ jsxs2("span", { id: "description-count", className: "text-xs text-gray-500", "aria-live": "polite", children: [
3410
+ descriptionValue?.length || 0,
3411
+ "/160"
3412
+ ] })
3413
+ ] }),
3414
+ errors.description && /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-red-600", role: "alert", children: errors.description.message })
3415
+ ] }),
3416
+ /* @__PURE__ */ jsxs2("div", { children: [
3417
+ /* @__PURE__ */ jsx6("label", { htmlFor: "canonicalUrl", className: "block text-sm font-medium text-gray-700 mb-1", children: "Canonical URL" }),
3418
+ /* @__PURE__ */ jsx6(
3419
+ Input,
3420
+ {
3421
+ id: "canonicalUrl",
3422
+ type: "url",
3423
+ ...register("canonicalUrl"),
3424
+ placeholder: "https://example.com/page"
3425
+ }
3426
+ ),
3427
+ errors.canonicalUrl && /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-red-600", role: "alert", children: errors.canonicalUrl.message })
3428
+ ] })
3429
+ ] })
3430
+ ] }),
3431
+ /* @__PURE__ */ jsxs2(Card, { children: [
3432
+ /* @__PURE__ */ jsxs2(CardHeader, { children: [
3433
+ /* @__PURE__ */ jsx6(CardTitle, { children: "Open Graph" }),
3434
+ /* @__PURE__ */ jsx6(CardDescription, { children: "Social media sharing metadata" })
3435
+ ] }),
3436
+ /* @__PURE__ */ jsxs2(CardContent, { className: "space-y-4", children: [
3437
+ /* @__PURE__ */ jsxs2("div", { children: [
3438
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogType", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Type" }),
3439
+ /* @__PURE__ */ jsxs2(
3440
+ "select",
3441
+ {
3442
+ id: "ogType",
3443
+ ...register("ogType"),
3444
+ className: "w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500",
3445
+ children: [
3446
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Select type" }),
3447
+ /* @__PURE__ */ jsx6("option", { value: "website", children: "Website" }),
3448
+ /* @__PURE__ */ jsx6("option", { value: "article", children: "Article" }),
3449
+ /* @__PURE__ */ jsx6("option", { value: "product", children: "Product" }),
3450
+ /* @__PURE__ */ jsx6("option", { value: "book", children: "Book" }),
3451
+ /* @__PURE__ */ jsx6("option", { value: "profile", children: "Profile" }),
3452
+ /* @__PURE__ */ jsx6("option", { value: "music", children: "Music" }),
3453
+ /* @__PURE__ */ jsx6("option", { value: "video", children: "Video" })
3454
+ ]
3455
+ }
3456
+ )
3457
+ ] }),
3458
+ /* @__PURE__ */ jsxs2("div", { children: [
3459
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogTitle", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Title" }),
3460
+ /* @__PURE__ */ jsx6(
3461
+ Input,
3462
+ {
3463
+ id: "ogTitle",
3464
+ ...register("ogTitle"),
3465
+ placeholder: "Open Graph title",
3466
+ maxLength: 60,
3467
+ "aria-describedby": "ogTitle-count"
3468
+ }
3469
+ ),
3470
+ /* @__PURE__ */ jsxs2("span", { id: "ogTitle-count", className: "text-xs text-gray-500 mt-1 block", "aria-live": "polite", children: [
3471
+ ogTitleValue?.length || 0,
3472
+ "/60"
3473
+ ] })
3474
+ ] }),
3475
+ /* @__PURE__ */ jsxs2("div", { children: [
3476
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogDescription", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Description" }),
3477
+ /* @__PURE__ */ jsx6(
3478
+ "textarea",
3479
+ {
3480
+ id: "ogDescription",
3481
+ ...register("ogDescription"),
3482
+ rows: 3,
3483
+ placeholder: "Open Graph description",
3484
+ maxLength: 200,
3485
+ className: "w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500",
3486
+ "aria-describedby": "ogDescription-count"
3487
+ }
3488
+ ),
3489
+ /* @__PURE__ */ jsxs2("span", { id: "ogDescription-count", className: "text-xs text-gray-500 mt-1 block", "aria-live": "polite", children: [
3490
+ ogDescriptionValue?.length || 0,
3491
+ "/200"
3492
+ ] })
3493
+ ] }),
3494
+ /* @__PURE__ */ jsxs2("div", { children: [
3495
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogImageUrl", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Image URL" }),
3496
+ /* @__PURE__ */ jsx6(
3497
+ Input,
3498
+ {
3499
+ id: "ogImageUrl",
3500
+ type: "url",
3501
+ ...register("ogImageUrl"),
3502
+ placeholder: "https://example.com/image.jpg"
3503
+ }
3504
+ ),
3505
+ errors.ogImageUrl && /* @__PURE__ */ jsx6("p", { className: "mt-1 text-sm text-red-600", role: "alert", children: errors.ogImageUrl.message }),
3506
+ watch("ogImageUrl") && /* @__PURE__ */ jsx6("div", { className: "mt-4", children: /* @__PURE__ */ jsx6(
3507
+ OGImagePreview,
3508
+ {
3509
+ imageUrl: watch("ogImageUrl") || "",
3510
+ expectedWidth: watch("ogImageWidth"),
3511
+ expectedHeight: watch("ogImageHeight"),
3512
+ title: watch("ogTitle") || watch("title"),
3513
+ description: watch("ogDescription") || watch("description")
3514
+ }
3515
+ ) })
3516
+ ] }),
3517
+ /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 gap-4", children: [
3518
+ /* @__PURE__ */ jsxs2("div", { children: [
3519
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogImageWidth", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Image Width" }),
3520
+ /* @__PURE__ */ jsx6(
3521
+ Input,
3522
+ {
3523
+ id: "ogImageWidth",
3524
+ type: "number",
3525
+ ...register("ogImageWidth", { valueAsNumber: true }),
3526
+ placeholder: "1200"
3527
+ }
3528
+ )
3529
+ ] }),
3530
+ /* @__PURE__ */ jsxs2("div", { children: [
3531
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogImageHeight", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Image Height" }),
3532
+ /* @__PURE__ */ jsx6(
3533
+ Input,
3534
+ {
3535
+ id: "ogImageHeight",
3536
+ type: "number",
3537
+ ...register("ogImageHeight", { valueAsNumber: true }),
3538
+ placeholder: "630"
3539
+ }
3540
+ )
3541
+ ] })
3542
+ ] }),
3543
+ /* @__PURE__ */ jsxs2("div", { children: [
3544
+ /* @__PURE__ */ jsx6("label", { htmlFor: "ogSiteName", className: "block text-sm font-medium text-gray-700 mb-1", children: "OG Site Name" }),
3545
+ /* @__PURE__ */ jsx6(Input, { id: "ogSiteName", ...register("ogSiteName"), placeholder: "Site Name" })
3546
+ ] })
3547
+ ] })
3548
+ ] }),
3549
+ /* @__PURE__ */ jsxs2(Card, { children: [
3550
+ /* @__PURE__ */ jsxs2(CardHeader, { children: [
3551
+ /* @__PURE__ */ jsx6(CardTitle, { children: "Twitter Card" }),
3552
+ /* @__PURE__ */ jsx6(CardDescription, { children: "Twitter sharing metadata" })
3553
+ ] }),
3554
+ /* @__PURE__ */ jsxs2(CardContent, { className: "space-y-4", children: [
3555
+ /* @__PURE__ */ jsxs2("div", { children: [
3556
+ /* @__PURE__ */ jsx6("label", { htmlFor: "twitterCard", className: "block text-sm font-medium text-gray-700 mb-1", children: "Twitter Card Type" }),
3557
+ /* @__PURE__ */ jsxs2(
3558
+ "select",
3559
+ {
3560
+ id: "twitterCard",
3561
+ ...register("twitterCard"),
3562
+ className: "w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500",
3563
+ children: [
3564
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Select type" }),
3565
+ /* @__PURE__ */ jsx6("option", { value: "summary", children: "Summary" }),
3566
+ /* @__PURE__ */ jsx6("option", { value: "summary_large_image", children: "Summary Large Image" }),
3567
+ /* @__PURE__ */ jsx6("option", { value: "app", children: "App" }),
3568
+ /* @__PURE__ */ jsx6("option", { value: "player", children: "Player" })
3569
+ ]
3570
+ }
3571
+ )
3572
+ ] }),
3573
+ /* @__PURE__ */ jsxs2("div", { children: [
3574
+ /* @__PURE__ */ jsx6("label", { htmlFor: "twitterTitle", className: "block text-sm font-medium text-gray-700 mb-1", children: "Twitter Title" }),
3575
+ /* @__PURE__ */ jsx6(
3576
+ Input,
3577
+ {
3578
+ id: "twitterTitle",
3579
+ ...register("twitterTitle"),
3580
+ placeholder: "Twitter title",
3581
+ maxLength: 70
3582
+ }
3583
+ )
3584
+ ] }),
3585
+ /* @__PURE__ */ jsxs2("div", { children: [
3586
+ /* @__PURE__ */ jsx6("label", { htmlFor: "twitterDescription", className: "block text-sm font-medium text-gray-700 mb-1", children: "Twitter Description" }),
3587
+ /* @__PURE__ */ jsx6(
3588
+ "textarea",
3589
+ {
3590
+ id: "twitterDescription",
3591
+ ...register("twitterDescription"),
3592
+ rows: 3,
3593
+ placeholder: "Twitter description",
3594
+ maxLength: 200,
3595
+ className: "w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
3596
+ }
3597
+ )
3598
+ ] }),
3599
+ /* @__PURE__ */ jsxs2("div", { children: [
3600
+ /* @__PURE__ */ jsx6("label", { htmlFor: "twitterImageUrl", className: "block text-sm font-medium text-gray-700 mb-1", children: "Twitter Image URL" }),
3601
+ /* @__PURE__ */ jsx6(
3602
+ Input,
3603
+ {
3604
+ id: "twitterImageUrl",
3605
+ type: "url",
3606
+ ...register("twitterImageUrl"),
3607
+ placeholder: "https://example.com/twitter-image.jpg"
3608
+ }
3609
+ )
3610
+ ] })
3611
+ ] })
3612
+ ] }),
3613
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-4 justify-end", children: [
3614
+ onCancel && /* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", onClick: onCancel, children: "Cancel" }),
3615
+ /* @__PURE__ */ jsxs2(Button, { type: "submit", disabled: loading, children: [
3616
+ loading ? "Saving..." : isEditing ? "Update" : "Create",
3617
+ " SEO Record"
3618
+ ] })
3619
+ ] })
3620
+ ] });
3621
+ }
3622
+
3623
+ // src/components/seo/SEORecordList.tsx
3624
+ import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
3625
+ function SEORecordList() {
3626
+ const [records, setRecords] = useState3([]);
3627
+ const [loading, setLoading] = useState3(true);
3628
+ const [error, setError] = useState3(null);
3629
+ const [editingId, setEditingId] = useState3(null);
3630
+ const [showForm, setShowForm] = useState3(false);
3631
+ const fetchRecords = async () => {
3632
+ setLoading(true);
3633
+ setError(null);
3634
+ try {
3635
+ const response = await fetch("/api/seo-records");
3636
+ if (!response.ok) {
3637
+ throw new Error("Failed to fetch SEO records");
3638
+ }
3639
+ const data = await response.json();
3640
+ setRecords(data.data || []);
3641
+ } catch (err) {
3642
+ setError(err instanceof Error ? err.message : "An error occurred");
3643
+ } finally {
3644
+ setLoading(false);
3645
+ }
3646
+ };
3647
+ useEffect2(() => {
3648
+ fetchRecords();
3649
+ }, []);
3650
+ const handleDelete = async (id) => {
3651
+ if (!confirm("Are you sure you want to delete this SEO record?")) {
3652
+ return;
3653
+ }
3654
+ try {
3655
+ const response = await fetch(`/api/seo-records/${id}`, {
3656
+ method: "DELETE"
3657
+ });
3658
+ if (!response.ok) {
3659
+ throw new Error("Failed to delete SEO record");
3660
+ }
3661
+ await fetchRecords();
3662
+ } catch (err) {
3663
+ alert(err instanceof Error ? err.message : "Failed to delete record");
3664
+ }
3665
+ };
3666
+ const handleEdit = (id) => {
3667
+ setEditingId(id);
3668
+ setShowForm(true);
3669
+ };
3670
+ const handleFormSuccess = () => {
3671
+ setShowForm(false);
3672
+ setEditingId(null);
3673
+ fetchRecords();
3674
+ };
3675
+ const handleCancel = () => {
3676
+ setShowForm(false);
3677
+ setEditingId(null);
3678
+ };
3679
+ const editingRecord = editingId ? records.find((r) => r.id === editingId) || void 0 : void 0;
3680
+ if (showForm) {
3681
+ return /* @__PURE__ */ jsxs3("div", { children: [
3682
+ /* @__PURE__ */ jsx7(
3683
+ Button,
3684
+ {
3685
+ variant: "outline",
3686
+ onClick: handleCancel,
3687
+ className: "mb-4",
3688
+ children: "\u2190 Back to List"
3689
+ }
3690
+ ),
3691
+ /* @__PURE__ */ jsx7(
3692
+ SEORecordForm,
3693
+ {
3694
+ record: editingRecord,
3695
+ onSuccess: handleFormSuccess,
3696
+ onCancel: handleCancel
3697
+ }
3698
+ )
3699
+ ] });
3700
+ }
3701
+ if (loading) {
3702
+ return /* @__PURE__ */ jsx7("div", { className: "text-center py-8", children: /* @__PURE__ */ jsx7("p", { className: "text-gray-600", children: "Loading SEO records..." }) });
3703
+ }
3704
+ if (error) {
3705
+ return /* @__PURE__ */ jsxs3("div", { className: "bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg", children: [
3706
+ /* @__PURE__ */ jsxs3("p", { children: [
3707
+ "Error: ",
3708
+ error
3709
+ ] }),
3710
+ /* @__PURE__ */ jsx7(Button, { onClick: fetchRecords, variant: "outline", className: "mt-2", children: "Retry" })
3711
+ ] });
3712
+ }
3713
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-4", children: [
3714
+ /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center", children: [
3715
+ /* @__PURE__ */ jsx7("h2", { className: "text-2xl font-bold text-gray-900", children: "SEO Records" }),
3716
+ /* @__PURE__ */ jsx7(Button, { onClick: () => setShowForm(true), children: "Create New Record" })
3717
+ ] }),
3718
+ records.length === 0 ? /* @__PURE__ */ jsx7(Card, { children: /* @__PURE__ */ jsxs3(CardContent, { className: "py-8 text-center", children: [
3719
+ /* @__PURE__ */ jsx7("p", { className: "text-gray-600 mb-4", children: "No SEO records yet." }),
3720
+ /* @__PURE__ */ jsx7(Button, { onClick: () => setShowForm(true), children: "Create Your First Record" })
3721
+ ] }) }) : /* @__PURE__ */ jsx7("div", { className: "grid gap-4", children: records.map((record) => /* @__PURE__ */ jsxs3(Card, { children: [
3722
+ /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-start", children: [
3723
+ /* @__PURE__ */ jsxs3("div", { children: [
3724
+ /* @__PURE__ */ jsx7(CardTitle, { className: "text-lg", children: record.routePath }),
3725
+ record.title && /* @__PURE__ */ jsx7("p", { className: "text-sm text-gray-600 mt-1", children: record.title })
3726
+ ] }),
3727
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
3728
+ /* @__PURE__ */ jsx7(
3729
+ Button,
3730
+ {
3731
+ variant: "outline",
3732
+ size: "sm",
3733
+ onClick: () => record.id && handleEdit(record.id),
3734
+ children: "Edit"
3735
+ }
3736
+ ),
3737
+ /* @__PURE__ */ jsx7(
3738
+ Button,
3739
+ {
3740
+ variant: "outline",
3741
+ size: "sm",
3742
+ onClick: () => record.id && handleDelete(record.id),
3743
+ children: "Delete"
3744
+ }
3745
+ )
3746
+ ] })
3747
+ ] }) }),
3748
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsxs3("dl", { className: "grid grid-cols-2 gap-4 text-sm", children: [
3749
+ record.description && /* @__PURE__ */ jsxs3(Fragment, { children: [
3750
+ /* @__PURE__ */ jsx7("dt", { className: "text-gray-500", children: "Description" }),
3751
+ /* @__PURE__ */ jsx7("dd", { className: "text-gray-900", children: record.description })
3752
+ ] }),
3753
+ record.ogType && /* @__PURE__ */ jsxs3(Fragment, { children: [
3754
+ /* @__PURE__ */ jsx7("dt", { className: "text-gray-500", children: "OG Type" }),
3755
+ /* @__PURE__ */ jsx7("dd", { className: "text-gray-900", children: record.ogType })
3756
+ ] }),
3757
+ record.twitterCard && /* @__PURE__ */ jsxs3(Fragment, { children: [
3758
+ /* @__PURE__ */ jsx7("dt", { className: "text-gray-500", children: "Twitter Card" }),
3759
+ /* @__PURE__ */ jsx7("dd", { className: "text-gray-900", children: record.twitterCard })
3760
+ ] }),
3761
+ record.validationStatus && /* @__PURE__ */ jsxs3(Fragment, { children: [
3762
+ /* @__PURE__ */ jsx7("dt", { className: "text-gray-500", children: "Status" }),
3763
+ /* @__PURE__ */ jsx7("dd", { className: "text-gray-900", children: /* @__PURE__ */ jsx7(
3764
+ "span",
3765
+ {
3766
+ className: `inline-flex px-2 py-1 rounded text-xs font-medium ${record.validationStatus === "valid" ? "bg-green-100 text-green-800" : record.validationStatus === "invalid" ? "bg-red-100 text-red-800" : record.validationStatus === "warning" ? "bg-yellow-100 text-yellow-800" : "bg-gray-100 text-gray-800"}`,
3767
+ children: record.validationStatus
3768
+ }
3769
+ ) })
3770
+ ] })
3771
+ ] }) })
3772
+ ] }, record.id)) })
3773
+ ] });
3774
+ }
3775
+
3776
+ // src/components/seo/ValidationDashboard.tsx
3777
+ import { useState as useState4, useEffect as useEffect3 } from "react";
3778
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
3779
+ var Link = ({ href, children, ...props }) => /* @__PURE__ */ jsx8("a", { href, ...props, children });
3780
+ function ValidationDashboard() {
3781
+ const [records, setRecords] = useState4([]);
3782
+ const [loading, setLoading] = useState4(true);
3783
+ const [validating, setValidating] = useState4({});
3784
+ const [validationResults, setValidationResults] = useState4({});
3785
+ useEffect3(() => {
3786
+ fetchRecords();
3787
+ }, []);
3788
+ const fetchRecords = async () => {
3789
+ setLoading(true);
3790
+ try {
3791
+ const response = await fetch("/api/seo-records");
3792
+ if (response.ok) {
3793
+ const data = await response.json();
3794
+ setRecords(data.data || []);
3795
+ }
3796
+ } catch {
3797
+ } finally {
3798
+ setLoading(false);
3799
+ }
3800
+ };
3801
+ const validateRecord = async (record) => {
3802
+ if (!record.id) return;
3803
+ setValidating((prev) => ({ ...prev, [record.id]: true }));
3804
+ try {
3805
+ const url = record.canonicalUrl || `https://example.com${record.routePath}`;
3806
+ const response = await fetch(`/api/seo-records/${record.id}/validate`, {
3807
+ method: "POST",
3808
+ headers: { "Content-Type": "application/json" },
3809
+ body: JSON.stringify({ url })
3810
+ });
3811
+ if (response.ok) {
3812
+ const data = await response.json();
3813
+ setValidationResults((prev) => ({
3814
+ ...prev,
3815
+ [record.id]: data
3816
+ }));
3817
+ const allIssues2 = data.validations.flatMap((v) => v.result.issues);
3818
+ const hasCritical = allIssues2.some((i) => i.severity === "critical");
3819
+ const hasWarnings = allIssues2.some((i) => i.severity === "warning");
3820
+ const validationStatus = hasCritical ? "invalid" : hasWarnings ? "warning" : "valid";
3821
+ await fetch(`/api/seo-records/${record.id}/update-validation`, {
3822
+ method: "PATCH",
3823
+ headers: { "Content-Type": "application/json" },
3824
+ body: JSON.stringify({
3825
+ validationStatus,
3826
+ validationErrors: allIssues2.reduce((acc, issue) => {
3827
+ acc[issue.field] = issue;
3828
+ return acc;
3829
+ }, {}),
3830
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
3831
+ })
3832
+ });
3833
+ fetchRecords();
3834
+ }
3835
+ } catch {
3836
+ } finally {
3837
+ setValidating((prev) => ({ ...prev, [record.id]: false }));
3838
+ }
3839
+ };
3840
+ const getSeverityColor = (severity) => {
3841
+ switch (severity) {
3842
+ case "critical":
3843
+ return "bg-red-100 text-red-800 border-red-200";
3844
+ case "warning":
3845
+ return "bg-yellow-100 text-yellow-800 border-yellow-200";
3846
+ case "info":
3847
+ return "bg-blue-100 text-blue-800 border-blue-200";
3848
+ default:
3849
+ return "bg-gray-100 text-gray-800 border-gray-200";
3850
+ }
3851
+ };
3852
+ const getSeverityIcon = (severity) => {
3853
+ switch (severity) {
3854
+ case "critical":
3855
+ return "\u274C";
3856
+ case "warning":
3857
+ return "\u26A0\uFE0F";
3858
+ case "info":
3859
+ return "\u2139\uFE0F";
3860
+ default:
3861
+ return "\u2022";
3862
+ }
3863
+ };
3864
+ if (loading) {
3865
+ return /* @__PURE__ */ jsxs4("div", { className: "text-center py-8", children: [
3866
+ /* @__PURE__ */ jsx8(Spinner, {}),
3867
+ /* @__PURE__ */ jsx8("p", { className: "text-gray-600 mt-4", children: "Loading records..." })
3868
+ ] });
3869
+ }
3870
+ const allIssues = [];
3871
+ Object.entries(validationResults).forEach(([recordId, validation]) => {
3872
+ const record = records.find((r) => r.id === recordId);
3873
+ if (record) {
3874
+ validation.validations.forEach((val) => {
3875
+ val.result.issues.forEach((issue) => {
3876
+ allIssues.push({
3877
+ record,
3878
+ issue,
3879
+ validationType: val.type
3880
+ });
3881
+ });
3882
+ });
3883
+ }
3884
+ });
3885
+ const criticalIssues = allIssues.filter((i) => i.issue.severity === "critical");
3886
+ const warningIssues = allIssues.filter((i) => i.issue.severity === "warning");
3887
+ const infoIssues = allIssues.filter((i) => i.issue.severity === "info");
3888
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-6", children: [
3889
+ /* @__PURE__ */ jsxs4("div", { className: "flex justify-between items-center", children: [
3890
+ /* @__PURE__ */ jsxs4("div", { children: [
3891
+ /* @__PURE__ */ jsx8("h2", { className: "text-2xl font-bold text-gray-900", children: "Validation Dashboard" }),
3892
+ /* @__PURE__ */ jsx8("p", { className: "text-gray-600 mt-1", children: "Validate your SEO records against live pages" })
3893
+ ] }),
3894
+ /* @__PURE__ */ jsx8(Button, { onClick: fetchRecords, variant: "outline", children: "Refresh Records" })
3895
+ ] }),
3896
+ /* @__PURE__ */ jsxs4("div", { className: "grid gap-4 md:grid-cols-4", children: [
3897
+ /* @__PURE__ */ jsx8(Card, { children: /* @__PURE__ */ jsx8(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxs4("div", { className: "text-center", children: [
3898
+ /* @__PURE__ */ jsx8("div", { className: "text-3xl font-bold text-gray-900", children: records.length }),
3899
+ /* @__PURE__ */ jsx8("div", { className: "text-sm text-gray-600 mt-1", children: "Total Records" })
3900
+ ] }) }) }),
3901
+ /* @__PURE__ */ jsx8(Card, { children: /* @__PURE__ */ jsx8(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxs4("div", { className: "text-center", children: [
3902
+ /* @__PURE__ */ jsx8("div", { className: "text-3xl font-bold text-red-600", children: criticalIssues.length }),
3903
+ /* @__PURE__ */ jsx8("div", { className: "text-sm text-gray-600 mt-1", children: "Critical Issues" })
3904
+ ] }) }) }),
3905
+ /* @__PURE__ */ jsx8(Card, { children: /* @__PURE__ */ jsx8(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxs4("div", { className: "text-center", children: [
3906
+ /* @__PURE__ */ jsx8("div", { className: "text-3xl font-bold text-yellow-600", children: warningIssues.length }),
3907
+ /* @__PURE__ */ jsx8("div", { className: "text-sm text-gray-600 mt-1", children: "Warnings" })
3908
+ ] }) }) }),
3909
+ /* @__PURE__ */ jsx8(Card, { children: /* @__PURE__ */ jsx8(CardContent, { className: "pt-6", children: /* @__PURE__ */ jsxs4("div", { className: "text-center", children: [
3910
+ /* @__PURE__ */ jsx8("div", { className: "text-3xl font-bold text-blue-600", children: infoIssues.length }),
3911
+ /* @__PURE__ */ jsx8("div", { className: "text-sm text-gray-600 mt-1", children: "Info" })
3912
+ ] }) }) })
3913
+ ] }),
3914
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
3915
+ /* @__PURE__ */ jsx8("h3", { className: "text-lg font-semibold text-gray-900", children: "SEO Records" }),
3916
+ records.length === 0 ? /* @__PURE__ */ jsx8(Card, { children: /* @__PURE__ */ jsxs4(CardContent, { className: "py-8 text-center", children: [
3917
+ /* @__PURE__ */ jsx8("p", { className: "text-gray-600", children: "No SEO records found." }),
3918
+ /* @__PURE__ */ jsx8(Link, { href: "/dashboard", children: /* @__PURE__ */ jsx8(Button, { className: "mt-4", children: "Create Your First Record" }) })
3919
+ ] }) }) : records.map((record) => {
3920
+ const result = record.id ? validationResults[record.id] : void 0;
3921
+ const hasIssues = result?.validations.some((v) => v.result.issues.length > 0);
3922
+ return /* @__PURE__ */ jsxs4(Card, { children: [
3923
+ /* @__PURE__ */ jsx8(CardHeader, { children: /* @__PURE__ */ jsxs4("div", { className: "flex justify-between items-start", children: [
3924
+ /* @__PURE__ */ jsxs4("div", { children: [
3925
+ /* @__PURE__ */ jsx8(CardTitle, { className: "text-lg", children: record.routePath }),
3926
+ record.title && /* @__PURE__ */ jsx8(CardDescription, { className: "mt-1", children: record.title })
3927
+ ] }),
3928
+ /* @__PURE__ */ jsx8(
3929
+ Button,
3930
+ {
3931
+ onClick: () => validateRecord(record),
3932
+ disabled: validating[record.id],
3933
+ size: "sm",
3934
+ children: validating[record.id] ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
3935
+ /* @__PURE__ */ jsx8(Spinner, { className: "mr-2" }),
3936
+ "Validating..."
3937
+ ] }) : "Validate"
3938
+ }
3939
+ )
3940
+ ] }) }),
3941
+ result && /* @__PURE__ */ jsxs4(CardContent, { children: [
3942
+ /* @__PURE__ */ jsx8("div", { className: "space-y-4", children: result.validations.map((validation, idx) => /* @__PURE__ */ jsxs4("div", { className: "border-t pt-4", children: [
3943
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 mb-2", children: [
3944
+ /* @__PURE__ */ jsxs4("span", { className: "font-medium text-sm text-gray-700", children: [
3945
+ validation.type.toUpperCase(),
3946
+ " Validation"
3947
+ ] }),
3948
+ validation.result.isValid ? /* @__PURE__ */ jsx8("span", { className: "text-xs bg-green-100 text-green-800 px-2 py-1 rounded", children: "Valid" }) : /* @__PURE__ */ jsx8("span", { className: "text-xs bg-red-100 text-red-800 px-2 py-1 rounded", children: "Issues Found" })
3949
+ ] }),
3950
+ validation.result.issues.length > 0 && /* @__PURE__ */ jsx8("div", { className: "space-y-2", children: validation.result.issues.map((issue, issueIdx) => /* @__PURE__ */ jsx8(
3951
+ "div",
3952
+ {
3953
+ className: `p-3 rounded border ${getSeverityColor(issue.severity)}`,
3954
+ children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-2", children: [
3955
+ /* @__PURE__ */ jsx8("span", { children: getSeverityIcon(issue.severity) }),
3956
+ /* @__PURE__ */ jsxs4("div", { className: "flex-1", children: [
3957
+ /* @__PURE__ */ jsx8("div", { className: "font-medium text-sm", children: issue.field }),
3958
+ /* @__PURE__ */ jsx8("div", { className: "text-sm mt-1", children: issue.message }),
3959
+ issue.expected && /* @__PURE__ */ jsxs4("div", { className: "text-xs mt-1", children: [
3960
+ "Expected: ",
3961
+ /* @__PURE__ */ jsx8("code", { className: "bg-white/50 px-1 rounded", children: issue.expected })
3962
+ ] }),
3963
+ issue.actual && /* @__PURE__ */ jsxs4("div", { className: "text-xs mt-1", children: [
3964
+ "Actual: ",
3965
+ /* @__PURE__ */ jsx8("code", { className: "bg-white/50 px-1 rounded", children: issue.actual })
3966
+ ] })
3967
+ ] })
3968
+ ] })
3969
+ },
3970
+ issueIdx
3971
+ )) }),
3972
+ validation.result.metadata && /* @__PURE__ */ jsxs4("div", { className: "mt-2 text-xs text-gray-600", children: [
3973
+ "Image: ",
3974
+ validation.result.metadata.width,
3975
+ "x",
3976
+ validation.result.metadata.height,
3977
+ " (",
3978
+ validation.result.metadata.format,
3979
+ validation.result.metadata.size ? `, ${(validation.result.metadata.size / 1024).toFixed(1)}KB` : "",
3980
+ ")"
3981
+ ] })
3982
+ ] }, idx)) }),
3983
+ hasIssues && /* @__PURE__ */ jsx8("div", { className: "mt-4", children: /* @__PURE__ */ jsx8(Link, { href: `/dashboard?edit=${record.id}`, children: /* @__PURE__ */ jsx8(Button, { variant: "outline", size: "sm", children: "Edit Record" }) }) })
3984
+ ] })
3985
+ ] }, record.id);
3986
+ })
3987
+ ] })
3988
+ ] });
3989
+ }
3990
+
3991
+ // src/lib/validation/image-validator.ts
3992
+ import sharp from "sharp";
3993
+ async function validateOGImage(imageUrl, expectedWidth, expectedHeight) {
3994
+ const issues = [];
3995
+ try {
3996
+ const response = await fetch(imageUrl, {
3997
+ headers: {
3998
+ "User-Agent": "Mozilla/5.0 (compatible; SEO-Console/1.0; +https://example.com/bot)"
3999
+ },
4000
+ signal: AbortSignal.timeout(15e3)
4001
+ // 15 second timeout for images
4002
+ });
4003
+ if (!response.ok) {
4004
+ return {
4005
+ isValid: false,
4006
+ issues: [
4007
+ {
4008
+ field: "image",
4009
+ severity: "critical",
4010
+ message: `Failed to fetch image: ${response.status} ${response.statusText}`,
4011
+ actual: imageUrl
4012
+ }
4013
+ ]
4014
+ };
4015
+ }
4016
+ const imageBuffer = await response.arrayBuffer();
4017
+ const buffer = Buffer.from(imageBuffer);
4018
+ const sizeInMB = buffer.length / (1024 * 1024);
4019
+ if (sizeInMB > 1) {
4020
+ issues.push({
4021
+ field: "image",
4022
+ severity: "warning",
4023
+ message: "Image file size exceeds 1MB recommendation",
4024
+ actual: `${sizeInMB.toFixed(2)}MB`
4025
+ });
4026
+ }
4027
+ const metadata = await sharp(buffer).metadata();
4028
+ const { width, height, format } = metadata;
4029
+ if (!width || !height) {
4030
+ return {
4031
+ isValid: false,
4032
+ issues: [
4033
+ {
4034
+ field: "image",
4035
+ severity: "critical",
4036
+ message: "Could not determine image dimensions",
4037
+ actual: imageUrl
4038
+ }
4039
+ ]
4040
+ };
4041
+ }
4042
+ const recommendedWidth = 1200;
4043
+ const recommendedHeight = 630;
4044
+ const aspectRatio = width / height;
4045
+ const recommendedAspectRatio = recommendedWidth / recommendedHeight;
4046
+ if (width < recommendedWidth || height < recommendedHeight) {
4047
+ issues.push({
4048
+ field: "image",
4049
+ severity: "warning",
4050
+ message: `Image dimensions below recommended size (${recommendedWidth}x${recommendedHeight})`,
4051
+ expected: `${recommendedWidth}x${recommendedHeight}`,
4052
+ actual: `${width}x${height}`
4053
+ });
4054
+ }
4055
+ if (Math.abs(aspectRatio - recommendedAspectRatio) > 0.1) {
4056
+ issues.push({
4057
+ field: "image",
4058
+ severity: "info",
4059
+ message: "Image aspect ratio differs from recommended 1.91:1",
4060
+ expected: "1.91:1",
4061
+ actual: `${aspectRatio.toFixed(2)}:1`
4062
+ });
4063
+ }
4064
+ const supportedFormats = ["jpeg", "jpg", "png", "webp", "avif", "gif"];
4065
+ if (!format || !supportedFormats.includes(format.toLowerCase())) {
4066
+ issues.push({
4067
+ field: "image",
4068
+ severity: "warning",
4069
+ message: "Image format may not be optimal for social sharing",
4070
+ expected: "JPEG, PNG, WebP, or AVIF",
4071
+ actual: format || "unknown"
4072
+ });
4073
+ }
4074
+ if (expectedWidth && width !== expectedWidth) {
4075
+ issues.push({
4076
+ field: "image",
4077
+ severity: "warning",
4078
+ message: "Image width does not match expected value",
4079
+ expected: `${expectedWidth}px`,
4080
+ actual: `${width}px`
4081
+ });
4082
+ }
4083
+ if (expectedHeight && height !== expectedHeight) {
4084
+ issues.push({
4085
+ field: "image",
4086
+ severity: "warning",
4087
+ message: "Image height does not match expected value",
4088
+ expected: `${expectedHeight}px`,
4089
+ actual: `${height}px`
4090
+ });
4091
+ }
4092
+ return {
4093
+ isValid: issues.filter((i) => i.severity === "critical").length === 0,
4094
+ issues,
4095
+ metadata: {
4096
+ width,
4097
+ height,
4098
+ format: format || "unknown",
4099
+ size: buffer.length
4100
+ }
4101
+ };
4102
+ } catch (error) {
4103
+ return {
4104
+ isValid: false,
4105
+ issues: [
4106
+ {
4107
+ field: "image",
4108
+ severity: "critical",
4109
+ message: error instanceof Error ? error.message : "Failed to validate image",
4110
+ actual: imageUrl
4111
+ }
4112
+ ]
4113
+ };
4114
+ }
4115
+ }
4116
+
4117
+ // src/lib/validation/html-validator.ts
4118
+ import * as cheerio from "cheerio";
4119
+ async function validateHTML(html, record, _baseUrl) {
4120
+ const issues = [];
4121
+ const $ = cheerio.load(html);
4122
+ const title = $("title").text().trim();
4123
+ if (record.title) {
4124
+ if (!title) {
4125
+ issues.push({
4126
+ field: "title",
4127
+ severity: "critical",
4128
+ message: "Title tag is missing",
4129
+ expected: record.title
4130
+ });
4131
+ } else if (title !== record.title) {
4132
+ issues.push({
4133
+ field: "title",
4134
+ severity: "warning",
4135
+ message: "Title tag does not match SEO record",
4136
+ expected: record.title,
4137
+ actual: title
4138
+ });
4139
+ }
4140
+ if (title.length > 60) {
4141
+ issues.push({
4142
+ field: "title",
4143
+ severity: "warning",
4144
+ message: "Title exceeds recommended 60 characters",
4145
+ actual: `${title.length} characters`
4146
+ });
4147
+ }
4148
+ }
4149
+ const metaDescription = $('meta[name="description"]').attr("content")?.trim();
4150
+ if (record.description) {
4151
+ if (!metaDescription) {
4152
+ issues.push({
4153
+ field: "description",
4154
+ severity: "critical",
4155
+ message: "Meta description is missing",
4156
+ expected: record.description
4157
+ });
4158
+ } else if (metaDescription !== record.description) {
4159
+ issues.push({
4160
+ field: "description",
4161
+ severity: "warning",
4162
+ message: "Meta description does not match SEO record",
4163
+ expected: record.description,
4164
+ actual: metaDescription
4165
+ });
4166
+ }
4167
+ if (metaDescription && metaDescription.length > 160) {
4168
+ issues.push({
4169
+ field: "description",
4170
+ severity: "warning",
4171
+ message: "Description exceeds recommended 160 characters",
4172
+ actual: `${metaDescription.length} characters`
4173
+ });
4174
+ }
4175
+ }
4176
+ if (record.ogTitle || record.ogDescription || record.ogImageUrl) {
4177
+ const ogTitle = $('meta[property="og:title"]').attr("content");
4178
+ const ogDescription = $('meta[property="og:description"]').attr("content");
4179
+ const ogImage = $('meta[property="og:image"]').attr("content");
4180
+ const ogType = $('meta[property="og:type"]').attr("content");
4181
+ const ogUrl = $('meta[property="og:url"]').attr("content");
4182
+ if (record.ogTitle && !ogTitle) {
4183
+ issues.push({
4184
+ field: "og:title",
4185
+ severity: "critical",
4186
+ message: "Open Graph title is missing",
4187
+ expected: record.ogTitle
4188
+ });
4189
+ }
4190
+ if (record.ogDescription && !ogDescription) {
4191
+ issues.push({
4192
+ field: "og:description",
4193
+ severity: "warning",
4194
+ message: "Open Graph description is missing",
4195
+ expected: record.ogDescription
4196
+ });
4197
+ }
4198
+ if (record.ogImageUrl && !ogImage) {
4199
+ issues.push({
4200
+ field: "og:image",
4201
+ severity: "critical",
4202
+ message: "Open Graph image is missing",
4203
+ expected: record.ogImageUrl
4204
+ });
4205
+ }
4206
+ if (record.ogType && ogType !== record.ogType) {
4207
+ issues.push({
4208
+ field: "og:type",
4209
+ severity: "warning",
4210
+ message: "Open Graph type does not match",
4211
+ expected: record.ogType,
4212
+ actual: ogType
4213
+ });
4214
+ }
4215
+ if (record.ogUrl && ogUrl !== record.ogUrl) {
4216
+ issues.push({
4217
+ field: "og:url",
4218
+ severity: "warning",
4219
+ message: "Open Graph URL does not match",
4220
+ expected: record.ogUrl,
4221
+ actual: ogUrl
4222
+ });
4223
+ }
4224
+ }
4225
+ if (record.twitterCard || record.twitterTitle || record.twitterImageUrl) {
4226
+ const twitterCard = $('meta[name="twitter:card"]').attr("content");
4227
+ const twitterTitle = $('meta[name="twitter:title"]').attr("content");
4228
+ const _twitterDescription = $('meta[name="twitter:description"]').attr("content");
4229
+ const twitterImage = $('meta[name="twitter:image"]').attr("content");
4230
+ if (record.twitterCard && twitterCard !== record.twitterCard) {
4231
+ issues.push({
4232
+ field: "twitter:card",
4233
+ severity: "warning",
4234
+ message: "Twitter card type does not match",
4235
+ expected: record.twitterCard,
4236
+ actual: twitterCard
4237
+ });
4238
+ }
4239
+ if (record.twitterTitle && !twitterTitle) {
4240
+ issues.push({
4241
+ field: "twitter:title",
4242
+ severity: "warning",
4243
+ message: "Twitter title is missing",
4244
+ expected: record.twitterTitle
4245
+ });
4246
+ }
4247
+ if (record.twitterImageUrl && !twitterImage) {
4248
+ issues.push({
4249
+ field: "twitter:image",
4250
+ severity: "warning",
4251
+ message: "Twitter image is missing",
4252
+ expected: record.twitterImageUrl
4253
+ });
4254
+ }
4255
+ }
4256
+ if (record.canonicalUrl) {
4257
+ const canonical = $('link[rel="canonical"]').attr("href");
4258
+ if (!canonical) {
4259
+ issues.push({
4260
+ field: "canonical",
4261
+ severity: "critical",
4262
+ message: "Canonical URL is missing",
4263
+ expected: record.canonicalUrl
4264
+ });
4265
+ } else if (canonical !== record.canonicalUrl) {
4266
+ issues.push({
4267
+ field: "canonical",
4268
+ severity: "warning",
4269
+ message: "Canonical URL does not match",
4270
+ expected: record.canonicalUrl,
4271
+ actual: canonical
4272
+ });
4273
+ }
4274
+ if (canonical && !canonical.startsWith("http://") && !canonical.startsWith("https://")) {
4275
+ issues.push({
4276
+ field: "canonical",
4277
+ severity: "warning",
4278
+ message: "Canonical URL should be absolute",
4279
+ actual: canonical
4280
+ });
4281
+ }
4282
+ }
4283
+ return {
4284
+ isValid: issues.filter((i) => i.severity === "critical").length === 0,
4285
+ issues,
4286
+ validatedAt: /* @__PURE__ */ new Date()
4287
+ };
4288
+ }
4289
+ export {
4290
+ Button,
4291
+ Card,
4292
+ CardContent,
4293
+ CardDescription,
4294
+ CardFooter,
4295
+ CardHeader,
4296
+ CardTitle,
4297
+ Input,
4298
+ OGImagePreview,
4299
+ SEORecordForm,
4300
+ SEORecordList,
4301
+ Spinner,
4302
+ ValidationDashboard,
4303
+ createSEORecord,
4304
+ createSEORecordSchema,
4305
+ deleteSEORecord,
4306
+ getSEORecords as getAllSEORecords,
4307
+ getRoutePathFromParams,
4308
+ getSEORecordById,
4309
+ getSEORecordByRoute,
4310
+ updateSEORecord,
4311
+ updateSEORecordSchema,
4312
+ useGenerateMetadata,
4313
+ validateHTML,
4314
+ validateOGImage
4315
+ };
4316
+ //# sourceMappingURL=index.mjs.map