feat: added bot, light and dark mode

This commit was merged in pull request #1.
This commit is contained in:
2026-04-22 10:22:22 +02:00
parent e83ec814d9
commit 2de003e497
36 changed files with 2047 additions and 498 deletions
@@ -7,10 +7,10 @@
.board-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
border: 2px solid #5A2C28;
border-radius: 10px 10px 0 0;
border: var(--border-width) solid var(--color-border);
border-radius: var(--border-radius-md) var(--border-radius-md) 0 0;
overflow: hidden;
background: #5A2C28;
background: var(--color-text-primary);
}
.square {
@@ -41,12 +41,13 @@
width: 28%;
height: 28%;
border-radius: 50%;
background: rgba(193, 158, 245, 0.75);
border: 2px solid #5A2C28;
background: var(--color-secondary-purple);
opacity: 0.75;
border: var(--border-width) solid var(--color-border);
}
.square.selected {
outline: 3px solid #BA6D4B;
outline: 3px solid var(--color-primary);
outline-offset: -3px;
}
@@ -55,7 +56,7 @@
width: 100%;
display: block;
object-fit: fill;
border: 2px solid #5A2C28;
border: var(--border-width) solid var(--color-border);
border-top: 0;
border-radius: 0 0 10px 10px;
border-radius: 0 0 var(--border-radius-md) var(--border-radius-md);
}
@@ -1,6 +1,6 @@
.piece {
width: clamp(50px, 11cqh, 160px);
height: clamp(50px, 11cqh, 160px);
height: clamp(40px, 8cqh, 120px);
display: block;
object-fit: contain;
pointer-events: none;
@@ -0,0 +1,63 @@
.input-card {
background: var(--color-bg-card);
border: var(--border-width) solid var(--color-border);
border-radius: var(--border-radius-md);
padding: var(--size-lg-padding);
display: flex;
flex-direction: column;
gap: var(--size-lg-gap);
}
.move-card {
padding: var(--size-sm-padding);
gap: var(--size-sm-gap);
}
.move-card input {
min-height: 45px;
padding: var(--size-md-padding);
}
.input-card label {
color: var(--color-text-primary);
font-weight: 700;
margin-bottom: var(--size-xs);
}
.input-card textarea {
resize: vertical;
height: 40px;
background-color:lightcyan;
border: 3px solid var(--color-border);
border-radius: var(--border-radius-sm);
}
.input-card input {
min-width: unset;
background-color:lightcyan;
border: 3px solid var(--color-border);
border-radius: var(--border-radius-sm);
}
.input-card .btn {
border: var(--button-border);
border-radius: var(--border-radius-sm);
background: var(--color-bg-button);
color: var(--color-text-primary);
padding: var(--button-padding);
cursor: pointer;
font-weight: 600;
transition: background-color 0.2s, color 0.2s;
}
.input-card .btn:hover {
background: var(--color-bg-button-hover);
color: var(--color-text-button-hover);
}
.hint-text {
margin: 0;
color: var(--color-text-primary);
font-size: 0.9rem;
}
@@ -0,0 +1,31 @@
<section [ngClass]="['input-card', cardClass]">
<label>{{ label }}</label>
@if (inputType === 'textarea') {
<textarea
#textareaInput
[placeholder]="placeholder"
[value]="value"
[rows]="rows"
(input)="onValueChange(textareaInput.value)"
class="form-input"
></textarea>
} @else {
<input
#textInput
type="text"
[placeholder]="placeholder"
[value]="value"
(input)="onValueChange(textInput.value)"
class="form-input"
/>
}
<button type="button" class="btn w-100" (click)="onButtonClick()">
{{ buttonLabel }}
</button>
@if (hintText) {
<p class="hint-text">{{ hintText }}</p>
}
</section>
@@ -0,0 +1,33 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-input-card',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './input-card.component.html',
styleUrl: './input-card.component.css',
})
export class InputCardComponent {
@Input() label: string = '';
@Input() placeholder: string = '';
@Input() buttonLabel: string = 'Send';
@Input() inputType: 'input' | 'textarea' = 'input';
@Input() value: string = '';
@Input() cardClass: string = '';
@Input() hintText: string = '';
@Input() rows: number = 4;
@Output() valueChange = new EventEmitter<string>();
@Output() buttonClick = new EventEmitter<void>();
onValueChange(newValue: string): void {
this.value = newValue;
this.valueChange.emit(newValue);
}
onButtonClick(): void {
this.buttonClick.emit();
}
}
@@ -0,0 +1,131 @@
.promotion-dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
z-index: 1000;
&.open {
opacity: 1;
visibility: visible;
}
}
.promotion-dialog {
background: white;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 90%;
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.promotion-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #e0e0e0;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s;
&:hover {
color: #333;
}
}
}
.promotion-body {
padding: 20px;
}
.promotion-prompt {
margin: 0 0 20px 0;
text-align: center;
color: #666;
font-size: 14px;
}
.promotion-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.promotion-button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 16px;
background: #f5f5f5;
border: 2px solid #ddd;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
&:hover {
background: #e8e8e8;
border-color: #999;
transform: translateY(-2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
box-shadow: none;
}
.piece-symbol {
font-size: 32px;
line-height: 1;
}
.piece-label {
font-size: 12px;
font-weight: 500;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
}
}
@@ -0,0 +1,26 @@
<div class="promotion-dialog-overlay" [class.open]="isOpen">
<div class="promotion-dialog">
<div class="promotion-header">
<h3>Pawn Promotion</h3>
<button class="close-btn" (click)="close()" aria-label="Close">&times;</button>
</div>
<div class="promotion-body">
<p class="promotion-prompt">Choose a piece to promote your pawn to:</p>
<div class="promotion-options">
@for (piece of promotionPieces; track piece.type) {
<button
class="promotion-button"
[attr.data-piece]="piece.type"
(click)="selectPromotion(piece.type)"
[title]="piece.label"
>
<span class="piece-symbol">{{ piece.symbol }}</span>
<span class="piece-label">{{ piece.label }}</span>
</button>
}
</div>
</div>
</div>
</div>
@@ -0,0 +1,39 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
type PromotionPieceType = 'queen' | 'rook' | 'bishop' | 'knight';
interface PromotionPieceOption {
type: PromotionPieceType;
label: string;
symbol: string;
}
@Component({
selector: 'app-promotion-dialog',
standalone: true,
imports: [CommonModule],
templateUrl: './promotion-dialog.component.html',
styleUrl: './promotion-dialog.component.css'
})
export class PromotionDialogComponent {
@Input() isOpen = false;
@Output() promotionSelected = new EventEmitter<PromotionPieceType>();
@Output() closed = new EventEmitter<void>();
promotionPieces: PromotionPieceOption[] = [
{ type: 'queen', label: 'Queen', symbol: '♕' },
{ type: 'rook', label: 'Rook', symbol: '♖' },
{ type: 'bishop', label: 'Bishop', symbol: '♗' },
{ type: 'knight', label: 'Knight', symbol: '♘' }
];
selectPromotion(type: PromotionPieceType): void {
this.promotionSelected.emit(type);
this.isOpen = false;
}
close(): void {
this.closed.emit();
this.isOpen = false;
}
}