feat: stratagem icons, session summary, queue preview, UX polish

- Download 65 SVG icons from community repo (scripts/download-icons.js)
- Gold CSS filter on all icons to match game theme
- Session summary modal with score/accuracy/top stratagems
- Queue preview strip (next 3 stratagems with icons)
- Score popup animation, icon shake on wrong input
- Icons in history, leaderboard, and best-per-stratagem tables
- server.js: icon fields on all stratagems, ELO in lobby-update WS events
This commit is contained in:
Jeremy Brandenburger
2026-03-31 08:48:56 +02:00
parent 0d971745a6
commit 2d27d9fe4d
72 changed files with 2280 additions and 372 deletions
+245
View File
@@ -1658,4 +1658,249 @@ select option { background: var(--bg-surface2); }
.page-title { font-size: 1.3rem; }
.match-wins { font-size: 1.8rem; }
.match-status-text { font-size: 1.2rem; }
.stratagem-icon-lg { width: 70px; height: 70px; }
}
/* ── Stratagem icons ─────────────────────────────────────────────────────── */
.stratagem-icon-wrap {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10px;
height: 90px;
}
.stratagem-icon-lg {
width: 86px;
height: 86px;
object-fit: contain;
filter: invert(1) sepia(1) saturate(3) hue-rotate(0deg) brightness(1.1);
/* Converts monochrome SVG → gold/yellow to match theme */
opacity: 0.92;
transition: transform 0.25s ease, opacity 0.2s;
}
.stratagem-icon-lg.icon-complete {
transform: scale(1.12);
filter: invert(1) sepia(1) saturate(6) hue-rotate(0deg) brightness(1.3);
}
.stratagem-icon-lg.icon-wrong {
animation: iconShake 0.35s ease;
}
.stratagem-icon-fallback {
font-size: 3rem;
opacity: 0.25;
}
.stratagem-icon-md {
width: 40px;
height: 40px;
object-fit: contain;
filter: invert(1) sepia(1) saturate(3) hue-rotate(0deg) brightness(1.1);
opacity: 0.85;
}
.stratagem-icon-sm {
width: 22px;
height: 22px;
object-fit: contain;
filter: invert(1) sepia(1) saturate(2) hue-rotate(0deg) brightness(1);
opacity: 0.7;
vertical-align: middle;
margin-right: 5px;
}
/* Icon shake animation */
@keyframes iconShake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-5px) rotate(-3deg); }
40% { transform: translateX(5px) rotate(3deg); }
60% { transform: translateX(-4px) rotate(-2deg); }
80% { transform: translateX(4px) rotate(2deg); }
}
/* ── Stratagem queue (upcoming stratagems) ───────────────────────────────── */
.stratagem-queue {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin: 6px 0 10px;
min-height: 52px;
}
.queue-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
opacity: 0.45;
transition: opacity 0.2s;
}
.queue-item:first-child { opacity: 0.7; }
.queue-icon {
width: 36px;
height: 36px;
object-fit: contain;
filter: invert(1) sepia(1) saturate(2) hue-rotate(0deg) brightness(1);
}
.queue-icon-fallback {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
background: var(--bg-surface2);
border-radius: 6px;
}
.queue-label {
font-size: 0.6rem;
color: var(--text-muted);
max-width: 60px;
text-align: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* ── Score popup ─────────────────────────────────────────────────────────── */
.score-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: var(--font-heading);
font-size: 2.2rem;
font-weight: 700;
color: var(--accent);
text-shadow: 0 0 20px var(--accent-glow), 0 2px 8px rgba(0,0,0,0.8);
pointer-events: none;
z-index: 900;
opacity: 0;
letter-spacing: 0.05em;
}
.score-popup.show {
animation: scoreFloat 0.9s ease-out forwards;
}
@keyframes scoreFloat {
0% { opacity: 1; transform: translate(-50%, -50%) scale(0.8); }
30% { opacity: 1; transform: translate(-50%, -70%) scale(1.1); }
100% { opacity: 0; transform: translate(-50%, -120%) scale(0.9); }
}
/* ── Session summary modal ───────────────────────────────────────────────── */
.summary-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.summary-stat {
background: var(--bg-surface2);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 12px 8px;
text-align: center;
}
.summary-stat-val {
font-family: var(--font-heading);
font-size: 1.5rem;
font-weight: 700;
color: var(--accent);
}
.summary-stat-label {
font-size: 0.7rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 2px;
}
.summary-top-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
border-bottom: 1px solid var(--border-dim);
}
.summary-top-item:last-child { border-bottom: none; }
.summary-top-rank {
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--text-muted);
width: 20px;
flex-shrink: 0;
}
.summary-top-name {
flex: 1;
font-size: 0.9rem;
}
.summary-top-time {
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--accent);
}
/* ── Arrow transition polish ─────────────────────────────────────────────── */
.arrow-key {
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.12s ease;
}
.arrow-key.active {
transform: scale(1.12);
}
/* ── pw-display ──────────────────────────────────────────────────────────── */
.pw-display {
margin-top: 10px;
padding: 10px 12px;
background: var(--bg-surface2);
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--accent);
word-break: break-all;
}
/* ── Admin layout ────────────────────────────────────────────────────────── */
.admin-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.admin-user-list { display: flex; flex-direction: column; gap: 8px; }
.admin-user-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid var(--border-dim);
}
.admin-user-row:last-child { border-bottom: none; }
@media (max-width: 768px) {
.summary-grid { grid-template-columns: repeat(2, 1fr); }
.admin-layout { grid-template-columns: 1fr; }
.stratagem-icon-lg { width: 70px; height: 70px; }
.stratagem-queue { gap: 8px; }
}