abhishek-portfolio-template 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.
Files changed (101) hide show
  1. package/README.md +59 -0
  2. package/bin/cli.js +54 -0
  3. package/package.json +27 -0
  4. package/template/components.json +22 -0
  5. package/template/next.config.ts +79 -0
  6. package/template/package.json +43 -0
  7. package/template/postcss.config.js +6 -0
  8. package/template/public/BoliviaSignature-ZpWnz.ttf +0 -0
  9. package/template/public/Gemini_Generated_Image_xc97toxc97toxc97.png +0 -0
  10. package/template/public/Hendrigo.otf +0 -0
  11. package/template/public/audiomass-output.mp3 +0 -0
  12. package/template/public/file.svg +1 -0
  13. package/template/public/globe.svg +1 -0
  14. package/template/public/googlec77e59474f5a09cb.html +1 -0
  15. package/template/public/icon-192x192.png +0 -0
  16. package/template/public/icon-512x512.png +0 -0
  17. package/template/public/next.svg +1 -0
  18. package/template/public/paper sound .mpeg +0 -0
  19. package/template/public/removebg.png +0 -0
  20. package/template/public/resume.pdf +0 -0
  21. package/template/public/sw.js +1 -0
  22. package/template/public/swe-worker-5c72df51bb1f6ee0.js +1 -0
  23. package/template/public/vercel.svg +1 -0
  24. package/template/public/window.svg +1 -0
  25. package/template/public/workbox-f1770938.js +1 -0
  26. package/template/src/app/about/page.tsx +91 -0
  27. package/template/src/app/actions/optimize-text.ts +54 -0
  28. package/template/src/app/gaming/page.tsx +308 -0
  29. package/template/src/app/github/[username]/page.tsx +97 -0
  30. package/template/src/app/globals.css +321 -0
  31. package/template/src/app/layout.tsx +39 -0
  32. package/template/src/app/manifest.ts +25 -0
  33. package/template/src/app/not-found.tsx +16 -0
  34. package/template/src/app/page.tsx +28 -0
  35. package/template/src/app/robots.ts +12 -0
  36. package/template/src/app/sitemap.ts +38 -0
  37. package/template/src/app/template.tsx +5 -0
  38. package/template/src/app/works/[slug]/page.tsx +50 -0
  39. package/template/src/app/works/client.tsx +44 -0
  40. package/template/src/app/works/page.tsx +24 -0
  41. package/template/src/components/about/about-client.tsx +259 -0
  42. package/template/src/components/home/bento-gallery.tsx +52 -0
  43. package/template/src/components/home/contact-section.tsx +34 -0
  44. package/template/src/components/home/craft-card.tsx +18 -0
  45. package/template/src/components/home/featured-projects.tsx +186 -0
  46. package/template/src/components/home/focus-card.tsx +171 -0
  47. package/template/src/components/home/identity-card.tsx +45 -0
  48. package/template/src/components/home/philosophy-card.tsx +104 -0
  49. package/template/src/components/home/skills-in-motion.tsx +109 -0
  50. package/template/src/components/home/tech-stack-marquee.tsx +56 -0
  51. package/template/src/components/ui/3d-folder.tsx +569 -0
  52. package/template/src/components/ui/avatar.tsx +50 -0
  53. package/template/src/components/ui/badge.tsx +36 -0
  54. package/template/src/components/ui/basic-avatar.tsx +12 -0
  55. package/template/src/components/ui/button.tsx +117 -0
  56. package/template/src/components/ui/clipboard-secret.tsx +39 -0
  57. package/template/src/components/ui/command-menu.tsx +519 -0
  58. package/template/src/components/ui/command-palette.tsx +152 -0
  59. package/template/src/components/ui/consciousness-mode.tsx +200 -0
  60. package/template/src/components/ui/copy-code-button.tsx +135 -0
  61. package/template/src/components/ui/display-cards.tsx +70 -0
  62. package/template/src/components/ui/dotted-map.tsx +128 -0
  63. package/template/src/components/ui/dropdown-menu.tsx +200 -0
  64. package/template/src/components/ui/emoji-rating.tsx +123 -0
  65. package/template/src/components/ui/exit-message.tsx +50 -0
  66. package/template/src/components/ui/image-zoom-overlay.tsx +178 -0
  67. package/template/src/components/ui/input-otp.tsx +71 -0
  68. package/template/src/components/ui/kbd.tsx +87 -0
  69. package/template/src/components/ui/location-tag.tsx +232 -0
  70. package/template/src/components/ui/minimal-testimonial.tsx +97 -0
  71. package/template/src/components/ui/mobile-menu.tsx +191 -0
  72. package/template/src/components/ui/navbar.tsx +148 -0
  73. package/template/src/components/ui/page-transition.tsx +24 -0
  74. package/template/src/components/ui/pixeleted-404-not-found.tsx +110 -0
  75. package/template/src/components/ui/preloader-wrapper.tsx +102 -0
  76. package/template/src/components/ui/preloader.tsx +104 -0
  77. package/template/src/components/ui/project-contributors.tsx +57 -0
  78. package/template/src/components/ui/scroll-area.tsx +117 -0
  79. package/template/src/components/ui/signature.tsx +173 -0
  80. package/template/src/components/ui/smooth-scroll.tsx +31 -0
  81. package/template/src/components/ui/social-icons.tsx +103 -0
  82. package/template/src/components/ui/social-stories.tsx +394 -0
  83. package/template/src/components/ui/sound-constants.ts +1 -0
  84. package/template/src/components/ui/text-explode.tsx +188 -0
  85. package/template/src/components/ui/toast.tsx +80 -0
  86. package/template/src/components/ui/tooltip.tsx +30 -0
  87. package/template/src/components/ui/user-location.tsx +151 -0
  88. package/template/src/components/ui/vertical-image-stack.tsx +345 -0
  89. package/template/src/components/works/changelog-overlay.tsx +212 -0
  90. package/template/src/components/works/currently-working-card.tsx +130 -0
  91. package/template/src/components/works/project-details-view.tsx +464 -0
  92. package/template/src/components/works/project-grid.tsx +81 -0
  93. package/template/src/fonts/BoliviaSignature-ZpWnz.ttf +0 -0
  94. package/template/src/fonts/Hendrigo.otf +0 -0
  95. package/template/src/lib/data.ts +61 -0
  96. package/template/src/lib/fonts.ts +14 -0
  97. package/template/src/lib/github.ts +15 -0
  98. package/template/src/lib/supabase.ts +11 -0
  99. package/template/src/lib/utils.ts +6 -0
  100. package/template/tailwind.config.ts +31 -0
  101. package/template/tsconfig.json +34 -0
@@ -0,0 +1,259 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import Image from "next/image";
5
+ import Link from "next/link";
6
+ import * as LucideIcons from "lucide-react";
7
+ import {
8
+ Gamepad2,
9
+ Film,
10
+ Plane,
11
+ Mail,
12
+ Phone,
13
+ MapPin,
14
+ User,
15
+ Download,
16
+ ExternalLink
17
+ } from "lucide-react";
18
+ import { RatingInteraction } from "@/components/ui/emoji-rating";
19
+
20
+ const BentoCard = ({ children, className = "" }: { children: React.ReactNode, className?: string }) => (
21
+ <motion.div
22
+ initial={{ opacity: 0, y: 20 }}
23
+ whileInView={{ opacity: 1, y: 0 }}
24
+ viewport={{ once: true }}
25
+ transition={{ duration: 0.5 }}
26
+ className={`bg-[#111] border border-white/5 rounded-[32px] overflow-hidden ${className}`}
27
+ >
28
+ {children}
29
+ </motion.div>
30
+ );
31
+
32
+ const Badge = ({ children }: { children: React.ReactNode }) => (
33
+ <span className="px-3 py-1 rounded-full bg-white/5 border border-white/10 text-xs font-medium text-white/60">
34
+ {children}
35
+ </span>
36
+ );
37
+
38
+ export function AboutClient({ general, experience, education, skills, interests }: any) {
39
+
40
+ // Helper to dynamic icon
41
+ const getIcon = (name: string) => {
42
+ const Icon = (LucideIcons as any)[name];
43
+ return Icon ? <Icon size={16} /> : <Gamepad2 size={16} />;
44
+ };
45
+
46
+ return (
47
+ <div className="max-w-6xl mx-auto space-y-6 px-4 md:px-8 lg:px-12 pt-32 pb-20">
48
+
49
+ {/* --- Top Row: Profile & Bio --- */}
50
+ <div className="grid grid-cols-1 md:grid-cols-12 gap-6">
51
+ <BentoCard className="md:col-span-4 h-[320px] relative group">
52
+ <Image
53
+ src={general.profile_image_url || "https://res.cloudinary.com/dap0u41dz/image/upload/v1766771167/file_00000000d51472078b7e2f9d883a6674_majhmb.jpg"}
54
+ alt={general.full_name}
55
+ fill
56
+ className="object-cover opacity-80 group-hover:scale-105 transition-transform duration-500"
57
+ />
58
+ <div className="absolute inset-x-0 bottom-0 p-6 bg-gradient-to-t from-black/80 to-transparent">
59
+ <h1 className="text-2xl font-bold">{general.full_name}</h1>
60
+ <p className="text-white/40 text-sm italic">{general.role_title}</p>
61
+ </div>
62
+ </BentoCard>
63
+
64
+ <BentoCard className="md:col-span-8 p-8 flex flex-col justify-center">
65
+ <p className="text-xl md:text-2xl font-medium leading-relaxed text-white/90 whitespace-pre-line">
66
+ {general.bio_description}
67
+ </p>
68
+ <div className="mt-8 flex flex-wrap gap-3">
69
+ <Badge>Founder</Badge>
70
+ <Badge>Product Designer</Badge>
71
+ <Badge>Systems Thinker</Badge>
72
+ </div>
73
+ </BentoCard>
74
+ </div>
75
+
76
+ {/* --- Interests Row --- */}
77
+ <BentoCard className="p-6">
78
+ <div className="flex flex-col md:flex-row md:items-center gap-6">
79
+ <span className="text-lg font-semibold border-r border-white/10 pr-6 flex items-center gap-2">
80
+ Interests
81
+ </span>
82
+ <div className="flex flex-wrap gap-4">
83
+ {interests.map((item: any, idx: number) => {
84
+ const isGaming = item.label === "Gaming";
85
+ const content = (
86
+ <>
87
+ {getIcon(item.icon_name)}
88
+ <span className="text-sm font-medium">{item.label}</span>
89
+ </>
90
+ );
91
+ const itemClassName = `
92
+ flex items-center gap-2 px-4 py-2 rounded-2xl border transition-all duration-300
93
+ ${isGaming
94
+ ? "bg-emerald-500/5 border-emerald-500/20 text-emerald-400 cursor-pointer hover:bg-emerald-500/10 hover:scale-105"
95
+ : "bg-white/[0.03] border-white/5 text-white/60"
96
+ }
97
+ `;
98
+
99
+ if (isGaming) {
100
+ return (
101
+ <Link key={idx} href="/gaming" className={itemClassName}>
102
+ {content}
103
+ </Link>
104
+ );
105
+ }
106
+
107
+ return (
108
+ <div key={idx} className={itemClassName}>
109
+ {content}
110
+ </div>
111
+ );
112
+ })}
113
+ {interests.length === 0 && <span className="text-sm text-white/20">No interests added yet.</span>}
114
+ </div>
115
+ </div>
116
+ </BentoCard>
117
+
118
+ {/* --- Experience & Education Grid --- */}
119
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
120
+
121
+ {/* Experience Cards */}
122
+ {experience.map((exp: any, idx: number) => (
123
+ <BentoCard key={exp.id} className="p-8 space-y-6">
124
+ <div className="flex justify-between items-start">
125
+ <div>
126
+ <h3 className="text-2xl font-bold">{exp.role}</h3>
127
+ <p className="text-white/40 text-sm">{exp.company}</p>
128
+ </div>
129
+ <span className="text-[10px] uppercase tracking-widest px-2 py-1 bg-white/5 border border-white/10 rounded-md">{exp.period}</span>
130
+ </div>
131
+ <div className="w-full h-px bg-white/5" />
132
+ <ul className="space-y-4 text-sm text-white/60">
133
+ {exp.description_points?.map((pt: string, i: number) => (
134
+ <li key={i} className="flex gap-3">
135
+ <span className="text-white/20">•</span>
136
+ {pt}
137
+ </li>
138
+ ))}
139
+ </ul>
140
+ </BentoCard>
141
+ ))}
142
+
143
+ {/* Education Card (Consolidated) */}
144
+ <BentoCard className="p-8 space-y-8 flex flex-col justify-between">
145
+ <div className="space-y-6">
146
+ {education.map((edu: any, idx: number) => (
147
+ <div key={edu.id} className="flex justify-between items-start">
148
+ <div>
149
+ <h3 className="text-xl font-bold">{edu.degree}</h3>
150
+ <p className="text-white/40 text-xs">{edu.institution}</p>
151
+ </div>
152
+ <span className="text-[10px] px-2 py-0.5 bg-white/5 border border-white/10 rounded">{edu.year}</span>
153
+ </div>
154
+ ))}
155
+ {education.length === 0 && <p className="text-sm text-white/40">No education added.</p>}
156
+ </div>
157
+ </BentoCard>
158
+ </div>
159
+
160
+
161
+
162
+ {/* --- Portfolio Row --- */}
163
+ <BentoCard className="p-6">
164
+ <div className="flex flex-col md:flex-row md:items-center gap-8">
165
+ <span className="text-lg font-bold border-r border-white/10 pr-8">Portfolio</span>
166
+ <div className="flex flex-wrap gap-6 text-sm font-medium text-white/40">
167
+ <a href="#" className="flex items-center gap-2 hover:text-white transition-colors"><span>Behance</span> <ExternalLink size={14} /></a>
168
+ <a href="#" className="flex items-center gap-2 hover:text-white transition-colors"><span>Dribbble</span> <ExternalLink size={14} /></a>
169
+ <a href="#" className="flex items-center gap-2 hover:text-white transition-colors"><span>Instagram</span> <ExternalLink size={14} /></a>
170
+ <a href="#" className="flex items-center gap-2 hover:text-white transition-colors"><span>LinkedIn</span> <ExternalLink size={14} /></a>
171
+ </div>
172
+ </div>
173
+ </BentoCard>
174
+
175
+ {/* --- Availability & Feedback Grid --- */}
176
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
177
+ {/* Availability Block */}
178
+ <BentoCard className="p-8">
179
+ <div className="flex flex-col h-full justify-between gap-8">
180
+ <div>
181
+ <h3 className="text-xl font-bold mb-4">Availability</h3>
182
+ <p className="text-white/40 text-sm leading-relaxed mb-6">
183
+ I am currently open to new opportunities and collaborations. I specialize in building scalable UI systems and fluid frontend experiences.
184
+ </p>
185
+ <div className="space-y-4">
186
+ {["Internships", "Freelance", "Consulting"].map((item) => (
187
+ <div key={item} className="flex items-center gap-3">
188
+ <div className="relative flex h-2 w-2">
189
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
190
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
191
+ </div>
192
+ <span className="text-sm font-medium text-white/80">{item}</span>
193
+ </div>
194
+ ))}
195
+ </div>
196
+ </div>
197
+ <div className={`flex items-center gap-3 px-4 py-3 rounded-2xl border w-fit transition-colors ${general.is_available ? "bg-emerald-500/5 border-emerald-500/10" : "bg-red-500/5 border-red-500/10"}`}>
198
+ <span className={`text-[10px] font-bold uppercase tracking-widest ${general.is_available ? "text-emerald-500" : "text-red-500"}`}>
199
+ {general.availability_status}
200
+ </span>
201
+ </div>
202
+ </div>
203
+ </BentoCard>
204
+
205
+ {/* Quick Feedback (Rating) Block */}
206
+ <BentoCard className="p-8 flex flex-col items-center justify-center text-center">
207
+ <RatingInteraction />
208
+ </BentoCard>
209
+ </div>
210
+
211
+ {/* --- Footer / Details --- */}
212
+ <BentoCard className="p-8">
213
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
214
+ <div className="flex items-center gap-4">
215
+ <div className="w-10 h-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white/40">
216
+ <User size={18} />
217
+ </div>
218
+ <div>
219
+ <p className="text-[10px] uppercase tracking-widest text-white/40 font-bold">Age</p>
220
+ <p className="text-sm font-medium">
221
+ {general.birthday ? (
222
+ `${Math.floor((Date.now() - new Date(general.birthday).getTime()) / 31557600000)} Years`
223
+ ) : "21 Years"}
224
+ </p>
225
+ </div>
226
+ </div>
227
+ <div className="flex items-center gap-4 group cursor-pointer" onClick={() => window.location.href = `mailto:${general.contact_email}`}>
228
+ <div className="w-10 h-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white/40 group-hover:bg-white/10 transition-colors">
229
+ <Mail size={18} />
230
+ </div>
231
+ <div>
232
+ <p className="text-[10px] uppercase tracking-widest text-white/40 font-bold">Email</p>
233
+ <p className="text-sm font-medium truncat max-w-[150px]">{general.contact_email}</p>
234
+ </div>
235
+ </div>
236
+ <div className="flex items-center gap-4">
237
+ <div className="w-10 h-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white/40">
238
+ <Phone size={18} />
239
+ </div>
240
+ <div>
241
+ <p className="text-[10px] uppercase tracking-widest text-white/40 font-bold">Phone</p>
242
+ <p className="text-sm font-medium">{general.phone_number || "+91 0000000000"}</p>
243
+ </div>
244
+ </div>
245
+ <div className="flex items-center gap-4">
246
+ <div className="w-10 h-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white/40">
247
+ <MapPin size={18} />
248
+ </div>
249
+ <div>
250
+ <p className="text-[10px] uppercase tracking-widest text-white/40 font-bold">Location</p>
251
+ <p className="text-sm font-medium">{general.location || "Kolkata, India"}</p>
252
+ </div>
253
+ </div>
254
+ </div>
255
+ </BentoCard>
256
+
257
+ </div>
258
+ );
259
+ }
@@ -0,0 +1,52 @@
1
+ "use client"
2
+ // Rebuilding layout to standard grid
3
+
4
+ import { motion } from "framer-motion"
5
+ import { IdentityCard } from "./identity-card"
6
+ import { VerticalImageStack } from "@/components/ui/vertical-image-stack"
7
+ import { FocusCard } from "./focus-card"
8
+ import { PhilosophyCard } from "./philosophy-card"
9
+ import { CraftCard } from "./craft-card"
10
+
11
+ export function BentoGallery() {
12
+ return (
13
+ <section className="w-full max-w-[1400px] mx-auto px-4 md:px-6 pb-12 pt-4 md:py-24 lg:py-0">
14
+
15
+ {/*
16
+ Responsive Bento Grid:
17
+ - Mobile: 1 Column
18
+ - Tablet (md): 2 Columns
19
+ - Desktop (lg): 3 Columns
20
+ */}
21
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 auto-rows-min items-start">
22
+
23
+ {/* 1. Identity Card - Top Left */}
24
+ <div className="order-1 md:order-1 lg:col-span-1 h-[450px] md:h-[500px] lg:h-[500px]">
25
+ <IdentityCard />
26
+ </div>
27
+
28
+ {/* 2. Gallery/Phone - Center Column (Spans 2 Rows on Desktop) */}
29
+ <div className="order-2 md:order-3 lg:order-2 md:col-span-1 lg:col-span-1 lg:row-span-2 h-[400px] md:h-[600px] lg:h-[764px] bg-[#111] rounded-[32px] border border-white/5 overflow-hidden relative shadow-2xl">
30
+ <div className="absolute inset-0">
31
+ <VerticalImageStack />
32
+ </div>
33
+ </div>
34
+
35
+ {/* 3. Focus Card - Top Right */}
36
+ <div className="order-3 md:order-2 lg:order-3 md:col-span-1 lg:col-span-1 h-[550px] md:h-[500px] lg:h-[500px]">
37
+ <FocusCard />
38
+ </div>
39
+
40
+ {/* 4. Philosophy Card - Bottom Left */}
41
+ <div className="order-4 md:order-4 lg:order-4 md:col-span-1 lg:col-span-1 h-[200px] md:h-[240px] lg:h-[240px]">
42
+ <PhilosophyCard />
43
+ </div>
44
+
45
+ {/* 5. Craft Card - Bottom Right */}
46
+ <div className="order-5 md:order-5 lg:order-5 md:col-span-1 lg:col-span-1 h-[200px] md:h-[240px] lg:h-[240px]">
47
+ <CraftCard />
48
+ </div>
49
+ </div>
50
+ </section>
51
+ )
52
+ }
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import Link from "next/link";
5
+ import { ArrowRight, Mail } from "lucide-react";
6
+ import { Signature } from "@/components/ui/signature";
7
+
8
+ export function ContactSection() {
9
+ return (
10
+ <section id="contact" className="py-32 relative overflow-hidden">
11
+ {/* Subtle Signature */}
12
+ <Signature />
13
+
14
+ <div className="flex flex-col items-center justify-center text-center max-w-2xl mx-auto px-4 relative z-10">
15
+ <h2 className="text-4xl md:text-5xl lg:text-6xl font-medium text-white mb-6">
16
+ Let's build something <span className="text-white/40">extraordinary.</span>
17
+ </h2>
18
+
19
+ <p className="text-white/60 text-lg md:text-xl mb-10 max-w-lg">
20
+ Currently available for select projects. Let's discuss your vision.
21
+ </p>
22
+
23
+ <Link
24
+ href="mailto:abhishek23main@gmail.com"
25
+ className="group flex items-center gap-3 bg-white text-black px-8 py-4 rounded-full font-medium hover:bg-white/90 transition-all hover:scale-105 active:scale-95"
26
+ >
27
+ <Mail className="w-5 h-5" />
28
+ <span>Start a Project</span>
29
+ <ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
30
+ </Link>
31
+ </div>
32
+ </section>
33
+ );
34
+ }
@@ -0,0 +1,18 @@
1
+ import { DottedMap } from "@/components/ui/dotted-map"
2
+
3
+ export function CraftCard() {
4
+ return (
5
+ <div className="relative w-full h-full min-h-[180px] rounded-[32px] overflow-hidden group border border-white/5 bg-[#111]">
6
+ <div className="absolute inset-0 flex items-center justify-center opacity-90 transition-opacity duration-100">
7
+ <DottedMap
8
+ className="text-white/40 w-full max-w-[6000px]"
9
+ markers={[{ lat: 20.6, lng: 78.96, size: 0.8 }]}
10
+ markerColor="#F97316"
11
+ dotRadius={0.2}
12
+ />
13
+ </div>
14
+
15
+
16
+ </div>
17
+ )
18
+ }
@@ -0,0 +1,186 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
4
+ import Image from "next/image";
5
+ import Link from "next/link";
6
+ import { ArrowUpRight } from "lucide-react";
7
+ import { Project } from "@/lib/data";
8
+ import { useEffect, useState } from "react";
9
+ import { supabase } from "@/lib/supabase";
10
+
11
+ // Strict type for Supabase response to avoid 'any'
12
+ interface SupabaseProject {
13
+ id: string;
14
+ title: string;
15
+ description: string;
16
+ tech_stack: string[];
17
+ image_url: string;
18
+ slug: string;
19
+ featured: boolean;
20
+ display_order: number;
21
+ }
22
+
23
+ function ProjectCard({ project }: { project: Project }) {
24
+ return (
25
+ <Link href={project.link} className="group block h-full">
26
+ <div className="relative h-full bg-[#111111] border border-white/5 rounded-[24px] overflow-hidden transition-colors duration-500 hover:border-white/10 flex flex-col group-hover:bg-[#161616]">
27
+ {/* Image Container */}
28
+ <div className="relative w-full aspect-[16/10] overflow-hidden">
29
+ <Image
30
+ src={project.image}
31
+ alt={project.title}
32
+ fill
33
+ sizes="(max-width: 768px) 100vw, 50vw"
34
+ className="object-cover transition-transform duration-700 ease-[0.25,1,0.5,1] group-hover:scale-105"
35
+ />
36
+ <div className="absolute inset-0 bg-black/20 group-hover:bg-transparent transition-colors duration-500" />
37
+ </div>
38
+
39
+ {/* Content */}
40
+ <div className="p-6 flex flex-col flex-1 justify-between">
41
+ <div>
42
+ <div className="flex items-start justify-between">
43
+ <h3 className="text-xl font-medium text-white/90 group-hover:text-white transition-colors">
44
+ {project.title}
45
+ </h3>
46
+ <ArrowUpRight className="w-5 h-5 text-white/30 group-hover:text-white group-hover:-translate-y-1 group-hover:translate-x-1 transition-all duration-300" />
47
+ </div>
48
+ <p className="mt-2 text-white/50 text-sm leading-relaxed group-hover:text-white/70 transition-colors line-clamp-2">
49
+ {project.description}
50
+ </p>
51
+ </div>
52
+
53
+ <div className="mt-6 flex flex-wrap gap-1.5">
54
+ {project.techStack.map((tech) => (
55
+ <span
56
+ key={tech}
57
+ className="px-2.5 py-0.5 rounded-full text-[10px] uppercase tracking-wider font-medium bg-white/5 text-white/40 border border-white/5"
58
+ >
59
+ {tech}
60
+ </span>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </Link>
66
+ );
67
+ }
68
+
69
+ function ProjectCardSkeleton() {
70
+ return (
71
+ <div className="h-full bg-[#111111] border border-white/5 rounded-[24px] overflow-hidden flex flex-col">
72
+ {/* Image Skeleton */}
73
+ <div className="relative w-full aspect-[16/10] bg-white/5 animate-pulse" />
74
+
75
+ {/* Content Skeleton */}
76
+ <div className="p-6 flex flex-col flex-1 justify-between gap-6">
77
+ <div className="space-y-3">
78
+ <div className="flex justify-between items-start">
79
+ <div className="h-7 w-1/2 bg-white/5 rounded-md animate-pulse" />
80
+ <div className="h-5 w-5 bg-white/5 rounded-md animate-pulse" />
81
+ </div>
82
+ <div className="space-y-2">
83
+ <div className="h-4 w-full bg-white/5 rounded-md animate-pulse" />
84
+ <div className="h-4 w-2/3 bg-white/5 rounded-md animate-pulse" />
85
+ </div>
86
+ </div>
87
+ <div className="flex gap-2">
88
+ <div className="h-5 w-16 bg-white/5 rounded-full animate-pulse" />
89
+ <div className="h-5 w-12 bg-white/5 rounded-full animate-pulse" />
90
+ <div className="h-5 w-20 bg-white/5 rounded-full animate-pulse" />
91
+ </div>
92
+ </div>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ export function FeaturedProjects() {
98
+ const [projects, setProjects] = useState<Project[]>([]);
99
+ const [isLoading, setIsLoading] = useState(true);
100
+ const [hasError, setHasError] = useState(false);
101
+
102
+ useEffect(() => {
103
+ async function fetchProjects() {
104
+ try {
105
+ const { data, error } = await supabase
106
+ .from('projects')
107
+ .select('*')
108
+ .eq('featured', true)
109
+ .order('display_order', { ascending: true });
110
+
111
+ if (error) {
112
+ console.error('Error fetching projects:', error);
113
+ setHasError(true);
114
+ } else if (data) {
115
+ // Safe type mapping
116
+ const mappedProjects: Project[] = (data as unknown as SupabaseProject[]).map((item) => ({
117
+ id: item.id,
118
+ title: item.title,
119
+ description: item.description,
120
+ techStack: item.tech_stack || [],
121
+ image: item.image_url,
122
+ link: `/works/${item.slug}`,
123
+ }));
124
+ setProjects(mappedProjects);
125
+ }
126
+ } catch (err) {
127
+ console.error("Unexpected error fetching projects", err);
128
+ setHasError(true);
129
+ } finally {
130
+ setIsLoading(false);
131
+ }
132
+ }
133
+
134
+ fetchProjects();
135
+ }, []);
136
+
137
+ // Error State
138
+ if (hasError) {
139
+ return (
140
+ <section className="py-20 flex justify-center text-white/40">
141
+ <p>Unable to load contents</p>
142
+ </section>
143
+ );
144
+ }
145
+
146
+ return (
147
+ <section id="featured-projects" className="py-20">
148
+ {/* Section Header */}
149
+ <div className="flex items-center justify-between mb-12 px-2">
150
+ <h2 className="text-3xl md:text-4xl font-medium text-white/90">
151
+ Selected Works
152
+ </h2>
153
+ <Link
154
+ href="/works"
155
+ className="group flex items-center gap-2 text-white/50 hover:text-white transition-colors text-sm md:text-base"
156
+ >
157
+ <span>View All</span>
158
+ <ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
159
+ </Link>
160
+ </div>
161
+
162
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
163
+ {isLoading ? (
164
+ // Skeleton Loading State
165
+ <>
166
+ <ProjectCardSkeleton />
167
+ <ProjectCardSkeleton />
168
+ </>
169
+ ) : (
170
+ // Real Data
171
+ projects.map((project, index) => (
172
+ <motion.div
173
+ key={project.id}
174
+ initial={{ opacity: 0, y: 20 }}
175
+ whileInView={{ opacity: 1, y: 0 }}
176
+ viewport={{ once: true, margin: "-100px" }}
177
+ transition={{ duration: 0.5, delay: index * 0.1 }}
178
+ >
179
+ <ProjectCard project={project} />
180
+ </motion.div>
181
+ ))
182
+ )}
183
+ </div>
184
+ </section>
185
+ );
186
+ }