@bendyline/gilde 0.1.91 → 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 (119) 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/ca/caselaw-washington-part-0001/manifest.json +24 -0
  31. package/data/knowledge-catalogs/ca/caselaw-washington-part-0001/versions/2026.10.1/manifest.json +318 -0
  32. package/data/knowledge-catalogs/ca/caselaw-washington-part-0002/manifest.json +24 -0
  33. package/data/knowledge-catalogs/ca/caselaw-washington-part-0002/versions/2026.10.1/manifest.json +266 -0
  34. package/data/knowledge-catalogs/ca/caselaw-washington-part-0003/manifest.json +24 -0
  35. package/data/knowledge-catalogs/ca/caselaw-washington-part-0003/versions/2026.10.1/manifest.json +186 -0
  36. package/data/knowledge-catalogs/ca/caselaw-washington-part-0004/manifest.json +24 -0
  37. package/data/knowledge-catalogs/ca/caselaw-washington-part-0004/versions/2026.10.1/manifest.json +138 -0
  38. package/data/knowledge-catalogs/ca/caselaw-washington-part-0005/manifest.json +24 -0
  39. package/data/knowledge-catalogs/ca/caselaw-washington-part-0005/versions/2026.10.1/manifest.json +138 -0
  40. package/data/knowledge-catalogs/index.json +1 -1
  41. package/data/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/manifest.json +22 -0
  42. package/data/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/versions/2026.10.16/manifest.json +18270 -0
  43. package/data/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/manifest.json +23 -0
  44. package/data/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/versions/2026.10.16/manifest.json +1098 -0
  45. package/data/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/manifest.json +23 -0
  46. package/data/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/versions/2026.10.16/manifest.json +722 -0
  47. package/data/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/manifest.json +23 -0
  48. package/data/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/versions/2026.10.16/manifest.json +530 -0
  49. package/data/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/manifest.json +23 -0
  50. package/data/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/versions/2026.10.16/manifest.json +602 -0
  51. package/data/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/manifest.json +23 -0
  52. package/data/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/versions/2026.10.16/manifest.json +250 -0
  53. package/data/knowledge-catalogs/mi/microsoftdocs-dotnet-en/manifest.json +22 -0
  54. package/data/knowledge-catalogs/mi/microsoftdocs-dotnet-en/versions/2026.10.16/manifest.json +16794 -0
  55. package/data/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/manifest.json +22 -0
  56. package/data/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/versions/2026.10.16/manifest.json +1106 -0
  57. package/data/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/manifest.json +22 -0
  58. package/data/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/versions/2026.10.16/manifest.json +234 -0
  59. package/data/knowledge-catalogs/mi/microsoftdocs-powershell-en/manifest.json +22 -0
  60. package/data/knowledge-catalogs/mi/microsoftdocs-powershell-en/versions/2026.10.16/manifest.json +522 -0
  61. package/data/knowledge-catalogs/mi/microsoftdocs-sql-server-en/manifest.json +22 -0
  62. package/data/knowledge-catalogs/mi/microsoftdocs-sql-server-en/versions/2026.10.16/manifest.json +1850 -0
  63. package/data/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/manifest.json +22 -0
  64. package/data/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/versions/2026.10.16/manifest.json +1734 -0
  65. package/data/knowledge-catalogs/mi/microsoftdocs-windows-server-en/manifest.json +22 -0
  66. package/data/knowledge-catalogs/mi/microsoftdocs-windows-server-en/versions/2026.10.16/manifest.json +774 -0
  67. package/data/knowledge-catalogs/raw-index.json +1 -1
  68. package/data/project-types/ch/checkers/versions/1.2.3/about.md +9 -0
  69. package/data/project-types/ch/checkers/versions/1.2.3/game.json +77 -0
  70. package/data/project-types/ch/checkers/versions/1.2.3/manifest.json +182 -0
  71. package/data/project-types/ch/checkers/versions/1.2.3/mission.md +9 -0
  72. package/data/project-types/ch/checkers/versions/1.2.3/pages/board/index.html +1158 -0
  73. package/data/project-types/ch/chess/versions/1.0.6/about.md +9 -0
  74. package/data/project-types/ch/chess/versions/1.0.6/game.json +127 -0
  75. package/data/project-types/ch/chess/versions/1.0.6/manifest.json +196 -0
  76. package/data/project-types/ch/chess/versions/1.0.6/mission.md +8 -0
  77. package/data/project-types/ch/chess/versions/1.0.6/pages/board/index.html +851 -0
  78. package/data/project-types/fi/fitness-coach/versions/1.1.0/about.md +8 -0
  79. package/data/project-types/fi/fitness-coach/versions/1.1.0/craftbooks/training-recap.json +34 -0
  80. package/data/project-types/fi/fitness-coach/versions/1.1.0/manifest.json +229 -0
  81. package/data/project-types/fi/fitness-coach/versions/1.1.0/mission.md +7 -0
  82. package/data/project-types/fi/fitness-coach/versions/1.1.0/pages/dashboard/index.html +383 -0
  83. package/data/project-types/fi/fitness-coach/versions/1.1.0/training.json +6 -0
  84. package/data/project-types/fl/flashcards/versions/1.1.2/about.md +8 -0
  85. package/data/project-types/fl/flashcards/versions/1.1.2/deck.json +7 -0
  86. package/data/project-types/fl/flashcards/versions/1.1.2/manifest.json +169 -0
  87. package/data/project-types/fl/flashcards/versions/1.1.2/mission.md +6 -0
  88. package/data/project-types/fl/flashcards/versions/1.1.2/pages/review/index.html +477 -0
  89. package/data/project-types/fl/flashcards/versions/1.2.0/about.md +8 -0
  90. package/data/project-types/fl/flashcards/versions/1.2.0/deck.json +7 -0
  91. package/data/project-types/fl/flashcards/versions/1.2.0/manifest.json +207 -0
  92. package/data/project-types/fl/flashcards/versions/1.2.0/mission.md +6 -0
  93. package/data/project-types/fl/flashcards/versions/1.2.0/pages/review/index.html +549 -0
  94. package/data/project-types/go/go/versions/1.0.4/about.md +9 -0
  95. package/data/project-types/go/go/versions/1.0.4/game.json +103 -0
  96. package/data/project-types/go/go/versions/1.0.4/manifest.json +182 -0
  97. package/data/project-types/go/go/versions/1.0.4/mission.md +8 -0
  98. package/data/project-types/go/go/versions/1.0.4/pages/board/index.html +855 -0
  99. package/data/project-types/index.json +1 -1
  100. package/data/project-types/la/language-trainer/versions/1.1.0/about.md +8 -0
  101. package/data/project-types/la/language-trainer/versions/1.1.0/manifest.json +245 -0
  102. package/data/project-types/la/language-trainer/versions/1.1.0/mission.md +4 -0
  103. package/data/project-types/la/language-trainer/versions/1.1.0/pages/dashboard/index.html +468 -0
  104. package/data/project-types/la/language-trainer/versions/1.1.0/progress.json +10 -0
  105. package/data/project-types/la/language-trainer/versions/1.1.0/reviews.json +3 -0
  106. package/data/project-types/la/language-trainer/versions/1.1.0/scenarios.json +84 -0
  107. package/data/project-types/raw-index.json +1 -1
  108. package/data/project-types/wo/word-clues/manifest.json +20 -0
  109. package/data/project-types/wo/word-clues/versions/1.0.0/about.md +7 -0
  110. package/data/project-types/wo/word-clues/versions/1.0.0/game.json +14 -0
  111. package/data/project-types/wo/word-clues/versions/1.0.0/manifest.json +205 -0
  112. package/data/project-types/wo/word-clues/versions/1.0.0/mission.md +3 -0
  113. package/data/project-types/wo/word-clues/versions/1.0.0/pages/board/index.html +399 -0
  114. package/data/project-types/wo/word-clues/versions/1.0.0/words.json +216 -0
  115. package/package.json +5 -3
  116. package/schemas/craftbook-doc.schema.json +14 -0
  117. package/schemas/craftbook-template-version.schema.json +14 -0
  118. package/schemas/craftbook-test.schema.json +2 -0
  119. package/schemas/project-type-version.schema.json +131 -0
@@ -0,0 +1,549 @@
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>Flashcards</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.5rem 1.25rem;
49
+ }
50
+ main { max-width: 560px; 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
+ .sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 1rem; }
58
+ .dots { display: flex; gap: 0.3rem; margin-bottom: 0.75rem; flex-wrap: wrap; }
59
+ .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--track); }
60
+ .dot.done-good { background: var(--good); }
61
+ .dot.done-miss { background: var(--warn); }
62
+ .dot.current { background: var(--accent); }
63
+ .flashcard {
64
+ background: var(--card);
65
+ border: 1px solid var(--track);
66
+ border-radius: 10px;
67
+ min-height: 200px;
68
+ display: grid;
69
+ place-items: center;
70
+ text-align: center;
71
+ padding: 1.5rem;
72
+ font-size: 1.15rem;
73
+ cursor: pointer;
74
+ user-select: none;
75
+ }
76
+ .flashcard .face-label {
77
+ display: block;
78
+ font-size: 0.7rem;
79
+ text-transform: uppercase;
80
+ letter-spacing: 0.05em;
81
+ color: var(--muted);
82
+ margin-bottom: 0.6rem;
83
+ }
84
+ .actions { display: flex; gap: 0.6rem; margin-top: 0.85rem; justify-content: center; }
85
+ .actions button {
86
+ border: 1px solid var(--track);
87
+ background: var(--card);
88
+ color: var(--ink);
89
+ border-radius: 6px;
90
+ padding: 0.45rem 1.1rem;
91
+ min-height: 44px;
92
+ font: inherit;
93
+ cursor: pointer;
94
+ }
95
+ .actions button.got { border-color: var(--good); color: var(--good); }
96
+ .actions button.miss { border-color: var(--warn); color: var(--warn); }
97
+ .actions button:disabled { opacity: 0.5; cursor: default; }
98
+ .empty, .done {
99
+ background: var(--card);
100
+ border: 1px dashed var(--track);
101
+ border-radius: 10px;
102
+ padding: 1.5rem;
103
+ text-align: center;
104
+ color: var(--muted);
105
+ }
106
+ .banner {
107
+ border: 1px solid var(--accent);
108
+ border-radius: 6px;
109
+ padding: 0.45rem 0.7rem;
110
+ font-size: 0.85rem;
111
+ margin-bottom: 0.75rem;
112
+ display: none;
113
+ }
114
+ .banner.show { display: block; }
115
+ .hint { font-size: 0.78rem; color: var(--muted); text-align: center; margin-top: 0.5rem; }
116
+ .note {
117
+ margin: 0 0 0.85rem;
118
+ padding: 0.5rem 0.65rem;
119
+ border: 1px solid var(--track);
120
+ border-radius: 6px;
121
+ background: var(--card);
122
+ color: var(--muted);
123
+ font-size: 0.8rem;
124
+ }
125
+ .demo-banner {
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: space-between;
129
+ gap: 0.75rem;
130
+ margin: 0 0 0.85rem;
131
+ padding: 0.5rem 0.65rem;
132
+ border: 1px solid var(--accent);
133
+ border-radius: 6px;
134
+ background: color-mix(in srgb, var(--card) 88%, var(--accent));
135
+ color: var(--ink);
136
+ font-size: 0.8rem;
137
+ }
138
+ .demo-banner[hidden] { display: none; }
139
+ .demo-banner strong { font-weight: 700; }
140
+ .demo-reset {
141
+ flex: none;
142
+ padding: 0.25rem 0.5rem;
143
+ border: 1px solid var(--accent);
144
+ border-radius: 6px;
145
+ background: var(--card);
146
+ color: var(--ink);
147
+ cursor: pointer;
148
+ font: inherit;
149
+ }
150
+ .demo-reset:hover { border-color: var(--ink); }
151
+ .forge {
152
+ margin-top: 1.5rem;
153
+ background: var(--card);
154
+ border: 1px solid var(--track);
155
+ border-radius: 10px;
156
+ padding: 0.85rem 1rem;
157
+ }
158
+ .forge h2 { font-size: 0.95rem; margin: 0 0 0.35rem; }
159
+ .forge p { margin: 0 0 0.5rem; color: var(--muted); font-size: 0.85rem; }
160
+ .forge textarea {
161
+ width: 100%;
162
+ min-height: 7rem;
163
+ resize: vertical;
164
+ font: inherit;
165
+ color: var(--ink);
166
+ background: var(--bg);
167
+ border: 1px solid var(--track);
168
+ border-radius: 6px;
169
+ padding: 0.5rem;
170
+ }
171
+ .forge .actions { justify-content: flex-start; }
172
+ </style>
173
+ </head>
174
+ <body>
175
+ <main>
176
+ <div class="demo-banner" data-gezel-demo-banner hidden>
177
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
178
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
179
+ </div>
180
+ <p class="note" id="browser-note" hidden>
181
+ Viewing from a browser tab — the deck stays live, but reviews happen in the app's Output pane.
182
+ </p>
183
+ <div class="banner" id="banner"></div>
184
+ <h1 id="title">Flashcards</h1>
185
+ <p class="sub" id="sub">Loading the deck…</p>
186
+ <div class="dots" id="dots"></div>
187
+ <div id="stage"></div>
188
+ <p class="hint" id="hint"></p>
189
+ <section class="forge" aria-labelledby="forge-title">
190
+ <h2 id="forge-title">Make cards from notes</h2>
191
+ <p>Paste a page of notes. Your study mate turns the key facts into cards.</p>
192
+ <textarea id="forge-notes" maxlength="3000" aria-label="Notes to turn into cards"></textarea>
193
+ <div class="actions"><button type="button" id="forge-go">Make cards</button></div>
194
+ <p id="forge-status" role="status"></p>
195
+ </section>
196
+ </main>
197
+ <script>
198
+ /* gezel-page-demo-stub v1 */
199
+ /*
200
+ * The canonical demo stub for Gezel type pages. MIT License.
201
+ *
202
+ * Paste this file's bytes verbatim into a <script> block ahead of the
203
+ * page's own code, then call makeDemoGezel({ page, tools, data }) with the
204
+ * page's demo handlers. Served by Gezel, the real window.gezel is already
205
+ * injected and the stub steps aside; opened as a raw file (the live demos
206
+ * on gezelgilde.com), it installs a window.gezel whose tools and data are
207
+ * the page-supplied handler maps — one code path in every mode. `tools`
208
+ * and `data` map a tool name / declared read path to a function; data
209
+ * handlers back read() and list(), and url() returns the handler's value
210
+ * or passes a data: URI through. See docs/page-authoring.md.
211
+ */
212
+ (function () {
213
+ 'use strict';
214
+ window.makeDemoGezel = function (opts) {
215
+ if (window.gezel) return window.gezel;
216
+ var page = (opts && opts.page) || {};
217
+ var tools = (opts && opts.tools) || {};
218
+ var data = (opts && opts.data) || {};
219
+ var themeListeners = [];
220
+ var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
221
+ var ui = {
222
+ theme: { mode: media && media.matches ? 'dark' : 'light' },
223
+ onTheme: function (cb) {
224
+ themeListeners.push(cb);
225
+ return function () {
226
+ var at = themeListeners.indexOf(cb);
227
+ if (at >= 0) themeListeners.splice(at, 1);
228
+ };
229
+ },
230
+ };
231
+ if (media && media.addEventListener) {
232
+ media.addEventListener('change', function (ev) {
233
+ ui.theme = { mode: ev.matches ? 'dark' : 'light' };
234
+ themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
235
+ });
236
+ }
237
+ function fail(code, message) {
238
+ var err = new Error(message);
239
+ err.code = code;
240
+ return Promise.reject(err);
241
+ }
242
+ function run(map, key, arg) {
243
+ if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
244
+ return Promise.resolve().then(function () { return map[key](arg); });
245
+ }
246
+ window.gezel = {
247
+ page: {
248
+ api: 1,
249
+ projectId: 'demo',
250
+ source: 'type',
251
+ entry: page.entry || 'index.html',
252
+ typeName: page.typeName || 'demo',
253
+ params: page.params || {},
254
+ mode: 'demo',
255
+ },
256
+ tools: {
257
+ list: function () { return Promise.resolve(Object.keys(tools)); },
258
+ invoke: function (name, input) {
259
+ var call = run(tools, name, input || {});
260
+ if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
261
+ return call.then(function (output) { return { output: output }; });
262
+ },
263
+ },
264
+ data: {
265
+ read: function (path, o) {
266
+ return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
267
+ },
268
+ list: function (path, o) {
269
+ return run(data, path, o || {}) || Promise.resolve([]);
270
+ },
271
+ watch: function () { return function () {}; },
272
+ url: function (path) {
273
+ if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
274
+ return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
275
+ },
276
+ },
277
+ ui: ui,
278
+ refresh: function () {},
279
+ };
280
+ return window.gezel;
281
+ };
282
+ })();
283
+ </script>
284
+ <script>
285
+ (function () {
286
+ 'use strict';
287
+
288
+ // ── demo deck (used only when the page opens as a raw file) ──
289
+ var demoDeck = {
290
+ subject: 'Dutch for a market morning',
291
+ cards: [
292
+ { id: 'card-1', front: 'Hoeveel kost dit?', back: 'How much does this cost?' },
293
+ { id: 'card-2', front: 'een bos bloemen', back: 'a bunch of flowers' },
294
+ { id: 'card-3', front: 'Mag ik pinnen?', back: 'Can I pay by card?' },
295
+ { id: 'card-4', front: 'Dat is alles, dank u.', back: 'That is all, thank you.' },
296
+ ],
297
+ dueCardIds: ['card-1', 'card-2', 'card-3', 'card-4'],
298
+ };
299
+ function demoCard(id) {
300
+ return demoDeck.cards.find(function (c) { return c.id === id; }) || null;
301
+ }
302
+
303
+ makeDemoGezel({
304
+ page: { entry: 'review/index.html', typeName: 'Flashcards' },
305
+ tools: {
306
+ record_review: function (input) {
307
+ var card = demoCard(input && input.cardId);
308
+ if (!card) throw new Error("No card '" + (input && input.cardId) + "' in the demo deck.");
309
+ // The demo deck stays due so the session can be replayed;
310
+ // Reset demo restores everything else.
311
+ return { summary: card.id + ' marked ' + input.verdict + '.' };
312
+ },
313
+ forge_from_notes: function (input) {
314
+ var notes = String((input && input.notes) || '').trim();
315
+ if (notes.length < 20) throw new Error('Paste a few sentences of notes to make cards from.');
316
+ var n = demoDeck.cards.length;
317
+ notes
318
+ .split(/[.\n]+/)
319
+ .map(function (line) { return line.trim(); })
320
+ .filter(function (line) { return line.length > 12; })
321
+ .slice(0, 3)
322
+ .forEach(function (line, i) {
323
+ var id = 'card-' + (n + i + 1);
324
+ demoDeck.cards.push({ id: id, front: 'Complete: ' + line.split(' ').slice(0, 4).join(' ') + '…', back: line });
325
+ demoDeck.dueCardIds.push(id);
326
+ });
327
+ return { status: 'forging', summary: 'Notes staged.' };
328
+ },
329
+ finish_session: function (input) {
330
+ var reviewed = (input && input.reviewed) || 0;
331
+ var missed = ((input && input.missedIds) || []).length;
332
+ return { summary: 'Reviewed ' + reviewed + ' card(s): ' + (reviewed - missed) + ' got it, ' + missed + ' missed.' };
333
+ },
334
+ },
335
+ data: {
336
+ 'deck.json': function () { return demoDeck; },
337
+ },
338
+ });
339
+
340
+ // ── one code path from here: window.gezel exists in every mode ──
341
+ var demoMode = gezel.page.mode === 'demo';
342
+ var browserMode = gezel.page.mode === 'browser';
343
+
344
+ function applyTheme(mode) {
345
+ document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
346
+ }
347
+ applyTheme(gezel.ui.theme.mode);
348
+ gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
349
+
350
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
351
+ if (browserMode) document.getElementById('browser-note').hidden = false;
352
+
353
+ var deck = null;
354
+ // The session queue is snapshotted from dueCardIds at start so a
355
+ // background change never reshuffles cards mid-session.
356
+ var queue = null;
357
+ var idx = 0;
358
+ var flipped = false;
359
+ var results = [];
360
+ var busy = false;
361
+
362
+ function esc(s) {
363
+ return String(s).replace(/[&<>"]/g, function (c) {
364
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
365
+ });
366
+ }
367
+ function setBanner(text) {
368
+ var el = document.getElementById('banner');
369
+ el.textContent = text || '';
370
+ el.className = text ? 'banner show' : 'banner';
371
+ }
372
+ function invokeErrorText(err) {
373
+ if (err && err.code === 'unavailable') {
374
+ return "Reviews happen in the app's Output pane — this browser tab stays view-only.";
375
+ }
376
+ return String(err && err.message ? err.message : err);
377
+ }
378
+ function cardById(id) {
379
+ return (deck.cards || []).find(function (c) { return c.id === id; }) || null;
380
+ }
381
+
382
+ function render() {
383
+ var stage = document.getElementById('stage');
384
+ var dots = document.getElementById('dots');
385
+ var hint = document.getElementById('hint');
386
+ if (!deck) return;
387
+ if (deck.subject) document.getElementById('title').textContent = 'Flashcards: ' + deck.subject;
388
+
389
+ if (!deck.cards || deck.cards.length === 0) {
390
+ document.getElementById('sub').textContent = 'The deck is empty.';
391
+ dots.innerHTML = '';
392
+ hint.textContent = '';
393
+ stage.innerHTML =
394
+ '<div class="empty">No cards yet — ask your Studiemaat to write the first ones.</div>';
395
+ return;
396
+ }
397
+
398
+ if (queue === null || (idx >= queue.length && queue.length === 0)) {
399
+ document.getElementById('sub').textContent =
400
+ deck.dueCardIds.length > 0
401
+ ? deck.dueCardIds.length + ' card(s) due.'
402
+ : 'Nothing due right now.';
403
+ dots.innerHTML = '';
404
+ hint.textContent = '';
405
+ stage.innerHTML =
406
+ deck.dueCardIds.length > 0
407
+ ? '<div class="actions"><button type="button" id="start">Start review (' + deck.dueCardIds.length + ')</button></div>'
408
+ : '<div class="done">All caught up — nothing due. Your Studiemaat can always add more.</div>';
409
+ var startBtn = document.getElementById('start');
410
+ if (startBtn) startBtn.addEventListener('click', startSession);
411
+ return;
412
+ }
413
+
414
+ if (idx >= queue.length) {
415
+ var missed = results.filter(function (r) { return r.verdict === 'missed'; }).length;
416
+ document.getElementById('sub').textContent =
417
+ 'Session done: ' + (results.length - missed) + ' got it, ' + missed + ' missed.';
418
+ hint.textContent = '';
419
+ stage.innerHTML = '<div class="done">Nice work — your Studiemaat has the recap.</div>';
420
+ return;
421
+ }
422
+
423
+ var card = cardById(queue[idx]);
424
+ if (!card) {
425
+ idx += 1;
426
+ render();
427
+ return;
428
+ }
429
+ document.getElementById('sub').textContent = 'Card ' + (idx + 1) + ' of ' + queue.length;
430
+ dots.innerHTML = queue
431
+ .map(function (id, i) {
432
+ if (i === idx) return '<span class="dot current"></span>';
433
+ var r = results[i];
434
+ if (!r) return '<span class="dot"></span>';
435
+ return '<span class="dot ' + (r.verdict === 'missed' ? 'done-miss' : 'done-good') + '"></span>';
436
+ })
437
+ .join('');
438
+ stage.innerHTML =
439
+ '<div class="flashcard" id="card"><div><span class="face-label">' +
440
+ (flipped ? 'Back' : 'Front') + '</span>' + esc(flipped ? card.back : card.front) +
441
+ '</div></div>' +
442
+ (flipped
443
+ ? '<div class="actions">' +
444
+ '<button type="button" class="got" id="got"' + (busy ? ' disabled' : '') + '>Got it</button>' +
445
+ '<button type="button" class="miss" id="miss"' + (busy ? ' disabled' : '') + '>Missed</button>' +
446
+ '</div>'
447
+ : '');
448
+ hint.textContent = flipped ? '' : 'Tap the card to reveal the answer.';
449
+ document.getElementById('card').addEventListener('click', function () {
450
+ if (!flipped) {
451
+ flipped = true;
452
+ render();
453
+ }
454
+ });
455
+ if (flipped) {
456
+ document.getElementById('got').addEventListener('click', function () { grade('got-it'); });
457
+ document.getElementById('miss').addEventListener('click', function () { grade('missed'); });
458
+ }
459
+ }
460
+
461
+ function startSession() {
462
+ queue = deck.dueCardIds.slice();
463
+ idx = 0;
464
+ flipped = false;
465
+ results = [];
466
+ render();
467
+ }
468
+
469
+ function grade(verdict) {
470
+ if (busy) return;
471
+ busy = true;
472
+ render();
473
+ var cardId = queue[idx];
474
+ gezel.tools
475
+ .invoke('record_review', { cardId: cardId, verdict: verdict })
476
+ .then(function () {
477
+ busy = false;
478
+ results[idx] = { cardId: cardId, verdict: verdict };
479
+ idx += 1;
480
+ flipped = false;
481
+ if (idx >= queue.length) return finishSession();
482
+ render();
483
+ })
484
+ .catch(function (err) {
485
+ busy = false;
486
+ setBanner(invokeErrorText(err));
487
+ render();
488
+ });
489
+ }
490
+
491
+ function finishSession() {
492
+ var missedIds = results
493
+ .filter(function (r) { return r.verdict === 'missed'; })
494
+ .map(function (r) { return r.cardId; });
495
+ return gezel.tools
496
+ .invoke('finish_session', { reviewed: results.length, missedIds: missedIds })
497
+ .catch(function () {})
498
+ .then(function () {
499
+ render();
500
+ return loadDeck();
501
+ });
502
+ }
503
+
504
+ function loadDeck() {
505
+ return gezel.data
506
+ .read('deck.json')
507
+ .then(function (next) {
508
+ deck = next;
509
+ // Only re-render outside an active session — mid-session the
510
+ // snapshot queue drives the UI.
511
+ if (queue === null || idx >= queue.length) render();
512
+ })
513
+ .catch(function () {});
514
+ }
515
+
516
+ var forgeButton = document.getElementById('forge-go');
517
+ var forgeStatus = document.getElementById('forge-status');
518
+ forgeButton.onclick = function () {
519
+ var notes = document.getElementById('forge-notes').value.trim();
520
+ if (notes.length < 20) {
521
+ forgeStatus.textContent = 'Paste a few sentences of notes first.';
522
+ return;
523
+ }
524
+ forgeButton.disabled = true;
525
+ forgeStatus.textContent = '';
526
+ gezel.tools
527
+ .invoke('forge_from_notes', { notes: notes })
528
+ .then(function () {
529
+ document.getElementById('forge-notes').value = '';
530
+ forgeStatus.textContent = demoMode
531
+ ? 'Added sample cards from your notes.'
532
+ : 'Your study mate is writing cards from your notes. They appear in the deck when ready.';
533
+ return loadDeck();
534
+ })
535
+ .catch(function (err) {
536
+ forgeStatus.textContent =
537
+ err && err.code === 'unavailable'
538
+ ? 'Making cards happens in the app’s Output pane.'
539
+ : (err && err.message) || 'That did not work. Try again.';
540
+ })
541
+ .then(function () { forgeButton.disabled = false; });
542
+ };
543
+
544
+ loadDeck();
545
+ gezel.data.watch('deck.json', function () { loadDeck(); });
546
+ })();
547
+ </script>
548
+ </body>
549
+ </html>
@@ -0,0 +1,9 @@
1
+ A standing Go table. You place black stones on the board in the **Output**
2
+ tab; your Go-speler plays white — reading the exact position, responding on the
3
+ board, and keeping the conversation {{personality}} and unhurried.
4
+
5
+ Choose a quick 9×9 game, a roomier 13×13 board, or the full 19×19 goban. The
6
+ Go-speler plays as an **{{playStyle}}**: opponents keep their reading private,
7
+ while instructors offer one light teaching observation at a time. The board
8
+ enforces captures, suicide, simple ko, passing, and Chinese-style area scoring.
9
+ Two passes settle the score.
@@ -0,0 +1,103 @@
1
+ {
2
+ "version": 1,
3
+ "boardSize": 9,
4
+ "squares": ".................................................................................",
5
+ "turn": "user",
6
+ "status": "playing",
7
+ "komi": 6.5,
8
+ "consecutivePasses": 0,
9
+ "previousPosition": null,
10
+ "legalMoves": [
11
+ "A9",
12
+ "B9",
13
+ "C9",
14
+ "D9",
15
+ "E9",
16
+ "F9",
17
+ "G9",
18
+ "H9",
19
+ "J9",
20
+ "A8",
21
+ "B8",
22
+ "C8",
23
+ "D8",
24
+ "E8",
25
+ "F8",
26
+ "G8",
27
+ "H8",
28
+ "J8",
29
+ "A7",
30
+ "B7",
31
+ "C7",
32
+ "D7",
33
+ "E7",
34
+ "F7",
35
+ "G7",
36
+ "H7",
37
+ "J7",
38
+ "A6",
39
+ "B6",
40
+ "C6",
41
+ "D6",
42
+ "E6",
43
+ "F6",
44
+ "G6",
45
+ "H6",
46
+ "J6",
47
+ "A5",
48
+ "B5",
49
+ "C5",
50
+ "D5",
51
+ "E5",
52
+ "F5",
53
+ "G5",
54
+ "H5",
55
+ "J5",
56
+ "A4",
57
+ "B4",
58
+ "C4",
59
+ "D4",
60
+ "E4",
61
+ "F4",
62
+ "G4",
63
+ "H4",
64
+ "J4",
65
+ "A3",
66
+ "B3",
67
+ "C3",
68
+ "D3",
69
+ "E3",
70
+ "F3",
71
+ "G3",
72
+ "H3",
73
+ "J3",
74
+ "A2",
75
+ "B2",
76
+ "C2",
77
+ "D2",
78
+ "E2",
79
+ "F2",
80
+ "G2",
81
+ "H2",
82
+ "J2",
83
+ "A1",
84
+ "B1",
85
+ "C1",
86
+ "D1",
87
+ "E1",
88
+ "F1",
89
+ "G1",
90
+ "H1",
91
+ "J1",
92
+ "pass"
93
+ ],
94
+ "captures": {
95
+ "user": 0,
96
+ "ai": 0
97
+ },
98
+ "score": null,
99
+ "moveLog": [],
100
+ "lastMove": null,
101
+ "lastMoveThought": "",
102
+ "updatedAt": "2026-07-28T00:00:00.000Z"
103
+ }