jsonresume-theme-monochrome-noir 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,29 @@
1
+ {
2
+ "name": "jsonresume-theme-monochrome-noir",
3
+ "version": "1.0.0",
4
+ "description": "Sharp, modern, ultra-clean theme with bold contrasts and minimal design",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "keywords": [
8
+ "jsonresume",
9
+ "theme",
10
+ "resume",
11
+ "monochrome",
12
+ "noir",
13
+ "minimal",
14
+ "editorial",
15
+ "creative-tech"
16
+ ],
17
+ "author": "JSON Resume Team",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "react": "^19.2.0",
21
+ "react-dom": "^19.2.0",
22
+ "styled-components": "^6.1.19",
23
+ "@resume/core": "0.1.0"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^19.2.0",
27
+ "react-dom": "^19.2.0"
28
+ }
29
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,558 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import {
4
+ Section,
5
+ SectionTitle,
6
+ ListItem,
7
+ DateRange,
8
+ ContactInfo,
9
+ Link,
10
+ safeUrl,
11
+ isExternalUrl,
12
+ } from '@resume/core';
13
+
14
+ const ResumeContainer = styled.div`
15
+ max-width: 850px;
16
+ margin: 0 auto;
17
+ padding: 80px 60px;
18
+ background: #f8f8f8;
19
+ font-family: 'Roboto Condensed', 'Arial Narrow', 'Helvetica Neue', sans-serif;
20
+ color: #000000;
21
+ line-height: 1.6;
22
+ letter-spacing: -0.3px;
23
+
24
+ @media print {
25
+ padding: 40px 30px;
26
+ max-width: 100%;
27
+ background: white;
28
+ }
29
+ `;
30
+
31
+ const Header = styled.header`
32
+ margin-bottom: 60px;
33
+ padding-bottom: 40px;
34
+ border-bottom: 1px solid #ffffff;
35
+ `;
36
+
37
+ const Name = styled.h1`
38
+ font-size: 4rem;
39
+ font-weight: 700;
40
+ color: #000000;
41
+ margin: 0 0 12px 0;
42
+ letter-spacing: -2px;
43
+ line-height: 0.9;
44
+ text-transform: uppercase;
45
+ `;
46
+
47
+ const Label = styled.p`
48
+ font-size: 1.125rem;
49
+ font-weight: 400;
50
+ color: #000000;
51
+ margin: 0 0 24px 0;
52
+ letter-spacing: 0.5px;
53
+ text-transform: uppercase;
54
+ `;
55
+
56
+ const Summary = styled.p`
57
+ font-size: 1rem;
58
+ line-height: 1.7;
59
+ color: #1a1a1a;
60
+ margin: 24px 0 0 0;
61
+ font-weight: 300;
62
+ max-width: 700px;
63
+ `;
64
+
65
+ const StyledContactInfo = styled(ContactInfo)`
66
+ font-size: 0.875rem;
67
+ color: #333333;
68
+ letter-spacing: -0.2px;
69
+
70
+ a {
71
+ color: #000000;
72
+ text-decoration: none;
73
+ border-bottom: 1px solid #000000;
74
+
75
+ &:hover {
76
+ border-bottom: 2px solid #000000;
77
+ }
78
+ }
79
+ `;
80
+
81
+ const ContentSection = styled.div`
82
+ margin-bottom: 50px;
83
+ padding-bottom: 40px;
84
+ border-bottom: 1px solid #ffffff;
85
+
86
+ &:last-child {
87
+ margin-bottom: 0;
88
+ border-bottom: none;
89
+ }
90
+ `;
91
+
92
+ const StyledSectionTitle = styled(SectionTitle)`
93
+ font-size: 0.9375rem;
94
+ font-weight: 700;
95
+ text-transform: uppercase;
96
+ letter-spacing: 1.5px;
97
+ color: #000000;
98
+ margin: 0 0 30px 0;
99
+ `;
100
+
101
+ const ExperienceItem = styled.div`
102
+ margin-bottom: 35px;
103
+
104
+ &:last-child {
105
+ margin-bottom: 0;
106
+ }
107
+ `;
108
+
109
+ const ExperienceHeader = styled.div`
110
+ margin-bottom: 12px;
111
+ `;
112
+
113
+ const Position = styled.h3`
114
+ font-size: 1.25rem;
115
+ font-weight: 700;
116
+ color: #000000;
117
+ margin: 0 0 6px 0;
118
+ letter-spacing: -0.5px;
119
+ `;
120
+
121
+ const Company = styled.div`
122
+ font-size: 1rem;
123
+ color: #1a1a1a;
124
+ font-weight: 500;
125
+ margin-bottom: 6px;
126
+ letter-spacing: -0.2px;
127
+ `;
128
+
129
+ const StyledDateRange = styled(DateRange)`
130
+ font-size: 0.8125rem;
131
+ color: #666666;
132
+ font-weight: 400;
133
+ text-transform: uppercase;
134
+ letter-spacing: 0.5px;
135
+ `;
136
+
137
+ const Description = styled.p`
138
+ font-size: 0.9375rem;
139
+ line-height: 1.6;
140
+ color: #1a1a1a;
141
+ margin: 12px 0 16px 0;
142
+ font-weight: 300;
143
+ `;
144
+
145
+ const Highlights = styled.ul`
146
+ list-style: none;
147
+ padding: 0;
148
+ margin: 0;
149
+ `;
150
+
151
+ const Highlight = styled.li`
152
+ font-size: 0.9375rem;
153
+ line-height: 1.6;
154
+ color: #1a1a1a;
155
+ margin-bottom: 8px;
156
+ padding-left: 20px;
157
+ position: relative;
158
+ font-weight: 300;
159
+
160
+ &:before {
161
+ content: '—';
162
+ position: absolute;
163
+ left: 0;
164
+ color: #000000;
165
+ font-weight: 700;
166
+ }
167
+ `;
168
+
169
+ const EducationItem = styled.div`
170
+ margin-bottom: 30px;
171
+
172
+ &:last-child {
173
+ margin-bottom: 0;
174
+ }
175
+ `;
176
+
177
+ const Institution = styled.h3`
178
+ font-size: 1.125rem;
179
+ font-weight: 700;
180
+ color: #000000;
181
+ margin: 0 0 6px 0;
182
+ letter-spacing: -0.3px;
183
+ `;
184
+
185
+ const Degree = styled.div`
186
+ font-size: 1rem;
187
+ color: #1a1a1a;
188
+ font-weight: 400;
189
+ margin-bottom: 6px;
190
+ letter-spacing: -0.2px;
191
+ `;
192
+
193
+ const SkillsGrid = styled.div`
194
+ display: grid;
195
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
196
+ gap: 20px;
197
+ `;
198
+
199
+ const SkillCategory = styled.div`
200
+ padding: 0;
201
+ `;
202
+
203
+ const SkillName = styled.h4`
204
+ font-size: 0.875rem;
205
+ font-weight: 700;
206
+ color: #000000;
207
+ margin: 0 0 8px 0;
208
+ text-transform: uppercase;
209
+ letter-spacing: 1px;
210
+ `;
211
+
212
+ const SkillKeywords = styled.div`
213
+ font-size: 0.875rem;
214
+ line-height: 1.5;
215
+ color: #333333;
216
+ font-weight: 300;
217
+ `;
218
+
219
+ const ProjectItem = styled.div`
220
+ margin-bottom: 35px;
221
+
222
+ &:last-child {
223
+ margin-bottom: 0;
224
+ }
225
+ `;
226
+
227
+ const ProjectName = styled.h3`
228
+ font-size: 1.125rem;
229
+ font-weight: 700;
230
+ color: #000000;
231
+ margin: 0 0 6px 0;
232
+ letter-spacing: -0.3px;
233
+ `;
234
+
235
+ const ProjectUrl = styled.div`
236
+ font-size: 0.875rem;
237
+ margin-bottom: 8px;
238
+
239
+ a {
240
+ color: #000000;
241
+ text-decoration: none;
242
+ border-bottom: 1px solid #000000;
243
+
244
+ &:hover {
245
+ border-bottom: 2px solid #000000;
246
+ }
247
+ }
248
+ `;
249
+
250
+ const AwardItem = styled.div`
251
+ margin-bottom: 25px;
252
+
253
+ &:last-child {
254
+ margin-bottom: 0;
255
+ }
256
+ `;
257
+
258
+ const AwardTitle = styled.h4`
259
+ font-size: 1rem;
260
+ font-weight: 700;
261
+ color: #000000;
262
+ margin: 0 0 6px 0;
263
+ letter-spacing: -0.2px;
264
+ `;
265
+
266
+ const Awarder = styled.div`
267
+ font-size: 0.9375rem;
268
+ color: #1a1a1a;
269
+ font-weight: 400;
270
+ margin-bottom: 6px;
271
+ `;
272
+
273
+ const LanguageList = styled.div`
274
+ display: flex;
275
+ flex-wrap: wrap;
276
+ gap: 20px;
277
+ `;
278
+
279
+ const LanguageItem = styled.div`
280
+ font-size: 0.9375rem;
281
+ `;
282
+
283
+ const LanguageName = styled.span`
284
+ font-weight: 700;
285
+ color: #000000;
286
+ letter-spacing: -0.2px;
287
+ `;
288
+
289
+ const LanguageFluency = styled.span`
290
+ color: #666666;
291
+ font-weight: 300;
292
+ margin-left: 8px;
293
+ `;
294
+
295
+ const InterestsList = styled.div`
296
+ display: flex;
297
+ flex-wrap: wrap;
298
+ gap: 16px;
299
+ `;
300
+
301
+ const InterestTag = styled.span`
302
+ display: inline-block;
303
+ padding: 8px 16px;
304
+ background: #000000;
305
+ color: #ffffff;
306
+ font-size: 0.8125rem;
307
+ font-weight: 500;
308
+ text-transform: uppercase;
309
+ letter-spacing: 0.5px;
310
+ `;
311
+
312
+ function Resume({ resume }) {
313
+ const {
314
+ basics,
315
+ work,
316
+ education,
317
+ skills,
318
+ volunteer,
319
+ awards,
320
+ publications,
321
+ languages,
322
+ interests,
323
+ projects,
324
+ references,
325
+ } = resume;
326
+
327
+ return (
328
+ <ResumeContainer>
329
+ <Header>
330
+ {basics && (
331
+ <>
332
+ {basics.name && <Name>{basics.name}</Name>}
333
+ {basics.label && <Label>{basics.label}</Label>}
334
+ <StyledContactInfo basics={basics} />
335
+ {basics.summary && <Summary>{basics.summary}</Summary>}
336
+ </>
337
+ )}
338
+ </Header>
339
+
340
+ {/* Work Experience */}
341
+ {work && work.length > 0 && (
342
+ <ContentSection>
343
+ <StyledSectionTitle>Experience</StyledSectionTitle>
344
+ {work.map((job, index) => (
345
+ <ExperienceItem key={index}>
346
+ <ExperienceHeader>
347
+ {job.position && <Position>{job.position}</Position>}
348
+ {job.name && <Company>{job.name}</Company>}
349
+ <StyledDateRange
350
+ startDate={job.startDate}
351
+ endDate={job.endDate}
352
+ />
353
+ </ExperienceHeader>
354
+
355
+ {job.summary && <Description>{job.summary}</Description>}
356
+
357
+ {job.highlights && job.highlights.length > 0 && (
358
+ <Highlights>
359
+ {job.highlights.map((highlight, i) => (
360
+ <Highlight key={i}>{highlight}</Highlight>
361
+ ))}
362
+ </Highlights>
363
+ )}
364
+ </ExperienceItem>
365
+ ))}
366
+ </ContentSection>
367
+ )}
368
+
369
+ {/* Education */}
370
+ {education && education.length > 0 && (
371
+ <ContentSection>
372
+ <StyledSectionTitle>Education</StyledSectionTitle>
373
+ {education.map((edu, index) => (
374
+ <EducationItem key={index}>
375
+ {edu.institution && <Institution>{edu.institution}</Institution>}
376
+ {edu.studyType && edu.area && (
377
+ <Degree>
378
+ {edu.studyType} in {edu.area}
379
+ </Degree>
380
+ )}
381
+ <StyledDateRange
382
+ startDate={edu.startDate}
383
+ endDate={edu.endDate}
384
+ />
385
+ {edu.score && <Description>GPA: {edu.score}</Description>}
386
+ </EducationItem>
387
+ ))}
388
+ </ContentSection>
389
+ )}
390
+
391
+ {/* Skills */}
392
+ {skills && skills.length > 0 && (
393
+ <ContentSection>
394
+ <StyledSectionTitle>Skills</StyledSectionTitle>
395
+ <SkillsGrid>
396
+ {skills.map((skill, index) => (
397
+ <SkillCategory key={index}>
398
+ {skill.name && <SkillName>{skill.name}</SkillName>}
399
+ {skill.keywords && skill.keywords.length > 0 && (
400
+ <SkillKeywords>{skill.keywords.join(', ')}</SkillKeywords>
401
+ )}
402
+ </SkillCategory>
403
+ ))}
404
+ </SkillsGrid>
405
+ </ContentSection>
406
+ )}
407
+
408
+ {/* Projects */}
409
+ {projects && projects.length > 0 && (
410
+ <ContentSection>
411
+ <StyledSectionTitle>Projects</StyledSectionTitle>
412
+ {projects.map((project, index) => (
413
+ <ProjectItem key={index}>
414
+ {project.name && <ProjectName>{project.name}</ProjectName>}
415
+ {project.url && (
416
+ <ProjectUrl>
417
+ <Link
418
+ href={safeUrl(project.url)}
419
+ target={isExternalUrl(project.url) ? '_blank' : undefined}
420
+ >
421
+ {project.url.replace(/^https?:\/\//, '')}
422
+ </Link>
423
+ </ProjectUrl>
424
+ )}
425
+ <StyledDateRange
426
+ startDate={project.startDate}
427
+ endDate={project.endDate}
428
+ />
429
+ {project.description && (
430
+ <Description>{project.description}</Description>
431
+ )}
432
+ {project.highlights && project.highlights.length > 0 && (
433
+ <Highlights>
434
+ {project.highlights.map((highlight, i) => (
435
+ <Highlight key={i}>{highlight}</Highlight>
436
+ ))}
437
+ </Highlights>
438
+ )}
439
+ </ProjectItem>
440
+ ))}
441
+ </ContentSection>
442
+ )}
443
+
444
+ {/* Volunteer */}
445
+ {volunteer && volunteer.length > 0 && (
446
+ <ContentSection>
447
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
448
+ {volunteer.map((vol, index) => (
449
+ <ExperienceItem key={index}>
450
+ <ExperienceHeader>
451
+ {vol.position && <Position>{vol.position}</Position>}
452
+ {vol.organization && <Company>{vol.organization}</Company>}
453
+ <StyledDateRange
454
+ startDate={vol.startDate}
455
+ endDate={vol.endDate}
456
+ />
457
+ </ExperienceHeader>
458
+ {vol.summary && <Description>{vol.summary}</Description>}
459
+ {vol.highlights && vol.highlights.length > 0 && (
460
+ <Highlights>
461
+ {vol.highlights.map((highlight, i) => (
462
+ <Highlight key={i}>{highlight}</Highlight>
463
+ ))}
464
+ </Highlights>
465
+ )}
466
+ </ExperienceItem>
467
+ ))}
468
+ </ContentSection>
469
+ )}
470
+
471
+ {/* Awards */}
472
+ {awards && awards.length > 0 && (
473
+ <ContentSection>
474
+ <StyledSectionTitle>Awards</StyledSectionTitle>
475
+ {awards.map((award, index) => (
476
+ <AwardItem key={index}>
477
+ {award.title && <AwardTitle>{award.title}</AwardTitle>}
478
+ {award.awarder && <Awarder>{award.awarder}</Awarder>}
479
+ {award.date && <StyledDateRange startDate={award.date} />}
480
+ {award.summary && <Description>{award.summary}</Description>}
481
+ </AwardItem>
482
+ ))}
483
+ </ContentSection>
484
+ )}
485
+
486
+ {/* Publications */}
487
+ {publications && publications.length > 0 && (
488
+ <ContentSection>
489
+ <StyledSectionTitle>Publications</StyledSectionTitle>
490
+ {publications.map((pub, index) => (
491
+ <AwardItem key={index}>
492
+ {pub.name && <AwardTitle>{pub.name}</AwardTitle>}
493
+ {pub.publisher && <Awarder>{pub.publisher}</Awarder>}
494
+ {pub.releaseDate && (
495
+ <StyledDateRange startDate={pub.releaseDate} />
496
+ )}
497
+ {pub.summary && <Description>{pub.summary}</Description>}
498
+ {pub.url && (
499
+ <ProjectUrl>
500
+ <Link
501
+ href={safeUrl(pub.url)}
502
+ target={isExternalUrl(pub.url) ? '_blank' : undefined}
503
+ >
504
+ {pub.url.replace(/^https?:\/\//, '')}
505
+ </Link>
506
+ </ProjectUrl>
507
+ )}
508
+ </AwardItem>
509
+ ))}
510
+ </ContentSection>
511
+ )}
512
+
513
+ {/* Languages */}
514
+ {languages && languages.length > 0 && (
515
+ <ContentSection>
516
+ <StyledSectionTitle>Languages</StyledSectionTitle>
517
+ <LanguageList>
518
+ {languages.map((language, index) => (
519
+ <LanguageItem key={index}>
520
+ <LanguageName>{language.language}</LanguageName>
521
+ {language.fluency && (
522
+ <LanguageFluency>— {language.fluency}</LanguageFluency>
523
+ )}
524
+ </LanguageItem>
525
+ ))}
526
+ </LanguageList>
527
+ </ContentSection>
528
+ )}
529
+
530
+ {/* Interests */}
531
+ {interests && interests.length > 0 && (
532
+ <ContentSection>
533
+ <StyledSectionTitle>Interests</StyledSectionTitle>
534
+ <InterestsList>
535
+ {interests.map((interest, index) => (
536
+ <InterestTag key={index}>{interest.name}</InterestTag>
537
+ ))}
538
+ </InterestsList>
539
+ </ContentSection>
540
+ )}
541
+
542
+ {/* References */}
543
+ {references && references.length > 0 && (
544
+ <ContentSection>
545
+ <StyledSectionTitle>References</StyledSectionTitle>
546
+ {references.map((ref, index) => (
547
+ <AwardItem key={index}>
548
+ {ref.name && <AwardTitle>{ref.name}</AwardTitle>}
549
+ {ref.reference && <Description>{ref.reference}</Description>}
550
+ </AwardItem>
551
+ ))}
552
+ </ContentSection>
553
+ )}
554
+ </ResumeContainer>
555
+ );
556
+ }
557
+
558
+ export default Resume;
package/src/index.js ADDED
@@ -0,0 +1,63 @@
1
+ import { renderToString } from 'react-dom/server';
2
+ import { ServerStyleSheet } from 'styled-components';
3
+ import Resume from './Resume.jsx';
4
+ import React from 'react';
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'} - Curriculum Vitae</title>
21
+
22
+ <!-- Google Fonts -->
23
+ <link rel="preconnect" href="https://fonts.googleapis.com">
24
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
25
+ <link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;700&display=swap" rel="stylesheet">
26
+
27
+ ${styles}
28
+
29
+ <style>
30
+ * {
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ html {
35
+ margin: 0;
36
+ padding: 0;
37
+ }
38
+
39
+ body {
40
+ margin: 0;
41
+ padding: 0;
42
+ background: #f8f8f8;
43
+ }
44
+
45
+ @media print {
46
+ body {
47
+ background: white;
48
+ }
49
+
50
+ @page {
51
+ margin: 0.5cm;
52
+ }
53
+ }
54
+ </style>
55
+ </head>
56
+ <body>
57
+ ${html}
58
+ </body>
59
+ </html>`;
60
+ } finally {
61
+ sheet.seal();
62
+ }
63
+ }