Migrate to SvelteKit

Close #158

commit b759884dce
Author: Made Baruna <made.setia@gmail.com>
Date:   Thu Jul 21 21:57:38 2022 +0700

    Add update popup

commit 00f8b192af
Author: Made Baruna <made.setia@gmail.com>
Date:   Thu Jul 21 20:09:18 2022 +0700

    Add service worker

commit 1cd1e40c77
Author: Made Baruna <made.setia@gmail.com>
Date:   Thu Jul 21 11:38:37 2022 +0700

    Update firebase config

commit edc036f62f
Author: Made Baruna <made.setia@gmail.com>
Date:   Wed Jul 20 23:33:38 2022 +0700

    Separate build getter

commit e780ab18bf
Author: Made Baruna <made.setia@gmail.com>
Date:   Wed Jul 20 22:16:28 2022 +0700

    Update readme

commit 7f0890acba
Author: Made Baruna <made.setia@gmail.com>
Date:   Wed Jul 20 22:07:25 2022 +0700

    Fix createEnv

commit 1df04e369f
Author: Made Baruna <made.setia@gmail.com>
Date:   Wed Jul 20 22:03:12 2022 +0700

    Migrate to svelte-kit
This commit is contained in:
Made Baruna 2022-07-21 22:24:14 +07:00
parent b06d4d5514
commit 593938c36d
103 changed files with 2761 additions and 5020 deletions

2
.gitignore vendored
View file

@ -3,4 +3,6 @@
/src/node_modules/@sapper/ /src/node_modules/@sapper/
yarn-error.log yarn-error.log
/__sapper__/ /__sapper__/
.svelte-kit
/build
.env .env

1
.npmrc Normal file
View file

@ -0,0 +1 @@
auto-install-peers=true

13
.prettierignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120
}

View file

@ -1,7 +0,0 @@
module.exports = {
trailingComma: 'all',
tabWidth: 2,
semi: true,
singleQuote: true,
printWidth: 120,
};

View file

@ -4,13 +4,13 @@
Your best Genshin Impact companion! Paimon.moe helps you plan what to farm with ascension calculator and database. It also tracks your progress with a todo list and a wish counter. Your best Genshin Impact companion! Paimon.moe helps you plan what to farm with ascension calculator and database. It also tracks your progress with a todo list and a wish counter.
Created with [Sapper](https://sapper.svelte.dev/) + [Tailwind CSS](https://tailwindcss.com/) Created with [SvelteKit](https://kit.svelte.dev/) + [Tailwind CSS](https://tailwindcss.com/)
# Development # Development
``` ```
# install dependencies # install dependencies
yarn pnpm i
# you need the api to run wish importer and wish tally # you need the api to run wish importer and wish tally
git clone https://github.com/MadeBaruna/paimon-moe-api git clone https://github.com/MadeBaruna/paimon-moe-api
@ -20,10 +20,10 @@ docker-compose up -d
# run in dev mode # run in dev mode
cp .env.example .env cp .env.example .env
vi .env vi .env
yarn dev pnpm dev
# export as production static site # export as production static site
yarn export pnpm build
``` ```
# License # License

View file

@ -1,50 +1,34 @@
{ {
"type": "module",
"name": "paimon-moe", "name": "paimon-moe",
"description": "A collection of tools to help playing Genshin Impact", "description": "A collection of tools to help playing Genshin Impact",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "NODE_OPTIONS=--max_old_space_size=4096 sapper dev", "dev": "vite dev --port 3000",
"build": "sapper build --legacy", "build": "NODE_OPTIONS=--max_old_space_size=4096 vite build",
"export": "NODE_OPTIONS=--max_old_space_size=4096 sapper export --legacy && cp __sapper__/build/firebase-messaging-sw.js __sapper__/export", "preview": "vite preview",
"start": "node __sapper__/build" "start": "node build"
},
"dependencies": {
"compression": "^1.7.1",
"exceljs": "^4.2.1",
"localforage": "^1.9.0",
"polka": "next",
"prettier": "^2.2.1",
"sirv": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/runtime": "^7.0.0",
"@mdi/js": "^5.9.55", "@mdi/js": "^5.9.55",
"@rollup/plugin-babel": "^5.0.0", "@sveltejs/adapter-static": "1.0.0-next.37",
"@rollup/plugin-commonjs": "^16.0.0", "@sveltejs/kit": "^1.0.0-next.379",
"@rollup/plugin-dynamic-import-vars": "^1.1.1", "autoprefixer": "^10.4.7",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-replace": "^2.3.4",
"@rollup/plugin-url": "^5.0.0",
"autoprefixer": "^10.0.1",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"dayjs": "^1.9.4", "dayjs": "^1.9.4",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"postcss": "^8.2.10", "exceljs": "^4.3.0",
"postcss-load-config": "^3.0.0", "localforage": "^1.10.0",
"postcss-nested": "^5.0.1", "lodash.debounce": "^4.0.8",
"rollup": "^2.3.4", "postcss": "^8.4.14",
"rollup-plugin-svelte": "^7.0.0", "postcss-nested": "^5.0.6",
"rollup-plugin-terser": "^7.0.0", "prettier": "^2.7.1",
"sapper": "^0.28.0", "prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.17.3", "svelte": "^3.17.3",
"svelte-i18n": "^3.3.6", "svelte-i18n": "^3.4.0",
"svelte-preprocess": "^4.5.1", "svelte-preprocess": "^4.10.7",
"svelte-simple-modal": "^0.6.1", "svelte-simple-modal": "^0.6.1",
"tailwindcss": "^1.9.5" "tailwindcss": "^3.1.6",
"vite": "^3.0.2"
} }
} }

1687
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

3
postcss.config.cjs Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
plugins: [require('postcss-nested'), require('tailwindcss'), require('autoprefixer')],
};

View file

@ -1,3 +0,0 @@
module.exports = {
plugins: [require('tailwindcss'), require('postcss-nested'), require('autoprefixer')],
};

View file

@ -1,172 +0,0 @@
import path from 'path';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import commonjs from '@rollup/plugin-commonjs';
import url from '@rollup/plugin-url';
import svelte from 'rollup-plugin-svelte';
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import json from '@rollup/plugin-json';
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
import config from 'sapper/config/rollup.js';
import { config as envConfig } from 'dotenv';
import pkg from './package.json';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
const preprocess = require('./svelte.config').preprocess;
const onwarn = (warning, onwarn) =>
(warning.code === 'MISSING_EXPORT' && /'preload'/.test(warning.message)) ||
(warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) ||
onwarn(warning);
const envData = {};
Object.entries(envConfig().parsed).forEach(([key, val]) => {
envData[`__paimon.env.${key}`] = `'${val}'`;
});
export default {
client: {
input: config.client.input(),
output: {
...config.client.output(),
sourcemap: dev ? 'inline' : true,
},
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
...envData,
}),
svelte({
compilerOptions: {
dev,
hydratable: true,
},
emitCss: true,
preprocess,
}),
url({
sourceDir: path.resolve(__dirname, 'src/node_modules/images'),
publicPath: '/client/',
}),
resolve({
browser: true,
dedupe: ['svelte'],
}),
commonjs(),
json(),
dynamicImportVars({
include: [
'**/*.svelte',
'**/*.json',
],
}),
legacy &&
babel({
extensions: ['.js', '.mjs', '.html', '.svelte'],
babelHelpers: 'runtime',
exclude: ['node_modules/@babel/**'],
presets: [
[
'@babel/preset-env',
{
targets: '> 0.25%, not dead',
},
],
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-transform-runtime',
{
useESModules: true,
},
],
],
}),
!dev &&
terser({
module: true,
}),
],
preserveEntrySignatures: false,
onwarn,
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode),
...envData,
}),
svelte({
compilerOptions: {
generate: 'ssr',
hydratable: true,
dev,
},
preprocess,
}),
url({
sourceDir: path.resolve(__dirname, 'src/node_modules/images'),
publicPath: '/client/',
emitFiles: false, // already emitted by client build
}),
resolve({
dedupe: ['svelte'],
}),
commonjs(),
json(),
dynamicImportVars({
include: [
'**/*.svelte',
'**/*.json',
],
}),
],
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),
preserveEntrySignatures: 'strict',
onwarn,
},
serviceworker: {
input: config.serviceworker.input().replace('service-worker', 'firebase-messaging-sw'),
output: {
...config.serviceworker.output(),
file: config.serviceworker.output().file.replace('service-worker', 'firebase-messaging-sw'),
},
plugins: [replace(envData), resolve(), commonjs(), !dev && terser()],
preserveEntrySignatures: false,
onwarn,
},
// serviceworker: {
// input: config.serviceworker.input(),
// output: config.serviceworker.output(),
// plugins: [
// resolve(),
// replace({
// 'process.browser': true,
// 'process.env.NODE_ENV': JSON.stringify(mode),
// }),
// commonjs(),
// !dev && terser(),
// ],
// preserveEntrySignatures: false,
// onwarn,
// },
};

View file

@ -1,14 +1,14 @@
const fs = require('fs'); import fs from 'fs';
let envString = ''; let envString = '';
envString += `GOOGLE_DRIVE_CLIENT_ID=${process.env.GOOGLE_DRIVE_CLIENT_ID}\n`; envString += `VITE_GOOGLE_DRIVE_CLIENT_ID=${process.env.GOOGLE_DRIVE_CLIENT_ID}\n`;
envString += `GOOGLE_DRIVE_API_KEY=${process.env.GOOGLE_DRIVE_API_KEY}\n`; envString += `VITE_GOOGLE_DRIVE_API_KEY=${process.env.GOOGLE_DRIVE_API_KEY}\n`;
envString += `API_HOST=${process.env.API_HOST}\n`; envString += `VITE_API_HOST=${process.env.API_HOST}\n`;
envString += `FIREBASE_API_KEY=${process.env.FIREBASE_API_KEY}\n` envString += `VITE_FIREBASE_API_KEY=${process.env.FIREBASE_API_KEY}\n`;
envString += `FIREBASE_AUTH_DOMAIN=${process.env.FIREBASE_AUTH_DOMAIN}\n` envString += `VITE_FIREBASE_AUTH_DOMAIN=${process.env.FIREBASE_AUTH_DOMAIN}\n`;
envString += `FIREBASE_PROJECT_ID=${process.env.FIREBASE_PROJECT_ID}\n` envString += `VITE_FIREBASE_PROJECT_ID=${process.env.FIREBASE_PROJECT_ID}\n`;
envString += `FIREBASE_STORAGE_BUCKET=${process.env.FIREBASE_STORAGE_BUCKET}\n` envString += `VITE_FIREBASE_STORAGE_BUCKET=${process.env.FIREBASE_STORAGE_BUCKET}\n`;
envString += `FIREBASE_MESSAGING_SENDER_ID=${process.env.FIREBASE_MESSAGING_SENDER_ID}\n` envString += `VITE_FIREBASE_MESSAGING_SENDER_ID=${process.env.FIREBASE_MESSAGING_SENDER_ID}\n`;
envString += `FIREBASE_APP_ID=${process.env.FIREBASE_APP_ID}\n` envString += `VITE_FIREBASE_APP_ID=${process.env.FIREBASE_APP_ID}\n`;
fs.writeFileSync('.env', envString); fs.writeFileSync('.env', envString);

3
src/app.css Normal file
View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -8,14 +8,12 @@
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content="https://paimon.moe/paimon-og.png" /> <meta property="og:image" content="https://paimon.moe/paimon-og.png" />
%sapper.base%
<link <link
href="https://fonts.googleapis.com/css2?family=Catamaran:wght@600;700;900&family=Poppins:wght@400;500;600&display=swap" href="https://fonts.googleapis.com/css2?family=Catamaran:wght@600;700;900&family=Poppins:wght@400;500;600&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<link rel="manifest" href="manifest.json" crossorigin="use-credentials" /> <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="/favicon.png" />
<!-- <script src="https://js.sentry-cdn.com/446c4cef71a54aafb71b698555500b7d.min.js" crossorigin="anonymous"></script> --> <!-- <script src="https://js.sentry-cdn.com/446c4cef71a54aafb71b698555500b7d.min.js" crossorigin="anonymous"></script> -->
<script async defer data-domain="paimon.moe" src="https://plausible.paimon.moe/js/paimonmoe.js"></script> <script async defer data-domain="paimon.moe" src="https://plausible.paimon.moe/js/paimonmoe.js"></script>
@ -26,26 +24,14 @@
<script src="https://www.gstatic.com/firebasejs/8.3.2/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/8.3.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.3.2/firebase-messaging.js"></script> <script src="https://www.gstatic.com/firebasejs/8.3.2/firebase-messaging.js"></script>
<style> <style lang="postcss">
html { html {
height: 100%; height: 100%;
background: #25294a; background: #25294a;
} }
</style> </style>
<!-- Sapper creates a <script> tag containing `src/client.js` %sveltekit.head%
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
<!-- Sapper generates a <style> tag containing critical CSS
for the current page. CSS for the rest of the app is
lazily loaded when it precaches secondary pages -->
%sapper.styles%
<!-- This contains the contents of the <svelte:head> component, if
the current page has one -->
%sapper.head%
<script> <script>
window.AdSlots = window.AdSlots || { cmd: [], disableScripts: ['gpt'] }; window.AdSlots = window.AdSlots || { cmd: [], disableScripts: ['gpt'] };
@ -54,8 +40,6 @@
<script async src="https://kumo.network-n.com/dist/app.js?n=2" site="paimonmoe"></script> <script async src="https://kumo.network-n.com/dist/app.js?n=2" site="paimonmoe"></script>
</head> </head>
<body class="font-body h-full bg-background-secondary"> <body class="font-body h-full bg-background-secondary">
<!-- The application will be rendered inside this element, <div id="sapper" class="flex flex-col h-full">%sveltekit.body%</div>
because `src/client.js` references it -->
<div id="sapper" class="flex flex-col h-full">%sapper.html%</div>
</body> </body>
</html> </html>

View file

@ -1,20 +0,0 @@
import * as sapper from '@sapper/app';
import localforage from 'localforage';
import { startClient } from './i18n.js';
console.log('localforage config');
localforage.config({
driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
name: 'paimonmoe',
version: 1.0,
description: 'paimonmoe local storage',
});
window.localforage = localforage;
startClient();
sapper.start({
target: document.querySelector('#sapper'),
});

View file

@ -2,7 +2,7 @@
export let type; export let type;
export let variant; export let variant;
export let id; export let id;
export let style; export let style = '';
let _class = ''; let _class = '';
export { _class as class }; export { _class as class };

View file

@ -166,7 +166,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.hovered { .hovered {
@apply text-white !important; @apply text-white !important;
@apply bg-primary; @apply bg-primary;

View file

@ -6,9 +6,9 @@
</script> </script>
<label <label
class="checkbox-wrapper flex flex-1 pl-4 items-center rounded-2xl h-14 {disabled ? 'cursor-not-allowed' : 'cursor-pointer'} {inverted class="checkbox-wrapper flex flex-1 pl-4 items-center rounded-2xl h-14 {disabled
? 'bg-item' ? 'cursor-not-allowed'
: 'bg-background'} {className}" : 'cursor-pointer'} {inverted ? 'bg-item' : 'bg-background'} {className}"
style="--bg: {inverted ? '#202442' : '#2D325A'};" style="--bg: {inverted ? '#202442' : '#2D325A'};"
> >
<span class="flex-1 text-white"><slot /></span> <span class="flex-1 text-white"><slot /></span>
@ -21,7 +21,7 @@
</svg> </svg>
</label> </label>
<style> <style lang="postcss">
.checkbox { .checkbox {
fill: none; fill: none;

View file

@ -12,8 +12,8 @@
const { open: openModal, close: closeModal } = getContext('simple-modal'); const { open: openModal, close: closeModal } = getContext('simple-modal');
const CLIENT_ID = __paimon.env.GOOGLE_DRIVE_CLIENT_ID; const CLIENT_ID = import.meta.env.VITE_GOOGLE_DRIVE_CLIENT_ID;
const API_KEY = __paimon.env.GOOGLE_DRIVE_API_KEY; const API_KEY = import.meta.env.VITE_GOOGLE_DRIVE_API_KEY;
const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']; const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'];
const SCOPES = 'https://www.googleapis.com/auth/drive.appdata'; const SCOPES = 'https://www.googleapis.com/auth/drive.appdata';

View file

@ -18,7 +18,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.header::after { .header::after {
content: ''; content: '';
top: -40px; top: -40px;

View file

@ -52,7 +52,7 @@
{/if} {/if}
{#if spin !== false} {#if spin !== false}
{#if inverse} {#if inverse}
<style> <style lang="postcss">
@keyframes spin-inverse { @keyframes spin-inverse {
to { to {
transform: rotate(-360deg); transform: rotate(-360deg);
@ -60,7 +60,7 @@
} }
</style> </style>
{:else} {:else}
<style> <style lang="postcss">
@keyframes spin { @keyframes spin {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
@ -76,7 +76,7 @@
{/if} {/if}
</svg> </svg>
<style> <style lang="postcss">
svg { svg {
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;

View file

@ -81,27 +81,6 @@
} }
</script> </script>
<!--
$w: var(--col-width); // minmax(Min(20em, 100%), 1fr);
$s: var(--grid-gap); // .5em;
-->
<style>
:global(.__grid--masonry) {
display: grid;
grid-template-columns: repeat(auto-fit, var(--col-width));
grid-template-rows: masonry;
justify-content: center;
grid-gap: var(--grid-gap);
padding: var(--grid-gap);
}
:global(.__grid--masonry > *) {
align-self: start;
}
:global(.__grid--masonry.__stretch-first > *:first-child) {
grid-column: 1/ -1;
}
</style>
<!-- <!--
An almost direct copy and paste of: https://css-tricks.com/a-lightweight-masonry-solution An almost direct copy and paste of: https://css-tricks.com/a-lightweight-masonry-solution
Usage: Usage:
@ -124,6 +103,28 @@ $s: var(--grid-gap); // .5em;
<div <div
bind:this={masonryElement} bind:this={masonryElement}
class={`__grid--masonry ${stretchFirst ? '__stretch-first' : ''}`} class={`__grid--masonry ${stretchFirst ? '__stretch-first' : ''}`}
style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}> style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}
>
<slot /> <slot />
</div> </div>
<!--
$w: var(--col-width); // minmax(Min(20em, 100%), 1fr);
$s: var(--grid-gap); // .5em;
-->
<style lang="postcss">
:global(.__grid--masonry) {
display: grid;
grid-template-columns: repeat(auto-fit, var(--col-width));
grid-template-rows: masonry;
justify-content: center;
grid-gap: var(--grid-gap);
padding: var(--grid-gap);
}
:global(.__grid--masonry > *) {
align-self: start;
}
:global(.__grid--masonry.__stretch-first > *:first-child) {
grid-column: 1/ -1;
}
</style>

View file

@ -145,7 +145,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.hovered { .hovered {
@apply text-white !important; @apply text-white !important;
@apply bg-primary; @apply bg-primary;

View file

@ -0,0 +1,51 @@
<script>
import { getContext, onMount } from 'svelte';
import { page } from '$app/stores';
import UpdateModal from './UpdateModal.svelte';
const { open, close } = getContext('simple-modal');
let broadcastChannel;
page.subscribe((p) => {
if (broadcastChannel) {
broadcastChannel.postMessage({
type: 'fetch-doc',
path: p.url.pathname,
});
}
});
async function refreshUpdate() {
open(
UpdateModal,
{
close,
},
{
closeButton: false,
styleWindow: { background: '#25294A', width: '500px' },
},
);
}
onMount(() => {
if ('serviceWorker' in navigator) {
broadcastChannel = new BroadcastChannel('paimonmoe-sw');
broadcastChannel.addEventListener('message', (event) => {
if (event.data.type === 'update') refreshUpdate();
});
navigator.serviceWorker.register('/service-worker.js').then(
function () {
console.log('service worker registration succeeded');
},
function (error) {
console.log('service worker registration failed:', error);
},
);
} else {
console.log('service workers are not supported');
}
});
</script>

View file

@ -1,6 +1,5 @@
<script> <script>
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { getContext } from 'svelte';
import { mdiCloseCircle } from '@mdi/js'; import { mdiCloseCircle } from '@mdi/js';
import { locale, t } from 'svelte-i18n'; import { locale, t } from 'svelte-i18n';
@ -30,7 +29,8 @@
{ id: 'pt', label: 'Português' }, { id: 'pt', label: 'Português' },
{ id: 'ru', label: 'Русский' }, { id: 'ru', label: 'Русский' },
]; ];
$: currentLocale = languages.find((e) => e.id === $locale.substring(0, 2)) || { id: 'en', label: 'English' }; $: currentLocale =
$locale !== null ? languages.find((e) => e.id === $locale.substring(0, 2)) || { id: 'en', label: 'English' } : '';
$: locales = languages.filter((e) => e.id !== currentLocale.id); $: locales = languages.filter((e) => e.id !== currentLocale.id);
function close() { function close() {
@ -60,7 +60,7 @@
{/if} {/if}
<SidebarItem <SidebarItem
on:clicked={close} on:clicked={close}
active={segment === undefined} active={segment === ''}
image="/images/home.png" image="/images/home.png"
label={$t('sidebar.home')} label={$t('sidebar.home')}
href="/" href="/"
@ -160,7 +160,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.paimon-bg::after { .paimon-bg::after {
content: ''; content: '';
top: -50px; top: -50px;

View file

@ -26,7 +26,7 @@
</div> </div>
</a> </a>
<style> <style lang="postcss">
.active { .active {
@apply bg-primary; @apply bg-primary;
@apply bg-opacity-75; @apply bg-opacity-75;

View file

@ -80,7 +80,7 @@
</div> </div>
{/if} {/if}
<style> <style lang="postcss">
.active { .active {
@apply bg-primary; @apply bg-primary;
@apply bg-opacity-75; @apply bg-opacity-75;

View file

@ -27,7 +27,7 @@
/> />
</div> </div>
<style> <style lang="postcss">
.container { .container {
height: fit-content; height: fit-content;
} }

View file

@ -18,7 +18,7 @@
} }
</script> </script>
<i on:mouseover={mouseOver} on:mouseleave={mouseLeave} on:mousemove={mouseMove}> <i on:mouseover={mouseOver} on:focus={mouseOver} on:mouseleave={mouseLeave} on:mousemove={mouseMove}>
<slot /> <slot />
</i> </i>
@ -26,7 +26,7 @@
<div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div> <div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div>
{/if} {/if}
<style> <style lang="postcss">
.tooltip { .tooltip {
@apply p-2 absolute rounded-xl bg-gray-400 border border-gray-800; @apply p-2 absolute rounded-xl bg-gray-400 border border-gray-800;
@apply text-sm text-background z-10; @apply text-sm text-background z-10;

View file

@ -16,7 +16,7 @@
} }
</script> </script>
<span on:mouseover={mouseOver} on:mouseleave={mouseLeave} bind:this={ref}> <span on:mouseover={mouseOver} on:focus={mouseOver} on:mouseleave={mouseLeave} bind:this={ref}>
<slot /> <slot />
</span> </span>
@ -24,7 +24,7 @@
<div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div> <div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div>
{/if} {/if}
<style> <style lang="postcss">
.tooltip { .tooltip {
@apply p-2 fixed rounded-xl bg-gray-400 border border-gray-800; @apply p-2 fixed rounded-xl bg-gray-400 border border-gray-800;
@apply text-sm text-background z-10; @apply text-sm text-background z-10;

View file

@ -0,0 +1,26 @@
<script>
import { t } from 'svelte-i18n';
import Button from '../components/Button.svelte';
export let close;
function reload() {
window.location.reload();
}
</script>
<div>
<p class="text-white font-bold mb-1 text-lg">{$t('update.newUpdate')}</p>
<p class="text-gray-400 mb-4">{$t('update.updateRefresh')}</p>
<div class="rounded-xl bg-background p-4 mb-4">
<p class="text-gray-200">{$t('update.whatsNew')}</p>
<ul class="list-disc text-white list-inside">
<li>Bug Fixes</li>
</ul>
</div>
<div class="flex justify-end space-x-2">
<Button on:click={close}>{$t('update.later')}</Button>
<Button on:click={reload} color="green">{$t('update.refresh')}</Button>
</div>
</div>

View file

@ -1,171 +1,169 @@
<script> <script>
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
// props // props
export let items; export let items;
export let height = '100%'; export let height = '100%';
export let itemHeight = undefined; export let itemHeight = undefined;
let foo; let foo;
// read-only, but visible to consumers via bind:start // read-only, but visible to consumers via bind:start
export let start = 0; export let start = 0;
export let end = 0; export let end = 0;
// local state // local state
let height_map = []; let height_map = [];
let rows; let rows;
let viewport; let viewport;
let contents; let contents;
let viewport_height = 0; let viewport_height = 0;
let visible; let visible;
let mounted; let mounted;
let top = 0; let top = 0;
let bottom = 0; let bottom = 0;
let average_height; let average_height;
$: visible = items.slice(start, end).map((data, i) => { $: visible = items.slice(start, end).map((data, i) => {
return { index: i + start, data }; return { index: i + start, data };
}); });
// whenever `items` changes, invalidate the current heightmap // whenever `items` changes, invalidate the current heightmap
$: if (mounted) refresh(items, viewport_height, itemHeight); $: if (mounted) refresh(items, viewport_height, itemHeight);
async function refresh(items, viewport_height, itemHeight) { async function refresh(items, viewport_height, itemHeight) {
const { scrollTop } = viewport; const { scrollTop } = viewport;
await tick(); // wait until the DOM is up to date await tick(); // wait until the DOM is up to date
let content_height = top - scrollTop; let content_height = top - scrollTop;
let i = start; let i = start;
while (content_height < viewport_height && i < items.length) { while (content_height < viewport_height && i < items.length) {
let row = rows[i - start]; let row = rows[i - start];
if (!row) { if (!row) {
end = i + 1; end = i + 1;
await tick(); // render the newly visible row await tick(); // render the newly visible row
row = rows[i - start]; row = rows[i - start];
} }
const row_height = height_map[i] = itemHeight || row.offsetHeight; const row_height = (height_map[i] = itemHeight || row.offsetHeight);
content_height += row_height; content_height += row_height;
i += 1; i += 1;
} }
end = i; end = i;
const remaining = items.length - end; const remaining = items.length - end;
average_height = (top + content_height) / end; average_height = (top + content_height) / end;
bottom = remaining * average_height; bottom = remaining * average_height;
height_map.length = items.length; height_map.length = items.length;
viewport.scrollTo(0, 0); viewport.scrollTo(0, 0);
} }
async function handle_scroll() { async function handle_scroll() {
const { scrollTop } = viewport; const { scrollTop } = viewport;
const old_start = start; const old_start = start;
for (let v = 0; v < rows.length; v += 1) { for (let v = 0; v < rows.length; v += 1) {
height_map[start + v] = itemHeight || rows[v].offsetHeight; height_map[start + v] = itemHeight || rows[v].offsetHeight;
} }
let i = 0; let i = 0;
let y = 0; let y = 0;
while (i < items.length) { while (i < items.length) {
const row_height = height_map[i] || average_height; const row_height = height_map[i] || average_height;
if (y + row_height > scrollTop) { if (y + row_height > scrollTop) {
start = i; start = i;
top = y; top = y;
break; break;
} }
y += row_height; y += row_height;
i += 1; i += 1;
} }
while (i < items.length) { while (i < items.length) {
y += height_map[i] || average_height; y += height_map[i] || average_height;
i += 1; i += 1;
if (y > scrollTop + viewport_height) break; if (y > scrollTop + viewport_height) break;
} }
end = i; end = i;
const remaining = items.length - end; const remaining = items.length - end;
average_height = y / end; average_height = y / end;
while (i < items.length) height_map[i++] = average_height; while (i < items.length) height_map[i++] = average_height;
bottom = remaining * average_height; bottom = remaining * average_height;
// prevent jumping if we scrolled up into unknown territory // prevent jumping if we scrolled up into unknown territory
if (start < old_start) { if (start < old_start) {
await tick(); await tick();
let expected_height = 0; let expected_height = 0;
let actual_height = 0; let actual_height = 0;
for (let i = start; i < old_start; i +=1) { for (let i = start; i < old_start; i += 1) {
if (rows[i - start]) { if (rows[i - start]) {
expected_height += height_map[i]; expected_height += height_map[i];
actual_height += itemHeight || rows[i - start].offsetHeight; actual_height += itemHeight || rows[i - start].offsetHeight;
} }
} }
const d = actual_height - expected_height; const d = actual_height - expected_height;
viewport.scrollTo(0, scrollTop + d); viewport.scrollTo(0, scrollTop + d);
} }
// TODO if we overestimated the space these // TODO if we overestimated the space these
// rows would occupy we may need to add some // rows would occupy we may need to add some
// more. maybe we can just call handle_scroll again? // more. maybe we can just call handle_scroll again?
} }
// trigger initial refresh // trigger initial refresh
onMount(() => { onMount(() => {
rows = contents.getElementsByTagName('svelte-virtual-list-row'); rows = contents.getElementsByTagName('svelte-virtual-list-row');
mounted = true; mounted = true;
}); });
</script> </script>
<style>
svelte-virtual-list-viewport {
position: relative;
overflow-y: auto;
-webkit-overflow-scrolling:touch;
display: block;
}
svelte-virtual-list-contents, svelte-virtual-list-row {
display: block;
}
svelte-virtual-list-row {
overflow: hidden;
}
</style>
<svelte-virtual-list-viewport <svelte-virtual-list-viewport
bind:this={viewport} bind:this={viewport}
bind:offsetHeight={viewport_height} bind:offsetHeight={viewport_height}
on:scroll={handle_scroll} on:scroll={handle_scroll}
style="height: {height};" style="height: {height};"
> >
<svelte-virtual-list-contents <svelte-virtual-list-contents bind:this={contents} style="padding-top: {top}px; padding-bottom: {bottom}px;">
bind:this={contents} {#each visible as row (row.index)}
style="padding-top: {top}px; padding-bottom: {bottom}px;" <svelte-virtual-list-row>
> <slot item={row.data} index={row.index}>Missing template</slot>
{#each visible as row (row.index)} </svelte-virtual-list-row>
<svelte-virtual-list-row> {/each}
<slot item={row.data} index={row.index}>Missing template</slot> </svelte-virtual-list-contents>
</svelte-virtual-list-row>
{/each}
</svelte-virtual-list-contents>
</svelte-virtual-list-viewport> </svelte-virtual-list-viewport>
<style lang="postcss">
svelte-virtual-list-viewport {
position: relative;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
display: block;
}
svelte-virtual-list-contents,
svelte-virtual-list-row {
display: block;
}
svelte-virtual-list-row {
overflow: hidden;
}
</style>

View file

@ -168,7 +168,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.hovered { .hovered {
@apply text-white !important; @apply text-white !important;
@apply bg-primary; @apply bg-primary;

View file

@ -190,7 +190,7 @@
try { try {
const res = await fetchRetry( const res = await fetchRetry(
`${__paimon.env.API_HOST}/corsproxy`, `${import.meta.env.VITE_API_HOST}/corsproxy`,
{ {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -527,7 +527,7 @@
</td> </td>
<td class="border-b border-gray-700 py-1"> <td class="border-b border-gray-700 py-1">
{#if wishes[code] !== undefined} {#if wishes[code] !== undefined}
<span class="text-white mr-2 whitespace-no-wrap"> <span class="text-white mr-2 whitespace-nowrap">
<Icon size={0.5} path={mdiClose} /> <Icon size={0.5} path={mdiClose} />
{numberFormat.format(wishes[code].length)} {numberFormat.format(wishes[code].length)}
</span> </span>
@ -585,10 +585,10 @@
{#if wishes[code] !== undefined} {#if wishes[code] !== undefined}
<tr> <tr>
<td class="border-b border-gray-700 py-1"> <td class="border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{type.name} Banner</span> <span class="text-white mr-2 whitespace-nowrap">{type.name} Banner</span>
</td> </td>
<td class="border-b border-gray-700 py-1"> <td class="border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap"> <span class="text-white mr-2 whitespace-nowrap">
<Icon size={0.5} path={mdiClose} /> <Icon size={0.5} path={mdiClose} />
{numberFormat.format(wishes[code].length)} {numberFormat.format(wishes[code].length)}
</span> </span>
@ -788,7 +788,7 @@
<div class="pb-16 md:pb-0" /> <div class="pb-16 md:pb-0" />
{/if} {/if}
<style> <style lang="postcss">
.pill { .pill {
@apply rounded-2xl; @apply rounded-2xl;
@apply border-2; @apply border-2;

View file

@ -2,12 +2,12 @@ importScripts('https://www.gstatic.com/firebasejs/8.3.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.3.2/firebase-messaging.js'); importScripts('https://www.gstatic.com/firebasejs/8.3.2/firebase-messaging.js');
const firebaseConfig = { const firebaseConfig = {
apiKey: __paimon.env.FIREBASE_API_KEY, apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: __paimon.env.FIREBASE_AUTH_DOMAIN, authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: __paimon.env.FIREBASE_PROJECT_ID, projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: __paimon.env.FIREBASE_STORAGE_BUCKET, storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: __paimon.env.FIREBASE_MESSAGING_SENDER_ID, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: __paimon.env.FIREBASE_APP_ID, appId: import.meta.env.VITE_FIREBASE_APP_ID,
}; };
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);

View file

@ -7,7 +7,7 @@ const bannerCategories = ['beginners', 'standard', 'character-event', 'weapon-ev
async function sendWish(data) { async function sendWish(data) {
try { try {
await fetch(`${__paimon.env.API_HOST}/wish`, { await fetch(`${import.meta.env.VITE_API_HOST}/wish`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data),
@ -19,7 +19,7 @@ async function sendWish(data) {
async function sendWishTotal(data) { async function sendWishTotal(data) {
try { try {
await fetch(`${__paimon.env.API_HOST}/wish/total`, { await fetch(`${import.meta.env.VITE_API_HOST}/wish/total`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data),
@ -31,7 +31,7 @@ async function sendWishTotal(data) {
async function sendWishConstellation(data) { async function sendWishConstellation(data) {
try { try {
await fetch(`${__paimon.env.API_HOST}/wish/constellation`, { await fetch(`${import.meta.env.VITE_API_HOST}/wish/constellation`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), body: JSON.stringify(data),

View file

@ -1,23 +1,22 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { register, addMessages, init, getLocaleFromNavigator, locale as $locale } from 'svelte-i18n'; import { register, addMessages, init, getLocaleFromNavigator, locale as $locale } from 'svelte-i18n';
import { browser } from '$app/env';
import en from './locales/en.json'; import en from './locales/en.json';
import enItems from './locales/items/en.json'; import enItems from './locales/items/en.json';
const INIT_OPTIONS = { const INIT_OPTIONS = {
fallbackLocale: 'en', fallbackLocale: 'en',
initialLocale: null, initialLocale: 'en',
}; };
let currentLocale = null; let isLoaded = false;
$locale.subscribe((value) => { $locale.subscribe((value) => {
if (value == null) return; if (value === null) return;
currentLocale = value; if (isLoaded) localStorage.setItem('locale', value);
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
localStorage.setItem('locale', value);
dayjsLocales[value]().then(() => dayjs.locale(value)); dayjsLocales[value]().then(() => dayjs.locale(value));
} }
}); });
@ -66,51 +65,27 @@ const dayjsLocales = {
vi: () => import('dayjs/locale/vi'), vi: () => import('dayjs/locale/vi'),
}; };
export function startClient() { export async function startClient() {
let used = 'en';
const savedLocale = localStorage.getItem('locale');
const detectedLocale = getLocaleFromNavigator().substring(0, 2);
if (savedLocale !== null) {
if (!supportedLanguage.includes(savedLocale)) {
localStorage.setItem('locale', 'en');
} else {
used = savedLocale;
}
} else if (supportedLanguage.includes(detectedLocale)) {
used = detectedLocale;
}
init({ init({
...INIT_OPTIONS, ...INIT_OPTIONS,
initialLocale: used,
}); });
}
const DOCUMENT_REGEX = /(^([^.?#@]+)?([?#](.+)?)?|service-worker.*?\.html)$/; if (browser) {
export function i18nMiddleware() { let used = 'en';
init(INIT_OPTIONS); const savedLocale = localStorage.getItem('locale');
const detectedLocale = getLocaleFromNavigator().substring(0, 2);
return (req, res, next) => { if (savedLocale !== null) {
const isDocument = DOCUMENT_REGEX.test(req.originalUrl); if (!supportedLanguage.includes(savedLocale)) {
if (!isDocument) { localStorage.setItem('locale', 'en');
next(); } else {
return; used = savedLocale;
}
let locale = 'en';
if (req.headers['accept-language']) {
const headerLang = req.headers['accept-language'].split(',')[0].trim();
if (headerLang.length > 1) {
locale = headerLang;
} }
} else { } else if (supportedLanguage.includes(detectedLocale)) {
locale = INIT_OPTIONS.initialLocale || INIT_OPTIONS.fallbackLocale; used = detectedLocale;
} }
if (locale != null && locale !== currentLocale) { $locale.set(used);
$locale.set(locale.substring(0, 2)); isLoaded = true;
} console.log('change i18n', used);
}
next();
};
} }

View file

@ -23,9 +23,6 @@
"message": "Your best Genshin Impact companion! Paimon.moe helps you plan what to farm with an ascension calculator, and it also tracks your progress with a todo list and a wish counter.", "message": "Your best Genshin Impact companion! Paimon.moe helps you plan what to farm with an ascension calculator, and it also tracks your progress with a todo list and a wish counter.",
"visitor": "{count} visitors in the last 7 days", "visitor": "{count} visitors in the last 7 days",
"banner": { "banner": {
"featured": [
"Eula"
],
"summoned": "Summoned", "summoned": "Summoned",
"percentage": "from all {rarity}", "percentage": "from all {rarity}",
"avg": "Pity average", "avg": "Pity average",
@ -143,10 +140,7 @@
"manualButton": "Enable Manual Input", "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 😅", "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", "globalWishTally": "Global Wish Stats",
"pityTooltip": [ "pityTooltip": ["Shows your current {rarity} pity", "{count} pulls to guaranteed {rarity}"],
"Shows your current {rarity} pity",
"{count} pulls to guaranteed {rarity}"
],
"import": { "import": {
"title": "Import Wish History", "title": "Import Wish History",
"faqsButton": "FAQ - READ FIRST", "faqsButton": "FAQ - READ FIRST",
@ -182,11 +176,7 @@
"server": "Select your server:", "server": "Select your server:",
"wishTallyCheck": "Submit pity for global wish stats", "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.", "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": [ "wishTallyCollected": ["What will be collected:", "and", "pity from your wish history"],
"What will be collected:",
"and",
"pity from your wish history"
],
"forceUpdateCheck": "Force update wish history (enable only if your wish history is not updating)", "forceUpdateCheck": "Force update wish history (enable only if your wish history is not updating)",
"header": [ "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!", "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!",
@ -364,11 +354,7 @@
"exportFinish": "Export success, please wait until your browser downloads the file!", "exportFinish": "Export success, please wait until your browser downloads the file!",
"wishTallyTitle": "Submit Wish Stats", "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.", "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": [ "wishTallyCollected": ["What will be collected:", "and", "pity from your wish history"],
"What will be collected:",
"and",
"pity from your wish history"
],
"wishTallySubmit": "Submit Wish Stats", "wishTallySubmit": "Submit Wish Stats",
"wishTallyThankyou": "Thank you for participating!", "wishTallyThankyou": "Thank you for participating!",
"manualTitle": "Manual Input Settings", "manualTitle": "Manual Input Settings",
@ -380,22 +366,13 @@
"subtitle": "After a 1x Wish:", "subtitle": "After a 1x Wish:",
"pressWhenYouGet": "Press {button} when you get {rarity}★", "pressWhenYouGet": "Press {button} when you get {rarity}★",
"p1": "It will automatically add the lifetime pulls, 5★, and 4★ pity", "p1": "It will automatically add the lifetime pulls, 5★, and 4★ pity",
"p2": [ "p2": ["When the", "pity reaches 10, it will automatically be reset to 0"],
"When the", "p3": ["When the", "pity reaches 90, it will automatically be reset to 0"],
"pity reaches 10, it will automatically be reset to 0"
],
"p3": [
"When the",
"pity reaches 90, it will automatically be reset to 0"
],
"p4": [ "p4": [
"After a 10x Wish, press", "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." "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": [ "p5": ["You can also press the", "button to edit the values manually!"],
"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." "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."
} }
}, },
@ -535,11 +512,7 @@
"calculateTalent": "Calculate Talent Material?", "calculateTalent": "Calculate Talent Material?",
"inputTalentLevel": "Input the 1st, 2nd & 3rd current talent level", "inputTalentLevel": "Input the 1st, 2nd & 3rd current talent level",
"inputTalentNotice": "If it has a different color, subtract it by 3", "inputTalentNotice": "If it has a different color, subtract it by 3",
"inputTalent": [ "inputTalent": ["1st talent lvl", "2nd talent lvl", "3rd talent lvl"],
"1st talent lvl",
"2nd talent lvl",
"3rd talent lvl"
],
"talentToLevel": "to level", "talentToLevel": "to level",
"calculate": "Calculate", "calculate": "Calculate",
"unknownInformation": "There are some unknown information", "unknownInformation": "There are some unknown information",
@ -548,11 +521,7 @@
"expWasted": "EXP Wasted", "expWasted": "EXP Wasted",
"addToTodo": "Add to Todo List", "addToTodo": "Add to Todo List",
"addedToTodo": "Added to Todo List", "addedToTodo": "Added to Todo List",
"talent": [ "talent": ["Attack", "Skill", "Burst"]
"Attack",
"Skill",
"Burst"
]
}, },
"expTable": { "expTable": {
"level": "Level", "level": "Level",
@ -644,10 +613,7 @@
"todo": { "todo": {
"title": "Todo List", "title": "Todo List",
"summary": "Summary", "summary": "Summary",
"empty": [ "empty": ["Nothing to do yet 😀", "Add some from the Items page or the Calculator!"],
"Nothing to do yet 😀",
"Add some from the Items page or the Calculator!"
],
"farmableToday": "Farmable Today", "farmableToday": "Farmable Today",
"resin": "Resin needed", "resin": "Resin needed",
"based": "Based on AR:{ar} and WL:{wl}", "based": "Based on AR:{ar} and WL:{wl}",
@ -962,5 +928,12 @@
"common": { "common": {
"dataSynced": "Data has been synced!", "dataSynced": "Data has been synced!",
"driveError": "Drive sync not available right now 😔" "driveError": "Drive sync not available right now 😔"
},
"update": {
"newUpdate": "Paimon.moe has a new update!",
"updateRefresh": "Click refresh to get the new update",
"whatsNew": "What's new",
"later": "Later",
"refresh": "Refresh"
} }
} }

View file

@ -1,10 +1,21 @@
<script context="module">
export function load({ error, status }) {
return {
props: {
status,
error,
},
};
}
</script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
export let status; export let status;
export let error; export let error;
const dev = process.env.NODE_ENV === 'development'; const dev = import.meta.env.DEV;
let refreshUrl; let refreshUrl;
@ -39,7 +50,7 @@
> >
Click here to refresh Click here to refresh
</a> </a>
<p class="text-white text-xl mt-2 text-center">Or you can try refresh the page by pressing CTRL+F5</p> <p class="text-white text-xl mt-2 text-center">Or you can try refresh the page by pressing CTRL+SHIFT+R</p>
<p class="text-white text-xl mt-2 text-center"> <p class="text-white text-xl mt-2 text-center">
You might also want to check your extension, like adblock, it sometimes wrongly block the script needed for the You might also want to check your extension, like adblock, it sometimes wrongly block the script needed for the
site. site.

View file

@ -1,21 +1,17 @@
<script context="module">
import { waitLocale } from 'svelte-i18n';
export async function preload() {
return waitLocale();
}
</script>
<script> <script>
import '../app.css';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
import { stores } from '@sapper/app'; import { fade } from 'svelte/transition';
import localforage from 'localforage';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { startClient } from '../i18n.js';
import { navigating, page } from '$app/stores';
import Modal from 'svelte-simple-modal'; import Modal from 'svelte-simple-modal';
import { mdiDiscord, mdiFacebook, mdiGithub, mdiReddit, mdiTwitter } from '@mdi/js';
import Tailwind from '../components/Tailwindcss.svelte';
import Sidebar from '../components/Sidebar/Sidebar.svelte'; import Sidebar from '../components/Sidebar/Sidebar.svelte';
import Header from '../components/Header.svelte'; import Header from '../components/Header.svelte';
import DataSync from '../components/DataSync.svelte'; import DataSync from '../components/DataSync.svelte';
@ -26,28 +22,35 @@
import SettingData from '../components/SettingData.svelte'; import SettingData from '../components/SettingData.svelte';
import Toast from '../components/Toast.svelte'; import Toast from '../components/Toast.svelte';
import Icon from '../components/Icon.svelte'; import Icon from '../components/Icon.svelte';
import { mdiDiscord, mdiFacebook, mdiGithub, mdiReddit, mdiTelegram, mdiTwitter } from '@mdi/js'; import ServiceWorker from '../components/ServiceWorker.svelte';
export let segment; const delayedPreloading = derived(navigating, (_, set) => {
set(true);
const { preloading, page } = stores(); setTimeout(() => set(true), 250);
const delayedPreloading = derived(preloading, (currentPreloading, set) => { });
setTimeout(() => set(currentPreloading), 250);
startClient();
page.subscribe(() => {
try {
window.reloadAdSlots();
} catch (error) {}
}); });
// check local storage save on load
onMount(async () => { onMount(async () => {
await checkLocalSave(); console.log('localforage config');
localforage.config({
page.subscribe(() => { driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
try { name: 'paimonmoe',
window.reloadAdSlots(); version: 1.0,
} catch (error) {} description: 'paimonmoe local storage',
}); });
window.localforage = localforage;
await checkLocalSave();
}); });
</script>
<Tailwind /> $: segment = $page.url.pathname.substring(1).split('/')[0];
</script>
<Header /> <Header />
<Modal> <Modal>
@ -63,8 +66,9 @@
<slot /> <slot />
</main> </main>
</DataSync> </DataSync>
<ServiceWorker />
</Modal> </Modal>
{#if $preloading && $delayedPreloading} {#if $navigating && $delayedPreloading}
<div transition:fade class="loading-bar" /> <div transition:fade class="loading-bar" />
{/if} {/if}
<div class="lg:ml-64 px-4 md:px-8 py-8 flex flex-col md:pb-24"> <div class="lg:ml-64 px-4 md:px-8 py-8 flex flex-col md:pb-24">
@ -81,18 +85,20 @@
<span class="text-gray-500">{$t('footer.community')}</span> <span class="text-gray-500">{$t('footer.community')}</span>
<div> <div>
<a <a
class="text-gray-400 hover:text-primary whitespace-no-wrap" class="text-gray-400 hover:text-primary whitespace-nowrap"
href="https://github.com/MadeBaruna/paimon-moe" href="https://github.com/MadeBaruna/paimon-moe"
target="_blank" target="_blank"
> >
<Icon path={mdiGithub} size={1} /> {$t('footer.link.github')} <Icon path={mdiGithub} size={1} />
{$t('footer.link.github')}
</a> </a>
<a <a
class="text-gray-400 hover:text-primary whitespace-no-wrap" class="text-gray-400 hover:text-primary whitespace-nowrap"
href="https://twitter.com/MadeBaruna" href="https://twitter.com/MadeBaruna"
target="_blank" target="_blank"
> >
<Icon path={mdiTwitter} size={1} /> {$t('footer.link.devTwitter')} <Icon path={mdiTwitter} size={1} />
{$t('footer.link.devTwitter')}
</a> </a>
</div> </div>
</div> </div>
@ -100,25 +106,28 @@
<span class="text-gray-500">{$t('footer.official')}</span> <span class="text-gray-500">{$t('footer.official')}</span>
<div> <div>
<a <a
class="text-gray-400 hover:text-primary mr-1 whitespace-no-wrap" class="text-gray-400 hover:text-primary mr-1 whitespace-nowrap"
href="https://discord.gg/4nbWsCGjjE" href="https://discord.gg/4nbWsCGjjE"
target="_blank" target="_blank"
> >
<Icon path={mdiDiscord} size={1} /> {$t('footer.link.discord')} <Icon path={mdiDiscord} size={1} />
{$t('footer.link.discord')}
</a> </a>
<a <a
class="text-gray-400 hover:text-primary mr-1 whitespace-no-wrap" class="text-gray-400 hover:text-primary mr-1 whitespace-nowrap"
href="https://www.facebook.com/Genshinimpact/" href="https://www.facebook.com/Genshinimpact/"
target="_blank" target="_blank"
> >
<Icon path={mdiFacebook} size={1} /> {$t('footer.link.facebook')} <Icon path={mdiFacebook} size={1} />
{$t('footer.link.facebook')}
</a> </a>
<a <a
class="text-gray-400 hover:text-primary whitespace-no-wrap" class="text-gray-400 hover:text-primary whitespace-nowrap"
href="https://www.reddit.com/r/Genshin_Impact/" href="https://www.reddit.com/r/Genshin_Impact/"
target="_blank" target="_blank"
> >
<Icon path={mdiReddit} size={1} /> {$t('footer.link.reddit')} <Icon path={mdiReddit} size={1} />
{$t('footer.link.reddit')}
</a> </a>
</div> </div>
</div> </div>
@ -132,7 +141,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.loading-bar { .loading-bar {
position: fixed; position: fixed;
top: 0; top: 0;

View file

@ -37,7 +37,7 @@
let user = ''; let user = '';
async function getData() { async function getData() {
const url = new URL(`${__paimon.env.API_HOST}/wish`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish`);
const query = new URLSearchParams({ banner: bannerId }); const query = new URLSearchParams({ banner: bannerId });
url.search = query.toString(); url.search = query.toString();

View file

@ -4,13 +4,13 @@
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { characters } from '../../data/characters'; import { characters } from '../../data/characters';
import { builds } from '../../data/build';
import Icon from '../../components/Icon.svelte'; import Icon from '../../components/Icon.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const promoted = ['kaedehara_kazuha', 'shikanoin_heizou']; export let builds;
const promoted = Object.keys(builds);
let current = 0; let current = 0;
async function change(index) { async function change(index) {
@ -121,7 +121,7 @@ bg-background-secondary rounded-xl py-2 px-4 hover:bg-background transition-colo
</a> </a>
</div> </div>
<style> <style lang="postcss">
.pill { .pill {
@apply rounded-2xl; @apply rounded-2xl;
@apply border-2; @apply border-2;

View file

@ -84,7 +84,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
{#each upcoming as item} {#each upcoming as item}
<div class="pl-2 pr-1 py-1 rounded-xl mb-1 flex" style="background: {item.color};"> <div class="pl-2 pr-1 py-1 rounded-xl mb-1 flex" style="background: {item.color};">
<span class="whitespace-no-wrap overflow-x-hidden flex-1 mr-1 text-sm" style="text-overflow: ellipsis;"> <span class="whitespace-nowrap overflow-x-hidden flex-1 mr-1 text-sm" style="text-overflow: ellipsis;">
{item.name} {item.name}
</span> </span>
<span class="bg-black bg-opacity-50 rounded-xl px-2 text-white text-sm">{item.time}</span> <span class="bg-black bg-opacity-50 rounded-xl px-2 text-white text-sm">{item.time}</span>
@ -97,7 +97,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
{#each current as item} {#each current as item}
<div class="pl-2 pr-1 py-1 rounded-xl mb-1 flex" style="background: {item.color};"> <div class="pl-2 pr-1 py-1 rounded-xl mb-1 flex" style="background: {item.color};">
<span class="whitespace-no-wrap overflow-x-hidden flex-1 mr-1 text-sm" style="text-overflow: ellipsis;"> <span class="whitespace-nowrap overflow-x-hidden flex-1 mr-1 text-sm" style="text-overflow: ellipsis;">
{item.name} {item.name}
</span> </span>
<span class="bg-black bg-opacity-50 rounded-xl px-2 text-white text-sm">{item.time}</span> <span class="bg-black bg-opacity-50 rounded-xl px-2 text-white text-sm">{item.time}</span>

View file

@ -12,7 +12,7 @@
// let count = '...'; // let count = '...';
// async function getData() { // async function getData() {
// const url = new URL(`${__paimon.env.API_HOST}/visitor`); // const url = new URL(`${import.meta.env.VITE_API_HOST}/visitor`);
// try { // try {
// const res = await fetch(url, { // const res = await fetch(url, {

View file

@ -1,14 +1,11 @@
<script context="module"> <script context="module">
import data from '../../data/achievement/en.json'; import achievementData from '../../data/achievement/en.json';
export async function preload() {
return { data };
}
</script> </script>
<script> <script>
import { locale, t } from 'svelte-i18n'; import { locale, t } from 'svelte-i18n';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import { mdiFilter } from '@mdi/js'; import { mdiFilter } from '@mdi/js';
import Check from '../../components/Check.svelte'; import Check from '../../components/Check.svelte';
@ -21,10 +18,10 @@
import { pushToast } from '../../stores/toast'; import { pushToast } from '../../stores/toast';
import Ad from '../../components/Ad.svelte'; import Ad from '../../components/Ad.svelte';
export let data;
let achievementContainer; let achievementContainer;
let data = achievementData;
let achievement = data; let achievement = data;
let checkList = {}; let checkList = {};
let list = []; let list = [];
@ -494,7 +491,7 @@
<Ad type="desktop" variant="lb" id="2" /> <Ad type="desktop" variant="lb" id="2" />
<Ad type="mobile" variant="lb" id="1" /> <Ad type="mobile" variant="lb" id="1" />
<style> <style lang="postcss">
.category { .category {
width: 100%; width: 100%;
} }

View file

@ -83,12 +83,12 @@
}; };
} }
export async function preload(page) { export async function load({ params }) {
const { id } = page.params; const { id } = params;
const artifact = data[id]; const artifact = data[id];
const recommendedCharacter = getCharacter(id); const recommendedCharacter = getCharacter(id);
return { id, artifact, recommendedCharacter }; return { props: { id, artifact, recommendedCharacter } };
} }
</script> </script>

View file

@ -1,8 +1,5 @@
<script context="module"> <script context="module">
import data from '../../data/artifacts/en.json'; import dataJson from '../../data/artifacts/en.json';
export async function preload() {
return { data };
}
</script> </script>
<script> <script>
@ -11,7 +8,7 @@
import TableHeader from '../../components/Table/TableHeader.svelte'; import TableHeader from '../../components/Table/TableHeader.svelte';
import { domains } from '../../data/domain.js'; import { domains } from '../../data/domain.js';
export let data; let data = dataJson;
let artifactList = []; let artifactList = [];
let artifacts = []; let artifacts = [];
let sortBy = 'maxRarity'; let sortBy = 'maxRarity';
@ -118,7 +115,7 @@
</svelte:head> </svelte:head>
<div class="lg:ml-64 pt-20 lg:pt-8"> <div class="lg:ml-64 pt-20 lg:pt-8">
<h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">{$t('artifact.title')}</h1> <h1 class="font-display px-4 md:px-8 font-black text-5xl text-white">{$t('artifact.title')}</h1>
<div class="block overflow-x-auto whitespace-no-wrap pb-8"> <div class="block overflow-x-auto whitespace-nowrap pb-8">
<div class="px-4 md:px-8 table max-w-full"> <div class="px-4 md:px-8 table max-w-full">
<table class="w-full block p-4 bg-item rounded-xl"> <table class="w-full block p-4 bg-item rounded-xl">
<thead> <thead>

View file

@ -630,7 +630,7 @@
{#if currentMax.usage[i] > 0} {#if currentMax.usage[i] > 0}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{currentMax.usage[i]} >{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -652,7 +652,7 @@
{#if item.amount > 0} {#if item.amount > 0}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{item.amount} >{item.amount}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -670,7 +670,7 @@
{/each} {/each}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{numberFormat.format(moraNeeded)} >{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >

View file

@ -109,7 +109,7 @@
} }
</script> </script>
<div class="block overflow-x-auto whitespace-no-wrap pb-1"> <div class="block overflow-x-auto whitespace-nowrap pb-1">
<div class="table w-full"> <div class="table w-full">
<div class="bg-item rounded-xl p-4 w-full"> <div class="bg-item rounded-xl p-4 w-full">
<table> <table>
@ -126,7 +126,7 @@
><Icon className="mb-1 text-gray-400" path={mdiArrowRight} size={0.7} /></td ><Icon className="mb-1 text-gray-400" path={mdiArrowRight} size={0.7} /></td
> >
<td class="pr-2 text-white text-center">{step[i + 1]}</td> <td class="pr-2 text-white text-center">{step[i + 1]}</td>
<td class="px-2 text-white whitespace-no-wrap" style="min-width: 180px;"> <td class="px-2 text-white whitespace-nowrap" style="min-width: 180px;">
{#each resources as res, j} {#each resources as res, j}
{#if row.usage[j] > 0} {#if row.usage[j] > 0}
<span class="mr-2"> <span class="mr-2">
@ -145,7 +145,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
td, td,
th { th {
@apply py-1; @apply py-1;

View file

@ -6,18 +6,28 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
let fateValues = [ let fateValues = [
{ id: "interwinedFate", name: $t('calculator.fateCount.interwinedFate'), image: "/images/intertwined_fate.png", amount: 0 }, {
{ id: "starglitter", name: $t('calculator.fateCount.starglitter'), image: "/images/starglitter.png", amount: 0 }, id: 'interwinedFate',
{ id: "stardust", name: $t('calculator.fateCount.stardust'), image: "/images/stardust.png", amount: 0 }, name: $t('calculator.fateCount.interwinedFate'),
{ id: "primogem", name: $t('calculator.fateCount.primogem'), image: "/images/primogem.png", amount: 0 }, image: '/images/intertwined_fate.png',
{ id: "genesisCrystal", name: $t('calculator.fateCount.genesisCrystal'), image: "/images/genesis_crystal.png", amount: 0 }, amount: 0,
{ id: "welkinMoon", name: $t('calculator.fateCount.welkinMoon'), image: "/images/welkin_moon.png", amount: 0 }, },
{ id: 'starglitter', name: $t('calculator.fateCount.starglitter'), image: '/images/starglitter.png', amount: 0 },
{ id: 'stardust', name: $t('calculator.fateCount.stardust'), image: '/images/stardust.png', amount: 0 },
{ id: 'primogem', name: $t('calculator.fateCount.primogem'), image: '/images/primogem.png', amount: 0 },
{
id: 'genesisCrystal',
name: $t('calculator.fateCount.genesisCrystal'),
image: '/images/genesis_crystal.png',
amount: 0,
},
{ id: 'welkinMoon', name: $t('calculator.fateCount.welkinMoon'), image: '/images/welkin_moon.png', amount: 0 },
]; ];
let parameters = [ let parameters = [
{ name: "Days until pull", amount: 0 }, { name: 'Days until pull', amount: 0 },
{ name: "Stardust Wishes (left this month)", amount: 5 } { name: 'Stardust Wishes (left this month)', amount: 5 },
] ];
let result = null; let result = null;
let totalPrimogem = 0; let totalPrimogem = 0;
@ -29,39 +39,44 @@
if (value.amount >= 0) { if (value.amount >= 0) {
let total = 0; let total = 0;
switch (value.id) { switch (value.id) {
case "interwinedFate": case 'interwinedFate':
total = value.amount*160; total = value.amount * 160;
break; break;
case "starglitter": case 'starglitter':
total = Math.floor(value.amount/5)*160; total = Math.floor(value.amount / 5) * 160;
break; break;
case "stardust": case 'stardust':
let dateNow = dayjs(); let dateNow = dayjs();
let monthPull = dateNow.add(parameters[0].amount, 'day').startOf('month'); let monthPull = dateNow.add(parameters[0].amount, 'day').startOf('month');
let monthDiff = monthPull.diff(dateNow, 'month'); let monthDiff = monthPull.diff(dateNow, 'month');
let maxStardustFate = monthDiff*5+parameters[1].amount; let maxStardustFate = monthDiff * 5 + parameters[1].amount;
total = Math.min(Math.floor(value.amount/75), maxStardustFate)*160; total = Math.min(Math.floor(value.amount / 75), maxStardustFate) * 160;
break; break;
case "primogem": case 'primogem':
total = value.amount; total = value.amount;
break; break;
case "genesisCrystal": case 'genesisCrystal':
total = value.amount; total = value.amount;
break; break;
case "welkinMoon": case 'welkinMoon':
let days = Math.min(value.amount, parameters[0].amount) let days = Math.min(value.amount, parameters[0].amount);
total = Math.floor(days/30)*300 + days*90 total = Math.floor(days / 30) * 300 + days * 90;
} }
if (total>0) { if (total > 0) {
totalPrimogem += total totalPrimogem += total;
result.push({name: value.name, image: value.image, amount: value.amount, total: total }) result.push({ name: value.name, image: value.image, amount: value.amount, total: total });
} }
} }
} }
if (parameters[0].amount>0) { if (parameters[0].amount > 0) {
let total = parameters[0].amount*60; let total = parameters[0].amount * 60;
totalPrimogem += total; totalPrimogem += total;
result.push({name: $t('calculator.fateCount.dailyCommission'), image: "/images/commission.png", amount: parameters[0].amount, total: total}) result.push({
name: $t('calculator.fateCount.dailyCommission'),
image: '/images/commission.png',
amount: parameters[0].amount,
total: total,
});
} }
} }
@ -83,7 +98,15 @@
</div> </div>
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<div class="w-full"> <div class="w-full">
<Input className="text-center" bind:value={value.amount} on:change={onChange} type="number" min="0" step="1" pattern="\d*" /> <Input
className="text-center"
bind:value={value.amount}
on:change={onChange}
type="number"
min="0"
step="1"
pattern="\d*"
/>
</div> </div>
</div> </div>
</div> </div>
@ -98,22 +121,28 @@
</div> </div>
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<div class="w-full"> <div class="w-full">
<Input className="text-center" bind:value={value.amount} on:change={onChange} type="number" min="0" step="1" pattern="\d*" /> <Input
className="text-center"
bind:value={value.amount}
on:change={onChange}
type="number"
min="0"
step="1"
pattern="\d*"
/>
</div> </div>
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
<div class="md:col-span-2 xl:col-span-1"> <div class="md:col-span-2 xl:col-span-1">
<Button className="block w-full md:w-auto" on:click={calculate} <Button className="block w-full md:w-auto" on:click={calculate}>{$t('calculator.fateCount.calculate')}</Button>
>{$t('calculator.fateCount.calculate')}</Button
>
{#if result !== null} {#if result !== null}
<div> <div>
<div <div
transition:fade={{ duration: 100 }} transition:fade={{ duration: 100 }}
class="rounded-xl bg-background p-4 block md:inline-block" class="rounded-xl bg-background p-4 block md:inline-block"
style="height: fit-content; width: fit-content;" style="height: fit-content; width: fit-content;"
> >
<table> <table>
<tr> <tr>
@ -138,7 +167,7 @@
</tr> </tr>
{/each} {/each}
<tr> <tr>
<td class="border-t border-gray-700 text-white text-right whitespace-no-wrap" colspan={5}> <td class="border-t border-gray-700 text-white text-right whitespace-nowrap" colspan={5}>
{$t('calculator.fateCount.totalPrimogem')} {$t('calculator.fateCount.totalPrimogem')}
{totalPrimogem} {totalPrimogem}
<img class="mr-1 w-6 inline" src="/images/primogem.png" alt="Primogem" /> <img class="mr-1 w-6 inline" src="/images/primogem.png" alt="Primogem" />

View file

@ -334,7 +334,7 @@
</tr> </tr>
{/each} {/each}
<tr> <tr>
<td class="border-t border-gray-700 text-white text-right whitespace-no-wrap" colspan={5}> <td class="border-t border-gray-700 text-white text-right whitespace-nowrap" colspan={5}>
{$t('calculator.fate.totalGenesis')} {$t('calculator.fate.totalGenesis')}
{numberFormat.format(resultTotal)} {numberFormat.format(resultTotal)}
<img class="mr-1 w-6 inline" src="/images/genesis_crystal.png" alt="Genesis" /> <img class="mr-1 w-6 inline" src="/images/genesis_crystal.png" alt="Genesis" />
@ -343,7 +343,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="border-t border-gray-700 text-white text-right whitespace-no-wrap" colspan={5}> <td class="border-t border-gray-700 text-white text-right whitespace-nowrap" colspan={5}>
{$t('calculator.fate.totalPrice')} {$t('calculator.fate.totalPrice')}
{currencyLabel}{numberFormat.format(resultTotalPrice)} {currencyLabel}{numberFormat.format(resultTotalPrice)}
</td> </td>

View file

@ -84,13 +84,13 @@
<p class="block text-center text-gray-400">{$t('calculator.friendship.based', { values: { ar: $ar } })}</p> <p class="block text-center text-gray-400">{$t('calculator.friendship.based', { values: { ar: $ar } })}</p>
<table class="text-gray-200"> <table class="text-gray-200">
<tr> <tr>
<td class="text-xl font-bold text-primary whitespace-no-wrap pr-4 border-b border-gray-700 pb-1"> <td class="text-xl font-bold text-primary whitespace-nowrap pr-4 border-b border-gray-700 pb-1">
{$t('calculator.friendship.resultDay', { values: { result } })} {$t('calculator.friendship.resultDay', { values: { result } })}
</td> </td>
<td class="border-b border-gray-700 pb-1">{$t('calculator.friendship.result')}</td> <td class="border-b border-gray-700 pb-1">{$t('calculator.friendship.result')}</td>
</tr> </tr>
<tr> <tr>
<td class="text-xl font-bold text-primary whitespace-no-wrap pr-4 pt-1"> <td class="text-xl font-bold text-primary whitespace-nowrap pr-4 pt-1">
{$t('calculator.friendship.resultDay', { values: { result: resultSerenitea } })} {$t('calculator.friendship.resultDay', { values: { result: resultSerenitea } })}
</td> </td>
<td class="pt-1">{$t('calculator.friendship.resultSerenitea')}</td> <td class="pt-1">{$t('calculator.friendship.resultSerenitea')}</td>
@ -102,7 +102,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.slider { .slider {
@apply w-full h-4 rounded-xl; @apply w-full h-4 rounded-xl;
-webkit-appearance: none; -webkit-appearance: none;

View file

@ -132,7 +132,7 @@
<table class="w-full"> <table class="w-full">
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{resinOutput.resin} >{resinOutput.resin}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -150,7 +150,7 @@
<tr><td colspan="2" class="text-white text-center pt-2">{$t('calculator.resin.or')}</td></tr> <tr><td colspan="2" class="text-white text-center pt-2">{$t('calculator.resin.or')}</td></tr>
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{resinOutput.condensed.resin} >{resinOutput.condensed.resin}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -166,7 +166,7 @@
</tr> </tr>
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{resinOutput.condensed.condensedResin} >{resinOutput.condensed.condensedResin}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -184,7 +184,9 @@
<tr> <tr>
<td class="text-red-400" colspan="2"> <td class="text-red-400" colspan="2">
{$t('calculator.resin.fullTime')}: {$t('calculator.resin.fullTime')}:
{fullTime.locale($t('calculator.resin.timeFormat')).format('dddd HH:mm:ss')} ({fullTime.locale($t('calculator.resin.timeFormat')).fromNow()}) {fullTime.locale($t('calculator.resin.timeFormat')).format('dddd HH:mm:ss')} ({fullTime
.locale($t('calculator.resin.timeFormat'))
.fromNow()})
</td> </td>
</tr> </tr>
</table> </table>

View file

@ -31,7 +31,7 @@
counTimeRelative(); counTimeRelative();
</script> </script>
<div class="block overflow-x-auto whitespace-no-wrap pb-1"> <div class="block overflow-x-auto whitespace-nowrap pb-1">
<div class="table w-full"> <div class="table w-full">
<div class="bg-item rounded-xl p-4 w-full"> <div class="bg-item rounded-xl p-4 w-full">
<table class="w-full"> <table class="w-full">
@ -57,7 +57,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
td, td,
th { th {
@apply py-1; @apply py-1;

View file

@ -441,7 +441,7 @@
{#if currentMax.usage[i] > 0} {#if currentMax.usage[i] > 0}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{currentMax.usage[i]} >{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -463,7 +463,7 @@
{#if item.amount > 0} {#if item.amount > 0}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{item.amount} >{item.amount}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -481,7 +481,7 @@
{/each} {/each}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap" <span class="text-white mr-2 whitespace-nowrap"
>{numberFormat.format(moraNeeded)} >{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >

View file

@ -1,14 +1,13 @@
<script context="module"> <script context="module">
import { builds as buildsJson } from '../../data/build';
import artifactData from '../../data/artifacts/en.json'; import artifactData from '../../data/artifacts/en.json';
import weaponData from '../../data/weapons/en.json'; import weaponData from '../../data/weapons/en.json';
export async function preload(page) { export async function load({ params, fetch }) {
const { id } = page.params; const { id } = params;
const data = await import(`../../data/characterData/${id}.json`); const data = await import(`../../data/characterData/${id}.json`);
const buildData = buildsJson[id]; const buildData = await (await fetch(`/characters/build/${id}.json`)).json();
return { id, data, buildData, artifactData, weaponData }; return { props: { id, data, buildData } };
} }
</script> </script>
@ -16,8 +15,6 @@
export let id; export let id;
export let data; export let data;
export let buildData; export let buildData;
export let artifactData;
export let weaponData;
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t, locale } from 'svelte-i18n'; import { t, locale } from 'svelte-i18n';
@ -340,34 +337,34 @@
</div> </div>
</div> </div>
</div> </div>
<div class="md:px-4 mt-4 block overflow-x-auto whitespace-no-wrap w-screen md:w-auto"> <div class="md:px-4 mt-4 block overflow-x-auto whitespace-nowrap w-screen md:w-auto">
<div class="px-4" style="width: min-content;"> <div class="px-4" style="width: min-content;">
<div class="table max-w-full rounded-xl border border-gray-200 border-opacity-25"> <div class="table max-w-full rounded-xl border border-gray-200 border-opacity-25">
<table class="text-gray-200 w-full"> <table class="text-gray-200 w-full">
<tr> <tr>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('characters.asc')} {$t('characters.asc')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('characters.lvl')} {$t('characters.lvl')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('characters.hp')} {$t('characters.hp')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('characters.atk')} {$t('characters.atk')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('characters.def')} {$t('characters.def')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2" <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2"
>{$t('characters.critRate')} >{$t('characters.critRate')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2" <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2"
>{$t('characters.critDamage')} >{$t('characters.critDamage')}
</td> </td>
{#if data.statGrow !== 'critRate' && data.statGrow !== 'critDamage'} {#if data.statGrow !== 'critRate' && data.statGrow !== 'critDamage'}
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t(`characters.${data.statGrow}`)} {$t(`characters.${data.statGrow}`)}
</td> </td>
{/if} {/if}
@ -556,7 +553,7 @@
class="flex items-center justify-center bg-background rounded-md px-2 py-1 mb-1 mr-1" class="flex items-center justify-center bg-background rounded-md px-2 py-1 mb-1 mr-1"
style="height: 40px;" style="height: 40px;"
> >
<p class="text-center whitespace-no-wrap text-primary" style="padding-top: 2px;"> <p class="text-center whitespace-nowrap text-primary" style="padding-top: 2px;">
{$t('artifact.choose2')} {$t('artifact.choose2')}
</p> </p>
</div> </div>
@ -735,7 +732,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.pill { .pill {
@apply rounded-2xl; @apply rounded-2xl;
@apply border-2; @apply border-2;

View file

@ -1,5 +1,5 @@
<script> <script>
import { afterUpdate, onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
export let id; export let id;
export let char; export let char;
@ -64,7 +64,7 @@
</div> </div>
</a> </a>
<style> <style lang="postcss">
.small { .small {
font-size: 12px; font-size: 12px;
line-height: 1; line-height: 1;
@ -75,8 +75,6 @@
.cell { .cell {
width: calc(33.33333% - 1rem); width: calc(33.33333% - 1rem);
@screen md { @apply md:w-24;
@apply w-24;
}
} }
</style> </style>

View file

@ -107,7 +107,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
td:not(:last-child) { td:not(:last-child) {
@apply border-r; @apply border-r;
} }

View file

@ -0,0 +1,11 @@
import { builds } from '../../../data/build';
/** @type {import('./__types/items').RequestHandler} */
export async function GET({ params }) {
const { id } = params;
const build = builds[id];
return {
body: build,
};
}

View file

@ -450,7 +450,7 @@
<p class="text-gray-400 px-4 md:px-8 font-medium pb-2 mt-4"> <p class="text-gray-400 px-4 md:px-8 font-medium pb-2 mt-4">
{$t('characters.subtitle')} {$t('characters.subtitle')}
</p> </p>
<div class="block overflow-x-auto whitespace-no-wrap pb-8"> <div class="block overflow-x-auto whitespace-nowrap pb-8">
<div class="px-4 md:px-8 table"> <div class="px-4 md:px-8 table">
<table class="w-full block p-4 bg-item rounded-xl"> <table class="w-full block p-4 bg-item rounded-xl">
<thead> <thead>
@ -521,7 +521,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
tr.rare:hover { tr.rare:hover {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,

View file

@ -1,11 +1,11 @@
<script context="module"> <script context="module">
import artifacts from '../../data/artifacts/en.json'; import artifactsJson from '../../data/artifacts/en.json';
import { domains } from '../../data/domain.js'; import { domains } from '../../data/domain.js';
export async function preload(page) { export async function load({ params }) {
const { id } = page.params; const { id } = params;
const domain = domains[id]; const domain = domains[id];
return { id, artifacts, domain }; return { props: { id, domain } };
} }
</script> </script>
@ -17,8 +17,8 @@
import Button from '../../components/Button.svelte'; import Button from '../../components/Button.svelte';
export let id; export let id;
export let artifacts;
export let domain; export let domain;
let artifacts = artifactsJson;
let currentArtifacts = []; let currentArtifacts = [];

View file

@ -1,5 +1,5 @@
<script context="module"> <script context="module">
import data from '../../data/fishing/en.json'; import dataJson from '../../data/fishing/en.json';
import locations from '../../data/fishing/location.json'; import locations from '../../data/fishing/location.json';
let spots = { let spots = {
@ -13,8 +13,8 @@
spots[location.location].push({ ...location, id }); spots[location.location].push({ ...location, id });
} }
export async function preload() { export async function load() {
return { data, spots }; return { props: { spots } };
} }
</script> </script>
@ -34,7 +34,7 @@
const { open: openModal, close: closeModal } = getContext('simple-modal'); const { open: openModal, close: closeModal } = getContext('simple-modal');
export let data; let data = dataJson;
export let spots; export let spots;
let fishList = data; let fishList = data;

View file

@ -64,7 +64,7 @@
{#each result as [item, amount], i} {#each result as [item, amount], i}
<tr> <tr>
<td class="text-right border-gray-700 py-1 {i === 0 ? '' : 'border-t'}"> <td class="text-right border-gray-700 py-1 {i === 0 ? '' : 'border-t'}">
<span class="text-white mr-2 whitespace-no-wrap"> <span class="text-white mr-2 whitespace-nowrap">
{amount} {amount}
<Icon size={0.5} path={mdiClose} /> <Icon size={0.5} path={mdiClose} />
</span> </span>
@ -82,7 +82,7 @@
{#if coins > 0} {#if coins > 0}
<tr> <tr>
<td class="text-right border-t border-gray-700 py-1"> <td class="text-right border-t border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap"> <span class="text-white mr-2 whitespace-nowrap">
{coins} {coins}
<Icon size={0.5} path={mdiClose} /> <Icon size={0.5} path={mdiClose} />
</span> </span>

View file

@ -1,14 +1,11 @@
<script context="module"> <script context="module">
import setsData from '../../data/furnishing/sets/en.json'; import setsDataJson from '../../data/furnishing/sets/en.json';
import data from '../../data/furnishing/en.json'; import furnishingDataJson from '../../data/furnishing/en.json';
export async function preload() {
return { data, setsData };
}
</script> </script>
<script> <script>
import { locale, t } from 'svelte-i18n'; import { locale, t } from 'svelte-i18n';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import { mdiCheckCircleOutline, mdiClose } from '@mdi/js'; import { mdiCheckCircleOutline, mdiClose } from '@mdi/js';
import Button from '../../components/Button.svelte'; import Button from '../../components/Button.svelte';
@ -21,8 +18,8 @@
const { open: openModal } = getContext('simple-modal'); const { open: openModal } = getContext('simple-modal');
export let data; let data = furnishingDataJson;
export let setsData; let setsData = setsDataJson;
let loading = true; let loading = true;
let furnishing = {}; let furnishing = {};
@ -277,7 +274,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.popup { .popup {
@apply text-sm pt-1 hidden p-2 rounded-xl; @apply text-sm pt-1 hidden p-2 rounded-xl;
} }

View file

@ -1,23 +1,20 @@
<script context="module"> <script context="module">
import data from '../../data/furnishing/en.json'; import dataJson from '../../data/furnishing/en.json';
import categories from '../../data/furnishing/category/en.json'; import categoriesJson from '../../data/furnishing/category/en.json';
export async function preload() {
return { data, categories };
}
</script> </script>
<script> <script>
import { locale, t } from 'svelte-i18n'; import { locale, t } from 'svelte-i18n';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { mdiMinus, mdiPlus } from '@mdi/js'; import { mdiMinus, mdiPlus } from '@mdi/js';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import Icon from '../../components/Icon.svelte'; import Icon from '../../components/Icon.svelte';
import { readSave, updateSave } from '../../stores/saveManager'; import { readSave, updateSave } from '../../stores/saveManager';
import { getAccountPrefix } from '../../stores/account'; import { getAccountPrefix } from '../../stores/account';
import Button from '../../components/Button.svelte'; import Button from '../../components/Button.svelte';
export let data; let data = dataJson;
export let categories; let categories = categoriesJson;
let loading = true; let loading = true;
let active = { let active = {
@ -221,7 +218,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.category { .category {
width: 100%; width: 100%;
} }

View file

@ -1,14 +1,11 @@
<script context="module"> <script context="module">
import data from '../../data/furnishing/en.json'; import dataJson from '../../data/furnishing/en.json';
export async function preload() {
return { data };
}
</script> </script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { locale, t } from 'svelte-i18n'; import { locale, t } from 'svelte-i18n';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import { mdiInformationOutline, mdiMinus, mdiPlus } from '@mdi/js'; import { mdiInformationOutline, mdiMinus, mdiPlus } from '@mdi/js';
import TableHeader from '../../components/Table/TableHeader.svelte'; import TableHeader from '../../components/Table/TableHeader.svelte';
@ -17,7 +14,7 @@
import { getAccountPrefix } from '../../stores/account'; import { getAccountPrefix } from '../../stores/account';
import { readSave, updateSave } from '../../stores/saveManager'; import { readSave, updateSave } from '../../stores/saveManager';
export let data; let data = dataJson;
let type = 'hall'; let type = 'hall';
let items = []; let items = [];
@ -259,7 +256,7 @@
</div> </div>
{/if} {/if}
<div class="flex mt-4 wrapper"> <div class="flex mt-4 wrapper">
<div class="block overflow-x-auto xl:overflow-x-visible whitespace-no-wrap"> <div class="block overflow-x-auto xl:overflow-x-visible whitespace-nowrap">
<div class="px-4 table"> <div class="px-4 table">
<table class="w-full block pl-4 pr-4 py-2 md:pl-8 md:py-4 bg-item rounded-xl"> <table class="w-full block pl-4 pr-4 py-2 md:pl-8 md:py-4 bg-item rounded-xl">
<tr> <tr>
@ -363,7 +360,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.pill { .pill {
@apply rounded-2xl; @apply rounded-2xl;
@apply border-2; @apply border-2;
@ -375,7 +372,7 @@
@apply outline-none; @apply outline-none;
@apply transition; @apply transition;
@apply duration-100; @apply duration-100;
@apply whitespace-no-wrap; @apply whitespace-nowrap;
&:hover { &:hover {
@apply border-primary; @apply border-primary;

View file

@ -1,6 +1,22 @@
<script context="module">
export async function load({ fetch }) {
const promoted = ['kaedehara_kazuha', 'shikanoin_heizou'];
const builds = {};
for (const p of promoted) {
const response = await fetch(`/characters/build/${p}.json`);
const b = await response.json();
builds[p] = b;
}
return {
props: { builds },
};
}
</script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import { locale } from 'svelte-i18n'; import { locale } from 'svelte-i18n';
import Masonry from '../components/Masonry.svelte'; import Masonry from '../components/Masonry.svelte';
@ -19,6 +35,8 @@
import Build from './_index/build.svelte'; import Build from './_index/build.svelte';
import Ad from '../components/Ad.svelte'; import Ad from '../components/Ad.svelte';
export let builds;
let refreshLayout; let refreshLayout;
let isMobile = false; let isMobile = false;
@ -64,7 +82,7 @@
<Ad type="mobile" variant="mpu" id="1" /> <Ad type="mobile" variant="mpu" id="1" />
</div> </div>
{/if} {/if}
<Build on:done={onDone} /> <Build on:done={onDone} {builds} />
<Event on:done={onDone} /> <Event on:done={onDone} />
<Item on:done={onDone} /> <Item on:done={onDone} />
<Discord on:done={onDone} /> <Discord on:done={onDone} />

View file

@ -169,7 +169,7 @@
<CharacterSelect bind:selected={selectedCharacter} placeholder={$t('items.searchCharacter')} /> <CharacterSelect bind:selected={selectedCharacter} placeholder={$t('items.searchCharacter')} />
<WeaponSelect bind:selected={selectedWeapon} placeholder={$t('items.searchWeapon')} /> <WeaponSelect bind:selected={selectedWeapon} placeholder={$t('items.searchWeapon')} />
</div> </div>
<div class="block overflow-x-auto whitespace-no-wrap pb-8"> <div class="block overflow-x-auto whitespace-nowrap pb-8">
<div class="px-4 md:px-8 table max-w-full"> <div class="px-4 md:px-8 table max-w-full">
<table class="w-full block p-4 bg-item rounded-xl"> <table class="w-full block p-4 bg-item rounded-xl">
<thead> <thead>
@ -283,7 +283,7 @@
</table> </table>
</div> </div>
</div> </div>
<div class="block overflow-x-auto whitespace-no-wrap pb-8"> <div class="block overflow-x-auto whitespace-nowrap pb-8">
<div class="px-4 md:px-8 table max-w-full"> <div class="px-4 md:px-8 table max-w-full">
<table class="w-full block p-4 bg-item rounded-xl"> <table class="w-full block p-4 bg-item rounded-xl">
<thead> <thead>
@ -338,7 +338,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
td { td {
@apply text-white; @apply text-white;
@apply px-4; @apply px-4;

View file

@ -1,14 +1,11 @@
<script context="module"> <script context="module">
import data from '../../data/radiantSpincrystal/en.json'; import dataJson from '../../data/radiantSpincrystal/en.json';
export async function preload() {
return { data };
}
</script> </script>
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { locale, t } from 'svelte-i18n'; import { locale, t } from 'svelte-i18n';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import { mdiMapMarker, mdiMusic, mdiOpenInNew } from '@mdi/js'; import { mdiMapMarker, mdiMusic, mdiOpenInNew } from '@mdi/js';
import Icon from '../../components/Icon.svelte'; import Icon from '../../components/Icon.svelte';
import Check from '../../components/Check.svelte'; import Check from '../../components/Check.svelte';
@ -16,7 +13,7 @@
import { readSave, updateSave } from '../../stores/saveManager'; import { readSave, updateSave } from '../../stores/saveManager';
import Ad from '../../components/Ad.svelte'; import Ad from '../../components/Ad.svelte';
export let data; let data = dataJson;
let spincrystals = data; let spincrystals = data;
let checkList = {}; let checkList = {};
@ -136,7 +133,7 @@
<Ad type="desktop" variant="lb" id="2" /> <Ad type="desktop" variant="lb" id="2" />
<Ad type="mobile" variant="lb" id="1" /> <Ad type="mobile" variant="lb" id="1" />
<style> <style lang="postcss">
.text { .text {
line-height: initial; line-height: initial;
} }

View file

@ -34,7 +34,7 @@
if ($firebaseToken === '') return; if ($firebaseToken === '') return;
console.log('get reminder'); console.log('get reminder');
const url = new URL(`${__paimon.env.API_HOST}/reminder`); const url = new URL(`${import.meta.env.VITE_API_HOST}/reminder`);
const query = new URLSearchParams({ token: $firebaseToken, type: 'hoyolab' }); const query = new URLSearchParams({ token: $firebaseToken, type: 'hoyolab' });
url.search = query.toString(); url.search = query.toString();
@ -59,7 +59,7 @@
async function deleteCurrentReminder() { async function deleteCurrentReminder() {
console.log('delete reminder'); console.log('delete reminder');
const url = new URL(`${__paimon.env.API_HOST}/reminder`); const url = new URL(`${import.meta.env.VITE_API_HOST}/reminder`);
const query = new URLSearchParams({ token: $firebaseToken, type: 'hoyolab' }); const query = new URLSearchParams({ token: $firebaseToken, type: 'hoyolab' });
url.search = query.toString(); url.search = query.toString();
@ -86,7 +86,7 @@
const reminderTime = dayjs(time, 'HH:mm').utc().year(2000).month(0).date(1).format('YYYY-MM-DD HH:mm:ssZ'); const reminderTime = dayjs(time, 'HH:mm').utc().year(2000).month(0).date(1).format('YYYY-MM-DD HH:mm:ssZ');
try { try {
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, { const res = await fetch(`${import.meta.env.VITE_API_HOST}/reminder`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({

View file

@ -45,7 +45,7 @@
if ($firebaseToken === '') return; if ($firebaseToken === '') return;
console.log('get reminder'); console.log('get reminder');
const url = new URL(`${__paimon.env.API_HOST}/reminder`); const url = new URL(`${import.meta.env.VITE_API_HOST}/reminder`);
const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' }); const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' });
url.search = query.toString(); url.search = query.toString();
@ -70,7 +70,7 @@
async function deleteCurrentReminder() { async function deleteCurrentReminder() {
console.log('delete reminder'); console.log('delete reminder');
const url = new URL(`${__paimon.env.API_HOST}/reminder`); const url = new URL(`${import.meta.env.VITE_API_HOST}/reminder`);
const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' }); const query = new URLSearchParams({ token: $firebaseToken, type: 'transformer' });
url.search = query.toString(); url.search = query.toString();
@ -109,7 +109,7 @@
} }
try { try {
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, { const res = await fetch(`${import.meta.env.VITE_API_HOST}/reminder`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({

View file

@ -62,7 +62,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
@screen md { @screen md {
.not-supported { .not-supported {
width: fit-content; width: fit-content;

View file

@ -67,7 +67,7 @@
<p class="text-red-400 mb-2">{$t('settings.importWarning')}</p> <p class="text-red-400 mb-2">{$t('settings.importWarning')}</p>
{#if !loading} {#if !loading}
<div class="flex"> <div class="flex">
<Button className="mr-2 overflow-hidden whitespace-no-wrap" on:click={() => input.click()}> <Button className="mr-2 overflow-hidden whitespace-nowrap" on:click={() => input.click()}>
{files !== null && files[0] ? files[0].name : $t('settings.selectFile')} {files !== null && files[0] ? files[0].name : $t('settings.selectFile')}
</Button> </Button>
{#if files !== null && files[0]} {#if files !== null && files[0]}

View file

@ -475,14 +475,14 @@
<a <a
href="https://discord.gg/tPURAYgHV9" href="https://discord.gg/tPURAYgHV9"
target="_blank" target="_blank"
class="whitespace-no-wrap bg-background rounded-xl pr-2 text-blue-400 hover:underline" class="whitespace-nowrap bg-background rounded-xl pr-2 text-blue-400 hover:underline"
><Icon path={mdiDiscord} /> Discord</a ><Icon path={mdiDiscord} /> Discord</a
> >
{$t('settings.or')} {$t('settings.or')}
<a <a
href="https://github.com/MadeBaruna/paimon-moe/issues" href="https://github.com/MadeBaruna/paimon-moe/issues"
target="_blank" target="_blank"
class="whitespace-no-wrap bg-background rounded-xl pr-2 text-blue-400 hover:underline" class="whitespace-nowrap bg-background rounded-xl pr-2 text-blue-400 hover:underline"
><Icon path={mdiGithub} /> Github Issues</a ><Icon path={mdiGithub} /> Github Issues</a
> >
{$t('settings.thanks')} {$t('settings.thanks')}

View file

@ -25,7 +25,6 @@
$: prevEnded = prev !== null && now.isAfter(prev.end); $: prevEnded = prev !== null && now.isAfter(prev.end);
$: shouldShowHourStart = diffStart <= 86400000 || event.duration > 6.5 || !prevNearby; $: shouldShowHourStart = diffStart <= 86400000 || event.duration > 6.5 || !prevNearby;
$: shouldShowHourEnd = diffEnd <= 86400000 || event.duration > 6.5 || !prevNearby; $: shouldShowHourEnd = diffEnd <= 86400000 || event.duration > 6.5 || !prevNearby;
</script> </script>
<div <div
@ -43,7 +42,7 @@
> >
<div class="event-item {nextDiff < 1 ? '' : 'rounded-xl'}" /> <div class="event-item {nextDiff < 1 ? '' : 'rounded-xl'}" />
<span <span
class="event-name text sticky left-0 font-display text-base md:text-lg text-black font-bold whitespace-no-wrap overflow-hidden" class="event-name text sticky left-0 font-display text-base md:text-lg text-black font-bold whitespace-nowrap overflow-hidden"
> >
{event.name} {event.name}
</span> </span>
@ -82,7 +81,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
div.event-item { div.event-item {
position: absolute; position: absolute;
opacity: 1; opacity: 1;
@ -102,5 +101,4 @@
text-shadow: var(--color) -1px -1px 4px, var(--color) 1px -1px 4px, var(--color) -1px 1px 4px, text-shadow: var(--color) -1px -1px 4px, var(--color) 1px -1px 4px, var(--color) -1px 1px 4px,
var(--color) 1px 1px 4px, var(--color) 0 0 10px; var(--color) 1px 1px 4px, var(--color) 0 0 10px;
} }
</style> </style>

View file

@ -332,7 +332,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
::-webkit-scrollbar { ::-webkit-scrollbar {
height: 8px; height: 8px;
} }

View file

@ -270,7 +270,6 @@
$: $todos, updateSummary(); $: $todos, updateSummary();
$: columnCount, updateId(); $: columnCount, updateId();
</script> </script>
<svelte:head> <svelte:head>
@ -304,7 +303,7 @@
{#each Object.entries(todayOnlyItems) as [id, amount]} {#each Object.entries(todayOnlyItems) as [id, amount]}
<tr class="today-only"> <tr class="today-only">
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-no-wrap`}> <span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-nowrap`}>
{numberFormat.format(amount)} {numberFormat.format(amount)}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -339,7 +338,7 @@
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<div class="flex justify-end items-center"> <div class="flex justify-end items-center">
<span class="text-white mr-2 whitespace-no-wrap"> <span class="text-white mr-2 whitespace-nowrap">
{numberFormat.format(amount)} {numberFormat.format(amount)}
</span> </span>
<img src="/images/resin.png" alt="resin" class="w-6 h-6 mr-2" /> <img src="/images/resin.png" alt="resin" class="w-6 h-6 mr-2" />
@ -375,7 +374,7 @@
{#each Object.entries(summary) as [id, amount]} {#each Object.entries(summary) as [id, amount]}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-no-wrap`}> <span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-nowrap`}>
{numberFormat.format(amount)} {numberFormat.format(amount)}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -460,7 +459,7 @@
{#each Object.entries(todo.resources).sort((a, b) => b[1] - a[1]) as [id, amount]} {#each Object.entries(todo.resources).sort((a, b) => b[1] - a[1]) as [id, amount]}
<tr> <tr>
<td class="text-right border-b border-gray-700 py-1"> <td class="text-right border-b border-gray-700 py-1">
<span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-no-wrap`}> <span class={`${amount === 0 ? 'line-through text-gray-600' : 'text-white'} mr-2 whitespace-nowrap`}>
{numberFormat.format(amount)} {numberFormat.format(amount)}
<Icon size={0.5} path={mdiClose} /></span <Icon size={0.5} path={mdiClose} /></span
> >
@ -491,11 +490,10 @@
</Masonry> </Masonry>
</div> </div>
<style> <style lang="postcss">
tr.today-only:last-child { tr.today-only:last-child {
td { td {
@apply border-b-0; @apply border-b-0;
} }
} }
</style> </style>

View file

@ -35,13 +35,13 @@
return Object.values(collection).sort((a, b) => a.id.localeCompare(b.id)); return Object.values(collection).sort((a, b) => a.id.localeCompare(b.id));
} }
export async function preload(page) { export async function load({ params }) {
const { id } = page.params; const { id } = params;
const weapon = data[id]; const weapon = data[id];
const materials = weaponList[id].ascension[0].items; const materials = weaponList[id].ascension[0].items;
const recommendedCharacter = getCharacter(id); const recommendedCharacter = getCharacter(id);
return { id, weapon, materials, recommendedCharacter }; return { props: { id, weapon, materials, recommendedCharacter } };
} }
</script> </script>
@ -137,22 +137,22 @@
</div> </div>
{/if} {/if}
<div class="mt-4 flex overflow-x-auto whitespace-no-wrap md:w-auto"> <div class="mt-4 flex overflow-x-auto whitespace-nowrap md:w-auto">
<div style="width: min-content;"> <div style="width: min-content;">
<div class="table max-w-full rounded-xl border border-gray-200 border-opacity-25"> <div class="table max-w-full rounded-xl border border-gray-200 border-opacity-25">
<table class="text-gray-200 w-full"> <table class="text-gray-200 w-full">
<tr> <tr>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('weapon.asc')} {$t('weapon.asc')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('weapon.lvl')} {$t('weapon.lvl')}
</td> </td>
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t('weapon.baseAtk')} {$t('weapon.baseAtk')}
</td> </td>
{#if weapon.secondary.name} {#if weapon.secondary.name}
<td class="text-center whitespace-no-wrap border-gray-700 font-semibold px-2"> <td class="text-center whitespace-nowrap border-gray-700 font-semibold px-2">
{$t(`weapon.${weapon.secondary.name}`)} {$t(`weapon.${weapon.secondary.name}`)}
</td> </td>
{/if} {/if}
@ -185,7 +185,7 @@
<Ad type="mobile" variant="lb" id="1" /> <Ad type="mobile" variant="lb" id="1" />
</div> </div>
<style> <style lang="postcss">
td:not(:last-child) { td:not(:last-child) {
@apply border-r; @apply border-r;
} }

View file

@ -1,8 +1,5 @@
<script context="module"> <script context="module">
import data from '../../data/weapons/en.json'; import data from '../../data/weapons/en.json';
export async function preload() {
return { data };
}
</script> </script>
<script> <script>
@ -12,7 +9,7 @@
import { formatStat } from '../../helper'; import { formatStat } from '../../helper';
import Ad from '../../components/Ad.svelte'; import Ad from '../../components/Ad.svelte';
export let data; let weaponData = data;
let weaponList = []; let weaponList = [];
let sortBy = 'name'; let sortBy = 'name';
let sortOrder = true; let sortOrder = true;
@ -34,7 +31,7 @@
function process() { function process() {
const _weapons = []; const _weapons = [];
for (const [id, weapon] of Object.entries(data)) { for (const [id, weapon] of Object.entries(weaponData)) {
if (['amber_bead', 'ebony_bow', 'quartz', 'the_flagstaff'].includes(id)) continue; if (['amber_bead', 'ebony_bow', 'quartz', 'the_flagstaff'].includes(id)) continue;
_weapons.push({ _weapons.push({
@ -94,7 +91,7 @@
async function changeLocale(locale) { async function changeLocale(locale) {
const _data = await import(`../../data/weapons/${locale}.json`); const _data = await import(`../../data/weapons/${locale}.json`);
data = _data.default; weaponData = _data.default;
process(); process();
} }
@ -115,7 +112,7 @@
<p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;"> <p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;">
{$t('weapon.subtitle')} {$t('weapon.subtitle')}
</p> </p>
<div class="block overflow-x-auto whitespace-no-wrap pb-8 relative"> <div class="block overflow-x-auto whitespace-nowrap pb-8 relative">
<Ad type="desktop" variant="mpu" id="1" class="absolute top-0" style="right: 50px;" /> <Ad type="desktop" variant="mpu" id="1" class="absolute top-0" style="right: 50px;" />
<div class="px-4 md:px-8 table max-w-full"> <div class="px-4 md:px-8 table max-w-full">
<table class="w-full block p-4 bg-item rounded-xl"> <table class="w-full block p-4 bg-item rounded-xl">

View file

@ -1,7 +1,7 @@
<script context="module"> <script context="module">
export async function preload(page) { export async function load({ params }) {
const { id } = page.params; const { id } = params;
return { id }; return { props: { id } };
} }
</script> </script>
@ -510,7 +510,7 @@
</div> </div>
{:else} {:else}
<div class="flex mt-4 wrapper"> <div class="flex mt-4 wrapper">
<div class="block overflow-x-auto xl:overflow-x-visible whitespace-no-wrap px"> <div class="block overflow-x-auto xl:overflow-x-visible whitespace-nowrap px">
<div class="flex pl-4 md:pl-8 mb-2"> <div class="flex pl-4 md:pl-8 mb-2">
<button on:click={() => toggleShowRarity(0)} class={`pill legendary ${showRarity[0] ? 'active' : ''}`}> <button on:click={() => toggleShowRarity(0)} class={`pill legendary ${showRarity[0] ? 'active' : ''}`}>
5 <Icon path={mdiStar} size={0.75} className="mb-1" /> 5 <Icon path={mdiStar} size={0.75} className="mb-1" />
@ -600,7 +600,7 @@
: ''}" : ''}"
> >
<td <td
class="border-t border-gray-700 px-4 text-gray-200 whitespace-no-wrap relative" class="border-t border-gray-700 px-4 text-gray-200 whitespace-nowrap relative"
style="font-family: monospace;" style="font-family: monospace;"
> >
{pull.formattedTime} {pull.formattedTime}
@ -710,19 +710,18 @@
<Ad type="mobile" variant="lb" id="1" /> <Ad type="mobile" variant="lb" id="1" />
</div> </div>
<style> <style lang="postcss">
.wrapper { .wrapper {
@apply flex-col-reverse; @apply flex-col-reverse;
.chart-area { .chart-area {
@apply px-4; @apply px-4;
@apply md:px-8;
@screen md {
@apply px-8;
}
} }
}
@media (min-width: 1920px) { @media (min-width: 1920px) {
.wrapper {
@apply flex-row; @apply flex-row;
.chart-area { .chart-area {

View file

@ -4,7 +4,7 @@
import { onMount, getContext, createEventDispatcher } from 'svelte'; import { onMount, getContext, createEventDispatcher } from 'svelte';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { mdiPencil, mdiStar, mdiChevronDown, mdiTableOfContents, mdiArrowUpCircle } from '@mdi/js'; import { mdiPencil, mdiStar, mdiChevronDown, mdiTableOfContents, mdiArrowUpCircle } from '@mdi/js';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
const { open: openModal, close: closeModal } = getContext('simple-modal'); const { open: openModal, close: closeModal } = getContext('simple-modal');
@ -319,7 +319,7 @@
isEdit ? 'bg-item flex-col py-2' : 'bg-background flex-row items-center justify-center mb-2 p-4' isEdit ? 'bg-item flex-col py-2' : 'bg-background flex-row items-center justify-center mb-2 p-4'
} rounded-xl flex relative`} } rounded-xl flex relative`}
> >
<span class="text-gray-200 whitespace-no-wrap flex-1"> <span class="text-gray-200 whitespace-nowrap flex-1">
{$t('wish.lifetimePulls')}<br /> {$t('wish.lifetimePulls')}<br />
<span class="flex items-center text-gray-600"> <span class="flex items-center text-gray-600">
<img class="w-4 h-4 mr-2" src="/images/primogem.png" alt="primogem" /> <img class="w-4 h-4 mr-2" src="/images/primogem.png" alt="primogem" />
@ -345,7 +345,7 @@
</div> </div>
</div> </div>
{/if} {/if}
<span class="text-gray-200 whitespace-no-wrap flex-1"> <span class="text-gray-200 whitespace-nowrap flex-1">
5★ {$t('wish.pity')} 5★ {$t('wish.pity')}
<br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: legendaryPity } })}</span> <br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: legendaryPity } })}</span>
</span> </span>
@ -374,7 +374,7 @@
</div> </div>
</div> </div>
{/if} {/if}
<span class="text-gray-200 whitespace-no-wrap flex-1"> <span class="text-gray-200 whitespace-nowrap flex-1">
4★ {$t('wish.pity')} 4★ {$t('wish.pity')}
<br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: 10 } })}</span> <br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: 10 } })}</span>
</span> </span>
@ -476,7 +476,7 @@
{:else if pull.type === 'unknown_3_star'} {:else if pull.type === 'unknown_3_star'}
<td class="border-b border-gray-700 py-1 pl-2 font-semibold text-primary">Unknown</td> <td class="border-b border-gray-700 py-1 pl-2 font-semibold text-primary">Unknown</td>
{/if} {/if}
<td class="border-b border-gray-700 text-xs py-1 px-2 whitespace-no-wrap" style="font-family: monospace;"> <td class="border-b border-gray-700 text-xs py-1 px-2 whitespace-nowrap" style="font-family: monospace;">
{pull.time} {pull.time}
</td> </td>
<td class="text-right border-b border-gray-700 py-1">{pull.pity}</td> <td class="text-right border-b border-gray-700 py-1">{pull.pity}</td>
@ -487,7 +487,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.pill { .pill {
@apply rounded-2xl; @apply rounded-2xl;
@apply border-2; @apply border-2;

View file

@ -70,7 +70,7 @@
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-left">{$t('wish.detail.rarity')}</td> <td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-left">{$t('wish.detail.rarity')}</td>
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.detail.total')}</td> <td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.detail.total')}</td>
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.detail.percent')}</td> <td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.detail.percent')}</td>
<td class="text-gray-400 text-sm font-display text-right whitespace-no-wrap">{$t('wish.detail.pityAverage')}</td> <td class="text-gray-400 text-sm font-display text-right whitespace-nowrap">{$t('wish.detail.pityAverage')}</td>
</tr> </tr>
<tr> <tr>
<td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700"> <td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700">
@ -101,7 +101,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="text-rare-from font-semibold pl-4 md:pl-4 pr-2 md:pr-4 border-t border-gray-700 whitespace-no-wrap"> <td class="text-rare-from font-semibold pl-4 md:pl-4 pr-2 md:pr-4 border-t border-gray-700 whitespace-nowrap">
{$t('wish.detail.character')} {$t('wish.detail.character')}
</td> </td>
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700"> <td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
@ -115,7 +115,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="text-rare-from font-semibold pl-4 md:pl-4 pr-2 md:pr-4 border-t border-gray-700 whitespace-no-wrap"> <td class="text-rare-from font-semibold pl-4 md:pl-4 pr-2 md:pr-4 border-t border-gray-700 whitespace-nowrap">
{$t('wish.detail.weapon')} {$t('wish.detail.weapon')}
</td> </td>
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700"> <td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
@ -136,13 +136,13 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
span.pity { span.pity {
@apply rounded-xl; @apply rounded-xl;
@apply text-gray-400; @apply text-gray-400;
@apply border; @apply border;
@apply border-legendary-from; @apply border-legendary-from;
@apply whitespace-no-wrap; @apply whitespace-nowrap;
@apply px-2; @apply px-2;
@apply mb-1; @apply mb-1;
@apply mr-1; @apply mr-1;

View file

@ -703,7 +703,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
.pill { .pill {
@apply rounded-2xl; @apply rounded-2xl;
@apply border-2; @apply border-2;

View file

@ -43,7 +43,7 @@
</p> </p>
<p class="mb-2"> <p class="mb-2">
{$t('wish.welcomeStart1')} {$t('wish.welcomeStart1')}
<span class="bg-background px-2 rounded-xl font-bold whitespace-no-wrap">{$t('wish.autoImport')}</span> <span class="bg-background px-2 rounded-xl font-bold whitespace-nowrap">{$t('wish.autoImport')}</span>
{$t('wish.welcomeStart2')} {$t('wish.welcomeStart2')}
<Icon path={mdiArrowUp} size={1.2} /> <Icon path={mdiArrowUp} size={1.2} />
</p> </p>
@ -54,7 +54,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.bubble::after { .bubble::after {
content: ''; content: '';
position: absolute; position: absolute;

View file

@ -51,7 +51,7 @@
if (wishCount === 0) return; if (wishCount === 0) return;
try { try {
const url = new URL(`${__paimon.env.API_HOST}/wish/summary`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish/summary`);
const query = new URLSearchParams({ banner: current }); const query = new URLSearchParams({ banner: current });
url.search = query.toString(); url.search = query.toString();
@ -99,7 +99,7 @@
if (percentages[current] === undefined) return; if (percentages[current] === undefined) return;
try { try {
const url = new URL(`${__paimon.env.API_HOST}/wish/summary/luck`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish/summary/luck`);
const query = new URLSearchParams({ banner: current, rarity }); const query = new URLSearchParams({ banner: current, rarity });
url.search = query.toString(); url.search = query.toString();
@ -151,7 +151,7 @@
} }
try { try {
const url = new URL(`${__paimon.env.API_HOST}/wish/summary/winrateoff`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish/summary/winrateoff`);
const query = new URLSearchParams({ banner: current, rarity }); const query = new URLSearchParams({ banner: current, rarity });
url.search = query.toString(); url.search = query.toString();
@ -368,7 +368,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
.pill { .pill {
@apply text-sm; @apply text-sm;
@apply rounded-2xl; @apply rounded-2xl;

View file

@ -3,7 +3,7 @@
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import { characters } from '../../data/characters'; import { characters } from '../../data/characters';
import { weaponList } from '../../data/weaponList'; import { weaponList } from '../../data/weaponList';
@ -304,7 +304,7 @@
</div> </div>
{/if} {/if}
<style> <style lang="postcss">
.container { .container {
@apply flex flex-col gap-4; @apply flex flex-col gap-4;
} }

View file

@ -32,7 +32,7 @@
<td class="text-white text-md font-semibold pr-2 md:pr-4 flex-1 w-full">{$t(`wish.types.${type.id}`)}</td> <td class="text-white text-md font-semibold pr-2 md:pr-4 flex-1 w-full">{$t(`wish.types.${type.id}`)}</td>
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.summary.total')}</td> <td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.summary.total')}</td>
<td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.summary.percent')}</td> <td class="text-gray-400 text-sm font-display pr-2 md:pr-4 text-right">{$t('wish.summary.percent')}</td>
<td class="text-gray-400 text-sm font-display text-right whitespace-no-wrap">{$t('wish.summary.pityAverage')}</td> <td class="text-gray-400 text-sm font-display text-right whitespace-nowrap">{$t('wish.summary.pityAverage')}</td>
</tr> </tr>
<tr> <tr>
<td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700"> <td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700">
@ -131,13 +131,13 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
span.pity { span.pity {
@apply rounded-xl; @apply rounded-xl;
@apply text-gray-400; @apply text-gray-400;
@apply border; @apply border;
@apply border-legendary-from; @apply border-legendary-from;
@apply whitespace-no-wrap; @apply whitespace-nowrap;
@apply px-2; @apply px-2;
@apply mb-1; @apply mb-1;
@apply mr-1; @apply mr-1;

View file

@ -1,7 +1,7 @@
<script> <script>
import { getContext, onMount } from 'svelte'; import { getContext, onMount } from 'svelte';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { goto } from '@sapper/app'; import { goto } from '$app/navigation';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { import {
mdiCheckBold, mdiCheckBold,
@ -251,7 +251,7 @@
try { try {
const res = await fetchRetry( const res = await fetchRetry(
`${__paimon.env.API_HOST}/corsproxy`, `${import.meta.env.VITE_API_HOST}/corsproxy`,
{ {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -286,7 +286,7 @@
} }
if (lastCount < fetchSize && dat.data.list.length > 0) { if (lastCount < fetchSize && dat.data.list.length > 0) {
await fetch(`${__paimon.env.API_HOST}/corsreset`); await fetch(`${import.meta.env.VITE_API_HOST}/corsreset`);
fetchSize = 6; fetchSize = 6;
lastCount = fetchSize; lastCount = fetchSize;
error = $t('wish.import.invalidData'); error = $t('wish.import.invalidData');
@ -828,7 +828,7 @@
async function getNews() { async function getNews() {
try { try {
const res = await fetch(`${__paimon.env.API_HOST}/news/wish`); const res = await fetch(`${import.meta.env.VITE_API_HOST}/news/wish`);
if (res.status === 200) { if (res.status === 200) {
const json = await res.json(); const json = await res.json();
news = json.message; news = json.message;
@ -1037,9 +1037,10 @@
<p class="text-white">{$t('wish.import.guide.pc2.3')}</p> <p class="text-white">{$t('wish.import.guide.pc2.3')}</p>
<div class="flex"> <div class="flex">
<pre <pre
class="bg-black text-white bg-opacity-50 whitespace-pre-wrap break-all p-2 rounded-xl text-xs select-all flex-1"> class="bg-black text-white bg-opacity-50 whitespace-pre-wrap break-all p-2 rounded-xl text-xs select-all flex-1">{$server ===
{$server === 'China' ? powershellScriptChina : powershellScript} 'China'
</pre> ? powershellScriptChina
: powershellScript}</pre>
<button <button
on:click={copyScript} on:click={copyScript}
class="bg-black bg-opacity-50 hover:bg-opacity-25 text-white px-2 ml-1 rounded-xl" class="bg-black bg-opacity-50 hover:bg-opacity-25 text-white px-2 ml-1 rounded-xl"
@ -1101,9 +1102,11 @@
</div> </div>
<div class="content flex-col items-center pb-2"> <div class="content flex-col items-center pb-2">
<p class="text-white">{$t('wish.import.guide.pclog.2')}</p> <p class="text-white">{$t('wish.import.guide.pclog.2')}</p>
<pre class="bg-black text-white bg-opacity-50 whitespace-pre-wrap break-all p-2 rounded-xl text-xs select-all"> <pre
{$server === 'China' ? $t('wish.import.logLocation.china') : $t('wish.import.logLocation.global')} class="bg-black text-white bg-opacity-50 whitespace-pre-wrap break-all p-2 rounded-xl text-xs select-all">{$server ===
</pre> 'China'
? $t('wish.import.logLocation.china')
: $t('wish.import.logLocation.global')}</pre>
</div> </div>
</div> </div>
<div class="flex space-x-3 mb-2"> <div class="flex space-x-3 mb-2">
@ -1363,10 +1366,10 @@
{#if wishes[code] !== undefined} {#if wishes[code] !== undefined}
<tr> <tr>
<td class="px-2 py-1"> <td class="px-2 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{type.name} Banner</span> <span class="text-white mr-2 whitespace-nowrap">{type.name} Banner</span>
</td> </td>
<td class="pr-2 py-1"> <td class="pr-2 py-1">
<span class="text-white mr-2 whitespace-no-wrap"> <span class="text-white mr-2 whitespace-nowrap">
<Icon size={0.5} path={mdiClose} /> <Icon size={0.5} path={mdiClose} />
{numberFormat.format(wishes[code].length)} {numberFormat.format(wishes[code].length)}
</span> </span>
@ -1416,7 +1419,7 @@
<Ad class="ml-4" type="desktop" variant="mpu" id="1" /> <Ad class="ml-4" type="desktop" variant="mpu" id="1" />
</div> </div>
<style> <style lang="postcss">
.step-number { .step-number {
min-height: 2rem; min-height: 2rem;
} }

View file

@ -255,7 +255,7 @@
</div> </div>
</div> </div>
<style> <style lang="postcss">
@media (min-width: 1920px) { @media (min-width: 1920px) {
.top-header { .top-header {
@apply flex-row; @apply flex-row;

View file

@ -1,294 +0,0 @@
<script context="module">
export async function preload(page) {
const { id } = page.params;
return { id };
}
</script>
<script>
import { mdiLoading } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import Icon from '../../../components/Icon.svelte';
import { banners } from '../../../data/banners';
import { characters } from '../../../data/characters';
import { weaponList } from '../../../data/weaponList';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(duration);
dayjs.extend(relativeTime);
const numberFormat = Intl.NumberFormat('en', {
maximumFractionDigits: 2,
minimumFractionDigits: 0,
});
const numberFormatFixed = Intl.NumberFormat('en', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
export let id;
let banner = {};
let loading = true;
let data;
let legendaryList = [];
let totalGuarantee = 0;
let totalFeatured = 0;
let legendaryPity = [];
let rarePity = [];
let rarePercentage = {
min: 0,
max: 0,
};
if (id.startsWith('2')) {
banner = banners.standard[0];
} else if (id.startsWith('3')) {
const index = Number(id.substring(4)) - 1;
banner = banners.characters[index];
} else if (id.startsWith('4')) {
const index = Number(id.substring(4)) - 1;
banner = banners.weapons[index];
}
async function getData() {
const url = new URL(`${__paimon.env.API_HOST}/wish`);
const query = new URLSearchParams({ banner: id });
url.search = query.toString();
try {
const res = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
data = await res.json();
let totalRare = data.total.rare;
if (id > 300011 && id < 400000) {
totalRare = data.list.reduce((prev, current) => {
if (banner.featuredRare.includes(current.name)) {
prev += current.count;
}
return prev;
}, 0);
}
legendaryList = data.list
.sort((a, b) => {
return b.count - a.count;
})
.map((e) => {
if (e.type === 'character') {
const rarity = characters[e.name].rarity;
e.percentage = rarity === 5 ? e.count / data.total.legendary : e.count / totalRare;
} else if (e.type === 'weapon') {
const rarity = weaponList[e.name].rarity;
e.percentage = rarity === 5 ? e.count / data.total.legendary : e.count / totalRare;
}
if (id !== '200001' && banner.featured.includes(e.name)) {
totalGuarantee = e.guaranteed;
totalFeatured = e.count;
}
return e;
});
legendaryPity = data.pityCount.legendary.slice(1, 91).map((e, i) => {
const percentage = e / data.countEachPity[i];
return {
total: e,
percentage,
};
});
rarePity = data.pityCount.rare.map((e) => ({
total: e,
percentage: e / data.total.rare,
}));
rarePity.forEach((e) => {
if (rarePercentage.min > e.percentage) {
rarePercentage.min = e.percentage;
}
if (rarePercentage.max < e.percentage) {
rarePercentage.max = e.percentage;
}
});
} catch (err) {
console.error(err);
}
loading = false;
}
function mapVal(value, x1, y1, x2, y2) {
return ((value - x1) * (y2 - x2)) / (y1 - x1) + x2;
}
onMount(() => {
getData();
});
</script>
<svelte:head>
<title>Wish Tally - Paimon.moe</title>
<meta name="description" content="Genshin Impact Wish Tally average pity percentage from paimon.moe users" />
<meta property="og:description" content="Genshin Impact Wish Tally average pity percentage from paimon.moe users" />
</svelte:head>
<div>
<div class="lg:ml-64 pt-20 lg:pt-8">
<h1 class="font-display px-4 md:px-8 font-black text-4xl text-white">{$t('wish.tally.title')} {banner.name}</h1>
<p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;">
{$t('wish.tally.subtitle')}
</p>
</div>
<div class="lg:ml-64 px-8">
<div class="flex flex-col lg:flex-row items-end">
<img src="/images/banners/{banner.name} {banner.image}.png" alt={banner.name} class="rounded-xl w-full h-auto lg:h-64 lg:w-auto" />
{#if loading}
<Icon className="m-4" path={mdiLoading} color="white" size={2} spin />
{:else}
<div class="border border-gray-700 rounded-xl ml-4" style="width: fit-content; height: fit-content;">
<table class="text-white">
<tr>
<td class="px-2 border-r border-gray-700">Last Update</td>
<td class="px-2 border-gray-700">{dayjs(data.time).fromNow()}</td>
</tr>
<tr>
<td class="px-2 border-t border-r border-gray-700">Wish Total</td>
<td class="px-2 border-t border-gray-700">{numberFormat.format(data.total.all)}</td>
</tr>
<tr>
<td class="px-2 border-t border-r border-gray-700">Total User</td>
<td class="px-2 border-t border-gray-700">{numberFormat.format(data.total.users)}</td>
</tr>
<tr>
<td class="px-2 border-t border-r border-gray-700">5★ Median</td>
<td class="px-2 border-t border-gray-700">{numberFormat.format(data.median.legendary)}</td>
</tr>
<tr>
<td class="px-2 border-t border-r border-gray-700">Total 5★</td>
<td class="px-2 border-t border-gray-700">
{numberFormat.format(data.total.legendary)}
({numberFormat.format((data.total.legendary / data.total.all) * 100)}%)
</td>
</tr>
<tr>
<td class="px-2 border-t border-r border-gray-700">Total 4★</td>
<td class="px-2 border-t border-gray-700">
{numberFormat.format(data.total.rare)}
({numberFormat.format((data.total.rare / data.total.all) * 100)}%)
</td>
</tr>
{#if id > 300000 && id < 400000}
<tr>
<td class="px-2 border-t border-r border-gray-700">Won 50:50</td>
<td class="px-2 border-t border-gray-700">
{numberFormat.format(
((totalFeatured - totalGuarantee) / (data.total.legendary - totalGuarantee)) * 100,
)}%
</td>
</tr>
{/if}
</table>
</div>
{/if}
</div>
{#if !loading}
<div class="flex flex-col lg:flex-row space-y-4 lg:space-x-4">
<div
class="border border-gray-700 rounded-xl mt-4 overflow-hidden"
style="width: fit-content; height: fit-content;"
>
<table class="text-white">
<tr>
<td class="text-center px-2 border-gray-700 text-sm">5★ Total<br />Chance%</td>
{#each [...new Array(10)] as _, i}
<td class="text-center px-2 border-l border-gray-700">{i + 1}</td>
{/each}
</tr>
{#each [...new Array(9)] as _, i}
<tr>
<td class="text-center px-2 border-t border-gray-700">{i * 10 + 1} - {(i + 1) * 10}</td>
{#each [...new Array(10)] as _, j}
<td
class="text-center px-2 border-t border-l border-gray-700"
style="font-family: monospace; background: rgba(25, 142, 81, {legendaryPity[i * 10 + j]
.percentage});"
title="Pity {i * 10 + j + 1}"
>
{legendaryPity[i * 10 + j].total}
<br />
{numberFormatFixed.format((legendaryPity[i * 10 + j].percentage || 0) * 100)}
</td>
{/each}
</tr>
{/each}
</table>
</div>
<div
class="border border-gray-700 rounded-xl mt-4 overflow-hidden"
style="width: fit-content; height: fit-content;"
>
<table class="text-white">
<tr>
<td class="text-center px-2 border-gray-700">4★</td>
<td class="text-center px-2 border-l border-gray-700">Total</td>
<td class="text-center px-2 border-l border-gray-700">%</td>
</tr>
{#each rarePity as pity, i}
<tr>
<td class="text-center px-2 border-t border-gray-700">{i + 1}</td>
<td class="text-center px-2 border-l border-t border-gray-700" style="font-family: monospace;">
{numberFormat.format(pity.total)}
</td>
<td
class="text-center px-2 border-l border-t border-gray-700"
style="font-family: monospace; background: rgba(25, 142, 81, {mapVal(
pity.percentage,
rarePercentage.min,
rarePercentage.max,
0,
1,
)});"
>
{numberFormat.format(pity.percentage * 100)}
</td>
</tr>
{/each}
</table>
</div>
<div class="border border-gray-700 rounded-xl mt-4" style="width: fit-content; height: fit-content;">
<table class="text-white">
<tr>
<td class="text-center px-2 border-r border-gray-700">Name</td>
<td class="text-center px-2 border-r border-gray-700">Total</td>
<td class="text-center px-2">%</td>
</tr>
{#each legendaryList as item}
<tr>
<td class="px-2 border-t border-r border-gray-700">
{item.type === 'character' ? characters[item.name].name : weaponList[item.name].name}
</td>
<td class="px-2 border-t border-r border-gray-700 text-right" style="font-family: monospace;">
{item.count}
</td>
<td class="px-2 border-t border-gray-700 text-right" style="font-family: monospace;">
{numberFormatFixed.format(item.percentage * 100)}
</td>
</tr>
{/each}
</table>
</div>
</div>
{/if}
</div>
</div>

View file

@ -106,9 +106,9 @@
}; };
const rareInclude = { const rareInclude = {
300011: ['rosaria'], 300011: ['rosaria'],
300012: ['yanfei', 'noelle', 'diona'], 300012: ['yanfei', 'noelle', 'diona'],
}; };
let promotedRarePercentage = 0; let promotedRarePercentage = 0;
let legendaryList = []; let legendaryList = [];
@ -122,7 +122,7 @@
}); });
async function getData() { async function getData() {
const url = new URL(`${__paimon.env.API_HOST}/wish`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish`);
const query = new URLSearchParams({ banner: id }); const query = new URLSearchParams({ banner: id });
url.search = query.toString(); url.search = query.toString();
@ -173,19 +173,22 @@
// only for zhongli banner upward // only for zhongli banner upward
if (id > 300011 && id < 400000) { if (id > 300011 && id < 400000) {
const totalRare = data.list.reduce((prev, current) => { const totalRare = data.list.reduce(
if (rareInclude[id].includes(current.name)) { (prev, current) => {
prev.total += current.count; if (rareInclude[id].includes(current.name)) {
} prev.total += current.count;
if (featured[1] === current.name) { }
prev.featured = current.count; if (featured[1] === current.name) {
} prev.featured = current.count;
return prev; }
}, { return prev;
total: 0, },
featured: 0, {
}); total: 0,
promotedRarePercentage = totalRare.featured / totalRare.total * 100 featured: 0,
},
);
promotedRarePercentage = (totalRare.featured / totalRare.total) * 100;
} }
legendary = { legendary = {
@ -340,7 +343,7 @@
100, 100,
)}% )}%
{$t('wish.tally.wonFiftyFifty')} {$t('wish.tally.wonFiftyFifty')}
{:else if id > 300011 && id < 400000 && i === 1} {:else if id > 300011 && id < 400000 && i === 1}
{numberFormat.format(promotedRarePercentage)}% {numberFormat.format(promotedRarePercentage)}%
{$t('wish.tally.fromFourStarFeatured')} {$t('wish.tally.fromFourStarFeatured')}
{:else} {:else}
@ -365,7 +368,7 @@
{numberFormat.format(legendary.percentage)}% {numberFormat.format(legendary.percentage)}%
</p> </p>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<p class="font-semibold whitespace-no-wrap"> <p class="font-semibold whitespace-nowrap">
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
@ -380,7 +383,7 @@
{numberFormatSecondary.format(rare.percentage)}% {numberFormatSecondary.format(rare.percentage)}%
</p> </p>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<p class="font-semibold whitespace-no-wrap"> <p class="font-semibold whitespace-nowrap">
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
@ -489,7 +492,7 @@
<table class="text-white w-full table-fixed text-sm"> <table class="text-white w-full table-fixed text-sm">
<tr> <tr>
<td <td
class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-no-wrap text-right border-b border-background" class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-nowrap text-right border-b border-background"
> >
5★<br />{$t('wish.tally.pity')} 5★<br />{$t('wish.tally.pity')}
</td> </td>
@ -535,11 +538,14 @@
</table> </table>
</div> </div>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div class="border border-background rounded-xl hidden xl:block overflow-hidden mr-4 mb-2" style="width: fit-content;"> <div
class="border border-background rounded-xl hidden xl:block overflow-hidden mr-4 mb-2"
style="width: fit-content;"
>
<table class="text-white text-sm"> <table class="text-white text-sm">
<tr> <tr>
<td <td
class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-no-wrap text-right border-b border-background" class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-nowrap text-right border-b border-background"
> >
4★ {$t('wish.tally.pity')} 4★ {$t('wish.tally.pity')}
</td> </td>
@ -578,20 +584,20 @@
</div> </div>
<div class="flex flex-wrap text-white -mt-2 mb-2"> <div class="flex flex-wrap text-white -mt-2 mb-2">
<div class="space-y-2 flex flex-col flex-wrap mr-2 mt-2"> <div class="space-y-2 flex flex-col flex-wrap mr-2 mt-2">
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.wishTotal')} <span class="font-semibold ml-2">{numberFormat.format(data.total.all)}</span> {$t('wish.tally.wishTotal')} <span class="font-semibold ml-2">{numberFormat.format(data.total.all)}</span>
</div> </div>
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.worth')} <img class="w-4 h-4 inline mx-1" src="/images/primogem.png" alt="primogem" /> {$t('wish.tally.worth')} <img class="w-4 h-4 inline mx-1" src="/images/primogem.png" alt="primogem" />
<span class="font-semibold">{numberFormat.format(data.total.all * 160)}</span> <span class="font-semibold">{numberFormat.format(data.total.all * 160)}</span>
</div> </div>
</div> </div>
<div class="space-y-2 flex flex-col flex-wrap mt-2"> <div class="space-y-2 flex flex-col flex-wrap mt-2">
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.median')} {$t('wish.tally.median')}
<span class="font-semibold ml-2">{numberFormat.format(data.median.legendary)}</span> <span class="font-semibold ml-2">{numberFormat.format(data.median.legendary)}</span>
</div> </div>
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.user')} <span class="font-semibold ml-2">{numberFormat.format(data.total.users)}</span> {$t('wish.tally.user')} <span class="font-semibold ml-2">{numberFormat.format(data.total.users)}</span>
</div> </div>
</div> </div>
@ -603,7 +609,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
@screen xl { @screen xl {
.pity-summary { .pity-summary {
min-width: 320px; min-width: 320px;

View file

@ -119,7 +119,7 @@
}); });
async function getData() { async function getData() {
const url = new URL(`${__paimon.env.API_HOST}/wish`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish`);
const query = new URLSearchParams({ banner: id }); const query = new URLSearchParams({ banner: id });
url.search = query.toString(); url.search = query.toString();
@ -381,7 +381,7 @@
{numberFormat.format(legendary.percentage)}% {numberFormat.format(legendary.percentage)}%
</p> </p>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<p class="font-semibold whitespace-no-wrap"> <p class="font-semibold whitespace-nowrap">
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
@ -396,7 +396,7 @@
{numberFormatSecondary.format(rare.percentage)}% {numberFormatSecondary.format(rare.percentage)}%
</p> </p>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<p class="font-semibold whitespace-no-wrap"> <p class="font-semibold whitespace-nowrap">
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
@ -505,7 +505,7 @@
<table class="text-white w-full table-fixed text-sm"> <table class="text-white w-full table-fixed text-sm">
<tr> <tr>
<td <td
class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-no-wrap text-right border-b border-background" class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-nowrap text-right border-b border-background"
> >
5★<br />{$t('wish.tally.pity')} 5★<br />{$t('wish.tally.pity')}
</td> </td>
@ -560,7 +560,7 @@
<table class="text-white text-sm"> <table class="text-white text-sm">
<tr> <tr>
<td <td
class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-no-wrap text-right border-b border-background" class="font-display text-gray-200 font-semibold px-2 py-1 whitespace-nowrap text-right border-b border-background"
> >
4★ {$t('wish.tally.pity')} 4★ {$t('wish.tally.pity')}
</td> </td>
@ -599,27 +599,27 @@
</div> </div>
<div class="flex flex-wrap text-white -mt-2 mb-2"> <div class="flex flex-wrap text-white -mt-2 mb-2">
<div class="space-y-2 flex flex-col flex-wrap mr-2 mt-2"> <div class="space-y-2 flex flex-col flex-wrap mr-2 mt-2">
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.wishTotal')} <span class="font-semibold ml-2">{numberFormat.format(data.total.all)}</span> {$t('wish.tally.wishTotal')} <span class="font-semibold ml-2">{numberFormat.format(data.total.all)}</span>
</div> </div>
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.worth')} <img class="w-4 h-4 inline mx-1" src="/images/primogem.png" alt="primogem" /> {$t('wish.tally.worth')} <img class="w-4 h-4 inline mx-1" src="/images/primogem.png" alt="primogem" />
<span class="font-semibold">{numberFormat.format(data.total.all * 160)}</span> <span class="font-semibold">{numberFormat.format(data.total.all * 160)}</span>
</div> </div>
</div> </div>
<div class="space-y-2 flex flex-col flex-wrap mr-2 mt-2"> <div class="space-y-2 flex flex-col flex-wrap mr-2 mt-2">
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.median')} {$t('wish.tally.median')}
<span class="font-semibold ml-2">{numberFormat.format(data.median.legendary)}</span> <span class="font-semibold ml-2">{numberFormat.format(data.median.legendary)}</span>
</div> </div>
<div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap"> <div class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap">
{$t('wish.tally.user')} <span class="font-semibold ml-2">{numberFormat.format(data.total.users)}</span> {$t('wish.tally.user')} <span class="font-semibold ml-2">{numberFormat.format(data.total.users)}</span>
</div> </div>
</div> </div>
<div class="space-y-2 flex flex-col flex-wrap mt-2"> <div class="space-y-2 flex flex-col flex-wrap mt-2">
<a <a
href="/wish/tally/{id}" href="/wish/tally/{id}"
class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-no-wrap hover:opacity-75" class="bg-background rounded-xl px-4 py-2 flex-1 flex items-center whitespace-nowrap hover:opacity-75"
> >
{$t('wish.tally.detail')} {$t('wish.tally.detail')}
</a> </a>
@ -632,7 +632,7 @@
{/if} {/if}
</div> </div>
<style> <style lang="postcss">
@screen xl { @screen xl {
.pity-summary { .pity-summary {
min-width: 320px; min-width: 320px;

View file

@ -142,7 +142,7 @@
loading = true; loading = true;
loadingCons = true; loadingCons = true;
const url = new URL(`${__paimon.env.API_HOST}/wish`); const url = new URL(`${import.meta.env.VITE_API_HOST}/wish`);
const query = new URLSearchParams({ banner: id }); const query = new URLSearchParams({ banner: id });
url.search = query.toString(); url.search = query.toString();
@ -626,7 +626,7 @@
{numberFormat.format(legendary.percentage)}% {numberFormat.format(legendary.percentage)}%
</td> </td>
<td class="bg-background rounded-r-xl py-4 pr-4 text-legendary-from"> <td class="bg-background rounded-r-xl py-4 pr-4 text-legendary-from">
<p class="font-semibold whitespace-no-wrap"> <p class="font-semibold whitespace-nowrap">
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
@ -644,7 +644,7 @@
{numberFormat.format(rare.percentage)}% {numberFormat.format(rare.percentage)}%
</td> </td>
<td class="bg-background rounded-r-xl py-4 pr-4 text-rare-from"> <td class="bg-background rounded-r-xl py-4 pr-4 text-rare-from">
<p class="font-semibold whitespace-no-wrap"> <p class="font-semibold whitespace-nowrap">
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
<Icon path={mdiStar} size={0.8} /> <Icon path={mdiStar} size={0.8} />
@ -818,7 +818,7 @@
<Ad type="mobile" variant="lb" id="2" /> <Ad type="mobile" variant="lb" id="2" />
</div> </div>
<style> <style lang="postcss">
@screen md { @screen md {
.select-name { .select-name {
width: 100%; width: 100%;

View file

@ -1,51 +0,0 @@
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
import fs from 'fs';
import path from 'path';
import { i18nMiddleware } from './i18n.js';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
function serve(pathname) {
const filter = (req) => req.path === pathname;
const read = (file) => fs.readFileSync(path.join('__sapper__/dev', file));
return (req, res, next) => {
if (filter(req)) {
try {
const file = path.posix.normalize(decodeURIComponent(req.path));
const data = read(file);
res.setHeader('Content-Type', 'text/javascript');
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.end(data);
} catch (err) {
if (err.code === 'ENOENT') {
next();
} else {
console.error(err);
res.statusCode = 500;
res.end('an error occurred while reading a static file from disk');
}
}
} else {
next();
}
};
}
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
i18nMiddleware(),
serve('/firebase-messaging-sw.js'),
serve('/firebase-messaging-sw.js.map'),
sapper.middleware(),
)
.listen(PORT, (err) => {
if (err) console.log('error', err);
});

View file

@ -1,86 +1,72 @@
import { timestamp, files, shell } from '@sapper/service-worker'; import { version } from '$service-worker';
const ASSETS = `cache${timestamp}`; const CACHE = `cache${version}`;
const channel = new BroadcastChannel('paimonmoe-sw');
// `shell` is an array of all the files generated by the bundler, self.addEventListener('install', (event) => {
// `files` is an array of everything in the `static` directory event.waitUntil(self.skipWaiting());
const to_cache = shell.concat(files);
const staticAssets = new Set(to_cache);
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting();
})
);
}); });
self.addEventListener('activate', event => { async function fetchAddCache(url) {
event.waitUntil( try {
caches.keys().then(async keys => { const cache = await caches.open(CACHE);
// delete old caches const cachedRes = await cache.match(url);
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}
self.clients.claim(); if (cachedRes) return;
})
);
});
const res = await fetch(url);
/** cache.put(url, res.clone());
* Fetch the asset from the network and store it in the cache. } catch (err) {
* Fall back to the cache if the user is offline. console.error(err);
*/ }
async function fetchAndCache(request) {
const cache = await caches.open(`offline${timestamp}`)
try {
const response = await fetch(request);
cache.put(request, response.clone());
return response;
} catch (err) {
const response = await cache.match(request);
if (response) return response;
throw err;
}
} }
self.addEventListener('fetch', event => { self.addEventListener('activate', (event) => {
if (event.request.method !== 'GET' || event.request.headers.has('range')) return; event.waitUntil(
caches.keys().then(async (keys) => {
let needUpdate = false;
// delete old caches
for (const key of keys) {
if (key !== CACHE) {
await caches.delete(key);
needUpdate = true;
}
}
const url = new URL(event.request.url); self.clients.claim();
console.log('SW NEED UPDATE', needUpdate);
if (needUpdate) {
channel.postMessage({
type: 'update',
version,
});
}
// don't try to handle e.g. data: URIs fetchAddCache('/');
const isHttp = url.protocol.startsWith('http'); channel.addEventListener('message', (event) => {
const isDevServerRequest = url.hostname === self.location.hostname && url.port !== self.location.port; if (event.data.type === 'fetch-doc') {
const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname); fetchAddCache(event.data.path);
const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset; }
});
if (isHttp && !isDevServerRequest && !skipBecauseUncached) { }),
event.respondWith( );
(async () => { });
// always serve static files and bundler-generated assets from cache.
// if your application has other URLs with data that will never change, self.addEventListener('fetch', async (event) => {
// set this variable to true for them and they will only be fetched once. if (!event.request.url.startsWith(self.location.origin) || event.request.method !== 'GET') return;
const cachedAsset = isStaticAsset && await caches.match(event.request);
event.respondWith(
// for pages, you might want to serve a shell `service-worker-index.html` file, (async () => {
// which Sapper has generated for you. It's not right for every const cache = await caches.open(CACHE);
// app, but if it's right for yours then uncomment this section const cachedRes = await cache.match(event.request);
/*
if (!cachedAsset && url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { if (cachedRes) {
return caches.match('/service-worker-index.html'); return cachedRes;
} }
*/
const res = await fetch(event.request);
return cachedAsset || fetchAndCache(event.request); cache.put(event.request, res.clone());
})() return res;
); })(),
} );
}); });

View file

@ -7,12 +7,12 @@ export const notificationSupported = writable(false);
export const notificationAllowed = writable(true); export const notificationAllowed = writable(true);
const firebaseConfig = { const firebaseConfig = {
apiKey: __paimon.env.FIREBASE_API_KEY, apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: __paimon.env.FIREBASE_AUTH_DOMAIN, authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: __paimon.env.FIREBASE_PROJECT_ID, projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: __paimon.env.FIREBASE_STORAGE_BUCKET, storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: __paimon.env.FIREBASE_MESSAGING_SENDER_ID, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: __paimon.env.FIREBASE_APP_ID, appId: import.meta.env.VITE_FIREBASE_APP_ID,
}; };
let messaging; let messaging;

View file

@ -1,6 +1,6 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import debounce from 'lodash/debounce'; import debounce from 'lodash.debounce';
import localforage from 'localforage'; import localforage from 'localforage';
import { t as $t } from 'svelte-i18n'; import { t as $t } from 'svelte-i18n';

View file

@ -1,506 +0,0 @@
# Publisher Collective Ads.txt file 2021-11-22 09:49:38
# Display
google.com, pub-6177961780147591, DIRECT, f08c47fec0942fa0
google.com, pub-2145138345242651, DIRECT, f08c47fec0942fa0
sovrn.com, 240955, DIRECT, fafdf38b16bf6b2b
lijit.com, 240955, DIRECT, fafdf38b16bf6b2b
lijit.com, 240955-eb, DIRECT, fafdf38b16bf6b2b
gumgum.com, 11645, RESELLER, ffdef49475d318a9
openx.com, 537120960, RESELLER, 6a698e2ec38604c6
openx.com, 83499, RESELLER, 6a698e2ec38604c6
openx.com, 538959099, RESELLER, 6a698e2ec38604c6
openx.com, 539924617, RESELLER, 6a698e2ec38604c6
openx.com, 540447791, RESELLER, 6a698e2ec38604c6
pubmatic.com, 137711, RESELLER, 5d62403b186f2ace
pubmatic.com, 156212, RESELLER, 5d62403b186f2ace
pubmatic.com, 62483, RESELLER, 5d62403b186f2ace
pubmatic.com, 156700, RESELLER, 5d62403b186f2ace
openx.com, 539870614, DIRECT, 6a698e2ec38604c6
rubiconproject.com, 18580, DIRECT, 0bfd66d529a55807
rubiconproject.com, 20406, RESELLER, 0bfd66d529a55807
rubiconproject.com, 17960, RESELLER, 0bfd66d529a55807
gumgum.com, 11645, RESELLER, ffdef49475d318a9
appnexus.com, 1360, RESELLER, f5ab79cb980f11d1
indexexchange.com, 189344, DIRECT, 50b1c356f2c5c8fc
indexexchange.com, 189345, DIRECT, 50b1c356f2c5c8fc
pubmatic.com, 158540, RESELLER, 5d62403b186f2ace
EMXDGT.com, 1290, DIRECT, 1e1d41537f7cad7f
Appnexus.com, 1356, RESELLER, f5ab79cb980f11d1
pubmatic.com, 158684, DIRECT, 5d62403b186f2ace
appnexus.com, 11440, DIRECT, f5ab79cb980f11d1
triplelift.com, 9332, DIRECT, 6c33edb13117fd86
triplelift.com, 9332-EB, DIRECT, 6c33edb13117fd86
yahoo.com, 56371, DIRECT
yahoo.com, 56374, DIRECT
rubiconproject.com, 9061, RESELLER, 0bfd66d529a55807
rubiconproject.com, 10061, RESELLER, 0bfd66d529a55807
rubiconproject.com, 17250, RESELLER, 0bfd66d529a55807
improvedigital.com, 1787, DIRECT
improvedigital.com, 1787, RESELLER
google.com, pub-1386280613967939, RESELLER, f08c47fec0942fa0
rhythmone.com, 1879993427, DIRECT, a670c89d4a324e47
video.unrulymedia.com, 1879993427, DIRECT
rhythmone.com, 907951026, DIRECT, a670c89d4a324e47
video.unrulymedia.com, 907951026, DIRECT
adagio.io, 1107, DIRECT
rubiconproject.com, 19116, RESELLER, 0bfd66d529a55807
pubmatic.com, 159110, RESELLER, 5d62403b186f2ace
improvedigital.com, 1790, RESELLER
onetag.com, 6b859b96c564fbe, RESELLER
onetag.com, 74d276d460678b8, DIRECT
yahoo.com, 58905, RESELLER, e1a5b5b6e3255540
aol.com, 58905, RESELLER, e1a5b5b6e3255540
appnexus.com, 13099, RESELLER
smartadserver.com, 4111, RESELLER
conversantmedia.com, 100316, RESELLER, 03113cd04947736d
yieldmo.com, 2850174797239755356, DIRECT
yieldmo.com, 2850176953179120222, DIRECT
contextweb.com, 561118, RESELLER, 89ff185a4c4e857c
appnexus.com, 7911, RESELLER
rhythmone.com, 3463482822,RESELLER,a670c89d4a324e47
video.unrulymedia.com, 3463482822, RESELLER
rubiconproject.com, 17070, RESELLER, 0bfd66d529a55807
pubnative.net, 1007284, RESELLER, d641df8625486a7b
pubnative.net, 1007285, RESELLER, d641df8625486a7b
pubnative.net, 1007286, RESELLER, d641df8625486a7b
admanmedia.com, 746, RESELLER
pubmatic.com, 160648, RESELLER, 5d62403b186f2ace
indexexchange.com, 194520, RESELLER, 50b1c356f2c5c8fc
onetag.com, 664e107d9f2b748, RESELLER
conversantmedia.com, 100270, RESELLER, 03113cd04947736d
improvedigital.com, 2021, DIRECT
aps.amazon.com,26c60b4f-549a-4efd-8ae0-f00e07c46204,DIRECT
pubmatic.com,157150,RESELLER,5d62403b186f2ace
pubmatic.com, 160006, RESELLER, 5d62403b186f2ace
pubmatic.com, 160096, RESELLER, 5d62403b186f2ace
openx.com,540191398,RESELLER,6a698e2ec38604c6
rubiconproject.com,18020,RESELLER,0bfd66d529a55807
appnexus.com,1908,RESELLER,f5ab79cb980f11d1
appnexus.com,3663,RESELLER,f5ab79cb980f11d1
adtech.com,12068,RESELLER,e1a5b5b6e3255540
districtm.io,100962,RESELLER,3fd707be9c4527c3
rhythmone.com,1654642120,RESELLER,a670c89d4a324e47
yahoo.com,55029,RESELLER,e1a5b5b6e3255540
indexexchange.com,192410,RESELLER,50b1c356f2c5c8fc
ad-generation.jp, 12474, RESELLER, 7f4ea9029ac04e53
adtech.com, 4958, DIRECT, e1a5b5b6e3255540
triplelift.com,7194,DIRECT,6c33edb13117fd86
EMXDGT.com,1803, DIRECT, 1e1d41537f7cad7f
Appnexus.com, 1356, RESELLER, f5ab79cb980f11d1
smaato.com,1100044650,RESELLER,07bcf65f187117b4
gumgum.com,14141,RESELLER,ffdef49475d318a9
Contextweb.com,562377,DIRECT,89ff185a4c4e857c
indexexchange.com, 193657, DIRECT
admanmedia.com,726,RESELLER
yieldmo.com,2719019867620450718,RESELLER
sharethrough.com,7144eb80,RESELLER
loopme.com,11405,RESELLER
emxdgt.com,2009,RESELLER,1e1d41537f7cad7f
yahoo.com, 59100, DIRECT
conversantmedia.com, 42024, DIRECT, 03113cd04947736d
smartadserver.com,4125,RESELLER,060d053dcf45cbf3
contextweb.com, 562541, RESELLER, 89ff185a4c4e857c
themediagrid.com,jtqkmp,RESELLER,35d5010d7789b49d
sovrn.com,375328,RESELLER,fafdf38b16bf6b2b
lijit.com,375328,RESELLER,fafdf38b16bf6b2b
smaato.com, 1100046863, DIRECT, 07bcf65f187117b4
smaato.com, 1100004890, DIRECT, 07bcf65f187117b4
adcolony.com, 496220845654deec, RESELLER, 1ad675c9de6b5176
admanmedia.com, 552, RESELLER
appnexus.com, 1752, RESELLER, f5ab79cb980f11d1
appnexus.com, 4052, RESELLER
appnexus.com, 8790, RESELLER, f5ab79cb980f11d1
bidmachine.io, 36, RESELLER
bidmachine.io, 60, RESELLER
bidmachine.io, 74, RESELLER
bidmachine.io, 77, RESELLER
blis.com, 86, RESELLER, 61453ae19a4b73f4
contextweb.com, 558622, RESELLER, 89ff185a4c4e857c
engagebdr.com, 16, RESELLER
gammassp.com, 1516331892, RESELLER, 31ac53fec2772a83
indexexchange.com, 183920, RESELLER, 50b1c356f2c5c8fc
indexexchange.com, 184270, RESELLER, 50b1c356f2c5c8fc
inmobi.com, 55049d2e109d4ac1820ca1432dda4e13, RESELLER, 83e75a7ae333ca9d
mobilefuse.com, 2281, RESELLER
openx.com, 540421297, RESELLER, 6a698e2ec38604c6
pokkt.com, 5886, RESELLER, c45702d9311e25fd
pubmatic.com, 156177, RESELLER, 5d62403b186f2ace
pubmatic.com, 156389, RESELLER, 5d62403b186f2ace
pubmatic.com, 156424, RESELLER, 5d62403b186f2ace
pubmatic.com, 156425, RESELLER, 5d62403b186f2ace
pubnative.net, 1004796, RESELLER, d641df8625486a7b
pubnative.net, 1007194, RESELLER, d641df8625486a7b
rhythmone.com, 4201299756, RESELLER, a670c89d4a324e47
smartadserver.com, 3117, RESELLER
startapp.com, smt, RESELLER
xad.com, 241, RESELLER, 81cbf0a75a5e0e9a
indexexchange.com, 184665, RESELLER, 50b1c356f2c5c8fc
synacor.com, 82321, RESELLER, e108f11b2cdf7d5b
33across.com, 0014000001aXjnGAAS, RESELLER, bbea06d9c4d2853c
adtech.com, 12094, RESELLER
advangelists.com, 8d3bba7425e7c98c50f52ca1b52d3735, RESELLER, 60d26397ec060f98
appnexus.com, 10239, RESELLER, f5ab79cb980f11d1
emxdgt.com, 326, RESELLER, 1e1d41537f7cad7f
google.com, pub-9557089510405422, RESELLER, f08c47fec0942fa0
gumgum.com, 13318, RESELLER, ffdef49475d318a9
openx.com, 537120563, RESELLER, 6a698e2ec38604c6
pubmatic.com, 156423, RESELLER, 5d62403b186f2ace
rhythmone.com, 2439829435, RESELLER, a670c89d4a324e47
rubiconproject.com, 16414, RESELLER, 0bfd66d529a55807
advertising.com, 19623, RESELLER
indexexchange.com, 183965, RESELLER, 50b1c356f2c5c8fc
pubmatic.com, 156084, RESELLER, 5d62403b186f2ace
pubmatic.com, 156325, RESELLER, 5d62403b186f2ace
pubmatic.com, 156458, RESELLER, 5d62403b186f2ace
rubiconproject.com, 18222, RESELLER, 0bfd66d529a55807
appnexus.com, 9316, RESELLER, f5ab79cb980f11d1
appnexus.com, 4052, RESELLER, f5ab79cb980f11d1
conversantmedia.com, 20923, RESELLER
openx.com, 540031703, RESELLER, 6a698e2ec38604c6
appnexus.com, 1908, RESELLER, f5ab79cb980f11d1
districtm.io, 101769, RESELLER, 3fd707be9c4527c3
google.com, pub-9685734445476814, RESELLER, f08c47fec0942fa0
improvedigital.com, 1669, RESELLER
indexexchange.com, 191740, RESELLER, 50b1c356f2c5c8fc
themediagrid.com, P5JONV, RESELLER, 35d5010d7789b49d
onetag.com, 572a470226457b8, RESELLER
openx.com, 540401713, RESELLER, 6a698e2ec38604c6
pubmatic.com, 156344, RESELLER, 5d62403b186f2ace
advertising.com, 28605, RESELLER
appnexus.com, 6849, RESELLER, f5ab79cb980f11d1
indexexchange.com, 182257, RESELLER, 50b1c356f2c5c8fc
pubmatic.com, 159277, RESELLER, 5d62403b186f2ace
rhythmone.com, 905992537, RESELLER, a670c89d4a324e47
rubiconproject.com, 15268, RESELLER, 0bfd66d529a55807
spotx.tv, 285547, RESELLER, 7842df1d2fe2db34
spotxchange.com, 285547, RESELLER, 7842df1d2fe2db34
video.unrulymedia.com, 905992537, RESELLER, a670c89d4a324e47
rubiconproject.com, 13344, RESELLER, 0bfd66d529a55807
spotx.tv, 94794, RESELLER, 7842df1d2fe2db34
spotxchange.com, 94794, RESELLER, 7842df1d2fe2db34
advertising.com, 8603, RESELLER
aol.com, 53392, RESELLER
freewheel.tv, 799841, RESELLER
freewheel.tv, 799921, RESELLER
pubmatic.com, 156307, RESELLER, 5d62403b186f2ace
rhythmone.com, 1166984029, RESELLER, a670c89d4a324e47
spotx.tv, 71451, RESELLER, 7842df1d2fe2db34
spotxchange.com, 71451, RESELLER, 7842df1d2fe2db34
tremorhub.com, z87wm, RESELLER, 1a4e959a1b50034a
aralego.com, par-488A3E6BD8D997D0ED8B3BD34D8BA4B, RESELLER
ucfunnel.com, par-488A3E6BD8D997D0ED8B3BD34D8BA4B, RESELLER
yahoo.com, 55317, RESELLER
pubnx.com, 337-1, RESELLER, 8728b7e97e589da4
justpremium.com,2776,DIRECT
appnexus.com, 7118, RESELLER
improvedigital.com, 185, RESELLER
indexexchange.com, 189872, RESELLER
openx.com, 539653634, RESELLER, 6a698e2ec38604c6
rhythmone.com, 4116102010, RESELLER, a670c89d4a324e47
video.unrulymedia.com, 4116102010, RESELLER
adingo.jp, 24292, DIRECT
pubmatic.com, 156313, RESELLER, 5d62403b186f2ace
appnexus.com, 7044, RESELLER, f5ab79cb980f11d1
openx.com, 540679900, RESELLER, 6a698e2ec38604c6
webeyemob.com, 70080, RESELLER
admixer.net, 3bc509b2-6568-4bd8-a997-9859ba0c9118, RESELLER
pubmatic.com, 158060, RESELLER, 5d62403b186f2ace
adcolony.com, 801e49d1be83b5f9, RESELLER, 1ad675c9de6b5176
amxrtb.com, 105199357, DIRECT
indexexchange.com, 191503, RESELLER
appnexus.com, 11786, RESELLER
appnexus.com, 9393, RESELLER
appnexus.com, 3153, RESELLER, f5ab79cb980f11d1
appnexus.com, 11924, RESELLER, f5ab79cb980f11d1
smartadserver.com, 3056, RESELLER
Appnexus.com, 1356, RESELLER, f5ab79cb980f11d1
appnexus.com, 1908, RESELLER, f5ab79cb980f11d1
lijit.com, 260380, RESELLER, fafdf38b16bf6b2b
sovrn.com, 260380, RESELLER, fafdf38b16bf6b2b
openx.com, 538959099, RESELLER, 6a698e2ec38604c6
pubmatic.com, 137711, RESELLER, 5d62403b186f2ace
rubiconproject.com, 17960, RESELLER, 0bfd66d529a55807
pubmatic.com, 158355, RESELLER, 5d62403b186f2ace
advertising.com, 28305, RESELLER
avct.cloud, 5f7eef8974c1ab4156b8df8e, DIRECT
smartadserver.com, 3894, DIRECT
contextweb.com, 560288, RESELLER, 89ff185a4c4e857c
pubmatic.com, 156439, RESELLER, 5d62403b186f2ace
pubmatic.com, 154037, RESELLER, 5d62403b186f2ace
rubiconproject.com, 16114, RESELLER, 0bfd66d529a55807
openx.com, 537149888, RESELLER, 6a698e2ec38604c6
appnexus.com, 3703, RESELLER, f5ab79cb980f11d1
districtm.io, 101760, RESELLER, 3fd707be9c4527c3
loopme.com, 5679, RESELLER, 6c8d5f95897a5a3b
xad.com, 958, RESELLER, 81cbf0a75a5e0e9a
rhythmone.com, 2564526802, RESELLER, a670c89d4a324e47
smaato.com, 1100044045, RESELLER, 07bcf65f187117b4
pubnative.net, 1006576, RESELLER, d641df8625486a7b
adyoulike.com, b4bf4fdd9b0b915f746f6747ff432bde, RESELLER
axonix.com, 57264, RESELLER
admanmedia.com, 43, RESELLER
smartadserver.com, 4012, DIRECT
smartadserver.com, 4016, DIRECT
smartadserver.com, 4071, DIRECT
smartadserver.com, 4073, DIRECT
smartadserver.com, 4074, DIRECT
themediagrid.com, GD57SQ, DIRECT, 35d5010d7789b49d
themediagrid.com, TU8HG3, DIRECT, 35d5010d7789b49d
outbrain.com, 0043a849877c1a638231d82dd1b7e08b62, DIRECT
appnexus.com, 7597, RESELLER, f5ab79cb980f11d1
smartadserver.com, 1827, RESELLER
improvedigital.com, 335, RESELLER
appnexus.com, 3538, RESELLER
appnexus.com, 3539, RESELLER
appnexus.com, 3540, RESELLER
appnexus.com, 7290, RESELLER
sharethrough.com, 8af06d76, DIRECT, d53b998a7bd4ecd2
network-n.com, pa_1f00a408, DIRECT
network-n.com, pa_e8715185, DIRECT
network-n.com, pa_601d8ead, DIRECT
network-n.com, pa_a94525bd, DIRECT
network-n.com, nn_dc828890, DIRECT
network-n.com, pa_c73f329c, DIRECT
network-n.com, pa_2df17875, DIRECT
network-n.com, pa_90a1369b, DIRECT
network-n.com, nn_a92f3c5a, DIRECT
network-n.com, pa_bf035bcc, DIRECT
network-n.com, pa_6eed24a5, DIRECT
network-n.com, pa_c8a21379, DIRECT
network-n.com, pa_989c8bff, DIRECT
network-n.com, nn_67b3bb73, DIRECT
network-n.com, nn_b811ecf3, DIRECT
network-n.com, pa_eef26576, DIRECT
network-n.com, pa_dcddaaf3, DIRECT
network-n.com, pa_778becb4, DIRECT
network-n.com, pa_21f59c37, DIRECT
network-n.com, pa_c01f38e8, DIRECT
network-n.com, pa_2d21e00f, DIRECT
network-n.com, pa_d176ae16, DIRECT
network-n.com, pa_ebb120be, DIRECT
network-n.com, pa_fc69d8ef, DIRECT
network-n.com, pa_2c42efa0, DIRECT
network-n.com, pa_553f610d, DIRECT
network-n.com, pa_96068c5c, DIRECT
network-n.com, pa_ab87406f, DIRECT
network-n.com, nn_a5e5e413, DIRECT
network-n.com, pa_e890a22a, DIRECT
network-n.com, nn_83d97ca3, DIRECT
network-n.com, pa_9435d9ba, DIRECT
network-n.com, pa_ac496ffe, DIRECT
network-n.com, pa_bc60f533, DIRECT
network-n.com, pa_24924b4f, DIRECT
network-n.com, pa_cefd0185, DIRECT
network-n.com, nn_f75084c9, DIRECT
network-n.com, pa_9e3ef9d5, DIRECT
network-n.com, nn_0c0a2f6a, DIRECT
network-n.com, pa_6f8e433a, DIRECT
network-n.com, nn_52929841, DIRECT
network-n.com, nn_16fa43c0, DIRECT
network-n.com, pa_709fd813, DIRECT
network-n.com, pa_ab112065, DIRECT
network-n.com, pa_fc8784a7, DIRECT
network-n.com, pa_6450db25, DIRECT
network-n.com, nn_0b8aac73, DIRECT
network-n.com, nn_bb9e3d06, DIRECT
network-n.com, pa_90d616a0, DIRECT
network-n.com, nn_e3dedfc5, DIRECT
network-n.com, pa_67d19afe, DIRECT
network-n.com, pa_c850e4e2, DIRECT
network-n.com, pa_569c4b29, DIRECT
network-n.com, pa_58acb23d, DIRECT
network-n.com, pa_c52e2e53, DIRECT
network-n.com, pa_d1a744aa, DIRECT
network-n.com, pa_c0fd608c, DIRECT
network-n.com, pa_e97f4d64, DIRECT
network-n.com, pa_abb78c2d, DIRECT
network-n.com, pa_d83981e9, DIRECT
network-n.com, pa_9a65dfce, DIRECT
network-n.com, pa_98a54d0d, DIRECT
network-n.com, nn_bac8480a, DIRECT
network-n.com, pa_bee9082e, DIRECT
network-n.com, pa_0312b635, DIRECT
network-n.com, pa_777b07ca, DIRECT
network-n.com, pa_07aaf1a3, DIRECT
network-n.com, pa_f2f8099b, DIRECT
network-n.com, nn_1cdeb505, DIRECT
network-n.com, pa_d2cb5c22, DIRECT
network-n.com, pa_f8d9d92f, DIRECT
network-n.com, pa_7c030473, DIRECT
network-n.com, pa_debf04a3, DIRECT
network-n.com, pa_58bd4c90, DIRECT
network-n.com, nn_93ace404, DIRECT
network-n.com, pa_91564061, DIRECT
network-n.com, pa_7fa5cef8, DIRECT
network-n.com, nn_991ceb73, DIRECT
network-n.com, pa_2a5a6810, DIRECT
network-n.com, pa_deb3cc73, DIRECT
network-n.com, nn_ccf14111, DIRECT
network-n.com, nn_dd147396, DIRECT
network-n.com, nn_c70bf603, DIRECT
network-n.com, pa_5012520c, DIRECT
network-n.com, pa_7113c86e, DIRECT
network-n.com, pa_ef8dab28, DIRECT
network-n.com, pa_d1ad6473, DIRECT
network-n.com, pa_755836fa, DIRECT
network-n.com, pa_fec65292, DIRECT
network-n.com, pa_f90a5700, DIRECT
network-n.com, pa_8f187460, DIRECT
network-n.com, pa_bb1db0e3, DIRECT
network-n.com, pa_5f907e2a, DIRECT
network-n.com, pa_73adf5f4, DIRECT
network-n.com, pa_17cde183, DIRECT
network-n.com, nn_d590fc4b, DIRECT
network-n.com, pa_e144c3fb, DIRECT
network-n.com, pa_695cef04, DIRECT
network-n.com, nn_20819473, DIRECT
network-n.com, pa_ae37eff2, DIRECT
network-n.com, pa_5b7caaf0, DIRECT
network-n.com, pa_cfc7cf7f, DIRECT
network-n.com, pa_8b270543, DIRECT
network-n.com, pa_17e2f1c8, DIRECT
network-n.com, pa_ed7b677a, DIRECT
network-n.com, nn_da08cd6a, DIRECT
network-n.com, nn_ea8d1b4e, DIRECT
network-n.com, pa_740be19b, DIRECT
network-n.com, pa_14616587, DIRECT
network-n.com, pa_207cb2a8, DIRECT
network-n.com, pa_b843870e, DIRECT
network-n.com, pa_50c4b9dd, DIRECT
network-n.com, nn_40020810, DIRECT
network-n.com, nn_0ab02a98, DIRECT
network-n.com, pa_2b66dc72, DIRECT
network-n.com, nn_218286a3, DIRECT
network-n.com, pa_cd3ca5de, DIRECT
network-n.com, pa_7da8e293, DIRECT
network-n.com, pa_0947c454, DIRECT
network-n.com, pa_d684b280, DIRECT
network-n.com, pa_c0c730e2, DIRECT
network-n.com, pa_ddf947b1, DIRECT
network-n.com, nn_ad9aa896, DIRECT
network-n.com, nn_668ba8bf, DIRECT
network-n.com, nn_5a267a53, DIRECT
network-n.com, pa_064fb665, DIRECT
network-n.com, nn_3f8d45b3, DIRECT
network-n.com, nn_eae4fb13, DIRECT
network-n.com, pa_57f34d61, DIRECT
network-n.com, nn_7442e9fc, DIRECT
network-n.com, pa_db5ae67a, DIRECT
network-n.com, pa_30e0139a, DIRECT
network-n.com, pa_43ea0a09, DIRECT
network-n.com, pa_2243b60b, DIRECT
network-n.com, nn_7004e887, DIRECT
network-n.com, pa_39bf5b96, DIRECT
network-n.com, pa_c201a0b6, DIRECT
network-n.com, nn_eef26576, DIRECT
network-n.com, nn_c73f329c, DIRECT
network-n.com, pa_6cdbf87f, DIRECT
network-n.com, nn_a94525bd, DIRECT
network-n.com, pa_0b71c320, DIRECT
network-n.com, nn_c69fccc0, DIRECT
network-n.com, pa_2e0fe469, DIRECT
network-n.com, nn_601d8ead, DIRECT
network-n.com, pa_5e3e6279, DIRECT
network-n.com, pa_82d73b26, DIRECT
network-n.com, pa_7bef0373, DIRECT
network-n.com, nn_39bf5b96, DIRECT
network-n.com, pa_0e5827bf, DIRECT
network-n.com, pa_5471d680, DIRECT
network-n.com, pa_ad4e5de9, DIRECT
network-n.com, pa_b568ea31, DIRECT
network-n.com, pa_177acef3, DIRECT
network-n.com, pa_fda9576c, DIRECT
network-n.com, pa_02b64c13, DIRECT
network-n.com, pa_8c91333e, DIRECT
network-n.com, pa_51ef4921, DIRECT
network-n.com, pa_622654f6, DIRECT
network-n.com, pa_78f6e17e, DIRECT
network-n.com, pa_3da102b3, DIRECT
network-n.com, pa_7a0dc518, DIRECT
network-n.com, pa_1c1156a0, DIRECT
network-n.com, pa_614dd772, DIRECT
network-n.com, pa_0d3e7ddf, DIRECT
network-n.com, pa_b3a8ca4e, DIRECT
network-n.com, nn_c9936d92, DIRECT
network-n.com, pa_58d7eb0a, DIRECT
network-n.com, pa_dc781657, DIRECT
network-n.com, pa_50dd4c60, DIRECT
network-n.com, pa_c69fccc0, DIRECT
network-n.com, pa_d9bd0d92, DIRECT
network-n.com, pa_51ee7e6e, DIRECT
network-n.com, pa_c9936d92, DIRECT
network-n.com, pa_f2af0d42, DIRECT
network-n.com, pa_fbbe6928, DIRECT
network-n.com, pa_1591dae1, DIRECT
network-n.com, pa_738413d8, DIRECT
network-n.com, pa_a276e152, DIRECT
network-n.com, pa_a730b684, DIRECT
network-n.com, pa_a863cb34, DIRECT
network-n.com, pa_3f85e405, DIRECT
network-n.com, pa_c201a0b6, DIRECT
network-n.com, pa_cddb4912, DIRECT
network-n.com, pa_eaba5433, DIRECT
network-n.com, pa_bdc939c3, DIRECT
network-n.com, pa_2b011291, DIRECT
network-n.com, pa_c43098ca, DIRECT
network-n.com, pa_2b011291, DIRECT
network-n.com, pa_2b011291, DIRECT
network-n.com, pa_a80811f1, DIRECT
network-n.com, pa_6bda6bb5, DIRECT
# Video
EMXDGT.com,1309, DIRECT, 1e1d41537f7cad7f
appnexus.com, 1356, RESELLER, f5ab79cb980f11d1
pubmatic.com, 158682, DIRECT, 5d62403b186f2ace
openx.com, 540886248, DIRECT, 6a698e2ec38604c6
google.com, pub-5760410923284845, DIRECT, f08c47fec0942fa0
themediagrid.com,EB14XD,DIRECT,35d5010d7789b49d
advertising.com, 28814, RESELLER
advertising.com, 28816, RESELLER
appnexus.com, 12564, RESELLER, f5ab79cb980f11d1
indexexchange.com, 193162, RESELLER
google.com, pub-5760410923284845, RESELLER, f08c47fec0942fa0
pubmatic.com, 159247, RESELLER, 5d62403b186f2ace
pubmatic.com, 160127, RESELLER, 5d62403b186f2ace
rhythmone.com, 4245644886, RESELLER, a670c89d4a324e47
video.unrulymedia.com, 4245644886, RESELLER
districtm.io, 102080, DIRECT, 3fd707be9c4527c3
appnexus.com, 1908, RESELLER, f5ab79cb980f11d1
advertising.com, 28784, DIRECT
advertising.com, 28783, DIRECT
indexexchange.com, 193792, DIRECT
sonobi.com, a015179bd7, DIRECT, d1a215d9eb5aee9e
rhythmone.com, 1059622079, RESELLER, a670c89d4a324e47
contextweb.com, 560606, RESELLER, 89ff185a4c4e857c
sonobi.com, 649aabf138, DIRECT, d1a215d9eb5aee9e
spotxchange.com, 268145, DIRECT, 7842df1d2fe2db34
spotx.tv, 268145, DIRECT, 7842df1d2fe2db34
spotxchange.com, 268145, RESELLER, 7842df1d2fe2db34
spotx.tv, 268145, RESELLER, 7842df1d2fe2db34
freewheel.tv, 1237631, DIRECT
freewheel.tv, 1237711, RESELLER
aps.amazon.com,968a0f5c-e5ed-4ba9-bf43-8be1f5b68988,DIRECT
pubmatic.com, 160887, RESELLER, 5d62403b186f2ace
pubmatic.com, 160887, DIRECT, 5d62403b186f2ace
primis.tech, 28588, DIRECT, b6b21d256ef43532
spotxchange.com, 84294, RESELLER, 7842df1d2fe2db34
spotx.tv, 84294, RESELLER, 7842df1d2fe2db34
advertising.com, 7372, RESELLER
yahoo.com, 59260, RESELLER
pubmatic.com, 156595, RESELLER, 5d62403b186f2ace
google.com, pub-1320774679920841, RESELLER, f08c47fec0942fa0
openx.com, 540258065, RESELLER, 6a698e2ec38604c6
rubiconproject.com, 20130, RESELLER, 0bfd66d529a55807
freewheel.tv, 19129, RESELLER, 74e8e47458f74754
freewheel.tv, 19133, RESELLER, 74e8e47458f74754
smartadserver.com, 3436, RESELLER, 060d053dcf45cbf3
indexexchange.com, 191923, RESELLER, 50b1c356f2c5c8fc
contextweb.com, 562350, RESELLER, 89ff185a4c4e857c
tremorhub.com, mb9eo-oqsbf, RESELLER, 1a4e959a1b50034a
telaria.com, mb9eo-oqsbf, RESELLER, 1a4e959a1b50034a
adform.com, 2078, RESELLER
xandr.com, 13171, RESELLER
supply.colossusssp.com, 290, RESELLER, 6c5b49d96ec1b458
emxdgt.com, 1349, RESELLER, 1e1d41537f7cad7f
Media.net, 8CU695QH7, RESELLER

View file

@ -1,11 +1,34 @@
const sveltePreprocess = require('svelte-preprocess'); // const sveltePreprocess = require('svelte-preprocess');
const postcss = require('./postcss.config'); // const postcss = require('./postcss.config');
const preprocess = sveltePreprocess({ // const preprocess = sveltePreprocess({
defaults: { // defaults: {
style: 'postcss', // style: 'postcss',
// },
// postcss,
// });
// module.exports = { preprocess };
import preprocess from 'svelte-preprocess';
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
prerender: {
default: true,
},
serviceWorker: {
register: false,
},
}, },
postcss, preprocess: [
}); preprocess({
postcss: true,
}),
],
};
module.exports = { preprocess }; export default config;

178
tailwind.config.cjs Normal file
View file

@ -0,0 +1,178 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
fontFamily: {
display: ['Catamaran', 'sans-serif'],
body: ['Poppins', 'sans-serif'],
},
colors: {
transparent: 'transparent',
current: 'currentColor',
black: '#000',
white: '#fff',
gray: {
100: '#f7fafc',
200: '#edf2f7',
300: '#e2e8f0',
400: '#cbd5e0',
500: '#a0aec0',
600: '#718096',
700: '#4a5568',
800: '#2d3748',
900: '#1a202c',
},
red: {
100: '#fff5f5',
200: '#fed7d7',
300: '#feb2b2',
400: '#fc8181',
500: '#f56565',
600: '#e53e3e',
700: '#c53030',
800: '#9b2c2c',
900: '#742a2a',
},
orange: {
100: '#fffaf0',
200: '#feebc8',
300: '#fbd38d',
400: '#f6ad55',
500: '#ed8936',
600: '#dd6b20',
700: '#c05621',
800: '#9c4221',
900: '#7b341e',
},
yellow: {
100: '#fffff0',
200: '#fefcbf',
300: '#faf089',
400: '#f6e05e',
500: '#ecc94b',
600: '#d69e2e',
700: '#b7791f',
800: '#975a16',
900: '#744210',
},
green: {
100: '#f0fff4',
200: '#c6f6d5',
300: '#9ae6b4',
400: '#68d391',
500: '#48bb78',
600: '#38a169',
700: '#2f855a',
800: '#276749',
900: '#22543d',
},
teal: {
100: '#e6fffa',
200: '#b2f5ea',
300: '#81e6d9',
400: '#4fd1c5',
500: '#38b2ac',
600: '#319795',
700: '#2c7a7b',
800: '#285e61',
900: '#234e52',
},
blue: {
100: '#ebf8ff',
200: '#bee3f8',
300: '#90cdf4',
400: '#63b3ed',
500: '#4299e1',
600: '#3182ce',
700: '#2b6cb0',
800: '#2c5282',
900: '#2a4365',
},
indigo: {
100: '#ebf4ff',
200: '#c3dafe',
300: '#a3bffa',
400: '#7f9cf5',
500: '#667eea',
600: '#5a67d8',
700: '#4c51bf',
800: '#434190',
900: '#3c366b',
},
purple: {
100: '#faf5ff',
200: '#e9d8fd',
300: '#d6bcfa',
400: '#b794f4',
500: '#9f7aea',
600: '#805ad5',
700: '#6b46c1',
800: '#553c9a',
900: '#44337a',
},
pink: {
100: '#fff5f7',
200: '#fed7e2',
300: '#fbb6ce',
400: '#f687b3',
500: '#ed64a6',
600: '#d53f8c',
700: '#b83280',
800: '#97266d',
900: '#702459',
},
background: '#202442',
'background-secondary': '#25294A',
item: '#2D325A',
primary: '#4E7CFF',
button: '#394076',
rare: {
from: '#D28FD6',
to: '#665680',
},
legendary: {
from: '#FFB13F',
to: '#846332',
},
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
'5xl': '3rem',
'6xl': '4rem',
},
extend: {
borderRadius: {
xl: '12px',
'2xl': '16px',
},
boxShadow: {
rare: '0 0 0 3px rgba(173, 118, 176, 0.5)',
legendary: '0 0 0 3px rgba(185, 129, 46, 0.5)',
outline: '0 0 0 2px #4E7CFF',
select: '0 20px 16px rgba(0, 0, 0, 0.5)',
},
height: {
14: '3.5rem',
},
width: {
14: '3.5rem',
},
},
},
};

Some files were not shown because too many files have changed in this diff Show more