jsonresume-theme-sidebar-photo-strip 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,26 @@
1
+ {
2
+ "name": "jsonresume-theme-sidebar-photo-strip",
3
+ "version": "1.0.0",
4
+ "description": "European creative layout with integrated grayscale photo sidebar and minimal aesthetic",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "jsonresume",
10
+ "theme",
11
+ "sidebar",
12
+ "photo",
13
+ "european",
14
+ "creative",
15
+ "minimal",
16
+ "two-column"
17
+ ],
18
+ "peerDependencies": {
19
+ "react": "^18.0.0 || ^19.0.0",
20
+ "react-dom": "^18.0.0 || ^19.0.0"
21
+ },
22
+ "dependencies": {
23
+ "styled-components": "6.1.19",
24
+ "@resume/core": "0.1.0"
25
+ }
26
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,494 @@
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
+ display: flex;
7
+ min-height: 100vh;
8
+ background: #f9fafb;
9
+ font-family: 'DM Sans', 'Plus Jakarta Sans', -apple-system, sans-serif;
10
+ color: #111827;
11
+
12
+ @media print {
13
+ min-height: auto;
14
+ }
15
+ `;
16
+
17
+ const Sidebar = styled.aside`
18
+ width: 28%;
19
+ background: #111827;
20
+ color: #f9fafb;
21
+ padding: 50px 30px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: 32px;
25
+
26
+ @media (max-width: 768px) {
27
+ width: 100%;
28
+ padding: 30px 20px;
29
+ }
30
+
31
+ @media print {
32
+ width: 28%;
33
+ padding: 40px 25px;
34
+ }
35
+ `;
36
+
37
+ const MainContent = styled.main`
38
+ flex: 1;
39
+ padding: 50px 60px;
40
+
41
+ @media (max-width: 768px) {
42
+ padding: 30px 20px;
43
+ }
44
+
45
+ @media print {
46
+ padding: 40px;
47
+ }
48
+ `;
49
+
50
+ const ProfilePhoto = styled.div`
51
+ width: 100%;
52
+ aspect-ratio: 3 / 4;
53
+ overflow: hidden;
54
+ border-radius: 4px;
55
+ margin-bottom: 8px;
56
+
57
+ img {
58
+ width: 100%;
59
+ height: 100%;
60
+ object-fit: cover;
61
+ filter: grayscale(100%);
62
+ }
63
+
64
+ @media (max-width: 768px) {
65
+ max-width: 200px;
66
+ margin: 0 auto 16px;
67
+ }
68
+ `;
69
+
70
+ const SidebarName = styled.h1`
71
+ font-size: 26px;
72
+ font-weight: 600;
73
+ color: #f9fafb;
74
+ margin: 0 0 6px 0;
75
+ letter-spacing: -0.3px;
76
+ line-height: 1.2;
77
+ `;
78
+
79
+ const SidebarLabel = styled.div`
80
+ font-size: 14px;
81
+ color: #9ca3af;
82
+ margin-bottom: 20px;
83
+ font-weight: 400;
84
+ letter-spacing: 0.3px;
85
+ `;
86
+
87
+ const SidebarSection = styled.div`
88
+ margin-bottom: 24px;
89
+
90
+ &:last-child {
91
+ margin-bottom: 0;
92
+ }
93
+ `;
94
+
95
+ const SidebarSectionTitle = styled.h2`
96
+ font-size: 13px;
97
+ font-weight: 600;
98
+ color: #f9fafb;
99
+ margin: 0 0 12px 0;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.8px;
102
+ `;
103
+
104
+ const SidebarText = styled.div`
105
+ font-size: 13px;
106
+ line-height: 1.6;
107
+ color: #d1d5db;
108
+ margin: 6px 0;
109
+
110
+ a {
111
+ color: #d1d5db;
112
+ text-decoration: none;
113
+ word-break: break-word;
114
+
115
+ &:hover {
116
+ color: #f9fafb;
117
+ }
118
+ }
119
+ `;
120
+
121
+ const SkillTag = styled.div`
122
+ display: inline-block;
123
+ padding: 4px 10px;
124
+ background: rgba(249, 250, 251, 0.1);
125
+ border-radius: 3px;
126
+ font-size: 12px;
127
+ margin: 4px 4px 4px 0;
128
+ color: #e5e7eb;
129
+ font-weight: 400;
130
+ `;
131
+
132
+ const StyledSectionTitle = styled(SectionTitle)`
133
+ font-size: 22px;
134
+ font-weight: 600;
135
+ color: #111827;
136
+ margin: 0 0 28px 0;
137
+ padding-bottom: 12px;
138
+ border-bottom: 2px solid #e5e7eb;
139
+ letter-spacing: -0.3px;
140
+ `;
141
+
142
+ const WorkItem = styled.div`
143
+ margin-bottom: 36px;
144
+
145
+ &:last-child {
146
+ margin-bottom: 0;
147
+ }
148
+ `;
149
+
150
+ const WorkHeader = styled.div`
151
+ margin-bottom: 12px;
152
+ `;
153
+
154
+ const Position = styled.h3`
155
+ font-size: 17px;
156
+ font-weight: 600;
157
+ color: #111827;
158
+ margin: 0 0 6px 0;
159
+ `;
160
+
161
+ const Company = styled.div`
162
+ font-size: 15px;
163
+ color: #4b5563;
164
+ font-weight: 500;
165
+ margin-bottom: 4px;
166
+ `;
167
+
168
+ const DateText = styled.div`
169
+ font-size: 13px;
170
+ color: #6b7280;
171
+ font-weight: 400;
172
+ letter-spacing: 0.2px;
173
+ `;
174
+
175
+ const WorkSummary = styled.p`
176
+ margin: 12px 0;
177
+ color: #374151;
178
+ line-height: 1.7;
179
+ font-size: 14px;
180
+ `;
181
+
182
+ const Highlights = styled.ul`
183
+ margin: 12px 0 0 0;
184
+ padding-left: 20px;
185
+ list-style-type: disc;
186
+
187
+ li {
188
+ margin: 6px 0;
189
+ color: #4b5563;
190
+ line-height: 1.7;
191
+ font-size: 14px;
192
+ padding-left: 4px;
193
+ }
194
+ `;
195
+
196
+ const EducationItem = styled.div`
197
+ margin-bottom: 28px;
198
+
199
+ &:last-child {
200
+ margin-bottom: 0;
201
+ }
202
+ `;
203
+
204
+ const Institution = styled.h3`
205
+ font-size: 16px;
206
+ font-weight: 600;
207
+ color: #111827;
208
+ margin: 0 0 6px 0;
209
+ `;
210
+
211
+ const Degree = styled.div`
212
+ font-size: 14px;
213
+ color: #4b5563;
214
+ margin-bottom: 4px;
215
+ line-height: 1.5;
216
+ `;
217
+
218
+ const EducationDate = styled.div`
219
+ font-size: 13px;
220
+ color: #6b7280;
221
+ `;
222
+
223
+ function Resume({ resume }) {
224
+ const {
225
+ basics = {},
226
+ work = [],
227
+ education = [],
228
+ skills = [],
229
+ projects = [],
230
+ volunteer = [],
231
+ awards = [],
232
+ publications = [],
233
+ languages = [],
234
+ interests = [],
235
+ references = [],
236
+ } = resume;
237
+
238
+ return (
239
+ <Layout>
240
+ <Sidebar>
241
+ {basics.image && (
242
+ <ProfilePhoto>
243
+ <img src={basics.image} alt={basics.name || 'Profile'} />
244
+ </ProfilePhoto>
245
+ )}
246
+
247
+ <div>
248
+ <SidebarName>{basics.name}</SidebarName>
249
+ {basics.label && <SidebarLabel>{basics.label}</SidebarLabel>}
250
+ </div>
251
+
252
+ {(basics.email ||
253
+ basics.phone ||
254
+ basics.url ||
255
+ basics.location?.city) && (
256
+ <SidebarSection>
257
+ <SidebarSectionTitle>Contact</SidebarSectionTitle>
258
+ {basics.email && (
259
+ <SidebarText>
260
+ <a href={`mailto:${basics.email}`}>{basics.email}</a>
261
+ </SidebarText>
262
+ )}
263
+ {basics.phone && <SidebarText>{basics.phone}</SidebarText>}
264
+ {basics.url && (
265
+ <SidebarText>
266
+ <a href={basics.url} target="_blank" rel="noopener noreferrer">
267
+ {basics.url.replace(/^https?:\/\//, '')}
268
+ </a>
269
+ </SidebarText>
270
+ )}
271
+ {basics.location?.city && (
272
+ <SidebarText>
273
+ {basics.location.city}
274
+ {basics.location.region && `, ${basics.location.region}`}
275
+ </SidebarText>
276
+ )}
277
+ </SidebarSection>
278
+ )}
279
+
280
+ {skills?.length > 0 && (
281
+ <SidebarSection>
282
+ <SidebarSectionTitle>Skills</SidebarSectionTitle>
283
+ {skills.map((skill, index) => (
284
+ <div key={index} style={{ marginBottom: '16px' }}>
285
+ <SidebarText style={{ fontWeight: 600, marginBottom: '8px' }}>
286
+ {skill.name}
287
+ </SidebarText>
288
+ <div>
289
+ {skill.keywords?.map((keyword, i) => (
290
+ <SkillTag key={i}>{keyword}</SkillTag>
291
+ ))}
292
+ </div>
293
+ </div>
294
+ ))}
295
+ </SidebarSection>
296
+ )}
297
+
298
+ {languages?.length > 0 && (
299
+ <SidebarSection>
300
+ <SidebarSectionTitle>Languages</SidebarSectionTitle>
301
+ {languages.map((lang, index) => (
302
+ <SidebarText key={index}>
303
+ <strong>{lang.language}</strong>
304
+ {lang.fluency && <span> • {lang.fluency}</span>}
305
+ </SidebarText>
306
+ ))}
307
+ </SidebarSection>
308
+ )}
309
+
310
+ {interests?.length > 0 && (
311
+ <SidebarSection>
312
+ <SidebarSectionTitle>Interests</SidebarSectionTitle>
313
+ {interests.map((interest, index) => (
314
+ <div key={index} style={{ marginBottom: '12px' }}>
315
+ <SidebarText style={{ fontWeight: 600 }}>
316
+ {interest.name}
317
+ </SidebarText>
318
+ {interest.keywords?.length > 0 && (
319
+ <SidebarText style={{ fontSize: '12px', marginTop: '4px' }}>
320
+ {interest.keywords.join(', ')}
321
+ </SidebarText>
322
+ )}
323
+ </div>
324
+ ))}
325
+ </SidebarSection>
326
+ )}
327
+ </Sidebar>
328
+
329
+ <MainContent>
330
+ {basics.summary && (
331
+ <Section>
332
+ <WorkSummary
333
+ style={{
334
+ fontSize: '15px',
335
+ marginBottom: '40px',
336
+ color: '#1f2937',
337
+ }}
338
+ >
339
+ {basics.summary}
340
+ </WorkSummary>
341
+ </Section>
342
+ )}
343
+
344
+ {work?.length > 0 && (
345
+ <Section>
346
+ <StyledSectionTitle>Experience</StyledSectionTitle>
347
+ {work.map((job, index) => (
348
+ <WorkItem key={index}>
349
+ <WorkHeader>
350
+ <Position>{job.position}</Position>
351
+ {job.name && <Company>{job.name}</Company>}
352
+ {(job.startDate || job.endDate) && (
353
+ <DateText>
354
+ <DateRange
355
+ startDate={job.startDate}
356
+ endDate={job.endDate}
357
+ />
358
+ </DateText>
359
+ )}
360
+ </WorkHeader>
361
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
362
+ {job.highlights?.length > 0 && (
363
+ <Highlights>
364
+ {job.highlights.map((highlight, i) => (
365
+ <li key={i}>{highlight}</li>
366
+ ))}
367
+ </Highlights>
368
+ )}
369
+ </WorkItem>
370
+ ))}
371
+ </Section>
372
+ )}
373
+
374
+ {projects?.length > 0 && (
375
+ <Section>
376
+ <StyledSectionTitle>Projects</StyledSectionTitle>
377
+ {projects.map((project, index) => (
378
+ <WorkItem key={index}>
379
+ <Position>{project.name}</Position>
380
+ {project.description && (
381
+ <WorkSummary>{project.description}</WorkSummary>
382
+ )}
383
+ {project.highlights?.length > 0 && (
384
+ <Highlights>
385
+ {project.highlights.map((highlight, i) => (
386
+ <li key={i}>{highlight}</li>
387
+ ))}
388
+ </Highlights>
389
+ )}
390
+ </WorkItem>
391
+ ))}
392
+ </Section>
393
+ )}
394
+
395
+ {education?.length > 0 && (
396
+ <Section>
397
+ <StyledSectionTitle>Education</StyledSectionTitle>
398
+ {education.map((edu, index) => (
399
+ <EducationItem key={index}>
400
+ <Institution>{edu.institution}</Institution>
401
+ <Degree>
402
+ {edu.studyType} in {edu.area}
403
+ {edu.score && ` • ${edu.score}`}
404
+ </Degree>
405
+ {(edu.startDate || edu.endDate) && (
406
+ <EducationDate>
407
+ <DateRange
408
+ startDate={edu.startDate}
409
+ endDate={edu.endDate}
410
+ />
411
+ </EducationDate>
412
+ )}
413
+ </EducationItem>
414
+ ))}
415
+ </Section>
416
+ )}
417
+
418
+ {volunteer?.length > 0 && (
419
+ <Section>
420
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
421
+ {volunteer.map((vol, index) => (
422
+ <WorkItem key={index}>
423
+ <WorkHeader>
424
+ <Position>{vol.position}</Position>
425
+ {vol.organization && <Company>{vol.organization}</Company>}
426
+ {(vol.startDate || vol.endDate) && (
427
+ <DateText>
428
+ <DateRange
429
+ startDate={vol.startDate}
430
+ endDate={vol.endDate}
431
+ />
432
+ </DateText>
433
+ )}
434
+ </WorkHeader>
435
+ {vol.summary && <WorkSummary>{vol.summary}</WorkSummary>}
436
+ {vol.highlights?.length > 0 && (
437
+ <Highlights>
438
+ {vol.highlights.map((highlight, i) => (
439
+ <li key={i}>{highlight}</li>
440
+ ))}
441
+ </Highlights>
442
+ )}
443
+ </WorkItem>
444
+ ))}
445
+ </Section>
446
+ )}
447
+
448
+ {awards?.length > 0 && (
449
+ <Section>
450
+ <StyledSectionTitle>Awards</StyledSectionTitle>
451
+ {awards.map((award, index) => (
452
+ <EducationItem key={index}>
453
+ <Institution>{award.title}</Institution>
454
+ {award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
455
+ {award.date && <EducationDate>{award.date}</EducationDate>}
456
+ {award.summary && <WorkSummary>{award.summary}</WorkSummary>}
457
+ </EducationItem>
458
+ ))}
459
+ </Section>
460
+ )}
461
+
462
+ {publications?.length > 0 && (
463
+ <Section>
464
+ <StyledSectionTitle>Publications</StyledSectionTitle>
465
+ {publications.map((pub, index) => (
466
+ <EducationItem key={index}>
467
+ <Institution>{pub.name}</Institution>
468
+ {pub.publisher && <Degree>Published by {pub.publisher}</Degree>}
469
+ {pub.releaseDate && (
470
+ <EducationDate>{pub.releaseDate}</EducationDate>
471
+ )}
472
+ {pub.summary && <WorkSummary>{pub.summary}</WorkSummary>}
473
+ </EducationItem>
474
+ ))}
475
+ </Section>
476
+ )}
477
+
478
+ {references?.length > 0 && (
479
+ <Section>
480
+ <StyledSectionTitle>References</StyledSectionTitle>
481
+ {references.map((ref, index) => (
482
+ <EducationItem key={index}>
483
+ <Institution>{ref.name}</Institution>
484
+ {ref.reference && <WorkSummary>{ref.reference}</WorkSummary>}
485
+ </EducationItem>
486
+ ))}
487
+ </Section>
488
+ )}
489
+ </MainContent>
490
+ </Layout>
491
+ );
492
+ }
493
+
494
+ 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=DM+Sans:wght@400;500;600;700&family=Plus+Jakarta+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: #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
+ }