aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-02-16 22:13:11 +0000
committermat <github@matdoes.dev>2022-02-16 22:13:11 +0000
commitcd982e64595ab47a5fa71a40f8249893dbf867df (patch)
treea2c9c5ed31bd1f5f48c3b992f344cc2f461ecebc
parent81543e5307c98df0d44c1cf553bcdfefbfe4ea7f (diff)
downloadskyblock-stats-cd982e64595ab47a5fa71a40f8249893dbf867df.tar.gz
skyblock-stats-cd982e64595ab47a5fa71a40f8249893dbf867df.tar.bz2
skyblock-stats-cd982e64595ab47a5fa71a40f8249893dbf867df.zip
add
-rw-r--r--src/lib/ListItemWithIcon.svelte18
-rw-r--r--src/lib/Toc.svelte27
-rw-r--r--src/lib/Tooltip.svelte75
-rw-r--r--src/lib/sections/Infobox.svelte70
-rw-r--r--src/lib/sections/Skills.svelte64
-rw-r--r--src/routes/constants.json.ts18
6 files changed, 272 insertions, 0 deletions
diff --git a/src/lib/ListItemWithIcon.svelte b/src/lib/ListItemWithIcon.svelte
new file mode 100644
index 0000000..2c7aade
--- /dev/null
+++ b/src/lib/ListItemWithIcon.svelte
@@ -0,0 +1,18 @@
+<script lang="ts">
+ export let url: string
+</script>
+
+<li class="list-item-with-icon" style="background: url({url}) 0 0/1em no-repeat">
+ <slot />
+</li>
+
+<style>
+ .list-item-with-icon {
+ list-style: none;
+ padding-left: 1.2em;
+ position: relative;
+ right: 1.2em;
+ image-rendering: crisp-edges;
+ image-rendering: pixelated;
+ }
+</style>
diff --git a/src/lib/Toc.svelte b/src/lib/Toc.svelte
new file mode 100644
index 0000000..190c6a4
--- /dev/null
+++ b/src/lib/Toc.svelte
@@ -0,0 +1,27 @@
+<script lang="ts">
+ import { cleanId } from './utils'
+
+ export let categories: string[]
+</script>
+
+<ul id="toc">
+ <h3>Contents</h3>
+ {#each categories as category}
+ <li><a href="#{category}">{cleanId(category)}</a></li>
+ {/each}
+</ul>
+
+<style>
+ #toc {
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ max-width: max-content;
+ padding: 0.75em;
+ border-radius: 1em;
+
+ display: inline-block;
+ background: rgba(0, 0, 0, 0.1);
+ }
+ #toc li {
+ list-style-type: none;
+ }
+</style>
diff --git a/src/lib/Tooltip.svelte b/src/lib/Tooltip.svelte
new file mode 100644
index 0000000..55cbc21
--- /dev/null
+++ b/src/lib/Tooltip.svelte
@@ -0,0 +1,75 @@
+<!--
+ @component
+
+ A tooltip that works without requiring JavaScript to be enabled.
+ When you hover or click on the element, it shows the value in a box above the element.
+ -->
+
+<span class="tooltip-container" tabindex="1">
+ <span class="tooltip-content">
+ <slot name="tooltip">No tooltip!</slot>
+ </span>
+ <span class="tooltip-inner">
+ <slot />
+ </span>
+</span>
+
+<!-- <li class="tooltip list-item-with-icon" data-tooltip="{% if skill.levelXpRequired != null %}{{ ((skill.levelXp/skill.levelXpRequired)*100)|round }}% to next level, {{ skill.levelXp|round|cleannumber }}/{{ skill.levelXpRequired|formatnumber(3) }} xp{% else %}{{ skill.levelXp|round|cleannumber }} extra xp{% endif %}" tabindex="1" style="background: url('{{ imageUrl }}') 0 0/1em no-repeat;"></li> -->
+<style>
+ .tooltip-container {
+ position: relative;
+ cursor: pointer;
+ touch-action: manipulation;
+ /*
+ * We remove the outline here because in safari the tooltip looks weird
+ * and glitchy. The outline is readded later on so it's only for the content.
+ */
+ outline: none !important;
+ }
+ .tooltip-container > .tooltip-content {
+ font-size: 1rem;
+ display: inline-block;
+ left: 50%;
+ opacity: 0;
+ position: absolute;
+
+ background: var(--theme-lightest-background);
+ border-radius: 0.25em;
+ bottom: 125%;
+ color: var(--theme-main-text);
+ margin-left: max(calc(-50% - 1em), -5em);
+ padding: 0.5em;
+
+ /* this doesn't work in safari */
+ transition-delay: none;
+ transition: all 200ms;
+
+ transform: scale(0.6) translateY(50%);
+
+ width: max-content;
+ text-align: center;
+ max-width: 10em;
+ cursor: auto;
+ box-shadow: 0 0 1em 0.5em #0002;
+ pointer-events: none;
+ }
+ .tooltip-container:hover > .tooltip-content,
+ .tooltip-container:focus > .tooltip-content {
+ opacity: 0.9;
+ transform: scale(1) translateY(0);
+ }
+
+ /* Here's where the outline is re-added */
+ .tooltip-container:focus > .tooltip-inner {
+ border-radius: 0.1em;
+ box-shadow: 0 0 0 2pt #06e7;
+ }
+ /*
+ * hide all the tooltips after if one is selected, unfortunately we can't
+ * select all the elements before and outside :(
+ */
+ :global(.tooltip-container:focus ~ .tooltip-container + .tooltip-content) {
+ transform: scale(0.6) translateY(50%);
+ opacity: 0;
+ }
+</style>
diff --git a/src/lib/sections/Infobox.svelte b/src/lib/sections/Infobox.svelte
new file mode 100644
index 0000000..eda7d22
--- /dev/null
+++ b/src/lib/sections/Infobox.svelte
@@ -0,0 +1,70 @@
+<script lang="ts">
+ import { generateInfobox, prettyTimestamp } from '$lib/profile'
+ import Username from '$lib/Username.svelte'
+ import Emoji from '$lib/Emoji.svelte'
+
+ export let data
+ export let constants
+</script>
+
+<!-- <div id="infobox">
+ <h2>{{ render.username(data.member, prefix=true) }} ({{ data.member.profileName }})</h2>
+ <p>{{ '💾'|twemojiHtml|safe }} Last save: {% if getTime() - data.member.last_save < 60 * 60 * 24 * 7 %}{{ ((getTime() - data.member.last_save) * 1000)|cleannumber('time') }} ago {% else %}{{ data.member.last_save|cleannumber('date') }}{% endif %}</p>
+ <p>{{ '🚶'|twemojiHtml|safe }} Profile created: {% if getTime() - data.member.first_join < 60 * 60 * 24 * 7 %}{{ ((getTime() - data.member.first_join) * 1000)|cleannumber('time') }} ago {% else %}{{ data.member.first_join|cleannumber('date') }}{% endif %}</p>
+ <p>{{ '✨'|twemojiHtml|safe }} Fairy souls: {{ data.member.fairy_souls.total }}/{{ getConstants().max_fairy_souls }}</p>
+{%- if data.profile.minion_count == getConstants().max_minions -%}<p>{{ '🤖'|twemojiHtml|safe }} Minion count: {{ data.profile.minion_count }}</p>{% endif %}
+{%- set mostSignificantKillsStat = {} -%}
+{%- set mostSignificantDeathsStat = {} -%}
+{%- for stat in data.member.stats -%}
+{%- if stat.category == 'kills' and stat.rawName != 'kills' and stat.value >= 200000 and stat.value > (mostSignificantKillsStat.value or 0) -%}
+{%- set mostSignificantKillsStat = stat -%}
+{%- endif -%}
+{%- if stat.category == 'deaths' and stat.rawName != 'deaths' and stat.value >= 1000000 and stat.value > (mostSignificantDeathsStat.value or 0) -%}
+{%- set mostSignificantDeathsStat = stat -%}
+{%- endif -%}
+{%- endfor -%}
+{%- if mostSignificantKillsStat.value -%}
+ <p>{{ '⚔️'|twemojiHtml|safe }} {{ mostSignificantKillsStat.value|cleannumber(mostSignificantKillsStat.unit or mostSignificantKillsStat.rawName|clean|lower) }}</p>
+{%- endif -%}
+{%- if mostSignificantDeathsStat.value -%}
+ <p>{{ '☠'|twemojiHtml|safe }} {{ mostSignificantDeathsStat.value|cleannumber(mostSignificantDeathsStat.unit or mostSignificantDeathsStat.rawName|clean|lower) }}</p>
+{%- endif -%}
+</div> -->
+
+<div id="infobox">
+ <h2><Username player={data.member} prefix /> ({data.member.profileName})</h2>
+ {#each generateInfobox(data, constants, { meta: false }) as item}
+ <p><Emoji value={item} /></p>
+ {/each}
+</div>
+
+<style>
+ #infobox {
+ float: right;
+ max-width: 95%;
+ background-color: rgba(20, 20, 20, 0.4);
+ padding: 1em;
+ margin-top: 2em;
+ width: 20em;
+ border-radius: 0.5em;
+ box-shadow: 0 0 1em #000;
+ }
+ p {
+ margin: 0 0 0.25em 0;
+ }
+ @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);
+ }
+ }
+</style>
diff --git a/src/lib/sections/Skills.svelte b/src/lib/sections/Skills.svelte
new file mode 100644
index 0000000..790da6d
--- /dev/null
+++ b/src/lib/sections/Skills.svelte
@@ -0,0 +1,64 @@
+<script lang="ts">
+ import Tooltip from '$lib/Tooltip.svelte'
+ import { cleanId, formatNumber } from '$lib/utils'
+
+ const skillImages = {
+ runecrafting: 'https://cdn.matdoes.dev/images/runescape/runecrafting.webp',
+ alchemy: 'https://cdn.matdoes.dev/images/runescape/herblore.webp',
+ combat: 'https://cdn.matdoes.dev/images/runescape/attack.webp',
+ enchanting: 'https://cdn.matdoes.dev/images/runescape/magic.webp',
+ foraging: 'https://cdn.matdoes.dev/images/runescape/woodcutting.webp',
+ mining: 'https://cdn.matdoes.dev/images/runescape/mining.webp',
+ taming: 'https://cdn.matdoes.dev/images/runescape/hunter.webp',
+ farming: 'https://cdn.matdoes.dev/images/runescape/farming.webp',
+ fishing: 'https://cdn.matdoes.dev/images/runescape/fishing.webp',
+ carpentry: 'https://cdn.matdoes.dev/images/runescape/construction.webp',
+ }
+
+ export let data
+</script>
+
+<ul>
+ {#each data.member.skills as skill}
+ <li
+ class="list-item-with-icon"
+ style="background: url({skillImages[skill.name]}) 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.name)}
+ <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;
+ }
+</style>
diff --git a/src/routes/constants.json.ts b/src/routes/constants.json.ts
new file mode 100644
index 0000000..301da58
--- /dev/null
+++ b/src/routes/constants.json.ts
@@ -0,0 +1,18 @@
+import { API_URL } from '$lib/api'
+
+export let constants: any = {}
+
+async function updateConstants() {
+ constants = await fetch(API_URL + 'constants').then(r => r.json())
+ console.log('updated constants')
+}
+
+updateConstants()
+setInterval(updateConstants, 60 * 60 * 1000) // update every hour
+
+export async function get({ request }) {
+ console.log('gotten constants')
+ return {
+ body: constants || {}
+ }
+} \ No newline at end of file