@bendyline/gilde 0.1.85 → 0.1.87

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/data/craftbook-templates/index.json +1 -1
  2. package/data/craftbook-templates/raw-index.json +1 -1
  3. package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/craftbook.json +226 -0
  4. package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/test.json +79 -0
  5. package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/manifest.json +24 -0
  6. package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/versions/2026.10.1/manifest.json +658 -0
  7. package/data/knowledge-catalogs/ca/caselaw-us-347-349/manifest.json +25 -0
  8. package/data/knowledge-catalogs/ca/caselaw-us-347-349/versions/2026.10.5/manifest.json +38 -0
  9. package/data/knowledge-catalogs/index.json +1 -1
  10. package/data/knowledge-catalogs/raw-index.json +1 -1
  11. package/data/project-types/ch/checkers/versions/1.2.1/about.md +9 -0
  12. package/data/project-types/ch/checkers/versions/1.2.1/game.json +77 -0
  13. package/data/project-types/ch/checkers/versions/1.2.1/manifest.json +168 -0
  14. package/data/project-types/ch/checkers/versions/1.2.1/mission.md +9 -0
  15. package/data/project-types/ch/checkers/versions/1.2.1/pages/board/index.html +1158 -0
  16. package/data/project-types/ch/chess/versions/1.0.4/about.md +9 -0
  17. package/data/project-types/ch/chess/versions/1.0.4/game.json +127 -0
  18. package/data/project-types/ch/chess/versions/1.0.4/manifest.json +182 -0
  19. package/data/project-types/ch/chess/versions/1.0.4/mission.md +8 -0
  20. package/data/project-types/ch/chess/versions/1.0.4/pages/board/index.html +851 -0
  21. package/data/project-types/de/design-scheme/versions/1.0.2/about.md +6 -0
  22. package/data/project-types/de/design-scheme/versions/1.0.2/manifest.json +70 -0
  23. package/data/project-types/de/design-scheme/versions/1.0.2/mission.md +3 -0
  24. package/data/project-types/de/design-scheme/versions/1.0.2/pages/gallery/index.html +212 -0
  25. package/data/project-types/de/design-scheme/versions/1.0.2/schemes/index.json +9 -0
  26. package/data/project-types/de/design-scheme/versions/1.0.2/schemes/warm-dusk.json +4 -0
  27. package/data/project-types/ev/event-planner/versions/1.0.2/about.md +7 -0
  28. package/data/project-types/ev/event-planner/versions/1.0.2/budget.json +5 -0
  29. package/data/project-types/ev/event-planner/versions/1.0.2/guests.json +10 -0
  30. package/data/project-types/ev/event-planner/versions/1.0.2/manifest.json +171 -0
  31. package/data/project-types/ev/event-planner/versions/1.0.2/mission.md +6 -0
  32. package/data/project-types/ev/event-planner/versions/1.0.2/pages/dashboard/index.html +283 -0
  33. package/data/project-types/fi/fitness-coach/versions/1.0.2/about.md +7 -0
  34. package/data/project-types/fi/fitness-coach/versions/1.0.2/manifest.json +126 -0
  35. package/data/project-types/fi/fitness-coach/versions/1.0.2/mission.md +7 -0
  36. package/data/project-types/fi/fitness-coach/versions/1.0.2/pages/dashboard/index.html +273 -0
  37. package/data/project-types/fi/fitness-coach/versions/1.0.2/training.json +6 -0
  38. package/data/project-types/fl/flashcards/versions/1.1.1/about.md +8 -0
  39. package/data/project-types/fl/flashcards/versions/1.1.1/deck.json +7 -0
  40. package/data/project-types/fl/flashcards/versions/1.1.1/manifest.json +168 -0
  41. package/data/project-types/fl/flashcards/versions/1.1.1/mission.md +6 -0
  42. package/data/project-types/fl/flashcards/versions/1.1.1/pages/review/index.html +477 -0
  43. package/data/project-types/fr/freelance-office/versions/1.0.2/about.md +6 -0
  44. package/data/project-types/fr/freelance-office/versions/1.0.2/invoices.json +9 -0
  45. package/data/project-types/fr/freelance-office/versions/1.0.2/manifest.json +143 -0
  46. package/data/project-types/fr/freelance-office/versions/1.0.2/mission.md +6 -0
  47. package/data/project-types/fr/freelance-office/versions/1.0.2/pages/dashboard/index.html +258 -0
  48. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/about.md +7 -0
  49. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/donations.json +7 -0
  50. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/manifest.json +127 -0
  51. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/mission.md +6 -0
  52. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/pages/dashboard/index.html +261 -0
  53. package/data/project-types/go/go/versions/1.0.2/about.md +9 -0
  54. package/data/project-types/go/go/versions/1.0.2/game.json +103 -0
  55. package/data/project-types/go/go/versions/1.0.2/manifest.json +168 -0
  56. package/data/project-types/go/go/versions/1.0.2/mission.md +8 -0
  57. package/data/project-types/go/go/versions/1.0.2/pages/board/index.html +855 -0
  58. package/data/project-types/ho/household-budget/versions/1.0.3/about.md +6 -0
  59. package/data/project-types/ho/household-budget/versions/1.0.3/ledger.json +5 -0
  60. package/data/project-types/ho/household-budget/versions/1.0.3/manifest.json +164 -0
  61. package/data/project-types/ho/household-budget/versions/1.0.3/mission.md +5 -0
  62. package/data/project-types/ho/household-budget/versions/1.0.3/pages/dashboard/index.html +340 -0
  63. package/data/project-types/ho/household-manual/versions/1.0.2/about.md +7 -0
  64. package/data/project-types/ho/household-manual/versions/1.0.2/manifest.json +153 -0
  65. package/data/project-types/ho/household-manual/versions/1.0.2/mission.md +8 -0
  66. package/data/project-types/ho/household-manual/versions/1.0.2/pages/dashboard/index.html +258 -0
  67. package/data/project-types/ho/household-manual/versions/1.0.2/systems.json +8 -0
  68. package/data/project-types/index.json +1 -1
  69. package/data/project-types/jo/job-hunt/versions/1.0.2/about.md +10 -0
  70. package/data/project-types/jo/job-hunt/versions/1.0.2/activity.json +4 -0
  71. package/data/project-types/jo/job-hunt/versions/1.0.2/manifest.json +213 -0
  72. package/data/project-types/jo/job-hunt/versions/1.0.2/mission.md +9 -0
  73. package/data/project-types/jo/job-hunt/versions/1.0.2/pages/dashboard/index.html +430 -0
  74. package/data/project-types/jo/job-hunt/versions/1.0.2/pipeline.json +6 -0
  75. package/data/project-types/la/language-trainer/versions/1.0.2/about.md +6 -0
  76. package/data/project-types/la/language-trainer/versions/1.0.2/manifest.json +80 -0
  77. package/data/project-types/la/language-trainer/versions/1.0.2/mission.md +4 -0
  78. package/data/project-types/la/language-trainer/versions/1.0.2/pages/dashboard/index.html +268 -0
  79. package/data/project-types/la/language-trainer/versions/1.0.2/progress.json +7 -0
  80. package/data/project-types/li/life-binder/versions/1.0.3/about.md +7 -0
  81. package/data/project-types/li/life-binder/versions/1.0.3/documents.json +145 -0
  82. package/data/project-types/li/life-binder/versions/1.0.3/manifest.json +138 -0
  83. package/data/project-types/li/life-binder/versions/1.0.3/mission.md +6 -0
  84. package/data/project-types/li/life-binder/versions/1.0.3/pages/dashboard/index.html +248 -0
  85. package/data/project-types/me/meal-planner/versions/1.0.2/about.md +6 -0
  86. package/data/project-types/me/meal-planner/versions/1.0.2/manifest.json +160 -0
  87. package/data/project-types/me/meal-planner/versions/1.0.2/menu.json +5 -0
  88. package/data/project-types/me/meal-planner/versions/1.0.2/mission.md +5 -0
  89. package/data/project-types/me/meal-planner/versions/1.0.2/pages/dashboard/index.html +248 -0
  90. package/data/project-types/me/meal-planner/versions/1.0.2/pantry.json +9 -0
  91. package/data/project-types/no/novel-writing-room/versions/1.0.2/about.md +7 -0
  92. package/data/project-types/no/novel-writing-room/versions/1.0.2/chapters.json +10 -0
  93. package/data/project-types/no/novel-writing-room/versions/1.0.2/manifest.json +158 -0
  94. package/data/project-types/no/novel-writing-room/versions/1.0.2/mission.md +7 -0
  95. package/data/project-types/no/novel-writing-room/versions/1.0.2/pages/dashboard/index.html +264 -0
  96. package/data/project-types/no/novel-writing-room/versions/1.0.2/sessions.json +6 -0
  97. package/data/project-types/raw-index.json +1 -1
  98. package/data/project-types/tr/trip-planner/versions/1.0.2/about.md +7 -0
  99. package/data/project-types/tr/trip-planner/versions/1.0.2/journal.json +4 -0
  100. package/data/project-types/tr/trip-planner/versions/1.0.2/manifest.json +194 -0
  101. package/data/project-types/tr/trip-planner/versions/1.0.2/mission.md +4 -0
  102. package/data/project-types/tr/trip-planner/versions/1.0.2/packing.json +8 -0
  103. package/data/project-types/tr/trip-planner/versions/1.0.2/pages/dashboard/index.html +308 -0
  104. package/data/project-types/tr/trip-planner/versions/1.0.2/trip.json +7 -0
  105. package/package.json +1 -1
  106. package/schemas/project-type-version.schema.json +13 -0
@@ -0,0 +1,6 @@
1
+ A project for exploring the visual direction of **{{brief}}**. Work with a designer to
2
+ develop color schemes and design directions, iterate on them in conversation, and keep the
3
+ ones that feel right.
4
+
5
+ Open the **Output** tab to see the scheme gallery — every palette the designer saves shows
6
+ up there as a set of swatches you can compare at a glance.
@@ -0,0 +1,70 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.2",
4
+ "releasedAt": "2026-10-06T00:00:00Z",
5
+ "extends": "design-prototype",
6
+ "params": {
7
+ "type": "object",
8
+ "properties": {
9
+ "brief": {
10
+ "type": "string",
11
+ "title": "What are you designing?",
12
+ "description": "A short brief — e.g. \"a calm meditation app\", \"a bold sports brand\".",
13
+ "default": "a new brand"
14
+ }
15
+ },
16
+ "required": [
17
+ "brief"
18
+ ]
19
+ },
20
+ "nameTemplate": "Design Scheme: {{brief}}",
21
+ "aboutTemplate": "about.md",
22
+ "missionTemplate": "mission.md",
23
+ "gezels": [
24
+ {
25
+ "templateId": "designer",
26
+ "voorman": true
27
+ }
28
+ ],
29
+ "scripts": {
30
+ "scheme-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'scheme-store',\n description:\n \"Save and list the project's design color schemes. Call action:'save' with a name and an array of hex colors to add a scheme (it lands in artifacts/schemes/ and shows on the project's gallery). Call action:'list' to see what's saved.\",\n inputs: {\n action: { type: 'string', description: \"'save' or 'list'.\", required: true },\n name: { type: 'string', description: \"Scheme name (required for 'save'), e.g. 'Dusk'.\" },\n colors: {\n type: 'json',\n description: \"Array of hex colors for 'save', e.g. [\\\"#1b1a2e\\\", \\\"#e0894a\\\", \\\"#f5f0e8\\\"].\",\n },\n },\n outputs: {\n count: { type: 'number', description: 'How many schemes are saved.' },\n message: { type: 'string', description: 'Human-readable summary.' },\n },\n requires: ['artifacts.read', 'artifacts.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\ninterface Scheme {\n name: string;\n file: string;\n colors: string[];\n}\n\nconst INDEX = 'schemes/index.json';\n\nasync function loadIndex(): Promise<Scheme[]> {\n try {\n const raw = await gezel.artifacts.read(INDEX);\n const parsed = JSON.parse(raw) as { schemes?: Scheme[] };\n return Array.isArray(parsed.schemes) ? parsed.schemes : [];\n } catch {\n return [];\n }\n}\n\nfunction slugify(s: string): string {\n return (\n s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '') || 'scheme'\n );\n}\n\nconst schemes = await loadIndex();\n\nif (input.action === 'save') {\n const name = (input.name ?? '').trim();\n const colors = Array.isArray(input.colors) ? input.colors.map(String) : [];\n if (!name || colors.length === 0) {\n gezel.output({ count: schemes.length, message: 'save needs a name and a non-empty colors array.' });\n } else {\n const file = `schemes/${slugify(name)}.json`;\n await gezel.artifacts.write(file, `${JSON.stringify({ name, colors }, null, 2)}\\n`);\n const next = [...schemes.filter((s) => s.file !== file), { name, file, colors }];\n await gezel.artifacts.write(INDEX, `${JSON.stringify({ schemes: next }, null, 2)}\\n`);\n gezel.output({ count: next.length, message: `Saved \"${name}\" (${colors.length} colors). ${next.length} scheme(s) total.` });\n }\n} else {\n gezel.output({\n count: schemes.length,\n message: schemes.length\n ? `${schemes.length} scheme(s): ${schemes.map((s) => s.name).join(', ')}.`\n : 'No schemes saved yet.',\n });\n}\n"
31
+ },
32
+ "tools": [
33
+ {
34
+ "name": "save_scheme",
35
+ "description": "Save a named color scheme so it shows on the gallery.",
36
+ "script": "scheme-store",
37
+ "inputs": {
38
+ "type": "object",
39
+ "properties": {
40
+ "name": {
41
+ "type": "string"
42
+ },
43
+ "colors": {
44
+ "type": "array",
45
+ "items": {
46
+ "type": "string"
47
+ }
48
+ }
49
+ }
50
+ },
51
+ "bind": {
52
+ "action": "save"
53
+ }
54
+ }
55
+ ],
56
+ "pages": {
57
+ "entry": "gallery/index.html",
58
+ "api": 1,
59
+ "reads": [
60
+ {
61
+ "source": "artifacts",
62
+ "path": "schemes/index.json"
63
+ }
64
+ ]
65
+ },
66
+ "artifactsSeed": [
67
+ "schemes/index.json",
68
+ "schemes/warm-dusk.json"
69
+ ]
70
+ }
@@ -0,0 +1,3 @@
1
+ - Develop color schemes and design directions for {{brief}}.
2
+ - Save each promising palette so it shows on the gallery for comparison.
3
+ - Converge on a small set of strong directions worth taking forward.
@@ -0,0 +1,212 @@
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>Scheme gallery</title>
8
+ <style>
9
+ :root { color-scheme: light dark; --bg: #faf7f2; --card: #fff; --ink: #2b2620; --muted: #7a7166; --line: #ece5da; --accent: #8a5a2b; }
10
+ :root[data-theme='dark'] { --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c; --line: #3a352f; --accent: #e0894a; }
11
+ @media (prefers-color-scheme: dark) {
12
+ :root:not([data-theme='light']) { --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c; --line: #3a352f; --accent: #e0894a; }
13
+ }
14
+ * { box-sizing: border-box; }
15
+ body { margin: 0; font: 16px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: var(--bg); color: var(--ink); padding: 2rem 1.25rem; }
16
+ main { max-width: 720px; margin: 0 auto; }
17
+ h1 { font-size: 1.5rem; margin: 0 0 0.25rem; }
18
+ .sub { color: var(--muted); margin: 0 0 1.5rem; }
19
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
20
+ .card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
21
+ .swatches { display: flex; height: 84px; }
22
+ .swatches > span { flex: 1; }
23
+ .card .name { padding: 0.6rem 0.85rem; font-weight: 600; }
24
+ .card .hexes { padding: 0 0.85rem 0.75rem; color: var(--muted); font-size: 0.78rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
25
+ .empty { color: var(--muted); font-style: italic; }
26
+ .demo-banner {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ gap: 0.75rem;
31
+ margin: 0 0 0.85rem;
32
+ padding: 0.5rem 0.65rem;
33
+ border: 1px solid var(--accent);
34
+ border-radius: 6px;
35
+ background: color-mix(in srgb, var(--card) 88%, var(--accent));
36
+ color: var(--ink);
37
+ font-size: 0.8rem;
38
+ }
39
+ .demo-banner[hidden] { display: none; }
40
+ .demo-banner strong { font-weight: 700; }
41
+ .demo-reset {
42
+ flex: none;
43
+ padding: 0.25rem 0.5rem;
44
+ border: 1px solid var(--accent);
45
+ border-radius: 6px;
46
+ background: var(--card);
47
+ color: var(--ink);
48
+ cursor: pointer;
49
+ font: inherit;
50
+ }
51
+ .demo-reset:hover { border-color: var(--ink); }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <main>
56
+ <div class="demo-banner" data-gezel-demo-banner hidden>
57
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
58
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
59
+ </div>
60
+ <h1>Scheme gallery</h1>
61
+ <p class="sub" id="sub">Loading schemes…</p>
62
+ <div class="grid" id="grid"></div>
63
+ </main>
64
+ <script>
65
+ /* gezel-page-demo-stub v1 */
66
+ /*
67
+ * The canonical demo stub for Gezel type pages. MIT License.
68
+ *
69
+ * Paste this file's bytes verbatim into a <script> block ahead of the
70
+ * page's own code, then call makeDemoGezel({ page, tools, data }) with the
71
+ * page's demo handlers. Served by Gezel, the real window.gezel is already
72
+ * injected and the stub steps aside; opened as a raw file (the live demos
73
+ * on gezelgilde.com), it installs a window.gezel whose tools and data are
74
+ * the page-supplied handler maps — one code path in every mode. `tools`
75
+ * and `data` map a tool name / declared read path to a function; data
76
+ * handlers back read() and list(), and url() returns the handler's value
77
+ * or passes a data: URI through. See docs/page-authoring.md.
78
+ */
79
+ (function () {
80
+ 'use strict';
81
+ window.makeDemoGezel = function (opts) {
82
+ if (window.gezel) return window.gezel;
83
+ var page = (opts && opts.page) || {};
84
+ var tools = (opts && opts.tools) || {};
85
+ var data = (opts && opts.data) || {};
86
+ var themeListeners = [];
87
+ var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
88
+ var ui = {
89
+ theme: { mode: media && media.matches ? 'dark' : 'light' },
90
+ onTheme: function (cb) {
91
+ themeListeners.push(cb);
92
+ return function () {
93
+ var at = themeListeners.indexOf(cb);
94
+ if (at >= 0) themeListeners.splice(at, 1);
95
+ };
96
+ },
97
+ };
98
+ if (media && media.addEventListener) {
99
+ media.addEventListener('change', function (ev) {
100
+ ui.theme = { mode: ev.matches ? 'dark' : 'light' };
101
+ themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
102
+ });
103
+ }
104
+ function fail(code, message) {
105
+ var err = new Error(message);
106
+ err.code = code;
107
+ return Promise.reject(err);
108
+ }
109
+ function run(map, key, arg) {
110
+ if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
111
+ return Promise.resolve().then(function () { return map[key](arg); });
112
+ }
113
+ window.gezel = {
114
+ page: {
115
+ api: 1,
116
+ projectId: 'demo',
117
+ source: 'type',
118
+ entry: page.entry || 'index.html',
119
+ typeName: page.typeName || 'demo',
120
+ params: page.params || {},
121
+ mode: 'demo',
122
+ },
123
+ tools: {
124
+ list: function () { return Promise.resolve(Object.keys(tools)); },
125
+ invoke: function (name, input) {
126
+ var call = run(tools, name, input || {});
127
+ if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
128
+ return call.then(function (output) { return { output: output }; });
129
+ },
130
+ },
131
+ data: {
132
+ read: function (path, o) {
133
+ return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
134
+ },
135
+ list: function (path, o) {
136
+ return run(data, path, o || {}) || Promise.resolve([]);
137
+ },
138
+ watch: function () { return function () {}; },
139
+ url: function (path) {
140
+ if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
141
+ return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
142
+ },
143
+ },
144
+ ui: ui,
145
+ refresh: function () {},
146
+ };
147
+ return window.gezel;
148
+ };
149
+ })();
150
+ </script>
151
+ <script>
152
+ (function () {
153
+ 'use strict';
154
+
155
+ // ── demo schemes (used only when the page opens as a raw file) ──
156
+ var demoSchemes = [
157
+ { name: 'Warm Dusk', colors: ['#2B2620', '#8D694D', '#D5B58C', '#EFE4D1', '#FFF8E8'] },
158
+ { name: 'Canal Morning', colors: ['#19323C', '#4F6D7A', '#9BB8B3', '#E7E3D4', '#F7F4EC'] },
159
+ { name: 'Workshop Sage', colors: ['#26352D', '#557A5B', '#A7B89A', '#DDD5BF', '#FAF7F2'] },
160
+ ];
161
+
162
+ makeDemoGezel({
163
+ page: { entry: 'gallery/index.html', typeName: 'Design Scheme', params: { brief: 'a new brand' } },
164
+ data: {
165
+ 'schemes/index.json': function () { return { schemes: demoSchemes }; },
166
+ },
167
+ });
168
+
169
+ // ── one code path from here: window.gezel exists in every mode ──
170
+ var demoMode = gezel.page.mode === 'demo';
171
+
172
+ function applyTheme(mode) {
173
+ document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
174
+ }
175
+ applyTheme(gezel.ui.theme.mode);
176
+ gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
177
+
178
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
179
+
180
+ function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
181
+ function isHex(c) { return typeof c === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(c); }
182
+ function render(schemes) {
183
+ var sub = document.getElementById('sub');
184
+ var grid = document.getElementById('grid');
185
+ if (!schemes.length) {
186
+ sub.textContent = 'No schemes yet — ask the designer to save one.';
187
+ grid.innerHTML = '';
188
+ return;
189
+ }
190
+ sub.textContent = schemes.length + ' scheme' + (schemes.length === 1 ? '' : 's');
191
+ grid.innerHTML = schemes.map(function (s) {
192
+ var colors = Array.isArray(s.colors) ? s.colors.filter(isHex) : [];
193
+ var bars = colors.map(function (c) { return '<span style="background:' + c + '"></span>'; }).join('');
194
+ return '<div class="card"><div class="swatches">' + bars + '</div>' +
195
+ '<div class="name">' + esc(s.name || 'Untitled') + '</div>' +
196
+ '<div class="hexes">' + colors.map(esc).join(' · ') + '</div></div>';
197
+ }).join('');
198
+ }
199
+
200
+ function loadSchemes() {
201
+ return gezel.data
202
+ .read('schemes/index.json', { source: 'artifacts' })
203
+ .then(function (data) { render(data && Array.isArray(data.schemes) ? data.schemes : []); })
204
+ .catch(function () { render([]); });
205
+ }
206
+
207
+ loadSchemes();
208
+ gezel.data.watch('schemes/index.json', function () { loadSchemes(); }, { source: 'artifacts' });
209
+ })();
210
+ </script>
211
+ </body>
212
+ </html>
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemes": [
3
+ {
4
+ "name": "Warm Dusk",
5
+ "file": "schemes/warm-dusk.json",
6
+ "colors": ["#1b1a2e", "#3a2e4d", "#b5651d", "#e0894a", "#f5f0e8"]
7
+ }
8
+ ]
9
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "Warm Dusk",
3
+ "colors": ["#1b1a2e", "#3a2e4d", "#b5651d", "#e0894a", "#f5f0e8"]
4
+ }
@@ -0,0 +1,7 @@
1
+ The planning table for {{occasion}} — {{date}}. Your Ceremoniemeester keeps the
2
+ three things that make or break an event: a guest list that's always current, a
3
+ budget recorded when money is committed, and a day-of runbook built early.
4
+
5
+ The **Output** tab shows the countdown, the headcount, and where the budget
6
+ stands. Ask "what does this week need?" and get the two or three things that
7
+ matter, not the whole list.
@@ -0,0 +1,5 @@
1
+ {
2
+ "version": 1,
3
+ "currency": "USD",
4
+ "entries": []
5
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 1,
3
+ "eventDate": "{{date}}",
4
+ "stages": [
5
+ "invited",
6
+ "yes",
7
+ "no"
8
+ ],
9
+ "records": []
10
+ }
@@ -0,0 +1,171 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.2",
4
+ "releasedAt": "2026-10-06T00:00:00Z",
5
+ "meesterManaged": false,
6
+ "params": {
7
+ "type": "object",
8
+ "properties": {
9
+ "occasion": {
10
+ "type": "string",
11
+ "title": "What are we celebrating?",
12
+ "description": "e.g. 'our wedding', 'Dad's 70th', 'the class reunion'.",
13
+ "default": "a big celebration"
14
+ },
15
+ "date": {
16
+ "type": "string",
17
+ "title": "When is it?",
18
+ "description": "The date, e.g. 2026-09-12. A rough date is fine — change it later.",
19
+ "default": "2026-12-31"
20
+ }
21
+ },
22
+ "required": [
23
+ "occasion",
24
+ "date"
25
+ ]
26
+ },
27
+ "nameTemplate": "Event: {{occasion}}",
28
+ "aboutTemplate": "about.md",
29
+ "missionTemplate": "mission.md",
30
+ "gezels": [
31
+ {
32
+ "templateId": "ceremoniemeester",
33
+ "voorman": true
34
+ }
35
+ ],
36
+ "tools": [
37
+ {
38
+ "name": "add_guest",
39
+ "description": "Add a guest or party to the list (status starts at invited).",
40
+ "script": "event-store",
41
+ "inputs": {
42
+ "type": "object",
43
+ "properties": {
44
+ "name": {
45
+ "type": "string"
46
+ },
47
+ "party": {
48
+ "type": "number"
49
+ },
50
+ "note": {
51
+ "type": "string"
52
+ }
53
+ },
54
+ "required": [
55
+ "name"
56
+ ]
57
+ },
58
+ "bind": {
59
+ "action": "add_guest"
60
+ }
61
+ },
62
+ {
63
+ "name": "guest_status",
64
+ "description": "Record an RSVP: 'yes', 'no', or back to 'invited'.",
65
+ "script": "event-store",
66
+ "inputs": {
67
+ "type": "object",
68
+ "properties": {
69
+ "id": {
70
+ "type": "string"
71
+ },
72
+ "name": {
73
+ "type": "string"
74
+ },
75
+ "status": {
76
+ "type": "string",
77
+ "enum": [
78
+ "invited",
79
+ "yes",
80
+ "no"
81
+ ]
82
+ },
83
+ "note": {
84
+ "type": "string"
85
+ }
86
+ },
87
+ "required": [
88
+ "status"
89
+ ]
90
+ },
91
+ "bind": {
92
+ "action": "guest_status"
93
+ }
94
+ },
95
+ {
96
+ "name": "record_spend",
97
+ "description": "Record committed spending (deposit, balance, purchase) in ordinary units.",
98
+ "script": "event-store",
99
+ "inputs": {
100
+ "type": "object",
101
+ "properties": {
102
+ "amount": {
103
+ "type": "number"
104
+ },
105
+ "category": {
106
+ "type": "string"
107
+ },
108
+ "note": {
109
+ "type": "string"
110
+ }
111
+ },
112
+ "required": [
113
+ "amount",
114
+ "category"
115
+ ]
116
+ },
117
+ "bind": {
118
+ "action": "record_spend"
119
+ }
120
+ },
121
+ {
122
+ "name": "event_status",
123
+ "description": "Countdown, headcount, and budget in one line.",
124
+ "script": "event-store",
125
+ "inputs": {
126
+ "type": "object",
127
+ "properties": {}
128
+ },
129
+ "bind": {
130
+ "action": "status"
131
+ }
132
+ },
133
+ {
134
+ "name": "guest_list",
135
+ "description": "Read the whole guest list with statuses.",
136
+ "script": "event-store",
137
+ "inputs": {
138
+ "type": "object",
139
+ "properties": {}
140
+ },
141
+ "bind": {
142
+ "action": "guest_list"
143
+ }
144
+ }
145
+ ],
146
+ "craftbooks": [
147
+ "runbook"
148
+ ],
149
+ "schedules": [],
150
+ "pages": {
151
+ "entry": "dashboard/index.html",
152
+ "api": 1,
153
+ "reads": [
154
+ {
155
+ "source": "workspace",
156
+ "path": "guests.json"
157
+ },
158
+ {
159
+ "source": "workspace",
160
+ "path": "budget.json"
161
+ }
162
+ ]
163
+ },
164
+ "workspaceSeed": [
165
+ "guests.json",
166
+ "budget.json"
167
+ ],
168
+ "scripts": {
169
+ "event-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { ledgerStore, rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'event-store',\n description:\n \"Read and update the event: guests (guests.json) and spending (budget.json). Call action:'status' for headcount + budget + countdown; 'add_guest' as invitations go out; 'guest_status' as RSVPs land (invited/yes/no); 'record_spend' when money is committed (ordinary units); 'guest_list' to read everyone.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'add_guest', 'guest_status', 'record_spend', or 'guest_list'.\",\n required: true,\n },\n name: { type: 'string', description: 'Guest name (party name is fine).' },\n party: { type: 'number', description: 'How many people this entry covers (default 1).' },\n status: { type: 'string', description: \"RSVP status: 'invited', 'yes', or 'no'.\" },\n id: { type: 'string', description: \"Guest id, e.g. 'guest-4'.\" },\n amount: { type: 'number', description: 'Spend amount in ordinary units.' },\n category: { type: 'string', description: \"Spend category, e.g. 'venue', 'catering'.\" },\n note: { type: 'string', description: 'Short note.' },\n },\n outputs: {\n invited: { type: 'number', description: 'People invited (sum of parties).' },\n coming: { type: 'number', description: \"People who said yes.\" },\n spentCents: { type: 'number', description: 'Total committed spend, in cents.' },\n summary: { type: 'string', description: 'The event picture.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── event-rules-start ──\n// Pure derivations: headcounts weighted by party size, and the countdown.\n\nfunction toCents(amount) {\n if (typeof amount !== 'number' || !isFinite(amount) || amount <= 0) return null;\n return Math.round(amount * 100);\n}\n\nfunction fmtCents(cents) {\n var abs = Math.abs(cents);\n return (cents < 0 ? '-' : '') + Math.floor(abs / 100) + '.' + String(abs % 100).padStart(2, '0');\n}\n\nfunction partyOf(record) {\n var p = record.fields && record.fields.party;\n return typeof p === 'number' && p >= 1 ? Math.floor(p) : 1;\n}\n\nfunction headcounts(records) {\n var invited = 0;\n var coming = 0;\n var awaiting = 0;\n records.forEach(function (r) {\n var size = partyOf(r);\n invited += size;\n if (r.status === 'yes') coming += size;\n if (r.status === 'invited') awaiting += size;\n });\n return { invited: invited, coming: coming, awaiting: awaiting };\n}\n\nfunction daysUntil(dateStr, nowMs) {\n var when = Date.parse(dateStr);\n if (!isFinite(when)) return null;\n return Math.ceil((when - nowMs) / (24 * 60 * 60 * 1000));\n}\n\nfunction eventLine(records, spentCents, eventDate, nowMs) {\n var heads = headcounts(records);\n var days = daysUntil(eventDate, nowMs);\n return (\n (days === null ? '' : days >= 0 ? days + ' day(s) to go. ' : 'The day has passed. ') +\n heads.coming +\n ' coming, ' +\n heads.awaiting +\n ' awaiting reply (' +\n heads.invited +\n ' invited). Spent ' +\n fmtCents(spentCents) +\n '.'\n );\n}\n// ── event-rules-end ──\n\nconst guests = rosterStore(gezel.fs, 'guests.json', {\n stages: ['invited', 'yes', 'no'],\n idPrefix: 'guest',\n});\nconst budget = ledgerStore(gezel.fs, 'budget.json');\n\nasync function readEventDate(): Promise<string> {\n try {\n const parsed = JSON.parse(await gezel.fs.read('guests.json')) as { eventDate?: string };\n return typeof parsed.eventDate === 'string' ? parsed.eventDate : '';\n } catch {\n return '';\n }\n}\n\nasync function resolveGuest(): Promise<string> {\n if (input.id) return input.id;\n if (input.name) {\n const wanted = input.name.toLowerCase();\n const matches = (await guests.list()).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. 'guest-4') or an exact guest 'name'.\");\n}\n\nif (input.action === 'add_guest') {\n if (!input.name) throw new Error(\"add_guest needs 'name'.\");\n await guests.add({\n status: 'invited',\n fields: {\n name: input.name,\n ...(typeof input.party === 'number' && input.party >= 1 ? { party: Math.floor(input.party) } : {}),\n ...(input.note ? { note: input.note } : {}),\n },\n });\n} else if (input.action === 'guest_status') {\n if (!input.status || !['invited', 'yes', 'no'].includes(input.status)) {\n throw new Error(\"guest_status needs status 'invited', 'yes', or 'no'.\");\n }\n await guests.transition(await resolveGuest(), input.status, input.note ? { note: input.note } : {});\n} else if (input.action === 'record_spend') {\n const cents = toCents(input.amount);\n if (cents === null || !input.category) {\n throw new Error(\"record_spend needs a positive 'amount' and a 'category' (e.g. 'venue').\");\n }\n await budget.add({\n amountCents: cents,\n kind: 'out',\n category: input.category.toLowerCase(),\n ...(input.note ? { note: input.note } : {}),\n });\n} else if (input.action !== 'status' && input.action !== 'guest_list') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, add_guest, guest_status, record_spend, or guest_list.`,\n );\n}\n\nconst records = await guests.list();\nconst totals = await budget.totals();\nconst heads = headcounts(records);\nconst eventDate = await readEventDate();\nconst listing =\n input.action === 'guest_list'\n ? records\n .map(\n (r) =>\n `${r.id} [${r.status}] ${String(r.fields.name ?? '?')}${partyOf(r) > 1 ? ` (party of ${partyOf(r)})` : ''}`,\n )\n .join('\\n') || 'The guest list is empty.'\n : '';\n\ngezel.output({\n invited: heads.invited,\n coming: heads.coming,\n spentCents: totals.outCents,\n summary: listing || eventLine(records, totals.outCents, eventDate, Date.now()),\n});\n"
170
+ }
171
+ }
@@ -0,0 +1,6 @@
1
+ - The day goes beautifully — planned backward from {{date}}, with a day-of
2
+ runbook so complete nobody has to think.
3
+ - The guest list is always current: every RSVP recorded the day it lands,
4
+ headcounts answered from the list.
5
+ - The budget is honest: money recorded when committed, not when remembered.
6
+ - After the event, every thank-you goes out — tracked to done.