Added WIP website
This commit is contained in:
commit
d6e1117d34
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
|
20
.eslintrc.cjs
Normal file
20
.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||||
|
plugins: ['svelte3', '@typescript-eslint'],
|
||||||
|
ignorePatterns: ['*.cjs'],
|
||||||
|
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||||
|
settings: {
|
||||||
|
'svelte3/typescript': () => require('typescript')
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2020
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
}
|
||||||
|
};
|
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.vercel
|
||||||
|
.output
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
72
.idea/workspace.xml
generated
Normal file
72
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="553038dc-ba96-4d5d-be6c-80b0d07ef67b" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ComposerSettings">
|
||||||
|
<execution />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="TypeScript File" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectId" id="2Ju38GOQc6CXKcUpV0lQIy2fZBr" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"WebServerToolWindowFactoryState": "false",
|
||||||
|
"last_opened_file_path": "/mnt/Git-Projects/blackbirdchess/blackbirdchess-website-wip",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"prettierjs.PrettierConfiguration.Package": "/mnt/Git-Projects/blackbirdchess/blackbirdchess-website-wip/node_modules/prettier",
|
||||||
|
"ts.external.directory.path": "/mnt/Git-Projects/blackbirdchess/blackbirdchess-website-wip/node_modules/typescript/lib",
|
||||||
|
"vue.rearranger.settings.migration": "true",
|
||||||
|
"vuejs.nuxt.types-notification-shown": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/pages" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="553038dc-ba96-4d5d-be6c-80b0d07ef67b" name="Changes" comment="" />
|
||||||
|
<created>1672913979211</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1672913979211</updated>
|
||||||
|
<workItem from="1672913980315" duration="6985000" />
|
||||||
|
<workItem from="1672923169638" duration="3621000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
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" } }]
|
||||||
|
}
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# 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.
|
5998
package-lock.json
generated
Normal file
5998
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
package.json
Normal file
40
package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "blackbirdchess-website-wip",
|
||||||
|
"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",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"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": "^1.0.0",
|
||||||
|
"@sveltejs/kit": "^1.0.0",
|
||||||
|
"@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-svelte3": "^4.0.0",
|
||||||
|
"prettier": "^2.8.0",
|
||||||
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
|
"svelte": "^3.54.0",
|
||||||
|
"svelte-check": "^2.9.2",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"vite": "^4.0.0",
|
||||||
|
"vitest": "^0.25.3",
|
||||||
|
"postcss": "^8.4.14",
|
||||||
|
"postcss-load-config": "^4.0.1",
|
||||||
|
"svelte-preprocess": "^4.10.7",
|
||||||
|
"autoprefixer": "^10.4.7",
|
||||||
|
"tailwindcss": "^3.1.5"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
13
postcss.config.cjs
Normal file
13
postcss.config.cjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const tailwindcss = require('tailwindcss');
|
||||||
|
const autoprefixer = require('autoprefixer');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
plugins: [
|
||||||
|
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||||
|
tailwindcss(), //But others, like autoprefixer, need to run after,
|
||||||
|
autoprefixer,
|
||||||
|
autoprefixer
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
9
src/app.d.ts
vendored
Normal file
9
src/app.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
// and what to do when importing types
|
||||||
|
declare namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
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>
|
9
src/app.postcss
Normal file
9
src/app.postcss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* Write your global styles here, in PostCSS syntax */
|
||||||
|
/* Write your global styles here, in PostCSS syntax */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
BIN
src/lib/images/bird.png
Normal file
BIN
src/lib/images/bird.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
5
src/routes/+layout.svelte
Normal file
5
src/routes/+layout.svelte
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
import '../app.postcss';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
18
src/routes/+page.svelte
Normal file
18
src/routes/+page.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<div class="container h-full">
|
||||||
|
<div class="h-full flex flex-row justify-center items-center">
|
||||||
|
<div class="content">
|
||||||
|
<img src="{bird}">
|
||||||
|
<p class="workinprogress">We are building something great!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import bird from '$lib/images/bird.png';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.workinprogress {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
26
src/routes/about/+page.svelte
Normal file
26
src/routes/about/+page.svelte
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<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://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||||
|
following into your command line and following the prompts:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>npm create svelte@latest</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>
|
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/sverdle/+page.server.ts
Normal file
69
src/routes/sverdle/+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/sverdle/+page.svelte
Normal file
406
src/routes/sverdle/+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(6) as _, row}
|
||||||
|
{@const current = row === i}
|
||||||
|
<h2 class="visually-hidden">Row {row + 1}</h2>
|
||||||
|
<div class="row" class:current>
|
||||||
|
{#each Array(5) as _, 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>
|
9
src/routes/sverdle/game.test.ts
Normal file
9
src/routes/sverdle/game.test.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { Game } from './game';
|
||||||
|
|
||||||
|
describe('game test', () => {
|
||||||
|
it('returns true when a valid word is entered', () => {
|
||||||
|
const game = new Game();
|
||||||
|
expect(game.enter('zorro'.split(''))).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
75
src/routes/sverdle/game.ts
Normal file
75
src/routes/sverdle/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(' ')}`;
|
||||||
|
}
|
||||||
|
}
|
95
src/routes/sverdle/how-to-play/+page.svelte
Normal file
95
src/routes/sverdle/how-to-play/+page.svelte
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<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>
|
9
src/routes/sverdle/how-to-play/+page.ts
Normal file
9
src/routes/sverdle/how-to-play/+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;
|
23
src/routes/sverdle/reduced-motion.ts
Normal file
23
src/routes/sverdle/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/sverdle/words.server.ts
Normal file
12980
src/routes/sverdle/words.server.ts
Normal file
File diff suppressed because it is too large
Load Diff
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:
|
21
svelte.config.js
Normal file
21
svelte.config.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import preprocess from 'svelte-preprocess';
|
||||||
|
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(),
|
||||||
|
preprocess({
|
||||||
|
postcss: true
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
13
tailwind.config.cjs
Normal file
13
tailwind.config.cjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const config = {
|
||||||
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
|
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: []
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = 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
|
||||||
|
}
|
11
vite.config.js
Normal file
11
vite.config.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
/** @type {import('vite').UserConfig} */
|
||||||
|
const config = {
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
test: {
|
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
Loading…
Reference in New Issue
Block a user