jsonresume-theme-investor-brief 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.
Files changed (3) hide show
  1. package/index.js +24 -0
  2. package/package.json +15 -0
  3. package/src/Resume.jsx +555 -0
package/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { renderToString } from 'react-dom/server';
3
+ import { ServerStyleSheet } from 'styled-components';
4
+ import Resume from './src/Resume.jsx';
5
+
6
+ export function render(resume) {
7
+ const sheet = new ServerStyleSheet();
8
+ const html = renderToString(sheet.collectStyles(<Resume resume={resume} />));
9
+ const styles = sheet.getStyleTags();
10
+ const title = (resume.basics && resume.basics.name) || 'Resume';
11
+
12
+ return `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <title>${title}</title>
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <link rel="preconnect" href="https://fonts.googleapis.com">
19
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
20
+ ${styles}
21
+ </head>
22
+ <body>${html}</body>
23
+ </html>`;
24
+ }
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "jsonresume-theme-investor-brief",
3
+ "version": "0.1.0",
4
+ "description": "Crisp, analytical, results-focused theme resembling a fund report. Data-first with Key Metrics section.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "peerDependencies": {
8
+ "react": "^18.0.0 || ^19.0.0",
9
+ "react-dom": "^18.0.0 || ^19.0.0"
10
+ },
11
+ "dependencies": {
12
+ "styled-components": "^6.1.19",
13
+ "@resume/core": "0.1.0"
14
+ }
15
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,555 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import {
4
+ Section,
5
+ SectionTitle,
6
+ DateRange,
7
+ ContactInfo,
8
+ Link,
9
+ safeUrl,
10
+ } from '@resume/core';
11
+
12
+ const Layout = styled.div`
13
+ max-width: 800px;
14
+ margin: 0 auto;
15
+ padding: 48px 40px;
16
+ background: #ffffff;
17
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
18
+ color: #1f2937;
19
+ line-height: 1.6;
20
+ font-size: 15px;
21
+
22
+ @media print {
23
+ padding: 24px;
24
+ }
25
+ `;
26
+
27
+ const Header = styled.header`
28
+ text-align: center;
29
+ margin-bottom: 40px;
30
+ padding-bottom: 32px;
31
+ border-bottom: 1px solid #e5e7eb;
32
+ `;
33
+
34
+ const Name = styled.h1`
35
+ font-size: 32px;
36
+ font-weight: 600;
37
+ margin: 0 0 8px 0;
38
+ color: #111827;
39
+ letter-spacing: -0.3px;
40
+ `;
41
+
42
+ const Label = styled.div`
43
+ font-size: 16px;
44
+ color: #6b7280;
45
+ font-weight: 400;
46
+ margin-bottom: 16px;
47
+ `;
48
+
49
+ const ContactWrapper = styled.div`
50
+ display: flex;
51
+ justify-content: center;
52
+ flex-wrap: wrap;
53
+ gap: 16px;
54
+ font-size: 14px;
55
+ color: #6b7280;
56
+ `;
57
+
58
+ const SummarySection = styled(Section)`
59
+ margin-bottom: 36px;
60
+ `;
61
+
62
+ const SummaryText = styled.p`
63
+ margin: 0;
64
+ font-size: 15px;
65
+ line-height: 1.7;
66
+ color: #374151;
67
+ text-align: left;
68
+ `;
69
+
70
+ const MainSection = styled(Section)`
71
+ margin-bottom: 36px;
72
+ padding-bottom: 32px;
73
+ border-bottom: 1px solid #e5e7eb;
74
+
75
+ &:last-child {
76
+ border-bottom: none;
77
+ }
78
+ `;
79
+
80
+ const MainSectionTitle = styled(SectionTitle)`
81
+ font-size: 13px;
82
+ font-weight: 600;
83
+ color: #111827;
84
+ margin: 0 0 20px 0;
85
+ text-transform: uppercase;
86
+ letter-spacing: 1.5px;
87
+ `;
88
+
89
+ const MetricsGrid = styled.div`
90
+ display: grid;
91
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
92
+ gap: 20px;
93
+ margin-bottom: 8px;
94
+ `;
95
+
96
+ const MetricCard = styled.div`
97
+ padding: 16px;
98
+ background: #f9fafb;
99
+ border-left: 2px solid #1f2937;
100
+ `;
101
+
102
+ const MetricLabel = styled.div`
103
+ font-size: 12px;
104
+ color: #6b7280;
105
+ text-transform: uppercase;
106
+ letter-spacing: 0.8px;
107
+ margin-bottom: 6px;
108
+ font-weight: 500;
109
+ `;
110
+
111
+ const MetricValue = styled.div`
112
+ font-size: 22px;
113
+ font-weight: 600;
114
+ color: #111827;
115
+ `;
116
+
117
+ const WorkItem = styled.div`
118
+ margin-bottom: 28px;
119
+
120
+ &:last-child {
121
+ margin-bottom: 0;
122
+ }
123
+ `;
124
+
125
+ const WorkHeader = styled.div`
126
+ display: flex;
127
+ justify-content: space-between;
128
+ align-items: baseline;
129
+ margin-bottom: 8px;
130
+ flex-wrap: wrap;
131
+ gap: 8px;
132
+ `;
133
+
134
+ const WorkTitle = styled.h3`
135
+ font-size: 17px;
136
+ font-weight: 600;
137
+ margin: 0;
138
+ color: #111827;
139
+ `;
140
+
141
+ const WorkMeta = styled.div`
142
+ font-size: 13px;
143
+ color: #9ca3af;
144
+ font-weight: 400;
145
+ `;
146
+
147
+ const WorkCompany = styled.div`
148
+ font-size: 15px;
149
+ color: #4b5563;
150
+ font-weight: 500;
151
+ margin-bottom: 10px;
152
+ `;
153
+
154
+ const WorkDescription = styled.p`
155
+ font-size: 15px;
156
+ line-height: 1.6;
157
+ margin: 12px 0 0 0;
158
+ color: #374151;
159
+ `;
160
+
161
+ const WorkHighlights = styled.ul`
162
+ margin: 12px 0 0 0;
163
+ padding-left: 20px;
164
+ list-style: none;
165
+
166
+ li {
167
+ margin-bottom: 8px;
168
+ font-size: 15px;
169
+ line-height: 1.6;
170
+ color: #374151;
171
+ position: relative;
172
+ padding-left: 4px;
173
+
174
+ &::before {
175
+ content: '•';
176
+ position: absolute;
177
+ left: -16px;
178
+ color: #1f2937;
179
+ font-weight: 600;
180
+ }
181
+ }
182
+ `;
183
+
184
+ const SkillsList = styled.div`
185
+ display: grid;
186
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
187
+ gap: 16px;
188
+ `;
189
+
190
+ const SkillItem = styled.div`
191
+ padding: 12px;
192
+ background: #f9fafb;
193
+ `;
194
+
195
+ const SkillName = styled.h4`
196
+ font-size: 13px;
197
+ font-weight: 600;
198
+ margin: 0 0 8px 0;
199
+ color: #111827;
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.5px;
202
+ `;
203
+
204
+ const SkillKeywords = styled.div`
205
+ font-size: 14px;
206
+ color: #4b5563;
207
+ line-height: 1.6;
208
+ `;
209
+
210
+ const SimpleList = styled.div`
211
+ display: grid;
212
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
213
+ gap: 16px;
214
+ `;
215
+
216
+ const SimpleCard = styled.div`
217
+ padding: 12px;
218
+ background: #f9fafb;
219
+ font-size: 14px;
220
+ line-height: 1.6;
221
+
222
+ strong {
223
+ font-weight: 600;
224
+ color: #111827;
225
+ }
226
+ `;
227
+
228
+ function Resume({ resume }) {
229
+ const {
230
+ basics = {},
231
+ work = [],
232
+ education = [],
233
+ skills = [],
234
+ projects = [],
235
+ volunteer = [],
236
+ awards = [],
237
+ publications = [],
238
+ languages = [],
239
+ interests = [],
240
+ references = [],
241
+ } = resume;
242
+
243
+ // Calculate key metrics from work experience
244
+ const calculateMetrics = () => {
245
+ const metrics = [];
246
+
247
+ // Total years of experience
248
+ if (work.length > 0) {
249
+ const totalYears = work.reduce((acc, job) => {
250
+ if (job.startDate) {
251
+ const start = new Date(job.startDate);
252
+ const end = job.endDate ? new Date(job.endDate) : new Date();
253
+ const years = (end - start) / (1000 * 60 * 60 * 24 * 365);
254
+ return acc + years;
255
+ }
256
+ return acc;
257
+ }, 0);
258
+ metrics.push({
259
+ label: 'Years Experience',
260
+ value: Math.round(totalYears),
261
+ });
262
+ }
263
+
264
+ // Companies worked at
265
+ if (work.length > 0) {
266
+ metrics.push({ label: 'Companies', value: work.length });
267
+ }
268
+
269
+ // Projects completed
270
+ if (projects.length > 0) {
271
+ metrics.push({ label: 'Projects', value: projects.length });
272
+ }
273
+
274
+ // Skills
275
+ if (skills.length > 0) {
276
+ const totalSkills = skills.reduce(
277
+ (acc, skill) => acc + (skill.keywords?.length || 0),
278
+ 0
279
+ );
280
+ metrics.push({ label: 'Core Skills', value: totalSkills });
281
+ }
282
+
283
+ return metrics;
284
+ };
285
+
286
+ const keyMetrics = calculateMetrics();
287
+
288
+ return (
289
+ <Layout>
290
+ <Header>
291
+ {basics.name && <Name>{basics.name}</Name>}
292
+ {basics.label && <Label>{basics.label}</Label>}
293
+ <ContactWrapper>
294
+ {basics.email && (
295
+ <ContactInfo type="email">{basics.email}</ContactInfo>
296
+ )}
297
+ {basics.phone && (
298
+ <ContactInfo type="phone">{basics.phone}</ContactInfo>
299
+ )}
300
+ {basics.location?.city && basics.location?.region && (
301
+ <ContactInfo type="location">
302
+ {basics.location.city}, {basics.location.region}
303
+ </ContactInfo>
304
+ )}
305
+ {basics.url && (
306
+ <ContactInfo type="url">
307
+ <a href={safeUrl(basics.url)}>{basics.url}</a>
308
+ </ContactInfo>
309
+ )}
310
+ {basics.profiles?.map((profile, index) => (
311
+ <ContactInfo key={index} type="social">
312
+ <a href={safeUrl(profile.url)}>{profile.network}</a>
313
+ </ContactInfo>
314
+ ))}
315
+ </ContactWrapper>
316
+ </Header>
317
+
318
+ {basics.summary && (
319
+ <SummarySection>
320
+ <SummaryText>{basics.summary}</SummaryText>
321
+ </SummarySection>
322
+ )}
323
+
324
+ {keyMetrics.length > 0 && (
325
+ <MainSection>
326
+ <MainSectionTitle>Key Metrics</MainSectionTitle>
327
+ <MetricsGrid>
328
+ {keyMetrics.map((metric, index) => (
329
+ <MetricCard key={index}>
330
+ <MetricLabel>{metric.label}</MetricLabel>
331
+ <MetricValue>{metric.value}</MetricValue>
332
+ </MetricCard>
333
+ ))}
334
+ </MetricsGrid>
335
+ </MainSection>
336
+ )}
337
+
338
+ {work.length > 0 && (
339
+ <MainSection>
340
+ <MainSectionTitle>Experience</MainSectionTitle>
341
+ {work.map((job, index) => (
342
+ <WorkItem key={index}>
343
+ <WorkHeader>
344
+ <WorkTitle>{job.position || job.name}</WorkTitle>
345
+ <WorkMeta>
346
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
347
+ </WorkMeta>
348
+ </WorkHeader>
349
+ {job.name && <WorkCompany>{job.name}</WorkCompany>}
350
+ {job.summary && <WorkDescription>{job.summary}</WorkDescription>}
351
+ {job.highlights && job.highlights.length > 0 && (
352
+ <WorkHighlights>
353
+ {job.highlights.map((highlight, i) => (
354
+ <li key={i}>{highlight}</li>
355
+ ))}
356
+ </WorkHighlights>
357
+ )}
358
+ </WorkItem>
359
+ ))}
360
+ </MainSection>
361
+ )}
362
+
363
+ {skills.length > 0 && (
364
+ <MainSection>
365
+ <MainSectionTitle>Skills</MainSectionTitle>
366
+ <SkillsList>
367
+ {skills.map((skill, index) => (
368
+ <SkillItem key={index}>
369
+ <SkillName>{skill.name}</SkillName>
370
+ {skill.keywords && skill.keywords.length > 0 && (
371
+ <SkillKeywords>{skill.keywords.join(', ')}</SkillKeywords>
372
+ )}
373
+ </SkillItem>
374
+ ))}
375
+ </SkillsList>
376
+ </MainSection>
377
+ )}
378
+
379
+ {projects.length > 0 && (
380
+ <MainSection>
381
+ <MainSectionTitle>Projects</MainSectionTitle>
382
+ {projects.map((project, index) => (
383
+ <WorkItem key={index}>
384
+ <WorkHeader>
385
+ <WorkTitle>
386
+ {project.url ? (
387
+ <Link href={safeUrl(project.url)}>{project.name}</Link>
388
+ ) : (
389
+ project.name
390
+ )}
391
+ </WorkTitle>
392
+ {(project.startDate || project.endDate) && (
393
+ <WorkMeta>
394
+ <DateRange
395
+ startDate={project.startDate}
396
+ endDate={project.endDate}
397
+ />
398
+ </WorkMeta>
399
+ )}
400
+ </WorkHeader>
401
+ {project.description && (
402
+ <WorkDescription>{project.description}</WorkDescription>
403
+ )}
404
+ {project.highlights && project.highlights.length > 0 && (
405
+ <WorkHighlights>
406
+ {project.highlights.map((highlight, i) => (
407
+ <li key={i}>{highlight}</li>
408
+ ))}
409
+ </WorkHighlights>
410
+ )}
411
+ </WorkItem>
412
+ ))}
413
+ </MainSection>
414
+ )}
415
+
416
+ {education.length > 0 && (
417
+ <MainSection>
418
+ <MainSectionTitle>Education</MainSectionTitle>
419
+ {education.map((edu, index) => (
420
+ <WorkItem key={index}>
421
+ <WorkHeader>
422
+ <WorkTitle>{edu.institution}</WorkTitle>
423
+ <WorkMeta>
424
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
425
+ </WorkMeta>
426
+ </WorkHeader>
427
+ {edu.studyType && edu.area && (
428
+ <WorkCompany>
429
+ {edu.studyType} in {edu.area}
430
+ </WorkCompany>
431
+ )}
432
+ {edu.score && <WorkDescription>GPA: {edu.score}</WorkDescription>}
433
+ </WorkItem>
434
+ ))}
435
+ </MainSection>
436
+ )}
437
+
438
+ {volunteer.length > 0 && (
439
+ <MainSection>
440
+ <MainSectionTitle>Volunteer</MainSectionTitle>
441
+ {volunteer.map((vol, index) => (
442
+ <WorkItem key={index}>
443
+ <WorkHeader>
444
+ <WorkTitle>{vol.position}</WorkTitle>
445
+ <WorkMeta>
446
+ <DateRange startDate={vol.startDate} endDate={vol.endDate} />
447
+ </WorkMeta>
448
+ </WorkHeader>
449
+ <WorkCompany>{vol.organization}</WorkCompany>
450
+ {vol.summary && <WorkDescription>{vol.summary}</WorkDescription>}
451
+ {vol.highlights && vol.highlights.length > 0 && (
452
+ <WorkHighlights>
453
+ {vol.highlights.map((highlight, i) => (
454
+ <li key={i}>{highlight}</li>
455
+ ))}
456
+ </WorkHighlights>
457
+ )}
458
+ </WorkItem>
459
+ ))}
460
+ </MainSection>
461
+ )}
462
+
463
+ {awards.length > 0 && (
464
+ <MainSection>
465
+ <MainSectionTitle>Awards</MainSectionTitle>
466
+ <SimpleList>
467
+ {awards.map((award, index) => (
468
+ <SimpleCard key={index}>
469
+ <strong>{award.title}</strong>
470
+ {award.awarder && ` — ${award.awarder}`}
471
+ {award.date && (
472
+ <div
473
+ style={{
474
+ fontSize: '13px',
475
+ color: '#9ca3af',
476
+ marginTop: '4px',
477
+ }}
478
+ >
479
+ {award.date}
480
+ </div>
481
+ )}
482
+ </SimpleCard>
483
+ ))}
484
+ </SimpleList>
485
+ </MainSection>
486
+ )}
487
+
488
+ {publications.length > 0 && (
489
+ <MainSection>
490
+ <MainSectionTitle>Publications</MainSectionTitle>
491
+ <SimpleList>
492
+ {publications.map((pub, index) => (
493
+ <SimpleCard key={index}>
494
+ <strong>{pub.name}</strong>
495
+ {pub.publisher && ` — ${pub.publisher}`}
496
+ {pub.releaseDate && (
497
+ <div
498
+ style={{
499
+ fontSize: '13px',
500
+ color: '#9ca3af',
501
+ marginTop: '4px',
502
+ }}
503
+ >
504
+ {pub.releaseDate}
505
+ </div>
506
+ )}
507
+ </SimpleCard>
508
+ ))}
509
+ </SimpleList>
510
+ </MainSection>
511
+ )}
512
+
513
+ {languages.length > 0 && (
514
+ <MainSection>
515
+ <MainSectionTitle>Languages</MainSectionTitle>
516
+ <SimpleList>
517
+ {languages.map((lang, index) => (
518
+ <SimpleCard key={index}>
519
+ <strong>{lang.language}</strong>
520
+ {lang.fluency && ` — ${lang.fluency}`}
521
+ </SimpleCard>
522
+ ))}
523
+ </SimpleList>
524
+ </MainSection>
525
+ )}
526
+
527
+ {interests.length > 0 && (
528
+ <MainSection>
529
+ <MainSectionTitle>Interests</MainSectionTitle>
530
+ <SimpleList>
531
+ {interests.map((interest, index) => (
532
+ <SimpleCard key={index}>{interest.name}</SimpleCard>
533
+ ))}
534
+ </SimpleList>
535
+ </MainSection>
536
+ )}
537
+
538
+ {references.length > 0 && (
539
+ <MainSection>
540
+ <MainSectionTitle>References</MainSectionTitle>
541
+ {references.map((ref, index) => (
542
+ <WorkItem key={index}>
543
+ <WorkTitle>{ref.name}</WorkTitle>
544
+ {ref.reference && (
545
+ <WorkDescription>{ref.reference}</WorkDescription>
546
+ )}
547
+ </WorkItem>
548
+ ))}
549
+ </MainSection>
550
+ )}
551
+ </Layout>
552
+ );
553
+ }
554
+
555
+ export default Resume;