self_cv/twentysecondcv.typ

206 lines
3.8 KiB
Typst
Raw Normal View History

2025-05-11 22:32:52 +08:00
/*
* 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
])
)
}