portosaurus 3.0.2 → 4.0.1

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 (60) hide show
  1. package/README.md +26 -126
  2. package/bin/portosaurus.mjs +8 -0
  3. package/package.json +6 -3
  4. package/src/assets/img/icon.png +0 -0
  5. package/src/assets/img/{icon.svg → svg/icon.svg} +35 -37
  6. package/src/assets/img/svg/project-blank.svg +140 -0
  7. package/src/assets/sample-resume.pdf +0 -0
  8. package/src/cli/build.mjs +2 -5
  9. package/src/cli/dev.mjs +27 -5
  10. package/src/cli/init.mjs +6 -12
  11. package/src/cli/schema.mjs +211 -0
  12. package/src/core/buildDocuConfig.mjs +306 -189
  13. package/src/core/constants.mjs +7 -1
  14. package/src/template/config.yml +150 -0
  15. package/src/template/notes/welcome.mdx +6 -0
  16. package/src/template/package.json +3 -3
  17. package/src/theme/MDXComponents.js +0 -1
  18. package/src/theme/components/AboutSection/index.js +39 -17
  19. package/src/theme/components/AboutSection/styles.module.css +151 -344
  20. package/src/theme/components/ContactSection/index.js +29 -14
  21. package/src/theme/components/ContactSection/styles.module.css +19 -8
  22. package/src/theme/components/ExperienceSection/index.js +19 -5
  23. package/src/theme/components/HeroSection/index.js +11 -4
  24. package/src/theme/components/HeroSection/styles.module.css +17 -16
  25. package/src/theme/components/NavArrow/index.js +114 -0
  26. package/src/theme/components/NavArrow/styles.module.css +107 -0
  27. package/src/theme/components/NoteIndex/index.js +66 -95
  28. package/src/theme/components/NoteIndex/styles.module.css +85 -89
  29. package/src/theme/components/Preview/components/FeedbackStates.js +3 -1
  30. package/src/theme/components/Preview/components/PreviewContent.js +91 -0
  31. package/src/theme/components/Preview/components/PreviewHeader.js +41 -33
  32. package/src/theme/components/Preview/components/Triggers/Pv.js +129 -72
  33. package/src/theme/components/Preview/components/ViewerWindow.js +198 -234
  34. package/src/theme/components/Preview/hooks/useAdaptiveSizing.js +115 -0
  35. package/src/theme/components/Preview/hooks/useDeepLinkHash.js +18 -23
  36. package/src/theme/components/Preview/hooks/useDockLayout.js +48 -8
  37. package/src/theme/components/Preview/hooks/useTouchZoom.js +118 -0
  38. package/src/theme/components/Preview/renderers/CodeRenderer.js +64 -25
  39. package/src/theme/components/Preview/state/index.js +70 -17
  40. package/src/theme/components/Preview/styles.module.css +181 -45
  41. package/src/theme/components/Preview/utils/index.js +11 -10
  42. package/src/theme/components/ProjectsSection/index.js +145 -148
  43. package/src/theme/components/ProjectsSection/styles.module.css +178 -112
  44. package/src/theme/components/SocialLinks/index.js +9 -7
  45. package/src/theme/components/Tooltip/index.js +31 -20
  46. package/src/theme/components/Tooltip/styles.module.css +101 -38
  47. package/src/theme/config/iconMappings.js +2 -0
  48. package/src/theme/css/custom.css +72 -0
  49. package/src/theme/hooks/useScrollReveal.js +30 -0
  50. package/src/theme/pages/index.js +7 -27
  51. package/src/theme/pages/notes.js +2 -2
  52. package/src/theme/pages/tasks.js +12 -11
  53. package/src/utils/cliUtils.mjs +23 -51
  54. package/src/utils/configUtils.mjs +95 -84
  55. package/src/utils/systemUtils.mjs +171 -0
  56. package/src/template/config.js +0 -68
  57. package/src/theme/components/ScrollToTop/index.js +0 -95
  58. package/src/theme/components/ScrollToTop/styles.module.css +0 -97
  59. package/src/theme/config/metaTags.js +0 -21
  60. /package/src/template/{.nojekyll → static/.nojekyll} +0 -0
@@ -8,98 +8,161 @@
8
8
 
9
9
  .hasUnderline {
10
10
  cursor: help;
11
- border-bottom: 1.5px dotted var(--ifm-color-primary);
12
- transition: border-bottom-color 0.2s ease;
11
+ text-decoration: none;
12
+ background-image: radial-gradient(
13
+ circle,
14
+ color-mix(in srgb, var(--ifm-color-primary), transparent 40%) 1px,
15
+ transparent 1.2px
16
+ );
17
+ background-position: bottom;
18
+ background-size: 6px 2.5px;
19
+ background-repeat: space no-repeat;
20
+ padding-bottom: 1px;
21
+ transition: background-image 0.2s ease;
13
22
  }
14
23
 
15
24
  .hasUnderline:hover {
16
- border-bottom-style: dashed;
17
- border-bottom-color: var(--ifm-color-primary-darker);
25
+ background-image: radial-gradient(
26
+ circle,
27
+ color-mix(in srgb, var(--ifm-color-primary-darker), transparent 10%) 1.2px,
28
+ transparent 1.5px
29
+ );
18
30
  }
19
31
 
20
32
  .tooltip {
21
33
  position: fixed;
22
34
  background-color: var(--tooltip-color, var(--ifm-background-surface-color));
23
35
  color: var(--tooltip-text-color, var(--ifm-font-color-base));
24
- padding: 6px 10px;
25
- border-radius: 4px;
26
- font-size: 0.8rem;
27
- font-weight: 400;
36
+ padding: 8px 12px;
37
+ border-radius: var(--ifm-global-radius);
38
+ font-size: 0.85rem;
39
+ font-weight: 450;
40
+ text-align: center;
41
+ line-height: 1.4;
28
42
  white-space: normal;
29
43
  word-break: break-word;
30
- max-width: min(320px, 90vw);
44
+ overflow-wrap: anywhere;
45
+ max-width: min(240px, 90vw);
31
46
  width: max-content;
32
47
  z-index: 9999;
33
48
  opacity: 0;
34
- animation: tooltipFadeIn 0.15s ease-out forwards;
35
- box-shadow: var(--ifm-global-shadow-md);
49
+ filter: var(
50
+ --tooltip-shadow,
51
+ drop-shadow(0 8px 24px rgba(var(--ifm-color-emphasis-900-rgb), 0.3))
52
+ );
53
+ border: 1px solid var(--ifm-color-emphasis-300);
36
54
  pointer-events: none;
37
55
  }
38
56
 
39
- .arrow {
40
- position: absolute;
41
- width: 8px;
42
- height: 8px;
43
- background-color: inherit;
57
+ .top {
58
+ transform: translateX(-50%) translateY(-100%);
59
+ animation: popUp 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
44
60
  }
45
61
 
46
- /* Positioning: coordinates are set via JS (top/left on the element).
47
- These classes only handle the transform offset from that anchor point. */
62
+ .bottom {
63
+ transform: translateX(-50%);
64
+ animation: popDown 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
65
+ }
48
66
 
49
- .top {
50
- transform: translateX(-50%) translateY(-100%);
67
+ .left {
68
+ transform: translateX(-100%) translateY(-50%);
69
+ animation: popLeft 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
70
+ }
71
+
72
+ .right {
73
+ transform: translateY(-50%);
74
+ animation: popRight 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
75
+ }
76
+
77
+ .arrow {
78
+ position: absolute;
79
+ width: 10px;
80
+ height: 10px;
81
+ background-color: inherit;
51
82
  }
52
83
 
53
84
  .top .arrow {
54
85
  top: 100%;
55
86
  left: 50%;
56
87
  transform: translateX(-50%) rotate(45deg);
57
- margin-top: -4px;
58
- }
59
-
60
- .bottom {
61
- transform: translateX(-50%);
88
+ margin-top: -5.5px;
89
+ border-right: 1px solid var(--ifm-color-emphasis-300);
90
+ border-bottom: 1px solid var(--ifm-color-emphasis-300);
62
91
  }
63
92
 
64
93
  .bottom .arrow {
65
94
  bottom: 100%;
66
95
  left: 50%;
67
96
  transform: translateX(-50%) rotate(45deg);
68
- margin-bottom: -4px;
69
- }
70
-
71
- .left {
72
- transform: translateX(-100%) translateY(-50%);
97
+ margin-bottom: -5.5px;
98
+ border-left: 1px solid var(--ifm-color-emphasis-300);
99
+ border-top: 1px solid var(--ifm-color-emphasis-300);
73
100
  }
74
101
 
75
102
  .left .arrow {
76
- right: -4px;
103
+ right: -5.5px;
77
104
  top: 50%;
78
105
  transform: translateY(-50%) rotate(45deg);
79
- }
80
-
81
- .right {
82
- transform: translateY(-50%);
106
+ border-right: 1px solid var(--ifm-color-emphasis-300);
107
+ border-top: 1px solid var(--ifm-color-emphasis-300);
83
108
  }
84
109
 
85
110
  .right .arrow {
86
- left: -4px;
111
+ left: -5.5px;
87
112
  top: 50%;
88
113
  transform: translateY(-50%) rotate(45deg);
114
+ border-left: 1px solid var(--ifm-color-emphasis-300);
115
+ border-bottom: 1px solid var(--ifm-color-emphasis-300);
89
116
  }
90
117
 
91
- @keyframes tooltipFadeIn {
118
+ @keyframes popUp {
92
119
  from {
93
120
  opacity: 0;
121
+ transform: translateX(-50%) translateY(-95%) scale(0.9);
94
122
  }
95
123
  to {
96
124
  opacity: 1;
125
+ transform: translateX(-50%) translateY(-100%) scale(1);
126
+ }
127
+ }
128
+
129
+ @keyframes popDown {
130
+ from {
131
+ opacity: 0;
132
+ transform: translateX(-50%) translateY(-5%) scale(0.9);
133
+ }
134
+ to {
135
+ opacity: 1;
136
+ transform: translateX(-50%) translateY(0) scale(1);
137
+ }
138
+ }
139
+
140
+ @keyframes popLeft {
141
+ from {
142
+ opacity: 0;
143
+ transform: translateX(-95%) translateY(-50%) scale(0.9);
144
+ }
145
+ to {
146
+ opacity: 1;
147
+ transform: translateX(-100%) translateY(-50%) scale(1);
148
+ }
149
+ }
150
+
151
+ @keyframes popRight {
152
+ from {
153
+ opacity: 0;
154
+ transform: translateX(5%) translateY(-50%) scale(0.9);
155
+ }
156
+ to {
157
+ opacity: 1;
158
+ transform: translateX(0) translateY(-50%) scale(1);
97
159
  }
98
160
  }
99
161
 
100
162
  /* Accessibility */
101
163
  @media (prefers-reduced-motion: reduce) {
102
164
  .tooltip {
103
- animation: none;
165
+ animation: none !important;
166
+ opacity: 1;
104
167
  }
105
168
  }
@@ -106,6 +106,7 @@ import {
106
106
  SiElasticsearch,
107
107
  SiGraphql,
108
108
  SiGoogledrive,
109
+ SiLua,
109
110
  } from "react-icons/si";
110
111
 
111
112
  import {
@@ -204,6 +205,7 @@ export const iconMap = {
204
205
  php: { icon: FaPhp, color: "#777BB4" },
205
206
  html: { icon: FaHtml5, color: "#E34F26" },
206
207
  css: { icon: FaCss3Alt, color: "#1572B6" },
208
+ lua: { icon: SiLua, color: "#000080" },
207
209
  sql: { icon: SiMysql, color: "#4479A1" },
208
210
  postgresql: { icon: BiLogoPostgresql, color: "#336791" },
209
211
  postgres: { icon: BiLogoPostgresql, color: "#336791" },
@@ -179,3 +179,75 @@ mark {
179
179
  justify-content: center;
180
180
  align-items: center;
181
181
  }
182
+
183
+ /* === Global Entrance Animations === */
184
+
185
+ @keyframes fadeIn {
186
+ from {
187
+ opacity: 0;
188
+ }
189
+ to {
190
+ opacity: 1;
191
+ }
192
+ }
193
+
194
+ @keyframes slideUp {
195
+ from {
196
+ opacity: 0;
197
+ transform: translateY(30px);
198
+ }
199
+ to {
200
+ opacity: 1;
201
+ transform: translateY(0);
202
+ }
203
+ }
204
+
205
+ @keyframes slideInLeft {
206
+ from {
207
+ opacity: 0;
208
+ transform: translateX(-40px);
209
+ }
210
+ to {
211
+ opacity: 1;
212
+ transform: translateX(0);
213
+ }
214
+ }
215
+
216
+ @keyframes slideInRight {
217
+ from {
218
+ opacity: 0;
219
+ transform: translateX(40px);
220
+ }
221
+ to {
222
+ opacity: 1;
223
+ transform: translateX(0);
224
+ }
225
+ }
226
+
227
+ @keyframes scaleIn {
228
+ from {
229
+ opacity: 0;
230
+ transform: scale(0.95);
231
+ }
232
+ to {
233
+ opacity: 1;
234
+ transform: scale(1);
235
+ }
236
+ }
237
+
238
+ @keyframes skillAppear {
239
+ to {
240
+ opacity: 1;
241
+ transform: translateY(0);
242
+ }
243
+ }
244
+
245
+ /* Base reveal state */
246
+ .reveal-on-scroll {
247
+ opacity: 0;
248
+ transition: opacity 0.3s ease;
249
+ }
250
+
251
+ .reveal-on-scroll.isVisible {
252
+ opacity: 1;
253
+ }
@@ -0,0 +1,30 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+
3
+ /**
4
+ * A reusable hook to detect when an element enters the viewport.
5
+ * @param {Object} options IntersectionObserver options
6
+ * @returns {Array} [ref, isVisible]
7
+ */
8
+ export default function useScrollReveal(options = { threshold: 0.05 }) {
9
+ const [isVisible, setIsVisible] = useState(false);
10
+ const elementRef = useRef(null);
11
+
12
+ useEffect(() => {
13
+ const observer = new IntersectionObserver(([entry]) => {
14
+ setIsVisible(entry.isIntersecting);
15
+ }, options);
16
+
17
+ const currentElement = elementRef.current;
18
+ if (currentElement) {
19
+ observer.observe(currentElement);
20
+ }
21
+
22
+ return () => {
23
+ if (currentElement) {
24
+ observer.unobserve(currentElement);
25
+ }
26
+ };
27
+ }, [options]);
28
+
29
+ return [elementRef, isVisible];
30
+ }
@@ -8,7 +8,7 @@ import AboutSection from "../components/AboutSection/index.js";
8
8
  import ProjectsSection from "../components/ProjectsSection/index.js";
9
9
  import ContactSection from "../components/ContactSection/index.js";
10
10
  import ExperienceSection from "../components/ExperienceSection/index.js";
11
- import ScrollToTop from "../components/ScrollToTop/index.js";
11
+ import NavArrow from "../components/NavArrow/index.js";
12
12
 
13
13
  export default function Home() {
14
14
  const { siteConfig } = useDocusaurusContext();
@@ -43,39 +43,19 @@ export default function Home() {
43
43
  <HeroSection id="me" />
44
44
 
45
45
  {/* About Section */}
46
- {aboutMe.enable !== false && (
47
- <AboutSection id="about" title="About Me" />
48
- )}
46
+ <AboutSection id="about" />
49
47
 
50
48
  {/* Projects Section */}
51
- {projects.enable !== false && (
52
- <ProjectsSection
53
- id="projects"
54
- title="My Projects"
55
- subtitle="A collection of all my works, with featured projects highlighted"
56
- />
57
- )}
49
+ <ProjectsSection id="projects" />
58
50
 
59
51
  {/* Experience Section */}
60
- {experience.enable !== false && (
61
- <ExperienceSection
62
- id="experience"
63
- title="Experience"
64
- subtitle="My professional journey and work experience"
65
- />
66
- )}
52
+ <ExperienceSection id="experience" />
67
53
 
68
54
  {/* Contact Section */}
69
- {socialLinks.enable !== false && (
70
- <ContactSection
71
- id="contact"
72
- title="Get In Touch"
73
- subtitle="Feel free to reach out for collaborations, questions, or just to say hello!"
74
- />
75
- )}
55
+ <ContactSection id="contact" />
76
56
 
77
- {/* Scroll to top button */}
78
- <ScrollToTop hideDelay={3500} />
57
+ {/* Smart Navigation Arrow */}
58
+ <NavArrow />
79
59
  </main>
80
60
  </Layout>
81
61
  );
@@ -1,7 +1,7 @@
1
1
  import Layout from "@theme/Layout";
2
2
  import NoteCards from "../components/NoteIndex/index.js";
3
3
  import { usePluginData } from "@docusaurus/useGlobalData";
4
- import ScrollToTop from "../components/ScrollToTop/index.js";
4
+ import NavArrow from "../components/NavArrow/index.js";
5
5
  import HashNavigation from "../utils/HashNavigation.js";
6
6
 
7
7
  const style = {
@@ -70,7 +70,7 @@ export default function Notes() {
70
70
  </p>
71
71
  </header>
72
72
  <NoteCards />
73
- <ScrollToTop />
73
+ <NavArrow />
74
74
  <HashNavigation
75
75
  elementPrefix="note-"
76
76
  elementSelector=".note-card"
@@ -97,8 +97,8 @@ function TaskList({ filterStatus, taskList }) {
97
97
 
98
98
  <div className="task-cell task-cell-title">
99
99
  <div className="task-title">{task.title}</div>
100
- {task.description && (
101
- <div className="task-description">{task.description}</div>
100
+ {task.desc && (
101
+ <div className="task-description">{task.desc}</div>
102
102
  )}
103
103
  </div>
104
104
 
@@ -256,8 +256,8 @@ export default function TasksPage() {
256
256
  const { customFields } = siteConfig || {};
257
257
 
258
258
  const tasksPage = customFields?.tasksPage;
259
- const title = tasksPage.title;
260
- const description = tasksPage.description;
259
+ const heading = tasksPage.heading || "Tasks";
260
+ const subheading = tasksPage.subheading || "Roadmap & Goals";
261
261
  const taskList =
262
262
  tasksPage.enable && tasksPage.taskList ? tasksPage.taskList : [];
263
263
 
@@ -276,7 +276,7 @@ export default function TasksPage() {
276
276
  </div>
277
277
  <h2 className="disabled-title">Tasks are currently disabled</h2>
278
278
  <p className="disabled-help">
279
- To enable tasks, set <code>tasks_page.enable</code> to{" "}
279
+ To enable tasks, set <code>tasks.enable</code> to{" "}
280
280
  <code>true</code>
281
281
  </p>
282
282
  </div>
@@ -287,17 +287,18 @@ export default function TasksPage() {
287
287
  }
288
288
 
289
289
  return (
290
- <Layout title={title} description={description}>
290
+ <Layout title={heading} description={subheading}>
291
291
  <Head>
292
- <meta property="og:title" content={title} />
293
- <meta property="og:description" content={description} />
294
- <meta name="twitter:title" content={title} />
295
- <meta name="twitter:description" content={description} />
292
+ <meta property="og:title" content={heading} />
293
+ <meta property="og:description" content={subheading} />
294
+ <meta name="twitter:title" content={heading} />
295
+ <meta name="twitter:description" content={subheading} />
296
296
  </Head>
297
297
 
298
298
  <div className="tasks-container">
299
299
  <div className="tasks-header">
300
- <h1 className="tasks-heading">{title}</h1>
300
+ <h1 className="tasks-heading">{heading}</h1>
301
+ {subheading && <p className="tasks-subheading">{subheading}</p>}
301
302
  </div>
302
303
 
303
304
  <div className="tasks-content">
@@ -12,17 +12,32 @@ export function writePortoConfigShim(UserRoot) {
12
12
  const dotDir = path.join(UserRoot, ".docusaurus", "portosaurus");
13
13
  fs.mkdirSync(dotDir, { recursive: true });
14
14
 
15
- const configJsAbsolute = path.join(UserRoot, "config.js").replace(/\\/g, "/");
15
+ const configYaml = ["config.yaml", "config.yml"].find((f) =>
16
+ fs.existsSync(path.join(UserRoot, f)),
17
+ );
18
+
19
+ if (!configYaml) {
20
+ logger.error("No config.yaml or config.yml found in project root.");
21
+ process.exit(1);
22
+ }
23
+
24
+ const configYamlAbsolute = path
25
+ .join(UserRoot, configYaml)
26
+ .replace(/\\/g, "/");
16
27
  const createDocuConfAbsolute = path
17
28
  .resolve(PortoRoot, "src/core/buildDocuConfig.mjs")
18
29
  .replace(/\\/g, "/");
19
30
 
20
31
  const shimContent = `// Auto-generated by Portosaurus CLI — do not edit
32
+ import fs from "fs";
33
+ import yaml from "js-yaml";
34
+
21
35
  /** @type {import('@docusaurus/types').Config} */
22
36
  export default async function getConfig() {
23
37
  const { buildDocuConfig } = await import("file://${createDocuConfAbsolute}");
24
- const { usrConf } = await import("file://${configJsAbsolute}");
25
- return buildDocuConfig(usrConf, "${UserRoot.replace(/\\/g, "/")}");
38
+ const yamlContent = fs.readFileSync("${configYamlAbsolute}", "utf8");
39
+ const rawConf = yaml.load(yamlContent);
40
+ return buildDocuConfig(rawConf, "${UserRoot.replace(/\\/g, "/")}");
26
41
  }
27
42
  `;
28
43
 
@@ -37,11 +52,13 @@ export default async function getConfig() {
37
52
  * Validate that the current directory is a Portosaurus project.
38
53
  */
39
54
  export function validateProject(UserRoot) {
40
- const configPath = path.join(UserRoot, "config.js");
41
- if (!fs.existsSync(configPath)) {
55
+ const configPath = ["config.yaml", "config.yml"].find((f) =>
56
+ fs.existsSync(path.join(UserRoot, f)),
57
+ );
58
+ if (!configPath) {
42
59
  logger.log("");
43
60
  logger.error(
44
- "config.js not found. Are you in a Portosaurus project directory?",
61
+ "config.yml/config.yaml not found. Are you in a Portosaurus project directory?",
45
62
  );
46
63
  process.exit(1);
47
64
  }
@@ -99,48 +116,3 @@ export function runDocusaurus(command, extraArgs, UserRoot, configPath) {
99
116
  });
100
117
  });
101
118
  }
102
-
103
- // Recursively copy a directory and apply text replacements.
104
- export function mirrorSync(src, dest, replacements = {}, ignores = []) {
105
- const ignoreSet = new Set(ignores);
106
- const textExtensions = new Set([
107
- ".js",
108
- ".mjs",
109
- ".json",
110
- ".md",
111
- ".mdx",
112
- ".yml",
113
- ".yaml",
114
- ".css",
115
- ".html",
116
- ".txt",
117
- ]);
118
-
119
- fs.mkdirSync(dest, { recursive: true });
120
-
121
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
122
- if (ignoreSet.has(entry.name)) continue;
123
-
124
- const srcPath = path.join(src, entry.name);
125
- const destPath = path.join(dest, entry.name);
126
-
127
- if (entry.isDirectory()) {
128
- mirrorSync(srcPath, destPath, replacements, ignores);
129
- } else {
130
- const ext = path.extname(entry.name).toLowerCase();
131
-
132
- // If replacements exist and it's a known text file, process it
133
- if (Object.keys(replacements).length > 0 && textExtensions.has(ext)) {
134
- let content = fs.readFileSync(srcPath, "utf8");
135
- for (const [key, value] of Object.entries(replacements)) {
136
- // Replace all occurrences of {{key}}
137
- const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
138
- content = content.replace(regex, value);
139
- }
140
- fs.writeFileSync(destPath, content);
141
- } else {
142
- fs.copyFileSync(srcPath, destPath); // Safe copy for binaries (images, etc) or when no replacements needed
143
- }
144
- }
145
- }
146
- }