portosaurus 1.18.14 → 1.18.16
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/README.md +1 -0
- package/bin/portosaurus.js +2 -2
- package/package.json +2 -2
- package/src/components/ProjectsSection/index.js +282 -226
- package/src/pages/Notes.js +1 -1
package/README.md
CHANGED
|
@@ -130,4 +130,5 @@ module.exports = {
|
|
|
130
130
|
- [Hugo Profile](https://hugo-profile.netlify.app/) - Design inspiration.
|
|
131
131
|
- [Catppuccin](https://github.com/catppuccin/catppuccin) - Color scheme that inspired the site's palette.
|
|
132
132
|
- [Deepseek R1](https://www.deepseek.com/) hosted using [Ollama](https://ollama.com/library/deepseek-r1) - prism.js theme & project card component.
|
|
133
|
+
- [Inkscape](https://inkscape.org/) - Icon drawing.
|
|
133
134
|
- Countless Internet forum posts - Filling me with information.
|
package/bin/portosaurus.js
CHANGED
|
@@ -305,7 +305,7 @@ program
|
|
|
305
305
|
.description("Show the generated Docusaurus config")
|
|
306
306
|
.option(
|
|
307
307
|
"-f, --file [filePath]",
|
|
308
|
-
"Output to a file (default:
|
|
308
|
+
"Output to a file (default: docusaurus.config.js)",
|
|
309
309
|
)
|
|
310
310
|
.option("-c, --config <path>", "Path to config file")
|
|
311
311
|
.action(async (options) => {
|
|
@@ -335,7 +335,7 @@ program
|
|
|
335
335
|
|
|
336
336
|
try {
|
|
337
337
|
if (options.file) {
|
|
338
|
-
let filename = "
|
|
338
|
+
let filename = "docusaurus.config.js";
|
|
339
339
|
if (typeof options.file === "string") {
|
|
340
340
|
filename = options.file;
|
|
341
341
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "portosaurus",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.16",
|
|
4
4
|
"author": "soymadip",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"description": "Complete portfolio cum personal website solution for your digital personality.",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"#config/*": "./src/config/*",
|
|
26
26
|
"#utils/*": "./src/utils/*.js",
|
|
27
27
|
"#css/*": "./src/css/*.css",
|
|
28
|
-
"#pages/*": "./src/
|
|
28
|
+
"#pages/*": "./src/pages/*.js",
|
|
29
29
|
"#components/*": "./src/components/*",
|
|
30
30
|
"#internal/*": "./src/internal/src/*"
|
|
31
31
|
},
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { useRef, useState, useEffect, useCallback, useMemo } from
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
|
2
|
+
import SliderImport from "react-slick";
|
|
3
|
+
const Slider = SliderImport.default || SliderImport;
|
|
4
|
+
import {
|
|
5
|
+
FaGithub,
|
|
6
|
+
FaGlobe,
|
|
7
|
+
FaPlay,
|
|
8
|
+
FaChevronLeft,
|
|
9
|
+
FaChevronRight,
|
|
10
|
+
FaStar,
|
|
11
|
+
} from "react-icons/fa";
|
|
4
12
|
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
5
|
-
import styles from
|
|
13
|
+
import styles from "./styles.module.css";
|
|
6
14
|
|
|
7
15
|
// Import slick carousel css
|
|
8
16
|
import "slick-carousel/slick/slick.css";
|
|
9
17
|
import "slick-carousel/slick/slick-theme.css";
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
// PART OF THIS COMPONENT IS AI GENERATED
|
|
12
20
|
|
|
13
21
|
export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
14
22
|
const { siteConfig } = useDocusaurusContext();
|
|
@@ -23,102 +31,119 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
23
31
|
const activeDotRef = useRef(null);
|
|
24
32
|
const dotsContainerRef = useRef(null);
|
|
25
33
|
|
|
26
|
-
// Default Settings
|
|
34
|
+
// Default Settings
|
|
27
35
|
const projectDefaults = {
|
|
28
36
|
title: "Future Project",
|
|
29
|
-
desc:
|
|
37
|
+
desc: "Coming soon...",
|
|
30
38
|
image: "img/project-blank.png",
|
|
31
39
|
state: "active",
|
|
32
|
-
tags:
|
|
40
|
+
tags: ["planned"],
|
|
33
41
|
};
|
|
34
42
|
|
|
43
|
+
const createPlaceholders = useCallback(
|
|
44
|
+
(count, existingProjects) => {
|
|
45
|
+
if (existingProjects.length === 0) return [];
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}, [projectDefaults]);
|
|
47
|
+
return [
|
|
48
|
+
...existingProjects,
|
|
49
|
+
...Array.from({ length: count }, (_, i) => ({
|
|
50
|
+
...projectDefaults,
|
|
51
|
+
|
|
52
|
+
// Dummy card config
|
|
53
|
+
state: "n/a",
|
|
54
|
+
title: `Project ${existingProjects.length + i + 1}`,
|
|
55
|
+
description: projectDefaults.desc,
|
|
56
|
+
image: projectDefaults.image,
|
|
57
|
+
id: `placeholder-${i}`,
|
|
58
|
+
tags: null,
|
|
59
|
+
})),
|
|
60
|
+
];
|
|
61
|
+
},
|
|
62
|
+
[projectDefaults],
|
|
63
|
+
);
|
|
54
64
|
|
|
55
65
|
// Get current slidesToShow based on screen width
|
|
56
66
|
const getVisibleSlidesPerView = useCallback(() => {
|
|
57
|
-
if (typeof window ===
|
|
58
|
-
|
|
67
|
+
if (typeof window === "undefined") return 3;
|
|
68
|
+
|
|
59
69
|
const width = window.innerWidth;
|
|
60
70
|
if (width <= 600) return 1;
|
|
61
71
|
if (width <= 1024) return 2;
|
|
62
72
|
return 3;
|
|
63
73
|
}, []);
|
|
64
|
-
|
|
65
|
-
const prepareProjects = useCallback(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
|
|
75
|
+
const prepareProjects = useCallback(
|
|
76
|
+
(projectList, slides) => {
|
|
77
|
+
if (!projectList?.length) return { projects: [], totalPages: 0 };
|
|
78
|
+
|
|
79
|
+
// Sort featured first
|
|
80
|
+
const sortedProjects = [...projectList]
|
|
81
|
+
.sort((a, b) => (a.featured ? -1 : 0) - (b.featured ? -1 : 0))
|
|
82
|
+
.map((project) => {
|
|
83
|
+
// Apply defaults if value not null
|
|
84
|
+
const processedProject = {
|
|
85
|
+
...project,
|
|
86
|
+
description:
|
|
87
|
+
project.desc === undefined ? projectDefaults.desc : project.desc,
|
|
88
|
+
image:
|
|
89
|
+
project.image === undefined
|
|
90
|
+
? projectDefaults.image
|
|
91
|
+
: project.image,
|
|
92
|
+
tags:
|
|
93
|
+
project.tags === undefined
|
|
94
|
+
? [...projectDefaults.tags]
|
|
95
|
+
: project.tags,
|
|
96
|
+
state:
|
|
97
|
+
project.state === undefined
|
|
98
|
+
? projectDefaults.state
|
|
99
|
+
: project.state,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Add ID
|
|
103
|
+
if (!processedProject.id) {
|
|
104
|
+
processedProject.id = processedProject.title
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.replace(/[^\w\s-]/g, "")
|
|
107
|
+
.replace(/\s+/g, "-")
|
|
108
|
+
.replace(/-+/g, "-");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return processedProject;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Calculate pagination and placeholder needs
|
|
115
|
+
const totalPages = Math.ceil(sortedProjects.length / slides);
|
|
116
|
+
const slotsPerPage = slides;
|
|
117
|
+
const totalSlots = totalPages * slotsPerPage;
|
|
118
|
+
const placeholderCount = totalSlots - sortedProjects.length;
|
|
119
|
+
|
|
120
|
+
// Return prepared data
|
|
121
|
+
return {
|
|
122
|
+
projects:
|
|
123
|
+
placeholderCount > 0
|
|
124
|
+
? createPlaceholders(placeholderCount, sortedProjects)
|
|
125
|
+
: sortedProjects,
|
|
126
|
+
totalPages,
|
|
80
127
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
processedProject.id = processedProject.title
|
|
85
|
-
.toLowerCase()
|
|
86
|
-
.replace(/[^\w\s-]/g, '')
|
|
87
|
-
.replace(/\s+/g, '-')
|
|
88
|
-
.replace(/-+/g, '-');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return processedProject;
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Calculate pagination and placeholder needs
|
|
95
|
-
const totalPages = Math.ceil(sortedProjects.length / slides);
|
|
96
|
-
const slotsPerPage = slides;
|
|
97
|
-
const totalSlots = totalPages * slotsPerPage;
|
|
98
|
-
const placeholderCount = totalSlots - sortedProjects.length;
|
|
99
|
-
|
|
100
|
-
// Return prepared data
|
|
101
|
-
return {
|
|
102
|
-
projects: placeholderCount > 0
|
|
103
|
-
? createPlaceholders(placeholderCount, sortedProjects)
|
|
104
|
-
: sortedProjects,
|
|
105
|
-
totalPages
|
|
106
|
-
};
|
|
107
|
-
}, [createPlaceholders, projectDefaults]);
|
|
128
|
+
},
|
|
129
|
+
[createPlaceholders, projectDefaults],
|
|
130
|
+
);
|
|
108
131
|
|
|
109
132
|
// Load and set up projects on initial load and on resize
|
|
110
133
|
useEffect(() => {
|
|
111
134
|
const projectShelf = siteConfig.customFields?.projects;
|
|
112
|
-
const configuredProjects = projectShelf?.enable
|
|
113
|
-
|
|
135
|
+
const configuredProjects = projectShelf?.enable
|
|
136
|
+
? projectShelf?.projects || []
|
|
137
|
+
: [];
|
|
138
|
+
|
|
114
139
|
const handleLayout = () => {
|
|
115
140
|
const newSlidesToShow = getVisibleSlidesPerView();
|
|
116
|
-
|
|
141
|
+
|
|
117
142
|
if (newSlidesToShow !== slidesToShow || !projects.length) {
|
|
118
143
|
setSlidesToShow(newSlidesToShow);
|
|
119
|
-
const { projects: newProjects, totalPages: newTotalPages } =
|
|
144
|
+
const { projects: newProjects, totalPages: newTotalPages } =
|
|
120
145
|
prepareProjects(configuredProjects, newSlidesToShow);
|
|
121
|
-
|
|
146
|
+
|
|
122
147
|
setProjects(newProjects);
|
|
123
148
|
setTotalPages(newTotalPages);
|
|
124
149
|
setAtEnd(newProjects.length <= newSlidesToShow);
|
|
@@ -127,124 +152,135 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
127
152
|
|
|
128
153
|
// Initial setup
|
|
129
154
|
handleLayout();
|
|
130
|
-
|
|
155
|
+
|
|
131
156
|
// Resize handler
|
|
132
|
-
window.addEventListener(
|
|
133
|
-
return () => window.removeEventListener(
|
|
134
|
-
}, [
|
|
157
|
+
window.addEventListener("resize", handleLayout);
|
|
158
|
+
return () => window.removeEventListener("resize", handleLayout);
|
|
159
|
+
}, [
|
|
160
|
+
siteConfig,
|
|
161
|
+
getVisibleSlidesPerView,
|
|
162
|
+
prepareProjects,
|
|
163
|
+
slidesToShow,
|
|
164
|
+
projects.length,
|
|
165
|
+
]);
|
|
135
166
|
|
|
136
167
|
// Method to go to a specific slide
|
|
137
|
-
const goToSlide = useCallback(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
168
|
+
const goToSlide = useCallback(
|
|
169
|
+
(index) => {
|
|
170
|
+
if (sliderRef.current) {
|
|
171
|
+
sliderRef.current.slickGoTo(index * slidesToShow);
|
|
172
|
+
setCurrentSlide(index);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
[slidesToShow],
|
|
176
|
+
);
|
|
145
177
|
|
|
146
178
|
useEffect(() => {
|
|
147
179
|
const scrollTimeout = setTimeout(() => {
|
|
148
|
-
|
|
149
180
|
if (activeDotRef.current && dotsContainerRef.current) {
|
|
150
181
|
const container = dotsContainerRef.current;
|
|
151
182
|
const activeDot = activeDotRef.current;
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
183
|
|
|
184
|
+
try {
|
|
155
185
|
// For first few
|
|
156
186
|
const adaptiveThreshold = Math.max(1, Math.floor(totalPages * 0.1));
|
|
157
187
|
if (currentSlide <= adaptiveThreshold) {
|
|
158
188
|
container.scrollTo({
|
|
159
189
|
left: 0,
|
|
160
|
-
behavior:
|
|
190
|
+
behavior: "smooth",
|
|
161
191
|
});
|
|
162
192
|
return;
|
|
163
193
|
}
|
|
164
|
-
|
|
194
|
+
|
|
165
195
|
// For last few
|
|
166
196
|
if (currentSlide >= totalPages - 2) {
|
|
167
197
|
const scrollMax = container.scrollWidth - container.clientWidth;
|
|
168
198
|
container.scrollTo({
|
|
169
199
|
left: scrollMax,
|
|
170
|
-
behavior:
|
|
200
|
+
behavior: "smooth",
|
|
171
201
|
});
|
|
172
202
|
return;
|
|
173
203
|
}
|
|
174
|
-
|
|
204
|
+
|
|
175
205
|
// Center the active dot at mobile
|
|
176
206
|
const dotRect = activeDot.getBoundingClientRect();
|
|
177
207
|
const containerRect = container.getBoundingClientRect();
|
|
178
|
-
|
|
208
|
+
|
|
179
209
|
// Check if dot is within visible area with margins
|
|
180
210
|
const isOutsideLeft = dotRect.left < containerRect.left + 20;
|
|
181
211
|
const isOutsideRight = dotRect.right > containerRect.right - 20;
|
|
182
|
-
|
|
212
|
+
|
|
183
213
|
if (isOutsideLeft || isOutsideRight) {
|
|
184
214
|
const dotPosition = activeDot.offsetLeft;
|
|
185
215
|
const dotWidth = activeDot.clientWidth;
|
|
186
216
|
const containerWidth = container.clientWidth;
|
|
187
|
-
const scrollPosition =
|
|
188
|
-
|
|
217
|
+
const scrollPosition =
|
|
218
|
+
dotPosition - containerWidth / 2 + dotWidth / 2;
|
|
219
|
+
|
|
189
220
|
// Disable smooth scroll if not supported
|
|
190
|
-
if (
|
|
221
|
+
if ("scrollBehavior" in document.documentElement.style) {
|
|
191
222
|
container.scrollTo({
|
|
192
223
|
left: Math.max(0, scrollPosition),
|
|
193
|
-
behavior:
|
|
224
|
+
behavior: "smooth",
|
|
194
225
|
});
|
|
195
226
|
} else {
|
|
196
227
|
container.scrollLeft = Math.max(0, scrollPosition);
|
|
197
228
|
}
|
|
198
229
|
}
|
|
199
230
|
} catch (error) {
|
|
200
|
-
console.warn(
|
|
231
|
+
console.warn("Dot scrolling error:", error);
|
|
201
232
|
}
|
|
202
233
|
}
|
|
203
234
|
}, 50);
|
|
204
|
-
|
|
235
|
+
|
|
205
236
|
return () => clearTimeout(scrollTimeout);
|
|
206
237
|
}, [currentSlide, totalPages]);
|
|
207
238
|
|
|
208
239
|
// Carousel settings
|
|
209
|
-
const settings = useMemo(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
const settings = useMemo(
|
|
241
|
+
() => ({
|
|
242
|
+
dots: false,
|
|
243
|
+
infinite: false,
|
|
244
|
+
speed: 600,
|
|
245
|
+
slidesToShow: Math.min(projects.length, slidesToShow),
|
|
246
|
+
slidesToScroll: slidesToShow,
|
|
247
|
+
autoplay: false,
|
|
248
|
+
adaptiveHeight: false,
|
|
249
|
+
centerPadding: "20px",
|
|
250
|
+
centerMode: false,
|
|
251
|
+
variableWidth: false,
|
|
252
|
+
swipeToSlide: false,
|
|
253
|
+
focusOnSelect: false,
|
|
254
|
+
responsive: [
|
|
255
|
+
{
|
|
256
|
+
breakpoint: 1024,
|
|
257
|
+
settings: {
|
|
258
|
+
slidesToShow: Math.min(projects.length, 2),
|
|
259
|
+
slidesToScroll: 2,
|
|
260
|
+
dots: false,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
breakpoint: 600,
|
|
265
|
+
settings: {
|
|
266
|
+
slidesToShow: 1,
|
|
267
|
+
slidesToScroll: 1,
|
|
268
|
+
dots: false,
|
|
269
|
+
arrows: false,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
className: styles.projectsCarousel,
|
|
274
|
+
beforeChange: (current, next) => {
|
|
275
|
+
setAtBeginning(next === 0);
|
|
276
|
+
setCurrentSlide(Math.floor(next / slidesToShow));
|
|
277
|
+
setAtEnd(
|
|
278
|
+
next + Math.min(projects.length, slidesToShow) >= projects.length,
|
|
279
|
+
);
|
|
230
280
|
},
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
slidesToShow: 1,
|
|
235
|
-
slidesToScroll: 1,
|
|
236
|
-
dots: false,
|
|
237
|
-
arrows: false,
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
],
|
|
241
|
-
className: styles.projectsCarousel,
|
|
242
|
-
beforeChange: (current, next) => {
|
|
243
|
-
setAtBeginning(next === 0);
|
|
244
|
-
setCurrentSlide(Math.floor(next / slidesToShow));
|
|
245
|
-
setAtEnd(next + Math.min(projects.length, slidesToShow) >= projects.length);
|
|
246
|
-
},
|
|
247
|
-
}), [projects, slidesToShow]);
|
|
281
|
+
}),
|
|
282
|
+
[projects, slidesToShow],
|
|
283
|
+
);
|
|
248
284
|
|
|
249
285
|
// Navigation handlers
|
|
250
286
|
const goToNext = useCallback(() => {
|
|
@@ -264,10 +300,10 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
264
300
|
if (!url) return null;
|
|
265
301
|
|
|
266
302
|
return (
|
|
267
|
-
<a
|
|
268
|
-
href={url}
|
|
269
|
-
target="_blank"
|
|
270
|
-
rel="noopener noreferrer"
|
|
303
|
+
<a
|
|
304
|
+
href={url}
|
|
305
|
+
target="_blank"
|
|
306
|
+
rel="noopener noreferrer"
|
|
271
307
|
className={styles.projectLink}
|
|
272
308
|
aria-label={ariaLabel}
|
|
273
309
|
>
|
|
@@ -279,48 +315,48 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
279
315
|
|
|
280
316
|
// Get state label and class
|
|
281
317
|
const getProjectStateInfo = useCallback((state) => {
|
|
282
|
-
switch(state?.toLowerCase()) {
|
|
318
|
+
switch (state?.toLowerCase()) {
|
|
283
319
|
// For projects currently in active development
|
|
284
|
-
case
|
|
285
|
-
return {
|
|
286
|
-
label:
|
|
320
|
+
case "active":
|
|
321
|
+
return {
|
|
322
|
+
label: "Active",
|
|
287
323
|
className: styles.stateActive,
|
|
288
324
|
};
|
|
289
325
|
// For finished projects
|
|
290
|
-
case
|
|
291
|
-
return {
|
|
292
|
-
label:
|
|
326
|
+
case "completed":
|
|
327
|
+
return {
|
|
328
|
+
label: "Completed",
|
|
293
329
|
className: styles.stateCompleted,
|
|
294
330
|
};
|
|
295
331
|
// For projects receiving updates/maintenance
|
|
296
|
-
case
|
|
297
|
-
return {
|
|
298
|
-
label:
|
|
332
|
+
case "maintenance":
|
|
333
|
+
return {
|
|
334
|
+
label: "Maintenance",
|
|
299
335
|
className: styles.stateMaintenance,
|
|
300
336
|
};
|
|
301
337
|
// For temporarily paused development
|
|
302
|
-
case
|
|
303
|
-
return {
|
|
304
|
-
label:
|
|
338
|
+
case "paused":
|
|
339
|
+
return {
|
|
340
|
+
label: "Paused",
|
|
305
341
|
className: styles.statePaused,
|
|
306
342
|
};
|
|
307
343
|
// For projects no longer maintained
|
|
308
|
-
case
|
|
309
|
-
return {
|
|
310
|
-
label:
|
|
344
|
+
case "archived":
|
|
345
|
+
return {
|
|
346
|
+
label: "Archived",
|
|
311
347
|
className: styles.stateArchived,
|
|
312
348
|
};
|
|
313
349
|
// For future projects in planning stage
|
|
314
|
-
case
|
|
315
|
-
return {
|
|
316
|
-
label:
|
|
350
|
+
case "planned":
|
|
351
|
+
return {
|
|
352
|
+
label: "Planned",
|
|
317
353
|
className: styles.statePlanned,
|
|
318
354
|
};
|
|
319
355
|
// Default state when not specified
|
|
320
|
-
case
|
|
356
|
+
case "n/a":
|
|
321
357
|
default:
|
|
322
|
-
return {
|
|
323
|
-
label:
|
|
358
|
+
return {
|
|
359
|
+
label: "N/A",
|
|
324
360
|
className: styles.stateNA,
|
|
325
361
|
};
|
|
326
362
|
}
|
|
@@ -332,9 +368,9 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
332
368
|
|
|
333
369
|
// Determine if we should use scrollable or centered layout
|
|
334
370
|
const fewDots = totalPages <= 5;
|
|
335
|
-
|
|
371
|
+
|
|
336
372
|
return (
|
|
337
|
-
<div
|
|
373
|
+
<div
|
|
338
374
|
className={`${styles.navDotsContainer} ${fewDots ? styles.centerDots : styles.scrollDots}`}
|
|
339
375
|
role="tablist"
|
|
340
376
|
aria-label="Project carousel navigation"
|
|
@@ -342,7 +378,7 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
342
378
|
{Array.from({ length: totalPages }, (_, i) => (
|
|
343
379
|
<button
|
|
344
380
|
key={i}
|
|
345
|
-
className={`${styles.navDot} ${currentSlide === i ? styles.activeDot :
|
|
381
|
+
className={`${styles.navDot} ${currentSlide === i ? styles.activeDot : ""}`}
|
|
346
382
|
onClick={() => goToSlide(i)}
|
|
347
383
|
aria-label={`Go to slide ${i + 1} of ${totalPages}`}
|
|
348
384
|
aria-selected={currentSlide === i}
|
|
@@ -356,17 +392,21 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
356
392
|
}, [currentSlide, totalPages, goToSlide]);
|
|
357
393
|
|
|
358
394
|
return (
|
|
359
|
-
<div
|
|
395
|
+
<div
|
|
396
|
+
id={id}
|
|
397
|
+
className={`${styles.projectsSection} ${className || ""}`}
|
|
398
|
+
role="region"
|
|
399
|
+
aria-label="Projects section"
|
|
400
|
+
>
|
|
360
401
|
<div className={styles.projectsContainer}>
|
|
361
402
|
<div className={styles.projectsHeader}>
|
|
362
|
-
<h2 className={styles.projectsTitle}>
|
|
363
|
-
{title || "My Projects"}
|
|
364
|
-
</h2>
|
|
403
|
+
<h2 className={styles.projectsTitle}>{title || "My Projects"}</h2>
|
|
365
404
|
<p className={styles.projectsSubtitle}>
|
|
366
|
-
{subtitle ||
|
|
405
|
+
{subtitle ||
|
|
406
|
+
"A collection of all my works, with featured projects highlighted"}
|
|
367
407
|
</p>
|
|
368
408
|
</div>
|
|
369
|
-
|
|
409
|
+
|
|
370
410
|
{projects.length === 0 ? (
|
|
371
411
|
<div className={styles.noProjects}>
|
|
372
412
|
<p>No projects to display.</p>
|
|
@@ -374,8 +414,8 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
374
414
|
) : (
|
|
375
415
|
<div className={styles.carouselContainer}>
|
|
376
416
|
{/* Desktop navigation buttons (sides) */}
|
|
377
|
-
<button
|
|
378
|
-
className={`${styles.carouselControl} ${styles.prevButton} ${styles.desktopOnly} ${atBeginning ? styles.disabledButton :
|
|
417
|
+
<button
|
|
418
|
+
className={`${styles.carouselControl} ${styles.prevButton} ${styles.desktopOnly} ${atBeginning ? styles.disabledButton : ""}`}
|
|
379
419
|
onClick={goToPrev}
|
|
380
420
|
aria-label="View previous projects"
|
|
381
421
|
aria-disabled={atBeginning}
|
|
@@ -384,25 +424,33 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
384
424
|
>
|
|
385
425
|
<FaChevronLeft aria-hidden="true" />
|
|
386
426
|
</button>
|
|
387
|
-
|
|
388
|
-
<div
|
|
427
|
+
|
|
428
|
+
<div
|
|
429
|
+
className={styles.carouselWrapper}
|
|
430
|
+
aria-roledescription="carousel"
|
|
431
|
+
aria-label="Projects carousel"
|
|
432
|
+
>
|
|
389
433
|
<Slider ref={sliderRef} {...settings}>
|
|
390
434
|
{projects.map((project, index) => (
|
|
391
|
-
<div
|
|
392
|
-
key={project.id || project.title + index}
|
|
393
|
-
className={styles.carouselSlide}
|
|
435
|
+
<div
|
|
436
|
+
key={project.id || project.title + index}
|
|
437
|
+
className={styles.carouselSlide}
|
|
394
438
|
data-project-id={project.id}
|
|
395
|
-
aria-roledescription="slide"
|
|
439
|
+
aria-roledescription="slide"
|
|
396
440
|
aria-label={`Project ${index + 1} of ${projects.length}: ${project.title}`}
|
|
397
441
|
>
|
|
398
|
-
<div
|
|
442
|
+
<div
|
|
443
|
+
className={`${styles.carouselCard} ${project.featured ? styles.featuredCard : ""}`}
|
|
444
|
+
>
|
|
399
445
|
{/* Project state badge */}
|
|
400
446
|
{project.state && (
|
|
401
|
-
<div
|
|
447
|
+
<div
|
|
402
448
|
className={styles.projectStateBadge}
|
|
403
449
|
title={`Project status: ${getProjectStateInfo(project.state).label}`}
|
|
404
450
|
>
|
|
405
|
-
<span
|
|
451
|
+
<span
|
|
452
|
+
className={`${styles.projectStateLabel} ${getProjectStateInfo(project.state).className}`}
|
|
453
|
+
>
|
|
406
454
|
{getProjectStateInfo(project.state).label}
|
|
407
455
|
</span>
|
|
408
456
|
</div>
|
|
@@ -410,74 +458,82 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
410
458
|
|
|
411
459
|
<div className={styles.projectImageContainer}>
|
|
412
460
|
{project.image && (
|
|
413
|
-
<img
|
|
414
|
-
src={project.image}
|
|
415
|
-
alt={project.title}
|
|
416
|
-
className={styles.projectImage}
|
|
461
|
+
<img
|
|
462
|
+
src={project.image}
|
|
463
|
+
alt={project.title}
|
|
464
|
+
className={styles.projectImage}
|
|
417
465
|
loading="lazy"
|
|
418
466
|
/>
|
|
419
467
|
)}
|
|
420
|
-
|
|
468
|
+
|
|
421
469
|
{/* Featured badge */}
|
|
422
470
|
{project.featured && (
|
|
423
|
-
<div
|
|
471
|
+
<div
|
|
472
|
+
className={styles.featuredBadge}
|
|
473
|
+
title="Featured Project"
|
|
474
|
+
aria-label="Featured project"
|
|
475
|
+
>
|
|
424
476
|
<FaStar aria-hidden="true" />
|
|
425
477
|
</div>
|
|
426
478
|
)}
|
|
427
479
|
</div>
|
|
428
|
-
|
|
480
|
+
|
|
429
481
|
<div className={styles.projectContent}>
|
|
430
482
|
<h3 className={styles.projectTitle}>{project.title}</h3>
|
|
431
|
-
|
|
483
|
+
|
|
432
484
|
{project.tags?.length > 0 && (
|
|
433
485
|
<div className={styles.projectTags}>
|
|
434
|
-
{project.tags.map(tag => (
|
|
435
|
-
<span key={tag} className={styles.projectTag}>
|
|
486
|
+
{project.tags.map((tag) => (
|
|
487
|
+
<span key={tag} className={styles.projectTag}>
|
|
488
|
+
{tag}
|
|
489
|
+
</span>
|
|
436
490
|
))}
|
|
437
491
|
</div>
|
|
438
492
|
)}
|
|
439
|
-
|
|
440
|
-
<p className={styles.projectDescription}>
|
|
493
|
+
|
|
494
|
+
<p className={styles.projectDescription}>
|
|
495
|
+
{project.description}
|
|
496
|
+
</p>
|
|
441
497
|
</div>
|
|
442
|
-
|
|
498
|
+
|
|
443
499
|
<div className={styles.projectLinks}>
|
|
444
500
|
{renderProjectLink(
|
|
445
|
-
project.website,
|
|
446
|
-
FaGlobe,
|
|
447
|
-
"Website",
|
|
448
|
-
`Visit ${project.title} website
|
|
501
|
+
project.website,
|
|
502
|
+
FaGlobe,
|
|
503
|
+
"Website",
|
|
504
|
+
`Visit ${project.title} website`,
|
|
449
505
|
)}
|
|
450
|
-
|
|
506
|
+
|
|
451
507
|
{renderProjectLink(
|
|
452
|
-
project.github,
|
|
453
|
-
FaGithub,
|
|
454
|
-
"Source",
|
|
455
|
-
`Repository with source code
|
|
508
|
+
project.github,
|
|
509
|
+
FaGithub,
|
|
510
|
+
"Source",
|
|
511
|
+
`Repository with source code`,
|
|
456
512
|
)}
|
|
457
|
-
|
|
513
|
+
|
|
458
514
|
{renderProjectLink(
|
|
459
|
-
project.Demo,
|
|
460
|
-
FaPlay,
|
|
461
|
-
"Demo",
|
|
462
|
-
`Live demo for ${project.title}
|
|
515
|
+
project.Demo,
|
|
516
|
+
FaPlay,
|
|
517
|
+
"Demo",
|
|
518
|
+
`Live demo for ${project.title}`,
|
|
463
519
|
)}
|
|
464
520
|
</div>
|
|
465
521
|
</div>
|
|
466
522
|
</div>
|
|
467
523
|
))}
|
|
468
524
|
</Slider>
|
|
469
|
-
|
|
525
|
+
|
|
470
526
|
{/* Desktop navigation dots */}
|
|
471
527
|
<div className={styles.desktopDotsContainer}>
|
|
472
528
|
{renderNavigationDots()}
|
|
473
529
|
</div>
|
|
474
|
-
|
|
530
|
+
|
|
475
531
|
{/* Mobile navigation controls (bottom) */}
|
|
476
532
|
<div className={styles.mobileNavigationControls}>
|
|
477
533
|
{totalPages > 1 && (
|
|
478
534
|
<>
|
|
479
|
-
<button
|
|
480
|
-
className={`${styles.carouselControl} ${styles.prevButton} ${atBeginning ? styles.disabledButton :
|
|
535
|
+
<button
|
|
536
|
+
className={`${styles.carouselControl} ${styles.prevButton} ${atBeginning ? styles.disabledButton : ""}`}
|
|
481
537
|
onClick={goToPrev}
|
|
482
538
|
aria-label="View previous projects"
|
|
483
539
|
aria-disabled={atBeginning}
|
|
@@ -486,17 +542,17 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
486
542
|
>
|
|
487
543
|
<FaChevronLeft aria-hidden="true" />
|
|
488
544
|
</button>
|
|
489
|
-
|
|
545
|
+
|
|
490
546
|
{/* Mobile navigation dots */}
|
|
491
|
-
<div
|
|
547
|
+
<div
|
|
492
548
|
className={styles.dotsScrollContainer}
|
|
493
549
|
ref={dotsContainerRef}
|
|
494
550
|
>
|
|
495
551
|
{renderNavigationDots()}
|
|
496
552
|
</div>
|
|
497
|
-
|
|
498
|
-
<button
|
|
499
|
-
className={`${styles.carouselControl} ${styles.nextButton} ${atEnd ? styles.disabledButton :
|
|
553
|
+
|
|
554
|
+
<button
|
|
555
|
+
className={`${styles.carouselControl} ${styles.nextButton} ${atEnd ? styles.disabledButton : ""}`}
|
|
500
556
|
onClick={goToNext}
|
|
501
557
|
aria-label="View next projects"
|
|
502
558
|
aria-disabled={atEnd}
|
|
@@ -509,10 +565,10 @@ export default function ProjectsSection({ id, className, title, subtitle }) {
|
|
|
509
565
|
)}
|
|
510
566
|
</div>
|
|
511
567
|
</div>
|
|
512
|
-
|
|
568
|
+
|
|
513
569
|
{/* Desktop navigation button (right side) */}
|
|
514
|
-
<button
|
|
515
|
-
className={`${styles.carouselControl} ${styles.nextButton} ${styles.desktopOnly} ${atEnd ? styles.disabledButton :
|
|
570
|
+
<button
|
|
571
|
+
className={`${styles.carouselControl} ${styles.nextButton} ${styles.desktopOnly} ${atEnd ? styles.disabledButton : ""}`}
|
|
516
572
|
onClick={goToNext}
|
|
517
573
|
aria-label="View next projects"
|
|
518
574
|
aria-disabled={atEnd}
|
package/src/pages/Notes.js
CHANGED
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import Layout from "@theme/Layout";
|
|
3
3
|
import NoteCards from "#components/NoteIndex/index.js";
|
|
4
4
|
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
5
|
-
import ScrollToTop from "
|
|
5
|
+
import ScrollToTop from "../components/ScrollToTop/index.js";
|
|
6
6
|
import HashNavigation from "../utils/HashNavigation.js";
|
|
7
7
|
|
|
8
8
|
const style = {
|