Customize resume data, themes, export script, and PDF outputs
- Update resume/data.yml and English strings - Adjust resume.vue, options, and theme components - Change export flow in scripts/export.js and package.json - Refresh PDF artifacts; add static/green.pdf; trim unused pdf/ Made-with: Cursor
This commit is contained in:
parent
29e753cd84
commit
c8a49018b1
10
package.json
10
package.json
@ -9,11 +9,13 @@
|
||||
"url": "git+https://github.com/salomonelli/best-resume-ever.git"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "node build/build.js",
|
||||
"docs": "node --openssl-legacy-provider build/build.js",
|
||||
"docs:serve": "cd docs/ && ws --port 8080 --rewrite '/best-resume-ever/* -> /$1'",
|
||||
"dev": "node build/dev-server.js",
|
||||
"start": "node build/dev-server.js",
|
||||
"dev": "node --openssl-legacy-provider build/dev-server.js",
|
||||
"start": "node --openssl-legacy-provider build/dev-server.js",
|
||||
"pdf": "node scripts/export.js",
|
||||
"pdf:green": "node scripts/export.js green",
|
||||
"export:green": "concurrently \"npm run dev\" \"node scripts/export.js green\" --success first --kill-others --raw",
|
||||
"preview": "node scripts/preview.js",
|
||||
"test:deleteFiles": "node test/scripts/deleteFiles.js",
|
||||
"test:cafe": "testcafe chromium test/",
|
||||
@ -84,7 +86,7 @@
|
||||
"pdf-image": "2.0.0",
|
||||
"postcss": "7.0.4",
|
||||
"postcss-cssnext": "3.1.0",
|
||||
"puppeteer": "1.8.0",
|
||||
"puppeteer": "^22.15.0",
|
||||
"rename": "1.0.4",
|
||||
"request": "2.88.0",
|
||||
"request-promise": "4.2.2",
|
||||
|
||||
BIN
pdf/cool-rtl.pdf
BIN
pdf/cool-rtl.pdf
Binary file not shown.
Binary file not shown.
BIN
pdf/cool.pdf
BIN
pdf/cool.pdf
Binary file not shown.
BIN
pdf/creative.pdf
BIN
pdf/creative.pdf
Binary file not shown.
BIN
pdf/green.pdf
BIN
pdf/green.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
pdf/oblique.pdf
BIN
pdf/oblique.pdf
Binary file not shown.
BIN
pdf/purple.pdf
BIN
pdf/purple.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
pdf/side-bar.pdf
BIN
pdf/side-bar.pdf
Binary file not shown.
243
resume/data.yml
243
resume/data.yml
@ -1,98 +1,195 @@
|
||||
/* #*/ export const PERSON = `
|
||||
# Any fields left unchanged, please delete so your resume is fully yours!
|
||||
# Experience: timeperiod = dates only; location = separate line. description: | = one bullet per line.
|
||||
# Optional: remote: true | false | hybrid; employment: contract | full-time | co-op (icons + legend on green/purple).
|
||||
# Optional contact: github_profile = public GitHub.com username (add if you use both Forge + GitHub).
|
||||
|
||||
name:
|
||||
first: John
|
||||
first: ILIA
|
||||
middle:
|
||||
last: Doe
|
||||
about: Hi, my name is John Doe. I'm just about the most boring type of person you could
|
||||
possibly imagine. I like collecting leaves from the tree in my back yard and documenting
|
||||
each time I eat a peanut that is non-uniform. I am not a robot. Please hire me.
|
||||
position: Software Developer
|
||||
last: DOBKIN
|
||||
about: "Driven software engineer with 20+ years of experience spanning product, platform, and industrial test automation — from global audit and financial systems (CaseWare, MNP, JazzIt) to modern web delivery for startups and enterprises alike. Builds and maintains a self-hosted infrastructure lab (Proxmox, Ansible, Caddy, CI runners) that mirrors production-grade DevOps practices. Seeking roles where I can provide testing guidance, strengthen CI/CD operations, and collaborate with teams to optimize product delivery."
|
||||
core_strengths: "E2E test automation (Cypress, Playwright, Selenium), BDD (SpecFlow, Cucumber), accessibility (AODA/WCAG), observability (Grafana, Prometheus), IaC (Terraform), API and performance testing (Postman, Artillery), and reusable frameworks built for team adoption."
|
||||
position: Software Development Engineer in Test
|
||||
|
||||
birth:
|
||||
year: 1990
|
||||
location: New York
|
||||
|
||||
# you may add more experiences by duplicating the template
|
||||
year:
|
||||
location: Thornhill, Ontario, Canada
|
||||
|
||||
experience:
|
||||
- company: Company A
|
||||
position: Developer
|
||||
timeperiod: since January 2016
|
||||
description: Programming and watching cute cat videos.
|
||||
website: https://example.com
|
||||
- company: Niyasoft Canada Inc.
|
||||
position: Test Automation Engineer
|
||||
timeperiod: August 2023 - April 2026
|
||||
remote: true
|
||||
employment: full-time
|
||||
location: Vaughan, Ontario, Canada
|
||||
description: |
|
||||
• Test Automation Engineer at an iGaming technology company specializing in online casino platform delivery; contributed to regulated, operator-facing products in an Agile environment with front-end and back-end teams.
|
||||
• Built and maintained Playwright UI automation, API and integration tests, and Artillery performance suites; focused coverage on high-risk journeys including payments, wallet and cashier flows, game and lobby integrations, and supporting back-office capabilities.
|
||||
• Validated responsible gaming and player-protection behavior: deposit, loss, and session limits; self-exclusion and cooling-off; reality checks and safer-gambling messaging; ensured flows behaved correctly for compliance and operator policy.
|
||||
• Exercised compliance-sensitive scenarios such as market and geo-eligibility, age-gating touchpoints, restricted jurisdictions, and audit-friendly logging and traceability expected in licensed wagering markets.
|
||||
• Managed GitHub Actions CI/CD for regression, functional, component, and smoke stages; used pull requests and code review daily to keep releases predictable for a high-availability real-money platform.
|
||||
• Monitored API behavior and reliability with GCP, Prometheus metrics, and broader observability, logging, and alerting; validated PostgreSQL-backed data and integrations while keeping automation aligned with GitHub-driven workflows.
|
||||
|
||||
- company: Company B
|
||||
position: Frontend Developer
|
||||
timeperiod: January 2015 - December 2015
|
||||
description: Fulfillment of extremely important tasks.
|
||||
- company: RIOS Canada
|
||||
position: Software Development Engineer in Test
|
||||
timeperiod: June 2022 - July 2023
|
||||
remote: true
|
||||
employment: contract
|
||||
location: Toronto, Ontario, Canada
|
||||
description: |
|
||||
• Integrated end-to-end Cypress automation from the ground up for GUI and API testing across critical product flows; led training for engineering.
|
||||
• Conducted AODA accessibility work: alt text, keyboard navigation, color contrast; scaled regression across web and mobile via Bitbucket CI/CD.
|
||||
• Used Ansible to automate provisioning for repeatable test environments; partnered with engineers and product on triage and quality gates.
|
||||
• Partnered with software engineers and product on defect triage, test reporting, and pragmatic quality gates.
|
||||
|
||||
- company: Company C
|
||||
position: Trainee
|
||||
timeperiod: March 2014 - December 2014
|
||||
description: Making coffee and baking cookies.
|
||||
- company: Attabotics
|
||||
position: QA Automation Developer
|
||||
timeperiod: September 2021 - May 2022
|
||||
remote: true
|
||||
employment: contract
|
||||
location: Calgary, Alberta, Canada
|
||||
description: |
|
||||
• Wrote Gherkin/SpecFlow scenarios with C# step definitions; sustained 3,500+ automated scenarios in a .NET / Azure environment.
|
||||
• Practiced left-shift QA within a large Agile team: testers engaged early in design and development.
|
||||
• Used Docker for local test environments and SQL Server for test data validation and traceability.
|
||||
|
||||
education:
|
||||
- degree: Master of Arts
|
||||
timeperiod: March 2012 - December 2013
|
||||
description: Major in Hacking and Computer Penetration, University A, New York, USA.
|
||||
website: https://example.com
|
||||
- company: Levkin Inc.
|
||||
position: Senior Software Developer
|
||||
timeperiod: October 2020 - August 2021
|
||||
remote: true
|
||||
employment: contract
|
||||
location: Vaughan, Ontario, Canada
|
||||
description: |
|
||||
• Built reusable Playwright testing building blocks with deterministic patterns, reducing flakiness and eliminating reliance on arbitrary sleeps or built-in waits.
|
||||
• Audited and refactored legacy test and UI code toward current standards; documented testing strategy and shared knowledge across the team.
|
||||
• Optimized GitLab CI/CD pipelines for speed and reliability; piped test and pipeline metrics into Grafana dashboards for release visibility.
|
||||
• Used GitLab for repositories, merge requests, and code review as part of day-to-day development and collaboration.
|
||||
• Provisioned AWS environments with Terraform, validated them end-to-end, and promoted changes to dev following the team's standard release procedure.
|
||||
|
||||
- degree: Bachelor of Science
|
||||
timeperiod: March 2009 - December 2011
|
||||
description: Major in Engineering, University B, Los Angeles, USA.
|
||||
- company: Accountants Templates Inc.
|
||||
position: Senior Software Developer
|
||||
timeperiod: August 2019 - August 2020
|
||||
remote: true
|
||||
employment: contract
|
||||
location: Calgary, Alberta, Canada
|
||||
description: |
|
||||
• Owned CaseWare/CaseView template delivery: compliance updates, standards-driven releases, and documentation for internal and client use.
|
||||
• Reviewed software for improvements and implemented recommendations; collaborated with support on reported issues.
|
||||
• Streamlined build and packaging workflows, removing approximately eight hours of manual effort per release cycle.
|
||||
|
||||
# skill level goes from 0 to 100
|
||||
- company: MNP LLP
|
||||
position: Senior Application Developer
|
||||
timeperiod: August 2017 - June 2019
|
||||
remote: true
|
||||
employment: full-time
|
||||
location: Toronto, Ontario, Canada
|
||||
description: |
|
||||
• Designed, developed, and maintained software integrating with CaseWare/CaseView; extended functionality with JavaScript where specifications required.
|
||||
• Delivered and maintained C# / .NET / .NET Core applications from technical specification through production.
|
||||
• Assisted automation testers with Selenium tests, Jenkins triggers, Cucumber reporting, and JIRA summaries for management.
|
||||
• Contributed automation strategy alongside hands-on Selenium/Cucumber work and Azure DevOps for planning, repos, and deployments.
|
||||
|
||||
- company: CaseWare International Inc.
|
||||
position: Software Developer
|
||||
timeperiod: August 2006 - June 2017
|
||||
remote: hybrid
|
||||
employment: full-time
|
||||
location: Toronto, Ontario, Canada
|
||||
description: |
|
||||
• Implemented features, resolved defects, and maintained financial and audit systems; supported distributors and clients.
|
||||
• Delivered client templates with JavaScript, HTML, YUI, jQuery, JSON, and CSS at global scale.
|
||||
• Designed and executed automated validation with SilkTest; mentored junior developers, built reusable JS libraries, and used Agile Scrum, Jira, and Git.
|
||||
|
||||
- company: ROLI Consulting
|
||||
position: Web/Application Developer
|
||||
timeperiod: January 2001 - July 2012
|
||||
remote: true
|
||||
employment: full-time
|
||||
location: Vaughan, Ontario, Canada
|
||||
description: |
|
||||
• Developed and maintained a voice broadcasting system and text-messaging service using Python and the Twilio API for commercial clients.
|
||||
• Designed and maintained websites across multiple stacks, including WordPress; provided general technical consulting for nonprofits and SMBs.
|
||||
|
||||
- company: Earlier Career
|
||||
timeperiod: May 2005 - August 2006
|
||||
sub_companies:
|
||||
- name: Kaboose Inc.
|
||||
remote: false
|
||||
employment: contract
|
||||
- name: Coutts Information Services
|
||||
remote: false
|
||||
employment: full-time
|
||||
- name: EDS / Scotiabank
|
||||
remote: false
|
||||
employment: co-op
|
||||
description: |
|
||||
• QA automation with QTP/Quality Center, cross-browser and end-to-end testing, and UAT support (Kaboose); Java/J2EE development (Coutts); Informatica ETL co-op on AIX/DB2 (EDS/Scotiabank).
|
||||
|
||||
education: []
|
||||
|
||||
# Skills: each line is one grid cell in green template (two columns).
|
||||
skills:
|
||||
- name: HTML5
|
||||
level: 99
|
||||
- name: CSS3
|
||||
level: 95
|
||||
- name: JavaScript
|
||||
level: 97
|
||||
- name: Node.js
|
||||
level: 93
|
||||
- name: Angular 2
|
||||
level: 60
|
||||
- name: TypeScript
|
||||
- name: "Test automation: Cypress, Playwright, Selenium, SilkTest, mobile & cross-browser E2E, API testing, JUnit, PyTest, TestRail, page object model"
|
||||
level: 96
|
||||
- name: "Languages & stacks: TypeScript, JavaScript, C#, Python, Java, .NET & ASP.NET, Node.js, HTML, CSS, Spring Boot"
|
||||
level: 92
|
||||
- name: "CI/CD: GitHub Actions, GitHub, GitLab CI, Bitbucket, Jenkins, Azure DevOps, Ansible, self-hosted runners"
|
||||
level: 92
|
||||
- name: "BDD: SpecFlow, Cucumber, Gherkin, .NET test stacks"
|
||||
level: 86
|
||||
- name: "Cloud & containers: AWS Lambda, Azure, GCP, Google Cloud, Docker"
|
||||
level: 84
|
||||
- name: "API & performance: Postman, Artillery, JMeter, REST APIs, Twilio, service testing"
|
||||
level: 88
|
||||
- name: "Observability: Grafana, Prometheus, Sentry, DataDog, metrics, logging, pipeline telemetry"
|
||||
level: 80
|
||||
- name: ES.Next
|
||||
level: 70
|
||||
- name: Docker
|
||||
level: 99
|
||||
knowledge: Also proficient in Adobe Photoshop and Illustrator, grew up bilingual
|
||||
(English and Klingon).
|
||||
- name: "Accessibility: AODA, WCAG-oriented audits"
|
||||
level: 80
|
||||
- name: "Data platforms: PostgreSQL, MySQL, SQL Server, DB2, Informatica, ETL"
|
||||
level: 78
|
||||
- name: "Delivery: Agile, Scrum, shift-left QA, Jira, Confluence, cross-functional SDET collaboration"
|
||||
level: 92
|
||||
- name: "Domain: CaseWare, CaseView, Crystal Reports, audit & finance software"
|
||||
level: 76
|
||||
- name: "Version control & IaC: Git, Terraform, Ansible, infrastructure as code"
|
||||
level: 88
|
||||
- name: "Homelab & local AI: Proxmox, Linux, Caddy, TrueNAS, Vaultwarden, SonarQube, n8n, Gitea, DNS, local LLM/GPU inference, privacy-first agents"
|
||||
level: 78
|
||||
|
||||
# Dense keyword line for ATS / AI parsers — mirrors tools and titles in your experience.
|
||||
knowledge: SDET, software development engineer in test, test automation engineer, QA automation, Cypress, Playwright, Selenium, SpecFlow, Cucumber, Gherkin, BDD, TypeScript, JavaScript, C#, .NET, Node.js, Python, PyTest, Java, Spring Boot, HTML, CSS, REST API, E2E, GitHub, GitLab, Bitbucket, Jenkins, Azure DevOps, Ansible, Terraform, infrastructure as code, CI/CD, continuous integration, Docker, Proxmox, AWS Lambda, Azure, GCP, Google Cloud, Prometheus, Grafana, Sentry, DataDog, Postman, Artillery, JMeter, JUnit, AODA, WCAG, accessibility testing, cross-browser testing, mobile testing, regression testing, smoke testing, functional testing, test strategy, page object model, PostgreSQL, MySQL, SQL Server, DB2, Informatica, ETL, Crystal Reports, TestRail, SilkTest, ASP.NET, Twilio, CaseWare, CaseView, Jira, Confluence, Agile, Scrum, shift-left QA, financial software, Linux, Caddy, TrueNAS, Vaultwarden, SonarQube, n8n, self-hosted CI runners, DNS, domain management, local AI, LLM, GPU inference, privacy-first automation, agentic workflows, email and calendar integration
|
||||
|
||||
projects:
|
||||
- name: best-resume-ever
|
||||
platform: Vue
|
||||
timeperiod: February 2016
|
||||
description: 👔 💼 Build fast 🚀 and easy multiple beautiful resumes and create your best CV ever! Made with Vue and LESS.
|
||||
url: https://github.com/salomonelli/best-resume-ever
|
||||
- name: Self-Hosted Infrastructure Lab
|
||||
description: |
|
||||
• Built and maintain a Proxmox-based homelab running production-grade services: Gitea, Vaultwarden, Vikunja, Uptime Kuma, Mailcow, Listmonk, n8n, SonarQube, and self-hosted CI runners.
|
||||
• Manage all provisioning, configuration, and site deployments with Ansible playbooks; use Caddy as a reverse proxy with automatic TLS.
|
||||
• Maintain several domains and host personal sites and services; administer Linux servers end-to-end including networking, security hardening, and monitoring.
|
||||
• Operate TrueNAS for networked storage and backups; use Vaultwarden for secure credential and sensitive data management.
|
||||
|
||||
# optional, not all resume templates have hobbies included
|
||||
hobbies:
|
||||
- name: Video Games
|
||||
iconClass: fa fa-gamepad
|
||||
url: https://example.com
|
||||
- name: Privacy-First Local AI Assistant
|
||||
description: |
|
||||
• Building a personalized, tool-using assistant wired into email, calendar, and everyday productivity flows—triage, drafting, scheduling, and repeatable tasks—so it can actually do work instead of stopping at generic chat.
|
||||
• Driving inference on local GPU hardware with self-hosted models so mail, calendar context, and prompts stay off third-party clouds; architecting for speed, control, and a hard privacy boundary aligned with the homelab stack.
|
||||
|
||||
- name: Drawing
|
||||
iconClass: fa fa-pencil
|
||||
url: https://example.com
|
||||
hobbies: []
|
||||
|
||||
contributions:
|
||||
- name: best-resume-ever
|
||||
description: 👔 💼 Build fast 🚀 and easy multiple beautiful resumes.
|
||||
url: https://github.com/salomonelli/best-resume-ever
|
||||
contributions: []
|
||||
|
||||
contact:
|
||||
email: john.doe@email.com
|
||||
phone: 0123 456789
|
||||
street: 1234 Broadway
|
||||
city: New York
|
||||
website: johndoe.com
|
||||
github: johnyD
|
||||
email: idobkin@gmail.com
|
||||
phone: +1 (647) 987-2792
|
||||
street:
|
||||
city: Toronto, Ontario, Canada
|
||||
website: https://www.linkedin.com/in/idobkin/
|
||||
website_label: LinkedIn
|
||||
github: https://git.levkin.ca
|
||||
github_label: Gitea
|
||||
personal_site: https://iliadobkin.com
|
||||
personal_site_label: iliadobkin.com
|
||||
# Uncomment and set your public github.com username if different from the Forge URL above:
|
||||
# github_profile: yourusername
|
||||
# github_profile_label: GitHub
|
||||
# en, de, fr, pt, ca, cn, it, es, th, pt-br, ru, sv, id, hu, pl, ja, ka, nl, he, zh-tw, lt, ko, el, nb-no
|
||||
lang: en
|
||||
`
|
||||
|
||||
@ -4,6 +4,8 @@ const path = require('path');
|
||||
const http = require('http');
|
||||
const config = require('../config');
|
||||
|
||||
const devPort = process.env.PORT || config.dev.port;
|
||||
|
||||
const {
|
||||
interval
|
||||
} = require('rxjs');
|
||||
@ -16,7 +18,7 @@ const {
|
||||
const fetchResponse = () => {
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
const req = http.request(`http://localhost:${config.dev.port}/#/`, response => res(response.statusCode));
|
||||
const req = http.request(`http://localhost:${devPort}/#/`, response => res(response.statusCode));
|
||||
req.on('error', (err) => rej(err));
|
||||
req.end();
|
||||
} catch (err) {
|
||||
@ -53,15 +55,35 @@ const convert = async () => {
|
||||
console.log('Exporting ...');
|
||||
try {
|
||||
const fullDirectoryPath = path.join(__dirname, '../pdf/');
|
||||
const directories = getResumesFromDirectories();
|
||||
directories.forEach(async (dir) => {
|
||||
let directories = getResumesFromDirectories();
|
||||
const resumeFilterRaw = (process.env.EXPORT_RESUME || process.argv[2] || '').trim();
|
||||
const resumeFilter = resumeFilterRaw.replace(/\.vue$/i, '').toLowerCase();
|
||||
if (resumeFilter) {
|
||||
directories = directories.filter(d => d.name.toLowerCase() === resumeFilter);
|
||||
if (directories.length === 0) {
|
||||
console.error(
|
||||
`No resume template "${resumeFilterRaw}". Expected a name like "green" (see src/resumes/*.vue).`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Resume filter: ' + directories.map(d => d.name).join(', '));
|
||||
}
|
||||
for (const dir of directories) {
|
||||
const browser = await puppeteer.launch({
|
||||
args: ['--no-sandbox']
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--font-render-hinting=none']
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.goto(`http://localhost:${config.dev.port}/#/resume/` + dir.name, {
|
||||
waitUntil: 'networkidle2'
|
||||
await page.goto(`http://localhost:${devPort}/#/resume/` + dir.name, {
|
||||
waitUntil: 'load',
|
||||
timeout: 120000
|
||||
});
|
||||
try {
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
} catch (_err) {
|
||||
/* ignore if fonts API missing */
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
|
||||
if (
|
||||
!fs.existsSync(fullDirectoryPath)
|
||||
@ -70,10 +92,11 @@ const convert = async () => {
|
||||
}
|
||||
await page.pdf({
|
||||
path: fullDirectoryPath + dir.name + '.pdf',
|
||||
format: 'A4'
|
||||
format: 'A4',
|
||||
printBackground: true
|
||||
});
|
||||
await browser.close();
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ const lang = {
|
||||
born: 'Born',
|
||||
bornIn: 'in',
|
||||
experience: 'Experience',
|
||||
experienceLegendIntro: 'Key',
|
||||
education: 'Education',
|
||||
skills: 'Skills',
|
||||
projects: 'Projects',
|
||||
|
||||
@ -18,7 +18,7 @@ export default Vue.component('resume', {
|
||||
|
||||
<style scoped>
|
||||
.page-inner{
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.page-wrapper {
|
||||
@ -31,7 +31,7 @@ export default Vue.component('resume', {
|
||||
}
|
||||
|
||||
.resume {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -39,9 +39,10 @@ export default Vue.component('resume', {
|
||||
background: white;
|
||||
position: relative;
|
||||
width: 21cm;
|
||||
height: 29.68cm;
|
||||
min-height: 29.68cm;
|
||||
height: auto;
|
||||
display: block;
|
||||
page-break-after: auto;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div v-if="person.education && person.education.length" class="section">
|
||||
<div class="section-headline">
|
||||
<i class="section-headline__icon material-icons">school</i>{{ lang.education }}
|
||||
</div>
|
||||
@ -149,7 +149,7 @@
|
||||
<a
|
||||
v-if="person.contact.website"
|
||||
class="section-link"
|
||||
:href="person.contact.website">
|
||||
:href="contactLinks.website">
|
||||
<i class="section-link__icon fa fa-globe"></i>{{ person.contact.website }}
|
||||
</a>
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
<a
|
||||
v-if="person.contact.website"
|
||||
class="section-link"
|
||||
:href="person.contact.website">
|
||||
:href="contactLinks.website">
|
||||
<i class="section-link__icon fa fa-globe"></i>{{ person.contact.website }}
|
||||
</a>
|
||||
|
||||
@ -119,7 +119,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div v-if="person.education && person.education.length" class="section">
|
||||
<div class="section-headline">
|
||||
<i class="section-headline__icon material-icons">school</i>{{ lang.education }}
|
||||
</div>
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
<a
|
||||
v-if="person.contact.website"
|
||||
class="section-link link"
|
||||
:href="person.contact.website">
|
||||
:href="contactLinks.website">
|
||||
<i class="section-link__icon fa fa-globe"></i>{{ person.contact.website }}
|
||||
</a>
|
||||
|
||||
@ -119,7 +119,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div v-if="person.education && person.education.length" class="section">
|
||||
<div class="section-headline">
|
||||
<i class="section-headline__icon material-icons">school</i>{{ lang.education }}
|
||||
</div>
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
<div class="social-container">
|
||||
<a v-if="person.contact.website"
|
||||
:href="person.contact.website">
|
||||
:href="contactLinks.website">
|
||||
|
||||
<div class="block-marged txt-full-white">
|
||||
<i class="fa fa-globe contact-icon"></i>
|
||||
@ -115,7 +115,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="education-section section">
|
||||
<div v-if="person.education && person.education.length" class="education-section section">
|
||||
<div class="icon">
|
||||
<i class="material-icons">school</i>
|
||||
<span class="section-headline">{{ lang.education }}</span>
|
||||
|
||||
@ -8,22 +8,101 @@
|
||||
<span id="email"><a :href='"mailto:" + person.contact.email'>
|
||||
<i class="fa fa-envelope" aria-hidden="true"></i> {{person.contact.email}}</a></span>
|
||||
<span id="phone"><i class='fa fa-phone-square' aria-hidden="true"></i> {{person.contact.phone}}</span>
|
||||
<span v-if="person.contact.website" id="website"><a :href='person.contact.website'><i class="fa fa-home" aria-hidden="true"></i> {{person.contact.website}}</a></span>
|
||||
<span v-if="person.contact.github" id="github"><a :href='contactLinks.github'><i class="fa fa-github" aria-hidden="true"></i> {{person.contact.github}}</a></span>
|
||||
<span v-if="person.contact.website" id="website"><a :href="contactLinks.website"><i class="fa fa-linkedin" aria-hidden="true"></i> {{ person.contact.website_label || person.contact.website }}</a></span>
|
||||
<span v-if="person.contact.github" id="github"><a :href="contactLinks.github"><i class="fa fa-code-fork" aria-hidden="true"></i> {{ person.contact.github_label || 'Git' }}</a></span>
|
||||
<span v-if="person.contact.github_profile && contactLinks.github_profile" id="github-profile"><a :href="contactLinks.github_profile"><i class="fa fa-github" aria-hidden="true"></i> {{ person.contact.github_profile_label || ('@' + String(person.contact.github_profile).replace(/^@/, '')) }}</a></span>
|
||||
<span v-if="person.contact.personal_site" id="personal-site"><a :href="contactLinks.personal_site"><i class="fa fa-globe" aria-hidden="true"></i> {{ person.contact.personal_site_label || person.contact.personal_site }}</a></span>
|
||||
<span v-if="person.birth && person.birth.location" id="location"><i class="fa fa-map-marker" aria-hidden="true"></i> {{person.birth.location}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="header-right">
|
||||
<div id="headshot"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="resume-about" v-if="person.about">
|
||||
<h2>{{ lang.about }}</h2>
|
||||
<p>{{person.about}}</p>
|
||||
<p v-if="person.core_strengths" class="core-strengths"><span class="core-strengths-label">Core strengths:</span> {{person.core_strengths}}</p>
|
||||
</div>
|
||||
<div id="resume-body">
|
||||
<div id="experience-container">
|
||||
<h2 id="experience-title">{{ lang.experience }}</h2>
|
||||
<p
|
||||
v-if="person.experience && person.experience.length"
|
||||
class="experience-legend"
|
||||
role="note">
|
||||
<span class="experience-legend-intro">{{ lang.experienceLegendIntro }}:</span>
|
||||
<span
|
||||
v-for="item in workArrangementLegendItems()"
|
||||
:key="item.key"
|
||||
class="experience-legend-item">
|
||||
<i :class="item.icon" aria-hidden="true"></i>
|
||||
<span class="experience-legend-text">{{ item.label }}</span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="spacer"></div>
|
||||
<div class="experience" v-for="experience in person.experience" :key="experience.company">
|
||||
<h2 class="company">{{experience.company}}</h2>
|
||||
<p class="job-info"><span class="job-title">{{experience.position}} | </span><span class="experience-timeperiod">{{experience.timeperiod}}</span></p>
|
||||
<p class="job-description" v-if="experience.description">{{experience.description}}</p>
|
||||
<h2 class="company-row">
|
||||
<span class="company-primary">
|
||||
<span class="company">{{experience.company}}</span><!--
|
||||
--><span
|
||||
v-if="experienceWorkBadges(experience).length"
|
||||
class="exp-name-icon-gutter"
|
||||
aria-hidden="true"></span><!--
|
||||
--><span
|
||||
v-if="experienceWorkBadges(experience).length"
|
||||
class="experience-icons"
|
||||
aria-hidden="true">
|
||||
<span
|
||||
v-for="b in experienceWorkBadges(experience)"
|
||||
:key="b.key"
|
||||
class="exp-icon-cell">
|
||||
<i
|
||||
:class="b.icon"
|
||||
:title="b.label"></i>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="experienceLocation(experience)" class="company-location">{{ experienceLocation(experience) }}</span>
|
||||
</h2>
|
||||
<p class="job-info" v-if="!experience.sub_companies">
|
||||
<span class="job-title">{{experience.position}}</span>
|
||||
<span class="experience-timeperiod">{{ experienceDateRange(experience) }}</span>
|
||||
</p>
|
||||
<p class="job-info sub-companies-line" v-if="experience.sub_companies && experience.sub_companies.length">
|
||||
<span class="sub-companies-list">
|
||||
<span
|
||||
v-for="(sub, si) in experience.sub_companies"
|
||||
:key="si"
|
||||
class="sub-company-entry">
|
||||
<span class="sub-company-name">{{ sub.name }}</span><!--
|
||||
--><span
|
||||
v-if="experienceWorkBadges(sub).length"
|
||||
class="sub-icon-gutter"
|
||||
aria-hidden="true"></span><!--
|
||||
--><span
|
||||
v-for="b in experienceWorkBadges(sub)"
|
||||
:key="b.key"
|
||||
class="sub-icon-cell"
|
||||
aria-hidden="true"><i
|
||||
:class="b.icon"
|
||||
:title="b.label"></i></span>
|
||||
<span
|
||||
v-if="si < experience.sub_companies.length - 1"
|
||||
class="sub-company-sep"
|
||||
aria-hidden="true">·</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="experience-timeperiod">{{ experienceDateRange(experience) }}</span>
|
||||
</p>
|
||||
<ul
|
||||
v-if="experienceDescriptionLines(experience).length"
|
||||
class="job-description-list">
|
||||
<li
|
||||
v-for="(line, idx) in experienceDescriptionLines(experience)"
|
||||
:key="idx"
|
||||
class="list-item-black">{{ stripLeadingBullet(line) }}</li>
|
||||
</ul>
|
||||
<ul v-if="experience.list" >
|
||||
<li v-for="(item, index) in experience.list" :key="index">
|
||||
<span class="list-item-black">
|
||||
@ -33,7 +112,22 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="education-container">
|
||||
<div id="projects-container" v-if="person.projects && person.projects.length">
|
||||
<h2 id="projects-title">{{ lang.projects }}</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="project" v-for="project in person.projects" :key="project.name">
|
||||
<h2 class="project-name">{{ project.name }}</h2>
|
||||
<ul
|
||||
v-if="experienceDescriptionLines(project).length"
|
||||
class="job-description-list">
|
||||
<li
|
||||
v-for="(line, idx) in experienceDescriptionLines(project)"
|
||||
:key="idx"
|
||||
class="list-item-black">{{ stripLeadingBullet(line) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="education-container" v-if="person.education && person.education.length">
|
||||
<h2 id="education-title">{{ lang.education }}</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="education" v-for="education in person.education" :key="education.degree">
|
||||
@ -41,23 +135,18 @@
|
||||
<p><span class="degree">{{education.degree}} | </span><span class="education-timeperiod">{{education.timeperiod}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="skills-container" v-if="person.skills != []">
|
||||
<div id="skills-container" v-if="person.skills && person.skills.length">
|
||||
<h2 id="skills-title">{{ lang.skills }}</h2>
|
||||
<div class="spacer"></div>
|
||||
<p id="skill-description">{{person.knowledge}}</p>
|
||||
<ul id="skill-list">
|
||||
<li class="skill" v-for="skill in person.skills" :key="skill.name">
|
||||
<span class="list-item-black">
|
||||
{{skill.name}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="resume-footer">
|
||||
<div v-if="person.about">
|
||||
<h2>{{ lang.about }}</h2>
|
||||
<p>{{person.about}}</p>
|
||||
<p
|
||||
v-if="person.knowledge && String(person.knowledge).trim()"
|
||||
id="skill-description">{{ person.knowledge }}</p>
|
||||
<div id="skill-grid">
|
||||
<div
|
||||
v-for="skill in person.skills"
|
||||
:key="skill.name"
|
||||
class="skill-cell">{{ skill.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -68,15 +157,50 @@ import Vue from 'vue';
|
||||
import { getVueOptions } from './options';
|
||||
|
||||
const name = 'green';
|
||||
export default Vue.component(name, getVueOptions(name));
|
||||
const baseOptions = getVueOptions(name);
|
||||
export default Vue.component(name, Object.assign({}, baseOptions, {
|
||||
methods: Object.assign({}, baseOptions.methods || {}, {
|
||||
experienceLocation (exp) {
|
||||
const loc = exp && exp.location;
|
||||
return (loc !== undefined && loc !== null && String(loc).trim()) ? String(loc).trim() : '';
|
||||
},
|
||||
experienceDateRange (exp) {
|
||||
const t = exp && exp.timeperiod;
|
||||
return (t !== undefined && t !== null && String(t).trim()) ? String(t).trim() : '';
|
||||
},
|
||||
experienceDescriptionLines (exp) {
|
||||
if (!exp || exp.description === undefined || exp.description === null) return [];
|
||||
const raw = String(exp.description).trim();
|
||||
if (!raw) return [];
|
||||
let lines = raw.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
||||
if (lines.length === 1 && lines[0].indexOf('•') !== -1) {
|
||||
const parts = lines[0].split(/\s•\s/).map(p => p.trim()).filter(Boolean);
|
||||
if (parts.length > 1) {
|
||||
lines = parts.map((p) => (p.charAt(0) === '•' ? p : '• ' + p));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
},
|
||||
stripLeadingBullet (line) {
|
||||
return String(line).replace(/^[•*-]\s*/, '').trim();
|
||||
}
|
||||
})
|
||||
}));
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="less" scoped>
|
||||
@text-green: #008000;
|
||||
@pad-x: 26px;
|
||||
#template {
|
||||
box-sizing:border-box;
|
||||
font-family:'Open Sans', sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
h1, h2 {
|
||||
/*font-family:'Open Sans Condensed', sans-serif;*/
|
||||
margin:0;
|
||||
@ -104,35 +228,35 @@ export default Vue.component(name, getVueOptions(name));
|
||||
|
||||
#resume-header {
|
||||
color: white;
|
||||
height: 136px;
|
||||
background-color: green;
|
||||
box-shadow: inset 0px 0px 200px #301030;
|
||||
padding: 40px 100px 25px;
|
||||
padding: 26px @pad-x 16px;
|
||||
|
||||
#header-left {
|
||||
/*width: 465px;*/
|
||||
width:100%;
|
||||
float: left;
|
||||
h1 {
|
||||
font-size:56px;
|
||||
font-size:46px;
|
||||
color:white;
|
||||
text-transform:uppercase;
|
||||
line-height:56px;
|
||||
line-height:46px;
|
||||
}
|
||||
h2 {
|
||||
font-size:22px;
|
||||
font-size:19px;
|
||||
color:white;
|
||||
}
|
||||
#info-flex {
|
||||
display:flex;
|
||||
margin-top:20px;
|
||||
font-size:14px;
|
||||
flex-wrap: wrap;
|
||||
margin-top:12px;
|
||||
font-size:11.5px;
|
||||
gap: 6px 0;
|
||||
|
||||
span {
|
||||
margin-right:25px;
|
||||
margin-right:20px;
|
||||
}
|
||||
i {
|
||||
margin-right:5px;
|
||||
margin-right:4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,71 +279,356 @@ export default Vue.component(name, getVueOptions(name));
|
||||
}*/
|
||||
}
|
||||
|
||||
#resume-body {
|
||||
padding: 40px 100px;
|
||||
#resume-about {
|
||||
flex-shrink: 0;
|
||||
padding: 8px @pad-x 10px;
|
||||
background-color: green;
|
||||
box-shadow: inset 0px 0px 100px #301030;
|
||||
box-sizing: border-box;
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
h2, p {
|
||||
color: white;
|
||||
}
|
||||
p {
|
||||
font-size: 10.5px;
|
||||
line-height: 1.38;
|
||||
margin: 0;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.core-strengths {
|
||||
margin-top: 5px;
|
||||
font-size: 10.5px;
|
||||
line-height: 1.4;
|
||||
white-space: normal;
|
||||
opacity: 0.92;
|
||||
}
|
||||
.core-strengths-label {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
#experience-title, #education-title, #skills-title {
|
||||
font-size:26px;
|
||||
#resume-body {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px @pad-x 10px;
|
||||
|
||||
#experience-title, #education-title, #skills-title, #projects-title {
|
||||
font-size:22px;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.experience {
|
||||
margin: 10px 0 10px 50px;
|
||||
ul {
|
||||
margin: 5px 0 0 0;
|
||||
#experience-container {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
box-sizing: border-box;
|
||||
#experience-title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.company, .education-description {
|
||||
font-size:20px;
|
||||
/* Block + inline-block: reliable in PDF print pipeline (avoids flex/padding quirks) */
|
||||
.experience-legend {
|
||||
display: block;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
font-size: 9px;
|
||||
line-height: 1.45;
|
||||
color: #5a7a5a;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.experience-legend-intro {
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
color: #4a6a4a;
|
||||
margin-right: 12px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.experience-legend-item {
|
||||
display: inline-block;
|
||||
margin-right: 34px;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
i {
|
||||
display: inline-block;
|
||||
color: @text-green;
|
||||
font-size: 11px;
|
||||
width: 1.25em;
|
||||
min-width: 1.25em;
|
||||
margin-right: 7px;
|
||||
text-align: center;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.experience-legend-text {
|
||||
display: inline;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.experience {
|
||||
margin: 0 0 8px 0;
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ul {
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.company-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 10px;
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.2;
|
||||
.company-primary {
|
||||
display: inline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.company {
|
||||
display: inline;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: @text-green;
|
||||
}
|
||||
.exp-name-icon-gutter {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
min-width: 10px;
|
||||
height: 1px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.experience-icons {
|
||||
display: inline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.exp-icon-cell {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: baseline;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
i {
|
||||
color: @text-green;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
opacity: 0.92;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
.company-location {
|
||||
font-size: 10.5px;
|
||||
font-weight: 400;
|
||||
color: #5a7a5a;
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
max-width: 55%;
|
||||
}
|
||||
}
|
||||
|
||||
.education-description {
|
||||
font-size: 18px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.job-info {
|
||||
margin-bottom:5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px 10px;
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
|
||||
.job-description-list {
|
||||
margin: 0;
|
||||
padding-left: 1.15em;
|
||||
list-style-position: outside;
|
||||
list-style-type: disc;
|
||||
li {
|
||||
font-size: 10.5px;
|
||||
line-height: 1.32;
|
||||
margin: 0 0 2px 0;
|
||||
padding-left: 2px;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
li::marker {
|
||||
color: @text-green;
|
||||
}
|
||||
}
|
||||
|
||||
.job-title, .degree {
|
||||
font-weight:700;
|
||||
color: @text-green;
|
||||
font-size:16px;
|
||||
font-size: 13px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 40%;
|
||||
}
|
||||
|
||||
.experience-timeperiod, .education-timeperiod {
|
||||
font-weight:100;
|
||||
.experience-timeperiod {
|
||||
font-weight: 400;
|
||||
color: #5a7a5a;
|
||||
font-size: 10.5px;
|
||||
line-height: 1.3;
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
max-width: 48%;
|
||||
}
|
||||
|
||||
.sub-companies-line {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sub-companies-list {
|
||||
flex: 1 1 auto;
|
||||
min-width: 40%;
|
||||
}
|
||||
|
||||
.sub-company-entry {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sub-company-name {
|
||||
font-weight: 700;
|
||||
color: @text-green;
|
||||
font-size:16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.education {
|
||||
margin: 10px 0 10px 50px;
|
||||
.sub-icon-gutter {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#skill-list {
|
||||
column-count: 3;
|
||||
list-style-position: inside;
|
||||
ul li {
|
||||
font-size:14px;
|
||||
.sub-icon-cell {
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
i {
|
||||
color: @text-green;
|
||||
font-size: 11px;
|
||||
opacity: 0.92;
|
||||
}
|
||||
}
|
||||
|
||||
#education-container, #skills-container {
|
||||
margin-top: 20px;
|
||||
.sub-company-sep {
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
color: #5a7a5a;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
#resume-footer {
|
||||
padding: 20px 100px;
|
||||
height: 135px;
|
||||
background-color: green;
|
||||
box-shadow: inset 0px 0px 100px #301030;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
h2, p {
|
||||
color:white;
|
||||
|
||||
.education-timeperiod {
|
||||
font-weight:100;
|
||||
color: @text-green;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.education {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
#skills-container {
|
||||
margin-top: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
box-sizing: border-box;
|
||||
#skills-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#skill-description {
|
||||
font-size: 8.5px;
|
||||
line-height: 1.28;
|
||||
color: #444;
|
||||
margin: 0 0 4px 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
column-count: 2;
|
||||
column-gap: 12px;
|
||||
column-fill: balance;
|
||||
}
|
||||
|
||||
#skill-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 10px;
|
||||
row-gap: 3px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.skill-cell {
|
||||
font-size: 9.5px;
|
||||
line-height: 1.25;
|
||||
color: #1a1a1a;
|
||||
min-width: 0;
|
||||
padding: 3px 6px;
|
||||
background: #f6faf6;
|
||||
border-left: 3px solid @text-green;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
#projects-container {
|
||||
margin-top: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
box-sizing: border-box;
|
||||
#projects-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.project {
|
||||
margin: 0 0 6px 0;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.project-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: @text-green;
|
||||
margin: 0 0 2px 0;
|
||||
}
|
||||
|
||||
#education-container {
|
||||
margin-top: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
box-sizing: border-box;
|
||||
#education-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -227,7 +636,8 @@ export default Vue.component(name, getVueOptions(name));
|
||||
.spacer {
|
||||
width:100%;
|
||||
border-bottom:1px solid @text-green;
|
||||
margin:5px 0 10px;
|
||||
|
||||
margin: 3px 0 4px;
|
||||
padding-top: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
<td><i class="fa fa-home" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a :href="person.contact.website">{{person.contact.website}}</a></td>
|
||||
<td><a :href="contactLinks.website">{{person.contact.website}}</a></td>
|
||||
<td><i class="fa fa-globe" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right half">
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education">
|
||||
<span class="degree">{{education.degree}}</span>
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
</tr>
|
||||
<tr v-if="person.contact.website">
|
||||
<td><i class="fa fa-globe" aria-hidden="true"></i></td>
|
||||
<td><a :href="person.contact.website">{{person.contact.website}}</a></td>
|
||||
<td><a :href="contactLinks.website">{{person.contact.website}}</a></td>
|
||||
</tr>
|
||||
<tr v-if="person.contact.github">
|
||||
<td><i class="fa fa-github" aria-hidden="true"></i></td>
|
||||
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right half">
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education" :key="education.degree">
|
||||
<span class="degree">{{education.degree}}</span>
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
<td><i class="fa fa-home" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr v-if="person.contact.website">
|
||||
<td><a :href="person.contact.website">{{person.contact.website}}</a></td>
|
||||
<td><a :href="contactLinks.website">{{person.contact.website}}</a></td>
|
||||
<td><i class="fa fa-globe" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr v-if="person.contact.github">
|
||||
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right half">
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education" :key="education.degree">
|
||||
<span class="degree">{{education.degree}}</span>
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a :href="person.contact.website" target="_blank">
|
||||
<a :href="contactLinks.website" target="_blank">
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<i class="material-icons">language</i>
|
||||
@ -109,6 +109,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="person.education && person.education.length">
|
||||
<div class="section-headline">{{ lang.education }}</div>
|
||||
<div class="block" v-for="education in person.education">
|
||||
<div class="block-helper"></div>
|
||||
@ -117,6 +118,7 @@
|
||||
{{education.timeperiod}}, {{education.description}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="farRightCol">
|
||||
<div class="section-headline">{{ lang.projects }}</div>
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a v-if="person.contact.website" :href="person.contact.website" target="_blank">
|
||||
<a v-if="person.contact.website" :href="contactLinks.website" target="_blank">
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<i class="material-icons">language</i>
|
||||
@ -122,6 +122,7 @@
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
<template v-if="person.education && person.education.length">
|
||||
<div class="section-headline">{{ lang.education }}</div>
|
||||
<div class="block" v-for="education in person.education" :key="education.degree">
|
||||
<a
|
||||
@ -133,6 +134,7 @@
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div style="clear:both;"></div>
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education">
|
||||
<div class="row">
|
||||
@ -77,7 +77,7 @@
|
||||
<span>; </span>
|
||||
<span>{{person.contact.street}}, {{person.contact.city}}</span>
|
||||
<span>; </span>
|
||||
<a :href="person.contact.website">
|
||||
<a :href="contactLinks.website">
|
||||
{{person.contact.website}}</a>
|
||||
<span>; </span>
|
||||
<a :href="'https://github.com/'+person.contact.github">
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education" :key="education.degree">
|
||||
<div class="row">
|
||||
@ -61,7 +61,7 @@
|
||||
<span>; </span>
|
||||
<span>{{person.contact.street}}, {{person.contact.city}}</span>
|
||||
<span>; </span>
|
||||
<a v-if="person.contact.website" :href="person.contact.website">
|
||||
<a v-if="person.contact.website" :href="contactLinks.website">
|
||||
{{person.contact.website}}</a>
|
||||
<span v-if="person.contact.website">; </span>
|
||||
<a v-if="person.contact.github" :href="'https://github.com/'+person.contact.github">
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education" :key="education.degree">
|
||||
<div class="row">
|
||||
@ -63,7 +63,7 @@
|
||||
<span>; </span>
|
||||
<span>{{person.contact.street}}, {{person.contact.city}}</span>
|
||||
<span>; </span>
|
||||
<a v-if="person.contact.website" :href="person.contact.website">
|
||||
<a v-if="person.contact.website" :href="contactLinks.website">
|
||||
{{person.contact.website}}</a>
|
||||
<span v-if="person.contact.website">; </span>
|
||||
<a v-if="person.contact.github" :href="contactLinks.github">
|
||||
|
||||
@ -6,6 +6,11 @@ import {
|
||||
terms
|
||||
} from '../terms';
|
||||
|
||||
function startsWithHttpScheme (value) {
|
||||
const v = String(value).toLowerCase();
|
||||
return v.startsWith('http://') || v.startsWith('https://');
|
||||
}
|
||||
|
||||
// Called by templates to decrease redundancy
|
||||
function getVueOptions (name) {
|
||||
const opt = {
|
||||
@ -35,8 +40,40 @@ function getVueOptions (name) {
|
||||
contactLinks() {
|
||||
const links = {};
|
||||
|
||||
if(this.person.contact.github) {
|
||||
links.github = `https://github.com/${this.person.contact.github}`;
|
||||
if (this.person.contact.github) {
|
||||
const g = String(this.person.contact.github).trim();
|
||||
if (startsWithHttpScheme(g)) {
|
||||
links.github = g;
|
||||
} else {
|
||||
links.github = `https://github.com/${g}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.person.contact.github_profile) {
|
||||
const u = String(this.person.contact.github_profile)
|
||||
.trim()
|
||||
.replace(/^@/, '');
|
||||
if (u) {
|
||||
links.github_profile = `https://github.com/${u}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.person.contact.website) {
|
||||
const w = String(this.person.contact.website).trim();
|
||||
if (startsWithHttpScheme(w)) {
|
||||
links.website = w;
|
||||
} else {
|
||||
links.website = `https://${w}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.person.contact.personal_site) {
|
||||
const ps = String(this.person.contact.personal_site).trim();
|
||||
if (startsWithHttpScheme(ps)) {
|
||||
links.personal_site = ps;
|
||||
} else {
|
||||
links.personal_site = `https://${ps}`;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.person.contact.codefights) {
|
||||
@ -61,6 +98,100 @@ function getVueOptions (name) {
|
||||
|
||||
return links;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
experienceWorkBadges (exp) {
|
||||
const badges = [];
|
||||
if (!exp) return badges;
|
||||
const r = exp.remote;
|
||||
const rs = r !== undefined && r !== null && String(r).toLowerCase();
|
||||
if (r === true || rs === 'true') {
|
||||
badges.push({
|
||||
key: 'remote',
|
||||
icon: 'fa fa-home',
|
||||
label: 'Remote'
|
||||
});
|
||||
} else if (rs === 'hybrid') {
|
||||
badges.push({
|
||||
key: 'hybrid',
|
||||
icon: 'fa fa-exchange',
|
||||
label: 'Hybrid'
|
||||
});
|
||||
} else if (r === false || rs === 'false') {
|
||||
badges.push({
|
||||
key: 'onsite',
|
||||
icon: 'fa fa-building',
|
||||
label: 'On-site'
|
||||
});
|
||||
}
|
||||
const empRaw = exp.employment && String(exp.employment).toLowerCase();
|
||||
const empTokens = empRaw ? empRaw.split(/[,;|]+/).map(t => t.trim().replace(/[\s_-]+/g, '')) : [];
|
||||
const empSet = empTokens.length ? empTokens : [empRaw && empRaw.replace(/[\s_-]+/g, '')];
|
||||
const empMap = {
|
||||
contract: {
|
||||
key: 'contract',
|
||||
icon: 'fa fa-file-text-o',
|
||||
label: 'Contract'
|
||||
},
|
||||
fulltime: {
|
||||
key: 'fulltime',
|
||||
icon: 'fa fa-briefcase',
|
||||
label: 'Full-time'
|
||||
},
|
||||
coop: {
|
||||
key: 'coop',
|
||||
icon: 'fa fa-graduation-cap',
|
||||
label: 'Co-op'
|
||||
},
|
||||
internship: {
|
||||
key: 'coop',
|
||||
icon: 'fa fa-graduation-cap',
|
||||
label: 'Co-op'
|
||||
}
|
||||
};
|
||||
const seen = {};
|
||||
empSet.forEach(e => {
|
||||
if (e && empMap[e] && !seen[empMap[e].key]) {
|
||||
seen[empMap[e].key] = true;
|
||||
badges.push(empMap[e]);
|
||||
}
|
||||
});
|
||||
return badges;
|
||||
},
|
||||
workArrangementLegendItems () {
|
||||
return [
|
||||
{
|
||||
key: 'remote',
|
||||
icon: 'fa fa-home',
|
||||
label: 'Remote'
|
||||
},
|
||||
{
|
||||
key: 'hybrid',
|
||||
icon: 'fa fa-exchange',
|
||||
label: 'Hybrid'
|
||||
},
|
||||
{
|
||||
key: 'onsite',
|
||||
icon: 'fa fa-building',
|
||||
label: 'On-site'
|
||||
},
|
||||
{
|
||||
key: 'contract',
|
||||
icon: 'fa fa-file-text-o',
|
||||
label: 'Contract'
|
||||
},
|
||||
{
|
||||
key: 'fulltime',
|
||||
icon: 'fa fa-briefcase',
|
||||
label: 'Full-time'
|
||||
},
|
||||
{
|
||||
key: 'coop',
|
||||
icon: 'fa fa-graduation-cap',
|
||||
label: 'Co-op'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
return opt;
|
||||
|
||||
@ -8,8 +8,9 @@
|
||||
<span id="email"><a :href='"mailto:" + person.contact.email'>
|
||||
<i class="fa fa-envelope" aria-hidden="true"></i> {{person.contact.email}}</a></span>
|
||||
<span id="phone"><i class='fa fa-phone-square' aria-hidden="true"></i> {{person.contact.phone}}</span>
|
||||
<span v-if="person.contact.website" id="website"><a :href='person.contact.website'><i class="fa fa-home" aria-hidden="true"></i> {{person.contact.website}}</a></span>
|
||||
<span v-if="person.contact.github" id="github"><a :href='contactLinks.github'><i class="fa fa-github" aria-hidden="true"></i> {{person.contact.github}}</a></span>
|
||||
<span v-if="person.contact.website" id="website"><a :href="contactLinks.website"><i class="fa fa-linkedin" aria-hidden="true"></i> {{ person.contact.website_label || person.contact.website }}</a></span>
|
||||
<span v-if="person.contact.github" id="github"><a :href="contactLinks.github"><i class="fa fa-code-fork" aria-hidden="true"></i> {{ person.contact.github_label || 'Git' }}</a></span>
|
||||
<span v-if="person.contact.github_profile && contactLinks.github_profile" id="github-profile"><a :href="contactLinks.github_profile"><i class="fa fa-github" aria-hidden="true"></i> {{ person.contact.github_profile_label || ('@' + String(person.contact.github_profile).replace(/^@/, '')) }}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="header-right">
|
||||
@ -19,9 +20,43 @@
|
||||
<div id="resume-body">
|
||||
<div id="experience-container">
|
||||
<h2 id="experience-title">{{ lang.experience }}</h2>
|
||||
<p
|
||||
v-if="person.experience && person.experience.length"
|
||||
class="experience-legend"
|
||||
role="note">
|
||||
<span class="experience-legend-intro">{{ lang.experienceLegendIntro }}:</span>
|
||||
<span
|
||||
v-for="item in workArrangementLegendItems()"
|
||||
:key="item.key"
|
||||
class="experience-legend-item">
|
||||
<i :class="item.icon" aria-hidden="true"></i>
|
||||
<span class="experience-legend-text">{{ item.label }}</span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="spacer"></div>
|
||||
<div class="experience" v-for="experience in person.experience" :key="experience.company">
|
||||
<h2 class="company">{{experience.company}}</h2>
|
||||
<h2 class="company-row">
|
||||
<span class="company-primary">
|
||||
<span class="company">{{experience.company}}</span><!--
|
||||
--><span
|
||||
v-if="experienceWorkBadges(experience).length"
|
||||
class="exp-name-icon-gutter"
|
||||
aria-hidden="true"></span><!--
|
||||
--><span
|
||||
v-if="experienceWorkBadges(experience).length"
|
||||
class="experience-icons"
|
||||
aria-hidden="true">
|
||||
<span
|
||||
v-for="b in experienceWorkBadges(experience)"
|
||||
:key="b.key"
|
||||
class="exp-icon-cell">
|
||||
<i
|
||||
:class="b.icon"
|
||||
:title="b.label"></i>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</h2>
|
||||
<p class="job-info"><span class="job-title">{{experience.position}} | </span><span class="experience-timeperiod">{{experience.timeperiod}}</span></p>
|
||||
<p class="job-description" v-if="experience.description">{{experience.description}}</p>
|
||||
<ul v-if="experience.list" >
|
||||
@ -33,7 +68,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="education-container">
|
||||
<div id="education-container" v-if="person.education && person.education.length">
|
||||
<h2 id="education-title">{{ lang.education }}</h2>
|
||||
<div class="spacer"></div>
|
||||
<div class="education" v-for="education in person.education" :key="education.degree">
|
||||
@ -41,10 +76,12 @@
|
||||
<p><span class="degree">{{education.degree}} | </span><span class="education-timeperiod">{{education.timeperiod}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="skills-container" v-if="person.skills != []">
|
||||
<div id="skills-container" v-if="person.skills && person.skills.length">
|
||||
<h2 id="skills-title">{{ lang.skills }}</h2>
|
||||
<div class="spacer"></div>
|
||||
<p id="skill-description">{{person.knowledge}}</p>
|
||||
<p
|
||||
v-if="person.knowledge && String(person.knowledge).trim()"
|
||||
id="skill-description">{{ person.knowledge }}</p>
|
||||
<ul id="skill-list">
|
||||
<li class="skill" v-for="skill in person.skills" :key="skill.name">
|
||||
<span class="list-item-black">
|
||||
@ -163,19 +200,120 @@ export default Vue.component(name, getVueOptions(name));
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
.experience {
|
||||
margin: 10px 0 10px 50px;
|
||||
ul {
|
||||
margin: 5px 0 0 0;
|
||||
#experience-container {
|
||||
padding-left: 50px;
|
||||
box-sizing: border-box;
|
||||
#experience-title {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.company, .education-description {
|
||||
.experience-legend {
|
||||
display: block;
|
||||
margin: 0 0 14px 0;
|
||||
padding: 0 12px 4px 0;
|
||||
font-size: 10.5px;
|
||||
line-height: 1.65;
|
||||
color: #555;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.experience-legend-intro {
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
color: @text-purple;
|
||||
margin-right: 12px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.experience-legend-item {
|
||||
display: inline-block;
|
||||
margin-right: 34px;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
i {
|
||||
display: inline-block;
|
||||
color: @text-purple;
|
||||
font-size: 12px;
|
||||
width: 1.25em;
|
||||
min-width: 1.25em;
|
||||
margin-right: 7px;
|
||||
text-align: center;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.experience-legend-text {
|
||||
display: inline;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.experience {
|
||||
margin: 0 0 22px 0;
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
ul {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.company-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 10px 14px;
|
||||
margin: 0 0 10px 0;
|
||||
line-height: 1.28;
|
||||
font-size: 20px;
|
||||
.company-primary {
|
||||
display: inline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.company {
|
||||
display: inline;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: @text-purple;
|
||||
}
|
||||
.exp-name-icon-gutter {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
height: 1px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.experience-icons {
|
||||
display: inline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.exp-icon-cell {
|
||||
display: inline-block;
|
||||
margin-right: 22px;
|
||||
vertical-align: baseline;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
i {
|
||||
color: @text-purple;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
opacity: 0.9;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.education-description {
|
||||
font-size:20px;
|
||||
}
|
||||
|
||||
.job-info {
|
||||
margin-bottom:5px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
|
||||
@ -227,7 +365,8 @@ export default Vue.component(name, getVueOptions(name));
|
||||
.spacer {
|
||||
width:100%;
|
||||
border-bottom:1px solid @text-purple;
|
||||
margin:5px 0 10px;
|
||||
|
||||
margin: 10px 0 10px;
|
||||
padding-top: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education">
|
||||
<div class="row">
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education">
|
||||
<div class="row">
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="education">
|
||||
<div v-if="person.education && person.education.length" class="education">
|
||||
<h3>{{ lang.education }}</h3>
|
||||
<div class="education-block" v-for="education in person.education" :key="education.degree">
|
||||
<div class="row">
|
||||
|
||||
BIN
static/green.pdf
Normal file
BIN
static/green.pdf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user