jsonresume-theme-pacific-horizon 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 +472 -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@400;500;600;700&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-pacific-horizon",
3
+ "version": "0.1.0",
4
+ "description": "Contemporary corporate resume with clean typography and blue tones, inspired by SaaS landing pages",
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,472 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import {
4
+ Section,
5
+ SectionTitle,
6
+ DateRange,
7
+ Badge,
8
+ BadgeList,
9
+ ContactInfo,
10
+ Link,
11
+ safeUrl,
12
+ } from '@resume/core';
13
+
14
+ const Layout = styled.div`
15
+ max-width: 900px;
16
+ margin: 0 auto;
17
+ padding: 64px 48px;
18
+ background: white;
19
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
20
+ sans-serif;
21
+ color: #111827;
22
+ line-height: 1.7;
23
+ font-size: 15px;
24
+
25
+ @media print {
26
+ padding: 40px 30px;
27
+ }
28
+ `;
29
+
30
+ const Header = styled.header`
31
+ margin-bottom: 48px;
32
+ text-align: center;
33
+ `;
34
+
35
+ const Name = styled.h1`
36
+ font-size: 48px;
37
+ font-weight: 700;
38
+ margin: 0 0 12px 0;
39
+ color: #111827;
40
+ letter-spacing: -1px;
41
+ line-height: 1.1;
42
+ `;
43
+
44
+ const Tagline = styled.div`
45
+ font-size: 20px;
46
+ color: #2563eb;
47
+ margin: 0 0 24px 0;
48
+ font-weight: 500;
49
+ `;
50
+
51
+ const Summary = styled.p`
52
+ font-size: 16px;
53
+ line-height: 1.8;
54
+ color: #4b5563;
55
+ margin: 24px auto 0 auto;
56
+ max-width: 700px;
57
+ `;
58
+
59
+ const StyledContactInfo = styled(ContactInfo)`
60
+ display: flex;
61
+ flex-wrap: wrap;
62
+ justify-content: center;
63
+ gap: 24px;
64
+ margin-top: 24px;
65
+
66
+ a {
67
+ color: #2563eb;
68
+ text-decoration: none;
69
+ font-weight: 500;
70
+ font-size: 14px;
71
+ transition: color 0.2s;
72
+
73
+ &:hover {
74
+ color: #1d4ed8;
75
+ }
76
+ }
77
+ `;
78
+
79
+ const StyledSection = styled(Section)`
80
+ margin-bottom: 48px;
81
+ padding-top: 32px;
82
+ border-top: 1px solid #e5e7eb;
83
+
84
+ &:first-of-type {
85
+ padding-top: 0;
86
+ border-top: none;
87
+ }
88
+ `;
89
+
90
+ const StyledSectionTitle = styled(SectionTitle)`
91
+ font-size: 24px;
92
+ font-weight: 700;
93
+ color: #111827;
94
+ margin: 0 0 32px 0;
95
+ letter-spacing: -0.5px;
96
+ `;
97
+
98
+ const WorkItem = styled.div`
99
+ margin-bottom: 32px;
100
+
101
+ &:last-child {
102
+ margin-bottom: 0;
103
+ }
104
+ `;
105
+
106
+ const ItemHeader = styled.div`
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: baseline;
110
+ margin-bottom: 8px;
111
+ gap: 16px;
112
+ `;
113
+
114
+ const ItemTitle = styled.h3`
115
+ font-size: 20px;
116
+ font-weight: 600;
117
+ margin: 0;
118
+ color: #111827;
119
+ flex: 1;
120
+ `;
121
+
122
+ const ItemDate = styled.div`
123
+ font-size: 14px;
124
+ font-weight: 500;
125
+ color: #6b7280;
126
+ white-space: nowrap;
127
+ `;
128
+
129
+ const ItemSubtitle = styled.div`
130
+ font-size: 17px;
131
+ font-weight: 500;
132
+ color: #2563eb;
133
+ margin-bottom: 4px;
134
+ `;
135
+
136
+ const ItemLocation = styled.div`
137
+ font-size: 14px;
138
+ color: #6b7280;
139
+ margin-bottom: 12px;
140
+ `;
141
+
142
+ const ItemDescription = styled.p`
143
+ font-size: 15px;
144
+ line-height: 1.7;
145
+ color: #4b5563;
146
+ margin: 12px 0;
147
+ `;
148
+
149
+ const HighlightsList = styled.ul`
150
+ margin: 12px 0 0 0;
151
+ padding-left: 20px;
152
+ list-style: disc;
153
+
154
+ li {
155
+ margin-bottom: 8px;
156
+ font-size: 15px;
157
+ line-height: 1.7;
158
+ color: #4b5563;
159
+
160
+ &::marker {
161
+ color: #2563eb;
162
+ }
163
+ }
164
+ `;
165
+
166
+ const SkillsContainer = styled.div`
167
+ display: grid;
168
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
169
+ gap: 24px;
170
+ `;
171
+
172
+ const SkillCategory = styled.div`
173
+ h4 {
174
+ font-size: 16px;
175
+ font-weight: 600;
176
+ color: #111827;
177
+ margin: 0 0 12px 0;
178
+ }
179
+ `;
180
+
181
+ const StyledBadgeList = styled(BadgeList)`
182
+ display: flex;
183
+ flex-wrap: wrap;
184
+ gap: 8px;
185
+ `;
186
+
187
+ const StyledBadge = styled(Badge)`
188
+ font-size: 13px;
189
+ padding: 6px 14px;
190
+ background: #eff6ff;
191
+ border: 1px solid #bfdbfe;
192
+ color: #1e40af;
193
+ font-weight: 500;
194
+ border-radius: 6px;
195
+ transition: all 0.2s;
196
+
197
+ &:hover {
198
+ background: #dbeafe;
199
+ border-color: #93c5fd;
200
+ }
201
+ `;
202
+
203
+ const SimpleCard = styled.div`
204
+ margin-bottom: 20px;
205
+
206
+ h4 {
207
+ font-size: 17px;
208
+ font-weight: 600;
209
+ color: #111827;
210
+ margin: 0 0 8px 0;
211
+ }
212
+
213
+ p {
214
+ font-size: 14px;
215
+ margin: 4px 0 0 0;
216
+ color: #6b7280;
217
+ }
218
+
219
+ &:last-child {
220
+ margin-bottom: 0;
221
+ }
222
+ `;
223
+
224
+ const EducationItem = styled(WorkItem)``;
225
+
226
+ const ProjectItem = styled(WorkItem)``;
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
+ return (
244
+ <Layout>
245
+ <Header>
246
+ {basics.name && <Name>{basics.name}</Name>}
247
+ {basics.label && <Tagline>{basics.label}</Tagline>}
248
+ <StyledContactInfo
249
+ email={basics.email}
250
+ phone={basics.phone}
251
+ url={basics.url}
252
+ location={basics.location}
253
+ profiles={basics.profiles}
254
+ />
255
+ {basics.summary && <Summary>{basics.summary}</Summary>}
256
+ </Header>
257
+
258
+ {work.length > 0 && (
259
+ <StyledSection>
260
+ <StyledSectionTitle>Experience</StyledSectionTitle>
261
+ {work.map((job, index) => (
262
+ <WorkItem key={index}>
263
+ <ItemHeader>
264
+ <ItemTitle>{job.position || job.name}</ItemTitle>
265
+ <ItemDate>
266
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
267
+ </ItemDate>
268
+ </ItemHeader>
269
+ {job.name && <ItemSubtitle>{job.name}</ItemSubtitle>}
270
+ {job.location && <ItemLocation>{job.location}</ItemLocation>}
271
+ {job.summary && <ItemDescription>{job.summary}</ItemDescription>}
272
+ {job.highlights && job.highlights.length > 0 && (
273
+ <HighlightsList>
274
+ {job.highlights.map((highlight, i) => (
275
+ <li key={i}>{highlight}</li>
276
+ ))}
277
+ </HighlightsList>
278
+ )}
279
+ </WorkItem>
280
+ ))}
281
+ </StyledSection>
282
+ )}
283
+
284
+ {education.length > 0 && (
285
+ <StyledSection>
286
+ <StyledSectionTitle>Education</StyledSectionTitle>
287
+ {education.map((edu, index) => (
288
+ <EducationItem key={index}>
289
+ <ItemHeader>
290
+ <ItemTitle>{edu.institution}</ItemTitle>
291
+ <ItemDate>
292
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
293
+ </ItemDate>
294
+ </ItemHeader>
295
+ {edu.studyType && edu.area && (
296
+ <ItemSubtitle>
297
+ {edu.studyType} in {edu.area}
298
+ </ItemSubtitle>
299
+ )}
300
+ {edu.score && <ItemDescription>GPA: {edu.score}</ItemDescription>}
301
+ {edu.courses && edu.courses.length > 0 && (
302
+ <HighlightsList>
303
+ {edu.courses.map((course, i) => (
304
+ <li key={i}>{course}</li>
305
+ ))}
306
+ </HighlightsList>
307
+ )}
308
+ </EducationItem>
309
+ ))}
310
+ </StyledSection>
311
+ )}
312
+
313
+ {skills.length > 0 && (
314
+ <StyledSection>
315
+ <StyledSectionTitle>Skills</StyledSectionTitle>
316
+ <SkillsContainer>
317
+ {skills.map((skill, index) => (
318
+ <SkillCategory key={index}>
319
+ <h4>{skill.name}</h4>
320
+ <StyledBadgeList>
321
+ {skill.keywords?.map((keyword, i) => (
322
+ <StyledBadge key={i}>{keyword}</StyledBadge>
323
+ ))}
324
+ </StyledBadgeList>
325
+ </SkillCategory>
326
+ ))}
327
+ </SkillsContainer>
328
+ </StyledSection>
329
+ )}
330
+
331
+ {projects.length > 0 && (
332
+ <StyledSection>
333
+ <StyledSectionTitle>Projects</StyledSectionTitle>
334
+ {projects.map((project, index) => (
335
+ <ProjectItem key={index}>
336
+ <ItemHeader>
337
+ <ItemTitle>
338
+ {project.url ? (
339
+ <Link href={safeUrl(project.url)}>{project.name}</Link>
340
+ ) : (
341
+ project.name
342
+ )}
343
+ </ItemTitle>
344
+ <ItemDate>
345
+ <DateRange
346
+ startDate={project.startDate}
347
+ endDate={project.endDate}
348
+ />
349
+ </ItemDate>
350
+ </ItemHeader>
351
+ {project.type && <ItemLocation>{project.type}</ItemLocation>}
352
+ {project.description && (
353
+ <ItemDescription>{project.description}</ItemDescription>
354
+ )}
355
+ {project.highlights && project.highlights.length > 0 && (
356
+ <HighlightsList>
357
+ {project.highlights.map((highlight, i) => (
358
+ <li key={i}>{highlight}</li>
359
+ ))}
360
+ </HighlightsList>
361
+ )}
362
+ </ProjectItem>
363
+ ))}
364
+ </StyledSection>
365
+ )}
366
+
367
+ {awards.length > 0 && (
368
+ <StyledSection>
369
+ <StyledSectionTitle>Awards</StyledSectionTitle>
370
+ {awards.map((award, index) => (
371
+ <SimpleCard key={index}>
372
+ <h4>{award.title}</h4>
373
+ <p>
374
+ {award.awarder} {award.date && `• ${award.date}`}
375
+ </p>
376
+ {award.summary && <p>{award.summary}</p>}
377
+ </SimpleCard>
378
+ ))}
379
+ </StyledSection>
380
+ )}
381
+
382
+ {publications.length > 0 && (
383
+ <StyledSection>
384
+ <StyledSectionTitle>Publications</StyledSectionTitle>
385
+ {publications.map((pub, index) => (
386
+ <SimpleCard key={index}>
387
+ <h4>
388
+ {pub.url ? (
389
+ <Link href={safeUrl(pub.url)}>{pub.name}</Link>
390
+ ) : (
391
+ pub.name
392
+ )}
393
+ </h4>
394
+ <p>
395
+ {pub.publisher} {pub.releaseDate && `• ${pub.releaseDate}`}
396
+ </p>
397
+ {pub.summary && <p>{pub.summary}</p>}
398
+ </SimpleCard>
399
+ ))}
400
+ </StyledSection>
401
+ )}
402
+
403
+ {volunteer.length > 0 && (
404
+ <StyledSection>
405
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
406
+ {volunteer.map((vol, index) => (
407
+ <WorkItem key={index}>
408
+ <ItemHeader>
409
+ <ItemTitle>{vol.position}</ItemTitle>
410
+ <ItemDate>
411
+ <DateRange startDate={vol.startDate} endDate={vol.endDate} />
412
+ </ItemDate>
413
+ </ItemHeader>
414
+ {vol.organization && (
415
+ <ItemSubtitle>{vol.organization}</ItemSubtitle>
416
+ )}
417
+ {vol.summary && <ItemDescription>{vol.summary}</ItemDescription>}
418
+ {vol.highlights && vol.highlights.length > 0 && (
419
+ <HighlightsList>
420
+ {vol.highlights.map((highlight, i) => (
421
+ <li key={i}>{highlight}</li>
422
+ ))}
423
+ </HighlightsList>
424
+ )}
425
+ </WorkItem>
426
+ ))}
427
+ </StyledSection>
428
+ )}
429
+
430
+ {languages.length > 0 && (
431
+ <StyledSection>
432
+ <StyledSectionTitle>Languages</StyledSectionTitle>
433
+ <StyledBadgeList>
434
+ {languages.map((lang, index) => (
435
+ <StyledBadge key={index}>
436
+ {lang.language} {lang.fluency && `— ${lang.fluency}`}
437
+ </StyledBadge>
438
+ ))}
439
+ </StyledBadgeList>
440
+ </StyledSection>
441
+ )}
442
+
443
+ {interests.length > 0 && (
444
+ <StyledSection>
445
+ <StyledSectionTitle>Interests</StyledSectionTitle>
446
+ {interests.map((interest, index) => (
447
+ <SimpleCard key={index}>
448
+ <h4>{interest.name}</h4>
449
+ {interest.keywords && interest.keywords.length > 0 && (
450
+ <p>{interest.keywords.join(', ')}</p>
451
+ )}
452
+ </SimpleCard>
453
+ ))}
454
+ </StyledSection>
455
+ )}
456
+
457
+ {references.length > 0 && (
458
+ <StyledSection>
459
+ <StyledSectionTitle>References</StyledSectionTitle>
460
+ {references.map((ref, index) => (
461
+ <SimpleCard key={index}>
462
+ <h4>{ref.name}</h4>
463
+ {ref.reference && <p>{ref.reference}</p>}
464
+ </SimpleCard>
465
+ ))}
466
+ </StyledSection>
467
+ )}
468
+ </Layout>
469
+ );
470
+ }
471
+
472
+ export default Resume;