jsonresume-theme-marketing-narrative 0.1.0
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.
- package/package.json +24 -0
- package/src/Resume.jsx +279 -0
- package/src/index.js +53 -0
- package/src/sections/EducationSection.jsx +31 -0
- package/src/sections/OtherSections.jsx +146 -0
- package/src/sections/ProjectsSection.jsx +33 -0
- package/src/sections/SkillsSection.jsx +29 -0
- package/src/sections/WorkSection.jsx +45 -0
- package/src/styles.js +225 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jsonresume-theme-marketing-narrative",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Marketing professionals - your narrative and campaigns are the focus",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"jsonresume",
|
|
10
|
+
"theme",
|
|
11
|
+
"marketing",
|
|
12
|
+
"narrative",
|
|
13
|
+
"creative",
|
|
14
|
+
"storytelling"
|
|
15
|
+
],
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
18
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"styled-components": "^6.1.19",
|
|
22
|
+
"@resume/core": "0.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/Resume.jsx
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { SectionTitle, ContactInfo } from '@resume/core';
|
|
4
|
+
import { WorkSection } from './sections/WorkSection.jsx';
|
|
5
|
+
import { SkillsSection } from './sections/SkillsSection.jsx';
|
|
6
|
+
import { ProjectsSection } from './sections/ProjectsSection.jsx';
|
|
7
|
+
import { EducationSection } from './sections/EducationSection.jsx';
|
|
8
|
+
import {
|
|
9
|
+
VolunteerSection,
|
|
10
|
+
AwardsSection,
|
|
11
|
+
PublicationsSection,
|
|
12
|
+
LanguagesSection,
|
|
13
|
+
InterestsSection,
|
|
14
|
+
ReferencesSection,
|
|
15
|
+
} from './sections/OtherSections.jsx';
|
|
16
|
+
|
|
17
|
+
// Styled components moved inline to fix webpack resolution
|
|
18
|
+
const Layout = styled.div`
|
|
19
|
+
max-width: 900px;
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
padding: 60px 50px;
|
|
22
|
+
background: white;
|
|
23
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
24
|
+
sans-serif;
|
|
25
|
+
color: #1f2937;
|
|
26
|
+
line-height: 1.9;
|
|
27
|
+
|
|
28
|
+
@media print {
|
|
29
|
+
padding: 40px;
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const Header = styled.header`
|
|
34
|
+
margin-bottom: 60px;
|
|
35
|
+
text-align: center;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const Name = styled.h1`
|
|
39
|
+
font-size: 48px;
|
|
40
|
+
font-weight: 700;
|
|
41
|
+
color: #111827;
|
|
42
|
+
margin: 0 0 12px 0;
|
|
43
|
+
letter-spacing: -0.5px;
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const Label = styled.div`
|
|
47
|
+
font-size: 18px;
|
|
48
|
+
color: #e11d48;
|
|
49
|
+
margin-bottom: 24px;
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
letter-spacing: 0.5px;
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const StyledContactInfo = styled(ContactInfo)`
|
|
56
|
+
font-size: 15px;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
margin-bottom: 30px;
|
|
59
|
+
|
|
60
|
+
a {
|
|
61
|
+
font-size: 15px;
|
|
62
|
+
color: #e11d48;
|
|
63
|
+
transition: opacity 0.2s;
|
|
64
|
+
|
|
65
|
+
&:hover {
|
|
66
|
+
opacity: 0.7;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const Summary = styled.div`
|
|
72
|
+
font-size: 17px;
|
|
73
|
+
line-height: 2;
|
|
74
|
+
color: #374151;
|
|
75
|
+
margin: 30px auto 0;
|
|
76
|
+
max-width: 800px;
|
|
77
|
+
font-weight: 400;
|
|
78
|
+
text-align: left;
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
export const StyledSectionTitle = styled(SectionTitle)`
|
|
82
|
+
font-size: 24px;
|
|
83
|
+
font-weight: 700;
|
|
84
|
+
color: #e11d48;
|
|
85
|
+
margin: 50px 0 30px 0;
|
|
86
|
+
padding-bottom: 12px;
|
|
87
|
+
border-bottom: 3px solid #e11d48;
|
|
88
|
+
text-transform: uppercase;
|
|
89
|
+
letter-spacing: 1px;
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
export const StoryItem = styled.div`
|
|
93
|
+
margin-bottom: 45px;
|
|
94
|
+
padding-bottom: 40px;
|
|
95
|
+
border-bottom: 1px solid #e5e7eb;
|
|
96
|
+
|
|
97
|
+
&:last-child {
|
|
98
|
+
margin-bottom: 0;
|
|
99
|
+
border-bottom: none;
|
|
100
|
+
padding-bottom: 0;
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
export const StoryHeader = styled.div`
|
|
105
|
+
margin-bottom: 20px;
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
export const Position = styled.h3`
|
|
109
|
+
font-size: 22px;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
color: #111827;
|
|
112
|
+
margin: 0 0 8px 0;
|
|
113
|
+
line-height: 1.4;
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
export const CompanyAndDate = styled.div`
|
|
117
|
+
display: flex;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
align-items: baseline;
|
|
120
|
+
gap: 16px;
|
|
121
|
+
margin-bottom: 16px;
|
|
122
|
+
|
|
123
|
+
@media (max-width: 640px) {
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
align-items: flex-start;
|
|
126
|
+
gap: 6px;
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
export const Company = styled.div`
|
|
131
|
+
font-size: 17px;
|
|
132
|
+
color: #e11d48;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
export const DateText = styled.div`
|
|
137
|
+
font-size: 15px;
|
|
138
|
+
color: #6b7280;
|
|
139
|
+
font-weight: 500;
|
|
140
|
+
white-space: nowrap;
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
export const Narrative = styled.div`
|
|
144
|
+
font-size: 16px;
|
|
145
|
+
line-height: 2;
|
|
146
|
+
color: #4b5563;
|
|
147
|
+
margin: 20px 0;
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
export const Achievements = styled.div`
|
|
151
|
+
margin: 20px 0 0 0;
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
export const AchievementItem = styled.div`
|
|
155
|
+
margin: 16px 0;
|
|
156
|
+
padding-left: 24px;
|
|
157
|
+
position: relative;
|
|
158
|
+
color: #374151;
|
|
159
|
+
line-height: 1.9;
|
|
160
|
+
font-size: 15px;
|
|
161
|
+
|
|
162
|
+
&:before {
|
|
163
|
+
content: '▸';
|
|
164
|
+
position: absolute;
|
|
165
|
+
left: 0;
|
|
166
|
+
color: #e11d48;
|
|
167
|
+
font-weight: 700;
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
export const EducationItem = styled.div`
|
|
172
|
+
margin-bottom: 32px;
|
|
173
|
+
padding-bottom: 30px;
|
|
174
|
+
border-bottom: 1px solid #f3f4f6;
|
|
175
|
+
|
|
176
|
+
&:last-child {
|
|
177
|
+
margin-bottom: 0;
|
|
178
|
+
border-bottom: none;
|
|
179
|
+
padding-bottom: 0;
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
export const Institution = styled.h3`
|
|
184
|
+
font-size: 20px;
|
|
185
|
+
font-weight: 600;
|
|
186
|
+
color: #111827;
|
|
187
|
+
margin: 0 0 10px 0;
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
export const Degree = styled.div`
|
|
191
|
+
font-size: 16px;
|
|
192
|
+
color: #4b5563;
|
|
193
|
+
margin-bottom: 8px;
|
|
194
|
+
line-height: 1.8;
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
export const EducationDate = styled.div`
|
|
198
|
+
font-size: 15px;
|
|
199
|
+
color: #6b7280;
|
|
200
|
+
font-weight: 500;
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
export const SkillsGrid = styled.div`
|
|
204
|
+
display: grid;
|
|
205
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
206
|
+
gap: 24px;
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
export const SkillCategory = styled.div`
|
|
210
|
+
padding: 20px;
|
|
211
|
+
background: #fef2f2;
|
|
212
|
+
border-radius: 8px;
|
|
213
|
+
border-left: 4px solid #e11d48;
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
export const SkillName = styled.h4`
|
|
217
|
+
font-size: 16px;
|
|
218
|
+
font-weight: 600;
|
|
219
|
+
color: #e11d48;
|
|
220
|
+
margin: 0 0 12px 0;
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
export const SkillTags = styled.div`
|
|
224
|
+
font-size: 15px;
|
|
225
|
+
color: #6b7280;
|
|
226
|
+
line-height: 1.8;
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
export const QuoteBlock = styled.blockquote`
|
|
230
|
+
font-size: 17px;
|
|
231
|
+
line-height: 2;
|
|
232
|
+
color: #4b5563;
|
|
233
|
+
font-style: italic;
|
|
234
|
+
margin: 20px 0;
|
|
235
|
+
padding: 24px 30px;
|
|
236
|
+
background: #fef2f2;
|
|
237
|
+
border-left: 4px solid #e11d48;
|
|
238
|
+
border-radius: 4px;
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
function Resume({ resume }) {
|
|
242
|
+
const {
|
|
243
|
+
basics = {},
|
|
244
|
+
work = [],
|
|
245
|
+
education = [],
|
|
246
|
+
skills = [],
|
|
247
|
+
projects = [],
|
|
248
|
+
volunteer = [],
|
|
249
|
+
awards = [],
|
|
250
|
+
publications = [],
|
|
251
|
+
languages = [],
|
|
252
|
+
interests = [],
|
|
253
|
+
references = [],
|
|
254
|
+
} = resume;
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<Layout>
|
|
258
|
+
<Header>
|
|
259
|
+
<Name>{basics.name}</Name>
|
|
260
|
+
{basics.label && <Label>{basics.label}</Label>}
|
|
261
|
+
<StyledContactInfo basics={basics} />
|
|
262
|
+
{basics.summary && <Summary>{basics.summary}</Summary>}
|
|
263
|
+
</Header>
|
|
264
|
+
|
|
265
|
+
<WorkSection work={work} />
|
|
266
|
+
<SkillsSection skills={skills} />
|
|
267
|
+
<ProjectsSection projects={projects} />
|
|
268
|
+
<EducationSection education={education} />
|
|
269
|
+
<VolunteerSection volunteer={volunteer} />
|
|
270
|
+
<AwardsSection awards={awards} />
|
|
271
|
+
<PublicationsSection publications={publications} />
|
|
272
|
+
<LanguagesSection languages={languages} />
|
|
273
|
+
<InterestsSection interests={interests} />
|
|
274
|
+
<ReferencesSection references={references} />
|
|
275
|
+
</Layout>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export default Resume;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderToString } from 'react-dom/server';
|
|
3
|
+
import { ServerStyleSheet } from 'styled-components';
|
|
4
|
+
import Resume from './Resume.jsx';
|
|
5
|
+
|
|
6
|
+
export function render(resume) {
|
|
7
|
+
const sheet = new ServerStyleSheet();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const html = renderToString(
|
|
11
|
+
sheet.collectStyles(<Resume resume={resume} />)
|
|
12
|
+
);
|
|
13
|
+
const styles = sheet.getStyleTags();
|
|
14
|
+
|
|
15
|
+
return `<!DOCTYPE html>
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="UTF-8">
|
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
20
|
+
<title>${resume.basics?.name || 'Resume'} - Resume</title>
|
|
21
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
22
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
23
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
24
|
+
${styles}
|
|
25
|
+
<style>
|
|
26
|
+
* {
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0;
|
|
30
|
+
}
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
background: #f9fafb;
|
|
35
|
+
}
|
|
36
|
+
@media print {
|
|
37
|
+
body {
|
|
38
|
+
background: white;
|
|
39
|
+
}
|
|
40
|
+
@page {
|
|
41
|
+
margin: 0.5in;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
${html}
|
|
48
|
+
</body>
|
|
49
|
+
</html>`;
|
|
50
|
+
} finally {
|
|
51
|
+
sheet.seal();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Section, DateRange } from '@resume/core';
|
|
3
|
+
import {
|
|
4
|
+
StyledSectionTitle,
|
|
5
|
+
EducationItem,
|
|
6
|
+
Institution,
|
|
7
|
+
Degree,
|
|
8
|
+
EducationDate,
|
|
9
|
+
} from '../Resume.jsx';
|
|
10
|
+
|
|
11
|
+
export function EducationSection({ education }) {
|
|
12
|
+
if (!education?.length) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Section>
|
|
16
|
+
<StyledSectionTitle>Education</StyledSectionTitle>
|
|
17
|
+
{education.map((edu, index) => (
|
|
18
|
+
<EducationItem key={index}>
|
|
19
|
+
<Institution>{edu.institution}</Institution>
|
|
20
|
+
<Degree>
|
|
21
|
+
{edu.studyType} in {edu.area}
|
|
22
|
+
{edu.score && ` • ${edu.score}`}
|
|
23
|
+
</Degree>
|
|
24
|
+
<EducationDate>
|
|
25
|
+
<DateRange startDate={edu.startDate} endDate={edu.endDate} />
|
|
26
|
+
</EducationDate>
|
|
27
|
+
</EducationItem>
|
|
28
|
+
))}
|
|
29
|
+
</Section>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Section, DateRange } from '@resume/core';
|
|
3
|
+
import {
|
|
4
|
+
StyledSectionTitle,
|
|
5
|
+
StoryItem,
|
|
6
|
+
StoryHeader,
|
|
7
|
+
Position,
|
|
8
|
+
CompanyAndDate,
|
|
9
|
+
Company,
|
|
10
|
+
DateText,
|
|
11
|
+
Narrative,
|
|
12
|
+
Achievements,
|
|
13
|
+
AchievementItem,
|
|
14
|
+
EducationItem,
|
|
15
|
+
Institution,
|
|
16
|
+
Degree,
|
|
17
|
+
EducationDate,
|
|
18
|
+
SkillsGrid,
|
|
19
|
+
SkillCategory,
|
|
20
|
+
SkillName,
|
|
21
|
+
SkillTags,
|
|
22
|
+
QuoteBlock,
|
|
23
|
+
} from '../Resume.jsx';
|
|
24
|
+
|
|
25
|
+
export function VolunteerSection({ volunteer }) {
|
|
26
|
+
if (!volunteer?.length) return null;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Section>
|
|
30
|
+
<StyledSectionTitle>Community Impact</StyledSectionTitle>
|
|
31
|
+
{volunteer.map((vol, index) => (
|
|
32
|
+
<StoryItem key={index}>
|
|
33
|
+
<StoryHeader>
|
|
34
|
+
<Position>{vol.position}</Position>
|
|
35
|
+
<CompanyAndDate>
|
|
36
|
+
{vol.organization && <Company>{vol.organization}</Company>}
|
|
37
|
+
{(vol.startDate || vol.endDate) && (
|
|
38
|
+
<DateText>
|
|
39
|
+
<DateRange startDate={vol.startDate} endDate={vol.endDate} />
|
|
40
|
+
</DateText>
|
|
41
|
+
)}
|
|
42
|
+
</CompanyAndDate>
|
|
43
|
+
</StoryHeader>
|
|
44
|
+
{vol.summary && <Narrative>{vol.summary}</Narrative>}
|
|
45
|
+
{vol.highlights?.length > 0 && (
|
|
46
|
+
<Achievements>
|
|
47
|
+
{vol.highlights.map((highlight, i) => (
|
|
48
|
+
<AchievementItem key={i}>{highlight}</AchievementItem>
|
|
49
|
+
))}
|
|
50
|
+
</Achievements>
|
|
51
|
+
)}
|
|
52
|
+
</StoryItem>
|
|
53
|
+
))}
|
|
54
|
+
</Section>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function AwardsSection({ awards }) {
|
|
59
|
+
if (!awards?.length) return null;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Section>
|
|
63
|
+
<StyledSectionTitle>Recognition</StyledSectionTitle>
|
|
64
|
+
{awards.map((award, index) => (
|
|
65
|
+
<EducationItem key={index}>
|
|
66
|
+
<Institution>{award.title}</Institution>
|
|
67
|
+
{award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
|
|
68
|
+
{award.date && <EducationDate>{award.date}</EducationDate>}
|
|
69
|
+
{award.summary && <Narrative>{award.summary}</Narrative>}
|
|
70
|
+
</EducationItem>
|
|
71
|
+
))}
|
|
72
|
+
</Section>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function PublicationsSection({ publications }) {
|
|
77
|
+
if (!publications?.length) return null;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Section>
|
|
81
|
+
<StyledSectionTitle>Published Work</StyledSectionTitle>
|
|
82
|
+
{publications.map((pub, index) => (
|
|
83
|
+
<EducationItem key={index}>
|
|
84
|
+
<Institution>{pub.name}</Institution>
|
|
85
|
+
{pub.publisher && <Degree>Published by {pub.publisher}</Degree>}
|
|
86
|
+
{pub.releaseDate && <EducationDate>{pub.releaseDate}</EducationDate>}
|
|
87
|
+
{pub.summary && <Narrative>{pub.summary}</Narrative>}
|
|
88
|
+
</EducationItem>
|
|
89
|
+
))}
|
|
90
|
+
</Section>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function LanguagesSection({ languages }) {
|
|
95
|
+
if (!languages?.length) return null;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Section>
|
|
99
|
+
<StyledSectionTitle>Languages</StyledSectionTitle>
|
|
100
|
+
<SkillsGrid>
|
|
101
|
+
{languages.map((lang, index) => (
|
|
102
|
+
<SkillCategory key={index}>
|
|
103
|
+
<SkillName>{lang.language}</SkillName>
|
|
104
|
+
{lang.fluency && <SkillTags>{lang.fluency}</SkillTags>}
|
|
105
|
+
</SkillCategory>
|
|
106
|
+
))}
|
|
107
|
+
</SkillsGrid>
|
|
108
|
+
</Section>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function InterestsSection({ interests }) {
|
|
113
|
+
if (!interests?.length) return null;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Section>
|
|
117
|
+
<StyledSectionTitle>Interests & Passions</StyledSectionTitle>
|
|
118
|
+
<SkillsGrid>
|
|
119
|
+
{interests.map((interest, index) => (
|
|
120
|
+
<SkillCategory key={index}>
|
|
121
|
+
<SkillName>{interest.name}</SkillName>
|
|
122
|
+
{interest.keywords?.length > 0 && (
|
|
123
|
+
<SkillTags>{interest.keywords.join(' • ')}</SkillTags>
|
|
124
|
+
)}
|
|
125
|
+
</SkillCategory>
|
|
126
|
+
))}
|
|
127
|
+
</SkillsGrid>
|
|
128
|
+
</Section>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function ReferencesSection({ references }) {
|
|
133
|
+
if (!references?.length) return null;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Section>
|
|
137
|
+
<StyledSectionTitle>Testimonials</StyledSectionTitle>
|
|
138
|
+
{references.map((ref, index) => (
|
|
139
|
+
<div key={index} style={{ marginBottom: '30px' }}>
|
|
140
|
+
<Institution>{ref.name}</Institution>
|
|
141
|
+
{ref.reference && <QuoteBlock>"{ref.reference}"</QuoteBlock>}
|
|
142
|
+
</div>
|
|
143
|
+
))}
|
|
144
|
+
</Section>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Section } from '@resume/core';
|
|
3
|
+
import {
|
|
4
|
+
StyledSectionTitle,
|
|
5
|
+
StoryItem,
|
|
6
|
+
Position,
|
|
7
|
+
Narrative,
|
|
8
|
+
Achievements,
|
|
9
|
+
AchievementItem,
|
|
10
|
+
} from '../Resume.jsx';
|
|
11
|
+
|
|
12
|
+
export function ProjectsSection({ projects }) {
|
|
13
|
+
if (!projects?.length) return null;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Section>
|
|
17
|
+
<StyledSectionTitle>Featured Campaigns & Projects</StyledSectionTitle>
|
|
18
|
+
{projects.map((project, index) => (
|
|
19
|
+
<StoryItem key={index}>
|
|
20
|
+
<Position>{project.name}</Position>
|
|
21
|
+
{project.description && <Narrative>{project.description}</Narrative>}
|
|
22
|
+
{project.highlights?.length > 0 && (
|
|
23
|
+
<Achievements>
|
|
24
|
+
{project.highlights.map((highlight, i) => (
|
|
25
|
+
<AchievementItem key={i}>{highlight}</AchievementItem>
|
|
26
|
+
))}
|
|
27
|
+
</Achievements>
|
|
28
|
+
)}
|
|
29
|
+
</StoryItem>
|
|
30
|
+
))}
|
|
31
|
+
</Section>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Section } from '@resume/core';
|
|
3
|
+
import {
|
|
4
|
+
StyledSectionTitle,
|
|
5
|
+
SkillsGrid,
|
|
6
|
+
SkillCategory,
|
|
7
|
+
SkillName,
|
|
8
|
+
SkillTags,
|
|
9
|
+
} from '../Resume.jsx';
|
|
10
|
+
|
|
11
|
+
export function SkillsSection({ skills }) {
|
|
12
|
+
if (!skills?.length) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Section>
|
|
16
|
+
<StyledSectionTitle>Toolkit</StyledSectionTitle>
|
|
17
|
+
<SkillsGrid>
|
|
18
|
+
{skills.map((skill, index) => (
|
|
19
|
+
<SkillCategory key={index}>
|
|
20
|
+
<SkillName>{skill.name}</SkillName>
|
|
21
|
+
{skill.keywords?.length > 0 && (
|
|
22
|
+
<SkillTags>{skill.keywords.join(' • ')}</SkillTags>
|
|
23
|
+
)}
|
|
24
|
+
</SkillCategory>
|
|
25
|
+
))}
|
|
26
|
+
</SkillsGrid>
|
|
27
|
+
</Section>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Section, DateRange } from '@resume/core';
|
|
3
|
+
import {
|
|
4
|
+
StyledSectionTitle,
|
|
5
|
+
StoryItem,
|
|
6
|
+
StoryHeader,
|
|
7
|
+
Position,
|
|
8
|
+
CompanyAndDate,
|
|
9
|
+
Company,
|
|
10
|
+
DateText,
|
|
11
|
+
Narrative,
|
|
12
|
+
Achievements,
|
|
13
|
+
AchievementItem,
|
|
14
|
+
} from '../Resume.jsx';
|
|
15
|
+
|
|
16
|
+
export function WorkSection({ work }) {
|
|
17
|
+
if (!work?.length) return null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Section>
|
|
21
|
+
<StyledSectionTitle>Professional Story</StyledSectionTitle>
|
|
22
|
+
{work.map((job, index) => (
|
|
23
|
+
<StoryItem key={index}>
|
|
24
|
+
<StoryHeader>
|
|
25
|
+
<Position>{job.position}</Position>
|
|
26
|
+
<CompanyAndDate>
|
|
27
|
+
{job.name && <Company>{job.name}</Company>}
|
|
28
|
+
<DateText>
|
|
29
|
+
<DateRange startDate={job.startDate} endDate={job.endDate} />
|
|
30
|
+
</DateText>
|
|
31
|
+
</CompanyAndDate>
|
|
32
|
+
</StoryHeader>
|
|
33
|
+
{job.summary && <Narrative>{job.summary}</Narrative>}
|
|
34
|
+
{job.highlights?.length > 0 && (
|
|
35
|
+
<Achievements>
|
|
36
|
+
{job.highlights.map((highlight, i) => (
|
|
37
|
+
<AchievementItem key={i}>{highlight}</AchievementItem>
|
|
38
|
+
))}
|
|
39
|
+
</Achievements>
|
|
40
|
+
)}
|
|
41
|
+
</StoryItem>
|
|
42
|
+
))}
|
|
43
|
+
</Section>
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/styles.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { SectionTitle, ContactInfo } from '@resume/core';
|
|
3
|
+
|
|
4
|
+
export const Layout = styled.div`
|
|
5
|
+
max-width: 900px;
|
|
6
|
+
margin: 0 auto;
|
|
7
|
+
padding: 60px 50px;
|
|
8
|
+
background: white;
|
|
9
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
10
|
+
sans-serif;
|
|
11
|
+
color: #1f2937;
|
|
12
|
+
line-height: 1.9;
|
|
13
|
+
|
|
14
|
+
@media print {
|
|
15
|
+
padding: 40px;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const Header = styled.header`
|
|
20
|
+
margin-bottom: 60px;
|
|
21
|
+
text-align: center;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export const Name = styled.h1`
|
|
25
|
+
font-size: 48px;
|
|
26
|
+
font-weight: 700;
|
|
27
|
+
color: #111827;
|
|
28
|
+
margin: 0 0 12px 0;
|
|
29
|
+
letter-spacing: -0.5px;
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
export const Label = styled.div`
|
|
33
|
+
font-size: 18px;
|
|
34
|
+
color: #e11d48;
|
|
35
|
+
margin-bottom: 24px;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
text-transform: uppercase;
|
|
38
|
+
letter-spacing: 0.5px;
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export const StyledContactInfo = styled(ContactInfo)`
|
|
42
|
+
font-size: 15px;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
margin-bottom: 30px;
|
|
45
|
+
|
|
46
|
+
a {
|
|
47
|
+
font-size: 15px;
|
|
48
|
+
color: #e11d48;
|
|
49
|
+
transition: opacity 0.2s;
|
|
50
|
+
|
|
51
|
+
&:hover {
|
|
52
|
+
opacity: 0.7;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
export const Summary = styled.div`
|
|
58
|
+
font-size: 17px;
|
|
59
|
+
line-height: 2;
|
|
60
|
+
color: #374151;
|
|
61
|
+
margin: 30px auto 0;
|
|
62
|
+
max-width: 800px;
|
|
63
|
+
font-weight: 400;
|
|
64
|
+
text-align: left;
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
export const StyledSectionTitle = styled(SectionTitle)`
|
|
68
|
+
font-size: 24px;
|
|
69
|
+
font-weight: 700;
|
|
70
|
+
color: #e11d48;
|
|
71
|
+
margin: 50px 0 30px 0;
|
|
72
|
+
padding-bottom: 12px;
|
|
73
|
+
border-bottom: 3px solid #e11d48;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
letter-spacing: 1px;
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
export const StoryItem = styled.div`
|
|
79
|
+
margin-bottom: 45px;
|
|
80
|
+
padding-bottom: 40px;
|
|
81
|
+
border-bottom: 1px solid #e5e7eb;
|
|
82
|
+
|
|
83
|
+
&:last-child {
|
|
84
|
+
margin-bottom: 0;
|
|
85
|
+
border-bottom: none;
|
|
86
|
+
padding-bottom: 0;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
export const StoryHeader = styled.div`
|
|
91
|
+
margin-bottom: 20px;
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
export const Position = styled.h3`
|
|
95
|
+
font-size: 22px;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
color: #111827;
|
|
98
|
+
margin: 0 0 8px 0;
|
|
99
|
+
line-height: 1.4;
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
export const CompanyAndDate = styled.div`
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: space-between;
|
|
105
|
+
align-items: baseline;
|
|
106
|
+
gap: 16px;
|
|
107
|
+
margin-bottom: 16px;
|
|
108
|
+
|
|
109
|
+
@media (max-width: 640px) {
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
align-items: flex-start;
|
|
112
|
+
gap: 6px;
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
export const Company = styled.div`
|
|
117
|
+
font-size: 17px;
|
|
118
|
+
color: #e11d48;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
export const DateText = styled.div`
|
|
123
|
+
font-size: 15px;
|
|
124
|
+
color: #6b7280;
|
|
125
|
+
font-weight: 500;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
export const Narrative = styled.div`
|
|
130
|
+
font-size: 16px;
|
|
131
|
+
line-height: 2;
|
|
132
|
+
color: #4b5563;
|
|
133
|
+
margin: 20px 0;
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
export const Achievements = styled.div`
|
|
137
|
+
margin: 20px 0 0 0;
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
export const AchievementItem = styled.div`
|
|
141
|
+
margin: 16px 0;
|
|
142
|
+
padding-left: 24px;
|
|
143
|
+
position: relative;
|
|
144
|
+
color: #374151;
|
|
145
|
+
line-height: 1.9;
|
|
146
|
+
font-size: 15px;
|
|
147
|
+
|
|
148
|
+
&:before {
|
|
149
|
+
content: '▸';
|
|
150
|
+
position: absolute;
|
|
151
|
+
left: 0;
|
|
152
|
+
color: #e11d48;
|
|
153
|
+
font-weight: 700;
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
export const EducationItem = styled.div`
|
|
158
|
+
margin-bottom: 32px;
|
|
159
|
+
padding-bottom: 30px;
|
|
160
|
+
border-bottom: 1px solid #f3f4f6;
|
|
161
|
+
|
|
162
|
+
&:last-child {
|
|
163
|
+
margin-bottom: 0;
|
|
164
|
+
border-bottom: none;
|
|
165
|
+
padding-bottom: 0;
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
export const Institution = styled.h3`
|
|
170
|
+
font-size: 20px;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
color: #111827;
|
|
173
|
+
margin: 0 0 10px 0;
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
export const Degree = styled.div`
|
|
177
|
+
font-size: 16px;
|
|
178
|
+
color: #4b5563;
|
|
179
|
+
margin-bottom: 8px;
|
|
180
|
+
line-height: 1.8;
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
export const EducationDate = styled.div`
|
|
184
|
+
font-size: 15px;
|
|
185
|
+
color: #6b7280;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
`;
|
|
188
|
+
|
|
189
|
+
export const SkillsGrid = styled.div`
|
|
190
|
+
display: grid;
|
|
191
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
192
|
+
gap: 24px;
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
export const SkillCategory = styled.div`
|
|
196
|
+
padding: 20px;
|
|
197
|
+
background: #fef2f2;
|
|
198
|
+
border-radius: 8px;
|
|
199
|
+
border-left: 4px solid #e11d48;
|
|
200
|
+
`;
|
|
201
|
+
|
|
202
|
+
export const SkillName = styled.h4`
|
|
203
|
+
font-size: 16px;
|
|
204
|
+
font-weight: 600;
|
|
205
|
+
color: #e11d48;
|
|
206
|
+
margin: 0 0 12px 0;
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
export const SkillTags = styled.div`
|
|
210
|
+
font-size: 15px;
|
|
211
|
+
color: #6b7280;
|
|
212
|
+
line-height: 1.8;
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
export const QuoteBlock = styled.blockquote`
|
|
216
|
+
font-size: 17px;
|
|
217
|
+
line-height: 2;
|
|
218
|
+
color: #4b5563;
|
|
219
|
+
font-style: italic;
|
|
220
|
+
margin: 20px 0;
|
|
221
|
+
padding: 24px 30px;
|
|
222
|
+
background: #fef2f2;
|
|
223
|
+
border-left: 4px solid #e11d48;
|
|
224
|
+
border-radius: 4px;
|
|
225
|
+
`;
|