Migrate to SvelteKit

Close #158

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

    Add update popup

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

    Add service worker

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

    Update firebase config

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

    Separate build getter

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

    Update readme

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

    Fix createEnv

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

    Migrate to svelte-kit
pull/1/head
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/
yarn-error.log
/__sapper__/
.svelte-kit
/build
.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.
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
```
# install dependencies
yarn
pnpm i
# you need the api to run wish importer and wish tally
git clone https://github.com/MadeBaruna/paimon-moe-api
@ -20,10 +20,10 @@ docker-compose up -d
# run in dev mode
cp .env.example .env
vi .env
yarn dev
pnpm dev
# export as production static site
yarn export
pnpm build
```
# License

View File

@ -1,50 +1,34 @@
{
"type": "module",
"name": "paimon-moe",
"description": "A collection of tools to help playing Genshin Impact",
"version": "0.0.1",
"scripts": {
"dev": "NODE_OPTIONS=--max_old_space_size=4096 sapper dev",
"build": "sapper build --legacy",
"export": "NODE_OPTIONS=--max_old_space_size=4096 sapper export --legacy && cp __sapper__/build/firebase-messaging-sw.js __sapper__/export",
"start": "node __sapper__/build"
},
"dependencies": {
"compression": "^1.7.1",
"exceljs": "^4.2.1",
"localforage": "^1.9.0",
"polka": "next",
"prettier": "^2.2.1",
"sirv": "^1.0.0"
"dev": "vite dev --port 3000",
"build": "NODE_OPTIONS=--max_old_space_size=4096 vite build",
"preview": "vite preview",
"start": "node build"
},
"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",
"@rollup/plugin-babel": "^5.0.0",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-dynamic-import-vars": "^1.1.1",
"@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",
"@sveltejs/adapter-static": "1.0.0-next.37",
"@sveltejs/kit": "^1.0.0-next.379",
"autoprefixer": "^10.4.7",
"chart.js": "^2.9.4",
"dayjs": "^1.9.4",
"dotenv": "^8.2.0",
"postcss": "^8.2.10",
"postcss-load-config": "^3.0.0",
"postcss-nested": "^5.0.1",
"rollup": "^2.3.4",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"sapper": "^0.28.0",
"exceljs": "^4.3.0",
"localforage": "^1.10.0",
"lodash.debounce": "^4.0.8",
"postcss": "^8.4.14",
"postcss-nested": "^5.0.6",
"prettier": "^2.7.1",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.17.3",
"svelte-i18n": "^3.3.6",
"svelte-preprocess": "^4.5.1",
"svelte-i18n": "^3.4.0",
"svelte-preprocess": "^4.10.7",
"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 = '';
envString += `GOOGLE_DRIVE_CLIENT_ID=${process.env.GOOGLE_DRIVE_CLIENT_ID}\n`;
envString += `GOOGLE_DRIVE_API_KEY=${process.env.GOOGLE_DRIVE_API_KEY}\n`;
envString += `API_HOST=${process.env.API_HOST}\n`;
envString += `FIREBASE_API_KEY=${process.env.FIREBASE_API_KEY}\n`
envString += `FIREBASE_AUTH_DOMAIN=${process.env.FIREBASE_AUTH_DOMAIN}\n`
envString += `FIREBASE_PROJECT_ID=${process.env.FIREBASE_PROJECT_ID}\n`
envString += `FIREBASE_STORAGE_BUCKET=${process.env.FIREBASE_STORAGE_BUCKET}\n`
envString += `FIREBASE_MESSAGING_SENDER_ID=${process.env.FIREBASE_MESSAGING_SENDER_ID}\n`
envString += `FIREBASE_APP_ID=${process.env.FIREBASE_APP_ID}\n`
envString += `VITE_GOOGLE_DRIVE_CLIENT_ID=${process.env.GOOGLE_DRIVE_CLIENT_ID}\n`;
envString += `VITE_GOOGLE_DRIVE_API_KEY=${process.env.GOOGLE_DRIVE_API_KEY}\n`;
envString += `VITE_API_HOST=${process.env.API_HOST}\n`;
envString += `VITE_FIREBASE_API_KEY=${process.env.FIREBASE_API_KEY}\n`;
envString += `VITE_FIREBASE_AUTH_DOMAIN=${process.env.FIREBASE_AUTH_DOMAIN}\n`;
envString += `VITE_FIREBASE_PROJECT_ID=${process.env.FIREBASE_PROJECT_ID}\n`;
envString += `VITE_FIREBASE_STORAGE_BUCKET=${process.env.FIREBASE_STORAGE_BUCKET}\n`;
envString += `VITE_FIREBASE_MESSAGING_SENDER_ID=${process.env.FIREBASE_MESSAGING_SENDER_ID}\n`;
envString += `VITE_FIREBASE_APP_ID=${process.env.FIREBASE_APP_ID}\n`;
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:image" content="https://paimon.moe/paimon-og.png" />
%sapper.base%
<link
href="https://fonts.googleapis.com/css2?family=Catamaran:wght@600;700;900&family=Poppins:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link rel="manifest" href="manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/png" href="/favicon.png" />
<!-- <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>
@ -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-messaging.js"></script>
<style>
<style lang="postcss">
html {
height: 100%;
background: #25294a;
}
</style>
<!-- Sapper creates a <script> tag containing `src/client.js`
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%
%sveltekit.head%
<script>
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>
</head>
<body class="font-body h-full bg-background-secondary">
<!-- The application will be rendered inside this element,
because `src/client.js` references it -->
<div id="sapper" class="flex flex-col h-full">%sapper.html%</div>
<div id="sapper" class="flex flex-col h-full">%sveltekit.body%</div>
</body>
</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 variant;
export let id;
export let style;
export let style = '';
let _class = '';
export { _class as class };

View File

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

View File

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

View File

@ -12,8 +12,8 @@
const { open: openModal, close: closeModal } = getContext('simple-modal');
const CLIENT_ID = __paimon.env.GOOGLE_DRIVE_CLIENT_ID;
const API_KEY = __paimon.env.GOOGLE_DRIVE_API_KEY;
const CLIENT_ID = import.meta.env.VITE_GOOGLE_DRIVE_CLIENT_ID;
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 SCOPES = 'https://www.googleapis.com/auth/drive.appdata';

View File

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

View File

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

View File

@ -81,27 +81,6 @@
}
</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
Usage:
@ -124,6 +103,28 @@ $s: var(--grid-gap); // .5em;
<div
bind:this={masonryElement}
class={`__grid--masonry ${stretchFirst ? '__stretch-first' : ''}`}
style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}>
style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}
>
<slot />
</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}
</div>
<style>
<style lang="postcss">
.hovered {
@apply text-white !important;
@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>
import { fly } from 'svelte/transition';
import { getContext } from 'svelte';
import { mdiCloseCircle } from '@mdi/js';
import { locale, t } from 'svelte-i18n';
@ -30,7 +29,8 @@
{ id: 'pt', label: 'Português' },
{ 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);
function close() {
@ -60,7 +60,7 @@
{/if}
<SidebarItem
on:clicked={close}
active={segment === undefined}
active={segment === ''}
image="/images/home.png"
label={$t('sidebar.home')}
href="/"
@ -160,7 +160,7 @@
</div>
</div>
<style>
<style lang="postcss">
.paimon-bg::after {
content: '';
top: -50px;

View File

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

View File

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

View File

@ -27,7 +27,7 @@
/>
</div>
<style>
<style lang="postcss">
.container {
height: fit-content;
}
@ -36,4 +36,4 @@
min-height: 100px;
@apply p-4;
}
</style>
</style>

View File

@ -18,7 +18,7 @@
}
</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 />
</i>
@ -26,7 +26,7 @@
<div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div>
{/if}
<style>
<style lang="postcss">
.tooltip {
@apply p-2 absolute rounded-xl bg-gray-400 border border-gray-800;
@apply text-sm text-background z-10;

View File

@ -16,7 +16,7 @@
}
</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 />
</span>
@ -24,7 +24,7 @@
<div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div>
{/if}
<style>
<style lang="postcss">
.tooltip {
@apply p-2 fixed rounded-xl bg-gray-400 border border-gray-800;
@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>
import { onMount, tick } from 'svelte';
import { onMount, tick } from 'svelte';
// props
export let items;
export let height = '100%';
export let itemHeight = undefined;
// props
export let items;
export let height = '100%';
export let itemHeight = undefined;
let foo;
let foo;
// read-only, but visible to consumers via bind:start
export let start = 0;
export let end = 0;
// read-only, but visible to consumers via bind:start
export let start = 0;
export let end = 0;
// local state
let height_map = [];
let rows;
let viewport;
let contents;
let viewport_height = 0;
let visible;
let mounted;
// local state
let height_map = [];
let rows;
let viewport;
let contents;
let viewport_height = 0;
let visible;
let mounted;
let top = 0;
let bottom = 0;
let average_height;
let top = 0;
let bottom = 0;
let average_height;
$: visible = items.slice(start, end).map((data, i) => {
return { index: i + start, data };
});
$: visible = items.slice(start, end).map((data, i) => {
return { index: i + start, data };
});
// whenever `items` changes, invalidate the current heightmap
$: if (mounted) refresh(items, viewport_height, itemHeight);
// whenever `items` changes, invalidate the current heightmap
$: if (mounted) refresh(items, viewport_height, itemHeight);
async function refresh(items, viewport_height, itemHeight) {
const { scrollTop } = viewport;
async function refresh(items, viewport_height, itemHeight) {
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 i = start;
let content_height = top - scrollTop;
let i = start;
while (content_height < viewport_height && i < items.length) {
let row = rows[i - start];
while (content_height < viewport_height && i < items.length) {
let row = rows[i - start];
if (!row) {
end = i + 1;
await tick(); // render the newly visible row
row = rows[i - start];
}
if (!row) {
end = i + 1;
await tick(); // render the newly visible row
row = rows[i - start];
}
const row_height = height_map[i] = itemHeight || row.offsetHeight;
content_height += row_height;
i += 1;
}
const row_height = (height_map[i] = itemHeight || row.offsetHeight);
content_height += row_height;
i += 1;
}
end = i;
end = i;
const remaining = items.length - end;
average_height = (top + content_height) / end;
const remaining = items.length - end;
average_height = (top + content_height) / end;
bottom = remaining * average_height;
bottom = remaining * average_height;
height_map.length = items.length;
viewport.scrollTo(0, 0);
}
}
async function handle_scroll() {
const { scrollTop } = viewport;
async function handle_scroll() {
const { scrollTop } = viewport;
const old_start = start;
const old_start = start;
for (let v = 0; v < rows.length; v += 1) {
height_map[start + v] = itemHeight || rows[v].offsetHeight;
}
for (let v = 0; v < rows.length; v += 1) {
height_map[start + v] = itemHeight || rows[v].offsetHeight;
}
let i = 0;
let y = 0;
let i = 0;
let y = 0;
while (i < items.length) {
const row_height = height_map[i] || average_height;
if (y + row_height > scrollTop) {
start = i;
top = y;
while (i < items.length) {
const row_height = height_map[i] || average_height;
if (y + row_height > scrollTop) {
start = i;
top = y;
break;
}
break;
}
y += row_height;
i += 1;
}
y += row_height;
i += 1;
}
while (i < items.length) {
y += height_map[i] || average_height;
i += 1;
while (i < items.length) {
y += height_map[i] || average_height;
i += 1;
if (y > scrollTop + viewport_height) break;
}
if (y > scrollTop + viewport_height) break;
}
end = i;
end = i;
const remaining = items.length - end;
average_height = y / end;
const remaining = items.length - end;
average_height = y / end;
while (i < items.length) height_map[i++] = average_height;
bottom = remaining * average_height;
while (i < items.length) height_map[i++] = average_height;
bottom = remaining * average_height;
// prevent jumping if we scrolled up into unknown territory
if (start < old_start) {
await tick();
// prevent jumping if we scrolled up into unknown territory
if (start < old_start) {
await tick();
let expected_height = 0;
let actual_height = 0;
let expected_height = 0;
let actual_height = 0;
for (let i = start; i < old_start; i +=1) {
if (rows[i - start]) {
expected_height += height_map[i];
actual_height += itemHeight || rows[i - start].offsetHeight;
}
}
for (let i = start; i < old_start; i += 1) {
if (rows[i - start]) {
expected_height += height_map[i];
actual_height += itemHeight || rows[i - start].offsetHeight;
}
}
const d = actual_height - expected_height;
viewport.scrollTo(0, scrollTop + d);
}
const d = actual_height - expected_height;
viewport.scrollTo(0, scrollTop + d);
}
// TODO if we overestimated the space these
// rows would occupy we may need to add some
// more. maybe we can just call handle_scroll again?
}
// TODO if we overestimated the space these
// rows would occupy we may need to add some
// more. maybe we can just call handle_scroll again?
}
// trigger initial refresh
onMount(() => {
rows = contents.getElementsByTagName('svelte-virtual-list-row');
mounted = true;
});
// trigger initial refresh
onMount(() => {
rows = contents.getElementsByTagName('svelte-virtual-list-row');
mounted = true;
});
</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
bind:this={viewport}
bind:offsetHeight={viewport_height}
on:scroll={handle_scroll}
style="height: {height};"
bind:this={viewport}
bind:offsetHeight={viewport_height}
on:scroll={handle_scroll}
style="height: {height};"
>
<svelte-virtual-list-contents
bind:this={contents}
style="padding-top: {top}px; padding-bottom: {bottom}px;"
>
{#each visible as row (row.index)}
<svelte-virtual-list-row>
<slot item={row.data} index={row.index}>Missing template</slot>
</svelte-virtual-list-row>
{/each}
</svelte-virtual-list-contents>
</svelte-virtual-list-viewport>
<svelte-virtual-list-contents bind:this={contents} style="padding-top: {top}px; padding-bottom: {bottom}px;">
{#each visible as row (row.index)}
<svelte-virtual-list-row>
<slot item={row.data} index={row.index}>Missing template</slot>
</svelte-virtual-list-row>
{/each}
</svelte-virtual-list-contents>
</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}
</div>
<style>
<style lang="postcss">
.hovered {
@apply text-white !important;
@apply bg-primary;

View File

@ -190,7 +190,7 @@
try {
const res = await fetchRetry(
`${__paimon.env.API_HOST}/corsproxy`,
`${import.meta.env.VITE_API_HOST}/corsproxy`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@ -527,7 +527,7 @@
</td>
<td class="border-b border-gray-700 py-1">
{#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} />
{numberFormat.format(wishes[code].length)}
</span>
@ -585,10 +585,10 @@
{#if wishes[code] !== undefined}
<tr>
<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 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} />
{numberFormat.format(wishes[code].length)}
</span>
@ -788,7 +788,7 @@
<div class="pb-16 md:pb-0" />
{/if}
<style>
<style lang="postcss">
.pill {
@apply rounded-2xl;
@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');
const firebaseConfig = {
apiKey: __paimon.env.FIREBASE_API_KEY,
authDomain: __paimon.env.FIREBASE_AUTH_DOMAIN,
projectId: __paimon.env.FIREBASE_PROJECT_ID,
storageBucket: __paimon.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: __paimon.env.FIREBASE_MESSAGING_SENDER_ID,
appId: __paimon.env.FIREBASE_APP_ID,
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
};
firebase.initializeApp(firebaseConfig);

View File

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

View File

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

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.",
"visitor": "{count} visitors in the last 7 days",
"banner": {
"featured": [
"Eula"
],
"summoned": "Summoned",
"percentage": "from all {rarity}",
"avg": "Pity average",
@ -143,10 +140,7 @@
"manualButton": "Enable Manual Input",
"errorBanner": "Banner time mismatch! Please adjust your server on the settings page. Still not working? Please leave a message on Discord 😅",
"globalWishTally": "Global Wish Stats",
"pityTooltip": [
"Shows your current {rarity} pity",
"{count} pulls to guaranteed {rarity}"
],
"pityTooltip": ["Shows your current {rarity} pity", "{count} pulls to guaranteed {rarity}"],
"import": {
"title": "Import Wish History",
"faqsButton": "FAQ - READ FIRST",
@ -182,11 +176,7 @@
"server": "Select your server:",
"wishTallyCheck": "Submit pity for global wish stats",
"wishTally": "We are doing a global wish stats! You can submit your wish stats to participate. All pity data will be aggregated to know what is the average pity of paimon.moe users.",
"wishTallyCollected": [
"What will be collected:",
"and",
"pity from your wish history"
],
"wishTallyCollected": ["What will be collected:", "and", "pity from your wish history"],
"forceUpdateCheck": "Force update wish history (enable only if your wish history is not updating)",
"header": [
"Import and backup your Genshin Impact wish history to keep it for more than 6 months. It also automatically tracks your pity and statistics about your wishes!",
@ -364,11 +354,7 @@
"exportFinish": "Export success, please wait until your browser downloads the file!",
"wishTallyTitle": "Submit Wish Stats",
"wishTally": "We are doing a global wish stats! You can submit your wish stats to participate. All pity data will be aggregated to know what is the average pity of paimon.moe users.",
"wishTallyCollected": [
"What will be collected:",
"and",
"pity from your wish history"
],
"wishTallyCollected": ["What will be collected:", "and", "pity from your wish history"],
"wishTallySubmit": "Submit Wish Stats",
"wishTallyThankyou": "Thank you for participating!",
"manualTitle": "Manual Input Settings",
@ -380,22 +366,13 @@
"subtitle": "After a 1x Wish:",
"pressWhenYouGet": "Press {button} when you get {rarity}★",
"p1": "It will automatically add the lifetime pulls, 5★, and 4★ pity",
"p2": [
"When the",
"pity reaches 10, it will automatically be reset to 0"
],
"p3": [
"When the",
"pity reaches 90, it will automatically be reset to 0"
],
"p2": ["When the", "pity reaches 10, it will automatically be reset to 0"],
"p3": ["When the", "pity reaches 90, it will automatically be reset to 0"],
"p4": [
"After a 10x Wish, press",
"but keep in mind that the pity counter might not be accurate, because there is no way to tell when the drop occured (maybe you got it on the 1st or even the 10th pull). To ensure that the counter is still accurate, you need to check the history table and add it one-by-one like you do 1x Wishes."
],
"p5": [
"You can also press the",
"button to edit the values manually!"
],
"p5": ["You can also press the", "button to edit the values manually!"],
"p6": "Press the arrow on the bottom to see your pulls' details. A popup will show up when you get a 5★ or 4★. You can also add or edit the table manually."
}
},
@ -535,11 +512,7 @@
"calculateTalent": "Calculate Talent Material?",
"inputTalentLevel": "Input the 1st, 2nd & 3rd current talent level",
"inputTalentNotice": "If it has a different color, subtract it by 3",
"inputTalent": [
"1st talent lvl",
"2nd talent lvl",
"3rd talent lvl"
],
"inputTalent": ["1st talent lvl", "2nd talent lvl", "3rd talent lvl"],
"talentToLevel": "to level",
"calculate": "Calculate",
"unknownInformation": "There are some unknown information",
@ -548,11 +521,7 @@
"expWasted": "EXP Wasted",
"addToTodo": "Add to Todo List",
"addedToTodo": "Added to Todo List",
"talent": [
"Attack",
"Skill",
"Burst"
]
"talent": ["Attack", "Skill", "Burst"]
},
"expTable": {
"level": "Level",
@ -644,10 +613,7 @@
"todo": {
"title": "Todo List",
"summary": "Summary",
"empty": [
"Nothing to do yet 😀",
"Add some from the Items page or the Calculator!"
],
"empty": ["Nothing to do yet 😀", "Add some from the Items page or the Calculator!"],
"farmableToday": "Farmable Today",
"resin": "Resin needed",
"based": "Based on AR:{ar} and WL:{wl}",
@ -962,5 +928,12 @@
"common": {
"dataSynced": "Data has been synced!",
"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>
import { onMount } from 'svelte';
export let status;
export let error;
const dev = process.env.NODE_ENV === 'development';
const dev = import.meta.env.DEV;
let refreshUrl;
@ -39,7 +50,7 @@
>
Click here to refresh
</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">
You might also want to check your extension, like adblock, it sometimes wrongly block the script needed for the
site.

View File

@ -1,21 +1,17 @@
<script context="module">
import { waitLocale } from 'svelte-i18n';
export async function preload() {
return waitLocale();
}
</script>
<script>
import '../app.css';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
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 { startClient } from '../i18n.js';
import { navigating, page } from '$app/stores';
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 Header from '../components/Header.svelte';
import DataSync from '../components/DataSync.svelte';
@ -26,28 +22,35 @@
import SettingData from '../components/SettingData.svelte';
import Toast from '../components/Toast.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 { preloading, page } = stores();
const delayedPreloading = derived(preloading, (currentPreloading, set) => {
setTimeout(() => set(currentPreloading), 250);
const delayedPreloading = derived(navigating, (_, set) => {
set(true);
setTimeout(() => set(true), 250);
});
startClient();
page.subscribe(() => {
try {
window.reloadAdSlots();
} catch (error) {}
});
// check local storage save on load
onMount(async () => {
await checkLocalSave();
page.subscribe(() => {
try {
window.reloadAdSlots();
} catch (error) {}
console.log('localforage config');
localforage.config({
driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
name: 'paimonmoe',
version: 1.0,
description: 'paimonmoe local storage',
});
window.localforage = localforage;
await checkLocalSave();
});
</script>
<Tailwind />
$: segment = $page.url.pathname.substring(1).split('/')[0];
</script>
<Header />
<Modal>
@ -63,8 +66,9 @@
<slot />
</main>
</DataSync>
<ServiceWorker />
</Modal>
{#if $preloading && $delayedPreloading}
{#if $navigating && $delayedPreloading}
<div transition:fade class="loading-bar" />
{/if}
<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>
<div>
<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"
target="_blank"
>
<Icon path={mdiGithub} size={1} /> {$t('footer.link.github')}
<Icon path={mdiGithub} size={1} />
{$t('footer.link.github')}
</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"
target="_blank"
>
<Icon path={mdiTwitter} size={1} /> {$t('footer.link.devTwitter')}
<Icon path={mdiTwitter} size={1} />
{$t('footer.link.devTwitter')}
</a>
</div>
</div>
@ -100,25 +106,28 @@
<span class="text-gray-500">{$t('footer.official')}</span>
<div>
<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"
target="_blank"
>
<Icon path={mdiDiscord} size={1} /> {$t('footer.link.discord')}
<Icon path={mdiDiscord} size={1} />
{$t('footer.link.discord')}
</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/"
target="_blank"
>
<Icon path={mdiFacebook} size={1} /> {$t('footer.link.facebook')}
<Icon path={mdiFacebook} size={1} />
{$t('footer.link.facebook')}
</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/"
target="_blank"
>
<Icon path={mdiReddit} size={1} /> {$t('footer.link.reddit')}
<Icon path={mdiReddit} size={1} />
{$t('footer.link.reddit')}
</a>
</div>
</div>
@ -132,7 +141,7 @@
</div>
</div>
<style>
<style lang="postcss">
.loading-bar {
position: fixed;
top: 0;

View File

@ -37,7 +37,7 @@
let user = '';
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 });
url.search = query.toString();

View File

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

View File

@ -84,7 +84,7 @@
<div class="flex flex-col">
{#each upcoming as item}
<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}
</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">
{#each current as item}
<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}
</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 = '...';
// async function getData() {
// const url = new URL(`${__paimon.env.API_HOST}/visitor`);
// const url = new URL(`${import.meta.env.VITE_API_HOST}/visitor`);
// try {
// const res = await fetch(url, {

View File

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

View File

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

View File

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

View File

@ -630,7 +630,7 @@
{#if currentMax.usage[i] > 0}
<tr>
<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]}
<Icon size={0.5} path={mdiClose} /></span
>
@ -652,7 +652,7 @@
{#if item.amount > 0}
<tr>
<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}
<Icon size={0.5} path={mdiClose} /></span
>
@ -670,7 +670,7 @@
{/each}
<tr>
<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)}
<Icon size={0.5} path={mdiClose} /></span
>

View File

@ -109,7 +109,7 @@
}
</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="bg-item rounded-xl p-4 w-full">
<table>
@ -126,7 +126,7 @@
><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="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}
{#if row.usage[j] > 0}
<span class="mr-2">
@ -145,7 +145,7 @@
</div>
</div>
<style>
<style lang="postcss">
td,
th {
@apply py-1;

View File

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

View File

@ -334,7 +334,7 @@
</tr>
{/each}
<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')}
{numberFormat.format(resultTotal)}
<img class="mr-1 w-6 inline" src="/images/genesis_crystal.png" alt="Genesis" />
@ -343,7 +343,7 @@
</td>
</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')}
{currencyLabel}{numberFormat.format(resultTotalPrice)}
</td>

View File

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

View File

@ -132,7 +132,7 @@
<table class="w-full">
<tr>
<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}
<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 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}
<Icon size={0.5} path={mdiClose} /></span
>
@ -166,7 +166,7 @@
</tr>
<tr>
<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}
<Icon size={0.5} path={mdiClose} /></span
>
@ -184,7 +184,9 @@
<tr>
<td class="text-red-400" colspan="2">
{$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>
</tr>
</table>

View File

@ -31,7 +31,7 @@
counTimeRelative();
</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="bg-item rounded-xl p-4 w-full">
<table class="w-full">
@ -57,7 +57,7 @@
</div>
</div>
<style>
<style lang="postcss">
td,
th {
@apply py-1;

View File

@ -441,7 +441,7 @@
{#if currentMax.usage[i] > 0}
<tr>
<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]}
<Icon size={0.5} path={mdiClose} /></span
>
@ -463,7 +463,7 @@
{#if item.amount > 0}
<tr>
<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}
<Icon size={0.5} path={mdiClose} /></span
>
@ -481,7 +481,7 @@
{/each}
<tr>
<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)}
<Icon size={0.5} path={mdiClose} /></span
>

View File

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

View File

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

View File

@ -107,7 +107,7 @@
{/if}
</div>
<style>
<style lang="postcss">
td:not(:last-child) {
@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">
{$t('characters.subtitle')}
</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">
<table class="w-full block p-4 bg-item rounded-xl">
<thead>
@ -521,7 +521,7 @@
{/if}
</div>
<style>
<style lang="postcss">
tr.rare:hover {
background: linear-gradient(
90deg,

View File

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

View File

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

View File

@ -64,7 +64,7 @@
{#each result as [item, amount], i}
<tr>
<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}
<Icon size={0.5} path={mdiClose} />
</span>
@ -82,7 +82,7 @@
{#if coins > 0}
<tr>
<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}
<Icon size={0.5} path={mdiClose} />
</span>

View File

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

View File

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

View File

@ -1,14 +1,11 @@
<script context="module">
import data from '../../data/furnishing/en.json';
export async function preload() {
return { data };
}
import dataJson from '../../data/furnishing/en.json';
</script>
<script>
import { onMount } from 'svelte';
import { locale, t } from 'svelte-i18n';
import debounce from 'lodash/debounce';
import debounce from 'lodash.debounce';
import { mdiInformationOutline, mdiMinus, mdiPlus } from '@mdi/js';
import TableHeader from '../../components/Table/TableHeader.svelte';
@ -17,7 +14,7 @@
import { getAccountPrefix } from '../../stores/account';
import { readSave, updateSave } from '../../stores/saveManager';
export let data;
let data = dataJson;
let type = 'hall';
let items = [];
@ -259,7 +256,7 @@
</div>
{/if}
<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">
<table class="w-full block pl-4 pr-4 py-2 md:pl-8 md:py-4 bg-item rounded-xl">
<tr>
@ -363,7 +360,7 @@
</div>
</div>
<style>
<style lang="postcss">
.pill {
@apply rounded-2xl;
@apply border-2;
@ -375,7 +372,7 @@
@apply outline-none;
@apply transition;
@apply duration-100;
@apply whitespace-no-wrap;
@apply whitespace-nowrap;
&:hover {
@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>
import { onMount } from 'svelte';
import debounce from 'lodash/debounce';
import debounce from 'lodash.debounce';
import { locale } from 'svelte-i18n';
import Masonry from '../components/Masonry.svelte';
@ -19,6 +35,8 @@
import Build from './_index/build.svelte';
import Ad from '../components/Ad.svelte';
export let builds;
let refreshLayout;
let isMobile = false;
@ -64,7 +82,7 @@
<Ad type="mobile" variant="mpu" id="1" />
</div>
{/if}
<Build on:done={onDone} />
<Build on:done={onDone} {builds} />
<Event on:done={onDone} />
<Item on:done={onDone} />
<Discord on:done={onDone} />

View File

@ -169,7 +169,7 @@
<CharacterSelect bind:selected={selectedCharacter} placeholder={$t('items.searchCharacter')} />
<WeaponSelect bind:selected={selectedWeapon} placeholder={$t('items.searchWeapon')} />
</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">
<table class="w-full block p-4 bg-item rounded-xl">
<thead>
@ -283,7 +283,7 @@
</table>
</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">
<table class="w-full block p-4 bg-item rounded-xl">
<thead>
@ -338,7 +338,7 @@
</div>
</div>
<style>
<style lang="postcss">
td {
@apply text-white;
@apply px-4;

View File

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

View File

@ -34,7 +34,7 @@
if ($firebaseToken === '') return;
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' });
url.search = query.toString();
@ -59,7 +59,7 @@
async function deleteCurrentReminder() {
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' });
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');
try {
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, {
const res = await fetch(`${import.meta.env.VITE_API_HOST}/reminder`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({

View File

@ -45,7 +45,7 @@
if ($firebaseToken === '') return;
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' });
url.search = query.toString();
@ -70,7 +70,7 @@
async function deleteCurrentReminder() {
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' });
url.search = query.toString();
@ -109,7 +109,7 @@
}
try {
const res = await fetch(`${__paimon.env.API_HOST}/reminder`, {
const res = await fetch(`${import.meta.env.VITE_API_HOST}/reminder`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({

View File

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

View File

@ -67,7 +67,7 @@
<p class="text-red-400 mb-2">{$t('settings.importWarning')}</p>
{#if !loading}
<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')}
</Button>
{#if files !== null && files[0]}

View File

@ -475,14 +475,14 @@
<a
href="https://discord.gg/tPURAYgHV9"
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
>
{$t('settings.or')}
<a
href="https://github.com/MadeBaruna/paimon-moe/issues"
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
>
{$t('settings.thanks')}

View File

@ -25,7 +25,6 @@
$: prevEnded = prev !== null && now.isAfter(prev.end);
$: shouldShowHourStart = diffStart <= 86400000 || event.duration > 6.5 || !prevNearby;
$: shouldShowHourEnd = diffEnd <= 86400000 || event.duration > 6.5 || !prevNearby;
</script>
<div
@ -43,7 +42,7 @@
>
<div class="event-item {nextDiff < 1 ? '' : 'rounded-xl'}" />
<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}
</span>
@ -82,7 +81,7 @@
{/if}
</div>
<style>
<style lang="postcss">
div.event-item {
position: absolute;
opacity: 1;
@ -102,5 +101,4 @@
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;
}
</style>

View File

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

View File

@ -270,7 +270,6 @@
$: $todos, updateSummary();
$: columnCount, updateId();
</script>
<svelte:head>
@ -304,7 +303,7 @@
{#each Object.entries(todayOnlyItems) as [id, amount]}
<tr class="today-only">
<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)}
<Icon size={0.5} path={mdiClose} /></span
>
@ -339,7 +338,7 @@
<tr>
<td class="text-right border-b border-gray-700 py-1">
<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)}
</span>
<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]}
<tr>
<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)}
<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]}
<tr>
<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)}
<Icon size={0.5} path={mdiClose} /></span
>
@ -491,11 +490,10 @@
</Masonry>
</div>
<style>
<style lang="postcss">
tr.today-only:last-child {
td {
@apply border-b-0;
}
}
</style>

View File

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

View File

@ -1,8 +1,5 @@
<script context="module">
import data from '../../data/weapons/en.json';
export async function preload() {
return { data };
}
</script>
<script>
@ -12,7 +9,7 @@
import { formatStat } from '../../helper';
import Ad from '../../components/Ad.svelte';
export let data;
let weaponData = data;
let weaponList = [];
let sortBy = 'name';
let sortOrder = true;
@ -34,7 +31,7 @@
function process() {
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;
_weapons.push({
@ -94,7 +91,7 @@
async function changeLocale(locale) {
const _data = await import(`../../data/weapons/${locale}.json`);
data = _data.default;
weaponData = _data.default;
process();
}
@ -115,7 +112,7 @@
<p class="text-gray-400 px-4 md:px-8 font-medium pb-4" style="margin-top: -1rem;">
{$t('weapon.subtitle')}
</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;" />
<div class="px-4 md:px-8 table max-w-full">
<table class="w-full block p-4 bg-item rounded-xl">

View File

@ -1,7 +1,7 @@
<script context="module">
export async function preload(page) {
const { id } = page.params;
return { id };
export async function load({ params }) {
const { id } = params;
return { props: { id } };
}
</script>
@ -510,7 +510,7 @@
</div>
{:else}
<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">
<button on:click={() => toggleShowRarity(0)} class={`pill legendary ${showRarity[0] ? 'active' : ''}`}>
5 <Icon path={mdiStar} size={0.75} className="mb-1" />
@ -600,7 +600,7 @@
: ''}"
>
<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;"
>
{pull.formattedTime}
@ -710,19 +710,18 @@
<Ad type="mobile" variant="lb" id="1" />
</div>
<style>
<style lang="postcss">
.wrapper {
@apply flex-col-reverse;
.chart-area {
@apply px-4;
@screen md {
@apply px-8;
}
@apply md:px-8;
}
}
@media (min-width: 1920px) {
@media (min-width: 1920px) {
.wrapper {
@apply flex-row;
.chart-area {

View File

@ -4,7 +4,7 @@
import { onMount, getContext, createEventDispatcher } from 'svelte';
import { slide } from 'svelte/transition';
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');
@ -319,7 +319,7 @@
isEdit ? 'bg-item flex-col py-2' : 'bg-background flex-row items-center justify-center mb-2 p-4'
} 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 />
<span class="flex items-center text-gray-600">
<img class="w-4 h-4 mr-2" src="/images/primogem.png" alt="primogem" />
@ -345,7 +345,7 @@
</div>
</div>
{/if}
<span class="text-gray-200 whitespace-no-wrap flex-1">
<span class="text-gray-200 whitespace-nowrap flex-1">
5★ {$t('wish.pity')}
<br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: legendaryPity } })}</span>
</span>
@ -374,7 +374,7 @@
</div>
</div>
{/if}
<span class="text-gray-200 whitespace-no-wrap flex-1">
<span class="text-gray-200 whitespace-nowrap flex-1">
4★ {$t('wish.pity')}
<br /><span class="text-gray-600">{$t('wish.guarantee', { values: { pity: 10 } })}</span>
</span>
@ -476,7 +476,7 @@
{:else if pull.type === 'unknown_3_star'}
<td class="border-b border-gray-700 py-1 pl-2 font-semibold text-primary">Unknown</td>
{/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}
</td>
<td class="text-right border-b border-gray-700 py-1">{pull.pity}</td>
@ -487,7 +487,7 @@
{/if}
</div>
<style>
<style lang="postcss">
.pill {
@apply rounded-2xl;
@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-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 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>
<td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700">
@ -101,7 +101,7 @@
</td>
</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')}
</td>
<td class="text-rare-from font-semibold pr-2 md:pr-4 text-right border-t border-gray-700">
@ -115,7 +115,7 @@
</td>
</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')}
</td>
<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>
<style>
<style lang="postcss">
span.pity {
@apply rounded-xl;
@apply text-gray-400;
@apply border;
@apply border-legendary-from;
@apply whitespace-no-wrap;
@apply whitespace-nowrap;
@apply px-2;
@apply mb-1;
@apply mr-1;

View File

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

View File

@ -43,7 +43,7 @@
</p>
<p class="mb-2">
{$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')}
<Icon path={mdiArrowUp} size={1.2} />
</p>
@ -54,7 +54,7 @@
</div>
</div>
<style>
<style lang="postcss">
.bubble::after {
content: '';
position: absolute;

View File

@ -51,7 +51,7 @@
if (wishCount === 0) return;
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 });
url.search = query.toString();
@ -99,7 +99,7 @@
if (percentages[current] === undefined) return;
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 });
url.search = query.toString();
@ -151,7 +151,7 @@
}
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 });
url.search = query.toString();
@ -368,7 +368,7 @@
</div>
</div>
<style>
<style lang="postcss">
.pill {
@apply text-sm;
@apply rounded-2xl;

View File

@ -3,7 +3,7 @@
import { createEventDispatcher, onMount } from 'svelte';
import dayjs from 'dayjs';
import debounce from 'lodash/debounce';
import debounce from 'lodash.debounce';
import { characters } from '../../data/characters';
import { weaponList } from '../../data/weaponList';
@ -304,7 +304,7 @@
</div>
{/if}
<style>
<style lang="postcss">
.container {
@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-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 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>
<td class="text-legendary-from font-semibold pr-2 md:pr-4 border-t border-gray-700">
@ -131,13 +131,13 @@
{/if}
</div>
<style>
<style lang="postcss">
span.pity {
@apply rounded-xl;
@apply text-gray-400;
@apply border;
@apply border-legendary-from;
@apply whitespace-no-wrap;
@apply whitespace-nowrap;
@apply px-2;
@apply mb-1;
@apply mr-1;

View File

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

View File

@ -255,7 +255,7 @@
</div>
</div>
<style>
<style lang="postcss">
@media (min-width: 1920px) {
.top-header {
@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

@ -104,11 +104,11 @@
min: 0,
max: 0,
};
const rareInclude = {
300011: ['rosaria'],
300012: ['yanfei', 'noelle', 'diona'],
};
300011: ['rosaria'],
300012: ['yanfei', 'noelle', 'diona'],
};
let promotedRarePercentage = 0;
let legendaryList = [];
@ -122,7 +122,7 @@
});
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 });
url.search = query.toString();
@ -173,19 +173,22 @@
// only for zhongli banner upward
if (id > 300011 && id < 400000) {
const totalRare = data.list.reduce((prev, current) => {
if (rareInclude[id].includes(current.name)) {
prev.total += current.count;
}
if (featured[1] === current.name) {
prev.featured = current.count;
}
return prev;
}, {
total: 0,
featured: 0,
});
promotedRarePercentage = totalRare.featured / totalRare.total * 100
const totalRare = data.list.reduce(
(prev, current) => {
if (rareInclude[id].includes(current.name)) {
prev.total += current.count;
}
if (featured[1] === current.name) {
prev.featured = current.count;
}
return prev;
},
{
total: 0,
featured: 0,
},
);
promotedRarePercentage = (totalRare.featured / totalRare.total) * 100;
}
legendary = {
@ -340,7 +343,7 @@
100,
)}%
{$t('wish.tally.wonFiftyFifty')}
{:else if id > 300011 && id < 400000 && i === 1}
{:else if id > 300011 && id < 400000 && i === 1}
{numberFormat.format(promotedRarePercentage)}%
{$t('wish.tally.fromFourStarFeatured')}
{:else}
@ -365,7 +368,7 @@
{numberFormat.format(legendary.percentage)}%
</p>
<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} />
@ -380,7 +383,7 @@
{numberFormatSecondary.format(rare.percentage)}%
</p>
<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} />
@ -489,7 +492,7 @@
<table class="text-white w-full table-fixed text-sm">
<tr>
<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')}
</td>
@ -535,11 +538,14 @@
</table>
</div>
<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">
<tr>
<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')}
</td>
@ -578,20 +584,20 @@
</div>
<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="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>
</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" />
<span class="font-semibold">{numberFormat.format(data.total.all * 160)}</span>
</div>
</div>
<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')}
<span class="font-semibold ml-2">{numberFormat.format(data.median.legendary)}</span>
</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>
</div>
</div>
@ -603,7 +609,7 @@
{/if}
</div>
<style>
<style lang="postcss">
@screen xl {
.pity-summary {
min-width: 320px;

View File

@ -119,7 +119,7 @@
});
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 });
url.search = query.toString();
@ -381,7 +381,7 @@
{numberFormat.format(legendary.percentage)}%
</p>
<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} />
@ -396,7 +396,7 @@
{numberFormatSecondary.format(rare.percentage)}%
</p>
<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} />
@ -505,7 +505,7 @@
<table class="text-white w-full table-fixed text-sm">
<tr>
<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')}
</td>
@ -560,7 +560,7 @@
<table class="text-white text-sm">
<tr>
<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')}
</td>
@ -599,27 +599,27 @@
</div>
<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="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>
</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" />
<span class="font-semibold">{numberFormat.format(data.total.all * 160)}</span>
</div>
</div>
<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')}
<span class="font-semibold ml-2">{numberFormat.format(data.median.legendary)}</span>
</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>
</div>
</div>
<div class="space-y-2 flex flex-col flex-wrap mt-2">
<a
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')}
</a>
@ -632,7 +632,7 @@
{/if}
</div>
<style>
<style lang="postcss">
@screen xl {
.pity-summary {
min-width: 320px;

View File

@ -142,7 +142,7 @@
loading = 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 });
url.search = query.toString();
@ -626,7 +626,7 @@
{numberFormat.format(legendary.percentage)}%
</td>
<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} />
@ -644,7 +644,7 @@
{numberFormat.format(rare.percentage)}%
</td>
<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} />
@ -818,7 +818,7 @@
<Ad type="mobile" variant="lb" id="2" />
</div>
<style>
<style lang="postcss">
@screen md {
.select-name {
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,
// `files` is an array of everything in the `static` directory
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('install', (event) => {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}
async function fetchAddCache(url) {
try {
const cache = await caches.open(CACHE);
const cachedRes = await cache.match(url);
self.clients.claim();
})
);
});
if (cachedRes) return;
/**
* Fetch the asset from the network and store it in the cache.
* Fall back to the cache if the user is offline.
*/
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;
}
const res = await fetch(url);
cache.put(url, res.clone());
} catch (err) {
console.error(err);
}
}
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
self.addEventListener('activate', (event) => {
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
const isHttp = url.protocol.startsWith('http');
const isDevServerRequest = url.hostname === self.location.hostname && url.port !== self.location.port;
const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname);
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,
// set this variable to true for them and they will only be fetched once.
const cachedAsset = isStaticAsset && await caches.match(event.request);
// for pages, you might want to serve a shell `service-worker-index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
if (!cachedAsset && url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
return caches.match('/service-worker-index.html');
}
*/
return cachedAsset || fetchAndCache(event.request);
})()
);
}
fetchAddCache('/');
channel.addEventListener('message', (event) => {
if (event.data.type === 'fetch-doc') {
fetchAddCache(event.data.path);
}
});
}),
);
});
self.addEventListener('fetch', async (event) => {
if (!event.request.url.startsWith(self.location.origin) || event.request.method !== 'GET') return;
event.respondWith(
(async () => {
const cache = await caches.open(CACHE);
const cachedRes = await cache.match(event.request);
if (cachedRes) {
return cachedRes;
}
const res = await fetch(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);
const firebaseConfig = {
apiKey: __paimon.env.FIREBASE_API_KEY,
authDomain: __paimon.env.FIREBASE_AUTH_DOMAIN,
projectId: __paimon.env.FIREBASE_PROJECT_ID,
storageBucket: __paimon.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: __paimon.env.FIREBASE_MESSAGING_SENDER_ID,
appId: __paimon.env.FIREBASE_APP_ID,
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
};
let messaging;
@ -67,7 +67,7 @@ async function initFirebase() {
firebase.initializeApp(firebaseConfig);
messaging = firebase.messaging();
}
await getToken();
}

View File

@ -1,6 +1,6 @@
import dayjs from 'dayjs';
import { writable } from 'svelte/store';
import debounce from 'lodash/debounce';
import debounce from 'lodash.debounce';
import localforage from 'localforage';
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 postcss = require('./postcss.config');
// const sveltePreprocess = require('svelte-preprocess');
// const postcss = require('./postcss.config');
const preprocess = sveltePreprocess({
defaults: {
style: 'postcss',
// const preprocess = sveltePreprocess({
// defaults: {
// 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