Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6bd7796
feat(experience): add view all link to Experience section
CodingWithJiro Sep 16, 2026
9f13993
feat(certificates): add view all link to Certificate section
CodingWithJiro Sep 16, 2026
4aea103
feat(experience): create initial experience page content
CodingWithJiro Sep 16, 2026
d6e3b95
feat(certificates): create initial certificates page content
CodingWithJiro Sep 16, 2026
25deb54
feat(experience): import work experiences to experience page and show…
CodingWithJiro Sep 17, 2026
9a65d6a
fix(ui): normalize flex spacing in experience section of the homepage
CodingWithJiro Sep 18, 2026
8cd470b
refactor(experiences): rearrange data and create a WORK_EXPERIENCES v…
CodingWithJiro Sep 18, 2026
b96f8bf
feat(experience): add responsibilities array string property to WorkE…
CodingWithJiro Sep 18, 2026
1765e46
chore(type): update WorkExperience type and add responbilities property
CodingWithJiro Sep 18, 2026
d9cf41c
feat(experience): replace workExperiences with WORK_EXPERIENCES data …
CodingWithJiro Sep 18, 2026
27079fe
chore(type): add companyUrl property for WorkExperience type
CodingWithJiro Sep 18, 2026
870ce24
feat(experience): add companyUrl to each item in WORK_EXPERIENCES
CodingWithJiro Sep 18, 2026
0f969c6
feat(experience): implement card based experience UI
CodingWithJiro Sep 18, 2026
db1a381
feat(experience): style mobile view for experience page
CodingWithJiro Sep 18, 2026
8a811bb
feat(experience): adjust sizes for medium size viewport
CodingWithJiro Sep 19, 2026
50eacae
fix(experience): switch responsibility data to correct details
CodingWithJiro Sep 19, 2026
3221ba9
feat(a11y): add aria label and focus visible outline for company links
CodingWithJiro Sep 19, 2026
518fd7b
test(e2e): visitor can see the experience section
CodingWithJiro Sep 20, 2026
c80b037
test(e2e): visitor can view the Experience page
CodingWithJiro Sep 20, 2026
372fa4a
test(e2e): visitor can see the Experience page contents
CodingWithJiro Sep 20, 2026
2e780c8
test(e2e): visitor can see the latest work experience in Experience page
CodingWithJiro Sep 20, 2026
a643c33
test(e2e): visitor can view a company website in new tab
CodingWithJiro Sep 20, 2026
d5794e6
test(e2e): visitor can tab to the Experience view all link and open t…
CodingWithJiro Sep 20, 2026
ab5b5a5
test(e2e): visitor can tab to the Certificates view all link and open…
CodingWithJiro Sep 20, 2026
b7e8fd8
test(e2e): visitor can focus on the company website link with keyboard
CodingWithJiro Sep 20, 2026
6dac1b1
feat(certificates): show initial certificates UI
CodingWithJiro Sep 21, 2026
cc7bab3
feat(certificates): remove before and after pseudo elements for each …
CodingWithJiro Sep 23, 2026
3cfa5a8
feat(certificate): add imgUrl property in Certificate type
CodingWithJiro Sep 23, 2026
78a473c
chore(certificates): add certificate webp images to public
CodingWithJiro Sep 23, 2026
92f6a18
feat(certificates): add imgUrl properties to each CERTIFICATES item
CodingWithJiro Sep 23, 2026
3d90dfe
feat(certificates): add initial styling and layout to certificate cards
CodingWithJiro Sep 23, 2026
36130e1
chore(types): update Certificate type and add description property
CodingWithJiro Sep 24, 2026
cee683d
feat(experiences): add description details for each certification
CodingWithJiro Sep 24, 2026
ce8a0d6
feat(certificates): add description to each certificate card
CodingWithJiro Sep 24, 2026
bfc64d9
refactor(certificates): replace View Credentials button and just make…
CodingWithJiro Sep 25, 2026
5e37c06
fix(certificates): update certificate link UI icon to align with title
CodingWithJiro Sep 25, 2026
1f08bb4
feat(a11y): add hover and focus states on each certificate link
CodingWithJiro Sep 25, 2026
cf85bce
feat(certificates): make page responsive up to desktop view
CodingWithJiro Sep 25, 2026
1bcf177
feat(certificates): add Footer component to certificates page
CodingWithJiro Sep 25, 2026
3d067bb
feat(a11y): add aria hidden true to certificate icon links
CodingWithJiro Sep 25, 2026
b2af5b2
chore(package): install YARL package for lightbox certificate interac…
CodingWithJiro Sep 26, 2026
0282834
feat(certificates): create CertificateImage UI component and use YARL…
CodingWithJiro Sep 26, 2026
cc092f0
feat(certificates): integrate CertificateImage UI component in certif…
CodingWithJiro Sep 26, 2026
b8bc9df
feat(a11y): add focus visible state on each certificate image
CodingWithJiro Sep 26, 2026
e3b9866
test(e2e): refactor test for when visitor can open certificate link i…
CodingWithJiro Sep 26, 2026
92bd65b
test(e2e): visitor can see the Certificates section
CodingWithJiro Sep 26, 2026
dd1b871
test(e2e): visitor can view the Certificates page
CodingWithJiro Sep 26, 2026
5730d23
test(e2e): visitor can see the Certificates page contents
CodingWithJiro Sep 26, 2026
34dc182
test(e2e): visitor can see the latest certificate entry in Certificat…
CodingWithJiro Sep 26, 2026
3bfa79e
test(e2e): visitor can open the latest certificate link in new tab
CodingWithJiro Sep 26, 2026
c3ef6e1
test(e2e): visitor can view enlarged image of the latest certificate …
CodingWithJiro Sep 26, 2026
0e40358
refactor(e2e): remove unused urlLink variable
CodingWithJiro Sep 26, 2026
72076d7
fix(data): edit readTime for blog-5 from 8 min to 9 min
CodingWithJiro Sep 26, 2026
c671f5c
feat(experience): add Footer component to experience page
CodingWithJiro Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"react-icons": "^5.6.0",
"shadcn": "^4.6.0",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0"
"tw-animate-css": "^1.4.0",
"yet-another-react-lightbox": "^3.32.2"
},
"devDependencies": {
"@playwright/test": "^1.61.1",
Expand All @@ -58,4 +59,4 @@
"typescript": "^5",
"vitest": "^4.1.5"
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 83 additions & 0 deletions src/app/certificates/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Breadcrumb from '@/components/sections/breadcrumb';
import ThemeToggle from '@/components/theme/theme-toggle';
import Footer from '@/components/sections/footer';
import CertificateImage from '@/components/ui/certificate-image';
import { CERTIFICATES } from '@/data/experiences';
import { FiExternalLink } from 'react-icons/fi';
import Link from 'next/link';

export default function ExperiencePage() {
return (
<main
id="main"
className="mx-auto min-h-screen max-w-130 px-4 py-8 md:max-w-4xl"
>
<header className="mb-8 flex items-center justify-between">
<Breadcrumb items={[]} currentLabel="Certificates" />
<ThemeToggle />
</header>

<section className="mb-8">
<h1 className="mb-1 text-lg font-bold md:text-3xl">Certificates</h1>
<h2 className="mb-6 text-sm font-medium md:text-base">
List of all my professional certifications to date.
</h2>
</section>

<ul className="grid grid-cols-1 gap-x-3 gap-y-3 min-[520px]:grid-cols-2 md:grid-cols-3 md:gap-y-6">
{CERTIFICATES.map(
({
id,
title,
institution,
dateReceived,
urlLink,
imgUrl,
description,
}) => {
return (
<li
className="border-ring/80 focus-within:border-foreground has-hover:border-foreground bg-background text-foreground mx-auto flex max-w-57.5 flex-col rounded-lg border p-2 transition-colors duration-150 ease-in-out md:max-w-65"
key={id}
>
<CertificateImage
src={imgUrl}
title={title}
alt={`Elmar Chavez's certificate for ${title}.`}
/>

<Link
className="group mb-1 flex items-center gap-1 outline-0"
href={urlLink}
target="_blank"
aria-label={`See Elmar Chavez's ${title} certificate in new tab`}
>
<h3 className="text-foreground text-sm font-semibold decoration-1 underline-offset-4 transition-colors duration-150 ease-in-out group-hover:underline group-focus-visible:underline md:text-base">
{title}
</h3>
<FiExternalLink
className="text-muted-foreground group-hover:text-foreground group-focus-visible:text-foreground relative bottom-px transition-colors duration-150 ease-in-out"
size={14}
aria-hidden="true"
/>
</Link>

<p className="text-foreground text-[0.75rem] transition-colors duration-150 ease-in-out md:text-sm">
{institution}
</p>
<p className="text-muted-foreground mb-2 text-[0.70rem] transition-colors duration-150 ease-in-out md:text-[0.75rem]">
Issued {dateReceived}
</p>
<p className="text-muted-foreground mb-2 text-[0.70rem] transition-colors duration-150 ease-in-out md:text-[0.75rem]">
{description}
</p>
</li>
);
},
)}
</ul>

<Footer />
</main>
);
}
90 changes: 90 additions & 0 deletions src/app/experience/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Breadcrumb from '@/components/sections/breadcrumb';
import Footer from '@/components/sections/footer';
import ThemeToggle from '@/components/theme/theme-toggle';
import { WORK_EXPERIENCES } from '@/data/experiences';
import Link from 'next/link';
import { FiExternalLink } from 'react-icons/fi';

export default function ExperiencePage() {
return (
<main
id="main"
className="mx-auto min-h-screen max-w-130 px-4 py-8 md:max-w-4xl"
>
<header className="mb-8 flex items-center justify-between">
<Breadcrumb items={[]} currentLabel="Experience" />
<ThemeToggle />
</header>

<section className="mb-8">
<h1 className="mb-1 text-lg font-bold md:text-3xl">Experience</h1>
<h2 className="mb-6 text-sm font-medium md:text-base">
My professional experience as a software engineer.
</h2>
</section>

<ul className="before:bg-border/50 relative mx-auto flex max-w-125 flex-col gap-4 before:absolute before:top-4 before:bottom-4 before:left-1 before:w-px md:gap-8">
{WORK_EXPERIENCES.slice()
.reverse()
.map(
(
{
id,
startDate,
endDate,
position,
companyName,
companyUrl,
location,
responsibilities,
},
index,
) => {
return (
<li className="group relative pl-5" key={id}>
<span
className={`border-border group-hover:bg-foreground absolute top-3 -left-1 size-4 rounded-full border-2 transition-colors duration-150 ease-in-out ${index === 0 ? 'bg-foreground' : 'bg-background'}`}
aria-hidden="true"
/>

<article className="border-border/40 bg-card group-hover:border-border rounded-lg border p-4 transition-colors duration-150">
<h3 className="mb-2 text-base font-semibold md:text-lg">
{position}
</h3>
<div className="mb-1 flex items-center gap-1 text-sm md:gap-2 md:text-base">
<Link
className="focus-visible:outline-muted-foreground text-muted-foreground hover:text-foreground flex items-center gap-0.5 rounded-lg underline-offset-4 outline-2 outline-offset-4 outline-transparent hover:underline hover:transition-colors hover:duration-150 hover:ease-in-out focus-visible:transition-colors focus-visible:duration-150 focus-visible:ease-in-out"
aria-label={`Open ${companyName}'s official website to new tab.`}
href={companyUrl}
target="_blank"
>
<span className="text-foreground">{companyName}</span>
<FiExternalLink className="relative bottom-0.5 size-4" />
</Link>
<span>·</span>
<p className="text-[0.75rem] font-light md:text-sm">
{location}
</p>
</div>
<p className="text-muted-foreground mb-4 text-[0.75rem] md:text-sm">{`${startDate} - ${endDate}`}</p>

<ul className="marker:text-muted-foreground flex list-disc flex-col gap-0 text-[0.75rem] leading-4.5 tracking-wide marker:text-base md:gap-1 md:text-sm">
{responsibilities.map((responsibility) => {
return (
<li className="ml-4" key={responsibility}>
{responsibility}
</li>
);
})}
</ul>
</article>
</li>
);
},
)}
</ul>

<Footer />
</main>
);
}
21 changes: 14 additions & 7 deletions src/components/sections/experience.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { FiExternalLink } from 'react-icons/fi';
import { workExperiences, certificates } from '@/data/experiences';
import ViewAllLink from '../ui/view-all-link';
import Link from 'next/link';

const Experience = () => {
return (
<section className="border-border/40 bg-card flex shrink-0 flex-col gap-4 rounded-lg border p-4 transition-colors duration-150 ease-in-out min-[488px]:flex-row md:max-w-[clamp(16rem,-12rem+50vw,20rem)] md:flex-col md:gap-8">
<article>
<h2 className="mb-3 text-lg font-bold">Experience</h2>
<section className="border-border/40 bg-card flex shrink-0 flex-col gap-4 rounded-lg border p-4 transition-colors duration-150 ease-in-out min-[488px]:flex-row md:max-w-[20rem] md:flex-col md:gap-8">
<article className="min-w-0 flex-1">
<header className="mb-3 flex items-center justify-between">
<h2 className="text-lg font-bold">Experience</h2>
<ViewAllLink href="/experience" />
</header>

<ul className="flex flex-col gap-4 md:gap-8">
{workExperiences.map(
Expand All @@ -26,8 +30,11 @@ const Experience = () => {
</ul>
</article>

<article>
<h2 className="mb-3 text-lg font-bold">Certificates</h2>
<article className="min-w-0 flex-1">
<header className="mb-3 flex items-center justify-between">
<h2 className="text-lg font-bold">Certificates</h2>
<ViewAllLink href="/certificates" />
</header>

<ul className="flex flex-col gap-4">
{certificates.map(
Expand All @@ -38,13 +45,13 @@ const Experience = () => {
key={id}
>
<Link
className="group decoration-muted-foreground relative mb-1 inline-block text-[0.85rem] font-semibold decoration-1 underline-offset-4 outline-0 hover:underline focus-visible:underline"
className="group decoration-muted-foreground mb-1 flex items-center gap-1 text-[0.85rem] font-semibold decoration-1 underline-offset-4 outline-0 hover:underline focus-visible:underline"
href={urlLink}
target="_blank"
aria-label={`Open ${title} certificate`}
>
{title}
<FiExternalLink className="group-focus-visible:text-foreground text-muted-foreground group-hover:text-foreground absolute top-px -right-5" />
<FiExternalLink className="group-focus-visible:text-foreground text-muted-foreground group-hover:text-foreground relative bottom-px" />
</Link>
<p className="flex flex-col text-[0.75rem]">
<span>{institution}</span>
Expand Down
64 changes: 64 additions & 0 deletions src/components/ui/certificate-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import { useMemo, useState } from 'react';
import Image from 'next/image';
import Lightbox from 'yet-another-react-lightbox';
import 'yet-another-react-lightbox/styles.css';

interface CertificateImageProps {
src: string;
alt: string;
title: string;
}

export default function CertificateImage({
src,
alt,
title,
}: CertificateImageProps) {
const [open, setOpen] = useState(false);
const slides = useMemo(
() => [
{
src,
alt,
width: 1250,
height: 830,
},
],
[src, alt],
);

const handleClick = () => setOpen(true);
const handleClose = () => setOpen(false);

return (
<>
<button
className="focus-visible:outline-foreground mb-4 block w-full cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2"
type="button"
onClick={handleClick}
aria-label={`Enlarge and view ${title} certificate.`}
>
<Image
className="border-ring/20 border"
src={src}
alt={alt}
width={1250}
height={830}
loading="eager"
/>
</button>

<Lightbox
open={open}
close={handleClose}
slides={slides}
render={{
buttonPrev: () => null,
buttonNext: () => null,
}}
/>
</>
);
}
2 changes: 1 addition & 1 deletion src/data/blog-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const BLOGS: BlogType[] = [
description:
'My thoughts on AI dependency and why struggling through problems is still one of the best ways to combat cognitive atrophy and become a better engineer.',
publishDate: 'September 2026',
readTime: '8 min read',
readTime: '9 min read',
tags: ['#ai', '#programming', '#software'],
blogUrl: `/blog/${getSlug('The Slow and Quiet Cognitive Atrophy of a Modern Software Engineer')}`,
imgUrl: '/img/blog-5.webp',
Expand Down
Loading
Loading