diff --git a/Makefile b/Makefile index de980ec..365449f 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,9 @@ resume: resume.typ typst compile --input lang=en resume.typ build/resume_en.pdf typst compile --input lang=de resume.typ build/resume_de.pdf -letter: letter.qmd templates/letter.latex - poetry run quarto render letter.qmd +letter: letter.typ + typst compile --input lang=en letter.typ build/letter_en.pdf + typst compile --input lang=de letter.typ build/letter_de.pdf clean: rm -f *CV.aux *CV.bcf *CV.log *CV.out *CV.run.xml *CV.pdf short_CV.tex long_CV.tex *CV.bbl *CV.blg *yaml_CV.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae62ba2 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Data-driven CV & resume + +My personal CV and resume files, automatically generated from a multi-lingual +yaml data file. + +The CV contains a full run-down of my educational and job experience to date, +while the resume is more compact, reduced to a single page +and can be tailored for a specific job area or expertise. + +Is called like the following: + +```typst +#resume( + yaml("content.yml"), + main: ("experience", "education", "volunteering", "skills", "languages"), + sidebar: ("volunteering", "skills") +) +``` + +This is the default invocation, though sidebar and main body sections can be exchanged at will. + +TODO: + +- [x] move double-resume sources (per langauge) to a variable or similar +- [x] separate volunteering from skills section +- [_] one function per skill section +- [_] resume prep: + - [ ] make experience groupable by client / short version + - [ ] enable display/hiding of sections/entries by tags? + +- [x] migrate to typst template? +- [x] ! Fix german summary to be like English summary +- [_] generalized entry? + Would have 'title', 'place', 'date', 'type'/'tags' + potential + 'publication', 'bullets' + +## Typst-driven branch + +- [x] Fix publication '\&'s +- [x] Fix en-dash/em-dash (e.g. in years) +- [x] Producable from yaml content +- [x] Can be switched between Ger/En +- [x] Try sidebar version + - [ ] Generalize sidebar version through abstraction/extractions +- [ ] unify items: experience/education/(thesis?) diff --git a/content.yml b/content.yml index c862e1a..93b6526 100644 --- a/content.yml +++ b/content.yml @@ -26,12 +26,24 @@ summary: en: | Since completing the EU-funded European Master of Global Studies Erasmus Mundus programme in 2021, I have acted as a research consultant focused on processes of inequality and poverty reduction, spatialization and collective organization, primarily through the lens of labour market policies. - Through producing the development research literature I have developed a broad range of skills in data acquisition, organization and visualization, as well as manuscript editing and reference management. Additionally, I have gained insights into event management, teaching assistance, content creation, system administration and website management. Beyond professional work I foster the development of free and open software and open science without barriers. + Through producing the development research literature I have developed a broad range of skills in data acquisition, organization and visualization, as well as manuscript editing and reference management. Additionally, I have gained insights into event management, teaching assistance, content creation, system and web administration. Beyond professional work I strive to foster the development of free and open software and open science without barriers. I welcome opportunities to deepen my expertise in these topics, in addition to those expanding my range of applicable skills. +experience_types: + 1: + de: Selbstständiger Schriftsteller Forschung + en: Independent research consultant + 2: + de: Honorararbeit + en: Salaried work + 3: + de: Gewerbeschein + en: Trade license + experience: - - date: + - typeid: 1 + date: de: 2024 en: 2024 title: @@ -53,9 +65,10 @@ experience: - date: de: 2023--2024 en: 2023--2024 + typeid: 1 title: de: Externer Forscher, Ungleichheiten auf dem Arbeitsmarkt - en: External researcher, Inequalities on the Labour market + en: External researcher, Inequalities on the labour market place: de: ILO en: ILO @@ -72,6 +85,7 @@ experience: - date: de: 2023 en: 2023 + typeid: 1 title: de: Consultant, Forschungsarbeit für nachhaltige Beschaffung in internationaler Logistik en: Consultant, Research on sustainable procurement in international logistics @@ -88,6 +102,7 @@ experience: - date: de: 2022 en: 2022 + typeid: 1 title: de: Consultant, Datenbankforschung internationale Hilfsgelder en: Consultant, Database research international aid funds @@ -104,6 +119,7 @@ experience: - date: de: 2022 en: 2022 + typeid: 1 title: de: Redaktionsarbeit, Soziale Absicherung und Widerstandsfähigkeit en: Editorial work, Social Protection and Resilience, Roskilde University @@ -123,6 +139,7 @@ experience: - date: de: 2022 en: 2022 + typeid: 1 title: de: Forschungsassistenz, Entwicklungsprojekte zur Reduzierung Ungleichheitstrends en: Research Assistant, Development projects to reduce inequality trends @@ -143,6 +160,7 @@ experience: - date: de: 2022 en: 2022 + typeid: 1 title: de: Consultant, Review Verknüpfung sozialer Schutz, Produktivität und Formalisierung en: Consultant, Social Protection, Productivity and Formalization Nexus Review @@ -162,6 +180,7 @@ experience: - date: de: 2022 en: 2022 + typeid: 1 title: de: Consultant, Review Arbeitsmarktpolitiken in Asien und dem Pazifik en: Consultant, Labour Market Policies Review in Asia and the Pacific @@ -179,6 +198,7 @@ experience: - date: de: 2021 en: 2021 + typeid: 1 title: de: Forschungsassistenz, Informelle Organisierung und Absicherung en: Research Assistant, Informal Organization and Social Security @@ -198,6 +218,7 @@ experience: - date: de: 2021 en: 2021 + typeid: 1 title: de: Redakteur, Soziale Absicherung informeller Arbeiter (SPIWORK) en: Editorial Assistant, Social Protection of Informal Workers (SPIWORK) @@ -217,6 +238,7 @@ experience: - date: de: 2018--2019 en: 2018--2019 + typeid: 2 title: de: Akademische Hilfskraft, Institut für Amerikastudien en: Academic Assistant, Institute of American Studies @@ -233,6 +255,7 @@ experience: - date: de: 2017--2019 en: 2017--2019 + typeid: 2 title: de: Studentische Hilfskraft, Professor Crister S. Garrett en: Student Assistant, Professor Crister S. Garrett @@ -249,6 +272,7 @@ experience: - date: de: 2018 en: 2018 + typeid: 2 title: de: Lehrassistenz, Transatlantische Sommerschule Cultures of Security en: Teaching Assistant, Trans Atlantic Summer School Cultures of Security @@ -265,6 +289,7 @@ experience: - date: de: 2017--2018 en: 2017--2018 + typeid: 2 title: de: Content-Management, Bachelor Plus/Alumni-System en: Content Management, Bachelor Plus/Alumni System @@ -281,6 +306,8 @@ experience: - date: de: 2014--2018 en: 2014--2018 + typeid: 2 + hidden: true # TODO: Allow hiding entries like this? title: de: Verkaufsassistent und Eventhelfer für historische Märkte en: Sales Assistant and Event Support for Historical Markets @@ -325,10 +352,10 @@ education: en: 2018 - place: de: HTWK Leipzig, Deutschland - en: HTWK Leipzig, Deutschland + en: HTWK Leipzig, Germany title: - de: Medieninformatik, BSc (nicht abgeschlossen) - en: Media Computer Science, BSc (not completed) + de: Medieninformatik, BSc (nicht abg.) + en: Media Computer Science, BSc (not compl.) date: de: 2015 en: 2015 diff --git a/cv.typ b/cv.typ index 89bb34c..518cd71 100644 --- a/cv.typ +++ b/cv.typ @@ -11,266 +11,290 @@ // $ typst compile --input lang=de cv.typ // #let lang = { - if "lang" in sys.inputs and sys.inputs.lang == "de" { - "de" - } else { - "en" - } + if "lang" in sys.inputs and sys.inputs.lang == "de" { + "de" + } else { + "en" + } } -#let sel_word_lang(de: "", en:"") = { - if lang == "de" { - de - } else { - en - } +#let sel_word_lang(de: "", en: "") = { + if lang == "de" { + de + } else { + en + } } #let _columns_3(left_body, center_body, right_body) = { - block[ - #box(width: 1fr)[ - #align(left)[#left_body] - ] - #box(width: 1fr)[ - #align(center)[#center_body] - ] - #box(width: 1fr)[ - #align(right)[#right_body] - ] + block[ + #box(width: 1fr)[ + #align(left)[#left_body] ] + #box(width: 1fr)[ + #align(center)[#center_body] + ] + #box(width: 1fr)[ + #align(right)[#right_body] + ] + ] } #let header(about, columns: (1.5fr, 1fr, 1fr)) = { - [= #about.fullname]; - let contact_fields = (for c in about.contact { - if "link" in c { - ([#c.icon ~ #link(c.link)[#c.text]],) - } else { - ([#c.icon ~ #c.text],) - } - }) - grid( - columns: columns, - gutter: 5pt, - ..contact_fields - ); + [= #about.fullname] + let contact_fields = ( + for c in about.contact { + if "link" in c { + ([#c.icon ~ #link(c.link)[#c.text]],) + } else { + ([#c.icon ~ #c.text],) + } + } + ) + grid( + columns: columns, + gutter: 5pt, + ..contact_fields + ) } #let subdued(body) = { - block(inset: 5%, width: 85%, text(fill:luma(150), body)) + block(inset: 5%, width: 85%, text(fill: luma(150), body)) } #let entry(item: ()) = { + if "title" in item { + [*#item.title.at(lang)*] + } + if "place" in item { if "title" in item { - [*#item.title.at(lang)*] + [, ] } - if "place" in item { - if "title" in item { - [, ] - } - [_#item.place.at(lang)_] - } - [#h(1fr)] - if "date" in item { - [ _#item.date.at(lang)_ \ ] - } - if "bullets" in item { - for bullet in item.bullets { - [- #bullet.at(lang)] - } - } - if "publication" in item { - subdued[#item.publication.at(lang) \ ] - }; - if "abstract" in item { - subdued[#item.abstract.at(lang) \ ] + [_#item.place.at(lang)_] + } + [#h(1fr)] + if "date" in item { + [ _#item.date.at(lang)_ \ ] + } + if "bullets" in item { + for bullet in item.bullets { + [- #bullet.at(lang)] } + } + if "publication" in item { + subdued[#item.publication.at(lang) \ ] + } + if "abstract" in item { + subdued[#item.abstract.at(lang) \ ] + } } -#let horizon_line() = {v(-3pt); line(length: 100%); v(-5pt)} +#let horizon_line() = { + v(-3pt) + line(length: 100%) + v(-5pt) +} -#let section_header(title) = {[== #title]; horizon_line()}; +#let section_header(title) = { + [== #title] + horizon_line() +}; #let section(title: "Section", entries: (), body) = { - section_header(title); - if body == none or body == [] { - for e in entries { - entry(item:e) - } - } else { - body + section_header(title) + if body == none or body == [] { + for e in entries { + entry(item: e) } + } else { + body + } }; // Slightly re-styled entry with PLACE first and TITLE second #let education_entry(item: ()) = { - assert("place" in item and "title" in item and "date" in item, message: "Education items require place, program and date."); - [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)]; - [ _#item.date.at(lang)_ \ ]; + assert( + "place" in item and "title" in item and "date" in item, + message: "Education items require place, program and date.", + ) + [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)] + [ _#item.date.at(lang)_ \ ] } // Restyled entry with PLACE not emphasized like usual, and no date but an abstract #let thesis_entry(item: ()) = { - assert("title" in item and "place" in item, message: "Thesis items require type and title."); - [*#item.title.at(lang)* #item.place.at(lang) #h(1fr)]; - [#par(item.abstract.at(lang))] + assert("title" in item and "place" in item, message: "Thesis items require type and title.") + [*#item.title.at(lang)* #item.place.at(lang) #h(1fr)] + [#par(item.abstract.at(lang))] } // skill-specific entry, changing its style for sidebar #let skill_item(item: (), is_sidebar: false) = { - let side_list(body) = if is_sidebar {list(body)} else {par(body)} - for skill in item { - side_list({ - [*#skill.name.at(lang)*] - if is_sidebar [\ ] else [ (] - for (i,v) in skill.items.enumerate() { - [#v.at(lang)] - if i < skill.items.len() - 1 { - [, ] - } - } - if not is_sidebar [)] - }) - } + let side_list(body) = if is_sidebar { list(body) } else { par(body) } + for skill in item { + side_list({ + [*#skill.name.at(lang)*] + if is_sidebar [\ ] else [ (] + for (i, v) in skill.items.enumerate() { + [#v.at(lang)] + if i < skill.items.len() - 1 { + [, ] + } + } + if not is_sidebar [)] + }) + } } #let cv(contents, use_sidebar: false) = { - set text(lang: lang) + set text(lang: lang) - let date_formatting = { - if lang == "de" { - "[day]. [month repr:long] [year]" - } else { - "[month repr:long] [day], [year]" - } + let date_formatting = { + if lang == "de" { + "[day]. [month repr:long] [year]" + } else { + "[month repr:long] [day], [year]" } - set page( - paper: "a4", - margin: (x: 0.9cm, y: 1.3cm), - footer: [ - #set text( - fill: luma(200), - size: 8pt, - ) - #_columns_3[ - #smallcaps[#datetime.today().display(date_formatting)] - ][ - #smallcaps[#contents.about.fullname] - ][ - #counter(page).display() - ] - ], - ) + } + set page( + paper: "a4", + margin: (x: 0.9cm, y: 1.3cm), + footer: [ + #set text( + fill: luma(200), + size: 8pt, + ) + #_columns_3[ + #smallcaps[#datetime.today().display(date_formatting)] + ][ + #smallcaps[#contents.about.fullname] + ][ + #context counter(page).display() + ] + ], + ) - set par(justify: true) + set par(justify: true) - header(contents.about) + header(contents.about) - let body = { - if "summary" in contents { - section(title:"", { - contents.summary.at(lang) - }) - }; - - if "experience" in contents { - let title = sel_word_lang(en:"Professional Experience", de:"Berufserfahrung") - section(title: title, entries:contents.experience)[] - } - - if "education" in contents { - let title = sel_word_lang(en:"Education", de:"Ausbildung") - section(title: title, entries:contents.thesis + contents.education)[] - } - - if not use_sidebar { - if "volunteering" in contents { - let title = sel_word_lang(en:"Volunteer Work", de:"Ehrenamt") - section(title: title, entries:contents.volunteering)[] - } - - if "skills" in contents { - let title = sel_word_lang(en:"Qualifications", de:"Qualifikationen") - section(title: title, { - skill_item(item:contents.skills) - }) - } - - if "languages" in contents { - let title = sel_word_lang(en:"Languages", de:"Sprachen") - section(title: title, { - skill_item(item:contents.languages) - }) - } - } + let body = { + if "summary" in contents { + section( + title: "", + { + contents.summary.at(lang) + }, + ) } - let sidebar = { - if "volunteering" in contents { - let title = sel_word_lang(en:"Volunteer Work", de:"Ehrenamt") - [== #title] - for e in contents.volunteering { - [ - - *#e.title.at(lang)* (#e.date.at(lang)) - #par(e.bullets.at(0).at(lang)) \ - ] - } - } + if "experience" in contents { + let title = sel_word_lang(en: "Professional Experience", de: "Berufserfahrung") + section(title: title, entries: contents.experience)[] + } - if "languages" in contents { - let title = sel_word_lang(en:"Languages", de:"Sprachen") - [== #title] - skill_item(item:contents.languages, is_sidebar: true) - [\ ] - } - - if "skills" in contents { - let title = sel_word_lang(en:"Qualifications", de:"Kenntnisse") - [== #title] - skill_item(item:contents.skills, is_sidebar: true) - } + if "education" in contents { + let title = sel_word_lang(en: "Education", de: "Ausbildung") + section(title: title, entries: contents.thesis + contents.education)[] } if not use_sidebar { - body - return - } - let margin = 1pt - grid( - columns: (2fr, 1fr), - block(outset: 0pt, inset: (top: 0.4 * margin, right: 0pt, rest: margin), stroke: none, width: 100%, { - set block(above: 10pt) - show heading.where(level: 1): it => style(s => { - let h = text(size: 18pt, upper(it)) - let dim = measure(h, s) - stack( - dir: ltr, - h, - place( - dy: 7pt, - dx: 10pt, - horizon + left, - line(stroke: accent-color, length: 100% - dim.width - 10pt) - ), - ) - }) - body - }), - { - v(20pt) - set block(inset: (left: 20 * margin, right: 20 * margin)) - show heading: it => align(right, upper(it)) - set list(marker: "") - show list: it => { - set par(justify: false) - align(right, block(it)) - } - sidebar - } - ) + if "volunteering" in contents { + let title = sel_word_lang(en: "Volunteer Work", de: "Ehrenamt") + section(title: title, entries: contents.volunteering)[] + } + if "skills" in contents { + let title = sel_word_lang(en: "Qualifications", de: "Qualifikationen") + section( + title: title, + { + skill_item(item: contents.skills) + }, + ) + } + + if "languages" in contents { + let title = sel_word_lang(en: "Languages", de: "Sprachen") + section( + title: title, + { + skill_item(item: contents.languages) + }, + ) + } + } + } + + let sidebar = { + if "volunteering" in contents { + let title = sel_word_lang(en: "Volunteer Work", de: "Ehrenamt") + [== #title] + for e in contents.volunteering { + [ + - *#e.title.at(lang)* (#e.date.at(lang)) + #par(e.bullets.at(0).at(lang)) \ + ] + } + } + + if "languages" in contents { + let title = sel_word_lang(en: "Languages", de: "Sprachen") + [== #title] + skill_item(item: contents.languages, is_sidebar: true) + [\ ] + } + + if "skills" in contents { + let title = sel_word_lang(en: "Qualifications", de: "Kenntnisse") + [== #title] + skill_item(item: contents.skills, is_sidebar: true) + } + } + + if not use_sidebar { + body + return + } + let margin = 1pt + grid( + columns: (2fr, 1fr), + block( + outset: 0pt, + inset: (top: 0.4 * margin, right: 0pt, rest: margin), + stroke: none, + width: 100%, + { + set block(above: 10pt) + show heading.where(level: 1): it => style(s => { + let h = text(size: 18pt, upper(it)) + let dim = measure(h, s) + stack( + dir: ltr, + h, + place( + dy: 7pt, + dx: 10pt, + horizon + left, + line(stroke: accent-color, length: 100% - dim.width - 10pt), + ), + ) + }) + body + }, + ), + { + v(20pt) + set block(inset: (left: 20 * margin, right: 20 * margin)) + show heading: it => align(right, upper(it)) + set list(marker: "") + show list: it => { + set par(justify: false) + align(right, block(it)) + } + sidebar + }, + ) } -#cv.with(use_sidebar: false)( - yaml("content.yml") -) +#cv.with(use_sidebar: false)(yaml("content.yml")) diff --git a/resume.typ b/resume.typ index 6babff7..6176dc6 100644 --- a/resume.typ +++ b/resume.typ @@ -11,265 +11,302 @@ // $ typst compile --input lang=de cv.typ // #let lang = { - if "lang" in sys.inputs and sys.inputs.lang == "de" { - "de" - } else { - "en" - } -} -#let sel_word_lang(de: "", en:"") = { - if lang == "de" { - de - } else { - en - } + if "lang" in sys.inputs and sys.inputs.lang == "de" { + "de" + } else { + "en" + } } #let _columns_3(left_body, center_body, right_body) = { - block[ - #box(width: 1fr)[ - #align(left)[#left_body] - ] - #box(width: 1fr)[ - #align(center)[#center_body] - ] - #box(width: 1fr)[ - #align(right)[#right_body] - ] + block[ + #box(width: 1fr)[ + #align(left)[#left_body] ] + #box(width: 1fr)[ + #align(center)[#center_body] + ] + #box(width: 1fr)[ + #align(right)[#right_body] + ] + ] } #let header(about, columns: (1.5fr, 1fr, 1fr)) = { - [= #about.fullname]; - let contact_fields = (for c in about.contact { - if "link" in c { - ([#c.icon ~ #link(c.link)[#c.text]],) - } else { - ([#c.icon ~ #c.text],) - } - }) - grid( - columns: columns, - gutter: 5pt, - ..contact_fields - ); + [= #about.fullname] + let contact_fields = ( + for c in about.contact { + if "link" in c { + ([#c.icon ~ #link(c.link)[#c.text]],) + } else { + ([#c.icon ~ #c.text],) + } + } + ) + grid( + columns: columns, + gutter: 5pt, + ..contact_fields + ) } #let subdued(body) = { - block(inset: 5%, width: 85%, text(fill:luma(150), body)) + block(inset: 5%, width: 85%, text(fill: luma(150), body)) } -#let freelance_by_client(experience:()) = { - let by_client = (:) - for item in experience { - let client = item.place.at(lang) - if client not in by_client { - by_client.insert(client, ()) - } - by_client.at(client).push((item.title.at(lang), item.date.at(lang) )) +#let by_client(experience: ()) = { + let by_client = (:) + + for item in experience { + let client = item.place.at(lang) + if client not in by_client { + by_client.insert(client, ()) + } + by_client.at(client).push((item.title.at(lang), item.date.at(lang))) + } + + for (client, jobs) in by_client { + [*#client*:] + for j in jobs { + [- #j.at(0) #h(1fr) #j.at(1)] + } + } +} + +#let by_experience_type(type: (), experience: ()) = { + let by_ty = (:) + for (id, desc) in type { + let matching_exp_items = experience.filter(item => int(item.typeid) == int(id)) + if matching_exp_items.len() == 0 { + return } - for (client, jobs) in by_client { - [*#client*:] - for j in jobs { - [- #j.at(0) #h(1fr) #j.at(1)] - } - } + [=== _#desc.at(lang)_] + by_client(experience: matching_exp_items) + } } #let entry(item: ()) = { + if "title" in item { + [*#item.title.at(lang)*] + } + if "place" in item { if "title" in item { - [*#item.title.at(lang)*] + [, ] } - if "place" in item { - if "title" in item { - [, ] - } - [_#item.place.at(lang)_] - } - [#h(1fr)] - if "date" in item { - [ _#item.date.at(lang)_ \ ] - } - if "bullets" in item { - for bullet in item.bullets { - [- #bullet.at(lang)] - } - } - if "publication" in item { - subdued[#item.publication.at(lang) \ ] - }; - if "abstract" in item { - subdued[#item.abstract.at(lang) \ ] + [_#item.place.at(lang)_] + } + [#h(1fr)] + if "date" in item { + [ _#item.date.at(lang)_ \ ] + } + if "bullets" in item { + for bullet in item.bullets { + [- #bullet.at(lang)] } + } + if "publication" in item { + subdued[#item.publication.at(lang) \ ] + } + if "abstract" in item { + subdued[#item.abstract.at(lang) \ ] + } } -#let horizon_line() = {v(-3pt); line(length: 100%); v(-5pt)} +#let horizon_line() = { + v(-3pt) + line(length: 100%) + v(-5pt) +} -#let section_header(title) = {[== #title]; horizon_line()}; +#let section_header(title) = { + [== #title] + horizon_line() +}; #let section(title: "Section", entries: (), body) = { - section_header(title); - if body == none or body == [] { - for e in entries { - entry(item:e) - } - } else { - body + section_header(title) + if body == none or body == [] { + for e in entries { + entry(item: e) } + } else { + body + } }; // Slightly re-styled entry with PLACE first and TITLE second #let education_entry(item: ()) = { - assert("place" in item and "title" in item and "date" in item, message: "Education items require place, program and date."); - [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)]; - [ _#item.date.at(lang)_ \ ]; + assert( + "place" in item and "title" in item and "date" in item, + message: "Education items require place, program and date.", + ) + [*#item.place.at(lang)*, #item.title.at(lang) #h(1fr)] + [ _#item.date.at(lang)_ \ ] } // Restyled entry with PLACE not emphasized like usual, and no date but an abstract #let thesis_entry(item: ()) = { - assert("title" in item and "place" in item, message: "Thesis items require type and title."); - [*#item.title.at(lang)* #item.place.at(lang) #h(1fr)]; - [#par(item.abstract.at(lang))] + assert("title" in item and "place" in item, message: "Thesis items require type and title.") + [*#item.title.at(lang)* #item.place.at(lang) #h(1fr)] + [#par(item.abstract.at(lang))] } // skill-specific entry, changing its style for sidebar #let skill_item(item: (), is_sidebar: false) = { - let side_list(body) = if is_sidebar {list(body)} else {par(body)} - for skill in item { - side_list({ - [*#skill.name.at(lang)*] - if is_sidebar [\ ] else [ (] - for (i,v) in skill.items.enumerate() { - [#v.at(lang)] - if i < skill.items.len() - 1 { - [, ] - } - } - if not is_sidebar [)] - }) - } + let side_list(body) = if is_sidebar { list(body) } else { par(body) } + for skill in item { + side_list({ + [*#skill.name.at(lang)*] + if is_sidebar [\ ] else [ (] + for (i, v) in skill.items.enumerate() { + [#v.at(lang)] + if i < skill.items.len() - 1 { + [, ] + } + } + if not is_sidebar [)] + }) + } } -#let resume(contents) = { - set text(lang: lang) +#let resume(contents, main: ("experience", "education"), sidebar: ("volunteering", "languages", "skills")) = { + set text(lang: lang) - let date_formatting = { - if lang == "de" { - "[day]. [month repr:long] [year]" - } else { - "[month repr:long] [day], [year]" - } + let date_formatting = { + if lang == "de" { + "[day]. [month repr:long] [year]" + } else { + "[month repr:long] [day], [year]" } - set page( - paper: "a4", - margin: (x: 0.9cm, y: 1.3cm), - footer: [ - #set text( - fill: luma(200), - size: 8pt, - ) - #_columns_3[ - #smallcaps[#datetime.today().display(date_formatting)] - ][ - #smallcaps[#contents.about.fullname] - ][ - #counter(page).display() - ] - ], - ) + } + set page( + paper: "a4", + margin: (x: 0.9cm, y: 1.3cm), + footer: [ + #set text( + fill: luma(200), + size: 8pt, + ) + #_columns_3[ + #smallcaps[#datetime.today().display(date_formatting)] + ][ + #smallcaps[#contents.about.fullname] + ][ + #context counter(page).display() + ] + ], + ) - set par(justify: true) + set par(justify: true) - header(contents.about) + header(contents.about) - let body = { -// if "summary" in contents { -// section(title:"", { -// contents.summary.at(lang) -// }) -// }; - - if "experience" in contents { - let title = sel_word_lang(en:"Professional Experience", de:"Berufserfahrung") - section(title: title)[] - freelance_by_client(experience:contents.experience) - - } - - if "education" in contents { - let title = sel_word_lang(en:"Education", de:"Ausbildung") - section(title: title, entries:contents.thesis + contents.education)[] - } - -// if "volunteering" in contents { -// let title = sel_word_lang(en:"Volunteer Work", de:"Ehrenamt") -// section(title: title, entries:contents.volunteering)[] -// } -// -// if "skills" in contents { -// let title = sel_word_lang(en:"Qualifications", de:"Qualifikationen") -// section(title: title, { -// skill_item(item:contents.skills) -// }) -// } -// -// if "languages" in contents { -// let title = sel_word_lang(en:"Languages", de:"Sprachen") -// section(title: title, { -// skill_item(item:contents.languages) -// }) -// } + let body = { + if "summary" in main and "summary" in contents { + section( + title: "", + { + contents.summary.at(lang) + }, + ) } - let sidebar = { - if "volunteering" in contents { - let title = sel_word_lang(en:"Volunteer Work", de:"Ehrenamt") - [== #title] - for e in contents.volunteering { - [ - - *#e.title.at(lang)* (#e.date.at(lang)) - #par(e.bullets.at(0).at(lang)) \ - ] - } - } - - if "languages" in contents { - let title = sel_word_lang(en:"Languages", de:"Sprachen") - [== #title] - skill_item(item:contents.languages, is_sidebar: true) - [\ ] - } - - if "skills" in contents { - let title = sel_word_lang(en:"Qualifications", de:"Kenntnisse") - [== #title] - skill_item(item:contents.skills, is_sidebar: true) - } + if "experience" in main and "experience" in contents { + let title = (en: "Professional Experience", de: "Berufserfahrung").at(lang) + section(title: title)[] + by_experience_type(experience: contents.experience, type: contents.experience_types) } - let margin = 1pt - grid( - columns: (2fr, 1fr), - block(outset: 0pt, inset: (top: 0.4 * margin, right: 0pt, rest: margin), stroke: none, width: 100%, { - set block(above: 10pt) - show heading.where(level: 1): it => style(s => { - let h = text(size: 18pt, upper(it)) - let dim = measure(h, s) - stack( - dir: ltr, - h, - place( - dy: 7pt, - dx: 10pt, - horizon + left, - line(stroke: accent-color, length: 100% - dim.width - 10pt) - ), - ) - }) - body - }), - align(right, block(fill: luma(250), width: 90%, + if "education" in main and "education" in contents { + let title = (en: "Education", de: "Ausbildung").at(lang) + section(title: title, entries: contents.thesis + contents.education)[] + } + + if "volunteering" in main and "volunteering" in contents { + let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang) + section(title: title, entries: contents.volunteering)[] + } + + if "skills" in main and "skills" in contents { + let title = (en: "Qualifications", de: "Qualifikationen").at(lang) + section( + title: title, + { + skill_item(item: contents.skills) + }, + ) + } + + if "languages" in main and "languages" in contents { + let title = (en: "Languages", de: "Sprachen").at(lang) + section( + title: title, + { + skill_item(item: contents.languages) + }, + ) + } + } + + let sidebar = { + if "volunteering" in sidebar and "volunteering" in contents { + let title = (en: "Volunteer Work", de: "Ehrenamt").at(lang) + [== #title] + for e in contents.at("volunteering") { + [ + - *#e.title.at(lang)* (#e.date.at(lang)) + #par(e.bullets.at(0).at(lang)) \ + ] + } + } + + if "languages" in sidebar and "languages" in contents { + let title = (en: "Languages", de: "Sprachen").at(lang) + [== #title] + skill_item(item: contents.languages, is_sidebar: true) + [\ ] + } + + if "skills" in sidebar and "skills" in contents { + let title = (en: "Qualifications", de: "Kenntnisse").at(lang) + [== #title] + skill_item(item: contents.skills, is_sidebar: true) + } + } + + let margin = 1pt + grid( + columns: (2fr, 1fr), + block( + outset: 0pt, + inset: (top: 0.4 * margin, right: 0pt, rest: margin), + stroke: none, + width: 100%, + { + set block(above: 10pt) + show heading.where(level: 1): it => style(s => { + let h = text(size: 18pt, upper(it)) + let dim = measure(h, s) + stack( + dir: ltr, + h, + place( + dy: 7pt, + dx: 10pt, + horizon + left, + line(stroke: accent-color, length: 100% - dim.width - 10pt), + ), + ) + }) + body + }, + ), + align( + right, + block( + fill: luma(250), + width: 90%, { v(15pt) set block(inset: (left: 20 * margin, right: 20 * margin)) @@ -279,14 +316,12 @@ set par(justify: false) align(right, block(it)) } - sidebar + sidebar v(15pt) - })) - ) - + }, + ), + ), + ) } -#resume( - yaml("content.yml") -) - +#resume(yaml("content.yml"))