jsonresume-theme-university-first 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/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # JSON Resume Theme - University First
2
+
3
+ **For fresh grads - education credentials come first, work second**
4
+
5
+ ## Overview
6
+
7
+ A professional, student-oriented resume theme designed for recent graduates and early-career applicants. This theme emphasizes educational achievements by placing the Education section first, followed by Skills, Projects, and then Work Experience.
8
+
9
+ ## Features
10
+
11
+ - **Education-First Layout**: Your academic credentials take center stage
12
+ - **Clean Typography**: Humanist sans-serif (Inter) at comfortable 11pt body text
13
+ - **School Blue Accent**: Professional #2563eb blue throughout
14
+ - **Scholarly Touch**: Serif fonts for institution names to emphasize academic credentials
15
+ - **White Background**: Clean, professional appearance
16
+ - **Single Column**: Easy to read and ATS-friendly
17
+ - **Comprehensive Sections**: Supports all 12 JSON Resume sections
18
+
19
+ ## Section Order
20
+
21
+ 1. Education
22
+ 2. Skills
23
+ 3. Projects
24
+ 4. Work Experience
25
+ 5. Volunteer Experience
26
+ 6. Awards & Honors
27
+ 7. Publications
28
+ 8. Languages
29
+ 9. Interests
30
+ 10. References
31
+
32
+ ## Design Philosophy
33
+
34
+ Lightweight and approachable design with bright blue accents and generous spacing. Balances youthful energy with professional readability - perfect for showcasing your academic journey while maintaining a polished, career-ready presentation.
35
+
36
+ ## Usage
37
+
38
+ This theme is part of the JSON Resume ecosystem and will be automatically available through the jsonresume.org platform.
39
+
40
+ ## License
41
+
42
+ MIT
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "jsonresume-theme-university-first",
3
+ "version": "0.1.0",
4
+ "description": "For fresh grads - education credentials come first, work second",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "jsonresume",
10
+ "theme",
11
+ "student",
12
+ "graduate",
13
+ "education",
14
+ "university",
15
+ "fresh-grad"
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,562 @@
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: 900px;
7
+ margin: 0 auto;
8
+ padding: 0;
9
+ background: linear-gradient(to bottom, #dbeafe 0%, #ffffff 300px);
10
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
11
+ sans-serif;
12
+ color: #1f2937;
13
+ font-size: 11pt;
14
+ line-height: 1.6;
15
+ min-height: 100vh;
16
+
17
+ @media print {
18
+ background: white;
19
+ min-height: auto;
20
+ }
21
+ `;
22
+
23
+ const Header = styled.header`
24
+ background: #2563eb;
25
+ color: white;
26
+ padding: 60px 50px;
27
+ text-align: center;
28
+ position: relative;
29
+
30
+ &::after {
31
+ content: '';
32
+ position: absolute;
33
+ bottom: -30px;
34
+ left: 50%;
35
+ transform: translateX(-50%);
36
+ width: 0;
37
+ height: 0;
38
+ border-left: 30px solid transparent;
39
+ border-right: 30px solid transparent;
40
+ border-top: 30px solid #2563eb;
41
+ }
42
+
43
+ @media print {
44
+ padding: 40px;
45
+ &::after {
46
+ display: none;
47
+ }
48
+ }
49
+ `;
50
+
51
+ const Name = styled.h1`
52
+ font-size: 48px;
53
+ font-weight: 800;
54
+ color: white;
55
+ margin: 0 0 12px 0;
56
+ letter-spacing: -0.5px;
57
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
58
+ `;
59
+
60
+ const Label = styled.div`
61
+ font-size: 18px;
62
+ color: #dbeafe;
63
+ margin-bottom: 20px;
64
+ font-weight: 600;
65
+ letter-spacing: 0.3px;
66
+ `;
67
+
68
+ const StyledContactInfo = styled(ContactInfo)`
69
+ font-size: 15px;
70
+ justify-content: center;
71
+ color: #dbeafe;
72
+
73
+ a {
74
+ font-size: 15px;
75
+ color: white;
76
+ text-decoration: none;
77
+
78
+ &:hover {
79
+ text-decoration: underline;
80
+ }
81
+ }
82
+ `;
83
+
84
+ const Content = styled.div`
85
+ padding: 60px 50px;
86
+
87
+ @media print {
88
+ padding: 40px;
89
+ }
90
+ `;
91
+
92
+ const Summary = styled.p`
93
+ font-size: 15px;
94
+ line-height: 1.8;
95
+ color: #475569;
96
+ margin: 0 0 40px 0;
97
+ text-align: center;
98
+ max-width: 700px;
99
+ margin-left: auto;
100
+ margin-right: auto;
101
+ background: white;
102
+ padding: 30px;
103
+ border-radius: 12px;
104
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
105
+ `;
106
+
107
+ const StyledSectionTitle = styled(SectionTitle)`
108
+ font-size: 22px;
109
+ font-weight: 700;
110
+ color: #2563eb;
111
+ margin: 0 0 24px 0;
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.5px;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 12px;
117
+
118
+ &::before {
119
+ content: '';
120
+ width: 4px;
121
+ height: 28px;
122
+ background: #2563eb;
123
+ border-radius: 2px;
124
+ }
125
+ `;
126
+
127
+ const EducationItem = styled.div`
128
+ margin-bottom: 20px;
129
+ background: white;
130
+ padding: 24px;
131
+ border-radius: 12px;
132
+ box-shadow: 0 2px 8px rgba(37, 99, 235, 0.08);
133
+ border-left: 4px solid #2563eb;
134
+ transition: all 0.2s ease;
135
+
136
+ &:hover {
137
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.15);
138
+ transform: translateX(4px);
139
+ }
140
+
141
+ &:last-child {
142
+ margin-bottom: 0;
143
+ }
144
+ `;
145
+
146
+ const Institution = styled.h3`
147
+ font-size: 19px;
148
+ font-weight: 700;
149
+ color: #1e40af;
150
+ margin: 0 0 8px 0;
151
+ `;
152
+
153
+ const Degree = styled.div`
154
+ font-size: 16px;
155
+ color: #374151;
156
+ margin-bottom: 6px;
157
+ font-weight: 600;
158
+ `;
159
+
160
+ const EducationMeta = styled.div`
161
+ font-size: 14px;
162
+ color: #6b7280;
163
+ display: flex;
164
+ gap: 12px;
165
+ flex-wrap: wrap;
166
+ `;
167
+
168
+ const Courses = styled.div`
169
+ margin-top: 8px;
170
+ font-size: 14px;
171
+ color: #4b5563;
172
+
173
+ strong {
174
+ color: #374151;
175
+ }
176
+ `;
177
+
178
+ const WorkItem = styled.div`
179
+ margin-bottom: 28px;
180
+
181
+ &:last-child {
182
+ margin-bottom: 0;
183
+ }
184
+ `;
185
+
186
+ const WorkHeader = styled.div`
187
+ display: flex;
188
+ justify-content: space-between;
189
+ align-items: baseline;
190
+ margin-bottom: 8px;
191
+ gap: 16px;
192
+
193
+ @media (max-width: 640px) {
194
+ flex-direction: column;
195
+ align-items: flex-start;
196
+ gap: 4px;
197
+ }
198
+ `;
199
+
200
+ const Position = styled.h3`
201
+ font-size: 16px;
202
+ font-weight: 600;
203
+ color: #111827;
204
+ margin: 0;
205
+ `;
206
+
207
+ const Company = styled.div`
208
+ font-size: 15px;
209
+ color: #2563eb;
210
+ font-weight: 500;
211
+ margin-top: 4px;
212
+ `;
213
+
214
+ const DateText = styled.div`
215
+ font-size: 13px;
216
+ color: #6b7280;
217
+ font-weight: 500;
218
+ white-space: nowrap;
219
+ `;
220
+
221
+ const WorkSummary = styled.p`
222
+ margin: 10px 0;
223
+ color: #4b5563;
224
+ line-height: 1.7;
225
+ font-size: 14px;
226
+ `;
227
+
228
+ const Highlights = styled.ul`
229
+ margin: 10px 0 0 0;
230
+ padding-left: 20px;
231
+ list-style-type: disc;
232
+
233
+ li {
234
+ margin: 6px 0;
235
+ color: #4b5563;
236
+ line-height: 1.7;
237
+ padding-left: 4px;
238
+ }
239
+ `;
240
+
241
+ const SkillsGrid = styled.div`
242
+ display: grid;
243
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
244
+ gap: 16px;
245
+ `;
246
+
247
+ const SkillCategory = styled.div`
248
+ padding: 14px;
249
+ background: #f0f9ff;
250
+ border-radius: 6px;
251
+ border-left: 3px solid #2563eb;
252
+ `;
253
+
254
+ const SkillName = styled.h4`
255
+ font-size: 14px;
256
+ font-weight: 600;
257
+ color: #111827;
258
+ margin: 0 0 6px 0;
259
+ `;
260
+
261
+ const SkillTags = styled.div`
262
+ font-size: 13px;
263
+ color: #6b7280;
264
+ line-height: 1.6;
265
+ `;
266
+
267
+ const ProjectItem = styled(WorkItem)``;
268
+
269
+ const ProjectTitle = styled(Position)`
270
+ font-size: 16px;
271
+ `;
272
+
273
+ const ProjectMeta = styled.div`
274
+ display: flex;
275
+ gap: 12px;
276
+ margin-top: 4px;
277
+ font-size: 13px;
278
+ color: #6b7280;
279
+ flex-wrap: wrap;
280
+ `;
281
+
282
+ const ProjectLink = styled.a`
283
+ color: #2563eb;
284
+ text-decoration: none;
285
+
286
+ &:hover {
287
+ text-decoration: underline;
288
+ }
289
+ `;
290
+
291
+ const AwardItem = styled(EducationItem)``;
292
+
293
+ const AwardTitle = styled(Institution)`
294
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
295
+ sans-serif;
296
+ font-size: 16px;
297
+ `;
298
+
299
+ const SimpleList = styled.div`
300
+ display: grid;
301
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
302
+ gap: 12px;
303
+ `;
304
+
305
+ const SimpleItem = styled.div`
306
+ font-size: 14px;
307
+ color: #4b5563;
308
+
309
+ strong {
310
+ color: #111827;
311
+ font-weight: 600;
312
+ }
313
+ `;
314
+
315
+ function Resume({ resume }) {
316
+ const {
317
+ basics = {},
318
+ work = [],
319
+ education = [],
320
+ skills = [],
321
+ projects = [],
322
+ volunteer = [],
323
+ awards = [],
324
+ publications = [],
325
+ languages = [],
326
+ interests = [],
327
+ references = [],
328
+ } = resume;
329
+
330
+ return (
331
+ <Layout>
332
+ <Header>
333
+ <Name>{basics.name}</Name>
334
+ {basics.label && <Label>{basics.label}</Label>}
335
+ <StyledContactInfo basics={basics} />
336
+ </Header>
337
+
338
+ <Content>
339
+ {basics.summary && <Summary>{basics.summary}</Summary>}
340
+
341
+ {education?.length > 0 && (
342
+ <Section>
343
+ <StyledSectionTitle>Education</StyledSectionTitle>
344
+ {education.map((edu, index) => (
345
+ <EducationItem key={index}>
346
+ <Institution>{edu.institution}</Institution>
347
+ <Degree>
348
+ {edu.studyType} in {edu.area}
349
+ </Degree>
350
+ <EducationMeta>
351
+ <DateText>
352
+ <DateRange
353
+ startDate={edu.startDate}
354
+ endDate={edu.endDate}
355
+ />
356
+ </DateText>
357
+ {edu.score && <span>GPA: {edu.score}</span>}
358
+ </EducationMeta>
359
+ {edu.courses?.length > 0 && (
360
+ <Courses>
361
+ <strong>Relevant Coursework:</strong>{' '}
362
+ {edu.courses.join(', ')}
363
+ </Courses>
364
+ )}
365
+ </EducationItem>
366
+ ))}
367
+ </Section>
368
+ )}
369
+
370
+ {skills?.length > 0 && (
371
+ <Section>
372
+ <StyledSectionTitle>Skills</StyledSectionTitle>
373
+ <SkillsGrid>
374
+ {skills.map((skill, index) => (
375
+ <SkillCategory key={index}>
376
+ <SkillName>{skill.name}</SkillName>
377
+ {skill.keywords?.length > 0 && (
378
+ <SkillTags>{skill.keywords.join(', ')}</SkillTags>
379
+ )}
380
+ </SkillCategory>
381
+ ))}
382
+ </SkillsGrid>
383
+ </Section>
384
+ )}
385
+
386
+ {projects?.length > 0 && (
387
+ <Section>
388
+ <StyledSectionTitle>Projects</StyledSectionTitle>
389
+ {projects.map((project, index) => (
390
+ <ProjectItem key={index}>
391
+ <ProjectTitle>{project.name}</ProjectTitle>
392
+ <ProjectMeta>
393
+ {project.startDate && (
394
+ <DateText>
395
+ <DateRange
396
+ startDate={project.startDate}
397
+ endDate={project.endDate}
398
+ />
399
+ </DateText>
400
+ )}
401
+ {project.url && (
402
+ <ProjectLink
403
+ href={project.url}
404
+ target="_blank"
405
+ rel="noopener noreferrer"
406
+ >
407
+ View Project
408
+ </ProjectLink>
409
+ )}
410
+ </ProjectMeta>
411
+ {project.description && (
412
+ <WorkSummary>{project.description}</WorkSummary>
413
+ )}
414
+ {project.highlights?.length > 0 && (
415
+ <Highlights>
416
+ {project.highlights.map((highlight, i) => (
417
+ <li key={i}>{highlight}</li>
418
+ ))}
419
+ </Highlights>
420
+ )}
421
+ </ProjectItem>
422
+ ))}
423
+ </Section>
424
+ )}
425
+
426
+ {work?.length > 0 && (
427
+ <Section>
428
+ <StyledSectionTitle>Experience</StyledSectionTitle>
429
+ {work.map((job, index) => (
430
+ <WorkItem key={index}>
431
+ <WorkHeader>
432
+ <div>
433
+ <Position>{job.position}</Position>
434
+ {job.name && <Company>{job.name}</Company>}
435
+ </div>
436
+ <DateText>
437
+ <DateRange
438
+ startDate={job.startDate}
439
+ endDate={job.endDate}
440
+ />
441
+ </DateText>
442
+ </WorkHeader>
443
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
444
+ {job.highlights?.length > 0 && (
445
+ <Highlights>
446
+ {job.highlights.map((highlight, i) => (
447
+ <li key={i}>{highlight}</li>
448
+ ))}
449
+ </Highlights>
450
+ )}
451
+ </WorkItem>
452
+ ))}
453
+ </Section>
454
+ )}
455
+
456
+ {volunteer?.length > 0 && (
457
+ <Section>
458
+ <StyledSectionTitle>Volunteer Experience</StyledSectionTitle>
459
+ {volunteer.map((vol, index) => (
460
+ <WorkItem key={index}>
461
+ <WorkHeader>
462
+ <div>
463
+ <Position>{vol.position}</Position>
464
+ {vol.organization && <Company>{vol.organization}</Company>}
465
+ </div>
466
+ {(vol.startDate || vol.endDate) && (
467
+ <DateText>
468
+ <DateRange
469
+ startDate={vol.startDate}
470
+ endDate={vol.endDate}
471
+ />
472
+ </DateText>
473
+ )}
474
+ </WorkHeader>
475
+ {vol.summary && <WorkSummary>{vol.summary}</WorkSummary>}
476
+ {vol.highlights?.length > 0 && (
477
+ <Highlights>
478
+ {vol.highlights.map((highlight, i) => (
479
+ <li key={i}>{highlight}</li>
480
+ ))}
481
+ </Highlights>
482
+ )}
483
+ </WorkItem>
484
+ ))}
485
+ </Section>
486
+ )}
487
+
488
+ {awards?.length > 0 && (
489
+ <Section>
490
+ <StyledSectionTitle>Awards & Honors</StyledSectionTitle>
491
+ {awards.map((award, index) => (
492
+ <AwardItem key={index}>
493
+ <AwardTitle>{award.title}</AwardTitle>
494
+ {award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
495
+ {award.date && <DateText>{award.date}</DateText>}
496
+ {award.summary && <WorkSummary>{award.summary}</WorkSummary>}
497
+ </AwardItem>
498
+ ))}
499
+ </Section>
500
+ )}
501
+
502
+ {publications?.length > 0 && (
503
+ <Section>
504
+ <StyledSectionTitle>Publications</StyledSectionTitle>
505
+ {publications.map((pub, index) => (
506
+ <AwardItem key={index}>
507
+ <AwardTitle>{pub.name}</AwardTitle>
508
+ {pub.publisher && <Degree>Published by {pub.publisher}</Degree>}
509
+ {pub.releaseDate && <DateText>{pub.releaseDate}</DateText>}
510
+ {pub.summary && <WorkSummary>{pub.summary}</WorkSummary>}
511
+ </AwardItem>
512
+ ))}
513
+ </Section>
514
+ )}
515
+
516
+ {languages?.length > 0 && (
517
+ <Section>
518
+ <StyledSectionTitle>Languages</StyledSectionTitle>
519
+ <SimpleList>
520
+ {languages.map((lang, index) => (
521
+ <SimpleItem key={index}>
522
+ <strong>{lang.language}</strong>
523
+ {lang.fluency && ` - ${lang.fluency}`}
524
+ </SimpleItem>
525
+ ))}
526
+ </SimpleList>
527
+ </Section>
528
+ )}
529
+
530
+ {interests?.length > 0 && (
531
+ <Section>
532
+ <StyledSectionTitle>Interests</StyledSectionTitle>
533
+ <SkillsGrid>
534
+ {interests.map((interest, index) => (
535
+ <SkillCategory key={index}>
536
+ <SkillName>{interest.name}</SkillName>
537
+ {interest.keywords?.length > 0 && (
538
+ <SkillTags>{interest.keywords.join(', ')}</SkillTags>
539
+ )}
540
+ </SkillCategory>
541
+ ))}
542
+ </SkillsGrid>
543
+ </Section>
544
+ )}
545
+
546
+ {references?.length > 0 && (
547
+ <Section>
548
+ <StyledSectionTitle>References</StyledSectionTitle>
549
+ {references.map((ref, index) => (
550
+ <AwardItem key={index}>
551
+ <AwardTitle>{ref.name}</AwardTitle>
552
+ {ref.reference && <WorkSummary>{ref.reference}</WorkSummary>}
553
+ </AwardItem>
554
+ ))}
555
+ </Section>
556
+ )}
557
+ </Content>
558
+ </Layout>
559
+ );
560
+ }
561
+
562
+ 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
+ }