@bendyline/gilde 0.1.92 → 0.1.93

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 (109) hide show
  1. package/README.md +6 -4
  2. package/data/craftbook-templates/fr/freeze-scope/versions/1.4.0/craftbook.json +94 -0
  3. package/data/craftbook-templates/fr/freeze-scope/versions/1.4.0/test.json +131 -0
  4. package/data/craftbook-templates/index.json +1 -1
  5. package/data/craftbook-templates/na/narrated-slideshow/versions/1.1.7/craftbook.json +315 -0
  6. package/data/craftbook-templates/na/narrated-slideshow/versions/1.1.7/test.json +89 -0
  7. package/data/craftbook-templates/ph/photo-library-nightly/manifest.json +22 -0
  8. package/data/craftbook-templates/ph/photo-library-nightly/versions/1.0.0/craftbook.json +186 -0
  9. package/data/craftbook-templates/ph/photo-library-nightly/versions/1.0.0/test.json +69 -0
  10. package/data/craftbook-templates/po/powerpoint-deck/versions/1.7.17/craftbook.json +486 -0
  11. package/data/craftbook-templates/po/powerpoint-deck/versions/1.7.17/test.json +265 -0
  12. package/data/craftbook-templates/raw-index.json +1 -1
  13. package/data/craftbook-templates/re/report-pdf/versions/1.1.7/craftbook.json +316 -0
  14. package/data/craftbook-templates/re/report-pdf/versions/1.1.7/test.json +101 -0
  15. package/data/craftbook-templates/re/research-to-document/versions/1.2.7/craftbook.json +387 -0
  16. package/data/craftbook-templates/re/research-to-document/versions/1.2.7/test.json +242 -0
  17. package/data/gezel-templates/cu/curator/manifest.json +20 -0
  18. package/data/gezel-templates/cu/curator/versions/1.0.0/about.md +22 -0
  19. package/data/gezel-templates/cu/curator/versions/1.0.0/manifest.json +23 -0
  20. package/data/gezel-templates/index.json +1 -1
  21. package/data/gezel-templates/pu/puzzelaar/manifest.json +19 -0
  22. package/data/gezel-templates/pu/puzzelaar/versions/1.0.0/about.md +18 -0
  23. package/data/gezel-templates/pu/puzzelaar/versions/1.0.0/manifest.json +16 -0
  24. package/data/gezel-templates/raw-index.json +1 -1
  25. package/data/gezel-templates/wo/woordsmid/manifest.json +19 -0
  26. package/data/gezel-templates/wo/woordsmid/versions/1.0.0/about.md +18 -0
  27. package/data/gezel-templates/wo/woordsmid/versions/1.0.0/manifest.json +16 -0
  28. package/data/knowledge-catalogs/be/bendyline-how-to-books/manifest.json +26 -0
  29. package/data/knowledge-catalogs/be/bendyline-how-to-books/versions/2026.10.8/manifest.json +8950 -0
  30. package/data/knowledge-catalogs/index.json +1 -1
  31. package/data/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/manifest.json +22 -0
  32. package/data/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/versions/2026.10.16/manifest.json +18270 -0
  33. package/data/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/manifest.json +23 -0
  34. package/data/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/versions/2026.10.16/manifest.json +1098 -0
  35. package/data/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/manifest.json +23 -0
  36. package/data/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/versions/2026.10.16/manifest.json +722 -0
  37. package/data/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/manifest.json +23 -0
  38. package/data/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/versions/2026.10.16/manifest.json +530 -0
  39. package/data/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/manifest.json +23 -0
  40. package/data/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/versions/2026.10.16/manifest.json +602 -0
  41. package/data/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/manifest.json +23 -0
  42. package/data/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/versions/2026.10.16/manifest.json +250 -0
  43. package/data/knowledge-catalogs/mi/microsoftdocs-dotnet-en/manifest.json +22 -0
  44. package/data/knowledge-catalogs/mi/microsoftdocs-dotnet-en/versions/2026.10.16/manifest.json +16794 -0
  45. package/data/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/manifest.json +22 -0
  46. package/data/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/versions/2026.10.16/manifest.json +1106 -0
  47. package/data/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/manifest.json +22 -0
  48. package/data/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/versions/2026.10.16/manifest.json +234 -0
  49. package/data/knowledge-catalogs/mi/microsoftdocs-powershell-en/manifest.json +22 -0
  50. package/data/knowledge-catalogs/mi/microsoftdocs-powershell-en/versions/2026.10.16/manifest.json +522 -0
  51. package/data/knowledge-catalogs/mi/microsoftdocs-sql-server-en/manifest.json +22 -0
  52. package/data/knowledge-catalogs/mi/microsoftdocs-sql-server-en/versions/2026.10.16/manifest.json +1850 -0
  53. package/data/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/manifest.json +22 -0
  54. package/data/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/versions/2026.10.16/manifest.json +1734 -0
  55. package/data/knowledge-catalogs/mi/microsoftdocs-windows-server-en/manifest.json +22 -0
  56. package/data/knowledge-catalogs/mi/microsoftdocs-windows-server-en/versions/2026.10.16/manifest.json +774 -0
  57. package/data/knowledge-catalogs/raw-index.json +1 -1
  58. package/data/project-types/ch/checkers/versions/1.2.3/about.md +9 -0
  59. package/data/project-types/ch/checkers/versions/1.2.3/game.json +77 -0
  60. package/data/project-types/ch/checkers/versions/1.2.3/manifest.json +182 -0
  61. package/data/project-types/ch/checkers/versions/1.2.3/mission.md +9 -0
  62. package/data/project-types/ch/checkers/versions/1.2.3/pages/board/index.html +1158 -0
  63. package/data/project-types/ch/chess/versions/1.0.6/about.md +9 -0
  64. package/data/project-types/ch/chess/versions/1.0.6/game.json +127 -0
  65. package/data/project-types/ch/chess/versions/1.0.6/manifest.json +196 -0
  66. package/data/project-types/ch/chess/versions/1.0.6/mission.md +8 -0
  67. package/data/project-types/ch/chess/versions/1.0.6/pages/board/index.html +851 -0
  68. package/data/project-types/fi/fitness-coach/versions/1.1.0/about.md +8 -0
  69. package/data/project-types/fi/fitness-coach/versions/1.1.0/craftbooks/training-recap.json +34 -0
  70. package/data/project-types/fi/fitness-coach/versions/1.1.0/manifest.json +229 -0
  71. package/data/project-types/fi/fitness-coach/versions/1.1.0/mission.md +7 -0
  72. package/data/project-types/fi/fitness-coach/versions/1.1.0/pages/dashboard/index.html +383 -0
  73. package/data/project-types/fi/fitness-coach/versions/1.1.0/training.json +6 -0
  74. package/data/project-types/fl/flashcards/versions/1.1.2/about.md +8 -0
  75. package/data/project-types/fl/flashcards/versions/1.1.2/deck.json +7 -0
  76. package/data/project-types/fl/flashcards/versions/1.1.2/manifest.json +169 -0
  77. package/data/project-types/fl/flashcards/versions/1.1.2/mission.md +6 -0
  78. package/data/project-types/fl/flashcards/versions/1.1.2/pages/review/index.html +477 -0
  79. package/data/project-types/fl/flashcards/versions/1.2.0/about.md +8 -0
  80. package/data/project-types/fl/flashcards/versions/1.2.0/deck.json +7 -0
  81. package/data/project-types/fl/flashcards/versions/1.2.0/manifest.json +207 -0
  82. package/data/project-types/fl/flashcards/versions/1.2.0/mission.md +6 -0
  83. package/data/project-types/fl/flashcards/versions/1.2.0/pages/review/index.html +549 -0
  84. package/data/project-types/go/go/versions/1.0.4/about.md +9 -0
  85. package/data/project-types/go/go/versions/1.0.4/game.json +103 -0
  86. package/data/project-types/go/go/versions/1.0.4/manifest.json +182 -0
  87. package/data/project-types/go/go/versions/1.0.4/mission.md +8 -0
  88. package/data/project-types/go/go/versions/1.0.4/pages/board/index.html +855 -0
  89. package/data/project-types/index.json +1 -1
  90. package/data/project-types/la/language-trainer/versions/1.1.0/about.md +8 -0
  91. package/data/project-types/la/language-trainer/versions/1.1.0/manifest.json +245 -0
  92. package/data/project-types/la/language-trainer/versions/1.1.0/mission.md +4 -0
  93. package/data/project-types/la/language-trainer/versions/1.1.0/pages/dashboard/index.html +468 -0
  94. package/data/project-types/la/language-trainer/versions/1.1.0/progress.json +10 -0
  95. package/data/project-types/la/language-trainer/versions/1.1.0/reviews.json +3 -0
  96. package/data/project-types/la/language-trainer/versions/1.1.0/scenarios.json +84 -0
  97. package/data/project-types/raw-index.json +1 -1
  98. package/data/project-types/wo/word-clues/manifest.json +20 -0
  99. package/data/project-types/wo/word-clues/versions/1.0.0/about.md +7 -0
  100. package/data/project-types/wo/word-clues/versions/1.0.0/game.json +14 -0
  101. package/data/project-types/wo/word-clues/versions/1.0.0/manifest.json +205 -0
  102. package/data/project-types/wo/word-clues/versions/1.0.0/mission.md +3 -0
  103. package/data/project-types/wo/word-clues/versions/1.0.0/pages/board/index.html +399 -0
  104. package/data/project-types/wo/word-clues/versions/1.0.0/words.json +216 -0
  105. package/package.json +5 -3
  106. package/schemas/craftbook-doc.schema.json +14 -0
  107. package/schemas/craftbook-template-version.schema.json +14 -0
  108. package/schemas/craftbook-test.schema.json +2 -0
  109. package/schemas/project-type-version.schema.json +131 -0
@@ -0,0 +1,468 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="gezel-demo" content="standalone" />
7
+ <title>Language practice</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light dark;
11
+ --bg: #faf7f2;
12
+ --card: #fff;
13
+ --ink: #2b2620;
14
+ --muted: #7a7166;
15
+ --accent: #b5651d;
16
+ --track: #ece5da;
17
+ --good: #4a7c59;
18
+ --warn: #a4552e;
19
+ }
20
+ :root[data-theme='dark'] {
21
+ --bg: #1c1a17;
22
+ --card: #262320;
23
+ --ink: #efe9e0;
24
+ --muted: #a59a8c;
25
+ --accent: #e0894a;
26
+ --track: #3a352f;
27
+ --good: #7fae8d;
28
+ --warn: #d98a63;
29
+ }
30
+ @media (prefers-color-scheme: dark) {
31
+ :root:not([data-theme='light']) {
32
+ --bg: #1c1a17;
33
+ --card: #262320;
34
+ --ink: #efe9e0;
35
+ --muted: #a59a8c;
36
+ --accent: #e0894a;
37
+ --track: #3a352f;
38
+ --good: #7fae8d;
39
+ --warn: #d98a63;
40
+ }
41
+ }
42
+ * { box-sizing: border-box; }
43
+ body {
44
+ margin: 0;
45
+ font: 15px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
46
+ background: var(--bg);
47
+ color: var(--ink);
48
+ padding: 1.25rem 1rem;
49
+ }
50
+ main { max-width: 600px; margin: 0 auto; }
51
+ h1 {
52
+ font-family: Georgia, 'PT Serif', 'Times New Roman', serif;
53
+ font-size: 1.35rem;
54
+ font-weight: 600;
55
+ margin: 0 0 0.15rem;
56
+ }
57
+ h2 { font-size: 0.95rem; margin: 1.25rem 0 0.5rem; }
58
+ .sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 1rem; }
59
+ .tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.5rem; }
60
+ .tile {
61
+ background: var(--card);
62
+ border: 1px solid var(--track);
63
+ border-radius: 10px;
64
+ padding: 0.65rem 0.4rem;
65
+ text-align: center;
66
+ }
67
+ .tile .n { font-size: 1.6rem; font-weight: 700; color: var(--accent); }
68
+ .tile .l { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
69
+ .panel {
70
+ background: var(--card);
71
+ border: 1px solid var(--track);
72
+ border-radius: 10px;
73
+ padding: 0.85rem 1rem;
74
+ }
75
+ .panel p { margin: 0.2rem 0; }
76
+ .muted { color: var(--muted); }
77
+ .scenes { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.5rem; }
78
+ button {
79
+ font: inherit;
80
+ color: var(--ink);
81
+ background: var(--card);
82
+ border: 1px solid var(--track);
83
+ border-radius: 6px;
84
+ min-height: 44px;
85
+ padding: 0.45rem 0.75rem;
86
+ cursor: pointer;
87
+ text-align: left;
88
+ }
89
+ button:hover:not(:disabled) { border-color: var(--accent); }
90
+ button:disabled { opacity: 0.5; cursor: default; }
91
+ .scene-btn .t { display: block; font-weight: 600; }
92
+ .scene-btn .r { display: block; font-size: 0.8rem; color: var(--muted); }
93
+ .actions { display: flex; gap: 0.5rem; margin-top: 0.65rem; flex-wrap: wrap; }
94
+ .actions button { text-align: center; }
95
+ button.got { border-color: var(--good); color: var(--good); }
96
+ button.miss { border-color: var(--warn); color: var(--warn); }
97
+ .card-face { font-size: 1.1rem; margin: 0.25rem 0; overflow-wrap: anywhere; }
98
+ .card-face s { color: var(--warn); }
99
+ .right { color: var(--good); font-weight: 600; }
100
+ ul.history { margin: 0; padding-left: 1.1rem; color: var(--muted); }
101
+ ul.history li { margin: 0.15rem 0; }
102
+ .banner {
103
+ border: 1px solid var(--accent);
104
+ border-radius: 6px;
105
+ padding: 0.45rem 0.7rem;
106
+ font-size: 0.85rem;
107
+ margin-bottom: 0.75rem;
108
+ }
109
+ .banner[hidden] { display: none; }
110
+ .demo-banner {
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: space-between;
114
+ gap: 0.75rem;
115
+ margin: 0 0 0.85rem;
116
+ padding: 0.5rem 0.65rem;
117
+ border: 1px solid var(--accent);
118
+ border-radius: 6px;
119
+ background: color-mix(in srgb, var(--card) 88%, var(--accent));
120
+ color: var(--ink);
121
+ font-size: 0.8rem;
122
+ }
123
+ .demo-banner[hidden] { display: none; }
124
+ .demo-reset { flex: none; text-align: center; }
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <main>
129
+ <div class="demo-banner" data-gezel-demo-banner hidden>
130
+ <span><strong>Demo mode</strong>: sample data stays in this page.</span>
131
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
132
+ </div>
133
+ <h1 id="title">Your language practice</h1>
134
+ <p class="sub" id="sub">Loading your progress…</p>
135
+ <p class="banner" id="notice" hidden></p>
136
+ <div class="tiles">
137
+ <div class="tile"><div class="n" id="level">-</div><div class="l">Level</div></div>
138
+ <div class="tile"><div class="n" id="streak">-</div><div class="l">Day streak</div></div>
139
+ <div class="tile"><div class="n" id="due">-</div><div class="l">Reviews due</div></div>
140
+ </div>
141
+
142
+ <section id="scene-section" hidden>
143
+ <h2>Scene in play</h2>
144
+ <div class="panel">
145
+ <p><strong id="scene-title"></strong></p>
146
+ <p class="muted" id="scene-detail"></p>
147
+ <p id="scene-goal"></p>
148
+ <p class="muted">Answer your tutor in the chat. Corrections appear under each reply.</p>
149
+ <div class="actions"><button type="button" id="end-scene">End scene</button></div>
150
+ </div>
151
+ </section>
152
+
153
+ <section id="review-section">
154
+ <h2>Review</h2>
155
+ <div class="panel" id="review"></div>
156
+ </section>
157
+
158
+ <section>
159
+ <h2>Scenes</h2>
160
+ <div class="scenes" id="scenes"></div>
161
+ </section>
162
+
163
+ <section id="history-section" hidden>
164
+ <h2>Recent scenes</h2>
165
+ <ul class="history" id="history"></ul>
166
+ </section>
167
+ </main>
168
+ <script>
169
+ /* gezel-page-demo-stub v1 */
170
+ /*
171
+ * The canonical demo stub for Gezel type pages. MIT License.
172
+ *
173
+ * Paste this file's bytes verbatim into a <script> block ahead of the
174
+ * page's own code, then call makeDemoGezel({ page, tools, data }) with the
175
+ * page's demo handlers. Served by Gezel, the real window.gezel is already
176
+ * injected and the stub steps aside; opened as a raw file (the live demos
177
+ * on gezelgilde.com), it installs a window.gezel whose tools and data are
178
+ * the page-supplied handler maps — one code path in every mode. `tools`
179
+ * and `data` map a tool name / declared read path to a function; data
180
+ * handlers back read() and list(), and url() returns the handler's value
181
+ * or passes a data: URI through. See docs/page-authoring.md.
182
+ */
183
+ (function () {
184
+ 'use strict';
185
+ window.makeDemoGezel = function (opts) {
186
+ if (window.gezel) return window.gezel;
187
+ var page = (opts && opts.page) || {};
188
+ var tools = (opts && opts.tools) || {};
189
+ var data = (opts && opts.data) || {};
190
+ var themeListeners = [];
191
+ var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
192
+ var ui = {
193
+ theme: { mode: media && media.matches ? 'dark' : 'light' },
194
+ onTheme: function (cb) {
195
+ themeListeners.push(cb);
196
+ return function () {
197
+ var at = themeListeners.indexOf(cb);
198
+ if (at >= 0) themeListeners.splice(at, 1);
199
+ };
200
+ },
201
+ };
202
+ if (media && media.addEventListener) {
203
+ media.addEventListener('change', function (ev) {
204
+ ui.theme = { mode: ev.matches ? 'dark' : 'light' };
205
+ themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
206
+ });
207
+ }
208
+ function fail(code, message) {
209
+ var err = new Error(message);
210
+ err.code = code;
211
+ return Promise.reject(err);
212
+ }
213
+ function run(map, key, arg) {
214
+ if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
215
+ return Promise.resolve().then(function () { return map[key](arg); });
216
+ }
217
+ window.gezel = {
218
+ page: {
219
+ api: 1,
220
+ projectId: 'demo',
221
+ source: 'type',
222
+ entry: page.entry || 'index.html',
223
+ typeName: page.typeName || 'demo',
224
+ params: page.params || {},
225
+ mode: 'demo',
226
+ },
227
+ tools: {
228
+ list: function () { return Promise.resolve(Object.keys(tools)); },
229
+ invoke: function (name, input) {
230
+ var call = run(tools, name, input || {});
231
+ if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
232
+ return call.then(function (output) { return { output: output }; });
233
+ },
234
+ },
235
+ data: {
236
+ read: function (path, o) {
237
+ return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
238
+ },
239
+ list: function (path, o) {
240
+ return run(data, path, o || {}) || Promise.resolve([]);
241
+ },
242
+ watch: function () { return function () {}; },
243
+ url: function (path) {
244
+ if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
245
+ return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
246
+ },
247
+ },
248
+ ui: ui,
249
+ refresh: function () {},
250
+ };
251
+ return window.gezel;
252
+ };
253
+ })();
254
+ </script>
255
+ <script>
256
+ // Reads progress.json, scenarios.json and reviews.json (pages.reads) and
257
+ // acts through the page tools start_scene, review_card and end_scene.
258
+ (function () {
259
+ 'use strict';
260
+
261
+ // ── demo data (used only when the page opens as a raw file) ──
262
+ var DAY = 24 * 60 * 60 * 1000;
263
+ var demoNow = Date.now();
264
+ var demo = {
265
+ progress: {
266
+ language: 'Spanish',
267
+ level: 2,
268
+ sessions: 6,
269
+ streak: 4,
270
+ lastPracticeDay: null,
271
+ scene: null,
272
+ history: [
273
+ { day: '2026-10-03', scene: 'Ordering at a café', turns: 6, mistakes: 2 },
274
+ { day: '2026-10-05', scene: 'At the market', turns: 8, mistakes: 3 },
275
+ { day: '2026-10-06', scene: 'At the pharmacy', turns: 7, mistakes: 1 },
276
+ ],
277
+ notes: [],
278
+ },
279
+ scenarios: [
280
+ { id: 'coffee', title: 'Ordering at a café', level: 1, role: 'barista', setting: 'A busy café.', goal: 'Order a drink and ask the price.' },
281
+ { id: 'market', title: 'At the market', level: 1, role: 'fruit seller', setting: 'A market stall.', goal: 'Buy three kinds of fruit.' },
282
+ { id: 'pharmacy', title: 'At the pharmacy', level: 2, role: 'pharmacist', setting: 'A small pharmacy.', goal: 'Ask what to take for a sore throat.' },
283
+ { id: 'luggage', title: 'Lost luggage', level: 2, role: 'airline agent', setting: 'The baggage desk.', goal: 'Report a missing suitcase.' },
284
+ { id: 'doctor', title: "At the doctor's", level: 3, role: 'doctor', setting: "A doctor's office.", goal: 'Explain an ankle injury.' },
285
+ ],
286
+ reviews: [
287
+ { id: 'r-1', wrong: 'una café', right: 'un café', note: 'café is masculine', box: 1, lastSeenAt: new Date(demoNow - 2 * DAY).toISOString() },
288
+ { id: 'r-2', wrong: 'soy cansado', right: 'estoy cansado', note: 'states use estar', box: 2, lastSeenAt: new Date(demoNow - 4 * DAY).toISOString() },
289
+ { id: 'r-3', wrong: 'me gusta las uvas', right: 'me gustan las uvas', note: '', box: 3, lastSeenAt: new Date(demoNow - DAY).toISOString() },
290
+ ],
291
+ };
292
+
293
+ function demoScene(id) {
294
+ var s = demo.scenarios.find(function (x) { return x.id === id; });
295
+ demo.progress.scene = { id: s.id, title: s.title, role: s.role, setting: s.setting, goal: s.goal, level: demo.progress.level, status: 'active', turns: 0, mistakes: 0 };
296
+ return { status: 'started' };
297
+ }
298
+
299
+ makeDemoGezel({
300
+ page: { entry: 'dashboard/index.html', typeName: 'Language Trainer', params: { language: 'Spanish' } },
301
+ tools: {
302
+ start_scene: function (input) { return demoScene(input.scenario); },
303
+ end_scene: function () {
304
+ if (demo.progress.scene) demo.progress.scene.status = 'done';
305
+ return { status: 'done' };
306
+ },
307
+ review_card: function (input) {
308
+ var card = demo.reviews.find(function (c) { return c.id === input.id; });
309
+ if (card) {
310
+ card.box = input.verdict === 'got-it' ? Math.min(3, card.box + 1) : 1;
311
+ card.lastSeenAt = new Date().toISOString();
312
+ }
313
+ return { status: 'idle' };
314
+ },
315
+ },
316
+ data: {
317
+ 'progress.json': function () { return demo.progress; },
318
+ 'scenarios.json': function () { return { scenarios: demo.scenarios }; },
319
+ 'reviews.json': function () { return { items: demo.reviews }; },
320
+ },
321
+ });
322
+
323
+ // ── one code path from here: window.gezel exists in every mode ──
324
+ var INTERVAL = { 1: 1, 2: 3, 3: 7 };
325
+ var state = { progress: null, scenarios: [], reviews: [], revealed: false };
326
+
327
+ function applyTheme(mode) {
328
+ document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
329
+ }
330
+ applyTheme(gezel.ui.theme.mode);
331
+ gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
332
+ var demoMode = gezel.page.mode === 'demo';
333
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
334
+
335
+ function el(id) { return document.getElementById(id); }
336
+ function esc(s) {
337
+ return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
338
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
339
+ });
340
+ }
341
+ function dueAt(card) {
342
+ return card.lastSeenAt ? Date.parse(card.lastSeenAt) + (INTERVAL[card.box] || 1) * DAY : 0;
343
+ }
344
+ function dueCards() {
345
+ var now = Date.now();
346
+ return state.reviews
347
+ .filter(function (c) { return dueAt(c) <= now; })
348
+ .sort(function (a, b) { return a.box - b.box || dueAt(a) - dueAt(b); });
349
+ }
350
+ function notice(text) {
351
+ el('notice').textContent = text;
352
+ el('notice').hidden = !text;
353
+ }
354
+
355
+ function act(tool, input) {
356
+ notice('');
357
+ return gezel.tools
358
+ .invoke(tool, input)
359
+ .then(function () { return load(); })
360
+ .catch(function (err) {
361
+ if (err && err.code === 'unavailable') notice('Scenes and reviews run in the app’s Output pane.');
362
+ else notice((err && err.message) || 'That did not work. Try again.');
363
+ });
364
+ }
365
+
366
+ function renderScene() {
367
+ var scene = state.progress && state.progress.scene;
368
+ var active = !!(scene && scene.status === 'active');
369
+ el('scene-section').hidden = !active;
370
+ if (!active) return;
371
+ el('scene-title').textContent = scene.title;
372
+ el('scene-detail').textContent = 'Your tutor plays the ' + scene.role + '. ' + (scene.setting || '');
373
+ el('scene-goal').textContent = 'Your goal: ' + scene.goal;
374
+ }
375
+
376
+ function renderReview() {
377
+ var due = dueCards();
378
+ el('due').textContent = due.length;
379
+ var box = el('review');
380
+ if (!due.length) {
381
+ var next = state.reviews.map(dueAt).filter(function (t) { return t > Date.now(); }).sort()[0];
382
+ box.innerHTML = state.reviews.length
383
+ ? '<p class="muted">Nothing due. ' + (next ? 'Next review ' + esc(new Date(next).toLocaleDateString()) + '.' : '') + '</p>'
384
+ : '<p class="muted">No corrections yet. Mistakes from your scenes land here to practice.</p>';
385
+ return;
386
+ }
387
+ var card = due[0];
388
+ box.innerHTML =
389
+ '<p class="muted">How do you say it correctly?</p>' +
390
+ '<p class="card-face"><s>' + esc(card.wrong) + '</s></p>' +
391
+ (state.revealed
392
+ ? '<p class="card-face right">' + esc(card.right) + '</p>' + (card.note ? '<p class="muted">' + esc(card.note) + '</p>' : '') +
393
+ '<div class="actions"><button type="button" class="got" data-verdict="got-it">Got it</button>' +
394
+ '<button type="button" class="miss" data-verdict="missed">Missed</button></div>'
395
+ : '<div class="actions"><button type="button" id="reveal">Show the answer</button></div>');
396
+ var reveal = el('reveal');
397
+ if (reveal) reveal.onclick = function () { state.revealed = true; renderReview(); };
398
+ Array.prototype.forEach.call(box.querySelectorAll('[data-verdict]'), function (b) {
399
+ b.onclick = function () {
400
+ state.revealed = false;
401
+ act('review_card', { id: card.id, verdict: b.getAttribute('data-verdict') });
402
+ };
403
+ });
404
+ }
405
+
406
+ function renderScenes() {
407
+ var level = (state.progress && state.progress.level) || 1;
408
+ var inPlay = !!(state.progress && state.progress.scene && state.progress.scene.status === 'active');
409
+ el('scenes').innerHTML = state.scenarios
410
+ .map(function (s) {
411
+ var locked = s.level > level;
412
+ return (
413
+ '<button type="button" class="scene-btn" data-scene="' + esc(s.id) + '"' + (locked || inPlay ? ' disabled' : '') + '>' +
414
+ '<span class="t">' + esc(s.title) + '</span>' +
415
+ '<span class="r">' + (locked ? 'Level ' + s.level : 'You talk to the ' + esc(s.role)) + '</span></button>'
416
+ );
417
+ })
418
+ .join('');
419
+ Array.prototype.forEach.call(el('scenes').querySelectorAll('[data-scene]'), function (b) {
420
+ b.onclick = function () { act('start_scene', { scenario: b.getAttribute('data-scene') }); };
421
+ });
422
+ }
423
+
424
+ function renderHistory() {
425
+ var history = (state.progress && state.progress.history) || [];
426
+ el('history-section').hidden = !history.length;
427
+ el('history').innerHTML = history
428
+ .slice(-5)
429
+ .reverse()
430
+ .map(function (h) {
431
+ return '<li>' + esc(h.scene) + ': ' + h.turns + ' turns, ' + h.mistakes + ' correction' + (h.mistakes === 1 ? '' : 's') + '</li>';
432
+ })
433
+ .join('');
434
+ }
435
+
436
+ function render() {
437
+ var p = state.progress || {};
438
+ el('title').textContent = 'Your ' + (p.language || 'language') + ' practice';
439
+ el('sub').textContent = (p.sessions || 0) + ' scene' + (p.sessions === 1 ? '' : 's') + ' played. Pick one below, or answer your tutor in the chat.';
440
+ el('level').textContent = p.level || 1;
441
+ el('streak').textContent = p.streak || 0;
442
+ renderScene();
443
+ renderReview();
444
+ renderScenes();
445
+ renderHistory();
446
+ }
447
+
448
+ function load() {
449
+ return Promise.all([
450
+ gezel.data.read('progress.json').catch(function () { return null; }),
451
+ gezel.data.read('scenarios.json').catch(function () { return null; }),
452
+ gezel.data.read('reviews.json').catch(function () { return null; }),
453
+ ]).then(function (r) {
454
+ state.progress = r[0] || { level: 1, sessions: 0, streak: 0 };
455
+ state.scenarios = (r[1] && r[1].scenarios) || [];
456
+ state.reviews = (r[2] && r[2].items) || [];
457
+ render();
458
+ });
459
+ }
460
+
461
+ el('end-scene').onclick = function () { act('end_scene', {}); };
462
+ load();
463
+ gezel.data.watch('progress.json', function () { load(); });
464
+ gezel.data.watch('reviews.json', function () { load(); });
465
+ })();
466
+ </script>
467
+ </body>
468
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "language": "{{language}}",
3
+ "level": 1,
4
+ "sessions": 0,
5
+ "streak": 0,
6
+ "lastPracticeDay": null,
7
+ "scene": null,
8
+ "history": [],
9
+ "notes": []
10
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "scenarios": [
3
+ {
4
+ "id": "coffee",
5
+ "title": "Ordering at a café",
6
+ "level": 1,
7
+ "role": "barista",
8
+ "setting": "A busy neighborhood café in the morning.",
9
+ "goal": "Order a drink and something to eat, then ask how much it costs."
10
+ },
11
+ {
12
+ "id": "market",
13
+ "title": "At the market",
14
+ "level": 1,
15
+ "role": "fruit seller",
16
+ "setting": "An outdoor market stall with fruit and vegetables.",
17
+ "goal": "Buy three kinds of fruit, ask for a price per kilo, and pay."
18
+ },
19
+ {
20
+ "id": "neighbor",
21
+ "title": "Meeting a new neighbor",
22
+ "level": 1,
23
+ "role": "new neighbor",
24
+ "setting": "The hallway of an apartment building.",
25
+ "goal": "Introduce yourself, say where you are from, and ask one question back."
26
+ },
27
+ {
28
+ "id": "directions",
29
+ "title": "Asking for directions",
30
+ "level": 1,
31
+ "role": "passer-by",
32
+ "setting": "A street corner near the train station.",
33
+ "goal": "Ask how to get to the museum and repeat the directions back."
34
+ },
35
+ {
36
+ "id": "pharmacy",
37
+ "title": "At the pharmacy",
38
+ "level": 2,
39
+ "role": "pharmacist",
40
+ "setting": "A small pharmacy; you have a sore throat.",
41
+ "goal": "Describe how you feel, ask what to take, and how often."
42
+ },
43
+ {
44
+ "id": "luggage",
45
+ "title": "Lost luggage",
46
+ "level": 2,
47
+ "role": "airline agent",
48
+ "setting": "The baggage desk at an airport.",
49
+ "goal": "Report a missing suitcase, describe it, and give a hotel address."
50
+ },
51
+ {
52
+ "id": "restaurant",
53
+ "title": "Booking a table",
54
+ "level": 2,
55
+ "role": "restaurant host on the phone",
56
+ "setting": "A phone call to a popular restaurant.",
57
+ "goal": "Book a table for four on Friday evening and ask about a vegetarian dish."
58
+ },
59
+ {
60
+ "id": "hotel",
61
+ "title": "Checking in at a hotel",
62
+ "level": 2,
63
+ "role": "hotel receptionist",
64
+ "setting": "The front desk of a small hotel.",
65
+ "goal": "Check in, ask about breakfast times, and request a quiet room."
66
+ },
67
+ {
68
+ "id": "doctor",
69
+ "title": "At the doctor's",
70
+ "level": 3,
71
+ "role": "doctor",
72
+ "setting": "A doctor's office; you hurt your ankle two days ago.",
73
+ "goal": "Explain what happened, answer questions, and understand the advice."
74
+ },
75
+ {
76
+ "id": "return",
77
+ "title": "Returning a faulty item",
78
+ "level": 3,
79
+ "role": "shop assistant",
80
+ "setting": "An electronics shop; the headphones you bought stopped working.",
81
+ "goal": "Explain the problem politely and ask for a refund or an exchange."
82
+ }
83
+ ]
84
+ }