@jhits/dashboard 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +36 -0
  2. package/next.config.ts +32 -0
  3. package/package.json +79 -0
  4. package/postcss.config.mjs +7 -0
  5. package/src/api/README.md +72 -0
  6. package/src/api/masterRouter.ts +150 -0
  7. package/src/api/pluginRouter.ts +135 -0
  8. package/src/app/[locale]/(auth)/layout.tsx +30 -0
  9. package/src/app/[locale]/(auth)/login/page.tsx +201 -0
  10. package/src/app/[locale]/catch-all/page.tsx +10 -0
  11. package/src/app/[locale]/dashboard/[...pluginRoute]/page.tsx +98 -0
  12. package/src/app/[locale]/dashboard/layout.tsx +42 -0
  13. package/src/app/[locale]/dashboard/page.tsx +121 -0
  14. package/src/app/[locale]/dashboard/preferences/page.tsx +295 -0
  15. package/src/app/[locale]/dashboard/profile/page.tsx +491 -0
  16. package/src/app/[locale]/layout.tsx +28 -0
  17. package/src/app/actions/preferences.ts +40 -0
  18. package/src/app/actions/user.ts +191 -0
  19. package/src/app/api/auth/[...nextauth]/route.ts +6 -0
  20. package/src/app/api/plugin-images/list/route.ts +96 -0
  21. package/src/app/api/plugin-images/upload/route.ts +88 -0
  22. package/src/app/api/telemetry/log/route.ts +10 -0
  23. package/src/app/api/telemetry/route.ts +12 -0
  24. package/src/app/api/uploads/[filename]/route.ts +33 -0
  25. package/src/app/globals.css +181 -0
  26. package/src/app/layout.tsx +4 -0
  27. package/src/assets/locales/en/common.json +47 -0
  28. package/src/assets/locales/nl/common.json +48 -0
  29. package/src/assets/locales/sv/common.json +48 -0
  30. package/src/assets/plugins.json +42 -0
  31. package/src/assets/public/Logo_JH_black.jpg +0 -0
  32. package/src/assets/public/Logo_JH_black.png +0 -0
  33. package/src/assets/public/Logo_JH_white.png +0 -0
  34. package/src/assets/public/animated-logo-white.svg +5 -0
  35. package/src/assets/public/logo_black.svg +5 -0
  36. package/src/assets/public/logo_white.svg +5 -0
  37. package/src/assets/public/noimagefound.jpg +0 -0
  38. package/src/components/DashboardCatchAll.tsx +95 -0
  39. package/src/components/DashboardRootLayout.tsx +37 -0
  40. package/src/components/PluginNotFound.tsx +24 -0
  41. package/src/components/Providers.tsx +59 -0
  42. package/src/components/dashboard/Sidebar.tsx +263 -0
  43. package/src/components/dashboard/Topbar.tsx +363 -0
  44. package/src/components/page.tsx +130 -0
  45. package/src/config.ts +230 -0
  46. package/src/i18n/navigation.ts +7 -0
  47. package/src/i18n/request.ts +41 -0
  48. package/src/i18n/routing.ts +35 -0
  49. package/src/i18n/translations.ts +20 -0
  50. package/src/index.tsx +69 -0
  51. package/src/lib/auth.ts +159 -0
  52. package/src/lib/db.ts +11 -0
  53. package/src/lib/get-website-info.ts +78 -0
  54. package/src/lib/modules-config.ts +68 -0
  55. package/src/lib/mongodb.ts +32 -0
  56. package/src/lib/plugin-registry.tsx +77 -0
  57. package/src/lib/website-context.tsx +39 -0
  58. package/src/proxy.ts +55 -0
  59. package/src/router.tsx +45 -0
  60. package/src/routes.tsx +3 -0
  61. package/src/server.ts +8 -0
  62. package/src/types/plugin.ts +24 -0
  63. package/src/types/preferences.ts +13 -0
@@ -0,0 +1,363 @@
1
+ "use client";
2
+ import { useEffect, useState } from 'react';
3
+ import { useTheme } from 'next-themes';
4
+ import { useLocale } from 'next-intl';
5
+ import { useRouter, usePathname, Link } from '@/i18n/navigation';
6
+ import { routing } from '@/i18n/routing';
7
+ import { Moon, Sun, Bell, User, Settings, ShieldCheck, Languages, ChevronRight, LogOut, Check, X, Loader2, ExternalLink, Home } from 'lucide-react';
8
+ import { motion, AnimatePresence } from 'framer-motion';
9
+ import { createPortal } from 'react-dom';
10
+ import { signOut, useSession } from 'next-auth/react';
11
+ import { useWebsite } from '../../lib/website-context';
12
+ import Image from 'next/image';
13
+
14
+ /* --- RESPONSIVE MODAL WRAPPER --- */
15
+ function TopbarModal({ children, title, className = "", onClose }: { children: React.ReactNode; title?: string; className?: string; onClose?: () => void }) {
16
+ const [mounted, setMounted] = useState(false);
17
+ useEffect(() => {
18
+ setTimeout(() => {
19
+ setMounted(true);
20
+ }, 0);
21
+ }, []);
22
+
23
+ if (!mounted) return null;
24
+
25
+ const modalContent = (
26
+ <>
27
+ <motion.div
28
+ initial={{ opacity: 0 }}
29
+ animate={{ opacity: 1 }}
30
+ exit={{ opacity: 0 }}
31
+ onClick={onClose}
32
+ className="fixed inset-0 bg-black/40 backdrop-blur-md z-9998 lg:hidden"
33
+ />
34
+
35
+ <motion.div
36
+ initial={{ opacity: 0, y: 10, scale: 0.98 }}
37
+ animate={{ opacity: 1, y: 0, scale: 1 }}
38
+ exit={{ opacity: 0, y: 10, scale: 0.98 }}
39
+ onClick={(e) => e.stopPropagation()}
40
+ className={`
41
+ fixed top-22 left-4 right-4 mx-auto w-[calc(100%-2rem)] z-9999 p-5
42
+ lg:absolute lg:top-full lg:right-0 lg:left-auto lg:mt-2 lg:w-72 lg:mx-0 lg:z-50 lg:p-4
43
+ bg-dashboard-card border border-dashboard-border
44
+ rounded-3xl shadow-2xl h-fit ${className}
45
+ `}
46
+ >
47
+ <div className="flex justify-between items-center mb-4 lg:mb-3">
48
+ {title && (
49
+ <h4 className="text-[10px] uppercase tracking-[0.2em] font-bold text-neutral-500 dark:text-neutral-400 px-1">
50
+ {title}
51
+ </h4>
52
+ )}
53
+ <button
54
+ onClick={onClose}
55
+ className="lg:hidden p-3 -mr-2 text-neutral-400 active:scale-90 transition-all"
56
+ >
57
+ <X className="size-6" />
58
+ </button>
59
+ </div>
60
+ <div className="h-fit">
61
+ {children}
62
+ </div>
63
+ </motion.div>
64
+ </>
65
+ );
66
+
67
+ if (typeof window !== 'undefined' && window.innerWidth < 1024) {
68
+ return createPortal(modalContent, document.body);
69
+ }
70
+
71
+ return modalContent;
72
+ }
73
+
74
+ /* --- SUB-COMPONENT: NOTIFICATIONS --- */
75
+ function NotificationMenu({ active, onToggle, onClose }: { active: boolean, onToggle: () => void, onClose: () => void }) {
76
+ const [notifications] = useState([
77
+ { id: 1, type: 'alert', title: 'Security Alert', message: 'New login from Stockholm, SE', time: '2m ago', unread: true },
78
+ { id: 2, type: 'update', title: 'System Update', message: 'V2.1 deployment successful.', time: '1h ago', unread: true },
79
+ { id: 3, type: 'success', title: 'Payment Received', message: 'Invoice #4432 has been paid.', time: '5h ago', unread: false },
80
+ { id: 4, type: 'message', title: 'Support Chat', message: 'Agent Sarah replied to your ticket.', time: '1d ago', unread: true },
81
+ ]);
82
+
83
+ const unreadCount = notifications.filter(n => n.unread).length;
84
+
85
+ return (
86
+ <div className="relative py-4">
87
+ <button
88
+ onClick={onToggle}
89
+ className="relative size-10 lg:size-10 flex items-center justify-center bg-dashboard-bg rounded-xl hover:bg-dashboard-card active:scale-95 transition-all"
90
+ >
91
+ <Bell className="size-5 lg:size-4 text-neutral-600 dark:text-neutral-200" />
92
+ {unreadCount > 0 && (
93
+ <span className="absolute top-2.5 right-2.5 flex h-2.5 w-2.5">
94
+ <span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-primary border-2 border-dashboard-sidebar"></span>
95
+ </span>
96
+ )}
97
+ </button>
98
+
99
+ <AnimatePresence>
100
+ {active && (
101
+ <TopbarModal title="Notifications" onClose={onClose} className="lg:w-80">
102
+ {/* THE FIX: We wrap the content in a flex container with a fixed height */}
103
+ <div className="flex flex-col max-h-[60dvh] lg:max-h-[400px]">
104
+
105
+ {/* SCROLLABLE AREA: Just the list */}
106
+ <div className="flex-1 overflow-y-auto pr-1 custom-scrollbar flex flex-col gap-2 p-1">
107
+ {notifications.map((n) => (
108
+ <div
109
+ key={n.id}
110
+ className={`p-4 lg:p-3 rounded-2xl border transition-all active:scale-[0.98] ${n.unread
111
+ ? 'bg-dashboard-card border-primary'
112
+ : 'bg-transparent border-dashboard-border opacity-60'
113
+ }`}
114
+ >
115
+ <div className="flex justify-between items-center mb-1">
116
+ <span className="text-[8px] font-bold uppercase tracking-widest text-primary">{n.type}</span>
117
+ <span className="text-[8px] text-neutral-500 dark:text-neutral-400">{n.time}</span>
118
+ </div>
119
+ <p className="text-xs font-bold text-dashboard-text uppercase">{n.title}</p>
120
+ <p className="text-[10px] text-neutral-600 dark:text-neutral-400 mt-0.5 line-clamp-1">{n.message}</p>
121
+ </div>
122
+ ))}
123
+ </div>
124
+
125
+ {/* INTEGRATED OPTION FOOTER */}
126
+ <div className="mt-2 pt-2 border-t border-dashboard-border">
127
+ <button className="w-full group flex cursor-pointer items-center justify-between px-4 py-3 rounded-xl hover:bg-dashboard-bg transition-all active:scale-[0.98]">
128
+ <span className="text-[10px] font-black uppercase tracking-widest text-neutral-500 group-hover:text-primary transition-colors">
129
+ View all activity
130
+ </span>
131
+ <ChevronRight className="size-3 text-neutral-400 group-hover:text-primary group-hover:translate-x-0.5 transition-all" />
132
+ </button>
133
+ </div>
134
+
135
+ </div>
136
+ </TopbarModal>
137
+ )}
138
+ </AnimatePresence>
139
+ </div>
140
+ );
141
+ }
142
+
143
+ /* --- SUB-COMPONENT: USER & LANGUAGE MODAL --- */
144
+ function UserMenu({ active, onToggle, onClose, locale, router, pathname }: { active: boolean, onToggle: () => void, onClose: () => void, locale: string, router: ReturnType<typeof useRouter>, pathname: string }) {
145
+ const { data: session } = useSession();
146
+ const [showLangSelector, setShowLangSelector] = useState(false);
147
+ const [isLoading, setIsLoading] = useState(false);
148
+
149
+ useEffect(() => {
150
+ if (!active) {
151
+ const timer = setTimeout(() => setShowLangSelector(false), 300);
152
+ return () => clearTimeout(timer);
153
+ }
154
+ }, [active]);
155
+
156
+ const handleLogout = async () => {
157
+ try {
158
+ setIsLoading(true);
159
+ await signOut({ callbackUrl: '/', redirect: true });
160
+ } catch (error) {
161
+ console.error('Logout error:', error);
162
+ setIsLoading(false);
163
+ }
164
+ };
165
+
166
+ const userName = session?.user?.name || "User";
167
+ const userRole = (session?.user as { role: string })?.role || "Member";
168
+ const userImage = session?.user?.image;
169
+ const initials = userName.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || "??";
170
+
171
+ const menuItems = [
172
+ { icon: User, label: 'Profile', href: '/dashboard/profile' },
173
+ { icon: ShieldCheck, label: 'Security', href: '/dashboard/security' },
174
+ { icon: Settings, label: 'Preferences', href: '/dashboard/preferences' }
175
+ ];
176
+
177
+ return (
178
+ <div className="relative py-4 pl-4 border-l border-dashboard-border">
179
+ <div onClick={onToggle} className="flex items-center gap-3 cursor-pointer group active:scale-95 transition-transform">
180
+ <div className="flex-col items-end hidden sm:flex text-right">
181
+ <span className="text-xs font-bold uppercase text-dashboard-text">{userName}</span>
182
+ <span className="text-[10px] text-primary font-medium uppercase italic tracking-tighter">{userRole}</span>
183
+ </div>
184
+ <div className="size-10 rounded-xl bg-primary flex items-center justify-center font-bold text-white shadow-lg shadow-primary/20 overflow-hidden relative">
185
+ {userImage ? <Image src={userImage} alt="Avatar" className="w-full h-full object-cover" width={40} height={40} /> : initials}
186
+ </div>
187
+ </div>
188
+
189
+ <AnimatePresence mode="wait">
190
+ {active && (
191
+ <TopbarModal title={showLangSelector ? "Language" : "Account"} onClose={onClose} className="lg:w-64">
192
+ <div className="overflow-hidden">
193
+ <AnimatePresence mode="wait" initial={false}>
194
+ {!showLangSelector ? (
195
+ <motion.div key="main" initial={{ x: -10, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: -10, opacity: 0 }} className="flex flex-col gap-1">
196
+ <div className="flex items-center gap-4 p-4 mb-2 bg-dashboard-bg rounded-2xl">
197
+ <div className="size-12 rounded-xl bg-primary flex items-center justify-center font-bold text-white overflow-hidden shadow-inner">
198
+ {userImage ? <Image src={userImage} alt="Avatar" className="w-full h-full object-cover" width={48} height={48} /> : initials}
199
+ </div>
200
+ <div className="flex flex-col">
201
+ <span className="text-sm font-black uppercase text-dashboard-text truncate max-w-[120px]">
202
+ {userName}
203
+ </span>
204
+ <span className="text-[11px] text-primary font-bold uppercase italic">{userRole}</span>
205
+ </div>
206
+ </div>
207
+
208
+ {menuItems.map((item) => (
209
+ <Link
210
+ href={item.href as Parameters<typeof Link>[0]['href']}
211
+ key={item.label}
212
+ onClick={onClose}
213
+ className="flex items-center gap-3 px-4 py-4 lg:px-3 lg:py-2 rounded-xl text-[13px] lg:text-xs font-bold uppercase text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-700 active:scale-[0.98] transition-all group"
214
+ >
215
+ <item.icon className="size-5 lg:size-3.5 text-neutral-500 dark:text-neutral-400 group-hover:text-primary transition-colors" />
216
+ {item.label}
217
+ </Link>
218
+ ))}
219
+
220
+ <button onClick={(e) => { e.stopPropagation(); setShowLangSelector(true); }} className="flex items-center justify-between px-4 py-4 lg:px-3 lg:py-2 rounded-xl text-[13px] lg:text-xs font-bold uppercase text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-700 active:scale-[0.98] transition-all">
221
+ <div className="flex items-center gap-3">
222
+ <Languages className="size-5 lg:size-3.5 text-neutral-500 dark:text-neutral-400" />
223
+ Language
224
+ </div>
225
+ <span className="text-[10px] font-bold text-neutral-400 flex gap-2 items-center">
226
+ {locale} <ChevronRight className="size-[13px]" />
227
+ </span>
228
+ </button>
229
+
230
+ <hr className="my-1 border-dashboard-border" />
231
+
232
+ {isLoading ? (
233
+ <div className="py-2 flex items-center justify-center">
234
+ <Loader2 className="size-5 animate-spin text-primary" />
235
+ </div>
236
+ ) : (
237
+ <button className="flex items-center gap-3 px-4 py-4 lg:px-3 lg:py-2 rounded-xl text-[13px] lg:text-xs font-bold uppercase text-red-600 hover:bg-red-50 dark:hover:bg-red-950/20 transition-all cursor-pointer" onClick={handleLogout}>
238
+ <LogOut className="size-5 lg:size-3.5" /> Sign Out
239
+ </button>
240
+ )}
241
+ </motion.div>
242
+ ) : (
243
+ <motion.div key="lang" initial={{ x: 10, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: 10, opacity: 0 }} className="space-y-1">
244
+ {routing.locales.map((loc) => (
245
+ <button
246
+ key={loc}
247
+ onClick={() => {
248
+ router.replace(pathname as Parameters<typeof router.replace>[0], { locale: loc });
249
+ onClose();
250
+ }}
251
+ className="w-full flex items-center justify-between px-4 py-4 lg:px-3 lg:py-2.5 rounded-xl text-[13px] lg:text-xs font-bold uppercase transition-all hover:bg-neutral-100 dark:hover:bg-neutral-700 active:scale-[0.98]"
252
+ >
253
+ <span className={locale === loc ? 'text-primary' : 'text-neutral-700 dark:text-neutral-200'}>{loc}</span>
254
+ {locale === loc && <Check className="size-4 text-primary" />}
255
+ </button>
256
+ ))}
257
+ <button onClick={(e) => { e.stopPropagation(); setShowLangSelector(false); }} className="w-full mt-2 py-3 lg:py-2 text-[10px] font-bold uppercase text-neutral-500 hover:text-neutral-900 dark:hover:text-neutral-200 transition-colors">
258
+ Back
259
+ </button>
260
+ </motion.div>
261
+ )}
262
+ </AnimatePresence>
263
+ </div>
264
+ </TopbarModal>
265
+ )}
266
+ </AnimatePresence>
267
+ </div>
268
+ );
269
+ }
270
+
271
+ export default function Topbar() {
272
+ const { theme, setTheme, resolvedTheme } = useTheme();
273
+ const [mounted, setMounted] = useState(false);
274
+ const locale = useLocale();
275
+ const router = useRouter();
276
+ const pathname = usePathname();
277
+ const [activeModal, setActiveModal] = useState<string | null>(null);
278
+
279
+ useEffect(() => {
280
+ setTimeout(() => {
281
+ setMounted(true);
282
+ }, 0);
283
+ }, []);
284
+
285
+ /**
286
+ * LOGIC:
287
+ * If we are in 'system' mode, we look at 'resolvedTheme' to see what's actually showing.
288
+ * If it's showing Dark (via system), the first click should take us to manual Light.
289
+ * If it's showing Light (via system), the first click should take us to manual Dark.
290
+ */
291
+ const handleThemeToggle = () => {
292
+ if (theme === 'system') {
293
+ // Move to the opposite of what the system is currently rendering
294
+ setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
295
+ } else {
296
+ // Standard binary toggle if already in manual mode
297
+ setTheme(theme === 'dark' ? 'light' : 'dark');
298
+ }
299
+ };
300
+
301
+ const handleToggle = (modalName: string) => setActiveModal(prev => prev === modalName ? null : modalName);
302
+
303
+ const website = useWebsite();
304
+
305
+ return (
306
+ <header className="h-20 bg-dashboard-sidebar border-b border-dashboard-border flex items-center px-4 sm:px-8 justify-between sticky top-0 z-40 select-none">
307
+ <div className="flex items-center gap-4 ml-12 lg:ml-0">
308
+ <div className="hidden xs:block h-1.5 w-1.5 shrink-0 rounded-full bg-emerald-500 animate-pulse" />
309
+ <div className="flex items-center gap-3">
310
+ <div className="text-[11px] lg:text-xs uppercase tracking-wider font-bold text-dashboard-text">
311
+ <span className="text-primary italic">{website.name}</span>
312
+ {website.tagline && (
313
+ <>
314
+ <span className="text-neutral-400 dark:text-neutral-500 mx-1">/</span>
315
+ <span className="text-neutral-600 dark:text-neutral-400">{website.tagline}</span>
316
+ </>
317
+ )}
318
+ </div>
319
+ <a
320
+ href={website.homeUrl}
321
+ className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-dashboard-bg hover:bg-dashboard-card transition-colors group"
322
+ title={`Go back to ${website.name}`}
323
+ target="_blank"
324
+ rel="noopener noreferrer"
325
+ >
326
+ <Home className="size-3.5 text-neutral-600 dark:text-neutral-400 group-hover:text-primary transition-colors" />
327
+ <span className="text-[10px] font-bold uppercase tracking-wider text-neutral-600 dark:text-neutral-400 group-hover:text-primary transition-colors hidden sm:inline">
328
+ Website
329
+ </span>
330
+ </a>
331
+ </div>
332
+ </div>
333
+
334
+ <div className="flex items-center gap-1 sm:gap-2">
335
+ <div className="relative"
336
+ onMouseEnter={() => window.innerWidth >= 1024 && setActiveModal('notif')}
337
+ onMouseLeave={() => window.innerWidth >= 1024 && setActiveModal(null)}>
338
+ <NotificationMenu active={activeModal === 'notif'} onToggle={() => handleToggle('notif')} onClose={() => setActiveModal(null)} />
339
+ </div>
340
+
341
+ {/* THE SYNCED THEME SWITCH */}
342
+ <button
343
+ onClick={handleThemeToggle}
344
+ className="size-10 flex items-center justify-center bg-dashboard-bg border border-dashboard-border rounded-xl hover:bg-dashboard-card active:scale-95 transition-all mx-1 group"
345
+ >
346
+ {mounted && (
347
+ resolvedTheme === 'dark' ? (
348
+ <Sun className="size-4 text-yellow-500 group-hover:rotate-45 transition-transform" />
349
+ ) : (
350
+ <Moon className="size-4 text-neutral-600 group-hover:-rotate-12 transition-transform" />
351
+ )
352
+ )}
353
+ </button>
354
+
355
+ <div className="relative"
356
+ onMouseEnter={() => window.innerWidth >= 1024 && setActiveModal('user')}
357
+ onMouseLeave={() => window.innerWidth >= 1024 && setActiveModal(null)}>
358
+ <UserMenu active={activeModal === 'user'} onToggle={() => handleToggle('user')} onClose={() => setActiveModal(null)} locale={locale} router={router} pathname={pathname} />
359
+ </div>
360
+ </div>
361
+ </header>
362
+ );
363
+ }
@@ -0,0 +1,130 @@
1
+ "use client";
2
+ import Link from 'next/link';
3
+ import { motion } from 'framer-motion';
4
+ import { ArrowRight, Sparkles, BarChart3, PenTool, Mail } from 'lucide-react';
5
+
6
+ export default function LandingPage() {
7
+ return (
8
+ <div className="min-h-screen bg-white dark:bg-neutral-950 text-neutral-950 dark:text-white selection:bg-primary/20 overflow-hidden">
9
+
10
+ {/* 1. Navigation */}
11
+ <nav className="fixed top-0 w-full z-50 bg-white/70 dark:bg-neutral-950/70 backdrop-blur-xl border-b border-neutral-100 dark:border-neutral-900">
12
+ <div className="flex items-center justify-between px-6 lg:px-12 py-5 max-w-7xl mx-auto">
13
+ <div className="text-2xl font-black tracking-tighter text-primary italic">
14
+ JHITS<span className="text-neutral-950 dark:text-white">.</span>
15
+ </div>
16
+
17
+ <div className="hidden md:flex items-center gap-10 text-[10px] font-black uppercase tracking-[0.2em] text-neutral-500">
18
+ <a href="#features" className="hover:text-primary transition-colors">Features</a>
19
+ <a href="#about" className="hover:text-primary transition-colors">Platform</a>
20
+ <a href="#pricing" className="hover:text-primary transition-colors">Pricing</a>
21
+ </div>
22
+
23
+ <Link
24
+ href="/login"
25
+ className="bg-neutral-950 dark:bg-white dark:text-neutral-950 text-white px-6 py-2.5 rounded-2xl font-black text-[10px] uppercase tracking-widest hover:scale-105 transition-all shadow-xl shadow-neutral-200 dark:shadow-none"
26
+ >
27
+ Open Dashboard
28
+ </Link>
29
+ </div>
30
+ </nav>
31
+
32
+ {/* 2. Hero Section */}
33
+ <main className="relative pt-40 pb-20 lg:pt-56 lg:pb-32 px-8 max-w-7xl mx-auto text-center">
34
+ {/* Background Decor */}
35
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-full -z-10">
36
+ <div className="absolute top-20 left-1/4 size-96 bg-primary/10 rounded-full blur-[120px] animate-pulse" />
37
+ <div className="absolute bottom-0 right-1/4 size-96 bg-blue-400/10 rounded-full blur-[120px]" />
38
+ </div>
39
+
40
+ <motion.div
41
+ initial={{ opacity: 0, y: 20 }}
42
+ animate={{ opacity: 1, y: 0 }}
43
+ className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-neutral-100 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 text-neutral-600 dark:text-neutral-400 font-bold text-[10px] uppercase tracking-[0.2em] mb-8"
44
+ >
45
+ <Sparkles className="size-3 text-primary" />
46
+ <span>Next-Gen SaaS Engine</span>
47
+ </motion.div>
48
+
49
+ <motion.h1
50
+ initial={{ opacity: 0, y: 20 }}
51
+ animate={{ opacity: 1, y: 0 }}
52
+ transition={{ delay: 0.1 }}
53
+ className="text-5xl md:text-8xl font-black tracking-tight text-neutral-950 dark:text-white mb-8 leading-[0.9]"
54
+ >
55
+ The Modular <br />
56
+ <span className="text-primary italic">Engine.</span>
57
+ </motion.h1>
58
+
59
+ <motion.p
60
+ initial={{ opacity: 0, y: 20 }}
61
+ animate={{ opacity: 1, y: 0 }}
62
+ transition={{ delay: 0.2 }}
63
+ className="text-lg md:text-xl text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto mb-12 leading-relaxed font-medium"
64
+ >
65
+ Manage content, analyze real-time data, and scale your
66
+ digital presence with our unified <span className="text-neutral-950 dark:text-white font-bold underline decoration-primary underline-offset-4">Platform v2.0</span>.
67
+ </motion.p>
68
+
69
+ <motion.div
70
+ initial={{ opacity: 0, y: 20 }}
71
+ animate={{ opacity: 1, y: 0 }}
72
+ transition={{ delay: 0.3 }}
73
+ className="flex flex-col sm:flex-row items-center justify-center gap-4"
74
+ >
75
+ <Link href="/login" className="group w-full sm:w-auto bg-primary text-white px-10 py-5 rounded-4xl font-black text-xs uppercase tracking-widest hover:scale-105 active:scale-95 transition-all shadow-2xl shadow-primary/30 flex items-center justify-center gap-2">
76
+ Get Started Free
77
+ <ArrowRight className="size-4 group-hover:translate-x-1 transition-transform" />
78
+ </Link>
79
+ <button className="w-full sm:w-auto px-10 py-5 rounded-4xl border-2 border-neutral-100 dark:border-neutral-900 font-black text-xs uppercase tracking-widest hover:bg-neutral-50 dark:hover:bg-neutral-900 transition-all">
80
+ Documentation
81
+ </button>
82
+ </motion.div>
83
+ </main>
84
+
85
+ {/* 3. Features Preview */}
86
+ <section id="features" className="py-24 px-8 max-w-7xl mx-auto">
87
+ <div className="flex flex-col md:flex-row justify-between items-end mb-16 gap-4">
88
+ <div className="text-left">
89
+ <h2 className="text-4xl font-black text-neutral-950 dark:text-white uppercase italic tracking-tighter">Integrated Modules</h2>
90
+ <p className="text-neutral-500 font-medium">Everything you need, nothing you don&apos;t.</p>
91
+ </div>
92
+ <div className="h-[2px] flex-1 bg-neutral-100 dark:bg-neutral-900 mx-8 hidden md:block" />
93
+ </div>
94
+
95
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
96
+ <FeatureCard
97
+ icon={BarChart3}
98
+ title="Statistics"
99
+ desc="Advanced metrics and user behavior tracking in real-time."
100
+ />
101
+ <FeatureCard
102
+ icon={PenTool}
103
+ title="Blog Engine"
104
+ desc="Modular content management with high-performance SEO."
105
+ />
106
+ <FeatureCard
107
+ icon={Mail}
108
+ title="Newsletter"
109
+ desc="Segmented user lists and automated delivery pipelines."
110
+ />
111
+ </div>
112
+ </section>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ function FeatureCard({ title, desc, icon: Icon }: { title: string; desc: string; icon: React.ElementType }) {
118
+ return (
119
+ <div className="group p-10 bg-white dark:bg-neutral-900 rounded-[2.5rem] border border-neutral-100 dark:border-neutral-800 shadow-sm hover:shadow-2xl hover:shadow-primary/5 hover:border-primary/30 transition-all duration-500">
120
+ <div className="w-14 h-14 bg-neutral-50 dark:bg-neutral-800 rounded-2xl mb-8 flex items-center justify-center text-neutral-400 group-hover:bg-primary group-hover:text-white transition-all duration-500 shadow-inner">
121
+ <Icon className="size-6" />
122
+ </div>
123
+ <h3 className="text-xl font-black mb-4 uppercase tracking-tight text-neutral-950 dark:text-white">{title}</h3>
124
+ <p className="text-neutral-600 dark:text-neutral-400 leading-relaxed text-sm font-medium">{desc}</p>
125
+ <div className="mt-8 flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-primary opacity-0 group-hover:opacity-100 transition-opacity">
126
+ Learn More <ArrowRight className="size-3" />
127
+ </div>
128
+ </div>
129
+ );
130
+ }