jsonresume-theme-typewriter-modern 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,24 @@
1
+ {
2
+ "name": "jsonresume-theme-typewriter-modern",
3
+ "version": "1.0.0",
4
+ "description": "Nostalgic typewriter aesthetic with monospace body and contemporary clarity",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "jsonresume",
10
+ "theme",
11
+ "typewriter",
12
+ "monospace",
13
+ "vintage",
14
+ "retro"
15
+ ],
16
+ "peerDependencies": {
17
+ "react": "^18.0.0 || ^19.0.0",
18
+ "react-dom": "^18.0.0 || ^19.0.0"
19
+ },
20
+ "dependencies": {
21
+ "@resume/core": "0.1.0",
22
+ "styled-components": "6.1.19"
23
+ }
24
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Section, SectionTitle } from '@resume/core';
4
+ import { Header } from './components/Header.jsx';
5
+ import { WorkExperience } from './components/WorkExperience.jsx';
6
+ import { Education } from './components/Education.jsx';
7
+ import { Skills } from './components/Skills.jsx';
8
+
9
+ const Layout = styled.div`
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 60px 40px;
13
+ background: #fefce8;
14
+ font-family: 'Courier Prime', 'Courier New', monospace;
15
+ color: #333333;
16
+ line-height: 1.8;
17
+
18
+ @media print {
19
+ padding: 40px;
20
+ background: white;
21
+ }
22
+ `;
23
+
24
+ const StyledSectionTitle = styled(SectionTitle)`
25
+ font-family: 'Work Sans', sans-serif;
26
+ font-size: 20px;
27
+ font-weight: 600;
28
+ color: #333333;
29
+ margin: 40px 0 24px 0;
30
+ padding-bottom: 8px;
31
+ border-bottom: 1px solid #333333;
32
+ text-transform: uppercase;
33
+ letter-spacing: 1px;
34
+ `;
35
+
36
+ function Resume({ resume }) {
37
+ const {
38
+ basics = {},
39
+ work = [],
40
+ education = [],
41
+ skills = [],
42
+ projects = [],
43
+ volunteer = [],
44
+ awards = [],
45
+ publications = [],
46
+ languages = [],
47
+ interests = [],
48
+ references = [],
49
+ } = resume;
50
+
51
+ return (
52
+ <Layout>
53
+ <Header basics={basics} />
54
+
55
+ {work?.length > 0 && (
56
+ <Section>
57
+ <StyledSectionTitle>Experience</StyledSectionTitle>
58
+ <WorkExperience work={work} />
59
+ </Section>
60
+ )}
61
+
62
+ {skills?.length > 0 && (
63
+ <Section>
64
+ <StyledSectionTitle>Skills</StyledSectionTitle>
65
+ <Skills skills={skills} />
66
+ </Section>
67
+ )}
68
+
69
+ {education?.length > 0 && (
70
+ <Section>
71
+ <StyledSectionTitle>Education</StyledSectionTitle>
72
+ <Education education={education} />
73
+ </Section>
74
+ )}
75
+
76
+ {projects?.length > 0 && (
77
+ <Section>
78
+ <StyledSectionTitle>Projects</StyledSectionTitle>
79
+ <WorkExperience work={projects} />
80
+ </Section>
81
+ )}
82
+
83
+ {volunteer?.length > 0 && (
84
+ <Section>
85
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
86
+ <WorkExperience work={volunteer} />
87
+ </Section>
88
+ )}
89
+
90
+ {awards?.length > 0 && (
91
+ <Section>
92
+ <StyledSectionTitle>Awards</StyledSectionTitle>
93
+ <Education
94
+ education={awards.map((a) => ({ ...a, institution: a.title }))}
95
+ />
96
+ </Section>
97
+ )}
98
+
99
+ {publications?.length > 0 && (
100
+ <Section>
101
+ <StyledSectionTitle>Publications</StyledSectionTitle>
102
+ <Education
103
+ education={publications.map((p) => ({ ...p, institution: p.name }))}
104
+ />
105
+ </Section>
106
+ )}
107
+
108
+ {languages?.length > 0 && (
109
+ <Section>
110
+ <StyledSectionTitle>Languages</StyledSectionTitle>
111
+ <Skills
112
+ skills={languages.map((l) => ({
113
+ name: l.language,
114
+ keywords: [l.fluency],
115
+ }))}
116
+ />
117
+ </Section>
118
+ )}
119
+
120
+ {interests?.length > 0 && (
121
+ <Section>
122
+ <StyledSectionTitle>Interests</StyledSectionTitle>
123
+ <Skills skills={interests} />
124
+ </Section>
125
+ )}
126
+
127
+ {references?.length > 0 && (
128
+ <Section>
129
+ <StyledSectionTitle>References</StyledSectionTitle>
130
+ <Education
131
+ education={references.map((r) => ({ ...r, institution: r.name }))}
132
+ />
133
+ </Section>
134
+ )}
135
+ </Layout>
136
+ );
137
+ }
138
+
139
+ export default Resume;
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { DateRange } from '@resume/core';
4
+
5
+ const EducationItem = styled.div`
6
+ margin-bottom: 24px;
7
+ position: relative;
8
+ padding-left: 120px;
9
+
10
+ &:last-child {
11
+ margin-bottom: 0;
12
+ }
13
+
14
+ @media (max-width: 640px) {
15
+ padding-left: 0;
16
+ }
17
+ `;
18
+
19
+ const DateText = styled.div`
20
+ position: absolute;
21
+ left: 0;
22
+ top: 2px;
23
+ font-size: 13px;
24
+ color: #666666;
25
+ font-weight: 400;
26
+ width: 100px;
27
+ text-align: left;
28
+ font-family: 'Courier Prime', monospace;
29
+
30
+ @media (max-width: 640px) {
31
+ position: static;
32
+ margin-bottom: 8px;
33
+ }
34
+ `;
35
+
36
+ const Institution = styled.h3`
37
+ font-family: 'Work Sans', sans-serif;
38
+ font-size: 16px;
39
+ font-weight: 600;
40
+ color: #333333;
41
+ margin: 0 0 6px 0;
42
+ `;
43
+
44
+ const Degree = styled.div`
45
+ font-size: 14px;
46
+ color: #555555;
47
+ margin-bottom: 4px;
48
+ font-family: 'Courier Prime', monospace;
49
+ `;
50
+
51
+ export function Education({ education = [] }) {
52
+ if (!education?.length) return null;
53
+
54
+ return (
55
+ <>
56
+ {education.map((edu, index) => (
57
+ <EducationItem key={index}>
58
+ <DateText>
59
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
60
+ </DateText>
61
+ <Institution>{edu.institution}</Institution>
62
+ <Degree>
63
+ {edu.studyType} in {edu.area}
64
+ {edu.score && ` • ${edu.score}`}
65
+ </Degree>
66
+ </EducationItem>
67
+ ))}
68
+ </>
69
+ );
70
+ }
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { ContactInfo } from '@resume/core';
4
+
5
+ const HeaderContainer = styled.header`
6
+ margin-bottom: 48px;
7
+ padding-bottom: 24px;
8
+ border-bottom: 2px solid #333333;
9
+ `;
10
+
11
+ const Name = styled.h1`
12
+ font-family: 'Work Sans', -apple-system, BlinkMacSystemFont, sans-serif;
13
+ font-size: 38px;
14
+ font-weight: 600;
15
+ color: #333333;
16
+ margin: 0 0 8px 0;
17
+ letter-spacing: 0.5px;
18
+ text-transform: uppercase;
19
+ `;
20
+
21
+ const Label = styled.div`
22
+ font-size: 16px;
23
+ color: #666666;
24
+ margin-bottom: 16px;
25
+ font-weight: 400;
26
+ letter-spacing: 0.5px;
27
+ `;
28
+
29
+ const StyledContactInfo = styled(ContactInfo)`
30
+ font-size: 14px;
31
+ font-family: 'Courier Prime', monospace;
32
+
33
+ a {
34
+ font-size: 14px;
35
+ color: #333333;
36
+ text-decoration: underline;
37
+ }
38
+ `;
39
+
40
+ const Summary = styled.p`
41
+ font-size: 15px;
42
+ line-height: 1.8;
43
+ color: #444444;
44
+ margin: 20px 0 0 0;
45
+ font-family: 'Courier Prime', monospace;
46
+ `;
47
+
48
+ export function Header({ basics = {} }) {
49
+ return (
50
+ <HeaderContainer>
51
+ <Name>{basics.name}</Name>
52
+ {basics.label && <Label>{basics.label}</Label>}
53
+ <StyledContactInfo basics={basics} />
54
+ {basics.summary && <Summary>{basics.summary}</Summary>}
55
+ </HeaderContainer>
56
+ );
57
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ const SkillsGrid = styled.div`
5
+ display: grid;
6
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
7
+ gap: 16px;
8
+ `;
9
+
10
+ const SkillCategory = styled.div`
11
+ padding: 12px;
12
+ background: #f9f9e8;
13
+ border: 1px solid #333333;
14
+ `;
15
+
16
+ const SkillName = styled.h4`
17
+ font-family: 'Work Sans', sans-serif;
18
+ font-size: 14px;
19
+ font-weight: 600;
20
+ color: #333333;
21
+ margin: 0 0 6px 0;
22
+ text-transform: uppercase;
23
+ letter-spacing: 0.5px;
24
+ `;
25
+
26
+ const SkillTags = styled.div`
27
+ font-size: 13px;
28
+ color: #555555;
29
+ line-height: 1.6;
30
+ font-family: 'Courier Prime', monospace;
31
+ `;
32
+
33
+ export function Skills({ skills = [] }) {
34
+ if (!skills?.length) return null;
35
+
36
+ return (
37
+ <SkillsGrid>
38
+ {skills.map((skill, index) => (
39
+ <SkillCategory key={index}>
40
+ <SkillName>{skill.name}</SkillName>
41
+ {skill.keywords?.length > 0 && (
42
+ <SkillTags>{skill.keywords.join(', ')}</SkillTags>
43
+ )}
44
+ </SkillCategory>
45
+ ))}
46
+ </SkillsGrid>
47
+ );
48
+ }
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { DateRange } from '@resume/core';
4
+
5
+ const WorkItem = styled.div`
6
+ margin-bottom: 32px;
7
+ position: relative;
8
+ padding-left: 120px;
9
+
10
+ &:last-child {
11
+ margin-bottom: 0;
12
+ }
13
+
14
+ @media (max-width: 640px) {
15
+ padding-left: 0;
16
+ }
17
+ `;
18
+
19
+ const DateText = styled.div`
20
+ position: absolute;
21
+ left: 0;
22
+ top: 2px;
23
+ font-size: 13px;
24
+ color: #666666;
25
+ font-weight: 400;
26
+ width: 100px;
27
+ text-align: left;
28
+ font-family: 'Courier Prime', monospace;
29
+
30
+ @media (max-width: 640px) {
31
+ position: static;
32
+ margin-bottom: 8px;
33
+ }
34
+ `;
35
+
36
+ const Position = styled.h3`
37
+ font-family: 'Work Sans', sans-serif;
38
+ font-size: 17px;
39
+ font-weight: 600;
40
+ color: #333333;
41
+ margin: 0 0 6px 0;
42
+ `;
43
+
44
+ const Company = styled.div`
45
+ font-size: 15px;
46
+ color: #555555;
47
+ font-weight: 400;
48
+ margin-bottom: 8px;
49
+ font-family: 'Courier Prime', monospace;
50
+ `;
51
+
52
+ const WorkSummary = styled.p`
53
+ margin: 12px 0;
54
+ color: #444444;
55
+ line-height: 1.8;
56
+ font-size: 14px;
57
+ font-family: 'Courier Prime', monospace;
58
+ `;
59
+
60
+ const Highlights = styled.ul`
61
+ margin: 12px 0 0 0;
62
+ padding-left: 20px;
63
+ list-style-type: square;
64
+
65
+ li {
66
+ margin: 8px 0;
67
+ color: #444444;
68
+ line-height: 1.8;
69
+ padding-left: 4px;
70
+ font-family: 'Courier Prime', monospace;
71
+ font-size: 14px;
72
+ }
73
+ `;
74
+
75
+ export function WorkExperience({ work = [] }) {
76
+ if (!work?.length) return null;
77
+
78
+ return (
79
+ <>
80
+ {work.map((job, index) => (
81
+ <WorkItem key={index}>
82
+ <DateText>
83
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
84
+ </DateText>
85
+ <Position>{job.position}</Position>
86
+ {job.name && <Company>{job.name}</Company>}
87
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
88
+ {job.highlights?.length > 0 && (
89
+ <Highlights>
90
+ {job.highlights.map((highlight, i) => (
91
+ <li key={i}>{highlight}</li>
92
+ ))}
93
+ </Highlights>
94
+ )}
95
+ </WorkItem>
96
+ ))}
97
+ </>
98
+ );
99
+ }
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=Courier+Prime:wght@400;700&family=Work+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: #f5f5f0;
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
+ }