Files
buildpath/frontend/components/matchup/Section.vue
Valentin Haudiquet 8f8fc0f1af
All checks were successful
pipeline / lint-and-format (push) Successful in 4m36s
pipeline / build-and-push-images (push) Successful in 1m50s
Matchups: implemented matchups
2026-01-25 00:22:40 +01:00

491 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref } from 'vue'
import { CDRAGON_BASE } from '~/utils/cdragon'
defineProps<{
matchups?: Array<MatchupData>
championId: number
}>()
function formatWinrate(winrate: number): string {
return (winrate * 100).toFixed(1) + '%'
}
function getWinrateColor(winrate: number): string {
if (winrate > 0.52) return '#4CAF50' // Green for strong matchups
if (winrate < 0.48) return '#F44336' // Red for weak matchups
return '#FFFFFF' // White for neutral matchups
}
function getChampionImageUrl(championId: number): string {
return `${CDRAGON_BASE}plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${championId}.png`
}
// Get background color based on winrate for spectrum
function getSpectrumBackground(winrate: number): string {
if (winrate > 0.52) return 'rgba(76, 175, 80, 0.2)' // Light green
if (winrate < 0.48) return 'rgba(244, 67, 54, 0.2)' // Light red
return 'rgba(255, 255, 255, 0.1)' // Light gray
}
// Spectrum scrolling functionality
const spectrumContainer = ref<HTMLDivElement | null>(null)
function handleWheelScroll(event: WheelEvent) {
if (spectrumContainer.value) {
// Horizontal scrolling with mouse wheel
spectrumContainer.value.scrollLeft += event.deltaY
event.preventDefault()
}
}
function scrollLeft() {
if (spectrumContainer.value) {
spectrumContainer.value.scrollBy({
left: -200,
behavior: 'smooth'
})
}
}
function scrollRight() {
if (spectrumContainer.value) {
spectrumContainer.value.scrollBy({
left: 200,
behavior: 'smooth'
})
}
}
</script>
<template>
<div class="counter-section">
<div class="counter-column">
<h2 class="section-title">Strong Against</h2>
<div v-if="matchups?.length" class="counter-list">
<div
v-for="(counter, index) in matchups.slice(0, 5)"
:key="'strong-' + index"
class="counter-item"
@click="navigateTo(`/champion/${counter.championAlias.toLowerCase()}`)"
>
<NuxtImg
class="champion-icon"
:src="getChampionImageUrl(counter.championId)"
width="40"
height="40"
format="webp"
/>
<div class="counter-info">
<span class="champion-name">{{
counter.championName || `Champion ${counter.championId}`
}}</span>
<span class="winrate" :style="{ color: getWinrateColor(counter.winrate) }">
{{ formatWinrate(counter.winrate) }} winrate
</span>
</div>
<div class="game-count">{{ counter.games }} games</div>
</div>
</div>
<div v-else class="no-data">
<p>No strong counter data available</p>
</div>
</div>
<div class="counter-column">
<h2 class="section-title">Weak Against</h2>
<div v-if="matchups?.length" class="counter-list">
<div
v-for="(counter, index) in matchups.slice(matchups.length - 5, matchups.length).reverse()"
:key="'weak-' + index"
class="counter-item"
@click="navigateTo(`/champion/${counter.championAlias.toLowerCase()}`)"
>
<NuxtImg
class="champion-icon"
:src="getChampionImageUrl(counter.championId)"
width="40"
height="40"
format="webp"
/>
<div class="counter-info">
<span class="champion-name">{{
counter.championName || `Champion ${counter.championId}`
}}</span>
<span class="winrate" :style="{ color: getWinrateColor(counter.winrate) }">
{{ formatWinrate(counter.winrate) }} win rate
</span>
</div>
<div class="game-count">{{ counter.games }} games</div>
</div>
</div>
<div v-else class="no-data">
<p>No weak counter data available</p>
</div>
</div>
<!-- Matchup Spectrum - Full range from easiest to hardest -->
<div v-if="matchups?.length" class="spectrum-section">
<h3 class="spectrum-title">Matchup Spectrum</h3>
<!-- Navigation buttons - moved outside scrollable container -->
<div class="spectrum-nav spectrum-nav-left" @click="scrollLeft">
<span></span>
</div>
<div class="spectrum-nav spectrum-nav-right" @click="scrollRight">
<span></span>
</div>
<div ref="spectrumContainer" class="spectrum-container" @wheel="handleWheelScroll">
<!-- Gradient overlays for scroll indication -->
<div class="spectrum-overlay spectrum-overlay-left"></div>
<div class="spectrum-overlay spectrum-overlay-right"></div>
<div
v-for="(matchup, index) in matchups"
:key="'spectrum-' + index"
class="spectrum-item"
:style="{ backgroundColor: getSpectrumBackground(matchup.winrate) }"
>
<NuxtImg
class="spectrum-icon"
:src="getChampionImageUrl(matchup.championId)"
width="32"
height="32"
format="webp"
/>
<span class="spectrum-winrate" :style="{ color: getWinrateColor(matchup.winrate) }">
{{ formatWinrate(matchup.winrate) }}
</span>
</div>
</div>
<div class="spectrum-legend">
<span class="legend-easy">Easiest</span>
<span class="legend-hard">Hardest</span>
</div>
</div>
</div>
</template>
<style scoped>
.counter-section {
display: flex;
gap: 32px;
margin-top: 20px;
flex-wrap: wrap;
justify-content: center;
}
.counter-column {
flex: 1;
min-width: 300px;
max-width: 400px;
}
.section-title {
font-size: 1.8rem;
margin-bottom: 16px;
color: var(--color-on-surface);
text-align: center;
font-weight: 600;
}
.counter-list {
/* background-color: var(--color-surface-darker); */
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.counter-item {
padding: 12px 16px;
margin-bottom: 8px;
background-color: var(--color-surface);
border-radius: 8px;
border: 1px solid;
border-color: var(--color-on-surface);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 12px;
}
.counter-item:hover {
background-color: var(--color-surface-lighter);
transform: translateY(-2px);
}
.champion-icon {
width: 40px;
height: 40px;
border-radius: 50%;
flex-shrink: 0;
}
.counter-info {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 0;
}
.champion-name {
font-size: 1.4rem;
font-weight: 500;
color: var(--color-on-surface);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.winrate {
font-size: 1.2rem;
font-weight: 600;
}
.game-count {
font-size: 1.1rem;
color: var(--color-on-surface);
white-space: nowrap;
margin-left: auto;
}
.no-data {
text-align: center;
padding: 24px;
color: var(--color-on-surface);
background-color: var(--color-surface-darker);
border-radius: 12px;
}
/* Spectrum Section Styles */
.spectrum-section {
width: 90%;
max-width: 900px;
margin-top: 24px;
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 48px;
position: relative;
}
.spectrum-title {
font-size: 1.6rem;
color: var(--color-on-surface);
text-align: center;
font-weight: 500;
margin-bottom: 8px;
}
.spectrum-container {
display: flex;
overflow-x: auto;
gap: 8px;
padding: 8px 0;
scrollbar-width: thin;
scrollbar-color: var(--color-surface) var(--color-surface-darker);
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
position: relative;
user-select: none;
}
/* Enhanced scrollbar styling */
.spectrum-container::-webkit-scrollbar {
height: 10px;
}
.spectrum-container::-webkit-scrollbar-thumb {
background-color: var(--color-surface);
border-radius: 5px;
border: 2px solid var(--color-surface-darker);
transition: all 0.3s ease;
}
.spectrum-container::-webkit-scrollbar-thumb:hover {
background-color: var(--color-surface-lighter);
border-width: 3px;
}
.spectrum-container::-webkit-scrollbar-track {
background-color: var(--color-surface-darker);
border-radius: 5px;
}
/* Gradient overlays for scroll indication */
.spectrum-overlay {
position: absolute;
top: 0;
height: 100%;
width: 60px;
pointer-events: none;
z-index: 1;
}
.spectrum-overlay-left {
left: 0;
background: linear-gradient(to right, var(--color-surface-container) 30%, transparent 100%);
}
.spectrum-overlay-right {
right: 0;
background: linear-gradient(to left, var(--color-surface-container) 30%, transparent 100%);
}
/* Scroll navigation buttons */
.spectrum-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0;
z-index: 2;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.spectrum-nav:hover {
background-color: rgba(0, 0, 0, 0.8);
transform: translateY(-50%) scale(1.1);
}
.spectrum-nav span {
color: white;
font-size: 1.4rem;
font-weight: bold;
line-height: 1;
user-select: none;
}
.spectrum-nav-left {
left: 4px;
}
.spectrum-nav-right {
right: 4px;
}
/* Show navigation buttons on hover */
.spectrum-section:hover .spectrum-nav {
opacity: 1;
}
.spectrum-item {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 8px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
min-width: 60px;
}
.spectrum-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.spectrum-icon {
width: 32px;
height: 32px;
border-radius: 50%;
}
.spectrum-winrate {
font-size: 1rem;
font-weight: 600;
white-space: nowrap;
}
.spectrum-legend {
display: flex;
justify-content: space-between;
padding: 0 16px;
font-size: 1.1rem;
color: var(--color-on-surface);
}
.legend-easy {
color: #4caf50;
font-weight: 500;
}
.legend-hard {
color: #f44336;
font-weight: 500;
}
@media (max-width: 768px) {
.counter-section {
flex-direction: column;
gap: 16px;
}
.counter-column {
min-width: 100%;
max-width: 100%;
}
.counter-item {
flex-direction: row;
align-items: center;
gap: 12px;
padding: 8px 12px;
margin-bottom: 6px;
}
.champion-icon {
width: 28px;
height: 28px;
}
.game-count {
margin-left: 0;
font-size: 1rem;
}
.spectrum-section {
width: 100%;
max-width: none;
margin-left: 0;
margin-right: 0;
}
.spectrum-container {
padding: 8px 0;
width: 100%;
overflow-x: auto;
}
.spectrum-item {
min-width: 50px;
gap: 4px;
}
.spectrum-icon {
width: 28px;
height: 28px;
}
.spectrum-winrate {
font-size: 0.9rem;
}
.spectrum-legend {
padding: 0 8px;
}
}
</style>