@bendyline/gilde 0.1.89 → 0.1.91

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 (139) hide show
  1. package/data/knowledge-catalogs/index.json +1 -1
  2. package/data/knowledge-catalogs/raw-index.json +1 -1
  3. package/data/knowledge-catalogs/re/regional-american-south/art.json +12 -0
  4. package/data/knowledge-catalogs/re/regional-american-south/logo.webp +0 -0
  5. package/data/knowledge-catalogs/re/regional-american-south/manifest.json +48 -0
  6. package/data/knowledge-catalogs/re/regional-american-south/versions/2026.10.3/manifest.json +133 -0
  7. package/data/knowledge-catalogs/re/regional-andes-amazon/art.json +12 -0
  8. package/data/knowledge-catalogs/re/regional-andes-amazon/logo.webp +0 -0
  9. package/data/knowledge-catalogs/re/regional-andes-amazon/manifest.json +48 -0
  10. package/data/knowledge-catalogs/re/regional-andes-amazon/versions/2026.10.3/manifest.json +133 -0
  11. package/data/knowledge-catalogs/re/regional-australia-nz/art.json +12 -0
  12. package/data/knowledge-catalogs/re/regional-australia-nz/logo.webp +0 -0
  13. package/data/knowledge-catalogs/re/regional-australia-nz/manifest.json +48 -0
  14. package/data/knowledge-catalogs/re/regional-australia-nz/versions/2026.10.3/manifest.json +133 -0
  15. package/data/knowledge-catalogs/re/regional-brazil/art.json +12 -0
  16. package/data/knowledge-catalogs/re/regional-brazil/logo.webp +0 -0
  17. package/data/knowledge-catalogs/re/regional-brazil/manifest.json +48 -0
  18. package/data/knowledge-catalogs/re/regional-brazil/versions/2026.10.3/manifest.json +133 -0
  19. package/data/knowledge-catalogs/re/regional-britain-ireland/art.json +12 -0
  20. package/data/knowledge-catalogs/re/regional-britain-ireland/logo.webp +0 -0
  21. package/data/knowledge-catalogs/re/regional-britain-ireland/manifest.json +48 -0
  22. package/data/knowledge-catalogs/re/regional-britain-ireland/versions/2026.10.3/manifest.json +133 -0
  23. package/data/knowledge-catalogs/re/regional-california-southwest/art.json +12 -0
  24. package/data/knowledge-catalogs/re/regional-california-southwest/logo.webp +0 -0
  25. package/data/knowledge-catalogs/re/regional-california-southwest/manifest.json +48 -0
  26. package/data/knowledge-catalogs/re/regional-california-southwest/versions/2026.10.3/manifest.json +133 -0
  27. package/data/knowledge-catalogs/re/regional-canada-alaska/art.json +12 -0
  28. package/data/knowledge-catalogs/re/regional-canada-alaska/logo.webp +0 -0
  29. package/data/knowledge-catalogs/re/regional-canada-alaska/manifest.json +48 -0
  30. package/data/knowledge-catalogs/re/regional-canada-alaska/versions/2026.10.3/manifest.json +133 -0
  31. package/data/knowledge-catalogs/re/regional-central-eastern-europe/art.json +12 -0
  32. package/data/knowledge-catalogs/re/regional-central-eastern-europe/logo.webp +0 -0
  33. package/data/knowledge-catalogs/re/regional-central-eastern-europe/manifest.json +48 -0
  34. package/data/knowledge-catalogs/re/regional-central-eastern-europe/versions/2026.10.3/manifest.json +133 -0
  35. package/data/knowledge-catalogs/re/regional-east-asia/art.json +12 -0
  36. package/data/knowledge-catalogs/re/regional-east-asia/logo.webp +0 -0
  37. package/data/knowledge-catalogs/re/regional-east-asia/manifest.json +48 -0
  38. package/data/knowledge-catalogs/re/regional-east-asia/versions/2026.10.3/manifest.json +133 -0
  39. package/data/knowledge-catalogs/re/regional-east-southern-africa/art.json +12 -0
  40. package/data/knowledge-catalogs/re/regional-east-southern-africa/logo.webp +0 -0
  41. package/data/knowledge-catalogs/re/regional-east-southern-africa/manifest.json +48 -0
  42. package/data/knowledge-catalogs/re/regional-east-southern-africa/versions/2026.10.3/manifest.json +133 -0
  43. package/data/knowledge-catalogs/re/regional-mediterranean/art.json +12 -0
  44. package/data/knowledge-catalogs/re/regional-mediterranean/logo.webp +0 -0
  45. package/data/knowledge-catalogs/re/regional-mediterranean/manifest.json +48 -0
  46. package/data/knowledge-catalogs/re/regional-mediterranean/versions/2026.10.3/manifest.json +133 -0
  47. package/data/knowledge-catalogs/re/regional-mexico-central-america/art.json +12 -0
  48. package/data/knowledge-catalogs/re/regional-mexico-central-america/logo.webp +0 -0
  49. package/data/knowledge-catalogs/re/regional-mexico-central-america/manifest.json +48 -0
  50. package/data/knowledge-catalogs/re/regional-mexico-central-america/versions/2026.10.3/manifest.json +133 -0
  51. package/data/knowledge-catalogs/re/regional-middle-east/art.json +12 -0
  52. package/data/knowledge-catalogs/re/regional-middle-east/logo.webp +0 -0
  53. package/data/knowledge-catalogs/re/regional-middle-east/manifest.json +48 -0
  54. package/data/knowledge-catalogs/re/regional-middle-east/versions/2026.10.3/manifest.json +133 -0
  55. package/data/knowledge-catalogs/re/regional-midwest/art.json +12 -0
  56. package/data/knowledge-catalogs/re/regional-midwest/logo.webp +0 -0
  57. package/data/knowledge-catalogs/re/regional-midwest/manifest.json +48 -0
  58. package/data/knowledge-catalogs/re/regional-midwest/versions/2026.10.3/manifest.json +133 -0
  59. package/data/knowledge-catalogs/re/regional-north-africa/art.json +12 -0
  60. package/data/knowledge-catalogs/re/regional-north-africa/logo.webp +0 -0
  61. package/data/knowledge-catalogs/re/regional-north-africa/manifest.json +48 -0
  62. package/data/knowledge-catalogs/re/regional-north-africa/versions/2026.10.3/manifest.json +133 -0
  63. package/data/knowledge-catalogs/re/regional-northeast/art.json +12 -0
  64. package/data/knowledge-catalogs/re/regional-northeast/logo.webp +0 -0
  65. package/data/knowledge-catalogs/re/regional-northeast/manifest.json +48 -0
  66. package/data/knowledge-catalogs/re/regional-northeast/versions/2026.10.3/manifest.json +133 -0
  67. package/data/knowledge-catalogs/re/regional-northern-eurasia/art.json +12 -0
  68. package/data/knowledge-catalogs/re/regional-northern-eurasia/logo.webp +0 -0
  69. package/data/knowledge-catalogs/re/regional-northern-eurasia/manifest.json +48 -0
  70. package/data/knowledge-catalogs/re/regional-northern-eurasia/versions/2026.10.3/manifest.json +133 -0
  71. package/data/knowledge-catalogs/re/regional-northern-europe/art.json +12 -0
  72. package/data/knowledge-catalogs/re/regional-northern-europe/logo.webp +0 -0
  73. package/data/knowledge-catalogs/re/regional-northern-europe/manifest.json +48 -0
  74. package/data/knowledge-catalogs/re/regional-northern-europe/versions/2026.10.3/manifest.json +133 -0
  75. package/data/knowledge-catalogs/re/regional-pacific-islands/art.json +12 -0
  76. package/data/knowledge-catalogs/re/regional-pacific-islands/logo.webp +0 -0
  77. package/data/knowledge-catalogs/re/regional-pacific-islands/manifest.json +48 -0
  78. package/data/knowledge-catalogs/re/regional-pacific-islands/versions/2026.10.3/manifest.json +133 -0
  79. package/data/knowledge-catalogs/re/regional-pacific-northwest/art.json +12 -0
  80. package/data/knowledge-catalogs/re/regional-pacific-northwest/logo.webp +0 -0
  81. package/data/knowledge-catalogs/re/regional-pacific-northwest/manifest.json +48 -0
  82. package/data/knowledge-catalogs/re/regional-pacific-northwest/versions/2026.10.3/manifest.json +133 -0
  83. package/data/knowledge-catalogs/re/regional-rockies-plains/art.json +12 -0
  84. package/data/knowledge-catalogs/re/regional-rockies-plains/logo.webp +0 -0
  85. package/data/knowledge-catalogs/re/regional-rockies-plains/manifest.json +48 -0
  86. package/data/knowledge-catalogs/re/regional-rockies-plains/versions/2026.10.3/manifest.json +133 -0
  87. package/data/knowledge-catalogs/re/regional-south-asia/art.json +12 -0
  88. package/data/knowledge-catalogs/re/regional-south-asia/logo.webp +0 -0
  89. package/data/knowledge-catalogs/re/regional-south-asia/manifest.json +48 -0
  90. package/data/knowledge-catalogs/re/regional-south-asia/versions/2026.10.3/manifest.json +133 -0
  91. package/data/knowledge-catalogs/re/regional-southeast-asia/art.json +12 -0
  92. package/data/knowledge-catalogs/re/regional-southeast-asia/logo.webp +0 -0
  93. package/data/knowledge-catalogs/re/regional-southeast-asia/manifest.json +48 -0
  94. package/data/knowledge-catalogs/re/regional-southeast-asia/versions/2026.10.3/manifest.json +133 -0
  95. package/data/knowledge-catalogs/re/regional-southern-cone/art.json +12 -0
  96. package/data/knowledge-catalogs/re/regional-southern-cone/logo.webp +0 -0
  97. package/data/knowledge-catalogs/re/regional-southern-cone/manifest.json +48 -0
  98. package/data/knowledge-catalogs/re/regional-southern-cone/versions/2026.10.3/manifest.json +133 -0
  99. package/data/knowledge-catalogs/re/regional-west-central-africa/art.json +12 -0
  100. package/data/knowledge-catalogs/re/regional-west-central-africa/logo.webp +0 -0
  101. package/data/knowledge-catalogs/re/regional-west-central-africa/manifest.json +48 -0
  102. package/data/knowledge-catalogs/re/regional-west-central-africa/versions/2026.10.3/manifest.json +133 -0
  103. package/data/knowledge-catalogs/re/regional-western-europe/art.json +12 -0
  104. package/data/knowledge-catalogs/re/regional-western-europe/logo.webp +0 -0
  105. package/data/knowledge-catalogs/re/regional-western-europe/manifest.json +48 -0
  106. package/data/knowledge-catalogs/re/regional-western-europe/versions/2026.10.3/manifest.json +133 -0
  107. package/data/project-types/ch/checkers/versions/1.2.2/about.md +9 -0
  108. package/data/project-types/ch/checkers/versions/1.2.2/game.json +77 -0
  109. package/data/project-types/ch/checkers/versions/1.2.2/manifest.json +181 -0
  110. package/data/project-types/ch/checkers/versions/1.2.2/mission.md +9 -0
  111. package/data/project-types/ch/checkers/versions/1.2.2/pages/board/index.html +1158 -0
  112. package/data/project-types/ch/chess/versions/1.0.5/about.md +9 -0
  113. package/data/project-types/ch/chess/versions/1.0.5/game.json +127 -0
  114. package/data/project-types/ch/chess/versions/1.0.5/manifest.json +195 -0
  115. package/data/project-types/ch/chess/versions/1.0.5/mission.md +8 -0
  116. package/data/project-types/ch/chess/versions/1.0.5/pages/board/index.html +851 -0
  117. package/data/project-types/fi/fitness-coach/versions/1.0.3/about.md +7 -0
  118. package/data/project-types/fi/fitness-coach/versions/1.0.3/manifest.json +127 -0
  119. package/data/project-types/fi/fitness-coach/versions/1.0.3/mission.md +7 -0
  120. package/data/project-types/fi/fitness-coach/versions/1.0.3/pages/dashboard/index.html +273 -0
  121. package/data/project-types/fi/fitness-coach/versions/1.0.3/training.json +6 -0
  122. package/data/project-types/go/go/versions/1.0.3/about.md +9 -0
  123. package/data/project-types/go/go/versions/1.0.3/game.json +103 -0
  124. package/data/project-types/go/go/versions/1.0.3/manifest.json +181 -0
  125. package/data/project-types/go/go/versions/1.0.3/mission.md +8 -0
  126. package/data/project-types/go/go/versions/1.0.3/pages/board/index.html +855 -0
  127. package/data/project-types/index.json +1 -1
  128. package/data/project-types/la/language-trainer/versions/1.0.3/about.md +6 -0
  129. package/data/project-types/la/language-trainer/versions/1.0.3/manifest.json +81 -0
  130. package/data/project-types/la/language-trainer/versions/1.0.3/mission.md +4 -0
  131. package/data/project-types/la/language-trainer/versions/1.0.3/pages/dashboard/index.html +268 -0
  132. package/data/project-types/la/language-trainer/versions/1.0.3/progress.json +7 -0
  133. package/data/project-types/raw-index.json +1 -1
  134. package/package.json +1 -1
  135. package/schemas/craftbook-doc.schema.json +61 -0
  136. package/schemas/craftbook-template-version.schema.json +61 -0
  137. package/schemas/craftbook-test.schema.json +1 -0
  138. package/schemas/knowledge-catalog-version.schema.json +1 -0
  139. package/schemas/project-type-version.schema.json +143 -0
@@ -0,0 +1,7 @@
1
+ Training toward **{{goal}}**, {{weeklyTarget}} session(s) a week. Your Coach
2
+ logs every workout the moment you report it, celebrates the streak, and keeps
3
+ the plan sized to the life you actually have — smaller when the week is hard,
4
+ growing again when it isn't.
5
+
6
+ The **Output** tab is the training board: the streak, this week against the
7
+ target, and the recent sessions.
@@ -0,0 +1,127 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.3",
4
+ "releasedAt": "2026-10-07T00:00:00Z",
5
+ "meesterManaged": false,
6
+ "leanProfile": true,
7
+ "params": {
8
+ "type": "object",
9
+ "properties": {
10
+ "goal": {
11
+ "type": "string",
12
+ "title": "What are you training toward?",
13
+ "description": "e.g. 'feel strong again', 'a 10k in October'.",
14
+ "default": "feeling strong and consistent"
15
+ },
16
+ "weeklyTarget": {
17
+ "type": "number",
18
+ "title": "Sessions per week",
19
+ "description": "How many training sessions a week to aim for.",
20
+ "default": 3
21
+ }
22
+ },
23
+ "required": [
24
+ "goal",
25
+ "weeklyTarget"
26
+ ]
27
+ },
28
+ "nameTemplate": "Fitness: {{goal}}",
29
+ "aboutTemplate": "about.md",
30
+ "missionTemplate": "mission.md",
31
+ "gezels": [
32
+ {
33
+ "templateId": "coach",
34
+ "voorman": true
35
+ }
36
+ ],
37
+ "tools": [
38
+ {
39
+ "name": "log_workout",
40
+ "description": "Log a training session the moment it is reported (activity + minutes).",
41
+ "script": "training-store",
42
+ "inputs": {
43
+ "type": "object",
44
+ "properties": {
45
+ "activity": {
46
+ "type": "string"
47
+ },
48
+ "minutes": {
49
+ "type": "number"
50
+ },
51
+ "note": {
52
+ "type": "string"
53
+ }
54
+ },
55
+ "required": [
56
+ "activity"
57
+ ]
58
+ },
59
+ "bind": {
60
+ "action": "log_workout"
61
+ }
62
+ },
63
+ {
64
+ "name": "set_target",
65
+ "description": "Change the weekly session target.",
66
+ "script": "training-store",
67
+ "inputs": {
68
+ "type": "object",
69
+ "properties": {
70
+ "weeklyTarget": {
71
+ "type": "number"
72
+ }
73
+ },
74
+ "required": [
75
+ "weeklyTarget"
76
+ ]
77
+ },
78
+ "bind": {
79
+ "action": "set_target"
80
+ }
81
+ },
82
+ {
83
+ "name": "training_status",
84
+ "description": "Streak, this week against the target, all-time count.",
85
+ "script": "training-store",
86
+ "inputs": {
87
+ "type": "object",
88
+ "properties": {}
89
+ },
90
+ "bind": {
91
+ "action": "status"
92
+ }
93
+ },
94
+ {
95
+ "name": "workout_history",
96
+ "description": "The recent sessions, most recent last.",
97
+ "script": "training-store",
98
+ "inputs": {
99
+ "type": "object",
100
+ "properties": {}
101
+ },
102
+ "bind": {
103
+ "action": "history"
104
+ }
105
+ }
106
+ ],
107
+ "craftbooks": [
108
+ "weekly-review"
109
+ ],
110
+ "schedules": [],
111
+ "pages": {
112
+ "entry": "dashboard/index.html",
113
+ "api": 1,
114
+ "reads": [
115
+ {
116
+ "source": "workspace",
117
+ "path": "training.json"
118
+ }
119
+ ]
120
+ },
121
+ "workspaceSeed": [
122
+ "training.json"
123
+ ],
124
+ "scripts": {
125
+ "training-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { logStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'training-store',\n description:\n \"Read and update the training log (training.json). Call action:'status' for streak + this week vs the target; 'log_workout' the moment a session is reported (activity + minutes); 'set_target' to change the weekly session target; 'history' for the recent sessions.\",\n inputs: {\n action: {\n type: 'string',\n description: \"One of 'status', 'log_workout', 'set_target', or 'history'.\",\n required: true,\n },\n activity: { type: 'string', description: \"What they did, e.g. 'run', 'gym', 'long walk'.\" },\n minutes: { type: 'number', description: 'How long the session ran, in minutes.' },\n note: { type: 'string', description: 'Anything worth remembering about the session.' },\n weeklyTarget: { type: 'number', description: 'Sessions per week to aim for (set_target).' },\n },\n outputs: {\n streakDays: { type: 'number', description: 'Consecutive days with a session, ending at the latest.' },\n thisWeek: { type: 'number', description: 'Sessions logged this week (Monday UTC start).' },\n weeklyTarget: { type: 'number', description: 'The current weekly session target.' },\n summary: { type: 'string', description: 'The training picture.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── fitness-rules-start ──\n// Pure derivations over logStore-shaped workout events: kind 'workout',\n// data {activity, minutes}. Weeks start Monday 00:00 UTC; nowIso is a\n// parameter so the rules stay clock-independent and testable.\n\nfunction weekStartMs(nowIso) {\n var day = new Date(nowIso.slice(0, 10) + 'T00:00:00Z');\n var sinceMonday = (day.getUTCDay() + 6) % 7;\n return day.getTime() - sinceMonday * 86400000;\n}\n\nfunction thisWeekCount(events, nowIso) {\n var start = weekStartMs(nowIso);\n return events.filter(function (e) {\n return Date.parse(e.at) >= start;\n }).length;\n}\n\nfunction trainingLine(stats, weekCount, weeklyTarget) {\n var target = weeklyTarget > 0 ? weeklyTarget : 0;\n var weekPart =\n 'This week: ' +\n weekCount +\n (target > 0 ? ' of ' + target + ' session(s)' : ' session(s)') +\n (target > 0 && weekCount >= target ? ' — target met.' : '.');\n var streakPart =\n stats.total === 0\n ? 'No sessions logged yet.'\n : 'Streak: ' + stats.streakDays + ' day(s); ' + stats.total + ' session(s) all time.';\n return weekPart + ' ' + streakPart;\n}\n// ── fitness-rules-end ──\n\nconst training = logStore(gezel.fs, 'training.json');\n\nasync function readWeeklyTarget(): Promise<number> {\n try {\n const parsed = JSON.parse(await gezel.fs.read('training.json')) as { weeklyTarget?: unknown };\n const target = Number(parsed.weeklyTarget);\n return Number.isFinite(target) && target > 0 ? Math.round(target) : 0;\n } catch {\n return 0;\n }\n}\n\nif (input.action === 'log_workout') {\n if (!input.activity) throw new Error(\"log_workout needs 'activity' (what they did).\");\n await training.append({\n kind: 'workout',\n ...(input.note ? { note: input.note } : {}),\n data: {\n activity: input.activity,\n ...(typeof input.minutes === 'number' && input.minutes > 0\n ? { minutes: Math.round(input.minutes) }\n : {}),\n },\n });\n} else if (input.action === 'set_target') {\n const target = Number(input.weeklyTarget);\n if (!Number.isFinite(target) || target <= 0) {\n throw new Error(\"set_target needs a positive 'weeklyTarget' (sessions per week).\");\n }\n // logStore has no field API; the passenger key flips via a targeted\n // read-modify-write that preserves the rest of the file.\n const raw = JSON.parse(await gezel.fs.read('training.json')) as Record<string, unknown>;\n raw.weeklyTarget = Math.round(target);\n await gezel.fs.write('training.json', `${JSON.stringify(raw, null, 2)}\\n`);\n} else if (input.action !== 'status' && input.action !== 'history') {\n throw new Error(`Unknown action '${input.action}'. Use status, log_workout, set_target, or history.`);\n}\n\nconst events = await training.list();\nconst stats = await training.stats();\nconst weeklyTarget = await readWeeklyTarget();\nconst nowIso = new Date().toISOString();\nconst weekCount = thisWeekCount(events, nowIso);\nconst listing =\n input.action === 'history'\n ? events\n .slice(-12)\n .map(\n (e) =>\n `${e.at.slice(0, 10)} ${String(e.data?.activity ?? 'session')}${typeof e.data?.minutes === 'number' ? ` (${e.data.minutes} min)` : ''}${e.note ? ` — ${e.note}` : ''}`,\n )\n .join('\\n') || 'No sessions yet.'\n : '';\n\ngezel.output({\n streakDays: stats.streakDays,\n thisWeek: weekCount,\n weeklyTarget,\n summary: listing || trainingLine(stats, weekCount, weeklyTarget),\n});\n"
126
+ }
127
+ }
@@ -0,0 +1,7 @@
1
+ - Build a training rhythm that holds: {{weeklyTarget}} session(s) a week toward
2
+ {{goal}}.
3
+ - Every session gets logged the day it happens — the streak and the weekly
4
+ count come from the record, never from memory.
5
+ - Consistency over intensity: a shrunk plan that survives a hard week beats an
6
+ ambitious one that dies in it.
7
+ - Encouragement always, guilt never.
@@ -0,0 +1,273 @@
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>Fitness Coach</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light dark;
11
+ --bg: #faf7f2; --card: #fff; --ink: #2b2620; --muted: #7a7166;
12
+ --accent: #b5651d; --track: #ece5da; --good: #4a7c59;
13
+ }
14
+ :root[data-theme='dark'] {
15
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
16
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d;
17
+ }
18
+ @media (prefers-color-scheme: dark) {
19
+ :root:not([data-theme='light']) {
20
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
21
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d;
22
+ }
23
+ }
24
+ * { box-sizing: border-box; }
25
+ body { margin: 0; font: 15px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--ink); padding: 1.5rem 1.25rem; }
26
+ main { max-width: 620px; margin: 0 auto; }
27
+ h1 { font-family: Georgia, 'PT Serif', 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; margin: 0 0 0.15rem; }
28
+ .sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.9rem; }
29
+ .stats { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; margin-bottom: 0.75rem; }
30
+ .stat { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; }
31
+ .stat .num { font-size: 1.9rem; font-weight: 700; font-variant-numeric: tabular-nums; }
32
+ .stat .num.good { color: var(--good); }
33
+ .stat .lbl { color: var(--muted); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; }
34
+ .week { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; margin-bottom: 0.75rem; }
35
+ .week h2, .recent h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.5rem; }
36
+ .dots { display: flex; gap: 0.4rem; }
37
+ .dot { width: 22px; height: 22px; border-radius: 6px; background: var(--track); }
38
+ .dot.hit { background: var(--good); }
39
+ .week .note { color: var(--muted); font-size: 0.85rem; margin-top: 0.5rem; }
40
+ .recent { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; font-size: 0.9rem; }
41
+ .recent ul { margin: 0; padding-left: 1.05rem; }
42
+ .recent li { margin: 0.25rem 0; }
43
+ .recent .when { color: var(--muted); font-variant-numeric: tabular-nums; }
44
+ .empty { color: var(--muted); font-style: italic; }
45
+ .demo-banner {
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: space-between;
49
+ gap: 0.75rem;
50
+ margin: 0 0 0.85rem;
51
+ padding: 0.5rem 0.65rem;
52
+ border: 1px solid var(--accent);
53
+ border-radius: 6px;
54
+ background: color-mix(in srgb, var(--card) 88%, var(--accent));
55
+ color: var(--ink);
56
+ font-size: 0.8rem;
57
+ }
58
+ .demo-banner[hidden] { display: none; }
59
+ .demo-banner strong { font-weight: 700; }
60
+ .demo-reset {
61
+ flex: none;
62
+ padding: 0.25rem 0.5rem;
63
+ border: 1px solid var(--accent);
64
+ border-radius: 6px;
65
+ background: var(--card);
66
+ color: var(--ink);
67
+ cursor: pointer;
68
+ font: inherit;
69
+ }
70
+ .demo-reset:hover { border-color: var(--ink); }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <main>
75
+ <div class="demo-banner" data-gezel-demo-banner hidden>
76
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
77
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
78
+ </div>
79
+ <h1 id="title">Fitness Coach</h1>
80
+ <p class="sub" id="sub">Loading the training log…</p>
81
+ <div class="stats">
82
+ <div class="stat"><div class="num good" id="streak">—</div><div class="lbl">Day streak</div></div>
83
+ <div class="stat"><div class="num" id="total">—</div><div class="lbl">Sessions all time</div></div>
84
+ </div>
85
+ <div class="week">
86
+ <h2>This week</h2>
87
+ <div class="dots" id="dots"></div>
88
+ <div class="note" id="weekNote"></div>
89
+ </div>
90
+ <div class="recent">
91
+ <h2>Recent sessions</h2>
92
+ <ul id="recent"><li class="empty">Nothing logged yet — tell your Coach about your first session.</li></ul>
93
+ </div>
94
+ </main>
95
+ <script>
96
+ /* gezel-page-demo-stub v1 */
97
+ /*
98
+ * The canonical demo stub for Gezel type pages. MIT License.
99
+ *
100
+ * Paste this file's bytes verbatim into a <script> block ahead of the
101
+ * page's own code, then call makeDemoGezel({ page, tools, data }) with the
102
+ * page's demo handlers. Served by Gezel, the real window.gezel is already
103
+ * injected and the stub steps aside; opened as a raw file (the live demos
104
+ * on gezelgilde.com), it installs a window.gezel whose tools and data are
105
+ * the page-supplied handler maps — one code path in every mode. `tools`
106
+ * and `data` map a tool name / declared read path to a function; data
107
+ * handlers back read() and list(), and url() returns the handler's value
108
+ * or passes a data: URI through. See docs/page-authoring.md.
109
+ */
110
+ (function () {
111
+ 'use strict';
112
+ window.makeDemoGezel = function (opts) {
113
+ if (window.gezel) return window.gezel;
114
+ var page = (opts && opts.page) || {};
115
+ var tools = (opts && opts.tools) || {};
116
+ var data = (opts && opts.data) || {};
117
+ var themeListeners = [];
118
+ var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
119
+ var ui = {
120
+ theme: { mode: media && media.matches ? 'dark' : 'light' },
121
+ onTheme: function (cb) {
122
+ themeListeners.push(cb);
123
+ return function () {
124
+ var at = themeListeners.indexOf(cb);
125
+ if (at >= 0) themeListeners.splice(at, 1);
126
+ };
127
+ },
128
+ };
129
+ if (media && media.addEventListener) {
130
+ media.addEventListener('change', function (ev) {
131
+ ui.theme = { mode: ev.matches ? 'dark' : 'light' };
132
+ themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
133
+ });
134
+ }
135
+ function fail(code, message) {
136
+ var err = new Error(message);
137
+ err.code = code;
138
+ return Promise.reject(err);
139
+ }
140
+ function run(map, key, arg) {
141
+ if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
142
+ return Promise.resolve().then(function () { return map[key](arg); });
143
+ }
144
+ window.gezel = {
145
+ page: {
146
+ api: 1,
147
+ projectId: 'demo',
148
+ source: 'type',
149
+ entry: page.entry || 'index.html',
150
+ typeName: page.typeName || 'demo',
151
+ params: page.params || {},
152
+ mode: 'demo',
153
+ },
154
+ tools: {
155
+ list: function () { return Promise.resolve(Object.keys(tools)); },
156
+ invoke: function (name, input) {
157
+ var call = run(tools, name, input || {});
158
+ if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
159
+ return call.then(function (output) { return { output: output }; });
160
+ },
161
+ },
162
+ data: {
163
+ read: function (path, o) {
164
+ return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
165
+ },
166
+ list: function (path, o) {
167
+ return run(data, path, o || {}) || Promise.resolve([]);
168
+ },
169
+ watch: function () { return function () {}; },
170
+ url: function (path) {
171
+ if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
172
+ return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
173
+ },
174
+ },
175
+ ui: ui,
176
+ refresh: function () {},
177
+ };
178
+ return window.gezel;
179
+ };
180
+ })();
181
+ </script>
182
+ <script>
183
+ (function () {
184
+ 'use strict';
185
+ var DAY = 86400000;
186
+
187
+ // ── demo log (used only when the page opens as a raw file) ──
188
+ var demoTraining = {
189
+ goal: 'a comfortable 10K',
190
+ weeklyTarget: 4,
191
+ events: [
192
+ { at: new Date(Date.now() - 6 * DAY).toISOString(), data: { activity: 'Easy run', minutes: 32 } },
193
+ { at: new Date(Date.now() - 4 * DAY).toISOString(), data: { activity: 'Mobility and strength', minutes: 25 } },
194
+ { at: new Date(Date.now() - 2 * DAY).toISOString(), data: { activity: 'Tempo intervals', minutes: 38 } },
195
+ { at: new Date().toISOString(), data: { activity: 'Recovery walk', minutes: 28 } },
196
+ ],
197
+ };
198
+
199
+ makeDemoGezel({
200
+ page: { entry: 'dashboard/index.html', typeName: 'Fitness Coach', params: { goal: 'feeling strong and consistent', weeklyTarget: 3 } },
201
+ data: {
202
+ 'training.json': function () { return demoTraining; },
203
+ },
204
+ });
205
+
206
+ // ── one code path from here: window.gezel exists in every mode ──
207
+ var demoMode = gezel.page.mode === 'demo';
208
+
209
+ function applyTheme(mode) {
210
+ document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
211
+ }
212
+ applyTheme(gezel.ui.theme.mode);
213
+ gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
214
+
215
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
216
+
217
+ function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
218
+ function utcDay(iso) { return iso.slice(0, 10); }
219
+ function weekStartMs(nowIso) {
220
+ var d = new Date(nowIso.slice(0, 10) + 'T00:00:00Z');
221
+ return d.getTime() - ((d.getUTCDay() + 6) % 7) * DAY;
222
+ }
223
+ function streakDays(events) {
224
+ if (!events.length) return 0;
225
+ var days = {};
226
+ var last = null;
227
+ events.forEach(function (e) { days[utcDay(e.at)] = true; if (last === null || e.at > last) last = e.at; });
228
+ var streak = 0;
229
+ var cursor = Date.parse(utcDay(last) + 'T00:00:00Z');
230
+ while (days[new Date(cursor).toISOString().slice(0, 10)]) { streak += 1; cursor -= DAY; }
231
+ return streak;
232
+ }
233
+
234
+ function render(doc) {
235
+ var events = Array.isArray(doc.events) ? doc.events : [];
236
+ var target = Number(doc.weeklyTarget);
237
+ if (!isFinite(target) || target <= 0) target = 0;
238
+ if (doc.goal) document.getElementById('title').textContent = 'Training toward: ' + doc.goal;
239
+ document.getElementById('sub').textContent = events.length ? events.length + ' session(s) on record' : 'The record starts with the first session.';
240
+ document.getElementById('streak').textContent = String(streakDays(events));
241
+ document.getElementById('total').textContent = String(events.length);
242
+
243
+ var start = weekStartMs(new Date().toISOString());
244
+ var week = events.filter(function (e) { return Date.parse(e.at) >= start; }).length;
245
+ var slots = Math.max(target, week, 1);
246
+ var dots = '';
247
+ for (var i = 0; i < slots; i++) dots += '<span class="dot' + (i < week ? ' hit' : '') + '"></span>';
248
+ document.getElementById('dots').innerHTML = dots;
249
+ document.getElementById('weekNote').textContent = target > 0
250
+ ? week + ' of ' + target + ' session(s)' + (week >= target ? ' — target met.' : '.')
251
+ : week + ' session(s) this week.';
252
+
253
+ if (events.length) {
254
+ document.getElementById('recent').innerHTML = events.slice(-8).reverse().map(function (e) {
255
+ var mins = e.data && isFinite(Number(e.data.minutes)) && Number(e.data.minutes) > 0 ? ' (' + Number(e.data.minutes) + ' min)' : '';
256
+ return '<li><span class="when">' + esc(utcDay(e.at)) + '</span> ' + esc((e.data && e.data.activity) || 'session') + mins + '</li>';
257
+ }).join('');
258
+ }
259
+ }
260
+
261
+ function loadTraining() {
262
+ return gezel.data
263
+ .read('training.json')
264
+ .then(function (next) { if (next) render(next); })
265
+ .catch(function () {});
266
+ }
267
+
268
+ loadTraining();
269
+ gezel.data.watch('training.json', function () { loadTraining(); });
270
+ })();
271
+ </script>
272
+ </body>
273
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 1,
3
+ "goal": "{{goal}}",
4
+ "weeklyTarget": {{weeklyTarget}},
5
+ "events": []
6
+ }
@@ -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
+ }