portosaurus 0.14.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.

Potentially problematic release.


This version of portosaurus might be problematic. Click here for more details.

Files changed (64) hide show
  1. package/.vscode/snippets.code-snippets +79 -0
  2. package/AGENTS.md +37 -0
  3. package/GG/config.js +233 -0
  4. package/GG/package.json +14 -0
  5. package/GG/static/.nojekyll +0 -0
  6. package/GG/static/docusaurus-snippet.css +3 -0
  7. package/GG/static/img/icon-bg.png +0 -0
  8. package/GG/static/img/icon-old.png +0 -0
  9. package/GG/static/img/icon.png +0 -0
  10. package/GG/static/img/project-blank.png +0 -0
  11. package/GG/static/img/social-card.jpeg +0 -0
  12. package/LICENSE +674 -0
  13. package/README.md +57 -0
  14. package/bin/portosaurus.js +136 -0
  15. package/package.json +36 -0
  16. package/src/config/iconMappings.js +329 -0
  17. package/src/config/metaTags.js +240 -0
  18. package/src/config/prism.js +179 -0
  19. package/src/config/sidebar.js +20 -0
  20. package/src/configLoader.js +99 -0
  21. package/src/index.js +79 -0
  22. package/src/pages/index.js +98 -0
  23. package/src/pages/notes.js +88 -0
  24. package/src/pages/tasks.js +251 -0
  25. package/src/theme/components/AboutSection/index.js +67 -0
  26. package/src/theme/components/AboutSection/styles.module.css +492 -0
  27. package/src/theme/components/ContactSection/index.js +87 -0
  28. package/src/theme/components/ContactSection/styles.module.css +327 -0
  29. package/src/theme/components/ExperienceSection/index.js +25 -0
  30. package/src/theme/components/ExperienceSection/styles.module.css +180 -0
  31. package/src/theme/components/HeroSection/index.js +63 -0
  32. package/src/theme/components/HeroSection/styles.module.css +471 -0
  33. package/src/theme/components/NoteIndex/index.js +119 -0
  34. package/src/theme/components/NoteIndex/styles.module.css +143 -0
  35. package/src/theme/components/ProjectsSection/index.js +529 -0
  36. package/src/theme/components/ProjectsSection/styles.module.css +830 -0
  37. package/src/theme/components/ScrollToTop/index.js +98 -0
  38. package/src/theme/components/ScrollToTop/styles.module.css +96 -0
  39. package/src/theme/components/SocialLinks/index.js +129 -0
  40. package/src/theme/components/SocialLinks/styles.module.css +55 -0
  41. package/src/theme/components/Tooltip/index.js +30 -0
  42. package/src/theme/components/Tooltip/styles.module.css +92 -0
  43. package/src/theme/css/bootstrap.css +6 -0
  44. package/src/theme/css/catppuccin.css +632 -0
  45. package/src/theme/css/custom.css +186 -0
  46. package/src/theme/css/tasks.css +868 -0
  47. package/src/theme/staticLink/.nojekyll +0 -0
  48. package/src/theme/staticLink/docusaurus-snippet.css +3 -0
  49. package/src/theme/staticLink/img/icon-bg.png +0 -0
  50. package/src/theme/staticLink/img/icon-old.png +0 -0
  51. package/src/theme/staticLink/img/icon.png +0 -0
  52. package/src/theme/staticLink/img/project-blank.png +0 -0
  53. package/src/theme/staticLink/img/social-card.jpeg +0 -0
  54. package/src/utils/HashNavigation.js +250 -0
  55. package/src/utils/appVersion.js +27 -0
  56. package/src/utils/cssUtils.js +99 -0
  57. package/src/utils/filterEnabledItems.js +21 -0
  58. package/src/utils/generateFavicon.js +256 -0
  59. package/src/utils/generateRobotsTxt.js +97 -0
  60. package/src/utils/iconExtractor.js +159 -0
  61. package/src/utils/imageDownloader.js +88 -0
  62. package/src/utils/imageProcessor.js +134 -0
  63. package/src/utils/linkShortner.js +0 -0
  64. package/src/utils/updateTitle.js +107 -0
@@ -0,0 +1,240 @@
1
+ import { getCssVar } from "../utils/cssUtils.js";
2
+
3
+ const backgroundColor = getCssVar('--ifm-background-color');
4
+
5
+ export const metaTags = [
6
+
7
+ // Theme color meta tags
8
+ {
9
+ tagName: 'meta',
10
+ attributes: {
11
+ name: 'msapplication-TileColor',
12
+ content: backgroundColor,
13
+ },
14
+ },
15
+ {
16
+ tagName: 'meta',
17
+ attributes: {
18
+ name: 'theme-color',
19
+ content: backgroundColor,
20
+ },
21
+ },
22
+
23
+ // Android Chrome icons
24
+ {
25
+ tagName: 'link',
26
+ attributes: {
27
+ rel: 'icon',
28
+ type: 'image/png',
29
+ sizes: '36x36',
30
+ href: '/favicon/android-chrome-36x36.png',
31
+ },
32
+ },
33
+ {
34
+ tagName: 'link',
35
+ attributes: {
36
+ rel: 'icon',
37
+ type: 'image/png',
38
+ sizes: '48x48',
39
+ href: '/favicon/android-chrome-48x48.png',
40
+ },
41
+ },
42
+ {
43
+ tagName: 'link',
44
+ attributes: {
45
+ rel: 'icon',
46
+ type: 'image/png',
47
+ sizes: '72x72',
48
+ href: '/favicon/android-chrome-72x72.png',
49
+ },
50
+ },
51
+ {
52
+ tagName: 'link',
53
+ attributes: {
54
+ rel: 'icon',
55
+ type: 'image/png',
56
+ sizes: '96x96',
57
+ href: '/favicon/android-chrome-96x96.png',
58
+ },
59
+ },
60
+ {
61
+ tagName: 'link',
62
+ attributes: {
63
+ rel: 'icon',
64
+ type: 'image/png',
65
+ sizes: '144x144',
66
+ href: '/favicon/android-chrome-144x144.png',
67
+ },
68
+ },
69
+ {
70
+ tagName: 'link',
71
+ attributes: {
72
+ rel: 'icon',
73
+ type: 'image/png',
74
+ sizes: '192x192',
75
+ href: '/favicon/android-chrome-192x192.png',
76
+ },
77
+ },
78
+ {
79
+ tagName: 'link',
80
+ attributes: {
81
+ rel: 'icon',
82
+ type: 'image/png',
83
+ sizes: '256x256',
84
+ href: '/favicon/android-chrome-256x256.png',
85
+ },
86
+ },
87
+ {
88
+ tagName: 'link',
89
+ attributes: {
90
+ rel: 'icon',
91
+ type: 'image/png',
92
+ sizes: '384x384',
93
+ href: '/favicon/android-chrome-384x384.png',
94
+ },
95
+ },
96
+ {
97
+ tagName: 'link',
98
+ attributes: {
99
+ rel: 'icon',
100
+ type: 'image/png',
101
+ sizes: '512x512',
102
+ href: '/favicon/android-chrome-512x512.png',
103
+ },
104
+ },
105
+
106
+ // Apple touch icons
107
+ {
108
+ tagName: 'link',
109
+ attributes: {
110
+ rel: 'apple-touch-icon',
111
+ sizes: '57x57',
112
+ href: '/favicon/apple-touch-icon-57x57.png',
113
+ },
114
+ },
115
+ {
116
+ tagName: 'link',
117
+ attributes: {
118
+ rel: 'apple-touch-icon',
119
+ sizes: '60x60',
120
+ href: '/favicon/apple-touch-icon-60x60.png',
121
+ },
122
+ },
123
+ {
124
+ tagName: 'link',
125
+ attributes: {
126
+ rel: 'apple-touch-icon',
127
+ sizes: '72x72',
128
+ href: '/favicon/apple-touch-icon-72x72.png',
129
+ },
130
+ },
131
+ {
132
+ tagName: 'link',
133
+ attributes: {
134
+ rel: 'apple-touch-icon',
135
+ sizes: '76x76',
136
+ href: '/favicon/apple-touch-icon-76x76.png',
137
+ },
138
+ },
139
+ {
140
+ tagName: 'link',
141
+ attributes: {
142
+ rel: 'apple-touch-icon',
143
+ sizes: '114x114',
144
+ href: '/favicon/apple-touch-icon-114x114.png',
145
+ },
146
+ },
147
+ {
148
+ tagName: 'link',
149
+ attributes: {
150
+ rel: 'apple-touch-icon',
151
+ sizes: '120x120',
152
+ href: '/favicon/apple-touch-icon-120x120.png',
153
+ },
154
+ },
155
+ {
156
+ tagName: 'link',
157
+ attributes: {
158
+ rel: 'apple-touch-icon',
159
+ sizes: '144x144',
160
+ href: '/favicon/apple-touch-icon-144x144.png',
161
+ },
162
+ },
163
+ {
164
+ tagName: 'link',
165
+ attributes: {
166
+ rel: 'apple-touch-icon',
167
+ sizes: '152x152',
168
+ href: '/favicon/apple-touch-icon-152x152.png',
169
+ },
170
+ },
171
+ {
172
+ tagName: 'link',
173
+ attributes: {
174
+ rel: 'apple-touch-icon',
175
+ sizes: '167x167',
176
+ href: '/favicon/apple-touch-icon-167x167.png',
177
+ },
178
+ },
179
+ {
180
+ tagName: 'link',
181
+ attributes: {
182
+ rel: 'apple-touch-icon',
183
+ sizes: '180x180',
184
+ href: '/favicon/apple-touch-icon-180x180.png',
185
+ },
186
+ },
187
+ {
188
+ tagName: 'link',
189
+ attributes: {
190
+ rel: 'apple-touch-icon',
191
+ sizes: '1024x1024',
192
+ href: '/favicon/apple-touch-icon-1024x1024.png',
193
+ },
194
+ },
195
+ {
196
+ tagName: 'link',
197
+ attributes: {
198
+ rel: 'apple-touch-icon-precomposed',
199
+ href: '/favicon/apple-touch-icon-precomposed.png',
200
+ },
201
+ },
202
+
203
+ // Standard favicons
204
+ {
205
+ tagName: 'link',
206
+ attributes: {
207
+ rel: 'icon',
208
+ type: 'image/png',
209
+ sizes: '16x16',
210
+ href: '/favicon/favicon-16x16.png',
211
+ },
212
+ },
213
+ {
214
+ tagName: 'link',
215
+ attributes: {
216
+ rel: 'icon',
217
+ type: 'image/png',
218
+ sizes: '32x32',
219
+ href: '/favicon/favicon-32x32.png',
220
+ },
221
+ },
222
+ {
223
+ tagName: 'link',
224
+ attributes: {
225
+ rel: 'icon',
226
+ type: 'image/png',
227
+ sizes: '48x48',
228
+ href: '/favicon/favicon-48x48.png',
229
+ },
230
+ },
231
+
232
+ // Web manifest
233
+ {
234
+ tagName: 'link',
235
+ attributes: {
236
+ rel: 'manifest',
237
+ href: '/favicon/manifest.webmanifest',
238
+ },
239
+ }
240
+ ];
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Catppuccin theme for Prism.js
3
+ **/
4
+
5
+ // Catppuccin Mocha (dark theme)
6
+ export const catppuccinMocha = {
7
+ plain: {
8
+ color: '#cdd6f4',
9
+ backgroundColor: '#181825',
10
+ },
11
+ styles: [
12
+ {
13
+ types: ['comment', 'prolog', 'doctype', 'cdata'],
14
+ style: {
15
+ color: '#6c7086',
16
+ fontStyle: 'italic',
17
+ },
18
+ },
19
+ {
20
+ types: ['namespace'],
21
+ style: {
22
+ opacity: 0.7,
23
+ },
24
+ },
25
+ {
26
+ types: ['string', 'char', 'attr-value'],
27
+ style: {
28
+ color: '#a6e3a1',
29
+ },
30
+ },
31
+ {
32
+ types: ['punctuation', 'operator'],
33
+ style: {
34
+ color: '#a6adc8',
35
+ },
36
+ },
37
+ {
38
+ types: [
39
+ 'entity',
40
+ 'url',
41
+ 'symbol',
42
+ 'number',
43
+ 'boolean',
44
+ 'variable',
45
+ 'constant',
46
+ 'property',
47
+ 'regex',
48
+ 'inserted',
49
+ ],
50
+ style: {
51
+ color: '#fab387',
52
+ },
53
+ },
54
+ {
55
+ types: ['atrule', 'keyword', 'attr-name', 'selector'],
56
+ style: {
57
+ color: '#cba6f7',
58
+ },
59
+ },
60
+ {
61
+ types: ['function', 'deleted', 'tag'],
62
+ style: {
63
+ color: '#f38ba8',
64
+ },
65
+ },
66
+ {
67
+ types: ['function-variable'],
68
+ style: {
69
+ color: '#89b4fa',
70
+ },
71
+ },
72
+ {
73
+ types: ['class-name'],
74
+ style: {
75
+ color: '#f9e2af',
76
+ },
77
+ },
78
+ {
79
+ types: ['important', 'bold'],
80
+ style: {
81
+ fontWeight: 'bold',
82
+ },
83
+ },
84
+ {
85
+ types: ['italic'],
86
+ style: {
87
+ fontStyle: 'italic',
88
+ },
89
+ },
90
+ ],
91
+ };
92
+
93
+ // Catppuccin Latte (light theme)
94
+ export const catppuccinLatte = {
95
+ plain: {
96
+ color: '#4c4f69',
97
+ backgroundColor: '#FAF9F6',
98
+ },
99
+ styles: [
100
+ {
101
+ types: ['comment', 'prolog', 'doctype', 'cdata'],
102
+ style: {
103
+ color: '#8c8fa1',
104
+ fontStyle: 'italic',
105
+ },
106
+ },
107
+ {
108
+ types: ['namespace'],
109
+ style: {
110
+ opacity: 0.7,
111
+ },
112
+ },
113
+ {
114
+ types: ['string', 'char', 'attr-value'],
115
+ style: {
116
+ color: '#40a02b',
117
+ },
118
+ },
119
+ {
120
+ types: ['punctuation', 'operator'],
121
+ style: {
122
+ color: '#8c8fa1',
123
+ },
124
+ },
125
+ {
126
+ types: [
127
+ 'entity',
128
+ 'url',
129
+ 'symbol',
130
+ 'number',
131
+ 'boolean',
132
+ 'variable',
133
+ 'constant',
134
+ 'property',
135
+ 'regex',
136
+ 'inserted',
137
+ ],
138
+ style: {
139
+ color: '#fe640b',
140
+ },
141
+ },
142
+ {
143
+ types: ['atrule', 'keyword', 'attr-name', 'selector'],
144
+ style: {
145
+ color: '#8839ef',
146
+ },
147
+ },
148
+ {
149
+ types: ['function', 'deleted', 'tag'],
150
+ style: {
151
+ color: '#d20f39',
152
+ },
153
+ },
154
+ {
155
+ types: ['function-variable'],
156
+ style: {
157
+ color: '#1e66f5',
158
+ },
159
+ },
160
+ {
161
+ types: ['class-name'],
162
+ style: {
163
+ color: '#df8e1d',
164
+ },
165
+ },
166
+ {
167
+ types: ['important', 'bold'],
168
+ style: {
169
+ fontWeight: 'bold',
170
+ },
171
+ },
172
+ {
173
+ types: ['italic'],
174
+ style: {
175
+ fontStyle: 'italic',
176
+ },
177
+ },
178
+ ],
179
+ };
@@ -0,0 +1,20 @@
1
+
2
+ const sidebar = {
3
+
4
+ notes: [
5
+ {
6
+ type: 'category',
7
+ label: 'Notes',
8
+ link: { type: 'doc', id: 'index' },
9
+ items: [
10
+ {
11
+ type: 'autogenerated',
12
+ dirName: '.'
13
+ }
14
+ ]
15
+ }
16
+ ]
17
+ };
18
+
19
+
20
+ module.exports = sidebar;
@@ -0,0 +1,99 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ function resolvePath(obj, path) {
5
+ const parts = path.split('.');
6
+ let current = obj;
7
+
8
+ for (const part of parts) {
9
+ if (current === null || current === undefined || typeof current !== 'object') {
10
+ return undefined;
11
+ }
12
+ current = current[part];
13
+ }
14
+
15
+ return current;
16
+ }
17
+
18
+ function parseStringValue(value, config) {
19
+ if (typeof value !== 'string') {
20
+ return value;
21
+ }
22
+
23
+ // Find all ${...} references
24
+ return value.replace(/\${([^}]+)}/g, (match, path) => {
25
+ const resolvedValue = resolvePath(config, path);
26
+
27
+ if (resolvedValue === undefined) {
28
+ console.warn(`Warning: Could not resolve reference "${path}" in config value "${value}"`);
29
+ return match;
30
+ }
31
+
32
+ if (typeof resolvedValue === 'string' && resolvedValue.includes('${')) {
33
+ return parseStringValue(resolvedValue, config);
34
+ }
35
+
36
+ return resolvedValue;
37
+ });
38
+ }
39
+
40
+ function parseConfigObject(obj, config) {
41
+ if (Array.isArray(obj)) {
42
+ return obj.map(item => parseConfigObject(item, config));
43
+ }
44
+
45
+ if (obj !== null && typeof obj === 'object') {
46
+ const result = {};
47
+ for (const [key, value] of Object.entries(obj)) {
48
+ result[key] = parseConfigObject(value, config);
49
+ }
50
+ return result;
51
+ }
52
+
53
+ if (typeof obj === 'string') {
54
+ return parseStringValue(obj, config);
55
+ }
56
+
57
+ return obj;
58
+ }
59
+
60
+ function parseConfig(config) {
61
+ const parsedConfig = JSON.parse(JSON.stringify(config));
62
+ return parseConfigObject(parsedConfig, config);
63
+ }
64
+
65
+ function loadUserConfig() {
66
+ const configPath = path.resolve(process.cwd(), 'config.js');
67
+
68
+ if (!fs.existsSync(configPath)) {
69
+ console.warn(`\n⚠️ No config.js found at ${configPath}. Using empty config.\n`);
70
+ return {};
71
+ }
72
+
73
+ try {
74
+ const rawConfig = require(configPath);
75
+ // Support both `module.exports = { usrConf: ... }` and direct object
76
+ const rootConfig = rawConfig.usrConf || rawConfig;
77
+
78
+ // Parse the config (resolve variables)
79
+ const parsedExports = {};
80
+ for (const key in rootConfig) {
81
+ parsedExports[key] = parseConfig(rootConfig[key]);
82
+ }
83
+
84
+ return {
85
+ ...parsedExports,
86
+ // Helper methods attached if needed, but mostly we just return the object
87
+ };
88
+
89
+ } catch (err) {
90
+ console.error(`\n❌ Error loading config.js:`, err);
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ loadUserConfig,
97
+ parseConfig,
98
+ resolvePath
99
+ };
package/src/index.js ADDED
@@ -0,0 +1,79 @@
1
+ const path = require('path');
2
+ const { loadUserConfig } = require('./configLoader');
3
+
4
+ module.exports = function preset(context, options) {
5
+ // 1. Load User Configuration
6
+ const userConfig = loadUserConfig();
7
+
8
+ // 2. Resolve Paths
9
+ const customCss = [
10
+ require.resolve('./theme/css/catppuccin.css'),
11
+ require.resolve('./theme/css/custom.css'),
12
+ require.resolve('./theme/css/tasks.css') // Globally available
13
+ ];
14
+
15
+ // 3. Define the Preset
16
+ return {
17
+ themes: [
18
+ // In Docusaurus 3, we can't easily "inject" a theme folder without a proper theme definition.
19
+ // But since we are packaging components, we can add a local plugin that performs `addRoute`
20
+ // or we just trust the user to rely on our components?
21
+ // Actually, standard way is to return a theme from options.
22
+ // We will assume `preset-classic` handles the heavy lifting, and we just override CSS.
23
+ // EXCEPT: We likely have swizzled components in `src/theme/components`.
24
+ // To use them, we need to register a theme plugin that points to `src/theme`.
25
+
26
+ // Let's define a simple inline theme plugin to expose our components
27
+ function (context, options) {
28
+ return {
29
+ name: 'docusaurus-theme-portosaurus-components',
30
+ getThemePath() {
31
+ return path.resolve(__dirname, './theme');
32
+ }
33
+ };
34
+ }
35
+ ],
36
+ plugins: [
37
+ // Search Plugin
38
+ [
39
+ require.resolve('@easyops-cn/docusaurus-search-local'),
40
+ {
41
+ hashed: true,
42
+ indexDocs: true,
43
+ docsDir: "notes",
44
+ docsRouteBasePath: "notes",
45
+ highlightSearchTermsOnTargetPage: true,
46
+ searchContextByPaths: ["notes", "blog"],
47
+ ...((userConfig.usrConf && userConfig.usrConf.search) || {})
48
+ }
49
+ ],
50
+ // Image Zoom
51
+ require.resolve('plugin-image-zoom'),
52
+ ],
53
+ presets: [
54
+ [
55
+ require.resolve('@docusaurus/preset-classic'),
56
+ {
57
+ docs: {
58
+ path: 'notes',
59
+ routeBasePath: 'notes',
60
+ sidebarPath: require.resolve('./config/sidebar.js'),
61
+ // Pass through any user overrides if needed
62
+ ...((userConfig.usrConf && userConfig.usrConf.docs) || {})
63
+ },
64
+ blog: {
65
+ path: 'blog',
66
+ showReadingTime: false,
67
+ // Pass through
68
+ ...((userConfig.usrConf && userConfig.usrConf.blog) || {})
69
+ },
70
+ theme: {
71
+ customCss: customCss,
72
+ },
73
+ // Pages??
74
+ // If we want to support user pages in `src/pages` (root), it works by default.
75
+ },
76
+ ],
77
+ ],
78
+ };
79
+ };
@@ -0,0 +1,98 @@
1
+ import Layout from "@theme/Layout";
2
+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
3
+ import UpdateTitle from '@site/src/utils/updateTitle';
4
+
5
+ // Import components
6
+ import HeroSection from "@site/src/components/HeroSection";
7
+ import AboutSection from "@site/src/components/AboutSection";
8
+ import ProjectsSection from "@site/src/components/ProjectsSection";
9
+ import ContactSection from "@site/src/components/ContactSection";
10
+ import ExperienceSection from "@site/src/components/ExperienceSection";
11
+ import ScrollToTop from "@site/src/components/ScrollToTop";
12
+
13
+
14
+ export default function Home() {
15
+ const { siteConfig } = useDocusaurusContext();
16
+ const { customFields } = siteConfig;
17
+
18
+ const aboutMe = customFields.aboutMe || {};
19
+ const projects = customFields.projects || {};
20
+ const socialLinks = customFields.socialLinks || {};
21
+ const experience = customFields.experience || {};
22
+
23
+ const sectionTitles = {
24
+ 'me': `Home | ${siteConfig.title}`,
25
+ 'about': `About Me | ${siteConfig.title}`,
26
+ 'projects': `Projects | ${siteConfig.title}`,
27
+ 'experience': `Experience | ${siteConfig.title}`,
28
+ 'contact': `Contact | ${siteConfig.title}`
29
+ };
30
+
31
+ const customStyles = `
32
+ /* For future */
33
+ `;
34
+
35
+
36
+ return (
37
+ <Layout
38
+ title="Me"
39
+ description="My portfolio website"
40
+ >
41
+ {/* Custom styles */}
42
+ <style>{customStyles}</style>
43
+
44
+ <UpdateTitle
45
+ sections={sectionTitles}
46
+ defaultTitle={siteConfig.title}
47
+ />
48
+
49
+ <main>
50
+
51
+ {/* Hero Section */}
52
+ <HeroSection
53
+ id="me"
54
+ />
55
+
56
+ {/* About Section */}
57
+ {(aboutMe.enable !== false) && (
58
+ <AboutSection
59
+ id="about"
60
+ title="About Me"
61
+ />
62
+ )}
63
+
64
+ {/* Projects Section */}
65
+ {(projects.enable !== false) && (
66
+ <ProjectsSection
67
+ id="projects"
68
+ title="My Projects"
69
+ subtitle="A collection of all my works, with featured projects highlighted"
70
+ />
71
+ )}
72
+
73
+ {/* Experience Section */}
74
+ {(experience.enable !== false) && (
75
+ <ExperienceSection
76
+ id="experience"
77
+ title="Experience"
78
+ subtitle="My professional journey and work experience"
79
+ />
80
+ )}
81
+
82
+ {/* Contact Section */}
83
+ {(socialLinks.enable !== false) && (
84
+ <ContactSection
85
+ id="contact"
86
+ title="Get In Touch"
87
+ subtitle="Feel free to reach out for collaborations, questions, or just to say hello!"
88
+ />
89
+ )}
90
+
91
+ {/* Scroll to top button */}
92
+ <ScrollToTop
93
+ hideDelay={3500}
94
+ />
95
+ </main>
96
+ </Layout>
97
+ );
98
+ }