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
|
.DS_Store
|
||||||
logs
|
node_modules
|
||||||
*.log
|
/build
|
||||||
npm-debug.log*
|
/.svelte-kit
|
||||||
yarn-debug.log*
|
/package
|
||||||
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
|
|
||||||
.env
|
.env
|
||||||
.env.test
|
.env.*
|
||||||
|
!.env.example
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
.vercel
|
||||||
.cache
|
.output
|
||||||
|
vite.config.js.timestamp-*
|
||||||
# next.js build output
|
vite.config.ts.timestamp-*
|
||||||
.next
|
|
||||||
|
|
||||||
# nuxt.js build output
|
|
||||||
.nuxt
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
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