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
@@ -1,3 +1,11 @@
1
+ :root {
2
+ --pv-backdrop-bg: rgba(var(--ifm-color-emphasis-900-rgb), 0.1);
3
+ --pv-popup-shadow: 0 20px 50px rgba(var(--ifm-color-emphasis-900-rgb), 0.3);
4
+ --pv-popup-width: min(900px, 90vw);
5
+ --pv-popup-height: min(700px, 85vh);
6
+ --pv-popup-radius: var(--ifm-global-radius, 16px);
7
+ }
8
+
1
9
  @keyframes fadeIn {
2
10
  from {
3
11
  opacity: 0;
@@ -28,13 +36,19 @@
28
36
  }
29
37
 
30
38
  .loading {
31
- flex: 1;
39
+ height: 100%;
40
+ width: 100%;
32
41
  display: flex;
33
42
  flex-direction: column;
34
43
  align-items: center;
35
44
  justify-content: center;
36
- min-height: 200px;
37
- gap: 16px;
45
+ gap: 20px;
46
+ }
47
+
48
+ .loadingIcon {
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
38
52
  }
39
53
 
40
54
  .loadingText {
@@ -43,24 +57,27 @@
43
57
 
44
58
  .loadingText p {
45
59
  margin: 0;
46
- font-weight: 700;
47
- font-size: 1.4rem;
60
+ font-weight: 600;
61
+ font-size: 1rem;
48
62
  color: var(--ifm-font-color-base);
49
63
  }
50
64
 
51
65
  .loadingText span {
52
- font-size: 0.95rem;
53
- color: var(--ifm-color-primary-lightest);
66
+ display: block;
67
+ margin-top: 4px;
68
+ font-size: 0.85rem;
69
+ color: var(--ifm-color-primary);
54
70
  opacity: 0.8;
55
71
  }
56
72
 
57
73
  .spinner {
58
- width: 40px;
59
- height: 40px;
60
- border: 4px solid var(--ifm-color-emphasis-200);
74
+ width: 36px;
75
+ height: 36px;
76
+ border: 3px solid
77
+ color-mix(in srgb, var(--ifm-color-primary), transparent 80%);
61
78
  border-top-color: var(--ifm-color-primary);
62
79
  border-radius: 50%;
63
- animation: spin 0.8s cubic-bezier(0.5, 0.1, 0.5, 0.9) infinite;
80
+ animation: spin 0.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
64
81
  }
65
82
 
66
83
  .spinnerSmall {
@@ -92,12 +109,15 @@
92
109
  flex-direction: column;
93
110
  background: var(--ifm-background-surface-color);
94
111
  box-shadow: var(--ifm-global-shadow-lw);
95
- transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
112
+ transition:
113
+ background 0.3s ease,
114
+ border 0.3s ease,
115
+ box-shadow 0.3s ease;
96
116
  z-index: 10;
97
117
  border: 1px solid var(--ifm-color-emphasis-200);
98
118
  }
99
119
 
100
- .modeFloating .windowFrame,
120
+ .modePip .windowFrame,
101
121
  .modePeek .windowFrame {
102
122
  overflow: hidden;
103
123
  }
@@ -110,7 +130,7 @@
110
130
  }
111
131
 
112
132
  /* --- Modes (visual-only — react-rnd handles positioning for floating/dock) --- */
113
- .modeFloating .windowFrame {
133
+ .modePip .windowFrame {
114
134
  border-radius: 12px;
115
135
  }
116
136
 
@@ -119,11 +139,12 @@
119
139
  border-radius: 0;
120
140
  border: none;
121
141
  border-left: 1px solid var(--ifm-toc-border-color);
142
+ border-right: none;
143
+ border-bottom: none;
122
144
  transition:
123
145
  border-left-color 0.2s ease,
124
146
  box-shadow 0.2s ease,
125
- width 0.4s cubic-bezier(0.22, 1, 0.36, 1),
126
- transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
147
+ transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
127
148
  }
128
149
 
129
150
  .modeDock .windowFrame.windowInteracting {
@@ -131,14 +152,66 @@
131
152
  box-shadow: -4px 0 12px -4px rgba(var(--ifm-color-primary-rgb), 0.3);
132
153
  }
133
154
 
155
+ .modePip .windowFrame {
156
+ border-radius: 12px;
157
+ overflow: hidden;
158
+ }
159
+
160
+ .modeFull .windowFrame {
161
+ position: absolute;
162
+ inset: 0;
163
+ width: 100vw;
164
+ height: 100vh;
165
+ border-radius: 0;
166
+ border: none;
167
+ z-index: 2000;
168
+ }
169
+
170
+ .fullFrame {
171
+ border-radius: 0 !important;
172
+ border: none !important;
173
+ }
174
+
134
175
  .modePeek .windowFrame {
135
176
  position: absolute;
136
177
  bottom: 0;
137
178
  left: 0;
138
179
  right: 0;
139
180
  width: 100vw;
140
- height: var(--mobile-peek-height, 40vh);
141
- border-radius: 16px 16px 0 0;
181
+ height: var(--mobile-peek-height, 50vh);
182
+ border-radius: 0;
183
+ border-top: 1px solid var(--ifm-toc-border-color);
184
+ }
185
+
186
+ .previewBackdrop {
187
+ position: absolute;
188
+ inset: 0;
189
+ background: var(--pv-backdrop-bg);
190
+ backdrop-filter: blur(2px);
191
+ -webkit-backdrop-filter: blur(2px);
192
+ z-index: 5;
193
+ cursor: pointer;
194
+ pointer-events: auto;
195
+ }
196
+
197
+ .modePopup .windowFrame {
198
+ position: absolute;
199
+ top: 50%;
200
+ left: 50%;
201
+ /* transform removed to allow framer-motion full control over centering animation */
202
+ width: var(--pv-popup-width);
203
+ height: var(--pv-popup-height);
204
+ border-radius: var(--pv-popup-radius);
205
+ box-shadow: var(--pv-popup-shadow);
206
+ overflow: hidden;
207
+ z-index: 10;
208
+ }
209
+
210
+ @media (max-width: 1024px) {
211
+ .modePopup .windowFrame {
212
+ width: 95vw;
213
+ height: 90vh;
214
+ }
142
215
  }
143
216
 
144
217
  /* --- Window Components: Header & Drag --- */
@@ -157,6 +230,11 @@
157
230
 
158
231
  .modeDock .dragHandleWrapper {
159
232
  padding-top: var(--ifm-navbar-height, 60px);
233
+ cursor: default;
234
+ }
235
+
236
+ .modeDock .dragHandleWrapper:active {
237
+ cursor: default;
160
238
  }
161
239
 
162
240
  .revealHeader {
@@ -173,22 +251,22 @@
173
251
  pointer-events: none;
174
252
  }
175
253
 
176
- .revealHeader .modalTitle {
254
+ .revealHeader .popupTitle {
177
255
  flex: none; /* Don't expand so centering works */
178
256
  justify-content: center;
179
257
  }
180
258
 
181
- .modeFloating .revealHeader,
259
+ .modePip .revealHeader,
182
260
  .modePeek .revealHeader {
183
261
  display: none;
184
262
  }
185
263
 
186
- .modalHeader {
187
- padding: 12px 20px;
264
+ .popupHeader {
265
+ padding: 8px 16px;
188
266
  display: flex;
189
267
  justify-content: space-between;
190
268
  align-items: center;
191
- min-height: 52px;
269
+ min-height: 44px;
192
270
  }
193
271
 
194
272
  .headerLeft {
@@ -200,7 +278,7 @@
200
278
  margin-right: 20px;
201
279
  }
202
280
 
203
- .modalTitle {
281
+ .popupTitle {
204
282
  margin: 0;
205
283
  font-size: 0.95rem;
206
284
  font-weight: 600;
@@ -259,6 +337,12 @@
259
337
  cursor: pointer;
260
338
  }
261
339
 
340
+ @media (max-width: 768px) {
341
+ .btnText {
342
+ display: none;
343
+ }
344
+ }
345
+
262
346
  .headerAction:hover {
263
347
  opacity: 0.9;
264
348
  transform: translateY(-1px);
@@ -276,8 +360,11 @@
276
360
  box-shadow: none;
277
361
  }
278
362
 
363
+ .headerActionClose {
364
+ background-color: var(--ifm-color-danger);
365
+ }
366
+
279
367
  .headerActionClose:hover {
280
- background-color: var(--ifm-color-danger) !important;
281
368
  box-shadow: 0 4px 12px rgba(var(--ifm-color-danger-rgb), 0.3);
282
369
  }
283
370
 
@@ -405,12 +492,29 @@
405
492
  min-height: 0;
406
493
  }
407
494
 
408
- .modalBody {
495
+ .popupBody {
409
496
  flex: 1;
410
- overflow-y: auto;
411
- background: var(--ifm-background-color);
497
+ overflow: auto;
498
+ position: relative;
412
499
  display: flex;
413
500
  flex-direction: column;
501
+ outline: none; /* Focusable for Vimium */
502
+ }
503
+
504
+ .isGrabbable {
505
+ cursor: grab;
506
+ }
507
+
508
+ .isText {
509
+ cursor: text;
510
+ }
511
+
512
+ .isPanning:not(.isText) {
513
+ cursor: grabbing !important;
514
+ }
515
+
516
+ .isPanning:not(.isText) * {
517
+ cursor: grabbing !important;
414
518
  }
415
519
 
416
520
  /* --- Media Views --- */
@@ -448,17 +552,18 @@
448
552
 
449
553
  .pdfView {
450
554
  flex: 1;
451
- overflow-y: auto;
452
555
  padding: 4px;
453
556
  background: var(--ifm-background-color);
454
557
  position: relative;
455
558
  display: flex;
456
559
  flex-direction: column;
457
- align-items: center;
560
+ align-items: safe center;
458
561
  }
459
562
 
460
563
  .pdfPage {
461
564
  margin-bottom: 4px;
565
+ margin-left: auto;
566
+ margin-right: auto;
462
567
  box-shadow: var(--ifm-global-shadow-md);
463
568
  animation: fadeIn 0.4s ease-out;
464
569
  }
@@ -467,18 +572,19 @@
467
572
  flex: 1;
468
573
  display: flex;
469
574
  flex-direction: column;
470
- position: relative; /* Ensure loader stays contained */
575
+ position: relative;
576
+ min-height: 0;
577
+ height: 100%;
471
578
  }
472
579
  .webFrame {
473
580
  flex: 1;
474
581
  border: none;
475
582
  background: var(--ifm-background-surface-color);
583
+ width: 100%;
584
+ height: 100%;
585
+ display: block;
476
586
  }
477
587
 
478
- /* Resize handles are now managed by react-rnd */
479
-
480
- /* Note: spinner and loading classes moved to top of file for better cascade */
481
-
482
588
  .errorState {
483
589
  flex: 1;
484
590
  display: flex;
@@ -501,13 +607,20 @@
501
607
  .previewTrigger {
502
608
  color: var(--ifm-link-color);
503
609
  cursor: pointer;
504
- text-decoration: none;
505
- font-weight: 500;
610
+ text-decoration: underline dashed;
611
+ text-decoration-color: color-mix(
612
+ in srgb,
613
+ var(--ifm-link-color),
614
+ transparent 40%
615
+ );
616
+ text-decoration-thickness: 1px;
617
+ text-underline-offset: 4px;
506
618
  transition: all 0.2s ease;
507
619
  }
508
620
  .previewTrigger:hover {
509
- color: var(--ifm-link-hover-color);
510
- text-decoration: underline;
621
+ text-decoration: underline solid;
622
+ text-decoration-color: var(--ifm-link-hover-color);
623
+ text-decoration-thickness: 1px;
511
624
  }
512
625
 
513
626
  .activeTrigger {
@@ -518,6 +631,10 @@
518
631
  text-underline-offset: 4px !important;
519
632
  }
520
633
 
634
+ .noUnderline {
635
+ text-decoration: none !important;
636
+ }
637
+
521
638
  .sourceFooter {
522
639
  margin: 2rem 0 1rem;
523
640
  padding-top: 1.2rem;
@@ -535,9 +652,11 @@
535
652
  /* --- react-rnd wrapper --- */
536
653
  .rndWrapper {
537
654
  pointer-events: auto;
655
+ display: flex;
656
+ flex-direction: column;
538
657
  }
539
658
 
540
- .modeFloating .rndWrapper {
659
+ .modePip .rndWrapper {
541
660
  border-radius: 12px;
542
661
  box-shadow: var(--ifm-global-shadow-lw);
543
662
  }
@@ -548,11 +667,22 @@
548
667
 
549
668
  /* --- Mobile Peek Handle --- */
550
669
  .peekHandle {
551
- width: 36px;
552
- height: 4px;
553
- border-radius: 2px;
554
- background: var(--ifm-color-emphasis-300);
555
- margin: 8px auto 0;
670
+ display: none;
671
+ }
672
+
673
+ .modePeek .windowFrame {
674
+ border-radius: 0;
675
+ border: none;
676
+ border-top: 1px solid var(--ifm-color-emphasis-400);
677
+ box-shadow: 0 -10px 40px rgba(var(--ifm-color-emphasis-900-rgb), 0.15);
678
+ transition:
679
+ border-top-color 0.2s ease,
680
+ box-shadow 0.2s ease;
681
+ }
682
+
683
+ .modePeek .windowFrame.windowInteracting {
684
+ border-top: 2px solid var(--ifm-color-primary);
685
+ box-shadow: 0 -4px 12px -4px rgba(var(--ifm-color-primary-rgb), 0.4);
556
686
  }
557
687
 
558
688
  /* --- Iframe Loading Overlay --- */
@@ -638,3 +768,9 @@
638
768
  :global(body.pv-dock-active) :global(.container.docItemWrapperEnhanced_JWYK) {
639
769
  max-width: none !important;
640
770
  }
771
+
772
+ /* --- Global Layout Hooks (when peeking on mobile/tablet) --- */
773
+ :global(body.pv-peek-active) :global(.main-wrapper) {
774
+ padding-bottom: var(--mobile-peek-height, 50vh);
775
+ transition: padding-bottom 0.3s cubic-bezier(0.16, 1, 0.3, 1);
776
+ }
@@ -26,6 +26,8 @@ const TEXT_EXTS = [
26
26
  "html",
27
27
  "xml",
28
28
  "sql",
29
+ "diff",
30
+ "patch",
29
31
  ];
30
32
 
31
33
  const IMAGE_EXTS = ["png", "jpg", "jpeg", "gif", "webp", "svg"];
@@ -64,11 +66,10 @@ export function resolveUrl(path) {
64
66
  }
65
67
 
66
68
  /**
67
- * Standardizes label/path into a clean URL slug.
69
+ * Standardizes a string into a clean URL slug.
68
70
  */
69
- export function generatePvSlug(label, path) {
70
- const sourceLabel = label || (path || "").split("/").pop() || "source";
71
- return sourceLabel
71
+ export function generatePvSlug(text) {
72
+ return (text || "preview")
72
73
  .toLowerCase()
73
74
  .replace(/[^a-z0-9]+/g, "-")
74
75
  .replace(/(^-|-$)/g, "");
@@ -76,10 +77,11 @@ export function generatePvSlug(label, path) {
76
77
 
77
78
  /**
78
79
  * Generates the unified PV hash with mode suffix.
80
+ * Format: #slug:pv-mode
79
81
  */
80
- export function generatePvHash(slug, isDocked) {
82
+ export function generatePvHash(slug, mode) {
81
83
  if (!slug) return "";
82
- return `${slug}-pv:${isDocked ? "dock" : "window"}`;
84
+ return `${slug}:pv-${mode || "popup"}`;
83
85
  }
84
86
 
85
87
  /**
@@ -88,9 +90,8 @@ export function generatePvHash(slug, isDocked) {
88
90
  export function parsePvHash(hash) {
89
91
  if (!hash) return null;
90
92
  const cleanHash = hash.replace("#", "");
91
- if (!cleanHash.includes("-pv:")) return null;
93
+ if (!cleanHash.includes(":pv-")) return null;
92
94
 
93
- const [slugPart, mode] = cleanHash.split(":");
94
- const slug = slugPart.replace("-pv", "");
95
- return { slug, mode, isDocked: mode === "dock" };
95
+ const [slug, mode] = cleanHash.split(":pv-");
96
+ return { slug, mode };
96
97
  }