Skip to content

Commit f403c6b

Browse files
aborrusoclaude
andcommitted
add typst-cards skill
Generates PNG images for online communication (social posts, carousels, infographics) using Typst. Includes brand-materials interview, supported formats (1:1, 4:5, 9:16, 16:9, 1.91:1) and three tested starter templates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0b3df09 commit f403c6b

5 files changed

Lines changed: 622 additions & 0 deletions

File tree

LOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# LOG
22

3+
## 2026-04-26
4+
5+
- `typst-cards`: new skill — generates PNG images for online communication (social posts, carousels, infographics) using Typst; brand-materials interview, supported formats (1:1, 4:5, 9:16, 16:9, 1.91:1), three reference starters (`theme-starter.typ`, `slides-1x1-starter.typ`, `slides-16x9-starter.typ`), known pitfalls
6+
37
## 2026-04-24
48

59
- `openalex`: strengthen API key security — added prominent SECURITY callout at top of SKILL.md; expanded Common Pitfalls rule to cover all output contexts (text responses, echoed commands, logs), not just verification

skills/typst-cards/SKILL.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
name: typst-cards
3+
description: Generate PNG images for online communication — social media, carousels, infographics, posts — using Typst. Use this skill whenever the user wants to create slides, cards, visual posts or any digital graphic content, even if they don't explicitly mention Typst. The skill drives an interview about brand materials (logo, palette, fonts, DESIGN.md), proposes the formats best suited to the context (Instagram 1:1, Stories 9:16, LinkedIn 16:9, etc.) and produces ready-to-use PNGs.
4+
---
5+
6+
## Purpose
7+
8+
Turn a textual context and optional brand materials into professional PNG images for online communication, using Typst as the rendering engine. The skill manages the full flow: interview → theme → generation → review.
9+
10+
## Prerequisites — verify Typst
11+
12+
Before any other step, check that Typst is installed:
13+
14+
```bash
15+
typst --version
16+
```
17+
18+
If not available, install it like this (Linux/WSL x86_64):
19+
20+
```bash
21+
curl -fsSL https://github.com/typst/typst/releases/latest/download/typst-x86_64-unknown-linux-musl.tar.xz -o /tmp/typst.tar.xz && tar -xf /tmp/typst.tar.xz -C /tmp/ && mv /tmp/typst-x86_64-unknown-linux-musl/typst ~/.local/bin/
22+
```
23+
24+
---
25+
26+
## Phase 1 — Interview (single message, mandatory)
27+
28+
Before creating any file, ask these questions to the user **in a single message**:
29+
30+
**Visual materials available?**
31+
- Do you have a logo? If yes, where is the file? (PNG, SVG, or other)
32+
- Do you have a `DESIGN.md` or brand guidelines to share?
33+
- Do you have color preferences? (specific palette, or a mood description: "dark and technical", "colorful and lively", "institutional", etc.)
34+
- Do you have specific fonts to use?
35+
36+
**Desired output:**
37+
- What is the topic or content of the cards?
38+
- How many slides or cards? (a single image or a carousel?)
39+
- Which formats do you need? (propose those best suited to the context, see table below)
40+
41+
If the user has no brand materials, propose a palette consistent with the context and ask for a quick confirmation before proceeding.
42+
43+
---
44+
45+
## Supported formats
46+
47+
| Name | Typical use | Pixels | width | height | PPI |
48+
|------|-------------|--------|-------|--------|-----|
49+
| Square 1:1 | Instagram post, carousels | 1080×1080 | 7.5in | 7.5in | 144 |
50+
| Portrait 4:5 | Instagram portrait | 1080×1350 | 7.5in | 9.375in | 144 |
51+
| Story 9:16 | Instagram/TikTok Stories, Reels | 1080×1920 | 7.5in | 13.33in | 144 |
52+
| Landscape 16:9 | Twitter/X, YouTube thumb, LinkedIn header | 1920×1080 | 13.33in | 7.5in | 144 |
53+
| LinkedIn/OG 1.91:1 | LinkedIn post, Open Graph meta | 1200×628 | 8.33in | 4.36in | 144 |
54+
55+
**Math**: `pixels = inches × PPI` → e.g. 7.5in × 144ppi = 1080px.
56+
57+
For carousels, always use the same format for every slide.
58+
59+
---
60+
61+
## Phase 2 — Material analysis
62+
63+
**With DESIGN.md**: read it and extract — and **apply in theme.typ**:
64+
- **Colors**: every token (primary, secondary, accent, surface, neutral…)
65+
- **Fonts**: if the DESIGN.md specifies different fonts for different roles (e.g. display vs body), use them all in theme.typ as separate variables (`DISPLAY`, `SANS`, `MONO`). For example: a serif display face for headlines, a sans-serif for body.
66+
- **Visual style**: restrained? bold? editorial? Steer the layout accordingly.
67+
- **Explicit rules**: if the DESIGN.md says "do not use X on Y", honor it.
68+
69+
Do not collapse everything into a single font — the display/body distinction is part of the brand.
70+
71+
**With logo**: note the path. You'll include it in Typst with:
72+
```typst
73+
#image("path/to/logo.png", height: 0.5in) // fixed height
74+
#image("path/to/logo.svg", width: 2in) // fixed width
75+
```
76+
77+
**Without materials**: pick a palette based on context:
78+
- Tech/data topic → dark background `#0d1117`, accent blue `#58a6ff`
79+
- Consumer/lifestyle topic → white background, vivid colors
80+
- Institutional/PA topic → sober palette, neutral tones with a measured accent
81+
- Editorial/journalism topic → strong typography, high contrast
82+
83+
---
84+
85+
## Phase 3 — File structure
86+
87+
Always create this structure in the project directory:
88+
89+
```
90+
<project>/carousel/
91+
├── theme.typ # design tokens and helper functions
92+
├── slides.typ # content (imports theme.typ)
93+
└── output/ # generated PNGs
94+
├── slide-1.png
95+
├── slide-2.png
96+
└── ...
97+
```
98+
99+
### Recommended starting point
100+
101+
Instead of writing from scratch, **copy one of the reference templates** from the skill folder and adapt it:
102+
103+
- `references/theme-starter.typ` → palette, fonts, helpers (`lbl`, `ctr`, `codebox`, `divider`)
104+
- `references/slides-1x1-starter.typ` → 5 Instagram 1:1 slides (cover + 3 content + outro)
105+
- `references/slides-16x9-starter.typ` → 3 LinkedIn 16:9 slides (cover + two-column + 3 stat)
106+
107+
The templates are **already tested and compile cleanly**. They give you a proven structure; you change colors, fonts, content.
108+
109+
```bash
110+
# example:
111+
cp <SKILL_DIR>/references/theme-starter.typ <project>/carousel/theme.typ
112+
cp <SKILL_DIR>/references/slides-1x1-starter.typ <project>/carousel/slides.typ
113+
# then edit content in slides.typ and tokens in theme.typ
114+
```
115+
116+
### theme.typ — design tokens
117+
118+
Adapt colors to the materials gathered. This is a starting point:
119+
120+
```typst
121+
// — palette —
122+
#let BG-DARK = rgb("#0d1117")
123+
#let BG-LIGHT = rgb("#ffffff")
124+
#let ACC-DARK = rgb("#58a6ff") // accent on dark background
125+
#let ACC-LIGHT = rgb("#0969da") // accent on light background
126+
#let FG-DARK = rgb("#e6edf3") // text on dark
127+
#let FG-LIGHT = rgb("#1c2128") // text on light
128+
#let MUTED-D = rgb("#8b949e") // secondary on dark
129+
#let MUTED-L = rgb("#656d76") // secondary on light
130+
#let CODE-BG = rgb("#161b22")
131+
#let CODE-BR = rgb("#30363d")
132+
133+
// — fonts —
134+
// Safe fonts on Linux/WSL: DejaVu Sans, DejaVu Sans Mono
135+
// Check availability: fc-list | grep -i "Font Name"
136+
#let SANS = ("DejaVu Sans", "Liberation Sans", "Arial")
137+
#let MONO = ("DejaVu Sans Mono", "Liberation Mono", "Courier New")
138+
139+
// — helper: section label —
140+
#let lbl(body, dark: false) = text(
141+
size: 9pt, weight: "bold", tracking: 2pt,
142+
fill: if dark { ACC-DARK } else { ACC-LIGHT },
143+
)[#upper(body)]
144+
145+
// — helper: slide counter (for carousels) —
146+
#let ctr(n, total, dark: false) = align(right)[
147+
#text(size: 9pt, fill: if dark { MUTED-D } else { MUTED-L })[#n / #total]
148+
]
149+
150+
// — helper: code block —
151+
#let codebox(body) = block(
152+
fill: CODE-BG, stroke: 0.5pt + CODE-BR,
153+
radius: 4pt, inset: (x: 14pt, y: 11pt), width: 100%,
154+
)[
155+
#set text(font: MONO, size: 10.5pt, fill: FG-DARK)
156+
#body
157+
]
158+
```
159+
160+
### slides.typ — base structure
161+
162+
```typst
163+
#import "theme.typ": *
164+
165+
// Pick the format (only one active line):
166+
#set page(width: 7.5in, height: 7.5in, margin: (x: 0.62in, y: 0.58in)) // 1:1
167+
// #set page(width: 7.5in, height: 9.375in, margin: (x: 0.62in, y: 0.58in)) // 4:5
168+
// #set page(width: 7.5in, height: 13.33in, margin: (x: 0.62in, y: 0.70in)) // 9:16
169+
// #set page(width: 13.33in, height: 7.5in, margin: (x: 0.80in, y: 0.58in)) // 16:9
170+
// #set page(width: 8.33in, height: 4.36in, margin: (x: 0.62in, y: 0.45in)) // 1.91:1
171+
172+
#set text(font: SANS, size: 15pt, fill: FG-LIGHT)
173+
174+
// — SLIDE 1 (dark background) —
175+
#set page(fill: BG-DARK)
176+
#v(1fr)
177+
#lbl(dark: true)[tag · topic]
178+
#v(0.15in)
179+
#text(size: 44pt, weight: 900, fill: FG-DARK)[
180+
Main title\
181+
of the slide
182+
]
183+
#v(0.2in)
184+
#text(size: 14pt, fill: MUTED-D)[Short subtitle or description]
185+
#v(1fr)
186+
#ctr(1, 6, dark: true)
187+
188+
// — SLIDE 2 (light background) — different settings → automatic page break
189+
#set page(fill: BG-LIGHT)
190+
#lbl[02 · section]
191+
#v(0.2in)
192+
#text(size: 31pt, weight: 900)[Section title]
193+
#v(0.2in)
194+
#text(size: 14pt, fill: MUTED-L)[Slide body text.]
195+
#v(1fr)
196+
#ctr(2, 6)
197+
198+
// — SLIDE 3 (same background as slide 2 → explicit pagebreak) —
199+
#pagebreak()
200+
// ... content ...
201+
```
202+
203+
### Compilation
204+
205+
```bash
206+
cd <project>/carousel
207+
typst compile slides.typ "output/slide-{p}.png" --ppi 144
208+
```
209+
210+
The `{p}` is replaced by the page number → one PNG per slide.
211+
212+
---
213+
214+
## Design principles for social cards
215+
216+
**Text hierarchy**: large title → subtitle → body. Max 3 levels. Don't crowd.
217+
218+
**White space**: generous margins (0.5–0.7in). Let the content breathe.
219+
220+
**Colors**: 2–3 max. A vivid accent on a neutral background always works.
221+
222+
**Font sizes on a 1080px canvas** (1pt Typst ≈ 2px output):
223+
- Cover title: 42–50pt
224+
- Section title: 28–34pt
225+
- Body: 14–16pt
226+
- Label/tag: 9–10pt bold uppercase with tracking
227+
228+
**Carousels**: counter at bottom right of every slide (`1 / 6`, `2 / 6`, ...).
229+
230+
**Story 9:16**: center the content vertically, use larger fonts, avoid corners.
231+
232+
---
233+
234+
## Known pitfalls
235+
236+
**Grid with wide numbers/text**: `columns: (1fr, 1fr, 1fr)` causes overflow if values are wide. Use a vertical layout or `columns: (auto, 1fr)` with a generous gutter.
237+
238+
**Unavailable fonts**: always specify fallbacks (`("Chosen Font", "DejaVu Sans", "Arial")`). Check with `fc-list | grep -i "name"`. A warning is not an error: Typst uses the fallback.
239+
240+
**`#set page(fill: X)` does not create a new page if X doesn't change**: between slides with the same fill, use an explicit `#pagebreak()`.
241+
242+
**`v(1fr)` works at page level**: it splits the leftover space. If you place two of them, the space is split evenly between the two points.
243+
244+
**Logo with transparent background**: prefer SVG when possible. PNG with alpha works but requires that the slide background does not contrast badly with it.
245+
246+
**The `<` symbol in Typst content**: it is interpreted as a label opening and causes an error. Replace with words ("less than", "lower than") or use `$lt$` in math mode.
247+
248+
**`leading` is not a parameter of `text()`**: it belongs to `par()`. To control line height of a text block:
249+
```typst
250+
// WRONG — error "unexpected argument: leading"
251+
#text(size: 14pt, leading: 1.4em)[...]
252+
253+
// CORRECT — use par leading inside a block
254+
#block[
255+
#set par(leading: 0.7em)
256+
#text(size: 14pt)[...]
257+
]
258+
```
259+
260+
**`align(center + horizon)` with long text causes incorrect wordwrap**: words can fuse. Prefer to handle vertical and horizontal alignment separately, or use `block(width: 100%)` to contain the text.
261+
262+
**Editorial pattern (kicker + huge title + footer)**:
263+
```typst
264+
// magazine/newspaper style — battle-tested on 1:1
265+
#text(size: 9pt, weight: "bold", tracking: 3pt, fill: ACC)[#upper("category · section")]
266+
#v(0.08in)
267+
#line(length: 100%, stroke: 1pt + ACC)
268+
#v(0.25in)
269+
#text(size: 14pt, fill: DIM)[Eyebrow]
270+
#v(0.05in)
271+
#text(size: 60pt, weight: 900)[Big title]
272+
#v(0.05in)
273+
#text(size: 18pt, weight: "bold", fill: ACC)[Accent subtitle]
274+
#v(1fr)
275+
#line(length: 100%, stroke: 0.5pt + rgb("#333333"))
276+
#v(0.1in)
277+
#grid(columns: (1fr, 1fr, 1fr),
278+
text(size: 11pt)[Date],
279+
align(center, text(size: 11pt)[Place]),
280+
align(right, text(size: 11pt, fill: ACC)[CTA]),
281+
)
282+
```
283+
284+
---
285+
286+
## Phase 4 — Review and iteration
287+
288+
1. Compile with `typst compile` and read the resulting PNGs with the Read tool
289+
2. Show the generated slides to the user
290+
3. Ask for feedback: colors, text sizes, layout, content
291+
4. Typst recompiles in <1s: iterate quickly until satisfied

0 commit comments

Comments
 (0)