@lernapps/app-templates 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +195 -2
  3. package/dist/appname-Bh23cUuz.mjs +12 -0
  4. package/dist/assessment-By2GGTJC.mjs +1696 -0
  5. package/dist/block-BK_ohsH1.mjs +82 -0
  6. package/dist/blocks-BmRrD7UY.mjs +149 -0
  7. package/dist/blocks.mjs +4 -0
  8. package/dist/check-BkEl-m-F.mjs +254 -0
  9. package/dist/content-cYBPYg_J.mjs +140 -0
  10. package/dist/contract-PONpxJtd.mjs +52 -0
  11. package/dist/e2e-BiMI-pCL.mjs +89 -0
  12. package/dist/engine-BqF2Vffn.mjs +136 -0
  13. package/dist/explainer-e2e.mjs +1297 -0
  14. package/dist/explainer-islands.mjs +197 -0
  15. package/dist/explainer-plugin.mjs +2131 -0
  16. package/dist/explainer.mjs +1246 -0
  17. package/dist/html-mffkHGHh.mjs +89 -0
  18. package/dist/image-CspJVfyQ.mjs +21 -0
  19. package/dist/interactive-e2e.mjs +140 -0
  20. package/dist/interactive-plugin.mjs +242 -0
  21. package/dist/interactive.mjs +75 -0
  22. package/dist/load-C-0hUaSC.mjs +16947 -0
  23. package/dist/needs-BzEahJ6X.mjs +97 -0
  24. package/dist/pages-9RaaomoO.mjs +81 -0
  25. package/dist/questions-e2e.mjs +1 -0
  26. package/dist/questions-engine.mjs +3 -0
  27. package/dist/questions.mjs +6 -0
  28. package/dist/quiz-BKc8JYtC.mjs +70 -0
  29. package/dist/quiz-e2e.mjs +174 -0
  30. package/dist/quiz-plugin.mjs +266 -0
  31. package/dist/quiz.mjs +246 -0
  32. package/dist/render-BJey0bwl.mjs +540 -0
  33. package/dist/render-BL6fV_NI.mjs +507 -0
  34. package/dist/render-DpRO_RKm.mjs +146 -0
  35. package/dist/result-_eQcamoz.mjs +180 -0
  36. package/dist/tasks-DADUYy52.mjs +74 -0
  37. package/dist/validate-COaZso7u.mjs +1072 -0
  38. package/dist/validate-CxtdeQ5S.mjs +146 -0
  39. package/dist/vue-DO1m6q1R.mjs +40 -0
  40. package/dist/vue-code-BkJjW_I_.mjs +320 -0
  41. package/docs/arc42/01-introduction-and-goals.arc42.md +82 -0
  42. package/docs/arc42/02-constraints.arc42.md +228 -0
  43. package/docs/arc42/03-system-context.arc42.md +118 -0
  44. package/docs/arc42/04-solution-strategy.arc42.md +47 -0
  45. package/docs/arc42/05-building-blocks.arc42.md +307 -0
  46. package/docs/arc42/06-runtime-view.arc42.md +98 -0
  47. package/docs/arc42/07-deployment-view.arc42.md +160 -0
  48. package/docs/arc42/08-concepts.arc42.md +123 -0
  49. package/docs/arc42/09-decisions.arc42.md +246 -0
  50. package/docs/arc42/10-quality-requirements.arc42.md +155 -0
  51. package/docs/arc42/11-risks.arc42.md +113 -0
  52. package/docs/arc42/12-glossary.arc42.md +162 -0
  53. package/docs/arc42/architecture-evidence.md +395 -0
  54. package/explainer/.arc42ignore +3 -0
  55. package/explainer/README.md +54 -0
  56. package/explainer/_gitignore +5 -0
  57. package/explainer/docs/arc42/03-explainer-system-context.arc42.md +35 -0
  58. package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +659 -0
  59. package/explainer/docs/arc42/06-explainer-runtime-view.arc42.md +105 -0
  60. package/explainer/docs/arc42/07-explainer-deployment-view.arc42.md +34 -0
  61. package/explainer/docs/arc42/08-explainer-concepts.arc42.md +17 -0
  62. package/explainer/docs/arc42/09-explainer-decisions.arc42.md +443 -0
  63. package/explainer/docs/arc42/10-explainer-quality-requirements.arc42.md +54 -0
  64. package/explainer/docs/arc42/11-explainer-risks.arc42.md +17 -0
  65. package/explainer/docs/arc42/12-explainer-glossary.arc42.md +185 -0
  66. package/explainer/e2e/explainer.e2e.ts +3 -0
  67. package/explainer/index.html +11 -0
  68. package/explainer/package.json +18 -0
  69. package/explainer/playwright.config.ts +1 -0
  70. package/explainer/src/app.yaml +20 -0
  71. package/explainer/src/components/ProzentFeld.test.ts +30 -0
  72. package/explainer/src/components/ProzentFeld.ts +121 -0
  73. package/explainer/src/components/ProzentFeld.vue +84 -0
  74. package/explainer/src/images/hunderterfeld-klammern.svg +105 -0
  75. package/explainer/src/main.ts +4 -0
  76. package/explainer/src/prozent/chapter.yaml +18 -0
  77. package/explainer/src/prozent/grundwert.yaml +115 -0
  78. package/explainer/src/prozent/prozentsatz.yaml +99 -0
  79. package/explainer/src/prozent/prozentwert.yaml +119 -0
  80. package/explainer/tsconfig.json +1 -0
  81. package/explainer/vite.config.ts +4 -0
  82. package/interactive/.arc42ignore +5 -0
  83. package/interactive/README.md +28 -0
  84. package/interactive/_gitignore +4 -0
  85. package/interactive/docs/arc42/05-interactive-building-blocks.arc42.md +518 -0
  86. package/interactive/docs/arc42/06-interactive-runtime-view.arc42.md +58 -0
  87. package/interactive/docs/arc42/07-interactive-deployment-view.arc42.md +35 -0
  88. package/interactive/docs/arc42/08-interactive-concepts.arc42.md +52 -0
  89. package/interactive/docs/arc42/09-interactive-decisions.arc42.md +130 -0
  90. package/interactive/docs/arc42/10-interactive-quality-requirements.arc42.md +45 -0
  91. package/interactive/docs/arc42/11-interactive-risks.arc42.md +20 -0
  92. package/interactive/docs/arc42/12-interactive-glossary.arc42.md +80 -0
  93. package/interactive/e2e/interactive.e2e.ts +3 -0
  94. package/interactive/index.html +11 -0
  95. package/interactive/package.json +18 -0
  96. package/interactive/playwright.config.ts +1 -0
  97. package/interactive/src/app.yaml +28 -0
  98. package/interactive/src/core/index.ts +101 -0
  99. package/interactive/src/data/lessons.json +4 -0
  100. package/interactive/src/env.d.ts +6 -0
  101. package/interactive/src/main.ts +6 -0
  102. package/interactive/src/messages/de.json +12 -0
  103. package/interactive/src/playthroughs/grundreihe.yaml +33 -0
  104. package/interactive/src/playthroughs/obere-reihe.yaml +14 -0
  105. package/interactive/src/ui/App.vue +85 -0
  106. package/interactive/tsconfig.json +1 -0
  107. package/interactive/vite.config.ts +4 -0
  108. package/package.json +144 -4
  109. package/quiz/README.md +31 -0
  110. package/quiz/_gitignore +5 -0
  111. package/quiz/docs/arc42/05-quiz-building-blocks.arc42.md +283 -0
  112. package/quiz/docs/arc42/06-quiz-runtime-view.arc42.md +21 -0
  113. package/quiz/docs/arc42/07-quiz-deployment-view.arc42.md +33 -0
  114. package/quiz/docs/arc42/09-quiz-decisions.arc42.md +36 -0
  115. package/quiz/docs/arc42/10-quiz-quality-requirements.arc42.md +53 -0
  116. package/quiz/docs/arc42/11-quiz-risks.arc42.md +19 -0
  117. package/quiz/docs/arc42/12-quiz-glossary.arc42.md +27 -0
  118. package/quiz/e2e/quiz.e2e.ts +3 -0
  119. package/quiz/index.html +11 -0
  120. package/quiz/package.json +18 -0
  121. package/quiz/playwright.config.ts +1 -0
  122. package/quiz/src/images/berge-ordnen.jpg +0 -0
  123. package/quiz/src/images/everest-hoehe.jpg +0 -0
  124. package/quiz/src/images/galapagos.jpg +0 -0
  125. package/quiz/src/images/grand-canyon.jpg +0 -0
  126. package/quiz/src/images/totes-meer.jpg +0 -0
  127. package/quiz/src/images/uluru.jpg +0 -0
  128. package/quiz/src/images/wasserfaelle-laender.jpg +0 -0
  129. package/quiz/src/main.ts +4 -0
  130. package/quiz/src/quiz.json +312 -0
  131. package/quiz/tsconfig.json +1 -0
  132. package/quiz/vite.config.ts +4 -0
  133. package/runtime/explainer/assessment-client.ts +478 -0
  134. package/runtime/explainer/assessment-page.ts +446 -0
  135. package/runtime/explainer/assessment.ts +393 -0
  136. package/runtime/explainer/check-page.ts +520 -0
  137. package/runtime/explainer/components/exercise.ts +409 -0
  138. package/runtime/explainer/components/props.ts +230 -0
  139. package/runtime/explainer/components/read.ts +428 -0
  140. package/runtime/explainer/content.ts +277 -0
  141. package/runtime/explainer/e2e.ts +1474 -0
  142. package/runtime/explainer/exercises.ts +324 -0
  143. package/runtime/explainer/explainer.css +1283 -0
  144. package/runtime/explainer/explainer.ts +838 -0
  145. package/runtime/explainer/explainer.v1.schema.json +1165 -0
  146. package/runtime/explainer/expression.ts +407 -0
  147. package/runtime/explainer/fraction.ts +107 -0
  148. package/runtime/explainer/generated/comparator.ts +31 -0
  149. package/runtime/explainer/generated/question.ts +165 -0
  150. package/runtime/explainer/generated/soundness.ts +280 -0
  151. package/runtime/explainer/generated/texts.ts +257 -0
  152. package/runtime/explainer/generated/types.ts +103 -0
  153. package/runtime/explainer/generated/values.ts +136 -0
  154. package/runtime/explainer/islands.ts +281 -0
  155. package/runtime/explainer/load.ts +457 -0
  156. package/runtime/explainer/messages/de.json +200 -0
  157. package/runtime/explainer/messages/en.json +200 -0
  158. package/runtime/explainer/messages.ts +13 -0
  159. package/runtime/explainer/needs.ts +122 -0
  160. package/runtime/explainer/open-task.ts +235 -0
  161. package/runtime/explainer/outline.ts +299 -0
  162. package/runtime/explainer/plugin.ts +488 -0
  163. package/runtime/explainer/position.ts +62 -0
  164. package/runtime/explainer/prompt.ts +75 -0
  165. package/runtime/explainer/render.ts +686 -0
  166. package/runtime/explainer/result.ts +238 -0
  167. package/runtime/explainer/routes.ts +119 -0
  168. package/runtime/explainer/schema.ts +140 -0
  169. package/runtime/explainer/scope.ts +87 -0
  170. package/runtime/explainer/stuck.ts +57 -0
  171. package/runtime/explainer/tasks.ts +109 -0
  172. package/runtime/explainer/tutor.ts +469 -0
  173. package/runtime/explainer/tutorpage.ts +669 -0
  174. package/runtime/explainer/video.ts +123 -0
  175. package/runtime/interactive/contract.ts +121 -0
  176. package/runtime/interactive/e2e.ts +190 -0
  177. package/runtime/interactive/interactive.css +82 -0
  178. package/runtime/interactive/interactive.ts +115 -0
  179. package/runtime/interactive/interactive.v1.schema.json +146 -0
  180. package/runtime/interactive/load.ts +188 -0
  181. package/runtime/interactive/messages/de.json +14 -0
  182. package/runtime/interactive/messages/en.json +14 -0
  183. package/runtime/interactive/messages.ts +11 -0
  184. package/runtime/interactive/playthrough.v1.schema.json +143 -0
  185. package/runtime/interactive/plugin.ts +266 -0
  186. package/runtime/interactive/render.ts +126 -0
  187. package/runtime/quiz/e2e.ts +233 -0
  188. package/runtime/quiz/engine.ts +242 -0
  189. package/runtime/quiz/messages/de.json +23 -0
  190. package/runtime/quiz/messages/en.json +23 -0
  191. package/runtime/quiz/plugin.ts +150 -0
  192. package/runtime/quiz/quiz.css +154 -0
  193. package/runtime/quiz/quiz.ts +56 -0
  194. package/runtime/quiz/quiz.v1.schema.json +72 -0
  195. package/runtime/quiz/render.ts +173 -0
  196. package/runtime/quiz/validate.ts +108 -0
  197. package/runtime/shared/appname.ts +16 -0
  198. package/runtime/shared/blocks/block.ts +162 -0
  199. package/runtime/shared/blocks/blocks.css +190 -0
  200. package/runtime/shared/blocks/blocks.v1.schema.json +432 -0
  201. package/runtime/shared/blocks/code.ts +26 -0
  202. package/runtime/shared/blocks/figures.ts +49 -0
  203. package/runtime/shared/blocks/image.ts +25 -0
  204. package/runtime/shared/blocks/index.ts +13 -0
  205. package/runtime/shared/blocks/messages/de.json +4 -0
  206. package/runtime/shared/blocks/messages/en.json +4 -0
  207. package/runtime/shared/blocks/render.ts +134 -0
  208. package/runtime/shared/blocks/validate.ts +165 -0
  209. package/runtime/shared/content.ts +197 -0
  210. package/runtime/shared/examples.ts +20 -0
  211. package/runtime/shared/html.ts +10 -0
  212. package/runtime/shared/languages.ts +94 -0
  213. package/runtime/shared/page.css +101 -0
  214. package/runtime/shared/pages.ts +114 -0
  215. package/runtime/shared/questions/e2e.ts +115 -0
  216. package/runtime/shared/questions/engine.ts +209 -0
  217. package/runtime/shared/questions/index.ts +39 -0
  218. package/runtime/shared/questions/messages/de.json +26 -0
  219. package/runtime/shared/questions/messages/en.json +26 -0
  220. package/runtime/shared/questions/question.ts +430 -0
  221. package/runtime/shared/questions/question.v1.schema.json +342 -0
  222. package/runtime/shared/questions/questions.css +192 -0
  223. package/runtime/shared/questions/render.ts +247 -0
  224. package/runtime/shared/questions/validate.ts +518 -0
  225. package/runtime/shared/schema.ts +44 -0
  226. package/runtime/shared/text/check.ts +68 -0
  227. package/runtime/shared/text/formula.ts +69 -0
  228. package/runtime/shared/text/markdown.ts +185 -0
  229. package/runtime/shared/text/text.v1.schema.json +34 -0
  230. package/runtime/shared/vue-code.ts +395 -0
  231. package/runtime/shared/vue.ts +38 -0
@@ -0,0 +1,17 @@
1
+ # Risks and Technical Debt
2
+
3
+ The explainer's risks, besides the generic ones.
4
+
5
+ ## YouTube's Player After the Click
6
+
7
+ Once the player of youtube-nocookie.com loads, YouTube gets the learner's IP address and may keep data in its own
8
+ frame; the privacy-enhanced host only defers that until the click. The app cannot limit what the player does.
9
+
10
+ ```arc42
11
+ :::risk
12
+ id: risk-explainer-video-after-click
13
+ severity: low
14
+ title: After the click, YouTube's player sends data to Google
15
+ mitigation: Nothing loads before the click; the note says what the click sends; the standing choice is the learner's and can be withdrawn; the catalog entry declares thirdParty on-click.
16
+ :::
17
+ ```
@@ -0,0 +1,185 @@
1
+ # Glossary
2
+
3
+ The explainer's terms, besides the generic ones.
4
+
5
+ ## Block
6
+
7
+ A page is a list of blocks from a closed set, each with its type; sections give the structure.
8
+
9
+ ```arc42
10
+ :::glossary-term
11
+ id: term-block
12
+ title: Block
13
+ definition: One element of an explainer page in its content YAML (a section, a text, a formula, a figure, an exercise, a vue-component, ...).
14
+ :::
15
+ ```
16
+
17
+ ## Island
18
+
19
+ Only a page with a component loads Vue.
20
+
21
+ ```arc42
22
+ :::glossary-term
23
+ id: term-island
24
+ title: Island
25
+ definition: The place of a Vue component on a static explainer page, rendered at build and hydrated in the browser.
26
+ :::
27
+ ```
28
+
29
+ ## Task Number
30
+
31
+ "Aufgabe Nr. n" on the page, `?<id>=<n>` in the address.
32
+
33
+ ```arc42
34
+ :::glossary-term
35
+ id: term-task-number
36
+ title: Task number
37
+ definition: The number of one of the distinct tasks of a seeded exercise, shared by the learner, the tutor and the address.
38
+ :::
39
+ ```
40
+
41
+ ## Generated Exercise
42
+
43
+ Written as `generated` in the content: given values, preconditions, a task template, an answer and typical mistakes.
44
+
45
+ ```arc42
46
+ :::glossary-term
47
+ id: term-generated-exercise
48
+ title: Generated exercise
49
+ definition: An exercise of the explainer drawn from a seed, property-tested at build and live in the browser.
50
+ :::
51
+ ```
52
+
53
+ ## Open Task
54
+
55
+ Written as `open-task` in the content. German: offene Aufgabe.
56
+
57
+ ```arc42
58
+ :::glossary-term
59
+ id: term-open-task
60
+ title: Open task
61
+ definition: An exercise the learner answers in their own words, typed or on paper with a photo, then compares with the points of a good answer.
62
+ :::
63
+ ```
64
+
65
+ ## Point
66
+
67
+ A point of a good answer to an open task, named P1, P2 … in the result and on the tutor page.
68
+
69
+ ```arc42
70
+ :::glossary-term
71
+ id: term-point
72
+ title: Point
73
+ definition: One checkable sentence a good answer to an open task has, about its content with the section it comes from, or about its form.
74
+ :::
75
+ ```
76
+
77
+ ## Scope
78
+
79
+ `scope` in the content, written at `scope/` beside each page, chapter and the app for the tutor.
80
+
81
+ ```arc42
82
+ :::glossary-term
83
+ id: term-scope
84
+ title: Scope
85
+ definition: The whole learning path of an explainer in the creator's own words, page by page, with what belongs to it and what does not; for the tutor, never shown to the learner.
86
+ :::
87
+ ```
88
+
89
+ ## Tutor Page
90
+
91
+ `<chapter>/<page>/tutor/`, the address a tutor's prompt names for a page.
92
+
93
+ ```arc42
94
+ :::glossary-term
95
+ id: term-tutor-page
96
+ title: Tutor page
97
+ definition: The one source of what the learner's own AI assistant gets about one content page: who it helps and how it guides, where the child is, then the page's content and its exercises with every task number, a generated one with its table of tasks; its query says where the learner is.
98
+ :::
99
+ ```
100
+
101
+ ## Table of Tasks
102
+
103
+ The inputs and outputs of each task number of a `generated` exercise on the tutor page, one line each:
104
+ `27 | 15 | 1260 | 189 | 18900` under `Nr. | p | G | Lösung | F1`.
105
+
106
+ ```arc42
107
+ :::glossary-term
108
+ id: term-table-of-tasks
109
+ title: Table of tasks
110
+ definition: For each task number of a generated exercise, or of an exercise component that declares it, under its template on the tutor page: the values of its placeholders, the solution and what each typical mistake gives, worked out, so that the tutor fills in and calculates nothing.
111
+ :::
112
+ ```
113
+
114
+ ## Way to the Tutor
115
+
116
+ How the learner reaches the tutor: A, "Mit <name> lernen", for a page; B, "Hilfe", for one task.
117
+
118
+ ```arc42
119
+ :::glossary-term
120
+ id: term-tutor-way
121
+ title: Way to the tutor
122
+ definition: Once an assistant is switched on, "Mit <name> lernen" (A) opens it with one sentence that names the page's tutor page with where the learner is; "Hilfe", "Warum?" after a wrong answer (B), gives the lines of one task, as a new chat (B1) or, on a page a tutor opened, copied for the running chat (B2).
123
+ :::
124
+ ```
125
+
126
+ ## Assistant Switched On
127
+
128
+ The learner's choice of a KI assistant, on the start page or beside the title of a page.
129
+
130
+ ```arc42
131
+ :::glossary-term
132
+ id: term-tutor-assistant
133
+ title: Assistant switched on
134
+ definition: One of the app's assistants, or copying the text, chosen once by the learner and kept on the device per app; while one is on, each content page has the bar of way A and every exercise the help button of way B.
135
+ :::
136
+ ```
137
+
138
+ ## Need
139
+
140
+ What helps a learner when the tutor talks to them, named by what helps, never by a diagnosis.
141
+
142
+ ```arc42
143
+ :::glossary-term
144
+ id: term-tutor-need
145
+ title: Need
146
+ definition: One of the ways the tutor can talk that a learner chooses, none, one or several, for every explainer or for one app (kurze-schritte, wenig-lesen, mehr-tiefe, andere-muttersprache, klar-und-woertlich, rechnen-anschaulich); the tutor page's query names them in bedarf=, and the tutor page holds a short section for each.
147
+ :::
148
+ ```
149
+
150
+ ## Lines of "Hilfe"
151
+
152
+ What way B gives the tutor about one task.
153
+
154
+ ```arc42
155
+ :::glossary-term
156
+ id: term-stuck-lines
157
+ title: Lines of "Hilfe"
158
+ definition: In the learner's voice: the page's tutor page with the page's task numbers, uebung and abschnitt, and the task; the last wrong input with its tries, the app's message for it word for word, the hint or solution opened; no rule for the tutor.
159
+ :::
160
+ ```
161
+
162
+ ## Result Line
163
+
164
+ What "Zurück" at one task copies for the learner to paste into the chat; "Zurück zum Tutor" copies the tutor page and a
165
+ line like it for each task of the session.
166
+
167
+ ```arc42
168
+ :::glossary-term
169
+ id: term-result-line
170
+ title: Result line
171
+ definition: The exercise, its task number, how far the learner got and the last input, with the absolute address of the page's tutor page with where the learner is.
172
+ :::
173
+ ```
174
+
175
+ ## Self-Assessment
176
+
177
+ German: Selbsteinschätzung. Optional, for the app or a chapter.
178
+
179
+ ```arc42
180
+ :::glossary-term
181
+ id: term-self-assessment
182
+ title: Self-assessment
183
+ definition: Exercises of an explainer's content pages, a few per page of its own, which the learner may answer before learning and which are checked only all together; the result says per content page whether it is probably known, to look at again or to read.
184
+ :::
185
+ ```
@@ -0,0 +1,3 @@
1
+ import { explainerTests } from "@lernapps/app-templates/explainer/e2e";
2
+
3
+ explainerTests();
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <script type="module" src="/src/main.ts"></script>
7
+ </head>
8
+ <body>
9
+ <!-- explainer -->
10
+ </body>
11
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "explainer",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vp dev",
7
+ "build": "vp build --outDir _site",
8
+ "check": "lernapps check"
9
+ },
10
+ "devDependencies": {
11
+ "@lernapps/app-templates": "file:..",
12
+ "@lernapps/tooling": "github:lernapps/tooling#7354ffe7ae7d8079ba5f714d5b402666d27df066",
13
+ "vite-plus": "1.4.0"
14
+ },
15
+ "overrides": {
16
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.4.0"
17
+ }
18
+ }
@@ -0,0 +1 @@
1
+ export { default } from "@lernapps/tooling/playwright";
@@ -0,0 +1,20 @@
1
+ # yaml-language-server: $schema=../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json
2
+ title: Prozentrechnung
3
+ intro: |
4
+ Prozentwert, Prozentsatz und Grundwert: Was sie bedeuten, wie du sie ausrechnest, und je ein Beispiel dazu.
5
+ Für die Klassen 7 und 8.
6
+ languages: [de]
7
+ chapters: [prozent]
8
+ sources:
9
+ - title: Prozentrechnung mittels Formeln (Serlo)
10
+ url: https://de.serlo.org/mathe/2193/prozentrechnung-mittels-formeln
11
+ scope:
12
+ text: |
13
+ Die App führt in die Prozentrechnung ein. Sie geht von drei Größen aus: dem Grundwert $G$ (das Ganze, 100 %), dem
14
+ Prozentsatz $p\,\%$ (welcher Anteil gemeint ist) und dem Prozentwert $W$ (der Teil, in der Einheit des Ganzen). Jede
15
+ Seite nimmt eine der drei Größen als die gesuchte und zeigt, wie man sie aus den beiden anderen ausrechnet. Der Weg
16
+ über 1 % hilft auf allen drei Seiten: Das Ganze geteilt durch 100 ist 1 %.
17
+ out:
18
+ - "Zinsen und Zinseszins"
19
+ - "Prozentpunkte und die Veränderung eines Prozentsatzes"
20
+ - "vermehrter und verminderter Grundwert (Preis mit Aufschlag oder nach Rabatt in einem Schritt)"
@@ -0,0 +1,30 @@
1
+ import { expect, test } from "vite-plus/test";
2
+ import { check, describe, generate, mistakes, reason, solution, wrong } from "./ProzentFeld.ts";
3
+
4
+ const props = {
5
+ task: "Das Feld hat {G} Kästchen. Färbe {p} % davon.",
6
+ mistakes: ["{p} Kästchen sind {p} % von 100.", "Das ist der Rest."],
7
+ };
8
+
9
+ test("p % of the field is right; p squares and the rest are wrong", () => {
10
+ for (let seed = 1; seed <= 50; seed++) {
11
+ const task = generate(seed, props);
12
+ expect([20, 25, 40, 50]).toContain(task.G);
13
+ expect(Number.isInteger((task.G * task.p) / 100)).toBe(true);
14
+ expect(check(task, solution(task))).toBe(true);
15
+ expect(check(task, wrong(task))).toBe(false);
16
+ for (const answer of mistakes(task)) if (answer !== undefined) expect(check(task, answer)).toBe(false);
17
+ expect(new Set(mistakes(task)).size).toBe(2);
18
+ }
19
+ });
20
+
21
+ test("describe fills in the task; each typical mistake gets its message", () => {
22
+ const task = { G: 20, p: 15 };
23
+ expect(describe(task, props, "de")).toEqual({
24
+ task: "Das Feld hat 20 Kästchen. Färbe 15 % davon.",
25
+ solution: "3 Kästchen: 20 · 15 : 100 = 3",
26
+ });
27
+ expect(reason(task, 15, props)).toBe("15 Kästchen sind 15 % von 100.");
28
+ expect(reason(task, 17, props)).toBe("Das ist der Rest.");
29
+ expect(reason(task, 4, props)).toBeUndefined();
30
+ });
@@ -0,0 +1,121 @@
1
+ // The logic of ProzentFeld.vue, a field of G squares, together 100 %, of which the learner colours p %: pure
2
+ // functions, which the build tests over fixed seeds (the exercise contract). A task is G and p; an answer is the
3
+ // number of squares coloured, whichever they are. The explainer checks an answer with check and gives the message of
4
+ // reason. describe states a task and its solution, about what the exercise is; template, params, mistakes and show give
5
+ // a tutor the task once and each number's inputs and outputs.
6
+
7
+ /** The props the logic needs: the task with {G} and {p}, and the app's message for each typical mistake. */
8
+ export interface Props {
9
+ task: string;
10
+ mistakes?: readonly string[];
11
+ }
12
+
13
+ export interface Task {
14
+ /** The number of squares, the whole: 100 %. */
15
+ G: number;
16
+ /** The percentage to colour. */
17
+ p: number;
18
+ }
19
+
20
+ /** The number of squares coloured. */
21
+ export type Answer = number;
22
+
23
+ /** The sizes of the field: never 100, so that p squares is a mistake. */
24
+ const SIZES = [20, 25, 40, 50];
25
+
26
+ /** A small seeded random generator (mulberry32). */
27
+ function random(seed: number): () => number {
28
+ let state = seed >>> 0;
29
+ return () => {
30
+ state = (state + 0x6d2b79f5) >>> 0;
31
+ let t = state;
32
+ t = Math.imul(t ^ (t >>> 15), t | 1);
33
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
34
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
35
+ };
36
+ }
37
+
38
+ /** The squares p % of the field are. */
39
+ const part = (task: Task) => (task.G * task.p) / 100;
40
+
41
+ /**
42
+ * The percentages of a field of `G` squares: whole squares, neither 50 % (the rest would be as many) nor a p whose
43
+ * rest is p squares (the two mistakes would give the same answer).
44
+ */
45
+ const percentages = (G: number) =>
46
+ Array.from({ length: 99 }, (_, i) => i + 1).filter(
47
+ (p) => Number.isInteger((G * p) / 100) && p !== 50 && G - (G * p) / 100 !== p,
48
+ );
49
+
50
+ /** The task of `seed`: a size and a percentage of it. */
51
+ export function generate(seed: number, _props?: Props): Task {
52
+ const next = random(seed);
53
+ const G = SIZES[Math.floor(next() * SIZES.length)] ?? 20;
54
+ const choices = percentages(G);
55
+ return { G, p: choices[Math.floor(next() * choices.length)] ?? 10 };
56
+ }
57
+
58
+ /** Whether `answer` squares are p % of the field. */
59
+ export function check(task: Task, answer: Answer): boolean {
60
+ return answer === part(task);
61
+ }
62
+
63
+ /** The right answer. */
64
+ export function solution(task: Task): Answer {
65
+ return part(task);
66
+ }
67
+
68
+ /** A wrong answer: the rest coloured. */
69
+ export function wrong(task: Task): Answer {
70
+ return task.G - part(task);
71
+ }
72
+
73
+ /** The answer of each typical mistake of about.mistakes: p squares (none when p is more than the field), the rest. */
74
+ export function mistakes(task: Task): (Answer | undefined)[] {
75
+ return [task.p < task.G ? task.p : undefined, wrong(task)];
76
+ }
77
+
78
+ /** A text of the props with {G}, {p} and {n} filled in. */
79
+ export const fill = (text: string, task: Task, n = 0) =>
80
+ text.replace(/\{G\}/g, String(task.G)).replace(/\{p\}/g, String(task.p)).replace(/\{n\}/g, String(n));
81
+
82
+ /** The app's message for a wrong answer, from the props: that of its typical mistake, or none. */
83
+ export function reason(task: Task, answer: Answer, props: Props, _lang?: string): string | undefined {
84
+ const index = mistakes(task).findIndex((mistaken) => mistaken === answer);
85
+ const message = index < 0 ? undefined : props.mistakes?.[index];
86
+ return message === undefined ? undefined : fill(message, task, answer);
87
+ }
88
+
89
+ /** The task as a learner sees it, and its solution. */
90
+ export function describe(task: Task, props: Props, _lang: string): { task: string; solution: string } {
91
+ return {
92
+ task: fill(props.task, task),
93
+ solution: `${show(part(task))}: ${task.G} · ${task.p} : 100 = ${part(task)}`,
94
+ };
95
+ }
96
+
97
+ /** The task as a learner reads it, with {G} and {p}: the prop task. */
98
+ export function template(props: Props, _lang: string): string {
99
+ return props.task;
100
+ }
101
+
102
+ /** What sets a task: the size of the field and the percentage. */
103
+ export function params(task: Task, _props: Props, _lang: string): Record<string, number> {
104
+ return { G: task.G, p: task.p };
105
+ }
106
+
107
+ /** An answer as text. */
108
+ export function show(answer: Answer, _task?: Task, _props?: Props, _lang?: string): string {
109
+ return `${answer} Kästchen`;
110
+ }
111
+
112
+ /** What the exercise is, for a tutor. */
113
+ export const about = {
114
+ task: "Ein Feld aus G Kästchen ist das Ganze, also 100 %. Das Kind färbt durch Antippen p % davon und drückt „Prüfen“ unter dem Feld. Wie das Hunderterfeld, nur hat das Feld nicht 100 Kästchen.",
115
+ answer: "Die Zahl der gefärbten Kästchen; welche es sind, ist egal.",
116
+ correct: "Richtig, wenn genau G · p : 100 Kästchen gefärbt sind.",
117
+ mistakes: [
118
+ "p Kästchen gefärbt, als hätte das Feld 100 Kästchen.",
119
+ "Den Rest gefärbt: die Kästchen, die nicht zu den p % gehören.",
120
+ ],
121
+ };
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ // A field of G squares, together 100 %, of which the learner colours p % by tapping, for the task of the number seed.
3
+ // Its logic is in ProzentFeld.ts. It is the input of the task: it emits the number of squares coloured with answer,
4
+ // and the explainer draws "Prüfen", checks the answer, gives the feedback with the message of a typical mistake, the
5
+ // task number and "Neue Aufgabe". Once the answer is checked, the field shows it as it is until the learner changes it.
6
+ import { computed, ref } from "vue";
7
+ import { fill, generate } from "./ProzentFeld.ts";
8
+
9
+ const props = defineProps<{
10
+ /** The task, with {G} for the number of squares and {p} for the percentage. */
11
+ task: string;
12
+ /** The count below the field, with {n} for the squares coloured and {G}. */
13
+ counter: string;
14
+ /** The name of a square for screen readers, with {n} for its number. */
15
+ cell: string;
16
+ /** The app's message for each typical mistake, in the order of about.mistakes, with {G}, {p} and {n}. */
17
+ mistakes: string[];
18
+ /** The task number, which the explainer gives. */
19
+ seed: number;
20
+ /** Whether the explainer checked the answer the field gives now. */
21
+ checked: boolean;
22
+ }>();
23
+ const emit = defineEmits<{ answer: [number] }>();
24
+
25
+ // the task of the number the explainer gives; "Neue Aufgabe" mounts the component afresh with another
26
+ const current = computed(() => generate(props.seed, props));
27
+ const coloured = ref<boolean[]>(Array.from({ length: current.value.G }, () => false));
28
+ const count = computed(() => coloured.value.filter(Boolean).length);
29
+ // ten squares to a row, five for a field of 25
30
+ const columns = computed(() => (current.value.G === 25 ? 5 : 10));
31
+
32
+ function toggle(index: number): void {
33
+ coloured.value[index] = !coloured.value[index];
34
+ emit("answer", count.value);
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <div class="prozent-feld">
40
+ <p>{{ fill(task, current) }}</p>
41
+ <div
42
+ class="prozent-feld-grid"
43
+ :class="{ 'prozent-feld-checked': checked }"
44
+ :style="{ gridTemplateColumns: `repeat(${columns}, 1fr)` }"
45
+ >
46
+ <button
47
+ v-for="(on, index) in coloured"
48
+ :key="index"
49
+ type="button"
50
+ :aria-pressed="on"
51
+ :aria-label="fill(cell, current, index + 1)"
52
+ @click="toggle(index)"
53
+ ></button>
54
+ </div>
55
+ <p aria-live="polite">{{ fill(counter, current, count) }}</p>
56
+ </div>
57
+ </template>
58
+
59
+ <style scoped>
60
+ .prozent-feld-grid {
61
+ display: grid;
62
+ gap: 2px;
63
+ max-width: 20rem;
64
+ }
65
+
66
+ .prozent-feld-grid button {
67
+ aspect-ratio: 1;
68
+ min-width: 1.5rem;
69
+ min-height: 1.5rem;
70
+ padding: 0;
71
+ border: 1px solid #1a1a1a;
72
+ border-radius: 0;
73
+ background: #fff;
74
+ }
75
+
76
+ .prozent-feld-grid button[aria-pressed="true"] {
77
+ background: #2f5fb3;
78
+ }
79
+
80
+ /* the answer checked: its squares a little lighter until the learner changes it */
81
+ .prozent-feld-checked button[aria-pressed="true"] {
82
+ background: #5b7fc4;
83
+ }
84
+ </style>
@@ -0,0 +1,105 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="240" height="300" viewBox="0 0 240 300">
2
+ <title>Hunderterfeld mit zwei Klammern</title>
3
+ <rect x="20" y="46" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
4
+ <rect x="40" y="46" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
5
+ <rect x="60" y="46" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
6
+ <rect x="80" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
7
+ <rect x="100" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
8
+ <rect x="120" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
9
+ <rect x="140" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
10
+ <rect x="160" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
11
+ <rect x="180" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
12
+ <rect x="200" y="46" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
13
+ <rect x="20" y="66" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
14
+ <rect x="40" y="66" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
15
+ <rect x="60" y="66" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
16
+ <rect x="80" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
17
+ <rect x="100" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
18
+ <rect x="120" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
19
+ <rect x="140" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
20
+ <rect x="160" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
21
+ <rect x="180" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
22
+ <rect x="200" y="66" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
23
+ <rect x="20" y="86" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
24
+ <rect x="40" y="86" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
25
+ <rect x="60" y="86" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
26
+ <rect x="80" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
27
+ <rect x="100" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
28
+ <rect x="120" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
29
+ <rect x="140" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
30
+ <rect x="160" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
31
+ <rect x="180" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
32
+ <rect x="200" y="86" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
33
+ <rect x="20" y="106" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
34
+ <rect x="40" y="106" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
35
+ <rect x="60" y="106" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
36
+ <rect x="80" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
37
+ <rect x="100" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
38
+ <rect x="120" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
39
+ <rect x="140" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
40
+ <rect x="160" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
41
+ <rect x="180" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
42
+ <rect x="200" y="106" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
43
+ <rect x="20" y="126" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
44
+ <rect x="40" y="126" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
45
+ <rect x="60" y="126" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
46
+ <rect x="80" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
47
+ <rect x="100" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
48
+ <rect x="120" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
49
+ <rect x="140" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
50
+ <rect x="160" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
51
+ <rect x="180" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
52
+ <rect x="200" y="126" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
53
+ <rect x="20" y="146" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
54
+ <rect x="40" y="146" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
55
+ <rect x="60" y="146" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
56
+ <rect x="80" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
57
+ <rect x="100" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
58
+ <rect x="120" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
59
+ <rect x="140" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
60
+ <rect x="160" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
61
+ <rect x="180" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
62
+ <rect x="200" y="146" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
63
+ <rect x="20" y="166" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
64
+ <rect x="40" y="166" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
65
+ <rect x="60" y="166" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
66
+ <rect x="80" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
67
+ <rect x="100" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
68
+ <rect x="120" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
69
+ <rect x="140" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
70
+ <rect x="160" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
71
+ <rect x="180" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
72
+ <rect x="200" y="166" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
73
+ <rect x="20" y="186" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
74
+ <rect x="40" y="186" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
75
+ <rect x="60" y="186" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
76
+ <rect x="80" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
77
+ <rect x="100" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
78
+ <rect x="120" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
79
+ <rect x="140" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
80
+ <rect x="160" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
81
+ <rect x="180" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
82
+ <rect x="200" y="186" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
83
+ <rect x="20" y="206" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
84
+ <rect x="40" y="206" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
85
+ <rect x="60" y="206" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
86
+ <rect x="80" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
87
+ <rect x="100" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
88
+ <rect x="120" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
89
+ <rect x="140" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
90
+ <rect x="160" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
91
+ <rect x="180" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
92
+ <rect x="200" y="206" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
93
+ <rect x="20" y="226" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
94
+ <rect x="40" y="226" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
95
+ <rect x="60" y="226" width="20" height="20" fill="#4a90d9" stroke="#6b6b6b" stroke-width="1"/>
96
+ <rect x="80" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
97
+ <rect x="100" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
98
+ <rect x="120" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
99
+ <rect x="140" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
100
+ <rect x="160" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
101
+ <rect x="180" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
102
+ <rect x="200" y="226" width="20" height="20" fill="#ffffff" stroke="#6b6b6b" stroke-width="1"/>
103
+ <path d="M20 40 Q20 35 25 35 H45 Q50 35 50 28 Q50 35 55 35 H75 Q80 35 80 40" fill="none" stroke="#1a1a1a" stroke-width="2"/>
104
+ <path d="M20 252 Q20 257 25 257 H115 Q120 257 120 264 Q120 257 125 257 H215 Q220 257 220 252" fill="none" stroke="#1a1a1a" stroke-width="2"/>
105
+ </svg>
@@ -0,0 +1,4 @@
1
+ import "@lernapps/app-templates/explainer/style.css";
2
+ import { startExplainer } from "@lernapps/app-templates/explainer";
3
+
4
+ startExplainer();
@@ -0,0 +1,18 @@
1
+ # yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json
2
+ title: Prozent
3
+ summary: Mit Prozenten vergleichst du Anteile, egal wie groß das Ganze ist.
4
+ pages: [prozentwert, prozentsatz, grundwert]
5
+ # The chapter's self-assessment, offered on the start page (an app with one chapter): three or four tasks per page,
6
+ # prozentwert-ueben and the exercise component prozentfeld each drawing a task of their own, the open task
7
+ # tauschen-erklaeren answered in the learner's own words.
8
+ selfAssessment:
9
+ pages:
10
+ - exercises:
11
+ - /prozent/prozentwert/#prozentwert-ueben
12
+ - /prozent/prozentwert/#prozentfeld
13
+ - /prozent/prozentwert/#tauschen-erklaeren
14
+ - /prozent/prozentsatz/#welche-rechnung
15
+ - exercises:
16
+ - /prozent/prozentsatz/#prozentsatz-ueben
17
+ - /prozent/grundwert/#klammern
18
+ - /prozent/grundwert/#grundwert-ueben