@portosaur/theme 0.1.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 (76) hide show
  1. package/README.md +13 -0
  2. package/assets/img/icon-old.png +0 -0
  3. package/assets/img/icon.png +0 -0
  4. package/assets/img/project-blank.png +0 -0
  5. package/assets/img/social-card.jpeg +0 -0
  6. package/assets/img/svg/icon-blog.svg +2 -0
  7. package/assets/img/svg/icon-close.svg +3 -0
  8. package/assets/img/svg/icon-dock.svg +4 -0
  9. package/assets/img/svg/icon-link.svg +5 -0
  10. package/assets/img/svg/icon-note.svg +2 -0
  11. package/assets/img/svg/icon-popup.svg +1 -0
  12. package/assets/img/svg/icon-save.svg +5 -0
  13. package/assets/img/svg/icon.svg +240 -0
  14. package/assets/img/svg/project-blank.svg +140 -0
  15. package/assets/sample-resume.pdf +0 -0
  16. package/package.json +41 -0
  17. package/plugins/README.md +8 -0
  18. package/src/index.d.ts +11 -0
  19. package/src/index.mjs +14 -0
  20. package/src/plugins/theme.mjs +13 -0
  21. package/theme/DocCategoryGeneratedIndexPage/index.jsx +15 -0
  22. package/theme/MDXComponents.jsx +19 -0
  23. package/theme/README.md +9 -0
  24. package/theme/Root.jsx +11 -0
  25. package/theme/components/AboutSection/index.jsx +264 -0
  26. package/theme/components/AboutSection/styles.module.css +309 -0
  27. package/theme/components/ContactSection/index.jsx +188 -0
  28. package/theme/components/ContactSection/styles.module.css +343 -0
  29. package/theme/components/ExperienceSection/index.jsx +119 -0
  30. package/theme/components/ExperienceSection/styles.module.css +183 -0
  31. package/theme/components/HeroSection/index.jsx +198 -0
  32. package/theme/components/HeroSection/styles.module.css +484 -0
  33. package/theme/components/NavArrow/index.jsx +124 -0
  34. package/theme/components/NavArrow/styles.module.css +107 -0
  35. package/theme/components/NoteIndex/index.jsx +182 -0
  36. package/theme/components/NoteIndex/styles.module.css +167 -0
  37. package/theme/components/Preview/components/FeedbackStates.jsx +200 -0
  38. package/theme/components/Preview/components/FileTabs.jsx +41 -0
  39. package/theme/components/Preview/components/PreviewContent.jsx +104 -0
  40. package/theme/components/Preview/components/PreviewHeader.jsx +411 -0
  41. package/theme/components/Preview/components/Triggers/Pv.jsx +253 -0
  42. package/theme/components/Preview/components/Triggers/SrcPv.jsx +55 -0
  43. package/theme/components/Preview/components/Triggers/index.jsx +2 -0
  44. package/theme/components/Preview/components/ViewerWindow.jsx +489 -0
  45. package/theme/components/Preview/hooks/useAdaptiveSizing.jsx +90 -0
  46. package/theme/components/Preview/hooks/useDeepLinkHash.jsx +24 -0
  47. package/theme/components/Preview/hooks/useDockLayout.jsx +86 -0
  48. package/theme/components/Preview/hooks/useFileFetch.jsx +38 -0
  49. package/theme/components/Preview/hooks/useTouchZoom.jsx +98 -0
  50. package/theme/components/Preview/index.jsx +3 -0
  51. package/theme/components/Preview/renderers/CodeRenderer.jsx +124 -0
  52. package/theme/components/Preview/renderers/ImageRenderer.jsx +74 -0
  53. package/theme/components/Preview/renderers/PdfRenderer.jsx +93 -0
  54. package/theme/components/Preview/renderers/WebRenderer.jsx +59 -0
  55. package/theme/components/Preview/state/index.jsx +177 -0
  56. package/theme/components/Preview/styles.module.css +776 -0
  57. package/theme/components/Preview/utils/index.jsx +62 -0
  58. package/theme/components/ProjectsSection/index.jsx +790 -0
  59. package/theme/components/ProjectsSection/styles.module.css +900 -0
  60. package/theme/components/SocialLinks/index.jsx +115 -0
  61. package/theme/components/SocialLinks/styles.module.css +57 -0
  62. package/theme/components/Tooltip/index.jsx +104 -0
  63. package/theme/components/Tooltip/styles.module.css +168 -0
  64. package/theme/config/iconMappings.jsx +427 -0
  65. package/theme/config/prism.jsx +72 -0
  66. package/theme/config/sidebar.jsx +11 -0
  67. package/theme/css/bootstrap.css +5 -0
  68. package/theme/css/catppuccin.css +618 -0
  69. package/theme/css/custom.css +253 -0
  70. package/theme/css/tasks.css +874 -0
  71. package/theme/hooks/useScrollReveal.jsx +20 -0
  72. package/theme/pages/index.jsx +104 -0
  73. package/theme/pages/notes.jsx +131 -0
  74. package/theme/pages/tasks.jsx +989 -0
  75. package/theme/utils/HashNavigation.jsx +185 -0
  76. package/theme/utils/updateTitle.jsx +65 -0
@@ -0,0 +1,790 @@
1
+ import { useRef, useState, useEffect, useCallback, useMemo } from "react";
2
+ import Slider from "react-slick";
3
+ import {
4
+ FaCode,
5
+ FaGlobe,
6
+ FaPlay,
7
+ FaChevronLeft,
8
+ FaChevronRight,
9
+ FaStar,
10
+ } from "react-icons/fa";
11
+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
12
+ import useScrollReveal from "../../hooks/useScrollReveal";
13
+ import Tooltip from "../Tooltip/index.js";
14
+ import useBrokenLinks from "@docusaurus/useBrokenLinks";
15
+ import styles from "./styles.module.css";
16
+ import "slick-carousel/slick/slick.css";
17
+ import "slick-carousel/slick/slick-theme.css";
18
+ export default function ProjectsSection({ id, className }) {
19
+ const { siteConfig } = useDocusaurusContext();
20
+ const brokenLinks = useBrokenLinks();
21
+ if (id) {
22
+ brokenLinks.collectAnchor(id);
23
+ }
24
+ const projectShelf = siteConfig.customFields?.projects || {};
25
+ if (projectShelf.enable === false) return null;
26
+ const isAutoplayEnabled = projectShelf.autoplay ?? true;
27
+ const displayHeading = projectShelf.heading;
28
+ const displaySubheading = projectShelf.subheading;
29
+ const [projects, setProjects] = useState([]);
30
+ const sliderRef = useRef(null);
31
+ const [atBeginning, setAtBeginning] = useState(true);
32
+ const [atEnd, setAtEnd] = useState(false);
33
+ const [slidesToShow, setSlidesToShow] = useState(3);
34
+ const [currentSlide, setCurrentSlide] = useState(0);
35
+ const [totalPages, setTotalPages] = useState(1);
36
+ const activeDotRef = useRef(null);
37
+ const dotsContainerRef = useRef(null);
38
+ const [sectionRef, isVisible] = useScrollReveal();
39
+ const getVisibleSlidesPerView = useCallback(() => {
40
+ if (typeof window === "undefined") return 3;
41
+ const width = window.innerWidth;
42
+ if (width <= 600) return 1;
43
+ if (width <= 1024) return 2;
44
+ return 3;
45
+ }, []);
46
+ const prepareProjects = useCallback((projectList, slides) => {
47
+ if (!projectList?.length) return { projects: [], totalPages: 0 };
48
+ const processedProjects = projectList.map((project, index) => {
49
+ const processed = {
50
+ ...project,
51
+ desc: project.desc || "N/A",
52
+ icon: project.icon || "img/project-blank.png",
53
+ bg: project.bg || null,
54
+ tags: project.tags || [],
55
+ state: project.state || "completed",
56
+ };
57
+ if (!processed.id) {
58
+ processed.id =
59
+ (processed.title || "project")
60
+ .toLowerCase()
61
+ .replace(/[^\w\s-]/g, "")
62
+ .replace(/\s+/g, "-")
63
+ .replace(/-+/g, "-") + `-${index}`;
64
+ }
65
+ return processed;
66
+ });
67
+ const totalPages = Math.ceil(processedProjects.length / slides);
68
+ processedProjects.sort((a, b) => {
69
+ if (a.featured && !b.featured) return -1;
70
+ if (!a.featured && b.featured) return 1;
71
+ return 0;
72
+ });
73
+ return { projects: processedProjects, totalPages };
74
+ }, []);
75
+ useEffect(() => {
76
+ const projectShelf = siteConfig.customFields?.projects;
77
+ const configuredProjects = projectShelf?.enable
78
+ ? projectShelf?.projects || []
79
+ : [];
80
+ const handleLayout = () => {
81
+ const newSlidesToShow = getVisibleSlidesPerView();
82
+ if (newSlidesToShow !== slidesToShow || !projects.length) {
83
+ setSlidesToShow(newSlidesToShow);
84
+ const { projects: newProjects, totalPages: newTotalPages } =
85
+ prepareProjects(configuredProjects, newSlidesToShow);
86
+ setProjects(newProjects);
87
+ setTotalPages(newTotalPages);
88
+ setAtEnd(newProjects.length <= newSlidesToShow);
89
+ }
90
+ };
91
+ handleLayout();
92
+ window.addEventListener("resize", handleLayout);
93
+ return () => window.removeEventListener("resize", handleLayout);
94
+ }, [
95
+ siteConfig,
96
+ getVisibleSlidesPerView,
97
+ prepareProjects,
98
+ slidesToShow,
99
+ projects.length,
100
+ ]);
101
+ const goToSlide = useCallback(
102
+ (index) => {
103
+ if (sliderRef.current) {
104
+ sliderRef.current.slickGoTo(index * slidesToShow);
105
+ setCurrentSlide(index);
106
+ }
107
+ },
108
+ [slidesToShow],
109
+ );
110
+ useEffect(() => {
111
+ const scrollTimeout = setTimeout(() => {
112
+ if (activeDotRef.current && dotsContainerRef.current) {
113
+ const container = dotsContainerRef.current;
114
+ const activeDot = activeDotRef.current;
115
+ try {
116
+ const adaptiveThreshold = Math.max(1, Math.floor(totalPages * 0.1));
117
+ if (currentSlide <= adaptiveThreshold) {
118
+ container.scrollTo({ left: 0, behavior: "smooth" });
119
+ return;
120
+ }
121
+ if (currentSlide >= totalPages - 2) {
122
+ const scrollMax = container.scrollWidth - container.clientWidth;
123
+ container.scrollTo({ left: scrollMax, behavior: "smooth" });
124
+ return;
125
+ }
126
+ const dotRect = activeDot.getBoundingClientRect();
127
+ const containerRect = container.getBoundingClientRect();
128
+ const isOutsideLeft = dotRect.left < containerRect.left + 20;
129
+ const isOutsideRight = dotRect.right > containerRect.right - 20;
130
+ if (isOutsideLeft || isOutsideRight) {
131
+ const dotPosition = activeDot.offsetLeft;
132
+ const dotWidth = activeDot.clientWidth;
133
+ const containerWidth = container.clientWidth;
134
+ const scrollPosition =
135
+ dotPosition - containerWidth / 2 + dotWidth / 2;
136
+ if ("scrollBehavior" in document.documentElement.style) {
137
+ container.scrollTo({
138
+ left: Math.max(0, scrollPosition),
139
+ behavior: "smooth",
140
+ });
141
+ } else {
142
+ container.scrollLeft = Math.max(0, scrollPosition);
143
+ }
144
+ }
145
+ } catch (error) {
146
+ console.warn("Dot scrolling error:", error);
147
+ }
148
+ }
149
+ }, 50);
150
+ return () => clearTimeout(scrollTimeout);
151
+ }, [currentSlide, totalPages]);
152
+ const settings = useMemo(
153
+ () => ({
154
+ dots: false,
155
+ infinite: false,
156
+ speed: 600,
157
+ slidesToShow,
158
+ slidesToScroll: slidesToShow,
159
+ autoplay: isAutoplayEnabled,
160
+ autoplaySpeed: 5000,
161
+ pauseOnHover: true,
162
+ adaptiveHeight: false,
163
+ centerPadding: "0px",
164
+ centerMode: false,
165
+ variableWidth: false,
166
+ swipeToSlide: false,
167
+ focusOnSelect: false,
168
+ arrows: false,
169
+ responsive: [
170
+ {
171
+ breakpoint: 1024,
172
+ settings: {
173
+ slidesToShow: Math.min(projects.length, 2),
174
+ slidesToScroll: 2,
175
+ dots: false,
176
+ },
177
+ },
178
+ {
179
+ breakpoint: 600,
180
+ settings: { slidesToShow: 1, slidesToScroll: 1, dots: false },
181
+ },
182
+ ],
183
+ className: styles.projectsCarousel,
184
+ beforeChange: (_, next) => {
185
+ setAtBeginning(next === 0);
186
+ const nextSlideIndex = Math.floor(next / slidesToShow);
187
+ setCurrentSlide(nextSlideIndex);
188
+ setAtEnd(next + slidesToShow >= projects.length);
189
+ },
190
+ }),
191
+ [projects, slidesToShow],
192
+ );
193
+ const goToNext = useCallback(() => {
194
+ if (!atEnd && sliderRef.current) {
195
+ sliderRef.current.slickNext();
196
+ }
197
+ }, [atEnd]);
198
+ const goToPrev = useCallback(() => {
199
+ if (!atBeginning && sliderRef.current) {
200
+ sliderRef.current.slickPrev();
201
+ }
202
+ }, [atBeginning]);
203
+ const renderProjectLink = useCallback((url, Icon, label, ariaLabel) => {
204
+ if (!url || url === "#" || url === "") return null;
205
+ return jsxDEV_7x81h0kn(
206
+ "a",
207
+ {
208
+ href: url,
209
+ target: "_blank",
210
+ rel: "noopener noreferrer",
211
+ className: styles.projectLink,
212
+ "aria-label": ariaLabel,
213
+ children: [
214
+ jsxDEV_7x81h0kn(Icon, {}, undefined, false, undefined, this),
215
+ jsxDEV_7x81h0kn(
216
+ "span",
217
+ { children: label },
218
+ undefined,
219
+ false,
220
+ undefined,
221
+ this,
222
+ ),
223
+ ],
224
+ },
225
+ undefined,
226
+ true,
227
+ undefined,
228
+ this,
229
+ );
230
+ }, []);
231
+ const getProjectStateInfo = useCallback((state) => {
232
+ switch (state?.toLowerCase()) {
233
+ case "active":
234
+ return { label: "Active", className: styles.stateActive };
235
+ case "completed":
236
+ return { label: "Completed", className: styles.stateCompleted };
237
+ case "maintenance":
238
+ return { label: "Maintenance", className: styles.stateMaintenance };
239
+ case "paused":
240
+ return { label: "Paused", className: styles.statePaused };
241
+ case "archived":
242
+ return { label: "Archived", className: styles.stateArchived };
243
+ case "planned":
244
+ return { label: "Planned", className: styles.statePlanned };
245
+ case "n/a":
246
+ default:
247
+ return { label: "N/A", className: styles.stateNA };
248
+ }
249
+ }, []);
250
+ const renderNavigationDots = useCallback(() => {
251
+ if (totalPages <= 1) return null;
252
+ const fewDots = totalPages <= 5;
253
+ return jsxDEV_7x81h0kn(
254
+ "div",
255
+ {
256
+ className: `${styles.navDotsContainer} ${fewDots ? styles.centerDots : styles.scrollDots}`,
257
+ role: "tablist",
258
+ "aria-label": "Project carousel navigation",
259
+ children: Array.from({ length: totalPages }, (_, i) =>
260
+ jsxDEV_7x81h0kn(
261
+ "button",
262
+ {
263
+ className: `${styles.navDot} ${currentSlide === i ? styles.activeDot : ""}`,
264
+ onClick: () => goToSlide(i),
265
+ "aria-label": `Go to slide ${i + 1} of ${totalPages}`,
266
+ "aria-selected": currentSlide === i,
267
+ role: "tab",
268
+ type: "button",
269
+ ref: currentSlide === i ? activeDotRef : null,
270
+ },
271
+ i,
272
+ false,
273
+ undefined,
274
+ this,
275
+ ),
276
+ ),
277
+ },
278
+ undefined,
279
+ false,
280
+ undefined,
281
+ this,
282
+ );
283
+ }, [currentSlide, totalPages, goToSlide]);
284
+ return jsxDEV_7x81h0kn(
285
+ "div",
286
+ {
287
+ id,
288
+ ref: sectionRef,
289
+ className: `${styles.projectsSection} ${isVisible ? "is-visible" : ""} ${className || ""}`,
290
+ role: "region",
291
+ "aria-label": "Projects section",
292
+ children: jsxDEV_7x81h0kn(
293
+ "div",
294
+ {
295
+ className: styles.projectsContainer,
296
+ children: [
297
+ jsxDEV_7x81h0kn(
298
+ "div",
299
+ {
300
+ className: styles.projectsHeader,
301
+ children: [
302
+ jsxDEV_7x81h0kn(
303
+ "h2",
304
+ {
305
+ className: styles.projectsTitle,
306
+ children: displayHeading,
307
+ },
308
+ undefined,
309
+ false,
310
+ undefined,
311
+ this,
312
+ ),
313
+ jsxDEV_7x81h0kn(
314
+ "p",
315
+ {
316
+ className: styles.projectsSubtitle,
317
+ children: displaySubheading,
318
+ },
319
+ undefined,
320
+ false,
321
+ undefined,
322
+ this,
323
+ ),
324
+ ],
325
+ },
326
+ undefined,
327
+ true,
328
+ undefined,
329
+ this,
330
+ ),
331
+ projects.length === 0
332
+ ? jsxDEV_7x81h0kn(
333
+ "div",
334
+ {
335
+ className: styles.noProjects,
336
+ children: jsxDEV_7x81h0kn(
337
+ "p",
338
+ { children: "No projects to display." },
339
+ undefined,
340
+ false,
341
+ undefined,
342
+ this,
343
+ ),
344
+ },
345
+ undefined,
346
+ false,
347
+ undefined,
348
+ this,
349
+ )
350
+ : jsxDEV_7x81h0kn(
351
+ "div",
352
+ {
353
+ className: styles.carouselContainer,
354
+ children: [
355
+ projects.length > slidesToShow &&
356
+ jsxDEV_7x81h0kn(
357
+ "button",
358
+ {
359
+ className: `${styles.carouselControl} ${styles.prevButton} ${styles.desktopOnly} ${atBeginning ? styles.disabledButton : ""}`,
360
+ onClick: goToPrev,
361
+ "aria-label": "View previous projects",
362
+ "aria-disabled": atBeginning,
363
+ type: "button",
364
+ disabled: atBeginning,
365
+ children: jsxDEV_7x81h0kn(
366
+ FaChevronLeft,
367
+ { "aria-hidden": "true" },
368
+ undefined,
369
+ false,
370
+ undefined,
371
+ this,
372
+ ),
373
+ },
374
+ undefined,
375
+ false,
376
+ undefined,
377
+ this,
378
+ ),
379
+ jsxDEV_7x81h0kn(
380
+ "div",
381
+ {
382
+ className: styles.carouselWrapper,
383
+ "aria-roledescription": "carousel",
384
+ "aria-label": "Projects carousel",
385
+ children: [
386
+ jsxDEV_7x81h0kn(
387
+ Slider,
388
+ {
389
+ ref: sliderRef,
390
+ ...settings,
391
+ children: projects.map((project, index) =>
392
+ jsxDEV_7x81h0kn(
393
+ "div",
394
+ {
395
+ className: styles.carouselSlide,
396
+ "data-project-id": project.id,
397
+ "aria-roledescription": "slide",
398
+ "aria-label": `Project ${index + 1} of ${projects.length}: ${project.title}`,
399
+ style: { "--card-index": index },
400
+ children: jsxDEV_7x81h0kn(
401
+ "div",
402
+ {
403
+ className: `${styles.carouselCard} ${project.featured ? styles.featuredCard : ""}`,
404
+ children: [
405
+ project.state &&
406
+ jsxDEV_7x81h0kn(
407
+ "div",
408
+ {
409
+ className:
410
+ styles.projectStateBadge,
411
+ title: `Project status: ${getProjectStateInfo(project.state).label}`,
412
+ children: jsxDEV_7x81h0kn(
413
+ "span",
414
+ {
415
+ className: `${styles.projectStateLabel} ${getProjectStateInfo(project.state).className}`,
416
+ children:
417
+ getProjectStateInfo(
418
+ project.state,
419
+ ).label,
420
+ },
421
+ undefined,
422
+ false,
423
+ undefined,
424
+ this,
425
+ ),
426
+ },
427
+ undefined,
428
+ false,
429
+ undefined,
430
+ this,
431
+ ),
432
+ jsxDEV_7x81h0kn(
433
+ "div",
434
+ {
435
+ className:
436
+ styles.projectImageContainer,
437
+ style: {
438
+ backgroundColor:
439
+ project.bg ||
440
+ "rgba(var(--ifm-color-primary-rgb), 0.05)",
441
+ },
442
+ children: [
443
+ jsxDEV_7x81h0kn(
444
+ "img",
445
+ {
446
+ src: project.icon,
447
+ alt: project.title,
448
+ className:
449
+ styles.projectImage,
450
+ loading: "lazy",
451
+ },
452
+ undefined,
453
+ false,
454
+ undefined,
455
+ this,
456
+ ),
457
+ project.tags?.length > 0 &&
458
+ (() => {
459
+ const extraCount =
460
+ project.tags.length - 3;
461
+ return jsxDEV_7x81h0kn(
462
+ "div",
463
+ {
464
+ className:
465
+ styles.projectTags,
466
+ children: [
467
+ project.tags
468
+ .slice(0, 3)
469
+ .map((tag) =>
470
+ jsxDEV_7x81h0kn(
471
+ "span",
472
+ {
473
+ className:
474
+ styles.projectTag,
475
+ children:
476
+ tag,
477
+ },
478
+ tag,
479
+ false,
480
+ undefined,
481
+ this,
482
+ ),
483
+ ),
484
+ extraCount > 0 &&
485
+ jsxDEV_7x81h0kn(
486
+ Tooltip,
487
+ {
488
+ msg: project.tags
489
+ .slice(3)
490
+ .join(", "),
491
+ underline: false,
492
+ gap: 13,
493
+ children:
494
+ jsxDEV_7x81h0kn(
495
+ "span",
496
+ {
497
+ className: `${styles.projectTag} ${styles.extraTagBtn}`,
498
+ children:
499
+ [
500
+ "+",
501
+ extraCount,
502
+ ],
503
+ },
504
+ undefined,
505
+ true,
506
+ undefined,
507
+ this,
508
+ ),
509
+ },
510
+ undefined,
511
+ false,
512
+ undefined,
513
+ this,
514
+ ),
515
+ ],
516
+ },
517
+ undefined,
518
+ true,
519
+ undefined,
520
+ this,
521
+ );
522
+ })(),
523
+ project.featured &&
524
+ jsxDEV_7x81h0kn(
525
+ "div",
526
+ {
527
+ className:
528
+ styles.featuredBadge,
529
+ title:
530
+ "Featured Project",
531
+ "aria-label":
532
+ "Featured project",
533
+ children:
534
+ jsxDEV_7x81h0kn(
535
+ FaStar,
536
+ {
537
+ "aria-hidden":
538
+ "true",
539
+ },
540
+ undefined,
541
+ false,
542
+ undefined,
543
+ this,
544
+ ),
545
+ },
546
+ undefined,
547
+ false,
548
+ undefined,
549
+ this,
550
+ ),
551
+ ],
552
+ },
553
+ undefined,
554
+ true,
555
+ undefined,
556
+ this,
557
+ ),
558
+ jsxDEV_7x81h0kn(
559
+ "div",
560
+ {
561
+ className:
562
+ styles.projectContent,
563
+ children: [
564
+ jsxDEV_7x81h0kn(
565
+ "h3",
566
+ {
567
+ className:
568
+ styles.projectTitle,
569
+ children: project.title,
570
+ },
571
+ undefined,
572
+ false,
573
+ undefined,
574
+ this,
575
+ ),
576
+ jsxDEV_7x81h0kn(
577
+ "p",
578
+ {
579
+ className:
580
+ styles.projectDescription,
581
+ children: project.desc,
582
+ },
583
+ undefined,
584
+ false,
585
+ undefined,
586
+ this,
587
+ ),
588
+ ],
589
+ },
590
+ undefined,
591
+ true,
592
+ undefined,
593
+ this,
594
+ ),
595
+ jsxDEV_7x81h0kn(
596
+ "div",
597
+ {
598
+ className: styles.projectLinks,
599
+ children: [
600
+ renderProjectLink(
601
+ project.website,
602
+ FaGlobe,
603
+ "Website",
604
+ `Visit ${project.title} website`,
605
+ ),
606
+ renderProjectLink(
607
+ project.repo,
608
+ FaCode,
609
+ "Source",
610
+ `Repository with source code`,
611
+ ),
612
+ renderProjectLink(
613
+ project.demo,
614
+ FaPlay,
615
+ "Demo",
616
+ `Live demo for ${project.title}`,
617
+ ),
618
+ ],
619
+ },
620
+ undefined,
621
+ true,
622
+ undefined,
623
+ this,
624
+ ),
625
+ ],
626
+ },
627
+ undefined,
628
+ true,
629
+ undefined,
630
+ this,
631
+ ),
632
+ },
633
+ project.id || project.title + index,
634
+ false,
635
+ undefined,
636
+ this,
637
+ ),
638
+ ),
639
+ },
640
+ undefined,
641
+ false,
642
+ undefined,
643
+ this,
644
+ ),
645
+ jsxDEV_7x81h0kn(
646
+ "div",
647
+ {
648
+ className: styles.desktopDotsContainer,
649
+ children: renderNavigationDots(),
650
+ },
651
+ undefined,
652
+ false,
653
+ undefined,
654
+ this,
655
+ ),
656
+ jsxDEV_7x81h0kn(
657
+ "div",
658
+ {
659
+ className: styles.mobileNavigationControls,
660
+ children:
661
+ totalPages > 1 &&
662
+ jsxDEV_7x81h0kn(
663
+ Fragment_8vg9x3sq,
664
+ {
665
+ children: [
666
+ jsxDEV_7x81h0kn(
667
+ "button",
668
+ {
669
+ className: `${styles.carouselControl} ${styles.prevButton} ${atBeginning ? styles.disabledButton : ""}`,
670
+ onClick: goToPrev,
671
+ "aria-label":
672
+ "View previous projects",
673
+ "aria-disabled": atBeginning,
674
+ type: "button",
675
+ disabled: atBeginning,
676
+ children: jsxDEV_7x81h0kn(
677
+ FaChevronLeft,
678
+ { "aria-hidden": "true" },
679
+ undefined,
680
+ false,
681
+ undefined,
682
+ this,
683
+ ),
684
+ },
685
+ undefined,
686
+ false,
687
+ undefined,
688
+ this,
689
+ ),
690
+ jsxDEV_7x81h0kn(
691
+ "div",
692
+ {
693
+ className:
694
+ styles.dotsScrollContainer,
695
+ ref: dotsContainerRef,
696
+ children: renderNavigationDots(),
697
+ },
698
+ undefined,
699
+ false,
700
+ undefined,
701
+ this,
702
+ ),
703
+ jsxDEV_7x81h0kn(
704
+ "button",
705
+ {
706
+ className: `${styles.carouselControl} ${styles.nextButton} ${atEnd ? styles.disabledButton : ""}`,
707
+ onClick: goToNext,
708
+ "aria-label": "View next projects",
709
+ "aria-disabled": atEnd,
710
+ type: "button",
711
+ disabled: atEnd,
712
+ children: jsxDEV_7x81h0kn(
713
+ FaChevronRight,
714
+ { "aria-hidden": "true" },
715
+ undefined,
716
+ false,
717
+ undefined,
718
+ this,
719
+ ),
720
+ },
721
+ undefined,
722
+ false,
723
+ undefined,
724
+ this,
725
+ ),
726
+ ],
727
+ },
728
+ undefined,
729
+ true,
730
+ undefined,
731
+ this,
732
+ ),
733
+ },
734
+ undefined,
735
+ false,
736
+ undefined,
737
+ this,
738
+ ),
739
+ ],
740
+ },
741
+ undefined,
742
+ true,
743
+ undefined,
744
+ this,
745
+ ),
746
+ projects.length > slidesToShow &&
747
+ jsxDEV_7x81h0kn(
748
+ "button",
749
+ {
750
+ className: `${styles.carouselControl} ${styles.nextButton} ${styles.desktopOnly} ${atEnd ? styles.disabledButton : ""}`,
751
+ onClick: goToNext,
752
+ "aria-label": "View next projects",
753
+ "aria-disabled": atEnd,
754
+ type: "button",
755
+ disabled: atEnd,
756
+ children: jsxDEV_7x81h0kn(
757
+ FaChevronRight,
758
+ {},
759
+ undefined,
760
+ false,
761
+ undefined,
762
+ this,
763
+ ),
764
+ },
765
+ undefined,
766
+ false,
767
+ undefined,
768
+ this,
769
+ ),
770
+ ],
771
+ },
772
+ undefined,
773
+ true,
774
+ undefined,
775
+ this,
776
+ ),
777
+ ],
778
+ },
779
+ undefined,
780
+ true,
781
+ undefined,
782
+ this,
783
+ ),
784
+ },
785
+ undefined,
786
+ false,
787
+ undefined,
788
+ this,
789
+ );
790
+ }