Skip to content

Instantly share code, notes, and snippets.

@kineticz
Last active June 17, 2026 09:46
Show Gist options
  • Select an option

  • Save kineticz/cc33d13db5eb98bac38eb597ea6afa42 to your computer and use it in GitHub Desktop.

Select an option

Save kineticz/cc33d13db5eb98bac38eb597ea6afa42 to your computer and use it in GitHub Desktop.
Puterize Typst Style — elegant en onderscheidend Typst template (blauw-cyaan-oranje, DM Sans + Red Hat Mono)

Puterize Typst Style

Puterize huisstijl-documenttemplate voor Typst — een moderne tech-stijl met blauw→cyaan gradient, oranje accenten, DM Sans en Red Hat Mono.

Vereisten

  • Typst ≥ 0.15.0
  • Fonts (staan al op je systeem, of installeer ze):
    • DM Sans — body en display
    • Red Hat Mono — code, labels, meta

Fonts installeren (indien niet aanwezig)

# DM Sans (Google Fonts, OFL)
sudo apt install fonts-dm-sans 2>/dev/null || \
  sudo cp fonts/*.ttf /usr/local/share/fonts/ && sudo fc-cache -fv

# Red Hat Mono (Red Hat, OFL)
sudo apt install redhat-mono-fonts 2>/dev/null

Snel starten

# Compileren
./build.sh example.typ

# Of met watch-modus (hercompileert bij wijzigingen)
./build.sh example.typ --watch

Gebruik als Typst-package

Optie 1: Lokaal package (aanbevolen)

#import "@local/puterize-typst:0.1.0": puterize-doc

Optie 2: Relatief pad

#import "lib.typ": puterize-doc

Volledig voorbeeld

#import "lib.typ": puterize-doc

#show: puterize-doc.with(
  title-pre:    "Schaalbare",
  title-accent: "infrastructuur",
  eyebrow:      "Technische architectuur · Puterize",
  subtitle:     "Een blauwdruk voor cloud-native platformen.",
  doc-id:       "PUTERIZE · ARCHITECTUURNOTITIE",
  doc-name:     "Architectuurnotitie",
  version:      "1.0",
  author:       "Kaiyin Zhong",
  distribution: "Engineering team",
)

= Eerste hoofdstuk
...

Parameters

Parameter Standaard Beschrijving
title-pre "Project" Eerste deel titel op cover
title-accent "document" Laatste woord — blauw + oranje punt
eyebrow "Technisch document · Puterize" Mono-label boven cover-titel
subtitle "" Beschrijving op cover
doc-id "PUTERIZE · DOC" Header links
doc-name "Document" Cover meta-blok
version "1.0" Cover meta-blok
author "Kaiyin Zhong" Cover meta-blok
distribution "Intern team" Cover meta-blok
brand-tag "Puterize · Infrastructuur · 2026" Header rechts
footer-tag "Puterize · Intern document" Footer links
date vandaag Datum in DD-MM-YYYY

Markup-conventies

Typst Resultaat
= Hoofdstuk Hoofdstuk — nieuwe pagina, "HOOFDSTUK XX" label, blauw-cyaan gradient underline
== Sectie Sectie — DM Sans SemiBold, oranje blokje marker
=== Subsectie Subsectie — DM Sans Medium, grijze tint
- item Bullet-list — oranje • markers
+ item Genummerde lijst — blauwe nummers
#quote(block: true) Callout — blauwe rand, "OPMERKING" in oranje
`code` Inline code — lichte blauwe chip
``` block Codeblok — donkere achtergrond, blauwe linkerrand
*strong* Vet — weight 600, ink
_emph_ Italic — grijze tint
#link("...") Link — blauw, cyaan dotted underline

Kleurenpalet

Variabele Hex Gebruik
puterize-blue #0E80EB Primair — koppen, links, accenten
puterize-cyan #00CAF0 Highlight — gradient-eindpunt
puterize-orange #F0A040 Warm accent — bullets, callout labels
puterize-ink #0F172A Hoofdtekst
puterize-ink-soft #334155 Secundaire tekst
puterize-muted #64748B Labels, meta
puterize-muted-soft #94A3B8 Paginanummers
puterize-rule #E2E8F0 Lijnen, borders
puterize-blue-soft #EFF6FF Subtiele blauwe wash (cover, callout-bg, inline code)
puterize-code-bg #0F172A Code block achtergrond
puterize-code-fg #E2E8F0 Code block tekst

Bestandsoverzicht

puterize-typst-style/
├── favicon.svg          # Logo — bron van het kleurenpalet
├── lib.typ              # Hoofdtemplate (puterize-doc)
├── example.typ          # Voorbeelddocument
├── build.sh             # Compileer-helper
├── typst.toml           # Package-configuratie
├── README.md            # Deze documentatie
└── .gitignore

Licentie

MIT — zie typst.toml

Display the source blob
Display the rendered blob
Raw
// =============================================================================
// Puterize Typst Style — Example Document
// =============================================================================
// Compile with: ./build.sh example.typ
// =============================================================================
#import "lib.typ": puterize-doc, puterize-blue, puterize-orange
#show: puterize-doc.with(
title-pre: "Schaalbare",
title-accent: "infrastructuur",
eyebrow: "Technische architectuur · Puterize",
subtitle: "Een blauwdruk voor het ontwerpen en beheren van cloud-native platformen met een focus op veerkracht, observability en reproduceerbaarheid.",
doc-id: "PUTERIZE · ARCHITECTUURNOTITIE",
doc-name: "Architectuurnotitie",
version: "1.0",
author: "Kaiyin Zhong",
distribution: "Engineering team",
brand-tag: "Puterize · Infrastructuur · 2026",
footer-tag: "Puterize · Intern document",
)
= Waarom deze blauwdruk en wat je ervan mag verwachten
Dit document beschrijft de technische fundering van ons platform. We zijn gegroeid van een enkele #raw("VPS") naar een gedistribueerd systeem, en dat vraagt om een doordachte aanpak.
== De aanleiding — waarom nu?
We hebben de afgelopen maanden drie incidenten gehad die rechtstreeks te herleiden zijn naar het ontbreken van een gedeelde architectuurvisie:
- Een #strong[productie-onderbreking] door een configuratiedrift tussen staging en productie
- Onverklaarbaar resource-gebruik doordat #emph[niemand] wist welke services waar draaiden
- Een deploy die twee uur duurde omdat de volgorde van componenten niet gedocumenteerd was
Dit document maakt de impliciete kennis expliciet.
== Doelstellingen en niet-doelen
#text(fill: puterize-blue, weight: 600)[Doelstellingen:]
+ Een eenduidig, grafisch overzicht van alle systeemcomponenten
+ Draaiboeken voor de drie meest voorkomende incident-scenario's
+ Een reproduceerbare opbouw vanaf nul — #raw("git clone && make up")
+ Kostenschatting per omgeving (dev, staging, prod)
#text(fill: puterize-blue, weight: 600)[Geen doelen:]
- Dit is geen vervanging voor runbooks in de monitoring-stack
- Geen security-audit of penetratietest
- Geen prestatie-benchmark
== Terminologie en conventies
Een paar termen die we consistent gebruiken door het hele document:
#table(
columns: (auto, 1fr),
[Term], [Betekenis],
[Control plane], [De set services die het platform bestuurt: CI/CD, orchestrator, secret manager.],
[Data plane], [Alles wat verkeer verwerkt: load balancers, API-gateways, workload-containers.],
[Cell], [Een onafhankelijk deploy-baar cluster; typisch één per regio of tenant.],
[Pulse], [Onze interne heartbeat-check die elke service elke 10s bevraagt.],
)
= Voorgestelde architectuur
We stellen een #strong[cell-based architecture] voor, waarbij elke cell een zelfstandig opererend cluster is met eigen control plane, data plane, en persistentie-laag.
#quote(block: true)[
De keuze voor cell-gebaseerde architectuur is niet gratuit. Tijdens het evalueren van alternatieven — monolithisch horizontaal schalen, microservices zonder harde grenzen, en event-driven mesh — bleek de cell-aanpak de beste balans te bieden tussen isolatie, operabiliteit, en kostenefficiëntie. Elke cel deelt geen fate met andere cellen; een storing in cel-A raakt cel-B niet.
]
== Componentendiagram
Dit is het overzicht op hoog niveau. Details per component volgen in hoofdstuk 3.
#table(
columns: (auto, auto, 1fr),
[Component], [Schaal], [Verantwoordelijkheid],
[API Gateway], [2+ per cel], [Rate limiting, auth, request-routing op basis van tenant-header.],
[Workload Controller], [1 per cel], [Orkestreert containers, herstart bij health-check failure, schaalt op basis van CPU/memory metrics.],
[Secret Store], [1 per cel], [Bewaart encryptie-sleutels, DB-wachtwoorden, API-tokens. Auto-rotate elke 72 uur.],
[Message Bus], [3 nodes per cel], [NATS-cluster met JetStream voor at-least-once delivery.],
[Observability Stack], [1 per cel], [OpenTelemetry collector, Tempo, Loki, Mimir — alle data blijft binnen de cel.],
)
== Configuratie-voorbeeld
Elke cel wordt beschreven door een declaratief manifest in #raw("YAML"):
```yaml
cell:
name: eu-west-1
region: europe-west4
networking:
ingress_cidr: 10.32.0.0/16
mesh_cidr: 100.64.0.0/10
control_plane:
replicas: 3
resources:
cpu: 2
memory: 4Gi
data_plane:
min_replicas: 2
max_replicas: 20
scaling:
metric: cpu_utilization
target: 0.7
persistence:
pg_version: 16
backup:
schedule: "0 */6 * * *"
retention_days: 30
```
Dit manifest is de #strong[enige bron van waarheid]. De #raw("cell-controller") leest het uit Git en brengt de werkelijkheid ernaartoe via een reconcilation loop.
= Risico's, mitigatie en open vragen
Elk ontwerp kent onzekerheden. We benoemen ze liever expliciet.
== Top-risico's
#table(
columns: (auto, auto, 1fr, auto),
[#text(fill: puterize-orange, weight: 700)[Risico]], [Kans], [Beschrijving], [Mitigatie],
[Split-brain], [Laag], [Twee cellen claimen dezelfde tenant door netwerkpartitie.], [Raft-leader election met fencing tokens.],
[Secret-lek], [Middel], [Rotatie faalt silent, credentials verlopen ongemerkt.], [#raw("rotated_secret_valid_after") check in health endpoint.],
[Cost-spike], [Hoog], [Auto-scaling schiet door bij traffic piek.], [Max-replicas cap per cel + budget-alert op 80%.],
[Data-verlies], [Laag], [Backup corrupt door bit-rot in cold storage.], [Maandelijkse restore-drill met checksum-verificatie.],
)
== Wat we nog moeten uitzoeken
- #link("https://www.cncf.io/projects/kubernetes/")[Kubernetes] vs Nomad voor de workload controller — beide voldoen, de keuze is organisatorisch, niet technisch.
- Welke #strong[echte] RTO/RPO eisen stellen de product owners? Zonder helder antwoord kunnen we niet dimensioneren.
- Moet de message bus cel-overstijgend kunnen routeren of blijft verkeer strikt lokaal?
// =============================================================================
// Puterize Typst Template — lib.typ
// =============================================================================
// Blauw-cyaan gradient huisstijl met oranje accenten.
// Fonts: DM Sans (body/display) + Red Hat Mono (code).
// Kleuren trouw aan favicon.svg: #0E80EB (blauw), #00CAF0 (cyaan), #F0A040 (oranje).
// =============================================================================
// ── COLOURS (from favicon.svg) ────────────────────────────────────────────────
#let puterize-blue = rgb("#0E80EB")
#let puterize-cyan = rgb("#00CAF0")
#let puterize-orange = rgb("#F0A040")
#let puterize-gold = rgb("#F6D040")
#let puterize-ink = rgb("#0F172A")
#let puterize-ink-soft = rgb("#334155")
#let puterize-muted = rgb("#64748B")
#let puterize-muted-soft = rgb("#94A3B8")
#let puterize-rule = rgb("#E2E8F0")
#let puterize-rule-soft = rgb("#F1F5F9")
#let puterize-blue-soft = rgb("#EFF6FF")
#let puterize-blue-pale = rgb("#DBEAFE")
#let puterize-code-bg = rgb("#0F172A")
#let puterize-code-fg = rgb("#E2E8F0")
// ── FONTS ─────────────────────────────────────────────────────────────────────
#let puterize-display = "DM Sans 9pt"
#let puterize-body = "DM Sans 9pt"
#let puterize-mono = "Red Hat Mono"
// ── HELPERS ───────────────────────────────────────────────────────────────────
/// Convert content to a plain string (for accent-word splitting).
/// Handles special characters like apostrophes gracefully.
#let to-string(content) = {
let s = repr(content)
// Strip outer brackets if the content was a sequence
if s.starts-with("[") and s.ends-with("]") {
s = s.slice(1, s.len() - 1)
}
// Collapse whitespace
s.split(" ").filter(w => w != "").join(" ")
}
/// Pad a number to two digits: 1 → "01".
#let pad2(n) = {
let s = str(n)
if s.len() < 2 { "0" + s } else { s }
}
/// Title where the *last word* is coloured blue.
/// Ends with an orange dot — the signature Puterize punctuation.
/// Falls back gracefully when content contains special characters.
#let accent-title(title, size: 32pt) = {
let raw-str = to-string(title)
let words = raw-str.split(" ")
// Detect if repr() leaked Typst syntax — fall back to plain rendering
let is-clean = words.all(w =>
not w.contains("(") and
not w.contains(")") and
not w.contains("[") and
not w.contains("]") and
not w.contains("#") and
not w.contains("smartquote")
)
if not is-clean or words.len() <= 1 {
// Fallback: render title as-is without word-splitting
[
#set text(size: size, weight: 700, font: puterize-display)
#title
#text(fill: puterize-orange, size: size * 1.15)[.]
]
} else {
let last = words.pop()
let pre = words.join(" ")
[
#set text(size: size, weight: 700, font: puterize-display)
#if pre != "" [
#pre
#h(0.15em)
]
#text(fill: puterize-blue)[#last]#text(fill: puterize-orange, size: size * 1.15)[.]
]
}
}
/// A thin horizontal rule with blue→cyan linear gradient.
#let gradient-rule(width: 100%, height: 4pt) = {
block(
width: width,
height: height,
fill: gradient.linear(
(puterize-blue, 0%),
(puterize-cyan, 100%),
angle: 90deg,
),
radius: 0.5pt,
)
}
// ── TEMPLATE ──────────────────────────────────────────────────────────────────
#let puterize-doc(
// cover
title-pre: "Project",
title-accent: "document",
eyebrow: "Technisch document · Puterize",
subtitle: "",
// meta
doc-id: "PUTERIZE · DOC",
doc-name: "Document",
version: "1.0",
author: "Kaiyin Zhong",
distribution: "Intern team",
// header / footer
brand-tag: "Puterize · Infrastructuur · 2026",
footer-tag: "Puterize · Intern document",
date: none,
// content
toc-intro: none,
body,
) = {
// derive date once
let the-date = if date != none { date } else {
datetime.today().display("[day]-[month]-[year]")
}
let toc-text = if toc-intro != none { toc-intro } else { subtitle }
// ── DOCUMENT-WIDE SETTINGS ──────────────────────────────────────────────────
set document(title: title-pre + " " + title-accent)
set text(
size: 10pt,
font: puterize-body,
tracking: -0.005em,
lang: "nl",
)
set par(
leading: 0.78em,
spacing: 1.0em,
justify: false,
)
// ── INLINE MARKUP ───────────────────────────────────────────────────────────
show strong: it => {
text(weight: 600, fill: puterize-ink, it)
}
show emph: it => {
text(style: "italic", fill: puterize-ink-soft, it)
}
show link: it => {
set text(fill: puterize-blue, weight: 500)
underline(
stroke: (paint: puterize-cyan, thickness: 0.6pt, dash: "dotted"),
offset: 2pt,
it,
)
}
// Inline code — light blue chip
show raw.where(block: false): it => {
box(
fill: puterize-blue-soft,
stroke: (paint: puterize-blue-pale, thickness: 0.5pt),
radius: 3pt,
outset: (y: 1.5pt, x: 3.5pt),
inset: (y: 1pt, x: 0pt),
text(font: puterize-mono, size: 9pt, fill: puterize-ink, it),
)
}
// ── BLOCK CODE ──────────────────────────────────────────────────────────────
show raw.where(block: true): it => {
block(
fill: puterize-code-bg,
stroke: (left: (paint: puterize-blue, thickness: 3pt)),
radius: 6pt,
inset: (top: 14pt, bottom: 14pt, left: 16pt, right: 16pt),
outset: (y: 10pt),
)[
#text(
font: puterize-mono,
size: 8.5pt,
fill: puterize-code-fg,
it,
)
]
}
// ── LISTS ───────────────────────────────────────────────────────────────────
// All lists — orange markers, comfortable spacing
show list: it => {
set list(
marker: text(fill: puterize-orange, weight: 700, size: 1.1em)[•],
indent: 2pt,
body-indent: 8pt,
)
it
}
// ── TABLES ──────────────────────────────────────────────────────────────────
// Set table defaults globally (does not affect grid elements like cover grids)
set table(
stroke: (
top: (paint: puterize-rule, thickness: 0.5pt),
bottom: (paint: puterize-rule, thickness: 0.5pt),
y: (paint: puterize-rule, thickness: 0.3pt),
x: (paint: puterize-rule, thickness: 0.3pt),
),
inset: (x: 10pt, y: 7pt),
fill: (x, y) => if y == 0 { puterize-blue-soft },
rows: 1,
)
// ── CALLOUTS (blockquote override) — panel accent with gradient top-stripe ──
show quote.where(block: true): it => {
block(
fill: puterize-blue-soft,
stroke: (paint: puterize-blue-pale, thickness: 0.8pt),
radius: 8pt,
inset: (top: 16pt, bottom: 14pt, left: 16pt, right: 16pt),
outset: (y: 12pt),
)[
// Gradient top-stripe via a zero-height block the full width
#block(
width: 100%,
height: 3pt,
fill: gradient.linear(
(puterize-blue, 0%),
(rgb("#DBEAFE"), 100%),
angle: 0deg,
),
)
#v(2pt)
#text(font: puterize-mono, size: 7.5pt, fill: puterize-orange, weight: 600)[OPMERKING]
#v(6pt)
#text(size: 10pt, fill: puterize-ink-soft, it.body)
]
}
// ── HEADINGS ────────────────────────────────────────────────────────────────
// Enable heading numbering for TOC (our show rules override display)
set heading(numbering: "1")
// Chapter counter for H1 display (start at 0, increments before each chapter)
let ch-num = counter("ch-num")
// Don't call update(0) which triggers increment; we start at 0 naturally
// H1 — Chapter: page break → watermarked number → accent title → gradient rule
show heading.where(level: 1): it => {
pagebreak(weak: false)
ch-num.step()
let n = ch-num.get().first()
v(4pt)
align(left)[
// Watermarked section number — 48pt, very faint, behind the title
#place(
top + left,
dx: -6pt,
dy: 0pt,
)[
#text(
font: puterize-display,
size: 48pt,
weight: 700,
fill: puterize-rule-soft,
tracking: -0.02em,
)[#pad2(n)]
]
// Chapter label
#text(font: puterize-mono, size: 8pt, fill: puterize-blue, weight: 600)[
HOOFDSTUK #pad2(n)
]
#v(4pt)
#accent-title(it.body, size: 28pt)
#v(10pt)
#gradient-rule(width: 100%, height: 3pt)
#v(16pt)
]
}
// H2 — orange square marker + DM Sans SemiBold
show heading.where(level: 2): it => {
v(22pt)
[
#block(
fill: puterize-orange,
width: 8pt,
height: 8pt,
radius: 1.2pt,
)
#h(8pt)
#text(font: puterize-display, weight: 600, size: 14pt, fill: puterize-ink, it.body)
]
v(8pt)
}
// H3 — DM Sans Medium, softer ink
show heading.where(level: 3): it => {
v(14pt)
[
#text(font: puterize-display, weight: 500, size: 11.5pt, fill: puterize-ink-soft, it.body)
]
v(5pt)
}
// ── COVER PAGE ──────────────────────────────────────────────────────────────
let cover = {
set page(
paper: "a4",
margin: (top: 48mm, bottom: 28mm, left: 28mm, right: 28mm),
fill: puterize-blue-soft,
)
// Cover has no header/footer
set page(numbering: none)
// Top gradient bar
place(top + left, dx: 0pt, dy: 0pt)[
#gradient-rule(width: 100%, height: 5pt)
]
// Eyebrow
text(font: puterize-mono, size: 9pt, fill: puterize-muted)[#eyebrow]
v(18pt)
// Title — DM Sans Black, 52pt
text(font: puterize-display, size: 52pt, tracking: -0.035em, fill: puterize-ink)[
#title-pre
#h(0.2em)
#text(fill: puterize-blue)[#title-accent]#text(fill: puterize-orange, size: 58pt)[.]
]
v(12pt)
// Subtitle
if subtitle != "" [
text(font: puterize-body, size: 13pt, fill: puterize-ink-soft, style: "italic")[#subtitle]
v(8pt)
]
// Bottom bar with meta
place(bottom + left, dx: 0pt, dy: 0pt)[
#block(
width: 100%,
stroke: (top: (paint: puterize-blue, thickness: 1pt)),
)[
#v(14pt)
#grid(
columns: (1fr, 1fr),
row-gutter: 8pt,
column-gutter: 16pt,
// Left: brand
[
#text(font: puterize-display, weight: 700, size: 12pt, fill: puterize-blue)[Puterize]
#v(2pt)
#text(font: puterize-mono, size: 7.5pt, fill: puterize-muted)[Document template]
],
// Right: meta table
[
#set text(font: puterize-mono, size: 7.5pt)
#grid(
columns: (auto, 1fr),
column-gutter: 16pt,
row-gutter: 4pt,
[Document:], text(fill: puterize-ink, weight: 500)[#doc-name],
[Versie:], text(fill: puterize-ink, weight: 500)[#version],
[Datum:], text(fill: puterize-ink, weight: 500)[#the-date],
[Auteur:], text(fill: puterize-ink, weight: 500)[#author],
[Distributie:], text(fill: puterize-ink, weight: 500)[#distribution],
)
],
)
]
]
}
cover
// ── PAGE SETUP (after cover) ────────────────────────────────────────────────
set page(
paper: "a4",
margin: (top: 28mm, bottom: 24mm, left: 28mm, right: 28mm),
// header
header: [
#set text(font: puterize-mono, size: 7.5pt)
#block(
width: 100%,
stroke: (bottom: (paint: puterize-blue, thickness: 0.8pt)),
)[
#grid(
columns: (1fr, 1fr),
[
#text(fill: puterize-muted)[#doc-id]
],
align(right)[
#text(fill: puterize-muted)[#brand-tag]
],
)
#v(6pt)
]
],
// footer
footer: [
#set text(font: puterize-mono, size: 7.5pt)
#block(
width: 100%,
stroke: (top: (paint: puterize-blue, thickness: 0.8pt)),
)[
#v(6pt)
#grid(
columns: (1fr, 1fr),
[
#text(fill: puterize-muted)[#footer-tag]
],
align(right)[
#text(fill: puterize-muted-soft)[#context { counter(page).display() }]
],
)
]
],
numbering: "1",
footer-descent: 6pt,
header-ascent: 6pt,
)
// ── TABLE OF CONTENTS ───────────────────────────────────────────────────────
pagebreak(weak: false)
v(10pt)
align(left)[
#text(font: puterize-mono, size: 9pt, fill: puterize-blue, weight: 600)[INHOUD]
#v(6pt)
#accent-title([Wat in dit document staat], size: 30pt)
]
v(14pt)
if toc-text != "" [
text(font: puterize-body, size: 11pt, fill: puterize-ink-soft)[#toc-text]
v(20pt)
]
// TOC with custom-styled entries via a fresh counter
let toc-counter = counter("toc-num")
toc-counter.update(0)
show outline.entry: it => {
toc-counter.step()
let n = toc-counter.get().first()
if it.level == 1 {
[
#text(font: puterize-mono, size: 8.5pt, fill: puterize-blue, weight: 600)[#pad2(n)]
#h(10pt)
#text(font: puterize-display, size: 12pt, weight: 600, fill: puterize-ink)[#it.element.body]
#v(3pt)
#line(
length: 100%,
stroke: (paint: puterize-rule, thickness: 0.5pt, dash: "dashed"),
)
#v(12pt)
]
}
}
set outline(
indent: 0pt,
)
outline(target: heading.where(level: 1))
pagebreak(weak: false)
// ── CONTENT ─────────────────────────────────────────────────────────────────
body
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment