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