Initial Commit, Feature parity to nextjs version
This commit is contained in:
parent
506751580a
commit
9401bdf9ed
42 changed files with 16511 additions and 88 deletions
13
.eslintignore
Normal file
13
.eslintignore
Normal 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
30
.eslintrc.cjs
Normal 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
98
.gitignore
vendored
|
@ -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
2
.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
engine-strict=true
|
||||
resolution-mode=highest
|
13
.prettierignore
Normal file
13
.prettierignore
Normal 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
9
.prettierrc
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
39
README.md
39
README.md
|
@ -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
38
package.json
Normal 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
12
src/app.d.ts
vendored
Normal 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
12
src/app.html
Normal 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>
|
1
src/lib/images/discord.svg
Normal file
1
src/lib/images/discord.svg
Normal 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 |
1
src/lib/images/svelte-logo.svg
Normal file
1
src/lib/images/svelte-logo.svg
Normal 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 |
BIN
src/lib/images/svelte-welcome.png
Normal file
BIN
src/lib/images/svelte-welcome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 352 KiB |
BIN
src/lib/images/svelte-welcome.webp
Normal file
BIN
src/lib/images/svelte-welcome.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
65
src/lib/types/stellaris.ts
Normal file
65
src/lib/types/stellaris.ts
Normal 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
31
src/routes/+layout.svelte
Normal 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
59
src/routes/+page.svelte
Normal 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
3
src/routes/+page.ts
Normal 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
102
src/routes/Counter.svelte
Normal 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
132
src/routes/Header.svelte
Normal 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>
|
14
src/routes/about/+page.svelte
Normal file
14
src/routes/about/+page.svelte
Normal 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>
|
9
src/routes/about/+page.ts
Normal file
9
src/routes/about/+page.ts
Normal 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;
|
69
src/routes/admin/+page.server.ts
Normal file
69
src/routes/admin/+page.server.ts
Normal 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;
|
406
src/routes/admin/+page.svelte
Normal file
406
src/routes/admin/+page.svelte
Normal 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
75
src/routes/admin/game.ts
Normal 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(' ')}`;
|
||||
}
|
||||
}
|
23
src/routes/admin/reduced-motion.ts
Normal file
23
src/routes/admin/reduced-motion.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
});
|
12980
src/routes/admin/words.server.ts
Normal file
12980
src/routes/admin/words.server.ts
Normal file
File diff suppressed because it is too large
Load diff
97
src/routes/legacy-graphs/+page.svelte
Normal file
97
src/routes/legacy-graphs/+page.svelte
Normal 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>
|
14
src/routes/legacy-graphs/+page.ts
Normal file
14
src/routes/legacy-graphs/+page.ts
Normal 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 }
|
||||
}
|
29
src/routes/legacy-graphs/EmpireStats.svelte
Normal file
29
src/routes/legacy-graphs/EmpireStats.svelte
Normal 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>
|
112
src/routes/legacy-graphs/EthicsBar.svelte
Normal file
112
src/routes/legacy-graphs/EthicsBar.svelte
Normal 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>
|
183
src/routes/legacy-graphs/EthicsWeb.svelte
Normal file
183
src/routes/legacy-graphs/EthicsWeb.svelte
Normal 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>
|
98
src/routes/legacy-graphs/PopsPie.svelte
Normal file
98
src/routes/legacy-graphs/PopsPie.svelte
Normal 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>
|
10
src/routes/sign-up/+page.svelte
Normal file
10
src/routes/sign-up/+page.svelte
Normal 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>
|
9
src/routes/sign-up/+page.ts
Normal file
9
src/routes/sign-up/+page.ts
Normal 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
99
src/routes/styles.css
Normal 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
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
23
svelte.config.js
Normal file
23
svelte.config.js
Normal 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
17
tsconfig.json
Normal 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
6
vite.config.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
Reference in a new issue