145 |
146 |
147 |
171 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webpage to PDF Resume
2 |
3 | A straightforward resume or CV template for the web, print, or PDF. Built with [Astro](https://astro.build) and [PandaCSS](https://panda-css.com).
4 |
5 | > [View demo](https://webpage-pdf-resume.vercel.app)
6 |
7 | ## Features
8 |
9 | - Separation of data (name, experience, education, etc) from page templates, allowing you to create different look and feels without copying and pasting your information.
10 | - Supports cover letters to match the resume's aesthetic, using Astro Collections and markdown.
11 | - Built to ship--when deployed, only the homepage resume is built for production and keeping other templates and cover letters in dev-only.
12 | - Job application-ready PDFs from your resume and cover letters.
13 |
14 | ## Who's this for?
15 |
16 | As a web developer, I got frustrated with Google Doc templates or resume builder sites that either cost a lot of money or have problems with their designs. When putting together my one-page resume, I knew how I wanted things to look, and I just wanted to use markup and CSS to make it!
17 |
18 | So I built this template to make it easy to bootstrap a readable and tasteful resume. It's structured to look great on the web, and produce a balanced, well-formatted one-pager when saved as a PDF or printed. The personal information is abstracted into JSON to make it easy to create new templates.
19 |
20 | ## How to use it
21 |
22 | ### Setup
23 |
24 | Fork and clone the repo. Note that [bun](https://bun.sh) is required.
25 |
26 | ```sh
27 | # Install dependencies
28 | bun install
29 | # Start development server
30 | bun dev
31 | ```
32 |
33 | ### Project Structure
34 |
35 | The `src/` folder has the following files and folders:
36 |
37 | ```tree
38 | components/
39 | content/
40 | layouts/
41 | pages/
42 | templates/
43 | data.json
44 | ```
45 |
46 | This mostly follows Astro's project structure, with a few additions
47 |
48 | All of the personal information that populates the template is located in `src/data.json`. Update this file with all of your information. This information will be referenced throughout the application.
49 |
50 | Inside the `pages/` directory, we have two entities: `index.astro` and `__letters`. All routes with the double-underscore prefix will not be in the final production bundle. This way, you can create good-looking cover letters that match your resume look and feel without shipping them to production. The `index.astro` should be your resume that lives on the homepage of the site. It will be the only page shipped to production.
51 |
52 | The `templates/` directory will contain the different look and feels you wish to create for your resume and letters. You're welcome to use the provided template or create additional ones. While this isn't necessary for most, it can be nice to have the flexibility to try different resume layouts and aesthetics without having to create a new project.
53 |
54 | ### Customize Look & Feel
55 |
56 | The template uses PandaCSS and its [default theme](https://panda-css.com/docs/customization/theme). It gives you all of the base tokens to quickly build out new designs or modify this one. To change the accent color or some of the default styling, update the `panda.config.ts` file.
57 |
58 | The provided font is [Rubik](https://fontsource.org/fonts/rubik). To change the font, find a font to your liking on [Fontsource](https://fontsource.org) and install it following their instructions.
59 |
60 | The provided favicon was generated from [favicon.io](https://favicon.io). Use your own favicon, or generate one there, and delete the provided favicon files in `/public`.
61 |
62 | ### Cover Letters
63 |
64 | You can write your cover letters in markdown in the `src/content/letters/` directory. Pages for your cover letters are generated in dev, but won't be shipped to production. You can see all of the links to the webpages generated by navigating tot he `/letters/list` route (e.g., `http://localhost:4321/letters/list`). Each cover letter page will have a "View as PDF" button--you can use this to generate the PDF, download it, and upload it for your job application.
65 |
66 | The provided template also includes a cover letter template that matches the resume.
67 |
68 | ## Printing
69 |
70 | The structure and content of this resume template is intended to fit onto a single page, suitable for a one-page resume. Depending on your personal information, the length may break onto a second page. I'd recommend futzing with the base font-size in the `src/index.css` file or adjust vertical spacing in the template to make it fit one page. You can also adjust the page margins for printing in the `src/index.css` file.
71 |
72 | To automate the creation of PDFs, this project uses [puppeteer](https://pptr.dev/guides/pdf-generation). Compared to using the browser to create PDFs (through the process of `Print -> Save as PDF`), puppeteer will produce a PDF with machine-readable text and give you the PDF file itself.
73 |
74 | The project includes a pre-push git hook that ensures that the git committed PDF of your resume is up-to-date with the source code. When the project is deployed to production, the PDF file will be present in the project, so it doesn't need to be recreated on the fly (so it also loads faster).
75 |
76 | ## Blocking search engines
77 |
78 | Disable search engine indexing by adding the following code to the in `src/layouts/Layout.astro`:
79 |
80 | ```html
81 |
82 | ```
83 |
84 | ## License
85 |
86 | NonCommercial-ShareAlike 1.0 Generic (CC NC-SA 1.0)
87 | https://creativecommons.org/licenses/nc-sa/1.0/
88 |
89 | ### You are free to:
90 |
91 | Share — copy and redistribute the material in any medium or format
92 |
93 | Adapt — remix, transform, and build upon the material
94 |
95 | ### Under the following terms:
96 |
97 | NonCommercial — You may not use the material for commercial purposes.
98 |
99 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
100 |
101 | ## Acknowledgements
102 |
103 | Hat-tip to [WebPraktikos/universal-resume](https://github.com/WebPraktikos/universal-resume) for showing what's possible with structuring and adapating a resume webpage for print. This is still a great and relevant project. I wanted to make a slightly different project in order to use web technologies I preferred and to abstract the data so that I could make different templates should I desire it.
104 |
--------------------------------------------------------------------------------
/astro-integrations/astro-dev-only-routes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 White Pine Studio, LLC
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 | */
10 |
11 | /**
12 | * Modified version of https://github.com/MoustaphaDev/astro-dev-only-routes
13 | * Original didn't support *.ts files, which is needed to create PDFs
14 | * of cover letters
15 | */
16 |
17 | import { fileURLToPath } from "url";
18 | import { globbySync } from "globby";
19 | import kleur from "kleur";
20 | import path from "path";
21 | import slash from "slash";
22 | import type { AstroIntegration } from "astro";
23 |
24 | // const pageExtRE = /\.(astro|mdx|md|tsx|ts|jsx|js)$/; Support later
25 | const pageExtRE = /\.(astro|ts)$/;
26 | const DOUBLE_UNDERSCORE = "__";
27 | export default function integration(): AstroIntegration {
28 | return {
29 | name: "astro-dev-only-routes",
30 | hooks: {
31 | // TODO: handle refresh when new routes are created or deleted
32 | "astro:config:setup": ({ injectRoute, config, command }) => {
33 | if (command === "build") {
34 | return;
35 | }
36 | let pagesDir = new URL("./src/pages", config.root);
37 |
38 | // TODO: support .mdx, .md, .ts, .js
39 | const devOnlyFiles = globbySync(
40 | [
41 | `**/${DOUBLE_UNDERSCORE}*.{astro,ts}`,
42 | `**/${DOUBLE_UNDERSCORE}*/**/*.{astro,ts}`,
43 | ],
44 | {
45 | cwd: pagesDir,
46 | },
47 | );
48 | if (devOnlyFiles.length === 0) {
49 | log("warn", "No dev-only routes found.");
50 | return;
51 | }
52 | const devOnlyRoutes = devOnlyFiles.map((route) => {
53 | const firstIndexOfDoubleUnderscore = route.indexOf(DOUBLE_UNDERSCORE);
54 |
55 | const pagesDirRelativePath = route.slice(
56 | 0,
57 | firstIndexOfDoubleUnderscore,
58 | );
59 | const routePath = route.slice(
60 | firstIndexOfDoubleUnderscore + DOUBLE_UNDERSCORE.length,
61 | );
62 | // .replace(pageExtRE, "");
63 |
64 | return { routePath, pagesDirRelativePath };
65 | });
66 |
67 | for (let { routePath, pagesDirRelativePath } of devOnlyRoutes) {
68 | // BUG in astro: deeply nested index.astro routes collide with root index.astro
69 | // Open an issue
70 | // const filename = routePath.slice(
71 | // routePath.lastIndexOf('/') + 1
72 | // );
73 | // if (filename === 'index') {
74 | // filename = ''
75 | // }
76 | const entryPoint = slash(
77 | path.join(
78 | fileURLToPath(pagesDir),
79 | `${DOUBLE_UNDERSCORE}${routePath}`,
80 | ),
81 | );
82 | const pattern = slash(
83 | path.join(pagesDirRelativePath, routePath.replace(pageExtRE, "")),
84 | );
85 |
86 | injectRoute({
87 | entrypoint: entryPoint,
88 | pattern,
89 | });
90 | }
91 |
92 | const devOnlyRoutesCount = devOnlyRoutes.length;
93 | const devOnlyRoutesTreeView = createTreeView(
94 | foldersToConsumableTree(devOnlyFiles),
95 | );
96 | log(
97 | "info",
98 | `Found ${devOnlyRoutesCount} dev-only route${
99 | devOnlyRoutesCount > 1 ? "s" : ""
100 | }. Here they are:\n${kleur.bold(devOnlyRoutesTreeView)}`,
101 | );
102 | },
103 | },
104 | };
105 | }
106 |
107 | function log(
108 | type: "info" | "warn" | "error",
109 | message: string,
110 | silent: boolean = false,
111 | ) {
112 | if (silent) return;
113 | const date = new Date().toLocaleTimeString();
114 | const bgColor =
115 | type === "error"
116 | ? kleur.bgRed
117 | : type === "warn"
118 | ? kleur.bgYellow
119 | : kleur.bgMagenta;
120 | type === "error";
121 | console.log(
122 | `${kleur.gray(date)}\n${bgColor(
123 | kleur.bold("[astro-dev-only-routes]"),
124 | )} ${message}`,
125 | );
126 | }
127 |
128 | function foldersToConsumableTree(folders: string[]) {
129 | const tree = {};
130 | for (let folder of folders) {
131 | const parts = folder.split(path.sep);
132 | const rootKey = parts.shift();
133 | if (!rootKey) {
134 | continue;
135 | }
136 | addNestedKeys(tree, [rootKey, ...parts]);
137 | }
138 | return tree;
139 | }
140 |
141 | function addNestedKeys(obj: object, keys: string[]) {
142 | let cursor = obj;
143 | for (let key of keys) {
144 | // @ts-ignore
145 | cursor[key] = cursor[key] || {};
146 | // @ts-ignore
147 | cursor = cursor[key];
148 | }
149 | }
150 |
151 | // TODO: make this
152 | // @ts-ignore
153 | function createTreeView(tree, indent = 0) {
154 | const BRANCH = kleur.magenta("├─");
155 | const HALF_BRANCH = kleur.magenta("└─");
156 | const DOWN_TRIANGLE = "▼";
157 |
158 | let result = "";
159 | const keys = Object.keys(tree);
160 | for (let i = 0; i < keys.length; i++) {
161 | const key = keys[i];
162 | const value = tree[key];
163 | const isLastKey = i === keys.length - 1;
164 | const maybeDownTriangle = isDir(value) ? DOWN_TRIANGLE : " ";
165 | const padding = " ".repeat(indent);
166 | const branch = isLastKey ? HALF_BRANCH : BRANCH;
167 | result += `${padding} ${branch} ${maybeDownTriangle} ${key}\n`;
168 | if (typeof value === "object") {
169 | result += createTreeView(value, indent + 1);
170 | } else {
171 | result += `${padding}${branch} ${value}\n`;
172 | }
173 | }
174 | return result;
175 | }
176 |
177 | // @ts-ignore
178 | function isDir(tree) {
179 | return Object.keys(tree).length > 0;
180 | }
181 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | NonCommercial-ShareAlike 1.0
2 |
3 | License
4 | ============
5 |
6 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC
7 | LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE
8 | LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE IS PROHIBITED.
9 |
10 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY
11 | THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN
12 | CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
13 |
14 | 1. Definitions
15 | ------------
16 |
17 | a. "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia,
18 | in which the Work in its entirety in unmodified form, along with a number of other
19 | contributions, constituting separate and independent works in themselves, are assembled
20 | into a collective whole. A work that constitutes a Collective Work will not be considered
21 | a Derivative Work (as defined below) for the purposes of this License.
22 |
23 | b. "Derivative Work" means a work based upon the Work or upon the Work and other
24 | pre-existing works, such as a translation, musical arrangement, dramatization,
25 | fictionalization, motion picture version, sound recording, art reproduction, abridgment,
26 | condensation, or any other form in which the Work may be recast, transformed, or adapted,
27 | except that a work that constitutes a Collective Work will not be considered a Derivative
28 | Work for the purpose of this License.
29 |
30 | c. "Licensor" means the individual or entity that offers the Work under the terms of this
31 | License.
32 |
33 | d. "Original Author" means the individual or entity who created the Work.
34 |
35 | e. "Work" means the copyrightable work of authorship offered under the terms of this
36 | License.
37 |
38 | f. "You" means an individual or entity exercising rights under this License who has not
39 | previously violated the terms of this License with respect to the Work, or who has
40 | received express permission from the Licensor to exercise rights under this License
41 | despite a previous violation.
42 |
43 | 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any
44 | rights arising from fair use, first sale or other limitations on the exclusive rights of
45 | the copyright owner under copyright law or other applicable laws.
46 |
47 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby
48 | grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the
49 | applicable copyright) license to exercise the rights in the Work as stated below:
50 |
51 | a. to reproduce the Work, to incorporate the Work into one or more Collective Works, and
52 | to reproduce the Work as incorporated in the Collective Works;
53 |
54 | b. to create and reproduce Derivative Works;
55 |
56 | c. to distribute copies or phonorecords of, display publicly, perform publicly, and
57 | perform publicly by means of a digital audio transmission the Work including as
58 | incorporated in Collective Works;
59 |
60 | d. to distribute copies or phonorecords of, display publicly, perform publicly, and
61 | perform publicly by means of a digital audio transmission Derivative Works;
62 |
63 | The above rights may be exercised in all media and formats whether now known or hereafter
64 | devised. The above rights include the right to make such modifications as are technically
65 | necessary to exercise the rights in other media and formats. All rights not expressly
66 | granted by Licensor are hereby reserved.
67 |
68 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and
69 | limited by the following restrictions:
70 |
71 | a. You may distribute, publicly display, publicly perform, or publicly digitally perform
72 | the Work only under the terms of this License, and You must include a copy of, or the
73 | Uniform Resource Identifier for, this License with every copy or phonorecord of the Work
74 | You distribute, publicly display, publicly perform, or publicly digitally perform. You may
75 | not offer or impose any terms on the Work that alter or restrict the terms of this License
76 | or the recipients' exercise of the rights granted hereunder. You may not sublicense the
77 | Work. You must keep intact all notices that refer to this License and to the disclaimer of
78 | warranties. You may not distribute, publicly display, publicly perform, or publicly
79 | digitally perform the Work with any technological measures that control access or use of
80 | the Work in a manner inconsistent with the terms of this License Agreement. The above
81 | applies to the Work as incorporated in a Collective Work, but this does not require the
82 | Collective Work apart from the Work itself to be made subject to the terms of this
83 | License. If You create a Collective Work, upon notice from any Licensor You must, to the
84 | extent practicable, remove from the Collective Work any reference to such Licensor or the
85 | Original Author, as requested. If You create a Derivative Work, upon notice from any
86 | Licensor You must, to the extent practicable, remove from the Derivative Work any
87 | reference to such Licensor or the Original Author, as requested.
88 |
89 | b. You may distribute, publicly display, publicly perform, or publicly digitally perform a
90 | Derivative Work only under the terms of this License, and You must include a copy of, or
91 | the Uniform Resource Identifier for, this License with every copy or phonorecord of each
92 | Derivative Work You distribute, publicly display, publicly perform, or publicly digitally
93 | perform. You may not offer or impose any terms on the Derivative Works that alter or
94 | restrict the terms of this License or the recipients' exercise of the rights granted
95 | hereunder, and You must keep intact all notices that refer to this License and to the
96 | disclaimer of warranties. You may not distribute, publicly display, publicly perform, or
97 | publicly digitally perform the Derivative Work with any technological measures that
98 | control access or use of the Work in a manner inconsistent with the terms of this License
99 | Agreement. The above applies to the Derivative Work as incorporated in a Collective Work,
100 | but this does not require the Collective Work apart from the Derivative Work itself to be
101 | made subject to the terms of this License.
102 |
103 | c. You may not exercise any of the rights granted to You in Section 3 above in any manner
104 | that is primarily intended for or directed toward commercial advantage or private monetary
105 | compensation. The exchange of the Work for other copyrighted works by means of digital
106 | file-sharing or otherwise shall not be considered to be intended for or directed toward
107 | commercial advantage or private monetary compensation, provided there is no payment of any
108 | monetary compensation in connection with the exchange of copyrighted works.
109 |
110 | 5. Representations, Warranties and Disclaimer
111 |
112 | a. By offering the Work for public release under this License, Licensor represents and
113 | warrants that, to the best of Licensor's knowledge after reasonable inquiry:
114 | i. Licensor has secured all rights in the Work necessary to grant the license rights
115 | hereunder and to permit the lawful exercise of the rights granted hereunder without You
116 | having any obligation to pay any royalties, compulsory license fees, residuals or any
117 | other payments;
118 | ii. The Work does not infringe the copyright, trademark, publicity rights, common law
119 | rights or any other right of any third party or constitute defamation, invasion of privacy
120 | or other tortious injury to any third party.
121 |
122 | b. EXCEPT AS EXPRESSLY STATED IN THIS LICENSE OR OTHERWISE AGREED IN WRITING OR REQUIRED
123 | BY APPLICABLE LAW, THE WORK IS LICENSED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
124 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES REGARDING
125 | THE CONTENTS OR ACCURACY OF THE WORK.
126 |
127 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, AND EXCEPT
128 | FOR DAMAGES ARISING FROM LIABILITY TO A THIRD PARTY RESULTING FROM BREACH OF THE
129 | WARRANTIES IN SECTION 5, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY
130 | FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF
131 | THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY
132 | OF SUCH DAMAGES.
133 |
134 | 7. Termination
135 | ------------
136 |
137 | a. This License and the rights granted hereunder will terminate automatically upon any
138 | breach by You of the terms of this License. Individuals or entities who have received
139 | Derivative Works or Collective Works from You under this License, however, will not have
140 | their licenses terminated provided such individuals or entities remain in full compliance
141 | with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this
142 | License.
143 |
144 | b. Subject to the above terms and conditions, the license granted here is perpetual (for
145 | the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor
146 | reserves the right to release the Work under different license terms or to stop
147 | distributing the Work at any time; provided, however that any such election will not serve
148 | to withdraw this License (or any other license that has been, or is required to be,
149 | granted under the terms of this License), and this License will continue in full force and
150 | effect unless terminated as stated above.
151 |
152 | 8. Miscellaneous
153 | ------------
154 |
155 | a. Each time You distribute or publicly digitally perform the Work or a Collective Work,
156 | the Licensor offers to the recipient a license to the Work on the same terms and
157 | conditions as the license granted to You under this License.
158 |
159 | b. Each time You distribute or publicly digitally perform a Derivative Work, Licensor
160 | offers to the recipient a license to the original Work on the same terms and conditions as
161 | the license granted to You under this License.
162 |
163 | c. If any provision of this License is invalid or unenforceable under applicable law, it
164 | shall not affect the validity or enforceability of the remainder of the terms of this
165 | License, and without further action by the parties to this agreement, such provision shall
166 | be reformed to the minimum extent necessary to make such provision valid and enforceable.
167 |
168 | d. No term or provision of this License shall be deemed waived and no breach consented to
169 | unless such waiver or consent shall be in writing and signed by the party to be charged
170 | with such waiver or consent.
171 |
172 | e. This License constitutes the entire agreement between the parties with respect to the
173 | Work licensed here. There are no understandings, agreements or representations with
174 | respect to the Work not specified here. Licensor shall not be bound by any additional
175 | provisions that may appear in any communication from You. This License may not be modified
176 | without the mutual written agreement of the Licensor and You.
177 |
--------------------------------------------------------------------------------