diff --git a/README.md b/README.md index edaedd1..a0aea65 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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`. diff --git a/examples/tests/themes.pdf b/examples/tests/themes.pdf new file mode 100644 index 0000000..8c8826a Binary files /dev/null and b/examples/tests/themes.pdf differ diff --git a/internal/presets.typ b/internal/presets.typ index 26d7b76..d08e926 100644 --- a/internal/presets.typ +++ b/internal/presets.typ @@ -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", + ), +) diff --git a/lib.typ b/lib.typ index ebdde65..6518793 100644 --- a/lib.typ +++ b/lib.typ @@ -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 diff --git a/tests/themes.typ b/tests/themes.typ new file mode 100644 index 0000000..adf6746 --- /dev/null +++ b/tests/themes.typ @@ -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))