#let default_font = "Raleway" #let icon_font = "Font Awesome 7 Free Solid" #let icon_size = 10pt /* Building blocks */ /********************************************************************/ /* Font awesome icon */ #let fa(txt, size: 7pt) = text( font: icon_font, size: size, txt, ) /* Underlined link or content if no url provided */ #let smartlink(url, content) = if url != none { link(url, underline(offset: 1.5pt, content)) } else { content } /* Inverted box */ #let inv_box(content, size: 10pt, bold: true, capital: true) = box( fill: black, inset: 3pt, text( fill: white, weight: if bold { "bold" } else { "regular" }, size: size, if capital { upper(content) } else { content }, ), ) /* Date subtext in experience */ #let date_tag(tag) = if tag != "" { v(-0.2em) text(size: 8pt)[#raw(tag)] } /* Document parts */ /********************************************************************/ /* Active jobs under author name */ #let active_job(title, location) = stack( dir: ttb, spacing: 0.5em, strong(title), location, ) /* Icons in top right */ #let iconlink(icon, label) = grid( columns: (auto, auto), column-gutter: 0.5em, align: (horizon, horizon), /* Icon */ square( size: icon_size * 1.8, fill: black, align( horizon + center, move( dy: -icon_size / 10, text(white, fa(icon, size: icon_size)), ), ), ), /* Text */ text(size: 9pt, label) ) /* Labeled percent bar */ #let skillbar(title, percent) = ( // Use array so alignment can be determined in parrent inv_box(title, size: 9pt, bold: false, capital: false), line(length: percent, stroke: 10pt), ) /* Labeled percent circles */ #let skillbubble(title, percent) = ( circle(fill: black, radius: percent * 2.5em), text(title), ) /* Experience entry */ #let experience(date, title, location, description, skills: (), tag: "") = block( breakable: false, grid( columns: (8em, auto), rows: (auto, auto), row-gutter: 0.7em, column-gutter: 1em, date, stack( dir: ltr, spacing: 1fr, strong(title), text(size: 8pt, weight: "bold", location), ), date_tag(tag), // par(justify: true, description), none, skills .map(raw) .join({ h(1em) raw("/") h(1em) }), ), ) /* Extra titled paragraphs */ #let info(title, content) = { inv_box(title, size: 8pt) set par(justify: true) set text(size: 8pt) content } /* Work field containing experiences, education, etc. */ #let field(name, bubbles: (), experiences: (), education: (), other: ()) = { /* Title */ heading(name) line(length: 100%, stroke: 0.05em) /* Bubbles */ if bubbles.len() > 0 { set align(center) box( grid( columns: bubbles.len(), rows: 2, row-gutter: 2em, column-gutter: 2em, align: (horizon), ..bubbles.map(pair => pair.at(0)).flatten(), ..bubbles.map(pair => pair.at(1)).flatten(), ), ) } /* Experinces */ if experiences.len() > 0 { inv_box("Experience") } stack(dir: ttb, spacing: 1.0em, ..experiences) /* Education */ if education.len() > 0 { inv_box("Education") } stack(dir: ttb, spacing: 1.0em, ..education) /* Other */ grid( columns: (auto, auto, auto), gutter: 3em, ..other, ) } /* Main document template */ #let CV( author: "Janez Novak", active_jobs: (), links: (), about: lorem(150), skills: (), fields: (), ) = { set document( title: "CV", author: author, description: author + "'s CV", ) set page( paper: "a4", margin: (top: 2.0cm, bottom: 3.0cm, left: 1.75cm, right: 1.75cm), ) set underline(stroke: (dash: "dotted")) set text(size: 9pt, spacing: 1.3pt, font: default_font) /* Content */ grid( columns: (1fr, 1fr), rows: (auto, auto), gutter: 2em, /* Name, jobs */ stack( dir: ttb, author.split(" ").map(a => { inv_box(a, size: 30pt) }).join(v(-1em)), v(0.7em), stack(spacing: 0.7em, ..active_jobs), ), /* Icon links */ box( height: 12%, columns( 2, gutter: 1em, links .map(l => { l v(-1em) }) .join(), ), ), /* About */ box[ #inv_box("About me") #par(justify: true)[ #context about ] ], /* Skill bar */ grid.cell( align: left + horizon, grid( columns: (1fr, 3fr), column-gutter: 1em, row-gutter: 0.2em, align: (right, horizon, left, horizon), ..skills.flatten(), ), ), ) set underline(stroke: (dash: "solid")) fields.join(pagebreak()) }