8e2afb93f3
- NCWF-5: scaffold /analysis route with ChessBoard viewer and navigation - NCWF-6: FEN / PGN / Game-ID input form with depth selector - NCWF-7: extend GameApiService with analyzePosition(); add AnalysisService with game-wide annotation pipeline; proxy /api/analysis -> :8087 - NCWF-8: EvalTimelineComponent — SVG win-chance chart per ply - NCWF-9: AnnotatedMoveListComponent — quality labels (!! ! ?! ? ??) derived from win-chance delta Also fix pre-existing app.spec.ts failure (missing provideHttpClient). Apply project-wide prettier formatting pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
2.7 KiB
HTML
97 lines
2.7 KiB
HTML
<div class="moves" role="list" #movesEl>
|
|
@if (movePairs.length === 0) {
|
|
<div class="moves-empty">No moves yet.</div>
|
|
} @else {
|
|
@for (pair of movePairs; track $index) {
|
|
<div class="mv-num" role="presentation">{{ $index + 1 }}</div>
|
|
<div
|
|
class="mv"
|
|
[class.current]="isWhiteViewing($index)"
|
|
role="listitem"
|
|
tabindex="0"
|
|
(click)="clickWhite($index)"
|
|
(keydown.enter)="clickWhite($index)"
|
|
>
|
|
{{ pair.white }}
|
|
</div>
|
|
<div
|
|
class="mv"
|
|
[class.current]="isBlackViewing($index)"
|
|
[class.mv-empty]="!pair.black"
|
|
role="listitem"
|
|
[attr.tabindex]="pair.black ? 0 : null"
|
|
(click)="clickBlack($index, pair.black)"
|
|
(keydown.enter)="clickBlack($index, pair.black)"
|
|
>
|
|
{{ pair.black ?? '…' }}
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="moves-foot">
|
|
<div class="moves-nav" role="group" aria-label="Move navigation">
|
|
<button class="icon-btn" title="First move" (click)="navigate.emit('first')">
|
|
<svg
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="11 17 6 12 11 7" />
|
|
<polyline points="18 17 13 12 18 7" />
|
|
</svg>
|
|
</button>
|
|
<button class="icon-btn" title="Previous move" (click)="navigate.emit('prev')">
|
|
<svg
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="15 18 9 12 15 6" />
|
|
</svg>
|
|
</button>
|
|
<button class="icon-btn" title="Next move" (click)="navigate.emit('next')">
|
|
<svg
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="9 18 15 12 9 6" />
|
|
</svg>
|
|
</button>
|
|
<button class="icon-btn" title="Last move / return to live" (click)="navigate.emit('last')">
|
|
<svg
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="13 17 18 12 13 7" />
|
|
<polyline points="6 17 11 12 6 7" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
@if (plyCount > 0) {
|
|
<span class="live-label" [class.reviewing]="!isLive">{{ isLive ? 'LIVE' : 'REVIEWING' }}</span>
|
|
}
|
|
</div>
|