jsonresume-theme-operations-precision 0.1.0 → 0.3.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/dist/style.css ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Resume Core Design Tokens
3
+ * Framework-agnostic CSS variables for theming
4
+ * Based on 2025 resume best practices and ATS compatibility research
5
+ */
6
+
7
+ :root {
8
+ /* Typography - ATS-friendly fonts */
9
+ --resume-font-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
10
+ --resume-font-serif: Cambria, Georgia, "Times New Roman", serif;
11
+ --resume-font-mono: "Courier New", Courier, monospace;
12
+
13
+ /* Font Sizes - Optimal readability */
14
+ --resume-size-name: 36px; /* Large, prominent name */
15
+ --resume-size-heading: 16px; /* Section titles */
16
+ --resume-size-subheading: 14px; /* Job titles, degrees */
17
+ --resume-size-body: 11px; /* Body text, descriptions */
18
+ --resume-size-small: 10px; /* Dates, locations, metadata */
19
+
20
+ /* Font Weights */
21
+ --resume-weight-normal: 400;
22
+ --resume-weight-medium: 500;
23
+ --resume-weight-semibold: 600;
24
+ --resume-weight-bold: 700;
25
+
26
+ /* Line Heights */
27
+ --resume-line-height-tight: 1.2;
28
+ --resume-line-height-normal: 1.5;
29
+ --resume-line-height-relaxed: 1.75;
30
+
31
+ /* Colors - Professional Theme (default) */
32
+ --resume-color-primary: #1a1a1a;
33
+ --resume-color-secondary: #4a4a4a;
34
+ --resume-color-accent: #2563eb;
35
+ --resume-color-background: #ffffff;
36
+ --resume-color-border: #e5e7eb;
37
+
38
+ /* Spacing - Consistent rhythm */
39
+ --resume-space-section: 24px; /* Between major sections */
40
+ --resume-space-item: 16px; /* Between list items */
41
+ --resume-space-tight: 8px; /* Within items */
42
+ --resume-space-margin: 48px; /* Page margins */
43
+
44
+ /* Layout - Optimal line length */
45
+ --resume-max-width: 660px; /* ~80 chars at body size */
46
+ --resume-column-gap: 24px;
47
+
48
+ /* Border Radius */
49
+ --resume-radius-sm: 4px;
50
+ --resume-radius-md: 8px;
51
+ --resume-radius-lg: 12px;
52
+
53
+ /* Shadows - Subtle depth */
54
+ --resume-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
55
+ --resume-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
56
+ }
57
+
58
+ /* Modern Theme Variant */
59
+ [data-theme="modern"] {
60
+ --resume-color-primary: #0f172a;
61
+ --resume-color-secondary: #475569;
62
+ --resume-color-accent: #8b5cf6;
63
+ --resume-font-sans: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
64
+ }
65
+
66
+ /* Classic Theme Variant */
67
+ [data-theme="classic"] {
68
+ --resume-color-primary: #000000;
69
+ --resume-color-secondary: #333333;
70
+ --resume-color-accent: #0066cc;
71
+ --resume-font-sans: Georgia, "Times New Roman", serif;
72
+ }
73
+
74
+ /* Minimal Theme Variant */
75
+ [data-theme="minimal"] {
76
+ --resume-color-primary: #18181b;
77
+ --resume-color-secondary: #71717a;
78
+ --resume-color-accent: #000000;
79
+ --resume-space-section: 32px;
80
+ --resume-space-item: 20px;
81
+ }
82
+
83
+ /* High Contrast Variant (Accessibility) */
84
+ [data-theme="high-contrast"] {
85
+ --resume-color-primary: #000000;
86
+ --resume-color-secondary: #000000;
87
+ --resume-color-accent: #0000ff;
88
+ --resume-color-background: #ffffff;
89
+ --resume-color-border: #000000;
90
+ }
91
+
92
+ /* Print Styles - ATS-Friendly PDF Export */
93
+ @media print {
94
+ :root {
95
+ /* Optimize for print */
96
+ --resume-space-section: 18px;
97
+ --resume-space-item: 12px;
98
+ }
99
+
100
+ @page {
101
+ size: A4;
102
+ margin: 0.5in;
103
+ }
104
+
105
+ /* Prevent awkward breaks */
106
+ .resume-section {
107
+ page-break-inside: avoid;
108
+ }
109
+
110
+ .resume-item {
111
+ break-inside: avoid;
112
+ }
113
+
114
+ /* Widows and orphans control */
115
+ p, li {
116
+ widows: 3;
117
+ orphans: 3;
118
+ }
119
+
120
+ /* Hyphenation for long words */
121
+ .resume-description {
122
+ hyphens: auto;
123
+ }
124
+
125
+ /* Hide interactive elements */
126
+ .no-print {
127
+ display: none !important;
128
+ }
129
+ }
130
+
131
+ /* RTL Support */
132
+ [dir="rtl"] {
133
+ text-align: right;
134
+ }
135
+
136
+ [dir="rtl"] .resume-item {
137
+ padding-left: 0;
138
+ padding-right: var(--resume-space-tight);
139
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "jsonresume-theme-operations-precision",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Operations professionals - show your systematic approach, process improvements",
5
- "main": "src/index.js",
5
+ "main": "./dist/index.js",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "keywords": [
@@ -18,7 +18,25 @@
18
18
  "react-dom": "^18.0.0 || ^19.0.0"
19
19
  },
20
20
  "dependencies": {
21
+ "marked": "^16.3.0",
21
22
  "styled-components": "^6.1.19",
22
- "@resume/core": "0.1.0"
23
+ "@jsonresume/core": "0.1.0"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "import": "./src/index.jsx",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./dist": {
31
+ "import": "./dist/index.js",
32
+ "default": "./dist/index.js"
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "@vitejs/plugin-react": "^4.3.4",
37
+ "vite": "^5.4.21"
38
+ },
39
+ "scripts": {
40
+ "build": "vite build"
23
41
  }
24
42
  }
package/src/Resume.jsx CHANGED
@@ -1,6 +1,15 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
- import { Section, SectionTitle, DateRange, ContactInfo } from '@resume/core';
3
+ import { marked } from 'marked';
4
+ import {
5
+ Section,
6
+ SectionTitle,
7
+ DateRange,
8
+ ContactInfo,
9
+ } from '@jsonresume/core';
10
+
11
+ const renderMarkdown = (text) =>
12
+ text ? marked.parse(text, { breaks: true }) : '';
4
13
 
5
14
  const Layout = styled.div`
6
15
  max-width: 850px;
@@ -58,7 +67,7 @@ const StyledContactInfo = styled(ContactInfo)`
58
67
  }
59
68
  `;
60
69
 
61
- const Summary = styled.p`
70
+ const Summary = styled.div`
62
71
  font-size: 15px;
63
72
  line-height: 1.6;
64
73
  color: #374151;
@@ -121,7 +130,7 @@ const DateText = styled.div`
121
130
  font-variant-numeric: tabular-nums;
122
131
  `;
123
132
 
124
- const WorkSummary = styled.p`
133
+ const WorkSummary = styled.div`
125
134
  margin: 8px 0;
126
135
  color: #4b5563;
127
136
  line-height: 1.6;
@@ -260,18 +269,23 @@ function Resume({ resume }) {
260
269
  volunteer = [],
261
270
  awards = [],
262
271
  publications = [],
272
+ certificates = [],
263
273
  languages = [],
264
274
  interests = [],
265
275
  references = [],
266
276
  } = resume;
267
277
 
268
278
  return (
269
- <Layout>
279
+ <Layout as="main">
270
280
  <Header>
271
281
  <Name>{basics.name}</Name>
272
282
  {basics.label && <Label>{basics.label}</Label>}
273
283
  <StyledContactInfo basics={basics} />
274
- {basics.summary && <Summary>{basics.summary}</Summary>}
284
+ {basics.summary && (
285
+ <Summary
286
+ dangerouslySetInnerHTML={{ __html: renderMarkdown(basics.summary) }}
287
+ />
288
+ )}
275
289
  </Header>
276
290
 
277
291
  {work?.length > 0 && (
@@ -288,11 +302,24 @@ function Resume({ resume }) {
288
302
  <DateRange startDate={job.startDate} endDate={job.endDate} />
289
303
  </DateText>
290
304
  </WorkHeader>
291
- {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
305
+ {job.summary && (
306
+ <WorkSummary
307
+ dangerouslySetInnerHTML={{
308
+ __html: renderMarkdown(job.summary),
309
+ }}
310
+ />
311
+ )}
292
312
  {job.highlights?.length > 0 && (
293
313
  <Highlights>
294
314
  {job.highlights.map((highlight, i) => (
295
- <li key={i}>{highlight}</li>
315
+ <li
316
+ key={i}
317
+ dangerouslySetInnerHTML={{
318
+ __html: marked.parseInline(highlight, {
319
+ breaks: true,
320
+ }),
321
+ }}
322
+ />
296
323
  ))}
297
324
  </Highlights>
298
325
  )}
@@ -354,12 +381,23 @@ function Resume({ resume }) {
354
381
  )}
355
382
  </ProjectHeader>
356
383
  {project.description && (
357
- <WorkSummary>{project.description}</WorkSummary>
384
+ <WorkSummary
385
+ dangerouslySetInnerHTML={{
386
+ __html: renderMarkdown(project.description),
387
+ }}
388
+ />
358
389
  )}
359
390
  {project.highlights?.length > 0 && (
360
391
  <Highlights>
361
392
  {project.highlights.map((highlight, i) => (
362
- <li key={i}>{highlight}</li>
393
+ <li
394
+ key={i}
395
+ dangerouslySetInnerHTML={{
396
+ __html: marked.parseInline(highlight, {
397
+ breaks: true,
398
+ }),
399
+ }}
400
+ />
363
401
  ))}
364
402
  </Highlights>
365
403
  )}
@@ -387,11 +425,24 @@ function Resume({ resume }) {
387
425
  </DateText>
388
426
  )}
389
427
  </WorkHeader>
390
- {vol.summary && <WorkSummary>{vol.summary}</WorkSummary>}
428
+ {vol.summary && (
429
+ <WorkSummary
430
+ dangerouslySetInnerHTML={{
431
+ __html: renderMarkdown(vol.summary),
432
+ }}
433
+ />
434
+ )}
391
435
  {vol.highlights?.length > 0 && (
392
436
  <Highlights>
393
437
  {vol.highlights.map((highlight, i) => (
394
- <li key={i}>{highlight}</li>
438
+ <li
439
+ key={i}
440
+ dangerouslySetInnerHTML={{
441
+ __html: marked.parseInline(highlight, {
442
+ breaks: true,
443
+ }),
444
+ }}
445
+ />
395
446
  ))}
396
447
  </Highlights>
397
448
  )}
@@ -408,7 +459,13 @@ function Resume({ resume }) {
408
459
  <Institution>{award.title}</Institution>
409
460
  {award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
410
461
  {award.date && <EducationDate>{award.date}</EducationDate>}
411
- {award.summary && <WorkSummary>{award.summary}</WorkSummary>}
462
+ {award.summary && (
463
+ <WorkSummary
464
+ dangerouslySetInnerHTML={{
465
+ __html: renderMarkdown(award.summary),
466
+ }}
467
+ />
468
+ )}
412
469
  </EducationItem>
413
470
  ))}
414
471
  </Section>
@@ -424,7 +481,26 @@ function Resume({ resume }) {
424
481
  {pub.releaseDate && (
425
482
  <EducationDate>{pub.releaseDate}</EducationDate>
426
483
  )}
427
- {pub.summary && <WorkSummary>{pub.summary}</WorkSummary>}
484
+ {pub.summary && (
485
+ <WorkSummary
486
+ dangerouslySetInnerHTML={{
487
+ __html: renderMarkdown(pub.summary),
488
+ }}
489
+ />
490
+ )}
491
+ </EducationItem>
492
+ ))}
493
+ </Section>
494
+ )}
495
+
496
+ {certificates?.length > 0 && (
497
+ <Section>
498
+ <StyledSectionTitle>Certifications</StyledSectionTitle>
499
+ {certificates.map((cert, index) => (
500
+ <EducationItem key={index}>
501
+ <Institution>{cert.name}</Institution>
502
+ {cert.issuer && <Degree>Issued by {cert.issuer}</Degree>}
503
+ {cert.date && <EducationDate>{cert.date}</EducationDate>}
428
504
  </EducationItem>
429
505
  ))}
430
506
  </Section>
package/vite.config.js ADDED
@@ -0,0 +1,33 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ build: {
7
+ ssr: true,
8
+ target: 'node18',
9
+ outDir: './dist',
10
+ emptyOutDir: true,
11
+ minify: false,
12
+ lib: {
13
+ entry: './src/index.jsx',
14
+ formats: ['es'],
15
+ fileName: 'index',
16
+ },
17
+ rollupOptions: {
18
+ external: ['react', 'react-dom', 'react-dom/server', 'react/jsx-runtime'],
19
+ output: {
20
+ exports: 'named',
21
+ },
22
+ },
23
+ },
24
+ ssr: {
25
+ // Force bundle these packages instead of externalizing
26
+ noExternal: [
27
+ 'styled-components',
28
+ '@emotion/is-prop-valid',
29
+ 'stylis',
30
+ 'shallowequal',
31
+ ],
32
+ },
33
+ });
File without changes