[READ-ONLY] Mirror of https://github.com/mrgnw/cv.
0

Configure Feed

Select the types of activity you want to include in your feed.

Squashed commit of the following:

commit f3ed781bb4d4da99f90c3a5fd550d13dcad8562d
Author: Morgan <2504532+mrgnw@users.noreply.github.com>
Date: Thu Jan 23 16:36:14 2025 +0100

pdf download button

commit 06e5d31e80099337804c90f83639eacf6c8b1f9d
Author: Morgan <2504532+mrgnw@users.noreply.github.com>
Date: Thu Jan 23 16:15:56 2025 +0100

generate all versions

commit db24032516a8d414c957bcdaed12bad4f47fa84a
Author: Morgan <2504532+mrgnw@users.noreply.github.com>
Date: Thu Jan 23 16:09:46 2025 +0100

remove redundancies in skills

commit 8dc46d19e8073694efed9cf64861aeef969d85fc
Author: Morgan <2504532+mrgnw@users.noreply.github.com>
Date: Thu Jan 23 16:05:57 2025 +0100

project includes links

commit 0b011a9e0b2213dad3ada1a77a86ea66618efd29
Author: Morgan <2504532+mrgnw@users.noreply.github.com>
Date: Thu Jan 23 15:31:35 2025 +0100

r/engineeringResumes template

+569 -72
+42 -35
generate-pdf.js
··· 29 29 const versionsDir = path.join('src', 'lib', 'versions'); 30 30 let files = fs.readdirSync(versionsDir) 31 31 .filter(file => file.endsWith('.json')) 32 - .map(file => `/${path.parse(file).name}`); 32 + .map(file => path.parse(file).name); 33 33 34 34 if (specificVersions?.includes('all')) { 35 35 return files; ··· 37 37 38 38 // If specific versions are provided, only process those 39 39 if (specificVersions?.length) { 40 - files = files.filter(file => specificVersions.includes(file.slice(1))); 40 + files = files.filter(file => specificVersions.includes(file)); 41 41 } 42 42 43 43 return files; 44 44 }; 45 45 46 - const waitForServer = (url, timeout = 10000) => new Promise((resolve, reject) => { 47 - const startTime = Date.now(); 48 - (function ping() { 49 - http.get(url, () => resolve()).on('error', () => { 50 - if (Date.now() - startTime > timeout) { 51 - reject(new Error('Server did not start in time')); 52 - } else { 53 - setTimeout(ping, 200); 54 - } 55 - }); 56 - })(); 57 - }); 46 + const waitForServer = (url) => { 47 + return new Promise((resolve, reject) => { 48 + const timeout = setTimeout(() => { 49 + reject(new Error('Server start timeout')); 50 + }, 30000); 51 + 52 + const checkServer = () => { 53 + http.get(url, (res) => { 54 + clearTimeout(timeout); 55 + resolve(); 56 + }).on('error', () => { 57 + setTimeout(checkServer, 100); 58 + }); 59 + }; 60 + 61 + checkServer(); 62 + }); 63 + }; 58 64 59 65 (async () => { 60 66 // Get versions from command line args OR get changed versions ··· 71 77 process.exit(1); 72 78 } 73 79 74 - const routes = getVersionNames(specificVersions); 75 - if (routes.length === 0) { 80 + const versions = getVersionNames(specificVersions); 81 + if (versions.length === 0) { 76 82 console.log('No versions to process'); 77 83 process.exit(0); 78 84 } 79 85 80 - console.log('Generating PDFs for versions:', routes.join(', ')); 86 + console.log('Generating PDFs for versions:', versions.join(', ')); 81 87 const browser = await chromium.launch(); 82 88 const page = await browser.newPage(); 83 89 ··· 97 103 fs.mkdirSync('static'); 98 104 } 99 105 100 - for (const route of routes) { 106 + // Generate both regular and engineering versions 107 + for (const version of versions) { 101 108 try { 102 - const url = `${serverUrl}${route}?print`; 103 - const versionName = route === '/' ? 'index' : route.slice(1); 104 - 105 - // Generate PDF path 106 - const pdfName = versionName === 'main' ? 109 + // Regular version 110 + const url = `${serverUrl}/${version}?print`; 111 + const pdfName = version === 'main' ? 107 112 'morgan-williams.pdf' : 108 - `morgan-williams.${versionName}.pdf`; 113 + `morgan-williams.${version}.pdf`; 109 114 const pdfPath = path.join('static', pdfName); 110 115 111 - // Navigate to the page and capture the response 112 - const response = await page.goto(url, { waitUntil: 'networkidle' }); 113 - 114 - // Check if the response is ok (status code 2xx) 115 - if (!response || !response.ok()) { 116 - console.error(`❌ ${response?.status()} ${route}`); 117 - continue; 118 - } 119 - 120 - // Generate PDF 116 + await page.goto(url, { waitUntil: 'networkidle' }); 121 117 await page.pdf({ path: pdfPath, ...pdfOptions }); 122 118 console.log(`🖨️ ${pdfPath}`); 123 119 120 + // Engineering version 121 + const engUrl = `${serverUrl}/eng/${version}?print`; 122 + const engPdfName = version === 'main' ? 123 + 'morgan-williams-eng.pdf' : 124 + `morgan-williams.${version}-eng.pdf`; 125 + const engPdfPath = path.join('static', engPdfName); 126 + 127 + await page.goto(engUrl, { waitUntil: 'networkidle' }); 128 + await page.pdf({ path: engPdfPath, ...pdfOptions }); 129 + console.log(`🖨️ ${engPdfPath}`); 130 + 124 131 } catch (error) { 125 - console.error(`⚠️ ${route}:`, error); 132 + console.error(`⚠️ ${version}:`, error); 126 133 } 127 134 } 128 135
+71 -24
src/lib/CV.svelte
··· 45 45 browser && new URLSearchParams(window.location.search).has("print"); 46 46 </script> 47 47 48 - <div class="max-w-3xl mx-auto p-8 bg-background text-foreground"> 49 - <header class="flex items-start justify-between"> 48 + <div class="max-w-3xl mx-auto p-8 bg-background text-foreground print:p-0 print:max-w-none print:m-0"> 49 + <header class="flex items-start justify-between mb-8 print:mb-2 print:mt-0"> 50 50 <div> 51 51 <h1 class="text-4xl font-bold mb-1">{name}</h1> 52 52 <div class="text-muted-foreground"> ··· 71 71 > 72 72 {email} 73 73 </a> 74 - <a 74 + <!-- <a 75 75 href="https://morganwill.com/cal" 76 76 target="_blank" 77 77 rel="noopener noreferrer" 78 78 class="hover:text-foreground transition-colors block" 79 79 > 80 80 morganwill.com/cal 81 - </a> 81 + </a> --> 82 82 <a 83 83 href={github} 84 84 target="_blank" ··· 98 98 </div> 99 99 </header> 100 100 101 + <div class="flex items-center gap-4 mb-2 w-[81%] print:mb-1"> 102 + <h2 class="text-2xl font-semibold shrink-0">Experience</h2> 103 + <Separator class="flex-grow" /> 104 + </div> 105 + <Experience {experience} {highlightedSkill} /> 106 + 107 + <!-- Projects --> 101 108 {#if projects} 102 - <div class="flex items-center gap-4 mb-2 w-[85%]"> 109 + <div class="flex items-center gap-4 mb-2 w-[85%] print:mb-1"> 103 110 <h2 class="text-2xl font-semibold shrink-0">Projects</h2> 104 111 <Separator class="flex-grow" /> 105 112 </div> 106 113 <Projects {projects} /> 107 114 {/if} 108 115 109 - <div class="flex items-center gap-4 mb-2 w-[81%]"> 110 - <h2 class="text-2xl font-semibold shrink-0">Experience</h2> 111 - <Separator class="flex-grow" /> 112 - </div> 113 - <Experience {experience} {highlightedSkill} /> 114 - 115 - <div class="flex items-center gap-4 mb-2 w-[85%]"> 116 + <!-- Education --> 117 + <div class="flex items-center gap-4 mb-2 w-[85%] print:mb-1"> 116 118 <h2 class="text-2xl font-semibold shrink-0">Education</h2> 117 119 <Separator class="flex-grow" /> 118 120 </div> 119 - <section class="education grid grid-cols-2 gap-x-8 gap-y-2"> 121 + <section class="education grid grid-cols-2 gap-x-8 gap-y-2 print:gap-y-1 print:gap-x-4"> 120 122 {#each education as edu} 121 123 <div class="education-entry"> 122 124 <div class="flex justify-between items-baseline"> ··· 139 141 </div> 140 142 {/each} 141 143 </section> 144 + 142 145 143 - <footer class="print-footnote mt-auto pt-4 {isPrinting ? 'hidden' : ''}"> 146 + <footer class="print-footnote mt-auto pt-4 print:hidden"> 144 147 <details> 145 148 <summary class="flex items-center gap-1 cursor-pointer list-none"> 146 149 <span class="text-sm font-semibold">Related keywords</span> ··· 202 205 @media print { 203 206 @page { 204 207 size: A4; 205 - margin: 6mm 6mm; 208 + margin: 10mm 12mm 12mm 10mm; 209 + } 210 + 211 + :global(body) { 212 + margin: 0 !important; 213 + padding: 0 !important; 214 + font-size: 11pt !important; 215 + line-height: 1.4 !important; 216 + } 217 + 218 + :global(main), :global(div) { 219 + margin-top: 0 !important; 220 + } 221 + 222 + :global(.text-4xl) { 223 + font-size: 26pt; 224 + } 225 + 226 + :global(.text-2xl) { 227 + font-size: 15pt; 228 + } 229 + 230 + :global(.text-xl) { 231 + font-size: 13pt; 232 + } 233 + 234 + :global(.text-base) { 235 + font-size: 11pt; 236 + } 237 + 238 + :global(.text-sm) { 239 + font-size: 10pt; 240 + } 241 + 242 + :global(.text-xs) { 243 + font-size: 9pt; 244 + } 245 + 246 + :global(.mb-8) { 247 + margin-bottom: 1.25rem !important; 248 + } 249 + 250 + :global(.mb-4) { 251 + margin-bottom: 0.875rem !important; 206 252 } 207 - body { 208 - transform: scale(0.78); 209 - transform-origin: top left; 253 + 254 + :global(.mb-2) { 255 + margin-bottom: 0.5rem !important; 256 + } 257 + 258 + :global(.gap-2) { 259 + gap: 0.5rem !important; 210 260 } 211 - .max-w-3xl { 212 - transform: scale(0.85); 213 - transform-origin: top left; 214 - width: 120%; 215 - max-width: none; 216 - margin: 0; 261 + 262 + :global(.p-8) { 263 + padding: 1rem !important; 217 264 } 218 265 219 266 .print-footnote {
+139
src/lib/EngCV.svelte
··· 1 + <script lang="ts"> 2 + import type { CVProps } from "../types"; 3 + import { browser } from "$app/environment"; 4 + import mainData from "$lib/versions/main.json"; 5 + import { format } from "date-fns"; 6 + import { FileText } from "lucide-svelte"; 7 + 8 + // Destructure props with defaults from mainData 9 + let { 10 + name = mainData.name, 11 + title = mainData.title, 12 + email = mainData.email, 13 + github = mainData.github, 14 + projects, 15 + experience = mainData.experience, 16 + skills = mainData.skills, 17 + education = mainData.education, 18 + version, 19 + pdfLink = version ? `/morgan-williams.${version}-eng.pdf` : '/morgan-williams-eng.pdf' 20 + }: CVProps = $props(); 21 + 22 + const iconSize = 30; 23 + function formatDate(date: string): string { 24 + return format(new Date(date), "MMM yyyy"); 25 + } 26 + 27 + const isPrinting = browser && new URLSearchParams(window.location.search).has("print"); 28 + </script> 29 + 30 + <div class="max-w-[800px] mx-auto p-8 bg-white text-black print:p-4 font-serif"> 31 + <!-- Name --> 32 + <header class="text-center mb-4"> 33 + <h1 class="text-4xl font-bold">{name}</h1> 34 + 35 + <!-- Contact Info --> 36 + <div class="mt-2 text-sm space-x-2"> 37 + <a href={`mailto:${email}`} class="hover:underline">{email}</a> 38 + <span>|</span> 39 + <a href={github} class="hover:underline">github.com/mrgnw</a> 40 + </div> 41 + </header> 42 + 43 + <!-- Skills --> 44 + <section class="mb-6"> 45 + <h2 class="text-lg font-bold border-b border-black pb-0.5 mb-2">Skills</h2> 46 + <div class="flex flex-wrap gap-x-8"> 47 + {skills.join(', ')} 48 + </div> 49 + </section> 50 + 51 + <!-- Experience --> 52 + <section class="mb-6"> 53 + <h2 class="text-lg font-bold border-b border-black pb-0.5 mb-2">Experience</h2> 54 + {#each experience as job} 55 + <div class="mb-4"> 56 + <div class="flex justify-between items-baseline"> 57 + <div> 58 + <span class="font-bold">{job.title},</span> 59 + <span>{job.company}</span> 60 + </div> 61 + <span class="text-sm"> 62 + {formatDate(job.start)} – {job.end ? formatDate(job.end) : "Present"} 63 + </span> 64 + </div> 65 + <ul class="list-disc ml-4 mt-1"> 66 + {#each job.description as bullet} 67 + <li class="text-sm leading-tight mb-1">{bullet}</li> 68 + {/each} 69 + </ul> 70 + </div> 71 + {/each} 72 + </section> 73 + 74 + <!-- Projects (if any) --> 75 + {#if projects?.length} 76 + <section class="mb-6"> 77 + <h2 class="text-lg font-bold border-b border-black pb-0.5 mb-2">Projects</h2> 78 + {#each projects as project} 79 + <div class="mb-3"> 80 + <div class="flex justify-between items-baseline"> 81 + <a href={project.url} class="font-bold hover:underline">{project.name}</a> 82 + <a href={project.url} class="text-sm hover:underline"> 83 + {project.url.replace(/^https?:\/\/(www\.)?/, '')} 84 + </a> 85 + </div> 86 + <p class="text-sm mt-0.5">{project.description}</p> 87 + </div> 88 + {/each} 89 + </section> 90 + {/if} 91 + 92 + <!-- Education --> 93 + <section> 94 + <h2 class="text-lg font-bold border-b border-black pb-0.5 mb-2">Education</h2> 95 + {#each education as edu} 96 + <div class="flex justify-between items-baseline mb-1"> 97 + <div> 98 + <span class="font-bold">{edu.provider}</span> 99 + <span> — {edu.degree}</span> 100 + </div> 101 + <span>{edu.year}</span> 102 + </div> 103 + {/each} 104 + </section> 105 + </div> 106 + 107 + <!-- PDF Download --> 108 + <a 109 + href={pdfLink} 110 + target="_blank" 111 + rel="noopener noreferrer" 112 + aria-label="Download Morgan's CV" 113 + class="no-print fixed bottom-4 right-4 bg-white p-2 rounded-full shadow-lg" 114 + data-sveltekit-preload-data="hover" 115 + > 116 + <FileText size={iconSize} /> 117 + </a> 118 + 119 + <style> 120 + @media print { 121 + @page { 122 + margin: 0.5in; 123 + } 124 + :global(body) { 125 + font-family: "Times New Roman", Times, serif; 126 + line-height: 1.4; 127 + color: black; 128 + background: white; 129 + } 130 + } 131 + 132 + :global(body) { 133 + font-family: "Bitstream Charter", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif; 134 + } 135 + 136 + :global(h1, h2, h3, h4, h5, h6) { 137 + font-weight: 600; 138 + } 139 + </style>
+3 -3
src/lib/Experience.svelte
··· 25 25 26 26 <section class="mb-8"> 27 27 {#each experience as exp} 28 - <div class="mb-4"> 29 - <div class="flex flex-col sm:flex-row justify-between gap-4"> 28 + <div class="mb-4 print:mb-3"> 29 + <div class="flex flex-col sm:flex-row justify-between gap-4 print:gap-2"> 30 30 <div class="flex-1"> 31 31 <h3 class="text-xl font-semibold"> 32 32 {exp.title} ··· 56 56 </div> 57 57 </div> 58 58 {#each exp.description as paragraph} 59 - <p class="mb-2 mt-1">{paragraph}</p> 59 + <p class="mb-2 mt-1 print:mb-1.5 print:mt-0.5">{paragraph}</p> 60 60 {/each} 61 61 </div> 62 62 {/each}
+168
src/lib/versions/coinbase.json
··· 1 + { 2 + "name": "Morgan Williams", 3 + "email": "morganfwilliams@me.com", 4 + "github": "https://github.com/mrgnw", 5 + "style": { 6 + "margins": { 7 + "left": "20mm", 8 + "right": "20mm", 9 + "top": "20mm", 10 + "bottom": "20mm" 11 + } 12 + }, 13 + "skills": [ 14 + "Python", 15 + "APIs", 16 + "ETL" 17 + ], 18 + "projects": [ 19 + { 20 + "name": "Ananas", 21 + "url": "https://ananas.xcc.es", 22 + "description": "Multi-lingual translator with history", 23 + "stack": [ 24 + "APIs", 25 + "LLM", 26 + "Serverless", 27 + "Frontend" 28 + ] 29 + }, 30 + { 31 + "name": "Resume", 32 + "url": "https://github.com/mrgnw/resume", 33 + "description": "online CV with versions & automatic PDF generation", 34 + "stack": [ 35 + "Backend", 36 + "Frontend", 37 + "Serverless" 38 + ] 39 + } 40 + ], 41 + "experience": [ 42 + { 43 + "title": "Data Engineering Consultant", 44 + "company": "National Care Dental", 45 + "start": "2022-05-01", 46 + "stack": ["Python", "APIs", "Training", "ETL", "AWS"], 47 + "description": [ 48 + "Created API endpoints to enable clients and third-party sources to interface with company data.", 49 + "Transitioned legacy workflows to modern data pipelines, accelerating tasks by 100x to 1000x through automation, reducing multi-hour processes to seconds or milliseconds.", 50 + "Mentored data analysts in Python and SQL, expanding their skills and capabilities, resulting in a substantial increase in productivity." 51 + ] 52 + }, 53 + { 54 + "title": "Senior Data Engineer", 55 + "company": "Persefoni", 56 + "start": "2021-08-01", 57 + "end": "2022-05-31", 58 + "stack": ["Python", "GoLang", "Data Pipelines"], 59 + "description": [ 60 + "Created API endpoints in GoLang, learning new tech & collaborating across teams to interface multiple services.", 61 + "Documented & created new processes to onboard, train, and increase teammate productivity.", 62 + "Implemented ETL data pipelines using PySpark capable of processing large datasets" 63 + ] 64 + }, 65 + { 66 + "title": "Software Engineer", 67 + "company": "Zelis Healthcare", 68 + "start": "2018-12-01", 69 + "end": "2021-08-01", 70 + "stack": ["Python", "APIs", "Data Pipelines"], 71 + "description": [ 72 + "Created new ETL workflows in Airflow and optimized existing processing times up to 1000x.", 73 + "Migrated multiple codebases to Python3.", 74 + "Created Python APIs and internal web interfaces, empowering business users across teams to meaningfully interact with company data." 75 + ] 76 + } 77 + ], 78 + "education": [ 79 + { 80 + "degree": "Coding Bootcamp", 81 + "provider": "vschool.io", 82 + "summary": "Python + JavaScript", 83 + "year": "2014" 84 + }, 85 + { 86 + "degree": "BA in Russian", 87 + "provider": "BYU", 88 + "year": "2014", 89 + "summary": "minor in advertising communications" 90 + } 91 + ], 92 + "keywords": [ 93 + "Big data", 94 + "snowflake", 95 + "mentoring", 96 + "training", 97 + "Full-Stack Development", 98 + "JavaScript", 99 + "TypeScript", 100 + "Python", 101 + "Svelte", 102 + "Node.js", 103 + "Express.js", 104 + "FastAPI", 105 + "PostgreSQL", 106 + "DuckDB", 107 + "SQL", 108 + "AWS", 109 + "Linux", 110 + "HTML5", 111 + "CSS3", 112 + "GoLang", 113 + "ETL", 114 + "API Development", 115 + "Web Development", 116 + "Data Engineering", 117 + "RESTful APIs", 118 + "Responsive Design", 119 + "UI/UX Best Practices", 120 + "Scalability", 121 + "Automation", 122 + "Performance Optimization", 123 + "Cloud Computing", 124 + "DevOps Practices", 125 + "Codebase Migration", 126 + "Data Pipelines", 127 + "Server-Side Development", 128 + "Client-Server Architecture", 129 + "API Endpoints", 130 + "Web Interfaces", 131 + "FHIR", 132 + "SvelteKit", 133 + "Tailwind CSS", 134 + "Docker", 135 + "Airflow", 136 + "Databricks", 137 + "Spark", 138 + "SQLMesh", 139 + "Alteryx", 140 + "GitHub", 141 + "Bitbucket", 142 + "GitLab", 143 + "Jenkins", 144 + "Kubernetes", 145 + "CI/CD Pipelines", 146 + "Docker Compose", 147 + "Terraform", 148 + "Ansible", 149 + "Prometheus", 150 + "Grafana", 151 + "Version Control Systems", 152 + "Data Infrastructure Design", 153 + "Data Pipeline Implementation", 154 + "Legacy Workflow Transition", 155 + "Automation of Business Processes", 156 + "API Endpoint Creation", 157 + "Cross-Team Collaboration", 158 + "Documentation and Process Creation", 159 + "Onboarding and Training Programs", 160 + "Internal Web Tooling", 161 + "Climate-Change Data Solutions", 162 + "Performance Optimization in Data Queries", 163 + "Scalable API Development", 164 + "Interactive Web Application Development", 165 + "RESTful API Integration", 166 + "Responsive UI Implementation" 167 + ] 168 + }
+8 -7
src/lib/versions/main.json
··· 1 1 { 2 2 "name": "Morgan Williams", 3 3 "title": "Rapid full-stack development at scale", 4 - "email": "hi@mrgnw.dev", 4 + "email": "morganfwilliams@me.com", 5 5 "github": "https://github.com/mrgnw", 6 + "portfolio": "https://morganwill.com", 6 7 "skills": [ 7 8 "Python", 8 9 "PostgreSQL", ··· 52 53 ], 53 54 "education": [ 54 55 { 55 - "degree": "Coding Bootcamp", 56 - "provider": "vschool.io", 57 - "summary": "Python + JavaScript", 58 - "year": "2014" 59 - }, 60 - { 61 56 "degree": "BA in Russian Language & Literature", 62 57 "provider": "BYU", 63 58 "year": "2014", 64 59 "summary": "minor in advertising communications" 60 + }, 61 + { 62 + "degree": "Coding Bootcamp", 63 + "provider": "vschool.io", 64 + "summary": "Python + JavaScript", 65 + "year": "2014" 65 66 } 66 67 ], 67 68 "keywords": [
+1 -1
src/routes/+layout.svelte
··· 5 5 </script> 6 6 7 7 <svelte:head> 8 - <title>Morgan Williams - Full-Stack Developer</title> 8 + <title>Morgan Williams</title> 9 9 <meta name="description" content="Morgan Williams - Rapid full-stack development at scale. Explore my skills, experience, and education."> 10 10 <meta name="keywords" content="Morgan Williams, full-stack development, data engineering, Python, Svelte, data architecture, servers"> 11 11 </svelte:head>
+6
src/routes/eng/+page.svelte
··· 1 + <script lang="ts"> 2 + import EngCV from "$lib/EngCV.svelte"; 3 + import mainData from "$lib/versions/main.json"; 4 + </script> 5 + 6 + <EngCV {...mainData} />
+15
src/routes/eng/[slug]/+page.server.ts
··· 1 + import type { PageServerLoad } from './$types'; 2 + import { error } from '@sveltejs/kit'; 3 + import { coalesceVersion } from '$lib/versionReader'; 4 + 5 + export const load = (async ({ params }) => { 6 + const { slug } = params; 7 + const data = coalesceVersion(slug); 8 + 9 + if (!data) { 10 + console.error(`Error loading CV version "${slug}"`); 11 + error(404, 'CV version not found'); 12 + } 13 + 14 + return { ...data, slug }; 15 + }) satisfies PageServerLoad;
+15
src/routes/eng/[slug]/+page.svelte
··· 1 + <script lang="ts"> 2 + import EngCV from '$lib/EngCV.svelte'; 3 + import type { PageServerLoad } from './$types'; 4 + 5 + interface Props { 6 + data: PageServerLoad; 7 + } 8 + 9 + let { data }: Props = $props(); 10 + const pdfLink = data.slug === 'main' ? 11 + '/morgan-williams-eng.pdf' : 12 + `/morgan-williams.${data.slug}-eng.pdf`; 13 + </script> 14 + 15 + <EngCV {...data} {pdfLink} />
+93
src/routes/margin/+page.svelte
··· 1 + <script> 2 + let margin = $state(25); 3 + </script> 4 + 5 + <div class="page-container"> 6 + <div class="page"> 7 + <div class="content"> 8 + <div class="controls"> 9 + <label> 10 + Print margin size (mm): 11 + <input 12 + type="number" 13 + bind:value={margin} 14 + min="5" 15 + max="75" 16 + step="5" 17 + /> 18 + </label> 19 + </div> 20 + <p> 21 + This page demonstrates print margins using CSS variables. 22 + The current margin is set to {margin} millimeters. 23 + </p> 24 + </div> 25 + </div> 26 + </div> 27 + 28 + <style> 29 + .controls { 30 + padding: 1rem; 31 + border-bottom: 1px solid #ccc; 32 + } 33 + 34 + .page-container { 35 + width: 100%; 36 + display: flex; 37 + justify-content: center; 38 + padding: 2rem; 39 + } 40 + 41 + .page { 42 + /* Standard US Letter size in pixels (215.9 x 279.4 mm) */ 43 + width: 215.9mm; 44 + height: 279.4mm; 45 + position: relative; 46 + 47 + /* Add outline to visualize the page */ 48 + outline: 2px solid #333; 49 + background: white; 50 + box-shadow: 0 0 10px rgba(0,0,0,0.1); 51 + } 52 + 53 + .content { 54 + position: absolute; 55 + top: 25mm; 56 + right: 25mm; 57 + bottom: 25mm; 58 + left: 25mm; 59 + outline: 1px dashed #999; 60 + overflow: auto; 61 + } 62 + 63 + p { 64 + padding: 1rem; 65 + margin: 0; 66 + } 67 + 68 + input { 69 + margin-left: 0.5rem; 70 + width: 5rem; 71 + } 72 + 73 + @media print { 74 + .page { 75 + outline: none; 76 + box-shadow: none; 77 + width: 100%; 78 + height: 100%; 79 + } 80 + 81 + .content { 82 + top: var(--print-margin); 83 + right: var(--print-margin); 84 + bottom: var(--print-margin); 85 + left: var(--print-margin); 86 + outline: none; 87 + } 88 + 89 + .controls { 90 + display: none; 91 + } 92 + } 93 + </style>
-1
src/types/index.ts
··· 50 50 title: string; 51 51 email: string; 52 52 github: string; 53 - pdfLink: string; 54 53 projects: Project[]; 55 54 experience: ExperienceItem[]; 56 55 skills: string[];
static/morgan-williams-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.backend-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.backend.pdf

This is a binary file and will not be displayed.

static/morgan-williams.coinbase-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.coinbase.pdf

This is a binary file and will not be displayed.

static/morgan-williams.data-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.data.pdf

This is a binary file and will not be displayed.

static/morgan-williams.dx-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.dx.pdf

This is a binary file and will not be displayed.

static/morgan-williams.english-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.english.pdf

This is a binary file and will not be displayed.

static/morgan-williams.fullstack-eng.pdf

This is a binary file and will not be displayed.

static/morgan-williams.fullstack.pdf

This is a binary file and will not be displayed.

static/morgan-williams.pdf

This is a binary file and will not be displayed.

+8 -1
tailwind.config.ts
··· 56 56 }, 57 57 fontFamily: { 58 58 sans: [...fontFamily.sans] 59 - } 59 + }, 60 + screens: { 61 + 'print': {'raw': 'print'}, 62 + }, 60 63 } 61 64 }, 62 65 plugins: [ ··· 66 69 '@media print': { 67 70 display: 'none', 68 71 }, 72 + }, 73 + '.print-exact': { 74 + '-webkit-print-color-adjust': 'exact', 75 + 'print-color-adjust': 'exact', 69 76 }, 70 77 }); 71 78 },