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