devfolio-page 0.2.4 → 0.2.6

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 (33) hide show
  1. package/dist/cli/helpers/validate.js +1 -1
  2. package/dist/generator/builder.js +116 -23
  3. package/dist/generator/themes/dark-academia/styles.css +194 -0
  4. package/dist/generator/themes/dark-academia/templates/about.html +115 -0
  5. package/dist/generator/themes/dark-academia/templates/experiments-index.html +0 -1
  6. package/dist/generator/themes/dark-academia/templates/homepage.html +3 -3
  7. package/dist/generator/themes/dark-academia/templates/project.html +0 -1
  8. package/dist/generator/themes/dark-academia/templates/projects-index.html +0 -1
  9. package/dist/generator/themes/dark-academia/templates/writing-index.html +0 -1
  10. package/dist/generator/themes/modern/templates/about.html +113 -0
  11. package/dist/generator/themes/modern/templates/experiments-index.html +0 -1
  12. package/dist/generator/themes/modern/templates/homepage.html +3 -3
  13. package/dist/generator/themes/modern/templates/project.html +0 -1
  14. package/dist/generator/themes/modern/templates/projects-index.html +0 -1
  15. package/dist/generator/themes/modern/templates/writing-index.html +0 -1
  16. package/dist/generator/themes/srcl/templates/about.html +102 -0
  17. package/dist/generator/themes/srcl/templates/homepage.html +3 -3
  18. package/package.json +3 -3
  19. package/src/generator/themes/dark-academia/styles.css +194 -0
  20. package/src/generator/themes/dark-academia/templates/about.html +115 -0
  21. package/src/generator/themes/dark-academia/templates/experiments-index.html +0 -1
  22. package/src/generator/themes/dark-academia/templates/homepage.html +3 -3
  23. package/src/generator/themes/dark-academia/templates/project.html +0 -1
  24. package/src/generator/themes/dark-academia/templates/projects-index.html +0 -1
  25. package/src/generator/themes/dark-academia/templates/writing-index.html +0 -1
  26. package/src/generator/themes/modern/templates/about.html +113 -0
  27. package/src/generator/themes/modern/templates/experiments-index.html +0 -1
  28. package/src/generator/themes/modern/templates/homepage.html +3 -3
  29. package/src/generator/themes/modern/templates/project.html +0 -1
  30. package/src/generator/themes/modern/templates/projects-index.html +0 -1
  31. package/src/generator/themes/modern/templates/writing-index.html +0 -1
  32. package/src/generator/themes/srcl/templates/about.html +102 -0
  33. package/src/generator/themes/srcl/templates/homepage.html +3 -3
@@ -86,7 +86,7 @@ export function validatePortfolio(yamlPath) {
86
86
  export function getPortfolioSummary(portfolio) {
87
87
  return {
88
88
  name: portfolio.meta.name,
89
- title: portfolio.meta.title,
89
+ title: portfolio.meta.title || '',
90
90
  experienceCount: portfolio.sections.experience?.length ?? 0,
91
91
  projectCount: portfolio.sections.projects?.length ?? 0,
92
92
  skillCategoryCount: portfolio.sections.skills
@@ -119,6 +119,9 @@ async function generateMultiPageSite(portfolio, themePath, outputDir, theme) {
119
119
  // Generate homepage
120
120
  const homepageFiles = await generateHomepage(portfolio, themePath, outputDir, theme);
121
121
  files.push(...homepageFiles);
122
+ // Generate about page
123
+ const aboutFiles = await generateAboutPage(portfolio, themePath, outputDir, theme);
124
+ files.push(...aboutFiles);
122
125
  // Generate project pages
123
126
  const projectFiles = await generateProjectPages(portfolio, themePath, outputDir, theme);
124
127
  files.push(...projectFiles);
@@ -150,14 +153,14 @@ async function generateHomepage(portfolio, themePath, outputDir, theme) {
150
153
  location: portfolio.meta.location,
151
154
  timezone: portfolio.meta.timezone,
152
155
  // Contact
153
- email: portfolio.contact.email,
154
- website: portfolio.contact.website,
155
- github: portfolio.contact.github,
156
- linkedin: portfolio.contact.linkedin,
157
- twitter: portfolio.contact.twitter,
156
+ email: portfolio.contact?.email,
157
+ website: portfolio.contact?.website,
158
+ github: portfolio.contact?.github,
159
+ linkedin: portfolio.contact?.linkedin,
160
+ twitter: portfolio.contact?.twitter,
158
161
  // Bio
159
- bio: portfolio.bio,
160
- bio_html: parseBio(portfolio.bio),
162
+ bio: portfolio.bio || '',
163
+ bio_html: parseBio(portfolio.bio || ''),
161
164
  about_short: portfolio.about?.short || '',
162
165
  // Featured content
163
166
  featured_projects: portfolio.projects?.filter((p) => p.featured) || [],
@@ -235,6 +238,44 @@ async function generateProjectsIndex(portfolio, themePath, outputDir, theme) {
235
238
  await fs.writeFile(path.join(outputDir, 'projects/index.html'), html);
236
239
  return ['projects/index.html'];
237
240
  }
241
+ async function generateAboutPage(portfolio, themePath, outputDir, theme) {
242
+ // Only generate if about section exists
243
+ if (!portfolio.about?.long && !portfolio.about?.short) {
244
+ return [];
245
+ }
246
+ const templatePath = path.join(themePath, 'templates/about.html');
247
+ if (!(await fileExists(templatePath))) {
248
+ return [];
249
+ }
250
+ await fs.mkdir(path.join(outputDir, 'about'), { recursive: true });
251
+ const template = await fs.readFile(templatePath, 'utf-8');
252
+ const settings = portfolio.settings || {};
253
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
254
+ // Convert skills object to array format for template
255
+ const skillsArray = portfolio.sections?.skills
256
+ ? Object.entries(portfolio.sections.skills).map(([category, items]) => ({
257
+ category,
258
+ items,
259
+ }))
260
+ : [];
261
+ const data = {
262
+ site_name: portfolio.meta.name,
263
+ tagline: portfolio.meta.tagline,
264
+ avatar: portfolio.meta.avatar,
265
+ about_content: parseMarkdown(portfolio.about?.long || portfolio.about?.short || ''),
266
+ contact: portfolio.contact,
267
+ hasContact: !!(portfolio.contact?.email || portfolio.contact?.github || portfolio.contact?.linkedin || portfolio.contact?.twitter || portfolio.contact?.website),
268
+ skills: skillsArray,
269
+ hasSkills: skillsArray.length > 0,
270
+ education: portfolio.sections?.education || [],
271
+ hasEducation: (portfolio.sections?.education?.length ?? 0) > 0,
272
+ colorScheme: settings.color_scheme || defaultColorScheme,
273
+ nav_links: generateNavLinks(portfolio, true, 'about'),
274
+ };
275
+ const html = Mustache.render(template, data);
276
+ await fs.writeFile(path.join(outputDir, 'about/index.html'), html);
277
+ return ['about/index.html'];
278
+ }
238
279
  async function generateExperimentsIndex(portfolio, themePath, outputDir, theme) {
239
280
  // Only generate if experiments exist
240
281
  if (!portfolio.experiments || portfolio.experiments.length === 0) {
@@ -360,7 +401,20 @@ function renderContentSection(section, partials) {
360
401
  function generateNavLinks(portfolio, fromSubdir = false, currentPage = 'home') {
361
402
  const links = [];
362
403
  const prefix = fromSubdir ? '../' : './';
363
- // Note: "Home" is not included since the site name already links home
404
+ // Always include Home link
405
+ links.push({
406
+ href: fromSubdir ? '../' : './',
407
+ label: 'Home',
408
+ active: currentPage === 'home',
409
+ });
410
+ // Include About link if about section exists
411
+ if (portfolio.about?.long || portfolio.about?.short) {
412
+ links.push({
413
+ href: `${prefix}about/`,
414
+ label: 'About',
415
+ active: currentPage === 'about',
416
+ });
417
+ }
364
418
  if ((portfolio.projects?.length ?? 0) > 0) {
365
419
  links.push({
366
420
  href: `${prefix}projects/`,
@@ -375,7 +429,7 @@ function generateNavLinks(portfolio, fromSubdir = false, currentPage = 'home') {
375
429
  active: currentPage === 'experiments',
376
430
  });
377
431
  }
378
- if ((portfolio.sections.writing?.length ?? 0) > 0) {
432
+ if ((portfolio.sections?.writing?.length ?? 0) > 0) {
379
433
  links.push({
380
434
  href: `${prefix}writing/`,
381
435
  label: 'Writing',
@@ -419,14 +473,14 @@ function prepareTemplateData(portfolio) {
419
473
  location: portfolio.meta.location,
420
474
  timezone: portfolio.meta.timezone,
421
475
  // Contact
422
- email: portfolio.contact.email,
423
- website: portfolio.contact.website,
424
- github: portfolio.contact.github,
425
- linkedin: portfolio.contact.linkedin,
426
- twitter: portfolio.contact.twitter,
476
+ email: portfolio.contact?.email,
477
+ website: portfolio.contact?.website,
478
+ github: portfolio.contact?.github,
479
+ linkedin: portfolio.contact?.linkedin,
480
+ twitter: portfolio.contact?.twitter,
427
481
  // Bio
428
- bio: portfolio.bio,
429
- bio_html: parseBio(portfolio.bio),
482
+ bio: portfolio.bio || '',
483
+ bio_html: parseBio(portfolio.bio || ''),
430
484
  // Sections
431
485
  experiences,
432
486
  projects: portfolio.sections.projects || [],
@@ -665,6 +719,9 @@ async function generateMultiPageSiteInMemory(portfolio, themePath, theme, files)
665
719
  // Generate homepage
666
720
  const homepageFiles = await generateHomepageInMemory(portfolio, themePath, theme, files);
667
721
  fileList.push(...homepageFiles);
722
+ // Generate about page
723
+ const aboutFiles = await generateAboutPageInMemory(portfolio, themePath, theme, files);
724
+ fileList.push(...aboutFiles);
668
725
  // Generate project pages
669
726
  const projectFiles = await generateProjectPagesInMemory(portfolio, themePath, theme, files);
670
727
  fileList.push(...projectFiles);
@@ -694,13 +751,13 @@ async function generateHomepageInMemory(portfolio, themePath, theme, files) {
694
751
  title: portfolio.meta.title,
695
752
  location: portfolio.meta.location,
696
753
  timezone: portfolio.meta.timezone,
697
- email: portfolio.contact.email,
698
- website: portfolio.contact.website,
699
- github: portfolio.contact.github,
700
- linkedin: portfolio.contact.linkedin,
701
- twitter: portfolio.contact.twitter,
702
- bio: portfolio.bio,
703
- bio_html: parseBio(portfolio.bio),
754
+ email: portfolio.contact?.email,
755
+ website: portfolio.contact?.website,
756
+ github: portfolio.contact?.github,
757
+ linkedin: portfolio.contact?.linkedin,
758
+ twitter: portfolio.contact?.twitter,
759
+ bio: portfolio.bio || '',
760
+ bio_html: parseBio(portfolio.bio || ''),
704
761
  about_short: portfolio.about?.short || '',
705
762
  featured_projects: portfolio.projects?.filter((p) => p.featured) || [],
706
763
  featured_writing: portfolio.sections.writing?.filter((w) => 'featured' in w && w.featured) || [],
@@ -769,6 +826,42 @@ async function generateProjectsIndexInMemory(portfolio, themePath, theme, files)
769
826
  files.set('projects/index.html', Buffer.from(html, 'utf-8'));
770
827
  return ['projects/index.html'];
771
828
  }
829
+ async function generateAboutPageInMemory(portfolio, themePath, theme, files) {
830
+ if (!portfolio.about?.long && !portfolio.about?.short) {
831
+ return [];
832
+ }
833
+ const templatePath = path.join(themePath, 'templates/about.html');
834
+ if (!(await fileExists(templatePath))) {
835
+ return [];
836
+ }
837
+ const template = await fs.readFile(templatePath, 'utf-8');
838
+ const settings = portfolio.settings || {};
839
+ const defaultColorScheme = theme === 'dark-academia' ? 'light' : 'dark';
840
+ // Convert skills object to array format for template
841
+ const skillsArray = portfolio.sections?.skills
842
+ ? Object.entries(portfolio.sections.skills).map(([category, items]) => ({
843
+ category,
844
+ items,
845
+ }))
846
+ : [];
847
+ const data = {
848
+ site_name: portfolio.meta.name,
849
+ tagline: portfolio.meta.tagline,
850
+ avatar: portfolio.meta.avatar,
851
+ about_content: parseMarkdown(portfolio.about?.long || portfolio.about?.short || ''),
852
+ contact: portfolio.contact,
853
+ hasContact: !!(portfolio.contact?.email || portfolio.contact?.github || portfolio.contact?.linkedin || portfolio.contact?.twitter || portfolio.contact?.website),
854
+ skills: skillsArray,
855
+ hasSkills: skillsArray.length > 0,
856
+ education: portfolio.sections?.education || [],
857
+ hasEducation: (portfolio.sections?.education?.length ?? 0) > 0,
858
+ colorScheme: settings.color_scheme || defaultColorScheme,
859
+ nav_links: generateNavLinks(portfolio, true, 'about'),
860
+ };
861
+ const html = Mustache.render(template, data);
862
+ files.set('about/index.html', Buffer.from(html, 'utf-8'));
863
+ return ['about/index.html'];
864
+ }
772
865
  async function generateExperimentsIndexInMemory(portfolio, themePath, theme, files) {
773
866
  if (!portfolio.experiments || portfolio.experiments.length === 0) {
774
867
  return [];
@@ -896,6 +896,200 @@ a:hover {
896
896
  gap: var(--spacing-xs);
897
897
  }
898
898
 
899
+ /* --------------------------------------------
900
+ About Page
901
+ -------------------------------------------- */
902
+ .about-content {
903
+ max-width: 640px;
904
+ margin: 0 auto;
905
+ }
906
+
907
+ .about-avatar {
908
+ text-align: center;
909
+ margin-bottom: var(--spacing-2xl);
910
+ }
911
+
912
+ .about-avatar img {
913
+ width: 160px;
914
+ height: 160px;
915
+ border-radius: 50%;
916
+ object-fit: cover;
917
+ border: 3px solid var(--border-color);
918
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
919
+ }
920
+
921
+ .about-bio {
922
+ margin-bottom: var(--spacing-3xl);
923
+ line-height: 1.85;
924
+ }
925
+
926
+ .about-bio h2,
927
+ .about-bio h3 {
928
+ font-family: var(--font-sans);
929
+ font-size: 0.75rem;
930
+ font-weight: 600;
931
+ text-transform: uppercase;
932
+ letter-spacing: 0.15em;
933
+ color: var(--text-tertiary);
934
+ margin-top: var(--spacing-2xl);
935
+ margin-bottom: var(--spacing-md);
936
+ padding-bottom: var(--spacing-xs);
937
+ border-bottom: 1px solid var(--border-color);
938
+ }
939
+
940
+ .about-bio h2:first-child,
941
+ .about-bio h3:first-child {
942
+ margin-top: 0;
943
+ }
944
+
945
+ .about-bio p {
946
+ color: var(--text-secondary);
947
+ margin-bottom: var(--spacing-md);
948
+ }
949
+
950
+ .about-bio p:last-child {
951
+ margin-bottom: 0;
952
+ }
953
+
954
+ .about-bio ul,
955
+ .about-bio ol {
956
+ color: var(--text-secondary);
957
+ margin-bottom: var(--spacing-md);
958
+ padding-left: var(--spacing-lg);
959
+ }
960
+
961
+ .about-bio li {
962
+ margin-bottom: var(--spacing-xs);
963
+ }
964
+
965
+ /* About Skills Section */
966
+ .about-skills {
967
+ margin-bottom: var(--spacing-3xl);
968
+ padding-top: var(--spacing-xl);
969
+ border-top: 1px solid var(--border-color);
970
+ }
971
+
972
+ .about-skills h2 {
973
+ font-family: var(--font-sans);
974
+ font-size: 0.75rem;
975
+ font-weight: 600;
976
+ text-transform: uppercase;
977
+ letter-spacing: 0.15em;
978
+ color: var(--text-tertiary);
979
+ margin-bottom: var(--spacing-xl);
980
+ }
981
+
982
+ .skill-category {
983
+ margin-bottom: var(--spacing-lg);
984
+ }
985
+
986
+ .skill-category:last-child {
987
+ margin-bottom: 0;
988
+ }
989
+
990
+ .skill-category h3 {
991
+ font-family: var(--font-sans);
992
+ font-size: 0.8rem;
993
+ font-weight: 600;
994
+ color: var(--text-primary);
995
+ margin-bottom: var(--spacing-sm);
996
+ }
997
+
998
+ .skill-category .project-tags {
999
+ display: flex;
1000
+ flex-wrap: wrap;
1001
+ gap: var(--spacing-xs);
1002
+ }
1003
+
1004
+ /* About Education Section */
1005
+ .about-education {
1006
+ margin-bottom: var(--spacing-3xl);
1007
+ padding-top: var(--spacing-xl);
1008
+ border-top: 1px solid var(--border-color);
1009
+ }
1010
+
1011
+ .about-education h2 {
1012
+ font-family: var(--font-sans);
1013
+ font-size: 0.75rem;
1014
+ font-weight: 600;
1015
+ text-transform: uppercase;
1016
+ letter-spacing: 0.15em;
1017
+ color: var(--text-tertiary);
1018
+ margin-bottom: var(--spacing-xl);
1019
+ }
1020
+
1021
+ .about-education .education-item {
1022
+ display: block;
1023
+ margin-bottom: var(--spacing-lg);
1024
+ padding-bottom: var(--spacing-lg);
1025
+ border-bottom: 1px solid var(--border-color);
1026
+ }
1027
+
1028
+ .about-education .education-item:last-child {
1029
+ margin-bottom: 0;
1030
+ padding-bottom: 0;
1031
+ border-bottom: none;
1032
+ }
1033
+
1034
+ .about-education .education-item h3 {
1035
+ font-size: 1rem;
1036
+ font-weight: 700;
1037
+ color: var(--text-primary);
1038
+ margin-bottom: var(--spacing-xs);
1039
+ }
1040
+
1041
+ .about-education .education-item .degree {
1042
+ font-style: italic;
1043
+ color: var(--text-secondary);
1044
+ margin-bottom: var(--spacing-xs);
1045
+ }
1046
+
1047
+ .about-education .education-item .date {
1048
+ font-family: var(--font-sans);
1049
+ font-size: 0.8rem;
1050
+ color: var(--text-tertiary);
1051
+ margin-bottom: var(--spacing-xs);
1052
+ }
1053
+
1054
+ .about-education .education-item .description {
1055
+ font-size: 0.9rem;
1056
+ color: var(--text-tertiary);
1057
+ }
1058
+
1059
+ /* About Contact Section */
1060
+ .about-contact {
1061
+ padding-top: var(--spacing-xl);
1062
+ border-top: 1px solid var(--border-color);
1063
+ }
1064
+
1065
+ .about-contact h2 {
1066
+ font-family: var(--font-sans);
1067
+ font-size: 0.75rem;
1068
+ font-weight: 600;
1069
+ text-transform: uppercase;
1070
+ letter-spacing: 0.15em;
1071
+ color: var(--text-tertiary);
1072
+ margin-bottom: var(--spacing-lg);
1073
+ }
1074
+
1075
+ .contact-links {
1076
+ display: flex;
1077
+ flex-wrap: wrap;
1078
+ gap: var(--spacing-sm) var(--spacing-lg);
1079
+ }
1080
+
1081
+ .contact-links a {
1082
+ font-family: var(--font-sans);
1083
+ font-size: 0.85rem;
1084
+ color: var(--text-secondary);
1085
+ text-decoration: underline;
1086
+ text-underline-offset: 3px;
1087
+ }
1088
+
1089
+ .contact-links a:hover {
1090
+ color: var(--accent-color);
1091
+ }
1092
+
899
1093
  /* Print styles */
900
1094
  @media print {
901
1095
  .site-nav {
@@ -0,0 +1,115 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>About - {{site_name}}</title>
7
+ <meta name="description" content="About {{site_name}}">
8
+ <link rel="stylesheet" href="../assets/styles.css">
9
+ <script>
10
+ (function() {
11
+ try {
12
+ var saved = localStorage.getItem('academia-theme');
13
+ if (saved) document.documentElement.dataset.theme = saved;
14
+ } catch(e) {}
15
+ })();
16
+ </script>
17
+ </head>
18
+ <body class="academia-theme" data-theme="{{colorScheme}}">
19
+
20
+ <nav class="site-nav">
21
+ <div class="nav-content">
22
+ <a href="../" class="nav-logo">{{site_name}}</a>
23
+ <div class="nav-links">
24
+ {{#nav_links}}
25
+ <a href="{{href}}"{{#active}} class="active"{{/active}}>{{label}}</a>
26
+ {{/nav_links}}
27
+ </div>
28
+ <button class="theme-btn" title="Toggle theme" aria-label="Toggle theme">
29
+ <svg class="icon-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
30
+ <circle cx="12" cy="12" r="5"></circle>
31
+ <line x1="12" y1="1" x2="12" y2="3"></line>
32
+ <line x1="12" y1="21" x2="12" y2="23"></line>
33
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
34
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
35
+ <line x1="1" y1="12" x2="3" y2="12"></line>
36
+ <line x1="21" y1="12" x2="23" y2="12"></line>
37
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
38
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
39
+ </svg>
40
+ <svg class="icon-moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
41
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
42
+ </svg>
43
+ </button>
44
+ </div>
45
+ </nav>
46
+
47
+ <main class="page-content">
48
+ <header class="page-header">
49
+ <div class="header-ornament"></div>
50
+ <h1>About</h1>
51
+ {{#tagline}}<p>{{tagline}}</p>{{/tagline}}
52
+ </header>
53
+
54
+ <div class="about-content">
55
+ {{#avatar}}
56
+ <div class="about-avatar">
57
+ <img src="{{avatar}}" alt="{{site_name}}" />
58
+ </div>
59
+ {{/avatar}}
60
+
61
+ <div class="about-bio">
62
+ {{{about_content}}}
63
+ </div>
64
+
65
+ {{#hasSkills}}
66
+ <section class="about-skills">
67
+ <h2>Skills & Expertise</h2>
68
+ {{#skills}}
69
+ <div class="skill-category">
70
+ <h3>{{category}}</h3>
71
+ <div class="project-tags">
72
+ {{#items}}<span class="tag">{{.}}</span>{{/items}}
73
+ </div>
74
+ </div>
75
+ {{/skills}}
76
+ </section>
77
+ {{/hasSkills}}
78
+
79
+ {{#hasEducation}}
80
+ <section class="about-education">
81
+ <h2>Education</h2>
82
+ {{#education}}
83
+ <div class="education-item">
84
+ <h3>{{institution}}</h3>
85
+ {{#degree}}<p class="degree">{{degree}}</p>{{/degree}}
86
+ {{#date}}<p class="date">{{date.start}} - {{date.end}}</p>{{/date}}
87
+ {{#description}}<p class="description">{{description}}</p>{{/description}}
88
+ </div>
89
+ {{/education}}
90
+ </section>
91
+ {{/hasEducation}}
92
+
93
+ {{#hasContact}}
94
+ <section class="about-contact">
95
+ <h2>Correspondence</h2>
96
+ <div class="contact-links">
97
+ {{#contact.email}}<a href="mailto:{{contact.email}}">{{contact.email}}</a>{{/contact.email}}
98
+ {{#contact.github}}<a href="https://github.com/{{contact.github}}" target="_blank">Archives</a>{{/contact.github}}
99
+ {{#contact.linkedin}}<a href="https://linkedin.com/in/{{contact.linkedin}}" target="_blank">LinkedIn</a>{{/contact.linkedin}}
100
+ {{#contact.twitter}}<a href="https://twitter.com/{{contact.twitter}}" target="_blank">Twitter</a>{{/contact.twitter}}
101
+ {{#contact.website}}<a href="{{contact.website}}" target="_blank">Website</a>{{/contact.website}}
102
+ </div>
103
+ </section>
104
+ {{/hasContact}}
105
+ </div>
106
+ </main>
107
+
108
+ <footer class="site-footer">
109
+ <div class="footer-ornament"></div>
110
+ <p>Crafted with <a href="https://devfolio.page" target="_blank">devfolio.page</a></p>
111
+ </footer>
112
+
113
+ <script src="../assets/script.js"></script>
114
+ </body>
115
+ </html>
@@ -21,7 +21,6 @@
21
21
  <div class="nav-content">
22
22
  <a href="../" class="nav-logo">{{site_name}}</a>
23
23
  <div class="nav-links">
24
- <a href="../">Home</a>
25
24
  {{#nav_links}}
26
25
  <a href="{{href}}"{{#active}} class="active"{{/active}}>{{label}}</a>
27
26
  {{/nav_links}}
@@ -21,9 +21,9 @@
21
21
  <div class="nav-content">
22
22
  <a href="./" class="nav-logo">{{name}}</a>
23
23
  <div class="nav-links">
24
- {{#hasProjects}}<a href="./projects/">Projects</a>{{/hasProjects}}
25
- {{#hasExperiments}}<a href="./experiments/">Experiments</a>{{/hasExperiments}}
26
- {{#hasWriting}}<a href="./writing/">Writing</a>{{/hasWriting}}
24
+ {{#nav_links}}
25
+ <a href="{{href}}"{{#active}} class="active"{{/active}}>{{label}}</a>
26
+ {{/nav_links}}
27
27
  </div>
28
28
  <button class="theme-btn" title="Toggle theme" aria-label="Toggle theme">
29
29
  <svg class="icon-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -21,7 +21,6 @@
21
21
  <div class="nav-content">
22
22
  <a href="../" class="nav-logo">{{site_name}}</a>
23
23
  <div class="nav-links">
24
- <a href="../">Home</a>
25
24
  {{#nav_links}}
26
25
  <a href="{{href}}"{{#active}} class="active"{{/active}}>{{label}}</a>
27
26
  {{/nav_links}}
@@ -21,7 +21,6 @@
21
21
  <div class="nav-content">
22
22
  <a href="../" class="nav-logo">{{site_name}}</a>
23
23
  <div class="nav-links">
24
- <a href="../">Home</a>
25
24
  {{#nav_links}}
26
25
  <a href="{{href}}"{{#active}} class="active"{{/active}}>{{label}}</a>
27
26
  {{/nav_links}}
@@ -21,7 +21,6 @@
21
21
  <div class="nav-content">
22
22
  <a href="../" class="nav-logo">{{site_name}}</a>
23
23
  <div class="nav-links">
24
- <a href="../">Home</a>
25
24
  {{#nav_links}}
26
25
  <a href="{{href}}"{{#active}} class="active"{{/active}}>{{label}}</a>
27
26
  {{/nav_links}}