Add TCG deck builder
|
@ -27,8 +27,8 @@
|
|||
"svelte": "^3.17.3",
|
||||
"svelte-i18n": "^3.4.0",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"svelte-simple-modal": "^0.6.1",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"svelte-simple-modal": "^1.4.5",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"vite": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ specifiers:
|
|||
svelte: ^3.17.3
|
||||
svelte-i18n: ^3.4.0
|
||||
svelte-preprocess: ^4.10.7
|
||||
svelte-simple-modal: ^0.6.1
|
||||
tailwindcss: ^3.1.6
|
||||
svelte-simple-modal: ^1.4.5
|
||||
tailwindcss: ^3.2.4
|
||||
vite: ^3.0.2
|
||||
|
||||
devDependencies:
|
||||
|
@ -40,8 +40,8 @@ devDependencies:
|
|||
svelte: 3.49.0
|
||||
svelte-i18n: 3.4.0_svelte@3.49.0
|
||||
svelte-preprocess: 4.10.7_nxvsp6sjiltnatqa6jdm4mr6zu
|
||||
svelte-simple-modal: 0.6.1_svelte@3.49.0
|
||||
tailwindcss: 3.1.6_postcss@8.4.14
|
||||
svelte-simple-modal: 1.4.5_svelte@3.49.0
|
||||
tailwindcss: 3.2.4_postcss@8.4.14
|
||||
vite: 3.0.2
|
||||
|
||||
packages:
|
||||
|
@ -785,8 +785,8 @@ packages:
|
|||
'@fast-csv/parse': 4.3.6
|
||||
dev: true
|
||||
|
||||
/fast-glob/3.2.11:
|
||||
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
|
||||
/fast-glob/3.2.12:
|
||||
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
|
@ -1228,6 +1228,16 @@ packages:
|
|||
postcss-selector-parser: 6.0.10
|
||||
dev: true
|
||||
|
||||
/postcss-nested/6.0.0_postcss@8.4.14:
|
||||
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
|
||||
engines: {node: '>=12.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.2.14
|
||||
dependencies:
|
||||
postcss: 8.4.14
|
||||
postcss-selector-parser: 6.0.10
|
||||
dev: true
|
||||
|
||||
/postcss-selector-parser/6.0.10:
|
||||
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -1513,10 +1523,10 @@ packages:
|
|||
svelte: 3.49.0
|
||||
dev: true
|
||||
|
||||
/svelte-simple-modal/0.6.1_svelte@3.49.0:
|
||||
resolution: {integrity: sha512-GJGYj+jymzuar105fwkZ73dtcSFCordpbHqt53iE1N1GdqhvEmSs24idRzyIcO7TrTD/V/287X1icFXp88RQHQ==}
|
||||
/svelte-simple-modal/1.4.5_svelte@3.49.0:
|
||||
resolution: {integrity: sha512-KJMQaU6GD/WnIfURIKwS4R4aB6s2DDG6NiYRJkUFRVbKPRvqcohb7Z5KFLyA5UsqdPvkEdFZ0UxqjNw7Lfqgtg==}
|
||||
peerDependencies:
|
||||
svelte: ^3.18.2
|
||||
svelte: ^3.31.2
|
||||
dependencies:
|
||||
svelte: 3.49.0
|
||||
dev: true
|
||||
|
@ -1526,8 +1536,8 @@ packages:
|
|||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/tailwindcss/3.1.6_postcss@8.4.14:
|
||||
resolution: {integrity: sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==}
|
||||
/tailwindcss/3.2.4_postcss@8.4.14:
|
||||
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
@ -1539,10 +1549,11 @@ packages:
|
|||
detective: 5.2.1
|
||||
didyoumean: 1.2.2
|
||||
dlv: 1.1.3
|
||||
fast-glob: 3.2.11
|
||||
fast-glob: 3.2.12
|
||||
glob-parent: 6.0.2
|
||||
is-glob: 4.0.3
|
||||
lilconfig: 2.0.6
|
||||
micromatch: 4.0.5
|
||||
normalize-path: 3.0.0
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.0.0
|
||||
|
@ -1550,7 +1561,7 @@ packages:
|
|||
postcss-import: 14.1.0_postcss@8.4.14
|
||||
postcss-js: 4.0.0_postcss@8.4.14
|
||||
postcss-load-config: 3.1.4_postcss@8.4.14
|
||||
postcss-nested: 5.0.6_postcss@8.4.14
|
||||
postcss-nested: 6.0.0_postcss@8.4.14
|
||||
postcss-selector-parser: 6.0.10
|
||||
postcss-value-parser: 4.2.0
|
||||
quick-lru: 5.1.1
|
||||
|
|
|
@ -132,6 +132,13 @@
|
|||
label={$t('sidebar.timeline')}
|
||||
href="/timeline"
|
||||
/>
|
||||
<SidebarItem
|
||||
on:clicked={close}
|
||||
active={segment === 'tcg'}
|
||||
image="/images/tcg.png"
|
||||
label={$t('sidebar.tcg')}
|
||||
href="/tcg"
|
||||
/>
|
||||
<SidebarItem
|
||||
on:clicked={close}
|
||||
active={segment === 'settings'}
|
||||
|
|
3593
src/data/tcg/de.json
Normal file
3593
src/data/tcg/en.json
Normal file
3587
src/data/tcg/es.json
Normal file
3587
src/data/tcg/fr.json
Normal file
3593
src/data/tcg/id.json
Normal file
3593
src/data/tcg/it.json
Normal file
3593
src/data/tcg/ja.json
Normal file
3593
src/data/tcg/ko.json
Normal file
3593
src/data/tcg/pt.json
Normal file
3593
src/data/tcg/ru.json
Normal file
42
src/data/tcg/tags/de.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Einzigartig",
|
||||
"slowly": "Kampfaktion",
|
||||
"attack": "Aktion unmöglich",
|
||||
"freezing": "Immunität gegen Gefroren",
|
||||
"control": "Immunität gegen Kontrolle",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri’ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Monster",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "Kein Elementartyp",
|
||||
"catalyst": "Katalysator",
|
||||
"bow": "Bogen",
|
||||
"claymore": "Zweihänder",
|
||||
"pole": "Stangenwaffe",
|
||||
"sword": "Einhänder",
|
||||
"cryo": "Kryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Elektro",
|
||||
"anemo": "Anemo",
|
||||
"geo": "Geo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Waffe",
|
||||
"artifact": "Artefakt",
|
||||
"talent": "Talent",
|
||||
"sheild": "Schild",
|
||||
"place": "Spielfeld",
|
||||
"ally": "Gefährte",
|
||||
"item": "Objekte",
|
||||
"resonance": "Elementarer Einklang",
|
||||
"food": "Gericht",
|
||||
"produce": "Dendro-Objekt"
|
||||
}
|
42
src/data/tcg/tags/en.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Only One",
|
||||
"slowly": "Combat Action",
|
||||
"attack": "Unable to Act",
|
||||
"freezing": "Immune to Frozen",
|
||||
"control": "Immune to Disables",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Monster",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "No Elemental Type",
|
||||
"catalyst": "Catalyst",
|
||||
"bow": "Bow",
|
||||
"claymore": "Claymore",
|
||||
"pole": "Polearm",
|
||||
"sword": "Sword",
|
||||
"cryo": "Cryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Electro",
|
||||
"anemo": "Anemo",
|
||||
"geo": "Geo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Weapon",
|
||||
"artifact": "Artifact",
|
||||
"talent": "Talent",
|
||||
"sheild": "Shield",
|
||||
"place": "Location",
|
||||
"ally": "Companion",
|
||||
"item": "Item",
|
||||
"resonance": "Elemental Resonance",
|
||||
"food": "Food",
|
||||
"produce": "Dendro Construct"
|
||||
}
|
42
src/data/tcg/tags/es.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Único",
|
||||
"slowly": "Acción de combate",
|
||||
"attack": "Acción imposible",
|
||||
"freezing": "Inmune a congelado",
|
||||
"control": "Inmune a pérdida de control",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Monstruo",
|
||||
"kairagi": "Samurái Kairagi",
|
||||
"none": "Sin tipo elemental",
|
||||
"catalyst": "Catalizador",
|
||||
"bow": "Arco",
|
||||
"claymore": "Mandoble",
|
||||
"pole": "Lanza",
|
||||
"sword": "Espada ligera",
|
||||
"cryo": "Cryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Electro",
|
||||
"anemo": "Anemo",
|
||||
"geo": "Geo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Arma",
|
||||
"artifact": "Artefacto",
|
||||
"talent": "Talento",
|
||||
"sheild": "Escudo",
|
||||
"place": "Ubicación",
|
||||
"ally": "Acompañante",
|
||||
"item": "Objeto",
|
||||
"resonance": "Consonancia elemental",
|
||||
"food": "Comida",
|
||||
"produce": "Creación Dendro"
|
||||
}
|
42
src/data/tcg/tags/fr.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Unique",
|
||||
"slowly": "Action de combat",
|
||||
"attack": "Aucune action possible",
|
||||
"freezing": "Anti-gel",
|
||||
"control": "Anti-contrôle",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Brutocollinus",
|
||||
"monster": "Monstre",
|
||||
"kairagi": "Oni des mers",
|
||||
"none": "Sans élément",
|
||||
"catalyst": "Catalyseur",
|
||||
"bow": "Arc",
|
||||
"claymore": "Épée à deux mains",
|
||||
"pole": "Arme d'hast",
|
||||
"sword": "Épée à une main",
|
||||
"cryo": "Cryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Électro",
|
||||
"anemo": "Anémo",
|
||||
"geo": "Géo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Arme",
|
||||
"artifact": "Artéfact",
|
||||
"talent": "Aptitude",
|
||||
"sheild": "Bouclier",
|
||||
"place": "Terrain",
|
||||
"ally": "Compagnon",
|
||||
"item": "Objet",
|
||||
"resonance": "Résonance élémentaire",
|
||||
"food": "Plat",
|
||||
"produce": "Construction Dendro"
|
||||
}
|
42
src/data/tcg/tags/id.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Satu-satunya",
|
||||
"slowly": "Aksi Tempur",
|
||||
"attack": "Tidak dapat beraksi",
|
||||
"freezing": "Kebal terhadap Frozen",
|
||||
"control": "Kebal terhadap kendali",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Monster",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "Tidak ada tipe elemen",
|
||||
"catalyst": "Catalyst",
|
||||
"bow": "Bow",
|
||||
"claymore": "Claymore",
|
||||
"pole": "Polearm",
|
||||
"sword": "Sword",
|
||||
"cryo": "Cryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Electro",
|
||||
"anemo": "Anemo",
|
||||
"geo": "Geo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Senjata",
|
||||
"artifact": "Artefak",
|
||||
"talent": "Talenta",
|
||||
"sheild": "Perisai",
|
||||
"place": "Lokasi",
|
||||
"ally": "Teman",
|
||||
"item": "Item",
|
||||
"resonance": "Resonansi Elemental",
|
||||
"food": "Masakan",
|
||||
"produce": "Dendro Construct"
|
||||
}
|
42
src/data/tcg/tags/it.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Unico",
|
||||
"slowly": "Azione di combattimento",
|
||||
"attack": "Impossibile agire",
|
||||
"freezing": "Immunità a Congelamento",
|
||||
"control": "Immunità a Controllo",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "I Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Mostro",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "Nessun tipo elementale",
|
||||
"catalyst": "Catalizzatore",
|
||||
"bow": "Arco",
|
||||
"claymore": "Claymore",
|
||||
"pole": "Alabarda",
|
||||
"sword": "Spada",
|
||||
"cryo": "Cryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Electro",
|
||||
"anemo": "Anemo",
|
||||
"geo": "Geo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Arma",
|
||||
"artifact": "Manufatto",
|
||||
"talent": "Talento",
|
||||
"sheild": "Scudo",
|
||||
"place": "Posizione",
|
||||
"ally": "Compagno",
|
||||
"item": "Oggetto",
|
||||
"resonance": "Risonanza elementale",
|
||||
"food": "Cibo",
|
||||
"produce": "Costrutto di Dendro"
|
||||
}
|
42
src/data/tcg/tags/ja.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "唯一",
|
||||
"slowly": "戦闘アクション",
|
||||
"attack": "行動不能",
|
||||
"freezing": "凍結無効",
|
||||
"control": "行動妨害無効",
|
||||
"mondstadt": "モンド",
|
||||
"liyue": "璃月",
|
||||
"inazuma": "稲妻",
|
||||
"sumeru": "スメール",
|
||||
"fontaine": "フォンテーヌ",
|
||||
"natlan": "ナタ",
|
||||
"snezhnaya": "スネージナヤ",
|
||||
"khaenriah": "カーンルイア",
|
||||
"fatui": "ファデュイ",
|
||||
"hilichurl": "ヒルチャール",
|
||||
"monster": "魔物",
|
||||
"kairagi": "海乱鬼",
|
||||
"none": "元素タイプなし",
|
||||
"catalyst": "法器",
|
||||
"bow": "弓",
|
||||
"claymore": "両手剣",
|
||||
"pole": "長柄武器",
|
||||
"sword": "片手剣",
|
||||
"cryo": "氷元素",
|
||||
"hydro": "水元素",
|
||||
"pyro": "炎元素",
|
||||
"electro": "雷元素",
|
||||
"anemo": "風元素",
|
||||
"geo": "岩元素",
|
||||
"dendro": "草元素",
|
||||
"weapon": "武器",
|
||||
"artifact": "聖遺物",
|
||||
"talent": "天賦",
|
||||
"sheild": "シールド",
|
||||
"place": "フィールド",
|
||||
"ally": "仲間",
|
||||
"item": "アイテム",
|
||||
"resonance": "元素共鳴",
|
||||
"food": "料理",
|
||||
"produce": "草元素の産物"
|
||||
}
|
42
src/data/tcg/tags/ko.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "유일",
|
||||
"slowly": "전투 행동",
|
||||
"attack": "행동 불가",
|
||||
"freezing": "빙결 면역",
|
||||
"control": "제어 면역",
|
||||
"mondstadt": "몬드",
|
||||
"liyue": "리월",
|
||||
"inazuma": "이나즈마",
|
||||
"sumeru": "수메르",
|
||||
"fontaine": "폰타인",
|
||||
"natlan": "나타",
|
||||
"snezhnaya": "스네즈나야",
|
||||
"khaenriah": "켄리아",
|
||||
"fatui": "우인단",
|
||||
"hilichurl": "츄츄족",
|
||||
"monster": "마물",
|
||||
"kairagi": "해란귀",
|
||||
"none": "원소 타입 없음",
|
||||
"catalyst": "법구",
|
||||
"bow": "활",
|
||||
"claymore": "양손검",
|
||||
"pole": "장병기",
|
||||
"sword": "한손검",
|
||||
"cryo": "얼음 원소",
|
||||
"hydro": "물 원소",
|
||||
"pyro": "불 원소",
|
||||
"electro": "번개 원소",
|
||||
"anemo": "바람 원소",
|
||||
"geo": "바위 원소",
|
||||
"dendro": "풀 원소",
|
||||
"weapon": "무기",
|
||||
"artifact": "성유물",
|
||||
"talent": "특성",
|
||||
"sheild": "보호막",
|
||||
"place": "필드",
|
||||
"ally": "동료",
|
||||
"item": "아이템",
|
||||
"resonance": "원소 공명",
|
||||
"food": "요리",
|
||||
"produce": "풀 원소 산물"
|
||||
}
|
42
src/data/tcg/tags/pt.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Único",
|
||||
"slowly": "Ação de Combate",
|
||||
"attack": "Não pode fazer ações",
|
||||
"freezing": "Imunidade a Congelado",
|
||||
"control": "Imunidade a Controle",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Monstro",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "Sem Tipo Elemental",
|
||||
"catalyst": "Catalisador",
|
||||
"bow": "Arco",
|
||||
"claymore": "Espadão",
|
||||
"pole": "Lança",
|
||||
"sword": "Espada",
|
||||
"cryo": "Cryo",
|
||||
"hydro": "Hydro",
|
||||
"pyro": "Pyro",
|
||||
"electro": "Electro",
|
||||
"anemo": "Anemo",
|
||||
"geo": "Geo",
|
||||
"dendro": "Dendro",
|
||||
"weapon": "Arma",
|
||||
"artifact": "Artefato",
|
||||
"talent": "Talento",
|
||||
"sheild": "Escudo",
|
||||
"place": "Campo",
|
||||
"ally": "Companheiro",
|
||||
"item": "Ítem",
|
||||
"resonance": "Ressonância Elemental",
|
||||
"food": "Prato",
|
||||
"produce": "Produto Dendro"
|
||||
}
|
42
src/data/tcg/tags/ru.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Уникальный",
|
||||
"slowly": "Боевое действие",
|
||||
"attack": "Невозможно действовать",
|
||||
"freezing": "Иммунитет к заморозке",
|
||||
"control": "Иммунитет к контролю",
|
||||
"mondstadt": "Мондштадт",
|
||||
"liyue": "Ли Юэ",
|
||||
"inazuma": "Инадзума",
|
||||
"sumeru": "Сумеру",
|
||||
"fontaine": "Фонтейн",
|
||||
"natlan": "Натлан",
|
||||
"snezhnaya": "Снежная",
|
||||
"khaenriah": "Каэнри'ах",
|
||||
"fatui": "Фатуи",
|
||||
"hilichurl": "Хиличурл",
|
||||
"monster": "Монстр",
|
||||
"kairagi": "Кайраги",
|
||||
"none": "Без элемента",
|
||||
"catalyst": "Катализатор",
|
||||
"bow": "Стрелковое",
|
||||
"claymore": "Двуручное",
|
||||
"pole": "Древковое",
|
||||
"sword": "Одноручное",
|
||||
"cryo": "Крио",
|
||||
"hydro": "Гидро",
|
||||
"pyro": "Пиро",
|
||||
"electro": "Электро",
|
||||
"anemo": "Анемо",
|
||||
"geo": "Гео",
|
||||
"dendro": "Дендро",
|
||||
"weapon": "Оружие",
|
||||
"artifact": "Артефакт",
|
||||
"talent": "Талант",
|
||||
"sheild": "Щит",
|
||||
"place": "Место",
|
||||
"ally": "Друзья",
|
||||
"item": "Предмет",
|
||||
"resonance": "Элементальный резонанс",
|
||||
"food": "Блюдо",
|
||||
"produce": "Дендро конструкция"
|
||||
}
|
42
src/data/tcg/tags/th.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "หนึ่งเดียว",
|
||||
"slowly": "ดำเนินการต่อสู้",
|
||||
"attack": "ไม่สามารถดำเนินการได้",
|
||||
"freezing": "ต้านทานการแช่แข็ง",
|
||||
"control": "ต้านทานการถูกควบคุม",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "มอนสเตอร์",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "ไม่มีธาตุ",
|
||||
"catalyst": "สื่อเวท",
|
||||
"bow": "ธนู",
|
||||
"claymore": "ดาบใหญ่",
|
||||
"pole": "หอก",
|
||||
"sword": "ดาบ",
|
||||
"cryo": "ธาตุน้ำแข็ง",
|
||||
"hydro": "ธาตุน้ำ",
|
||||
"pyro": "ธาตุไฟ",
|
||||
"electro": "ธาตุไฟฟ้า",
|
||||
"anemo": "ธาตุลม",
|
||||
"geo": "ธาตุหิน",
|
||||
"dendro": "ธาตุไม้",
|
||||
"weapon": "อาวุธ",
|
||||
"artifact": "อาร์ติแฟกต์",
|
||||
"talent": "พรสวรรค์",
|
||||
"sheild": "โล่ป้องกัน",
|
||||
"place": "สถานที่",
|
||||
"ally": "เพื่อน",
|
||||
"item": "ไอเทม",
|
||||
"resonance": "การสั่นพ้องของธาตุ",
|
||||
"food": "อาหาร",
|
||||
"produce": "วัตถุธาตุไม้"
|
||||
}
|
42
src/data/tcg/tags/tr.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Tek",
|
||||
"slowly": "Savaş Hamlesi",
|
||||
"attack": "Hamle Yapılamıyor",
|
||||
"freezing": "Donma Bağışıklığı",
|
||||
"control": "Kontrol Bağışıklığı",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Dağ Yabanisi",
|
||||
"monster": "Canavar",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "Element Türü Yok",
|
||||
"catalyst": "Katalizör",
|
||||
"bow": "Yay",
|
||||
"claymore": "Çift Elli Kılıç",
|
||||
"pole": "Mızrak",
|
||||
"sword": "Kılıç",
|
||||
"cryo": "Buz",
|
||||
"hydro": "Su",
|
||||
"pyro": "Ateş",
|
||||
"electro": "Elektrik",
|
||||
"anemo": "Rüzgar",
|
||||
"geo": "Toprak",
|
||||
"dendro": "Doğa",
|
||||
"weapon": "Silah",
|
||||
"artifact": "Yadigar",
|
||||
"talent": "Yetenek",
|
||||
"sheild": "Kalkan",
|
||||
"place": "Konum",
|
||||
"ally": "Ortak",
|
||||
"item": "Eşya",
|
||||
"resonance": "Element Rezonansı",
|
||||
"food": "Yiyecek",
|
||||
"produce": "Doğa Yapısı"
|
||||
}
|
42
src/data/tcg/tags/tw.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "唯一",
|
||||
"slowly": "戰鬥行動",
|
||||
"attack": "無法行動",
|
||||
"freezing": "免疫凍結",
|
||||
"control": "免疫控制",
|
||||
"mondstadt": "蒙德",
|
||||
"liyue": "璃月",
|
||||
"inazuma": "稻妻",
|
||||
"sumeru": "須彌",
|
||||
"fontaine": "楓丹",
|
||||
"natlan": "納塔",
|
||||
"snezhnaya": "至冬",
|
||||
"khaenriah": "坎瑞亞",
|
||||
"fatui": "愚人眾",
|
||||
"hilichurl": "丘丘人",
|
||||
"monster": "魔物",
|
||||
"kairagi": "海亂鬼",
|
||||
"none": "無元素類型",
|
||||
"catalyst": "法器",
|
||||
"bow": "弓",
|
||||
"claymore": "雙手劍",
|
||||
"pole": "長柄武器",
|
||||
"sword": "單手劍",
|
||||
"cryo": "冰元素",
|
||||
"hydro": "水元素",
|
||||
"pyro": "火元素",
|
||||
"electro": "雷元素",
|
||||
"anemo": "風元素",
|
||||
"geo": "岩元素",
|
||||
"dendro": "草元素",
|
||||
"weapon": "武器",
|
||||
"artifact": "聖遺物",
|
||||
"talent": "天賦",
|
||||
"sheild": "護盾",
|
||||
"place": "場地",
|
||||
"ally": "夥伴",
|
||||
"item": "道具",
|
||||
"resonance": "元素共鳴",
|
||||
"food": "料理",
|
||||
"produce": "草元素產物"
|
||||
}
|
42
src/data/tcg/tags/vi.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "Duy Nhất",
|
||||
"slowly": "Hành Động Chiến Đấu",
|
||||
"attack": "Không thể hành động",
|
||||
"freezing": "Miễn Dịch Đóng Băng",
|
||||
"control": "Miễn Dịch Khống Chế",
|
||||
"mondstadt": "Mondstadt",
|
||||
"liyue": "Liyue",
|
||||
"inazuma": "Inazuma",
|
||||
"sumeru": "Sumeru",
|
||||
"fontaine": "Fontaine",
|
||||
"natlan": "Natlan",
|
||||
"snezhnaya": "Snezhnaya",
|
||||
"khaenriah": "Khaenri'ah",
|
||||
"fatui": "Fatui",
|
||||
"hilichurl": "Hilichurl",
|
||||
"monster": "Ma Vật",
|
||||
"kairagi": "Kairagi",
|
||||
"none": "Không Nguyên Tố",
|
||||
"catalyst": "Pháp Khí",
|
||||
"bow": "Cung",
|
||||
"claymore": "Trọng Kiếm",
|
||||
"pole": "Vũ Khí Cán Dài",
|
||||
"sword": "Kiếm Đơn",
|
||||
"cryo": "Nguyên Tố Băng",
|
||||
"hydro": "Nguyên Tố Thủy",
|
||||
"pyro": "Nguyên Tố Hỏa",
|
||||
"electro": "Nguyên Tố Lôi",
|
||||
"anemo": "Nguyên Tố Phong",
|
||||
"geo": "Nguyên Tố Nham",
|
||||
"dendro": "Nguyên Tố Thảo",
|
||||
"weapon": "Vũ Khí",
|
||||
"artifact": "Thánh Di Vật",
|
||||
"talent": "Thiên Phú",
|
||||
"sheild": "Khiên",
|
||||
"place": "Địa Danh",
|
||||
"ally": "Đồng Đội",
|
||||
"item": "Đạo Cụ",
|
||||
"resonance": "Cộng Hưởng Nguyên Tố",
|
||||
"food": "Thức Ăn",
|
||||
"produce": "Tạo Vật Thảo"
|
||||
}
|
42
src/data/tcg/tags/zh.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"unique": "唯一",
|
||||
"slowly": "战斗行动",
|
||||
"attack": "无法行动",
|
||||
"freezing": "免疫冻结",
|
||||
"control": "免疫控制",
|
||||
"mondstadt": "蒙德",
|
||||
"liyue": "璃月",
|
||||
"inazuma": "稻妻",
|
||||
"sumeru": "须弥",
|
||||
"fontaine": "枫丹",
|
||||
"natlan": "纳塔",
|
||||
"snezhnaya": "至冬",
|
||||
"khaenriah": "坎瑞亚",
|
||||
"fatui": "愚人众",
|
||||
"hilichurl": "丘丘人",
|
||||
"monster": "魔物",
|
||||
"kairagi": "海乱鬼",
|
||||
"none": "无元素类型",
|
||||
"catalyst": "法器",
|
||||
"bow": "弓",
|
||||
"claymore": "双手剑",
|
||||
"pole": "长柄武器",
|
||||
"sword": "单手剑",
|
||||
"cryo": "冰元素",
|
||||
"hydro": "水元素",
|
||||
"pyro": "火元素",
|
||||
"electro": "雷元素",
|
||||
"anemo": "风元素",
|
||||
"geo": "岩元素",
|
||||
"dendro": "草元素",
|
||||
"weapon": "武器",
|
||||
"artifact": "圣遗物",
|
||||
"talent": "天赋",
|
||||
"sheild": "护盾",
|
||||
"place": "场地",
|
||||
"ally": "伙伴",
|
||||
"item": "道具",
|
||||
"resonance": "元素共鸣",
|
||||
"food": "料理",
|
||||
"produce": "草元素产物"
|
||||
}
|
3593
src/data/tcg/th.json
Normal file
3593
src/data/tcg/tr.json
Normal file
3593
src/data/tcg/tw.json
Normal file
3593
src/data/tcg/vi.json
Normal file
3593
src/data/tcg/zh.json
Normal file
|
@ -17,6 +17,7 @@
|
|||
"radiantSpincrystal": "Radiant Spincrystal",
|
||||
"calendar": "Calendar",
|
||||
"banners": "Character Reruns",
|
||||
"tcg": "TCG",
|
||||
"settings": "Settings",
|
||||
"donate": "Donate"
|
||||
},
|
||||
|
@ -142,7 +143,10 @@
|
|||
"manualButton": "Enable Manual Input",
|
||||
"errorBanner": "Banner time mismatch! Please adjust your server on the settings page. Still not working? Please leave a message on Discord 😅",
|
||||
"globalWishTally": "Global Wish Stats",
|
||||
"pityTooltip": ["Shows your current {rarity} pity", "{count} pulls to guaranteed {rarity}"],
|
||||
"pityTooltip": [
|
||||
"Shows your current {rarity} pity",
|
||||
"{count} pulls to guaranteed {rarity}"
|
||||
],
|
||||
"import": {
|
||||
"title": "Import Wish History",
|
||||
"faqsButton": "FAQ - READ FIRST",
|
||||
|
@ -178,7 +182,11 @@
|
|||
"server": "Select your server:",
|
||||
"wishTallyCheck": "Submit pity for global wish stats",
|
||||
"wishTally": "We are doing a global wish stats! You can submit your wish stats to participate. All pity data will be aggregated to know what is the average pity of paimon.moe users.",
|
||||
"wishTallyCollected": ["What will be collected:", "and", "pity from your wish history"],
|
||||
"wishTallyCollected": [
|
||||
"What will be collected:",
|
||||
"and",
|
||||
"pity from your wish history"
|
||||
],
|
||||
"forceUpdateCheck": "Force update wish history (enable only if your wish history is not updating)",
|
||||
"header": [
|
||||
"Import and backup your Genshin Impact wish history to keep it for more than 6 months. It also automatically tracks your pity and statistics about your wishes!",
|
||||
|
@ -388,7 +396,11 @@
|
|||
"exportFinish": "Export success, please wait until your browser downloads the file!",
|
||||
"wishTallyTitle": "Submit Wish Stats",
|
||||
"wishTally": "We are doing a global wish stats! You can submit your wish stats to participate. All pity data will be aggregated to know what is the average pity of paimon.moe users.",
|
||||
"wishTallyCollected": ["What will be collected:", "and", "pity from your wish history"],
|
||||
"wishTallyCollected": [
|
||||
"What will be collected:",
|
||||
"and",
|
||||
"pity from your wish history"
|
||||
],
|
||||
"wishTallySubmit": "Submit Wish Stats",
|
||||
"wishTallyThankyou": "Thank you for participating!",
|
||||
"manualTitle": "Manual Input Settings",
|
||||
|
@ -400,13 +412,22 @@
|
|||
"subtitle": "After a 1x Wish:",
|
||||
"pressWhenYouGet": "Press {button} when you get {rarity}★",
|
||||
"p1": "It will automatically add the lifetime pulls, 5★, and 4★ pity",
|
||||
"p2": ["When the", "pity reaches 10, it will automatically be reset to 0"],
|
||||
"p3": ["When the", "pity reaches 90, it will automatically be reset to 0"],
|
||||
"p2": [
|
||||
"When the",
|
||||
"pity reaches 10, it will automatically be reset to 0"
|
||||
],
|
||||
"p3": [
|
||||
"When the",
|
||||
"pity reaches 90, it will automatically be reset to 0"
|
||||
],
|
||||
"p4": [
|
||||
"After a 10x Wish, press",
|
||||
"but keep in mind that the pity counter might not be accurate, because there is no way to tell when the drop occured (maybe you got it on the 1st or even the 10th pull). To ensure that the counter is still accurate, you need to check the history table and add it one-by-one like you do 1x Wishes."
|
||||
],
|
||||
"p5": ["You can also press the", "button to edit the values manually!"],
|
||||
"p5": [
|
||||
"You can also press the",
|
||||
"button to edit the values manually!"
|
||||
],
|
||||
"p6": "Press the arrow on the bottom to see your pulls' details. A popup will show up when you get a 5★ or 4★. You can also add or edit the table manually."
|
||||
}
|
||||
},
|
||||
|
@ -546,7 +567,11 @@
|
|||
"calculateTalent": "Calculate Talent Material?",
|
||||
"inputTalentLevel": "Input the 1st, 2nd & 3rd current talent level",
|
||||
"inputTalentNotice": "If it has a different color, subtract it by 3",
|
||||
"inputTalent": ["1st talent lvl", "2nd talent lvl", "3rd talent lvl"],
|
||||
"inputTalent": [
|
||||
"1st talent lvl",
|
||||
"2nd talent lvl",
|
||||
"3rd talent lvl"
|
||||
],
|
||||
"talentToLevel": "to level",
|
||||
"calculate": "Calculate",
|
||||
"unknownInformation": "There are some unknown information",
|
||||
|
@ -555,7 +580,11 @@
|
|||
"expWasted": "EXP Wasted",
|
||||
"addToTodo": "Add to Todo List",
|
||||
"addedToTodo": "Added to Todo List",
|
||||
"talent": ["Attack", "Skill", "Burst"]
|
||||
"talent": [
|
||||
"Attack",
|
||||
"Skill",
|
||||
"Burst"
|
||||
]
|
||||
},
|
||||
"expTable": {
|
||||
"level": "Level",
|
||||
|
@ -647,7 +676,10 @@
|
|||
"todo": {
|
||||
"title": "Todo List",
|
||||
"summary": "Summary",
|
||||
"empty": ["Nothing to do yet 😀", "Add some from the Items page or the Calculator!"],
|
||||
"empty": [
|
||||
"Nothing to do yet 😀",
|
||||
"Add some from the Items page or the Calculator!"
|
||||
],
|
||||
"farmableToday": "Farmable Today",
|
||||
"resin": "Resin needed",
|
||||
"based": "Based on AR:{ar} and WL:{wl}",
|
||||
|
@ -966,7 +998,10 @@
|
|||
},
|
||||
"common": {
|
||||
"dataSynced": "Data has been synced!",
|
||||
"driveError": "Drive sync not available right now 😔"
|
||||
"driveError": "Drive sync not available right now 😔",
|
||||
"open": "Open",
|
||||
"delete": "Delete",
|
||||
"deleteConfirm": "Delete?"
|
||||
},
|
||||
"update": {
|
||||
"newUpdate": "Paimon.moe has a new update!",
|
||||
|
@ -984,5 +1019,31 @@
|
|||
"sortByRerun": "Sorted by oldest re-run",
|
||||
"bannerTitle": "Character Release Timeline",
|
||||
"bannerSubtitle": "See when the character is released and their last re-run"
|
||||
},
|
||||
"tcg": {
|
||||
"title": "Genius Invokation TCG",
|
||||
"requirementResonance": "You need at least 2 {element} characters in your deck to add this card!",
|
||||
"requirementTalent": "You need {character} in your deck to add this card!",
|
||||
"addedToDeck": "Added to deck!",
|
||||
"deletedFromDeck": "Deleted from deck!",
|
||||
"removedFromDeck": "Removed one from deck!",
|
||||
"alreadyMaxCharacters": "You already have 3 character cards in the deck!",
|
||||
"alreadyMaxActions": "You already have 30 action cards in the deck!",
|
||||
"requirementInDeck": "You need to remove {card} card to remove {character} from your deck!",
|
||||
"addToDeck": "Add to Deck",
|
||||
"removeFromDeck": "{type} from Deck",
|
||||
"delete": "Delete",
|
||||
"remove": "Remove",
|
||||
"compare": "Compare",
|
||||
"noCardOnDeck": "There is no card yet in this deck",
|
||||
"loadDefaultDeck": "Load Starter Deck",
|
||||
"saveDeck": "Save Deck",
|
||||
"hideDeck": "Hide Deck",
|
||||
"showDeck": "Show Deck",
|
||||
"addDeck": "Add Deck",
|
||||
"selectDeck": "Select Deck",
|
||||
"loadingLink": "Generating link...",
|
||||
"loadingLinkError": "Error generating link 😥",
|
||||
"shareDeck": "Share Deck \"{name}\""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@
|
|||
</script>
|
||||
|
||||
<Header />
|
||||
<Modal>
|
||||
<Modal styleWindowWrap={{ margin: '1rem' }}>
|
||||
<Sidebar {segment} />
|
||||
{#if $showSidebar}
|
||||
<Sidebar {segment} mobile />
|
||||
|
|
18
src/routes/deck/[id].svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script context="module">
|
||||
export function load({ params }) {
|
||||
const { id } = params;
|
||||
return {
|
||||
status: 301,
|
||||
redirect: `/tcg/${id}`,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Genius Invokation TCG - Paimon.moe</title>
|
||||
<meta name="description" content="Genshin Impact Genius Invokation TCG Deck Builder" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Genshin Impact Genius Invokation TCG Deck Builder, see card information, build and share your deck!"
|
||||
/>
|
||||
</svelte:head>
|
76
src/routes/tcg/[id].svelte
Normal file
|
@ -0,0 +1,76 @@
|
|||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
const { id } = params;
|
||||
return { props: { id } };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import Index from './index.svelte';
|
||||
|
||||
export let id;
|
||||
let loading = true;
|
||||
let error = false;
|
||||
let deck = null;
|
||||
|
||||
async function getDeck() {
|
||||
try {
|
||||
const url = new URL(`${import.meta.env.VITE_API_HOST}/deck/${id}`);
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
error = true;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const deckData = JSON.parse(data.deck);
|
||||
|
||||
deck = {
|
||||
name: data.name,
|
||||
...deckData,
|
||||
};
|
||||
|
||||
loading = false;
|
||||
} catch (err) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (id === '@') {
|
||||
if (deck === null) {
|
||||
goto('/tcg');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
getDeck();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Genius Invokation TCG - Paimon.moe</title>
|
||||
<meta name="description" content="Genshin Impact Genius Invokation TCG Deck Builder" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Genshin Impact Genius Invokation TCG Deck Builder, see card information, build and share your deck!"
|
||||
/>
|
||||
</svelte:head>
|
||||
{#if loading}
|
||||
<div class="lg:ml-64 pt-20 lg:pt-8 px-4 lg:px-8 max-w-full">
|
||||
<h1 class="text-white text-3xl">Loading Deck...</h1>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="lg:ml-64 pt-20 lg:pt-8 px-4 lg:px-8 max-w-full">
|
||||
<h1 class="text-white text-3xl">Deck not found 😪</h1>
|
||||
</div>
|
||||
{:else}
|
||||
<Index sharedDeck={deck} sharedId={id} />
|
||||
{/if}
|
328
src/routes/tcg/_card.svelte
Normal file
|
@ -0,0 +1,328 @@
|
|||
<script>
|
||||
import { mdiCompare, mdiMinus, mdiPlus } from '@mdi/js';
|
||||
import { onMount, tick, createEventDispatcher, getContext } from 'svelte';
|
||||
import Detail from './_detail.svelte';
|
||||
import CardButton from './_cardButton.svelte';
|
||||
import DetailModal from './_detailModal.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let card;
|
||||
export let compare;
|
||||
export let count = 0;
|
||||
export let index = 0;
|
||||
export let showDetail = false;
|
||||
export let smallScreen = false;
|
||||
export let draggable = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const { open, close } = getContext('simple-modal');
|
||||
|
||||
const sizes = {
|
||||
base: {
|
||||
card: 'w-32',
|
||||
hp: 'w-8',
|
||||
hpVal: 'text-2xl',
|
||||
element: 'w-10',
|
||||
elementVal: 'text-2xl',
|
||||
energyStar: 'w-6',
|
||||
energyStarMargin: '-mb-1',
|
||||
energyStarPos: 'top-2 -right-3',
|
||||
},
|
||||
small: {
|
||||
card: 'w-24',
|
||||
hp: 'w-6',
|
||||
hpVal: 'text-xl',
|
||||
element: 'w-8',
|
||||
elementVal: 'text-xl',
|
||||
energyStar: 'w-6',
|
||||
energyStarMargin: '-mb-1',
|
||||
energyStarPos: 'top-2 -right-3',
|
||||
},
|
||||
large: {
|
||||
card: 'w-64',
|
||||
hp: 'w-10',
|
||||
hpVal: 'text-3xl',
|
||||
element: 'w-12',
|
||||
elementVal: 'text-3xl',
|
||||
energyStar: 'w-8',
|
||||
energyStarMargin: '-mb-1',
|
||||
energyStarPos: 'top-5 -right-4',
|
||||
},
|
||||
};
|
||||
export let size = 'base';
|
||||
|
||||
let loaded = false;
|
||||
|
||||
let nameLabel;
|
||||
let smallName;
|
||||
let isDragging = false;
|
||||
let isDragHovered = false;
|
||||
let isHovered = false;
|
||||
let x, y, width, height;
|
||||
let lastMoveEvent;
|
||||
|
||||
let cardContainer;
|
||||
let detailContainer;
|
||||
|
||||
async function adjustNameSize() {
|
||||
if (nameLabel === undefined) return;
|
||||
smallName = false;
|
||||
await tick();
|
||||
const height = nameLabel.clientHeight;
|
||||
smallName = height > 40;
|
||||
}
|
||||
|
||||
async function mouseOver(event) {
|
||||
if (!showDetail) return;
|
||||
|
||||
let bounds = cardContainer.getBoundingClientRect();
|
||||
|
||||
x = event.clientX - bounds.left + 10;
|
||||
y = event.clientY - bounds.top + 20;
|
||||
isHovered = true;
|
||||
|
||||
await tick();
|
||||
|
||||
const detailRect = detailContainer.getBoundingClientRect();
|
||||
width = detailRect.width;
|
||||
height = detailRect.height;
|
||||
|
||||
mouseMove(event);
|
||||
}
|
||||
|
||||
function mouseMove(event) {
|
||||
lastMoveEvent = event;
|
||||
|
||||
let bounds = cardContainer.getBoundingClientRect();
|
||||
let curX = event.clientX - bounds.left + 20;
|
||||
let curY = event.clientY - bounds.top + 5;
|
||||
|
||||
const detailRectRightX = event.clientX + width + 40;
|
||||
const detailRectBottomY = event.clientY + height + 20;
|
||||
|
||||
if (detailRectRightX >= window.innerWidth) {
|
||||
curX -= width + 30;
|
||||
}
|
||||
|
||||
if (detailRectBottomY >= window.innerHeight) {
|
||||
curY = window.innerHeight - height - bounds.top - 5;
|
||||
}
|
||||
|
||||
x = curX;
|
||||
y = curY;
|
||||
}
|
||||
|
||||
function mouseLeave() {
|
||||
lastMoveEvent = undefined;
|
||||
isHovered = false;
|
||||
}
|
||||
|
||||
async function refreshPos() {
|
||||
if (!loaded || lastMoveEvent === undefined) return;
|
||||
await mouseOver(lastMoveEvent);
|
||||
mouseMove(lastMoveEvent);
|
||||
}
|
||||
|
||||
async function setCompare() {
|
||||
dispatch('compare');
|
||||
}
|
||||
|
||||
async function addToDeck() {
|
||||
dispatch('addToDeck');
|
||||
}
|
||||
|
||||
async function removeFromDeck() {
|
||||
dispatch('removeFromDeck');
|
||||
}
|
||||
|
||||
function showDetailModal() {
|
||||
open(
|
||||
DetailModal,
|
||||
{
|
||||
card,
|
||||
compare,
|
||||
showCompare,
|
||||
count,
|
||||
addToDeck,
|
||||
removeFromDeck,
|
||||
close,
|
||||
},
|
||||
{
|
||||
closeButton: false,
|
||||
styleWindow: {
|
||||
background: '#25294A',
|
||||
width: 'fit-content',
|
||||
'max-width': showCompare ? '1280px' : '860px',
|
||||
},
|
||||
styleContent: { padding: 0 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleDragStart(e) {
|
||||
isHovered = false;
|
||||
isDragging = true;
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', index);
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
function handleDragEnter() {
|
||||
isDragHovered = true;
|
||||
}
|
||||
|
||||
function handleDragOver(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function handleDragLeave() {
|
||||
isDragHovered = false;
|
||||
}
|
||||
|
||||
function handleDrop(e) {
|
||||
e.stopPropagation();
|
||||
isDragHovered = false;
|
||||
const sourceIndex = Number(e.dataTransfer.getData('text/plain'));
|
||||
dispatch('swapOrder', { from: index, to: sourceIndex });
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
adjustNameSize();
|
||||
loaded = true;
|
||||
|
||||
if (draggable) {
|
||||
cardContainer.addEventListener('dragstart', handleDragStart);
|
||||
cardContainer.addEventListener('dragend', handleDragEnd);
|
||||
cardContainer.addEventListener('dragenter', handleDragEnter);
|
||||
cardContainer.addEventListener('dragleave', handleDragLeave);
|
||||
cardContainer.addEventListener('dragover', handleDragOver);
|
||||
cardContainer.addEventListener('drop', handleDrop);
|
||||
}
|
||||
});
|
||||
|
||||
$: card.name, adjustNameSize();
|
||||
$: showCompare = compare !== undefined;
|
||||
$: compare, refreshPos();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="group relative {draggable ? 'cursor-move' : 'cursor-pointer'} {sizes[size].card}"
|
||||
on:mouseover={mouseOver}
|
||||
on:mouseleave={mouseLeave}
|
||||
on:mousemove={mouseMove}
|
||||
on:focus={mouseOver}
|
||||
bind:this={cardContainer}
|
||||
>
|
||||
<div class="relative">
|
||||
<img
|
||||
src="/images/tcg/{card.id}.png"
|
||||
alt={card.name}
|
||||
class="w-full rounded-xl duration-100 {isDragHovered ? 'ring-4 ring-primary rounded-xl' : ''} {isDragging
|
||||
? 'opacity-40'
|
||||
: ''}"
|
||||
loading="lazy"
|
||||
width="420"
|
||||
height="720"
|
||||
on:click={showDetailModal}
|
||||
/>
|
||||
{#if count}
|
||||
<div class="absolute bottom-0 right-0">
|
||||
<div class="relative -mb-[1px] -mr-[1px]">
|
||||
<img src="/images/tcg/icons/counter.png" alt="counter" width="68" height="68" class="w-8 h-8" />
|
||||
<div class="w-full h-full flex items-center justify-center top-0 left-0 absolute">
|
||||
<p
|
||||
class="font-bold text-white pt-[2px] pr-[1px] {sizes[size].elementVal}"
|
||||
style="-webkit-text-stroke: 1.2px black;"
|
||||
>
|
||||
{count}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if size !== 'large'}
|
||||
<div class="group-hover:hidden group-hover:md:flex absolute hidden bottom-1 left-1 flex-col gap-1">
|
||||
{#if count > 0}
|
||||
<CardButton
|
||||
icon={mdiMinus}
|
||||
label={count > 1 ? $t('tcg.remove') : $t('tcg.delete')}
|
||||
on:click={removeFromDeck}
|
||||
/>
|
||||
{#if card.type !== 'character'}
|
||||
<CardButton icon={mdiPlus} label={$t('tcg.addToDeck')} on:click={addToDeck} />
|
||||
{/if}
|
||||
{:else}
|
||||
<CardButton icon={mdiPlus} label={$t('tcg.addToDeck')} on:click={addToDeck} />
|
||||
{/if}
|
||||
<CardButton icon={mdiCompare} label={$t('tcg.compare')} on:click={setCompare} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if isHovered}
|
||||
<div
|
||||
class="absolute z-50 flex {showCompare ? 'w-[860px]' : 'w-[600px]'}"
|
||||
style="top: {y}px; left: {x}px;"
|
||||
bind:this={detailContainer}
|
||||
>
|
||||
<Detail {card} {smallScreen} />
|
||||
{#if showCompare}
|
||||
<div class="w-2" />
|
||||
<Detail card={compare} {smallScreen} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if size !== 'large'}
|
||||
<p
|
||||
bind:this={nameLabel}
|
||||
class="text-white text-center leading-none pt-1 min-h-[32px] {smallName ? 'text-xs' : 'text-sm'}"
|
||||
>
|
||||
{card.name}
|
||||
</p>
|
||||
{/if}
|
||||
{#if card.type === 'character'}
|
||||
<div class="absolute top-0 left-0 -mx-1 -my-2 z-10">
|
||||
<img src="/images/tcg/icons/hp.png" alt="HP" class={sizes[size].hp} width="140" height="172" />
|
||||
<div class="w-full h-full flex items-center justify-center top-0 left-0 absolute">
|
||||
<p class="font-bold text-white pt-1 {sizes[size].hpVal}" style="-webkit-text-stroke: 1.2px black;">
|
||||
{card.hp}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute {sizes[size].energyStarPos}">
|
||||
{#each Array(card.energy) as _}
|
||||
<img
|
||||
src="/images/tcg/icons/energy_card.png"
|
||||
alt="Energy"
|
||||
class="{sizes[size].energyStar} {sizes[size].energyStarMargin}"
|
||||
width="100"
|
||||
height="100"
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="absolute top-0 left-0 -mx-2 -my-2 flex">
|
||||
{#each card.skills.cost.length > 0 ? card.skills.cost : [{}] as cost}
|
||||
<div class="relative">
|
||||
<img
|
||||
src="/images/tcg/icons/{cost.type || 'same'}.png"
|
||||
alt={cost.type}
|
||||
class={sizes[size].element}
|
||||
width="140"
|
||||
height="172"
|
||||
/>
|
||||
<div class="w-full h-full flex items-center justify-center top-0 left-0 absolute">
|
||||
<p
|
||||
class="font-bold text-white pt-[2px] pr-[1px] {sizes[size].elementVal}"
|
||||
style="-webkit-text-stroke: 1.2px black;"
|
||||
>
|
||||
{cost.count || 0}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
17
src/routes/tcg/_cardButton.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script>
|
||||
import Icon from '../../components/Icon.svelte';
|
||||
|
||||
export let icon;
|
||||
export let label;
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click
|
||||
class="group/button w-8 hover:w-fit h-8 text-sm bg-black bg-opacity-90 rounded-full
|
||||
text-white text-center hover:ring-2 ring-primary duration-100 flex items-center justify-center z-30"
|
||||
>
|
||||
<div class="min-w-[2rem] w-8">
|
||||
<Icon path={icon} />
|
||||
</div>
|
||||
<p class="hidden group-hover/button:block pr-2 text-xs leading-none">{label}</p>
|
||||
</button>
|
218
src/routes/tcg/_deck.svelte
Normal file
|
@ -0,0 +1,218 @@
|
|||
<script>
|
||||
import { mdiContentSave, mdiDownload, mdiPencil, mdiShareVariant } from '@mdi/js';
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { slide } from 'svelte/transition';
|
||||
import Button from '../../components/Button.svelte';
|
||||
import Icon from '../../components/Icon.svelte';
|
||||
import Input from '../../components/Input.svelte';
|
||||
import Card from './_card.svelte';
|
||||
import DeckModal from './_deckModal.svelte';
|
||||
import ShareModal from './_shareModal.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const { open, close } = getContext('simple-modal');
|
||||
|
||||
export let cards, decks, deck, size, characterCount, actionCount, compare, showDetail;
|
||||
export let sharedDeck, sharedId;
|
||||
export let setCompare, addToDeck, removeFromDeck, selectDeck, loadDeck, swapCharacterCardPos;
|
||||
|
||||
let editName = false;
|
||||
let name = '';
|
||||
|
||||
function showDeckSelectionModal() {
|
||||
open(
|
||||
DeckModal,
|
||||
{
|
||||
decks,
|
||||
close,
|
||||
selectDeck: changeDeck,
|
||||
save: () => dispatch('save'),
|
||||
},
|
||||
{
|
||||
closeButton: false,
|
||||
styleWindow: { background: '#25294A', width: '600px' },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function share() {
|
||||
open(
|
||||
ShareModal,
|
||||
{
|
||||
deck,
|
||||
setShareId,
|
||||
id: sharedId,
|
||||
},
|
||||
{
|
||||
closeButton: false,
|
||||
styleWindow: { background: '#25294A', width: '400px' },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function changeDeck(index) {
|
||||
selectDeck(index);
|
||||
setShareId(null);
|
||||
}
|
||||
|
||||
function setShareId(id) {
|
||||
console.log('set', id);
|
||||
sharedId = id;
|
||||
}
|
||||
|
||||
function saveSharedDeck() {
|
||||
dispatch('saveShared');
|
||||
}
|
||||
|
||||
function toggleEditName() {
|
||||
name = deck.name;
|
||||
editName = true;
|
||||
}
|
||||
|
||||
function saveName() {
|
||||
deck.name = name;
|
||||
editName = false;
|
||||
dispatch('save');
|
||||
}
|
||||
|
||||
function swapOrder(event) {
|
||||
swapCharacterCardPos(event.detail.from, event.detail.to);
|
||||
}
|
||||
|
||||
function loadDefaultDeck() {
|
||||
loadDeck(
|
||||
{ diluc: 1, kaeya: 1, sucrose: 1 },
|
||||
{
|
||||
magic_guide: 1,
|
||||
raven_bow: 1,
|
||||
white_iron_greatsword: 1,
|
||||
white_tassel: 1,
|
||||
travelers_handy_sword: 1,
|
||||
broken_rimes_echo: 1,
|
||||
'wine-stained_tricorne': 1,
|
||||
witchs_scorching_hat: 1,
|
||||
thunder_summoners_crown: 1,
|
||||
viridescent_venerers_diadem: 1,
|
||||
mask_of_solitude_basalt: 1,
|
||||
laurel_coronet: 1,
|
||||
changing_shifts: 1,
|
||||
strategize: 1,
|
||||
'i_havent_lost_yet!': 1,
|
||||
'leave_it_to_me!': 1,
|
||||
when_the_crane_returned: 1,
|
||||
starsigns: 1,
|
||||
calxs_arts: 1,
|
||||
master_of_weaponry: 1,
|
||||
blessing_of_the_divine_relics_installation: 1,
|
||||
quick_knit: 1,
|
||||
send_off: 1,
|
||||
guardians_oath: 1,
|
||||
sweet_madame: 1,
|
||||
minty_meat_rolls: 1,
|
||||
dawn_winery: 1,
|
||||
favonius_cathedral: 1,
|
||||
paimon: 1,
|
||||
'the_bestest_travel_companion!': 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative bg-black bg-opacity-50 px-4 pt-4 pb-2 rounded-xl mb-4" transition:slide={{ duration: 200 }}>
|
||||
<div class="absolute top-4 right-4 flex flex-row-reverse md:flex-col gap-4 xl:gap-0">
|
||||
<button
|
||||
class="rounded-lg pl-2 pr-4 py-1 w-fit ring-2 ring-gray-700 hover:ring-primary duration-100"
|
||||
on:click={() => dispatch('toggleDeck')}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/tcg/icons/card_character.png" alt="character card" class="w-8" />
|
||||
<p class="text-white text-xl">{characterCount}</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<img src="/images/tcg/icons/card.png" alt="character card" class="w-8" />
|
||||
<p class="text-white text-xl">{actionCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pl-2 text-white text-xs text-center">{$t('tcg.hideDeck')}</p>
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg md:mt-4 px-2 py-1 ring-2 ring-gray-700 hover:ring-primary duration-100 text-white"
|
||||
on:click={showDeckSelectionModal}
|
||||
>
|
||||
{$t('tcg.selectDeck')}
|
||||
</button>
|
||||
</div>
|
||||
<div class="pt-24 md:pt-0 pb-4 flex items-center w-full">
|
||||
{#if editName}
|
||||
<div class="flex w-full max-w-screen-sm">
|
||||
<div class="w-full max-w-full">
|
||||
<Input className="w-full" bind:value={name} />
|
||||
</div>
|
||||
<button class="text-white ml-4 rounded-xl hover:ring-2 ring-primary p-1 duration-100" on:click={saveName}>
|
||||
<Icon path={mdiContentSave} />
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex xl:pr-96 w-full items-center">
|
||||
<h1 class="text-white font-bold text-lg xl:text-3xl max-w-full break-words">
|
||||
{deck.name}
|
||||
</h1>
|
||||
{#if sharedDeck === null}
|
||||
<button
|
||||
class="text-white ml-4 rounded-xl hover:ring-2 ring-primary p-1 duration-100"
|
||||
on:click={toggleEditName}
|
||||
>
|
||||
<Icon path={mdiPencil} />
|
||||
</button>
|
||||
<button class="text-white ml-4 rounded-xl hover:ring-2 ring-primary p-1 duration-100" on:click={share}>
|
||||
<Icon path={mdiShareVariant} />
|
||||
</button>
|
||||
{:else}
|
||||
<button class="text-white ml-4 rounded-xl hover:ring-2 ring-primary p-1 duration-100" on:click={share}>
|
||||
<Icon path={mdiShareVariant} />
|
||||
</button>
|
||||
<Button className="ml-4" on:click={saveSharedDeck}>{$t('tcg.saveDeck')}</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if characterCount === 0 && actionCount === 0}
|
||||
<div>
|
||||
<p class="text-white text-lg pb-2">{$t('tcg.noCardOnDeck')}</p>
|
||||
<Button on:click={loadDefaultDeck}>{$t('tcg.loadDefaultDeck')}</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-3 pb-4">
|
||||
{#each Object.keys(deck.characters) as id, index}
|
||||
<Card
|
||||
card={cards[id]}
|
||||
{size}
|
||||
{compare}
|
||||
{showDetail}
|
||||
{index}
|
||||
draggable
|
||||
count={1}
|
||||
on:compare={() => setCompare(cards[id])}
|
||||
on:addToDeck={() => addToDeck('characters', cards[id])}
|
||||
on:removeFromDeck={() => removeFromDeck('characters', cards[id])}
|
||||
on:swapOrder={swapOrder}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-3">
|
||||
{#each Object.entries(deck.actions) as [id, count]}
|
||||
<Card
|
||||
card={cards[id]}
|
||||
{size}
|
||||
{compare}
|
||||
{count}
|
||||
{showDetail}
|
||||
on:compare={() => setCompare(cards[id])}
|
||||
on:addToDeck={() => addToDeck('actions', cards[id])}
|
||||
on:removeFromDeck={() => removeFromDeck('actions', cards[id])}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
71
src/routes/tcg/_deckModal.svelte
Normal file
|
@ -0,0 +1,71 @@
|
|||
<script>
|
||||
import { t } from 'svelte-i18n';
|
||||
import Button from '../../components/Button.svelte';
|
||||
|
||||
export let decks;
|
||||
export let selectDeck, close, save;
|
||||
|
||||
const confirmDelete = [];
|
||||
|
||||
function addDeck() {
|
||||
decks.push({
|
||||
name: `Deck #${decks.length + 1}`,
|
||||
characters: {},
|
||||
actions: {},
|
||||
});
|
||||
decks = decks;
|
||||
save();
|
||||
}
|
||||
|
||||
function deleteDeck(index) {
|
||||
if (confirmDelete[index] === true) {
|
||||
deleteDeckConfirm(index);
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDelete[index] = true;
|
||||
setTimeout(() => {
|
||||
confirmDelete[index] = false;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function deleteDeckConfirm(index) {
|
||||
decks.splice(index, 1);
|
||||
decks = decks;
|
||||
selectDeck(0);
|
||||
save();
|
||||
}
|
||||
|
||||
function changeDeck(index) {
|
||||
selectDeck(index);
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="pb-4">
|
||||
<Button on:click={addDeck}>{$t('tcg.addDeck')}</Button>
|
||||
</div>
|
||||
<table class="border-separate border-spacing-0">
|
||||
{#each decks as deck, i}
|
||||
<tr class="group">
|
||||
<td class="group-last:border-b-0 border-b border-gray-700 w-full px-2 text-white align-middle">
|
||||
<div class="flex items-center gap-2">
|
||||
{#each Object.keys(deck.characters) as char}
|
||||
<img src="/images/tcg/avatar/{char}.png" alt={char} class="w-10" />
|
||||
{/each}
|
||||
<p class="pt-1 pl-2">{deck.name}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="group-last:border-b-0 border-b border-gray-700 py-2 pr-2 whitespace-nowrap text-right">
|
||||
{#if decks.length > 1}
|
||||
<Button size="sm" color="red" on:click={() => deleteDeck(i)}>
|
||||
{$t(confirmDelete[i] ? 'common.deleteConfirm' : 'common.delete')}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button size="sm" on:click={() => changeDeck(i)}>{$t('common.open')}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
153
src/routes/tcg/_detail.svelte
Normal file
|
@ -0,0 +1,153 @@
|
|||
<script context="module">
|
||||
import tagsJson from '../../data/tcg/tags/en.json';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let card;
|
||||
export let smallScreen = false;
|
||||
export let withBackground = true;
|
||||
|
||||
const sizes = {
|
||||
base: {
|
||||
text: 'text-sm',
|
||||
title: 'text-base',
|
||||
gap: 'gap-y-2',
|
||||
tag: 'w-8',
|
||||
image: 'w-12',
|
||||
element: 'w-10',
|
||||
padding: 'p-2',
|
||||
},
|
||||
small: {
|
||||
text: 'text-xs',
|
||||
title: 'text-xs',
|
||||
gap: 'gap-y-1',
|
||||
tag: 'w-6',
|
||||
image: 'w-6',
|
||||
element: 'w-8',
|
||||
padding: 'p-1',
|
||||
},
|
||||
};
|
||||
|
||||
let tags = tagsJson;
|
||||
|
||||
$: size = sizes[smallScreen ? 'small' : 'base'];
|
||||
</script>
|
||||
|
||||
{#if card.type === 'character'}
|
||||
<div
|
||||
class="flex flex-col bg-opacity-95 rounded-xl text-white w-full h-fit
|
||||
origin-top-left {withBackground ? 'bg-background' : ''} {size.gap} {size.padding}"
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<img
|
||||
src="/images/tcg/icons/elements/{card.element}.png"
|
||||
alt={card.element}
|
||||
class={size.tag}
|
||||
width="128"
|
||||
height="128"
|
||||
/>
|
||||
<p class="{size.title} font-bold flex-1">{card.name}</p>
|
||||
<img
|
||||
src="/images/tcg/icons/weapons/{card.weapon}.png"
|
||||
alt={card.weapon}
|
||||
class={size.tag}
|
||||
width="64"
|
||||
height="64"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col text-sm {size.gap}">
|
||||
{#each card.skills as skill, i}
|
||||
<div class="bg-black bg-opacity-60 p-2 rounded-xl">
|
||||
<div class="flex items-center pb-2">
|
||||
<img class={size.image} src="/images/tcg/skills/{card.id}/skill_{i + 1}.png" alt={skill.name} />
|
||||
<div class="pr-3 flex-1">
|
||||
<p class="pl-2 font-bold leading-tight {size.title}">{skill.name}</p>
|
||||
<p class="pl-2 {size.title}" style="color: #ffd780ff;">{skill.type}</p>
|
||||
</div>
|
||||
<div class="flex">
|
||||
{#each skill.cost as cost}
|
||||
<div class="relative">
|
||||
<img
|
||||
src="/images/tcg/icons/{cost.type}.png"
|
||||
alt={cost.type}
|
||||
class={size.element}
|
||||
width="140"
|
||||
height="172"
|
||||
/>
|
||||
<div class="w-full h-full flex items-center justify-center top-0 left-0 absolute">
|
||||
<p
|
||||
class="font-bold text-2xl text-white pt-[2px] pr-[1px]"
|
||||
style="-webkit-text-stroke: 1.2px black;"
|
||||
>
|
||||
{cost.count}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<p class={size.text}>{@html skill.desc.replace(/\\n/g, '<br/>')}</p>
|
||||
{#if skill.sub}
|
||||
<p class="{size.title} font-bold pt-4">{skill.sub.name}</p>
|
||||
<p class={size.text}>
|
||||
{@html skill.sub.desc.replace(/\\n/g, '<br/>')}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex flex-col gap-2 bg-opacity-95 p-2 rounded-xl text-white w-full h-fit
|
||||
{withBackground ? 'bg-background' : ''}"
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<p class="{size.title} font-bold flex-1">{card.name}</p>
|
||||
</div>
|
||||
<div class="bg-black bg-opacity-60 p-2 rounded-xl">
|
||||
<div class="flex items-center pb-2">
|
||||
<div class="pr-3 flex-1 flex gap-2 items-center">
|
||||
<img
|
||||
src="/images/tcg/icons/types/{card.type}.png"
|
||||
alt={card.type}
|
||||
class={size.image}
|
||||
width="64"
|
||||
height="64"
|
||||
/>
|
||||
<p class="{size.title} font-bold leading-tight">{tags[card.type]}</p>
|
||||
</div>
|
||||
<div class="flex">
|
||||
{#if card.skills.cost.length === 0}
|
||||
<div class="relative">
|
||||
<img src="/images/tcg/icons/same.png" alt="same" class={size.image} width="140" height="172" />
|
||||
<div class="w-full h-full flex items-center justify-center top-0 left-0 absolute">
|
||||
<p class="font-bold text-2xl text-white pt-[2px] pr-[1px]" style="-webkit-text-stroke: 1.2px black;">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#each card.skills.cost as cost}
|
||||
<div class="relative">
|
||||
<img
|
||||
src="/images/tcg/icons/{cost.type}.png"
|
||||
alt={cost.type}
|
||||
class={size.element}
|
||||
width="140"
|
||||
height="172"
|
||||
/>
|
||||
<div class="w-full h-full flex items-center justify-center top-0 left-0 absolute">
|
||||
<p class="font-bold text-2xl text-white pt-[2px] pr-[1px]" style="-webkit-text-stroke: 1.2px black;">
|
||||
{cost.count}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<p class={size.text}>{@html card.skills.desc.replace(/\\n/g, '<br/>')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
43
src/routes/tcg/_detailModal.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import { t } from 'svelte-i18n';
|
||||
import Button from '../../components/Button.svelte';
|
||||
import Card from './_card.svelte';
|
||||
import Detail from './_detail.svelte';
|
||||
|
||||
export let card;
|
||||
export let showCompare;
|
||||
export let compare;
|
||||
export let count;
|
||||
export let addToDeck;
|
||||
export let removeFromDeck;
|
||||
export let close;
|
||||
|
||||
function add() {
|
||||
addToDeck();
|
||||
close();
|
||||
}
|
||||
|
||||
function remove() {
|
||||
removeFromDeck();
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col-reverse items-center md:items-start md:flex-row">
|
||||
<div class="p-2 md:p-4 flex flex-col-reverse md:flex-col gap-4 md:gap-2">
|
||||
<Card {card} {count} size="large" />
|
||||
{#if (card.type === 'character' && count === undefined) || card.type !== 'character'}
|
||||
<Button on:click={add}>{$t('tcg.addToDeck')}</Button>
|
||||
{/if}
|
||||
{#if count > 0}
|
||||
<Button on:click={remove}>
|
||||
{$t('tcg.removeFromDeck', { values: { type: count === 1 ? $t('tcg.delete') : $t('tcg.remove') } })}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<Detail {card} withBackground={false} />
|
||||
{#if showCompare}
|
||||
<div class="w-2" />
|
||||
<Detail card={compare} withBackground={false} />
|
||||
{/if}
|
||||
</div>
|
80
src/routes/tcg/_shareModal.svelte
Normal file
|
@ -0,0 +1,80 @@
|
|||
<script>
|
||||
import { mdiCheck, mdiContentCopy } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import Button from '../../components/Button.svelte';
|
||||
import Icon from '../../components/Icon.svelte';
|
||||
|
||||
export let deck;
|
||||
|
||||
let loading = true;
|
||||
let error = false;
|
||||
let copied = false;
|
||||
export let id = null;
|
||||
export let setShareId;
|
||||
|
||||
async function submitDeck() {
|
||||
try {
|
||||
const url = new URL(`${import.meta.env.VITE_API_HOST}/deck`);
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(deck),
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
error = true;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
id = data.id;
|
||||
|
||||
setShareId(id);
|
||||
|
||||
loading = false;
|
||||
} catch (err) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
function copyLink() {
|
||||
try {
|
||||
navigator.clipboard.writeText(generatedLink);
|
||||
copied = true;
|
||||
|
||||
setTimeout(() => {
|
||||
copied = false;
|
||||
}, 2000);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (id === null) {
|
||||
submitDeck();
|
||||
} else {
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
$: generatedLink = `paimon.moe/deck/${id}`;
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<p class="text-white">{$t('tcg.loadingLink')}</p>
|
||||
{:else if error}
|
||||
<p class="text-white">{$t('tcg.loadingLinkError')}</p>
|
||||
{:else}
|
||||
<div class="flex flex-col">
|
||||
<p class="text-gray-400 pb-4">{$t('tcg.shareDeck', { values: { name: deck.name } })}</p>
|
||||
<div class="flex gap-4">
|
||||
<div class="bg-background rounded-xl px-4 flex items-center flex-1">
|
||||
<p class="text-white">{generatedLink}</p>
|
||||
</div>
|
||||
<Button on:click={copyLink}>
|
||||
<Icon path={copied ? mdiCheck : mdiContentCopy} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
468
src/routes/tcg/index.svelte
Normal file
|
@ -0,0 +1,468 @@
|
|||
<script context="module">
|
||||
import dataJson from '../../data/tcg/en.json';
|
||||
import tagsJson from '../../data/tcg/tags/en.json';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { mdiDockWindow, mdiImageSizeSelectLarge, mdiImageSizeSelectSmall } from '@mdi/js';
|
||||
import { locale, t } from 'svelte-i18n';
|
||||
import Icon from '../../components/Icon.svelte';
|
||||
import Card from './_card.svelte';
|
||||
import Deck from './_deck.svelte';
|
||||
import { pushToast } from '../../stores/toast';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { getAccountPrefix } from '../../stores/account';
|
||||
import { readSave, updateSave } from '../../stores/saveManager';
|
||||
|
||||
let data = dataJson;
|
||||
let tags = tagsJson;
|
||||
|
||||
export let sharedDeck = null;
|
||||
export let sharedId = null;
|
||||
|
||||
let cards = {};
|
||||
let characters = [];
|
||||
let _actions = [];
|
||||
let actions = [];
|
||||
|
||||
/**
|
||||
* @typedef Card
|
||||
* @type {Object}
|
||||
* @property {string} id
|
||||
* @property {string} type
|
||||
* @property {string} name
|
||||
* @property {string} element
|
||||
* @property {string} [requirement]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Deck
|
||||
* @type {Object}
|
||||
* @property {Object.<string, number>} characters
|
||||
* @property {Object.<string, number>} actions
|
||||
*/
|
||||
|
||||
/** @type Deck[] */
|
||||
let decks = [
|
||||
{
|
||||
name: 'Deck #1',
|
||||
characters: {},
|
||||
actions: {},
|
||||
},
|
||||
];
|
||||
let activeDeck = 0;
|
||||
|
||||
let compare;
|
||||
let display = 'character';
|
||||
let size = 'base';
|
||||
let showDetail = true;
|
||||
let showDeck = false;
|
||||
let smallScreen = false;
|
||||
|
||||
let filter = {
|
||||
ally: true,
|
||||
artifact: true,
|
||||
food: true,
|
||||
item: true,
|
||||
none: true,
|
||||
place: true,
|
||||
resonance: true,
|
||||
talent: true,
|
||||
weapon: true,
|
||||
};
|
||||
|
||||
function process() {
|
||||
cards = {};
|
||||
characters = [];
|
||||
_actions = [];
|
||||
actions = [];
|
||||
|
||||
for (const card of data) {
|
||||
cards[card.id] = card;
|
||||
if (card.type === 'character') {
|
||||
characters.push(card);
|
||||
} else {
|
||||
_actions.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
characters.sort((a, b) => a.name.localeCompare(b.name));
|
||||
_actions.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
||||
actions = _actions;
|
||||
}
|
||||
|
||||
function setCompare(card) {
|
||||
compare = card;
|
||||
}
|
||||
|
||||
function removeCompare() {
|
||||
compare = undefined;
|
||||
}
|
||||
|
||||
function changeDisplay(type) {
|
||||
display = type;
|
||||
}
|
||||
|
||||
function changeSize(type) {
|
||||
size = type;
|
||||
}
|
||||
|
||||
function toggleDetail() {
|
||||
showDetail = !showDetail;
|
||||
}
|
||||
|
||||
function toggleFilter(type) {
|
||||
const filterCount = Object.keys(filter).length;
|
||||
const trueCount = Object.values(filter).reduce((prev, cur) => prev + (cur ? 1 : 0), 0);
|
||||
|
||||
if (trueCount === filterCount) {
|
||||
Object.keys(filter).forEach((e) => {
|
||||
filter[e] = false;
|
||||
});
|
||||
} else if (trueCount === 1 && filter[type]) {
|
||||
Object.keys(filter).forEach((e) => {
|
||||
if (e === type) {
|
||||
filter[type] = false;
|
||||
return;
|
||||
}
|
||||
filter[e] = true;
|
||||
});
|
||||
}
|
||||
|
||||
filter[type] = !filter[type];
|
||||
|
||||
actions = _actions.filter((card) => filter[card.type]);
|
||||
}
|
||||
|
||||
function toggleShowDeck() {
|
||||
showDeck = !showDeck;
|
||||
if (showDeck) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function selectDeck(index) {
|
||||
sharedDeck = null;
|
||||
activeDeck = index;
|
||||
saveData();
|
||||
}
|
||||
|
||||
function loadDeck(characters, actions) {
|
||||
deck.characters = characters;
|
||||
deck.actions = actions;
|
||||
saveData();
|
||||
}
|
||||
|
||||
function saveSharedDeck() {
|
||||
decks.push(sharedDeck);
|
||||
decks = decks;
|
||||
activeDeck = decks.length - 1;
|
||||
|
||||
sharedDeck = null;
|
||||
sharedId = null;
|
||||
|
||||
saveData();
|
||||
goto('/tcg/@');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {Card} card
|
||||
*/
|
||||
function addToDeck(type, card) {
|
||||
if (type === 'characters' && characterCount === 3) {
|
||||
pushToast($t('tcg.alreadyMaxCharacters'));
|
||||
return;
|
||||
} else if (type === 'actions' && actionCount === 30) {
|
||||
pushToast($t('tcg.alreadyMaxActions'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (card.type === 'resonance') {
|
||||
const elementCount = {};
|
||||
for (const id of Object.keys(deck.characters)) {
|
||||
const charCard = cards[id];
|
||||
if (elementCount[charCard.element] === undefined) elementCount[charCard.element] = 0;
|
||||
elementCount[charCard.element]++;
|
||||
}
|
||||
|
||||
if ((elementCount[card.requirement] || 0) < 2) {
|
||||
pushToast($t('tcg.requirementResonance', { values: { element: tags[card.requirement] } }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (card.type === 'talent') {
|
||||
if (deck.characters[card.requirement] === undefined) {
|
||||
pushToast($t('tcg.requirementTalent', { values: { character: cards[card.requirement].name } }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pushToast($t('tcg.addedToDeck'));
|
||||
|
||||
if (deck[type][card.id] === undefined) {
|
||||
deck[type][card.id] = 0;
|
||||
}
|
||||
deck[type][card.id] += 1;
|
||||
|
||||
saveData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {Card} card
|
||||
*/
|
||||
function removeFromDeck(type, card) {
|
||||
let deleted = false;
|
||||
|
||||
if (type === 'characters') {
|
||||
const elementCount = {};
|
||||
for (const id of Object.keys(deck.characters)) {
|
||||
if (id === card.id) continue;
|
||||
|
||||
const charCard = cards[id];
|
||||
if (elementCount[charCard.element] === undefined) elementCount[charCard.element] = 0;
|
||||
elementCount[charCard.element]++;
|
||||
}
|
||||
|
||||
for (const id of Object.keys(deck.actions)) {
|
||||
const c = cards[id];
|
||||
if (c.requirement === card.id || (c.requirement === card.element && elementCount[card.element] < 2)) {
|
||||
pushToast($t('tcg.requirementInDeck', { values: { character: card.name, card: c.name } }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deck[type][card.id]--;
|
||||
if (deck[type][card.id] === 0) {
|
||||
deleted = true;
|
||||
delete deck[type][card.id];
|
||||
}
|
||||
deck[type] = deck[type];
|
||||
|
||||
if (deleted) {
|
||||
pushToast($t('tcg.deletedFromDeck'));
|
||||
} else {
|
||||
pushToast($t('tcg.removedFromDeck'));
|
||||
}
|
||||
|
||||
saveData();
|
||||
}
|
||||
|
||||
function swapCharacterCardPos(from, to) {
|
||||
const keys = Object.keys(deck.characters);
|
||||
[keys[from], keys[to]] = [keys[to], keys[from]];
|
||||
deck.characters = keys.reduce((prev, cur) => {
|
||||
prev[cur] = 1;
|
||||
return prev;
|
||||
}, {});
|
||||
saveData();
|
||||
}
|
||||
|
||||
async function readLocalData() {
|
||||
const prefix = getAccountPrefix();
|
||||
const tcgData = await readSave(`${prefix}tcg-decks`);
|
||||
const tcgSelectedDeck = await readSave(`${prefix}tcg-activedeck`);
|
||||
if (tcgData !== null) {
|
||||
decks = tcgData;
|
||||
activeDeck = tcgSelectedDeck;
|
||||
}
|
||||
}
|
||||
|
||||
const saveData = debounce(async () => {
|
||||
const prefix = getAccountPrefix();
|
||||
await updateSave(`${prefix}tcg-decks`, decks);
|
||||
await updateSave(`${prefix}tcg-activedeck`, activeDeck);
|
||||
}, 2000);
|
||||
|
||||
async function changeLocale(locale) {
|
||||
const dataJson = await import(`../../data/tcg/${locale}.json`);
|
||||
const tagsDataJson = await import(`../../data/tcg/tags/${locale}.json`);
|
||||
data = dataJson.default;
|
||||
tags = tagsDataJson.default;
|
||||
process();
|
||||
}
|
||||
|
||||
process();
|
||||
|
||||
onMount(async () => {
|
||||
await readLocalData();
|
||||
smallScreen = window.innerHeight < 900;
|
||||
showDetail = window.innerWidth > 600;
|
||||
if (smallScreen) {
|
||||
size = 'small';
|
||||
}
|
||||
|
||||
if (sharedDeck !== null) {
|
||||
showDeck = true;
|
||||
}
|
||||
|
||||
locale.subscribe((val) => {
|
||||
changeLocale(val);
|
||||
});
|
||||
});
|
||||
|
||||
/** @type Deck */
|
||||
$: deck = sharedDeck === null ? decks[activeDeck] : sharedDeck;
|
||||
$: characterCount = Object.keys(deck.characters).length;
|
||||
$: actionCount = Object.values(deck.actions).reduce((prev, cur) => prev + cur, 0);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Genius Invokation TCG - Paimon.moe</title>
|
||||
<meta name="description" content="Genshin Impact Genius Invokation TCG Deck Builder" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Genshin Impact Genius Invokation TCG Deck Builder, see card information, build and share your deck!"
|
||||
/>
|
||||
</svelte:head>
|
||||
<div class="lg:ml-64 pt-20 lg:pt-8 px-4 lg:px-8 max-w-full">
|
||||
{#if showDeck}
|
||||
<Deck
|
||||
{cards}
|
||||
{decks}
|
||||
{deck}
|
||||
{size}
|
||||
{characterCount}
|
||||
{actionCount}
|
||||
{compare}
|
||||
{setCompare}
|
||||
{showDetail}
|
||||
{addToDeck}
|
||||
{removeFromDeck}
|
||||
{selectDeck}
|
||||
{loadDeck}
|
||||
{sharedDeck}
|
||||
{sharedId}
|
||||
{swapCharacterCardPos}
|
||||
on:toggleDeck={toggleShowDeck}
|
||||
on:save={() => saveData()}
|
||||
on:saveShared={saveSharedDeck}
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex gap-2 justify-center md:justify-start">
|
||||
<div class="flex">
|
||||
<button on:click={() => changeDisplay('character')} class="pill {display === 'character' ? 'active' : ''}">
|
||||
Character Card
|
||||
</button>
|
||||
<button on:click={() => changeDisplay('action')} class="pill {display === 'action' ? 'active' : ''}">
|
||||
Action Card
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button on:click={() => changeSize('base')} class="pill {size === 'base' ? 'active' : ''}">
|
||||
<Icon path={mdiImageSizeSelectLarge} />
|
||||
</button>
|
||||
<button on:click={() => changeSize('small')} class="pill {size === 'small' ? 'active' : ''}">
|
||||
<Icon path={mdiImageSizeSelectSmall} />
|
||||
</button>
|
||||
</div>
|
||||
<button on:click={() => toggleDetail()} class="hidden md:block pill solo {showDetail ? 'active' : ''}">
|
||||
<Icon path={mdiDockWindow} />
|
||||
</button>
|
||||
</div>
|
||||
{#if display === 'action'}
|
||||
<div class="pt-2 flex flex-wrap gap-2 justify-center md:justify-start">
|
||||
{#each Object.entries(filter) as [key, val]}
|
||||
<button on:click={() => toggleFilter(key)} class="pill solo {val ? 'active' : ''}">
|
||||
{tags[key]}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if display === 'character'}
|
||||
<div class="flex flex-wrap justify-center md:justify-start gap-x-4 gap-y-3 pt-8">
|
||||
{#each characters as card}
|
||||
<Card
|
||||
{card}
|
||||
{compare}
|
||||
{size}
|
||||
{showDetail}
|
||||
{smallScreen}
|
||||
count={deck.characters[card.id]}
|
||||
on:compare={() => setCompare(card)}
|
||||
on:addToDeck={() => addToDeck('characters', card)}
|
||||
on:removeFromDeck={() => removeFromDeck('characters', card)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap justify-center md:justify-start gap-x-4 gap-y-3 pt-8">
|
||||
{#each actions as card}
|
||||
<Card
|
||||
{card}
|
||||
{compare}
|
||||
{size}
|
||||
{showDetail}
|
||||
{smallScreen}
|
||||
count={deck.actions[card.id]}
|
||||
on:compare={() => setCompare(card)}
|
||||
on:addToDeck={() => addToDeck('actions', card)}
|
||||
on:removeFromDeck={() => removeFromDeck('actions', card)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="fixed bottom-2 md:bottom-auto md:top-0 right-0 z-50 flex justify-end">
|
||||
{#if !showDeck}
|
||||
<button
|
||||
class="bg-black bg-opacity-80 rounded-lg pl-2 pr-4 py-1 mr-2 mt-2 w-fit hover:ring-2 ring-primary duration-100"
|
||||
transition:fly={{ duration: 100, y: -100 }}
|
||||
on:click={toggleShowDeck}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/tcg/icons/card_character.png" alt="character card" class="w-8" />
|
||||
<p class="text-white text-xl">{characterCount}</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<img src="/images/tcg/icons/card.png" alt="character card" class="w-8" />
|
||||
<p class="text-white text-xl">{actionCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pl-2 text-white text-xs text-center">{$t('tcg.showDeck')}</p>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if compare !== undefined}
|
||||
<button
|
||||
class="fixed bottom-2 right-2 bg-black bg-opacity-80 text-white rounded-lg px-3 py-2 mt-2 mr-2 hover:ring-2 ring-primary duration-100"
|
||||
on:click={removeCompare}
|
||||
transition:fly={{ duration: 100, y: 100 }}
|
||||
>
|
||||
<p>Remove Compare</p>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.pill {
|
||||
@apply border-2 border-white border-opacity-25;
|
||||
@apply text-white text-sm 2xl:text-base;
|
||||
@apply px-2 2xl:px-4 py-1 whitespace-nowrap;
|
||||
@apply outline-none;
|
||||
@apply transition duration-100;
|
||||
@apply first:rounded-l-xl first:border-r-0;
|
||||
@apply last:rounded-r-xl last:border-l-0;
|
||||
|
||||
&.solo {
|
||||
@apply rounded-xl border-2;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@apply bg-primary;
|
||||
@apply border-primary;
|
||||
@apply text-background;
|
||||
}
|
||||
}
|
||||
</style>
|
BIN
static/images/tcg.png
Normal file
After Width: | Height: | Size: 907 B |
BIN
static/images/tcg/abyssal_summons.png
Normal file
After Width: | Height: | Size: 310 KiB |
BIN
static/images/tcg/adeptus_temptation.png
Normal file
After Width: | Height: | Size: 265 KiB |
BIN
static/images/tcg/adventurers_bandana.png
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
static/images/tcg/aquila_favonia.png
Normal file
After Width: | Height: | Size: 284 KiB |
BIN
static/images/tcg/archaic_petra.png
Normal file
After Width: | Height: | Size: 342 KiB |
BIN
static/images/tcg/avatar/barbara.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
static/images/tcg/avatar/bennett.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
static/images/tcg/avatar/chongyun.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
static/images/tcg/avatar/collei.png
Normal file
After Width: | Height: | Size: 98 KiB |
BIN
static/images/tcg/avatar/cyno.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
static/images/tcg/avatar/diluc.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
static/images/tcg/avatar/diona.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
static/images/tcg/avatar/fatui_pyro_agent.png
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
static/images/tcg/avatar/fischl.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
static/images/tcg/avatar/ganyu.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
static/images/tcg/avatar/jadeplume_terrorshroom.png
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
static/images/tcg/avatar/jean.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
static/images/tcg/avatar/kaeya.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
static/images/tcg/avatar/kamisato_ayaka.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
static/images/tcg/avatar/keqing.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
static/images/tcg/avatar/maguu_kenki.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
static/images/tcg/avatar/mirror_maiden.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
static/images/tcg/avatar/mona.png
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
static/images/tcg/avatar/ningguang.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
static/images/tcg/avatar/noelle.png
Normal file
After Width: | Height: | Size: 98 KiB |
BIN
static/images/tcg/avatar/razor.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
static/images/tcg/avatar/rhodeia_of_loch.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
static/images/tcg/avatar/stonehide_lawachurl.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
static/images/tcg/avatar/sucrose.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
static/images/tcg/avatar/xiangling.png
Normal file
After Width: | Height: | Size: 98 KiB |
BIN
static/images/tcg/avatar/xingqiu.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
static/images/tcg/avatar/yoimiya.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
static/images/tcg/awakening.png
Normal file
After Width: | Height: | Size: 262 KiB |
BIN
static/images/tcg/barbara.png
Normal file
After Width: | Height: | Size: 363 KiB |
BIN
static/images/tcg/bennett.png
Normal file
After Width: | Height: | Size: 319 KiB |
BIN
static/images/tcg/blessing_of_the_divine_relics_installation.png
Normal file
After Width: | Height: | Size: 385 KiB |
BIN
static/images/tcg/blizzard_strayer.png
Normal file
After Width: | Height: | Size: 334 KiB |
BIN
static/images/tcg/broken_rimes_echo.png
Normal file
After Width: | Height: | Size: 287 KiB |
BIN
static/images/tcg/calxs_arts.png
Normal file
After Width: | Height: | Size: 313 KiB |
BIN
static/images/tcg/chang_the_ninth.png
Normal file
After Width: | Height: | Size: 229 KiB |
BIN
static/images/tcg/changing_shifts.png
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
static/images/tcg/chaotic_entropy.png
Normal file
After Width: | Height: | Size: 205 KiB |
BIN
static/images/tcg/chef_mao.png
Normal file
After Width: | Height: | Size: 252 KiB |
BIN
static/images/tcg/chongyun.png
Normal file
After Width: | Height: | Size: 252 KiB |
BIN
static/images/tcg/cold-blooded_strike.png
Normal file
After Width: | Height: | Size: 276 KiB |
BIN
static/images/tcg/collei.png
Normal file
After Width: | Height: | Size: 276 KiB |
BIN
static/images/tcg/crimson_witch_of_flames.png
Normal file
After Width: | Height: | Size: 310 KiB |
BIN
static/images/tcg/crossfire.png
Normal file
After Width: | Height: | Size: 255 KiB |
BIN
static/images/tcg/cuilein-anbar.png
Normal file
After Width: | Height: | Size: 224 KiB |
BIN
static/images/tcg/cyno.png
Normal file
After Width: | Height: | Size: 378 KiB |
BIN
static/images/tcg/dandelion_field.png
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
static/images/tcg/dawn_winery.png
Normal file
After Width: | Height: | Size: 261 KiB |
BIN
static/images/tcg/deepwood_memories.png
Normal file
After Width: | Height: | Size: 324 KiB |
BIN
static/images/tcg/diluc.png
Normal file
After Width: | Height: | Size: 355 KiB |