@bendyline/gilde 0.1.6 → 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.
- package/README.md +17 -0
- package/data/chat-models/ge/{gemma4-e2b-q8 → gemma4-e2b-q4}/manifest.json +28 -31
- package/data/chat-models/ge/gemma4-e2b-q4/versions/1.2.0/manifest.json +71 -0
- package/data/chat-models/ge/{gemma4-e4b-q8 → gemma4-e4b-q4}/manifest.json +28 -26
- package/data/chat-models/ge/gemma4-e4b-q4/versions/1.2.0/manifest.json +76 -0
- package/data/chat-models/index.json +1 -1
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/about.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/care.json +4 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/manifest.json +177 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/meds.json +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/mission.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/pages/dashboard/index.html +167 -0
- package/data/project-types/ch/checkers/versions/1.1.2/about.md +9 -0
- package/data/project-types/ch/checkers/versions/1.1.2/game.json +77 -0
- package/data/project-types/ch/checkers/versions/1.1.2/manifest.json +166 -0
- package/data/project-types/ch/checkers/versions/1.1.2/mission.md +9 -0
- package/data/project-types/ch/checkers/versions/1.1.2/pages/board/index.html +1091 -0
- package/data/project-types/ch/chess/versions/1.0.2/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.2/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.2/manifest.json +180 -0
- package/data/project-types/ch/chess/versions/1.0.2/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.2/pages/board/index.html +627 -0
- package/data/project-types/ch/chess/versions/1.0.3/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.3/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.3/manifest.json +180 -0
- package/data/project-types/ch/chess/versions/1.0.3/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.3/pages/board/index.html +769 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/about.md +6 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/manifest.json +69 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/mission.md +3 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/pages/gallery/index.html +105 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/schemes/index.json +9 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/schemes/warm-dusk.json +4 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/about.md +7 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/budget.json +5 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/guests.json +10 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/manifest.json +170 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/mission.md +6 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/pages/dashboard/index.html +186 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/about.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/manifest.json +125 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/mission.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/pages/dashboard/index.html +172 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/training.json +6 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/about.md +8 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/deck.json +7 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/manifest.json +167 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/pages/review/index.html +396 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/about.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/invoices.json +9 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/manifest.json +142 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/pages/dashboard/index.html +157 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/about.md +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/donations.json +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/manifest.json +126 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/pages/dashboard/index.html +160 -0
- package/data/project-types/go/go/versions/1.0.1/about.md +9 -0
- package/data/project-types/go/go/versions/1.0.1/game.json +103 -0
- package/data/project-types/go/go/versions/1.0.1/manifest.json +166 -0
- package/data/project-types/go/go/versions/1.0.1/mission.md +8 -0
- package/data/project-types/go/go/versions/1.0.1/pages/board/index.html +778 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/about.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/ledger.json +5 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/manifest.json +163 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/mission.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/pages/dashboard/index.html +233 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/about.md +7 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/manifest.json +152 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/mission.md +8 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/pages/dashboard/index.html +157 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/systems.json +8 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/jo/job-hunt/versions/1.0.1/about.md +10 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/activity.json +4 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/manifest.json +212 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/mission.md +9 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/pages/dashboard/index.html +327 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/pipeline.json +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/about.md +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/manifest.json +79 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/mission.md +4 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/pages/dashboard/index.html +153 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/progress.json +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/about.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/documents.json +145 -0
- package/data/project-types/li/life-binder/versions/1.0.1/manifest.json +137 -0
- package/data/project-types/li/life-binder/versions/1.0.1/mission.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/pages/dashboard/index.html +147 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/about.md +6 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/manifest.json +159 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/menu.json +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/mission.md +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/pages/dashboard/index.html +152 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/pantry.json +9 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/about.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/chapters.json +10 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/manifest.json +157 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/mission.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/pages/dashboard/index.html +165 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/sessions.json +6 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/about.md +7 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/journal.json +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/manifest.json +193 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/mission.md +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/packing.json +8 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/pages/dashboard/index.html +215 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/trip.json +7 -0
- package/package.json +4 -2
- /package/data/chat-models/ge/{gemma4-e2b-q8 → gemma4-e2b-q4}/versions/1.1.0/manifest.json +0 -0
- /package/data/chat-models/ge/{gemma4-e2b-q8 → gemma4-e2b-q4}/versions/1.1.2/manifest.json +0 -0
- /package/data/chat-models/ge/{gemma4-e4b-q8 → gemma4-e4b-q4}/versions/1.1.0/manifest.json +0 -0
- /package/data/chat-models/ge/{gemma4-e4b-q8 → gemma4-e4b-q4}/versions/1.1.2/manifest.json +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
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>Freelance office</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: 820px; 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 1rem; }
|
|
25
|
+
.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; margin-bottom: 1rem; }
|
|
26
|
+
.tile { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.7rem 0.85rem; }
|
|
27
|
+
.tile .n { font-size: 1.35rem; font-weight: 700; color: var(--accent); line-height: 1.2; }
|
|
28
|
+
.tile .n.warn { color: var(--warn); }
|
|
29
|
+
.tile .n.good { color: var(--good); }
|
|
30
|
+
.tile .l { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
31
|
+
.board { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; }
|
|
32
|
+
.col h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.4rem; font-weight: 600; display: flex; justify-content: space-between; }
|
|
33
|
+
.col h2 .count { color: var(--ink); }
|
|
34
|
+
.card { background: var(--card); border: 1px solid var(--track); border-radius: 6px; padding: 0.5rem 0.6rem; margin-bottom: 0.45rem; font-size: 0.85rem; }
|
|
35
|
+
.card .co { font-weight: 600; overflow-wrap: anywhere; }
|
|
36
|
+
.card .amt { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
37
|
+
.chip { display: inline-block; border: 1px solid var(--warn); color: var(--warn); border-radius: 4px; padding: 0 0.3rem; font-size: 0.7rem; line-height: 1.4; margin-left: 0.3rem; }
|
|
38
|
+
.empty { color: var(--muted); font-style: italic; background: var(--card); border: 1px dashed var(--track); border-radius: 10px; padding: 0.85rem 1.25rem; text-align: center; margin-bottom: 0.75rem; }
|
|
39
|
+
@media (max-width: 640px) { .board, .tiles { grid-template-columns: 1fr; } }
|
|
40
|
+
+ .demo-banner {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
gap: 0.75rem;
|
|
45
|
+
margin: 0 0 0.85rem;
|
|
46
|
+
padding: 0.5rem 0.65rem;
|
|
47
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
48
|
+
border-radius: 6px;
|
|
49
|
+
background: color-mix(in srgb, var(--card, var(--surface, #fffdf8)) 88%, var(--accent, #8a5a2b));
|
|
50
|
+
color: var(--ink, #2b2620);
|
|
51
|
+
font-size: 0.8rem;
|
|
52
|
+
}
|
|
53
|
+
.demo-banner[hidden] { display: none; }
|
|
54
|
+
.demo-banner strong { font-weight: 700; }
|
|
55
|
+
.demo-reset {
|
|
56
|
+
flex: none;
|
|
57
|
+
padding: 0.25rem 0.5rem;
|
|
58
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
background: var(--card, var(--surface, #fffdf8));
|
|
61
|
+
color: var(--ink, #2b2620);
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
font: inherit;
|
|
64
|
+
}
|
|
65
|
+
.demo-reset:hover { border-color: var(--ink, #2b2620); }
|
|
66
|
+
</style>
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<main>
|
|
70
|
+
+ <div class="demo-banner" data-gezel-demo-banner hidden>
|
|
71
|
+
<span><strong>Demo mode</strong> — sample data stays in this page.</span>
|
|
72
|
+
<button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
|
|
73
|
+
</div>
|
|
74
|
+
<h1>Freelance office</h1>
|
|
75
|
+
<p class="sub" id="sub">Loading the invoice book…</p>
|
|
76
|
+
<div class="tiles">
|
|
77
|
+
<div class="tile"><div class="n" id="t-out">—</div><div class="l">Outstanding</div></div>
|
|
78
|
+
<div class="tile"><div class="n warn" id="t-over">—</div><div class="l">Overdue</div></div>
|
|
79
|
+
<div class="tile"><div class="n good" id="t-paid">—</div><div class="l">Paid this month</div></div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="empty" id="empty" hidden>No invoices yet — tell your Kantoormeester when you bill something.</div>
|
|
82
|
+
<div class="board" id="board"></div>
|
|
83
|
+
</main>
|
|
84
|
+
<script>
|
|
85
|
+
(function () {
|
|
86
|
+
'use strict';
|
|
87
|
+
var parts = location.pathname.split('/').filter(Boolean);
|
|
88
|
+
var typeIdx = parts.indexOf('type');
|
|
89
|
+
var capability = typeIdx > 0 ? parts[typeIdx - 1] : null;
|
|
90
|
+
var projectId = typeIdx >= 0 ? parts[typeIdx + 1] : null;
|
|
91
|
+
var failedPolls = 0;
|
|
92
|
+
var STAGES = ['draft', 'sent', 'paid'];
|
|
93
|
+
var demoMode = !capability || !projectId;
|
|
94
|
+
var demoMonth = new Date().toISOString();
|
|
95
|
+
var demoInvoices = { records: [
|
|
96
|
+
{ id: 'inv-104', status: 'draft', updatedAt: demoMonth, fields: { client: 'Northwind Studio', amountCents: 125000 } },
|
|
97
|
+
{ id: 'inv-103', status: 'sent', updatedAt: demoMonth, fields: { client: 'Juniper Press', amountCents: 240000, dueAt: new Date(Date.now() + 9 * 86400000).toISOString() } },
|
|
98
|
+
{ id: 'inv-102', status: 'sent', updatedAt: demoMonth, fields: { client: 'Canal House', amountCents: 87500, dueAt: new Date(Date.now() - 4 * 86400000).toISOString() } },
|
|
99
|
+
{ id: 'inv-101', status: 'paid', updatedAt: demoMonth, fields: { client: 'Veld Works', amountCents: 162500 } },
|
|
100
|
+
] };
|
|
101
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
102
|
+
|
|
103
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
104
|
+
function fmt(cents) { var abs = Math.abs(cents); return (cents < 0 ? '-' : '') + Math.floor(abs / 100) + '.' + String(abs % 100).padStart(2, '0'); }
|
|
105
|
+
function cap(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
|
|
106
|
+
|
|
107
|
+
function render(book) {
|
|
108
|
+
var records = Array.isArray(book.records) ? book.records : [];
|
|
109
|
+
var now = Date.now();
|
|
110
|
+
var month = new Date().toISOString().slice(0, 7);
|
|
111
|
+
var outstanding = 0, overdue = 0, paidMonth = 0;
|
|
112
|
+
records.forEach(function (r) {
|
|
113
|
+
var cents = (r.fields && r.fields.amountCents) || 0;
|
|
114
|
+
if (r.status === 'sent') {
|
|
115
|
+
outstanding += cents;
|
|
116
|
+
var due = r.fields && r.fields.dueAt ? Date.parse(r.fields.dueAt) : NaN;
|
|
117
|
+
if (isFinite(due) && due < now) overdue += 1;
|
|
118
|
+
}
|
|
119
|
+
if (r.status === 'paid' && r.updatedAt.slice(0, 7) === month) paidMonth += cents;
|
|
120
|
+
});
|
|
121
|
+
document.getElementById('sub').textContent = records.length + ' invoice(s) in the book';
|
|
122
|
+
document.getElementById('t-out').textContent = fmt(outstanding);
|
|
123
|
+
document.getElementById('t-over').textContent = overdue;
|
|
124
|
+
document.getElementById('t-paid').textContent = fmt(paidMonth);
|
|
125
|
+
document.getElementById('empty').hidden = records.length > 0;
|
|
126
|
+
|
|
127
|
+
document.getElementById('board').innerHTML = STAGES.map(function (stage) {
|
|
128
|
+
var here = records.filter(function (r) { return r.status === stage; });
|
|
129
|
+
var cards = here.map(function (r) {
|
|
130
|
+
var due = r.fields && r.fields.dueAt ? Date.parse(r.fields.dueAt) : NaN;
|
|
131
|
+
var isOver = stage === 'sent' && isFinite(due) && due < now;
|
|
132
|
+
return '<div class="card"><div class="co">' + esc((r.fields && r.fields.client) || r.id) +
|
|
133
|
+
(isOver ? '<span class="chip">overdue</span>' : '') + '</div><div class="amt">' +
|
|
134
|
+
fmt((r.fields && r.fields.amountCents) || 0) +
|
|
135
|
+
(r.fields && r.fields.dueAt ? ' · due ' + esc(String(r.fields.dueAt).slice(0, 10)) : '') +
|
|
136
|
+
'</div></div>';
|
|
137
|
+
}).join('');
|
|
138
|
+
return '<div class="col"><h2><span>' + cap(stage) + '</span><span class="count">' + here.length + '</span></h2>' + cards + '</div>';
|
|
139
|
+
}).join('');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function poll() {
|
|
143
|
+
if (demoMode) { render(demoInvoices); return Promise.resolve(); }
|
|
144
|
+
if (!capability || !projectId) return Promise.resolve();
|
|
145
|
+
return fetch('/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/invoices.json', { cache: 'no-store' })
|
|
146
|
+
.then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); })
|
|
147
|
+
.then(function (next) { failedPolls = 0; render(next); })
|
|
148
|
+
.catch(function () {
|
|
149
|
+
failedPolls += 1;
|
|
150
|
+
if (failedPolls === 3 && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
poll().then(function () { setInterval(function () { void poll(); }, 5000); });
|
|
154
|
+
})();
|
|
155
|
+
</script>
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Headquarters for the drive: **{{cause}}**. Your Aanjager keeps the thermometer
|
|
2
|
+
honest — every gift recorded the moment it lands, every donor thanked before the
|
|
3
|
+
drive forgets — and drafts the outreach and progress posts that keep momentum
|
|
4
|
+
visible.
|
|
5
|
+
|
|
6
|
+
The **Output** tab is the thermometer: raised against the goal, the latest
|
|
7
|
+
gifts, and the thank-yous still owed.
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
"cause": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"title": "What are you raising for?",
|
|
12
|
+
"description": "e.g. 'new jerseys for the team', 'the library roof'.",
|
|
13
|
+
"default": "a good cause"
|
|
14
|
+
},
|
|
15
|
+
"goalCents": {
|
|
16
|
+
"type": "number",
|
|
17
|
+
"title": "Goal (in cents)",
|
|
18
|
+
"description": "The target, in cents — e.g. 500000 for 5,000.00.",
|
|
19
|
+
"default": 100000
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"cause",
|
|
24
|
+
"goalCents"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"nameTemplate": "Fundraiser: {{cause}}",
|
|
28
|
+
"aboutTemplate": "about.md",
|
|
29
|
+
"missionTemplate": "mission.md",
|
|
30
|
+
"gezels": [
|
|
31
|
+
{
|
|
32
|
+
"templateId": "aanjager",
|
|
33
|
+
"voorman": true
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"tools": [
|
|
37
|
+
{
|
|
38
|
+
"name": "record_donation",
|
|
39
|
+
"description": "Record a gift the moment it lands (ordinary units, donor name).",
|
|
40
|
+
"script": "drive-store",
|
|
41
|
+
"inputs": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"donor": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"amount": {
|
|
48
|
+
"type": "number"
|
|
49
|
+
},
|
|
50
|
+
"note": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"required": [
|
|
55
|
+
"amount"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"bind": {
|
|
59
|
+
"action": "record_donation"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "mark_thanked",
|
|
64
|
+
"description": "Record that a thank-you went out for a gift.",
|
|
65
|
+
"script": "drive-store",
|
|
66
|
+
"inputs": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"id": {
|
|
70
|
+
"type": "string"
|
|
71
|
+
},
|
|
72
|
+
"donor": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"bind": {
|
|
78
|
+
"action": "mark_thanked"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "drive_status",
|
|
83
|
+
"description": "The thermometer and the thank-you debt.",
|
|
84
|
+
"script": "drive-store",
|
|
85
|
+
"inputs": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"properties": {}
|
|
88
|
+
},
|
|
89
|
+
"bind": {
|
|
90
|
+
"action": "status"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "donor_list",
|
|
95
|
+
"description": "Every gift, with thank-you state.",
|
|
96
|
+
"script": "drive-store",
|
|
97
|
+
"inputs": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"properties": {}
|
|
100
|
+
},
|
|
101
|
+
"bind": {
|
|
102
|
+
"action": "donor_list"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"craftbooks": [
|
|
107
|
+
"social-thread",
|
|
108
|
+
"drive-wrap-up"
|
|
109
|
+
],
|
|
110
|
+
"schedules": [],
|
|
111
|
+
"pages": {
|
|
112
|
+
"entry": "dashboard/index.html",
|
|
113
|
+
"reads": [
|
|
114
|
+
{
|
|
115
|
+
"source": "workspace",
|
|
116
|
+
"path": "donations.json"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"workspaceSeed": [
|
|
121
|
+
"donations.json"
|
|
122
|
+
],
|
|
123
|
+
"scripts": {
|
|
124
|
+
"drive-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { ledgerStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'drive-store',\n description:\n \"Read and update the fundraising drive (donations.json). Call action:'status' for the thermometer + unthanked list; 'record_donation' the moment a gift lands (ordinary units, donor name); 'mark_thanked' once the thank-you goes out.\",\n inputs: {\n action: {\n type: 'string',\n description: \"One of 'status', 'record_donation', 'mark_thanked', or 'donor_list'.\",\n required: true,\n },\n donor: { type: 'string', description: \"Donor name ('anonymous' is fine).\" },\n amount: { type: 'number', description: 'Gift amount in ordinary units.' },\n note: { type: 'string', description: 'Anything worth remembering about the gift.' },\n id: { type: 'string', description: \"Donation id, e.g. 'ledger-4'.\" },\n },\n outputs: {\n raisedCents: { type: 'number', description: 'Total raised, in cents.' },\n donors: { type: 'number', description: 'Number of gifts.' },\n unthanked: { type: 'number', description: 'Gifts still awaiting a thank-you.' },\n summary: { type: 'string', description: 'The drive picture.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── drive-rules-start ──\n// Pure derivations over ledgerStore-shaped donation entries: entries are\n// kind 'in', category 'donation', data {donor, thanked}.\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 driveShape(entries) {\n var raised = 0;\n var unthanked = [];\n entries.forEach(function (e) {\n raised += e.amountCents;\n if (!e.data || e.data.thanked !== true) unthanked.push(e);\n });\n return { raised: raised, unthanked: unthanked };\n}\n\nfunction driveLine(entries, goalCents) {\n var shape = driveShape(entries);\n var pct = goalCents > 0 ? Math.min(100, Math.round((shape.raised / goalCents) * 100)) : null;\n return (\n 'Raised ' +\n fmtCents(shape.raised) +\n (goalCents > 0 ? ' of ' + fmtCents(goalCents) + ' (' + pct + ' percent)' : '') +\n ' across ' +\n entries.length +\n ' gift(s). ' +\n (shape.unthanked.length === 0\n ? 'Everyone thanked.'\n : shape.unthanked.length + ' thank-you(s) still owed.')\n );\n}\n// ── drive-rules-end ──\n\nconst donations = ledgerStore(gezel.fs, 'donations.json');\n\nasync function readGoalCents(): Promise<number> {\n try {\n const parsed = JSON.parse(await gezel.fs.read('donations.json')) as { goalCents?: unknown };\n const goal = Number(parsed.goalCents);\n return Number.isFinite(goal) && goal > 0 ? Math.round(goal) : 0;\n } catch {\n return 0;\n }\n}\n\nif (input.action === 'record_donation') {\n const cents = toCents(input.amount);\n if (cents === null) throw new Error(\"record_donation needs a positive 'amount' (ordinary units).\");\n await donations.add({\n amountCents: cents,\n kind: 'in',\n category: 'donation',\n ...(input.note ? { note: input.note } : {}),\n data: { donor: input.donor || 'anonymous', thanked: false },\n });\n} else if (input.action === 'mark_thanked') {\n if (!input.id && !input.donor) throw new Error(\"Pass 'id' (e.g. 'ledger-4') or a unique 'donor'.\");\n // ledgerStore is append-only by design; thanked flips via a targeted\n // read-modify-write on the entry's data (the one mutation this drive needs).\n const raw = JSON.parse(await gezel.fs.read('donations.json')) as {\n entries: Array<{ id: string; data?: { donor?: string; thanked?: boolean } }>;\n };\n const matches = raw.entries.filter((e) =>\n input.id\n ? e.id === input.id\n : String(e.data?.donor ?? '').toLowerCase() === String(input.donor).toLowerCase() &&\n e.data?.thanked !== true,\n );\n if (matches.length === 0) throw new Error('No matching unthanked donation found.');\n if (matches.length > 1 && !input.id) {\n throw new Error(\n `Multiple unthanked gifts from '${input.donor}': ${matches.map((m) => m.id).join(', ')}. Pass id.`,\n );\n }\n matches[0]!.data = { ...(matches[0]!.data ?? {}), thanked: true };\n await gezel.fs.write('donations.json', `${JSON.stringify(raw, null, 2)}\\n`);\n} else if (input.action !== 'status' && input.action !== 'donor_list') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, record_donation, mark_thanked, or donor_list.`,\n );\n}\n\nconst entries = await donations.list();\nconst goalCents = await readGoalCents();\nconst shape = driveShape(entries);\nconst listing =\n input.action === 'donor_list'\n ? entries\n .map(\n (e) =>\n `${e.id} ${fmtCents(e.amountCents)} from ${String(e.data?.donor ?? 'anonymous')}${e.data?.thanked === true ? '' : ' (thank-you owed)'}`,\n )\n .join('\\n') || 'No gifts yet.'\n : '';\n\ngezel.output({\n raisedCents: shape.raised,\n donors: entries.length,\n unthanked: shape.unthanked.length,\n summary: listing || driveLine(entries, goalCents),\n});\n"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
- Reach the goal for {{cause}} — the thermometer tells the truth the whole way.
|
|
2
|
+
- Every gift is recorded the day it lands; every donor is thanked, tracked to
|
|
3
|
+
done.
|
|
4
|
+
- Outreach stays warm and specific — what the money does, never guilt.
|
|
5
|
+
- The wrap-up report names the total, the impact, and the people, ready for the
|
|
6
|
+
next organizer.
|
|
@@ -0,0 +1,160 @@
|
|
|
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>Fundraiser HQ</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: 620px; 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
|
+
.thermo { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 1rem; margin-bottom: 0.75rem; }
|
|
26
|
+
.thermo .raised { font-size: 1.9rem; font-weight: 700; color: var(--good); font-variant-numeric: tabular-nums; }
|
|
27
|
+
.thermo .goal { color: var(--muted); font-size: 0.9rem; }
|
|
28
|
+
.bar { background: var(--track); border-radius: 4px; height: 14px; overflow: hidden; margin-top: 0.6rem; }
|
|
29
|
+
.bar > span { display: block; height: 100%; background: var(--good); border-radius: 4px; transition: width 0.4s ease; }
|
|
30
|
+
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
|
|
31
|
+
.panel { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; font-size: 0.9rem; }
|
|
32
|
+
.panel h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.5rem; }
|
|
33
|
+
.panel ul { margin: 0; padding-left: 1.05rem; }
|
|
34
|
+
.panel li { margin: 0.25rem 0; }
|
|
35
|
+
.panel .amt { font-weight: 600; }
|
|
36
|
+
.panel .who { color: var(--muted); }
|
|
37
|
+
.owed li { color: var(--warn); }
|
|
38
|
+
.empty { color: var(--muted); font-style: italic; }
|
|
39
|
+
@media (max-width: 640px) { .cols { grid-template-columns: 1fr; } }
|
|
40
|
+
+ .demo-banner {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
gap: 0.75rem;
|
|
45
|
+
margin: 0 0 0.85rem;
|
|
46
|
+
padding: 0.5rem 0.65rem;
|
|
47
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
48
|
+
border-radius: 6px;
|
|
49
|
+
background: color-mix(in srgb, var(--card, var(--surface, #fffdf8)) 88%, var(--accent, #8a5a2b));
|
|
50
|
+
color: var(--ink, #2b2620);
|
|
51
|
+
font-size: 0.8rem;
|
|
52
|
+
}
|
|
53
|
+
.demo-banner[hidden] { display: none; }
|
|
54
|
+
.demo-banner strong { font-weight: 700; }
|
|
55
|
+
.demo-reset {
|
|
56
|
+
flex: none;
|
|
57
|
+
padding: 0.25rem 0.5rem;
|
|
58
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
background: var(--card, var(--surface, #fffdf8));
|
|
61
|
+
color: var(--ink, #2b2620);
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
font: inherit;
|
|
64
|
+
}
|
|
65
|
+
.demo-reset:hover { border-color: var(--ink, #2b2620); }
|
|
66
|
+
</style>
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<main>
|
|
70
|
+
+ <div class="demo-banner" data-gezel-demo-banner hidden>
|
|
71
|
+
<span><strong>Demo mode</strong> — sample data stays in this page.</span>
|
|
72
|
+
<button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
|
|
73
|
+
</div>
|
|
74
|
+
<h1 id="title">Fundraiser HQ</h1>
|
|
75
|
+
<p class="sub" id="sub">Loading the drive…</p>
|
|
76
|
+
<div class="thermo">
|
|
77
|
+
<div class="raised" id="raised">—</div>
|
|
78
|
+
<div class="goal" id="goal"></div>
|
|
79
|
+
<div class="bar"><span id="bar" style="width: 0%"></span></div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="cols">
|
|
82
|
+
<div class="panel">
|
|
83
|
+
<h2>Latest gifts</h2>
|
|
84
|
+
<ul id="gifts"><li class="empty">No gifts yet — the first one starts the story.</li></ul>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="panel">
|
|
87
|
+
<h2>Thank-yous owed</h2>
|
|
88
|
+
<ul class="owed" id="owed"><li class="empty">All caught up.</li></ul>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</main>
|
|
92
|
+
<script>
|
|
93
|
+
(function () {
|
|
94
|
+
'use strict';
|
|
95
|
+
var parts = location.pathname.split('/').filter(Boolean);
|
|
96
|
+
var typeIdx = parts.indexOf('type');
|
|
97
|
+
var capability = typeIdx > 0 ? parts[typeIdx - 1] : null;
|
|
98
|
+
var projectId = typeIdx >= 0 ? parts[typeIdx + 1] : null;
|
|
99
|
+
var failedPolls = 0;
|
|
100
|
+
var demoMode = !capability || !projectId;
|
|
101
|
+
var demoDonations = {
|
|
102
|
+
cause: 'The neighborhood reading garden',
|
|
103
|
+
goalCents: 1000000,
|
|
104
|
+
entries: [
|
|
105
|
+
{ amountCents: 12500, data: { donor: 'Mara', thanked: true } },
|
|
106
|
+
{ amountCents: 50000, data: { donor: 'Canal Street Books', thanked: true } },
|
|
107
|
+
{ amountCents: 7500, data: { donor: 'Anonymous', thanked: false } },
|
|
108
|
+
{ amountCents: 25000, data: { donor: 'The De Vries family', thanked: false } },
|
|
109
|
+
{ amountCents: 100000, data: { donor: 'Veld Works', thanked: true } },
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
113
|
+
|
|
114
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
115
|
+
function fmt(cents) { var abs = Math.abs(cents); return (cents < 0 ? '-' : '') + Math.floor(abs / 100) + '.' + String(abs % 100).padStart(2, '0'); }
|
|
116
|
+
|
|
117
|
+
function render(doc) {
|
|
118
|
+
var entries = Array.isArray(doc.entries) ? doc.entries : [];
|
|
119
|
+
var goal = Number(doc.goalCents);
|
|
120
|
+
if (!isFinite(goal) || goal <= 0) goal = 0;
|
|
121
|
+
var raised = 0;
|
|
122
|
+
entries.forEach(function (e) { raised += e.amountCents; });
|
|
123
|
+
if (doc.cause) document.getElementById('title').textContent = 'Fundraiser: ' + doc.cause;
|
|
124
|
+
document.getElementById('sub').textContent = entries.length + ' gift(s) so far';
|
|
125
|
+
document.getElementById('raised').textContent = fmt(raised);
|
|
126
|
+
document.getElementById('goal').textContent = goal > 0 ? 'of ' + fmt(goal) + ' — ' + Math.min(100, Math.round((raised / goal) * 100)) + ' percent there' : '';
|
|
127
|
+
document.getElementById('bar').style.width = (goal > 0 ? Math.min(100, Math.round((raised / goal) * 100)) : raised > 0 ? 100 : 0) + '%';
|
|
128
|
+
|
|
129
|
+
var recent = entries.slice(-6).reverse();
|
|
130
|
+
if (recent.length) {
|
|
131
|
+
document.getElementById('gifts').innerHTML = recent.map(function (e) {
|
|
132
|
+
return '<li><span class="amt">' + fmt(e.amountCents) + '</span> <span class="who">from ' + esc((e.data && e.data.donor) || 'anonymous') + '</span></li>';
|
|
133
|
+
}).join('');
|
|
134
|
+
}
|
|
135
|
+
var owed = entries.filter(function (e) { return !e.data || e.data.thanked !== true; });
|
|
136
|
+
if (owed.length) {
|
|
137
|
+
document.getElementById('owed').innerHTML = owed.slice(0, 8).map(function (e) {
|
|
138
|
+
return '<li>' + esc((e.data && e.data.donor) || 'anonymous') + ' (' + fmt(e.amountCents) + ')</li>';
|
|
139
|
+
}).join('');
|
|
140
|
+
} else {
|
|
141
|
+
document.getElementById('owed').innerHTML = '<li class="empty">All caught up.</li>';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function poll() {
|
|
146
|
+
if (demoMode) { render(demoDonations); return Promise.resolve(); }
|
|
147
|
+
if (!capability || !projectId) return Promise.resolve();
|
|
148
|
+
return fetch('/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/donations.json', { cache: 'no-store' })
|
|
149
|
+
.then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); })
|
|
150
|
+
.then(function (next) { failedPolls = 0; render(next); })
|
|
151
|
+
.catch(function () {
|
|
152
|
+
failedPolls += 1;
|
|
153
|
+
if (failedPolls === 3 && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
poll().then(function () { setInterval(function () { void poll(); }, 5000); });
|
|
157
|
+
})();
|
|
158
|
+
</script>
|
|
159
|
+
</body>
|
|
160
|
+
</html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
A standing Go table. You place black stones on the board in the **Output**
|
|
2
|
+
tab; your Go-speler plays white — reading the exact position, responding on the
|
|
3
|
+
board, and keeping the conversation {{personality}} and unhurried.
|
|
4
|
+
|
|
5
|
+
Choose a quick 9×9 game, a roomier 13×13 board, or the full 19×19 goban. The
|
|
6
|
+
Go-speler plays as an **{{playStyle}}**: opponents keep their reading private,
|
|
7
|
+
while instructors offer one light teaching observation at a time. The board
|
|
8
|
+
enforces captures, suicide, simple ko, passing, and Chinese-style area scoring.
|
|
9
|
+
Two passes settle the score.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"boardSize": 9,
|
|
4
|
+
"squares": ".................................................................................",
|
|
5
|
+
"turn": "user",
|
|
6
|
+
"status": "playing",
|
|
7
|
+
"komi": 6.5,
|
|
8
|
+
"consecutivePasses": 0,
|
|
9
|
+
"previousPosition": null,
|
|
10
|
+
"legalMoves": [
|
|
11
|
+
"A9",
|
|
12
|
+
"B9",
|
|
13
|
+
"C9",
|
|
14
|
+
"D9",
|
|
15
|
+
"E9",
|
|
16
|
+
"F9",
|
|
17
|
+
"G9",
|
|
18
|
+
"H9",
|
|
19
|
+
"J9",
|
|
20
|
+
"A8",
|
|
21
|
+
"B8",
|
|
22
|
+
"C8",
|
|
23
|
+
"D8",
|
|
24
|
+
"E8",
|
|
25
|
+
"F8",
|
|
26
|
+
"G8",
|
|
27
|
+
"H8",
|
|
28
|
+
"J8",
|
|
29
|
+
"A7",
|
|
30
|
+
"B7",
|
|
31
|
+
"C7",
|
|
32
|
+
"D7",
|
|
33
|
+
"E7",
|
|
34
|
+
"F7",
|
|
35
|
+
"G7",
|
|
36
|
+
"H7",
|
|
37
|
+
"J7",
|
|
38
|
+
"A6",
|
|
39
|
+
"B6",
|
|
40
|
+
"C6",
|
|
41
|
+
"D6",
|
|
42
|
+
"E6",
|
|
43
|
+
"F6",
|
|
44
|
+
"G6",
|
|
45
|
+
"H6",
|
|
46
|
+
"J6",
|
|
47
|
+
"A5",
|
|
48
|
+
"B5",
|
|
49
|
+
"C5",
|
|
50
|
+
"D5",
|
|
51
|
+
"E5",
|
|
52
|
+
"F5",
|
|
53
|
+
"G5",
|
|
54
|
+
"H5",
|
|
55
|
+
"J5",
|
|
56
|
+
"A4",
|
|
57
|
+
"B4",
|
|
58
|
+
"C4",
|
|
59
|
+
"D4",
|
|
60
|
+
"E4",
|
|
61
|
+
"F4",
|
|
62
|
+
"G4",
|
|
63
|
+
"H4",
|
|
64
|
+
"J4",
|
|
65
|
+
"A3",
|
|
66
|
+
"B3",
|
|
67
|
+
"C3",
|
|
68
|
+
"D3",
|
|
69
|
+
"E3",
|
|
70
|
+
"F3",
|
|
71
|
+
"G3",
|
|
72
|
+
"H3",
|
|
73
|
+
"J3",
|
|
74
|
+
"A2",
|
|
75
|
+
"B2",
|
|
76
|
+
"C2",
|
|
77
|
+
"D2",
|
|
78
|
+
"E2",
|
|
79
|
+
"F2",
|
|
80
|
+
"G2",
|
|
81
|
+
"H2",
|
|
82
|
+
"J2",
|
|
83
|
+
"A1",
|
|
84
|
+
"B1",
|
|
85
|
+
"C1",
|
|
86
|
+
"D1",
|
|
87
|
+
"E1",
|
|
88
|
+
"F1",
|
|
89
|
+
"G1",
|
|
90
|
+
"H1",
|
|
91
|
+
"J1",
|
|
92
|
+
"pass"
|
|
93
|
+
],
|
|
94
|
+
"captures": {
|
|
95
|
+
"user": 0,
|
|
96
|
+
"ai": 0
|
|
97
|
+
},
|
|
98
|
+
"score": null,
|
|
99
|
+
"moveLog": [],
|
|
100
|
+
"lastMove": null,
|
|
101
|
+
"lastMoveThought": "",
|
|
102
|
+
"updatedAt": "2026-07-28T00:00:00.000Z"
|
|
103
|
+
}
|