Graduately/content/README.md
FBukovina f7470d333c Add the Next.js web app and shared lesson content.
Move lessons out of the C sources into JSON so the web client can use the same material as the desktop app, and point release builds at desktop/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 16:06:15 +02:00

80 lines
3.4 KiB
Markdown

# content/
Shared lesson content for the whole project. One folder, one file per lesson —
instead of lesson text being buried inside each unit's C source file.
`schema.ts` is the single description of every shape here. Both the extractor in
`tools/` and the web app in `web/` type against it, so a change there shows up as
a type error on both sides.
## Layout
```
content/
schema.ts TypeScript types for everything below
themes.json 10 palettes, dark + light (24 colors each)
subjects.json the 13 subjects, their i18n keys and routes
paths.json serpentine unit paths per subject
manifest.json subject and lesson ids
i18n/
ui.json UI chrome strings, keyed by identifier
content.json lesson strings, keyed by the Czech text
lessons/
deutsch/unit-01.json … unit-03.json
networks/unit-01.json … unit-27.json
hardware/unit-01.json … unit-03.json
czech/mluvnice.json, orwell-1984.json, fuks-spalovac-mrtvol.json
```
`paths.json` holds one entry per path. A subject usually has exactly one, but
Czech has two: the top-level area picker and the `reading` branch listing the
books, distinguished by the `branch` field.
## Lesson shape
A lesson holds theory `slides` and a list of `tasks`. Each task is one bubble on
the unit map and contains `items` of a single `kind`:
| kind | what it is |
| ---- | ---------- |
| `choice` | multiple choice, `correct` indexes `options` |
| `fill` | sentence split into `segments` with a gap between each; `input` picks dropdown or text |
| `dialogue` | dialogue lines with one missing word each |
| `assembly` | build a sentence from scrambled words |
| `assign` | sort labelled cards into named groups |
| `free` | open question with a model answer |
| `typed` | type the answer, `answers` lists accepted spellings |
| `fields` | one prompt, several labelled fields (Czech grammatical categories) |
| `reveal` | self-assessed: write an answer, then compare it with the model |
| `hangman` | guess the word letter by letter |
| `profile` | "Steckbrief" line with a model answer |
| `table` | editable grid, some cells prefilled |
| `subnet` | VLSM subnetting worksheet |
`fill` absorbs every gap variant in the desktop app. A few flags steer it:
`letters` marks gaps holding missing letters inside a word rather than whole
words, `gapPool` picks the task's `pool` or `altPool` per gap (the "Ordne zu"
question-word and answer columns), and `heading` starts a labelled group of rows.
Two conventions carried over from the desktop app:
- **Pipe-separated answers.** `"bin|heiße"` means either spelling is accepted.
- **Lenient comparison.** Answers are matched after lowercasing, expanding
`ä ö ü ß` to `ae oe ue ss`, dropping accents and ignoring punctuation, so Czech
diacritics and German umlauts are optional. See `web/src/lib/answers.ts`.
Titles come either as a literal `title` or as a `titleKey` to look up in the
i18n tables, because German unit names are identical in both languages while
network lesson names are translated.
## Regenerating
The C sources in `desktop/` are still where content is authored, so this folder
is a generated one-way export:
```bash
npm run content # from the repo root, or: node tools/build-content.mjs
```
The extractor parses the C initializers directly and throws on any shape it does
not recognise, so a bad export fails loudly rather than emitting empty lessons.