├── src ├── components │ ├── ExternalDisplayLink.astro │ ├── ArrowUpRightIcon.astro │ ├── LocationIcon.astro │ ├── MailIcon.astro │ ├── GithubStarIcon.astro │ ├── GlobeIcon.astro │ ├── PhoneIcon.astro │ ├── SectionHeader.astro │ ├── Card.astro │ ├── PrintButton.astro │ └── BackgroundPatternLight.astro ├── env.d.ts ├── pages │ ├── index.astro │ └── __letters │ │ ├── list.astro │ │ ├── [...slug].astro │ │ └── [slug]-download.ts ├── content │ ├── config.ts │ └── letters │ │ └── meta-engineer.md ├── index.css ├── templates │ └── clean-professional │ │ ├── CoverLetter.astro │ │ ├── Page.astro │ │ ├── Masthead.astro │ │ └── Resume.astro ├── layouts │ └── Layout.astro └── data.json ├── public ├── robots.txt ├── favicon.ico ├── crossline-dots.webp ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── emily-johnson-resume.pdf ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── site.webmanifest ├── background-bullseye.svg ├── background-triangles.svg ├── background-pattern.svg └── background-pattern-dark.svg ├── bun.lockb ├── postcss.config.cjs ├── tsconfig.json ├── .prettierrc.mjs ├── .gitignore ├── scripts ├── puppeteer-pdf.ts └── create-pdf.ts ├── package.json ├── astro.config.mjs ├── panda.config.ts ├── README.md ├── astro-integrations └── astro-dev-only-routes.ts └── LICENSE /src/components/ExternalDisplayLink.astro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/bun.lockb -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("@pandacss/dev/postcss")()], 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/crossline-dots.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/crossline-dots.webp -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/emily-johnson-resume.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/emily-johnson-resume.pdf -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roginfarrer/webpage-pdf-resume/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Resume from "src/templates/clean-professional/Resume.astro"; 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "styled-system/*": ["./styled-system/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | export default { 3 | plugins: ['prettier-plugin-astro'], 4 | overrides: [ 5 | { 6 | files: '*.astro', 7 | options: { 8 | parser: 'astro', 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from "astro:content"; 2 | 3 | const letters = defineCollection({ 4 | type: "content", 5 | schema: z.object({ 6 | company: z.string(), 7 | title: z.string(), 8 | }), 9 | }); 10 | 11 | export const collections = { letters }; 12 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @layer reset, base, tokens, recipes, utilities; 2 | 3 | @media print { 4 | @page { 5 | margin-top: 0; 6 | margin-bottom: 0; 7 | margin-left: 0; 8 | margin-right: 0; 9 | } 10 | 11 | body { 12 | -webkit-print-color-adjust: exact !important; 13 | color-adjust: exact !important; 14 | margin: 0.5in 0.5in; 15 | } 16 | 17 | html { 18 | font-size: 11px !important; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/ArrowUpRightIcon.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const props = Astro.props; 3 | --- 4 | 5 | 12 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | 26 | ## Panda 27 | styled-system 28 | styled-system-studio 29 | .vercel 30 | -------------------------------------------------------------------------------- /src/components/LocationIcon.astro: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/components/MailIcon.astro: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/GithubStarIcon.astro: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /public/background-bullseye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/templates/clean-professional/CoverLetter.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { css } from "styled-system/css"; 3 | import Page from "./Page.astro"; 4 | --- 5 | 6 | 7 |
16 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/components/GlobeIcon.astro: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/pages/__letters/list.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import CoverLetter from "../../templates/clean-professional/CoverLetter.astro"; 3 | import { getCollection } from "astro:content"; 4 | import { flex } from "styled-system/patterns"; 5 | 6 | const allLetters = await getCollection("letters"); 7 | --- 8 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /src/templates/clean-professional/Page.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../../layouts/Layout.astro"; 3 | import { css } from "styled-system/css"; 4 | 5 | import Masthead from "./Masthead.astro"; 6 | import "@fontsource-variable/rubik"; 7 | --- 8 | 9 | 10 |
16 |
21 | 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/components/PhoneIcon.astro: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/SectionHeader.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { css } from "styled-system/css"; 3 | import { flex } from "styled-system/patterns"; 4 | --- 5 | 6 |

18 | 19 |
27 |
28 |

29 | -------------------------------------------------------------------------------- /src/pages/__letters/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import CoverLetter from "../../templates/clean-professional/CoverLetter.astro"; 3 | import { getCollection } from "astro:content"; 4 | import PrintButton from "src/components/PrintButton.astro"; 5 | import { css } from "styled-system/css/css"; 6 | 7 | export async function getStaticPaths() { 8 | const letters = await getCollection("letters"); 9 | return letters.map((entry) => ({ 10 | params: { slug: entry.slug }, 11 | props: { entry }, 12 | })); 13 | } 14 | const { entry } = Astro.props; 15 | const { Content } = await entry.render(); 16 | --- 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 | -------------------------------------------------------------------------------- /scripts/puppeteer-pdf.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from "puppeteer"; 2 | import data from "../src/data.json"; 3 | 4 | export async function createPdf( 5 | port: string, 6 | log: (message: string) => void = console.log, 7 | debug: (message: string) => void = console.log, 8 | ) { 9 | if (!port) { 10 | throw new Error("Missing port"); 11 | } 12 | 13 | let browser = await puppeteer.launch({ 14 | ignoreHTTPSErrors: true, 15 | }); 16 | const page = await browser.newPage(); 17 | 18 | debug(`opening page at port: ${port}`); 19 | await page.goto(`http://localhost:${port}/`, { waitUntil: "networkidle2" }); 20 | 21 | const path = `public/${data.meta.name.toLowerCase().replaceAll(" ", "-")}-resume.pdf`; 22 | await page.pdf({ path }); 23 | log(`Created ${path}`); 24 | 25 | await browser.close(); 26 | } 27 | -------------------------------------------------------------------------------- /src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import data from "../data.json"; 3 | import "../index.css"; 4 | 5 | const title = `${data.meta.name}'s Resume`; 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {title} 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpage-pdf-resume", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "prepare": "husky && panda codegen", 8 | "dev": "astro dev", 9 | "start": "astro dev", 10 | "build": "astro check && astro build", 11 | "preview": "astro preview", 12 | "astro": "astro", 13 | "create-pdf-hook": "bun ./scripts/create-pdf.ts" 14 | }, 15 | "dependencies": { 16 | "@astrojs/check": "^0.5.10", 17 | "@fontsource-variable/rubik": "^5.0.21", 18 | "astro": "^4.6.1", 19 | "astro-dev-only-routes": "^0.0.2", 20 | "typescript": "^5.4.5" 21 | }, 22 | "devDependencies": { 23 | "@pandacss/dev": "^0.37.2", 24 | "@types/bun": "^1.1.3", 25 | "dotenv": "^16.4.5", 26 | "husky": "^9.0.11", 27 | "p-debounce": "^4.0.0", 28 | "prettier": "^3.2.5", 29 | "prettier-plugin-astro": "^0.13.0", 30 | "puppeteer": "^22.10.0", 31 | "tree-kill": "^1.2.2" 32 | }, 33 | "author": "Rogin Farrer ", 34 | "license": "NonCommercial-ShareAlike 1.0 Generic", 35 | "description": "A tasteful, printable resume written using Astro and PandaCSS.", 36 | "homepage": "https://github.com/roginfarrer/webpage-pdf-resume" 37 | } 38 | -------------------------------------------------------------------------------- /src/content/letters/meta-engineer.md: -------------------------------------------------------------------------------- 1 | --- 2 | company: Meta 3 | title: Software Engineer 4 | --- 5 | 6 | Dear Hiring Manager, 7 | 8 | I'm excited to apply for the Mid-Level Software Engineer position at Meta, where I can leverage my skills and experience to contribute to the development of innovative products and services. 9 | 10 | With 10 years of experience in the field, I possess a strong foundation in software development principles, patterns, and best practices. My expertise lies in web development, and I'm well-versed in agile development methodologies. 11 | 12 | In my current role at Amazon, I've had the opportunity to [briefly describe a significant accomplishment or project]. I've also developed strong collaboration and communication skills, having worked with cross-functional teams to deliver high-quality products. 13 | 14 | I'm particularly drawn to Meta's commitment to [mention a specific value or mission that resonates with you]. As a software engineer, I believe it's essential to stay up-to-date with the latest technologies and trends, and I'm excited about the prospect of working with a talented team of engineers who share this passion. 15 | 16 | Thank you for considering my application. I'm excited about the opportunity to discuss how my skills and experience align with Meta's goals and vision. 17 | 18 | Sincerely, 19 | 20 | Emily Johnson 21 | -------------------------------------------------------------------------------- /src/pages/__letters/[slug]-download.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro"; 2 | import { getCollection } from "astro:content"; 3 | import puppeteer from "puppeteer"; 4 | 5 | export async function getStaticPaths() { 6 | const letters = await getCollection("letters"); 7 | return letters.map((entry) => ({ 8 | params: { slug: entry.slug }, 9 | props: { entry }, 10 | })); 11 | } 12 | 13 | export const GET: APIRoute = async ({ request, params }) => { 14 | try { 15 | let browser = await puppeteer.launch({ 16 | ignoreHTTPSErrors: true, 17 | timeout: 15000, 18 | }); 19 | const page = await browser.newPage(); 20 | 21 | console.log(`Attempting to create PDF for ${request.url}`); 22 | 23 | await page.goto(`http://localhost:4321/letters/${params.slug}`, { 24 | waitUntil: "networkidle2", 25 | }); 26 | 27 | let buffer = await page.pdf({ timeout: 15000 }); 28 | 29 | console.log("PDF created."); 30 | 31 | for (let _page of await browser.pages()) { 32 | await _page.close(); 33 | } 34 | await browser.close(); 35 | 36 | return new Response(buffer, { 37 | status: 200, 38 | headers: { 39 | "Content-Type": "application/pdf", 40 | }, 41 | }); 42 | } catch (e: any) { 43 | return new Response(null, { 44 | status: 404, 45 | statusText: e.message, 46 | }); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | title: string; 4 | body: string; 5 | href: string; 6 | } 7 | 8 | const { href, title, body } = Astro.props; 9 | --- 10 | 11 | 22 | 62 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | 3 | import devOnlyRoutes from "./astro-integrations/astro-dev-only-routes"; 4 | import { createPdf } from "./scripts/puppeteer-pdf"; 5 | import pDebounce from "p-debounce"; 6 | 7 | /** @type {string} */ 8 | let PORT; 9 | /** @type {import('astro').AstroIntegrationLogger} */ 10 | let externalLogger; 11 | 12 | let debouncedCreatePdf = pDebounce(async () => { 13 | try { 14 | await createPdf( 15 | PORT, 16 | (msg) => externalLogger.info(msg), 17 | (msg) => externalLogger.debug(msg), 18 | ); 19 | } catch (e) { 20 | externalLogger.error("Failed to create resume PDF. Error:"); 21 | // @ts-ignore 22 | externalLogger.error(e.message); 23 | } 24 | }, 1000); 25 | 26 | // https://astro.build/config 27 | export default defineConfig({ 28 | vite: { 29 | plugins: [ 30 | { 31 | name: "create-resume-pdf", 32 | watchChange: async function (filename) { 33 | if (filename.includes("src/pages/index.astro")) { 34 | await debouncedCreatePdf(); 35 | } 36 | }, 37 | }, 38 | ], 39 | }, 40 | integrations: [ 41 | devOnlyRoutes(), 42 | { 43 | name: "create-resume-pdf", 44 | hooks: { 45 | "astro:server:start": async ({ address: { port }, logger }) => { 46 | externalLogger = logger; 47 | PORT = port.toString(); 48 | try { 49 | await createPdf( 50 | PORT, 51 | (message) => logger.info(message), 52 | (msg) => logger.debug(msg), 53 | ); 54 | } catch (e) { 55 | logger.error("Failed to create resume PDF. Error:"); 56 | // @ts-ignore 57 | logger.error(e.message); 58 | } 59 | }, 60 | }, 61 | }, 62 | ], 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/PrintButton.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { css } from "styled-system/css"; 3 | 4 | type Props = { 5 | href: string; 6 | }; 7 | 8 | let { href } = Astro.props; 9 | --- 10 | 11 | 28 | 29 | 88 | -------------------------------------------------------------------------------- /src/templates/clean-professional/Masthead.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import GlobeIcon from "src/components/GlobeIcon.astro"; 3 | import MailIcon from "src/components/MailIcon.astro"; 4 | import LocationIcon from "src/components/LocationIcon.astro"; 5 | import PhoneIcon from "src/components/PhoneIcon.astro"; 6 | 7 | import { css } from "styled-system/css"; 8 | import { flex } from "styled-system/patterns"; 9 | import data from "../../data.json"; 10 | 11 | const metaStyle = flex({ 12 | align: "center", 13 | gap: "2", 14 | justify: "flex-end", 15 | textDecoration: "none", 16 | "&:first-child": { 17 | position: "relative", 18 | top: "1px", 19 | }, 20 | }); 21 | 22 | const formattedWebsite = data.meta.website.replace( 23 | /^http[s]?:\/\/[[w]{3}\.]?/, 24 | "", 25 | ); 26 | --- 27 | 28 |
34 |
41 |

51 | {data.meta.name} 52 |

53 |

{data.meta.title}

54 |
55 |
63 |
{data.meta.email}
64 |
{data.meta.phone}
65 |
66 | {data.meta.location} 67 |
68 | {formattedWebsite} 76 |
77 |
78 | -------------------------------------------------------------------------------- /scripts/create-pdf.ts: -------------------------------------------------------------------------------- 1 | import { parseArgs } from "node:util"; 2 | import * as cp from "node:child_process"; 3 | import treeKill from "tree-kill"; 4 | import { createPdf } from "./puppeteer-pdf"; 5 | 6 | interface Arguments { 7 | debug?: boolean; 8 | logger: Console; 9 | } 10 | 11 | let proc: cp.ChildProcessWithoutNullStreams; 12 | 13 | async function startServer({ 14 | logger, 15 | }: Arguments): Promise<[cp.ChildProcessWithoutNullStreams, string]> { 16 | try { 17 | let port = "4600"; 18 | proc = cp.spawn("bun", ["dev", "--port", port]); 19 | await new Promise((resolve) => { 20 | proc.on("spawn", () => { 21 | logger.log("bun dev server started"); 22 | }); 23 | 24 | proc.stdout.on("data", (data) => { 25 | if (data.toString().includes("http://localhost:")) { 26 | let matches = /http:\/\/localhost:(\d{4})/.exec( 27 | data.toString() as string, 28 | ); 29 | if (matches) { 30 | logger.log("found dev server created a port: ", matches[1]); 31 | port = matches[1]; 32 | } 33 | resolve(); 34 | } 35 | }); 36 | }); 37 | 38 | return [proc, port]; 39 | } catch (e) { 40 | throw e; 41 | } 42 | } 43 | 44 | (async () => { 45 | const { values } = parseArgs({ 46 | options: { 47 | debug: { 48 | type: "boolean", 49 | }, 50 | }, 51 | }); 52 | let logger = new Proxy(console, { 53 | get() { 54 | if (values.debug) { 55 | // @ts-expect-error 56 | return Reflect.get(...arguments); 57 | } 58 | return () => {}; 59 | }, 60 | }); 61 | 62 | let port; 63 | [proc, port] = await startServer({ logger }); 64 | await createPdf(port); 65 | await cleanup(); 66 | })(); 67 | 68 | async function cleanup() { 69 | console.log("Cleaning up..."); 70 | await new Promise((resolve) => { 71 | if (proc) { 72 | proc.on("close", (code) => { 73 | console.log(`Subprocess exited with code ${code}`); 74 | resolve(); 75 | }); 76 | treeKill(proc.pid!); 77 | } else { 78 | resolve(); 79 | } 80 | }); 81 | } 82 | async function handleExit(signal: string) { 83 | console.log(`${signal} received, terminating subprocess...`); 84 | await cleanup(); 85 | process.exit(0); 86 | } 87 | process.on("SIGINT", () => handleExit("SIGINT")); 88 | process.on("SIGTERM", () => handleExit("SIGTERM")); 89 | process.on("uncaughtException", async (err) => { 90 | console.error("Uncaught Exception:", err); 91 | await cleanup(); 92 | process.exit(1); 93 | }); 94 | -------------------------------------------------------------------------------- /panda.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@pandacss/dev"; 2 | 3 | export default defineConfig({ 4 | // Whether to use css reset 5 | preflight: true, 6 | 7 | // Where to look for your css declarations 8 | include: [ 9 | "./src/**/*.{ts,tsx,js,jsx,astro}", 10 | "./pages/**/*.{ts,tsx,js,jsx,astro}", 11 | ], 12 | 13 | // Files to exclude 14 | exclude: [], 15 | 16 | // Useful for theme customization 17 | theme: { 18 | extend: { 19 | keyframes: { 20 | speedOutRight: { 21 | "25%": { 22 | transform: "rotate(15deg)", 23 | }, 24 | "50%": { 25 | transform: "rotate(-15deg)", 26 | }, 27 | "100%": { 28 | transform: "rotate(-15deg) translate(200%, 75px)", 29 | }, 30 | }, 31 | }, 32 | semanticTokens: { 33 | colors: { 34 | $accent: { 35 | DEFAULT: { 36 | value: { 37 | base: "{colors.blue.700}", 38 | _osDark: "{colors.blue.500}", 39 | }, 40 | }, 41 | 50: { value: "{colors.blue.50}" }, 42 | 100: { value: "{colors.blue.100}" }, 43 | 200: { value: "{colors.blue.200}" }, 44 | 300: { value: "{colors.blue.300}" }, 45 | 400: { value: "{colors.blue.400}" }, 46 | 500: { value: "{colors.blue.500}" }, 47 | 600: { value: "{colors.blue.600}" }, 48 | 700: { value: "{colors.blue.700}" }, 49 | 800: { value: "{colors.blue.800}" }, 50 | 900: { value: "{colors.blue.900}" }, 51 | 950: { value: "{colors.blue.950}" }, 52 | }, 53 | $bodyText: { 54 | value: { 55 | base: "{colors.slate.700}", 56 | _osDark: "{colors.slate.50}", 57 | }, 58 | }, 59 | $page: { 60 | value: { 61 | base: "white", 62 | _osDark: "{colors.gray.900}", 63 | }, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | 70 | conditions: { 71 | extend: { 72 | print: ["@media print"], 73 | }, 74 | }, 75 | 76 | globalCss: { 77 | extend: { 78 | "body, html": { 79 | color: "$bodyText", 80 | bg: "$page", 81 | fontFamily: '"Rubik Variable", sans-serif', 82 | fontSize: { base: "md", xl: "lg" }, 83 | lineHeight: "1.25", 84 | textWrap: "pretty", 85 | a: { 86 | textDecoration: "underline", 87 | }, 88 | _print: { 89 | lineHeight: "1.15", 90 | }, 91 | }, 92 | ".prose": { 93 | "& p": { 94 | marginBottom: "4", 95 | lineHeight: "1.5", 96 | fontSize: "lg", 97 | }, 98 | }, 99 | }, 100 | }, 101 | 102 | // The output directory for your css system 103 | outdir: "styled-system", 104 | }); 105 | -------------------------------------------------------------------------------- /public/background-triangles.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "name": "Emily Johnson", 4 | "title": "Software Engineer", 5 | "website": "https://emjo.biz", 6 | "location": "Seattle, WA", 7 | "email": "emily.johnson@example.com", 8 | "phone": "+1 (555) 987-6543", 9 | "bio": "Dedicated software engineer with a strong background in building robust and scalable applications. Skilled in Java, Python, and JavaScript technologies. Experienced with Meta, Amazon, and Google." 10 | }, 11 | "education": [ 12 | { 13 | "institution": "University of Washington", 14 | "location": "Seattle, WA", 15 | "area": "Computer Science", 16 | "degreeType": "Bachelor of Science", 17 | "endDate": "2015" 18 | } 19 | ], 20 | "work": [ 21 | { 22 | "company": "Google", 23 | "position": "Senior Software Engineer", 24 | "location": "Mountain View, CA", 25 | "startDate": "2020", 26 | "endDate": "Present", 27 | "highlights": [ 28 | "Developing machine learning algorithms for search optimization and personalized content recommendations", 29 | "Mentoring junior engineers in coding best practices and fostering a culture of continuous learning", 30 | "Led performance optimization initiatives resulting in 40% faster response times and enhanced user experience", 31 | "Collaborated with product teams to define technical requirements and priorities for new feature development" 32 | ] 33 | }, 34 | { 35 | "company": "Amazon", 36 | "position": "Software Development Engineer", 37 | "location": "Seattle, WA", 38 | "startDate": "2017", 39 | "endDate": "2020", 40 | "highlights": [ 41 | "Designed and implemented microservices architecture to improve system scalability", 42 | "Collaborated with product teams to define technical requirements and priorities for new feature development", 43 | "Led a team of 5 engineers in project delivery and coordinated sprint planning activities", 44 | "Improved system reliability by 30% through automated testing and continuous integration practices", 45 | "Contributed to code review process and maintained code quality standards across the team" 46 | ] 47 | }, 48 | { 49 | "company": "Meta", 50 | "position": "Software Engineer", 51 | "location": "Menlo Park, CA", 52 | "startDate": "2015", 53 | "endDate": "2017", 54 | "highlights": [ 55 | "Developed backend systems for social media platform", 56 | "Optimized database performance resulting in 20% faster response times", 57 | "Collaborated with cross-functional teams to deliver features on schedule and meet project milestones", 58 | "Collaborated with product teams to define technical requirements and priorities for new feature development", 59 | "Implemented RESTful APIs for external integrations and ensured data security measures" 60 | ] 61 | } 62 | ], 63 | "skills": [ 64 | { 65 | "name": "Programming Languages", 66 | "keywords": ["Java", "Python", "JavaScript"] 67 | }, 68 | { 69 | "name": "Tools & Technologies", 70 | "keywords": [ 71 | "Docker", 72 | "Kubernetes", 73 | "Git", 74 | "MySQL", 75 | "MongoDB", 76 | "PostgreSQL" 77 | ] 78 | } 79 | ], 80 | "projects": [ 81 | { 82 | "description": "The library for web and native user interfaces.", 83 | "url": "https://github.com/facebook/react", 84 | "name": "React.js", 85 | "repo": "facebook/react", 86 | "stars": "221k" 87 | }, 88 | { 89 | "description": "Cybernetically enhanced web apps.", 90 | "url": "https://github.com/sveltejs/svelte", 91 | "name": "Svelte.js", 92 | "repo": "sveltejs/svelte", 93 | "stars": "76.3k" 94 | }, 95 | { 96 | "description": "Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.", 97 | "url": "https://github.com/vuejs/core", 98 | "name": "Vue.js", 99 | "repo": "vuejs/core", 100 | "stars": "44.4k" 101 | } 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /public/background-pattern.svg: -------------------------------------------------------------------------------- 1 | 144 | -------------------------------------------------------------------------------- /public/background-pattern-dark.svg: -------------------------------------------------------------------------------- 1 | 144 | -------------------------------------------------------------------------------- /src/components/BackgroundPatternLight.astro: -------------------------------------------------------------------------------- 1 | 148 | -------------------------------------------------------------------------------- /src/templates/clean-professional/Resume.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import SectionHeader from "src/components/SectionHeader.astro"; 3 | import GithubStarIcon from "src/components/GithubStarIcon.astro"; 4 | import ArrowUpRightIcon from "src/components/ArrowUpRightIcon.astro"; 5 | import PrintButton from "src/components/PrintButton.astro"; 6 | 7 | import Page from "./Page.astro"; 8 | import { css } from "styled-system/css"; 9 | import { flex } from "styled-system/patterns"; 10 | import data from "../../data.json"; 11 | import "@fontsource-variable/rubik"; 12 | 13 | const subtitle = css({ fontWeight: "bold", fontSize: "lg" }); 14 | 15 | const pdfFilename = `/${data.meta.name.toLowerCase().replaceAll(" ", "-")}-resume.pdf`; 16 | --- 17 | 18 | 19 |
22 | 23 |
24 | About Me 25 |

{data.meta.bio}

26 | Work 27 |
28 | { 29 | data.work.map((work) => { 30 | return ( 31 |
32 |

33 | {work.position} 34 |

35 |
44 |
{work.company}
45 |
46 | {work.startDate} - {work.endDate},  {work.location} 47 |
48 |
49 |
    57 | {work.highlights.map((highlight) => ( 58 |
  • {highlight}
  • 59 | ))} 60 |
61 |
62 | ); 63 | }) 64 | } 65 |
66 | Open Source 67 |
    68 | { 69 | data.projects.map((project) => { 70 | const titleChild = project.url ? ( 71 | 77 | {project.name} 78 | 84 | 85 | ) : ( 86 | <>{project.name} 87 | ); 88 | return ( 89 |
  • 93 |
    94 |

    {titleChild}

    95 |

    104 | 113 | {project.stars} 114 |

    115 |
    116 |

    {project.description}

    117 |
  • 118 | ); 119 | }) 120 | } 121 |
122 | Education 123 |
    124 | { 125 | data.education.map((edu) => ( 126 |
  • 127 |

    {edu.institution}

    128 |

    {edu.degreeType}

    129 |

    {edu.endDate}

    130 |
  • 131 | )) 132 | } 133 |
134 | Skills 135 |
    136 | { 137 | data.skills.map((skill) => ( 138 |
  • 139 |

    {skill.name}

    140 |

    {skill.keywords.join(", ")}

    141 |
  • 142 | )) 143 | } 144 |
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 | --------------------------------------------------------------------------------