업무 중일 때랑 주말, 퇴근 후 짬짬히 프론트엔드 작업을 계속 진행했다. 슬슬 코드가 더러워지기 시작하는데 모듈화할 것은 모듈화 하고 간편화하는 작업을 진행하는 시간도 가져야겠다.
일단 첫번째 페이지는 기존 전적사이트의 틀을 그대로 따라갈 것이다. 왼쪽에 티어와 랭크 승률등을 기록하는 컴포넌트까지 구현을 완료한 상황에서 그 아래에 사용했던 챔피언 목록들에 대한 간단한 정보를 보여주는 컴포넌트를 구현했다.
이런식으로 나타나며 championUsedInfo라는 컴포넌트로 따로 만들어서 import해서 화면단에 띄우기로 한다. mounted시에 바로 axios GET을 통해 업데이트를 했다면 데이터베이스에 들어 있을 참가자 데이터를 조회해서 데잍터를 가져온다.
// 서버에 Get 요청 //
mounted() {
axiosInstance
.get("http://localhost:8000/api/championused/", {
params: {
userId: this.userId,
},
})
.then((res) => {
const data = JSON.parse(res.data);
this.$store.commit("setChampionUsed", data);
console.log(this.championUsed);
})
.catch((err) => {
console.log(err);
});
},
class ChampionUsedView(APIView):
def get(self, request):
userId = request.query_params.get('userId')
user_instance = User.objects.get(username=userId)
ra = RiotAPI(RIOTAPIKEY)
try:
# 이미 존재하는 레코드를 가져옵니다.
entry = UserRiot.objects.get(user=user_instance)
puuid = entry.riot_puuid
except UserRiot.DoesNotExist:
return Response({'error': '소환사 정보가 구축 되지 않았습니다.'}, status=status.HTTP_400_BAD_REQUEST)
else:
par_entry = Participant.objects.filter(puuid=puuid)
result = (
par_entry
.values('championId')
.annotate(
wins = Count('pk', filter=Q(win=True)),
losses = Count('pk', filter=Q(win=False)),
avg_kda = Avg('kda'),
kills = Sum('kills'),
assists = Sum('assists'),
deaths = Sum('deaths'),
)
)
response = {}
champion_name = load_json_file('./champion_name.json')
for item in result:
championId = item['championId']
wins = item['wins']
losses = item['losses']
avg_kda = round((item['kills'] + item['assists']) / item['deaths'], 2) if item['deaths'] > 0 else 0
total = wins + losses
win_rate = round(wins / (wins + losses) * 100, 1) if wins + losses > 0 else 0
name = champion_name[str(championId)]
response[championId] = {
'wins': wins,
'losses': losses,
'win_rate': win_rate,
'avg_kda': avg_kda,
'total': total,
'name': name
}
json_data = json.dumps(response, ensure_ascii=False)
return JsonResponse(json_data, status=status.HTTP_200_OK, safe=False)
Get요청시에는 userId를 param에 담아 전달해야하며, 원하는 데이터만 데이터베이스에서 뽑기 위해서 result 변수에 .value()로 하여금 championId로 그룹바이를 하여 .annotate()로 보낼 필드들을 생성했다. django model에서는 데이터베이스에서 원하는 데이터만 뽑기 위해 여러 메서드들이 존재한다. Q, Sum, Count, Avg등을 사용하여 result 쿼리셋을 만들어주고 for문을 통해 각 챔피언 별로 데이터를 빈 딕셔너리에 추가하여 json으로 인코딩하여 프론트로 보내준다. name의 경우 한글 닉네임을 추가하기 위해 한글 이름 정보가 저장된(라이엇에서 제공하는) json파일을 다운받아 import해서 활용했다.
프론트엔드에서 잘 추가되는 모습이고 이를 for문을 통해 반복해서 보여주기만 하면 된다. 그 전에 디자인을 짜야한다.
<template>
<v-card color="#1A1627" rounded="LG"
><v-card-subtitle class="text-white pa-2 pb-0 pl-3"
>Your Champion</v-card-subtitle
>
<v-card
v-for="(champion, index) in championUsed.slice(0, 6)"
:key="index"
color="#1A1627"
class="pa-2 box glow"
>
<v-row>
<v-col cols="3">
<v-img
class="custom-cropped round ml-4 mt-2"
:src="`https://raw.communitydragon.org/14.5/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${champion[0]}.png`"
>
</v-img>
</v-col>
<v-col cols="5" class="pt-4" :style="{ 'font-weight': 'bold' }">
{{ champion[1].name }}
<br />
<span :style="getKDAStyle(champion[1].avg_kda, champion[1].total)">
{{ champion[1].avg_kda }} KDA
</span>
</v-col>
<v-col cols="4" class="pt-4">
<span
:style="getWinRateStyle(champion[1].win_rate, champion[1].total)"
>
{{ champion[1].win_rate }} %
</span>
<br class=/>
<span :style="{ fontWeight: 'bold' }">
{{ champion[1].total }} 게임
</span>
</v-col>
</v-row>
</v-card>
<v-expansion-panels color="#1A1627">
<v-expansion-panel title="More">
<v-expansion-panel-text>
<v-card
v-for="(champion, index) in championUsed.slice(
6,
championUsed.length
)"
:key="index"
color="#1A1627"
class="pa-2"
>
<v-row>
<v-col cols="3">
<v-img
class="custom-cropped round ml-4 mt-2"
:src="`https://raw.communitydragon.org/14.5/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${champion[0]}.png`"
>
</v-img>
</v-col>
<v-col cols="5" class="pt-4">
{{ champion[1].name }}
<br />
<span
:style="getKDAStyle(champion[1].avg_kda, champion[1].total)"
>
{{ champion[1].avg_kda }} KDA
</span>
</v-col>
<v-col cols="4" class="pt-4">
<span
:style="
getWinRateStyle(champion[1].win_rate, champion[1].total)
"
>
{{ champion[1].win_rate }} %
</span>
<br class=/>
<span :style="{ fontWeight: 'bold' }">
{{ champion[1].total }} 게임
</span>
</v-col>
</v-row>
</v-card>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card>
</template>
템플릿 디자인은 다음과 같다. 받아온 json데이터를 vuex에 잘 심어주고, 그리드 시스템에 이제 어느정도 적응이 되어 cols를 잘 나누고 class로 여백을 잘 맞추어서 주었다. v-expansion-panels을 사용해서 6개만 보여주고 나머지는 more을 눌렀을 때 보여주도록 설계 했다. 보여주기 전에 데이터를 매치수로 정렬하여 많이 플레이 한 챔피언 순으로 보여준다.
css를 승률이나 kda마다 다르게 적용하여 좋은 플레이를 보여주는 챔피언들에 대해서는 색깔로 강조했다. (기존 전적사이트들 처럼)
그리고 바로 오른쪽에는 개별 매치들을 보여준다. MVP에 해당하는 활약을 보였을 경우 그라디언트 css색상이 적용되는 경우로 디자인했다. css animation 기능을 사용해서 빛나는 느낌을 주었다. (아래 참조)
.mvp-card {
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.25) 40%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.25) 60%,
rgba(255, 255, 255, 0) 100%
),
linear-gradient(to right, #5a4cbb, #5a84a2, #8f9dbe, #a199bd, #ab81bf);
background-repeat: repeat-y;
background-size: 200px 120px, auto;
background-position: 0, 0 0;
animation: glow 3.5s infinite;
}
@keyframes glow {
to {
background-position: 100% 0, 0 0;
}
}
카드 디자인은 세 가지로 구분했다. 기본 디자인을 유지한 채 승리 시에는 블루 계열의 카드로, 패배시에는 레드 계열 카드 안에 들어가는 img들은 ddragon.json파일을 파싱해서 가져온다. 그리고 expansion기능을 추가해서 아래를 열었을 때 상세한 정보들을 띄울 예정이다.
전체코드는 다음과 같다.
<template>
<v-main class="main-container">
<v-container>
<v-row>
<v-col>
<v-card variant="elevated" color="#26293C">
<v-row>
<v-col cols="4">
<ProfileCard />
</v-col>
</v-row>
</v-card>
</v-col>
</v-row>
</v-container>
<v-container class="pt-0">
<v-card variant="elevated" color="#26293C">
<v-tabs v-model="tab" bg-color="#1A1627">
<v-tab value="one">종합</v-tab>
<v-tab value="two">챔피언</v-tab>
<v-tab value="three">상세분석</v-tab>
</v-tabs>
<v-card-text>
<v-window v-model="tab">
<v-window-item value="one">
<v-card
variant="elevated"
color="#26293C"
class="pa-0"
rounded="sm"
><v-row>
<v-col cols="4">
<v-card color="#1A1627" rounded="LG" class="pb-4 pl-3">
<v-card-subtitle class="text-white pa-2 pb-0 pl-3"
>솔로 랭크</v-card-subtitle
>
<v-container class="mx-4 pl-3">
<v-row>
<v-col cols="2" class="pa-1 pl-0 pr-3">
<v-img
class="custom-cropped"
cover
aspect-ratio="1"
:src="emblemSrc"
></v-img>
</v-col>
<v-col cols="6" class="pa-0 px-3">
<v-card color="#1A1627" class="text-h5" flat>
<span
:style="{
color: emblemFontColor,
'font-weight': 'bold',
textAlign: 'right',
}"
>
{{ this.summonerProfile.tier }}
{{ this.summonerProfile.rank }}
</span>
</v-card>
<v-card color="#1A1627" class="text-white" flat>
<span
:style="{
textAlign: 'right',
'font-weight': 'bold',
}"
>
{{ this.summonerProfile.leaguePoints }} LP
</span>
</v-card>
</v-col>
<v-col cols="4" class="pa-0">
<v-card
color="#1A1627"
class="text-white mt-1"
flat
>
<span :style="{ 'font-weight': 'bold' }">
{{ this.summonerProfile.wins }}W
{{ this.summonerProfile.losses }}L
</span>
</v-card>
<v-card
color="#1A1627"
class="text-white mt-1"
flat
:style="{ 'font-weight': 'bold' }"
>
승률 {{ this.summonerProfile.winrate }}%
</v-card>
</v-col>
</v-row>
</v-container>
</v-card>
<ChampionUsedInfo />
</v-col>
<v-col cols="8" class="pl-0">
<v-card
variant="elevated"
color="#26293C"
class="pa-0"
rounded="sm"
>
<v-row>
<v-col cols="12" class="pb-0">
<v-card
color="#1A1627"
rounded="LG"
class="pa-3"
max-height="100%"
>
<div class="image-container">
<v-img
v-for="(champion, index) in championUsed.slice(
0,
9
)"
:key="index"
:src="`https://raw.communitydragon.org/14.5/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${champion[0]}.png`"
max-width="45"
max-height="45"
min-height="30"
min-width="30"
class="custom-cropped.champion round pa-1"
>
<div class="image-overlay">
<span class="number">{{
champion[1].total
}}</span>
</div>
</v-img>
</div>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col>
<v-card
v-for="(match, index) in Object.values(
matchData
).slice(0, 10)"
:key="index"
rounded="LG"
class="pa-3 mb-1 text-white"
max-height="100%"
:class="{
'loss-card ': !match.win,
'win-card': match.win,
'mvp-card': match.mvp,
}"
>
<v-row class="pa-2">
<div
class="custom-bar"
:class="{ loss: !match.win, win: match.win }"
></div>
<v-col cols="3" class="pa-0">
<div class="pt-2 pl-3">
<h3>Ranked Solo</h3>
<h6>
{{ formatUnixTime(match.gameEndTimestamp) }}
</h6>
<p class="mt-1"></p>
<span
:class="{
'loss-font': !match.win,
'win-font': match.win,
}"
>
패배
</span>
<span style="font-size: x-small">
{{ formatUnixTime_m(match.gameLength) }}
</span>
<h5>Emerald 2</h5>
</div>
</v-col>
<v-col cols="2" class="pa-0">
<div>
<v-img
:src="`https://raw.communitydragon.org/14.5/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${match.championId}.png`"
max-width="55"
max-height="55"
min-height="30"
min-width="30"
class="custom-cropped.champion round pa-1"
@click="
filterChampionMatch(match.championId)
"
>
</v-img>
</div>
<div
class="pt-1"
style="display: flex; flex-direction: row"
>
<v-img
:src="`https://ddragon.leagueoflegends.com/cdn/14.6.1/img/spell/${
this.spell[match.summoner1Id].name
}.png`"
max-width="27.5"
max-height="27.5"
min-height="15"
min-width="15"
class="custom-cropped.champion round pl-3"
>
</v-img>
<p></p>
<v-img
:src="`https://ddragon.leagueoflegends.com/cdn/14.6.1/img/spell/${
this.spell[match.summoner2Id].name
}.png`"
max-width="27.5"
max-height="27.5"
min-height="15"
min-width="15"
class="custom-cropped.champion round pa-1"
>
</v-img>
</div>
</v-col>
<v-col cols="3" class="px-0">
<div style="text-align: center">
<h4>
{{ match.kills }} /
<span style="color: red">{{
match.deaths
}}</span>
/
{{ match.assists }}
</h4>
</div>
<div style="text-align: center">
<h6 :class="{ 'high-kda': match.kda >= 4 }">
{{ match.kda.toFixed(1) }} KDA
</h6>
</div>
</v-col>
<v-col cols="3"> </v-col>
<v-col cols="1">
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
:icon="
show
? 'mdi-chevron-up'
: 'mdi-chevron-down'
"
@click="show = !show"
></v-btn>
</v-card-actions>
</v-col>
</v-row>
<v-expand-transition>
<div v-show="show">
<p class="py-1"></p>
<v-divider class="py-1"></v-divider>
<v-card
:class="{
'loss-card': !match.win,
'win-card': match.win,
}"
class="text-grey"
flat
>
{{ match.bountyGold }}
{{ match.damagePerMinute }}
{{ match.ddpt.toFixed(5) * 100 }}
{{ match.dgpt.toFixed(5) * 100 }}
{{ match.dpg.toFixed(2) }}
{{ match.dpt.toFixed(2) }}
{{ match.goldPerMinute.toFixed(2) }}
{{ match.killParticipation.toFixed(3) * 100 }}
{{ match.killNearTurret }}
</v-card>
</div>
</v-expand-transition>
</v-card>
</v-col>
</v-row>
</v-card>
</v-col>
</v-row>
</v-card>
</v-window-item>
<v-window-item value="two"> Two </v-window-item>
<v-window-item value="three"
><DetailStats></DetailStats>
</v-window-item>
</v-window>
</v-card-text>
</v-card>
</v-container>
</v-main>
</template>
<script>
import ProfileCard from "./ProfileCard.vue";
import ChampionUsedInfo from "./ChampionUsedInfo.vue";
import DetailStats from "./DetailStats.vue";
import axiosInstance from "../setaxios.js";
import { mapState } from "vuex";
export default {
data() {
return {
show: false,
tab: null,
emblemColor: {
BRONZE: "brown", // BRONZE 티어에 대한 색상
SILVER: "silver", // SILVER 티어에 대한 색상
GOLD: "gold", // GOLD 티어에 대한 색상
PLATINUM: "cyan", // PLATINUM 티어에 대한 색상
EMERALD: "#12C3C9", // EMERALD 티어에 대한 색상
DIAMOND: "#3F80C9", // DIAMOND 티어에 대한 색상
MASTER: "purple", // MASTER 티어에 대한 색상
GRANDMASTER: "red", // GRANDMASTER 티어에 대한 색상
CHALLENGER: "orange", // CHALLENGER 티어에 대한 색상
},
emblem: {
BRONZE:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-bronze.png",
SILVER:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-silver.png",
GOLD: "https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-gold.png",
PLATINUM:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-platinum.png",
EMERALD:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-emerald.png",
DIAMOND:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-diamond.png",
MASTER:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-master.png",
GRANDMASTER:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-grandmaster.png",
CHALLENGER:
"https://raw.communitydragon.org/14.5/plugins/rcp-fe-lol-static-assets/global/default/images/ranked-emblem/emblem-challenger.png",
},
spell: {
baseUrl: "https://ddragon.leagueoflegends.com/cdn/14.6.1/img/spell/",
21: {
name: "SummonerBarrier",
name_ko: "방어막",
description:
"2초 동안 방어막으로 감싸 피해를 105~411(챔피언 레벨에 따라 변동)만큼 흡수합니다.",
},
1: {
name: "SummonerBoost",
name_ko: "정화",
description:
"챔피언에 걸린 모든 이동 불가와 (제압 및 공중으로 띄우는 효과 제외) 소환사 주문에 의한 해로운 효과를 제거하고 새로 적용되는 이동 불가 효과들의 지속시간을 3초간 65% 감소시킵니다.",
},
4: {
name: "SummonerFlash",
name_ko: "점멸",
description: "커서 방향으로 챔피언이 짧은 거리를 순간이동합니다.",
},
14: {
name: "SummonerDot",
name_ko: "점화",
description:
"적 챔피언을 불태워 5초 동안 70~410의 고정 피해(챔피언 레벨에 따라 변동)를 입히고 모습을 드러내며 치료 효과를 감소시킵니다.",
},
3: {
name: "SummonerExhaust",
name_ko: "탈진",
description:
"적 챔피언을 지치게 만들어 3초 동안 이동 속도를 30% 느리게 하고 적 챔피언이 가하는 피해량을 35% 낮춥니다.",
},
6: {
name: "SummonerHaste",
name_ko: "유체화",
description:
"챔피언이 15초 동안 유닛과 충돌하지 않게 되며 챔피언 레벨에 따라 이동 속도가 24~48% 증가합니다.",
},
7: {
name: "SummonerHeal",
name_ko: "회복",
description:
"자신과 대상 아군 챔피언의 체력을 80~318만큼 회복시키고 1초 동안 이동 속도가 30% 증가합니다. 최근 소환사 주문 회복의 영향을 받은 유닛의 경우 치유량이 절반만 적용됩니다.",
},
13: {
name: "SummonerMana",
name_ko: "총명",
description:
"최대 마나량의 50%를 회복합니다. 주변 아군도 최대 마나량의 25%가 회복됩니다.",
},
11: {
name: "SummonerSmite",
name_ko: "강타",
description: "대상 몬스터에게 600~1,200의 고정 피해를 입힙니다.",
},
12: {
name: "SummonerTeleport",
name_ko: "순간이동",
description:
"4초 동안 정신을 집중한 다음, 대상으로 지정한 아군 구조물로 순간이동합니다. 10분에 강력 순간이동으로 업그레이드됩니다. 강력 순간이동은 아군 구조물, 미니언, 혹은 와드를 대상으로 지정할 수 있습니다.",
},
},
};
},
name: "MainPage",
computed: {
...mapState([
"userId",
"userSummoner",
"summonerProfile",
"championUsed",
"matchData",
]),
emblemSrc() {
// this.summonerProfile.tier 값에 따라 해당하는 emblem 경로를 반환합니다.
return this.emblem[this.summonerProfile.tier];
},
emblemFontColor() {
return this.emblemColor[this.summonerProfile.tier];
},
},
mounted() {
axiosInstance
.get("http://localhost:8000/api/update/", {
params: {
userId: this.userId,
},
})
.then((res) => {
console.log(res.data);
this.name = res.data.name;
this.level = res.data.summonerLevel;
this.icon = res.data.profileIconId;
this.iconUrl = require("../assets/profileicon/" + this.icon + ".png");
this.tagName = this.userSummoner;
this.tier = res.data.tier;
this.rank = res.data.rank;
this.leaguePoints = res.data.leaguePoints;
this.wins = res.data.wins;
this.losses = res.data.losses;
this.winrate = (this.wins / (this.wins + this.losses)) * 100;
this.winrate = this.winrate.toFixed(1);
this.$store.commit("setSummonerProfile", {
name: this.name,
level: this.level,
icon: this.icon,
iconUrl: this.iconUrl,
tagName: this.tagName,
tier: this.tier,
rank: this.rank,
leaguePoints: this.leaguePoints,
wins: this.wins,
losses: this.losses,
winrate: this.winrate,
});
axiosInstance
.get("http://localhost:8000/api/match_individual/", {
params: {
userId: this.userId,
},
})
.then((res) => {
const data = JSON.parse(res.data);
this.$store.commit("setMatchData", data);
})
.catch((err) => {
console.log(err);
});
})
.catch((err) => {
console.log(err);
});
},
methods: {
formatUnixTime(unixTime) {
// 유닉스 시간을 초 단위로 변환
let seconds = unixTime;
// Date 객체 생성
let date = new Date(parseInt(seconds));
// 년도, 월, 일, 시간을 가져옴
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let hours = date.getHours();
let minutes = date.getMinutes();
// 두 자리로 만들기 위해 10 미만의 숫자에는 '0'을 추가
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
if (hours < 10) {
hours = "0" + hours;
}
if (minutes < 10) {
minutes = "0" + minutes;
}
// 결과 반환
return `${year}-${month}-${day} ${hours}시 ${minutes}분`;
},
formatUnixTime_m(seconds) {
let minutes = Math.floor(seconds / 60);
let remainingSeconds = Math.floor(seconds % 60);
// 결과를 문자열로 반환
return `${minutes}분 ${remainingSeconds}초`;
},
filterChampionMatch(championId) {
console.log(championId);
},
},
components: {
ProfileCard,
ChampionUsedInfo,
DetailStats,
},
};
</script>
<style scoped>
.main-container {
background-image: url("../assets/mainpage_bg.png");
}
.neon {
animation: neon 1s ease infinite;
-moz-animation: neon 1s ease infinite;
-webkit-animation: neon 1s ease infinite;
}
.custom-cropped {
object-fit: cover; /* 이미지를 부모 요소에 맞추고, 가로세로 비율을 유지한 채로 중앙에 배치합니다. */
transform: scale(3.5);
transform-origin: center; /* 변환의 기준점을 이미지의 중심으로 설정합니다. */
}
.custom-cropped.champion {
transform: scale(1);
}
.image-overlay {
position: absolute;
bottom: 8%;
left: 50%;
transform: translate(-50%, 50%);
background-color: rgba(0, 0, 0, 0.9);
color: white;
padding: 4px;
border-radius: 4px;
}
.number {
font-weight: bold;
}
.image-container {
display: flex;
flex-wrap: wrap;
}
.custom-bar {
position: absolute; /* 절대적 위치 설정 */
top: 0;
left: 0;
width: 1%; /* 카드의 3%로 설정 */
height: 100%; /* 높이를 카드의 높이와 동일하게 설정 */
}
.loss {
background-color: #f13352;
}
.win {
background-color: #3376fe;
}
.loss-font {
color: #f13352;
}
.win-font {
color: #3376fe;
}
.kda-font {
color: #3376fe;
font-weight: bold;
}
.win-card {
background-color: #1f2d55;
}
.loss-card {
background-color: #432124;
}
.mvp-card {
/* 희미한 사선 빛을 추가합니다. */
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.25) 40%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.25) 60%,
rgba(255, 255, 255, 0) 100%
),
linear-gradient(to right, #5a4cbb, #5a84a2, #8f9dbe, #a199bd, #ab81bf);
/* 첫 번째 그라디언트에 번짐 효과를 적용합니다. */
/* 배경 이미지를 반복하고, 크기와 위치를 조절합니다. */
background-repeat: repeat-y;
background-size: 200px 120px, auto;
background-position: 0, 0 0;
/* 애니메이션 효과를 추가합니다. */
animation: glow 3.5s infinite;
}
@keyframes glow {
to {
background-position: 100% 0, 0 0;
}
}
.high-kda {
color: #ba1457;
}
</style>
매치 카드를 완성하고 이제는 카드 안에 세부 데이터를 어떻게 나열할지 고민할 듯 하다.