Files
HIPCTF2/frontend/src/app/features/challenges/challenge-card.component.ts
T
2026-07-24 12:58:34 +02:00

113 lines
3.3 KiB
TypeScript

import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BoardCard } from './challenges.pure';
import { CategoryIconComponent } from '../../shared/components/category-icon/category-icon.component';
@Component({
selector: 'app-challenge-card',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, CategoryIconComponent],
styles: [`
:host { display: block; }
.card {
display: flex; flex-direction: column; gap: 8px;
padding: 12px 14px; border: 2px solid #1a1a24;
background: #000000;
cursor: pointer; transition: all 0.15s ease-in-out;
min-height: 90px;
text-align: left;
color: inherit;
font: inherit;
width: 100%;
}
.card:hover {
border-color: var(--neon-purple);
box-shadow: 0 0 12px var(--neon-purple-glow);
transform: translateY(-1px);
}
.card.solved {
border-color: var(--neon-green);
}
.card-head { display: flex; align-items: center; gap: 10px; }
.name {
font-family: var(--font-mono);
font-weight: 800;
font-size: 0.95rem;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #ffffff;
text-transform: uppercase;
}
.check {
color: var(--neon-green);
font-size: 16px;
font-weight: bold;
}
.meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
font-family: var(--font-mono);
padding-top: 4px;
border-top: 1px solid #111116;
}
.points {
font-weight: 800;
color: #ffffff;
}
.solves-count {
color: #777788;
}
.diff {
font-weight: 800;
text-transform: uppercase;
font-size: 10px;
letter-spacing: 0.5px;
}
.diff-LOW { color: var(--neon-green); }
.diff-MEDIUM { color: var(--neon-yellow); }
.diff-HIGH { color: var(--neon-red); }
`],
template: `
<button
type="button"
class="card"
[class.solved]="card.solvedByMe"
[attr.data-testid]="'challenge-card-' + card.id"
[attr.data-solved]="card.solvedByMe ? 'true' : 'false'"
[attr.aria-label]="'Challenge ' + card.name + (card.solvedByMe ? ', solved' : ', not solved')"
[attr.aria-pressed]="card.solvedByMe"
(click)="open.emit(card.id)"
>
<div class="card-head">
<app-category-icon
[category]="card.categoryAbbreviation"
[size]="20"
[color]="card.solvedByMe ? 'var(--neon-green)' : 'var(--neon-purple)'"
/>
<span class="name">{{ card.name }}</span>
<span
*ngIf="card.solvedByMe"
class="check"
[attr.data-testid]="'challenge-check-' + card.id"
aria-hidden="true"
>✓</span>
</div>
<div class="meta">
<span class="diff" [class]="'diff-' + card.difficulty">{{ card.difficulty }}</span>
<span class="points" [attr.data-testid]="'points-' + card.id">{{ card.livePoints }} PTS</span>
<span class="solves-count">{{ card.solveCount }} 👥</span>
</div>
</button>
`,
})
export class ChallengeCardComponent {
@Input({ required: true }) card!: BoardCard;
@Output() open = new EventEmitter<string>();
}