jsonresume-theme-product-manager-canvas 1.0.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 ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "jsonresume-theme-product-manager-canvas",
3
+ "version": "1.0.0",
4
+ "description": "Product Manager Canvas - Analytical resume theme focusing on metrics and impact",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "resume",
10
+ "cv",
11
+ "jsonresume-theme",
12
+ "product-manager",
13
+ "metrics"
14
+ ],
15
+ "peerDependencies": {
16
+ "react": "^18.0.0 || ^19.0.0",
17
+ "react-dom": "^18.0.0 || ^19.0.0"
18
+ },
19
+ "dependencies": {
20
+ "styled-components": "^6.1.19",
21
+ "@resume/core": "0.1.0"
22
+ }
23
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,427 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Section, SectionTitle, DateRange, ContactInfo } from '@resume/core';
4
+
5
+ const Layout = styled.div`
6
+ max-width: 850px;
7
+ margin: 0 auto;
8
+ padding: 60px 40px;
9
+ background: white;
10
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
11
+ sans-serif;
12
+ color: #1f2937;
13
+
14
+ @media print {
15
+ padding: 40px;
16
+ }
17
+ `;
18
+
19
+ const Header = styled.header`
20
+ margin-bottom: 50px;
21
+ padding-bottom: 30px;
22
+ border-bottom: 1px solid #e5e7eb;
23
+ `;
24
+
25
+ const Name = styled.h1`
26
+ font-size: 40px;
27
+ font-weight: 700;
28
+ color: #111827;
29
+ margin: 0 0 8px 0;
30
+ letter-spacing: -0.5px;
31
+ `;
32
+
33
+ const Label = styled.div`
34
+ font-size: 18px;
35
+ font-weight: 500;
36
+ color: #6b7280;
37
+ margin-bottom: 20px;
38
+ `;
39
+
40
+ const Summary = styled.p`
41
+ font-size: 15px;
42
+ line-height: 1.8;
43
+ color: #4b5563;
44
+ margin: 20px 0 0 0;
45
+ max-width: 700px;
46
+ `;
47
+
48
+ const StyledSectionTitle = styled(SectionTitle)`
49
+ font-size: 26px;
50
+ font-weight: 800;
51
+ color: #111827;
52
+ margin: 48px 0 28px 0;
53
+ padding-bottom: 12px;
54
+ border-bottom: 3px solid #7c3aed;
55
+
56
+ &:first-child {
57
+ margin-top: 0;
58
+ }
59
+ `;
60
+
61
+ const WorkItem = styled.div`
62
+ margin-bottom: 40px;
63
+ padding: 28px;
64
+ background: #fafafa;
65
+ border-radius: 8px;
66
+ border-left: 6px solid #7c3aed;
67
+
68
+ &:last-child {
69
+ margin-bottom: 0;
70
+ }
71
+
72
+ @media print {
73
+ background: #fafafa;
74
+ }
75
+ `;
76
+
77
+ const WorkHeader = styled.div`
78
+ display: flex;
79
+ justify-content: space-between;
80
+ align-items: flex-start;
81
+ margin-bottom: 14px;
82
+ flex-wrap: wrap;
83
+ gap: 12px;
84
+ `;
85
+
86
+ const Position = styled.h3`
87
+ font-size: 20px;
88
+ font-weight: 700;
89
+ color: #111827;
90
+ margin: 0;
91
+ `;
92
+
93
+ const Company = styled.div`
94
+ font-size: 17px;
95
+ color: #7c3aed;
96
+ font-weight: 600;
97
+ margin-top: 6px;
98
+ `;
99
+
100
+ const WorkDate = styled.div`
101
+ font-size: 15px;
102
+ color: #64748b;
103
+ font-weight: 500;
104
+ white-space: nowrap;
105
+ `;
106
+
107
+ const WorkSummary = styled.p`
108
+ margin: 16px 0;
109
+ color: #334155;
110
+ line-height: 1.75;
111
+ font-size: 15px;
112
+ `;
113
+
114
+ const ImpactLabel = styled.div`
115
+ font-size: 14px;
116
+ font-weight: 800;
117
+ color: #7c3aed;
118
+ margin: 24px 0 14px;
119
+ text-transform: uppercase;
120
+ letter-spacing: 1.2px;
121
+ `;
122
+
123
+ const Highlights = styled.ul`
124
+ margin: 0;
125
+ padding-left: 24px;
126
+ list-style-type: disc;
127
+
128
+ li {
129
+ margin: 10px 0;
130
+ color: #4b5563;
131
+ line-height: 1.75;
132
+ padding-left: 4px;
133
+ }
134
+ `;
135
+
136
+ const EducationItem = styled.div`
137
+ margin-bottom: 28px;
138
+ padding: 20px;
139
+ background: #fafafa;
140
+ border-radius: 8px;
141
+ border-left: 5px solid #7c3aed;
142
+
143
+ &:last-child {
144
+ margin-bottom: 0;
145
+ }
146
+
147
+ @media print {
148
+ background: #fafafa;
149
+ }
150
+ `;
151
+
152
+ const Institution = styled.h3`
153
+ font-size: 18px;
154
+ font-weight: 700;
155
+ color: #111827;
156
+ margin: 0 0 8px 0;
157
+ `;
158
+
159
+ const Degree = styled.div`
160
+ font-size: 16px;
161
+ color: #334155;
162
+ margin-bottom: 6px;
163
+ font-weight: 500;
164
+ `;
165
+
166
+ const EducationDate = styled.div`
167
+ font-size: 14px;
168
+ color: #64748b;
169
+ `;
170
+
171
+ const SkillsGrid = styled.div`
172
+ display: grid;
173
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
174
+ gap: 18px;
175
+ `;
176
+
177
+ const SkillCard = styled.div`
178
+ padding: 16px 18px;
179
+ background: white;
180
+ border-radius: 8px;
181
+ border: 2px solid #e9d5ff;
182
+ transition: all 0.2s ease;
183
+
184
+ &:hover {
185
+ border-color: #7c3aed;
186
+ box-shadow: 0 2px 8px rgba(124, 58, 237, 0.1);
187
+ }
188
+
189
+ @media print {
190
+ background: white;
191
+ border-color: #e9d5ff;
192
+ }
193
+ `;
194
+
195
+ const SkillName = styled.h4`
196
+ font-size: 16px;
197
+ font-weight: 700;
198
+ color: #111827;
199
+ margin: 0 0 10px 0;
200
+ `;
201
+
202
+ const SkillTags = styled.div`
203
+ font-size: 14px;
204
+ color: #334155;
205
+ line-height: 1.7;
206
+ `;
207
+
208
+ function Resume({ resume }) {
209
+ const {
210
+ basics = {},
211
+ work = [],
212
+ education = [],
213
+ skills = [],
214
+ projects = [],
215
+ volunteer = [],
216
+ awards = [],
217
+ publications = [],
218
+ languages = [],
219
+ interests = [],
220
+ references = [],
221
+ } = resume;
222
+
223
+ return (
224
+ <Layout>
225
+ <Header>
226
+ <Name>{basics.name}</Name>
227
+ {basics.label && <Label>{basics.label}</Label>}
228
+ <ContactInfo basics={basics} />
229
+ {basics.summary && <Summary>{basics.summary}</Summary>}
230
+ </Header>
231
+
232
+ {work?.length > 0 && (
233
+ <Section>
234
+ <StyledSectionTitle>Professional Experience</StyledSectionTitle>
235
+ {work.map((job, index) => (
236
+ <WorkItem key={index}>
237
+ <WorkHeader>
238
+ <div>
239
+ <Position>{job.position}</Position>
240
+ {job.name && <Company>{job.name}</Company>}
241
+ </div>
242
+ <WorkDate>
243
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
244
+ </WorkDate>
245
+ </WorkHeader>
246
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
247
+ {job.highlights?.length > 0 && (
248
+ <>
249
+ <ImpactLabel>Key Impact & Achievements</ImpactLabel>
250
+ <Highlights>
251
+ {job.highlights.map((highlight, i) => (
252
+ <li key={i}>{highlight}</li>
253
+ ))}
254
+ </Highlights>
255
+ </>
256
+ )}
257
+ </WorkItem>
258
+ ))}
259
+ </Section>
260
+ )}
261
+
262
+ {education?.length > 0 && (
263
+ <Section>
264
+ <StyledSectionTitle>Education</StyledSectionTitle>
265
+ {education.map((edu, index) => (
266
+ <EducationItem key={index}>
267
+ <Institution>{edu.institution}</Institution>
268
+ <Degree>
269
+ {edu.studyType} in {edu.area}
270
+ {edu.score && ` • ${edu.score}`}
271
+ </Degree>
272
+ <EducationDate>
273
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
274
+ </EducationDate>
275
+ </EducationItem>
276
+ ))}
277
+ </Section>
278
+ )}
279
+
280
+ {skills?.length > 0 && (
281
+ <Section>
282
+ <StyledSectionTitle>Skills & Expertise</StyledSectionTitle>
283
+ <SkillsGrid>
284
+ {skills.map((skill, index) => (
285
+ <SkillCard key={index}>
286
+ <SkillName>{skill.name}</SkillName>
287
+ {skill.keywords?.length > 0 && (
288
+ <SkillTags>{skill.keywords.join(' • ')}</SkillTags>
289
+ )}
290
+ </SkillCard>
291
+ ))}
292
+ </SkillsGrid>
293
+ </Section>
294
+ )}
295
+
296
+ {projects?.length > 0 && (
297
+ <Section>
298
+ <StyledSectionTitle>Key Projects</StyledSectionTitle>
299
+ {projects.map((project, index) => (
300
+ <WorkItem key={index}>
301
+ <Position>{project.name}</Position>
302
+ {project.description && (
303
+ <WorkSummary>{project.description}</WorkSummary>
304
+ )}
305
+ {project.highlights?.length > 0 && (
306
+ <>
307
+ <ImpactLabel>Project Outcomes</ImpactLabel>
308
+ <Highlights>
309
+ {project.highlights.map((highlight, i) => (
310
+ <li key={i}>{highlight}</li>
311
+ ))}
312
+ </Highlights>
313
+ </>
314
+ )}
315
+ </WorkItem>
316
+ ))}
317
+ </Section>
318
+ )}
319
+
320
+ {volunteer?.length > 0 && (
321
+ <Section>
322
+ <StyledSectionTitle>Volunteer Experience</StyledSectionTitle>
323
+ {volunteer.map((vol, index) => (
324
+ <WorkItem key={index}>
325
+ <WorkHeader>
326
+ <div>
327
+ <Position>{vol.position}</Position>
328
+ {vol.organization && <Company>{vol.organization}</Company>}
329
+ </div>
330
+ {(vol.startDate || vol.endDate) && (
331
+ <WorkDate>
332
+ <DateRange
333
+ startDate={vol.startDate}
334
+ endDate={vol.endDate}
335
+ />
336
+ </WorkDate>
337
+ )}
338
+ </WorkHeader>
339
+ {vol.summary && <WorkSummary>{vol.summary}</WorkSummary>}
340
+ {vol.highlights?.length > 0 && (
341
+ <Highlights>
342
+ {vol.highlights.map((highlight, i) => (
343
+ <li key={i}>{highlight}</li>
344
+ ))}
345
+ </Highlights>
346
+ )}
347
+ </WorkItem>
348
+ ))}
349
+ </Section>
350
+ )}
351
+
352
+ {awards?.length > 0 && (
353
+ <Section>
354
+ <StyledSectionTitle>Awards & Recognition</StyledSectionTitle>
355
+ {awards.map((award, index) => (
356
+ <EducationItem key={index}>
357
+ <Institution>{award.title}</Institution>
358
+ {award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
359
+ {award.date && <EducationDate>{award.date}</EducationDate>}
360
+ {award.summary && <WorkSummary>{award.summary}</WorkSummary>}
361
+ </EducationItem>
362
+ ))}
363
+ </Section>
364
+ )}
365
+
366
+ {publications?.length > 0 && (
367
+ <Section>
368
+ <StyledSectionTitle>Publications</StyledSectionTitle>
369
+ {publications.map((pub, index) => (
370
+ <EducationItem key={index}>
371
+ <Institution>{pub.name}</Institution>
372
+ {pub.publisher && <Degree>Published by {pub.publisher}</Degree>}
373
+ {pub.releaseDate && (
374
+ <EducationDate>{pub.releaseDate}</EducationDate>
375
+ )}
376
+ {pub.summary && <WorkSummary>{pub.summary}</WorkSummary>}
377
+ </EducationItem>
378
+ ))}
379
+ </Section>
380
+ )}
381
+
382
+ {languages?.length > 0 && (
383
+ <Section>
384
+ <StyledSectionTitle>Languages</StyledSectionTitle>
385
+ <SkillsGrid>
386
+ {languages.map((lang, index) => (
387
+ <SkillCard key={index}>
388
+ <SkillName>{lang.language}</SkillName>
389
+ {lang.fluency && <SkillTags>{lang.fluency}</SkillTags>}
390
+ </SkillCard>
391
+ ))}
392
+ </SkillsGrid>
393
+ </Section>
394
+ )}
395
+
396
+ {interests?.length > 0 && (
397
+ <Section>
398
+ <StyledSectionTitle>Interests</StyledSectionTitle>
399
+ <SkillsGrid>
400
+ {interests.map((interest, index) => (
401
+ <SkillCard key={index}>
402
+ <SkillName>{interest.name}</SkillName>
403
+ {interest.keywords?.length > 0 && (
404
+ <SkillTags>{interest.keywords.join(' • ')}</SkillTags>
405
+ )}
406
+ </SkillCard>
407
+ ))}
408
+ </SkillsGrid>
409
+ </Section>
410
+ )}
411
+
412
+ {references?.length > 0 && (
413
+ <Section>
414
+ <StyledSectionTitle>References</StyledSectionTitle>
415
+ {references.map((ref, index) => (
416
+ <EducationItem key={index}>
417
+ <Institution>{ref.name}</Institution>
418
+ {ref.reference && <WorkSummary>{ref.reference}</WorkSummary>}
419
+ </EducationItem>
420
+ ))}
421
+ </Section>
422
+ )}
423
+ </Layout>
424
+ );
425
+ }
426
+
427
+ export default Resume;
package/src/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import { renderToString } from 'react-dom/server';
2
+ import { ServerStyleSheet } from 'styled-components';
3
+ import Resume from './Resume.jsx';
4
+
5
+ export function render(resume) {
6
+ const sheet = new ServerStyleSheet();
7
+ const html = renderToString(sheet.collectStyles(<Resume resume={resume} />));
8
+ const styles = sheet.getStyleTags();
9
+ return `<!DOCTYPE html><head>
10
+ <title>${resume.basics.name} - Resume</title>
11
+ <meta charset="utf-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
13
+ <link rel="preconnect" href="https://fonts.googleapis.com">
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
16
+ ${styles}
17
+ </head><body>${html}</body></html>`;
18
+ }
19
+
20
+ export default { render };