Skip to content

Commit 26f2e71

Browse files
committed
Refactor project date handling and sorted portfolio projects by reverse chronological order
1 parent a5b28ff commit 26f2e71

5 files changed

Lines changed: 156 additions & 30 deletions

File tree

.eleventy.js

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,94 @@
1+
const MONTH_LABELS = {
2+
1: "Jan.",
3+
2: "Feb.",
4+
3: "Mar.",
5+
4: "Apr.",
6+
5: "May.",
7+
6: "Jun.",
8+
7: "Jul.",
9+
8: "Aug.",
10+
9: "Sept.",
11+
10: "Oct.",
12+
11: "Nov.",
13+
12: "Dec.",
14+
};
15+
16+
function normalizeMonthYear(value) {
17+
if (!value || typeof value !== "object") return null;
18+
const year = Number(value.year);
19+
if (Number.isNaN(year)) return null;
20+
21+
if (value.month === undefined || value.month === null) {
22+
return { year, month: null };
23+
}
24+
25+
const month = Number(value.month);
26+
if (Number.isNaN(month) || month < 1 || month > 12) return null;
27+
return { year, month };
28+
}
29+
30+
function structuredDateToSortKey(dateValue) {
31+
if (!dateValue || typeof dateValue !== "object") return null;
32+
const start = normalizeMonthYear(dateValue.start);
33+
const end = normalizeMonthYear(dateValue.end);
34+
const present = Boolean(dateValue.present);
35+
36+
if (start) {
37+
return start.year * 100 + (start.month || 1);
38+
}
39+
if (present) return 999912;
40+
if (end) {
41+
return end.year * 100 + (end.month || 12);
42+
}
43+
return null;
44+
}
45+
46+
function formatMonthYear(value) {
47+
const normalized = normalizeMonthYear(value);
48+
if (!normalized) return "";
49+
if (!normalized.month) return String(normalized.year);
50+
return `${MONTH_LABELS[normalized.month]} ${normalized.year}`;
51+
}
52+
53+
function formatStructuredDate(dateValue) {
54+
if (!dateValue || typeof dateValue !== "object") return "";
55+
const startText = formatMonthYear(dateValue.start);
56+
const endText = formatMonthYear(dateValue.end);
57+
58+
if (!startText && !endText && !dateValue.present) return "";
59+
if (dateValue.present && startText) return `${startText} - Present`;
60+
if (startText && endText) {
61+
if (startText === endText) return startText;
62+
return `${startText} - ${endText}`;
63+
}
64+
return startText || endText;
65+
}
66+
67+
function projectSortKey(project) {
68+
if (!project || typeof project !== "object") return -1;
69+
const structuredSortKey = structuredDateToSortKey(project.date);
70+
return structuredSortKey === null ? -1 : structuredSortKey;
71+
}
72+
173
module.exports = function (eleventyConfig) {
274
eleventyConfig.addPassthroughCopy({ "src/assets": "assets" });
375
eleventyConfig.addPassthroughCopy({ "src/images": "images" });
76+
eleventyConfig.addFilter("sortProjectsByDateDesc", (projects) => {
77+
if (!Array.isArray(projects)) return [];
78+
return [...projects].sort((a, b) => {
79+
const keyDiff = projectSortKey(b) - projectSortKey(a);
80+
if (keyDiff !== 0) return keyDiff;
81+
return (a.title || "").localeCompare(b.title || "");
82+
});
83+
});
84+
eleventyConfig.addFilter("formatProjectDate", (project) => {
85+
if (!project || typeof project !== "object") return "";
86+
return formatStructuredDate(project.date);
87+
});
488

589
return {
690
dir: { input: "src", output: "_site" },
791
htmlTemplateEngine: "njk",
892
markdownTemplateEngine: "njk",
993
};
10-
};
94+
};

src/_data/projects.json

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"image": "/images/duckwild.png",
66
"alt": "Prototype wildlife tracking node with camera and LoRa mesh networking",
77
"description": "A low-power wildlife monitoring system that uses on-device computer vision and a LoRa mesh network to detect and identify animals without relying on cellular or Wi-Fi connectivity.",
8-
"dates": "Sept. 2025 – Present",
8+
"date": {
9+
"start": { "month": 9, "year": 2025 },
10+
"present": true
11+
},
912
"tags": [
1013
"Embedded Systems",
1114
"Computer Vision",
@@ -114,7 +117,10 @@
114117
"image": "/images/camera-flowchart.png",
115118
"alt": "Flowchart for digital camera firmware",
116119
"description": "Built a STM32-based digital camera by integrating camera, LCD screen, and SD card storage.",
117-
"dates": "May. 2025 - Jun. 2025",
120+
"date": {
121+
"start": { "month": 5, "year": 2025 },
122+
"end": { "month": 6, "year": 2025 }
123+
},
118124
"tags": ["STM32", "DMA", "SPI", "I2C", "Firmware/Hardware Debugging"],
119125
"projectPage": {
120126
"url": "/projects/bare-metal-digital-camera.html",
@@ -144,7 +150,9 @@
144150
"image": "/images/thread-process-meme.png",
145151
"alt": "Meme about Linux threads and processes",
146152
"description": "A small user‑level threading library written in C with a round‑robin scheduler.",
147-
"dates": "Jan. 2026",
153+
"date": {
154+
"start": { "month": 1, "year": 2026 }
155+
},
148156
"tags": ["Linux", "C", "Threads"],
149157
"projectPage": {
150158
"url": "/projects/ieee-grid-stabilization.html",
@@ -164,7 +172,10 @@
164172
"image": "/images/bull_robot.png",
165173
"alt": "Bull robot",
166174
"description": "An autonomous mobile robot that detects humans, recognizes red objects, and follows a target in real time using stereo vision and onboard AI.",
167-
"dates": "Nov. 2025 - Dec. 2025",
175+
"date": {
176+
"start": { "month": 11, "year": 2025 },
177+
"end": { "month": 12, "year": 2025 }
178+
},
168179
"tags": ["Robotics", "Artificial intelligence", "Python", "ROS 2"],
169180
"projectPage": {
170181
"url": "/projects/bull-robot.html",
@@ -274,7 +285,10 @@
274285
"image": "/images/IEEE_logo.png",
275286
"alt": "IEEE logo",
276287
"description": "An officially published IEEE paper documenting the work our team completed in the 2025 WERC Environmental Engineering Competition to stabilize grid operations using green hydrogen technology",
277-
"dates": "Oct. 2024 - Apr. 2025",
288+
"date": {
289+
"start": { "month": 10, "year": 2024 },
290+
"end": { "month": 4, "year": 2025 }
291+
},
278292
"tags": ["Multidisciplinary", "Experimental design", "Technical writing"],
279293
"projectPage": {
280294
"url": "/projects/ieee-grid-stabilization.html",
@@ -294,7 +308,10 @@
294308
"image": "/images/Pipelined RISCV Otter.png",
295309
"alt": "RISCV Otter diagram",
296310
"description": "A five-stage, pipelined and cached MCU capable of handling data/control hazards by stalling, forwarding, and flushing.",
297-
"dates": "Sept. 2024 – Dec. 2024",
311+
"date": {
312+
"start": { "month": 9, "year": 2024 },
313+
"end": { "month": 12, "year": 2024 }
314+
},
298315
"tags": ["Computer Architecture", "RISC-V", "FPGA", "SystemVerilog"],
299316
"projectPage": {
300317
"url": "/projects/pipelined-riscv.html",
@@ -314,7 +331,10 @@
314331
"image": "/images/dtmf.jpg",
315332
"alt": "Keypad",
316333
"description": "A dual-tone multi-frequency decoder coded in Python using integer-based digital signal processing.",
317-
"dates": "Sept. 2024 - Dec. 2024",
334+
"date": {
335+
"start": { "month": 9, "year": 2024 },
336+
"end": { "month": 12, "year": 2024 }
337+
},
318338
"tags": [
319339
"Digital signal processing",
320340
"Signal processing",
@@ -336,7 +356,9 @@
336356
"image": "/images/Sliding-Window-Protocol.jpg",
337357
"alt": "Sliding Window Protocol diagram",
338358
"description": "Built a reliable file transfer protocol on top of UDP using a Selective Reject sliding window. The project includes a client and forked server that handle packet loss, reordering, and corruption via checksums, cumulative ACKs (RR), targeted NACKs (SREJ), and an EOF/ACK teardown.",
339-
"dates": "May. 2025",
359+
"date": {
360+
"start": { "month": 5, "year": 2025 }
361+
},
340362
"tags": ["Networks", "Protocol design", "UDP", "C"],
341363
"projectPage": {
342364
"url": "/projects/reliable-udp-transfer.html",
@@ -372,7 +394,9 @@
372394
"image": "/images/SLOinformeddemo.gif",
373395
"alt": "Me at the Hackathon",
374396
"description": "A weekend long hackathon where our group presented an app to improve civic engagement in SLO. We won an award for best pitch!",
375-
"dates": "Mar. 2025",
397+
"date": {
398+
"start": { "month": 3, "year": 2025 }
399+
},
376400
"tags": ["Hackathon", "Pitching", "Rapid prototyping"],
377401
"projectPage": {
378402
"url": "/projects/sloinformed-polyhacks.html",
@@ -392,7 +416,10 @@
392416
"image": "/images/customdefinitions logo.png",
393417
"alt": "CustomDefinitions logo",
394418
"description": "A digital shop where I sell custom-made wall decor.",
395-
"dates": "2020 – Present",
419+
"date": {
420+
"start": { "year": 2020 },
421+
"present": true
422+
},
396423
"tags": ["Entrepreneurship", "Design"],
397424
"projectPage": {
398425
"url": "/projects/customdefinitions.html",
@@ -412,7 +439,9 @@
412439
"image": "/images/Shortcut Logo Icon.png",
413440
"alt": "Apple Shortcuts logo",
414441
"description": "An Apple shortcut to take and send photos and videos that you don't want around forever.",
415-
"dates": "2023",
442+
"date": {
443+
"start": { "month": 4, "year": 2023 }
444+
},
416445
"tags": ["Automation", "Productivity"],
417446
"projectPage": {
418447
"url": "/projects/temporary-media-shortcut.html",
@@ -431,7 +460,10 @@
431460
"image": "/images/website.png",
432461
"alt": "Image representing a website",
433462
"description": "A website built using HTML, CSS, and JavaScript.",
434-
"dates": "Sept. 2024 – Present",
463+
"date": {
464+
"start": { "month": 9, "year": 2024 },
465+
"present": true
466+
},
435467
"tags": ["Web Development", "HTML/CSS/JS", "Node.js", "Eleventy"],
436468
"projectPage": {
437469
"url": "/projects/personal-website.html",

src/assets/css/styles.min.css

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,9 @@ img {
348348

349349
/* Portfolio Section */
350350
.portfolio {
351-
column-width: 300px;
352-
column-gap: 20px;
351+
display: grid;
352+
grid-template-columns: repeat(3, minmax(0, 1fr));
353+
gap: 20px;
353354
}
354355
.portfolio.currently-building-list{display:flex;flex-direction:column;gap:1.5rem;width:100%;column-width:auto;column-gap:0}
355356

@@ -373,7 +374,7 @@ img {
373374
align-content: start;
374375
break-inside: avoid;
375376
-webkit-column-break-inside: avoid;
376-
margin-bottom: 20px;
377+
margin-bottom: 0;
377378
}
378379

379380
.project:hover {
@@ -481,9 +482,13 @@ footer p {
481482
}
482483

483484
.portfolio {
484-
column-count: 1;
485-
column-gap: 0;
486-
column-width: 100%;
485+
grid-template-columns: 1fr;
486+
}
487+
}
488+
489+
@media (max-width: 1100px) and (min-width: 769px) {
490+
.portfolio {
491+
grid-template-columns: repeat(2, minmax(0, 1fr));
487492
}
488493
}
489494

src/portfolio.njk

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ title: Portfolio
66
<h1 class="page-title">Projects & Proof-of-Concepts</h1>
77
<p class="section-lead">Some of my passion projects, extracurricular activities, and classwork—from published research to weekend prototypes.</p>
88

9+
{% set sortedProjects = projects | sortProjectsByDateDesc %}
910
{% set hasBuilding = false %}
10-
{% for project in projects %}
11+
{% for project in sortedProjects %}
1112
{% if project.currentlyBuilding %}
1213
{% set hasBuilding = true %}
1314
{% endif %}
@@ -22,7 +23,7 @@ title: Portfolio
2223
</div>
2324

2425
<div class="portfolio currently-building-list">
25-
{% for project in projects %}
26+
{% for project in sortedProjects %}
2627
{% if project.currentlyBuilding %}
2728
<div class="project project-wide">
2829
<div class="project-media">
@@ -36,8 +37,9 @@ title: Portfolio
3637
<span class="pill">Currently building</span>
3738
</div>
3839
<p class="project-name">{{ project.title }}</p>
39-
{% if project.dates %}
40-
<p class="project-date">{{ project.dates }}</p>
40+
{% set projectDateText = project | formatProjectDate %}
41+
{% if projectDateText %}
42+
<p class="project-date">{{ projectDateText }}</p>
4143
{% endif %}
4244
<p class="project-description">{{ project.description }}</p>
4345

@@ -74,7 +76,7 @@ title: Portfolio
7476
</div>
7577

7678
<div class="portfolio">
77-
{% for project in projects %}
79+
{% for project in sortedProjects %}
7880
{% if not project.currentlyBuilding %}
7981
<div class="project">
8082
<a>
@@ -83,8 +85,9 @@ title: Portfolio
8385

8486
<div class="project-details">
8587
<p class="project-name">{{ project.title }}</p>
86-
{% if project.dates %}
87-
<p class="project-date">{{ project.dates }}</p>
88+
{% set projectDateText = project | formatProjectDate %}
89+
{% if projectDateText %}
90+
<p class="project-date">{{ projectDateText }}</p>
8891
{% endif %}
8992
<p class="project-description">{{ project.description }}</p>
9093

src/projects/project-page.njk

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ permalink: "/projects/{{ project.slug }}/index.html"
1010
{# If project pages are disabled for this project, output a simple message #}
1111
{% if not project.projectPage or not project.projectPage.enabled %}
1212
<h1 class="page-title">{{ project.title }}</h1>
13-
{% if project.dates %}
14-
<p class="project-date">{{ project.dates }}</p>
13+
{% set projectDateText = project | formatProjectDate %}
14+
{% if projectDateText %}
15+
<p class="project-date">{{ projectDateText }}</p>
1516
{% endif %}
1617
<p class="section-lead">This project page isn’t available yet.</p>
1718
<div class="cta-row">
@@ -21,8 +22,9 @@ permalink: "/projects/{{ project.slug }}/index.html"
2122
{% else %}
2223

2324
<h1 class="page-title">{{ project.title }}</h1>
24-
{% if project.dates %}
25-
<p class="project-date">{{ project.dates }}</p>
25+
{% set projectDateText = project | formatProjectDate %}
26+
{% if projectDateText %}
27+
<p class="project-date">{{ projectDateText }}</p>
2628
{% endif %}
2729
<p class="section-lead">{{ project.description }}</p>
2830

0 commit comments

Comments
 (0)