Fu01978 commited on
Commit
71dec69
·
verified ·
1 Parent(s): 150a953

Upload index (14).html

Browse files
Files changed (1) hide show
  1. templates/index (14).html +1200 -0
templates/index (14).html ADDED
@@ -0,0 +1,1200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.0">
6
+ <title>Chess Analysis</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&family=Crimson+Pro:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
+ <style>
11
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
12
+
13
+ :root {
14
+ --bg: #07070e;
15
+ --bg-card: #0f0f1d;
16
+ --bg-elevated: #16162a;
17
+ --bg-deepest: #050509;
18
+ --gold: #c9a84c;
19
+ --gold-light: #e8c97a;
20
+ --gold-dim: #7a6430;
21
+ --gold-glow: rgba(201,168,76,0.15);
22
+ --text: #ede8dc;
23
+ --text-sub: #9a9282;
24
+ --text-muted: #5a5650;
25
+ --border: #1e1e36;
26
+ --border-bright:#2e2e4e;
27
+ /* eval-bar 12px + gap 6px + rank-labels 20px + gap 8px + body-padding 20px = 66px */
28
+ --sq: min(66px, calc((100vw - 80px) / 8));
29
+ }
30
+
31
+ html, body {
32
+ height: 100%;
33
+ font-family: 'Crimson Pro', Georgia, serif;
34
+ background: var(--bg);
35
+ color: var(--text);
36
+ overflow: hidden;
37
+ }
38
+
39
+ body::before {
40
+ content: '';
41
+ position: fixed; inset: 0; z-index: 0;
42
+ background:
43
+ radial-gradient(ellipse 80% 60% at 15% 50%, rgba(20,18,60,0.5) 0%, transparent 70%),
44
+ radial-gradient(ellipse 60% 80% at 85% 10%, rgba(10,25,60,0.4) 0%, transparent 60%);
45
+ pointer-events: none;
46
+ }
47
+
48
+ /* ── Screen system ───────────────────────────────────────────────────── */
49
+ .screen {
50
+ position: fixed; inset: 0; z-index: 1;
51
+ display: flex; flex-direction: column; align-items: center;
52
+ overflow-y: auto;
53
+ -webkit-overflow-scrolling: touch;
54
+ opacity: 0;
55
+ pointer-events: none;
56
+ transition: opacity 0.35s ease;
57
+ }
58
+ .screen.active {
59
+ opacity: 1;
60
+ pointer-events: all;
61
+ }
62
+
63
+ /* ── Shared ──────────────────────────────────────────────────────────── */
64
+ .mono { font-family: 'JetBrains Mono', 'Consolas', monospace; }
65
+
66
+ .btn {
67
+ display: inline-flex; align-items: center; gap: 8px;
68
+ border: none; cursor: pointer;
69
+ font-family: 'Crimson Pro', serif;
70
+ font-weight: 500;
71
+ letter-spacing: 0.05em;
72
+ transition: all 0.2s;
73
+ }
74
+ .btn:disabled { opacity: 0.3; cursor: default; }
75
+
76
+ .btn-gold {
77
+ background: linear-gradient(135deg, #b8922a 0%, #c9a84c 40%, #e8c97a 100%);
78
+ color: #07070e;
79
+ font-size: 17px;
80
+ padding: 13px 40px;
81
+ border-radius: 3px;
82
+ box-shadow: 0 4px 20px rgba(201,168,76,0.25), inset 0 1px 0 rgba(255,255,255,0.15);
83
+ }
84
+ .btn-gold:hover {
85
+ background: linear-gradient(135deg, #c9a84c 0%, #e8c97a 60%, #f5dca0 100%);
86
+ box-shadow: 0 6px 30px rgba(201,168,76,0.4), inset 0 1px 0 rgba(255,255,255,0.2);
87
+ transform: translateY(-1px);
88
+ }
89
+ .btn-ghost {
90
+ background: transparent;
91
+ color: var(--text-sub);
92
+ font-size: 14px;
93
+ padding: 7px 14px;
94
+ border: 1px solid var(--border-bright);
95
+ border-radius: 3px;
96
+ }
97
+ .btn-ghost:hover { color: var(--text); border-color: var(--gold-dim); background: rgba(201,168,76,0.05); }
98
+
99
+ /* ── Screen 1: Input ─────────────────────────────────────────────────── */
100
+ #s-input {
101
+ justify-content: center;
102
+ padding: 40px 20px;
103
+ }
104
+
105
+ .brand {
106
+ text-align: center;
107
+ margin-bottom: 36px;
108
+ }
109
+ .brand-name {
110
+ font-family: 'Playfair Display', serif;
111
+ font-size: 13px;
112
+ font-weight: 400;
113
+ letter-spacing: 0.35em;
114
+ text-transform: uppercase;
115
+ color: var(--gold);
116
+ }
117
+ .brand-line {
118
+ display: inline-block;
119
+ width: 40px; height: 1px;
120
+ background: var(--gold-dim);
121
+ vertical-align: middle;
122
+ margin: 0 12px;
123
+ opacity: 0.5;
124
+ }
125
+
126
+ .input-card {
127
+ background: var(--bg-card);
128
+ border: 1px solid var(--border);
129
+ border-radius: 4px;
130
+ padding: 48px 52px;
131
+ width: 100%; max-width: 640px;
132
+ box-shadow:
133
+ 0 40px 100px rgba(0,0,0,0.6),
134
+ 0 0 0 1px rgba(201,168,76,0.06),
135
+ inset 0 1px 0 rgba(255,255,255,0.03);
136
+ }
137
+
138
+ @media (max-width: 520px) {
139
+ .input-card { padding: 28px 22px; }
140
+ .input-card h1 { font-size: 24px; }
141
+ #pgn { height: 160px; font-size: 12px; }
142
+ }
143
+
144
+ .input-card h1 {
145
+ font-family: 'Playfair Display', serif;
146
+ font-size: 32px; font-weight: 600;
147
+ color: var(--text);
148
+ margin-bottom: 6px;
149
+ }
150
+ .input-card .subtitle {
151
+ font-size: 16px; font-weight: 300;
152
+ color: var(--text-sub);
153
+ margin-bottom: 32px;
154
+ }
155
+
156
+ .field-label {
157
+ display: block;
158
+ font-size: 11px; font-weight: 500;
159
+ letter-spacing: 0.2em; text-transform: uppercase;
160
+ color: var(--gold-dim);
161
+ margin-bottom: 8px;
162
+ }
163
+
164
+ #pgn {
165
+ width: 100%; height: 200px;
166
+ background: var(--bg-deepest);
167
+ border: 1px solid var(--border);
168
+ border-radius: 3px;
169
+ color: var(--text);
170
+ font-family: 'JetBrains Mono', monospace;
171
+ font-size: 13px;
172
+ line-height: 1.6;
173
+ padding: 16px;
174
+ resize: vertical;
175
+ outline: none;
176
+ transition: border-color 0.2s;
177
+ margin-bottom: 28px;
178
+ }
179
+ #pgn::placeholder { color: var(--text-muted); }
180
+ #pgn:focus { border-color: var(--gold-dim); }
181
+
182
+ .depth-group { margin-bottom: 36px; }
183
+ .depth-row {
184
+ display: flex; align-items: center; gap: 16px;
185
+ }
186
+ #depth {
187
+ flex: 1;
188
+ -webkit-appearance: none;
189
+ height: 3px;
190
+ background: var(--border-bright);
191
+ border-radius: 2px;
192
+ outline: none;
193
+ }
194
+ #depth::-webkit-slider-thumb {
195
+ -webkit-appearance: none;
196
+ width: 16px; height: 16px;
197
+ border-radius: 50%;
198
+ background: var(--gold);
199
+ cursor: pointer;
200
+ box-shadow: 0 0 8px rgba(201,168,76,0.4);
201
+ }
202
+ #depth::-moz-range-thumb {
203
+ width: 16px; height: 16px;
204
+ border-radius: 50%;
205
+ background: var(--gold);
206
+ cursor: pointer; border: none;
207
+ }
208
+ #depth-val {
209
+ font-family: 'JetBrains Mono', monospace;
210
+ font-size: 20px; font-weight: 500;
211
+ color: var(--gold);
212
+ min-width: 28px; text-align: right;
213
+ }
214
+ .depth-hint {
215
+ display: block;
216
+ font-size: 13px; font-weight: 300;
217
+ color: var(--text-muted);
218
+ margin-top: 8px;
219
+ }
220
+
221
+ /* ── Screen 2: Loading ───────────────────────────────────────────────── */
222
+ #s-loading { justify-content: center; align-items: center; }
223
+
224
+ .loading-inner {
225
+ text-align: center;
226
+ width: 480px; max-width: 90vw;
227
+ }
228
+ .loading-inner h2 {
229
+ font-family: 'Playfair Display', serif;
230
+ font-size: 22px; font-weight: 400;
231
+ letter-spacing: 0.15em;
232
+ color: var(--text-sub);
233
+ margin-bottom: 36px;
234
+ text-transform: uppercase;
235
+ }
236
+
237
+ .progress-track {
238
+ width: 100%; height: 3px;
239
+ background: var(--border);
240
+ border-radius: 2px;
241
+ overflow: hidden;
242
+ margin-bottom: 20px;
243
+ }
244
+ #progress-bar {
245
+ height: 100%;
246
+ background: linear-gradient(90deg, var(--gold-dim), var(--gold), var(--gold-light));
247
+ border-radius: 2px;
248
+ width: 0%;
249
+ transition: width 0.45s cubic-bezier(0.4,0,0.2,1);
250
+ box-shadow: 0 0 12px rgba(201,168,76,0.5);
251
+ }
252
+
253
+ #progress-msg {
254
+ font-size: 14px; font-weight: 300;
255
+ color: var(--text-sub);
256
+ letter-spacing: 0.05em;
257
+ }
258
+ #progress-pct {
259
+ font-family: 'JetBrains Mono', monospace;
260
+ font-size: 12px;
261
+ color: var(--gold-dim);
262
+ margin-top: 10px;
263
+ }
264
+
265
+ /* ── Screen 3: Analysis ──────────────────────────────────────────────── */
266
+ #s-analysis {
267
+ align-items: center;
268
+ padding: 0 10px 40px;
269
+ }
270
+
271
+ .analysis-header {
272
+ display: flex; align-items: center; justify-content: space-between;
273
+ width: 100%; max-width: 620px;
274
+ padding: 12px 0 12px;
275
+ border-bottom: 1px solid var(--border);
276
+ margin-bottom: 14px;
277
+ gap: 8px;
278
+ }
279
+
280
+ .players {
281
+ text-align: center; flex: 1;
282
+ display: flex; align-items: center; justify-content: center; gap: 10px;
283
+ }
284
+ .player-name {
285
+ font-family: 'Playfair Display', serif;
286
+ font-size: 18px; font-weight: 500;
287
+ }
288
+ .white-player { color: #f0ede4; }
289
+ .black-player { color: #a09880; }
290
+ .vs {
291
+ font-size: 12px; font-weight: 300;
292
+ letter-spacing: 0.2em; text-transform: uppercase;
293
+ color: var(--text-muted);
294
+ }
295
+
296
+ @media (max-width: 420px) {
297
+ .player-name { font-size: 13px; }
298
+ .vs { display: none; }
299
+ .players { gap: 6px; }
300
+ .btn-ghost { font-size: 12px; padding: 5px 8px; }
301
+ }
302
+
303
+ /* ── Board ───────────────────────────────────────────────────────────── */
304
+ .board-area { position: relative; }
305
+
306
+ .board-with-coords {
307
+ display: flex; align-items: flex-start; gap: 0;
308
+ }
309
+
310
+ /* ── Eval bar ────────────────────────────────────────────────────────── */
311
+ #eval-bar-wrap {
312
+ position: relative;
313
+ width: 12px;
314
+ height: calc(var(--sq) * 8);
315
+ border-radius: 3px;
316
+ overflow: hidden;
317
+ background: #1c1a18;
318
+ margin-right: 6px;
319
+ flex-shrink: 0;
320
+ box-shadow: 0 0 0 1px var(--border);
321
+ }
322
+ #eval-bar-fill {
323
+ position: absolute;
324
+ bottom: 0; left: 0; right: 0;
325
+ background: #f0ede4;
326
+ height: 50%;
327
+ transition: height 0.5s cubic-bezier(0.4, 0, 0.2, 1);
328
+ border-radius: 0 0 3px 3px;
329
+ }
330
+
331
+ .rank-labels {
332
+ display: flex; flex-direction: column;
333
+ justify-content: space-around;
334
+ padding-right: 8px;
335
+ padding-bottom: calc(var(--sq) * 0); /* align with board */
336
+ }
337
+ .coord {
338
+ font-family: 'JetBrains Mono', monospace;
339
+ font-size: 11px;
340
+ color: var(--text-muted);
341
+ width: var(--sq); height: var(--sq);
342
+ display: flex; align-items: center; justify-content: center;
343
+ user-select: none;
344
+ }
345
+ .rank-labels .coord { width: auto; }
346
+
347
+ .board-column { display: flex; flex-direction: column; }
348
+
349
+ #board {
350
+ display: grid;
351
+ grid-template-columns: repeat(8, var(--sq));
352
+ grid-template-rows: repeat(8, var(--sq));
353
+ box-shadow:
354
+ 0 0 0 2px var(--border),
355
+ 0 0 0 3px var(--border-bright),
356
+ 0 24px 80px rgba(0,0,0,0.8),
357
+ 0 4px 16px rgba(0,0,0,0.5);
358
+ }
359
+
360
+ .file-labels {
361
+ display: flex;
362
+ margin-top: 4px;
363
+ }
364
+ .file-labels .coord { height: auto; padding: 4px 0; }
365
+
366
+ .sq {
367
+ width: var(--sq); height: var(--sq);
368
+ position: relative;
369
+ display: flex; align-items: center; justify-content: center;
370
+ overflow: hidden;
371
+ }
372
+ .sq.light { background: #f0d9b5; }
373
+ .sq.dark { background: #b58863; }
374
+
375
+ .overlay {
376
+ position: absolute; inset: 0; z-index: 1;
377
+ pointer-events: none;
378
+ }
379
+ .from-ov { background: rgba(255, 220, 60, 0.35); }
380
+ .to-ov { opacity: 0.72; }
381
+
382
+ .cls-badge {
383
+ position: absolute; top: 2px; right: 2px; z-index: 4;
384
+ width: calc(var(--sq) * 0.31); height: calc(var(--sq) * 0.31);
385
+ min-width: 14px; min-height: 14px;
386
+ border-radius: 50%;
387
+ display: flex; align-items: center; justify-content: center;
388
+ font-size: calc(var(--sq) * 0.16); font-weight: 700;
389
+ color: #fff;
390
+ font-family: 'JetBrains Mono', monospace;
391
+ pointer-events: none;
392
+ box-shadow: 0 1px 4px rgba(0,0,0,0.6);
393
+ }
394
+
395
+ .piece {
396
+ position: relative; z-index: 2;
397
+ font-size: calc(var(--sq) * 0.64); line-height: 1;
398
+ display: flex; align-items: center; justify-content: center;
399
+ width: 100%; height: 100%;
400
+ pointer-events: none; user-select: none;
401
+ }
402
+ .wp {
403
+ color: #fff;
404
+ text-shadow:
405
+ 0 0 2px #000, 0 0 3px #000,
406
+ 1px 1px 0 #000, -1px -1px 0 #000,
407
+ 1px -1px 0 #000, -1px 1px 0 #000;
408
+ }
409
+ .bp {
410
+ color: #1c1a18;
411
+ text-shadow:
412
+ 0 0 2px rgba(255,255,255,0.55),
413
+ 1px 1px 0 rgba(255,255,255,0.3),
414
+ -1px -1px 0 rgba(255,255,255,0.3);
415
+ }
416
+
417
+ /* ── Navigation ──────────────────────────────────────────────────────── */
418
+ .nav-row {
419
+ display: flex; align-items: center; gap: 20px;
420
+ margin-top: 12px;
421
+ }
422
+ .nav-btn {
423
+ background: var(--bg-card);
424
+ border: 1px solid var(--border-bright);
425
+ color: var(--text-sub);
426
+ width: 40px; height: 40px;
427
+ border-radius: 3px;
428
+ font-size: 16px; cursor: pointer;
429
+ display: flex; align-items: center; justify-content: center;
430
+ transition: all 0.15s;
431
+ }
432
+ .nav-btn:hover:not(:disabled) { border-color: var(--gold-dim); color: var(--text); background: var(--bg-elevated); }
433
+ .nav-btn:disabled { opacity: 0.25; cursor: default; }
434
+
435
+ #move-indicator {
436
+ font-family: 'JetBrains Mono', monospace;
437
+ font-size: 13px; font-weight: 500;
438
+ color: var(--text-sub);
439
+ min-width: 100px; text-align: center;
440
+ }
441
+
442
+ /* ── Move timeline ───────────────────────────────────────────────────── */
443
+ #move-timeline {
444
+ display: flex; gap: 3px;
445
+ margin-top: 10px;
446
+ max-width: calc(var(--sq) * 8 + 46px);
447
+ width: 100%;
448
+ overflow-x: auto;
449
+ padding-bottom: 4px;
450
+ scrollbar-width: thin;
451
+ scrollbar-color: var(--border-bright) transparent;
452
+ }
453
+ .tl-dot {
454
+ flex-shrink: 0;
455
+ width: 10px; height: 10px;
456
+ border-radius: 50%;
457
+ cursor: pointer;
458
+ transition: transform 0.15s, opacity 0.15s;
459
+ opacity: 0.65;
460
+ }
461
+ .tl-dot:hover { opacity: 1; transform: scale(1.3); }
462
+ .tl-dot.tl-active {
463
+ opacity: 1; transform: scale(1.25);
464
+ box-shadow: 0 0 6px currentColor;
465
+ }
466
+
467
+ /* ── Info box ───────────────────────���────────────────────────────────── */
468
+ #info-box {
469
+ margin-top: 12px;
470
+ width: 100%;
471
+ max-width: calc(var(--sq) * 8 + 46px);
472
+ background: var(--bg-card);
473
+ border: 1px solid var(--border);
474
+ border-radius: 4px;
475
+ overflow: visible;
476
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
477
+ margin-bottom: 24px;
478
+ }
479
+
480
+ .info-start {
481
+ padding: 28px 32px;
482
+ color: var(--text-muted);
483
+ font-size: 15px; font-weight: 300;
484
+ font-style: italic;
485
+ }
486
+
487
+ .info-header {
488
+ padding: 16px 18px 14px;
489
+ border-bottom: 1px solid var(--border);
490
+ display: grid;
491
+ grid-template-columns: auto 1fr auto;
492
+ align-items: center;
493
+ gap: 12px;
494
+ }
495
+
496
+ @media (max-width: 420px) {
497
+ .info-header { grid-template-columns: auto 1fr; gap: 8px; padding: 12px 14px; }
498
+ .ep-badge { display: none; }
499
+ .info-comment { padding: 10px 14px; font-size: 13px; }
500
+ .info-row { padding: 8px 14px; gap: 8px; grid-template-columns: 90px 1fr; }
501
+ .info-label { font-size: 10px; }
502
+ .info-val { font-size: 13px; }
503
+ .info-val.mono { font-size: 11px; }
504
+ .move-name { font-size: 16px; }
505
+ .cls-label { font-size: 10px; }
506
+ }
507
+
508
+ .cls-pill {
509
+ display: inline-flex; align-items: center; gap: 7px;
510
+ padding: 5px 12px 5px 9px;
511
+ border-radius: 99px;
512
+ border: 1px solid;
513
+ white-space: nowrap;
514
+ }
515
+ .cls-icon {
516
+ font-size: 12px;
517
+ font-family: 'JetBrains Mono', monospace;
518
+ }
519
+ .cls-label {
520
+ font-size: 11px; font-weight: 600;
521
+ letter-spacing: 0.15em;
522
+ font-family: 'Crimson Pro', serif;
523
+ }
524
+
525
+ .move-name {
526
+ font-family: 'Playfair Display', serif;
527
+ font-size: 20px; font-weight: 600;
528
+ color: var(--text);
529
+ }
530
+
531
+ .ep-badge {
532
+ font-family: 'JetBrains Mono', monospace;
533
+ font-size: 11px;
534
+ color: var(--text-muted);
535
+ white-space: nowrap;
536
+ }
537
+
538
+ .info-comment {
539
+ padding: 13px 18px;
540
+ font-size: 15px; font-weight: 300;
541
+ font-style: italic;
542
+ color: var(--text-sub);
543
+ border-bottom: 1px solid var(--border);
544
+ line-height: 1.5;
545
+ }
546
+
547
+ .info-details { padding: 4px 0 8px; }
548
+
549
+ .info-row {
550
+ display: grid;
551
+ grid-template-columns: 120px 1fr;
552
+ align-items: baseline;
553
+ gap: 12px;
554
+ padding: 9px 18px;
555
+ }
556
+ .info-row + .info-row { border-top: 1px solid var(--border); }
557
+
558
+ .info-label {
559
+ font-size: 11px; font-weight: 500;
560
+ letter-spacing: 0.18em; text-transform: uppercase;
561
+ color: var(--text-muted);
562
+ }
563
+ .info-val {
564
+ font-size: 15px;
565
+ color: var(--text);
566
+ }
567
+ .info-val.mono {
568
+ font-family: 'JetBrains Mono', monospace;
569
+ font-size: 13px;
570
+ }
571
+ .cont-text {
572
+ color: var(--text-sub);
573
+ line-height: 1.7;
574
+ word-break: break-word;
575
+ }
576
+
577
+ .best-row.found .info-val {
578
+ color: #6aa84f;
579
+ font-weight: 500;
580
+ }
581
+ /* ── Screen 4: Summary ───────────────────────────────────────────────── */
582
+ #s-summary {
583
+ justify-content: flex-start;
584
+ align-items: center;
585
+ padding: 0 16px 40px;
586
+ }
587
+
588
+ .summary-header {
589
+ display: flex; align-items: center; justify-content: space-between;
590
+ width: 100%; max-width: 680px;
591
+ padding: 12px 0;
592
+ border-bottom: 1px solid var(--border);
593
+ margin-bottom: 24px;
594
+ gap: 8px;
595
+ }
596
+ .summary-title {
597
+ font-family: 'Playfair Display', serif;
598
+ font-size: 20px; font-weight: 600;
599
+ color: var(--text);
600
+ text-align: center; flex: 1;
601
+ }
602
+
603
+ .summary-grid {
604
+ display: grid;
605
+ grid-template-columns: 1fr 1fr;
606
+ gap: 16px;
607
+ width: 100%; max-width: 680px;
608
+ }
609
+ @media (max-width: 520px) {
610
+ .summary-grid { grid-template-columns: 1fr; }
611
+ }
612
+
613
+ .summary-card {
614
+ background: var(--bg-card);
615
+ border: 1px solid var(--border);
616
+ border-radius: 4px;
617
+ overflow: hidden;
618
+ box-shadow: 0 8px 24px rgba(0,0,0,0.4);
619
+ }
620
+
621
+ .summary-card-header {
622
+ padding: 14px 20px 12px;
623
+ border-bottom: 1px solid var(--border);
624
+ display: flex; align-items: baseline; justify-content: space-between;
625
+ gap: 12px;
626
+ }
627
+ .summary-player-name {
628
+ font-family: 'Playfair Display', serif;
629
+ font-size: 17px; font-weight: 600;
630
+ }
631
+ .summary-accuracy {
632
+ display: flex; align-items: baseline; gap: 5px;
633
+ }
634
+ .summary-accuracy-num {
635
+ font-family: 'JetBrains Mono', monospace;
636
+ font-size: 22px; font-weight: 500;
637
+ color: var(--gold);
638
+ }
639
+ .summary-accuracy-label {
640
+ font-size: 11px; font-weight: 400;
641
+ letter-spacing: 0.12em; text-transform: uppercase;
642
+ color: var(--text-muted);
643
+ }
644
+
645
+ .summary-rows { padding: 6px 0 10px; }
646
+
647
+ .summary-row {
648
+ display: flex; align-items: center;
649
+ padding: 6px 20px;
650
+ gap: 10px;
651
+ }
652
+ .summary-row:hover { background: rgba(255,255,255,0.02); }
653
+
654
+ .summary-icon {
655
+ width: 22px; height: 22px;
656
+ border-radius: 50%;
657
+ display: flex; align-items: center; justify-content: center;
658
+ font-size: 9px; font-weight: 700;
659
+ font-family: 'JetBrains Mono', monospace;
660
+ color: #fff;
661
+ flex-shrink: 0;
662
+ }
663
+ .summary-cls-name {
664
+ font-size: 13px; font-weight: 400;
665
+ color: var(--text-sub);
666
+ flex: 1;
667
+ }
668
+ .summary-count {
669
+ font-family: 'JetBrains Mono', monospace;
670
+ font-size: 14px; font-weight: 500;
671
+ color: var(--text);
672
+ min-width: 20px; text-align: right;
673
+ }
674
+ .summary-bar-wrap {
675
+ width: 60px; height: 4px;
676
+ background: var(--border);
677
+ border-radius: 2px;
678
+ overflow: hidden;
679
+ }
680
+ .summary-bar-fill {
681
+ height: 100%; border-radius: 2px;
682
+ }
683
+
684
+ .summary-cta {
685
+ margin-top: 20px;
686
+ display: flex; gap: 10px; justify-content: center;
687
+ }
688
+ </style>
689
+ </head>
690
+ <body>
691
+
692
+ <!-- ═══════════════════════════════════════════════════════════════════════
693
+ SCREEN 1 — PGN Input
694
+ ══════════════════════════════════════════════════════════════════════════ -->
695
+ <div id="s-input" class="screen active">
696
+ <div style="margin-top: auto; margin-bottom: auto; width: 100%; max-width: 640px; padding: 40px 20px;">
697
+ <div class="brand">
698
+ <span class="brand-line"></span>
699
+ <span class="brand-name">Chess Analysis</span>
700
+ <span class="brand-line"></span>
701
+ </div>
702
+
703
+ <div class="input-card">
704
+ <h1>Game Analysis</h1>
705
+ <p class="subtitle">Paste your PGN below and set analysis depth</p>
706
+
707
+ <label class="field-label" for="pgn">PGN Notation</label>
708
+ <textarea id="pgn" placeholder="[Event &quot;Chess Match&quot;]&#10;[White &quot;Player One&quot;]&#10;[Black &quot;Player Two&quot;]&#10;&#10;1. e4 e5 2. Nf3 Nc6 3. Bb5 ..."></textarea>
709
+
710
+ <div class="depth-group">
711
+ <label class="field-label" for="depth">Analysis Depth</label>
712
+ <div class="depth-row">
713
+ <input type="range" id="depth" min="6" max="22" value="9">
714
+ <span id="depth-val">9</span>
715
+ </div>
716
+ <span class="depth-hint">Deeper = stronger analysis, longer wait</span>
717
+ </div>
718
+
719
+ <div style="text-align: center;">
720
+ <button id="analyze-btn" class="btn btn-gold">Analyze Game</button>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ </div>
725
+
726
+
727
+ <!-- ═══════════════════════════════════════════════════════════════════════
728
+ SCREEN 2 — Loading
729
+ ══════════════════════════════════════════════════════════════════════════ -->
730
+ <div id="s-loading" class="screen">
731
+ <div class="loading-inner" style="margin: auto;">
732
+ <h2>Analyzing</h2>
733
+ <div class="progress-track">
734
+ <div id="progress-bar"></div>
735
+ </div>
736
+ <p id="progress-msg">Initializing engine…</p>
737
+ <p id="progress-pct">0%</p>
738
+ </div>
739
+ </div>
740
+
741
+
742
+ <!-- ═══════════════════════════════════════════════════════════════════════
743
+ SCREEN 3 — Summary
744
+ ══════════════════════════════════════════════════════════════════════════ -->
745
+ <div id="s-summary" class="screen">
746
+
747
+ <div class="summary-header">
748
+ <button id="summary-back-btn" class="btn btn-ghost">← New Analysis</button>
749
+ <div class="summary-title">Game Summary</div>
750
+ <button id="summary-review-btn" class="btn btn-gold" style="font-size:14px; padding:9px 20px;">Review Board →</button>
751
+ </div>
752
+
753
+ <div class="summary-grid" id="summary-grid">
754
+ <!-- populated by JS -->
755
+ </div>
756
+
757
+ </div>
758
+
759
+
760
+ <!-- ═══════════════════════════════════════════════════════════════════════
761
+ SCREEN 4 — Analysis
762
+ ═════════════════════════════════════════════════════════════════════════��� -->
763
+ <div id="s-analysis" class="screen">
764
+
765
+ <div class="analysis-header">
766
+ <button id="back-btn" class="btn btn-ghost">← New Analysis</button>
767
+ <div class="players">
768
+ <span id="white-name" class="player-name white-player">White</span>
769
+ <span class="vs">vs</span>
770
+ <span id="black-name" class="player-name black-player">Black</span>
771
+ </div>
772
+ <button id="flip-btn" class="btn btn-ghost">⇅ Flip</button>
773
+ </div>
774
+
775
+ <div class="board-area">
776
+ <div class="board-with-coords">
777
+ <div id="eval-bar-wrap"><div id="eval-bar-fill"></div></div>
778
+ <div class="rank-labels" id="rank-labels"></div>
779
+ <div class="board-column">
780
+ <div id="board"></div>
781
+ <div class="file-labels" id="file-labels"></div>
782
+ </div>
783
+ </div>
784
+ </div>
785
+
786
+ <div class="nav-row">
787
+ <button id="prev-btn" class="nav-btn" title="Previous move (←)">←</button>
788
+ <span id="move-indicator">Start</span>
789
+ <button id="next-btn" class="nav-btn" title="Next move (→)">→</button>
790
+ </div>
791
+
792
+ <div id="move-timeline"></div>
793
+
794
+ <div id="info-box">
795
+ <div class="info-start"><p>Press → to begin stepping through the game.</p></div>
796
+ </div>
797
+
798
+ </div>
799
+
800
+ <script>
801
+ // ── Constants ──────────────────────────────────────────────────────────
802
+ const PIECE_MAP = {
803
+ 'K':'♔','Q':'♕','R':'♖','B':'♗','N':'♘','P':'♙',
804
+ 'k':'♚','q':'♛','r':'♜','b':'♝','n':'♞','p':'♟'
805
+ };
806
+
807
+ const CLS_META = {
808
+ Brilliant: { color:'#22d1cc', icon:'!!', label:'Brilliant' },
809
+ Great: { color:'#5b8dd9', icon:'!', label:'Great' },
810
+ Miss: { color:'#e05555', icon:'×', label:'Miss' },
811
+ Best: { color:'#5fb153', icon:'★', label:'Best' },
812
+ Excellent: { color:'#96d16a', icon:'👍', label:'Excellent' },
813
+ Good: { color:'#a8d672', icon:'✓', label:'Good' },
814
+ Inaccuracy: { color:'#f0c040', icon:'?!', label:'Inaccuracy' },
815
+ Mistake: { color:'#e08030', icon:'?', label:'Mistake' },
816
+ Blunder: { color:'#d43030', icon:'??', label:'Blunder' },
817
+ };
818
+
819
+ // ── State ──────────────────────────────────────────────────────────────
820
+ let gameData = null;
821
+ let curIdx = -1; // -1 = start, 0..n-1 = after that move
822
+ let flipped = false;
823
+
824
+ // ── Eval bar ───────────────────────────────────────────────────────────
825
+ function getWhiteEP(mv) {
826
+ return mv.color === 'white' ? mv.ep_after : 1.0 - mv.ep_after;
827
+ }
828
+
829
+ function updateEvalBar(epWhite) {
830
+ // epWhite: 0.0 (black winning) → 1.0 (white winning), 0.5 = equal
831
+ const fill = document.getElementById('eval-bar-fill');
832
+ if (!fill) return;
833
+ const pct = Math.max(2, Math.min(98, epWhite * 100));
834
+ fill.style.height = pct + '%';
835
+ }
836
+
837
+ // ── Summary screen ─────────────────────────────────────────────────────
838
+ const CLS_ORDER = [
839
+ "Brilliant","Great","Best","Excellent","Good",
840
+ "Inaccuracy","Mistake","Blunder","Miss"
841
+ ];
842
+ const CLS_DISPLAY = {
843
+ "Miss": "Missed Tactic",
844
+ };
845
+
846
+ function renderSummary(data) {
847
+ const grid = document.getElementById('summary-grid');
848
+ grid.innerHTML = '';
849
+
850
+ const colors = [
851
+ { key: 'white', name: data.white, nameClass: 'white-player' },
852
+ { key: 'black', name: data.black, nameClass: 'black-player' },
853
+ ];
854
+
855
+ for (const { key, name, nameClass } of colors) {
856
+ const stats = data.summary[key];
857
+ const counts = stats.counts;
858
+ const total = Object.values(counts).reduce((a, b) => a + b, 0);
859
+ const maxCount = Math.max(1, ...Object.values(counts));
860
+
861
+ const rows = CLS_ORDER.map(cls => {
862
+ const meta = CLS_META[cls];
863
+ const count = counts[cls] || 0;
864
+ const barPct = Math.round((count / maxCount) * 100);
865
+ const label = CLS_DISPLAY[cls] || cls;
866
+ return `
867
+ <div class="summary-row">
868
+ <div class="summary-icon" style="background:${meta.color}">${meta.icon}</div>
869
+ <span class="summary-cls-name">${label}</span>
870
+ <div class="summary-bar-wrap">
871
+ <div class="summary-bar-fill" style="width:${barPct}%;background:${meta.color}88"></div>
872
+ </div>
873
+ <span class="summary-count">${count}</span>
874
+ </div>`;
875
+ }).join('');
876
+
877
+ grid.innerHTML += `
878
+ <div class="summary-card">
879
+ <div class="summary-card-header">
880
+ <span class="summary-player-name ${nameClass}">${name}</span>
881
+ <div class="summary-accuracy">
882
+ <span class="summary-accuracy-num">${stats.accuracy}%</span>
883
+ <span class="summary-accuracy-label">accuracy</span>
884
+ </div>
885
+ </div>
886
+ <div class="summary-rows">${rows}</div>
887
+ </div>`;
888
+ }
889
+ }
890
+
891
+ // ── Screen switching ───────────────────────────────────────────────────
892
+ function showScreen(id) {
893
+ document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
894
+ document.getElementById(id).classList.add('active');
895
+ }
896
+
897
+ // ── FEN parser ─────────────────────────────────────────────────────────
898
+ function parseFEN(fen) {
899
+ const rows = fen.split(' ')[0].split('/');
900
+ const board = {};
901
+ for (let r = 0; r < 8; r++) {
902
+ const rank = 8 - r;
903
+ let file = 0;
904
+ for (const ch of rows[r]) {
905
+ if (ch >= '1' && ch <= '8') { file += +ch; }
906
+ else { board['abcdefgh'[file] + rank] = ch; file++; }
907
+ }
908
+ }
909
+ return board;
910
+ }
911
+
912
+ // ── Board renderer ─────────────────────────────────────────────────────
913
+ function renderBoard(fen, fromSq, toSq, cls) {
914
+ const pieces = parseFEN(fen);
915
+ const boardEl = document.getElementById('board');
916
+ boardEl.innerHTML = '';
917
+
918
+ const rankOrder = flipped ? [1,2,3,4,5,6,7,8] : [8,7,6,5,4,3,2,1];
919
+ const fileOrder = flipped
920
+ ? ['h','g','f','e','d','c','b','a']
921
+ : ['a','b','c','d','e','f','g','h'];
922
+
923
+ const meta = cls ? (CLS_META[cls] || null) : null;
924
+
925
+ for (const rank of rankOrder) {
926
+ for (let fi = 0; fi < 8; fi++) {
927
+ const file = fileOrder[fi];
928
+ const sq = file + rank;
929
+ const isLight = (fi + rank) % 2 === 0;
930
+
931
+ const div = document.createElement('div');
932
+ div.className = `sq ${isLight ? 'light' : 'dark'}`;
933
+
934
+ // From-square highlight
935
+ if (sq === fromSq) {
936
+ const ov = document.createElement('div');
937
+ ov.className = 'overlay from-ov';
938
+ div.appendChild(ov);
939
+ }
940
+
941
+ // To-square highlight + badge
942
+ if (sq === toSq && meta) {
943
+ const ov = document.createElement('div');
944
+ ov.className = 'overlay to-ov';
945
+ ov.style.background = meta.color;
946
+ div.appendChild(ov);
947
+
948
+ const badge = document.createElement('div');
949
+ badge.className = 'cls-badge';
950
+ badge.textContent = meta.icon;
951
+ badge.style.background = meta.color;
952
+ div.appendChild(badge);
953
+ }
954
+
955
+ // Piece
956
+ const piece = pieces[sq];
957
+ if (piece) {
958
+ const span = document.createElement('span');
959
+ span.className = `piece ${piece === piece.toUpperCase() ? 'wp' : 'bp'}`;
960
+ span.textContent = PIECE_MAP[piece] || piece;
961
+ div.appendChild(span);
962
+ }
963
+
964
+ boardEl.appendChild(div);
965
+ }
966
+ }
967
+
968
+ updateCoords(rankOrder, fileOrder);
969
+ }
970
+
971
+ function updateCoords(rankOrder, fileOrder) {
972
+ const rl = document.getElementById('rank-labels');
973
+ rl.innerHTML = rankOrder.map(r =>
974
+ `<div class="coord" style="width:auto; padding-right:0">${r}</div>`
975
+ ).join('');
976
+
977
+ const fl = document.getElementById('file-labels');
978
+ fl.innerHTML = fileOrder.map(f =>
979
+ `<div class="coord" style="height:auto">${f}</div>`
980
+ ).join('');
981
+ }
982
+
983
+ // ── Timeline ───────────────────────────────────────────────────────────
984
+ function buildTimeline() {
985
+ const tl = document.getElementById('move-timeline');
986
+ tl.innerHTML = '';
987
+ gameData.moves.forEach((mv, i) => {
988
+ const dot = document.createElement('div');
989
+ dot.className = 'tl-dot';
990
+ dot.style.background = (CLS_META[mv.classification] || {color:'#555'}).color;
991
+ dot.title = `${mv.move_number}${mv.color==='white'?'.':'...'} ${mv.san} — ${mv.classification}`;
992
+ dot.addEventListener('click', () => navigateTo(i));
993
+ tl.appendChild(dot);
994
+ });
995
+ }
996
+
997
+ function refreshTimeline() {
998
+ const dots = document.querySelectorAll('.tl-dot');
999
+ dots.forEach((d, i) => {
1000
+ d.classList.toggle('tl-active', i === curIdx);
1001
+ });
1002
+ // Scroll active dot into view
1003
+ if (curIdx >= 0 && dots[curIdx]) {
1004
+ dots[curIdx].scrollIntoView({ behavior: 'smooth', inline: 'nearest', block: 'nearest' });
1005
+ }
1006
+ }
1007
+
1008
+ // ── Info box ───────────────────────────────────────────────────────────
1009
+ function renderStartInfo() {
1010
+ document.getElementById('info-box').innerHTML = `
1011
+ <div class="info-start">
1012
+ <p>Press <strong>→</strong> or the right arrow key to step through the game.</p>
1013
+ </div>`;
1014
+ }
1015
+
1016
+ function renderMoveInfo(mv) {
1017
+ const meta = CLS_META[mv.classification] || { color:'#888', icon:'?', label: mv.classification };
1018
+ const ep = mv.ep_loss > 0.001
1019
+ ? `−${(mv.ep_loss * 100).toFixed(1)}% EP`
1020
+ : 'No EP lost';
1021
+
1022
+ const moveLabel = mv.color === 'white'
1023
+ ? `${mv.move_number}. ${mv.san}`
1024
+ : `${mv.move_number}… ${mv.san}`;
1025
+
1026
+ const bestHtml = mv.best_move_san
1027
+ ? `<div class="info-row best-row">
1028
+ <span class="info-label">Best move</span>
1029
+ <span class="info-val mono">${mv.color === 'white' ? mv.move_number + '.' : mv.move_number + '…'} ${mv.best_move_san}</span>
1030
+ </div>`
1031
+ : `<div class="info-row best-row found">
1032
+ <span class="info-label">Best move</span>
1033
+ <span class="info-val found">✓ You found the best move</span>
1034
+ </div>`;
1035
+
1036
+ const contHtml = mv.continuation_fmt
1037
+ ? `<div class="info-row cont-row">
1038
+ <span class="info-label">Continuation</span>
1039
+ <span class="info-val mono cont-text">${mv.continuation_fmt}</span>
1040
+ </div>`
1041
+ : '';
1042
+
1043
+ document.getElementById('info-box').innerHTML = `
1044
+ <div class="info-header">
1045
+ <div class="cls-pill" style="background:${meta.color}18; border-color:${meta.color}55; color:${meta.color}">
1046
+ <span class="cls-icon">${meta.icon}</span>
1047
+ <span class="cls-label">${meta.label.toUpperCase()}</span>
1048
+ </div>
1049
+ <div class="move-name">${moveLabel}</div>
1050
+ <div class="ep-badge">${ep}</div>
1051
+ </div>
1052
+ <div class="info-comment">"${mv.comment}"</div>
1053
+ <div class="info-details">
1054
+ ${bestHtml}
1055
+ ${contHtml}
1056
+ </div>`;
1057
+ }
1058
+
1059
+ // ── Navigation ─────────────────────────────────────────────────────────
1060
+ function navigateTo(idx) {
1061
+ if (!gameData) return;
1062
+ const max = gameData.moves.length - 1;
1063
+ curIdx = Math.max(-1, Math.min(idx, max));
1064
+
1065
+ document.getElementById('prev-btn').disabled = curIdx === -1;
1066
+ document.getElementById('next-btn').disabled = curIdx === max;
1067
+
1068
+ if (curIdx === -1) {
1069
+ renderBoard(gameData.initial_fen, null, null, null);
1070
+ document.getElementById('move-indicator').textContent = 'Start';
1071
+ renderStartInfo();
1072
+ updateEvalBar(0.5);
1073
+ } else {
1074
+ const mv = gameData.moves[curIdx];
1075
+ renderBoard(mv.fen_after, mv.from_square, mv.to_square, mv.classification);
1076
+ document.getElementById('move-indicator').textContent =
1077
+ `Move ${curIdx + 1} / ${gameData.moves.length}`;
1078
+ renderMoveInfo(mv);
1079
+ updateEvalBar(getWhiteEP(mv));
1080
+ }
1081
+
1082
+ refreshTimeline();
1083
+ }
1084
+
1085
+ // ── Load completed analysis ────────────────────────────────────────────
1086
+ function loadAnalysis(data) {
1087
+ gameData = data;
1088
+ curIdx = -1;
1089
+ flipped = false;
1090
+
1091
+ document.getElementById('white-name').textContent = data.white;
1092
+ document.getElementById('black-name').textContent = data.black;
1093
+
1094
+ buildTimeline();
1095
+ navigateTo(-1);
1096
+
1097
+ // Show summary first
1098
+ renderSummary(data);
1099
+ showScreen('s-summary');
1100
+ }
1101
+
1102
+ // ── Progress bar ───────────────────────────────────────────────────────
1103
+ function setProgress(pct, msg) {
1104
+ document.getElementById('progress-bar').style.width = `${Math.round(pct * 100)}%`;
1105
+ document.getElementById('progress-msg').textContent = msg;
1106
+ document.getElementById('progress-pct').textContent = `${Math.round(pct * 100)}%`;
1107
+ }
1108
+
1109
+ // ── Submit analysis ────────────────────────────────────────────────────
1110
+ async function submitAnalysis() {
1111
+ const pgn = document.getElementById('pgn').value.trim();
1112
+ const depth = parseInt(document.getElementById('depth').value, 10);
1113
+
1114
+ if (!pgn) { alert('Please paste a PGN to analyze.'); return; }
1115
+
1116
+ showScreen('s-loading');
1117
+ setProgress(0, 'Connecting to analysis engine…');
1118
+
1119
+ let jobId;
1120
+ try {
1121
+ const res = await fetch('/api/analyze', {
1122
+ method: 'POST',
1123
+ headers: { 'Content-Type': 'application/json' },
1124
+ body: JSON.stringify({ pgn, depth })
1125
+ });
1126
+ const d = await res.json();
1127
+ if (d.error) throw new Error(d.error);
1128
+ jobId = d.job_id;
1129
+ } catch (err) {
1130
+ alert('Error: ' + err.message);
1131
+ showScreen('s-input');
1132
+ return;
1133
+ }
1134
+
1135
+ const evtSrc = new EventSource(`/api/stream/${jobId}`);
1136
+
1137
+ evtSrc.onmessage = (e) => {
1138
+ const data = JSON.parse(e.data);
1139
+ if (data.type === 'progress') {
1140
+ setProgress(data.progress, data.message);
1141
+ } else if (data.type === 'complete') {
1142
+ evtSrc.close();
1143
+ loadAnalysis(data.data);
1144
+ } else if (data.type === 'error') {
1145
+ evtSrc.close();
1146
+ alert('Analysis error: ' + data.message);
1147
+ showScreen('s-input');
1148
+ }
1149
+ };
1150
+
1151
+ evtSrc.onerror = () => {
1152
+ evtSrc.close();
1153
+ alert('Connection lost. Please try again.');
1154
+ showScreen('s-input');
1155
+ };
1156
+ }
1157
+
1158
+ // ── Wire up controls ───────────────────────────────────────────────────
1159
+ document.getElementById('depth').addEventListener('input', e => {
1160
+ document.getElementById('depth-val').textContent = e.target.value;
1161
+ });
1162
+
1163
+ document.getElementById('analyze-btn').addEventListener('click', submitAnalysis);
1164
+
1165
+ document.getElementById('summary-back-btn').addEventListener('click', () => {
1166
+ showScreen('s-input');
1167
+ });
1168
+
1169
+ document.getElementById('summary-review-btn').addEventListener('click', () => {
1170
+ showScreen('s-analysis');
1171
+ });
1172
+
1173
+ document.getElementById('back-btn').addEventListener('click', () => {
1174
+ showScreen('s-summary');
1175
+ });
1176
+
1177
+ document.getElementById('prev-btn').addEventListener('click', () => navigateTo(curIdx - 1));
1178
+ document.getElementById('next-btn').addEventListener('click', () => navigateTo(curIdx + 1));
1179
+
1180
+ document.getElementById('flip-btn').addEventListener('click', () => {
1181
+ flipped = !flipped;
1182
+ if (!gameData) return;
1183
+ if (curIdx === -1) {
1184
+ renderBoard(gameData.initial_fen, null, null, null);
1185
+ } else {
1186
+ const mv = gameData.moves[curIdx];
1187
+ renderBoard(mv.fen_after, mv.from_square, mv.to_square, mv.classification);
1188
+ }
1189
+ });
1190
+
1191
+ document.addEventListener('keydown', (e) => {
1192
+ if (!gameData) return;
1193
+ const active = document.querySelector('.screen.active');
1194
+ if (!active || active.id !== 's-analysis') return;
1195
+ if (e.key === 'ArrowRight') { e.preventDefault(); navigateTo(curIdx + 1); }
1196
+ if (e.key === 'ArrowLeft') { e.preventDefault(); navigateTo(curIdx - 1); }
1197
+ });
1198
+ </script>
1199
+ </body>
1200
+ </html>