jsonresume-theme-signal 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.
@@ -0,0 +1,207 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
7
+ <title>{{#if basics.name}}{{#if basics.label}}{{basics.name}} - {{basics.label}}{{else}}{{basics.name}}{{/if}}{{else}}Resume{{/if}}</title>
8
+ <style>
9
+ {{{css}}}
10
+ </style>
11
+ </head>
12
+ <body class="{{#if is_pdf}}pdf-mode{{/if}}">
13
+ <div class="resume-container">
14
+ {{#if (or basics.linkedin basics.github basics.email basics.pdf_url)}}
15
+ <div class="action-links">
16
+ {{#if basics.linkedin}}
17
+ <a href="{{basics.linkedin}}" class="action-link" target="_blank" rel="noopener" aria-label="LinkedIn">
18
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
19
+ <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
20
+ </svg>
21
+ </a>
22
+ {{/if}}
23
+ {{#if basics.github}}
24
+ <a href="{{basics.github}}" class="action-link" target="_blank" rel="noopener" aria-label="GitHub">
25
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
26
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
27
+ </svg>
28
+ </a>
29
+ {{/if}}
30
+ {{#if basics.email}}
31
+ <a href="mailto:{{basics.email}}" class="action-link" aria-label="Email">
32
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
33
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
34
+ <polyline points="22,6 12,13 2,6"/>
35
+ </svg>
36
+ </a>
37
+ {{/if}}
38
+ {{#if basics.pdf_url}}
39
+ <a href="{{basics.pdf_url}}" class="action-link" aria-label="Download PDF">
40
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
42
+ <polyline points="7 10 12 15 17 10"/>
43
+ <line x1="12" y1="15" x2="12" y2="3"/>
44
+ </svg>
45
+ </a>
46
+ {{/if}}
47
+ </div>
48
+ {{/if}}
49
+
50
+ <h1>{{basics.name}}</h1>
51
+ <p>{{#if basics.location}}{{#if basics.location.city}}{{basics.location.city}}{{#if basics.location.region}}, {{basics.location.region}}{{/if}}{{else if basics.location.region}}{{basics.location.region}}{{/if}}{{/if}}{{#if basics.email}} | <a href="mailto:{{basics.email}}">{{basics.email}}</a>{{/if}}{{#if basics.phone}}<span class="print-contact"> | {{basics.phone}}</span>{{/if}}{{#if basics.linkedin}}<span class="print-contact"> | <a href="{{basics.linkedin}}">{{stripUrl basics.linkedin}}</a></span>{{/if}}</p>
52
+
53
+ {{#if basics.summary}}
54
+ <p>{{basics.summary}}</p>
55
+ {{/if}}
56
+
57
+ {{#if work.length}}
58
+ <h2>Professional Experience</h2>
59
+ {{#each work}}
60
+ <div class="company-header">
61
+ <span class="company-name">{{this.name}}</span>
62
+ {{#if this.description}}<span class="company-desc">{{this.description}}</span>{{/if}}
63
+ </div>
64
+ {{#each this.roles}}
65
+ <div class="role-header">
66
+ <span class="role-title">{{this.position}}</span>
67
+ <span class="role-date">{{this.dates}}</span>
68
+ </div>
69
+ {{#if this.highlights.length}}
70
+ <ul>
71
+ {{#each this.highlights}}
72
+ <li>{{this}}</li>
73
+ {{/each}}
74
+ </ul>
75
+ {{/if}}
76
+ {{/each}}
77
+ {{/each}}
78
+ {{/if}}
79
+
80
+ {{#if early_career.length}}
81
+ <h2>Early Career History</h2>
82
+ <table class="skills-table early-career">
83
+ {{#each early_career}}
84
+ <tr>
85
+ <td class="skill-cat">{{this.dates}}</td>
86
+ <td class="skill-list">{{this.details}}</td>
87
+ </tr>
88
+ {{/each}}
89
+ </table>
90
+ {{/if}}
91
+
92
+ {{#if skills.length}}
93
+ <h2>Core Technologies & Architectures</h2>
94
+ <table class="skills-table">
95
+ {{#each skills}}
96
+ <tr>
97
+ <td class="skill-cat">{{this.name}}</td>
98
+ <td class="skill-list">{{join this.keywords ", "}}</td>
99
+ </tr>
100
+ {{/each}}
101
+ </table>
102
+ {{/if}}
103
+
104
+ {{#if education.length}}
105
+ <h2>Education</h2>
106
+ <div class="education">
107
+ {{#each education}}
108
+ <strong>{{this.studyType}}, {{this.area}}</strong>, {{this.institution}}
109
+ {{/each}}
110
+ </div>
111
+ {{/if}}
112
+ </div>
113
+
114
+ <script>
115
+ (function() {
116
+ function autoFit() {
117
+ var container = document.querySelector('.resume-container');
118
+ if (!container) return;
119
+
120
+ var fitPages = "{{fit_pages}}";
121
+ var fitTolerance = parseFloat("{{fit_tolerance}}") || 0.28;
122
+ var density = "{{density}}";
123
+
124
+ if (density === 'compact') {
125
+ document.documentElement.style.setProperty('--print-font-size', '9.5pt');
126
+ document.documentElement.style.setProperty('--print-line-height', '1.36');
127
+ document.documentElement.style.setProperty('--print-section-gap', '10px');
128
+ document.documentElement.style.setProperty('--print-bullet-gap', '3px');
129
+ } else if (density === 'spacious') {
130
+ document.documentElement.style.setProperty('--print-font-size', '10.5pt');
131
+ document.documentElement.style.setProperty('--print-line-height', '1.5');
132
+ document.documentElement.style.setProperty('--print-section-gap', '18px');
133
+ }
134
+
135
+ if (fitPages === 'off' || !fitPages) return;
136
+
137
+ // Temporarily apply pdf-mode during measurement so DOM dimensions match 680px print geometry
138
+ var wasPdfMode = document.body.classList.contains('pdf-mode');
139
+ if (!wasPdfMode) {
140
+ document.body.classList.add('pdf-mode');
141
+ }
142
+
143
+ // Reset scale to 1.0 to measure natural uncompressed print layout
144
+ document.documentElement.style.setProperty('--print-scale', '1');
145
+
146
+ // Letter printable height = 11in - 36mm margins = ~920px
147
+ var pageHeight = 920;
148
+ var naturalHeight = container.getBoundingClientRect().height;
149
+ var currentPages = naturalHeight / pageHeight;
150
+
151
+ var targetPages;
152
+ var parsedFitPages = parseInt(fitPages, 10);
153
+ if (!isNaN(parsedFitPages) && parsedFitPages > 0) {
154
+ targetPages = parsedFitPages;
155
+ } else {
156
+ var basePages = Math.floor(currentPages);
157
+ var overflow = currentPages - basePages;
158
+ if (basePages >= 1 && overflow > 0 && overflow <= fitTolerance) {
159
+ targetPages = basePages;
160
+ } else {
161
+ targetPages = Math.ceil(currentPages);
162
+ }
163
+ }
164
+
165
+ // Budget per page accounting for internal page break slack (e.g. unbroken roles/headers)
166
+ var usablePageBudget = pageHeight - 55;
167
+ var targetMaxHeight = targetPages === 1 ? (pageHeight - 20) : (targetPages * usablePageBudget - 20);
168
+
169
+ if (naturalHeight > targetMaxHeight) {
170
+ var minScale = 0.72;
171
+ var maxScale = 1.0;
172
+ var bestScale = 1.0;
173
+
174
+ for (var iter = 0; iter < 16; iter++) {
175
+ var mid = (minScale + maxScale) / 2;
176
+ document.documentElement.style.setProperty('--print-scale', mid.toFixed(4));
177
+ var h = container.getBoundingClientRect().height;
178
+ if (h <= targetMaxHeight) {
179
+ bestScale = mid;
180
+ minScale = mid;
181
+ } else {
182
+ maxScale = mid;
183
+ }
184
+ }
185
+ document.documentElement.style.setProperty('--print-scale', bestScale.toFixed(4));
186
+ }
187
+
188
+ // Restore original body class if it wasn't rendered with is_pdf
189
+ if (!wasPdfMode) {
190
+ document.body.classList.remove('pdf-mode');
191
+ }
192
+ }
193
+
194
+ if (document.fonts && document.fonts.ready) {
195
+ document.fonts.ready.then(autoFit);
196
+ }
197
+ if (document.readyState === 'loading') {
198
+ document.addEventListener('DOMContentLoaded', autoFit);
199
+ } else {
200
+ autoFit();
201
+ }
202
+ window.addEventListener('load', autoFit);
203
+ window.addEventListener('beforeprint', autoFit);
204
+ })();
205
+ </script>
206
+ </body>
207
+ </html>
package/src/types.ts ADDED
@@ -0,0 +1,119 @@
1
+ export interface ResumeProfile {
2
+ network?: string;
3
+ username?: string;
4
+ url?: string;
5
+ }
6
+
7
+ export interface ResumeLocation {
8
+ address?: string;
9
+ postalCode?: string;
10
+ city?: string;
11
+ countryCode?: string;
12
+ region?: string;
13
+ }
14
+
15
+ export interface ResumeBasics {
16
+ name?: string;
17
+ label?: string;
18
+ image?: string;
19
+ email?: string;
20
+ phone?: string;
21
+ url?: string;
22
+ summary?: string;
23
+ location?: ResumeLocation;
24
+ profiles?: ResumeProfile[];
25
+ linkedin?: string;
26
+ github?: string;
27
+ pdf_url?: string;
28
+ }
29
+
30
+ export interface ResumeWorkRole {
31
+ position?: string;
32
+ startDate?: string;
33
+ endDate?: string;
34
+ summary?: string;
35
+ highlights?: string[];
36
+ dates?: string;
37
+ }
38
+
39
+ export interface ResumeWork {
40
+ name?: string;
41
+ position?: string;
42
+ url?: string;
43
+ startDate?: string;
44
+ endDate?: string;
45
+ summary?: string;
46
+ highlights?: string[];
47
+ description?: string;
48
+ roles?: ResumeWorkRole[];
49
+ }
50
+
51
+ export interface EarlyCareerItem {
52
+ dates?: string;
53
+ details?: string;
54
+ }
55
+
56
+ export interface ResumeSkill {
57
+ name?: string;
58
+ level?: string;
59
+ keywords?: string[];
60
+ }
61
+
62
+ export interface ResumeEducation {
63
+ institution?: string;
64
+ url?: string;
65
+ area?: string;
66
+ studyType?: string;
67
+ startDate?: string;
68
+ endDate?: string;
69
+ score?: string;
70
+ courses?: string[];
71
+ }
72
+
73
+ export interface ResumeCertificate {
74
+ name?: string;
75
+ date?: string;
76
+ issuer?: string;
77
+ url?: string;
78
+ }
79
+
80
+ export interface ResumePublication {
81
+ name?: string;
82
+ publisher?: string;
83
+ releaseDate?: string;
84
+ url?: string;
85
+ summary?: string;
86
+ }
87
+
88
+ export interface ResumeData {
89
+ basics?: ResumeBasics;
90
+ work?: ResumeWork[];
91
+ early_career?: EarlyCareerItem[];
92
+ skills?: ResumeSkill[];
93
+ education?: ResumeEducation[];
94
+ certificates?: ResumeCertificate[];
95
+ publications?: ResumePublication[];
96
+ meta?: Record<string, unknown>;
97
+ [key: string]: unknown;
98
+ }
99
+
100
+ export type PageFitMode = number | "auto" | "off";
101
+ export type LayoutDensity = "compact" | "normal" | "spacious";
102
+
103
+ export interface ThemeFitOptions {
104
+ fitPages?: PageFitMode;
105
+ fitTolerance?: number;
106
+ density?: LayoutDensity;
107
+ }
108
+
109
+ export interface ThemeOptions extends ThemeFitOptions {
110
+ asOfYear?: number;
111
+ is_pdf?: boolean;
112
+ }
113
+
114
+ export interface PreparedResumeData extends ResumeData {
115
+ is_pdf?: boolean;
116
+ fit_pages?: PageFitMode;
117
+ fit_tolerance?: number;
118
+ density?: LayoutDensity;
119
+ }