207 lines
7.9 KiB
HTML
207 lines
7.9 KiB
HTML
<app-promotion-dialog
|
|
[isOpen]="facade.isPromotionDialogOpen"
|
|
(promotionSelected)="facade.onPromotionSelected($event)"
|
|
(closed)="facade.onPromotionClosed()" />
|
|
|
|
<div class="game-shell">
|
|
<div class="page">
|
|
|
|
<!-- Breadcrumb -->
|
|
<nav class="crumb" aria-label="Breadcrumb">
|
|
<a routerLink="/" class="crumb-link">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="15 18 9 12 15 6"/>
|
|
</svg>
|
|
Back to lobby
|
|
</a>
|
|
<span class="crumb-sep">/</span>
|
|
<span class="crumb-current">1 vs 1 Game</span>
|
|
</nav>
|
|
|
|
<!-- Game header -->
|
|
<header class="game-header">
|
|
<div class="game-title">
|
|
<h1>
|
|
1 vs 1 Game
|
|
@if (facade.game) {
|
|
<span class="tag-rated">Live</span>
|
|
}
|
|
</h1>
|
|
<div class="game-meta-strip">
|
|
<span class="game-id">
|
|
ID <strong>{{ facade.gameId }}</strong>
|
|
<button class="copy-btn" type="button" title="Copy game ID" (click)="copyGameId()">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
</svg>
|
|
</button>
|
|
</span>
|
|
@if (facade.state) {
|
|
<span class="meta-dot"></span>
|
|
<span>Move {{ moveNumber }}</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="header-actions">
|
|
<button class="btn-ghost" type="button" (click)="flipBoard()">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="17 1 21 5 17 9"/>
|
|
<path d="M3 11V9a4 4 0 0 1 4-4h14"/>
|
|
<polyline points="7 23 3 19 7 15"/>
|
|
<path d="M21 13v2a4 4 0 0 1-4 4H3"/>
|
|
</svg>
|
|
Flip
|
|
</button>
|
|
<button class="btn" type="button" (click)="copyUrl()">Share</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Loading / error states -->
|
|
@if (facade.loading) {
|
|
<div class="state-message">
|
|
<span class="status-pulse"></span>
|
|
Loading game…
|
|
</div>
|
|
} @else if (facade.state) {
|
|
@if (facade.errorMessage) {
|
|
<div class="state-message state-error">{{ facade.errorMessage }}</div>
|
|
}
|
|
|
|
<!-- Game completed banner -->
|
|
@if (facade.isGameFinished && facade.gameCompletionMessage) {
|
|
<div class="completion-banner">
|
|
<span class="completion-title">{{ facade.gameCompletionMessage }}</span>
|
|
<a routerLink="/" class="completion-link">Start new game</a>
|
|
</div>
|
|
}
|
|
|
|
<!-- Main layout -->
|
|
<div class="layout">
|
|
|
|
<!-- BOARD COLUMN -->
|
|
<div class="board-col">
|
|
|
|
<!-- Opponent (top) -->
|
|
<app-player-card
|
|
[name]="blackPlayerName"
|
|
[initial]="blackPlayerInitial"
|
|
color="black"
|
|
[isActive]="!flipped ? facade.state.turn === 'black' : facade.state.turn === 'white'"
|
|
[clockDisplay]="!flipped ? blackClock : whiteClock"
|
|
[isLowTime]="!flipped ? isLowTimeBlack : isLowTimeWhite" />
|
|
|
|
<!-- Status strip -->
|
|
<div class="status-strip">
|
|
<div class="status-left">
|
|
<span class="status-pulse"></span>
|
|
<span class="status-text" [innerHTML]="statusMessage"></span>
|
|
</div>
|
|
<span class="status-side">
|
|
{{ facade.state.turn === 'white' ? 'WHITE' : 'BLACK' }} TO MOVE
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Board -->
|
|
<div class="board-wrap">
|
|
<app-chess-board
|
|
[fen]="facade.state.fen"
|
|
[selectedSquare]="facade.selectedSquare"
|
|
[highlightedSquares]="facade.highlightedSquares"
|
|
[boardTheme]="boardTheme"
|
|
(squareSelected)="facade.onBoardSquareSelected($event)" />
|
|
</div>
|
|
|
|
<!-- Current player (bottom) -->
|
|
<app-player-card
|
|
[name]="whitePlayerName"
|
|
[initial]="whitePlayerInitial"
|
|
color="white"
|
|
[isActive]="!flipped ? facade.state.turn === 'white' : facade.state.turn === 'black'"
|
|
[clockDisplay]="!flipped ? whiteClock : blackClock"
|
|
[isLowTime]="!flipped ? isLowTimeWhite : isLowTimeBlack" />
|
|
|
|
<!-- Board action buttons -->
|
|
<app-board-actions-bar
|
|
[undoAvailable]="facade.state.undoAvailable"
|
|
[isGameFinished]="facade.isGameFinished"
|
|
(takeback)="onTakeback()"
|
|
(offerDraw)="onOfferDraw()"
|
|
(resign)="onResign()" />
|
|
|
|
</div>
|
|
|
|
<!-- SIDE COLUMN -->
|
|
<aside class="side">
|
|
|
|
<!-- Move history (collapsible) -->
|
|
<details class="side-card" open>
|
|
<summary class="side-card-summary">
|
|
<span class="side-card-title">Move History</span>
|
|
<span class="side-card-meta">{{ facade.state.moves.length }} plies</span>
|
|
<svg class="chev" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="6 9 12 15 18 9"/>
|
|
</svg>
|
|
</summary>
|
|
<app-move-history
|
|
[moves]="facade.state.moves"
|
|
(navigate)="onMoveNavigate($event)" />
|
|
</details>
|
|
|
|
<!-- Play move (collapsible) -->
|
|
<details class="side-card" open>
|
|
<summary class="side-card-summary">
|
|
<span class="side-card-title">Play Move</span>
|
|
<svg class="chev" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="6 9 12 15 18 9"/>
|
|
</svg>
|
|
</summary>
|
|
<div class="side-card-body">
|
|
<div class="uci-row">
|
|
<input
|
|
id="uci-input"
|
|
type="text"
|
|
class="uci-input"
|
|
placeholder="e.g. e2e4"
|
|
autocomplete="off"
|
|
[value]="facade.moveInput"
|
|
(input)="facade.moveInput = $any($event.target).value"
|
|
(keydown.enter)="facade.submitMove()" />
|
|
<button class="btn btn-primary" type="button" (click)="facade.submitMove()">Send</button>
|
|
</div>
|
|
<p class="uci-hint">Click a piece on the board to see legal targets.</p>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Board design (collapsible) -->
|
|
<details class="side-card">
|
|
<summary class="side-card-summary">
|
|
<span class="side-card-title">Board Design</span>
|
|
<svg class="chev" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="6 9 12 15 18 9"/>
|
|
</svg>
|
|
</summary>
|
|
<div class="side-card-body">
|
|
<div class="seg" role="tablist" aria-label="Board theme">
|
|
<button class="seg-btn" [class.active]="boardTheme === 'arabian'" role="tab" (click)="setBoardTheme('arabian')">Arabian</button>
|
|
<button class="seg-btn" [class.active]="boardTheme === 'classic'" role="tab" (click)="setBoardTheme('classic')">Classic</button>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<!-- Export (collapsible) -->
|
|
<app-export-panel [fen]="facade.state.fen" [pgn]="facade.state.pgn" />
|
|
|
|
</aside>
|
|
</div>
|
|
}
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast notification -->
|
|
@if (toastMessage) {
|
|
<div class="toast show">{{ toastMessage }}</div>
|
|
}
|