Fu01978 commited on
Commit
2e14b7d
·
verified ·
1 Parent(s): ac84159

Delete templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +0 -1209
templates/index.html DELETED
@@ -1,1209 +0,0 @@
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
- Book: { color:'#b5854a', icon:'📖', label:'Book' },
809
- Brilliant: { color:'#22d1cc', icon:'!!', label:'Brilliant' },
810
- Great: { color:'#5b8dd9', icon:'!', label:'Great' },
811
- Miss: { color:'#e05555', icon:'×', label:'Miss' },
812
- Best: { color:'#5fb153', icon:'★', label:'Best' },
813
- Excellent: { color:'#96d16a', icon:'👍', label:'Excellent' },
814
- Good: { color:'#a8d672', icon:'✓', label:'Good' },
815
- Inaccuracy: { color:'#f0c040', icon:'?!', label:'Inaccuracy' },
816
- Mistake: { color:'#e08030', icon:'?', label:'Mistake' },
817
- Blunder: { color:'#d43030', icon:'??', label:'Blunder' },
818
- };
819
-
820
- // ── State ──────────────────────────────────────────────────────────────
821
- let gameData = null;
822
- let curIdx = -1; // -1 = start, 0..n-1 = after that move
823
- let flipped = false;
824
-
825
- // ── Eval bar ───────────────────────────────────────────────────────────
826
- function getWhiteEP(mv) {
827
- return mv.color === 'white' ? mv.ep_after : 1.0 - mv.ep_after;
828
- }
829
-
830
- function updateEvalBar(epWhite) {
831
- // epWhite: 0.0 (black winning) → 1.0 (white winning), 0.5 = equal
832
- const fill = document.getElementById('eval-bar-fill');
833
- if (!fill) return;
834
- const pct = Math.max(2, Math.min(98, epWhite * 100));
835
- fill.style.height = pct + '%';
836
- }
837
-
838
- // ── Summary screen ─────────────────────────────────────────────────────
839
- const CLS_ORDER = [
840
- "Book","Brilliant","Great","Best","Excellent","Good",
841
- "Inaccuracy","Mistake","Blunder","Miss"
842
- ];
843
- const CLS_DISPLAY = {
844
- "Miss": "Missed Tactic",
845
- };
846
-
847
- function renderSummary(data) {
848
- const grid = document.getElementById('summary-grid');
849
- grid.innerHTML = '';
850
-
851
- const colors = [
852
- { key: 'white', name: data.white, nameClass: 'white-player' },
853
- { key: 'black', name: data.black, nameClass: 'black-player' },
854
- ];
855
-
856
- for (const { key, name, nameClass } of colors) {
857
- const stats = data.summary[key];
858
- const counts = stats.counts;
859
- const total = Object.values(counts).reduce((a, b) => a + b, 0);
860
- const maxCount = Math.max(1, ...Object.values(counts));
861
-
862
- const rows = CLS_ORDER.map(cls => {
863
- const meta = CLS_META[cls];
864
- const count = counts[cls] || 0;
865
- const barPct = Math.round((count / maxCount) * 100);
866
- const label = CLS_DISPLAY[cls] || cls;
867
- return `
868
- <div class="summary-row">
869
- <div class="summary-icon" style="background:${meta.color}">${meta.icon}</div>
870
- <span class="summary-cls-name">${label}</span>
871
- <div class="summary-bar-wrap">
872
- <div class="summary-bar-fill" style="width:${barPct}%;background:${meta.color}88"></div>
873
- </div>
874
- <span class="summary-count">${count}</span>
875
- </div>`;
876
- }).join('');
877
-
878
- grid.innerHTML += `
879
- <div class="summary-card">
880
- <div class="summary-card-header">
881
- <span class="summary-player-name ${nameClass}">${name}</span>
882
- <div class="summary-accuracy">
883
- <span class="summary-accuracy-num">${stats.accuracy}%</span>
884
- <span class="summary-accuracy-label">accuracy</span>
885
- </div>
886
- </div>
887
- <div class="summary-rows">${rows}</div>
888
- </div>`;
889
- }
890
- }
891
-
892
- // ── Screen switching ───────────────────────────────────────────────────
893
- function showScreen(id) {
894
- document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
895
- document.getElementById(id).classList.add('active');
896
- }
897
-
898
- // ── FEN parser ─────────────────────────────────────────────────────────
899
- function parseFEN(fen) {
900
- const rows = fen.split(' ')[0].split('/');
901
- const board = {};
902
- for (let r = 0; r < 8; r++) {
903
- const rank = 8 - r;
904
- let file = 0;
905
- for (const ch of rows[r]) {
906
- if (ch >= '1' && ch <= '8') { file += +ch; }
907
- else { board['abcdefgh'[file] + rank] = ch; file++; }
908
- }
909
- }
910
- return board;
911
- }
912
-
913
- // ── Board renderer ─────────────────────────────────────────────────────
914
- function renderBoard(fen, fromSq, toSq, cls) {
915
- const pieces = parseFEN(fen);
916
- const boardEl = document.getElementById('board');
917
- boardEl.innerHTML = '';
918
-
919
- const rankOrder = flipped ? [1,2,3,4,5,6,7,8] : [8,7,6,5,4,3,2,1];
920
- const fileOrder = flipped
921
- ? ['h','g','f','e','d','c','b','a']
922
- : ['a','b','c','d','e','f','g','h'];
923
-
924
- const meta = cls ? (CLS_META[cls] || null) : null;
925
-
926
- for (const rank of rankOrder) {
927
- for (let fi = 0; fi < 8; fi++) {
928
- const file = fileOrder[fi];
929
- const sq = file + rank;
930
- const isLight = (fi + rank) % 2 === 0;
931
-
932
- const div = document.createElement('div');
933
- div.className = `sq ${isLight ? 'light' : 'dark'}`;
934
-
935
- // From-square highlight
936
- if (sq === fromSq) {
937
- const ov = document.createElement('div');
938
- ov.className = 'overlay from-ov';
939
- div.appendChild(ov);
940
- }
941
-
942
- // To-square highlight + badge
943
- if (sq === toSq && meta) {
944
- const ov = document.createElement('div');
945
- ov.className = 'overlay to-ov';
946
- ov.style.background = meta.color;
947
- div.appendChild(ov);
948
-
949
- const badge = document.createElement('div');
950
- badge.className = 'cls-badge';
951
- badge.textContent = meta.icon;
952
- badge.style.background = meta.color;
953
- div.appendChild(badge);
954
- }
955
-
956
- // Piece
957
- const piece = pieces[sq];
958
- if (piece) {
959
- const span = document.createElement('span');
960
- span.className = `piece ${piece === piece.toUpperCase() ? 'wp' : 'bp'}`;
961
- span.textContent = PIECE_MAP[piece] || piece;
962
- div.appendChild(span);
963
- }
964
-
965
- boardEl.appendChild(div);
966
- }
967
- }
968
-
969
- updateCoords(rankOrder, fileOrder);
970
- }
971
-
972
- function updateCoords(rankOrder, fileOrder) {
973
- const rl = document.getElementById('rank-labels');
974
- rl.innerHTML = rankOrder.map(r =>
975
- `<div class="coord" style="width:auto; padding-right:0">${r}</div>`
976
- ).join('');
977
-
978
- const fl = document.getElementById('file-labels');
979
- fl.innerHTML = fileOrder.map(f =>
980
- `<div class="coord" style="height:auto">${f}</div>`
981
- ).join('');
982
- }
983
-
984
- // ── Timeline ───────────────────────────────────────────────────────────
985
- function buildTimeline() {
986
- const tl = document.getElementById('move-timeline');
987
- tl.innerHTML = '';
988
- gameData.moves.forEach((mv, i) => {
989
- const dot = document.createElement('div');
990
- dot.className = 'tl-dot';
991
- dot.style.background = (CLS_META[mv.classification] || {color:'#555'}).color;
992
- dot.title = `${mv.move_number}${mv.color==='white'?'.':'...'} ${mv.san} — ${mv.classification}`;
993
- dot.addEventListener('click', () => navigateTo(i));
994
- tl.appendChild(dot);
995
- });
996
- }
997
-
998
- function refreshTimeline() {
999
- const dots = document.querySelectorAll('.tl-dot');
1000
- dots.forEach((d, i) => {
1001
- d.classList.toggle('tl-active', i === curIdx);
1002
- });
1003
- // Scroll active dot into view
1004
- if (curIdx >= 0 && dots[curIdx]) {
1005
- dots[curIdx].scrollIntoView({ behavior: 'smooth', inline: 'nearest', block: 'nearest' });
1006
- }
1007
- }
1008
-
1009
- // ── Info box ───────────────────────────────────────────────────────────
1010
- function renderStartInfo() {
1011
- document.getElementById('info-box').innerHTML = `
1012
- <div class="info-start">
1013
- <p>Press <strong>→</strong> or the right arrow key to step through the game.</p>
1014
- </div>`;
1015
- }
1016
-
1017
- function renderMoveInfo(mv) {
1018
- const meta = CLS_META[mv.classification] || { color:'#888', icon:'?', label: mv.classification };
1019
- const ep = mv.ep_loss > 0.001
1020
- ? `−${(mv.ep_loss * 100).toFixed(1)}% EP`
1021
- : 'No EP lost';
1022
-
1023
- const moveLabel = mv.color === 'white'
1024
- ? `${mv.move_number}. ${mv.san}`
1025
- : `${mv.move_number}… ${mv.san}`;
1026
-
1027
- const bestHtml = mv.best_move_san
1028
- ? `<div class="info-row best-row">
1029
- <span class="info-label">Best move</span>
1030
- <span class="info-val mono">${mv.color === 'white' ? mv.move_number + '.' : mv.move_number + '…'} ${mv.best_move_san}</span>
1031
- </div>`
1032
- : `<div class="info-row best-row found">
1033
- <span class="info-label">Best move</span>
1034
- <span class="info-val found">✓ You found the best move</span>
1035
- </div>`;
1036
-
1037
- const openingHtml = mv.opening_name
1038
- ? `<div class="info-row">
1039
- <span class="info-label">Opening</span>
1040
- <span class="info-val">${mv.opening_eco ? '<span style="font-family:monospace;color:var(--gold-dim);font-size:12px;margin-right:6px">' + mv.opening_eco + '</span>' : ''}${mv.opening_name}</span>
1041
- </div>`
1042
- : '';
1043
-
1044
- const contHtml = mv.continuation_fmt
1045
- ? `<div class="info-row cont-row">
1046
- <span class="info-label">Continuation</span>
1047
- <span class="info-val mono cont-text">${mv.continuation_fmt}</span>
1048
- </div>`
1049
- : '';
1050
-
1051
- document.getElementById('info-box').innerHTML = `
1052
- <div class="info-header">
1053
- <div class="cls-pill" style="background:${meta.color}18; border-color:${meta.color}55; color:${meta.color}">
1054
- <span class="cls-icon">${meta.icon}</span>
1055
- <span class="cls-label">${meta.label.toUpperCase()}</span>
1056
- </div>
1057
- <div class="move-name">${moveLabel}</div>
1058
- <div class="ep-badge">${ep}</div>
1059
- </div>
1060
- <div class="info-comment">"${mv.comment}"</div>
1061
- <div class="info-details">
1062
- ${openingHtml}
1063
- ${bestHtml}
1064
- ${contHtml}
1065
- </div>`;
1066
- }
1067
-
1068
- // ── Navigation ─────────────────────────────────────────────────────────
1069
- function navigateTo(idx) {
1070
- if (!gameData) return;
1071
- const max = gameData.moves.length - 1;
1072
- curIdx = Math.max(-1, Math.min(idx, max));
1073
-
1074
- document.getElementById('prev-btn').disabled = curIdx === -1;
1075
- document.getElementById('next-btn').disabled = curIdx === max;
1076
-
1077
- if (curIdx === -1) {
1078
- renderBoard(gameData.initial_fen, null, null, null);
1079
- document.getElementById('move-indicator').textContent = 'Start';
1080
- renderStartInfo();
1081
- updateEvalBar(0.5);
1082
- } else {
1083
- const mv = gameData.moves[curIdx];
1084
- renderBoard(mv.fen_after, mv.from_square, mv.to_square, mv.classification);
1085
- document.getElementById('move-indicator').textContent =
1086
- `Move ${curIdx + 1} / ${gameData.moves.length}`;
1087
- renderMoveInfo(mv);
1088
- updateEvalBar(getWhiteEP(mv));
1089
- }
1090
-
1091
- refreshTimeline();
1092
- }
1093
-
1094
- // ── Load completed analysis ────────────────────────────────────────────
1095
- function loadAnalysis(data) {
1096
- gameData = data;
1097
- curIdx = -1;
1098
- flipped = false;
1099
-
1100
- document.getElementById('white-name').textContent = data.white;
1101
- document.getElementById('black-name').textContent = data.black;
1102
-
1103
- buildTimeline();
1104
- navigateTo(-1);
1105
-
1106
- // Show summary first
1107
- renderSummary(data);
1108
- showScreen('s-summary');
1109
- }
1110
-
1111
- // ── Progress bar ───────────────────────────────────────────────────────
1112
- function setProgress(pct, msg) {
1113
- document.getElementById('progress-bar').style.width = `${Math.round(pct * 100)}%`;
1114
- document.getElementById('progress-msg').textContent = msg;
1115
- document.getElementById('progress-pct').textContent = `${Math.round(pct * 100)}%`;
1116
- }
1117
-
1118
- // ── Submit analysis ────────────────────────────────────────────────────
1119
- async function submitAnalysis() {
1120
- const pgn = document.getElementById('pgn').value.trim();
1121
- const depth = parseInt(document.getElementById('depth').value, 10);
1122
-
1123
- if (!pgn) { alert('Please paste a PGN to analyze.'); return; }
1124
-
1125
- showScreen('s-loading');
1126
- setProgress(0, 'Connecting to analysis engine…');
1127
-
1128
- let jobId;
1129
- try {
1130
- const res = await fetch('/api/analyze', {
1131
- method: 'POST',
1132
- headers: { 'Content-Type': 'application/json' },
1133
- body: JSON.stringify({ pgn, depth })
1134
- });
1135
- const d = await res.json();
1136
- if (d.error) throw new Error(d.error);
1137
- jobId = d.job_id;
1138
- } catch (err) {
1139
- alert('Error: ' + err.message);
1140
- showScreen('s-input');
1141
- return;
1142
- }
1143
-
1144
- const evtSrc = new EventSource(`/api/stream/${jobId}`);
1145
-
1146
- evtSrc.onmessage = (e) => {
1147
- const data = JSON.parse(e.data);
1148
- if (data.type === 'progress') {
1149
- setProgress(data.progress, data.message);
1150
- } else if (data.type === 'complete') {
1151
- evtSrc.close();
1152
- loadAnalysis(data.data);
1153
- } else if (data.type === 'error') {
1154
- evtSrc.close();
1155
- alert('Analysis error: ' + data.message);
1156
- showScreen('s-input');
1157
- }
1158
- };
1159
-
1160
- evtSrc.onerror = () => {
1161
- evtSrc.close();
1162
- alert('Connection lost. Please try again.');
1163
- showScreen('s-input');
1164
- };
1165
- }
1166
-
1167
- // ── Wire up controls ───────────────────────────────────────────────────
1168
- document.getElementById('depth').addEventListener('input', e => {
1169
- document.getElementById('depth-val').textContent = e.target.value;
1170
- });
1171
-
1172
- document.getElementById('analyze-btn').addEventListener('click', submitAnalysis);
1173
-
1174
- document.getElementById('summary-back-btn').addEventListener('click', () => {
1175
- showScreen('s-input');
1176
- });
1177
-
1178
- document.getElementById('summary-review-btn').addEventListener('click', () => {
1179
- showScreen('s-analysis');
1180
- });
1181
-
1182
- document.getElementById('back-btn').addEventListener('click', () => {
1183
- showScreen('s-summary');
1184
- });
1185
-
1186
- document.getElementById('prev-btn').addEventListener('click', () => navigateTo(curIdx - 1));
1187
- document.getElementById('next-btn').addEventListener('click', () => navigateTo(curIdx + 1));
1188
-
1189
- document.getElementById('flip-btn').addEventListener('click', () => {
1190
- flipped = !flipped;
1191
- if (!gameData) return;
1192
- if (curIdx === -1) {
1193
- renderBoard(gameData.initial_fen, null, null, null);
1194
- } else {
1195
- const mv = gameData.moves[curIdx];
1196
- renderBoard(mv.fen_after, mv.from_square, mv.to_square, mv.classification);
1197
- }
1198
- });
1199
-
1200
- document.addEventListener('keydown', (e) => {
1201
- if (!gameData) return;
1202
- const active = document.querySelector('.screen.active');
1203
- if (!active || active.id !== 's-analysis') return;
1204
- if (e.key === 'ArrowRight') { e.preventDefault(); navigateTo(curIdx + 1); }
1205
- if (e.key === 'ArrowLeft') { e.preventDefault(); navigateTo(curIdx - 1); }
1206
- });
1207
- </script>
1208
- </body>
1209
- </html>