├── .editorconfig
├── .github
└── workflows
│ ├── pulls.yml
│ └── release.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── assets
└── preview.png
├── index.js
├── package-lock.json
├── package.json
├── resume.handlebars
├── style.css
└── test
└── fixture.resume.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | curly_bracket_next_line = false
11 | spaces_around_operators = true
12 |
13 | [*.bat]
14 | end_of_line = crlf
15 |
16 | [*.cs]
17 | curly_bracket_next_line = true
18 |
19 | [*.{cpp,cs,gradle,java,kt,py,rs}]
20 | indent_size = 4
21 |
22 | [*.{js,ts}]
23 | quote_type = single
24 |
25 | [*.{markdown,md}]
26 | trim_trailing_whitespace = false
27 |
28 | [*.tsv]
29 | indent_style = tab
30 |
--------------------------------------------------------------------------------
/.github/workflows/pulls.yml:
--------------------------------------------------------------------------------
1 | name: Test Run
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-node@v3
17 | with:
18 | node-version: 20
19 | - run: npm ci
20 | - run: npx resume export --theme . --resume ./test/fixture.resume.json /tmp/resume.html
21 | - run: npx resume export --theme . --resume ./test/fixture.resume.json /tmp/resume.pdf
22 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Node.js Package
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | publish-npm:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | - uses: actions/setup-node@v3
13 | with:
14 | node-version: 20
15 | registry-url: https://registry.npmjs.org/
16 | - run: npm publish --access public
17 | env:
18 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | resume.json
4 | *.pdf
5 | *.html
6 |
7 | .vscode/
8 | *.code-workspace
9 | .history/
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github/
2 | assets/
3 | test/
4 | .editorconfig
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2014 James Spencer
4 | Copyright (c) 2022-2023 JSON Resume and Contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Class Theme for JSON Resume
2 |
3 | [](https://matrix.to/#/#json-resume:one.ems.host)
4 | [](https://www.npmjs.com/package/@jsonresume/jsonresume-theme-class)
5 |
6 | A modern theme for [JSON Resume](http://jsonresume.org/) which is self-contained. The content of the résumé will work offline and can be hosted without depending on or making requests to third-party servers.
7 |
8 | ## Usage
9 |
10 | ```sh
11 | # Install resume-cli via npm, yarn, pnpm, or whatever package manager you want
12 | npm install --global resume-cli
13 |
14 | # Install @jsonresume/jsonresume-theme-class in the directory resume.json is in
15 | npm install @jsonresume/jsonresume-theme-class
16 |
17 | # Export as an HTML page, ready to be served by any web server
18 | resume export --theme @jsonresume/jsonresume-theme-class index.html
19 |
20 | # Export a PDF document, it's recommended to use your name as the file name
21 | resume export --theme @jsonresume/jsonresume-theme-class your-name.pdf
22 | ```
23 |
24 | ### Notes
25 |
26 | * It's recommended to declare the `meta.language` property in your JSON Resume for accessibility. This is the [BCP47 tag](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/lang#language_tag_syntax) for the language your your résumé is written in. For example, `en` for English.
27 |
28 | ## Features
29 |
30 | ### JSON Resume 1.0.0
31 |
32 | This supports the JSON Resume 1.0.0 spec, and is backward compatible with earlier versions.
33 |
34 | ### Application Tracking System (ATS) Friendly
35 |
36 | Many companies and recruiters use [ATS](https://en.wikipedia.org/wiki/Applicant_tracking_system) systems that [parse CV's](https://en.wikipedia.org/wiki/R%C3%A9sum%C3%A9_parsing) and extract the information into a standard format. Part of maintaining this theme includes reviewing this and adhering to standard practices when building the résumé.
37 |
38 | ### Markdown
39 |
40 | You can use inline Markdown on properties to make text bold, italic, or link them to external pages. This namely applies to the `summary` and `highlights` properties in the JSON Resume schema.
41 |
42 | ### Open Graph Protocol
43 |
44 | Populates the `head` of the HTML document with [Open Graph](https://ogp.me/) tags. This allows social media platforms and instant messengers to create embeds when your résumé is shared.
45 |
46 | ### Dark Mode
47 |
48 | Includes a dark mode, and uses the [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS property to provide a positive user-experience.
49 |
50 | ### Optimized
51 |
52 | This theme makes no external connections, doesn't embed scripts, and is lightweight by design. Both HTML and PDF exports will be minimal.
53 |
54 | ## Preview
55 |
56 | 
57 |
--------------------------------------------------------------------------------
/assets/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsonresume/jsonresume-theme-class/b7cc0ad149ab0ecdd08f6916b43eac0ca88b0e62/assets/preview.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const Handlebars = require("handlebars");
3 | const { marked } = require("marked");
4 | const { minify } = require('html-minifier');
5 |
6 | /**
7 | * Custom renderer for marked, namely to disable unwanted features.
8 | * We only want to allow basic inline elements, like links, bold, or inline-code.
9 | *
10 | * @type {object}
11 | */
12 | const renderer = {
13 | heading(text) {
14 | return text;
15 | },
16 | html(html) {
17 | return html;
18 | },
19 | hr() {
20 | return '';
21 | },
22 | list(body) {
23 | return body;
24 | },
25 | listitem(text) {
26 | return text;
27 | },
28 | br() {
29 | return '';
30 | },
31 | paragraph(text) {
32 | return text;
33 | }
34 | }
35 |
36 | marked.use({ renderer });
37 |
38 | /**
39 | * Plugins to enable to minify HTML after generating from the template.
40 | */
41 | const minifyOptions = {
42 | collapseBooleanAttributes: true,
43 | collapseWhitespace: true,
44 | decodeEntities: true,
45 | minifyCSS: true,
46 | removeComments: true,
47 | removeRedundantAttributes: true,
48 | sortAttributes: true,
49 | sortClassName: true,
50 | };
51 |
52 | Handlebars.registerHelper("date", (body) => {
53 | if (!body) {
54 | return "Present"
55 | }
56 |
57 | const date = new Date(body);
58 |
59 | const datetime = date.toISOString();
60 | const localeString = date.toLocaleDateString('en-US', {
61 | month: "short",
62 | year: "numeric"
63 | });
64 |
65 | return ``;
66 | });
67 |
68 | Handlebars.registerHelper("markdown", (body) => {
69 | return marked.parse(body);
70 | });
71 |
72 | Handlebars.registerHelper("link", (body) => {
73 | const parsed = new URL(body);
74 | const host = (parsed.host.startsWith('www.')) ? parsed.host.substring(4) : parsed.host;
75 | return `${host}`;
76 | });
77 |
78 | /**
79 | * @param {Object} resume
80 | * @returns {string}
81 | */
82 | function render(resume) {
83 | const css = fs.readFileSync(__dirname + "/style.css", "utf-8");
84 | const template = fs.readFileSync(__dirname + "/resume.handlebars", "utf-8");
85 | const { profiles } = resume.basics;
86 |
87 | if (Array.isArray(profiles)) {
88 | const xTwitter = profiles.find((profile) => {
89 | const name = profile.network.toLowerCase();
90 | return name === 'x' || name === 'twitter';
91 | });
92 |
93 | if (xTwitter) {
94 | let { username, url } = xTwitter;
95 |
96 | if (!username && url) {
97 | const match = url.match(/https?:\/\/.+?\/(\w{1,15})/);
98 |
99 | if (match.length == 2) {
100 | username = match[1];
101 | }
102 | }
103 |
104 | if (username && !username.startsWith('@')) {
105 | username = `@${username}`;
106 | }
107 |
108 | resume.custom = {
109 | xTwitterHandle: username
110 | }
111 | }
112 | }
113 |
114 | const html = Handlebars.compile(template)({
115 | css,
116 | resume
117 | });
118 |
119 | return minify(html, minifyOptions);
120 | }
121 |
122 | module.exports = {
123 | render
124 | };
125 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jsonresume/jsonresume-theme-class",
3 | "version": "0.4.2",
4 | "description": "Class Theme for JSON Resume",
5 | "author": "Seth Falco",
6 | "license": "MIT",
7 | "private": false,
8 | "keywords": [
9 | "resume",
10 | "cv",
11 | "handlebars",
12 | "jsonresume",
13 | "jsonresume-theme",
14 | "jsonresume-theme-class"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/jsonresume/jsonresume-theme-class"
19 | },
20 | "scripts": {
21 | "serve": "resume serve --theme . --resume ./test/fixture.resume.json",
22 | "build:pdf": "resume export --theme . --resume ./test/fixture.resume.json resume.pdf",
23 | "build:html": "resume export --theme . --resume ./test/fixture.resume.json resume.html"
24 | },
25 | "dependencies": {
26 | "handlebars": "^4.7.8",
27 | "html-minifier": "^4.0.0",
28 | "marked": "^11.1.1"
29 | },
30 | "devDependencies": {
31 | "resume-cli": "^3.0.8"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/resume.handlebars:
--------------------------------------------------------------------------------
1 |
2 | {{#if resume.meta.language}}
3 |
4 | {{else}}
5 |
6 | {{/if}}
7 |
8 | {{#if resume.basics.name}}
9 | {{resume.basics.name}} - CV
10 |
11 | {{else}}
12 | CV
13 | {{/if}}
14 |
15 | {{#if resume.basics.summary}}
16 |
17 |
18 | {{/if}}
19 |
20 | {{#if resume.basics.label}}
21 |
22 | {{/if}}
23 |
24 | {{#if resume.basics.image}}
25 |
26 |
27 | {{#if resume.basics.name}}
28 |
29 | {{/if}}
30 | {{/if}}
31 |
32 | {{#if resume.custom.xTwitterHandle}}
33 |
34 | {{/if}}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {{#resume.basics}}
46 |
47 | {{name}}
48 | {{#if label}}
49 | {{label}}
50 | {{/if}}
51 |
52 |
53 |
54 |
55 |
84 | {{#if summary}}
85 |
86 |
About
87 |
{{{markdown summary}}}
88 |
89 | {{/if}}
90 | {{#if profiles.length}}
91 |
92 | {{#each profiles}}
93 |
94 | {{#if network}}
95 |
96 | {{network}}
97 |
98 | {{/if}}
99 |
112 |
113 | {{/each}}
114 |
115 | {{/if}}
116 |
117 | {{/resume.basics}}
118 |
119 | {{#if resume.work.length}}
120 |
121 | Work Experience
122 | {{#each resume.work}}
123 |
124 | {{#if name}}
125 |
126 | {{name}}
127 |
128 | {{else if company}}
129 |
130 | {{company}}
131 |
132 | {{/if}}
133 |
134 |
135 | {{#if position}}
136 |
137 | {{position}}
138 |
139 | {{/if}}
140 |
141 |
142 | {{#if startDate}}
143 | {{{date startDate}}} - {{{date endDate}}}
144 | {{/if}}
145 |
146 |
147 | {{#if url}}
148 |
149 | {{{link url}}}
150 |
151 | {{else if website}}
152 |
155 | {{/if}}
156 |
157 |
158 | {{#if summary}}
159 |
160 |
{{{markdown summary}}}
161 |
162 | {{/if}}
163 | {{#if highlights.length}}
164 |
165 | {{#each highlights}}
166 | - {{{markdown .}}}
167 | {{/each}}
168 |
169 | {{/if}}
170 |
171 | {{/each}}
172 |
173 | {{/if}}
174 |
175 | {{#if resume.volunteer.length}}
176 |
177 | Volunteer
178 | {{#each resume.volunteer}}
179 |
180 | {{#if organization}}
181 |
182 | {{organization}}
183 |
184 | {{/if}}
185 |
186 |
187 | {{#if position}}
188 |
189 | {{position}}
190 |
191 | {{/if}}
192 |
193 |
194 | {{#if startDate}}
195 |
196 | {{{date startDate}}}
197 |
198 |
199 | - {{{date endDate}}}
200 |
201 | {{/if}}
202 |
203 |
204 | {{#if url}}
205 |
206 | {{{link url}}}
207 |
208 | {{else if website}}
209 |
212 | {{/if}}
213 |
214 |
215 | {{#if summary}}
216 |
217 |
{{{markdown summary}}}
218 |
219 | {{/if}}
220 | {{#if highlights.length}}
221 |
222 | {{#each highlights}}
223 | - {{{markdown .}}}
224 | {{/each}}
225 |
226 | {{/if}}
227 |
228 | {{/each}}
229 |
230 | {{/if}}
231 |
232 | {{#if resume.education.length}}
233 |
234 | Education
235 | {{#each resume.education}}
236 |
237 |
238 |
239 | {{#if institution}}
240 |
241 | {{institution}}
242 |
243 | {{/if}}
244 |
245 |
246 | {{#if startDate}}
247 |
248 | {{{date startDate}}}
249 |
250 |
251 | - {{{date endDate}}}
252 |
253 | {{/if}}
254 |
255 |
256 | {{#if url}}
257 |
258 | {{{link url}}}
259 |
260 | {{/if}}
261 |
262 |
263 | {{#if qualification}}
264 |
265 | {{qualification}}
266 |
267 | {{/if}}
268 |
269 | {{#if courses.length}}
270 |
271 | {{#each courses}}
272 | - {{.}}
273 | {{/each}}
274 |
275 | {{/if}}
276 |
277 | {{#if dissertation}}
278 |
279 | Dissertation: {{dissertation}}
280 |
281 | {{/if}}
282 |
283 | {{/each}}
284 |
285 | {{/if}}
286 |
287 | {{#if resume.skills.length}}
288 |
289 | Skills
290 | {{#each resume.skills}}
291 |
292 | {{#if name}}
293 |
294 | {{name}}
295 |
296 | {{/if}}
297 | {{#if level}}
298 |
299 | {{level}}
300 |
301 | {{/if}}
302 | {{#if keywords.length}}
303 |
304 | {{#each keywords}}
305 | - {{.}}
306 | {{/each}}
307 |
308 | {{/if}}
309 |
310 | {{/each}}
311 |
312 | {{/if}}
313 |
314 | {{#if resume.languages.length}}
315 |
316 | Languages
317 | {{#each resume.languages}}
318 |
319 | {{#if language}}
320 |
{{language}}
321 | {{/if}}
322 | {{#if fluency}}
323 |
324 | {{fluency}}
325 |
326 | {{/if}}
327 |
328 | {{/each}}
329 |
330 | {{/if}}
331 |
332 | {{#if resume.interests.length}}
333 |
334 | Interests
335 | {{#each resume.interests}}
336 |
337 | {{#if name}}
338 |
339 | {{name}}
340 |
341 | {{/if}}
342 | {{#if keywords.length}}
343 |
344 | {{#each keywords}}
345 | - {{.}}
346 | {{/each}}
347 |
348 | {{/if}}
349 |
350 | {{/each}}
351 |
352 | {{/if}}
353 |
354 | {{#if resume.references.length}}
355 |
356 | References
357 | {{#each resume.references}}
358 |
359 | {{#if reference}}
360 |
361 | {{reference}}
362 |
363 | {{/if}}
364 | {{#if name}}
365 |
366 | — {{name}}
367 |
368 | {{/if}}
369 |
370 | {{/each}}
371 |
372 | {{/if}}
373 |
374 |
375 |
376 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | --fg-color: black;
3 | --bg-color: #fff;
4 | --header-h1-fg-color: #fff;
5 | --header-h2-fg-color: #f3f3f3;
6 | --header-bg-color: #1866c3;
7 | --link-color: #3061a5;
8 | --separator-color: #e2e2e2;
9 |
10 | margin: 0;
11 | color: var(--fg-color);
12 | background: var(--bg-color);
13 | font-size: 15px;
14 | font-family: Roboto, 'Helvetica Neue', 'Arial Nova', 'Liberation Sans', Arial, sans-serif;
15 | }
16 |
17 | @media (prefers-color-scheme: dark) {
18 | body {
19 | --fg-color: #eee;
20 | --bg-color: #2c2c2c;
21 | --header-h1-fg-color: #ddd;
22 | --header-h2-fg-color: #eee;
23 | --header-bg-color: #1b1b1b;
24 | --link-color: #70a1e5;
25 | --separator-color: #939393
26 | }
27 | }
28 |
29 | em {
30 | color: #999;
31 | }
32 |
33 | a {
34 | color: var(--link-color);
35 | text-decoration: none;
36 | }
37 |
38 | section {
39 | margin-bottom: 2em;
40 | }
41 |
42 | .item {
43 | break-inside: avoid;
44 | }
45 |
46 | header {
47 | background: var(--header-bg-color);
48 | padding: 50px;
49 | margin-bottom: 50px;
50 | }
51 |
52 | header h1 {
53 | color: var(--header-h1-fg-color);
54 | max-width: 772px;
55 | margin: 0 auto;
56 | }
57 |
58 | header h2 {
59 | color: var(--header-h2-fg-color);
60 | font-size: 1em;
61 | max-width: 772px;
62 | margin: 0 auto;
63 | }
64 |
65 | .container {
66 | max-width: 772px;
67 | padding: 0 50px;
68 | margin: 0 auto;
69 | }
70 |
71 | #basics {
72 | margin-bottom: 10px;
73 | border-bottom: 1px var(--separator-color) solid;
74 | }
75 |
76 | .split {
77 | display: flex;
78 | flex-wrap: wrap;
79 | }
80 |
81 | .split strong {
82 | margin-right: 2em;
83 | }
84 |
85 | .website,
86 | .email,
87 | .phone,
88 | #profiles .item,
89 | #languages .item,
90 | #interests .item,
91 | #skills .item {
92 | width: 50%;
93 | }
94 |
95 | .username {
96 | margin-top: .5em;
97 | }
98 |
99 | .work_date,
100 | .work_position,
101 | .work_website,
102 | .institution,
103 | .study_date,
104 | .qualification {
105 | margin-bottom: 10px;
106 | width: 30%;
107 | }
108 |
109 | .courses,
110 | #references,
111 | #interests {
112 | clear: both;
113 | }
114 |
115 | .dissertation {
116 | margin-top: 10px;
117 | }
118 |
119 | #work,
120 | #volunteer {
121 | padding-bottom: 5px;
122 | border-bottom: 1px var(--separator-color) solid;
123 | }
124 |
125 | #work .item,
126 | #volunteer .item {
127 | margin: 25px 0;
128 | }
129 |
130 | #skills {
131 | margin-bottom: 10px;
132 | border-bottom: 1px var(--separator-color) solid;
133 | padding-bottom: 5px;
134 | overflow: hidden;
135 | }
136 |
137 | #skills .item,
138 | #languages .item,
139 | #interests .item {
140 | margin: 0 0 25px 0;
141 | }
142 |
143 | @page {
144 | margin: 2em 0 0 0;
145 | }
146 |
147 | @page :first {
148 | margin: 0;
149 | }
150 |
--------------------------------------------------------------------------------
/test/fixture.resume.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json",
3 | "meta": {
4 | "language": "en"
5 | },
6 | "basics": {
7 | "name": "Seth Falco",
8 | "label": "Open-Sourcerer 🧙♂️",
9 | "email": "seth@example.org",
10 | "phone": "+00 0694201337",
11 | "summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sollicitudin nibh sit amet commodo nulla facilisi nullam.",
12 | "location": {
13 | "countryCode": "GB"
14 | },
15 | "profiles": [
16 | {
17 | "network": "GitHub",
18 | "username": "SethFalco",
19 | "url": "https://github.com/SethFalco"
20 | },
21 | {
22 | "network": "LinkedIn",
23 | "username": "sethfalco",
24 | "url": "https://www.linkedin.com/in/sethfalco/"
25 | }
26 | ]
27 | },
28 | "work": [
29 | {
30 | "name": "JSON Resume",
31 | "location": "Remote",
32 | "position": "Open-Sourcerer 🧙♂️",
33 | "url": "https://jsonresume.org/",
34 | "startDate": "2021-10",
35 | "summary": "Facilisis volutpat est velit egestas dui id ornare.",
36 | "highlights": [
37 | "Sollicitudin nibh sit amet commodo nulla facilisi nullam.",
38 | "A scelerisque purus semper eget.",
39 | "Sed egestas egestas fringilla phasellus faucibus scelerisque.",
40 | "Purus semper eget duis at tellus at urna condimentum mattis."
41 | ]
42 | }
43 | ],
44 | "volunteer": [
45 | {
46 | "organization": "SVGO",
47 | "position": "Open-Sourcerer 🧙♂️",
48 | "url": "https://svgo.dev/",
49 | "startDate": "2023-09",
50 | "summary": "Cursus risus at ultrices mi tempus imperdiet nulla.",
51 | "highlights": [
52 | "Sed odio morbi quis commodo odio aenean sed adipiscing.",
53 | "Diam sollicitudin tempor id eu nisl nunc mi."
54 | ]
55 | }
56 | ],
57 | "education": [
58 | {
59 | "institution": "freeCodeCamp",
60 | "url": "https://www.freecodecamp.org/",
61 | "area": "Information Technology",
62 | "studyType": "Community-Study"
63 | }
64 | ],
65 | "certificates": [
66 | {
67 | "name": "edX Verified Certificate for Introduction to Web Accessibility",
68 | "date": "2023-04-17",
69 | "issuer": "edX"
70 | }
71 | ],
72 | "publications": [
73 | {
74 | "name": "What are Bookmarklets? How to Use JavaScript to Make a Bookmarklet in Chromium and Firefox",
75 | "publisher": "freeCodeCamp",
76 | "releaseDate": "2021-06-17",
77 | "url": "https://www.freecodecamp.org/news/what-are-bookmarklets/"
78 | }
79 | ],
80 | "languages": [
81 | {
82 | "language": "English",
83 | "fluency": "Native"
84 | },
85 | {
86 | "language": "French",
87 | "fluency": "Elementary"
88 | }
89 | ]
90 | }
91 |
--------------------------------------------------------------------------------