Add shared card schemas layouts and renderer

This commit is contained in:
mpabi
2026-07-15 08:41:20 +02:00
commit 65299458ed
19 changed files with 7786 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
name: Check card layouts
on:
push:
branches:
- main
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check schemas and render example
run: |
python3 -m pip install -r requirements-dev.txt
CARD_LAYOUTS_REQUIRE_JSONSCHEMA=1 python3 scripts/check_repository.py
+12
View File
@@ -0,0 +1,12 @@
__pycache__/
*.py[cod]
.pytest_cache/
examples/card/build/
examples/card/web/
examples/card/doc/*.aux
examples/card/doc/*.fdb_latexmk
examples/card/doc/*.fls
examples/card/doc/*.log
examples/card/doc/*.out
examples/card/doc/*.pdf
+14
View File
@@ -0,0 +1,14 @@
.PHONY: check render-example pdf-example
PYTHON ?= python3
EXAMPLE_CARD := examples/card
EXAMPLE_TEX_DIR := $(EXAMPLE_CARD)/build/tex
render-example:
$(PYTHON) tools/render_card.py $(EXAMPLE_CARD)
pdf-example: render-example
cd $(EXAMPLE_TEX_DIR) && latexmk -pdf -interaction=nonstopmode -halt-on-error main.tex
check:
$(PYTHON) scripts/check_repository.py
+129
View File
@@ -0,0 +1,129 @@
# card-layouts
Wspólne źródło schematów, layoutów i generatora kart pracy używanych przez
serie edukacyjne. Jeden plik `card_source.json` opisuje treść oraz mapowanie
edukacyjne, a `tools/render_card.py` generuje z niego równolegle LaTeX i HTML.
Repozytorium rozdziela trzy warstwy:
1. **Treść karty**`card_source.json`: cele, efekty nauczania, kryteria,
drzewka marginesowe, kroki oraz zadania.
2. **Layout**`templates/*.json`: geometria strony, role marginesów,
komponenty i wybór emitera TeX/HTML.
3. **Seria**`series.json`: kolejność kart, repozytoria, wersje i kontrakt
uruchamiania zadań.
## Zawartość
```text
card-layouts/
├── schemas/
│ ├── card-source.schema.json
│ ├── card-template.schema.json
│ └── series.schema.json
├── templates/
│ ├── karta-klasyczna.json
│ ├── karta-5a.json
│ └── karta-5b.json
├── tools/render_card.py
├── docs/
│ ├── CARD.md
│ └── SERIES.md
└── examples/
├── card/
└── series/
```
## Layouty
| Layout | Przeznaczenie | Marginesy | Emitery |
|---|---|---|---|
| `karta-klasyczna` | elastyczna karta sekcyjna | dwa panele drzewek | TeX + HTML |
| `karta-5a` | wielostronicowa karta A4 landscape | TECH po lewej, OG po prawej | TeX + HTML |
| `karta-5b` | referencyjna karta A4 portrait | gołe drzewka TECH/OG | TeX + HTML |
`karta-5b` jest obecnym layoutem referencyjnym. Używa geometrii
`0,55 / 2,05 / 0,55 / 14,70 / 0,55 / 2,05 / 0,55 cm`; lewy margines
zawiera efekty zawodowe `TECH`, a prawy efekty ogólne `OG`.
## Szybki start
Wygenerowanie przykładowej karty:
```bash
make render-example
```
Powstaną:
```text
examples/card/build/tex/main.tex
examples/card/build/html/index.html
examples/card/build/html/style.css
```
Złożenie kontrolnego PDF-u wymaga `latexmk` i pakietów LaTeX używanych przez
generator:
```bash
make pdf-example
```
Pełna kontrola repozytorium:
```bash
make check
```
Walidacja względem JSON Schema jest wykonywana, gdy dostępny jest pakiet
`jsonschema`; CI instaluje go z `requirements-dev.txt`.
Generator można też wywołać bezpośrednio dla dowolnego katalogu karty:
```bash
python3 tools/render_card.py /sciezka/do/karty
python3 tools/render_card.py /sciezka/do/karty --template templates/karta-5b.json
```
Katalog karty musi zawierać `json/card_source.json`. Ścieżki wyjściowe
określa obiekt `generated` w tym pliku.
## Model edukacyjny
Źródłem marginesów nie jest luźny tekst. Relacje są jawne:
```text
WE (wymaganie edukacyjne)
└── EN/EK (efekt nauczania lub efekt kształcenia)
└── KW (kryterium weryfikacji)
```
Każda sekcja i każdy krok mogą wskazywać te same drzewka przez `tree_refs`.
Dzięki temu TeX i HTML pokazują identyczne powiązanie treści z efektami.
Szczegóły modelu karty opisuje [docs/CARD.md](docs/CARD.md), a manifestu
serii [docs/SERIES.md](docs/SERIES.md).
## Słowniki podstaw programowych
Generator może rozszerzać skróty z zewnętrznych słowników JSON. Karta podaje
ich katalog przez:
```json
{
"dictionary_refs": {
"pp_json_root": "../../pp/json"
}
}
```
Brak słowników nie blokuje generowania, jeśli karta zawiera kompletne pola
`display` i `text` we własnych drzewkach.
## Zasada zmian
- zmiana treści należy do `card_source.json`;
- zmiana geometrii i stylu należy do `templates/*.json` oraz emitera;
- wygenerowanych plików nie edytujemy ręcznie;
- nowa wersja layoutu wymaga aktualizacji numeru `version`, przykładu i testu
obu wyjść.
+81
View File
@@ -0,0 +1,81 @@
# Opis repozytorium pojedynczej karty
Każda karta jest osobnym repozytorium lub samodzielnym katalogiem. Jej źródłem
prawdy jest `json/card_source.json`; pliki TeX i HTML są artefaktami
generowanymi.
## Minimalne drzewo
```text
moja-karta/
├── README.md
├── json/card_source.json
├── doc/
├── web/
└── src/
```
## Obowiązkowe warstwy danych
### Metadane
Obiekt `card` identyfikuje serię, numer, wersję, UUID, autora i status.
Obiekt `generated` wskazuje ścieżki wyjściowe TeX/HTML/CSS. Pole `template`
wybiera layout.
### Efekty i kryteria
- `educational_requirements` — wymagania `WE`;
- `learning_effects` — efekty `EN` lub `EK`;
- `assessment_criteria` — mierzalne kryteria `KW`;
- `educational_requirements.*.learning_tree` — połączenia renderowane na
marginesach.
Każdy wpis `learning_tree.ogolne[]` lub `learning_tree.zawodowe[]` ma stabilne
`tree_id`, odwołanie `effect_ref`, etykietę `display` oraz co najmniej jedno
powiązane `KW`.
### Sekcje i kroki
Pole `sections[]` porządkuje treść. Kroki `sections[].steps[]` mają:
- `id` i `title`;
- `tree_refs` wskazujące drzewka z marginesów;
- opcjonalne odwołania do wymagań, efektów, kryteriów, równań i figur.
Layouty 5a/5b obsługują również:
- `model_block.steps` — kroki modelowania;
- `code_block.blocks[].web_steps` — interaktywne kroki HTML;
- `hardware_procedure[]` — tabela `step/action/condition` w TeX i HTML.
### Zadania
`tasks` jest słownikiem zadań, a `tasks_order` określa ich kolejność. Każde
zadanie posiada polecenie `prompt_tex`, kryterium zaliczenia i odwołania do
modelu edukacyjnego.
## Zalecany README karty
README konkretnej karty powinien zawierać:
1. nazwę, numer i miejsce w serii;
2. cel oraz rezultat obserwowalny;
3. mapowanie do źródeł i efektów nauczania;
4. listę kroków/zadań;
5. polecenia generowania i testowania;
6. warunek `PASS`;
7. informację, które pliki są źródłowe, a które generowane.
Gotowy przykład znajduje się w `examples/card/README.md`.
## Generowanie
Z katalogu repozytorium `card-layouts`:
```bash
python3 tools/render_card.py /sciezka/do/mojej-karty
```
Generator najpierw sprawdza spójność identyfikatorów WE/EN/EK/KW, kroków,
zadań i odwołań, a dopiero potem zapisuje oba formaty.
+40
View File
@@ -0,0 +1,40 @@
# Opis repozytorium i README serii
Seria jest uporządkowaną ścieżką kart. Jej manifest nie kopiuje treści kart;
przechowuje kolejność, adres repozytorium, gałąź, wersję dokumentu i kontrakt
uruchamiania zadań.
## Manifest `series.json`
Najważniejsze pola:
- `version` — wersja formatu manifestu;
- `series`, `org`, `title` — identyfikacja serii;
- `default_branch` — domyślna gałąź kart;
- `cards[]` — uporządkowane wpisy kart;
- `cards[].build` — cele budowania i czyszczenia;
- `cards[].tasks[]` — cele uruchamiane przez narzędzia ucznia.
Schemat znajduje się w `schemas/series.schema.json`, a minimalny manifest w
`examples/series/series.json`.
## Zalecany README serii
README serii powinien odpowiadać na pięć pytań:
1. Jaki jest rezultat całej ścieżki?
2. Jakie są wymagania wejściowe?
3. W jakiej kolejności realizujemy karty i dlaczego?
4. Z jakich książek, dokumentacji i przykładów korzystamy?
5. Jaki wspólny kontrakt `PASS` obowiązuje wszystkie karty?
Zalecana tabela mapuje:
```text
karta → lekcja → źródło → eksperyment → rezultat → stan
```
README nie zastępuje `series.json`: Markdown opisuje narrację i decyzje,
natomiast JSON jest kontraktem maszynowym dla narzędzi.
Przykład znajduje się w `examples/series/README.md`.
+36
View File
@@ -0,0 +1,36 @@
# demo-01 / pierwszy krok
Karta `01/02` demonstracyjnej serii `demo`. Pokazuje minimalny kontrakt
generatora: jedno wymaganie edukacyjne, efekty ogólne i zawodowe na
marginesach, kryterium weryfikacji oraz kroki obecne w TeX i HTML.
## Cel
Uczeń uruchamia generator, rozpoznaje pliki źródłowe oraz potrafi wskazać,
który krok realizuje dane wymaganie i kryterium.
## Kroki
1. Sprawdź `json/card_source.json`.
2. Wygeneruj TeX i HTML.
3. Potwierdź obecność marginesów `TECH` i `OG`.
4. Zapisz dowód wykonania polecenia kontrolnego.
## Generowanie
Z katalogu głównego repozytorium `card-layouts`:
```bash
make render-example
make pdf-example
```
## Warunek PASS
- generator kończy pracę kodem `0`;
- istnieją `build/tex/main.tex`, `build/html/index.html` i `style.css`;
- oba wyjścia zawierają tytuł karty i identyfikatory drzewek;
- PDF składa się bez błędu.
Źródłem prawdy jest `json/card_source.json`. Plików w `build/` nie edytujemy
ręcznie.
+586
View File
@@ -0,0 +1,586 @@
{
"$schema": "../../../schemas/card-source.schema.json",
"schema": "esc-card-source.v1",
"card": {
"id": "demo-01",
"series": "demo",
"number": "01",
"count": "02",
"slug": "pierwszy-krok",
"title": "DEMO-01: Jedno źródło, dwa formaty",
"topic": "Generowanie TeX i HTML z mapą efektów nauczania",
"project": "System kart pracy",
"subject": "Zajęcia projektowe",
"level": "przykład techniczny",
"dominant": "informatyka i kompetencje techniczne",
"revision_date": "15.07.2026",
"status": "Przykład",
"version": "v0.1",
"uuid": "00000000-0000-4000-8000-000000000001",
"author": "Zespół edukacyjny",
"year": "2026"
},
"generated": {
"tex": "build/tex/main.tex",
"html": "build/html/index.html",
"html_css": "build/html/style.css"
},
"template": "templates/karta-5b.json",
"front_page_break": false,
"metric_fill_fields": [
{
"label": "Uczeń",
"kind": "fill_in_line"
},
{
"label": "Klasa · data",
"kind": "fill_in_line"
}
],
"side_margin_tree_layout": {
"columns": [
{
"id": "zawodowe",
"label": "TECH",
"side": "left",
"tree": "WE -> EK -> KW",
"description": "Efekty techniczne po lewej stronie."
},
{
"id": "ogolne",
"label": "OG",
"side": "right",
"tree": "WE -> EN -> KW",
"description": "Efekty ogólne po prawej stronie."
}
]
},
"learning_effects": {
"DEMO.EN01": {
"bloom_level": "Zastosowanie",
"label": "Generowanie",
"text": "Uczeń generuje TeX i HTML z jednego pliku JSON.",
"assessment_criteria": [
"DEMO.KW01"
]
},
"DEMO.EK01": {
"bloom_level": "Analiza",
"label": "Spójność",
"text": "Uczeń rozpoznaje wspólny model danych i layoutu.",
"assessment_criteria": [
"DEMO.KW01"
]
}
},
"assessment_criteria": {
"DEMO.KW01": {
"text": "Generator kończy pracę kodem 0, a oba wyjścia zawierają tytuł, kroki i drzewka marginesowe.",
"learning_effects": [
"DEMO.EN01",
"DEMO.EK01"
]
}
},
"educational_requirements": {
"DEMO.WE01": {
"text": "Wygenerowanie spójnej karty pracy z jednego źródła.",
"label": "Jedno źródło",
"learning_effects": [
"DEMO.EN01",
"DEMO.EK01"
],
"learning_tree": {
"schema": "we-learning-tree.v1",
"policy": "Każdy efekt ma co najmniej jedno mierzalne kryterium.",
"ogolne": [
{
"effect_ref": "DEMO.EN01",
"display": "EN IP WO 01.01.01",
"source": "IP",
"official": "01.01",
"local": "01",
"text": "Uczeń generuje TeX i HTML z jednego pliku JSON.",
"kind": "WO",
"tree_id": "DEMO.WE01.OG.IP.01.01.01",
"kw": [
{
"criterion_ref": "DEMO.KW01",
"display": "KW IP WS 01.01.01",
"source": "IP",
"kind": "WS",
"official": "01.01",
"local": "01",
"text": "Oba artefakty zawierają te same metadane i kroki."
}
]
}
],
"zawodowe": [
{
"effect_ref": "DEMO.EK01",
"display": "EK INF04 01.01.01",
"source": "I4",
"official": "01.01",
"local": "01",
"text": "Uczeń rozpoznaje wspólny model danych i layoutu.",
"kind": "EK",
"tree_id": "DEMO.WE01.TECH.I4.01.01.01",
"kw": [
{
"criterion_ref": "DEMO.KW01",
"display": "KW INF04 01.01.01.01",
"source": "I4",
"kind": "KW",
"official": "01.01.01",
"local": "01",
"text": "Uczeń wskazuje źródło treści, layout i oba artefakty."
}
]
}
]
}
}
},
"sections": [
{
"title": "Uruchomienie generatora",
"content_kind": "prose",
"content_tex": "Jedno źródło JSON jest renderowane do dwóch formatów.",
"educational_requirement_refs": [
"DEMO.WE01"
],
"learning_effect_refs": [
"DEMO.EN01",
"DEMO.EK01"
],
"assessment_criterion_refs": [
"DEMO.KW01"
],
"area_tree_refs": [
"DEMO.WE01.OG.IP.01.01.01",
"DEMO.WE01.TECH.I4.01.01.01"
],
"steps": [
{
"id": "K1",
"title": "Otwórz card_source.json i odczytaj metadane.",
"tree_refs": [
"DEMO.WE01.OG.IP.01.01.01",
"DEMO.WE01.TECH.I4.01.01.01"
],
"educational_requirement_refs": [
"DEMO.WE01"
],
"learning_effect_refs": [
"DEMO.EN01",
"DEMO.EK01"
],
"assessment_criterion_refs": [
"DEMO.KW01"
]
},
{
"id": "K2",
"title": "Uruchom generator i sprawdź oba wyjścia.",
"tree_refs": [
"DEMO.WE01.OG.IP.01.01.01",
"DEMO.WE01.TECH.I4.01.01.01"
],
"educational_requirement_refs": [
"DEMO.WE01"
],
"learning_effect_refs": [
"DEMO.EN01",
"DEMO.EK01"
],
"assessment_criterion_refs": [
"DEMO.KW01"
]
}
]
}
],
"tasks": {
"task01": {
"prompt_tex": "Wygeneruj TeX i HTML oraz porównaj tytuł i identyfikatory efektów.",
"criterion": "Oba pliki istnieją i pochodzą z tego samego źródła.",
"educational_requirement_refs": [
"DEMO.WE01"
],
"learning_effect_refs": [
"DEMO.EN01",
"DEMO.EK01"
],
"assessment_criterion_ref": "DEMO.KW01",
"links": {
"equations": [],
"figures": []
}
}
},
"tasks_order": [
"task01"
],
"mission": "Wygeneruj kartę pracy do TeX i HTML z jednego źródła, a następnie potwierdź, że oba formaty pokazują tę samą mapę efektów i te same kroki.",
"objectives": [
"Rozpoznasz rozdzielenie treści, layoutu i manifestu serii.",
"Wygenerujesz TeX i HTML jednym poleceniem.",
"Powiążesz krok z wymaganiem, efektem i kryterium."
],
"objective_refs": [
{
"tech": "01",
"og": "01"
},
{
"tech": "01",
"og": "01"
},
{
"tech": "01",
"og": "01"
}
],
"agenda": [
{
"time": "0--5 min",
"work": "Odczyt źródła i drzewek edukacyjnych."
},
{
"time": "5--10 min",
"work": "Generowanie TeX i HTML."
},
{
"time": "10--15 min",
"work": "Porównanie rezultatów i zapis PASS."
}
],
"hw_gate": {
"title": "Bramka wejścia",
"criteria": [
{
"check": "Python 3 jest dostępny.",
"evidence": "python3 --version"
},
{
"check": "Źródło JSON jest poprawne składniowo.",
"evidence": "python3 -m json.tool json/card_source.json"
}
]
},
"tech_stack": {
"software": [
{
"name": "Python",
"use": "walidacja i generowanie"
},
{
"name": "LaTeX",
"use": "skład PDF"
},
{
"name": "HTML/CSS",
"use": "wersja ekranowa i druk z przeglądarki"
}
],
"hardware": [
{
"name": "Komputer",
"use": "uruchomienie przykładu"
}
]
},
"theory_refs": {
"intro": "Treść i layout są osobnymi warstwami. Identyfikatory WE, EN/EK i KW tworzą graf powiązań, który renderer umieszcza na marginesach.",
"known_refs": [],
"new_concepts": [
{
"term": "źródło prawdy",
"definition": "Plik, z którego odtwarzamy wszystkie artefakty bez ręcznych poprawek."
},
{
"term": "layout",
"definition": "Kontrakt geometrii, komponentów i emiterów niezależny od treści karty."
}
]
},
"math_block": {
"title": "Kontrola liczby wyjść",
"tasks": [
{
"prompt": "Policz formaty generowane z jednego źródła.",
"equation_tex": "N_{wyjsc} = N_{TeX} + N_{HTML} = 1 + 1 = 2",
"answer_hint": "Dwa formaty, jedno źródło."
}
]
},
"parts": {
"a": {
"title": "CZĘŚĆ A -- model",
"body": "Sprawdź identyfikatory i zależności przed generowaniem."
},
"b": {
"title": "CZĘŚĆ B -- weryfikacja",
"body": "Porównaj TeX, HTML i wynik PDF."
}
},
"model_block": {
"title": "Model danych",
"notebook": "nie dotyczy",
"output_constants": "json/card_source.json",
"steps": [
"Odczytaj metadane karty.",
"Przejdź po relacji WE -> EN/EK -> KW.",
"Sprawdź tree_refs dla każdego kroku."
],
"constants": [
{
"name": "SOURCE_COUNT",
"value": "1"
},
{
"name": "OUTPUT_COUNT",
"value": "2"
}
]
},
"code_block": {
"title": "Do wykonania: render przykładu",
"context": "Pracujemy wyłącznie na plikach przykładowych w tym repozytorium.",
"blocks": [
{
"title": "Bloczek 1 -- GENERATOR",
"slug": "generator",
"goal": "Wytworzyć oba formaty i sprawdzić ich spójność.",
"context": "Kroki są zapisywane w JSON i emitowane do interaktywnego HTML.",
"web_steps": [
"Uruchom make render-example.",
"Otwórz build/html/index.html.",
"Sprawdź build/tex/main.tex.",
"Potwierdź obecność TECH, OG i kroku K2."
],
"web_commands": [
{
"label": "Render",
"command": "make render-example"
},
{
"label": "Kontrola",
"command": "make check"
}
],
"web_files": [
"json/card_source.json",
"build/tex/main.tex",
"build/html/index.html"
],
"web_evidence": [
"Polecenie kończy się kodem 0.",
"Oba wyjścia zawierają tytuł karty."
],
"web_safety": [
"Nie edytuj ręcznie plików w build/."
],
"columns": [
{
"title": "Artefakty",
"items": [
"build/tex/main.tex",
"build/html/index.html",
"build/html/style.css"
]
},
{
"title": "Procedura",
"items": [
"render",
"kontrola marginesów",
"kontrola kroków"
]
},
{
"title": "Uczeń oddaje",
"items": [
"wynik PASS",
"krótkie porównanie obu formatów"
]
}
],
"pass_condition": "TeX i HTML istnieją oraz zawierają te same metadane."
}
]
},
"container_test": {
"title": "Test bez sprzętu",
"commands": [
"make render-example",
"make check"
],
"pass_condition": "Wszystkie kontrole kończą się kodem 0."
},
"bom": [
{
"item": "Python 3",
"check": "Interpreter dostępny."
},
{
"item": "latexmk",
"check": "Wymagany tylko dla PDF."
}
],
"safety_rules": [
{
"title": "Jedno źródło",
"body": "Nie poprawiaj osobno TeX i HTML; popraw card_source.json lub layout."
},
{
"title": "Stabilne identyfikatory",
"body": "Nie zmieniaj tree_id po opublikowaniu karty bez migracji odwołań."
}
],
"reference_settings": [
{
"test": "layout",
"dps": "karta-5b",
"xy": "TeX + HTML"
}
],
"figures": [],
"connection_section": {
"title": "Przepływ danych: JSON -> renderer -> TeX/HTML",
"intro": "Źródło treści i layout są wczytywane osobno, a następnie przekazywane do dwóch emiterów.",
"pin_table": [
{
"rp2350": "card_source.json",
"isolator_a": "walidacja",
"isolator_b": "emiter",
"xy": "main.tex / index.html"
}
],
"parameters": [
"Treść jest niezależna od geometrii.",
"Oba emitery korzystają z przygotowanego modelu danych."
],
"warnings": [
"Ręczna zmiana artefaktu znika przy kolejnym generowaniu."
],
"source_note": "Repozytorium card-layouts jest źródłem generatora, schematów i layoutów."
},
"hardware_procedure": [
{
"step": "1",
"action": "Uruchom make render-example.",
"condition": "Generator zwraca kod 0."
},
{
"step": "2",
"action": "Otwórz HTML i sprawdź marginesy TECH/OG oraz kroki.",
"condition": "Widoczne są oba drzewka i lista kroków."
},
{
"step": "3",
"action": "Złóż PDF poleceniem make pdf-example.",
"condition": "Powstaje main.pdf bez błędu LaTeX."
}
],
"observation_table": {
"title": "Karta kontroli artefaktów",
"columns": [
"Artefakt",
"Oczekiwany element",
"Wynik"
],
"rows": [
{
"cells": [
"HTML",
"tytuł, TECH, OG, kroki",
""
]
},
{
"cells": [
"TeX/PDF",
"tytuł, TECH, OG, procedura",
""
]
}
]
},
"troubleshooting": [
{
"symptom": "Brak layoutu",
"hypothesis": "Błędna ścieżka template.",
"action": "Sprawdź pole template i katalog templates/.",
"result": ""
},
{
"symptom": "Błąd tree_ref",
"hypothesis": "Krok wskazuje nieistniejące drzewko.",
"action": "Porównaj tree_refs z educational_requirements.*.learning_tree.",
"result": ""
}
],
"analysis_tasks": [
"Wyjaśnij, dlaczego treść i layout są osobnymi plikami.",
"Wskaż relację między DEMO.WE01, DEMO.EN01 i DEMO.KW01.",
"Porównaj elementy obecne w TeX i HTML."
],
"team_decision": {
"title": "Decyzja zespołu",
"questions": [
"Który layout wybieramy?",
"Jakie były alternatywy?",
"Jak potwierdziliśmy oba formaty?",
"Który commit dokumentuje decyzję?"
]
},
"git_gate": {
"title": "Brama wyjścia",
"criteria": [
{
"criterion": "JSON przechodzi walidację generatora.",
"evidence": "make check"
},
{
"criterion": "TeX i HTML zostały wygenerowane.",
"evidence": "examples/card/build/"
},
{
"criterion": "PDF składa się bez błędu.",
"evidence": "main.pdf"
}
]
},
"session_record": {
"title": "Rekord pracy",
"files": [
{
"path": "README.md",
"purpose": "opis wyniku i poleceń"
}
],
"timeline_sources": [
"git log --oneline",
"wynik make check"
]
},
"ai_rules": {
"allowed": [
"wyjaśnienie błędu walidacji",
"review spójności identyfikatorów"
],
"forbidden": [
"fabrykowanie wyniku PASS",
"ręczna poprawa wygenerowanego pliku zamiast źródła"
],
"student_owns": [
"treść kroków",
"mapowanie efektów",
"wynik kontroli"
],
"log_path": "ai-log.md"
},
"references": []
}
+27
View File
@@ -0,0 +1,27 @@
# Seria demo
Minimalna seria pokazująca, jak opisywać kolejność kart niezależnie od ich
layoutu i generatora.
## Rezultat ścieżki
Po przejściu serii uczeń potrafi wygenerować kartę z JSON, odczytać marginesy
efektów nauczania oraz wykonać kroki zakończone jednoznacznym `PASS`.
## Mapa
| Karta | Temat | Rezultat | Stan |
|---:|---|---|---|
| 01 | źródło JSON i layout 5b | TeX, HTML i PDF z jednego źródła | gotowa |
| 02 | własna karta | poprawny model WE → EN/EK → KW | planowana |
## Kontrakt wspólny
Każda karta:
- posiada `json/card_source.json` zgodny ze schematem;
- generuje TeX i HTML tym samym narzędziem;
- ma jawne kroki oraz kryterium `PASS`;
- nie wymaga ręcznej edycji artefaktów wygenerowanych.
Maszynowym opisem tej kolejności jest `series.json`.
+41
View File
@@ -0,0 +1,41 @@
{
"$schema": "../../schemas/series.schema.json",
"version": 1,
"series": "demo",
"org": "edu-demo",
"title": "Demonstracja generatora kart",
"default_branch": "deploy",
"cards": [
{
"id": "01",
"repo": "lab-demo-card-layout",
"branch": "deploy",
"title": "demo / pierwszy krok",
"summary": "Jedno źródło JSON generujące TeX i HTML z marginesami efektów nauczania.",
"kind": "lab",
"status": "example",
"area": "demo",
"card_series": "demo",
"card_number": "01",
"card_count": "02",
"slug": "pierwszy-krok",
"version": "v0.1",
"document_uuid": "00000000-0000-4000-8000-000000000001",
"pdf": "doc/pdf/demo-01-pierwszy-krok-v0.1.pdf",
"build": {
"driver": "make",
"all_target": "check",
"clean_target": "clean"
},
"tasks": [
{
"id": "task01",
"title": "Wygeneruj oba formaty",
"target": "render",
"source": "json/card_source.json",
"build_dir": "build"
}
]
}
]
}
+1
View File
@@ -0,0 +1 @@
jsonschema>=4,<5
+524
View File
@@ -0,0 +1,524 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Źródło karty pracy",
"description": "Schemat wspólnego źródła treści generowanego do TeX i HTML.",
"type": "object",
"required": [
"schema",
"card",
"generated",
"template",
"learning_effects",
"assessment_criteria",
"educational_requirements",
"sections",
"tasks",
"tasks_order"
],
"properties": {
"$schema": {
"type": "string"
},
"schema": {
"const": "esc-card-source.v1"
},
"card": {
"$ref": "#/$defs/card"
},
"generated": {
"$ref": "#/$defs/generated"
},
"template": {
"type": "string",
"minLength": 1
},
"dictionary_refs": {
"type": "object",
"properties": {
"pp_json_root": {
"type": "string"
}
},
"additionalProperties": true
},
"side_margin_tree_layout": {
"$ref": "#/$defs/sideMarginLayout"
},
"learning_effects": {
"type": "object",
"minProperties": 1,
"additionalProperties": {
"$ref": "#/$defs/learningEffect"
}
},
"assessment_criteria": {
"type": "object",
"minProperties": 1,
"additionalProperties": {
"$ref": "#/$defs/assessmentCriterion"
}
},
"educational_requirements": {
"type": "object",
"minProperties": 1,
"additionalProperties": {
"$ref": "#/$defs/educationalRequirement"
}
},
"sections": {
"type": "array",
"items": {
"$ref": "#/$defs/section"
}
},
"tasks": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/task"
}
},
"tasks_order": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
}
},
"model_block": {
"type": "object",
"properties": {
"steps": {
"$ref": "#/$defs/stringList"
}
},
"additionalProperties": true
},
"code_block": {
"type": "object",
"properties": {
"blocks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"slug": {
"type": "string"
},
"goal": {
"type": "string"
},
"web_steps": {
"$ref": "#/$defs/stringList"
},
"web_commands": {
"type": "array",
"items": {
"type": "object",
"required": ["label", "command"],
"properties": {
"label": {
"type": "string"
},
"command": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"hardware_procedure": {
"type": "array",
"items": {
"$ref": "#/$defs/procedureStep"
}
}
},
"$defs": {
"stringList": {
"type": "array",
"items": {
"type": "string"
}
},
"card": {
"type": "object",
"required": [
"id",
"series",
"number",
"count",
"slug",
"title",
"version",
"uuid"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"series": {
"type": "string",
"minLength": 1
},
"number": {
"type": "string",
"minLength": 1
},
"count": {
"type": "string",
"minLength": 1
},
"slug": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"title": {
"type": "string",
"minLength": 1
},
"version": {
"type": "string",
"minLength": 1
},
"uuid": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": true
},
"generated": {
"type": "object",
"required": ["tex", "html"],
"properties": {
"tex": {
"type": "string",
"minLength": 1
},
"html": {
"type": "string",
"minLength": 1
},
"html_css": {
"type": "string"
},
"bibliography": {
"type": "string"
},
"report_tasks_json": {
"type": "string"
},
"report_tasks_tex": {
"type": "string"
}
},
"additionalProperties": false
},
"sideMarginLayout": {
"type": "object",
"required": ["columns"],
"properties": {
"columns": {
"type": "array",
"minItems": 2,
"items": {
"type": "object",
"required": ["id", "label", "side", "tree"],
"properties": {
"id": {
"enum": ["ogolne", "zawodowe"]
},
"label": {
"type": "string"
},
"side": {
"enum": ["left", "right"]
},
"tree": {
"type": "string"
},
"description": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"learningEffect": {
"type": "object",
"required": ["text"],
"properties": {
"text": {
"type": "string",
"minLength": 1
},
"label": {
"type": "string"
},
"bloom_level": {
"type": "string"
},
"assessment_criteria": {
"$ref": "#/$defs/stringList"
}
},
"additionalProperties": true
},
"assessmentCriterion": {
"type": "object",
"required": ["text", "learning_effects"],
"properties": {
"text": {
"type": "string",
"minLength": 1
},
"learning_effects": {
"$ref": "#/$defs/stringList"
}
},
"additionalProperties": true
},
"educationalRequirement": {
"type": "object",
"required": ["text", "learning_effects", "learning_tree"],
"properties": {
"text": {
"type": "string",
"minLength": 1
},
"label": {
"type": "string"
},
"learning_effects": {
"$ref": "#/$defs/stringList"
},
"learning_tree": {
"type": "object",
"required": ["ogolne", "zawodowe"],
"properties": {
"schema": {
"type": "string"
},
"policy": {
"type": "string"
},
"ogolne": {
"type": "array",
"items": {
"$ref": "#/$defs/treeItem"
}
},
"zawodowe": {
"type": "array",
"items": {
"$ref": "#/$defs/treeItem"
}
}
},
"additionalProperties": true
}
},
"additionalProperties": true
},
"treeItem": {
"type": "object",
"required": ["tree_id", "effect_ref", "display", "text", "kw"],
"properties": {
"tree_id": {
"type": "string",
"minLength": 1
},
"effect_ref": {
"type": "string",
"minLength": 1
},
"display": {
"type": "string",
"minLength": 1
},
"text": {
"type": "string",
"minLength": 1
},
"source": {
"type": "string"
},
"official": {
"type": "string"
},
"local": {
"type": "string"
},
"kind": {
"type": "string"
},
"kw": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/treeCriterion"
}
}
},
"additionalProperties": true
},
"treeCriterion": {
"type": "object",
"required": ["criterion_ref", "display", "text"],
"properties": {
"criterion_ref": {
"type": "string",
"minLength": 1
},
"display": {
"type": "string",
"minLength": 1
},
"text": {
"type": "string",
"minLength": 1
},
"source": {
"type": "string"
},
"official": {
"type": "string"
},
"local": {
"type": "string"
},
"kind": {
"type": "string"
}
},
"additionalProperties": true
},
"section": {
"type": "object",
"required": ["title"],
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"content_kind": {
"type": "string"
},
"content_tex": {
"type": "string"
},
"area_tree_refs": {
"$ref": "#/$defs/stringList"
},
"educational_requirement_refs": {
"$ref": "#/$defs/stringList"
},
"learning_effect_refs": {
"$ref": "#/$defs/stringList"
},
"assessment_criterion_refs": {
"$ref": "#/$defs/stringList"
},
"steps": {
"type": "array",
"items": {
"$ref": "#/$defs/sectionStep"
}
}
},
"additionalProperties": true
},
"sectionStep": {
"type": "object",
"required": ["id", "title", "tree_refs"],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"title": {
"type": "string",
"minLength": 1
},
"tree_refs": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"educational_requirement_refs": {
"$ref": "#/$defs/stringList"
},
"learning_effect_refs": {
"$ref": "#/$defs/stringList"
},
"assessment_criterion_refs": {
"$ref": "#/$defs/stringList"
},
"equation_refs": {
"$ref": "#/$defs/stringList"
},
"figure_refs": {
"$ref": "#/$defs/stringList"
}
},
"additionalProperties": true
},
"task": {
"type": "object",
"required": ["prompt_tex", "criterion"],
"properties": {
"prompt_tex": {
"type": "string"
},
"criterion": {
"type": "string"
},
"assessment_criterion_ref": {
"type": "string"
},
"educational_requirement_refs": {
"$ref": "#/$defs/stringList"
},
"learning_effect_refs": {
"$ref": "#/$defs/stringList"
}
},
"additionalProperties": true
},
"procedureStep": {
"type": "object",
"required": ["step", "action", "condition"],
"properties": {
"step": {
"type": "string"
},
"action": {
"type": "string",
"minLength": 1
},
"condition": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
}
},
"additionalProperties": true
}
+134
View File
@@ -0,0 +1,134 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Layout karty pracy",
"description": "Schemat pliku templates/karta-*.json używanego przez render_card.py.",
"type": "object",
"required": [
"schema",
"id",
"name",
"version",
"description",
"tokens",
"emitters"
],
"properties": {
"$schema": {
"type": "string"
},
"schema": {
"const": "esc-card-template.v1"
},
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"name": {
"type": "string",
"minLength": 1
},
"version": {
"type": "string",
"minLength": 1
},
"approved": {
"type": "string"
},
"description": {
"type": "string",
"minLength": 1
},
"tokens": {
"type": "object",
"properties": {
"page": {
"type": "object",
"properties": {
"format": {
"type": "string"
},
"orientation": {
"enum": ["portrait", "landscape"]
},
"width_px": {
"type": "number",
"exclusiveMinimum": 0
},
"height_px": {
"type": "number",
"exclusiveMinimum": 0
}
},
"additionalProperties": true
},
"grid": {
"type": "object",
"properties": {
"columns": {
"type": "array",
"minItems": 3,
"items": {
"type": "string"
}
},
"left_role": {
"type": "string"
},
"right_role": {
"type": "string"
},
"geometry_cm": {
"type": "object",
"additionalProperties": {
"type": "number"
}
}
},
"additionalProperties": true
},
"typography": {
"type": "object",
"additionalProperties": true
},
"colors": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": true
},
"components": {
"type": "object",
"additionalProperties": true
},
"block_map": {
"type": "object",
"additionalProperties": true
},
"emitters": {
"type": "object",
"required": ["html", "latex"],
"properties": {
"html": {
"enum": ["classic", "5a", "5b"]
},
"latex": {
"enum": ["classic", "5a", "5b"]
}
},
"additionalProperties": false
},
"limitations": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"additionalProperties": true
}
+158
View File
@@ -0,0 +1,158 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Manifest serii kart",
"description": "Maszynowy opis kolejności repozytoriów kart i ich zadań.",
"type": "object",
"required": [
"version",
"series",
"org",
"title",
"default_branch",
"cards"
],
"properties": {
"$schema": {
"type": "string"
},
"version": {
"const": 1
},
"series": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"org": {
"type": "string",
"minLength": 1
},
"title": {
"type": "string",
"minLength": 1
},
"default_branch": {
"type": "string",
"minLength": 1
},
"cards": {
"type": "array",
"items": {
"$ref": "#/$defs/cardEntry"
}
}
},
"$defs": {
"cardEntry": {
"type": "object",
"required": [
"id",
"repo",
"branch",
"title",
"summary",
"status",
"card_series",
"card_number",
"card_count",
"slug",
"version",
"build",
"tasks"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"repo": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"branch": {
"type": "string",
"minLength": 1
},
"title": {
"type": "string",
"minLength": 1
},
"summary": {
"type": "string"
},
"kind": {
"type": "string"
},
"status": {
"type": "string"
},
"area": {
"type": "string"
},
"card_series": {
"type": "string"
},
"card_number": {
"type": "string"
},
"card_count": {
"type": "string"
},
"slug": {
"type": "string"
},
"version": {
"type": "string"
},
"document_uuid": {
"type": "string"
},
"pdf": {
"type": "string"
},
"build": {
"type": "object",
"required": ["driver", "all_target", "clean_target"],
"properties": {
"driver": {
"const": "make"
},
"all_target": {
"type": "string"
},
"clean_target": {
"type": "string"
}
},
"additionalProperties": true
},
"tasks": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "title", "target"],
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"target": {
"type": "string"
},
"source": {
"type": "string"
},
"build_dir": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
}
},
"additionalProperties": false
}
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCHEMAS = ROOT / "schemas"
TEMPLATES = ROOT / "templates"
EXAMPLE_CARD = ROOT / "examples" / "card"
EXAMPLE_SERIES = ROOT / "examples" / "series" / "series.json"
def load_json(path: Path) -> dict:
with path.open(encoding="utf-8") as stream:
value = json.load(stream)
if not isinstance(value, dict):
raise AssertionError(f"{path}: korzeń JSON musi być obiektem")
return value
def check_json_files() -> None:
for path in sorted(SCHEMAS.glob("*.json")):
schema = load_json(path)
assert schema.get("$schema", "").endswith("2020-12/schema"), path
for path in sorted(TEMPLATES.glob("*.json")):
template = load_json(path)
assert template.get("schema") == "esc-card-template.v1", path
assert template.get("emitters", {}).get("latex"), path
assert template.get("emitters", {}).get("html"), path
card = load_json(EXAMPLE_CARD / "json" / "card_source.json")
assert card.get("schema") == "esc-card-source.v1"
assert card.get("sections")
assert card.get("educational_requirements")
series = load_json(EXAMPLE_SERIES)
assert series.get("version") == 1
assert series.get("cards")
def check_optional_json_schema() -> None:
try:
import jsonschema
except ModuleNotFoundError:
if os.environ.get("CARD_LAYOUTS_REQUIRE_JSONSCHEMA") == "1":
raise AssertionError("brak pakietu jsonschema wymaganego przez pełną kontrolę")
print("INFO: jsonschema nie jest zainstalowany; wykonuję walidację generatora.")
return
pairs = [
(SCHEMAS / "card-source.schema.json", EXAMPLE_CARD / "json" / "card_source.json"),
(SCHEMAS / "series.schema.json", EXAMPLE_SERIES),
]
pairs.extend(
(SCHEMAS / "card-template.schema.json", path)
for path in sorted(TEMPLATES.glob("*.json"))
)
for schema_path, document_path in pairs:
jsonschema.Draft202012Validator(load_json(schema_path)).validate(load_json(document_path))
def check_render() -> None:
subprocess.run(
[sys.executable, str(ROOT / "tools" / "render_card.py"), str(EXAMPLE_CARD)],
cwd=ROOT,
check=True,
)
tex_path = EXAMPLE_CARD / "build" / "tex" / "main.tex"
html_path = EXAMPLE_CARD / "build" / "html" / "index.html"
css_path = EXAMPLE_CARD / "build" / "html" / "style.css"
for path in (tex_path, html_path, css_path):
if not path.is_file() or path.stat().st_size == 0:
raise AssertionError(f"brak wygenerowanego pliku: {path}")
tex = tex_path.read_text(encoding="utf-8")
html = html_path.read_text(encoding="utf-8")
for marker in ("DEMO-01", "IP WO", "INF04"):
if marker not in tex:
raise AssertionError(f"TeX nie zawiera znacznika {marker!r}")
if marker not in html:
raise AssertionError(f"HTML nie zawiera znacznika {marker!r}")
def main() -> int:
check_json_files()
check_optional_json_schema()
check_render()
print("PASS: schematy, layouty, przykład serii oraz render TeX/HTML są spójne.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+184
View File
@@ -0,0 +1,184 @@
{
"$schema": "../schemas/card-template.schema.json",
"schema": "esc-card-template.v1",
"id": "karta-5a",
"name": "Karta 5a",
"version": "1.0",
"description": "Wielostronicowy layout A4 landscape wyekstrahowany z referencyjnego pliku series/esc/html/ESC-04 karta.html; pierwsze dwie strony zachowują konwencję wzorca, dalsze strony dokładają pełną treść karty.",
"tokens": {
"page": {
"format": "a4",
"orientation": "landscape",
"width_px": 1123,
"height_px": 794,
"padding_px": {
"top": 38,
"right": 38,
"bottom": 30,
"left": 38
},
"paper": "#fefefc",
"desk": "#eceae4",
"ink": "#1c1c1a"
},
"grid": {
"columns": [
"112px",
"1fr",
"112px"
],
"gap_px": 19,
"left_role": "TECH",
"right_role": "OG"
},
"typography": {
"text": "STIX Two Text",
"mono": "JetBrains Mono",
"body_px": 13,
"line_height": 1.55,
"margin_tree_px": 7.5,
"label_px": 8.5,
"footer_px": 9.5
},
"colors": {
"paper": "#fefefc",
"desk": "#eceae4",
"ink": "#1c1c1a",
"muted": "#8a887f",
"subtle": "#4a4944",
"header": "#f4f3ef",
"line": "#d8d6d0",
"line_soft": "#e4e2dc",
"tree_line": "#c9c7bf",
"hairline": "#f0efe9",
"accent": "#3b5a8f"
}
},
"components": {
"section_box": {
"border": "1px solid #1c1c1a",
"header": "§N + uppercase title, letter-spacing .12em, background #f4f3ef"
},
"metric_header": {
"border": "1.5px solid #1c1c1a",
"title_bar": true,
"grid": "2x2",
"fill_fields": [
"Uczeń",
"Klasa · data"
]
},
"margin_tree_panel": {
"left": "TECH — zawodowe; WE → EK → KW",
"right": "OG — ogólne; WE → EN → KW",
"footer": "KW wspólne + dominanta"
},
"checkbox_row": {
"size_px": 11,
"border": "1.2px solid #1c1c1a"
},
"agenda_grid": {
"columns": [
"56px",
"1fr"
],
"time_color": "#3b5a8f"
},
"theory_table": {
"border": "1px solid #d8d6d0",
"id_font": "JetBrains Mono"
},
"tech_stack_table": {
"columns": [
"88px",
"1fr"
],
"groups": [
"Software",
"Hardware"
]
},
"footer": {
"rule": "1.5px solid #1c1c1a",
"format": "commit · uuid · wersja · data | strona N / M | status"
},
"fill_in_line": {
"style": "1px dotted #9a988f"
}
},
"block_map": {
"page_1": [
"metric_header",
"mission",
"objectives",
"agenda",
"hw_gate"
],
"page_2": [
"running_card_header",
"theory_refs",
"tech_stack",
"math_block"
],
"page_3": [
"part_a_banner",
"model_block",
"code_block",
"container_test",
"bom"
],
"page_4": [
"part_b_banner",
"safety_rules",
"reference_settings",
"figures",
"hardware_procedure"
],
"page_5": [
"observation_table"
],
"page_6": [
"troubleshooting",
"analysis_tasks"
],
"page_7": [
"team_decision",
"git_gate",
"session_record",
"ai_rules",
"references"
],
"objectives": "checkbox_row + objective_refs",
"agenda": "agenda_grid",
"hw_gate": "checkbox_row + teacher_signature",
"theory_refs": "theory_table",
"tech_stack": "tech_stack_table",
"math_block": "equations + answer_lines",
"model_block": "notebook + constants_table + manual_inputs",
"code_block": "skeleton_files + interfaces + responsibilities + pass_condition",
"container_test": "command_list + evidence_line",
"bom": "checkbox_table",
"safety_rules": "golden_rules_grid",
"reference_settings": "compact_reference_table",
"figures": "kicad_includegraphics",
"hardware_procedure": "checkbox_steps",
"observation_table": "spacious_grid",
"troubleshooting": "symptom_hypothesis_action_result_log",
"analysis_tasks": "answer_lines",
"team_decision": "mini_adr",
"git_gate": "checkbox_criterion_evidence_signature",
"ai_rules": "compact_policy_box",
"references": "compact_mono_list"
},
"emitters": {
"html": "5a",
"latex": "5a"
},
"limitations": {
"latex": [
"pdfLaTeX nie używa fontów Google Fonts; renderer wybiera stix2, jeśli jest dostępny, oraz mono z pakietu inconsolata/beramono/lmodern jako fallback.",
"HTML odwzorowuje pikselowe rozmiary 1123x794; LaTeX mapuje je na A4 landscape w centymetrach.",
"Panele drzewek w LaTeX są statycznymi minipage, bez interaktywnych tooltipów HTML."
]
}
}
+215
View File
@@ -0,0 +1,215 @@
{
"$schema": "../schemas/card-template.schema.json",
"schema": "esc-card-template.v1",
"id": "karta-5b",
"name": "Karta 5b",
"version": "1.3",
"approved": "04.07.2026 — layout referencyjny zatwierdzony przez prowadzącego (symetria 0,55/2,05/0,55/14,70; gołe drzewka); v1.1 — nagłówki punktowe zamiast §, 05.07.2026; v1.2 — linie nagłówka/stopki jako warstwa strony ograniczona do bloku treści, 06.07.2026; v1.3 — margines gorny/dolny 2,5 cm, 06.07.2026",
"description": "Wielostronicowy layout A4 portrait: charakter 5a z ramkami sekcji, ale z golymi drzewkami WE->EN/EK jako marginalia bez paneli i bez luznych KW. Linie strony sa warstwa overlay i nie wychodza pod drzewka. Blok tresci zaczyna sie 2,5 cm od gornej krawedzi i konczy 2,5 cm od dolnej.",
"tokens": {
"page": {
"format": "a4",
"orientation": "portrait",
"width_px": 794,
"height_px": 1123,
"padding_px": {
"top": 95,
"right": 21,
"bottom": 95,
"left": 21
},
"paper": "#fefefc",
"desk": "#eceae4",
"ink": "#1c1c1a"
},
"grid": {
"columns": [
"77px",
"21px",
"1fr",
"21px",
"77px"
],
"gap_px": 0,
"left_gap_px": 21,
"right_gap_px": 21,
"geometry_cm": {
"top": 2.5,
"bottom": 2.5,
"outer_margin_left": 0.55,
"left_tree": 2.05,
"left_gap": 0.55,
"content": 14.7,
"content_height": 24.7,
"right_gap": 0.55,
"right_tree": 2.05,
"outer_margin_right": 0.55
},
"left_role": "TECH",
"right_role": "OG"
},
"page_rules": {
"layer": "overlay",
"portrait_content_width_cm": 14.7,
"portrait_content_height_cm": 24.7,
"portrait_rule_scope": "kolumna tresci; linia nie wchodzi pod drzewka ani w przerwy marginesowe",
"landscape_schematic_slot_mm": 242,
"landscape_schematic_slot_height_mm": 160,
"landscape_schematic_figure_height_mm": 135,
"landscape_rule_scope": "slot schematu technicznego; pasek sekcji i stopka maja ta sama szerokosc co arkusz",
"line_weight_pt": 0.5
},
"typography": {
"text": "STIX Two Text",
"mono": "JetBrains Mono",
"body_px": 11,
"line_height": 1.42,
"margin_tree_px": 7.2,
"label_px": 7.8,
"footer_px": 8.2
},
"colors": {
"paper": "#fefefc",
"desk": "#eceae4",
"ink": "#1c1c1a",
"muted": "#8a887f",
"subtle": "#4a4944",
"header": "#f4f3ef",
"line": "#d8d6d0",
"line_soft": "#e4e2dc",
"tree_line": "#c9c7bf",
"hairline": "#f0efe9",
"accent": "#3b5a8f"
}
},
"components": {
"section_box": {
"border": "1px solid #1c1c1a",
"header": "N. + uppercase title, letter-spacing .12em, background #f4f3ef"
},
"metric_header": {
"border": "1.5px solid #1c1c1a",
"title_bar": true,
"grid": "2x2",
"fill_fields": [
"Uczen",
"Klasa · data"
]
},
"margin_tree_panel": {
"left": "TECH jako gole drzewko tekstowe WE->EK/EN bez KW, bez ramki, tla, naglowka i stopki",
"right": "OG jako gole drzewko tekstowe WE->EK/EN bez KW, bez ramki, tla, naglowka i stopki",
"footer": false
},
"checkbox_row": {
"size_px": 10,
"border": "1.1px solid #1c1c1a"
},
"agenda_grid": {
"columns": [
"46px",
"1fr"
],
"time_color": "#3b5a8f"
},
"theory_table": {
"border": "1px solid #d8d6d0",
"id_font": "JetBrains Mono"
},
"tech_stack_table": {
"columns": [
"72px",
"1fr"
],
"groups": [
"Software",
"Hardware"
]
},
"footer": {
"rule": "1.5px solid #1c1c1a",
"layer": "page overlay",
"width": "content block only: 14.7cm portrait, 242mm technical landscape",
"format": "commit · uuid · wersja · data | strona N / M | status"
},
"running_card_header": {
"rule": "1.4px solid #1c1c1a",
"layer": "page overlay",
"width": "content block only: 14.7cm portrait, 242mm technical landscape"
},
"fill_in_line": {
"style": "1px dotted #9a988f"
}
},
"block_map": {
"page_1": [
"metric_header",
"mission",
"objectives",
"agenda",
"hw_gate",
"theory_refs",
"tech_stack"
],
"page_2": [
"running_card_header",
"math_block",
"part_a_banner",
"model_block",
"code_block",
"container_test",
"bom"
],
"page_3": [
"part_b_banner",
"safety_rules",
"reference_settings",
"figures",
"hardware_procedure"
],
"page_4": [
"observation_table",
"troubleshooting"
],
"page_5": [
"analysis_tasks",
"team_decision",
"git_gate",
"session_record",
"ai_rules",
"references"
],
"objectives": "checkbox_row + objective_refs",
"agenda": "agenda_grid",
"hw_gate": "checkbox_row + teacher_signature",
"theory_refs": "theory_table",
"tech_stack": "tech_stack_table",
"math_block": "equations + answer_lines",
"model_block": "notebook + constants_table + manual_inputs",
"code_block": "skeleton_files + interfaces + responsibilities + pass_condition",
"container_test": "command_list + evidence_line",
"bom": "checkbox_table",
"safety_rules": "golden_rules_grid",
"reference_settings": "compact_reference_table",
"figures": "kicad_includegraphics",
"hardware_procedure": "checkbox_steps",
"observation_table": "spacious_grid",
"troubleshooting": "symptom_hypothesis_action_result_log",
"analysis_tasks": "answer_lines",
"team_decision": "mini_adr",
"git_gate": "checkbox_criterion_evidence_signature",
"ai_rules": "compact_policy_box",
"references": "compact_mono_list"
},
"emitters": {
"html": "5b",
"latex": "5b"
},
"limitations": {
"latex": [
"pdfLaTeX nie uzywa fontow Google Fonts; renderer wybiera stix2, jesli jest dostepny, oraz mono z pakietu inconsolata/beramono/lmodern jako fallback.",
"HTML odwzorowuje pikselowe rozmiary 794x1123; LaTeX mapuje je na A4 portrait z minipage dla marginesow.",
"Drzewka marginesowe sa statycznym tekstem mono: lewy margines TECH, prawy margines OG."
]
}
}
+74
View File
@@ -0,0 +1,74 @@
{
"$schema": "../schemas/card-template.schema.json",
"schema": "esc-card-template.v1",
"id": "karta-klasyczna",
"name": "Karta klasyczna",
"version": "1.0",
"description": "Dotychczasowy portretowy layout generatora: metryczka, marginesy z drzewkami po bokach, sekcje pełnej karty i słownik.",
"tokens": {
"page": {
"format": "a4",
"orientation": "portrait",
"paper": "#ffffff",
"margin_cm": {
"left": 2.15,
"right": 2.15,
"top": 2.2,
"bottom": 2.2
}
},
"grid": {
"columns": [
"116px",
"minmax(0, 1fr)",
"116px"
],
"gap_px": 18
},
"typography": {
"text": "Georgia, Times New Roman, serif",
"mono": "Latin Modern Mono, ui-monospace, SFMono-Regular, Consolas, monospace",
"body_px": 17,
"margin_px": 7
},
"colors": {
"paper": "#ffffff",
"desk": "#eeeeee",
"ink": "#111111",
"muted": "#666666",
"line": "#b9b9b9",
"line_soft": "#dddddd",
"we": "#e00000",
"og": "#008000",
"tech": "#d36b00",
"kw": "#0018c8"
}
},
"components": {
"section_box": "classic_section",
"metric_header": "classic_front_matter",
"margin_tree_panel": "classic_margin_notes",
"checkbox_row": "classic_empty_check",
"agenda_grid": "classic_compact_agenda",
"theory_table": "classic_tabularx",
"tech_stack_table": "classic_tabularx",
"footer": "classic_fancyhdr",
"fill_in_line": "classic_dotfill"
},
"block_map": {
"front_matter": "metric_header",
"front_page_scope": "classic_scope",
"objectives": "section_box",
"agenda": "section_box",
"hw_gate": "section_box",
"theory_refs": "section_box",
"tech_stack": "section_box",
"sections": "section_box",
"bibliography": "section_box",
"dictionary": "dictionary_page"
},
"emitters": {
"html": "classic",
"latex": "classic"
}
}
+5411
View File
File diff suppressed because it is too large Load Diff