Skip to content

Commit eca0860

Browse files
committed
Experiences and other data comes from a single json file
* Also updated the resume to work with the new data structure * Style updates to the resume to support downloading * Updated the schema for the portfolio
1 parent ae88926 commit eca0860

File tree

7 files changed

+264
-251
lines changed

7 files changed

+264
-251
lines changed

resume-generator/template.pug

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ html(lang="en")
4242
.a4-page
4343
.action-bar.no-print
4444
button(onclick="window.print()")
45-
svg(width='25' height='22' viewbox='0 0 25 22' fill='none' xmlns='http://www.w3.org/2000/svg')
46-
path(d='M19.6113 0.25C19.7975 0.250059 19.9749 0.325939 20.1045 0.458984C20.2338 0.591855 20.3055 0.770971 20.3057 0.956055V4.81543H22.1299C23.5658 4.81543 24.7499 5.94567 24.75 7.34766V16.4785C24.7499 16.6637 24.6782 16.8427 24.5488 16.9756C24.4192 17.1087 24.2419 17.1845 24.0557 17.1846H20.3057V21.0439C20.3055 21.229 20.2338 21.4081 20.1045 21.541C19.9749 21.6741 19.7975 21.7499 19.6113 21.75H5.38867C5.20246 21.7499 5.02513 21.6741 4.89551 21.541C4.76615 21.4081 4.69446 21.229 4.69434 21.0439V17.1846H0.944336C0.758061 17.1845 0.580822 17.1087 0.451172 16.9756C0.321767 16.8427 0.250065 16.6637 0.25 16.4785V7.34766C0.250096 5.94567 1.43415 4.81543 2.87012 4.81543H4.69434V0.956055C4.69446 0.77097 4.76615 0.591854 4.89551 0.458984C5.02513 0.325939 5.20246 0.250059 5.38867 0.25H19.6113ZM6.08301 20.3369H18.917V14.4453H6.08301V20.3369ZM2.87012 6.22852C2.17472 6.22852 1.63877 6.7486 1.63867 7.34766V15.7715H4.69434V13.7393C4.69434 13.5541 4.76612 13.3752 4.89551 13.2422C5.02514 13.109 5.20238 13.0323 5.38867 13.0322H19.6113C19.7976 13.0323 19.9749 13.109 20.1045 13.2422C20.2339 13.3752 20.3057 13.5541 20.3057 13.7393V15.7715H23.3613V7.34766C23.3612 6.7486 22.8253 6.22852 22.1299 6.22852H2.87012ZM19.167 8.46777C19.4711 8.46786 19.7617 8.5918 19.9746 8.81055C20.1873 9.02912 20.3057 9.32462 20.3057 9.63086C20.3056 9.85927 20.2394 10.0835 20.1152 10.2744C19.9909 10.4654 19.8132 10.6153 19.6045 10.7041C19.3956 10.7929 19.1655 10.8159 18.9434 10.7705C18.7212 10.7251 18.5182 10.6133 18.3594 10.4502C18.2007 10.2872 18.0933 10.0806 18.0498 9.85645C18.0064 9.63217 18.0279 9.39917 18.1133 9.1875C18.1987 8.97577 18.3439 8.79363 18.5312 8.66504C18.7187 8.53639 18.9402 8.46777 19.167 8.46777ZM6.08301 4.81543H18.917V1.66309H6.08301V4.81543Z' stroke-width='0.5')
45+
svg(xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='#000000' viewbox='0 0 256 256')
46+
path(d='M214.67,72H200V40a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8V72H41.33C27.36,72,16,82.77,16,96v80a8,8,0,0,0,8,8H56v32a8,8,0,0,0,8,8H192a8,8,0,0,0,8-8V184h32a8,8,0,0,0,8-8V96C240,82.77,228.64,72,214.67,72ZM72,48H184V72H72ZM184,208H72V160H184Zm40-40H200V152a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8v16H32V96c0-4.41,4.19-8,9.33-8H214.67c5.14,0,9.33,3.59,9.33,8Zm-24-52a12,12,0,1,1-12-12A12,12,0,0,1,200,116Z')
47+
a(download="" href="/Resume-CraigWayneGovender.pdf" target="_blank")
48+
svg(xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='#000000' viewbox='0 0 256 256')
49+
path(d='M224,144v64a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V144a8,8,0,0,1,16,0v56H208V144a8,8,0,0,1,16,0Zm-101.66,5.66a8,8,0,0,0,11.32,0l40-40a8,8,0,0,0-11.32-11.32L136,124.69V32a8,8,0,0,0-16,0v92.69L93.66,98.34a8,8,0,0,0-11.32,11.32Z')
4750
header
4851
h1 #{first_names} #{last_name}
4952
h2 #{latest_role}
@@ -61,20 +64,21 @@ html(lang="en")
6164
| #{contact.phone}
6265
span
6366
i.fab.fa-linkedin
64-
a(target="_blank" href=`https://www.linkedin.com/in/${contact.linkedin_username}`)
65-
| in/#{contact.linkedin_username}
67+
a(target="_blank" href=`https://www.linkedin.com/in/${social.linkedin}`)
68+
| in/#{social.linkedin}
6669
p.summary #{summary}
6770

6871
section.experience
6972
.title EMPLOYMENT
70-
each experience in work_experience
73+
// last 4 experience
74+
each experience in (work_experience.slice(0,4))
7175
.item
7276
header
7377
h4.title #{experience.position}
7478
.date
7579
| #{experience.period.start} - #{experience.period.end}
7680
.subtitle
77-
span.organization #{experience.organization}
81+
span.organization #{experience.organization.name}
7882
span.location #{experience.location.country}
7983
ul
8084
each responsibility in experience.responsibilities
@@ -101,7 +105,7 @@ html(lang="en")
101105
if index < sorted_skills.length - 1
102106
| ,
103107
.supported-brands.muted.no-print
104-
.title Supported Products
108+
.title ATS Friendly
105109
svg.active(viewbox='0 0 51.4 107.7' xmlns='http://www.w3.org/2000/svg')
106110
path(fill="#23A47F" d='M44.9 32c0 5.2-2.2 9.8-5.8 13.4-4 4-9.8 5-9.8 8.4 0 4.6 7.4 3.2 14.5 10.3 4.7 4.7 7.6 10.9 7.6 18.1 0 14.2-11.4 25.5-25.7 25.5S0 96.4 0 82.2C0 75 2.9 68.8 7.6 64.1c7.1-7.1 14.5-5.7 14.5-10.3 0-3.4-5.8-4.4-9.8-8.4-3.6-3.6-5.8-8.2-5.8-13.6C6.5 21.4 15 13 25.4 13c2 0 3.8.3 5.3.3 2.7 0 4.1-1.2 4.1-3.1 0-1.1-.5-2.5-.5-4 0-3.4 2.9-6.2 6.4-6.2S47 2.9 47 6.4c0 3.7-2.9 5.4-5.1 6.2-1.8.6-3.2 1.4-3.2 3.2C38.7 19.2 44.9 22.5 44.9 32zM42.9 82.2c0-9.9-7.3-17.9-17.2-17.9s-17.2 8-17.2 17.9c0 9.8 7.3 17.9 17.2 17.9S42.9 92 42.9 82.2zM37 31.8c0-6.3-5.1-11.5-11.3-11.5s-11.3 5.2-11.3 11.5S5.1 38.1 11.3 31.8z')
107111

src/components/Experience.tsx

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1-
import {Badge} from "./ui/badge";
21
import {motion} from "motion/react";
32
import {ImageWithFallback} from "./figma/ImageWithFallback";
43

54
interface Position {
6-
company: string;
7-
company_url: string;
8-
companyLogo: string;
5+
organization: {
6+
name: string,
7+
url: string,
8+
logo: string
9+
},
910
position: string;
10-
date_start: string;
11-
date_end?: string;
12-
location: string;
11+
period: {
12+
start: string,
13+
end?: string
14+
}
15+
location: {
16+
city: string
17+
country: string
18+
}
1319
description: string;
14-
achievements: string[];
20+
responsibilities: string[];
1521
technologies: string[];
1622
}
1723

@@ -23,10 +29,8 @@ interface ExperienceProps {
2329
}
2430
}
2531

26-
27-
// Function to extract and format start date from duration string
2832
function formatTimelineDate(position: Position): { year: number; month: string } {
29-
const start_date_obj = new Date(position.date_start);
33+
const start_date_obj = new Date(position.period.start);
3034
const month_index = start_date_obj.getMonth();
3135

3236
const month_lists = [
@@ -157,16 +161,13 @@ export function Experience({data}: ExperienceProps) {
157161
viewport={{once: true}}
158162
transition={{duration: 0.6}}
159163
>
160-
<Badge variant="outline" className="mb-4">Experience</Badge>
161164
<h2 className="mb-6">{data.subtitle}</h2>
162165
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
163166
{data.description}
164167
</p>
165168
</motion.div>
166169

167-
{/* Timeline Container */}
168170
<div className="relative">
169-
{/* Vertical Timeline Line - Hidden on mobile */}
170171
<div className="absolute left-8 top-0 bottom-0 w-0.5 bg-border overflow-hidden hidden md:block">
171172
<motion.div
172173
className="w-full bg-gradient-to-b from-primary via-primary to-primary/60"
@@ -177,7 +178,6 @@ export function Experience({data}: ExperienceProps) {
177178
/>
178179
</div>
179180

180-
{/* Timeline Items */}
181181
<motion.div
182182
className="space-y-8"
183183
variants={containerVariants}
@@ -194,7 +194,6 @@ export function Experience({data}: ExperienceProps) {
194194
variants={cardVariants}
195195
className="relative pl-0 md:pl-24"
196196
>
197-
{/* Timeline Date - Hidden on mobile */}
198197
<motion.div
199198
className="absolute left-0 top-6 flex flex-col items-center text-center min-w-16 hidden md:flex"
200199
variants={timelineDotVariants}
@@ -207,19 +206,13 @@ export function Experience({data}: ExperienceProps) {
207206
className="text-sm font-semibold text-foreground bg-background px-2 py-1 rounded">
208207
{timelineDate.month}
209208
</div>
210-
{/* Connection dot */}
211-
<div
212-
className="w-3 h-3 bg-primary rounded-full border-2 border-background shadow-sm mt-2"/>
213209
</motion.div>
214210

215-
{/* Experience Card */}
216211
<div
217212
className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-all duration-300 relative">
218-
{/* Card Connector Line */}
219213
<div
220214
className="absolute left-0 top-9 w-6 h-0.5 bg-border -translate-x-full hidden md:block"></div>
221215

222-
{/* Location in top right */}
223216
<motion.div
224217
className="absolute top-4 right-4"
225218
initial={{opacity: 0, x: 20}}
@@ -228,12 +221,11 @@ export function Experience({data}: ExperienceProps) {
228221
transition={{duration: 0.4, delay: 0.3}}
229222
>
230223
<p className="text-xs text-gray-500 bg-gray-50 px-2 py-1 rounded-md">
231-
{exp.location}
224+
{exp.location.city}, {exp.location.country}
232225
</p>
233226
</motion.div>
234227

235-
{/* Header with company logo and title */}
236-
<div className="flex items-start gap-4 mb-4 pr-20">
228+
<div className="flex items-start gap-4 pr-20">
237229
<motion.div
238230
className="w-14 h-14 rounded-xl flex items-center justify-center flex-shrink-0 border-2 border-gray-200 overflow-hidden bg-white"
239231
initial={{opacity: 0, scale: 0.5, rotate: -10}}
@@ -242,8 +234,8 @@ export function Experience({data}: ExperienceProps) {
242234
transition={{duration: 0.4, delay: 0.2}}
243235
>
244236
<ImageWithFallback
245-
src={exp.companyLogo}
246-
alt={`${exp.company} logo`}
237+
src={exp.organization.logo}
238+
alt={`${exp.organization.name} logo`}
247239
className="w-full h-full object-cover"
248240
/>
249241
</motion.div>
@@ -253,46 +245,39 @@ export function Experience({data}: ExperienceProps) {
253245
{exp.position}
254246
</h3>
255247
<p className="text-lg text-primary font-medium mb-2">
256-
<a href={exp.company_url} target="_blank">
257-
{exp.company}
248+
<a href={exp.organization.url} target="_blank">
249+
{exp.organization.name}
258250
</a>
259251
</p>
260252
</div>
261253
</div>
262254

263-
{/* Description and achievements as bullet points */}
264255
<motion.div
265256
className="space-y-3 mb-6"
266257
variants={bulletsContainerVariants}
267258
initial="hidden"
268259
whileInView="visible"
269260
viewport={{once: true}}
270261
>
271-
{/* Main description as first bullet */}
272262
<motion.div
273263
className="flex items-start gap-3"
274264
variants={bulletVariants}
275265
>
276-
<div
277-
className="w-2 h-2 bg-primary rounded-full mt-2 flex-shrink-0"></div>
278-
<p className="text-gray-700 leading-relaxed">{exp.description}</p>
279266
</motion.div>
280267

281-
{/* Achievements as additional bullets */}
282-
{exp.achievements.map((achievement, achIndex) => (
268+
{exp.responsibilities.map((responsibility, responsibility_index) => (
283269
<motion.div
284-
key={achIndex}
270+
key={responsibility_index}
285271
className="flex items-start gap-3"
286272
variants={bulletVariants}
287273
>
288274
<div
289275
className="w-2 h-2 bg-primary/70 rounded-full mt-2 flex-shrink-0"></div>
290-
<p className="text-gray-700 leading-relaxed">{achievement}</p>
276+
<p className="text-gray-700 leading-relaxed">{responsibility}</p>
291277
</motion.div>
292278
))}
293279
</motion.div>
294280

295-
{/* Technology badges */}
296281
<motion.div
297282
className="flex flex-wrap gap-2"
298283
variants={badgeContainerVariants}
@@ -315,15 +300,6 @@ export function Experience({data}: ExperienceProps) {
315300
);
316301
})}
317302
</motion.div>
318-
319-
{/* Timeline End Marker - Hidden on mobile */}
320-
<motion.div
321-
className="absolute left-6 -bottom-2 w-4 h-4 bg-gradient-to-br from-primary/60 to-primary/30 rounded-full border-4 border-background shadow-sm hidden md:block"
322-
initial={{scale: 0, opacity: 0}}
323-
whileInView={{scale: 1, opacity: 1}}
324-
viewport={{once: true}}
325-
transition={{duration: 0.4, delay: 1.5}}
326-
/>
327303
</div>
328304
</div>
329305
</section>

src/components/Hero.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import HeroProfileImage from './HeroProfileImage/HeroProfileImage';
77
interface HeroProps {
88
data: {
99
title: string;
10-
availabilityStatus: string;
11-
description: string;
10+
summary: string;
1211
profileImage: string;
1312
socialLinks: {
1413
github: string;
@@ -59,10 +58,7 @@ export function Hero({ data }: HeroProps) {
5958
animate="visible"
6059
>
6160
<motion.div className="space-y-2" variants={itemVariants}>
62-
<Badge variant="secondary" className="mb-4 mt-8 md:mt-0">
63-
{data.availabilityStatus}
64-
</Badge>
65-
<h1 className="text-4xl lg:text-5xl xl:text-6xl tracking-tight">
61+
<h1 className="text-4xl lg:text-5xl xl:text-6xl tracking-tight mb-4 mt-8 md:mt-0">
6662
{data.title.split(' ').slice(0, 2).join(' ')}
6763
<span className="block text-primary">{data.title.split(' ').slice(2).join(' ')}</span>
6864
</h1>
@@ -72,7 +68,7 @@ export function Hero({ data }: HeroProps) {
7268
className="text-lg text-muted-foreground max-w-lg"
7369
variants={itemVariants}
7470
>
75-
{data.description}
71+
{data.summary}
7672
</motion.p>
7773

7874
<motion.div

0 commit comments

Comments
 (0)