aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-12-15 20:19:42 -0600
committerGitHub <noreply@github.com>2022-12-15 20:19:42 -0600
commited5eedab8f9fc90dadf5c442cf559572d1b35f0c (patch)
tree01a763fd11810e9970f14f7dae180e95b279de9a /src/lib
parent89bf3d31e36ad3bdfd45461ee6fb69a4c791f848 (diff)
parent103689520f51991a1e9a4ca5829fe2f46d1a32c2 (diff)
downloadskyblock-stats-ed5eedab8f9fc90dadf5c442cf559572d1b35f0c.tar.gz
skyblock-stats-ed5eedab8f9fc90dadf5c442cf559572d1b35f0c.tar.bz2
skyblock-stats-ed5eedab8f9fc90dadf5c442cf559572d1b35f0c.zip
Merge pull request #6 from skyblockstats/sveltekit-v1
Sveltekit v1
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/AuctionPriceScatterplot.svelte2
-rw-r--r--src/lib/BackgroundImage.svelte2
-rw-r--r--src/lib/Collapsible.svelte2
-rw-r--r--src/lib/LoginButton.svelte2
-rw-r--r--src/lib/MayorSkin.svelte24
-rw-r--r--src/lib/layout/Loader.svelte2
-rw-r--r--src/lib/minecraft/inventory.ts2
-rw-r--r--src/lib/sections/AccessoryBagUpgrades.svelte100
-rw-r--r--src/lib/sections/Achievements.svelte87
-rw-r--r--src/lib/sections/Armor.svelte14
-rw-r--r--src/lib/sections/Auctions.svelte109
-rw-r--r--src/lib/sections/Bank.svelte89
-rw-r--r--src/lib/sections/Claimed.svelte33
-rw-r--r--src/lib/sections/Collections.svelte62
-rw-r--r--src/lib/sections/Coop.svelte79
-rw-r--r--src/lib/sections/Essence.svelte27
-rw-r--r--src/lib/sections/FarmingContests.svelte62
-rw-r--r--src/lib/sections/Harp.svelte72
-rw-r--r--src/lib/sections/Infobox.svelte83
-rw-r--r--src/lib/sections/Inventories.svelte105
-rw-r--r--src/lib/sections/Leaderboards.svelte46
-rw-r--r--src/lib/sections/Minions.svelte40
-rw-r--r--src/lib/sections/Pets.svelte79
-rw-r--r--src/lib/sections/Skills.svelte87
-rw-r--r--src/lib/sections/Slayers.svelte74
-rw-r--r--src/lib/sections/StatList.svelte48
-rw-r--r--src/lib/sections/Zones.svelte42
27 files changed, 26 insertions, 1348 deletions
diff --git a/src/lib/AuctionPriceScatterplot.svelte b/src/lib/AuctionPriceScatterplot.svelte
index da1aed2..17a09f1 100644
--- a/src/lib/AuctionPriceScatterplot.svelte
+++ b/src/lib/AuctionPriceScatterplot.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { browser } from '$app/env'
+ import { browser } from '$app/environment'
import type { ItemAuctionsSchema, SimpleAuctionSchema } from './APITypes'
import type { PreviewedAuctionData } from './utils'
diff --git a/src/lib/BackgroundImage.svelte b/src/lib/BackgroundImage.svelte
index 0b2f043..17d8705 100644
--- a/src/lib/BackgroundImage.svelte
+++ b/src/lib/BackgroundImage.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
- import { browser } from '$app/env'
+ import { browser } from '$app/environment'
export let url: string
let styleHtml = `<style class="background-image-style">:root{--background:url(${url})}</style>`
diff --git a/src/lib/Collapsible.svelte b/src/lib/Collapsible.svelte
index a7b6105..68fee79 100644
--- a/src/lib/Collapsible.svelte
+++ b/src/lib/Collapsible.svelte
@@ -4,7 +4,7 @@
Collapsible content that works without JS but is enhanced by it.
-->
<script lang="ts">
- import { browser } from '$app/env'
+ import { browser } from '$app/environment'
import { onMount } from 'svelte'
import { cleanId } from './utils'
diff --git a/src/lib/LoginButton.svelte b/src/lib/LoginButton.svelte
index bba078e..7ebf5d7 100644
--- a/src/lib/LoginButton.svelte
+++ b/src/lib/LoginButton.svelte
@@ -6,7 +6,7 @@
{#if loggedIn}
<a href="/profile"><button class="login-button">Edit profile</button></a>
{:else}
- <a href="/login">
+ <a href="/login" rel="external">
<button class="login-button"
><img src="/discord-mark-light.svg" alt="Discord logo" />Log in with Discord</button
>
diff --git a/src/lib/MayorSkin.svelte b/src/lib/MayorSkin.svelte
index ad0a9a8..193a3d5 100644
--- a/src/lib/MayorSkin.svelte
+++ b/src/lib/MayorSkin.svelte
@@ -16,6 +16,7 @@
// special mayors
derpy: 'be0f89466528ad5eca5a6506adddd896ff78c4fd21facaa74a8c4a809c89207',
scorpius: '8f26fa0c47536e78e337257d898af8b1ebc87c0894503375234035ff2c7ef8f0',
+ finnegan: 'e7747fbee9fb39be39b00d3d483eb2f88b4bae82417ab5cb1b1aa930dd7b6689',
// unique mayors
technoblade: '786c039d969d1839155255e38e7b06a626ea9f8baf9cb55e0a77311efe18a3e',
@@ -23,14 +24,21 @@
faith: '64b39d0756b92b8b7599d1f971580088954e21c5f60c673d0d4f63693fb002b5',
}
- let url: string
+ let url: string | undefined
$: {
- if (name.toLowerCase() === 'derpy') url = '/villager.png'
- else url = `https://mc-heads.net/body/${skinIds[name.toLowerCase()]}`
+ if (name.toLowerCase() === 'jerry') url = '/villager.png'
+ else {
+ const skinId = skinIds[name.toLowerCase()]
+ url = skinId ? `https://mc-heads.net/body/${skinId}` : undefined
+ }
}
</script>
-<img src={url} alt="Mayor {toTitleCase(name)}" />
+{#if url}
+ <img src={url} alt="Mayor {toTitleCase(name)}" />
+{:else}
+ <div class="no-skin-found">Unknown mayor</div>
+{/if}
<style>
img {
@@ -39,4 +47,12 @@
height: 12em;
margin: 0 auto;
}
+
+ .no-skin-found {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 12em;
+ margin: 0 auto;
+ }
</style>
diff --git a/src/lib/layout/Loader.svelte b/src/lib/layout/Loader.svelte
index 3884e55..dd04924 100644
--- a/src/lib/layout/Loader.svelte
+++ b/src/lib/layout/Loader.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { browser } from '$app/env'
+ import { browser } from '$app/environment'
import { navigating } from '$app/stores'
let progress = 0
diff --git a/src/lib/minecraft/inventory.ts b/src/lib/minecraft/inventory.ts
index 227ed0c..5bd79c2 100644
--- a/src/lib/minecraft/inventory.ts
+++ b/src/lib/minecraft/inventory.ts
@@ -1,6 +1,6 @@
import * as skyblockAssets from 'skyblock-assets'
import { vanilla } from '$lib/packs'
-import { browser } from '$app/env'
+import { browser } from '$app/environment'
export interface Item {
id?: string
diff --git a/src/lib/sections/AccessoryBagUpgrades.svelte b/src/lib/sections/AccessoryBagUpgrades.svelte
deleted file mode 100644
index 74530d1..0000000
--- a/src/lib/sections/AccessoryBagUpgrades.svelte
+++ /dev/null
@@ -1,100 +0,0 @@
-<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/lib/sections/Achievements.svelte b/src/lib/sections/Achievements.svelte
deleted file mode 100644
index 92ba468..0000000
--- a/src/lib/sections/Achievements.svelte
+++ /dev/null
@@ -1,87 +0,0 @@
-<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/lib/sections/Armor.svelte b/src/lib/sections/Armor.svelte
deleted file mode 100644
index 285a898..0000000
--- a/src/lib/sections/Armor.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<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/lib/sections/Auctions.svelte b/src/lib/sections/Auctions.svelte
deleted file mode 100644
index 50d47b6..0000000
--- a/src/lib/sections/Auctions.svelte
+++ /dev/null
@@ -1,109 +0,0 @@
-<!--
- @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/lib/sections/Bank.svelte b/src/lib/sections/Bank.svelte
deleted file mode 100644
index 9bd2b8f..0000000
--- a/src/lib/sections/Bank.svelte
+++ /dev/null
@@ -1,89 +0,0 @@
-<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/lib/sections/Claimed.svelte b/src/lib/sections/Claimed.svelte
deleted file mode 100644
index 8f44796..0000000
--- a/src/lib/sections/Claimed.svelte
+++ /dev/null
@@ -1,33 +0,0 @@
-<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/lib/sections/Collections.svelte b/src/lib/sections/Collections.svelte
deleted file mode 100644
index 4baa660..0000000
--- a/src/lib/sections/Collections.svelte
+++ /dev/null
@@ -1,62 +0,0 @@
-<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/lib/sections/Coop.svelte b/src/lib/sections/Coop.svelte
deleted file mode 100644
index ff858f1..0000000
--- a/src/lib/sections/Coop.svelte
+++ /dev/null
@@ -1,79 +0,0 @@
-<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/lib/sections/Essence.svelte b/src/lib/sections/Essence.svelte
deleted file mode 100644
index 244dbcd..0000000
--- a/src/lib/sections/Essence.svelte
+++ /dev/null
@@ -1,27 +0,0 @@
-<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/lib/sections/FarmingContests.svelte b/src/lib/sections/FarmingContests.svelte
deleted file mode 100644
index 8eef53d..0000000
--- a/src/lib/sections/FarmingContests.svelte
+++ /dev/null
@@ -1,62 +0,0 @@
-<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/lib/sections/Harp.svelte b/src/lib/sections/Harp.svelte
deleted file mode 100644
index 6a5c9cc..0000000
--- a/src/lib/sections/Harp.svelte
+++ /dev/null
@@ -1,72 +0,0 @@
-<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/lib/sections/Infobox.svelte b/src/lib/sections/Infobox.svelte
deleted file mode 100644
index f8d2889..0000000
--- a/src/lib/sections/Infobox.svelte
+++ /dev/null
@@ -1,83 +0,0 @@
-<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}
- <!-- hack so fairy souls is clickable to get to the leaderboards -->
- {#if item.includes('Fairy souls')}
- <a href="/leaderboards/fairy_souls" class="fairy-souls-leaderboard"
- ><p><Emoji value={item} /></p></a
- >
- {:else}
- <p><Emoji value={item} /></p>
- {/if}
- {/each}
- </div>
- <div id="infobox-extra">
- <p>Player UUID:</p>
- <b><code>{data.member.uuid}</code></b>
- <p>Profile UUID:</p>
- <b><code>{data.profile.uuid}</code></b>
- </div>
-</div>
-
-<style>
- #infobox-container {
- float: right;
- max-width: 95%;
- width: 20em;
- }
- #infobox {
- background-color: rgba(20, 20, 20, 0.4);
- padding: 1em;
- margin-top: 2em;
- border-radius: 0.5em;
- box-shadow: 0 0 1em #000;
- }
- #infobox-extra {
- opacity: 0.5;
- margin-top: 0.5rem;
- }
- #infobox-extra p {
- margin: 0;
- }
- p {
- margin: 0 0 0.25em 0;
- }
- .fairy-souls-leaderboard {
- color: inherit;
- text-decoration: none;
- }
- @media only screen and (max-width: 600px) {
- #infobox {
- position: relative;
- right: -2em;
- margin-top: 0;
- }
- }
- @media only screen and (max-width: 550px) {
- #infobox {
- position: unset;
- box-shadow: none;
- float: none;
- border: 1px solid var(--theme-lighter-background);
- }
- }
- @media only screen and (max-width: 460px) {
- #infobox-container {
- max-width: 100%;
- float: left;
- }
- }
-</style>
diff --git a/src/lib/sections/Inventories.svelte b/src/lib/sections/Inventories.svelte
deleted file mode 100644
index 1dd7d28..0000000
--- a/src/lib/sections/Inventories.svelte
+++ /dev/null
@@ -1,105 +0,0 @@
-<script lang="ts">
- import { inventoryIconMap, skyblockItemToUrl, type Item } from '$lib/minecraft/inventory'
- import Inventory from '$lib/minecraft/Inventory.svelte'
- import type { MatcherFile } from 'skyblock-assets'
- import { cleanId } from '$lib/utils'
- import AccessoryBagUpgrades from './AccessoryBagUpgrades.svelte'
- import type { CleanMemberProfile } from '$lib/APITypes'
-
- export let data: CleanMemberProfile
- export let pack: MatcherFile
-
- let displayingInventories: string[] = []
- for (const inventoryName in data.member.inventories)
- if (inventoryName !== 'armor') displayingInventories.push(inventoryName)
-
- let selectedInventoryName: string = displayingInventories[0]
-</script>
-
-{#if displayingInventories.length > 1}
- <div id="inventory-tabs">
- {#each displayingInventories as inventoryName}
- <button
- class="inventory-tab"
- class:inventory-tab-active={inventoryName === selectedInventoryName}
- on:click={() => (selectedInventoryName = inventoryName)}
- >
- {#if inventoryName in inventoryIconMap}
- <img
- class="inventory-tab-icon"
- loading="lazy"
- src={skyblockItemToUrl(inventoryIconMap[inventoryName], pack, 50)}
- alt={cleanId(inventoryName)}
- />
- {/if}
- <span class="inventory-tab-name">{cleanId(inventoryName)}</span>
- </button>
- {/each}
- </div>
-{/if}
-{#if data.member.inventories}
- {#each displayingInventories as inventoryName}
- {#if inventoryName === selectedInventoryName}
- <span id={inventoryName} class="inventory-content">
- <Inventory items={data.member.inventories[inventoryName]} {pack} name={inventoryName} />
- </span>
- {#if inventoryName == 'accessory_bag'}
- <AccessoryBagUpgrades {data} />
- {/if}
- {/if}
- {/each}
-{/if}
-
-<style>
- #inventory-tabs {
- margin-bottom: 1em;
- overflow: hidden;
- border-radius: 1em;
- max-width: 40em;
- /* box-shadow: 0 0 1em #000; */
- }
- .inventory-tab {
- /* background-color: var(--theme-lighter-background); */
- background-color: rgba(20, 20, 20, 0.4);
- color: var(--theme-main-text);
- border: none;
- border-radius: 0;
- padding: 0 0.5em;
- cursor: pointer;
- transition-duration: 200ms;
- height: 2.5em;
- vertical-align: middle;
- }
- .inventory-tab-icon {
- height: 1.5em;
- width: 1.5em;
- vertical-align: text-bottom;
- position: relative;
- top: 0.1em;
- image-rendering: crisp-edges;
- image-rendering: pixelated;
- }
- .inventory-tab-name {
- vertical-align: text-top;
- }
- .inventory-tab:hover,
- .inventory-tab-active {
- background-color: rgba(40, 40, 40, 0.9);
- }
-
- .inventory-content {
- display: inline-grid;
- }
-
- @media only screen and (max-width: 480px) {
- .inventory-content :global(.item) {
- /* there's no good way to override the existing 32px size without !important :( */
- font-size: 24px !important;
- }
- }
- @media only screen and (max-width: 350px) {
- .inventory-content :global(.item) {
- font-size: 16px !important;
- }
- }
-</style>
diff --git a/src/lib/sections/Leaderboards.svelte b/src/lib/sections/Leaderboards.svelte
deleted file mode 100644
index 817f59a..0000000
--- a/src/lib/sections/Leaderboards.svelte
+++ /dev/null
@@ -1,46 +0,0 @@
-<script lang="ts">
- import { fetchApi } from '$lib/api'
-
- import type { CleanMemberProfile } from '$lib/APITypes'
- import Emoji from '$lib/Emoji.svelte'
- import { cleanId, formatNumberFromUnit } from '$lib/utils'
-
- export let data: CleanMemberProfile
-</script>
-
-{#await fetchApi(`player/${data.member.uuid}/${data.profile.uuid}/leaderboards`, fetch).then( r => r.json() )}
- Loading...
-{:then leaderboards}
- {#if leaderboards.length > 0}
- <ul>
- {#each leaderboards as leaderboard}
- <li class="leaderboard-item">
- <a href="/leaderboard/{leaderboard.name}" class="leaderboard-item-anchor">
- {leaderboard.positionIndex + 1}) <b>{cleanId(leaderboard.name)}</b>: {formatNumberFromUnit(
- leaderboard.value,
- leaderboard.unit ?? null
- )}
- </a>
- </li>
- {/each}
- </ul>
- {:else}
- <p>This player isn't in any leaderboards. <Emoji value="😦" /></p>
- {/if}
-{/await}
-
-<style>
- .leaderboard-item-anchor {
- color: inherit;
- }
- .leaderboard-item {
- list-style-type: none;
- }
- ul {
- padding-left: 0;
- margin-top: 0.5em;
- }
- p {
- margin: 0.5rem 0;
- }
-</style>
diff --git a/src/lib/sections/Minions.svelte b/src/lib/sections/Minions.svelte
deleted file mode 100644
index 856d8f0..0000000
--- a/src/lib/sections/Minions.svelte
+++ /dev/null
@@ -1,40 +0,0 @@
-<script lang="ts">
- import type { CleanMemberProfile } from '$lib/APITypes'
- import { cleanId, toRomanNumerals } from '$lib/utils'
-
- export let data: CleanMemberProfile
-</script>
-
-<p class="unique-minions-text">
- Unique minions:
- <span class="minions-fraction">
- <b>{data.profile.minionCount}</b>/{data.profile.maxUniqueMinions}
- </span>
-</p>
-<table>
- {#each data.profile.minions as minion}
- <tr>
- <th>{cleanId(minion.name)}</th>
- {#each minion.levels as unlocked, i}
- <td class="minion-table-item" class:unlocked>
- {toRomanNumerals(i + 1)}
- </td>
- {/each}
- </tr>
- {/each}
-</table>
-
-<style>
- .unique-minions-text {
- color: var(--theme-darker-text);
- }
- .minions-fraction {
- color: var(--theme-main-text);
- }
- .minion-table-item:not(.unlocked) {
- opacity: 0.2;
- }
- .minion-table-item.unlocked {
- color: #3e3;
- }
-</style>
diff --git a/src/lib/sections/Pets.svelte b/src/lib/sections/Pets.svelte
deleted file mode 100644
index 7f660c4..0000000
--- a/src/lib/sections/Pets.svelte
+++ /dev/null
@@ -1,79 +0,0 @@
-<script lang="ts">
- import type { CleanMemberProfile } from '$lib/APITypes'
- import { cleanId, toRomanNumerals, toTitleCase } from '$lib/utils'
-
- export let data: CleanMemberProfile
-
- // we convert it to a set to remove duplicates
- const petsAcquiredCount = new Set(data.member.pets.list.map(p => p.id)).size
- const totalPetsCount = data.member.pets.missingIds.length + petsAcquiredCount
-</script>
-
-{#if data.member.zones}
- <p class="zones-visited-text">
- Pets acquired:
- <span class="zones-visited-number">
- <b>{petsAcquiredCount}</b>/{totalPetsCount}
- </span>
- </p>
- <div class="pets-list">
- {#each data.member.pets.list as pet}
- <div class="individual-pet-data">
- <h3>{cleanId(pet.id.toLowerCase())}</h3>
- <p>Level: <b>{pet.level.toLocaleString()}</b></p>
- <p>Tier: <b>{toTitleCase(pet.tier)}</b></p>
- {#if pet.item}
- <p>Item: <b>{pet.item.display.name}</b></p>
- {/if}
- </div>
- {/each}
- </div>
- {#if data.member.pets.missingIds.length > 0}
- <h3 class="missing-pets-title">Missing</h3>
- <ul>
- {#each data.member.pets.missingIds as petId}
- <li class="missing-pet">{toTitleCase(cleanId(petId.toLowerCase()))}</li>
- {/each}
- </ul>
- {/if}
-{/if}
-
-<style>
- p {
- margin: 0;
- }
- .zones-visited-text {
- color: var(--theme-darker-text);
- margin: 0.5em 0;
- }
- .zones-visited-number {
- color: var(--theme-main-text);
- }
- .missing-pet {
- opacity: 0.5;
- }
- .individual-pet-data {
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(0, 0, 0, 0.1);
- padding: 0.75em;
- border-radius: 1em;
- width: 9rem;
- }
-
- .pets-list {
- display: flex;
- flex-wrap: wrap;
- max-width: 40rem;
- column-gap: 0.5rem;
- row-gap: 0.5rem;
- }
-
- .missing-pets-title {
- margin-top: 1rem;
- }
-
- ul {
- padding-left: 1em;
- margin: 0;
- }
-</style>
diff --git a/src/lib/sections/Skills.svelte b/src/lib/sections/Skills.svelte
deleted file mode 100644
index 8fc5aaf..0000000
--- a/src/lib/sections/Skills.svelte
+++ /dev/null
@@ -1,87 +0,0 @@
-<script lang="ts">
- import Emoji from '$lib/Emoji.svelte'
-
- import Tooltip from '$lib/Tooltip.svelte'
- import { cleanId, formatNumber } from '$lib/utils'
-
- const skillImages = {
- runecrafting: '/skill-icons/runecrafting.webp',
- alchemy: '/skill-icons/herblore.webp',
- combat: '/skill-icons/attack.webp',
- enchanting: '/skill-icons/magic.webp',
- foraging: '/skill-icons/woodcutting.webp',
- mining: '/skill-icons/mining.webp',
- taming: '/skill-icons/hunter.webp',
- farming: '/skill-icons/farming.webp',
- fishing: '/skill-icons/fishing.webp',
- carpentry: '/skill-icons/construction.webp',
- social: '/skill-icons/agility.webp',
- }
-
- export let data
-</script>
-
-{#if !data.member.skills.apiEnabled}
- <p class="skills-api-warning">
- <Emoji value="⚠" /> Skills API is disabled for this profile, so the values shown may be inaccurate.
- </p>
-{/if}
-<ul>
- {#each data.member.skills.list as skill}
- <li
- class="list-item-with-icon"
- style="background: url({skillImages[skill.id]}) 0 0/1em no-repeat"
- >
- <Tooltip>
- <span slot="tooltip">
- {#if skill.levelXpRequired !== null}
- {Math.round((skill.levelXp / skill.levelXpRequired) * 100)}% to next level, {Math.round(
- skill.levelXp
- ).toLocaleString()}/{formatNumber(skill.levelXpRequired, 3)} xp
- {:else}
- {Math.round(skill.levelXp).toLocaleString()} extra xp
- {/if}
- </span>
- <span>
- {cleanId(skill.id)}
- <span class="skill-level" class:skill-maxed={skill.level === skill.maxLevel}>
- {skill.level}
- </span>
- </span>
- </Tooltip>
- </li>
- {/each}
-</ul>
-
-<style>
- .skill-level {
- opacity: 0.9;
- }
- .skill-maxed {
- color: #0e0;
- opacity: 1;
- }
- .list-item-with-icon {
- list-style: none;
- padding-left: 1.2em;
- position: relative;
- right: 1.2em;
- image-rendering: crisp-edges;
- image-rendering: pixelated;
- }
-
- .skills-api-warning {
- margin-top: 0;
- }
-
- ul {
- margin-top: 0;
- display: flex;
- flex-wrap: wrap;
- max-width: 30em;
- }
- ul > li {
- width: 10em;
- margin: 0.25em 0.25em 0 0;
- }
-</style>
diff --git a/src/lib/sections/Slayers.svelte b/src/lib/sections/Slayers.svelte
deleted file mode 100644
index 40fa67d..0000000
--- a/src/lib/sections/Slayers.svelte
+++ /dev/null
@@ -1,74 +0,0 @@
-<script lang="ts">
- import type { CleanMemberProfile } from '$lib/APITypes'
- import { cleanId, toRomanNumerals } from '$lib/utils'
-
- export let data: CleanMemberProfile
-</script>
-
-{#if data.member.slayers}
- <div class="slayer-info-text total-slayer-info-text">
- <p>Xp: <b>{data.member.slayers.xp}</b></p>
- <p>Kills: <b>{data.member.slayers.kills}</b></p>
- </div>
- <div class="slayers-list">
- {#each data.member.slayers.bosses as slayer}
- <div class="individual-slayer-data">
- <h3>{slayer.name ? cleanId(slayer.name) : cleanId(slayer.rawName)}</h3>
- <div class="slayer-info-text">
- <p>Xp: <b>{slayer.xp.toLocaleString()}</b></p>
- <p>Level: <b>{slayer.level}</b></p>
- </div>
- <table>
- <tr>
- {#each slayer.tiers as tier}
- <th>Tier {toRomanNumerals(tier.tier)}</th>
- {/each}
- </tr>
- <tr>
- {#each slayer.tiers as tier}
- <td class="slayer-tier-kills">
- {tier.kills.toLocaleString()}
- </td>
- {/each}
- </tr>
- </table>
- </div>
- {/each}
- </div>
-{/if}
-
-<style>
- p {
- margin: 0;
- }
-
- .total-slayer-info-text {
- margin: 0.5em 0;
- }
-
- .slayer-tier-kills {
- text-align: center;
- }
-
- .slayer-info-text {
- color: var(--theme-darker-text);
- }
- .slayer-info-text b {
- color: var(--theme-main-text);
- }
-
- .individual-slayer-data {
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: rgba(0, 0, 0, 0.1);
- padding: 0.75em;
- border-radius: 1em;
- }
-
- .slayers-list {
- display: flex;
- flex-wrap: wrap;
- max-width: 40rem;
- column-gap: 0.5rem;
- row-gap: 0.5rem;
- }
-</style>
diff --git a/src/lib/sections/StatList.svelte b/src/lib/sections/StatList.svelte
deleted file mode 100644
index 38604d7..0000000
--- a/src/lib/sections/StatList.svelte
+++ /dev/null
@@ -1,48 +0,0 @@
-<!--
- @component
-
- A sorted list of a user's stats, with the total sometimes being at the top.
--->
-<script lang="ts">
- import { cleanId, millisecondsToTime } from '$lib/utils'
- import type { StatItem } from '$lib/APITypes'
-
- export let stats: StatItem[]
-</script>
-
-<ul>
- {#each stats.sort((a, b) => b.value - a.value) as stat}
- <li class:total-stat={stat.categorizedName === 'total'}>
- <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>
-
-<style>
- .total-stat .stat-name {
- color: var(--theme-darker-text);
- }
- .total-stat .stat-value {
- font-weight: bold;
- }
-
- .total-stat {
- list-style-type: none;
- padding: 0 0 0.5em 0;
- right: 1em;
- }
- li {
- position: relative;
- }
- ul {
- margin-top: 0.5em;
- padding-left: 1em;
- }
-</style>
diff --git a/src/lib/sections/Zones.svelte b/src/lib/sections/Zones.svelte
deleted file mode 100644
index f7e993b..0000000
--- a/src/lib/sections/Zones.svelte
+++ /dev/null
@@ -1,42 +0,0 @@
-<script lang="ts">
- import type { CleanMemberProfile } from '$lib/APITypes'
- import { cleanId } from '$lib/utils'
-
- export let data: CleanMemberProfile
-
- let zonesVisitedCount = data.member.zones ? data.member.zones?.filter(z => z.visited).length : 0
-</script>
-
-{#if data.member.zones}
- <p class="zones-visited-text">
- Zones visited:
- <span class="zones-visited-number">
- <b>{zonesVisitedCount}</b>/{data.member.zones.length}
- </span>
- </p>
- <ul>
- {#each data.member.zones.filter(z => z.visited) as zone}
- <li>{cleanId(zone.name)}</li>
- {/each}
- {#each data.member.zones.filter(z => !z.visited) as zone}
- <li class="unvisited-zone">{cleanId(zone.name)}</li>
- {/each}
- </ul>
-{/if}
-
-<style>
- .zones-visited-text {
- color: var(--theme-darker-text);
- margin: 0.5em 0;
- }
- .zones-visited-number {
- color: var(--theme-main-text);
- }
- .unvisited-zone {
- opacity: 0.5;
- }
- ul {
- padding-left: 1em;
- margin: 0;
- }
-</style>