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