@bendyline/gilde 0.1.7 → 0.1.8

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 (106) hide show
  1. package/README.md +17 -0
  2. package/data/project-types/ca/caregiving-binder/versions/1.0.1/about.md +8 -0
  3. package/data/project-types/ca/caregiving-binder/versions/1.0.1/care.json +4 -0
  4. package/data/project-types/ca/caregiving-binder/versions/1.0.1/manifest.json +177 -0
  5. package/data/project-types/ca/caregiving-binder/versions/1.0.1/meds.json +8 -0
  6. package/data/project-types/ca/caregiving-binder/versions/1.0.1/mission.md +8 -0
  7. package/data/project-types/ca/caregiving-binder/versions/1.0.1/pages/dashboard/index.html +167 -0
  8. package/data/project-types/ch/checkers/versions/1.1.2/about.md +9 -0
  9. package/data/project-types/ch/checkers/versions/1.1.2/game.json +77 -0
  10. package/data/project-types/ch/checkers/versions/1.1.2/manifest.json +166 -0
  11. package/data/project-types/ch/checkers/versions/1.1.2/mission.md +9 -0
  12. package/data/project-types/ch/checkers/versions/1.1.2/pages/board/index.html +1091 -0
  13. package/data/project-types/ch/chess/versions/1.0.2/about.md +9 -0
  14. package/data/project-types/ch/chess/versions/1.0.2/game.json +127 -0
  15. package/data/project-types/ch/chess/versions/1.0.2/manifest.json +180 -0
  16. package/data/project-types/ch/chess/versions/1.0.2/mission.md +8 -0
  17. package/data/project-types/ch/chess/versions/1.0.2/pages/board/index.html +627 -0
  18. package/data/project-types/ch/chess/versions/1.0.3/about.md +9 -0
  19. package/data/project-types/ch/chess/versions/1.0.3/game.json +127 -0
  20. package/data/project-types/ch/chess/versions/1.0.3/manifest.json +180 -0
  21. package/data/project-types/ch/chess/versions/1.0.3/mission.md +8 -0
  22. package/data/project-types/ch/chess/versions/1.0.3/pages/board/index.html +769 -0
  23. package/data/project-types/de/design-scheme/versions/1.0.1/about.md +6 -0
  24. package/data/project-types/de/design-scheme/versions/1.0.1/manifest.json +69 -0
  25. package/data/project-types/de/design-scheme/versions/1.0.1/mission.md +3 -0
  26. package/data/project-types/de/design-scheme/versions/1.0.1/pages/gallery/index.html +105 -0
  27. package/data/project-types/de/design-scheme/versions/1.0.1/schemes/index.json +9 -0
  28. package/data/project-types/de/design-scheme/versions/1.0.1/schemes/warm-dusk.json +4 -0
  29. package/data/project-types/ev/event-planner/versions/1.0.1/about.md +7 -0
  30. package/data/project-types/ev/event-planner/versions/1.0.1/budget.json +5 -0
  31. package/data/project-types/ev/event-planner/versions/1.0.1/guests.json +10 -0
  32. package/data/project-types/ev/event-planner/versions/1.0.1/manifest.json +170 -0
  33. package/data/project-types/ev/event-planner/versions/1.0.1/mission.md +6 -0
  34. package/data/project-types/ev/event-planner/versions/1.0.1/pages/dashboard/index.html +186 -0
  35. package/data/project-types/fi/fitness-coach/versions/1.0.1/about.md +7 -0
  36. package/data/project-types/fi/fitness-coach/versions/1.0.1/manifest.json +125 -0
  37. package/data/project-types/fi/fitness-coach/versions/1.0.1/mission.md +7 -0
  38. package/data/project-types/fi/fitness-coach/versions/1.0.1/pages/dashboard/index.html +172 -0
  39. package/data/project-types/fi/fitness-coach/versions/1.0.1/training.json +6 -0
  40. package/data/project-types/fl/flashcards/versions/1.0.1/about.md +8 -0
  41. package/data/project-types/fl/flashcards/versions/1.0.1/deck.json +7 -0
  42. package/data/project-types/fl/flashcards/versions/1.0.1/manifest.json +167 -0
  43. package/data/project-types/fl/flashcards/versions/1.0.1/mission.md +6 -0
  44. package/data/project-types/fl/flashcards/versions/1.0.1/pages/review/index.html +396 -0
  45. package/data/project-types/fr/freelance-office/versions/1.0.1/about.md +6 -0
  46. package/data/project-types/fr/freelance-office/versions/1.0.1/invoices.json +9 -0
  47. package/data/project-types/fr/freelance-office/versions/1.0.1/manifest.json +142 -0
  48. package/data/project-types/fr/freelance-office/versions/1.0.1/mission.md +6 -0
  49. package/data/project-types/fr/freelance-office/versions/1.0.1/pages/dashboard/index.html +157 -0
  50. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/about.md +7 -0
  51. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/donations.json +7 -0
  52. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/manifest.json +126 -0
  53. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/mission.md +6 -0
  54. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/pages/dashboard/index.html +160 -0
  55. package/data/project-types/go/go/versions/1.0.1/about.md +9 -0
  56. package/data/project-types/go/go/versions/1.0.1/game.json +103 -0
  57. package/data/project-types/go/go/versions/1.0.1/manifest.json +166 -0
  58. package/data/project-types/go/go/versions/1.0.1/mission.md +8 -0
  59. package/data/project-types/go/go/versions/1.0.1/pages/board/index.html +778 -0
  60. package/data/project-types/ho/household-budget/versions/1.0.1/about.md +6 -0
  61. package/data/project-types/ho/household-budget/versions/1.0.1/ledger.json +5 -0
  62. package/data/project-types/ho/household-budget/versions/1.0.1/manifest.json +163 -0
  63. package/data/project-types/ho/household-budget/versions/1.0.1/mission.md +6 -0
  64. package/data/project-types/ho/household-budget/versions/1.0.1/pages/dashboard/index.html +233 -0
  65. package/data/project-types/ho/household-manual/versions/1.0.1/about.md +7 -0
  66. package/data/project-types/ho/household-manual/versions/1.0.1/manifest.json +152 -0
  67. package/data/project-types/ho/household-manual/versions/1.0.1/mission.md +8 -0
  68. package/data/project-types/ho/household-manual/versions/1.0.1/pages/dashboard/index.html +157 -0
  69. package/data/project-types/ho/household-manual/versions/1.0.1/systems.json +8 -0
  70. package/data/project-types/index.json +1 -1
  71. package/data/project-types/jo/job-hunt/versions/1.0.1/about.md +10 -0
  72. package/data/project-types/jo/job-hunt/versions/1.0.1/activity.json +4 -0
  73. package/data/project-types/jo/job-hunt/versions/1.0.1/manifest.json +212 -0
  74. package/data/project-types/jo/job-hunt/versions/1.0.1/mission.md +9 -0
  75. package/data/project-types/jo/job-hunt/versions/1.0.1/pages/dashboard/index.html +327 -0
  76. package/data/project-types/jo/job-hunt/versions/1.0.1/pipeline.json +6 -0
  77. package/data/project-types/la/language-trainer/versions/1.0.1/about.md +6 -0
  78. package/data/project-types/la/language-trainer/versions/1.0.1/manifest.json +79 -0
  79. package/data/project-types/la/language-trainer/versions/1.0.1/mission.md +4 -0
  80. package/data/project-types/la/language-trainer/versions/1.0.1/pages/dashboard/index.html +153 -0
  81. package/data/project-types/la/language-trainer/versions/1.0.1/progress.json +7 -0
  82. package/data/project-types/li/life-binder/versions/1.0.1/about.md +7 -0
  83. package/data/project-types/li/life-binder/versions/1.0.1/documents.json +145 -0
  84. package/data/project-types/li/life-binder/versions/1.0.1/manifest.json +137 -0
  85. package/data/project-types/li/life-binder/versions/1.0.1/mission.md +7 -0
  86. package/data/project-types/li/life-binder/versions/1.0.1/pages/dashboard/index.html +147 -0
  87. package/data/project-types/me/meal-planner/versions/1.0.1/about.md +6 -0
  88. package/data/project-types/me/meal-planner/versions/1.0.1/manifest.json +159 -0
  89. package/data/project-types/me/meal-planner/versions/1.0.1/menu.json +5 -0
  90. package/data/project-types/me/meal-planner/versions/1.0.1/mission.md +5 -0
  91. package/data/project-types/me/meal-planner/versions/1.0.1/pages/dashboard/index.html +152 -0
  92. package/data/project-types/me/meal-planner/versions/1.0.1/pantry.json +9 -0
  93. package/data/project-types/no/novel-writing-room/versions/1.0.1/about.md +7 -0
  94. package/data/project-types/no/novel-writing-room/versions/1.0.1/chapters.json +10 -0
  95. package/data/project-types/no/novel-writing-room/versions/1.0.1/manifest.json +157 -0
  96. package/data/project-types/no/novel-writing-room/versions/1.0.1/mission.md +7 -0
  97. package/data/project-types/no/novel-writing-room/versions/1.0.1/pages/dashboard/index.html +165 -0
  98. package/data/project-types/no/novel-writing-room/versions/1.0.1/sessions.json +6 -0
  99. package/data/project-types/tr/trip-planner/versions/1.0.1/about.md +7 -0
  100. package/data/project-types/tr/trip-planner/versions/1.0.1/journal.json +4 -0
  101. package/data/project-types/tr/trip-planner/versions/1.0.1/manifest.json +193 -0
  102. package/data/project-types/tr/trip-planner/versions/1.0.1/mission.md +4 -0
  103. package/data/project-types/tr/trip-planner/versions/1.0.1/packing.json +8 -0
  104. package/data/project-types/tr/trip-planner/versions/1.0.1/pages/dashboard/index.html +215 -0
  105. package/data/project-types/tr/trip-planner/versions/1.0.1/trip.json +7 -0
  106. package/package.json +4 -2
package/README.md CHANGED
@@ -57,6 +57,23 @@ against the JSON Schemas in schemas/ (generated from gezel's
57
57
 
58
58
  canonical Zod schemas — do not edit them here).
59
59
 
60
+ ## Try the project pages locally
61
+
62
+ Run the standalone page gallery from the repository root:
63
+
64
+ ```
65
+ npm run demos
66
+ ```
67
+
68
+ Then open [http://127.0.0.1:4173/](http://127.0.0.1:4173/). The home page
69
+ links to every latest project-type web experience. Each page recognizes
70
+ that it is outside Gezel, uses local sample data, and can be reset by
71
+ reloading. To choose a different address:
72
+
73
+ ```
74
+ npm run demos -- --host 127.0.0.1 --port 8080
75
+ ```
76
+
60
77
  ## Licensing
61
78
 
62
79
  The catalog structure and tooling are MIT. Each model entry carries its
@@ -0,0 +1,8 @@
1
+ The care record for {{person}} — medications, appointments, and the day-to-day
2
+ journal, kept by a discreet Zorgcoördinator so no hard week has to rely on
3
+ memory. Before each doctor visit, ask for the visit prep: a one-page brief of
4
+ what changed since last time.
5
+
6
+ This record is private by construction: it lives in plain files on this machine
7
+ and never leaves it. The **Output** tab shows the current picture — active
8
+ meds, what's coming up, and the recent journal.
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "events": []
4
+ }
@@ -0,0 +1,177 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.1",
4
+ "releasedAt": "2026-08-01T00:00:00Z",
5
+ "meesterManaged": false,
6
+ "params": {
7
+ "type": "object",
8
+ "properties": {
9
+ "person": {
10
+ "type": "string",
11
+ "title": "Who is the care for?",
12
+ "description": "A first name is enough — e.g. 'Mom', 'Jan'.",
13
+ "default": "Mom"
14
+ }
15
+ },
16
+ "required": [
17
+ "person"
18
+ ]
19
+ },
20
+ "nameTemplate": "Caregiving Binder",
21
+ "aboutTemplate": "about.md",
22
+ "missionTemplate": "mission.md",
23
+ "gezels": [
24
+ {
25
+ "templateId": "zorgcoordinator",
26
+ "voorman": true
27
+ }
28
+ ],
29
+ "tools": [
30
+ {
31
+ "name": "add_med",
32
+ "description": "Record a medication (name, dose/schedule, prescriber).",
33
+ "script": "care-store",
34
+ "inputs": {
35
+ "type": "object",
36
+ "properties": {
37
+ "name": {
38
+ "type": "string"
39
+ },
40
+ "dose": {
41
+ "type": "string"
42
+ },
43
+ "prescriber": {
44
+ "type": "string"
45
+ }
46
+ },
47
+ "required": [
48
+ "name",
49
+ "dose"
50
+ ]
51
+ },
52
+ "bind": {
53
+ "action": "add_med"
54
+ }
55
+ },
56
+ {
57
+ "name": "stop_med",
58
+ "description": "Mark a medication stopped (with why, when known).",
59
+ "script": "care-store",
60
+ "inputs": {
61
+ "type": "object",
62
+ "properties": {
63
+ "id": {
64
+ "type": "string"
65
+ },
66
+ "name": {
67
+ "type": "string"
68
+ },
69
+ "note": {
70
+ "type": "string"
71
+ }
72
+ }
73
+ },
74
+ "bind": {
75
+ "action": "stop_med"
76
+ }
77
+ },
78
+ {
79
+ "name": "log_entry",
80
+ "description": "Add a journal entry — a note, a symptom observation, an update.",
81
+ "script": "care-store",
82
+ "inputs": {
83
+ "type": "object",
84
+ "properties": {
85
+ "kind": {
86
+ "type": "string",
87
+ "enum": [
88
+ "note",
89
+ "symptom",
90
+ "update"
91
+ ]
92
+ },
93
+ "note": {
94
+ "type": "string"
95
+ }
96
+ },
97
+ "required": [
98
+ "note"
99
+ ]
100
+ },
101
+ "bind": {
102
+ "action": "log_entry"
103
+ }
104
+ },
105
+ {
106
+ "name": "log_appointment",
107
+ "description": "Record an appointment (what and when, ISO).",
108
+ "script": "care-store",
109
+ "inputs": {
110
+ "type": "object",
111
+ "properties": {
112
+ "note": {
113
+ "type": "string"
114
+ },
115
+ "when": {
116
+ "type": "string"
117
+ }
118
+ },
119
+ "required": [
120
+ "note",
121
+ "when"
122
+ ]
123
+ },
124
+ "bind": {
125
+ "action": "log_appointment"
126
+ }
127
+ },
128
+ {
129
+ "name": "care_status",
130
+ "description": "Active meds, upcoming appointments, journal count.",
131
+ "script": "care-store",
132
+ "inputs": {
133
+ "type": "object",
134
+ "properties": {}
135
+ },
136
+ "bind": {
137
+ "action": "status"
138
+ }
139
+ },
140
+ {
141
+ "name": "visit_prep",
142
+ "description": "The one-page brief for the next doctor visit: meds + what happened since the last one.",
143
+ "script": "care-store",
144
+ "inputs": {
145
+ "type": "object",
146
+ "properties": {}
147
+ },
148
+ "bind": {
149
+ "action": "visit_prep"
150
+ }
151
+ }
152
+ ],
153
+ "craftbooks": [
154
+ "visit-prep"
155
+ ],
156
+ "schedules": [],
157
+ "pages": {
158
+ "entry": "dashboard/index.html",
159
+ "reads": [
160
+ {
161
+ "source": "workspace",
162
+ "path": "meds.json"
163
+ },
164
+ {
165
+ "source": "workspace",
166
+ "path": "care.json"
167
+ }
168
+ ]
169
+ },
170
+ "workspaceSeed": [
171
+ "meds.json",
172
+ "care.json"
173
+ ],
174
+ "scripts": {
175
+ "care-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { logStore, rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'care-store',\n description:\n \"Read and update the care record: medications (meds.json) and the care journal (care.json). Call action:'status' for active meds + upcoming appointments; 'add_med' / 'stop_med' on changes; 'log_entry' for journal notes and symptoms; 'log_appointment' with when (ISO); 'visit_prep' before a doctor visit.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'add_med', 'stop_med', 'log_entry', 'log_appointment', or 'visit_prep'.\",\n required: true,\n },\n name: { type: 'string', description: 'Medication name.' },\n dose: { type: 'string', description: \"Dose and schedule, e.g. '10mg mornings'.\" },\n prescriber: { type: 'string', description: 'Who prescribed it.' },\n id: { type: 'string', description: \"Medication id, e.g. 'med-2'.\" },\n kind: { type: 'string', description: \"Journal kind: 'note', 'symptom', or 'update'.\" },\n note: { type: 'string', description: 'The journal entry or appointment description.' },\n when: { type: 'string', description: 'Appointment time (ISO), e.g. 2026-08-02T14:00.' },\n },\n outputs: {\n meds: { type: 'number', description: 'Active medications.' },\n upcoming: { type: 'number', description: 'Upcoming appointments.' },\n summary: { type: 'string', description: 'The care picture (or the visit prep).' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── care-rules-start ──\n// Pure derivations: upcoming appointments (journal events kind\n// 'appointment' with data.whenAt in the future) and the since-last-visit\n// window for visit prep.\n\nfunction upcomingAppointments(events, nowMs) {\n return events\n .filter(function (e) {\n var when = e.data && typeof e.data.whenAt === 'string' ? Date.parse(e.data.whenAt) : NaN;\n return e.kind === 'appointment' && isFinite(when) && when >= nowMs;\n })\n .sort(function (a, b) {\n return Date.parse(a.data.whenAt) - Date.parse(b.data.whenAt);\n });\n}\n\nfunction lastVisitMs(events, nowMs) {\n var past = events.filter(function (e) {\n var when = e.data && typeof e.data.whenAt === 'string' ? Date.parse(e.data.whenAt) : NaN;\n return e.kind === 'appointment' && isFinite(when) && when < nowMs;\n });\n if (past.length === 0) return null;\n return Math.max.apply(\n null,\n past.map(function (e) {\n return Date.parse(e.data.whenAt);\n }),\n );\n}\n\nfunction sinceWindow(events, nowMs) {\n var last = lastVisitMs(events, nowMs);\n return events.filter(function (e) {\n return e.kind !== 'appointment' && (last === null || Date.parse(e.at) > last);\n });\n}\n// ── care-rules-end ──\n\nconst meds = rosterStore(gezel.fs, 'meds.json', {\n stages: ['active', 'stopped'],\n idPrefix: 'med',\n});\nconst journal = logStore(gezel.fs, 'care.json');\n\nasync function resolveMed(): Promise<string> {\n if (input.id) return input.id;\n if (input.name) {\n const wanted = input.name.toLowerCase();\n const matches = (await meds.list({ status: 'active' })).filter(\n (r) => String(r.fields.name ?? '').toLowerCase() === wanted,\n );\n if (matches.length === 1) return matches[0]!.id;\n }\n throw new Error(\"Pass 'id' (e.g. 'med-2') or an exact active medication 'name'.\");\n}\n\nif (input.action === 'add_med') {\n if (!input.name || !input.dose) throw new Error(\"add_med needs 'name' and 'dose'.\");\n await meds.add({\n status: 'active',\n fields: {\n name: input.name,\n dose: input.dose,\n ...(input.prescriber ? { prescriber: input.prescriber } : {}),\n },\n });\n await journal.append({ kind: 'update', note: `Started ${input.name} (${input.dose}).` });\n} else if (input.action === 'stop_med') {\n const id = await resolveMed();\n const med = await meds.get(id);\n await meds.transition(id, 'stopped', input.note ? { note: input.note } : {});\n await journal.append({\n kind: 'update',\n note: `Stopped ${String(med?.fields.name ?? id)}${input.note ? ` — ${input.note}` : ''}.`,\n });\n} else if (input.action === 'log_entry') {\n if (!input.note) throw new Error(\"log_entry needs 'note'.\");\n const kind = input.kind === 'symptom' || input.kind === 'update' ? input.kind : 'note';\n await journal.append({ kind, note: input.note });\n} else if (input.action === 'log_appointment') {\n if (!input.note || !input.when) throw new Error(\"log_appointment needs 'note' and 'when' (ISO).\");\n await journal.append({ kind: 'appointment', note: input.note, data: { whenAt: input.when } });\n} else if (input.action !== 'status' && input.action !== 'visit_prep') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, add_med, stop_med, log_entry, log_appointment, or visit_prep.`,\n );\n}\n\nconst activeMeds = await meds.list({ status: 'active' });\nconst events = await journal.list();\nconst nowMs = Date.now();\nconst upcoming = upcomingAppointments(events, nowMs);\n\nlet summary: string;\nif (input.action === 'visit_prep') {\n const since = sinceWindow(events, nowMs);\n const medLines = activeMeds\n .map((m) => `- ${String(m.fields.name)} ${String(m.fields.dose ?? '')}`.trim())\n .join('\\n');\n const noteLines = since\n .map((e) => `- [${e.kind}] ${String(e.at).slice(0, 10)}: ${e.note ?? ''}`)\n .join('\\n');\n summary = `VISIT PREP\\nActive medications:\\n${medLines || '- none recorded'}\\nSince the last visit:\\n${noteLines || '- quiet — nothing logged'}\\nUpcoming:\\n${upcoming.map((e) => `- ${String(e.data?.whenAt).slice(0, 16)} ${e.note ?? ''}`).join('\\n') || '- none scheduled'}`;\n} else {\n const next = upcoming[0];\n summary = `${activeMeds.length} active medication(s). ${upcoming.length} upcoming appointment(s)${\n next ? ` — next: ${String(next.data?.whenAt).slice(0, 16)} ${next.note ?? ''}` : ''\n }. ${events.filter((e) => e.kind !== 'appointment').length} journal entr(ies).`;\n}\n\ngezel.output({\n meds: activeMeds.length,\n upcoming: upcoming.length,\n summary,\n});\n"
176
+ }
177
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "stages": [
4
+ "active",
5
+ "stopped"
6
+ ],
7
+ "records": []
8
+ }
@@ -0,0 +1,8 @@
1
+ - The medication list is always current — starts, stops, and dose changes
2
+ recorded the day they happen.
3
+ - Every doctor visit is walked into prepared: the since-last-visit brief in
4
+ hand.
5
+ - The journal captures observations in plain words; anything urgent-sounding
6
+ gets the call-the-doctor-now line, never a diagnosis.
7
+ - Family updates are one ask away — honest, warm, no needless alarm.
8
+ - Nothing leaves this machine.
@@ -0,0 +1,167 @@
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>Caregiving binder</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; --warn: #a4552e;
13
+ }
14
+ @media (prefers-color-scheme: dark) {
15
+ :root {
16
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
17
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
18
+ }
19
+ }
20
+ * { box-sizing: border-box; }
21
+ 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; }
22
+ main { max-width: 680px; margin: 0 auto; }
23
+ h1 { font-family: Georgia, 'PT Serif', 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; margin: 0 0 0.15rem; }
24
+ .sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.9rem; }
25
+ .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
26
+ .panel { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; font-size: 0.9rem; }
27
+ .panel h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.5rem; }
28
+ .panel ul { margin: 0; padding-left: 1.05rem; }
29
+ .panel li { margin: 0.25rem 0; }
30
+ .panel .meta { color: var(--muted); font-size: 0.78rem; }
31
+ .journal { margin-top: 0.6rem; }
32
+ .journal li { color: var(--muted); }
33
+ .journal li strong { color: var(--ink); font-weight: 600; }
34
+ .kind { display: inline-block; border: 1px solid var(--track); border-radius: 4px; padding: 0 0.3rem; font-size: 0.7rem; line-height: 1.4; margin-right: 0.3rem; color: var(--muted); }
35
+ .kind.symptom { color: var(--warn); border-color: var(--warn); }
36
+ .empty { color: var(--muted); font-style: italic; }
37
+ .privacy { color: var(--muted); font-size: 0.78rem; text-align: center; margin-top: 0.75rem; }
38
+ @media (max-width: 640px) { .cols { grid-template-columns: 1fr; } }
39
+ + .demo-banner {
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: space-between;
43
+ gap: 0.75rem;
44
+ margin: 0 0 0.85rem;
45
+ padding: 0.5rem 0.65rem;
46
+ border: 1px solid var(--accent, #8a5a2b);
47
+ border-radius: 6px;
48
+ background: color-mix(in srgb, var(--card, var(--surface, #fffdf8)) 88%, var(--accent, #8a5a2b));
49
+ color: var(--ink, #2b2620);
50
+ font-size: 0.8rem;
51
+ }
52
+ .demo-banner[hidden] { display: none; }
53
+ .demo-banner strong { font-weight: 700; }
54
+ .demo-reset {
55
+ flex: none;
56
+ padding: 0.25rem 0.5rem;
57
+ border: 1px solid var(--accent, #8a5a2b);
58
+ border-radius: 6px;
59
+ background: var(--card, var(--surface, #fffdf8));
60
+ color: var(--ink, #2b2620);
61
+ cursor: pointer;
62
+ font: inherit;
63
+ }
64
+ .demo-reset:hover { border-color: var(--ink, #2b2620); }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <main>
69
+ + <div class="demo-banner" data-gezel-demo-banner hidden>
70
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
71
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
72
+ </div>
73
+ <h1>Caregiving binder</h1>
74
+ <p class="sub" id="sub">Loading the record…</p>
75
+ <div class="cols">
76
+ <div class="panel">
77
+ <h2>Active medications</h2>
78
+ <ul id="meds"><li class="empty">None recorded yet.</li></ul>
79
+ <h2 style="margin-top: 0.9rem">Upcoming</h2>
80
+ <ul id="upcoming"><li class="empty">Nothing scheduled.</li></ul>
81
+ </div>
82
+ <div class="panel">
83
+ <h2>Recent journal</h2>
84
+ <ul class="journal" id="journal"><li class="empty">The journal is empty — a quiet start.</li></ul>
85
+ </div>
86
+ </div>
87
+ <p class="privacy">This record lives on this machine and never leaves it.</p>
88
+ </main>
89
+ <script>
90
+ (function () {
91
+ 'use strict';
92
+ var parts = location.pathname.split('/').filter(Boolean);
93
+ var typeIdx = parts.indexOf('type');
94
+ var capability = typeIdx > 0 ? parts[typeIdx - 1] : null;
95
+ var projectId = typeIdx >= 0 ? parts[typeIdx + 1] : null;
96
+ var failedPolls = 0;
97
+ var demoMode = !capability || !projectId;
98
+ var demoNow = Date.now();
99
+ var demoMeds = { records: [
100
+ { id: 'med-1', status: 'active', fields: { name: 'Lisinopril', dose: '10 mg each morning', prescriber: 'Dr. Chen' } },
101
+ { id: 'med-2', status: 'active', fields: { name: 'Vitamin D', dose: '1 tablet with lunch' } },
102
+ ] };
103
+ var demoCare = { events: [
104
+ { kind: 'appointment', note: 'Primary care follow-up', data: { whenAt: new Date(demoNow + 12 * 86400000).toISOString() } },
105
+ { kind: 'symptom', at: new Date(demoNow - 86400000).toISOString(), note: 'Energy was better after an afternoon walk.' },
106
+ { kind: 'note', at: new Date(demoNow - 3 * 86400000).toISOString(), note: 'Refill collected; pill organizer set for the week.' },
107
+ { kind: 'update', at: new Date(demoNow - 6 * 86400000).toISOString(), note: 'Blood pressure readings added to the visit notes.' },
108
+ ] };
109
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
110
+
111
+ function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
112
+ function fetchJson(file) {
113
+ return fetch('/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/' + file, { cache: 'no-store' })
114
+ .then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); });
115
+ }
116
+
117
+ function render(meds, care) {
118
+ var active = ((meds && meds.records) || []).filter(function (r) { return r.status === 'active'; });
119
+ var events = (care && care.events) || [];
120
+ var now = Date.now();
121
+ var upcoming = events.filter(function (e) {
122
+ var when = e.data && e.data.whenAt ? Date.parse(e.data.whenAt) : NaN;
123
+ return e.kind === 'appointment' && isFinite(when) && when >= now;
124
+ }).sort(function (a, b) { return Date.parse(a.data.whenAt) - Date.parse(b.data.whenAt); });
125
+
126
+ document.getElementById('sub').textContent =
127
+ active.length + ' active medication(s) · ' + upcoming.length + ' upcoming appointment(s)';
128
+
129
+ if (active.length) {
130
+ document.getElementById('meds').innerHTML = active.map(function (m) {
131
+ var f = m.fields || {};
132
+ return '<li>' + esc(f.name || m.id) + ' <span class="meta">' + esc(f.dose || '') +
133
+ (f.prescriber ? ' · ' + esc(f.prescriber) : '') + '</span></li>';
134
+ }).join('');
135
+ }
136
+ if (upcoming.length) {
137
+ document.getElementById('upcoming').innerHTML = upcoming.slice(0, 4).map(function (e) {
138
+ return '<li>' + esc(String(e.data.whenAt).slice(0, 16).replace('T', ' ')) + ' <span class="meta">' + esc(e.note || '') + '</span></li>';
139
+ }).join('');
140
+ }
141
+ var recent = events.filter(function (e) { return e.kind !== 'appointment'; }).slice(-6).reverse();
142
+ if (recent.length) {
143
+ document.getElementById('journal').innerHTML = recent.map(function (e) {
144
+ return '<li><span class="kind ' + esc(e.kind) + '">' + esc(e.kind) + '</span>' +
145
+ '<strong>' + esc(String(e.at).slice(0, 10)) + '</strong> — ' + esc(e.note || '') + '</li>';
146
+ }).join('');
147
+ }
148
+ }
149
+
150
+ function poll() {
151
+ if (demoMode) { render(demoMeds, demoCare); return Promise.resolve(); }
152
+ if (!capability || !projectId) return Promise.resolve();
153
+ return Promise.all([
154
+ fetchJson('meds.json').catch(function () { return { records: [] }; }),
155
+ fetchJson('care.json').catch(function () { return { events: [] }; }),
156
+ ])
157
+ .then(function (results) { failedPolls = 0; render(results[0], results[1]); })
158
+ .catch(function () {
159
+ failedPolls += 1;
160
+ if (failedPolls === 3 && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
161
+ });
162
+ }
163
+ poll().then(function () { setInterval(function () { void poll(); }, 6000); });
164
+ })();
165
+ </script>
166
+ </body>
167
+ </html>
@@ -0,0 +1,9 @@
1
+ A standing checkers table. You play red on the board in the **Output** tab; your
2
+ Damspeler plays black — watching the position, playing back, and keeping up a
3
+ running line of table talk in chat ({{personality}} style, always friendly).
4
+ The Damspeler plays as an **{{playStyle}}**: opponents keep their strategy to
5
+ themselves, while instructors offer brief teaching observations.
6
+
7
+ Click a red piece, click a highlighted square, and the game is on. The board
8
+ enforces the rules for both sides — the chosen capture rule, multi-jumps, kings
9
+ — so every game is a fair one.
@@ -0,0 +1,77 @@
1
+ {
2
+ "version": 1,
3
+ "squares": ".b.b.b.bb.b.b.b..b.b.b.b................r.r.r.r..r.r.r.rr.r.r.r.",
4
+ "turn": "user",
5
+ "status": "playing",
6
+ "userColor": "red",
7
+ "forceCaptures": true,
8
+ "legalMoves": [
9
+ {
10
+ "from": "a3",
11
+ "to": "b4",
12
+ "path": [
13
+ "a3",
14
+ "b4"
15
+ ],
16
+ "captures": []
17
+ },
18
+ {
19
+ "from": "c3",
20
+ "to": "b4",
21
+ "path": [
22
+ "c3",
23
+ "b4"
24
+ ],
25
+ "captures": []
26
+ },
27
+ {
28
+ "from": "c3",
29
+ "to": "d4",
30
+ "path": [
31
+ "c3",
32
+ "d4"
33
+ ],
34
+ "captures": []
35
+ },
36
+ {
37
+ "from": "e3",
38
+ "to": "d4",
39
+ "path": [
40
+ "e3",
41
+ "d4"
42
+ ],
43
+ "captures": []
44
+ },
45
+ {
46
+ "from": "e3",
47
+ "to": "f4",
48
+ "path": [
49
+ "e3",
50
+ "f4"
51
+ ],
52
+ "captures": []
53
+ },
54
+ {
55
+ "from": "g3",
56
+ "to": "f4",
57
+ "path": [
58
+ "g3",
59
+ "f4"
60
+ ],
61
+ "captures": []
62
+ },
63
+ {
64
+ "from": "g3",
65
+ "to": "h4",
66
+ "path": [
67
+ "g3",
68
+ "h4"
69
+ ],
70
+ "captures": []
71
+ }
72
+ ],
73
+ "moveLog": [],
74
+ "lastMove": null,
75
+ "lastMoveThought": "",
76
+ "updatedAt": "2026-07-18T00:00:00.000Z"
77
+ }