diff --git a/core/base/assets.py b/core/base/assets.py index c60fbd93..68210b1d 100644 --- a/core/base/assets.py +++ b/core/base/assets.py @@ -457,8 +457,18 @@ class _NamecardAssets(_AssetsService): def game_name(self) -> str: return NAMECARD_DATA[str(self.id)]["icon"] + @lru_cache + def _get_id_from_avatar_id(self, avatar_id: Union[int, str]) -> int: + avatar_icon_name = AVATAR_DATA[str(avatar_id)]["icon"].replace("AvatarIcon", "NameCardIcon") + for namecard_id, namecard_data in NAMECARD_DATA.items(): + if namecard_data["icon"] == avatar_icon_name: + return int(namecard_id) + raise ValueError(avatar_id) + def __call__(self, target: int) -> "_NamecardAssets": result = _NamecardAssets(self.client) + if target > 10000000: + target = self._get_id_from_avatar_id(target) result.id = target result.enka = DEFAULT_EnkaAssets.namecards(target) return result diff --git a/metadata/scripts/metadatas.py b/metadata/scripts/metadatas.py index 46dc90e1..8f6fde64 100644 --- a/metadata/scripts/metadatas.py +++ b/metadata/scripts/metadatas.py @@ -9,7 +9,7 @@ from httpx import AsyncClient, RemoteProtocolError, Response, URL from utils.const import AMBR_HOST, PROJECT_ROOT from utils.log import logger -__all__ = ["update_metadata_from_ambr", "update_metadata_from_github", "make_github_fast"] +__all__ = ["update_metadata_from_ambr", "update_metadata_from_github", "make_github_fast", "RESOURCE_DEFAULT_PATH"] GENSHIN_PY_DATA_REPO = parse_token("aHR0cHM6Ly9naXRsYWIuY29tL0RpbWJyZWF0aC9nYW1lZGF0YS8tL3Jhdy9tYXN0ZXIv").decode() RESOURCE_REPO = "PaiGramTeam/PaiGram_Resources" RESOURCE_BRANCH = "remote" diff --git a/modules/apihelper/client/components/calendar.py b/modules/apihelper/client/components/calendar.py new file mode 100644 index 00000000..0733fb97 --- /dev/null +++ b/modules/apihelper/client/components/calendar.py @@ -0,0 +1,359 @@ +import re +from datetime import datetime, timedelta +from typing import List, Tuple, Optional, Dict, Union + +from httpx import AsyncClient + +from core.base.assets import AssetsService +from metadata.genshin import AVATAR_DATA +from metadata.scripts.metadatas import RESOURCE_DEFAULT_PATH +from metadata.shortname import roleToId +from modules.apihelper.models.genshin.calendar import Date, FinalAct, ActEnum, ActDetail, ActTime, BirthChar +from modules.wiki.character import Character + + +class Calendar: + """原神活动日历""" + + ANNOUNCEMENT_LIST = "https://hk4e-api.mihoyo.com/common/hk4e_cn/announcement/api/getAnnList" + ANNOUNCEMENT_PARAMS = { + "game": "hk4e", + "game_biz": "hk4e_cn", + "lang": "zh-cn", + "bundle_id": "hk4e_cn", + "platform": "pc", + "region": "cn_gf01", + "level": "55", + "uid": "100000000", + } + MIAO_API = "http://miaoapi.cn/api/calendar" + REMOTE_API = f"https://raw.fastgit.org/{RESOURCE_DEFAULT_PATH}calendar.json" + IGNORE_IDS = [ + 495, # 有奖问卷调查开启! + 1263, # 米游社《原神》专属工具一览 + 423, # 《原神》玩家社区一览 + 422, # 《原神》防沉迷系统说明 + 762, # 《原神》公平运营声明 + 762, # 《原神》公平运营声明 + ] + IGNORE_RE = re.compile(r"(内容专题页|版本更新说明|调研|防沉迷|米游社|专项意见|更新修复与优化|问卷调查|版本更新通知|更新时间说明|预下载功能|周边限时|周边上新|角色演示)") + FULL_TIME_RE = re.compile(r"(魔神任务)") + + def __init__(self): + self.client = AsyncClient() + self.birthday_list = self.gen_birthday_list() + + @staticmethod + def gen_birthday_list() -> Dict[str, List[str]]: + """生成生日列表""" + birthday_list = {} + for value in AVATAR_DATA.values(): + key = "_".join([str(i) for i in value["birthday"]]) + data = birthday_list.get(key, []) + data.append(value["name"]) + birthday_list[key] = data + return birthday_list + + @staticmethod + def get_now_hour() -> datetime: + """获取当前时间""" + return datetime.now().replace(minute=0, second=0, microsecond=0) + + async def req_cal_data(self) -> Tuple[List[List[ActDetail]], Dict[str, ActTime]]: + """请求日历数据""" + list_data = await self.client.get(self.ANNOUNCEMENT_LIST, params=self.ANNOUNCEMENT_PARAMS) + list_data = list_data.json() + + new_list_data = [[], []] + for idx, data in enumerate(list_data.get("data", {}).get("list", [])): + for item in data.get("list", []): + new_list_data[idx].append(ActDetail(**item)) + time_map = {} + req = await self.client.get(self.MIAO_API) + if req.status_code == 200: + miao_data = req.json() + time_map.update({key: ActTime(**value) for key, value in miao_data.get("data", {}).items()}) + req = await self.client.get(self.REMOTE_API) + if req.status_code == 200: + remote_data = req.json() + time_map.update({key: ActTime(**value) for key, value in remote_data.get("data", {}).items()}) + return new_list_data, time_map + + @staticmethod + def date_to_weekday(date_: datetime) -> str: + """日期转换为星期""" + time = ["一", "二", "三", "四", "五", "六", "日"] + return time[date_.weekday()] + + async def get_date_list(self) -> Tuple[List[Date], datetime, datetime, timedelta, float]: + """获取日历数据""" + data_list: List[Date] = [] + today = self.get_now_hour() + temp = today - timedelta(days=7) + month = 0 + date, week, is_today = [], [], [] + start_date, end_date = None, None + for i in range(13): + temp += timedelta(days=1) + m, d, w = temp.month, temp.day, self.date_to_weekday(temp) + if month == 0: + start_date = temp + month = m + if month != m and len(date) > 0: + data_list.append(Date(month=month, date=date, week=week, is_today=is_today)) + date, week, is_today = [], [], [] + month = m + date.append(d) + week.append(w) + is_today.append(temp == today) + if i == 12: + data_list.append(Date(month=month, date=date, week=week, is_today=is_today)) + end_date = temp + start_time = start_date.replace(hour=0, minute=0, second=0, microsecond=0) + end_time = end_date.replace(hour=23, minute=59, second=59, microsecond=999999) + total_range: timedelta = end_time - start_time + now_left: float = (self.get_now_hour() - start_time) / total_range * 100 + return ( + data_list, + start_time, + end_time, + total_range, + now_left, + ) + + @staticmethod + def human_read(d: timedelta) -> str: + """将日期转换为人类可读""" + hour = d.seconds // 3600 + minute = d.seconds // 60 % 60 + if minute >= 59: + hour += 1 + text = "" + if d.days: + text += f"{d.days}天" + if hour: + text += f"{hour}小时" + return text + + @staticmethod + def count_width( + act: FinalAct, + detail: Optional[ActTime], + ds: ActDetail, + start_time: datetime, + end_time: datetime, + total_range: timedelta, + ) -> Tuple[datetime, datetime]: + """计算宽度""" + + def get_date(d1: str, d2: str) -> datetime: + if d1 and len(d1) > 6: + return datetime.strptime(d1, "%Y-%m-%d %H:%M:%S") + return datetime.strptime(d2, "%Y-%m-%d %H:%M:%S") + + s_date = get_date(detail and detail.start, ds.start_time) + e_date = get_date(detail and detail.end, ds.end_time) + s_time = max(s_date, start_time) + e_time = min(e_date, end_time) + + s_range = s_time - start_time + e_range = e_time - start_time + + act.left = s_range / total_range * 100 + act.width = e_range / total_range * 100 - act.left + act.duration = (e_time - s_time).total_seconds() + act.start = s_date.strftime("%m-%d %H:%M") + act.end = e_date.strftime("%m-%d %H:%M") + return s_date, e_date + + def parse_label(self, act: FinalAct, is_act: bool, s_date: datetime, e_date: datetime) -> None: + """解析活动标签""" + now = self.get_now_hour() + label = "" + if self.FULL_TIME_RE.findall(act.title) or e_date - s_date > timedelta(days=365): + label = f"{s_date.strftime('%m-%d %H:%M')} 后永久有效" if s_date < now else "永久有效" + elif s_date < now < e_date: + label = f'{e_date.strftime("%m-%d %H:%M")} ({self.human_read(e_date - now)}后结束)' + if act.width > (38 if is_act else 55): + label = f"{s_date.strftime('%m-%d %H:%M')} ~ {label}" + elif s_date > now: + label = f'{s_date.strftime("%m-%d %H:%M")} ({self.human_read(s_date - now)}后开始)' + elif is_act: + label = f"{s_date.strftime('%m-%d %H:%M')} ~ {e_date.strftime('%m-%d %H:%M')}" + act.label = label + + @staticmethod + async def parse_type(act: FinalAct, assets: AssetsService) -> None: + """解析活动类型""" + if "神铸赋形" in act.title: + act.type = ActEnum.weapon + act.title = re.sub(r"(单手剑|双手剑|长柄武器|弓|法器|·)", "", act.title) + act.sort = 2 + elif "祈愿" in act.title: + act.type = ActEnum.character + if reg_ret := re.search(r"·(.*)\(", act.title): + char_name = reg_ret[1] + char = assets.avatar(roleToId(char_name)) + act.banner = (await assets.namecard(char.id).navbar()).as_uri() + act.face = (await char.icon()).as_uri() + act.sort = 1 + elif "纪行" in act.title: + act.type = ActEnum.no_display + elif act.title == "深渊": + act.type = ActEnum.abyss + + async def get_list( + self, + ds: ActDetail, + start_time: datetime, + end_time: datetime, + total_range: timedelta, + time_map: Dict[str, ActTime], + is_act: bool, + assets: AssetsService, + ) -> Optional[FinalAct]: + """获取活动列表""" + act = FinalAct( + id=ds.ann_id, + type=ActEnum.activity if is_act else ActEnum.normal, + title=ds.title, + banner=ds.banner if is_act else "", + sort=5 if is_act else 10, + icon=ds.tag_icon, + ) + detail: Optional[ActTime] = time_map.get(str(act.id)) + + if act.id in self.IGNORE_IDS or self.IGNORE_RE.findall(act.title) or (detail and not detail.display): + return None + await self.parse_type(act, assets) + s_date, e_date = self.count_width(act, detail, ds, start_time, end_time, total_range) + self.parse_label(act, is_act, s_date, e_date) + + if s_date <= end_time and e_date >= start_time: + act.mergeStatus = 1 if act.type in {ActEnum.activity, ActEnum.normal} else 0 + return act + + @staticmethod + def get_abyss_cal(start_time: datetime, end_time: datetime) -> List[List[Union[datetime, str]]]: + """获取深渊日历""" + last = datetime.now().replace(day=1) - timedelta(days=2) + last_month = last.month + curr = datetime.now() + curr_month = curr.month + next_date = last + timedelta(days=40) + next_month = next_date.month + + def start(date: datetime, up: bool = False): + return date.replace(day=1 if up else 16, hour=4, minute=0, second=0, microsecond=0) + + def end(date: datetime, up: bool = False): + return date.replace(day=1 if up else 16, hour=3, minute=59, second=59, microsecond=999999) + + check = [ + [start(last, False), end(last, True), f"{last_month}月下半"], + [start(curr, True), end(curr, False), f"{curr_month}月上半"], + [start(curr, False), end(next_date, True), f"{curr_month}月下半"], + [start(next_date, True), end(next_date, False), f"{next_month}月上半"], + ] + ret = [] + for ds in check: + s, e, _ = ds + if (s <= start_time <= e) or (s <= end_time <= e): + ret.append(ds) + return ret + + async def get_birthday_char( + self, date_list: List[Date], assets: AssetsService + ) -> Tuple[int, Dict[str, Dict[str, List[BirthChar]]]]: + """获取生日角色""" + birthday_char_line = 0 + birthday_chars = {} + for date in date_list: + birthday_chars[str(date.month)] = {} + for d in date.date: + key = f"{date.month}_{d}" + if char := self.birthday_list.get(key): + birthday_char_line = max(len(char), birthday_char_line) + birthday_chars[str(date.month)][str(d)] = [] + for c in char: + character = await Character.get_by_name(c) + birthday_chars[str(date.month)][str(d)].append( + BirthChar( + name=c, + star=character.rarity, + icon=(await assets.avatar(roleToId(c)).icon()).as_uri(), + ) + ) + return birthday_char_line, birthday_chars + + @staticmethod + def get_merge_next(target: List[FinalAct], li: FinalAct) -> Optional[FinalAct]: + """获取下一个可以合并的活动""" + return next( + (li2 for li2 in target if (li2.mergeStatus == 1) and (li.left + li.width <= li2.left)), + None, + ) + + def merge_list(self, target: List[FinalAct]) -> Tuple[List[List[FinalAct]], int, int]: + """将两个活动合并为一行""" + char_count = 0 + char_old = 0 + ret: List[List[FinalAct]] = [] + for idx, li in enumerate(target): + if li.type == ActEnum.character: + char_count += 1 + if li.left == 0: + char_old += 1 + li.idx = char_count + if li.mergeStatus == 1: + if li2 := self.get_merge_next(target[idx + 1 :], li): + li.mergeStatus = 2 + li2.mergeStatus = 2 + ret.append([li, li2]) + if li.mergeStatus != 2: + li.mergeStatus = 2 + ret.append([li]) + return ret, char_count, char_old + + async def get_photo_data(self, assets: AssetsService) -> Dict: + """获取数据""" + now = self.get_now_hour() + list_data, time_map = await self.req_cal_data() + ( + date_list, + start_time, + end_time, + total_range, + now_left, + ) = await self.get_date_list() + birthday_char_line, birthday_chars = await self.get_birthday_char(date_list, assets) + target: List[FinalAct] = [] + abyss: List[FinalAct] = [] + + for ds in list_data[1]: + if act := await self.get_list(ds, start_time, end_time, total_range, time_map, True, assets): + target.append(act) + for ds in list_data[0]: + if act := await self.get_list(ds, start_time, end_time, total_range, time_map, False, assets): + target.append(act) + abyss_cal = self.get_abyss_cal(start_time, end_time) + for t in abyss_cal: + ds = ActDetail( + title=f"「深境螺旋」· {t[2]}", + start_time=t[0].strftime("%Y-%m-%d %H:%M:%S"), + end_time=t[1].strftime("%Y-%m-%d %H:%M:%S"), + ) + if act := await self.get_list(ds, start_time, end_time, total_range, {}, True, assets): + abyss.append(act) + target.sort(key=lambda x: (x.sort, x.start, x.duration)) + target, char_count, char_old = self.merge_list(target) + return { + "date_list": date_list, + "now_left": now_left, + "list": target, + "abyss": abyss, + "char_mode": f"char-{char_count}-{char_old}", + "now_time": now.strftime("%Y-%m-%d %H 时"), + "birthday_char_line": birthday_char_line, + "birthday_chars": birthday_chars, + } diff --git a/modules/apihelper/models/genshin/calendar.py b/modules/apihelper/models/genshin/calendar.py new file mode 100644 index 00000000..f951c99d --- /dev/null +++ b/modules/apihelper/models/genshin/calendar.py @@ -0,0 +1,76 @@ +from enum import Enum +from typing import List + +from pydantic import BaseModel + + +class Date(BaseModel): + """日历日期""" + + month: int + date: List[int] + week: List[str] + is_today: List[bool] + + +class ActEnum(str, Enum): + """活动类型""" + + character = "character" + weapon = "weapon" + activity = "activity" + normal = "normal" + no_display = "pass" + abyss = "abyss" + + def __str__(self) -> str: + return self.value + + +class FinalAct(BaseModel): + """最终活动数据""" + + id: int + type: ActEnum + title: str + banner: str + mergeStatus: int = 0 + face: str = "" + icon: str = "" + left: float = 0.0 + width: float = 0.0 + label: str = "" + sort: int = 0 + idx: int = 0 + start: str = "" + end: str = "" + duration: int = 0 + + +class ActDetail(BaseModel): + """活动详情""" + + ann_id: int = 0 + banner: str = "" + tag_icon: str = "" + title: str + start_time: str + end_time: str + ... + + +class ActTime(BaseModel): + """活动时间""" + + title: str = "" + start: str = "" + end: str = "" + display: bool = True + + +class BirthChar(BaseModel): + """生日角色""" + + name: str + star: int + icon: str diff --git a/plugins/genshin/birthday.py b/plugins/genshin/birthday.py index 347caf58..831a8de8 100644 --- a/plugins/genshin/birthday.py +++ b/plugins/genshin/birthday.py @@ -19,6 +19,7 @@ from core.user import UserService from core.user.error import UserNotFoundError from metadata.genshin import AVATAR_DATA from metadata.shortname import roleToId, roleToName +from modules.apihelper.client.components.calendar import Calendar from utils.bot import get_args from utils.decorators.error import error_callable from utils.decorators.restricts import restricts @@ -48,12 +49,7 @@ class BirthdayPlugin(Plugin, BasePlugin): cookie_service: CookiesService = None, ): """Load Data.""" - self.birthday_list = {} - for value in AVATAR_DATA.values(): - key = "_".join([str(i) for i in value["birthday"]]) - data = self.birthday_list.get(key, []) - data.append(value["name"]) - self.birthday_list.update({key: data}) + self.birthday_list = Calendar.gen_birthday_list() self.user_service = user_service self.cookie_service = cookie_service diff --git a/plugins/genshin/calendar.py b/plugins/genshin/calendar.py new file mode 100644 index 00000000..77be2a46 --- /dev/null +++ b/plugins/genshin/calendar.py @@ -0,0 +1,62 @@ +from typing import Dict + +from telegram import Update +from telegram.constants import ChatAction +from telegram.ext import CallbackContext, MessageHandler, filters + +from core.base.assets import AssetsService +from core.base.redisdb import RedisDB +from core.baseplugin import BasePlugin +from core.plugin import Plugin, handler +from core.template import TemplateService +from modules.apihelper.client.components.calendar import Calendar +from utils.decorators.error import error_callable +from utils.decorators.restricts import restricts +from utils.log import logger + +try: + import ujson as jsonlib +except ImportError: + import json as jsonlib + + +class CalendarPlugin(Plugin, BasePlugin): + """活动日历查询""" + + def __init__( + self, + template_service: TemplateService = None, + assets_service: AssetsService = None, + redis: RedisDB = None, + ): + self.template_service = template_service + self.assets_service = assets_service + self.calendar = Calendar() + self.cache = redis.client + + async def _fetch_data(self) -> Dict: + if data := await self.cache.get("plugin:calendar"): + return jsonlib.loads(data.decode("utf-8")) + data = await self.calendar.get_photo_data(self.assets_service) + await self.cache.set("plugin:calendar", jsonlib.dumps(data, default=lambda x: x.dict()), ex=1800) + return data + + @handler.command("calendar", block=False) + @handler(MessageHandler, filters=filters.Regex(r"^(活动)+(日历|日历列表)$"), block=False) + @restricts() + @error_callable + async def command_start(self, update: Update, _: CallbackContext) -> None: + user = update.effective_user + message = update.effective_message + mode = "list" if "列表" in message.text else "calendar" + logger.info("用户 %s[%s] 查询日历 | 模式 %s", user.full_name, user.id, mode) + await message.reply_chat_action(ChatAction.TYPING) + data = await self._fetch_data() + data["display_mode"] = mode + image = await self.template_service.render( + "genshin/calendar/calendar.html", + data, + query_selector=".container", + ) + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + await image.reply_photo(message) diff --git a/resources/bot/help/help.html b/resources/bot/help/help.html index 9e9ccbeb..d2c233b8 100644 --- a/resources/bot/help/help.html +++ b/resources/bot/help/help.html @@ -146,9 +146,16 @@
角色生日
-
/birthday_card
+
+ /birthday_card + +
领取角色生日画片
+
+
/calendar
+
活动日历
+
diff --git a/resources/genshin/calendar/calendar.css b/resources/genshin/calendar/calendar.css new file mode 100644 index 00000000..04db4eaf --- /dev/null +++ b/resources/genshin/calendar/calendar.css @@ -0,0 +1,450 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; +} +body { + font-size: 18px; + color: #1e1f20; + transform: scale(1); + transform-origin: 0 0; + width: 996px; +} +.container { + width: 996px; + padding: 10px 0px 10px 0px; + background-size: 100% 100%; +} +.logo { + font-size: 18px; + text-align: center; + color: #fff; + margin: 20px 0 10px 0; +} +.calendar { + min-height: 400px; + position: relative; + padding: 1px 0; + width: 956px; + margin: 20px; + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.6); + border-radius: 10px; +} +.cal-bg { + position: absolute; + width: 956px; + top: 0; + left: 0; + right: 0; + bottom: 0; + text-align: center; + border-collapse: collapse; + height: 100%; + box-shadow: 0 0 1px 0 #fff inset; + border-radius: 10px; + overflow: hidden; +} +.cal-bg table { + height: 100%; +} +.cal-bg .tr.thead { + background: rgba(0, 0, 0, 0.8); + height: 40px; +} +.cal-bg td { + box-shadow: 0 0 1px 0 #fff; +} +.cal-bg td.date { + width: 7.692%; +} +.cal-bg td.date span { + display: block; + line-height: 18px; +} +.cal-bg td.date span.date-week { + line-height: 12px; + font-size: 12px; + color: #888; +} +.cal-bg td.date.current-date { + background: #d3bc8e; + border: 1px solid #d3bc8e; + color: #000; +} +.cal-bg td.date.current-date span.date-week { + color: #000; +} +.cal-bg td.line { + background: rgba(0, 0, 0, 0.4); + vertical-align: top; +} +.cal-bg td.line.current-date { + background: rgba(211, 188, 142, 0.4); +} +.cal-bg .card { + width: 65px; + height: 76px; + margin: 8px auto -4px; +} +.cal-bg .card .img { + height: 60px; +} +.cal-bg .card .char-name { + position: absolute; + bottom: 0; + left: 0; + right: 0; + line-height: 17px; + font-size: 12px; + background: #e8e2d8; +} +.cal-list { + position: relative; + padding-top: 80px; + overflow: hidden; +} +.cal-list.char-num-0 { + padding-top: 80px; +} +.cal-list.char-num-1 { + padding-top: 160px; +} +.cal-list.char-num-2 { + padding-top: 240px; +} +.cal-list.char-num-3 { + padding-top: 320px; +} +.cal-list .cal-item { + margin-bottom: 15px; + border-radius: 5px; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + overflow: hidden; + background: #e8e2d8; + z-index: 1; +} +.cal-list .cal-item:after { + content: ""; + display: block; + position: absolute; + left: 3px; + top: 3px; + right: 4px; + bottom: 4px; + box-shadow: 0 0 1px 0 #000 inset, 0 0 2px 0 #222a3b; + border-radius: 4px; +} +.cal-list .cal-item .info { + position: relative; + display: inline-block; + padding: 15px 50px 15px 55px; + min-width: calc(100% - 400px); + border-radius: 5px; + background-image: linear-gradient(to right, #E8E2D8, #E8E2D8 80%, rgba(232, 226, 216, 0) 100%); +} +.cal-list .cal-item .banner { + position: absolute; + width: 100%; + max-width: 500px; + top: 0; + bottom: 0; + right: 0; + background-size: 100% auto; + background-position: left 40%; +} +.cal-list .cal-item strong { + display: block; + font-weight: normal; +} +.cal-list .cal-item span { + display: block; + font-size: 12px; +} +.cal-list .cal-item.type-character { + overflow: visible; + margin-top: 20px; +} +.cal-list .cal-item.type-character .info { + padding-left: 65px; +} +.cal-list .cal-item.type-character .character-img { + height: 75px; + position: absolute; + bottom: 0; + left: 0; + z-index: 10; +} +.cal-list .cal-item.type-normal { + margin-top: 5px; + margin-bottom: 5px; +} +.cal-list .cal-item.type-normal:last-child { + margin-bottom: 15px; +} +.cal-list .cal-item.type-normal:after { + display: none; +} +.cal-list .cal-item.type-normal:first-of-type { + margin-top: 20px; +} +.cal-list .cal-item.type-normal .info { + padding: 8px 20px 8px 15px; + line-height: 16px; + color: #4b5366; +} +.cal-list .cal-item.type-normal .cal-icon { + width: 23px; + height: 23px; + top: 6px; + margin-left: -3px; + margin-right: 5px; +} +.cal-list .cal-item.type-normal strong { + font-size: 16px; +} +.cal-list .cal-item.type-normal .info { + padding-left: 38px; +} +.cal-list .cal-item.type-normal strong, +.cal-list .cal-item.type-normal span { + display: inline; +} +.cal-list .cal-item.type-normal.small-mode span { + display: block; + margin-left: 0; +} +.cal-list .cal-item.type-normal.li-col1 { + margin-top: -40px; +} +.cal-list .cal-item .cal-icon { + position: absolute; + width: 40px; + height: 40px; + left: 10px; + top: 10px; +} +.cal-list .cal-item.li-col1 { + margin-top: -82px; +} +.cal-list.char-2-1 .type-character.li-idx-2, +.cal-list.char-3-1 .type-character.li-idx-2 { + margin-top: -82px; +} +.cal-list.char-3-2 .type-character.li-idx-3 { + margin-top: -82px; +} +.cal-list.char-4-2 .type-character.li-idx-3 { + margin-top: -166px; +} +.cal-list .type-weapon.li-idx-2 { + margin-top: -82px; +} +.calendar .now-line { + position: absolute; + top: 86px; + bottom: -18px; + width: 2px; + box-shadow: 0 0 5px 0 #fff; + background: #fff; + opacity: 0.8; +} +.calendar .now-line:after { + content: ""; + display: block; + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 20px solid #fff; + position: absolute; + bottom: -8px; + left: -9px; + transform: scaleY(0.7); + transform-origin: bottom center; +} +.calendar .now-line.line2 { + z-index: 3; + opacity: 0.5; + background: #d3bc8d; + width: 2px; + box-shadow: none; +} +.now-time { + text-align: center; + padding-top: 5px; + margin-bottom: 5px; +} +.now-time span { + color: #fff; + background: rgba(0, 0, 0, 0.6); + border-radius: 30px; + padding: 10px 15px; + border: 1px solid #fff; + display: inline-block; +} +.cal-abyss-cont { + padding-top: 15px; + height: 80px; + position: relative; +} +.cal-abyss-cont .cal-item { + border-radius: 0; + background: url("img/abyss.jpg") #333465 top right no-repeat; + position: absolute; +} +.cal-abyss-cont .cal-item .info { + background: none; + color: #d3bc8d; + background-image: linear-gradient(to right, #333465, #333465 80%, rgba(51, 52, 101, 0) 100%); +} +.cal-abyss-cont .cal-item:before { + content: ""; + display: block; + width: 3px; + left: 0; + top: 1px; + bottom: 1px; + position: absolute; + background: #d3bc8d; + z-index: 8; +} +.cal-abyss-cont .cal-item:after { + box-shadow: 0 0 1px 0 #fff; + border-radius: 0; +} +.calendar-mode .for-list-mode { + display: none; +} +.list-mode .container { + width: 740px; +} +.list-mode .for-calendar-mode { + display: none; +} +.list-mode .cal-bg { + width: initial; +} +.list-mode .cal-list { + padding: 45px 10px 0; +} +.list-mode .calendar { + width: 700px; +} +.list-mode .cal-abyss-cont { + height: initial !important; +} +.list-mode .cal-item { + position: relative; + margin-left: 0 !important; + width: initial !important; + left: 0 !important; +} +.list-mode .now-line { + display: none; +} +.daily-talent { + display: flex; + flex-wrap: wrap; + margin: 5px 10px 0; + background: rgba(0, 0, 0, 0.5); + padding: 10px 9px 10px; + border-radius: 10px; +} +.daily-talent .item-icon { + overflow: visible; +} +.daily-talent .card { + width: 87px; + height: 105px; + margin: 10px 0 15px; +} +.daily-talent .card .item-icon { + width: 77px; + margin: 0 6px; + height: 82px; + padding-top: 5px; +} +.daily-talent .card .img { + width: 77px; + height: 77px; +} +.daily-talent .card .weekly { + position: absolute; + width: 24px; + height: 24px; + border-radius: 50%; + bottom: -10px; + right: -3px; + background-color: rgba(232, 226, 216, 0.9); + box-shadow: 0 0 2px 0 #000; + overflow: visible; +} +.daily-talent .card .weekly .weekly-icon { + width: 30px; + height: 30px; + margin: -3px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} +.daily-talent .card .banner { + height: 20px; + padding-top: 1px; + line-height: 20px; + color: #fff; + position: relative; + margin-bottom: 8px; +} +.daily-talent .card .banner .title { + margin-right: -50px; + width: calc(100% + 50px); + display: flex; + position: absolute; + top: 0; + left: 0; + z-index: 2; + text-shadow: 0 0 1px rgba(0, 0, 0, 0.8), 1px 1px 2px rgba(0, 0, 0, 0.8); + padding-left: 45px; + font-size: 18px; +} +.daily-talent .card .banner .icon { + width: 40px; + height: 40px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + display: inline-block; + position: absolute; + left: 0; + top: -8px; +} +.daily-talent .card .banner .line { + height: 6px; + width: 100%; + margin-top: 13px; +} +.daily-talent .card .banner .line.first { + margin-left: 35%; + width: 65%; + border-radius: 3px 0 0 3px; +} +.daily-talent .card .banner .line.last { + width: 94%; + border-radius: 0 3px 3px 0; +} +.daily-talent .card .banner.city-1 .line { + background: #37c9b8; +} +.daily-talent .card .banner.city-2 .line { + background: #bca244; +} +.daily-talent .card .banner.city-3 .line { + background: #ac60c9; +} +.daily-talent .card .banner.city-4 .line { + background: #54b640; +} +/*# sourceMappingURL=calendar.css.map */ \ No newline at end of file diff --git a/resources/genshin/calendar/calendar.html b/resources/genshin/calendar/calendar.html new file mode 100644 index 00000000..f8ac6def --- /dev/null +++ b/resources/genshin/calendar/calendar.html @@ -0,0 +1,103 @@ + + + + + + + + Calendar + + + +
+
+
+ + + {% for d in date_list %} + + {% endfor %} + + + {% for d in date_list %} + {% for dn in range(d.date.__len__()) %} + + {% endfor %} + {% endfor %} + + + {% for d in date_list %} + {% for dn in range(d.date.__len__()) %} + + {% endfor %} + {% endfor %} + +
{{ d.month }}月
+ {{ d.date[dn] }}日 + 周{{ d.week[dn] }} +
+ {% for char in birthday_chars[d.month|string][d.date[dn]|string] %} +
+
+
+ {{ char.name }}{{ '生日' if char.name.__len__() < 4 else '' }} +
+
+ {% endfor %} +
+
+
+ + + + + + + +
活动列表
+
+
+
+ {% for li in abyss %} +
+
+ + {{ li.title }} + {{ li.label }} +
+
+ {% endfor %} +
+ {% for lis in list %} + {% for li in lis %} +
+ {% if li.banner %} + + {% endif %} +
+ {% if li.type == "character" %} + + {% else %} + + {% endif %} + {{ li.title }} + {{ li.label }} +
+
+ {% endfor %} + {% endfor %} +
+
+
+
+
+ {{ now_time }} +
+ +
+ + \ No newline at end of file diff --git a/resources/genshin/calendar/calendar.less b/resources/genshin/calendar/calendar.less new file mode 100644 index 00000000..ab803cb6 --- /dev/null +++ b/resources/genshin/calendar/calendar.less @@ -0,0 +1,573 @@ +//linear-gradient(to right, rgba(232, 226, 216, 1), rgba(232, 226, 216, 1) 80%, rgba(232, 226, 216, 0) 100%); + +.linear-bg(@color) { + background-image: linear-gradient(to right, @color, @color 80%, fade(@color, 0) 100%); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; +} + +body { + font-size: 18px; + color: #1e1f20; + transform: scale(1); + transform-origin: 0 0; + width: 996px; +} + +.container { + width: 996px; + padding: 10px 0px 10px 0px; + background-size: 100% 100%; +} + +.logo { + font-size: 18px; + text-align: center; + color: #fff; + margin: 20px 0 10px 0; +} + +.calendar { + min-height: 400px; + position: relative; + padding: 1px 0; + width: 956px; + margin: 20px; + box-shadow: 0 0 0 10px rgba(0, 0, 0, .6); + border-radius: 10px; +} + +.cal-bg { + position: absolute; + width: 956px; + top: 0; + left: 0; + right: 0; + bottom: 0; + text-align: center; + border-collapse: collapse; + height: 100%; + box-shadow: 0 0 1px 0 #fff inset; + border-radius: 10px; + overflow: hidden; + + table { + height: 100%; + } + + + .tr.thead { + background: rgba(0, 0, 0, .8); + height: 40px; + + td { + } + } + + td { + box-shadow: 0 0 1px 0 #fff; + + &.date { + width: 7.692%; + + span { + display: block; + line-height: 18px; + } + + span.date-num { + + } + + span.date-week { + line-height: 12px; + font-size: 12px; + color: #888; + } + + &.current-date { + background: #d3bc8e; + border: 1px solid #d3bc8e; + color: #000; + + span.date-week { + color: #000; + } + } + + + } + + &.line { + background: rgba(0, 0, 0, 0.4); + vertical-align: top; + + &.current-date { + background: rgba(211, 188, 142, .4) + } + } + } + + .card { + width: 65px; + height: 76px; + margin: 8px auto -4px; + + .img { + height: 60px; + } + + .char-name { + position: absolute; + bottom: 0; + left: 0; + right: 0; + line-height: 17px; + font-size: 12px; + background: #e8e2d8; + } + } +} + +.cal-list { + position: relative; + padding-top: 80px; + overflow: hidden; + + &.char-num-0 { + padding-top: 80px; + } + + &.char-num-1 { + padding-top: 160px; + } + + &.char-num-2 { + padding-top: 240px; + } + + &.char-num-3 { + padding-top: 320px; + } + + .cal-item { + margin-bottom: 15px; + border-radius: 5px; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + overflow: hidden; + background: rgba(232, 226, 216, 1); + z-index: 1; + + &:after { + content: ""; + display: block; + position: absolute; + left: 3px; + top: 3px; + right: 4px; + bottom: 4px; + box-shadow: 0 0 1px 0 #000 inset, 0 0 2px 0 #222a3b; + border-radius: 4px; + } + + .info { + position: relative; + display: inline-block; + padding: 15px 50px 15px 55px; + min-width: calc(100% - 400px); + border-radius: 5px; + .linear-bg(#E8E2D8); + } + + .banner { + position: absolute; + width: 100%; + max-width: 500px; + top: 0; + bottom: 0; + right: 0; + background-size: 100% auto; + background-position: left 40%; + + } + + strong { + display: block; + font-weight: normal; + } + + span { + display: block; + font-size: 12px; + } + + + &.type-character { + overflow: visible; + margin-top: 20px; + + .info { + padding-left: 65px; + + } + + .character-img { + height: 75px; + position: absolute; + bottom: 0; + left: 0; + z-index: 10; + + } + } + + &.type-normal { + margin-top: 5px; + margin-bottom: 5px; + + &:last-child { + margin-bottom: 15px; + } + + &:after { + display: none; + } + + + &:first-of-type { + margin-top: 20px; + } + + .info { + padding: 8px 20px 8px 15px; + line-height: 16px; + color: #4b5366; + } + + .cal-icon { + + width: 23px; + height: 23px; + top: 6px; + margin-left: -3px; + margin-right: 5px; + } + + strong { + font-size: 16px; + } + + .info { + padding-left: 38px; + } + + + strong, + span { + display: inline; + } + + + &.small-mode span { + display: block; + margin-left: 0; + } + + &.li-col1 { + margin-top: -40px; + } + } + + .cal-icon { + position: absolute; + width: 40px; + height: 40px; + left: 10px; + top: 10px; + } + + &.li-col1 { + margin-top: -82px; + } + } + + &.char-2-1, + &.char-3-1 { + .type-character.li-idx-2 { + margin-top: -82px; + } + } + + &.char-3-2 { + .type-character.li-idx-3 { + margin-top: -82px; + } + } + + &.char-4-2 { + .type-character.li-idx-3 { + margin-top: -166px; + } + } + + .type-weapon.li-idx-2 { + margin-top: -82px; + } + + +} + +.calendar .now-line { + position: absolute; + top: 86px; + bottom: -18px; + width: 2px; + box-shadow: 0 0 5px 0 #fff; + background: #fff; + opacity: .8; + + &:after { + content: ""; + display: block; + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 20px solid #fff; + position: absolute; + bottom: -8px; + left: -9px; + transform: scaleY(.7); + transform-origin: bottom center; + } + + &.line2 { + z-index: 3; + opacity: .5; + background: rgb(211, 188, 141); + width: 2px; + box-shadow: none; + + &:after { + } + } +} + +.now-time { + text-align: center; + padding-top: 5px; + margin-bottom: 5px; + + span { + color: #fff; + background: rgba(0, 0, 0, 0.6); + border-radius: 30px; + padding: 10px 15px; + border: 1px solid #fff; + display: inline-block; + } +} + +.cal-abyss-cont { + padding-top: 15px; + height: 80px; + position: relative; + + .cal-item { + border-radius: 0; + background: url("img/abyss.jpg") #333465 top right no-repeat; + position: absolute; + + .info { + background: none; + color: rgba(211, 188, 141, 1); + .linear-bg(#333465); + } + + &:before { + content: ""; + display: block; + width: 3px; + left: 0; + top: 1px; + bottom: 1px; + position: absolute; + background: #d3bc8d; + z-index: 8; + + } + + &:after { + box-shadow: 0 0 1px 0 #fff; + border-radius: 0; + } + } +} + +.calendar-mode { + .for-list-mode { + display: none; + } +} + +.list-mode { + + .container { + width: 740px; + } + + .for-calendar-mode { + display: none; + } + + .cal-bg { + width: initial; + } + + .cal-list { + padding: 45px 10px 0; + } + + .calendar { + width: 700px; + } + + .cal-abyss-cont { + height: initial !important; + } + + .cal-item { + position: relative; + margin-left: 0 !important; + width: initial !important; + left: 0 !important; + } + + .now-line { + display: none; + } +} + +@width: 77px; + +.daily-talent { + display: flex; + flex-wrap: wrap; + margin: 5px 10px 0; + background: rgba(0, 0, 0, .5); + padding: 10px 9px 10px; + border-radius: 10px; + + + .item-icon { + overflow: visible; + } + + .card { + width: @width + 10px; + height: @width + 28px; + margin: 10px 0 15px; + + + .item-icon { + width: @width; + margin: 0 6px; + height: @width + 5px; + padding-top: 5px; + } + + .img { + width: @width; + height: @width; + } + + .weekly { + position: absolute; + width: 24px; + height: 24px; + border-radius: 50%; + bottom: -10px; + right: -3px; + background-color: rgba(232, 226, 216, 0.9); + box-shadow: 0 0 2px 0 #000; + overflow: visible; + + .weekly-icon { + width: 30px; + height: 30px; + margin: -3px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + } + } + + .banner { + height: 20px; + padding-top: 1px; + line-height: 20px; + color: #fff; + position: relative; + margin-bottom: 8px; + + .title { + margin-right: -50px; + width: calc(100% + 50px); + display: flex; + position: absolute; + top: 0; + left: 0; + z-index: 2; + text-shadow: 0 0 1px rgba(0, 0, 0, .8), 1px 1px 2px rgba(0, 0, 0, .8); + padding-left: 45px; + font-size: 18px; + } + + .icon { + width: 40px; + height: 40px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + display: inline-block; + position: absolute; + left: 0; + top: -8px; + } + + .line { + height: 6px; + width: 100%; + margin-top: 13px; + + &.first { + margin-left: 35%; + width: 65%; + border-radius: 3px 0 0 3px; + } + + &.last { + width: 94%; + border-radius: 0 3px 3px 0; + } + } + + .city(@name, @bg) { + &.city-@{name} .line { + background: @bg; + } + } + .city(1, #37c9b8); + .city(2, #bca244); + .city(3, #ac60c9); + .city(4, #54b640); + } + } +} \ No newline at end of file diff --git a/resources/genshin/calendar/common.css b/resources/genshin/calendar/common.css new file mode 100644 index 00000000..e22541da --- /dev/null +++ b/resources/genshin/calendar/common.css @@ -0,0 +1,521 @@ +@font-face { + font-family: 'Number'; + src: url("../../fonts/tttgbnumber.ttf") format('truetype'); +} + +@font-face { + font-family: 'YS'; + src: url("../../fonts/HYWenHei-65W.ttf") format('truetype'); +} + +.font-ys { + font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-user-select: none; + user-select: none; +} + +body { + font-size: 18px; + color: #1e1f20; + font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; + transform: scale(1.4); + transform-origin: 0 0; + width: 600px; +} + +.container { + width: 600px; + padding: 20px 15px 10px 15px; + background-size: contain; +} + +.head-box { + border-radius: 15px; + padding: 10px 20px; + position: relative; + color: #fff; + margin-top: 30px; +} + +.head-box .title { + font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; + font-size: 36px; + text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); +} + +.head-box .title .label { + display: inline-block; + margin-left: 10px; +} + +.head-box .genshin_logo { + position: absolute; + top: 1px; + right: 15px; + width: 97px; +} + +.head-box .label { + font-size: 16px; + text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); +} + +.head-box .label span { + color: #d3bc8e; + padding: 0 2px; +} + +.notice { + color: #888; + font-size: 12px; + text-align: right; + padding: 12px 5px 5px; +} + +.notice-center { + color: #fff; + text-align: center; + margin-bottom: 10px; + text-shadow: 1px 1px 1px #333; +} + +.copyright { + font-size: 14px; + text-align: center; + color: #fff; + position: relative; + padding-left: 10px; + text-shadow: 1px 1px 1px #000; + margin: 10px 0; +} + +.copyright .version { + color: #d3bc8e; + display: inline-block; + padding: 0 3px; +} + +/* */ +.cons { + display: inline-block; + vertical-align: middle; + padding: 0 5px; + border-radius: 4px; +} + +.cons-0 { + background: #666; + color: #fff; +} + +.cons-n0 { + background: #404949; + color: #fff; +} + +.cons-1 { + background: #5cbac2; + color: #fff; +} + +.cons-2 { + background: #339d61; + color: #fff; +} + +.cons-3 { + background: #3e95b9; + color: #fff; +} + +.cons-4 { + background: #3955b7; + color: #fff; +} + +.cons-5 { + background: #531ba9cf; + color: #fff; +} + +.cons-6 { + background: #ff5722; + color: #fff; +} + +.cons2-0 { + border-radius: 4px; + background: #666; + color: #fff; +} + +.cons2-1 { + border-radius: 4px; + background: #71b1b7; + color: #fff; +} + +.cons2-2 { + border-radius: 4px; + background: #369961; + color: #fff; +} + +.cons2-3 { + border-radius: 4px; + background: #4596b9; + color: #fff; +} + +.cons2-4 { + border-radius: 4px; + background: #4560b9; + color: #fff; +} + +.cons2-5 { + border-radius: 4px; + background: #531ba9cf; + color: #fff; +} + +.cons2-6 { + border-radius: 4px; + background: #ff5722; + color: #fff; +} + +/******** Fetter ********/ +.fetter { + width: 50px; + height: 50px; + display: inline-block; + background: url('img/fetter.png'); + background-size: auto 100%; +} + +.fetter.fetter1 { + background-position: 0% 0; +} + +.fetter.fetter2 { + background-position: 11.11111111% 0; +} + +.fetter.fetter3 { + background-position: 22.22222222% 0; +} + +.fetter.fetter4 { + background-position: 33.33333333% 0; +} + +.fetter.fetter5 { + background-position: 44.44444444% 0; +} + +.fetter.fetter6 { + background-position: 55.55555556% 0; +} + +.fetter.fetter7 { + background-position: 66.66666667% 0; +} + +.fetter.fetter8 { + background-position: 77.77777778% 0; +} + +.fetter.fetter9 { + background-position: 88.88888889% 0; +} + +.fetter.fetter10 { + background-position: 100% 0; +} + +/******** ELEM ********/ +.elem-hydro .talent-icon { + background-image: url("../player_card/img/talent-hydro.png"); +} + +.elem-hydro .elem-bg, +.hydro-bg { + background-image: url("../player_card/img/bg-hydro.jpg"); +} + +.elem-anemo .talent-icon { + background-image: url("../player_card/img/talent-anemo.png"); +} + +.elem-anemo .elem-bg, +.anemo-bg { + background-image: url("../player_card/img/bg-anemo.jpg"); +} + +.elem-cryo .talent-icon { + background-image: url("../player_card/img/talent-cryo.png"); +} + +.elem-cryo .elem-bg, +.cryo-bg { + background-image: url("../player_card/img/bg-cryo.jpg"); +} + +.elem-electro .talent-icon { + background-image: url("../player_card/img/talent-electro.png"); +} + +.elem-electro .elem-bg, +.electro-bg { + background-image: url("../player_card/img/bg-electro.jpg"); +} + +.elem-geo .talent-icon { + background-image: url("../player_card/img/talent-geo.png"); +} + +.elem-geo .elem-bg, +.geo-bg { + background-image: url("../player_card/img/bg-geo.jpg"); +} + +.elem-pyro .talent-icon { + background-image: url("../player_card/img/talent-pyro.png"); +} + +.elem-pyro .elem-bg, +.pyro-bg { + background-image: url("../player_card/img/bg-pyro.jpg"); +} + +.elem-dendro .talent-icon { + background-image: url("../player_card/img/talent-dendro.png"); +} + +.elem-dendro .elem-bg, +.dendro-bg { + background-image: url("../player_card/img/bg-dendro.jpg"); +} + +/* cont */ +.cont { + border-radius: 10px; + background: url("img/card-bg.png") top left repeat-x; + background-size: auto 100%; + margin: 5px 15px 5px 10px; + position: relative; + box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, 0.8); + overflow: hidden; + color: #fff; + font-size: 16px; +} + +.cont-title { + background: rgba(0, 0, 0, 0.4); + box-shadow: 0 0 1px 0 #fff; + color: #d3bc8e; + padding: 10px 20px; + text-align: left; + border-radius: 10px 10px 0 0; +} + +.cont-title span { + font-size: 12px; + color: #aaa; + margin-left: 10px; + font-weight: normal; +} + +.cont-title.border-less { + background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); + box-shadow: none; + padding-bottom: 5px; +} + +.cont-body { + padding: 10px 15px; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 1px 0 #fff; + font-weight: normal; +} + +.cont-footer { + padding: 10px 15px; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + font-weight: normal; +} + +.cont > ul.cont-msg { + display: block; + padding: 5px 10px; + background: rgba(0, 0, 0, 0.5); +} + +ul.cont-msg, +.cont-footer ul { + padding-left: 15px; +} + +ul.cont-msg li, +.cont-footer ul li { + margin: 5px 0; + margin-left: 15px; +} + +ul.cont-msg li strong, +.cont-footer ul li strong { + font-weight: normal; + margin: 0 2px; + color: #d3bc8e; +} + +.cont-table { + display: table; + width: 100%; +} + +.cont-table .tr { + display: table-row; +} + +.cont-table .tr:nth-child(even) { + background: rgba(0, 0, 0, 0.4); +} + +.cont-table .tr:nth-child(odd) { + background: rgba(50, 50, 50, 0.4); +} + +.cont-table .tr > div, +.cont-table .tr > td { + display: table-cell; + box-shadow: 0 0 1px 0 #fff; +} + +.cont-table .tr > div.value-full { + display: table; + width: 200%; +} + +.cont-table .tr > div.value-none { + box-shadow: none; +} + +.cont-table .thead { + text-align: center; +} + +.cont-table .thead > div, +.cont-table .thead > td { + color: #d3bc8e; + background: rgba(0, 0, 0, 0.4); + line-height: 40px; + height: 40px; +} + +.cont-table .title, +.cont-table .th { + color: #d3bc8e; + padding-right: 15px; + text-align: right; + background: rgba(0, 0, 0, 0.4); + min-width: 100px; + vertical-align: middle; +} + +.logo { + font-size: 18px; + text-align: center; + color: #fff; + margin: 20px 0 10px 0; +} + +/* item-icon */ +.item-icon { + width: 100%; + height: 100%; + border-radius: 4px; + position: relative; + overflow: hidden; +} + +.item-icon .img { + width: 100%; + height: 100%; + display: block; + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.item-icon.artis .img { + width: 84%; + height: 84%; + margin: 8%; +} + +.item-icon.star4 { + background-image: url("../abyss/background/roleStarBg4.png"); +} + +.item-icon.opacity-bg.star4 { + background-image: url("../abyss/background/roleStarBg4.png"); +} + +.item-icon.star5 { + background-image: url("../abyss/background/roleStarBg5.png"); +} + +.item-icon.opacity-bg.star5 { + background-image: url("../abyss/background/roleStarBg5.png"); +} + +.item-list { + display: flex; +} + +.item-list .item-card { + width: 70px; + background: #e7e5d9; +} + +.item-list .item-icon { + border-bottom-left-radius: 0; + border-bottom-right-radius: 12px; +} + +.item-list .item-title { + color: #222; + font-size: 13px; + text-align: center; + padding: 2px; + white-space: nowrap; + overflow: hidden; +} + +.item-list .item-icon { + height: initial; +} + +.item-list .item-badge { + position: absolute; + display: block; + left: 0; + top: 0; + background: rgba(0, 0, 0, 0.6); + font-size: 12px; + color: #fff; + padding: 4px 5px 3px; + border-radius: 0 0 6px 0; +} + +/*# sourceMappingURL=common.css.map */ \ No newline at end of file diff --git a/resources/genshin/calendar/common.less b/resources/genshin/calendar/common.less new file mode 100644 index 00000000..5bd21172 --- /dev/null +++ b/resources/genshin/calendar/common.less @@ -0,0 +1,391 @@ +.font(@name, @file) { + @font-face { + font-family: @name; + src: url("../../fonts/@{file}.ttf") format('truetype'); + } +} + +.font('Number', 'tttgbnumber'); +.font('YS', 'HYWenHei-65W'); + +.font-ys { + font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-user-select: none; + user-select: none; +} + +body { + font-size: 18px; + color: #1e1f20; + font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; + transform: scale(1.4); + transform-origin: 0 0; + width: 600px; +} + +.container { + width: 600px; + padding: 20px 15px 10px 15px; + background-size: contain; +} + + +.head-box { + border-radius: 15px; + padding: 10px 20px; + position: relative; + color: #fff; + margin-top: 30px; + + .title { + .font-ys; + font-size: 36px; + text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .9); + + .label { + display: inline-block; + margin-left: 10px; + } + } + + .genshin_logo { + position: absolute; + top: 1px; + right: 15px; + width: 97px; + } + + .label { + font-size: 16px; + text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, .9); + + span { + color: #d3bc8e; + padding: 0 2px; + } + } +} + + +.notice { + color: #888; + font-size: 12px; + text-align: right; + padding: 12px 5px 5px; +} + +.notice-center { + color: #fff; + text-align: center; + margin-bottom: 10px; + text-shadow: 1px 1px 1px #333; +} + +.copyright { + font-size: 14px; + text-align: center; + color: #fff; + position: relative; + padding-left: 10px; + text-shadow: 1px 1px 1px #000; + margin: 10px 0; + + .version { + color: #d3bc8e; + display: inline-block; + padding: 0 3px; + } +} + + +/* */ + +.cons { + display: inline-block; + vertical-align: middle; + padding: 0 5px; + border-radius: 4px; +} + + +.cons(@idx, @bg, @color:#fff) { + .cons-@{idx} { + background: @bg; + color: @color; + } +} + +.cons(0, #666); +.cons(n0, #404949); +.cons(1, #5cbac2); +.cons(2, #339d61); +.cons(3, #3e95b9); +.cons(4, #3955b7); +.cons(5, #531ba9cf); +.cons(6, #ff5722); + +.cons2(@idx, @bg, @color:#fff) { + .cons2-@{idx} { + border-radius: 4px; + background: @bg; + color: @color; + } +} + +.cons2(0, #666); +.cons2(1, #71b1b7); +.cons2(2, #369961); +.cons2(3, #4596b9); +.cons2(4, #4560b9); +.cons2(5, #531ba9cf); +.cons2(6, #ff5722); + +/******** Fetter ********/ + +.fetter { + width: 50px; + height: 50px; + display: inline-block; + background: url('img/fetter.png'); + background-size: auto 100%; + @fetters: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; + each(@fetters, { + &.fetter@{value} { + background-position: (-100%/9)+(100%/9)*@value 0; + } + }) +} + +/******** ELEM ********/ + +@elems: hydro, anemo, cryo, electro, geo, pyro, dendro; + +each(@elems, { + .elem-@{value} .talent-icon { + background-image: url("../player_card/img/talent-@{value}.png"); + } + + .elem-@{value} .elem-bg, + .@{value}-bg { + background-image: url("../player_card/img/bg-@{value}.jpg"); + } +}) + + +/* cont */ + +.cont { + border-radius: 10px; + background: url("img/card-bg.png") top left repeat-x; + background-size: auto 100%; + // backdrop-filter: blur(3px); + margin: 5px 15px 5px 10px; + position: relative; + box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, .8); + overflow: hidden; + color: #fff; + font-size: 16px; +} + + +.cont-title { + background: rgba(0, 0, 0, .4); + box-shadow: 0 0 1px 0 #fff; + color: #d3bc8e; + padding: 10px 20px; + text-align: left; + border-radius: 10px 10px 0 0; + + span { + font-size: 12px; + color: #aaa; + margin-left: 10px; + font-weight: normal; + } + + &.border-less { + background: linear-gradient(rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)); + box-shadow: none; + padding-bottom: 5px; + } +} + +.cont-body { + padding: 10px 15px; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 1px 0 #fff; + font-weight: normal; +} + + +.cont-footer { + padding: 10px 15px; + font-size: 12px; + background: rgba(0, 0, 0, 0.5); + font-weight: normal; +} + +.cont > ul.cont-msg { + display: block; + padding: 5px 10px; + background: rgba(0, 0, 0, 0.5); +} + +ul.cont-msg, .cont-footer ul { + padding-left: 15px; + + li { + margin: 5px 0; + margin-left: 15px; + + strong { + font-weight: normal; + margin: 0 2px; + color: #d3bc8e; + } + } +} + +.cont-table { + display: table; + width: 100%; +} + +.cont-table .tr { + display: table-row; +} + +.cont-table .tr:nth-child(even) { + background: rgba(0, 0, 0, .4); +} + +.cont-table .tr:nth-child(odd) { + background: rgba(50, 50, 50, .4); +} + +.cont-table .tr > div, +.cont-table .tr > td { + display: table-cell; + box-shadow: 0 0 1px 0 #fff; +} + +.cont-table .tr > div.value-full { + display: table; + width: 200%; +} + +.cont-table .tr > div.value-none { + box-shadow: none; +} + +.cont-table .thead { + text-align: center; +} + +.cont-table .thead > div, +.cont-table .thead > td { + color: #d3bc8e; + background: rgba(0, 0, 0, .4); + line-height: 40px; + height: 40px; +} + + +.cont-table .title, +.cont-table .th { + color: #d3bc8e; + padding-right: 15px; + text-align: right; + background: rgba(0, 0, 0, .4); + min-width: 100px; + vertical-align: middle; +} + +.logo { + font-size: 18px; + text-align: center; + color: #fff; + margin: 20px 0 10px 0; +} + +/* item-icon */ +.item-icon { + width: 100%; + height: 100%; + border-radius: 4px; + position: relative; + overflow: hidden; + + .img { + width: 100%; + height: 100%; + display: block; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + } + + &.artis { + .img { + width: 84%; + height: 84%; + margin: 8%; + } + } + + @stars: 4, 5; + each(@stars, { + &.star@{value} { + background-image: url("../abyss/background/roleStarBg@{value}.png"); + } + &.opacity-bg.star@{value} { + background-image: url("../abyss/background/roleStarBg@{value}.png"); + } + }) +} + +.item-list { + display: flex; + + .item-card { + width: 70px; + background: #e7e5d9; + } + + .item-icon { + border-bottom-left-radius: 0; + border-bottom-right-radius: 12px; + } + + .item-title { + color: #222; + font-size: 13px; + text-align: center; + padding: 2px; + white-space: nowrap; + overflow: hidden; + } + + .item-icon { + height: initial; + } + + .item-badge { + position: absolute; + display: block; + left: 0; + top: 0; + background: rgba(0, 0, 0, 0.6); + font-size: 12px; + color: #fff; + padding: 4px 5px 3px; + border-radius: 0 0 6px 0; + } +} \ No newline at end of file diff --git a/resources/genshin/calendar/example.html b/resources/genshin/calendar/example.html new file mode 100644 index 00000000..2ce6a036 --- /dev/null +++ b/resources/genshin/calendar/example.html @@ -0,0 +1,199 @@ + + + + + + + + miao-plugin + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
2月
+ 9日 + 周四 + + 10日 + 周五 + + 11日 + 周五 + + 12日 + 周五 + + 13日 + 周五 + + 14日 + 周五 + + 15日 + 周五 + + 16日 + 周五 + + 17日 + 周五 + + 18日 + 周五 + + 19日 + 周五 + + 20日 + 周五 + + 21日 + 周五 +
+ + + + + + + + + + + + +
+
+
+ + + + + + + +
活动列表
+
+
+
+
+
+ + 「深境螺旋」· 2月上半 + 02-01 04:00 ~ 02-16 03:59 (5小时2分钟后结束) +
+
+
+
+ + 「深境螺旋」· 2月下半 + 02-16 04:00 (5小时2分钟后开始) +
+
+
+
+ +
+ + 「神铸赋形」祈愿:「护摩之杖」「若水」概率UP! + 02-07 18:00 ~ 02-28 14:59 (12天15小时56分钟后结束) +
+
+
+ +
+ + 「花时来信」神里绫华衣装限时折扣 + 01-18 11:00 ~ 02-27 03:59 (11天4小时56分钟后结束) +
+
+
+ +
+ + 「合韵纪行」活动说明 + 01-18 11:00 ~ 02-27 03:59 (11天4小时56分钟后结束) +
+
+
+ +
+ + 胡桃 + 02-07 18:00 ~ 02-28 14:59 (12天15小时56分钟后结束) +
+
+
+
+ + 【有奖活动】「磬弦奏华夜」3.4版本攻略征集活动 + 01-18 18:00 ~ 02-26 23:59 (11天55分钟后结束) +
+
+
+
+
+
+
+ 当前时间:2023-02-15 23:03 +
+ +
+ + \ No newline at end of file diff --git a/resources/genshin/calendar/img/abyss-icon.png b/resources/genshin/calendar/img/abyss-icon.png new file mode 100644 index 00000000..f658b514 Binary files /dev/null and b/resources/genshin/calendar/img/abyss-icon.png differ diff --git a/resources/genshin/calendar/img/abyss.jpg b/resources/genshin/calendar/img/abyss.jpg new file mode 100644 index 00000000..a9e67f7a Binary files /dev/null and b/resources/genshin/calendar/img/abyss.jpg differ diff --git a/resources/genshin/calendar/img/card-bg.png b/resources/genshin/calendar/img/card-bg.png new file mode 100644 index 00000000..036d416e Binary files /dev/null and b/resources/genshin/calendar/img/card-bg.png differ diff --git a/resources/genshin/calendar/img/fetter.png b/resources/genshin/calendar/img/fetter.png new file mode 100644 index 00000000..1ccdcf0b Binary files /dev/null and b/resources/genshin/calendar/img/fetter.png differ