@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
@@ -0,0 +1,1091 @@
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>Checkers</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light dark;
11
+ --bg: #faf7f2;
12
+ --card: #fff;
13
+ --ink: #2b2620;
14
+ --muted: #7a7166;
15
+ --accent: #b5651d;
16
+ --track: #ece5da;
17
+ --sq-light: #f3ead9;
18
+ --sq-dark: #b08968;
19
+ --sq-hint: #7a9e7e;
20
+ --red-a: #c9502c;
21
+ --red-b: #8f3018;
22
+ --black-a: #4a423a;
23
+ --black-b: #211c17;
24
+ --coord-on-light: rgba(43, 38, 32, 0.38);
25
+ --coord-on-dark: rgba(255, 250, 242, 0.5);
26
+ --frame-a: #9a7557;
27
+ --frame-b: #513725;
28
+ --frame-edge: #352217;
29
+ --last-move: rgba(239, 180, 92, 0.36);
30
+ }
31
+ @media (prefers-color-scheme: dark) {
32
+ :root {
33
+ --bg: #1c1a17;
34
+ --card: #262320;
35
+ --ink: #efe9e0;
36
+ --muted: #a59a8c;
37
+ --accent: #e0894a;
38
+ --track: #3a352f;
39
+ --sq-light: #3c362e;
40
+ --sq-dark: #6b4f37;
41
+ --sq-hint: #5d7f62;
42
+ --red-a: #d96a42;
43
+ --red-b: #9c3b1e;
44
+ --black-a: #8c8378;
45
+ --black-b: #3a342d;
46
+ --coord-on-light: rgba(239, 233, 224, 0.38);
47
+ --coord-on-dark: rgba(255, 248, 238, 0.48);
48
+ --frame-a: #74543d;
49
+ --frame-b: #34241a;
50
+ --frame-edge: #1b120d;
51
+ --last-move: rgba(236, 159, 85, 0.32);
52
+ }
53
+ }
54
+ * { box-sizing: border-box; }
55
+ body {
56
+ margin: 0;
57
+ font: 15px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
58
+ background: var(--bg);
59
+ color: var(--ink);
60
+ padding: 1.25rem;
61
+ }
62
+ main { max-width: 720px; margin: 0 auto; }
63
+ header { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
64
+ h1 {
65
+ font-family: Georgia, 'PT Serif', 'Times New Roman', serif;
66
+ font-size: 1.35rem;
67
+ font-weight: 600;
68
+ margin: 0;
69
+ }
70
+ .status { color: var(--muted); font-size: 0.9rem; margin: 0.15rem 0 0.9rem; }
71
+ .status .thinking { color: var(--accent); }
72
+ button.newgame {
73
+ border: 1px solid var(--track);
74
+ background: var(--card);
75
+ color: var(--ink);
76
+ border-radius: 6px;
77
+ padding: 0.3rem 0.7rem;
78
+ font: inherit;
79
+ font-size: 0.85rem;
80
+ cursor: pointer;
81
+ }
82
+ button.newgame:hover { border-color: var(--accent); }
83
+ dialog {
84
+ width: min(430px, calc(100vw - 2rem));
85
+ border: 1px solid var(--track);
86
+ border-radius: 10px;
87
+ padding: 0;
88
+ background: var(--card);
89
+ color: var(--ink);
90
+ box-shadow: 0 18px 55px rgba(0, 0, 0, 0.35);
91
+ }
92
+ dialog::backdrop { background: rgba(20, 17, 14, 0.62); }
93
+ .new-game-form { padding: 1rem; }
94
+ .new-game-form h2 {
95
+ font-family: Georgia, 'PT Serif', 'Times New Roman', serif;
96
+ font-size: 1.1rem;
97
+ margin: 0;
98
+ }
99
+ .dialog-intro { color: var(--muted); margin: 0.2rem 0 0.9rem; font-size: 0.85rem; }
100
+ .rule-fieldset { border: 0; margin: 0; padding: 0; }
101
+ .rule-fieldset legend {
102
+ color: var(--muted);
103
+ font-size: 0.72rem;
104
+ font-weight: 700;
105
+ letter-spacing: 0.05em;
106
+ margin-bottom: 0.35rem;
107
+ text-transform: uppercase;
108
+ }
109
+ .rule-tray {
110
+ display: grid;
111
+ grid-template-columns: 1fr 1fr;
112
+ gap: 0.35rem;
113
+ padding: 0.35rem;
114
+ border: 1px solid var(--track);
115
+ border-radius: 10px;
116
+ background: var(--track);
117
+ }
118
+ .rule-key {
119
+ min-width: 0;
120
+ border: 1px solid transparent;
121
+ border-radius: 6px;
122
+ padding: 0.55rem 0.6rem;
123
+ background: var(--card);
124
+ color: var(--ink);
125
+ cursor: pointer;
126
+ font: inherit;
127
+ text-align: left;
128
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
129
+ }
130
+ .rule-key:hover { border-color: var(--accent); }
131
+ .rule-key[aria-checked='true'] {
132
+ border-color: var(--accent);
133
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
134
+ transform: translateY(1px);
135
+ }
136
+ .rule-key strong { display: block; font-size: 0.88rem; }
137
+ .rule-key span { display: block; color: var(--muted); font-size: 0.75rem; line-height: 1.3; margin-top: 0.1rem; }
138
+ .dialog-actions { display: flex; justify-content: flex-end; gap: 0.45rem; margin-top: 1rem; }
139
+ .dialog-button {
140
+ border: 1px solid var(--accent);
141
+ border-radius: 6px;
142
+ padding: 0.4rem 0.75rem;
143
+ background: var(--accent);
144
+ color: var(--bg);
145
+ cursor: pointer;
146
+ font: inherit;
147
+ font-size: 0.85rem;
148
+ }
149
+ .dialog-button.secondary { border-color: var(--track); background: var(--card); color: var(--ink); }
150
+ .table { display: grid; grid-template-columns: minmax(0, 1fr) 220px; gap: 0.9rem; }
151
+ .boardwrap {
152
+ background: linear-gradient(145deg, var(--frame-a), var(--frame-b));
153
+ border: 1px solid var(--frame-edge);
154
+ border-radius: 14px;
155
+ padding: 0.85rem;
156
+ box-shadow:
157
+ inset 0 1px 0 rgba(255, 255, 255, 0.24),
158
+ inset 0 -4px 8px rgba(0, 0, 0, 0.24),
159
+ 0 10px 24px rgba(31, 21, 15, 0.22);
160
+ }
161
+ .board {
162
+ position: relative;
163
+ display: grid;
164
+ grid-template-columns: repeat(8, 1fr);
165
+ grid-template-rows: repeat(8, minmax(0, 1fr));
166
+ aspect-ratio: 1;
167
+ border: 1px solid var(--frame-edge);
168
+ border-radius: 8px;
169
+ overflow: hidden;
170
+ user-select: none;
171
+ box-shadow:
172
+ inset 0 0 0 1px rgba(255, 255, 255, 0.08),
173
+ 0 5px 12px rgba(0, 0, 0, 0.3);
174
+ }
175
+ .cell { position: relative; display: grid; min-width: 0; min-height: 0; place-items: center; }
176
+ .cell.light { background: var(--sq-light); }
177
+ .cell.dark { background: var(--sq-dark); cursor: default; }
178
+ .cell::before {
179
+ content: '';
180
+ position: absolute;
181
+ inset: 0;
182
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.13), transparent 46%, rgba(0, 0, 0, 0.09));
183
+ pointer-events: none;
184
+ }
185
+ .cell.dark.selectable { cursor: pointer; }
186
+ .cell.last-move { box-shadow: inset 0 0 0 999px var(--last-move); }
187
+ .cell.last-move-from { box-shadow: inset 0 0 0 2px var(--last-move); }
188
+ .cell.dark.selected {
189
+ outline: 3px solid var(--accent);
190
+ outline-offset: -3px;
191
+ box-shadow: inset 0 0 14px rgba(255, 255, 255, 0.16);
192
+ }
193
+ .cell.dark.hint { box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.08); cursor: pointer; }
194
+ .coord {
195
+ position: absolute;
196
+ top: 6%;
197
+ left: 7%;
198
+ z-index: 2;
199
+ font-size: clamp(0.45rem, 1.25vw, 0.65rem);
200
+ font-weight: 600;
201
+ letter-spacing: 0.01em;
202
+ line-height: 1;
203
+ pointer-events: none;
204
+ user-select: none;
205
+ }
206
+ .cell.light .coord { color: var(--coord-on-light); }
207
+ .cell.dark .coord { color: var(--coord-on-dark); }
208
+ .cell.dark.hint::after {
209
+ content: '';
210
+ width: 26%;
211
+ height: 26%;
212
+ z-index: 3;
213
+ border-radius: 50%;
214
+ background: var(--sq-hint);
215
+ box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.16), 0 2px 6px rgba(0, 0, 0, 0.24);
216
+ opacity: 0.92;
217
+ animation: hint-breathe 1.6s ease-in-out infinite;
218
+ }
219
+ .piece {
220
+ position: relative;
221
+ z-index: 1;
222
+ width: 76%;
223
+ height: 76%;
224
+ border: 1px solid rgba(30, 20, 14, 0.45);
225
+ border-radius: 50%;
226
+ display: grid;
227
+ place-items: center;
228
+ font-weight: 700;
229
+ font-size: clamp(0.62rem, 1.7vw, 0.9rem);
230
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
231
+ box-shadow:
232
+ inset 0 4px 5px rgba(255, 255, 255, 0.23),
233
+ inset 0 -6px 8px rgba(0, 0, 0, 0.32),
234
+ 0 5px 7px rgba(0, 0, 0, 0.34);
235
+ transition: transform 150ms ease-out, box-shadow 150ms ease-out, opacity 120ms ease-out;
236
+ }
237
+ .piece::before {
238
+ content: '';
239
+ position: absolute;
240
+ inset: 12%;
241
+ border: 1px solid rgba(255, 255, 255, 0.2);
242
+ border-radius: 50%;
243
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.17);
244
+ pointer-events: none;
245
+ }
246
+ .piece.red { background: radial-gradient(circle at 32% 25%, var(--red-a), var(--red-b) 72%); color: #f8e8de; }
247
+ .piece.black { background: radial-gradient(circle at 32% 25%, var(--black-a), var(--black-b) 72%); color: #d9d2c7; }
248
+ .piece.king::before { inset: 16%; border: 2px double currentColor; opacity: 0.72; }
249
+ .cell.selectable .piece:hover,
250
+ .cell.selected .piece { transform: translateY(-2px) scale(1.025); }
251
+ .piece.draggable { cursor: grab; touch-action: none; }
252
+ .piece.dragging {
253
+ z-index: 9;
254
+ cursor: grabbing;
255
+ opacity: 0.94;
256
+ transition: none;
257
+ filter: drop-shadow(0 10px 7px rgba(0, 0, 0, 0.34));
258
+ }
259
+ .piece.moving-piece {
260
+ position: absolute;
261
+ z-index: 10;
262
+ margin: 0;
263
+ pointer-events: none;
264
+ transition: none;
265
+ }
266
+ .piece.move-source { opacity: 0; }
267
+ @keyframes hint-breathe {
268
+ 0%, 100% { transform: scale(0.9); opacity: 0.7; }
269
+ 50% { transform: scale(1.06); opacity: 1; }
270
+ }
271
+ .aside { display: flex; flex-direction: column; gap: 0.6rem; min-width: 0; }
272
+ .panel {
273
+ background: var(--card);
274
+ border: 1px solid var(--track);
275
+ border-radius: 10px;
276
+ padding: 0.7rem 0.85rem;
277
+ font-size: 0.85rem;
278
+ }
279
+ .panel h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.35rem; }
280
+ .moveThought { font-style: italic; }
281
+ .moveThought .who { color: var(--muted); font-style: normal; font-size: 0.78rem; display: block; margin-bottom: 0.15rem; }
282
+ .log { margin: 0; padding-left: 1.05rem; max-height: 200px; overflow-y: auto; }
283
+ .log li { margin: 0.15rem 0; color: var(--muted); }
284
+ .log li.latest { color: var(--ink); }
285
+ .banner {
286
+ border: 1px solid var(--accent);
287
+ border-radius: 6px;
288
+ padding: 0.45rem 0.7rem;
289
+ font-size: 0.85rem;
290
+ margin-bottom: 0.75rem;
291
+ display: none;
292
+ }
293
+ .banner.show { display: block; }
294
+ @media (prefers-reduced-motion: reduce) {
295
+ .cell.dark.hint::after { animation: none; }
296
+ .piece { transition: none; }
297
+ }
298
+ @media (max-width: 640px) { .table { grid-template-columns: 1fr; } }
299
+ + .demo-banner {
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: space-between;
303
+ gap: 0.75rem;
304
+ margin: 0 0 0.85rem;
305
+ padding: 0.5rem 0.65rem;
306
+ border: 1px solid var(--accent, #8a5a2b);
307
+ border-radius: 6px;
308
+ background: color-mix(in srgb, var(--card, var(--surface, #fffdf8)) 88%, var(--accent, #8a5a2b));
309
+ color: var(--ink, #2b2620);
310
+ font-size: 0.8rem;
311
+ }
312
+ .demo-banner[hidden] { display: none; }
313
+ .demo-banner strong { font-weight: 700; }
314
+ .demo-reset {
315
+ flex: none;
316
+ padding: 0.25rem 0.5rem;
317
+ border: 1px solid var(--accent, #8a5a2b);
318
+ border-radius: 6px;
319
+ background: var(--card, var(--surface, #fffdf8));
320
+ color: var(--ink, #2b2620);
321
+ cursor: pointer;
322
+ font: inherit;
323
+ }
324
+ .demo-reset:hover { border-color: var(--ink, #2b2620); }
325
+ </style>
326
+ </head>
327
+ <body>
328
+ <main>
329
+ + <div class="demo-banner" data-gezel-demo-banner hidden>
330
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
331
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
332
+ </div>
333
+ <div class="banner" id="banner"></div>
334
+ <header>
335
+ <h1>Checkers</h1>
336
+ <button type="button" class="newgame" id="newgame">New game</button>
337
+ </header>
338
+ <p class="status" id="status">Setting up the board…</p>
339
+ <div class="table">
340
+ <div class="boardwrap"><div class="board" id="board"></div></div>
341
+ <div class="aside">
342
+ <div class="panel moveThought" id="moveThought-panel" hidden>
343
+ <span class="who">Damspeler says</span>
344
+ <span id="moveThought"></span>
345
+ </div>
346
+ <div class="panel">
347
+ <h2>Moves</h2>
348
+ <ol class="log" id="log"><li>No moves yet — red opens.</li></ol>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ <dialog id="new-game-dialog" aria-labelledby="new-game-title">
353
+ <form class="new-game-form" id="new-game-form">
354
+ <h2 id="new-game-title">New game</h2>
355
+ <p class="dialog-intro">Choose the capture rule for this game.</p>
356
+ <fieldset class="rule-fieldset">
357
+ <legend>Captures</legend>
358
+ <div class="rule-tray" role="radiogroup" aria-label="Capture rule">
359
+ <button type="button" class="rule-key" role="radio" aria-checked="true" data-force-captures="true">
360
+ <strong>Required</strong>
361
+ <span>Standard rules. Take a capture when one is available.</span>
362
+ </button>
363
+ <button type="button" class="rule-key" role="radio" aria-checked="false" data-force-captures="false">
364
+ <strong>Optional</strong>
365
+ <span>Ordinary moves stay available when you can capture.</span>
366
+ </button>
367
+ </div>
368
+ </fieldset>
369
+ <div class="dialog-actions">
370
+ <button type="button" class="dialog-button secondary" id="cancel-new-game">Cancel</button>
371
+ <button type="button" class="dialog-button" id="start-new-game">Start game</button>
372
+ </div>
373
+ </form>
374
+ </dialog>
375
+ </main>
376
+ <script>
377
+ (function () {
378
+ 'use strict';
379
+
380
+ // ── gezelPage helper: postMessage bridge to the first-party parent.
381
+ // The sandboxed page's only write path — the parent validates the
382
+ // tool against the type manifest and relays to the page-invoke
383
+ // route. Not embedded (opened in a browser tab) → invoke rejects
384
+ // and the board goes read-only.
385
+ var gezelPage = (function () {
386
+ var seq = 0;
387
+ var pending = {};
388
+ window.addEventListener('message', function (event) {
389
+ var d = event.data;
390
+ if (!d || d.__gezelPageResult !== true || typeof d.id !== 'string') return;
391
+ var entry = pending[d.id];
392
+ if (!entry) return;
393
+ delete pending[d.id];
394
+ clearTimeout(entry.timer);
395
+ if (d.ok) entry.resolve(d);
396
+ else entry.reject(new Error(d.error || 'invoke failed'));
397
+ });
398
+ return {
399
+ embedded: function () {
400
+ return demoMode || Boolean(capability && projectId);
401
+ },
402
+ invoke: function (tool, input) {
403
+ return new Promise(function (resolve, reject) {
404
+ if (demoMode) {
405
+ try { resolve(demoInvoke(tool, input)); } catch (error) { reject(error); }
406
+ return;
407
+ }
408
+ if (window.parent === window) {
409
+ reject(new Error('not embedded in Gezel'));
410
+ return;
411
+ }
412
+ seq += 1;
413
+ var id = 'pi-' + Date.now() + '-' + seq;
414
+ pending[id] = {
415
+ resolve: resolve,
416
+ reject: reject,
417
+ timer: setTimeout(function () {
418
+ delete pending[id];
419
+ reject(new Error('invoke timed out'));
420
+ }, 45000),
421
+ };
422
+ window.parent.postMessage(
423
+ { __gezelPageInvoke: true, id: id, tool: tool, input: input || {} },
424
+ '*',
425
+ );
426
+ });
427
+ },
428
+ refresh: function () {
429
+ if (!demoMode && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
430
+ },
431
+ };
432
+ })();
433
+
434
+ // ── data plumbing: capability + projectId from our own path; state
435
+ // via capability-scoped polling of game.json (declared in
436
+ // pages.reads).
437
+ var parts = location.pathname.split('/').filter(Boolean);
438
+ var typeIdx = parts.indexOf('type');
439
+ var capability = typeIdx > 0 ? parts[typeIdx - 1] : null;
440
+ var projectId = typeIdx >= 0 ? parts[typeIdx + 1] : null;
441
+ var FILES = 'abcdefgh';
442
+ var demoMode = !capability || !projectId;
443
+
444
+ var state = null;
445
+ var selected = null;
446
+ var moveOrigin = null;
447
+ var movePath = [];
448
+ var previewSquares = null;
449
+ var animationSquares = null;
450
+ var busy = false;
451
+ var animating = false;
452
+ var suppressClickUntil = 0;
453
+ var newGameForceCaptures = true;
454
+ var pollTimer = null;
455
+ var failedPolls = 0;
456
+ var demoGame = null;
457
+ if (demoMode) {
458
+ document.querySelector('[data-gezel-demo-banner]').hidden = false;
459
+ demoGame = demoNewGame(true);
460
+ }
461
+
462
+ function sqName(x, y) { return FILES[x] + String(8 - y); }
463
+ function parseSquare(name) {
464
+ return { x: FILES.indexOf(name[0].toLowerCase()), y: 8 - Number(name[1]) };
465
+ }
466
+ function withPiece(squares, x, y, ch) {
467
+ var index = y * 8 + x;
468
+ return squares.slice(0, index) + ch + squares.slice(index + 1);
469
+ }
470
+ function applyPreviewHop(squares, from, to) {
471
+ var start = parseSquare(from);
472
+ var end = parseSquare(to);
473
+ var ch = squares[start.y * 8 + start.x];
474
+ var next = withPiece(squares, start.x, start.y, '.');
475
+ if (Math.abs(start.x - end.x) === 2) {
476
+ next = withPiece(next, (start.x + end.x) / 2, (start.y + end.y) / 2, '.');
477
+ }
478
+ if (ch === 'r' && end.y === 0) ch = 'R';
479
+ if (ch === 'b' && end.y === 7) ch = 'B';
480
+ return withPiece(next, end.x, end.y, ch);
481
+ }
482
+ function demoSide(ch) {
483
+ if (ch === 'r' || ch === 'R') return 'user';
484
+ if (ch === 'b' || ch === 'B') return 'ai';
485
+ return null;
486
+ }
487
+ function demoDirections(ch) {
488
+ if (ch === 'R' || ch === 'B') return [[-1,-1],[1,-1],[-1,1],[1,1]];
489
+ return ch === 'r' ? [[-1,-1],[1,-1]] : [[-1,1],[1,1]];
490
+ }
491
+ function demoCaptures(squares, x, y, ch, origin, path, captures, out) {
492
+ var found = false;
493
+ demoDirections(ch).forEach(function (d) {
494
+ var mx = x + d[0]; var my = y + d[1];
495
+ var tx = x + d[0] * 2; var ty = y + d[1] * 2;
496
+ if (tx < 0 || tx > 7 || ty < 0 || ty > 7 || mx < 0 || mx > 7 || my < 0 || my > 7) return;
497
+ var middle = squares[my * 8 + mx];
498
+ if (demoSide(middle) === null || demoSide(middle) === demoSide(ch) || squares[ty * 8 + tx] !== '.') return;
499
+ found = true;
500
+ var next = withPiece(withPiece(withPiece(squares, x, y, '.'), mx, my, '.'), tx, ty, ch);
501
+ demoCaptures(next, tx, ty, ch, origin, path.concat(sqName(tx, ty)), captures.concat(sqName(mx, my)), out);
502
+ });
503
+ if (!found && captures.length) out.push({ from: origin, to: path[path.length - 1], path: path, captures: captures });
504
+ }
505
+ function demoMoves(game) {
506
+ var captures = []; var simple = [];
507
+ for (var y = 0; y < 8; y += 1) {
508
+ for (var x = 0; x < 8; x += 1) {
509
+ var ch = game.squares[y * 8 + x];
510
+ if (demoSide(ch) !== game.turn) continue;
511
+ var from = sqName(x, y);
512
+ demoCaptures(game.squares, x, y, ch, from, [from], [], captures);
513
+ demoDirections(ch).forEach(function (d) {
514
+ var tx = x + d[0]; var ty = y + d[1];
515
+ if (tx >= 0 && tx < 8 && ty >= 0 && ty < 8 && game.squares[ty * 8 + tx] === '.') {
516
+ simple.push({ from: from, to: sqName(tx, ty), path: [from, sqName(tx, ty)], captures: [] });
517
+ }
518
+ });
519
+ }
520
+ }
521
+ return captures.length && game.forceCaptures !== false ? captures : captures.concat(simple);
522
+ }
523
+ function demoNewGame(forceCaptures) {
524
+ var game = {
525
+ version: 1,
526
+ squares: '.b.b.b.bb.b.b.b..b.b.b.b................r.r.r.r..r.r.r.rr.r.r.r.',
527
+ turn: 'user', status: 'playing', userColor: 'red', forceCaptures: forceCaptures !== false,
528
+ legalMoves: [], lastMove: null, lastMoveThought: '', moveLog: [],
529
+ };
530
+ demoGame = game;
531
+ game.legalMoves = demoMoves(game);
532
+ return game;
533
+ }
534
+ function demoInvoke(tool, input) {
535
+ if (tool === 'new_game') { demoGame = demoNewGame(input.forceCaptures); return { ok: true }; }
536
+ if (tool !== 'user_move') throw new Error('Unknown demo action: ' + tool);
537
+ var move = demoGame.legalMoves.find(function (m) { return m.from === input.from && m.to === input.to; });
538
+ if (!move) throw new Error('That move is not available in the hot-seat demo.');
539
+ var next = demoGame.squares;
540
+ for (var i = 1; i < move.path.length; i += 1) next = applyPreviewHop(next, move.path[i - 1], move.path[i]);
541
+ demoGame.squares = next;
542
+ demoGame.lastMove = { by: demoGame.turn, from: move.from, to: move.to };
543
+ demoGame.moveLog.push({ by: demoGame.turn, from: move.from, to: move.to, captures: move.captures.slice() });
544
+ demoGame.turn = demoGame.turn === 'user' ? 'ai' : 'user';
545
+ demoGame.legalMoves = demoMoves(demoGame);
546
+ if (demoGame.legalMoves.length === 0) demoGame.status = demoGame.turn === 'user' ? 'ai_won' : 'user_won';
547
+ return { ok: true };
548
+ }
549
+ function squaresForRender() {
550
+ return previewSquares || animationSquares || (state && state.squares) || '.'.repeat(64);
551
+ }
552
+ function resetMovePreview() {
553
+ selected = null;
554
+ moveOrigin = null;
555
+ movePath = [];
556
+ previewSquares = null;
557
+ }
558
+ function beginMove(name) {
559
+ selected = name;
560
+ moveOrigin = name;
561
+ movePath = [name];
562
+ previewSquares = null;
563
+ }
564
+ function matchingMoves() {
565
+ if (!state || !moveOrigin || movePath.length === 0) return [];
566
+ return state.legalMoves.filter(function (move) {
567
+ if (move.from !== moveOrigin || move.path.length < movePath.length) return false;
568
+ for (var i = 0; i < movePath.length; i += 1) {
569
+ if (move.path[i] !== movePath[i]) return false;
570
+ }
571
+ return true;
572
+ });
573
+ }
574
+ function currentHints() {
575
+ var hints = {};
576
+ matchingMoves().forEach(function (move) {
577
+ var landing = move.path[movePath.length];
578
+ if (!landing) return;
579
+ if (!hints[landing]) hints[landing] = [];
580
+ hints[landing].push(move);
581
+ });
582
+ return hints;
583
+ }
584
+ function originMap() {
585
+ var origins = {};
586
+ if (!state) return origins;
587
+ if (movePath.length > 1 && selected) {
588
+ origins[selected] = true;
589
+ return origins;
590
+ }
591
+ state.legalMoves.forEach(function (move) { origins[move.from] = true; });
592
+ return origins;
593
+ }
594
+ function esc(s) {
595
+ return String(s).replace(/[&<>"]/g, function (c) {
596
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
597
+ });
598
+ }
599
+
600
+ function setBanner(text) {
601
+ var el = document.getElementById('banner');
602
+ el.textContent = text || '';
603
+ el.className = text ? 'banner show' : 'banner';
604
+ }
605
+
606
+ function statusLine() {
607
+ if (!state) return 'Setting up the board…';
608
+ if (demoMode && state.status === 'playing') {
609
+ var demoCaps = state.legalMoves.some(function (m) { return m.captures.length > 0; });
610
+ return 'Hot seat — ' + (state.turn === 'user' ? 'red' : 'black') + ' to move' + (demoCaps ? ' (capture available).' : '.');
611
+ }
612
+ if (demoMode && state.status === 'user_won') return 'Red wins the hot-seat demo.';
613
+ if (demoMode && state.status === 'ai_won') return 'Black wins the hot-seat demo.';
614
+ if (state.status === 'user_won') return 'You won! A gracious rematch awaits.';
615
+ if (state.status === 'ai_won') return 'The Damspeler takes this one — rematch?';
616
+ if (animating) return 'Piece in motion…';
617
+ if (busy) return 'Sending your move…';
618
+ if (movePath.length > 1) return 'Keep jumping — choose the next landing square.';
619
+ if (state.turn === 'ai') return 'Damspeler is thinking…';
620
+ var caps = state.legalMoves.some(function (m) { return m.captures.length > 0; });
621
+ if (caps && state.forceCaptures !== false) {
622
+ return 'Your move — a capture is available (captures are forced).';
623
+ }
624
+ if (caps) return 'Your move — a capture is available, but optional.';
625
+ return state.forceCaptures === false ? 'Your move — captures are optional.' : 'Your move.';
626
+ }
627
+
628
+ function render() {
629
+ document.getElementById('status').innerHTML =
630
+ state && !demoMode && state.turn === 'ai' && state.status === 'playing'
631
+ ? '<span class="thinking">' + esc(statusLine()) + '</span>'
632
+ : esc(statusLine());
633
+
634
+ var moveThoughtPanel = document.getElementById('moveThought-panel');
635
+ if (state && state.lastMoveThought) {
636
+ moveThoughtPanel.hidden = false;
637
+ document.getElementById('moveThought').textContent = state.lastMoveThought;
638
+ } else {
639
+ moveThoughtPanel.hidden = true;
640
+ }
641
+
642
+ var log = document.getElementById('log');
643
+ if (state && state.moveLog.length) {
644
+ log.innerHTML = state.moveLog
645
+ .slice(-8)
646
+ .map(function (m, i, arr) {
647
+ var text = (m.by === 'user' ? 'Red ' : 'Black ') + m.from +
648
+ (m.captures.length ? 'x' : '-') + m.to;
649
+ return '<li' + (i === arr.length - 1 ? ' class="latest"' : '') + '>' + esc(text) + '</li>';
650
+ })
651
+ .join('');
652
+ }
653
+
654
+ var board = document.getElementById('board');
655
+ board.innerHTML = '';
656
+ if (!state) return;
657
+ var myTurn = (demoMode || state.turn === 'user') && state.status === 'playing' && !busy && !animating;
658
+ var origins = myTurn ? originMap() : {};
659
+ var hints = myTurn && selected ? currentHints() : {};
660
+ var shownSquares = squaresForRender();
661
+ for (var y = 0; y < 8; y += 1) {
662
+ for (var x = 0; x < 8; x += 1) {
663
+ (function (x, y) {
664
+ var name = sqName(x, y);
665
+ var ch = shownSquares[y * 8 + x];
666
+ var dark = (x + y) % 2 === 1;
667
+ var cell = document.createElement('div');
668
+ cell.dataset.square = name;
669
+ var cls = 'cell ' + (dark ? 'dark' : 'light');
670
+ if (name === selected) cls += ' selected';
671
+ if (hints[name]) cls += ' hint';
672
+ if (myTurn && origins[name]) cls += ' selectable';
673
+ if (!previewSquares && !animationSquares && state.lastMove) {
674
+ if (name === state.lastMove.to) cls += ' last-move';
675
+ if (name === state.lastMove.from) cls += ' last-move-from';
676
+ }
677
+ cell.className = cls;
678
+ var coord = document.createElement('span');
679
+ coord.className = 'coord';
680
+ coord.textContent = name.toUpperCase();
681
+ coord.setAttribute('aria-hidden', 'true');
682
+ cell.appendChild(coord);
683
+ if (ch !== '.') {
684
+ var piece = document.createElement('div');
685
+ var red = ch === 'r' || ch === 'R';
686
+ piece.className = 'piece ' + (red ? 'red' : 'black');
687
+ if (ch === 'R' || ch === 'B') {
688
+ piece.className += ' king';
689
+ piece.textContent = 'K';
690
+ }
691
+ if (myTurn && origins[name]) {
692
+ piece.className += ' draggable';
693
+ enablePieceDrag(piece, name);
694
+ }
695
+ cell.appendChild(piece);
696
+ }
697
+ cell.addEventListener('click', function () {
698
+ if (!myTurn || Date.now() < suppressClickUntil) return;
699
+ if (hints[name]) {
700
+ playLanding(name);
701
+ } else if (origins[name]) {
702
+ if (selected === name && movePath.length <= 1) resetMovePreview();
703
+ else beginMove(name);
704
+ render();
705
+ } else if (selected && movePath.length <= 1) {
706
+ resetMovePreview();
707
+ render();
708
+ }
709
+ });
710
+ board.appendChild(cell);
711
+ })(x, y);
712
+ }
713
+ }
714
+ }
715
+
716
+ function prefersReducedMotion() {
717
+ return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
718
+ }
719
+
720
+ function capturedSquare(from, to) {
721
+ var start = parseSquare(from);
722
+ var end = parseSquare(to);
723
+ if (Math.abs(start.x - end.x) !== 2) return null;
724
+ return sqName((start.x + end.x) / 2, (start.y + end.y) / 2);
725
+ }
726
+
727
+ function animateHop(from, to) {
728
+ if (prefersReducedMotion()) return Promise.resolve();
729
+ var board = document.getElementById('board');
730
+ var sourceCell = board.querySelector('[data-square="' + from + '"]');
731
+ var targetCell = board.querySelector('[data-square="' + to + '"]');
732
+ var sourcePiece = sourceCell && sourceCell.querySelector('.piece');
733
+ if (!sourcePiece || !targetCell) return Promise.resolve();
734
+
735
+ var boardRect = board.getBoundingClientRect();
736
+ var sourceRect = sourcePiece.getBoundingClientRect();
737
+ var targetRect = targetCell.getBoundingClientRect();
738
+ var clone = sourcePiece.cloneNode(true);
739
+ clone.classList.remove('draggable', 'dragging', 'move-source');
740
+ clone.classList.add('moving-piece');
741
+ clone.style.left = sourceRect.left - boardRect.left + 'px';
742
+ clone.style.top = sourceRect.top - boardRect.top + 'px';
743
+ clone.style.width = sourceRect.width + 'px';
744
+ clone.style.height = sourceRect.height + 'px';
745
+ board.appendChild(clone);
746
+ sourcePiece.classList.add('move-source');
747
+
748
+ var dx = targetRect.left + targetRect.width / 2 - (sourceRect.left + sourceRect.width / 2);
749
+ var dy = targetRect.top + targetRect.height / 2 - (sourceRect.top + sourceRect.height / 2);
750
+ var turn = dx >= 0 ? 5 : -5;
751
+ var motion = clone.animate(
752
+ [
753
+ { transform: 'translate(0, 0) rotate(0deg) scale(1)' },
754
+ { transform: 'translate(' + dx * 0.55 + 'px, ' + dy * 0.55 + 'px) rotate(' + turn + 'deg) scale(1.1)', offset: 0.55 },
755
+ { transform: 'translate(' + dx + 'px, ' + dy + 'px) rotate(0deg) scale(1)' },
756
+ ],
757
+ { duration: 320, easing: 'cubic-bezier(.2,.76,.25,1)' },
758
+ );
759
+
760
+ var capture = capturedSquare(from, to);
761
+ var capturedPiece = capture && board.querySelector('[data-square="' + capture + '"] .piece');
762
+ var captureFinished = Promise.resolve();
763
+ if (capturedPiece) {
764
+ var captureMotion = capturedPiece.animate(
765
+ [
766
+ { transform: 'scale(1)', opacity: 1 },
767
+ { transform: 'scale(0.72) rotate(7deg)', opacity: 0 },
768
+ ],
769
+ { duration: 190, delay: 100, easing: 'ease-in' },
770
+ );
771
+ captureFinished = captureMotion.finished.catch(function () {});
772
+ }
773
+
774
+ return Promise.all([motion.finished.catch(function () {}), captureFinished]).then(function () {
775
+ clone.remove();
776
+ sourcePiece.classList.remove('move-source');
777
+ });
778
+ }
779
+
780
+ function paintCurrentMove() {
781
+ var board = document.getElementById('board');
782
+ Array.prototype.forEach.call(board.querySelectorAll('.cell.selected, .cell.hint'), function (cell) {
783
+ cell.classList.remove('selected', 'hint');
784
+ });
785
+ if (selected) {
786
+ var selectedCell = board.querySelector('[data-square="' + selected + '"]');
787
+ if (selectedCell) selectedCell.classList.add('selected');
788
+ }
789
+ var hints = currentHints();
790
+ Object.keys(hints).forEach(function (name) {
791
+ var cell = board.querySelector('[data-square="' + name + '"]');
792
+ if (cell) cell.classList.add('hint');
793
+ });
794
+ }
795
+
796
+ function landingAtPoint(piece, x, y) {
797
+ var oldPointerEvents = piece.style.pointerEvents;
798
+ piece.style.pointerEvents = 'none';
799
+ var hit = document.elementFromPoint(x, y);
800
+ piece.style.pointerEvents = oldPointerEvents;
801
+ var cell = hit && hit.closest ? hit.closest('.cell') : null;
802
+ var name = cell && cell.dataset.square;
803
+ return name && currentHints()[name] ? name : null;
804
+ }
805
+
806
+ function nearestThrownLanding(x, y, vx, vy) {
807
+ var speed = Math.sqrt(vx * vx + vy * vy);
808
+ if (speed < 0.18) return null;
809
+ var travel = Math.min(100, speed * 105);
810
+ var px = x + (vx / speed) * travel;
811
+ var py = y + (vy / speed) * travel;
812
+ var best = null;
813
+ var bestDistance = Infinity;
814
+ Object.keys(currentHints()).forEach(function (name) {
815
+ var cell = document.querySelector('[data-square="' + name + '"]');
816
+ if (!cell) return;
817
+ var rect = cell.getBoundingClientRect();
818
+ var cx = rect.left + rect.width / 2;
819
+ var cy = rect.top + rect.height / 2;
820
+ var distance = Math.sqrt((cx - px) * (cx - px) + (cy - py) * (cy - py));
821
+ if (distance < bestDistance && distance < rect.width * 0.9) {
822
+ best = name;
823
+ bestDistance = distance;
824
+ }
825
+ });
826
+ return best;
827
+ }
828
+
829
+ function enablePieceDrag(piece, name) {
830
+ piece.addEventListener('pointerdown', function (event) {
831
+ if (event.button !== 0 || busy || animating) return;
832
+ var start = { x: event.clientX, y: event.clientY, at: event.timeStamp };
833
+ var previous = start;
834
+ var last = start;
835
+ var moved = false;
836
+ piece.setPointerCapture(event.pointerId);
837
+
838
+ function onMove(moveEvent) {
839
+ var dx = moveEvent.clientX - start.x;
840
+ var dy = moveEvent.clientY - start.y;
841
+ if (!moved && Math.sqrt(dx * dx + dy * dy) < 4) return;
842
+ if (!moved) {
843
+ moved = true;
844
+ if (movePath.length <= 1 && moveOrigin !== name) beginMove(name);
845
+ paintCurrentMove();
846
+ piece.classList.add('dragging');
847
+ }
848
+ previous = last;
849
+ last = { x: moveEvent.clientX, y: moveEvent.clientY, at: moveEvent.timeStamp };
850
+ piece.style.transform =
851
+ 'translate(' + dx + 'px, ' + dy + 'px) rotate(' + dx * 0.035 + 'deg) scale(1.06)';
852
+ }
853
+
854
+ function onEnd(endEvent) {
855
+ piece.removeEventListener('pointermove', onMove);
856
+ piece.removeEventListener('pointerup', onEnd);
857
+ piece.removeEventListener('pointercancel', onEnd);
858
+ try { piece.releasePointerCapture(event.pointerId); } catch (_) {}
859
+ if (!moved) return;
860
+
861
+ // Swallow only the synthetic click emitted with this drag;
862
+ // the next deliberate hop should be available immediately.
863
+ suppressClickUntil = Date.now() + 80;
864
+ var elapsed = Math.max(1, last.at - previous.at);
865
+ var vx = (last.x - previous.x) / elapsed;
866
+ var vy = (last.y - previous.y) / elapsed;
867
+ var landing = landingAtPoint(piece, endEvent.clientX, endEvent.clientY) ||
868
+ nearestThrownLanding(endEvent.clientX, endEvent.clientY, vx, vy);
869
+ var currentTransform = piece.style.transform;
870
+ piece.classList.remove('dragging');
871
+ piece.style.transform = '';
872
+ if (landing) {
873
+ playLanding(landing);
874
+ return;
875
+ }
876
+
877
+ var rebound = piece.animate(
878
+ [
879
+ { transform: currentTransform },
880
+ { transform: 'translate(0, 0) rotate(0deg) scale(1)' },
881
+ ],
882
+ { duration: 180, easing: 'cubic-bezier(.2,.8,.3,1)' },
883
+ );
884
+ rebound.finished.catch(function () {}).then(render);
885
+ }
886
+
887
+ piece.addEventListener('pointermove', onMove);
888
+ piece.addEventListener('pointerup', onEnd);
889
+ piece.addEventListener('pointercancel', onEnd);
890
+ });
891
+ }
892
+
893
+ function playLanding(landing) {
894
+ var candidates = currentHints()[landing];
895
+ if (!candidates || !selected || busy || animating) return;
896
+ var from = selected;
897
+ var before = squaresForRender();
898
+ animating = true;
899
+ document.getElementById('status').textContent = statusLine();
900
+ animateHop(from, landing).then(function () {
901
+ previewSquares = applyPreviewHop(before, from, landing);
902
+ movePath.push(landing);
903
+ selected = landing;
904
+ animating = false;
905
+ var remaining = matchingMoves();
906
+ var hasNext = remaining.some(function (move) { return move.path.length > movePath.length; });
907
+ if (hasNext) {
908
+ render();
909
+ return;
910
+ }
911
+ var complete = remaining.find(function (move) { return move.path.length === movePath.length; });
912
+ if (!complete) {
913
+ resetMovePreview();
914
+ setBanner('That jump sequence is no longer available. Please choose again.');
915
+ render();
916
+ return;
917
+ }
918
+ submitMove(complete);
919
+ });
920
+ }
921
+
922
+ function submitMove(move) {
923
+ busy = true;
924
+ render();
925
+ gezelPage
926
+ .invoke('user_move', { from: move.from, to: move.to })
927
+ .then(function (res) {
928
+ busy = false;
929
+ if (res.reaction && res.reaction.delivered === false && res.reaction.reason === 'engagement-off') {
930
+ setBanner('Move played — but the AI is off in Settings, so the Damspeler cannot reply.');
931
+ }
932
+ return poll();
933
+ })
934
+ .catch(function (err) {
935
+ busy = false;
936
+ resetMovePreview();
937
+ setBanner(String(err && err.message ? err.message : err));
938
+ render();
939
+ });
940
+ }
941
+
942
+ function animateServerMove(sequence, next) {
943
+ animating = true;
944
+ resetMovePreview();
945
+ animationSquares = state.squares;
946
+ render();
947
+ var index = 1;
948
+
949
+ function nextHop() {
950
+ if (index >= sequence.path.length) return Promise.resolve();
951
+ var from = sequence.path[index - 1];
952
+ var to = sequence.path[index];
953
+ return animateHop(from, to).then(function () {
954
+ animationSquares = applyPreviewHop(animationSquares, from, to);
955
+ index += 1;
956
+ render();
957
+ return nextHop();
958
+ });
959
+ }
960
+
961
+ return nextHop().then(function () {
962
+ animationSquares = null;
963
+ state = next;
964
+ animating = false;
965
+ render();
966
+ });
967
+ }
968
+
969
+ function syncCaptureChoice() {
970
+ Array.prototype.forEach.call(document.querySelectorAll('.rule-key'), function (button) {
971
+ button.setAttribute(
972
+ 'aria-checked',
973
+ String((button.dataset.forceCaptures === 'true') === newGameForceCaptures),
974
+ );
975
+ });
976
+ }
977
+
978
+ function openNewGameDialog() {
979
+ if (!gezelPage.embedded() || busy || animating) return;
980
+ newGameForceCaptures = !state || state.forceCaptures !== false;
981
+ syncCaptureChoice();
982
+ document.getElementById('new-game-dialog').showModal();
983
+ }
984
+
985
+ function newGame(forceCaptures) {
986
+ if (!gezelPage.embedded()) return;
987
+ resetMovePreview();
988
+ animationSquares = null;
989
+ busy = true;
990
+ render();
991
+ gezelPage
992
+ .invoke('new_game', { forceCaptures: forceCaptures })
993
+ .then(function () {
994
+ busy = false;
995
+ setBanner('');
996
+ return poll();
997
+ })
998
+ .catch(function (err) {
999
+ busy = false;
1000
+ setBanner(String(err && err.message ? err.message : err));
1001
+ render();
1002
+ });
1003
+ }
1004
+
1005
+ function transitionState(next) {
1006
+ if (!state) {
1007
+ state = next;
1008
+ resetMovePreview();
1009
+ render();
1010
+ return Promise.resolve();
1011
+ }
1012
+
1013
+ var addedMove = next.moveLog.length > state.moveLog.length;
1014
+ if (!addedMove) {
1015
+ state = next;
1016
+ render();
1017
+ return Promise.resolve();
1018
+ }
1019
+
1020
+ if (previewSquares) {
1021
+ state = next;
1022
+ resetMovePreview();
1023
+ render();
1024
+ return Promise.resolve();
1025
+ }
1026
+
1027
+ var last = next.lastMove;
1028
+ if (last && last.by === 'ai') {
1029
+ var sequence = state.legalMoves.find(function (move) {
1030
+ return move.from === last.from && move.to === last.to;
1031
+ });
1032
+ if (sequence) return animateServerMove(sequence, next);
1033
+ }
1034
+
1035
+ state = next;
1036
+ resetMovePreview();
1037
+ render();
1038
+ return Promise.resolve();
1039
+ }
1040
+
1041
+ function poll() {
1042
+ if (demoMode) return transitionState(demoGame);
1043
+ if (!capability || !projectId) return Promise.resolve();
1044
+ return fetch(
1045
+ '/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/game.json',
1046
+ { cache: 'no-store' },
1047
+ )
1048
+ .then(function (r) {
1049
+ if (!r.ok) throw new Error('poll ' + r.status);
1050
+ return r.json();
1051
+ })
1052
+ .then(function (next) {
1053
+ failedPolls = 0;
1054
+ return transitionState(next);
1055
+ })
1056
+ .catch(function () {
1057
+ failedPolls += 1;
1058
+ // The 2 h absolute capability expiry surfaces as failing
1059
+ // polls — ask the parent to re-mint and reload the frame.
1060
+ if (failedPolls === 3) gezelPage.refresh();
1061
+ });
1062
+ }
1063
+
1064
+ function schedule() {
1065
+ if (pollTimer) clearTimeout(pollTimer);
1066
+ if (demoMode) return;
1067
+ var wait = state && state.turn === 'ai' && state.status === 'playing' ? 1500 : 5000;
1068
+ pollTimer = setTimeout(function () {
1069
+ poll().then(schedule, schedule);
1070
+ }, wait);
1071
+ }
1072
+
1073
+ Array.prototype.forEach.call(document.querySelectorAll('.rule-key'), function (button) {
1074
+ button.addEventListener('click', function () {
1075
+ newGameForceCaptures = button.dataset.forceCaptures === 'true';
1076
+ syncCaptureChoice();
1077
+ });
1078
+ });
1079
+ document.getElementById('newgame').addEventListener('click', openNewGameDialog);
1080
+ document.getElementById('cancel-new-game').addEventListener('click', function () {
1081
+ document.getElementById('new-game-dialog').close();
1082
+ });
1083
+ document.getElementById('start-new-game').addEventListener('click', function () {
1084
+ document.getElementById('new-game-dialog').close();
1085
+ newGame(newGameForceCaptures);
1086
+ });
1087
+ poll().then(schedule, schedule);
1088
+ })();
1089
+ </script>
1090
+ </body>
1091
+ </html>