portfolify 2.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 (66) hide show
  1. package/dist/commands/generate.d.ts +7 -0
  2. package/dist/commands/generate.d.ts.map +1 -0
  3. package/dist/commands/generate.js +129 -0
  4. package/dist/commands/generate.js.map +1 -0
  5. package/dist/commands/portfolio.d.ts +12 -0
  6. package/dist/commands/portfolio.d.ts.map +1 -0
  7. package/dist/commands/portfolio.js +292 -0
  8. package/dist/commands/portfolio.js.map +1 -0
  9. package/dist/generator/index.d.ts +3 -0
  10. package/dist/generator/index.d.ts.map +1 -0
  11. package/dist/generator/index.js +572 -0
  12. package/dist/generator/index.js.map +1 -0
  13. package/dist/generator/portfolio-generator.d.ts +4 -0
  14. package/dist/generator/portfolio-generator.d.ts.map +1 -0
  15. package/dist/generator/portfolio-generator.js +1577 -0
  16. package/dist/generator/portfolio-generator.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +40 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/prompts/index.d.ts +81 -0
  22. package/dist/prompts/index.d.ts.map +1 -0
  23. package/dist/prompts/index.js +330 -0
  24. package/dist/prompts/index.js.map +1 -0
  25. package/dist/prompts/portfolio-prompts.d.ts +60 -0
  26. package/dist/prompts/portfolio-prompts.d.ts.map +1 -0
  27. package/dist/prompts/portfolio-prompts.js +635 -0
  28. package/dist/prompts/portfolio-prompts.js.map +1 -0
  29. package/dist/themes/index.d.ts +15 -0
  30. package/dist/themes/index.d.ts.map +1 -0
  31. package/dist/themes/index.js +64 -0
  32. package/dist/themes/index.js.map +1 -0
  33. package/dist/utils/file.d.ts +5 -0
  34. package/dist/utils/file.d.ts.map +1 -0
  35. package/dist/utils/file.js +33 -0
  36. package/dist/utils/file.js.map +1 -0
  37. package/dist/utils/logger.d.ts +9 -0
  38. package/dist/utils/logger.d.ts.map +1 -0
  39. package/dist/utils/logger.js +22 -0
  40. package/dist/utils/logger.js.map +1 -0
  41. package/dist/utils/validator.d.ts +27 -0
  42. package/dist/utils/validator.d.ts.map +1 -0
  43. package/dist/utils/validator.js +322 -0
  44. package/dist/utils/validator.js.map +1 -0
  45. package/package.json +66 -0
  46. package/templates/index.html +16 -0
  47. package/templates/package.json +37 -0
  48. package/templates/postcss.config.js +6 -0
  49. package/templates/src/App.tsx +56 -0
  50. package/templates/src/components/Blog.tsx +119 -0
  51. package/templates/src/components/Footer.tsx +206 -0
  52. package/templates/src/components/Hero.tsx +182 -0
  53. package/templates/src/components/Projects.tsx +130 -0
  54. package/templates/src/components/SEO.tsx +38 -0
  55. package/templates/src/components/Skills.tsx +107 -0
  56. package/templates/src/components/ThemeToggle.tsx +39 -0
  57. package/templates/src/config/portfolio.json +61 -0
  58. package/templates/src/content/blog/welcome.mdx +29 -0
  59. package/templates/src/lib/blog.ts +63 -0
  60. package/templates/src/lib/utils.ts +6 -0
  61. package/templates/src/main.tsx +13 -0
  62. package/templates/src/styles/globals.css +123 -0
  63. package/templates/tailwind.config.js +65 -0
  64. package/templates/tsconfig.json +37 -0
  65. package/templates/tsconfig.node.json +12 -0
  66. package/templates/vite.config.ts +24 -0
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,56 @@
1
+ import { useState, useEffect } from 'react';
2
+ import Hero from './components/Hero';
3
+ import Projects from './components/Projects';
4
+ import Skills from './components/Skills';
5
+ import Blog from './components/Blog';
6
+ import Footer from './components/Footer';
7
+ import SEO from './components/SEO';
8
+ import ThemeToggle from './components/ThemeToggle';
9
+ import portfolioData from './config/portfolio.json';
10
+
11
+ function App() {
12
+ const [isDarkMode, setIsDarkMode] = useState(() => {
13
+ // Check localStorage first, then use config default
14
+ const stored = localStorage.getItem('theme');
15
+ if (stored) return stored === 'dark';
16
+ return portfolioData.defaultDarkMode ?? true;
17
+ });
18
+
19
+ useEffect(() => {
20
+ // Apply theme class to document
21
+ if (isDarkMode) {
22
+ document.documentElement.classList.add('dark');
23
+ document.documentElement.classList.remove('light');
24
+ } else {
25
+ document.documentElement.classList.add('light');
26
+ document.documentElement.classList.remove('dark');
27
+ }
28
+ localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
29
+ }, [isDarkMode]);
30
+
31
+ const toggleTheme = () => {
32
+ setIsDarkMode(prev => !prev);
33
+ };
34
+
35
+ return (
36
+ <>
37
+ <SEO
38
+ title={`${portfolioData.name} - ${portfolioData.role}`}
39
+ description={portfolioData.bio}
40
+ name={portfolioData.name}
41
+ />
42
+ <div className="min-h-screen bg-background text-foreground transition-colors duration-300">
43
+ {portfolioData.enableDarkMode && (
44
+ <ThemeToggle isDarkMode={isDarkMode} toggleTheme={toggleTheme} />
45
+ )}
46
+ <Hero data={portfolioData} />
47
+ <Projects projects={portfolioData.projects} />
48
+ <Skills skills={portfolioData.skills} />
49
+ {portfolioData.enableBlog && <Blog />}
50
+ <Footer social={portfolioData.social} name={portfolioData.name} />
51
+ </div>
52
+ </>
53
+ );
54
+ }
55
+
56
+ export default App;
@@ -0,0 +1,119 @@
1
+ import { motion } from 'framer-motion';
2
+ import { Calendar, Clock } from 'lucide-react';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ // Sample blog posts - in a real app, these would come from MDX files
6
+ const blogPosts = [
7
+ {
8
+ title: 'Getting Started with React and TypeScript',
9
+ excerpt: 'Learn how to set up a modern React project with TypeScript and best practices.',
10
+ date: '2024-01-15',
11
+ readTime: '5 min read',
12
+ slug: 'react-typescript-guide',
13
+ },
14
+ {
15
+ title: 'Building Performant Web Applications',
16
+ excerpt: 'Tips and tricks for optimizing your web applications for better performance.',
17
+ date: '2024-01-10',
18
+ readTime: '8 min read',
19
+ slug: 'performant-web-apps',
20
+ },
21
+ {
22
+ title: 'Modern CSS Techniques',
23
+ excerpt: 'Explore the latest CSS features and how to use them in your projects.',
24
+ date: '2024-01-05',
25
+ readTime: '6 min read',
26
+ slug: 'modern-css-techniques',
27
+ },
28
+ ];
29
+
30
+ export default function Blog() {
31
+ const containerVariants = {
32
+ hidden: { opacity: 0 },
33
+ visible: {
34
+ opacity: 1,
35
+ transition: {
36
+ staggerChildren: 0.2,
37
+ },
38
+ },
39
+ };
40
+
41
+ const cardVariants = {
42
+ hidden: { opacity: 0, y: 30 },
43
+ visible: {
44
+ opacity: 1,
45
+ y: 0,
46
+ transition: { duration: 0.6, ease: 'easeOut' },
47
+ },
48
+ };
49
+
50
+ return (
51
+ <section className="py-20 px-4">
52
+ <div className="max-w-6xl mx-auto">
53
+ <motion.div
54
+ initial={{ opacity: 0, y: 20 }}
55
+ whileInView={{ opacity: 1, y: 0 }}
56
+ viewport={{ once: true }}
57
+ transition={{ duration: 0.6 }}
58
+ className="text-center mb-16"
59
+ >
60
+ <h2 className="text-4xl md:text-5xl font-bold mb-4">
61
+ Latest <span className="gradient-text">Blog Posts</span>
62
+ </h2>
63
+ <p className="text-muted-foreground text-lg">
64
+ Thoughts, tutorials, and insights
65
+ </p>
66
+ </motion.div>
67
+
68
+ <motion.div
69
+ variants={containerVariants}
70
+ initial="hidden"
71
+ whileInView="visible"
72
+ viewport={{ once: true }}
73
+ className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
74
+ >
75
+ {blogPosts.map((post, index) => (
76
+ <motion.article
77
+ key={index}
78
+ variants={cardVariants}
79
+ whileHover={{ scale: 1.03, y: -5 }}
80
+ className={cn(
81
+ 'glass rounded-xl p-6 hover:glow-effect transition-all duration-300',
82
+ 'border border-border/50 hover:border-primary/50 cursor-pointer'
83
+ )}
84
+ >
85
+ <h3 className="text-xl font-bold mb-3 gradient-text">
86
+ {post.title}
87
+ </h3>
88
+
89
+ <p className="text-muted-foreground mb-4 line-clamp-2">
90
+ {post.excerpt}
91
+ </p>
92
+
93
+ <div className="flex items-center gap-4 text-sm text-muted-foreground">
94
+ <div className="flex items-center gap-1">
95
+ <Calendar className="w-4 h-4" />
96
+ {new Date(post.date).toLocaleDateString('en-US', {
97
+ month: 'short',
98
+ day: 'numeric',
99
+ year: 'numeric',
100
+ })}
101
+ </div>
102
+ <div className="flex items-center gap-1">
103
+ <Clock className="w-4 h-4" />
104
+ {post.readTime}
105
+ </div>
106
+ </div>
107
+
108
+ <div className="mt-4 pt-4 border-t border-border/50">
109
+ <span className="text-primary font-medium hover:underline">
110
+ Read more →
111
+ </span>
112
+ </div>
113
+ </motion.article>
114
+ ))}
115
+ </motion.div>
116
+ </div>
117
+ </section>
118
+ );
119
+ }
@@ -0,0 +1,206 @@
1
+ import { Github, Linkedin, Twitter, Mail, Heart, Instagram, Youtube, Phone, Music, Palette } from 'lucide-react';
2
+ import { cn } from '@/lib/utils';
3
+
4
+ interface FooterProps {
5
+ social: {
6
+ github?: string;
7
+ linkedin?: string;
8
+ twitter?: string;
9
+ email?: string;
10
+ instagram?: string;
11
+ behance?: string;
12
+ dribbble?: string;
13
+ youtube?: string;
14
+ tiktok?: string;
15
+ phone?: string;
16
+ };
17
+ name: string;
18
+ }
19
+
20
+ export default function Footer({ social, name }: FooterProps) {
21
+ const currentYear = new Date().getFullYear();
22
+
23
+ const normalizeLink = (url: string, baseUrl: string) => {
24
+ if (!url) return '';
25
+ if (url.startsWith('http') || url.startsWith('mailto:') || url.startsWith('tel:')) return url;
26
+ return `${baseUrl}/${url.replace(/^@/, '')}`;
27
+ };
28
+
29
+ return (
30
+ <footer className="py-12 px-4 border-t border-border/50 bg-muted/20">
31
+ <div className="max-w-6xl mx-auto">
32
+ <div className="flex flex-col items-center gap-6">
33
+ {/* Social Links */}
34
+ <div className="flex flex-wrap justify-center gap-4">
35
+ {social.github && (
36
+ <a
37
+ href={normalizeLink(social.github, 'https://github.com')}
38
+ target="_blank"
39
+ rel="noopener noreferrer"
40
+ className={cn(
41
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
42
+ 'hover:scale-110 active:scale-95'
43
+ )}
44
+ aria-label="GitHub"
45
+ >
46
+ <Github className="w-5 h-5" />
47
+ </a>
48
+ )}
49
+
50
+ {social.linkedin && (
51
+ <a
52
+ href={normalizeLink(social.linkedin, 'https://linkedin.com/in')}
53
+ target="_blank"
54
+ rel="noopener noreferrer"
55
+ className={cn(
56
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
57
+ 'hover:scale-110 active:scale-95'
58
+ )}
59
+ aria-label="LinkedIn"
60
+ >
61
+ <Linkedin className="w-5 h-5" />
62
+ </a>
63
+ )}
64
+
65
+ {social.twitter && (
66
+ <a
67
+ href={normalizeLink(social.twitter, 'https://twitter.com')}
68
+ target="_blank"
69
+ rel="noopener noreferrer"
70
+ className={cn(
71
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
72
+ 'hover:scale-110 active:scale-95'
73
+ )}
74
+ aria-label="Twitter"
75
+ >
76
+ <Twitter className="w-5 h-5" />
77
+ </a>
78
+ )}
79
+
80
+ {social.instagram && (
81
+ <a
82
+ href={normalizeLink(social.instagram, 'https://instagram.com')}
83
+ target="_blank"
84
+ rel="noopener noreferrer"
85
+ className={cn(
86
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
87
+ 'hover:scale-110 active:scale-95'
88
+ )}
89
+ aria-label="Instagram"
90
+ >
91
+ <Instagram className="w-5 h-5" />
92
+ </a>
93
+ )}
94
+
95
+ {social.youtube && (
96
+ <a
97
+ href={normalizeLink(social.youtube, 'https://youtube.com')}
98
+ target="_blank"
99
+ rel="noopener noreferrer"
100
+ className={cn(
101
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
102
+ 'hover:scale-110 active:scale-95'
103
+ )}
104
+ aria-label="YouTube"
105
+ >
106
+ <Youtube className="w-5 h-5" />
107
+ </a>
108
+ )}
109
+
110
+ {social.behance && (
111
+ <a
112
+ href={normalizeLink(social.behance, 'https://behance.net')}
113
+ target="_blank"
114
+ rel="noopener noreferrer"
115
+ className={cn(
116
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
117
+ 'hover:scale-110 active:scale-95'
118
+ )}
119
+ aria-label="Behance"
120
+ >
121
+ <Palette className="w-5 h-5" />
122
+ </a>
123
+ )}
124
+
125
+ {social.dribbble && (
126
+ <a
127
+ href={normalizeLink(social.dribbble, 'https://dribbble.com')}
128
+ target="_blank"
129
+ rel="noopener noreferrer"
130
+ className={cn(
131
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
132
+ 'hover:scale-110 active:scale-95'
133
+ )}
134
+ aria-label="Dribbble"
135
+ >
136
+ <svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
137
+ <path d="M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm7.938 5.5a10.27 10.27 0 012.312 6.375c-3.375-.688-6.375-.688-9-.375.375-.75.375-.75.75-1.5C15.875 9.125 18.5 7.625 19.938 5.5zM12 1.75c2.688 0 5.125 1.063 6.938 2.75-1.313 1.875-3.688 3.375-5.313 4.125-1.687-3-3.562-5.438-4.125-6.188A10.2 10.2 0 0112 1.75zm-4.063.938c.563.75 2.438 3.188 4.125 6.063-4.312 1.125-8.063 1.125-9 1.125.687-3.25 2.625-6 4.875-7.188zM1.75 12v-.375c1.125.063 5.625 0 10.313-1.313.313.563.563 1.125.875 1.688-4.688 1.313-7.875 4.5-9.375 6.563A10.196 10.196 0 011.75 12zm10.25 10.25c-2.438 0-4.687-.813-6.5-2.188 1.313-1.875 4.125-4.813 8.438-6.188 1.125 3 2.063 6.375 2.438 8.063a10.3 10.3 0 01-4.376.313zm6.063-1.688c-.375-1.563-1.188-4.688-2.25-7.5 2.438-.313 5.063 0 8.063.563-.563 3-2.625 5.625-5.813 6.937z"/>
138
+ </svg>
139
+ </a>
140
+ )}
141
+
142
+ {social.tiktok && (
143
+ <a
144
+ href={normalizeLink(social.tiktok, 'https://tiktok.com/@')}
145
+ target="_blank"
146
+ rel="noopener noreferrer"
147
+ className={cn(
148
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
149
+ 'hover:scale-110 active:scale-95'
150
+ )}
151
+ aria-label="TikTok"
152
+ >
153
+ <Music className="w-5 h-5" />
154
+ </a>
155
+ )}
156
+
157
+ {social.email && (
158
+ <a
159
+ href={social.email.startsWith('mailto:') ? social.email : `mailto:${social.email}`}
160
+ className={cn(
161
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
162
+ 'hover:scale-110 active:scale-95'
163
+ )}
164
+ aria-label="Email"
165
+ >
166
+ <Mail className="w-5 h-5" />
167
+ </a>
168
+ )}
169
+
170
+ {social.phone && (
171
+ <a
172
+ href={social.phone.startsWith('tel:') ? social.phone : `tel:${social.phone}`}
173
+ className={cn(
174
+ 'p-3 rounded-full glass hover:glow-effect transition-all duration-300',
175
+ 'hover:scale-110 active:scale-95'
176
+ )}
177
+ aria-label="Phone"
178
+ >
179
+ <Phone className="w-5 h-5" />
180
+ </a>
181
+ )}
182
+ </div>
183
+
184
+ {/* Built with Portfolify Badge */}
185
+ <a
186
+ href="https://github.com/halloffame12/portfolify"
187
+ target="_blank"
188
+ rel="noopener noreferrer"
189
+ className={cn(
190
+ 'px-4 py-2 rounded-full glass hover:glow-effect transition-all duration-300',
191
+ 'flex items-center gap-2 text-sm hover:scale-105'
192
+ )}
193
+ >
194
+ Built with <Heart className="w-4 h-4 text-red-500 fill-red-500" /> using{' '}
195
+ <span className="gradient-text font-semibold">Portfolify</span>
196
+ </a>
197
+
198
+ {/* Copyright */}
199
+ <p className="text-muted-foreground text-sm">
200
+ © {currentYear} {name}. All rights reserved.
201
+ </p>
202
+ </div>
203
+ </div>
204
+ </footer>
205
+ );
206
+ }
@@ -0,0 +1,182 @@
1
+ import { motion } from 'framer-motion';
2
+ import { Github, Linkedin, Mail, Download, Twitter } from 'lucide-react';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ interface HeroProps {
6
+ data: {
7
+ name: string;
8
+ role: string;
9
+ bio: string;
10
+ social: {
11
+ github?: string;
12
+ linkedin?: string;
13
+ twitter?: string;
14
+ email?: string;
15
+ };
16
+ };
17
+ }
18
+
19
+ export default function Hero({ data }: HeroProps) {
20
+ const containerVariants = {
21
+ hidden: { opacity: 0 },
22
+ visible: {
23
+ opacity: 1,
24
+ transition: {
25
+ staggerChildren: 0.2,
26
+ delayChildren: 0.3,
27
+ },
28
+ },
29
+ };
30
+
31
+ const itemVariants = {
32
+ hidden: { opacity: 0, y: 20 },
33
+ visible: {
34
+ opacity: 1,
35
+ y: 0,
36
+ transition: { duration: 0.6, ease: 'easeOut' },
37
+ },
38
+ };
39
+
40
+ const normalizeLink = (url: string, baseUrl: string) => {
41
+ if (!url) return '';
42
+ if (url.startsWith('http') || url.startsWith('mailto:')) return url;
43
+ return `${baseUrl}/${url.replace(/^@/, '')}`;
44
+ };
45
+
46
+ return (
47
+ <section className="min-h-screen flex items-center justify-center px-4 py-20 relative overflow-hidden">
48
+ {/* Animated background gradient */}
49
+ <div className="absolute inset-0 bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 opacity-30 animate-pulse" />
50
+
51
+ {/* Floating orbs */}
52
+ <div className="absolute top-20 left-10 w-72 h-72 bg-primary/30 rounded-full blur-3xl animate-float" />
53
+ <div className="absolute bottom-20 right-10 w-96 h-96 bg-secondary/30 rounded-full blur-3xl animate-float" style={{ animationDelay: '1s' }} />
54
+
55
+ <motion.div
56
+ className="max-w-4xl mx-auto text-center relative z-10"
57
+ variants={containerVariants}
58
+ initial="hidden"
59
+ animate="visible"
60
+ >
61
+ <motion.div variants={itemVariants}>
62
+ <h1 className="text-5xl md:text-7xl lg:text-8xl font-bold mb-6">
63
+ Hi, I'm{' '}
64
+ <span className="gradient-text animate-glow">
65
+ {data.name}
66
+ </span>
67
+ </h1>
68
+ </motion.div>
69
+
70
+ <motion.div variants={itemVariants}>
71
+ <h2 className="text-2xl md:text-4xl font-semibold text-muted-foreground mb-8">
72
+ {data.role}
73
+ </h2>
74
+ </motion.div>
75
+
76
+ <motion.p
77
+ variants={itemVariants}
78
+ className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed"
79
+ >
80
+ {data.bio}
81
+ </motion.p>
82
+
83
+ <motion.div
84
+ variants={itemVariants}
85
+ className="flex flex-wrap gap-4 justify-center mb-12"
86
+ >
87
+ {data.social.github && (
88
+ <a
89
+ href={normalizeLink(data.social.github, 'https://github.com')}
90
+ target="_blank"
91
+ rel="noopener noreferrer"
92
+ className={cn(
93
+ 'px-6 py-3 rounded-lg glass hover:glow-effect transition-all duration-300',
94
+ 'flex items-center gap-2 hover:scale-105 active:scale-95'
95
+ )}
96
+ >
97
+ <Github className="w-5 h-5" />
98
+ GitHub
99
+ </a>
100
+ )}
101
+
102
+ {data.social.linkedin && (
103
+ <a
104
+ href={normalizeLink(data.social.linkedin, 'https://linkedin.com/in')}
105
+ target="_blank"
106
+ rel="noopener noreferrer"
107
+ className={cn(
108
+ 'px-6 py-3 rounded-lg glass hover:glow-effect transition-all duration-300',
109
+ 'flex items-center gap-2 hover:scale-105 active:scale-95'
110
+ )}
111
+ >
112
+ <Linkedin className="w-5 h-5" />
113
+ LinkedIn
114
+ </a>
115
+ )}
116
+
117
+ {data.social.twitter && (
118
+ <a
119
+ href={normalizeLink(data.social.twitter, 'https://twitter.com')}
120
+ target="_blank"
121
+ rel="noopener noreferrer"
122
+ className={cn(
123
+ 'px-6 py-3 rounded-lg glass hover:glow-effect transition-all duration-300',
124
+ 'flex items-center gap-2 hover:scale-105 active:scale-95'
125
+ )}
126
+ >
127
+ <Twitter className="w-5 h-5" />
128
+ Twitter
129
+ </a>
130
+ )}
131
+
132
+ {data.social.email && (
133
+ <a
134
+ href={data.social.email.startsWith('mailto:') ? data.social.email : `mailto:${data.social.email}`}
135
+ className={cn(
136
+ 'px-6 py-3 rounded-lg glass hover:glow-effect transition-all duration-300',
137
+ 'flex items-center gap-2 hover:scale-105 active:scale-95'
138
+ )}
139
+ >
140
+ <Mail className="w-5 h-5" />
141
+ Email
142
+ </a>
143
+ )}
144
+
145
+ <button
146
+ className={cn(
147
+ 'px-6 py-3 rounded-lg bg-gradient-to-r from-primary to-secondary hover:glow-effect transition-all duration-300',
148
+ 'flex items-center gap-2 hover:scale-105 active:scale-95 font-semibold'
149
+ )}
150
+ >
151
+ <Download className="w-5 h-5" />
152
+ Resume
153
+ </button>
154
+ </motion.div>
155
+
156
+ <motion.div
157
+ variants={itemVariants}
158
+ className="flex justify-center"
159
+ >
160
+ <a
161
+ href="#projects"
162
+ title="Scroll to projects"
163
+ aria-label="Scroll to projects section"
164
+ className="text-muted-foreground hover:text-foreground transition-colors animate-bounce"
165
+ >
166
+ <svg
167
+ className="w-6 h-6"
168
+ fill="none"
169
+ strokeLinecap="round"
170
+ strokeLinejoin="round"
171
+ strokeWidth="2"
172
+ viewBox="0 0 24 24"
173
+ stroke="currentColor"
174
+ >
175
+ <path d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
176
+ </svg>
177
+ </a>
178
+ </motion.div>
179
+ </motion.div>
180
+ </section>
181
+ );
182
+ }