Began implementing necessary API calls
This commit is contained in:
		@@ -16,7 +16,7 @@
 | 
				
			|||||||
				"ms-python.pylint"
 | 
									"ms-python.pylint"
 | 
				
			||||||
			]
 | 
								]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Features to add to the dev container. More info: https://containers.dev/features.
 | 
						// Features to add to the dev container. More info: https://containers.dev/features.
 | 
				
			||||||
	// "features": {},
 | 
						// "features": {},
 | 
				
			||||||
@@ -25,7 +25,7 @@
 | 
				
			|||||||
	// "forwardPorts": [],
 | 
						// "forwardPorts": [],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Use 'postCreateCommand' to run commands after the container is created.
 | 
						// Use 'postCreateCommand' to run commands after the container is created.
 | 
				
			||||||
	// "postCreateCommand": "pip3 install --user -r requirements.txt",
 | 
						"postCreateCommand": "pip3 install --user -r requirements.txt",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Configure tool-specific properties.
 | 
						// Configure tool-specific properties.
 | 
				
			||||||
	// "customizations": {},
 | 
						// "customizations": {},
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								mlstmyfasta.client/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								mlstmyfasta.client/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,24 +0,0 @@
 | 
				
			|||||||
test-results
 | 
					 | 
				
			||||||
node_modules
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Output
 | 
					 | 
				
			||||||
.output
 | 
					 | 
				
			||||||
.vercel
 | 
					 | 
				
			||||||
.netlify
 | 
					 | 
				
			||||||
.wrangler
 | 
					 | 
				
			||||||
/.svelte-kit
 | 
					 | 
				
			||||||
/build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# OS
 | 
					 | 
				
			||||||
.DS_Store
 | 
					 | 
				
			||||||
Thumbs.db
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Env
 | 
					 | 
				
			||||||
.env
 | 
					 | 
				
			||||||
.env.*
 | 
					 | 
				
			||||||
!.env.example
 | 
					 | 
				
			||||||
!.env.test
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Vite
 | 
					 | 
				
			||||||
vite.config.js.timestamp-*
 | 
					 | 
				
			||||||
vite.config.ts.timestamp-*
 | 
					 | 
				
			||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
engine-strict=true
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
# Package Managers
 | 
					 | 
				
			||||||
package-lock.json
 | 
					 | 
				
			||||||
pnpm-lock.yaml
 | 
					 | 
				
			||||||
yarn.lock
 | 
					 | 
				
			||||||
@@ -1,15 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
	"useTabs": true,
 | 
					 | 
				
			||||||
	"singleQuote": true,
 | 
					 | 
				
			||||||
	"trailingComma": "none",
 | 
					 | 
				
			||||||
	"printWidth": 100,
 | 
					 | 
				
			||||||
	"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
 | 
					 | 
				
			||||||
	"overrides": [
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"files": "*.svelte",
 | 
					 | 
				
			||||||
			"options": {
 | 
					 | 
				
			||||||
				"parser": "svelte"
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
# sv
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 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
 | 
					 | 
				
			||||||
npx sv create
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# create a new project in my-app
 | 
					 | 
				
			||||||
npx sv create 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://svelte.dev/docs/kit/adapters) for your target environment.
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
import { expect, test } from '@playwright/test';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test('home page has expected h1', async ({ page }) => {
 | 
					 | 
				
			||||||
	await page.goto('/');
 | 
					 | 
				
			||||||
	await expect(page.locator('h1')).toBeVisible();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
import prettier from 'eslint-config-prettier';
 | 
					 | 
				
			||||||
import js from '@eslint/js';
 | 
					 | 
				
			||||||
import { includeIgnoreFile } from '@eslint/compat';
 | 
					 | 
				
			||||||
import svelte from 'eslint-plugin-svelte';
 | 
					 | 
				
			||||||
import globals from 'globals';
 | 
					 | 
				
			||||||
import { fileURLToPath } from 'node:url';
 | 
					 | 
				
			||||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** @type {import('eslint').Linter.Config[]} */
 | 
					 | 
				
			||||||
export default [
 | 
					 | 
				
			||||||
	includeIgnoreFile(gitignorePath),
 | 
					 | 
				
			||||||
	js.configs.recommended,
 | 
					 | 
				
			||||||
	...svelte.configs['flat/recommended'],
 | 
					 | 
				
			||||||
	prettier,
 | 
					 | 
				
			||||||
	...svelte.configs['flat/prettier'],
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		languageOptions: {
 | 
					 | 
				
			||||||
			globals: {
 | 
					 | 
				
			||||||
				...globals.browser,
 | 
					 | 
				
			||||||
				...globals.node
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
@@ -1,19 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
	"extends": "./.svelte-kit/tsconfig.json",
 | 
					 | 
				
			||||||
	"compilerOptions": {
 | 
					 | 
				
			||||||
		"allowJs": true,
 | 
					 | 
				
			||||||
		"checkJs": true,
 | 
					 | 
				
			||||||
		"esModuleInterop": true,
 | 
					 | 
				
			||||||
		"forceConsistentCasingInFileNames": true,
 | 
					 | 
				
			||||||
		"resolveJsonModule": true,
 | 
					 | 
				
			||||||
		"skipLibCheck": true,
 | 
					 | 
				
			||||||
		"sourceMap": true,
 | 
					 | 
				
			||||||
		"strict": true,
 | 
					 | 
				
			||||||
		"moduleResolution": "bundler"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
 | 
					 | 
				
			||||||
	// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// 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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										4694
									
								
								mlstmyfasta.client/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4694
									
								
								mlstmyfasta.client/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,45 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
	"name": "mlstmyfasta.client",
 | 
					 | 
				
			||||||
	"private": true,
 | 
					 | 
				
			||||||
	"version": "0.0.1",
 | 
					 | 
				
			||||||
	"type": "module",
 | 
					 | 
				
			||||||
	"scripts": {
 | 
					 | 
				
			||||||
		"dev": "vite dev",
 | 
					 | 
				
			||||||
		"build": "vite build",
 | 
					 | 
				
			||||||
		"preview": "vite preview",
 | 
					 | 
				
			||||||
		"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
 | 
					 | 
				
			||||||
		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
 | 
					 | 
				
			||||||
		"format": "prettier --write .",
 | 
					 | 
				
			||||||
		"lint": "prettier --check . && eslint .",
 | 
					 | 
				
			||||||
		"test:unit": "vitest",
 | 
					 | 
				
			||||||
		"test": "npm run test:unit -- --run && npm run test:e2e",
 | 
					 | 
				
			||||||
		"test:e2e": "playwright test"
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	"devDependencies": {
 | 
					 | 
				
			||||||
		"@eslint/compat": "^1.2.3",
 | 
					 | 
				
			||||||
		"@fontsource/fira-mono": "^5.0.0",
 | 
					 | 
				
			||||||
		"@neoconfetti/svelte": "^2.0.0",
 | 
					 | 
				
			||||||
		"@playwright/test": "^1.45.3",
 | 
					 | 
				
			||||||
		"@sveltejs/adapter-static": "^3.0.6",
 | 
					 | 
				
			||||||
		"@sveltejs/kit": "^2.0.0",
 | 
					 | 
				
			||||||
		"@sveltejs/vite-plugin-svelte": "^4.0.0",
 | 
					 | 
				
			||||||
		"autoprefixer": "^10.4.20",
 | 
					 | 
				
			||||||
		"eslint": "^9.7.0",
 | 
					 | 
				
			||||||
		"eslint-config-prettier": "^9.1.0",
 | 
					 | 
				
			||||||
		"eslint-plugin-svelte": "^2.36.0",
 | 
					 | 
				
			||||||
		"globals": "^15.0.0",
 | 
					 | 
				
			||||||
		"prettier": "^3.3.2",
 | 
					 | 
				
			||||||
		"prettier-plugin-svelte": "^3.2.6",
 | 
					 | 
				
			||||||
		"prettier-plugin-tailwindcss": "^0.6.5",
 | 
					 | 
				
			||||||
		"svelte": "^5.0.0",
 | 
					 | 
				
			||||||
		"svelte-check": "^4.0.0",
 | 
					 | 
				
			||||||
		"tailwindcss": "^3.4.9",
 | 
					 | 
				
			||||||
		"typescript": "^5.0.0",
 | 
					 | 
				
			||||||
		"vite": "^5.4.11",
 | 
					 | 
				
			||||||
		"vitest": "^2.0.4"
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	"dependencies": {
 | 
					 | 
				
			||||||
		"@tailwindcss/forms": "^0.5.9",
 | 
					 | 
				
			||||||
		"@tailwindcss/typography": "^0.5.15"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,10 +0,0 @@
 | 
				
			|||||||
import { defineConfig } from '@playwright/test';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default defineConfig({
 | 
					 | 
				
			||||||
	webServer: {
 | 
					 | 
				
			||||||
		command: 'npm run build && npm run preview',
 | 
					 | 
				
			||||||
		port: 4173
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	testDir: 'e2e'
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
export default {
 | 
					 | 
				
			||||||
	plugins: {
 | 
					 | 
				
			||||||
		tailwindcss: {},
 | 
					 | 
				
			||||||
		autoprefixer: {}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,110 +0,0 @@
 | 
				
			|||||||
@import 'tailwindcss/base';
 | 
					 | 
				
			||||||
@import 'tailwindcss/components';
 | 
					 | 
				
			||||||
@import 'tailwindcss/utilities';
 | 
					 | 
				
			||||||
@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-0: rgb(202, 216, 228);
 | 
					 | 
				
			||||||
	--color-bg-1: hsl(209, 36%, 86%);
 | 
					 | 
				
			||||||
	--color-bg-2: hsl(224, 44%, 95%);
 | 
					 | 
				
			||||||
	--color-theme-1: #ff3e00;
 | 
					 | 
				
			||||||
	--color-theme-2: #4075a6;
 | 
					 | 
				
			||||||
	--color-text: rgba(0, 0, 0, 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-1);
 | 
					 | 
				
			||||||
	background-size: 100vw 100vh;
 | 
					 | 
				
			||||||
	background-image: radial-gradient(
 | 
					 | 
				
			||||||
			50% 50% at 50% 50%,
 | 
					 | 
				
			||||||
			rgba(255, 255, 255, 0.75) 0%,
 | 
					 | 
				
			||||||
			rgba(255, 255, 255, 0) 100%
 | 
					 | 
				
			||||||
		),
 | 
					 | 
				
			||||||
		linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										13
									
								
								mlstmyfasta.client/src/app.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								mlstmyfasta.client/src/app.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,13 +0,0 @@
 | 
				
			|||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
 | 
					 | 
				
			||||||
// for information about these interfaces
 | 
					 | 
				
			||||||
declare global {
 | 
					 | 
				
			||||||
	namespace App {
 | 
					 | 
				
			||||||
		// interface Error {}
 | 
					 | 
				
			||||||
		// interface Locals {}
 | 
					 | 
				
			||||||
		// interface PageData {}
 | 
					 | 
				
			||||||
		// interface PageState {}
 | 
					 | 
				
			||||||
		// interface Platform {}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {};
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
<!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, initial-scale=1" />
 | 
					 | 
				
			||||||
		%sveltekit.head%
 | 
					 | 
				
			||||||
	</head>
 | 
					 | 
				
			||||||
	<body data-sveltekit-preload-data="hover">
 | 
					 | 
				
			||||||
		<div style="display: contents">%sveltekit.body%</div>
 | 
					 | 
				
			||||||
	</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
import { describe, it, expect } from 'vitest';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('sum test', () => {
 | 
					 | 
				
			||||||
	it('adds 1 + 2 to equal 3', () => {
 | 
					 | 
				
			||||||
		expect(1 + 2).toBe(3);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,58 +0,0 @@
 | 
				
			|||||||
<script>
 | 
					 | 
				
			||||||
	import Header from './Header.svelte';
 | 
					 | 
				
			||||||
	import '../app.css';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** @type {{children: import('svelte').Snippet}} */
 | 
					 | 
				
			||||||
	let { children } = $props();
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="app">
 | 
					 | 
				
			||||||
	<Header />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<main>
 | 
					 | 
				
			||||||
		{@render children()}
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<footer>
 | 
					 | 
				
			||||||
		<p>
 | 
					 | 
				
			||||||
			visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to learn about SvelteKit
 | 
					 | 
				
			||||||
		</p>
 | 
					 | 
				
			||||||
	</footer>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
	.app {
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		flex-direction: column;
 | 
					 | 
				
			||||||
		min-height: 100vh;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	main {
 | 
					 | 
				
			||||||
		flex: 1;
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		flex-direction: column;
 | 
					 | 
				
			||||||
		padding: 1rem;
 | 
					 | 
				
			||||||
		width: 100%;
 | 
					 | 
				
			||||||
		max-width: 64rem;
 | 
					 | 
				
			||||||
		margin: 0 auto;
 | 
					 | 
				
			||||||
		box-sizing: border-box;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	footer {
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		flex-direction: column;
 | 
					 | 
				
			||||||
		justify-content: center;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		padding: 12px;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	footer a {
 | 
					 | 
				
			||||||
		font-weight: bold;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (min-width: 480px) {
 | 
					 | 
				
			||||||
		footer {
 | 
					 | 
				
			||||||
			padding: 12px 0;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
// 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;
 | 
					 | 
				
			||||||
@@ -1,59 +0,0 @@
 | 
				
			|||||||
<script>
 | 
					 | 
				
			||||||
	import Counter from './Counter.svelte';
 | 
					 | 
				
			||||||
	import welcome from '$lib/images/svelte-welcome.webp';
 | 
					 | 
				
			||||||
	import welcomeFallback 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={welcomeFallback} 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>
 | 
					 | 
				
			||||||
@@ -1,110 +0,0 @@
 | 
				
			|||||||
<script>
 | 
					 | 
				
			||||||
	import { spring } from 'svelte/motion';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	let count = $state(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// svelte-ignore state_referenced_locally
 | 
					 | 
				
			||||||
	const displayedCount = spring(count);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	$effect(() => {
 | 
					 | 
				
			||||||
		displayedCount.set(count);
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
	let offset = $derived(modulo($displayedCount, 1));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @param {number} n
 | 
					 | 
				
			||||||
	 * @param {number} m
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	function modulo(n, m) {
 | 
					 | 
				
			||||||
		// handle negative numbers
 | 
					 | 
				
			||||||
		return ((n % m) + m) % m;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="counter">
 | 
					 | 
				
			||||||
	<button onclick={() => (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($displayedCount + 1)}</strong>
 | 
					 | 
				
			||||||
			<strong>{Math.floor($displayedCount)}</strong>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<button onclick={() => (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>
 | 
					 | 
				
			||||||
@@ -1,129 +0,0 @@
 | 
				
			|||||||
<script>
 | 
					 | 
				
			||||||
	import { page } from '$app/stores';
 | 
					 | 
				
			||||||
	import logo from '$lib/images/svelte-logo.svg';
 | 
					 | 
				
			||||||
	import github from '$lib/images/github.svg';
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<header>
 | 
					 | 
				
			||||||
	<div class="corner">
 | 
					 | 
				
			||||||
		<a href="https://svelte.dev/docs/kit">
 | 
					 | 
				
			||||||
			<img src={logo} alt="SvelteKit" />
 | 
					 | 
				
			||||||
		</a>
 | 
					 | 
				
			||||||
	</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="/">Home</a>
 | 
					 | 
				
			||||||
			</li>
 | 
					 | 
				
			||||||
			<li aria-current={$page.url.pathname === '/about' ? 'page' : undefined}>
 | 
					 | 
				
			||||||
				<a href="/about">About</a>
 | 
					 | 
				
			||||||
			</li>
 | 
					 | 
				
			||||||
			<li aria-current={$page.url.pathname.startsWith('/sverdle') ? 'page' : undefined}>
 | 
					 | 
				
			||||||
				<a href="/sverdle">Sverdle</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={github} alt="GitHub" />
 | 
					 | 
				
			||||||
		</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(255, 255, 255, 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>
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
@@ -1,26 +0,0 @@
 | 
				
			|||||||
<svelte:head>
 | 
					 | 
				
			||||||
	<title>About</title>
 | 
					 | 
				
			||||||
	<meta name="description" content="About this app" />
 | 
					 | 
				
			||||||
</svelte:head>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="text-column">
 | 
					 | 
				
			||||||
	<h1>About this app</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>
 | 
					 | 
				
			||||||
		This is a <a href="https://svelte.dev/docs/kit">SvelteKit</a> app. You can make your own by typing
 | 
					 | 
				
			||||||
		the following into your command line and following the prompts:
 | 
					 | 
				
			||||||
	</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<pre>npx sv create</pre>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>
 | 
					 | 
				
			||||||
		The page you're looking at is purely static HTML, with no client-side interactivity needed.
 | 
					 | 
				
			||||||
		Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
 | 
					 | 
				
			||||||
		the devtools network panel and reloading.
 | 
					 | 
				
			||||||
	</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>
 | 
					 | 
				
			||||||
		The <a href="/sverdle">Sverdle</a> page illustrates SvelteKit's data loading and form handling. Try
 | 
					 | 
				
			||||||
		using it with JavaScript disabled!
 | 
					 | 
				
			||||||
	</p>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
import { fail } from '@sveltejs/kit';
 | 
					 | 
				
			||||||
import { Game } from './game';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** @satisfies {import('./$types').PageServerLoad} */
 | 
					 | 
				
			||||||
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 {import('./$types').Actions} */
 | 
					 | 
				
			||||||
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(), { path: '/' });
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * 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 = /** @type {string[]} */ (data.getAll('guess'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!game.enter(guess)) {
 | 
					 | 
				
			||||||
			return fail(400, { badGuess: true });
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cookies.set('sverdle', game.toString(), { path: '/' });
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	restart: async ({ cookies }) => {
 | 
					 | 
				
			||||||
		cookies.delete('sverdle', { path: '/' });
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,420 +0,0 @@
 | 
				
			|||||||
<script>
 | 
					 | 
				
			||||||
	import { enhance } from '$app/forms';
 | 
					 | 
				
			||||||
	import { confetti } from '@neoconfetti/svelte';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	import { reducedMotion } from './reduced-motion';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @typedef {Object} Props
 | 
					 | 
				
			||||||
	 * @property {import('./$types').PageData} data
 | 
					 | 
				
			||||||
	 * @property {import('./$types').ActionData} form
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @type {Props}
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	let { data, form = $bindable() } = $props();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Whether or not the user has won */
 | 
					 | 
				
			||||||
	let won = $derived(data.answers.at(-1) === 'xxxxx');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** The index of the current guess */
 | 
					 | 
				
			||||||
	let i = $derived(won ? -1 : data.answers.length);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** The current guess */
 | 
					 | 
				
			||||||
	// svelte-ignore state_referenced_locally
 | 
					 | 
				
			||||||
	let currentGuess = $state(data.guesses[i] || '');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	$effect(() => {
 | 
					 | 
				
			||||||
		currentGuess = data.guesses[i] || '';
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Whether the current guess can be submitted */
 | 
					 | 
				
			||||||
	let submittable = $derived(currentGuess.length === 5);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const { classnames, description } = $derived.by(() => {
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * A map of classnames for all letters that have been guessed,
 | 
					 | 
				
			||||||
		 * used for styling the keyboard
 | 
					 | 
				
			||||||
		 * @type {Record<string, 'exact' | 'close' | 'missing'>}
 | 
					 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
		let classnames = {};
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * A map of descriptions for all letters that have been guessed,
 | 
					 | 
				
			||||||
		 * used for adding text for assistive technology (e.g. screen readers)
 | 
					 | 
				
			||||||
		 * @type {Record<string, string>}
 | 
					 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
		let 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';
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		return { classnames, description };
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Modify the game state without making a trip to the server,
 | 
					 | 
				
			||||||
	 * if client-side JavaScript is enabled
 | 
					 | 
				
			||||||
	 * @param {MouseEvent} event
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	function update(event) {
 | 
					 | 
				
			||||||
		event.preventDefault();
 | 
					 | 
				
			||||||
		const key = /** @type {HTMLButtonElement} */ (event.target).getAttribute('data-key');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (key === 'backspace') {
 | 
					 | 
				
			||||||
			currentGuess = currentGuess.slice(0, -1);
 | 
					 | 
				
			||||||
			if (form?.badGuess) form.badGuess = false;
 | 
					 | 
				
			||||||
		} else if (currentGuess.length < 5) {
 | 
					 | 
				
			||||||
			currentGuess += key;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Trigger form logic in response to a keydown event, so that
 | 
					 | 
				
			||||||
	 * desktop users can use the keyboard to play the game
 | 
					 | 
				
			||||||
	 * @param {KeyboardEvent} event
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	function keydown(event) {
 | 
					 | 
				
			||||||
		if (event.metaKey) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (event.key === 'Enter' && !submittable) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		document
 | 
					 | 
				
			||||||
			.querySelector(`[data-key="${event.key}" i]`)
 | 
					 | 
				
			||||||
			?.dispatchEvent(new MouseEvent('click', { cancelable: true }));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<svelte:window onkeydown={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 guess = current ? currentGuess : data.guesses[row]}
 | 
					 | 
				
			||||||
					{@const answer = data.answers[row]?.[column]}
 | 
					 | 
				
			||||||
					{@const value = guess?.[column] ?? ''}
 | 
					 | 
				
			||||||
					{@const selected = current && column === guess.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
 | 
					 | 
				
			||||||
					onclick={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
 | 
					 | 
				
			||||||
								onclick={update}
 | 
					 | 
				
			||||||
								data-key={letter}
 | 
					 | 
				
			||||||
								class={classnames[letter]}
 | 
					 | 
				
			||||||
								disabled={submittable}
 | 
					 | 
				
			||||||
								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: $reducedMotion ? 0 : undefined,
 | 
					 | 
				
			||||||
			force: 0.7,
 | 
					 | 
				
			||||||
			stageWidth: window.innerWidth,
 | 
					 | 
				
			||||||
			stageHeight: window.innerHeight,
 | 
					 | 
				
			||||||
			colors: ['#ff3e00', '#40b3ff', '#676778']
 | 
					 | 
				
			||||||
		}}
 | 
					 | 
				
			||||||
	></div>
 | 
					 | 
				
			||||||
{/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>
 | 
					 | 
				
			||||||
@@ -1,72 +0,0 @@
 | 
				
			|||||||
import { words, allowed } from './words.server';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class Game {
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Create a game object from the player's cookie, or initialise a new game
 | 
					 | 
				
			||||||
	 * @param {string | undefined} serialized
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	constructor(serialized = 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 = /** @type {string[]} */ ([]);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		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
 | 
					 | 
				
			||||||
	 * @param {string[]} letters
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	enter(letters) {
 | 
					 | 
				
			||||||
		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(' ')}`;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
@@ -1,95 +0,0 @@
 | 
				
			|||||||
<svelte:head>
 | 
					 | 
				
			||||||
	<title>How to play Sverdle</title>
 | 
					 | 
				
			||||||
	<meta name="description" content="How to play Sverdle" />
 | 
					 | 
				
			||||||
</svelte:head>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="text-column">
 | 
					 | 
				
			||||||
	<h1>How to play Sverdle</h1>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>
 | 
					 | 
				
			||||||
		Sverdle is a clone of <a href="https://www.nytimes.com/games/wordle/index.html">Wordle</a>, the
 | 
					 | 
				
			||||||
		word guessing game. To play, enter a five-letter English word. For example:
 | 
					 | 
				
			||||||
	</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<div class="example">
 | 
					 | 
				
			||||||
		<span class="close">r</span>
 | 
					 | 
				
			||||||
		<span class="missing">i</span>
 | 
					 | 
				
			||||||
		<span class="close">t</span>
 | 
					 | 
				
			||||||
		<span class="missing">z</span>
 | 
					 | 
				
			||||||
		<span class="exact">y</span>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>
 | 
					 | 
				
			||||||
		The <span class="exact">y</span> is in the right place. <span class="close">r</span> and
 | 
					 | 
				
			||||||
		<span class="close">t</span>
 | 
					 | 
				
			||||||
		are the right letters, but in the wrong place. The other letters are wrong, and can be discarded.
 | 
					 | 
				
			||||||
		Let's make another guess:
 | 
					 | 
				
			||||||
	</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<div class="example">
 | 
					 | 
				
			||||||
		<span class="exact">p</span>
 | 
					 | 
				
			||||||
		<span class="exact">a</span>
 | 
					 | 
				
			||||||
		<span class="exact">r</span>
 | 
					 | 
				
			||||||
		<span class="exact">t</span>
 | 
					 | 
				
			||||||
		<span class="exact">y</span>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>This time we guessed right! You have <strong>six</strong> guesses to get the word.</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<p>
 | 
					 | 
				
			||||||
		Unlike the original Wordle, Sverdle runs on the server instead of in the browser, making it
 | 
					 | 
				
			||||||
		impossible to cheat. It uses <code><form></code> and cookies to submit data, meaning you can
 | 
					 | 
				
			||||||
		even play with JavaScript disabled!
 | 
					 | 
				
			||||||
	</p>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
	span {
 | 
					 | 
				
			||||||
		display: inline-flex;
 | 
					 | 
				
			||||||
		justify-content: center;
 | 
					 | 
				
			||||||
		align-items: center;
 | 
					 | 
				
			||||||
		font-size: 0.8em;
 | 
					 | 
				
			||||||
		width: 2.4em;
 | 
					 | 
				
			||||||
		height: 2.4em;
 | 
					 | 
				
			||||||
		background-color: white;
 | 
					 | 
				
			||||||
		box-sizing: border-box;
 | 
					 | 
				
			||||||
		border-radius: 2px;
 | 
					 | 
				
			||||||
		border-width: 2px;
 | 
					 | 
				
			||||||
		color: rgba(0, 0, 0, 0.7);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.missing {
 | 
					 | 
				
			||||||
		background: rgba(255, 255, 255, 0.5);
 | 
					 | 
				
			||||||
		color: rgba(0, 0, 0, 0.5);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.close {
 | 
					 | 
				
			||||||
		border-style: solid;
 | 
					 | 
				
			||||||
		border-color: var(--color-theme-2);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.exact {
 | 
					 | 
				
			||||||
		background: var(--color-theme-2);
 | 
					 | 
				
			||||||
		color: white;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.example {
 | 
					 | 
				
			||||||
		display: flex;
 | 
					 | 
				
			||||||
		justify-content: flex-start;
 | 
					 | 
				
			||||||
		margin: 1rem 0;
 | 
					 | 
				
			||||||
		gap: 0.2rem;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	.example span {
 | 
					 | 
				
			||||||
		font-size: 1.4rem;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	p span {
 | 
					 | 
				
			||||||
		position: relative;
 | 
					 | 
				
			||||||
		border-width: 1px;
 | 
					 | 
				
			||||||
		border-radius: 1px;
 | 
					 | 
				
			||||||
		font-size: 0.4em;
 | 
					 | 
				
			||||||
		transform: scale(2) translate(0, -10%);
 | 
					 | 
				
			||||||
		margin: 0 1em;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
@@ -1,26 +0,0 @@
 | 
				
			|||||||
import { readable } from 'svelte/store';
 | 
					 | 
				
			||||||
import { browser } from '$app/environment';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getInitialMotionPreference = () => {
 | 
					 | 
				
			||||||
	if (!browser) return false;
 | 
					 | 
				
			||||||
	return window.matchMedia(reducedMotionQuery).matches;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const reducedMotion = readable(getInitialMotionPreference(), (set) => {
 | 
					 | 
				
			||||||
	if (browser) {
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @param {MediaQueryListEvent} event
 | 
					 | 
				
			||||||
		 */
 | 
					 | 
				
			||||||
		const setReducedMotion = (event) => {
 | 
					 | 
				
			||||||
			set(event.matches);
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
		const mediaQueryList = window.matchMedia(reducedMotionQuery);
 | 
					 | 
				
			||||||
		mediaQueryList.addEventListener('change', setReducedMotion);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return () => {
 | 
					 | 
				
			||||||
			mediaQueryList.removeEventListener('change', setReducedMotion);
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
@@ -1,3 +0,0 @@
 | 
				
			|||||||
# https://www.robotstxt.org/robotstxt.html
 | 
					 | 
				
			||||||
User-agent: *
 | 
					 | 
				
			||||||
Disallow:
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
import adapter from '@sveltejs/adapter-static';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** @type {import('@sveltejs/kit').Config} */
 | 
					 | 
				
			||||||
const config = {
 | 
					 | 
				
			||||||
	kit: {
 | 
					 | 
				
			||||||
		// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
 | 
					 | 
				
			||||||
		// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
 | 
					 | 
				
			||||||
		// See https://svelte.dev/docs/kit/adapters for more information about adapters.
 | 
					 | 
				
			||||||
		adapter: adapter()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default config;
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
import forms from '@tailwindcss/forms';
 | 
					 | 
				
			||||||
import typography from '@tailwindcss/typography';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** @type {import('tailwindcss').Config} */
 | 
					 | 
				
			||||||
export default {
 | 
					 | 
				
			||||||
	content: ['./src/**/*.{html,js,svelte,ts}'],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	theme: {
 | 
					 | 
				
			||||||
		extend: {}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	plugins: [typography, forms]
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,10 +0,0 @@
 | 
				
			|||||||
import { defineConfig } from 'vitest/config';
 | 
					 | 
				
			||||||
import { sveltekit } from '@sveltejs/kit/vite';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default defineConfig({
 | 
					 | 
				
			||||||
	plugins: [sveltekit()],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	test: {
 | 
					 | 
				
			||||||
		include: ['src/**/*.{test,spec}.{js,ts}']
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
[metadata]
 | 
					 | 
				
			||||||
name = mlstmyfasta.server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[options]
 | 
					 | 
				
			||||||
package_dir =
 | 
					 | 
				
			||||||
    = src
 | 
					 | 
				
			||||||
install_requires =
 | 
					 | 
				
			||||||
    sqlalchemy       ==2.0
 | 
					 | 
				
			||||||
    flask-sqlalchemy ==3.0
 | 
					 | 
				
			||||||
    flask            ==2.3
 | 
					 | 
				
			||||||
    pyvcf3           ==1
 | 
					 | 
				
			||||||
    requests         ==2.29
 | 
					 | 
				
			||||||
    cachier          ==2.1
 | 
					 | 
				
			||||||
    biopython        ==1.81
 | 
					 | 
				
			||||||
    scipy            ==1.10
 | 
					 | 
				
			||||||
    spacy            ==3.4
 | 
					 | 
				
			||||||
    matplotlib       ==3.7
 | 
					 | 
				
			||||||
    cupy             ==12.1
 | 
					 | 
				
			||||||
    en-core-sci-lg   ==0.5.1
 | 
					 | 
				
			||||||
    scispacy         ==0.5.2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tool:pytest]
 | 
					 | 
				
			||||||
pythonpath = src
 | 
					 | 
				
			||||||
testpaths = tests
 | 
					 | 
				
			||||||
log_cli = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[pylint]
 | 
					 | 
				
			||||||
max-line-length = 88
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[isort]
 | 
					 | 
				
			||||||
profile = black
 | 
					 | 
				
			||||||
							
								
								
									
										46
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					[build-system]
 | 
				
			||||||
 | 
					requires = ["setuptools >= 61.0"]
 | 
				
			||||||
 | 
					build-backend = "setuptools.build_meta"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[project]
 | 
				
			||||||
 | 
					name = "mlstmyfasta"
 | 
				
			||||||
 | 
					dynamic = ["version"]
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    "biopython",
 | 
				
			||||||
 | 
					    "requests",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					requires-python = ">=3.11"
 | 
				
			||||||
 | 
					description = "A tool to rapidly fetch fetch MLST profiles given sequences for various diseases."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.pyright]
 | 
				
			||||||
 | 
					extraPaths = ["src"]
 | 
				
			||||||
 | 
					exclude = [
 | 
				
			||||||
 | 
					    "**/node_modules",
 | 
				
			||||||
 | 
					    "**/__pycache__"
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					executionEnvironments = [
 | 
				
			||||||
 | 
					    {root = "src"}
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.setuptools]
 | 
				
			||||||
 | 
					package-dir = {"" = "src"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.pytest.ini_options]
 | 
				
			||||||
 | 
					testpaths = [
 | 
				
			||||||
 | 
					    "tests"
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					pythonpath = [
 | 
				
			||||||
 | 
					    "src"
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					addopts = [
 | 
				
			||||||
 | 
					    "--import-mode=importlib",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					asyncio_mode = "auto"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.pylint.main]
 | 
				
			||||||
 | 
					source-roots = src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.pylint.format]
 | 
				
			||||||
 | 
					# Maximum number of characters on a single line.
 | 
				
			||||||
 | 
					max-line-length = 88
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					requests
 | 
				
			||||||
 | 
					biopython
 | 
				
			||||||
							
								
								
									
										0
									
								
								src/mlstmyfasta/engine/annotations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/mlstmyfasta/engine/annotations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								src/mlstmyfasta/engine/data/MLST.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/mlstmyfasta/engine/data/MLST.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class Allele:
 | 
				
			||||||
 | 
					    allele_name: str
 | 
				
			||||||
 | 
					    allele_variant: str
 | 
				
			||||||
							
								
								
									
										0
									
								
								src/mlstmyfasta/engine/data/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/mlstmyfasta/engine/data/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										12
									
								
								src/mlstmyfasta/engine/data/genomics.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/mlstmyfasta/engine/data/genomics.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class Sequence:
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    sequence: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class SequenceFeature:
 | 
				
			||||||
 | 
					    type: str
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
							
								
								
									
										0
									
								
								src/mlstmyfasta/engine/external/databases/institutpasteur/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/mlstmyfasta/engine/external/databases/institutpasteur/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										13
									
								
								src/mlstmyfasta/engine/external/databases/institutpasteur/query.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/mlstmyfasta/engine/external/databases/institutpasteur/query.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					from mlstmyfasta.engine.data import MLST
 | 
				
			||||||
 | 
					from mlstmyfasta.engine.external.databases.institutpasteur.structures import SequenceRequestBody
 | 
				
			||||||
 | 
					from mlstmyfasta.engine.external.databases.institutpasteur.structures import SequenceResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def query_fasta_mlst_profile(database: str, request_body: SequenceRequestBody):
 | 
				
			||||||
 | 
					    url: str = f"https://bigsdb.pasteur.fr/api/db/{database}/mlst/sequence"
 | 
				
			||||||
 | 
					    request_result = requests.post(url, json=json.dumps(request_body), timeout=10000)
 | 
				
			||||||
 | 
					    seq_result: SequenceResponse = request_result.json()
 | 
				
			||||||
 | 
					    result = list()
 | 
				
			||||||
 | 
					    for exact_match in seq_result.exact_matches:
 | 
				
			||||||
 | 
					        result.append(MLST.Allele())
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/mlstmyfasta/engine/external/databases/institutpasteur/structures.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/mlstmyfasta/engine/external/databases/institutpasteur/structures.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					from typing import Sequence, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class SequenceRequestBody:
 | 
				
			||||||
 | 
					    sequence: str
 | 
				
			||||||
 | 
					    details: Union[None, str]
 | 
				
			||||||
 | 
					    partial_matches: Union[None, bool]
 | 
				
			||||||
 | 
					    base64: Union[None, bool]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class SequenceResponse:
 | 
				
			||||||
 | 
					    exact_matches: Sequence[tuple[str, str]]
 | 
				
			||||||
 | 
					    start: Union[None, int]
 | 
				
			||||||
 | 
					    end: Union[None, int]
 | 
				
			||||||
 | 
					    orientation: Union[None, str]
 | 
				
			||||||
 | 
					    length: Union[None, int]
 | 
				
			||||||
 | 
					    contig: Union[None, str]
 | 
				
			||||||
		Reference in New Issue
	
	Block a user