Initial Commit, Feature parity to nextjs version

This commit is contained in:
Neshura 2023-08-06 04:16:32 +02:00
parent 506751580a
commit 9401bdf9ed
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
42 changed files with 16511 additions and 88 deletions

13
.eslintignore 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

30
.eslintrc.cjs Normal file
View file

@ -0,0 +1,30 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

98
.gitignore vendored
View file

@ -1,88 +1,12 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
.env.*
!.env.example
.vercel
.output
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

2
.npmrc Normal file
View file

@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

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

9
.prettierrc Normal file
View file

@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View file

@ -1 +1,38 @@
# chellaris-signup-site
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

38
package.json Normal file
View file

@ -0,0 +1,38 @@
{
"name": "chellaris-signup-site",
"version": "0.0.1",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"@types/cookie": "^0.5.1",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"chart.js": "^4.3.3",
"chartjs-plugin-datalabels": "^2.2.0",
"svelte-chartjs": "^3.1.2"
}
}

12
src/app.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

12
src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#5865f2;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 988 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View file

@ -0,0 +1,65 @@
export enum Species {
Humanoid = 0,
Mammalian = 1,
Reptilian = 2,
Avian = 3,
Arthropod = 4,
Molluscoid = 5,
Fungoid = 6,
Plantoid = 7,
Lithoid = 8,
Necroid = 9,
Aquatic = 10,
Toxoid = 11,
Machine = 12,
}
export enum Ethics {
Egalitarian = 0,
Authoritarian = 1,
Militarist = 2,
Pacifist = 3,
Xenophobe = 4,
Xenophile = 5,
Competitive = 6,
Cooperative = 7,
Elitist = 8,
Pluralist = 9,
Materialist = 10,
Spiritualist = 11,
Ecologist = 12,
Industrialist = 13,
}
export enum Scale {
normal = 1,
fanatic = 2,
}
export class EthicsDataLegacy {
// Array index determines fanatic or regular, Index value represents number of occurences
private data: Array<number> = [0]
constructor(data: Array<number>) {
this.data[Scale.normal] = data[0],
this.data[Scale.fanatic] = data[1]
}
sum(weigthed: boolean): number {
let sum = 0;
// skip 0 index since it isn't used
this.data.slice(1, this.data.length).forEach((value, index) => {
// Since the index is accessed via scale enum this works, if not weighted override to 1
let factor = index + 1;
if (!weigthed) {
factor = 1;
}
sum = sum + value * factor;
});
return sum;
}
sumRegular(): number {
return this.data[Scale.normal];
}
}

31
src/routes/+layout.svelte Normal file
View file

@ -0,0 +1,31 @@
<script>
import Header from './Header.svelte';
import './styles.css';
</script>
<div class="app">
<Header />
<main>
<slot />
</main>
</div>
<style>
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 2rem;
width: 100%;
max-height: 90vh;
margin: 0 auto;
box-sizing: border-box;
}
</style>

59
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,59 @@
<script>
import Counter from './Counter.svelte';
import welcome from '$lib/images/svelte-welcome.webp';
import welcome_fallback from '$lib/images/svelte-welcome.png';
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<section>
<h1>
<span class="welcome">
<picture>
<source srcset={welcome} type="image/webp" />
<img src={welcome_fallback} alt="Welcome" />
</picture>
</span>
to your new<br />SvelteKit app
</h1>
<h2>
try editing <strong>src/routes/+page.svelte</strong>
</h2>
<Counter />
</section>
<style>
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 0.6;
}
h1 {
width: 100%;
}
.welcome {
display: block;
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
</style>

3
src/routes/+page.ts Normal file
View file

@ -0,0 +1,3 @@
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in production
export const prerender = true;

102
src/routes/Counter.svelte Normal file
View file

@ -0,0 +1,102 @@
<script lang="ts">
import { spring } from 'svelte/motion';
let count = 0;
const displayed_count = spring();
$: displayed_count.set(count);
$: offset = modulo($displayed_count, 1);
function modulo(n: number, m: number) {
// handle negative numbers
return ((n % m) + m) % m;
}
</script>
<div class="counter">
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5" />
</svg>
</button>
<div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg>
</button>
</div>
<style>
.counter {
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0;
}
.counter button {
width: 2em;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 0;
background-color: transparent;
touch-action: manipulation;
font-size: 2rem;
}
.counter button:hover {
background-color: var(--color-bg-1);
}
svg {
width: 25%;
height: 25%;
}
path {
vector-effect: non-scaling-stroke;
stroke-width: 2px;
stroke: #444;
}
.counter-viewport {
width: 8em;
height: 4em;
overflow: hidden;
text-align: center;
position: relative;
}
.counter-viewport strong {
position: absolute;
display: flex;
width: 100%;
height: 100%;
font-weight: 400;
color: var(--color-theme-1);
font-size: 4rem;
align-items: center;
justify-content: center;
}
.counter-digits {
position: absolute;
width: 100%;
height: 100%;
}
.hidden {
top: -100%;
user-select: none;
}
</style>

132
src/routes/Header.svelte Normal file
View file

@ -0,0 +1,132 @@
<script>
import { page } from '$app/stores';
import logo from '$lib/images/svelte-logo.svg';
import discord from '$lib/images/discord.svg';
</script>
<header>
<div class="corner">
</div>
<nav>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg>
<ul>
<li aria-current={$page.url.pathname === '/' ? 'page' : undefined}>
<a href="/">Graphs</a>
</li>
<li aria-current={$page.url.pathname === '/legacy-graphs' ? 'page' : undefined}>
<a href="/legacy-graphs">Game 15 Graphs</a>
</li>
<li aria-current={$page.url.pathname.startsWith('/sign-up') ? 'page' : undefined}>
<a href="/sign-up">Empire Sign-Up</a>
</li>
<li aria-current={$page.url.pathname.startsWith('/admin') ? 'page' : undefined}>
<a href="/admin">Admin Menu</a>
</li>
<li aria-current={$page.url.pathname.startsWith('/about') ? 'page' : undefined}>
<a href="/about">About</a>
</li>
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg>
</nav>
<div class="corner">
<a href="https://github.com/sveltejs/kit">
<img src={discord} alt="Discord" />
</a>
</div>
</header>
<style>
header {
display: flex;
justify-content: space-between;
}
.corner {
width: 3em;
height: 3em;
}
.corner a {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.corner img {
width: 2em;
height: 2em;
object-fit: contain;
}
nav {
display: flex;
justify-content: center;
--background: rgba(0, 0, 0, 0.7);
}
svg {
width: 2em;
height: 3em;
display: block;
}
path {
fill: var(--background);
}
ul {
position: relative;
padding: 0;
margin: 0;
height: 3em;
display: flex;
justify-content: center;
align-items: center;
list-style: none;
background: var(--background);
background-size: contain;
}
li {
position: relative;
height: 100%;
}
li[aria-current='page']::before {
--size: 6px;
content: '';
width: 0;
height: 0;
position: absolute;
top: 0;
left: calc(50% - var(--size));
border: var(--size) solid transparent;
border-top: var(--size) solid var(--color-theme-1);
}
nav a {
display: flex;
height: 100%;
align-items: center;
padding: 0 0.5rem;
color: var(--color-text);
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
transition: color 0.2s linear;
}
a:hover {
color: var(--color-theme-1);
}
</style>

View file

@ -0,0 +1,14 @@
<svelte:head>
<title>About</title>
<meta name="description" content="About this app" />
</svelte:head>
<div class="text-column">
<h1>About this site</h1>
<p>
This site replaces the long used Excel Sheet and Google Docs used for managing the Chellaris Sign-Up process.
For now only the Sign-Up statistics and Admin work have been migrated along with some fancy pants graphs.
The Empire Sign-Up process may follow at a later date.
</p>
</div>

View file

@ -0,0 +1,9 @@
import { dev } from '$app/environment';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement
export const csr = dev;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in production
export const prerender = true;

View file

@ -0,0 +1,69 @@
import { fail } from '@sveltejs/kit';
import { Game } from './game';
import type { PageServerLoad, Actions } from './$types';
export const load = (({ cookies }) => {
const game = new Game(cookies.get('sverdle'));
return {
/**
* The player's guessed words so far
*/
guesses: game.guesses,
/**
* An array of strings like '__x_c' corresponding to the guesses, where 'x' means
* an exact match, and 'c' means a close match (right letter, wrong place)
*/
answers: game.answers,
/**
* The correct answer, revealed if the game is over
*/
answer: game.answers.length >= 6 ? game.answer : null
};
}) satisfies PageServerLoad;
export const actions = {
/**
* Modify game state in reaction to a keypress. If client-side JavaScript
* is available, this will happen in the browser instead of here
*/
update: async ({ request, cookies }) => {
const game = new Game(cookies.get('sverdle'));
const data = await request.formData();
const key = data.get('key');
const i = game.answers.length;
if (key === 'backspace') {
game.guesses[i] = game.guesses[i].slice(0, -1);
} else {
game.guesses[i] += key;
}
cookies.set('sverdle', game.toString());
},
/**
* Modify game state in reaction to a guessed word. This logic always runs on
* the server, so that people can't cheat by peeking at the JavaScript
*/
enter: async ({ request, cookies }) => {
const game = new Game(cookies.get('sverdle'));
const data = await request.formData();
const guess = data.getAll('guess') as string[];
if (!game.enter(guess)) {
return fail(400, { badGuess: true });
}
cookies.set('sverdle', game.toString());
},
restart: async ({ cookies }) => {
cookies.delete('sverdle');
}
} satisfies Actions;

View file

@ -0,0 +1,406 @@
<script lang="ts">
import { confetti } from '@neoconfetti/svelte';
import { enhance } from '$app/forms';
import type { PageData, ActionData } from './$types';
import { reduced_motion } from './reduced-motion';
export let data: PageData;
export let form: ActionData;
/** Whether or not the user has won */
$: won = data.answers.at(-1) === 'xxxxx';
/** The index of the current guess */
$: i = won ? -1 : data.answers.length;
/** Whether the current guess can be submitted */
$: submittable = data.guesses[i]?.length === 5;
/**
* A map of classnames for all letters that have been guessed,
* used for styling the keyboard
*/
let classnames: Record<string, 'exact' | 'close' | 'missing'>;
/**
* A map of descriptions for all letters that have been guessed,
* used for adding text for assistive technology (e.g. screen readers)
*/
let description: Record<string, string>;
$: {
classnames = {};
description = {};
data.answers.forEach((answer, i) => {
const guess = data.guesses[i];
for (let i = 0; i < 5; i += 1) {
const letter = guess[i];
if (answer[i] === 'x') {
classnames[letter] = 'exact';
description[letter] = 'correct';
} else if (!classnames[letter]) {
classnames[letter] = answer[i] === 'c' ? 'close' : 'missing';
description[letter] = answer[i] === 'c' ? 'present' : 'absent';
}
}
});
}
/**
* Modify the game state without making a trip to the server,
* if client-side JavaScript is enabled
*/
function update(event: MouseEvent) {
const guess = data.guesses[i];
const key = (event.target as HTMLButtonElement).getAttribute(
'data-key'
);
if (key === 'backspace') {
data.guesses[i] = guess.slice(0, -1);
if (form?.badGuess) form.badGuess = false;
} else if (guess.length < 5) {
data.guesses[i] += key;
}
}
/**
* Trigger form logic in response to a keydown event, so that
* desktop users can use the keyboard to play the game
*/
function keydown(event: KeyboardEvent) {
if (event.metaKey) return;
document
.querySelector(`[data-key="${event.key}" i]`)
?.dispatchEvent(new MouseEvent('click', { cancelable: true }));
}
</script>
<svelte:window on:keydown={keydown} />
<svelte:head>
<title>Sverdle</title>
<meta name="description" content="A Wordle clone written in SvelteKit" />
</svelte:head>
<h1 class="visually-hidden">Sverdle</h1>
<form
method="POST"
action="?/enter"
use:enhance={() => {
// prevent default callback from resetting the form
return ({ update }) => {
update({ reset: false });
};
}}
>
<a class="how-to-play" href="/sverdle/how-to-play">How to play</a>
<div class="grid" class:playing={!won} class:bad-guess={form?.badGuess}>
{#each Array.from(Array(6).keys()) as row (row)}
{@const current = row === i}
<h2 class="visually-hidden">Row {row + 1}</h2>
<div class="row" class:current>
{#each Array.from(Array(5).keys()) as column (column)}
{@const answer = data.answers[row]?.[column]}
{@const value = data.guesses[row]?.[column] ?? ''}
{@const selected = current && column === data.guesses[row].length}
{@const exact = answer === 'x'}
{@const close = answer === 'c'}
{@const missing = answer === '_'}
<div class="letter" class:exact class:close class:missing class:selected>
{value}
<span class="visually-hidden">
{#if exact}
(correct)
{:else if close}
(present)
{:else if missing}
(absent)
{:else}
empty
{/if}
</span>
<input name="guess" disabled={!current} type="hidden" {value} />
</div>
{/each}
</div>
{/each}
</div>
<div class="controls">
{#if won || data.answers.length >= 6}
{#if !won && data.answer}
<p>the answer was "{data.answer}"</p>
{/if}
<button data-key="enter" class="restart selected" formaction="?/restart">
{won ? 'you won :)' : `game over :(`} play again?
</button>
{:else}
<div class="keyboard">
<button data-key="enter" class:selected={submittable} disabled={!submittable}>enter</button>
<button
on:click|preventDefault={update}
data-key="backspace"
formaction="?/update"
name="key"
value="backspace"
>
back
</button>
{#each ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] as row}
<div class="row">
{#each row as letter}
<button
on:click|preventDefault={update}
data-key={letter}
class={classnames[letter]}
disabled={data.guesses[i].length === 5}
formaction="?/update"
name="key"
value={letter}
aria-label="{letter} {description[letter] || ''}"
>
{letter}
</button>
{/each}
</div>
{/each}
</div>
{/if}
</div>
</form>
{#if won}
<div
style="position: absolute; left: 50%; top: 30%"
use:confetti={{
particleCount: $reduced_motion ? 0 : undefined,
force: 0.7,
stageWidth: window.innerWidth,
stageHeight: window.innerHeight,
colors: ['#ff3e00', '#40b3ff', '#676778']
}}
/>
{/if}
<style>
form {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
flex: 1;
}
.how-to-play {
color: var(--color-text);
}
.how-to-play::before {
content: 'i';
display: inline-block;
font-size: 0.8em;
font-weight: 900;
width: 1em;
height: 1em;
padding: 0.2em;
line-height: 1;
border: 1.5px solid var(--color-text);
border-radius: 50%;
text-align: center;
margin: 0 0.5em 0 0;
position: relative;
top: -0.05em;
}
.grid {
--width: min(100vw, 40vh, 380px);
max-width: var(--width);
align-self: center;
justify-self: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.grid .row {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 0.2rem;
margin: 0 0 0.2rem 0;
}
@media (prefers-reduced-motion: no-preference) {
.grid.bad-guess .row.current {
animation: wiggle 0.5s;
}
}
.grid.playing .row.current {
filter: drop-shadow(3px 3px 10px var(--color-bg-0));
}
.letter {
aspect-ratio: 1;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
box-sizing: border-box;
text-transform: lowercase;
border: none;
font-size: calc(0.08 * var(--width));
border-radius: 2px;
background: white;
margin: 0;
color: rgba(0, 0, 0, 0.7);
}
.letter.missing {
background: rgba(255, 255, 255, 0.5);
color: rgba(0, 0, 0, 0.5);
}
.letter.exact {
background: var(--color-theme-2);
color: white;
}
.letter.close {
border: 2px solid var(--color-theme-2);
}
.selected {
outline: 2px solid var(--color-theme-1);
}
.controls {
text-align: center;
justify-content: center;
height: min(18vh, 10rem);
}
.keyboard {
--gap: 0.2rem;
position: relative;
display: flex;
flex-direction: column;
gap: var(--gap);
height: 100%;
}
.keyboard .row {
display: flex;
justify-content: center;
gap: 0.2rem;
flex: 1;
}
.keyboard button,
.keyboard button:disabled {
--size: min(8vw, 4vh, 40px);
background-color: white;
color: black;
width: var(--size);
border: none;
border-radius: 2px;
font-size: calc(var(--size) * 0.5);
margin: 0;
}
.keyboard button.exact {
background: var(--color-theme-2);
color: white;
}
.keyboard button.missing {
opacity: 0.5;
}
.keyboard button.close {
border: 2px solid var(--color-theme-2);
}
.keyboard button:focus {
background: var(--color-theme-1);
color: white;
outline: none;
}
.keyboard button[data-key='enter'],
.keyboard button[data-key='backspace'] {
position: absolute;
bottom: 0;
width: calc(1.5 * var(--size));
height: calc(1 / 3 * (100% - 2 * var(--gap)));
text-transform: uppercase;
font-size: calc(0.3 * var(--size));
padding-top: calc(0.15 * var(--size));
}
.keyboard button[data-key='enter'] {
right: calc(50% + 3.5 * var(--size) + 0.8rem);
}
.keyboard button[data-key='backspace'] {
left: calc(50% + 3.5 * var(--size) + 0.8rem);
}
.keyboard button[data-key='enter']:disabled {
opacity: 0.5;
}
.restart {
width: 100%;
padding: 1rem;
background: rgba(255, 255, 255, 0.5);
border-radius: 2px;
border: none;
}
.restart:focus,
.restart:hover {
background: var(--color-theme-1);
color: white;
outline: none;
}
@keyframes wiggle {
0% {
transform: translateX(0);
}
10% {
transform: translateX(-2px);
}
30% {
transform: translateX(4px);
}
50% {
transform: translateX(-6px);
}
70% {
transform: translateX(+4px);
}
90% {
transform: translateX(-2px);
}
100% {
transform: translateX(0);
}
}
</style>

75
src/routes/admin/game.ts Normal file
View file

@ -0,0 +1,75 @@
import { words, allowed } from './words.server';
export class Game {
index: number;
guesses: string[];
answers: string[];
answer: string;
/**
* Create a game object from the player's cookie, or initialise a new game
*/
constructor(serialized: string | undefined = undefined) {
if (serialized) {
const [index, guesses, answers] = serialized.split('-');
this.index = +index;
this.guesses = guesses ? guesses.split(' ') : [];
this.answers = answers ? answers.split(' ') : [];
} else {
this.index = Math.floor(Math.random() * words.length);
this.guesses = ['', '', '', '', '', ''];
this.answers = [];
}
this.answer = words[this.index];
}
/**
* Update game state based on a guess of a five-letter word. Returns
* true if the guess was valid, false otherwise
*/
enter(letters: string[]) {
const word = letters.join('');
const valid = allowed.has(word);
if (!valid) return false;
this.guesses[this.answers.length] = word;
const available = Array.from(this.answer);
const answer = Array(5).fill('_');
// first, find exact matches
for (let i = 0; i < 5; i += 1) {
if (letters[i] === available[i]) {
answer[i] = 'x';
available[i] = ' ';
}
}
// then find close matches (this has to happen
// in a second step, otherwise an early close
// match can prevent a later exact match)
for (let i = 0; i < 5; i += 1) {
if (answer[i] === '_') {
const index = available.indexOf(letters[i]);
if (index !== -1) {
answer[i] = 'c';
available[index] = ' ';
}
}
}
this.answers.push(answer.join(''));
return true;
}
/**
* Serialize game state so it can be set as a cookie
*/
toString() {
return `${this.index}-${this.guesses.join(' ')}-${this.answers.join(' ')}`;
}
}

View file

@ -0,0 +1,23 @@
import { readable } from 'svelte/store';
import { browser } from '$app/environment';
const reduced_motion_query = '(prefers-reduced-motion: reduce)';
const get_initial_motion_preference = () => {
if (!browser) return false;
return window.matchMedia(reduced_motion_query).matches;
};
export const reduced_motion = readable(get_initial_motion_preference(), (set) => {
if (browser) {
const set_reduced_motion = (event: MediaQueryListEvent) => {
set(event.matches);
};
const media_query_list = window.matchMedia(reduced_motion_query);
media_query_list.addEventListener('change', set_reduced_motion);
return () => {
media_query_list.removeEventListener('change', set_reduced_motion);
};
}
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,97 @@
<script lang="ts">
import EthicsWeb from './EthicsWeb.svelte';
import EthicsBar from './EthicsBar.svelte';
import PopsPie from './PopsPie.svelte';
import EmpireStats from './EmpireStats.svelte';
export let data: {
popsData: Array<number>;
ethicsData: Array<Array<number>>;
empireData: number;
};
</script>
<svelte:head>
<title>Game 15 Graphs</title>
<meta name="description" content="Graphs for Chellaris Game 15" />
</svelte:head>
<div class="fullscreen-margin">
<div class="half-vertical">
<div class="two-thirds-horizontal">
<EthicsWeb {data} />
</div>
<div class="one-third-horizontal">
<EthicsBar {data} />
</div>
</div>
<div class="half-vertical">
<div class="half-horizontal">
<EmpireStats {data} />
</div>
<div class="half-horizontal">
<PopsPie {data} />
</div>
</div>
</div>
<style>
.fullscreen-margin {
display: flex;
flex-direction: row;
min-height: 100%;
flex-grow: 1;
}
.half-vertical {
display: flex;
flex-direction: column;
width: 50%;
max-width: 50%;
min-height: 100%;
max-height: 100%;
justify-content: center;
}
.half-horizontal {
display: flex;
flex-direction: row;
width: 100%;
max-width: 100%;
min-height: 50%;
max-height: 50%;
justify-content: center;
}
.one-third-horizontal {
display: flex;
flex-direction: row;
width: 100%;
max-width: 100%;
min-height: 34%;
max-height: 34%;
justify-content: center;
}
.two-thirds-horizontal {
display: flex;
flex-direction: row;
width: 100%;
max-width: 100%;
min-height: 66%;
max-height: 66%;
justify-content: center;
}
</style>

View file

@ -0,0 +1,14 @@
/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
const apiBaseUrl = 'https://www.chellaris.net/api/v1';
const popsRet: { speciesArray: Array<number> } = await (await fetch(apiBaseUrl + '/species')).json();
const popsData: Array<number> = popsRet.speciesArray;
const ethicsRet: { sheetData: Array<Array<number>> } = await (await fetch(apiBaseUrl + '/ethics')).json();
const ethicsData: Array<Array<number>> = ethicsRet.sheetData;
const empireRet: { empireCount: number } = await (await fetch(apiBaseUrl + '/empires')).json();
const empireData: number = empireRet.empireCount;
return { popsData, ethicsData, empireData }
}

View file

@ -0,0 +1,29 @@
<script lang="ts">
export let data: {
popsData: Array<number>;
ethicsData: Array<Array<number>>;
empireData: number;
};
</script>
<div class="chart">
<p>
There are {data.empireData} Empires signed up for Game 15.
</p>
</div>
<style>
.chart {
display: flex;
flex-direction: column;
min-width: 100%;
max-width: 100%;
min-height: 100%;
max-height: 100%;
justify-content: center;
align-items: center;
}
</style>

View file

@ -0,0 +1,112 @@
<script lang="ts">
import { Bar } from 'svelte-chartjs';
import { Ethics, EthicsDataLegacy } from '$lib/types/stellaris';
import { Chart as ChartJS, registerables } from 'chart.js';
export let data: {
popsData: Array<number>;
ethicsData: Array<Array<number>>;
empireData: number;
};
ChartJS.register(...registerables);
const parseEthicsData = (data: Array<Array<number>>) => {
let parsedEthicsData: Array<EthicsDataLegacy> = new Array<EthicsDataLegacy>();
Object.keys(Ethics).forEach((ethic: number | string) => {
if (Number.isInteger(Number(ethic))) {
parsedEthicsData[Number(ethic)] = new EthicsDataLegacy(data[Number(ethic)]);
}
});
return parsedEthicsData;
};
const ethicsData: Array<EthicsDataLegacy> = parseEthicsData(data.ethicsData);
let sumArray = new Array<number>();
let labelArray = new Array<string>();
ethicsData.forEach((ethicsData, index) => {
sumArray[index] = ethicsData.sum(false);
labelArray[index] = Ethics[index];
});
let zipped: Array<{ sums: number; labels: string }> = [];
for (let i = 0; i < ethicsData.length; i++) {
zipped.push({ sums: ethicsData[i].sum(false), labels: Ethics[i] });
}
zipped.sort((a: { sums: number; labels: string }, b: { sums: number; labels: string }) =>
a.sums > b.sums ? -1 : 1
);
for (let i = 0; i < zipped.length; i++) {
let z = zipped[i];
(sumArray[i] = z.sums), (labelArray[i] = z.labels);
}
let scaleLimit = Number((sumArray[0] * 1.2).toFixed(0));
scaleLimit % 2 == 0 ? (scaleLimit = scaleLimit) : (scaleLimit = scaleLimit + 1);
const chart_data = {
labels: labelArray,
datasets: [
{
label: 'Portrait Picks',
data: sumArray,
backgroundColor: ['rgb(3, 99, 143)'],
hoverOffset: 4
}
]
};
const options = {
plugins: {
scales: {
y: {
beginAtZero: true,
max: scaleLimit
}
},
tooltip: {
callbacks: {
label: function (ctx: any) {
return (
((ctx.dataset.data[ctx.dataIndex] * 100) / data.empireData).toFixed(1) +
'% | ' +
ctx.dataset.data[ctx.dataIndex] +
' / ' +
data.empireData
);
}
}
},
legend: {
display: false
},
datalabels: {
display: false
}
}
};
</script>
<div class="chart">
<Bar data={chart_data} {options} />
</div>
<style>
.chart {
display: flex;
flex-direction: column;
min-width: 100%;
max-width: 100%;
min-height: 100%;
max-height: 100%;
justify-content: center;
align-items: center;
}
</style>

View file

@ -0,0 +1,183 @@
<script lang="ts">
import { writable, type Writable } from "svelte/store";
import { Radar } from 'svelte-chartjs';
import { Ethics, EthicsDataLegacy } from '$lib/types/stellaris';
import { Chart as ChartJS, registerables } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
export let data: {
popsData: Array<number>;
ethicsData: Array<Array<number>>;
empireData: number;
};
ChartJS.register(...registerables, ChartDataLabels);
const parseEthicsData = (data: Array<Array<number>>) => {
let parsedEthicsData: Array<EthicsDataLegacy> = new Array<EthicsDataLegacy>();
Object.keys(Ethics).forEach((ethic: number | string) => {
if (Number.isInteger(Number(ethic))) {
parsedEthicsData[Number(ethic)] = new EthicsDataLegacy(data[Number(ethic)]);
}
});
return parsedEthicsData;
};
let weighted: boolean;
let store: Writable<string>;
if (typeof localStorage !== "undefined") {
store = writable(localStorage.getItem("weighted") || "true");
store.subscribe($ => weighted = JSON.parse($))()
}
else {
weighted = true;
}
const ethicsData: Array<EthicsDataLegacy> = parseEthicsData(data.ethicsData);
let sumTotalArray = new Array<number>();
let sumRegularArray = new Array<number>();
const labelsArray = [
Ethics.Egalitarian,
Ethics.Militarist,
Ethics.Xenophobe,
Ethics.Competitive,
Ethics.Elitist,
Ethics.Materialist,
Ethics.Ecologist,
Ethics.Authoritarian,
Ethics.Pacifist,
Ethics.Xenophile,
Ethics.Cooperative,
Ethics.Pluralist,
Ethics.Spiritualist,
Ethics.Industrialist
];
labelsArray.forEach((ethic: Ethics, index: number) => {
sumTotalArray[index] = ethicsData[ethic].sum(weighted);
sumRegularArray[index] = ethicsData[ethic].sumRegular();
});
let scaleLimit = 0;
sumTotalArray.forEach((elem) => {
if (elem >= scaleLimit) {
scaleLimit = elem + 1;
}
});
scaleLimit % 2 == 0 ? (scaleLimit = scaleLimit) : (scaleLimit = scaleLimit + 1);
const chart_data = {
labels: labelsArray.map((ethic) => Ethics[ethic]),
datasets: [
{
label: 'Total Ethics',
data: sumTotalArray,
fill: true,
backgroundColor: '#254A6FAA',
borderColor: '#356A9F',
radius: 3
},
{
label: 'Regular Ethics',
data: sumRegularArray,
fill: true,
backgroundColor: '#000000AA',
borderColor: '#254A6F',
radius: 3
}
]
};
const options = {
plugins: {
tooltip: {
callbacks: {
label: (ctx: any) =>
`${ctx.dataset.label}: ${ctx.dataset.data[ctx.dataIndex]} ${
weighted ? 'Points' : 'Picks'
}`
}
},
datalabels: {
display: false
}
},
legend: { display: true },
elements: {
line: {
borderWidth: 3
}
},
scales: {
r: {
max: scaleLimit,
min: 0,
ticks: {
display: false
},
grid: {
color: 'grey'
},
angleLines: {
color: 'grey'
}
}
}
};
function updateChartData() {
labelsArray.forEach((ethic: Ethics, index: number) => {
sumTotalArray[index] = ethicsData[ethic].sum(weighted);
});
scaleLimit = 0;
sumTotalArray.forEach((elem) => {
if (elem >= scaleLimit) {
scaleLimit = elem + 1;
}
});
scaleLimit % 5 == 0 ? (scaleLimit = scaleLimit) : (scaleLimit = scaleLimit + 5 - scaleLimit % 5);
chart_data.datasets[0].data = sumTotalArray;
options.scales.r.max = scaleLimit;
if (typeof localStorage !== "undefined") {
console.log("Set Store to ", weighted)
localStorage.setItem("weighted", weighted.toString())
}
}
</script>
<div class="chart">
<Radar data={chart_data} {options} />
<label>
<input type="checkbox" bind:checked={weighted} on:change={updateChartData} />
Use weighted Ethics
</label>
</div>
<style>
.chart {
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 1rem;
min-width: 100%;
max-width: 100%;
min-height: 100%;
max-height: 100%;
justify-content: center;
align-items: center;
}
</style>

View file

@ -0,0 +1,98 @@
<script lang="ts">
import { Pie } from 'svelte-chartjs';
import { Species } from '$lib/types/stellaris';
import { Chart as ChartJS, registerables } from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
export let data: {
popsData: Array<number>;
ethicsData: Array<Array<number>>;
empireData: number;
};
ChartJS.register(...registerables, ChartDataLabels);
const chart_data = {
labels: data.popsData.map((_elem: any, idx: number) => {
return Species[idx];
}),
datasets: [
{
label: 'Portrait Picks',
data: data.popsData,
backgroundColor: [
'rgb(255, 200, 200)',
'rgb(88, 47, 0)',
'rgb(255, 205, 86)',
'rgb(255, 255, 255)',
'rgb(228, 120, 25)',
'rgb(0, 247, 255)',
'rgb(252, 1, 197)',
'rgb(120, 205, 120)',
'rgb(255, 60, 60)',
'rgb(120, 60, 120)',
'rgb(54, 162, 235)',
'rgb(0, 255, 60)',
'rgb(120, 120, 120)'
],
hoverOffset: 4
}
]
};
const options = {
plugins: {
tooltip: {
callbacks: {
label: function (ctx: any) {
let sum = 0;
ctx.dataset.data.map((elem: number) => {
sum = sum + Number(elem);
});
return (
((ctx.dataset.data[ctx.dataIndex] * 100) / sum).toFixed(1) +
'% | ' +
ctx.dataset.data[ctx.dataIndex] +
' / ' +
sum
);
}
}
},
legend: {
display: false
},
datalabels: {
formatter: (value: number, context: any) => {
if (value > 0) {
return Species[context.dataIndex];
} else {
return '';
}
},
color: 'black'
}
}
};
</script>
<div class="chart">
<Pie data={chart_data} {options} />
</div>
<style>
.chart {
display: flex;
flex-direction: column;
min-width: 100%;
max-width: 100%;
min-height: 100%;
max-height: 100%;
justify-content: center;
align-items: center;
}
</style>

View file

@ -0,0 +1,10 @@
<svelte:head>
<title>Empire Sign-Up</title>
<meta name="description" content="Empire Sign-Up" />
</svelte:head>
<div class="text-column">
<h1>Empire Sign-Up: Not yet implemented</h1>
<p>For now only Sign-Up statistics and the backend have been migrated.</p>
</div>

View file

@ -0,0 +1,9 @@
import { dev } from '$app/environment';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement
export const csr = dev;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in production
export const prerender = true;

99
src/routes/styles.css Normal file
View file

@ -0,0 +1,99 @@
@import '@fontsource/fira-mono';
:root {
--font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'Fira Mono', monospace;
--color-bg: #1a1a1a;
--color-theme-1: #ff3e00;
--color-theme-2: #4075a6;
--color-text: rgba(255, 255, 255, 0.7);
--column-width: 42rem;
--column-margin-top: 4rem;
font-family: var(--font-body);
color: var(--color-text);
}
body {
min-height: 100vh;
margin: 0;
background-attachment: fixed;
background-color: var(--color-bg);
background-size: 100vw 100vh;
}
h1,
h2,
p {
font-weight: 400;
}
p {
line-height: 1.5;
}
a {
color: var(--color-theme-1);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
font-size: 2rem;
text-align: center;
}
h2 {
font-size: 1rem;
}
pre {
font-size: 16px;
font-family: var(--font-mono);
background-color: rgba(255, 255, 255, 0.45);
border-radius: 3px;
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
padding: 0.5em;
overflow-x: auto;
color: var(--color-text);
}
.text-column {
display: flex;
max-width: 48rem;
flex: 0.6;
flex-direction: column;
justify-content: center;
margin: 0 auto;
}
input,
button {
font-size: inherit;
font-family: inherit;
}
button:focus:not(:focus-visible) {
outline: none;
}
@media (min-width: 720px) {
h1 {
font-size: 2.4rem;
}
}
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

3
static/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

23
svelte.config.js Normal file
View file

@ -0,0 +1,23 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
},
optimizeDeps: {
exclude: ['svelte-chartjs']
},
};
export default config;

17
tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
vite.config.ts Normal file
View file

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});

1663
yarn.lock Normal file

File diff suppressed because it is too large Load diff