jsonresume-theme-french-atelier 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 +560 -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=Playfair+Display:wght@400;700;900&family=Work+Sans:wght@400;500;600&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-french-atelier",
3
+ "version": "0.1.0",
4
+ "description": "Artistic precision with editorial-inspired layout, high-contrast serif titles, and deep plum accents",
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,560 @@
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: 750px;
16
+ margin: 0 auto;
17
+ padding: 56px 44px;
18
+ background: #fafafa;
19
+ font-family: 'Work Sans', -apple-system, BlinkMacSystemFont, sans-serif;
20
+ color: #3a3a3a;
21
+ line-height: 1.75;
22
+ font-size: 14.5px;
23
+
24
+ @media print {
25
+ background: white;
26
+ padding: 36px 28px;
27
+ }
28
+ `;
29
+
30
+ const Header = styled.header`
31
+ margin-bottom: 56px;
32
+ padding-bottom: 40px;
33
+ border-bottom: 2px solid #3b0a45;
34
+ position: relative;
35
+
36
+ &::after {
37
+ content: '';
38
+ position: absolute;
39
+ bottom: -1px;
40
+ left: 0;
41
+ width: 120px;
42
+ height: 2px;
43
+ background: #3b0a45;
44
+ }
45
+ `;
46
+
47
+ const Name = styled.h1`
48
+ font-family: 'Playfair Display', Georgia, serif;
49
+ font-size: 56px;
50
+ font-weight: 900;
51
+ margin: 0 0 8px 0;
52
+ color: #1a1a1a;
53
+ letter-spacing: -1.5px;
54
+ line-height: 1.1;
55
+ `;
56
+
57
+ const Tagline = styled.div`
58
+ font-size: 18px;
59
+ color: #3b0a45;
60
+ margin: 0 0 24px 0;
61
+ font-weight: 600;
62
+ letter-spacing: 0.5px;
63
+ text-transform: uppercase;
64
+ font-size: 13px;
65
+ `;
66
+
67
+ const Summary = styled.p`
68
+ font-size: 15px;
69
+ line-height: 1.8;
70
+ color: #4a4a4a;
71
+ margin: 24px 0 0 0;
72
+ max-width: 650px;
73
+ `;
74
+
75
+ const StyledContactInfo = styled(ContactInfo)`
76
+ display: grid;
77
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
78
+ gap: 12px 24px;
79
+ margin-top: 24px;
80
+ font-size: 13px;
81
+
82
+ a {
83
+ color: #3b0a45;
84
+ text-decoration: none;
85
+ font-weight: 500;
86
+ position: relative;
87
+ padding-bottom: 2px;
88
+ transition: color 0.2s;
89
+
90
+ &::after {
91
+ content: '';
92
+ position: absolute;
93
+ bottom: 0;
94
+ left: 0;
95
+ width: 0;
96
+ height: 1px;
97
+ background: #3b0a45;
98
+ transition: width 0.3s ease;
99
+ }
100
+
101
+ &:hover {
102
+ color: #2d0836;
103
+
104
+ &::after {
105
+ width: 100%;
106
+ }
107
+ }
108
+ }
109
+ `;
110
+
111
+ const StyledSection = styled(Section)`
112
+ margin-bottom: 48px;
113
+ `;
114
+
115
+ const StyledSectionTitle = styled(SectionTitle)`
116
+ font-family: 'Playfair Display', Georgia, serif;
117
+ font-size: 32px;
118
+ font-weight: 700;
119
+ color: #1a1a1a;
120
+ margin: 0 0 32px 0;
121
+ letter-spacing: -0.5px;
122
+ padding-bottom: 12px;
123
+ border-bottom: 1px solid #d4d4d4;
124
+ position: relative;
125
+
126
+ &::after {
127
+ content: '';
128
+ position: absolute;
129
+ bottom: -1px;
130
+ left: 0;
131
+ width: 80px;
132
+ height: 1px;
133
+ background: #3b0a45;
134
+ }
135
+ `;
136
+
137
+ const WorkItem = styled.div`
138
+ margin-bottom: 36px;
139
+ padding-left: 20px;
140
+ border-left: 1px solid #e0e0e0;
141
+ position: relative;
142
+
143
+ &::before {
144
+ content: '';
145
+ position: absolute;
146
+ left: -4px;
147
+ top: 6px;
148
+ width: 7px;
149
+ height: 7px;
150
+ border-radius: 50%;
151
+ background: #3b0a45;
152
+ }
153
+
154
+ &:last-child {
155
+ margin-bottom: 0;
156
+ }
157
+ `;
158
+
159
+ const ItemHeader = styled.div`
160
+ margin-bottom: 12px;
161
+ `;
162
+
163
+ const ItemTitleRow = styled.div`
164
+ display: flex;
165
+ justify-content: space-between;
166
+ align-items: baseline;
167
+ gap: 16px;
168
+ margin-bottom: 6px;
169
+ `;
170
+
171
+ const ItemTitle = styled.h3`
172
+ font-family: 'Playfair Display', Georgia, serif;
173
+ font-size: 22px;
174
+ font-weight: 700;
175
+ margin: 0;
176
+ color: #1a1a1a;
177
+ flex: 1;
178
+ `;
179
+
180
+ const ItemDate = styled.div`
181
+ font-size: 12px;
182
+ font-weight: 600;
183
+ color: #737373;
184
+ white-space: nowrap;
185
+ text-transform: uppercase;
186
+ letter-spacing: 0.5px;
187
+ `;
188
+
189
+ const ItemSubtitle = styled.div`
190
+ font-size: 16px;
191
+ font-weight: 600;
192
+ color: #3b0a45;
193
+ margin-bottom: 4px;
194
+ `;
195
+
196
+ const ItemLocation = styled.div`
197
+ font-size: 13px;
198
+ color: #737373;
199
+ margin-bottom: 12px;
200
+ font-style: italic;
201
+ `;
202
+
203
+ const ItemDescription = styled.p`
204
+ font-size: 14.5px;
205
+ line-height: 1.75;
206
+ color: #4a4a4a;
207
+ margin: 12px 0;
208
+ `;
209
+
210
+ const HighlightsList = styled.ul`
211
+ margin: 12px 0 0 0;
212
+ padding-left: 20px;
213
+ list-style: none;
214
+
215
+ li {
216
+ position: relative;
217
+ margin-bottom: 8px;
218
+ font-size: 14.5px;
219
+ line-height: 1.7;
220
+ color: #4a4a4a;
221
+ padding-left: 12px;
222
+
223
+ &::before {
224
+ content: '—';
225
+ position: absolute;
226
+ left: 0;
227
+ color: #3b0a45;
228
+ font-weight: 600;
229
+ }
230
+ }
231
+ `;
232
+
233
+ const SkillsContainer = styled.div`
234
+ display: grid;
235
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
236
+ gap: 28px;
237
+ `;
238
+
239
+ const SkillCategory = styled.div`
240
+ h4 {
241
+ font-family: 'Playfair Display', Georgia, serif;
242
+ font-size: 18px;
243
+ font-weight: 700;
244
+ color: #1a1a1a;
245
+ margin: 0 0 14px 0;
246
+ }
247
+ `;
248
+
249
+ const StyledBadgeList = styled(BadgeList)`
250
+ display: flex;
251
+ flex-wrap: wrap;
252
+ gap: 8px;
253
+ `;
254
+
255
+ const StyledBadge = styled(Badge)`
256
+ font-size: 13px;
257
+ padding: 6px 14px;
258
+ background: #f5f0f6;
259
+ border: 1px solid #3b0a45;
260
+ color: #3b0a45;
261
+ font-weight: 500;
262
+ border-radius: 0;
263
+ transition: all 0.2s;
264
+
265
+ &:hover {
266
+ background: #3b0a45;
267
+ color: white;
268
+ }
269
+ `;
270
+
271
+ const SimpleCard = styled.div`
272
+ margin-bottom: 24px;
273
+ padding-left: 20px;
274
+ border-left: 1px solid #e0e0e0;
275
+
276
+ h4 {
277
+ font-family: 'Playfair Display', Georgia, serif;
278
+ font-size: 18px;
279
+ font-weight: 700;
280
+ color: #1a1a1a;
281
+ margin: 0 0 8px 0;
282
+ }
283
+
284
+ p {
285
+ font-size: 13px;
286
+ margin: 4px 0 0 0;
287
+ color: #737373;
288
+ }
289
+
290
+ &:last-child {
291
+ margin-bottom: 0;
292
+ }
293
+ `;
294
+
295
+ const EducationItem = styled(WorkItem)``;
296
+
297
+ const ProjectItem = styled(WorkItem)``;
298
+
299
+ function Resume({ resume }) {
300
+ const {
301
+ basics = {},
302
+ work = [],
303
+ education = [],
304
+ skills = [],
305
+ projects = [],
306
+ volunteer = [],
307
+ awards = [],
308
+ publications = [],
309
+ languages = [],
310
+ interests = [],
311
+ references = [],
312
+ } = resume;
313
+
314
+ return (
315
+ <Layout>
316
+ <Header>
317
+ {basics.name && <Name>{basics.name}</Name>}
318
+ {basics.label && <Tagline>{basics.label}</Tagline>}
319
+ <StyledContactInfo
320
+ email={basics.email}
321
+ phone={basics.phone}
322
+ url={basics.url}
323
+ location={basics.location}
324
+ profiles={basics.profiles}
325
+ />
326
+ {basics.summary && <Summary>{basics.summary}</Summary>}
327
+ </Header>
328
+
329
+ {work.length > 0 && (
330
+ <StyledSection>
331
+ <StyledSectionTitle>Experience</StyledSectionTitle>
332
+ {work.map((job, index) => (
333
+ <WorkItem key={index}>
334
+ <ItemHeader>
335
+ <ItemTitleRow>
336
+ <ItemTitle>{job.position || job.name}</ItemTitle>
337
+ <ItemDate>
338
+ <DateRange
339
+ startDate={job.startDate}
340
+ endDate={job.endDate}
341
+ />
342
+ </ItemDate>
343
+ </ItemTitleRow>
344
+ {job.name && <ItemSubtitle>{job.name}</ItemSubtitle>}
345
+ {job.location && <ItemLocation>{job.location}</ItemLocation>}
346
+ </ItemHeader>
347
+ {job.summary && <ItemDescription>{job.summary}</ItemDescription>}
348
+ {job.highlights && job.highlights.length > 0 && (
349
+ <HighlightsList>
350
+ {job.highlights.map((highlight, i) => (
351
+ <li key={i}>{highlight}</li>
352
+ ))}
353
+ </HighlightsList>
354
+ )}
355
+ </WorkItem>
356
+ ))}
357
+ </StyledSection>
358
+ )}
359
+
360
+ {education.length > 0 && (
361
+ <StyledSection>
362
+ <StyledSectionTitle>Education</StyledSectionTitle>
363
+ {education.map((edu, index) => (
364
+ <EducationItem key={index}>
365
+ <ItemHeader>
366
+ <ItemTitleRow>
367
+ <ItemTitle>{edu.institution}</ItemTitle>
368
+ <ItemDate>
369
+ <DateRange
370
+ startDate={edu.startDate}
371
+ endDate={edu.endDate}
372
+ />
373
+ </ItemDate>
374
+ </ItemTitleRow>
375
+ {edu.studyType && edu.area && (
376
+ <ItemSubtitle>
377
+ {edu.studyType} in {edu.area}
378
+ </ItemSubtitle>
379
+ )}
380
+ {edu.score && <ItemLocation>Score: {edu.score}</ItemLocation>}
381
+ </ItemHeader>
382
+ {edu.courses && edu.courses.length > 0 && (
383
+ <HighlightsList>
384
+ {edu.courses.map((course, i) => (
385
+ <li key={i}>{course}</li>
386
+ ))}
387
+ </HighlightsList>
388
+ )}
389
+ </EducationItem>
390
+ ))}
391
+ </StyledSection>
392
+ )}
393
+
394
+ {skills.length > 0 && (
395
+ <StyledSection>
396
+ <StyledSectionTitle>Skills</StyledSectionTitle>
397
+ <SkillsContainer>
398
+ {skills.map((skill, index) => (
399
+ <SkillCategory key={index}>
400
+ <h4>{skill.name}</h4>
401
+ <StyledBadgeList>
402
+ {skill.keywords?.map((keyword, i) => (
403
+ <StyledBadge key={i}>{keyword}</StyledBadge>
404
+ ))}
405
+ </StyledBadgeList>
406
+ </SkillCategory>
407
+ ))}
408
+ </SkillsContainer>
409
+ </StyledSection>
410
+ )}
411
+
412
+ {projects.length > 0 && (
413
+ <StyledSection>
414
+ <StyledSectionTitle>Projects</StyledSectionTitle>
415
+ {projects.map((project, index) => (
416
+ <ProjectItem key={index}>
417
+ <ItemHeader>
418
+ <ItemTitleRow>
419
+ <ItemTitle>
420
+ {project.url ? (
421
+ <Link href={safeUrl(project.url)}>{project.name}</Link>
422
+ ) : (
423
+ project.name
424
+ )}
425
+ </ItemTitle>
426
+ <ItemDate>
427
+ <DateRange
428
+ startDate={project.startDate}
429
+ endDate={project.endDate}
430
+ />
431
+ </ItemDate>
432
+ </ItemTitleRow>
433
+ {project.type && <ItemLocation>{project.type}</ItemLocation>}
434
+ </ItemHeader>
435
+ {project.description && (
436
+ <ItemDescription>{project.description}</ItemDescription>
437
+ )}
438
+ {project.highlights && project.highlights.length > 0 && (
439
+ <HighlightsList>
440
+ {project.highlights.map((highlight, i) => (
441
+ <li key={i}>{highlight}</li>
442
+ ))}
443
+ </HighlightsList>
444
+ )}
445
+ </ProjectItem>
446
+ ))}
447
+ </StyledSection>
448
+ )}
449
+
450
+ {awards.length > 0 && (
451
+ <StyledSection>
452
+ <StyledSectionTitle>Awards</StyledSectionTitle>
453
+ {awards.map((award, index) => (
454
+ <SimpleCard key={index}>
455
+ <h4>{award.title}</h4>
456
+ <p>
457
+ {award.awarder} {award.date && `• ${award.date}`}
458
+ </p>
459
+ {award.summary && <p>{award.summary}</p>}
460
+ </SimpleCard>
461
+ ))}
462
+ </StyledSection>
463
+ )}
464
+
465
+ {publications.length > 0 && (
466
+ <StyledSection>
467
+ <StyledSectionTitle>Publications</StyledSectionTitle>
468
+ {publications.map((pub, index) => (
469
+ <SimpleCard key={index}>
470
+ <h4>
471
+ {pub.url ? (
472
+ <Link href={safeUrl(pub.url)}>{pub.name}</Link>
473
+ ) : (
474
+ pub.name
475
+ )}
476
+ </h4>
477
+ <p>
478
+ {pub.publisher} {pub.releaseDate && `• ${pub.releaseDate}`}
479
+ </p>
480
+ {pub.summary && <p>{pub.summary}</p>}
481
+ </SimpleCard>
482
+ ))}
483
+ </StyledSection>
484
+ )}
485
+
486
+ {volunteer.length > 0 && (
487
+ <StyledSection>
488
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
489
+ {volunteer.map((vol, index) => (
490
+ <WorkItem key={index}>
491
+ <ItemHeader>
492
+ <ItemTitleRow>
493
+ <ItemTitle>{vol.position}</ItemTitle>
494
+ <ItemDate>
495
+ <DateRange
496
+ startDate={vol.startDate}
497
+ endDate={vol.endDate}
498
+ />
499
+ </ItemDate>
500
+ </ItemTitleRow>
501
+ {vol.organization && (
502
+ <ItemSubtitle>{vol.organization}</ItemSubtitle>
503
+ )}
504
+ </ItemHeader>
505
+ {vol.summary && <ItemDescription>{vol.summary}</ItemDescription>}
506
+ {vol.highlights && vol.highlights.length > 0 && (
507
+ <HighlightsList>
508
+ {vol.highlights.map((highlight, i) => (
509
+ <li key={i}>{highlight}</li>
510
+ ))}
511
+ </HighlightsList>
512
+ )}
513
+ </WorkItem>
514
+ ))}
515
+ </StyledSection>
516
+ )}
517
+
518
+ {languages.length > 0 && (
519
+ <StyledSection>
520
+ <StyledSectionTitle>Languages</StyledSectionTitle>
521
+ <StyledBadgeList>
522
+ {languages.map((lang, index) => (
523
+ <StyledBadge key={index}>
524
+ {lang.language} {lang.fluency && `— ${lang.fluency}`}
525
+ </StyledBadge>
526
+ ))}
527
+ </StyledBadgeList>
528
+ </StyledSection>
529
+ )}
530
+
531
+ {interests.length > 0 && (
532
+ <StyledSection>
533
+ <StyledSectionTitle>Interests</StyledSectionTitle>
534
+ {interests.map((interest, index) => (
535
+ <SimpleCard key={index}>
536
+ <h4>{interest.name}</h4>
537
+ {interest.keywords && interest.keywords.length > 0 && (
538
+ <p>{interest.keywords.join(', ')}</p>
539
+ )}
540
+ </SimpleCard>
541
+ ))}
542
+ </StyledSection>
543
+ )}
544
+
545
+ {references.length > 0 && (
546
+ <StyledSection>
547
+ <StyledSectionTitle>References</StyledSectionTitle>
548
+ {references.map((ref, index) => (
549
+ <SimpleCard key={index}>
550
+ <h4>{ref.name}</h4>
551
+ {ref.reference && <p>{ref.reference}</p>}
552
+ </SimpleCard>
553
+ ))}
554
+ </StyledSection>
555
+ )}
556
+ </Layout>
557
+ );
558
+ }
559
+
560
+ export default Resume;