diff --git a/plugins/genshin/avatar_list.py b/plugins/genshin/avatar_list.py new file mode 100644 index 00000000..ab84e4b3 --- /dev/null +++ b/plugins/genshin/avatar_list.py @@ -0,0 +1,200 @@ +"""练度统计""" +from typing import Iterable, List, Optional, Sequence + +from arkowrapper import ArkoWrapper +from enkanetwork import Assets as EnkaAssets, EnkaNetworkAPI +from genshin import Client +from genshin.models import CalculatorCharacterDetails, CalculatorTalent, Character +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputFile, Message, Update, User +from telegram.constants import ChatAction, ParseMode +from telegram.ext import CallbackContext, filters + +from core.base.assets import AssetsService +from core.baseplugin import BasePlugin +from core.config import config +from core.cookies.error import CookiesNotFoundError +from core.cookies.services import CookiesService +from core.plugin import Plugin, handler +from core.template import TemplateService +from core.user.error import UserNotFoundError +from metadata.genshin import AVATAR_DATA, NAMECARD_DATA +from modules.wiki.base import Model +from utils.decorators.error import error_callable +from utils.decorators.restricts import restricts +from utils.helpers import get_genshin_client +from utils.log import logger + + +class AvatarListPlugin(Plugin, BasePlugin): + def __init__( + self, cookies_service: CookiesService, assets_service: AssetsService, template_service: TemplateService + ) -> None: + self.cookies_service = cookies_service + self.assets_service = assets_service + self.template_service = template_service + self.enka_client = EnkaNetworkAPI(lang="chs", agent=config.enka_network_api_agent) + self.enka_assets = EnkaAssets(lang="chs") + + async def get_user_client(self, user: User, message: Message, context: CallbackContext) -> Optional[Client]: + try: + return await get_genshin_client(user.id) + except UserNotFoundError: # 若未找到账号 + if filters.ChatType.GROUPS.filter(message): + buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_uid")]] + reply_msg = await message.reply_text( + "未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", reply_markup=InlineKeyboardMarkup(buttons) + ) + self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 30) + self._add_delete_message_job(context, message.chat_id, message.message_id, 30) + else: + await message.reply_text("未查询到您所绑定的账号信息,请先私聊派蒙绑定账号") + except CookiesNotFoundError: + if filters.ChatType.GROUPS.filter(message): + buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_uid")]] + reply_msg = await message.reply_text( + "此功能需要绑定cookie后使用,请先私聊派蒙绑定账号", + reply_markup=InlineKeyboardMarkup(buttons), + parse_mode=ParseMode.HTML, + ) + self._add_delete_message_job(context, reply_msg.chat_id, reply_msg.message_id, 30) + self._add_delete_message_job(context, message.chat_id, message.message_id, 30) + else: + await message.reply_text("此功能需要绑定cookie后使用,请先私聊派蒙进行绑定", parse_mode=ParseMode.HTML) + + async def get_avatars_data(self, characters: Sequence[Character], client: Client, max_length: int = None): + avatar_datas: List[AvatarData] = [] + for num, character in enumerate(characters): + if num == max_length: # 若已经有 max_length 个角色 + break + detail = await client.get_character_details(character) + if character.id == 10000005: # 针对男草主 + talents = [] + for talent in detail.talents: + if "普通攻击" in talent.name: + talent.Config.allow_mutation = True + # noinspection Pydantic + talent.group_id = 1131 + if talent.type in ["attack", "skill", "burst"]: + talents.append(talent) + else: + talents = [t for t in detail.talents if t.type in ["attack", "skill", "burst"]] + buffed_talents = [] + for constellation in filter(lambda x: x.pos in [3, 5], character.constellations[: character.constellation]): + if result := list( + filter(lambda x: all([x.name in constellation.effect]), talents) # pylint: disable=W0640 + ): + buffed_talents.append(result[0].type) + avatar_datas.append( + AvatarData( + avatar=character, + detail=detail, + icon=(await self.assets_service.avatar(character.id).side()).as_uri(), + weapon=( + await self.assets_service.weapon(character.weapon.id).__getattr__( + "icon" if character.weapon.ascension < 2 else "awaken" + )() + ).as_uri(), + skills=[ + SkillData(skill=s, buffed=s.type in buffed_talents) + for s in sorted(talents, key=lambda x: ["attack", "skill", "burst"].index(x.type)) + ], + ) + ) + return avatar_datas + + async def get_final_data(self, client: Client, characters: Sequence[Character], update: Update): + try: + response = await self.enka_client.fetch_user(client.uid) + namecard = (await self.assets_service.namecard(response.player.namecard.id).navbar()).as_uri() + avatar = (await self.assets_service.avatar(response.player.icon.id).icon()).as_uri() + nickname = response.player.nickname + rarity = {k: v["rank"] for k, v in AVATAR_DATA.items()}[str(response.player.icon.id)] + except Exception as e: # pylint: disable=W0703 + logger.debug(f"enka 请求失败: {e}") + choices = ArkoWrapper(characters).filter(lambda x: x.friendship == 10) # 筛选出好感满了的角色 + if not choices: # 若没有满好感角色、则以好感等级排序 + choices = ArkoWrapper(characters).sort(lambda x: x.friendship, reverse=True) + namecard_choices = ( # 找到与角色对应的满好感名片ID + ArkoWrapper(choices) + .map(lambda x: next(filter(lambda y: y["name"].split(".")[0] == x.name, NAMECARD_DATA.values()), None)) + .filter(lambda x: x) + .map(lambda x: x["id"]) + ) + namecard = (await self.assets_service.namecard(namecard_choices[0]).navbar()).as_uri() + avatar = (await self.assets_service.avatar(cid := choices[0].id).icon()).as_uri() + nickname = update.effective_user.full_name + rarity = {k: v["rank"] for k, v in AVATAR_DATA.items()}[str(cid)] + return namecard, avatar, nickname, rarity + + @handler.command("avatars", filters.Regex(r"^/avatars\s*(?:(\d+)|(all))?$")) + @handler.message(filters.Regex(r"^(全部)?练度统计$")) + @restricts(30) + @error_callable + async def avatar_list(self, update: Update, context: CallbackContext): + user = update.effective_user + message = update.effective_message + + args = context.match + + all_avatars = any(["all" in args.groups(), "全部" in args.groups()]) # 是否发送全部角色 + + logger.info(f"用户 {user.full_name}[{user.id}] [bold]练度统计[/bold]: all={all_avatars}", extra={"markup": True}) + + client = await self.get_user_client(user, message, context) + if not client: + return + + notice = await message.reply_text("派蒙需要收集整理数据,还请耐心等待哦~") + await message.reply_chat_action(ChatAction.TYPING) + + characters = await client.get_genshin_characters(client.uid) + + avatar_datas: List[AvatarData] = await self.get_avatars_data(characters, client, None if all_avatars else 20) + + namecard, avatar, nickname, rarity = await self.get_final_data(client, characters, update) + + render_data = { + "uid": client.uid, # 玩家uid + "nickname": nickname, # 玩家昵称 + "avatar": avatar, # 玩家头像 + "rarity": rarity, # 玩家头像对应的角色星级 + "namecard": namecard, # 玩家名片 + "avatar_datas": avatar_datas, # 角色数据 + "has_more": len(characters) != len(avatar_datas), # 是否显示了全部角色 + } + + await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if all_avatars else ChatAction.UPLOAD_PHOTO) + + image = await self.template_service.render( + "genshin/avatar_list/main.html", + render_data, + viewport={"width": 1040, "height": 500}, + full_page=True, + query_selector=".container", + ) + self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5) + if all_avatars and len(characters) > 20: + await message.reply_document(InputFile(image, filename="练度统计.png")) + else: + await message.reply_photo(image) + + logger.info( + f"用户 {user.full_name}[{user.id}] [bold]练度统计[/bold]发送{'文件' if all_avatars else '图片'}成功", + extra={"markup": True}, + ) + + +class SkillData(Model): + """天赋数据""" + + skill: CalculatorTalent + buffed: bool = False + """是否得到了命座加成""" + + +class AvatarData(Model): + avatar: Character + detail: CalculatorCharacterDetails + icon: str + weapon: str + skills: Iterable[SkillData] diff --git a/poetry.lock b/poetry.lock index def33e8b..4ea6e876 100644 --- a/poetry.lock +++ b/poetry.lock @@ -159,7 +159,7 @@ python-versions = ">=3.5" dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "backports.zoneinfo" @@ -234,7 +234,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode-backport = ["unicodedata2"] +unicode_backport = ["unicodedata2"] [[package]] name = "click" @@ -366,7 +366,7 @@ python-versions = ">=3.7" [[package]] name = "genshin" version = "1.2.4" -description = "" +description = "An API wrapper for Genshin Impact." category = "main" optional = false python-versions = ">=3.8" @@ -386,7 +386,7 @@ geetest = ["rsa"] type = "git" url = "https://github.com/thesadru/genshin.py" reference = "HEAD" -resolved_reference = "d6aa54384cbcb260adab1b221d0f8673ac0caf4a" +resolved_reference = "7b3a4a71bfdf84d9f1bf984e91c0bcf73f9dfa7f" [[package]] name = "greenlet" @@ -960,19 +960,19 @@ aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] +mysql_connector = ["mysql-connector-python"] oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3_binary"] diff --git a/resources/genshin/avatar_list/example.html b/resources/genshin/avatar_list/example.html new file mode 100644 index 00000000..926597ab --- /dev/null +++ b/resources/genshin/avatar_list/example.html @@ -0,0 +1,144 @@ + + + + + Avatar List + + + + + +
+
+
+
avatar
+
+
+
+
Karako
+
UID: 100206192
+
+
+ +
+
+
+
#
+
角色
+
等级
+
好感
+
命座
+
普攻
+
战技
+
爆发
+
武器
+
+
+
1
+ +
+
+ side icon +
+
神里绫华
+
+ +
90
+
10
+
+
6
+
+ +
13
+
13
+
13
+ +
+
Lv.90
+
+
5
+
+
weapon
+
雾切之回光
+
+
+
+
+ 2 +
+ +
+
+ side icon +
+
刻晴
+
+ + +
90
+
10
+
+
5
+
+ +
13
+
13
+
13
+ +
+
Lv.90
+
+
5
+
+
weapon
+
黑剑
+
+
+
+
+ 3 +
+ +
+
+ side icon +
+
迪卢克
+
+ + +
90
+
10
+
+
5
+
+ +
9
+
12
+
9
+ +
+
Lv.9  
+
+
1
+
+
weapon
+
狼的末路
+
+
+
+
+ *想查看完整数据请在指令中加上all或者全部: /avatars all全部练度查询 +
+
※技能列表每 6 个小时更新一次
+
+
+
+ + \ No newline at end of file diff --git a/resources/genshin/avatar_list/main.html b/resources/genshin/avatar_list/main.html new file mode 100644 index 00000000..8b915d09 --- /dev/null +++ b/resources/genshin/avatar_list/main.html @@ -0,0 +1,157 @@ + + + + + Avatar List + + + + + +
+
+
+
avatar
+
+
+
+
{{ nickname }}
+
UID: {{ uid }}
+
+
+ +
+
+
+
#
+
角色
+
等级
+
好感
+
命座
+
普攻
+
战技
+
爆发
+
武器
+
+ {% for avatar_data in avatar_datas %} + {% set avatar = avatar_data.avatar %} + {% set weapon = avatar.weapon %} + {% set skill_datas = avatar_data.skills %} + {% set is_traveler = avatar.name == '旅行者' %} + {% if avatar.rarity == 5 %} + {% set row_bg = 'rgb(240 226 179)' %} + {% else %} + {% set row_bg = 'rgb(229 171 229/70%)' %} + {% endif %} +
+
{{ loop.index }}
+ +
+
+ side icon +
+
+ {% if is_traveler %} + {% if avatar.id == 10000007 %} + 萤 + {% else %} + 空 + {% endif %} + {% else %} + {{ avatar.name }} + {% endif %} +
+
+ +
{{ avatar.level }}
+
+ {% if is_traveler %} + / + {% else %} + {{ avatar.friendship }} + {% endif %} +
+
+
{{ constellation }}
+
+ + {% for skill_data in skill_datas %} + {% set skill = skill_data.skill %} + {% set talent_style = 'talent' %} + {% set skill_level = skill.level %} + + {% if skill_level < 4 %} + {% set talent_style = talent_style + ' talent-level-first' %} + {% endif %} + + {% if skill.max_level == skill.level %} + {% set talent_style = talent_style + ' talent-level-max' %} + {% endif %} + + {% if skill_data.buffed %} + {% set talent_style = talent_style + ' talent-buffed' %} + {% set skill_level = skill_level + 3 %} + {% endif %} + {% if skill.max_level != skill.level %} + {% if skill_level < 4 %} + {% set talent_style = talent_style + ' talent-level-1' %} + {% elif skill_level < 6 %} + {% set talent_style = talent_style + ' talent-level-2' %} + {% elif skill_level < 9 %} + {% set talent_style = talent_style + ' talent-level-3' %} + {% else %} + {% set talent_style = talent_style + ' talent-level-4' %} + {% endif %} + {% endif %} +
{{ skill_level }}
+ {% endfor %} + +
+
+ {% if weapon.level < 10 %} + Lv.{{ weapon.level }}   + {% else %} + Lv.{{ weapon.level }} + {% endif %} +
+
+
{{ weapon.refinement }}
+
+
weapon
+
{{ weapon.name }}
+
+
+ {% endfor %} +
+ {% if has_more %} +
+ *想查看完整数据请在指令中加上all或者全部: /avatars all全部练度查询 +
+ {% endif %} +
※技能列表每 6 个小时更新一次
+
+
+
+ + \ No newline at end of file diff --git a/resources/genshin/avatar_list/style.css b/resources/genshin/avatar_list/style.css new file mode 100644 index 00000000..0171681d --- /dev/null +++ b/resources/genshin/avatar_list/style.css @@ -0,0 +1,429 @@ +:root { + --white: rgb(246 248 249); + --bg-color: rgb(233 229 220); + --h-color: rgb(203 189 162); + --red: rgb(255 86 33/ 80%); + --pink: rgb(215 57 203/80%); + --purple: rgb(159 68 211/80%); + --blue: rgb(98 168 233/ 80%); + --cyan: rgb(4 150 255/80%); + --green: rgb(67 185 124/ 80%); + --grey: rgb(189 191 190); +} + +.color::before { + content: ''; + width: calc(1em + 3px); + height: calc(1em + 12px); + position: absolute; + top: 50%; + left: 50%; + z-index: 1; + transform: translateX(-50%) translateY(-50%); + border-radius: 8px; + box-shadow: 1px 1px 10px rgb(0 0 0/20%); +} + +.green::before { + background-image: linear-gradient(135deg, rgb(129, 251, 184) 10%, rgb(40, 199, 111) 100%); +} + +.cyan::before { + background-image: linear-gradient(135deg, rgb(144, 247, 236) 10%, rgb(50, 204, 188) 100%); +} + +.blue::before { + background-image: linear-gradient(135deg, rgb(171, 220, 255) 10%, rgb(3, 150, 255) 100%); +} + +.purple::before { + background-image: linear-gradient(135deg, rgb(206, 159, 252) 10%, rgb(115, 103, 240) 100%); +} + +.pink::before { + background-image: linear-gradient(135deg, rgb(246, 206, 236) 10%, rgb(217, 57, 205) 100%); +} + +.red::before { + background-image: linear-gradient(to top left, rgb(255, 8, 68) 0%, rgb(255, 177, 153) 100%); +} + +/* stylelint-disable */ +body { + margin: 0; + padding: 0; + background-color: rgb(236, 236, 236); +} + +.container { + width: 1000px; + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + padding: 20px; +} + +.container > div { + box-shadow: 1px 1px 15px rgb(0 0 0 /60%); +} + +.head { + width: 100%; + height: 150px; + margin-bottom: 40px; + background-color: rgb(236, 229, 216); + background-repeat: no-repeat; + background-size: auto calc(100% + 2px); + background-position: 0 -1px; + border-radius: 50px 100px 100px 50px; + position: relative; + display: flex; + align-items: center; + overflow: hidden; +} + +.avatar { + width: 110px; + height: 110px; + margin: 0 60px 0 70px; + filter: drop-shadow(1px 1px 10px rgb(0 0 0/50%)); +} + +.avatar > div { + width: inherit; + height: 200%; + position: absolute; + bottom: 0; + z-index: 1; + border-radius: 0 0 200px 200px; + overflow: hidden; +} + +.avatar > div::before { + content: ''; + width: calc(100% - 6px); + height: calc((100% / 2 - 6px) / 2); + position: absolute; + left: 50%; + bottom: 0; + z-index: 3; + transform: translateX(-50%); + border-radius: 0 0 200px 200px; + border-bottom: 3px solid var(--white); + border-right: 3px solid var(--white); + border-left: 3px solid var(--white); +} + +.avatar > div::after { + content: ''; + width: calc(100% - 6px); + height: calc(100% / 2 - 6px); + position: absolute; + left: 50%; + bottom: 0; + z-index: 1; + transform: translateX(-50%); + border-radius: 50%; + border-top: 3px solid var(--white); + border-right: 3px solid var(--white); + border-left: 3px solid var(--white); +} + +.avatar > div > img { + width: inherit; + position: absolute; + bottom: 0; + z-index: 2; +} + +.player { + text-shadow: 1px 1px 5px rgb(0 0 0/10%); +} + +.nickname { + font-size: 40px; + font-weight: bolder; + color: var(--white); + text-shadow: 1px 1px 10px rgb(0 0 0/30%); +} + +.uid { + font-size: 20px; + color: var(--white); + text-shadow: 1px 1px 10px rgb(0 0 0/30%); +} + +.logo { + width: 200px; + height: 100%; + margin-left: auto; + margin-right: 8%; + background-image: url("../../img/logo.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + filter: drop-shadow(5px 5px 10px rgb(0 0 0/50%)); +} + +.content { + width: 100%; + background-color: var(--white); + border-radius: 20px; + position: relative; + display: flex; + flex-flow: column; + justify-items: center; + overflow: hidden; + font-size: 21px; +} + +.row { + display: flex; + align-items: center; + width: 100%; + position: relative; + z-index: 0; +} + +.second-row::before { + content: ''; + width: 100%; + height: 100%; + position: absolute; + z-index: 0; + background-color: rgb(0 0 0/10%); +} + +.second-row > div:first-child::before, +.second-row > div:nth-child(2)::before { + content: ''; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: -1; + background-color: rgb(200 200 200 /30%); +} + +.content > .row:first-child { + background-color: rgb(204, 204, 204); + font-weight: bold; +} + +.row > div { + padding: 10px 0; + height: calc(1em + 4px); + flex: 1; + text-align: center; + border-style: solid; + border-width: 0 1px 1px 0; + border-color: rgb(208, 208, 208); + position: relative; + z-index: 1; +} + +.row > div:last-child { + border-right-width: 0; +} + +.row > div:first-child:not(.content > .row:first-child > div:first-child), +.row > div:nth-child(2):not(.content > .row:first-child > div:nth-child(2)) { + border-right-color: rgb(203, 190, 148); + border-left-color: rgba(0, 0, 0, 0); +} + +.number { + position: relative; + z-index: 2; + color: rgb(102, 102, 102); +} + +.color > .number { + color: var(--white); +} + +.role { + display: flex; + position: relative; +} + +.role-icon { + border-right-color: rgba(0, 0, 0, 0) !important; +} + +.role-icon > img { + height: calc(100% + 10px); + position: absolute; + left: 15px; + bottom: 4px; + filter: drop-shadow(0 0 2px rgb(0 0 0/50%)); +} + +.role-name { + flex: 2.5 !important; + text-align: left !important; +} + +.weapon { + position: relative; + display: inline-flex; + justify-content: center; + text-align: left !important; +} + +.weapon > div { + position: relative; + z-index: 2; +} + +.weapon > div:has(.number) { + margin: 0 10px !important; +} + +.weapon > div:first-child { + width: 80px; + text-align: right; +} + +.weapon > div:last-child { + width: 140px; +} + +.weapon > div:has(img) { + filter: drop-shadow(1px 1px 2px rgb(0 0 0/80%)); +} + +.weapon > div > img { + height: 40px; + position: relative; + bottom: 8px; +} + +.weapon-1-star { + background-color: rgb(250 250 250); + box-shadow: inset 0 0 10px 2px rgb(220 220 220); +} + +.weapon-2-star { + background-color: rgb(250 250 250); + box-shadow: inset 0 0 10px 2px rgb(195, 237, 183); +} + +.weapon-3-star { + background-color: rgb(228, 237, 252); + box-shadow: inset 0 0 10px 2px rgb(183, 190, 237); +} + +.weapon-4-star { + background-color: rgb(250, 228, 241); + box-shadow: inset 0 0 10px 2px rgb(233, 182, 221); +} + +.weapon-5-star { + background-color: rgb(255, 246, 221); + box-shadow: inset 0 0 10px 2px rgb(239, 215, 153); +} + +.full-friendship { + color: var(--white); + background-size: 85%; + background-repeat: no-repeat; + background-position: center center; + background-image: url(data:image/svg+xml;base64,PHN2ZyB0PSIxNjY1NzE2OTc4NTM2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI3MTEiCiAgICAgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPgogICAgPHBhdGggZD0iTTUzMy41MDQgMjY4LjI4OHEzMy43OTItNDEuOTg0IDcxLjY4LTc1Ljc3NiAzMi43NjgtMjcuNjQ4IDc0LjI0LTUwLjE3NnQ4Ni41MjgtMTkuNDU2cTYzLjQ4OCA1LjEyIDEwNS45ODQgMzAuMjA4dDY3LjU4NCA2My40ODggMzQuMzA0IDg3LjA0IDYuMTQ0IDk5Ljg0LTE3LjkyIDk3Ljc5Mi0zNi44NjQgODcuMDQtNDguNjQgNzQuNzUyLTUzLjI0OCA2MS45NTJxLTQwLjk2IDQxLjk4NC04NS41MDQgNzguMzM2dC04NC45OTIgNjIuNDY0LTczLjcyOCA0MS40NzItNTEuNzEyIDE1LjM2cS0yMC40OCAxLjAyNC01Mi4yMjQtMTQuMzM2dC02OS42MzItNDEuNDcyLTc5Ljg3Mi02MS45NTItODIuOTQ0LTc1Ljc3NnEtMjYuNjI0LTI1LjYtNTcuMzQ0LTU5LjM5MnQtNTcuODU2LTc0LjI0LTQ2LjU5Mi04Ny41NTItMjEuNTA0LTEwMC4zNTIgMTEuMjY0LTk5Ljg0IDM5LjkzNi04My40NTYgNjUuNTM2LTYxLjk1MiA4OC4wNjQtMzUuMzI4cTI0LjU3Ni01LjEyIDQ5LjE1Mi0xLjUzNnQ0OC4xMjggMTIuMjg4IDQ1LjA1NiAyMi4wMTYgNDAuOTYgMjcuNjQ4cTQ1LjA1NiAzMy43OTIgODYuMDE2IDgwLjg5NnoiCiAgICAgICAgICBwLWlkPSIyNzEyIiBmaWxsPSIjZGUyOTEwIj48L3BhdGg+Cjwvc3ZnPg==); + filter: drop-shadow(1px 1px 5px rgb(0 0 0/20%)); +} + +.talent { + position: absolute; + background-size: contain, 1.6em; + background-repeat: no-repeat; + background-position: center center; + text-shadow: 1px 1px 2px rgb(0 0 0 /20%); + z-index: -1 !important; + border-right-width: 0 !important; + border-left-width: 0 !important; +} + +.talent-buffed { + font-weight: bold; +} + +.talent-level-first { + background-color: rgb(189, 191, 190) !important; +} + +.talent-level-1 { + background-color: rgb(189, 191, 190); +} + +.talent-level-first.talent-level-2.talent-buffed { + color: rgb(0, 108, 199); +} + +.talent-level-2 { + background-color: var(--green); +} + +.talent-level-3 { + background-color: var(--blue); +} + +.talent-level-4 { + background-color: rgb(190, 160, 250); +} + +.talent-level-max { + background-image: linear-gradient(90deg, rgba(251, 129, 124, 0.8) 0%, rgba(255, 93, 85, 0.65) 50%, rgba(251, 129, 124, 0.8) 100%), url("../../img/crown.png") !important; +} + +.talent-level-1.talent-buffed { + color: rgb(0, 108, 199); +} + +.talent-level-2.talent-buffed { + color: rgb(0, 88, 0); +} + +.talent-level-3.talent-buffed { + color: rgb(0, 108, 199); +} + +.talent-level-4.talent-buffed { + color: rgb(114, 4, 101); +} + +.talent-level-max.talent-buffed { + color: rgb(183, 0, 0) !important; + text-shadow: 0 0 4px white !important; +} + +.content > .row:nth-last-child(2) > div { + border-bottom-width: 0 !important; +} + +.notice { + padding: 5px 0; + font-size: 14px; + font-style: italic; + background-color: rgb(204, 204, 204); + display: inline-flex; +} + +.notice > div { + padding: 5px 20px; +} + +.notice > div:last-child { + text-align: right; + margin-left: auto; +} + +code { + padding: 5px; + background-color: rgb(0 0 0/10%); + border-radius: 5px; +} + +/* stylelint-enable */ \ No newline at end of file diff --git a/resources/img/crown.png b/resources/img/crown.png new file mode 100644 index 00000000..cdc082ef Binary files /dev/null and b/resources/img/crown.png differ diff --git a/resources/img/logo.png b/resources/img/logo.png new file mode 100644 index 00000000..3e0bce0a Binary files /dev/null and b/resources/img/logo.png differ