Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,29 @@ Reorder by overriding either array; both are concatenated in order:

Drop the portrait via `basics.image: none` for a fully text-only header.

### Theme presets

`themes` is a dict of vetted preset bundles — coherent combinations of `accent`, `font`, `columnRatio`, and `headerTextAlign` that are tedious to dial in by hand. Each theme is a partial `preferences` dict; spread it over your own overrides:

```typst
#import "@preview/altacv:1.1.1": alta, themes // x-release-please-version

#alta(cv, preferences: themes.modern)

// Spread + override: any preferences key (built-in or theme) can be
// tweaked after the spread, so you keep the theme's identity but
// adjust individual knobs.
#alta(cv, preferences: themes.modern + (imageSize: 7em))
```

| Theme | Accent | Font | Layout |
|---|---|---|---|
| `classic` | `palettes.teal` | `Lato` | 65/35 split, left-aligned header |
| `modern` | `palettes.navy` | `Inter` | 50/50 split, centred header |
| `minimal` | `palettes.charcoal` | `Source Sans 3` | 55/45 split, left-aligned header |

Themes only touch keys that interact visually — anything they don't set (paper, margins, image sizing, section order, …) falls back to `_default_preferences`. The `Inter` and `Source Sans 3` fonts must be installed on the build host; missing fonts fall back to Typst's defaults rather than panicking, so a missing font yields a warning but still renders.

### Labels

All display strings the template emits. Override any subset via `labels:`; the rest fall back to English defaults. Unknown keys panic. Use for translation or local renaming.
Expand Down Expand Up @@ -470,10 +493,11 @@ The defaults live in [`internal/labels-en.toml`](internal/labels-en.toml) — a
| `divider()` | Dashed grey rule used between entries within a section. |
| `styled-link(content, dest: none)` | Accent-coloured italic styling for entry titles (publications, awards, projects). Wraps in a link when `dest` is supplied. |
| `palettes` | Dict of curated accent presets — `teal`, `navy`, `crimson`, `forest`, `plum`, `charcoal`. Use as `accent: palettes.navy`. |
| `themes` | Dict of vetted preset bundles — `classic`, `modern`, `minimal`. Each is a partial `preferences` dict combining accent, font, and layout. Use as `preferences: themes.modern`. |
| `maps-providers` | Dict of map deep-link URL templates — `google`, `apple`, `bing`, `duckduckgo`, `osm`. Use as `mapsProvider: maps-providers.osm`. |

```typst
#import "@preview/altacv:1.1.1": alta, tag, divider, palettes, maps-providers // x-release-please-version
#import "@preview/altacv:1.1.1": alta, tag, divider, palettes, themes, maps-providers // x-release-please-version
```

The contact bar is rendered from `basics.email`, `basics.phone`, `basics.location`, `basics.url`, `basics.profiles`. Visual separators are stripped from the `tel:` dialable part. Suppress or swap deep links via `preferences.linkContactInfo` and `preferences.mapsProvider`.
Expand Down
Binary file added examples/tests/themes.pdf
Binary file not shown.
29 changes: 29 additions & 0 deletions internal/presets.typ
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,32 @@
duckduckgo: "https://duckduckgo.com/?q={q}&iaxm=maps",
osm: "https://www.openstreetmap.org/search?query={q}",
)

// Vetted preset bundles spread over caller overrides via dict-merge:
//
// #alta(cv, preferences: themes.modern + (imageSize: 7em))
//
// `classic` re-declares the current defaults rather than aliasing them
// — themes are bundles by name, not pointers to "whatever the default
// is right now", so a future default shift won't silently move `classic`.
// `imagePosition` is omitted because it's only meaningful with a portrait.
#let themes = (
classic: (
font: "Lato",
accent: palettes.teal,
columnRatio: 0.65,
headerTextAlign: "left",
),
modern: (
font: "Inter",
accent: palettes.navy,
columnRatio: 0.50,
headerTextAlign: "center",
),
minimal: (
font: "Source Sans 3",
accent: palettes.charcoal,
columnRatio: 0.55,
headerTextAlign: "left",
),
)
2 changes: 1 addition & 1 deletion lib.typ
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// (page margins, column gutter, rule thicknesses) are visual choices
// independent of text size.

#import "internal/presets.typ": palettes, maps-providers
#import "internal/presets.typ": palettes, maps-providers, themes
#import "internal/state.typ": _body_size_state, _accent_state, _max_rating_state, _body_colour, _emphasis_colour
#import "internal/defaults.typ": _default_labels
#import "internal/validation.typ": _strict_merge, _check_bool
Expand Down
47 changes: 47 additions & 0 deletions tests/themes.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// One page per built-in theme + a final page demonstrating
// spread-and-override. `font` is forced back to `Lato` for the
// non-classic themes because CI lacks `Inter` / `Source Sans 3`,
// so the snapshot stays warning-free while every other theme knob
// (accent, columnRatio, headerTextAlign) still gets exercised.

#import "../lib.typ": alta, themes

// Pin the declared font on each theme so a typo in `internal/presets.typ`
// (e.g. `"Intr"`) is caught here rather than silently falling back to
// the system default and producing an identical-looking snapshot.
#assert.eq(themes.classic.font, "Lato")
#assert.eq(themes.modern.font, "Inter")
#assert.eq(themes.minimal.font, "Source Sans 3")

#let cv = (
basics: (
name: "Sample User",
label: "Engineer",
email: "sample@example.com",
location: "Dublin, Ireland",
),
work: (
(
name: "Acme",
position: "Engineer",
startDate: "Jan 2024",
highlights: ([Shipped things.],),
),
),
skills: ((name: "Languages", keywords: ("Scala", "Python")),),
)

#for (i, key) in themes.keys().enumerate() {
if i > 0 { pagebreak() }
let theme = themes.at(key)
// `font: "Lato"` is a no-op for `classic` and the CI-host workaround
// for `modern` / `minimal`; merging unconditionally keeps the loop
// body uniform across themes.
alta(cv, preferences: theme + (font: "Lato"))
}

#pagebreak()

// Spread-and-override: themes are partial dicts, so any preferences
// key — built-in or theme — can be overridden after the spread.
#alta(cv, preferences: themes.modern + (font: "Lato", imageSize: 7em))