jsonresume-theme-sales-hunter 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 ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "jsonresume-theme-sales-hunter",
3
+ "version": "0.1.0",
4
+ "description": "Sales professionals - numbers, quotas, performance metrics highly visible",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "jsonresume",
10
+ "theme",
11
+ "sales",
12
+ "performance",
13
+ "metrics",
14
+ "ats"
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,452 @@
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: 50px 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: 40px;
21
+ padding-bottom: 25px;
22
+ border-bottom: 3px solid #16a34a;
23
+ `;
24
+
25
+ const Name = styled.h1`
26
+ font-size: 48px;
27
+ font-weight: 800;
28
+ color: #111827;
29
+ margin: 0 0 6px 0;
30
+ letter-spacing: -0.8px;
31
+ text-transform: uppercase;
32
+ `;
33
+
34
+ const Label = styled.div`
35
+ font-size: 18px;
36
+ color: #16a34a;
37
+ margin-bottom: 18px;
38
+ font-weight: 700;
39
+ text-transform: uppercase;
40
+ letter-spacing: 0.5px;
41
+ `;
42
+
43
+ const StyledContactInfo = styled(ContactInfo)`
44
+ font-size: 14px;
45
+
46
+ a {
47
+ font-size: 14px;
48
+ color: #16a34a;
49
+ font-weight: 600;
50
+ }
51
+ `;
52
+
53
+ const Summary = styled.p`
54
+ font-size: 15px;
55
+ line-height: 1.7;
56
+ color: #4b5563;
57
+ margin: 18px 0 0 0;
58
+ `;
59
+
60
+ const StyledSectionTitle = styled(SectionTitle)`
61
+ font-size: 22px;
62
+ font-weight: 800;
63
+ color: #111827;
64
+ margin: 35px 0 20px 0;
65
+ padding-bottom: 10px;
66
+ border-bottom: 3px solid #16a34a;
67
+ text-transform: uppercase;
68
+ letter-spacing: 0.5px;
69
+ `;
70
+
71
+ const WorkItem = styled.div`
72
+ margin-bottom: 30px;
73
+ padding: 20px;
74
+ background: #f9fafb;
75
+ border-radius: 6px;
76
+ border-left: 4px solid #16a34a;
77
+
78
+ &:last-child {
79
+ margin-bottom: 0;
80
+ }
81
+ `;
82
+
83
+ const WorkHeader = styled.div`
84
+ display: flex;
85
+ justify-content: space-between;
86
+ align-items: baseline;
87
+ margin-bottom: 10px;
88
+ gap: 16px;
89
+
90
+ @media (max-width: 640px) {
91
+ flex-direction: column;
92
+ align-items: flex-start;
93
+ gap: 4px;
94
+ }
95
+ `;
96
+
97
+ const Position = styled.h3`
98
+ font-size: 19px;
99
+ font-weight: 700;
100
+ color: #111827;
101
+ margin: 0;
102
+ `;
103
+
104
+ const Company = styled.div`
105
+ font-size: 17px;
106
+ color: #16a34a;
107
+ font-weight: 700;
108
+ margin-top: 4px;
109
+ `;
110
+
111
+ const DateText = styled.div`
112
+ font-size: 14px;
113
+ color: #6b7280;
114
+ font-weight: 600;
115
+ white-space: nowrap;
116
+ `;
117
+
118
+ const WorkSummary = styled.p`
119
+ margin: 12px 0;
120
+ color: #4b5563;
121
+ line-height: 1.7;
122
+ font-size: 14px;
123
+ `;
124
+
125
+ const Highlights = styled.ul`
126
+ margin: 12px 0 0 0;
127
+ padding-left: 0;
128
+ list-style-type: none;
129
+
130
+ li {
131
+ margin: 10px 0;
132
+ color: #1f2937;
133
+ line-height: 1.7;
134
+ padding-left: 28px;
135
+ position: relative;
136
+ font-size: 14px;
137
+ font-weight: 600;
138
+
139
+ &::before {
140
+ content: '▶';
141
+ position: absolute;
142
+ left: 0;
143
+ color: #16a34a;
144
+ font-size: 12px;
145
+ font-weight: 700;
146
+ }
147
+
148
+ strong {
149
+ color: #16a34a;
150
+ font-weight: 800;
151
+ font-size: 15px;
152
+ }
153
+ }
154
+ `;
155
+
156
+ const EducationItem = styled.div`
157
+ margin-bottom: 22px;
158
+ padding: 18px;
159
+ background: #f9fafb;
160
+ border-radius: 6px;
161
+ border-left: 4px solid #16a34a;
162
+
163
+ &:last-child {
164
+ margin-bottom: 0;
165
+ }
166
+ `;
167
+
168
+ const Institution = styled.h3`
169
+ font-size: 17px;
170
+ font-weight: 700;
171
+ color: #111827;
172
+ margin: 0 0 6px 0;
173
+ `;
174
+
175
+ const Degree = styled.div`
176
+ font-size: 15px;
177
+ color: #4b5563;
178
+ margin-bottom: 4px;
179
+ font-weight: 600;
180
+ `;
181
+
182
+ const EducationDate = styled.div`
183
+ font-size: 14px;
184
+ color: #6b7280;
185
+ font-weight: 600;
186
+ `;
187
+
188
+ const SkillsGrid = styled.div`
189
+ display: grid;
190
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
191
+ gap: 16px;
192
+ `;
193
+
194
+ const SkillCategory = styled.div`
195
+ padding: 16px;
196
+ background: #16a34a;
197
+ border-radius: 6px;
198
+ color: white;
199
+ `;
200
+
201
+ const SkillName = styled.h4`
202
+ font-size: 15px;
203
+ font-weight: 800;
204
+ color: white;
205
+ margin: 0 0 8px 0;
206
+ text-transform: uppercase;
207
+ letter-spacing: 0.5px;
208
+ `;
209
+
210
+ const SkillTags = styled.div`
211
+ font-size: 13px;
212
+ color: rgba(255, 255, 255, 0.95);
213
+ line-height: 1.6;
214
+ font-weight: 600;
215
+ `;
216
+
217
+ function Resume({ resume }) {
218
+ const {
219
+ basics = {},
220
+ work = [],
221
+ education = [],
222
+ skills = [],
223
+ projects = [],
224
+ volunteer = [],
225
+ awards = [],
226
+ publications = [],
227
+ languages = [],
228
+ interests = [],
229
+ references = [],
230
+ } = resume;
231
+
232
+ const enhanceHighlight = (text) => {
233
+ return text.replace(
234
+ /(\d+%|\$[\d,]+[KkMm]?|\d+[Xx])/g,
235
+ '<strong>$1</strong>'
236
+ );
237
+ };
238
+
239
+ return (
240
+ <Layout>
241
+ <Header>
242
+ <Name>{basics.name}</Name>
243
+ {basics.label && <Label>{basics.label}</Label>}
244
+ <StyledContactInfo basics={basics} />
245
+ {basics.summary && <Summary>{basics.summary}</Summary>}
246
+ </Header>
247
+
248
+ {work?.length > 0 && (
249
+ <Section>
250
+ <StyledSectionTitle>Experience</StyledSectionTitle>
251
+ {work.map((job, index) => (
252
+ <WorkItem key={index}>
253
+ <WorkHeader>
254
+ <div>
255
+ <Position>{job.position}</Position>
256
+ {job.name && <Company>{job.name}</Company>}
257
+ </div>
258
+ <DateText>
259
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
260
+ </DateText>
261
+ </WorkHeader>
262
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
263
+ {job.highlights?.length > 0 && (
264
+ <Highlights>
265
+ {job.highlights.map((highlight, i) => (
266
+ <li
267
+ key={i}
268
+ dangerouslySetInnerHTML={{
269
+ __html: enhanceHighlight(highlight),
270
+ }}
271
+ />
272
+ ))}
273
+ </Highlights>
274
+ )}
275
+ </WorkItem>
276
+ ))}
277
+ </Section>
278
+ )}
279
+
280
+ {skills?.length > 0 && (
281
+ <Section>
282
+ <StyledSectionTitle>Skills</StyledSectionTitle>
283
+ <SkillsGrid>
284
+ {skills.map((skill, index) => (
285
+ <SkillCategory key={index}>
286
+ <SkillName>{skill.name}</SkillName>
287
+ {skill.keywords?.length > 0 && (
288
+ <SkillTags>{skill.keywords.join(', ')}</SkillTags>
289
+ )}
290
+ </SkillCategory>
291
+ ))}
292
+ </SkillsGrid>
293
+ </Section>
294
+ )}
295
+
296
+ {education?.length > 0 && (
297
+ <Section>
298
+ <StyledSectionTitle>Education</StyledSectionTitle>
299
+ {education.map((edu, index) => (
300
+ <EducationItem key={index}>
301
+ <Institution>{edu.institution}</Institution>
302
+ <Degree>
303
+ {edu.studyType} in {edu.area}
304
+ {edu.score && ` • ${edu.score}`}
305
+ </Degree>
306
+ <EducationDate>
307
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
308
+ </EducationDate>
309
+ </EducationItem>
310
+ ))}
311
+ </Section>
312
+ )}
313
+
314
+ {projects?.length > 0 && (
315
+ <Section>
316
+ <StyledSectionTitle>Projects</StyledSectionTitle>
317
+ {projects.map((project, index) => (
318
+ <WorkItem key={index}>
319
+ <Position>{project.name}</Position>
320
+ {project.description && (
321
+ <WorkSummary>{project.description}</WorkSummary>
322
+ )}
323
+ {project.highlights?.length > 0 && (
324
+ <Highlights>
325
+ {project.highlights.map((highlight, i) => (
326
+ <li
327
+ key={i}
328
+ dangerouslySetInnerHTML={{
329
+ __html: enhanceHighlight(highlight),
330
+ }}
331
+ />
332
+ ))}
333
+ </Highlights>
334
+ )}
335
+ </WorkItem>
336
+ ))}
337
+ </Section>
338
+ )}
339
+
340
+ {volunteer?.length > 0 && (
341
+ <Section>
342
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
343
+ {volunteer.map((vol, index) => (
344
+ <WorkItem key={index}>
345
+ <WorkHeader>
346
+ <div>
347
+ <Position>{vol.position}</Position>
348
+ {vol.organization && <Company>{vol.organization}</Company>}
349
+ </div>
350
+ {(vol.startDate || vol.endDate) && (
351
+ <DateText>
352
+ <DateRange
353
+ startDate={vol.startDate}
354
+ endDate={vol.endDate}
355
+ />
356
+ </DateText>
357
+ )}
358
+ </WorkHeader>
359
+ {vol.summary && <WorkSummary>{vol.summary}</WorkSummary>}
360
+ {vol.highlights?.length > 0 && (
361
+ <Highlights>
362
+ {vol.highlights.map((highlight, i) => (
363
+ <li
364
+ key={i}
365
+ dangerouslySetInnerHTML={{
366
+ __html: enhanceHighlight(highlight),
367
+ }}
368
+ />
369
+ ))}
370
+ </Highlights>
371
+ )}
372
+ </WorkItem>
373
+ ))}
374
+ </Section>
375
+ )}
376
+
377
+ {awards?.length > 0 && (
378
+ <Section>
379
+ <StyledSectionTitle>Awards</StyledSectionTitle>
380
+ {awards.map((award, index) => (
381
+ <EducationItem key={index}>
382
+ <Institution>{award.title}</Institution>
383
+ {award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
384
+ {award.date && <EducationDate>{award.date}</EducationDate>}
385
+ {award.summary && <WorkSummary>{award.summary}</WorkSummary>}
386
+ </EducationItem>
387
+ ))}
388
+ </Section>
389
+ )}
390
+
391
+ {publications?.length > 0 && (
392
+ <Section>
393
+ <StyledSectionTitle>Publications</StyledSectionTitle>
394
+ {publications.map((pub, index) => (
395
+ <EducationItem key={index}>
396
+ <Institution>{pub.name}</Institution>
397
+ {pub.publisher && <Degree>Published by {pub.publisher}</Degree>}
398
+ {pub.releaseDate && (
399
+ <EducationDate>{pub.releaseDate}</EducationDate>
400
+ )}
401
+ {pub.summary && <WorkSummary>{pub.summary}</WorkSummary>}
402
+ </EducationItem>
403
+ ))}
404
+ </Section>
405
+ )}
406
+
407
+ {languages?.length > 0 && (
408
+ <Section>
409
+ <StyledSectionTitle>Languages</StyledSectionTitle>
410
+ <SkillsGrid>
411
+ {languages.map((lang, index) => (
412
+ <SkillCategory key={index}>
413
+ <SkillName>{lang.language}</SkillName>
414
+ {lang.fluency && <SkillTags>{lang.fluency}</SkillTags>}
415
+ </SkillCategory>
416
+ ))}
417
+ </SkillsGrid>
418
+ </Section>
419
+ )}
420
+
421
+ {interests?.length > 0 && (
422
+ <Section>
423
+ <StyledSectionTitle>Interests</StyledSectionTitle>
424
+ <SkillsGrid>
425
+ {interests.map((interest, index) => (
426
+ <SkillCategory key={index}>
427
+ <SkillName>{interest.name}</SkillName>
428
+ {interest.keywords?.length > 0 && (
429
+ <SkillTags>{interest.keywords.join(', ')}</SkillTags>
430
+ )}
431
+ </SkillCategory>
432
+ ))}
433
+ </SkillsGrid>
434
+ </Section>
435
+ )}
436
+
437
+ {references?.length > 0 && (
438
+ <Section>
439
+ <StyledSectionTitle>References</StyledSectionTitle>
440
+ {references.map((ref, index) => (
441
+ <EducationItem key={index}>
442
+ <Institution>{ref.name}</Institution>
443
+ {ref.reference && <WorkSummary>{ref.reference}</WorkSummary>}
444
+ </EducationItem>
445
+ ))}
446
+ </Section>
447
+ )}
448
+ </Layout>
449
+ );
450
+ }
451
+
452
+ 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;600;700;800&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: #f5f5f5;
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
+ }