Add character detail
|
@ -24,7 +24,7 @@
|
||||||
"@mdi/js": "^5.7.55",
|
"@mdi/js": "^5.7.55",
|
||||||
"@rollup/plugin-babel": "^5.0.0",
|
"@rollup/plugin-babel": "^5.0.0",
|
||||||
"@rollup/plugin-commonjs": "^16.0.0",
|
"@rollup/plugin-commonjs": "^16.0.0",
|
||||||
"@rollup/plugin-dynamic-import-vars": "^1.1.0",
|
"@rollup/plugin-dynamic-import-vars": "^1.1.1",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^10.0.0",
|
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||||
"@rollup/plugin-replace": "^2.3.4",
|
"@rollup/plugin-replace": "^2.3.4",
|
||||||
|
|
|
@ -7,6 +7,7 @@ import svelte from 'rollup-plugin-svelte';
|
||||||
import babel from '@rollup/plugin-babel';
|
import babel from '@rollup/plugin-babel';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import json from '@rollup/plugin-json';
|
import json from '@rollup/plugin-json';
|
||||||
|
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
|
||||||
|
|
||||||
import config from 'sapper/config/rollup.js';
|
import config from 'sapper/config/rollup.js';
|
||||||
import { config as envConfig } from 'dotenv';
|
import { config as envConfig } from 'dotenv';
|
||||||
|
@ -59,6 +60,12 @@ export default {
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
json(),
|
json(),
|
||||||
|
dynamicImportVars({
|
||||||
|
include: [
|
||||||
|
'**/*.svelte',
|
||||||
|
'**/*.json',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
legacy &&
|
legacy &&
|
||||||
babel({
|
babel({
|
||||||
|
@ -121,6 +128,12 @@ export default {
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
json(),
|
json(),
|
||||||
|
dynamicImportVars({
|
||||||
|
include: [
|
||||||
|
'**/*.svelte',
|
||||||
|
'**/*.json',
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),
|
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),
|
||||||
|
|
||||||
|
@ -134,12 +147,7 @@ export default {
|
||||||
...config.serviceworker.output(),
|
...config.serviceworker.output(),
|
||||||
file: config.serviceworker.output().file.replace('service-worker', 'firebase-messaging-sw'),
|
file: config.serviceworker.output().file.replace('service-worker', 'firebase-messaging-sw'),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [replace(envData), resolve(), commonjs(), !dev && terser()],
|
||||||
replace(envData),
|
|
||||||
resolve(),
|
|
||||||
commonjs(),
|
|
||||||
!dev && terser(),
|
|
||||||
],
|
|
||||||
|
|
||||||
preserveEntrySignatures: false,
|
preserveEntrySignatures: false,
|
||||||
onwarn,
|
onwarn,
|
||||||
|
|
|
@ -71,29 +71,29 @@ export const eventsData = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'Invitation of Windblume - 1.4 Event',
|
name: 'Invitation of Windblume - 1.4 Event',
|
||||||
pos: '0% 20%',
|
pos: '0% 20%',
|
||||||
image: 'update14.png',
|
image: 'update14.png',
|
||||||
start: '2021-03-19 10:00:00',
|
start: '2021-03-19 10:00:00',
|
||||||
end: '2021-04-05 04:00:00',
|
end: '2021-04-05 04:00:00',
|
||||||
color: '#79D2EB',
|
color: '#79D2EB',
|
||||||
zoom: '120%',
|
zoom: '120%',
|
||||||
url: 'https://genshin.mihoyo.com/en/news/detail/9407',
|
url: 'https://genshin.mihoyo.com/en/news/detail/9407',
|
||||||
showOnHome: true,
|
showOnHome: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Wishful Drops - Oceanid Event',
|
name: 'Wishful Drops - Oceanid Event',
|
||||||
pos: '0% 20%',
|
pos: '0% 20%',
|
||||||
image: 'wishful_drops.jpg',
|
image: 'wishful_drops.jpg',
|
||||||
start: '2021-04-09 10:00:00',
|
start: '2021-04-09 10:00:00',
|
||||||
end: '2021-04-16 04:00:00',
|
end: '2021-04-16 04:00:00',
|
||||||
color: '#579DE5',
|
color: '#579DE5',
|
||||||
zoom: '170%',
|
zoom: '170%',
|
||||||
url: 'https://www.hoyolab.com/genshin/article/286280',
|
url: 'https://www.hoyolab.com/genshin/article/286280',
|
||||||
showOnHome: true,
|
showOnHome: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'Act I',
|
name: 'Act I',
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { getAccountPrefix } from "../stores/account";
|
||||||
|
import { readSave, updateSave } from "../stores/saveManager";
|
||||||
|
|
||||||
|
const bannerCategories = ['beginners', 'standard', 'character-event', 'weapon-event'];
|
||||||
|
|
||||||
|
function readLocalData(path) {
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
const data = readSave(`${prefix}${path}`);
|
||||||
|
if (data !== null) {
|
||||||
|
const counterData = JSON.parse(data);
|
||||||
|
const pullData = counterData.pulls || [];
|
||||||
|
|
||||||
|
return pullData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processCharacters() {
|
||||||
|
const characters = {};
|
||||||
|
for (const id of bannerCategories) {
|
||||||
|
const data = readLocalData(`wish-counter-${id}`);
|
||||||
|
if (data === null) continue;
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
|
if (item.type === 'character') {
|
||||||
|
if (characters[item.id] === undefined) {
|
||||||
|
characters[item.id] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
characters[item.id]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
updateSave(`${prefix}characters`, JSON.stringify(characters));
|
||||||
|
}
|
|
@ -68,9 +68,30 @@
|
||||||
"element": "Element",
|
"element": "Element",
|
||||||
"rarity": "Rarity",
|
"rarity": "Rarity",
|
||||||
"weapon": "Weapon",
|
"weapon": "Weapon",
|
||||||
|
"talents": "Talents",
|
||||||
|
"passiveTalents": "Passive Talents",
|
||||||
|
"constellations": "Constellations",
|
||||||
|
"asc": "ASC",
|
||||||
|
"lvl": "LVL",
|
||||||
"hp": "HP",
|
"hp": "HP",
|
||||||
"atk": "ATK",
|
"atk": "ATK",
|
||||||
"def": "DEF"
|
"def": "DEF",
|
||||||
|
"hpPercent": "HP%",
|
||||||
|
"atkPercent": "ATK%",
|
||||||
|
"defPercent": "DEF%",
|
||||||
|
"critRate": "CRIT Rate",
|
||||||
|
"critDamage": "CRIT DMG",
|
||||||
|
"em": "Elemental Mastery",
|
||||||
|
"er": "Energy Recharge",
|
||||||
|
"healingBonus": "Healing Bonus",
|
||||||
|
"pyroDamageBonus": "Pyro DMG Bonus",
|
||||||
|
"hydroDamageBonus": "Hydro DMG Bonus",
|
||||||
|
"dendroDamageBonus": "Dendro DMG Bonus",
|
||||||
|
"electroDamageBonus": "Electro DMG Bonus",
|
||||||
|
"cryoDamageBonus": "Cryo DMG Bonus",
|
||||||
|
"anemoDamageBonus": "Anemo DMG Bonus",
|
||||||
|
"physicalDamageBonus": "Physical DMG Bonus",
|
||||||
|
"geoDamageBonus": "Geo DMG Bonus"
|
||||||
},
|
},
|
||||||
"wish": {
|
"wish": {
|
||||||
"title": "Wish Counter",
|
"title": "Wish Counter",
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
<script>
|
|
||||||
import { t } from 'svelte-i18n'
|
|
||||||
import { mdiStar } from '@mdi/js';
|
|
||||||
|
|
||||||
import Icon from '../components/Icon.svelte';
|
|
||||||
import TableHeader from '../components/Table/TableHeader.svelte';
|
|
||||||
|
|
||||||
import { characters } from '../data/characters';
|
|
||||||
|
|
||||||
let sortBy = '';
|
|
||||||
let sortOrder = false;
|
|
||||||
|
|
||||||
$: chars = Object.entries(characters).sort((a, b) => {
|
|
||||||
switch (sortBy) {
|
|
||||||
case 'name':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].name.localeCompare(b[1].name);
|
|
||||||
} else {
|
|
||||||
return b[1].name.localeCompare(a[1].name);
|
|
||||||
}
|
|
||||||
case 'element':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].element.name.localeCompare(b[1].element.name);
|
|
||||||
} else {
|
|
||||||
return b[1].element.name.localeCompare(a[1].element.name);
|
|
||||||
}
|
|
||||||
case 'rarity':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].rarity - b[1].rarity;
|
|
||||||
} else {
|
|
||||||
return b[1].rarity - a[1].rarity;
|
|
||||||
}
|
|
||||||
case 'weapon':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].weapon.name.localeCompare(b[1].weapon.name);
|
|
||||||
} else {
|
|
||||||
return b[1].weapon.name.localeCompare(a[1].weapon.name);
|
|
||||||
}
|
|
||||||
case 'hp':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].stats.hp - b[1].stats.hp;
|
|
||||||
} else {
|
|
||||||
return b[1].stats.hp - a[1].stats.hp;
|
|
||||||
}
|
|
||||||
case 'atk':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].stats.atk - b[1].stats.atk;
|
|
||||||
} else {
|
|
||||||
return b[1].stats.atk - a[1].stats.atk;
|
|
||||||
}
|
|
||||||
case 'def':
|
|
||||||
if (sortOrder) {
|
|
||||||
return a[1].stats.def - b[1].stats.def;
|
|
||||||
} else {
|
|
||||||
return b[1].stats.def - a[1].stats.def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function sort(by) {
|
|
||||||
if (sortBy === by) {
|
|
||||||
sortOrder = !sortOrder;
|
|
||||||
} else {
|
|
||||||
sortBy = by;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Paimon.moe</title>
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Your best Genshin Impact companion! Help you plan what to farm with ascension calculator and database. Also track your progress with todo and wish counter."
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="Your best Genshin Impact companion! Help you plan what to farm with ascension calculator and database. Also track your progress with todo and wish counter."
|
|
||||||
/>
|
|
||||||
</svelte:head>
|
|
||||||
<div class="lg:ml-64 pt-20 lg:pt-8">
|
|
||||||
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">{$t('characters.title')}</h1>
|
|
||||||
<p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;">
|
|
||||||
※ {$t('characters.subtitle')}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="block overflow-x-auto whitespace-no-wrap pb-8">
|
|
||||||
<div class="px-4 md:px-8 table">
|
|
||||||
<table class="w-full block p-4 bg-item rounded-xl">
|
|
||||||
<thead>
|
|
||||||
<th style="min-width: 4rem;" />
|
|
||||||
<TableHeader on:click={() => sort('name')} sort={sortBy === 'name'} order={sortOrder}>{$t('characters.name')}</TableHeader>
|
|
||||||
<TableHeader on:click={() => sort('element')} sort={sortBy === 'element'} order={sortOrder} align="center">
|
|
||||||
{$t('characters.element')}
|
|
||||||
</TableHeader>
|
|
||||||
<TableHeader on:click={() => sort('rarity')} sort={sortBy === 'rarity'} order={sortOrder} align="center">
|
|
||||||
{$t('characters.rarity')}
|
|
||||||
</TableHeader>
|
|
||||||
<TableHeader on:click={() => sort('weapon')} sort={sortBy === 'weapon'} order={sortOrder} align="center">
|
|
||||||
{$t('characters.weapon')}
|
|
||||||
</TableHeader>
|
|
||||||
<TableHeader on:click={() => sort('hp')} sort={sortBy === 'hp'} order={sortOrder} align="center">
|
|
||||||
{$t('characters.hp')}
|
|
||||||
</TableHeader>
|
|
||||||
<TableHeader on:click={() => sort('atk')} sort={sortBy === 'atk'} order={sortOrder} align="center">
|
|
||||||
{$t('characters.atk')}
|
|
||||||
</TableHeader>
|
|
||||||
<TableHeader on:click={() => sort('def')} sort={sortBy === 'def'} order={sortOrder} align="center">
|
|
||||||
{$t('characters.def')}
|
|
||||||
</TableHeader>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each chars as [id, char] (id)}
|
|
||||||
<tr class={`rounded cursor-pointer ${char.rarity === 4 ? 'rare' : 'legendary'}`}>
|
|
||||||
<td class="rarity w-16 sticky" style="padding: 0; left: 0px;">
|
|
||||||
<img class="w-12 h-12 rounded-full" src={`/images/characters/${id}.png`} alt={char.name} />
|
|
||||||
</td>
|
|
||||||
<td>{char.name}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<img class="w-8 h-8 inline" src={`/images/elements/${char.element.id}.png`} alt={char.element.name} />
|
|
||||||
</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<Icon color={char.rarity === 5 ? '#B9812E' : '#AD76B0'} path={mdiStar} />
|
|
||||||
</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<img class="w-8 h-8 inline" src={`/images/weapons/${char.weapon.id}.png`} alt={char.weapon.name} />
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{char.stats.hp}</td>
|
|
||||||
<td class="text-center">{char.stats.atk}</td>
|
|
||||||
<td class="text-center">{char.stats.def}</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
tr.rare:hover {
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
rgba(0, 0, 0, 0) 0%,
|
|
||||||
rgba(173, 118, 176, 0.85) 10%,
|
|
||||||
rgba(102, 86, 128, 0.85) 80%,
|
|
||||||
rgba(0, 0, 0, 0) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.legendary:hover {
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
rgba(0, 0, 0, 0) 0%,
|
|
||||||
rgba(185, 129, 46, 0.85) 10%,
|
|
||||||
rgba(132, 99, 50, 0.85) 80%,
|
|
||||||
rgba(0, 0, 0, 0) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
@apply text-white;
|
|
||||||
@apply px-2;
|
|
||||||
padding-top: 0.85rem;
|
|
||||||
padding-bottom: 0.85rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
<script context="module">
|
||||||
|
export async function preload(page) {
|
||||||
|
const { id } = page.params;
|
||||||
|
const data = await import(`../../data/characterData/${id}.json`);
|
||||||
|
|
||||||
|
return { id, data };
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let id;
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import { mdiCircle, mdiContentSave, mdiMinus, mdiPencil, mdiPlus, mdiStar } from '@mdi/js';
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import Button from '../../components/Button.svelte';
|
||||||
|
import { getAccountPrefix } from '../../stores/account';
|
||||||
|
import { readSave } from '../../stores/saveManager';
|
||||||
|
import { characters } from '../../data/characters';
|
||||||
|
import { itemGroup } from '../../data/itemGroup';
|
||||||
|
|
||||||
|
import SkillCard from './_skillCard.svelte';
|
||||||
|
import PassiveSkillCard from './_passiveSkillCard.svelte';
|
||||||
|
|
||||||
|
let constellationDiv;
|
||||||
|
|
||||||
|
const numberFormat = Intl.NumberFormat('en', {
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const character = characters[id];
|
||||||
|
const bookId = character.material.book[0].id;
|
||||||
|
const book = itemGroup[bookId];
|
||||||
|
const materials = character.ascension[1].items;
|
||||||
|
|
||||||
|
let constellationCount = -1;
|
||||||
|
let manualCount = 0;
|
||||||
|
let editConstallation = false;
|
||||||
|
|
||||||
|
const showedIndex = [1, 20, 21, 41, 42, 52, 53, 63, 64, 74, 75, 85, 86, 96];
|
||||||
|
const level = [1, 20, 20, 40, 40, 50, 50, 60, 60, 70, 70, 80, 80, 90];
|
||||||
|
const ascen = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6];
|
||||||
|
|
||||||
|
function getConstellationCount() {
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
const data = readSave(`${prefix}characters`);
|
||||||
|
if (data !== null) {
|
||||||
|
const constellation = JSON.parse(data);
|
||||||
|
if (constellation[id]) {
|
||||||
|
constellationCount = constellationCount[id].default + constellationCount[id].wish - 1;
|
||||||
|
manualCount = constellationCount[id].manual;
|
||||||
|
} else {
|
||||||
|
constellationCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editConstellationCount(val) {
|
||||||
|
manualCount = Math.max(0, manualCount + val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConstellationCount() {
|
||||||
|
editConstallation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToView(view) {
|
||||||
|
view.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getConstellationCount();
|
||||||
|
});
|
||||||
|
|
||||||
|
$: constellationCountTotal = constellationCount + manualCount;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Paimon.moe</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Genshin Impact {character.name} build, guide, constellation, and skill information"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Genshin Impact {character.name} build, guide, constellation, and skill information"
|
||||||
|
/>
|
||||||
|
</svelte:head>
|
||||||
|
<div class="lg:ml-64 pt-20 lg:pt-8">
|
||||||
|
<div class="flex flex-col xl:flex-row items-start">
|
||||||
|
<img
|
||||||
|
class="character-image object-cover md:pl-8 self-center xl:self-auto"
|
||||||
|
src="/images/characters/full/{id}.png"
|
||||||
|
alt={character.name}
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col items-start mt-4 xl:mt-0 side-detail pt-4 xl:pt-0">
|
||||||
|
<div class="flex items-center px-4 md:px-8">
|
||||||
|
<h1 class="font-display font-black text-4xl md:text-5xl text-white mr-4 z-0">{character.name}</h1>
|
||||||
|
<img
|
||||||
|
class="h-10 mr-4 z-10 object-contain"
|
||||||
|
src="/images/elements/{character.element.id}.png"
|
||||||
|
alt={character.element.name}
|
||||||
|
/>
|
||||||
|
<div class="flex gap-1 {editConstallation ? 'flex-col' : ''} md:flex-row items-center">
|
||||||
|
{#if constellationCountTotal > -1}
|
||||||
|
<p class="text-3xl text-gray-200 bg-black bg-opacity-50 rounded-xl px-2 font-semibold">
|
||||||
|
C{constellationCountTotal}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{#if editConstallation}
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
<Button size="sm" on:click={() => editConstellationCount(1)}>
|
||||||
|
<Icon path={mdiPlus} />
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" on:click={() => editConstellationCount(-1)}>
|
||||||
|
<Icon path={mdiMinus} />
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" on:click={saveConstellationCount}>
|
||||||
|
<Icon path={mdiContentSave} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="ml-2 rounded-xl hover:bg-black hover:bg-opacity-25 cursor-pointer p-2"
|
||||||
|
on:click={() => {
|
||||||
|
editConstallation = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon path={mdiPencil} className="text-gray-400" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="{character.rarity === 5
|
||||||
|
? 'text-legendary-from'
|
||||||
|
: 'text-rare-from'} px-4 md:px-8 text-2xl flex items-center z-0 -mt-2 md:-mt-4"
|
||||||
|
>
|
||||||
|
<Icon path={mdiStar} />
|
||||||
|
<Icon path={mdiStar} />
|
||||||
|
<Icon path={mdiStar} />
|
||||||
|
<Icon path={mdiStar} />
|
||||||
|
{#if character.rarity === 5}
|
||||||
|
<Icon path={mdiStar} />
|
||||||
|
{/if}
|
||||||
|
<Icon path={mdiCircle} size={0.4} className="mx-2 mt-1" color="white" />
|
||||||
|
<p class="text-base text-white font-semibold mt-1">{character.weapon.name}</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-200 px-4 md:px-8">{data.description}</p>
|
||||||
|
<div class="flex flex-col md:flex-row mt-4 gap-4 px-4 md:px-8">
|
||||||
|
<div class="text-gray-200 rounded-xl border border-gray-200 border-opacity-25 p-4">
|
||||||
|
<p>Talent Book</p>
|
||||||
|
<div class="flex items-center mt-2">
|
||||||
|
<div class="mr-2 h-12 w-12 bg-background rounded-xl p-1">
|
||||||
|
<img src="/images/items/{bookId}.png" alt={book.name} class="h-full max-w-full object-contain" />
|
||||||
|
</div>
|
||||||
|
<p class="mb-1 font-semibold">{book.name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-200 rounded-xl border border-gray-200 border-opacity-25 p-4">
|
||||||
|
<p>Ascension Materials</p>
|
||||||
|
<div class="flex items-center mt-2">
|
||||||
|
{#each materials as material}
|
||||||
|
{#if material.item.id !== 'none'}
|
||||||
|
<div class="mr-2 h-12 w-12 bg-background rounded-xl p-1">
|
||||||
|
<img
|
||||||
|
src="/images/items/{material.item.id}.png"
|
||||||
|
alt={material.item.name}
|
||||||
|
title={material.item.name}
|
||||||
|
class="h-full max-w-full object-contain mx-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:px-4 mt-4 block overflow-x-auto whitespace-no-wrap w-screen md:w-auto">
|
||||||
|
<div class="px-4" style="width: min-content;">
|
||||||
|
<div class="table max-w-full rounded-xl border border-gray-200 border-opacity-25">
|
||||||
|
<table class="text-gray-200 w-full">
|
||||||
|
<tr>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2">
|
||||||
|
{$t('characters.asc')}
|
||||||
|
</td>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2">
|
||||||
|
{$t('characters.lvl')}
|
||||||
|
</td>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2">
|
||||||
|
{$t('characters.hp')}
|
||||||
|
</td>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2">
|
||||||
|
{$t('characters.atk')}
|
||||||
|
</td>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2">
|
||||||
|
{$t('characters.def')}
|
||||||
|
</td>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"
|
||||||
|
>{$t('characters.critRate')}
|
||||||
|
</td>
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"
|
||||||
|
>{$t('characters.critDamage')}
|
||||||
|
</td>
|
||||||
|
{#if data.statGrow !== 'critRate' && data.statGrow !== 'critDamage'}
|
||||||
|
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2">
|
||||||
|
{$t(`characters.${data.statGrow}`)}
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{#each showedIndex as index, i}
|
||||||
|
<tr>
|
||||||
|
{#if i % 2 === 0}
|
||||||
|
<td rowspan={2} class="text-center border-t border-gray-700 px-2">{ascen[i]}</td>
|
||||||
|
{/if}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">{level[i]}</td>
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">{Math.round(data.hp[index])}</td>
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">{Math.round(data.atk[index])}</td>
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">{Math.round(data.def[index])}</td>
|
||||||
|
{#if data.statGrow === 'critRate'}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">
|
||||||
|
{numberFormat.format(data.critRate[index] * 100)}%
|
||||||
|
</td>
|
||||||
|
{:else}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">5%</td>
|
||||||
|
{/if}
|
||||||
|
{#if data.statGrow === 'critDamage'}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">
|
||||||
|
{numberFormat.format(data.critDamage[index] * 100)}%
|
||||||
|
</td>
|
||||||
|
{:else}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">50%</td>
|
||||||
|
{/if}
|
||||||
|
{#if data.statGrow !== 'critRate' && data.statGrow !== 'critDamage'}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">
|
||||||
|
{numberFormat.format(data[data.statGrow][index] * 100)}%
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button className="mt-4 mx-4 md:mx-8" on:click={() => scrollToView(constellationDiv)}>
|
||||||
|
{$t('characters.constellations')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mt-4 text-white px-4 md:px-8">
|
||||||
|
<p class="font-black font-display text-2xl mt-4">{$t('characters.talents')}</p>
|
||||||
|
<SkillCard {id} image="talent_1" data={data.attack} withQuote={false} />
|
||||||
|
<SkillCard {id} image="talent_2" data={data.elementalSkill} withQuote={true} />
|
||||||
|
<SkillCard {id} image="talent_3" data={data.burst} withQuote={true} />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col text-white px-4 md:px-8">
|
||||||
|
<p class="font-black font-display text-2xl mt-4">{$t('characters.passiveTalents')}</p>
|
||||||
|
{#each data.passives as passive, i}
|
||||||
|
<PassiveSkillCard {id} image="talent_{i + 4}" data={passive} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col text-white px-4 md:px-8" id="constellations" bind:this={constellationDiv}>
|
||||||
|
<a href="/characters/{id}/#constellations" class="font-black font-display text-2xl mt-4"
|
||||||
|
>{$t('characters.constellations')}</a
|
||||||
|
>
|
||||||
|
{#each data.constellations as constellation, i}
|
||||||
|
<PassiveSkillCard
|
||||||
|
{id}
|
||||||
|
fade={constellationCountTotal > -1 && constellationCountTotal < i + 1}
|
||||||
|
image={`constellation_${i + 1}`}
|
||||||
|
data={constellation}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.character-image {
|
||||||
|
height: calc(100vh - 4rem);
|
||||||
|
max-height: 700px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-detail {
|
||||||
|
margin-top: -40vh;
|
||||||
|
background: linear-gradient(180deg, rgba(37, 41, 74, 0) 0%, rgba(37, 41, 74, 0.75) 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen xl {
|
||||||
|
.character-image {
|
||||||
|
max-width: 550px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-detail {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td:not(:last-child) {
|
||||||
|
@apply border-r;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
export let id;
|
||||||
|
export let image;
|
||||||
|
export let data;
|
||||||
|
export let fade = false;
|
||||||
|
|
||||||
|
const description = data.description.replace(/\\n/g, '<br/>').replace(/·/g, '- ');
|
||||||
|
const name = data.name;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="py-4 rounded-xl bg-item flex flex-col mb-4" style="{fade ? 'filter: grayscale(30%);' : ''}">
|
||||||
|
<div class="flex mb-2 items-start px-4">
|
||||||
|
<img src="/images/skills/{id}/{image}.png" alt={name} class="w-16 h-16 mr-4" />
|
||||||
|
<div>
|
||||||
|
<p class="font-black font-display text-xl">{name}</p>
|
||||||
|
<p class="skill-description">{@html description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,122 @@
|
||||||
|
<script>
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
export let id;
|
||||||
|
export let image;
|
||||||
|
export let data;
|
||||||
|
export let withQuote;
|
||||||
|
|
||||||
|
let iter = [...new Array(13)];
|
||||||
|
|
||||||
|
const lastIndex = withQuote ? data.description.indexOf('<i>') : data.description.length;
|
||||||
|
const description = data.description.substring(0, lastIndex).replace(/\\n/g, '<br/>').replace(/·/g, '- ');
|
||||||
|
const quote = data.description
|
||||||
|
.substring(lastIndex, data.description.length)
|
||||||
|
.replace('<i>', '')
|
||||||
|
.replace('</i>', '')
|
||||||
|
.replace(/\\n/g, '<br/>')
|
||||||
|
.replace(/·/g, '- ');
|
||||||
|
const name = data.name;
|
||||||
|
|
||||||
|
const numberFormat1Digit = Intl.NumberFormat('en', {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
const numberFormat2Digit = Intl.NumberFormat('en', {
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatter(type, number) {
|
||||||
|
switch (type) {
|
||||||
|
case 'i':
|
||||||
|
return Math.round(number);
|
||||||
|
case 'p':
|
||||||
|
return `${Math.round(number * 100)}%`;
|
||||||
|
case '1p':
|
||||||
|
return `${numberFormat1Digit.format(number * 100)}%`;
|
||||||
|
case '2p':
|
||||||
|
return `${numberFormat2Digit.format(number * 100)}%`;
|
||||||
|
case '1f':
|
||||||
|
return `${numberFormat1Digit.format(number)}`;
|
||||||
|
case '2f':
|
||||||
|
return `${numberFormat2Digit.format(number)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function format(str, args) {
|
||||||
|
// let formatted = str.replace(/(\/{)|(\+{)|\s[^+]+/g, (token) => ` ${token} `);
|
||||||
|
let formatted = str.replace(/[\+\/]{/g, (token) => ` ${token}`);
|
||||||
|
formatted = formatted.replace(/{[0-9]:\w+}/g, (text) => {
|
||||||
|
const splitted = text.substring(1, text.length - 1).split(':');
|
||||||
|
return formatter(splitted[1], args[splitted[0]]);
|
||||||
|
});
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="py-4 rounded-xl bg-item flex flex-col mb-4">
|
||||||
|
<div class="flex mb-2 items-center px-4">
|
||||||
|
<img src="/images/skills/{id}/{image}.png" alt={name} class="w-16 h-16 mr-4" />
|
||||||
|
<div>
|
||||||
|
<p class="font-black font-display text-xl">{name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="skill-description px-4">{@html description}</p>
|
||||||
|
{#if withQuote}
|
||||||
|
<p class="text-sm text-gray-400 italic mt-2 px-4">{@html quote}</p>
|
||||||
|
{/if}
|
||||||
|
<div class="mt-4 block overflow-x-auto">
|
||||||
|
<div class="px-4" style="width: fit-content;">
|
||||||
|
<div class="table max-w-full rounded-xl border border-gray-200 border-opacity-25">
|
||||||
|
<table class="text-gray-200 text-sm">
|
||||||
|
<tr>
|
||||||
|
<td class="border-gray-700 px-2">{$t('characters.lvl')}</td>
|
||||||
|
{#each iter as _, i}
|
||||||
|
<td class="text-center border-gray-700 px-2">{i + 1}</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{#each data.skillLabels as label, i}
|
||||||
|
<tr>
|
||||||
|
<td class="border-t border-gray-700 px-2" style="min-width: 150px;">{label}</td>
|
||||||
|
{#each data.skillStats[i].slice(0, 13) as stat}
|
||||||
|
<td class="text-center border-t border-gray-700 px-2">
|
||||||
|
{@html format(data.skillStatsLabels[i], stat)}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td:not(:last-child) {
|
||||||
|
@apply border-r;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(span.color) {
|
||||||
|
@apply text-primary font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(p.skill-description > br) {
|
||||||
|
line-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen lg {
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
@apply rounded-xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,252 @@
|
||||||
|
<script>
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { mdiStar, mdiViewGrid, mdiViewList } from '@mdi/js';
|
||||||
|
|
||||||
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import TableHeader from '../../components/Table/TableHeader.svelte';
|
||||||
|
|
||||||
|
import { characters } from '../../data/characters';
|
||||||
|
import { getAccountPrefix } from '../../stores/account';
|
||||||
|
import { readSave } from '../../stores/saveManager';
|
||||||
|
|
||||||
|
let sortBy = '';
|
||||||
|
let sortOrder = false;
|
||||||
|
let type = 'grid';
|
||||||
|
let showConstellation = false;
|
||||||
|
let constellation = {};
|
||||||
|
|
||||||
|
$: chars = Object.entries(characters).sort((a, b) => {
|
||||||
|
switch (sortBy) {
|
||||||
|
case 'name':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].name.localeCompare(b[1].name);
|
||||||
|
} else {
|
||||||
|
return b[1].name.localeCompare(a[1].name);
|
||||||
|
}
|
||||||
|
case 'element':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].element.name.localeCompare(b[1].element.name);
|
||||||
|
} else {
|
||||||
|
return b[1].element.name.localeCompare(a[1].element.name);
|
||||||
|
}
|
||||||
|
case 'rarity':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].rarity - b[1].rarity;
|
||||||
|
} else {
|
||||||
|
return b[1].rarity - a[1].rarity;
|
||||||
|
}
|
||||||
|
case 'weapon':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].weapon.name.localeCompare(b[1].weapon.name);
|
||||||
|
} else {
|
||||||
|
return b[1].weapon.name.localeCompare(a[1].weapon.name);
|
||||||
|
}
|
||||||
|
case 'hp':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].stats.hp - b[1].stats.hp;
|
||||||
|
} else {
|
||||||
|
return b[1].stats.hp - a[1].stats.hp;
|
||||||
|
}
|
||||||
|
case 'atk':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].stats.atk - b[1].stats.atk;
|
||||||
|
} else {
|
||||||
|
return b[1].stats.atk - a[1].stats.atk;
|
||||||
|
}
|
||||||
|
case 'def':
|
||||||
|
if (sortOrder) {
|
||||||
|
return a[1].stats.def - b[1].stats.def;
|
||||||
|
} else {
|
||||||
|
return b[1].stats.def - a[1].stats.def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function sort(by) {
|
||||||
|
if (sortBy === by) {
|
||||||
|
sortOrder = !sortOrder;
|
||||||
|
} else {
|
||||||
|
sortBy = by;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConstellation() {
|
||||||
|
const prefix = getAccountPrefix();
|
||||||
|
const data = readSave(`${prefix}characters`);
|
||||||
|
if (data !== null) {
|
||||||
|
constellation = JSON.parse(data);
|
||||||
|
showConstellation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getConstellation();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Paimon.moe</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Your best Genshin Impact companion! Help you plan what to farm with ascension calculator and database. Also track your progress with todo and wish counter."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Your best Genshin Impact companion! Help you plan what to farm with ascension calculator and database. Also track your progress with todo and wish counter."
|
||||||
|
/>
|
||||||
|
</svelte:head>
|
||||||
|
<div class="lg:ml-64 pt-20 lg:pt-8">
|
||||||
|
<div class="flex items-center px-4 md:px-8">
|
||||||
|
<h1 class="font-display font-black text-4xl md:text-5xl text-white mr-4">{$t('characters.title')}</h1>
|
||||||
|
<div class="flex text-white" style="height: fit-content;">
|
||||||
|
<button
|
||||||
|
class="{type === 'grid' ? 'bg-background' : 'bg-item'} p-2 rounded-l-xl cursor-pointer hover:bg-opacity-75"
|
||||||
|
on:click={() => {
|
||||||
|
type = 'grid';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon path={mdiViewGrid} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="{type === 'table'
|
||||||
|
? 'bg-background'
|
||||||
|
: 'bg-item'} bg-background p-2 rounded-r-xl cursor-pointer hover:bg-opacity-75"
|
||||||
|
on:click={() => {
|
||||||
|
type = 'table';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon path={mdiViewList} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if type === 'grid'}
|
||||||
|
<div class="px-4 md:pl-6 md:pr-4 flex flex-wrap max-w-screen-xl mt-2">
|
||||||
|
{#each chars as [id, char] (id)}
|
||||||
|
<a
|
||||||
|
href="/characters/{id}"
|
||||||
|
class="m-2 cell relative cursor-pointer transition duration-100 hover:opacity-100 hover:shadow-xl rounded-xl {!showConstellation ||
|
||||||
|
constellation[id]
|
||||||
|
? ''
|
||||||
|
: 'opacity-50'}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-full rounded-t-xl bg-opacity-50 overflow-hidden {char.rarity === 5
|
||||||
|
? 'bg-legendary-from'
|
||||||
|
: 'bg-rare-from'}"
|
||||||
|
>
|
||||||
|
<img class="w-full h-full" src={`/images/characters/${id}.png`} alt={char.name} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute top-0 right-0 bg-black bg-opacity-75 rounded-full flex items-center shadow-md"
|
||||||
|
style="padding: 4px; margin: -10px;"
|
||||||
|
>
|
||||||
|
{#if constellation[id]}
|
||||||
|
<span class="mx-1 text-white text-xs font-semibold">
|
||||||
|
C{Math.min(constellation[id] - 1, 6)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<img class="w-4 h-4" src={`/images/elements/${char.element.id}.png`} alt={char.element.name} />
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-item rounded-b-xl overflow-hidden">
|
||||||
|
<p class="text-white text-sm p-1 text-center whitespace-no-wrap">{char.name}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="text-gray-400 px-4 md:px-8 font-medium pb-2 mt-4">
|
||||||
|
※ {$t('characters.subtitle')}
|
||||||
|
</p>
|
||||||
|
<div class="block overflow-x-auto whitespace-no-wrap pb-8">
|
||||||
|
<div class="px-4 md:px-8 table">
|
||||||
|
<table class="w-full block p-4 bg-item rounded-xl">
|
||||||
|
<thead>
|
||||||
|
<th style="min-width: 4rem;" />
|
||||||
|
<TableHeader on:click={() => sort('name')} sort={sortBy === 'name'} order={sortOrder}
|
||||||
|
>{$t('characters.name')}</TableHeader
|
||||||
|
>
|
||||||
|
<TableHeader on:click={() => sort('element')} sort={sortBy === 'element'} order={sortOrder} align="center">
|
||||||
|
{$t('characters.element')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader on:click={() => sort('rarity')} sort={sortBy === 'rarity'} order={sortOrder} align="center">
|
||||||
|
{$t('characters.rarity')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader on:click={() => sort('weapon')} sort={sortBy === 'weapon'} order={sortOrder} align="center">
|
||||||
|
{$t('characters.weapon')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader on:click={() => sort('hp')} sort={sortBy === 'hp'} order={sortOrder} align="center">
|
||||||
|
{$t('characters.hp')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader on:click={() => sort('atk')} sort={sortBy === 'atk'} order={sortOrder} align="center">
|
||||||
|
{$t('characters.atk')}
|
||||||
|
</TableHeader>
|
||||||
|
<TableHeader on:click={() => sort('def')} sort={sortBy === 'def'} order={sortOrder} align="center">
|
||||||
|
{$t('characters.def')}
|
||||||
|
</TableHeader>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each chars as [id, char] (id)}
|
||||||
|
<tr class={`rounded cursor-pointer ${char.rarity === 4 ? 'rare' : 'legendary'}`}>
|
||||||
|
<td class="rarity w-16 sticky" style="padding: 0; left: 0px;">
|
||||||
|
<img class="w-12 h-12 rounded-full" src={`/images/characters/${id}.png`} alt={char.name} />
|
||||||
|
</td>
|
||||||
|
<td>{char.name}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<img class="w-8 h-8 inline" src={`/images/elements/${char.element.id}.png`} alt={char.element.name} />
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<Icon color={char.rarity === 5 ? '#B9812E' : '#AD76B0'} path={mdiStar} />
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<img class="w-8 h-8 inline" src={`/images/weapons/${char.weapon.id}.png`} alt={char.weapon.name} />
|
||||||
|
</td>
|
||||||
|
<td class="text-center">{char.stats.hp}</td>
|
||||||
|
<td class="text-center">{char.stats.atk}</td>
|
||||||
|
<td class="text-center">{char.stats.def}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cell {
|
||||||
|
width: calc(33.33333% - 1rem);
|
||||||
|
|
||||||
|
@screen md {
|
||||||
|
@apply w-24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.rare:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(173, 118, 176, 0.85) 10%,
|
||||||
|
rgba(102, 86, 128, 0.85) 80%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.legendary:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(185, 129, 46, 0.85) 10%,
|
||||||
|
rgba(132, 99, 50, 0.85) 80%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
@apply text-white;
|
||||||
|
@apply px-2;
|
||||||
|
padding-top: 0.85rem;
|
||||||
|
padding-bottom: 0.85rem;
|
||||||
|
}
|
||||||
|
</style>
|
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 131 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 211 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 235 KiB |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 192 KiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 239 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 265 KiB |
After Width: | Height: | Size: 191 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 2.6 KiB |