├── .npmrc
├── src
├── routes
│ ├── +layout.ts
│ ├── +layout.svelte
│ └── +page.svelte
├── lib
│ ├── id.ts
│ ├── components
│ │ ├── label-content-grid.svelte
│ │ ├── row.svelte
│ │ ├── icon.svelte
│ │ ├── v-sep.svelte
│ │ ├── bottom.svelte
│ │ ├── add-icon.svelte
│ │ ├── title.svelte
│ │ ├── info-icon.svelte
│ │ ├── number-display.svelte
│ │ ├── close-icon.svelte
│ │ ├── resin.svelte
│ │ ├── cell.svelte
│ │ ├── fodder-input.svelte
│ │ ├── mat-input.svelte
│ │ ├── level-exp-table.svelte
│ │ ├── resin-breakdown.svelte
│ │ ├── required-cells.svelte
│ │ └── exp-input.svelte
│ ├── exp-calcs.ts
│ └── data.ts
├── app.html
├── app.d.ts
└── app.pcss
├── static
├── resin.webp
├── essence.png
└── unction.png
├── .prettierignore
├── vite.config.ts
├── .gitignore
├── README.md
├── postcss.config.cjs
├── .eslintignore
├── .prettierrc
├── tailwind.config.cjs
├── tsconfig.json
├── .eslintrc.cjs
├── svelte.config.js
└── package.json
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/src/routes/+layout.ts:
--------------------------------------------------------------------------------
1 | export const prerender = true;
2 |
--------------------------------------------------------------------------------
/static/resin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrandonXLF/artifact-exp-calc/main/static/resin.webp
--------------------------------------------------------------------------------
/src/lib/id.ts:
--------------------------------------------------------------------------------
1 | let count = 0;
2 |
3 | export function makeId() {
4 | return 'id-' + ++count;
5 | }
6 |
--------------------------------------------------------------------------------
/static/essence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrandonXLF/artifact-exp-calc/main/static/essence.png
--------------------------------------------------------------------------------
/static/unction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrandonXLF/artifact-exp-calc/main/static/unction.png
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore files for PNPM, NPM and YARN
2 | pnpm-lock.yaml
3 | package-lock.json
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/src/lib/components/label-content-grid.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 | build.sh
12 | .htaccess
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Genshin Artifact EXP Calculator
2 |
3 | Calculate the level fodder artifacts and artifact enhancement materials will bring an artifact to and how much artifact EXP / resin is needed to reach a specific level.
4 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const tailwindcss = require('tailwindcss');
2 | const autoprefixer = require('autoprefixer');
3 |
4 | const config = {
5 | plugins: [tailwindcss(), autoprefixer]
6 | };
7 |
8 | module.exports = config;
9 |
--------------------------------------------------------------------------------
/src/lib/components/row.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/lib/components/icon.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/src/lib/components/v-sep.svelte:
--------------------------------------------------------------------------------
1 |
2 |
​
3 |
6 |
7 |
--------------------------------------------------------------------------------
/src/lib/components/bottom.svelte:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/lib/components/add-icon.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/lib/components/title.svelte:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "printWidth": 100,
6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
7 | "overrides": [
8 | {
9 | "files": "*.svelte",
10 | "options": {
11 | "parser": "svelte"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/components/info-icon.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/lib/components/number-display.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 | {formattedNumber}{#if unit}{' '}{unit}{/if}
14 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Genshin Artifact EXP Calculator
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/lib/components/close-icon.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/lib/components/resin.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 |
12 | type Rarity = 1 | 2 | 3 | 4 | 5;
13 | type DomainLevel = 1 | 2 | 3 | 4;
14 | type DomainRarity = 3 | 4 | 5;
15 |
16 | type ResinInfo = {
17 | domainLevel: DomainLevel;
18 | use: Record;
19 | };
20 |
21 | type Fodder = [number, Rarity, number];
22 | }
23 |
24 | export {};
25 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config}*/
2 | const config = {
3 | content: ['./src/**/*.{html,js,svelte,ts}'],
4 |
5 | theme: {
6 | extend: {
7 | borderColor: {
8 | DEFAULT: '#aaa',
9 | light: '#777',
10 | 'extra-light': '#444'
11 | },
12 | colors: {
13 | main: '#222',
14 | secondary: '#333',
15 | input: '#282828'
16 | },
17 | dropShadow: {
18 | popover: '0 3px 6px #222'
19 | },
20 | gridTemplateColumns: {
21 | 'label-content': 'max-content 1fr'
22 | }
23 | }
24 | },
25 |
26 | plugins: []
27 | };
28 |
29 | module.exports = config;
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | //
16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
17 | // from the referenced tsconfig.json - TypeScript does not merge them in
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/components/cell.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {#if title}
11 |
{title}
12 | {/if}
13 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type { import("eslint").Linter.Config } */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:svelte/recommended',
8 | 'prettier'
9 | ],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['@typescript-eslint'],
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaVersion: 2020,
15 | extraFileExtensions: ['.svelte']
16 | },
17 | env: {
18 | browser: true,
19 | es2017: true,
20 | node: true
21 | },
22 | overrides: [
23 | {
24 | files: ['*.svelte'],
25 | parser: 'svelte-eslint-parser',
26 | parserOptions: {
27 | parser: '@typescript-eslint/parser'
28 | }
29 | }
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------
/src/app.pcss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @layer base {
4 | html {
5 | line-height: 1;
6 | }
7 |
8 | body {
9 | @apply bg-main;
10 | @apply text-white;
11 | @apply m-4;
12 | @apply [color-scheme:dark];
13 | @apply [height:unset];
14 | }
15 |
16 | main {
17 | line-height: 1.5;
18 | }
19 |
20 | a {
21 | @apply text-blue-300;
22 | }
23 |
24 | a:focus,
25 | a:hover {
26 | @apply underline;
27 | }
28 |
29 | summary {
30 | @apply cursor-pointer;
31 | @apply select-none;
32 | }
33 |
34 | select,
35 | input[type='number'] {
36 | @apply px-1;
37 | @apply py-0.5;
38 | @apply border;
39 | @apply border-light;
40 | @apply rounded-md;
41 | @apply bg-input;
42 | }
43 |
44 | button {
45 | @apply border-none;
46 | @apply hover:bg-secondary;
47 | @apply focus:bg-secondary;
48 | }
49 |
50 | h3 {
51 | @apply mb-2;
52 | }
53 | }
54 |
55 | @tailwind components;
56 | @tailwind utilities;
57 |
--------------------------------------------------------------------------------
/src/lib/components/fodder-input.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | {#each fodderList as [exp, rarity, times], i}
16 |
17 | deleteFodder(i)}
24 | />
25 | |
26 | {/each}
27 |
28 |
(fodderList = [...fodderList, [0, 4, 1]])}
32 | >
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-static';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: [vitePreprocess({})],
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter({
15 | // default options are shown. On some platforms
16 | // these options are set automatically — see below
17 | pages: 'build',
18 | assets: 'build',
19 | fallback: undefined,
20 | precompress: false,
21 | strict: true
22 | })
23 | }
24 | };
25 |
26 | export default config;
27 |
--------------------------------------------------------------------------------
/src/lib/components/mat-input.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | =
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/lib/components/level-exp-table.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 | Level
12 | Total EXP
13 | EXP to Next
14 |
15 |
16 |
17 | {#each EXP_AMOUNTS[rarity] as x, i}
18 |
19 |
20 | {i}
21 |
22 |
23 |
24 |
25 | {#if i === 0}
26 |
27 | {/if}
28 |
29 |
30 | {#if i < LEVEL_MAXES[rarity]}
31 |
32 | +
33 |
34 | {:else}
35 |
36 | {/if}
37 |
38 | {/each}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "artifact-exp-calc",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite dev",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
12 | "lint": "prettier --check . && eslint .",
13 | "format": "prettier --write ."
14 | },
15 | "devDependencies": {
16 | "@sveltejs/adapter-static": "^3.0.1",
17 | "@sveltejs/kit": "^2.0.0",
18 | "@sveltejs/vite-plugin-svelte": "^3.0.0",
19 | "@types/eslint": "^8.56.0",
20 | "@typescript-eslint/eslint-plugin": "^7.0.0",
21 | "@typescript-eslint/parser": "^7.0.0",
22 | "autoprefixer": "^10.4.16",
23 | "eslint": "^8.56.0",
24 | "eslint-config-prettier": "^9.1.0",
25 | "eslint-plugin-svelte": "^2.35.1",
26 | "postcss": "^8.4.32",
27 | "postcss-load-config": "^5.0.2",
28 | "prettier": "^3.1.1",
29 | "prettier-plugin-svelte": "^3.1.2",
30 | "prettier-plugin-tailwindcss": "^0.5.9",
31 | "svelte": "^4.2.7",
32 | "svelte-check": "^3.6.0",
33 | "svelte-popperjs": "^1.3.2",
34 | "tailwindcss": "^3.3.6",
35 | "tslib": "^2.4.1",
36 | "typescript": "^5.0.0",
37 | "vite": "^5.0.3"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/lib/exp-calcs.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ARTIFACTS_PER_RUN,
3 | DOMAIN_RARITIES,
4 | EXP_AMOUNTS,
5 | EXP_BASES,
6 | FODDER_EXP_VALUE,
7 | LEVEL_MAXES
8 | } from './data';
9 |
10 | export function calcFodderExp(exp: number, rarity: Rarity) {
11 | return EXP_BASES[rarity] + exp * FODDER_EXP_VALUE;
12 | }
13 |
14 | export function calculateTotalExp(current: number, mat: number, fodderList: Fodder[]) {
15 | return (
16 | current +
17 | mat +
18 | fodderList.reduce((prev, [exp, rarity, times]) => prev + calcFodderExp(exp, rarity) * times, 0)
19 | );
20 | }
21 |
22 | export function calcRunsRequired(exp: number, { domainLevel, use }: ResinInfo) {
23 | let expPerRun = 0;
24 |
25 | for (const rarity of DOMAIN_RARITIES) {
26 | if (!use[rarity]) continue;
27 | expPerRun += EXP_BASES[rarity] * ARTIFACTS_PER_RUN[domainLevel][rarity];
28 | }
29 |
30 | return exp / expPerRun;
31 | }
32 |
33 | export function calcLevelAndRemainder(exp: number, rarity: Rarity): [number, number] {
34 | let start = 0;
35 | let end: number = EXP_AMOUNTS[rarity].length;
36 |
37 | while (end - start > 1) {
38 | const currentIndex = Math.floor((start + end) / 2);
39 |
40 | if (EXP_AMOUNTS[rarity][currentIndex] < exp) {
41 | start = currentIndex;
42 | } else if (EXP_AMOUNTS[rarity][currentIndex] > exp) {
43 | end = currentIndex;
44 | } else {
45 | return [currentIndex, 0];
46 | }
47 | }
48 |
49 | return [start, exp - EXP_AMOUNTS[rarity][start]];
50 | }
51 |
52 | export function calcMaxExp(rarity: Rarity) {
53 | return EXP_AMOUNTS[rarity][LEVEL_MAXES[rarity]]!;
54 | }
55 |
--------------------------------------------------------------------------------
/src/lib/components/resin-breakdown.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 | Domain level:
24 |
25 | 1
26 | 2
27 | 3
28 | 4
29 |
30 |
31 |
32 |
60 |
61 |
--------------------------------------------------------------------------------
/src/lib/data.ts:
--------------------------------------------------------------------------------
1 | export const FODDER_EXP_VALUE = 0.8;
2 | export const AVERAGE_BONUS = 1.13;
3 |
4 | type LevelValues = T extends { length: L }
5 | ? [number, ...T]
6 | : LevelValues;
7 |
8 | type RarityLevelValues = {
9 | [K in Rarity]: LevelValues<(typeof LEVEL_MAXES)[K]>;
10 | };
11 |
12 | export const EXP_AMOUNTS: RarityLevelValues = {
13 | 5: [
14 | 0, 3000, 6725, 11150, 16300, 22200, 28875, 36375, 44725, 53950, 64075, 75125, 87150, 100175,
15 | 115325, 132925, 153300, 176800, 203850, 234900, 270475
16 | ],
17 | 4: [
18 | 0, 2400, 5375, 8925, 13050, 17775, 23125, 29125, 35800, 43175, 51275, 60125, 69750, 80175,
19 | 92300, 106375, 122675
20 | ],
21 | 3: [0, 1800, 4025, 6675, 9775, 13325, 17325, 21825, 26825, 32350, 38425, 45050, 52275],
22 | 2: [0, 1200, 2700, 4475, 6525],
23 | 1: [0, 600, 1350, 2225, 3250]
24 | };
25 |
26 | export const EXP_MAXES: RarityLevelValues = {
27 | 5: [
28 | 3000, 3725, 4425, 5150, 5900, 6675, 7500, 8350, 9225, 10125, 11050, 12025, 13025, 15150, 17600,
29 | 20375, 23500, 27050, 31050, 35575, 0
30 | ],
31 | 4: [
32 | 2400, 2975, 3550, 4125, 4725, 5350, 6000, 6675, 7375, 8100, 8850, 9625, 10425, 12125, 14075,
33 | 16300, 0
34 | ],
35 | 3: [1800, 2225, 2650, 3100, 3550, 4000, 4500, 5000, 5525, 6075, 6625, 7225, 0],
36 | 2: [1200, 1500, 1775, 2050, 0],
37 | 1: [600, 750, 875, 1025, 0]
38 | };
39 |
40 | export const EXP_BASES: Record = {
41 | 1: 420,
42 | 2: 840,
43 | 3: 1260,
44 | 4: 2520,
45 | 5: 3780
46 | };
47 |
48 | export const LEVEL_MAXES = {
49 | 5: 20,
50 | 4: 16,
51 | 3: 12,
52 | 2: 4,
53 | 1: 4
54 | } as const satisfies Record;
55 |
56 | export const ARTIFACTS_PER_RUN: Record> = {
57 | 1: {
58 | 3: 6.39,
59 | 4: 0.71,
60 | 5: 0
61 | },
62 | 2: {
63 | 3: 5.68,
64 | 4: 1.42,
65 | 5: 0
66 | },
67 | 3: {
68 | 3: 4.97,
69 | 4: 1.775,
70 | 5: 0.355
71 | },
72 | 4: {
73 | 3: 3.55,
74 | 4: 2.485,
75 | 5: 1.064
76 | }
77 | };
78 |
79 | export const RESIN_PER_RUN = 20;
80 | export const DOMAIN_RARITIES = [3, 4, 5] as DomainRarity[];
81 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | |
34 |
35 |
36 |
37 | |
38 |
39 |
40 |
41 | |
42 |
43 |
44 |
45 | Show EXP at each level
46 |
47 |
48 |
49 | |
50 |
51 |
52 |
53 |
54 |
55 |
56 | |
57 |
58 |
59 |
60 |
61 | |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/lib/components/required-cells.svelte:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 | Average
35 |
36 |
37 | ≈
38 |
39 |
40 | x1 Only
41 |
42 |
43 | ≈
44 |
45 |
46 |
47 | |
48 |
49 |
50 |
​
51 |
52 |
(showResinInfo = false)}>
53 |
{
60 | if (!mouseEnterTick) {
61 | showResinInfo = !showResinInfo;
62 | }
63 | }}
64 | on:mouseenter={() => {
65 | showResinInfo = true;
66 |
67 | mouseEnterTick = true;
68 | setTimeout(() => (mouseEnterTick = false));
69 | }}
70 | >
71 |
72 |
73 | {#if showResinInfo}
74 |
82 |
87 |
88 | |
89 |
90 | {/if}
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/lib/components/exp-input.svelte:
--------------------------------------------------------------------------------
1 |
58 |
59 |
60 | {#if !noRaritySet}
61 | Rarity
62 | setRarity(+e.target.value)}>
63 | 1
64 | 2
65 | 3
66 | 4
67 | 5
68 |
69 |
70 | {/if}
71 | Level
72 | {#if readOnly}
73 |
74 | {:else}
75 | setExp(e.currentTarget.valueAsNumber, remainder, true)}
84 | />
85 | {/if}
86 | {#if !noRemainder}
87 | EXP
88 | {#if readOnly}
89 |
90 |
91 | /
92 |
93 |
94 | {:else}
95 | setExp(level, e.currentTarget.valueAsNumber)}
104 | />
105 | {/if}
106 | {/if}
107 | =
108 |
109 | {#if !noRaritySet && (isFodder || showTimes)}
110 |
111 | {/if}
112 | {#if isFodder}
113 | Value
114 |
115 | {/if}
116 | {#if showTimes}
117 | ×
118 |
119 |
120 | dispatch('delete')}>
121 |
122 |
123 |
124 | {/if}
125 |
126 |
--------------------------------------------------------------------------------