/* * Twenty Seconds Curriculum Vitae in Typst * Author: Xiaoliang Wang * Date: 2023-08-04 * License: MIT (see included file LICENSE) */ #import "@preview/fontawesome:0.2.1": * #let headercolor = gray #let pblue = rgb("#0395DE") #let gray80 = rgb("#333333") // \color{black!80} #let sidecolor = rgb("#E7E7E7") #let mainblue = rgb("#0E5484") #let maingray = rgb("#B9B9B9") #let fontSize = ( tiny: 5pt, scriptsize: 7pt, footnotesize: 8pt, small: 9pt, normalsize: 10pt, large: 12pt, Large: 14pt, LARGE: 17pt, huge: 20pt, Huge: 25pt, ) /** * @pages {int} total pages to calculate left block height, since it's difficult to calculate using typst. default to 1. * @left {block} left block * @right {block} right block */ #let main( pages: 1, left, right, ) = { set page( margin: ( left: 0cm, right: 0cm, top: 0cm, bottom: 0cm, ) ) grid( columns: (35%, 65%), rows: auto, // column-gutter: 1em, block( fill: sidecolor, height: pages * 100%, pad( top: 1cm, rest: 0.5cm, left ) ), block( height: auto, pad( top: 0.7cm, rest: 0.5cm, right, ) ), ) } #let profile( name: "", jobtitle: "", ) = { text(fill: pblue, size: fontSize.Huge, name) // {\Huge\color{pblue}\cvname} linebreak() v(2mm) text(fill: gray80, size: fontSize.Large, jobtitle) // {\Large\color{black!80}\cvjobtitle} } #let profile_section(title) = { v(3mm) align(left)[ #text(size: fontSize.huge, fill: gray80)[#title] #box(width: 1fr, baseline: -0.5em, line(length: 100%, stroke: gray80)) ] } // score is 1 base #let skill_checkbox(checked) = { box( width: 1em, height: 1em, stroke: mainblue, radius: 0.2em, if checked { rect( width: 0.6em, height: 0.6em, fill: mainblue, radius: 0.1em, ) } ) } /* interest item is dictionary ( interest: "数据可视化", subskills: ( (name: "Tableau", checked: true), (name: "Python matplotlib", checked: true), (name: "D3.js", checked: false), ) ) */ #let show_interests(interests) = { set text(size: fontSize.large, fill: gray80) for interest in interests { text(interest.interest) linebreak() if interest.at("subskills", default: none) != none { grid( columns: (auto, 1fr), column-gutter: 0.5em, row-gutter: 0.3em, ..interest.subskills.map(subskill => ( [#skill_checkbox(subskill.checked)], [#text(size: fontSize.normalsize)[#subskill.name]] )).flatten() ) } else { v(1em) } } } /* contact item is dictionary ( icon: "linkedin", solid: false, // Whether to use the solid version of the icon text: "https://www.linkedin.com/in/someone", ) */ #let show_contacts(contacts) = { v(3mm) let c = () for contact in contacts { c.push(fa-icon(contact.icon, solid: contact.at("solid", default: false), fill: pblue)) c.push(contact.text) } grid( columns: (auto, auto), column-gutter: 1em, row-gutter: 1em, ..c ) } #let body_section(slice:6, title) = { let (header, tailer) = (title.slice(0, slice), title.slice(slice)) set text(size: fontSize.LARGE) block[ #v(3mm) #strong()[ #text(fill: pblue, header)#text(fill: headercolor, tailer) ] ] } /* #1 period, like From - To #2 title #3 note, basic note #4 addtional_note #5 body: the main body */ #let twentyitem( period: "", title: "", note: "", addtional_note: "", body: "" ) = { grid( columns: (20%, 80%), period, par([ #block()[ #strong(title) #box(width: 1fr) #text(size: fontSize.footnotesize, note) ] #if (addtional_note.len() > 0) { block(addtional_note) } #body ]) ) }