metabinaries 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +32 -0
- package/src/constants.js +94 -0
- package/src/index.js +219 -0
- package/src/templates/app-clean.js +1066 -0
- package/src/templates/configs.js +391 -0
- package/src/templates/core-clean.js +269 -0
- package/src/templates/feature-ai-chat.js +1786 -0
- package/src/templates/folder-structure.js +29 -0
- package/src/templates/layout-clean.js +788 -0
- package/src/templates/misc.js +275 -0
- package/src/templates/packages.js +74 -0
- package/src/templates/ui-2.js +585 -0
- package/src/templates/ui-3.js +606 -0
- package/src/templates/ui-4.js +1025 -0
- package/src/templates/ui.js +777 -0
- package/src/utils.js +38 -0
|
@@ -0,0 +1,1066 @@
|
|
|
1
|
+
// app-clean.js - FIXED VERSION with NO syntax errors
|
|
2
|
+
export const appTemplates = {
|
|
3
|
+
'app/layout.tsx': `import './globals.css';
|
|
4
|
+
import { Providers } from '@/lib/providers';
|
|
5
|
+
import type { Viewport } from 'next';
|
|
6
|
+
import { Cairo } from 'next/font/google';
|
|
7
|
+
|
|
8
|
+
const cairo = Cairo({
|
|
9
|
+
subsets: ['arabic', 'latin'],
|
|
10
|
+
display: 'swap',
|
|
11
|
+
variable: '--font-cairo',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const viewport: Viewport = {
|
|
15
|
+
width: 'device-width',
|
|
16
|
+
initialScale: 1,
|
|
17
|
+
maximumScale: 5,
|
|
18
|
+
themeColor: [
|
|
19
|
+
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
|
|
20
|
+
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default function RootLayout({
|
|
25
|
+
children,
|
|
26
|
+
}: Readonly<{
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}>) {
|
|
29
|
+
return (
|
|
30
|
+
<html lang='en' suppressHydrationWarning className={cairo.variable}>
|
|
31
|
+
<body className={\`\${cairo.className} antialiased\`}>
|
|
32
|
+
<Providers>
|
|
33
|
+
{children}
|
|
34
|
+
</Providers>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
);
|
|
38
|
+
}`,
|
|
39
|
+
|
|
40
|
+
'app/not-found.tsx': `'use client';
|
|
41
|
+
|
|
42
|
+
import { motion } from 'framer-motion';
|
|
43
|
+
import { Button } from '@/components/ui/button';
|
|
44
|
+
import { Home, ArrowLeft } from 'lucide-react';
|
|
45
|
+
import Link from 'next/link';
|
|
46
|
+
import { useRouter } from 'next/navigation';
|
|
47
|
+
import { useCallback, useMemo } from 'react';
|
|
48
|
+
|
|
49
|
+
export default function NotFound() {
|
|
50
|
+
const router = useRouter();
|
|
51
|
+
|
|
52
|
+
const handleGoBack = useCallback(() => {
|
|
53
|
+
router.back();
|
|
54
|
+
}, [router]);
|
|
55
|
+
|
|
56
|
+
const floatingElements = useMemo(() =>
|
|
57
|
+
Array.from({ length: 6 }, (_, i) => ({
|
|
58
|
+
id: i,
|
|
59
|
+
x: \`\${Math.random() * 100}%\`,
|
|
60
|
+
y: \`\${Math.random() * 100}%\`,
|
|
61
|
+
duration: 3 + Math.random() * 2,
|
|
62
|
+
delay: Math.random() * 2
|
|
63
|
+
})), []
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className='relative min-h-screen w-full flex items-center justify-center overflow-hidden bg-white dark:bg-slate-950 px-6'
|
|
69
|
+
role="main"
|
|
70
|
+
aria-labelledby="error-heading"
|
|
71
|
+
>
|
|
72
|
+
<div className='absolute inset-0 overflow-hidden pointer-events-none' aria-hidden="true">
|
|
73
|
+
<div className='absolute top-[-10%] right-[-10%] w-[40%] h-[40%] rounded-full bg-blue-500/10 dark:bg-blue-500/20 blur-[100px] animate-pulse' />
|
|
74
|
+
<div className='absolute bottom-[-10%] left-[-10%] w-[40%] h-[40%] rounded-full bg-indigo-500/10 dark:bg-indigo-500/20 blur-[100px] animate-pulse' />
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className='relative z-10 max-w-2xl w-full text-center'>
|
|
78
|
+
<motion.div
|
|
79
|
+
initial={{ opacity: 0, scale: 0.5 }}
|
|
80
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
81
|
+
transition={{ duration: 0.8, ease: "easeOut" }}
|
|
82
|
+
className='relative'
|
|
83
|
+
>
|
|
84
|
+
<h1
|
|
85
|
+
className='text-[12rem] md:text-[16rem] font-black leading-none tracking-tighter text-slate-100 dark:text-slate-900 select-none'
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
>
|
|
88
|
+
404
|
|
89
|
+
</h1>
|
|
90
|
+
|
|
91
|
+
<motion.div
|
|
92
|
+
initial={{ y: 20, opacity: 0 }}
|
|
93
|
+
animate={{ y: 0, opacity: 1 }}
|
|
94
|
+
transition={{ delay: 0.5, duration: 0.5 }}
|
|
95
|
+
className='absolute inset-0 flex items-center justify-center'
|
|
96
|
+
>
|
|
97
|
+
<h2
|
|
98
|
+
id="error-heading"
|
|
99
|
+
className='text-4xl md:text-6xl font-bold bg-gradient-to-b from-slate-900 to-slate-600 dark:from-white dark:to-slate-400 bg-clip-text text-transparent'
|
|
100
|
+
>
|
|
101
|
+
Lost in Space
|
|
102
|
+
</h2>
|
|
103
|
+
</motion.div>
|
|
104
|
+
</motion.div>
|
|
105
|
+
|
|
106
|
+
<motion.div
|
|
107
|
+
initial={{ opacity: 0, y: 20 }}
|
|
108
|
+
animate={{ opacity: 1, y: 0 }}
|
|
109
|
+
transition={{ delay: 0.7, duration: 0.5 }}
|
|
110
|
+
className='mt-8 space-y-6'
|
|
111
|
+
>
|
|
112
|
+
<p className='text-lg md:text-xl text-slate-500 dark:text-slate-400 max-w-md mx-auto'>
|
|
113
|
+
The page you are looking for doesn't exist or has been moved to another quadrant.
|
|
114
|
+
</p>
|
|
115
|
+
|
|
116
|
+
<div className='flex flex-wrap items-center justify-center gap-4 pt-4'>
|
|
117
|
+
<Button
|
|
118
|
+
asChild
|
|
119
|
+
size='lg'
|
|
120
|
+
className='h-12 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-semibold shadow-lg shadow-blue-500/25 transition-all'
|
|
121
|
+
>
|
|
122
|
+
<Link href='/en' prefetch={true}>
|
|
123
|
+
<Home className='mr-2 w-4 h-4' aria-hidden="true" />
|
|
124
|
+
Back Home
|
|
125
|
+
</Link>
|
|
126
|
+
</Button>
|
|
127
|
+
|
|
128
|
+
<Button
|
|
129
|
+
onClick={handleGoBack}
|
|
130
|
+
variant='outline'
|
|
131
|
+
size='lg'
|
|
132
|
+
className='h-12 px-6 rounded-full border-slate-200 dark:border-slate-800 text-slate-700 dark:text-slate-300 font-semibold hover:bg-slate-50 hover:text-slate-900 dark:hover:bg-slate-900 dark:hover:text-white transition-all'
|
|
133
|
+
>
|
|
134
|
+
<ArrowLeft className='mr-2 w-4 h-4' aria-hidden="true" />
|
|
135
|
+
Go Back
|
|
136
|
+
</Button>
|
|
137
|
+
</div>
|
|
138
|
+
</motion.div>
|
|
139
|
+
|
|
140
|
+
<div className='absolute -z-10 top-0 left-0 w-full h-full pointer-events-none' aria-hidden="true">
|
|
141
|
+
{floatingElements.map((el) => (
|
|
142
|
+
<motion.div
|
|
143
|
+
key={el.id}
|
|
144
|
+
className='absolute w-2 h-2 bg-blue-500/30 rounded-full'
|
|
145
|
+
initial={{
|
|
146
|
+
x: el.x,
|
|
147
|
+
y: el.y,
|
|
148
|
+
opacity: 0
|
|
149
|
+
}}
|
|
150
|
+
animate={{
|
|
151
|
+
y: [null, '-20px', '20px'],
|
|
152
|
+
opacity: [0, 1, 0]
|
|
153
|
+
}}
|
|
154
|
+
transition={{
|
|
155
|
+
duration: el.duration,
|
|
156
|
+
repeat: Infinity,
|
|
157
|
+
delay: el.delay,
|
|
158
|
+
ease: "easeInOut"
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}`,
|
|
167
|
+
|
|
168
|
+
'app/robots.ts': `import { MetadataRoute } from 'next';
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Robots Configuration - Production Ready
|
|
172
|
+
*
|
|
173
|
+
* Advanced configuration for proper indexing while protecting
|
|
174
|
+
* sensitive routes and managing crawler behavior.
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
const APP_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://metabinaries.com';
|
|
178
|
+
|
|
179
|
+
const IS_PRODUCTION =
|
|
180
|
+
process.env.NODE_ENV === 'production' &&
|
|
181
|
+
(process.env.VERCEL_ENV === 'production' || !process.env.VERCEL_ENV);
|
|
182
|
+
|
|
183
|
+
export default function robots(): MetadataRoute.Robots {
|
|
184
|
+
// Block all indexing in non-production environments
|
|
185
|
+
if (!IS_PRODUCTION) {
|
|
186
|
+
return {
|
|
187
|
+
rules: {
|
|
188
|
+
userAgent: '*',
|
|
189
|
+
disallow: '/',
|
|
190
|
+
},
|
|
191
|
+
sitemap: \`\${APP_URL}/sitemap.xml\`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
rules: [
|
|
197
|
+
// Main crawlers configuration
|
|
198
|
+
{
|
|
199
|
+
userAgent: '*',
|
|
200
|
+
allow: [
|
|
201
|
+
'/',
|
|
202
|
+
'/api/og', // Allow OG image generation for social sharing
|
|
203
|
+
'/public/', // Allow public assets if needed
|
|
204
|
+
],
|
|
205
|
+
disallow: [
|
|
206
|
+
// API & System Routes
|
|
207
|
+
'/api/',
|
|
208
|
+
'/_next/',
|
|
209
|
+
'/static/',
|
|
210
|
+
|
|
211
|
+
// Admin & Private Routes
|
|
212
|
+
'/admin*',
|
|
213
|
+
'/dashboard*',
|
|
214
|
+
'/account*',
|
|
215
|
+
|
|
216
|
+
// Dynamic & Utility Routes
|
|
217
|
+
'/*?*', // URLs with query parameters
|
|
218
|
+
'/*/draft', // Draft content
|
|
219
|
+
'/preview*', // Preview pages
|
|
220
|
+
'/**/not-found',
|
|
221
|
+
|
|
222
|
+
// File Types
|
|
223
|
+
'/*.json', // JSON files
|
|
224
|
+
'/*.xml', // XML files (except sitemap)
|
|
225
|
+
'/*.env', // Environment files
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// AI Crawlers Management
|
|
230
|
+
{
|
|
231
|
+
userAgent: [
|
|
232
|
+
'GPTBot', // OpenAI
|
|
233
|
+
'ChatGPT-User', // ChatGPT Browse
|
|
234
|
+
'CCBot', // Common Crawl
|
|
235
|
+
'anthropic-ai', // Anthropic
|
|
236
|
+
'Claude-Web', // Claude
|
|
237
|
+
'Google-Extended',// Google AI (Bard)
|
|
238
|
+
],
|
|
239
|
+
disallow: '/',
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
// Aggressive Scrapers
|
|
243
|
+
{
|
|
244
|
+
userAgent: [
|
|
245
|
+
'SemrushBot',
|
|
246
|
+
'AhrefsBot',
|
|
247
|
+
'DotBot',
|
|
248
|
+
],
|
|
249
|
+
crawlDelay: 10,
|
|
250
|
+
disallow: '/api/',
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
sitemap: \`\${APP_URL}/sitemap.xml\`,
|
|
254
|
+
host: APP_URL,
|
|
255
|
+
};
|
|
256
|
+
}`,
|
|
257
|
+
|
|
258
|
+
'app/[locale]/layout.tsx': (projectName, options = {}) => {
|
|
259
|
+
const { includeAIChat = false } = options;
|
|
260
|
+
|
|
261
|
+
return `import type { Metadata } from 'next';
|
|
262
|
+
import { NextIntlClientProvider } from 'next-intl';
|
|
263
|
+
import {
|
|
264
|
+
getMessages,
|
|
265
|
+
getTranslations,
|
|
266
|
+
setRequestLocale
|
|
267
|
+
} from 'next-intl/server';
|
|
268
|
+
import { notFound } from 'next/navigation';
|
|
269
|
+
import { routing } from '@/i18n/routing';
|
|
270
|
+
import { Footer } from '@/components/layout/Footer';
|
|
271
|
+
import { Header } from '@/components/layout/Header';
|
|
272
|
+
${includeAIChat ? "import { AIChat } from '@/components/layout/AIChat';" : ""}
|
|
273
|
+
|
|
274
|
+
type Locale = (typeof routing.locales)[number];
|
|
275
|
+
|
|
276
|
+
function isValidLocale(locale: string): locale is Locale {
|
|
277
|
+
return routing.locales.includes(locale as Locale);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function generateStaticParams() {
|
|
281
|
+
return routing.locales.map((locale) => ({ locale }));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function generateMetadata({
|
|
285
|
+
params,
|
|
286
|
+
}: {
|
|
287
|
+
params: Promise<{ locale: string }>;
|
|
288
|
+
}): Promise<Metadata> {
|
|
289
|
+
const { locale } = await params;
|
|
290
|
+
|
|
291
|
+
if (!isValidLocale(locale)) {
|
|
292
|
+
return {};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
title: {
|
|
299
|
+
default: t('title'),
|
|
300
|
+
template: \`%s | \${t('title')}\`,
|
|
301
|
+
},
|
|
302
|
+
description: t('description'),
|
|
303
|
+
keywords: t('keywords').split(',').map(k => k.trim()),
|
|
304
|
+
authors: [{ name: 'MetaBinaries' }],
|
|
305
|
+
creator: 'MetaBinaries',
|
|
306
|
+
metadataBase: new URL(
|
|
307
|
+
process.env.NEXT_PUBLIC_APP_URL || 'https://metabinaries.com'
|
|
308
|
+
),
|
|
309
|
+
alternates: {
|
|
310
|
+
canonical: \`/\${locale}\`,
|
|
311
|
+
languages: {
|
|
312
|
+
'en': '/en',
|
|
313
|
+
'ar': '/ar',
|
|
314
|
+
'x-default': '/en',
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
openGraph: {
|
|
318
|
+
type: 'website',
|
|
319
|
+
locale: locale === 'ar' ? 'ar_EG' : 'en_US',
|
|
320
|
+
alternateLocale: locale === 'ar' ? 'en_US' : 'ar_EG',
|
|
321
|
+
url: \`/\${locale}\`,
|
|
322
|
+
title: t('title'),
|
|
323
|
+
description: t('description'),
|
|
324
|
+
siteName: t('siteName'),
|
|
325
|
+
images: [
|
|
326
|
+
{
|
|
327
|
+
url: \`/og-\${locale}.jpg\`,
|
|
328
|
+
width: 1200,
|
|
329
|
+
height: 630,
|
|
330
|
+
alt: t('title'),
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
twitter: {
|
|
335
|
+
card: 'summary_large_image',
|
|
336
|
+
title: t('title'),
|
|
337
|
+
description: t('description'),
|
|
338
|
+
images: [\`/og-\${locale}.jpg\`],
|
|
339
|
+
creator: '@metabinaries',
|
|
340
|
+
},
|
|
341
|
+
robots: {
|
|
342
|
+
index: true,
|
|
343
|
+
follow: true,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default async function LocaleLayout({
|
|
349
|
+
children,
|
|
350
|
+
params,
|
|
351
|
+
}: Readonly<{
|
|
352
|
+
children: React.ReactNode;
|
|
353
|
+
params: Promise<{ locale: string }>;
|
|
354
|
+
}>) {
|
|
355
|
+
const { locale } = await params;
|
|
356
|
+
|
|
357
|
+
if (!isValidLocale(locale)) {
|
|
358
|
+
notFound();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
setRequestLocale(locale);
|
|
362
|
+
|
|
363
|
+
const messages = await getMessages({ locale });
|
|
364
|
+
|
|
365
|
+
const isRTL = locale === 'ar';
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<NextIntlClientProvider
|
|
369
|
+
messages={messages}
|
|
370
|
+
locale={locale}
|
|
371
|
+
timeZone="Africa/Cairo"
|
|
372
|
+
>
|
|
373
|
+
<div
|
|
374
|
+
dir={isRTL ? 'rtl' : 'ltr'}
|
|
375
|
+
className="flex min-h-screen flex-col"
|
|
376
|
+
>
|
|
377
|
+
<Header />
|
|
378
|
+
<main className="flex-1">
|
|
379
|
+
{children}
|
|
380
|
+
</main>
|
|
381
|
+
<Footer />
|
|
382
|
+
${includeAIChat ? "<AIChat />" : ""}
|
|
383
|
+
</div>
|
|
384
|
+
</NextIntlClientProvider>
|
|
385
|
+
);
|
|
386
|
+
}`;
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
'app/[locale]/sitemap.ts': `import { MetadataRoute } from 'next';
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Sitemap Generator - Production Ready
|
|
393
|
+
*
|
|
394
|
+
* This advanced sitemap configuration implements SEO best practices for
|
|
395
|
+
* multilingual Next.js applications, including hreflang alternates.
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
const APP_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://metabinaries.com';
|
|
399
|
+
const LOCALES = ['en', 'ar'] as const;
|
|
400
|
+
|
|
401
|
+
type Locale = (typeof LOCALES)[number];
|
|
402
|
+
type ChangeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
403
|
+
|
|
404
|
+
interface RouteConfig {
|
|
405
|
+
path: string;
|
|
406
|
+
changeFrequency: ChangeFrequency;
|
|
407
|
+
priority: number;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const STATIC_ROUTES: RouteConfig[] = [
|
|
411
|
+
{ path: '', changeFrequency: 'daily', priority: 1.0 },
|
|
412
|
+
{ path: '/features', changeFrequency: 'weekly', priority: 0.8 },
|
|
413
|
+
{ path: '/about', changeFrequency: 'monthly', priority: 0.8 },
|
|
414
|
+
{ path: '/contact', changeFrequency: 'monthly', priority: 0.7 },
|
|
415
|
+
{ path: '/blog', changeFrequency: 'daily', priority: 0.9 },
|
|
416
|
+
{ path: '/privacy', changeFrequency: 'yearly', priority: 0.3 },
|
|
417
|
+
{ path: '/terms', changeFrequency: 'yearly', priority: 0.3 },
|
|
418
|
+
];
|
|
419
|
+
|
|
420
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
421
|
+
return generateStaticEntries();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function generateStaticEntries(): MetadataRoute.Sitemap {
|
|
425
|
+
return STATIC_ROUTES.flatMap((route) =>
|
|
426
|
+
LOCALES.map((locale) => ({
|
|
427
|
+
url: \`\${APP_URL}/\${locale}\${route.path}\`,
|
|
428
|
+
lastModified: new Date(),
|
|
429
|
+
changeFrequency: route.changeFrequency,
|
|
430
|
+
priority: route.priority,
|
|
431
|
+
alternates: {
|
|
432
|
+
languages: Object.fromEntries(
|
|
433
|
+
LOCALES.map((l) => [l, \`\${APP_URL}/\${l}\${route.path}\`])
|
|
434
|
+
),
|
|
435
|
+
},
|
|
436
|
+
}))
|
|
437
|
+
);
|
|
438
|
+
}`,
|
|
439
|
+
|
|
440
|
+
'app/[locale]/loading.tsx': `import { Loading } from '@/components/shared/loading-ui';
|
|
441
|
+
|
|
442
|
+
export default function LoadingPage() {
|
|
443
|
+
return <Loading />;
|
|
444
|
+
}`,
|
|
445
|
+
|
|
446
|
+
'app/[locale]/error.tsx': `'use client';
|
|
447
|
+
|
|
448
|
+
import { useEffect } from 'react';
|
|
449
|
+
import { Button } from '@/components/ui/button';
|
|
450
|
+
import { AlertCircle, RefreshCw, Home } from 'lucide-react';
|
|
451
|
+
|
|
452
|
+
export default function Error({
|
|
453
|
+
error,
|
|
454
|
+
reset,
|
|
455
|
+
}: {
|
|
456
|
+
error: Error & { digest?: string };
|
|
457
|
+
reset: () => void;
|
|
458
|
+
}) {
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
console.error(error);
|
|
461
|
+
}, [error]);
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<div
|
|
465
|
+
className="flex min-h-[100dvh] w-full flex-col items-center justify-center bg-background p-6"
|
|
466
|
+
role="alert"
|
|
467
|
+
aria-live="assertive"
|
|
468
|
+
>
|
|
469
|
+
<div className="flex flex-col items-center gap-6 max-w-md text-center">
|
|
470
|
+
<div className="relative">
|
|
471
|
+
<div className="absolute inset-0 rounded-full bg-destructive/20 animate-ping" />
|
|
472
|
+
<div className="relative flex h-20 w-20 items-center justify-center rounded-full bg-destructive/10 border border-destructive/20">
|
|
473
|
+
<AlertCircle className="h-10 w-10 text-destructive" aria-hidden="true" />
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
|
|
477
|
+
<div className="space-y-2">
|
|
478
|
+
<h2 className="text-2xl font-bold tracking-tight text-foreground">
|
|
479
|
+
Something went wrong!
|
|
480
|
+
</h2>
|
|
481
|
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
482
|
+
An unexpected error occurred. Please try again or return to the home page.
|
|
483
|
+
</p>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
{error.digest && (
|
|
487
|
+
<p className="text-xs text-muted-foreground/60 font-mono bg-muted px-3 py-1.5 rounded-md">
|
|
488
|
+
Error ID: {error.digest}
|
|
489
|
+
</p>
|
|
490
|
+
)}
|
|
491
|
+
|
|
492
|
+
<div className="flex flex-col sm:flex-row gap-3 w-full sm:w-auto">
|
|
493
|
+
<Button
|
|
494
|
+
onClick={() => reset()}
|
|
495
|
+
variant="default"
|
|
496
|
+
className="gap-2"
|
|
497
|
+
>
|
|
498
|
+
<RefreshCw className="h-4 w-4" aria-hidden="true" />
|
|
499
|
+
Try again
|
|
500
|
+
</Button>
|
|
501
|
+
<Button
|
|
502
|
+
onClick={() => (window.location.href = '/')}
|
|
503
|
+
variant="outline"
|
|
504
|
+
className="gap-2"
|
|
505
|
+
>
|
|
506
|
+
<Home className="h-4 w-4" aria-hidden="true" />
|
|
507
|
+
Go Home
|
|
508
|
+
</Button>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<span className="sr-only">
|
|
512
|
+
An error has occurred. You can try again or go back to the home page.
|
|
513
|
+
</span>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
}`,
|
|
518
|
+
|
|
519
|
+
'app/[locale]/page.tsx': `'use client';
|
|
520
|
+
|
|
521
|
+
import { motion } from 'framer-motion';
|
|
522
|
+
import { Button } from '@/components/ui/button';
|
|
523
|
+
import { Badge } from '@/components/ui/badge';
|
|
524
|
+
import { Shield, Globe, Zap, ArrowRight, Sparkles } from 'lucide-react';
|
|
525
|
+
import Link from 'next/link';
|
|
526
|
+
|
|
527
|
+
export default function HomePage() {
|
|
528
|
+
return (
|
|
529
|
+
<div className='relative min-h-screen overflow-hidden bg-white dark:bg-slate-950 font-sans selection:bg-blue-500/30'>
|
|
530
|
+
<div className='fixed inset-0 overflow-hidden pointer-events-none'>
|
|
531
|
+
<div className='absolute top-[-10%] left-[-10%] w-[50%] h-[50%] rounded-full bg-blue-600/5 dark:bg-blue-600/10 blur-[120px]' />
|
|
532
|
+
<div className='absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] rounded-full bg-indigo-600/5 dark:bg-indigo-600/10 blur-[120px]' />
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<div className='relative z-10 container mx-auto px-6 py-24 md:py-32 flex flex-col items-center text-center'>
|
|
536
|
+
<motion.div
|
|
537
|
+
initial={{ opacity: 0, scale: 0.9 }}
|
|
538
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
539
|
+
transition={{ duration: 0.5 }}
|
|
540
|
+
className='mb-8'
|
|
541
|
+
>
|
|
542
|
+
<Badge
|
|
543
|
+
variant='outline'
|
|
544
|
+
className='py-1.5 px-4 border-blue-100 dark:border-blue-900 bg-blue-50/50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 backdrop-blur-sm shadow-sm'
|
|
545
|
+
>
|
|
546
|
+
<Sparkles className='w-3.5 h-3.5 mr-2' />
|
|
547
|
+
Release v1.0 is here
|
|
548
|
+
</Badge>
|
|
549
|
+
</motion.div>
|
|
550
|
+
|
|
551
|
+
<motion.h1
|
|
552
|
+
initial={{ opacity: 0, y: 30 }}
|
|
553
|
+
animate={{ opacity: 1, y: 0 }}
|
|
554
|
+
transition={{ duration: 0.7, delay: 0.1 }}
|
|
555
|
+
className='max-w-4xl text-5xl md:text-8xl font-black tracking-tight text-slate-900 dark:text-white mb-8'
|
|
556
|
+
>
|
|
557
|
+
Build something <br />
|
|
558
|
+
<span className='bg-gradient-to-r from-blue-600 via-indigo-600 to-violet-600 bg-clip-text text-transparent'>
|
|
559
|
+
Extraordinary
|
|
560
|
+
</span>
|
|
561
|
+
</motion.h1>
|
|
562
|
+
|
|
563
|
+
<motion.p
|
|
564
|
+
initial={{ opacity: 0, y: 20 }}
|
|
565
|
+
animate={{ opacity: 1, y: 0 }}
|
|
566
|
+
transition={{ duration: 0.7, delay: 0.2 }}
|
|
567
|
+
className='max-w-2xl text-lg md:text-xl text-slate-500 dark:text-slate-400 mb-12 leading-relaxed'
|
|
568
|
+
>
|
|
569
|
+
The ultimate Next.js starter kit for developers who care about aesthetics, performance, and scalability. Start your journey today.
|
|
570
|
+
</motion.p>
|
|
571
|
+
|
|
572
|
+
<motion.div
|
|
573
|
+
initial={{ opacity: 0, y: 20 }}
|
|
574
|
+
animate={{ opacity: 1, y: 0 }}
|
|
575
|
+
transition={{ duration: 0.7, delay: 0.3 }}
|
|
576
|
+
className='flex flex-wrap items-center justify-center gap-4'
|
|
577
|
+
>
|
|
578
|
+
<Button asChild size='lg' className='h-14 px-8 rounded-full bg-blue-600 hover:bg-blue-700 text-white text-lg font-semibold shadow-xl shadow-blue-500/25'>
|
|
579
|
+
<Link href='#features'>
|
|
580
|
+
Get Started <ArrowRight className='ml-2 w-5 h-5' />
|
|
581
|
+
</Link>
|
|
582
|
+
</Button>
|
|
583
|
+
<Button asChild variant='outline' size='lg' className='h-14 px-8 rounded-full border-slate-200 dark:border-slate-800 text-lg font-semibold hover:bg-slate-50 dark:hover:bg-slate-900'>
|
|
584
|
+
<a href='https://nextjs.org/docs' target='_blank' rel='noopener noreferrer'>Documentation</a>
|
|
585
|
+
</Button>
|
|
586
|
+
</motion.div>
|
|
587
|
+
|
|
588
|
+
<motion.div
|
|
589
|
+
initial={{ opacity: 0, y: 40 }}
|
|
590
|
+
animate={{ opacity: 1, y: 0 }}
|
|
591
|
+
transition={{ duration: 0.8, delay: 0.5 }}
|
|
592
|
+
className='grid md:grid-cols-3 gap-6 mt-32 w-full max-w-5xl'
|
|
593
|
+
id='features'
|
|
594
|
+
>
|
|
595
|
+
{[
|
|
596
|
+
{ icon: <Zap className='text-yellow-500' />, title: 'Incredible Speed', desc: 'Optimized for Core Web Vitals and lightning-fast LCP.' },
|
|
597
|
+
{ icon: <Globe className='text-blue-500' />, title: 'I18n Built-in', desc: 'Seamless English and Arabic support out of the box.' },
|
|
598
|
+
{ icon: <Shield className='text-green-500' />, title: 'Enterprise Ready', desc: 'Scalable architecture using modern React patterns.' }
|
|
599
|
+
].map((f, i) => (
|
|
600
|
+
<div key={i} className='p-8 rounded-3xl border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 backdrop-blur-sm hover:border-blue-500/50 transition-all group'>
|
|
601
|
+
<div className='w-12 h-12 rounded-2xl bg-white dark:bg-slate-800 shadow-sm flex items-center justify-center mb-6 group-hover:scale-110 transition-transform'>
|
|
602
|
+
{f.icon}
|
|
603
|
+
</div>
|
|
604
|
+
<h3 className='text-xl font-bold mb-3 text-slate-900 dark:text-white'>{f.title}</h3>
|
|
605
|
+
<p className='text-slate-500 dark:text-slate-400 text-sm leading-relaxed'>{f.desc}</p>
|
|
606
|
+
</div>
|
|
607
|
+
))}
|
|
608
|
+
</motion.div>
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
);
|
|
612
|
+
}`,
|
|
613
|
+
|
|
614
|
+
'app/globals.css': `/**
|
|
615
|
+
* Global Styles - Customization Guide
|
|
616
|
+
*
|
|
617
|
+
* 🎨 WHAT TO CUSTOMIZE:
|
|
618
|
+
*
|
|
619
|
+
* 1. ✅ Colors (Primary, Secondary, etc.)
|
|
620
|
+
* 2. ✅ Border Radius
|
|
621
|
+
* 3. ✅ Typography (Font sizes, weights)
|
|
622
|
+
* 4. ⚠️ Keep everything else as-is
|
|
623
|
+
*/
|
|
624
|
+
|
|
625
|
+
@import "tailwindcss";
|
|
626
|
+
|
|
627
|
+
/* ========================================
|
|
628
|
+
🎨 CUSTOMIZATION SECTION
|
|
629
|
+
Change these values for your brand
|
|
630
|
+
======================================== */
|
|
631
|
+
|
|
632
|
+
@theme {
|
|
633
|
+
/*
|
|
634
|
+
⚠️ DON'T TOUCH - These map to CSS variables
|
|
635
|
+
Only change the :root values below
|
|
636
|
+
*/
|
|
637
|
+
--color-border: hsl(var(--border));
|
|
638
|
+
--color-input: hsl(var(--input));
|
|
639
|
+
--color-ring: hsl(var(--ring));
|
|
640
|
+
--color-background: hsl(var(--background));
|
|
641
|
+
--color-foreground: hsl(var(--foreground));
|
|
642
|
+
--color-primary: hsl(var(--primary));
|
|
643
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
644
|
+
--color-secondary: hsl(var(--secondary));
|
|
645
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
646
|
+
--color-destructive: hsl(var(--destructive));
|
|
647
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
648
|
+
--color-muted: hsl(var(--muted));
|
|
649
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
650
|
+
--color-accent: hsl(var(--accent));
|
|
651
|
+
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
652
|
+
--color-popover: hsl(var(--popover));
|
|
653
|
+
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
654
|
+
--color-card: hsl(var(--card));
|
|
655
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
656
|
+
|
|
657
|
+
/*
|
|
658
|
+
✅ CUSTOMIZE - Border Radius
|
|
659
|
+
Change this to match your design style
|
|
660
|
+
*/
|
|
661
|
+
--radius-lg: var(--radius);
|
|
662
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
663
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
664
|
+
|
|
665
|
+
/* ⚠️ DON'T TOUCH - Animation definitions */
|
|
666
|
+
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
667
|
+
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
668
|
+
|
|
669
|
+
@keyframes accordion-down {
|
|
670
|
+
from { height: 0; }
|
|
671
|
+
to { height: var(--radix-accordion-content-height); }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
@keyframes accordion-up {
|
|
675
|
+
from { height: var(--radix-accordion-content-height); }
|
|
676
|
+
to { height: 0; }
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
@keyframes fade-in {
|
|
680
|
+
from { opacity: 0; }
|
|
681
|
+
to { opacity: 1; }
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
@keyframes slide-in-from-top {
|
|
685
|
+
from { transform: translateY(-100%); }
|
|
686
|
+
to { transform: translateY(0); }
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
@keyframes slide-in-from-bottom {
|
|
690
|
+
from { transform: translateY(100%); }
|
|
691
|
+
to { transform: translateY(0); }
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* ========================================
|
|
696
|
+
✅ CUSTOMIZE THESE VALUES
|
|
697
|
+
======================================== */
|
|
698
|
+
|
|
699
|
+
@layer base {
|
|
700
|
+
/*
|
|
701
|
+
🎨 LIGHT MODE COLORS
|
|
702
|
+
Change these HSL values to match your brand
|
|
703
|
+
|
|
704
|
+
Format: HUE SATURATION LIGHTNESS
|
|
705
|
+
Example: 221.2 83.2% 53.3%
|
|
706
|
+
|
|
707
|
+
🔗 Use this tool: https://hslpicker.com
|
|
708
|
+
*/
|
|
709
|
+
:root {
|
|
710
|
+
/* Background Colors */
|
|
711
|
+
--background: 0 0% 100%; /* ✅ White - Change for different bg */
|
|
712
|
+
--foreground: 222.2 84% 4.9%; /* ✅ Near black - Text color */
|
|
713
|
+
|
|
714
|
+
/* Card Colors */
|
|
715
|
+
--card: 0 0% 100%; /* ✅ White cards */
|
|
716
|
+
--card-foreground: 222.2 84% 4.9%; /* ✅ Card text color */
|
|
717
|
+
|
|
718
|
+
/* Popover Colors */
|
|
719
|
+
--popover: 0 0% 100%;
|
|
720
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
721
|
+
|
|
722
|
+
/* 🎨 PRIMARY COLOR - Your Brand Color */
|
|
723
|
+
--primary: 221.2 83.2% 53.3%; /* ✅ Blue - CHANGE THIS */
|
|
724
|
+
--primary-foreground: 210 40% 98%; /* ✅ White text on primary */
|
|
725
|
+
|
|
726
|
+
/* Examples for different brands:
|
|
727
|
+
Red: 0 84% 60%
|
|
728
|
+
Green: 142 76% 36%
|
|
729
|
+
Purple: 262 83% 58%
|
|
730
|
+
Orange: 25 95% 53%
|
|
731
|
+
*/
|
|
732
|
+
|
|
733
|
+
/* Secondary Color */
|
|
734
|
+
--secondary: 210 40% 96.1%; /* ✅ Light gray */
|
|
735
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
736
|
+
|
|
737
|
+
/* Muted (Subtle backgrounds) */
|
|
738
|
+
--muted: 210 40% 96.1%;
|
|
739
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
740
|
+
|
|
741
|
+
/* Accent */
|
|
742
|
+
--accent: 210 40% 96.1%;
|
|
743
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
744
|
+
|
|
745
|
+
/* Destructive (Error/Delete) */
|
|
746
|
+
--destructive: 0 84.2% 60.2%; /* ⚠️ Red - Usually keep as-is */
|
|
747
|
+
--destructive-foreground: 210 40% 98%;
|
|
748
|
+
|
|
749
|
+
/* Border & Input */
|
|
750
|
+
--border: 214.3 31.8% 91.4%; /* ✅ Light gray borders */
|
|
751
|
+
--input: 214.3 31.8% 91.4%; /* ✅ Input borders */
|
|
752
|
+
--ring: 221.2 83.2% 53.3%; /* ✅ Focus ring (match primary) */
|
|
753
|
+
|
|
754
|
+
/* 🎨 BORDER RADIUS - Your Design Style */
|
|
755
|
+
--radius: 0.5rem; /* ✅ CHANGE THIS
|
|
756
|
+
0rem = Sharp (No radius)
|
|
757
|
+
0.25rem = Subtle (4px)
|
|
758
|
+
0.5rem = Moderate (8px) - Default
|
|
759
|
+
0.75rem = Rounded (12px)
|
|
760
|
+
1rem = Very Rounded (16px)
|
|
761
|
+
9999px = Pills/Fully rounded
|
|
762
|
+
*/
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/*
|
|
766
|
+
🌙 DARK MODE COLORS
|
|
767
|
+
Change these if you want a custom dark theme
|
|
768
|
+
*/
|
|
769
|
+
.dark {
|
|
770
|
+
--background: 222.2 84% 4.9%; /* ✅ Dark blue-gray */
|
|
771
|
+
--foreground: 210 40% 98%; /* ✅ Near white */
|
|
772
|
+
|
|
773
|
+
--card: 222.2 84% 4.9%;
|
|
774
|
+
--card-foreground: 210 40% 98%;
|
|
775
|
+
|
|
776
|
+
--popover: 222.2 84% 4.9%;
|
|
777
|
+
--popover-foreground: 210 40% 98%;
|
|
778
|
+
|
|
779
|
+
/* 🎨 PRIMARY COLOR - Usually brighter in dark mode */
|
|
780
|
+
--primary: 217.2 91.2% 59.8%; /* ✅ Lighter blue */
|
|
781
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
782
|
+
|
|
783
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
784
|
+
--secondary-foreground: 210 40% 98%;
|
|
785
|
+
|
|
786
|
+
--muted: 217.2 32.6% 17.5%;
|
|
787
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
788
|
+
|
|
789
|
+
--accent: 217.2 32.6% 17.5%;
|
|
790
|
+
--accent-foreground: 210 40% 98%;
|
|
791
|
+
|
|
792
|
+
--destructive: 0 62.8% 30.6%; /* ⚠️ Darker red for dark mode */
|
|
793
|
+
--destructive-foreground: 210 40% 98%;
|
|
794
|
+
|
|
795
|
+
--border: 217.2 32.6% 17.5%; /* ✅ Dark borders */
|
|
796
|
+
--input: 217.2 32.6% 17.5%;
|
|
797
|
+
--ring: 224.3 76.3% 48%; /* ✅ Focus ring */
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/* ⚠️ DON'T TOUCH - Core resets */
|
|
801
|
+
* {
|
|
802
|
+
border-color: hsl(var(--border));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
html {
|
|
806
|
+
-webkit-font-smoothing: antialiased;
|
|
807
|
+
-moz-osx-font-smoothing: grayscale;
|
|
808
|
+
text-rendering: optimizeLegibility;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
body {
|
|
812
|
+
background-color: hsl(var(--background));
|
|
813
|
+
color: hsl(var(--foreground));
|
|
814
|
+
font-feature-settings: "rlig" 1, "calt" 1;
|
|
815
|
+
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
[dir="rtl"] {
|
|
819
|
+
font-feature-settings: "ss01" 1;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/* ========================================
|
|
824
|
+
✅ OPTIONAL: CUSTOMIZE TYPOGRAPHY
|
|
825
|
+
======================================== */
|
|
826
|
+
|
|
827
|
+
@layer base {
|
|
828
|
+
/*
|
|
829
|
+
🎨 CUSTOMIZE - Heading sizes
|
|
830
|
+
Change these based on your design system
|
|
831
|
+
*/
|
|
832
|
+
h1 {
|
|
833
|
+
font-size: 2.25rem; /* ✅ 36px - Change if needed */
|
|
834
|
+
line-height: 2.5rem;
|
|
835
|
+
font-weight: 700; /* ✅ Bold - Change (300-900) */
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
h2 {
|
|
839
|
+
font-size: 1.875rem; /* ✅ 30px */
|
|
840
|
+
line-height: 2.25rem;
|
|
841
|
+
font-weight: 600;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
h3 {
|
|
845
|
+
font-size: 1.5rem; /* ✅ 24px */
|
|
846
|
+
line-height: 2rem;
|
|
847
|
+
font-weight: 600;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
h4 {
|
|
851
|
+
font-size: 1.25rem; /* ✅ 20px */
|
|
852
|
+
line-height: 1.75rem;
|
|
853
|
+
font-weight: 600;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/*
|
|
857
|
+
🎨 CUSTOMIZE - Paragraph spacing
|
|
858
|
+
*/
|
|
859
|
+
p {
|
|
860
|
+
line-height: 1.7; /* ✅ Change for tighter/looser text */
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/*
|
|
864
|
+
🎨 CUSTOMIZE - Link styles
|
|
865
|
+
*/
|
|
866
|
+
a {
|
|
867
|
+
text-decoration: none;
|
|
868
|
+
transition: color 0.15s ease-in-out;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
a:hover {
|
|
872
|
+
color: hsl(var(--primary)); /* ✅ Links turn primary color on hover */
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/* ⚠️ DON'T TOUCH - Accessibility */
|
|
876
|
+
*:focus-visible {
|
|
877
|
+
outline: 2px solid hsl(var(--ring));
|
|
878
|
+
outline-offset: 2px;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
*:focus:not(:focus-visible) {
|
|
882
|
+
outline: none;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/* ========================================
|
|
887
|
+
✅ OPTIONAL: CUSTOMIZE SCROLLBAR
|
|
888
|
+
======================================== */
|
|
889
|
+
|
|
890
|
+
::-webkit-scrollbar {
|
|
891
|
+
width: 10px; /* ✅ Change scrollbar width (8px-14px) */
|
|
892
|
+
height: 10px;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
::-webkit-scrollbar-track {
|
|
896
|
+
background: transparent; /* ✅ Or use a color */
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
::-webkit-scrollbar-thumb {
|
|
900
|
+
background: hsl(var(--muted-foreground) / 0.2);
|
|
901
|
+
border-radius: 10px;
|
|
902
|
+
border: 2px solid hsl(var(--background));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
::-webkit-scrollbar-thumb:hover {
|
|
906
|
+
background: hsl(var(--muted-foreground) / 0.3); /* ✅ Darker on hover */
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
* {
|
|
910
|
+
scrollbar-width: thin;
|
|
911
|
+
scrollbar-color: hsl(var(--muted-foreground) / 0.2) transparent;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/* ========================================
|
|
915
|
+
✅ OPTIONAL: CUSTOMIZE SELECTION
|
|
916
|
+
======================================== */
|
|
917
|
+
|
|
918
|
+
::selection {
|
|
919
|
+
background-color: hsl(var(--primary) / 0.2); /* ✅ Selection highlight */
|
|
920
|
+
color: hsl(var(--foreground));
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
::-moz-selection {
|
|
924
|
+
background-color: hsl(var(--primary) / 0.2);
|
|
925
|
+
color: hsl(var(--foreground));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/* ========================================
|
|
929
|
+
⚠️ DON'T TOUCH - Utility Classes
|
|
930
|
+
======================================== */
|
|
931
|
+
|
|
932
|
+
@layer utilities {
|
|
933
|
+
.scrollbar-hide {
|
|
934
|
+
-ms-overflow-style: none;
|
|
935
|
+
scrollbar-width: none;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.scrollbar-hide::-webkit-scrollbar {
|
|
939
|
+
display: none;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.scroll-smooth {
|
|
943
|
+
scroll-behavior: smooth;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
[dir="rtl"] .rtl\:rotate-180 {
|
|
947
|
+
transform: rotate(180deg);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.animation-delay-200 {
|
|
951
|
+
animation-delay: 200ms;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.animation-delay-400 {
|
|
955
|
+
animation-delay: 400ms;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.animation-delay-600 {
|
|
959
|
+
animation-delay: 600ms;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/* ========================================
|
|
964
|
+
⚠️ DON'T TOUCH - Accessibility
|
|
965
|
+
======================================== */
|
|
966
|
+
|
|
967
|
+
.sr-only {
|
|
968
|
+
position: absolute;
|
|
969
|
+
width: 1px;
|
|
970
|
+
height: 1px;
|
|
971
|
+
padding: 0;
|
|
972
|
+
margin: -1px;
|
|
973
|
+
overflow: hidden;
|
|
974
|
+
clip: rect(0, 0, 0, 0);
|
|
975
|
+
white-space: nowrap;
|
|
976
|
+
border-width: 0;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.skip-to-content {
|
|
980
|
+
position: absolute;
|
|
981
|
+
top: -100%;
|
|
982
|
+
left: 0;
|
|
983
|
+
z-index: 9999;
|
|
984
|
+
padding: 1rem 2rem;
|
|
985
|
+
background-color: hsl(var(--background));
|
|
986
|
+
color: hsl(var(--foreground));
|
|
987
|
+
border: 2px solid hsl(var(--border));
|
|
988
|
+
border-radius: var(--radius);
|
|
989
|
+
transition: top 0.3s ease-in-out;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.skip-to-content:focus {
|
|
993
|
+
top: 1rem;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/* ========================================
|
|
997
|
+
⚠️ DON'T TOUCH - Print Styles
|
|
998
|
+
======================================== */
|
|
999
|
+
|
|
1000
|
+
@media print {
|
|
1001
|
+
*, *::before, *::after {
|
|
1002
|
+
background: white !important;
|
|
1003
|
+
color: black !important;
|
|
1004
|
+
box-shadow: none !important;
|
|
1005
|
+
text-shadow: none !important;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
a, a:visited {
|
|
1009
|
+
text-decoration: underline;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
a[href]::after {
|
|
1013
|
+
content: " (" attr(href) ")";
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
img {
|
|
1017
|
+
max-width: 100% !important;
|
|
1018
|
+
page-break-inside: avoid;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
p, h2, h3 {
|
|
1022
|
+
orphans: 3;
|
|
1023
|
+
widows: 3;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
h2, h3 {
|
|
1027
|
+
page-break-after: avoid;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/* ========================================
|
|
1032
|
+
⚠️ DON'T TOUCH - Performance
|
|
1033
|
+
======================================== */
|
|
1034
|
+
|
|
1035
|
+
.will-change-transform {
|
|
1036
|
+
will-change: transform;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
.will-change-opacity {
|
|
1040
|
+
will-change: opacity;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
img, video {
|
|
1044
|
+
height: auto;
|
|
1045
|
+
max-width: 100%;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
img[loading="lazy"] {
|
|
1049
|
+
min-height: 100px;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/* ========================================
|
|
1053
|
+
✅ ADD YOUR CUSTOM STYLES HERE
|
|
1054
|
+
======================================== */
|
|
1055
|
+
|
|
1056
|
+
/*
|
|
1057
|
+
Example: Custom button styles
|
|
1058
|
+
|
|
1059
|
+
.btn-custom {
|
|
1060
|
+
background: hsl(var(--primary));
|
|
1061
|
+
color: hsl(var(--primary-foreground));
|
|
1062
|
+
padding: 0.5rem 1rem;
|
|
1063
|
+
border-radius: var(--radius);
|
|
1064
|
+
}
|
|
1065
|
+
*/`,
|
|
1066
|
+
};
|