kwaad-cv-source/cv.typ

238 lines
6.7 KiB
Typst
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#set page(
paper: "a4",
margin: (left: 0.8cm, right: 0.8cm, top: 0.7cm, bottom: 0.6cm),
background: place(top + left)[
#image("assets/blob_background.png", width: 7.8cm)
],
)
#set text(
font: ("Liberation Sans", "DejaVu Sans", "Noto Sans"),
size: 7.5pt,
fill: rgb("#1e293b"),
lang: "fr",
)
#set par(justify: false, leading: 0.42em)
#let data = yaml("cv_data.yaml")
#let primary = rgb("#0284c7") // Bleu moderne (Sky-600)
#let primary-dark = rgb("#0369a1") // Bleu foncé
#let text-dark = rgb("#0f172a") // Slate-900
#let text-muted = rgb("#64748b") // Slate-500
#let border-color = rgb("#e2e8f0") // Slate-200
#let badge-bg = rgb("#f1f5f9") // Slate-100
// Titre de section stylisé
#let section-title(title, icon: none) = {
v(0.4em)
block(width: 100%)[
#text(size: 10pt, weight: "bold", fill: text-dark)[#upper(title)]
#v(-0.35em)
#line(length: 100%, stroke: 1.5pt + primary)
]
v(0.25em)
}
// Badge de compétence
#let skill-badge(term) = {
box(
fill: badge-bg,
radius: 3pt,
inset: (x: 4pt, y: 2pt),
baseline: 0%,
outset: 0pt,
)[#text(size: 6.8pt, weight: "medium", fill: rgb("#334155"))[#term]]
}
#let icon-row(icon-path, content) = {
grid(
columns: (13pt, 1fr),
gutter: 5pt,
align: (center + horizon, left + horizon),
image(icon-path, width: 10pt),
content
)
}
// Layout en 2 colonnes bien proportionnées : Gauche (5.4cm) - Droite (1fr)
#grid(
columns: (5.4cm, 1fr),
column-gutter: 0.7cm,
// ==========================================
// COLONNE DE GAUCHE : Profil, Contact, Compétences
// ==========================================
[
// Photo
#align(center)[
#block(width: 3.2cm, height: 4.3cm)[
#align(center + horizon)[
#image("assets/profile.png", width: 2.95cm)
]
]
]
#section-title("Coordonnées")
#stack(
spacing: 0.45em,
if data.contact.phone != none and data.contact.phone != "" [
#icon-row("assets/icons/phone.png", text(size: 7.2pt)[#link("tel:" + data.contact.phone.replace(" ", ""))[#data.contact.phone]])
],
icon-row("assets/icons/mail.png", text(size: 7.2pt)[#link("mailto:" + data.contact.email)[#data.contact.email]]),
icon-row("assets/icons/home.png", text(size: 7.2pt)[#data.contact.location]),
if data.contact.mobility != none and data.contact.mobility != "" [
#icon-row("assets/icons/pin.png", text(size: 7.2pt)[#data.contact.mobility])
],
icon-row("assets/icons/car.png", text(size: 7.2pt)[#data.contact.licenses]),
icon-row("assets/icons/link.png", text(size: 7.2pt)[#link(data.contact.linkedin_url)[#data.contact.linkedin]]),
)
#section-title("Compétences Clés")
#for s in data.skills [
#text(weight: "bold", size: 7.3pt, fill: text-dark)[#s.category] \
#v(-0.2em)
#let items = s.items.split(", ")
#items.map(skill-badge).join(" ")
#v(0.35em)
]
#section-title("Certifications")
#for c in data.certifications [
#let is-rncp = c.contains("RNCP 7")
#grid(
columns: (7pt, 1fr),
gutter: 4pt,
text(fill: primary)[•],
text(size: 7pt)[
#if is-rncp [
#text(weight: "bold", fill: primary-dark)[#c]
] else [
#c
]
]
)
#v(0.15em)
]
#section-title("Langues & Soft Skills")
#for lang in data.languages [
*#lang.name* : #lang.details \
]
#v(0.15em)
#text(size: 7pt, fill: text-muted)[
*Soft skills :* #data.soft_skills.join(" • ")
]
#section-title("Liens & Références")
#for l in data.links [
#grid(
columns: (7pt, 1fr),
gutter: 4pt,
text(fill: primary)[•],
text(size: 7pt)[#link(l.url)[#l.label.replace("https://", "")]]
)
#v(0.1em)
]
#section-title("Centres d'intérêt")
#text(size: 7pt)[
#data.interests.join(" • ")
]
],
// ==========================================
// COLONNE DE DROITE : En-tête, Résumé, Expériences, Formation
// ==========================================
[
// En-tête avec hiérarchie claire
#v(0.1cm)
#text(size: 18pt, weight: "bold", fill: text-dark)[#data.header.name] \
#v(-0.35em)
#box[
#text(size: 11pt, weight: "bold", fill: primary)[#data.header.title]
#text(size: 9pt, fill: text-muted)[ | #data.header.subtitle]
]
// Résumé
#section-title("Profil Professionnel")
#text(size: 7.3pt, fill: rgb("#334155"))[#data.summary]
// Expériences professionnelles
#section-title("Expériences Professionnelles")
#for exp in data.experiences [
#block(width: 100%, breakable: false)[
// Ligne 1 : Titre + Date alignée à droite
#grid(
columns: (1fr, auto),
align: (left + horizon, right + horizon),
[
#text(size: 8.6pt, weight: "bold", fill: text-dark)[#exp.title]
],
[
#box(
fill: badge-bg,
radius: 3pt,
inset: (x: 5pt, y: 1.5pt),
)[#text(size: 7pt, weight: "bold", fill: primary-dark)[#exp.dates]]
]
)
#v(-0.25em)
// Ligne 2 : Logo + Entreprise
#box(height: 12pt)[
#grid(
columns: (auto, auto),
gutter: 5pt,
align: horizon,
image(exp.logo, height: 10.5pt),
text(size: 8pt, weight: "medium", style: "italic", fill: rgb("#475569"))[#exp.company – #exp.location]
)
]
#v(0.05em)
// Missions avec puces soignées
#for task in exp.tasks [
#grid(
columns: (7pt, 1fr),
gutter: 4pt,
align: (center + top, left + top),
text(size: 7pt, fill: primary)[▸],
text(size: 7.2pt)[#task]
)
]
#if "languages" in exp [
#v(-0.15em)
#pad(left: 11pt)[
#text(size: 6.9pt, fill: text-muted)[
_Technologies :_ #text(fill: text-dark)[#exp.languages]
]
]
]
]
#v(0.28em)
]
// Formation
#section-title("Formation")
#for edu in data.education [
#let is-rncp = edu.degree.contains("RNCP 7")
#grid(
columns: (2.3cm, 1fr),
gutter: 6pt,
align: (left, left),
text(size: 7pt, fill: text-muted, weight: "medium")[#edu.dates],
[
#if is-rncp [
#text(size: 7.2pt, weight: "bold", fill: primary-dark)[#edu.degree]
] else [
#text(size: 7.2pt, weight: "bold")[#edu.degree]
] \
#v(-0.35em)
#text(size: 6.8pt, fill: text-muted)[#edu.school]
]
)
#v(0.12em)
]
]
)