portosaurus 1.18.7 → 1.18.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portosaurus",
3
- "version": "1.18.7",
3
+ "version": "1.18.9",
4
4
  "author": "soymadip",
5
5
  "license": "GPL-3.0-only",
6
6
  "description": "Complete portfolio cum personal website solution for your digital personality.",
@@ -34,7 +34,6 @@
34
34
  "./utils/*": "./src/utils/*.js",
35
35
  "./config/*": "./src/config/*.js",
36
36
  "./css/*": "./src/css/*.css",
37
- "./internal/*": "./src/internal/src/*",
38
37
  "./pages/*": "./src/pages/*.js"
39
38
  },
40
39
  "bin": {
@@ -47,7 +46,9 @@
47
46
  "src/utils/",
48
47
  "src/config/",
49
48
  "src/css/",
50
- "src/img"
49
+ "src/img",
50
+ "src/pages/",
51
+ "src/components/"
51
52
  ],
52
53
  "dependencies": {
53
54
  "@docusaurus/core": "^3.9.2",
@@ -0,0 +1,67 @@
1
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
2
+ import styles from './styles.module.css';
3
+
4
+ export default function AboutSection({ id, className}) {
5
+ const { siteConfig } = useDocusaurusContext();
6
+ const { customFields } = siteConfig;
7
+ const aboutMe = customFields.aboutMe || {};
8
+
9
+ return (
10
+ <div id={id} className={`${styles.aboutSection} ${className || ''}`} role="region" aria-label="About me section">
11
+ <div className={styles.aboutContainer}>
12
+ <div className={styles.aboutHeader}>
13
+ <h2 className={styles.aboutHeading}>{"About Me"}</h2>
14
+ </div>
15
+
16
+ <div className={styles.aboutContent}>
17
+ <div className={styles.aboutBio}>
18
+ <div className={styles.bioImageContainer}>
19
+ {aboutMe.image && (
20
+ <div className={styles.imageWrapper}>
21
+ <img
22
+ src={aboutMe.image}
23
+ alt="About Me"
24
+ className={styles.aboutImage}
25
+ loading="lazy"
26
+ />
27
+ </div>
28
+ )}
29
+ </div>
30
+
31
+ <div className={styles.bioTextContainer}>
32
+ <div className={styles.bioText}>
33
+ {Array.isArray(aboutMe.description) ? (
34
+ aboutMe.description.map((paragraph, index) => (
35
+ <p key={index} className={styles.aboutParagraph}>{paragraph}</p>
36
+ ))
37
+ ) : (
38
+ <p className={styles.aboutParagraph}>
39
+ {aboutMe.description || "Information about me goes here."}
40
+ </p>
41
+ )}
42
+ </div>
43
+
44
+ {aboutMe.skills && aboutMe.skills.length > 0 && (
45
+ <div className={styles.skillsContainer}>
46
+ <h3 className={styles.skillsTitle} id="skills-heading">My Skills</h3>
47
+ <div className={styles.skillsGrid} role="list">
48
+ {aboutMe.skills.map((skill, index) => (
49
+ <div
50
+ key={index}
51
+ className={styles.skillBadge}
52
+ role="listitem"
53
+ style={{ animationDelay: `${index * 0.05}s` }}
54
+ >
55
+ {skill}
56
+ </div>
57
+ ))}
58
+ </div>
59
+ </div>
60
+ )}
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,492 @@
1
+
2
+ /* Animations*/
3
+
4
+ @keyframes skillAppear {
5
+ to {
6
+ opacity: 1;
7
+ transform: translateY(0);
8
+ }
9
+ }
10
+
11
+ @keyframes fadeSlideIn {
12
+ from {
13
+ opacity: 0;
14
+ transform: translateY(10px);
15
+ }
16
+ to {
17
+ opacity: 1;
18
+ transform: translateY(0);
19
+ }
20
+ }
21
+
22
+ @keyframes slideUp {
23
+ from {
24
+ opacity: 0;
25
+ transform: translateY(20px);
26
+ }
27
+ to {
28
+ opacity: 1;
29
+ transform: translateY(0);
30
+ }
31
+ }
32
+
33
+ @keyframes fadeIn {
34
+ from { opacity: 0; }
35
+ to { opacity: 1; }
36
+ }
37
+
38
+
39
+ /* Styles */
40
+
41
+ .aboutSection {
42
+ scroll-margin-top: var(--ifm-scroll-margin-top);
43
+ padding: 0.5rem 0 9.5rem;
44
+ background-color: var(--ifm-background-color);
45
+ min-height: 100vh;
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ position: relative;
50
+ width: 100%;
51
+ }
52
+
53
+ .aboutContainer {
54
+ max-width: 1200px;
55
+ margin: 0 auto;
56
+ padding: 0 2rem;
57
+ width: 100%;
58
+ position: relative;
59
+ z-index: 1;
60
+ }
61
+
62
+ .aboutHeader {
63
+ text-align: center;
64
+ margin-bottom: 4.7rem;
65
+ }
66
+
67
+ .aboutHeading {
68
+ font-size: 2.8rem;
69
+ font-weight: 600;
70
+ color: var(--ifm-color-primary);
71
+ margin-bottom: 0.6rem;
72
+ animation: slideUp 0.5s ease-out forwards;
73
+ position: relative;
74
+ display: inline-block;
75
+ }
76
+
77
+ .aboutHeading::after {
78
+ content: '';
79
+ position: absolute;
80
+ width: 30%;
81
+ height: 3px;
82
+ bottom: -0.5rem;
83
+ left: 35%;
84
+ background-color: var(--ifm-color-primary);
85
+ border-radius: 2px;
86
+ }
87
+
88
+ .aboutContent {
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 3rem;
92
+ padding: 0;
93
+ animation: fadeIn 0.8s ease-out 0.3s both;
94
+ align-items: center;
95
+ }
96
+
97
+ .aboutBio {
98
+ display: flex;
99
+ flex-direction: row;
100
+ gap: 4rem;
101
+ width: 100%;
102
+ align-items: center;
103
+ }
104
+
105
+ .bioImageContainer {
106
+ flex: 1;
107
+ display: flex;
108
+ justify-content: center;
109
+ align-items: center;
110
+ padding-top: 0;
111
+ align-self: stretch;
112
+ margin-bottom: 1.3rem;
113
+ }
114
+
115
+ .imageWrapper {
116
+ position: relative;
117
+ border-radius: 12px;
118
+ overflow: hidden;
119
+ box-shadow: 0 10px 20px var(--ifm-shadow-color);
120
+ transform: none;
121
+ transition: transform 0.4s ease, box-shadow 0.4s ease;
122
+ max-width: 350px;
123
+ margin: auto 0;
124
+ }
125
+
126
+ .imageWrapper::before {
127
+ content: '';
128
+ position: absolute;
129
+ top: 0;
130
+ left: 0;
131
+ right: 0;
132
+ bottom: 0;
133
+ border: 2px solid var(--ifm-color-primary);
134
+ border-radius: 10px;
135
+ opacity: 0.2;
136
+ pointer-events: none;
137
+ z-index: 1;
138
+ }
139
+
140
+ .imageWrapper:hover {
141
+ transform: translateY(-8px);
142
+ box-shadow: 0 15px 30px var(--ifm-shadow-color);
143
+ }
144
+
145
+ .aboutImage {
146
+ display: block;
147
+ max-width: 100%;
148
+ height: auto;
149
+ border-radius: 8px;
150
+ object-fit: cover;
151
+ transition: filter 0.3s ease;
152
+ }
153
+
154
+ .bioTextContainer {
155
+ flex: 1.5;
156
+ display: flex;
157
+ flex-direction: column;
158
+ gap: 1.2rem;
159
+ align-self: flex-start;
160
+ }
161
+
162
+ .bioText {
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 8px;
166
+ align-self: flex-start;
167
+ margin-top: -0.5rem;
168
+ }
169
+
170
+ .aboutParagraph {
171
+ margin-bottom: 0.2rem;
172
+ font-size: 1.05rem;
173
+ line-height: 1.8;
174
+ color: var(--ifm-font-color-base);
175
+ position: relative;
176
+ padding-left: 0.8rem;
177
+ opacity: 0;
178
+ animation: fadeSlideIn 0.5s ease-out forwards;
179
+ animation-delay: calc(0.3s + var(--paragraph-index, 0) * 0.15s);
180
+ }
181
+
182
+ .aboutParagraph:last-child {
183
+ margin-bottom: 0;
184
+ }
185
+
186
+ .aboutParagraph::before {
187
+ content: '';
188
+ position: absolute;
189
+ left: 0;
190
+ top: 0.5rem;
191
+ bottom: 0.5rem;
192
+ width: 3px;
193
+ background: linear-gradient(to bottom, var(--ifm-color-primary), transparent);
194
+ border-radius: 4px;
195
+ opacity: 0.7;
196
+ }
197
+
198
+ .resumeButtonWrapper {
199
+ margin-top: 1rem;
200
+ }
201
+
202
+ .resumeButton {
203
+ display: inline-block;
204
+ padding: 0.6rem 1.5rem;
205
+ font-size: 0.95rem;
206
+ font-weight: 500;
207
+ text-decoration: none;
208
+ color: var(--ifm-background-color);
209
+ background-color: var(--ifm-color-primary);
210
+ border-radius: 4px;
211
+ transition: all 0.3s ease, transform 0.2s ease;
212
+ border: 2px solid var(--ifm-color-primary);
213
+ position: relative;
214
+ overflow: hidden;
215
+ z-index: 1;
216
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
217
+ }
218
+
219
+ .resumeButton::before {
220
+ content: '';
221
+ position: absolute;
222
+ top: 0;
223
+ left: -100%;
224
+ width: 100%;
225
+ height: 100%;
226
+ background: linear-gradient(
227
+ 90deg,
228
+ transparent,
229
+ rgba(255, 255, 255, 0.2),
230
+ transparent
231
+ );
232
+ transition: left 0.7s ease;
233
+ z-index: -1;
234
+ }
235
+
236
+ .resumeButton:hover {
237
+ background-color: transparent;
238
+ color: var(--ifm-color-primary);
239
+ transform: translateY(-3px);
240
+ box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
241
+ text-decoration: none;
242
+ }
243
+
244
+ .resumeButton:hover::before {
245
+ left: 100%;
246
+ }
247
+
248
+ .buttonText {
249
+ position: relative;
250
+ z-index: 2;
251
+ }
252
+
253
+ .skillsContainer {
254
+ width: 100%;
255
+ padding: 0.8rem 0 0.5rem;
256
+ position: relative;
257
+ overflow: hidden;
258
+ margin-top: 0;
259
+ }
260
+
261
+ .skillsTitle {
262
+ font-size: 1.4rem;
263
+ margin-bottom: 1.2rem;
264
+ color: var(--ifm-color-primary);
265
+ font-weight: 600;
266
+ padding-left: 0;
267
+ position: relative;
268
+ display: inline-block;
269
+ }
270
+
271
+ .skillsTitle::after {
272
+ content: '';
273
+ position: absolute;
274
+ width: 40%;
275
+ height: 2px;
276
+ bottom: -0.4rem;
277
+ left: 0;
278
+ background-color: var(--ifm-color-primary);
279
+ opacity: 0.5;
280
+ border-radius: 2px;
281
+ }
282
+
283
+ .skillsGrid {
284
+ display: flex;
285
+ flex-wrap: wrap;
286
+ gap: 0.9rem;
287
+ padding-left: 0;
288
+ padding-top: 0.4rem;
289
+ margin-bottom: 0.5rem;
290
+ }
291
+
292
+ .skillBadge {
293
+ background-color: rgba(var(--ctp-lavender-rgb), 0.08);
294
+ color: var(--ctp-lavender);
295
+ border-radius: 20px;
296
+ padding: 0.5rem 1rem;
297
+ font-size: 0.95rem;
298
+ font-weight: 500;
299
+ transition: all 0.3s ease;
300
+ border: 1px solid rgba(var(--ctp-lavender-rgb), 0.15);
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ opacity: 0;
305
+ animation: skillAppear 0.4s ease-out forwards;
306
+ transform: translateY(10px);
307
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
308
+ }
309
+
310
+ .skillBadge:hover {
311
+ transform: translateY(-3px);
312
+ box-shadow: 0 5px 12px rgba(0, 0, 0, 0.1);
313
+ background-color: rgba(var(--ctp-lavender-rgb), 0.15);
314
+ border-color: rgba(var(--ctp-lavender-rgb), 0.3);
315
+ }
316
+
317
+ /* Responsive styles */
318
+ @media (max-width: 992px) {
319
+ .aboutBio {
320
+ gap: 3rem;
321
+ }
322
+ }
323
+
324
+ @media (max-width: 768px) {
325
+ .aboutSection {
326
+ scroll-margin-top: var(--ifm-scroll-margin-top-mobile);
327
+ padding: 1.5rem 0 4rem;
328
+ }
329
+
330
+ .aboutContainer {
331
+ padding: 0 1.5rem;
332
+ }
333
+
334
+ .aboutHeader {
335
+ margin-bottom: 2rem;
336
+ }
337
+
338
+ .aboutHeading {
339
+ font-size: 2.4rem;
340
+ }
341
+
342
+ .aboutBio {
343
+ flex-direction: column;
344
+ gap: 2rem;
345
+ }
346
+
347
+ .bioImageContainer {
348
+ width: 100%;
349
+ padding-top: 0;
350
+ padding-bottom: 0.8rem;
351
+ margin-bottom: 0.8rem;
352
+ }
353
+
354
+ .imageWrapper {
355
+ transform: none;
356
+ max-width: 300px;
357
+ margin: 0 auto;
358
+ }
359
+
360
+ .bioTextContainer {
361
+ width: 100%;
362
+ gap: 1rem;
363
+ }
364
+
365
+ .skillsContainer {
366
+ padding: 0.6rem 0 0.5rem;
367
+ margin-top: 0;
368
+ }
369
+
370
+ .skillsTitle {
371
+ margin-bottom: 1rem;
372
+ display: block;
373
+ text-align: center;
374
+ width: 100%;
375
+ }
376
+
377
+ .skillsTitle::after {
378
+ width: 40px;
379
+ left: 50%;
380
+ transform: translateX(-50%);
381
+ }
382
+
383
+ .skillsGrid {
384
+ justify-content: center;
385
+ }
386
+ }
387
+
388
+ @media (max-width: 480px) {
389
+ .aboutSection {
390
+ scroll-margin-top: 25px;
391
+ padding: 0.5rem 0 2.5rem;
392
+ min-height: auto;
393
+ }
394
+
395
+ .aboutContainer {
396
+ padding: 0 1rem;
397
+ }
398
+
399
+ .aboutHeader {
400
+ margin-bottom: 1.4rem;
401
+ }
402
+
403
+ .aboutHeading {
404
+ font-size: 1.8rem;
405
+ }
406
+
407
+ .aboutContent {
408
+ gap: 1.5rem;
409
+ }
410
+
411
+ .aboutBio {
412
+ gap: 1.6rem;
413
+ }
414
+
415
+ .bioImageContainer {
416
+ padding-top: 0;
417
+ padding-bottom: 0.3rem;
418
+ margin-bottom: 0;
419
+ }
420
+
421
+ .imageWrapper {
422
+ max-width: 200px;
423
+ margin: 0 auto 0.5rem;
424
+ }
425
+
426
+ .aboutParagraph {
427
+ font-size: 0.95rem;
428
+ line-height: 1.6;
429
+ padding-left: 0.6rem;
430
+ }
431
+
432
+ .aboutParagraph::before {
433
+ width: 2px;
434
+ }
435
+
436
+ .skillsTitle {
437
+ font-size: 1.1rem;
438
+ margin-bottom: 0.8rem;
439
+ text-align: center;
440
+ }
441
+
442
+ .skillsGrid {
443
+ gap: 0.6rem;
444
+ justify-content: center;
445
+ padding-top: 0.3rem;
446
+ }
447
+
448
+ .skillBadge {
449
+ font-size: 0.8rem;
450
+ padding: 0.35rem 0.8rem;
451
+ border-radius: 16px;
452
+ }
453
+
454
+ .bioTextContainer {
455
+ gap: 0.6rem;
456
+ }
457
+
458
+ .skillsContainer {
459
+ padding: 0.3rem 0 0.5rem;
460
+ }
461
+ }
462
+
463
+ @media (max-width: 350px) {
464
+ .aboutHeading {
465
+ font-size: 1.6rem;
466
+ }
467
+
468
+ .imageWrapper {
469
+ max-width: 180px;
470
+ }
471
+
472
+ .skillBadge {
473
+ font-size: 0.75rem;
474
+ padding: 0.3rem 0.7rem;
475
+ }
476
+
477
+ .aboutParagraph {
478
+ font-size: 0.9rem;
479
+ line-height: 1.5;
480
+ }
481
+ }
482
+
483
+ @media (prefers-reduced-motion: reduce) {
484
+ .aboutHeading, .aboutContent, .aboutImage,
485
+ .resumeButton, .skillBadge, .imageWrapper, .resumeButton::before,
486
+ .aboutParagraph {
487
+ animation: none !important;
488
+ transition: none !important;
489
+ opacity: 1;
490
+ transform: none !important;
491
+ }
492
+ }
@@ -0,0 +1,95 @@
1
+ import { FaQuestionCircle } from "react-icons/fa";
2
+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
3
+ import { iconMap as defaultIconMap } from "portosaurus/config/iconMappings";
4
+ import styles from "./styles.module.css";
5
+
6
+ const sortEmail = (links) => {
7
+ return [...links].sort((a, b) => {
8
+ const isEmailA =
9
+ a.url?.startsWith("mailto:") ||
10
+ a.icon?.toLowerCase().includes("email") ||
11
+ a.name?.toLowerCase().includes("email");
12
+
13
+ const isEmailB =
14
+ b.url?.startsWith("mailto:") ||
15
+ b.icon?.toLowerCase().includes("email") ||
16
+ b.name?.toLowerCase().includes("email");
17
+
18
+ if (isEmailA && !isEmailB) return -1;
19
+ if (!isEmailA && isEmailB) return 1;
20
+
21
+ return 0;
22
+ });
23
+ };
24
+
25
+ export default function ContactSection({ id, className, title, subtitle }) {
26
+ const { siteConfig } = useDocusaurusContext();
27
+ const { customFields } = siteConfig;
28
+
29
+ let socialLinks = customFields.socialLinks.links || [];
30
+
31
+ socialLinks = sortEmail(socialLinks);
32
+
33
+ return (
34
+ <div
35
+ id={id}
36
+ className={`${styles.contactSection} ${className || ""}`}
37
+ role="region"
38
+ aria-label="Contact section"
39
+ >
40
+ <div className={styles.contactContainer}>
41
+ <div className={styles.contactHeader}>
42
+ <h2 className={styles.contactTitle}>{title || "Get In Touch"}</h2>
43
+ <p className={styles.contactSubtitle}>
44
+ {subtitle ||
45
+ "Feel free to reach out for collaborations, questions, or just to say hello!"}
46
+ </p>
47
+ </div>
48
+
49
+ {/* SocialCard */}
50
+ <div className={styles.gridWrapper}>
51
+ <div
52
+ className={styles.socialGrid}
53
+ role="list"
54
+ aria-label="Social media and contact links"
55
+ >
56
+ {socialLinks.map((social, index) => {
57
+ const iconData = defaultIconMap[social.icon] || {};
58
+
59
+ const name = social.name || "?";
60
+ const Icon = iconData.icon || FaQuestionCircle;
61
+ const iconColor = social.color || iconData.color || "#3578e5";
62
+ const description =
63
+ name === "?" ? "" : social.desc || `Connect with me on ${name}`;
64
+ const url = social.url;
65
+
66
+ return (
67
+ <a
68
+ key={name}
69
+ href={url}
70
+ target="_blank"
71
+ rel="noopener noreferrer"
72
+ className={styles.socialCard}
73
+ style={{
74
+ "--card-index": index,
75
+ "--icon-hover-color": iconColor,
76
+ }}
77
+ aria-label={`Connect with me on ${name}: ${description}`}
78
+ role="listitem"
79
+ >
80
+ {Icon && (
81
+ <div className={styles.socialIcon}>
82
+ <Icon aria-hidden="true" />
83
+ </div>
84
+ )}
85
+ <h3 className={styles.socialTitle}>{name}</h3>
86
+ <p className={styles.socialDesc}>{description}</p>
87
+ </a>
88
+ );
89
+ })}
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ );
95
+ }