summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElizabeth Hunt <elizabeth.hunt@simponic.xyz>2023-07-19 20:38:24 -0700
committerElizabeth Hunt <elizabeth.hunt@simponic.xyz>2023-07-19 20:38:24 -0700
commit0fd9fb097552686f2257c1aa689d797e80057bd1 (patch)
treeb8d0367bf7b62c049af60ace301ce1cffc08d821
downloadjumpstorm-0fd9fb097552686f2257c1aa689d797e80057bd1.tar.gz
jumpstorm-0fd9fb097552686f2257c1aa689d797e80057bd1.zip
initial commit
-rw-r--r--client/.eslintrc.js11
-rw-r--r--client/.gitignore24
-rw-r--r--client/.vscode/extensions.json3
-rw-r--r--client/README.md47
-rw-r--r--client/index.html19
-rw-r--r--client/lib/Game.ts70
-rw-r--r--client/lib/JumpStorm.ts54
-rw-r--r--client/lib/components/BoundingBox.ts97
-rw-r--r--client/lib/components/Collide.ts7
-rw-r--r--client/lib/components/Component.ts7
-rw-r--r--client/lib/components/Control.ts7
-rw-r--r--client/lib/components/FacingDirection.ts13
-rw-r--r--client/lib/components/Forces.ts17
-rw-r--r--client/lib/components/Gravity.ts13
-rw-r--r--client/lib/components/Jump.ts10
-rw-r--r--client/lib/components/Mass.ts10
-rw-r--r--client/lib/components/Moment.ts10
-rw-r--r--client/lib/components/Sprite.ts92
-rw-r--r--client/lib/components/TopCollidable.ts7
-rw-r--r--client/lib/components/Velocity.ts15
-rw-r--r--client/lib/components/WallBounded.ts7
-rw-r--r--client/lib/components/index.ts15
-rw-r--r--client/lib/components/names.ts15
-rw-r--r--client/lib/config/assets.ts40
-rw-r--r--client/lib/config/constants.ts34
-rw-r--r--client/lib/config/index.ts3
-rw-r--r--client/lib/config/sprites.ts49
-rw-r--r--client/lib/entities/Entity.ts33
-rw-r--r--client/lib/entities/Floor.ts31
-rw-r--r--client/lib/entities/Player.ts68
-rw-r--r--client/lib/entities/index.ts3
-rw-r--r--client/lib/interfaces/Action.ts5
-rw-r--r--client/lib/interfaces/Direction.ts6
-rw-r--r--client/lib/interfaces/Draw.ts9
-rw-r--r--client/lib/interfaces/LeaderBoardEntry.ts5
-rw-r--r--client/lib/interfaces/Vec2.ts22
-rw-r--r--client/lib/interfaces/index.ts5
-rw-r--r--client/lib/structures/QuadTree.ts154
-rw-r--r--client/lib/structures/index.ts1
-rw-r--r--client/lib/systems/Collision.ts214
-rw-r--r--client/lib/systems/FacingDirection.ts39
-rw-r--r--client/lib/systems/Input.ts86
-rw-r--r--client/lib/systems/Physics.ts94
-rw-r--r--client/lib/systems/Render.ts41
-rw-r--r--client/lib/systems/System.ts15
-rw-r--r--client/lib/systems/WallBounds.ts35
-rw-r--r--client/lib/systems/index.ts8
-rw-r--r--client/lib/systems/names.ts8
-rw-r--r--client/lib/utils/dotProduct.ts4
-rw-r--r--client/lib/utils/index.ts3
-rw-r--r--client/lib/utils/normalizeVector.ts8
-rw-r--r--client/lib/utils/rotateVector.ts15
-rw-r--r--client/package-lock.json2455
-rw-r--r--client/package.json24
-rw-r--r--client/public/assets/coffee_left.pngbin0 -> 1485 bytes
-rw-r--r--client/public/assets/coffee_right.pngbin0 -> 2064 bytes
-rw-r--r--client/public/assets/floor_tile_120.pngbin0 -> 1404 bytes
-rw-r--r--client/public/assets/floor_tile_160.pngbin0 -> 1566 bytes
-rw-r--r--client/public/assets/floor_tile_40.pngbin0 -> 750 bytes
-rw-r--r--client/public/assets/floor_tile_80.pngbin0 -> 1104 bytes
-rw-r--r--client/public/css/colors.css45
-rw-r--r--client/public/css/style.css95
-rw-r--r--client/public/css/tf.css33
-rw-r--r--client/public/css/theme.css17
-rw-r--r--client/public/fonts/CozetteVector.ttfbin0 -> 294440 bytes
-rw-r--r--client/public/fonts/scientifica.ttfbin0 -> 120736 bytes
-rw-r--r--client/public/img/kangaroo.svg18
-rw-r--r--client/src/App.svelte31
-rw-r--r--client/src/components/GameCanvas.svelte28
-rw-r--r--client/src/components/LeaderBoard.svelte25
-rw-r--r--client/src/components/LeaderBoardCard.svelte20
-rw-r--r--client/src/main.ts7
-rw-r--r--client/src/routes/Home.svelte12
-rw-r--r--client/src/vite-env.d.ts2
-rw-r--r--client/svelte.config.js7
-rw-r--r--client/tsconfig.json28
-rw-r--r--client/tsconfig.node.json9
-rw-r--r--client/vite.config.ts7
78 files changed, 4471 insertions, 0 deletions
diff --git a/client/.eslintrc.js b/client/.eslintrc.js
new file mode 100644
index 0000000..f200fbf
--- /dev/null
+++ b/client/.eslintrc.js
@@ -0,0 +1,11 @@
+module.exports = {
+ extends: [
+ // add more generic rule sets here, such as:
+ "eslint:recommended",
+ "plugin:svelte/recommended",
+ ],
+ rules: {
+ // override/add rules settings here, such as:
+ // 'svelte/rule-name': 'error'
+ },
+};
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/client/.vscode/extensions.json b/client/.vscode/extensions.json
new file mode 100644
index 0000000..bdef820
--- /dev/null
+++ b/client/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["svelte.svelte-vscode"]
+}
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..e6cd94f
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,47 @@
+# Svelte + TS + Vite
+
+This template should help get you started developing with Svelte and TypeScript in Vite.
+
+## Recommended IDE Setup
+
+[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
+
+## Need an official Svelte framework?
+
+Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
+
+## Technical considerations
+
+**Why use this over SvelteKit?**
+
+- It brings its own routing solution which might not be preferable for some users.
+- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
+
+This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
+
+Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
+
+**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
+
+Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
+
+**Why include `.vscode/extensions.json`?**
+
+Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
+
+**Why enable `allowJs` in the TS template?**
+
+While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
+
+**Why is HMR not preserving my local component state?**
+
+HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
+
+If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
+
+```ts
+// store.ts
+// An extremely simple external store
+import { writable } from 'svelte/store'
+export default writable(0)
+```
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..00b94e7
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <link rel="icon" type="image/svg+xml" href="/img/kangaroo.svg" />
+ <link rel="stylesheet" href="/css/style.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>jumpstorm</title>
+ </head>
+ <body>
+ <noscript>
+ <div style="text-align: center">
+ <h1>yeah, unfortunately you need javascript :)</h1>
+ </div>
+ </noscript>
+ <div id="app"></div>
+ <script type="module" src="/src/main.ts"></script>
+ </body>
+</html>
diff --git a/client/lib/Game.ts b/client/lib/Game.ts
new file mode 100644
index 0000000..d6ffb47
--- /dev/null
+++ b/client/lib/Game.ts
@@ -0,0 +1,70 @@
+import { Entity } from "./entities";
+import { System } from "./systems";
+
+export class Game {
+ private entities: Map<number, Entity>;
+ private systems: Map<string, System>;
+ private systemOrder: string[];
+
+ private running: boolean;
+ private lastTimeStamp: number;
+
+ constructor() {
+ this.running = false;
+ this.systemOrder = [];
+ this.systems = new Map();
+ this.entities = new Map();
+ }
+
+ public start() {
+ this.lastTimeStamp = performance.now();
+ this.running = true;
+ }
+
+ public addEntity(entity: Entity) {
+ this.entities.set(entity.id, entity);
+ }
+
+ public getEntity(id: number): Entity {
+ return this.entities.get(id);
+ }
+
+ public removeEntity(id: number) {
+ this.entities.delete(id);
+ }
+
+ public addSystem(system: System) {
+ if (!this.systemOrder.includes(system.name)) {
+ this.systemOrder.push(system.name);
+ }
+ this.systems.set(system.name, system);
+ }
+
+ public getSystem(name: string): System {
+ return this.systems.get(name);
+ }
+
+ public doGameLoop = (timeStamp: number) => {
+ if (!this.running) {
+ return;
+ }
+
+ const dt = timeStamp - this.lastTimeStamp;
+ this.lastTimeStamp = timeStamp;
+
+ const componentEntities = new Map<string, Set<number>>();
+ this.entities.forEach((entity) =>
+ entity.getComponents().forEach((component) => {
+ if (!componentEntities.has(component.name)) {
+ componentEntities.set(component.name, new Set<number>([entity.id]));
+ return;
+ }
+ componentEntities.get(component.name).add(entity.id);
+ })
+ );
+
+ this.systemOrder.forEach((systemName) => {
+ this.systems.get(systemName).update(dt, this.entities, componentEntities);
+ });
+ };
+}
diff --git a/client/lib/JumpStorm.ts b/client/lib/JumpStorm.ts
new file mode 100644
index 0000000..c76d9bc
--- /dev/null
+++ b/client/lib/JumpStorm.ts
@@ -0,0 +1,54 @@
+import { Floor, Player } from "./entities";
+import { Game } from "./Game";
+import {
+ WallBounds,
+ FacingDirection,
+ Render,
+ Physics,
+ Input,
+ Collision,
+} from "./systems";
+
+export class JumpStorm {
+ private game: Game;
+
+ constructor(ctx: CanvasRenderingContext2D) {
+ this.game = new Game();
+
+ [
+ this.createInputSystem(),
+ new FacingDirection(),
+ new Physics(),
+ new Collision(),
+ new WallBounds(ctx.canvas.width),
+ new Render(ctx),
+ ].forEach((system) => this.game.addSystem(system));
+
+ [new Floor(160), new Player()].forEach((entity) =>
+ this.game.addEntity(entity)
+ );
+ }
+
+ public play() {
+ this.game.start();
+
+ const loop = (timestamp: number) => {
+ this.game.doGameLoop(timestamp);
+ requestAnimationFrame(loop); // tail call recursion! /s
+ };
+ requestAnimationFrame(loop);
+ }
+
+ private createInputSystem(): Input {
+ const inputSystem = new Input();
+
+ window.addEventListener("keydown", (e) => {
+ if (!e.repeat) {
+ inputSystem.keyPressed(e.key);
+ }
+ });
+ window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key));
+
+ return inputSystem;
+ }
+}
diff --git a/client/lib/components/BoundingBox.ts b/client/lib/components/BoundingBox.ts
new file mode 100644
index 0000000..2b1d648
--- /dev/null
+++ b/client/lib/components/BoundingBox.ts
@@ -0,0 +1,97 @@
+import { Component, ComponentNames } from ".";
+import type { Coord2D, Dimension2D } from "../interfaces";
+import { dotProduct, rotateVector, normalizeVector } from "../utils";
+
+export class BoundingBox extends Component {
+ public center: Coord2D;
+ public dimension: Dimension2D;
+ public rotation: number;
+
+ constructor(center: Coord2D, dimension: Dimension2D, rotation?: number) {
+ super(ComponentNames.BoundingBox);
+
+ this.center = center;
+ this.dimension = dimension;
+ this.rotation = rotation ?? 0;
+ }
+
+ public isCollidingWith(box: BoundingBox): boolean {
+ const boxes = [this.getVertices(), box.getVertices()];
+ for (const poly of boxes) {
+ for (let i = 0; i < poly.length; ++i) {
+ const [A, B] = [poly[i], poly[(i + 1) % poly.length]];
+ const normal: Coord2D = { x: B.y - A.y, y: A.x - B.x };
+
+ const [[minThis, maxThis], [minBox, maxBox]] = boxes.map((box) =>
+ box.reduce(
+ ([min, max], vertex) => {
+ const projection = dotProduct(normal, vertex);
+ return [Math.min(min, projection), Math.max(max, projection)];
+ },
+ [Infinity, -Infinity]
+ )
+ );
+
+ if (maxThis < minBox || maxBox < minThis) return false;
+ }
+ }
+
+ return true;
+ }
+
+ public getVertices(): Coord2D[] {
+ return [
+ { x: -this.dimension.width / 2, y: -this.dimension.height / 2 },
+ { x: -this.dimension.width / 2, y: this.dimension.height / 2 },
+ { x: this.dimension.width / 2, y: this.dimension.height / 2 },
+ { x: this.dimension.width / 2, y: -this.dimension.height / 2 },
+ ]
+ .map((vertex) => rotateVector(vertex, this.rotation))
+ .map((vertex) => {
+ return {
+ x: vertex.x + this.center.x,
+ y: vertex.y + this.center.y,
+ };
+ });
+ }
+
+ private getAxes() {
+ const corners: Coord2D[] = this.getVerticesRelativeToCenter();
+ const axes: Coord2D[] = [];
+
+ for (let i = 0; i < corners.length; ++i) {
+ const [cornerA, cornerB] = [
+ corners[i],
+ corners[(i + 1) % corners.length],
+ ].map((corner) => rotateVector(corner, this.rotation));
+
+ axes.push(
+ normalizeVector({
+ x: cornerB.y - cornerA.y,
+ y: -(cornerB.x - cornerA.x),
+ })
+ );
+ }
+
+ return axes;
+ }
+
+ private project(axis: Coord2D): [number, number] {
+ const corners = this.getCornersRelativeToCenter();
+ let [min, max] = [Infinity, -Infinity];
+
+ for (const corner of corners) {
+ const rotated = rotateVector(corner, this.rotation);
+ const translated = {
+ x: rotated.x + this.center.x,
+ y: rotated.y + this.center.y,
+ };
+ const projection = dotProduct(translated, axis);
+
+ min = Math.min(projection, min);
+ max = Math.max(projection, max);
+ }
+
+ return [min, max];
+ }
+}
diff --git a/client/lib/components/Collide.ts b/client/lib/components/Collide.ts
new file mode 100644
index 0000000..889ecf8
--- /dev/null
+++ b/client/lib/components/Collide.ts
@@ -0,0 +1,7 @@
+import { Component, ComponentNames } from ".";
+
+export class Collide extends Component {
+ constructor() {
+ super(ComponentNames.Collide);
+ }
+}
diff --git a/client/lib/components/Component.ts b/client/lib/components/Component.ts
new file mode 100644
index 0000000..7331982
--- /dev/null
+++ b/client/lib/components/Component.ts
@@ -0,0 +1,7 @@
+export abstract class Component {
+ public readonly name: string;
+
+ constructor(name: string) {
+ this.name = name;
+ }
+}
diff --git a/client/lib/components/Control.ts b/client/lib/components/Control.ts
new file mode 100644
index 0000000..094ef1c
--- /dev/null
+++ b/client/lib/components/Control.ts
@@ -0,0 +1,7 @@
+import { Component, ComponentNames } from ".";
+
+export class Control extends Component {
+ constructor() {
+ super(ComponentNames.Control);
+ }
+}
diff --git a/client/lib/components/FacingDirection.ts b/client/lib/components/FacingDirection.ts
new file mode 100644
index 0000000..1c701a3
--- /dev/null
+++ b/client/lib/components/FacingDirection.ts
@@ -0,0 +1,13 @@
+import { Component, ComponentNames, Sprite } from ".";
+
+export class FacingDirection extends Component {
+ public readonly facingLeftSprite: Sprite;
+ public readonly facingRightSprite: Sprite;
+
+ constructor(facingLeftSprite: Sprite, facingRightSprite: Sprite) {
+ super(ComponentNames.FacingDirection);
+
+ this.facingLeftSprite = facingLeftSprite;
+ this.facingRightSprite = facingRightSprite;
+ }
+}
diff --git a/client/lib/components/Forces.ts b/client/lib/components/Forces.ts
new file mode 100644
index 0000000..bf540a1
--- /dev/null
+++ b/client/lib/components/Forces.ts
@@ -0,0 +1,17 @@
+import type { Accel2D, Force2D } from "../interfaces";
+import { Component } from "./Component";
+import { ComponentNames } from ".";
+
+/**
+ * A list of forces and torque, (in newtons, and newton-meters respectively)
+ * to apply on one Physics system update (after which, they are cleared).
+ */
+export class Forces extends Component {
+ public forces: Force2D[];
+
+ constructor(forces?: Force2D[]) {
+ super(ComponentNames.Forces);
+
+ this.forces = forces ?? [];
+ }
+}
diff --git a/client/lib/components/Gravity.ts b/client/lib/components/Gravity.ts
new file mode 100644
index 0000000..89fcb67
--- /dev/null
+++ b/client/lib/components/Gravity.ts
@@ -0,0 +1,13 @@
+import { ComponentNames, Component } from ".";
+
+export class Gravity extends Component {
+ private static DEFAULT_TERMINAL_VELOCITY = 5;
+
+ public terminalVelocity: number;
+
+ constructor(terminalVelocity?: number) {
+ super(ComponentNames.Gravity);
+ this.terminalVelocity =
+ terminalVelocity ?? Gravity.DEFAULT_TERMINAL_VELOCITY;
+ }
+}
diff --git a/client/lib/components/Jump.ts b/client/lib/components/Jump.ts
new file mode 100644
index 0000000..0b40767
--- /dev/null
+++ b/client/lib/components/Jump.ts
@@ -0,0 +1,10 @@
+import { Component, ComponentNames } from ".";
+
+export class Jump extends Component {
+ public canJump: boolean;
+
+ constructor() {
+ super(ComponentNames.Jump);
+ this.canJump = false;
+ }
+}
diff --git a/client/lib/components/Mass.ts b/client/lib/components/Mass.ts
new file mode 100644
index 0000000..daa2d71
--- /dev/null
+++ b/client/lib/components/Mass.ts
@@ -0,0 +1,10 @@
+import { Component, ComponentNames } from ".";
+
+export class Mass extends Component {
+ public mass: number;
+
+ constructor(mass: number) {
+ super(ComponentNames.Mass);
+ this.mass = mass;
+ }
+}
diff --git a/client/lib/components/Moment.ts b/client/lib/components/Moment.ts
new file mode 100644
index 0000000..3d0dd2f
--- /dev/null
+++ b/client/lib/components/Moment.ts
@@ -0,0 +1,10 @@
+import { Component, ComponentNames } from ".";
+
+export class Moment extends Component {
+ public inertia: number;
+
+ constructor(inertia: number) {
+ super(ComponentNames.Moment);
+ this.inertia = inertia;
+ }
+}
diff --git a/client/lib/components/Sprite.ts b/client/lib/components/Sprite.ts
new file mode 100644
index 0000000..90e1389
--- /dev/null
+++ b/client/lib/components/Sprite.ts
@@ -0,0 +1,92 @@
+import { Component, ComponentNames } from ".";
+import type { Dimension2D, DrawArgs, Coord2D } from "../interfaces";
+
+export class Sprite extends Component {
+ private sheet: HTMLImageElement;
+
+ private spriteImgPos: Coord2D;
+ private spriteImgDimensions: Dimension2D;
+
+ private msPerFrame: number;
+ private msSinceLastFrame: number;
+ private currentFrame: number;
+ private numFrames: number;
+
+ constructor(
+ sheet: HTMLImageElement,
+ spriteImgPos: Coord2D,
+ spriteImgDimensions: Dimension2D,
+ msPerFrame: number,
+ numFrames: number
+ ) {
+ super(ComponentNames.Sprite);
+
+ this.sheet = sheet;
+ this.spriteImgPos = spriteImgPos;
+ this.spriteImgDimensions = spriteImgDimensions;
+ this.msPerFrame = msPerFrame;
+ this.numFrames = numFrames;
+
+ this.msSinceLastFrame = 0;
+ this.currentFrame = 0;
+ }
+
+ public update(dt: number) {
+ this.msSinceLastFrame += dt;
+ if (this.msSinceLastFrame >= this.msPerFrame) {
+ this.currentFrame = (this.currentFrame + 1) % this.numFrames;
+ this.msSinceLastFrame = 0;
+ }
+ }
+
+ public draw(ctx: CanvasRenderingContext2D, drawArgs: DrawArgs) {
+ const { center, rotation, tint, opacity } = drawArgs;
+
+ ctx.save();
+ ctx.translate(center.x, center.y);
+ if (rotation != 0) {
+ ctx.rotate(rotation * (Math.PI / 180));
+ }
+ ctx.translate(-center.x, -center.y);
+
+ if (opacity) {
+ ctx.globalAlpha = opacity;
+ }
+
+ ctx.drawImage(
+ this.sheet,
+ ...this.getSpriteArgs(),
+ ...this.getDrawArgs(drawArgs)
+ );
+
+ if (tint) {
+ ctx.globalAlpha = 0.5;
+ ctx.globalCompositeOperation = "source-atop";
+ ctx.fillStyle = tint;
+ ctx.fillRect(...this.getDrawArgs(drawArgs));
+ }
+
+ ctx.restore();
+ }
+
+ private getSpriteArgs(): [sx: number, sy: number, sw: number, sh: number] {
+ return [
+ this.spriteImgPos.x + this.currentFrame * this.spriteImgDimensions.width,
+ this.spriteImgPos.y,
+ this.spriteImgDimensions.width,
+ this.spriteImgDimensions.height,
+ ];
+ }
+
+ private getDrawArgs({
+ center,
+ dimension,
+ }: DrawArgs): [dx: number, dy: number, dw: number, dh: number] {
+ return [
+ center.x - dimension.width / 2,
+ center.y - dimension.height / 2,
+ dimension.width,
+ dimension.height,
+ ];
+ }
+}
diff --git a/client/lib/components/TopCollidable.ts b/client/lib/components/TopCollidable.ts
new file mode 100644
index 0000000..7fb147d
--- /dev/null
+++ b/client/lib/components/TopCollidable.ts
@@ -0,0 +1,7 @@
+import { Component, ComponentNames } from ".";
+
+export class TopCollidable extends Component {
+ constructor() {
+ super(ComponentNames.TopCollidable);
+ }
+}
diff --git a/client/lib/components/Velocity.ts b/client/lib/components/Velocity.ts
new file mode 100644
index 0000000..119427d
--- /dev/null
+++ b/client/lib/components/Velocity.ts
@@ -0,0 +1,15 @@
+import type { Velocity2D } from "../interfaces";
+import { Component } from "./Component";
+import { ComponentNames } from ".";
+
+export class Velocity extends Component {
+ public dCartesian: Velocity2D;
+ public dTheta: number;
+
+ constructor(dCartesian: Velocity2D, dTheta: number) {
+ super(ComponentNames.Velocity);
+
+ this.dCartesian = dCartesian;
+ this.dTheta = dTheta;
+ }
+}
diff --git a/client/lib/components/WallBounded.ts b/client/lib/components/WallBounded.ts
new file mode 100644
index 0000000..5f787e1
--- /dev/null
+++ b/client/lib/components/WallBounded.ts
@@ -0,0 +1,7 @@
+import { Component, ComponentNames } from ".";
+
+export class WallBounded extends Component {
+ constructor() {
+ super(ComponentNames.WallBounded);
+ }
+}
diff --git a/client/lib/components/index.ts b/client/lib/components/index.ts
new file mode 100644
index 0000000..67f1259
--- /dev/null
+++ b/client/lib/components/index.ts
@@ -0,0 +1,15 @@
+export * from "./Component";
+export * from "./BoundingBox";
+export * from "./Velocity";
+export * from "./Forces";
+export * from "./Sprite";
+export * from "./FacingDirection";
+export * from "./Jump";
+export * from "./TopCollidable";
+export * from "./Collide";
+export * from "./Control";
+export * from "./WallBounded";
+export * from "./Gravity";
+export * from "./Mass";
+export * from "./Moment";
+export * from "./names";
diff --git a/client/lib/components/names.ts b/client/lib/components/names.ts
new file mode 100644
index 0000000..e2ee3d3
--- /dev/null
+++ b/client/lib/components/names.ts
@@ -0,0 +1,15 @@
+export namespace ComponentNames {
+ export const Sprite = "Sprite";
+ export const BoundingBox = "BoundingBox";
+ export const Velocity = "Velocity";
+ export const FacingDirection = "FacingDirection";
+ export const Control = "Control";
+ export const Jump = "Jump";
+ export const TopCollidable = "TopCollidable";
+ export const Collide = "Collide";
+ export const WallBounded = "WallBounded";
+ export const Gravity = "Gravity";
+ export const Forces = "Forces";
+ export const Mass = "Mass";
+ export const Moment = "Moment";
+}
diff --git a/client/lib/config/assets.ts b/client/lib/config/assets.ts
new file mode 100644
index 0000000..51a5303
--- /dev/null
+++ b/client/lib/config/assets.ts
@@ -0,0 +1,40 @@
+import type { SpriteSpec } from "./sprites";
+import { SPRITE_SPECS } from "./sprites";
+
+export const IMAGES = new Map<string, HTMLImageElement>();
+
+export const loadSpritesIntoImageElements = (
+ spriteSpecs: Partial<SpriteSpec>[]
+): Promise<void>[] => {
+ const spritePromises: Promise<void>[] = [];
+
+ for (const spriteSpec of spriteSpecs) {
+ if (spriteSpec.sheet) {
+ const img = new Image();
+ img.src = spriteSpec.sheet;
+ IMAGES.set(spriteSpec.sheet, img);
+
+ spritePromises.push(
+ new Promise((resolve) => {
+ img.onload = () => resolve();
+ })
+ );
+ }
+
+ if (spriteSpec.states) {
+ spritePromises.push(
+ ...loadSpritesIntoImageElements(Object.values(spriteSpec.states))
+ );
+ }
+ }
+
+ return spritePromises;
+};
+
+export const loadAssets = () =>
+ Promise.all([
+ ...loadSpritesIntoImageElements(
+ Array.from(SPRITE_SPECS.keys()).map((key) => SPRITE_SPECS.get(key))
+ ),
+ // TODO: Sound
+ ]);
diff --git a/client/lib/config/constants.ts b/client/lib/config/constants.ts
new file mode 100644
index 0000000..27c8160
--- /dev/null
+++ b/client/lib/config/constants.ts
@@ -0,0 +1,34 @@
+import { Action } from "../interfaces";
+
+export namespace KeyConstants {
+ export const KeyActions: Record<string, Action> = {
+ a: Action.MOVE_LEFT,
+ ArrowLeft: Action.MOVE_LEFT,
+ d: Action.MOVE_RIGHT,
+ ArrowRight: Action.MOVE_RIGHT,
+ w: Action.JUMP,
+ ArrowUp: Action.JUMP,
+ };
+
+ export const ActionKeys: Map<Action, string[]> = Object.keys(
+ KeyActions
+ ).reduce((acc: Map<Action, string[]>, key) => {
+ const action = KeyActions[key];
+
+ if (acc.has(action)) {
+ acc.get(action).push(key);
+ return acc;
+ }
+
+ acc.set(action, [key]);
+ return acc;
+ }, new Map<Action, string[]>());
+}
+
+export namespace PhysicsConstants {
+ export const MAX_JUMP_TIME_MS = 150;
+ export const GRAVITY = 0.0075;
+ export const PLAYER_MOVE_VEL = 1;
+ export const PLAYER_JUMP_ACC = -0.01;
+ export const PLAYER_JUMP_INITIAL_VEL = -0.9;
+}
diff --git a/client/lib/config/index.ts b/client/lib/config/index.ts
new file mode 100644
index 0000000..7a1052a
--- /dev/null
+++ b/client/lib/config/index.ts
@@ -0,0 +1,3 @@
+export * from "./constants";
+export * from "./assets.ts";
+export * from "./sprites.ts";
diff --git a/client/lib/config/sprites.ts b/client/lib/config/sprites.ts
new file mode 100644
index 0000000..18bec73
--- /dev/null
+++ b/client/lib/config/sprites.ts
@@ -0,0 +1,49 @@
+export enum Sprites {
+ FLOOR,
+ TRAMPOLINE,
+ COFFEE,
+}
+
+export interface SpriteSpec {
+ sheet: string;
+ width: number;
+ height: number;
+ frames: number;
+ msPerFrame: number;
+ states?: Record<string | number, Partial<SpriteSpec>>;
+}
+
+export const SPRITE_SPECS: Map<Sprites, Partial<SpriteSpec>> = new Map<
+ Sprites,
+ SpriteSpec
+>();
+
+const floorSpriteSpec = {
+ height: 40,
+ frames: 3,
+ msPerFrame: 125,
+ states: {},
+};
+floorSpriteSpec.states = [40, 80, 120, 160].reduce((acc, cur) => {
+ acc[cur] = {
+ width: cur,
+ sheet: `/assets/floor_tile_${cur}.png`,
+ };
+ return acc;
+}, {});
+SPRITE_SPECS.set(Sprites.FLOOR, floorSpriteSpec);
+
+SPRITE_SPECS.set(Sprites.COFFEE, {
+ msPerFrame: 100,
+ width: 60,
+ height: 45,
+ frames: 3,
+ states: {
+ LEFT: {
+ sheet: "/assets/coffee_left.png",
+ },
+ RIGHT: {
+ sheet: "/assets/coffee_right.png",
+ },
+ },
+});
diff --git a/client/lib/entities/Entity.ts b/client/lib/entities/Entity.ts
new file mode 100644
index 0000000..e57ccde
--- /dev/null
+++ b/client/lib/entities/Entity.ts
@@ -0,0 +1,33 @@
+import type { Component } from "../components";
+import { ComponentNotFoundError } from "../exceptions";
+
+export abstract class Entity {
+ private static ID = 0;
+
+ public readonly id: number;
+ public readonly components: Map<string, Component>;
+
+ constructor() {
+ this.id = Entity.ID++;
+ this.components = new Map();
+ }
+
+ public addComponent(component: Component) {
+ this.components.set(component.name, component);
+ }
+
+ public getComponent<T extends Component>(name: string): T {
+ if (!this.hasComponent(name)) {
+ throw new Error("Entity does not have component " + name);
+ }
+ return this.components.get(name) as T;
+ }
+
+ public getComponents(): Component[] {
+ return Array.from(this.components.values());
+ }
+
+ public hasComponent(name: string): boolean {
+ return this.components.has(name);
+ }
+}
diff --git a/client/lib/entities/Floor.ts b/client/lib/entities/Floor.ts
new file mode 100644
index 0000000..d51badc
--- /dev/null
+++ b/client/lib/entities/Floor.ts
@@ -0,0 +1,31 @@
+import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config";
+import { BoundingBox, Sprite } from "../components";
+import { TopCollidable } from "../components/TopCollidable";
+import { Entity } from "../entities";
+
+export class Floor extends Entity {
+ private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(Sprites.FLOOR);
+
+ constructor(width: number) {
+ super();
+
+ this.addComponent(
+ new Sprite(
+ IMAGES.get(Floor.spriteSpec.states[width].sheet),
+ { x: 0, y: 0 },
+ { width, height: Floor.spriteSpec.height },
+ Floor.spriteSpec.msPerFrame,
+ Floor.spriteSpec.frames
+ )
+ );
+
+ this.addComponent(
+ new BoundingBox(
+ { x: 300, y: 300 },
+ { width, height: Floor.spriteSpec.height }
+ )
+ );
+
+ this.addComponent(new TopCollidable());
+ }
+}
diff --git a/client/lib/entities/Player.ts b/client/lib/entities/Player.ts
new file mode 100644
index 0000000..0ba5a41
--- /dev/null
+++ b/client/lib/entities/Player.ts
@@ -0,0 +1,68 @@
+import { Entity } from ".";
+import { IMAGES, SPRITE_SPECS, Sprites, type SpriteSpec } from "../config";
+import {
+ Jump,
+ FacingDirection,
+ BoundingBox,
+ Sprite,
+ Velocity,
+ Gravity,
+ WallBounded,
+ Forces,
+ Collide,
+ Control,
+ Mass,
+ Moment,
+} from "../components";
+import { PhysicsConstants } from "../config";
+import { Direction } from "../interfaces";
+
+export class Player extends Entity {
+ private static MASS: number = 10;
+ private static MOI: number = 1000;
+
+ private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(Sprites.COFFEE);
+
+ constructor() {
+ super();
+
+ this.addComponent(
+ new BoundingBox(
+ { x: 300, y: 100 },
+ { width: Player.spriteSpec.width, height: Player.spriteSpec.height },
+ 0
+ )
+ );
+
+ this.addComponent(new Velocity({ dx: 0, dy: 0 }, 0));
+
+ this.addComponent(new Mass(Player.MASS));
+ this.addComponent(new Moment(Player.MOI));
+ this.addComponent(new Forces());
+ this.addComponent(new Gravity());
+
+ this.addComponent(new Jump());
+ this.addComponent(new Control());
+
+ this.addComponent(new Collide());
+ this.addComponent(new WallBounded());
+
+ this.addFacingDirectionComponents();
+ }
+
+ private addFacingDirectionComponents() {
+ const [leftSprite, rightSprite] = [Direction.LEFT, Direction.RIGHT].map(
+ (direction) =>
+ new Sprite(
+ IMAGES.get(Player.spriteSpec.states[direction].sheet),
+ { x: 0, y: 0 },
+ { width: Player.spriteSpec.width, height: Player.spriteSpec.height },
+ Player.spriteSpec.msPerFrame,
+ Player.spriteSpec.frames
+ )
+ );
+
+ this.addComponent(new FacingDirection(leftSprite, rightSprite));
+ this.addComponent(leftSprite); // face Left by default
+ }
+}
diff --git a/client/lib/entities/index.ts b/client/lib/entities/index.ts
new file mode 100644
index 0000000..a921512
--- /dev/null
+++ b/client/lib/entities/index.ts
@@ -0,0 +1,3 @@
+export * from "./Entity";
+export * from "./Floor";
+export * from "./Player";
diff --git a/client/lib/interfaces/Action.ts b/client/lib/interfaces/Action.ts
new file mode 100644
index 0000000..61c89e1
--- /dev/null
+++ b/client/lib/interfaces/Action.ts
@@ -0,0 +1,5 @@
+export enum Action {
+ MOVE_LEFT,
+ MOVE_RIGHT,
+ JUMP,
+}
diff --git a/client/lib/interfaces/Direction.ts b/client/lib/interfaces/Direction.ts
new file mode 100644
index 0000000..0bc6ef3
--- /dev/null
+++ b/client/lib/interfaces/Direction.ts
@@ -0,0 +1,6 @@
+export enum Direction {
+ UP = "UP",
+ DOWN = "DOWN",
+ LEFT = "LEFT",
+ RIGHT = "RIGHT",
+}
diff --git a/client/lib/interfaces/Draw.ts b/client/lib/interfaces/Draw.ts
new file mode 100644
index 0000000..6561a01
--- /dev/null
+++ b/client/lib/interfaces/Draw.ts
@@ -0,0 +1,9 @@
+import type { Coord2D, Dimension2D } from "./";
+
+export interface DrawArgs {
+ center: Coord2D;
+ dimension: Dimension2D;
+ tint?: string;
+ opacity?: number;
+ rotation?: number;
+}
diff --git a/client/lib/interfaces/LeaderBoardEntry.ts b/client/lib/interfaces/LeaderBoardEntry.ts
new file mode 100644
index 0000000..1b1e7b3
--- /dev/null
+++ b/client/lib/interfaces/LeaderBoardEntry.ts
@@ -0,0 +1,5 @@
+export interface LeaderBoardEntry {
+ name: string;
+ score: number;
+ avatar: string;
+}
diff --git a/client/lib/interfaces/Vec2.ts b/client/lib/interfaces/Vec2.ts
new file mode 100644
index 0000000..b2bae37
--- /dev/null
+++ b/client/lib/interfaces/Vec2.ts
@@ -0,0 +1,22 @@
+export interface Coord2D {
+ x: number;
+ y: number;
+}
+
+export interface Dimension2D {
+ width: number;
+ height: number;
+}
+
+export interface Velocity2D {
+ dx: number;
+ dy: number;
+}
+
+export interface Force2D {
+ fCartesian: {
+ fx: number;
+ fy: number;
+ };
+ torque: number;
+}
diff --git a/client/lib/interfaces/index.ts b/client/lib/interfaces/index.ts
new file mode 100644
index 0000000..0398abd
--- /dev/null
+++ b/client/lib/interfaces/index.ts
@@ -0,0 +1,5 @@
+export * from "./LeaderBoardEntry";
+export * from "./Vec2";
+export * from "./Draw";
+export * from "./Direction";
+export * from "./Action";
diff --git a/client/lib/structures/QuadTree.ts b/client/lib/structures/QuadTree.ts
new file mode 100644
index 0000000..7913e59
--- /dev/null
+++ b/client/lib/structures/QuadTree.ts
@@ -0,0 +1,154 @@
+import type { Coord2D, Dimension2D } from "../interfaces";
+import { ComponentNames, BoundingBox } from "../components";
+import { Entity } from "../entities";
+
+interface BoxedEntry {
+ id: number;
+ dimension: Dimension2D;
+ center: Coord2D;
+}
+
+enum Quadrant {
+ I,
+ II,
+ III,
+ IV,
+}
+
+export class QuadTree {
+ private maxLevels: number;
+ private splitThreshold: number;
+ private level: number;
+ private topLeft: Coord2D;
+ private dimension: Dimension2D;
+
+ private children: Map<Quadrant, QuadTree>;
+ private objects: BoxedEntry[];
+
+ constructor(
+ topLeft: Coord2D,
+ dimension: Dimension2D,
+ maxLevels: number,
+ splitThreshold: number,
+ level?: number
+ ) {
+ this.children = [];
+ this.objects = [];
+
+ this.maxLevels = maxLevels;
+ this.splitThreshold = splitThreshold;
+ this.level = level ?? 0;
+ }
+
+ public insert(id: number, dimension: Dimension2D, center: Coord2D): void {
+ if (this.hasChildren()) {
+ this.getIndices(boundingBox).forEach((i) =>
+ this.children[i].insert(id, dimension, center)
+ );
+ return;
+ }
+
+ this.objects.push({ id, dimension, center });
+
+ if (
+ this.objects.length > this.splitThreshold &&
+ this.level < this.maxLevels
+ ) {
+ if (!this.hasChildren()) {
+ this.performSplit();
+ }
+ this.realignObjects();
+ }
+ }
+
+ public clear(): void {
+ this.objects = [];
+ if (this.hasChildren()) {
+ this.children.forEach((child) => child.clear());
+ this.children.clear();
+ }
+ }
+
+ public getNeighborIds(boxedEntry: BoxedEntry): number[] {
+ const neighbors: number[] = this.objects.map(({ id }) => id);
+
+ if (this.hasChildren()) {
+ this.getQuadrants(boxedEntry).forEach((quadrant) => {
+ this.children
+ .get(quadrant)
+ .getNeighborIds(boxedEntry)
+ .forEach((id) => neighbors.push(id));
+ });
+ }
+
+ return neighbors;
+ }
+
+ private performSplit(): void {
+ const halfWidth = this.dimension.width / 2;
+ const halfHeight = this.dimension.height / 2;
+
+ [
+ [Quadrant.I, { x: this.topLeft.x + halfWidth, y: this.topLeft.y }],
+ [Quadrant.II, { ...this.topLeft }],
+ [Quadrant.III, { x: this.topLeft.x, y: this.topLeft.y + halfHeight }],
+ [
+ Quadrant.IV,
+ { x: this.topLeft.x + halfWidth, y: this.topLeft.y + halfHeight },
+ ],
+ ].forEach(([quadrant, pos]) => {
+ this.children.set(
+ quadrant,
+ new QuadTree(
+ pos,
+ { width: halfWidth, height: halfHeight },
+ this.maxLevels,
+ this.splitThreshold,
+ this.level + 1
+ )
+ );
+ });
+ }
+
+ private getQuandrants(boxedEntry: BoxedEntry): Quadrant[] {
+ const treeCenter: Coord2D = {
+ x: this.topLeft.x + this.dimension.width / 2,
+ y: this.topLeft.y + this.dimension.height / 2,
+ };
+
+ return [
+ [Quadrant.I, (x, y) => x >= treeCenter.x && y < treeCenter.y],
+ [Quadrant.II, (x, y) => x < treeCenter.x && y < treeCenter.y],
+ [Quadrant.III, (x, y) => x < treeCenter.x && y >= treeCenter.y],
+ [Quadrant.IV, (x, y) => x >= treeCenter.x && y >= treeCenter.y],
+ ]
+ .filter(
+ ([_quadrant, condition]) =>
+ condition(
+ boxedEntry.center.x + boxedEntry.dimension.width / 2,
+ boxedEntry.center.y + boxedEntry.dimension.height / 2
+ ) ||
+ condition(
+ boxedEntry.center.x - boxedEntry.dimension.width / 2,
+ boxedEntry.center.y - boxedEntry.dimension.height / 2
+ )
+ )
+ .map(([quadrant]) => quadrant);
+ }
+
+ private realignObjects(): void {
+ this.objects.forEach((boxedEntry) => {
+ this.getQuadrants(boxedEntry).forEach((direction) => {
+ this.children
+ .get(direction)
+ .insert(boxedEntry.id, boxedEntry.dimension, boxedEntry.center);
+ });
+ });
+
+ this.objects = [];
+ }
+
+ private hasChildren() {
+ return this.children && this.children.length > 0;
+ }
+}
diff --git a/client/lib/structures/index.ts b/client/lib/structures/index.ts
new file mode 100644
index 0000000..605a82a
--- /dev/null
+++ b/client/lib/structures/index.ts
@@ -0,0 +1 @@
+export * from "./QuadTree";
diff --git a/client/lib/systems/Collision.ts b/client/lib/systems/Collision.ts
new file mode 100644
index 0000000..16ad8c6
--- /dev/null
+++ b/client/lib/systems/Collision.ts
@@ -0,0 +1,214 @@
+import { SystemNames, System } from ".";
+import {
+ Mass,
+ BoundingBox,
+ ComponentNames,
+ Jump,
+ Velocity,
+ Moment,
+} from "../components";
+import { PhysicsConstants } from "../config";
+import { Entity } from "../entities";
+import type { Dimension2D } from "../interfaces";
+import { QuadTree } from "../structures";
+
+export class Collision extends System {
+ private static readonly COLLIDABLE_COMPONENTS = [
+ ComponentNames.Collide,
+ ComponentNames.TopCollidable,
+ ];
+ private static readonly QUADTREE_MAX_LEVELS = 10;
+ private static readonly QUADTREE_SPLIT_THRESHOLD = 10;
+
+ private quadTree: QuadTree;
+
+ constructor(screenDimensions: Dimension2D) {
+ super(SystemNames.Collision);
+
+ this.quadTree = new QuadTree(
+ { x: 0, y: 0 },
+ screenDimensions,
+ Collision.QUADTREE_MAX_LEVELS,
+ Collision.QUADTREE_SPLIT_THRESHOLD
+ );
+ }
+
+ public update(
+ dt: number,
+ entityMap: Map<number, Entity>,
+ entityComponents: Map<string, Set<number>>
+ ) {
+ this.quadTree.clear();
+
+ const entitiesToAddToQuadtree: Entity[] = [];
+ Collision.COLLIDABLE_COMPONENTS.map((componentName) =>
+ entityComponents.get(componentName)
+ ).forEach((entityIds: Set<number>) =>
+ entityIds.forEach((id) => {
+ const entity = entityMap.get(id);
+ if (!entity.hasComponent(ComponentNames.BoundingBox)) {
+ return;
+ }
+ entitiesToAddToQuadtree.push(entity);
+ })
+ );
+
+ entitiesToAddToQuadtree.forEach((entity) => {
+ const boundingBox = entity.getComponent<BoundingBox>(
+ ComponentNames.BoundingBox
+ );
+
+ this.quadTree.insert(
+ entity.id,
+ boundingBox.dimension,
+ boundingBox.center
+ );
+ });
+
+ const collidingEntities = this.getCollidingEntities(
+ entitiesToAddToQuadtree,
+ entityMap
+ );
+ collidingEntities.forEach(([entityAId, entityBId]) => {
+ const [entityA, entityB] = [entityAId, entityBId].map((id) =>
+ entityMap.get(id)
+ );
+ this.performCollision(entityA, entityB);
+ });
+ }
+
+ private performCollision(entityA: Entity, entityB: Entity) {
+ const [entityABoundingBox, entityBBoundingBox] = [entityA, entityB].map(
+ (entity) => entity.getComponent<BoundingBox>(ComponentNames.BoundingBox)
+ );
+
+ let velocity: Velocity;
+ if (entityA.hasComponent(ComponentNames.Velocity)) {
+ velocity = entityA.getComponent<Velocity>(ComponentNames.Velocity);
+ }
+
+ if (
+ entityA.hasComponent(ComponentNames.Collide) &&
+ entityB.hasComponent(ComponentNames.TopCollidable) &&
+ entityABoundingBox.center.y <= entityBBoundingBox.center.y &&
+ velocity &&
+ velocity.dCartesian.dy >= 0 // don't apply floor logic when coming through the bottom
+ ) {
+ if (entityBBoundingBox.rotation != 0) {
+ throw new Error(
+ `entity with id ${entityB.id} has TopCollidable component and a non-zero rotation. that is not (yet) supported.`
+ );
+ }
+
+ // remove previous velocity in the y axis
+ velocity.dCartesian.dy = 0;
+
+ // apply normal force
+ if (entityA.hasComponent(ComponentNames.Gravity)) {
+ const mass = entityA.getComponent<Mass>(ComponentNames.Mass).mass;
+ const F_n = -mass * PhysicsConstants.GRAVITY;
+
+ entityA.getComponent<Forces>(ComponentNames.Forces).forces.push({
+ fCartesian: { fy: F_n },
+ });
+ }
+
+ // reset the entities' jump
+ if (entityA.hasComponent(ComponentNames.Jump)) {
+ entityA.getComponent<Jump>(ComponentNames.Jump).canJump = true;
+ }
+
+ entityABoundingBox.center.y =
+ entityBBoundingBox.center.y -
+ entityBBoundingBox.dimension.height / 2 -
+ this.getDyToPushOutOfFloor(entityABoundingBox, entityBBoundingBox);
+ }
+ }
+
+ private getCollidingEntities(
+ collidableEntities: Entity[],
+ entityMap: Map<number, Entity>
+ ): [number, number][] {
+ const collidingEntityIds: [number, number] = [];
+
+ for (const entity of collidableEntities) {
+ const boundingBox = entity.getComponent<BoundingBox>(
+ ComponentNames.BoundingBox
+ );
+
+ this.quadTree
+ .getNeighborIds({
+ id: entity.id,
+ dimension: boundingBox.dimension,
+ center: boundingBox.center,
+ })
+ .filter((neighborId) => neighborId != entity.id)
+ .forEach((neighborId) => {
+ const neighborBoundingBox = entityMap
+ .get(neighborId)
+ .getComponent<BoundingBox>(ComponentNames.BoundingBox);
+
+ if (boundingBox.isCollidingWith(neighborBoundingBox)) {
+ collidingEntityIds.push([entity.id, neighborId]);
+ }
+ });
+ }
+
+ return collidingEntityIds;
+ }
+
+ private getDyToPushOutOfFloor(
+ entityBoundingBox: BoundingBox,
+ floorBoundingBox: BoundingBox
+ ): number {
+ // ramblings: https://excalidraw.com/#json=z-xD86Za4a3duZuV2Oky0,KaGe-5iHJu1Si8inEo4GLQ
+ const {
+ rotation,
+ center: { x, y },
+ dimension: { width, height },
+ } = entityBoundingBox;
+
+ let rads = rotation * (Math.PI / 180);
+ if (rads >= Math.PI) {
+ rads -= Math.PI; // we have symmetry so we can skip two cases
+ }
+
+ let boundedCollisionX = 0; // bounded x on the surface from width
+ let clippedX = 0; // x coordinate of the vertex below the surface
+ let outScribedRectangleHeight, dy, dx;
+
+ if (rads <= Math.PI / 2) {
+ dx = (width * Math.cos(rads) - height * Math.sin(rads)) / 2;
+ outScribedRectangleHeight =
+ width * Math.sin(rads) + height * Math.cos(rads);
+ } else if (rads <= Math.PI) {
+ rads -= Math.PI / 2;
+ dx = (height * Math.cos(rads) - width * Math.sin(rads)) / 2;
+ outScribedRectangleHeight =
+ width * Math.cos(rads) + height * Math.sin(rads);
+ }
+
+ if (x >= floorBoundingBox.center.x) {
+ clippedX = x + dx;
+ boundedCollisionX = Math.min(
+ floorBoundingBox.center.x + floorBoundingBox.dimension.width / 2,
+ clippedX
+ );
+ return (
+ outScribedRectangleHeight / 2 -
+ Math.max((clippedX - boundedCollisionX) * Math.tan(rads), 0)
+ );
+ }
+
+ clippedX = x - dx;
+ boundedCollisionX = Math.max(
+ floorBoundingBox.center.x - floorBoundingBox.dimension.width / 2,
+ clippedX
+ );
+
+ return (
+ outScribedRectangleHeight / 2 -
+ Math.max((boundedCollisionX - clippedX) * Math.tan(rads), 0)
+ );
+ }
+}
diff --git a/client/lib/systems/FacingDirection.ts b/client/lib/systems/FacingDirection.ts
new file mode 100644
index 0000000..fbb4c7c
--- /dev/null
+++ b/client/lib/systems/FacingDirection.ts
@@ -0,0 +1,39 @@
+import {
+ ComponentNames,
+ Velocity,
+ FacingDirection as FacingDirectionComponent,
+} from "../components";
+import type { Entity } from "../entities";
+import { System, SystemNames } from "./";
+
+export class FacingDirection extends System {
+ constructor() {
+ super(SystemNames.FacingDirection);
+ }
+
+ public update(
+ _dt: number,
+ entityMap: Map<number, Entity>,
+ componentEntities: Map<string, Set<number>>
+ ) {
+ componentEntities
+ .get(ComponentNames.FacingDirection)
+ ?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+ if (!entity.hasComponent(ComponentNames.Velocity)) {
+ return;
+ }
+
+ const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
+ const facingDirection = entity.getComponent<FacingDirectionComponent>(
+ ComponentNames.FacingDirection
+ );
+
+ if (velocity.dCartesian.dx > 0) {
+ entity.addComponent(facingDirection.facingRightSprite);
+ } else if (velocity.dCartesian.dx < 0) {
+ entity.addComponent(facingDirection.facingLeftSprite);
+ }
+ });
+ }
+}
diff --git a/client/lib/systems/Input.ts b/client/lib/systems/Input.ts
new file mode 100644
index 0000000..92932dd
--- /dev/null
+++ b/client/lib/systems/Input.ts
@@ -0,0 +1,86 @@
+import {
+ Jump,
+ Forces,
+ Acceleration,
+ ComponentNames,
+ Velocity,
+ Mass,
+} from "../components";
+import { KeyConstants, PhysicsConstants } from "../config";
+import type { Entity } from "../entities";
+import { Action } from "../interfaces";
+import { System, SystemNames } from "./";
+
+export class Input extends System {
+ private keys: Set<string>;
+ private actionTimeStamps: Map<Action, number>;
+
+ constructor() {
+ super(SystemNames.Input);
+
+ this.keys = new Set<number>();
+ this.actionTimeStamps = new Map<Action, number>();
+ }
+
+ public keyPressed(key: string) {
+ this.keys.add(key);
+ }
+
+ public keyReleased(key: string) {
+ this.keys.delete(key);
+ }
+
+ private hasSomeKey(keys: string[]): boolean {
+ return keys.some((key) => this.keys.has(key));
+ }
+
+ public update(
+ dt: number,
+ entityMap: Map<number, Entity>,
+ componentEntities: Map<string, Set<number>>
+ ) {
+ componentEntities.get(ComponentNames.Control)?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+ if (!entity.hasComponent(ComponentNames.Velocity)) {
+ return;
+ }
+
+ const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
+
+ if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_RIGHT))) {
+ velocity.dCartesian.dx = PhysicsConstants.PLAYER_MOVE_VEL;
+ } else if (
+ this.hasSomeKey(KeyConstants.ActionKeys.get(Action.MOVE_LEFT))
+ ) {
+ velocity.dCartesian.dx = -PhysicsConstants.PLAYER_MOVE_VEL;
+ } else {
+ velocity.dCartesian.dx = 0;
+ }
+ });
+
+ componentEntities.get(ComponentNames.Jump)?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+ const jump = entity.getComponent<Jump>(ComponentNames.Jump);
+ const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
+
+ if (this.hasSomeKey(KeyConstants.ActionKeys.get(Action.JUMP))) {
+ if (jump.canJump) {
+ this.actionTimeStamps.set(Action.JUMP, performance.now());
+
+ velocity.dCartesian.dy = PhysicsConstants.PLAYER_JUMP_INITIAL_VEL;
+ jump.canJump = false;
+ }
+
+ if (
+ performance.now() - this.actionTimeStamps.get(Action.JUMP) <
+ PhysicsConstants.MAX_JUMP_TIME_MS
+ ) {
+ const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
+ entity.getComponent<Forces>(ComponentNames.Forces)?.forces.push({
+ fCartesian: { fy: mass * PhysicsConstants.PLAYER_JUMP_ACC },
+ });
+ }
+ }
+ });
+ }
+}
diff --git a/client/lib/systems/Physics.ts b/client/lib/systems/Physics.ts
new file mode 100644
index 0000000..319ae29
--- /dev/null
+++ b/client/lib/systems/Physics.ts
@@ -0,0 +1,94 @@
+import { System, SystemNames } from ".";
+import {
+ Acceleration,
+ BoundingBox,
+ ComponentNames,
+ Forces,
+ Gravity,
+ Velocity,
+ Mass,
+ Jump,
+} from "../components";
+import { PhysicsConstants } from "../config";
+import type { Entity } from "../entities";
+import type { Force2D } from "../interfaces";
+
+export class Physics extends System {
+ constructor() {
+ super(SystemNames.Physics);
+ }
+
+ public update(
+ dt: number,
+ entityMap: Map<number, Entity>,
+ componentEntities: Map<string, Set<number>>
+ ): void {
+ componentEntities.get(ComponentNames.Forces)?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+
+ const mass = entity.getComponent<Mass>(ComponentNames.Mass).mass;
+ const forces = entity.getComponent<Forces>(ComponentNames.Forces).forces;
+ const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
+ const inertia = entity.getComponent<Moment>(
+ ComponentNames.Moment
+ ).inertia;
+
+ // F_g = mg, applied only until terminal velocity is reached
+ if (entity.hasComponent(ComponentNames.Gravity)) {
+ const gravity = entity.getComponent<Gravity>(ComponentNames.Gravity);
+ if (velocity.dCartesian.dy <= gravity.terminalVelocity) {
+ forces.push({
+ fCartesian: {
+ fy: mass * PhysicsConstants.GRAVITY,
+ },
+ });
+ }
+ }
+
+ // ma = Σ(F), Iα = Σ(T)
+ const sumOfForces = forces.reduce(
+ (accum: Force2D, { fCartesian, torque }: Force2D) => ({
+ fCartesian: {
+ fx: accum.fCartesian.fx + (fCartesian?.fx ?? 0),
+ fy: accum.fCartesian.fy + (fCartesian?.fy ?? 0),
+ },
+ torque: accum.torque + (torque ?? 0),
+ }),
+ { fCartesian: { fx: 0, fy: 0 }, torque: 0 }
+ );
+
+ // integrate accelerations
+ const [ddy, ddx] = [
+ sumOfForces.fCartesian.fy,
+ sumOfForces.fCartesian.fx,
+ ].map((x) => x / mass);
+ velocity.dCartesian.dx += ddx * dt;
+ velocity.dCartesian.dy += ddy * dt;
+ velocity.dTheta += (sumOfForces.torque * dt) / inertia;
+ // clear the forces
+ entity.getComponent<Forces>(ComponentNames.Forces).forces = [];
+
+ // maybe we fell off the floor
+ if (ddy > 0 && entity.hasComponent(ComponentNames.Jump)) {
+ entity.getComponent<Jump>(ComponentNames.Jump).canJump = false;
+ }
+ });
+
+ componentEntities.get(ComponentNames.Velocity)?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+ const velocity = entity.getComponent<Velocity>(ComponentNames.Velocity);
+ const boundingBox = entity.getComponent<BoundingBox>(
+ ComponentNames.BoundingBox
+ );
+
+ // integrate velocity
+ boundingBox.center.x += velocity.dCartesian.dx * dt;
+ boundingBox.center.y += velocity.dCartesian.dy * dt;
+ boundingBox.rotation += velocity.dTheta * dt;
+ boundingBox.rotation =
+ (boundingBox.rotation < 0
+ ? 360 + boundingBox.rotation
+ : boundingBox.rotation) % 360;
+ });
+ }
+}
diff --git a/client/lib/systems/Render.ts b/client/lib/systems/Render.ts
new file mode 100644
index 0000000..0c76b00
--- /dev/null
+++ b/client/lib/systems/Render.ts
@@ -0,0 +1,41 @@
+import { System, SystemNames } from ".";
+import { BoundingBox, ComponentNames, Sprite } from "../components";
+import type { Entity } from "../entities";
+import type { DrawArgs } from "../interfaces";
+
+export class Render extends System {
+ private ctx: CanvasRenderingContext2D;
+
+ constructor(ctx: CanvasRenderingContext2D) {
+ super(SystemNames.Render);
+ this.ctx = ctx;
+ }
+
+ public update(
+ dt: number,
+ entityMap: Map<number, Entity>,
+ componentEntities: Map<string, Set<number>>
+ ) {
+ this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
+
+ componentEntities.get(ComponentNames.Sprite)?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+ const sprite = entity.getComponent<Sprite>(ComponentNames.Sprite);
+ sprite.update(dt);
+
+ let drawArgs: DrawArgs;
+ if (entity.hasComponent(ComponentNames.BoundingBox)) {
+ const boundingBox = entity.getComponent<BoundingBox>(
+ ComponentNames.BoundingBox
+ );
+
+ drawArgs = {
+ center: boundingBox.center,
+ dimension: boundingBox.dimension,
+ rotation: boundingBox.rotation,
+ };
+ }
+ sprite.draw(this.ctx, drawArgs);
+ });
+ }
+}
diff --git a/client/lib/systems/System.ts b/client/lib/systems/System.ts
new file mode 100644
index 0000000..2accc97
--- /dev/null
+++ b/client/lib/systems/System.ts
@@ -0,0 +1,15 @@
+import { Entity } from "../entities";
+
+export abstract class System {
+ public readonly name: string;
+
+ constructor(name: string) {
+ this.name = name;
+ }
+
+ abstract update(
+ dt: number,
+ entityMap: Map<number, Entity>,
+ componentEntities: Map<string, Set<number>>
+ ): void;
+}
diff --git a/client/lib/systems/WallBounds.ts b/client/lib/systems/WallBounds.ts
new file mode 100644
index 0000000..3fd5dc4
--- /dev/null
+++ b/client/lib/systems/WallBounds.ts
@@ -0,0 +1,35 @@
+import { System, SystemNames } from ".";
+import { BoundingBox, ComponentNames } from "../components";
+import type { Entity } from "../entities";
+
+export class WallBounds extends System {
+ private screenWidth: number;
+
+ constructor(screenWidth: number) {
+ super(SystemNames.WallBounds);
+
+ this.screenWidth = screenWidth;
+ }
+
+ public update(
+ _dt: number,
+ entityMap: Map<number, Entity>,
+ componentEntities: Map<string, Set<number>>
+ ) {
+ componentEntities.get(ComponentNames.WallBounded)?.forEach((entityId) => {
+ const entity = entityMap.get(entityId);
+ if (!entity.hasComponent(ComponentNames.BoundingBox)) {
+ return;
+ }
+
+ const boundingBox = entity.getComponent<BoundingBox>(
+ ComponentNames.BoundingBox
+ );
+
+ boundingBox.center.x = Math.min(
+ this.screenWidth - boundingBox.dimension.width / 2,
+ Math.max(boundingBox.dimension.width / 2, boundingBox.center.x)
+ );
+ });
+ }
+}
diff --git a/client/lib/systems/index.ts b/client/lib/systems/index.ts
new file mode 100644
index 0000000..6cb6f35
--- /dev/null
+++ b/client/lib/systems/index.ts
@@ -0,0 +1,8 @@
+export * from "./names";
+export * from "./System";
+export * from "./Render";
+export * from "./Physics";
+export * from "./Input";
+export * from "./FacingDirection";
+export * from "./Collision";
+export * from "./WallBounds";
diff --git a/client/lib/systems/names.ts b/client/lib/systems/names.ts
new file mode 100644
index 0000000..23f31fc
--- /dev/null
+++ b/client/lib/systems/names.ts
@@ -0,0 +1,8 @@
+export namespace SystemNames {
+ export const Render = "Render";
+ export const Physics = "Physics";
+ export const FacingDirection = "FacingDirection";
+ export const Input = "Input";
+ export const Collision = "Collision";
+ export const WallBounds = "WallBounds";
+}
diff --git a/client/lib/utils/dotProduct.ts b/client/lib/utils/dotProduct.ts
new file mode 100644
index 0000000..59f8857
--- /dev/null
+++ b/client/lib/utils/dotProduct.ts
@@ -0,0 +1,4 @@
+import type { Coord2D } from "../interfaces";
+
+export const dotProduct = (vector1: Coord2D, vector2: Coord2D): number =>
+ vector1.x * vector2.x + vector1.y * vector2.y;
diff --git a/client/lib/utils/index.ts b/client/lib/utils/index.ts
new file mode 100644
index 0000000..1f8e2f0
--- /dev/null
+++ b/client/lib/utils/index.ts
@@ -0,0 +1,3 @@
+export * from "./rotateVector";
+export * from "./normalizeVector";
+export * from "./dotProduct";
diff --git a/client/lib/utils/normalizeVector.ts b/client/lib/utils/normalizeVector.ts
new file mode 100644
index 0000000..e6dfd7f
--- /dev/null
+++ b/client/lib/utils/normalizeVector.ts
@@ -0,0 +1,8 @@
+import type { Coord2D } from "../interfaces";
+
+export const normalizeVector = (vector: Coord2D): Coord2D => {
+ const { x, y } = vector;
+ const length = Math.sqrt(x * x + y * y);
+
+ return { x: x / length, y: y / length };
+};
diff --git a/client/lib/utils/rotateVector.ts b/client/lib/utils/rotateVector.ts
new file mode 100644
index 0000000..82bb54d
--- /dev/null
+++ b/client/lib/utils/rotateVector.ts
@@ -0,0 +1,15 @@
+import type { Coord2D } from "../interfaces";
+
+/**
+ * ([[cos(θ), -sin(θ),]) ([x,)
+ * ([sin(θ), cos(θ)] ]) ( y])
+ */
+export const rotateVector = (vector: Coord2D, theta: number): Coord2D => {
+ const rads = (theta * Math.PI) / 180;
+ const [cos, sin] = [Math.cos(rads), Math.sin(rads)];
+
+ return {
+ x: vector.x * cos - vector.y * sin,
+ y: vector.x * sin + vector.y * cos,
+ };
+};
diff --git a/client/package-lock.json b/client/package-lock.json
new file mode 100644
index 0000000..641f566
--- /dev/null
+++ b/client/package-lock.json
@@ -0,0 +1,2455 @@
+{
+ "name": "client",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "client",
+ "version": "0.0.0",
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^2.0.4",
+ "@tsconfig/svelte": "^4.0.1",
+ "eslint": "^8.43.0",
+ "eslint-plugin-svelte": "^2.31.1",
+ "svelte": "^3.59.2",
+ "svelte-check": "^3.3.1",
+ "svelte-routing": "^1.10.0",
+ "tslib": "^2.5.0",
+ "typescript": "^5.0.2",
+ "vite": "^4.3.9"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
+ "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
+ "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
+ "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
+ "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
+ "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
+ "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
+ "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
+ "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
+ "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
+ "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
+ "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
+ "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
+ "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
+ "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
+ "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
+ "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
+ "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
+ "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
+ "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
+ "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
+ "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
+ "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz",
+ "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
+ "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.5.2",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.43.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
+ "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
+ "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+ "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.2.tgz",
+ "integrity": "sha512-ePfcC48ftMKhkT0OFGdOyycYKnnkT6i/buzey+vHRTR/JpQvuPzzhf1PtKqCDQfJRgoPSN2vscXs6gLigx/zGw==",
+ "dev": true,
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^1.0.3",
+ "debug": "^4.3.4",
+ "deepmerge": "^4.3.1",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.0",
+ "svelte-hmr": "^0.15.2",
+ "vitefu": "^0.2.4"
+ },
+ "engines": {
+ "node": "^14.18.0 || >= 16"
+ },
+ "peerDependencies": {
+ "svelte": "^3.54.0 || ^4.0.0",
+ "vite": "^4.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.3.tgz",
+ "integrity": "sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^14.18.0 || >= 16"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^2.2.0",
+ "svelte": "^3.54.0 || ^4.0.0",
+ "vite": "^4.0.0"
+ }
+ },
+ "node_modules/@tsconfig/svelte": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz",
+ "integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
+ "dev": true
+ },
+ "node_modules/@types/pug": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
+ "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+ "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-indent": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
+ "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/es6-promise": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
+ "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
+ "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.17.19",
+ "@esbuild/android-arm64": "0.17.19",
+ "@esbuild/android-x64": "0.17.19",
+ "@esbuild/darwin-arm64": "0.17.19",
+ "@esbuild/darwin-x64": "0.17.19",
+ "@esbuild/freebsd-arm64": "0.17.19",
+ "@esbuild/freebsd-x64": "0.17.19",
+ "@esbuild/linux-arm": "0.17.19",
+ "@esbuild/linux-arm64": "0.17.19",
+ "@esbuild/linux-ia32": "0.17.19",
+ "@esbuild/linux-loong64": "0.17.19",
+ "@esbuild/linux-mips64el": "0.17.19",
+ "@esbuild/linux-ppc64": "0.17.19",
+ "@esbuild/linux-riscv64": "0.17.19",
+ "@esbuild/linux-s390x": "0.17.19",
+ "@esbuild/linux-x64": "0.17.19",
+ "@esbuild/netbsd-x64": "0.17.19",
+ "@esbuild/openbsd-x64": "0.17.19",
+ "@esbuild/sunos-x64": "0.17.19",
+ "@esbuild/win32-arm64": "0.17.19",
+ "@esbuild/win32-ia32": "0.17.19",
+ "@esbuild/win32-x64": "0.17.19"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.43.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
+ "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@eslint/eslintrc": "^2.0.3",
+ "@eslint/js": "8.43.0",
+ "@humanwhocodes/config-array": "^0.11.10",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.0",
+ "eslint-visitor-keys": "^3.4.1",
+ "espree": "^9.5.2",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-svelte": {
+ "version": "2.31.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.31.1.tgz",
+ "integrity": "sha512-08v+DqzHiwIVEbi+266D7+BDhayp9OSqCwa/lHaZlZOlFY0vZLYs/h7SkkUPzA5fTVt8OUJBtvCxFiWEYOvvGg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14",
+ "debug": "^4.3.1",
+ "esutils": "^2.0.3",
+ "known-css-properties": "^0.27.0",
+ "postcss": "^8.4.5",
+ "postcss-load-config": "^3.1.4",
+ "postcss-safe-parser": "^6.0.0",
+ "postcss-selector-parser": "^6.0.11",
+ "semver": "^7.5.3",
+ "svelte-eslint-parser": "^0.31.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0-0",
+ "svelte": "^3.37.0 || ^4.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
+ "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.5.2",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
+ "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flat-cache/node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/known-css-properties": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz",
+ "integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==",
+ "dev": true
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
+ "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.24",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
+ "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dev": true,
+ "dependencies": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-safe-parser": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
+ "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.3.3"
+ }
+ },
+ "node_modules/postcss-scss": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz",
+ "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
+ }
+ ],
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.19"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.13",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.2.tgz",
+ "integrity": "sha512-VLnkxZMDr3jpxgtmS8pQZ0UvhslmF4ADq/9w4erkctbgjCqLW9oa89fJuXEs4ZmgyoF7Dm8rMDKSS5b5u2hHUg==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/sander": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
+ "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
+ "dev": true,
+ "dependencies": {
+ "es6-promise": "^3.1.2",
+ "graceful-fs": "^4.1.3",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
+ "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sorcery": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
+ "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.14",
+ "buffer-crc32": "^0.2.5",
+ "minimist": "^1.2.0",
+ "sander": "^0.5.0"
+ },
+ "bin": {
+ "sorcery": "bin/sorcery"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/svelte": {
+ "version": "3.59.2",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
+ "integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/svelte-check": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.4.4.tgz",
+ "integrity": "sha512-Uys9+R65cj8TmP8f5UpS7B2xKpNLYNxEWJsA5ZoKcWq/uwvABFF7xS6iPQGLoa7hxz0DS6xU60YFpmq06E4JxA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "chokidar": "^3.4.1",
+ "fast-glob": "^3.2.7",
+ "import-fresh": "^3.2.1",
+ "picocolors": "^1.0.0",
+ "sade": "^1.7.4",
+ "svelte-preprocess": "^5.0.3",
+ "typescript": "^5.0.3"
+ },
+ "bin": {
+ "svelte-check": "bin/svelte-check"
+ },
+ "peerDependencies": {
+ "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0"
+ }
+ },
+ "node_modules/svelte-eslint-parser": {
+ "version": "0.31.0",
+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.31.0.tgz",
+ "integrity": "sha512-/31RpBf/e3YjoFphjsyo3JRyN1r4UalGAGafXrZ6EJK4h4COOO0rbfBoen5byGsXnIJKsrlC1lkEd2Vzpq2IDg==",
+ "dev": true,
+ "dependencies": {
+ "eslint-scope": "^7.0.0",
+ "eslint-visitor-keys": "^3.0.0",
+ "espree": "^9.0.0",
+ "postcss": "^8.4.23",
+ "postcss-scss": "^4.0.6"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "svelte": "^3.37.0 || ^4.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/svelte-hmr": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.2.tgz",
+ "integrity": "sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20 || ^14.13.1 || >= 16"
+ },
+ "peerDependencies": {
+ "svelte": "^3.19.0 || ^4.0.0-next.0"
+ }
+ },
+ "node_modules/svelte-preprocess": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz",
+ "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@types/pug": "^2.0.6",
+ "detect-indent": "^6.1.0",
+ "magic-string": "^0.27.0",
+ "sorcery": "^0.11.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 14.10.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.10.2",
+ "coffeescript": "^2.5.1",
+ "less": "^3.11.3 || ^4.0.0",
+ "postcss": "^7 || ^8",
+ "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0",
+ "pug": "^3.0.0",
+ "sass": "^1.26.8",
+ "stylus": "^0.55.0",
+ "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
+ "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0",
+ "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "coffeescript": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "postcss-load-config": {
+ "optional": true
+ },
+ "pug": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/svelte-preprocess/node_modules/magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/svelte-routing": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/svelte-routing/-/svelte-routing-1.10.0.tgz",
+ "integrity": "sha512-ZYQEj7FK03iVHpzNg1Uk7svlD8siFks0a+IPUxn7ckaEL7H1uQl17gTdYRMjuEw2f7ZtgE77l6WDjmGql14JbA==",
+ "dev": true
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+ "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==",
+ "dev": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
+ "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/vite": {
+ "version": "4.3.9",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
+ "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.17.5",
+ "postcss": "^8.4.23",
+ "rollup": "^3.21.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz",
+ "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==",
+ "dev": true,
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 0000000..1ddd2b8
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-check --tsconfig ./tsconfig.json"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^2.0.4",
+ "@tsconfig/svelte": "^4.0.1",
+ "eslint": "^8.43.0",
+ "eslint-plugin-svelte": "^2.31.1",
+ "svelte": "^3.59.2",
+ "svelte-check": "^3.3.1",
+ "svelte-routing": "^1.10.0",
+ "tslib": "^2.5.0",
+ "typescript": "^5.0.2",
+ "vite": "^4.3.9"
+ }
+}
diff --git a/client/public/assets/coffee_left.png b/client/public/assets/coffee_left.png
new file mode 100644
index 0000000..1c58591
--- /dev/null
+++ b/client/public/assets/coffee_left.png
Binary files differ
diff --git a/client/public/assets/coffee_right.png b/client/public/assets/coffee_right.png
new file mode 100644
index 0000000..fc7f00d
--- /dev/null
+++ b/client/public/assets/coffee_right.png
Binary files differ
diff --git a/client/public/assets/floor_tile_120.png b/client/public/assets/floor_tile_120.png
new file mode 100644
index 0000000..3658e2e
--- /dev/null
+++ b/client/public/assets/floor_tile_120.png
Binary files differ
diff --git a/client/public/assets/floor_tile_160.png b/client/public/assets/floor_tile_160.png
new file mode 100644
index 0000000..b094150
--- /dev/null
+++ b/client/public/assets/floor_tile_160.png
Binary files differ
diff --git a/client/public/assets/floor_tile_40.png b/client/public/assets/floor_tile_40.png
new file mode 100644
index 0000000..3d15fec
--- /dev/null
+++ b/client/public/assets/floor_tile_40.png
Binary files differ
diff --git a/client/public/assets/floor_tile_80.png b/client/public/assets/floor_tile_80.png
new file mode 100644
index 0000000..52b36a2
--- /dev/null
+++ b/client/public/assets/floor_tile_80.png
Binary files differ
diff --git a/client/public/css/colors.css b/client/public/css/colors.css
new file mode 100644
index 0000000..067ddcd
--- /dev/null
+++ b/client/public/css/colors.css
@@ -0,0 +1,45 @@
+:root {
+ --bg: #fbf1c7;
+ --text: #3c3836;
+ --red: #9d0006;
+ --green: #6d790e;
+ --yellow: #b57614;
+ --blue: #075678;
+ --aqua: #57ab7e;
+ --purple: #b16286;
+ --orange: #af3a03;
+}
+
+[data-theme="dark"] {
+ --bg: #282828;
+ --text: #f9f5d7;
+ --red: #fb4934;
+ --green: #b8bb26;
+ --yellow: #fabd2f;
+ --blue: #83a598;
+ --aqua: #8ec07c;
+ --purple: #d3869b;
+ --orange: #d65d0e;
+}
+
+.red {
+ color: var(--red);
+}
+.green {
+ color: var(--green);
+}
+.yellow {
+ color: var(--yellow);
+}
+.blue {
+ color: var(--blue);
+}
+.aqua {
+ color: var(--aqua);
+}
+.purple {
+ color: var(--purple);
+}
+.orange {
+ color: var(--orange);
+}
diff --git a/client/public/css/style.css b/client/public/css/style.css
new file mode 100644
index 0000000..cdfef76
--- /dev/null
+++ b/client/public/css/style.css
@@ -0,0 +1,95 @@
+@import url("./theme.css");
+@import url("./tf.css");
+
+@font-face {
+ font-family: "scientifica";
+ src: url("/fonts/scientifica.ttf");
+}
+
+* {
+ padding: 0;
+ margin: 0;
+ font-family: "scientifica", monospace;
+ transition: background 0.2s ease-in-out;
+ font-smooth: never;
+}
+
+html,
+body {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ background-color: var(--bg);
+ color: var(--text);
+}
+
+a {
+ color: var(--blue);
+}
+a:visited {
+ color: var(--blue);
+}
+
+.main {
+ min-height: 100vh;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ min-width: 600px;
+ width: 45%;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 0;
+}
+
+.header {
+ display: flex;
+ justify-content: space-around;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
+.content {
+ border-top: 1px solid var(--yellow);
+ border-bottom: 1px solid var(--yellow);
+ max-height: 90vh;
+}
+
+.footer {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
+.nav {
+ display: flex;
+}
+
+.title {
+ text-decoration: none;
+}
+.title:hover {
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.centered-game {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-bottom: 1rem;
+ height: 100%;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.centered-game canvas {
+ display: block;
+ max-height: 90%;
+ width: auto;
+ max-width: 100%;
+ border: 1px solid var(--yellow);
+ border-radius: 0.5rem;
+ margin: 0;
+}
diff --git a/client/public/css/tf.css b/client/public/css/tf.css
new file mode 100644
index 0000000..c1acd72
--- /dev/null
+++ b/client/public/css/tf.css
@@ -0,0 +1,33 @@
+.tf {
+ position: relative;
+ z-index: 1;
+
+ transition: color 0.3s ease-out;
+ transition: opacity 0.5s ease-in-out;
+}
+
+.tf:before {
+ background: rgb(162, 254, 254);
+ background: linear-gradient(
+ 90deg,
+ rgba(162, 254, 254, 1) 0%,
+ rgba(249, 187, 250, 1) 25%,
+ rgba(250, 250, 250, 1) 50%,
+ rgba(249, 187, 250, 1) 75%,
+ rgba(162, 254, 254, 1) 100%
+ );
+
+ content: "";
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ position: absolute;
+ z-index: -1;
+ opacity: 0;
+ transition: all 0.5s;
+}
+
+.tf:hover:before {
+ opacity: 1;
+}
diff --git a/client/public/css/theme.css b/client/public/css/theme.css
new file mode 100644
index 0000000..c65b2a8
--- /dev/null
+++ b/client/public/css/theme.css
@@ -0,0 +1,17 @@
+@import url("./colors.css");
+
+.primary {
+ color: var(--aqua);
+}
+.secondary {
+ color: var(--blue);
+}
+.tertiary {
+ color: var(--purple);
+}
+.warning {
+ color: var(--yellow);
+}
+.error {
+ color: var(--red);
+}
diff --git a/client/public/fonts/CozetteVector.ttf b/client/public/fonts/CozetteVector.ttf
new file mode 100644
index 0000000..8dde356
--- /dev/null
+++ b/client/public/fonts/CozetteVector.ttf
Binary files differ
diff --git a/client/public/fonts/scientifica.ttf b/client/public/fonts/scientifica.ttf
new file mode 100644
index 0000000..1753d31
--- /dev/null
+++ b/client/public/fonts/scientifica.ttf
Binary files differ
diff --git a/client/public/img/kangaroo.svg b/client/public/img/kangaroo.svg
new file mode 100644
index 0000000..f23ddec
--- /dev/null
+++ b/client/public/img/kangaroo.svg
@@ -0,0 +1,18 @@
+<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
+ <g id="color">
+ <path fill="#6a462f" d="M15.0063,35.5407l-.64,1.78v.01l-1.03,2.84.02-.09-2.1-.09.26-6.04,1.6-3.64a26.7307,26.7307,0,0,0,1.61,4.61v.01C14.8163,35.1307,14.9064,35.3407,15.0063,35.5407Z"/>
+ <path fill="#6a462f" d="M32.3664,57.0307a2.5121,2.5121,0,0,1-2.33,1.57h-3.12a1.9779,1.9779,0,0,0,1.53-1.18l.15-.37a2.0617,2.0617,0,0,0,.15-1.15l-.67-4.43a2.1283,2.1283,0,0,1,1.17-2.22,8.5517,8.5517,0,0,0,2.6-2.18.99.99,0,0,1-.11.23c-.07.09-.14.18-.22.27a2.9535,2.9535,0,0,1-.3.31c-.05.05-.11.1-.16.15-.47.47.59,3.74.7,4.42l.76,3.43A2.0617,2.0617,0,0,1,32.3664,57.0307Z"/>
+ <path fill="#a57939" d="M64.6786,57.1107l-20.7161.21c-9.2877.0047-11.414-7.36-11.5239-7.68a10.6424,10.6424,0,0,1-.8278-2.179c-.02.02.1576-.181.1276-.161-.07.09-.14.18-.22.27a2.9145,2.9145,0,0,1-.3.31c-.05.05-.11.1-.16.15a3.58,3.58,0,0,1-.49.4,3.409,3.409,0,0,1-.33.24,9.6919,9.6919,0,0,1-.99.58,2.1283,2.1283,0,0,0-1.17,2.22l.67,4.43a2.0609,2.0609,0,0,1-.15,1.15l-.15.37a1.9782,1.9782,0,0,1-1.53,1.18,2.6453,2.6453,0,0,1-.29.02h-13.06a.9034.9034,0,0,1-.68-1.5,1.8072,1.8072,0,0,1,1.2-.6l5.6-.46,3.26-.26a1.57,1.57,0,0,0,1.21-.73,1.4836,1.4836,0,0,0,.27-.94l-.04-.94-.27-6.46a9.1724,9.1724,0,0,1-.6-1,9.0318,9.0318,0,0,1-1.02-4.18v-.05q-.51-.3-.99-.63a20.9138,20.9138,0,0,1-3.85-3.36l-.13.19-1.67,2.47-.52,4.73h-2v-4.73l.02-.09,1.37-5.15v-.01l-.32-2.15a.8939.8939,0,0,1,.05.08.5246.5246,0,0,0-.06-.15,20.2076,20.2076,0,0,1-1.93-6.48c-.06-.4-.1-.8-.13-1.21a1.1019,1.1019,0,0,1-.02-.18,3.3887,3.3887,0,0,1-.02-.45c-.02-.28-.02-.57-.02-.86l-3.5-1.3c-.56-.17-1.49-.5623-1.63-1.3422a1.4662,1.4662,0,0,1,.49-1.33l1.58-.9777v-.7a1.0654,1.0654,0,0,1,.38-.82l.66-.56-1.88-2.5a.3811.3811,0,0,1,.43-.59l2.25.76,2.31,1.46,2.5-1.31a3.7236,3.7236,0,0,1,1.76-.43h.66a.48.48,0,0,1,.28.87l-2.91,2.14a1.781,1.781,0,0,0-.68,1.91c.08.29.16.58.26.86a8.01,8.01,0,0,0,6.1,5.14,20.78,20.78,0,0,1,6.01,2.18,21.034,21.034,0,0,1,6.7,5.81v.01c.1.12.19.27.29.41,0,.01.01.01.01.02a11.3217,11.3217,0,0,1,1.13,2.22,12.2135,12.2135,0,0,1,.75,4.8c-.01.14-.01.27-.01.39a20.3172,20.3172,0,0,0,.3,3.52,19.4651,19.4651,0,0,0,1.03,3.81,3.3983,3.3983,0,0,0,.13.34c.06.19.15.37.23.56l.01.02a5.546,5.546,0,0,0,1.04,1.87,5.3627,5.3627,0,0,0,2.65,1.76,36.5639,36.5639,0,0,0,4.51.81l18.51,2.78C64.9186,56.1607,64.9684,57.1107,64.6786,57.1107Z"/>
+ </g>
+ <g id="line">
+ <line x1="34.0489" x2="34.0489" y1="32.7943" y2="32.8034" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
+ <line x1="14.725" x2="14.3664" y1="35.105" y2="37.3322" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M22.4963,41.5007c-.34-.2-.68-.41-1-.62a18.3131,18.3131,0,0,1-3.97-3.18"/>
+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M12.2763,23.5207c0,.29,0,.58.02.86,0,.38.03.75.06,1.12.02.24.04.48.07.72a26.0614,26.0614,0,0,0,.69,4.09,26.7476,26.7476,0,0,0,1.61,4.61v.01c.09.2.18.41.28.61"/>
+ <polyline fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="18.836 30.821 19.426 34.881 17.656 37.511 17.526 37.701 15.856 40.171 15.336 44.901 13.336 44.901 13.336 40.171 14.366 37.331 14.366 37.321 15.006 35.541 15.756 33.491 16.036 30.731"/>
+ <polyline fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="13.359 40.081 11.259 39.991 11.519 33.951 13.119 30.311"/>
+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M26.6563,33.9107a9.0823,9.0823,0,0,0-4.16,7.59v.05a9.0307,9.0307,0,0,0,1.02,4.18,9.1243,9.1243,0,0,0,.6,1l.27,6.46.04.94a1.483,1.483,0,0,1-.27.94,1.57,1.57,0,0,1-1.21.73l-3.26.26-5.6.46a1.8069,1.8069,0,0,0-1.2.6.9034.9034,0,0,0,.68,1.5h13.06a2.6417,2.6417,0,0,0,.29-.02,1.9781,1.9781,0,0,0,1.53-1.18l.15-.37a2.0609,2.0609,0,0,0,.15-1.15l-.67-4.43a2.1282,2.1282,0,0,1,1.17-2.22,8.5511,8.5511,0,0,0,2.6-2.18c.01-.02.02-.03.03-.05a3.2588,3.2588,0,0,0,.72-2.07"/>
+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.2281,22.2784a1.5987,1.5987,0,0,1-1.08-1.4,1.4665,1.4665,0,0,1,.49-1.33l1.58-.9777v-.7a1.0654,1.0654,0,0,1,.38-.82l.66-.56-1.88-2.5a.3811.3811,0,0,1,.43-.59l2.25.76,2.31,1.46,2.5-1.31a3.7236,3.7236,0,0,1,1.76-.43h.66a.48.48,0,0,1,.28.87l-2.91,2.14a1.7813,1.7813,0,0,0-.68,1.91c.08.29.16.58.26.86a8.01,8.01,0,0,0,6.1,5.14,20.78,20.78,0,0,1,6.01,2.18,21.034,21.034,0,0,1,6.7,5.81v.01c.1.12.19.27.29.41,0,.01.01.01.01.02a11.3212,11.3212,0,0,1,1.13,2.22,12.21,12.21,0,0,1,.75,4.8c-.01.14-.01.27-.01.39a20.3073,20.3073,0,0,0,.3,3.52,19.4649,19.4649,0,0,0,1.03,3.81,3.4226,3.4226,0,0,0,.13.34,4.4066,4.4066,0,0,0,.23.56l.01.02a5.546,5.546,0,0,0,1.04,1.87,5.3634,5.3634,0,0,0,2.65,1.76,36.5639,36.5639,0,0,0,4.51.81l18.51,2.78"/>
+ <path fill="none" stroke="#000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M31.0585,48.0307c-.47.47.59,3.74.7,4.42l.76,3.43a2.0609,2.0609,0,0,1-.15,1.15l-.15.37a1.977,1.977,0,0,1-1.82,1.2h-3.48"/>
+ </g>
+</svg>
diff --git a/client/src/App.svelte b/client/src/App.svelte
new file mode 100644
index 0000000..e76c187
--- /dev/null
+++ b/client/src/App.svelte
@@ -0,0 +1,31 @@
+<script>
+ import { Router, Link, Route } from "svelte-routing";
+ import Home from "./routes/Home.svelte";
+
+ export let url = "/";
+</script>
+
+<Router {url}>
+ <div class="main">
+ <div class="header">
+ <div class="nav">
+ <h1>jumpstorm</h1>
+ </div>
+ </div>
+ <div class="content">
+ <Route path="/"><Home /></Route>
+ </div>
+ <div class="footer">
+ <span>
+ built by
+ <a href="https://github.com/simponic" target="_blank" class="tf">
+ simponic
+ </a>
+ </span>
+ <span>
+ | inspired by
+ <a href="https://www.youtube.com/watch?v=6qBcG31eiJY">carykh</a>
+ </span>
+ </div>
+ </div>
+</Router>
diff --git a/client/src/components/GameCanvas.svelte b/client/src/components/GameCanvas.svelte
new file mode 100644
index 0000000..766a08a
--- /dev/null
+++ b/client/src/components/GameCanvas.svelte
@@ -0,0 +1,28 @@
+<script lang="ts">
+ import { onMount } from "svelte";
+ import { Game } from "../../lib/Game";
+ import { Render } from "../../lib/systems";
+ import { Floor } from "../../lib/entities";
+ import { loadAssets } from "../../lib/config";
+ import { JumpStorm } from "../../lib/JumpStorm";
+
+ let canvas: HTMLCanvasElement;
+ let ctx: CanvasRenderingContext2D;
+
+ export let width: number;
+ export let height: number;
+
+ let jumpStorm: JumpStorm;
+
+ onMount(() => {
+ ctx = canvas.getContext("2d");
+ ctx.imageSmoothingEnabled = false;
+
+ loadAssets().then(() => {
+ jumpStorm = new JumpStorm(ctx);
+ jumpStorm.play();
+ });
+ });
+</script>
+
+<canvas bind:this={canvas} {width} {height} />
diff --git a/client/src/components/LeaderBoard.svelte b/client/src/components/LeaderBoard.svelte
new file mode 100644
index 0000000..d6e4ce3
--- /dev/null
+++ b/client/src/components/LeaderBoard.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+ import { type LeaderBoardEntry } from "../../lib/interfaces";
+ import LeaderBoardCard from "./LeaderBoardCard.svelte";
+
+ const MAX_ENTRIES = 8;
+
+ export let entries: LeaderBoardEntry[] = [];
+</script>
+
+<div class="leaderboard">
+ {#each entries
+ .sort((a, b) => b.score - a.score)
+ .slice(0, MAX_ENTRIES) as entry}
+ <LeaderBoardCard {entry} />
+ {/each}
+</div>
+
+<style>
+ .leaderboard {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ grid-template-rows: repeat(2, 1fr);
+ gap: 10px;
+ }
+</style>
diff --git a/client/src/components/LeaderBoardCard.svelte b/client/src/components/LeaderBoardCard.svelte
new file mode 100644
index 0000000..446d734
--- /dev/null
+++ b/client/src/components/LeaderBoardCard.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { type LeaderBoardEntry } from "../../lib/interfaces";
+
+ export let entry: LeaderBoardEntry = {
+ name: "simponic",
+ score: 100,
+ };
+</script>
+
+<div class="entry">
+ <span class="name">{entry.name}</span>
+ <span class="score">{entry.score}</span>
+</div>
+
+<style>
+ .entry {
+ display: flex;
+ justify-content: space-between;
+ }
+</style>
diff --git a/client/src/main.ts b/client/src/main.ts
new file mode 100644
index 0000000..5332616
--- /dev/null
+++ b/client/src/main.ts
@@ -0,0 +1,7 @@
+import App from "./App.svelte";
+
+const app = new App({
+ target: document.getElementById("app"),
+});
+
+export default app;
diff --git a/client/src/routes/Home.svelte b/client/src/routes/Home.svelte
new file mode 100644
index 0000000..935ed69
--- /dev/null
+++ b/client/src/routes/Home.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+ import GameCanvas from "../components/GameCanvas.svelte";
+ import LeaderBoard from "../components/LeaderBoard.svelte";
+
+ let width: number = 600;
+ let height: number = 800;
+</script>
+
+<div class="centered-game">
+ <GameCanvas {width} {height} />
+ <LeaderBoard />
+</div>
diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/client/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+/// <reference types="svelte" />
+/// <reference types="vite/client" />
diff --git a/client/svelte.config.js b/client/svelte.config.js
new file mode 100644
index 0000000..b0683fd
--- /dev/null
+++ b/client/svelte.config.js
@@ -0,0 +1,7 @@
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
+
+export default {
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+}
diff --git a/client/tsconfig.json b/client/tsconfig.json
new file mode 100644
index 0000000..eabe862
--- /dev/null
+++ b/client/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "resolveJsonModule": true,
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable checkJs if you'd like to use dynamic types in JS.
+ * Note that setting allowJs false does not prevent the use
+ * of JS in `.svelte` files.
+ */
+ "allowJs": true,
+ "checkJs": true,
+ "isolatedModules": true
+ },
+ "include": [
+ "lib/**/*.d.ts",
+ "lib/**/*.ts",
+ "lib/**/*.js",
+ "src/**/*.d.ts",
+ "src/**/*.ts",
+ "src/**/*.js",
+ "src/**/*.svelte"
+ ],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json
new file mode 100644
index 0000000..494bfe0
--- /dev/null
+++ b/client/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler"
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/client/vite.config.ts b/client/vite.config.ts
new file mode 100644
index 0000000..d701969
--- /dev/null
+++ b/client/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import { svelte } from '@sveltejs/vite-plugin-svelte'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [svelte()],
+})