diff options
| author | mat <github@matdoes.dev> | 2022-12-15 18:22:09 -0600 |
|---|---|---|
| committer | mat <github@matdoes.dev> | 2022-12-15 18:22:09 -0600 |
| commit | 054fe6ddee1ecbbf336c1ec9fbce08ebd0a54ebd (patch) | |
| tree | ebcbebfe4948cbcf72f8d0c644a8f37714867754 /src/routes | |
| parent | 925ad2bfff17f8d9381731fb0050936dcd7e0a72 (diff) | |
| download | skyblock-stats-054fe6ddee1ecbbf336c1ec9fbce08ebd0a54ebd.tar.gz skyblock-stats-054fe6ddee1ecbbf336c1ec9fbce08ebd0a54ebd.tar.bz2 skyblock-stats-054fe6ddee1ecbbf336c1ec9fbce08ebd0a54ebd.zip | |
fix mayor skin + move sections dir
Diffstat (limited to 'src/routes')
22 files changed, 1358 insertions, 20 deletions
diff --git a/src/routes/election/+page.svelte b/src/routes/election/+page.svelte index dc4719e..54ac668 100644 --- a/src/routes/election/+page.svelte +++ b/src/routes/election/+page.svelte @@ -4,7 +4,7 @@ import { colorCodes, formattingCodeToHtml, millisecondsToTime, skyblockTime } from '$lib/utils' import type { ElectionData } from '$lib/APITypes' import { onDestroy, onMount } from 'svelte' - import MayorSkin from '$lib/MayorSkin.svelte' + import MayorSkin from '../../lib/MayorSkin.svelte' import { invalidate } from '$app/navigation' import { browser } from '$app/environment' diff --git a/src/routes/player/[player]/[profile]/+page.svelte b/src/routes/player/[player]/[profile]/+page.svelte index 247619d..8a3adbb 100644 --- a/src/routes/player/[player]/[profile]/+page.svelte +++ b/src/routes/player/[player]/[profile]/+page.svelte @@ -1,31 +1,31 @@ <script lang="ts"> import { inventoryIconMap, skyblockItemToUrl } from '$lib/minecraft/inventory' - import FarmingContests from '$lib/sections/FarmingContests.svelte' - import Leaderboards from '$lib/sections/Leaderboards.svelte' - import Achievements from '$lib/sections/Achievements.svelte' - import Inventories from '$lib/sections/Inventories.svelte' - import Collections from '$lib/sections/Collections.svelte' + import FarmingContests from './sections/FarmingContests.svelte' + import Leaderboards from './sections/Leaderboards.svelte' + import Achievements from './sections/Achievements.svelte' + import Inventories from './sections/Inventories.svelte' + import Collections from './sections/Collections.svelte' import { chooseDefaultBackground } from '$lib/backgrounds' import BackgroundImage from '$lib/BackgroundImage.svelte' import type { CleanMemberProfile } from '$lib/APITypes' import Username from '$lib/minecraft/Username.svelte' - import StatList from '$lib/sections/StatList.svelte' - import Auctions from '$lib/sections/Auctions.svelte' - import Infobox from '$lib/sections/Infobox.svelte' - import Minions from '$lib/sections/Minions.svelte' - import Essence from '$lib/sections/Essence.svelte' - import Slayers from '$lib/sections/Slayers.svelte' + import StatList from './sections/StatList.svelte' + import Auctions from './sections/Auctions.svelte' + import Infobox from './sections/Infobox.svelte' + import Minions from './sections/Minions.svelte' + import Essence from './sections/Essence.svelte' + import Slayers from './sections/Slayers.svelte' import type { MatcherFile } from 'skyblock-assets' - import Claimed from '$lib/sections/Claimed.svelte' + import Claimed from './sections/Claimed.svelte' import Collapsible from '$lib/Collapsible.svelte' - import Skills from '$lib/sections/Skills.svelte' + import Skills from './sections/Skills.svelte' import { generateInfobox } from '$lib/profile' - import Zones from '$lib/sections/Zones.svelte' - import Armor from '$lib/sections/Armor.svelte' - import Harp from '$lib/sections/Harp.svelte' - import Pets from '$lib/sections/Pets.svelte' - import Coop from '$lib/sections/Coop.svelte' - import Bank from '$lib/sections/Bank.svelte' + import Zones from './sections/Zones.svelte' + import Armor from './sections/Armor.svelte' + import Harp from './sections/Harp.svelte' + import Pets from './sections/Pets.svelte' + import Coop from './sections/Coop.svelte' + import Bank from './sections/Bank.svelte' import type { PageData } from './$types' import Header from '$lib/Header.svelte' import Emoji from '$lib/Emoji.svelte' diff --git a/src/routes/player/[player]/[profile]/sections/AccessoryBagUpgrades.svelte b/src/routes/player/[player]/[profile]/sections/AccessoryBagUpgrades.svelte new file mode 100644 index 0000000..74530d1 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/AccessoryBagUpgrades.svelte @@ -0,0 +1,100 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Emoji from '$lib/Emoji.svelte' + import ListItemWithIcon from '$lib/ListItemWithIcon.svelte' + import { cleanId, skyblockTime } from '$lib/utils' + + export let data: CleanMemberProfile + + $: bagData = data.member.accessoryBagUpgrades +</script> + +<span class="accessory-bag-upgrades"> + <h3>Upgrades</h3> + <div class="accessory-bag-info-text"> + <p>Purchased: <b>{bagData.upgrades.purchased}</b></p> + <p>Coins spent: <b>{bagData.upgrades.coinsSpent.toLocaleString()}</b></p> + <p>Extra slots: <b>{bagData.upgrades.extraSlots}</b></p> + </div> + + <h3>Powers</h3> + {#if bagData.powers.selected} + <p class="accessory-bag-info-text"> + Selected: <b>{cleanId(bagData.powers.selected)}</b> + </p> + {/if} + <ul> + {#each bagData.powers.list as power} + <li> + {#if bagData.powers.selected === power} + <b>{cleanId(power)}</b> + {:else} + {cleanId(power)} + {/if} + </li> + {/each} + </ul> + + <div class="tuning-templates"> + {#each bagData.tuningTemplates as template, template_index} + <div class="tuning-template"> + <h3>Template #{template_index + 1}</h3> + <div class="accessory-bag-info-text"> + {#each Object.entries(template) as [statName, statValue]} + <p>{cleanId(statName)}: <b>{statValue}</b></p> + {/each} + </div> + </div> + {/each} + </div> +</span> + +<style> + p, + ul { + margin: 0; + } + ul { + padding-left: 1.5em; + } + h3 { + margin: 0.5em 0 0 0; + } + + .accessory-bag-info-text { + color: var(--theme-darker-text); + } + .accessory-bag-info-text b { + color: var(--theme-main-text); + } + + .tuning-templates { + display: flex; + flex-wrap: wrap; + max-width: 40rem; + column-gap: 0.5rem; + row-gap: 0.5rem; + margin-top: 1rem; + } + .tuning-template { + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(0, 0, 0, 0.1); + padding: 0.75em; + border-radius: 1em; + } + .tuning-template h3 { + margin: 0; + } + + .accessory-bag-upgrades { + /* width: 5rem; */ + } + @media only screen and (min-width: 1160px) { + .accessory-bag-upgrades { + display: inline-grid; + position: relative; + top: -1.5em; + left: 0.5em; + } + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Achievements.svelte b/src/routes/player/[player]/[profile]/sections/Achievements.svelte new file mode 100644 index 0000000..92ba468 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Achievements.svelte @@ -0,0 +1,87 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Tooltip from '$lib/Tooltip.svelte' + export let data: CleanMemberProfile +</script> + +{#if data.member.achievements} + <h3> + Tiered + <span class="achievement-count"> + ({data.member.achievements.tiered.filter(a => a.amount).length}/{data.member.achievements + .tiered.length}) + </span> + </h3> + <ul> + {#each data.member.achievements.tiered as achievement} + <li class="achievement"> + <Tooltip> + <span slot="tooltip"> + {achievement.description} + </span> + + <span class:achievement-locked={achievement.amount === 0}> + {achievement.name}: {#if achievement.next} + <b class="achievement-amount">{achievement.amount}</b>/{achievement.next} + {:else} + <span class="achievement-amount achievement-amount-maxed">{achievement.amount}</span> + {/if} + </span> + </Tooltip> + </li> + {/each} + </ul> + + <h3> + Challenge + <span class="achievement-count"> + ({data.member.achievements.challenge.filter(a => a.unlocked).length}/{data.member.achievements + .challenge.length}) + </span> + </h3> + <ul> + {#each data.member.achievements.challenge as achievement} + <li class="achievement"> + <Tooltip> + <span slot="tooltip"> + {achievement.description} + </span> + + {#if achievement.unlocked} + <span> + {achievement.name} + </span> + {:else} + <span class="achievement-locked"> + {achievement.name} + </span> + {/if} + </Tooltip> + </li> + {/each} + </ul> +{/if} + +<style> + ul { + margin: 0; + padding-left: 1em; + } + + .achievement-locked { + opacity: 0.5; + } + + .achievement-count { + color: var(--theme-darker-text); + font-weight: normal; + } + + .achievement-amount { + opacity: 0.9; + } + .achievement-amount-maxed { + color: #0e0; + opacity: 1; + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Armor.svelte b/src/routes/player/[player]/[profile]/sections/Armor.svelte new file mode 100644 index 0000000..285a898 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Armor.svelte @@ -0,0 +1,14 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Inventory from '$lib/minecraft/Inventory.svelte' + import type { MatcherFile } from 'skyblock-assets' + + export let data: CleanMemberProfile + export let pack: MatcherFile +</script> + +{#if data.member.inventories} + <span> + <Inventory items={data.member.inventories.armor} name="armor" groupLimit={1} {pack} /> + </span> +{/if} diff --git a/src/routes/player/[player]/[profile]/sections/Auctions.svelte b/src/routes/player/[player]/[profile]/sections/Auctions.svelte new file mode 100644 index 0000000..50d47b6 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Auctions.svelte @@ -0,0 +1,109 @@ +<!-- + @component + + A list of the player's past auctions, and their auction stats. +--> +<script lang="ts"> + import { cleanId, millisecondsToTime } from '$lib/utils' + import type { CleanMemberProfile, StatItem } from '$lib/APITypes' + import { fetchApi } from '$lib/api' + import type { MatcherFile } from 'skyblock-assets' + import Auction from '$lib/Auction.svelte' + + export let data: CleanMemberProfile + export let stats: StatItem[] + export let pack: MatcherFile + + let onlyThisProfile = true + + let auctions: any[] = [] + let loading = true + + let page = 0 + let totalPages: number | undefined = undefined + + async function updateAuctions() { + loading = true + const thisPage = page + page += 1 + const auctionsResponse = await fetchApi( + `playerauctions/${data.member.uuid}?page=${thisPage}`, + fetch + ).then(r => r.json()) + loading = false + auctions = [...auctions, ...auctionsResponse.auctions] + totalPages = auctionsResponse.pages + } + + updateAuctions() +</script> + +<div class="auction-stats-and-list-container"> + <ul> + {#each stats.sort((a, b) => b.value - a.value) as stat} + <li> + <span class="stat-name">{cleanId(stat.categorizedName)}:</span> + <span class="stat-value"> + {#if stat.unit === 'time'} + {millisecondsToTime(stat.value)} + {:else} + {stat.value.toLocaleString()} + {/if} + </span> + </li> + {/each} + </ul> + + <div class="player-auctions-list-container"> + {#if loading || auctions.length > 0} + <h3>Auctions sold</h3> + {/if} + {#if auctions.length > 0} + <div class="player-auctions-list"> + {#each auctions as auction} + {#if !onlyThisProfile || auction.sellerProfileUuid == data.profile.uuid} + <Auction {auction} {pack} /> + {/if} + {/each} + </div> + {#if !loading && page != totalPages} + <button on:click={updateAuctions}>Show more</button> + {/if} + {/if} + {#if loading} + Loading... + {/if} + </div> +</div> + +<style> + li { + position: relative; + } + ul { + padding-left: 1em; + margin-top: 0.5em; + width: max-content; + } + .auction-stats-and-list-container { + display: grid; + grid-template-columns: 1fr auto; + } + + @media (max-width: 600px) { + .auction-stats-and-list-container { + grid-template-columns: 1fr; + } + } + + .player-auctions-list { + display: flex; + flex-wrap: wrap; + column-gap: 0.5rem; + row-gap: 0.5rem; + } + .player-auctions-list-container { + margin-top: 0.5em; + margin-left: 0.5em; + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Bank.svelte b/src/routes/player/[player]/[profile]/sections/Bank.svelte new file mode 100644 index 0000000..9bd2b8f --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Bank.svelte @@ -0,0 +1,89 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Tooltip from '$lib/Tooltip.svelte' + import ConditionalLink from '$lib/ConditionalLink.svelte' + import { + colorCodeCharacter, + formattingCodeToHtml, + millisecondsToTime, + removeFormattingCode, + } from '$lib/utils' + + export let data: CleanMemberProfile +</script> + +{#if data.profile.bank} + <div class="bank-main-current-balance"> + <p> + Current bank balance: + <span class="bank-main-current-balance-value"> + <b>{data.profile.bank.balance?.toLocaleString()}</b> coins + </span> + </p> + <p> + Purse: + <span class="bank-main-current-balance-value"> + <b>{data.member.purse.toLocaleString()}</b> coins + </span> + </p> + </div> + {#each data.profile.bank.history as transaction} + <div> + <span class="transaction-player"> + <ConditionalLink + href="/player/{removeFormattingCode(transaction.name)}" + isWrapped={transaction.name.startsWith(colorCodeCharacter)} + > + {@html formattingCodeToHtml(transaction.name)} + </ConditionalLink> + </span> + <Tooltip> + <span slot="tooltip"> + New balance: <b>{transaction.total.toLocaleString()}</b> + </span> + <span + class:difference-positive={transaction.change > 0} + class:difference-negative={transaction.change < 0} + > + {transaction.change > 0 + ? '+' + transaction.change.toLocaleString() + : transaction.change.toLocaleString()} + </span> + </Tooltip> + + <span class="transaction-timeago"> + {millisecondsToTime(Date.now() - transaction.timestamp)} ago + </span> + </div> + {/each} +{/if} + +<style> + .difference-positive { + color: #0f0; + } + .difference-negative { + color: red; + } + + .transaction-timeago { + color: var(--theme-darker-text); + } + + .transaction-player { + font-family: Minecraft, 'Atkinson Hyperlegible', sans-serif; + font-size: 0.8em; + } + + .bank-main-current-balance { + margin: 0.5em 0; + color: var(--theme-darker-text); + } + + .bank-main-current-balance-value { + color: var(--theme-main-text); + } + p { + margin: 0; + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Claimed.svelte b/src/routes/player/[player]/[profile]/sections/Claimed.svelte new file mode 100644 index 0000000..8f44796 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Claimed.svelte @@ -0,0 +1,33 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import { cleanId, millisecondsToTime, toTitleCase } from '$lib/utils' + + export let data: CleanMemberProfile +</script> + +{#if data.member.claimed && data.member.claimed.length > 0} + <ul> + {#each data.member.claimed as claimed} + <li> + <b class="claimed-item-name">{toTitleCase(cleanId(claimed.id))}</b> + <span class="claimed-item-timestamp"> + {millisecondsToTime(Date.now() - claimed.timestamp)} ago + </span> + </li> + {/each} + </ul> +{/if} + +<style> + p { + margin: 0; + } + + ul { + margin: 0.5em 0; + } + + .claimed-item-timestamp { + color: var(--theme-darker-text); + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Collections.svelte b/src/routes/player/[player]/[profile]/sections/Collections.svelte new file mode 100644 index 0000000..4baa660 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Collections.svelte @@ -0,0 +1,62 @@ +<script lang="ts"> + import type { CleanMemberProfile, Collection } from '$lib/APITypes' + import { skyblockItemToUrl } from '$lib/minecraft/inventory' + import ListItemWithIcon from '$lib/ListItemWithIcon.svelte' + import type { MatcherFile } from 'skyblock-assets' + import Tooltip from '$lib/Tooltip.svelte' + import { cleanId } from '$lib/utils' + + export let data: CleanMemberProfile + export let pack: MatcherFile + + const categories: Record<string, Collection[]> = {} + if (data.member.collections) + for (const collection of data.member.collections) { + if (!categories[collection.category]) categories[collection.category] = [] + categories[collection.category].push(collection) + } +</script> + +{#if data.member.collections} + {#each Object.keys(categories).sort() as categoryName} + {@const collections = categories[categoryName]} + <h3>{cleanId(categoryName)}</h3> + <ul> + {#each collections as collection} + <ListItemWithIcon + src={skyblockItemToUrl(collection.name, pack)} + alt={cleanId(collection.name)} + > + <Tooltip> + <span slot="tooltip"> + Amount: {collection.amount.toLocaleString()} + </span> + {cleanId(collection.name)} + <span class="coll-level">{collection.level}</span> + </Tooltip> + </ListItemWithIcon> + {/each} + </ul> + {/each} +{/if} + +<style> + ul { + margin: 0; + display: flex; + flex-wrap: wrap; + width: fit-content; + /* this ensures there's at most 2 lines */ + max-width: 30em; + } + + ul > :global(li) { + width: 12em; + height: 1.5em; + text-overflow: ellipsis; + } + + h3 { + margin: 0.5em 0 0.5em 0.5em; + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Coop.svelte b/src/routes/player/[player]/[profile]/sections/Coop.svelte new file mode 100644 index 0000000..ff858f1 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Coop.svelte @@ -0,0 +1,79 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Username from '$lib/minecraft/Username.svelte' + import { millisecondsToTime } from '$lib/utils' + export let data: CleanMemberProfile + + $: isProfileCreator = data.member.coopInvitation?.invitedBy?.uuid == data.member.uuid +</script> + +{#if data.member.coopInvitation} + <div class="info-text primary-info-text"> + {#if isProfileCreator} + <p><b class="info-text-value">Created co-op</b></p> + {:else} + <p> + Invited by {#if data.member.coopInvitation.invitedBy} + <Username player={data.member.coopInvitation.invitedBy} prefix /> + {:else} + <b>Unknown player</b> + {/if} + </p> + {/if} + <p> + {isProfileCreator ? 'Began creation' : 'Invited'}: + <span class="info-text-value coop-invited-timeago"> + <b>{millisecondsToTime(Date.now() - data.member.coopInvitation.invitedTimestamp)}</b> ago + </span> + </p> + {#if data.member.coopInvitation.acceptedTimestamp} + <p> + {isProfileCreator ? 'Finished creation' : 'Accepted invite'}: + <span class="info-text-value coop-accepted-invite-after"> + after <b> + {millisecondsToTime( + data.member.coopInvitation.acceptedTimestamp - + data.member.coopInvitation.invitedTimestamp + )} + </b> + </span> + </p> + {/if} + </div> + <h3>Members</h3> + {#each data.profile.members.filter(m => !m.left) as player} + <span class="member"> + <Username {player} headType="2d" hyperlinkToProfile /> + </span> + {/each} + {#if data.profile.members.filter(m => m.left).length > 0} + <h3 class="former-members-title">Former members</h3> + {#each data.profile.members.filter(m => m.left) as player} + <span class="member"> + <Username {player} headType="2d" hyperlinkToProfile={data.profile.uuid} /> + </span> + {/each} + {/if} +{/if} + +<style> + p { + margin: 0; + } + .primary-info-text { + margin: 0.5em 0; + } + .info-text { + color: var(--theme-darker-text); + } + .info-text .info-text-value { + color: var(--theme-main-text); + } + + .member { + display: block; + } + .former-members-title { + margin-top: 0.5rem; + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Essence.svelte b/src/routes/player/[player]/[profile]/sections/Essence.svelte new file mode 100644 index 0000000..244dbcd --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Essence.svelte @@ -0,0 +1,27 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import { cleanId, toTitleCase } from '$lib/utils' + + export let data: CleanMemberProfile +</script> + +{#if data.member.essence.types.length > 0} + <ul> + {#each data.member.essence.types as essenceType} + <li> + {toTitleCase(cleanId(essenceType.id))}: + <b class="essence-type-amount">{essenceType.amount.toLocaleString()}</b> + </li> + {/each} + </ul> +{/if} + +<style> + p { + margin: 0; + } + + ul { + margin: 0.5em 0; + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/FarmingContests.svelte b/src/routes/player/[player]/[profile]/sections/FarmingContests.svelte new file mode 100644 index 0000000..8eef53d --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/FarmingContests.svelte @@ -0,0 +1,62 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Emoji from '$lib/Emoji.svelte' + import ListItemWithIcon from '$lib/ListItemWithIcon.svelte' + import { skyblockItemToUrl } from '$lib/minecraft/inventory' + import { skyblockTime } from '$lib/utils' + + export let data: CleanMemberProfile + + let cachedItemUrls: Record<string, string> = {} + function cachedSkyblockItemToUrl(item: string) { + if (!cachedItemUrls[item]) cachedItemUrls[item] = skyblockItemToUrl(item) + return cachedItemUrls[item] + } +</script> + +<div class="info-text primary-info-text"> + <p>Talked to Jacob: <Emoji value={data.member.farmingContests.talkedToJacob ? '✅' : '❌'} /></p> +</div> +<div class="farming-contests-list"> + {#each data.member.farmingContests.list as farmingContest} + <div class="farming-contest"> + <p class="farming-contest-date"> + {new Date( + skyblockTime(farmingContest.year, farmingContest.month, farmingContest.day) + ).toUTCString()} + </p> + <ul> + {#each farmingContest.crops as crop} + <ListItemWithIcon src={cachedSkyblockItemToUrl(crop.item)}> + <b>{crop.amount.toLocaleString()}</b> collected + {#if crop.position} + <span class="farming-contest-item-placement"> + (#{crop.position}/{crop.participants}) + </span> + {/if} + </ListItemWithIcon> + {/each} + </ul> + </div> + {/each} +</div> + +<style> + p, + ul { + margin: 0; + } + + .primary-info-text { + margin: 0.5em 0; + } + + .info-text { + color: var(--theme-darker-text); + } + + .farming-contest-item-placement, + .farming-contest-date { + color: var(--theme-darker-text); + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Harp.svelte b/src/routes/player/[player]/[profile]/sections/Harp.svelte new file mode 100644 index 0000000..6a5c9cc --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Harp.svelte @@ -0,0 +1,72 @@ +<script lang="ts"> + import type { CleanMemberProfile } from '$lib/APITypes' + import Emoji from '$lib/Emoji.svelte' + import { cleanId, millisecondsToTime, toTitleCase } from '$lib/utils' + + export let data: CleanMemberProfile +</script> + +<div class="info-text primary-info-text"> + <p>Claimed Melody's hair: <Emoji value={data.member.harp.claimedMelodysHair ? '✅' : '❌'} /></p> + {#if data.member.harp.selected} + <p> + Selected song: + <b class="info-text-value">{toTitleCase(cleanId(data.member.harp.selected.id))}</b> + <span class="harp-selection-timeago"> + {millisecondsToTime(Date.now() - data.member.harp.selected.timestamp)} ago + </span> + </p> + {/if} +</div> +<div class="harp-songs-list"> + {#each data.member.harp.songs as song} + <div class="harp-song" class:selected-harp-song={song.id === data.member.harp.selected?.id}> + <h3>{toTitleCase(cleanId(song.id))}</h3> + <div class="info-text"> + {#if song.completions} + <p>Completions: <b class="info-text-value">{song.completions}</b></p> + {/if} + {#if song.perfectCompletions} + <p>Perfect completions: <b class="info-text-value">{song.perfectCompletions}</b></p> + {:else} + <p>Progress: <b class="info-text-value">{Math.floor(song.progress * 100)}%</b></p> + {/if} + </div> + </div> + {/each} +</div> + +<style> + p { + margin: 0; + } + + .primary-info-text { + margin: 0.5em 0; + } + + .info-text { + color: var(--theme-darker-text); + } + .info-text .info-text-value { + color: var(--theme-main-text); + } + + .harp-songs-list { + display: flex; + flex-wrap: wrap; + max-width: 40rem; + column-gap: 0.5rem; + row-gap: 0.5rem; + } + .harp-song { + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(0, 0, 0, 0.1); + padding: 0.75em; + border-radius: 1em; + width: 12em; + } + .selected-harp-song { + border: 1px solid rgba(255, 255, 255, 0.2); + } +</style> diff --git a/src/routes/player/[player]/[profile]/sections/Infobox.svelte b/src/routes/player/[player]/[profile]/sections/Infobox.svelte new file mode 100644 index 0000000..f8d2889 --- /dev/null +++ b/src/routes/player/[player]/[profile]/sections/Infobox.svelte @@ -0,0 +1,83 @@ +<script lang="ts"> + import { generateInfobox } from '$lib/profile' + import Username from '$lib/minecraft/Username.svelte' + import Emoji from '$lib/Emoji.svelte' + + export let data +</script> + +<div id="infobox-container"> + <div id="infobox"> + <h2> + <Username player={data.member} prefix /> ({data.member.left + ? 'Removed' + : data.member.profileName}) + </h2> + {#each generateInfobox(data) as item} |
