@sjtdev/koishi-plugin-dota2tracker 2.2.2 → 2.3.0
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 +32 -0
- package/dist/index.js +1 -1
- package/lib/index.js +128 -94
- package/{queries → lib/queries}/MatchInfo.graphql +13 -0
- package/lib/templates/common/components/building_icons.ejs +20 -0
- package/lib/templates/common/styles/normalize.min.css +1 -0
- package/lib/templates/hero/hero_1.ejs +69 -0
- package/lib/templates/images/7.38_simple_minimap.png +0 -0
- package/lib/templates/item/item/recipe.ejs +9 -0
- package/lib/templates/item/item/style.css +1 -0
- package/lib/templates/item/item.ejs +52 -0
- package/lib/templates/item/itemlist.ejs +11 -0
- package/lib/templates/match/match_1/base.css +1 -0
- package/lib/templates/match/match_1/item.ejs +1 -0
- package/lib/templates/match/match_1/main.ejs +8 -0
- package/lib/templates/match/match_1/player.ejs +1 -0
- package/lib/templates/match/match_1/style.css +1 -0
- package/lib/templates/match/match_1.ejs +18 -0
- package/lib/templates/match/match_2/original.css +1 -0
- package/lib/templates/match/match_2/original.ejs +10 -0
- package/lib/templates/match/match_2+/charts.ejs +1 -0
- package/lib/templates/match/match_2+/extra.css +1 -0
- package/lib/templates/match/match_2+/lane_outcome.ejs +56 -0
- package/lib/templates/match/match_2+/map.ejs +160 -0
- package/lib/templates/match/match_2+.ejs +1 -0
- package/lib/templates/match/match_2.ejs +1 -0
- package/lib/templates/player/player_1/base.css +1 -0
- package/lib/templates/player/player_1/private.ejs +1 -0
- package/lib/templates/player/player_1.ejs +78 -0
- package/lib/templates/rank/rank_fun.ejs +1 -0
- package/lib/templates/report/daily/base.css +1 -0
- package/lib/templates/report/daily.ejs +29 -0
- package/package.json +2 -2
- package/template/hero/hero_1.ejs +0 -900
- package/template/item/item/recipe.ejs +0 -51
- package/template/item/item/style.css +0 -244
- package/template/item/item.ejs +0 -140
- package/template/item/itemlist.ejs +0 -99
- package/template/match/match_1/item.ejs +0 -11
- package/template/match/match_1/main.ejs +0 -37
- package/template/match/match_1/player.ejs +0 -154
- package/template/match/match_1/style.css +0 -764
- package/template/match/match_1.ejs +0 -56
- package/template/match/match_2/original.css +0 -463
- package/template/match/match_2/original.ejs +0 -192
- package/template/match/match_2+/charts.ejs +0 -261
- package/template/match/match_2+/extra.css +0 -143
- package/template/match/match_2+/lane_outcome.ejs +0 -157
- package/template/match/match_2+.ejs +0 -27
- package/template/match/match_2.ejs +0 -18
- package/template/player/player_1/private.ejs +0 -5
- package/template/player/player_1.ejs +0 -654
- package/template/rank/rank_fun.ejs +0 -131
- package/template/report/daily.ejs +0 -191
- /package/{queries → lib/queries}/Constants.graphql +0 -0
- /package/{queries → lib/queries}/GetWeeklyMetaByPosition.graphql +0 -0
- /package/{queries → lib/queries}/PlayerExtraInfo.graphql +0 -0
- /package/{queries → lib/queries}/PlayerInfoWith25Matches.graphql +0 -0
- /package/{queries → lib/queries}/PlayerPerformanceForHeroRecommendation.graphql +0 -0
- /package/{queries → lib/queries}/PlayersInfoWith10MatchesForGuild.graphql +0 -0
- /package/{queries → lib/queries}/PlayersLastmatchRankinfo.graphql +0 -0
- /package/{queries → lib/queries}/PlayersMatchesForDaily.graphql +0 -0
- /package/{queries → lib/queries}/RequestMatchDataAnalysis.graphql +0 -0
- /package/{queries → lib/queries}/VerifyingPlayer.graphql +0 -0
- /package/{template → lib/templates}/images/bei.jpg +0 -0
- /package/{template → lib/templates}/images/disconnected.png +0 -0
- /package/{template → lib/templates}/images/flag_dire.png +0 -0
- /package/{template → lib/templates}/images/flag_radiant.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_1.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_2.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_3.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_4.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_5.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_6.png +0 -0
- /package/{template → lib/templates}/images/lane_fail.svg +0 -0
- /package/{template → lib/templates}/images/lane_jungle.svg +0 -0
- /package/{template → lib/templates}/images/lane_stomp.svg +0 -0
- /package/{template → lib/templates}/images/lane_stomped.svg +0 -0
- /package/{template → lib/templates}/images/lane_tie.svg +0 -0
- /package/{template → lib/templates}/images/lane_victory.svg +0 -0
- /package/{template → lib/templates}/images/logo_dire.png +0 -0
- /package/{template → lib/templates}/images/logo_radiant.png +0 -0
- /package/{template → lib/templates}/images/medal_0.png +0 -0
- /package/{template → lib/templates}/images/medal_1.png +0 -0
- /package/{template → lib/templates}/images/medal_2.png +0 -0
- /package/{template → lib/templates}/images/medal_3.png +0 -0
- /package/{template → lib/templates}/images/medal_4.png +0 -0
- /package/{template → lib/templates}/images/medal_5.png +0 -0
- /package/{template → lib/templates}/images/medal_6.png +0 -0
- /package/{template → lib/templates}/images/medal_7.png +0 -0
- /package/{template → lib/templates}/images/medal_8.png +0 -0
- /package/{template → lib/templates}/images/medal_8b.png +0 -0
- /package/{template → lib/templates}/images/medal_8c.png +0 -0
- /package/{template → lib/templates}/images/scepter.png +0 -0
- /package/{template → lib/templates}/images/scepter_0.png +0 -0
- /package/{template → lib/templates}/images/scepter_1.png +0 -0
- /package/{template → lib/templates}/images/shard.png +0 -0
- /package/{template → lib/templates}/images/shard_0.png +0 -0
- /package/{template → lib/templates}/images/shard_1.png +0 -0
- /package/{template → lib/templates}/images/star_0.png +0 -0
- /package/{template → lib/templates}/images/star_1.png +0 -0
- /package/{template → lib/templates}/images/star_2.png +0 -0
- /package/{template → lib/templates}/images/star_3.png +0 -0
- /package/{template → lib/templates}/images/star_4.png +0 -0
- /package/{template → lib/templates}/images/star_5.png +0 -0
- /package/{template → lib/templates}/images/xi.jpg +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="<%= languageTag %>"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Document</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"/><style>body,html{background-color:#000;color:#fff;width:800px}.wrapper>:not(.skills){margin:5px;box-shadow:0 0 5px #fff;display:flex}img{width:100%;vertical-align:middle}p{margin:0}.hero{position:relative}.hero img{width:25%}.hero .pri_attr{position:absolute;width:48px;left:25%}.hero .info{display:flex;width:75%;flex-direction:column;justify-content:space-around;align-items:center;padding:4px}.hero .info .name{font-size:24px}.hero .info .roles .role{font-size:14px}.hero .info .roles .role:not(:last-child){margin-right:12px}.hero .info .roles .role::after{margin-left:3px;font-size:22px}.hero .info .roles .role.level1::after{content:"■□□"}.hero .info .roles .role.level2::after{content:"■■□"}.hero .info .roles .role.level3::after{content:"■■■"}.hero .info .attrs{height:16px;font-size:16px;line-height:1}.hero .info .attrs>span{margin:0 8px;vertical-align:middle}.hero .info .attrs>span::before{display:inline-block;content:"";background-size:100%;width:16px;height:16px;vertical-align:top}.hero .info .attrs>span.str::before{background-image:url("<%= getImageUrl('hero_strength', ImageType.Icons) %>")}.hero .info .attrs>span.agi::before{background-image:url("<%= getImageUrl('hero_agility', ImageType.Icons) %>")}.hero .info .attrs>span.int::before{background-image:url("<%= getImageUrl('hero_intelligence', ImageType.Icons) %>")}.details{flex-direction:row}.details>*{width:50%}.wrapper .hype{display:block;padding:8px;line-height:1.25}.wrapper .hype .npe{color:#a5e0f3;font-weight:700;line-height:1.25;margin-bottom:8px}.talents{display:grid;grid-template-rows:repeat(4,35px);gap:10px;border:#444 10px solid;box-sizing:border-box;position:relative}.talents::before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:#444;pointer-events:none;box-sizing:border-box;width:395px;transform:translate(-10px,-10px)}.talents .talent{width:375px;text-align:center;display:flex;align-items:center;background:#000;position:relative}.talents .talent .left,.talents .talent .right{width:170px;font-size:12px}.talents .talent .level{flex:0 0 auto;width:35px;height:35px;font-size:18px;line-height:35px;text-align:center;box-sizing:border-box;border-radius:100%;color:#e7d292;text-shadow:0 0 8px #ff531c;background-color:#444}.details .list table td{width:50%;text-align:center}.details .list table tr:nth-child(2n){background-color:#333}.details .list{display:flex;flex-direction:column;padding:6px}.details .list>*{flex-grow:1}.details .bars{height:64px;flex-grow:0}.details .bars>div{height:28px;margin-bottom:4px;border-radius:5px;box-shadow:inset 0 0 2px rgba(0,0,0,.5),0 0 2px rgba(0,0,0,.3);position:relative;display:flex;align-items:center}.details .bars .health{background:linear-gradient(to top,#279027 0,#27a329 56%,#96e891 83%,#366a35 100%)}.details .bars .mana{background:linear-gradient(to top,#4676c4 0,#2b76d5 56%,#95cdff 100%)}.details .bars .mana.zero{background:0 0}.details .bars span{text-shadow:#000 1px 1px 4px}.details .bars .number{font-size:18px;position:absolute;left:50%;transform:translateX(-50%)}.details .bars .suffix{margin-left:auto;font-size:14px;padding-right:10px}.details .stats{display:flex;justify-content:space-around;line-height:1.3}.details .stats>.column>p{padding:4px;color:#b2b2b2}.details .stats .stat img{width:20px}.facets{display:flex;flex-wrap:wrap;gap:10px}.facets>.facet,.skill>.facet{flex:1 1 calc(50% - 10px);box-sizing:border-box;background-color:#181f24;position:relative;border:1px solid #2b2f33}.facets>.facet:nth-child(odd):last-child{flex-basis:100%}.facets>.facet>.name_back,.skill>.facet>.name_back{position:absolute;height:50px;width:100%}.facet>.name_back.type_0{background:linear-gradient(to right,#9f3c3c,#4a2026)}.facet>.name_line.type_0{filter:invert(22%) sepia(100%) saturate(100%) hue-rotate(316deg) brightness(98%) contrast(100%)}.facet>.name_back.type_1{background:linear-gradient(to right,#c8a45c,#6f3d21)}.facet>.name_line.type_1{filter:invert(54%) sepia(99%) saturate(100%) hue-rotate(0deg) brightness(97%) contrast(100%)}.facet>.name_back.type_2{background:linear-gradient(to right,#a2b23e,#2d5a18)}.facet>.name_line.type_2{filter:invert(57%) sepia(100%) saturate(100%) hue-rotate(32deg) brightness(93%) contrast(100%)}.facet>.name_back.type_3{background:linear-gradient(to right,#547ea6,#2a385e)}.facet>.name_line.type_3{filter:invert(39%) sepia(100%) saturate(99%) hue-rotate(167deg) brightness(99%) contrast(100%)}.facet>.name_back.type_4{background:linear-gradient(to right,#675cae,#261c44)}.facet>.name_line.type_4{filter:invert(33%) sepia(100%) saturate(100%) hue-rotate(207deg) brightness(99%) contrast(100%)}.facet>.name_back.type_5{background:linear-gradient(to right,#adb6be,#4e5557)}.facet>.name_line.type_5{filter:invert(73%) sepia(23%) saturate(99%) hue-rotate(166deg) brightness(93%) contrast(94%)}.facet>.name_line{position:absolute;background-size:cover;height:50px;width:100%;background-image:url("https://cdn.akamai.steamstatic.com/apps/dota2/images/dota_react/icons/facets/ripple_texture.png")}.facet>.name{height:50px;line-height:50px;z-index:1;position:relative;display:flex}.facet>.name>img{width:24px;padding:13px;background-color:#0003}.facet>.name>span{margin-left:16px;letter-spacing:2px;text-shadow:2px 2px 3px rgba(0,0,0,.3),4px 4px 6px rgba(0,0,0,.2),6px 6px 9px rgba(0,0,0,.1)}.facet>.content{padding:12px;display:flex;flex-direction:column;gap:12px}.facet>.content>.ability{display:flex;flex-direction:column;gap:12px}.facet>.content>.ability>.name{background:linear-gradient(to right,#9bcdff17 0,#9bcdff09 30%,#d0e8ff00 100%);line-height:1}.facet>.content>.ability>.name>img{width:30px}.facet>.content>.ability>.name>span{margin-left:10px;font-size:14px}.facet>.content .description{color:#9ab0cd}.facet>.content>.ability>.attributes{font-size:12px;display:flex;flex-direction:column;gap:5px}.facet>.content>.ability>.attributes .item{color:#737373}.facet>.content .value{color:#fff}.skills{width:800px;display:flex;flex-wrap:wrap}.skill{background-color:#141b1f;margin:5px;box-shadow:0 0 5px #fff;width:390px}.skill>:not(:nth-child(2)){padding-left:8px}.skill>.title{position:relative;background-color:#1f272b;padding:8px;font-weight:100;height:auto;width:auto}.skill>.title>.name{font-family:KaiTi,"楷体","楷体_GB2312",STKaiti,serif}.skill>.title>.is_innate{font-size:14px;line-height:18px;width:auto;display:inline;padding:2px 8px;box-sizing:content-box;background-color:#5b93d1}.skill>.title.name_back>img{width:16px}.skill img.scepter,.skill img.shard{position:absolute;width:24px;right:8px;top:50%;transform:translateY(-50%)}.skill .img_stats{display:flex;color:#546780;margin-bottom:16px;border-top:#2a363c 1px solid;border-bottom:#2a363c 1px solid}.skill .img_stats img{width:128px}.skill .img_stats .stats{padding:8px}.skill .stats .dmg_type.Physical span{color:#ae2f28}.skill .stats .dmg_type.Magical span{color:#5b93d1}.skill .stats .dmg_type.Pure span{color:#c29c4a}.skill .stats .dispellable.No span{color:red}.skill .stats .dispellable.Strong span{color:#9828ae}.skill .stats .bkbpierce.Yes span{color:#6add71}.skill>.description{color:#9bb1ce;margin-bottom:32px}.skill>.facet{padding-left:0;margin-bottom:16px}.skill .value{color:#fff}.skill .aghanim_description{padding-left:0;color:#9bb1ce;margin-bottom:16px;border:#263945 solid 3px;box-sizing:border-box}.skill .aghanim_description .title{display:block;font-size:20px;background-color:#263945;padding:12px}.skill .aghanim_description .desc{margin:12px;display:block}.skill .aghanim_description img{position:unset;transform:none;margin-right:8px}.skill .notes{padding:12px;margin-bottom:12px;color:#9fb7c6;background-color:#263945}.skill .attributes{line-height:1.2em;margin-bottom:12px}.skill .attributes .heading{color:#546780}.skill .attributes .values{color:#4b525d}.skill .attributes .values img{width:16px}.skill .attributes .facet{display:inline-flex;font-size:1em;align-items:center;line-height:1.2em;position:unset;color:#fff}.skill .attributes .facet span{height:auto;width:auto;position:unset;padding:0 2px}.skill .attributes .facet img{width:auto;height:1em;padding-right:.2em}.skill .attributes .alternative .plus{display:none}.skill .attributes .primary~.alternative .plus{display:inline}.skill .attributes .primary~.alternative::before{content:"("}.skill .attributes .primary~.alternative::after{content:")"}.skill .cooldown{padding-right:12px}.skill .cooldown::before,.skill .mana_cost::before{content:"";background-size:100%;width:21px;height:21px;display:inline-block;vertical-align:middle}.skill .cooldown::before{background-image:url("data:image/webp;base64,UklGRnABAABXRUJQVlA4TGMBAAAvE8AEEE2QTdqmkI5pRP/DS2wBEIT/bw8R/U8Ddq1NipSvGnd3eXJ3d3kmEM+GTDwBciCBTQCd7qpCeokBQdu2cc6f8tcQEBT5P9oEIGiwYuMBECqAbCUVIIAa19Py1I/AQMrJVGEABACe+DRqcl2GUxeOwIAzQkAAMCBxyi3BRcbSkzAKyIDTciQOAAgwyx43JA4CWRkeQXAAQKkGEs5+/siARwFHAHCUiAGwPwAEAGQSDgCAoCQUQc7OkwMbhgAAJBIKEAtkE9enBgcABEUxAIwyZ6cOx/lTkPiHsX+mqCQTMQRwcmAszPLUoTiCkVAAbDKEAWWUjWlGMYyEAR4n4wMeiBk8W9M1lphIQmAsmWxBJ1vfAJZIBMAoJwNQ0JTN04/yRaCCftofwWBkAMAjQ6BILaWMsEkFFVTTk/qEETyQlYFXnh9jmABKbkgzNQgemYwnTOKFEgAAOGJAeUbyhQIAAA==")}.skill .mana_cost::before{background-image:url("data:image/webp;base64,UklGRsYAAABXRUJQVlA4TLkAAAAvE8AEEIfBOIDbtEmdpEDhS8G2kSTF0eteuLOebJz/0KDREgwBAGEu/89yqe5LCyEaDT03NBcLpEXnJgkZKS2dmlqAlNSCgJXURBoNCHAY2bbSvEP0fxzi7sGh//rslRDR/xQxS0bXRkRMmr5Ho8iISY5523Ztr2vabBuaZZLVE99wRqWAUgGFAv4K+Cngo4CXAp4KeCjgroCbAi4KVv7pOnFwF8aP9+fRXeAaMZ63GbXWiIhxGJ0bEQA=")}.skill .lore{font-size:13px;color:#3e4f5b;padding:8px;line-height:1.25}body .wrapper>.lore{display:block;line-height:1.25;padding:8px;font-family:KaiTi,"楷体","楷体_GB2312",STKaiti,serif;color:#aaa}</style> <% if (fontFamily) { %> <%- "<style>" %> <%- `body { font-family: ${fontFamily}; }` %> <%- "</style>" %> <% } %> </head><body> <% let hero = data; %> <% if (hero.primary_attr==3) {
|
|
2
|
+
const base_damage = Math.floor((hero.str_base+hero.agi_base+hero.int_base)*0.45);
|
|
3
|
+
hero.damage_max+=base_damage;
|
|
4
|
+
hero.damage_min+=base_damage;
|
|
5
|
+
} %> <% const primary_attrs = { "3": "hero_universal", "0": "hero_strength", "1": "hero_agility", "2": "hero_intelligence" }; %> <% const hero_dc = dotaconstants.heroes[hero.id]; %> <div class="wrapper"><div class="hero" id="<%= hero.id %>"><img src="<%= getImageUrl(hero["name"].match(/^npc_dota_hero_(.+)$/)[1], ImageType.Heroes) %>"/> <img class="pri_attr" src="<%= getImageUrl(primary_attrs[hero.primary_attr], ImageType.Icons) %>"/><div class="info"><p class="name"><%= hero.name_loc %></p><p class="roles"> <% hero.role_levels.forEach((item, index) => { %> <% if (item > 0) { %> <span class="role level<%= item %>"><%= $t("dota2tracker.template.roles."+index) %></span> <% } %> <% }); %> </p><p class="attrs"><span class="str"><%= hero.str_base %> <span class="gain">+<%= hero.str_gain.toFixed(1) %></span></span><span class="agi"><%= hero.agi_base %> <span class="gain">+<%= hero.agi_gain.toFixed(1) %></span></span><span class="int"><%= hero.int_base %> <span class="gain">+<%= hero.int_gain.toFixed(1) %></span></span></p></div></div><div class="hype"><p class="npe"><%= hero.npe_desc_loc %></p> <%- hero.hype_loc %> </div><div class="details"><div class="talents"><div class="talent"><div class="left"><%= hero.talents[7].name_loc %></div><div class="level">25</div><div class="right"><%= hero.talents[6].name_loc %></div></div><div class="talent"><div class="left"><%= hero.talents[5].name_loc %></div><div class="level">20</div><div class="right"><%= hero.talents[4].name_loc %></div></div><div class="talent"><div class="left"><%= hero.talents[3].name_loc %></div><div class="level">15</div><div class="right"><%= hero.talents[2].name_loc %></div></div><div class="talent"><div class="left"><%= hero.talents[1].name_loc %></div><div class="level">10</div><div class="right"><%= hero.talents[0].name_loc %></div></div></div><div class="list"><div class="bars"><div class="health"><span class="number"><%= hero.max_health %></span><span class="suffix">+<%= hero.health_regen.toFixed(1) %></span></div><div class="mana<%= !hero.max_mana ? ' zero' : '' %>"><span class="number"><%= hero.max_mana %></span><span class="suffix">+<%= hero.mana_regen.toFixed(1) %></span></div></div><div class="stats"><div class="column"><p><%= $t("dota2tracker.template.attack") %></p><div class="stat"><img src="<%= getImageUrl("icon_damage", ImageType.HeroStats) %>"/> <span><%= hero.damage_min %>~<%= hero.damage_max %></span></div><div class="stat"><img src="<%= getImageUrl("icon_attack_time", ImageType.HeroStats) %>"/> <span><%= hero.attack_rate.toFixed(1) %></span></div><div class="stat"><img src="<%= getImageUrl("icon_attack_range", ImageType.HeroStats) %>"/> <span><%= hero.attack_range %></span></div> <% if (hero_dc.attack_type !== "Melee") { %> <div class="stat"><img src="<%= getImageUrl("icon_projectile_speed", ImageType.HeroStats) %>"/> <span><%= hero.projectile_speed %></span></div> <% } %> </div><div class="column"><p><%= $t("dota2tracker.template.defense") %></p><div class="stat"><img src="<%= getImageUrl("icon_armor", ImageType.HeroStats) %>"/> <span><%= hero.armor.toFixed(1) %></span></div><div class="stat"><img src="<%= getImageUrl("icon_magic_resist", ImageType.HeroStats) %>"/> <span><%= hero.magic_resistance %>%</span></div></div><div class="column"><p><%= $t("dota2tracker.template.mobility") %></p><div class="stat"><img src="<%= getImageUrl("icon_movement_speed", ImageType.HeroStats) %>"/> <span><%= hero.movement_speed %></span></div><div class="stat"><img src="<%= getImageUrl("icon_turn_rate", ImageType.HeroStats) %>"/> <span><%= hero.turn_rate.toFixed(1) %></span></div><div class="stat"><img src="<%= getImageUrl("icon_vision", ImageType.HeroStats) %>"/> <span><%= hero.sight_range_day %> / <%= hero.sight_range_night %></span></div></div></div></div></div><div class="facets"> <% hero.facets.forEach(facet => { %> <div class="facet"><div class="name_back type_<%= facet.color %>"></div><div class="name_line type_<%= facet.color %>"></div><p class="name"><img src="<%= getImageUrl(facet.icon, ImageType.IconsFacets) %>"/> <span><%- facet.title_loc %></span></p><div class="content"> <% if (facet.description_loc && !facet.abilities?.some(ability => ability.description_ability_loc === facet.description_loc)) { %> <p class="description"><%- facet.description_loc %></p> <% } %> <% if (facet.abilities) { %> <% facet.abilities.forEach(ability => { %> <div class="ability"><div class="name"><img src="<%= getImageUrl(ability.name, ImageType.Abilities) %>" onerror="this.onerror=null; this.src='<%= getImageUrl(`innate_icon`, ImageType.Icons) %>';"/> <span><%- ability.name_loc %></span></div> <% if (ability.description_ability_loc) { %> <div class="description"><%- ability.description_ability_loc %></div> <% } %> <% if (ability.attributes && ability.attributes.length) { %> <% ability.attributes.forEach(attr => { %> <div class="attributes"><p><span class="item"><%- attr.heading_loc %></span><span class="values"><%- attr.values.map(value => value + (attr.is_percentage ? "%" : "")).join(" / ") %></span></p></div> <% }); %> <% } %> </div> <% }); %> <% } %> </div></div> <% }); %> </div><div class="skills"> <% hero.abilities.forEach((item) => {
|
|
6
|
+
const ability_dc = dotaconstants.abilities[item.name];
|
|
7
|
+
|
|
8
|
+
// 预先查找冷却和耗蓝数据,避免在HTML中反复find
|
|
9
|
+
const cdStats = item.special_values.find(sv => sv.name == "AbilityCooldown");
|
|
10
|
+
const manaStats = item.special_values.find(sv => sv.name == "AbilityManaCost");
|
|
11
|
+
|
|
12
|
+
// 预先计算是否需要显示冷却/耗蓝 (非空 且 不全为0)
|
|
13
|
+
const hasCd = cdStats?.values_float.length && !(cdStats.values_float.length === 1 && cdStats.values_float[0] === 0);
|
|
14
|
+
const hasMana = manaStats?.values_float.length && !(manaStats.values_float.length === 1 && manaStats.values_float[0] === 0);
|
|
15
|
+
%> <div class="skill<%= item.facet ? ' facet' : '' %>" data-innate="<%= item.ability_is_innate && !item.ability_is_facet %>"><p class="title<%= item.facet ? (' name_back type_' + item.facet?.color) : '' %>"> <% if (item.facet) { %> <img src="<%= getImageUrl(item.facet?.icon, ImageType.IconsFacets) %>"> <% } %> <span class="name"><%= item.name_loc %></span> <% if (item.ability_is_innate && !item.ability_is_facet) { %> <span class="is_innate"><%= $t("dota2tracker.template.innate") %></span> <% } %> <% if (item.ability_is_granted_by_scepter) { %> <img src="<%= getImageUrl("scepter") %>" class="scepter"> <% } %> <% if (item.ability_is_granted_by_shard) { %> <img src="<%= getImageUrl("shard") %>" class="shard"> <% } %> </p><div class="img_stats"><img src="<%= getImageUrl(item.name, ImageType.Abilities) %>" onerror="this.onerror=null; this.src='<%= getImageUrl(`innate_icon`, ImageType.Icons) %>';"/><div class="stats"><p class="behavior"> <%= $t("dota2tracker.template.ability") %> <%= [].concat(ability_dc?.behavior)
|
|
16
|
+
.filter((beh) => beh !== "Hidden" || !(item.ability_is_granted_by_shard || item.ability_is_granted_by_scepter))
|
|
17
|
+
.map((beh) => $t("dota2tracker.template.behavior." + beh)).join("/") %> </p> <% if (ability_dc?.target_team) { %> <p class="target_team"> <%= $t("dota2tracker.template.affects") %> <%= [].concat(ability_dc?.target_team).map((tt) => $t("dota2tracker.template.target_team." + tt)).join("/") %> </p> <% } %> <% if (!Array.isArray(ability_dc?.dmg_type) && ability_dc?.dmg_type) { %> <p class="dmg_type <%= ability_dc?.dmg_type %>"> <%= $t("dota2tracker.template.damage_type") %> <span><%= $t("dota2tracker.template.damage_type_" + ability_dc?.dmg_type) %></span></p> <% } %> <% if (ability_dc?.dispellable) { %> <p class="dispellable <%= ability_dc?.dispellable == 'Strong Dispels Only' ? 'Strong' : ability_dc?.dispellable %>"> <%= $t("dota2tracker.template.dispellable") %> <span><%= $t("dota2tracker.template." + (ability_dc?.dispellable == "Strong Dispels Only" ? "dispellable_Strong" : ability_dc?.dispellable)) %></span></p> <% } %> <% if (!Array.isArray(ability_dc?.bkbpierce) && ability_dc?.bkbpierce) { %> <p class="bkbpierce <%= ability_dc?.bkbpierce %>"> <%= $t("dota2tracker.template.bkbpierce") %> <span><%= $t("dota2tracker.template." + ability_dc?.bkbpierce) %></span></p> <% } %> </div></div><p class="description"><%- item.desc_loc %></p> <%
|
|
18
|
+
const facetStartIndex = hero.facets.length - item.facets_loc.length;
|
|
19
|
+
item.facets_loc.forEach((facet_loc, i) => {
|
|
20
|
+
const realIndex = i + facetStartIndex;
|
|
21
|
+
if (realIndex >= 0 && facet_loc !== "") {
|
|
22
|
+
const currentFacet = hero.facets[realIndex];
|
|
23
|
+
%> <div class="facet"><div class="name_back type_<%= currentFacet.color %>"></div><div class="name_line type_<%= currentFacet.color %>"></div><p class="name"><img src="<%= getImageUrl(currentFacet.icon, ImageType.IconsFacets) %>"/> <span><%- currentFacet.title_loc %></span></p><div class="content"><div class="ability"><div class="description"><%- currentFacet.abilities.find(ab => ab.id == item.id)?.description_ability_loc %></div></div></div></div> <%
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
%> <% if (item.ability_has_scepter && !item.ability_is_granted_by_scepter && item.scepter_loc) { %> <p class="aghanim_description"><span class="title"><img src="<%= getImageUrl("scepter") %>" class="scepter"><%= $t("dota2tracker.template.scepter") %></span><span class="desc"><%- item.scepter_loc %></span></p> <% } %> <% if (item.ability_has_shard && !item.ability_is_granted_by_shard && item.shard_loc) { %> <p class="aghanim_description"><span class="title"><img src="<%= getImageUrl("shard") %>" class="shard"><%= $t("dota2tracker.template.shard") %></span><span class="desc"><%- item.shard_loc %></span></p> <% } %> <% if (item.notes_loc && item.notes_loc.length > 0) { %> <div class="notes"> <% item.notes_loc.forEach((note) => { %> <p><%- note %></p> <% }); %> </div> <% } %> <div class="attributes"> <%
|
|
27
|
+
item.special_values.filter(sv => sv.heading_loc).forEach((sv) => {
|
|
28
|
+
// 预计算条件,简化模板逻辑
|
|
29
|
+
const isAllZero = sv.values_float?.every(value => value === 0) || sv.values_float.length == 0;
|
|
30
|
+
const hasScepterVal = sv.values_scepter && sv.values_scepter.length;
|
|
31
|
+
const hasShardVal = sv.values_shard && sv.values_shard.length;
|
|
32
|
+
const hasFacetVal = sv.facet_bonus.name && hero.facets.some(facet => facet.name == sv.facet_bonus.name);
|
|
33
|
+
const hasTalentVal = sv.bonuses.length > 0;
|
|
34
|
+
|
|
35
|
+
// 如果基础值全为0,且没有任何额外加成(A杖/魔晶/命石),则该条目其实没意义,但在原逻辑里似乎是作为空 primary 渲染?
|
|
36
|
+
// 原逻辑:如果 (全0) 且 (有任意额外加成) -> 显示空 primary。
|
|
37
|
+
// 如果 (全0) 且 (无额外加成) -> 这条根本不会显示?不,原逻辑里 primary 即使全0也会渲染 span,只是加了 empty class。
|
|
38
|
+
|
|
39
|
+
// 让我们遵循原逻辑:
|
|
40
|
+
// Primary 部分:除非 (全0 且 有额外加成),否则渲染数值。
|
|
41
|
+
// 如果 (全0 且 有额外加成) -> 渲染内容为空字符串。
|
|
42
|
+
%> <p><span class="heading"><%= sv.heading_loc %></span><span class="values"> <% if (!isAllZero || (isAllZero && (hasScepterVal || hasShardVal || hasFacetVal))) { %> <span class="primary<%= isAllZero ? ' empty' : '' %>"> <% if (!isAllZero) { %> <%= sv.values_float.map(value => value + (sv.is_percentage ? "%" : "")).join(" / ") %> <% } %> </span> <% } %> <% if (hasScepterVal) { %> <span class="alternative scepter"><img src="<%= getImageUrl("scepter") %>"/> <%- sv.values_scepter.map(value => (value > 0 ? '<span class="plus">+</span>' : "") + value + (sv.is_percentage ? "%" : "")).join(" / ") %> </span> <% } %> <% if (hasShardVal) { %> <span class="alternative shard"><img src="<%= getImageUrl("shard") %>"/> <%- sv.values_shard.map(value => (value > 0 ? '<span class="plus">+</span>' : "") + value + (sv.is_percentage ? "%" : "")).join(" / ") %> </span> <% } %> <% if (hasFacetVal) {
|
|
43
|
+
const facet = hero.facets.find(f => f.name == sv.facet_bonus.name);
|
|
44
|
+
%> <span class="alternative facet"><span class="facet"><span class="name_back type_<%= facet.color %>"><img src="<%= getImageUrl(facet.icon, ImageType.IconsFacets) %>"/> <%= sv.facet_bonus.values.map(value => value + (sv.is_percentage ? "%" : "")).join(" / ") %> </span></span></span> <% } %> <% if (hasTalentVal) { %> <span class="alternative talent"> <% sv.bonuses.forEach(bonus => { %> <img src="<%= getImageUrl("talents", "icons", "svg") %>"/> <%- (bonus.value > 0 ? '<span class="plus">+</span>' : "") + bonus.value + (sv.is_percentage ? "%" : "") %> <% }); %> </span> <% } %> </span></p> <% }); %> </div><p> <% if (hasCd) { %> <span class="cooldown"> <%= cdStats.values_float.join(" / ") %> </span> <% } %> <% if (hasMana) { %> <span class="mana_cost"> <%= manaStats.values_float.join(" / ") %> </span> <% } %> </p> <% if (item.lore_loc) { %> <p class="lore"><%= item.lore_loc %></p> <% } %> </div> <% }); %> </div><div class="lore"> <%- hero.bio_loc %> </div></div></body><script>document.addEventListener('DOMContentLoaded', function() {
|
|
45
|
+
const items = document.querySelectorAll('.skills > .skill');
|
|
46
|
+
items.forEach(item => {
|
|
47
|
+
// const name = item.getAttribute('data-name');
|
|
48
|
+
const abilityIsInnate = item.getAttribute('data-innate') === 'true';
|
|
49
|
+
const img = item.querySelector('.img_stats > img');
|
|
50
|
+
const imageUrl = img.src;
|
|
51
|
+
|
|
52
|
+
// Check if image exists
|
|
53
|
+
const image = new Image();
|
|
54
|
+
image.src = imageUrl;
|
|
55
|
+
image.onload = function() {
|
|
56
|
+
// Image exists, do nothing
|
|
57
|
+
};
|
|
58
|
+
image.onerror = function() {
|
|
59
|
+
// Image doesn't exist
|
|
60
|
+
if (abilityIsInnate) {
|
|
61
|
+
item.style.order = -1;
|
|
62
|
+
item.style.flexBasis = "100%";
|
|
63
|
+
img.src = '<%- getImageUrl("innate_icon",ImageType.Icons) %>'; // Set backup image URL
|
|
64
|
+
// item.querySelector(".cooldown").style.display = "none";
|
|
65
|
+
// item.querySelector(".mana_cost").style.display = "none";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
});</script></html>
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% const item = data; %> <% (item.recipes.length ? item.recipes : [undefined]).forEach(recipe => { %> <div class="recipes"> <% if (item.builds_into.length) { %> <% const multipleClass = item.builds_into.length > 1 ? " multiple" : ""; %> <div class="upper<%= multipleClass %>"> <% item.builds_into.forEach(targetItem => { %> <div class="item"><img src="<%= getImageUrl(targetItem.name.startsWith("recipe_") ? "recipe" : targetItem.name, ImageType.Items) %>"><p><%= targetItem.name_loc %></p></div> <% }) %> </div><div class="vline<%= multipleClass %>"> <% for(let i = 0; i < item.builds_into.length; i++) { %> <div class="item line"></div> <% } %> </div><div class="hline<%= multipleClass %>"></div><div class="vline<%= multipleClass ? " direct" : "" %>"><div class="item line"></div></div> <% } %> <div class="middle"><div class="item"><img src="<%= getImageUrl(item.name.startsWith("recipe_") ? "recipe" : item.name, ImageType.Items) %>"><p><%= item.name_loc %></p></div></div> <% if (item.recipes.length && recipe) { %> <%
|
|
2
|
+
// 检查是否需要显式添加“卷轴”到合成列表
|
|
3
|
+
// 注意:这里避免直接修改原 recipe.items 对象,创建一个新数组
|
|
4
|
+
let ingredients = [...recipe.items];
|
|
5
|
+
if (dotaconstants.items["recipe_" + item.name]) {
|
|
6
|
+
ingredients.push({ name: "recipe", name_loc: $t("dota2tracker.template.recipe") });
|
|
7
|
+
}
|
|
8
|
+
const multipleClass = ingredients.length > 1 ? " multiple" : "";
|
|
9
|
+
%> <div class="vline<%= multipleClass ? " direct" : "" %>"><div class="item line"></div></div><div class="hline<%= multipleClass %>"></div><div class="vline<%= multipleClass %>"> <% for(let i = 0; i < ingredients.length; i++) { %> <div class="item line"></div> <% } %> </div><div class="lower<%= multipleClass %>"> <% ingredients.forEach(ingredient => { %> <div class="item"><img src="<%= getImageUrl(ingredient.name.startsWith("recipe_") ? "recipe" : ingredient.name, ImageType.Items) %>"><p><%= ingredient.name_loc %></p></div> <% }) %> </div> <% } %> </div> <% }) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{margin:0;padding:0}html,body{background-color:#000;font-size:16px;width:404px}.container{margin:2px;background-color:#222d35;width:400px;overflow:hidden}.container>*{padding:10px}.container>*.empty{padding:0}.header{position:relative;background-color:#32424e;color:#fff;display:flex}.header>img{width:70px;height:auto;vertical-align:middle;position:relative}.header div{margin-left:10px}.header .name{font-family:KaiTi,\6977\4f53,\6977\4f53_GB2312,STKaiti,serif;text-shadow:1px 1px 2px #111}.header .name .item_id{color:#999}.header .cost{display:flex;color:#f4d652;align-items:center}.stats{padding:5px;color:#667e9b;font-size:14px;margin:0 10px;border-block:1px solid #405159;white-space:nowrap}.stats p{display:flex}.stats .dmg_type span.Physical{color:#ae2f28}.stats .dmg_type span.Magical{color:#5b93d1}.stats .dmg_type span.Pure{color:#c29c4a}.stats .dispellable span.No{color:red}.stats .dispellable span.Strong{color:#9828ae}.stats .bkbpierce span.Yes{color:#6add71}.container>.attrs{color:#aabbd2;text-shadow:1px 1px 0 #333}.value{color:#fff;font-weight:700}.abilities{font-size:14px;margin:10px;padding:0;display:flex;flex-direction:column;gap:6px}.ability.passive{background-color:#2d3c49;color:#7e8b9e}.ability.passive h1{color:#cce2ff;background-image:linear-gradient(to right,#3b505e,#2b3c47)}.ability.active{background-color:#2a3550;color:#9ba2d4}.ability.active h1{color:#aaf;background-image:linear-gradient(to right,#5155b9,#2a3550);display:flex;justify-content:space-between}.ability h1{font-size:14px;padding:6px 8px;text-shadow:1px 1px 2px #111;font-weight:400;display:flex;justify-content:space-between;align-items:center}.ability>p{padding:8px;text-shadow:1px 1px 2px #000}.ability .attrs{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end;max-width:50%}.ability .attr{display:flex;gap:6px;align-items:center}.ability .attr>.icon{width:16px;height:16px;border-radius:4px;border:1px solid #000}.notes{font-size:14px;margin:10px;color:#beddf0;background-color:#3b5566}.lore{margin:10px;font-size:12px;color:#566d7e;background-color:#172025}.recipes{overflow:hidden;font-size:12px;color:#fff;padding:20px 50px}.recipes>*{display:flex;width:100%;justify-content:center}.recipes>*.multiple{justify-content:space-between}.recipes .item{width:60px;text-align:center}.recipes .item img{width:100%}.hline.multiple{width:calc(100% - 59px);background-color:#fff;height:1px;margin-left:30px}.recipes .line{height:5px;background:linear-gradient(0deg,#fff,#fff) no-repeat center/1px 100%}.recipes *.multiple .line,.recipes *.direct .line{height:10px}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const item = data;
|
|
3
|
+
const citem = dotaconstants.items[item.name];
|
|
4
|
+
|
|
5
|
+
// --- 工具函数与数据预处理 ---
|
|
6
|
+
function capitalize(str) {
|
|
7
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function toArray(obj) {
|
|
11
|
+
return Array.isArray(obj) ? obj : [obj];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 构建占位符字典
|
|
15
|
+
const valueMap = item.special_values.reduce((map, val) => {
|
|
16
|
+
map[val.name] = val.is_percentage ?
|
|
17
|
+
`${val.values_float.join("/")}%` :
|
|
18
|
+
val.values_float.join("/");
|
|
19
|
+
return map;
|
|
20
|
+
}, {});
|
|
21
|
+
|
|
22
|
+
// 文本替换辅助函数
|
|
23
|
+
const processText = (text) => {
|
|
24
|
+
if (!text) return "";
|
|
25
|
+
return text
|
|
26
|
+
.replace(/%(\w+)%/g, (_, key) => `<span class="value">${valueMap[key] ?? `[${key}]`}</span>`)
|
|
27
|
+
.replace(/%%/g, '<span class="value">%</span>')
|
|
28
|
+
.trim();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// 解析技能描述
|
|
32
|
+
function parseAbilityDesc(desc) {
|
|
33
|
+
if (!desc) return [];
|
|
34
|
+
const abilityRegExp = /<h1>(.*?)<\/h1>((?:(?!<h1>).|\n)*)/g;
|
|
35
|
+
return Array.from(desc.matchAll(abilityRegExp)).map(match => ({
|
|
36
|
+
name: match[1].trim(),
|
|
37
|
+
desc: processText(match[2])
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 预计算数据
|
|
42
|
+
const parsedAbilities = parseAbilityDesc(item.desc_loc);
|
|
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 => {
|
|
45
|
+
const match = value.heading_loc.match(/^([+-]?)(.*)/) || [];
|
|
46
|
+
const sign = match[1] || '+';
|
|
47
|
+
const rawText = match[2];
|
|
48
|
+
const processedText = rawText.replace(/\$(\w+)/, (_, p1) => $t(`dota2tracker.template.item_token.${p1}`));
|
|
49
|
+
const isPositive = (value.values_float[0] > 0);
|
|
50
|
+
%> <p class="attr_item <%= isPositive ? "positive" : "negative" %>"> <%= sign %> <span class="value"><%= value.values_float.map(v => v + (value.is_percentage ? "%" : "")).join("/") %></span><span class="desc"><%= processedText %></span></p> <% }) %> </div> <% if (parsedAbilities.length) { %> <div class="abilities"> <% parsedAbilities.forEach(ability => {
|
|
51
|
+
const type = ability.name.startsWith($t("dota2tracker.template.behavior.Passive")) ? "passive" : "active";
|
|
52
|
+
%> <div class="ability <%= type %>"><h1><p class="name"><%= ability.name %></p> <% if (type === "active") { %> <div class="attrs"> <% if (item.mana_costs && item.mana_costs[0]) { %> <div class="attr"><div class="icon" style="background:linear-gradient(#00a4db,#007196)"></div><span class="value"><%= item.mana_costs.join("/") %></span></div> <% } %> <% if (item.cooldowns && item.cooldowns[0]) { %> <div class="attr"><img class="icon" src="<%= getImageUrl("cooldown", ImageType.Icons) %>"> <span class="value"><%= item.cooldowns.join("/") %></span></div> <% } %> </div> <% } %> </h1><p><%- ability.desc %></p></div> <% }); %> </div> <% } %> <% if (processedNotes.length) { %> <div class="notes"> <% processedNotes.forEach(note => { %> <p class="note"><%- note %></p> <% }) %> </div> <% } %> <% if (item.lore_loc) { %> <div class="lore"><p><%= item.lore_loc %></p></div> <% } %> </div> <% if (item.recipes.length || item.builds_into.length) { %> <%- include("./item/recipe") %> <% } %> </body></html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%
|
|
2
|
+
function calculateFontSize(text) {
|
|
3
|
+
const chineseRegex = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/g;
|
|
4
|
+
const totalUnits = text.split('').reduce((acc, char) => {
|
|
5
|
+
return acc + (chineseRegex.test(char) ? 2 : 1);
|
|
6
|
+
}, 0);
|
|
7
|
+
const maxWidth = 44;
|
|
8
|
+
let fontSize = Math.floor((maxWidth / totalUnits) * 1.8);
|
|
9
|
+
return Math.min(12, Math.max(6, fontSize));
|
|
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>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.box{border-radius:5px;border:2px solid rgba(255,255,255,.25);overflow:hidden}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<% const isRecipe = item && !isNeutral && item.isRecipe; %> <% const recipeBgStyle = isRecipe ? `background-image: url('${getImageUrl("recipe", ImageType.Items)}');` : ''; %> <% let imgSrc = ""; %> <% if (item) { %> <% if (isNeutral) { %> <% imgSrc = getImageUrl(dotaconstants.item_ids[item], ImageType.Items); %> <% } else { %> <% imgSrc = getImageUrl(item.name, ImageType.Items); %> <% } %> <% } %> <div class="relative <%= style %>" <%-`style="${recipeBgStyle}"`%>> <% if (item) { %> <% if (!isNeutral) { %> <img class="w-full h-full object-cover aspect-[11/8] <%= isRecipe ? "opacity-75 scale-[0.6] origin-top-right" : '' %>" src="<%= imgSrc %>"><p class="absolute w-full text-[#ccc] text-center bottom-0 leading-[1] text-xs bg-stone-700/50"><%= item.time %></p> <% } else { %> <img class="w-full h-full object-contain" src="<%= imgSrc %>"> <% } %> <% } %> </div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% const startTime = DateTime.fromSeconds(match.startDateTime).toFormat("yyyy-MM-dd HH:mm:ss").slice(2); %> <% const endTime = DateTime.fromSeconds(match.endDateTime).toFormat("yyyy-MM-dd HH:mm:ss").slice(2); %> <% const gameModeStr = ($t("dota2tracker.template.lobby_types." + match.lobbyType) || match.lobbyType) + " / " + ($t("dota2tracker.template.game_modes." + match.gameMode) || match.gameMode); %> <% const regionStr = $t("dota2tracker.template.regions." + match.regionId); %> <% const bannedHeroes = (match.pickBans ?? [])
|
|
2
|
+
.filter(x => !x.isPick)
|
|
3
|
+
.map(hero => {
|
|
4
|
+
const heroData = dotaconstants.heroes[hero.bannedHeroId];
|
|
5
|
+
const matchName = /^npc_dota_hero_(?<name>.+)$/.exec(heroData.name);
|
|
6
|
+
return matchName ? matchName[1] : "";
|
|
7
|
+
});
|
|
8
|
+
%> <% const banGradientStyle = "background-image: linear-gradient(to bottom left, transparent calc(50% - 1px), red calc(50% - 1px), black calc(50% + 1px), transparent calc(50% + 1px));"; %> <div class="w-[790px] h-[100px] flex justify-between box m-[5px]"><div class="flag w-[100px] h-full flex justify-center items-end bg-cover <%= !match.didRadiantWin ? 'grayscale' : "" %>" style="background-image:url('<%= getImageUrl("flag_radiant") %>')"> <%= match.didRadiantWin ? $t("dota2tracker.template.won") : "" %> </div><div class="details grow relative flex-col flex"><p class="w-full flex justify-around"><span><%= $t("dota2tracker.template.match_id_") %><%= match.id %></span><span><%= $t("dota2tracker.template.game_mode_") %><%= gameModeStr %></span><span><%= $t("dota2tracker.template.region_") %><%= regionStr %></span></p><p class="w-full flex justify-around"><span><%= $t("dota2tracker.template.start_time_") %><%= startTime %></span><span><%= $t("dota2tracker.template.end_time_") %><%= endTime %></span></p><p class="w-full grow score flex justify-evenly items-end"><span class="score radiant text-3xl"><%= match.radiant.killsCount %></span><span class="time text-sm"><%= match.durationTime %></span><span class="score dire text-3xl"><%= match.dire.killsCount %></span></p><div class="rank absolute w-[64px] h-[64px] bottom-[10px] left-1/2 -translate-x-1/2<%- match.odParsed && match.lobbyType !== "RANKED" ? ` grayscale` : "" %>"><img class="star absolute w-full" src="<%= getImageUrl('star_' + (match.rank ? match.rank.toString().split('')[1] : '')) %>"> <img class="medal absolute w-full" src="<%= getImageUrl('medal_' + (match.rank ? match.rank.toString().split('')[0] : '')) %>"></div></div><div class="flag w-[100px] h-full flex justify-center items-end bg-cover <%= match.didRadiantWin ? 'grayscale' : "" %>" style="background-image:url('<%= getImageUrl("flag_dire") %>')"> <%= !match.didRadiantWin ? $t("dota2tracker.template.won") : "" %> </div></div><div class="w-[790px] grid grid-cols-2 gap-[5px] mx-[5px]"> <% match.players.forEach(player => { %> <%- include(`./player.ejs`, { match, player, utils: {kc, dc, laneSVG}, facetColor, partyColor }) %> <% }); %> </div><div class="ban_list box m-[5px] flex flex-wrap"> <% bannedHeroes.forEach(heroName => { %> <div class="ban_hero relative w-[10%]"><i class="absolute w-full h-full z-1" <%- `style="${banGradientStyle}"` %>></i> <img class="grayscale" src="<%= getImageUrl(heroName, ImageType.Heroes) %>" alt=""/></div> <% }); %> </div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<% const p = player; %> <% const isLD = p.hero.id == 80; %> <% const gridCol = !p.isRadiant + 1; %> <% const gridRow = (p.position ? parseInt(p.position.slice(-1)) : 0) % 5; %> <% const gridStyle = `grid-column: ${gridCol}; grid-row: ${gridRow}`; %> <% const pColor = partyColor[p.partyId]; %> <% const partyLineStyle = `background-color: ${pColor};`; %> <% const partyMarkStyle = `color: ${pColor};`; %> <% const orderColors = ['red', 'yellow', 'green']; %> <% const orderColor = orderColors[Math.floor(p.order / 4)] || 'white'; %> <% const kcVal = Math.floor(p.killContribution * 100); %> <% const dcVal = Math.floor(p.deathContribution * 100); %> <% const kcStyle = `color: ${utils.kc(kcVal)}`; %> <% const dcStyle = `color: ${utils.dc(dcVal)}`; %> <% const facetGradient = facetColor[p.facet?.color ?? 'Black']; %> <% const facetIcon = p.facet?.icon; %> <% const facetName = p.facet?.displayName ?? "?"; %> <% const damageReport = p.stats?.heroDamageReport?.receivedTotal || {}; %> <% const dealtReport = p.stats?.heroDamageReport?.dealtTotal || {}; %> <% const damageReceived = (damageReport.physicalDamage || 0) + (damageReport.magicalDamage || 0) + (damageReport.pureDamage || 0); %> <% const ccStun = ((dealtReport.stunDuration || 0) / 100).toFixed(2); %> <% const ccSlow = ((dealtReport.slowDuration || 0) / 100).toFixed(2); %> <% const ccDisable = ((dealtReport.disableDuration || 0) / 100).toFixed(2); %> <div class="box h-[320px] flex flex-col gap-[4px] rounded-[5px]" <%- `style="${gridStyle}"` %>><section class="grid grid-cols-[112px_1fr_100px] grid-rows-[63px_24px]"><div class="avatar relative h-full relative row-1 col-1 text-[10px] text-shadow-[-1px_1px_2px_#000,1px_-1px_2px_#000,-1px_1px_2px_#000,1px_1px_2px_#000]"> <% if (p.leaverStatus != "NONE" && p.leaverStatus != "DISCONNECTED") { %> <img class="leaver absolute w-full h-full object-contain" src="<%= getImageUrl("disconnected") %>"/> <% } %> <div class="party contents"><i class="party_line absolute w-full h-[2px]" <%- `style="${partyLineStyle}"` %>></i> <b class="party_mark absolute leading-[1.5] w-[16px] top-[3px] left-[2px] bg-black/80 text-center" <%- `style="${partyMarkStyle}"` %>> <%= match.party[p.partyId] %> </b></div><div class="hero_info absolute top-[3px] right-0 text-right leading-[1.25]"><p class="order" <%- `style="color: ${orderColor};"` %>> <%= p.isRandom ? $t("dota2tracker.template.random") : $t("dota2tracker.template.pick_order", [`${p.order == null ? "?" : p.order + 1}`]) %> </p><p class="position"><%= $t("dota2tracker.template.position_" + p.position?.slice(-1)) ?? '' %></p></div><p class="level absolute bottom-0 right-0 text-xs px-[2px]"><%= p.level %></p><img class="h-full" src="<%= getImageUrl(p.hero.shortName, ImageType.Heroes) %>"/></div><div class="player_profile mx-[10px] row-1 col-2 w-auto h-full overflow-hidden text-center flex flex-col justify-around leading-[1]"><p class="player_name truncate max-w-full"><%= p.steamAccount.name %></p><p class="flex gap-[12px] justify-center"><span class="kda"><%= p.kills %>/<%= p.deaths %>/<%= p.assists %></span><span class="kc" <%- `style="${kcStyle}"` %>><%= kcVal %>%</span> <span class="dc" <%- `style="${dcStyle}"` %>><%= dcVal %>%</span></p><p class="flex gap-[16px] justify-center"><span class="networth text-[rgb(203,176,42)]"><%= p.networth %></span><span class="score"><%= (p.heroDamage / p.networth)?.toFixed(2) %></span></p></div><div class="lane_rank row-1 col-3 flex w-full h-full justify-between"><div class="lane w-[44px] h-full"><p class="text-[10px] text-center"><%= $t("dota2tracker.template.lane_" + p.laneResult) %></p><div class="h-[44px] w-[44px]"><%- utils.laneSVG[p.laneResult] %></div></div><div class="rank_plus w-[36px] h-full relative text-[10px] leading-[1.15] text-shadow-[-1px_1px_0_#000,1px_-1px_0_#000,-1px_1px_0_#000,1px_1px_0_#000]"><div class="rank absolute top-0 size-[36px] z-2"><img class="medal absolute inset-0" src="<%= getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>"/> <img class="stars absolute inset-0" src="<%= getImageUrl('star_' + p.rank.star) %>"/><p class="leader absolute bottom-0 text-center w-full"><%= p.steamAccount?.seasonLeaderboardRank ?? "" %></p></div> <% if (p.dotaPlus) { %> <div class="plus absolute bottom-0 size-[36px] z-1"><img class="" src="<%= getImageUrl("hero_badge_" + (p.dotaPlus ? Math.ceil((p.dotaPlus?.level + 1) / 6) : 1)) %>"><p class="level absolute bottom-0 text-center w-full"><%= p.dotaPlus?.level %></p></div> <% } %> </div></div><div class="facet row-2 col-1 w-full h-full grid grid-cols-[24px_1fr] grid-rows-1 bg-linear-to-r <%- facetGradient %>"> <% if (p.facet) { %> <div class="w-[24px] h-[24px] flex items-center justify-center bg-[#4444]"><img class="h-[18px]" src="<%= getImageUrl(facetIcon, ImageType.IconsFacets) %>"/></div> <% } %> <div class="facet_name w-full truncate grow px-[4px] text-sm flex justify-center items-center"><span class="w-full truncate text-center"><%= facetName %></span></div></div><div class="titles row-2 col-start-2 col-end-3 w-full flex items-center pl-[12px] gap-[6px]"> <% p.titles.forEach(item => { %> <% const [title, color] = $t(item).split("-"); %> <span <%- `style="color: ${color};"` %>><%= title %></span> <% }); %> </div></section> <% if (!isLD) { %> <section class="items w-full grid grid-cols-[repeat(3,3fr)_2fr_3fr] grid-rows-2"><div class="items_main contents"> <% for(let i = 0; i < 6; i++) { %> <%- include("./item.ejs", {item: p.items[i], style: `col-${i%3+1}`, isNeutral: false}) %> <% } %> </div><div class="items_backpack grayscale col-4 row-start-1 row-span-2 flex flex-col"> <% for(let i = 0; i < 3; i++) { %> <%- include("./item.ejs", {item: p.backpacks[i], style: "", isNeutral: false}) %> <% } %> </div><div class="items_neutral contents"> <%- include("./item.ejs", {item: p.neutral0Id, style: "row-start-1 row-span-2 col-5 flex items-center h-full", isNeutral: true}) %> </div></section> <% } else { %> <section class="items w-full grid grid-cols-[repeat(6,3fr)_10fr] grid-rows-4"><div class="items_hero contents"><div class="items_main contents"> <% for(let i = 0; i < 6; i++) { %> <%- include("./item.ejs", {item: p.items[i], style: `row-1 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_backpack grayscale contents"> <% for(let i = 0; i < 3; i++) { %> <%- include("./item.ejs", {item: p.backpacks[i], style: `row-2 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_neutral contents"> <%- include("./item.ejs", {item: p.neutral0Id, style: "row-2 col-6", isNeutral: true}) %> </div></div><div class="items_unit contents"><div class="items_main contents"> <% for(let i = 0; i < 6; i++) { %> <%- include("./item.ejs", {item: p.unitItems[i], style: `row-3 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_backpack grayscale contents"> <% for(let i = 0; i < 3; i++) { %> <%- include("./item.ejs", {item: p.unitBackpacks[i], style: `row-4 col-${i+1}`, isNeutral: false}) %> <% } %> </div><div class="items_neutral contents"> <%- include("./item.ejs", {item: p.additionalUnit.neutral0Id, style: "row-4 col-6", isNeutral: true}) %> </div></div></section> <% } %> <section class="buffs_supports w-full h-[24px] flex justify-between"><div class="buffs flex h-[24px]"> <% p.buffs.forEach(buff => { %> <div class="relative"><img class="w-[33px] h-[24px]" src="<%= getImageUrl(dotaconstants[`${buff.type}_ids`][buff.id], ImageType[buff.type === "ability" ? "Abilities" : "Items"]) %>"/><p class="count absolute w-full bottom-0 leading-[1] text-center bg-stone-700/50 text-[10px]"><%= buff.stackCount || "" %></p></div> <% }); %> </div><div class="supports flex h-[24px]"> <% p.supportItemsCount.forEach(supportItem => { %> <div class="relative"><img class="w-[33px] h-[24px]" src="<%= getImageUrl(supportItem.name, ImageType.Items) %>"/><p class="count absolute w-full bottom-0 leading-[1] text-center bg-stone-700/50 text-[10px]"><%= supportItem.count || "" %></p></div> <% }); %> </div></section><section class="player-stat h-full flex flex-col justify-around text-[13px] leading-[24px] whitespace-nowrap"><p class="w-full flex justify-around"><span><%= $t("dota2tracker.template.hero_damage_") %><span class="hero_damage"><%= p.heroDamage %></span></span><span><%= $t("dota2tracker.template.building_damage_") %><span class="building_damage"><%= p.towerDamage %></span></span><span><%= $t("dota2tracker.template.damage_received_") %><span class="tak"><%= damageReceived %></span></span></p><p class="w-full flex justify-around"><span><%= $t("dota2tracker.template.lasthit_") %><span class="lh"><%= p.numLastHits %></span>/<span class="dn"><%= p.numDenies %></span></span><span><%= $t("dota2tracker.template.GPM/XPM_") %><span class="gpm"><%= p.goldPerMinute %></span>/<span class="xpm"><%= p.experiencePerMinute %></span></span><span><%= $t("dota2tracker.template.heal_") %><span class="heal"><%= p.heroHealing %></span></span></p><p class="w-full flex justify-center"><span><%= $t("dota2tracker.template.crowd_control_duration_") %></span><span><%= ccStun + "/" %></span><span><%= ccSlow + "/" %></span><span><%= ccDisable %></span>s</p></section></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.13 | 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-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-stone-700: oklch(37.4% .01 67.558);--color-black: #000;--color-white: #fff;--spacing: .25rem;--text-xs: .75rem;--text-xs--line-height: calc(1 / .75);--text-sm: .875rem;--text-sm--line-height: calc(1.25 / .875);--text-3xl: 1.875rem;--text-3xl--line-height: 1.2 ;--default-font-family: var(--font-sans);--default-mono-font-family: var(--font-mono)}}@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{.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.top-0{top:calc(var(--spacing) * 0)}.top-\[3px\]{top:3px}.right-0{right:calc(var(--spacing) * 0)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-\[10px\]{bottom:10px}.left-1\/2{left:50%}.left-\[2px\]{left:2px}.z-1{z-index:1}.z-2{z-index:2}.col-1{grid-column:1}.col-2{grid-column:2}.col-3{grid-column:3}.col-4{grid-column:4}.col-5{grid-column:5}.col-6{grid-column:6}.col-start-2{grid-column-start:2}.col-end-3{grid-column-end:3}.row-1{grid-row:1}.row-2{grid-row:2}.row-3{grid-row:3}.row-4{grid-row:4}.row-span-2{grid-row:span 2 / span 2}.row-start-1{grid-row-start:1}.m-\[5px\]{margin:5px}.mx-\[5px\]{margin-inline:5px}.mx-\[10px\]{margin-inline:10px}.contents{display:contents}.flex{display:flex}.grid{display:grid}.aspect-\[11\/8\]{aspect-ratio:11/8}.size-\[36px\]{width:36px;height:36px}.h-\[2px\]{height:2px}.h-\[18px\]{height:18px}.h-\[24px\]{height:24px}.h-\[44px\]{height:44px}.h-\[64px\]{height:64px}.h-\[100px\]{height:100px}.h-\[320px\]{height:320px}.h-full{height:100%}.w-\[10\%\]{width:10%}.w-\[16px\]{width:16px}.w-\[24px\]{width:24px}.w-\[33px\]{width:33px}.w-\[36px\]{width:36px}.w-\[44px\]{width:44px}.w-\[64px\]{width:64px}.w-\[100px\]{width:100px}.w-\[790px\]{width:790px}.w-\[800px\]{width:800px}.w-auto{width:auto}.w-full{width:100%}.max-w-full{max-width:100%}.grow{flex-grow:1}.origin-top-right{transform-origin:top right}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-\[0\.6\]{scale:.6}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[24px_1fr\]{grid-template-columns:24px 1fr}.grid-cols-\[112px_1fr_100px\]{grid-template-columns:112px 1fr 100px}.grid-cols-\[repeat\(3\,3fr\)_2fr_3fr\]{grid-template-columns:repeat(3,3fr) 2fr 3fr}.grid-cols-\[repeat\(6\,3fr\)_10fr\]{grid-template-columns:repeat(6,3fr) 10fr}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.grid-rows-\[63px_24px\]{grid-template-rows:63px 24px}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-evenly{justify-content:space-evenly}.gap-\[4px\]{gap:4px}.gap-\[5px\]{gap:5px}.gap-\[6px\]{gap:6px}.gap-\[12px\]{gap:12px}.gap-\[16px\]{gap:16px}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.overflow-hidden{overflow:hidden}.rounded-\[5px\]{border-radius:5px}.border{border-style:var(--tw-border-style);border-width:1px}.bg-\[\#4444\]{background-color:#4444}.bg-black{background-color:var(--color-black)}.bg-black\/80{background-color:color-mix(in srgb,#000 80%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-black) 80%,transparent)}}.bg-stone-700\/50{background-color:color-mix(in srgb,oklch(37.4% .01 67.558) 50%,transparent);@supports (color: color-mix(in lab,red,red)){background-color:color-mix(in oklab,var(--color-stone-700) 50%,transparent)}}.bg-linear-to-r{--tw-gradient-position: to right;@supports (background-image: linear-gradient(in lab,red,red)){--tw-gradient-position: to right in oklab}background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#2d2d2d\]{--tw-gradient-from: #2d2d2d;--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))}.from-\[\#9f3c3c\]{--tw-gradient-from: #9f3c3c;--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))}.from-\[\#547ea6\]{--tw-gradient-from: #547ea6;--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))}.from-\[\#675cae\]{--tw-gradient-from: #675cae;--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))}.from-\[\#a2b23e\]{--tw-gradient-from: #a2b23e;--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))}.from-\[\#adb6be\]{--tw-gradient-from: #adb6be;--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))}.from-\[\#c8a45c\]{--tw-gradient-from: #c8a45c;--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))}.to-\[\#000000\]{--tw-gradient-to: #000000;--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))}.to-\[\#2a385e\]{--tw-gradient-to: #2a385e;--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))}.to-\[\#2d5a18\]{--tw-gradient-to: #2d5a18;--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))}.to-\[\#4a2026\]{--tw-gradient-to: #4a2026;--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))}.to-\[\#4e5557\]{--tw-gradient-to: #4e5557;--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))}.to-\[\#6f3d21\]{--tw-gradient-to: #6f3d21;--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))}.to-\[\#261c44\]{--tw-gradient-to: #261c44;--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}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.px-\[2px\]{padding-inline:2px}.px-\[4px\]{padding-inline:4px}.pl-\[12px\]{padding-left:12px}.text-center{text-align:center}.text-right{text-align:right}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading, var(--text-3xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading, var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading, var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[13px\]{font-size:13px}.leading-\[1\.5\]{--tw-leading: 1.5;line-height:1.5}.leading-\[1\.15\]{--tw-leading: 1.15;line-height:1.15}.leading-\[1\.25\]{--tw-leading: 1.25;line-height:1.25}.leading-\[1\]{--tw-leading: 1;line-height:1}.leading-\[24px\]{--tw-leading: 24px;line-height:24px}.whitespace-nowrap{white-space:nowrap}.text-\[\#ccc\]{color:#ccc}.text-\[rgb\(203\,176\,42\)\]{color:#cbb02a}.text-white{color:var(--color-white)}.opacity-75{opacity:75%}.grayscale{--tw-grayscale: grayscale(100%);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,)}.text-shadow-\[-1px_1px_0_\#000\,1px_-1px_0_\#000\,-1px_1px_0_\#000\,1px_1px_0_\#000\]{text-shadow:-1px 1px 0 var(--tw-text-shadow-color, #000),1px -1px 0 var(--tw-text-shadow-color, #000),-1px 1px 0 var(--tw-text-shadow-color, #000),1px 1px 0 var(--tw-text-shadow-color, #000)}.text-shadow-\[-1px_1px_2px_\#000\,1px_-1px_2px_\#000\,-1px_1px_2px_\#000\,1px_1px_2px_\#000\]{text-shadow:-1px 1px 2px var(--tw-text-shadow-color, #000),1px -1px 2px var(--tw-text-shadow-color, #000),-1px 1px 2px var(--tw-text-shadow-color, #000),1px 1px 2px var(--tw-text-shadow-color, #000)}}@property --tw-translate-x{syntax: "*"; inherits: false; initial-value: 0;}@property --tw-translate-y{syntax: "*"; inherits: false; initial-value: 0;}@property --tw-translate-z{syntax: "*"; inherits: false; initial-value: 0;}@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-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-text-shadow-color{syntax: "*"; inherits: false;}@property --tw-text-shadow-alpha{syntax: "<percentage>"; inherits: false; initial-value: 100%;}@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-translate-x: 0;--tw-translate-y: 0;--tw-translate-z: 0;--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-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-text-shadow-color: initial;--tw-text-shadow-alpha: 100%}}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% const match = data; %> <% const partyColor = ["#caffe5","#ffe484","#e19be2","#ccdaf4"]; %> <% const facetColor = {
|
|
2
|
+
Red: "from-[#9f3c3c] to-[#4a2026]",
|
|
3
|
+
Yellow: "from-[#c8a45c] to-[#6f3d21]",
|
|
4
|
+
Green: "from-[#a2b23e] to-[#2d5a18]",
|
|
5
|
+
Blue: "from-[#547ea6] to-[#2a385e]",
|
|
6
|
+
Purple: "from-[#675cae] to-[#261c44]",
|
|
7
|
+
Gray: "from-[#adb6be] to-[#4e5557]",
|
|
8
|
+
Black: "from-[#2d2d2d] to-[#000000]"
|
|
9
|
+
};
|
|
10
|
+
%> <% const kc = (num) => { let red = (255 - (num * 255) / 100).toFixed(2); return `rgb(255,${red},${red})`; }; %> <% const dc = (num) => { let gray = ((50 - Math.min(num, 50)) * (255 / 50)).toFixed(2); return `rgb(${gray},${gray},${gray})`; }; %> <% const laneSVG = {
|
|
11
|
+
stomp: getImageUrl("lane_stomp", undefined, ImageFormat.svg),
|
|
12
|
+
advantage: getImageUrl("lane_victory", undefined, ImageFormat.svg),
|
|
13
|
+
disadvantage: getImageUrl("lane_fail", undefined, ImageFormat.svg),
|
|
14
|
+
stomped: getImageUrl("lane_stomped", undefined, ImageFormat.svg),
|
|
15
|
+
tie: getImageUrl("lane_tie", undefined, ImageFormat.svg),
|
|
16
|
+
jungle: getImageUrl("lane_jungle", undefined, ImageFormat.svg),
|
|
17
|
+
};
|
|
18
|
+
%> <!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>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
p{margin:0}img{width:100%;vertical-align:middle}html,body{overflow:hidden;width:800px}nav{font-size:14px;line-height:1.8;height:72px;color:#fff;background-color:#000;display:flex;flex-direction:row;justify-content:space-around;align-items:center}nav>div>p:first-of-type{font-weight:700}nav .match_id p:first-of-type{font-size:16px}nav>div.match_id>p.success:after{content:"\203b<%= $t('dota2tracker.template.analysis_successful') %>";color:#7ba334}nav>div.match_id>p.fail:after{content:"\203b<%= $t('dota2tracker.template.analysis_incomplete') %>";color:#ffb400}nav>div.match_id>p.opendota:after{content:"\203b<%= $t('dota2tracker.template.analysis_by_opendota') %>";color:#34a39a}nav>.rank{width:48px;height:48px;position:relative}nav>.rank>img{width:48px;height:48px;position:absolute}.radiant{color:#3c9028}.dire{color:#9c3628}.match_result{font-weight:700;height:49px;display:flex;justify-content:center;align-items:center}.match_result .win{margin:0 25px}.match_result .win.radiant:after{content:"<%=$t('dota2tracker.template.radiant_won')%>"}.match_result .win.dire:after{content:"<%=$t('dota2tracker.template.dire_won')%>"}.players{display:flex;flex-direction:column}.players .panel{padding:0 10px}.players .panel{padding-top:4px;height:40px;border-top:3px solid #fff;font-size:13.3px;display:grid;grid-template-columns:32px 56px 378px repeat(4,1fr)}.players .panel.radiant{border-color:#3c9028}.players .panel.dire{border-color:#9c3628}.players .panel p{line-height:16px;margin-left:8px}.players .panel .win{font-size:20px;line-height:32px}.players .panel .data{color:#aaa}.player:not(:last-child){border-bottom:1px solid #e1e1e1}.player{color:#000;width:100%;display:grid;grid-template-columns:64px 48px 88px 160px 112px 252px 36px 20px;grid-template-rows:19px 14px 14px 14px;padding-bottom:3px;font-size:12px;line-height:14px;overflow:hidden;justify-content:center}.player>.row-1{margin-top:6px}.player>.hero_avatar{margin-bottom:5px;width:64px;grid-row:1 / span 3;grid-column:1;position:relative}.player>.hero_avatar>.level{width:20px;height:15px;background-color:#323232;position:absolute;bottom:0;right:0;font-size:12px;line-height:15px;color:#fff;text-align:center}.player>.hero_avatar>.party_line{position:absolute;height:2px;top:0;width:100%}.player>.hero_avatar>.party_mark{position:absolute;line-height:1.5;text-align:center;width:16px;font-size:10px;top:3px;left:1px;background-color:#000c}.player>.hero_avatar.party_I>.party_line{background-color:#caffe5}.player>.hero_avatar.party_I>.party_mark{color:#caffe5}.player>.hero_avatar.party_I>.party_mark:after{content:"I"}.player>.hero_avatar.party_II>.party_line{background-color:#ffe484}.player>.hero_avatar.party_II>.party_mark{color:#ffe484}.player>.hero_avatar.party_II>.party_mark:after{content:"II"}.player>.hero_avatar.party_III>.party_line{background-color:#e19be2}.player>.hero_avatar.party_III>.party_mark{color:#e19be2}.player>.hero_avatar.party_III>.party_mark:after{content:"III"}.player>.hero_avatar.party_IV>.party_line{background-color:#ccdaf4}.player>.hero_avatar.party_IV>.party_mark{color:#ccdaf4}.player>.hero_avatar.party_IV>.party_mark:after{content:"IV"}.player>.facet{color:#fff;width:100%;height:16px;grid-row:4;grid-column:1;display:grid;grid-template-columns:16px auto;z-index:1;position:relative;top:-5px}.player>.facet>img{padding:2px;width:12px;height:12px;background-color:#4444}.player>.facet>span{padding:0 2px;line-height:1;display:grid;place-items:center;align-items:center;justify-content:center;height:100%}.player>.facet>span>*{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.player>.facet.Red{background:linear-gradient(to right,#9f3c3c,#4a2026)}.player>.facet.Yellow{background:linear-gradient(to right,#c8a45c,#6f3d21)}.player>.facet.Green{background:linear-gradient(to right,#a2b23e,#2d5a18)}.player>.facet.Blue{background:linear-gradient(to right,#547ea6,#2a385e)}.player>.facet.Purple{background:linear-gradient(to right,#675cae,#261c44)}.player>.facet.Gray{background:linear-gradient(to right,#adb6be,#4e5557)}.player>.facet.Black{background:linear-gradient(to right,#2d2d2d,#000);display:flex;justify-content:center}.player>.rank{position:relative;grid-row:1 / span 3;grid-column:2;width:48px;height:48px}.player>.rank>img{position:absolute}.player>.rank>p{position:absolute;width:100%;bottom:1.5px;text-align:center;font-size:8px;color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}.player>.titles{grid-row:4;grid-column:2/4;margin-left:6px}.player>.player_name{grid-row:1;grid-column:3 / span 2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.player>.player_name>.rank{color:#aaa}.player.radiant>.player_name>.name{color:#3c9028}.player.dire>.player_name>.name{color:#9c3628}.player .pick,.player .networth{grid-column:3}.player .networth .gold{color:#cbb02a;text-shadow:1px 1px 0 #000}.player .hero_damage{grid-row:2;grid-column:4}.player .damage_received{grid-row:3;grid-column:4}.player .tower_damage{grid-row:4;grid-column:4}.player .kda{grid-row:1;grid-column:5}.player .kill_contribution{grid-row:2;grid-column:5}.player .stun_duration{grid-row:3;grid-column:5}.player .heal{grid-row:4;grid-column:5}.player .items{grid-row:1 / span 4;grid-column:6;display:grid;grid-template-columns:24px 192px auto;grid-template-rows:32px 24px}.player .items>div{display:flex;background-color:silver}.player .items .normal{height:32px;grid-column:1/-1;grid-row:1}.player .items .backpack{height:24px;width:96px;grid-row:2}.player .items .normal .item{width:40px;height:30px;margin:1px;position:relative}.player .items .time{position:absolute;width:100%;text-align:center;bottom:0;height:11px;line-height:11px;color:#ccc;background-color:#323232}.player .items .backpack{filter:grayscale(100%)}.player .items .backpack .item,.player .items .bear .item{width:30px;height:22px;margin:1px;position:relative}.player .neutral_item{grid-row:1 / span 3;grid-column:7;overflow:hidden;height:32px;width:32px;border-radius:50%;background-size:auto 100%;background-position:center;margin-left:2px}.player .items .item.recipe{background-image:url(https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/items/recipe.png);background-size:100%}.player .ahgs{grid-row:1 / span 3;grid-column:8}.player .items .bear{height:24px;width:192px;grid-row:2;grid-column:2}.player.bear .items .bear .time{font-size:10px}.player.bear .items .bear_icon{grid-row:2;grid-column:1}.player.bear .items .neutral_item{height:24px;width:24px;grid-row:2;grid-column:3}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<% const match = data; %> <% const darken = (col, amt) => {
|
|
2
|
+
col = col.replace(/^#/, '');
|
|
3
|
+
if (col.length === 3) col = col.split('').map(c => c + c).join('');
|
|
4
|
+
const num = parseInt(col, 16);
|
|
5
|
+
const r = Math.max(0, (num >> 16) - Math.round(2.55 * amt));
|
|
6
|
+
const g = Math.max(0, ((num >> 8) & 0x00FF) - Math.round(2.55 * amt));
|
|
7
|
+
const b = Math.max(0, (num & 0x0000FF) - Math.round(2.55 * amt));
|
|
8
|
+
return `#${(0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
9
|
+
};
|
|
10
|
+
%> <% const getFacetSize = n => { const l=n?.length||0; return l<=4?"11px":l===5?"8px":l>=6?"7px":"10px"; }; %> <% const startTime = DateTime.fromSeconds(match.startDateTime).toFormat("yyyy-MM-dd HH:mm:ss").slice(2); %> <% const regionName = $t("dota2tracker.template.regions." + match.regionId); %> <% const modeName = ($t("dota2tracker.template.lobby_types." + match.lobbyType) || match.lobbyType) + "/" + ($t("dota2tracker.template.game_modes." + match.gameMode) || match.gameMode); %> <% const statusClass = match.odParsed ? "opendota" : (match.parsedDateTime ? "success" : "fail"); %> <% const rankStyle = (match.odParsed && match.lobbyType !== "RANKED") ? "filter:grayscale(1)" : ""; %> <nav><div class="match_id"><p><%= $t("dota2tracker.template.match_id_").slice(0, -1) %> <%- match.id %></p><p class="<%= statusClass %>"></p></div><div class="start_time"><p><%= $t("dota2tracker.template.start_time_").slice(0, -1) %></p><p><%- startTime %></p></div><div class="duration"><p><%= $t("dota2tracker.template.duration_").slice(0, -1) %></p><p><%- match.durationTime %></p></div><div class="region"><p><%= $t("dota2tracker.template.region_").slice(0, -1) %></p><p><%- regionName %></p></div><div class="mode"><p><%= $t("dota2tracker.template.game_mode_").slice(0, -1) %></p><p><%- modeName %></p></div><div class="rank" <%- `style="${rankStyle}"` %>><img src="<%- getImageUrl('medal_' + (match.rank?.toString().split('')[0] ?? '0')) %>" alt=""/> <img style="z-index:1" src="<%- getImageUrl('star_' + (match.rank?.toString().split('')[1] ?? '0')) %>" alt=""/></div></nav><section class="match_result"><span class="kills radiant"><%- match.radiant.killsCount %></span><span class="win <%- match.didRadiantWin ? 'radiant' : 'dire' %>"></span> <span class="kills dire"><%- match.dire.killsCount %></span></section><section class="players"> <% ['radiant', 'dire'].forEach(team => { %> <% const isRadiant = team === 'radiant'; %> <% const teamData = match[team]; %> <% const orderStyle = `order: ${isRadiant ? 0 : 50}`; %> <% const teamName = isRadiant ? ['Radiant', '天辉'] : ['Dire', '夜魇']; %> <% const showWin = (isRadiant === match.didRadiantWin); %> <section class="panel <%= team %>" <%- `style="${orderStyle}"` %>><img src="<%- getImageUrl('logo_' + team) %>"><p><%- teamName.join('<br>') %></p> <% if (showWin) { %> <p class="win"><%= $t('dota2tracker.template.won') %></p> <% } else { %> <p></p> <% } %> <p class="data"><%= $t('dota2tracker.template.kill') %><br><%= teamData.killsCount %></p><p class="data"><%= $t('dota2tracker.template.total_damage') %><br><%= teamData.heroDamage %></p><p class="data"><%= $t('dota2tracker.template.total_gold') %><br><%= teamData.networth %></p><p class="data"><%= $t('dota2tracker.template.total_experience') %><br><%= teamData.experience %></p></section> <% }); %> <% match.players.forEach(p => { %> <% const isBear = p.hero.id == 80; %> <% const orderStyle = `order: ${p.team === 'radiant' ? 1 : 100}`; %> <% const partyClass = p.partyId != null ? ' party_' + match.party[p.partyId] : ''; %> <% const facetColor = p.facet?.color ?? 'Black'; %> <% const facetStyle = `font-size: ${getFacetSize(p.facet?.displayName)}`; %> <% const facetName = p.facet?.displayName ?? '?'; %> <% const teamTotalDmg = match[p.team].heroDamage || 1; %> <% const teamTotalTaken = match[p.team].damageReceived || 1; %> <% const dmgPct = (p.heroDamage / teamTotalDmg * 100).toFixed(2); %> <% const takenPct = (p.damageReceived / teamTotalTaken * 100).toFixed(2); %> <% const kdaVal = ((p.kills + p.assists) / (p.deaths || 1)).toFixed(2); %> <% const kcVal = (p.killContribution * 100).toFixed(2); %> <% const stunVal = ((p.stats.heroDamageReport?.dealtTotal.stunDuration ?? 0) / 100).toFixed(2); %> <% const pBuffs = p.stats?.matchPlayerBuffEvent || []; %> <% const hasAghs = p.items.some(i => i?.id == 108) || p.backpacks.some(i => i?.id == 108) || pBuffs.some(b => b.itemId == 108); %> <% const hasShard = pBuffs.some(b => b.itemId == 609); %> <div class="player <%= p.team %><%= isBear ? ' bear' : '' %>" <%- `style="${orderStyle}"` %>><div class="hero_avatar row-1<%= partyClass %>"><img src="<%- getImageUrl(p.hero.shortName, ImageType.Heroes) %>"/><p class="level"><%= p.level %></p><p class="party_line"></p><p class="party_mark"></p></div><div class="facet <%= facetColor %>"> <% if (p.facet) { %> <img src="<%- getImageUrl(p.facet.icon, ImageType.IconsFacets) %>"> <% } %> <span <%- `style="${facetStyle}"` %>><p><%= facetName %></p></span></div><div class="rank"><img src="<%- getImageUrl('medal_' + (p.rank.inTop100 ?? p.rank.medal)) %>" class="medal"/> <img src="<%- getImageUrl('star_' + p.rank.star) %>" class="stars"/><p><%= p.steamAccount.seasonLeaderboardRank ?? '' %></p></div><div class="titles"> <% p.titles.forEach(item => { %> <% const [title, color] = $t(item).split('-'); %> <span <%- `style="color: ${darken(color, 25)};"` %>><%= title %></span> <% }); %> </div><div class="player_name row-1"><span class="rank">[<%= $t('dota2tracker.template.ranks.' + p.rank.medal) %><%= p.rank.star || '' %>]</span> <span class="name"><%= p.steamAccount.name %></span></div><p class="pick"> <%- p.isRandom ? $t('dota2tracker.template.random') : $t('dota2tracker.template.pick_order', [p.order == null ? '?' : p.order + 1]) %> <%= $t('dota2tracker.template.position_' + p.position?.slice(-1)) ?? '' %> </p><p class="networth"><span class="gold"><%= p.formattedNetworth %></span>(<%= (p.heroDamage / p.networth)?.toFixed(2) %>)</p><p class="hero_damage"> <%= $t('dota2tracker.template.hero_damage_') %><%= p.heroDamage %> (<%= dmgPct %>%)</p><p class="damage_received"> <%= $t('dota2tracker.template.damage_received_') %><%= p.damageReceived %> (<%= takenPct %>%)</p><p class="tower_damage"> <%= $t('dota2tracker.template.building_damage_') %><%= p.towerDamage %> </p><p class="kda row-1"> <%= p.kills %>/<%= p.deaths %>/<%= p.assists %> (<%= kdaVal %>)</p><p class="kill_contribution"> <%= $t('dota2tracker.template.kill_contribution_') %><%= kcVal %>%</p><p class="stun_duration"> <%= $t('dota2tracker.template.crowd_control_duration_') %> <%= stunVal %>s</p><p class="heal"> <%= $t('dota2tracker.template.heal_') %><%= p.heroHealing %> </p><div class="items row-1"><div class="normal"> <% p.items.forEach(item => { %> <div class="item<%= item?.isRecipe ? ' recipe' : '' %>" data-id="<%= item?.id ?? 0 %>"><img src="<%- item ? getImageUrl(item.name, ImageType.Items) : '' %>" alt=""/> <% if (item) { %> <p class="time"><%= item.time %></p> <% } %> </div> <% }); %> </div> <% if (!isBear) { %> <div class="backpack"> <% p.backpacks.forEach(item => { %> <div class="item<%= item?.isRecipe ? ' recipe' : '' %>"><img src="<%- item ? getImageUrl(item.name, ImageType.Items) : '' %>" alt=""/></div> <% }); %> </div> <% } else { %> <img class="bear_icon" src="<%- getImageUrl('lone_druid_spirit_bear', ImageType.Abilities) %>" alt=""><div class="bear"> <% p.unitItems.forEach(item => { %> <div class="item<%= item?.isRecipe ? ' recipe' : '' %>" data-id="<%= item?.id ?? 0 %>"><img src="<%- item ? getImageUrl(item.name, ImageType.Items) : '' %>" alt=""/> <% if (item) { %> <p class="time"><%= item.time %></p> <% } %> </div> <% }); %> </div><div class="neutral_item" <%- `style="background-image: url(${getImageUrl(dotaconstants.item_ids[p.additionalUnit.neutral0Id], ImageType.Items)})"` %>></div> <% } %> </div><div class="neutral_item row-1" <%- `style="background-image: url(${getImageUrl(dotaconstants.item_ids[p.neutral0Id], ImageType.Items)})"` %>></div><div class="ahgs row-1"><img src="<%- getImageUrl('scepter_' + (hasAghs ? 1 : 0)) %>" alt=""/> <img src="<%- getImageUrl('shard_' + (hasShard ? 1 : 0)) %>" alt=""/></div></div> <% }); %> </section>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<% const match = data; %> <% const svgWidth = 400; %> <% const svgHeight = 225; %> <% const padding = 50; %> <% const chartH = svgHeight - padding * 2; %> <% const chartW = svgWidth - padding * 2; %> <% const mainData = match.radiantNetworthLeads ?? []; %> <% const secData = match.radiantExperienceLeads ?? []; %> <% const maxVal = Math.max(...mainData.map(Math.abs), ...secData.map(Math.abs)); %> <% const duration = match.durationSeconds; %> <% const xScale = chartW / (duration - 1); %> <% const yScale = chartH / (maxVal * 2); %> <% const formatNum = n => Math.floor(n).toLocaleString(); %> <% const getX = i => padding + (i * 60 > duration ? ((((i - 1) * 60) + (duration % 60)) * xScale) : (i * xScale * 60)); %> <% const getY = (val, scale) => svgHeight / 2 - val * scale; %> <% const secPoints = secData.map((v, i) => `${getX(i)},${getY(v, yScale)}`).join(' '); %> <% const secPoly = `${secPoints} ${svgWidth - padding},${svgHeight / 2} ${padding},${svgHeight / 2}`; %> <% const mainPointsRaw = mainData.map((v, i) => ({ val: v, x: getX(i), y: getY(v, yScale) })); %> <% const mainPolyPoints = mainPointsRaw.map(p => `${p.x},${p.y}`).join(' '); %> <% let upperPts = [], lowerPts = []; %> <% for (let i = 0; i < mainPointsRaw.length; i++) { %> <% const curr = mainPointsRaw[i], next = mainPointsRaw[i + 1]; %> <% if (curr.val >= 0) upperPts.push(`${curr.x},${curr.y}`); else lowerPts.push(`${curr.x},${curr.y}`); %> <% if (next && ((curr.val >= 0 && next.val < 0) || (curr.val < 0 && next.val >= 0))) { %> <% const t = Math.abs(curr.val) / (Math.abs(curr.val) + Math.abs(next.val)); %> <% const crossX = curr.x + t * (next.x - curr.x); %> <% const crossY = svgHeight / 2; %> <% upperPts.push(`${crossX},${crossY}`); lowerPts.push(`${crossX},${crossY}`); %> <% } %> <% } %> <% upperPts.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`); %> <% lowerPts.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`); %> <% const wrPoints = match.winRates ? [match.winRates[0]].concat(match.winRates).map((v, i) => `${getX(i)},${svgHeight - padding - v * chartH}`).join(' ') : ''; %> <% const timeLabels = []; %> <% for (let i = 0; i < mainData.length - 1; i += 10) { %> <% timeLabels.push({ x: getX(i), text: `${i.toString().padStart(2, '0')}:00`, showText: ((duration / 60) - i) >= 5 }); %> <% } %> <% const valLabels = [maxVal, maxVal / 2, 0, -maxVal / 2, -maxVal]; %> <% const pNwData = match.players.map(p => p.stats.networthPerMinute.concat(p.networth)); %> <% const maxNw = Math.max(...pNwData.flat().map(Math.abs)); %> <% const nwYScale = chartH / maxNw; %> <% const nwLabels = [maxNw, maxNw * 0.75, maxNw * 0.5, maxNw * 0.25, 0]; %> <% const pColors = dotaconstants.player_colors; %> <div id="charts"><svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg"><text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold"><%= $t("dota2tracker.template.situation") %></text><image x="<%= padding %>" y="<%= padding %>" width="20" height="20" href="<%= getImageUrl("logo_radiant") %>"/><image x="<%= padding %>" y="<%= svgHeight - padding * 1.4 %>" width="20" height="20" href="<%= getImageUrl("logo_dire") %>"/><text x="<%= padding + 20 %>" y="<%= padding + 10 %>" dominant-baseline="middle" fill="#3c9028"><%= $t("dota2tracker.template.radiant") %></text><text x="<%= padding + 20 %>" y="<%= svgHeight - padding * 1.4 + 10 %>" dominant-baseline="middle" fill="#9c3628"><%= $t("dota2tracker.template.dire") %></text> <% for (let i = 0; i < 5; i++) { %> <% const y = padding + i * (chartH / 4); %> <line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1"/><text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNum(valLabels[i]) %></text> <% } %> <% timeLabels.forEach(lbl => { %> <line x1="<%= lbl.x %>" y1="<%= padding %>" x2="<%= lbl.x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1"/> <% if (lbl.showText) { %> <text x="<%= lbl.x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= lbl.text %></text> <% } %> <% }); %> <line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1"/><text x="<%= svgWidth - padding %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= `${Math.floor(duration / 60)}:${(duration % 60).toString().padStart(2, '0')}` %></text> <% if (wrPoints) { %> <polyline points="<%= wrPoints %>" fill="none" stroke="lightgreen" stroke-width="3"/> <% } %> <polyline points="<%= secPoints %>" fill="none" stroke="gray" stroke-width="1"/><polygon points="<%= secPoly %>" fill="rgba(128, 128, 128, 0.3)"/><polyline points="<%= mainPolyPoints %>" fill="none" stroke="rgba(100, 100, 100, 0.3)" stroke-width="2"/><polygon points="<%= upperPts.join(' ') %>" fill="rgba(0, 255, 0, 0.3)"/><polygon points="<%= lowerPts.join(' ') %>" fill="rgba(255, 0, 0, 0.3)"/><g transform="translate(<%= padding %>, <%= svgHeight - 20 %>)"><g transform="translate(0, 0)"><path d="M0,5 Q5,0 10,5 Z" fill="rgba(0, 255, 0, 0.5)"/><path d="M10,5 Q15,10 20,5 Z" fill="rgba(255, 0, 0, 0.5)"/><path d="M0,5 Q5,0 10,5 T20,5" fill="none" stroke="rgba(100, 100, 100, 0.3)" stroke-width="1"/><text x="30" y="7" font-size="12" <%- match.odParsed ? 'fill="#ccc"' : '' %>> <%= match.odParsed ? $t("dota2tracker.template.opendota.gold_t") : $t("dota2tracker.template.networth") %> </text></g><g transform="translate(120, 0)"><path d="M0,5 Q5,0 10,5 T20,5" fill="none" stroke="gray" stroke-width="1"/><text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.experience") %></text></g><g transform="translate(220, 0)"><path d="M0,5 Q5,0 10,5 T20,5" fill="none" stroke="lightgreen" stroke-width="3"/><text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.winrate") %></text></g></g></svg> <svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg"> <% if (match.odParsed) { %> <text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold" fill="#ccc"><%= $t("dota2tracker.template.opendota.networth_unavailable") %></text><text x="50%" y="<%= svgHeight / 2 + 10 %>" text-anchor="middle" font-size="12" fill="#ccc"><%= $t("dota2tracker.template.opendota.networth_unavailable_reason") %></text> <% } else { %> <text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold"><%= $t("dota2tracker.template.networth") %></text> <% for (let i = 0; i < 5; i++) { %> <% const y = padding + i * (chartH / 4); %> <line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1"/><text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNum(nwLabels[i]) %></text> <% } %> <% timeLabels.forEach(lbl => { %> <line x1="<%= lbl.x %>" y1="<%= padding %>" x2="<%= lbl.x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1"/> <% if (lbl.showText) { %> <text x="<%= lbl.x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= lbl.text %></text> <% } %> <% }); %> <line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1"/><text x="<%= svgWidth - padding %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= `${Math.floor(duration / 60)}:${(duration % 60).toString().padStart(2, '0')}` %></text> <% pNwData.forEach((data, i) => { %> <% const color = pColors[match.players[i].playerSlot] || '#000'; %> <% const pts = data.map((v, idx) => `${getX(idx)},${svgHeight - padding - v * nwYScale}`).join(' '); %> <polyline points="<%= pts %>" fill="none" stroke="<%= color %>" stroke-width="2"/> <% }); %> <g transform="translate(<%= (svgWidth - (match.players.length * 30)) / 2 %>, <%= svgHeight - 27 %>)"> <% [...match.players].sort((a, b) => a.networth - b.networth).forEach((p, i) => { %> <% const color = pColors[p.playerSlot] || '#000'; %> <rect x="<%= i * 30 %>" y="0" width="24" height="24" rx="6" ry="6" fill="#f0f0f0" stroke="<%= color %>" stroke-width="3"/><image x="<%= i * 30 %>" y="0" width="24" height="24" href="<%= getImageUrl(p.hero.shortName, ImageType.HeroIcons) %>" clip-path="inset(0 round 6)"/> <% }); %> </g> <% } %> </svg></div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
html{overflow:visible}body{display:flex;flex-direction:column;overflow:visible}#regular{width:800px}#extra{font-size:10px;width:100%;>.tip{width:100%;line-height:4;text-align:center;color:#ccc}#charts{display:flex;>*{width:50%}}.container{height:300px;display:flex;.lane_outcome{position:relative;display:flex;flex-direction:column;width:500px;margin-right:20px;>.title{width:100%;text-align:center;padding:0;margin:0;line-height:50px}.panel{display:flex;flex-direction:column;height:100%;justify-content:space-evenly;.lane{display:flex;flex-direction:column;border-radius:10px;border:#ccc solid 1px;padding:2px;>.title{width:100%;text-align:center;height:32px;>p:nth-child(1){font-size:12px}>p:nth-child(2){font-size:14px}}img.hero{width:24px;height:24px}.kda{height:100%;line-height:32px;text-align:center;font-size:10px}.graph{height:32px;display:flex;align-items:center;justify-content:center;svg{shape-rendering:crispEdges}text{dominant-baseline:middle;white-space:nowrap}}}.details{display:grid;grid-template-columns:24px 32px 44px auto 44px 32px 24px;grid-template-rows:1fr;gap:1px;font-size:12px;height:32px;line-height:32px;align-items:center}}.subtitle{position:absolute;inset:36px 0 0;text-align:center;color:#ccc;font-size:12px;margin:0;z-index:1}}.map{box-sizing:border-box;width:300px;padding:25px;font-size:8px;pointer-events:none;text-shadow:0px 1px 1px rgba(0,0,0,.8);use{&.radiant{color:#95cc4b}&.dire{color:#ca4633}&.dead{color:#555}}}}}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<% const match = data; %> <% const networthBarLength = 240; %> <% const getLevelInfo = (xp) => {
|
|
2
|
+
const XP = dotaconstants.xp_level;
|
|
3
|
+
let lvl = XP.findIndex(l => xp < l) - 1;
|
|
4
|
+
if (lvl < 0) lvl = XP.length - 1;
|
|
5
|
+
const curr = xp - XP[lvl];
|
|
6
|
+
const next = XP[lvl + 1] - XP[lvl];
|
|
7
|
+
const pct = curr / next;
|
|
8
|
+
const r = 12, size = 32, center = 16;
|
|
9
|
+
const circum = 2 * Math.PI * r;
|
|
10
|
+
const prog = circum * pct;
|
|
11
|
+
const gap = circum - prog;
|
|
12
|
+
return { lvl, prog, gap, r, size, center };
|
|
13
|
+
};
|
|
14
|
+
%> <% const getLaneData = (posRadiant, posDire) => {
|
|
15
|
+
// 筛选逻辑:根据位置和阵营精确匹配
|
|
16
|
+
// posRadiant/posDire 是数字数组,如 [3] 或 [3, 4]
|
|
17
|
+
const rivals = match.players.filter(p => {
|
|
18
|
+
const pos = parseInt(p.position.slice(-1)); // 提取位置数字
|
|
19
|
+
const isRad = p.isRadiant;
|
|
20
|
+
return (isRad && posRadiant.includes(pos)) || (!isRad && posDire.includes(pos));
|
|
21
|
+
}).sort((a, b) => b.isRadiant - a.isRadiant); // 排序确保:天辉(1)在前,夜魇(0)在后
|
|
22
|
+
|
|
23
|
+
// 如果凑不齐两个人(比如有人掉线没数据,或者本来就是1v0),则不渲染这一组
|
|
24
|
+
if (rivals.length < 2) return null;
|
|
25
|
+
|
|
26
|
+
const p1 = rivals[0], p2 = rivals[1];
|
|
27
|
+
const getStat = (p, type) => {
|
|
28
|
+
if (match.odParsed) return "-";
|
|
29
|
+
return p.stats[type + "Events"]?.filter(e => e.time <= 600).length || 0;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 经济计算 (10分钟快照)
|
|
33
|
+
const getGold10 = p => p.stats.networthPerMinute.at(Math.min(10, p.stats.networthPerMinute.length - 1));
|
|
34
|
+
const g1 = getGold10(p1), g2 = getGold10(p2);
|
|
35
|
+
const totalG = g1 + g2, split = g1 / (totalG || 1);
|
|
36
|
+
const diff = Math.abs(g2 - g1);
|
|
37
|
+
const diffColor = g1 > g2 ? "#3c9028" : "#9c3628";
|
|
38
|
+
|
|
39
|
+
// 经验计算
|
|
40
|
+
const getXp10 = p => p.stats.experiencePerMinute.slice(0, Math.min(11, p.stats.experiencePerMinute.length - 1)).reduce((a, b) => a + b, 0);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
p1: { ...p1, k: getStat(p1, 'kill'), d: getStat(p1, 'death'), a: getStat(p1, 'assist'), lvl: getLevelInfo(getXp10(p1)) },
|
|
44
|
+
p2: { ...p2, k: getStat(p2, 'kill'), d: getStat(p2, 'death'), a: getStat(p2, 'assist'), lvl: getLevelInfo(getXp10(p2)) },
|
|
45
|
+
gold: { g1, g2, split, diff, diffColor, isOd: match.odParsed }
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
%> <div class="lane_outcome"> <% if (match.odParsed) { %> <p class="subtitle"><%= $t("dota2tracker.template.opendota.lane_outcome_tip") %></p> <% } %> <div class="panel"> <% const lanes = [
|
|
49
|
+
// 上路: (天辉3 vs 夜魇1) 和 (天辉4 vs 夜魇5)
|
|
50
|
+
["lane_top", match.topLaneOutcome, [ [[3], [1]], [[4], [5]] ]],
|
|
51
|
+
// 中路: (天辉2 vs 夜魇2)
|
|
52
|
+
["lane_mid", match.midLaneOutcome, [ [[2], [2]] ]],
|
|
53
|
+
// 下路: (天辉1 vs 夜魇3) 和 (天辉5 vs 夜魇4)
|
|
54
|
+
["lane_bottom", match.bottomLaneOutcome, [ [[1], [3]], [[5], [4]] ]]
|
|
55
|
+
];
|
|
56
|
+
%> <% lanes.forEach(([labelKey, outcome, pairs]) => { %> <div class="lane"> <% pairs.forEach(([radPos, direPos]) => { %> <% const laneData = getLaneData(radPos, direPos); %> <% if (laneData) { %> <div class="details"><img src="<%= getImageUrl(laneData.p1.hero.shortName, ImageType.HeroIcons) %>" class="hero radiant"/> <svg width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="16" r="12" fill="none" stroke="#eee" stroke-width="2"/><circle cx="16" cy="16" r="12" fill="none" stroke="#ffd700" stroke-width="2" stroke-dasharray="<%= laneData.p1.lvl.prog %> <%= laneData.p1.lvl.gap %>" stroke-linecap="round" transform="rotate(-90 16 16)"/><text x="16" y="16" text-anchor="middle" dominant-baseline="middle" fill="#333" font-size="10" font-weight="bold"><%= laneData.p1.lvl.lvl %></text></svg><p class="kda"><%= laneData.p1.k %>/<%= laneData.p1.d %>/<%= laneData.p1.a %></p><div class="graph"><svg width="<%= networthBarLength %>" height="32" viewBox="0 0 <%= networthBarLength %> 32"><g transform="translate(0,0)"><rect y="9" width="<%= networthBarLength %>" height="14" fill="#9c3628"/><rect y="9" width="<%= networthBarLength * laneData.gold.split %>" height="14" fill="#3c9028"/><line x1="<%= networthBarLength * laneData.gold.split %>" y1="9" x2="<%= networthBarLength * laneData.gold.split %>" y2="25" stroke="#fff" stroke-width="1"/><text x="5" y="50%" dominant-baseline="middle" fill="#fff" font-size="10" text-anchor="start"> <%= laneData.gold.isOd ? "~" : "" %><%= laneData.gold.g1 %> </text><text x="<%= networthBarLength - 5 %>" y="50%" dominant-baseline="middle" fill="#fff" font-size="10" text-anchor="end"> <%= laneData.gold.isOd ? "~" : "" %><%= laneData.gold.g2 %> </text><text x="<%= networthBarLength * laneData.gold.split %>" y="5px" dominant-baseline="middle" fill="<%= laneData.gold.diffColor %>" font-size="10" text-anchor="middle">+<%= laneData.gold.diff %> </text></g></svg></div><p class="kda"><%= laneData.p2.k %>/<%= laneData.p2.d %>/<%= laneData.p2.a %></p><svg width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="16" r="12" fill="none" stroke="#eee" stroke-width="2"/><circle cx="16" cy="16" r="12" fill="none" stroke="#ffd700" stroke-width="2" stroke-dasharray="<%= laneData.p2.lvl.prog %> <%= laneData.p2.lvl.gap %>" stroke-linecap="round" transform="rotate(-90 16 16)"/><text x="16" y="16" text-anchor="middle" dominant-baseline="middle" fill="#333" font-size="10" font-weight="bold"><%= laneData.p2.lvl.lvl %></text></svg> <img src="<%= getImageUrl(laneData.p2.hero.shortName, ImageType.HeroIcons) %>" class="hero dire"/></div> <% } %> <% }); %> </div> <% }); %> </div></div>
|