@sjtdev/koishi-plugin-dota2tracker 2.5.2 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # 更新日志
2
2
 
3
+ ### [2.5.4](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.5.3...v2.5.4) (2026-02-26)
4
+
5
+ ### 🚀 功能优化
6
+
7
+ * **database:** 增加缓存群友列表机制,加入对拉取有效群友时的检测,每小时更新缓存 ([1ac1df9](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/1ac1df9513a883b17031fdc3e912fccc0af8a3e2)), closes [#17](https://github.com/sjtdev/koishi-plugin-dota2tracker/issues/17)
8
+
9
+ ### 🐛 Bug 修复
10
+
11
+ * **match-watcher:** 修复无有效群友时还会发送查询请求 ([a8d1e8c](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/a8d1e8c173d71b850516912729e398943d3ea391))
12
+
13
+ ### [2.5.3](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.5.2...v2.5.3) (2026-02-23)
14
+
15
+ ### 🎨 样式
16
+
17
+ * **report/daily:** 缩窄整体宽度 ([ef756e0](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/ef756e0f418715a63b50586653221f92c25afd91))
18
+
19
+ ### 🐛 Bug 修复
20
+
21
+ * 尝试修复在**异常状态群组**中获取群友信息时失败导致流程阻塞的问题 ([f00841d](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/f00841dd82794afd69987926804147a4b49fbfee)), closes [#17](https://github.com/sjtdev/koishi-plugin-dota2tracker/issues/17)
22
+ * **database:** 修改`dt_match_extension`键类型定义排除某些可能的隐患 ([72c0bbe](https://github.com/sjtdev/koishi-plugin-dota2tracker/commit/72c0bbebc0faf540aea29c8e2e4ae8210ab13b3e))
23
+
3
24
  ### [2.5.2](https://github.com/sjtdev/koishi-plugin-dota2tracker/compare/v2.5.1...v2.5.2) (2026-02-20)
4
25
 
5
26
  ### 🐛 Bug 修复
package/lib/index.js CHANGED
@@ -1627,7 +1627,7 @@ var MatchService = class _MatchService extends import_koishi4.Service {
1627
1627
  const facetData = await _MatchService.constantsInjectFacetData(constantsQuery, matchQuery, languageTag, this.ctx.dota2tracker.hero);
1628
1628
  this.ctx.dota2tracker.cache.setFacetConstantsCache(languageTag, constantsQuery);
1629
1629
  const match = _MatchService.extendMatchData(matchQuery, facetData, this.ctx.dota2tracker.dotaconstants);
1630
- this.ctx.dota2tracker.report.recordMatchExtension(match);
1630
+ this.recordMatchExtension(match);
1631
1631
  return match;
1632
1632
  } catch (error) {
1633
1633
  this.ctx.dota2tracker.cache.deleteFacetConstantsCache(languageTag);
@@ -1892,6 +1892,21 @@ var MatchService = class _MatchService extends import_koishi4.Service {
1892
1892
  static isMatchParsed(matchQuery) {
1893
1893
  return matchQuery?.match?.parsedDateTime && matchQuery?.match?.players.filter((player) => player?.stats?.heroDamageReport?.dealtTotal).length > 0;
1894
1894
  }
1895
+ async recordMatchExtension(match) {
1896
+ const extensionData = { matchId: match.id, players: [] };
1897
+ for (const player of match.players) {
1898
+ extensionData.players.push({
1899
+ steamAccountId: player.steamAccountId,
1900
+ rankSnapshot: player.rank,
1901
+ mvpScore: player.mvpScore,
1902
+ titles: player.titles,
1903
+ utilityScore: player.utilityScore,
1904
+ laneResult: player.laneResult,
1905
+ partyId: player.partyId
1906
+ });
1907
+ }
1908
+ this.ctx.dota2tracker.database.insertMatchExtension(extensionData.matchId, new Date(match.startDateTime * 1e3), extensionData);
1909
+ }
1895
1910
  };
1896
1911
  function createItemObject(itemId, purchaseTimesMap, purchaseTimeIndices, dotaconstants) {
1897
1912
  if (itemId === void 0 || itemId === null) {
@@ -2298,13 +2313,13 @@ var DatabaseService = class extends import_koishi7.Service {
2298
2313
  }
2299
2314
  constructor(ctx) {
2300
2315
  super(ctx, "dota2tracker.database", true);
2301
- ctx.model.extend("dt_subscribed_guilds", { id: "unsigned", guildId: "string", platform: "string" }, { autoInc: true });
2316
+ ctx.model.extend("dt_subscribed_guilds", { id: "unsigned", channelId: { type: "string", legacy: ["guildId"] }, platform: "string" }, { autoInc: true });
2302
2317
  ctx.model.extend(
2303
2318
  "dt_subscribed_players",
2304
2319
  {
2305
2320
  id: "unsigned",
2306
2321
  userId: "string",
2307
- guildId: "string",
2322
+ channelId: { type: "string", legacy: ["guildId"] },
2308
2323
  platform: "string",
2309
2324
  steamId: "integer",
2310
2325
  nickName: "string",
@@ -2312,21 +2327,82 @@ var DatabaseService = class extends import_koishi7.Service {
2312
2327
  },
2313
2328
  { autoInc: true }
2314
2329
  );
2315
- ctx.model.extend("dt_match_extension", { matchId: "unsigned", startTime: "timestamp", data: "json" }, { autoInc: false, primary: ["matchId"] });
2330
+ ctx.model.extend("dt_match_extension", { matchId: "string", startTime: "timestamp", data: "json" }, { autoInc: false, primary: ["matchId"] });
2331
+ ctx.cron("0 * * * *", async () => {
2332
+ await this.refreshAllGuildMemberCaches();
2333
+ });
2334
+ }
2335
+ /** 群成员列表内存缓存: Map<"platform:channelId", Set<userId>> */
2336
+ guildMemberCache = /* @__PURE__ */ new Map();
2337
+ /**
2338
+ * 刷新指定频道的群成员缓存。
2339
+ * 通过 channel 表获取 assignee(bot selfId),再调用 bot.getGuildMemberList。
2340
+ * 失败时记录 warn 并清除该 key(保守策略)。
2341
+ */
2342
+ async refreshGuildMemberCache(platform, channelId) {
2343
+ const cacheKey = `${platform}:${channelId}`;
2344
+ try {
2345
+ const channelRow = (await this.ctx.database.get("channel", { id: channelId })).at(0);
2346
+ const selfId = channelRow?.assignee;
2347
+ const guildId = channelRow?.guildId ?? channelId;
2348
+ if (!selfId) {
2349
+ this.guildMemberCache.delete(cacheKey);
2350
+ return;
2351
+ }
2352
+ const bot = this.ctx.bots[`${platform}:${selfId}`];
2353
+ if (!bot) {
2354
+ this.guildMemberCache.delete(cacheKey);
2355
+ return;
2356
+ }
2357
+ const members = await bot.getGuildMemberList(guildId);
2358
+ const userIds = new Set(members.data.map((m) => m.user.id));
2359
+ this.guildMemberCache.set(cacheKey, userIds);
2360
+ } catch (error) {
2361
+ this.logger.warn(`获取频道 ${cacheKey} 成员列表失败,已清除对应缓存:` + error);
2362
+ this.guildMemberCache.delete(cacheKey);
2363
+ }
2364
+ }
2365
+ /** 全量刷新所有已订阅频道的群成员缓存(每小时 cron 调用) */
2366
+ async refreshAllGuildMemberCaches() {
2367
+ const subscribedChannels = await this.ctx.database.get("dt_subscribed_guilds", void 0);
2368
+ await Promise.allSettled(subscribedChannels.map((ch) => this.refreshGuildMemberCache(ch.platform, ch.channelId)));
2369
+ }
2370
+ /**
2371
+ * 判断玩家是否仍在群组中。
2372
+ * 缓存未命中(冷启动阶段)时保守放行,返回 true。
2373
+ */
2374
+ isPlayerInGuild(platform, channelId, userId) {
2375
+ const cached = this.guildMemberCache.get(`${platform}:${channelId}`);
2376
+ if (!cached) return true;
2377
+ return cached.has(userId);
2378
+ }
2379
+ /** 删除指定频道的群成员缓存(取消订阅时内部调用) */
2380
+ deleteGuildMemberCache(platform, channelId) {
2381
+ this.guildMemberCache.delete(`${platform}:${channelId}`);
2382
+ }
2383
+ /** 从缓存中移除单个用户(取消绑定时内部调用,保留频道内其他成员的缓存)*/
2384
+ removeUserFromGuildMemberCache(platform, channelId, userId) {
2385
+ const cached = this.guildMemberCache.get(`${platform}:${channelId}`);
2386
+ if (cached) cached.delete(userId);
2316
2387
  }
2317
2388
  async insertMatchExtension(matchId, startTime, data) {
2318
- return this.ctx.database.upsert("dt_match_extension", [{ matchId, startTime, data }]);
2389
+ return this.ctx.database.upsert("dt_match_extension", [{ matchId: String(matchId), startTime, data }]);
2319
2390
  }
2320
2391
  async getMatchExtension(matchIds) {
2321
- return this.ctx.database.get("dt_match_extension", { matchId: matchIds });
2392
+ const rows = await this.ctx.database.get("dt_match_extension", {
2393
+ matchId: matchIds.map((id) => String(id))
2394
+ });
2395
+ return rows.map((row) => ({
2396
+ ...row,
2397
+ matchId: Number(row.matchId)
2398
+ }));
2322
2399
  }
2323
2400
  async setPlayerRank(playerId, rank) {
2324
2401
  return this.ctx.database.set("dt_subscribed_players", playerId, { rank });
2325
2402
  }
2326
2403
  async getActiveSubscribedPlayers() {
2327
- const subscribedGuilds = await this.ctx.database.get("dt_subscribed_guilds", void 0);
2328
- const subscribedPlayersInGuild = (await this.ctx.database.get("dt_subscribed_players", void 0)).filter((player) => subscribedGuilds.some((guild) => guild.guildId == player.guildId));
2329
- return subscribedPlayersInGuild;
2404
+ const subscribedChannels = await this.ctx.database.get("dt_subscribed_guilds", void 0);
2405
+ return (await this.ctx.database.get("dt_subscribed_players", void 0)).filter((player) => subscribedChannels.some((ch) => ch.channelId == player.channelId)).filter((player) => this.isPlayerInGuild(player.platform, player.channelId, player.userId));
2330
2406
  }
2331
2407
  async isUserBinded(session) {
2332
2408
  const subscribedPlayer = await this.ctx.database.get("dt_subscribed_players", this.getUserQuery(session));
@@ -2337,23 +2413,32 @@ var DatabaseService = class extends import_koishi7.Service {
2337
2413
  return subscribedPlayer.length > 0 ? subscribedPlayer[0] : null;
2338
2414
  }
2339
2415
  async bindUser(session, steamId, nickName) {
2340
- return this.ctx.database.create("dt_subscribed_players", {
2416
+ const result = await this.ctx.database.create("dt_subscribed_players", {
2341
2417
  ...this.getUserQuery(session),
2342
2418
  steamId: Number(steamId),
2343
2419
  nickName: nickName || ""
2344
2420
  });
2421
+ this.refreshGuildMemberCache(session.event.platform, session.event.channel.id);
2422
+ return result;
2345
2423
  }
2346
2424
  async unbindUser(session) {
2425
+ this.removeUserFromGuildMemberCache(session.event.platform, session.event.channel.id, session.event.user.id);
2347
2426
  return this.ctx.database.remove("dt_subscribed_players", this.getUserQuery(session));
2348
2427
  }
2428
+ async renamePlayer(playerId, nickName) {
2429
+ return this.ctx.database.set("dt_subscribed_players", playerId, { nickName });
2430
+ }
2349
2431
  async isChannelSubscribed(session) {
2350
2432
  const subscribedChannels = await this.ctx.database.get("dt_subscribed_guilds", this.getChannelQuery(session));
2351
2433
  return subscribedChannels.length > 0;
2352
2434
  }
2353
2435
  async subscribeChannel(session) {
2354
- return this.ctx.database.create("dt_subscribed_guilds", this.getChannelQuery(session));
2436
+ const result = await this.ctx.database.create("dt_subscribed_guilds", this.getChannelQuery(session));
2437
+ this.refreshGuildMemberCache(session.event.platform, session.event.channel.id);
2438
+ return result;
2355
2439
  }
2356
2440
  async unSubscribeChannel(session) {
2441
+ this.deleteGuildMemberCache(session.event.platform, session.event.channel.id);
2357
2442
  return this.ctx.database.remove("dt_subscribed_guilds", this.getChannelQuery(session));
2358
2443
  }
2359
2444
  async getSubscribedMembersInChannel(session) {
@@ -2364,13 +2449,13 @@ var DatabaseService = class extends import_koishi7.Service {
2364
2449
  */
2365
2450
  getChannelQuery(session) {
2366
2451
  return {
2367
- guildId: session.event.channel.id,
2452
+ channelId: session.event.channel.id,
2368
2453
  platform: session.event.platform
2369
2454
  };
2370
2455
  }
2371
2456
  getUserQuery(session) {
2372
2457
  return {
2373
- guildId: session.event.channel.id,
2458
+ channelId: session.event.channel.id,
2374
2459
  platform: session.event.platform,
2375
2460
  userId: session.event.user.id
2376
2461
  };
@@ -2378,14 +2463,14 @@ var DatabaseService = class extends import_koishi7.Service {
2378
2463
  /** 从已订阅玩家中查找玩家返回SteamId,不需要以昵称匹配时仅需传入Session */
2379
2464
  async getSubscribedPlayerByNickNameOrSession(session, nickName) {
2380
2465
  const player = (await this.ctx.database.get("dt_subscribed_players", {
2381
- guildId: session.event.channel.id,
2466
+ channelId: session.event.channel.id,
2382
2467
  platform: session.event.platform,
2383
2468
  ...nickName ? { nickName } : { userId: session.event.user.id }
2384
2469
  }))?.[0];
2385
2470
  return player;
2386
2471
  }
2387
- getChannelInfo({ platform, guildId }) {
2388
- return `${platform}:${guildId}`;
2472
+ getChannelInfo({ platform, channelId }) {
2473
+ return `${platform}:${channelId}`;
2389
2474
  }
2390
2475
  };
2391
2476
 
@@ -2752,7 +2837,6 @@ var ViewRenderer = class extends import_koishi10.Service {
2752
2837
  $t: /* @__PURE__ */ __name((key, params) => this.ctx.dota2tracker.i18n.$t(languageTag, key, params), "$t"),
2753
2838
  languageTag,
2754
2839
  Random: import_koishi10.Random,
2755
- fontFamily: this.config.templateFonts.map((f) => `${f}`).join(", "),
2756
2840
  getImageUrl: this.getImageUrl.bind(this)
2757
2841
  };
2758
2842
  try {
@@ -3281,6 +3365,7 @@ var MatchWatcherTask = class extends import_koishi13.Service {
3281
3365
  async discovery() {
3282
3366
  try {
3283
3367
  const activePlayers = await this.ctx.dota2tracker.database.getActiveSubscribedPlayers();
3368
+ if (activePlayers.length === 0) return;
3284
3369
  const uniqueSteamIds = activePlayers.map((player) => player.steamId).filter((steamId, index, self) => self.indexOf(steamId) === index);
3285
3370
  const playersData = (await this.ctx.dota2tracker.stratzAPI.queryPlayersLastMatchRankInfo({ steamAccountIds: uniqueSteamIds })).players;
3286
3371
  await this.discoverNewMatches(playersData, activePlayers);
@@ -3300,10 +3385,10 @@ var MatchWatcherTask = class extends import_koishi13.Service {
3300
3385
  }
3301
3386
  const playersByGuild = /* @__PURE__ */ new Map();
3302
3387
  for (const player of relevantActivePlayers) {
3303
- const guildKey = `${player.platform}:${player.guildId}`;
3388
+ const guildKey = `${player.platform}:${player.channelId}`;
3304
3389
  if (!playersByGuild.has(guildKey)) {
3305
3390
  playersByGuild.set(guildKey, {
3306
- guildInfo: { platform: player.platform, channelId: player.guildId, guildId: player.guildId },
3391
+ guildInfo: { platform: player.platform, channelId: player.channelId },
3307
3392
  players: []
3308
3393
  });
3309
3394
  }
@@ -3324,7 +3409,7 @@ var MatchWatcherTask = class extends import_koishi13.Service {
3324
3409
  });
3325
3410
  messageToLogger.push({
3326
3411
  platform: guildInfo.platform,
3327
- guildId: guildInfo.guildId,
3412
+ guildId: guildInfo.channelId,
3328
3413
  players
3329
3414
  });
3330
3415
  subscribers.push(subscriber);
@@ -3359,9 +3444,14 @@ var MatchWatcherTask = class extends import_koishi13.Service {
3359
3444
  const prevRank = ranks["prevRank"];
3360
3445
  const currRank = ranks["currRank"];
3361
3446
  if (prevRank.medal !== currRank.medal || prevRank.star !== currRank.star && this.config.rankBroadStar || prevRank.leader !== currRank.leader && this.config.rankBroadLeader) {
3362
- const guildMember = await this.ctx.bots.find((bot) => bot.platform == subPlayer.platform)?.getGuildMember?.(subPlayer.guildId, subPlayer.userId);
3447
+ let guildMember;
3448
+ try {
3449
+ guildMember = await this.ctx.bots.find((bot) => bot.platform == subPlayer.platform)?.getGuildMember?.(subPlayer.channelId, subPlayer.userId);
3450
+ } catch (error) {
3451
+ this.logger.warn(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.fetch_guilds_failed") + error);
3452
+ }
3363
3453
  const name2 = subPlayer.nickName ?? guildMember?.nick ?? playersData.find((player) => player.steamAccount.id == subPlayer.steamId)?.steamAccount.name ?? String(subPlayer.steamId);
3364
- const languageTag = await this.ctx.dota2tracker.i18n.getLanguageTag({ channelId: subPlayer.guildId });
3454
+ const languageTag = await this.ctx.dota2tracker.i18n.getLanguageTag({ channelId: subPlayer.channelId });
3365
3455
  if (this.config.rankBroadFun === true) {
3366
3456
  const img = await this.ctx.dota2tracker.view.renderToImageByFile(
3367
3457
  {
@@ -3376,13 +3466,13 @@ var MatchWatcherTask = class extends import_koishi13.Service {
3376
3466
  "rank" /* Rank */,
3377
3467
  languageTag
3378
3468
  );
3379
- await this.ctx.broadcast([`${subPlayer.platform}:${subPlayer.guildId}`], img);
3469
+ await this.ctx.broadcast([`${subPlayer.platform}:${subPlayer.channelId}`], img);
3380
3470
  } else {
3381
3471
  const message = this.ctx.dota2tracker.messageBuilder.buildRankChangedMessage(languageTag, name2, prevRank, currRank);
3382
- await this.ctx.broadcast([`${subPlayer.platform}:${subPlayer.guildId}`], message);
3472
+ await this.ctx.broadcast([`${subPlayer.platform}:${subPlayer.channelId}`], message);
3383
3473
  }
3384
3474
  this.ctx.dota2tracker.database.setPlayerRank(subPlayer.id, rankMap.get(subPlayer.steamId));
3385
- this.logger.info(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.rank_sent", { platform: subPlayer.platform, guildId: subPlayer.guildId, player: { nickName: subPlayer.nickName, steamId: subPlayer.steamId } }));
3475
+ this.logger.info(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.rank_sent", { platform: subPlayer.platform, guildId: subPlayer.channelId, player: { nickName: subPlayer.nickName, steamId: subPlayer.steamId } }));
3386
3476
  }
3387
3477
  } else {
3388
3478
  this.ctx.dota2tracker.database.setPlayerRank(subPlayer.id, rankMap.get(subPlayer.steamId));
@@ -3426,8 +3516,7 @@ var ParsePollingTask = class extends import_koishi14.Service {
3426
3516
  source: "AUTOMATIC",
3427
3517
  type: "CHANNEL",
3428
3518
  platform: target.platform,
3429
- channelId: target.channelId,
3430
- guildId: target.guildId
3519
+ channelId: target.channelId
3431
3520
  };
3432
3521
  }
3433
3522
  add(matchId, subscribers) {
@@ -3529,15 +3618,15 @@ var ParsePollingTask = class extends import_koishi14.Service {
3529
3618
  }
3530
3619
  };
3531
3620
 
3532
- // src/app/tasks/report.task.ts
3621
+ // src/app/tasks/daily-report.task.ts
3533
3622
  var import_koishi15 = require("koishi");
3534
3623
  var import_luxon9 = require("luxon");
3535
- var ReportTask = class extends import_koishi15.Service {
3624
+ var DailyReportTask = class extends import_koishi15.Service {
3536
3625
  static {
3537
- __name(this, "ReportTask");
3626
+ __name(this, "DailyReportTask");
3538
3627
  }
3539
3628
  constructor(ctx) {
3540
- super(ctx, "dota2tracker.report-task", true);
3629
+ super(ctx, "dota2tracker.daily-report-task", true);
3541
3630
  this.config = ctx.config;
3542
3631
  if (this.config.dailyReportSwitch) {
3543
3632
  ctx.cron(`0 ${this.config.dailyReportHours} * * *`, async () => {
@@ -3551,7 +3640,7 @@ var ReportTask = class extends import_koishi15.Service {
3551
3640
  }
3552
3641
  }
3553
3642
  async runDailyJob() {
3554
- const bundles = await this.ctx.dota2tracker.report.generateDailyReportBundles();
3643
+ const bundles = await this.ctx.dota2tracker.dailyReport.generateDailyReportBundles();
3555
3644
  for (const bundle of bundles) {
3556
3645
  const image = await this.ctx.dota2tracker.view.renderToImageByFile(bundle.report, "daily", "report" /* Report */, await this.ctx.dota2tracker.i18n.getLanguageTag({ channelId: bundle.channelId }));
3557
3646
  await this.ctx.broadcast([`${bundle.platform}:${bundle.channelId}`], image);
@@ -3566,12 +3655,12 @@ var ReportTask = class extends import_koishi15.Service {
3566
3655
  }
3567
3656
  }
3568
3657
  async report_legacy(timeAgo, titleKey, showCombi) {
3569
- const subscribedGuilds = await this.ctx.database.get("dt_subscribed_guilds", void 0);
3570
- const subscribedPlayersInGuild = (await this.ctx.database.get("dt_subscribed_players", void 0)).filter((player) => subscribedGuilds.some((guild) => guild.guildId == player.guildId));
3571
- const steamIds = subscribedPlayersInGuild.map((player) => player.steamId).filter((value, index, self) => self.indexOf(value) === index);
3658
+ const subscribedChannels = await this.ctx.database.get("dt_subscribed_guilds", void 0);
3659
+ const subscribedPlayersInChannel = (await this.ctx.database.get("dt_subscribed_players", void 0)).filter((player) => subscribedChannels.some((ch) => ch.channelId == player.channelId));
3660
+ const steamIds = subscribedPlayersInChannel.map((player) => player.steamId).filter((value, index, self) => self.indexOf(value) === index);
3572
3661
  const players = (await this.ctx.dota2tracker.stratzAPI.queryPlayersMatchesForDaily_legacy(steamIds, timeAgo)).players.filter((player) => player.matches?.length > 0);
3573
3662
  const matches = players.map((player) => player.matches.map((match) => match)).flat().filter((item, index, self) => index === self.findIndex((t) => t.id === item.id));
3574
- for (let subPlayer of subscribedPlayersInGuild) {
3663
+ for (let subPlayer of subscribedPlayersInChannel) {
3575
3664
  let player = players.find((player2) => subPlayer.steamId == player2.steamAccount.id);
3576
3665
  if (!player) continue;
3577
3666
  let guildMember;
@@ -3590,8 +3679,8 @@ var ReportTask = class extends import_koishi15.Service {
3590
3679
  player.avgImp = roundToDecimalPlaces(player.matches.reduce((acc, match) => acc + match.players.find((innerPlayer) => innerPlayer.steamAccount.id == player.steamAccount.id).imp, 0) / player.matches.length, 0);
3591
3680
  subPlayer = Object.assign(subPlayer, player);
3592
3681
  }
3593
- for (let guild of subscribedGuilds) {
3594
- const currentsubscribedPlayers = subscribedPlayersInGuild.filter((player) => player.platform == guild.platform && player.guildId == guild.guildId && player.matches?.length);
3682
+ for (let channel of subscribedChannels) {
3683
+ const currentsubscribedPlayers = subscribedPlayersInChannel.filter((player) => player.platform == channel.platform && player.channelId == channel.channelId && player.matches?.length);
3595
3684
  if (currentsubscribedPlayers.length) {
3596
3685
  const currentsubscribedPlayersIds = currentsubscribedPlayers.map((player) => player.steamId);
3597
3686
  const combinationsMap = /* @__PURE__ */ new Map();
@@ -3617,9 +3706,9 @@ var ReportTask = class extends import_koishi15.Service {
3617
3706
  });
3618
3707
  const combinations = Array.from(combinationsMap.values());
3619
3708
  try {
3620
- const languageTag = await this.ctx.dota2tracker.i18n.getLanguageTag({ channelId: guild.guildId });
3709
+ const languageTag = await this.ctx.dota2tracker.i18n.getLanguageTag({ channelId: channel.channelId });
3621
3710
  await this.ctx.broadcast(
3622
- [`${guild.platform}:${guild.guildId}`],
3711
+ [`${channel.platform}:${channel.channelId}`],
3623
3712
  await this.ctx.dota2tracker.view.renderToImageByFile(
3624
3713
  {
3625
3714
  title: this.ctx.dota2tracker.i18n.$t(languageTag, titleKey),
@@ -3636,7 +3725,7 @@ var ReportTask = class extends import_koishi15.Service {
3636
3725
  languageTag
3637
3726
  )
3638
3727
  );
3639
- this.logger.info(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.report_sent", { title: this.ctx.dota2tracker.i18n.$t(languageTag, titleKey), guildId: guild.guildId, platform: guild.platform }));
3728
+ this.logger.info(this.ctx.dota2tracker.i18n.gt("dota2tracker.logger.report_sent", { title: this.ctx.dota2tracker.i18n.$t(languageTag, titleKey), guildId: channel.channelId, platform: channel.platform }));
3640
3729
  } catch (error) {
3641
3730
  this.logger.error(error);
3642
3731
  }
@@ -4000,7 +4089,7 @@ function registerUserCommand(ctx) {
4000
4089
  if (!session.isDirect) {
4001
4090
  const sessionPlayer = await ctx.dota2tracker.database.getBindedUser(session);
4002
4091
  if (sessionPlayer) {
4003
- await ctx.database.remove("dt_subscribed_players", sessionPlayer.id);
4092
+ ctx.dota2tracker.database.unbindUser(session);
4004
4093
  return session.text(".unbind_success");
4005
4094
  } else {
4006
4095
  return session.text(".not_binded");
@@ -4020,7 +4109,7 @@ function registerUserCommand(ctx) {
4020
4109
  if (nick_name === sessionPlayer.nickName) {
4021
4110
  return session.text(".nick_name_same");
4022
4111
  }
4023
- await ctx.database.set("dt_subscribed_players", sessionPlayer.id, { nickName: nick_name });
4112
+ await ctx.dota2tracker.database.renamePlayer(sessionPlayer.id, nick_name);
4024
4113
  return session.text(".rename_success", { nick_name });
4025
4114
  } else {
4026
4115
  return session.text(".not_binded");
@@ -4551,32 +4640,17 @@ function convertBuildingEvents(objectives) {
4551
4640
  }
4552
4641
  __name(convertBuildingEvents, "convertBuildingEvents");
4553
4642
 
4554
- // src/app/core/report.service.ts
4643
+ // src/app/core/daily-report.service.ts
4555
4644
  var import_koishi18 = require("koishi");
4556
4645
  var import_luxon11 = require("luxon");
4557
- var ReportService = class _ReportService extends import_koishi18.Service {
4646
+ var DailyReportService = class _DailyReportService extends import_koishi18.Service {
4558
4647
  static {
4559
- __name(this, "ReportService");
4648
+ __name(this, "DailyReportService");
4560
4649
  }
4561
4650
  constructor(ctx) {
4562
- super(ctx, "dota2tracker.report", true);
4651
+ super(ctx, "dota2tracker.daily-report", true);
4563
4652
  this.config = ctx.config;
4564
4653
  }
4565
- async recordMatchExtension(match) {
4566
- const extensionData = { matchId: match.id, players: [] };
4567
- for (const player of match.players) {
4568
- extensionData.players.push({
4569
- steamAccountId: player.steamAccountId,
4570
- rankSnapshot: player.rank,
4571
- mvpScore: player.mvpScore,
4572
- titles: player.titles,
4573
- utilityScore: player.utilityScore,
4574
- laneResult: player.laneResult,
4575
- partyId: player.partyId
4576
- });
4577
- }
4578
- this.ctx.dota2tracker.database.insertMatchExtension(extensionData.matchId, new Date(match.startDateTime * 1e3), extensionData);
4579
- }
4580
4654
  /**
4581
4655
  * 入口函数,返回报告数据
4582
4656
  */
@@ -4590,7 +4664,7 @@ var ReportService = class _ReportService extends import_koishi18.Service {
4590
4664
  const allMatchIds = [...new Set(data.players.flatMap((p) => (p.matches || []).map((m) => m.id)))].map((id) => Number(id));
4591
4665
  const extensions = await this.ctx.dota2tracker.database.getMatchExtension(allMatchIds);
4592
4666
  const getImageUrl = this.ctx.dota2tracker.view.getImageUrl.bind(this.ctx.dota2tracker.view);
4593
- return await _ReportService.formatDailyReportBundles(
4667
+ return await _DailyReportService.formatDailyReportBundles(
4594
4668
  data,
4595
4669
  users,
4596
4670
  extensions,
@@ -4673,7 +4747,7 @@ var ReportService = class _ReportService extends import_koishi18.Service {
4673
4747
  static groupUsersByChannel(users) {
4674
4748
  const groups = /* @__PURE__ */ new Map();
4675
4749
  for (const user of users) {
4676
- const key = `${user.platform}:${user.guildId}`;
4750
+ const key = `${user.platform}:${user.channelId}`;
4677
4751
  if (!groups.has(key)) groups.set(key, []);
4678
4752
  groups.get(key).push(user);
4679
4753
  }
@@ -5001,7 +5075,7 @@ async function apply(ctx, config) {
5001
5075
  if (ctx.cron) {
5002
5076
  ctx.dota2tracker.matchWatcher = new MatchWatcherTask(ctx);
5003
5077
  ctx.dota2tracker.parsePolling = new ParsePollingTask(ctx);
5004
- ctx.dota2tracker.reportTask = new ReportTask(ctx);
5078
+ ctx.dota2tracker.dailyReportTask = new DailyReportTask(ctx);
5005
5079
  ctx.cron("* * * * *", async () => {
5006
5080
  await ctx.dota2tracker.matchWatcher.discovery();
5007
5081
  await ctx.dota2tracker.parsePolling.polling();
@@ -5011,7 +5085,7 @@ async function apply(ctx, config) {
5011
5085
  }
5012
5086
  ctx.dota2tracker.hero = new HeroService(ctx);
5013
5087
  ctx.dota2tracker.item = new ItemService(ctx);
5014
- ctx.dota2tracker.report = new ReportService(ctx);
5088
+ ctx.dota2tracker.dailyReport = new DailyReportService(ctx);
5015
5089
  ctx.dota2tracker.cache = new CacheService(ctx);
5016
5090
  ctx.dota2tracker.database = new DatabaseService(ctx);
5017
5091
  ctx.dota2tracker.valveAPI = new ValveAPI(ctx);
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Document</title> <%- `<style>` %> <%- include("../common/styles/normalize.min.css") %> <%- include(`./hero_1/base.css`) %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body> <% let hero = data; %> <% if (hero.primary_attr==3) {
1
+ <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Document</title> <%- `<style>` %> <%- include("../common/styles/normalize.min.css") %> <%- include(`./hero_1/base.css`) %> <%- `</style>` %> </head><body> <% let hero = data; %> <% if (hero.primary_attr==3) {
2
2
  const base_damage = Math.floor((hero.str_base+hero.agi_base+hero.int_base)*0.45);
3
3
  hero.damage_max+=base_damage;
4
4
  hero.damage_min+=base_damage;
@@ -41,7 +41,7 @@
41
41
  // 预计算数据
42
42
  const parsedAbilities = parseAbilityDesc(item.desc_loc);
43
43
  const processedNotes = (item.notes_loc || []).map(processText);
44
- %> <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <%- `<style>` %> <%- include('./item/style.css') %> <% if (fontFamily) { %> <%- `body { font-family: ${fontFamily}; }` %> <% } %> <%- `</style>` %> </head><body><div class="container"><div class="header"><img src="<%= getImageUrl(item.name, ImageType.Items) %>"><div><p class="name"><%= item.name_loc %> <span class="item_id"><%= item.name %></span></p> <% if (item.item_cost) { %> <p class="cost"><img style="height:20px;width:auto;margin-right:4px" src="<%= getImageUrl("gold", ImageType.Icons) %>"> <%= item.item_cost %> </p> <% } %> </div></div> <% if (citem?.abilities?.length) { %> <div class="stats"><p class="behavior"> <%= $t("dota2tracker.template.ability") %> <% if (citem.abilities.some(ability => capitalize(ability.type) === 'Active')) { %> <%= toArray(citem.behavior).map((beh) => $t("dota2tracker.template.behavior." + beh)).join("/") %> <% } else { %> <%= $t("dota2tracker.template.behavior.Passive") %> <% } %> </p> <% if (citem.target_team && citem.target_team.length) { %> <p class="target_team"> <%= $t("dota2tracker.template.affects") %> <%= toArray(citem.target_team).map((tt) => $t("dota2tracker.template.target_team." + tt)).join("/") %> </p> <% } %> <% if (citem.dmg_type) { %> <p class="dmg_type"> <%= $t("dota2tracker.template.damage_type") %> <span class="<%= citem.dmg_type %>"><%= $t("dota2tracker.template.damage_type_" + citem.dmg_type) %></span></p> <% } %> <% if (citem.dispellable) { %> <p class="dispellable"> <%= $t("dota2tracker.template.dispellable") %> <span class="<%= citem.dispellable %>"> <%= $t("dota2tracker.template." + (citem.dispellable == "Strong Dispels Only" ? "dispellable_Strong" : citem.dispellable)) %> </span></p> <% } %> <% if (citem.bkbpierce) { %> <p class="bkbpierce"> <%= $t("dota2tracker.template.bkbpierce") %> <span class="<%= citem.bkbpierce %>"><%= $t("dota2tracker.template." + citem.bkbpierce) %></span></p> <% } %> </div> <% } %> <div class="attrs"> <% item.special_values.filter(value => value.heading_loc).forEach(value => {
44
+ %> <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <%- `<style>` %> <%- include('./item/style.css') %> <%- `</style>` %> </head><body><div class="container"><div class="header"><img src="<%= getImageUrl(item.name, ImageType.Items) %>"><div><p class="name"><%= item.name_loc %> <span class="item_id"><%= item.name %></span></p> <% if (item.item_cost) { %> <p class="cost"><img style="height:20px;width:auto;margin-right:4px" src="<%= getImageUrl("gold", ImageType.Icons) %>"> <%= item.item_cost %> </p> <% } %> </div></div> <% if (citem?.abilities?.length) { %> <div class="stats"><p class="behavior"> <%= $t("dota2tracker.template.ability") %> <% if (citem.abilities.some(ability => capitalize(ability.type) === 'Active')) { %> <%= toArray(citem.behavior).map((beh) => $t("dota2tracker.template.behavior." + beh)).join("/") %> <% } else { %> <%= $t("dota2tracker.template.behavior.Passive") %> <% } %> </p> <% if (citem.target_team && citem.target_team.length) { %> <p class="target_team"> <%= $t("dota2tracker.template.affects") %> <%= toArray(citem.target_team).map((tt) => $t("dota2tracker.template.target_team." + tt)).join("/") %> </p> <% } %> <% if (citem.dmg_type) { %> <p class="dmg_type"> <%= $t("dota2tracker.template.damage_type") %> <span class="<%= citem.dmg_type %>"><%= $t("dota2tracker.template.damage_type_" + citem.dmg_type) %></span></p> <% } %> <% if (citem.dispellable) { %> <p class="dispellable"> <%= $t("dota2tracker.template.dispellable") %> <span class="<%= citem.dispellable %>"> <%= $t("dota2tracker.template." + (citem.dispellable == "Strong Dispels Only" ? "dispellable_Strong" : citem.dispellable)) %> </span></p> <% } %> <% if (citem.bkbpierce) { %> <p class="bkbpierce"> <%= $t("dota2tracker.template.bkbpierce") %> <span class="<%= citem.bkbpierce %>"><%= $t("dota2tracker.template." + citem.bkbpierce) %></span></p> <% } %> </div> <% } %> <div class="attrs"> <% item.special_values.filter(value => value.heading_loc).forEach(value => {
45
45
  const match = value.heading_loc.match(/^([+-]?)(.*)/) || [];
46
46
  const sign = match[1] || '+';
47
47
  const rawText = match[2];
@@ -8,4 +8,4 @@ function calculateFontSize(text) {
8
8
  let fontSize = Math.floor((maxWidth / totalUnits) * 1.8);
9
9
  return Math.min(12, Math.max(6, fontSize));
10
10
  }
11
- %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/> <%- "<style>" %> * { margin: 0; padding: 0; } html, body { height: auto; background-color: #000; color: #fff; } html { width: 232px; } body { width: 220px; margin: 6px; } .container { width: 220px; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; row-gap: 8px; } .container img { width: 44px; height: 32px; } .container .item { width: 48px; height: 54px; display: flex; flex-direction: column; font-size: 12px; align-items: center; justify-content: space-around; } .name { overflow: hidden; text-overflow: ellipsis; text-align: center; line-height: 1.2; min-height: 12px; } <% if (fontFamily) { %> body { font-family: <%= fontFamily %>; } <% } %> <%- "</style>" %> </head><body><div class="container"> <% data.forEach(function(item) { %> <div class="item"><img src="<%= getImageUrl(item.name, ImageType.Items) %>"/><div class="name" <%- `style="font-size: ${calculateFontSize(item.name_loc)}px"` %>> <%= item.name_loc %> </div></div> <% }); %> </div></body></html>
11
+ %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/> <%- "<style>" %> * { margin: 0; padding: 0; } html, body { height: auto; background-color: #000; color: #fff; } html { width: 232px; } body { width: 220px; margin: 6px; } .container { width: 220px; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; row-gap: 8px; } .container img { width: 44px; height: 32px; } .container .item { width: 48px; height: 54px; display: flex; flex-direction: column; font-size: 12px; align-items: center; justify-content: space-around; } .name { overflow: hidden; text-overflow: ellipsis; text-align: center; line-height: 1.2; min-height: 12px; } <%- "</style>" %> </head><body><div class="container"> <% data.forEach(function(item) { %> <div class="item"><img src="<%= getImageUrl(item.name, ImageType.Items) %>"/><div class="name" <%- `style="font-size: ${calculateFontSize(item.name_loc)}px"` %>> <%= item.name_loc %> </div></div> <% }); %> </div></body></html>
@@ -15,4 +15,4 @@
15
15
  tie: getImageUrl("lane_tie", undefined, ImageFormat.svg),
16
16
  jungle: getImageUrl("lane_jungle", undefined, ImageFormat.svg),
17
17
  };
18
- %> <%- include("../common/utils/match") %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- `<style>` %> <%- include(`./match_1/base.css`) %> <%- include(`./match_1/style.css`) %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body class="bg-black w-[800px] text-white"> <%- include(`./match_1/main.ejs`, { match, partyColor, facetColor, kc, dc, laneSVG }) %> </body></html>
18
+ %> <%- include("../common/utils/match") %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- `<style>` %> <%- include(`./match_1/base.css`) %> <%- include(`./match_1/style.css`) %> <%- `</style>` %> </head><body class="bg-black w-[800px] text-white"> <%- include(`./match_1/main.ejs`, { match, partyColor, facetColor, kc, dc, laneSVG }) %> </body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- "<style>" %> <%- include("../common/styles/normalize.min.css") %> <%- include("./match_2/original.css") %> <%- include("./match_2+/extra.css") %> <% if (fontFamily) { %> <%- `body { font-family: ${fontFamily}; }` %> <% } %> <%- "</style>" %> </head><body><section id="regular"><%- include('./match_2/original') %></section><section id="extra"> <% if (data.parsedDateTime && data.radiantNetworthLeads && data.radiantExperienceLeads) { %> <%- include('./match_2+/charts') %> <div class="container"> <%- include('./match_2+/map') %> <%- include('./match_2+/lane_outcome') %> </div> <% } else { %> <div class="tip"><%= $t("dota2tracker.template.empty_extra_info") %></div> <% } %> </section></body></html>
1
+ <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- "<style>" %> <%- include("../common/styles/normalize.min.css") %> <%- include("./match_2/original.css") %> <%- include("./match_2+/extra.css") %> <%- "</style>" %> </head><body><section id="regular"><%- include('./match_2/original') %></section><section id="extra"> <% if (data.parsedDateTime && data.radiantNetworthLeads && data.radiantExperienceLeads) { %> <%- include('./match_2+/charts') %> <div class="container"> <%- include('./match_2+/map') %> <%- include('./match_2+/lane_outcome') %> </div> <% } else { %> <div class="tip"><%= $t("dota2tracker.template.empty_extra_info") %></div> <% } %> </section></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- "<style>" %> <%- include("../common/styles/normalize.min.css") %> <%- include("./match_2/original.css") %> <% if (fontFamily) { %> <%- `body { font-family: ${fontFamily}; }` %> <% } %> <%- "</style>" %> </head><body> <%- include('./match_2/original') %> </body></html>
1
+ <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Match</title> <%- "<style>" %> <%- include("../common/styles/normalize.min.css") %> <%- include("./match_2/original.css") %> <%- "</style>" %> </head><body> <%- include('./match_2/original') %> </body></html>
@@ -75,4 +75,4 @@
75
75
  const impAvg = posMatches.length > 0 ? Math.round(posMatches.reduce((acc, m) => acc + m.players.find(ip => ip.steamAccount.id == p.steamAccount.id).imp, 0) / posMatches.length) : "-";
76
76
  p.positionPerformance.push({ pos: i + 1, icon: posIcons[i], matchCount: posMatches.length, winCount: wins, imp: impAvg });
77
77
  }
78
- %> <% const posScale = getScale(p.positionPerformance.filter(pos => pos.matchCount > 1), true); %> <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Player</title> <%- `<style>` %> <%- include("../common/styles/normalize.min.css") %> <%- include(`./player_1/base.css`) %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body><div class="wrapper"><div class="player"><div class="avatar"><img src="<%= p.steamAccount?.avatar %>" alt=""/></div><div class="info"><p class="name"> <%= p.steamAccount.name %> <% if (p.guildMember) { %> <span class="guild <%= getGuildClass(p.guildMember.guild.currentPercentile) %>"><%= p.guildMember.guild.tag %></span> <% } %> <% if (p.genHero) { %> <span class="hero_name"><%= p.genHero.name %></span> <% } %> </p><p class="matches"> <%= $t("dota2tracker.template.match_count_") %><%= p.matchCount %> (<span class="win"><%= p.winCount %></span>/<span class="lose"><%= p.matchCount - p.winCount %></span>) &nbsp;&nbsp; <%= $t("dota2tracker.template.winrate_") %> <span <%- `style="color:${getWinRateColor(p.winCount / p.matchCount)};"` %>><%= ((p.winCount / p.matchCount) * 100).toFixed(2) %>%</span></p><p class="matches<%= isAnon ? " blur" : "" %>"><span><%= $t("dota2tracker.template.last25matches_") %> <span class="win"><%= nearWin %></span>/<span class="lose"><%= nearCount - nearWin %></span></span>&nbsp; <span><%= $t("dota2tracker.template.winrate_") %> <span <%- `style="color:${getWinRateColor(nearWin / nearCount)};"` %>><%= ((nearWin / nearCount) * 100).toFixed(2) %>%</span> </span>&nbsp; <span><%= $t("dota2tracker.template.imp_") %><%= isAnon ? "?" : ((p.performance?.imp > 0 ? "+" : "") + p.performance?.imp) %></span></p><p class="matches<%= isAnon ? " blur" : "" %>"><span><%= $t("dota2tracker.template.lane") %> <span class="victory"><%= outcomes.victory + outcomes.stomp %>(<span class="stomp"><%= outcomes.stomp %></span>)</span>-<span class="tie"><%= outcomes.tie %></span>-<span class="fail"><%= outcomes.fail + outcomes.stomped %>(<span class="stomped"><%= outcomes.stomped %></span>)</span> </span>&nbsp;&nbsp; <% const laneTotal = outcomes.victory + outcomes.stomp + outcomes.tie + outcomes.fail + outcomes.stomped; %> <% const laneScore = (outcomes.victory + outcomes.stomp + outcomes.tie / 2) / laneTotal; %> <span><%= $t("dota2tracker.template.lane_advantage_rate_") %> <span <%- `style="color:${getWinRateColor(laneScore)};"` %>><%= isAnon ? "?" : (laneScore * 100).toFixed(2) %>%</span></span></p></div><div class="rank<%= p.isEstimatedRank ? " estimated" : "" %>"><img class="medal" src="<%= getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>" alt=""/> <img class="star" src="<%= getImageUrl('star_' + p.rank.star) %>" alt=""/><p><%= p.steamAccount.seasonLeaderboardRank ?? "" %></p></div></div><div class="hero_winrate"><div class="heroes"><p class="tip row total"><%= p.genHero ? $t("dota2tracker.template.all_matches_") : $t("dota2tracker.template.top10_") %></p><span class="tip"><%= $t("dota2tracker.template.hero") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.match_count") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.winrate") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.imp") %></span><span class="tip win_count" style="justify-self:end;margin-right:2px"><%= $t("dota2tracker.template.win_count") %></span><span class="tip lose_count" style="justify-self:start;margin-left:2px"><%= $t("dota2tracker.template.lose_count") %></span> <% heroList.forEach(hero => { %> <span><img alt="" src="<%= getImageUrl(hero.hero.shortName, ImageType.HeroIcons) %>"/></span><span class="count"><%= hero.matchCount %></span><span class="win_rate"><%= ((hero.winCount / hero.matchCount) * 100).toFixed(0) %>%</span> <span class="imp"><%= (hero.imp > 0 ? "+" : "") + hero.imp %></span><span class="win" <%- `style="${hero.winCount == 0 ? "visibility:hidden;" : ""}width: ${hero.winCount * heroScale}px"` %>><%= hero.winCount %></span><span class="lose" <%- `style="${hero.matchCount - hero.winCount == 0 ? "visibility:hidden;" : ""}width: ${(hero.matchCount - hero.winCount) * heroScale}px"` %>><%= hero.matchCount - hero.winCount %></span> <% }); %> <p class="tip row near"><%= $t("dota2tracker.template.recently_positions") %></p><span class="tip"><%= $t("dota2tracker.template.position") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.match_count") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.winrate") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.imp") %></span><span class="tip win_count" style="justify-self:end;margin-right:2px"><%= $t("dota2tracker.template.win_count") %></span><span class="tip lose_count" style="justify-self:start;margin-left:2px"><%= $t("dota2tracker.template.lose_count") %></span> <% p.positionPerformance.forEach(pos => { %> <span><img src="<%= getImageUrl(pos.icon, ImageType.IconsFacets) %>"></span><span class="count"><%= pos.matchCount %></span><span class="win_rate"><%= pos.matchCount > 0 ? ((pos.winCount / pos.matchCount) * 100).toFixed(0) : "-" %>%</span> <span class="imp"><%= (pos.imp > 0 ? "+" : "") + pos.imp %></span><span class="win" <%- `style="${pos.winCount == 0 ? "visibility:hidden;" : ""}width: ${pos.winCount * posScale}px"` %>><%= pos.winCount %></span><span class="lose" <%- `style="${pos.matchCount - pos.winCount == 0 ? "visibility:hidden;" : ""}width: ${(pos.matchCount - pos.winCount) * posScale}px"` %>><%= pos.matchCount - pos.winCount %></span> <% }); %> </div> <% if (isAnon) { %> <%- include("player_1/private", {$t, player: p}) %> <% } %> </div> <% if (Math.abs(p.streak) > 1 && !isAnon) { %> <div class="streak" <%- `style="box-shadow:none;color:${getWinRateColor((p.streak + 10) / 20)};"` %>> <%= Math.abs(p.streak) + (p.streak > 0 ? $t("dota2tracker.template.winning_streak") : $t("dota2tracker.template.losing_streak")) %> </div> <% } %> <div><table class="matches"><colgroup><col style="width:auto"/><col style="width:auto"/><col style="width:40px"/><col style="width:auto"/><col style="width:40px"/><col style="width:auto"/><col style="width:auto"/><col style="width:auto"/><col style="width:40px"/></colgroup><thead><tr><th><%= $t("dota2tracker.template.id") %></th><th><%= $t("dota2tracker.template.mode") %></th><th><%= $t("dota2tracker.template.hero") %></th><th><%= $t("dota2tracker.template.kda_kc") %></th><th><%= $t("dota2tracker.template.lane") %></th><th><%= $t("dota2tracker.template.time") %></th><th><%= $t("dota2tracker.template.duration") %></th><th><%= $t("dota2tracker.template.imp") %></th><th><%= $t("dota2tracker.template.rank") %></th></tr></thead><tbody> <% matchesData.forEach(m => { %> <tr class="match <%= m.didWin ? "win" : "lose" %>"><td><%- m.parsedDateTime ? m.id : `<p>${m.id}</p><p>${$t("dota2tracker.template.un_parsed")}</p>` %></td><td><p><%= $t("dota2tracker.template.lobby_types." + m.lobbyType) || m.lobbyType %></p><p><%= $t("dota2tracker.template.game_modes." + m.gameMode) || m.gameMode %></p></td><td><img alt="" src="<%= getImageUrl(m.inner.hero.shortName, ImageType.HeroIcons) %>"/></td><td style="line-height:20px"><p><%= m.kda %> (<%= (m.parsedDateTime ? "" : "≈") + m.kp %>%)</p><p><%= m.inner.kills %>/<%= m.inner.deaths %>/<%= m.inner.assists %></p></td><td><div class="player_lane <%= m.laneRes %>"><%- getImageUrl("lane_" + m.laneRes, undefined, ImageFormat.svg) %></div></td><td style="line-height:20px"><%= m.timeStr %></td><td><%= m.durationTime %></td><td><%= m.inner.imp != null ? ((m.inner.imp >= 0 ? "+" : "") + m.inner.imp) : "?" %></td><td><img class="medal" src="<%= getImageUrl("medal_" + m.rank?.toString().split("")[0]) %>" style="width:100%"/></td></tr> <% }); %> </tbody></table> <% if (isAnon) { %> <%- include("player_1/private", {$t, player: p}) %> <% } %> </div><div class="plus"> <% p.dotaPlus.forEach(hero => { %> <div class="hero"><img src="<%= getImageUrl(hero.shortName, ImageType.Heroes) %>" alt=""/><div class="level"><img src="<%= getImageUrl("hero_badge_" + Math.ceil((hero.level + 1) / 6)) %>" alt=""/><span><%= hero.level %></span></div><span><%= ((hero.winCount / hero.matchCount) * 100).toFixed(2) %>%</span> <span><%= hero.matchCount %></span></div> <% }); %> <% if (isAnon) { %> <%- include("player_1/private", {$t, player: p}) %> <% } %> </div></div></body></html>
78
+ %> <% const posScale = getScale(p.positionPerformance.filter(pos => pos.matchCount > 1), true); %> <!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Player</title> <%- `<style>` %> <%- include("../common/styles/normalize.min.css") %> <%- include(`./player_1/base.css`) %> <%- `</style>` %> </head><body><div class="wrapper"><div class="player"><div class="avatar"><img src="<%= p.steamAccount?.avatar %>" alt=""/></div><div class="info"><p class="name"> <%= p.steamAccount.name %> <% if (p.guildMember) { %> <span class="guild <%= getGuildClass(p.guildMember.guild.currentPercentile) %>"><%= p.guildMember.guild.tag %></span> <% } %> <% if (p.genHero) { %> <span class="hero_name"><%= p.genHero.name %></span> <% } %> </p><p class="matches"> <%= $t("dota2tracker.template.match_count_") %><%= p.matchCount %> (<span class="win"><%= p.winCount %></span>/<span class="lose"><%= p.matchCount - p.winCount %></span>) &nbsp;&nbsp; <%= $t("dota2tracker.template.winrate_") %> <span <%- `style="color:${getWinRateColor(p.winCount / p.matchCount)};"` %>><%= ((p.winCount / p.matchCount) * 100).toFixed(2) %>%</span></p><p class="matches<%= isAnon ? " blur" : "" %>"><span><%= $t("dota2tracker.template.last25matches_") %> <span class="win"><%= nearWin %></span>/<span class="lose"><%= nearCount - nearWin %></span></span>&nbsp; <span><%= $t("dota2tracker.template.winrate_") %> <span <%- `style="color:${getWinRateColor(nearWin / nearCount)};"` %>><%= ((nearWin / nearCount) * 100).toFixed(2) %>%</span> </span>&nbsp; <span><%= $t("dota2tracker.template.imp_") %><%= isAnon ? "?" : ((p.performance?.imp > 0 ? "+" : "") + p.performance?.imp) %></span></p><p class="matches<%= isAnon ? " blur" : "" %>"><span><%= $t("dota2tracker.template.lane") %> <span class="victory"><%= outcomes.victory + outcomes.stomp %>(<span class="stomp"><%= outcomes.stomp %></span>)</span>-<span class="tie"><%= outcomes.tie %></span>-<span class="fail"><%= outcomes.fail + outcomes.stomped %>(<span class="stomped"><%= outcomes.stomped %></span>)</span> </span>&nbsp;&nbsp; <% const laneTotal = outcomes.victory + outcomes.stomp + outcomes.tie + outcomes.fail + outcomes.stomped; %> <% const laneScore = (outcomes.victory + outcomes.stomp + outcomes.tie / 2) / laneTotal; %> <span><%= $t("dota2tracker.template.lane_advantage_rate_") %> <span <%- `style="color:${getWinRateColor(laneScore)};"` %>><%= isAnon ? "?" : (laneScore * 100).toFixed(2) %>%</span></span></p></div><div class="rank<%= p.isEstimatedRank ? " estimated" : "" %>"><img class="medal" src="<%= getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>" alt=""/> <img class="star" src="<%= getImageUrl('star_' + p.rank.star) %>" alt=""/><p><%= p.steamAccount.seasonLeaderboardRank ?? "" %></p></div></div><div class="hero_winrate"><div class="heroes"><p class="tip row total"><%= p.genHero ? $t("dota2tracker.template.all_matches_") : $t("dota2tracker.template.top10_") %></p><span class="tip"><%= $t("dota2tracker.template.hero") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.match_count") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.winrate") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.imp") %></span><span class="tip win_count" style="justify-self:end;margin-right:2px"><%= $t("dota2tracker.template.win_count") %></span><span class="tip lose_count" style="justify-self:start;margin-left:2px"><%= $t("dota2tracker.template.lose_count") %></span> <% heroList.forEach(hero => { %> <span><img alt="" src="<%= getImageUrl(hero.hero.shortName, ImageType.HeroIcons) %>"/></span><span class="count"><%= hero.matchCount %></span><span class="win_rate"><%= ((hero.winCount / hero.matchCount) * 100).toFixed(0) %>%</span> <span class="imp"><%= (hero.imp > 0 ? "+" : "") + hero.imp %></span><span class="win" <%- `style="${hero.winCount == 0 ? "visibility:hidden;" : ""}width: ${hero.winCount * heroScale}px"` %>><%= hero.winCount %></span><span class="lose" <%- `style="${hero.matchCount - hero.winCount == 0 ? "visibility:hidden;" : ""}width: ${(hero.matchCount - hero.winCount) * heroScale}px"` %>><%= hero.matchCount - hero.winCount %></span> <% }); %> <p class="tip row near"><%= $t("dota2tracker.template.recently_positions") %></p><span class="tip"><%= $t("dota2tracker.template.position") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.match_count") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.winrate") %></span><span class="tip" style="margin:0 4px"><%= $t("dota2tracker.template.imp") %></span><span class="tip win_count" style="justify-self:end;margin-right:2px"><%= $t("dota2tracker.template.win_count") %></span><span class="tip lose_count" style="justify-self:start;margin-left:2px"><%= $t("dota2tracker.template.lose_count") %></span> <% p.positionPerformance.forEach(pos => { %> <span><img src="<%= getImageUrl(pos.icon, ImageType.IconsFacets) %>"></span><span class="count"><%= pos.matchCount %></span><span class="win_rate"><%= pos.matchCount > 0 ? ((pos.winCount / pos.matchCount) * 100).toFixed(0) : "-" %>%</span> <span class="imp"><%= (pos.imp > 0 ? "+" : "") + pos.imp %></span><span class="win" <%- `style="${pos.winCount == 0 ? "visibility:hidden;" : ""}width: ${pos.winCount * posScale}px"` %>><%= pos.winCount %></span><span class="lose" <%- `style="${pos.matchCount - pos.winCount == 0 ? "visibility:hidden;" : ""}width: ${(pos.matchCount - pos.winCount) * posScale}px"` %>><%= pos.matchCount - pos.winCount %></span> <% }); %> </div> <% if (isAnon) { %> <%- include("player_1/private", {$t, player: p}) %> <% } %> </div> <% if (Math.abs(p.streak) > 1 && !isAnon) { %> <div class="streak" <%- `style="box-shadow:none;color:${getWinRateColor((p.streak + 10) / 20)};"` %>> <%= Math.abs(p.streak) + (p.streak > 0 ? $t("dota2tracker.template.winning_streak") : $t("dota2tracker.template.losing_streak")) %> </div> <% } %> <div><table class="matches"><colgroup><col style="width:auto"/><col style="width:auto"/><col style="width:40px"/><col style="width:auto"/><col style="width:40px"/><col style="width:auto"/><col style="width:auto"/><col style="width:auto"/><col style="width:40px"/></colgroup><thead><tr><th><%= $t("dota2tracker.template.id") %></th><th><%= $t("dota2tracker.template.mode") %></th><th><%= $t("dota2tracker.template.hero") %></th><th><%= $t("dota2tracker.template.kda_kc") %></th><th><%= $t("dota2tracker.template.lane") %></th><th><%= $t("dota2tracker.template.time") %></th><th><%= $t("dota2tracker.template.duration") %></th><th><%= $t("dota2tracker.template.imp") %></th><th><%= $t("dota2tracker.template.rank") %></th></tr></thead><tbody> <% matchesData.forEach(m => { %> <tr class="match <%= m.didWin ? "win" : "lose" %>"><td><%- m.parsedDateTime ? m.id : `<p>${m.id}</p><p>${$t("dota2tracker.template.un_parsed")}</p>` %></td><td><p><%= $t("dota2tracker.template.lobby_types." + m.lobbyType) || m.lobbyType %></p><p><%= $t("dota2tracker.template.game_modes." + m.gameMode) || m.gameMode %></p></td><td><img alt="" src="<%= getImageUrl(m.inner.hero.shortName, ImageType.HeroIcons) %>"/></td><td style="line-height:20px"><p><%= m.kda %> (<%= (m.parsedDateTime ? "" : "≈") + m.kp %>%)</p><p><%= m.inner.kills %>/<%= m.inner.deaths %>/<%= m.inner.assists %></p></td><td><div class="player_lane <%= m.laneRes %>"><%- getImageUrl("lane_" + m.laneRes, undefined, ImageFormat.svg) %></div></td><td style="line-height:20px"><%= m.timeStr %></td><td><%= m.durationTime %></td><td><%= m.inner.imp != null ? ((m.inner.imp >= 0 ? "+" : "") + m.inner.imp) : "?" %></td><td><img class="medal" src="<%= getImageUrl("medal_" + m.rank?.toString().split("")[0]) %>" style="width:100%"/></td></tr> <% }); %> </tbody></table> <% if (isAnon) { %> <%- include("player_1/private", {$t, player: p}) %> <% } %> </div><div class="plus"> <% p.dotaPlus.forEach(hero => { %> <div class="hero"><img src="<%= getImageUrl(hero.shortName, ImageType.Heroes) %>" alt=""/><div class="level"><img src="<%= getImageUrl("hero_badge_" + Math.ceil((hero.level + 1) / 6)) %>" alt=""/><span><%= hero.level %></span></div><span><%= ((hero.winCount / hero.matchCount) * 100).toFixed(2) %>%</span> <span><%= hero.matchCount %></span></div> <% }); %> <% if (isAnon) { %> <%- include("player_1/private", {$t, player: p}) %> <% } %> </div></div></body></html>
@@ -1 +1 @@
1
- <% const { name, avatar, isRising, prevRank, currRank, date } = data; %> <% const kind = isRising ? "xi" : "bei"; %> <% const avatarHtml = `<img src="${avatar}" />`; %> <% const prevHtml = `<span class="rank prev">${$t("dota2tracker.template.ranks." + prevRank.medal)}${prevRank.leader ?? prevRank.star}</span>`; %> <% const currHtml = `<span class="rank curr">${$t("dota2tracker.template.ranks." + currRank.medal)}${currRank.leader ?? currRank.star}</span>`; %> <% const msgKey = isRising ? "rank_fun_up_message" : "rank_fun_down_message"; %> <% const rawMsg = $t("dota2tracker.template." + msgKey, { name: name }); %> <% const finalMessage = rawMsg.replace('AVATAR_PLACEHOLDER', avatarHtml).replace('PREV_PLACEHOLDER', prevHtml).replace('CURR_PLACEHOLDER', currHtml); %> <!DOCTYPE html><html><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Rank Change</title> <%- `<style>` %> * { margin: 0; padding: 0; } html { width: 1024px; height: 768px; } body { background: no-repeat center center / cover; } .wrapper { padding: 80px; padding-top: 180px; } .wrapper > p { font-size: 32px; line-height: 48px; height: 48px; text-align: center; } body.xi p.bei, body.bei p.xi { display: none; } .wrapper > p > span { margin: 0 10px; } .wrapper > p > span.prev { color: gray; } .wrapper > p > span.curr { font-weight: bold; color: red; } .wrapper > p > img { width: 48px; height: 48px; vertical-align: middle; line-height: 48px; } .ranks { margin-top: 40px; display: flex; width: 100%; height: 256px; justify-content: space-evenly; } div.rank { position: relative; width: 256px; height: 256px; } div.rank > img { position: absolute; } div.rank.prev { filter: grayscale(1); } div.rank.curr { transform: scale(1.25); } div.rank p { font-size: 36px; bottom: 20px; width: 100%; text-align: center; color: #fff; position: absolute; } <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body class="<%= kind %>" <%- `style="background-image: url(${getImageUrl(kind, undefined, "jpg")})"` %>><div class="wrapper"><p class="<%= kind %>"> <%- finalMessage %> </p><p></p><div class="ranks"><div class="rank prev"><img src="<%- getImageUrl('medal_' + (prevRank.inTop100 ?? prevRank.medal)) %>" alt=""/> <img src="<%- getImageUrl('star_' + prevRank.star) %>" alt=""/><p><%= prevRank.leader ?? "" %></p></div><div class="rank curr"><img src="<%- getImageUrl('medal_' + (currRank.inTop100 ?? currRank.medal)) %>" alt=""/> <img src="<%- getImageUrl('star_' + currRank.star) %>" alt=""/><p><%= currRank.leader ?? "" %></p></div></div><p style="text-align:right;margin-top:40px">—— <%= date %></p></div></body></html>
1
+ <% const { name, avatar, isRising, prevRank, currRank, date } = data; %> <% const kind = isRising ? "xi" : "bei"; %> <% const avatarHtml = `<img src="${avatar}" />`; %> <% const prevHtml = `<span class="rank prev">${$t("dota2tracker.template.ranks." + prevRank.medal)}${prevRank.leader ?? prevRank.star}</span>`; %> <% const currHtml = `<span class="rank curr">${$t("dota2tracker.template.ranks." + currRank.medal)}${currRank.leader ?? currRank.star}</span>`; %> <% const msgKey = isRising ? "rank_fun_up_message" : "rank_fun_down_message"; %> <% const rawMsg = $t("dota2tracker.template." + msgKey, { name: name }); %> <% const finalMessage = rawMsg.replace('AVATAR_PLACEHOLDER', avatarHtml).replace('PREV_PLACEHOLDER', prevHtml).replace('CURR_PLACEHOLDER', currHtml); %> <!DOCTYPE html><html><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Rank Change</title> <%- `<style>` %> * { margin: 0; padding: 0; } html { width: 1024px; height: 768px; } body { background: no-repeat center center / cover; } .wrapper { padding: 80px; padding-top: 180px; } .wrapper > p { font-size: 32px; line-height: 48px; height: 48px; text-align: center; } body.xi p.bei, body.bei p.xi { display: none; } .wrapper > p > span { margin: 0 10px; } .wrapper > p > span.prev { color: gray; } .wrapper > p > span.curr { font-weight: bold; color: red; } .wrapper > p > img { width: 48px; height: 48px; vertical-align: middle; line-height: 48px; } .ranks { margin-top: 40px; display: flex; width: 100%; height: 256px; justify-content: space-evenly; } div.rank { position: relative; width: 256px; height: 256px; } div.rank > img { position: absolute; } div.rank.prev { filter: grayscale(1); } div.rank.curr { transform: scale(1.25); } div.rank p { font-size: 36px; bottom: 20px; width: 100%; text-align: center; color: #fff; position: absolute; } <%- `</style>` %> </head><body class="<%= kind %>" <%- `style="background-image: url(${getImageUrl(kind, undefined, "jpg")})"` %>><div class="wrapper"><p class="<%= kind %>"> <%- finalMessage %> </p><p></p><div class="ranks"><div class="rank prev"><img src="<%- getImageUrl('medal_' + (prevRank.inTop100 ?? prevRank.medal)) %>" alt=""/> <img src="<%- getImageUrl('star_' + prevRank.star) %>" alt=""/><p><%= prevRank.leader ?? "" %></p></div><div class="rank curr"><img src="<%- getImageUrl('medal_' + (currRank.inTop100 ?? currRank.medal)) %>" alt=""/> <img src="<%- getImageUrl('star_' + currRank.star) %>" alt=""/><p><%= currRank.leader ?? "" %></p></div></div><p style="text-align:right;margin-top:40px">—— <%= date %></p></div></body></html>
@@ -1 +1 @@
1
- /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */@layer properties;@layer theme,base,components,utilities;@layer theme{:root,:host{--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-serif: "Cinzel", serif;--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-orange-500: oklch(70.5% .213 47.604);--color-slate-300: oklch(86.9% .022 252.894);--color-slate-400: oklch(70.4% .04 256.788);--color-slate-500: oklch(55.4% .046 257.417);--color-slate-600: oklch(44.6% .043 257.281);--color-slate-700: oklch(37.2% .044 257.287);--color-slate-800: oklch(27.9% .041 260.031);--color-black: #000;--color-white: #fff;--spacing: .25rem;--container-md: 28rem;--text-xs: .75rem;--text-xs--line-height: calc(1 / .75);--text-sm: .875rem;--text-sm--line-height: calc(1.25 / .875);--text-base: 1rem;--text-base--line-height: 1.5 ;--text-lg: 1.125rem;--text-lg--line-height: calc(1.75 / 1.125);--text-xl: 1.25rem;--text-xl--line-height: calc(1.75 / 1.25);--text-2xl: 1.5rem;--text-2xl--line-height: calc(2 / 1.5);--text-3xl: 1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl: 2.25rem;--text-4xl--line-height: calc(2.5 / 2.25);--text-6xl: 3.75rem;--text-6xl--line-height: 1;--font-weight-medium: 500;--font-weight-bold: 700;--font-weight-black: 900;--tracking-tight: -.025em;--tracking-wider: .05em;--tracking-widest: .1em;--leading-tight: 1.25;--radius-lg: .5rem;--radius-xl: .75rem;--drop-shadow-md: 0 3px 3px rgb(0 0 0 / .12);--blur-sm: 8px;--default-transition-duration: .15s;--default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);--default-font-family: var(--font-sans);--default-mono-font-family: var(--font-mono);--color-primary: #137fec;--color-background-dark: #101922;--color-dota-red: #ff3c3c;--color-dota-gold: #e8bc56;--color-dota-green: #0bda5b;--color-card-dark: #16202c;--font-display: "Inter", sans-serif}}@layer base{*,:after,:before,::backdrop,::file-selector-button{box-sizing:border-box;margin:0;padding:0;border:0 solid}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family:var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings, normal);font-variation-settings:var(--default-font-variation-settings, normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings, normal);font-variation-settings:var(--default-mono-font-variation-settings, normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea,::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;border-radius:0;background-color:transparent;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px){::placeholder{color:currentcolor;@supports (color: color-mix(in lab,red,red)){color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]),::file-selector-button{appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer utilities{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.inset-x-0{inset-inline:calc(var(--spacing) * 0)}.bottom-0{bottom:calc(var(--spacing) * 0)}.z-10{z-index:10}.col-span-1{grid-column:span 1 / span 1}.col-span-2{grid-column:span 2 / span 2}.col-span-3{grid-column:span 3 / span 3}.col-span-4{grid-column:span 4 / span 4}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-auto{margin-top:auto}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-4{height:calc(var(--spacing) * 4)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-\[27px\]{height:27px}.h-full{height:100%}.min-h-\[22px\]{min-height:22px}.min-h-\[280px\]{min-height:280px}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing) * 1)}.w-6{width:calc(var(--spacing) * 6)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-\[48px\]{width:48px}.w-full{width:100%}.max-w-\[900px\]{max-width:900px}.max-w-md{max-width:var(--container-md)}.min-w-\[1000px\]{min-width:1000px}.flex-1{flex:1}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:calc(infinity * 1px)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dota-gold{border-color:var(--color-dota-gold)}.border-dota-red{border-color:var(--color-dota-red)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-white\/5{border-color:color-mix(in srgb,#fff 5%,transparent);@supports (color: color-mix(in lab,red,red)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.border-white\/10{border-color:color-mix(in srgb,#fff 10%,transparent);@supports (color: color-mix(in lab,red,red)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.bg-\[\#0d141c\]{background-color:#0d141c}.bg-\[\#16202c\]{background-color:#16202c}.bg-black\/20{background-color:color-mix(in srgb,#000 20%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-black) 20%,transparent)}}.bg-black\/40{background-color:color-mix(in srgb,#000 40%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.bg-card-dark{background-color:var(--color-card-dark)}.bg-dota-gold{background-color:var(--color-dota-gold)}.bg-dota-gold\/50{background-color:color-mix(in srgb,#e8bc56 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-dota-gold) 50%,transparent)}}.bg-dota-green\/50{background-color:color-mix(in srgb,#0bda5b 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-dota-green) 50%,transparent)}}.bg-dota-red{background-color:var(--color-dota-red)}.bg-dota-red\/50{background-color:color-mix(in srgb,#ff3c3c 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-dota-red) 50%,transparent)}}.bg-orange-500{background-color:var(--color-orange-500)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-gradient-to-t{--tw-gradient-position: to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-card-dark{--tw-gradient-from: var(--color-card-dark);--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-card-dark\/80{--tw-gradient-via: color-mix(in srgb, #16202c 80%, transparent);@supports (color: color-mix(in lab,red,red)){--tw-gradient-via: color-mix(in oklab, var(--color-card-dark) 80%, transparent)}--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-via-stops)}.to-transparent{--tw-gradient-to: transparent;--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.bg-cover{background-size:cover}.bg-center{background-position:center}.fill-current{fill:currentcolor}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.font-display{font-family:var(--font-display)}.font-mono{font-family:var(--font-mono)}.font-serif{font-family:var(--font-serif)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading, var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading, var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading, var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading, var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading, var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading, var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading, var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading, var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading: 1;line-height:1}.leading-tight{--tw-leading: var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight: var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight: var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight: var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-\[0\.2em\]{--tw-tracking: .2em;letter-spacing:.2em}.tracking-tight{--tw-tracking: var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking: var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking: var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-black{color:var(--color-black)}.text-dota-gold{color:var(--color-dota-gold)}.text-dota-green{color:var(--color-dota-green)}.text-dota-red{color:var(--color-dota-red)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-60{opacity:60%}.shadow-\[0_0_30px_rgba\(232\,188\,86\,0\.15\)\]{--tw-shadow: 0 0 30px var(--tw-shadow-color, rgba(232,188,86,.15));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_30px_rgba\(255\,60\,60\,0\.15\)\]{--tw-shadow: 0 0 30px var(--tw-shadow-color, rgba(255,60,60,.15));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-dota-gold\/20{--tw-shadow-color: color-mix(in srgb, #e8bc56 20%, transparent);@supports (color: color-mix(in lab,red,red)){--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-dota-gold) 20%, transparent) var(--tw-shadow-alpha), transparent)}}.shadow-dota-red\/20{--tw-shadow-color: color-mix(in srgb, #ff3c3c 20%, transparent);@supports (color: color-mix(in lab,red,red)){--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-dota-red) 20%, transparent) var(--tw-shadow-alpha), transparent)}}.drop-shadow-\[0_1px_2px_rgba\(0\,0\,0\,0\.8\)\]{--tw-drop-shadow-size: drop-shadow(0 1px 2px var(--tw-drop-shadow-color, rgba(0,0,0,.8)));--tw-drop-shadow: var(--tw-drop-shadow-size);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-\[0_2px_10px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow-size: drop-shadow(0 2px 10px var(--tw-drop-shadow-color, rgba(0,0,0,.5)));--tw-drop-shadow: var(--tw-drop-shadow-size);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-md{--tw-drop-shadow-size: drop-shadow(0 3px 3px var(--tw-drop-shadow-color, rgb(0 0 0 / .12)));--tw-drop-shadow: drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur: blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function));transition-duration:var(--tw-duration, var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function));transition-duration:var(--tw-duration, var(--default-transition-duration))}.duration-700{--tw-duration: .7s;transition-duration:.7s}.group-hover\:scale-105{&:is(:where(.group):hover *){@media (hover: hover){--tw-scale-x: 105%;--tw-scale-y: 105%;--tw-scale-z: 105%;scale:var(--tw-scale-x) var(--tw-scale-y)}}}.hover\:bg-white\/\[0\.02\]{&:hover{@media (hover: hover){background-color:color-mix(in srgb,#fff 2%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-white) 2%,transparent)}}}}.sm\:px-6{@media (width >= 40rem){padding-inline:calc(var(--spacing) * 6)}}.md\:grid{@media (width >= 48rem){display:grid}}.md\:grid-cols-4{@media (width >= 48rem){grid-template-columns:repeat(4,minmax(0,1fr))}}.md\:grid-cols-12{@media (width >= 48rem){grid-template-columns:repeat(12,minmax(0,1fr))}}.md\:flex-row{@media (width >= 48rem){flex-direction:row}}.md\:text-6xl{@media (width >= 48rem){font-size:var(--text-6xl);line-height:var(--tw-leading, var(--text-6xl--line-height))}}}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:var(--color-background-dark)}::-webkit-scrollbar-thumb{background:#233648;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--color-primary)}@property --tw-border-style{syntax: "*"; inherits: false; initial-value: solid;}@property --tw-gradient-position{syntax: "*"; inherits: false;}@property --tw-gradient-from{syntax: "<color>"; inherits: false; initial-value: #0000;}@property --tw-gradient-via{syntax: "<color>"; inherits: false; initial-value: #0000;}@property --tw-gradient-to{syntax: "<color>"; inherits: false; initial-value: #0000;}@property --tw-gradient-stops{syntax: "*"; inherits: false;}@property --tw-gradient-via-stops{syntax: "*"; inherits: false;}@property --tw-gradient-from-position{syntax: "<length-percentage>"; inherits: false; initial-value: 0%;}@property --tw-gradient-via-position{syntax: "<length-percentage>"; inherits: false; initial-value: 50%;}@property --tw-gradient-to-position{syntax: "<length-percentage>"; inherits: false; initial-value: 100%;}@property --tw-leading{syntax: "*"; inherits: false;}@property --tw-font-weight{syntax: "*"; inherits: false;}@property --tw-tracking{syntax: "*"; inherits: false;}@property --tw-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: "*"; inherits: false;}@property --tw-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: "*"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: "*"; inherits: false;}@property --tw-ring-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: "*"; inherits: false;}@property --tw-inset-ring-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: "*"; inherits: false;}@property --tw-ring-offset-width{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: "*"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-blur{syntax: "*"; inherits: false;}@property --tw-brightness{syntax: "*"; inherits: false;}@property --tw-contrast{syntax: "*"; inherits: false;}@property --tw-grayscale{syntax: "*"; inherits: false;}@property --tw-hue-rotate{syntax: "*"; inherits: false;}@property --tw-invert{syntax: "*"; inherits: false;}@property --tw-opacity{syntax: "*"; inherits: false;}@property --tw-saturate{syntax: "*"; inherits: false;}@property --tw-sepia{syntax: "*"; inherits: false;}@property --tw-drop-shadow{syntax: "*"; inherits: false;}@property --tw-drop-shadow-color{syntax: "*"; inherits: false;}@property --tw-drop-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-drop-shadow-size{syntax: "*"; inherits: false;}@property --tw-backdrop-blur{syntax: "*"; inherits: false;}@property --tw-backdrop-brightness{syntax: "*"; inherits: false;}@property --tw-backdrop-contrast{syntax: "*"; inherits: false;}@property --tw-backdrop-grayscale{syntax: "*"; inherits: false;}@property --tw-backdrop-hue-rotate{syntax: "*"; inherits: false;}@property --tw-backdrop-invert{syntax: "*"; inherits: false;}@property --tw-backdrop-opacity{syntax: "*"; inherits: false;}@property --tw-backdrop-saturate{syntax: "*"; inherits: false;}@property --tw-backdrop-sepia{syntax: "*"; inherits: false;}@property --tw-duration{syntax: "*"; inherits: false;}@property --tw-scale-x{syntax: "*"; inherits: false; initial-value: 1;}@property --tw-scale-y{syntax: "*"; inherits: false; initial-value: 1;}@property --tw-scale-z{syntax: "*"; inherits: false; initial-value: 1;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-gradient-position: initial;--tw-gradient-from: #0000;--tw-gradient-via: #0000;--tw-gradient-to: #0000;--tw-gradient-stops: initial;--tw-gradient-via-stops: initial;--tw-gradient-from-position: 0%;--tw-gradient-via-position: 50%;--tw-gradient-to-position: 100%;--tw-leading: initial;--tw-font-weight: initial;--tw-tracking: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-blur: initial;--tw-brightness: initial;--tw-contrast: initial;--tw-grayscale: initial;--tw-hue-rotate: initial;--tw-invert: initial;--tw-opacity: initial;--tw-saturate: initial;--tw-sepia: initial;--tw-drop-shadow: initial;--tw-drop-shadow-color: initial;--tw-drop-shadow-alpha: 100%;--tw-drop-shadow-size: initial;--tw-backdrop-blur: initial;--tw-backdrop-brightness: initial;--tw-backdrop-contrast: initial;--tw-backdrop-grayscale: initial;--tw-backdrop-hue-rotate: initial;--tw-backdrop-invert: initial;--tw-backdrop-opacity: initial;--tw-backdrop-saturate: initial;--tw-backdrop-sepia: initial;--tw-duration: initial;--tw-scale-x: 1;--tw-scale-y: 1;--tw-scale-z: 1}}}
1
+ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */@layer properties;@layer theme,base,components,utilities;@layer theme{:root,:host{--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-serif: "Cinzel", serif;--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-orange-500: oklch(70.5% .213 47.604);--color-slate-300: oklch(86.9% .022 252.894);--color-slate-400: oklch(70.4% .04 256.788);--color-slate-500: oklch(55.4% .046 257.417);--color-slate-600: oklch(44.6% .043 257.281);--color-slate-700: oklch(37.2% .044 257.287);--color-slate-800: oklch(27.9% .041 260.031);--color-black: #000;--color-white: #fff;--spacing: .25rem;--container-md: 28rem;--text-xs: .75rem;--text-xs--line-height: calc(1 / .75);--text-sm: .875rem;--text-sm--line-height: calc(1.25 / .875);--text-base: 1rem;--text-base--line-height: 1.5 ;--text-lg: 1.125rem;--text-lg--line-height: calc(1.75 / 1.125);--text-xl: 1.25rem;--text-xl--line-height: calc(1.75 / 1.25);--text-2xl: 1.5rem;--text-2xl--line-height: calc(2 / 1.5);--text-3xl: 1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl: 2.25rem;--text-4xl--line-height: calc(2.5 / 2.25);--text-6xl: 3.75rem;--text-6xl--line-height: 1;--font-weight-medium: 500;--font-weight-bold: 700;--font-weight-black: 900;--tracking-tight: -.025em;--tracking-wider: .05em;--tracking-widest: .1em;--leading-tight: 1.25;--radius-lg: .5rem;--radius-xl: .75rem;--drop-shadow-md: 0 3px 3px rgb(0 0 0 / .12);--blur-sm: 8px;--default-transition-duration: .15s;--default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);--default-font-family: var(--font-sans);--default-mono-font-family: var(--font-mono);--color-primary: #137fec;--color-background-dark: #101922;--color-dota-red: #ff3c3c;--color-dota-gold: #e8bc56;--color-dota-green: #0bda5b;--color-card-dark: #16202c;--font-display: "Inter", sans-serif}}@layer base{*,:after,:before,::backdrop,::file-selector-button{box-sizing:border-box;margin:0;padding:0;border:0 solid}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family:var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings, normal);font-variation-settings:var(--default-font-variation-settings, normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings, normal);font-variation-settings:var(--default-mono-font-variation-settings, normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea,::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;border-radius:0;background-color:transparent;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px){::placeholder{color:currentcolor;@supports (color: color-mix(in lab,red,red)){color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]),::file-selector-button{appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer utilities{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.inset-x-0{inset-inline:calc(var(--spacing) * 0)}.bottom-0{bottom:calc(var(--spacing) * 0)}.z-10{z-index:10}.col-span-1{grid-column:span 1 / span 1}.col-span-2{grid-column:span 2 / span 2}.col-span-3{grid-column:span 3 / span 3}.col-span-4{grid-column:span 4 / span 4}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-auto{margin-top:auto}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-4{height:calc(var(--spacing) * 4)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-\[27px\]{height:27px}.h-full{height:100%}.min-h-\[22px\]{min-height:22px}.min-h-\[280px\]{min-height:280px}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing) * 1)}.w-6{width:calc(var(--spacing) * 6)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-\[48px\]{width:48px}.w-\[1024px\]{width:1024px}.w-full{width:100%}.max-w-\[900px\]{max-width:900px}.max-w-md{max-width:var(--container-md)}.min-w-\[1000px\]{min-width:1000px}.flex-1{flex:1}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:calc(infinity * 1px)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dota-gold{border-color:var(--color-dota-gold)}.border-dota-red{border-color:var(--color-dota-red)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-white\/5{border-color:color-mix(in srgb,#fff 5%,transparent);@supports (color: color-mix(in lab,red,red)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.border-white\/10{border-color:color-mix(in srgb,#fff 10%,transparent);@supports (color: color-mix(in lab,red,red)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.bg-\[\#0d141c\]{background-color:#0d141c}.bg-\[\#16202c\]{background-color:#16202c}.bg-black\/20{background-color:color-mix(in srgb,#000 20%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-black) 20%,transparent)}}.bg-black\/40{background-color:color-mix(in srgb,#000 40%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.bg-card-dark{background-color:var(--color-card-dark)}.bg-dota-gold{background-color:var(--color-dota-gold)}.bg-dota-gold\/50{background-color:color-mix(in srgb,#e8bc56 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-dota-gold) 50%,transparent)}}.bg-dota-green\/50{background-color:color-mix(in srgb,#0bda5b 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-dota-green) 50%,transparent)}}.bg-dota-red{background-color:var(--color-dota-red)}.bg-dota-red\/50{background-color:color-mix(in srgb,#ff3c3c 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-dota-red) 50%,transparent)}}.bg-orange-500{background-color:var(--color-orange-500)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-gradient-to-t{--tw-gradient-position: to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-card-dark{--tw-gradient-from: var(--color-card-dark);--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-card-dark\/80{--tw-gradient-via: color-mix(in srgb, #16202c 80%, transparent);@supports (color: color-mix(in lab,red,red)){--tw-gradient-via: color-mix(in oklab, var(--color-card-dark) 80%, transparent)}--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-via-stops)}.to-transparent{--tw-gradient-to: transparent;--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.bg-cover{background-size:cover}.bg-center{background-position:center}.fill-current{fill:currentcolor}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.font-display{font-family:var(--font-display)}.font-mono{font-family:var(--font-mono)}.font-serif{font-family:var(--font-serif)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading, var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading, var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading, var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading, var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading, var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading, var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading, var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading, var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading: 1;line-height:1}.leading-tight{--tw-leading: var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight: var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight: var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight: var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-\[0\.2em\]{--tw-tracking: .2em;letter-spacing:.2em}.tracking-tight{--tw-tracking: var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking: var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking: var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-black{color:var(--color-black)}.text-dota-gold{color:var(--color-dota-gold)}.text-dota-green{color:var(--color-dota-green)}.text-dota-red{color:var(--color-dota-red)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-60{opacity:60%}.shadow-\[0_0_30px_rgba\(232\,188\,86\,0\.15\)\]{--tw-shadow: 0 0 30px var(--tw-shadow-color, rgba(232,188,86,.15));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_30px_rgba\(255\,60\,60\,0\.15\)\]{--tw-shadow: 0 0 30px var(--tw-shadow-color, rgba(255,60,60,.15));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / .1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / .1));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-dota-gold\/20{--tw-shadow-color: color-mix(in srgb, #e8bc56 20%, transparent);@supports (color: color-mix(in lab,red,red)){--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-dota-gold) 20%, transparent) var(--tw-shadow-alpha), transparent)}}.shadow-dota-red\/20{--tw-shadow-color: color-mix(in srgb, #ff3c3c 20%, transparent);@supports (color: color-mix(in lab,red,red)){--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-dota-red) 20%, transparent) var(--tw-shadow-alpha), transparent)}}.drop-shadow-\[0_1px_2px_rgba\(0\,0\,0\,0\.8\)\]{--tw-drop-shadow-size: drop-shadow(0 1px 2px var(--tw-drop-shadow-color, rgba(0,0,0,.8)));--tw-drop-shadow: var(--tw-drop-shadow-size);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-\[0_2px_10px_rgba\(0\,0\,0\,0\.5\)\]{--tw-drop-shadow-size: drop-shadow(0 2px 10px var(--tw-drop-shadow-color, rgba(0,0,0,.5)));--tw-drop-shadow: var(--tw-drop-shadow-size);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-md{--tw-drop-shadow-size: drop-shadow(0 3px 3px var(--tw-drop-shadow-color, rgb(0 0 0 / .12)));--tw-drop-shadow: drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur: blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function));transition-duration:var(--tw-duration, var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function));transition-duration:var(--tw-duration, var(--default-transition-duration))}.duration-700{--tw-duration: .7s;transition-duration:.7s}.group-hover\:scale-105{&:is(:where(.group):hover *){@media (hover: hover){--tw-scale-x: 105%;--tw-scale-y: 105%;--tw-scale-z: 105%;scale:var(--tw-scale-x) var(--tw-scale-y)}}}.hover\:bg-white\/\[0\.02\]{&:hover{@media (hover: hover){background-color:color-mix(in srgb,#fff 2%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-white) 2%,transparent)}}}}.sm\:px-6{@media (width >= 40rem){padding-inline:calc(var(--spacing) * 6)}}.md\:grid{@media (width >= 48rem){display:grid}}.md\:grid-cols-4{@media (width >= 48rem){grid-template-columns:repeat(4,minmax(0,1fr))}}.md\:grid-cols-12{@media (width >= 48rem){grid-template-columns:repeat(12,minmax(0,1fr))}}.md\:flex-row{@media (width >= 48rem){flex-direction:row}}.md\:text-6xl{@media (width >= 48rem){font-size:var(--text-6xl);line-height:var(--tw-leading, var(--text-6xl--line-height))}}}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:var(--color-background-dark)}::-webkit-scrollbar-thumb{background:#233648;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--color-primary)}@property --tw-border-style{syntax: "*"; inherits: false; initial-value: solid;}@property --tw-gradient-position{syntax: "*"; inherits: false;}@property --tw-gradient-from{syntax: "<color>"; inherits: false; initial-value: #0000;}@property --tw-gradient-via{syntax: "<color>"; inherits: false; initial-value: #0000;}@property --tw-gradient-to{syntax: "<color>"; inherits: false; initial-value: #0000;}@property --tw-gradient-stops{syntax: "*"; inherits: false;}@property --tw-gradient-via-stops{syntax: "*"; inherits: false;}@property --tw-gradient-from-position{syntax: "<length-percentage>"; inherits: false; initial-value: 0%;}@property --tw-gradient-via-position{syntax: "<length-percentage>"; inherits: false; initial-value: 50%;}@property --tw-gradient-to-position{syntax: "<length-percentage>"; inherits: false; initial-value: 100%;}@property --tw-leading{syntax: "*"; inherits: false;}@property --tw-font-weight{syntax: "*"; inherits: false;}@property --tw-tracking{syntax: "*"; inherits: false;}@property --tw-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: "*"; inherits: false;}@property --tw-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: "*"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: "*"; inherits: false;}@property --tw-ring-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: "*"; inherits: false;}@property --tw-inset-ring-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: "*"; inherits: false;}@property --tw-ring-offset-width{syntax: "<length>"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: "*"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: "*"; inherits: false; initial-value: 0 0 #0000;}@property --tw-blur{syntax: "*"; inherits: false;}@property --tw-brightness{syntax: "*"; inherits: false;}@property --tw-contrast{syntax: "*"; inherits: false;}@property --tw-grayscale{syntax: "*"; inherits: false;}@property --tw-hue-rotate{syntax: "*"; inherits: false;}@property --tw-invert{syntax: "*"; inherits: false;}@property --tw-opacity{syntax: "*"; inherits: false;}@property --tw-saturate{syntax: "*"; inherits: false;}@property --tw-sepia{syntax: "*"; inherits: false;}@property --tw-drop-shadow{syntax: "*"; inherits: false;}@property --tw-drop-shadow-color{syntax: "*"; inherits: false;}@property --tw-drop-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@property --tw-drop-shadow-size{syntax: "*"; inherits: false;}@property --tw-backdrop-blur{syntax: "*"; inherits: false;}@property --tw-backdrop-brightness{syntax: "*"; inherits: false;}@property --tw-backdrop-contrast{syntax: "*"; inherits: false;}@property --tw-backdrop-grayscale{syntax: "*"; inherits: false;}@property --tw-backdrop-hue-rotate{syntax: "*"; inherits: false;}@property --tw-backdrop-invert{syntax: "*"; inherits: false;}@property --tw-backdrop-opacity{syntax: "*"; inherits: false;}@property --tw-backdrop-saturate{syntax: "*"; inherits: false;}@property --tw-backdrop-sepia{syntax: "*"; inherits: false;}@property --tw-duration{syntax: "*"; inherits: false;}@property --tw-scale-x{syntax: "*"; inherits: false; initial-value: 1;}@property --tw-scale-y{syntax: "*"; inherits: false; initial-value: 1;}@property --tw-scale-z{syntax: "*"; inherits: false; initial-value: 1;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-gradient-position: initial;--tw-gradient-from: #0000;--tw-gradient-via: #0000;--tw-gradient-to: #0000;--tw-gradient-stops: initial;--tw-gradient-via-stops: initial;--tw-gradient-from-position: 0%;--tw-gradient-via-position: 50%;--tw-gradient-to-position: 100%;--tw-leading: initial;--tw-font-weight: initial;--tw-tracking: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-blur: initial;--tw-brightness: initial;--tw-contrast: initial;--tw-grayscale: initial;--tw-hue-rotate: initial;--tw-invert: initial;--tw-opacity: initial;--tw-saturate: initial;--tw-sepia: initial;--tw-drop-shadow: initial;--tw-drop-shadow-color: initial;--tw-drop-shadow-alpha: 100%;--tw-drop-shadow-size: initial;--tw-backdrop-blur: initial;--tw-backdrop-brightness: initial;--tw-backdrop-contrast: initial;--tw-backdrop-grayscale: initial;--tw-backdrop-hue-rotate: initial;--tw-backdrop-invert: initial;--tw-backdrop-opacity: initial;--tw-backdrop-saturate: initial;--tw-backdrop-sepia: initial;--tw-duration: initial;--tw-scale-x: 1;--tw-scale-y: 1;--tw-scale-z: 1}}}
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html><html class="dark" lang="en"><head><meta charset="utf-8"/><meta content="width=device-width,initial-scale=1" name="viewport"/><title>Dota 2 Daily Recap</title> <%- `<style>` %> <%- include(`./daily/style.css`) %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> <style>::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#101922}::-webkit-scrollbar-thumb{background:#233648;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#137fec}.bg-texture{background-color:#101922;background-image:radial-gradient(at 0 0,rgba(19,127,236,.08) 0,transparent 50%),radial-gradient(at 100% 0,rgba(255,60,60,.05) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(232,188,86,.05) 0,transparent 50%)}</style></head><body class="bg-texture font-display text-white overflow-x-hidden antialiased min-h-screen min-w-[1000px]"><div class="w-full flex justify-center py-8 px-4 sm:px-6"><div class="w-full max-w-[900px] flex flex-col gap-8"><div class="flex flex-col items-center justify-center pt-2 pb-4 text-center"><div class="mb-3 border-b-2 border-dota-red pb-1"><span class="text-xs font-bold tracking-[0.2em] uppercase text-dota-gold drop-shadow-md"> <%= $t("dota2tracker.template.report.daily.plugin_name") %> </span></div><h1 class="text-4xl md:text-6xl font-serif font-black tracking-tight text-white uppercase drop-shadow-[0_2px_10px_rgba(0,0,0,0.5)] mb-2"> <%= $t("dota2tracker.template.report.daily.title") %> </h1><div class="flex flex-col items-center gap-1 text-slate-400 font-medium"><p class="text-lg tracking-widest uppercase text-slate-300"><%= data.meta.date %></p><p class="text-xs opacity-60 max-w-md mx-auto"> <%= data.meta.summary %> </p></div></div><div class="grid grid-cols-2 md:grid-cols-4 gap-3"><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.matches") %> </span><span class="text-3xl font-black text-white"><%= data.headerStats.matches.value %></span><span class="text-xs text-slate-400 mt-1"><%= data.headerStats.matches.subtext %></span></div><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><div class="absolute inset-x-0 bottom-0 h-1 <%= data.headerStats.winRate.isWinRateAbove50 ? 'bg-dota-green/50' : 'bg-dota-red/50' %>"></div><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.win_rate") %> </span><span class="text-3xl font-black <%= data.headerStats.winRate.isWinRateAbove50 ? 'text-dota-green' : 'text-dota-red' %>"><%= data.headerStats.winRate.value %></span><span class="text-xs mt-1 <%= data.headerStats.winRate.isPositive ? 'text-dota-green' : 'text-dota-red' %>"><%= data.headerStats.winRate.subtext %></span></div><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><div class="absolute inset-x-0 bottom-0 h-1 bg-dota-red/50"></div><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.kills") %> </span><span class="text-3xl font-black text-dota-red"><%= data.headerStats.kills.value %></span><span class="text-xs text-slate-400 mt-1"><%= data.headerStats.kills.subtext %></span></div><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><div class="absolute inset-x-0 bottom-0 h-1 bg-dota-gold/50"></div><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.duration") %> </span><span class="text-3xl font-black text-dota-gold"><%= data.headerStats.duration.value %></span><span class="text-xs text-slate-400 mt-1"> <%= data.headerStats.duration.subtext %> </span></div></div><div class="flex flex-col md:flex-row gap-6 mt-2"> <% const lights = [data.spotlights.mvp, data.spotlights.lvp]; %> <% lights.forEach(function(light) { %> <div class="flex-1 relative rounded-lg border <%= light.type === 'MVP' ? 'border-dota-gold shadow-[0_0_30px_rgba(232,188,86,0.15)]' : 'border-dota-red shadow-[0_0_30px_rgba(255,60,60,0.15)]' %> bg-card-dark overflow-hidden group"><div class="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-105" style="background-image:url('<%= light.player.heroBannerUrl %>');opacity:.4"></div><div class="absolute inset-0 bg-gradient-to-t from-card-dark via-card-dark/80 to-transparent"></div><div class="relative z-10 p-6 flex flex-col h-full min-h-[280px]"><div class="flex justify-between items-center"><div class="<%= light.type === 'MVP' ? 'bg-dota-gold text-black shadow-dota-gold/20' : 'bg-dota-red text-white shadow-dota-red/20' %> text-xs font-black uppercase px-3 py-1 rounded shadow-lg"> <%= light.type === 'MVP' ? $t("dota2tracker.template.report.daily.spotlight.mvp_title") : $t("dota2tracker.template.report.daily.spotlight.lvp_title") %> </div><svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 fill-current" viewBox="0 0 24 24"> <% if (light.type === 'MVP') { %> <path class="text-dota-gold" d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/> <% } else { %> <path class="text-dota-red" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/> <% } %> </svg></div><div class="mt-auto"><h3 class="text-3xl font-serif font-bold text-white leading-none mb-1 drop-shadow-md"><%= light.player.name %></h3><p class="text-slate-300 text-sm font-medium leading-none mb-1"><%= light.player.heroName %></p><p class="text-slate-400 text-sm font-mono mb-3"><%= light.player.kda %></p><div class="bg-black/40 backdrop-blur-sm rounded-lg p-3 border border-white/10"><div class="flex flex-col gap-2"><div class="flex justify-between items-center border-b border-white/10 pb-2"><span class="text-xs text-slate-400 uppercase tracking-widest font-bold"> <%= light.score.label %> </span><span class="font-mono text-2xl font-bold <%= light.type === 'MVP' ? 'text-dota-gold' : 'text-dota-red' %>"><%= light.score.value %></span></div><div class="flex flex-wrap gap-2 pt-1 min-h-[22px]"> <% if (light.badges && light.badges.length > 0) { %> <% light.badges.forEach(function(badge) { %> <%
1
+ <!DOCTYPE html><html class="dark" lang="en"><head><meta charset="utf-8"/><meta content="width=device-width,initial-scale=1" name="viewport"/><title>Dota 2 Daily Recap</title> <%- `<style>` %> <%- include(`./daily/style.css`) %> <%- `</style>` %> <style>::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#101922}::-webkit-scrollbar-thumb{background:#233648;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#137fec}.bg-texture{background-color:#101922;background-image:radial-gradient(at 0 0,rgba(19,127,236,.08) 0,transparent 50%),radial-gradient(at 100% 0,rgba(255,60,60,.05) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(232,188,86,.05) 0,transparent 50%)}</style></head><body class="bg-texture font-display text-white overflow-x-hidden antialiased min-h-screen w-[1024px] min-w-[1000px]"><div class="w-full flex justify-center py-8 px-4 sm:px-6"><div class="w-full max-w-[900px] flex flex-col gap-8"><div class="flex flex-col items-center justify-center pt-2 pb-4 text-center"><div class="mb-3 border-b-2 border-dota-red pb-1"><span class="text-xs font-bold tracking-[0.2em] uppercase text-dota-gold drop-shadow-md"> <%= $t("dota2tracker.template.report.daily.plugin_name") %> </span></div><h1 class="text-4xl md:text-6xl font-serif font-black tracking-tight text-white uppercase drop-shadow-[0_2px_10px_rgba(0,0,0,0.5)] mb-2"> <%= $t("dota2tracker.template.report.daily.title") %> </h1><div class="flex flex-col items-center gap-1 text-slate-400 font-medium"><p class="text-lg tracking-widest uppercase text-slate-300"><%= data.meta.date %></p><p class="text-xs opacity-60 max-w-md mx-auto"> <%= data.meta.summary %> </p></div></div><div class="grid grid-cols-2 md:grid-cols-4 gap-3"><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.matches") %> </span><span class="text-3xl font-black text-white"><%= data.headerStats.matches.value %></span><span class="text-xs text-slate-400 mt-1"><%= data.headerStats.matches.subtext %></span></div><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><div class="absolute inset-x-0 bottom-0 h-1 <%= data.headerStats.winRate.isWinRateAbove50 ? 'bg-dota-green/50' : 'bg-dota-red/50' %>"></div><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.win_rate") %> </span><span class="text-3xl font-black <%= data.headerStats.winRate.isWinRateAbove50 ? 'text-dota-green' : 'text-dota-red' %>"><%= data.headerStats.winRate.value %></span><span class="text-xs mt-1 <%= data.headerStats.winRate.isPositive ? 'text-dota-green' : 'text-dota-red' %>"><%= data.headerStats.winRate.subtext %></span></div><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><div class="absolute inset-x-0 bottom-0 h-1 bg-dota-red/50"></div><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.kills") %> </span><span class="text-3xl font-black text-dota-red"><%= data.headerStats.kills.value %></span><span class="text-xs text-slate-400 mt-1"><%= data.headerStats.kills.subtext %></span></div><div class="flex flex-col items-center justify-center p-4 rounded bg-card-dark border border-white/5 shadow-lg relative overflow-hidden group"><div class="absolute inset-x-0 bottom-0 h-1 bg-dota-gold/50"></div><span class="text-slate-500 text-[10px] font-bold uppercase tracking-widest mb-1"> <%= $t("dota2tracker.template.report.daily.stats.duration") %> </span><span class="text-3xl font-black text-dota-gold"><%= data.headerStats.duration.value %></span><span class="text-xs text-slate-400 mt-1"> <%= data.headerStats.duration.subtext %> </span></div></div><div class="flex flex-col md:flex-row gap-6 mt-2"> <% const lights = [data.spotlights.mvp, data.spotlights.lvp]; %> <% lights.forEach(function(light) { %> <div class="flex-1 relative rounded-lg border <%= light.type === 'MVP' ? 'border-dota-gold shadow-[0_0_30px_rgba(232,188,86,0.15)]' : 'border-dota-red shadow-[0_0_30px_rgba(255,60,60,0.15)]' %> bg-card-dark overflow-hidden group"><div class="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-105" style="background-image:url('<%= light.player.heroBannerUrl %>');opacity:.4"></div><div class="absolute inset-0 bg-gradient-to-t from-card-dark via-card-dark/80 to-transparent"></div><div class="relative z-10 p-6 flex flex-col h-full min-h-[280px]"><div class="flex justify-between items-center"><div class="<%= light.type === 'MVP' ? 'bg-dota-gold text-black shadow-dota-gold/20' : 'bg-dota-red text-white shadow-dota-red/20' %> text-xs font-black uppercase px-3 py-1 rounded shadow-lg"> <%= light.type === 'MVP' ? $t("dota2tracker.template.report.daily.spotlight.mvp_title") : $t("dota2tracker.template.report.daily.spotlight.lvp_title") %> </div><svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 fill-current" viewBox="0 0 24 24"> <% if (light.type === 'MVP') { %> <path class="text-dota-gold" d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/> <% } else { %> <path class="text-dota-red" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/> <% } %> </svg></div><div class="mt-auto"><h3 class="text-3xl font-serif font-bold text-white leading-none mb-1 drop-shadow-md"><%= light.player.name %></h3><p class="text-slate-300 text-sm font-medium leading-none mb-1"><%= light.player.heroName %></p><p class="text-slate-400 text-sm font-mono mb-3"><%= light.player.kda %></p><div class="bg-black/40 backdrop-blur-sm rounded-lg p-3 border border-white/10"><div class="flex flex-col gap-2"><div class="flex justify-between items-center border-b border-white/10 pb-2"><span class="text-xs text-slate-400 uppercase tracking-widest font-bold"> <%= light.score.label %> </span><span class="font-mono text-2xl font-bold <%= light.type === 'MVP' ? 'text-dota-gold' : 'text-dota-red' %>"><%= light.score.value %></span></div><div class="flex flex-wrap gap-2 pt-1 min-h-[22px]"> <% if (light.badges && light.badges.length > 0) { %> <% light.badges.forEach(function(badge) { %> <%
2
2
  // Convert HEX to RGB for background opacity
3
3
  const r = parseInt(badge.hexColor.slice(1, 3), 16);
4
4
  const g = parseInt(badge.hexColor.slice(3, 5), 16);
@@ -18,4 +18,4 @@
18
18
  rightStyle: `width: ${right}px`
19
19
  };
20
20
  };
21
- %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Daily Report</title> <%- `<style>` %> <%- include("../common/styles/normalize.min.css") %> <%- include("./daily_legacy/base.css") %> <% if (fontFamily) { %><%- `body { font-family: ${fontFamily}; }` %><% } %> <%- `</style>` %> </head><body><h3 class="title"><%= title %></h3><div class="players"> <% players.forEach(p => { %> <% const winRate = Math.round((p.winCount / p.matches.length) * 100); %> <% const imp = getImpInfo(p.avgImp); %> <div class="player"><img src="<%= p.steamAccount.avatar %>" class="avatar"/> <span class="name"><%= p.name %></span><span class="count"><span class="win"><%= $t("dota2tracker.template.report_won") %><%= p.winCount %></span><span class="lose"><%= $t("dota2tracker.template.report_lost") %><%= p.loseCount %></span><span><%= $t("dota2tracker.template.report_winrate") %> <%= winRate %>%</span></span><div class="performance"><div class="<%= imp.barClass %>"><div class="left" <%- `style="${imp.leftStyle}"` %>></div><div class="pipe"></div><div class="right" <%- `style="${imp.rightStyle}"` %>></div></div><span class="score_value"><%= imp.valStr %></span></div><span class="kda"><%= p.avgKills %>/<%= p.avgDeaths %>/<%= p.avgAssists %> (<%= p.avgKDA %>)</span></div> <% }); %> </div><div class="combinations" <%- !showCombi ? `style="display:none;"` : "" %>><span style="grid-column:1/-1"><%= $t("dota2tracker.template.combined_win_loss_summary") %></span> <% combinations.forEach(combi => { %> <% const combiRate = Math.round((combi.winCount / combi.matches.length) * 100); %> <div class="players"> <% combi.players.forEach(p => { %> <img src="<%= p.steamAccount.avatar %>" class="avatar"/> <% }); %> </div><span class="win"><%= $t("dota2tracker.template.report_won") %><%= combi.winCount %></span><span class="lose"><%= $t("dota2tracker.template.report_lost") %><%= combi.matches.length - combi.winCount %></span><span><%= $t("dota2tracker.template.report_winrate") %> <%= combiRate %>%</span> <% }); %> </div></body></html>
21
+ %> <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Daily Report</title> <%- `<style>` %> <%- include("../common/styles/normalize.min.css") %> <%- include("./daily_legacy/base.css") %> <%- `</style>` %> </head><body><h3 class="title"><%= title %></h3><div class="players"> <% players.forEach(p => { %> <% const winRate = Math.round((p.winCount / p.matches.length) * 100); %> <% const imp = getImpInfo(p.avgImp); %> <div class="player"><img src="<%= p.steamAccount.avatar %>" class="avatar"/> <span class="name"><%= p.name %></span><span class="count"><span class="win"><%= $t("dota2tracker.template.report_won") %><%= p.winCount %></span><span class="lose"><%= $t("dota2tracker.template.report_lost") %><%= p.loseCount %></span><span><%= $t("dota2tracker.template.report_winrate") %> <%= winRate %>%</span></span><div class="performance"><div class="<%= imp.barClass %>"><div class="left" <%- `style="${imp.leftStyle}"` %>></div><div class="pipe"></div><div class="right" <%- `style="${imp.rightStyle}"` %>></div></div><span class="score_value"><%= imp.valStr %></span></div><span class="kda"><%= p.avgKills %>/<%= p.avgDeaths %>/<%= p.avgAssists %> (<%= p.avgKDA %>)</span></div> <% }); %> </div><div class="combinations" <%- !showCombi ? `style="display:none;"` : "" %>><span style="grid-column:1/-1"><%= $t("dota2tracker.template.combined_win_loss_summary") %></span> <% combinations.forEach(combi => { %> <% const combiRate = Math.round((combi.winCount / combi.matches.length) * 100); %> <div class="players"> <% combi.players.forEach(p => { %> <img src="<%= p.steamAccount.avatar %>" class="avatar"/> <% }); %> </div><span class="win"><%= $t("dota2tracker.template.report_won") %><%= combi.winCount %></span><span class="lose"><%= $t("dota2tracker.template.report_lost") %><%= combi.matches.length - combi.winCount %></span><span><%= $t("dota2tracker.template.report_winrate") %> <%= combiRate %>%</span> <% }); %> </div></body></html>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sjtdev/koishi-plugin-dota2tracker",
3
3
  "description": "koishi插件-追踪群友的DOTA2对局 | A Koishi plugin to track Dota 2 matches",
4
- "version": "2.5.2",
4
+ "version": "2.5.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [