# Writing recipes for Erics Rezepte

This guide is for people and AI agents who write or edit recipes for
https://rezepte.wendland.dev. It describes the complete file format and the house style. Lists
marked as generated below reflect the current state of the site.

- Source of every recipe as Markdown: `https://rezepte.wendland.dev/<category>/<recipe>.md`,
  e.g. https://rezepte.wendland.dev/nudeln/carbonara.md
- Repository: https://forge.tionis.dev/eric/rezepte (recipes live in `recipes/<category>/<recipe>.md`)

## In short

- One Markdown file per recipe in `recipes/<category>/`, file name = URL slug
  (lowercase, `-` between words, `ae/oe/ue/ss` for umlauts), e.g.
  `recipes/nudeln/spaghetti-al-limone.md` → `/nudeln/spaghetti-al-limone/`.
- Everything is **German**: title, text, tags, frontmatter values.
- YAML frontmatter, then a `## Zubereitung` section with a numbered list of steps. The
  ingredient list, portion scaler, timers and recipe grid are all generated from the
  steps, so there is **no ingredient list** in the file.
- Exception: dishes that need no steps (a sandwich, a snack plate) list their
  ingredients under `## Zutaten` instead, see [Recipes without steps](#recipes-without-steps).
- `<!-- … -->` is a comment: kept in the file, shown nowhere, e.g. a step that wasn't
  needed so far but might be again.
- Anything after the steps is plain Markdown. Two sections are shown as boxes:
  `## Tipps` (serving ideas, variations) and `## Notiz` (a personal remark, shown like a
  handwritten note, e.g. "Mit mehr Zitrone!").

## Frontmatter

Unknown fields are an error. Put text that contains `: ` in quotes, e.g.
`beschreibung: "Reis ohne Topf: aus der Mikrowelle."`, otherwise YAML reads it as a key.

| Field | Required | Type | Meaning |
| --- | --- | --- | --- |
| `titel` | yes | text | Title of the recipe |
| `beschreibung` | no | text | One sentence shown under the title |
| `portionen` | no | number | Portions the amounts are written for. Without it, the scaler changes the amount by factor (½×, 1×, 2× …). |
| `einheit` | no | text | What a portion is, singular, default `Portion` (e.g. `Glas`, `Cupcake`, `kleiner Cocktail`). Its plural must be in the vocabulary. |
| `zeit` | no | integer | Total time in minutes |
| `arbeitszeit` | no | integer | Hands-on time in minutes |
| `tags` | no | list | Lowercase German tags, reuse existing ones (see below) |
| `quelle` | no | URL | Original recipe this one is based on |
| `bild` | no | path | Photo next to the file, e.g. `./carbonara.webp` |
| `entwurf` | no | boolean | `true` hides the recipe on the published site |
| `hinzugefuegt` | no, but please set it | date | When the recipe joined the collection, `YYYY-MM-DD`; drives "Neu in der Sammlung" |
| `von` | no | text | Who added the recipe, if not Eric (e.g. `Christina`) |
| `varianten` | no | list | Variants of the recipe, e.g. `[Schwein, Hähnchen, Rind]`; steps tagged `[Rind]` belong to one (see [Variants](#variants)) |
| `art` | no | `beilage` or `komponente` | Not a dish on its own: a side that goes next to a dish (rice, wedges, gratin) or a component that goes into one (sauce, dressing, pesto, filling). Without it the recipe is a dish. |

## Steps

Steps are the numbered list under `## Zubereitung`. Inside a step, three kinds of
markup exist:

| Markup | Meaning |
| --- | --- |
| `@Zwiebel{1}(fein hacken)` | Ingredient with amount and preparation note, both optional: `@Salz`, `@Pfeffer(frisch gemahlen)`, `@Mehl{200 g}` |
| `@?Schinken{50 g}` | Optional ingredient |
| `@Knoblauchzehe{1}\|@Knoblauchpulver{½ TL}` | Alternatives: one or the other |
| `{2 EL}` | A quantity on its own, e.g. "davon {2 EL} Saft". The unit decides what it does: durations become timers, temperatures stay as written, everything else scales with the portions. |
| `{10 min}(einkochen)` | A timer with a name, shown in the timer panel as "Schritt 2: einkochen" |
| `&Soße:` | At the very start of a step: the step belongs to the track "Soße" |
| `&Soße` | Anywhere else: the result of the track "Soße" goes into this step |

### Ingredients

- Multi-word names need the braces **directly** after the name, without a space:
  `@Garam Masala{2.5 g}`, `@Crushed Ice{}`. A single word needs none: `@Salz`.
- A `{…}` with a space before it is never part of an ingredient; it is a quantity
  on its own. So `@Salz {1 TL}` is wrong, `@Salz{1 TL}` is right.
- Names may not contain `. , ; : ! ? ( ) [ ] { } & @`.
- Notes in `(…)` cannot contain parentheses themselves; use `;` or `–` to separate
  parts: `@Kartoffeln{6}(geschält und gegart; in grobe Würfel schneiden)`.
- Notes are text: an `@` or `&` in them is not read (a build warning), so an ingredient used
  while preparing another goes into the step: `@Fleisch{450 g}(würfeln) mit @Salz würzen`.
  A quantity in braces scales with the portions there too:
  `@Wasser{100 ml}(von insgesamt {660 ml})`.
- The same ingredient may appear in several steps; amounts are summed in the ingredient
  list (`@Knoblauch{1 Zehe}` twice → "2 Zehen Knoblauch").
- Singular or plural of the name both work (`@Zwiebel{2}`, `@Zwiebeln{2}`) as long as
  the word is in the vocabulary.

### Optional ingredients and alternatives

- `@?` marks an ingredient that can be left out: `@?Sesamöl{1 TL}`, `@?Chili`. The
  ingredient list labels it "optional", and the plan's shopping list leaves it unticked.
  If the same ingredient is also needed without `?`, both share one line ("2 Zehen
  Knoblauch, davon optional: 1"), since it is bought anyway.
  The step itself shows only what is written, so keep saying it there, in the text
  ("optional mit @?Sesam bestreuen") or in the note (`@?Sesamöl{1 TL}(optional, aber sehr gut)`).
- `|` joins alternatives into one ingredient, each with its own amount and note:
  `@Knoblauchzehe{1}(fein gerieben)|@Knoblauchpulver{½ TL}`, `@Milch|@Joghurt`. The page
  shows "1 Knoblauchzehe oder ½ TL Knoblauchpulver"; in the shopping list you pick one.
  More than two work too: `@Paprikapulver|@Gewürzsumach|@Chilipulver`.
- `@?` on alternatives makes the whole choice optional: `@?Rosmarin{}|@Thymian{}`.
- Use `|` when it is a different thing to buy. A variety of the same ingredient stays in
  the note: `@Paprikapulver{1 TL}(geräuchert oder edelsüß)`.

### Amounts

- Numbers: `2`, `0,5` or `0.5`, fractions `½ ¼ ¾ ⅓ ⅔` or `1/2`, mixed `1 1/2`, ranges `3-4`.
- Units: metric (`g`, `kg`, `ml`, `cl`, `l`) are converted automatically (1000 g → 1 kg);
  all other units must be in the vocabulary (`EL`, `TL`, `Zehe`, `Bund`, `Prise`, …).
- Free text is allowed where there is no number: `@Fischsauce{ein paar Spritzer}`.
- Amounts that don't scale linearly get points after `;`, in portions: `@Wasser{660 ml; 12:
  1200 ml}` is 660 ml at the recipe's `portionen` and 1200 ml at 12 portions. Between points
  the amount runs straight, beyond them it scales proportionally from the nearest one; more
  points are allowed (`; 18: 1700 ml`). Only with `portionen`, not for ranges, and only from
  a source that says so (e.g. the packet's table); don't invent them.
- Counted ingredients without unit (`@Eier{2}`) need a vocabulary entry for their plural.

### Timers and temperatures

- Every duration in a step is a timer: `{15 min}`, `{30 s}`, `{1 h}`, ranges `{10-12 min}`.
  Also accepted: `Minuten`, `Min.`, `Std.`, `Stunden`, `Sek.`. Plain-text durations
  ("10 Minuten") produce a build warning.
- If a step has more than one timer, **name each of them** with a short phrase from the
  step: `{1 min}(Knoblauch anbraten)` … `{8 min}(einkochen)`.
- Temperatures are written as quantities and do not scale: `{200 °C}`.

### Tracks

Tracks describe work that happens in parallel (rice cooks while the sauce simmers). They
drive the recipe grid, which reads like a tree from the ingredients down to the dish.

- A step without `&Name:` continues the step before it. Simple recipes need no tracks.
- `&Name:` at the start of a step starts the track (or continues it, if it exists).
- `&Name` elsewhere in a step brings that track's result into this step.
- Every track has to end up in the finished dish: bring it in with `&Name` in a later
  step. A track that is never used, or a reference to a track that doesn't exist, is a
  build error.
- Multi-word names: `&{Rote Soße}`.
- Preheating the oven is a track of its own: `1. &Ofen: Den Ofen auf {200 °C} vorheizen.`,
  later "im &Ofen {20 min} backen".
- Name tracks after the work (`&Reis`, `&Soße`, `&Gemüse`): people cooking together see the
  name when they divide the steps among themselves ("Wer macht was?" on the recipe page).

## Variants

When a few steps differ by the main ingredient (pork, chicken or beef), the recipe has
variants instead of one file each. `varianten` in the frontmatter names them, the first is
shown by default; a step that starts with `[Name]` (or `[Name, Name]`) belongs only to those
variants, every other step to all. Several steps in a row with different tags are the
alternatives for that place; each variant numbers its own steps.

```markdown
varianten: [Schwein, Hähnchen, Rind]
…
4. [Schwein] &Fleisch: @Schweinehüfte{450 g}(würfeln) kräftig anbraten.
4. [Hähnchen] &Fleisch: @Hähnchen{450 g}(würfeln) kräftig anbraten.
4. [Rind] &Fleisch: @Rinderschulter{450 g}(würfeln) kräftig anbraten.
5. Das &Fleisch in den Topf geben …
```

The page has a switch for them; the ingredient list, grid, timers and the plan's shopping
list follow the chosen variant. Every variant has to be a complete recipe on its own (all its
tracks used). Variants that are really different dishes stay separate recipes (below).

## Recipes without steps

Some dishes need no instructions, only what goes in and how much, e.g. a sandwich. They
still get a file, so they show up in their category, the search, "Was kann ich damit
kochen?" and the plan (scaled to the portions). Instead of `## Zubereitung` they have a
`## Zutaten` section with a bulleted list, exactly one ingredient per item, with the same
markup (amounts, notes, `@?`, `|`). Set `portionen` so the plan can scale it. Any text after
the list is plain Markdown, e.g. a sentence on how to put it together.

```markdown
---
titel: Käsetoast
portionen: 1
tags: [vegetarisch, schnell]
hinzugefuegt: 2026-10-02
---

## Zutaten

- @Toastbrot{2 Scheiben}
- @Gouda{2 Scheiben}
- @?Tomate{½}(in Scheiben)
- @Butter{}|@Mayonnaise{}(zum Bestreichen)

Belegen und im Kontaktgrill goldbraun toasten.
```

## House style

- Keep the author's wording where it exists; fix obvious typos only.
- Don't invent amounts, portion counts or times. Leave them out instead.
- Put pure preparation ("Zwiebel fein hacken") into the ingredient's note rather than a
  step of its own. The page then suggests doing it while an earlier step waits (a timer of
  3 minutes or more: "Währenddessen vorbereiten"), or before step 1 ("Vorher"). Words like
  schneiden, hacken, reiben, schälen, waschen, würfeln tell preparation from a description
  ("neutral", "frisch", "laut Packung"); where they get it wrong, a `!` in front of a part of
  the note makes it preparation and a `~` makes it not: `(~geschält, TK)`,
  `(Golden Curry; !in Stücke brechen)`. Parts are separated by `;`. The markers don't show.
- Something that has to start well before it is needed (soaking rice, marinating) is a
  track of its own at the start: `1. &Reis: @Sushireis{600 g}(waschen) … {10 min}(einweichen)`.
- Variants that differ in a few steps go into one recipe (see [Variants](#variants)); a
  variant that is really another dish becomes its own recipe, linked from each other's
  `## Tipps` with a relative link, e.g. `[Carbonara](/nudeln/carbonara/)`.
- Reuse existing tags; tags describe diet (`vegetarisch`, `vegan`), effort (`schnell`,
  `einfach`), device (`mikrowelle`, `ofen`, `wok`), cuisine (`italienisch`, `asiatisch`,
  …) or main ingredient (`hähnchen`, `rind`, `fisch`, …). Don't tag the category.
- `schnell` means at most about 30 minutes in total.
- Mark what isn't a meal by itself with `art`: everything in `grundlagen` is a `komponente`,
  plain rice or potato sides are a `beilage`. They are labelled as such, left out of the
  plan's suggestions and listed separately in "Was kann ich damit kochen?". Link to them
  from the recipes that use them (`[Pesto rosso](/grundlagen/pesto-rosso/)`); their page
  then shows "Verwendet in".

## Categories (generated)

The folder is the category. A new category needs a code change, so prefer an existing one.

| Folder | Name | Recipes |
| --- | --- | --- |
| `nudeln` | Nudeln | 15 |
| `reis` | Reis | 11 |
| `kartoffeln` | Kartoffeln | 6 |
| `fleisch-fisch` | Fleisch & Fisch | 8 |
| `salate` | Salate | 2 |
| `sandwiches` | Sandwiches & Wraps | 7 |
| `desserts` | Desserts & Süßes | 4 |
| `drinks` | Drinks | 7 |
| `grundlagen` | Grundlagen | 10 |

## Tags in use (generated)

`schnell` (19), `asiatisch` (17), `vegetarisch` (15), `hähnchen` (14), `einfach` (13), `mikrowelle` (9), `ofen` (9), `italienisch` (5), `vegan` (5), `curry` (4), `dressing` (4), `rind` (4), `alkoholfrei` (3), `alkoholisch` (3), `sauce` (3), `tofu` (3), `wok` (3), `backen` (2), `fisch` (2), `auflauf` (1), `dip` (1), `frühstück` (1), `garnelen` (1), `indisch` (1), `indonesisch` (1), `kontaktgrill` (1), `mealprep` (1), `mexikanisch` (1), `schwein` (1), `sirup` (1), `thailändisch` (1), `vietnamesisch` (1)

## Vocabulary (generated)

Singular and plural forms from `recipes/vokabular.yml`. Units and counted ingredients that
are not listed produce a build warning; add them to that file as `Singular: Plural`
(words that don't change are listed twice, e.g. `EL: EL`).

Entries marked `(= …)` are another name of the same ingredient, e.g.
`Karotte: Karotten = Möhre` or, with a conversion, `Knoblauchzehe: Knoblauchzehen = 1 Zehe
Knoblauch`. The ingredient list and the shopping list sum them under the main name; the step
keeps the word as written. When a new recipe uses another name for an ingredient that is
already in the collection, prefer the existing name, or add such an entry.

Portion/Portionen, Cupcake/Cupcakes, Törtchen, Tortillapizza/Tortillapizzen, großes Glas/große Gläser, kleiner Cocktail/kleine Cocktails, Becher, Beutel, Blatt/Blätter, Bund, cm, Dose/Dosen, EL, Flasche/Flaschen, Glas/Gläser, Handvoll, Haferl, Knolle/Knollen, Kopf/Köpfe, Messerspitze/Messerspitzen, Packung/Packungen, Pck., Prise/Prisen, Scheibe/Scheiben, Segment/Segmente, Schote/Schoten, Spalte/Spalten, Spritzer, Stange/Stangen, Stiel/Stiele, Streifen, Stück, Tasse/Tassen, Teil/Teile, TL, Tropfen, Würfel, Zehe/Zehen, Zweig/Zweige, Ajitsuke-Ei/Ajitsuke-Eier, Apfel/Äpfel, Gurke/Gurken, Karotte/Karotten (= Möhre), Ananasscheibe/Ananasscheiben, Anchovisfilet/Anchovisfilets, Avocado/Avocados, Baguette/Baguettes, Brühwürfel, Chilischote/Chilischoten, Cocktailkirsche/Cocktailkirschen, Ei/Eier, Eigelb, Frühlingszwiebel/Frühlingszwiebeln, Hähnchenbrustfilet/Hähnchenbrustfilets, Hähnchenunterschenkel, Kartoffel/Kartoffeln, Knoblauchzehe/Knoblauchzehen (= 1 Zehe Knoblauch), Limette/Limetten, Mini-Gurke/Mini-Gurken, Möhre/Möhren, Orange/Orangen, Papierförmchen, Paprika, Romana-Salatherz/Romana-Salatherzen, Peperoni, Rinderhüftsteak/Rinderhüftsteaks, Schalotte/Schalotten, Schweinekotelett/Schweinekoteletts, Strauchtomate/Strauchtomaten, Tomate/Tomaten, Tortilla/Tortillas, Tortilla-Wrap/Tortilla-Wraps (= Tortillafladen), Tortillafladen, Zitrone/Zitronen, Bio-Limette/Bio-Limetten, Bio-Zitrone/Bio-Zitronen, gelbe Paprika, grüne Paprika, gelbe Cocktailtomate/gelbe Cocktailtomaten, rote Cocktailtomate/rote Cocktailtomaten, rote Paprika, rote Zwiebel/rote Zwiebeln, Zuckerschote/Zuckerschoten, Zwiebel/Zwiebeln, Balsamico Bianco (= heller Balsamico), weißer Balsamico (= heller Balsamico), Basilikumblättern (= Basilikum), Korianderblättern (= Koriander), Cherrytomate/Cherrytomaten (= Kirschtomate), Kirschtomate/Kirschtomaten, Chilisoße (= Chilisauce), Eiswürfeln (= Eiswürfel), Hähnchenbrust (= Hähnchenbrustfilet), Hoisinpaste (= Hoisinsauce), Muskatnuss/Muskatnüsse (= Muskat), Pinienkernen (= Pinienkerne), Schlagsahne (= Sahne), Speckwürfeln (= Speckwürfel), Sriracha-Sauce (= Sriracha), Sushi-Reis (= Sushireis), Worcestershiresauce (= Worcestersauce)

## Example

`recipes/nudeln/spaghetti-al-limone.md`, with tracks, named timers, notes and a quantity on
its own:

```markdown
---
titel: Spaghetti al Limone
beschreibung: Spaghetti in Zitronen-Brühe mit Parmesan, Ei und Petersilie.
portionen: 2
tags: [italienisch]
hinzugefuegt: 2022-03-22
---

## Zubereitung

1. &Nudeln: Wasser für die Spaghetti aufkochen.
2. &Sauce: @Brühwürfel{1} in @Wasser{250 ml}(warm) auflösen. In einem kleinen Topf @Olivenöl{2 EL} erhitzen, @Knoblauchzehe{1}(schälen, fein hacken) zugeben und bei niedriger Temperatur {1 min}(Knoblauch anbraten) anbraten. Mit der Brühe ablöschen, {4 EL} Saft von @Bio-Zitrone{1}(heiß abwaschen, Schale abreiben, 2 Scheiben beiseitelegen, Saft auspressen) zugeben und bei mittlerer Temperatur ca. {8 min}(einkochen) etwa auf die Hälfte einkochen lassen.
3. &Nudeln: @Spaghetti{250 g} mit @Salz{1 EL} in das kochende Wasser geben und ca. {8 min} bissfest garen.
4. &Ei-Käse-Mischung: In einer Schale @Parmesan{25 g}(fein reiben) mit @Eier{2} vermengen.
5. &Sauce: Die eingekochte Brühe vom Herd nehmen und {1 TL} Zitronenschale, @Zucker{1 TL} sowie @Butter{2 EL} zugeben.
6. &Nudeln: Die Spaghetti abgießen und zurück in den Topf geben. &Ei-Käse-Mischung, eingekochte &Sauce sowie @Petersilie{1 Bund}(ca. 50 g; einige Spitzen zum Dekorieren beiseitelegen, die übrigen Blätter abzupfen und fein hacken) zügig unterheben und alles gut vermengen. Nach Geschmack mit @Pfeffer würzen.
7. Die Spaghetti auf Tellern anrichten und mit den Zitronenscheiben und Petersilienspitzen dekorieren.
```

## Checking and handing in

The build rejects what it can't read and warns about the rest; fix both. To check a file
the same way before:

- In the repository: `pnpm build`, or for one file
  `pnpm cli check recipes/<category>/<slug>.md --vocabulary recipes/vokabular.yml`.
- Without it: `node rezepte.mjs check <file>` with the CLI from https://rezepte.wendland.dev/rezepte.mjs
  (Node 22+), which also takes a changed vocabulary with `--vocabulary <file>`.
- In the site's editor (`/bearbeiten/`), the preview shows the same errors and warnings.

With repository access, commit the file (and the vocabulary) to `main`. Without it, put
it into a kitchen as a draft (`node rezepte.mjs draft put <category>/<slug> <file>`, with
the kitchen's invitation in `REZEPTE_KUECHE`); a person publishes it in the editor.

## Checklist

- [ ] File in `recipes/<category>/<slug>.md`, slug lowercase with `-`.
- [ ] Frontmatter has `titel` and `hinzugefuegt`; no fields besides the ones listed above.
- [ ] Sides have `art: beilage`, sauces, dressings and other components `art: komponente`.
- [ ] Steps under `## Zubereitung` as a numbered list; no separate ingredient list (except
      `## Zutaten` in a recipe without steps).
- [ ] Optional ingredients marked with `@?`, alternatives joined with `|`.
- [ ] Every ingredient marked with `@`, multi-word names with `{}` directly after them.
- [ ] Every duration is a timer; steps with several timers have named timers.
- [ ] Every track started with `&Name:` is used later with `&Name`.
- [ ] Counted ingredients and non-metric units are in the vocabulary.
- [ ] Nothing invented; wording kept. Say which steps had to be added or reworded.
- [ ] The check above reports no errors and no warnings.
